yyb
2026-04-28 6ff219555c53c37d4daae0747751043ea103af1e
水印签到拜访照片
已添加2个文件
已修改1个文件
755 ■■■■■ 文件已修改
src/components/AlbumImageUpload/index.vue 527 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/cooperativeOffice/clientVisit/detail.vue 161 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/filePreview.js 67 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
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: "正在上传...", 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>
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;
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;
};