| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866 |
- <!-- 采集协议单管理 · 储存证 Tab -->
- <!-- eslint-disable vue/no-v-model-argument -->
- <template>
- <div class="storage-cert-tab">
- <div class="filter-block">
- <el-form :model="searchForm" class="filter-form" @submit.prevent>
- <div class="filter-grid">
- <el-form-item label="关键词">
- <el-input v-model="searchForm.keyword" placeholder="姓名" clearable />
- </el-form-item>
- <el-form-item label="制作状态">
- <el-select
- v-model="searchForm.makeStatus"
- placeholder="全部"
- clearable
- style="width: 100%"
- >
- <el-option label="未制作" value="0" />
- <el-option label="已制作" value="1" />
- </el-select>
- </el-form-item>
- <el-form-item label="入库日期">
- <el-date-picker
- v-model="storageInDateRange"
- type="daterange"
- range-separator="至"
- start-placeholder="开始日期"
- end-placeholder="结束日期"
- value-format="YYYY-MM-DD"
- style="width: 100%"
- />
- </el-form-item>
- </div>
- <div class="filter-actions-row">
- <div class="filter-actions-end">
- <el-button class="btn-ghost" @click="handleReset">
- <el-icon><RefreshLeft /></el-icon>重置
- </el-button>
- <el-button class="btn-main" type="primary" @click="handleSearch">
- <el-icon><Search /></el-icon>搜索
- </el-button>
- </div>
- </div>
- </el-form>
- </div>
- <div class="list-body list-wrap">
- <el-table
- class="list-table"
- :data="tableData"
- v-loading="loading"
- stripe
- row-key="id"
- :row-class-name="storageCertRowClassName"
- @row-dblclick="handleView"
- >
- <el-table-column
- type="index"
- label="序号"
- min-width="68"
- align="center"
- header-align="center"
- :index="(i) => (pagination.page - 1) * pagination.pageSize + i + 1"
- />
- <el-table-column
- prop="protocolStatus"
- label="协议状态"
- min-width="100"
- align="center"
- header-align="center"
- >
- <template #default="{ row }">
- <el-tag
- :type="protocolStatusTagType(String(row.protocolStatus || ''))"
- size="small"
- >{{ protocolStatusDisplayLabel(String(row.protocolStatus || ''), String(row.voidType || '')) }}</el-tag
- >
- </template>
- </el-table-column>
- <el-table-column
- prop="motherName"
- label="母亲姓名"
- min-width="100"
- align="center"
- header-align="center"
- />
- <el-table-column
- prop="motherPhone"
- label="母亲联系方式"
- min-width="130"
- align="center"
- header-align="center"
- >
- <template #default="{ row }">{{ row.motherPhone || '—' }}</template>
- </el-table-column>
- <el-table-column
- prop="motherIdCard"
- label="身份证号"
- min-width="170"
- align="center"
- header-align="center"
- >
- <template #default="{ row }">{{ maskIdCard(row.motherIdCard) }}</template>
- </el-table-column>
- <el-table-column
- prop="newbornInfo"
- label="新生儿信息"
- min-width="200"
- align="center"
- header-align="center"
- show-overflow-tooltip
- />
- <el-table-column
- prop="sampleInfo"
- label="样本信息"
- min-width="200"
- align="center"
- header-align="center"
- show-overflow-tooltip
- />
- <el-table-column
- prop="storageInDate"
- label="入库日期"
- min-width="120"
- align="center"
- header-align="center"
- >
- <template #default="{ row }">{{ formatStorageInDate(row.storageInDate) }}</template>
- </el-table-column>
- <el-table-column
- prop="makeStatus"
- label="制作状态"
- min-width="100"
- align="center"
- header-align="center"
- >
- <template #default="{ row }">
- <el-tag :type="row.makeStatus == '1' ? 'success' : 'warning'" size="small">
- {{ row.makeStatus == '1' ? '已制作' : '未制作' }}
- </el-tag>
- </template>
- </el-table-column>
- <el-table-column
- prop="makeDate"
- label="制作日期"
- min-width="120"
- align="center"
- header-align="center"
- >
- <template #default="{ row }">{{ formatStorageInDate(row.makeDate) }}</template>
- </el-table-column>
- <el-table-column
- label="操作"
- min-width="260"
- fixed="right"
- align="center"
- header-align="center"
- >
- <template #default="{ row }">
- <el-button class="btn-op-item primary" @click="handleUpload(row)">
- <el-icon><Upload /></el-icon>上传储存证
- </el-button>
- <el-button class="btn-op-item" @click="handleView(row)">
- <el-icon><View /></el-icon>查看
- </el-button>
- </template>
- </el-table-column>
- </el-table>
- <div class="pagination list-pager-bar">
- <el-pagination
- :current-page="pagination.page"
- :page-size="pagination.pageSize"
- :total="pagination.total"
- :page-sizes="[10, 20, 50]"
- layout="total, sizes, prev, pager, next"
- @size-change="(val) => ((pagination.page = 1), (pagination.pageSize = val), loadData())"
- @current-change="(val) => ((pagination.page = val), loadData())"
- />
- </div>
- </div>
- <!-- 上传储存证弹窗 -->
- <el-dialog
- v-model="uploadDialogVisible"
- title="上传储存证"
- width="720px"
- class="dialog-form-layout"
- align-center
- append-to-body
- modal-class="protocol-dialog-mask"
- destroy-on-close
- :close-on-click-modal="false"
- >
- <el-form
- ref="uploadFormRef"
- :model="uploadForm"
- :rules="uploadFormRules"
- label-position="top"
- class="storage-cert-dialog-form"
- scroll-to-error
- :scroll-into-view-options="{ behavior: 'smooth', block: 'center' }"
- >
- <el-form-item label="母亲姓名">
- <el-input :model-value="uploadRow?.motherName" disabled />
- </el-form-item>
- <el-form-item label="储存证附件" class="form-col-span-2">
- <div
- class="attachment-upload"
- :class="{ 'is-dragover': isCertAttachDragover }"
- v-bind="certAttachDropHandlers"
- >
- <div class="attachment-list">
- <el-upload
- ref="certAttachmentUploadRef"
- :auto-upload="false"
- :show-file-list="false"
- :multiple="true"
- accept="image/*,video/*,.pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx,.txt,.zip,.rar"
- @change="handleCertAttachmentChange"
- >
- <div class="upload-card">
- <el-icon :size="28" color="#047857"><Plus /></el-icon>
- <span class="upload-card-text">上传附件</span>
- </div>
- </el-upload>
- <div
- v-for="(file, index) in certAttachments"
- :key="file.id || index"
- class="attachment-item"
- :class="{ 'is-uploading': file._uploading }"
- >
- <div
- class="attachment-preview"
- @click="
- isImageFile(file) && file.fileUrl ? handlePreviewAttachment(file) : undefined
- "
- >
- <template v-if="isImageFile(file) && file.fileUrl">
- <img :src="fullFileUrl(file.fileUrl)" :alt="file.fileName" />
- </template>
- <template v-else-if="isImageFile(file) && !file.fileUrl">
- <el-icon :size="24" color="#94a3b8"><Loading /></el-icon>
- </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="handleDownloadAttachment(file)"
- ><Download
- /></el-icon>
- <el-icon
- class="action-btn action-btn--danger"
- title="删除"
- @click="removeCertAttachment(index)"
- ><Delete
- /></el-icon>
- </div>
- </div>
- </div>
- </div>
- <span class="upload-hint"
- >支持拖拽或点击上传图片、视频、PDF、Word、Excel等文件,单文件不超过 20MB</span
- >
- </div>
- </el-form-item>
- <div class="form-grid-2">
- <el-form-item label="制作日期" prop="makeDate">
- <el-date-picker
- v-model="uploadForm.makeDate"
- type="date"
- format="YYYYMMDD"
- value-format="YYYYMMDD"
- placeholder="选择日期"
- style="width: 100%"
- />
- </el-form-item>
- <el-form-item label="备注">
- <el-input v-model="uploadForm.remark" type="textarea" :rows="2" placeholder="可选" />
- </el-form-item>
- </div>
- </el-form>
- <template #footer>
- <el-button class="btn-ghost" @click="uploadDialogVisible = false">
- <el-icon><Close /></el-icon>取消
- </el-button>
- <el-button
- class="btn-main"
- type="primary"
- :loading="submitLoading"
- @click="handleSubmitUpload"
- >
- <el-icon><Check /></el-icon>确定
- </el-button>
- </template>
- </el-dialog>
- <el-dialog
- v-model="previewVisible"
- title="文件预览"
- width="800px"
- align-center
- append-to-body
- destroy-on-close
- >
- <div v-if="previewType === 'image'" class="preview-container">
- <img
- :src="previewUrl"
- alt="预览"
- style="max-width: 100%; max-height: 70vh; display: block; margin: 0 auto"
- />
- </div>
- <div v-else class="preview-container">
- <video
- :src="previewUrl"
- controls
- style="max-width: 100%; max-height: 70vh; display: block; margin: 0 auto"
- />
- </div>
- </el-dialog>
- <!-- 查看弹窗(与满月待办管理等一致:审核+储存证+保险) -->
- <ProtocolViewDialog
- v-model="viewVisible"
- :protocol-id="viewProtocolId"
- :list-row="viewRow"
- show-audit-history
- show-storage-cert
- show-fullmoon-insurance
- />
- </div>
- </template>
- <script setup lang="ts">
- import { ref, reactive, onMounted, nextTick } from 'vue'
- import { ElMessage } from 'element-plus'
- import {
- Plus,
- Close,
- Check,
- Search,
- RefreshLeft,
- View,
- Upload,
- Delete,
- Document,
- VideoPlay,
- Loading,
- Download
- } from '@element-plus/icons-vue'
- import ProtocolViewDialog from '../protocol/ProtocolViewDialog.vue'
- import { fetchStorageCertList } from '@/api/admin'
- import {
- fetchUpdateCollectionProtocol,
- type CollectionProtocolRow
- } from '@/api/collection-protocol'
- import { fetchUploadAttachment } from '@/api/business'
- import { downloadAttachmentFile } from '@/utils/download-attachment'
- import { useAttachmentDropZone } from '@/composables/useAttachmentDropZone'
- import { maskIdCard } from '@/utils/mask-sensitive'
- import { protocolStatusDisplayLabel, protocolStatusTagType } from '@/utils/status-tag-registry'
- interface CertAttachmentItem {
- id: string
- fileName: string
- fileUrl: string
- fileSize?: number
- fileSizeDisplay?: string
- fileType?: string
- _uploading?: boolean
- }
- defineOptions({ name: 'CellStorageStorageCertTab' })
- const searchForm = reactive({ keyword: '', makeStatus: '0' })
- const storageInDateRange = ref<[string, string] | null>(null)
- const tableData = ref<any[]>([])
- const loading = ref(false)
- const pagination = reactive({ page: 1, pageSize: 10, total: 0 })
- // ---------------------------------------------------------------------------
- // 上传弹窗
- // ---------------------------------------------------------------------------
- const uploadDialogVisible = ref(false)
- const uploadRow = ref<any>(null)
- const uploadFormRef = ref()
- const certAttachmentUploadRef = ref()
- const certAttachments = ref<CertAttachmentItem[]>([])
- const submitLoading = ref(false)
- const minioBaseUrl = (import.meta.env.VITE_MINIO_BASE_URL || '').replace(/\/$/, '')
- const uploadForm = reactive({
- makeDate: '',
- remark: ''
- })
- const uploadFormRules = {
- makeDate: [{ required: true, message: '请选择制作日期', trigger: 'change' }]
- }
- const previewVisible = ref(false)
- const previewUrl = ref('')
- const previewType = ref<'image' | 'video'>('image')
- // ---------------------------------------------------------------------------
- // 查看弹窗(与满月待办管理等一致)
- // ---------------------------------------------------------------------------
- const viewVisible = ref(false)
- const viewProtocolId = ref<string | null>(null)
- const viewRow = ref<CollectionProtocolRow | null>(null)
- /** 新生儿已满月且储存证未制作时标红(列表仅含已生效协议) */
- function isStorageCertMakePendingRow(row: Record<string, unknown>) {
- if (String(row.makeStatus ?? '0') === '1') return false
- const delivery = String(row.newbornDeliveryTime || '').trim()
- if (!delivery) return false
- const deliveryDate = new Date(delivery.replace(/-/g, '/'))
- if (Number.isNaN(deliveryDate.getTime())) return false
- const fullmoon = new Date(deliveryDate)
- fullmoon.setDate(fullmoon.getDate() + 30)
- const today = new Date()
- today.setHours(0, 0, 0, 0)
- fullmoon.setHours(0, 0, 0, 0)
- return fullmoon <= today
- }
- function storageCertRowClassName({ row }: { row: Record<string, unknown> }) {
- return isStorageCertMakePendingRow(row) ? 'row-storage-cert-pending' : ''
- }
- function formatStorageInDate(val: unknown) {
- const s = String(val || '').trim()
- if (!s) return '—'
- const digits = s.replace(/\D/g, '')
- if (digits.length >= 8) {
- return `${digits.slice(0, 4)}-${digits.slice(4, 6)}-${digits.slice(6, 8)}`
- }
- return s
- }
- // ---------------------------------------------------------------------------
- // 数据加载
- // ---------------------------------------------------------------------------
- async function loadData() {
- loading.value = true
- try {
- const res = await fetchStorageCertList({
- page: pagination.page,
- pageSize: pagination.pageSize,
- keyword: searchForm.keyword || undefined,
- makeStatus: searchForm.makeStatus || undefined,
- storageInDateStart: storageInDateRange.value?.[0] || undefined,
- storageInDateEnd: storageInDateRange.value?.[1] || undefined
- })
- tableData.value = (res as any)?.list || []
- pagination.total = (res as any)?.total ?? 0
- } finally {
- loading.value = false
- }
- }
- function handleSearch() {
- pagination.page = 1
- loadData()
- }
- function handleReset() {
- searchForm.keyword = ''
- searchForm.makeStatus = '0'
- storageInDateRange.value = null
- pagination.page = 1
- loadData()
- }
- // ---------------------------------------------------------------------------
- // 上传储存证
- // ---------------------------------------------------------------------------
- function fullFileUrl(suffix: string) {
- if (!suffix) return ''
- if (suffix.startsWith('http://') || suffix.startsWith('https://') || suffix.startsWith('/')) {
- return suffix
- }
- return `${minioBaseUrl}/${suffix}`
- }
- function isImageFile(file: CertAttachmentItem) {
- 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: CertAttachmentItem) {
- 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/')
- )
- }
- function parseCertUrlsToAttachments(urls: string): CertAttachmentItem[] {
- return urls
- .split(',')
- .map((u) => u.trim())
- .filter(Boolean)
- .map((url, index) => {
- const rawName = url.split('/').pop() || ''
- const fileName = decodeURIComponent(rawName) || `附件${index + 1}`
- return {
- id: `existing_${index}`,
- fileName,
- fileUrl: url,
- fileType: '',
- _uploading: false
- }
- })
- }
- function certAttachmentsToUrls() {
- return certAttachments.value
- .filter((f) => f.fileUrl && !f._uploading)
- .map((f) => f.fileUrl)
- .join(',')
- }
- async function handleCertAttachmentChange(uploadFile: any) {
- const file = uploadFile.raw || uploadFile
- if (!file) return
- const tempId = `temp_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`
- certAttachments.value.push({
- id: tempId,
- fileName: file.name,
- fileUrl: '',
- fileSize: file.size,
- fileSizeDisplay: `${(file.size / 1024).toFixed(1)} KB`,
- fileType: file.type || '',
- _uploading: true
- })
- const idx = certAttachments.value.length - 1
- try {
- const fd = new FormData()
- fd.append('folder', 'storage-cert')
- fd.append('file', file)
- const res = await fetchUploadAttachment(fd)
- certAttachments.value[idx] = {
- ...certAttachments.value[idx],
- id: res.id || tempId,
- fileName: file.name || res.fileName,
- fileUrl: res.fileUrl,
- fileSize: res.fileSize || file.size,
- fileSizeDisplay: res.fileSizeDisplay || `${(file.size / 1024).toFixed(1)} KB`,
- fileType: res.fileType || file.type || '',
- _uploading: false
- }
- } catch {
- ElMessage.error('文件上传失败')
- certAttachments.value.splice(idx, 1)
- } finally {
- nextTick(() => certAttachmentUploadRef.value?.clearFiles())
- }
- }
- function removeCertAttachment(index: number) {
- certAttachments.value.splice(index, 1)
- }
- const { isDragover: isCertAttachDragover, dropZoneHandlers: certAttachDropHandlers } =
- useAttachmentDropZone({
- onFiles: (files) => {
- for (const file of files) {
- void handleCertAttachmentChange({ raw: file })
- }
- }
- })
- function handlePreviewAttachment(file: CertAttachmentItem) {
- if (file._uploading) return
- previewUrl.value = fullFileUrl(file.fileUrl)
- previewType.value = isVideoFile(file) ? 'video' : 'image'
- previewVisible.value = true
- }
- function handleDownloadAttachment(file: CertAttachmentItem) {
- downloadAttachmentFile(file, { fullFileUrl })
- }
- function handleUpload(row: any) {
- uploadRow.value = row
- certAttachments.value = parseCertUrlsToAttachments(String(row.certImageUrls || ''))
- uploadForm.makeDate = String(row.makeDate || '').trim() || getTodayStr()
- uploadForm.remark = String(row.remark || '').trim()
- uploadDialogVisible.value = true
- }
- async function handleSubmitUpload() {
- if (certAttachments.value.some((f) => f._uploading)) {
- ElMessage.warning('附件上传中,请稍候')
- return
- }
- const uploaded = certAttachments.value.filter((f) => f.fileUrl && !f._uploading)
- const hasAttachments = uploaded.length > 0
- if (hasAttachments) {
- const valid = await uploadFormRef.value?.validate?.().catch(() => false)
- if (!valid) return
- }
- submitLoading.value = true
- try {
- const protocolId = uploadRow.value?.protocolId || uploadRow.value?.id
- await fetchUpdateCollectionProtocol(protocolId, {
- storageCert: {
- makeStatus: hasAttachments ? '1' : '0',
- certImageUrls: hasAttachments ? certAttachmentsToUrls() : '',
- makeDate: hasAttachments ? uploadForm.makeDate : '',
- remark: uploadForm.remark?.trim() || undefined
- }
- })
- ElMessage.success(hasAttachments ? '上传成功' : '保存成功')
- uploadDialogVisible.value = false
- await loadData()
- } catch {
- /* 全局已处理 */
- } finally {
- submitLoading.value = false
- }
- }
- function handleView(row: any) {
- viewProtocolId.value = String(row.protocolId || row.id)
- viewRow.value = row as CollectionProtocolRow
- viewVisible.value = true
- }
- // ---------------------------------------------------------------------------
- // 工具函数
- // ---------------------------------------------------------------------------
- function getTodayStr(): string {
- const d = new Date()
- const yyyy = d.getFullYear()
- const mm = String(d.getMonth() + 1).padStart(2, '0')
- const dd = String(d.getDate()).padStart(2, '0')
- return `${yyyy}${mm}${dd}`
- }
- // 输入检索条件时自动搜索(防抖 300ms)
- watchDebounced(
- [searchForm, storageInDateRange],
- () => {
- pagination.page = 1
- loadData()
- },
- { debounce: 300, deep: true }
- )
- onMounted(() => {
- loadData()
- })
- </script>
- <style scoped lang="scss">
- .storage-cert-dialog-form {
- :deep(.el-form-item) {
- margin-bottom: 0;
- }
- }
- .form-col-span-2 {
- grid-column: 1 / -1;
- }
- .attachment-upload {
- width: 100%;
- }
- .upload-card {
- width: 140px;
- height: 96px;
- border: 1px dashed #a8d4c4;
- border-radius: 8px;
- background: #f0fdf4;
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- gap: 6px;
- cursor: pointer;
- transition:
- border-color 0.15s,
- background 0.15s;
- &:hover {
- border-color: #047857;
- border-style: solid;
- background: #e8f4ee;
- }
- }
- .upload-card-text {
- font-size: 12px;
- color: #047857;
- font-weight: 500;
- line-height: 1;
- }
- .upload-hint {
- display: block;
- font-size: 11px;
- color: var(--mint-text-muted, #334155);
- line-height: 1;
- margin-top: 8px;
- }
- .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);
- }
- &.is-uploading {
- opacity: 0.6;
- pointer-events: none;
- }
- }
- .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;
- }
- &--danger:hover {
- background: #ef4444;
- color: #fff;
- }
- }
- .preview-container {
- text-align: center;
- }
- .list-table :deep(tr.row-storage-cert-pending > td.el-table__cell) {
- background-color: #fee2e2 !important;
- color: #991b1b;
- }
- .list-table :deep(tr.row-storage-cert-pending:hover > td.el-table__cell) {
- background-color: #fecaca !important;
- }
- </style>
|