fix: 全站时间统一为北京时间(Asia/Shanghai)

新增 src/utils/datetime.ts 共享 formatBjTime 工具,
13 个页面(11后台+2前台) 的 toLocaleString/formatDate
改为显式北京时间。Dashboard.tsx 保留图表标签的
formatDate(split) 不受影响。
This commit is contained in:
QihuAI Design committed 2026-07-16 12:39:35 +08:00
1 parent 82afe14a23
commit b3866f770c
12 files changed
+34 -19

No files matched your search

+2 -1
View File
@@ -11,6 +11,7 @@
import React, { useState, useEffect, useCallback } from 'react';
import { adminAPI } from '@/api';
import { formatBjTime } from '@/utils/datetime';
@@ -1012,7 +1013,7 @@ const Achievement: React.FC = () => {
<div style={{ fontSize: '12px', color: '#8892B0', marginTop: '8px' }}>
完成时间: {new Date(ach.completed_at).toLocaleString('zh-CN')}
完成时间: {formatBjTime(ach.completed_at)}
</div>
+4 -3
View File
@@ -1,6 +1,7 @@
import { useState, useEffect, useCallback } from 'react';
import { adminAPI } from '@/api';
import './Apps.css';
import { formatBjTime } from '@/utils/datetime';
interface AIApp {
id: string;
@@ -779,9 +780,9 @@ function AppDetailDrawer({ app, analytics, onClose, onAction }: {
{/* Timestamps */}
<div style={{ marginTop: '24px', fontSize: '13px', color: 'var(--color-text-tertiary)' }}>
<div>创建时间: {app.created_at ? new Date(app.created_at).toLocaleString('zh-CN') : '-'}</div>
<div>更新时间: {app.updated_at ? new Date(app.updated_at).toLocaleString('zh-CN') : '-'}</div>
{app.published_at && <div>发布时间: {new Date(app.published_at).toLocaleString('zh-CN')}</div>}
<div>创建时间: {app.created_at ? formatBjTime(app.created_at) : '-'}</div>
<div>更新时间: {app.updated_at ? formatBjTime(app.updated_at) : '-'}</div>
{app.published_at && <div>发布时间: {formatBjTime(app.published_at)}</div>}
</div>
</div>
+2 -1
View File
@@ -1,5 +1,6 @@
import React, { useState, useEffect } from 'react';
import { adminAPI } from '@/api';
import { formatBjTime } from '@/utils/datetime';
interface AutomationTask {
id: string;
@@ -270,7 +271,7 @@ const Automation: React.FC = () => {
// Format time
const formatTime = (ts: string) => {
if (!ts) return '-';
try { return new Date(ts).toLocaleString('zh-CN'); }
try { return formatBjTime(ts); }
catch { return ts; }
};
+3 -7
View File
@@ -1,6 +1,7 @@
import React, { useState, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { adminAPI } from '@/api';
import { formatBjTime } from '@/utils/datetime';
const SVGIcon: React.FC<{ children: React.ReactNode; size?: number }> = ({ children, size = 20 }) => (
<svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round">
@@ -226,12 +227,7 @@ const Dashboard: React.FC = () => {
fetchData();
}, []);
const formatDate = (d: string) => {
if (!d) return '-';
const dt = new Date(d);
if (isNaN(dt.getTime())) return d.slice(0, 19) || '-';
return dt.toLocaleDateString('zh-CN', { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' });
};
const formatDate = (d: string | null) => formatBjTime(d);
const formatTokens = (n: number): string => {
if (n >= 100000000) return (n / 100000000).toFixed(2) + '亿';
@@ -336,7 +332,7 @@ const Dashboard: React.FC = () => {
</div>
))}
<div style={{ marginTop: 12, fontSize: 11, color: '#6B7280', textAlign: 'center' }}>
服务器时间: {stats.server_time ? new Date(stats.server_time).toLocaleString('zh-CN') : '-'}
服务器时间: {stats.server_time ? formatBjTime(stats.server_time) : '-'}
</div>
</div>
</div>
+2 -1
View File
@@ -1,5 +1,6 @@
import { useState, useEffect, useCallback } from 'react';
import './Developer.css';
import { formatBjTime } from '@/utils/datetime';
const API_BASE = '/app/api';
@@ -216,7 +217,7 @@ export default function AdminDeveloper() {
const fmtTime = (t: string | null) => {
if (!t) return '-';
try { return new Date(t).toLocaleString('zh-CN'); } catch { return t; }
try { return formatBjTime(t); } catch { return t; }
};
const statusBadge = (status: string) => {
+2 -1
View File
@@ -1,5 +1,6 @@
import { useState, useEffect, useCallback } from 'react';
import './Developer.css';
import { formatBjTime } from '@/utils/datetime';
const API_BASE = '/app/api';
@@ -169,7 +170,7 @@ export default function AdminDeveloperTools() {
const fmtTime = (t: string | null) => {
if (!t) return '-';
try { return new Date(t).toLocaleString('zh-CN'); } catch { return t; }
try { return formatBjTime(t); } catch { return t; }
};
const statusBadge = (status: string) => {
+2 -1
View File
@@ -3,6 +3,7 @@ import React, { useState, useEffect, useCallback } from 'react';
import {
adminAPI,
} from '@/api';
import { formatBjTime } from '@/utils/datetime';
// Platform icons (simple SVG representations)
const platformIcons: Record<string, string> = {
@@ -329,7 +330,7 @@ const ImBots: React.FC = () => {
// Format time
const formatTime = (time: string) => {
if (!time) return '-';
return new Date(time).toLocaleString('zh-CN');
return formatBjTime(time);
};
const tabs: { key: Tab; label: string; icon: string }[] = [
+2 -1
View File
@@ -1,5 +1,6 @@
import React, { useState, useEffect, useCallback } from 'react';
import { adminAPI } from '@/api';
import { formatBjTime } from '@/utils/datetime';
const styles: Record<string, React.CSSProperties> = {
container: { padding: 24, backgroundColor: '#f5f7fa', minHeight: '100vh' },
@@ -277,7 +278,7 @@ export default function AdminMcp() {
<td style={styles.td}>{getTransportBadge(s.transport_type)}</td>
<td style={styles.td}>{getStatusBadge(s.status)}</td>
<td style={styles.td}>{s.tools_count || '-'}</td>
<td style={styles.td}>{s.last_connected_at ? new Date(s.last_connected_at).toLocaleString() : '从未'}</td>
<td style={styles.td}>{s.last_connected_at ? formatBjTime(s.last_connected_at) : '从未'}</td>
<td style={styles.td}>
<span style={{ ...styles.badge, backgroundColor: s.is_enabled ? '#dcfce7' : '#f3f4f6', color: s.is_enabled ? '#166534' : '#6b7280' }}>
{s.is_enabled ? '是' : '否'}
+2 -1
View File
@@ -3,6 +3,7 @@
import React, { useState, useEffect, useCallback } from 'react';
import { adminAPI } from '@/api';
import { formatBjTime } from '@/utils/datetime';
@@ -464,7 +465,7 @@ export default function PrivacySettings() {
const formatDate = (d: string | null) => d ? new Date(d).toLocaleString('zh-CN') : '-';
const formatDate = (d: string | null) => d ? formatBjTime(d) : '-';
+2 -1
View File
@@ -3,6 +3,7 @@ import { adminAPI } from '@/api';
import { mockUsers, mockMembershipPlans } from '@/mock/data';
import type { User, RegisterSettings } from '@/types';
import './Users.css';
import { formatBjTime } from '@/utils/datetime';
// ===== SVG Icons =====
const IconSearch = () => (
@@ -141,7 +142,7 @@ const UserDetailModal: React.FC<UserDetailModalProps> = ({ user, onClose }) => {
</div>
<div className="user-detail-item">
<span className="detail-label">注册时间</span>
<span className="detail-value">{new Date(user.createdAt).toLocaleString('zh-CN')}</span>
<span className="detail-value">{formatBjTime(user.createdAt)}</span>
</div>
</div>
</div>
@@ -4,6 +4,7 @@ import type { EvolutionSkill, EvolutionEvent, EvolutionProfile, SkillTreeData, S
import html2canvas from 'html2canvas';
import jsPDF from 'jspdf';
import './EvolutionDashboard.css';
import { formatBjTime } from '@/utils/datetime';
// ==================== SVG Icons ====================
const Icon: React.FC<{ children: React.ReactNode; size?: number; className?: string }> = ({ children, size = 16, className }) => (
@@ -486,7 +487,7 @@ const TimelinePanel: React.FC<{
<p className="evo-timeline-desc">{event.description}</p>
<div className="evo-timeline-time">
<Icon size={12}><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></Icon>
{new Date(event.created_at).toLocaleString()}
{formatBjTime(event.created_at)}
</div>
</div>
</div>
+9
View File
@@ -0,0 +1,9 @@
// 后端按北京时间存储(无时区标记),此处显式按 Asia/Shanghai 渲染,
// 避免浏览器本地时区导致时间显示错乱。
export function formatBjTime(t?: string | null): string {
if (!t) return '-';
const iso = /[Z+]/.test(t) ? t : t + '+08:00';
const d = new Date(iso);
if (isNaN(d.getTime())) return t;
return d.toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai', hour12: false });
}