From 1e71cfb6ec97cff6531dec65a3fb5cb24b2c18ac Mon Sep 17 00:00:00 2001
From: spring <2396852758@qq.com>
Date: 星期一, 03 十一月 2025 09:57:48 +0800
Subject: [PATCH] fix: 附件完善
---
 src/pages/routingInspection/upload.vue          |  240 ++++++++++++++-----
 src/pages/production/twist/attachment/index.vue |  221 ++++++++++++++----
 src/pages/production/wire/attachment/index.vue  |  224 ++++++++++++++----
 3 files changed, 517 insertions(+), 168 deletions(-)
diff --git a/src/pages/production/twist/attachment/index.vue b/src/pages/production/twist/attachment/index.vue
index c654299..0d17e10 100644
--- a/src/pages/production/twist/attachment/index.vue
+++ b/src/pages/production/twist/attachment/index.vue
@@ -17,26 +17,54 @@
     <view class="attachment-list">
       <wd-status-tip v-if="attachmentList.length === 0" image="content" tip="鏆傛棤闄勪欢" />
 
-      <wd-card
-        v-for="item in attachmentList"
-        :key="item.id"
-        type="rectangle"
-        custom-class="attachment-card"
-        :border="false"
-      >
-        <view class="attachment-item" @click="previewAttachment(item)">
-          <view class="attachment-info">
-            <view class="attachment-name">{{ item.bucketFileName || item.name }}</view>
-            <view class="attachment-meta">
-              <text class="file-type">{{ getFileType(item.bucketFileName) }}</text>
-              <text class="upload-time">{{ formatTime(item.createTime) }}</text>
+      <view v-for="item in attachmentList" :key="item.id" class="attachment-card">
+        <view class="media-wrapper" @click="previewAttachment(item)">
+          <!-- 鍥剧墖棰勮 -->
+          <template v-if="isImageType(item.url)">
+            <image
+              v-if="!item.loadError"
+              :src="getFullUrl(item.url)"
+              mode="aspectFill"
+              class="media-preview"
+              @error="onImageError(item)"
+              @load="onImageLoad(item)"
+            />
+            <!-- 鍥剧墖鍔犺浇澶辫触鏄剧ず榛樿鍥炬爣 -->
+            <view v-else class="file-icon-wrapper">
+              <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
+              v-if="!item.loadError"
+              :src="getFullUrl(item.url)"
+              class="media-preview"
+              :controls="false"
+              :show-center-play-btn="false"
+              @error="onVideoError(item)"
+            />
+            <!-- 瑙嗛鍔犺浇澶辫触鏄剧ず榛樿鍥炬爣 -->
+            <view v-else class="file-icon-wrapper">
+              <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="attachment-actions" @click.stop>
-            <wd-icon name="delete" color="#ff4757" @click="deleteAttachment(item.id)" />
+
+          <!-- 鍒犻櫎鎸夐挳 -->
+          <view class="delete-btn" @click.stop="deleteAttachment(item.id)">
+            <wd-icon name="delete" color="#fff" size="20px" />
           </view>
         </view>
-      </wd-card>
+      </view>
     </view>
 
     <wd-toast />
@@ -48,6 +76,12 @@
 import { useToast } from "wot-design-uni";
 import AttachmentAPI from "@/api/product/attachment";
 
+// H5 浣跨敤 VITE_APP_BASE_API 浣滀负浠g悊璺緞锛屽叾浠栧钩鍙颁娇鐢� VITE_APP_API_URL 浣滀负璇锋眰璺緞
+let baseUrl = import.meta.env.VITE_APP_API_URL;
+// #ifdef H5
+baseUrl = import.meta.env.VITE_APP_BASE_API;
+// #endif
+
 const toast = useToast();
 
 // 椤甸潰鍙傛暟
@@ -56,6 +90,57 @@
 const attachmentList = ref<any[]>([]);
 
 const detailData = ref<any>({});
