From 696193c57180e1352fb543500ef4b033dac3abb3 Mon Sep 17 00:00:00 2001 From: WrBug Date: Wed, 21 Jan 2026 05:13:28 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E7=B3=BB=E7=BB=9F?= =?UTF-8?q?=E6=9B=B4=E6=96=B0=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 系统更新模块移到系统设置页面最上方 - 版本号显示使用 Tag 格式(gitTag) - 版本号 Tag 根据是否有新版本显示不同颜色(黄色=有新版本,绿色=无新版本) - 版本号使用镂空样式,字号 8px - 支持 Markdown 渲染更新内容 - 美化系统更新页面样式 - 在 Layout 中添加版本更新检查,有新版本时显示提示 --- frontend/src/components/Layout.tsx | 129 +++++++---- frontend/src/pages/SystemSettings.tsx | 6 +- frontend/src/pages/SystemUpdate.tsx | 315 +++++++++++++++++++------- 3 files changed, 333 insertions(+), 117 deletions(-) diff --git a/frontend/src/components/Layout.tsx b/frontend/src/components/Layout.tsx index 2198672..64c43de 100644 --- a/frontend/src/components/Layout.tsx +++ b/frontend/src/components/Layout.tsx @@ -1,6 +1,6 @@ import { useState, useEffect } from 'react' import { useNavigate, useLocation } from 'react-router-dom' -import { Layout as AntLayout, Menu, Drawer, Button, Modal } from 'antd' +import { Layout as AntLayout, Menu, Drawer, Button, Modal, Tag } from 'antd' import { useTranslation } from 'react-i18next' import { useMediaQuery } from 'react-responsive' import { @@ -19,16 +19,37 @@ import { TwitterOutlined, CheckCircleOutlined, SendOutlined, - ApiOutlined, NotificationOutlined + ApiOutlined, + NotificationOutlined } from '@ant-design/icons' import type { MenuProps } from 'antd' import type { ReactNode } from 'react' -import { removeToken, getVersionText, getGitHubTagUrl } from '../utils' +import { removeToken, getVersionText, getGitHubTagUrl, getVersionInfo } from '../utils' import { wsManager } from '../services/websocket' +import { apiClient } from '../services/api' import Logo from './Logo' const { Header, Content, Sider } = AntLayout +// 添加动画样式 +const style = document.createElement('style') +style.textContent = ` + @keyframes versionUpdatePulse { + 0%, 100% { + opacity: 1; + transform: scale(1); + } + 50% { + opacity: 0.7; + transform: scale(1.1); + } + } +` +if (!document.head.querySelector('style[data-version-update-animation]')) { + style.setAttribute('data-version-update-animation', 'true') + document.head.appendChild(style) +} + interface LayoutProps { children: ReactNode } @@ -39,6 +60,7 @@ const Layout: React.FC = ({ children }) => { const location = useLocation() const isMobile = useMediaQuery({ maxWidth: 768 }) const [mobileMenuOpen, setMobileMenuOpen] = useState(false) + const [hasUpdate, setHasUpdate] = useState(false) // 获取当前选中的菜单项 const getSelectedKeys = (): string[] => { @@ -72,6 +94,29 @@ const Layout: React.FC = ({ children }) => { } setOpenKeys(keys) }, [location.pathname]) + + // 检查是否有新版本 + useEffect(() => { + const checkUpdate = async () => { + try { + const response = await apiClient.get('/update/check') + if (response.data.code === 0 && response.data.data) { + setHasUpdate(response.data.data.hasUpdate || false) + } + } catch (error) { + // 静默失败,不影响页面使用 + console.debug('检查更新失败:', error) + } + } + + // 页面加载时检查一次 + checkUpdate() + + // 每5分钟检查一次 + const interval = setInterval(checkUpdate, 5 * 60 * 1000) + + return () => clearInterval(interval) + }, []) const menuItems: MenuProps['items'] = [ { @@ -210,27 +255,32 @@ const Layout: React.FC = ({ children }) => { size="normal" darkMode={true} /> - { - const version = getVersionText() - if (version === 'dev') { - e.preventDefault() + { + if (hasUpdate) { + navigate('/system-settings') } }} + bordered={false} style={{ - color: 'rgba(255, 255, 255, 0.7)', - fontSize: '12px', - fontWeight: 'normal', - textDecoration: 'none', - cursor: getVersionText() === 'dev' ? 'default' : 'pointer' + cursor: hasUpdate ? 'pointer' : 'default', + fontSize: '8px', + padding: '1px 6px', + margin: 0, + background: 'transparent', + border: `1px solid ${hasUpdate ? '#faad14' : '#52c41a'}`, + borderRadius: '4px', + color: hasUpdate ? '#faad14' : '#52c41a', + lineHeight: '1.4', + display: 'inline-flex', + alignItems: 'center', + verticalAlign: 'middle' }} - title={getVersionText() === 'dev' ? '' : '查看版本发布'} + title={hasUpdate ? '有新版本可用,点击前往系统更新' : '当前已是最新版本'} > - v{getVersionText()} - + {getVersionInfo().gitTag || `v${getVersionText()}`} +
= ({ children }) => { marginBottom: '12px', textAlign: 'center', display: 'flex', - alignItems: 'flex-end', + alignItems: 'center', justifyContent: 'center', gap: '6px' }}> PolyHermes - { - const version = getVersionText() - if (version === 'dev') { - e.preventDefault() + { + if (hasUpdate) { + navigate('/system-settings') } }} + bordered={false} style={{ - color: 'rgba(255, 255, 255, 0.7)', - fontSize: '12px', - fontWeight: 'normal', - textDecoration: 'none', - cursor: getVersionText() === 'dev' ? 'default' : 'pointer', - lineHeight: '1', - paddingBottom: '2px' + cursor: hasUpdate ? 'pointer' : 'default', + fontSize: '8px', + padding: '1px 6px', + margin: 0, + background: 'transparent', + border: `1px solid ${hasUpdate ? '#faad14' : '#52c41a'}`, + borderRadius: '4px', + color: hasUpdate ? '#faad14' : '#52c41a', + lineHeight: '1.4', + display: 'inline-flex', + alignItems: 'center', + verticalAlign: 'middle' }} - title={getVersionText() === 'dev' ? '' : '查看版本发布'} + title={hasUpdate ? '有新版本可用,点击前往系统更新' : '当前已是最新版本'} > - v{getVersionText()} - + {getVersionInfo().gitTag || `v${getVersionText()}`} +
{ {t('systemSettings.title') || '通用设置'}
+ {/* 系统更新 */} + + {/* 第一部分:多语言 */} { )} - - {/* 第五部分:系统更新 */} - ) } diff --git a/frontend/src/pages/SystemUpdate.tsx b/frontend/src/pages/SystemUpdate.tsx index 60f5c1b..599db6f 100644 --- a/frontend/src/pages/SystemUpdate.tsx +++ b/frontend/src/pages/SystemUpdate.tsx @@ -8,6 +8,8 @@ import { InfoCircleOutlined } from '@ant-design/icons' import { apiClient } from '../services/api' +import ReactMarkdown from 'react-markdown' +import remarkGfm from 'remark-gfm' const { Title, Text, Paragraph } = Typography @@ -172,145 +174,302 @@ const SystemUpdate: React.FC = () => { - - 系统更新 + + 系统更新 } - style={{ marginBottom: '16px' }} + style={{ + marginBottom: '16px', + borderRadius: '8px', + boxShadow: '0 2px 8px rgba(0,0,0,0.06)' + }} > {/* 当前版本信息 */} -
- 当前版本 - - +
+
+
+ 当前版本 +
+
v{currentVersion || 'unknown'} - - +
+
+
- - {/* 更新状态 */} {updateStatus.updating && ( 系统正在更新 + } description={ -
-

{updateStatus.message}

+
+
+ {updateStatus.message} +
`${percent}%`} />
} type="info" showIcon icon={} + style={{ + borderRadius: '8px', + border: '1px solid #91d5ff' + }} /> )} {updateStatus.error && ( 更新失败} + description={ +
+ {updateStatus.error} +
+ } type="error" showIcon closable onClose={() => setUpdateStatus(prev => ({ ...prev, error: null }))} + style={{ + borderRadius: '8px' + }} /> )} {/* 检查更新 */} {!updateStatus.updating && ( - +
{updateInfo && !updateInfo.hasUpdate && ( + 当前已是最新版本 + + } type="success" showIcon icon={} + style={{ + marginTop: '12px', + borderRadius: '8px', + border: '1px solid #b7eb8f' + }} /> )} - +
)} {/* 更新信息 */} {updateInfo && updateInfo.hasUpdate && !updateStatus.updating && ( - - 发现新版本: - - v{updateInfo.latestVersion} - - {updateInfo.prerelease && ( - Pre-release - )} -
- } - description={ -
- - 发布时间: - {formatDate(updateInfo.publishedAt)} - - - {updateInfo.releaseNotes && ( -
- 更新内容: -
-
-                                                {updateInfo.releaseNotes}
-                                            
-
-
- )} - -
- + v{updateInfo.latestVersion} + + {updateInfo.prerelease && ( + + Pre-release + + )} + +
+ +
+ +
+
+ 发布时间 +
+
+ {formatDate(updateInfo.publishedAt)} +
+
+ + {updateInfo.releaseNotes && ( +
+
+ 更新内容 +
+
+
+

, + h2: ({node, ...props}) =>

, + h3: ({node, ...props}) =>

, + h4: ({node, ...props}) =>

, + p: ({node, ...props}) =>

, + ul: ({node, ...props}) =>