update order page
This commit is contained in:
@@ -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')
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -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"
|
||||||
|
type="text"
|
||||||
|
size="small"
|
||||||
|
@click="handleCopyRaw"
|
||||||
|
>
|
||||||
|
<template #icon><CopyOutlined /></template>
|
||||||
|
复制 JSON
|
||||||
|
</a-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
v-if="rawDetail?.raw"
|
v-if="rawDetail?.raw"
|
||||||
class="m-0 text-xs max-h-80 overflow-auto"
|
class="bg-gray-50 rounded border border-gray-200 p-3 overflow-auto"
|
||||||
>{{ JSON.stringify(rawDetail.raw, null, 2) }}</pre>
|
style="max-height: calc(100vh - 120px)"
|
||||||
<span v-else class="text-gray-400">暂无数据</span>
|
>
|
||||||
</a-descriptions-item>
|
<pre class="m-0 text-xs leading-relaxed whitespace-pre-wrap break-all">{{ JSON.stringify(rawDetail.raw, null, 2) }}</pre>
|
||||||
</a-descriptions>
|
</div>
|
||||||
</template>
|
|
||||||
|
<a-empty v-else description="暂无原始数据" />
|
||||||
|
</div>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
</a-spin>
|
</a-spin>
|
||||||
</a-drawer>
|
</a-drawer>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user