pages_theme_settings.tsx 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344
  1. import React, { useState, useEffect } from 'react';
  2. import {
  3. Button, Space,
  4. Form, message,
  5. Card, Spin, Typography,
  6. Switch,
  7. Popconfirm, Radio, InputNumber,ColorPicker,
  8. } from 'antd';
  9. import dayjs from 'dayjs';
  10. import weekday from 'dayjs/plugin/weekday';
  11. import localeData from 'dayjs/plugin/localeData';
  12. import 'dayjs/locale/zh-cn';
  13. import type {
  14. ColorScheme
  15. } from '../share/types.ts';
  16. import { ThemeMode } from '../share/types.ts';
  17. import {
  18. FontSize,
  19. CompactMode,
  20. } from '../share/types.ts';
  21. import { useTheme } from './hooks_sys.tsx';
  22. // 配置 dayjs 插件
  23. dayjs.extend(weekday);
  24. dayjs.extend(localeData);
  25. // 设置 dayjs 语言
  26. dayjs.locale('zh-cn');
  27. const { Title } = Typography;
  28. // 定义预设配色方案 - 按明暗模式分组
  29. const COLOR_SCHEMES: Record<ThemeMode, Record<string, ColorScheme>> = {
  30. [ThemeMode.LIGHT]: {
  31. DEFAULT: {
  32. name: '默认浅色',
  33. primary: '#1890ff',
  34. background: '#f0f2f5',
  35. text: '#000000'
  36. },
  37. BLUE: {
  38. name: '蓝色',
  39. primary: '#096dd9',
  40. background: '#e6f7ff',
  41. text: '#003a8c'
  42. },
  43. GREEN: {
  44. name: '绿色',
  45. primary: '#52c41a',
  46. background: '#f6ffed',
  47. text: '#135200'
  48. },
  49. WARM: {
  50. name: '暖橙',
  51. primary: '#fa8c16',
  52. background: '#fff7e6',
  53. text: '#873800'
  54. }
  55. },
  56. [ThemeMode.DARK]: {
  57. DEFAULT: {
  58. name: '默认深色',
  59. primary: '#177ddc',
  60. background: '#141414',
  61. text: '#ffffff'
  62. },
  63. MIDNIGHT: {
  64. name: '午夜蓝',
  65. primary: '#1a3b7a',
  66. background: '#0a0a1a',
  67. text: '#e0e0e0'
  68. },
  69. FOREST: {
  70. name: '森林',
  71. primary: '#2e7d32',
  72. background: '#121212',
  73. text: '#e0e0e0'
  74. },
  75. SUNSET: {
  76. name: '日落',
  77. primary: '#f5222d',
  78. background: '#1a1a1a',
  79. text: '#ffffff'
  80. }
  81. }
  82. };
  83. // 主题设置页面
  84. export const ThemeSettingsPage = () => {
  85. const { isDark, currentTheme, updateTheme, saveTheme, resetTheme } = useTheme();
  86. const [form] = Form.useForm();
  87. const [loading, setLoading] = useState(false);
  88. // 处理配色方案选择
  89. const handleColorSchemeChange = (schemeName: string) => {
  90. const currentMode = form.getFieldValue('theme_mode') as ThemeMode;
  91. const scheme = COLOR_SCHEMES[currentMode][schemeName];
  92. if (!scheme) return;
  93. form.setFieldsValue({
  94. primary_color: scheme.primary,
  95. background_color: scheme.background,
  96. text_color: scheme.text
  97. });
  98. updateTheme({
  99. primary_color: scheme.primary,
  100. background_color: scheme.background,
  101. text_color: scheme.text
  102. });
  103. };
  104. // 初始化表单数据
  105. useEffect(() => {
  106. form.setFieldsValue({
  107. theme_mode: currentTheme.theme_mode,
  108. primary_color: currentTheme.primary_color,
  109. background_color: currentTheme.background_color || (isDark ? '#141414' : '#f0f2f5'),
  110. font_size: currentTheme.font_size,
  111. is_compact: currentTheme.is_compact
  112. });
  113. }, [currentTheme, form, isDark]);
  114. // 处理表单提交
  115. const handleSubmit = async (values: any) => {
  116. try {
  117. setLoading(true);
  118. await saveTheme(values);
  119. } catch (error) {
  120. message.error('保存主题设置失败');
  121. } finally {
  122. setLoading(false);
  123. }
  124. };
  125. // 处理重置
  126. const handleReset = async () => {
  127. try {
  128. setLoading(true);
  129. await resetTheme();
  130. } catch (error) {
  131. message.error('重置主题设置失败');
  132. } finally {
  133. setLoading(false);
  134. }
  135. };
  136. // 处理表单值变化 - 实时预览
  137. const handleValuesChange = (changedValues: any) => {
  138. updateTheme(changedValues);
  139. };
  140. return (
  141. <div>
  142. <Title level={2}>主题设置</Title>
  143. <Card>
  144. <Spin spinning={loading}>
  145. <Form
  146. form={form}
  147. layout="vertical"
  148. onFinish={handleSubmit}
  149. onValuesChange={handleValuesChange}
  150. initialValues={{
  151. theme_mode: currentTheme.theme_mode,
  152. primary_color: currentTheme.primary_color,
  153. background_color: currentTheme.background_color || (isDark ? '#141414' : '#f0f2f5'),
  154. font_size: currentTheme.font_size,
  155. is_compact: currentTheme.is_compact
  156. }}
  157. >
  158. {/* 配色方案选择 */}
  159. <Form.Item label="预设配色方案">
  160. <Space wrap>
  161. {(() => {
  162. const themeMode = (form.getFieldValue('theme_mode') as ThemeMode) || ThemeMode.LIGHT;
  163. const schemes = COLOR_SCHEMES[themeMode] || {};
  164. const currentPrimary = form.getFieldValue('primary_color');
  165. const currentBg = form.getFieldValue('background_color');
  166. const currentText = form.getFieldValue('text_color');
  167. return Object.entries(schemes).map(([key, scheme]) => {
  168. const isActive =
  169. scheme.primary === currentPrimary &&
  170. scheme.background === currentBg &&
  171. scheme.text === currentText;
  172. return (
  173. <Button
  174. key={key}
  175. onClick={() => {
  176. handleColorSchemeChange(key);
  177. form.setFieldValue('scheme_name', scheme.name);
  178. }}
  179. style={{
  180. backgroundColor: scheme.background,
  181. color: scheme.text,
  182. borderColor: isActive ? scheme.text : scheme.primary,
  183. borderWidth: isActive ? 2 : 1,
  184. boxShadow: isActive ? `0 0 0 2px ${scheme.primary}` : 'none',
  185. fontWeight: isActive ? 'bold' : 'normal',
  186. transition: 'all 0.3s'
  187. }}
  188. >
  189. {scheme.name}
  190. {isActive && (
  191. <span style={{ marginLeft: 4 }}>✓</span>
  192. )}
  193. </Button>
  194. );
  195. });
  196. })()}
  197. </Space>
  198. </Form.Item>
  199. {/* 主题模式 */}
  200. <Form.Item
  201. label="主题模式"
  202. name="theme_mode"
  203. rules={[{ required: true, message: '请选择主题模式' }]}
  204. >
  205. <Radio.Group>
  206. <Radio value={ThemeMode.LIGHT}>浅色模式</Radio>
  207. <Radio value={ThemeMode.DARK}>深色模式</Radio>
  208. </Radio.Group>
  209. </Form.Item>
  210. {/* 主题色 */}
  211. <Form.Item
  212. label="主题色"
  213. name="primary_color"
  214. rules={[{ required: true, message: '请选择主题色' }]}
  215. >
  216. <ColorPicker
  217. value={form.getFieldValue('primary_color')}
  218. onChange={(color) => {
  219. form.setFieldValue('primary_color', color.toHexString());
  220. updateTheme({ primary_color: color.toHexString() });
  221. }}
  222. allowClear
  223. />
  224. </Form.Item>
  225. {/* 背景色 */}
  226. <Form.Item
  227. label="背景色"
  228. name="background_color"
  229. rules={[{ required: true, message: '请选择背景色' }]}
  230. >
  231. <ColorPicker
  232. value={form.getFieldValue('background_color')}
  233. onChange={(color) => {
  234. form.setFieldValue('background_color', color.toHexString());
  235. updateTheme({ background_color: color.toHexString() });
  236. }}
  237. allowClear
  238. />
  239. </Form.Item>
  240. {/* 文字颜色 */}
  241. <Form.Item
  242. label="文字颜色"
  243. name="text_color"
  244. rules={[{ required: true, message: '请选择文字颜色' }]}
  245. >
  246. <ColorPicker
  247. value={form.getFieldValue('text_color')}
  248. onChange={(color) => {
  249. form.setFieldValue('text_color', color.toHexString());
  250. updateTheme({ text_color: color.toHexString() });
  251. }}
  252. allowClear
  253. />
  254. </Form.Item>
  255. {/* 圆角大小 */}
  256. <Form.Item
  257. label="圆角大小"
  258. name="border_radius"
  259. rules={[{ required: true, message: '请设置圆角大小' }]}
  260. initialValue={6}
  261. >
  262. <InputNumber<number>
  263. min={0}
  264. max={20}
  265. addonAfter="px"
  266. />
  267. </Form.Item>
  268. {/* 字体大小 */}
  269. <Form.Item
  270. label="字体大小"
  271. name="font_size"
  272. rules={[{ required: true, message: '请选择字体大小' }]}
  273. >
  274. <Radio.Group>
  275. <Radio value={FontSize.SMALL}>小</Radio>
  276. <Radio value={FontSize.MEDIUM}>中</Radio>
  277. <Radio value={FontSize.LARGE}>大</Radio>
  278. </Radio.Group>
  279. </Form.Item>
  280. {/* 紧凑模式 */}
  281. <Form.Item
  282. label="紧凑模式"
  283. name="is_compact"
  284. valuePropName="checked"
  285. getValueFromEvent={(checked: boolean) => checked ? CompactMode.COMPACT : CompactMode.NORMAL}
  286. getValueProps={(value: CompactMode) => ({
  287. checked: value === CompactMode.COMPACT
  288. })}
  289. >
  290. <Switch
  291. checkedChildren="开启"
  292. unCheckedChildren="关闭"
  293. />
  294. </Form.Item>
  295. {/* 操作按钮 */}
  296. <Form.Item>
  297. <Space>
  298. <Button type="primary" htmlType="submit">
  299. 保存设置
  300. </Button>
  301. <Popconfirm
  302. title="确定要重置主题设置吗?"
  303. onConfirm={handleReset}
  304. okText="确定"
  305. cancelText="取消"
  306. >
  307. <Button>重置为默认值</Button>
  308. </Popconfirm>
  309. </Space>
  310. </Form.Item>
  311. </Form>
  312. </Spin>
  313. </Card>
  314. </div>
  315. );
  316. };