Files
zkxw/web-admin/src/views/cell-storage/protocol/ProtocolAttachmentReadonly.vue
T
zlwuandCursor 76316fad7a fix(web-admin): 修复样本接收弹窗协议附件不展示
弹窗改用与列表一致的 MiniReadonly 缩略图,并统一 attachmentFullUrl 解析 /minio 路径;编辑时合并详情附件字段,分组顺序优先知情同意书与调查表。

关联 #43

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-11-05 22:42:47 +08:00

233 lines
5.4 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!-- 采集协议单 — 只读附件列表(样式对齐新增/编辑弹窗 attachment-upload) -->
<template>
<span v-if="!files.length" class="protocol-attachment-empty">暂无附件</span>
<div v-else class="attachment-upload">
<div class="attachment-list">
<div
v-for="(file, index) in files"
:key="file.id || file.fileUrl || index"
class="attachment-item"
:class="{ 'is-compact': size === 'compact' }"
>
<div
class="attachment-preview"
@click="file.fileUrl ? emit('preview', file) : undefined"
>
<template v-if="isImageFile(file) && file.fileUrl">
<img :src="resolveFileUrl(file.fileUrl)" :alt="file.fileName" />
</template>
<template v-else-if="isVideoFile(file)">
<div class="attachment-preview__icon attachment-preview__icon--video">
<el-icon :size="28"><VideoPlay /></el-icon>
</div>
</template>
<template v-else>
<div class="attachment-preview__icon attachment-preview__icon--doc">
<el-icon :size="28"><Document /></el-icon>
</div>
</template>
</div>
<div class="attachment-footer">
<span class="attachment-name" :title="file.fileName">{{
file.fileName || '未命名'
}}</span>
<div class="attachment-actions">
<el-icon class="action-btn" title="下载" @click.stop="emit('download', file)">
<Download />
</el-icon>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { Document, Download, VideoPlay } from '@element-plus/icons-vue'
import { attachmentFullUrl } from '@/composables/useAttachmentActions'
withDefaults(
defineProps<{
files: Array<Record<string, any>>
size?: 'default' | 'compact'
}>(),
{ size: 'default' }
)
const emit = defineEmits<{
(e: 'preview', file: Record<string, any>): void
(e: 'download', file: Record<string, any>): void
}>()
function resolveFileUrl(suffix: string) {
return attachmentFullUrl(suffix)
}
function isImageFile(file: { fileType?: string; fileName?: string; fileUrl?: string }) {
const url = (file.fileUrl || '').toLowerCase()
const name = (file.fileName || '').toLowerCase()
return (
/\.(jpg|jpeg|png|gif|bmp|webp|svg)(\?|$)/.test(url) ||
/\.(jpg|jpeg|png|gif|bmp|webp|svg)$/.test(name) ||
(file.fileType || '').startsWith('image/')
)
}
function isVideoFile(file: { fileType?: string; fileName?: string; fileUrl?: string }) {
const url = (file.fileUrl || '').toLowerCase()
const name = (file.fileName || '').toLowerCase()
return (
/\.(mp4|webm|ogg|mov|avi)(\?|$)/.test(url) ||
/\.(mp4|webm|ogg|mov|avi)$/.test(name) ||
(file.fileType || '').startsWith('video/')
)
}
</script>
<style scoped lang="scss">
.protocol-attachment-empty {
font-size: 13px;
color: var(--mint-text-muted, #334155);
}
.attachment-upload {
width: 100%;
}
.attachment-list {
display: flex;
flex-wrap: wrap;
gap: 12px;
}
.attachment-item {
width: 140px;
display: flex;
flex-direction: column;
border: 1px solid #e2e8f0;
border-radius: 8px;
background: #fff;
overflow: hidden;
transition:
border-color 0.15s,
box-shadow 0.15s;
&:hover {
border-color: #047857;
box-shadow: 0 2px 8px rgba(4, 120, 87, 0.1);
}
}
.attachment-preview {
width: 100%;
height: 96px;
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
background: #f8fafc;
overflow: hidden;
cursor: pointer;
img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
}
.attachment-preview__icon {
width: 48px;
height: 48px;
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
&--video {
background: #ede9fe;
color: #7c3aed;
}
&--doc {
background: #e0f2fe;
color: #0284c7;
}
}
.attachment-footer {
display: flex;
flex-direction: column;
flex: 1;
padding: 6px 8px;
}
.attachment-name {
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
font-size: 11px;
color: #334155;
line-height: 1.4;
word-break: break-all;
min-height: calc(11px * 1.4 * 2);
}
.attachment-actions {
display: flex;
justify-content: center;
align-items: center;
gap: 8px;
margin-top: 4px;
flex-shrink: 0;
}
.action-btn {
width: 22px;
height: 22px;
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
color: var(--mint-text-muted, #334155);
cursor: pointer;
background: #f1f5f9;
transition:
background 0.15s,
color 0.15s;
&:hover {
background: #047857;
color: #fff;
}
}
.attachment-item.is-compact {
width: 88px;
.attachment-preview {
height: 64px;
}
.attachment-preview__icon {
width: 32px;
height: 32px;
border-radius: 8px;
}
.attachment-footer {
padding: 4px 6px;
}
.attachment-name {
-webkit-line-clamp: 1;
min-height: 0;
font-size: 10px;
}
}
</style>