Compare commits
2 Commits
bc8dc4cff6
...
6b6761f7bb
| Author | SHA1 | Date | |
|---|---|---|---|
| 6b6761f7bb | |||
| 52c33a9940 |
@ -14,12 +14,6 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ant-design/icons": "5.x",
|
"@ant-design/icons": "5.x",
|
||||||
"@reduxjs/toolkit": "^1.9.3",
|
"@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/aws-s3": "4.1.0",
|
||||||
"@uppy/core": "4.2.0",
|
"@uppy/core": "4.2.0",
|
||||||
"@uppy/dashboard": "4.1.0",
|
"@uppy/dashboard": "4.1.0",
|
||||||
@ -29,6 +23,8 @@
|
|||||||
"@uppy/progress-bar": "4.0.0",
|
"@uppy/progress-bar": "4.0.0",
|
||||||
"@uppy/react": "4.0.2",
|
"@uppy/react": "4.0.2",
|
||||||
"@uppy/status-bar": "4.0.3",
|
"@uppy/status-bar": "4.0.3",
|
||||||
|
"@wangeditor/editor": "^5.1.23",
|
||||||
|
"@wangeditor/editor-for-react": "^1.0.6",
|
||||||
"ahooks": "^3.7.6",
|
"ahooks": "^3.7.6",
|
||||||
"antd": "^5.12.2",
|
"antd": "^5.12.2",
|
||||||
"axios": "^1.3.4",
|
"axios": "^1.3.4",
|
||||||
|
|||||||
@ -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 {
|
.chapter-main-body {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: auto;
|
height: auto;
|
||||||
@ -7,6 +16,7 @@
|
|||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
.left-box {
|
.left-box {
|
||||||
width: 300px;
|
width: 300px;
|
||||||
@ -14,13 +24,13 @@
|
|||||||
height: auto;
|
height: auto;
|
||||||
min-height: calc(100vh - 172px);
|
min-height: calc(100vh - 172px);
|
||||||
border-right: 2px solid #f6f6f6;
|
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;
|
box-sizing: border-box;
|
||||||
padding: 24px 16px;
|
padding: 24px 16px;
|
||||||
background-color: white;
|
background-color: white;
|
||||||
}
|
}
|
||||||
.right-box {
|
.right-box {
|
||||||
width: calc(100% - 354px);
|
width: calc(100% - 301px);
|
||||||
float: left;
|
float: left;
|
||||||
height: auto;
|
height: auto;
|
||||||
min-height: calc(100vh - 172px);
|
min-height: calc(100vh - 172px);
|
||||||
|
|||||||
@ -4,10 +4,8 @@ import React, { useEffect, useState } from 'react';
|
|||||||
import { ChapterTree } from './compenents/chapterTree';
|
import { ChapterTree } from './compenents/chapterTree';
|
||||||
import { BackBartment } from '../../compenents';
|
import { BackBartment } from '../../compenents';
|
||||||
import styles from './chapter.module.less';
|
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 { GetChapterListApi } from '../../api/textbook';
|
||||||
|
import TextbookEditor from './compenents/TextEditor/TextbookEditor';
|
||||||
|
|
||||||
export interface ChapterItemModel {
|
export interface ChapterItemModel {
|
||||||
created_at: string;
|
created_at: string;
|
||||||
@ -62,8 +60,10 @@ const ChapterManagementPage = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="playedu-main-body">
|
<div className={styles['chapter-box']}>
|
||||||
<BackBartment title={t('textbook.chapter.management')} />
|
<div style={{ margin: 24 }}>
|
||||||
|
<BackBartment title={t('textbook.chapter.management')} />
|
||||||
|
</div>
|
||||||
<div className={styles['chapter-main-body']}>
|
<div className={styles['chapter-main-body']}>
|
||||||
<div className={styles['left-box']}>
|
<div className={styles['left-box']}>
|
||||||
<ChapterTree
|
<ChapterTree
|
||||||
@ -77,13 +77,13 @@ const ChapterManagementPage = () => {
|
|||||||
</div>
|
</div>
|
||||||
<div className={styles['right-box']}>
|
<div className={styles['right-box']}>
|
||||||
{/*{selectedChapter ? (*/}
|
{/*{selectedChapter ? (*/}
|
||||||
<EnhancedTextbookEditor
|
<TextbookEditor
|
||||||
chapterId={selectedChapter?.id || 22}
|
chapterId={selectedChapter?.id || 22}
|
||||||
chapterTitle={selectedChapter?.name || '测试数据'}
|
chapterTitle={selectedChapter?.name || '测试数据'}
|
||||||
initialContent="请编写内容"
|
initialContent="请编写内容"
|
||||||
onSave={onSave}
|
onSave={onSave}
|
||||||
onContentChange={onContentChange}
|
onContentChange={onContentChange}
|
||||||
></EnhancedTextbookEditor>
|
></TextbookEditor>
|
||||||
{/* : (
|
{/* : (
|
||||||
<div>点击左侧目录选择</div>
|
<div>点击左侧目录选择</div>
|
||||||
)}*/}
|
)}*/}
|
||||||
|
|||||||
@ -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<EditorProps> = ({
|
|
||||||
chapterId,
|
|
||||||
chapterTitle,
|
|
||||||
initialContent = '',
|
|
||||||
onSave,
|
|
||||||
onContentChange,
|
|
||||||
}) => {
|
|
||||||
const [isEditing, setIsEditing] = useState(false);
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
// 保留
|
|
||||||
/* const [wordCount, setWordCount] = useState(0);
|
|
||||||
const [lastSaved, setLastSaved] = useState<Date | null>(null);*/
|
|
||||||
|
|
||||||
// 初始化编辑器
|
|
||||||
const editor = useEditor({
|
|
||||||
extensions,
|
|
||||||
content:
|
|
||||||
initialContent ||
|
|
||||||
`
|
|
||||||
<h2>${chapterTitle}</h2>
|
|
||||||
<p>开始编写本章节的内容...</p>
|
|
||||||
`,
|
|
||||||
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 ||
|
|
||||||
`
|
|
||||||
<h2>${chapterTitle}</h2>
|
|
||||||
<p>开始编写本章节的内容...</p>
|
|
||||||
`
|
|
||||||
);
|
|
||||||
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 (
|
|
||||||
<div className="enhanced-textbook-editor empty-state">
|
|
||||||
<div className="empty-content">
|
|
||||||
<div className="empty-icon"></div>
|
|
||||||
<h3>请选择章节</h3>
|
|
||||||
<p>从左侧目录中选择一个章节开始编辑</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!editor) {
|
|
||||||
return <div>编辑器加载中...</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="enhanced-textbook-editor">
|
|
||||||
{/* 编辑器头部 */}
|
|
||||||
<div className="editor-header">
|
|
||||||
<div className="header-left">
|
|
||||||
<h2 className="chapter-title">{chapterTitle}</h2>
|
|
||||||
<span className="chapter-id">所属章节: {chapterId}</span>
|
|
||||||
</div>
|
|
||||||
<div className="header-right">
|
|
||||||
{/*<div className="editor-stats">
|
|
||||||
<span className="word-count">字数: {wordCount}</span>
|
|
||||||
{isEditing && <span className="editing-indicator">● 编辑中</span>}
|
|
||||||
<span className="last-saved">最后保存: {formatTime(lastSaved)}</span>
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
className={`save-button ${isEditing ? 'has-changes' : ''}`}
|
|
||||||
onClick={handleManualSave}
|
|
||||||
disabled={!isEditing}
|
|
||||||
>
|
|
||||||
{isEditing ? '保存更改' : '已保存'}
|
|
||||||
</button>*/}
|
|
||||||
<Button style={{ marginRight: 15 }} onClick={handlePreview}>
|
|
||||||
<EyeFilled />
|
|
||||||
{t('textbook.resource.btnPreview')}
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
type="primary"
|
|
||||||
style={{ marginRight: 15 }}
|
|
||||||
onClick={() => {
|
|
||||||
alert('知识图谱');
|
|
||||||
}}
|
|
||||||
disabled={true}
|
|
||||||
>
|
|
||||||
<ForkOutlined />
|
|
||||||
{t('textbook.resource.knowledge')}
|
|
||||||
</Button>
|
|
||||||
<Button type="primary" style={{ marginRight: 15 }} onClick={handleManualSave}>
|
|
||||||
<SaveFilled />
|
|
||||||
{t('textbook.resource.btnSave')}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="editor-main-box">
|
|
||||||
{/* 工具栏 */}
|
|
||||||
<div className="toolbar-container">
|
|
||||||
<EnhancedToolbar editor={editor} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* 编辑器内容 */}
|
|
||||||
<div className="editor-container">
|
|
||||||
<EditorContent editor={editor} />
|
|
||||||
</div>
|
|
||||||
{/* 状态栏 */}
|
|
||||||
<div className="editor-status">
|
|
||||||
<div className="status-info">
|
|
||||||
<span> 章节编辑器</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default EnhancedTextbookEditor;
|
|
||||||
@ -78,42 +78,6 @@
|
|||||||
font-weight: 500;
|
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;
|
background: #8F8F8F;
|
||||||
position: relative;
|
position: relative;
|
||||||
margin: 20px;
|
margin: 20px;
|
||||||
height: 640px;
|
height: 590px;
|
||||||
overflow-y: scroll;
|
overflow: hidden;
|
||||||
overflow-x: hidden;
|
|
||||||
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.1), 0 6px 20px 0 rgba(0, 0, 0, 0.1);
|
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;
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
@ -134,11 +97,6 @@
|
|||||||
background: #f8f9fa;
|
background: #f8f9fa;
|
||||||
border-bottom: 1px solid #e1e5e9;
|
border-bottom: 1px solid #e1e5e9;
|
||||||
padding: 12px 16px;
|
padding: 12px 16px;
|
||||||
position: sticky;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
z-index: 100;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.enhanced-toolbar {
|
.enhanced-toolbar {
|
||||||
@ -204,92 +162,6 @@
|
|||||||
background: white;
|
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 {
|
.editor-status {
|
||||||
padding: 8px 16px;
|
padding: 8px 16px;
|
||||||
@ -0,0 +1,155 @@
|
|||||||
|
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<EditorProps> = ({
|
||||||
|
chapterId,
|
||||||
|
chapterTitle,
|
||||||
|
initialContent = '',
|
||||||
|
onSave,
|
||||||
|
}) => {
|
||||||
|
const [isEditing, setIsEditing] = useState(false);
|
||||||
|
const { t } = useTranslation();
|
||||||
|
// editor 实例
|
||||||
|
const [editor, setEditor] = useState<IDomEditor | null>(null); // TS 语法
|
||||||
|
|
||||||
|
// 编辑器内容
|
||||||
|
const [html, setHtml] = useState('<p>hello</p>');
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (editor && initialContent !== editor.getHtml()) {
|
||||||
|
editor.insertText(
|
||||||
|
// 添加内容
|
||||||
|
initialContent ||
|
||||||
|
`
|
||||||
|
<h2>${chapterTitle}</h2>
|
||||||
|
<p>开始编写本章节的内容...</p>
|
||||||
|
`
|
||||||
|
);
|
||||||
|
setIsEditing(false);
|
||||||
|
|
||||||
|
// 更新统计
|
||||||
|
// const text = editor.getText();
|
||||||
|
// setWordCount(text.split(/\s+/).filter((word) => word.length > 0).length);
|
||||||
|
}
|
||||||
|
}, [initialContent, editor, chapterTitle]);
|
||||||
|
|
||||||
|
// 工具栏配置
|
||||||
|
const toolbarConfig: Partial<IToolbarConfig> = {}; // TS 语法
|
||||||
|
|
||||||
|
// 编辑器配置
|
||||||
|
const editorConfig: Partial<IEditorConfig> = {
|
||||||
|
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 (
|
||||||
|
<div className="enhanced-textbook-editor empty-state">
|
||||||
|
<div className="empty-content">
|
||||||
|
<div className="empty-icon"></div>
|
||||||
|
<h3>请选择章节</h3>
|
||||||
|
<p>从左侧目录中选择一个章节开始编辑</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="enhanced-textbook-editor">
|
||||||
|
{/* 编辑器头部 */}
|
||||||
|
<div className="editor-header">
|
||||||
|
<div className="header-left">
|
||||||
|
<h2 className="chapter-title">{chapterTitle}</h2>
|
||||||
|
<span className="chapter-id">所属章节: {chapterId}</span>
|
||||||
|
</div>
|
||||||
|
<div className="header-right">
|
||||||
|
<Button style={{ marginRight: 10 }} onClick={handlePreview}>
|
||||||
|
<EyeFilled />
|
||||||
|
{t('textbook.resource.btnPreview')}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
style={{ marginRight: 10 }}
|
||||||
|
onClick={() => {
|
||||||
|
alert('知识图谱');
|
||||||
|
}}
|
||||||
|
disabled={true}
|
||||||
|
>
|
||||||
|
<ForkOutlined />
|
||||||
|
{t('textbook.resource.knowledge')}
|
||||||
|
</Button>
|
||||||
|
<Button type="primary" style={{ marginRight: 5 }} onClick={handleManualSave}>
|
||||||
|
<SaveFilled />
|
||||||
|
{t('textbook.resource.btnSave')}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="editor-main-box">
|
||||||
|
<Toolbar
|
||||||
|
editor={editor}
|
||||||
|
defaultConfig={toolbarConfig}
|
||||||
|
mode="default"
|
||||||
|
style={{ borderBottom: '1px solid #ccc' }}
|
||||||
|
/>
|
||||||
|
<Editor
|
||||||
|
defaultConfig={editorConfig}
|
||||||
|
value={html}
|
||||||
|
onCreated={setEditor}
|
||||||
|
onChange={(editor) => setHtml(editor.getHtml())}
|
||||||
|
mode="default"
|
||||||
|
style={{ height: '550px', overflowY: 'hidden' }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TextbookEditor;
|
||||||
@ -118,8 +118,6 @@ export const CreateTextbook: React.FC<CourseCreateProps> = ({ editId, isEdit, op
|
|||||||
// 接口位置
|
// 接口位置
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
if (isEdit) {
|
if (isEdit) {
|
||||||
console.log(thumb, 'thumb');
|
|
||||||
console.log('thumb 类型:', typeof thumb);
|
|
||||||
UpdateTextbookApi(
|
UpdateTextbookApi(
|
||||||
editId,
|
editId,
|
||||||
values.title,
|
values.title,
|
||||||
@ -144,8 +142,6 @@ export const CreateTextbook: React.FC<CourseCreateProps> = ({ editId, isEdit, op
|
|||||||
setLoading(false);
|
setLoading(false);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
console.log(thumb, 'thumb');
|
|
||||||
console.log('thumb 类型:', typeof thumb);
|
|
||||||
CreateTextbookApi(
|
CreateTextbookApi(
|
||||||
values.title,
|
values.title,
|
||||||
thumb,
|
thumb,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user