diff --git a/VERSION b/VERSION index 95300b3..e782d58 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -v2026.07.19.11 +v2026.07.20.1 diff --git a/frontend/src/pages/admin/Achievement.tsx b/frontend/src/pages/admin/Achievement.tsx index afb6d98..54aca34 100644 --- a/frontend/src/pages/admin/Achievement.tsx +++ b/frontend/src/pages/admin/Achievement.tsx @@ -1121,7 +1121,7 @@ const Achievement: React.FC = () => { - {entry.last_completed ? new Date(entry.last_completed).toLocaleDateString('zh-CN') : '-'} + {entry.last_completed ? formatBjDate(entry.last_completed) : '-'} diff --git a/frontend/src/pages/admin/Ads.tsx b/frontend/src/pages/admin/Ads.tsx index b8f1bed..1470f90 100644 --- a/frontend/src/pages/admin/Ads.tsx +++ b/frontend/src/pages/admin/Ads.tsx @@ -1,6 +1,7 @@ import React, { useState, useEffect } from 'react'; import { adminAPI, marketAPI } from '@/api'; import type { AdBanner, MarketApp } from '@/types'; +import { formatBjDate } from '@/utils/datetime'; import './Ads.css'; const SVGIcon: React.FC<{ children: React.ReactNode; size?: number }> = ({ children, size = 16 }) => ( @@ -281,7 +282,7 @@ const Ads: React.FC = () => { - {new Date(banner.createdAt).toLocaleDateString()} + {formatBjDate(banner.createdAt)}
diff --git a/frontend/src/pages/admin/Apps.tsx b/frontend/src/pages/admin/Apps.tsx index 7ad8920..e252d25 100644 --- a/frontend/src/pages/admin/Apps.tsx +++ b/frontend/src/pages/admin/Apps.tsx @@ -1,7 +1,7 @@ import { useState, useEffect, useCallback } from 'react'; import { adminAPI } from '@/api'; import './Apps.css'; -import { formatBjTime } from '@/utils/datetime'; +import { formatBjTime, formatBjDate } from '@/utils/datetime'; interface AIApp { id: string; @@ -224,11 +224,7 @@ export default function AdminApps() { }; const formatDate = (dt: string | null) => { - if (!dt) return '-'; - try { - const d = new Date(dt); - return d.toLocaleDateString('zh-CN') + ' ' + d.toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' }); - } catch { return dt; } + return formatBjTime(dt); }; return ( @@ -738,7 +734,7 @@ function AppDetailDrawer({ app, analytics, onClose, onAction }: {
v{v.version} {v.changelog} - {v.created_at ? new Date(v.created_at).toLocaleDateString('zh-CN') : '-'} + {v.created_at ? formatBjDate(v.created_at) : '-'}
))}
diff --git a/frontend/src/pages/front/AppServices.tsx b/frontend/src/pages/front/AppServices.tsx index 294d1dd..e21b7d8 100644 --- a/frontend/src/pages/front/AppServices.tsx +++ b/frontend/src/pages/front/AppServices.tsx @@ -1,6 +1,7 @@ // @ts-nocheck import React, { useState, useEffect, useCallback } from 'react'; import { imUserAPI } from '@/api'; +import { formatBjDate } from '@/utils/datetime'; const PRIMARY = '#5B5FED'; @@ -106,7 +107,7 @@ const AppServices: React.FC = () => {

{app.description || '暂无描述'}

- {app.published_at ? '上架于 ' + new Date(app.published_at).toLocaleDateString('zh-CN') : ''} + {app.published_at ? '上架于 ' + formatBjDate(app.published_at) : ''}
diff --git a/frontend/src/pages/front/AssetsPage.tsx b/frontend/src/pages/front/AssetsPage.tsx index 0ad69b6..5957904 100644 --- a/frontend/src/pages/front/AssetsPage.tsx +++ b/frontend/src/pages/front/AssetsPage.tsx @@ -2,6 +2,7 @@ import React, { useState, useEffect, useCallback } from 'react'; import { useNavigate } from 'react-router-dom'; import type { Asset, AssetCategory, AssetCategoryDef, AssetFileType } from '@/types'; import { assetsAPI } from '@/api'; +import { formatBjTime } from '@/utils/datetime'; import './AssetsPage.css'; const fileTypeLabels: Record = { @@ -46,7 +47,7 @@ function formatSize(bytes?: number): string { } function formatDate(dateStr: string): string { - return new Date(dateStr).toLocaleDateString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' }); + return formatBjTime(dateStr); } // 将文件扩展名或API返回的file_type映射到前端fileType标签 diff --git a/frontend/src/pages/front/ChatPage.tsx b/frontend/src/pages/front/ChatPage.tsx index 58a402e..4b5f2e4 100644 --- a/frontend/src/pages/front/ChatPage.tsx +++ b/frontend/src/pages/front/ChatPage.tsx @@ -1,6 +1,7 @@ import React, { useState, useRef, useEffect, useMemo, useCallback } from 'react'; import { useParams, useNavigate } from 'react-router-dom'; import { chatAPI, knowledgeAPI, marketAPI, evolutionAPI } from '@/api'; +import { formatChatTime } from '@/utils/datetime'; import type { Conversation, Message } from '@/types'; import './ChatPage.css'; @@ -396,21 +397,7 @@ function escapeHtml(text: string): string { // ===== Time Formatting ===== function formatTime(iso: string): string { - const date = new Date(iso); - const now = new Date(); - const diff = now.getTime() - date.getTime(); - const oneDay = 24 * 60 * 60 * 1000; - - if (diff < oneDay) { - return date.toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' }); - } else if (diff < 7 * oneDay) { - const days = ['周日', '周一', '周二', '周三', '周四', '周五', '周六']; - return days[date.getDay()]; - } else if (date.getFullYear() === now.getFullYear()) { - return `${date.getMonth() + 1}/${date.getDate()}`; - } else { - return `${date.getFullYear()}/${date.getMonth() + 1}/${date.getDate()}`; - } + return formatChatTime(iso); } // ===== Helpers: normalize API responses ===== diff --git a/frontend/src/pages/front/MarketPage.tsx b/frontend/src/pages/front/MarketPage.tsx index 1c6c9ba..0e0d836 100644 --- a/frontend/src/pages/front/MarketPage.tsx +++ b/frontend/src/pages/front/MarketPage.tsx @@ -1596,6 +1596,7 @@ const MarketPage: React.FC = () => { const [localApps, setLocalApps] = useState([]); const [loading, setLoading] = useState(false); const [searchQuery, setSearchQuery] = useState(''); + const searchTimerRef = useRef(null); const [capabilityFilter, setCapabilityFilter] = useState(null); const [installToast, setInstallToast] = useState(null); @@ -1893,7 +1894,7 @@ const MarketPage: React.FC = () => { className="form-input" placeholder="搜索应用名称或描述..." value={searchQuery} - onChange={e => { setSearchQuery(e.target.value); loadApps(e.target.value); }} + onChange={e => { const v = e.target.value; setSearchQuery(v); if (searchTimerRef.current) clearTimeout(searchTimerRef.current); searchTimerRef.current = window.setTimeout(() => loadApps(v), 350); }} style={{ paddingLeft: 32, flex: 1, maxWidth: 400 }} /> {searchQuery && ( diff --git a/frontend/src/utils/datetime.ts b/frontend/src/utils/datetime.ts index 3c53d40..08d0605 100644 --- a/frontend/src/utils/datetime.ts +++ b/frontend/src/utils/datetime.ts @@ -7,3 +7,34 @@ export function formatBjTime(t?: string | null): string { if (isNaN(d.getTime())) return t; return d.toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai', hour12: false }); } + +// 仅日期(北京时间) +export function formatBjDate(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.toLocaleDateString('zh-CN', { timeZone: 'Asia/Shanghai' }); +} + +// 将任意 Date 换算为「北京墙钟」对应的 UTC 视图,便于按北京日历日比较 +function toBjView(d: Date): Date { + return new Date(d.getTime() + (d.getTimezoneOffset() + 480) * 60000); +} + +// 聊天消息时间:今天 HH:MM / 昨天 HH:MM / MM-DD HH:MM / YYYY-MM-DD HH:MM(北京时间) +export function formatChatTime(iso?: string | null): string { + if (!iso) return ''; + const s = /[Z+]/.test(iso) ? iso : iso + '+08:00'; + const d = new Date(s); + if (isNaN(d.getTime())) return ''; + const bj = toBjView(d); + const nowBj = toBjView(new Date()); + const dayDiff = Math.floor(nowBj.getTime() / 86400000) - Math.floor(bj.getTime() / 86400000); + const hhmm = d.toLocaleTimeString('zh-CN', { timeZone: 'Asia/Shanghai', hour: '2-digit', minute: '2-digit' }); + if (dayDiff <= 0) return hhmm; + if (dayDiff === 1) return '昨天 ' + hhmm; + const md = d.toLocaleDateString('zh-CN', { timeZone: 'Asia/Shanghai', month: '2-digit', day: '2-digit' }); + if (bj.getUTCFullYear() === nowBj.getUTCFullYear()) return md + ' ' + hhmm; + return d.toLocaleDateString('zh-CN', { timeZone: 'Asia/Shanghai', year: 'numeric', month: '2-digit', day: '2-digit' }) + ' ' + hhmm; +}