文件路径: src/client/admin/pages/SilverKnowledges.tsx
import React, { useState, useRef } from 'react';
import { Card, Button, Space, Tag, Modal, message, Switch, Popconfirm } from 'antd';
import { PlusOutlined, EditOutlined, DeleteOutlined, EyeOutlined, StarOutlined } from '@ant-design/icons';
import type { ProColumns, ActionType } from '@ant-design/pro-table';
import ProTable from '@ant-design/pro-table';
import { useNavigate } from 'react-router';
import { useAuth } from '../hooks/AuthProvider';
import type { InferResponseType } from 'hono/client';
import { silverUsersClient } from '@/client/api';
import dayjs from 'dayjs';
type SilverKnowledge = InferResponseType<typeof silverUsersClient.knowledges.$get, 200>['data'][0];
const SilverKnowledges: React.FC = () => {
const navigate = useNavigate();
const { user } = useAuth();
const actionRef = useRef<ActionType>();
const [loading, setLoading] = useState(false);
const handleAdd = () => {
navigate('/admin/silver-knowledges/new');
};
const handleEdit = (record: SilverKnowledge) => {
navigate(`/admin/silver-knowledges/${record.id}/edit`);
};
const handleDelete = async (id: number) => {
try {
await silverUsersClient.knowledges[':id'].$delete({
param: { id: id.toString() }
});
message.success('删除成功');
actionRef.current?.reload();
} catch (error) {
message.error('删除失败');
}
};
const handleToggleStatus = async (record: SilverKnowledge, newStatus: number) => {
try {
await silverUsersClient.knowledges[':id'].$put({
param: { id: record.id.toString() },
json: { status: newStatus }
});
message.success('状态更新成功');
actionRef.current?.reload();
} catch (error) {
message.error('状态更新失败');
}
};
const handleToggleFeatured = async (record: SilverKnowledge, isFeatured: boolean) => {
try {
await silverUsersClient.knowledges[':id'].$put({
param: { id: record.id.toString() },
json: { isFeatured: isFeatured ? 1 : 0 }
});
message.success(isFeatured ? '已设为推荐' : '已取消推荐');
actionRef.current?.reload();
} catch (error) {
message.error('推荐设置失败');
}
};
const columns: ProColumns<SilverKnowledge>[] = [
{
title: 'ID',
dataIndex: 'id',
width: 80,
hideInSearch: true,
},
{
title: '标题',
dataIndex: 'title',
ellipsis: true,
width: 200,
},
{
title: '分类',
dataIndex: 'category',
render: (_, record) => record.category?.name || '-',
width: 100,
},
{
title: '类型',
dataIndex: 'type',
valueEnum: {
1: { text: '文章', status: 'Processing' },
2: { text: '视频', status: 'Success' },
3: { text: '文档', status: 'Default' },
4: { text: '课程', status: 'Warning' },
5: { text: '经验分享', status: 'Success' },
6: { text: '案例分享', status: 'Processing' },
7: { text: '研究报告', status: 'Error' },
},
width: 100,
},
{
title: '状态',
dataIndex: 'status',
render: (_, record) => {
const statusMap = {
0: <Tag color="default">草稿</Tag>,
1: <Tag color="success">已发布</Tag>,
2: <Tag color="warning">已隐藏</Tag>,
3: <Tag color="error">已删除</Tag>,
4: <Tag color="processing">审核中</Tag>,
};
return statusMap[record.status] || '-';
},
valueEnum: {
0: { text: '草稿', status: 'Default' },
1: { text: '已发布', status: 'Success' },
2: { text: '已隐藏', status: 'Warning' },
3: { text: '已删除', status: 'Error' },
4: { text: '审核中', status: 'Processing' },
},
width: 100,
},
{
title: '作者',
dataIndex: ['user', 'nickname'],
width: 100,
hideInSearch: true,
},
{
title: '浏览',
dataIndex: 'viewCount',
width: 80,
hideInSearch: true,
},
{
title: '点赞',
dataIndex: 'likeCount',
width: 80,
hideInSearch: true,
},
{
title: '收藏',
dataIndex: 'favoriteCount',
width: 80,
hideInSearch: true,
},
{
title: '推荐',
dataIndex: 'isFeatured',
render: (_, record) => (
<Switch
checked={record.isFeatured === 1}
onChange={(checked) => handleToggleFeatured(record, checked)}
checkedChildren="是"
unCheckedChildren="否"
/>
),
width: 80,
hideInSearch: true,
},
{
title: '创建时间',
dataIndex: 'createdAt',
render: (_, record) => dayjs(record.createdAt).format('YYYY-MM-DD HH:mm'),
width: 150,
hideInSearch: true,
},
{
title: '操作',
valueType: 'option',
width: 200,
render: (_, record) => (
<Space>
<Button
type="link"
size="small"
icon={<EyeOutlined />}
onClick={() => window.open(`/knowledge/${record.id}`, '_blank')}
>
查看
</Button>
<Button
type="link"
size="small"
icon={<EditOutlined />}
onClick={() => handleEdit(record)}
>
编辑
</Button>
<Popconfirm
title="确定要删除吗?"
onConfirm={() => handleDelete(record.id)}
okText="确定"
cancelText="取消"
>
<Button danger type="link" size="small" icon={<DeleteOutlined />}>
删除
</Button>
</Popconfirm>
</Space>
),
},
];
return (
<Card>
<ProTable<SilverKnowledge>
headerTitle="银龄智库管理"
actionRef={actionRef}
rowKey="id"
search={{
labelWidth: 120,
}}
toolBarRender={() => [
<Button key="add" type="primary" onClick={handleAdd} icon={<PlusOutlined />}>
新建知识
</Button>,
]}
request={async (params) => {
const response = await silverUsersClient.knowledges.$get({
query: {
page: params.current || 1,
pageSize: params.pageSize || 10,
keyword: params.title,
...params,
}
});
const data = await response.json();
return {
data: data.data,
success: response.ok,
total: data.pagination.total,
};
}}
columns={columns}
pagination={{
showSizeChanger: true,
showQuickJumper: true,
}}
/>
</Card>
);
};
export default SilverKnowledges;
文件路径: src/client/admin/pages/SilverKnowledgeForm.tsx
import React, { useEffect, useState } from 'react';
import { Card, Form, Input, Select, Button, Space, Upload, message, Row, Col } from 'antd';
import { InboxOutlined, ArrowLeftOutlined } from '@ant-design/icons';
import { useNavigate, useParams } from 'react-router';
import ReactQuill from 'react-quill';
import 'react-quill/dist/quill.snow.css';
import { useAuth } from '../hooks/AuthProvider';
import { silverUsersClient } from '@/client/api';
import type { InferResponseType, InferRequestType } from 'hono/client';
import dayjs from 'dayjs';
const { Option } = Select;
const { TextArea } = Input;
const { Dragger } = Upload;
type SilverKnowledge = InferResponseType<typeof silverUsersClient.knowledges[':id'].$get, 200>;
type CreateKnowledge = InferRequestType<typeof silverUsersClient.knowledges.$post>['json'];
type UpdateKnowledge = InferRequestType<typeof silverUsersClient.knowledges[':id'].$put>['json'];
const knowledgeTypes = [
{ value: 1, label: '文章' },
{ value: 2, label: '视频' },
{ value: 3, label: '文档' },
{ value: 4, label: '课程' },
{ value: 5, label: '经验分享' },
{ value: 6, label: '案例分享' },
{ value: 7, label: '研究报告' },
];
const knowledgeStatus = [
{ value: 0, label: '草稿' },
{ value: 1, label: '已发布' },
{ value: 2, label: '已隐藏' },
{ value: 4, label: '审核中' },
];
const SilverKnowledgeForm: React.FC = () => {
const navigate = useNavigate();
const { id } = useParams();
const { user } = useAuth();
const [form] = Form.useForm();
const [loading, setLoading] = useState(false);
const [categories, setCategories] = useState<any[]>([]);
useEffect(() => {
fetchCategories();
if (id) {
fetchKnowledge();
}
}, [id]);
const fetchCategories = async () => {
try {
const response = await silverUsersClient['knowledge-categories'].$get();
const data = await response.json();
setCategories(data.data || []);
} catch (error) {
console.error('获取分类失败:', error);
}
};
const fetchKnowledge = async () => {
if (!id) return;
try {
const response = await silverUsersClient.knowledges[':id'].$get({
param: { id }
});
const data = await response.json();
form.setFieldsValue({
...data,
tags: data.tags ? JSON.parse(data.tags) : [],
attachments: data.attachments ? JSON.parse(data.attachments) : [],
});
} catch (error) {
message.error('获取知识详情失败');
}
};
const handleSubmit = async (values: any) => {
setLoading(true);
try {
const submitData = {
...values,
userId: user?.id,
tags: values.tags ? JSON.stringify(values.tags) : undefined,
attachments: values.attachments ? JSON.stringify(values.attachments) : undefined,
};
if (id) {
await silverUsersClient.knowledges[':id'].$put({
param: { id },
json: submitData
});
message.success('更新成功');
} else {
await silverUsersClient.knowledges.$post({
json: submitData
});
message.success('创建成功');
}
navigate('/admin/silver-knowledges');
} catch (error) {
message.error(id ? '更新失败' : '创建失败');
} finally {
setLoading(false);
}
};
const handleBack = () => {
navigate('/admin/silver-knowledges');
};
const uploadProps = {
name: 'file',
multiple: true,
action: '/api/v1/files/upload-policy',
onChange(info: any) {
const { status } = info.file;
if (status === 'done') {
message.success(`${info.file.name} 上传成功`);
} else if (status === 'error') {
message.error(`${info.file.name} 上传失败`);
}
},
};
return (
<Card
title={id ? '编辑知识' : '新建知识'}
extra={
<Button icon={<ArrowLeftOutlined />} onClick={handleBack}>
返回列表
</Button>
}
>
<Form
form={form}
layout="vertical"
onFinish={handleSubmit}
initialValues={{
type: 1,
status: 0,
isFeatured: 0,
sortOrder: 0,
}}
>
<Row gutter={24}>
<Col span={16}>
<Form.Item
name="title"
label="标题"
rules={[{ required: true, message: '请输入标题' }]}
>
<Input placeholder="请输入知识标题" maxLength={255} />
</Form.Item>
<Form.Item
name="content"
label="内容"
rules={[{ required: true, message: '请输入内容' }]}
>
<ReactQuill
theme="snow"
style={{ height: 300 }}
placeholder="请输入知识内容"
/>
</Form.Item>
<Form.Item
name="summary"
label="摘要"
>
<TextArea
rows={4}
placeholder="请输入知识摘要(选填)"
maxLength={500}
showCount
/>
</Form.Item>
</Col>
<Col span={8}>
<Form.Item
name="categoryId"
label="分类"
>
<Select placeholder="请选择分类" allowClear>
{categories.map(category => (
<Option key={category.id} value={category.id}>
{category.name}
</Option>
))}
</Select>
</Form.Item>
<Form.Item
name="type"
label="类型"
rules={[{ required: true, message: '请选择类型' }]}
>
<Select placeholder="请选择类型">
{knowledgeTypes.map(type => (
<Option key={type.value} value={type.value}>
{type.label}
</Option>
))}
</Select>
</Form.Item>
<Form.Item
name="status"
label="状态"
rules={[{ required: true, message: '请选择状态' }]}
>
<Select placeholder="请选择状态">
{knowledgeStatus.map(status => (
<Option key={status.value} value={status.value}>
{status.label}
</Option>
))}
</Select>
</Form.Item>
<Form.Item
name="tags"
label="标签"
>
<Select
mode="tags"
placeholder="请输入标签"
tokenSeparators={[',']}
/>
</Form.Item>
<Form.Item
name="keywords"
label="关键词"
>
<TextArea
rows={3}
placeholder="请输入搜索关键词,用逗号分隔"
/>
</Form.Item>
<Form.Item
name="author"
label="原作者"
>
<Input placeholder="请输入原作者" maxLength={100} />
</Form.Item>
<Form.Item
name="source"
label="知识来源"
>
<Input placeholder="请输入知识来源" maxLength={255} />
</Form.Item>
<Form.Item
name="coverImage"
label="封面图片"
>
<Input placeholder="请输入封面图片URL" />
</Form.Item>
<Form.Item
name="attachments"
label="附件"
>
<Dragger {...uploadProps}>
<p className="ant-upload-drag-icon">
<InboxOutlined />
</p>
<p className="ant-upload-text">点击或拖拽文件到此处上传</p>
<p className="ant-upload-hint">支持单个或批量上传</p>
</Dragger>
</Form.Item>
</Col>
</Row>
<Form.Item>
<Space>
<Button type="primary" htmlType="submit" loading={loading}>
{id ? '更新' : '创建'}
</Button>
<Button onClick={handleBack}>取消</Button>
</Space>
</Form.Item>
</Form>
</Card>
);
};
export default SilverKnowledgeForm;
添加导入:
import { BookOutlined } from '@ant-design/icons';
添加菜单项:
{
key: 'silver-knowledges',
label: '银龄智库发布',
icon: <BookOutlined />,
path: '/admin/silver-knowledges',
permission: 'silver-knowledge:manage'
}
添加导入:
import { SilverKnowledgesPage } from './pages/SilverKnowledges';
import { SilverKnowledgeFormPage } from './pages/SilverKnowledgeForm';
添加路由:
{
path: 'silver-knowledges',
element: <SilverKnowledgesPage />,
errorElement: <ErrorPage />
},
{
path: 'silver-knowledges/new',
element: <SilverKnowledgeFormPage />,
errorElement: <ErrorPage />
},
{
path: 'silver-knowledges/:id/edit',
element: <SilverKnowledgeFormPage />,
errorElement: <ErrorPage />
}
添加类型定义:
import type { SilverKnowledgeRoutes } from '@/server/api/silver-users/knowledges';
export const silverUsersClient = hc<SilverKnowledgeRoutes>('/api/v1', {
fetch: axiosFetch,
}).api.v1['silver-users'];
# 安装富文本编辑器
npm install react-quill @types/react-quill
# 安装日期处理
npm install dayjs
/admin/silver-knowledges/admin/silver-knowledges/new/admin/silver-knowledges/:id/edit需要用户具有 silver-knowledge:manage 权限才能访问