<template>
|
<view class="attachment-container">
|
<!-- 头部操作区 -->
|
<view class="header-actions">
|
<wd-button
|
icon="file-add"
|
:round="false"
|
size="small"
|
custom-class="add_btn"
|
@click="addAttachment"
|
>
|
新增
|
</wd-button>
|
</view>
|
|
<!-- 附件列表 -->
|
<view class="attachment-list">
|
<wd-status-tip v-if="attachmentList.length === 0" image="content" tip="暂无附件" />
|
|
<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>
|
</view>
|
|
<wd-toast />
|
</view>
|
</template>
|
|
<script setup lang="ts">
|
import { ref, onMounted } 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 toast = useToast();
|
|
// 页面参数
|
const reportId = ref("");
|
const reportType = ref("绞线");
|
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 {
|
detailData.value = data;
|
console.log(" detailData.value", detailData.value);
|
const pages = getCurrentPages();
|
const currentPage = pages[pages.length - 1];
|
const options = (currentPage as any).options;
|
const currentReportId = detailData.value.id;
|
|
if (currentReportId) {
|
reportId.value = currentReportId;
|
|
// 直接调用通用查看接口查询附件列表
|
// 使用示例中的附件ID数组 [850,851]
|
|
const attachmentIds: number[] =
|
detailData.value.attachmentId !== null ? detailData.value.attachmentId.split(",") : []; // 使用HTTP文件中的示例数据
|
if (attachmentIds.length === 0) {
|
return;
|
}
|
const { data } = await AttachmentAPI.listAttachmentFiles(attachmentIds);
|
attachmentList.value = data || [];
|
} else {
|
attachmentList.value = [];
|
}
|
} catch (error) {
|
console.error("获取附件列表失败:", error);
|
toast.show("获取附件列表失败");
|
attachmentList.value = [];
|
}
|
};
|
|
// 新增附件
|
const addAttachment = () => {
|
// 显示选择文件类型的弹窗
|
uni.showActionSheet({
|
itemList: ["选择图片", "选择视频", "拍照", "录像"],
|
success: (res) => {
|
switch (res.tapIndex) {
|
case 0: // 选择图片
|
chooseImages();
|
break;
|
case 1: // 选择视频
|
chooseVideos();
|
break;
|
case 2: // 拍照
|
takePhoto();
|
break;
|
case 3: // 录像
|
recordVideo();
|
break;
|
}
|
},
|
fail: (error) => {
|
console.error("选择文件类型失败:", error);
|
toast.show("选择文件类型失败");
|
},
|
});
|
};
|
|
// 选择图片
|
const chooseImages = () => {
|
uni.chooseImage({
|
count: 9,
|
sizeType: ["original", "compressed"],
|
sourceType: ["album"],
|
success: async (res) => {
|
const filePaths = Array.isArray(res.tempFilePaths) ? res.tempFilePaths : [res.tempFilePaths];
|
await handleFileUpload(filePaths);
|
},
|
fail: (error) => {
|
console.error("选择图片失败:", error);
|
toast.show("选择图片失败");
|
},
|
});
|
};
|
|
// 选择视频
|
const chooseVideos = () => {
|
uni.chooseVideo({
|
sourceType: ["album"],
|
maxDuration: 60,
|
camera: "back",
|
success: async (res) => {
|
await handleFileUpload([res.tempFilePath]);
|
},
|
fail: (error) => {
|
console.error("选择视频失败:", error);
|
toast.show("选择视频失败");
|
},
|
});
|
};
|
|
// 拍照
|
const takePhoto = () => {
|
uni.chooseImage({
|
count: 1,
|
sizeType: ["original", "compressed"],
|
sourceType: ["camera"],
|
success: async (res) => {
|
const filePaths = Array.isArray(res.tempFilePaths) ? res.tempFilePaths : [res.tempFilePaths];
|
await handleFileUpload(filePaths);
|
},
|
fail: (error) => {
|
console.error("拍照失败:", error);
|
toast.show("拍照失败");
|
},
|
});
|
};
|
|
// 录像
|
const recordVideo = () => {
|
uni.chooseVideo({
|
sourceType: ["camera"],
|
maxDuration: 60,
|
camera: "back",
|
success: async (res) => {
|
await handleFileUpload([res.tempFilePath]);
|
},
|
fail: (error) => {
|
console.error("录像失败:", error);
|
toast.show("录像失败");
|
},
|
});
|
};
|
|
// 处理文件上传
|
const handleFileUpload = async (filePaths: string[]) => {
|
try {
|
toast.show("正在上传...");
|
|
// 上传文件
|
const uploadResults: any = await AttachmentAPI.uploadAttachmentFiles(filePaths);
|
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
|
const attachmentId = attachmentList.value.map((item: any) => item.id).join(",");
|
|
// 关联到报工
|
if (attachmentId) {
|
await AttachmentAPI.addOutputAttachments({
|
id: parseInt(detailData.value.id),
|
attachmentId: attachmentId,
|
});
|
detailData.value.attachmentId = attachmentId;
|
}
|
|
toast.show("上传成功");
|
} catch (error) {
|
console.error("上传失败:", error);
|
toast.show("上传失败");
|
}
|
};
|
|
// 删除附件
|
const deleteAttachment = async (aid: number) => {
|
try {
|
uni.showModal({
|
title: "确认删除",
|
content: "确定要删除这个附件吗?",
|
success: async (res) => {
|
if (res.confirm) {
|
// 前端手动删除:直接从列表中移除这条数据
|
attachmentList.value = attachmentList.value.filter((item) => item.id !== aid);
|
|
// 获取剩余的附件ID组合
|
const attachmentId = attachmentList.value.map((item) => item.id).join(",");
|
|
// 调用报工添加附件接口,更新附件关联
|
await AttachmentAPI.addOutputAttachments({
|
id: parseInt(detailData.value.id),
|
attachmentId: attachmentId,
|
});
|
detailData.value.attachmentId = attachmentId;
|
toast.show("删除成功");
|
}
|
},
|
});
|
} catch (error) {
|
console.error("删除失败:", error);
|
toast.show("删除失败");
|
}
|
};
|
|
// 预览附件
|
const previewAttachment = (item: any) => {
|
// 根据文件类型进行预览
|
const fileType = getFileType(item.url);
|
const fullUrl = getFullUrl(item.url);
|
|
if (fileType.startsWith("image")) {
|
// 图片预览
|
uni.previewImage({
|
urls: [fullUrl],
|
current: fullUrl,
|
});
|
} else {
|
// 其他文件类型,可以下载或打开
|
uni.downloadFile({
|
url: fullUrl,
|
success: (res) => {
|
uni.openDocument({
|
filePath: res.tempFilePath,
|
success: () => {
|
// 打开文档成功
|
},
|
fail: (error) => {
|
console.error("打开文档失败:", error);
|
toast.show("无法预览此文件类型");
|
},
|
});
|
},
|
fail: (error) => {
|
console.error("下载文件失败:", error);
|
toast.show("下载文件失败");
|
},
|
});
|
}
|
};
|
|
// 获取文件类型
|
const getFileType = (urlOrFileName: string) => {
|
if (!urlOrFileName) return "unknown";
|
const extension = getExtension(urlOrFileName);
|
switch (extension) {
|
case "jpg":
|
case "jpeg":
|
case "png":
|
case "gif":
|
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":
|
case "docx":
|
return "word";
|
case "xls":
|
case "xlsx":
|
return "excel";
|
case "ppt":
|
case "pptx":
|
return "powerpoint";
|
case "txt":
|
return "text";
|
case "zip":
|
case "rar":
|
return "archive";
|
default:
|
return "file";
|
}
|
};
|
|
// 格式化文件大小
|
const formatFileSize = (size: number) => {
|
if (size < 1024) return size + " B";
|
if (size < 1024 * 1024) return (size / 1024).toFixed(1) + " KB";
|
return (size / (1024 * 1024)).toFixed(1) + " MB";
|
};
|
|
// 格式化时间
|
const formatTime = (time: string) => {
|
const date = new Date(time);
|
return date.toLocaleString();
|
};
|
|
onMounted(() => {
|
uni.$on("detailData", (data) => {
|
// 处理接收到的数据
|
getAttachmentList(data);
|
});
|
});
|
</script>
|
|
<style lang="scss" scoped>
|
.attachment-container {
|
padding: 12px;
|
background: #f3f9f8;
|
min-height: 100vh;
|
}
|
|
.header-actions {
|
margin-bottom: 12px;
|
|
:deep(.add_btn) {
|
background: #0d867f;
|
color: white;
|
border: none;
|
}
|
}
|
|
.attachment-list {
|
display: grid;
|
grid-template-columns: repeat(3, 1fr);
|
gap: 8px;
|
|
.attachment-card {
|
width: 100%;
|
aspect-ratio: 1;
|
}
|
}
|
|
.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;
|
justify-content: center;
|
z-index: 10;
|
}
|
}
|
</style>
|