feat(admin): 统一订单与支付枚举中文映射

This commit is contained in:
2026-11-12 19:16:04 +08:00
parent 0359fd62cb
commit e5f923706b
4 changed files with 268 additions and 28 deletions
@@ -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<string, string> = {
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<AdminPaymentRow> = [
title: '类型',
dataIndex: 'biz_type',
width: 120,
render: value => <Tag bordered={false}>{formatText(value)}</Tag>,
render: value => <Tag bordered={false}>{paymentBizTypeLabel(value)}</Tag>,
},
{
title: '金额',
@@ -196,7 +198,12 @@ const paymentColumns: TableColumnsType<AdminPaymentRow> = [
width: 110,
render: value => <StatusTag meta={readStatusMeta(paymentStatusMap, 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: (
<div className="admin-timeline-item">
<div className="admin-timeline-item__head">
<strong>{formatText(event.action || event.stage)}</strong>
<strong>{processActionLabel(event.action || event.stage)}</strong>
<span>{formatDateTime(event.created_at)}</span>
</div>
{event.content ? <p>{event.content}</p> : null}
{event.reason ? <p>原因:{event.reason}</p> : null}
<div className="admin-timeline-item__meta">
<span>操作方:{formatText(event.actor_name || event.actor_type)}</span>
<span>
操作方:{formatText(event.actor_name || participantTypeLabel(event.actor_type))}
</span>
{event.target_name ? <span>对象:{event.target_name}</span> : null}
{event.stage ? <span>阶段:{event.stage}</span> : null}
{event.stage ? <span>阶段:{processStageLabel(event.stage)}</span> : null}
</div>
</div>
),
@@ -242,7 +251,7 @@ function HandoffRecords({ records }: { records: AdminOrderHandoffRecord[] }) {
{records.map(record => (
<article key={record.id} className="admin-record-item">
<div className="admin-record-item__head">
<strong>{handoffTypeText[record.type] ?? formatText(record.type)}</strong>
<strong>{handoffRecordTypeLabel(record.type)}</strong>
<span>{formatDateTime(record.created_at)}</span>
</div>
<p className="admin-record-item__content">{record.content || emptyText}</p>
@@ -953,13 +962,14 @@ export function AdminOrderDetailPage() {
<Descriptions column={{ xs: 1, sm: 2 }} size="small">
<Descriptions.Item label="申诉单号">#{order.active_dispute.id}</Descriptions.Item>
<Descriptions.Item label="类型">
{formatText(order.active_dispute.type)}
{disputeTypeLabel(order.active_dispute.type)}
</Descriptions.Item>
<Descriptions.Item label="发起方">
{order.active_dispute.initiator_type} · {order.active_dispute.initiator_id}
{participantTypeLabel(order.active_dispute.initiator_type)} ·{' '}
{order.active_dispute.initiator_id}
</Descriptions.Item>
<Descriptions.Item label="状态">
{formatText(order.active_dispute.status)}
{disputeStatusLabel(order.active_dispute.status)}
</Descriptions.Item>
</Descriptions>
</Card>
@@ -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<string, { label: string; color: string }> = {
pending: { label: '待支付', color: 'gold' },
@@ -91,8 +93,8 @@ const columns: TableColumnsType<AdminPaymentRow> = [
width: 170,
render: (_, row) => (
<div className="admin-stack">
<span>{formatText(row.provider)}</span>
<span className="admin-mono">{formatText(row.pay_way)}</span>
<span>{paymentProviderLabel(row.provider)}</span>
<span className="admin-mono">{paymentMethodLabel(row.pay_way)}</span>
</div>
),
},
@@ -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('客服')
})
})
+154
View File
@@ -0,0 +1,154 @@
/** 后台订单/支付相关枚举的中文展示。未知值保留原始 code,方便排查新枚举。 */
function labelOf(map: Record<string, string>, value?: string | null) {
const code = (value ?? '').trim()
if (!code) return '—'
return map[code] ?? code
}
export const paymentBizTypeMap: Record<string, string> = {
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<string, string> = {
leshua: '拉卡拉',
shuncheng: '顺诚',
mock: '模拟支付',
demo: '演示渠道',
}
export function paymentProviderLabel(value?: string | null) {
return labelOf(paymentProviderMap, value)
}
export const paymentMethodMap: Record<string, string> = {
wechat: '微信支付',
alipay: '支付宝',
unionpay: '银联',
qq: 'QQ 钱包',
mock: '模拟支付',
}
export function paymentMethodLabel(value?: string | null) {
return labelOf(paymentMethodMap, value)
}
export const processStageMap: Record<string, string> = {
create: '创建',
payment: '支付',
handoff: '交接',
checkout: '结账',
settlement: '结算',
dispute: '争议',
}
export function processStageLabel(value?: string | null) {
return labelOf(processStageMap, value)
}
export const processActionMap: Record<string, string> = {
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<string, string> = {
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<string, string> = {
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<string, string> = {
open: '处理中',
processing: '处理中',
resolved: '已解决',
cancelled: '已取消',
}
export function disputeStatusLabel(value?: string | null) {
return labelOf(disputeStatusMap, value)
}
export const participantTypeMap: Record<string, string> = {
user: '用户',
admin: '客服',
system: '系统',
}
export function participantTypeLabel(value?: string | null) {
return labelOf(participantTypeMap, value)
}