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;
+}
|