| src/pages/production/twist/attachment/index.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/pages/production/wire/attachment/index.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/pages/routingInspection/upload.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | 
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 作为代理路径,其他平台使用 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(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 { @@ -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> 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 作为代理路径,其他平台使用 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(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 { @@ -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>  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 作为代理路径,其他平台使用 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(http 或 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); // 获取剩余的附件ID组合 attachmentIds.value = attachmentList.value.map((item) => item.id).join(","); // 获取剩余的附件ID 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>