Files
admin-web/src/modules/loading-lists/components/LoadingListDetailDrawer.vue
T

292 lines
21 KiB
Vue
Raw Normal View History

2026-08-10 15:36:53 +08:00
<script setup lang="ts">
2026-09-14 15:33:35 +08:00
import '@/styles/adminDensity.css'
2026-08-10 15:36:53 +08:00
import { computed } from 'vue'
2026-08-28 16:54:02 +08:00
import { ArrowLeft, Check, Pencil } from '@lucide/vue'
2026-08-10 15:36:53 +08:00
import ApiErrorMeta from '@/shared/components/ApiErrorMeta.vue'
import DataTable, { type DataTableColumn } from '@/shared/components/DataTable.vue'
2026-08-10 16:24:54 +08:00
import DetailPageLayout from '@/shared/components/DetailPageLayout.vue'
2026-08-10 15:36:53 +08:00
import LoadErrorState from '@/shared/components/LoadErrorState.vue'
import StatusBadge from '@/shared/components/StatusBadge.vue'
2026-09-09 17:27:00 +08:00
import PalletLabelPrintDialog from './PalletLabelPrintDialog.vue'
2026-08-11 19:35:28 +08:00
import { loadingListStatusLabel } from '../status'
2026-08-10 15:36:53 +08:00
import type { ApiErrorDetails } from '@/shared/api/http'
import type { LoadingDetail, LoadingHistory, LoadingItem, LoadingListMode, LoadingListSummary, PreloadSummary } from '../api/platformLoadingLists'
2026-08-10 15:36:53 +08:00
const props = defineProps<{
mode: LoadingListMode
loading: boolean
detail: LoadingDetail | null
error: ApiErrorDetails | null
canEdit: boolean
embedded?: boolean
canEditTransport?: boolean
transportNote?: string
confirmSaving?: boolean
2026-08-10 15:36:53 +08:00
}>()
2026-08-28 16:54:02 +08:00
const emit = defineEmits<{ back: []; retry: []; edit: []; editWarehouseRemark: [item: LoadingItem]; confirmPreload: [] }>()
2026-08-10 15:36:53 +08:00
2026-08-18 17:40:40 +08:00
const actionColumn: DataTableColumn = { key: 'actions', label: '操作', fixed: 'left', width: 132, slotName: 'actions', hideable: false, movable: false }
2026-08-18 16:39:44 +08:00
const baseItemColumns: DataTableColumn[] = [
2026-08-18 16:09:43 +08:00
{ key: 'sequence', prop: 'loadSequence', label: '装柜顺序', width: 92 },
2026-08-18 16:39:44 +08:00
{ key: 'inboundCode', prop: 'inboundCode', label: '入仓码', minWidth: 170, className: 'identifier-column', showOverflowTooltip: true },
2026-09-08 17:24:49 +08:00
{ key: 'palletNo', prop: 'palletNo', label: '托盘号', minWidth: 155, className: 'identifier-column', showOverflowTooltip: true },
{ key: 'unitType', label: '装柜单位', width: 110, slotName: 'unitType' },
2026-08-18 16:39:44 +08:00
{ key: 'orderNo', prop: 'orderNo', label: '订单号', minWidth: 170, className: 'identifier-column', showOverflowTooltip: true },
{ key: 'containerNo', prop: 'containerNo', label: '集装箱号', minWidth: 165, className: 'identifier-column', showOverflowTooltip: true, formatter: (row: LoadingItem) => row.containerNo || '待补录' },
2026-08-18 16:39:44 +08:00
{ key: 'skuCode', prop: 'skuCode', label: 'SKU', minWidth: 150, className: 'identifier-column', showOverflowTooltip: true },
{ key: 'destinationWarehouse', prop: 'destinationWarehouse', label: '目的仓', minWidth: 150, className: 'identifier-column', showOverflowTooltip: true },
2026-08-17 19:52:04 +08:00
{ key: 'customerName', prop: 'customerName', label: '客户', minWidth: 150 },
2026-09-08 17:38:09 +08:00
{ key: 'taxRefundFlag', label: '是否退税', width: 96, align: 'center', slotName: 'taxRefundFlag' },
2026-08-17 19:52:04 +08:00
{ key: 'chineseName', prop: 'chineseName', label: '中文品名', minWidth: 160 },
{ key: 'englishName', prop: 'englishName', label: '英文品名', minWidth: 170 },
{ key: 'netWeightKg', label: '净重(kg)', width: 110, align: 'right', slotName: 'netWeight' },
2026-08-10 15:36:53 +08:00
{ 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' },
2026-08-17 19:52:04 +08:00
{ 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' },
2026-08-10 15:36:53 +08:00
{ key: 'fbaTrackingNo', prop: 'fbaTrackingNo', label: 'FBA 追踪编码', minWidth: 155 },
{ key: 'shipmentNo', prop: 'shipmentNo', label: '货件号', minWidth: 140 },
{ key: 'shippingMark', prop: 'shippingMark', label: '唛头', minWidth: 130 },
2026-08-18 16:09:43 +08:00
{ 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' },
2026-08-10 15:36:53 +08:00
{ 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)
2026-08-18 16:39:44 +08:00
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 blockingReasonLabels: Record<string, string> = {
LOADING_PLAN_NOT_EDITING: '装柜计划已不在可确认的编辑状态',
PRELOAD_LINK_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(';'))
2026-08-10 15:36:53 +08:00
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: '确认预装',
2026-08-11 11:52:30 +08:00
START_LOADING: '开始拣货', COMPLETE: '完成装柜',
2026-08-12 09:25:55 +08:00
} as Record<string, string>)[action] || '状态更新'
2026-08-10 15:36:53 +08:00
}
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>
2026-09-14 15:33:35 +08:00
<section v-loading="loading" class="loading-detail-content" :class="{ 'admin-density preload-density': mode === 'preload' }" :aria-label="title" :aria-busy="loading">
2026-08-18 17:40:40 +08:00
<section class="detail-overview" aria-label="清单概览">
<header class="detail-page-header" :class="{ 'detail-page-header--embedded': embedded }">
2026-08-18 16:39:44 +08:00
<el-button link type="primary" class="detail-page-header__back" @click="emit('back')"><ArrowLeft :size="16" />返回列表</el-button>
<div v-if="!embedded" class="detail-page-header__identity">
2026-08-18 16:39:44 +08:00
<h1>{{ title }}</h1>
2026-08-28 16:54:02 +08:00
<p>{{ mode === 'preload' ? '核对运输信息、确认预装并维护逐条仓库备注' : '查看已完成装柜明细和柜次费用' }}</p>
2026-08-18 16:39:44 +08:00
</div>
<div v-if="detail && summary" class="detail-page-header__actions">
2026-09-14 15:33:35 +08:00
<PalletLabelPrintDialog :dense="mode === 'preload'" v-if="summary.warehouseId" :plan-id="summary.planId" :warehouse-id="summary.warehouseId" :disabled="!summary.containerNo || summary.status === 'EDITING'" />
2026-08-18 16:39:44 +08:00
<template v-if="canEditItemRemark">
<el-button v-if="canEditTransport" @click="emit('edit')"><Pencil :size="16" />{{ summary.containerNo ? '纠正集装箱号 / 航次' : '补录集装箱号 / 航次' }}</el-button>
2026-08-18 17:40:40 +08:00
<el-button type="primary" :loading="confirmSaving" :disabled="!preloadSummary?.readiness.canConfirm" @click="emit('confirmPreload')"><Check :size="16" />确认预装</el-button>
2026-08-18 16:39:44 +08:00
</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>
2026-08-10 15:36:53 +08:00
<div><span>仓库</span><strong>{{ summary.warehouseName || '—' }}</strong></div>
2026-08-11 19:35:28 +08:00
<div><span>状态</span><StatusBadge :status="loadingListStatusLabel(summary.status)" /></div>
2026-08-10 15:36:53 +08:00
<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>
2026-08-12 10:59:49 +08:00
<div class="detail-summary__wide"><span>客户</span><strong>{{ preloadSummary.customers.map((item) => item.customerName || '客户信息缺失').join('、') || '客户信息缺失' }}</strong></div>
2026-08-10 15:36:53 +08:00
</template>
<div><span>创建时间</span><strong>{{ formatDateTime(summary.createdAt) }}</strong></div>
</section>
2026-08-18 16:39:44 +08:00
</section>
<LoadErrorState v-if="error" title="清单详情加载失败" :message="error.message" @retry="emit('retry')">
<template #details><ApiErrorMeta :details="error" /></template>
</LoadErrorState>
2026-08-10 15:36:53 +08:00
2026-08-18 16:39:44 +08:00
<div v-else-if="detail && summary" class="detail-layout">
<DetailPageLayout :ariaLabel="title">
2026-08-18 17:40:40 +08:00
<div v-if="preloadSummary && preloadSummary.readiness.confirmationStatus !== 'CONFIRMED' && !preloadSummary.readiness.canConfirm" class="readiness-note" role="status">
<strong>暂不能确认预装:</strong>
<span>{{ blockingReasonText || '请刷新详情获取最新确认条件' }}</span>
</div>
<div v-if="preloadSummary" class="readiness-note transport-note" role="status"><strong>真实集装箱号:</strong><span>{{ transportNote }}</span></div>
2026-08-10 15:36:53 +08:00
2026-08-18 17:40:40 +08:00
<section class="detail-section">
2026-08-10 15:36:53 +08:00
<DataTable
:data="detail.items"
:columns="itemColumns"
2026-08-12 18:16:44 +08:00
stripe
2026-08-10 15:36:53 +08:00
preference-key="loading-list-detail-items"
row-key="itemId"
empty-text="暂无装柜货物明细"
aria-label="装柜货物明细"
>
2026-08-18 16:39:44 +08:00
<template #toolbar-start><div class="detail-section__heading"><div><h2>装柜货物明细 <span>{{ detail.items.length }} 条</span></h2><p>仓库备注将随装柜完成资料下发海外仓</p></div></div></template>
2026-08-18 16:09:43 +08:00
<template #actions="{ row }">
2026-08-18 16:39:44 +08:00
<el-button size="small" type="primary" plain :aria-label="`编辑 ${row.inboundCode || row.orderNo} 的仓库备注`" @click="emit('editWarehouseRemark', row as LoadingItem)"><Pencil :size="14" />编辑备注</el-button>
2026-08-18 16:09:43 +08:00
</template>
2026-09-08 17:24:49 +08:00
<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>
2026-08-10 15:36:53 +08:00
<template #grossWeight="{ row }">{{ formatNumber(row.grossWeightKg) }}</template>
<template #netWeight="{ row }">{{ formatNumber(row.netWeightKg) }}</template>
2026-09-08 17:38:09 +08:00
<template #taxRefundFlag="{ row }">{{ row.taxRefundFlag === true ? '是' : row.taxRefundFlag === false ? '否' : '—' }}</template>
2026-08-10 15:36:53 +08:00
<template #volume="{ row }">{{ formatNumber(row.volumeCbm) }}</template>
2026-08-17 19:52:04 +08:00
<template #dimensionalWeight="{ row }">{{ formatNumber(row.dimensionalWeightKg) }}</template>
2026-08-18 16:09:43 +08:00
<template #customerRemark="{ row }"><div class="remark-cell"><span>{{ row.customerRemark || '—' }}</span><small>来源:客户下单</small></div></template>
<template #internalRemark="{ row }"><div class="remark-cell"><span>{{ row.internalRemark || '—' }}</span><small>来源:订单内部备注</small></div></template>
<template #warehouseRemark="{ row }"><div class="remark-cell"><span>{{ row.warehouseRemark || '—' }}</span><small>{{ mode === 'preload' ? '由仓库维护' : '装柜完成时冻结' }}</small></div></template>
2026-08-10 15:36:53 +08:00
<template #frozenAt="{ row }">{{ formatDateTime(row.frozenAt) }}</template>
</DataTable>
</section>
2026-08-18 17:40:40 +08:00
<section class="history-section" aria-label="操作历史">
<header><h2>操作历史</h2><span>{{ visibleHistories.length }} 条</span></header>
2026-09-14 15:33:35 +08:00
<el-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>
2026-08-10 15:36:53 +08:00
</section>
2026-08-10 16:24:54 +08:00
</DetailPageLayout>
2026-08-10 15:36:53 +08:00
</div>
2026-08-12 16:31:46 +08:00
</section>
2026-08-10 15:36:53 +08:00
</template>
<style scoped>
2026-08-18 17:40:40 +08:00
.loading-detail-content { display: grid; min-width: 0; gap: 16px; padding: 16px; border: 1px solid var(--cc-border); border-radius: 8px; background: #fff; }
.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: 16px; padding: 8px 0 12px; }
.detail-page-header--embedded { grid-template-columns: minmax(0, 1fr) auto; padding-top: 0; }
2026-08-18 16:39:44 +08:00
.detail-page-header__back { margin-right: 2px; }
.detail-page-header__identity { min-width: 0; }
.detail-page-header__identity h1 { margin: 0; color: var(--cc-text); font-size: 18px; line-height: 1.35; }
.detail-page-header__identity p { margin: 3px 0 0; color: var(--cc-muted); font-size: 12px; }
.detail-page-header__actions { display: flex; align-items: center; justify-content: flex-end; gap: 8px; }
.detail-page-header__actions :deep(.el-button + .el-button) { margin-left: 0; }
2026-08-18 17:40:40 +08:00
.detail-summary { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 6px 28px; padding: 10px 0 2px; border-top: 1px solid var(--cc-border); }
.detail-summary > div { display: flex; min-width: 0; min-height: 26px; align-items: center; gap: 6px; }
.detail-summary > div > span:first-child { flex: 0 0 auto; color: var(--cc-muted); font-size: 12px; white-space: nowrap; }
.detail-summary > div > span:first-child::after { content: ":"; }
.detail-summary strong { min-width: 0; color: var(--cc-text); font-size: 13px; overflow-wrap: anywhere; }
2026-08-18 16:39:44 +08:00
.detail-summary :deep(.status-badge) { width: fit-content; }
2026-08-10 15:36:53 +08:00
.detail-summary small { color: var(--cc-muted); font-size: 11px; }
2026-08-18 17:40:40 +08:00
.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 4px; color: var(--cc-text); font-size: 12px; }
.readiness-note strong { flex: 0 0 auto; color: var(--el-color-warning-dark-2); }
.readiness-note span { min-width: 0; color: var(--cc-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.detail-section, .history-section { min-width: 0; overflow: hidden; padding: 16px 0 0; border-top: 1px solid var(--cc-border); }
2026-08-18 16:39:44 +08:00
.detail-section__heading h2 { margin: 0; font-size: 15px; }
.detail-section__heading h2 span { margin-left: 6px; color: var(--cc-muted); font-size: 12px; font-weight: 400; }
.detail-section__heading p { margin: 2px 0 0; color: var(--cc-muted); font-size: 11px; line-height: 1.35; }
.detail-section :deep(.data-table__toolbar) { min-height: 52px; padding: 7px 12px; }
2026-08-10 15:36:53 +08:00
.detail-section :deep(.el-table) { width: 100%; }
2026-08-18 16:39:44 +08:00
.detail-section :deep(.el-table th.el-table__cell) { padding: 7px 0; }
.detail-section :deep(.el-table td.el-table__cell) { padding: 6px 0; }
.detail-section :deep(.identifier-column .cell) { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
2026-08-10 15:36:53 +08:00
.detail-section small { display: block; margin-top: 3px; color: var(--cc-muted); }
2026-08-18 16:09:43 +08:00
.remark-cell { min-width: 0; line-height: 1.45; overflow-wrap: anywhere; }
2026-08-10 15:36:53 +08:00
.history-section header { display: flex; align-items: baseline; gap: 10px; margin-bottom: 18px; }
.history-section h2 { margin: 0; font-size: 16px; }
.history-section header span { color: var(--cc-muted); font-size: 12px; }
2026-09-14 15:33:35 +08:00
.preload-density { gap: 12px; padding: 12px; border-radius: 4px; }
.preload-density .detail-page-header { gap: 12px; padding: 0 0 8px; }
.preload-density .detail-page-header__identity h1 { font-size: 16px; overflow-wrap: anywhere; }
.preload-density .detail-page-header__actions { flex-wrap: wrap; }
.preload-density .detail-summary { gap: 0; padding: 0; border-left: 1px solid var(--cc-border); }
.preload-density .detail-summary > div { gap: 0; align-items: stretch; min-height: 36px; border-bottom: 1px solid var(--cc-border); border-right: 1px solid var(--cc-border); }
.preload-density .detail-summary > div > span:first-child { display: flex; align-items: center; flex: 0 0 96px; padding: 6px 8px; background: #f8fafc; }
.preload-density .detail-summary > div > span:first-child::after { content: none; }
.preload-density .detail-summary strong, .preload-density .detail-summary :deep(.status-badge) { align-self: center; margin: 6px 8px; font-weight: 400; }
.preload-density .detail-summary__wide { grid-column: span 2; }
.preload-density .detail-layout :deep(.detail-page-layout__content) { gap: 12px; }
.preload-density .detail-section, .preload-density .history-section { padding-top: 8px; }
.preload-density .detail-section__heading h2 { font-size: 14px; }
.preload-density .detail-section__heading p, .preload-density small { font-size: 12px; }
.preload-density .readiness-note { align-items: baseline; }
.preload-density .readiness-note span { white-space: normal; overflow: visible; overflow-wrap: anywhere; }
@media (max-width: 720px) { .preload-density .detail-summary { grid-template-columns: 1fr; } .preload-density .detail-summary__wide { grid-column: auto; } }
2026-08-10 15:36:53 +08:00
</style>