| | |
| | | <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="delete-btn" @click.stop="deleteAttachment(item.id)"> |
| | | <wd-icon name="delete" color="#fff" size="20px" /> |
| | | </view> |
| | | </view> |
| | | <view class="attachment-actions" @click.stop> |
| | | <wd-icon name="delete" color="#ff4757" @click="deleteAttachment(item.id)" /> |
| | | </view> |
| | | </view> |
| | | </wd-card> |
| | | </view> |
| | | |
| | | <wd-toast /> |
| | |
| | | import { useToast } from "wot-design-uni"; |
| | | import AttachmentAPI from "@/api/product/attachment"; |
| | | |
| | | // H5 使用 VITE_APP_BASE_API 作为代理路径,其他平台使用 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(); |
| | | |
| | | // 页面参数 |
| | |
| | | const attachmentList = ref<any[]>([]); |
| | | |
| | | const detailData = ref<any>({}); |
| | | |
| | | // 获取完整的图片/视频 URL |
| | | const getFullUrl = (url: string) => { |
| | | if (!url) return ""; |
| | | // 如果已经是完整的 URL(http 或 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 { |
| | |
| | | // 预览附件 |
| | | 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); |
| | |
| | | }; |
| | | |
| | | // 获取文件类型 |
| | | 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": |
| | |
| | | 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": |
| | |
| | | } |
| | | |
| | | .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 { |
| | | .media-wrapper { |
| | | position: relative; |
| | | width: 100%; |
| | | height: 100%; |
| | | border-radius: 8px; |
| | | overflow: hidden; |
| | | background: #f5f5f5; |
| | | |
| | | .media-preview { |
| | | width: 100%; |
| | | height: 100%; |
| | | object-fit: cover; |
| | | } |
| | | |
| | | .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; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .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; |
| | | padding: 12px; |
| | | |
| | | .attachment-info { |
| | | flex: 1; |
| | | |
| | | .attachment-name { |
| | | font-size: 16px; |
| | | font-weight: 500; |
| | | color: #333; |
| | | margin-bottom: 4px; |
| | | word-break: break-all; |
| | | } |
| | | |
| | | .attachment-meta { |
| | | display: flex; |
| | | gap: 12px; |
| | | font-size: 12px; |
| | | color: #999; |
| | | } |
| | | } |
| | | |
| | | .attachment-actions { |
| | | margin-left: 12px; |
| | | |
| | | :deep(.wd-icon) { |
| | | font-size: 20px; |
| | | cursor: pointer; |
| | | } |
| | | justify-content: center; |
| | | z-index: 10; |
| | | } |
| | | } |
| | | </style> |
| | | |
| | | |
| | | |