Files
lotteryFront/src/lib/money.ts
kang b819894e75 feat: 增强 iframe 通信机制与通知处理功能
实现 resolvePostMessageTargetOrigin,优化 iframe 消息通信中的目标来源(origin)解析与校验。
更新 IframeBridge:支持定期刷新允许的来源列表,并优化消息事件管理机制。
重构 usePendingWalletReconcile:优化待对账通知的获取与缓存逻辑,提升性能与用户体验。
增强 NotificationsScreen:新增待对账通知内容,并优化界面展示效果。
更新英文、尼泊尔语与中文语言包,新增待对账通知相关翻译文案。
2026-06-01 13:38:30 +08:00

114 lines
3.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 与后端约定:金额存最小货币单位(如 NPR 2 位小数 → 分);展示时除以 10^displayDecimals。
* 展示分隔符与 {@link CurrencyFormatter} / 后台「货币格式」设置一致。
*/
import { getCurrencyDisplayFormat } from "@/lib/currency-display-settings";
import { usePlayerSessionStore } from "@/stores/player-session-store";
const DEFAULT_DECIMAL_PLACES = 2;
export function getCurrencyDecimalPlaces(currencyCode: string): number {
const code = currencyCode.trim().toUpperCase();
const row = usePlayerSessionStore
.getState()
.currencies.find((item) => item.code === code);
const decimals = row?.decimal_places;
if (typeof decimals === "number" && Number.isFinite(decimals) && decimals >= 0) {
return decimals;
}
return DEFAULT_DECIMAL_PLACES;
}
function formatIntegerWithThousands(value: number, thousandsSep: string): string {
const digits = String(Math.trunc(value));
if (!thousandsSep) return digits;
return digits.replace(/\B(?=(\d{3})+(?!\d))/g, thousandsSep);
}
function formatMinorWithDisplaySettings(
minorUnits: number,
format = getCurrencyDisplayFormat(),
): string {
const { displayDecimals, decimalSeparator, thousandsSeparator } = format;
const divisor = Math.max(1, 10 ** displayDecimals);
const negative = minorUnits < 0;
const abs = Math.abs(minorUnits);
const integerPart = Math.floor(abs / divisor);
const fractionRaw = abs % divisor;
const fractionPadded = String(fractionRaw).padStart(displayDecimals, "0");
const integerPartFormatted = formatIntegerWithThousands(
integerPart,
thousandsSeparator,
);
if (displayDecimals === 0) {
return `${negative ? "-" : ""}${integerPartFormatted}`;
}
return `${negative ? "-" : ""}${integerPartFormatted}${decimalSeparator}${fractionPadded}`;
}
export function formatMinorAsCurrency(
minor: number | string,
currencyCode: string,
decimalPlaces?: number,
): string {
const n = typeof minor === "string" ? Number(minor) : minor;
if (!Number.isFinite(n)) return `${currencyCode}`;
const format = getCurrencyDisplayFormat();
const resolvedDisplayDecimals =
typeof decimalPlaces === "number" &&
Number.isFinite(decimalPlaces) &&
decimalPlaces >= 0
? Math.min(12, Math.trunc(decimalPlaces))
: format.displayDecimals;
const amount = formatMinorWithDisplaySettings(n, {
...format,
displayDecimals: resolvedDisplayDecimals,
});
return `${currencyCode} ${amount}`;
}
/**
* 用户输入如 `1000` 或 `1,000.50` → 最小货币单位整数(按币种实际 decimal_places
*/
export function parseDecimalInputToMinor(
raw: string,
decimalPlacesOrCurrencyCode?: number | string,
): number | null {
const decimalPlaces =
typeof decimalPlacesOrCurrencyCode === "string"
? getCurrencyDecimalPlaces(decimalPlacesOrCurrencyCode)
: typeof decimalPlacesOrCurrencyCode === "number" &&
Number.isFinite(decimalPlacesOrCurrencyCode) &&
decimalPlacesOrCurrencyCode >= 0
? decimalPlacesOrCurrencyCode
: decimalPlacesOrCurrencyCode;
const resolvedDecimalPlaces = decimalPlaces ?? DEFAULT_DECIMAL_PLACES;
const format = getCurrencyDisplayFormat();
let cleaned = raw.trim();
if (format.thousandsSeparator) {
cleaned = cleaned.split(format.thousandsSeparator).join("");
}
if (format.decimalSeparator && format.decimalSeparator !== ".") {
cleaned = cleaned.replaceAll(format.decimalSeparator, ".");
}
cleaned = cleaned.replace(/,/g, "");
if (cleaned === "") return null;
const n = Number(cleaned);
if (!Number.isFinite(n) || n < 0) return null;
const factor = 10 ** resolvedDecimalPlaces;
const minor = Math.round(n * factor);
if (!Number.isSafeInteger(minor)) return null;
return minor;
}