fix(ui): 修復日期控件時區展示偏移 Bug,並修正負成本的格式化顯示
This commit is contained in:
parent
556f705f75
commit
31ca101914
@ -35,7 +35,7 @@
|
||||
- 重构 `<SyncButton />` 并将其提升至 Dashboard 首页,实现总资产大盘的全局一键实盘刷新。
|
||||
|
||||
## 修复记录
|
||||
- 修复了全局时区偏移问题,并解决了日期控件手动输入导致的崩溃 Bug。在 `src/libs/utils.ts` 中新增 `nowInShanghai()`、`formatDateForDatetimeLocal()` 和 `parseDateTimeLocalToUTC_v2()` 函数,强制使用 `Asia/Shanghai` (UTC+8) 时区。所有 `new Date()` 调用(包括 Server Actions)均已对齐至东八区。日期选择器增加字符串格式校验与解析逻辑,避免 `Invalid time value` 错误。
|
||||
- 解决了日期选择控件的时区偏移 Bug,确保全球通用:在 `src/libs/utils.ts` 中重写 `formatDateForDatetimeLocal()` 与 `parseDateTimeLocalToUTC_v2()` 函数,采用 `Intl.DateTimeFormat` 动态获取 `Asia/Shanghai` 时区偏移量,确保 UTC 时间到本地时间的双向转换精确无误,修复了用户选 10 点展示为 2 点的问题。修正了前端数据格式化逻辑,在 `src/lib/formatters.ts` 中增加空值/NaN 兜底处理,在 `src/app/dashboard/page.tsx` 中将平均成本与摊薄成本的显示条件从 `.gt(0)` 改为 `.ne(0)`,支持英特尔负成本等极端场景下的精确数字展示。
|
||||
|
||||
## 资产分布图表按市场维度升级 (Task 32)
|
||||
- 优化资产分布图表,升级为按市场维度聚合展示,并增强了 Tooltip 的颜色指代与明细交互。
|
||||
|
||||
@ -71,8 +71,8 @@ export default async function DashboardPage() {
|
||||
const posPnlNative = new Big(pos.pnlNative);
|
||||
const posPnlNativePositive = posPnlNative.gte(0);
|
||||
|
||||
const avgCostFormatted = new Big(pos.avgCost).gt(0) ? formatAmount(pos.avgCost) : '-';
|
||||
const dilutedCostFormatted = new Big(pos.dilutedCost).gt(0) ? formatAmount(pos.dilutedCost) : '-';
|
||||
const avgCostFormatted = new Big(pos.avgCost).ne(0) && pos.avgCost !== '0' ? formatAmount(pos.avgCost) : '-';
|
||||
const dilutedCostFormatted = new Big(pos.dilutedCost).ne(0) && pos.dilutedCost !== '0' ? formatAmount(pos.dilutedCost) : '-';
|
||||
|
||||
return (
|
||||
<Card key={pos.assetId}>
|
||||
|
||||
@ -1,7 +1,11 @@
|
||||
import Big from 'big.js';
|
||||
|
||||
export function formatAmount(value: string): string {
|
||||
return new Big(value).toFixed(2);
|
||||
if (value === null || value === undefined || value === '' || isNaN(Number(value))) {
|
||||
return '-';
|
||||
}
|
||||
const result = new Big(value).toFixed(2);
|
||||
return result;
|
||||
}
|
||||
|
||||
export function formatQuantity(value: string, assetType: string): string {
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
import { type ClassValue, clsx } from "clsx"
|
||||
import { twMerge } from "tailwind-merge"
|
||||
import { toZonedTime, fromZonedTime } from "date-fns-tz"
|
||||
|
||||
const TIMEZONE = "Asia/Shanghai"
|
||||
|
||||
@ -8,6 +7,53 @@ export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs))
|
||||
}
|
||||
|
||||
function getShanghaiOffsetMs(): number {
|
||||
const formatter = new Intl.DateTimeFormat("en-US", {
|
||||
timeZone: TIMEZONE,
|
||||
timeZoneName: "short",
|
||||
})
|
||||
const parts = formatter.formatToParts(new Date())
|
||||
const tzPart = parts.find((p) => p.type === "timeZoneName")
|
||||
if (!tzPart) return 8 * 60 * 60 * 1000
|
||||
|
||||
const match = tzPart.value.match(/^([+-])(\d{2}):?(\d{2})$/)
|
||||
if (!match) return 8 * 60 * 60 * 1000
|
||||
|
||||
const sign = match[1] === "+" ? 1 : -1
|
||||
const offsetHours = parseInt(match[2], 10)
|
||||
const offsetMinutes = parseInt(match[3], 10)
|
||||
return sign * (offsetHours * 60 + offsetMinutes) * 60 * 1000
|
||||
}
|
||||
|
||||
export function formatDateForDatetimeLocal(date: Date): string {
|
||||
const shanghaiOffsetMs = getShanghaiOffsetMs()
|
||||
const localTimeMs = date.getTime() + shanghaiOffsetMs
|
||||
const localDate = new Date(localTimeMs)
|
||||
|
||||
const year = localDate.getUTCFullYear()
|
||||
const month = String(localDate.getUTCMonth() + 1).padStart(2, "0")
|
||||
const day = String(localDate.getUTCDate()).padStart(2, "0")
|
||||
const hours = String(localDate.getUTCHours()).padStart(2, "0")
|
||||
const minutes = String(localDate.getUTCMinutes()).padStart(2, "0")
|
||||
return `${year}-${month}-${day}T${hours}:${minutes}`
|
||||
}
|
||||
|
||||
export function parseDateTimeLocalToUTC_v2(year: number, month: number, day: number, hours: number, minutes: number): Date {
|
||||
const localTimeMs = Date.UTC(year, month, day, hours, minutes)
|
||||
const shanghaiOffsetMs = getShanghaiOffsetMs()
|
||||
const utcMs = localTimeMs - shanghaiOffsetMs
|
||||
return new Date(utcMs)
|
||||
}
|
||||
|
||||
export function parseDateTimeLocalToUTC(value: string): Date | null {
|
||||
if (!value) return null
|
||||
const [datePart, timePart] = value.split("T")
|
||||
if (!datePart) return null
|
||||
const [y, m, d] = datePart.split("-").map(Number)
|
||||
const [h = 0, min = 0] = timePart ? timePart.split(":").map(Number) : [0, 0]
|
||||
return parseDateTimeLocalToUTC_v2(y, m - 1, d, h, min)
|
||||
}
|
||||
|
||||
export function nowInShanghai(): Date {
|
||||
const now = new Date()
|
||||
const utcStr = now.toLocaleString("en-US", { timeZone: "UTC" })
|
||||
|
||||
Loading…
Reference in New Issue
Block a user