import type { CartItem, FlashSale, GroupBuying, GroupBuyingGroup, Order, OrderAddress, PageResponse, Product, User, } from '@/types/api' import type { AdminHotProductRow, AdminOrderRow, AdminProductRow, AdminRecentOrderRow, AdminUserRow, } from '@/types/admin' import { DEFAULT_PRODUCT_IMAGE, normalizeStorageImageUrl, resolveImageUrl } from '@/utils/image' const toNumber = (value: unknown, fallback = 0) => { const result = Number(value) return Number.isFinite(result) ? result : fallback } const toString = (value: unknown, fallback = '') => { if (value === null || value === undefined) { return fallback } return String(value) } const toIsoLikeString = (value: unknown) => { const raw = toString(value) return raw || new Date().toISOString() } export const buildOrderNo = (id: number | string) => { const numericId = toString(id).padStart(6, '0') return `FS${numericId}` } export const mapUserStatusText = (status: number) => { return status === 1 ? '正常' : '禁用' } export const mapOrderStatus = (status: number | string): Order['status'] => { const value = typeof status === 'string' ? status : toNumber(status) if (value === 'PENDING' || value === 1) return 'PENDING' if (value === 'PAID' || value === 2) return 'PAID' if (value === 'SHIPPED' || value === 3) return 'SHIPPED' if (value === 'COMPLETED' || value === 4) return 'COMPLETED' if (value === 'CANCELLED' || value === 5) return 'CANCELLED' return 'PENDING' } export const mapFlashSaleStatus = (status: number | string): FlashSale['status'] => { const value = typeof status === 'string' ? status : toNumber(status) if (value === 'UPCOMING' || value === 1) return 'UPCOMING' if (value === 'ACTIVE' || value === 2) return 'ACTIVE' if (value === 'ENDED' || value === 3) return 'ENDED' if (value === 'PAUSED' || value === 4) return 'PAUSED' return 'UPCOMING' } export const mapProductStatus = (status: number | string, stock = 0): Product['status'] => { const value = typeof status === 'string' ? status : toNumber(status) if (stock <= 0) return 'SOLD_OUT' if (value === 'OFF_SALE' || value === 0) return 'OFF_SALE' return 'ON_SALE' } export const normalizeUser = (user: Record): User => { const username = toString(user.username) return { id: toNumber(user.id), username, email: toString(user.email), phone: toString(user.phone), avatar: resolveImageUrl(toString(user.avatar, '')), role: toString(user.role).toUpperCase() === 'ADMIN' ? 'ADMIN' : username === 'admin' ? 'ADMIN' : 'USER', status: toNumber(user.status, 1) === 1 ? 'ACTIVE' : 'BANNED', createdAt: toIsoLikeString(user.createdAt), updatedAt: toIsoLikeString(user.updatedAt || user.createdAt), } } export const normalizeProduct = (product: Record): Product => { const stock = toNumber(product.stock) const imageUrl = resolveImageUrl(toString(product.imageUrl, '')) return { id: toNumber(product.id), name: toString(product.name), description: toString(product.description), price: toNumber(product.price), stock, imageUrl, images: imageUrl ? [imageUrl] : [DEFAULT_PRODUCT_IMAGE], category: toString(product.category, '默认分类'), status: mapProductStatus(product.status, stock), sales: toNumber(product.sales), views: toNumber(product.viewCount ?? product.views), createdAt: toIsoLikeString(product.createdAt), updatedAt: toIsoLikeString(product.updatedAt || product.createdAt), } } export const normalizeFlashSale = (flashSale: Record): FlashSale => { const flashStock = toNumber(flashSale.flashStock) const remainingStock = toNumber(flashSale.remainingStock, flashStock) return { id: toNumber(flashSale.id), productId: toNumber(flashSale.productId), productName: toString(flashSale.productName), productImageUrl: resolveImageUrl(toString(flashSale.productImageUrl, '')), originalPrice: toNumber(flashSale.originalPrice), flashPrice: toNumber(flashSale.flashPrice), flashStock, remainingStock, startTime: toIsoLikeString(flashSale.startTime), endTime: toIsoLikeString(flashSale.endTime), status: mapFlashSaleStatus(flashSale.status), limitPerUser: toNumber(flashSale.limitPerUser, 1), description: toString(flashSale.description || flashSale.statusDescription), createdAt: toIsoLikeString(flashSale.createdAt), updatedAt: toIsoLikeString(flashSale.updatedAt || flashSale.createdAt), } } const buildOrderAddress = (order: Record): OrderAddress | undefined => { const name = toString(order.receiverName) const phone = toString(order.receiverPhone) const address = toString(order.receiverAddress) if (!name && !phone && !address) { return undefined } return { name, phone, province: '', city: '', district: '', address, } } export const normalizeOrder = (order: Record): Order => { const totalAmount = toNumber(order.totalAmount ?? order.totalPrice) const quantity = toNumber(order.quantity, 1) const status = mapOrderStatus(order.status) const createdAt = toIsoLikeString(order.createdAt) const updatedAt = toIsoLikeString(order.updatedAt || order.createdAt) const productImage = resolveImageUrl(toString(order.productImageUrl, '')) const fallbackItem = { id: toNumber(order.productId || order.id), productId: toNumber(order.productId), productName: toString(order.productName, '未知商品'), productImage, price: quantity > 0 ? Number((totalAmount / quantity).toFixed(2)) : totalAmount, quantity, subtotal: totalAmount, } const items = Array.isArray(order.items) && order.items.length > 0 ? order.items.map((item: Record) => ({ id: toNumber(item.id || item.productId), productId: toNumber(item.productId), productName: toString(item.productName, '未知商品'), productImage: resolveImageUrl(toString(item.productImageUrl || item.productImage, '')), price: toNumber(item.price), quantity: toNumber(item.quantity, 1), subtotal: toNumber(item.subtotal ?? item.price), })) : [fallbackItem] return { id: toNumber(order.id), orderNo: toString(order.orderNo, buildOrderNo(order.id)), userId: toNumber(order.userId), username: toString(order.username), totalAmount, paymentAmount: totalAmount, paymentMethod: toString(order.paymentMethod) || (status === 'PENDING' ? undefined : 'ONLINE'), status, items, address: buildOrderAddress(order), remark: toString(order.remark), createdAt, updatedAt, paidAt: order.paidAt ? toIsoLikeString(order.paidAt) : (status === 'PAID' || status === 'SHIPPED' || status === 'COMPLETED' ? updatedAt : undefined), shippedAt: order.shippedAt ? toIsoLikeString(order.shippedAt) : (status === 'SHIPPED' || status === 'COMPLETED' ? updatedAt : undefined), completedAt: order.completedAt ? toIsoLikeString(order.completedAt) : (status === 'COMPLETED' ? updatedAt : undefined), } } export const normalizeCartItems = (cart: Record | undefined): CartItem[] => { const items = Array.isArray(cart?.items) ? cart.items : [] return items.map((item: Record) => ({ id: toString(item.productId), productId: toNumber(item.productId), productName: toString(item.productName), productImage: resolveImageUrl(toString(item.productImageUrl || item.productImage, '')), price: toNumber(item.productPrice), quantity: toNumber(item.quantity, 1), stock: toNumber(item.stock), selected: true, createdAt: new Date().toISOString(), })) } export const normalizePage = (payload: Record, mapper: (item: Record) => T): PageResponse => { const content = Array.isArray(payload.content) ? payload.content.map((item: Record) => mapper(item)) : [] const size = toNumber(payload.size, content.length || 10) const pageNumber = toNumber(payload.currentPage ?? payload.number) const totalElements = toNumber(payload.totalElements, content.length) const totalPages = toNumber(payload.totalPages, size > 0 ? Math.ceil(totalElements / size) : 1) return { content, totalElements, totalPages, size, number: pageNumber, first: pageNumber <= 0, last: totalPages === 0 ? true : pageNumber >= totalPages - 1, } } export const normalizeAdminRecentOrder = (order: Record): AdminRecentOrderRow => ({ id: toNumber(order.id), orderNo: buildOrderNo(order.id), username: toString(order.username), productName: toString(order.productName), quantity: toNumber(order.quantity, 1), totalAmount: toNumber(order.totalAmount ?? order.totalPrice), status: mapOrderStatus(order.status), createdAt: toIsoLikeString(order.createdAt), isFlashSale: Boolean(order.isFlashSale), }) export const normalizeAdminHotProduct = (product: Record): AdminHotProductRow => ({ id: toNumber(product.id), name: toString(product.name), price: toNumber(product.price), stock: toNumber(product.stock), sales: toNumber(product.sales), }) export const normalizeAdminUser = (user: Record): AdminUserRow => ({ id: toNumber(user.id), username: toString(user.username), email: toString(user.email), phone: toString(user.phone), status: toNumber(user.status, 1), statusText: mapUserStatusText(toNumber(user.status, 1)), role: toString(user.role).toUpperCase() === 'ADMIN' || toString(user.username) === 'admin' ? 'ADMIN' : 'USER', isOnline: Boolean(user.isOnline), createdAt: toIsoLikeString(user.createdAt), lastLogin: user.lastLogin ? toIsoLikeString(user.lastLogin) : undefined, }) export const normalizeAdminOrder = (order: Record): AdminOrderRow => ({ id: toNumber(order.id), orderNo: buildOrderNo(order.id), username: toString(order.username), productName: toString(order.productName), productId: toNumber(order.productId), quantity: toNumber(order.quantity, 1), totalAmount: toNumber(order.totalAmount), status: mapOrderStatus(order.status), createdAt: toIsoLikeString(order.createdAt), isFlashSale: Boolean(order.isFlashSale), }) export const normalizeAdminProduct = (product: Record): AdminProductRow => ({ id: toNumber(product.id), name: toString(product.name), description: toString(product.description), category: toString(product.category, '默认分类'), price: toNumber(product.price), stock: toNumber(product.stock), status: toNumber(product.status, 1), imageUrl: normalizeStorageImageUrl(toString(product.imageUrl, '')), createdAt: toIsoLikeString(product.createdAt), updatedAt: product.updatedAt ? toIsoLikeString(product.updatedAt) : undefined, totalSales: toNumber(product.totalSales), totalRevenue: toNumber(product.totalRevenue), viewCount: toNumber(product.viewCount), rating: toNumber(product.rating), }) export const mapGroupBuyingStatus = (status: number | string): GroupBuying['status'] => { const value = typeof status === 'string' ? status : toNumber(status) if (value === 'DRAFT' || value === 0) return 'DRAFT' if (value === 'UPCOMING' || value === 1) return 'UPCOMING' if (value === 'ACTIVE' || value === 2) return 'ACTIVE' if (value === 'ENDED' || value === 3) return 'ENDED' return 'DRAFT' } export const mapGroupStatus = (status: number | string): GroupBuyingGroup['status'] => { const value = typeof status === 'string' ? status : toNumber(status) if (value === 'FORMING' || value === 1) return 'FORMING' if (value === 'SUCCESS' || value === 2) return 'SUCCESS' if (value === 'FAILED' || value === 3) return 'FAILED' return 'FORMING' } export const normalizeGroupBuying = (gb: Record): GroupBuying => ({ id: toNumber(gb.id), productId: toNumber(gb.productId), productName: toString(gb.productName), productImageUrl: resolveImageUrl(toString(gb.productImageUrl, '')), productPrice: toNumber(gb.productPrice), groupPrice: toNumber(gb.groupPrice), requiredMembers: toNumber(gb.requiredMembers, 2), durationMinutes: toNumber(gb.durationMinutes, 1440), totalStock: toNumber(gb.totalStock), remainingStock: toNumber(gb.remainingStock), maxPerUser: toNumber(gb.maxPerUser, 1), status: mapGroupBuyingStatus(gb.status), statusDescription: toString(gb.statusDescription), startTime: toIsoLikeString(gb.startTime), endTime: toIsoLikeString(gb.endTime), createdAt: toIsoLikeString(gb.createdAt), updatedAt: toIsoLikeString(gb.updatedAt || gb.createdAt), activeGroupCount: toNumber(gb.activeGroupCount), discount: toNumber(gb.discount), }) export const normalizeGroupBuyingGroup = (group: Record): GroupBuyingGroup => ({ id: toNumber(group.id), groupNo: toString(group.groupNo), groupBuyingId: toNumber(group.groupBuyingId), leaderUserId: toNumber(group.leaderUserId), leaderUsername: toString(group.leaderUsername), requiredMembers: toNumber(group.requiredMembers, 2), currentMembers: toNumber(group.currentMembers, 1), status: mapGroupStatus(group.status), statusDescription: toString(group.statusDescription), expireTime: toIsoLikeString(group.expireTime), createdAt: toIsoLikeString(group.createdAt), completedAt: group.completedAt ? toIsoLikeString(group.completedAt) : undefined, members: Array.isArray(group.members) ? group.members.map((m: Record) => ({ id: toNumber(m.id), userId: toNumber(m.userId), username: toString(m.username), avatar: resolveImageUrl(toString(m.avatar, '')), orderId: m.orderId ? toNumber(m.orderId) : undefined, status: toNumber(m.status), joinedAt: toIsoLikeString(m.joinedAt), })) : [], groupBuying: group.groupBuying ? normalizeGroupBuying(group.groupBuying) : undefined, })