From b8309eb3aee3f4613096e40b83f75492f0442938 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=BE=B7=E5=AE=87?= Date: Mon, 5 Oct 2026 18:12:22 +0800 Subject: [PATCH] =?UTF-8?q?=E9=87=8D=E6=9E=84(=E5=90=8E=E5=8F=B0=E9=87=91?= =?UTF-8?q?=E9=A2=9D):=20=E6=8A=BD=E5=87=BA=E7=BB=9F=E4=B8=80=E7=9A=84?= =?UTF-8?q?=E5=85=83=E9=87=91=E9=A2=9D=E6=A0=BC=E5=BC=8F=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - shared AdminFormat 新增 formatAdminCurrencyCents(分 → ¥x.xx)并从 components 导出 - AdminUserDetailDialog / AdminMembershipPlanPage / AdminRechargeOrderPage / AdminRechargeProductPage 删除各自重复的 formatMoney/formatPrice - 补充 formatAdminCurrencyCents 单测,避免后台各页面金额格式漂移 --- .../src/components/AdminUserDetailDialog.tsx | 13 ++--- .../src/pages/AdminMembershipPlanPage.tsx | 9 +-- .../src/pages/AdminRechargeOrderPage.tsx | 55 ++++++++++++++----- .../src/pages/AdminRechargeProductPage.tsx | 7 +-- .../src/components/admin/AdminFormat.test.ts | 14 ++++- .../src/components/admin/AdminFormat.ts | 5 ++ packages/shared/src/components/admin/index.ts | 6 +- 7 files changed, 74 insertions(+), 35 deletions(-) diff --git a/apps/admin-web/src/components/AdminUserDetailDialog.tsx b/apps/admin-web/src/components/AdminUserDetailDialog.tsx index 66e31cf27..f3a02184e 100644 --- a/apps/admin-web/src/components/AdminUserDetailDialog.tsx +++ b/apps/admin-web/src/components/AdminUserDetailDialog.tsx @@ -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({ {order.productTitle || order.productId} {order.paidAmountCents > 0 - ? formatMoney(order.paidAmountCents) + ? formatAdminCurrencyCents(order.paidAmountCents) : '未支付'} {order.pointsDelta} 泥点 - {formatMoney(order.cumulativeSuccessRefundCents)} + {formatAdminCurrencyCents( + order.cumulativeSuccessRefundCents, + )} 欠账 {order.unrecoveredPoints} 泥点 {formatOrderStatus(order.status)} @@ -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 '-'; diff --git a/apps/admin-web/src/pages/AdminMembershipPlanPage.tsx b/apps/admin-web/src/pages/AdminMembershipPlanPage.tsx index 75a9c3365..c9198234f 100644 --- a/apps/admin-web/src/pages/AdminMembershipPlanPage.tsx +++ b/apps/admin-web/src/pages/AdminMembershipPlanPage.tsx @@ -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({ - {formatPrice(entry.monthPriceCents)} /{' '} - {formatPrice(entry.yearPriceCents)} + {formatAdminCurrencyCents(entry.monthPriceCents)} /{' '} + {formatAdminCurrencyCents(entry.yearPriceCents)} {entry.periodPoints} {formatPlanLimits(entry)} @@ -460,7 +461,3 @@ function formatPlanLimits(entry: ProfileMembershipPlanAdminResponse) { : `${entry.concurrentJobLimit} 个`; return `${modelLabel} · 并发 ${concurrency}`; } - -function formatPrice(priceCents: number) { - return `¥${(priceCents / 100).toFixed(2)}`; -} diff --git a/apps/admin-web/src/pages/AdminRechargeOrderPage.tsx b/apps/admin-web/src/pages/AdminRechargeOrderPage.tsx index efca3ed1d..1d5bf6c42 100644 --- a/apps/admin-web/src/pages/AdminRechargeOrderPage.tsx +++ b/apps/admin-web/src/pages/AdminRechargeOrderPage.tsx @@ -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({
预检后剩余可退, - 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({ {formatOrderPaidAmount(order)} {order.paidAmountCents > 0 ? null : ( - 订单 {formatMoney(order.amountCents)} + 订单 {formatAdminCurrencyCents(order.amountCents)} )} @@ -1117,7 +1144,9 @@ function RechargeOrderRow({ ) : null} - 累计 {formatMoney(order.cumulativeSuccessRefundCents)} + + 累计 {formatAdminCurrencyCents(order.cumulativeSuccessRefundCents)} + 应追回 {order.targetRecoveryPoints} / 已追回 {order.recoveredPoints} @@ -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) : '未支付'; } diff --git a/apps/admin-web/src/pages/AdminRechargeProductPage.tsx b/apps/admin-web/src/pages/AdminRechargeProductPage.tsx index 4b6c3c0ea..a53ab5285 100644 --- a/apps/admin-web/src/pages/AdminRechargeProductPage.tsx +++ b/apps/admin-web/src/pages/AdminRechargeProductPage.tsx @@ -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({ {entry.productId} - {formatPrice(entry.priceCents)} + {formatAdminCurrencyCents(entry.priceCents)} {formatProductContent(entry)} {entry.enabled ? '启用' : '停用'} @@ -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)) { diff --git a/packages/shared/src/components/admin/AdminFormat.test.ts b/packages/shared/src/components/admin/AdminFormat.test.ts index 13b169cab..f3a8c7466 100644 --- a/packages/shared/src/components/admin/AdminFormat.test.ts +++ b/packages/shared/src/components/admin/AdminFormat.test.ts @@ -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'); + }); +}); diff --git a/packages/shared/src/components/admin/AdminFormat.ts b/packages/shared/src/components/admin/AdminFormat.ts index 21e10f666..d69a0edfc 100644 --- a/packages/shared/src/components/admin/AdminFormat.ts +++ b/packages/shared/src/components/admin/AdminFormat.ts @@ -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, diff --git a/packages/shared/src/components/admin/index.ts b/packages/shared/src/components/admin/index.ts index 8483c98ba..081fcb5ec 100644 --- a/packages/shared/src/components/admin/index.ts +++ b/packages/shared/src/components/admin/index.ts @@ -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,