| | |
| | | </template> |
| | | </u-input> |
| | | </u-form-item> |
| | | <u-form-item label="拜访照片"> |
| | | <AlbumImageUpload |
| | | v-model="form.storageBlobDTO" |
| | | @change="onVisitImageChange" |
| | | :limit="6" |
| | | :enableWatermark="true" |
| | | :watermarkText="getVisitWatermarkText" |
| | | :beforeUpload="beforeUploadVisitImages" |
| | | action="/file/upload" |
| | | tipText="" |
| | | /> |
| | | </u-form-item> |
| | | </u-cell-group> |
| | | <!-- 备注信息 --> |
| | | <u-cell-group title="备注信息"> |
| | |
| | | |
| | | import { ref, onMounted } from "vue"; |
| | | import PageHeader from "@/components/PageHeader.vue"; |
| | | import AlbumImageUpload from "@/components/AlbumImageUpload/index.vue"; |
| | | import { normalizeFileUrl } from "@/utils/filePreview"; |
| | | import config from "@/config.js"; |
| | | import { |
| | | clientVisitSignIn, |
| | | clientVisitUpdate, |
| | |
| | | longitude: "", |
| | | locationAddress: "", |
| | | remark: "", |
| | | // 临时文件id列表(提交用) |
| | | tempFileIds: [], |
| | | // 正式文件列表(回显用,后端查询会返回) |
| | | commonFileList: [], |
| | | // 上传组件回传的文件列表(后端若不接收可忽略;先随表单提交) |
| | | storageBlobDTO: [], |
| | | }); |
| | | |
| | | // 页面状态 |
| | |
| | | Object.keys(source).forEach(k => { |
| | | submitData[k] = source[k]; |
| | | }); |
| | | // 从上传列表提取 tempFileIds(兼容 tempId / tempFileId / id) |
| | | if (Array.isArray(submitData.storageBlobDTO)) { |
| | | submitData.tempFileIds = submitData.storageBlobDTO |
| | | .map(f => f?.tempId ?? f?.tempFileId ?? f?.id) |
| | | .filter(v => v !== undefined && v !== null && v !== ""); |
| | | } else { |
| | | submitData.tempFileIds = Array.isArray(submitData.tempFileIds) |
| | | ? submitData.tempFileIds |
| | | : []; |
| | | } |
| | | // 兼容后端只接收 URL 字符串的场景:额外补一个 visitImageUrls |
| | | if (Array.isArray(submitData.storageBlobDTO)) { |
| | | submitData.visitImageUrls = submitData.storageBlobDTO |
| | | .map(f => f?.url) |
| | | .filter(Boolean) |
| | | .join(","); |
| | | } |
| | | console.log("submitData", submitData); |
| | | if (isEdit.value) { |
| | | const { code } = await clientVisitUpdate(submitData); |
| | |
| | | } |
| | | }; |
| | | const isEdit = ref(false); |
| | | const getVisitFileKey = file => { |
| | | return ( |
| | | file?.tempId ?? |
| | | file?.tempFileId ?? |
| | | file?.id ?? |
| | | file?.url ?? |
| | | file?.downloadUrl ?? |
| | | "" |
| | | ); |
| | | }; |
| | | const onVisitImageChange = list => { |
| | | const nextList = Array.isArray(list) ? list.slice() : []; |
| | | form.value.storageBlobDTO = nextList; |
| | | |
| | | // 同步提交用 tempFileIds,避免删除后仍携带旧 ID |
| | | form.value.tempFileIds = nextList |
| | | .map(f => f?.tempId ?? f?.tempFileId ?? f?.id) |
| | | .filter(v => v !== undefined && v !== null && v !== ""); |
| | | |
| | | // 编辑场景下同步 commonFileList,避免提交时残留已删除文件 |
| | | if (Array.isArray(form.value.commonFileList)) { |
| | | const keepKeys = new Set(nextList.map(getVisitFileKey).filter(Boolean)); |
| | | form.value.commonFileList = form.value.commonFileList.filter(item => |
| | | keepKeys.has(getVisitFileKey(item)) |
| | | ); |
| | | } |
| | | }; |
| | | const isLikelyPathValue = value => { |
| | | const v = String(value || "").trim(); |
| | | if (!v) return false; |
| | | if (/^(https?:)?\/\//i.test(v)) return true; |
| | | if (/^(blob:|data:|wxfile:|file:|content:)/i.test(v)) return true; |
| | | if (v.includes("/") || v.includes("\\")) return true; |
| | | if (/\.[a-zA-Z0-9]{2,8}($|\?)/.test(v)) return true; |
| | | return false; |
| | | }; |
| | | const buildVisitFilePreviewUrl = file => { |
| | | const candidates = [ |
| | | file?.link, |
| | | file?.url, |
| | | file?.path, |
| | | file?.filePath, |
| | | file?.tempPath, |
| | | file?.urlFull, |
| | | file?.downloadUrl, |
| | | ].filter(Boolean); |
| | | |
| | | for (const raw of candidates) { |
| | | if (!isLikelyPathValue(raw)) continue; |
| | | const normalized = normalizeFileUrl(raw); |
| | | if (normalized) return normalized; |
| | | } |
| | | |
| | | for (const raw of candidates) { |
| | | const value = String(raw || "").trim(); |
| | | if (!value) continue; |
| | | // 兜底走 common/download,兼容某些后端只返回相对存储路径 |
| | | return ( |
| | | (config.baseUrl || "") + |
| | | "/common/download?fileName=" + |
| | | encodeURIComponent(value) + |
| | | "&delete=false" |
| | | ); |
| | | } |
| | | return ""; |
| | | }; |
| | | onLoad(() => { |
| | | // 编辑拜访时,从本地存储获取拜访记录 |
| | | const visit = uni.getStorageSync("clientVisit"); |
| | |
| | | form.value = visit; |
| | | isEdit.value = true; |
| | | console.log("form.value", form.value); |
| | | |
| | | // 回显:若后端/列表页塞了 commonFileList,则转成上传组件可识别的 storageBlobDTO |
| | | const commonFileList = Array.isArray(visit?.commonFileList) |
| | | ? visit.commonFileList |
| | | : []; |
| | | if (commonFileList.length && (!Array.isArray(visit?.storageBlobDTO) || !visit.storageBlobDTO.length)) { |
| | | form.value.commonFileList = commonFileList; |
| | | form.value.storageBlobDTO = commonFileList |
| | | .map(f => { |
| | | const previewUrl = buildVisitFilePreviewUrl(f); |
| | | return { |
| | | ...f, |
| | | tempId: f?.tempId ?? f?.tempFileId ?? f?.id, |
| | | id: f?.id ?? f?.tempId ?? f?.tempFileId, |
| | | url: previewUrl, |
| | | downloadUrl: previewUrl, |
| | | }; |
| | | }) |
| | | .filter(x => x?.url); |
| | | } |
| | | |
| | | // 编辑场景:同步一份 tempFileIds,便于后端需要时直接使用 |
| | | if (!Array.isArray(form.value.tempFileIds) || !form.value.tempFileIds.length) { |
| | | form.value.tempFileIds = Array.isArray(form.value.storageBlobDTO) |
| | | ? form.value.storageBlobDTO |
| | | .map(f => f?.tempId ?? f?.tempFileId ?? f?.id) |
| | | .filter(Boolean) |
| | | : []; |
| | | } |
| | | } else { |
| | | isEdit.value = false; |
| | | } |
| | |
| | | onMounted(() => { |
| | | initPageData(); |
| | | }); |
| | | |
| | | const beforeUploadVisitImages = () => { |
| | | const name = (form.value.customerName || "").trim(); |
| | | if (!name) { |
| | | showToast("请先填写客户名称后上传"); |
| | | return false; |
| | | } |
| | | const visitTime = (form.value.purposeDate || "").trim(); |
| | | if (!visitTime) { |
| | | showToast("请先选择拜访时间后上传"); |
| | | return false; |
| | | } |
| | | return true; |
| | | }; |
| | | |
| | | const getVisitWatermarkText = () => { |
| | | const name = (form.value.customerName || "").trim(); |
| | | // 水印时间改为“拜访时间” |
| | | const visitTime = (form.value.purposeDate || "").trim(); |
| | | const time = visitTime || dayjs().format("YYYY-MM-DD HH:mm:ss"); |
| | | return name ? `${name}\n${time}` : `\n${time}`; |
| | | }; |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | @import "@/static/scss/form-common.scss"; |
| | | .client-visit { |
| | | .client-visit-detail { |
| | | min-height: 100vh; |
| | | background: #f8f9fa; |
| | | padding-bottom: 5rem; |
| | | padding-bottom: calc(5rem + env(safe-area-inset-bottom)); |
| | | } |
| | | |
| | | .footer-btns { |
| | | position: fixed; |
| | | left: 0; |
| | | right: 0; |
| | | bottom: 0; |
| | | bottom: calc(env(safe-area-inset-bottom) + 8px); |
| | | background: #fff; |
| | | display: flex; |
| | | justify-content: space-around; |