时区问题

This commit is contained in:
zhx 2026-06-07 14:51:01 +08:00
parent 2118a2ee4d
commit e2c0565dbe
11 changed files with 82 additions and 25 deletions

View File

@ -2,7 +2,7 @@ import { useMemo } from "react";
import { useNavigate } from "react-router-dom";
import { useAuth } from "@/app/auth/AuthProvider.jsx";
import { getFrequentSecondLevelMenus } from "@/app/navigation/menuUsage.js";
import { formatMillis } from "@/shared/utils/time.js";
import { TimeText } from "@/shared/ui/TimeText.jsx";
export function DashboardFrequentMenus({ menus = [] }) {
const { user } = useAuth();
@ -32,7 +32,7 @@ export function DashboardFrequentMenus({ menus = [] }) {
<span className="frequent-menu-card__group">{item.parentLabel}</span>
<strong>{item.label}</strong>
<span className="frequent-menu-card__meta">
进入 {item.count || 0} · {formatMillis(item.lastVisitedAtMs)}
进入 {item.count || 0} · <TimeText value={item.lastVisitedAtMs} />
</span>
</button>
);

View File

@ -5,7 +5,7 @@ import MenuItem from "@mui/material/MenuItem";
import TextField from "@mui/material/TextField";
import { AdminActionIconButton } from "@/shared/ui/AdminListLayout.jsx";
import { Button } from "@/shared/ui/Button.jsx";
import { formatMillis } from "@/shared/utils/time.js";
import { TimeText } from "@/shared/ui/TimeText.jsx";
import { stageOptions } from "@/features/lucky-gift/constants.js";
import { formFromConfig } from "@/features/lucky-gift/configModel.js";
import styles from "@/features/lucky-gift/lucky-gift.module.css";
@ -44,7 +44,7 @@ export function LuckyGiftConfigSummary({
<SummaryItem label="房间小时">{formatNumber(config?.roomHourlyPayoutCap)}</SummaryItem>
<SummaryItem label="阶段">{formatStages(form.stages)}</SummaryItem>
<SummaryItem label="更新时间">
{config?.createdAtMs ? formatMillis(config.createdAtMs) : configLoading ? "加载中" : "-"}
{config?.createdAtMs ? <TimeText value={config.createdAtMs} /> : configLoading ? "加载中" : "-"}
</SummaryItem>
</div>
</div>

View File

@ -12,8 +12,8 @@ import { DataTable } from "@/shared/ui/DataTable.jsx";
import { SideDrawer } from "@/shared/ui/SideDrawer.jsx";
import { usePaginatedQuery } from "@/shared/hooks/usePaginatedQuery.js";
import { TimeRangeFilter } from "@/shared/ui/TimeRangeFilter.jsx";
import { TimeText } from "@/shared/ui/TimeText.jsx";
import { createTextColumnFilter } from "@/shared/ui/tableFilters.js";
import { formatMillis } from "@/shared/utils/time.js";
import styles from "@/features/operations/operations.module.css";
const pageSize = 50;
@ -47,7 +47,7 @@ const columns = [
key: "createdAtMs",
label: "时间",
width: "minmax(170px, 0.85fr)",
render: (entry) => formatMillis(entry.createdAtMs),
render: (entry) => <TimeText value={entry.createdAtMs} />,
},
];
@ -252,7 +252,7 @@ function CoinLedgerDetailDrawer({ entry, onClose, open }) {
detailRow("流水 ID", entry.entryId),
detailRow("业务类型", bizTypeLabel(entry.bizType)),
detailRow("方向", directionLabel(entry.direction)),
detailRow("发生时间", formatMillis(entry.createdAtMs)),
detailRow("发生时间", <TimeText value={entry.createdAtMs} />),
]}
title="交易信息"
/>

View File

@ -3,7 +3,7 @@ import Drawer from "@mui/material/Drawer";
import IconButton from "@mui/material/IconButton";
import { DataState } from "@/shared/ui/DataState.jsx";
import { DataTable } from "@/shared/ui/DataTable.jsx";
import { formatMillis } from "@/shared/utils/time.js";
import { TimeText } from "@/shared/ui/TimeText.jsx";
import styles from "@/features/red-packets/red-packets.module.css";
const claimColumns = [
@ -29,7 +29,7 @@ const claimColumns = [
key: "createdAt",
label: "领取时间",
width: "minmax(180px, 0.9fr)",
render: (claim) => formatMillis(claim.createdAtMs),
render: (claim) => <TimeText value={claim.createdAtMs} />,
},
];
@ -67,8 +67,8 @@ export function RedPacketDetailDrawer({ loading, onClose, open, packet }) {
: "-"
}
/>
<InfoRow label="开启时间" value={formatMillis(packet.openAtMs)} />
<InfoRow label="过期时间" value={formatMillis(packet.expiresAtMs)} />
<InfoRow label="开启时间" value={<TimeText value={packet.openAtMs} />} />
<InfoRow label="过期时间" value={<TimeText value={packet.expiresAtMs} />} />
<DataTable
columns={claimColumns}
emptyLabel="暂无领取记录"

View File

@ -2,7 +2,7 @@ import EditOutlined from "@mui/icons-material/EditOutlined";
import RefreshOutlined from "@mui/icons-material/RefreshOutlined";
import { Button } from "@/shared/ui/Button.jsx";
import { AdminActionIconButton } from "@/shared/ui/AdminListLayout.jsx";
import { formatMillis } from "@/shared/utils/time.js";
import { TimeText } from "@/shared/ui/TimeText.jsx";
import styles from "@/features/room-rocket/room-rocket.module.css";
const broadcastScopeLabels = {
@ -33,7 +33,7 @@ export function RoomRocketConfigSummary({ canUpdate, config, configLoading, onEd
</SummaryItem>
<SummaryItem label="角色奖励">{rewardStackPolicyLabels[config?.rewardStackPolicy] || "-"}</SummaryItem>
<SummaryItem label="更新时间">
{config?.updatedAtMs ? formatMillis(config.updatedAtMs) : configLoading ? "加载中" : "-"}
{config?.updatedAtMs ? <TimeText value={config.updatedAtMs} /> : configLoading ? "加载中" : "-"}
</SummaryItem>
</div>
<LevelPreview config={config} loading={configLoading} />

View File

@ -2,6 +2,7 @@ import EditOutlined from "@mui/icons-material/EditOutlined";
import RefreshOutlined from "@mui/icons-material/RefreshOutlined";
import CircularProgress from "@mui/material/CircularProgress";
import { Button } from "@/shared/ui/Button.jsx";
import { TimeText } from "@/shared/ui/TimeText.jsx";
import styles from "@/features/room-turnover-reward/room-turnover-reward.module.css";
export function RoomTurnoverRewardConfigSummary({ canUpdate, config, configLoading, onEdit, onRefresh }) {
@ -31,7 +32,9 @@ export function RoomTurnoverRewardConfigSummary({ canUpdate, config, configLoadi
</div>
<div className={styles.summaryItem}>
<span className={styles.summaryLabel}>更新时间</span>
<span className={styles.summaryValue}>{config?.updatedAtMs ? config.updatedAtMs : "-"}</span>
<span className={styles.summaryValue}>
{config?.updatedAtMs ? <TimeText value={config.updatedAtMs} /> : configLoading ? "加载中" : "-"}
</span>
</div>
</div>
<div className={styles.summaryActions}>

View File

@ -0,0 +1,52 @@
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { beforeEach, expect, test } from "vitest";
import { TimeZoneProvider, useTimeZone } from "@/app/timezone/TimeZoneProvider.jsx";
import { RoomTurnoverRewardConfigSummary } from "./RoomTurnoverRewardConfigSummary.jsx";
const updatedAtMs = Date.UTC(2026, 5, 7, 5, 4, 13);
beforeEach(() => {
window.localStorage.clear();
});
test("formats summary update time through the selected admin timezone", async () => {
const user = userEvent.setup();
render(
<TimeZoneProvider>
<SummaryFixture />
</TimeZoneProvider>
);
expect(screen.queryByText(String(updatedAtMs))).not.toBeInTheDocument();
expect(screen.getByText("2026-06-07 13:04:13")).toBeInTheDocument();
await user.click(screen.getByRole("button", { name: "UTC" }));
expect(screen.getByText("2026-06-07 05:04:13")).toBeInTheDocument();
expect(screen.queryByText(String(updatedAtMs))).not.toBeInTheDocument();
});
function SummaryFixture() {
const { setTimeZone } = useTimeZone();
return (
<>
<button type="button" onClick={() => setTimeZone("UTC")}>
UTC
</button>
<RoomTurnoverRewardConfigSummary
canUpdate
config={{
enabled: true,
tiers: [{ rewardCoinAmount: 50, status: "active" }],
updatedAtMs
}}
configLoading={false}
onEdit={() => {}}
onRefresh={() => {}}
/>
</>
);
}

View File

@ -7,7 +7,7 @@ import { AdminActionIconButton, AdminListBody, AdminListPage } from "@/shared/ui
import { DataState } from "@/shared/ui/DataState.jsx";
import { DataTable } from "@/shared/ui/DataTable.jsx";
import { createOptionsColumnFilter, createTextColumnFilter } from "@/shared/ui/tableFilters.js";
import { formatMillis } from "@/shared/utils/time.js";
import { TimeText } from "@/shared/ui/TimeText.jsx";
const settlementStatusOptions = [
["pending", "待发放"],
@ -33,8 +33,10 @@ const columnsBase = [
width: "minmax(260px, 1.1fr)",
render: (item) => (
<div className={styles.stack}>
<span>{formatMillis(item.periodStartMs)}</span>
<span className={styles.meta}> {formatMillis(item.periodEndMs)}</span>
<TimeText value={item.periodStartMs} />
<span className={styles.meta}>
<TimeText value={item.periodEndMs} />
</span>
</div>
),
},

View File

@ -3,7 +3,7 @@ import MeetingRoomOutlined from "@mui/icons-material/MeetingRoomOutlined";
import PersonOutlineOutlined from "@mui/icons-material/PersonOutlineOutlined";
import { useEffect, useState } from "react";
import { SideDrawer } from "@/shared/ui/SideDrawer.jsx";
import { formatMillis } from "@/shared/utils/time.js";
import { TimeText } from "@/shared/ui/TimeText.jsx";
import { roomStatusLabels } from "@/features/rooms/constants.js";
import styles from "@/features/rooms/rooms.module.css";
@ -37,8 +37,8 @@ export function RoomDetailDrawer({ onClose, open, room }) {
["房间状态", roomStatusLabels[normalizedStatus]],
["房间模式", room.mode],
["区域", room.regionName],
["创建时间", formatMillis(room.createdAtMs)],
["更新时间", formatMillis(room.updatedAtMs)]
["创建时间", <TimeText key="created-at" value={room.createdAtMs} />],
["更新时间", <TimeText key="updated-at" value={room.updatedAtMs} />]
]}
title="基础信息"
/>
@ -47,7 +47,7 @@ export function RoomDetailDrawer({ onClose, open, room }) {
<DetailSection
items={[
["操作人", closeOperator],
["操作时间", formatMillis(room.closedAtMs)],
["操作时间", <TimeText key="closed-at" value={room.closedAtMs} />],
["关闭原因", room.closeReason]
]}
title="关闭信息"

View File

@ -6,7 +6,7 @@ import MenuItem from "@mui/material/MenuItem";
import TextField from "@mui/material/TextField";
import { DataState } from "@/shared/ui/DataState.jsx";
import { AdminActionIconButton, AdminListBody, AdminListPage, AdminListToolbar } from "@/shared/ui/AdminListLayout.jsx";
import { formatMillis } from "@/shared/utils/time.js";
import { TimeText } from "@/shared/ui/TimeText.jsx";
import { useRoomConfigPage } from "@/features/rooms/hooks/useRoomConfigPage.js";
import styles from "@/features/rooms/rooms.module.css";
@ -45,7 +45,7 @@ export function RoomConfigPage() {
<div className={styles.configTitle}>
<h2>座位数</h2>
<span>
{page.form.updatedAtMs ? formatMillis(page.form.updatedAtMs) : "默认配置"}
{page.form.updatedAtMs ? <TimeText value={page.form.updatedAtMs} /> : "默认配置"}
</span>
</div>
</div>

View File

@ -2,7 +2,7 @@ import EditOutlined from "@mui/icons-material/EditOutlined";
import RefreshOutlined from "@mui/icons-material/RefreshOutlined";
import { Button } from "@/shared/ui/Button.jsx";
import { AdminActionIconButton } from "@/shared/ui/AdminListLayout.jsx";
import { formatMillis } from "@/shared/utils/time.js";
import { TimeText } from "@/shared/ui/TimeText.jsx";
import styles from "@/features/seven-day-checkin/seven-day-checkin.module.css";
export function SevenDayCheckInConfigSummary({ canUpdate, config, configLoading, onEdit, onRefresh, resourceGroups }) {
@ -16,7 +16,7 @@ export function SevenDayCheckInConfigSummary({ canUpdate, config, configLoading,
<div className={styles.summaryItems}>
<ConfigStatus config={config} loading={configLoading} />
<SummaryItem label="更新时间">
{config?.updatedAtMs ? formatMillis(config.updatedAtMs) : configLoading ? "加载中" : "-"}
{config?.updatedAtMs ? <TimeText value={config.updatedAtMs} /> : configLoading ? "加载中" : "-"}
</SummaryItem>
</div>
<RewardPreview config={config} loading={configLoading} resourceGroups={resourceGroups} />