2025-05-29 15:03:59 +08:00

296 lines
8.2 KiB
Vue
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="upload-file">
<el-upload
multiple
:action="uploadFileUrl"
:before-upload="handleBeforeUpload"
:file-list="fileList"
:limit="limit"
:on-error="handleUploadError"
:on-exceed="handleExceed"
:on-success="handleUploadSuccess"
:on-remove="handleFileRemove"
:show-file-list="true"
:headers="headers"
class="upload-file-uploader"
ref="fileUpload"
>
<!-- 上传按钮 -->
<el-button type="primary">选取文件</el-button>
<!-- 上传提示 -->
<template #tip>
<div class="el-upload__tip" v-if="showTip">
请上传
<template v-if="fileSize"> 大小不超过 <b style="color: #f56c6c">{{ fileSize }}MB</b> </template>
<template v-if="fileType"> 格式为 <b style="color: #f56c6c">{{ fileType.join("/") }}</b> </template>
的文件
</div>
</template>
</el-upload>
<!-- 自定义文件列表可选取决于是否需要自定义文件列表 -->
<template v-if="false">
<transition-group class="upload-file-list el-upload-list el-upload-list--text" name="el-fade-in-linear" tag="ul">
<li :key="file.uid" class="el-upload-list__item ele-upload-list__item-content" v-for="(file, index) in fileList">
<el-button link :href="`${baseUrl}${file.url}`" target="_blank">
<span class="el-icon-document"> {{ getFileName(file.name) }} </span>
</el-button>
<div class="ele-upload-list__item-content-action">
<el-button link @click="handleDelete(index)" type="danger">删除</el-button>
</div>
</li>
</transition-group>
</template>
</div>
</template>
<script setup>
import { getToken } from "@/utils/auth";
import { ref, computed, watch, getCurrentInstance } from 'vue';
const props = defineProps({
modelValue: [String, Object, Array],
// 数量限制
limit: {
type: Number,
default: 5,
},
// 大小限制(MB)
fileSize: {
type: Number,
default: 100,
},
// 文件类型, 例如['png', 'jpg', 'jpeg']
fileType: {
type: Array,
default: () => ["doc", "xls", "ppt", "txt", "pdf"],
},
// 是否显示提示
isShowTip: {
type: Boolean,
default: true
},
// 新增:自定义上传地址
uploadFileUrl: {
type: String,
default: ''
}
});
const { proxy } = getCurrentInstance();
const emit = defineEmits();
const number = ref(0);
const uploadList = ref([]);
const baseUrl = import.meta.env.VITE_APP_BASE_API;
const uploadFileUrl = computed(() => {
// 优先使用外部传入的 uploadFileUrl 属性
if (props.uploadFileUrl) {
return props.uploadFileUrl;
}
return baseUrl + "/common/upload"; // 修改为通用上传接口
});
const headers = ref({ Authorization: "Bearer " + getToken() });
const fileList = ref([]);
const showTip = computed(
() => props.isShowTip && (props.fileType || props.fileSize)
);
watch(() => props.modelValue, val => {
if (val) {
let temp = 1;
try {
// 首先将值转为数组
let list = [];
if (typeof val === 'string') {
// 尝试解析为JSON
try {
const parsedValue = JSON.parse(val);
list = Array.isArray(parsedValue) ? parsedValue : [parsedValue];
} catch (e) {
// 不是有效的JSON按逗号分隔处理
list = val.split(',');
}
} else if (Array.isArray(val)) {
list = val;
} else {
list = [val];
}
// 然后将数组转为对象数组
fileList.value = list.map(item => {
if (typeof item === "string") {
item = { name: getFileName(item), url: item };
}
item.uid = item.uid || new Date().getTime() + temp++;
return item;
});
} catch (error) {
console.error('解析文件列表失败:', error);
fileList.value = [];
}
} else {
fileList.value = [];
}
}, { deep: true, immediate: true });
// 上传前校检格式和大小
function handleBeforeUpload(file) {
// 校检文件类型
if (props.fileType.length) {
const fileName = file.name.split('.');
const fileExt = fileName[fileName.length - 1].toLowerCase();
const isTypeOk = props.fileType.some(type =>
type.toLowerCase() === fileExt
);
if (!isTypeOk) {
proxy.$modal.msgError(`文件格式不正确, 请上传${props.fileType.join("/")}格式文件!`);
return false;
}
}
// 校检文件大小
if (props.fileSize) {
const isLt = file.size / 1024 / 1024 < props.fileSize;
if (!isLt) {
proxy.$modal.msgError(`上传文件大小不能超过 ${props.fileSize} MB!`);
return false;
}
}
proxy.$modal.loading("正在上传文件,请稍候...");
number.value++;
return true;
}
// 文件个数超出
function handleExceed() {
proxy.$modal.msgError(`上传文件数量不能超过 ${props.limit} 个!`);
}
// 上传失败
function handleUploadError(err) {
number.value--;
proxy.$modal.closeLoading();
proxy.$modal.msgError("上传文件失败: " + (err.message || '未知错误'));
}
// El-Upload自带的文件移除事件
function handleFileRemove(file) {
handleDelete(fileList.value.findIndex(item => item.uid === file.uid));
}
// 上传成功回调
function handleUploadSuccess(res, file) {
if (res.code === 200) {
uploadList.value.push({
name: res.fileName || res.originalFilename || file.name,
url: res.url
});
uploadedSuccessfully();
} else {
number.value--;
proxy.$modal.closeLoading();
proxy.$modal.msgError(res.msg || '上传失败');
proxy.$refs.fileUpload.handleRemove(file);
uploadedSuccessfully();
}
}
// 删除文件
function handleDelete(index) {
if (index < 0 || index >= fileList.value.length) {
return;
}
// 获取要删除的文件路径
const file = fileList.value[index];
if (!file || !file.url) {
return;
}
// 确认删除
proxy.$modal.confirm('确定要删除该文件吗?').then(() => {
// 显示加载中
proxy.$modal.loading("删除文件中,请稍候...");
// 如果存在删除附件的API调用它
if (window.deleteAttachment) {
window.deleteAttachment(file.url).then(() => {
// 从前端列表中移除
fileList.value.splice(index, 1);
emit("update:modelValue", listToString(fileList.value));
proxy.$modal.closeLoading();
proxy.$modal.msgSuccess("删除成功");
}).catch(error => {
console.error("删除文件失败:", error);
proxy.$modal.closeLoading();
proxy.$modal.msgError("删除失败: " + (error.message || "未知错误"));
});
} else {
// 仅从前端列表中移除
fileList.value.splice(index, 1);
emit("update:modelValue", listToString(fileList.value));
proxy.$modal.closeLoading();
proxy.$modal.msgSuccess("文件已从列表中移除");
}
}).catch(() => {
// 取消删除
});
}
// 上传结束处理
function uploadedSuccessfully() {
if (number.value > 0 && uploadList.value.length === number.value) {
fileList.value = fileList.value.filter(f => f.url !== undefined).concat(uploadList.value);
uploadList.value = [];
number.value = 0;
emit("update:modelValue", listToString(fileList.value));
proxy.$modal.closeLoading();
}
}
// 获取文件名称
function getFileName(name) {
if (!name) return '';
if (name.lastIndexOf("/") > -1) {
return name.slice(name.lastIndexOf("/") + 1);
} else {
return name;
}
}
// 对象转成指定字符串分隔
function listToString(list, separator) {
// 构建JSON数组
const fileDataArray = list.map(item => ({
name: item.name || getFileName(item.url),
url: item.url
}));
// 返回JSON字符串
return JSON.stringify(fileDataArray);
}
</script>
<style scoped lang="scss">
.upload-file-uploader {
margin-bottom: 5px;
}
.upload-file-list .el-upload-list__item {
border: 1px solid #e4e7ed;
line-height: 2;
margin-bottom: 10px;
position: relative;
}
.upload-file-list .ele-upload-list__item-content {
display: flex;
justify-content: space-between;
align-items: center;
color: inherit;
}
.ele-upload-list__item-content-action .el-link {
margin-right: 10px;
}
</style>