516 lines
17 KiB
TypeScript
516 lines
17 KiB
TypeScript
/**
|
||
* KRA - Settings Drawer Component
|
||
* System configuration panel with appearance, layout, and general settings
|
||
*/
|
||
import React, { useState, useEffect } from 'react';
|
||
import { Drawer, Tabs, Switch, Select, InputNumber, Button, message, Modal, Upload } from 'antd';
|
||
import {
|
||
BgColorsOutlined,
|
||
LayoutOutlined,
|
||
SettingOutlined,
|
||
CheckOutlined,
|
||
SunOutlined,
|
||
MoonOutlined,
|
||
DesktopOutlined,
|
||
ReloadOutlined,
|
||
ExportOutlined,
|
||
ImportOutlined,
|
||
GithubOutlined,
|
||
BookOutlined,
|
||
} from '@ant-design/icons';
|
||
import { useAppStore, type DarkMode, type SideMode, type TransitionType, type GlobalSize } from '@/models/app';
|
||
import Logo from '@/components/Logo/Logo';
|
||
import './SettingDrawer.less';
|
||
|
||
interface SettingDrawerProps {
|
||
open: boolean;
|
||
onClose: () => void;
|
||
}
|
||
|
||
// Theme colors
|
||
const themeColors = [
|
||
{ name: '拂晓蓝', color: '#1890ff' },
|
||
{ name: '薄暮红', color: '#f5222d' },
|
||
{ name: '火山橙', color: '#fa541c' },
|
||
{ name: '日暮黄', color: '#faad14' },
|
||
{ name: '极光绿', color: '#52c41a' },
|
||
{ name: '明青', color: '#13c2c2' },
|
||
{ name: '酱紫', color: '#722ed1' },
|
||
{ name: '洋红', color: '#eb2f96' },
|
||
];
|
||
|
||
// Layout modes
|
||
const layoutModes: { key: SideMode; title: string; icon: React.ReactNode }[] = [
|
||
{ key: 'normal', title: '侧边菜单', icon: <LayoutOutlined /> },
|
||
{ key: 'head', title: '顶部菜单', icon: <DesktopOutlined /> },
|
||
{ key: 'combination', title: '混合菜单', icon: <LayoutOutlined /> },
|
||
];
|
||
|
||
// Section Header Component
|
||
const SectionHeader: React.FC<{ title: string }> = ({ title }) => (
|
||
<div className="kra-setting-section-header">
|
||
<div className="kra-setting-divider" />
|
||
<span className="kra-setting-section-title">{title}</span>
|
||
<div className="kra-setting-divider" />
|
||
</div>
|
||
);
|
||
|
||
// Setting Item Component
|
||
const SettingItem: React.FC<{
|
||
label: string;
|
||
description?: string;
|
||
children: React.ReactNode;
|
||
}> = ({ label, description, children }) => (
|
||
<div className="kra-setting-item">
|
||
<div className="kra-setting-item-label">
|
||
<span className="kra-setting-label">{label}</span>
|
||
{description && <span className="kra-setting-description">{description}</span>}
|
||
</div>
|
||
<div className="kra-setting-item-control">{children}</div>
|
||
</div>
|
||
);
|
||
|
||
// Appearance Settings Tab
|
||
const AppearanceSettings: React.FC = () => {
|
||
const { config, setDarkMode, setPrimaryColor, setWeakness, setGrey, setShowWatermark, setGlobalSize } = useAppStore();
|
||
|
||
const darkModeOptions: { key: DarkMode; icon: React.ReactNode; label: string }[] = [
|
||
{ key: 'light', icon: <SunOutlined />, label: '浅色' },
|
||
{ key: 'dark', icon: <MoonOutlined />, label: '深色' },
|
||
{ key: 'auto', icon: <DesktopOutlined />, label: '跟随系统' },
|
||
];
|
||
|
||
return (
|
||
<div className="kra-setting-content">
|
||
{/* Theme Mode */}
|
||
<div className="kra-setting-section">
|
||
<SectionHeader title="主题模式" />
|
||
<div className="kra-theme-mode-selector">
|
||
{darkModeOptions.map((option) => (
|
||
<div
|
||
key={option.key}
|
||
className={`kra-theme-mode-item ${config.darkMode === option.key ? 'active' : ''}`}
|
||
onClick={() => setDarkMode(option.key)}
|
||
style={config.darkMode === option.key ? { backgroundColor: config.primaryColor } : {}}
|
||
>
|
||
{option.icon}
|
||
<span>{option.label}</span>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
|
||
{/* Theme Color */}
|
||
<div className="kra-setting-section">
|
||
<SectionHeader title="主题颜色" />
|
||
<div className="kra-theme-color-picker">
|
||
{themeColors.map((item) => (
|
||
<div
|
||
key={item.color}
|
||
className={`kra-color-item ${config.primaryColor === item.color ? 'active' : ''}`}
|
||
style={{ backgroundColor: item.color }}
|
||
onClick={() => setPrimaryColor(item.color)}
|
||
title={item.name}
|
||
>
|
||
{config.primaryColor === item.color && <CheckOutlined />}
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
|
||
{/* Global Size */}
|
||
<div className="kra-setting-section">
|
||
<SectionHeader title="全局尺寸" />
|
||
<div className="kra-setting-card">
|
||
<SettingItem label="全局尺寸" description="设置全局组件尺寸">
|
||
<Select
|
||
value={config.globalSize}
|
||
onChange={(value: GlobalSize) => setGlobalSize(value)}
|
||
style={{ width: 140 }}
|
||
options={[
|
||
{ value: 'default', label: '默认就好了' },
|
||
{ value: 'large', label: '大点好' },
|
||
{ value: 'small', label: '小的也不错' },
|
||
]}
|
||
/>
|
||
</SettingItem>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Visual Accessibility */}
|
||
<div className="kra-setting-section">
|
||
<SectionHeader title="视觉辅助" />
|
||
<div className="kra-setting-card">
|
||
<SettingItem label="灰色模式" description="降低色彩饱和度">
|
||
<Switch checked={config.grey} onChange={setGrey} />
|
||
</SettingItem>
|
||
<SettingItem label="色弱模式" description="优化色彩对比度">
|
||
<Switch checked={config.weakness} onChange={setWeakness} />
|
||
</SettingItem>
|
||
<SettingItem label="显示水印" description="在页面显示水印标识">
|
||
<Switch checked={config.showWatermark} onChange={setShowWatermark} />
|
||
</SettingItem>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
};
|
||
|
||
|
||
// Layout Settings Tab
|
||
const LayoutSettings: React.FC = () => {
|
||
const {
|
||
config,
|
||
setSideMode,
|
||
setShowTabs,
|
||
setTransitionType,
|
||
setLayoutSideWidth,
|
||
setLayoutSideCollapsedWidth,
|
||
} = useAppStore();
|
||
|
||
return (
|
||
<div className="kra-setting-content">
|
||
{/* Layout Mode */}
|
||
<div className="kra-setting-section">
|
||
<SectionHeader title="布局模式" />
|
||
<div className="kra-layout-mode-selector">
|
||
{layoutModes.map((mode) => (
|
||
<div
|
||
key={mode.key}
|
||
className={`kra-layout-mode-item ${config.sideMode === mode.key ? 'active' : ''}`}
|
||
onClick={() => setSideMode(mode.key)}
|
||
style={config.sideMode === mode.key ? { borderColor: config.primaryColor } : {}}
|
||
>
|
||
<div className="kra-layout-preview">
|
||
{mode.key === 'normal' && (
|
||
<>
|
||
<div className="preview-side" style={{ backgroundColor: config.primaryColor }} />
|
||
<div className="preview-main">
|
||
<div className="preview-header" />
|
||
<div className="preview-content" />
|
||
</div>
|
||
</>
|
||
)}
|
||
{mode.key === 'head' && (
|
||
<>
|
||
<div className="preview-top-header" style={{ backgroundColor: config.primaryColor }} />
|
||
<div className="preview-body" />
|
||
</>
|
||
)}
|
||
{mode.key === 'combination' && (
|
||
<>
|
||
<div className="preview-top-header" style={{ backgroundColor: config.primaryColor }} />
|
||
<div className="preview-main">
|
||
<div className="preview-side" />
|
||
<div className="preview-content" />
|
||
</div>
|
||
</>
|
||
)}
|
||
</div>
|
||
<span className="kra-layout-mode-title">{mode.title}</span>
|
||
{config.sideMode === mode.key && (
|
||
<div className="kra-layout-mode-check" style={{ backgroundColor: config.primaryColor }}>
|
||
<CheckOutlined />
|
||
</div>
|
||
)}
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
|
||
{/* Interface Config */}
|
||
<div className="kra-setting-section">
|
||
<SectionHeader title="界面配置" />
|
||
<div className="kra-setting-card">
|
||
<SettingItem label="显示标签页" description="页面标签导航">
|
||
<Switch checked={config.showTabs} onChange={setShowTabs} />
|
||
</SettingItem>
|
||
<SettingItem label="页面切换动画" description="页面过渡效果">
|
||
<Select
|
||
value={config.transitionType}
|
||
onChange={(value: TransitionType) => setTransitionType(value)}
|
||
style={{ width: 120 }}
|
||
options={[
|
||
{ value: 'fade', label: '淡入淡出' },
|
||
{ value: 'slide', label: '滑动' },
|
||
{ value: 'zoom', label: '缩放' },
|
||
{ value: 'none', label: '无动画' },
|
||
]}
|
||
/>
|
||
</SettingItem>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Size Config */}
|
||
<div className="kra-setting-section">
|
||
<SectionHeader title="尺寸配置" />
|
||
<div className="kra-setting-card">
|
||
<div className="kra-size-config-item">
|
||
<div className="kra-size-config-info">
|
||
<h4>侧边栏展开宽度</h4>
|
||
<p>侧边栏完全展开时的宽度</p>
|
||
</div>
|
||
<div className="kra-size-config-control">
|
||
<InputNumber
|
||
value={config.layoutSideWidth}
|
||
min={150}
|
||
max={400}
|
||
step={10}
|
||
onChange={(value) => value && setLayoutSideWidth(value)}
|
||
/>
|
||
<span className="unit">px</span>
|
||
</div>
|
||
</div>
|
||
<div className="kra-size-config-item">
|
||
<div className="kra-size-config-info">
|
||
<h4>侧边栏收缩宽度</h4>
|
||
<p>侧边栏收缩时的最小宽度</p>
|
||
</div>
|
||
<div className="kra-size-config-control">
|
||
<InputNumber
|
||
value={config.layoutSideCollapsedWidth}
|
||
min={60}
|
||
max={100}
|
||
onChange={(value) => value && setLayoutSideCollapsedWidth(value)}
|
||
/>
|
||
<span className="unit">px</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
};
|
||
|
||
|
||
// General Settings Tab
|
||
const GeneralSettings: React.FC = () => {
|
||
const { config, resetConfig, updateConfig } = useAppStore();
|
||
const [browserInfo, setBrowserInfo] = useState('');
|
||
const [screenResolution, setScreenResolution] = useState('');
|
||
|
||
useEffect(() => {
|
||
const userAgent = navigator.userAgent;
|
||
if (userAgent.includes('Chrome')) {
|
||
setBrowserInfo('Chrome');
|
||
} else if (userAgent.includes('Firefox')) {
|
||
setBrowserInfo('Firefox');
|
||
} else if (userAgent.includes('Safari')) {
|
||
setBrowserInfo('Safari');
|
||
} else if (userAgent.includes('Edge')) {
|
||
setBrowserInfo('Edge');
|
||
} else {
|
||
setBrowserInfo('Unknown');
|
||
}
|
||
setScreenResolution(`${screen.width}×${screen.height}`);
|
||
}, []);
|
||
|
||
const handleResetConfig = () => {
|
||
Modal.confirm({
|
||
title: '重置配置',
|
||
content: '确定要重置所有配置吗?此操作不可撤销。',
|
||
okText: '确定',
|
||
cancelText: '取消',
|
||
onOk: () => {
|
||
resetConfig();
|
||
message.success('配置已重置');
|
||
},
|
||
});
|
||
};
|
||
|
||
const handleExportConfig = () => {
|
||
const configData = JSON.stringify(config, null, 2);
|
||
const blob = new Blob([configData], { type: 'application/json' });
|
||
const url = URL.createObjectURL(blob);
|
||
const link = document.createElement('a');
|
||
link.href = url;
|
||
link.download = `kra-config-${new Date().toISOString().split('T')[0]}.json`;
|
||
document.body.appendChild(link);
|
||
link.click();
|
||
document.body.removeChild(link);
|
||
URL.revokeObjectURL(url);
|
||
message.success('配置已导出');
|
||
};
|
||
|
||
const handleImportConfig = (file: File) => {
|
||
const reader = new FileReader();
|
||
reader.onload = (e) => {
|
||
try {
|
||
const importedConfig = JSON.parse(e.target?.result as string);
|
||
updateConfig(importedConfig);
|
||
message.success('配置已导入');
|
||
} catch {
|
||
message.error('配置文件格式错误');
|
||
}
|
||
};
|
||
reader.readAsText(file);
|
||
return false;
|
||
};
|
||
|
||
return (
|
||
<div className="kra-setting-content">
|
||
{/* System Info */}
|
||
<div className="kra-setting-section">
|
||
<SectionHeader title="系统信息" />
|
||
<div className="kra-setting-card">
|
||
<div className="kra-system-info-grid">
|
||
<div className="kra-system-info-item">
|
||
<span className="label">版本</span>
|
||
<span className="value">v1.0.0</span>
|
||
</div>
|
||
<div className="kra-system-info-item">
|
||
<span className="label">前端框架</span>
|
||
<span className="value">React 19</span>
|
||
</div>
|
||
<div className="kra-system-info-item">
|
||
<span className="label">UI 组件库</span>
|
||
<span className="value">Ant Design</span>
|
||
</div>
|
||
<div className="kra-system-info-item">
|
||
<span className="label">构建工具</span>
|
||
<span className="value">UMI</span>
|
||
</div>
|
||
<div className="kra-system-info-item">
|
||
<span className="label">浏览器</span>
|
||
<span className="value">{browserInfo}</span>
|
||
</div>
|
||
<div className="kra-system-info-item">
|
||
<span className="label">屏幕分辨率</span>
|
||
<span className="value">{screenResolution}</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Config Management */}
|
||
<div className="kra-setting-section">
|
||
<SectionHeader title="配置管理" />
|
||
<div className="kra-setting-card">
|
||
<div className="kra-config-action-item">
|
||
<div className="kra-config-action-icon danger">
|
||
<ReloadOutlined />
|
||
</div>
|
||
<div className="kra-config-action-info">
|
||
<h4>重置配置</h4>
|
||
<p>将所有设置恢复为默认值</p>
|
||
</div>
|
||
<Button danger onClick={handleResetConfig}>
|
||
重置配置
|
||
</Button>
|
||
</div>
|
||
<div className="kra-config-action-item">
|
||
<div className="kra-config-action-icon primary">
|
||
<ExportOutlined />
|
||
</div>
|
||
<div className="kra-config-action-info">
|
||
<h4>导出配置</h4>
|
||
<p>导出当前配置为 JSON 文件</p>
|
||
</div>
|
||
<Button type="primary" onClick={handleExportConfig}>
|
||
导出配置
|
||
</Button>
|
||
</div>
|
||
<div className="kra-config-action-item">
|
||
<div className="kra-config-action-icon success">
|
||
<ImportOutlined />
|
||
</div>
|
||
<div className="kra-config-action-info">
|
||
<h4>导入配置</h4>
|
||
<p>从 JSON 文件导入配置</p>
|
||
</div>
|
||
<Upload
|
||
accept=".json"
|
||
showUploadList={false}
|
||
beforeUpload={handleImportConfig}
|
||
>
|
||
<Button style={{ backgroundColor: '#52c41a', borderColor: '#52c41a', color: '#fff' }}>
|
||
导入配置
|
||
</Button>
|
||
</Upload>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* About Project */}
|
||
<div className="kra-setting-section">
|
||
<SectionHeader title="关于项目" />
|
||
<div className="kra-setting-card">
|
||
<div className="kra-about-project">
|
||
<div className="kra-about-logo">
|
||
<Logo size="large" />
|
||
</div>
|
||
<div className="kra-about-info">
|
||
<h4>Kratos Admin</h4>
|
||
<p>基于 React 19 + Go Kratos 的全栈开发基础平台,提供完整的后台管理解决方案</p>
|
||
<div className="kra-about-links">
|
||
<a href="https://github.com/go-kratos/kratos" target="_blank" rel="noopener noreferrer">
|
||
<GithubOutlined /> GitHub 仓库
|
||
</a>
|
||
<span className="divider">·</span>
|
||
<a href="https://go-kratos.dev/" target="_blank" rel="noopener noreferrer">
|
||
<BookOutlined /> 官方文档
|
||
</a>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
};
|
||
|
||
// Main Setting Drawer Component
|
||
const SettingDrawer: React.FC<SettingDrawerProps> = ({ open, onClose }) => {
|
||
const { config, resetConfig } = useAppStore();
|
||
|
||
const tabItems = [
|
||
{
|
||
key: 'appearance',
|
||
label: '外观',
|
||
children: <AppearanceSettings />,
|
||
},
|
||
{
|
||
key: 'layout',
|
||
label: '布局',
|
||
children: <LayoutSettings />,
|
||
},
|
||
{
|
||
key: 'general',
|
||
label: '通用',
|
||
children: <GeneralSettings />,
|
||
},
|
||
];
|
||
|
||
return (
|
||
<Drawer
|
||
title={
|
||
<div className="kra-setting-drawer-header">
|
||
<span>系统配置</span>
|
||
<Button
|
||
type="primary"
|
||
size="small"
|
||
onClick={resetConfig}
|
||
style={{ backgroundColor: config.primaryColor, borderColor: config.primaryColor }}
|
||
>
|
||
重置配置
|
||
</Button>
|
||
</div>
|
||
}
|
||
placement="right"
|
||
width={500}
|
||
open={open}
|
||
onClose={onClose}
|
||
className="kra-setting-drawer"
|
||
>
|
||
<Tabs
|
||
defaultActiveKey="appearance"
|
||
items={tabItems}
|
||
centered
|
||
className="kra-setting-tabs"
|
||
/>
|
||
</Drawer>
|
||
);
|
||
};
|
||
|
||
export default SettingDrawer;
|