feat: show settings as modal dialog

This commit is contained in:
aits2026
2026-03-10 13:32:19 +08:00
parent 5b2e48badd
commit 37796c98c9
4 changed files with 248 additions and 77 deletions

View File

@@ -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;

View File

@@ -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 (
<div className="settings-page">
{message && <div className={`message-toast ${message.success ? 'success' : 'error'}`}>{message.text}</div>}
<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>}
{/* 多账号选择对话框 */}
{showWxidSelect && wxidOptions.length > 1 && (
<div className="wxid-dialog-overlay" onClick={() => setShowWxidSelect(false)}>
<div className="wxid-dialog" onClick={(e) => e.stopPropagation()}>
<div className="wxid-dialog-header">
<h3></h3>
<p>使</p>
</div>
<div className="wxid-dialog-list">
{wxidOptions.map((opt) => (
<div
key={opt.wxid}
className={`wxid-dialog-item ${opt.wxid === wxid ? 'active' : ''}`}
onClick={() => handleSelectWxid(opt.wxid)}
>
<span className="wxid-id">{opt.wxid}</span>
<span className="wxid-date"> {new Date(opt.modifiedTime).toLocaleString()}</span>
</div>
))}
</div>
<div className="wxid-dialog-footer">
<button className="btn btn-secondary" onClick={() => setShowWxidSelect(false)}></button>
{/* 多账号选择对话框 */}
{showWxidSelect && wxidOptions.length > 1 && (
<div className="wxid-dialog-overlay" onClick={() => setShowWxidSelect(false)}>
<div className="wxid-dialog" onClick={(e) => e.stopPropagation()}>
<div className="wxid-dialog-header">
<h3></h3>
<p>使</p>
</div>
<div className="wxid-dialog-list">
{wxidOptions.map((opt) => (
<div
key={opt.wxid}
className={`wxid-dialog-item ${opt.wxid === wxid ? 'active' : ''}`}
onClick={() => handleSelectWxid(opt.wxid)}
>
<span className="wxid-id">{opt.wxid}</span>
<span className="wxid-date"> {new Date(opt.modifiedTime).toLocaleString()}</span>
</div>
))}
</div>
<div className="wxid-dialog-footer">
<button className="btn btn-secondary" onClick={() => setShowWxidSelect(false)}></button>
</div>
</div>
</div>
</div>
)}
)}
<div className="settings-header">
<h1></h1>
<div className="settings-actions">
<button className="btn btn-secondary" onClick={handleTestConnection} disabled={isLoading || isTesting}>
<Plug size={16} /> {isTesting ? '测试中...' : '测试连接'}
</button>
<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-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)}
>
<tab.icon size={16} />
<span>{tab.label}</span>
</button>
))}
</div>
<div className="settings-body">
{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()}
</div>
</div>
</div>
<div className="settings-tabs">
{tabs.map(tab => (
<button key={tab.id} className={`tab-btn ${activeTab === tab.id ? 'active' : ''}`} onClick={() => setActiveTab(tab.id)}>
<tab.icon size={16} />
<span>{tab.label}</span>
</button>
))}
</div>
<div className="settings-body">
{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()}
</div>
</div>
)
}