fix: 全站时间统一为北京时间(Asia/Shanghai)
新增 src/utils/datetime.ts 共享 formatBjTime 工具, 13 个页面(11后台+2前台) 的 toLocaleString/formatDate 改为显式北京时间。Dashboard.tsx 保留图表标签的 formatDate(split) 不受影响。
This commit is contained in:
1 parent
82afe14a23
commit
b3866f770c
12 files changed
+34
-19
No files matched your search
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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; }
|
||||
};
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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 }[] = [
|
||||
|
||||
@@ -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 ? '是' : '否'}
|
||||
|
||||
@@ -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) : '-';
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 });
|
||||
}
|
||||
Reference in new issue
Block a user