From a7a0a5468800a0c9d7d612ac0ced1a071a3caa8a Mon Sep 17 00:00:00 2001 From: jacy <18049821889@163.com> Date: Mon, 3 Aug 2026 14:53:54 +0800 Subject: [PATCH] =?UTF-8?q?fix(mini-user):=20=E5=BE=AE=E4=BF=A1=E5=B0=8F?= =?UTF-8?q?=E7=A8=8B=E5=BA=8F=E8=A1=A5=20Intl=20=E5=85=9C=E5=BA=95?= =?UTF-8?q?=E5=B9=B6=E5=8E=BB=E6=8E=89=20toLocaleString?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Cursor --- apps/mini-user/src/app.tsx | 1 + apps/mini-user/src/lib/intl-polyfill.ts | 72 +++++++++++++++++++ apps/mini-user/src/lib/money.ts | 9 +++ apps/mini-user/src/pages/benefit/index.tsx | 5 +- apps/mini-user/src/pages/mine/index.tsx | 5 +- .../mini-user/src/pages/redeem-code/index.tsx | 5 +- .../src/pages/redeem-success/index.tsx | 10 +-- apps/mini-user/src/pages/redeem/index.tsx | 5 +- .../src/pages/store-detail/index.tsx | 7 +- 9 files changed, 93 insertions(+), 26 deletions(-) create mode 100644 apps/mini-user/src/lib/intl-polyfill.ts create mode 100644 apps/mini-user/src/lib/money.ts diff --git a/apps/mini-user/src/app.tsx b/apps/mini-user/src/app.tsx index a65b356..bd8cca0 100644 --- a/apps/mini-user/src/app.tsx +++ b/apps/mini-user/src/app.tsx @@ -1,3 +1,4 @@ +import './lib/intl-polyfill'; import './lib/text-encoding-polyfill'; import { PropsWithChildren, useRef } from 'react'; import Taro, { useDidShow } from '@tarojs/taro'; diff --git a/apps/mini-user/src/lib/intl-polyfill.ts b/apps/mini-user/src/lib/intl-polyfill.ts new file mode 100644 index 0000000..0858baa --- /dev/null +++ b/apps/mini-user/src/lib/intl-polyfill.ts @@ -0,0 +1,72 @@ +/** + * 微信小程序基础库无 Intl。业务代码已避免依赖,此处仅作兜底, + * 防止旧包 / 依赖偶发 `new Intl.DateTimeFormat` 直接白屏。 + */ +function pad(n: number) { + return String(n).padStart(2, '0'); +} + +function shanghaiParts(date: Date) { + const sh = new Date(date.getTime() + 8 * 60 * 60 * 1000); + return { + year: String(sh.getUTCFullYear()), + month: pad(sh.getUTCMonth() + 1), + day: pad(sh.getUTCDate()), + hour: pad(sh.getUTCHours()), + minute: pad(sh.getUTCMinutes()), + second: pad(sh.getUTCSeconds()), + }; +} + +function installIntlStub() { + const root = (typeof globalThis !== 'undefined' + ? globalThis + : typeof global !== 'undefined' + ? global + : typeof wx !== 'undefined' + ? wx + : {}) as typeof globalThis & { Intl?: typeof Intl }; + + if (typeof root.Intl !== 'undefined' && typeof root.Intl.DateTimeFormat === 'function') { + return; + } + + class MiniDateTimeFormat { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + constructor(_locales?: string | string[], _options?: Record) {} + + format(date?: Date | number) { + const d = date instanceof Date ? date : new Date(date ?? Date.now()); + if (Number.isNaN(d.getTime())) return ''; + const p = shanghaiParts(d); + return `${p.year}-${p.month}-${p.day} ${p.hour}:${p.minute}:${p.second}`; + } + + formatToParts(date?: Date | number) { + const d = date instanceof Date ? date : new Date(date ?? Date.now()); + if (Number.isNaN(d.getTime())) return []; + const p = shanghaiParts(d); + return [ + { type: 'year', value: p.year }, + { type: 'literal', value: '-' }, + { type: 'month', value: p.month }, + { type: 'literal', value: '-' }, + { type: 'day', value: p.day }, + { type: 'literal', value: ' ' }, + { type: 'hour', value: p.hour }, + { type: 'literal', value: ':' }, + { type: 'minute', value: p.minute }, + { type: 'literal', value: ':' }, + { type: 'second', value: p.second }, + ]; + } + } + + root.Intl = { + DateTimeFormat: MiniDateTimeFormat, + } as unknown as typeof Intl; +} + +installIntlStub(); + +export {}; diff --git a/apps/mini-user/src/lib/money.ts b/apps/mini-user/src/lib/money.ts new file mode 100644 index 0000000..1b500dd --- /dev/null +++ b/apps/mini-user/src/lib/money.ts @@ -0,0 +1,9 @@ +/** 金额展示(不依赖 Intl / toLocaleString,兼容微信小程序) */ +export function formatMoney(amount: number | string): string { + const n = typeof amount === 'number' ? amount : Number(amount); + if (!Number.isFinite(n)) return '0.00'; + const fixed = n.toFixed(2); + const [intPart, dec] = fixed.split('.'); + const withComma = intPart.replace(/\B(?=(\d{3})+(?!\d))/g, ','); + return `${withComma}.${dec}`; +} diff --git a/apps/mini-user/src/pages/benefit/index.tsx b/apps/mini-user/src/pages/benefit/index.tsx index 85e034c..83c4108 100644 --- a/apps/mini-user/src/pages/benefit/index.tsx +++ b/apps/mini-user/src/pages/benefit/index.tsx @@ -12,6 +12,7 @@ import { DEFAULT_SHARE_TITLE, toWeappShareMessage, } from '../../lib/wechat-share'; +import { formatMoney } from '../../lib/money'; import iconBenefit from '../../assets/tabbar/benefit-active.png'; type BenefitSummary = { @@ -38,10 +39,6 @@ type RedeemHistoryItem = { createdAt: string; }; -function formatMoney(amount: number) { - return amount.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 }); -} - function usagePercent(coupon: CouponItem) { const total = Number(coupon.totalAmount); if (total <= 0) return 0; diff --git a/apps/mini-user/src/pages/mine/index.tsx b/apps/mini-user/src/pages/mine/index.tsx index 397e29d..6257404 100644 --- a/apps/mini-user/src/pages/mine/index.tsx +++ b/apps/mini-user/src/pages/mine/index.tsx @@ -37,6 +37,7 @@ import iconStores from '../../assets/icons/可用门店.png'; import iconCs from '../../assets/icons/联系客服.png'; import iconQualification from '../../assets/icons/资质公示.png'; import iconAbout from '../../assets/icons/关于我们.png'; +import { formatMoney } from '../../lib/money'; const ORDER_SHORTCUTS = [ { tab: 'pending_pay', icon: iconPendingPay, label: '待付款' }, @@ -54,10 +55,6 @@ const SERVICES = [ const isWeapp = process.env.TARO_ENV === 'weapp'; -function formatMoney(amount: number) { - return amount.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 }); -} - export default function MinePage() { const [authed, setAuthed] = useState(() => isLoggedIn()); const [profile, setProfile] = useState(null); diff --git a/apps/mini-user/src/pages/redeem-code/index.tsx b/apps/mini-user/src/pages/redeem-code/index.tsx index e7cde41..0e7e23b 100644 --- a/apps/mini-user/src/pages/redeem-code/index.tsx +++ b/apps/mini-user/src/pages/redeem-code/index.tsx @@ -6,6 +6,7 @@ import PageShell from '../../components/PageShell'; import RedeemQrCode from '../../components/RedeemQrCode'; import SubPageHeader from '../../components/SubPageHeader'; import { request, toast } from '../../lib/api'; +import { formatMoney } from '../../lib/money'; const POLL_INTERVAL_MS = 2500; const LAST_REDEEM_RECORD_KEY = 'lastRedeemRecordId'; @@ -26,10 +27,6 @@ type RedeemTokenStatus = } | { status: 'EXPIRED' }; -function formatMoney(amount: number) { - return amount.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 }); -} - function formatTimer(seconds: number) { const mins = Math.floor(seconds / 60); const secs = seconds % 60; diff --git a/apps/mini-user/src/pages/redeem-success/index.tsx b/apps/mini-user/src/pages/redeem-success/index.tsx index bcf392f..890640b 100644 --- a/apps/mini-user/src/pages/redeem-success/index.tsx +++ b/apps/mini-user/src/pages/redeem-success/index.tsx @@ -5,6 +5,7 @@ import PageShell from '../../components/PageShell'; import SubPageHeader from '../../components/SubPageHeader'; import { request, toast } from '../../lib/api'; import { formatShanghaiDateTime } from '../../lib/datetime'; +import { formatMoney } from '../../lib/money'; const LAST_REDEEM_RESULT_KEY = 'lastRedeemResult'; @@ -17,15 +18,6 @@ type RedeemRecord = { createdAt: string; }; -function formatMoney(amount: number) { - const n = Number(amount); - if (!Number.isFinite(n)) return '0.00'; - const fixed = n.toFixed(2); - const [intPart, dec] = fixed.split('.'); - const withComma = intPart.replace(/\B(?=(\d{3})+(?!\d))/g, ','); - return `${withComma}.${dec}`; -} - /** 与权益「历史记录」一致:2026-08-03 13:53:03(Asia/Shanghai) */ function formatChinaDateTime(input?: string | null) { return formatShanghaiDateTime(input ?? new Date()); diff --git a/apps/mini-user/src/pages/redeem/index.tsx b/apps/mini-user/src/pages/redeem/index.tsx index 86206dc..c7af0c0 100644 --- a/apps/mini-user/src/pages/redeem/index.tsx +++ b/apps/mini-user/src/pages/redeem/index.tsx @@ -5,6 +5,7 @@ import PageShell from '../../components/PageShell'; import SubPageHeader from '../../components/SubPageHeader'; import { goLogin } from '../../lib/auth-nav'; import { isLoggedIn, request, toast } from '../../lib/api'; +import { formatMoney } from '../../lib/money'; type BenefitSummary = { totalBalance: number; @@ -13,10 +14,6 @@ type BenefitSummary = { const MIN_REDEEM_AMOUNT = 0.01; -function formatMoney(amount: number) { - return amount.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 }); -} - /** * 核销金额输入清洗: * - 只保留数字与一个小数点 diff --git a/apps/mini-user/src/pages/store-detail/index.tsx b/apps/mini-user/src/pages/store-detail/index.tsx index 0b03c8f..a482e62 100644 --- a/apps/mini-user/src/pages/store-detail/index.tsx +++ b/apps/mini-user/src/pages/store-detail/index.tsx @@ -104,9 +104,14 @@ function formatRedeemAmountYuan(amount: number | string) { function formatRecentRedeemLine(row: RecentRedeem) { try { + // 优先用服务端拼好的 text,避免客户端时区 / Intl 差异 if (row.text?.trim()) return row.text.trim(); const label = String(row.userLabel || '用户***').trim() || '用户***'; - const time = formatRedeemTime(row.createdAt); + const rawTime = String(row.createdAt || '').trim(); + const time = + /^\d{4}-\d{2}-\d{2}/.test(rawTime) + ? rawTime.slice(0, 19).replace('T', ' ') + : formatRedeemTime(row.createdAt); const amount = formatRedeemAmountYuan(row.amount); return `${label} ${time} 核销${amount}元`; } catch {