diff --git a/app/backend/package.json b/app/backend/package.json
index 73d07ba..d63cce6 100644
--- a/app/backend/package.json
+++ b/app/backend/package.json
@@ -14,12 +14,6 @@
"dependencies": {
"@ant-design/icons": "5.x",
"@reduxjs/toolkit": "^1.9.3",
- "@tiptap/extension-color": "^3.11.0",
- "@tiptap/extension-text-align": "^3.11.0",
- "@tiptap/extension-text-style": "^3.11.0",
- "@tiptap/extension-underline": "^3.11.0",
- "@tiptap/react": "^3.11.0",
- "@tiptap/starter-kit": "^3.11.0",
"@uppy/aws-s3": "4.1.0",
"@uppy/core": "4.2.0",
"@uppy/dashboard": "4.1.0",
@@ -29,6 +23,8 @@
"@uppy/progress-bar": "4.0.0",
"@uppy/react": "4.0.2",
"@uppy/status-bar": "4.0.3",
+ "@wangeditor/editor": "^5.1.23",
+ "@wangeditor/editor-for-react": "^1.0.6",
"ahooks": "^3.7.6",
"antd": "^5.12.2",
"axios": "^1.3.4",
diff --git a/app/backend/src/pages/textbook/chapter.module.less b/app/backend/src/pages/textbook/chapter.module.less
index 074dacf..c5b5c49 100644
--- a/app/backend/src/pages/textbook/chapter.module.less
+++ b/app/backend/src/pages/textbook/chapter.module.less
@@ -1,3 +1,12 @@
+.chapter-box {
+ width: 100%;
+ height: auto;
+ min-height: calc(100vh - 172px);
+ float: left;
+ background-color: white;
+ box-sizing: border-box;
+ border-radius: 12px;
+}
.chapter-main-body {
width: 100%;
height: auto;
@@ -7,6 +16,7 @@
border-radius: 12px;
display: flex;
flex-direction: row;
+ justify-content: space-between;
overflow: hidden;
.left-box {
width: 300px;
@@ -14,13 +24,13 @@
height: auto;
min-height: calc(100vh - 172px);
border-right: 2px solid #f6f6f6;
- box-shadow: 0 12px 40px rgba(0, 0, 0, 0.25), 0 6px 20px rgba(0, 0, 0, 0.2);
+ box-shadow: 0 12px 40px rgba(0, 0, 0, 0.1), 0 6px 20px rgba(0, 0, 0, 0.2);
box-sizing: border-box;
padding: 24px 16px;
background-color: white;
}
.right-box {
- width: calc(100% - 354px);
+ width: calc(100% - 301px);
float: left;
height: auto;
min-height: calc(100vh - 172px);
diff --git a/app/backend/src/pages/textbook/chapter.tsx b/app/backend/src/pages/textbook/chapter.tsx
index 0291e28..8732306 100644
--- a/app/backend/src/pages/textbook/chapter.tsx
+++ b/app/backend/src/pages/textbook/chapter.tsx
@@ -4,10 +4,8 @@ import React, { useEffect, useState } from 'react';
import { ChapterTree } from './compenents/chapterTree';
import { BackBartment } from '../../compenents';
import styles from './chapter.module.less';
-import { department } from '../../api/index';
-import EditorTextbookContent from './compenents/TextEditor/EditorToolbar';
-import EnhancedTextbookEditor from './compenents/TextEditor/EnhancedTextbookEditor';
import { GetChapterListApi } from '../../api/textbook';
+import TextbookEditor from './compenents/TextEditor/TextbookEditor';
export interface ChapterItemModel {
created_at: string;
@@ -62,8 +60,10 @@ const ChapterManagementPage = () => {
};
return (
-
-
+
+
+
+
{
{/*{selectedChapter ? (*/}
-
+ >
{/* : (
点击左侧目录选择
)}*/}
diff --git a/app/backend/src/pages/textbook/compenents/TextEditor/EnhancedTextbookEditor.tsx b/app/backend/src/pages/textbook/compenents/TextEditor/EnhancedTextbookEditor.tsx
deleted file mode 100644
index 214077c..0000000
--- a/app/backend/src/pages/textbook/compenents/TextEditor/EnhancedTextbookEditor.tsx
+++ /dev/null
@@ -1,216 +0,0 @@
-// components/EnhancedTextbookEditor.tsx
-import React, { useEffect, useState, useCallback } from 'react';
-import { EditorContent, useEditor } from '@tiptap/react';
-import { TextStyleKit } from '@tiptap/extension-text-style';
-import StarterKit from '@tiptap/starter-kit';
-import EnhancedToolbar from './EditorToolbar';
-import { EditorProps } from '../../../../types/editor';
-import './EnhancedTextbookEditor.less';
-import TextAlign from '@tiptap/extension-text-align';
-import Highlight from '@tiptap/extension-highlight';
-import Color from '@tiptap/extension-color';
-import { Button } from 'antd';
-import {
- EyeFilled,
- EyeOutlined,
- ForkOutlined,
- SaveFilled,
- UploadOutlined,
-} from '@ant-design/icons';
-import { useTranslation } from 'react-i18next';
-
-const extensions = [
- TextStyleKit,
- StarterKit.configure({
- heading: {
- levels: [1, 2, 3],
- },
- }),
- TextAlign.configure({
- types: ['heading', 'paragraph'],
- alignments: ['left', 'center', 'right', 'justify'],
- }),
- Highlight.configure({
- multicolor: true,
- }),
- Color.configure({
- types: ['textStyle'],
- }),
-];
-
-const EnhancedTextbookEditor: React.FC
= ({
- chapterId,
- chapterTitle,
- initialContent = '',
- onSave,
- onContentChange,
-}) => {
- const [isEditing, setIsEditing] = useState(false);
- const { t } = useTranslation();
-
- // 保留
- /* const [wordCount, setWordCount] = useState(0);
- const [lastSaved, setLastSaved] = useState(null);*/
-
- // 初始化编辑器
- const editor = useEditor({
- extensions,
- content:
- initialContent ||
- `
- ${chapterTitle}
- 开始编写本章节的内容...
- `,
- immediatelyRender: false,
- onUpdate: ({ editor }) => {
- const content = editor.getHTML();
- const text = editor.getText();
-
- // 更新统计
- // setWordCount(text.split(/\s+/).filter((word) => word.length > 0).length);
-
- // 通知父组件
- onContentChange(chapterId, content);
- setIsEditing(true);
- },
- onBlur: ({ editor }) => {
- // 失去焦点时自动保存
- if (isEditing) {
- const content = editor.getHTML();
- onSave(chapterId, content);
- // setLastSaved(new Date());
- setIsEditing(false);
- }
- },
- editorProps: {
- attributes: {
- class: 'editor-content',
- 'data-chapter-id': chapterId,
- },
- },
- });
-
- // 章节切换时更新内容
- useEffect(() => {
- if (editor && initialContent !== editor.getHTML()) {
- editor.commands.setContent(
- initialContent ||
- `
- ${chapterTitle}
- 开始编写本章节的内容...
- `
- );
- setIsEditing(false);
-
- // 更新统计
- const text = editor.getText();
- // setWordCount(text.split(/\s+/).filter((word) => word.length > 0).length);
- }
- }, [chapterId, initialContent, chapterTitle, editor]);
-
- // 手动保存
- const handleManualSave = useCallback(() => {
- if (editor && isEditing) {
- const content = editor.getHTML();
-
- onSave(chapterId, content);
- // setLastSaved(new Date());
- setIsEditing(false);
- }
- }, [editor, chapterId, isEditing, onSave]);
-
- // 格式化时间
- const formatTime = (date: Date | null): string => {
- if (!date) return '未保存';
- return date.toLocaleTimeString('zh-CN', {
- hour: '2-digit',
- minute: '2-digit',
- });
- };
-
- // 预览
- const handlePreview = () => {
- alert('preview');
- };
-
- if (!chapterId) {
- return (
-
-
-
-
请选择章节
-
从左侧目录中选择一个章节开始编辑
-
-
- );
- }
-
- if (!editor) {
- return 编辑器加载中...
;
- }
-
- return (
-
- {/* 编辑器头部 */}
-
-
-
{chapterTitle}
- 所属章节: {chapterId}
-
-
- {/*
- 字数: {wordCount}
- {isEditing && ● 编辑中}
- 最后保存: {formatTime(lastSaved)}
-
-
*/}
-
-
-
-
-
-
-
- {/* 工具栏 */}
-
-
-
-
- {/* 编辑器内容 */}
-
-
-
- {/* 状态栏 */}
-
-
-
- );
-};
-
-export default EnhancedTextbookEditor;
diff --git a/app/backend/src/pages/textbook/compenents/TextEditor/EnhancedTextbookEditor.less b/app/backend/src/pages/textbook/compenents/TextEditor/TextbookEditor.less
similarity index 57%
rename from app/backend/src/pages/textbook/compenents/TextEditor/EnhancedTextbookEditor.less
rename to app/backend/src/pages/textbook/compenents/TextEditor/TextbookEditor.less
index 93fd936..da0c07e 100644
--- a/app/backend/src/pages/textbook/compenents/TextEditor/EnhancedTextbookEditor.less
+++ b/app/backend/src/pages/textbook/compenents/TextEditor/TextbookEditor.less
@@ -78,42 +78,6 @@
font-weight: 500;
}
-.editing-indicator {
- color: #28a745;
- font-weight: 500;
-}
-
-.save-button {
- padding: 8px 16px;
- border: 1px solid #007bff;
- background: #007bff;
- color: white;
- border-radius: 4px;
- cursor: pointer;
- font-size: 14px;
- transition: all 0.2s;
-}
-
-.save-button:hover:not(:disabled) {
- background: #0056b3;
- border-color: #0056b3;
-}
-
-.save-button:disabled {
- background: #6c757d;
- border-color: #6c757d;
- cursor: not-allowed;
-}
-
-.save-button.has-changes {
- background: #28a745;
- border-color: #28a745;
-}
-
-.save-button.has-changes:hover {
- background: #218838;
- border-color: #1e7e34;
-}
/*编辑区域*/
@@ -121,9 +85,8 @@
background: #8F8F8F;
position: relative;
margin: 20px;
- height: 640px;
- overflow-y: scroll;
- overflow-x: hidden;
+ height: 590px;
+ overflow: hidden;
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.1), 0 6px 20px 0 rgba(0, 0, 0, 0.1);
border-radius: 4px;
}
@@ -134,11 +97,6 @@
background: #f8f9fa;
border-bottom: 1px solid #e1e5e9;
padding: 12px 16px;
- position: sticky;
- top: 0;
- left: 0;
- right: 0;
- z-index: 100;
}
.enhanced-toolbar {
@@ -204,92 +162,6 @@
background: white;
}
-/* 编辑器内容样式 */
-.editor-content {
- outline: none;
- padding: 20px;
- min-height: 510px;
- font-size: 14px;
- line-height: 1.6;
- color: #333;
-}
-
-.editor-content h1,
-.editor-content h2,
-.editor-content h3,
-.editor-content h4,
-.editor-content h5,
-.editor-content h6 {
- margin: 1.5em 0 0.5em 0;
- font-weight: 600;
- line-height: 1.25;
-}
-
-.editor-content h1 {
- font-size: 2em;
- border-bottom: 2px solid #007bff;
- padding-bottom: 0.3em;
-}
-
-.editor-content h2 {
- font-size: 1.5em;
-}
-
-.editor-content h3 {
- font-size: 1.25em;
-}
-
-.editor-content p {
- margin: 1em 0;
-}
-
-.editor-content ul,
-.editor-content ol {
- margin: 1em 0;
- padding-left: 2em;
-}
-
-.editor-content li {
- margin: 0.5em 0;
-}
-
-.editor-content blockquote {
- border-left: 4px solid #007bff;
- margin: 1.5em 0;
- padding: 0.5em 1em;
- background: #f8f9fa;
- font-style: italic;
-}
-
-.editor-content code {
- background: #f1f3f4;
- padding: 0.2em 0.4em;
- border-radius: 3px;
- font-family: 'Monaco', 'Menlo', monospace;
- font-size: 0.9em;
-}
-
-.editor-content pre {
- background: #1a1a1a;
- color: #f8f9fa;
- padding: 1em;
- border-radius: 6px;
- overflow-x: auto;
- margin: 1.5em 0;
-}
-
-.editor-content pre code {
- background: none;
- padding: 0;
- color: inherit;
-}
-
-.editor-content hr {
- border: none;
- border-top: 2px solid #dee2e6;
- margin: 2em 0;
-}
-
/* 状态栏 */
.editor-status {
padding: 8px 16px;
diff --git a/app/backend/src/pages/textbook/compenents/TextEditor/TextbookEditor.tsx b/app/backend/src/pages/textbook/compenents/TextEditor/TextbookEditor.tsx
new file mode 100644
index 0000000..ed45423
--- /dev/null
+++ b/app/backend/src/pages/textbook/compenents/TextEditor/TextbookEditor.tsx
@@ -0,0 +1,156 @@
+import React, { useEffect, useState, useCallback } from 'react';
+import { EditorProps } from '../../../../types/editor';
+import './TextbookEditor.less';
+import { Button } from 'antd';
+import { EyeFilled, ForkOutlined, SaveFilled } from '@ant-design/icons';
+import { useTranslation } from 'react-i18next';
+import '@wangeditor/editor/dist/css/style.css';
+import { Editor, Toolbar } from '@wangeditor/editor-for-react';
+import { IDomEditor, IEditorConfig, IToolbarConfig } from '@wangeditor/editor';
+import { DomEditor } from '@wangeditor/editor';
+
+const TextbookEditor: React.FC = ({
+ chapterId,
+ chapterTitle,
+ initialContent = '',
+ onSave,
+}) => {
+ const [isEditing, setIsEditing] = useState(false);
+ const { t } = useTranslation();
+
+ // editor 实例
+ const [editor, setEditor] = useState(null); // TS 语法
+
+ // 编辑器内容
+ const [html, setHtml] = useState('hello
');
+
+ useEffect(() => {
+ if (editor && initialContent !== editor.getHtml()) {
+ editor.insertText(
+ // 添加内容
+ initialContent ||
+ `
+ ${chapterTitle}
+ 开始编写本章节的内容...
+ `
+ );
+ setIsEditing(false);
+
+ // 更新统计
+ const text = editor.getText();
+ // setWordCount(text.split(/\s+/).filter((word) => word.length > 0).length);
+ }
+ }, []);
+
+ // 工具栏配置
+ const toolbarConfig: Partial = {}; // TS 语法
+
+ // 编辑器配置
+ const editorConfig: Partial = {
+ placeholder: '请输入内容...',
+ };
+
+ /*仅用于本地开发 查看按钮*/
+ if (editor) {
+ const toolbar = DomEditor.getToolbar(editor);
+ const curToolbarConfig = toolbar?.getConfig();
+ console.log(curToolbarConfig?.toolbarKeys);
+ }
+
+ toolbarConfig.excludeKeys = ['fullScreen']; //移除不想要的fullScreen
+
+ useEffect(() => {
+ return () => {
+ if (editor == null) return;
+ editor.destroy();
+ setEditor(null);
+ };
+ }, [chapterId, initialContent, chapterTitle, editor]);
+
+ // 手动保存
+ const handleManualSave = useCallback(() => {
+ if (editor && isEditing) {
+ onSave(chapterId, html);
+ // setLastSaved(new Date());
+ setIsEditing(false);
+ }
+ }, [editor, chapterId, isEditing, onSave]);
+
+ // 格式化时间
+ const formatTime = (date: Date | null): string => {
+ if (!date) return '未保存';
+ return date.toLocaleTimeString('zh-CN', {
+ hour: '2-digit',
+ minute: '2-digit',
+ });
+ };
+
+ // 预览
+ const handlePreview = () => {
+ alert('preview');
+ };
+
+ if (!chapterId) {
+ return (
+
+
+
+
请选择章节
+
从左侧目录中选择一个章节开始编辑
+
+
+ );
+ }
+
+ return (
+
+ {/* 编辑器头部 */}
+
+
+
{chapterTitle}
+ 所属章节: {chapterId}
+
+
+
+
+
+
+
+
+
+
+ setHtml(editor.getHtml())}
+ mode="default"
+ style={{ height: '550px', overflowY: 'hidden' }}
+ />
+
+
+ );
+};
+
+export default TextbookEditor;
diff --git a/app/backend/src/pages/textbook/compenents/createTextbook.tsx b/app/backend/src/pages/textbook/compenents/createTextbook.tsx
index 8c35b98..e53049c 100644
--- a/app/backend/src/pages/textbook/compenents/createTextbook.tsx
+++ b/app/backend/src/pages/textbook/compenents/createTextbook.tsx
@@ -118,8 +118,6 @@ export const CreateTextbook: React.FC = ({ editId, isEdit, op
// 接口位置
setLoading(true);
if (isEdit) {
- console.log(thumb, 'thumb');
- console.log('thumb 类型:', typeof thumb);
UpdateTextbookApi(
editId,
values.title,
@@ -144,8 +142,6 @@ export const CreateTextbook: React.FC = ({ editId, isEdit, op
setLoading(false);
});
} else {
- console.log(thumb, 'thumb');
- console.log('thumb 类型:', typeof thumb);
CreateTextbookApi(
values.title,
thumb,