|
|
@@ -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>
|