完成大量美化

This commit is contained in:
2026-05-01 14:44:18 +08:00
parent 1d0f0ae0ef
commit 6b65b24b0f
49 changed files with 2487 additions and 9019 deletions

View File

@@ -144,11 +144,15 @@
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
/* Optimize backdrop filter for better performance */
backdrop-filter: blur(4px);
-webkit-backdrop-filter: blur(4px);
z-index: var(--z-modal-backdrop);
display: flex;
justify-content: center;
padding-top: var(--spacing-xl);
/* Optimize animation for better performance */
will-change: opacity;
animation: fadeIn var(--transition-normal);
}
@@ -172,6 +176,8 @@
overflow: hidden;
display: flex;
flex-direction: column;
/* Optimize animation for better performance */
will-change: transform, opacity;
animation: slideDown var(--transition-smooth);
}
@@ -220,20 +226,20 @@
display: inline-flex;
align-items: center;
padding: 6px 14px;
background: linear-gradient(135deg, rgba(102, 126, 234, 0.08) 0%, rgba(118, 75, 162, 0.08) 100%);
border: 1px solid rgba(102, 126, 234, 0.2);
background: var(--gradient-subtle);
border: 1px solid var(--color-border);
border-radius: 16px;
font-size: 13px;
color: #495057;
color: var(--color-text-primary);
font-weight: 500;
transition: all 0.15s ease;
}
.global-book-item-topbar:hover {
background: linear-gradient(135deg, rgba(102, 126, 234, 0.12) 0%, rgba(118, 75, 162, 0.12) 100%);
border-color: rgba(102, 126, 234, 0.3);
background: linear-gradient(135deg, var(--color-accent-light) 0%, var(--color-accent-light) 100%);
border-color: var(--color-accent);
transform: translateY(-1px);
box-shadow: 0 2px 6px rgba(102, 126, 234, 0.15);
box-shadow: 0 2px 6px var(--color-accent-light);
}
.global-book-name-topbar {
@@ -250,6 +256,73 @@
padding: 20px;
}
/* ==================== 设置面板样式 ==================== */
.setting-section {
margin-bottom: var(--spacing-xl);
}
.setting-section:last-child {
margin-bottom: 0;
}
.setting-section h4 {
margin: 0 0 var(--spacing-md) 0;
font-size: 0.95rem;
font-weight: 600;
color: var(--color-text-primary);
font-family: var(--font-ui);
}
.setting-options {
display: flex;
gap: var(--spacing-sm);
margin-bottom: var(--spacing-sm);
}
.setting-option {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
padding: var(--spacing-sm) var(--spacing-md);
background-color: var(--color-bg-secondary);
border: 2px solid var(--color-border);
border-radius: var(--radius-md);
cursor: pointer;
transition: all var(--transition-fast);
font-size: 0.9rem;
color: var(--color-text-secondary);
position: relative;
}
.setting-option input {
position: absolute;
opacity: 0;
width: 0;
height: 0;
}
.setting-option:hover {
border-color: var(--color-accent);
background-color: var(--color-bg-tertiary);
color: var(--color-text-primary);
}
.setting-option.active {
border-color: var(--color-accent);
background-color: var(--color-accent-light);
color: var(--color-accent);
font-weight: 600;
}
.setting-description {
margin: var(--spacing-xs) 0 0 0;
font-size: 0.8rem;
color: var(--color-text-muted);
line-height: 1.5;
}
/* 主内容区域 */
.main-container {
margin-top: 50px;

View File

@@ -5,7 +5,7 @@ import ThemeToggle from './items/ThemeToggle';
import useWorldBookStore from '../../Store/SideBarLeft/WorldBookSlice';
import useApiConfigStore from '../../Store/SideBarLeft/ApiConfigSlice';
const Toolbar = () => {
const Toolbar = ({ sidebarMode, colorTheme, onSidebarModeChange, onColorThemeChange }) => {
const [activePanel, setActivePanel] = useState(null);
const panelRef = useRef(null);
const [currentUserRole, setCurrentUserRole] = useState({ name: '', description: '' });
@@ -53,19 +53,19 @@ const Toolbar = () => {
loadApiModels();
}, [activeMap, fetchProfile]);
// 点击外部关闭面板
React.useEffect(() => {
const handleClickOutside = (event) => {
if (panelRef.current && !panelRef.current.contains(event.target)) {
setActivePanel(null);
}
};
// 点击外部关闭面板 - 使用 useCallback 优化
const handleClickOutside = React.useCallback((event) => {
if (panelRef.current && !panelRef.current.contains(event.target)) {
setActivePanel(null);
}
}, []);
React.useEffect(() => {
document.addEventListener('mousedown', handleClickOutside);
return () => {
document.removeEventListener('mousedown', handleClickOutside);
};
}, []);
}, [handleClickOutside]);
// 加载当前用户角色
useEffect(() => {
@@ -170,7 +170,7 @@ const Toolbar = () => {
</div>
</div>
{/* 全局世界书面板 */}
{/* 全局世界书面板 - 使用条件渲染优化 */}
{activePanel === 'worldBook' && (
<div className="panel-overlay" ref={panelRef}>
<div className="panel-content">
@@ -197,7 +197,7 @@ const Toolbar = () => {
</div>
)}
{/* 设置面板 */}
{/* 设置面板 - 使用条件渲染优化 */}
{activePanel === 'settings' && (
<div className="panel-overlay" ref={panelRef}>
<div className="panel-content">
@@ -208,13 +208,53 @@ const Toolbar = () => {
</button>
</div>
<div className="panel-body">
<p>系统设置内容...</p>
{/* 左侧栏模式 */}
<div className="setting-section">
<h4>左侧栏模式</h4>
<div className="setting-options">
<label className={`setting-option ${sidebarMode === 'fixed' ? 'active' : ''}`}>
<input
type="radio"
name="sidebarMode"
value="fixed"
checked={sidebarMode === 'fixed'}
onChange={(e) => onSidebarModeChange(e.target.value)}
/>
<span>固定</span>
</label>
<label className={`setting-option ${sidebarMode === 'smart' ? 'active' : ''}`}>
<input
type="radio"
name="sidebarMode"
value="smart"
checked={sidebarMode === 'smart'}
onChange={(e) => onSidebarModeChange(e.target.value)}
/>
<span>智能</span>
</label>
<label className={`setting-option ${sidebarMode === 'expanded' ? 'active' : ''}`}>
<input
type="radio"
name="sidebarMode"
value="expanded"
checked={sidebarMode === 'expanded'}
onChange={(e) => onSidebarModeChange(e.target.value)}
/>
<span>扩展</span>
</label>
</div>
<p className="setting-description">
{sidebarMode === 'fixed' && '左侧栏保持默认宽度,不响应交互'}
{sidebarMode === 'smart' && '鼠标悬停时自动展开,移开后收起'}
{sidebarMode === 'expanded' && '左侧栏保持最大宽度'}
</p>
</div>
</div>
</div>
</div>
)}
{/* 拓展面板 */}
{/* 拓展面板 - 使用条件渲染优化 */}
{activePanel === 'extensions' && (
<div className="panel-overlay" ref={panelRef}>
<div className="panel-content">

View File

@@ -2,3 +2,115 @@
.theme-toggle {
/* All styles inherited from .action-btn */
}
.theme-toggle-wrapper {
position: relative;
}
/* 主题选择面板 */
.theme-selector-panel {
position: absolute;
top: calc(100% + 8px);
right: 0;
background-color: var(--color-bg-secondary);
border: 1px solid var(--color-border);
border-radius: var(--radius-lg);
box-shadow: var(--shadow-xl);
min-width: 280px;
z-index: var(--z-popover);
animation: fadeInScale var(--transition-fast);
}
@keyframes fadeInScale {
from {
opacity: 0;
transform: scale(0.95) translateY(-10px);
}
to {
opacity: 1;
transform: scale(1) translateY(0);
}
}
.theme-selector-header {
padding: var(--spacing-sm) var(--spacing-md);
border-bottom: 1px solid var(--color-border-light);
}
.theme-selector-title {
font-size: 0.85rem;
font-weight: 600;
color: var(--color-text-primary);
font-family: var(--font-ui);
}
.theme-list {
padding: var(--spacing-xs);
display: flex;
flex-direction: column;
gap: 2px;
}
.theme-item {
display: flex;
align-items: center;
gap: var(--spacing-sm);
padding: var(--spacing-sm) var(--spacing-md);
background: transparent;
border: none;
border-radius: var(--radius-md);
cursor: pointer;
transition: all var(--transition-fast);
text-align: left;
width: 100%;
}
.theme-item:hover {
background-color: var(--color-bg-tertiary);
}
.theme-item.active {
background-color: var(--color-accent-light);
border: 1px solid var(--color-accent);
}
.theme-item-icon {
font-size: 1.3rem;
line-height: 1;
flex-shrink: 0;
width: 24px;
text-align: center;
}
.theme-item-info {
flex: 1;
min-width: 0;
}
.theme-item-name {
font-size: 0.9rem;
font-weight: 600;
color: var(--color-text-primary);
margin-bottom: 2px;
font-family: var(--font-ui);
}
.theme-item-desc {
font-size: 0.75rem;
color: var(--color-text-muted);
line-height: 1.3;
}
.theme-color-preview {
display: flex;
gap: 3px;
flex-shrink: 0;
}
.theme-color-bg,
.theme-color-accent {
width: 16px;
height: 16px;
border-radius: 3px;
border: 1px solid rgba(0, 0, 0, 0.1);
}

View File

@@ -1,32 +1,137 @@
import React, { useState, useEffect } from 'react';
import React, { useState, useEffect, useRef } from 'react';
import './ThemeToggle.css';
const themes = [
{
id: 'paper',
name: '仿纸暖黄',
description: '经典护眼,像 Kindle',
icon: '📖',
colors: {
bg: '#F5ECD7',
text: '#3E3A35',
accent: '#8B9A6B'
}
},
{
id: 'modern',
name: '现代清爽',
description: '干净明快,适合网文',
icon: '✨',
colors: {
bg: '#F8F8F8',
text: '#2C2C2C',
accent: '#4A9FB0'
}
},
{
id: 'dark',
name: '夜间暗色',
description: '黑底灰字,夜间必备',
icon: '🌙',
colors: {
bg: '#1E1E1E',
text: '#BDBBB6',
accent: '#D4A76A'
}
},
{
id: 'green',
name: '豆沙绿',
description: '自然疗愈,绿色经典',
icon: '🍃',
colors: {
bg: '#DCE5D9',
text: '#2B3330',
accent: '#7D9B7A'
}
}
];
const ThemeToggle = () => {
const [theme, setTheme] = useState(() => {
// 从 localStorage 读取主题,默认为 dark
const savedTheme = localStorage.getItem('theme');
return savedTheme || 'dark';
const [isOpen, setIsOpen] = useState(false);
const [currentTheme, setCurrentTheme] = useState(() => {
return localStorage.getItem('colorTheme') || 'dark';
});
const panelRef = useRef(null);
useEffect(() => {
// 应用主题到 document
document.documentElement.setAttribute('data-theme', theme);
document.documentElement.setAttribute('data-theme', currentTheme);
document.documentElement.setAttribute('data-color-theme', currentTheme);
// 保存到 localStorage
localStorage.setItem('theme', theme);
}, [theme]);
localStorage.setItem('theme', currentTheme);
localStorage.setItem('colorTheme', currentTheme);
}, [currentTheme]);
const toggleTheme = () => {
setTheme(prev => prev === 'dark' ? 'light' : 'dark');
// 点击外部关闭面板
useEffect(() => {
const handleClickOutside = (event) => {
if (panelRef.current && !panelRef.current.contains(event.target)) {
setIsOpen(false);
}
};
if (isOpen) {
document.addEventListener('mousedown', handleClickOutside);
}
return () => {
document.removeEventListener('mousedown', handleClickOutside);
};
}, [isOpen]);
const handleThemeSelect = (themeId) => {
setCurrentTheme(themeId);
setIsOpen(false);
};
const currentThemeData = themes.find(t => t.id === currentTheme) || themes[2];
return (
<button
className="action-btn theme-toggle"
onClick={toggleTheme}
title={theme === 'light' ? '切换到夜间模式' : '切换到白天模式'}
>
{theme === 'light' ? '☾' : '☀'}
</button>
<div className="theme-toggle-wrapper" ref={panelRef}>
<button
className="action-btn theme-toggle"
onClick={() => setIsOpen(!isOpen)}
title="切换主题"
>
{currentThemeData.icon}
</button>
{/* 主题选择面板 */}
{isOpen && (
<div className="theme-selector-panel">
<div className="theme-selector-header">
<span className="theme-selector-title">选择主题</span>
</div>
<div className="theme-list">
{themes.map(theme => (
<button
key={theme.id}
className={`theme-item ${currentTheme === theme.id ? 'active' : ''}`}
onClick={() => handleThemeSelect(theme.id)}
>
<div className="theme-item-icon">{theme.icon}</div>
<div className="theme-item-info">
<div className="theme-item-name">{theme.name}</div>
<div className="theme-item-desc">{theme.description}</div>
</div>
<div className="theme-color-preview">
<div
className="theme-color-bg"
style={{ backgroundColor: theme.colors.bg }}
/>
<div
className="theme-color-accent"
style={{ backgroundColor: theme.colors.accent }}
/>
</div>
</button>
))}
</div>
</div>
)}
</div>
);
};