yyb
2026-04-28 346804c2fe1e3189b89947c17685ff9ab1e4922c
新增自适应水印功能及访问照片展示功能
已修改2个文件
134 ■■■■■ 文件已修改
src/components/AlbumImageUpload/index.vue 36 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/cooperativeOffice/clientVisit/view.vue 98 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
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)");
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;