From 6ff219555c53c37d4daae0747751043ea103af1e Mon Sep 17 00:00:00 2001
From: yyb <995253665@qq.com>
Date: 星期二, 28 四月 2026 11:32:39 +0800
Subject: [PATCH] 水印签到拜访照片
---
src/utils/filePreview.js | 67 +++++
src/pages/cooperativeOffice/clientVisit/detail.vue | 161 ++++++++++++
src/components/AlbumImageUpload/index.vue | 527 ++++++++++++++++++++++++++++++++++++++++
3 files changed, 752 insertions(+), 3 deletions(-)
diff --git a/src/components/AlbumImageUpload/index.vue b/src/components/AlbumImageUpload/index.vue
new file mode 100644
index 0000000..e30231f
--- /dev/null
+++ b/src/components/AlbumImageUpload/index.vue
@@ -0,0 +1,527 @@
+<template>
+ <view class="album-image-upload">
+ <view class="actions" v-if="!readonly">
+ <u-button
+ type="primary"
+ size="small"
+ :loading="uploading"
+ :disabled="uploading || innerList.length >= limit"
+ @click="chooseFromAlbum"
+ >
+ {{ buttonText }}
+ </u-button>
+ <view class="tip" v-if="tipText">{{ tipText }}</view>
+ </view>
+
+ <view v-if="innerList.length" class="list">
+ <view
+ v-for="(img, idx) in innerList"
+ :key="img.id || img.url || idx"
+ class="item"
+ >
+ <image
+ class="img"
+ :src="img.previewUrl || img.url"
+ mode="aspectFill"
+ @click="preview(idx)"
+ />
+ <view v-if="!readonly" class="del" @click.stop="remove(idx)">脳</view>
+ </view>
+ </view>
+
+ <!-- 闅愯棌鐢诲竷锛氱敤浜庡浘鐗囧姞姘村嵃 -->
+ <canvas
+ v-if="enableWatermark"
+ :canvas-id="canvasId"
+ :id="canvasId"
+ :style="{
+ position: 'absolute',
+ left: '-9999px',
+ top: '-9999px',
+ width: canvasWidth + 'px',
+ height: canvasHeight + 'px'
+ }"
+ :width="canvasWidth"
+ :height="canvasHeight"
+ />
+ </view>
+</template>
+
+<script setup>
+import { computed, getCurrentInstance, ref, watch } from "vue";
+import config from "@/config.js";
+import { getToken } from "@/utils/auth";
+import { normalizeFileUrl } from "@/utils/filePreview";
+
+const props = defineProps({
+ modelValue: { type: Array, default: () => [] },
+ // 鏈�澶у紶鏁�
+ limit: { type: Number, default: 6 },
+ // 涓婁紶鎺ュ彛 path锛堜細鎷兼帴 config.baseUrl锛�
+ action: { type: String, default: "/invoiceLedger/upload" },
+ // 涓婁紶瀛楁鍚�
+ name: { type: String, default: "file" },
+ // 浠呯浉鍐岋紙鍥哄畾锛夛紝鏆撮湶鍑烘潵鏂逛究鏈潵鎵╁睍
+ sourceType: { type: Array, default: () => ["album"] },
+ // 閫夋嫨鍘嬬缉/鍘熷浘
+ sizeType: { type: Array, default: () => ["compressed", "original"] },
+ // 鏂囨
+ buttonText: { type: String, default: "浠庣浉鍐岄�夋嫨" },
+ tipText: { type: String, default: "浠呮敮鎸佷粠鐩稿唽閫夋嫨" },
+ // 鍙
+ readonly: { type: Boolean, default: false },
+
+ // 姘村嵃
+ enableWatermark: { type: Boolean, default: true },
+ watermarkText: { type: [String, Function], default: "" },
+
+ // 涓婁紶鍓嶄笟鍔℃牎楠�/鎷︽埅锛氳繑鍥� false 鐩存帴鎷︽埅
+ beforeUpload: { type: Function, default: null },
+});
+
+const emit = defineEmits(["update:modelValue", "change", "error"]);
+
+const instance = getCurrentInstance();
+const uploading = ref(false);
+const innerList = ref([]);
+
+watch(
+ () => props.modelValue,
+ val => {
+ innerList.value = Array.isArray(val) ? val.filter(v => v?.url) : [];
+ },
+ { immediate: true, deep: true }
+);
+
+const canvasId = `album_wm_canvas_${Date.now()}_${Math.random()
+ .toString(16)
+ .slice(2)}`;
+const canvasWidth = ref(1);
+const canvasHeight = ref(1);
+
+const uploadUrl = computed(() => {
+ const base = config.baseUrl || "";
+ return base + props.action;
+});
+
+const resolveWatermarkText = () => {
+ try {
+ return typeof props.watermarkText === "function"
+ ? props.watermarkText()
+ : props.watermarkText;
+ } catch (e) {
+ return "";
+ }
+};
+
+const normalizeUploadResultToFile = data => {
+ if (!data) return null;
+ if (Array.isArray(data)) return normalizeUploadResultToFile(data[0]);
+ if (typeof data === "string") {
+ const url = normalizeFileUrl(data);
+ return url ? { url, downloadUrl: url } : null;
+ }
+ if (typeof data === "object") {
+ const url = normalizeFileUrl(
+ data.url || data.downloadUrl || data.tempPath || data.path
+ );
+ if (url) return { ...data, url, downloadUrl: url };
+ return null;
+ }
+ return null;
+};
+
+const addWatermarkByBrowserCanvas = (tempFilePath, text) => {
+ return new Promise((resolve, reject) => {
+ try {
+ const wmText = String(text || "").trim();
+ if (!wmText) return resolve(tempFilePath);
+
+ const img = new Image();
+ img.onload = () => {
+ try {
+ const w = img.naturalWidth || img.width || 1;
+ const h = img.naturalHeight || img.height || 1;
+ const canvas = document.createElement("canvas");
+ canvas.width = w;
+ canvas.height = h;
+ const ctx = canvas.getContext("2d");
+ if (!ctx) return resolve(tempFilePath);
+
+ ctx.drawImage(img, 0, 0, w, h);
+
+ const lines = wmText.split(/\r?\n/).filter(Boolean);
+ const fontSize = Math.max(8, Math.floor(Math.min(w, h) * 0.014));
+ const padding = Math.max(3, Math.floor(fontSize * 0.35));
+ const lineGap = Math.max(1, Math.floor(fontSize * 0.1));
+ const edgeGap = 0;
+
+ ctx.font = `${fontSize}px sans-serif`;
+ const maxChars = Math.max(...lines.map(t => (t || "").length), 0);
+ const approxTextWidth = Math.min(
+ w * 0.55,
+ Math.max(fontSize * maxChars * 0.5, fontSize * 4)
+ );
+ const blockW = approxTextWidth + padding * 2;
+ const blockH =
+ lines.length * fontSize + (lines.length - 1) * lineGap + padding * 2;
+ const blockX = Math.max(0, w - blockW - edgeGap);
+ const blockY = Math.max(0, h - blockH - edgeGap);
+
+ ctx.fillStyle = "rgba(0,0,0,0.2)";
+ ctx.fillRect(blockX, blockY, blockW, blockH);
+ ctx.fillStyle = "rgba(255,255,255,0.95)";
+ lines.forEach((t, idx) => {
+ const textWidth = ctx.measureText(t).width;
+ const x = Math.max(blockX + padding, blockX + blockW - padding - textWidth);
+ const y = blockY + padding + fontSize + idx * (fontSize + lineGap);
+ ctx.fillText(t, x, y);
+ });
+
+ canvas.toBlob(
+ blob => {
+ if (!blob) return resolve(tempFilePath);
+ resolve(URL.createObjectURL(blob));
+ },
+ "image/jpeg",
+ 0.92
+ );
+ } catch (e) {
+ resolve(tempFilePath);
+ }
+ };
+ img.onerror = () => resolve(tempFilePath);
+ img.src = tempFilePath;
+ } catch (e) {
+ reject(e);
+ }
+ });
+};
+
+const addWatermarkToImage = (tempFilePath, text) => {
+ return new Promise((resolve, reject) => {
+ if (!tempFilePath) return reject(new Error("鍥剧墖璺緞涓嶅瓨鍦�"));
+ const wmText = String(text || "").trim();
+ if (!props.enableWatermark || !wmText) return resolve(tempFilePath);
+ // H5 绔紭鍏堜娇鐢ㄦ祻瑙堝櫒鍘熺敓 canvas锛岄伩鍏� uni canvas 鐢熸垚绌虹櫧鍥�
+ if (typeof window !== "undefined" && typeof document !== "undefined") {
+ addWatermarkByBrowserCanvas(tempFilePath, wmText)
+ .then(resolve)
+ .catch(() => resolve(tempFilePath));
+ return;
+ }
+
+ uni.getImageInfo({
+ src: tempFilePath,
+ success: info => {
+ try {
+ const w = info.width || 1;
+ const h = info.height || 1;
+ canvasWidth.value = w;
+ canvasHeight.value = h;
+
+ const ctx = uni.createCanvasContext(canvasId, instance);
+ ctx.drawImage(tempFilePath, 0, 0, w, h);
+
+ const lines = wmText.split(/\r?\n/).filter(Boolean);
+ const fontSize = Math.max(8, Math.floor(Math.min(w, h) * 0.014));
+ const padding = Math.max(3, Math.floor(fontSize * 0.35));
+ const lineGap = Math.max(1, Math.floor(fontSize * 0.1));
+ const edgeGap = 0;
+
+ ctx.setFontSize(fontSize);
+ ctx.setFillStyle("rgba(0,0,0,0.2)");
+
+ const maxChars = Math.max(...lines.map(t => (t || "").length), 0);
+ const approxTextWidth = Math.min(
+ w * 0.55,
+ Math.max(fontSize * maxChars * 0.5, fontSize * 4)
+ );
+ const blockW = approxTextWidth + padding * 2;
+ const blockH =
+ lines.length * fontSize + (lines.length - 1) * lineGap + padding * 2;
+ const blockX = Math.max(0, w - blockW - edgeGap);
+ const blockY = Math.max(0, h - blockH - edgeGap);
+
+ ctx.fillRect(blockX, blockY, blockW, blockH);
+ ctx.setFillStyle("rgba(255,255,255,0.95)");
+ lines.forEach((t, idx) => {
+ const textWidth = fontSize * String(t || "").length * 0.55;
+ const x = Math.max(blockX + padding, blockX + blockW - padding - textWidth);
+ const y = blockY + padding + fontSize + idx * (fontSize + lineGap);
+ ctx.fillText(t, x, y);
+ });
+
+ ctx.draw(false, () => {
+ uni.canvasToTempFilePath(
+ {
+ canvasId,
+ width: w,
+ height: h,
+ destWidth: w,
+ destHeight: h,
+ fileType: "jpg",
+ quality: 0.92,
+ success: r => resolve(r.tempFilePath),
+ fail: err => reject(err),
+ },
+ instance
+ );
+ });
+ } catch (e) {
+ reject(e);
+ }
+ },
+ fail: err => reject(err),
+ });
+ });
+};
+
+const uploadOneByH5FormData = (filePath, extraFormData = {}) => {
+ return new Promise(async (resolve, reject) => {
+ try {
+ const resp = await fetch(filePath);
+ const blob = await resp.blob();
+ const formData = new FormData();
+ const explicitName = String(
+ extraFormData?.fileName || extraFormData?.originalName || ""
+ ).trim();
+ const pathExt = getFileExtFromPath(filePath);
+ const mimeExt = String(blob?.type || "")
+ .toLowerCase()
+ .split("/")
+ .pop();
+ const uploadFileName =
+ explicitName || `file_${Date.now()}.${pathExt || mimeExt || "jpg"}`;
+ formData.append(props.name, blob, uploadFileName);
+
+ Object.keys(extraFormData || {}).forEach(key => {
+ if (key === "fileName" || key === "originalName") return;
+ const val = extraFormData[key];
+ if (val === undefined || val === null) return;
+ formData.append(key, String(val));
+ });
+
+ const xhr = new XMLHttpRequest();
+ xhr.open("POST", uploadUrl.value, true);
+ xhr.setRequestHeader("Authorization", "Bearer " + getToken());
+ xhr.onload = () => {
+ try {
+ const body = JSON.parse(xhr.responseText || "{}");
+ if (body.code === 200) {
+ const file = normalizeUploadResultToFile(body.data);
+ if (!file) return reject(new Error("涓婁紶鎴愬姛浣嗘湭杩斿洖鏂囦欢淇℃伅"));
+ resolve(file);
+ } else {
+ reject(new Error(body.msg || "涓婁紶澶辫触"));
+ }
+ } catch (e) {
+ reject(e);
+ }
+ };
+ xhr.onerror = () => reject(new Error("涓婁紶澶辫触"));
+ xhr.send(formData);
+ } catch (e) {
+ reject(e);
+ }
+ });
+};
+
+const uploadOne = (filePath, extraFormData = {}) => {
+ const isH5BlobPath =
+ typeof window !== "undefined" &&
+ typeof FormData !== "undefined" &&
+ String(filePath || "").startsWith("blob:");
+ if (isH5BlobPath) {
+ return uploadOneByH5FormData(filePath, extraFormData);
+ }
+ return new Promise((resolve, reject) => {
+ uni.uploadFile({
+ url: uploadUrl.value,
+ filePath,
+ name: props.name,
+ formData: extraFormData,
+ header: { Authorization: "Bearer " + getToken() },
+ success: res => {
+ try {
+ const body = JSON.parse(res.data || "{}");
+ if (body.code === 200) {
+ const file = normalizeUploadResultToFile(body.data);
+ if (!file) return reject(new Error("涓婁紶鎴愬姛浣嗘湭杩斿洖鏂囦欢淇℃伅"));
+ resolve(file);
+ } else {
+ reject(new Error(body.msg || "涓婁紶澶辫触"));
+ }
+ } catch (e) {
+ reject(e);
+ }
+ },
+ fail: err => reject(err),
+ });
+ });
+};
+
+const getFileExtFromPath = filePath => {
+ const raw = String(filePath || "").split("?")[0].split("#")[0];
+ const match = raw.match(/\.([a-zA-Z0-9]+)$/);
+ return (match?.[1] || "").toLowerCase();
+};
+
+const getFileNameFromPath = filePath => {
+ const raw = String(filePath || "").split("?")[0].split("#")[0];
+ const seg = raw.split("/").pop() || "";
+ return seg.split("\\").pop() || "";
+};
+
+const buildUploadMeta = (originalPath, rawOriginalName = "") => {
+ const cleanOriginalName = String(rawOriginalName || "").trim();
+ const pathName = getFileNameFromPath(originalPath);
+ const fallbackName = cleanOriginalName || pathName;
+ const ext =
+ getFileExtFromPath(fallbackName) || getFileExtFromPath(originalPath) || "jpg";
+ const fileName = fallbackName || `${Date.now()}.${ext}`;
+ return {
+ ext,
+ fileName,
+ formData: { fileName },
+ };
+};
+
+const resolvePreviewPath = (wmPath, originPath) => {
+ return new Promise(resolve => {
+ if (!wmPath) return resolve(originPath || "");
+ uni.getImageInfo({
+ src: wmPath,
+ success: () => resolve(wmPath),
+ fail: () => resolve(originPath || wmPath || ""),
+ });
+ });
+};
+
+const emitList = list => {
+ emit("update:modelValue", list);
+ emit("change", list);
+};
+
+const chooseFromAlbum = () => {
+ if (props.readonly) return;
+ if (typeof props.beforeUpload === "function") {
+ const ok = props.beforeUpload();
+ if (ok === false) return;
+ }
+
+ const remaining = Math.max(0, props.limit - innerList.value.length);
+ if (remaining <= 0) {
+ uni.showToast({ title: `鏈�澶氬彧鑳戒笂浼�${props.limit}寮燻, icon: "none" });
+ return;
+ }
+
+ uni.chooseImage({
+ count: remaining,
+ sizeType: props.sizeType,
+ sourceType: props.sourceType,
+ success: async res => {
+ const paths = res?.tempFilePaths || [];
+ const tempFiles = Array.isArray(res?.tempFiles) ? res.tempFiles : [];
+ if (!paths.length) return;
+
+ uploading.value = true;
+ uni.showLoading({ title: "姝e湪涓婁紶...", mask: true });
+ try {
+ const wmText = resolveWatermarkText();
+ for (let idx = 0; idx < paths.length; idx++) {
+ const p = paths[idx];
+ const picked = tempFiles[idx] || {};
+ const originalName = picked.name || getFileNameFromPath(p);
+ const wmPath = await addWatermarkToImage(p, wmText);
+ const uploadMeta = buildUploadMeta(p, originalName);
+ const uploaded = await uploadOne(wmPath, uploadMeta.formData);
+ // 淇濈暀鍚庣杩斿洖鍦板潃鐢ㄤ簬鍚庣画鍥炴樉/鎻愪氦锛岀珛鍗抽瑙堜紭鍏堝睍绀哄姞姘村嵃鍚庣殑鏈湴鍥�
+ uploaded.previewUrl = await resolvePreviewPath(wmPath, p);
+ uploaded.originalName = uploaded.originalName || uploadMeta.fileName;
+ uploaded.suffix = uploaded.suffix || uploadMeta.ext;
+ innerList.value.push(uploaded);
+ emitList(innerList.value.slice());
+ }
+ uni.showToast({ title: "涓婁紶鎴愬姛", icon: "none" });
+ } catch (e) {
+ emit("error", e);
+ uni.showToast({ title: e?.message || "涓婁紶澶辫触", icon: "none" });
+ } finally {
+ uploading.value = false;
+ uni.hideLoading();
+ }
+ },
+ });
+};
+
+const remove = idx => {
+ const list = innerList.value.slice();
+ list.splice(idx, 1);
+ innerList.value = list;
+ emitList(list);
+};
+
+const preview = idx => {
+ const urls = innerList.value
+ .map(i => i?.previewUrl || i?.url || i?.downloadUrl)
+ .filter(Boolean);
+ if (!urls.length) return;
+ uni.previewImage({ urls, current: urls[idx] || urls[0] });
+};
+</script>
+
+<style scoped lang="scss">
+.album-image-upload {
+ width: 100%;
+}
+
+.actions {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ padding: 6px 0;
+}
+
+.tip {
+ font-size: 12px;
+ color: #999;
+}
+
+.list {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 8px;
+ padding: 8px 0 4px;
+}
+
+.item {
+ position: relative;
+ width: 76px;
+ height: 76px;
+ border-radius: 8px;
+ overflow: hidden;
+ background: #f5f5f5;
+ border: 1px solid #eee;
+}
+
+.img {
+ width: 100%;
+ height: 100%;
+}
+
+.del {
+ position: absolute;
+ right: 4px;
+ top: 4px;
+ width: 18px;
+ height: 18px;
+ border-radius: 50%;
+ background: rgba(0, 0, 0, 0.55);
+ color: #fff;
+ font-size: 14px;
+ line-height: 18px;
+ text-align: center;
+}
+</style>
+
diff --git a/src/pages/cooperativeOffice/clientVisit/detail.vue b/src/pages/cooperativeOffice/clientVisit/detail.vue
index 0bab2d7..5fb3677 100644
--- a/src/pages/cooperativeOffice/clientVisit/detail.vue
+++ b/src/pages/cooperativeOffice/clientVisit/detail.vue
@@ -61,6 +61,18 @@
</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="澶囨敞淇℃伅">
@@ -105,6 +117,9 @@
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,
@@ -128,6 +143,12 @@
longitude: "",
locationAddress: "",
remark: "",
+ // 涓存椂鏂囦欢id鍒楄〃锛堟彁浜ょ敤锛�
+ tempFileIds: [],
+ // 姝e紡鏂囦欢鍒楄〃锛堝洖鏄剧敤锛屽悗绔煡璇細杩斿洖锛�
+ commonFileList: [],
+ // 涓婁紶缁勪欢鍥炰紶鐨勬枃浠跺垪琛紙鍚庣鑻ヤ笉鎺ユ敹鍙拷鐣ワ紱鍏堥殢琛ㄥ崟鎻愪氦锛�
+ storageBlobDTO: [],
});
// 椤甸潰鐘舵��
@@ -275,6 +296,23 @@
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);
@@ -305,6 +343,72 @@
}
};
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");
@@ -312,6 +416,35 @@
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;
}
@@ -330,21 +463,43 @@
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;
diff --git a/src/utils/filePreview.js b/src/utils/filePreview.js
new file mode 100644
index 0000000..a98ee1e
--- /dev/null
+++ b/src/utils/filePreview.js
@@ -0,0 +1,67 @@
+import config from "@/config.js";
+
+// 缁熶竴鏂囦欢棰勮鍦板潃杞崲锛堝榻� inspectionUpload 鐨� normalizeFileUrl 瑙勫垯锛�
+export const normalizeFileUrl = (rawUrl = "") => {
+ let fileUrl = rawUrl || "";
+ if (typeof fileUrl === "string") {
+ fileUrl = fileUrl.trim().replace(/^['"]|['"]$/g, "");
+ }
+ const javaApi = config.fileUrl || config.baseUrl || "";
+ const localPrefixes = ["wxfile://", "file://", "content://", "blob:", "data:"];
+
+ if (localPrefixes.some(prefix => String(fileUrl).startsWith(prefix))) {
+ return fileUrl;
+ }
+
+ if (fileUrl && String(fileUrl).indexOf("\\") > -1) {
+ const lowerPath = String(fileUrl).toLowerCase();
+ const uploadPathIndex = lowerPath.indexOf("uploadpath");
+ const prodIndex = lowerPath.indexOf("\\prod\\");
+ const tempIndex = lowerPath.indexOf("\\temp\\");
+
+ if (uploadPathIndex > -1) {
+ fileUrl = String(fileUrl).substring(uploadPathIndex).replace(/\\/g, "/");
+ } else if (prodIndex > -1) {
+ const relative = String(fileUrl)
+ .substring(prodIndex + "\\prod\\".length)
+ .replace(/\\/g, "/");
+ fileUrl = `/profile/prod/${relative}`;
+ } else if (tempIndex > -1) {
+ const relative = String(fileUrl)
+ .substring(tempIndex + "\\temp\\".length)
+ .replace(/\\/g, "/");
+ fileUrl = `/profile/temp/${relative}`;
+ } else {
+ fileUrl = String(fileUrl).replace(/\\/g, "/");
+ }
+ }
+
+ // /javaWork/.../file/prod/xxx -> /profile/prod/xxx
+ const normalizedLower = String(fileUrl).toLowerCase();
+ const fileProdIdx = normalizedLower.indexOf("/file/prod/");
+ if (fileProdIdx > -1) {
+ fileUrl = `/profile/prod/${String(fileUrl).substring(
+ fileProdIdx + "/file/prod/".length
+ )}`;
+ }
+
+ // /javaWork/.../file/temp/xxx -> /profile/temp/xxx
+ const fileTempIdx = normalizedLower.indexOf("/file/temp/");
+ if (fileTempIdx > -1) {
+ fileUrl = `/profile/temp/${String(fileUrl).substring(
+ fileTempIdx + "/file/temp/".length
+ )}`;
+ }
+
+ if (/^\/?uploadPath/i.test(String(fileUrl))) {
+ fileUrl = String(fileUrl).replace(/^\/?uploadPath/i, "/profile");
+ }
+
+ if (fileUrl && !String(fileUrl).startsWith("http")) {
+ if (!String(fileUrl).startsWith("/")) fileUrl = "/" + fileUrl;
+ fileUrl = javaApi + fileUrl;
+ }
+
+ return fileUrl;
+};
+
--
Gitblit v1.9.3