修复采购审核竞态并整理管理页面展示

This commit is contained in:
2026-10-30 15:42:37 +08:00
parent 8cc8b4fb94
commit 4abd6ffd5d
23 changed files with 791 additions and 75 deletions
@@ -2,8 +2,11 @@ import { flushPromises, mount, shallowMount } from '@vue/test-utils'
import { afterEach, describe, expect, it, vi } from 'vitest'
import ElementPlus from 'element-plus'
import LoadingListDetailDrawer from './LoadingListDetailDrawer.vue'
import { printLoadingList } from '../printLoadingList'
import type { LoadingDetail } from '../api/platformLoadingLists'
vi.mock('../printLoadingList', () => ({ printLoadingList: vi.fn(async () => true) }))
afterEach(() => vi.restoreAllMocks())
describe('shared manifest presentation', () => {
@@ -33,7 +36,7 @@ describe('shared manifest presentation', () => {
})
it.each([undefined, '返回货柜概览'])('keeps print in the common header and emits return for %s', async (backLabel) => {
const print = vi.spyOn(window, 'print').mockImplementation(() => {})
const print = vi.mocked(printLoadingList)
const wrapper = shallowMount(LoadingListDetailDrawer, {
props: { mode: 'confirmed', dense: true, backLabel, canEdit: false, loading: false, error: null,
detail: { summary: { planId: '1', warehouseId: '11', loadingListNo: 'LP-TEST', status: 'COMPLETED' }, items: [], histories: [] } as unknown as LoadingDetail },
@@ -44,7 +47,7 @@ describe('shared manifest presentation', () => {
const button = wrapper.get('.detail-page-header__actions button')
expect(button.text()).toBe('打印装柜清单')
await button.trigger('click'); await flushPromises()
expect(print).toHaveBeenCalledTimes(1)
expect(print).toHaveBeenCalledExactlyOnceWith(wrapper.element)
expect(wrapper.get('.detail-page-header__back').text()).toBe(backLabel || '返回列表')
await wrapper.get('.detail-page-header__back').trigger('click')
expect(wrapper.emitted('back')).toHaveLength(1)
@@ -2,6 +2,7 @@
import '@/styles/adminDensity.css'
import '@/styles/adminWorkbench.css'
import { computed, ref, watch } from 'vue'
import { ElMessage } from 'element-plus'
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'
@@ -30,6 +31,15 @@ const props = defineProps<{
}>()
const emit = defineEmits<{ back: []; retry: []; edit: []; editRemarks: [item: LoadingItem]; confirmPreload: [] }>()
const printRoot = ref<HTMLElement>()
async function printDetail() {
if (props.mode !== 'confirmed' || props.loading || props.error || !props.detail) return false
try { return await printLoadingList(printRoot.value) }
catch (cause) { ElMessage.error(cause instanceof Error ? cause.message : '装柜清单打印失败,请重试'); return false }
}
defineExpose({ print: printDetail })
const actionColumn: DataTableColumn = { key: 'actions', label: '操作', fixed: 'left', width: 132, slotName: 'actions', hideable: false, movable: false }
const baseItemColumns: DataTableColumn[] = [
@@ -169,7 +179,7 @@ function formatNumber(value?: number | null, digits = 3) {
</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 ref="printRoot" 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>
@@ -177,7 +187,7 @@ function formatNumber(value?: number | null, digits = 3) {
<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>
<el-button v-if="mode === 'confirmed'" :disabled="loading || Boolean(error)" @click="printDetail"><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>
@@ -185,7 +195,7 @@ function formatNumber(value?: number | null, digits = 3) {
</template>
</div>
</header>
<section v-if="detail && summary" class="detail-summary" aria-label="清单基本信息">
<section v-if="detail && summary" class="detail-summary" aria-label="清单基本信息" data-loading-list-print-summary>
<div><span>柜次号</span><strong>{{ summary.cabinetNo || '—' }}</strong></div>
<div><span>集装箱号</span><strong>{{ summary.containerNo || '待补录' }}</strong></div>
<div><span>仓库</span><strong>{{ summary.warehouseName || '—' }}</strong></div>
@@ -213,7 +223,7 @@ function formatNumber(value?: number | null, digits = 3) {
</div>
<section class="detail-section">
<div class="workbench-table"><DataTable
<div class="workbench-table" data-loading-list-print-items><DataTable
:data="detail.items"
:columns="itemColumns"
stripe