145 lines
3.0 KiB
Vue
Raw Normal View History

<template>
<view class="files-wrap">
<view class="files-label" :class="required ? 'form-cell--required' : ''">{{ name }}</view>
<view class="grid">
<view v-for="(f, idx) in files" :key="idx" class="item" @click="preview(idx)">
<image class="thumb" :src="f.url" mode="aspectFill" />
<view class="remove" @click.stop="remove(idx)">×</view>
</view>
<view class="add" @click="add">
<view class="plus">+</view>
</view>
</view>
</view>
</template>
<script setup>
import { computed } from 'vue';
import { chooseAndUploadImage } from '@/utils/file';
import { toast } from '@/utils/widget';
const emits = defineEmits(['change']);
const props = defineProps({
form: { type: Object, default: () => ({}) },
name: { default: '' },
required: { type: Boolean, default: false },
title: { default: '' },
disableChange: { type: Boolean, default: false },
max: { type: [Number, String], default: 9 },
});
const maxCount = computed(() => {
const n = Number(props.max);
return Number.isFinite(n) && n > 0 ? Math.min(n, 9) : 9;
});
const files = computed(() => {
const v = props.form?.[props.title];
if (Array.isArray(v)) {
return v
.map((i) => {
if (typeof i === 'string') return { url: i };
if (i && typeof i === 'object' && i.url) return { url: String(i.url) };
return null;
})
.filter(Boolean);
}
if (typeof v === 'string' && v) return [{ url: v }];
return [];
});
function emitValue(list) {
emits('change', { title: props.title, value: list });
}
function preview(idx) {
const urls = files.value.map((i) => i.url);
if (!urls.length) return;
uni.previewImage({ urls, current: urls[idx] });
}
function remove(idx) {
if (props.disableChange) return;
const next = files.value.filter((_, i) => i !== idx);
emitValue(next);
}
async function add() {
if (props.disableChange) return;
if (files.value.length >= maxCount.value) {
toast(`最多上传${maxCount.value}`);
return;
}
const url = await chooseAndUploadImage({ count: 1 });
if (!url) return;
emitValue([...files.value, { url }]);
}
</script>
<style lang="scss" scoped>
@import '../cell-style.css';
.files-wrap {
padding: 24rpx 30rpx;
border-bottom: 1px solid #eee;
background: #fff;
}
.files-label {
font-size: 28rpx;
line-height: 42rpx;
}
.grid {
margin-top: 18rpx;
display: flex;
flex-wrap: wrap;
gap: 18rpx;
}
.item {
width: 160rpx;
height: 160rpx;
border-radius: 10rpx;
overflow: hidden;
position: relative;
background: #f6f6f6;
}
.thumb {
width: 100%;
height: 100%;
}
.remove {
position: absolute;
top: 6rpx;
right: 6rpx;
width: 36rpx;
height: 36rpx;
line-height: 36rpx;
text-align: center;
background: rgba(0, 0, 0, 0.55);
color: #fff;
border-radius: 18rpx;
font-size: 28rpx;
}
.add {
width: 160rpx;
height: 160rpx;
border-radius: 10rpx;
border: 1px dashed #cfcfcf;
display: flex;
align-items: center;
justify-content: center;
color: #999;
}
.plus {
font-size: 56rpx;
line-height: 56rpx;
}
</style>