| | |
| | | if (!Array) { |
| | | const _easycom_uni_icons2 = common_vendor.resolveComponent("uni-icons"); |
| | | const _easycom_uni_popup2 = common_vendor.resolveComponent("uni-popup"); |
| | | const _easycom_uni_file_picker2 = common_vendor.resolveComponent("uni-file-picker"); |
| | | (_easycom_uni_icons2 + _easycom_uni_popup2 + _easycom_uni_file_picker2)(); |
| | | (_easycom_uni_icons2 + _easycom_uni_popup2)(); |
| | | } |
| | | const _easycom_uni_icons = () => "../../node-modules/@dcloudio/uni-ui/lib/uni-icons/uni-icons.js"; |
| | | const _easycom_uni_popup = () => "../../node-modules/@dcloudio/uni-ui/lib/uni-popup/uni-popup.js"; |
| | | const _easycom_uni_file_picker = () => "../../node-modules/@dcloudio/uni-ui/lib/uni-file-picker/uni-file-picker.js"; |
| | | if (!Math) { |
| | | (_easycom_uni_icons + _easycom_uni_popup + _easycom_uni_file_picker)(); |
| | | (_easycom_uni_icons + _easycom_uni_popup)(); |
| | | } |
| | | const _sfc_main = { |
| | | __name: "index", |
| | | props: { |
| | | files: { |
| | | type: Array, |
| | | default: () => [] |
| | | }, |
| | | gradesFiles: { |
| | | // 新增属性:成绩单附件列表 |
| | | type: Array, |
| | | default: () => [] |
| | | }, |
| | | readonly: { |
| | | type: Boolean, |
| | | default: false |
| | | }, |
| | | position: { |
| | | type: Object, |
| | | default: () => ({ |
| | | right: "30rpx", |
| | | bottom: "200rpx" |
| | | }) |
| | | }, |
| | | bgColor: { |
| | | type: String, |
| | | default: "#67AFAB" |
| | | }, |
| | | maxCount: { |
| | | type: Number, |
| | | default: 9 |
| | | }, |
| | | showGradeSlip: { |
| | | type: Boolean, |
| | | default: false |
| | | }, |
| | | isGradeRequired: { |
| | | type: Boolean, |
| | | default: false |
| | | } |
| | | files: { type: Array, default: () => [] }, |
| | | gradesFiles: { type: Array, default: () => [] }, |
| | | readonly: { type: Boolean, default: false }, |
| | | position: { type: Object, default: () => ({ right: "30rpx", bottom: "200rpx" }) }, |
| | | bgColor: { type: String, default: "#67AFAB" }, |
| | | maxCount: { type: Number, default: 9 }, |
| | | showGradeSlip: { type: Boolean, default: false }, |
| | | isGradeRequired: { type: Boolean, default: false } |
| | | }, |
| | | emits: [ |
| | | "update:files", |
| | | "update:gradesFiles", |
| | | // 新增事件:更新成绩单附件 |
| | | "upload", |
| | | "preview", |
| | | "upload-grade", |
| | | // 新增事件:上传成绩单附件 |
| | | "upload-base" |
| | | // 新增事件:上传基础附件 |
| | | ], |
| | | emits: ["update:files", "update:gradesFiles", "upload", "preview", "upload-grade", "upload-base"], |
| | | setup(__props, { expose: __expose, emit: __emit }) { |
| | | const userStore = stores_user.useUserStore(); |
| | | const props = __props; |
| | | const emit = __emit; |
| | | const popup = common_vendor.ref(null); |
| | | const filePicker = common_vendor.ref(null); |
| | | const baseFiles = common_vendor.ref([]); |
| | | const gradeFiles = common_vendor.ref([]); |
| | | const showButton = common_vendor.ref(true); |
| | | const mainColor = common_vendor.ref("#67AFAB"); |
| | | const baseUrlHt = userStore.baseUrlHt; |
| | | const currentTab = common_vendor.ref("base"); |
| | | const blobUrls = common_vendor.ref([]); |
| | | const currentFileList = common_vendor.computed(() => { |
| | | if (!props.showGradeSlip) { |
| | | if (!props.showGradeSlip) |
| | | return baseFiles.value; |
| | | } |
| | | return currentTab.value === "base" ? baseFiles.value : gradeFiles.value; |
| | | }); |
| | | const totalFileCount = common_vendor.computed(() => { |
| | | return baseFiles.value.length + gradeFiles.value.length; |
| | | }); |
| | | const totalFileCount = common_vendor.computed(() => baseFiles.value.length + gradeFiles.value.length); |
| | | common_vendor.watch(() => props.files, (newFiles) => { |
| | | baseFiles.value = [...newFiles]; |
| | | }, { immediate: true }); |
| | |
| | | if (!path) |
| | | return ""; |
| | | if (path.startsWith("http://") || path.startsWith("https://")) { |
| | | return path; |
| | | try { |
| | | const url = new URL(path); |
| | | return `${baseUrlHt}${url.pathname}${url.search}${url.hash}`; |
| | | } catch { |
| | | return path; |
| | | } |
| | | } |
| | | return `${baseUrlHt}${path.startsWith("/") ? "" : "/"}${path}`; |
| | | }; |
| | | const imageStyles = { |
| | | width: 120, |
| | | height: 120, |
| | | border: false |
| | | }; |
| | | const supportedImageTypes = [ |
| | | "image/jpeg", |
| | | "image/png", |
| | | "image/gif", |
| | | "image/webp", |
| | | "image/bmp", |
| | | "image/svg+xml" |
| | | ]; |
| | | const supportedImageTypes = ["image/jpeg", "image/png", "image/gif", "image/webp", "image/bmp", "image/svg+xml"]; |
| | | const onPopupChange = (e) => { |
| | | showButton.value = !e.show; |
| | | }; |
| | |
| | | return `${(size / (1024 * 1024)).toFixed(1)}MB`; |
| | | }; |
| | | const togglePopup = () => { |
| | | if (popup.value) { |
| | | popup.value.open(); |
| | | } |
| | | var _a; |
| | | (_a = popup.value) == null ? void 0 : _a.open(); |
| | | }; |
| | | const closePopup = () => { |
| | | if (popup.value) { |
| | | popup.value.close(); |
| | | } |
| | | }; |
| | | const chooseFile = () => { |
| | | var _a; |
| | | (_a = filePicker.value) == null ? void 0 : _a.choose(); |
| | | (_a = popup.value) == null ? void 0 : _a.close(); |
| | | }; |
| | | const getFileTypeFromName = (filename) => { |
| | | if (!filename) |
| | | return "application/octet-stream"; |
| | | const ext = filename.split(".").pop().toLowerCase(); |
| | | const typeMap = { |
| | | jpg: "image/jpeg", |
| | | jpeg: "image/jpeg", |
| | | png: "image/png", |
| | | gif: "image/gif", |
| | | webp: "image/webp", |
| | | bmp: "image/bmp", |
| | | SVG: "image/svg+xml", |
| | | pdf: "application/pdf" |
| | | }; |
| | | return typeMap[ext] || "application/octet-stream"; |
| | | }; |
| | | const onFileSelect = (e) => { |
| | | const addFilesToCurrentTab = (newFiles) => { |
| | | const isGradeTab = props.showGradeSlip && currentTab.value === "grade"; |
| | | const targetFiles = isGradeTab ? gradeFiles.value : baseFiles.value; |
| | | const newFiles = e.tempFiles.filter((file) => { |
| | | const fileExt = file.name ? file.name.split(".").pop().toLowerCase() : ""; |
| | | const isImage = supportedImageTypes.includes(file.type) || ["jpg", "jpeg", "png", "gif", "webp", "bmp", "svg"].includes(fileExt); |
| | | const isPDF = file.type && file.type.includes("pdf") || fileExt === "pdf"; |
| | | return isImage || isPDF; |
| | | }).map((file) => ({ |
| | | name: file.name, |
| | | url: file.path || file.url, |
| | | type: file.type || getFileTypeFromName(file.name), |
| | | size: file.size, |
| | | file, |
| | | status: "pending", |
| | | // 明确标记附件类型 |
| | | attachmentType: isGradeTab ? "grade" : "base" |
| | | })); |
| | | if (targetFiles.length + newFiles.length > props.maxCount) { |
| | | common_vendor.index.showToast({ |
| | | title: `最多只能上传${props.maxCount}个文件`, |
| | | icon: "none" |
| | | common_vendor.index.showToast({ title: `最多只能上传${props.maxCount}个文件`, icon: "none" }); |
| | | newFiles.forEach((f) => { |
| | | if (f.url && f.url.startsWith("blob:")) |
| | | URL.revokeObjectURL(f.url); |
| | | }); |
| | | return; |
| | | } |
| | |
| | | emit("update:files", baseFiles.value); |
| | | } |
| | | }; |
| | | const getAllFiles = () => { |
| | | return [...baseFiles.value, ...gradeFiles.value]; |
| | | const chooseFile = () => { |
| | | common_vendor.index.showActionSheet({ |
| | | itemList: ["拍照/相册", "从聊天记录选择文件"], |
| | | success: (res) => { |
| | | const remain = props.maxCount - currentFileList.value.length; |
| | | if (remain <= 0) { |
| | | common_vendor.index.showToast({ title: `最多上传${props.maxCount}个文件`, icon: "none" }); |
| | | return; |
| | | } |
| | | if (res.tapIndex === 0) { |
| | | common_vendor.index.chooseImage({ |
| | | count: remain, |
| | | sizeType: ["original", "compressed"], |
| | | sourceType: ["album", "camera"], |
| | | success: (chooseRes) => { |
| | | const files = chooseRes.tempFiles.map((file) => ({ |
| | | name: file.name || "image.jpg", |
| | | path: file.path, |
| | | url: file.path, |
| | | uploadPath: file.path, |
| | | // 小程序临时文件路径 |
| | | type: file.type || "image/jpeg", |
| | | size: file.size, |
| | | raw: file, |
| | | status: "pending" |
| | | })); |
| | | addFilesToCurrentTab(files); |
| | | } |
| | | }); |
| | | } else if (res.tapIndex === 1) { |
| | | common_vendor.index.chooseMessageFile({ |
| | | count: remain, |
| | | type: "all", |
| | | success: (chooseRes) => { |
| | | const files = chooseRes.tempFiles.map((file) => ({ |
| | | name: file.name, |
| | | path: file.path, |
| | | url: file.path, |
| | | uploadPath: file.path, |
| | | type: file.type, |
| | | size: file.size, |
| | | raw: file, |
| | | status: "pending" |
| | | })); |
| | | addFilesToCurrentTab(files); |
| | | } |
| | | }); |
| | | } |
| | | } |
| | | }); |
| | | }; |
| | | const removeFile = (type, index, event) => { |
| | | if (event) { |
| | | if (event) |
| | | event.stopPropagation(); |
| | | const file = type === "grade" ? gradeFiles.value[index] : baseFiles.value[index]; |
| | | if (file.url && file.url.startsWith("blob:")) { |
| | | URL.revokeObjectURL(file.url); |
| | | const idx = blobUrls.value.indexOf(file.url); |
| | | if (idx !== -1) |
| | | blobUrls.value.splice(idx, 1); |
| | | } |
| | | if (type === "grade") { |
| | | gradeFiles.value.splice(index, 1); |
| | |
| | | } |
| | | }; |
| | | const previewFile = (file) => { |
| | | const fullUrl = getFullUrl(file.url); |
| | | let previewUrl = file.url; |
| | | if (previewUrl && !previewUrl.startsWith("blob:") && !previewUrl.startsWith("file://") && !previewUrl.startsWith("/")) { |
| | | previewUrl = getFullUrl(previewUrl); |
| | | } |
| | | if (file.type && supportedImageTypes.includes(file.type)) { |
| | | const allFiles = getAllFiles(); |
| | | common_vendor.index.previewImage({ |
| | | urls: allFiles.filter((f) => f.type && supportedImageTypes.includes(f.type)).map((f) => getFullUrl(f.url)), |
| | | current: fullUrl |
| | | }); |
| | | const allFiles = [...baseFiles.value, ...gradeFiles.value]; |
| | | const imageUrls = allFiles.filter((f) => f.type && supportedImageTypes.includes(f.type)).map((f) => f.url.startsWith("blob:") || f.url.startsWith("file://") || f.url.startsWith("/") ? f.url : getFullUrl(f.url)); |
| | | common_vendor.index.previewImage({ urls: imageUrls, current: previewUrl }); |
| | | } else if (file.type && file.type.includes("pdf")) { |
| | | common_vendor.index.downloadFile({ |
| | | url: fullUrl, |
| | | url: previewUrl, |
| | | success: (res) => { |
| | | const filePath = res.tempFilePath; |
| | | common_vendor.index.openDocument({ |
| | | filePath, |
| | | fileType: "pdf", |
| | | success: () => common_vendor.index.__f__("log", "at components/attachment/index.vue:358", "打开PDF成功"), |
| | | fail: (err) => { |
| | | common_vendor.index.__f__("error", "at components/attachment/index.vue:360", "打开PDF失败", err); |
| | | common_vendor.index.showToast({ |
| | | title: "打开PDF失败", |
| | | icon: "none" |
| | | }); |
| | | } |
| | | }); |
| | | common_vendor.index.openDocument({ filePath: res.tempFilePath, fileType: "pdf" }); |
| | | }, |
| | | fail: (err) => { |
| | | common_vendor.index.__f__("error", "at components/attachment/index.vue:369", "下载PDF失败", err); |
| | | common_vendor.index.showToast({ |
| | | title: "下载PDF失败", |
| | | icon: "none" |
| | | }); |
| | | } |
| | | fail: () => common_vendor.index.showToast({ title: "打开PDF失败", icon: "none" }) |
| | | }); |
| | | } else { |
| | | emit("preview", file); |
| | |
| | | const uploadFile = (file, type) => { |
| | | return new Promise((resolve, reject) => { |
| | | const token = common_vendor.index.getStorageSync("token"); |
| | | let filePath = file.uploadPath || file.url || file.path; |
| | | if (!filePath || typeof filePath !== "string") { |
| | | reject(new Error("无效的文件路径")); |
| | | return; |
| | | } |
| | | let uploadUrl = "/api/common/upload"; |
| | | uploadUrl = "https://opo.qduh.cn/common/upload"; |
| | | common_vendor.index.uploadFile({ |
| | | url: "/api/common/upload", |
| | | filePath: file.path || file.url, |
| | | url: uploadUrl, |
| | | filePath, |
| | | name: "file", |
| | | header: { |
| | | "Authorization": `Bearer ${token}` |
| | | }, |
| | | header: { "Authorization": `Bearer ${token}` }, |
| | | success: (res) => { |
| | | if (res.statusCode === 200) { |
| | | const data = JSON.parse(res.data); |
| | | common_vendor.index.__f__("log", "at components/attachment/index.vue:397", data, "文件"); |
| | | if (data.code === 200) { |
| | | resolve({ |
| | | ...data, |
| | | fileName: data.fileName |
| | | }); |
| | | } else { |
| | | reject(new Error(data.msg || "上传失败")); |
| | | try { |
| | | const data = JSON.parse(res.data); |
| | | if (data.code === 200) |
| | | resolve({ ...data, fileName: data.fileName }); |
| | | else |
| | | reject(new Error(data.msg || "上传失败")); |
| | | } catch { |
| | | reject(new Error("解析响应失败")); |
| | | } |
| | | } else { |
| | | } else |
| | | reject(new Error(`上传失败,状态码: ${res.statusCode}`)); |
| | | } |
| | | }, |
| | | fail: (err) => { |
| | | reject(err); |
| | | } |
| | | fail: reject |
| | | }); |
| | | }); |
| | | }; |
| | | const confirmUpload = async () => { |
| | | if (props.showGradeSlip && props.isGradeRequired && gradeFiles.value.length === 0) { |
| | | common_vendor.index.showToast({ |
| | | title: "请上传成绩单附件", |
| | | icon: "none" |
| | | }); |
| | | common_vendor.index.showToast({ title: "请上传成绩单附件", icon: "none" }); |
| | | currentTab.value = "grade"; |
| | | return; |
| | | } |
| | | const allFiles = getAllFiles(); |
| | | if (allFiles.length === 0) { |
| | | common_vendor.index.showToast({ |
| | | title: "请先添加附件", |
| | | icon: "none" |
| | | }); |
| | | const totalPending = [...baseFiles.value, ...gradeFiles.value].filter((f) => !f.url || f.status === "pending").length; |
| | | if (totalPending === 0) { |
| | | common_vendor.index.showToast({ title: "没有待上传的文件", icon: "none" }); |
| | | return; |
| | | } |
| | | common_vendor.index.showLoading({ |
| | | title: "上传中", |
| | | mask: true |
| | | }); |
| | | common_vendor.index.showLoading({ title: "上传中", mask: true }); |
| | | try { |
| | | const pendingBaseFiles = baseFiles.value.filter((file) => !file.url || file.status === "pending"); |
| | | const pendingGradeFiles = gradeFiles.value.filter((file) => !file.url || file.status === "pending"); |
| | | for (const file of pendingBaseFiles) { |
| | | const pendingBase = baseFiles.value.filter((f) => !f.url || f.status === "pending"); |
| | | const pendingGrade = gradeFiles.value.filter((f) => !f.url || f.status === "pending"); |
| | | for (const file of pendingBase) { |
| | | try { |
| | | file.status = "uploading"; |
| | | const res = await uploadFile(file.file, "base"); |
| | | const res = await uploadFile(file, "base"); |
| | | Object.assign(file, { |
| | | url: res.url, |
| | | fileName: res.name, |
| | |
| | | size: res.size |
| | | }); |
| | | emit("upload-base", file); |
| | | } catch (error) { |
| | | common_vendor.index.__f__("error", "at components/attachment/index.vue:466", "上传失败:", error); |
| | | } catch (err) { |
| | | file.status = "error"; |
| | | common_vendor.index.showToast({ |
| | | title: `文件 ${file.name} 上传失败`, |
| | | icon: "none" |
| | | }); |
| | | common_vendor.index.showToast({ title: `文件 ${file.name} 上传失败`, icon: "none" }); |
| | | } |
| | | } |
| | | for (const file of pendingGradeFiles) { |
| | | for (const file of pendingGrade) { |
| | | try { |
| | | file.status = "uploading"; |
| | | const res = await uploadFile(file.file, "grade"); |
| | | const res = await uploadFile(file, "grade"); |
| | | Object.assign(file, { |
| | | url: res.fileName, |
| | | fileName: res.fileName, |
| | |
| | | status: "success" |
| | | }); |
| | | emit("upload-grade", file); |
| | | } catch (error) { |
| | | common_vendor.index.__f__("error", "at components/attachment/index.vue:489", "成绩单附件上传失败:", error); |
| | | } catch (err) { |
| | | file.status = "error"; |
| | | common_vendor.index.showToast({ |
| | | title: `文件 ${file.name} 上传失败`, |
| | | icon: "none" |
| | | }); |
| | | common_vendor.index.showToast({ title: `文件 ${file.name} 上传失败`, icon: "none" }); |
| | | } |
| | | } |
| | | common_vendor.index.__f__("log", "at components/attachment/index.vue:497", baseFiles.value, "1"); |
| | | common_vendor.index.__f__("log", "at components/attachment/index.vue:498", gradeFiles.value, "2"); |
| | | emit("update:files", baseFiles.value); |
| | | emit("update:gradesFiles", gradeFiles.value); |
| | | common_vendor.index.showToast({ |
| | | title: "上传完成", |
| | | icon: "success" |
| | | }); |
| | | common_vendor.index.showToast({ title: "上传完成", icon: "success" }); |
| | | closePopup(); |
| | | } catch (error) { |
| | | common_vendor.index.__f__("error", "at components/attachment/index.vue:511", "上传出错:", error); |
| | | common_vendor.index.showToast({ |
| | | title: "上传出错", |
| | | icon: "none" |
| | | }); |
| | | common_vendor.index.__f__("error", "at components/attachment/index.vue:398", "上传出错:", error); |
| | | common_vendor.index.showToast({ title: "上传出错", icon: "none" }); |
| | | } finally { |
| | | common_vendor.index.hideLoading(); |
| | | } |
| | | }; |
| | | const getFilesByType = (type) => { |
| | | return type === "grade" ? gradeFiles.value : baseFiles.value; |
| | | }; |
| | | __expose({ |
| | | getFilesByType, |
| | | getAllFiles |
| | | const getFilesByType = (type) => type === "grade" ? gradeFiles.value : baseFiles.value; |
| | | const getAllFiles = () => [...baseFiles.value, ...gradeFiles.value]; |
| | | common_vendor.onBeforeUnmount(() => { |
| | | blobUrls.value.forEach((url) => URL.revokeObjectURL(url)); |
| | | blobUrls.value = []; |
| | | }); |
| | | __expose({ getFilesByType, getAllFiles }); |
| | | return (_ctx, _cache) => { |
| | | return common_vendor.e({ |
| | | a: totalFileCount.value > 0 |
| | |
| | | type: "bottom", |
| | | ["safe-area"]: false |
| | | }), |
| | | J: !__props.readonly |
| | | }, !__props.readonly ? { |
| | | K: common_vendor.sr(filePicker, "2d320176-8", { |
| | | "k": "filePicker" |
| | | }), |
| | | L: common_vendor.o(onFileSelect), |
| | | M: common_vendor.o(_ctx.onFileDelete), |
| | | N: common_vendor.p({ |
| | | ["auto-upload"]: false, |
| | | ["file-mediatype"]: "all", |
| | | limit: __props.maxCount - currentFileList.value.length, |
| | | ["image-styles"]: imageStyles |
| | | }) |
| | | } : {}, { |
| | | O: common_vendor.gei(_ctx, "") |
| | | J: common_vendor.gei(_ctx, "") |
| | | }); |
| | | }; |
| | | } |