重构(后台金额): 抽出统一的元金额格式化

- shared AdminFormat 新增 formatAdminCurrencyCents(分 → ¥x.xx)并从 components 导出
- AdminUserDetailDialog / AdminMembershipPlanPage / AdminRechargeOrderPage / AdminRechargeProductPage 删除各自重复的 formatMoney/formatPrice
- 补充 formatAdminCurrencyCents 单测,避免后台各页面金额格式漂移
This commit is contained in:
2026-10-05 18:12:22 +08:00
parent 87a3794423
commit b8309eb3ae
7 changed files with 74 additions and 35 deletions
@@ -9,6 +9,7 @@ import {
AdminStatusPill,
AdminTag,
AdminTagList,
formatAdminCurrencyCents,
} from '@genarrative/shared/components';
import { RefreshCcw, ShieldAlert, UserRound, X } from 'lucide-react';
import { useEffect, useRef, useState } from 'react';
@@ -382,12 +383,14 @@ export function AdminUserDetailDialog({
<td>{order.productTitle || order.productId}</td>
<td>
{order.paidAmountCents > 0
? formatMoney(order.paidAmountCents)
? formatAdminCurrencyCents(order.paidAmountCents)
: '未支付'}
</td>
<td>{order.pointsDelta} 泥点</td>
<td>
{formatMoney(order.cumulativeSuccessRefundCents)}
{formatAdminCurrencyCents(
order.cumulativeSuccessRefundCents,
)}
<small>欠账 {order.unrecoveredPoints} 泥点</small>
</td>
<td>{formatOrderStatus(order.status)}</td>
@@ -442,7 +445,7 @@ function UserIdentityHeader({ detail }: { detail: AdminUserDetailResponse }) {
value: (
<>
{typeof detail.cumulativeRechargedCents === 'number'
? formatMoney(detail.cumulativeRechargedCents)
? formatAdminCurrencyCents(detail.cumulativeRechargedCents)
: '未知'}
</>
),
@@ -580,10 +583,6 @@ function MembershipSection({
);
}
function formatMoney(cents: number) {
return `¥${(cents / 100).toFixed(2)}`;
}
function formatMicros(value: number) {
if (!Number.isFinite(value) || value <= 0) {
return '-';
@@ -8,6 +8,7 @@ import {
AdminPage,
AdminPageHeading,
AdminPanel,
formatAdminCurrencyCents,
} from '@genarrative/shared/components';
import { RefreshCcw, Save } from 'lucide-react';
import { FormEvent, useEffect, useState } from 'react';
@@ -410,8 +411,8 @@ export function AdminMembershipPlanPage({
</small>
</td>
<td>
{formatPrice(entry.monthPriceCents)} /{' '}
{formatPrice(entry.yearPriceCents)}
{formatAdminCurrencyCents(entry.monthPriceCents)} /{' '}
{formatAdminCurrencyCents(entry.yearPriceCents)}
</td>
<td>{entry.periodPoints}</td>
<td>{formatPlanLimits(entry)}</td>
@@ -460,7 +461,3 @@ function formatPlanLimits(entry: ProfileMembershipPlanAdminResponse) {
: `${entry.concurrentJobLimit} 个`;
return `${modelLabel} · 并发 ${concurrency}`;
}
function formatPrice(priceCents: number) {
return `¥${(priceCents / 100).toFixed(2)}`;
}
@@ -13,6 +13,7 @@ import {
AdminPanelHeading,
AdminTag,
AdminTagList,
formatAdminCurrencyCents,
} from '@genarrative/shared/components';
import {
AlertTriangle,
@@ -312,7 +313,7 @@ export function AdminRechargeOrderPage({
setRefundRequestId(requestId);
const confirmed = await confirmWrite({
action: '发起微信退款并追回泥点',
target: `${refundOrder.orderId} / ${formatMoney(amountCents)}`,
target: `${refundOrder.orderId} / ${formatAdminCurrencyCents(amountCents)}`,
});
if (!confirmed || submitLockRef.current) {
return;
@@ -721,15 +722,19 @@ export function AdminRechargeOrderPage({
<div className="admin-refund-summary-grid">
<Metric
label="订单实付"
value={formatMoney(refundOrder.paidAmountCents)}
value={formatAdminCurrencyCents(refundOrder.paidAmountCents)}
/>
<Metric
label="累计已退"
value={formatMoney(refundOrder.cumulativeSuccessRefundCents)}
value={formatAdminCurrencyCents(
refundOrder.cumulativeSuccessRefundCents,
)}
/>
<Metric
label="剩余可退"
value={formatMoney(refundOrder.remainingRefundableCents)}
value={formatAdminCurrencyCents(
refundOrder.remainingRefundableCents,
)}
/>
<Metric
label="永久泥点"
@@ -859,7 +864,13 @@ export function AdminRechargeOrderPage({
},
{
label: <>预检后剩余可退</>,
value: <>{formatMoney(preview.remainingRefundableCents)}</>,
value: (
<>
{formatAdminCurrencyCents(
preview.remainingRefundableCents,
)}
</>
),
},
]}
/>
@@ -984,16 +995,32 @@ export function AdminRechargeOrderPage({
},
{
label: <>本地订单总额</>,
value: <>{formatMoney(manualReviewTarget.order.amountCents)}</>,
value: (
<>
{formatAdminCurrencyCents(
manualReviewTarget.order.amountCents,
)}
</>
),
},
{
label: <>退款订单总额</>,
value: <>{formatMoney(manualReviewTarget.refund.totalCents)}</>,
value: (
<>
{formatAdminCurrencyCents(
manualReviewTarget.refund.totalCents,
)}
</>
),
},
{
label: <>本次退款金额</>,
value: (
<>{formatMoney(manualReviewTarget.refund.refundCents)}</>
<>
{formatAdminCurrencyCents(
manualReviewTarget.refund.refundCents,
)}
</>
),
},
]}
@@ -1107,7 +1134,7 @@ function RechargeOrderRow({
<td>
<strong>{formatOrderPaidAmount(order)}</strong>
{order.paidAmountCents > 0 ? null : (
<small>订单 {formatMoney(order.amountCents)}</small>
<small>订单 {formatAdminCurrencyCents(order.amountCents)}</small>
)}
</td>
<td>
@@ -1117,7 +1144,9 @@ function RechargeOrderRow({
) : null}
</td>
<td>
<span>累计 {formatMoney(order.cumulativeSuccessRefundCents)}</span>
<span>
累计 {formatAdminCurrencyCents(order.cumulativeSuccessRefundCents)}
</span>
<small>
应追回 {order.targetRecoveryPoints} / 已追回 {order.recoveredPoints}
</small>
@@ -1313,14 +1342,10 @@ function formatMembershipChangeSummary(
return `${action} ${planPath}${cycle}`;
}
function formatMoney(cents: number) {
return `¥${(cents / 100).toFixed(2)}`;
}
/** 实付只属于真正支付过的订单:未支付 / 已关闭 / 已过期订单显示“未支付”。 */
function formatOrderPaidAmount(order: AdminRechargeOrderEntryPayload) {
return order.paidAmountCents > 0
? formatMoney(order.paidAmountCents)
? formatAdminCurrencyCents(order.paidAmountCents)
: '未支付';
}
@@ -8,6 +8,7 @@ import {
AdminPage,
AdminPageHeading,
AdminPanel,
formatAdminCurrencyCents,
formatAdminDateTime,
} from '@genarrative/shared/components';
import { RefreshCcw, Save } from 'lucide-react';
@@ -333,7 +334,7 @@ export function AdminRechargeProductPage({
</AdminButton>
<small>{entry.productId}</small>
</td>
<td>{formatPrice(entry.priceCents)}</td>
<td>{formatAdminCurrencyCents(entry.priceCents)}</td>
<td>{formatProductContent(entry)}</td>
<td>{entry.enabled ? '启用' : '停用'}</td>
</tr>
@@ -384,10 +385,6 @@ function formatProductContent(
return `${entry.pointsAmount} 泥点`;
}
function formatPrice(priceCents: number) {
return `¥${(priceCents / 100).toFixed(2)}`;
}
function parseInteger(value: string) {
const parsed = Number.parseInt(value, 10);
if (!Number.isFinite(parsed)) {
@@ -1,6 +1,10 @@
import { describe, expect, test } from 'vitest';
import { formatAdminDate, formatAdminDateTime } from './AdminFormat';
import {
formatAdminCurrencyCents,
formatAdminDate,
formatAdminDateTime,
} from './AdminFormat';
describe('formatAdminDateTime', () => {
test('把后端的 seconds.microsZ 口径当成同一时刻渲染', () => {
@@ -44,3 +48,11 @@ describe('formatAdminDate', () => {
expect(formatAdminDate('1791008678.817257Z')).not.toBe('-');
});
});
describe('formatAdminCurrencyCents', () => {
test('分转成带两位小数的元', () => {
expect(formatAdminCurrencyCents(0)).toBe('¥0.00');
expect(formatAdminCurrencyCents(600)).toBe('¥6.00');
expect(formatAdminCurrencyCents(1234)).toBe('¥12.34');
});
});
@@ -54,6 +54,11 @@ export function formatAdminDateTime(
}
}
/** 后台统一的金额展示:分 → `¥12.34`;避免各后台页面各自实现导致格式漂移。 */
export function formatAdminCurrencyCents(cents: number): string {
return `¥${(cents / 100).toFixed(2)}`;
}
/** 后台统一的日期展示(不含时分秒):同样接受 `seconds.microsZ` 口径。 */
export function formatAdminDate(
value: string | number | Date | null | undefined,
@@ -19,7 +19,11 @@ export type {
AdminFormRowProps,
} from './AdminField';
export { AdminField, AdminFieldGroup, AdminFormRow } from './AdminField';
export { formatAdminDate, formatAdminDateTime } from './AdminFormat';
export {
formatAdminCurrencyCents,
formatAdminDate,
formatAdminDateTime,
} from './AdminFormat';
export type {
AdminInfoItemProps,
AdminInfoListEntry,