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 React, { useState, useEffect, useCallback } from 'react';
|
||||||
|
|
||||||
import { adminAPI } from '@/api';
|
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' }}>
|
<div style={{ fontSize: '12px', color: '#8892B0', marginTop: '8px' }}>
|
||||||
|
|
||||||
完成时间: {new Date(ach.completed_at).toLocaleString('zh-CN')}
|
完成时间: {formatBjTime(ach.completed_at)}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { useState, useEffect, useCallback } from 'react';
|
import { useState, useEffect, useCallback } from 'react';
|
||||||
import { adminAPI } from '@/api';
|
import { adminAPI } from '@/api';
|
||||||
import './Apps.css';
|
import './Apps.css';
|
||||||
|
import { formatBjTime } from '@/utils/datetime';
|
||||||
|
|
||||||
interface AIApp {
|
interface AIApp {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -779,9 +780,9 @@ function AppDetailDrawer({ app, analytics, onClose, onAction }: {
|
|||||||
|
|
||||||
{/* Timestamps */}
|
{/* Timestamps */}
|
||||||
<div style={{ marginTop: '24px', fontSize: '13px', color: 'var(--color-text-tertiary)' }}>
|
<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.created_at ? formatBjTime(app.created_at) : '-'}</div>
|
||||||
<div>更新时间: {app.updated_at ? new Date(app.updated_at).toLocaleString('zh-CN') : '-'}</div>
|
<div>更新时间: {app.updated_at ? formatBjTime(app.updated_at) : '-'}</div>
|
||||||
{app.published_at && <div>发布时间: {new Date(app.published_at).toLocaleString('zh-CN')}</div>}
|
{app.published_at && <div>发布时间: {formatBjTime(app.published_at)}</div>}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { adminAPI } from '@/api';
|
import { adminAPI } from '@/api';
|
||||||
|
import { formatBjTime } from '@/utils/datetime';
|
||||||
|
|
||||||
interface AutomationTask {
|
interface AutomationTask {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -270,7 +271,7 @@ const Automation: React.FC = () => {
|
|||||||
// Format time
|
// Format time
|
||||||
const formatTime = (ts: string) => {
|
const formatTime = (ts: string) => {
|
||||||
if (!ts) return '-';
|
if (!ts) return '-';
|
||||||
try { return new Date(ts).toLocaleString('zh-CN'); }
|
try { return formatBjTime(ts); }
|
||||||
catch { return ts; }
|
catch { return ts; }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { adminAPI } from '@/api';
|
import { adminAPI } from '@/api';
|
||||||
|
import { formatBjTime } from '@/utils/datetime';
|
||||||
|
|
||||||
const SVGIcon: React.FC<{ children: React.ReactNode; size?: number }> = ({ children, size = 20 }) => (
|
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">
|
<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();
|
fetchData();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const formatDate = (d: string) => {
|
const formatDate = (d: string | null) => formatBjTime(d);
|
||||||
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 formatTokens = (n: number): string => {
|
const formatTokens = (n: number): string => {
|
||||||
if (n >= 100000000) return (n / 100000000).toFixed(2) + '亿';
|
if (n >= 100000000) return (n / 100000000).toFixed(2) + '亿';
|
||||||
@@ -336,7 +332,7 @@ const Dashboard: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
<div style={{ marginTop: 12, fontSize: 11, color: '#6B7280', textAlign: 'center' }}>
|
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { useState, useEffect, useCallback } from 'react';
|
import { useState, useEffect, useCallback } from 'react';
|
||||||
import './Developer.css';
|
import './Developer.css';
|
||||||
|
import { formatBjTime } from '@/utils/datetime';
|
||||||
|
|
||||||
const API_BASE = '/app/api';
|
const API_BASE = '/app/api';
|
||||||
|
|
||||||
@@ -216,7 +217,7 @@ export default function AdminDeveloper() {
|
|||||||
|
|
||||||
const fmtTime = (t: string | null) => {
|
const fmtTime = (t: string | null) => {
|
||||||
if (!t) return '-';
|
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) => {
|
const statusBadge = (status: string) => {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { useState, useEffect, useCallback } from 'react';
|
import { useState, useEffect, useCallback } from 'react';
|
||||||
import './Developer.css';
|
import './Developer.css';
|
||||||
|
import { formatBjTime } from '@/utils/datetime';
|
||||||
|
|
||||||
const API_BASE = '/app/api';
|
const API_BASE = '/app/api';
|
||||||
|
|
||||||
@@ -169,7 +170,7 @@ export default function AdminDeveloperTools() {
|
|||||||
|
|
||||||
const fmtTime = (t: string | null) => {
|
const fmtTime = (t: string | null) => {
|
||||||
if (!t) return '-';
|
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) => {
|
const statusBadge = (status: string) => {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import React, { useState, useEffect, useCallback } from 'react';
|
|||||||
import {
|
import {
|
||||||
adminAPI,
|
adminAPI,
|
||||||
} from '@/api';
|
} from '@/api';
|
||||||
|
import { formatBjTime } from '@/utils/datetime';
|
||||||
|
|
||||||
// Platform icons (simple SVG representations)
|
// Platform icons (simple SVG representations)
|
||||||
const platformIcons: Record<string, string> = {
|
const platformIcons: Record<string, string> = {
|
||||||
@@ -329,7 +330,7 @@ const ImBots: React.FC = () => {
|
|||||||
// Format time
|
// Format time
|
||||||
const formatTime = (time: string) => {
|
const formatTime = (time: string) => {
|
||||||
if (!time) return '-';
|
if (!time) return '-';
|
||||||
return new Date(time).toLocaleString('zh-CN');
|
return formatBjTime(time);
|
||||||
};
|
};
|
||||||
|
|
||||||
const tabs: { key: Tab; label: string; icon: string }[] = [
|
const tabs: { key: Tab; label: string; icon: string }[] = [
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import React, { useState, useEffect, useCallback } from 'react';
|
import React, { useState, useEffect, useCallback } from 'react';
|
||||||
import { adminAPI } from '@/api';
|
import { adminAPI } from '@/api';
|
||||||
|
import { formatBjTime } from '@/utils/datetime';
|
||||||
|
|
||||||
const styles: Record<string, React.CSSProperties> = {
|
const styles: Record<string, React.CSSProperties> = {
|
||||||
container: { padding: 24, backgroundColor: '#f5f7fa', minHeight: '100vh' },
|
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}>{getTransportBadge(s.transport_type)}</td>
|
||||||
<td style={styles.td}>{getStatusBadge(s.status)}</td>
|
<td style={styles.td}>{getStatusBadge(s.status)}</td>
|
||||||
<td style={styles.td}>{s.tools_count || '-'}</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}>
|
<td style={styles.td}>
|
||||||
<span style={{ ...styles.badge, backgroundColor: s.is_enabled ? '#dcfce7' : '#f3f4f6', color: s.is_enabled ? '#166534' : '#6b7280' }}>
|
<span style={{ ...styles.badge, backgroundColor: s.is_enabled ? '#dcfce7' : '#f3f4f6', color: s.is_enabled ? '#166534' : '#6b7280' }}>
|
||||||
{s.is_enabled ? '是' : '否'}
|
{s.is_enabled ? '是' : '否'}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
import React, { useState, useEffect, useCallback } from 'react';
|
import React, { useState, useEffect, useCallback } from 'react';
|
||||||
|
|
||||||
import { adminAPI } from '@/api';
|
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 { mockUsers, mockMembershipPlans } from '@/mock/data';
|
||||||
import type { User, RegisterSettings } from '@/types';
|
import type { User, RegisterSettings } from '@/types';
|
||||||
import './Users.css';
|
import './Users.css';
|
||||||
|
import { formatBjTime } from '@/utils/datetime';
|
||||||
|
|
||||||
// ===== SVG Icons =====
|
// ===== SVG Icons =====
|
||||||
const IconSearch = () => (
|
const IconSearch = () => (
|
||||||
@@ -141,7 +142,7 @@ const UserDetailModal: React.FC<UserDetailModalProps> = ({ user, onClose }) => {
|
|||||||
</div>
|
</div>
|
||||||
<div className="user-detail-item">
|
<div className="user-detail-item">
|
||||||
<span className="detail-label">注册时间</span>
|
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import type { EvolutionSkill, EvolutionEvent, EvolutionProfile, SkillTreeData, S
|
|||||||
import html2canvas from 'html2canvas';
|
import html2canvas from 'html2canvas';
|
||||||
import jsPDF from 'jspdf';
|
import jsPDF from 'jspdf';
|
||||||
import './EvolutionDashboard.css';
|
import './EvolutionDashboard.css';
|
||||||
|
import { formatBjTime } from '@/utils/datetime';
|
||||||
|
|
||||||
// ==================== SVG Icons ====================
|
// ==================== SVG Icons ====================
|
||||||
const Icon: React.FC<{ children: React.ReactNode; size?: number; className?: string }> = ({ children, size = 16, className }) => (
|
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>
|
<p className="evo-timeline-desc">{event.description}</p>
|
||||||
<div className="evo-timeline-time">
|
<div className="evo-timeline-time">
|
||||||
<Icon size={12}><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></Icon>
|
<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>
|
</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