update product list page
This commit is contained in:
@@ -49,7 +49,7 @@ const mockProducts: ProductRecord[] = [
|
|||||||
company_id: 2,
|
company_id: 2,
|
||||||
platform_id: 1,
|
platform_id: 1,
|
||||||
store_id: 2,
|
store_id: 2,
|
||||||
status_id: 0,
|
status_id: 3,
|
||||||
platform_item_id: 'TB002',
|
platform_item_id: 'TB002',
|
||||||
platform_model_id: null,
|
platform_model_id: null,
|
||||||
name: '测试商品B - 这是一个非常长的商品名称用于测试省略号效果',
|
name: '测试商品B - 这是一个非常长的商品名称用于测试省略号效果',
|
||||||
@@ -340,7 +340,8 @@ describe('ProductsPage', () => {
|
|||||||
|
|
||||||
const tags = wrapper.findAll('.ant-tag')
|
const tags = wrapper.findAll('.ant-tag')
|
||||||
const tagTexts = tags.map((t) => t.text())
|
const tagTexts = tags.map((t) => t.text())
|
||||||
expect(tagTexts).toContain('上架')
|
// mockProducts: status_id=1 → 草稿, status_id=3 → 下架
|
||||||
|
expect(tagTexts).toContain('草稿')
|
||||||
expect(tagTexts).toContain('下架')
|
expect(tagTexts).toContain('下架')
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -451,6 +452,106 @@ describe('ProductsPage', () => {
|
|||||||
expect(drawerHtml).toContain('tb_001')
|
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 () => {
|
it('shows placeholder when ext/raw is null', async () => {
|
||||||
const { api } = await mountPage()
|
const { api } = await mountPage()
|
||||||
vi.mocked(api.get)
|
vi.mocked(api.get)
|
||||||
|
|||||||
@@ -19,8 +19,13 @@ const rawDetail = ref<RawProductDetail | null>(null)
|
|||||||
let detailRequestId = 0
|
let detailRequestId = 0
|
||||||
|
|
||||||
const statusMap: Record<number, { text: string; color: string }> = {
|
const statusMap: Record<number, { text: string; color: string }> = {
|
||||||
1: { text: '上架', color: 'green' },
|
1: { text: '草稿', color: 'default' },
|
||||||
0: { text: '下架', color: 'red' },
|
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 = [
|
const columns = [
|
||||||
@@ -133,8 +138,13 @@ async function handleViewDetail(record: { id: number }) {
|
|||||||
allow-clear
|
allow-clear
|
||||||
style="width: 120px"
|
style="width: 120px"
|
||||||
>
|
>
|
||||||
<a-select-option :value="1">上架</a-select-option>
|
<a-select-option :value="1">草稿</a-select-option>
|
||||||
<a-select-option :value="0">下架</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-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="商品名称">
|
<a-form-item label="商品名称">
|
||||||
|
|||||||
Reference in New Issue
Block a user