kra/web/src/layouts/components/SettingDrawer.tsx

516 lines
17 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 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;