本章学习目标理解知识库的产品价值与定位实现文档列表与分类检索集成 Markdown 编辑器理解文档与任务单的关联设计掌握知识沉淀工作流8.1 知识库的产品价值知识库是企业知识沉淀和共享的核心载体。在后台管理系统中它不只是一个文档存储库更与业务流程深度结合。8.1.1 核心价值价值点说明知识沉淀将处理过的问题和解决方案整理成文档避免重复踩坑效率提升处理任务时可快速检索相关知识缩短解决时间新人培训新员工通过知识库快速了解业务和常见问题质量保障标准化解决方案确保处理质量的一致性8.1.2 与任务单的联动任务单处理中 ──► 发现新知识 ──► 沉淀到知识库 Λ │ │ │ └────── 检索相关知识 ◄─────────────┘8.2 数据模型设计// src/types/knowledge.tsexportinterfaceKnowledgeDoc{id:string;title:string;content:string;// Markdown 内容summary:string;// 摘要categoryId:string;categoryName:string;tags:string[];author:string;authorId:string;status:draft|published|archived;viewCount:number;likeCount:number;relatedTicketIds:string[];createdAt:string;updatedAt:string;publishedAt?:string;}exportinterfaceKnowledgeCategory{id:string;name:string;parentId?:string;sort:number;docCount:number;children?:KnowledgeCategory[];}exportinterfaceKnowledgeFilters{keyword?:string;categoryId?:string;tag?:string;status?:string;}8.3 文档列表与分类检索知识中心采用经典的左侧分类树 右侧文档列表的布局。8.3.1 页面布局┌──────────┬──────────────────────────────────────┐ │ │ 搜索框 │ │ 分类树 ├──────────────────────────────────────┤ │ │ 文档列表 │ │ - 分类A │ 文档标题 / 摘要 / 分类 / 标签 / 浏览量│ │ - 子分类│ │ │ - 分类B │ 分页 │ └──────────┴──────────────────────────────────────┘8.3.2 列表页实现// src/pages/knowledge/list/index.tsx import { useState } from react; import { Layout, Input, List, Tag, Space, Button, Avatar, Typography, Pagination, Card } from antd; import { SearchOutlined, PlusOutlined, FileTextOutlined } from ant-design/icons; import { useNavigate } from react-router-dom; import dayjs from dayjs; import CategoryTree from ./components/CategoryTree; import { knowledgeApi } from services/knowledge; import { KnowledgeDoc, KnowledgeFilters } from types/knowledge; import { useTable } from hooks/useTable; const { Sider, Content } Layout; const { Title, Text } Typography; export default function KnowledgeList() { const navigate useNavigate(); const [searchText, setSearchText] useState(); const [selectedCategory, setSelectedCategory] useStatestring | undefined(); const { dataSource, total, loading, page, pageSize, handleSearch, handlePageChange } useTable KnowledgeDoc, KnowledgeFilters ({ fetchFn: (params) knowledgeApi.getList(params), defaultPageSize: 10, }); const handleSearchClick () { handleSearch({ keyword: searchText, categoryId: selectedCategory }); }; const handleCategorySelect (id: string | undefined) { setSelectedCategory(id); handleSearch({ keyword: searchText, categoryId: id }); }; return ( Layout style{{ minHeight: calc(100vh - 64px) }} Sider width{240} style{{ background: #fff, borderRight: 1px solid #f0f0f0 }} div style{{ padding: 16, borderBottom: 1px solid #f0f0f0 }} Space FileTextOutlined style{{ color: #1677ff }} / Text strong知识分类/Text /Space /div CategoryTree onSelect{handleCategorySelect} / /Sider Content style{{ padding: 24 }} Space directionvertical sizemiddle style{{ width: 100% }} Space.Compact style{{ width: 100%, maxWidth: 600 }} Input placeholder搜索文档标题或内容... prefix{SearchOutlined /} value{searchText} onChange{(e) setSearchText(e.target.value)} onPressEnter{handleSearchClick} allowClear / Button typeprimary onClick{handleSearchClick} 搜索 /Button /Space.Compact Card sizesmall div style{{ display: flex, justifyContent: space-between, marginBottom: 16 }} Text typesecondary共找到 {total} 篇文档/Text Button typeprimary icon{PlusOutlined /} onClick{() navigate(/knowledge/create)} 新建文档 /Button /div List itemLayoutvertical dataSource{dataSource} loading{loading} renderItem{(doc) ( List.Item key{doc.id} style{{ cursor: pointer }} onClick{() navigate(/knowledge/${doc.id})} List.Item.Meta title{Title level{5} style{{ margin: 0 }}{doc.title}/Title} description{ Space directionvertical size{8} style{{ width: 100% }} Text typesecondary style{{ display: -webkit-box, WebkitLineClamp: 2, WebkitBoxOrient: vertical, overflow: hidden, }} {doc.summary || doc.content.slice(0, 200)} /Text Space sizesmall wrap Tag colorblue{doc.categoryName}/Tag {doc.tags?.slice(0, 3).map((tag) ( Tag key{tag}{tag}/Tag ))} /Space Space sizelarge Avatar size{20}{doc.author?.[0]}/Avatar Text typesecondary style{{ fontSize: 12 }}{doc.author}/Text Text typesecondary style{{ fontSize: 12 }}浏览 {doc.viewCount}/Text Text typesecondary style{{ fontSize: 12 }} {dayjs(doc.updatedAt).format(YYYY-MM-DD)} /Text /Space /Space } / /List.Item )} / div style{{ textAlign: center, marginTop: 16 }} Pagination current{page} pageSize{pageSize} total{total} onChange{handlePageChange} / /div /Card /Space /Content /Layout ); }8.3.3 分类树组件// src/pages/knowledge/list/components/CategoryTree.tsx import { useEffect, useState } from react; import { Tree } from antd; import { knowledgeApi } from services/knowledge; import { KnowledgeCategory } from types/knowledge; interface Props { onSelect: (id: string | undefined) void; } export default function CategoryTree({ onSelect }: Props) { const [treeData, setTreeData] useStateany[]([]); const [loading, setLoading] useState(false); useEffect(() { const fetchCategories async () { setLoading(true); try { const list await knowledgeApi.getCategories(); const buildTree (items: KnowledgeCategory[], parentId?: string): any[] { return items .filter((i) i.parentId parentId) .sort((a, b) a.sort - b.sort) .map((item) ({ key: item.id, title: ${item.name} (${item.docCount}), children: buildTree(items, item.id), })); }; setTreeData(buildTree(list)); } finally { setLoading(false); } }; fetchCategories(); }, []); return ( Tree treeData{treeData} defaultExpandAll loading{loading} showLine onSelect{(keys) { onSelect(keys.length 0 ? String(keys[0]) : undefined); }} style{{ padding: 8px 12px }} / ); }8.4 Markdown 编辑器集成Markdown 是技术文档的事实标准。我们用mdxeditor/editor或更轻量的方案实现编辑功能。8.4.1 方案选型方案特点适用场景mdxeditor/editor功能最全所见即所得重编辑器react-md-editor轻量分栏预览中等复杂度textarea 预览最简单极简场景这里我们用「编辑区 预览区」双栏的方案简单且可控。8.4.2 文档编辑器页面// src/pages/knowledge/editor/index.tsx import { useState, useEffect } from react; import { useParams, useNavigate } from react-router-dom; import { Button, Input, Select, Space, Card, Row, Col, Tag, message } from antd; import { ArrowLeftOutlined, SaveOutlined, SendOutlined } from ant-design/icons; import ReactMarkdown from react-markdown; import { knowledgeApi } from services/knowledge; import { KnowledgeDoc } from types/knowledge; const { TextArea } Input; const { Option } Select; export default function KnowledgeEditor() { const { id } useParams(); const navigate useNavigate(); const isEdit !!id; const [title, setTitle] useState(); const [content, setContent] useState(); const [categoryId, setCategoryId] useState(); const [tags, setTags] useStatestring[]([]); const [saving, setSaving] useState(false); const [tab, setTab] useStateedit | preview | split(split); useEffect(() { if (id) { knowledgeApi.getDetail(id).then((doc: KnowledgeDoc) { setTitle(doc.title); setContent(doc.content); setCategoryId(doc.categoryId); setTags(doc.tags || []); }); } }, [id]); const handleSave async (status: draft | published draft) { if (!title.trim()) { message.warning(请输入文档标题); return; } if (!content.trim()) { message.warning(请输入文档内容); return; } setSaving(true); try { const data { title, content, categoryId, tags, status }; if (isEdit id) { await knowledgeApi.update(id, data); message.success(保存成功); } else { const result await knowledgeApi.create(data); message.success(创建成功); navigate(/knowledge/${result.id}); return; } } finally { setSaving(false); } }; return ( div style{{ padding: 24 }} Card sizesmall style{{ marginBottom: 16 }} Space style{{ width: 100%, justifyContent: space-between }} Space Button icon{ArrowLeftOutlined /} onClick{() navigate(-1)} 返回 /Button Button.Group Button type{tab edit ? primary : default} onClick{() setTab(edit)} 编辑 /Button Button type{tab split ? primary : default} onClick{() setTab(split)} 分栏 /Button Button type{tab preview ? primary : default} onClick{() setTab(preview)} 预览 /Button /Button.Group /Space Space Button icon{SaveOutlined /} onClick{() handleSave(draft)} loading{saving} 保存草稿 /Button Button typeprimary icon{SendOutlined /} onClick{() handleSave(published)} loading{saving} 发布 /Button /Space /Space /Card Card sizesmall style{{ marginBottom: 16 }} Space directionvertical sizemiddle style{{ width: 100% }} Input placeholder请输入文档标题 value{title} onChange{(e) setTitle(e.target.value)} style{{ fontSize: 20, fontWeight: 600, height: 48 }} maxLength{100} showCount / Space Select placeholder选择分类 style{{ width: 200 }} value{categoryId || undefined} onChange{setCategoryId} Option valuecat1常见问题/Option Option valuecat2操作指南/Option Option valuecat3技术文档/Option /Select Select modetags placeholder输入标签回车添加 style{{ minWidth: 300, flex: 1 }} value{tags} onChange{setTags} / /Space /Space /Card Row gutter{16} {(tab edit || tab split) ( Col span{tab split ? 12 : 24} TextArea value{content} onChange{(e) setContent(e.target.value)} placeholder使用 Markdown 语法编写文档内容... style{{ width: 100%, minHeight: 500, fontFamily: monospace, lineHeight: 1.6 }} / /Col )} {(tab preview || tab split) ( Col span{tab split ? 12 : 24} Card styles{{ body: { padding: 24, minHeight: 500 } }} div classNamemarkdown-body ReactMarkdown{content || *暂无内容开始编辑吧...*}/ReactMarkdown /div /Card /Col )} /Row /div ); }8.5 文档与任务单的关联知识库不是孤立的它需要和业务系统联动。最核心的联动就是任务单与知识文档的双向关联。8.5.1 关联场景从任务单创建知识处理完一个任务后一键将解决方案沉淀为知识文档从知识关联任务一篇文档可以关联多个相关的任务单作为案例参考智能推荐在任务单详情页自动推荐相关的知识文档8.5.2 在任务详情页展示相关知识// src/pages/ticket/detail/components/RelatedKnowledge.tsx import { useState, useEffect } from react; import { Card, List, Tag, Space, Typography, Empty } from antd; import { BookOutlined, FileTextOutlined } from ant-design/icons; import { useNavigate } from react-router-dom; import { knowledgeApi } from services/knowledge; import { KnowledgeDoc } from types/knowledge; const { Text } Typography; interface Props { ticketId: string; } export default function RelatedKnowledge({ ticketId }: Props) { const navigate useNavigate(); const [docs, setDocs] useStateKnowledgeDoc[]([]); const [loading, setLoading] useState(false); useEffect(() { const fetchRelated async () { setLoading(true); try { const result await knowledgeApi.getRelatedByTicket(ticketId); setDocs(result.list || []); } finally { setLoading(false); } }; fetchRelated(); }, [ticketId]); return ( Card sizesmall title{ Space BookOutlined style{{ color: #1677ff }} / span相关知识/span /Space } {docs.length 0 !loading ? ( Empty description暂无相关知识 image{Empty.PRESENTED_IMAGE_SIMPLE} / ) : ( List sizesmall dataSource{docs} loading{loading} renderItem{(doc) ( List.Item style{{ cursor: pointer }} onClick{() navigate(/knowledge/${doc.id})} List.Item.Meta avatar{FileTextOutlined style{{ color: #faad14 }} /} title{doc.title} description{ Space sizesmall Tag{doc.categoryName}/Tag Text typesecondary style{{ fontSize: 12 }} 浏览 {doc.viewCount} /Text /Space } / /List.Item )} / )} /Card ); }8.6 知识沉淀工作流知识沉淀不是自发的需要一套工作流来驱动。8.6.1 沉淀流程任务完成 │ ▼ AI 自动提取要点 ──► 生成初稿 │ ▼ 处理人审阅 ──► 修改补充 │ ▼ 提交审核 ──► 审核人审核 │ ▼ 发布上线 ──► 进入知识库8.6.2 一键沉淀功能在任务详情页提供「沉淀为知识」按钮自动把任务信息填充到新建文档表单import { Button, Modal, message } from antd; import { SaveOutlined } from ant-design/icons; import { useNavigate } from react-router-dom; import { knowledgeApi } from services/knowledge; interface Props { ticketId: string; title: string; description: string; categoryId: string; } export default function沉淀Knowledge({ ticketId, title, description, categoryId }: Props) { const navigate useNavigate(); const handle沉淀 () { Modal.confirm({ title: 沉淀为知识文档, content: 将根据当前任务单内容创建一篇知识文档草稿是否继续, okText: 创建草稿, onOk: async () { const result await knowledgeApi.createFromTicket(ticketId, { title: 【知识沉淀】${title}, categoryId, }); message.success(已创建知识草稿); navigate(/knowledge/edit/${result.id}); }, }); }; return ( Button icon{SaveOutlined /} onClick{handle沉淀} 沉淀为知识 /Button ); }注意上面组件名用了中文只是为了表达清晰实际项目中请使用英文命名如KnowledgeDepositButton。本章小结知识点关键内容产品价值知识沉淀、效率提升、新人培训、质量保障数据模型文档、分类、标签、关联任务单列表页左侧分类树 右侧文档列表 搜索Markdown 编辑器编辑/预览/分栏三种模式支持分类和标签文档关联任务单与知识的双向关联智能推荐相关知识知识沉淀从任务单一键生成知识草稿审阅后发布下一章预告我们来实现数据统计与分析学习多维度筛选、图表可视化和数据导出功能。