update product list page

This commit is contained in:
2026-04-01 16:15:21 +08:00
parent 8438c31ee8
commit 9a8431de81
2 changed files with 117 additions and 6 deletions
@@ -49,7 +49,7 @@ const mockProducts: ProductRecord[] = [
company_id: 2,
platform_id: 1,
store_id: 2,
status_id: 0,
status_id: 3,
platform_item_id: 'TB002',
platform_model_id: null,
name: '测试商品B - 这是一个非常长的商品名称用于测试省略号效果',
@@ -340,7 +340,8 @@ describe('ProductsPage', () => {
const tags = wrapper.findAll('.ant-tag')
const tagTexts = tags.map((t) => t.text())
expect(tagTexts).toContain('上架')
// mockProducts: status_id=1 → 草稿, status_id=3 → 下架
expect(tagTexts).toContain('草稿')
expect(tagTexts).toContain('下架')
})
@@ -451,6 +452,106 @@ describe('ProductsPage', () => {
expect(drawerHtml).toContain('tb_001')
})
describe('status mapping coverage', () => {
const statusExpectations: Array<{ id: number; text: string; color: string }> = [
{ id: 1, text: '草稿', color: 'default' },
{ id: 2, text: '在售', color: 'green' },
{ id: 3, text: '下架', color: 'orange' },
{ id: 4, text: '封禁', color: 'red' },
{ id: 5, text: '审核中', color: 'blue' },
{ id: 6, text: '卖家删除', color: 'volcano' },
{ id: 7, text: '平台删除', color: 'volcano' },
]
it.each(statusExpectations)(
'renders status_id=$id as "$text"',
async ({ id, text }) => {
const { api } = await import('@/utils/request')
const singleProduct = { ...mockProducts[0], id: 100 + id, status_id: id }
vi.mocked(api.get).mockImplementation((url: string) => {
if (url === '/api/v1/products')
return Promise.resolve({ items: [singleProduct], total: 1, page: 1, per_page: 15 }) as never
if (url === '/api/v1/companies') return Promise.resolve(mockLookupCompanies) as never
if (url === '/api/v1/platforms') return Promise.resolve(mockLookupPlatforms) as never
if (url === '/api/v1/stores') return Promise.resolve(mockLookupStores) as never
return Promise.resolve([]) as never
})
const { default: ProductsPage } = await import('../index.vue')
wrapper = mount(ProductsPage, {
attachTo: document.body,
global: {
stubs: {
SearchOutlined: { template: '<span />' },
ReloadOutlined: { template: '<span />' },
EyeOutlined: { template: '<span />' },
CascadeFilter: { template: '<div class="cascade-filter-stub" />' },
},
},
})
await flushPromises()
await nextTick()
const tags = wrapper.findAll('.ant-tag')
const tagTexts = tags.map((t) => t.text())
expect(tagTexts).toContain(text)
},
)
it('renders fallback for unknown status_id', async () => {
const { api } = await import('@/utils/request')
const unknownProduct = { ...mockProducts[0], id: 999, status_id: 99 }
vi.mocked(api.get).mockImplementation((url: string) => {
if (url === '/api/v1/products')
return Promise.resolve({ items: [unknownProduct], total: 1, page: 1, per_page: 15 }) as never
if (url === '/api/v1/companies') return Promise.resolve(mockLookupCompanies) as never
if (url === '/api/v1/platforms') return Promise.resolve(mockLookupPlatforms) as never
if (url === '/api/v1/stores') return Promise.resolve(mockLookupStores) as never
return Promise.resolve([]) as never
})
const { default: ProductsPage } = await import('../index.vue')
wrapper = mount(ProductsPage, {
attachTo: document.body,
global: {
stubs: {
SearchOutlined: { template: '<span />' },
ReloadOutlined: { template: '<span />' },
EyeOutlined: { template: '<span />' },
CascadeFilter: { template: '<div class="cascade-filter-stub" />' },
},
},
})
await flushPromises()
await nextTick()
const tags = wrapper.findAll('.ant-tag')
const tagTexts = tags.map((t) => t.text())
expect(tagTexts).toContain('状态 99')
})
})
it('renders all 7 status filter options', async () => {
await mountPage()
// Ant Design Vue 渲染 select options 到 body portal,需先打开下拉
const selectEl = document.body.querySelector('.ant-select-selector')
selectEl?.dispatchEvent(new MouseEvent('mousedown', { bubbles: true }))
await flushPromises()
await nextTick()
const optionEls = document.body.querySelectorAll('.ant-select-item-option')
const optionTexts = Array.from(optionEls).map((el) => el.textContent?.trim())
expect(optionTexts).toContain('草稿')
expect(optionTexts).toContain('在售')
expect(optionTexts).toContain('下架')
expect(optionTexts).toContain('封禁')
expect(optionTexts).toContain('审核中')
expect(optionTexts).toContain('卖家删除')
expect(optionTexts).toContain('平台删除')
expect(optionTexts.length).toBe(7)
})
it('shows placeholder when ext/raw is null', async () => {
const { api } = await mountPage()
vi.mocked(api.get)
+14 -4
View File
@@ -19,8 +19,13 @@ const rawDetail = ref<RawProductDetail | null>(null)
let detailRequestId = 0
const statusMap: Record<number, { text: string; color: string }> = {
1: { text: '上架', color: 'green' },
0: { text: '下架', color: 'red' },
1: { text: '草稿', color: 'default' },
2: { text: '在售', color: 'green' },
3: { text: '下架', color: 'orange' },
4: { text: '封禁', color: 'red' },
5: { text: '审核中', color: 'blue' },
6: { text: '卖家删除', color: 'volcano' },
7: { text: '平台删除', color: 'volcano' },
}
const columns = [
@@ -133,8 +138,13 @@ async function handleViewDetail(record: { id: number }) {
allow-clear
style="width: 120px"
>
<a-select-option :value="1">上架</a-select-option>
<a-select-option :value="0">下架</a-select-option>
<a-select-option :value="1">草稿</a-select-option>
<a-select-option :value="2">在售</a-select-option>
<a-select-option :value="3">下架</a-select-option>
<a-select-option :value="4">封禁</a-select-option>
<a-select-option :value="5">审核中</a-select-option>
<a-select-option :value="6">卖家删除</a-select-option>
<a-select-option :value="7">平台删除</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="商品名称">