diff --git a/src/App.tsx b/src/App.tsx index 79b5253..f50a7b2 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,5 +1,5 @@ -import { useEffect, useState } from 'react' -import { Routes, Route, Navigate, useNavigate, useLocation } from 'react-router-dom' +import { useEffect, useRef, useState } from 'react' +import { Routes, Route, Navigate, useNavigate, useLocation, type Location } from 'react-router-dom' import TitleBar from './components/TitleBar' import Sidebar from './components/Sidebar' import RouteGuard from './components/RouteGuard' @@ -47,6 +47,13 @@ function RouteStateRedirect({ to }: { to: string }) { function App() { const navigate = useNavigate() const location = useLocation() + const settingsBackgroundRef = useRef({ + pathname: '/home', + search: '', + hash: '', + state: null, + key: 'settings-fallback' + } as Location) const { setDbConnected, @@ -70,7 +77,12 @@ function App() { const isChatHistoryWindow = location.pathname.startsWith('/chat-history/') const isStandaloneChatWindow = location.pathname === '/chat-window' const isNotificationWindow = location.pathname === '/notification-window' - const isExportRoute = location.pathname === '/export' + const isSettingsRoute = location.pathname === '/settings' + const settingsRouteState = location.state as { backgroundLocation?: Location; initialTab?: unknown } | null + const routeLocation = isSettingsRoute + ? settingsRouteState?.backgroundLocation ?? settingsBackgroundRef.current + : location + const isExportRoute = routeLocation.pathname === '/export' const [themeHydrated, setThemeHydrated] = useState(false) const [sidebarCollapsed, setSidebarCollapsed] = useState(false) @@ -89,6 +101,12 @@ function App() { // 数据收集同意状态 const [showAnalyticsConsent, setShowAnalyticsConsent] = useState(false) + useEffect(() => { + if (location.pathname !== '/settings') { + settingsBackgroundRef.current = location + } + }, [location]) + useEffect(() => { const root = document.documentElement const body = document.body @@ -437,6 +455,25 @@ function App() { } // 主窗口 - 完整布局 + const handleCloseSettings = () => { + const backgroundLocation = settingsRouteState?.backgroundLocation ?? settingsBackgroundRef.current + if (backgroundLocation.pathname === '/settings') { + navigate('/home', { replace: true }) + return + } + navigate( + { + pathname: backgroundLocation.pathname, + search: backgroundLocation.search, + hash: backgroundLocation.hash + }, + { + replace: true, + state: backgroundLocation.state + } + ) + } + return (
- + } /> } /> } /> @@ -584,7 +621,6 @@ function App() { } /> } /> - } />
+ + {isSettingsRoute && ( + + )} ) } diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx index fbd0694..e215448 100644 --- a/src/components/Sidebar.tsx +++ b/src/components/Sidebar.tsx @@ -284,7 +284,11 @@ function Sidebar({ collapsed }: SidebarProps) { const openSettingsFromAccountMenu = () => { setIsAccountMenuOpen(false) - navigate('/settings') + navigate('/settings', { + state: { + backgroundLocation: location + } + }) } const handleConfirmClearAccountData = async () => { @@ -432,7 +436,12 @@ function Sidebar({ collapsed }: SidebarProps) { setLocked(true) return } - navigate('/settings', { state: { initialTab: 'security' } }) + navigate('/settings', { + state: { + initialTab: 'security', + backgroundLocation: location + } + }) }} title={collapsed ? (authEnabled ? '锁定' : '未锁定') : undefined} > diff --git a/src/pages/SettingsPage.scss b/src/pages/SettingsPage.scss index 8dc2525..40b8f73 100644 --- a/src/pages/SettingsPage.scss +++ b/src/pages/SettingsPage.scss @@ -1,17 +1,38 @@ +.settings-modal-overlay { + position: fixed; + top: 41px; + left: 0; + right: 0; + bottom: 0; + z-index: 2050; + display: flex; + align-items: center; + justify-content: center; + padding: 28px 32px; + background: rgba(15, 23, 42, 0.28); + backdrop-filter: blur(10px); +} + .settings-page { display: flex; flex-direction: column; - height: 100%; - margin: -24px; + width: min(1160px, calc(100vw - 96px)); + height: min(820px, calc(100vh - 120px)); + max-height: 100%; padding: 24px; + background: var(--bg-primary); + border: 1px solid var(--border-color); + border-radius: 24px; + box-shadow: 0 28px 80px rgba(15, 23, 42, 0.22); overflow: hidden; } .settings-header { display: flex; - align-items: center; + align-items: flex-start; justify-content: space-between; - margin-bottom: 20px; + gap: 20px; + margin-bottom: 18px; flex-shrink: 0; h1 { @@ -22,29 +43,76 @@ } } +.settings-title-block { + display: flex; + flex-direction: column; + gap: 6px; + + p { + margin: 0; + font-size: 13px; + color: var(--text-tertiary); + line-height: 1.6; + } +} + .settings-actions { display: flex; + align-items: center; gap: 12px; } +.settings-close-btn { + width: 36px; + height: 36px; + padding: 0; + border: 1px solid var(--border-color); + border-radius: 10px; + background: var(--bg-secondary); + color: var(--text-secondary); + display: inline-flex; + align-items: center; + justify-content: center; + cursor: pointer; + transition: background 0.2s ease, color 0.2s ease, border-color 0.2s ease; + + &:hover { + background: var(--bg-tertiary); + color: var(--text-primary); + border-color: rgba(139, 115, 85, 0.28); + } +} + +.settings-layout { + flex: 1; + min-height: 0; + display: flex; + gap: 20px; + overflow: hidden; +} + .settings-tabs { display: flex; - gap: 4px; - padding: 4px; - background: var(--bg-tertiary); - border-radius: 12px; - margin-bottom: 20px; + flex-direction: column; + gap: 6px; + padding: 12px; + width: 220px; flex-shrink: 0; - width: fit-content; + background: var(--bg-secondary); + border: 1px solid var(--border-color); + border-radius: 20px; + overflow-y: auto; } .tab-btn { display: flex; align-items: center; gap: 6px; - padding: 10px 18px; + width: 100%; + justify-content: flex-start; + padding: 11px 14px; border: none; - border-radius: 8px; + border-radius: 12px; font-size: 14px; font-weight: 500; cursor: pointer; @@ -67,6 +135,7 @@ .settings-body { flex: 1; overflow-y: auto; + min-width: 0; padding-right: 8px; &::-webkit-scrollbar { @@ -85,8 +154,10 @@ .tab-content { background: var(--bg-secondary); - border-radius: 16px; + border: 1px solid var(--border-color); + border-radius: 20px; padding: 24px; + min-height: 100%; .section-desc { font-size: 13px; @@ -932,7 +1003,7 @@ padding: 10px 24px; border-radius: 9999px; font-size: 14px; - z-index: 100; + z-index: 2200; animation: slideDown 0.3s ease; &.success { @@ -946,6 +1017,27 @@ } } +@media (max-width: 960px) { + .settings-modal-overlay { + padding: 20px; + } + + .settings-page { + width: min(100%, calc(100vw - 40px)); + height: min(100%, calc(100vh - 82px)); + padding: 20px; + } + + .settings-layout { + flex-direction: column; + } + + .settings-tabs { + width: 100%; + max-height: 220px; + } +} + @keyframes slideDown { from { opacity: 0; diff --git a/src/pages/SettingsPage.tsx b/src/pages/SettingsPage.tsx index 8afa61b..d0ae9b2 100644 --- a/src/pages/SettingsPage.tsx +++ b/src/pages/SettingsPage.tsx @@ -10,7 +10,7 @@ import { Eye, EyeOff, FolderSearch, FolderOpen, Search, Copy, RotateCcw, Trash2, Plug, Check, Sun, Moon, Monitor, Palette, Database, HardDrive, Info, RefreshCw, ChevronDown, Download, Mic, - ShieldCheck, Fingerprint, Lock, KeyRound, Bell, Globe, BarChart2 + ShieldCheck, Fingerprint, Lock, KeyRound, Bell, Globe, BarChart2, X } from 'lucide-react' import { Avatar } from '../components/Avatar' import './SettingsPage.scss' @@ -36,7 +36,11 @@ interface WxidOption { modifiedTime: number } -function SettingsPage() { +interface SettingsPageProps { + onClose?: () => void +} + +function SettingsPage({ onClose }: SettingsPageProps = {}) { const location = useLocation() const { isDbConnected, @@ -195,6 +199,17 @@ function SettingsPage() { setActiveTab(initialTab) }, [location.state]) + useEffect(() => { + if (!onClose) return + const handleKeyDown = (event: KeyboardEvent) => { + if (event.key === 'Escape') { + onClose() + } + } + document.addEventListener('keydown', handleKeyDown) + return () => document.removeEventListener('keydown', handleKeyDown) + }, [onClose]) + useEffect(() => { const removeDb = window.electronAPI.key.onDbKeyStatus((payload: { message: string; level: number }) => { setDbKeyStatus(payload.message) @@ -2049,66 +2064,81 @@ function SettingsPage() { ) return ( -
- {message &&
{message.text}
} +
onClose?.()}> +
event.stopPropagation()}> + {message &&
{message.text}
} - {/* 多账号选择对话框 */} - {showWxidSelect && wxidOptions.length > 1 && ( -
setShowWxidSelect(false)}> -
e.stopPropagation()}> -
-

检测到多个微信账号

-

请选择要使用的账号

-
-
- {wxidOptions.map((opt) => ( -
handleSelectWxid(opt.wxid)} - > - {opt.wxid} - 最后修改 {new Date(opt.modifiedTime).toLocaleString()} -
- ))} -
-
- + {/* 多账号选择对话框 */} + {showWxidSelect && wxidOptions.length > 1 && ( +
setShowWxidSelect(false)}> +
e.stopPropagation()}> +
+

检测到多个微信账号

+

请选择要使用的账号

+
+
+ {wxidOptions.map((opt) => ( +
handleSelectWxid(opt.wxid)} + > + {opt.wxid} + 最后修改 {new Date(opt.modifiedTime).toLocaleString()} +
+ ))} +
+
+ +
-
- )} + )} -
-

设置

-
- +
+
+

设置

+

在这里集中调整 WeFlow 的功能、外观与数据行为。

+
+
+ + {onClose && ( + + )} +
+
+ +
+
+ {tabs.map(tab => ( + + ))} +
+ +
+ {activeTab === 'appearance' && renderAppearanceTab()} + {activeTab === 'notification' && renderNotificationTab()} + {activeTab === 'database' && renderDatabaseTab()} + {activeTab === 'models' && renderModelsTab()} + {activeTab === 'cache' && renderCacheTab()} + {activeTab === 'api' && renderApiTab()} + {activeTab === 'analytics' && renderAnalyticsTab()} + {activeTab === 'security' && renderSecurityTab()} + {activeTab === 'about' && renderAboutTab()} +
- -
- {tabs.map(tab => ( - - ))} -
- -
- {activeTab === 'appearance' && renderAppearanceTab()} - {activeTab === 'notification' && renderNotificationTab()} - {activeTab === 'database' && renderDatabaseTab()} - {activeTab === 'models' && renderModelsTab()} - {activeTab === 'cache' && renderCacheTab()} - {activeTab === 'api' && renderApiTab()} - {activeTab === 'analytics' && renderAnalyticsTab()} - {activeTab === 'security' && renderSecurityTab()} - {activeTab === 'about' && renderAboutTab()} -
-
) }