From 346804c2fe1e3189b89947c17685ff9ab1e4922c Mon Sep 17 00:00:00 2001
From: yyb <995253665@qq.com>
Date: 星期二, 28 四月 2026 11:55:22 +0800
Subject: [PATCH] 新增自适应水印功能及访问照片展示功能

---
 src/pages/cooperativeOffice/clientVisit/view.vue |   98 ++++++++++++++++++++++++++++++++
 src/components/AlbumImageUpload/index.vue        |   36 +++++++++--
 2 files changed, 126 insertions(+), 8 deletions(-)

diff --git a/src/components/AlbumImageUpload/index.vue b/src/components/AlbumImageUpload/index.vue
index e30231f..6a4799b 100644
--- a/src/components/AlbumImageUpload/index.vue
+++ b/src/components/AlbumImageUpload/index.vue
@@ -131,6 +131,30 @@
   return null;
 };
 
+const getAdaptiveWatermarkStyle = (width, height) => {
+  const w = Math.max(1, Number(width) || 1);
+  const h = Math.max(1, Number(height) || 1);
+  const shortSide = Math.min(w, h);
+  const longSide = Math.max(w, h);
+  // 鍒嗘閫傞厤锛氬皬鍥惧噺灏忓瓧鍙凤紝澶у浘淇濇寔褰撳墠瑙嗚鏁堟灉
+  const isSmallImage = shortSide <= 720;
+  const fontSize = isSmallImage
+    ? Math.max(
+        10,
+        Math.min(28, Math.floor(shortSide * 0.016 + longSide * 0.004))
+      )
+    : Math.max(
+        14,
+        Math.min(56, Math.floor(shortSide * 0.022 + longSide * 0.006))
+      );
+  return {
+    fontSize,
+    padding: Math.max(isSmallImage ? 4 : 5, Math.floor(fontSize * 0.4)),
+    lineGap: Math.max(isSmallImage ? 1 : 2, Math.floor(fontSize * 0.16)),
+    edgeGap: 0,
+  };
+};
+
 const addWatermarkByBrowserCanvas = (tempFilePath, text) => {
   return new Promise((resolve, reject) => {
     try {
@@ -151,10 +175,8 @@
           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;
+          const { fontSize, padding, lineGap, edgeGap } =
+            getAdaptiveWatermarkStyle(w, h);
 
           ctx.font = `${fontSize}px sans-serif`;
           const maxChars = Math.max(...lines.map(t => (t || "").length), 0);
@@ -224,10 +246,8 @@
           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;
+          const { fontSize, padding, lineGap, edgeGap } =
+            getAdaptiveWatermarkStyle(w, h);
 
           ctx.setFontSize(fontSize);
           ctx.setFillStyle("rgba(0,0,0,0.2)");
diff --git a/src/pages/cooperativeOffice/clientVisit/view.vue b/src/pages/cooperativeOffice/clientVisit/view.vue
index 18d1d98..bee21f2 100644
--- a/src/pages/cooperativeOffice/clientVisit/view.vue
+++ b/src/pages/cooperativeOffice/clientVisit/view.vue
@@ -44,6 +44,22 @@
           <text class="info-label">缁忕含搴�</text>
           <text class="info-value">{{ form.latitude }}, {{ form.longitude }}</text>
         </view>
+        <view class="info-item photo-item">
+          <text class="info-label">鎷滆鐓х墖</text>
+          <view class="photo-wrap">
+            <view v-if="visitImageList.length"
+                  class="photo-list">
+              <image v-for="(img, idx) in visitImageList"
+                     :key="img.id || img.url || idx"
+                     class="photo-img"
+                     :src="img.url"
+                     mode="aspectFill"
+                     @click="previewVisitImage(idx)" />
+            </view>
+            <text v-else
+                  class="empty-text">-</text>
+          </view>
+        </view>
       </view>
       <!-- 澶囨敞淇℃伅 -->
       <view class="section">
@@ -68,6 +84,7 @@
 
   import { ref, onMounted } from "vue";
   import PageHeader from "@/components/PageHeader.vue";
+  import { normalizeFileUrl } from "@/utils/filePreview";
   import useUserStore from "@/store/modules/user";
 
   const userStore = useUserStore();
@@ -86,6 +103,56 @@
     locationAddress: "",
     remark: "",
   });
+  const visitImageList = ref([]);
+
+  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?.downloadUrl,
+      file?.path,
+      file?.filePath,
+      file?.tempPath,
+      file?.urlFull,
+    ].filter(Boolean);
+    for (const raw of candidates) {
+      if (!isLikelyPathValue(raw)) continue;
+      const normalized = normalizeFileUrl(raw);
+      if (normalized) return normalized;
+    }
+    return "";
+  };
+
+  const buildVisitImageList = row => {
+    const rawList = [];
+    if (Array.isArray(row?.commonFileList)) rawList.push(...row.commonFileList);
+    if (Array.isArray(row?.storageBlobDTO)) rawList.push(...row.storageBlobDTO);
+    const uniq = [];
+    const seen = new Set();
+    rawList.forEach(item => {
+      const url = buildVisitFilePreviewUrl(item);
+      if (!url || seen.has(url)) return;
+      seen.add(url);
+      uniq.push({ ...item, url });
+    });
+    visitImageList.value = uniq;
+  };
+
+  const previewVisitImage = idx => {
+    const urls = visitImageList.value.map(item => item?.url).filter(Boolean);
+    if (!urls.length) return;
+    uni.previewImage({ urls, current: urls[idx] || urls[0] });
+  };
 
   // 杩斿洖涓婁竴椤�
   const goBack = () => {
@@ -100,6 +167,7 @@
     const row = uni.getStorageSync("clientVisit");
     if (row) {
       form.value = { ...row };
+      buildVisitImageList(row);
     } else {
       showToast("鏆傛棤鎷滆璁板綍鏁版嵁");
     }
@@ -165,6 +233,36 @@
     text-align: right;
   }
 
+  .photo-item {
+    align-items: flex-start;
+  }
+
+  .photo-wrap {
+    flex: 1;
+    display: flex;
+    justify-content: flex-start;
+  }
+
+  .photo-list {
+    display: flex;
+    flex-wrap: wrap;
+    justify-content: flex-start;
+    gap: 8px;
+    width: 100%;
+  }
+
+  .photo-img {
+    width: 72px;
+    height: 72px;
+    border-radius: 8px;
+    background: #f5f5f5;
+  }
+
+  .empty-text {
+    color: #999;
+    line-height: 22px;
+  }
+
   .multi-line {
     text-align: left;
     word-break: break-all;

--
Gitblit v1.9.3