提交
This commit is contained in:
1 parent
f90726927e
commit
6eea13370c
103 files changed
+38401
-32231
No files matched your search
@@ -0,0 +1,222 @@
|
||||
<!-- 协议附件 — 迷你缩略图只读(卡片列表用) -->
|
||||
<template>
|
||||
<div class="protocol-attachment-mini">
|
||||
<div v-if="label" class="protocol-attachment-mini__label">{{ label }}</div>
|
||||
<span v-if="!files.length" class="protocol-attachment-mini__empty">{{ emptyText }}</span>
|
||||
<div v-else class="protocol-attachment-mini__list">
|
||||
<div
|
||||
v-for="(file, index) in files"
|
||||
:key="String(file.id || file.fileUrl || index)"
|
||||
class="protocol-attachment-mini__item"
|
||||
>
|
||||
<div
|
||||
class="protocol-attachment-mini__preview"
|
||||
:class="{ 'is-clickable': isImageFile(file) && file.fileUrl }"
|
||||
@click="isImageFile(file) && file.fileUrl ? emit('preview', file) : undefined"
|
||||
>
|
||||
<template v-if="isImageFile(file) && file.fileUrl">
|
||||
<img
|
||||
:src="resolveFileUrl(String(file.fileUrl || ''))"
|
||||
:alt="String(file.fileName || '')"
|
||||
/>
|
||||
</template>
|
||||
<template v-else-if="isVideoFile(file)">
|
||||
<div class="protocol-attachment-mini__icon protocol-attachment-mini__icon--video">
|
||||
<el-icon :size="20"><VideoPlay /></el-icon>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="protocol-attachment-mini__icon protocol-attachment-mini__icon--doc">
|
||||
<el-icon :size="20"><Document /></el-icon>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class="protocol-attachment-mini__footer">
|
||||
<span class="protocol-attachment-mini__name" :title="String(file.fileName || '')">{{
|
||||
file.fileName || '未命名'
|
||||
}}</span>
|
||||
<el-icon
|
||||
class="protocol-attachment-mini__download"
|
||||
title="下载"
|
||||
@click.stop="emit('download', file)"
|
||||
>
|
||||
<Download />
|
||||
</el-icon>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Document, Download, VideoPlay } from '@element-plus/icons-vue'
|
||||
import { attachmentFullUrl } from '@/composables/useAttachmentActions'
|
||||
|
||||
defineOptions({ name: 'ProtocolAttachmentMiniReadonly' })
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
label?: string
|
||||
files: Array<Record<string, unknown>>
|
||||
emptyText?: string
|
||||
}>(),
|
||||
{
|
||||
label: '',
|
||||
emptyText: '无'
|
||||
}
|
||||
)
|
||||
|
||||
const emit = defineEmits<{
|
||||
preview: [file: Record<string, unknown>]
|
||||
download: [file: Record<string, unknown>]
|
||||
}>()
|
||||
|
||||
function resolveFileUrl(suffix: string) {
|
||||
return attachmentFullUrl(suffix)
|
||||
}
|
||||
|
||||
function isImageFile(file: Record<string, unknown>) {
|
||||
const url = String(file.fileUrl || '').toLowerCase()
|
||||
const name = String(file.fileName || '').toLowerCase()
|
||||
return (
|
||||
/\.(jpg|jpeg|png|gif|bmp|webp|svg)(\?|$)/.test(url) ||
|
||||
/\.(jpg|jpeg|png|gif|bmp|webp|svg)$/.test(name) ||
|
||||
String(file.fileType || '').startsWith('image/')
|
||||
)
|
||||
}
|
||||
|
||||
function isVideoFile(file: Record<string, unknown>) {
|
||||
const url = String(file.fileUrl || '').toLowerCase()
|
||||
const name = String(file.fileName || '').toLowerCase()
|
||||
return (
|
||||
/\.(mp4|webm|ogg|mov|avi)(\?|$)/.test(url) ||
|
||||
/\.(mp4|webm|ogg|mov|avi)$/.test(name) ||
|
||||
String(file.fileType || '').startsWith('video/')
|
||||
)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.protocol-attachment-mini {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.protocol-attachment-mini__label {
|
||||
margin-bottom: 3px;
|
||||
font-size: 10px;
|
||||
line-height: 1.35;
|
||||
color: var(--mint-text-muted, #64748b);
|
||||
}
|
||||
|
||||
.protocol-attachment-mini__empty {
|
||||
font-size: 11px;
|
||||
line-height: 1.35;
|
||||
color: var(--mint-text-muted, #64748b);
|
||||
}
|
||||
|
||||
.protocol-attachment-mini__list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.protocol-attachment-mini__item {
|
||||
width: 80px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 6px;
|
||||
background: #fff;
|
||||
overflow: hidden;
|
||||
transition:
|
||||
border-color 0.15s,
|
||||
box-shadow 0.15s;
|
||||
|
||||
&:hover {
|
||||
border-color: #047857;
|
||||
box-shadow: 0 2px 6px rgb(4 120 87 / 10%);
|
||||
}
|
||||
}
|
||||
|
||||
.protocol-attachment-mini__preview {
|
||||
width: 100%;
|
||||
height: 56px;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #f8fafc;
|
||||
overflow: hidden;
|
||||
|
||||
&.is-clickable {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.protocol-attachment-mini__icon {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
&--video {
|
||||
background: #ede9fe;
|
||||
color: #7c3aed;
|
||||
}
|
||||
|
||||
&--doc {
|
||||
background: #e0f2fe;
|
||||
color: #0284c7;
|
||||
}
|
||||
}
|
||||
|
||||
.protocol-attachment-mini__footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
padding: 3px 4px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.protocol-attachment-mini__name {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
font-size: 10px;
|
||||
line-height: 1.3;
|
||||
color: #334155;
|
||||
}
|
||||
|
||||
.protocol-attachment-mini__download {
|
||||
flex-shrink: 0;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 11px;
|
||||
color: var(--mint-text-muted, #64748b);
|
||||
cursor: pointer;
|
||||
background: #f1f5f9;
|
||||
transition:
|
||||
background 0.15s,
|
||||
color 0.15s;
|
||||
|
||||
&:hover {
|
||||
background: #047857;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,10 +1,9 @@
|
||||
<!-- 样本编码 + 特殊样本警示(列表场景:编码与说明均为红色加粗) -->
|
||||
<!-- 样本编码 + 特殊样本警示(特殊样本以 danger Tag 展示) -->
|
||||
<template>
|
||||
<span
|
||||
class="sample-code-with-special"
|
||||
:class="[
|
||||
`sample-code-with-special--${layout}`,
|
||||
{ 'sample-code-with-special--danger': isSpecial },
|
||||
{ 'sample-code-with-special--mono': monospace }
|
||||
]"
|
||||
>
|
||||
@@ -14,6 +13,7 @@
|
||||
:is-special="isSpecial"
|
||||
:special-remark="specialRemark"
|
||||
:layout="layout"
|
||||
:size="tagSize"
|
||||
/>
|
||||
</span>
|
||||
</template>
|
||||
@@ -32,6 +32,7 @@
|
||||
fallback?: string
|
||||
showCode?: boolean
|
||||
monospace?: boolean
|
||||
tagSize?: 'large' | 'default' | 'small'
|
||||
}>(),
|
||||
{
|
||||
sampleCode: '',
|
||||
@@ -40,7 +41,8 @@
|
||||
layout: 'inline',
|
||||
fallback: '—',
|
||||
showCode: true,
|
||||
monospace: false
|
||||
monospace: false,
|
||||
tagSize: 'small'
|
||||
}
|
||||
)
|
||||
|
||||
@@ -53,20 +55,21 @@
|
||||
<style scoped lang="scss">
|
||||
.sample-code-with-special {
|
||||
&.sample-code-with-special--inline {
|
||||
display: inline;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
&.sample-code-with-special--block {
|
||||
display: block;
|
||||
min-width: 0;
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.sample-code-with-special--mono .sample-code-with-special__code {
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
||||
}
|
||||
|
||||
.sample-code-with-special--danger .sample-code-with-special__code {
|
||||
color: var(--el-color-danger);
|
||||
font-weight: 700;
|
||||
}
|
||||
</style>
|
||||
@@ -1,8 +1,7 @@
|
||||
<!-- 特殊样本警示展示(红色加粗) -->
|
||||
<!-- 特殊样本警示展示(danger Tag) -->
|
||||
<template>
|
||||
<span v-if="isSpecial" class="special-sample-display" :class="`special-sample-display--${layout}`">
|
||||
<span class="special-sample-mark">【特殊样本】</span>
|
||||
<span v-if="displayRemark" class="special-sample-remark">{{ displayRemark }}</span>
|
||||
<el-tag type="danger" :size="size">{{ tagText }}</el-tag>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
@@ -14,35 +13,46 @@
|
||||
isSpecial?: boolean
|
||||
specialRemark?: string
|
||||
layout?: 'inline' | 'block'
|
||||
size?: 'large' | 'default' | 'small'
|
||||
}>(),
|
||||
{
|
||||
isSpecial: false,
|
||||
specialRemark: '',
|
||||
layout: 'inline'
|
||||
layout: 'inline',
|
||||
size: 'small'
|
||||
}
|
||||
)
|
||||
|
||||
const displayRemark = computed(() => String(props.specialRemark || '').trim())
|
||||
|
||||
const tagText = computed(() => {
|
||||
if (displayRemark.value) return `特殊样本:${displayRemark.value}`
|
||||
return '特殊样本'
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.special-sample-display {
|
||||
&.special-sample-display--inline {
|
||||
display: inline;
|
||||
display: inline-flex;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
&.special-sample-display--block {
|
||||
display: block;
|
||||
margin-top: 4px;
|
||||
min-width: 0;
|
||||
max-width: 100%;
|
||||
|
||||
:deep(.el-tag) {
|
||||
display: inline-block;
|
||||
max-width: 100%;
|
||||
height: auto !important;
|
||||
white-space: normal;
|
||||
word-break: break-word;
|
||||
line-height: 1.4;
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.special-sample-mark,
|
||||
.special-sample-remark {
|
||||
color: var(--el-color-danger);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.special-sample-remark {
|
||||
margin-left: 4px;
|
||||
}
|
||||
</style>
|
||||
Reference in new issue
Block a user