import type { AppAddressDefaultSetResponse, AppAddressListResponse, AppAddressSaveResponse, AppAfterSaleApplyResponse, AppCartListResponse, AppCartSaveResponse, AppGoodsDetailResponse, AppGoodsPageResponse, AppHomeMerchantGoodsResponse, AppHomeResponse, AppRecentNewGoodsResponse, AppOrderDetailResponse, AppOrderConfirmResponse, AppOrderPageResponse, AppTradeCreateResponse, CartItemInfo, CartGoodsGroupInfo, CartMerchantGroupInfo, GoodsDetailInfo, GoodsSkuInfo, GoodsSummary, AppTradePreviewResponse, HomeMerchantGoodsBlock, MerchantDeliveryOptionInfo, MerchantId, MerchantInfo, MerchantPackageOptionInfo, OrderDetailInfo, OrderSummary, RecommendMerchantInfo, RecentNewGoodsItem, TradePreviewInfo, TradePreviewItemInfo, TradePreviewStoreInfo, } from '@/types/mall' type MaybeMallIdentityPayload = { uid?: number memberId?: MerchantId | number } type MaybeMallMerchantPayload = { merchantId?: MerchantId | number storeId?: number | string } type MaybeMallIdPayload = { id?: string | number skuId?: string | number skuIds?: Array addressId?: string | number orderId?: string | number orderItemId?: string | number categoryId?: string | number buyItems?: Array<{ skuId?: string | number; buyNum?: number }> merchantOptions?: Array<{ merchantId?: MerchantId | number deliveryOptionId?: string | number packageOptionId?: string | number }> } type RawRecommendMerchantPayload = Partial & { id?: RecommendMerchantInfo['id'] merchantId?: MerchantId | number | string merchantName?: string logo?: string notice?: string status?: string rankScore?: number | string goodsCount?: number | string recentNewGoodsCount?: number | string orderCount?: number | string } type RawMerchantEntityPayload = Partial & { id?: MerchantInfo['id'] | string merchantId?: MerchantId | number | string merchantName?: string logo?: string notice?: string status?: string } type RawGoodsSummaryPayload = Partial & { id?: GoodsSummary['id'] | number spuId?: GoodsSummary['id'] | number merchantId?: MerchantId | number | string createdAt?: GoodsSummary['createdAt'] | string | null updatedAt?: GoodsSummary['updatedAt'] | string | null createAt?: number | string | null updateAt?: number | string | null created_at?: number | string | null updated_at?: number | string | null createTime?: number | string | null updateTime?: number | string | null create_time?: number | string | null update_time?: number | string | null } const toId = (value: string | number | null | undefined): string => (value == null ? '' : String(value)) const toMerchantId = (value: MerchantId | number | string | null | undefined): MerchantId => toId(value) const toNumber = (value: number | string | null | undefined): number => { const parsed = Number(value) return Number.isFinite(parsed) ? parsed : 0 } const toBoolean = (value: boolean | number | string | null | undefined): boolean => { if (typeof value === 'boolean') { return value } if (typeof value === 'number') { return value !== 0 } if (typeof value === 'string') { return ['1', 'true', 'yes'].includes(value.trim().toLowerCase()) } return false } const pickText = (...values: Array): string => { for (const value of values) { if (typeof value === 'string' && value.trim()) { return value } } return '' } const toUnixTime = (value: number | string | null | undefined): number => { if (typeof value === 'number' && Number.isFinite(value)) { return value > 1_000_000_000_000 ? Math.floor(value / 1000) : value } if (typeof value !== 'string') { return 0 } const text = value.trim() if (!text) { return 0 } const numeric = Number(text) if (Number.isFinite(numeric)) { return numeric > 1_000_000_000_000 ? Math.floor(numeric / 1000) : numeric } const parsed = Date.parse(text.replace(/-/g, '/')) return Number.isFinite(parsed) ? Math.floor(parsed / 1000) : 0 } export const normalizeMallAppPayload = (payload: TPayload): TPayload => { const normalized = { ...payload } as TPayload & MaybeMallIdentityPayload & MaybeMallMerchantPayload & MaybeMallIdPayload delete normalized.uid if (normalized.memberId != null) { normalized.memberId = toMerchantId(normalized.memberId) } if (normalized.id != null) { normalized.id = toId(normalized.id) } if (normalized.skuId != null) { normalized.skuId = toId(normalized.skuId) } if (normalized.skuIds != null) { normalized.skuIds = normalized.skuIds.map(toId) } if (normalized.addressId != null) { normalized.addressId = toId(normalized.addressId) } if (normalized.orderId != null) { normalized.orderId = toId(normalized.orderId) } if (normalized.orderItemId != null) { normalized.orderItemId = toId(normalized.orderItemId) } if (normalized.categoryId != null) { normalized.categoryId = toId(normalized.categoryId) } if (normalized.buyItems != null) { normalized.buyItems = normalized.buyItems.map(item => ({ ...item, skuId: item.skuId == null ? item.skuId : toId(item.skuId), })) } if (normalized.merchantOptions != null) { normalized.merchantOptions = normalized.merchantOptions.map(option => ({ ...option, merchantId: option.merchantId == null ? option.merchantId : toMerchantId(option.merchantId), deliveryOptionId: option.deliveryOptionId == null ? option.deliveryOptionId : toId(option.deliveryOptionId), packageOptionId: option.packageOptionId == null ? option.packageOptionId : toId(option.packageOptionId), })) } if (normalized.merchantId != null) { normalized.merchantId = toMerchantId(normalized.merchantId) } if (normalized.merchantId == null && normalized.storeId != null) { normalized.merchantId = toMerchantId(normalized.storeId) } delete normalized.storeId return normalized as TPayload } const normalizeGoodsSku = (sku: GoodsSkuInfo): GoodsSkuInfo => ({ ...sku, id: toId(sku.id), spuId: String(sku.spuId), merchantId: toMerchantId(sku.merchantId), storeId: toId(sku.storeId), }) const normalizeGoodsSummary = (goods: RawGoodsSummaryPayload): GoodsSummary => { const normalized = goods as GoodsSummary return { ...normalized, id: String(goods.spuId ?? goods.id ?? ''), merchantId: toMerchantId(goods.merchantId), merchantName: pickText(goods.merchantName), merchantLogo: pickText(goods.merchantLogo), storeId: toId(goods.storeId), categoryId: toId(goods.categoryId), createdAt: toUnixTime(goods.createdAt ?? goods.createAt ?? goods.created_at ?? goods.createTime ?? goods.create_time), updatedAt: toUnixTime(goods.updatedAt ?? goods.updateAt ?? goods.updated_at ?? goods.updateTime ?? goods.update_time), } } const normalizeRecentNewGoodsItem = (goods: RecentNewGoodsItem): RecentNewGoodsItem => ({ ...goods, merchantId: goods.merchantId == null ? undefined : toMerchantId(goods.merchantId), merchantName: pickText(goods.merchantName, goods.storeName), merchantLogo: goods.merchantLogo?.trim(), storeName: pickText(goods.storeName, goods.merchantName), spuId: String(goods.spuId), createdAt: toUnixTime(goods.createdAt), }) const normalizeGoodsDetailInfo = (detail: GoodsDetailInfo): GoodsDetailInfo => ({ ...detail, id: toId(detail.id), merchantId: toMerchantId(detail.merchantId), storeId: toId(detail.storeId), categoryId: toId(detail.categoryId), createdAt: toUnixTime(detail.createdAt), updatedAt: toUnixTime(detail.updatedAt), skus: (detail.skus ?? []).map(normalizeGoodsSku), }) const normalizeCartItem = (item: CartItemInfo): CartItemInfo => ({ ...item, id: toId(item.id), memberId: item.memberId == null ? undefined : toMerchantId(item.memberId), merchantId: toMerchantId(item.merchantId), merchantName: pickText(item.merchantName), merchantLogo: pickText(item.merchantLogo), storeId: toId(item.storeId), spuId: String(item.spuId), skuId: toId(item.skuId), itemAmount: item.itemAmount ?? (Number.isFinite(Number(item.salePrice)) && Number.isFinite(Number(item.buyNum)) ? (Number(item.salePrice) * Number(item.buyNum)).toFixed(2) : '0'), }) const normalizeCartGoodsGroup = (goods: CartGoodsGroupInfo): CartGoodsGroupInfo => ({ ...goods, spuId: String(goods.spuId), spuName: pickText(goods.spuName), coverUrl: pickText(goods.coverUrl), checked: Boolean(goods.checked), goodsAmount: goods.goodsAmount ?? '0', checkedAmount: goods.checkedAmount ?? '0', items: (goods.items ?? []).map(normalizeCartItem), }) const normalizeCartMerchantGroup = (group: CartMerchantGroupInfo): CartMerchantGroupInfo => ({ ...group, merchantId: toMerchantId(group.merchantId), merchantName: pickText(group.merchantName) || '未命名商家', merchantLogo: pickText(group.merchantLogo), checked: Boolean(group.checked), itemCount: toNumber(group.itemCount), goodsCount: toNumber(group.goodsCount), goodsAmount: group.goodsAmount ?? '0', checkedAmount: group.checkedAmount ?? '0', goods: (group.goods ?? []).map(normalizeCartGoodsGroup), }) const normalizeTradePreviewItem = (item: TradePreviewItemInfo): TradePreviewItemInfo => ({ ...item, spuId: String(item.spuId), skuId: toId(item.skuId), }) const normalizeDeliveryOption = (option: MerchantDeliveryOptionInfo): MerchantDeliveryOptionInfo => ({ ...option, id: toId(option.id), merchantId: toMerchantId(option.merchantId), }) const normalizePackageOption = (option: MerchantPackageOptionInfo): MerchantPackageOptionInfo => ({ ...option, id: toId(option.id), merchantId: toMerchantId(option.merchantId), }) const normalizeTradePreviewGroup = (group: TradePreviewStoreInfo): TradePreviewStoreInfo => ({ ...group, merchantId: toMerchantId(group.merchantId), storeId: group.storeId == null ? undefined : toId(group.storeId), deliveryOptions: (group.deliveryOptions ?? []).map(normalizeDeliveryOption), packageOptions: (group.packageOptions ?? []).map(normalizePackageOption), selectedDeliveryOption: group.selectedDeliveryOption ? normalizeDeliveryOption(group.selectedDeliveryOption) : undefined, selectedPackageOption: group.selectedPackageOption ? normalizePackageOption(group.selectedPackageOption) : undefined, items: (group.items ?? []).map(normalizeTradePreviewItem), }) const normalizeOrderSummary = (order: OrderSummary): OrderSummary => ({ ...order, id: toId(order.id), tradeId: toId(order.tradeId), memberId: order.memberId == null ? undefined : toMerchantId(order.memberId), merchantId: toMerchantId(order.merchantId), storeId: toId(order.storeId), }) const normalizeOrderDetailInfo = (order: OrderDetailInfo): OrderDetailInfo => ({ ...order, id: toId(order.id), tradeId: toId(order.tradeId), memberId: order.memberId == null ? undefined : toMerchantId(order.memberId), merchantId: toMerchantId(order.merchantId), storeId: toId(order.storeId), selectedDeliveryOption: order.selectedDeliveryOption ? normalizeDeliveryOption(order.selectedDeliveryOption) : undefined, selectedPackageOption: order.selectedPackageOption ? normalizePackageOption(order.selectedPackageOption) : undefined, items: (order.items ?? []).map(item => ({ ...item, id: toId(item.id), orderId: toId(item.orderId), tradeId: toId(item.tradeId), spuId: String(item.spuId), skuId: toId(item.skuId), })), }) const normalizeRecommendMerchant = ( merchant: RawRecommendMerchantPayload | null | undefined ): RecommendMerchantInfo | undefined => { if (!merchant) { return undefined } return { ...(merchant as RecommendMerchantInfo), id: toId(merchant.id ?? merchant.merchantId), merchantId: toMerchantId(merchant.merchantId ?? merchant.id), storeName: pickText(merchant.storeName, merchant.merchantName), storeLogo: pickText(merchant.storeLogo, merchant.logo), storeNotice: pickText(merchant.storeNotice, merchant.notice), storeStatus: (merchant.storeStatus ?? merchant.status ?? 'enabled') as RecommendMerchantInfo['storeStatus'], rankScore: toNumber(merchant.rankScore), goodsCount: toNumber(merchant.goodsCount), recentNewGoodsCount: toNumber(merchant.recentNewGoodsCount), orderCount: toNumber(merchant.orderCount), } } const normalizeMerchantEntity = ( merchant: RawMerchantEntityPayload | null | undefined ): MerchantInfo | undefined => { if (!merchant) { return undefined } return { ...(merchant as MerchantInfo), id: toId(merchant.id ?? merchant.merchantId), merchantId: toMerchantId(merchant.merchantId ?? merchant.id), storeName: pickText(merchant.storeName, merchant.merchantName), storeLogo: pickText(merchant.storeLogo, merchant.logo), storeNotice: pickText(merchant.storeNotice, merchant.notice), storeStatus: (merchant.storeStatus ?? merchant.status ?? 'enabled') as MerchantInfo['storeStatus'], } } const normalizeAddressInfo = (address: AppAddressListResponse['list'][number]) => ({ ...address, id: toId(address.id), }) const normalizeBanner = (banner: AppHomeResponse['banners'][number]) => ({ ...banner, id: toId(banner.id), }) const normalizeCategoryNav = (category: AppHomeResponse['categoryNavs'][number]) => ({ ...category, categoryId: toId(category.categoryId), }) const normalizeChannel = (channel: AppHomeResponse['channels'][number]) => ({ ...channel, id: toId(channel.id), }) const normalizeHomeMerchantGoodsBlock = ( block: HomeMerchantGoodsBlock | undefined ): HomeMerchantGoodsBlock | undefined => { if (!block) { return undefined } const merchant = normalizeRecommendMerchant(block.merchant ?? block.store) if (!merchant) { return undefined } return { ...block, merchant, goods: block.goods.map(normalizeGoodsSummary), store: merchant, } } export const normalizeAppGoodsPageResponse = ( payload: AppGoodsPageResponse ): AppGoodsPageResponse => ({ ...payload, list: payload.list.map(normalizeGoodsSummary), }) export const normalizeAppRecentNewGoodsResponse = ( payload: AppRecentNewGoodsResponse ): AppRecentNewGoodsResponse => ({ ...payload, total: payload.total ?? 0, list: (payload.list ?? []).map(normalizeRecentNewGoodsItem), }) export const normalizeAppCartListResponse = ( payload: AppCartListResponse ): AppCartListResponse => ({ ...payload, merchantGroups: (payload.merchantGroups ?? []).map(normalizeCartMerchantGroup), itemCount: toNumber(payload.itemCount), goodsCount: toNumber(payload.goodsCount), goodsAmount: payload.goodsAmount ?? '0', checkedCount: toNumber(payload.checkedCount), checkedAmount: payload.checkedAmount ?? '0', }) export const normalizeAppCartSaveResponse = ( payload: AppCartSaveResponse ): AppCartSaveResponse => ({ ...payload, cartId: toId(payload.cartId), }) export const normalizeAppAddressListResponse = ( payload: AppAddressListResponse ): AppAddressListResponse => ({ ...payload, list: (payload.list ?? []).map(normalizeAddressInfo), }) export const normalizeAppAddressSaveResponse = ( payload: AppAddressSaveResponse ): AppAddressSaveResponse => ({ ...payload, addressId: toId(payload.addressId), }) export const normalizeAppAddressDefaultSetResponse = ( payload: AppAddressDefaultSetResponse ): AppAddressDefaultSetResponse => ({ ...payload, addressId: toId(payload.addressId), }) export const normalizeAppOrderPageResponse = ( payload: AppOrderPageResponse ): AppOrderPageResponse => ({ ...payload, list: payload.list.map(normalizeOrderSummary), }) export const normalizeAppOrderDetailResponse = ( payload: AppOrderDetailResponse ): AppOrderDetailResponse => ({ ...payload, data: normalizeOrderDetailInfo(payload.data), }) export const normalizeAppOrderConfirmResponse = ( payload: AppOrderConfirmResponse ): AppOrderConfirmResponse => ({ ...payload, orderId: toId(payload.orderId), }) export const normalizeAppAfterSaleApplyResponse = ( payload: AppAfterSaleApplyResponse ): AppAfterSaleApplyResponse => ({ ...payload, afterSaleId: toId(payload.afterSaleId), }) export const normalizeAppHomeResponse = (payload: AppHomeResponse): AppHomeResponse => { const recommendMerchants = (payload.recommendMerchants ?? payload.recommendStores ?? []) .map(normalizeRecommendMerchant) .filter(Boolean) as RecommendMerchantInfo[] const featuredMerchants = (payload.featuredMerchants ?? payload.featuredStores ?? []) .map(normalizeRecommendMerchant) .filter(Boolean) as RecommendMerchantInfo[] return { ...payload, banners: (payload.banners ?? []).map(normalizeBanner), categoryNavs: (payload.categoryNavs ?? []).map(normalizeCategoryNav), channels: (payload.channels ?? []).map(normalizeChannel), recommendGoods: payload.recommendGoods.map(normalizeGoodsSummary), recommendMerchants, featuredMerchants, recommendStores: recommendMerchants, featuredStores: featuredMerchants, } } export const normalizeAppHomeMerchantGoodsResponse = ( payload: AppHomeMerchantGoodsResponse ): AppHomeMerchantGoodsResponse => ({ ...payload, data: normalizeHomeMerchantGoodsBlock(payload.data) ?? ({ merchant: { id: '0', merchantId: '0', storeName: '', storeLogo: '', storeNotice: '', storeStatus: 'disabled', mainCategoryName: '', recommendReason: '', rankScore: 0, goodsCount: 0, recentNewGoodsCount: 0, orderCount: 0, }, store: { id: '0', merchantId: '0', storeName: '', storeLogo: '', storeNotice: '', storeStatus: 'disabled', mainCategoryName: '', recommendReason: '', rankScore: 0, goodsCount: 0, recentNewGoodsCount: 0, orderCount: 0, }, goods: [], } satisfies HomeMerchantGoodsBlock), }) export const normalizeAppGoodsDetailResponse = ( payload: AppGoodsDetailResponse ): AppGoodsDetailResponse => { const merchant = normalizeMerchantEntity(payload.merchant ?? payload.store) return { ...payload, data: normalizeGoodsDetailInfo(payload.data), merchant: merchant ?? payload.store!, store: merchant ?? payload.store, } } export const normalizeAppTradePreviewResponse = ( payload: AppTradePreviewResponse ): AppTradePreviewResponse => { const data = payload.data as TradePreviewInfo & { uid?: number memberId?: MerchantId | number merchantGroups?: TradePreviewInfo['merchantGroups'] storeGroups?: TradePreviewInfo['merchantGroups'] } const merchantGroups = data.merchantGroups ?? data.storeGroups ?? [] return { ...payload, data: { ...data, addressId: toId(data.addressId), uid: data.uid ?? 0, memberId: data.memberId == null ? undefined : toMerchantId(data.memberId), merchantGroups: merchantGroups.map(normalizeTradePreviewGroup), storeGroups: merchantGroups.map(normalizeTradePreviewGroup), }, } } export const normalizeAppTradeCreateResponse = ( payload: AppTradeCreateResponse ): AppTradeCreateResponse => ({ ...payload, tradeId: toId(payload.tradeId), payUrl: pickText(payload.payUrl), payExternal: toBoolean(payload.payExternal), payExpiredAt: toUnixTime(payload.payExpiredAt), payChannel: pickText(payload.payChannel), paySubject: pickText(payload.paySubject), orderIds: (payload.orderIds ?? []).map(toId), })