145 lines
3.0 KiB
Vue
145 lines
3.0 KiB
Vue
|
|
<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>
|
|||
|
|
|