mirror of
https://github.com/hicccc77/WeFlow.git
synced 2026-03-24 23:06:51 +00:00
feat: show settings as modal dialog
This commit is contained in:
50
src/App.tsx
50
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<Location>({
|
||||
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 (
|
||||
<div className="app-container">
|
||||
<div className="window-drag-region" aria-hidden="true" />
|
||||
@@ -568,7 +605,7 @@ function App() {
|
||||
<ExportPage />
|
||||
</div>
|
||||
|
||||
<Routes>
|
||||
<Routes location={routeLocation}>
|
||||
<Route path="/" element={<HomePage />} />
|
||||
<Route path="/home" element={<HomePage />} />
|
||||
<Route path="/chat" element={<ChatPage />} />
|
||||
@@ -584,7 +621,6 @@ function App() {
|
||||
<Route path="/dual-report" element={<DualReportPage />} />
|
||||
<Route path="/dual-report/view" element={<DualReportWindow />} />
|
||||
|
||||
<Route path="/settings" element={<SettingsPage />} />
|
||||
<Route path="/export" element={<div className="export-route-anchor" aria-hidden="true" />} />
|
||||
<Route path="/sns" element={<SnsPage />} />
|
||||
<Route path="/contacts" element={<ContactsPage />} />
|
||||
@@ -593,6 +629,10 @@ function App() {
|
||||
</RouteGuard>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
{isSettingsRoute && (
|
||||
<SettingsPage onClose={handleCloseSettings} />
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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}
|
||||
>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,7 +2064,8 @@ function SettingsPage() {
|
||||
)
|
||||
|
||||
return (
|
||||
<div className="settings-page">
|
||||
<div className="settings-modal-overlay" onClick={() => onClose?.()}>
|
||||
<div className="settings-page" onClick={(event) => event.stopPropagation()}>
|
||||
{message && <div className={`message-toast ${message.success ? 'success' : 'error'}`}>{message.text}</div>}
|
||||
|
||||
{/* 多账号选择对话框 */}
|
||||
@@ -2080,17 +2096,30 @@ function SettingsPage() {
|
||||
)}
|
||||
|
||||
<div className="settings-header">
|
||||
<div className="settings-title-block">
|
||||
<h1>设置</h1>
|
||||
<p>在这里集中调整 WeFlow 的功能、外观与数据行为。</p>
|
||||
</div>
|
||||
<div className="settings-actions">
|
||||
<button className="btn btn-secondary" onClick={handleTestConnection} disabled={isLoading || isTesting}>
|
||||
<Plug size={16} /> {isTesting ? '测试中...' : '测试连接'}
|
||||
</button>
|
||||
{onClose && (
|
||||
<button type="button" className="settings-close-btn" onClick={onClose} aria-label="关闭设置">
|
||||
<X size={18} />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="settings-tabs">
|
||||
<div className="settings-layout">
|
||||
<div className="settings-tabs" role="tablist" aria-label="设置项">
|
||||
{tabs.map(tab => (
|
||||
<button key={tab.id} className={`tab-btn ${activeTab === tab.id ? 'active' : ''}`} onClick={() => setActiveTab(tab.id)}>
|
||||
<button
|
||||
key={tab.id}
|
||||
className={`tab-btn ${activeTab === tab.id ? 'active' : ''}`}
|
||||
onClick={() => setActiveTab(tab.id)}
|
||||
>
|
||||
<tab.icon size={16} />
|
||||
<span>{tab.label}</span>
|
||||
</button>
|
||||
@@ -2108,7 +2137,8 @@ function SettingsPage() {
|
||||
{activeTab === 'security' && renderSecurityTab()}
|
||||
{activeTab === 'about' && renderAboutTab()}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user