update refund items
This commit is contained in:
@@ -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(),
|
||||||
@@ -91,7 +97,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: '天猫', 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' },
|
||||||
@@ -260,10 +266,26 @@ describe('useRefundItemStore', () => {
|
|||||||
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('天猫')
|
||||||
expect(store.storeMap.get(1)).toBe('店铺1')
|
expect(store.storeMap.get(1)).toBe('店铺1')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('filters null and "null" labels in lookup maps', async () => {
|
||||||
|
const { api } = await import('@/utils/request')
|
||||||
|
vi.mocked(api.get)
|
||||||
|
.mockResolvedValueOnce([{ id: 1, name: 'CompanyA', label: null }, { id: 2, name: 'CompanyB', label: 'null' }])
|
||||||
|
.mockResolvedValueOnce([{ id: 1, name: 'Tmall', label: 'null', developer_id: 1 }])
|
||||||
|
.mockResolvedValueOnce([{ id: 1, company_id: 1, platform_id: 1, name: 'Store1', label: null }])
|
||||||
|
|
||||||
|
const store = useRefundItemStore()
|
||||||
|
await store.loadLookups()
|
||||||
|
|
||||||
|
expect(store.companyMap.get(1)).toBe('CompanyA')
|
||||||
|
expect(store.companyMap.get(2)).toBe('CompanyB')
|
||||||
|
expect(store.platformMap.get(1)).toBe('Tmall')
|
||||||
|
expect(store.storeMap.get(1)).toBe('Store1')
|
||||||
|
})
|
||||||
|
|
||||||
it('handles loadLookups failure gracefully', async () => {
|
it('handles loadLookups failure gracefully', async () => {
|
||||||
const { api } = await import('@/utils/request')
|
const { api } = await import('@/utils/request')
|
||||||
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
|
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
|
||||||
@@ -287,6 +309,7 @@ describe('RefundItemsPage', () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
setActivePinia(createPinia())
|
setActivePinia(createPinia())
|
||||||
vi.restoreAllMocks()
|
vi.restoreAllMocks()
|
||||||
|
mockRouterPush.mockClear()
|
||||||
document.body.innerHTML = ''
|
document.body.innerHTML = ''
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -533,4 +556,92 @@ describe('RefundItemsPage', () => {
|
|||||||
|
|
||||||
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('天猫')
|
||||||
|
expect(html).not.toContain('平台 #1')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders copy buttons before text in platform_refund_id column', async () => {
|
||||||
|
await mountPage()
|
||||||
|
|
||||||
|
const cells = wrapper.findAll('td')
|
||||||
|
const refundIdCell = cells.find((c) => c.text().includes('RF-20260101-001'))
|
||||||
|
expect(refundIdCell).toBeDefined()
|
||||||
|
|
||||||
|
const cellHtml = refundIdCell!.html()
|
||||||
|
const copyPos = cellHtml.indexOf('copy-icon-stub')
|
||||||
|
const textPos = cellHtml.indexOf('RF-20260101-001')
|
||||||
|
expect(copyPos).toBeLessThan(textPos)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders copy button for platform_sub_order_id when value exists', async () => {
|
||||||
|
await mountPage()
|
||||||
|
|
||||||
|
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 platform_sub_order_id when value is null', async () => {
|
||||||
|
await mountPage()
|
||||||
|
|
||||||
|
const cells = wrapper.findAll('td')
|
||||||
|
const dashCells = cells.filter((c) => c.text().trim() === '-')
|
||||||
|
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' },
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('handleGoToRefund passes platform_refund_id and auto_submit', async () => {
|
||||||
|
await mountPage()
|
||||||
|
|
||||||
|
// refund_id column renders as clickable link
|
||||||
|
const links = wrapper.findAll('a')
|
||||||
|
const refundLink = links.find((l) => l.text().trim() === '201')
|
||||||
|
expect(refundLink).toBeDefined()
|
||||||
|
|
||||||
|
await refundLink!.trigger('click')
|
||||||
|
await flushPromises()
|
||||||
|
|
||||||
|
expect(mockRouterPush).toHaveBeenCalledWith({
|
||||||
|
path: '/refunds',
|
||||||
|
query: { platform_refund_id: 'RF-20260101-001', auto_submit: '1' },
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -45,8 +45,8 @@ const columns = [
|
|||||||
{ title: '退款单ID', key: 'refund_id', width: 100 },
|
{ title: '退款单ID', key: 'refund_id', width: 100 },
|
||||||
{ title: '平台退款ID', key: 'platform_refund_id', width: 160, ellipsis: true },
|
{ title: '平台退款ID', key: 'platform_refund_id', width: 160, ellipsis: true },
|
||||||
{ title: '关联订单ID', key: 'platform_order_id', width: 160, ellipsis: true },
|
{ title: '关联订单ID', key: 'platform_order_id', width: 160, ellipsis: true },
|
||||||
{ title: '子订单ID', dataIndex: 'platform_sub_order_id', width: 140, ellipsis: true },
|
{ title: '子订单ID', key: 'platform_sub_order_id', width: 140, ellipsis: true },
|
||||||
{ title: '平台商品ID', dataIndex: 'platform_product_id', width: 140, ellipsis: true },
|
{ title: '平台商品ID', key: 'platform_product_id', width: 140, ellipsis: true },
|
||||||
{ title: '退款状态', key: 'refund_status', width: 110 },
|
{ title: '退款状态', key: 'refund_status', width: 110 },
|
||||||
{ title: '退款类型', key: 'refund_type', width: 130 },
|
{ title: '退款类型', key: 'refund_type', width: 130 },
|
||||||
{ title: '数量', dataIndex: 'quantity', width: 80 },
|
{ title: '数量', dataIndex: 'quantity', width: 80 },
|
||||||
@@ -103,7 +103,7 @@ function formatTime(time: string | null) {
|
|||||||
return time.replace('T', ' ').substring(0, 16)
|
return time.replace('T', ' ').substring(0, 16)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleCopyId(text: string, label: string) {
|
async function handleCopy(text: string, label: string) {
|
||||||
try {
|
try {
|
||||||
await navigator.clipboard.writeText(text)
|
await navigator.clipboard.writeText(text)
|
||||||
message.success(`已复制${label}`)
|
message.success(`已复制${label}`)
|
||||||
@@ -113,11 +113,17 @@ async function handleCopyId(text: string, label: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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' },
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleGoToRefund(refundId: number) {
|
function handleGoToRefund(platformRefundId: string) {
|
||||||
router.push({ path: '/refunds', query: { id: String(refundId) } })
|
router.push({
|
||||||
|
path: '/refunds',
|
||||||
|
query: { platform_refund_id: platformRefundId, auto_submit: '1' },
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleViewDetail(record: { id: number }) {
|
async function handleViewDetail(record: { id: number }) {
|
||||||
@@ -147,7 +153,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>
|
||||||
@@ -260,29 +266,50 @@ async function handleViewDetail(record: { id: number }) {
|
|||||||
<template v-else-if="column.key === 'refund_id'">
|
<template v-else-if="column.key === 'refund_id'">
|
||||||
<a
|
<a
|
||||||
class="text-blue-500 cursor-pointer"
|
class="text-blue-500 cursor-pointer"
|
||||||
@click="handleGoToRefund(record.refund_id)"
|
@click="handleGoToRefund(record.platform_refund_id)"
|
||||||
>
|
>
|
||||||
{{ record.refund_id }}
|
{{ record.refund_id }}
|
||||||
</a>
|
</a>
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="column.key === 'platform_refund_id'">
|
<template v-else-if="column.key === 'platform_refund_id'">
|
||||||
{{ record.platform_refund_id }}
|
<span class="inline-flex items-center gap-1">
|
||||||
<CopyOutlined
|
<CopyOutlined
|
||||||
class="ml-1 cursor-pointer text-gray-400 hover:text-blue-500"
|
class="flex-shrink-0 cursor-pointer text-gray-400 hover:text-blue-500"
|
||||||
@click.stop="handleCopyId(record.platform_refund_id, '平台退款ID')"
|
@click.stop="handleCopy(record.platform_refund_id, '平台退款ID')"
|
||||||
/>
|
/>
|
||||||
|
<span class="truncate">{{ record.platform_refund_id }}</span>
|
||||||
|
</span>
|
||||||
</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="handleCopyId(record.platform_order_id, '平台订单ID')"
|
</a>
|
||||||
/>
|
</span>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="column.key === 'platform_sub_order_id'">
|
||||||
|
<span v-if="record.platform_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.platform_sub_order_id, '子订单ID')"
|
||||||
|
/>
|
||||||
|
<span class="truncate">{{ record.platform_sub_order_id }}</span>
|
||||||
|
</span>
|
||||||
|
<span v-else>-</span>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="column.key === '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 === 'refund_status'">
|
<template v-else-if="column.key === 'refund_status'">
|
||||||
<a-tag
|
<a-tag
|
||||||
@@ -351,7 +378,7 @@ async function handleViewDetail(record: { id: number }) {
|
|||||||
<a-descriptions-item label="退款单ID">
|
<a-descriptions-item label="退款单ID">
|
||||||
<a
|
<a
|
||||||
class="text-blue-500 cursor-pointer"
|
class="text-blue-500 cursor-pointer"
|
||||||
@click="handleGoToRefund(refundItemDetail.refund_id)"
|
@click="handleGoToRefund(refundItemDetail.platform_refund_id)"
|
||||||
>
|
>
|
||||||
{{ refundItemDetail.refund_id }}
|
{{ refundItemDetail.refund_id }}
|
||||||
</a>
|
</a>
|
||||||
@@ -360,7 +387,7 @@ async function handleViewDetail(record: { id: number }) {
|
|||||||
{{ refundItemDetail.platform_refund_id }}
|
{{ refundItemDetail.platform_refund_id }}
|
||||||
<CopyOutlined
|
<CopyOutlined
|
||||||
class="ml-2 cursor-pointer text-blue-500"
|
class="ml-2 cursor-pointer text-blue-500"
|
||||||
@click="handleCopyId(refundItemDetail.platform_refund_id, '平台退款ID')"
|
@click="handleCopy(refundItemDetail.platform_refund_id, '平台退款ID')"
|
||||||
/>
|
/>
|
||||||
</a-descriptions-item>
|
</a-descriptions-item>
|
||||||
<a-descriptions-item label="父退款ID">
|
<a-descriptions-item label="父退款ID">
|
||||||
@@ -375,7 +402,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="handleCopyId(refundItemDetail.platform_order_id, '平台订单ID')"
|
@click="handleCopy(refundItemDetail.platform_order_id, '平台订单ID')"
|
||||||
/>
|
/>
|
||||||
</a-descriptions-item>
|
</a-descriptions-item>
|
||||||
<a-descriptions-item label="子订单ID">
|
<a-descriptions-item label="子订单ID">
|
||||||
|
|||||||
@@ -35,24 +35,24 @@ export const useRefundItemStore = defineStore('refundItem', () => {
|
|||||||
|
|
||||||
// 名称映射数据
|
// 名称映射数据
|
||||||
const companies = ref<LookupItem[]>([])
|
const companies = ref<LookupItem[]>([])
|
||||||
const platforms = ref<{ id: number; developer_id: number }[]>([])
|
const platforms = ref<{ id: number; name: string; label?: string; developer_id: number }[]>([])
|
||||||
const stores = ref<(LookupItem & { company_id: number; platform_id: number })[]>([])
|
const stores = ref<(LookupItem & { company_id: number; platform_id: number })[]>([])
|
||||||
|
|
||||||
const companyMap = computed(
|
const companyMap = computed(
|
||||||
() => new Map(companies.value.map((c) => [c.id, c.label || c.name])),
|
() => new Map(companies.value.map((c) => [c.id, (c.label && c.label !== 'null') ? c.label : c.name])),
|
||||||
)
|
)
|
||||||
const platformMap = computed(
|
const platformMap = computed(
|
||||||
() => new Map(platforms.value.map((p) => [p.id, `平台 #${p.id}`])),
|
() => new Map(platforms.value.map((p) => [p.id, (p.label && p.label !== 'null') ? p.label : (p.name || `平台 #${p.id}`)])),
|
||||||
)
|
)
|
||||||
const storeMap = computed(
|
const storeMap = computed(
|
||||||
() => new Map(stores.value.map((s) => [s.id, s.label || s.name])),
|
() => new Map(stores.value.map((s) => [s.id, (s.label && s.label !== 'null') ? s.label : s.name])),
|
||||||
)
|
)
|
||||||
|
|
||||||
async function loadLookups() {
|
async function loadLookups() {
|
||||||
try {
|
try {
|
||||||
const [c, p, s] = await Promise.all([
|
const [c, p, s] = await Promise.all([
|
||||||
api.get<LookupItem[]>('/api/v1/companies'),
|
api.get<LookupItem[]>('/api/v1/companies'),
|
||||||
api.get<{ id: number; developer_id: number }[]>('/api/v1/platforms'),
|
api.get<{ id: number; name: string; label?: string; developer_id: number }[]>('/api/v1/platforms'),
|
||||||
api.get<(LookupItem & { company_id: number; platform_id: number })[]>(
|
api.get<(LookupItem & { company_id: number; platform_id: number })[]>(
|
||||||
'/api/v1/stores',
|
'/api/v1/stores',
|
||||||
),
|
),
|
||||||
|
|||||||
Reference in New Issue
Block a user