新增知识库相关接口定义,优化知识库API逻辑,提供获取、创建、更新和删除知识的功能。同时,更新页面组件以支持知识库管理,提升代码可维护性和用户体验。
This commit is contained in:
@@ -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<KnowInfoListResponse> => {
|
||||
try {
|
||||
const response = await axios.get(`${API_BASE_URL}/know-infos`, { params });
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
// 获取单个知识详情
|
||||
getKnowInfo: async (id: number): Promise<KnowInfoResponse> => {
|
||||
try {
|
||||
const response = await axios.get(`${API_BASE_URL}/know-infos/${id}`);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
// 创建知识
|
||||
createKnowInfo: async (data: Partial<KnowInfo>): Promise<KnowInfoCreateResponse> => {
|
||||
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<KnowInfo>): Promise<KnowInfoUpdateResponse> => {
|
||||
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<KnowInfoDeleteResponse> => {
|
||||
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<SystemSettingGroupData[]> => {
|
||||
|
||||
440
client/admin/pages_know.tsx
Normal file
440
client/admin/pages_know.tsx
Normal file
@@ -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<number | null>(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<KnowInfo>) => {
|
||||
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 key={tag}>{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 <Tag color={color}>{text}</Tag>;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'created_at',
|
||||
key: 'created_at',
|
||||
render: (date: string) => new Date(date).toLocaleString(),
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
render: (_: any, record: KnowInfo) => (
|
||||
<Space size="middle">
|
||||
<Button type="link" onClick={() => handleEdit(record.id)}>编辑</Button>
|
||||
<Popconfirm
|
||||
title="确定要删除这篇文章吗?"
|
||||
onConfirm={() => handleDelete(record.id)}
|
||||
okText="确定"
|
||||
cancelText="取消"
|
||||
>
|
||||
<Button type="link" danger>删除</Button>
|
||||
</Popconfirm>
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Card title="知识库管理" className="mb-4">
|
||||
<Form
|
||||
layout="inline"
|
||||
onFinish={handleSearch}
|
||||
style={{ marginBottom: '16px' }}
|
||||
>
|
||||
<Form.Item name="title" label="标题">
|
||||
<Input placeholder="请输入文章标题" />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item name="category" label="分类">
|
||||
<Input placeholder="请输入文章分类" />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item>
|
||||
<Space>
|
||||
<Button type="primary" htmlType="submit">
|
||||
搜索
|
||||
</Button>
|
||||
<Button onClick={() => {
|
||||
setSearchParams({
|
||||
title: '',
|
||||
category: '',
|
||||
page: 1,
|
||||
limit: 10,
|
||||
});
|
||||
}}>
|
||||
重置
|
||||
</Button>
|
||||
<Button type="primary" onClick={handleAdd}>
|
||||
添加文章
|
||||
</Button>
|
||||
</Space>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
|
||||
<Table
|
||||
columns={columns}
|
||||
dataSource={articles}
|
||||
rowKey="id"
|
||||
loading={isListLoading}
|
||||
pagination={{
|
||||
current: pagination.current,
|
||||
pageSize: pagination.pageSize,
|
||||
total: pagination.total,
|
||||
onChange: handlePageChange,
|
||||
showSizeChanger: true,
|
||||
}}
|
||||
/>
|
||||
</Card>
|
||||
|
||||
<Modal
|
||||
title={formMode === 'create' ? '添加知识库文章' : '编辑知识库文章'}
|
||||
open={modalVisible}
|
||||
onOk={() => form.submit()}
|
||||
onCancel={() => setModalVisible(false)}
|
||||
width={800}
|
||||
>
|
||||
<Form
|
||||
form={form}
|
||||
layout="vertical"
|
||||
onFinish={handleSubmit}
|
||||
initialValues={{
|
||||
audit_status: AuditStatus.PENDING,
|
||||
}}
|
||||
>
|
||||
<Row gutter={16}>
|
||||
<Col span={12}>
|
||||
<Form.Item
|
||||
name="title"
|
||||
label="文章标题"
|
||||
rules={[{ required: true, message: '请输入文章标题' }]}
|
||||
>
|
||||
<Input placeholder="请输入文章标题" />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Form.Item
|
||||
name="category"
|
||||
label="文章分类"
|
||||
>
|
||||
<Input placeholder="请输入文章分类" />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<Form.Item
|
||||
name="tags"
|
||||
label="文章标签"
|
||||
help="多个标签请用英文逗号分隔,如: 服务器,网络,故障"
|
||||
>
|
||||
<Input placeholder="请输入文章标签,多个标签请用英文逗号分隔" />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="content"
|
||||
label="文章内容"
|
||||
rules={[{ required: true, message: '请输入文章内容' }]}
|
||||
>
|
||||
<Input.TextArea rows={15} placeholder="请输入文章内容,支持Markdown格式" />
|
||||
</Form.Item>
|
||||
|
||||
<Row gutter={16}>
|
||||
<Col span={12}>
|
||||
<Form.Item
|
||||
name="author"
|
||||
label="文章作者"
|
||||
>
|
||||
<Input placeholder="请输入文章作者" />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Form.Item
|
||||
name="cover_url"
|
||||
label="封面图片URL"
|
||||
>
|
||||
<Input placeholder="请输入封面图片URL" />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<Form.Item
|
||||
name="audit_status"
|
||||
label="审核状态"
|
||||
>
|
||||
<Select options={auditStatusOptions} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item>
|
||||
<Space>
|
||||
<Button type="primary" htmlType="submit" loading={isLoading}>
|
||||
{formMode === 'create' ? '创建' : '保存'}
|
||||
</Button>
|
||||
<Button onClick={() => setModalVisible(false)}>取消</Button>
|
||||
</Space>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -349,396 +349,6 @@ export const UsersPage = () => {
|
||||
);
|
||||
};
|
||||
|
||||
// 知识库管理页面组件
|
||||
export const KnowInfoPage = () => {
|
||||
const [modalVisible, setModalVisible] = useState(false);
|
||||
const [formMode, setFormMode] = useState<'create' | 'edit'>('create');
|
||||
const [editingId, setEditingId] = useState<number | null>(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<KnowInfo>) => {
|
||||
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 key={tag}>{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 <Tag color={color}>{text}</Tag>;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'created_at',
|
||||
key: 'created_at',
|
||||
render: (date: string) => new Date(date).toLocaleString(),
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
render: (_: any, record: KnowInfo) => (
|
||||
<Space size="middle">
|
||||
<Button type="link" onClick={() => handleEdit(record.id)}>编辑</Button>
|
||||
<Popconfirm
|
||||
title="确定要删除这篇文章吗?"
|
||||
onConfirm={() => handleDelete(record.id)}
|
||||
okText="确定"
|
||||
cancelText="取消"
|
||||
>
|
||||
<Button type="link" danger>删除</Button>
|
||||
</Popconfirm>
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Card title="知识库管理" className="mb-4">
|
||||
<Form
|
||||
layout="inline"
|
||||
onFinish={handleSearch}
|
||||
style={{ marginBottom: '16px' }}
|
||||
>
|
||||
<Form.Item name="title" label="标题">
|
||||
<Input placeholder="请输入文章标题" />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item name="category" label="分类">
|
||||
<Input placeholder="请输入文章分类" />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item>
|
||||
<Space>
|
||||
<Button type="primary" htmlType="submit">
|
||||
搜索
|
||||
</Button>
|
||||
<Button onClick={() => {
|
||||
setSearchParams({
|
||||
title: '',
|
||||
category: '',
|
||||
page: 1,
|
||||
limit: 10,
|
||||
});
|
||||
}}>
|
||||
重置
|
||||
</Button>
|
||||
<Button type="primary" onClick={handleAdd}>
|
||||
添加文章
|
||||
</Button>
|
||||
</Space>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
|
||||
<Table
|
||||
columns={columns}
|
||||
dataSource={articles}
|
||||
rowKey="id"
|
||||
loading={isListLoading}
|
||||
pagination={{
|
||||
current: pagination.current,
|
||||
pageSize: pagination.pageSize,
|
||||
total: pagination.total,
|
||||
onChange: handlePageChange,
|
||||
showSizeChanger: true,
|
||||
}}
|
||||
/>
|
||||
</Card>
|
||||
|
||||
<Modal
|
||||
title={formMode === 'create' ? '添加知识库文章' : '编辑知识库文章'}
|
||||
open={modalVisible}
|
||||
onOk={() => form.submit()}
|
||||
onCancel={() => setModalVisible(false)}
|
||||
width={800}
|
||||
>
|
||||
<Form
|
||||
form={form}
|
||||
layout="vertical"
|
||||
onFinish={handleSubmit}
|
||||
initialValues={{
|
||||
audit_status: AuditStatus.PENDING,
|
||||
}}
|
||||
>
|
||||
<Row gutter={16}>
|
||||
<Col span={12}>
|
||||
<Form.Item
|
||||
name="title"
|
||||
label="文章标题"
|
||||
rules={[{ required: true, message: '请输入文章标题' }]}
|
||||
>
|
||||
<Input placeholder="请输入文章标题" />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Form.Item
|
||||
name="category"
|
||||
label="文章分类"
|
||||
>
|
||||
<Input placeholder="请输入文章分类" />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<Form.Item
|
||||
name="tags"
|
||||
label="文章标签"
|
||||
help="多个标签请用英文逗号分隔,如: 服务器,网络,故障"
|
||||
>
|
||||
<Input placeholder="请输入文章标签,多个标签请用英文逗号分隔" />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="content"
|
||||
label="文章内容"
|
||||
rules={[{ required: true, message: '请输入文章内容' }]}
|
||||
>
|
||||
<Input.TextArea rows={15} placeholder="请输入文章内容,支持Markdown格式" />
|
||||
</Form.Item>
|
||||
|
||||
<Row gutter={16}>
|
||||
<Col span={12}>
|
||||
<Form.Item
|
||||
name="author"
|
||||
label="文章作者"
|
||||
>
|
||||
<Input placeholder="请输入文章作者" />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Form.Item
|
||||
name="cover_url"
|
||||
label="封面图片URL"
|
||||
>
|
||||
<Input placeholder="请输入封面图片URL" />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<Form.Item
|
||||
name="audit_status"
|
||||
label="审核状态"
|
||||
>
|
||||
<Select options={auditStatusOptions} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item>
|
||||
<Space>
|
||||
<Button type="primary" htmlType="submit" loading={isLoading}>
|
||||
{formMode === 'create' ? '创建' : '保存'}
|
||||
</Button>
|
||||
<Button onClick={() => setModalVisible(false)}>取消</Button>
|
||||
</Space>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// 文件库管理页面
|
||||
export const FileLibraryPage = () => {
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { JSDOM } from 'npm:jsdom'
|
||||
import { JSDOM } from 'jsdom'
|
||||
import React from 'react'
|
||||
import 'npm:jsdom-global'
|
||||
import {render, fireEvent, within, screen} from '@testing-library/react'
|
||||
import { ThemeSettingsPage } from "../pages_settings.tsx"
|
||||
import { ThemeProvider } from "../hooks_sys.tsx"
|
||||
@@ -79,7 +78,7 @@ Deno.test('主题设置页面测试', async (t) => {
|
||||
</QueryClientProvider>
|
||||
)
|
||||
|
||||
debug(await findByRole('radio', { name: /浅色模式/i }))
|
||||
// debug(await findByRole('radio', { name: /浅色模式/i }))
|
||||
|
||||
// 测试1: 渲染基本元素
|
||||
await t.step('应渲染主题设置标题', async () => {
|
||||
|
||||
@@ -72,9 +72,9 @@ import {
|
||||
import {
|
||||
DashboardPage,
|
||||
UsersPage,
|
||||
KnowInfoPage,
|
||||
FileLibraryPage
|
||||
} from './pages_sys.tsx';
|
||||
import { KnowInfoPage } from './pages_know.tsx';
|
||||
import { MessagesPage } from './pages_messages.tsx';
|
||||
import {
|
||||
SettingsPage,
|
||||
|
||||
@@ -29,7 +29,8 @@
|
||||
"react-hook-form": "https://esm.d8d.fun/react-hook-form@7.55.0?dev&deps=react@19.0.0,react-dom@19.0.0",
|
||||
"@heroicons/react/24/outline": "https://esm.d8d.fun/@heroicons/react@2.1.1/24/outline?dev&deps=react@19.0.0,react-dom@19.0.0",
|
||||
"@heroicons/react/24/solid": "https://esm.d8d.fun/@heroicons/react@2.1.1/24/solid?dev&deps=react@19.0.0,react-dom@19.0.0",
|
||||
"@testing-library/react": "https://esm.d8d.fun/@testing-library/react@16.3.0?dev&deps=react@19.0.0,react-dom@19.0.0"
|
||||
"@testing-library/react": "https://esm.d8d.fun/@testing-library/react@16.3.0?dev&deps=react@19.0.0,react-dom@19.0.0",
|
||||
"jsdom":"npm:jsdom@26.0.0"
|
||||
},
|
||||
"compilerOptions": {
|
||||
"lib": ["dom", "dom.iterable", "esnext", "deno.ns"]
|
||||
|
||||
Reference in New Issue
Block a user