111 lines
3.1 KiB
Vue
111 lines
3.1 KiB
Vue
<template>
|
||
<div class="file-uploader">
|
||
<el-upload
|
||
class="file-uploader__upload"
|
||
:http-request="customUploadRequest"
|
||
:on-success="handleUploadSuccess"
|
||
:on-remove="handleRemove"
|
||
:file-list="fileList"
|
||
list-type="picture-card"
|
||
:limit="limit"
|
||
:show-file-list="true"
|
||
:auto-upload="true"
|
||
:disabled="readonly"
|
||
:accept="accept"
|
||
@preview="handlePreview"
|
||
>
|
||
<el-icon v-if="fileList.length < limit"><Plus /></el-icon>
|
||
</el-upload>
|
||
<el-image-viewer v-if="previewShow" :url-list="previewList" :initial-index="previewIndex" @close="previewShow = false" />
|
||
</div>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, computed } from 'vue';
|
||
import { Plus } from '@element-plus/icons-vue';
|
||
import { CommonUpload } from '@/apis/index';
|
||
|
||
// 1. props & emit
|
||
const props = defineProps({
|
||
modelValue: { type: [Array, String], default: () => [] },
|
||
ossUrl: { type: String, default: 'http://gov-cloud.oss-cn-chengdu.aliyuncs.com/' },
|
||
limit: { type: Number, default: 5 },
|
||
accept: { type: String, default: 'image/*' },
|
||
readonly: { type: Boolean, default: false },
|
||
});
|
||
const emit = defineEmits(['update:modelValue']);
|
||
|
||
// 2. 中间层 computed:统一成数组,写时根据 limit 决定发出数组还是字符串
|
||
const selectedFiles = computed({
|
||
get() {
|
||
// 回显:如果父传字符串且 limit===1,就把它当成长度 1 数组
|
||
if (props.limit === 1 && typeof props.modelValue === 'string' && props.modelValue) {
|
||
return [props.modelValue];
|
||
}
|
||
// 其他情况,确保数组
|
||
return Array.isArray(props.modelValue) ? props.modelValue : [];
|
||
},
|
||
set(val) {
|
||
// 内部操作后:如果单文件场景,传字符串;多文件场景,传数组
|
||
if (props.limit === 1) {
|
||
emit('update:modelValue', val.length ? val[0] : '');
|
||
} else {
|
||
emit('update:modelValue', val);
|
||
}
|
||
},
|
||
});
|
||
|
||
// 3. fileList & previewList 都基于 selectedFiles
|
||
const fileList = computed(() =>
|
||
selectedFiles.value.map((path, idx) => ({
|
||
name: `file_${idx}`,
|
||
url: props.ossUrl + path,
|
||
uid: `${idx}`,
|
||
}))
|
||
);
|
||
const previewShow = ref(false);
|
||
const previewIndex = ref(0);
|
||
const previewList = computed(() => fileList.value.map((f) => f.url));
|
||
|
||
// 4. 上传 & 移除
|
||
const customUploadRequest = async ({ file, onSuccess, onError }) => {
|
||
const formData = new FormData();
|
||
formData.append('file', file);
|
||
try {
|
||
const res = await CommonUpload(formData);
|
||
onSuccess(res, file);
|
||
} catch (err) {
|
||
onError(err);
|
||
}
|
||
};
|
||
function handleUploadSuccess(res) {
|
||
const relative = res.data?.url;
|
||
if (!relative) return;
|
||
// 推入中间层
|
||
selectedFiles.value = [...selectedFiles.value, relative];
|
||
}
|
||
function handleRemove(file) {
|
||
const rel = file.url.replace(props.ossUrl, '');
|
||
selectedFiles.value = selectedFiles.value.filter((p) => p !== rel);
|
||
}
|
||
|
||
// 5. 预览
|
||
function handlePreview(file) {
|
||
const idx = fileList.value.findIndex((item) => item.uid === file.uid);
|
||
if (idx >= 0) {
|
||
previewIndex.value = idx;
|
||
previewShow.value = true;
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style scoped>
|
||
.file-uploader {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
}
|
||
.file-uploader__upload {
|
||
margin-right: 16px;
|
||
}
|
||
</style>
|