feat(web): 知识库页面(蚕病百科 + AI 解读)

This commit is contained in:
weijuesen
2026-08-12 10:51:22 +08:00
parent 3afc34d0de
commit b8af17d5f5
4 changed files with 381 additions and 0 deletions
+330
View File
@@ -0,0 +1,330 @@
import { useRef, useState } from 'react';
import { Button, Form, Input, InputNumber, Modal, Popconfirm, Select, Switch, Tabs, Tag, message } from 'antd';
import { PlusOutlined } from '@ant-design/icons';
import { ProTable, type ActionType, type ProColumns } from '@ant-design/pro-components';
import {
listDiseases,
createDisease,
updateDisease,
deleteDisease,
listArticles,
createArticle,
updateArticle,
deleteArticle,
type Disease,
type KnowledgeArticle,
} from '../dal/knowledge';
import { authService } from '../services/auth';
const CATEGORY_LABELS: Record<string, string> = {
viral: '病毒性',
fungal: '真菌性',
bacterial: '细菌性',
protozoan: '原虫性',
other: '其他',
};
const KIND_LABELS: Record<string, string> = {
ai_guide: 'AI 结果解读',
lamp_guide: 'LAMP 教程',
sers_guide: 'SERS 教程',
seasonal_tip: '季节性提醒',
};
const canWrite = () => authService.hasPermission('knowledge:write');
function DiseaseTab() {
const actionRef = useRef<ActionType>();
const [modalOpen, setModalOpen] = useState(false);
const [form] = Form.useForm<Partial<Disease>>();
const [editing, setEditing] = useState<Disease | null>(null);
const columns: ProColumns<Disease>[] = [
{ title: '序号', valueType: 'indexBorder', search: false, width: 60 },
{ title: '名称', dataIndex: 'name' },
{
title: '类别',
dataIndex: 'category',
valueType: 'select',
valueEnum: Object.fromEntries(
Object.entries(CATEGORY_LABELS).map(([k, v]) => [k, { text: v }]),
),
render: (_, r) => <Tag>{CATEGORY_LABELS[r.category] || r.category}</Tag>,
},
{ title: '高发阶段', dataIndex: 'highRiskStage', search: false, ellipsis: true, render: (_, r) => r.highRiskStage || '-' },
{ title: '排序', dataIndex: 'sortOrder', search: false, width: 70 },
{
title: '状态',
dataIndex: 'enabled',
search: false,
width: 80,
render: (_, r) => (r.enabled === false ? <Tag color="default"></Tag> : <Tag color="green"></Tag>),
},
{
title: '操作',
valueType: 'option',
render: (_, r) => [
<a
key="edit"
onClick={() => {
setEditing(r);
form.setFieldsValue(r);
setModalOpen(true);
}}
>
</a>,
...(canWrite()
? [
<Popconfirm
key="del"
title="确认删除该病种?"
onConfirm={async () => {
await deleteDisease(r.id);
message.success('已删除');
actionRef.current?.reload();
}}
>
<a style={{ color: '#ff4d4f' }}></a>
</Popconfirm>,
]
: []),
],
},
];
const openCreate = () => {
setEditing(null);
form.resetFields();
form.setFieldsValue({ category: 'viral', enabled: true, sortOrder: 0 });
setModalOpen(true);
};
return (
<>
<ProTable<Disease>
actionRef={actionRef}
rowKey="id"
columns={columns}
search={{ labelWidth: 'auto' }}
request={async (params) => {
const res = await listDiseases({
category: params.category,
keyword: params.name,
});
return { data: res, total: res.length, success: true };
}}
toolBarRender={() =>
canWrite()
? [
<Button key="new" type="primary" icon={<PlusOutlined />} onClick={openCreate}>
</Button>,
]
: []
}
/>
<Modal
title={editing ? '编辑病种' : '新建病种'}
open={modalOpen}
width={720}
onCancel={() => setModalOpen(false)}
onOk={async () => {
const v = await form.validateFields();
if (editing) await updateDisease(editing.id, v);
else await createDisease(v);
message.success('保存成功');
setModalOpen(false);
actionRef.current?.reload();
}}
>
<Form form={form} layout="vertical">
<Form.Item label="名称" name="name" rules={[{ required: true, message: '请输入名称' }]}>
<Input placeholder="如:核型多角体病" />
</Form.Item>
<Form.Item label="类别" name="category" rules={[{ required: true, message: '请选择类别' }]}>
<Select
options={Object.entries(CATEGORY_LABELS).map(([value, label]) => ({ value, label }))}
/>
</Form.Item>
<Form.Item label="病原" name="pathogen">
<Input.TextArea rows={2} />
</Form.Item>
<Form.Item label="传播途径" name="transmission">
<Input.TextArea rows={2} />
</Form.Item>
<Form.Item label="传染媒介" name="medium">
<Input.TextArea rows={2} />
</Form.Item>
<Form.Item label="潜伏期" name="incubation">
<Input />
</Form.Item>
<Form.Item label="主要症状" name="symptoms">
<Input.TextArea rows={3} />
</Form.Item>
<Form.Item label="高发阶段" name="highRiskStage">
<Input />
</Form.Item>
<Form.Item label="高发条件" name="highRiskCondition">
<Input />
</Form.Item>
<Form.Item label="致死时间" name="lethalTime">
<Input />
</Form.Item>
<Form.Item label="传播趋势" name="spreadTrend">
<Input.TextArea rows={2} />
</Form.Item>
<Form.Item label="连发性特征" name="recurrence">
<Input.TextArea rows={2} />
</Form.Item>
<Form.Item label="检测方法" name="detection">
<Input.TextArea rows={2} />
</Form.Item>
<Form.Item label="防治指南" name="prevention">
<Input.TextArea rows={4} />
</Form.Item>
<Form.Item label="排序" name="sortOrder">
<InputNumber min={0} style={{ width: '100%' }} />
</Form.Item>
<Form.Item label="启用" name="enabled" valuePropName="checked">
<Switch />
</Form.Item>
</Form>
</Modal>
</>
);
}
function ArticleTab() {
const actionRef = useRef<ActionType>();
const [modalOpen, setModalOpen] = useState(false);
const [form] = Form.useForm<Partial<KnowledgeArticle>>();
const [editing, setEditing] = useState<KnowledgeArticle | null>(null);
const columns: ProColumns<KnowledgeArticle>[] = [
{ title: '序号', valueType: 'indexBorder', search: false, width: 60 },
{ title: '标题', dataIndex: 'title' },
{
title: '类型',
dataIndex: 'kind',
valueType: 'select',
valueEnum: Object.fromEntries(
Object.entries(KIND_LABELS).map(([k, v]) => [k, { text: v }]),
),
render: (_, r) => <Tag>{KIND_LABELS[r.kind] || r.kind}</Tag>,
},
{ title: '摘要', dataIndex: 'summary', search: false, ellipsis: true, render: (_, r) => r.summary || '-' },
{ title: '排序', dataIndex: 'sortOrder', search: false, width: 70 },
{
title: '操作',
valueType: 'option',
render: (_, r) => [
<a
key="edit"
onClick={() => {
setEditing(r);
form.setFieldsValue(r);
setModalOpen(true);
}}
>
</a>,
...(canWrite()
? [
<Popconfirm
key="del"
title="确认删除该文章?"
onConfirm={async () => {
await deleteArticle(r.id);
message.success('已删除');
actionRef.current?.reload();
}}
>
<a style={{ color: '#ff4d4f' }}></a>
</Popconfirm>,
]
: []),
],
},
];
const openCreate = () => {
setEditing(null);
form.resetFields();
form.setFieldsValue({ kind: 'ai_guide', enabled: true, sortOrder: 0 });
setModalOpen(true);
};
return (
<>
<ProTable<KnowledgeArticle>
actionRef={actionRef}
rowKey="id"
columns={columns}
search={{ labelWidth: 'auto' }}
request={async (params) => {
const res = await listArticles({ kind: params.kind });
return { data: res, total: res.length, success: true };
}}
toolBarRender={() =>
canWrite()
? [
<Button key="new" type="primary" icon={<PlusOutlined />} onClick={openCreate}>
</Button>,
]
: []
}
/>
<Modal
title={editing ? '编辑文章' : '新建文章'}
open={modalOpen}
width={720}
onCancel={() => setModalOpen(false)}
onOk={async () => {
const v = await form.validateFields();
if (editing) await updateArticle(editing.id, v);
else await createArticle(v);
message.success('保存成功');
setModalOpen(false);
actionRef.current?.reload();
}}
>
<Form form={form} layout="vertical">
<Form.Item label="类型" name="kind" rules={[{ required: true, message: '请选择类型' }]}>
<Select
options={Object.entries(KIND_LABELS).map(([value, label]) => ({ value, label }))}
/>
</Form.Item>
<Form.Item label="标题" name="title" rules={[{ required: true, message: '请输入标题' }]}>
<Input />
</Form.Item>
<Form.Item label="摘要" name="summary">
<Input.TextArea rows={2} />
</Form.Item>
<Form.Item label="正文" name="content">
<Input.TextArea rows={8} />
</Form.Item>
<Form.Item label="排序" name="sortOrder">
<InputNumber min={0} style={{ width: '100%' }} />
</Form.Item>
<Form.Item label="启用" name="enabled" valuePropName="checked">
<Switch />
</Form.Item>
</Form>
</Modal>
</>
);
}
export default function KnowledgePage() {
return (
<Tabs
defaultActiveKey="diseases"
items={[
{ key: 'diseases', label: '蚕病百科', children: <DiseaseTab /> },
{ key: 'articles', label: 'AI 结果解读', children: <ArticleTab /> },
]}
/>
);
}