<template>
|
<view class="attachment-page">
|
<!-- 页面头部 -->
|
<PageHeader :title="`查看附件 - ${taskInfo?.taskName || ''}`"
|
@back="goBack" />
|
<!-- 页面内容 -->
|
<view class="attachment-content">
|
<!-- 当前分类的附件列表 -->
|
<view class="attachment-list-container">
|
<view v-if="attachmentList.length > 0"
|
class="attachment-list">
|
<view v-for="(file, index) in attachmentList"
|
:key="index"
|
class="attachment-item"
|
@click="previewAttachment(file)">
|
<view class="attachment-preview-container">
|
<image v-if="isImageFile(file)"
|
:src="file.url || file.downloadUrl"
|
class="attachment-preview"
|
mode="aspectFill" />
|
<view v-else
|
class="attachment-video-preview">
|
<u-icon name="video"
|
size="40"
|
color="#409eff"></u-icon>
|
<text class="video-text">视频</text>
|
</view>
|
</view>
|
<view class="attachment-info">
|
<text class="attachment-name">{{ file.originalFilename || file.bucketFilename || file.name || '附件' }}</text>
|
<text class="attachment-size">{{ formatFileSize(file.byteSize || file.size) }}</text>
|
</view>
|
</view>
|
</view>
|
<view v-else
|
class="attachment-empty">
|
<u-icon name="folder-open"
|
size="60"
|
color="#ccc"></u-icon>
|
<text class="empty-text">该分类暂无附件</text>
|
</view>
|
</view>
|
</view>
|
<!-- 视频预览弹窗 -->
|
<view v-if="showVideoDialog"
|
class="video-modal-overlay"
|
@click="closeVideoPreview">
|
<view class="video-modal-container"
|
@click.stop>
|
<view class="video-modal-header">
|
<text class="video-modal-title">{{ currentVideoFile?.originalFilename || '视频预览' }}</text>
|
<view class="close-btn-video"
|
@click="closeVideoPreview">
|
<u-icon name="close"
|
size="20"
|
color="#fff"></u-icon>
|
</view>
|
</view>
|
<view class="video-modal-body">
|
<video v-if="currentVideoFile"
|
:src="currentVideoFile.url || currentVideoFile.downloadUrl"
|
class="video-player"
|
controls
|
autoplay
|
@error="handleVideoError"></video>
|
</view>
|
</view>
|
</view>
|
</view>
|
</template>
|
|
<script setup>
|
import { ref } from "vue";
|
import { onLoad } from "@dcloudio/uni-app";
|
import PageHeader from "@/components/PageHeader.vue";
|
import config from "@/config";
|
|
// 任务信息
|
const taskInfo = ref(null);
|
|
// 附件列表
|
const attachmentList = ref([]);
|
|
// 视频预览相关状态
|
const showVideoDialog = ref(false);
|
const currentVideoFile = ref(null);
|
|
// 文件访问基础域
|
const filePreviewBase = config.fileUrl;
|
|
// 页面加载
|
onLoad(options => {
|
if (options.taskInfo) {
|
try {
|
taskInfo.value = JSON.parse(decodeURIComponent(options.taskInfo));
|
loadAttachments();
|
} catch (e) {
|
console.error("解析任务信息失败:", e);
|
uni.showToast({
|
title: "加载失败",
|
icon: "error",
|
});
|
}
|
}
|
});
|
|
// 加载附件数据
|
const loadAttachments = () => {
|
const task = taskInfo.value;
|
if (!task) return;
|
|
attachmentList.value = [];
|
|
// 获取附件列表,优先从 commonFileListBeforeVO 获取
|
let rawList = [];
|
if (Array.isArray(task.commonFileListBeforeVO)) {
|
rawList = task.commonFileListBeforeVO;
|
} else if (Array.isArray(task.commonFileListBefore)) {
|
rawList = task.commonFileListBefore;
|
} else if (Array.isArray(task.commonFileList)) {
|
// 降级:从通用列表过滤 type 为 10 的
|
rawList = task.commonFileList.filter(f => f?.type === 10);
|
}
|
|
const mapToViewFile = file => {
|
// 优先使用 previewURL 或 url
|
const rawUrl =
|
file?.previewURL ||
|
file?.url ||
|
file?.downloadUrl ||
|
file?.downloadURL ||
|
"";
|
const u = normalizeFileUrl(rawUrl);
|
|
return {
|
...file,
|
name:
|
file?.name || file?.originalFilename || file?.bucketFilename || "附件",
|
bucketFilename:
|
file?.bucketFilename || file?.name || file?.originalFilename,
|
originalFilename: file?.originalFilename || file?.name,
|
url: u,
|
downloadUrl: u,
|
size: file?.size || file?.byteSize || 0,
|
};
|
};
|
|
attachmentList.value = rawList.map(f => mapToViewFile(f));
|
};
|
|
// 将后端返回的文件地址规范成可访问URL
|
const normalizeFileUrl = rawUrl => {
|
try {
|
if (!rawUrl || typeof rawUrl !== "string") return "";
|
const url = rawUrl.trim();
|
if (!url) return "";
|
if (/^https?:\/\//i.test(url)) return url;
|
if (url.startsWith("/")) return `${filePreviewBase}${url}`;
|
|
// Windows path -> web path
|
if (/^[a-zA-Z]:\\/.test(url)) {
|
const normalized = url.replace(/\\/g, "/");
|
const idx = normalized.indexOf("/prod/");
|
if (idx >= 0) {
|
const relative = normalized.slice(idx + "/prod/".length);
|
return `${filePreviewBase}/${relative}`;
|
}
|
return normalized;
|
}
|
|
return `${filePreviewBase}/${url.replace(/^\//, "")}`;
|
} catch (e) {
|
return rawUrl || "";
|
}
|
};
|
|
// 返回上一页
|
const goBack = () => {
|
uni.navigateBack();
|
};
|
|
// 判断是否为图片文件
|
const isImageFile = file => {
|
if (file.contentType && file.contentType.startsWith("image/")) {
|
return true;
|
}
|
if (file.type === "image") return true;
|
|
const name = file.bucketFilename || file.originalFilename || file.name || "";
|
const ext = name.split(".").pop()?.toLowerCase();
|
return ["jpg", "jpeg", "png", "gif", "webp"].includes(ext);
|
};
|
|
// 预览附件
|
const previewAttachment = file => {
|
if (isImageFile(file)) {
|
const imageUrls = attachmentList.value
|
.filter(f => isImageFile(f))
|
.map(f => f.url || f.downloadUrl);
|
|
uni.previewImage({
|
urls: imageUrls,
|
current: file.url || file.downloadUrl,
|
});
|
} else {
|
showVideoPreview(file);
|
}
|
};
|
|
// 显示视频预览
|
const showVideoPreview = file => {
|
currentVideoFile.value = file;
|
showVideoDialog.value = true;
|
};
|
|
// 关闭视频预览
|
const closeVideoPreview = () => {
|
showVideoDialog.value = false;
|
currentVideoFile.value = null;
|
};
|
|
// 视频播放错误处理
|
const handleVideoError = () => {
|
uni.showToast({
|
title: "视频播放失败",
|
icon: "error",
|
});
|
};
|
|
// 格式化文件大小
|
const formatFileSize = size => {
|
if (!size) return "";
|
if (size < 1024) return size + "B";
|
if (size < 1024 * 1024) return (size / 1024).toFixed(1) + "KB";
|
return (size / (1024 * 1024)).toFixed(1) + "MB";
|
};
|
</script>
|
|
<style scoped>
|
.attachment-page {
|
min-height: 100vh;
|
background-color: #f5f5f5;
|
}
|
|
.attachment-content {
|
padding: 15px;
|
}
|
|
/* 标签页样式 */
|
.attachment-tabs {
|
display: flex;
|
background: #fff;
|
border-radius: 12px;
|
margin-bottom: 15px;
|
padding: 4px;
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
|
}
|
|
.tab-item {
|
flex: 1;
|
text-align: center;
|
padding: 12px 8px;
|
font-size: 14px;
|
color: #666;
|
border-radius: 8px;
|
transition: all 0.3s ease;
|
}
|
|
.tab-item.active {
|
background: #409eff;
|
color: #fff;
|
font-weight: 500;
|
}
|
|
/* 附件列表样式 */
|
.attachment-list-container {
|
background: #fff;
|
border-radius: 12px;
|
padding: 15px;
|
min-height: 400px;
|
}
|
|
.attachment-list {
|
display: flex;
|
flex-wrap: wrap;
|
gap: 15px;
|
}
|
|
.attachment-item {
|
width: calc(33.33% - 10px);
|
background: #f8f9fa;
|
border-radius: 12px;
|
overflow: hidden;
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
transition: all 0.3s ease;
|
}
|
|
.attachment-item:active {
|
transform: scale(0.98);
|
}
|
|
.attachment-preview-container {
|
width: 100%;
|
height: 120px;
|
background: #e9ecef;
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
}
|
|
.attachment-preview {
|
width: 100%;
|
height: 100%;
|
object-fit: cover;
|
}
|
|
.attachment-video-preview {
|
display: flex;
|
flex-direction: column;
|
align-items: center;
|
justify-content: center;
|
gap: 8px;
|
}
|
|
.video-text {
|
font-size: 12px;
|
color: #666;
|
}
|
|
.attachment-info {
|
padding: 10px;
|
}
|
|
.attachment-name {
|
font-size: 12px;
|
color: #333;
|
display: block;
|
white-space: nowrap;
|
overflow: hidden;
|
text-overflow: ellipsis;
|
margin-bottom: 4px;
|
}
|
|
.attachment-size {
|
font-size: 10px;
|
color: #999;
|
}
|
|
/* 空状态样式 */
|
.attachment-empty {
|
display: flex;
|
flex-direction: column;
|
align-items: center;
|
justify-content: center;
|
padding: 80px 20px;
|
color: #999;
|
}
|
|
.empty-text {
|
margin-top: 15px;
|
font-size: 14px;
|
}
|
|
/* 视频弹窗样式 */
|
.video-modal-overlay {
|
position: fixed;
|
top: 0;
|
left: 0;
|
right: 0;
|
bottom: 0;
|
background: rgba(0, 0, 0, 0.9);
|
z-index: 10000;
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
padding: 20px;
|
}
|
|
.video-modal-container {
|
width: 100%;
|
max-width: 800px;
|
background: #000;
|
border-radius: 12px;
|
overflow: hidden;
|
}
|
|
.video-modal-header {
|
display: flex;
|
justify-content: space-between;
|
align-items: center;
|
padding: 15px 20px;
|
background: #1a1a1a;
|
}
|
|
.video-modal-title {
|
font-size: 16px;
|
color: #fff;
|
font-weight: 500;
|
}
|
|
.close-btn-video {
|
width: 32px;
|
height: 32px;
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
background: rgba(255, 255, 255, 0.1);
|
border-radius: 50%;
|
}
|
|
.video-modal-body {
|
padding: 20px;
|
}
|
|
.video-player {
|
width: 100%;
|
height: 400px;
|
border-radius: 8px;
|
}
|
</style>
|