verify product list
This commit is contained in:
@@ -224,6 +224,42 @@ describe('useProductStore', () => {
|
|||||||
expect(store.platformMap.get(1)).toBe('平台 #1')
|
expect(store.platformMap.get(1)).toBe('平台 #1')
|
||||||
expect(store.storeMap.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 drawerLoading = ref(false)
|
||||||
const productDetail = ref<ProductDetail | null>(null)
|
const productDetail = ref<ProductDetail | null>(null)
|
||||||
const rawDetail = ref<RawProductDetail | null>(null)
|
const rawDetail = ref<RawProductDetail | null>(null)
|
||||||
|
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: 'green' },
|
||||||
@@ -57,6 +58,7 @@ function handlePageChange(page: number, pageSize: number) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function formatPrice(record: { price: string; currency: string }) {
|
function formatPrice(record: { price: string; currency: string }) {
|
||||||
|
if (!record.price || isNaN(Number(record.price))) return '-'
|
||||||
const symbols: Record<string, string> = {
|
const symbols: Record<string, string> = {
|
||||||
CNY: '¥',
|
CNY: '¥',
|
||||||
USD: '$',
|
USD: '$',
|
||||||
@@ -68,12 +70,23 @@ function formatPrice(record: { price: string; currency: string }) {
|
|||||||
return `${symbol}${record.price}`
|
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) {
|
function formatTime(time: string | null) {
|
||||||
if (!time) return '-'
|
if (!time) return '-'
|
||||||
return time.replace('T', ' ').substring(0, 16)
|
return time.replace('T', ' ').substring(0, 16)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleViewDetail(record: { id: number }) {
|
async function handleViewDetail(record: { id: number }) {
|
||||||
|
const currentRequestId = ++detailRequestId
|
||||||
drawerVisible.value = true
|
drawerVisible.value = true
|
||||||
drawerLoading.value = true
|
drawerLoading.value = true
|
||||||
productDetail.value = null
|
productDetail.value = null
|
||||||
@@ -84,6 +97,9 @@ async function handleViewDetail(record: { id: number }) {
|
|||||||
api.get<RawProductDetail>(`/api/v1/raw/products/${record.id}`),
|
api.get<RawProductDetail>(`/api/v1/raw/products/${record.id}`),
|
||||||
])
|
])
|
||||||
|
|
||||||
|
// 竞态保护:若用户已切换到另一商品详情,丢弃过期响应
|
||||||
|
if (currentRequestId !== detailRequestId) return
|
||||||
|
|
||||||
if (normalResult.status === 'fulfilled') {
|
if (normalResult.status === 'fulfilled') {
|
||||||
productDetail.value = normalResult.value
|
productDetail.value = normalResult.value
|
||||||
} else {
|
} else {
|
||||||
@@ -92,6 +108,8 @@ async function handleViewDetail(record: { id: number }) {
|
|||||||
|
|
||||||
if (rawResult.status === 'fulfilled') {
|
if (rawResult.status === 'fulfilled') {
|
||||||
rawDetail.value = rawResult.value
|
rawDetail.value = rawResult.value
|
||||||
|
} else {
|
||||||
|
console.warn('加载原始数据(raw)失败', rawResult.reason)
|
||||||
}
|
}
|
||||||
|
|
||||||
drawerLoading.value = false
|
drawerLoading.value = false
|
||||||
@@ -260,10 +278,10 @@ async function handleViewDetail(record: { id: number }) {
|
|||||||
{{ productDetail.hscode || '-' }}
|
{{ productDetail.hscode || '-' }}
|
||||||
</a-descriptions-item>
|
</a-descriptions-item>
|
||||||
<a-descriptions-item label="商品链接" :span="2">
|
<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 }}
|
{{ productDetail.url }}
|
||||||
</a>
|
</a>
|
||||||
<span v-else>-</span>
|
<span v-else>{{ productDetail.url || '-' }}</span>
|
||||||
</a-descriptions-item>
|
</a-descriptions-item>
|
||||||
<a-descriptions-item label="平台创建时间">
|
<a-descriptions-item label="平台创建时间">
|
||||||
{{ formatTime(productDetail.created_date) }}
|
{{ formatTime(productDetail.created_date) }}
|
||||||
|
|||||||
@@ -126,6 +126,8 @@ export const useProductStore = defineStore('product', () => {
|
|||||||
pagination.total = data.total
|
pagination.total = data.total
|
||||||
pagination.page = data.page
|
pagination.page = data.page
|
||||||
} catch (err: unknown) {
|
} catch (err: unknown) {
|
||||||
|
products.value = []
|
||||||
|
pagination.total = 0
|
||||||
const msg = err instanceof Error ? err.message : '获取产品列表失败'
|
const msg = err instanceof Error ? err.message : '获取产品列表失败'
|
||||||
message.error(msg)
|
message.error(msg)
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
Reference in New Issue
Block a user