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} - > -
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -