优化
This commit is contained in:
@@ -181,6 +181,30 @@ test('shows fulfillment courier information as read-only client detail', () => {
|
||||
assert.doesNotMatch(source, /v-model="form\.trackingNumber"/)
|
||||
})
|
||||
|
||||
test('loads merchant inbound billing as an isolated read-only detail section', () => {
|
||||
assert.match(source, /import InboundBillingPanel/)
|
||||
assert.match(source, /import \{ getMerchantInboundBilling \}/)
|
||||
assert.match(source, /void loadInboundBilling\(order\)/)
|
||||
assert.match(source, /<InboundBillingPanel/)
|
||||
assert.match(source, /@retry="retryInboundBilling"/)
|
||||
assert.match(source, /error instanceof ApiError && error\.code === 'A0404'/)
|
||||
assert.match(source, /order\.status === 'DRAFT'/)
|
||||
assert.match(source, /订单提交后可查看入库结果/)
|
||||
assert.doesNotMatch(source, /confirmMerchantInbound|adjustMerchantInbound|sendMerchantInbound/)
|
||||
})
|
||||
|
||||
test('keeps platform enterprise scope and tenant terminology out of the customer order UI', () => {
|
||||
const templateSource = source.slice(source.indexOf('<template>'), source.lastIndexOf('</template>'))
|
||||
assert.doesNotMatch(templateSource, /租户|tenantId|所属企业|企业筛选/)
|
||||
})
|
||||
|
||||
test('discards stale inbound billing responses when the drawer closes 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, /watch\(detailOpen,[\s\S]*resetInboundBillingState\(\)/)
|
||||
})
|
||||
|
||||
test('protects unsaved drafts and exposes recoverable load failures', () => {
|
||||
assert.match(source, /onBeforeRouteLeave/)
|
||||
assert.match(source, /beforeunload/)
|
||||
|
||||
@@ -6,12 +6,16 @@ import { AlertTriangle, Box, Eye, FilePlus2, MapPin, PackageCheck, Pencil, Plus,
|
||||
import { FIRST_LEG_ORDER_STATUSES, FIRST_LEG_TRANSPORT_MODES, type FirstLegOrderStatus } from '@chongchong/shared'
|
||||
import QueryToolbar, { type QueryField, type QueryModel } from '@/components/common/QueryToolbar.vue'
|
||||
import DataTable, { type DataTableColumn } from '@/components/common/DataTable.vue'
|
||||
import { ApiError } from '@/api/http'
|
||||
import FirstLegOrderEditorShell from '@/modules/first-leg/components/FirstLegOrderEditorShell.vue'
|
||||
import InboundBillingPanel from '@/modules/first-leg/components/InboundBillingPanel.vue'
|
||||
import { getMerchantInboundBilling } from '@/modules/first-leg/api/inboundBilling'
|
||||
import { useFirstLegOrdersStore } from '@/modules/first-leg/store/firstLegOrders'
|
||||
import { useTenantAdministrationStore } from '@/modules/tenant-management/public'
|
||||
import { useAuthStore } from '@/modules/auth/public'
|
||||
import type { FirstLegPartyRole, FirstLegPartySnapshot } from '@/modules/first-leg/data/firstLegParties'
|
||||
import type { FirstLegOrder, FirstLegOrderDraft } from '@/modules/first-leg/types/firstLegOrder'
|
||||
import type { MerchantInboundBilling } from '@/modules/first-leg/types/inboundBilling'
|
||||
import {
|
||||
canClientEditFirstLegOrder,
|
||||
createEmptyCargoItem,
|
||||
@@ -70,10 +74,16 @@ const detailId = ref<string>()
|
||||
const saving = ref(false)
|
||||
const detailLoading = ref(false)
|
||||
const detailError = ref('')
|
||||
const inboundBilling = ref<MerchantInboundBilling | null>(null)
|
||||
const inboundBillingLoading = ref(false)
|
||||
const inboundBillingUnavailable = ref(false)
|
||||
const inboundBillingUnavailableMessage = ref('入库及费用结果尚未发布')
|
||||
const inboundBillingError = ref('')
|
||||
const supportingDataLoading = ref(false)
|
||||
const supportingDataErrors = ref<string[]>([])
|
||||
const editorBaseline = ref('')
|
||||
let cargoSequence = 0
|
||||
let inboundBillingRequestSequence = 0
|
||||
|
||||
interface CachedPrivateAddress {
|
||||
id: string
|
||||
@@ -293,7 +303,14 @@ watch(isCreatePage, (isCreate, wasCreate) => {
|
||||
resetEditorValidation()
|
||||
}, { immediate: true })
|
||||
|
||||
onBeforeUnmount(() => window.removeEventListener('beforeunload', handleBeforeUnload))
|
||||
watch(detailOpen, (open) => {
|
||||
if (!open) resetInboundBillingState()
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('beforeunload', handleBeforeUnload)
|
||||
resetInboundBillingState()
|
||||
})
|
||||
|
||||
onBeforeRouteLeave(async () => {
|
||||
if (!editorDirty.value) return true
|
||||
@@ -536,6 +553,7 @@ async function openDetail(order: FirstLegOrder) {
|
||||
detailOpen.value = true
|
||||
detailLoading.value = true
|
||||
detailError.value = ''
|
||||
void loadInboundBilling(order)
|
||||
try {
|
||||
await store.loadDetail(order.id)
|
||||
} catch (error) {
|
||||
@@ -551,6 +569,58 @@ async function retryDetail() {
|
||||
if (order) await openDetail(order)
|
||||
}
|
||||
|
||||
function resetInboundBillingState() {
|
||||
inboundBillingRequestSequence += 1
|
||||
inboundBilling.value = null
|
||||
inboundBillingLoading.value = false
|
||||
inboundBillingUnavailable.value = false
|
||||
inboundBillingUnavailableMessage.value = '入库及费用结果尚未发布'
|
||||
inboundBillingError.value = ''
|
||||
}
|
||||
|
||||
function isCurrentInboundBillingRequest(sequence: number, orderId: string) {
|
||||
return sequence === inboundBillingRequestSequence
|
||||
&& detailOpen.value
|
||||
&& detailId.value === orderId
|
||||
}
|
||||
|
||||
async function loadInboundBilling(order: FirstLegOrder) {
|
||||
const sequence = ++inboundBillingRequestSequence
|
||||
inboundBilling.value = null
|
||||
inboundBillingLoading.value = false
|
||||
inboundBillingUnavailable.value = false
|
||||
inboundBillingUnavailableMessage.value = '入库及费用结果尚未发布'
|
||||
inboundBillingError.value = ''
|
||||
|
||||
if (order.status === 'DRAFT') {
|
||||
inboundBillingUnavailable.value = true
|
||||
inboundBillingUnavailableMessage.value = '订单提交后可查看入库结果'
|
||||
return
|
||||
}
|
||||
|
||||
inboundBillingLoading.value = true
|
||||
try {
|
||||
const result = await getMerchantInboundBilling(order.id)
|
||||
if (!isCurrentInboundBillingRequest(sequence, order.id)) return
|
||||
inboundBilling.value = result
|
||||
} catch (error) {
|
||||
if (!isCurrentInboundBillingRequest(sequence, order.id)) return
|
||||
if (error instanceof ApiError && error.code === 'A0404') {
|
||||
inboundBillingUnavailable.value = true
|
||||
inboundBillingUnavailableMessage.value = '入库及费用结果尚未发布'
|
||||
} else {
|
||||
inboundBillingError.value = error instanceof Error ? error.message : '入库与费用加载失败'
|
||||
}
|
||||
} finally {
|
||||
if (isCurrentInboundBillingRequest(sequence, order.id)) inboundBillingLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function retryInboundBilling() {
|
||||
const order = detailId.value ? store.getById(detailId.value) : undefined
|
||||
if (order) await loadInboundBilling(order)
|
||||
}
|
||||
|
||||
async function confirmDiscardChanges() {
|
||||
try {
|
||||
await ElMessageBox.confirm('当前订单还有未保存的修改。离开后这些内容将丢失。', '放弃未保存的修改?', {
|
||||
@@ -865,6 +935,14 @@ async function searchOrders(model: QueryModel) {
|
||||
<div v-else class="express-empty"><PackageCheck :size="20" /><div><strong>平台尚未维护快递信息</strong><span>快递公司和快递单号由平台后台维护,客户端仅只读展示。</span></div></div>
|
||||
</section>
|
||||
<section class="detail-section"><h3>货物汇总</h3><div v-if="detailSummary" class="cargo-summary"><div><Box :size="18" /><span>明细</span><strong>{{ detailOrder.items.length }} 项</strong></div><div><span>件数</span><strong>{{ detailSummary.pieces }}</strong></div><div><span>毛重</span><strong>{{ detailSummary.grossWeightKg.toLocaleString() }} kg</strong></div><div><span>体积</span><strong>{{ detailSummary.volumeCbm }} m³</strong></div></div><ul class="cargo-detail-list"><li v-for="item in detailOrder.items" :key="item.id"><strong>{{ item.cargoNameCn }}</strong><span>{{ item.sku || '无 SKU' }} · {{ item.pieces }} 件 · {{ item.grossWeightKg }} kg</span></li></ul></section>
|
||||
<InboundBillingPanel
|
||||
:data="inboundBilling"
|
||||
:loading="inboundBillingLoading"
|
||||
:unavailable="inboundBillingUnavailable"
|
||||
:unavailable-message="inboundBillingUnavailableMessage"
|
||||
:error="inboundBillingError"
|
||||
@retry="retryInboundBilling"
|
||||
/>
|
||||
<section class="detail-section"><h3>处理时间线</h3><el-timeline><el-timeline-item v-for="item in [...detailOrder.timeline].reverse()" :key="item.id" :timestamp="item.occurredAt" placement="top"><strong>{{ item.title }}</strong><p>{{ item.description || '-' }}</p><span>{{ item.operatorName || '未提供' }}</span></el-timeline-item></el-timeline></section>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user