feat(pickup): 店铺管理支持历史店铺可视化手动映射与一键批量清洗

- 数据库升级:新增 pickup_shop_mappings 映射别名表,并在 admin_pickups 增加 raw_shop_name 永久保留原始店铺录入值
- 后端映射能力:提供历史店铺聚合统计、单条/批量映射至目标标准店铺、映射规则撤销与提号单还原接口;新建提号时若录入已映射别名自动归一化
- 前端管理升级:PickupShopDialog 升级为双 Tab 结构,Tab 2「历史店铺映射与清洗」支持总览统计卡片、状态筛选、单条/批量映射清洗、一键设为标准店铺及撤销还原
- 验证方式:执行并通过全套代码门禁(scripts/check.sh:SQL约束、go vet/build/test、MySQL测试库覆盖率、vue大小、eslint、prettier、vue-tsc及vite构建)
This commit is contained in:
yml2213
2026-10-09 13:31:11 +08:00
parent 9d9adfbd5e
commit f3c5235b81
12 changed files with 1187 additions and 100 deletions
@@ -23,6 +23,7 @@ export interface AdminPickup {
platform: string
third_party_order_no: string
shop_name: string
raw_shop_name?: string
account_source: PickupAccountSource
source_channel: string
settlement_mode: PickupSettlementMode
@@ -194,6 +195,41 @@ export interface PickupShopQuery {
status?: string
}
export type PickupShopMappingStatus = 'standard' | 'mapped' | 'unmapped'
export interface PickupShopHistoricalItem {
source_name: string
count: number
mapping_status: PickupShopMappingStatus
target_shop_id?: number
target_shop_name?: string
remark?: string
}
export interface PickupShopHistoricalStats {
items: PickupShopHistoricalItem[]
total_pickups: number
total_shops: number
standard_count: number
mapped_count: number
unmapped_count: number
}
export interface MapHistoricalShopsRequest {
from_shop_names: string[]
target_shop_name: string
create_target_missing?: boolean
target_platform?: string
remark?: string
}
export interface MapHistoricalShopsResult {
updated_pickups_count: number
target_shop_name: string
target_shop_id: number
created_standard_shop: boolean
}
function cleanParams(query: object) {
return Object.fromEntries(
Object.entries(query).filter(([, value]) => value !== '' && value !== undefined)
@@ -339,3 +375,26 @@ export async function deletePickupShop(id: number) {
)
return data.data
}
export async function fetchHistoricalShopStats() {
const { data } = await apiClient.get<ApiResponse<PickupShopHistoricalStats>>(
'/admin/pickups/shops/historical-stats'
)
return data.data
}
export async function mapHistoricalShops(req: MapHistoricalShopsRequest) {
const { data } = await apiClient.post<ApiResponse<MapHistoricalShopsResult>>(
'/admin/pickups/shops/map-historical',
req
)
return data.data
}
export async function deleteShopMapping(sourceName: string) {
const { data } = await apiClient.delete<ApiResponse<{ deleted: boolean }>>(
'/admin/pickups/shops/mappings',
{ params: { source_name: sourceName } }
)
return data.data
}