| | |
| | | v-for="(file, index) in fileList" |
| | | > |
| | | <el-link :href="`${file.url}`" :underline="false" target="_blank"> |
| | | <span class="el-icon-document"> {{ file.bucketFilename }} </span> |
| | | <!-- 取8位 --> |
| | | <span class="el-icon-document"> |
| | | {{ file.bucketFilename.substring(0, 18) }} |
| | | </span> |
| | | </el-link> |
| | | <div class="ele-upload-list__item-content-action"> |
| | | <el-link |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, computed, watch, onMounted, nextTick, getCurrentInstance } from 'vue'; |
| | | import { getToken } from "@/utils/auth"; |
| | | import Sortable from "sortablejs"; |
| | | import { ElMessage } from "element-plus"; |
| | | import axios from "axios"; |
| | | |
| | | // Props 定义 |
| | | const props = defineProps({ |
| | | modelValue: [String, Object, Array], |
| | | // 上传接口地址 |
| | | action: { |
| | | type: String, |
| | | default: "/common/minioUploads", |
| | | }, |
| | | // 上传携带的参数 |
| | | data: { |
| | | type: Object, |
| | | }, |
| | | // 数量限制 |
| | | limit: { |
| | | type: Number, |
| | | default: 5, |
| | | }, |
| | | // 大小限制(MB) |
| | | fileSize: { |
| | | type: Number, |
| | | default: 5, |
| | | }, |
| | | // 文件类型, 例如['png', 'jpg', 'jpeg'] |
| | | action: { type: String, default: "/common/minioUploads" }, |
| | | data: { type: Object }, |
| | | limit: { type: Number, default: 5 }, |
| | | fileSize: { type: Number, default: 5 }, |
| | | fileType: { |
| | | type: Array, |
| | | default: () => ["doc", "docx", "xls", "xlsx", "ppt", "pptx", "txt", "pdf"], |
| | | default: () => ["doc", "docx", "xls", "xlsx", "ppt", "pptx", "txt", "pdf"] |
| | | }, |
| | | // 是否显示提示 |
| | | isShowTip: { |
| | | type: Boolean, |
| | | default: true, |
| | | }, |
| | | // 禁用组件(仅查看文件) |
| | | disabled: { |
| | | type: Boolean, |
| | | default: false, |
| | | }, |
| | | // 拖动排序 |
| | | drag: { |
| | | type: Boolean, |
| | | default: true, |
| | | }, |
| | | isShowTip: { type: Boolean, default: true }, |
| | | disabled: { type: Boolean, default: false }, |
| | | drag: { type: Boolean, default: true }, |
| | | statusType: { type: Number, default: "" }, // 用于区分不同状态的上传 |
| | | }); |
| | | |
| | | // 组件实例和事件 |
| | | const { proxy } = getCurrentInstance(); |
| | | const emit = defineEmits(); |
| | | const emit = defineEmits(['update:modelValue']); |
| | | |
| | | // 响应式数据 |
| | | const number = ref(0); |
| | | const uploadList = ref([]); |
| | | const baseUrl = import.meta.env.VITE_APP_BASE_API; |
| | | const uploadFileUrl = ref(import.meta.env.VITE_APP_BASE_API + props.action); // 上传文件服务器地址 |
| | | const headers = ref({ Authorization: "Bearer " + getToken() }); |
| | | const fileList = ref([]); |
| | | const showTip = computed( |
| | | () => props.isShowTip && (props.fileType || props.fileSize) |
| | | ); |
| | | |
| | | // 计算属性 |
| | | const uploadFileUrl = computed(() => import.meta.env.VITE_APP_BASE_API + props.action); |
| | | const headers = computed(() => ({ Authorization: "Bearer " + getToken() })); |
| | | const showTip = computed(() => props.isShowTip && (props.fileType || props.fileSize)); |
| | | // 初始化和编辑初始化方法 |
| | | const init = () =>{ |
| | | fileList.value = []; |
| | | uploadList.value = []; |
| | | number.value = 0; |
| | | } |
| | | }; |
| | | |
| | | const editInit = (val) => { |
| | | fileList.value = []; |
| | | val.storageBlobDTO.forEach(element => { |
| | | console.log("编辑初始化", element); |
| | | val.storageBlobDTO.forEach((element) => { |
| | | fileList.value.push(element); |
| | | uploadedSuccessfully(); |
| | | }); |
| | | // uploadList.value.push |
| | | }; |
| | | defineExpose({ |
| | | init, |
| | | editInit |
| | | }); |
| | | |
| | | // 暴露方法 |
| | | defineExpose({ init, editInit }); |
| | | |
| | | // 监听 modelValue 变化 |
| | | watch( |
| | | () => props.modelValue, |
| | | (val) => { |
| | | if (val) { |
| | | let temp = 1; |
| | | // 首先将值转为数组 |
| | | const list = Array.isArray(val) ? val : props.modelValue.split(","); |
| | | // 然后将数组转为对象数组 |
| | | fileList.value = list.map((item) => { |
| | | if (typeof item === "string") { |
| | | item = { name: item, url: item }; |
| | |
| | | }); |
| | | } else { |
| | | fileList.value = []; |
| | | return []; |
| | | } |
| | | }, |
| | | { deep: true, immediate: true } |
| | | ); |
| | | |
| | | // 文件上传处理 |
| | | const UploadImage = (param) => { |
| | | const formData = new FormData(); |
| | | formData.append("files", param.file); |
| | | axios |
| | | .post(uploadFileUrl.value, formData, { |
| | | formData.append("type", props.statusType); |
| | | axios.post(uploadFileUrl.value, formData, { |
| | | headers: { |
| | | "Content-Type": "multipart/form-data", |
| | | ...headers.value, |
| | | }, |
| | | onUploadProgress: (progressEvent) => { |
| | | // 进度条更新 |
| | | const percent = Math.round( |
| | | (progressEvent.loaded * 100) / progressEvent.total |
| | | ); |
| | | const percent = Math.round((progressEvent.loaded * 100) / progressEvent.total); |
| | | param.onProgress({ percent }); |
| | | }, |
| | | }) |
| | | .then((response) => { |
| | | if (response.data.code === 200) { |
| | | console.log("上传成功", response.data); |
| | | handleUploadSuccess(response.data, param.file); |
| | | // 更新父组件 |
| | | ElMessage.success("上传成功"); |
| | | emit("update:modelValue", fileList.value); |
| | | } else { |
| | | param.onError(new Error(response.data.msg)); |
| | | ElMessage.error(response.data.msg); |
| | | handleUploadError(); |
| | | } |
| | | }) |
| | | .catch((error) => { |
| | | param.onError(error); |
| | | }); |
| | | }; |
| | | // 获取文件Base64(可选功能,暂时保留) |
| | | const getBase64ByUrl = (url) => { |
| | | return new Promise((resolve, reject) => { |
| | | const xhr = new XMLHttpRequest(); |
| | | xhr.open("GET", url, true); |
| | | xhr.responseType = "blob"; |
| | | xhr.onload = function () { |
| | | if (this.status === 200) { |
| | | const reader = new FileReader(); |
| | | reader.onloadend = () => resolve(reader.result); |
| | | reader.readAsDataURL(this.response); |
| | | } else { |
| | | reject(new Error("Failed to fetch image")); |
| | | } |
| | | }; |
| | | xhr.onerror = () => reject(new Error("Network error")); |
| | | xhr.send(); |
| | | }); |
| | | }; |
| | | |
| | | // 移动端下载图片 |
| | | const downloadImageForMobile = (imgSrc, name) => { |
| | | const image = new Image(); |
| | | image.setAttribute("crossOrigin", "anonymous"); |
| | | image.onload = () => { |
| | | const canvas = document.createElement("canvas"); |
| | | canvas.width = image.width; |
| | | canvas.height = image.height; |
| | | const ctx = canvas.getContext("2d"); |
| | | ctx.drawImage(image, 0, 0, image.width, image.height); |
| | | const url = canvas.toDataURL("image/png"); |
| | | const a = document.createElement("a"); |
| | | a.download = name; |
| | | a.href = url; |
| | | a.click(); |
| | | }; |
| | | image.src = imgSrc; |
| | | }; |
| | | |
| | | // 下载文件 |
| | | function handleDownload(index) { |
| | | const url = fileList.value[index].downloadUrl; |
| | | const handleDownload = (index) => { |
| | | const file = fileList.value[index]; |
| | | const url = file.url; |
| | | if (!url) { |
| | | proxy.$modal.msgError("文件链接不存在,无法下载"); |
| | | return; |
| | | } |
| | | // 读取预览图片的数据 |
| | | const link = document.createElement("a"); |
| | | link.href = url; // 链接地址 |
| | | link.download = fileList.value[index].bucketFilename; // 设置下载文件名 |
| | | link.click(); |
| | | // 移动端特殊处理 |
| | | if (window.navigator.userAgent.match(/(iPhone|iPod|Android|ios)/i)) { |
| | | const fileName = file.bucketFilename || file.key; |
| | | downloadImageForMobile(url, fileName); |
| | | return; |
| | | } |
| | | |
| | | |
| | | // 上传前校检格式和大小 |
| | | function handleBeforeUpload(file) { |
| | | // 校检文件类型 |
| | | // if (props.fileType.length) { |
| | | // const fileName = file.name.split('.') |
| | | // const fileExt = fileName[fileName.length - 1] |
| | | // const isTypeOk = props.fileType.indexOf(fileExt) >= 0 |
| | | // if (!isTypeOk) { |
| | | // proxy.$modal.msgError(`文件格式不正确,请上传${props.fileType.join("/")}格式文件!`) |
| | | // return false |
| | | // } |
| | | // } |
| | | // 校检文件名是否包含特殊字符 |
| | | // 桌面端下载 |
| | | const link = document.createElement("a"); |
| | | link.href = url; |
| | | link.download = file.bucketFilename || file.key; |
| | | link.click(); |
| | | }; |
| | | // 上传前校验 |
| | | const handleBeforeUpload = (file) => { |
| | | // 校验文件名特殊字符 |
| | | if (file.name.includes(",")) { |
| | | proxy.$modal.msgError("文件名不正确,不能包含英文逗号!"); |
| | | return false; |
| | | } |
| | | // 校检文件大小 |
| | | |
| | | // 校验文件大小 |
| | | if (props.fileSize) { |
| | | const isLt = file.size / 1024 / 1024 < props.fileSize; |
| | | if (!isLt) { |
| | |
| | | return false; |
| | | } |
| | | } |
| | | |
| | | proxy.$modal.loading("正在上传文件,请稍候..."); |
| | | number.value++; |
| | | return true; |
| | | } |
| | | }; |
| | | |
| | | // 文件个数超出 |
| | | function handleExceed() { |
| | | // 文件个数超出限制 |
| | | const handleExceed = () => { |
| | | proxy.$modal.msgError(`上传文件数量不能超过 ${props.limit} 个!`); |
| | | } |
| | | }; |
| | | |
| | | // 上传失败 |
| | | function handleUploadError(err) { |
| | | proxy.$modal.msgError("上传文件失败"); |
| | | // 上传失败处理 |
| | | const handleUploadError = () => { |
| | | ElMessage.error("上传文件失败"); |
| | | proxy.$modal.closeLoading(); |
| | | } |
| | | }; |
| | | |
| | | // 上传成功回调 |
| | | function handleUploadSuccess(res, file) { |
| | | const handleUploadSuccess = (res, file) => { |
| | | if (res.code === 200) { |
| | | console.log("上传成功", res); |
| | | uploadList.value.push(res.data[0]) |
| | | uploadList.value.push(res.data[0]); |
| | | uploadedSuccessfully(); |
| | | // proxy.$modal.msgSuccess("文件上传成功"); |
| | | } else { |
| | | number.value--; |
| | | proxy.$modal.closeLoading(); |
| | |
| | | proxy.$refs.fileUpload.handleRemove(file); |
| | | uploadedSuccessfully(); |
| | | } |
| | | } |
| | | }; |
| | | |
| | | // 删除文件 |
| | | function handleDelete(index) { |
| | | const handleDelete = (index) => { |
| | | fileList.value.splice(index, 1); |
| | | emit("update:modelValue", listToString(fileList.value)); |
| | | } |
| | | }; |
| | | |
| | | // 上传结束处理 |
| | | function uploadedSuccessfully() { |
| | | const uploadedSuccessfully = () => { |
| | | if (number.value > 0 && uploadList.value.length === number.value) { |
| | | fileList.value = fileList.value |
| | | .filter((f) => f.url !== undefined) |
| | |
| | | emit("update:modelValue"); |
| | | proxy.$modal.closeLoading(); |
| | | } |
| | | } |
| | | }; |
| | | |
| | | // 获取文件名称 |
| | | function getFileName(name) { |
| | | // 如果是url那么取最后的名字 如果不是直接返回 |
| | | if (name.lastIndexOf("/") > -1) { |
| | | return name.slice(name.lastIndexOf("/") + 1); |
| | | } else { |
| | | return name; |
| | | } |
| | | } |
| | | |
| | | // 对象转成指定字符串分隔 |
| | | function listToString(list, separator) { |
| | | let strs = ""; |
| | | separator = separator || ","; |
| | | for (let i in list) { |
| | | if (list[i].url) { |
| | | strs += list[i].url + separator; |
| | | } |
| | | } |
| | | return strs != "" ? strs.substr(0, strs.length - 1) : ""; |
| | | } |
| | | const listToString = (list, separator = ",") => { |
| | | const strs = list |
| | | .filter(item => item.url) |
| | | .map(item => item.url) |
| | | .join(separator); |
| | | return strs; |
| | | }; |
| | | |
| | | // 初始化拖拽排序 |
| | | onMounted(() => { |
| | | if (props.drag && !props.disabled) { |
| | | nextTick(() => { |
| | | const element = |
| | | proxy.$refs.uploadFileList?.$el || proxy.$refs.uploadFileList; |
| | | const element = proxy.$refs.uploadFileList?.$el || proxy.$refs.uploadFileList; |
| | | if (element) { |
| | | Sortable.create(element, { |
| | | ghostClass: "file-upload-darg", |
| | | onEnd: (evt) => { |
| | |
| | | emit("update:modelValue", listToString(fileList.value)); |
| | | }, |
| | | }); |
| | | } |
| | | }); |
| | | } |
| | | }); |