+
+// 鑾峰彇瀹屾暣鐨勫浘鐗�/瑙嗛 URL
+const getFullUrl = (url: string) => {
+  if (!url) return "";
+  // 濡傛灉宸茬粡鏄畬鏁寸殑 URL锛坔ttp 鎴� https 寮�澶达級锛岀洿鎺ヨ繑鍥�
+  if (url.startsWith("http://") || url.startsWith("https://")) {
+    return url;
+  }
+  // 濡傛灉鏄浉瀵硅矾寰勶紝鎷兼帴鍩虹 URL
+  return `${baseUrl}${url.startsWith("/") ? "" : "/"}${url}`;
+};
+
+// 浠� 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 onImageLoad = (item: any) => {
+  item.loadError = false;
+};
+
+// 鍥剧墖鍔犺浇澶辫触
+const onImageError = (item: any) => {
+  console.error("鍥剧墖鍔犺浇澶辫触:", item.url);
+  item.loadError = true;
+};
+
+// 瑙嗛鍔犺浇澶辫触
+const onVideoError = (item: any) => {
+  console.error("瑙嗛鍔犺浇澶辫触:", item.url);
+  item.loadError = true;
+};
+
 // 鑾峰彇闄勪欢鍒楄〃
 const getAttachmentList = async (data: any) => {
   try {
@@ -252,24 +337,24 @@
 // 棰勮闄勪欢
 const previewAttachment = (item: any) => {
   // 鏍规嵁鏂囦欢绫诲瀷杩涜棰勮
-  const fileName = item.bucketFileName || item.name;
-  const fileType = getFileType(fileName);
+  const fileType = getFileType(item.url);
+  const fullUrl = getFullUrl(item.url);
 
   if (fileType.startsWith("image")) {
     // 鍥剧墖棰勮
     uni.previewImage({
-      urls: [item.url],
-      current: item.url,
+      urls: [fullUrl],
+      current: fullUrl,
     });
   } else {
     // 鍏朵粬鏂囦欢绫诲瀷锛屽彲浠ヤ笅杞芥垨鎵撳紑
     uni.downloadFile({
-      url: item.url,
+      url: fullUrl,
       success: (res) => {
         uni.openDocument({
           filePath: res.tempFilePath,
           success: () => {
-            console.log("鎵撳紑鏂囨。鎴愬姛");
+            // 鎵撳紑鏂囨。鎴愬姛
           },
           fail: (error) => {
             console.error("鎵撳紑鏂囨。澶辫触:", error);
@@ -286,9 +371,9 @@
 };
 
 // 鑾峰彇鏂囦欢绫诲瀷
-const getFileType = (fileName: string) => {
-  if (!fileName) return "unknown";
-  const extension = fileName.split(".").pop()?.toLowerCase();
+const getFileType = (urlOrFileName: string) => {
+  if (!urlOrFileName) return "unknown";
+  const extension = getExtension(urlOrFileName);
   switch (extension) {
     case "jpg":
     case "jpeg":
@@ -297,6 +382,14 @@
     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":
@@ -357,43 +450,69 @@
 }
 
 .attachment-list {
+  display: grid;
+  grid-template-columns: repeat(3, 1fr);
+  gap: 8px;
+
   .attachment-card {
-    margin-bottom: 12px;
-    border-radius: 4px;
+    width: 100%;
+    aspect-ratio: 1;
   }
 }
 
-.attachment-item {
-  display: flex;
-  align-items: center;
-  padding: 12px;
+.media-wrapper {
+  position: relative;
+  width: 100%;
+  height: 100%;
+  border-radius: 8px;
+  overflow: hidden;
+  background: #f5f5f5;
 
-  .attachment-info {
-    flex: 1;
+  .media-preview {
+    width: 100%;
+    height: 100%;
+    object-fit: cover;
+  }
 
-    .attachment-name {
-      font-size: 16px;
-      font-weight: 500;
-      color: #333;
-      margin-bottom: 4px;
-      word-break: break-all;
-    }
+  .file-icon-wrapper {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: center;
+    width: 100%;
+    height: 100%;
+    padding: 8px;
+    text-align: center;
 
-    .attachment-meta {
-      display: flex;
-      gap: 12px;
+    .file-name {
+      margin-top: 8px;
       font-size: 12px;
-      color: #999;
+      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;
+      }
     }
   }
 
-  .attachment-actions {
-    margin-left: 12px;
-
-    :deep(.wd-icon) {
-      font-size: 20px;
-      cursor: pointer;
-    }
+  .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>
diff --git a/src/pages/production/wire/attachment/index.vue b/src/pages/production/wire/attachment/index.vue
index c654299..0f802e0 100644
--- a/src/pages/production/wire/attachment/index.vue
+++ b/src/pages/production/wire/attachment/index.vue
@@ -17,26 +17,54 @@
     <view class="attachment-list">
       <wd-status-tip v-if="attachmentList.length === 0" image="content" tip="鏆傛棤闄勪欢" />
 
-      <wd-card
-        v-for="item in attachmentList"
-        :key="item.id"
-        type="rectangle"
-        custom-class="attachment-card"
-        :border="false"
-      >
-        <view class="attachment-item" @click="previewAttachment(item)">
-          <view class="attachment-info">
-            <view class="attachment-name">{{ item.bucketFileName || item.name }}</view>
-            <view class="attachment-meta">
-              <text class="file-type">{{ getFileType(item.bucketFileName) }}</text>
-              <text class="upload-time">{{ formatTime(item.createTime) }}</text>
+      <view v-for="item in attachmentList" :key="item.id" class="attachment-card">
+        <view class="media-wrapper" @click="previewAttachment(item)">
+          <!-- 鍥剧墖棰勮 -->
+          <template v-if="isImageType(item.url)">
+            <image
+              v-if="!item.loadError"
+              :src="getFullUrl(item.url)"
+              mode="aspectFill"
+              class="media-preview"
+              @error="onImageError(item)"
+              @load="onImageLoad(item)"
+            />
+            <!-- 鍥剧墖鍔犺浇澶辫触鏄剧ず榛樿鍥炬爣 -->
+            <view v-else class="file-icon-wrapper">
+              <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
+              v-if="!item.loadError"
+              :src="getFullUrl(item.url)"
+              class="media-preview"
+              :controls="false"
+              :show-center-play-btn="false"
+              @error="onVideoError(item)"
+            />
+            <!-- 瑙嗛鍔犺浇澶辫触鏄剧ず榛樿鍥炬爣 -->
+            <view v-else class="file-icon-wrapper">
+              <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="attachment-actions" @click.stop>
-            <wd-icon name="delete" color="#ff4757" @click="deleteAttachment(item.id)" />
+
+          <!-- 鍒犻櫎鎸夐挳 -->
+          <view class="delete-btn" @click.stop="deleteAttachment(item.id)">
+            <wd-icon name="delete" color="#fff" size="20px" />
           </view>
         </view>
-      </wd-card>
+      </view>
     </view>
 
     <wd-toast />
@@ -48,6 +76,12 @@
 import { useToast } from "wot-design-uni";
 import AttachmentAPI from "@/api/product/attachment";
 
+// H5 浣跨敤 VITE_APP_BASE_API 浣滀负浠g悊璺緞锛屽叾浠栧钩鍙颁娇鐢� VITE_APP_API_URL 浣滀负璇锋眰璺緞
+let baseUrl = import.meta.env.VITE_APP_API_URL;
+// #ifdef H5
+baseUrl = import.meta.env.VITE_APP_BASE_API;
+// #endif
+
 const toast = useToast();
 
 // 椤甸潰鍙傛暟
@@ -56,6 +90,57 @@
 const attachmentList = ref<any[]>([]);
 
 const detailData = ref<any>({});
+
+// 鑾峰彇瀹屾暣鐨勫浘鐗�/瑙嗛 URL
+const getFullUrl = (url: string) => {
+  if (!url) return "";
+  // 濡傛灉宸茬粡鏄畬鏁寸殑 URL锛坔ttp 鎴� https 寮�澶达級锛岀洿鎺ヨ繑鍥�
+  if (url.startsWith("http://") || url.startsWith("https://")) {
+    return url;
+  }
+  // 濡傛灉鏄浉瀵硅矾寰勶紝鎷兼帴鍩虹 URL
+  return `${baseUrl}${url.startsWith("/") ? "" : "/"}${url}`;
+};
+
+// 浠� 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 onImageLoad = (item: any) => {
+  item.loadError = false;
+};
+
+// 鍥剧墖鍔犺浇澶辫触
+const onImageError = (item: any) => {
+  console.error("鍥剧墖鍔犺浇澶辫触:", item.url);
+  item.loadError = true;
+};
+
+// 瑙嗛鍔犺浇澶辫触
+const onVideoError = (item: any) => {
+  console.error("瑙嗛鍔犺浇澶辫触:", item.url);
+  item.loadError = true;
+};
+
 // 鑾峰彇闄勪欢鍒楄〃
 const getAttachmentList = async (data: any) => {
   try {
@@ -252,24 +337,24 @@
 // 棰勮闄勪欢
 const previewAttachment = (item: any) => {
   // 鏍规嵁鏂囦欢绫诲瀷杩涜棰勮
-  const fileName = item.bucketFileName || item.name;
-  const fileType = getFileType(fileName);
+  const fileType = getFileType(item.url);
+  const fullUrl = getFullUrl(item.url);
 
   if (fileType.startsWith("image")) {
     // 鍥剧墖棰勮
     uni.previewImage({
-      urls: [item.url],
-      current: item.url,
+      urls: [fullUrl],
+      current: fullUrl,
     });
   } else {
     // 鍏朵粬鏂囦欢绫诲瀷锛屽彲浠ヤ笅杞芥垨鎵撳紑
     uni.downloadFile({
-      url: item.url,
+      url: fullUrl,
       success: (res) => {
         uni.openDocument({
           filePath: res.tempFilePath,
           success: () => {
-            console.log("鎵撳紑鏂囨。鎴愬姛");
+            // 鎵撳紑鏂囨。鎴愬姛
           },
           fail: (error) => {
             console.error("鎵撳紑鏂囨。澶辫触:", error);
@@ -286,9 +371,9 @@
 };
 
 // 鑾峰彇鏂囦欢绫诲瀷
-const getFileType = (fileName: string) => {
-  if (!fileName) return "unknown";
-  const extension = fileName.split(".").pop()?.toLowerCase();
+const getFileType = (urlOrFileName: string) => {
+  if (!urlOrFileName) return "unknown";
+  const extension = getExtension(urlOrFileName);
   switch (extension) {
     case "jpg":
     case "jpeg":
@@ -297,6 +382,14 @@
     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":
@@ -357,43 +450,72 @@
 }
 
 .attachment-list {
+  display: grid;
+  grid-template-columns: repeat(3, 1fr);
+  gap: 8px;
+
   .attachment-card {
-    margin-bottom: 12px;
-    border-radius: 4px;
+    width: 100%;
+    aspect-ratio: 1;
   }
 }
 
-.attachment-item {
-  display: flex;
-  align-items: center;
-  padding: 12px;
+.media-wrapper {
+  position: relative;
+  width: 100%;
+  height: 100%;
+  border-radius: 8px;
+  overflow: hidden;
+  background: #f5f5f5;
 
-  .attachment-info {
-    flex: 1;
+  .media-preview {
+    width: 100%;
+    height: 100%;
+    object-fit: cover;
+  }
 
-    .attachment-name {
-      font-size: 16px;
-      font-weight: 500;
-      color: #333;
-      margin-bottom: 4px;
-      word-break: break-all;
-    }
+  .file-icon-wrapper {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: center;
+    width: 100%;
+    height: 100%;
+    padding: 8px;
+    text-align: center;
 
-    .attachment-meta {
-      display: flex;
-      gap: 12px;
+    .file-name {
+      margin-top: 8px;
       font-size: 12px;
-      color: #999;
+      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;
+      }
     }
   }
 
-  .attachment-actions {
-    margin-left: 12px;
-
-    :deep(.wd-icon) {
-      font-size: 20px;
-      cursor: pointer;
-    }
+  .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>
+
+
+
diff --git a/src/pages/routingInspection/upload.vue b/src/pages/routingInspection/upload.vue
index 70e3ad0..5b69b44 100644
--- a/src/pages/routingInspection/upload.vue
+++ b/src/pages/routingInspection/upload.vue
@@ -18,26 +18,54 @@
     <view class="attachment-list">
       <wd-status-tip v-if="attachmentList.length === 0" image="content" tip="鏆傛棤闄勪欢" />
 
-      <wd-card
-        v-for="item in attachmentList"
-        :key="item.id"
-        type="rectangle"
-        custom-class="attachment-card"
-        :border="false"
-      >
-        <view class="attachment-item" @click="previewAttachment(item)">
-          <view class="attachment-info">
-            <view class="attachment-name">{{ item.bucketFileName || item.name }}</view>
-            <view class="attachment-meta">
-              <text class="file-type">{{ getFileType(item.bucketFileName) }}</text>
-              <text class="upload-time">{{ formatTime(item.createTime) }}</text>
+      <view v-for="item in attachmentList" :key="item.id" class="attachment-card">
+        <view class="media-wrapper" @click="previewAttachment(item)">
+          <!-- 鍥剧墖棰勮 -->
+          <template v-if="isImageType(item.url)">
+            <image
+              v-if="!item.loadError"
+              :src="getFullUrl(item.url)"
+              mode="aspectFill"
+              class="media-preview"
+              @error="onImageError(item)"
+              @load="onImageLoad(item)"
+            />
+            <!-- 鍥剧墖鍔犺浇澶辫触鏄剧ず榛樿鍥炬爣 -->
+            <view v-else class="file-icon-wrapper">
+              <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
+              v-if="!item.loadError"
+              :src="getFullUrl(item.url)"
+              class="media-preview"
+              :controls="false"
+              :show-center-play-btn="false"
+              @error="onVideoError(item)"
+            />
+            <!-- 瑙嗛鍔犺浇澶辫触鏄剧ず榛樿鍥炬爣 -->
+            <view v-else class="file-icon-wrapper">
+              <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="attachment-actions" @click.stop v-if="isEdit">
-            <wd-icon name="delete" color="#ff4757" @click="deleteAttachment(item.id)" />
+
+          <!-- 鍒犻櫎鎸夐挳 -->
+          <view class="delete-btn" @click.stop="deleteAttachment(item.id)" v-if="isEdit">
+            <wd-icon name="delete" color="#fff" size="20px" />
           </view>
         </view>
-      </wd-card>
+      </view>
     </view>
 
     <wd-toast />
@@ -45,9 +73,15 @@
 </template>
 
 <script setup lang="ts">
-import { ref, onMounted } from "vue";
+import { ref } 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 baseUrl = import.meta.env.VITE_APP_API_URL;
+// #ifdef H5
+baseUrl = import.meta.env.VITE_APP_BASE_API;
+// #endif
 
 // 澶栭儴鍙傛暟
 const props = defineProps({
@@ -57,14 +91,36 @@
 });
 
 const toast = useToast();
-const attachmentList =
-  props.deviceType == "1"
-    ? ref<any[]>(props.detailData.value.files || [])
-    : ref<any[]>(props.detailData.files || []);
-const attachmentIds =
-  props.deviceType == "1"
-    ? ref<string[]>(props.detailData.value.attachmentId || [])
-    : ref<string[]>(props.detailData.attachmentId || []);
+const attachmentList = ref<any[]>(props.detailData.files || []);
+const attachmentIds = ref<string[]>(props.detailData.attachmentId || []);
+
+// 鑾峰彇瀹屾暣鐨勫浘鐗�/瑙嗛 URL
+const getFullUrl = (url: string) => {
+  if (!url) return "";
+  // 濡傛灉宸茬粡鏄畬鏁寸殑 URL锛坔ttp 鎴� https 寮�澶达級锛岀洿鎺ヨ繑鍥�
+  if (url.startsWith("http://") || url.startsWith("https://")) {
+    return url;
+  }
+  // 濡傛灉鏄浉瀵硅矾寰勶紝鎷兼帴鍩虹 URL
+  return `${baseUrl}${url.startsWith("/") ? "" : "/"}${url}`;
+};
+
+// 鍥剧墖鍔犺浇鎴愬姛
+const onImageLoad = (item: any) => {
+  item.loadError = false;
+};
+
+// 鍥剧墖鍔犺浇澶辫触
+const onImageError = (item: any) => {
+  console.error("鍥剧墖鍔犺浇澶辫触:", item.url);
+  item.loadError = true;
+};
+
+// 瑙嗛鍔犺浇澶辫触
+const onVideoError = (item: any) => {
+  console.error("瑙嗛鍔犺浇澶辫触:", item.url);
+  item.loadError = true;
+};
 
 // 鏂板闄勪欢
 const addAttachment = () => {
@@ -170,17 +226,14 @@
     const result = uploadResults.map((it: any) => {
       return it.data;
     });
-    console.log("result", result);
 
     // 鏇存柊闄勪欢鍒楄〃
     const flattenedResult = result.flat();
     attachmentList.value.push(...flattenedResult);
-    console.log(attachmentList.value);
 
     // 鎻愬彇闄勪欢ID
     attachmentIds.value = attachmentList.value.map((item: any) => item.id);
     toast.show("涓婁紶鎴愬姛");
-    console.log("111", attachmentIds.value.attachmentId);
   } catch (error) {
     console.error("涓婁紶澶辫触:", error);
     toast.show("涓婁紶澶辫触");
@@ -198,13 +251,12 @@
           // 鍓嶇鎵嬪姩鍒犻櫎锛氱洿鎺ヤ粠鍒楄〃涓Щ闄よ繖鏉℃暟鎹�
           attachmentList.value = attachmentList.value.filter((item) => item.id !== aid);
 
-          // 鑾峰彇鍓╀綑鐨勯檮浠禝D缁勫悎
-          attachmentIds.value = attachmentList.value.map((item) => item.id).join(",");
+          // 鑾峰彇鍓╀綑鐨勯檮浠禝D
+          attachmentIds.value = attachmentList.value.map((item) => item.id);
           toast.show("鍒犻櫎鎴愬姛");
         }
       },
     });
-    console.log("111", attachmentIds.value.attachmentId);
   } catch (error) {
     console.error("鍒犻櫎澶辫触:", error);
     toast.show("鍒犻櫎澶辫触");
@@ -214,24 +266,24 @@
 // 棰勮闄勪欢
 const previewAttachment = (item: any) => {
   // 鏍规嵁鏂囦欢绫诲瀷杩涜棰勮
-  const fileName = item.bucketFileName || item.name;
-  const fileType = getFileType(fileName);
+  const fileType = getFileType(item.url);
+  const fullUrl = getFullUrl(item.url);
 
   if (fileType.startsWith("image")) {
     // 鍥剧墖棰勮
     uni.previewImage({
-      urls: [item.url],
-      current: item.url,
+      urls: [fullUrl],
+      current: fullUrl,
     });
   } else {
     // 鍏朵粬鏂囦欢绫诲瀷锛屽彲浠ヤ笅杞芥垨鎵撳紑
     uni.downloadFile({
-      url: item.url,
+      url: fullUrl,
       success: (res) => {
         uni.openDocument({
           filePath: res.tempFilePath,
           success: () => {
-            console.log("鎵撳紑鏂囨。鎴愬姛");
+            // 鎵撳紑鏂囨。鎴愬姛
           },
           fail: (error) => {
             console.error("鎵撳紑鏂囨。澶辫触:", error);
@@ -247,10 +299,32 @@
   }
 };
 
+// 浠� 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 = (fileName: string) => {
-  if (!fileName) return "unknown";
-  const extension = fileName.split(".").pop()?.toLowerCase();
+const getFileType = (urlOrFileName: string) => {
+  if (!urlOrFileName) return "unknown";
+  const extension = getExtension(urlOrFileName);
   switch (extension) {
     case "jpg":
     case "jpeg":
@@ -259,6 +333,14 @@
     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":
@@ -317,43 +399,69 @@
 }
 
 .attachment-list {
+  display: grid;
+  grid-template-columns: repeat(3, 1fr);
+  gap: 8px;
+
   .attachment-card {
-    margin-bottom: 12px;
-    border-radius: 4px;
+    width: 100%;
+    aspect-ratio: 1;
   }
 }
 
-.attachment-item {
-  display: flex;
-  align-items: center;
-  padding: 12px;
+.media-wrapper {
+  position: relative;
+  width: 100%;
+  height: 100%;
+  border-radius: 8px;
+  overflow: hidden;
+  background: #f5f5f5;
 
-  .attachment-info {
-    flex: 1;
+  .media-preview {
+    width: 100%;
+    height: 100%;
+    object-fit: cover;
+  }
 
-    .attachment-name {
-      font-size: 16px;
-      font-weight: 500;
-      color: #333;
-      margin-bottom: 4px;
-      word-break: break-all;
-    }
+  .file-icon-wrapper {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: center;
+    width: 100%;
+    height: 100%;
+    padding: 8px;
+    text-align: center;
 
-    .attachment-meta {
-      display: flex;
-      gap: 12px;
+    .file-name {
+      margin-top: 8px;
       font-size: 12px;
-      color: #999;
+      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;
+      }
     }
   }
 
-  .attachment-actions {
-    margin-left: 12px;
-
-    :deep(.wd-icon) {
-      font-size: 20px;
-      cursor: pointer;
-    }
+  .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