lxr hai 1 día
pai
achega
9e0f865534
Modificáronse 1 ficheiros con 172 adicións e 56 borrados
  1. 172 56
      src/views/storeDecoration/decorationCompanyDetail.vue

+ 172 - 56
src/views/storeDecoration/decorationCompanyDetail.vue

@@ -65,16 +65,49 @@
         </el-form-item>
 
         <el-form-item label="上传房屋图纸" required>
-          <el-upload
-            v-model:file-list="fileList"
-            list-type="picture-card"
-            :disabled="true"
-            :on-preview="handlePictureCardPreview"
-            :on-remove="handleRemove"
-          >
-            <el-icon><Plus /></el-icon>
-          </el-upload>
-          <div class="upload-tip">({{ fileList.length }}/9)</div>
+          <div class="attachment-wrap">
+            <div class="attachment-grid">
+              <div v-for="(url, index) in formData.attachmentUrls" :key="`${index}-${url}`" class="attachment-card">
+                <template v-if="isAttachmentVideo(url)">
+                  <div
+                    class="attachment-card-inner attachment-card-inner--video"
+                    @click="openVideoPreview(getAttachmentVideoSrc(url))"
+                  >
+                    <video
+                      class="attachment-video-thumb"
+                      :poster="getVideoThumbPoster(url) || undefined"
+                      :src="getAttachmentVideoSrc(url)"
+                      muted
+                      preload="metadata"
+                      playsinline
+                    />
+                    <div class="video-overlay">
+                      <el-icon class="play-icon">
+                        <VideoPlay />
+                      </el-icon>
+                    </div>
+                  </div>
+                </template>
+                <el-image
+                  v-else
+                  :src="url"
+                  fit="cover"
+                  class="attachment-image"
+                  :preview-src-list="imagePreviewList"
+                  :initial-index="getImagePreviewIndex(url)"
+                  preview-teleported
+                />
+              </div>
+              <div
+                v-if="formData.attachmentUrls.length < 9"
+                class="attachment-card attachment-card--placeholder"
+                aria-hidden="true"
+              >
+                <el-icon><Plus /></el-icon>
+              </div>
+            </div>
+            <div class="upload-tip">({{ formData.attachmentUrls.length }}/9)</div>
+          </div>
         </el-form-item>
 
         <el-form-item label="联系人" required>
@@ -100,22 +133,23 @@
       </div>
     </div>
 
-    <!-- 图片预览 -->
-    <el-image-viewer
-      v-if="imageViewerVisible"
-      :url-list="imageViewerUrlList"
-      :initial-index="imageViewerInitialIndex"
-      @close="imageViewerVisible = false"
-    />
+    <el-dialog
+      v-model="videoDialogVisible"
+      title="视频预览"
+      width="min(640px, 92vw)"
+      destroy-on-close
+      @close="previewVideoUrl = ''"
+    >
+      <video v-if="previewVideoUrl" :src="previewVideoUrl" controls autoplay class="dialog-video" />
+    </el-dialog>
   </div>
 </template>
 
 <script setup lang="ts" name="decorationCompanyDetail">
-import { ref, onMounted, watch } from "vue";
+import { ref, computed, onMounted, watch } from "vue";
 import { useRoute, useRouter } from "vue-router";
 import { ElMessage } from "element-plus";
-import { Plus, Close } from "@element-plus/icons-vue";
-import type { UploadFile } from "element-plus";
+import { Plus, Close, VideoPlay } from "@element-plus/icons-vue";
 import { getDecorationDetail } from "@/api/modules/storeDecoration";
 
 const route = useRoute();
@@ -142,10 +176,60 @@ const formData = ref<any>({
   userId: "" // 兼容字段
 });
 
