basic-upload.vue——基本上传组件
<template>
<div class="basic-upload-wrap">
<el-upload
ref="uploadRef"
:file-list="fileList"
:accept="accept"
@update:file-list="(data) => emits('update:file-list', data)"
:http-request="uploadFile"
:on-exceed="handleExceed"
:on-progress="handleUpdate"
:on-success="handleUpdate"
:on-remove="preDelete"
v-bind="$attrs"
>
<slot>
<div class="upload-btn-wrapper">
<el-button class="upload-btn" type="primary">
<svg-icon name="cloud-upload" />上传文件
</el-button>
<div class="prompt" v-if="accept">只支持{{ accept }}的格式文件</div>
</div>
</slot>
</el-upload>
</div>
</template>
<script setup>
import api from "@/api/documentInfo";
const { uploadFiles, deleteFiles } = api;
const emits = defineEmits(["update:file-list"]);
const props = defineProps({
fileList: {
type: Array,
default: () => [],
},
accept: {
type: String,
},
});
const uploadRef = ref();
const pendingDels = [];
const controllerMap = {};
async function uploadFile(options) {
const { file, onProgress, onSuccess, onError } = options;
const formData = new FormData();
formData.append("files", file);
const controller = new AbortController();
controllerMap[file.uid] = controller;
const { code, message, data } = await uploadFiles(formData, {
onUploadProgress: (event) => {
handleProgress(event, onProgress);
},
signal: controller.signal,
});
if (code !== "0") {
onError(message || "上传失败");
}
onSuccess(data);
}
let timer;
function handleProgress(event, onProgress) {
let complete = (event.loaded / event.total) * 100;
if (complete < 90) {
event.percent = complete;
onProgress(event);
return;
}
if (timer) return;
timer = window.setInterval(() => {
complete += (100 - complete) * 0.2;
if (complete > 99 && timer) {
window.clearInterval(timer);
timer = null;
}
event.percent = complete;
onProgress(event);
}, 500);
}
function handleExceed(files, uploadFiles) {
ElMessage.warning(`最多可上传${uploadFiles.length}个附件`);
}
function handleUpdate(response, uploadFile, uploadFiles) {
const res = uploadFiles.map((item) => {
return item.response ? item.response : item;
});
emits("update:file-list", res);
}
const preDelete = (file) => {
controllerMap[file.uid]?.abort?.();
delete controllerMap[file.uid];
pendingDels.push(file);
const updatedFileList = props.fileList.filter((f) => f.uid !== file.uid);
emits("update:file-list", updatedFileList);
};
const confirmDelete = async () => {
if (pendingDels.length > 0) {
const ids = [
...new Set(pendingDels.map((item) => item.id).filter((v) => v)),
];
if (!ids.length) return;
await deleteFiles(ids);
pendingDels.length = 0;
}
};
const deleteUnBind = () => {
pendingDels.push(...props.fileList.filter((v) => !v.kbId));
confirmDelete();
};
onMounted(() => {
if (uploadRef.value) {
uploadRef.value.preDelete = preDelete;
uploadRef.value.confirmDelete = confirmDelete;
uploadRef.value.deleteUnBind = deleteUnBind;
}
});
defineExpose({
upload: uploadRef,
});
</script>
<style lang="scss" scoped>
.upload-btn-wrapper {
display: flex;
align-items: center;
.prompt {
display: inline-block;
color: #88c4f9;
font-size: 12px;
margin-left: 8px;
line-height: 1.2;
}
}
</style>
list-upload.vue——文件上传列表
<template>
<div class="list-upload-wrap">
<basic-upload
ref="uploadRef"
:show-file-list="false"
:file-list="fileList"
@update:file-list="(data) => emits('update:file-list', data)"
:accept="accept"
v-bind="$attrs"
>
</basic-upload>
<ul class="file-list-wrap" v-if="!!fileList.length">
<li v-for="item in fileList" :key="item.id">
<div class="item-content">
<span class="file-name" @click="handlePreview(item)">
{{ item.documentName || item.name }}
</span>
<svg-icon name="close" class="close" @click.stop="handleDel(item)" />
</div>
<el-progress
v-if="item.percentage !== undefined && item.percentage !== 100"
:percentage="item.percentage"
:format="(percentage) => ''"
:text-inside="true"
:stroke-width="4"
class="el-upload-list__item-progress"
>
</el-progress>
</li>
</ul>
</div>
</template>
<script setup>
import { handlePreview } from "./utils.js";
const emits = defineEmits(["update:file-list"]);
defineProps({
fileList: {
type: Array,
default: () => [],
},
accept: {
type: String,
},
});
const uploadRef = ref();
const handleDel = (val) => {
uploadRef.value.upload?.preDelete?.(val);
};
const upload = computed(() => uploadRef.value?.upload);
defineExpose({
upload,
});
</script>
<style lang="scss" scoped>
.list-upload-wrap {
width: 100%;
}
.file-list-wrap {
$hover-color: #0093ff;
list-style-type: none;
padding: 0;
margin-top: 16px;
max-height: 30vh;
overflow-y: auto;
padding-right: 6px;
li {
margin-bottom: 20px;
line-height: 1.2;
&:last-child {
margin-bottom: 0;
}
.item-content {
display: flex;
align-items: center;
justify-content: space-between;
}
.file-name {
font-family: "Microsoft YaHei", "Microsoft YaHei";
font-weight: 400;
font-size: 14px;
color: #ffffff;
text-align: left;
font-style: normal;
text-transform: none;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
cursor: pointer;
&:hover {
color: $hover-color;
}
}
.close {
font-size: 19px;
flex: 19px 0 0;
cursor: pointer;
&:hover {
color: $hover-color;
}
}
.el-upload-list__item-progress {
width: 100%;
position: relative;
top: 2px;
margin-bottom: 12px;
}
}
}
</style>
utils.js——预览跳转
import router from "@/router";
// 预览文件
export const handlePreview = (file) => {
if (!file) {
return;
}
const name = file.documentName;
const url = (process.env.VUE_APP_FILE_PATH || "") + file.documentUrl;
if (!name || !url || !router) return;
const lowerCaseName = name.toLowerCase();
const suffixName = "." + lowerCaseName.split(".").pop();
// 1. 处理文本和文档类型
if ([".txt", ".doc", ".docx", ".pdf"].includes(suffixName)) {
let routeUrl = router.resolve({
name: "office-preview",
query: { url, name },
});
window.open(routeUrl.href, "_blank");
return;
}
// 2. 处理图片和视频类型
if (
[".png", ".jpg", ".jpeg", ".bmp", ".gif", ".mp4"].includes(
suffixName
)
) {
window.open(url, "_blank");
return;
}
// 3. 提示不支持的文件类型
ElMessage({
message: "暂不支持该格式文件预览",
type: "warning",
});
};
Use
<template>
<el-form-item label="应急预案:" prop="documentList">
<list-upload ref="uploadRef" v-model:file-list="ruleForm.documentList"
:accept="'.doc, .docx, .txt, .pdf, .png, .jpg, .jpeg, .gif'" :limit="20"></list-upload>
</el-form-item>
</template>
<script setup>
const uploadRef = ref();
function close() {
uploadRef.value.upload?.deleteUnBind();
dialogVisible.value = false;
}
async function submit() {
const formData = {
id: ruleForm.value.id,
documentList: ruleForm.value.documentList,
};
await update(formData);
await uploadRef.value.upload?.confirmDelete();
ElMessage.success("操作成功");
}
</script>