yyb
2026-04-28 6ff219555c53c37d4daae0747751043ea103af1e
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: [],
    // 正式文件列表(回显用,后端查询会返回)
    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;