pages_theme_settings.tsx 11 KB

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