update order page
This commit is contained in:
@@ -516,4 +516,153 @@ describe('OrdersPage', () => {
|
||||
const drawerHtml = document.body.querySelector('.ant-drawer')?.innerHTML || ''
|
||||
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')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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 }) {
|
||||
const currentRequestId = ++detailRequestId
|
||||
drawerVisible.value = true
|
||||
@@ -244,7 +254,13 @@ async function handleViewDetail(record: { id: number }) {
|
||||
{{ store.storeMap.get(record.store_id) || record.store_id }}
|
||||
</template>
|
||||
<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 v-else-if="column.key === 'total_amount'">
|
||||
{{ formatAmount(record.total_amount) }}
|
||||
@@ -296,13 +312,15 @@ async function handleViewDetail(record: { id: number }) {
|
||||
<a-drawer
|
||||
title="订单详情"
|
||||
:open="drawerVisible"
|
||||
:width="720"
|
||||
:width="1200"
|
||||
@close="drawerVisible = false"
|
||||
>
|
||||
<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="状态">
|
||||
<a-tag
|
||||
@@ -349,7 +367,7 @@ async function handleViewDetail(record: { id: number }) {
|
||||
</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="订单总金额">
|
||||
{{ formatAmount(orderDetail.total_amount) }}
|
||||
</a-descriptions-item>
|
||||
@@ -383,7 +401,7 @@ async function handleViewDetail(record: { id: number }) {
|
||||
</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="创建时间">
|
||||
{{ formatTime(orderDetail.created_date) }}
|
||||
</a-descriptions-item>
|
||||
@@ -434,7 +452,7 @@ async function handleViewDetail(record: { id: number }) {
|
||||
</a-table>
|
||||
|
||||
<!-- 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>
|
||||
<pre v-if="orderDetail.ext" class="m-0 text-xs max-h-80 overflow-auto">{{
|
||||
JSON.stringify(orderDetail.ext, null, 2)
|
||||
@@ -443,17 +461,36 @@ async function handleViewDetail(record: { id: number }) {
|
||||
</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
|
||||
<!-- raw JSON -->
|
||||
<a-descriptions title="原始数据 (raw)" :column="1" bordered>
|
||||
<a-descriptions-item>
|
||||
<pre
|
||||
</a-col>
|
||||
|
||||
<!-- 右栏:原始 JSON 数据 -->
|
||||
<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"
|
||||
class="m-0 text-xs max-h-80 overflow-auto"
|
||||
>{{ JSON.stringify(rawDetail.raw, null, 2) }}</pre>
|
||||
<span v-else class="text-gray-400">暂无数据</span>
|
||||
</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</template>
|
||||
type="text"
|
||||
size="small"
|
||||
@click="handleCopyRaw"
|
||||
>
|
||||
<template #icon><CopyOutlined /></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-drawer>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user