update order page

This commit is contained in:
2026-04-02 15:36:11 +08:00
parent 6d7f892237
commit c0f3a0d794
2 changed files with 203 additions and 17 deletions
@@ -516,4 +516,153 @@ describe('OrdersPage', () => {
const drawerHtml = document.body.querySelector('.ant-drawer')?.innerHTML || '' const drawerHtml = document.body.querySelector('.ant-drawer')?.innerHTML || ''
expect(drawerHtml).toContain('暂无数据') expect(drawerHtml).toContain('暂无数据')
}) })
// ─── P17.1 双栏布局测试 ─────────────────────────────────
async function openDrawerWithData(options?: { rawFail?: boolean }) {
const { api } = await mountPage()
if (options?.rawFail) {
vi.mocked(api.get)
.mockResolvedValueOnce(mockOrderDetail)
.mockRejectedValueOnce(new Error('Not found'))
} else {
vi.mocked(api.get)
.mockResolvedValueOnce(mockOrderDetail)
.mockResolvedValueOnce(mockRawDetail)
}
const buttons = Array.from(document.body.querySelectorAll('.ant-btn'))
const viewBtn = buttons.find((b) => b.textContent?.trim() === '查看')
viewBtn?.dispatchEvent(new MouseEvent('click', { bubbles: true }))
await flushPromises()
await nextTick()
return { api }
}
it('renders two-column layout in drawer', async () => {
await openDrawerWithData()
const drawer = document.body.querySelector('.ant-drawer')
const row = drawer?.querySelector('.ant-row')
expect(row).toBeTruthy()
const cols = row?.querySelectorAll(':scope > .ant-col')
expect(cols?.length).toBe(2)
})
it('left column contains all business sections', async () => {
await openDrawerWithData()
const drawer = document.body.querySelector('.ant-drawer')
const cols = drawer?.querySelectorAll('.ant-row > .ant-col')
const leftCol = cols?.[0]
expect(leftCol).toBeTruthy()
const leftHtml = leftCol?.innerHTML || ''
expect(leftHtml).toContain('基本信息')
expect(leftHtml).toContain('金额信息')
expect(leftHtml).toContain('时间与地址')
expect(leftHtml).toContain('订单子项')
expect(leftHtml).toContain('扩展数据')
})
it('right column renders raw JSON', async () => {
await openDrawerWithData()
const drawer = document.body.querySelector('.ant-drawer')
const cols = drawer?.querySelectorAll('.ant-row > .ant-col')
const rightCol = cols?.[1]
expect(rightCol).toBeTruthy()
const rightHtml = rightCol?.innerHTML || ''
expect(rightHtml).toContain('原始数据')
expect(rightHtml).toContain('tmall_001')
expect(rightHtml).toContain('TRADE_FINISHED')
})
it('right column shows empty state when raw is null', async () => {
await openDrawerWithData({ rawFail: true })
const drawer = document.body.querySelector('.ant-drawer')
const cols = drawer?.querySelectorAll('.ant-row > .ant-col')
const rightCol = cols?.[1]
expect(rightCol).toBeTruthy()
const rightHtml = rightCol?.innerHTML || ''
expect(rightHtml).toContain('暂无原始数据')
})
it('right column copy button calls clipboard API', async () => {
const writeTextMock = vi.fn().mockResolvedValue(undefined)
Object.assign(navigator, {
clipboard: { writeText: writeTextMock },
})
await openDrawerWithData()
const drawer = document.body.querySelector('.ant-drawer')
const cols = drawer?.querySelectorAll('.ant-row > .ant-col')
const rightCol = cols?.[1]
const copyBtn = rightCol?.querySelector('.ant-btn')
expect(copyBtn).toBeTruthy()
copyBtn?.dispatchEvent(new MouseEvent('click', { bubbles: true }))
await flushPromises()
await nextTick()
expect(writeTextMock).toHaveBeenCalledWith(
JSON.stringify(mockRawDetail.raw, null, 2),
)
})
it('left column does not contain raw data section', async () => {
await openDrawerWithData()
const drawer = document.body.querySelector('.ant-drawer')
const cols = drawer?.querySelectorAll('.ant-row > .ant-col')
const leftCol = cols?.[0]
const leftHtml = leftCol?.innerHTML || ''
expect(leftHtml).not.toContain('tmall_001')
expect(leftHtml).not.toContain('TRADE_FINISHED')
})
it('table copy button is before platform_order_id text', async () => {
await mountPage()
const table = document.body.querySelector('.ant-table')
const cells = table?.querySelectorAll('td') || []
let targetCell: Element | null = null
cells.forEach((cell) => {
if (cell.textContent?.includes('ORD-20260101-001')) {
targetCell = cell
}
})
expect(targetCell).toBeTruthy()
const span = targetCell!.querySelector('.inline-flex')
expect(span).toBeTruthy()
const children = span?.children
// First child is copy icon, second is text
expect(children?.[0]?.classList.contains('copy-icon-stub')).toBe(true)
expect(children?.[1]?.textContent).toContain('ORD-20260101-001')
})
it('table copy button calls clipboard API', async () => {
const writeTextMock = vi.fn().mockResolvedValue(undefined)
Object.assign(navigator, {
clipboard: { writeText: writeTextMock },
})
await mountPage()
const table = document.body.querySelector('.ant-table')
const copyIcon = table?.querySelector('.copy-icon-stub')
expect(copyIcon).toBeTruthy()
copyIcon?.dispatchEvent(new MouseEvent('click', { bubbles: true }))
await flushPromises()
await nextTick()
expect(writeTextMock).toHaveBeenCalledWith('ORD-20260101-001')
})
}) })
+54 -17
View File
@@ -131,6 +131,16 @@ async function handleCopyOrderId(platformOrderId: string) {
} }
} }
async function handleCopyRaw() {
if (!rawDetail.value?.raw) return
try {
await navigator.clipboard.writeText(JSON.stringify(rawDetail.value.raw, null, 2))
message.success('已复制到剪贴板')
} catch {
message.error('复制失败')
}
}
async function handleViewDetail(record: { id: number }) { async function handleViewDetail(record: { id: number }) {
const currentRequestId = ++detailRequestId const currentRequestId = ++detailRequestId
drawerVisible.value = true drawerVisible.value = true
@@ -244,7 +254,13 @@ 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'">
{{ record.platform_order_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="handleCopyOrderId(record.platform_order_id)"
/>
<span class="truncate">{{ record.platform_order_id }}</span>
</span>
</template> </template>
<template v-else-if="column.key === 'total_amount'"> <template v-else-if="column.key === 'total_amount'">
{{ formatAmount(record.total_amount) }} {{ formatAmount(record.total_amount) }}
@@ -296,13 +312,15 @@ async function handleViewDetail(record: { id: number }) {
<a-drawer <a-drawer
title="订单详情" title="订单详情"
:open="drawerVisible" :open="drawerVisible"
:width="720" :width="1200"
@close="drawerVisible = false" @close="drawerVisible = false"
> >
<a-spin :spinning="drawerLoading"> <a-spin :spinning="drawerLoading">
<template v-if="orderDetail"> <a-row :gutter="24" v-if="orderDetail">
<!-- 左栏:业务详情 -->
<a-col :span="14">
<!-- 基本信息 --> <!-- 基本信息 -->
<a-descriptions title="基本信息" :column="2" bordered class="mb-4"> <a-descriptions title="基本信息" :column="2" bordered size="small" class="mb-4">
<a-descriptions-item label="ID">{{ orderDetail.id }}</a-descriptions-item> <a-descriptions-item label="ID">{{ orderDetail.id }}</a-descriptions-item>
<a-descriptions-item label="状态"> <a-descriptions-item label="状态">
<a-tag <a-tag
@@ -349,7 +367,7 @@ async function handleViewDetail(record: { id: number }) {
</a-descriptions> </a-descriptions>
<!-- 金额信息 --> <!-- 金额信息 -->
<a-descriptions title="金额信息" :column="2" bordered class="mb-4"> <a-descriptions title="金额信息" :column="2" bordered size="small" class="mb-4">
<a-descriptions-item label="订单总金额"> <a-descriptions-item label="订单总金额">
{{ formatAmount(orderDetail.total_amount) }} {{ formatAmount(orderDetail.total_amount) }}
</a-descriptions-item> </a-descriptions-item>
@@ -383,7 +401,7 @@ async function handleViewDetail(record: { id: number }) {
</a-descriptions> </a-descriptions>
<!-- 时间与地址 --> <!-- 时间与地址 -->
<a-descriptions title="时间与地址" :column="2" bordered class="mb-4"> <a-descriptions title="时间与地址" :column="2" bordered size="small" class="mb-4">
<a-descriptions-item label="创建时间"> <a-descriptions-item label="创建时间">
{{ formatTime(orderDetail.created_date) }} {{ formatTime(orderDetail.created_date) }}
</a-descriptions-item> </a-descriptions-item>
@@ -434,7 +452,7 @@ async function handleViewDetail(record: { id: number }) {
</a-table> </a-table>
<!-- ext JSON --> <!-- ext JSON -->
<a-descriptions title="扩展数据 (ext)" :column="1" bordered class="mb-4"> <a-descriptions title="扩展数据 (ext)" :column="1" bordered size="small" class="mb-4">
<a-descriptions-item> <a-descriptions-item>
<pre v-if="orderDetail.ext" class="m-0 text-xs max-h-80 overflow-auto">{{ <pre v-if="orderDetail.ext" class="m-0 text-xs max-h-80 overflow-auto">{{
JSON.stringify(orderDetail.ext, null, 2) JSON.stringify(orderDetail.ext, null, 2)
@@ -443,17 +461,36 @@ async function handleViewDetail(record: { id: number }) {
</a-descriptions-item> </a-descriptions-item>
</a-descriptions> </a-descriptions>
<!-- raw JSON --> </a-col>
<a-descriptions title="原始数据 (raw)" :column="1" bordered>
<a-descriptions-item> <!-- 右栏:原始 JSON 数据 -->
<pre <a-col :span="10">
<div class="sticky top-0">
<div class="flex items-center justify-between mb-2">
<h4 class="text-base font-medium m-0">原始数据</h4>
<a-button
v-if="rawDetail?.raw" v-if="rawDetail?.raw"
class="m-0 text-xs max-h-80 overflow-auto" type="text"
>{{ JSON.stringify(rawDetail.raw, null, 2) }}</pre> size="small"
<span v-else class="text-gray-400">暂无数据</span> @click="handleCopyRaw"
</a-descriptions-item> >
</a-descriptions> <template #icon><CopyOutlined /></template>
</template> 复制 JSON
</a-button>
</div>
<div
v-if="rawDetail?.raw"
class="bg-gray-50 rounded border border-gray-200 p-3 overflow-auto"
style="max-height: calc(100vh - 120px)"
>
<pre class="m-0 text-xs leading-relaxed whitespace-pre-wrap break-all">{{ JSON.stringify(rawDetail.raw, null, 2) }}</pre>
</div>
<a-empty v-else description="暂无原始数据" />
</div>
</a-col>
</a-row>
</a-spin> </a-spin>
</a-drawer> </a-drawer>
</div> </div>