update order items

This commit is contained in:
2026-04-03 10:17:03 +08:00
parent 51292b618a
commit 5189798732
2 changed files with 160 additions and 20 deletions
@@ -20,6 +20,12 @@ Object.defineProperty(window, 'matchMedia', {
}), }),
}) })
const mockRouterPush = vi.fn()
vi.mock('vue-router', () => ({
useRoute: () => ({ query: {} }),
useRouter: () => ({ push: mockRouterPush }),
}))
vi.mock('@/utils/request', () => ({ vi.mock('@/utils/request', () => ({
api: { api: {
get: vi.fn(), get: vi.fn(),
@@ -100,7 +106,7 @@ const mockLookupCompanies = [
{ id: 1, name: 'Company A', label: '公司A' }, { id: 1, name: 'Company A', label: '公司A' },
{ id: 2, name: 'Company B', label: '公司B' }, { id: 2, name: 'Company B', label: '公司B' },
] ]
const mockLookupPlatforms = [{ id: 1, developer_id: 1 }] const mockLookupPlatforms = [{ id: 1, name: 'Tmall', label: 'Tmall', developer_id: 1 }]
const mockLookupStores = [ const mockLookupStores = [
{ id: 1, company_id: 1, platform_id: 1, name: 'Store 1', label: '店铺1' }, { id: 1, company_id: 1, platform_id: 1, name: 'Store 1', label: '店铺1' },
{ id: 2, company_id: 2, platform_id: 1, name: 'Store 2', label: '店铺2' }, { id: 2, company_id: 2, platform_id: 1, name: 'Store 2', label: '店铺2' },
@@ -251,7 +257,7 @@ describe('useOrderItemStore', () => {
await store.loadLookups() await store.loadLookups()
expect(store.companyMap.get(1)).toBe('公司A') expect(store.companyMap.get(1)).toBe('公司A')
expect(store.platformMap.get(1)).toBe('平台 #1') expect(store.platformMap.get(1)).toBe('Tmall')
expect(store.storeMap.get(1)).toBe('店铺1') expect(store.storeMap.get(1)).toBe('店铺1')
}) })
@@ -267,6 +273,45 @@ describe('useOrderItemStore', () => {
expect(store.companyMap.size).toBe(0) expect(store.companyMap.size).toBe(0)
warnSpy.mockRestore() warnSpy.mockRestore()
}) })
it('companyMap falls back to name when label is "null"', async () => {
const { api } = await import('@/utils/request')
vi.mocked(api.get)
.mockResolvedValueOnce([{ id: 1, name: 'RealName', label: 'null' }])
.mockResolvedValueOnce(mockLookupPlatforms)
.mockResolvedValueOnce(mockLookupStores)
const store = useOrderItemStore()
await store.loadLookups()
expect(store.companyMap.get(1)).toBe('RealName')
})
it('platformMap falls back to name when label is missing', async () => {
const { api } = await import('@/utils/request')
vi.mocked(api.get)
.mockResolvedValueOnce(mockLookupCompanies)
.mockResolvedValueOnce([{ id: 1, name: 'JD', developer_id: 1 }])
.mockResolvedValueOnce(mockLookupStores)
const store = useOrderItemStore()
await store.loadLookups()
expect(store.platformMap.get(1)).toBe('JD')
})
it('platformMap shows fallback when both label and name are empty', async () => {
const { api } = await import('@/utils/request')
vi.mocked(api.get)
.mockResolvedValueOnce(mockLookupCompanies)
.mockResolvedValueOnce([{ id: 3, name: '', developer_id: 1 }])
.mockResolvedValueOnce(mockLookupStores)
const store = useOrderItemStore()
await store.loadLookups()
expect(store.platformMap.get(3)).toBe('平台 #3')
})
}) })
}) })
@@ -278,6 +323,7 @@ describe('OrderItemsPage', () => {
beforeEach(() => { beforeEach(() => {
setActivePinia(createPinia()) setActivePinia(createPinia())
vi.restoreAllMocks() vi.restoreAllMocks()
mockRouterPush.mockClear()
document.body.innerHTML = '' document.body.innerHTML = ''
}) })
@@ -515,4 +561,82 @@ describe('OrderItemsPage', () => {
expect(errorSpy).toHaveBeenCalledWith('获取订单项详情失败') expect(errorSpy).toHaveBeenCalledWith('获取订单项详情失败')
}) })
it('filter form has filter-form class', async () => {
await mountPage()
const form = wrapper.find('form.filter-form')
expect(form.exists()).toBe(true)
})
it('renders platform name from lookup map (not hardcoded fallback)', async () => {
await mountPage()
const html = wrapper.html()
expect(html).toContain('Tmall')
expect(html).not.toContain('平台 #1')
})
it('renders copy buttons before text in platform_order_id column', async () => {
await mountPage()
// Find table cells containing our platform order IDs
const cells = wrapper.findAll('td')
const orderIdCell = cells.find((c) => c.text().includes('ORD-20260101-001'))
expect(orderIdCell).toBeDefined()
// CopyOutlined stub should appear before the link text
const cellHtml = orderIdCell!.html()
const copyPos = cellHtml.indexOf('copy-icon-stub')
const linkPos = cellHtml.indexOf('ORD-20260101-001')
expect(copyPos).toBeLessThan(linkPos)
})
it('renders copy button for sub_order_id when value exists', async () => {
await mountPage()
// mockOrderItems[0] has sub_order_id = 'SUB-001'
const cells = wrapper.findAll('td')
const subOrderCell = cells.find((c) => c.text().includes('SUB-001'))
expect(subOrderCell).toBeDefined()
expect(subOrderCell!.find('.copy-icon-stub').exists()).toBe(true)
})
it('does not render copy button for sub_order_id when value is null', async () => {
await mountPage()
// mockOrderItems[1] has sub_order_id = null, should show '-'
const cells = wrapper.findAll('td')
// Find all cells that contain exactly '-' and are in sub_order_id column context
// The second row's sub_order_id cell should have '-' and no copy icon
const dashCells = cells.filter((c) => c.text().trim() === '-')
// At least one dash cell should have no copy-icon-stub
const noCopyDash = dashCells.some((c) => !c.find('.copy-icon-stub').exists())
expect(noCopyDash).toBe(true)
})
it('renders copy button for platform_product_id column', async () => {
await mountPage()
const cells = wrapper.findAll('td')
const prodIdCell = cells.find((c) => c.text().includes('PROD-001'))
expect(prodIdCell).toBeDefined()
expect(prodIdCell!.find('.copy-icon-stub').exists()).toBe(true)
})
it('handleGoToOrder includes auto_submit in router push', async () => {
await mountPage()
const links = wrapper.findAll('a')
const orderLink = links.find((l) => l.text().includes('ORD-20260101-001'))
expect(orderLink).toBeDefined()
await orderLink!.trigger('click')
await flushPromises()
expect(mockRouterPush).toHaveBeenCalledWith({
path: '/orders',
query: { platform_order_id: 'ORD-20260101-001', auto_submit: '1' },
})
})
}) })
+34 -18
View File
@@ -96,17 +96,20 @@ function formatTime(time: string | null) {
return time.replace('T', ' ').substring(0, 16) return time.replace('T', ' ').substring(0, 16)
} }
async function handleCopyOrderId(platformOrderId: string) { async function handleCopy(text: string, label: string = 'ID') {
try { try {
await navigator.clipboard.writeText(platformOrderId) await navigator.clipboard.writeText(text)
message.success('已复制平台订单ID') message.success(`已复制${label}`)
} catch { } catch {
message.error('复制失败') message.error('复制失败')
} }
} }
function handleGoToOrder(platformOrderId: string) { function handleGoToOrder(platformOrderId: string) {
router.push({ path: '/orders', query: { platform_order_id: platformOrderId } }) router.push({
path: '/orders',
query: { platform_order_id: platformOrderId, auto_submit: '1' },
})
} }
async function handleViewDetail(record: { id: number }) { async function handleViewDetail(record: { id: number }) {
@@ -137,7 +140,7 @@ async function handleViewDetail(record: { id: number }) {
<!-- Filter area --> <!-- Filter area -->
<a-card class="mb-4"> <a-card class="mb-4">
<a-form layout="inline" @submit.prevent="handleSearch"> <a-form layout="inline" class="filter-form" @submit.prevent="handleSearch">
<a-form-item> <a-form-item>
<CascadeFilter v-model="store.cascadeValue" /> <CascadeFilter v-model="store.cascadeValue" />
</a-form-item> </a-form-item>
@@ -207,22 +210,35 @@ async function handleViewDetail(record: { id: number }) {
{{ store.storeMap.get(record.store_id) || record.store_id }} {{ store.storeMap.get(record.store_id) || record.store_id }}
</template> </template>
<template v-else-if="column.key === 'platform_order_id'"> <template v-else-if="column.key === 'platform_order_id'">
<a <span class="inline-flex items-center gap-1">
class="text-blue-500 cursor-pointer" <CopyOutlined
@click="handleGoToOrder(record.platform_order_id)" class="flex-shrink-0 cursor-pointer text-gray-400 hover:text-blue-500"
> @click.stop="handleCopy(record.platform_order_id, '平台订单ID')"
{{ record.platform_order_id }} />
</a> <a class="truncate text-blue-500 cursor-pointer"
<CopyOutlined @click="handleGoToOrder(record.platform_order_id)">
class="ml-1 cursor-pointer text-gray-400 hover:text-blue-500" {{ record.platform_order_id }}
@click.stop="handleCopyOrderId(record.platform_order_id)" </a>
/> </span>
</template> </template>
<template v-else-if="column.key === 'sub_order_id'"> <template v-else-if="column.key === 'sub_order_id'">
{{ record.sub_order_id || '-' }} <span v-if="record.sub_order_id" class="inline-flex items-center gap-1">
<CopyOutlined
class="flex-shrink-0 cursor-pointer text-gray-400 hover:text-blue-500"
@click.stop="handleCopy(record.sub_order_id, '子订单ID')"
/>
<span class="truncate">{{ record.sub_order_id }}</span>
</span>
<span v-else>-</span>
</template> </template>
<template v-else-if="column.key === 'platform_product_id'"> <template v-else-if="column.key === 'platform_product_id'">
{{ record.platform_product_id }} <span class="inline-flex items-center gap-1">
<CopyOutlined
class="flex-shrink-0 cursor-pointer text-gray-400 hover:text-blue-500"
@click.stop="handleCopy(record.platform_product_id, '平台商品ID')"
/>
<span class="truncate">{{ record.platform_product_id }}</span>
</span>
</template> </template>
<template v-else-if="column.key === 'product_sku'"> <template v-else-if="column.key === 'product_sku'">
{{ record.product_sku || '-' }} {{ record.product_sku || '-' }}
@@ -318,7 +334,7 @@ async function handleViewDetail(record: { id: number }) {
</a> </a>
<CopyOutlined <CopyOutlined
class="ml-2 cursor-pointer text-blue-500" class="ml-2 cursor-pointer text-blue-500"
@click="handleCopyOrderId(itemDetail.parent_order.platform_order_id)" @click="handleCopy(itemDetail.parent_order.platform_order_id, '平台订单ID')"
/> />
</a-descriptions-item> </a-descriptions-item>
<a-descriptions-item label="订单状态"> <a-descriptions-item label="订单状态">