verify product list
This commit is contained in:
@@ -224,6 +224,42 @@ describe('useProductStore', () => {
|
||||
expect(store.platformMap.get(1)).toBe('平台 #1')
|
||||
expect(store.storeMap.get(1)).toBe('店铺1')
|
||||
})
|
||||
|
||||
it('handles loadLookups failure gracefully', async () => {
|
||||
const { api } = await import('@/utils/request')
|
||||
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
|
||||
vi.mocked(api.get).mockRejectedValueOnce(new Error('Network error'))
|
||||
|
||||
const store = useProductStore()
|
||||
await store.loadLookups()
|
||||
|
||||
expect(warnSpy).toHaveBeenCalledWith('加载查找表数据失败', expect.any(Error))
|
||||
expect(store.companyMap.size).toBe(0)
|
||||
expect(store.platformMap.size).toBe(0)
|
||||
expect(store.storeMap.size).toBe(0)
|
||||
warnSpy.mockRestore()
|
||||
})
|
||||
})
|
||||
|
||||
describe('fetchProducts error handling', () => {
|
||||
it('clears products on fetch failure', async () => {
|
||||
const { api } = await import('@/utils/request')
|
||||
const { message } = await import('ant-design-vue')
|
||||
vi.spyOn(message, 'error')
|
||||
|
||||
const store = useProductStore()
|
||||
// 先加载一些数据
|
||||
vi.mocked(api.get).mockResolvedValueOnce(mockPaginatedResponse)
|
||||
await store.fetchProducts()
|
||||
expect(store.products.length).toBe(2)
|
||||
|
||||
// 再让请求失败
|
||||
vi.mocked(api.get).mockRejectedValueOnce(new Error('Server error'))
|
||||
await store.fetchProducts()
|
||||
|
||||
expect(store.products).toEqual([])
|
||||
expect(store.pagination.total).toBe(0)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ const drawerVisible = ref(false)
|
||||
const drawerLoading = ref(false)
|
||||
const productDetail = ref<ProductDetail | null>(null)
|
||||
const rawDetail = ref<RawProductDetail | null>(null)
|
||||
let detailRequestId = 0
|
||||
|
||||
const statusMap: Record<number, { text: string; color: string }> = {
|
||||
1: { text: '上架', color: 'green' },
|
||||
@@ -57,6 +58,7 @@ function handlePageChange(page: number, pageSize: number) {
|
||||
}
|
||||
|
||||
function formatPrice(record: { price: string; currency: string }) {
|
||||
if (!record.price || isNaN(Number(record.price))) return '-'
|
||||
const symbols: Record<string, string> = {
|
||||
CNY: '¥',
|
||||
USD: '$',
|
||||
@@ -68,12 +70,23 @@ function formatPrice(record: { price: string; currency: string }) {
|
||||
return `${symbol}${record.price}`
|
||||
}
|
||||
|
||||
function isSafeUrl(url: string | null): boolean {
|
||||
if (!url) return false
|
||||
try {
|
||||
const parsed = new URL(url)
|
||||
return ['http:', 'https:'].includes(parsed.protocol)
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
function formatTime(time: string | null) {
|
||||
if (!time) return '-'
|
||||
return time.replace('T', ' ').substring(0, 16)
|
||||
}
|
||||
|
||||
async function handleViewDetail(record: { id: number }) {
|
||||
const currentRequestId = ++detailRequestId
|
||||
drawerVisible.value = true
|
||||
drawerLoading.value = true
|
||||
productDetail.value = null
|
||||
@@ -84,6 +97,9 @@ async function handleViewDetail(record: { id: number }) {
|
||||
api.get<RawProductDetail>(`/api/v1/raw/products/${record.id}`),
|
||||
])
|
||||
|
||||
// 竞态保护:若用户已切换到另一商品详情,丢弃过期响应
|
||||
if (currentRequestId !== detailRequestId) return
|
||||
|
||||
if (normalResult.status === 'fulfilled') {
|
||||
productDetail.value = normalResult.value
|
||||
} else {
|
||||
@@ -92,6 +108,8 @@ async function handleViewDetail(record: { id: number }) {
|
||||
|
||||
if (rawResult.status === 'fulfilled') {
|
||||
rawDetail.value = rawResult.value
|
||||
} else {
|
||||
console.warn('加载原始数据(raw)失败', rawResult.reason)
|
||||
}
|
||||
|
||||
drawerLoading.value = false
|
||||
@@ -260,10 +278,10 @@ async function handleViewDetail(record: { id: number }) {
|
||||
{{ productDetail.hscode || '-' }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="商品链接" :span="2">
|
||||
<a v-if="productDetail.url" :href="productDetail.url" target="_blank">
|
||||
<a v-if="isSafeUrl(productDetail.url)" :href="productDetail.url!" target="_blank" rel="noopener noreferrer">
|
||||
{{ productDetail.url }}
|
||||
</a>
|
||||
<span v-else>-</span>
|
||||
<span v-else>{{ productDetail.url || '-' }}</span>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="平台创建时间">
|
||||
{{ formatTime(productDetail.created_date) }}
|
||||
|
||||
@@ -126,6 +126,8 @@ export const useProductStore = defineStore('product', () => {
|
||||
pagination.total = data.total
|
||||
pagination.page = data.page
|
||||
} catch (err: unknown) {
|
||||
products.value = []
|
||||
pagination.total = 0
|
||||
const msg = err instanceof Error ? err.message : '获取产品列表失败'
|
||||
message.error(msg)
|
||||
} finally {
|
||||
|
||||
Reference in New Issue
Block a user