From e2b882777bb43ec89cbff67c29f5035319f91810 Mon Sep 17 00:00:00 2001 From: zyh Date: Fri, 11 Apr 2025 07:09:18 +0000 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E7=9F=A5=E8=AF=86=E5=BA=93?= =?UTF-8?q?=E7=AE=A1=E7=90=86=E9=A1=B5=E9=9D=A2=EF=BC=8C=E5=8C=85=E5=90=AB?= =?UTF-8?q?=E7=9F=A5=E8=AF=86=E5=BA=93=E6=96=87=E7=AB=A0=E7=9A=84=E5=A2=9E?= =?UTF-8?q?=E5=88=A0=E6=94=B9=E6=9F=A5=E5=8A=9F=E8=83=BD=EF=BC=8C=E4=BC=98?= =?UTF-8?q?=E5=8C=96API=E6=8E=A5=E5=8F=A3=E5=AE=9A=E4=B9=89=EF=BC=8C?= =?UTF-8?q?=E6=8F=90=E5=8D=87=E7=94=A8=E6=88=B7=E4=BD=93=E9=AA=8C=E5=92=8C?= =?UTF-8?q?=E4=BB=A3=E7=A0=81=E5=8F=AF=E7=BB=B4=E6=8A=A4=E6=80=A7=E3=80=82?= =?UTF-8?q?=E5=90=8C=E6=97=B6=EF=BC=8C=E6=9B=B4=E6=96=B0=E4=B8=BB=E9=A2=98?= =?UTF-8?q?=E8=AE=BE=E7=BD=AE=E9=A1=B5=E9=9D=A2=EF=BC=8C=E6=95=B4=E5=90=88?= =?UTF-8?q?=E9=85=8D=E8=89=B2=E6=96=B9=E6=A1=88=E9=80=BB=E8=BE=91=EF=BC=8C?= =?UTF-8?q?=E5=A2=9E=E5=BC=BA=E7=94=A8=E6=88=B7=E4=BA=A4=E4=BA=92=E4=BD=93?= =?UTF-8?q?=E9=AA=8C=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/admin/api.ts | 2 +- .../{pages_know.tsx => pages_know_info.tsx} | 78 +--- client/admin/pages_settings.tsx | 360 ----------------- client/admin/pages_theme_settings.tsx | 367 ++++++++++++++++++ ....test.tsx => pages_theme_setting.test.tsx} | 2 +- client/admin/web_app.tsx | 21 +- 6 files changed, 391 insertions(+), 439 deletions(-) rename client/admin/{pages_know.tsx => pages_know_info.tsx} (83%) create mode 100644 client/admin/pages_theme_settings.tsx rename client/admin/test/{theme_setting_page.test.tsx => pages_theme_setting.test.tsx} (98%) diff --git a/client/admin/api.ts b/client/admin/api.ts index 43931fb..90f4d76 100644 --- a/client/admin/api.ts +++ b/client/admin/api.ts @@ -606,7 +606,7 @@ export interface LoginLocationUpdateResponse { } // 知识库相关接口类型定义 -interface KnowInfoListResponse { +export interface KnowInfoListResponse { data: KnowInfo[]; pagination: { total: number; diff --git a/client/admin/pages_know.tsx b/client/admin/pages_know_info.tsx similarity index 83% rename from client/admin/pages_know.tsx rename to client/admin/pages_know_info.tsx index d4957bc..30d6e9b 100644 --- a/client/admin/pages_know.tsx +++ b/client/admin/pages_know_info.tsx @@ -36,6 +36,8 @@ import { getEnumOptions } from './utils.ts'; import { FileAPI, UserAPI, + KnowInfoAPI, + type KnowInfoListResponse } from './api.ts'; @@ -64,29 +66,14 @@ export const KnowInfoPage = () => { }); // 使用React Query获取知识库文章列表 - const { data: articlesData, isLoading: isListLoading, refetch } = useQuery({ - queryKey: ['articles', searchParams], - queryFn: async () => { - const { title, category, page, limit } = searchParams; - const params = new URLSearchParams(); - - if (title) params.append('title', title); - if (category) params.append('category', category); - params.append('page', String(page)); - params.append('limit', String(limit)); - - const response = await fetch(`/api/know-info?${params.toString()}`, { - headers: { - 'Authorization': `Bearer ${localStorage.getItem('token')}`, - }, - }); - - if (!response.ok) { - throw new Error('获取知识库文章列表失败'); - } - - return await response.json(); - } + const { data: articlesData, isLoading: isListLoading, refetch } = useQuery({ + queryKey: ['knowInfos', searchParams], + queryFn: () => KnowInfoAPI.getKnowInfos({ + page: searchParams.page, + pageSize: searchParams.limit, + search: searchParams.title, + categoryId: searchParams.category ? Number(searchParams.category) : undefined + }) }); const articles = articlesData?.data || []; @@ -95,17 +82,8 @@ export const KnowInfoPage = () => { // 获取单个知识库文章 const fetchArticle = async (id: number) => { try { - const response = await fetch(`/api/know-info/${id}`, { - headers: { - 'Authorization': `Bearer ${localStorage.getItem('token')}`, - }, - }); - - if (!response.ok) { - throw new Error('获取知识库文章详情失败'); - } - - return await response.json(); + const response = await KnowInfoAPI.getKnowInfo(id); + return response.data; } catch (error) { message.error('获取知识库文章详情失败'); return null; @@ -117,24 +95,9 @@ export const KnowInfoPage = () => { setIsLoading(true); try { - const url = formMode === 'create' - ? '/api/know-info' - : `/api/know-info/${editingId}`; - - const method = formMode === 'create' ? 'POST' : 'PUT'; - - const response = await fetch(url, { - method, - headers: { - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${localStorage.getItem('token')}`, - }, - body: JSON.stringify(values), - }); - - if (!response.ok) { - throw new Error(formMode === 'create' ? '创建知识库文章失败' : '更新知识库文章失败'); - } + const response = formMode === 'create' + ? await KnowInfoAPI.createKnowInfo(values) + : await KnowInfoAPI.updateKnowInfo(editingId!, values); message.success(formMode === 'create' ? '创建知识库文章成功' : '更新知识库文章成功'); setModalVisible(false); @@ -162,16 +125,7 @@ export const KnowInfoPage = () => { // 处理删除 const handleDelete = async (id: number) => { try { - const response = await fetch(`/api/know-info/${id}`, { - method: 'DELETE', - headers: { - 'Authorization': `Bearer ${localStorage.getItem('token')}`, - }, - }); - - if (!response.ok) { - throw new Error('删除知识库文章失败'); - } + await KnowInfoAPI.deleteKnowInfo(id); message.success('删除知识库文章成功'); refetch(); diff --git a/client/admin/pages_settings.tsx b/client/admin/pages_settings.tsx index 8001147..dfa3061 100644 --- a/client/admin/pages_settings.tsx +++ b/client/admin/pages_settings.tsx @@ -74,116 +74,6 @@ const GROUP_DESCRIPTIONS: Record> = { - [ThemeMode.LIGHT]: { - DEFAULT: { - name: '默认浅色', - primary: '#1890ff', - background: '#f0f2f5', - text: '#000000' - }, - BLUE: { - name: '蓝色', - primary: '#096dd9', - background: '#e6f7ff', - text: '#003a8c' - }, - GREEN: { - name: '绿色', - primary: '#52c41a', - background: '#f6ffed', - text: '#135200' - }, - WARM: { - name: '暖橙', - primary: '#fa8c16', - background: '#fff7e6', - text: '#873800' - } - }, - [ThemeMode.DARK]: { - DEFAULT: { - name: '默认深色', - primary: '#177ddc', - background: '#141414', - text: '#ffffff' - }, - MIDNIGHT: { - name: '午夜蓝', - primary: '#1a3b7a', - background: '#0a0a1a', - text: '#e0e0e0' - }, - FOREST: { - name: '森林', - primary: '#2e7d32', - background: '#121212', - text: '#e0e0e0' - }, - SUNSET: { - name: '日落', - primary: '#f5222d', - background: '#1a1a1a', - text: '#ffffff' - } - } -}; - -// 颜色选择器组件 -// const ColorPicker: React.FC<{ -// value?: string; -// onChange?: (color: string) => void; -// label?: string; -// }> = ({ value = '#1890ff', onChange, label = '选择颜色' }) => { -// const [color, setColor] = useState(value); -// const [open, setOpen] = useState(false); - -// // 更新颜色(预览) -// const handleColorChange = (e: React.ChangeEvent) => { -// const newColor = e.target.value; -// setColor(newColor); -// }; - -// // 关闭时确认颜色 -// const handleOpenChange = (visible: boolean) => { -// if (!visible && color) { -// onChange?.(color); -// } -// setOpen(visible); -// }; - -// return ( -// -// -// -// } -// > -// -// -// ); -// }; - -// 基础设置页面 export const SettingsPage = () => { const [form] = Form.useForm(); const queryClient = useQueryClient(); @@ -397,253 +287,3 @@ export const SettingsPage = () => { ); }; -// 主题设置页面 -export const ThemeSettingsPage = () => { - const { isDark, currentTheme, updateTheme, saveTheme, resetTheme } = useTheme(); - const [form] = Form.useForm(); - const [loading, setLoading] = useState(false); - - // 处理配色方案选择 - const handleColorSchemeChange = (schemeName: string) => { - const currentMode = form.getFieldValue('theme_mode') as ThemeMode; - const scheme = COLOR_SCHEMES[currentMode][schemeName]; - if (!scheme) return; - form.setFieldsValue({ - primary_color: scheme.primary, - background_color: scheme.background, - text_color: scheme.text - }); - updateTheme({ - primary_color: scheme.primary, - background_color: scheme.background, - text_color: scheme.text - }); - }; - - // 初始化表单数据 - useEffect(() => { - form.setFieldsValue({ - theme_mode: currentTheme.theme_mode, - primary_color: currentTheme.primary_color, - background_color: currentTheme.background_color || (isDark ? '#141414' : '#f0f2f5'), - font_size: currentTheme.font_size, - is_compact: currentTheme.is_compact - }); - }, [currentTheme, form, isDark]); - - // 处理表单提交 - const handleSubmit = async (values: any) => { - try { - setLoading(true); - await saveTheme(values); - } catch (error) { - message.error('保存主题设置失败'); - } finally { - setLoading(false); - } - }; - - // 处理重置 - const handleReset = async () => { - try { - setLoading(true); - await resetTheme(); - } catch (error) { - message.error('重置主题设置失败'); - } finally { - setLoading(false); - } - }; - - // 处理表单值变化 - 实时预览 - const handleValuesChange = (changedValues: any) => { - updateTheme(changedValues); - }; - - return ( -
- 主题设置 - - -
- {/* 配色方案选择 */} - - - {(() => { - const themeMode = (form.getFieldValue('theme_mode') as ThemeMode) || ThemeMode.LIGHT; - const schemes = COLOR_SCHEMES[themeMode] || {}; - const currentPrimary = form.getFieldValue('primary_color'); - const currentBg = form.getFieldValue('background_color'); - const currentText = form.getFieldValue('text_color'); - - return Object.entries(schemes).map(([key, scheme]) => { - const isActive = - scheme.primary === currentPrimary && - scheme.background === currentBg && - scheme.text === currentText; - - return ( - - ); - }); - })()} - - - - {/* 主题模式 */} - - - 浅色模式 - 深色模式 - - - - {/* 主题色 */} - - { - form.setFieldValue('primary_color', color.toHexString()); - updateTheme({ primary_color: color.toHexString() }); - }} - allowClear - /> - - - {/* 背景色 */} - - { - form.setFieldValue('background_color', color.toHexString()); - updateTheme({ background_color: color.toHexString() }); - }} - allowClear - /> - - - {/* 文字颜色 */} - - { - form.setFieldValue('text_color', color.toHexString()); - updateTheme({ text_color: color.toHexString() }); - }} - allowClear - /> - - - {/* 圆角大小 */} - - - min={0} - max={20} - addonAfter="px" - /> - - - {/* 字体大小 */} - - - - - - - - - {/* 紧凑模式 */} - checked ? CompactMode.COMPACT : CompactMode.NORMAL} - getValueProps={(value: CompactMode) => ({ - checked: value === CompactMode.COMPACT - })} - > - - - - {/* 操作按钮 */} - - - - - - - - -
-
-
-
- ); -}; diff --git a/client/admin/pages_theme_settings.tsx b/client/admin/pages_theme_settings.tsx new file mode 100644 index 0000000..613d10d --- /dev/null +++ b/client/admin/pages_theme_settings.tsx @@ -0,0 +1,367 @@ +import React, { useState, useEffect } from 'react'; +import { + Layout, Menu, Button, Table, Space, + Form, Input, Select, message, Modal, + Card, Spin, Row, Col, Breadcrumb, Avatar, + Dropdown, ConfigProvider, theme, Typography, + Switch, Badge, Image, Upload, Divider, Descriptions, + Popconfirm, Tag, Statistic, DatePicker, Radio, Progress, Tabs, List, Alert, Collapse, Empty, Drawer, InputNumber,ColorPicker, + Popover +} from 'antd'; +import { + UploadOutlined, + ReloadOutlined, + SaveOutlined, + BgColorsOutlined +} from '@ant-design/icons'; +import { debounce } from 'lodash'; +import { + useQuery, + useMutation, + useQueryClient, +} from '@tanstack/react-query'; +import dayjs from 'dayjs'; +import weekday from 'dayjs/plugin/weekday'; +import localeData from 'dayjs/plugin/localeData'; +import 'dayjs/locale/zh-cn'; +import type { + FileLibrary, FileCategory, KnowInfo, SystemSetting, SystemSettingValue, + ColorScheme +} from '../share/types.ts'; +import { ThemeMode } from '../share/types.ts'; + +import { + SystemSettingGroup, + SystemSettingKey, + FontSize, + CompactMode, + AllowedFileType +} from '../share/types.ts'; + + +import { getEnumOptions } from './utils.ts'; + +import { + SystemAPI, +} from './api.ts'; + +import { useTheme } from './hooks_sys.tsx'; + +import { Uploader } from './components_uploader.tsx'; + +// 配置 dayjs 插件 +dayjs.extend(weekday); +dayjs.extend(localeData); + +// 设置 dayjs 语言 +dayjs.locale('zh-cn'); + +const { Title } = Typography; + + +// 定义预设配色方案 - 按明暗模式分组 +const COLOR_SCHEMES: Record> = { + [ThemeMode.LIGHT]: { + DEFAULT: { + name: '默认浅色', + primary: '#1890ff', + background: '#f0f2f5', + text: '#000000' + }, + BLUE: { + name: '蓝色', + primary: '#096dd9', + background: '#e6f7ff', + text: '#003a8c' + }, + GREEN: { + name: '绿色', + primary: '#52c41a', + background: '#f6ffed', + text: '#135200' + }, + WARM: { + name: '暖橙', + primary: '#fa8c16', + background: '#fff7e6', + text: '#873800' + } + }, + [ThemeMode.DARK]: { + DEFAULT: { + name: '默认深色', + primary: '#177ddc', + background: '#141414', + text: '#ffffff' + }, + MIDNIGHT: { + name: '午夜蓝', + primary: '#1a3b7a', + background: '#0a0a1a', + text: '#e0e0e0' + }, + FOREST: { + name: '森林', + primary: '#2e7d32', + background: '#121212', + text: '#e0e0e0' + }, + SUNSET: { + name: '日落', + primary: '#f5222d', + background: '#1a1a1a', + text: '#ffffff' + } + } +}; + +// 主题设置页面 +export const ThemeSettingsPage = () => { + const { isDark, currentTheme, updateTheme, saveTheme, resetTheme } = useTheme(); + const [form] = Form.useForm(); + const [loading, setLoading] = useState(false); + + // 处理配色方案选择 + const handleColorSchemeChange = (schemeName: string) => { + const currentMode = form.getFieldValue('theme_mode') as ThemeMode; + const scheme = COLOR_SCHEMES[currentMode][schemeName]; + if (!scheme) return; + form.setFieldsValue({ + primary_color: scheme.primary, + background_color: scheme.background, + text_color: scheme.text + }); + updateTheme({ + primary_color: scheme.primary, + background_color: scheme.background, + text_color: scheme.text + }); + }; + + // 初始化表单数据 + useEffect(() => { + form.setFieldsValue({ + theme_mode: currentTheme.theme_mode, + primary_color: currentTheme.primary_color, + background_color: currentTheme.background_color || (isDark ? '#141414' : '#f0f2f5'), + font_size: currentTheme.font_size, + is_compact: currentTheme.is_compact + }); + }, [currentTheme, form, isDark]); + + // 处理表单提交 + const handleSubmit = async (values: any) => { + try { + setLoading(true); + await saveTheme(values); + } catch (error) { + message.error('保存主题设置失败'); + } finally { + setLoading(false); + } + }; + + // 处理重置 + const handleReset = async () => { + try { + setLoading(true); + await resetTheme(); + } catch (error) { + message.error('重置主题设置失败'); + } finally { + setLoading(false); + } + }; + + // 处理表单值变化 - 实时预览 + const handleValuesChange = (changedValues: any) => { + updateTheme(changedValues); + }; + + return ( +
+ 主题设置 + + +
+ {/* 配色方案选择 */} + + + {(() => { + const themeMode = (form.getFieldValue('theme_mode') as ThemeMode) || ThemeMode.LIGHT; + const schemes = COLOR_SCHEMES[themeMode] || {}; + const currentPrimary = form.getFieldValue('primary_color'); + const currentBg = form.getFieldValue('background_color'); + const currentText = form.getFieldValue('text_color'); + + return Object.entries(schemes).map(([key, scheme]) => { + const isActive = + scheme.primary === currentPrimary && + scheme.background === currentBg && + scheme.text === currentText; + + return ( + + ); + }); + })()} + + + + {/* 主题模式 */} + + + 浅色模式 + 深色模式 + + + + {/* 主题色 */} + + { + form.setFieldValue('primary_color', color.toHexString()); + updateTheme({ primary_color: color.toHexString() }); + }} + allowClear + /> + + + {/* 背景色 */} + + { + form.setFieldValue('background_color', color.toHexString()); + updateTheme({ background_color: color.toHexString() }); + }} + allowClear + /> + + + {/* 文字颜色 */} + + { + form.setFieldValue('text_color', color.toHexString()); + updateTheme({ text_color: color.toHexString() }); + }} + allowClear + /> + + + {/* 圆角大小 */} + + + min={0} + max={20} + addonAfter="px" + /> + + + {/* 字体大小 */} + + + + + + + + + {/* 紧凑模式 */} + checked ? CompactMode.COMPACT : CompactMode.NORMAL} + getValueProps={(value: CompactMode) => ({ + checked: value === CompactMode.COMPACT + })} + > + + + + {/* 操作按钮 */} + + + + + + + + +
+
+
+
+ ); +}; diff --git a/client/admin/test/theme_setting_page.test.tsx b/client/admin/test/pages_theme_setting.test.tsx similarity index 98% rename from client/admin/test/theme_setting_page.test.tsx rename to client/admin/test/pages_theme_setting.test.tsx index 1b70646..adc7f6c 100644 --- a/client/admin/test/theme_setting_page.test.tsx +++ b/client/admin/test/pages_theme_setting.test.tsx @@ -1,7 +1,7 @@ import { JSDOM } from 'jsdom' import React from 'react' import {render, fireEvent, within, screen} from '@testing-library/react' -import { ThemeSettingsPage } from "../pages_settings.tsx" +import { ThemeSettingsPage } from "../pages_theme_settings.tsx" import { ThemeProvider } from "../hooks_sys.tsx" import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { diff --git a/client/admin/web_app.tsx b/client/admin/web_app.tsx index 3911943..3e3557d 100644 --- a/client/admin/web_app.tsx +++ b/client/admin/web_app.tsx @@ -74,22 +74,13 @@ import { UsersPage, FileLibraryPage } from './pages_sys.tsx'; -import { KnowInfoPage } from './pages_know.tsx'; +import { KnowInfoPage } from './pages_know_info.tsx'; import { MessagesPage } from './pages_messages.tsx'; -import { - SettingsPage, - ThemeSettingsPage, - } from './pages_settings.tsx'; - import { - ChartDashboardPage, - } from './pages_chart.tsx'; - import { - LoginMapPage - } from './pages_map.tsx'; - -import { - LoginPage, -} from './pages_login_reg.tsx'; +import {SettingsPage } from './pages_settings.tsx'; +import {ThemeSettingsPage} from './pages_theme_settings.tsx' +import { ChartDashboardPage } from './pages_chart.tsx'; +import { LoginMapPage } from './pages_map.tsx'; +import { LoginPage } from './pages_login_reg.tsx'; // 配置 dayjs 插件