feat: 时间时区使用浏览器时区

This commit is contained in:
hzm
2026-02-17 12:37:27 +08:00
parent e93dedca9f
commit 279988cefd
3 changed files with 55 additions and 8 deletions

View File

@@ -4,9 +4,17 @@ import timezone from 'dayjs/plugin/timezone';
dayjs.extend(utc); dayjs.extend(utc);
dayjs.extend(timezone); dayjs.extend(timezone);
dayjs.tz.setDefault('Asia/Shanghai');
const TZ = 'Asia/Shanghai'; const DEFAULT_TZ = 'Asia/Shanghai';
const getBrowserTimeZone = () => {
if (typeof Intl !== 'undefined' && Intl.DateTimeFormat) {
const tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
return tz || DEFAULT_TZ;
}
return DEFAULT_TZ;
};
const TZ = getBrowserTimeZone();
dayjs.tz.setDefault(TZ);
const nowInTz = () => dayjs().tz(TZ); const nowInTz = () => dayjs().tz(TZ);
const toTz = (input) => (input ? dayjs.tz(input, TZ) : nowInTz()); const toTz = (input) => (input ? dayjs.tz(input, TZ) : nowInTz());

View File

@@ -11,9 +11,17 @@ import { CalendarIcon, MinusIcon, PlusIcon } from './Icons';
dayjs.extend(utc); dayjs.extend(utc);
dayjs.extend(timezone); dayjs.extend(timezone);
dayjs.tz.setDefault('Asia/Shanghai');
const TZ = 'Asia/Shanghai'; const DEFAULT_TZ = 'Asia/Shanghai';
const getBrowserTimeZone = () => {
if (typeof Intl !== 'undefined' && Intl.DateTimeFormat) {
const tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
return tz || DEFAULT_TZ;
}
return DEFAULT_TZ;
};
const TZ = getBrowserTimeZone();
dayjs.tz.setDefault(TZ);
const nowInTz = () => dayjs().tz(TZ); const nowInTz = () => dayjs().tz(TZ);
const toTz = (input) => (input ? dayjs.tz(input, TZ) : nowInTz()); const toTz = (input) => (input ? dayjs.tz(input, TZ) : nowInTz());
const formatDate = (input) => toTz(input).format('YYYY-MM-DD'); const formatDate = (input) => toTz(input).format('YYYY-MM-DD');

View File

@@ -17,9 +17,17 @@ import packageJson from '../package.json';
dayjs.extend(utc); dayjs.extend(utc);
dayjs.extend(timezone); dayjs.extend(timezone);
dayjs.tz.setDefault('Asia/Shanghai');
const TZ = 'Asia/Shanghai'; const DEFAULT_TZ = 'Asia/Shanghai';
const getBrowserTimeZone = () => {
if (typeof Intl !== 'undefined' && Intl.DateTimeFormat) {
const tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
return tz || DEFAULT_TZ;
}
return DEFAULT_TZ;
};
const TZ = getBrowserTimeZone();
dayjs.tz.setDefault(TZ);
const nowInTz = () => dayjs().tz(TZ); const nowInTz = () => dayjs().tz(TZ);
const toTz = (input) => (input ? dayjs.tz(input, TZ) : nowInTz()); const toTz = (input) => (input ? dayjs.tz(input, TZ) : nowInTz());
const formatDate = (input) => toTz(input).format('YYYY-MM-DD'); const formatDate = (input) => toTz(input).format('YYYY-MM-DD');
@@ -1914,6 +1922,14 @@ export default function HomePage() {
// 用户认证状态 // 用户认证状态
const [user, setUser] = useState(null); const [user, setUser] = useState(null);
const [lastSyncTime, setLastSyncTime] = useState(null);
useEffect(() => {
const stored = window.localStorage.getItem('localUpdatedAt');
if (stored) {
setLastSyncTime(stored);
}
}, []);
const [userMenuOpen, setUserMenuOpen] = useState(false); const [userMenuOpen, setUserMenuOpen] = useState(false);
const [loginModalOpen, setLoginModalOpen] = useState(false); const [loginModalOpen, setLoginModalOpen] = useState(false);
const [loginEmail, setLoginEmail] = useState(''); const [loginEmail, setLoginEmail] = useState('');
@@ -2477,7 +2493,9 @@ export default function HomePage() {
if (prevSig === nextSig) return; if (prevSig === nextSig) return;
} }
if (!skipSyncRef.current) { if (!skipSyncRef.current) {
window.localStorage.setItem('localUpdatedAt', nowInTz().toISOString()); const now = nowInTz().toISOString();
window.localStorage.setItem('localUpdatedAt', now);
setLastSyncTime(now);
} }
scheduleSync(); scheduleSync();
} }
@@ -2486,6 +2504,9 @@ export default function HomePage() {
setItem: (key, value) => { setItem: (key, value) => {
const prevValue = key === 'funds' ? window.localStorage.getItem(key) : null; const prevValue = key === 'funds' ? window.localStorage.getItem(key) : null;
window.localStorage.setItem(key, value); window.localStorage.setItem(key, value);
if (key === 'localUpdatedAt') {
setLastSyncTime(value);
}
triggerSync(key, prevValue, value); triggerSync(key, prevValue, value);
}, },
removeItem: (key) => { removeItem: (key) => {
@@ -2496,7 +2517,9 @@ export default function HomePage() {
clear: () => { clear: () => {
window.localStorage.clear(); window.localStorage.clear();
if (!skipSyncRef.current) { if (!skipSyncRef.current) {
window.localStorage.setItem('localUpdatedAt', nowInTz().toISOString()); const now = nowInTz().toISOString();
window.localStorage.setItem('localUpdatedAt', now);
setLastSyncTime(now);
} }
scheduleSync(); scheduleSync();
} }
@@ -2507,6 +2530,9 @@ export default function HomePage() {
const keys = new Set(['funds', 'favorites', 'groups', 'collapsedCodes', 'refreshMs', 'holdings', 'pendingTrades', 'viewMode']); const keys = new Set(['funds', 'favorites', 'groups', 'collapsedCodes', 'refreshMs', 'holdings', 'pendingTrades', 'viewMode']);
const onStorage = (e) => { const onStorage = (e) => {
if (!e.key) return; if (!e.key) return;
if (e.key === 'localUpdatedAt') {
setLastSyncTime(e.newValue);
}
if (!keys.has(e.key)) return; if (!keys.has(e.key)) return;
if (e.key === 'funds') { if (e.key === 'funds') {
const prevSig = getFundCodesSignature(e.oldValue); const prevSig = getFundCodesSignature(e.oldValue);
@@ -3978,6 +4004,11 @@ export default function HomePage() {
<div className="user-info"> <div className="user-info">
<span className="user-email">{user.email}</span> <span className="user-email">{user.email}</span>
<span className="user-status">已登录</span> <span className="user-status">已登录</span>
{lastSyncTime && (
<span className="muted" style={{ fontSize: '10px', marginTop: 2 }}>
同步于 {dayjs(lastSyncTime).format('MM-DD HH:mm')}
</span>
)}
</div> </div>
</div> </div>
<div className="user-menu-divider" /> <div className="user-menu-divider" />