From af4f45eaa2703ecf991bd10f07f6df179f2677d9 Mon Sep 17 00:00:00 2001
From: spring <2396852758@qq.com>
Date: 星期三, 19 十一月 2025 10:04:45 +0800
Subject: [PATCH] Merge branch 'refs/heads/yyb'

---
 src/pages/routingInspection/upload.vue |  587 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 587 insertions(+), 0 deletions(-)

diff --git a/src/pages/routingInspection/upload.vue b/src/pages/routingInspection/upload.vue
new file mode 100644
index 0000000..1a50117
--- /dev/null
+++ b/src/pages/routingInspection/upload.vue
@@ -0,0 +1,587 @@
+<template>
+  <view class="attachment-container">
+    <!-- 澶撮儴鎿嶄綔鍖� -->
+    <view class="header-actions">
+      <wd-button
+        icon="file-add"
+        :round="false"
+        size="small"
+        custom-class="add_btn"
+        @click="addAttachment"
+        v-if="isEdit"
+      >
+        鏂板
+      </wd-button>
+    </view>
+
+    <!-- 闄勪欢鍒楄〃 -->
+    <view class="attachment-list">
+      <wd-status-tip
+        v-if="attachmentList.length === 0"
+        image="content"
+        tip="鏆傛棤闄勪欢"
+        custom-class="status-tip-full"
+      />
+
+      <view v-for="(item, index) in attachmentList" :key="item.id || index" class="attachment-card">
+        <view class="media-wrapper" @click="previewAttachment(item)">
+          <!-- 鍥剧墖棰勮 -->
+          <template v-if="isImageType(item.url)">
+            <image
+              :src="getFullUrl(item.url)"
+              mode="aspectFill"
+              class="media-preview"
+              style="width: 100%; height: 100%"
+              @error="onImageError(item, index)"
+              @load="onImageLoad(item, index)"
+              :show-menu-by-longpress="true"
+            />
+            <!-- 鍔犺浇涓伄缃� -->
+            <view v-if="item.loading" class="loading-mask">
+              <text class="loading-text">鍔犺浇涓�...</text>
+            </view>
+            <!-- 鍥剧墖鍔犺浇澶辫触鏄剧ず榛樿鍥炬爣 -->
+            <view v-if="item.loadError" class="file-icon-wrapper error-overlay">
+              <wd-icon name="picture" size="48px" color="#ccc" />
+              <text class="file-name error-text">鍔犺浇澶辫触</text>
+            </view>
+          </template>
+
+          <!-- 瑙嗛棰勮 -->
+          <template v-else-if="isVideoType(item.url)">
+            <video
+              :src="getFullUrl(item.url)"
+              class="media-preview"
+              :controls="false"
+              :show-center-play-btn="true"
+              @error="onVideoError(item, index)"
+              object-fit="cover"
+            />
+            <!-- 瑙嗛鍔犺浇澶辫触鏄剧ず榛樿鍥炬爣 -->
+            <view v-if="item.loadError" class="file-icon-wrapper error-overlay">
+              <wd-icon name="video" size="48px" color="#ccc" />
+              <text class="file-name error-text">鍔犺浇澶辫触</text>
+            </view>
+          </template>
+
+          <!-- 鍏朵粬鏂囦欢绫诲瀷鏄剧ず鍥炬爣 -->
+          <view v-else class="file-icon-wrapper">
+            <wd-icon name="file-outline" size="48px" color="#999" />
+            <text class="file-name">鏂囦欢</text>
+          </view>
+
+          <!-- 鍒犻櫎鎸夐挳 -->
+          <view class="delete-btn" @click.stop="deleteAttachment(item.id)" v-if="isEdit">
+            <wd-icon name="delete" color="#fff" size="20px" />
+          </view>
+        </view>
+      </view>
+    </view>
+
+    <wd-toast />
+  </view>
+</template>
+
+<script setup lang="ts">
+import { ref, computed, watch } from "vue";
+import { useToast } from "wot-design-uni";
+import AttachmentAPI from "@/api/product/attachment";
+
+// H5 浣跨敤 VITE_APP_BASE_API 浣滀负浠g悊璺緞锛屽叾浠栧钩鍙颁娇鐢� VITE_APP_API_URL 浣滀负璇锋眰璺緞
+let baseUrlValue = import.meta.env.VITE_APP_API_URL || "";
+// #ifdef H5
+baseUrlValue = import.meta.env.VITE_APP_BASE_API || "";
+// #endif
+
+const baseUrl = ref(baseUrlValue); // 浣跨敤ref浣垮叾鍦ㄦā鏉夸腑鍙闂�
+
+// 澶栭儴鍙傛暟
+const props = defineProps({
+  detailData: { type: Object, default: () => ({}) },
+  isEdit: { type: Boolean, default: false },
+  deviceType: { type: String, default: "" },
+});
+
+const toast = useToast();
+
+// 鑾峰彇鍒濆鏁版嵁
+const getInitialData = () => {
+  // 澶勭悊涓嶅悓鐨勬暟鎹粨鏋�
+  let data = props.detailData;
+
+  // 濡傛灉鏄� ref 瀵硅薄锛岃幏鍙栧叾 value
+  if (data && typeof data === "object" && "value" in data) {
+    data = data.value;
+  }
+
+  // 濡傛灉鏄暟缁勶紝鐩存帴杩斿洖
+  if (Array.isArray(data)) {
+    return data.map((item) => ({
+      ...item,
+      loading: false,
+      loadError: false,
+    }));
+  }
+
+  // 濡傛灉鏈� files 灞炴��
+  if (data && data.files) {
+    const files = Array.isArray(data.files) ? data.files : [];
+    return files.map((item) => ({
+      ...item,
+      loading: false,
+      loadError: false,
+    }));
+  }
+
+  return [];
+};
+
+const attachmentList = ref<any[]>(getInitialData());
+const attachmentIds = ref<string[]>(attachmentList.value.map((item: any) => item.id) || []);
+
+// 鐩戝惉 props.detailData 鍙樺寲
+watch(
+  () => props.detailData,
+  (newVal) => {
+    const newData = getInitialData();
+    if (newData.length > 0) {
+      attachmentList.value = newData.map((item) => ({
+        ...item,
+        loading: false,
+        loadError: false,
+      }));
+      attachmentIds.value = newData.map((item: any) => item.id);
+    }
+  },
+  { deep: true, immediate: false }
+);
+
+// 鑾峰彇瀹屾暣鐨勫浘鐗�/瑙嗛 URL
+const getFullUrl = (url: string) => {
+  if (!url) return "";
+
+  // 濡傛灉宸茬粡鏄畬鏁寸殑 URL锛坔ttp 鎴� https 寮�澶达級锛岀洿鎺ヨ繑鍥�
+  if (url.startsWith("http://") || url.startsWith("https://")) {
+    return url;
+  }
+
+  // 妫�鏌� baseUrl 鏄惁鏈夋晥
+  if (!baseUrl.value) {
+    console.error("鉂� baseUrl鏈厤缃紝url:", url);
+    return url;
+  }
+
+  // 濡傛灉鏄浉瀵硅矾寰勶紝鎷兼帴鍩虹 URL
+  const separator = url.startsWith("/") || baseUrl.value.endsWith("/") ? "" : "/";
+  return `${baseUrl.value}${separator}${url}`;
+};
+
+// 鍥剧墖鍔犺浇鎴愬姛
+const onImageLoad = (item: any, index: number) => {
+  item.loading = false;
+  item.loadError = false;
+  attachmentList.value = [...attachmentList.value];
+};
+
+// 鍥剧墖鍔犺浇澶辫触
+const onImageError = (item: any, index: number) => {
+  console.error(`鍥剧墖鍔犺浇澶辫触 [${index}]:`, item.url);
+  item.loading = false;
+  item.loadError = true;
+  attachmentList.value = [...attachmentList.value];
+};
+
+// 瑙嗛鍔犺浇澶辫触
+const onVideoError = (item: any, index: number) => {
+  console.error(`瑙嗛鍔犺浇澶辫触 [${index}]:`, item.url);
+  item.loading = false;
+  item.loadError = true;
+  attachmentList.value = [...attachmentList.value];
+};
+
+// 鏂板闄勪欢
+const addAttachment = () => {
+  // 鏄剧ず閫夋嫨鏂囦欢绫诲瀷鐨勫脊绐�
+  uni.showActionSheet({
+    itemList: ["閫夋嫨鍥剧墖", /* "閫夋嫨瑙嗛", */ "鎷嶇収" /* , "褰曞儚" */],
+    success: (res) => {
+      switch (res.tapIndex) {
+        case 0: // 閫夋嫨鍥剧墖
+          chooseImages();
+          break;
+        // case 1: // 閫夋嫨瑙嗛
+        //   chooseVideos();
+        //   break;
+        case 1: // 鎷嶇収
+          takePhoto();
+          break;
+        // case 3: // 褰曞儚
+        //   recordVideo();
+        //   break;
+      }
+    },
+    fail: (error) => {
+      console.error("閫夋嫨鏂囦欢绫诲瀷澶辫触:", error);
+      toast.show("閫夋嫨鏂囦欢绫诲瀷澶辫触");
+    },
+  });
+};
+
+// 閫夋嫨鍥剧墖
+const chooseImages = () => {
+  uni.chooseImage({
+    count: 9,
+    sizeType: ["original", "compressed"],
+    sourceType: ["album"],
+    success: async (res) => {
+      const filePaths = Array.isArray(res.tempFilePaths) ? res.tempFilePaths : [res.tempFilePaths];
+      await handleFileUpload(filePaths);
+    },
+    fail: (error) => {
+      console.error("閫夋嫨鍥剧墖澶辫触:", error);
+      toast.show("閫夋嫨鍥剧墖澶辫触");
+    },
+  });
+};
+
+// 閫夋嫨瑙嗛
+const chooseVideos = () => {
+  uni.chooseVideo({
+    sourceType: ["album"],
+    maxDuration: 60,
+    camera: "back",
+    success: async (res) => {
+      await handleFileUpload([res.tempFilePath]);
+    },
+    fail: (error) => {
+      console.error("閫夋嫨瑙嗛澶辫触:", error);
+      toast.show("閫夋嫨瑙嗛澶辫触");
+    },
+  });
+};
+
+// 鎷嶇収
+const takePhoto = () => {
+  uni.chooseImage({
+    count: 1,
+    sizeType: ["original", "compressed"],
+    sourceType: ["camera"],
+    success: async (res) => {
+      const filePaths = Array.isArray(res.tempFilePaths) ? res.tempFilePaths : [res.tempFilePaths];
+      await handleFileUpload(filePaths);
+    },
+    fail: (error) => {
+      console.error("鎷嶇収澶辫触:", error);
+      toast.show("鎷嶇収澶辫触");
+    },
+  });
+};
+
+// 褰曞儚
+const recordVideo = () => {
+  uni.chooseVideo({
+    sourceType: ["camera"],
+    maxDuration: 60,
+    camera: "back",
+    success: async (res) => {
+      await handleFileUpload([res.tempFilePath]);
+    },
+    fail: (error) => {
+      console.error("褰曞儚澶辫触:", error);
+      toast.show("褰曞儚澶辫触");
+    },
+  });
+};
+
+// 澶勭悊鏂囦欢涓婁紶
+const handleFileUpload = async (filePaths: string[]) => {
+  try {
+    toast.show("姝e湪涓婁紶...");
+
+    // 涓婁紶鏂囦欢
+    const uploadResults: any = await AttachmentAPI.uploadAttachmentFiles(filePaths);
+    const result = uploadResults.map((it: any) => {
+      return it.data;
+    });
+
+    // 鏇存柊闄勪欢鍒楄〃
+    const flattenedResult = result.flat();
+    attachmentList.value.push(...flattenedResult);
+
+    // 鎻愬彇闄勪欢ID
+    attachmentIds.value = attachmentList.value.map((item: any) => item.id);
+    toast.show("涓婁紶鎴愬姛");
+  } catch (error) {
+    console.error("涓婁紶澶辫触:", error);
+    toast.show("涓婁紶澶辫触");
+  }
+};
+
+// 鍒犻櫎闄勪欢
+const deleteAttachment = async (aid: number) => {
+  try {
+    uni.showModal({
+      title: "纭鍒犻櫎",
+      content: "纭畾瑕佸垹闄よ繖涓檮浠跺悧锛�",
+      success: async (res) => {
+        if (res.confirm) {
+          // 鍓嶇鎵嬪姩鍒犻櫎锛氱洿鎺ヤ粠鍒楄〃涓Щ闄よ繖鏉℃暟鎹�
+          attachmentList.value = attachmentList.value.filter((item) => item.id !== aid);
+
+          // 鑾峰彇鍓╀綑鐨勯檮浠禝D
+          attachmentIds.value = attachmentList.value.map((item) => item.id);
+          toast.show("鍒犻櫎鎴愬姛");
+        }
+      },
+    });
+  } catch (error) {
+    console.error("鍒犻櫎澶辫触:", error);
+    toast.show("鍒犻櫎澶辫触");
+  }
+};
+
+// 棰勮闄勪欢
+const previewAttachment = (item: any) => {
+  // 鏍规嵁鏂囦欢绫诲瀷杩涜棰勮
+  const fileType = getFileType(item.url);
+  const fullUrl = getFullUrl(item.url);
+
+  if (fileType.startsWith("image")) {
+    // 鍥剧墖棰勮
+    uni.previewImage({
+      urls: [fullUrl],
+      current: fullUrl,
+    });
+  } else {
+    // 鍏朵粬鏂囦欢绫诲瀷锛屽彲浠ヤ笅杞芥垨鎵撳紑
+    uni.downloadFile({
+      url: fullUrl,
+      success: (res) => {
+        uni.openDocument({
+          filePath: res.tempFilePath,
+          success: () => {
+            // 鎵撳紑鏂囨。鎴愬姛
+          },
+          fail: (error) => {
+            console.error("鎵撳紑鏂囨。澶辫触:", error);
+            toast.show("鏃犳硶棰勮姝ゆ枃浠剁被鍨�");
+          },
+        });
+      },
+      fail: (error) => {
+        console.error("涓嬭浇鏂囦欢澶辫触:", error);
+        toast.show("涓嬭浇鏂囦欢澶辫触");
+      },
+    });
+  }
+};
+
+// 浠� URL 鎴栨枃浠跺悕涓彁鍙栨墿灞曞悕
+const getExtension = (urlOrFileName: string) => {
+  if (!urlOrFileName) return "";
+  // 绉婚櫎鏌ヨ鍙傛暟鍜屽搱甯�
+  const cleanUrl = urlOrFileName.split("?")[0].split("#")[0];
+  // 鑾峰彇鏈�鍚庝竴涓偣鍚庨潰鐨勫唴瀹�
+  const extension = cleanUrl.split(".").pop()?.toLowerCase();
+  return extension || "";
+};
+
+// 鍒ゆ柇鏄惁涓哄浘鐗囩被鍨�
+const isImageType = (urlOrFileName: string) => {
+  const extension = getExtension(urlOrFileName);
+  return ["jpg", "jpeg", "png", "gif", "bmp", "webp"].includes(extension);
+};
+
+// 鍒ゆ柇鏄惁涓鸿棰戠被鍨�
+const isVideoType = (urlOrFileName: string) => {
+  const extension = getExtension(urlOrFileName);
+  return ["mp4", "mov", "avi", "wmv", "flv", "mkv", "webm"].includes(extension);
+};
+
+// 鑾峰彇鏂囦欢绫诲瀷
+const getFileType = (urlOrFileName: string) => {
+  if (!urlOrFileName) return "unknown";
+  const extension = getExtension(urlOrFileName);
+  switch (extension) {
+    case "jpg":
+    case "jpeg":
+    case "png":
+    case "gif":
+    case "bmp":
+    case "webp":
+      return "image";
+    case "mp4":
+    case "mov":
+    case "avi":
+    case "wmv":
+    case "flv":
+    case "mkv":
+    case "webm":
+      return "video";
+    case "pdf":
+      return "pdf";
+    case "doc":
+    case "docx":
+      return "word";
+    case "xls":
+    case "xlsx":
+      return "excel";
+    case "ppt":
+    case "pptx":
+      return "powerpoint";
+    case "txt":
+      return "text";
+    case "zip":
+    case "rar":
+      return "archive";
+    default:
+      return "file";
+  }
+};
+
+// 鏍煎紡鍖栨枃浠跺ぇ灏�
+const formatFileSize = (size: number) => {
+  if (size < 1024) return size + " B";
+  if (size < 1024 * 1024) return (size / 1024).toFixed(1) + " KB";
+  return (size / (1024 * 1024)).toFixed(1) + " MB";
+};
+
+// 鏍煎紡鍖栨椂闂�
+const formatTime = (time: string) => {
+  const date = new Date(time);
+  return date.toLocaleString();
+};
+// 瀵瑰鏆撮湶鏂规硶锛氳幏鍙栨墍鏈夐渶鎻愪氦鐨勬枃浠�
+const getSubmitFiles = () => ({
+  newFiles: attachmentIds.value || [],
+});
+defineExpose({ getSubmitFiles });
+</script>
+
+<style lang="scss" scoped>
+.attachment-container {
+  padding: 12px;
+  background: #f3f9f8;
+  min-height: 100vh;
+}
+
+.header-actions {
+  margin-bottom: 12px;
+
+  :deep(.add_btn) {
+    background: #0d867f;
+    color: white;
+    border: none;
+  }
+}
+
+.attachment-list {
+  display: grid;
+  grid-template-columns: repeat(3, 1fr);
+  gap: 8px;
+
+  :deep(.status-tip-full) {
+    grid-column: 1 / -1;
+    width: 100%;
+  }
+
+  .attachment-card {
+    width: 100%;
+    position: relative;
+
+    // 浣跨敤 padding-top 瀹炵幇姝f柟褰紙鍏煎鎬ф洿濂斤級
+    &::before {
+      content: "";
+      display: block;
+      padding-top: 100%; // 楂樺害绛変簬瀹藉害
+    }
+  }
+}
+
+.media-wrapper {
+  position: absolute;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  border-radius: 8px;
+  overflow: hidden;
+  background: #f5f5f5;
+
+  .media-preview {
+    width: 100%;
+    height: 100%;
+    object-fit: cover;
+    display: block;
+  }
+
+  .loading-mask {
+    position: absolute;
+    top: 0;
+    left: 0;
+    right: 0;
+    bottom: 0;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    background: rgba(0, 0, 0, 0.3);
+    z-index: 5;
+
+    .loading-text {
+      font-size: 12px;
+      color: #fff;
+    }
+  }
+
+  .file-icon-wrapper {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: center;
+    width: 100%;
+    height: 100%;
+    padding: 8px;
+    text-align: center;
+
+    .file-name {
+      margin-top: 8px;
+      font-size: 12px;
+      color: #666;
+      word-break: break-all;
+      display: -webkit-box;
+      -webkit-line-clamp: 2;
+      line-clamp: 2;
+      -webkit-box-orient: vertical;
+      overflow: hidden;
+
+      &.error-text {
+        color: #ff4757;
+      }
+    }
+
+    &.error-overlay {
+      position: absolute;
+      top: 0;
+      left: 0;
+      right: 0;
+      bottom: 0;
+      background: rgba(255, 255, 255, 0.9);
+      z-index: 5;
+    }
+  }
+
+  .delete-btn {
+    position: absolute;
+    top: 4px;
+    right: 4px;
+    width: 28px;
+    height: 28px;
+    border-radius: 50%;
+    background: rgba(0, 0, 0, 0.5);
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    z-index: 10;
+  }
+}
+</style>

--
Gitblit v1.9.3