章节系统
管理小说的具体章节内容,支持独立编辑和状态追踪
📖 系统概述
章节系统负责管理小说的具体内容,采用扁平结构存储章节正文。每个章节可以独立编辑,也可以与大纲节点关联,实现结构化创作。系统支持多种状态追踪,帮助作者清晰掌握创作进度。
🎯 功能特性
1. 章节管理
| 功能 | 说明 |
|---|---|
| 创建章节 | 新建空白章节或从大纲创建 |
| 编辑内容 | 富文本编辑,支持实时保存 |
| 排序调整 | 通过 chapter_number 控制顺序 |
| 批量操作 | 批量删除、批量状态更新 |
2. 大纲关联
章节可以与大纲节点(OutlineNode)建立一对一关联:
OutlineNode (chapter 类型)
│
└──→ Chapter
- outline_node_id = OutlineNode.id
- 自动同步标题
- 内容独立存储关联优势:
- 结构清晰:大纲和内容对应
- 导航便捷:从大纲直接跳转到章节
- 进度追踪:大纲视图显示章节完成状态
3. 状态追踪
| 状态 | 值 | 说明 |
|---|---|---|
| 草稿 | draft | 正在编写,未完成 |
| 已完成 | completed | 作者已完成编写 |
| AI 生成 | ai_generated | 由 AI 生成的内容 |
draft (草稿)
│
├──→ completed (已完成) ← 手动完成
│
└──→ ai_generated (AI生成) ← AI 生成
│
└──→ completed ← 审核后标记完成4. 字数统计
- 单章字数: 实时计算章节内容字数
- 累计统计: 汇总到项目总字数
- 目标追踪: 支持设置章节目标字数
🗄️ 数据模型
Chapter 模型
python
class Chapter(models.Model):
id = fields.IntField(pk=True)
uuid = fields.UUIDField() # 外部引用UUID
project_id = fields.IntField() # 所属项目
outline_node_id = fields.IntField(null=True) # 关联大纲节点(可选)
title = fields.CharField(max_length=200) # 章节标题
content = fields.TextField(null=True) # 章节正文
chapter_number = fields.IntField(default=0) # 章节序号
word_count = fields.IntField(default=0) # 字数
status = fields.CharField(max_length=20, default="draft") # 状态
created_at = fields.DatetimeField(auto_now_add=True)
updated_at = fields.DatetimeField(auto_now=True)
class Meta:
table = "chapters"关联关系
NovelProject
│
├──→ Chapter (多个)
│ │
│ └──→ OutlineNode (可选,一对一)
│
└──→ chapter_ids (JSON): 存储章节顺序🔌 API 接口
基础路径
/api/chapter接口列表
| 方法 | 路径 | 说明 |
|---|---|---|
GET | /project/{project_id} | 获取项目所有章节 |
POST | / | 创建章节 |
GET | /{chapter_id} | 获取章节详情 |
PUT | /{chapter_id} | 更新章节 |
DELETE | /{chapter_id} | 删除章节 |
PUT | /{chapter_id}/status | 更新章节状态 |
PUT | /{chapter_id}/content | 更新章节内容(自动保存) |
POST | /from-outline/{node_id} | 从大纲节点创建章节 |
PUT | /reorder | 调整章节顺序 |
请求/响应示例
创建章节
请求:
json
POST /api/chapter
{
"project_id": 1,
"title": "第一章 初遇",
"chapter_number": 1,
"outline_node_id": 5
}响应:
json
{
"id": 1,
"uuid": "550e8400-e29b-41d4-a716-446655440000",
"project_id": 1,
"title": "第一章 初遇",
"content": null,
"chapter_number": 1,
"word_count": 0,
"status": "draft",
"outline_node_id": 5,
"created_at": "2026-01-04T12:00:00Z"
}更新章节内容
请求:
json
PUT /api/chapter/1/content
{
"content": "清晨的阳光透过窗帘的缝隙..."
}响应:
json
{
"id": 1,
"word_count": 15,
"updated_at": "2026-01-04T12:30:00Z"
}获取项目章节列表
请求:
GET /api/chapter/project/1?status=draft响应:
json
{
"chapters": [
{
"id": 1,
"title": "第一章 初遇",
"chapter_number": 1,
"word_count": 2500,
"status": "completed"
},
{
"id": 2,
"title": "第二章 误会",
"chapter_number": 2,
"word_count": 1800,
"status": "draft"
}
],
"total_word_count": 4300
}🖥️ 用户交互
章节列表页面
布局结构:
┌─────────────────────────────────────────────────────┐
│ 项目名称 > 章节管理 [新建章节] [批量操作] │
├─────────────────────────────────────────────────────┤
│ 筛选: [全部▼] [草稿] [已完成] [AI生成] │
├─────────────────────────────────────────────────────┤
│ ┌─────────────────────────────────────────────┐ │
│ │ □ 第一章 初遇 2,500字 ● 已完成 │ │
│ │ 最后编辑: 2026-01-04 12:30 │ │
│ ├─────────────────────────────────────────────┤ │
│ │ □ 第二章 误会 1,800字 ○ 草稿 │ │
│ │ 最后编辑: 2026-01-04 14:20 │ │
│ └─────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────┘章节编辑页面
布局结构:
┌─────────────────────────────────────────────────────┐
│ ← 返回 第一章 初遇 [状态: 草稿▼] [保存] [AI续写]│
├─────────────────────────────────────────────────────┤
│ ┌─────────────────────────────────────────────┐ │
│ │ │ │
│ │ 章节内容编辑区 │ │
│ │ │ │
│ │ 清晨的阳光透过窗帘的缝隙... │ │
│ │ │ │
│ │ │ │
│ └─────────────────────────────────────────────┘ │
├─────────────────────────────────────────────────────┤
│ 字数: 2,500 / 目标: 3,000 自动保存: 12:30:45 │
└─────────────────────────────────────────────────────┘交互操作
| 操作 | 方式 | 说明 |
|---|---|---|
| 新建章节 | 点击按钮 | 弹出创建对话框 |
| 编辑章节 | 点击章节 | 进入编辑页面 |
| 切换状态 | 下拉菜单 | 快速切换章节状态 |
| 删除章节 | 右键/按钮 | 确认后删除 |
| 拖拽排序 | 拖拽行 | 调整章节顺序 |
| 自动保存 | 自动 | 编辑时自动保存(防抖) |
从大纲创建章节
1. 在大纲系统中选中 chapter 类型节点
↓
2. 点击"创建章节"
↓
3. 自动创建关联章节
- 标题同步大纲节点标题
- 建立 outline_node_id 关联
↓
4. 跳转到章节编辑页面⚙️ 技术实现
前端
路径: src/features/chapter/frontend/
| 文件 | 说明 |
|---|---|
api.ts | API 调用封装 |
pages/ChapterListPage.tsx | 章节列表页 |
pages/ChapterEditPage.tsx | 章节编辑页 |
components/ChapterEditor.tsx | 编辑器组件 |
components/ChapterCard.tsx | 章节卡片组件 |
hooks/useAutoSave.ts | 自动保存 Hook |
后端
路径: src/features/chapter/backend/
| 文件 | 说明 |
|---|---|
models.py | Chapter 模型 |
schemas.py | Pydantic 验证模型 |
router.py | FastAPI 路由 |
services/chapter_service.py | 章节业务逻辑 |
services/word_count_service.py | 字数统计服务 |
自动保存实现
typescript
// useAutoSave.ts
const useAutoSave = (content: string, onSave: Function) => {
const debouncedSave = useDebouncedCallback(
async (content) => {
await onSave(content)
},
2000 // 2秒防抖
)
useEffect(() => {
if (content) {
debouncedSave(content)
}
}, [content])
}字数统计
python
def count_words(content: str) -> int:
"""统计中文字数(含标点)"""
if not content:
return 0
# 移除空白字符后计算长度
return len(content.replace(" ", "").replace("\n", ""))