- Complete knowledge base list and detail pages - Complete document upload component - Fix CORS config (add PUT/DELETE method support) - Fix file upload issues (disabled state and beforeUpload return value) - Add detailed debug logs (cleaned up) - Create Day 21-22 completion summary document
710 lines
16 KiB
Markdown
710 lines
16 KiB
Markdown
# Day 21 - 知识库管理前端开发完成 ✅
|
||
|
||
**完成时间**: 2025年10月11日
|
||
**提交记录**: feat(frontend): Day 21 knowledge base management frontend completed
|
||
|
||
---
|
||
|
||
## 📋 任务概览
|
||
|
||
完成知识库管理的前端开发,包括:
|
||
1. ✅ 前端API服务封装
|
||
2. ✅ Zustand状态管理
|
||
3. ✅ 知识库列表组件
|
||
4. ✅ 创建/编辑知识库对话框
|
||
5. ✅ 文档上传组件
|
||
6. ✅ 文档列表组件
|
||
7. ✅ 主页面集成
|
||
8. ✅ 前端编译测试通过
|
||
|
||
---
|
||
|
||
## 🎨 前端架构
|
||
|
||
### 1. API服务层 (`knowledgeBaseApi.ts`)
|
||
|
||
**类型定义**:
|
||
```typescript
|
||
export interface KnowledgeBase {
|
||
id: string;
|
||
userId: string;
|
||
name: string;
|
||
description?: string;
|
||
difyDatasetId: string;
|
||
fileCount: number;
|
||
totalSizeBytes: number;
|
||
createdAt: string;
|
||
updatedAt: string;
|
||
_count?: { documents: number };
|
||
}
|
||
|
||
export interface Document {
|
||
id: string;
|
||
kbId: string;
|
||
filename: string;
|
||
fileType: string;
|
||
fileSizeBytes: number;
|
||
status: 'uploading' | 'parsing' | 'indexing' | 'completed' | 'error';
|
||
progress: number;
|
||
errorMessage?: string;
|
||
segmentsCount?: number;
|
||
tokensCount?: number;
|
||
uploadedAt: string;
|
||
processedAt?: string;
|
||
}
|
||
```
|
||
|
||
**API方法**:
|
||
```typescript
|
||
// 知识库管理
|
||
knowledgeBaseApi.getList() // 获取列表
|
||
knowledgeBaseApi.getById(id) // 获取详情
|
||
knowledgeBaseApi.create(data) // 创建
|
||
knowledgeBaseApi.update(id, data) // 更新
|
||
knowledgeBaseApi.delete(id) // 删除
|
||
knowledgeBaseApi.getStats(id) // 统计信息
|
||
knowledgeBaseApi.search(id, query) // 检索(RAG)
|
||
|
||
// 文档管理
|
||
documentApi.getList(kbId) // 获取列表
|
||
documentApi.upload(kbId, file) // 上传
|
||
documentApi.delete(id) // 删除
|
||
documentApi.reprocess(id) // 重新处理
|
||
```
|
||
|
||
### 2. 状态管理 (`useKnowledgeBaseStore.ts`)
|
||
|
||
**Zustand Store**:
|
||
```typescript
|
||
interface KnowledgeBaseState {
|
||
// 数据状态
|
||
knowledgeBases: KnowledgeBase[];
|
||
currentKb: KnowledgeBase | null;
|
||
documents: Document[];
|
||
loading: boolean;
|
||
error: string | null;
|
||
|
||
// 知识库操作
|
||
fetchKnowledgeBases: () => Promise<void>;
|
||
fetchKnowledgeBaseById: (id: string) => Promise<void>;
|
||
createKnowledgeBase: (name, description?) => Promise<KnowledgeBase>;
|
||
updateKnowledgeBase: (id, name?, description?) => Promise<void>;
|
||
deleteKnowledgeBase: (id: string) => Promise<void>;
|
||
|
||
// 文档操作
|
||
fetchDocuments: (kbId: string) => Promise<void>;
|
||
uploadDocument: (kbId, file, onProgress?) => Promise<Document>;
|
||
deleteDocument: (id: string) => Promise<void>;
|
||
reprocessDocument: (id: string) => Promise<void>;
|
||
}
|
||
```
|
||
|
||
**特点**:
|
||
- ✅ 集中式状态管理
|
||
- ✅ 自动错误处理
|
||
- ✅ Loading状态管理
|
||
- ✅ 操作后自动刷新
|
||
|
||
---
|
||
|
||
## 🧩 核心组件
|
||
|
||
### 1. KnowledgeBaseList(知识库列表)
|
||
|
||
**功能**:
|
||
- ✅ 卡片式展示(3列网格布局)
|
||
- ✅ 配额显示(已使用 X/3 个)
|
||
- ✅ 文档数量、总大小、创建时间
|
||
- ✅ 编辑/删除操作
|
||
- ✅ 空状态提示
|
||
- ✅ 配额上限禁用创建按钮
|
||
|
||
**UI特点**:
|
||
```typescript
|
||
// 卡片式布局
|
||
<Card hoverable onClick={onSelectClick}>
|
||
<Card.Meta
|
||
avatar={<FolderIcon />}
|
||
title={kb.name}
|
||
description={
|
||
- 描述(省略显示)
|
||
- 文档数量
|
||
- 总大小
|
||
- 创建时间
|
||
}
|
||
/>
|
||
<Actions>
|
||
- 编辑按钮
|
||
- 删除按钮(带确认)
|
||
</Actions>
|
||
</Card>
|
||
```
|
||
|
||
### 2. CreateKBDialog(创建对话框)
|
||
|
||
**功能**:
|
||
- ✅ 表单验证(名称必填、长度限制)
|
||
- ✅ 描述字段(可选、字数统计)
|
||
- ✅ 配额提示信息
|
||
- ✅ Loading状态
|
||
- ✅ 错误提示
|
||
|
||
**验证规则**:
|
||
```typescript
|
||
- 名称:必填、最多50字
|
||
- 描述:选填、最多200字
|
||
```
|
||
|
||
**提示信息**:
|
||
```
|
||
📌 提示
|
||
• 每个用户最多创建 3 个知识库
|
||
• 每个知识库最多上传 50 个文档
|
||
• 支持的文件格式:PDF、DOC、DOCX、TXT、MD
|
||
• 单个文件最大10MB
|
||
```
|
||
|
||
### 3. EditKBDialog(编辑对话框)
|
||
|
||
**功能**:
|
||
- ✅ 自动填充现有数据
|
||
- ✅ 表单验证
|
||
- ✅ 更新成功提示
|
||
- ✅ 错误处理
|
||
|
||
### 4. DocumentUpload(文档上传)
|
||
|
||
**功能**:
|
||
- ✅ 拖拽上传
|
||
- ✅ 点击上传
|
||
- ✅ 文件类型验证(前端)
|
||
- ✅ 文件大小验证(10MB)
|
||
- ✅ 上传进度显示
|
||
- ✅ 配额检查(50个/知识库)
|
||
- ✅ 实时进度条
|
||
|
||
**验证逻辑**:
|
||
```typescript
|
||
const beforeUpload = (file: File) => {
|
||
// 检查数量限制
|
||
if (currentDocumentCount >= 50) {
|
||
message.error('已达到文档数量上限(50个)');
|
||
return Upload.LIST_IGNORE;
|
||
}
|
||
|
||
// 检查文件类型
|
||
const allowedTypes = ['application/pdf', 'application/msword', ...];
|
||
if (!allowedTypes.includes(file.type)) {
|
||
message.error('不支持的文件类型');
|
||
return Upload.LIST_IGNORE;
|
||
}
|
||
|
||
// 检查文件大小(10MB)
|
||
if (file.size > 10 * 1024 * 1024) {
|
||
message.error('文件大小不能超过 10MB');
|
||
return Upload.LIST_IGNORE;
|
||
}
|
||
|
||
return false; // 手动上传
|
||
};
|
||
```
|
||
|
||
**上传流程**:
|
||
```typescript
|
||
customRequest = async (options) => {
|
||
1. 调用 useKnowledgeBaseStore.uploadDocument()
|
||
2. 显示上传进度(0-100%)
|
||
3. 上传完成后通知父组件刷新
|
||
4. 显示成功/失败消息
|
||
}
|
||
```
|
||
|
||
### 5. DocumentList(文档列表)
|
||
|
||
**功能**:
|
||
- ✅ 表格展示
|
||
- ✅ 状态图标(上传中、解析中、索引中、已就绪、失败)
|
||
- ✅ 进度显示
|
||
- ✅ 文件大小格式化
|
||
- ✅ Token数、段落数显示
|
||
- ✅ 删除操作(带确认)
|
||
- ✅ 重试按钮(失败状态)
|
||
- ✅ 分页
|
||
- ✅ 空状态
|
||
|
||
**状态图标**:
|
||
```typescript
|
||
const statusConfig = {
|
||
uploading: { color: 'blue', icon: <LoadingOutlined />, text: '上传中' },
|
||
parsing: { color: 'processing', icon: <ClockCircleOutlined />, text: '解析中' },
|
||
indexing: { color: 'processing', icon: <ClockCircleOutlined />, text: '索引中' },
|
||
completed: { color: 'success', icon: <CheckCircleOutlined />, text: '已就绪' },
|
||
error: { color: 'error', icon: <CloseCircleOutlined />, text: '失败' },
|
||
};
|
||
```
|
||
|
||
**进度显示**:
|
||
- 处理中:进度条(0-100%)
|
||
- 已完成:显示段落数和Token数
|
||
- 失败:显示错误提示(Tooltip)
|
||
|
||
### 6. KnowledgePage(主页面)
|
||
|
||
**双视图架构**:
|
||
|
||
#### 视图1:知识库列表
|
||
```typescript
|
||
<KnowledgeBaseList
|
||
knowledgeBases={knowledgeBases}
|
||
onCreateClick={() => setCreateDialogOpen(true)}
|
||
onEditClick={handleEdit}
|
||
onDeleteClick={handleDelete}
|
||
onSelectClick={handleSelectKb} // 切换到详情视图
|
||
/>
|
||
|
||
<CreateKBDialog />
|
||
<EditKBDialog />
|
||
```
|
||
|
||
#### 视图2:知识库详情
|
||
```typescript
|
||
<返回按钮 onClick={handleBackToList} />
|
||
|
||
<Card>
|
||
<知识库名称和描述>
|
||
<编辑按钮>
|
||
</Card>
|
||
|
||
<Tabs>
|
||
<TabPane key="documents">
|
||
<DocumentUpload />
|
||
<DocumentList />
|
||
</TabPane>
|
||
|
||
<TabPane key="stats">
|
||
<统计卡片网格>
|
||
- 总文档数
|
||
- 已就绪数
|
||
- 处理中数
|
||
- 失败数
|
||
- 总Token数
|
||
- 总段落数
|
||
</统计卡片网格>
|
||
</TabPane>
|
||
</Tabs>
|
||
```
|
||
|
||
**实时状态轮询**:
|
||
```typescript
|
||
useEffect(() => {
|
||
if (!currentKb) return;
|
||
|
||
// 检查是否有处理中的文档
|
||
const hasProcessing = documents.some(doc =>
|
||
['uploading', 'parsing', 'indexing'].includes(doc.status)
|
||
);
|
||
|
||
if (!hasProcessing) return;
|
||
|
||
// 每5秒轮询一次
|
||
const interval = setInterval(() => {
|
||
fetchDocuments(currentKb.id);
|
||
}, 5000);
|
||
|
||
return () => clearInterval(interval);
|
||
}, [currentKb, documents]);
|
||
```
|
||
|
||
---
|
||
|
||
## 🎯 核心功能实现
|
||
|
||
### 1. 配额管理
|
||
|
||
**知识库配额(3个/用户)**:
|
||
```typescript
|
||
// 前端检查
|
||
const canCreateMore = knowledgeBases.length < 3;
|
||
|
||
<Button
|
||
disabled={!canCreateMore}
|
||
onClick={onCreateClick}
|
||
>
|
||
创建知识库
|
||
</Button>
|
||
|
||
// 后端验证
|
||
if (user.kbUsed >= user.kbQuota) {
|
||
throw new Error('Knowledge base quota exceeded');
|
||
}
|
||
```
|
||
|
||
**文档配额(50个/知识库)**:
|
||
```typescript
|
||
// 前端检查
|
||
const isAtLimit = currentDocumentCount >= 50;
|
||
|
||
<Upload
|
||
disabled={isAtLimit}
|
||
beforeUpload={(file) => {
|
||
if (isAtLimit) {
|
||
message.error('已达到文档数量上限(50个)');
|
||
return Upload.LIST_IGNORE;
|
||
}
|
||
}}
|
||
/>
|
||
|
||
// 后端验证
|
||
const documentCount = await prisma.document.count({ where: { kbId } });
|
||
if (documentCount >= 50) {
|
||
throw new Error('Document limit exceeded');
|
||
}
|
||
```
|
||
|
||
### 2. 文件验证
|
||
|
||
**前端验证**:
|
||
```typescript
|
||
const allowedTypes = [
|
||
'application/pdf',
|
||
'application/msword',
|
||
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||
'text/plain',
|
||
'text/markdown',
|
||
];
|
||
|
||
const allowedExtensions = ['.pdf', '.doc', '.docx', '.txt', '.md'];
|
||
|
||
// 类型检查
|
||
if (!allowedTypes.includes(file.type) &&
|
||
!allowedExtensions.some(ext => file.name.endsWith(ext))) {
|
||
message.error('不支持的文件类型');
|
||
return Upload.LIST_IGNORE;
|
||
}
|
||
|
||
// 大小检查
|
||
if (file.size > 10 * 1024 * 1024) {
|
||
message.error('文件大小不能超过 10MB');
|
||
return Upload.LIST_IGNORE;
|
||
}
|
||
```
|
||
|
||
**后端验证**:
|
||
```typescript
|
||
// Controller层
|
||
const allowedTypes = [
|
||
'application/pdf',
|
||
'application/msword',
|
||
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||
'text/plain',
|
||
'text/markdown',
|
||
];
|
||
|
||
if (!allowedTypes.includes(fileType)) {
|
||
return reply.status(400).send({
|
||
success: false,
|
||
message: 'File type not supported',
|
||
});
|
||
}
|
||
|
||
if (fileSizeBytes > 10 * 1024 * 1024) {
|
||
return reply.status(400).send({
|
||
success: false,
|
||
message: 'File size exceeds 10MB limit',
|
||
});
|
||
}
|
||
```
|
||
|
||
### 3. 状态追踪
|
||
|
||
**文档处理状态流**:
|
||
```
|
||
uploading → parsing → indexing → completed
|
||
↘ error
|
||
```
|
||
|
||
**前端轮询**:
|
||
```typescript
|
||
// 每5秒检查一次处理中的文档
|
||
const interval = setInterval(() => {
|
||
if (hasProcessingDocs) {
|
||
fetchDocuments(currentKb.id);
|
||
}
|
||
}, 5000);
|
||
```
|
||
|
||
**后端轮询**:
|
||
```typescript
|
||
// Service层:上传后自动轮询Dify状态
|
||
async function pollDocumentStatus(userId, kbId, documentId, difyDocumentId) {
|
||
for (let i = 0; i < 30; i++) {
|
||
await new Promise(resolve => setTimeout(resolve, 2000));
|
||
|
||
const difyDocument = await difyClient.getDocument(
|
||
knowledgeBase.difyDatasetId,
|
||
difyDocumentId
|
||
);
|
||
|
||
await prisma.document.update({
|
||
where: { id: documentId },
|
||
data: {
|
||
status: difyDocument.indexing_status,
|
||
progress: calculateProgress(difyDocument),
|
||
segmentsCount: difyDocument.word_count,
|
||
tokensCount: difyDocument.tokens,
|
||
},
|
||
});
|
||
|
||
if (difyDocument.indexing_status === 'completed') break;
|
||
if (difyDocument.indexing_status === 'error') break;
|
||
}
|
||
}
|
||
```
|
||
|
||
### 4. 错误处理
|
||
|
||
**统一错误处理**:
|
||
```typescript
|
||
// Store层
|
||
try {
|
||
await operation();
|
||
} catch (error: any) {
|
||
const errorMsg = error.response?.data?.message || '操作失败';
|
||
set({ error: errorMsg, loading: false });
|
||
throw new Error(errorMsg);
|
||
}
|
||
|
||
// 组件层
|
||
useEffect(() => {
|
||
if (error) {
|
||
message.error(error);
|
||
clearError();
|
||
}
|
||
}, [error]);
|
||
|
||
// 操作层
|
||
const handleDelete = async (kb: KnowledgeBase) => {
|
||
try {
|
||
await deleteKnowledgeBase(kb.id);
|
||
message.success('知识库删除成功');
|
||
} catch (error: any) {
|
||
message.error(error.message || '删除失败');
|
||
}
|
||
};
|
||
```
|
||
|
||
---
|
||
|
||
## 📊 UI/UX设计
|
||
|
||
### 1. 响应式布局
|
||
|
||
**知识库列表**:
|
||
```css
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
|
||
gap: 16px;
|
||
```
|
||
|
||
**统计信息**:
|
||
```css
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||
gap: 16px;
|
||
```
|
||
|
||
### 2. 交互反馈
|
||
|
||
**Loading状态**:
|
||
- ✅ 按钮 `loading` 属性
|
||
- ✅ 列表 `loading` 属性
|
||
- ✅ 上传进度条
|
||
- ✅ Spin加载指示器
|
||
|
||
**成功/失败提示**:
|
||
```typescript
|
||
message.success('操作成功');
|
||
message.error('操作失败');
|
||
message.warning('已达到上限');
|
||
```
|
||
|
||
**确认对话框**:
|
||
```typescript
|
||
<Popconfirm
|
||
title="确认删除?"
|
||
description="删除知识库将同时删除其中的所有文档,此操作不可恢复。"
|
||
onConfirm={onDelete}
|
||
okText="确认"
|
||
cancelText="取消"
|
||
okButtonProps={{ danger: true }}
|
||
>
|
||
<Button danger>删除</Button>
|
||
</Popconfirm>
|
||
```
|
||
|
||
### 3. 状态可视化
|
||
|
||
**状态图标**:
|
||
- 🔵 上传中(Loading图标 + 蓝色)
|
||
- 🟡 解析中/索引中(Clock图标 + 黄色)
|
||
- 🟢 已就绪(CheckCircle图标 + 绿色)
|
||
- 🔴 失败(CloseCircle图标 + 红色)
|
||
|
||
**进度显示**:
|
||
- 处理中:`<Progress percent={progress} status="active" />`
|
||
- 已完成:`段落数 | Token数`
|
||
- 失败:`处理失败(带错误详情Tooltip)`
|
||
|
||
---
|
||
|
||
## ✅ 测试验证
|
||
|
||
### 编译测试
|
||
```bash
|
||
cd D:\MyCursor\AIclinicalresearch\frontend
|
||
npm run build
|
||
|
||
✅ TypeScript编译通过(0错误)
|
||
✅ Vite构建成功
|
||
✅ 产物大小:1.98MB(gzip: 656.82KB)
|
||
```
|
||
|
||
### 代码质量
|
||
- ✅ TypeScript类型安全
|
||
- ✅ ESLint检查通过
|
||
- ✅ 组件化设计
|
||
- ✅ 状态管理规范
|
||
- ✅ 错误处理完善
|
||
|
||
---
|
||
|
||
## 📦 新增文件
|
||
|
||
```
|
||
frontend/src/
|
||
├── api/
|
||
│ └── knowledgeBaseApi.ts # API服务(270行)
|
||
├── stores/
|
||
│ └── useKnowledgeBaseStore.ts # 状态管理(180行)
|
||
├── components/knowledge/
|
||
│ ├── KnowledgeBaseList.tsx # 知识库列表(205行)
|
||
│ ├── CreateKBDialog.tsx # 创建对话框(95行)
|
||
│ ├── EditKBDialog.tsx # 编辑对话框(85行)
|
||
│ ├── DocumentUpload.tsx # 文档上传(145行)
|
||
│ └── DocumentList.tsx # 文档列表(220行)
|
||
└── pages/
|
||
└── KnowledgePage.tsx # 主页面(281行)
|
||
```
|
||
|
||
**总计**:8个文件,约1481行代码
|
||
|
||
---
|
||
|
||
## 🎯 功能清单
|
||
|
||
### 知识库管理 ✅
|
||
- [x] 创建知识库(带表单验证)
|
||
- [x] 编辑知识库(名称、描述)
|
||
- [x] 删除知识库(带确认、级联删除)
|
||
- [x] 查看知识库列表(卡片式)
|
||
- [x] 查看知识库详情(文档列表、统计)
|
||
- [x] 配额管理(3个/用户)
|
||
|
||
### 文档管理 ✅
|
||
- [x] 上传文档(拖拽/点击)
|
||
- [x] 删除文档(带确认)
|
||
- [x] 重新处理文档(失败时)
|
||
- [x] 查看文档列表(表格式)
|
||
- [x] 状态追踪(5种状态)
|
||
- [x] 进度显示(实时更新)
|
||
- [x] 配额管理(50个/知识库)
|
||
|
||
### 文件验证 ✅
|
||
- [x] 类型验证(PDF、DOC、DOCX、TXT、MD)
|
||
- [x] 大小验证(10MB上限)
|
||
- [x] 数量验证(50个上限)
|
||
- [x] 前端预检查
|
||
- [x] 后端二次验证
|
||
|
||
### 实时更新 ✅
|
||
- [x] 状态轮询(每5秒)
|
||
- [x] 上传进度(0-100%)
|
||
- [x] 自动刷新(操作后)
|
||
- [x] 错误提示(实时)
|
||
|
||
### UI/UX ✅
|
||
- [x] 响应式布局
|
||
- [x] Loading状态
|
||
- [x] 成功/失败提示
|
||
- [x] 确认对话框
|
||
- [x] 空状态提示
|
||
- [x] 状态图标
|
||
- [x] 进度条
|
||
- [x] Tooltip提示
|
||
|
||
---
|
||
|
||
## 🚀 后续优化建议
|
||
|
||
### 1. 性能优化
|
||
- [ ] 虚拟滚动(大量文档时)
|
||
- [ ] 懒加载图片/文件预览
|
||
- [ ] 分页加载优化
|
||
- [ ] 代码分割(动态导入)
|
||
|
||
### 2. 功能增强
|
||
- [ ] 文件预览功能
|
||
- [ ] 批量上传
|
||
- [ ] 批量删除
|
||
- [ ] 导入/导出知识库
|
||
- [ ] 知识库搜索
|
||
- [ ] 文档内容搜索
|
||
- [ ] 标签管理
|
||
- [ ] 文档分类
|
||
|
||
### 3. 用户体验
|
||
- [ ] 上传队列管理
|
||
- [ ] 断点续传
|
||
- [ ] 拖拽排序
|
||
- [ ] 键盘快捷键
|
||
- [ ] 暗黑模式
|
||
- [ ] 移动端适配
|
||
|
||
---
|
||
|
||
## 💡 技术亮点
|
||
|
||
1. **组件化设计**:8个独立组件,职责清晰,易于维护
|
||
2. **状态管理**:Zustand集中管理,操作简洁
|
||
3. **类型安全**:完整的TypeScript类型定义
|
||
4. **错误处理**:三层错误处理(API → Store → Component)
|
||
5. **实时更新**:智能轮询,只在需要时启动
|
||
6. **用户体验**:丰富的交互反馈,操作流畅
|
||
7. **配额管理**:前后端双重验证,防止滥用
|
||
8. **文件验证**:多重验证机制,保证数据质量
|
||
|
||
---
|
||
|
||
## 📝 提交信息
|
||
|
||
```bash
|
||
git commit -m "feat(frontend): Day 21 knowledge base management frontend completed"
|
||
|
||
9 files changed, 1466 insertions(+)
|
||
create mode 100644 frontend/src/api/knowledgeBaseApi.ts
|
||
create mode 100644 frontend/src/components/knowledge/CreateKBDialog.tsx
|
||
create mode 100644 frontend/src/components/knowledge/DocumentList.tsx
|
||
create mode 100644 frontend/src/components/knowledge/DocumentUpload.tsx
|
||
create mode 100644 frontend/src/components/knowledge/EditKBDialog.tsx
|
||
create mode 100644 frontend/src/components/knowledge/KnowledgeBaseList.tsx
|
||
create mode 100644 frontend/src/stores/useKnowledgeBaseStore.ts
|
||
rewrite frontend/src/pages/AgentChatPage.tsx
|
||
rewrite frontend/src/pages/KnowledgePage.tsx
|
||
```
|
||
|
||
---
|
||
|
||
**总结**:Day 21成功完成知识库管理的前端开发,所有组件功能完整,编译测试通过,为用户提供了流畅的知识库管理体验!🎉
|
||
|
||
|