优化
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import '@/styles/adminDensity.css'
|
||||
import { computed } from 'vue'
|
||||
import { ArrowLeft, Check, Pencil } from '@lucide/vue'
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import { ArrowLeft, Check, ChevronRight, Pencil } 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'
|
||||
@@ -18,10 +18,11 @@ const props = defineProps<{
|
||||
detail: LoadingDetail | null
|
||||
error: ApiErrorDetails | null
|
||||
canEdit: boolean
|
||||
canPushPda?: boolean
|
||||
pdaAssigneeSelected?: boolean
|
||||
dense?: boolean
|
||||
embedded?: boolean
|
||||
canEditTransport?: boolean
|
||||
transportNote?: string
|
||||
confirmSaving?: boolean
|
||||
}>()
|
||||
|
||||
@@ -101,6 +102,13 @@ const visibleHistories = computed(() => {
|
||||
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: '装柜明细尚未完整关联入库预装票据',
|
||||
@@ -156,13 +164,13 @@ function formatNumber(value?: number | null, digits = 3) {
|
||||
<el-button link type="primary" class="detail-page-header__back" :class="{ 'density-back': mode === 'preload' || dense }" @click="emit('back')"><ArrowLeft :size="16" />返回列表</el-button>
|
||||
<div v-if="!embedded" class="detail-page-header__identity">
|
||||
<h1>{{ title }}</h1>
|
||||
<p>{{ mode === 'preload' ? '核对运输信息、确认预装并维护逐条仓库备注' : '查看已完成装柜明细和柜次费用' }}</p>
|
||||
<p v-if="mode !== 'preload'">查看已完成装柜明细和柜次费用</p>
|
||||
</div>
|
||||
<div v-if="detail && summary" class="detail-page-header__actions">
|
||||
<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 type="primary" :loading="confirmSaving" :disabled="!preloadSummary?.readiness.canConfirm" @click="emit('confirmPreload')"><Check :size="16" />确认预装</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>
|
||||
@@ -192,7 +200,6 @@ function formatNumber(value?: number | null, digits = 3) {
|
||||
<strong>暂不能确认预装:</strong>
|
||||
<span>{{ blockingReasonText || '请刷新详情获取最新确认条件' }}</span>
|
||||
</div>
|
||||
<div v-if="preloadSummary" class="readiness-note transport-note" role="status"><strong>真实集装箱号:</strong><span>{{ transportNote }}</span></div>
|
||||
|
||||
<section class="detail-section">
|
||||
<DataTable
|
||||
@@ -204,7 +211,7 @@ function formatNumber(value?: number | null, digits = 3) {
|
||||
empty-text="暂无装柜货物明细"
|
||||
aria-label="装柜货物明细"
|
||||
>
|
||||
<template #toolbar-start><div class="detail-section__heading"><div><h2>装柜货物明细 <span>{{ detail.items.length }} 条</span></h2><p>仓库备注将随装柜完成资料下发海外仓</p></div></div></template>
|
||||
<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>
|
||||
@@ -222,9 +229,9 @@ function formatNumber(value?: number | null, digits = 3) {
|
||||
</DataTable>
|
||||
</section>
|
||||
|
||||
<section class="history-section" aria-label="操作历史">
|
||||
<header><h2>操作历史</h2><span>{{ visibleHistories.length }} 条</span></header>
|
||||
<el-table :data="visibleHistories" border stripe empty-text="暂无操作历史" aria-label="清单操作历史">
|
||||
<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>
|
||||
@@ -262,16 +269,21 @@ function formatNumber(value?: number | null, digits = 3) {
|
||||
.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: var(--admin-space-6, 16px) 0 0; border-top: 1px solid var(--cc-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(--cc-muted); font-size: var(--admin-font-caption, 12px); font-weight: 400; }
|
||||
.detail-section__heading p { margin: 2px 0 0; color: var(--cc-muted); font-size: var(--admin-font-caption, 11px); line-height: 1.35; }
|
||||
.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(--cc-muted); }
|
||||
.remark-cell { min-width: 0; line-height: 1.45; overflow-wrap: anywhere; }
|
||||
.history-section header { display: flex; align-items: baseline; gap: var(--admin-space-4, 10px); margin-bottom: 18px; }
|
||||
.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(--cc-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(--cc-muted); font-size: var(--admin-font-caption, 12px); }
|
||||
.loading-density { gap: var(--admin-space-5, 12px); padding: var(--admin-space-5, 12px); border-radius: var(--admin-radius, 4px); }
|
||||
@@ -287,8 +299,8 @@ function formatNumber(value?: number | null, digits = 3) {
|
||||
.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 header { margin-bottom: var(--admin-space-3, 8px); }
|
||||
.loading-density .detail-section__heading p, .loading-density small { font-size: var(--admin-font-caption, 12px); }
|
||||
.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; } }
|
||||
|
||||
@@ -116,11 +116,11 @@ describe('LoadingListsView', () => {
|
||||
expect(confirmedDetailSource).toContain('MODE_STATUS_MISMATCH')
|
||||
expect(confirmedDetailSource).toContain('watch([planId, warehouseId], () => void loadDetail())')
|
||||
expect(confirmedDetailSource).toContain('if (sequence !== loadSequence) return')
|
||||
expect(preloadDetailSource).toContain('watch([planId, warehouseId], () => void loadDetail())')
|
||||
expect(preloadDetailSource).toContain('watch([planId, warehouseId], () => {')
|
||||
})
|
||||
|
||||
it('uses backend readiness for whole-plan confirmation and translates blocking reasons', () => {
|
||||
expect(detailSource).toContain(':disabled="!preloadSummary?.readiness.canConfirm"')
|
||||
expect(detailSource).toContain(':disabled="!preloadSummary?.readiness.canConfirm || !pdaAssigneeSelected"')
|
||||
expect(detailSource).toContain("OCEAN_BOOKING_NOT_RELEASED: '关联订舱尚未放舱'")
|
||||
expect(detailSource).toContain("PRELOAD_ALREADY_CONFIRMED: '整份预装计划已经确认'")
|
||||
expect(detailSource).toContain("confirmationStatus !== 'CONFIRMED' && !preloadSummary.readiness.canConfirm")
|
||||
@@ -185,7 +185,7 @@ describe('LoadingListsView', () => {
|
||||
it('keeps customer remarks read-only while exposing three editable collaboration remarks', () => {
|
||||
expect(detailSource).toContain('来源:客户下单')
|
||||
expect(detailSource).toContain('来源:订单内部备注')
|
||||
expect(detailSource).toContain('仓库备注将随装柜完成资料下发海外仓')
|
||||
expect(detailSource).not.toContain('仓库备注将随装柜完成资料下发海外仓')
|
||||
expect(detailSource).toContain("emit('editRemarks', row as LoadingItem)")
|
||||
expect(detailSource).toContain("props.mode === 'preload' && props.canEdit && ['EDITING', 'LOADING'].includes(summary.value?.status || '')")
|
||||
expect(detailSource).not.toContain("emit('editCustomerRemark'")
|
||||
|
||||
Reference in New Issue
Block a user