优化
This commit is contained in:
@@ -14,6 +14,16 @@ test('renders loading, pending, isolated error, and published states', () => {
|
||||
assert.match(source, /\$emit\('retry'\)/)
|
||||
})
|
||||
|
||||
test('supports a standalone dialog and keeps the unpushed billing-weight state visible', () => {
|
||||
assert.match(source, /dialogOnly\?: boolean/)
|
||||
assert.match(source, /v-if="!props\.dialogOnly"/)
|
||||
assert.match(source, /watch\(\(\) => props\.autoOpen/)
|
||||
assert.match(source, /if \(autoOpen\) feeDialogOpen\.value = true/)
|
||||
assert.match(source, /v-else-if="props\.unavailable \|\| !props\.data"/)
|
||||
assert.match(source, /平台推送计费重后,可在这里核对并确认/)
|
||||
assert.match(source, /@closed="emit\('closed'\)"/)
|
||||
})
|
||||
|
||||
test('shows the published merchant inbound, measurement details and customer confirmation entry', () => {
|
||||
for (const label of ['入仓码', '平台收货仓', '实际毛重', '实测体积', '托盘与库位', '换算与精度', '最终计费量', '基础费用', '附加费用', '费用合计', '平台备注']) {
|
||||
assert.match(source, new RegExp(label))
|
||||
|
||||
@@ -21,6 +21,7 @@ const props = withDefaults(defineProps<{
|
||||
unavailableMessage?: string
|
||||
error?: string
|
||||
autoOpen?: boolean
|
||||
dialogOnly?: boolean
|
||||
canConfirm?: boolean
|
||||
}>(), {
|
||||
data: null,
|
||||
@@ -29,18 +30,20 @@ const props = withDefaults(defineProps<{
|
||||
unavailableMessage: '还未测量推送',
|
||||
error: '',
|
||||
autoOpen: false,
|
||||
dialogOnly: false,
|
||||
canConfirm: false,
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
retry: []
|
||||
confirmed: []
|
||||
closed: []
|
||||
}>()
|
||||
|
||||
const feeDialogOpen = ref(false)
|
||||
const confirming = ref(false)
|
||||
watch(() => [props.autoOpen, props.data] as const, ([autoOpen, data]) => {
|
||||
if (autoOpen && data) feeDialogOpen.value = true
|
||||
watch(() => props.autoOpen, (autoOpen) => {
|
||||
if (autoOpen) feeDialogOpen.value = true
|
||||
}, { immediate: true })
|
||||
|
||||
async function confirmFee() {
|
||||
@@ -75,7 +78,7 @@ async function confirmFee() {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="inbound-billing-panel" aria-labelledby="inbound-billing-title">
|
||||
<section v-if="!props.dialogOnly" class="inbound-billing-panel" aria-labelledby="inbound-billing-title">
|
||||
<header class="inbound-billing-heading">
|
||||
<h3 id="inbound-billing-title">入库与费用</h3>
|
||||
<div v-if="props.data" class="inbound-billing-actions">
|
||||
@@ -200,8 +203,24 @@ async function confirmFee() {
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<el-dialog v-model="feeDialogOpen" title="确认测量与计费数据" width="min(980px, 94vw)" append-to-body>
|
||||
<dl v-if="props.data" class="inbound-measurements fee-dialog-summary">
|
||||
</section>
|
||||
|
||||
<el-dialog v-model="feeDialogOpen" title="确认测量与计费数据" width="min(980px, 94vw)" append-to-body @closed="emit('closed')">
|
||||
<div v-if="props.loading" class="inbound-billing-loading" aria-live="polite" aria-busy="true">
|
||||
<el-skeleton :rows="4" animated />
|
||||
<span class="sr-only">正在加载计费重</span>
|
||||
</div>
|
||||
<div v-else-if="props.error" class="inbound-billing-state is-error" role="alert">
|
||||
<TriangleAlert :size="22" />
|
||||
<div><strong>计费重加载失败</strong><span>{{ props.error }}</span></div>
|
||||
<el-button @click="$emit('retry')"><RefreshCw :size="15" />重新加载</el-button>
|
||||
</div>
|
||||
<div v-else-if="props.unavailable || !props.data" class="inbound-billing-state is-pending" role="status">
|
||||
<Clock3 :size="22" />
|
||||
<div><strong>{{ props.unavailableMessage }}</strong><span>平台推送计费重后,可在这里核对并确认。</span></div>
|
||||
</div>
|
||||
<template v-else>
|
||||
<dl class="inbound-measurements fee-dialog-summary">
|
||||
<div><dt>计费总箱数</dt><dd>{{ formatInboundNumber(props.data.measurementSummary.actualPackageCount, 0) }}</dd></div>
|
||||
<div><dt>计费总毛重</dt><dd>{{ formatInboundMeasure(props.data.measurementSummary.actualGrossWeightKg, 'kg') }}</dd></div>
|
||||
<div><dt>外箱规总体积</dt><dd>{{ formatInboundMeasure(props.data.measurementSummary.actualVolumeCbm, 'm³') }}</dd></div>
|
||||
@@ -222,7 +241,7 @@ async function confirmFee() {
|
||||
<el-table-column prop="boxSpecification" label="备注" min-width="130" />
|
||||
</el-table>
|
||||
<el-alert
|
||||
v-if="props.data?.latestFee.customerConfirmationStatus === 'CONFIRMED'"
|
||||
v-if="props.data.latestFee.customerConfirmationStatus === 'CONFIRMED'"
|
||||
class="fee-confirmed-alert"
|
||||
:title="`客户已于 ${formatInboundDateTime(props.data.latestFee.customerConfirmedAt)} 确认,费用已锁定,不允许修改。`"
|
||||
type="success"
|
||||
@@ -230,12 +249,12 @@ async function confirmFee() {
|
||||
show-icon
|
||||
/>
|
||||
<el-alert v-else class="fee-confirmed-alert" title="请核对全部计费数据。点击确认后还会进行一次确认,完成后费用将锁定。" type="warning" :closable="false" show-icon />
|
||||
</template>
|
||||
<template #footer>
|
||||
<el-button @click="feeDialogOpen = false">关闭</el-button>
|
||||
<el-button v-if="props.data?.latestFee.customerConfirmationStatus !== 'CONFIRMED' && props.canConfirm" type="primary" :loading="confirming" @click="confirmFee">确认费用</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</section>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -502,11 +502,21 @@ test('keeps platform enterprise scope and tenant terminology out of the customer
|
||||
assert.doesNotMatch(templateSource, /租户|tenantId|所属企业|企业筛选/)
|
||||
})
|
||||
|
||||
test('discards stale inbound billing responses when the drawer closes or the user changes orders', () => {
|
||||
test('opens billing-weight confirmation independently from order details', () => {
|
||||
const handler = source.slice(source.indexOf('function openFeeConfirmation'), source.indexOf('async function retryDetail'))
|
||||
assert.match(handler, /feeConfirmationOpen\.value = true/)
|
||||
assert.match(handler, /loadInboundBilling\(order, '未推送计费重'\)/)
|
||||
assert.doesNotMatch(handler, /openDetail\(/)
|
||||
assert.match(source, /v-if="feeConfirmationOpen"[\s\S]*dialog-only[\s\S]*auto-open/)
|
||||
assert.match(source, /unavailable-message="未推送计费重"/)
|
||||
})
|
||||
|
||||
test('discards stale inbound billing responses when billing views close or the user changes orders', () => {
|
||||
assert.match(source, /let inboundBillingRequestSequence = 0/)
|
||||
assert.match(source, /const sequence = \+\+inboundBillingRequestSequence/)
|
||||
assert.match(source, /sequence === inboundBillingRequestSequence[\s\S]*detailOpen\.value[\s\S]*detailId\.value === orderId/)
|
||||
assert.match(source, /sequence === inboundBillingRequestSequence[\s\S]*inboundBillingOrderId\.value === orderId[\s\S]*detailOpen\.value \|\| feeConfirmationOpen\.value/)
|
||||
assert.match(source, /watch\(detailOpen,[\s\S]*resetInboundBillingState\(\)/)
|
||||
assert.match(source, /watch\(feeConfirmationOpen,[\s\S]*resetInboundBillingState\(\)/)
|
||||
})
|
||||
|
||||
test('protects unsaved drafts and exposes recoverable load failures', () => {
|
||||
|
||||
@@ -200,8 +200,9 @@ const inboundBilling = ref<MerchantInboundBilling | null>(null)
|
||||
const inboundBillingLoading = ref(false)
|
||||
const inboundBillingUnavailable = ref(false)
|
||||
const inboundBillingUnavailableMessage = ref('还未测量推送')
|
||||
const openFeeDialogWithDetail = ref(false)
|
||||
const inboundBillingError = ref('')
|
||||
const inboundBillingOrderId = ref('')
|
||||
const feeConfirmationOpen = ref(false)
|
||||
const customsByOrder = reactive<Record<string, MerchantCustomsContainer | null | undefined>>({})
|
||||
const customsEligibilityPending = new Set<string>()
|
||||
const customsDialogOpen = ref(false)
|
||||
@@ -649,12 +650,16 @@ watch(isCreatePage, (isCreate, wasCreate) => {
|
||||
}, { immediate: true })
|
||||
|
||||
watch(detailOpen, (open) => {
|
||||
if (!open) {
|
||||
if (!open && !feeConfirmationOpen.value) {
|
||||
resetInboundBillingState()
|
||||
if (detailOrder.value) releaseProductImagePreviews(detailOrder.value.items)
|
||||
}
|
||||
})
|
||||
|
||||
watch(feeConfirmationOpen, (open) => {
|
||||
if (!open && !detailOpen.value) resetInboundBillingState()
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
resetInboundBillingState()
|
||||
channelRequestSequence += 1
|
||||
@@ -1194,8 +1199,8 @@ async function openEdit(order: FirstLegOrder) {
|
||||
}
|
||||
}
|
||||
|
||||
async function openDetail(order: FirstLegOrder, showFeeDialog = false) {
|
||||
openFeeDialogWithDetail.value = showFeeDialog
|
||||
async function openDetail(order: FirstLegOrder) {
|
||||
feeConfirmationOpen.value = false
|
||||
detailId.value = order.id
|
||||
detailCargoPage.value = 1
|
||||
detailOpen.value = true
|
||||
@@ -1215,7 +1220,11 @@ async function openDetail(order: FirstLegOrder, showFeeDialog = false) {
|
||||
}
|
||||
|
||||
function openFeeConfirmation(order: FirstLegOrder) {
|
||||
void openDetail(order, true)
|
||||
detailOpen.value = false
|
||||
resetInboundBillingState()
|
||||
inboundBillingUnavailableMessage.value = '未推送计费重'
|
||||
feeConfirmationOpen.value = true
|
||||
void loadInboundBilling(order, '未推送计费重')
|
||||
}
|
||||
|
||||
async function retryDetail() {
|
||||
@@ -1225,6 +1234,7 @@ async function retryDetail() {
|
||||
|
||||
function resetInboundBillingState() {
|
||||
inboundBillingRequestSequence += 1
|
||||
inboundBillingOrderId.value = ''
|
||||
inboundBilling.value = null
|
||||
inboundBillingLoading.value = false
|
||||
inboundBillingUnavailable.value = false
|
||||
@@ -1234,16 +1244,17 @@ function resetInboundBillingState() {
|
||||
|
||||
function isCurrentInboundBillingRequest(sequence: number, orderId: string) {
|
||||
return sequence === inboundBillingRequestSequence
|
||||
&& detailOpen.value
|
||||
&& detailId.value === orderId
|
||||
&& inboundBillingOrderId.value === orderId
|
||||
&& (detailOpen.value || feeConfirmationOpen.value)
|
||||
}
|
||||
|
||||
async function loadInboundBilling(order: FirstLegOrder) {
|
||||
async function loadInboundBilling(order: FirstLegOrder, unavailableMessage = '还未测量推送') {
|
||||
const sequence = ++inboundBillingRequestSequence
|
||||
inboundBillingOrderId.value = order.id
|
||||
inboundBilling.value = null
|
||||
inboundBillingLoading.value = false
|
||||
inboundBillingUnavailable.value = false
|
||||
inboundBillingUnavailableMessage.value = '还未测量推送'
|
||||
inboundBillingUnavailableMessage.value = unavailableMessage
|
||||
inboundBillingError.value = ''
|
||||
|
||||
if (order.status === 'DRAFT') {
|
||||
@@ -1261,7 +1272,9 @@ async function loadInboundBilling(order: FirstLegOrder) {
|
||||
if (!isCurrentInboundBillingRequest(sequence, order.id)) return
|
||||
if (error instanceof ApiError && error.code === 'A0404') {
|
||||
inboundBillingUnavailable.value = true
|
||||
inboundBillingUnavailableMessage.value = error.message || '还未测量推送'
|
||||
inboundBillingUnavailableMessage.value = feeConfirmationOpen.value
|
||||
? '未推送计费重'
|
||||
: error.message || unavailableMessage
|
||||
} else {
|
||||
inboundBillingError.value = error instanceof Error ? error.message : '入库与费用加载失败'
|
||||
}
|
||||
@@ -1271,8 +1284,12 @@ async function loadInboundBilling(order: FirstLegOrder) {
|
||||
}
|
||||
|
||||
async function retryInboundBilling() {
|
||||
const order = detailId.value ? store.getById(detailId.value) : undefined
|
||||
if (order) await loadInboundBilling(order)
|
||||
const order = inboundBillingOrderId.value ? store.getById(inboundBillingOrderId.value) : undefined
|
||||
if (order) await loadInboundBilling(order, feeConfirmationOpen.value ? '未推送计费重' : '还未测量推送')
|
||||
}
|
||||
|
||||
function handleInboundFeeConfirmed() {
|
||||
if (!feeConfirmationOpen.value) void retryInboundBilling()
|
||||
}
|
||||
|
||||
async function confirmDiscardChanges() {
|
||||
@@ -2154,19 +2171,32 @@ async function searchOrders(model: QueryModel) {
|
||||
<OrderProgressPanel :order="detailOrder" />
|
||||
<InboundBillingPanel
|
||||
:data="inboundBilling"
|
||||
:auto-open="openFeeDialogWithDetail"
|
||||
:loading="inboundBillingLoading"
|
||||
:unavailable="inboundBillingUnavailable"
|
||||
:unavailable-message="inboundBillingUnavailableMessage"
|
||||
:error="inboundBillingError"
|
||||
:can-confirm="canConfirmInboundFee"
|
||||
@retry="retryInboundBilling"
|
||||
@confirmed="retryInboundBilling"
|
||||
@confirmed="handleInboundFeeConfirmed"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
<template #footer><div v-if="detailOrder" class="editor-actions"><el-button @click="detailOpen = false">关闭</el-button><el-button v-if="canEditOrder && canClientEditFirstLegOrder(detailOrder.status)" :loading="openingEditor" @click="openEdit(detailOrder)"><Pencil :size="16" />编辑草稿</el-button></div></template>
|
||||
</el-dialog>
|
||||
<InboundBillingPanel
|
||||
v-if="feeConfirmationOpen"
|
||||
dialog-only
|
||||
auto-open
|
||||
:data="inboundBilling"
|
||||
:loading="inboundBillingLoading"
|
||||
:unavailable="inboundBillingUnavailable"
|
||||
unavailable-message="未推送计费重"
|
||||
:error="inboundBillingError"
|
||||
:can-confirm="canConfirmInboundFee"
|
||||
@retry="retryInboundBilling"
|
||||
@confirmed="handleInboundFeeConfirmed"
|
||||
@closed="feeConfirmationOpen = false"
|
||||
/>
|
||||
<CustomsDocumentsDialog
|
||||
v-model="customsDialogOpen"
|
||||
:order-id="customsDialogOrderId"
|
||||
|
||||
Reference in New Issue
Block a user