task-118(任务与重复分析中心): 实现重复检查详情抽屉
duplicate-model.ts 增加 DuplicateDetail 解析与详情分页负载解析,duplicate-api.ts 增加 fetchDuplicateDetail(GET /duplicate-check-detail)。 TDD: task-118.test.ts 8 用例先 RED 后 GREEN。
This commit is contained in:
@@ -1,6 +1,11 @@
|
||||
/** 店铺数据重复检查适配(任务 115-119):/api/admin/shop-data-crawl/duplicate-check-* 系列端点。 */
|
||||
import { http } from '@/api/http'
|
||||
import { parseDuplicateItemsPage, parseDuplicateOverview, type DuplicateOverview } from './duplicate-model.ts'
|
||||
import {
|
||||
parseDuplicateDetailPage,
|
||||
parseDuplicateItemsPage,
|
||||
parseDuplicateOverview,
|
||||
type DuplicateOverview,
|
||||
} from './duplicate-model.ts'
|
||||
import { toDuplicateItemsQuery, type DuplicateFilter } from './duplicate-filter.ts'
|
||||
|
||||
export const DUPLICATE_CHECK_ENDPOINT = '/api/admin/shop-data-crawl'
|
||||
@@ -24,3 +29,15 @@ export async function fetchDuplicateItems(
|
||||
})
|
||||
return parseDuplicateItemsPage(data)
|
||||
}
|
||||
|
||||
/** 加载撞款详情卡片分页:GET /duplicate-check-detail。 */
|
||||
export async function fetchDuplicateDetail(
|
||||
filter: DuplicateFilter,
|
||||
page: number,
|
||||
pageSize: number,
|
||||
): Promise<ReturnType<typeof parseDuplicateDetailPage>> {
|
||||
const { data } = await http.get<unknown>(`${DUPLICATE_CHECK_ENDPOINT}/duplicate-check-detail`, {
|
||||
params: toDuplicateItemsQuery(filter, page, pageSize),
|
||||
})
|
||||
return parseDuplicateDetailPage(data)
|
||||
}
|
||||
|
||||
@@ -139,6 +139,60 @@ function parseDuplicateMetrics(raw: unknown): DuplicateMetrics | null {
|
||||
}
|
||||
}
|
||||
|
||||
/** 撞款详情卡(抽屉):跨店聚合一张卡片。 */
|
||||
export interface DuplicateDetail {
|
||||
asin: string
|
||||
shopCount: number
|
||||
recordCount: number
|
||||
shopNames: string[]
|
||||
brand: string
|
||||
firstDate: string
|
||||
occurrences: DuplicateOccurrence[]
|
||||
}
|
||||
|
||||
/** 解析单张撞款详情卡;缺 asin 视为无效。 */
|
||||
export function parseDuplicateDetail(raw: unknown): DuplicateDetail | null {
|
||||
if (!raw || typeof raw !== 'object') return null
|
||||
const r = raw as Record<string, unknown>
|
||||
const asin = text(r.asin)
|
||||
if (!asin) return null
|
||||
const shopNamesRaw = r.shop_names ?? r.shopNames
|
||||
const shopNames = Array.isArray(shopNamesRaw)
|
||||
? (shopNamesRaw as unknown[]).map((name) => text(name)).filter(Boolean)
|
||||
: []
|
||||
return {
|
||||
asin,
|
||||
shopCount: count(r.shop_count ?? r.shopCount),
|
||||
recordCount: count(r.record_count ?? r.recordCount),
|
||||
shopNames,
|
||||
brand: text(r.brand),
|
||||
firstDate: text(r.first_date ?? r.firstDate),
|
||||
occurrences: Array.isArray(r.occurrences)
|
||||
? r.occurrences.map((occ) => parseDuplicateOccurrence(occ)).filter((occ): occ is DuplicateOccurrence => occ !== null)
|
||||
: [],
|
||||
}
|
||||
}
|
||||
|
||||
/** 详情分页负载(data: {pending,items,total,page,page_size}) → 前端结果。 */
|
||||
export function parseDuplicateDetailPage(payload: unknown): {
|
||||
pending: boolean
|
||||
items: DuplicateDetail[]
|
||||
total: number
|
||||
page: number
|
||||
pageSize: number
|
||||
} {
|
||||
const core = unwrap<unknown>(payload)
|
||||
const record = core && typeof core === 'object' ? (core as Record<string, unknown>) : {}
|
||||
const items = Array.isArray(record.items)
|
||||
? record.items.map((raw) => parseDuplicateDetail(raw)).filter((detail): detail is DuplicateDetail => detail !== null)
|
||||
: []
|
||||
const total = count(record.total)
|
||||
const pageNumber = count(record.page) >= 1 ? count(record.page) : 1
|
||||
const rawSize = record.page_size ?? record.pageSize
|
||||
const pageSize = count(rawSize) >= 1 ? count(rawSize) : 20
|
||||
return { pending: record.pending === true, items, total, page: pageNumber, pageSize }
|
||||
}
|
||||
|
||||
/** 明细列表分页负载(data: {pending,items,total,page,page_size}) → 前端结果。 */
|
||||
export function parseDuplicateItemsPage(payload: unknown): {
|
||||
pending: boolean
|
||||
|
||||
Reference in New Issue
Block a user