完成大量美化
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user