优化
This commit is contained in:
@@ -15,11 +15,19 @@ import { useAccessPreviewStore } from '@/core/access/accessPreview'
|
||||
import { useAuthStore } from '@/core/auth/auth'
|
||||
import CustomerDetailView from './CustomerDetailView.vue'
|
||||
|
||||
const { routerPush } = vi.hoisted(() => ({ routerPush: vi.fn() }))
|
||||
const { routerPush, routeControl } = vi.hoisted(() => ({
|
||||
routerPush: vi.fn(),
|
||||
routeControl: { setTenantId: (_tenantId: string) => {} },
|
||||
}))
|
||||
|
||||
vi.mock('vue-router', async (importOriginal) => ({
|
||||
...await importOriginal<typeof import('vue-router')>(),
|
||||
useRoute: () => ({ params: { tenantId: '8' } }),
|
||||
...await (async () => {
|
||||
const { reactive } = await import('vue')
|
||||
const route = reactive({ params: { tenantId: '8' } })
|
||||
routeControl.setTenantId = (tenantId: string) => { route.params.tenantId = tenantId }
|
||||
return { useRoute: () => route }
|
||||
})(),
|
||||
useRouter: () => ({ push: routerPush }),
|
||||
}))
|
||||
|
||||
@@ -59,6 +67,7 @@ describe('CustomerDetailView', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
routeControl.setTenantId('8')
|
||||
vi.stubGlobal('ResizeObserver', ResizeObserverStub)
|
||||
Object.defineProperty(HTMLElement.prototype, 'scrollIntoView', { configurable: true, value: vi.fn() })
|
||||
Object.defineProperty(URL, 'createObjectURL', { configurable: true, value: vi.fn(() => 'blob:qualification-preview') })
|
||||
@@ -121,7 +130,7 @@ describe('CustomerDetailView', () => {
|
||||
.find((button) => button.textContent?.trim() === '计费规则')
|
||||
billingTab?.click()
|
||||
await flushPromises()
|
||||
expect(document.body.textContent).toContain('暂无计费规则查看权限')
|
||||
expect(document.body.textContent).toContain('暂无尺寸计算规则查看权限')
|
||||
expect(document.body.textContent).not.toContain('计费规则尚未接入')
|
||||
})
|
||||
|
||||
@@ -159,4 +168,28 @@ describe('CustomerDetailView', () => {
|
||||
expect(listPlatformFirstLegOrders).toHaveBeenCalledTimes(3)
|
||||
expect(listPlatformFirstLegOrders).toHaveBeenLastCalledWith({ tenantId: tenant.id, page: 1, size: 10 })
|
||||
})
|
||||
|
||||
it('reloads a reused detail view and ignores the previous customer response', async () => {
|
||||
const secondTenant = { ...tenant, id: '9', tenantCode: 'TEN9', tenantName: '远航供应链', legalName: '远航供应链有限公司' }
|
||||
let resolveFirstTenant!: (value: PlatformTenantResource) => void
|
||||
const firstTenantRequest = new Promise<PlatformTenantResource>((resolve) => { resolveFirstTenant = resolve })
|
||||
vi.mocked(getPlatformTenant).mockImplementation((tenantId) => tenantId === '8' ? firstTenantRequest : Promise.resolve(secondTenant))
|
||||
vi.mocked(listTenantQualifications).mockResolvedValue([])
|
||||
vi.mocked(listPlatformTenantMembers).mockResolvedValue([])
|
||||
vi.mocked(listPlatformTenantOrderCounts).mockImplementation(async (tenantIds) => [{ tenantId: tenantIds[0], orderCount: 0 }])
|
||||
vi.mocked(listPlatformFirstLegOrders).mockResolvedValue({ records: [], page: 1, size: 10, total: 0, pages: 0 })
|
||||
|
||||
mount(CustomerDetailView, { attachTo: document.body, global: { plugins: [pinia, ElementPlus] } })
|
||||
await flushPromises()
|
||||
routeControl.setTenantId('9')
|
||||
await flushPromises()
|
||||
|
||||
expect(getPlatformTenant).toHaveBeenCalledWith('9')
|
||||
expect(document.body.textContent).toContain('远航供应链')
|
||||
|
||||
resolveFirstTenant(tenant)
|
||||
await flushPromises()
|
||||
expect(document.body.textContent).toContain('远航供应链')
|
||||
expect(document.body.textContent).not.toContain('柏悦物流')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
||||
import { computed, onBeforeUnmount, ref, watch } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import {
|
||||
ArrowLeft, BarChart3, Box, Building2, CircleDollarSign, ClipboardList, Eye,
|
||||
@@ -54,6 +54,9 @@ const orderPageSize = ref(10)
|
||||
const customerOrdersLoading = ref(false)
|
||||
const customerOrdersError = ref<ApiErrorDetails | null>(null)
|
||||
let skipNextOrderPageLoad = false
|
||||
let detailRequestSerial = 0
|
||||
let orderRequestSerial = 0
|
||||
let disposed = false
|
||||
const previews = ref<Record<string, PreviewState>>({})
|
||||
const previewOpen = ref(false)
|
||||
const previewName = ref('')
|
||||
@@ -124,20 +127,23 @@ function transportLabel(order: PlatformFirstLegOrderResource) {
|
||||
}
|
||||
function formattedDate(value?: string | null) { return value ? value.replace('T', ' ').slice(0, 16) : '—' }
|
||||
|
||||
async function loadCustomerOrders(page = orderPage.value) {
|
||||
async function loadCustomerOrders(page = orderPage.value, requestedTenantId = tenantId.value) {
|
||||
if (!access.can('platform:order:list')) return
|
||||
const serial = ++orderRequestSerial
|
||||
customerOrdersLoading.value = true
|
||||
customerOrdersError.value = null
|
||||
try {
|
||||
const result = await listPlatformFirstLegOrders({ tenantId: tenantId.value, page, size: orderPageSize.value })
|
||||
const result = await listPlatformFirstLegOrders({ tenantId: requestedTenantId, page, size: orderPageSize.value })
|
||||
if (disposed || serial !== orderRequestSerial || requestedTenantId !== tenantId.value) return
|
||||
customerOrders.value = result.records
|
||||
customerOrderTotal.value = result.total
|
||||
if (orderCount.value === null) orderCount.value = result.total
|
||||
} catch (error) {
|
||||
if (disposed || serial !== orderRequestSerial || requestedTenantId !== tenantId.value) return
|
||||
customerOrders.value = []
|
||||
customerOrdersError.value = getApiErrorDetails(error, '运单记录加载失败,请稍后重试')
|
||||
} finally {
|
||||
customerOrdersLoading.value = false
|
||||
if (!disposed && serial === orderRequestSerial) customerOrdersLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -178,15 +184,25 @@ async function openPreview(item: PlatformTenantQualification) {
|
||||
}
|
||||
|
||||
async function loadDetail() {
|
||||
const requestedTenantId = tenantId.value
|
||||
const serial = ++detailRequestSerial
|
||||
loading.value = true
|
||||
loadError.value = null
|
||||
clearPreviews()
|
||||
customer.value = null
|
||||
qualifications.value = []
|
||||
members.value = []
|
||||
orderCount.value = null
|
||||
customerOrders.value = []
|
||||
customerOrderTotal.value = 0
|
||||
customerOrdersError.value = null
|
||||
try {
|
||||
const [tenantResult, qualificationResult, memberResult] = await Promise.all([
|
||||
getPlatformTenant(tenantId.value),
|
||||
listTenantQualifications(tenantId.value),
|
||||
listPlatformTenantMembers(tenantId.value),
|
||||
getPlatformTenant(requestedTenantId),
|
||||
listTenantQualifications(requestedTenantId),
|
||||
listPlatformTenantMembers(requestedTenantId),
|
||||
])
|
||||
if (disposed || serial !== detailRequestSerial || requestedTenantId !== tenantId.value) return
|
||||
customer.value = tenantResult
|
||||
qualifications.value = qualificationResult
|
||||
members.value = memberResult
|
||||
@@ -198,12 +214,13 @@ async function loadDetail() {
|
||||
if (access.can('platform:order:list')) {
|
||||
if (orderPage.value !== 1) skipNextOrderPageLoad = true
|
||||
orderPage.value = 1
|
||||
customerOrders.value = []
|
||||
customerOrderTotal.value = 0
|
||||
optional.push(listPlatformTenantOrderCounts([tenantId.value]).then((records) => {
|
||||
orderCount.value = Number(records.find((item) => String(item.tenantId) === tenantId.value)?.orderCount ?? 0)
|
||||
}).catch(() => { orderCount.value = null }))
|
||||
optional.push(loadCustomerOrders(1))
|
||||
optional.push(listPlatformTenantOrderCounts([requestedTenantId]).then((records) => {
|
||||
if (disposed || serial !== detailRequestSerial || requestedTenantId !== tenantId.value) return
|
||||
orderCount.value = Number(records.find((item) => String(item.tenantId) === requestedTenantId)?.orderCount ?? 0)
|
||||
}).catch(() => {
|
||||
if (!disposed && serial === detailRequestSerial && requestedTenantId === tenantId.value) orderCount.value = null
|
||||
}))
|
||||
optional.push(loadCustomerOrders(1, requestedTenantId))
|
||||
} else {
|
||||
customerOrdersLoading.value = false
|
||||
customerOrdersError.value = null
|
||||
@@ -212,11 +229,14 @@ async function loadDetail() {
|
||||
}
|
||||
await Promise.all(optional)
|
||||
} catch (error) {
|
||||
if (disposed || serial !== detailRequestSerial || requestedTenantId !== tenantId.value) return
|
||||
customer.value = null
|
||||
qualifications.value = []
|
||||
members.value = []
|
||||
loadError.value = getApiErrorDetails(error, '客户详情加载失败,请稍后重试')
|
||||
} finally { loading.value = false }
|
||||
} finally {
|
||||
if (!disposed && serial === detailRequestSerial) loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function openOrder(order: PlatformFirstLegOrderResource) {
|
||||
@@ -253,8 +273,25 @@ function handleTabKeydown(event: KeyboardEvent, key: DetailTab) {
|
||||
button?.focus()
|
||||
selectTab(tabs[next].key, button)
|
||||
}
|
||||
onMounted(loadDetail)
|
||||
onBeforeUnmount(clearPreviews)
|
||||
watch(tenantId, (value) => {
|
||||
activeTab.value = 'basic'
|
||||
if (value) void loadDetail()
|
||||
else {
|
||||
detailRequestSerial += 1
|
||||
orderRequestSerial += 1
|
||||
loading.value = false
|
||||
customer.value = null
|
||||
qualifications.value = []
|
||||
members.value = []
|
||||
customerOrders.value = []
|
||||
}
|
||||
}, { immediate: true })
|
||||
onBeforeUnmount(() => {
|
||||
disposed = true
|
||||
detailRequestSerial += 1
|
||||
orderRequestSerial += 1
|
||||
clearPreviews()
|
||||
})
|
||||
watch(orderPage, (page, previousPage) => {
|
||||
if (skipNextOrderPageLoad) {
|
||||
skipNextOrderPageLoad = false
|
||||
|
||||
@@ -23,11 +23,23 @@ import QueryToolbar from '@/shared/components/QueryToolbar.vue'
|
||||
import { showExportTaskCreatedDialog } from '@/shared/components/exportTaskCreatedDialog'
|
||||
import CustomersView from './CustomersView.vue'
|
||||
|
||||
const { routerPush, routerReplace } = vi.hoisted(() => ({ routerPush: vi.fn(), routerReplace: vi.fn() }))
|
||||
const { routerPush, routerReplace, routeControl } = vi.hoisted(() => ({
|
||||
routerPush: vi.fn(),
|
||||
routerReplace: vi.fn(),
|
||||
routeControl: { setEditId: (_editId: string) => {} },
|
||||
}))
|
||||
|
||||
vi.mock('vue-router', async (importOriginal) => ({
|
||||
...await importOriginal<typeof import('vue-router')>(),
|
||||
useRoute: () => ({ query: {} }),
|
||||
...await (async () => {
|
||||
const { reactive } = await import('vue')
|
||||
const route = reactive<{ query: { edit?: string } }>({ query: {} })
|
||||
routeControl.setEditId = (editId: string) => {
|
||||
if (editId) route.query.edit = editId
|
||||
else delete route.query.edit
|
||||
}
|
||||
return { useRoute: () => route }
|
||||
})(),
|
||||
useRouter: () => ({ push: routerPush, replace: routerReplace }),
|
||||
}))
|
||||
|
||||
@@ -89,6 +101,7 @@ describe('CustomersView', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
routeControl.setEditId('')
|
||||
vi.stubGlobal('ResizeObserver', ResizeObserverStub)
|
||||
Object.defineProperty(URL, 'createObjectURL', { configurable: true, value: vi.fn(() => 'blob:qualification-preview') })
|
||||
Object.defineProperty(URL, 'revokeObjectURL', { configurable: true, value: vi.fn() })
|
||||
@@ -208,6 +221,18 @@ describe('CustomersView', () => {
|
||||
expect(getPlatformTenant).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('opens the requested editor when a cached list receives an edit query', async () => {
|
||||
mount(CustomersView, { attachTo: document.body, global: { plugins: [pinia, ElementPlus] } })
|
||||
await flushPromises()
|
||||
|
||||
routeControl.setEditId(String(fullTenant.id))
|
||||
await flushPromises()
|
||||
|
||||
expect(getPlatformTenant).toHaveBeenCalledWith(fullTenant.id)
|
||||
expect(document.querySelector<HTMLInputElement>('input[placeholder="请输入手机号或固定电话"]')?.value).toBe(fullTenant.contactPhone)
|
||||
expect(routerReplace).toHaveBeenCalledWith({ name: 'customers' })
|
||||
})
|
||||
|
||||
it('saves both required files and the credit code through one package action', async () => {
|
||||
vi.mocked(listTenantQualifications).mockResolvedValue([{
|
||||
id: 'qualification-legal', tenantId: fullTenant.id, qualificationType: 'LEGAL_PERSON_ID',
|
||||
|
||||
@@ -652,7 +652,23 @@ async function exportCustomers() {
|
||||
if (openDownloadCenter) await router.push({ name: 'download-center' })
|
||||
}
|
||||
|
||||
async function openRouteEditor(editId: string) {
|
||||
if (!editId || !access.can('platform:customer:edit')) return
|
||||
const row = result.value.records.find((item) => String(item.id) === editId)
|
||||
try {
|
||||
await openEdit(row ?? await getPlatformTenant(editId))
|
||||
} catch (error) {
|
||||
ElMessage.error(getApiErrorMessage(error, '客户资料加载失败'))
|
||||
}
|
||||
if (route.query.edit === editId) void router.replace({ name: 'customers' })
|
||||
}
|
||||
|
||||
watch([page, pageSize], () => { void loadCustomers() })
|
||||
watch(
|
||||
() => typeof route.query.edit === 'string' ? route.query.edit : '',
|
||||
(editId) => { if (editId) void openRouteEditor(editId) },
|
||||
{ immediate: true },
|
||||
)
|
||||
watch(editorOpen, (open) => {
|
||||
if (!open) {
|
||||
editorLoadSequence += 1
|
||||
@@ -666,18 +682,7 @@ watch(
|
||||
},
|
||||
)
|
||||
onBeforeUnmount(clearQualificationPreviews)
|
||||
onMounted(async () => {
|
||||
await loadCustomers()
|
||||
const editId = typeof route.query.edit === 'string' ? route.query.edit : ''
|
||||
if (!editId || !access.can('platform:customer:edit')) return
|
||||
const row = result.value.records.find((item) => String(item.id) === editId)
|
||||
if (row) await openEdit(row)
|
||||
else {
|
||||
try { await openEdit(await getPlatformTenant(editId)) }
|
||||
catch (error) { ElMessage.error(getApiErrorMessage(error, '客户资料加载失败')) }
|
||||
}
|
||||
void router.replace({ name: 'customers' })
|
||||
})
|
||||
onMounted(() => { void loadCustomers() })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
Reference in New Issue
Block a user