import { resolveFileUrl } from '@/utils/image' import { formatPrice } from '@/utils/format' import { parseCartSpecText } from '@/utils/cartSpec' import { listAftersales } from '@/api/orderAftersale' import { ORDER_ACTION, ORDER_ACTION_LABEL, REVIEW_ITEM_STATUS, AFTERSALE_APPLY_TYPE_OPTIONS, AFTERSALE_TAB, ORDER_STATUS, ORDER_AFTERSALE_CARD_LABEL, ORDER_AFTERSALE_CARD_STATUS } from '@/constants/order' /** 订单级不再展示的评价按钮(改在商品行) */ const ORDER_LEVEL_REVIEW_CODES = [ORDER_ACTION.REVIEW, ORDER_ACTION.VIEW_REVIEW] const GOODS_PLACEHOLDER = '/static/logo.png' const SHOP_PLACEHOLDER = '/static/logo.png' /** 后端凭证图可能是逗号拼接字符串,统一转成数组 */ function normalizePicList(raw) { if (!raw) return [] if (Array.isArray(raw)) return raw.filter(Boolean) if (typeof raw === 'string') { return raw.split(',').map((s) => s.trim()).filter(Boolean) } return [] } function mapAftersaleApplyTypeText(applyType) { const hit = AFTERSALE_APPLY_TYPE_OPTIONS.find((opt) => opt.value === applyType) return hit ? hit.label : applyType || '' } function mapAftersaleStatusText(status) { if (status === '2') return '售后完结' if (status === '1') return '商家处理中' return status || '' } function mapAftersaleProgressText(stage) { if (stage === 'FINISHED') return '商家处理 → 售后完结(当前:售后完结)' return '商家处理 → 售后完结(当前:商家处理)' } function mapFirstItem(item) { if (!item) return null const specText = (item.goodsSpec || item.specText || '').trim() || '默认' return { orderItemId: item.itemId || item.orderItemId, goodsId: item.goodsId, goodsName: item.goodsName || '', goodsImage: item.goodsImage || item.mainPic || '', mainPic: item.mainPic || '', displayPic: resolveFileUrl(item.goodsImage || item.mainPic) || GOODS_PLACEHOLDER, specText, specList: parseCartSpecText(specText), quantity: Number(item.quantity) || 1, unitPrice: item.unitPrice, priceText: formatPrice(item.unitPrice), reviewStatus: item.reviewStatus, reviewId: item.reviewId } } function mapOrderItemRow(row) { if (!row) return null const specText = (row.goodsSpec || row.specText || '').trim() || '默认' return { orderItemId: row.orderItemId || row.itemId, goodsId: row.goodsId, goodsName: row.goodsName || '', goodsImage: row.goodsImage || row.mainPic || '', mainPic: row.mainPic || '', displayPic: resolveFileUrl(row.goodsImage || row.mainPic) || GOODS_PLACEHOLDER, specText, specList: parseCartSpecText(specText), serviceDesc: row.serviceDesc || '', quantity: Number(row.quantity) || 1, unitPrice: row.unitPrice || row.salePrice, priceText: formatPrice(row.unitPrice || row.salePrice), lineAmount: row.lineAmount || row.subtotal, lineAmountText: formatPrice(row.lineAmount || row.subtotal), buyerRemark: row.buyerRemark || '', reviewStatus: row.reviewStatus, reviewId: row.reviewId } } /** 订单已有进行中/已完结售后时,不再展示「申请售后」 */ export function hasOrderAftersale(aftersaleStatus) { const status = aftersaleStatus != null ? String(aftersaleStatus) : '' return ( status === ORDER_AFTERSALE_CARD_STATUS.IN_PROGRESS || status === ORDER_AFTERSALE_CARD_STATUS.FINISHED ) } function mapActions(actions, aftersaleStatus) { const hideAftersale = hasOrderAftersale(aftersaleStatus) return (actions || []) .filter((code) => !ORDER_LEVEL_REVIEW_CODES.includes(code)) .filter((code) => !(hideAftersale && code === ORDER_ACTION.AFTERSALE)) .map((code) => ({ code, label: ORDER_ACTION_LABEL[code] || code })) .filter((item) => item.label) } /** 详情页补查订单售后态(列表接口有 aftersaleStatus,详情接口暂无) */ export async function resolveOrderAftersaleStatus(orderId) { if (!orderId) return null try { const inProgressRes = await listAftersales({ tab: AFTERSALE_TAB.IN_PROGRESS, pageNum: 1, pageSize: 100 }) const inProgressRows = inProgressRes.rows || [] if (inProgressRows.some((row) => String(row.orderId) === String(orderId))) { return ORDER_AFTERSALE_CARD_STATUS.IN_PROGRESS } const finishedRes = await listAftersales({ tab: AFTERSALE_TAB.FINISHED, pageNum: 1, pageSize: 100 }) const finishedRows = finishedRes.rows || [] if (finishedRows.some((row) => String(row.orderId) === String(orderId))) { return ORDER_AFTERSALE_CARD_STATUS.FINISHED } } catch (e) { // 查询失败时不阻断详情展示 } return null } /** 交易成功商品行:待评价→评价,已评价→查看评价 */ export function getItemReviewAction(item) { if (!item || !item.reviewStatus) return null if (item.reviewStatus === REVIEW_ITEM_STATUS.PENDING) { return { code: ORDER_ACTION.REVIEW, label: ORDER_ACTION_LABEL[ORDER_ACTION.REVIEW] } } if (item.reviewStatus === REVIEW_ITEM_STATUS.DONE) { return { code: ORDER_ACTION.VIEW_REVIEW, label: ORDER_ACTION_LABEL[ORDER_ACTION.VIEW_REVIEW] } } return null } /** 评价列表「待评价」:订单行展平为待评价商品行 */ export function flattenPendingReviewItems(rows) { const result = [] for (const row of rows || []) { const card = mapOrderListRow(row) if (!card) continue for (const item of card.items || []) { if (item.reviewStatus !== REVIEW_ITEM_STATUS.PENDING) continue result.push({ key: `pending-${card.orderId}-${item.orderItemId}`, orderId: card.orderId, item }) } } return result } /** 评价列表「已评价」行 → 紧凑卡片模型 */ export function mapReviewDoneRow(row) { if (!row) return null const item = mapFirstItem(row.firstItem) const content = (row.content || '').trim() return { key: `done-${row.reviewId || row.orderId}-${item?.orderItemId || 0}`, reviewId: row.reviewId, orderId: row.orderId, orderItemId: item?.orderItemId, score: Number(row.score) || 0, content, contentBrief: trimText(content, 48, '此用户未填写评价内容'), reviewTime: row.reviewTime || row.createTime || '', item } } function trimText(text, maxLen, emptyFallback = '') { const s = (text || '').trim() if (!s) return emptyFallback if (s.length <= maxLen) return s return `${s.slice(0, maxLen)}…` } /** * 列表卡片状态文案(MO-L4 调整) * 仅「售后处理中」覆盖主状态;售后已完结后订单应变已关闭,列表不再展示「售后已完成」 */ export function mapOrderCardStatusText(orderStatusText, aftersaleStatus, orderStatus) { if (aftersaleStatus === ORDER_AFTERSALE_CARD_STATUS.IN_PROGRESS) { return ORDER_AFTERSALE_CARD_LABEL[aftersaleStatus] } if (aftersaleStatus === ORDER_AFTERSALE_CARD_STATUS.FINISHED) { if (orderStatus === ORDER_STATUS.CLOSED) { return orderStatusText || '已关闭' } // 售后已完结但订单主状态未同步(异常数据)时,仍按已关闭展示 return '已关闭' } return orderStatusText || '' } /** 列表行 VO → 卡片模型 */ export function mapOrderListRow(row) { if (!row) return null const rawItems = Array.isArray(row.items) && row.items.length ? row.items : row.firstItem ? [row.firstItem] : [] const items = rawItems.map(mapFirstItem).filter(Boolean) const firstItem = items[0] || mapFirstItem(row.firstItem) const orderStatusText = row.orderStatusText || '' const aftersaleStatus = row.aftersaleStatus || null return { orderId: row.orderId, orderNo: row.orderNo || '', orderStatus: row.orderStatus, orderStatusText, aftersaleStatus, statusText: mapOrderCardStatusText(orderStatusText, aftersaleStatus, row.orderStatus), statusIsAftersale: aftersaleStatus === ORDER_AFTERSALE_CARD_STATUS.IN_PROGRESS, amountLabel: row.orderStatus === ORDER_STATUS.PENDING_PAY ? '应付' : '实付', shopId: row.shopId, shopName: row.shopName || '', shopAvatar: resolveFileUrl(row.shopAvatar) || SHOP_PLACEHOLDER, payAmount: row.payAmount, payAmountText: formatPrice(row.payAmount), goodsAmount: row.goodsAmount, freightAmount: row.freightAmount, createTime: row.createTime || '', itemCount: Number(row.itemCount) || items.length, items, firstItem, payRemainSeconds: row.payRemainSeconds, reviewStatus: row.reviewStatus, actions: mapActions(row.actions, aftersaleStatus) } } /** 详情 VO → 页面模型;options.aftersaleStatus 用于补全售后态并过滤操作按钮 */ export function mapOrderDetail(data, options = {}) { if (!data) return null const items = (data.items || []).map(mapOrderItemRow).filter(Boolean) const latestTrace = data.latestTrace || null const aftersaleStatus = data.aftersaleStatus || options.aftersaleStatus || null return { orderId: data.orderId, orderNo: data.orderNo || '', orderStatus: data.orderStatus, statusText: data.orderStatusText || '', payStatus: data.payStatus, payType: data.payType, payTypeText: data.payTypeText || '', shopId: data.shopId, shopName: data.shopName || '', shopAvatar: resolveFileUrl(data.shopAvatar) || SHOP_PLACEHOLDER, consigneeName: data.consigneeName || '', consigneeMobile: data.consigneeMobile || '', consigneeAddress: data.consigneeAddress || '', goodsAmount: data.goodsAmount, goodsAmountText: formatPrice(data.goodsAmount), freightAmount: data.freightAmount, freightAmountText: formatPrice(data.freightAmount), freightDesc: data.freightDesc || '', payAmount: data.payAmount, payAmountText: formatPrice(data.payAmount), createTime: data.createTime || '', payTime: data.payTime || '', payExpireTime: data.payExpireTime || '', payRemainSeconds: data.payRemainSeconds, finishTime: data.finishTime || '', shipTime: data.shipTime || '', closeType: data.closeType, closeTypeText: data.closeTypeText || '', closeReason: data.closeReason || '', deliveryType: data.deliveryType, logisticsCompany: data.logisticsCompany || '', trackingNo: data.trackingNo || '', vehicleNo: data.vehicleNo || '', courierName: data.courierName || '', courierMobile: data.courierMobile || '', latestTrace: latestTrace ? { traceType: latestTrace.traceType, traceTime: latestTrace.traceTime || '', content: latestTrace.content || '' } : null, items, reviewStatus: data.reviewStatus, reviewId: data.reviewId, aftersaleStatus, actions: mapActions(data.actions, aftersaleStatus) } } /** 评价详情 */ export function mapOrderReview(data) { if (!data) return null const pics = (data.pics || []).map((p) => resolveFileUrl(p) || p).filter(Boolean) return { score: Number(data.score) || 0, content: data.content || '', pics, createTime: data.createTime || '', replyContent: data.replyContent || '', replyTime: data.replyTime || '' } } /** 售后列表行 */ export function mapAftersaleListRow(row) { if (!row) return null const specText = (row.goodsSpec || row.specText || '').trim() || '默认' return { aftersaleId: row.aftersaleId, aftersaleNo: row.aftersaleNo || '', aftersaleStatus: row.aftersaleStatus, statusText: row.aftersaleStatusText || row.statusText || mapAftersaleStatusText(row.aftersaleStatus), applyType: row.applyType, applyTypeText: row.applyTypeText || mapAftersaleApplyTypeText(row.applyType), applyReason: row.applyReason || '', applyAmount: row.applyAmount, applyAmountText: formatPrice(row.applyAmount), createTime: row.createTime || '', goodsName: row.goodsName || '', goodsImage: row.goodsImage || row.mainPic || '', mainPic: row.mainPic || '', displayPic: resolveFileUrl(row.goodsImage || row.mainPic) || GOODS_PLACEHOLDER, specList: parseCartSpecText(specText), orderId: row.orderId, orderNo: row.orderNo || '' } } /** 售后详情 */ export function mapAftersaleDetail(data) { if (!data) return null const info = data.info || data const specText = (info.goodsSpec || info.specText || '').trim() || '默认' const progressStage = data.progressStage || info.progressStage || '' const aftersaleStatus = info.aftersaleStatus || data.aftersaleStatus return { aftersaleId: info.aftersaleId || data.aftersaleId, aftersaleNo: info.aftersaleNo || data.aftersaleNo || '', aftersaleStatus, statusText: info.aftersaleStatusText || info.statusText || mapAftersaleStatusText(aftersaleStatus), progress: data.progress || info.progress || progressStage, progressText: data.progressText || info.progressText || mapAftersaleProgressText(progressStage), applyType: info.applyType, applyTypeText: info.applyTypeText || mapAftersaleApplyTypeText(info.applyType), applyReason: info.applyReason || '', returnQuantity: info.returnQuantity, applyAmount: info.applyAmount, applyAmountText: formatPrice(info.applyAmount), description: info.description || '', evidencePics: normalizePicList(info.evidencePics).map((p) => resolveFileUrl(p) || p), createTime: info.createTime || '', finishTime: info.finishTime || '', processResult: data.processResult || info.processResult || '', orderId: info.orderId, orderNo: info.orderNo || data.orderNo || '', orderItemId: info.orderItemId, goodsName: info.goodsName || '', goodsImage: info.goodsImage || info.mainPic || '', displayPic: resolveFileUrl(info.goodsImage || info.mainPic) || GOODS_PLACEHOLDER, specList: parseCartSpecText(specText), quantity: Number(info.quantity) || 1 } } /** 待支付倒计时文案 */ export function formatPayCountdown(seconds) { if (seconds == null || seconds <= 0) return '已超时' const h = Math.floor(seconds / 3600) const m = Math.floor((seconds % 3600) / 60) const s = seconds % 60 if (h > 0) { return `${h}:${String(m).padStart(2, '0')}:${String(s).padStart(2, '0')}` } return `${String(m).padStart(2, '0')}:${String(s).padStart(2, '0')}` }