diff --git a/.codegraph/daemon.pid b/.codegraph/daemon.pid index bd35f59..15da5d8 100644 --- a/.codegraph/daemon.pid +++ b/.codegraph/daemon.pid @@ -1,6 +1,6 @@ { - "pid": 77548, + "pid": 122144, "version": "0.9.9", "socketPath": "\\\\.\\pipe\\codegraph-aef859f0205bacea", - "startedAt": 1781164466085 + "startedAt": 1781578435571 } diff --git a/src/components/shell/PageDispatcher.tsx b/src/components/shell/PageDispatcher.tsx index 6f55001..153dea0 100644 --- a/src/components/shell/PageDispatcher.tsx +++ b/src/components/shell/PageDispatcher.tsx @@ -26,6 +26,7 @@ import {AccountInfo} from '@/pages/panels/AccountInfo'; import {BillingInfo} from '@/pages/panels/BillingInfo'; import {RechargePage} from '@/pages/panels/RechargePage'; import {OrdersPage} from '@/pages/panels/OrdersPage'; +import {VipPage} from '@/pages/panels/VipPage'; // ---------- 类型 ---------- @@ -82,7 +83,7 @@ const PAGE_MAP: Record = { width: () => Math.min(window.outerHeight * 0.65, 720), height: () => Math.min(window.outerHeight * 0.65, 680), }, - '/vip': {label: '开会员/激活'}, + '/vip': {component: VipPage, label: 'VIP 会员开通', width: 560}, '/recharge': {component: RechargePage, label: '充值', width: 520}, '/billing': {label: '账单记录', component: BillingInfo, diff --git a/src/pages/panels/OrdersPage.tsx b/src/pages/panels/OrdersPage.tsx index f2e0708..1314093 100644 --- a/src/pages/panels/OrdersPage.tsx +++ b/src/pages/panels/OrdersPage.tsx @@ -17,13 +17,19 @@ import { DollarOutlined, ReloadOutlined } from '@ant-design/icons'; import { PaymentAPI, PAYMENT_STATUS, - PAYMENT_METHODS, - getPaymentStatusText, type PaymentStatus, type OrderListResponseBody, type OrderInfoResponseBody, } from '@/services/modules/payments'; +import { + VipAPI, + VipOrderStatus, + type VipOrderStatusValue, + type VipOrderRead, +} from '@/services/modules/vip-api'; import { centToYuan, formatDate } from '@/utils/display'; +import { EnumResolver } from '@/utils/enum-resolver'; +import { RequestError, RequestErrorType } from '@/services/request'; const { Text } = Typography; @@ -41,20 +47,13 @@ const STATUS_FILTERS = [ { label: '支付失败', value: PAYMENT_STATUS.Failed }, ]; -/** 状态 → Tag 颜色映射 */ -const STATUS_TAG_COLOR: Record = { - [PAYMENT_STATUS.Pending]: 'processing', - [PAYMENT_STATUS.Paid]: 'success', - [PAYMENT_STATUS.Expired]: 'default', - [PAYMENT_STATUS.Failed]: 'error', -}; +/** 订单类型 */ +type OrderType = 'recharge' | 'vip'; -/** 支付方式 → 显示文本 */ -function getPaymentChannelLabel(channel: string): string { - if (channel === PAYMENT_METHODS.Alipay) return '支付宝'; - if (channel === PAYMENT_METHODS.WeChat) return '微信支付'; - return channel; -} +const ORDER_TYPE_OPTIONS = [ + { label: '充值订单', value: 'recharge' as OrderType }, + { label: 'VIP订单', value: 'vip' as OrderType }, +]; // ---------- 组件 ---------- @@ -64,12 +63,14 @@ export function OrdersPage() { // ======== 筛选 & 分页 ======== + const [orderType, setOrderType] = useState('recharge'); const [statusFilter, setStatusFilter] = useState('all'); const [pagination, setPagination] = useState({ page: 1, page_size: 10 }); // ======== 数据状态 ======== - const [orders, setOrders] = useState([]); + const [rechargeOrders, setRechargeOrders] = useState([]); + const [vipOrders, setVipOrders] = useState([]); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); @@ -91,20 +92,34 @@ export function OrdersPage() { setLoading(true); setError(null); try { - const params: { page: number; page_size: number; status?: PaymentStatus } = { - page: pagination.page, - page_size: pagination.page_size, - }; - if (statusFilter !== 'all') { - params.status = statusFilter as PaymentStatus; - } - const data = await PaymentAPI.getOrdersListInfo(params, controller.signal); - if (mountedRef.current) { - setOrders(data); + if (orderType === 'recharge') { + const params: { page: number; page_size: number; status?: PaymentStatus } = { + page: pagination.page, + page_size: pagination.page_size, + }; + if (statusFilter !== 'all') { + params.status = statusFilter as PaymentStatus; + } + const data = await PaymentAPI.getOrdersListInfo(params, controller.signal); + if (mountedRef.current) { + setRechargeOrders(data); + } + } else { + const params: { page?: number; page_size?: number; status?: VipOrderStatusValue } = { + page: pagination.page, + page_size: pagination.page_size, + }; + if (statusFilter !== 'all') { + params.status = statusFilter as VipOrderStatusValue; + } + const data = await VipAPI.getVipOrders(params); + if (mountedRef.current) { + setVipOrders(data); + } } } catch (err) { - // AbortError 是预期行为(用户切换筛选导致),不视为错误 - if ((err as Error)?.name === 'AbortError') return; + // axios 拦截器将取消错误转为 RequestError(CANCELLED),非原始 AbortError + if (err instanceof RequestError && err.type === RequestErrorType.CANCELLED) return; if (mountedRef.current) { setError((err as Error)?.message || '加载订单列表失败'); } @@ -113,7 +128,7 @@ export function OrdersPage() { setLoading(false); } } - }, [pagination, statusFilter]); + }, [pagination, statusFilter, orderType]); // 筛选或分页变化 → 重新获取 useEffect(() => { @@ -164,7 +179,7 @@ export function OrdersPage() { const newStatus = order.status; // 局部更新 orders state(merge 最新订单信息) - setOrders((prev) => + setRechargeOrders((prev) => prev.map((o) => (o.id === orderId ? { ...o, ...order } : o)), ); @@ -199,21 +214,76 @@ export function OrdersPage() { pollingRef.current.set(orderId, timer); }, [message, fetchOrders]); + /** VIP 订单轮询(与充值订单轮询模式一致,使用 VipAPI.getVipOrder) */ + const startVipPolling = useCallback((orderId: string) => { + const existing = pollingRef.current.get(orderId); + if (existing) { + clearTimeout(existing); + pollingRef.current.delete(orderId); + } + + let cancelled = false; + + const poll = async () => { + if (cancelled || !mountedRef.current) return; + + try { + const order = await VipAPI.getVipOrder(orderId); + if (cancelled || !mountedRef.current) return; + + const newStatus = order.status; + + setVipOrders((prev) => + prev.map((o) => (o.id === orderId ? { ...o, ...order } : o)), + ); + + if (newStatus === VipOrderStatus.Pending) { + const timer = setTimeout(poll, POLL_INTERVAL_MS); + pollingRef.current.set(orderId, timer); + } else { + pollingRef.current.delete(orderId); + fetchOrders(); + + if (newStatus === VipOrderStatus.Paid) { + message.success('支付成功!VIP 权益已生效'); + } else if (newStatus === VipOrderStatus.Expired) { + message.warning('订单已过期'); + } else if (newStatus === VipOrderStatus.Cancelled) { + message.info('订单已取消'); + } + } + } catch { + if (!cancelled && mountedRef.current) { + const timer = setTimeout(poll, POLL_INTERVAL_MS * 2); + pollingRef.current.set(orderId, timer); + } + } + }; + + const timer = setTimeout(poll, POLL_INTERVAL_MS); + pollingRef.current.set(orderId, timer); + }, [message, fetchOrders]); + // ======== 操作回调 ======== /** "继续支付":打开支付页面 + 启动轮询 */ const handleContinuePay = useCallback( - (order: OrderListResponseBody) => { - window.open(order.pay_url, '_blank'); - startPolling(order.id); + (orderId: string, payUrl: string) => { + window.open(payUrl, '_blank'); + if (orderType === 'recharge') { + startPolling(orderId); + } else { + startVipPolling(orderId); + } message.info('已打开支付页面,请完成支付'); }, - [startPolling, message], + [orderType, startPolling, startVipPolling, message], ); // ======== 表格列定义 ======== - const columns = useMemo(() => [ + /** 充值订单列 */ + const rechargeColumns = useMemo(() => [ { title: '订单编号', dataIndex: 'id', @@ -221,9 +291,7 @@ export function OrdersPage() { width: 200, ellipsis: true, render: (id: string) => ( - - {id} - + {id} ), }, { @@ -233,9 +301,7 @@ export function OrdersPage() { width: 90, align: 'center' as const, render: (amount: number) => ( - - ¥{centToYuan(amount)} - + ¥{centToYuan(amount)} ), }, { @@ -245,7 +311,7 @@ export function OrdersPage() { width: 90, align: 'center' as const, render: (channel: string) => ( - {getPaymentChannelLabel(channel)} + {EnumResolver.payments.method.label(channel)} ), }, { @@ -254,9 +320,12 @@ export function OrdersPage() { key: 'status', width: 90, align: 'center' as const, - render: (status: PaymentStatus) => ( - - {getPaymentStatusText(status)} + render: (status: string) => ( + + {EnumResolver.payments.status.label(status as PaymentStatus)} ), }, @@ -282,21 +351,91 @@ export function OrdersPage() { type="primary" size="small" icon={} - onClick={() => handleContinuePay(record)} + onClick={() => handleContinuePay(record.id, record.pay_url)} > 继续支付 ); } - return ( - - — - - ); + return ; }, }, ], [token.colorPrimary, handleContinuePay]); + /** VIP 订单列 */ + const vipColumns = useMemo(() => [ + { + title: '订单编号', + dataIndex: 'id', + key: 'id', + width: 220, + ellipsis: true, + render: (id: string) => ( + {id} + ), + }, + { + title: '金额', + dataIndex: 'amount_cent', + key: 'amount_cent', + width: 90, + align: 'center' as const, + render: (amount: number) => ( + ¥{centToYuan(amount)} + ), + }, + { + title: '状态', + dataIndex: 'status', + key: 'status', + width: 90, + align: 'center' as const, + render: (status: string) => ( + + {EnumResolver.vip.orderStatus.label(status as VipOrderStatusValue)} + + ), + }, + { + title: '创建时间', + dataIndex: 'created_at', + key: 'created_at', + width: 130, + align: 'center' as const, + render: (date: Date) => ( + {formatDate(date)} + ), + }, + { + title: '操作', + key: 'action', + width: 110, + align: 'center' as const, + render: (_: unknown, record: VipOrderRead) => { + if (record.status === VipOrderStatus.Pending && record.pay_url) { + return ( + + ); + } + return ; + }, + }, + ], [token.colorPrimary, handleContinuePay]); + + /** 当前类型对应的列和数据 */ + const columns = orderType === 'recharge' ? rechargeColumns : vipColumns; + const orders = orderType === 'recharge' ? rechargeOrders : vipOrders; + // ============================================================ // 渲染:错误态(无数据时) // ============================================================ @@ -324,12 +463,34 @@ export function OrdersPage() { // 渲染:正常 // ============================================================ + /** 当前订单类型对应的状态筛选项 */ + const currentStatusFilters = orderType === 'recharge' + ? STATUS_FILTERS + : [ + { label: '全部', value: 'all' }, + { label: '待支付', value: VipOrderStatus.Pending }, + { label: '已支付', value: VipOrderStatus.Paid }, + { label: '已过期', value: VipOrderStatus.Expired }, + { label: '已取消', value: VipOrderStatus.Cancelled }, + ]; + return (
- {/* ---- 状态筛选 ---- */} + {/* ---- 订单类型 + 状态筛选 ---- */} { + setOrderType(val as OrderType); + setStatusFilter('all'); + setPagination((prev) => ({ ...prev, page: 1 })); + }} + block + style={{ backgroundColor: token.colorFillSecondary }} + /> + { setStatusFilter(val as string); @@ -348,7 +509,8 @@ export function OrdersPage() { {/* ---- 订单表格 ---- */} - + {/* eslint-disable-next-line @typescript-eslint/no-explicit-any */} + columns={columns} dataSource={orders} rowKey="id" diff --git a/src/pages/panels/VipPage.tsx b/src/pages/panels/VipPage.tsx new file mode 100644 index 0000000..07435bf --- /dev/null +++ b/src/pages/panels/VipPage.tsx @@ -0,0 +1,636 @@ +// ============================================================ +// VipPage — VIP 会员开通页面 +// +// 通过 PageDispatcher 调度(Modal 容器),导航栏"开会员/激活"按钮触发。 +// 四部分布局: +// 激活码 — Space.Compact 输入框 + 激活按钮 +// 套餐选择 — Radio.Group 卡片式单选,API 获取套餐列表 +// 支付协议 — Checkbox + LegalTextModal(用户服务协议) + 确认开通 +// 权益说明 — 选中套餐的大图展示 +// ============================================================ + +import {useState, useCallback, useEffect, useRef} from 'react'; +import { + Radio, + Button, + Checkbox, + Input, + Image, + Typography, + App, + Space, + Divider, + theme as antTheme, + Spin, + Result, +} from 'antd'; +import {CrownOutlined, GiftOutlined, DollarOutlined} from '@ant-design/icons'; + +import {useTheme} from '@/components/providers/ThemeProvider'; +import {LegalTextModal} from '@/components/ui/LegalTextModal'; +import { + VipAPI, + VipOrderStatus, + type VipLevelsItem, + type VipOrderStatusValue, +} from '@/services/modules/vip-api'; +import {centToYuan} from '@/utils/display'; +import {EnumResolver} from '@/utils/enum-resolver'; +import {emit, EVENTS} from '@/utils/event-bus'; +import {RequestError, RequestErrorType} from '@/services/request'; + +const {Text} = Typography; + +// ---------- 常量 ---------- + +/** 订单状态轮询间隔(毫秒) */ +const POLL_INTERVAL_MS = 3000; + +/** 页面阶段 */ +type VipPhase = 'form' | 'activate' | VipOrderStatusValue; + +/** 状态 → Result status 映射 */ +function getResultStatus(status: VipOrderStatusValue): 'success' | 'info' | 'error' { + if (status === VipOrderStatus.Paid) return 'success'; + if (status === VipOrderStatus.Pending) return 'info'; + return 'error'; +} + +// ---------- 组件 ---------- + +export function VipPage() { + const {isDark} = useTheme(); + const {message} = App.useApp(); + const {token} = antTheme.useToken(); + + // ======== 套餐列表 ======== + + const [levels, setLevels] = useState([]); + const [loading, setLoading] = useState(true); + const [fetchError, setFetchError] = useState(null); + const abortRef = useRef(null); + const mountedRef = useRef(true); + + // ======== 表单状态 ======== + + const [selectedLevelId, setSelectedLevelId] = useState(null); + const [agreedToTerms, setAgreedToTerms] = useState(false); + const [submitting, setSubmitting] = useState(false); + const [errors, setErrors] = useState>({}); + const [legalOpen, setLegalOpen] = useState(false); + + // ======== 激活码 ======== + + const [activateCode, setActivateCode] = useState(''); + const [activating, setActivating] = useState(false); + + // ======== 支付订单状态 ======== + + const [phase, setPhase] = useState('form'); + const [vipOrderId, setVipOrderId] = useState(null); + const [vipOrderStatus, setVipOrderStatus] = useState(null); + const [vipPayUrl, setVipPayUrl] = useState(null); + + // ======== 工具函数 ======== + + const clearError = useCallback((name: string) => { + setErrors((prev) => { + if (!(name in prev)) return prev; + const next = {...prev}; + delete next[name]; + return next; + }); + }, []); + + // ======== 获取套餐列表 ======== + + const fetchLevels = useCallback(async () => { + abortRef.current?.abort(); + const controller = new AbortController(); + abortRef.current = controller; + + setLoading(true); + setFetchError(null); + try { + const data = await VipAPI.getVipLevels(controller.signal); + if (mountedRef.current) { + setLevels(data.filter((l) => l.is_active && l.image)); + } + } catch (err) { + // axios 拦截器将取消错误转为 RequestError(CANCELLED),非原始 AbortError + if (err instanceof RequestError && err.type === RequestErrorType.CANCELLED) return; + if (mountedRef.current) { + setFetchError((err as Error)?.message || '加载套餐列表失败'); + } + } finally { + if (mountedRef.current) { + setLoading(false); + } + } + }, []); + + useEffect(() => { + mountedRef.current = true; + void fetchLevels(); + return () => { + mountedRef.current = false; + abortRef.current?.abort(); + }; + }, [fetchLevels]); + + // ======== 校验 ======== + + const validate = useCallback((): Record => { + const errs: Record = {}; + + if (selectedLevelId === null) { + errs.selectedLevel = '请选择会员套餐'; + } + + if (!agreedToTerms) { + errs.agreedToTerms = '请阅读并同意用户服务协议'; + } + + return errs; + }, [selectedLevelId, agreedToTerms]); + + // ======== 提交 ======== + + const handleSubmit = useCallback(async () => { + const validationErrors = validate(); + if (Object.keys(validationErrors).length > 0) { + setErrors(validationErrors); + return; + } + + setSubmitting(true); + try { + const res = await VipAPI.createVipOrder({ + vip_level_id: selectedLevelId!, + payment_channel: 'alipay', + }); + + // 保存订单信息,进入待支付阶段 + setVipOrderId(res.id); + setVipOrderStatus(VipOrderStatus.Pending); + setVipPayUrl(res.pay_url); + setPhase(VipOrderStatus.Pending); + + window.open(res.pay_url ?? '', '_blank'); + void message.success('订单已创建,请在新页面中完成支付'); + } catch (err: unknown) { + message.error((err as Error)?.message || '创建订单失败,请重试'); + } finally { + setSubmitting(false); + } + }, [selectedLevelId, validate, message]); + + // ======== 激活码提交 ======== + + const handleActivate = useCallback(async () => { + const code = activateCode.trim(); + if (!code) { + message.warning('请输入激活码'); + return; + } + + setActivating(true); + try { + await VipAPI.activateVipCode({code}); + void message.success('激活成功!VIP 权益已生效'); + emit(EVENTS.CLOSE_PAGE); + } catch (err: unknown) { + message.error((err as Error)?.message || '激活失败,请检查激活码是否正确'); + } finally { + setActivating(false); + } + }, [activateCode, message]); + + // ======== 操作回调 ======== + + // ======== 订单状态轮询 ======== + + useEffect(() => { + if (phase !== VipOrderStatus.Pending || !vipOrderId) return; + + let timer: ReturnType; + let cancelled = false; + + const poll = async () => { + try { + const order = await VipAPI.getVipOrder(vipOrderId); + if (cancelled) return; + + const newStatus = order.status; + setVipOrderStatus(newStatus); + + if (newStatus === VipOrderStatus.Pending) { + timer = setTimeout(poll, POLL_INTERVAL_MS); + } else { + setPhase(newStatus); + if (newStatus === VipOrderStatus.Paid) { + void message.success('支付成功!VIP 权益已生效'); + } + } + } catch { + if (!cancelled) timer = setTimeout(poll, POLL_INTERVAL_MS * 2); + } + }; + + timer = setTimeout(poll, POLL_INTERVAL_MS); + return () => { + cancelled = true; + clearTimeout(timer); + }; + }, [phase, vipOrderId, message]); + + // ======== 页面锁定(待支付时禁止关闭容器) ======== + + useEffect(() => { + const locked = phase === VipOrderStatus.Pending; + emit(EVENTS.PAGE_LOCK, { locked }); + return () => { + if (locked) emit(EVENTS.PAGE_LOCK, { locked: false }); + }; + }, [phase]); + + // ======== 操作回调 ======== + + /** 重新下单:回到表单,保留套餐选择 */ + const handleRetry = useCallback(() => { + setPhase('form'); + setVipOrderId(null); + setVipOrderStatus(null); + setVipPayUrl(null); + }, []); + + const handleClose = useCallback(() => { + emit(EVENTS.CLOSE_PAGE); + }, []); + + // ================================================================ + // 渲染:加载态 + // ================================================================ + + if (loading) { + return ( +
+ +
+ 加载套餐列表… +
+
+
+ ); + } + + // ================================================================ + // 渲染:错误态 + // ================================================================ + + if (fetchError) { + return ( +
+ + 重试 + + } + /> +
+ ); + } + + // ================================================================ + // 渲染:支付状态视图(非 form / activate 阶段) + // ================================================================ + + if (phase !== 'form' && phase !== 'activate') { + const statusText = vipOrderStatus ? EnumResolver.vip.orderStatus.label(vipOrderStatus) : ''; + const isPending = phase === VipOrderStatus.Pending; + const isPaid = phase === VipOrderStatus.Paid; + + return ( + <> +
+ + {/* ---- 订单信息卡片 ---- */} + {isPending && ( +
+ + + 订单编号:{vipOrderId} + + + + 支付金额: + + ¥{(() => { + const lvl = levels.find((l) => l.id === selectedLevelId); + return lvl ? centToYuan(lvl.price_cent) : '—'; + })()} + + + + + 套餐: + {levels.find((l) => l.id === selectedLevelId)?.name ?? '—'} + + +
+ )} + + {/* ---- 操作按钮 ---- */} + + {isPending && vipPayUrl && ( + + )} + {!isPending && !isPaid && ( + + )} + + +
+
+ + {/* ---- 协议弹窗(待支付阶段仍可查看) ---- */} + setLegalOpen(false)} + /> + + ); + } + + // ================================================================ + // 渲染:正常 + // ================================================================ + + return ( + <> +
+
+ + + 使用激活码 + + + setActivateCode(e.target.value)} + placeholder="输入 VIP 激活码" + allowClear + onPressEnter={() => void handleActivate()} + style={{flex: 1}} + /> + + +
+ + + 选择会员套餐 + + + {/* 双列布局:左列套餐选择 | 右列权益图库 */} +
+ {/* ---- 左列:套餐选择 ---- */} +
+ { + setSelectedLevelId(e.target.value); + clearError('selectedLevel'); + }} + style={{width: '100%'}} + > + + {levels.map((level) => ( + +
+
+ + {level.name} + + + ¥{centToYuan(level.price_cent)} + +
+
+ + {level.duration_days}天 + + {level.gift_balance_cent > 0 && ( + + 赠{centToYuan(level.gift_balance_cent)}积分 + + )} + + {EnumResolver.vip.targetAccountType.label(level.target_account_type)} + +
+ {level.description && ( + + {level.description} + + )} +
+
+ ))} +
+
+ + {errors.selectedLevel && ( + + {errors.selectedLevel} + + )} +
+ + {/* ---- 右列:权益图库 ---- */} +
+ + 权益说明(点击放大) + + {levels.map((level) => ( +
+ {`${level.name} +
+ ))} +
+
+ + {/* ---- Part 2:支付协议 + 确认开通 ---- */} +
+ { + setAgreedToTerms(e.target.checked); + clearError('agreedToTerms'); + }} + > + + 我已阅读并同意 + + + + {errors.agreedToTerms && ( + + {errors.agreedToTerms} + + )} +
+ + {/* 操作按钮 */} +
+ + +
+
+ + {/* ---- 协议弹窗 ---- */} + setLegalOpen(false)} + /> + + ); +} diff --git a/src/services/modules/vip-api.ts b/src/services/modules/vip-api.ts index a5f065d..820d6fc 100644 --- a/src/services/modules/vip-api.ts +++ b/src/services/modules/vip-api.ts @@ -53,7 +53,7 @@ export interface VipLevelsItem { model_package_id: number; description: Nullable; image: Nullable; - is_active: number; + is_active: boolean; sort_order: number; target_account_type: TargetAccountTypeValue; seat_limit: Nullable; @@ -147,6 +147,12 @@ export class VipAPI { return await get(VipApiUrl.VipOrders, params as Record); } + /** 获取单个 VIP 订单详情 */ + static async getVipOrder(orderId: string): Promise { + const url = `${VipApiUrl.VipOrders}/${orderId}`; + return await get(url); + } + /** 模拟支付成功(仅开发测试用) */ static async mockPay(orderId: string): Promise { return await get(`${VipApiUrl.MockPay}/${orderId}`); diff --git a/src/utils/enum-resolver.ts b/src/utils/enum-resolver.ts index 9ed8e63..4e614e5 100644 --- a/src/utils/enum-resolver.ts +++ b/src/utils/enum-resolver.ts @@ -20,6 +20,11 @@ // EnumResolver.announcement.type.color('feature') // "purple" // ============================================================ +import { + PAYMENT_STATUS_TEXT_MAP, + type PaymentStatus, +} from '@/services/modules/payments'; + import { EntryTypeLabelMap, EntryTypeColorMap, @@ -77,6 +82,27 @@ const SLUG_TO_CATEGORY: Record = Object.fromEntries( // ============================================================ export const EnumResolver = { + /** 支付 */ + payments: { + status: { + label: (value: PaymentStatus) => + PAYMENT_STATUS_TEXT_MAP[value] || value || '未知状态', + color: (value: PaymentStatus) => { + if (value === 'pending') return 'processing'; + if (value === 'paid') return 'success'; + if (value === 'failed') return 'error'; + return 'default'; + }, + }, + method: { + label: (value: string) => { + if (value === 'alipay') return '支付宝'; + if (value === 'wechat') return '微信支付'; + return value || '—'; + }, + }, + }, + /** 账单 */ billing: { entryType: { @@ -116,6 +142,12 @@ export const EnumResolver = { orderStatus: { label: (value: VipOrderStatusValue) => VipOrderStatusLabelMap[value] || value || '未知订单状态', + color: (value: VipOrderStatusValue) => { + if (value === 'pending') return 'processing'; + if (value === 'paid') return 'success'; + if (value === 'cancelled') return 'error'; + return 'default'; + }, }, targetAccountType: { label: (value: TargetAccountTypeValue) =>