94d550db9b
- 调金币/现金:新增「设为指定值」模式;修 InputNumber addonAfter 废弃告警; 弹窗抽成共享组件 AdjustBalanceModals(列表页/详情页复用,内含防竞态余额拉取) - 新增 lib/format.ts 统一金额/时间格式化:区分 UTC 字段与钱包流水(北京 wall-clock), 修正提现详情现金流水快 8 小时、用户注册时间慢 8 小时等偏差 - 提现页:账本校验(ledger-check)改按需触发,写操作后不再重算;现金流水时区修正 - 用户详情页:加载失败 Result 兜底(不再永久转圈)+ 现金流水 tab + 调金币/现金/封禁入口 - 金币审计页:加「只看不一致」过滤;截断提示改用后端精确 truncated 旗标 - 用户列表:注册/最近登录/ID 列排序 + 渠道/昵称/时间范围筛选 + 批量封禁解封/开关调试链接 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: OuYingJun1024 <1034284404@qq.com> Reviewed-on: #9 Co-authored-by: ouzhou <ouzhou@wonderable.ai> Co-committed-by: ouzhou <ouzhou@wonderable.ai>
48 lines
2.1 KiB
TypeScript
48 lines
2.1 KiB
TypeScript
// 全站统一的金额 / 时间格式化。
|
|
//
|
|
// 后端有两种时间口径,序列化后从字符串无法区分,必须按字段语义选对函数:
|
|
// 1. UTC 口径:`server_default=func.now()` 的字段(用户/提现单/审计日志/广告发奖记录)。
|
|
// 入库是 UTC,SQLite 下序列化不带时区、Postgres 下带 +00:00。统一当 UTC 解析后转北京显示。
|
|
// 2. 北京 wall-clock 口径:钱包流水(coin_transaction / cash_transaction)。入库即
|
|
// `datetime.now(CN_TZ).replace(tzinfo=None)`,字面就是北京时间且无时区,**不能再做时区换算**。
|
|
// 历史上各页面混用 `new Date().toLocaleString`(按本地解析)与 `apiTime`(补 Z 当 UTC),
|
|
// 导致提现详情里现金流水时间快 8 小时。此模块收口,按口径区分。
|
|
import dayjs from 'dayjs';
|
|
import utc from 'dayjs/plugin/utc';
|
|
import timezone from 'dayjs/plugin/timezone';
|
|
import relativeTime from 'dayjs/plugin/relativeTime';
|
|
import 'dayjs/locale/zh-cn';
|
|
|
|
dayjs.extend(utc);
|
|
dayjs.extend(timezone);
|
|
dayjs.extend(relativeTime);
|
|
dayjs.locale('zh-cn');
|
|
|
|
const TZ = 'Asia/Shanghai';
|
|
|
|
/** 金额:分 → ¥元(两位小数)。 */
|
|
export const yuan = (cents: number) => `¥${(cents / 100).toFixed(2)}`;
|
|
|
|
const hasTz = (v: string) => /(?:Z|[+-]\d{2}:?\d{2})$/i.test(v);
|
|
|
|
/** 把 UTC 口径字符串解析为带时区的 dayjs(无时区后缀的补 Z 当 UTC)。 */
|
|
export const utcDayjs = (v: string) => dayjs(hasTz(v) ? v : `${v}Z`);
|
|
|
|
/** UTC 口径时间 → 北京时间显示(用户/提现单/审计/广告发奖记录)。 */
|
|
export function formatUtcTime(v?: string | null, fmt = 'YYYY-MM-DD HH:mm'): string {
|
|
if (!v) return '-';
|
|
return utcDayjs(v).tz(TZ).format(fmt);
|
|
}
|
|
|
|
/** UTC 口径相对时间(如「3 分钟前」)。 */
|
|
export function utcFromNow(v?: string | null): string {
|
|
if (!v) return '-';
|
|
return utcDayjs(v).fromNow();
|
|
}
|
|
|
|
/** 北京 wall-clock 口径时间 → 原样格式化,不做时区换算(钱包金币/现金流水)。 */
|
|
export function formatWallTime(v?: string | null, fmt = 'YYYY-MM-DD HH:mm'): string {
|
|
if (!v) return '-';
|
|
return dayjs(v).format(fmt);
|
|
}
|