/** * 附件字段兼容工具 * * 后端各模块的附件字段名与形态不统一: * 对象数组:storageBlobVOs(回显) / attachmentList / fileList,项含 url||previewURL||fileUrl||path * 逗号字符串:attachmentUrls / attachmentIds * 页面统一转成 [{url}] 交给 CommonUpload,提交时再按各模块约定转回。 */ import config from "@/config"; import { getToken } from "@/utils/auth"; /** * 任意形态的附件数据 → 对象数组(每项保证有可渲染的 url) */ export function toFileList(source) { if (!source) return []; if (Array.isArray(source)) { return source .map(item => { if (typeof item === "string") return item ? { url: item } : null; const url = item?.url || item?.previewURL || item?.fileUrl || item?.path; return url ? { ...item, url } : null; }) .filter(Boolean); } if (typeof source === "string") { return source .split(",") .map(part => part.trim()) .filter(Boolean) .map(url => ({ url })); } return []; } /** * 对象数组 / 字符串数组 → 逗号分隔字符串(attachmentUrls 这类后端字段要的形态) */ export function toUrlString(fileList) { return (fileList || []) .map(item => typeof item === "string" ? item : item?.url || item?.previewURL || "" ) .filter(Boolean) .join(","); } /** * 附件 url → 可下载的完整地址 * 附件接口回的常是 /profile/upload/... 这类后端相对路径,直接丢给 uni.downloadFile 会失败, * 按管理端 getRealUrl 的做法补上 baseUrl。 */ export function resolveFileUrl(url) { const target = String(url ?? "").trim(); if (!target) return ""; if (/^https?:\/\//i.test(target)) return target; return `${config.baseUrl}${target.startsWith("/") ? "" : "/"}${target}`; } /** * 打开附件:request.ts 未透传 responseType,blob 下载不可用, * 只能 downloadFile 到临时文件再 openDocument。 */ export function openFile(url) { const target = resolveFileUrl(url); if (!target) { uni.showToast({ title: "文件地址无效", icon: "none" }); return; } uni.showLoading({ title: "打开中...", mask: true }); uni.downloadFile({ url: target, header: { Authorization: "Bearer " + getToken() }, success: res => { if (res.statusCode !== 200) { uni.showToast({ title: "文件下载失败", icon: "none" }); return; } uni.openDocument({ filePath: res.tempFilePath, showMenu: true, fail: () => uni.showToast({ title: "暂不支持预览该文件", icon: "none" }), }); }, fail: () => uni.showToast({ title: "文件下载失败", icon: "none" }), complete: () => uni.hideLoading(), }); } /** * 后端生成文件(PDF/Word)并下载打开。 * * 这类接口是 POST + body,而 uni.downloadFile 只支持 GET,所以只能先用 * uni.request 拿 arraybuffer,再落到 USER_DATA_PATH 后 openDocument。 * uni.getFileSystemManager 在 H5 不存在,H5 下会提示不支持。 * * @param {string} path 接口路径,如 /quality/qualityInspect/downPdf * @param {object} data 请求体 * @param {string} fileName 落盘文件名(含后缀) */ export function downloadFileByPost(path, data = {}, fileName = "download.pdf") { uni.showLoading({ title: "生成中...", mask: true }); return new Promise((resolve, reject) => { uni.request({ url: `${config.baseUrl}${path}`, method: "POST", data, responseType: "arraybuffer", header: { Authorization: "Bearer " + getToken(), "Content-Type": "application/json", }, success: res => { const contentType = String( res.header?.["Content-Type"] || res.header?.["content-type"] || "" ); // 后端出错时回的是 JSON(code/msg),照原样写成 pdf 会得到一个打不开的文件 if (res.statusCode !== 200 || contentType.includes("application/json")) { uni.showToast({ title: "生成文件失败", icon: "none" }); reject(new Error("生成文件失败")); return; } const fs = uni.getFileSystemManager && uni.getFileSystemManager(); if (!fs) { uni.showToast({ title: "当前环境不支持下载文件", icon: "none" }); reject(new Error("unsupported")); return; } const filePath = `${uni.env.USER_DATA_PATH}/${fileName}`; fs.writeFile({ filePath, data: res.data, encoding: "binary", success: () => { uni.openDocument({ filePath, showMenu: true, fail: () => uni.showToast({ title: "暂不支持预览该文件", icon: "none" }), }); resolve(filePath); }, fail: () => { uni.showToast({ title: "文件保存失败", icon: "none" }); reject(new Error("文件保存失败")); }, }); }, fail: () => { uni.showToast({ title: "生成文件失败", icon: "none" }); reject(new Error("生成文件失败")); }, complete: () => uni.hideLoading(), }); }); }