Files
mall/src/apis/mall-app/normalize.ts
魔方熊成祥 5a968a719f 1
2026-05-07 22:54:21 +08:00

636 lines
19 KiB
TypeScript

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<string | number>
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<RecommendMerchantInfo> & {
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<MerchantInfo> & {
id?: MerchantInfo['id'] | string
merchantId?: MerchantId | number | string
merchantName?: string
logo?: string
notice?: string
status?: string
}
type RawGoodsSummaryPayload = Partial<GoodsSummary> & {
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 | null | undefined>): 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 = <TPayload extends object>(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),
})