diff --git a/frontend/src/pages/Chapters.tsx b/frontend/src/pages/Chapters.tsx index 96786bb..7f95fe9 100644 --- a/frontend/src/pages/Chapters.tsx +++ b/frontend/src/pages/Chapters.tsx @@ -1,6 +1,6 @@ import { useState, useEffect, useRef, useMemo } from 'react'; -import { List, Button, Modal, Form, Input, Select, message, Empty, Space, Badge, Tag, Card, Tooltip, InputNumber, Alert, Radio, Descriptions, Collapse, Popconfirm, FloatButton } from 'antd'; -import { EditOutlined, FileTextOutlined, ThunderboltOutlined, LockOutlined, DownloadOutlined, SettingOutlined, FundOutlined, SyncOutlined, CheckCircleOutlined, CloseCircleOutlined, RocketOutlined, StopOutlined, InfoCircleOutlined, CaretRightOutlined, DeleteOutlined, BookOutlined, FormOutlined, PlusOutlined } from '@ant-design/icons'; +import { List, Button, Modal, Form, Input, Select, message, Empty, Space, Badge, Tag, Card, InputNumber, Alert, Radio, Descriptions, Collapse, Popconfirm, FloatButton } from 'antd'; +import { EditOutlined, FileTextOutlined, ThunderboltOutlined, LockOutlined, DownloadOutlined, SettingOutlined, FundOutlined, SyncOutlined, CheckCircleOutlined, CloseCircleOutlined, RocketOutlined, StopOutlined, InfoCircleOutlined, CaretRightOutlined, DeleteOutlined, BookOutlined, FormOutlined, PlusOutlined, ReadOutlined } from '@ant-design/icons'; import { useStore } from '../store'; import { useChapterSync } from '../store/hooks'; import { projectApi, writingStyleApi, chapterApi } from '../services/api'; @@ -10,6 +10,7 @@ import ExpansionPlanEditor from '../components/ExpansionPlanEditor'; import { SSELoadingOverlay } from '../components/SSELoadingOverlay'; import { SSEProgressModal } from '../components/SSEProgressModal'; import FloatingIndexPanel from '../components/FloatingIndexPanel'; +import ChapterReader from '../components/ChapterReader'; const { TextArea } = Input; @@ -39,6 +40,10 @@ export default function Chapters() { const pollingIntervalsRef = useRef>({}); const [isIndexPanelVisible, setIsIndexPanelVisible] = useState(false); + // 阅读器状态 + const [readerVisible, setReaderVisible] = useState(false); + const [readingChapter, setReadingChapter] = useState(null); + // 规划编辑状态 const [planEditorVisible, setPlanEditorVisible] = useState(false); const [editingPlanChapter, setEditingPlanChapter] = useState(null); @@ -1170,11 +1175,9 @@ export default function Chapters() { ); case 'failed': return ( - - } color="error"> - 分析失败 - - + } color="error" title={task.error_message || undefined}> + 分析失败 + ); default: return null; @@ -1478,6 +1481,24 @@ export default function Chapters() { } }; + // 打开阅读器 + const handleOpenReader = (chapter: Chapter) => { + setReadingChapter(chapter); + setReaderVisible(true); + }; + + // 阅读器切换章节 + const handleReaderChapterChange = async (chapterId: string) => { + try { + const response = await fetch(`/api/chapters/${chapterId}`); + if (!response.ok) throw new Error('获取章节失败'); + const newChapter = await response.json(); + setReadingChapter(newChapter); + } catch { + message.error('加载章节失败'); + } + }; + return (
{contextHolder} @@ -1561,6 +1582,15 @@ export default function Chapters() { alignItems: isMobile ? 'flex-start' : 'center', }} actions={isMobile ? undefined : [ + , - + {isAnalyzing ? '分析中' : '查看分析'} + ); })(),
@@ -1644,6 +1669,14 @@ export default function Chapters() { {isMobile && ( + , - + {isAnalyzing ? '分析中' : '查看分析'} + ); })(), - + ); })()} @@ -2556,6 +2592,19 @@ export default function Chapters() { onChapterSelect={handleChapterSelect} /> + {/* 章节阅读器 */} + {readingChapter && ( + { + setReaderVisible(false); + setReadingChapter(null); + }} + onChapterChange={handleReaderChapterChange} + /> + )} + {/* 规划编辑器 */} {editingPlanChapter && currentProject && (() => { let parsedPlanData = null;