<template>
|
<view class="attachment-page">
|
<!-- 页面头部 -->
|
<PageHeader :title="`查看附件 - ${taskInfo?.taskName || ''}`" @back="goBack" />
|
|
<!-- 页面内容 -->
|
<view class="attachment-content">
|
<!-- 分类标签页 -->
|
<view class="attachment-tabs">
|
<view
|
class="tab-item"
|
:class="{ active: currentViewType === 'before' }"
|
@click="switchViewType('before')"
|
>
|
生产前 ({{ getAttachmentsByType(0).length }})
|
</view>
|
<view
|
class="tab-item"
|
:class="{ active: currentViewType === 'after' }"
|
@click="switchViewType('after')"
|
>
|
生产中 ({{ getAttachmentsByType(1).length }})
|
</view>
|
<view
|
class="tab-item"
|
:class="{ active: currentViewType === 'issue' }"
|
@click="switchViewType('issue')"
|
>
|
生产后 ({{ getAttachmentsByType(2).length }})
|
</view>
|
</view>
|
|
<!-- 当前分类的附件列表 -->
|
<view class="attachment-list-container">
|
<view v-if="getCurrentViewAttachments().length > 0" class="attachment-list">
|
<view
|
v-for="(file, index) in getCurrentViewAttachments()"
|
: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 currentViewType = ref('before'); // 'before', 'after', 'issue'
|
|
// 视频预览相关状态
|
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 = [];
|
|
// 后端反显字段
|
const allList = Array.isArray(task?.commonFileList) ? task.commonFileList : [];
|
const beforeList = Array.isArray(task?.commonFileListBefore)
|
? task.commonFileListBefore
|
: allList.filter((f) => f?.type === 10);
|
const afterList = Array.isArray(task?.commonFileListAfter)
|
? task.commonFileListAfter
|
: allList.filter((f) => f?.type === 11);
|
const issueList = Array.isArray(task?.commonFileListIssue)
|
? task.commonFileListIssue
|
: allList.filter((f) => f?.type === 12);
|
|
const mapToViewFile = (file, viewType) => {
|
const u = normalizeFileUrl(file?.url || file?.downloadUrl || '');
|
return {
|
...file,
|
type: viewType,
|
name: file?.name || file?.originalFilename || file?.bucketFilename,
|
bucketFilename: file?.bucketFilename || file?.name,
|
originalFilename: file?.originalFilename || file?.name,
|
url: u,
|
downloadUrl: u,
|
size: file?.size || file?.byteSize,
|
};
|
};
|
|
attachmentList.value.push(...beforeList.map((f) => mapToViewFile(f, 0)));
|
attachmentList.value.push(...afterList.map((f) => mapToViewFile(f, 1)));
|
attachmentList.value.push(...issueList.map((f) => mapToViewFile(f, 2)));
|
};
|
|
// 将后端返回的文件地址规范成可访问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 switchViewType = (type) => {
|
currentViewType.value = type;
|
};
|
|
// 根据type获取对应分类的附件
|
const getAttachmentsByType = (typeValue) => {
|
return attachmentList.value.filter((file) => file.type === typeValue) || [];
|
};
|
|
// 获取当前查看类型的附件
|
const getCurrentViewAttachments = () => {
|
switch (currentViewType.value) {
|
case 'before':
|
return getAttachmentsByType(0);
|
case 'after':
|
return getAttachmentsByType(1);
|
case 'issue':
|
return getAttachmentsByType(2);
|
default:
|
return [];
|
}
|
};
|
|
// 判断是否为图片文件
|
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 = getCurrentViewAttachments()
|
.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>
|