diff --git a/frontend/src/features/admin/AdminOrderDetailPage.tsx b/frontend/src/features/admin/AdminOrderDetailPage.tsx index de5ee46..2c7af4e 100644 --- a/frontend/src/features/admin/AdminOrderDetailPage.tsx +++ b/frontend/src/features/admin/AdminOrderDetailPage.tsx @@ -48,6 +48,16 @@ import { } from '@/features/admin/api' import { AdminPageHeader } from '@/features/admin/components/AdminPageHeader' import { StatusTag } from '@/features/admin/components/StatusTag' +import { + disputeStatusLabel, + disputeTypeLabel, + handoffRecordTypeLabel, + participantTypeLabel, + paymentBizTypeLabel, + paymentProviderLabel, + processActionLabel, + processStageLabel, +} from '@/features/admin/enumLabels' import { accountSourceLabel, checkoutStatusMap, @@ -148,14 +158,6 @@ const disputeTypeOptions = [ { label: '结账金额争议', value: 'checkout_amount' }, ] -const handoffTypeText: Record = { - handoff: '交接', - return: '归还', - checkout: '结账', - submit: '提交', - confirm: '确认', -} - function errorText(error: unknown) { const response = (error as { response?: { data?: { message?: string } } })?.response if (response?.data?.message) return response.data.message @@ -181,7 +183,7 @@ const paymentColumns: TableColumnsType = [ title: '类型', dataIndex: 'biz_type', width: 120, - render: value => {formatText(value)}, + render: value => {paymentBizTypeLabel(value)}, }, { title: '金额', @@ -196,7 +198,12 @@ const paymentColumns: TableColumnsType = [ width: 110, render: value => , }, - { title: '渠道', dataIndex: 'provider', width: 100, render: value => formatText(value) }, + { + title: '渠道', + dataIndex: 'provider', + width: 100, + render: value => paymentProviderLabel(value), + }, { title: '创建时间', dataIndex: 'created_at', @@ -216,15 +223,17 @@ function ProcessTimeline({ events }: { events: AdminOrderProcessEvent[] }) { children: (
- {formatText(event.action || event.stage)} + {processActionLabel(event.action || event.stage)} {formatDateTime(event.created_at)}
{event.content ?

{event.content}

: null} {event.reason ?

原因:{event.reason}

: null}
- 操作方:{formatText(event.actor_name || event.actor_type)} + + 操作方:{formatText(event.actor_name || participantTypeLabel(event.actor_type))} + {event.target_name ? 对象:{event.target_name} : null} - {event.stage ? 阶段:{event.stage} : null} + {event.stage ? 阶段:{processStageLabel(event.stage)} : null}
), @@ -242,7 +251,7 @@ function HandoffRecords({ records }: { records: AdminOrderHandoffRecord[] }) { {records.map(record => (
- {handoffTypeText[record.type] ?? formatText(record.type)} + {handoffRecordTypeLabel(record.type)} {formatDateTime(record.created_at)}

{record.content || emptyText}

@@ -953,13 +962,14 @@ export function AdminOrderDetailPage() { #{order.active_dispute.id} - {formatText(order.active_dispute.type)} + {disputeTypeLabel(order.active_dispute.type)} - {order.active_dispute.initiator_type} · {order.active_dispute.initiator_id} + {participantTypeLabel(order.active_dispute.initiator_type)} ·{' '} + {order.active_dispute.initiator_id} - {formatText(order.active_dispute.status)} + {disputeStatusLabel(order.active_dispute.status)} diff --git a/frontend/src/features/admin/AdminPaymentsPage.tsx b/frontend/src/features/admin/AdminPaymentsPage.tsx index feeb488..6a84556 100644 --- a/frontend/src/features/admin/AdminPaymentsPage.tsx +++ b/frontend/src/features/admin/AdminPaymentsPage.tsx @@ -7,6 +7,12 @@ import { fetchAdminPayments } from '@/features/admin/api' import { AdminPageHeader } from '@/features/admin/components/AdminPageHeader' import { DataTable } from '@/features/admin/components/DataTable' import { FilterBar, FilterField } from '@/features/admin/components/FilterBar' +import { + paymentBizTypeMap, + paymentMethodLabel, + paymentProviderLabel, + paymentProviderMap, +} from '@/features/admin/enumLabels' import type { AdminPaymentQuery, AdminPaymentRow } from '@/features/admin/types' import { useAdminSessionStore } from '@/stores/adminSession' import { formatDateTime, formatMoney, formatText } from '@/shared/utils/format' @@ -27,16 +33,12 @@ const statusOptions = [ { value: 'closed', label: '已关闭' }, ] -const bizTypeOptions = [ - { value: 'order_pay', label: '订单支付' }, - { value: 'main_recharge', label: '充值' }, -] +const bizTypeOptions = Object.entries(paymentBizTypeMap).map(([value, label]) => ({ value, label })) -const providerOptions = [ - { value: 'leshua', label: '拉卡拉' }, - { value: 'shuncheng', label: '顺诚' }, - { value: 'mock', label: '模拟支付' }, -] +const providerOptions = Object.entries(paymentProviderMap).map(([value, label]) => ({ + value, + label, +})) const statusMeta: Record = { pending: { label: '待支付', color: 'gold' }, @@ -91,8 +93,8 @@ const columns: TableColumnsType = [ width: 170, render: (_, row) => (
- {formatText(row.provider)} - {formatText(row.pay_way)} + {paymentProviderLabel(row.provider)} + {paymentMethodLabel(row.pay_way)}
), }, diff --git a/frontend/src/features/admin/enumLabels.test.ts b/frontend/src/features/admin/enumLabels.test.ts new file mode 100644 index 0000000..746ffee --- /dev/null +++ b/frontend/src/features/admin/enumLabels.test.ts @@ -0,0 +1,74 @@ +import { describe, expect, it } from 'vitest' + +import { + disputeStatusLabel, + disputeTypeLabel, + handoffRecordTypeLabel, + participantTypeLabel, + paymentBizTypeLabel, + paymentMethodLabel, + paymentProviderLabel, + processActionLabel, + processStageLabel, +} from '@/features/admin/enumLabels' + +describe('paymentBizTypeLabel', () => { + it('映射订单与退款业务类型', () => { + expect(paymentBizTypeLabel('order_pay')).toBe('订单支付') + expect(paymentBizTypeLabel('deposit')).toBe('押金支付') + expect(paymentBizTypeLabel('admin_refund')).toBe('人工退款') + }) + + it('未知值保留原值,空值显示占位符', () => { + expect(paymentBizTypeLabel('new_biz_type')).toBe('new_biz_type') + expect(paymentBizTypeLabel('')).toBe('—') + expect(paymentBizTypeLabel(null)).toBe('—') + }) +}) + +describe('paymentProviderLabel / paymentMethodLabel', () => { + it('映射渠道与支付方式', () => { + expect(paymentProviderLabel('leshua')).toBe('拉卡拉') + expect(paymentProviderLabel('demo')).toBe('演示渠道') + expect(paymentMethodLabel('wechat')).toBe('微信支付') + expect(paymentMethodLabel('alipay')).toBe('支付宝') + }) + + it('未知值保留原值', () => { + expect(paymentProviderLabel('new_provider')).toBe('new_provider') + expect(paymentMethodLabel('new_method')).toBe('new_method') + }) +}) + +describe('processActionLabel / processStageLabel', () => { + it('映射真实流程动作与阶段', () => { + expect(processActionLabel('owner_handoff_submitted')).toBe('号主提交交接') + expect(processActionLabel('platform_checkout_confirmed')).toBe('客服确认结账') + expect(processStageLabel('handoff')).toBe('交接') + }) + + it('兼容 order.xxx 旧动作格式', () => { + expect(processActionLabel('order.created')).toBe('创建订单') + expect(processActionLabel('order.paid')).toBe('支付完成') + expect(processActionLabel('order.handoff_received')).toBe('租客确认收号') + }) + + it('未知动作保留原值', () => { + expect(processActionLabel('order.unknown_action')).toBe('order.unknown_action') + expect(processStageLabel('unknown_stage')).toBe('unknown_stage') + }) +}) + +describe('交接与申诉映射', () => { + it('映射交接记录类型', () => { + expect(handoffRecordTypeLabel('owner_handoff')).toBe('号主交接') + expect(handoffRecordTypeLabel('platform_checkout_dispute_opened')).toBe('客服发起结账争议') + }) + + it('映射申诉类型、状态与发起方', () => { + expect(disputeTypeLabel('checkout')).toBe('结账争议') + expect(disputeStatusLabel('open')).toBe('处理中') + expect(participantTypeLabel('user')).toBe('用户') + expect(participantTypeLabel('admin')).toBe('客服') + }) +}) diff --git a/frontend/src/features/admin/enumLabels.ts b/frontend/src/features/admin/enumLabels.ts new file mode 100644 index 0000000..e3fdc2a --- /dev/null +++ b/frontend/src/features/admin/enumLabels.ts @@ -0,0 +1,154 @@ +/** 后台订单/支付相关枚举的中文展示。未知值保留原始 code,方便排查新枚举。 */ + +function labelOf(map: Record, value?: string | null) { + const code = (value ?? '').trim() + if (!code) return '—' + return map[code] ?? code +} + +export const paymentBizTypeMap: Record = { + order_pay: '订单支付', + deposit: '押金支付', + wallet_recharge: '钱包充值', + main_recharge: '充值', + cancel_refund: '取消退款', + admin_close_refund: '客服关闭退款', + admin_refund: '人工退款', + checkout_refund: '结账退款', + deposit_refund: '押金退款', + rent_refund: '租金退款', + arbitration_refund: '仲裁退款', + admin_seal_refund: '封存退款', + mohong_pay: '撞车支付', + mohong_refund: '撞车退款', + quality_owner_pay: '优质号主保证金', +} + +export function paymentBizTypeLabel(value?: string | null) { + return labelOf(paymentBizTypeMap, value) +} + +export const paymentProviderMap: Record = { + leshua: '拉卡拉', + shuncheng: '顺诚', + mock: '模拟支付', + demo: '演示渠道', +} + +export function paymentProviderLabel(value?: string | null) { + return labelOf(paymentProviderMap, value) +} + +export const paymentMethodMap: Record = { + wechat: '微信支付', + alipay: '支付宝', + unionpay: '银联', + qq: 'QQ 钱包', + mock: '模拟支付', +} + +export function paymentMethodLabel(value?: string | null) { + return labelOf(paymentMethodMap, value) +} + +export const processStageMap: Record = { + create: '创建', + payment: '支付', + handoff: '交接', + checkout: '结账', + settlement: '结算', + dispute: '争议', +} + +export function processStageLabel(value?: string | null) { + return labelOf(processStageMap, value) +} + +export const processActionMap: Record = { + created: '创建订单', + paid: '支付完成', + owner_handoff_submitted: '号主提交交接', + handoff_submitted: '号主提交交接', + platform_handoff_submitted: '客服代交接', + admin_force_handoff: '客服确认交接', + renter_received_confirmed: '租客确认收号', + handoff_received: '租客确认收号', + checkout_submitted: '租客发起结账', + checkout_countered: '修改结账方案', + checkout_confirmed: '号主确认结账', + checkout_accepted: '租客接受结账方案', + checkout_disputed: '发起结账争议', + platform_checkout_countered: '客服修改结账方案', + platform_checkout_confirmed: '客服确认结账', + platform_checkout_dispute_opened: '客服发起结账争议', + offline_settlement_confirmed: '确认线下结算', + deposit_held: '暂扣押金', + deposit_released: '归还暂扣押金', +} + +export function processActionLabel(value?: string | null) { + const code = (value ?? '').trim() + if (!code) return '—' + const direct = processActionMap[code] + if (direct) return direct + // 兼容手工数据/旧数据里的 order.xxx 形式。 + if (code.startsWith('order.')) { + const suffix = code.slice('order.'.length) + const mapped = processActionMap[suffix] + if (mapped) return mapped + } + return code +} + +export const handoffRecordTypeMap: Record = { + owner_handoff: '号主交接', + owner_return: '号主归还', + renter_return: '租客归还', + platform_handoff: '客服代交接', + admin_force_handoff: '客服确认交接', + renter_checkout: '租客发起结账', + platform_checkout_counter: '客服修改结账方案', + platform_checkout_dispute_opened: '客服发起结账争议', +} + +export function handoffRecordTypeLabel(value?: string | null) { + return labelOf(handoffRecordTypeMap, value) +} + +export const disputeTypeMap: Record = { + cannot_login: '无法登录', + false_description: '描述不符', + account_banned: '账号封禁', + asset_loss: '资产损失', + haf_coin_dispute: '哈夫币争议', + handoff_timeout: '交接超时', + return_timeout: '归还超时', + checkout_amount: '结账金额争议', + checkout: '结账争议', + checkout_dispute: '结账争议', +} + +export function disputeTypeLabel(value?: string | null) { + return labelOf(disputeTypeMap, value) +} + +export const disputeStatusMap: Record = { + open: '处理中', + processing: '处理中', + resolved: '已解决', + cancelled: '已取消', +} + +export function disputeStatusLabel(value?: string | null) { + return labelOf(disputeStatusMap, value) +} + +export const participantTypeMap: Record = { + user: '用户', + admin: '客服', + system: '系统', +} + +export function participantTypeLabel(value?: string | null) { + return labelOf(participantTypeMap, value) +}