/** * 固定兑换链接(`/redeem`)的纯展示逻辑。 * * 这个页面是「无 token 公开链接」:用户只输订单号,系统按订单状态决定下一步。 * 把路径判定、金额格式化、状态 → 文案/色调集中在这里,页面只负责渲染。 * 刻意写成纯函数:不依赖 React / antd,也不使用 `@/` 别名, * 方便 `node --experimental-strip-types --test` 直接跑单测。 */ import type { RedeemOrderStatus } from '../types/redeem' export type { RedeemOrderStatus } /** 状态色调:与 antd Result / Alert 的语义色一致 */ export type RedeemStatusTone = 'success' | 'warning' | 'error' | 'info' export type RedeemStatusView = { tone: RedeemStatusTone /** 页面主标题(中性文案,不出现平台名) */ title: string } /** * 固定兑换链接是**路径式**(`https://<域名>/redeem`,不带 `#`), * 判断时只认 `/redeem`(容忍一个或多个结尾斜杠),`/redeemx`、`/` 都不算。 */ export function isRedeemPath(pathname: unknown): boolean { const normalized = String(pathname ?? '').trim() if (!normalized) return false return normalized.replace(/\/+$/, '') === '/redeem' } /** * 分 → 元展示(与后台 `formatMoney` 同款格式)。 * 空值 / 非法值按 0 处理:`1990 → ¥19.90`、`0 → ¥0.00`。 */ export function formatRedeemAmount(fen: unknown): string { return `¥${((Number(fen || 0) || 0) / 100).toFixed(2)}` } /** * 时间 → `YYYY-MM-DD HH:mm`;空值或非法时间返回空串(调用方自行显示占位符)。 */ export function formatRedeemTime(value: unknown): string { const normalized = String(value ?? '').trim() if (!normalized) return '' const date = new Date(normalized) if (Number.isNaN(date.getTime())) return '' const pad = (input: number) => String(input).padStart(2, '0') const datePart = `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}` const timePart = `${pad(date.getHours())}:${pad(date.getMinutes())}` return `${datePart} ${timePart}` } /** * 订单状态 → 页面标题与色调。 * * 后端以后新增状态时不能崩:未知状态一律回退成中性提示。 * 注意 `pending_certificate` 是「稍后再试」而不是「失败」,所以用 info 而不是 error。 */ export function resolveRedeemStatusView(status: RedeemOrderStatus): RedeemStatusView { switch (status) { case 'not_found': return { tone: 'warning', title: '未找到订单' } case 'unpaid': return { tone: 'warning', title: '订单未支付' } case 'cancelled': return { tone: 'error', title: '订单已取消' } case 'refunded': return { tone: 'error', title: '订单已退款' } case 'pending_certificate': return { tone: 'info', title: '兑换券准备中' } case 'redeemable': return { tone: 'success', title: '可以兑换' } case 'verified': return { tone: 'success', title: '已兑换' } default: return { tone: 'info', title: '订单状态未知' } } }