111 lines
3.1 KiB
Vue
Raw Normal View History

2025-06-12 17:06:06 +08:00
<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"
2025-06-12 17:06:06 +08:00
>
<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
2025-06-12 17:06:06 +08:00
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 : [];
2025-06-12 17:06:06 +08:00
},
set(val) {
// 内部操作后:如果单文件场景,传字符串;多文件场景,传数组
if (props.limit === 1) {
emit('update:modelValue', val.length ? val[0] : '');
} else {
emit('update:modelValue', val);
}
2025-06-12 17:06:06 +08:00
},
});
// 3. fileList & previewList 都基于 selectedFiles
const fileList = computed(() =>
selectedFiles.value.map((path, idx) => ({
name: `file_${idx}`,
2025-06-12 17:06:06 +08:00
url: props.ossUrl + path,
uid: `${idx}`,
}))
);
2025-06-12 17:06:06 +08:00
const previewShow = ref(false);
const previewIndex = ref(0);
const previewList = computed(() => fileList.value.map((f) => f.url));
2025-06-12 17:06:06 +08:00
// 4. 上传 & 移除
2025-06-12 17:06:06 +08:00
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];
2025-06-12 17:06:06 +08:00
}
function handleRemove(file) {
const rel = file.url.replace(props.ossUrl, '');
selectedFiles.value = selectedFiles.value.filter((p) => p !== rel);
2025-06-12 17:06:06 +08:00
}
// 5. 预览
2025-06-12 17:06:06 +08:00
function handlePreview(file) {
const idx = fileList.value.findIndex((item) => item.uid === file.uid);
if (idx >= 0) {
2025-06-12 17:06:06 +08:00
previewIndex.value = idx;
previewShow.value = true;
}
}
</script>
<style scoped>
.file-uploader {
display: flex;
flex-wrap: wrap;
}
.file-uploader__upload {
margin-right: 16px;
}
</style>