-const fileList = ref<UploadFile[]>([]);
-const imageViewerVisible = ref(false);
-const imageViewerUrlList = ref<string[]>([]);
-const imageViewerInitialIndex = ref(0);
+const videoDialogVisible = ref(false);
+const previewVideoUrl = ref("");
+
+/** 与 caseDetail / 常见上传一致:扩展名或「视频|封面」 */
+const isAttachmentVideo = (raw: string) => {
+  if (!raw || typeof raw !== "string") return false;
+  if (raw.includes("|")) return true;
+  return /\.(mp4|avi|mov|wmv|flv|webm|m4v|3gp)(\?|#|$)/i.test(raw);
+};
+
+const getAttachmentVideoSrc = (raw: string) => {
+  if (!raw || typeof raw !== "string") return "";
+  if (raw.includes("|")) return raw.split("|")[0].trim();
+  return raw;
+};
+
+const getExplicitVideoCover = (raw: string) => {
+  if (!raw || typeof raw !== "string" || !raw.includes("|")) return "";
+  const parts = raw
+    .split("|")
+    .map(s => s.trim())
+    .filter(Boolean);
+  return parts.length >= 2 ? parts[1] : "";
+};
+
+/** OSS 视频首帧快照,与 storeCoverMap 一致,利于卡片缩略图 */
+const getOssVideoSnapshotUrl = (videoUrl: string) => {
+  if (!videoUrl || typeof videoUrl !== "string") return "";
+  const isOss =
+    videoUrl.includes("aliyuncs.com") ||
+    videoUrl.includes("oss-cn-") ||
+    videoUrl.includes("oss.") ||
+    videoUrl.includes("alien-volume");
+  if (!isOss) return "";
+  const sep = videoUrl.includes("?") ? "&" : "?";
+  return `${videoUrl}${sep}x-oss-process=video/snapshot,t_0,f_jpg,w_800,h_600,m_fast`;
+};
+
+const getVideoThumbPoster = (raw: string) => {
+  const explicit = getExplicitVideoCover(raw);
+  if (explicit) return explicit;
+  const src = getAttachmentVideoSrc(raw);
+  return getOssVideoSnapshotUrl(src);
+};
+
+const imagePreviewList = computed(() => (formData.value.attachmentUrls || []).filter((u: string) => u && !isAttachmentVideo(u)));
+
+const getImagePreviewIndex = (url: string) => imagePreviewList.value.indexOf(url);
+
+const openVideoPreview = (url: string) => {
+  if (!url) return;
+  previewVideoUrl.value = url;
+  videoDialogVisible.value = true;
+};
 
 // 联系业主(跳转聊天页面)
 const handleContact = () => {
@@ -196,16 +280,6 @@ const initData = async () => {
         createUserId: data.createUserId || data.userId || "",
         userId: data.userId || data.createUserId || ""
       };
-
-      // 处理附件列表
-      if (formData.value.attachmentUrls && formData.value.attachmentUrls.length > 0) {
-        fileList.value = formData.value.attachmentUrls.map((url: string, index: number) => ({
-          uid: index,
-          name: `图片${index + 1}`,
-          url: url,
-          status: "success"
-        }));
-      }
     }
   } catch (error: any) {
     console.error("获取详情失败:", error);
@@ -213,20 +287,6 @@ const initData = async () => {
   }
 };
 
-// 图片预览
-const handlePictureCardPreview = (file: UploadFile) => {
-  if (file.url) {
-    imageViewerUrlList.value = fileList.value.map((item: UploadFile) => item.url || "").filter(Boolean);
-    imageViewerInitialIndex.value = fileList.value.findIndex((item: UploadFile) => item.uid === file.uid);
-    imageViewerVisible.value = true;
-  }
-};
-
-// 删除图片(详情页禁用,这里只是占位)
-const handleRemove = () => {
-  // 详情页不允许删除
-};
-
 // 关闭页面
 const handleClose = () => {
   router.go(-1);
@@ -283,19 +343,75 @@ onMounted(() => {
     font-size: 12px;
     color: #999999;
   }
+  .attachment-wrap {
+    width: 100%;
+  }
+  .attachment-grid {
+    display: flex;
+    flex-wrap: wrap;
+    gap: 8px;
+    align-items: flex-start;
+  }
+  .attachment-card {
+    box-sizing: border-box;
+    width: 100px;
+    height: 100px;
+    overflow: hidden;
+    background: #fafafa;
+    border: 1px solid #e4e7ed;
+    border-radius: 4px;
+  }
+  .attachment-card--placeholder {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    font-size: 24px;
+    color: #8c939d;
+    pointer-events: none;
+    border-style: dashed;
+  }
+  .attachment-card-inner {
+    position: relative;
+    width: 100%;
+    height: 100%;
+    cursor: pointer;
+  }
+  .attachment-image {
+    display: block;
+    width: 100%;
+    height: 100%;
+  }
+  .attachment-video-thumb {
+    display: block;
+    width: 100%;
+    height: 100%;
+    vertical-align: top;
+    pointer-events: none;
+    object-fit: cover;
+  }
+  .video-overlay {
+    position: absolute;
+    inset: 0;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    pointer-events: none;
+    background: rgb(0 0 0 / 25%);
+  }
+  .video-overlay .play-icon {
+    font-size: 28px;
+    color: #ffffff;
+  }
+  .dialog-video {
+    display: block;
+    width: 100%;
+    max-height: 70vh;
+  }
   .detail-footer {
     padding: 20px 0 0;
     margin-top: 20px;
     text-align: center;
     border-top: 1px solid #ebeef5;
   }
-  :deep(.el-upload--picture-card) {
-    width: 100px;
-    height: 100px;
-  }
-  :deep(.el-upload-list--picture-card .el-upload-list__item) {
-    width: 100px;
-    height: 100px;
-  }
 }
 </style>