238 lines
8.5 KiB
TypeScript
238 lines
8.5 KiB
TypeScript
import React from 'react';
|
|
import {debounce} from 'lodash';
|
|
import {Button, Col, ColorPicker, Divider, Drawer, InputNumber, Row, Select, Switch, theme, Tooltip} from 'antd';
|
|
import useGlobalStore from "@/stores/global";
|
|
import {configTheme, darkColorTheme, defaultColorTheme} from "@/layout/theme.ts";
|
|
import {algorithmOptions} from "@/layout/algorithm.ts";
|
|
|
|
import {useTranslation} from 'react-i18next';
|
|
|
|
const {useToken} = theme;
|
|
|
|
|
|
// 主题配置项
|
|
const THEME_CONFIGS = [
|
|
{key: 'colorPrimary', label: 'layout.colorPrimary'},
|
|
{key: 'colorText', label: 'layout.colorText'},
|
|
{key: 'colorBg', label: 'layout.colorBg'},
|
|
{key: 'colorSuccess', label: 'layout.colorSuccess'},
|
|
{key: 'colorWarning', label: 'layout.colorWarning'},
|
|
{key: 'colorError', label: 'layout.colorError'},
|
|
{key: 'bodyBg', label: 'layout.bodyBg'},
|
|
{key: 'footerBg', label: 'layout.footerBg'},
|
|
{key: 'headerBg', label: 'layout.headerBg'},
|
|
{key: 'headerColor', label: 'layout.headerColor'},
|
|
{key: 'siderBg', label: 'layout.siderBg'},
|
|
{key: 'siderColor', label: 'layout.siderColor'},
|
|
{key: 'colorBorder', label: 'layout.colorBorder'},
|
|
];
|
|
|
|
// 风格配置项
|
|
const STYLE_CONFIGS = [
|
|
{key: 'fixedFooter', label: 'layout.fixedFooter', type: 'switch'},
|
|
{key: 'borderRadius', label: 'layout.borderRadius', type: 'number', min: 0, max: 30},
|
|
{key: 'controlHeight', label: 'layout.controlHeight', type: 'number', min: 0},
|
|
{key: 'headerPadding', label: 'layout.headerPadding', type: 'number', min: 0},
|
|
{key: 'headerHeight', label: 'layout.headerHeight', type: 'number', min: 0},
|
|
{key: 'siderWeight', label: 'layout.siderWeight', type: 'number', min: 0},
|
|
{key: 'bodyPadding', label: 'layout.bodyPadding', type: 'number', min: 0},
|
|
];
|
|
|
|
// 预设主题列表
|
|
const THEME_LIST = [
|
|
{background: '/static/theme/default.svg', name: 'light', title: 'layout.themeLight'},
|
|
{background: '/static/theme/dark.svg', name: 'dark', title: 'layout.themeDark'},
|
|
];
|
|
|
|
// 布局配置
|
|
const LAYOUT_CONFIGS = [
|
|
{
|
|
key: 'side', title: 'layout.layoutSide', render: (token: any) => (
|
|
<>
|
|
<div className="rounded-sm w-full h-6 mb-1.5" style={{background: token.colorPrimaryBorder}}/>
|
|
<div className="flex">
|
|
<div className="rounded-sm h-16 w-6" style={{background: token.colorPrimary}}/>
|
|
<div className="rounded-sm flex-1 ml-1.5" style={{background: token.colorPrimaryBg}}/>
|
|
</div>
|
|
</>
|
|
)
|
|
},
|
|
{
|
|
key: 'top', title: 'layout.layoutTop', render: (token: any) => (
|
|
<>
|
|
<div className="rounded-sm h-6 w-full mb-1.5" style={{background: token.colorPrimary}}/>
|
|
<div className="rounded-sm h-16" style={{background: token.colorPrimaryBg}}/>
|
|
</>
|
|
)
|
|
},
|
|
{
|
|
key: 'mix', title: 'layout.layoutMix', render: (token: any) => (
|
|
<>
|
|
<div className="rounded-sm w-full h-6 mb-1.5" style={{background: token.colorPrimary}}/>
|
|
<div className="flex">
|
|
<div className="rounded-sm h-16 w-6" style={{background: token.colorPrimary}}/>
|
|
<div className="rounded-sm flex-1 ml-1.5" style={{background: token.colorPrimaryBg}}/>
|
|
</div>
|
|
</>
|
|
)
|
|
},
|
|
{
|
|
key: 'columns', title: 'layout.layoutColumns', render: (token: any) => (
|
|
<div className="flex">
|
|
<div className="rounded-sm mr-1.5 w-3 h-24" style={{background: token.colorPrimary}}/>
|
|
<div className="rounded-sm mr-1.5 w-6 h-24" style={{background: token.colorPrimaryHover}}/>
|
|
<div className="rounded-sm flex-auto h-24" style={{background: token.colorPrimaryBg}}/>
|
|
</div>
|
|
)
|
|
},
|
|
];
|
|
|
|
const SettingDrawer: React.FC = () => {
|
|
const {t} = useTranslation();
|
|
const {token} = useToken();
|
|
const themeDrawer = useGlobalStore(state => state.themeDrawer);
|
|
const setThemeDrawer = useGlobalStore(state => state.setThemeDrawer);
|
|
const setThemeConfig = useGlobalStore(state => state.setThemeConfig);
|
|
const themeConfig = useGlobalStore(state => state.themeConfig);
|
|
const layout = useGlobalStore(state => state.layout);
|
|
const setLayout = useGlobalStore(state => state.setLayout);
|
|
|
|
// 翻译后的算法选项
|
|
const translatedAlgorithmOptions = algorithmOptions?.map(option => ({
|
|
...option,
|
|
label: t(option.label as string)
|
|
})) || [];
|
|
|
|
// 防抖更新主题配置(带过渡动画)
|
|
const changeSetting = debounce((key: string, value: any) => {
|
|
setThemeConfig({...themeConfig, [key]: value});
|
|
}, 400, {leading: true, trailing: false});
|
|
|
|
// 处理主题切换(带圆形扩散动画)
|
|
const handleThemeChange = (e: React.MouseEvent<HTMLDivElement>) => {
|
|
const target = (e.target as HTMLElement).closest('[data-theme]');
|
|
if (!target) return;
|
|
|
|
const themeName = (target as HTMLElement).dataset.theme;
|
|
const themeMap = {
|
|
dark: darkColorTheme,
|
|
light: defaultColorTheme
|
|
};
|
|
|
|
if (themeMap[themeName as keyof typeof themeMap]) {
|
|
setThemeConfig({...themeConfig, ...themeMap[themeName as keyof typeof themeMap]});
|
|
}
|
|
};
|
|
|
|
// 重置主题(带过渡动画)
|
|
const resetTheme = () => {
|
|
setThemeConfig({...configTheme, ...defaultColorTheme});
|
|
};
|
|
|
|
return (
|
|
<Drawer
|
|
styles={{body: {paddingTop: 10}}}
|
|
placement="right"
|
|
closable={false}
|
|
onClose={() => setThemeDrawer(false)}
|
|
open={themeDrawer}
|
|
footer={(
|
|
<div className="flex w-full justify-between">
|
|
<Button onClick={resetTheme}>
|
|
{t('layout.resetTheme')}
|
|
</Button>
|
|
</div>
|
|
)}
|
|
>
|
|
{/* 布局样式 */}
|
|
<Divider>{t('layout.layoutStyle')}</Divider>
|
|
<Row gutter={[20, 20]}>
|
|
{LAYOUT_CONFIGS.map(({key, title, render}) => (
|
|
<Col span={12} key={key}>
|
|
<Tooltip title={t(title)}>
|
|
<div
|
|
className="p-2 rounded-lg cursor-pointer"
|
|
style={{
|
|
boxShadow: token.boxShadow,
|
|
borderRadius: token.borderRadius,
|
|
border: layout === key ? `2px solid ${token.colorPrimary}` : '2px solid transparent',
|
|
}}
|
|
onClick={() => setLayout(key as any)}
|
|
>
|
|
{render(token)}
|
|
</div>
|
|
</Tooltip>
|
|
</Col>
|
|
))}
|
|
</Row>
|
|
|
|
{/* 预设主题 */}
|
|
<Divider>{t('layout.presetTheme')}</Divider>
|
|
<Row gutter={20} onClick={handleThemeChange}>
|
|
{THEME_LIST.map((item) => (
|
|
<Col span={8} key={item.name} className="mb-2.5">
|
|
<div
|
|
data-theme={item.name}
|
|
className="cursor-pointer overflow-hidden border-solid"
|
|
style={{
|
|
borderRadius: token.borderRadius,
|
|
borderWidth: themeConfig.themeScheme === item.name ? '2px' : '0px',
|
|
borderColor: themeConfig.themeScheme === item.name ? token.colorPrimary : 'transparent'
|
|
}}
|
|
>
|
|
<img src={item.background} alt={item.name}/>
|
|
</div>
|
|
<div className="text-center mt-1.5">{t(item.title)}</div>
|
|
</Col>
|
|
))}
|
|
<Col span={24} className="mb-2.5">
|
|
<div className="flex justify-between items-center">
|
|
<div>{t('layout.themeAlgorithm')}</div>
|
|
<Select
|
|
value={themeConfig.algorithm}
|
|
style={{ width: 160 }}
|
|
onChange={(value) => changeSetting('algorithm', value)}
|
|
options={translatedAlgorithmOptions}
|
|
/>
|
|
</div>
|
|
</Col>
|
|
</Row>
|
|
|
|
{/* 主题颜色 */}
|
|
<Divider>{t('layout.themeColor')}</Divider>
|
|
{THEME_CONFIGS.map(({key, label}) => (
|
|
<div key={key} className="flex justify-between items-center mb-2.5">
|
|
<div>{t(label)}</div>
|
|
<ColorPicker
|
|
showText
|
|
value={themeConfig[key as keyof typeof themeConfig] as string}
|
|
onChange={(value) => changeSetting(key, value.toCssString())}
|
|
/>
|
|
</div>
|
|
))}
|
|
|
|
{/* 风格配置 */}
|
|
<Divider>{t('layout.styleConfig')}</Divider>
|
|
{STYLE_CONFIGS.map(({key, label, type, ...rest}) => (
|
|
<div key={key} className="flex justify-between items-center mb-2.5">
|
|
<div>{t(label)}</div>
|
|
{type === 'switch' ? (
|
|
<Switch
|
|
value={themeConfig[key as keyof typeof themeConfig] as boolean}
|
|
onChange={(value) => changeSetting(key, value)}
|
|
/>
|
|
) : (
|
|
<InputNumber
|
|
value={themeConfig[key as keyof typeof themeConfig] as number}
|
|
onChange={(value) => changeSetting(key, value)}
|
|
{...rest}
|
|
/>
|
|
)}
|
|
</div>
|
|
))}
|
|
</Drawer>
|
|
);
|
|
};
|
|
|
|
export default SettingDrawer;
|