This commit is contained in:
2026-09-09 14:32:04 +08:00
parent f62a3175f7
commit a2515119b7
8 changed files with 166 additions and 33 deletions
@@ -107,7 +107,7 @@ const canSave = computed(() => editable.value && access.can(isNew.value ? 'platf
const formWritable = computed(() => canSave.value)
const canDelete = computed(() => detail.value?.status === 'DRAFT' && access.can('platform:firstleg:ocean-booking:delete'))
const canRelease = computed(() => detail.value?.status === 'DRAFT' && access.can('platform:firstleg:ocean-booking:release'))
const canPrint = computed(() => detail.value?.status === 'RELEASED' && access.can('platform:firstleg:ocean-booking:list'))
const canPrint = computed(() => !!detail.value && access.can('platform:firstleg:ocean-booking:list'))
const canManageDocuments = computed(() => detail.value?.status === 'DRAFT' && access.can('platform:firstleg:ocean-booking:edit'))
const documentManageReason = computed(() => !access.can('platform:firstleg:ocean-booking:edit') ? '暂无订舱资料维护权限' : '')
const workflowBusy = computed(() => saving.value || actionSaving.value)
@@ -385,7 +385,7 @@ onMounted(() => { void load(); void loadOptions() })
<div class="section-title">
<div><h2>订舱信息</h2><p v-if="detail?.status === 'DRAFT'">草稿可反复保存;放舱后资料同步至关联货柜并锁定。</p><p v-else-if="detail">已放舱资料只读。</p></div>
<div class="actions">
<el-tooltip :content="canPrint ? '选择出口货物托运单或提单确认件' : detail?.status === 'DRAFT' ? '订舱放舱后方可打印' : '暂无订舱单据打印权限'" placement="top"><span><el-button v-if="detail" plain :disabled="!canPrint || workflowBusy" @click="printOpen = true"><Printer :size="16" />打印单据</el-button></span></el-tooltip>
<el-tooltip :content="canPrint ? '按当前已保存资料打印出口货物托运单或提单确认件' : '暂无订舱单据打印权限'" placement="top"><span><el-button v-if="detail" plain :disabled="!canPrint || workflowBusy" @click="printOpen = true"><Printer :size="16" />打印单据</el-button></span></el-tooltip>
<el-button v-if="canDelete" type="danger" plain :loading="actionSaving" :disabled="workflowBusy" @click="removeDraft"><Trash2 :size="16" />删除草稿</el-button>
<el-button v-if="canRelease" type="primary" plain :loading="actionSaving" :disabled="workflowBusy" @click="openReleaseDialog">放舱</el-button>
<el-button v-if="canSave" type="primary" :loading="saving" :disabled="workflowBusy" @click="save"><Save :size="16" />保存草稿</el-button>
@@ -40,6 +40,21 @@ describe('OceanBookingsView', () => {
expect(source).toContain("access.can('platform:firstleg:ocean-booking:list')")
})
it.each(['DRAFT', 'RELEASED'] as const)('opens printing for %s with incomplete booking data', async (status) => {
vi.mocked(listOceanBookings).mockResolvedValueOnce({ records: [{
id: '183', bookingNo: 'OB-183', status, containerCount: 1,
billOfLadingNo: null, shipperName: null, consigneeName: null,
}], page: 1, size: 20, total: 1, pages: 1 } as never)
const wrapper = mount(OceanBookingsView, { global: { plugins: [ElementPlus], stubs: { OceanBookingPrintDialog: true } } })
await (wrapper.vm as unknown as { load: () => Promise<void> }).load()
await flushPromises()
const button = wrapper.findAll('button').find((item) => item.text().includes('打印单据'))!
expect(button.attributes('disabled')).toBeUndefined()
await button.trigger('click')
expect(wrapper.findComponent({ name: 'OceanBookingPrintDialog' }).attributes('modelvalue')).toBe('true')
wrapper.unmount()
})
it('uses only the frozen DRAFT and RELEASED status options', () => {
expect(source).toContain('options: oceanBookingStatusOptions')
expect(source).toContain(':status="oceanBookingStatusLabel(row.status)"')
@@ -62,7 +62,7 @@ async function load() { loading.value = true; error.value = null; try { const ke
function open(id: string) { void router.push(`/ocean-bookings/${id}`) }
function createBooking() { void router.push('/ocean-bookings/new') }
function openPrint(row: Record<string, unknown>) { printBooking.value = { id: String(row.id), bookingNo: typeof row.bookingNo === 'string' ? row.bookingNo : null }; printOpen.value = true }
function printHint(row: Record<string, unknown>) { return !canPrint.value ? '暂无订舱单据打印权限' : row.status !== 'RELEASED' ? '订舱放舱后方可打印' : '选择出口货物托运单或提单确认件' }
function printHint() { return !canPrint.value ? '暂无订舱单据打印权限' : '按当前已保存资料打印出口货物托运单或提单确认件' }
function search() { if (page.value === 1) void load(); else page.value = 1 }
watch([page, pageSize], () => void load()); onMounted(() => void loadCustomers())
onActivated(() => void load())
@@ -75,7 +75,7 @@ onActivated(() => void load())
<section v-if="!error" class="surface-panel table-panel">
<DataTable v-model:page="page" v-model:page-size="pageSize" :data="rows" :columns="columns" :loading="loading" :total="total" stripe preference-key="ocean-bookings" aria-label="订舱列表" row-key="id" empty-text="暂无符合筛选条件的订舱">
<template #toolbar-actions><el-button v-if="canCreate" type="primary" @click="createBooking"><Plus :size="16" />新增订舱</el-button></template>
<template #actions="{ row }"><div class="table-actions"><el-button size="small" type="primary" plain @click="open(row.id)"><Pencil v-if="row.status === 'DRAFT'" :size="15" /><Eye v-else :size="15" />{{ row.status === 'DRAFT' ? '编辑' : '查看' }}</el-button><el-tooltip :content="printHint(row)" placement="top"><span><el-button size="small" plain :disabled="!canPrint || row.status !== 'RELEASED'" @click="openPrint(row)"><Printer :size="15" />打印单据</el-button></span></el-tooltip></div></template>
<template #actions="{ row }"><div class="table-actions"><el-button size="small" type="primary" plain @click="open(row.id)"><Pencil v-if="row.status === 'DRAFT'" :size="15" /><Eye v-else :size="15" />{{ row.status === 'DRAFT' ? '编辑' : '查看' }}</el-button><el-tooltip :content="printHint()" placement="top"><span><el-button size="small" plain :disabled="!canPrint" @click="openPrint(row)"><Printer :size="15" />打印单据</el-button></span></el-tooltip></div></template>
<template #vessel="{ row }">{{ row.vesselName || '—' }}<span v-if="row.voyageNo"> / {{ row.voyageNo }}</span></template>
<template #etd="{ row }">{{ formatDateTime(row.etd) }}</template>
<template #eta="{ row }">{{ formatDateTime(row.eta) }}</template>