From 602317ea44956bd318b3362750cc946decfe9a6a Mon Sep 17 00:00:00 2001 From: zyh Date: Fri, 11 Apr 2025 06:59:42 +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=9B=B8=E5=85=B3=E6=8E=A5=E5=8F=A3=E5=AE=9A=E4=B9=89=EF=BC=8C?= =?UTF-8?q?=E4=BC=98=E5=8C=96=E7=9F=A5=E8=AF=86=E5=BA=93API=E9=80=BB?= =?UTF-8?q?=E8=BE=91=EF=BC=8C=E6=8F=90=E4=BE=9B=E8=8E=B7=E5=8F=96=E3=80=81?= =?UTF-8?q?=E5=88=9B=E5=BB=BA=E3=80=81=E6=9B=B4=E6=96=B0=E5=92=8C=E5=88=A0?= =?UTF-8?q?=E9=99=A4=E7=9F=A5=E8=AF=86=E7=9A=84=E5=8A=9F=E8=83=BD=E3=80=82?= =?UTF-8?q?=E5=90=8C=E6=97=B6=EF=BC=8C=E6=9B=B4=E6=96=B0=E9=A1=B5=E9=9D=A2?= =?UTF-8?q?=E7=BB=84=E4=BB=B6=E4=BB=A5=E6=94=AF=E6=8C=81=E7=9F=A5=E8=AF=86?= =?UTF-8?q?=E5=BA=93=E7=AE=A1=E7=90=86=EF=BC=8C=E6=8F=90=E5=8D=87=E4=BB=A3?= =?UTF-8?q?=E7=A0=81=E5=8F=AF=E7=BB=B4=E6=8A=A4=E6=80=A7=E5=92=8C=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E4=BD=93=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 | 91 ++- client/admin/pages_know.tsx | 440 ++++++++++++ client/admin/pages_sys.tsx | 390 ----------- client/admin/test/theme_setting_page.test.tsx | 5 +- client/admin/web_app.tsx | 2 +- deno.json | 3 +- deno.lock | 629 ++++++++++++++++++ 7 files changed, 1164 insertions(+), 396 deletions(-) create mode 100644 client/admin/pages_know.tsx diff --git a/client/admin/api.ts b/client/admin/api.ts index dd83fbf..43931fb 100644 --- a/client/admin/api.ts +++ b/client/admin/api.ts @@ -6,7 +6,7 @@ import type { User, FileLibrary, FileCategory, ThemeSettings, SystemSetting, SystemSettingGroupData, LoginLocation, LoginLocationDetail, - Message, UserMessage + Message, UserMessage, KnowInfo } from '../share/types.ts'; @@ -605,6 +605,37 @@ export interface LoginLocationUpdateResponse { data: LoginLocationDetail; } +// 知识库相关接口类型定义 +interface KnowInfoListResponse { + data: KnowInfo[]; + pagination: { + total: number; + current: number; + pageSize: number; + totalPages: number; + }; +} + +interface KnowInfoResponse { + data: KnowInfo; + message?: string; +} + +interface KnowInfoCreateResponse { + message: string; + data: KnowInfo; +} + +interface KnowInfoUpdateResponse { + message: string; + data: KnowInfo; +} + +interface KnowInfoDeleteResponse { + message: string; + id: number; +} + // 地图相关API export const MapAPI = { @@ -648,6 +679,64 @@ export const MapAPI = { }; // 系统设置API +// 知识库API +export const KnowInfoAPI = { + // 获取知识库列表 + getKnowInfos: async (params?: { + page?: number; + pageSize?: number; + search?: string; + categoryId?: number; + }): Promise => { + try { + const response = await axios.get(`${API_BASE_URL}/know-infos`, { params }); + return response.data; + } catch (error) { + throw error; + } + }, + + // 获取单个知识详情 + getKnowInfo: async (id: number): Promise => { + try { + const response = await axios.get(`${API_BASE_URL}/know-infos/${id}`); + return response.data; + } catch (error) { + throw error; + } + }, + + // 创建知识 + createKnowInfo: async (data: Partial): Promise => { + try { + const response = await axios.post(`${API_BASE_URL}/know-infos`, data); + return response.data; + } catch (error) { + throw error; + } + }, + + // 更新知识 + updateKnowInfo: async (id: number, data: Partial): Promise => { + try { + const response = await axios.put(`${API_BASE_URL}/know-infos/${id}`, data); + return response.data; + } catch (error) { + throw error; + } + }, + + // 删除知识 + deleteKnowInfo: async (id: number): Promise => { + try { + const response = await axios.delete(`${API_BASE_URL}/know-infos/${id}`); + return response.data; + } catch (error) { + throw error; + } + } +}; + export const SystemAPI = { // 获取所有系统设置 getSettings: async (): Promise => { diff --git a/client/admin/pages_know.tsx b/client/admin/pages_know.tsx new file mode 100644 index 0000000..d4957bc --- /dev/null +++ b/client/admin/pages_know.tsx @@ -0,0 +1,440 @@ +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 +} from 'antd'; +import { + UploadOutlined, + FileImageOutlined, + FileExcelOutlined, + FileWordOutlined, + FilePdfOutlined, + FileOutlined, +} from '@ant-design/icons'; +import { + useQuery, +} 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 +} from '../share/types.ts'; + +import { + AuditStatus,AuditStatusNameMap, + OssType, +} from '../share/types.ts'; + +import { getEnumOptions } from './utils.ts'; + +import { + FileAPI, + UserAPI, +} from './api.ts'; + + +// 配置 dayjs 插件 +dayjs.extend(weekday); +dayjs.extend(localeData); + +// 设置 dayjs 语言 +dayjs.locale('zh-cn'); + +const { Title } = Typography; + + +// 知识库管理页面组件 +export const KnowInfoPage = () => { + const [modalVisible, setModalVisible] = useState(false); + const [formMode, setFormMode] = useState<'create' | 'edit'>('create'); + const [editingId, setEditingId] = useState(null); + const [form] = Form.useForm(); + const [isLoading, setIsLoading] = useState(false); + const [searchParams, setSearchParams] = useState({ + title: '', + category: '', + page: 1, + limit: 10, + }); + + // 使用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 articles = articlesData?.data || []; + const pagination = articlesData?.pagination || { current: 1, pageSize: 10, total: 0 }; + + // 获取单个知识库文章 + 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(); + } catch (error) { + message.error('获取知识库文章详情失败'); + return null; + } + }; + + // 处理表单提交 + const handleSubmit = async (values: Partial) => { + 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' ? '创建知识库文章失败' : '更新知识库文章失败'); + } + + message.success(formMode === 'create' ? '创建知识库文章成功' : '更新知识库文章成功'); + setModalVisible(false); + form.resetFields(); + refetch(); + } catch (error) { + message.error((error as Error).message); + } finally { + setIsLoading(false); + } + }; + + // 处理编辑 + const handleEdit = async (id: number) => { + const article = await fetchArticle(id); + + if (article) { + setFormMode('edit'); + setEditingId(id); + form.setFieldsValue(article); + setModalVisible(true); + } + }; + + // 处理删除 + 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('删除知识库文章失败'); + } + + message.success('删除知识库文章成功'); + refetch(); + } catch (error) { + message.error((error as Error).message); + } + }; + + // 处理搜索 + const handleSearch = (values: any) => { + setSearchParams(prev => ({ + ...prev, + title: values.title || '', + category: values.category || '', + page: 1, + })); + }; + + // 处理分页 + const handlePageChange = (page: number, pageSize?: number) => { + setSearchParams(prev => ({ + ...prev, + page, + limit: pageSize || prev.limit, + })); + }; + + // 处理添加 + const handleAdd = () => { + setFormMode('create'); + setEditingId(null); + form.resetFields(); + setModalVisible(true); + }; + + // 审核状态映射 + const auditStatusOptions = getEnumOptions(AuditStatus, AuditStatusNameMap); + + // 表格列定义 + const columns = [ + { + title: 'ID', + dataIndex: 'id', + key: 'id', + width: 80, + }, + { + title: '标题', + dataIndex: 'title', + key: 'title', + }, + { + title: '分类', + dataIndex: 'category', + key: 'category', + }, + { + title: '标签', + dataIndex: 'tags', + key: 'tags', + render: (tags: string) => tags ? tags.split(',').map(tag => ( + {tag} + )) : null, + }, + { + title: '作者', + dataIndex: 'author', + key: 'author', + }, + { + title: '审核状态', + dataIndex: 'audit_status', + key: 'audit_status', + render: (status: AuditStatus) => { + let color = ''; + let text = ''; + + switch(status) { + case AuditStatus.PENDING: + color = 'orange'; + text = '待审核'; + break; + case AuditStatus.APPROVED: + color = 'green'; + text = '已通过'; + break; + case AuditStatus.REJECTED: + color = 'red'; + text = '已拒绝'; + break; + default: + color = 'default'; + text = '未知'; + } + + return {text}; + }, + }, + { + title: '创建时间', + dataIndex: 'created_at', + key: 'created_at', + render: (date: string) => new Date(date).toLocaleString(), + }, + { + title: '操作', + key: 'action', + render: (_: any, record: KnowInfo) => ( + + + handleDelete(record.id)} + okText="确定" + cancelText="取消" + > + + + + ), + }, + ]; + + return ( +
+ +
+ + + + + + + + + + + + + + + +
+ + + + + form.submit()} + onCancel={() => setModalVisible(false)} + width={800} + > +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - -
- - - form.submit()} - onCancel={() => setModalVisible(false)} - width={800} - > -
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -