Files
admin-web/src/modules/loading-lists/components/LoadingListDetailDrawer.vue
T
2026-10-20 23:34:11 +08:00

311 lines
24 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.
<script setup lang="ts">
import '@/styles/adminDensity.css'
import '@/styles/adminWorkbench.css'
import { computed, ref, watch } from 'vue'
import { ArrowLeft, Check, ChevronRight, Pencil, Printer } from '@lucide/vue'
import ApiErrorMeta from '@/shared/components/ApiErrorMeta.vue'
import DataTable, { type DataTableColumn } from '@/shared/components/DataTable.vue'
import DetailPageLayout from '@/shared/components/DetailPageLayout.vue'
import LoadErrorState from '@/shared/components/LoadErrorState.vue'
import StatusBadge from '@/shared/components/StatusBadge.vue'
import PalletLabelPrintDialog from './PalletLabelPrintDialog.vue'
import { loadingListStatusLabel } from '../status'
import { printLoadingList } from '../printLoadingList'
import type { ApiErrorDetails } from '@/shared/api/http'
import type { LoadingDetail, LoadingHistory, LoadingItem, LoadingListMode, LoadingListSummary, PreloadSummary } from '../api/platformLoadingLists'
const props = defineProps<{
mode: LoadingListMode
loading: boolean
detail: LoadingDetail | null
error: ApiErrorDetails | null
canEdit: boolean
canPushPda?: boolean
pdaAssigneeSelected?: boolean
dense?: boolean
embedded?: boolean
backLabel?: string
canEditTransport?: boolean
confirmSaving?: boolean
}>()
const emit = defineEmits<{ back: []; retry: []; edit: []; editRemarks: [item: LoadingItem]; confirmPreload: [] }>()
const actionColumn: DataTableColumn = { key: 'actions', label: '操作', fixed: 'left', width: 132, slotName: 'actions', hideable: false, movable: false }
const baseItemColumns: DataTableColumn[] = [
{ key: 'sequence', prop: 'loadSequence', label: '装柜顺序', width: 92 },
{ key: 'inboundCode', prop: 'inboundCode', label: '入仓码', minWidth: 170, className: 'identifier-column', showOverflowTooltip: true },
{ key: 'palletNo', prop: 'palletNo', label: '托盘号', minWidth: 155, className: 'identifier-column', showOverflowTooltip: true },
{ key: 'unitType', label: '装柜单位', width: 110, slotName: 'unitType' },
{ key: 'orderNo', prop: 'orderNo', label: '订单号', minWidth: 210, className: 'identifier-column', showOverflowTooltip: true },
{ key: 'containerNo', prop: 'containerNo', label: '集装箱号', minWidth: 165, className: 'identifier-column', showOverflowTooltip: true, formatter: (row: LoadingItem) => row.containerNo || '待补录' },
{ key: 'skuCode', prop: 'skuCode', label: 'SKU', minWidth: 220, className: 'identifier-column', showOverflowTooltip: true },
{ key: 'destinationWarehouse', prop: 'destinationWarehouse', label: '目的仓', minWidth: 150, className: 'identifier-column', showOverflowTooltip: true },
{ key: 'customerName', prop: 'customerName', label: '客户', minWidth: 220 },
{ key: 'taxRefundFlag', label: '是否退税', width: 96, align: 'center', slotName: 'taxRefundFlag' },
{ key: 'chineseName', prop: 'chineseName', label: '中文品名', minWidth: 160 },
{ key: 'englishName', prop: 'englishName', label: '英文品名', minWidth: 170 },
{ key: 'netWeightKg', label: '净重(kg)', width: 110, align: 'right', slotName: 'netWeight' },
{ key: 'packageCount', prop: 'packageCount', label: '箱数', width: 86, align: 'right' },
{ key: 'palletCount', prop: 'palletCount', label: '托盘数', width: 86, align: 'right' },
{ key: 'volumeCbm', label: '体积(m³)', width: 110, align: 'right', slotName: 'volume' },
{ key: 'grossWeightKg', label: '毛重(kg)', width: 110, align: 'right', slotName: 'grossWeight' },
{ key: 'dimensionalWeightKg', label: '材积重(kg)', width: 115, align: 'right', slotName: 'dimensionalWeight' },
{ key: 'productQuantity', prop: 'productQuantity', label: '产品数', width: 96, align: 'right' },
{ key: 'unitsPerPackage', prop: 'unitsPerPackage', label: '每箱产品数', width: 110, align: 'right' },
{ key: 'fbaTrackingNo', prop: 'fbaTrackingNo', label: 'FBA 追踪编码', minWidth: 155 },
{ key: 'shipmentNo', prop: 'shipmentNo', label: '货件号', minWidth: 140 },
{ key: 'shippingMark', prop: 'shippingMark', label: '唛头', minWidth: 130 },
{ key: 'customerRemark', prop: 'customerRemark', label: '备注(客户)', minWidth: 190, slotName: 'customerRemark' },
{ key: 'internalRemark', prop: 'internalRemark', label: '备注(内部)', minWidth: 190, slotName: 'internalRemark' },
{ key: 'warehouseRemark', prop: 'warehouseRemark', label: '备注(仓库)', minWidth: 210, slotName: 'warehouseRemark' },
{ key: 'customerServiceRemark', prop: 'customerServiceRemark', label: '备注(客服)', minWidth: 210, slotName: 'customerServiceRemark' },
{ key: 'frozenAt', label: '冻结时间', minWidth: 150, slotName: 'frozenAt' },
]
const summary = computed<LoadingListSummary | null>(() => props.detail?.summary || null)
const preloadSummary = computed(() => props.mode === 'preload' ? summary.value as PreloadSummary | null : null)
const canEditItemRemark = computed(() => props.mode === 'preload' && props.canEdit && ['EDITING', 'LOADING'].includes(summary.value?.status || ''))
const itemColumns = computed(() => canEditItemRemark.value ? [actionColumn, ...baseItemColumns] : baseItemColumns)
const volatileSnapshotFields = new Set(['requestHash', 'version', 'updatedAt', 'operatorId', 'operatorName', 'occurredAt'])
function stableSnapshotValue(value: unknown): unknown {
if (Array.isArray(value)) return value.map(stableSnapshotValue)
if (!value || typeof value !== 'object') return value
return Object.fromEntries(Object.entries(value as Record<string, unknown>)
.filter(([key]) => !volatileSnapshotFields.has(key))
.sort(([left], [right]) => left.localeCompare(right))
.map(([key, nested]) => [key, stableSnapshotValue(nested)]))
}
function normalizedSnapshot(value: string | null) {
if (!value) return null
try { return stableSnapshotValue(JSON.parse(value) as unknown) } catch { return value }
}
function isPureStatusNoop(history: LoadingHistory) {
return history.actionType === 'STATUS_UPDATE'
&& history.fromStatus === history.toStatus
&& JSON.stringify(normalizedSnapshot(history.beforeSnapshot)) === JSON.stringify(normalizedSnapshot(history.afterSnapshot))
}
function historyEventKey(history: LoadingHistory) {
if (history.operationId) return `operation:${history.operationId}`
return history.historyId ? `history:${history.historyId}` : null
}
const visibleHistories = computed(() => {
const seen = new Set<string>()
return (props.detail?.histories || []).filter((history) => {
if (isPureStatusNoop(history)) return false
const eventKey = historyEventKey(history)
if (!eventKey) return true
if (seen.has(eventKey)) return false
seen.add(eventKey)
return true
})
})
const historyExpanded = ref(props.mode !== 'preload')
let lastHistoryPlanId: string | number | undefined
watch(() => props.detail?.summary.planId, (id) => {
if (id == null) return
if (lastHistoryPlanId !== undefined && id !== lastHistoryPlanId) historyExpanded.value = props.mode !== 'preload'
lastHistoryPlanId = id
})
const blockingReasonLabels: Record<string, string> = {
LOADING_PLAN_NOT_EDITING: '装柜计划已不在可确认的编辑状态',
PRELOAD_LINK_MISSING: '装柜明细尚未完整关联入库预装票据',
CONTAINER_NO_MISSING: '尚未录入真实集装箱号,请先补录后再确认预装',
OCEAN_BOOKING_NOT_LINKED: '尚未关联真实订舱',
OCEAN_BOOKING_NOT_RELEASED: '关联订舱尚未放舱',
PDA_TASK_ALREADY_CREATED: 'PDA 任务已创建,无需重复确认',
PRELOAD_ALREADY_CONFIRMED: '整份预装计划已经确认',
}
const confirmationStatusLabels: Record<string, string> = {
PENDING: '待确认', PARTIALLY_CONFIRMED: '部分票据已确认', CONFIRMED: '已确认',
}
const oceanBookingStatusLabels: Record<string, string> = {
CONFIRMED: '已确认', CHANGE_PENDING: '改船处理中', RELEASED: '已放舱', CANCELLED: '已取消',
}
const pdaTaskStatusLabels: Record<string, string> = {
PENDING: '待领取', CLAIMED: '已领取', LOADING: '作业中', AWAITING_REVIEW: '待人工复核', COMPLETED: '已完成',
}
const blockingReasonText = computed(() => (preloadSummary.value?.readiness.blockingReasons || [])
.map((reason) => blockingReasonLabels[reason] || '存在未满足的确认条件')
.join(';'))
const title = computed(() => {
if (!summary.value) return props.mode === 'preload' ? '预装清单详情' : '装柜清单详情'
const number = props.mode === 'preload'
? (summary.value as PreloadSummary).preloadNo
: 'loadingListNo' in summary.value ? summary.value.loadingListNo : ''
return `${props.mode === 'preload' ? '预装清单' : '装柜清单'} · ${number}`
})
function actionLabel(action: string) {
return ({
CREATE: '创建计划', ASSIGN: '分配货物', ADJUST: '调整货物', DELETE: '移除货物',
RECOMMEND: '智能排柜', PUSH_PRELOAD: '推送预装', UPDATE_TRANSPORT: '修改运输信息',
CONFIRM_PRELOAD: '确认预装',
START_LOADING: '开始拣货', COMPLETE: '完成装柜',
} as Record<string, string>)[action] || '状态更新'
}
function formatDateTime(value?: string | null) {
return value ? value.replace('T', ' ').slice(0, 16) : '—'
}
function formatNumber(value?: number | null, digits = 3) {
if (value === null || value === undefined || !Number.isFinite(Number(value))) return '—'
return Number(value).toLocaleString('zh-CN', { maximumFractionDigits: digits })
}
</script>
<template>
<section v-loading="loading" class="loading-detail-content admin-workbench" :class="{ 'admin-density loading-density': mode === 'preload' || dense, 'workbench-panel is-compact is-page-surface': !embedded }" :aria-label="title" :aria-busy="loading">
<section class="detail-overview" aria-label="清单概览">
<header class="detail-page-header" :class="{ 'detail-page-header--embedded': embedded }">
<el-button link type="primary" class="detail-page-header__back" :class="{ 'density-back': mode === 'preload' || dense }" @click="emit('back')"><ArrowLeft :size="16" />{{ backLabel || '返回列表' }}</el-button>
<div v-if="!embedded" class="detail-page-header__identity">
<h1>{{ title }}</h1>
</div>
<div v-if="detail && summary" class="detail-page-header__actions">
<el-button v-if="mode === 'confirmed'" :disabled="loading" @click="printLoadingList"><Printer :size="16" />打印装柜清单</el-button>
<PalletLabelPrintDialog :dense="mode === 'preload' || dense" v-if="summary.warehouseId" :plan-id="summary.planId" :warehouse-id="summary.warehouseId" :disabled="!summary.containerNo || summary.status === 'EDITING'" />
<template v-if="canEditItemRemark">
<el-button v-if="canEditTransport" @click="emit('edit')"><Pencil :size="16" />{{ summary.containerNo ? '纠正集装箱号 / 航次' : '补录集装箱号 / 航次' }}</el-button>
<el-button v-if="canPushPda" type="primary" :loading="confirmSaving" :disabled="!preloadSummary?.readiness.canConfirm || !pdaAssigneeSelected" @click="emit('confirmPreload')"><Check :size="16" />确认预装并推送 PDA</el-button>
</template>
</div>
</header>
<section v-if="detail && summary" class="detail-summary" aria-label="清单基本信息">
<div><span>柜次号</span><strong>{{ summary.cabinetNo || '—' }}</strong></div>
<div><span>集装箱号</span><strong>{{ summary.containerNo || '待补录' }}</strong></div>
<div><span>仓库</span><strong>{{ summary.warehouseName || '—' }}</strong></div>
<div><span>状态</span><StatusBadge :status="loadingListStatusLabel(summary.status)" /></div>
<template v-if="preloadSummary">
<div><span>船名 / 航次</span><strong>{{ preloadSummary.vesselName || '—' }} / {{ preloadSummary.voyageNo || '—' }}</strong></div>
<div><span>箱型</span><strong>{{ preloadSummary.containerType || '—' }}</strong></div>
<div><span>预装确认</span><StatusBadge :status="confirmationStatusLabels[preloadSummary.readiness.confirmationStatus] || '未知状态'" /></div>
<div><span>订舱状态</span><strong>{{ preloadSummary.readiness.oceanBookingStatus ? (oceanBookingStatusLabels[preloadSummary.readiness.oceanBookingStatus] || '未知状态') : '未关联' }}</strong></div>
<div><span>PDA 状态</span><strong>{{ preloadSummary.readiness.pdaTaskStatus ? (pdaTaskStatusLabels[preloadSummary.readiness.pdaTaskStatus] || '未知状态') : (preloadSummary.readiness.readyForPda ? '可下发' : '未就绪') }}</strong></div>
<div class="detail-summary__wide"><span>客户</span><strong>{{ preloadSummary.customers.map((item) => item.customerName || '客户信息缺失').join('、') || '客户信息缺失' }}</strong></div>
</template>
<div><span>创建时间</span><strong>{{ formatDateTime(summary.createdAt) }}</strong></div>
</section>
</section>
<LoadErrorState v-if="error" title="清单详情加载失败" :message="error.message" @retry="emit('retry')">
<template #details><ApiErrorMeta :details="error" /></template>
</LoadErrorState>
<div v-else-if="detail && summary" class="detail-layout">
<DetailPageLayout :ariaLabel="title">
<div v-if="preloadSummary && preloadSummary.readiness.confirmationStatus !== 'CONFIRMED' && !preloadSummary.readiness.canConfirm" class="readiness-note" role="status">
<strong>暂不能确认预装:</strong>
<span>{{ blockingReasonText || '请刷新详情获取最新确认条件' }}</span>
</div>
<section class="detail-section">
<div class="workbench-table"><DataTable
:data="detail.items"
:columns="itemColumns"
stripe
preference-key="loading-list-detail-items"
row-key="itemId"
empty-text="暂无装柜货物明细"
aria-label="装柜货物明细"
>
<template #toolbar-start><div class="detail-section__heading"><h2>装柜货物明细 <span>{{ detail.items.length }} 条</span></h2><slot v-if="mode === 'preload'" name="pda-operator" /></div></template>
<template #actions="{ row }">
<el-button :class="{ 'density-action': mode === 'preload' || dense }" size="small" :type="mode === 'preload' ? undefined : 'primary'" :plain="mode !== 'preload'" :aria-label="`编辑 ${row.inboundCode || row.orderNo} 的备注`" @click="emit('editRemarks', row as LoadingItem)"><Pencil v-if="mode !== 'preload'" :size="14" />编辑备注</el-button>
</template>
<template #unitType="{ row }"><el-tag size="small" :type="row.unitType === 'SEALED_PALLET' ? 'success' : 'info'">{{ row.unitType === 'SEALED_PALLET' ? '封托托盘' : '箱件' }}</el-tag><small v-if="row.indivisible">整托不可拆</small></template>
<template #grossWeight="{ row }">{{ formatNumber(row.grossWeightKg) }}</template>
<template #netWeight="{ row }">{{ formatNumber(row.netWeightKg) }}</template>
<template #taxRefundFlag="{ row }">{{ row.taxRefundFlag === true ? '是' : row.taxRefundFlag === false ? '否' : '—' }}</template>
<template #volume="{ row }">{{ formatNumber(row.volumeCbm) }}</template>
<template #dimensionalWeight="{ row }">{{ formatNumber(row.dimensionalWeightKg) }}</template>
<template #customerRemark="{ row }"><div class="remark-cell"><span>{{ row.customerRemark || '—' }}</span></div></template>
<template #internalRemark="{ row }"><div class="remark-cell"><span>{{ row.internalRemark || '—' }}</span></div></template>
<template #warehouseRemark="{ row }"><div class="remark-cell"><span>{{ row.warehouseRemark || '—' }}</span></div></template>
<template #customerServiceRemark="{ row }"><div class="remark-cell"><span>{{ row.customerServiceRemark || '—' }}</span></div></template>
<template #frozenAt="{ row }">{{ formatDateTime(row.frozenAt) }}</template>
</DataTable></div>
</section>
<section class="history-section" :class="{ 'is-expanded': historyExpanded }" aria-label="操作历史">
<header><button type="button" class="history-section__toggle" :aria-expanded="historyExpanded" aria-controls="loading-history-table" @click="historyExpanded = !historyExpanded"><h2>操作历史</h2><span>{{ visibleHistories.length }} 条</span><ChevronRight :size="16" class="history-section__arrow" :class="{ 'is-expanded': historyExpanded }" aria-hidden="true" /></button></header>
<el-table v-if="historyExpanded" id="loading-history-table" :data="visibleHistories" border stripe empty-text="暂无操作历史" aria-label="清单操作历史">
<el-table-column label="时间" width="170"><template #default="{ row }">{{ formatDateTime(row.occurredAt) }}</template></el-table-column>
<el-table-column label="事件" width="140"><template #default="{ row }">{{ actionLabel(row.actionType) }}</template></el-table-column>
<el-table-column label="状态变化" min-width="200"><template #default="{ row }"><span v-if="row.fromStatus || row.toStatus">{{ loadingListStatusLabel(row.fromStatus) }} → {{ loadingListStatusLabel(row.toStatus) }}</span><span v-else>—</span></template></el-table-column>
<el-table-column label="操作人" min-width="160"><template #default="{ row }">{{ row.operatorName || '操作人信息缺失' }}</template></el-table-column>
<el-table-column label="原因 / 备注" min-width="240"><template #default="{ row }">{{ row.operationReason || '—' }}</template></el-table-column>
</el-table>
</section>
</DetailPageLayout>
</div>
</section>
</template>
<style scoped>
.loading-detail-content { display: grid; min-width: 0; gap: var(--admin-space-6, 16px); }
.detail-layout { display: grid; min-width: 0; }
.detail-overview { min-width: 0; }
.detail-page-header { display: grid; grid-template-columns: auto minmax(0, 1fr) auto; align-items: center; gap: var(--admin-space-6, 16px); padding: var(--admin-space-3, 8px) 0 var(--admin-space-5, 12px); }
.detail-page-header--embedded { grid-template-columns: minmax(0, 1fr) auto; padding-top: 0; }
.detail-page-header__back { margin-right: 2px; }
.detail-page-header__identity { min-width: 0; }
.detail-page-header__identity h1 { margin: 0; color: var(--workbench-text); font-size: 18px; line-height: 1.35; }
.detail-page-header__actions { display: flex; align-items: center; justify-content: flex-end; gap: var(--admin-space-3, 8px); }
.detail-page-header__actions :deep(.el-button + .el-button) { margin-left: 0; }
.detail-summary { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: var(--admin-space-2, 6px) 28px; padding: var(--admin-space-4, 10px) 0 2px; border-top: 1px solid var(--workbench-border); }
.detail-summary > div { display: flex; min-width: 0; min-height: 26px; align-items: center; gap: var(--admin-space-2, 6px); }
.detail-summary > div > span:first-child { flex: 0 0 auto; color: var(--workbench-muted); font-size: var(--admin-font-caption, 12px); white-space: nowrap; }
.detail-summary > div > span:first-child::after { content: ":"; }
.detail-summary strong { min-width: 0; color: var(--workbench-text); font-size: var(--admin-font-body, 13px); overflow-wrap: anywhere; }
.detail-summary :deep(.status-badge) { width: fit-content; }
.detail-summary small { color: var(--workbench-muted); font-size: var(--admin-font-caption, 11px); }
.detail-summary__wide { grid-column: auto; }
.detail-layout :deep(.detail-page-layout__content) { display: grid; min-width: 0; gap: 20px; }
.readiness-note { display: flex; min-width: 0; align-items: center; padding: 2px var(--admin-space-1, 4px); color: var(--workbench-text); font-size: var(--admin-font-caption, 12px); }
.readiness-note strong { flex: 0 0 auto; color: var(--el-color-warning-dark-2); }
.readiness-note span { min-width: 0; color: var(--workbench-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.detail-section, .history-section { min-width: 0; overflow: hidden; padding: var(--admin-space-6, 16px) 0 0; border-top: 1px solid var(--workbench-border); }
.detail-section__heading { display: flex; align-items: center; flex-wrap: wrap; gap: 8px 16px; }
.detail-section__heading h2 { margin: 0; font-size: 15px; }
.detail-section__heading h2 span { margin-left: var(--admin-space-2, 6px); color: var(--workbench-muted); font-size: var(--admin-font-caption, 12px); font-weight: 400; }
.detail-section :deep(.el-table) { width: 100%; }
.detail-section :deep(.el-table th.el-table__cell) { padding: 7px 0; }
.detail-section :deep(.el-table td.el-table__cell) { padding: var(--admin-space-2, 6px) 0; }
.detail-section :deep(.identifier-column .cell) { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.detail-section small { display: block; margin-top: 3px; color: var(--workbench-muted); }
.remark-cell { min-width: 0; line-height: 1.45; overflow-wrap: anywhere; }
.history-section header { margin: 0; }
.history-section.is-expanded header { margin-bottom: 18px; }
.history-section__toggle { display: inline-flex; align-items: center; gap: var(--admin-space-4, 10px); padding: 0; border: 0; background: transparent; color: inherit; cursor: pointer; }
.history-section__toggle:focus-visible { outline: 2px solid var(--el-color-primary); outline-offset: 4px; border-radius: 2px; }
.history-section__arrow { color: var(--workbench-muted); transition: transform 0.2s ease; }
.history-section__arrow.is-expanded { transform: rotate(90deg); }
.history-section h2 { margin: 0; font-size: var(--admin-font-title, 16px); }
.history-section header span { color: var(--workbench-muted); font-size: var(--admin-font-caption, 12px); }
.loading-density { gap: var(--admin-space-5, 12px); }
.loading-density .detail-page-header { gap: var(--admin-space-5, 12px); padding: 0 0 var(--admin-space-3, 8px); }
.loading-density .detail-page-header__identity h1 { font-size: var(--admin-font-title, 16px); overflow-wrap: anywhere; }
.loading-density .detail-page-header__actions { flex-wrap: wrap; }
.loading-density .detail-summary { gap: 0; padding: 0; border-left: 1px solid var(--workbench-border); }
.loading-density .detail-summary > div { gap: 0; align-items: stretch; min-height: var(--admin-row-height, 36px); border-bottom: 1px solid var(--workbench-border); border-right: 1px solid var(--workbench-border); }
.loading-density .detail-summary > div > span:first-child { display: flex; align-items: center; flex: 0 0 96px; padding: var(--admin-space-2, 6px) var(--admin-space-3, 8px); background: var(--admin-label-bg, #f8fafc); }
.loading-density .detail-summary > div > span:first-child::after { content: none; }
.loading-density .detail-summary strong, .loading-density .detail-summary :deep(.status-badge) { align-self: center; margin: var(--admin-space-2, 6px) var(--admin-space-3, 8px); font-weight: 400; }
.loading-density .detail-summary__wide { grid-column: span 2; }
.loading-density .detail-layout :deep(.detail-page-layout__content) { gap: var(--admin-space-5, 12px); }
.loading-density .detail-section, .loading-density .history-section { padding-top: var(--admin-space-3, 8px); }
.loading-density .detail-section__heading h2, .loading-density .history-section h2 { font-size: var(--admin-font-section, 14px); }
.loading-density .history-section.is-expanded header { margin-bottom: var(--admin-space-3, 8px); }
.loading-density small { font-size: var(--admin-font-caption, 12px); }
.loading-density .readiness-note { align-items: baseline; }
.loading-density .readiness-note span { white-space: normal; overflow: visible; overflow-wrap: anywhere; }
@media (max-width: 720px) { .loading-density .detail-summary { grid-template-columns: 1fr; } .loading-density .detail-summary__wide { grid-column: auto; } }
@media print { .loading-detail-content { padding: var(--admin-space-5, 12px); border: 1px solid var(--cc-border); border-radius: var(--admin-radius, 4px); background: var(--admin-surface, #fff); box-shadow: none; } .loading-detail-content :deep(.workbench-table > .data-table) { border-radius: 0; box-shadow: none; } }
</style>