Skip to content
项目
群组
代码片段
帮助
当前项目
正在载入...
登录 / 注册
切换导航面板
C
center-book
项目
项目
详情
活动
周期分析
仓库
仓库
文件
提交
分支
标签
贡献者
图表
比较
统计图
议题
0
议题
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
CI / CD
CI / CD
流水线
作业
日程
统计图
Wiki
Wiki
代码片段
代码片段
成员
成员
折叠边栏
关闭边栏
活动
图像
聊天
创建新问题
作业
提交
问题看板
Open sidebar
EzijingWeb
center-book
Commits
f16dc6b6
提交
f16dc6b6
authored
7月 22, 2026
作者:
王鹏飞
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
refactor(admin): expose editor shell facade
上级
5754fb5c
显示空白字符变更
内嵌
并排
正在显示
2 个修改的文件
包含
985 行增加
和
982 行删除
+985
-982
index.jsx
apps/admin/src/common/wangeditor-customer/index.jsx
+2
-982
WangEditorCustomer.jsx
...c/common/wangeditor-customer/shell/WangEditorCustomer.jsx
+983
-0
没有找到文件。
apps/admin/src/common/wangeditor-customer/index.jsx
浏览文件 @
f16dc6b6
import
{
useState
,
useEffect
,
useRef
,
forwardRef
,
useImperativeHandle
}
from
"react"
;
import
{
Button
,
Divider
,
Space
,
Modal
}
from
"antd"
;
import
{
EyeOutlined
,
SaveOutlined
,
CloseOutlined
,
HistoryOutlined
}
from
"@ant-design/icons"
;
import
dayjs
from
"dayjs"
;
import
"./utils/iconfont"
;
import
'./index.less'
import
{
useSelector
,
useDispatch
}
from
"react-redux"
;
import
{
setPracticeRandom
}
from
"@/store/modules/editor"
;
// import formulaEditor from 'easy-formula-editor'
import
"./utils/jax.less"
;
import
{
Boot
}
from
"@wangeditor/editor"
;
import
{
registerEditorFeatures
}
from
"./core/register-editor-features"
;
import
{
EditorDialogHost
}
from
"./core/EditorDialogHost"
;
import
{
editorFeatureRegistry
}
from
"./features"
;
import
{
SlateEditor
,
DomEditor
,
SlateElement
,
SlateTransforms
}
from
"@wangeditor/editor"
;
import
{
Editor
,
Toolbar
}
from
"@wangeditor/editor-for-react"
;
import
{
fontFamilyList
,
lineHeightList
}
from
"./utils/setting"
;
import
"@wangeditor/editor/dist/css/style.css"
;
// 引入 css
import
timesave
from
"@/assets/images/timesave.png"
;
import
relativeTime
from
"dayjs/plugin/relativeTime"
;
dayjs
.
extend
(
relativeTime
);
import
ImageModal
from
"./features/image/ImageDialog"
;
import
VideoModal
from
"./features/video/VideoDialog"
;
import
GalleryModal
from
"./features/gallery/GalleryDialog"
;
import
AudioModal
from
"./features/audio/AudioDialog"
;
import
ChapterTitleModal
from
"./features/chapter-structure/TitleDialog"
;
import
ChapterItemModal
from
"./features/chapter-structure/SectionDialog"
;
import
PracticeModal
from
"./features/practice/PracticeInsertDialog"
;
import
FormulaModal
from
"./features/formula/FormulaDialog"
;
import
TooltipModal
from
"./features/tooltip/TooltipDialog"
;
import
LinkModal
from
"./features/link/LinkDialog"
;
import
ExpandModal
from
"./features/expand-read/ExpandReadDialog"
;
import
PracticeSettingModal
from
"./features/practice/PracticeSettingsDialog"
;
import
PreviewModal
from
"./features/preview/PreviewDialog"
;
import
HistoryModal
from
"./features/history/HistoryDialog"
;
import
"./index.less"
;
registerEditorFeatures
(
Boot
);
const
tabsMenu
=
[
{
key
:
"text"
,
title
:
"文本设置"
},
{
key
:
"style"
,
title
:
"样式模版"
},
];
const
WangEditorCustomer
=
(
props
,
ref
)
=>
{
const
{
chapterId
,
bookId
,
contentId
,
html
,
setHtml
,
saveContent
,
gData
,
nowTitle
,
disabled
,
isView
=
false
,
}
=
props
;
const
dispatch
=
useDispatch
();
// 自动保存时间
const
{
autosaveTime
}
=
useSelector
((
state
)
=>
state
.
editor
);
const
toolbarRef
=
useRef
();
const
[
tabKey
,
setTabKey
]
=
useState
(
"text"
);
// editor 实例
const
[
editor
,
setEditor
]
=
useState
(
null
);
const
[
content
,
setContent
]
=
useState
(
html
);
const
saveRef
=
useRef
();
const
[
titleVisible
,
setTitleVisible
]
=
useState
(
false
);
// 章头
const
[
titleInfo
,
setTitleInfo
]
=
useState
({});
const
[
sectionVisible
,
setSectionVisible
]
=
useState
(
false
);
const
[
sectionInfo
,
setSectionInfo
]
=
useState
({});
// 节头
const
[
practiceOpenVisible
,
setPracticeOpenVisible
]
=
useState
(
false
);
// 练习
const
[
practiceVisible
,
setPracticeVisible
]
=
useState
(
false
);
// 练习答题设置
const
[
galleryVisible
,
setGalleryVisible
]
=
useState
(
false
);
// 画廊
const
[
galleryInfo
,
setGalleryInfo
]
=
useState
({});
const
[
imageVisible
,
setImageVisible
]
=
useState
(
false
);
// 插入图片
const
[
imageInfo
,
setImageInfo
]
=
useState
({});
const
[
tooltipVisible
,
setTooltipVisible
]
=
useState
(
false
);
// 气泡框
const
[
tooltipInfo
,
setTooltipInfo
]
=
useState
({});
// 气泡框
const
[
linkVisible
,
setLinkVisible
]
=
useState
(
false
);
// 气泡框
const
[
linkInfo
,
setLinkInfo
]
=
useState
({});
// 气泡框
const
[
expandVisible
,
setExpandVisible
]
=
useState
(
false
);
// 扩展阅读
const
[
expandInfo
,
setExpandInfo
]
=
useState
({});
// 扩展阅读内容
const
[
priviewVisible
,
setPreviewVisible
]
=
useState
(
false
);
const
[
historyVisible
,
setHistoryVisible
]
=
useState
(
false
);
//点击历史
const
[
selectionSize
,
setSelectionSize
]
=
useState
(
16
);
// 当前字号大小
const
[
isOnline
,
setIsOnline
]
=
useState
(
false
);
const
handleLegacyDialog
=
({
key
,
payload
})
=>
{
if
(
key
===
"chapterTitleNum"
)
{
setTitleInfo
(
payload
);
setTitleVisible
(
true
);
}
else
if
(
key
===
"chapterItemNum"
)
{
setSectionInfo
(
payload
);
setSectionVisible
(
true
);
}
else
if
(
key
===
"practiceNum"
)
{
const
{
practiceNum
,
title
,
theme
}
=
payload
;
dispatch
(
setPracticeRandom
({
practiceNum
,
practiceTitle
:
title
,
practiceTheme
:
theme
}));
setPracticeVisible
(
true
);
}
else
if
(
key
===
"galleryNum"
)
{
setGalleryInfo
(
payload
);
setGalleryVisible
(
true
);
}
else
if
(
key
===
"tooltipNum"
)
{
setTooltipInfo
(
payload
);
setTooltipVisible
(
true
);
}
else
if
(
key
===
"linkNum"
)
{
setLinkInfo
(
payload
);
setLinkVisible
(
true
);
}
else
if
(
key
===
"chapterExpand"
)
{
setExpandInfo
(
payload
);
setExpandVisible
(
true
);
}
};
useEffect
(()
=>
{
if
(
!
editor
)
return
;
editor
.
on
(
"wangeditor-customer:legacy-dialog"
,
handleLegacyDialog
);
return
()
=>
{
editor
.
off
(
"wangeditor-customer:legacy-dialog"
,
handleLegacyDialog
);
};
},
[
editor
]);
const
closePanel
=
()
=>
{
dispatch
(
setPracticeRandom
({
practiceNum
:
null
,
practiceTitle
:
null
}));
setPracticeVisible
(
false
);
};
useImperativeHandle
(
ref
,
()
=>
{
return
{
editor
,
};
});
const
listenNodeStyle
=
(
path
)
=>
{
const
children
=
editor
.
children
;
const
getNodeFontSize
=
(
targetNode
)
=>
{
const
element
=
editor
.
toDOMNode
(
targetNode
);
const
textElement
=
element
.
querySelector
(
"[data-slate-string='true']"
);
const
parentNode
=
editor
.
getParentNode
(
targetNode
);
const
fallbackElement
=
parentNode
?
editor
.
toDOMNode
(
parentNode
)
:
element
;
return
window
.
getComputedStyle
(
textElement
||
fallbackElement
).
fontSize
;
};
let
node
=
null
;
if
(
path
[
1
]
===
0
)
{
node
=
children
[
path
[
0
]];
if
(
node
.
children
.
length
>
0
)
{
setSelectionSize
(
parseInt
(
getNodeFontSize
(
node
)));
}
else
{
node
=
children
[
path
[
0
]].
children
[
0
];
if
(
node
.
fontSize
)
{
setSelectionSize
(
parseInt
(
node
.
fontSize
));
}
else
{
setSelectionSize
(
parseInt
(
getNodeFontSize
(
node
)));
}
}
}
else
{
node
=
children
[
path
[
0
]].
children
[
path
[
1
]];
if
(
node
.
fontSize
)
{
setSelectionSize
(
parseInt
(
node
.
fontSize
));
}
else
{
setSelectionSize
(
parseInt
(
getNodeFontSize
(
node
)));
}
}
};
// 工具栏配置
const
toolbarConfig
=
{
toolbarKeys
:
[
"redo"
,
"undo"
,
"|"
,
"fontFamily"
,
"fontSize"
,
"lineHeight"
,
"color"
,
"bold"
,
"italic"
,
"through"
,
"underline"
,
"bgColor"
,
"bulletedList"
,
"numberedList"
,
"indent"
,
"delIndent"
,
"sub"
,
"sup"
,
"justifyLeft"
,
"justifyCenter"
,
"justifyRight"
,
"justifyJustify"
,
"divider"
,
"|"
,
"ImageAuto"
,
"AIImage"
,
// 'ImageAutoOnline',
"GalleryAuto"
,
// 'GalleryAutoOnline',
"VideoAuto"
,
"AIVideo"
,
"AudioAuto"
,
"insertTable"
,
"Icon"
,
"|"
,
"codeBlock"
,
// 代码块
"blockquote"
,
// 引用
"CustomerLink"
,
"FormulaAuto"
,
"ChapterTitle"
,
"ChapterItem"
,
"Practice"
,
"TooltipAuto"
,
"ExpandRead"
,
"RemoveSpaces"
,
"ConvertTooltipType"
,
"|"
,
"AIRewrite"
,
"AIExpand"
,
"AISummary"
,
"AIPolishing"
,
"AIPunctuation"
,
"AIContentInspect"
,
"|"
,
"AIQuestionSingle"
,
"AIQuestionMultiple"
,
"AIQuestionJudge"
,
"AIQuestionGapFill"
,
"AIQuestionOpenEnded"
,
"|"
,
"AIDigitalHuman"
,
"AITranslate"
,
"AISearch"
,
// 'AIWrite',
// 'AIBaiduSearch',
],
};
const
imageRef
=
useRef
();
const
galleryRef
=
useRef
();
const
videoRef
=
useRef
();
const
audioRef
=
useRef
();
const
formulaRef
=
useRef
();
const
tooltipRef
=
useRef
();
const
chapterTitleRef
=
useRef
();
const
chapterItemRef
=
useRef
();
const
practiceRef
=
useRef
();
const
practiceSettingRef
=
useRef
();
const
previewRef
=
useRef
();
const
jsonContent
=
[
{
type
:
"paragraph"
,
lineHeight
:
"1.5"
,
children
:
[{
text
:
""
,
fontFamily
:
"黑体"
,
fontSize
:
"18px"
}],
},
];
// 编辑器配置
let
editorConfig
=
{
placeholder
:
"请输入内容..."
,
hoverbarKeys
:
{
text
:
{
menuKeys
:
[
"CustomerLink"
,
"bulletedList"
,
"numberedList"
,
"|"
,
"bold"
,
"italic"
,
"through"
,
"underline"
,
"sub"
,
"sup"
,
"|"
,
"color"
,
"bgColor"
,
"clearStyle"
,
"AIChat"
,
],
},
image
:
{
menuKeys
:
[
"imageWidth30"
,
"imageWidth50"
,
"imageWidth100"
,
"ImageEditor"
,
"deleteImage"
],
},
ImageAuto
:
{
menuKeys
:
[
"imageWidthChpater100"
,
"imageWidthChpater50"
,
"imageWidthChpater30"
,
"convertToLinkCard"
,
],
},
},
MENU_CONF
:
{
fontSize
:
{
fontSizeList
:
[
{
name
:
"初号"
,
value
:
"56px"
},
{
name
:
"小初"
,
value
:
"48px"
},
{
name
:
"一号"
,
value
:
"34px"
},
{
name
:
"小一"
,
value
:
"32px"
},
{
name
:
"二号"
,
value
:
"29px"
},
{
name
:
"小二"
,
value
:
"24px"
},
{
name
:
"三号"
,
value
:
"21px"
},
{
name
:
"小三"
,
value
:
"20px"
},
{
name
:
"四号"
,
value
:
"18px"
},
{
name
:
"小四"
,
value
:
"16px"
},
{
name
:
"五号"
,
value
:
"14px"
},
{
name
:
"小五"
,
value
:
"12px"
},
{
name
:
"六号"
,
value
:
"10px"
},
{
name
:
"小六"
,
value
:
"8px"
},
{
name
:
"七号"
,
value
:
"7px"
},
{
name
:
"八号"
,
value
:
"6px"
},
],
},
fontFamily
:
{
fontFamilyList
},
lineHeight
:
{
lineHeightList
},
},
};
// 编辑器按钮重排
const
toolSettingReplace
=
()
=>
{
setTimeout
(()
=>
{
const
editorToolbar
=
document
.
querySelector
(
".editor-toolbar-container"
);
if
(
!
editorToolbar
)
return
;
// 设置菜单模块标题
const
dividerElements
=
editorToolbar
.
querySelectorAll
(
".w-e-bar-divider"
);
const
dividerTitles
=
[
"常用格式"
,
"媒体资源"
,
"高级模块"
,
"AI文本辅助"
,
"AI试题辅助"
,
"更多AI功能"
,
];
dividerElements
.
forEach
((
element
,
index
)
=>
{
element
.
innerHTML
=
dividerTitles
[
index
];
});
// 设置菜单标题
const
menuButtonElements
=
editorToolbar
.
querySelectorAll
(
".w-e-bar-item button"
);
menuButtonElements
.
forEach
((
element
,
index
)
=>
{
if
(
index
>
1
&&
index
<
22
)
return
;
const
width
=
index
===
0
||
index
===
1
?
"50%"
:
"25%"
;
element
.
parentElement
.
style
.
width
=
width
;
element
.
classList
.
add
(
"has-title"
);
const
title
=
element
.
getAttribute
(
"data-tooltip"
);
const
span
=
document
.
createElement
(
"span"
);
span
.
innerHTML
=
title
;
span
.
className
=
"title"
;
element
.
appendChild
(
span
);
});
},
50
);
};
editorConfig
.
onFocus
=
()
=>
{
clearTimeout
(
saveRef
.
current
);
};
editorConfig
.
onBlur
=
(
editor
)
=>
{
// 失焦保存
setHtml
(
editor
.
getHtml
());
setContent
(
editor
.
getHtml
());
};
editorConfig
.
onChange
=
(
editor
)
=>
{
setHtml
(
editor
.
getHtml
());
setContent
(
editor
.
getHtml
());
};
// 及时销毁 editor ,重要!
useEffect
(()
=>
{
if
(
editor
)
{
toolSettingReplace
();
// 图片上传
editor
.
on
(
"ImageMenuClick"
,
()
=>
{
console
.
log
(
"ImageMenuClick"
,
"----"
);
setIsOnline
(
false
);
setImageVisible
(
true
);
});
// 在线图片
editor
.
on
(
"ImageOnlineMenuClick"
,
()
=>
{
console
.
log
(
"ImageOnlineMenuClick"
,
"----"
);
setIsOnline
(
true
);
setImageVisible
(
true
);
});
// 画廊上传
editor
.
on
(
"GalleryMenuClick"
,
()
=>
{
if
(
editor
.
selection
)
{
listenNodeStyle
(
editor
.
selection
.
anchor
.
path
);
}
console
.
log
(
"GalleryMenuClick"
,
"----"
);
// galleryRef.current.setVisible(true);
setIsOnline
(
false
);
setGalleryVisible
(
true
);
});
// 在线画廊
editor
.
on
(
"GalleryOnlineMenuClick"
,
()
=>
{
if
(
editor
.
selection
)
{
listenNodeStyle
(
editor
.
selection
.
anchor
.
path
);
}
console
.
log
(
"GalleryOnlineMenuClick"
,
"----"
);
// galleryRef.current.setVisible(true);
setIsOnline
(
true
);
setGalleryVisible
(
true
);
});
// 视频上传
editor
.
on
(
"VideoMenuClick"
,
()
=>
{
console
.
log
(
"VideoMenuClick"
,
"----"
);
videoRef
.
current
.
setVisible
(
true
);
});
// 音频上传
editor
.
on
(
"AudioMenuClick"
,
()
=>
{
console
.
log
(
"AudioMenuClick"
,
"----"
);
audioRef
.
current
.
setVisible
(
true
);
});
// 章节
editor
.
on
(
"ChapterItemMenuClick"
,
()
=>
{
console
.
log
(
"ChapterItemMenuClick"
,
"----"
);
setSectionVisible
(
true
);
});
// 章头
editor
.
on
(
"ChapterTitleClick"
,
()
=>
{
console
.
log
(
"ChapterTitleClick"
,
"----"
);
setTitleVisible
(
true
);
});
// 交互练习
editor
.
on
(
"PracticeMenuClick"
,
()
=>
{
console
.
log
(
"PracticeMenuClick"
,
"----"
);
setPracticeOpenVisible
(
true
);
});
// 公式
editor
.
on
(
"FormulaMenuClick"
,
()
=>
{
console
.
log
(
"FormulaMenuClick"
,
"----"
);
formulaRef
.
current
.
setVisible
(
true
);
});
// 插入图片
editor
.
on
(
"ImageEditorClick"
,
()
=>
{
console
.
log
(
"ImageEditorClick"
,
"----"
);
const
nodeEntries
=
SlateEditor
.
nodes
(
editor
,
{
match
:
(
node
)
=>
{
// JS syntax
if
(
SlateElement
.
isElement
(
node
))
{
if
(
node
.
type
===
"paragraph"
)
{
return
true
;
// 匹配 paragraph
}
}
return
false
;
},
universal
:
true
,
});
let
info
=
{};
for
(
let
nodeEntry
of
nodeEntries
)
{
const
[
node
,
path
]
=
nodeEntry
;
node
.
children
.
forEach
((
item
)
=>
{
if
(
item
.
type
===
"image"
)
{
info
.
image
=
item
;
}
});
info
.
node
=
node
;
info
.
path
=
path
;
}
setImageInfo
(
info
);
setIsOnline
(
false
);
setImageVisible
(
true
);
});
// 气泡
editor
.
on
(
"TooltipMenuClick"
,
()
=>
{
if
(
editor
.
selection
)
{
listenNodeStyle
(
editor
.
selection
.
anchor
.
path
);
}
setTooltipVisible
(
true
);
});
// 链接
editor
.
on
(
"CustomerLinkClick"
,
()
=>
{
if
(
editor
.
selection
)
{
listenNodeStyle
(
editor
.
selection
.
anchor
.
path
);
}
setLinkVisible
(
true
);
});
// 扩展
editor
.
on
(
"ExpandReadClick"
,
()
=>
{
if
(
editor
.
selection
)
{
listenNodeStyle
(
editor
.
selection
.
anchor
.
path
);
}
setExpandVisible
(
true
);
});
editor
.
addMark
(
"fontSize"
,
"18px"
);
editor
.
addMark
(
"fontFamily"
,
"黑体"
);
editor
.
addMark
(
"lineHeight"
,
1.5
);
setContent
(
html
);
if
(
gData
.
length
>
0
)
{
editor
.
enable
();
editor
.
focus
();
}
else
{
editor
.
disable
();
}
}
return
()
=>
{
if
(
editor
===
null
)
return
;
editor
.
destroy
();
setEditor
(
null
);
};
},
[
editor
]);
useEffect
(()
=>
{
if
(
editor
)
{
if
(
gData
.
length
>
0
&&
!
disabled
)
{
editor
.
enable
();
}
else
{
editor
.
disable
();
}
}
},
[
gData
,
editor
,
disabled
]);
const
tabKeyChange
=
(
key
)
=>
{
if
(
key
===
"text"
)
{
toolSettingReplace
();
}
setTabKey
(
key
);
editor
.
focus
();
};
// 预览
const
previewIt
=
async
()
=>
{
if
(
!
isView
)
{
await
saveContent
();
}
setPreviewVisible
(
true
);
};
// 历史
const
historyIt
=
async
()
=>
{
setHistoryVisible
(
true
);
};
const
colorList
=
[
{
color
:
"#ab1941"
,
name
:
"默认"
},
{
color
:
"#2970f6"
,
name
:
"蓝色"
},
{
color
:
"#2ad882"
,
name
:
"绿色"
},
{
color
:
"#eb3351"
,
name
:
"红色"
},
];
const
setColor
=
(
color
)
=>
{
const
headers
=
document
.
querySelectorAll
(
`.w-e-scroll .chapter-item-header`
);
const
sections
=
document
.
querySelectorAll
(
`.w-e-scroll .chapter-item-section`
);
headers
.
forEach
((
item
)
=>
{
const
node
=
DomEditor
.
toSlateNode
(
editor
,
item
);
const
path
=
DomEditor
.
findPath
(
editor
,
node
);
SlateTransforms
.
setNodes
(
editor
,
{
...
node
,
textColor
:
"#ffffff"
,
bgColor
:
color
},
{
at
:
path
},
);
});
sections
.
forEach
((
item
)
=>
{
const
node
=
DomEditor
.
toSlateNode
(
editor
,
item
);
const
path
=
DomEditor
.
findPath
(
editor
,
node
);
SlateTransforms
.
setNodes
(
editor
,
{
...
node
,
textColor
:
"#ffffff"
,
bgColor
:
color
},
{
at
:
path
},
);
});
};
return
(
<
div
className=
"wangeditor-customer-container"
>
<
div
className=
"editor-content-container"
>
<
div
className=
"title-head"
>
<
div
className=
"left"
>
<
h4
>
{
nowTitle
}
</
h4
>
</
div
>
<
div
className=
"right"
>
<
Space
>
{
!
isView
&&
(
<>
<
div
className=
"save-time"
>
<
div
className=
"img"
>
<
img
src=
{
timesave
}
/>
<
span
>
自动保存
</
span
>
</
div
>
<
span
className=
"time"
>
{
autosaveTime
>
0
?
`上次保存发生在 ${dayjs(autosaveTime).format("YYYY-MM-DD HH:mm:ss")}`
:
""
}
</
span
>
</
div
>
<
Button
type=
"primary"
icon=
{
<
SaveOutlined
/>
}
className=
"view"
onClick=
{
saveContent
}
style=
{
{
color
:
"#fff"
}
}
disabled=
{
disabled
||
!
contentId
}
>
保存
</
Button
>
</>
)
}
<
Button
icon=
{
<
EyeOutlined
/>
}
className=
"history"
onClick=
{
previewIt
}
disabled=
{
!
contentId
}
>
预览
</
Button
>
{
!
isView
&&
(
<
Button
icon=
{
<
HistoryOutlined
/>
}
className=
"history"
onClick=
{
historyIt
}
disabled=
{
disabled
||
!
contentId
}
>
历史
</
Button
>
)
}
</
Space
>
</
div
>
</
div
>
<
Divider
style=
{
{
margin
:
0
}
}
/>
<
Editor
defaultConfig=
{
editorConfig
}
defaultContent=
{
jsonContent
}
value=
{
content
}
onCreated=
{
setEditor
}
mode=
"default"
style=
{
{
height
:
"calc(100vh - 250px)"
,
overflowY
:
"hidden"
}
}
/>
</
div
>
{
!
isView
&&
(
<
div
className=
"menu-tabs-key"
>
<
div
className=
"tabs"
>
{
tabsMenu
&&
tabsMenu
.
length
&&
tabsMenu
.
map
((
item
)
=>
{
return
(
<
div
className=
{
`tabs-item ${item.key === tabKey ? "active" : ""}`
}
key=
{
item
.
key
}
onClick=
{
()
=>
tabKeyChange
(
item
.
key
)
}
>
{
item
.
title
}
<
span
></
span
>
</
div
>
);
})
}
</
div
>
<
div
className=
"menu-tabs-content"
>
{
tabKey
===
"text"
&&
(
<
div
ref=
{
toolbarRef
}
className=
"toolbox-parent"
>
<
Toolbar
editor=
{
editor
}
defaultConfig=
{
toolbarConfig
}
mode=
"default"
className=
"editor-toolbar-container"
></
Toolbar
>
</
div
>
)
}
{
tabKey
===
"style"
&&
(
<
div
className=
"styletem"
>
<
p
>
样式模板
</
p
>
<
ul
>
{
colorList
.
map
((
item
)
=>
{
return
(
<
li
key=
{
item
.
color
}
>
<
div
className=
"left"
>
<
span
className=
"color"
style=
{
{
backgroundColor
:
item
.
color
}
}
></
span
>
<
b
className=
"type"
>
{
item
.
name
}
</
b
>
</
div
>
<
Button
type=
"link"
className=
"use"
onClick=
{
()
=>
setColor
(
item
.
color
)
}
>
使用
</
Button
>
</
li
>
);
})
}
</
ul
>
</
div
>
)
}
</
div
>
</
div
>
)
}
<
Modal
open=
{
imageVisible
}
footer=
{
null
}
centered
destroyOnHidden
title=
"插入图片"
classNames=
{
{
header
:
"editor-header-customer"
,
body
:
"editor-body-customer"
,
wrapper
:
"editor-wrapper-customer"
,
}
}
maskClosable=
{
false
}
onCancel=
{
()
=>
setImageVisible
(
false
)
}
>
<
ImageModal
ref=
{
imageRef
}
isOnline=
{
isOnline
}
editor=
{
editor
}
setImageVisible=
{
setImageVisible
}
imageInfo=
{
imageInfo
}
setImageInfo=
{
setImageInfo
}
/>
</
Modal
>
<
VideoModal
ref=
{
videoRef
}
editor=
{
editor
}
/>
<
Modal
open=
{
galleryVisible
}
footer=
{
null
}
centered
destroyOnHidden
title=
"插入画廊"
classNames=
{
{
header
:
"editor-header-customer"
,
body
:
"editor-body-customer"
,
wrapper
:
"editor-wrapper-customer"
,
}
}
maskClosable=
{
false
}
onCancel=
{
()
=>
setGalleryVisible
(
false
)
}
width=
"800px"
>
<
GalleryModal
ref=
{
galleryRef
}
isOnline=
{
isOnline
}
editor=
{
editor
}
chapterId=
{
chapterId
}
bookId=
{
bookId
}
setGalleryVisible=
{
setGalleryVisible
}
galleryInfo=
{
galleryInfo
}
setGalleryInfo=
{
setGalleryInfo
}
selectionSize=
{
selectionSize
}
/>
</
Modal
>
<
AudioModal
ref=
{
audioRef
}
editor=
{
editor
}
/>
<
Modal
open=
{
titleVisible
}
footer=
{
null
}
centered
destroyOnHidden
title=
"插入章头"
classNames=
{
{
header
:
"editor-header-customer"
,
body
:
"editor-body-customer"
,
wrapper
:
"editor-wrapper-customer"
,
}
}
maskClosable=
{
false
}
onCancel=
{
()
=>
setTitleVisible
(
false
)
}
>
<
ChapterTitleModal
ref=
{
chapterTitleRef
}
editor=
{
editor
}
setTitleVisible=
{
setTitleVisible
}
titleInfo=
{
titleInfo
}
setTitleInfo=
{
setTitleInfo
}
/>
</
Modal
>
<
Modal
open=
{
sectionVisible
}
footer=
{
null
}
centered
destroyOnHidden
title=
"插入节头"
classNames=
{
{
header
:
"editor-header-customer"
,
body
:
"editor-body-customer"
,
wrapper
:
"editor-wrapper-customer"
,
}
}
maskClosable=
{
false
}
onCancel=
{
()
=>
setSectionVisible
(
false
)
}
>
<
ChapterItemModal
ref=
{
chapterItemRef
}
editor=
{
editor
}
setSectionVisible=
{
setSectionVisible
}
sectionInfo=
{
sectionInfo
}
setSectionInfo=
{
setSectionInfo
}
/>
</
Modal
>
{
/* 插入练习 */
}
<
Modal
open=
{
practiceOpenVisible
}
footer=
{
null
}
centered
destroyOnHidden
title=
"插入交互练习"
classNames=
{
{
header
:
"editor-header-customer"
,
body
:
"editor-body-customer"
,
wrapper
:
"editor-wrapper-customer"
,
}
}
maskClosable=
{
false
}
onCancel=
{
()
=>
setPracticeOpenVisible
(
false
)
}
>
<
PracticeModal
ref=
{
practiceRef
}
chapterId=
{
chapterId
}
bookId=
{
bookId
}
editor=
{
editor
}
setPracticeOpenVisible=
{
setPracticeOpenVisible
}
/>
</
Modal
>
<
FormulaModal
ref=
{
formulaRef
}
editor=
{
editor
}
/>
<
Modal
open=
{
tooltipVisible
}
footer=
{
null
}
centered
destroyOnHidden
title=
"气泡"
classNames=
{
{
header
:
"editor-header-customer"
,
body
:
"editor-body-customer"
,
wrapper
:
"editor-wrapper-customer"
,
}
}
maskClosable=
{
false
}
onCancel=
{
()
=>
setTooltipVisible
(
false
)
}
>
<
TooltipModal
ref=
{
tooltipRef
}
editor=
{
editor
}
chapterId=
{
chapterId
}
bookId=
{
bookId
}
tooltipInfo=
{
tooltipInfo
}
setTooltipInfo=
{
setTooltipInfo
}
setTooltipVisible=
{
setTooltipVisible
}
selectionSize=
{
selectionSize
}
/>
</
Modal
>
<
Modal
open=
{
linkVisible
}
footer=
{
null
}
centered
destroyOnHidden
title=
"链接"
classNames=
{
{
header
:
"editor-header-customer"
,
body
:
"editor-body-customer"
,
wrapper
:
"editor-wrapper-customer"
,
}
}
maskClosable=
{
false
}
onCancel=
{
()
=>
setLinkVisible
(
false
)
}
>
<
LinkModal
ref=
{
tooltipRef
}
editor=
{
editor
}
chapterId=
{
chapterId
}
bookId=
{
bookId
}
gData=
{
gData
}
linkInfo=
{
linkInfo
}
setLinkInfo=
{
setLinkInfo
}
setLinkVisible=
{
setLinkVisible
}
selectionSize=
{
selectionSize
}
/>
</
Modal
>
{
/* 扩展 */
}
<
Modal
open=
{
expandVisible
}
footer=
{
null
}
centered
destroyOnHidden
title=
"扩展阅读"
classNames=
{
{
header
:
"editor-header-customer"
,
body
:
"editor-body-customer"
,
wrapper
:
"editor-wrapper-customer"
,
}
}
maskClosable=
{
false
}
onCancel=
{
()
=>
setExpandVisible
(
false
)
}
width=
"70%"
>
<
ExpandModal
ref=
{
previewRef
}
editor=
{
editor
}
chapterId=
{
chapterId
}
bookId=
{
bookId
}
expandInfo=
{
expandInfo
}
setExpandInfo=
{
setExpandInfo
}
setExpandVisible=
{
setExpandVisible
}
selectionSize=
{
selectionSize
}
/>
</
Modal
>
<
Modal
open=
{
practiceVisible
}
footer=
{
null
}
centered
destroyOnHidden
title=
{
null
}
classNames=
{
{
header
:
"practice-topic-header"
,
body
:
"practice-topic-content"
,
wrapper
:
"practice-topic-modal"
,
}
}
maskClosable=
{
false
}
onCancel=
{
closePanel
}
closeIcon=
{
false
}
width=
"90%"
>
<
PracticeSettingModal
ref=
{
practiceSettingRef
}
nodes=
{
practiceRef
.
current
&&
practiceRef
.
current
.
nodes
?
practiceRef
.
current
.
nodes
:
null
}
chapterId=
{
chapterId
}
bookId=
{
bookId
}
editor=
{
editor
}
closePanel=
{
closePanel
}
/>
</
Modal
>
{
/* 预览 */
}
<
Modal
open=
{
priviewVisible
}
header=
{
null
}
footer=
{
null
}
destroyOnHidden
centered
keyboard=
{
false
}
closeIcon=
{
<
CloseOutlined
style=
{
{
fontSize
:
20
,
color
:
"#fff"
}
}
/>
}
maskClosable=
{
false
}
classNames=
{
{
body
:
"phone-body"
,
wrapper
:
"phone-wrapper"
}
}
wrapClassName=
"wrap-phone-privew"
width=
"494px"
onCancel=
{
()
=>
setPreviewVisible
(
false
)
}
>
<
PreviewModal
ref=
{
previewRef
}
gData=
{
gData
}
editor=
{
editor
}
chapterId=
{
chapterId
}
bookId=
{
bookId
}
nowTitle=
{
nowTitle
}
/>
</
Modal
>
{
/* 历史 */
}
<
Modal
open=
{
historyVisible
}
header=
{
null
}
footer=
{
null
}
destroyOnHidden
centered
keyboard=
{
false
}
closeIcon=
{
false
}
maskClosable=
{
false
}
width=
"90%"
onCancel=
{
()
=>
setHistoryVisible
(
false
)
}
wrapClassName=
"history1"
style=
{
{
padding
:
0
}
}
>
<
HistoryModal
setHistoryVisible=
{
setHistoryVisible
}
chapterId=
{
chapterId
}
bookId=
{
bookId
}
editor=
{
editor
}
saveContent=
{
saveContent
}
setContent=
{
setContent
}
/>
</
Modal
>
<
EditorDialogHost
editor=
{
editor
}
registry=
{
editorFeatureRegistry
}
runtime=
{
{
editor
,
chapterId
,
bookId
,
gData
,
nowTitle
,
saveContent
,
setContent
}
}
/>
</
div
>
);
};
export
default
forwardRef
(
WangEditorCustomer
);
export
{
default
}
from
'./shell/WangEditorCustomer'
apps/admin/src/common/wangeditor-customer/shell/WangEditorCustomer.jsx
0 → 100644
浏览文件 @
f16dc6b6
import
{
useState
,
useEffect
,
useRef
,
forwardRef
,
useImperativeHandle
}
from
"react"
;
import
{
Button
,
Divider
,
Space
,
Modal
}
from
"antd"
;
import
{
EyeOutlined
,
SaveOutlined
,
CloseOutlined
,
HistoryOutlined
}
from
"@ant-design/icons"
;
import
dayjs
from
"dayjs"
;
import
"../utils/iconfont"
;
import
{
useSelector
,
useDispatch
}
from
"react-redux"
;
import
{
setPracticeRandom
}
from
"@/store/modules/editor"
;
// import formulaEditor from 'easy-formula-editor'
import
"../utils/jax.less"
;
import
{
Boot
}
from
"@wangeditor/editor"
;
import
{
registerEditorFeatures
}
from
"../core/register-editor-features"
;
import
{
EditorDialogHost
}
from
"../core/EditorDialogHost"
;
import
{
editorFeatureRegistry
}
from
"../features"
;
import
{
SlateEditor
,
DomEditor
,
SlateElement
,
SlateTransforms
}
from
"@wangeditor/editor"
;
import
{
Editor
,
Toolbar
}
from
"@wangeditor/editor-for-react"
;
import
{
fontFamilyList
,
lineHeightList
}
from
"../utils/setting"
;
import
"@wangeditor/editor/dist/css/style.css"
;
// 引入 css
import
timesave
from
"@/assets/images/timesave.png"
;
import
relativeTime
from
"dayjs/plugin/relativeTime"
;
dayjs
.
extend
(
relativeTime
);
import
ImageModal
from
"../features/image/ImageDialog"
;
import
VideoModal
from
"../features/video/VideoDialog"
;
import
GalleryModal
from
"../features/gallery/GalleryDialog"
;
import
AudioModal
from
"../features/audio/AudioDialog"
;
import
ChapterTitleModal
from
"../features/chapter-structure/TitleDialog"
;
import
ChapterItemModal
from
"../features/chapter-structure/SectionDialog"
;
import
PracticeModal
from
"../features/practice/PracticeInsertDialog"
;
import
FormulaModal
from
"../features/formula/FormulaDialog"
;
import
TooltipModal
from
"../features/tooltip/TooltipDialog"
;
import
LinkModal
from
"../features/link/LinkDialog"
;
import
ExpandModal
from
"../features/expand-read/ExpandReadDialog"
;
import
PracticeSettingModal
from
"../features/practice/PracticeSettingsDialog"
;
import
PreviewModal
from
"../features/preview/PreviewDialog"
;
import
HistoryModal
from
"../features/history/HistoryDialog"
;
import
"../index.less"
;
registerEditorFeatures
(
Boot
);
const
tabsMenu
=
[
{
key
:
"text"
,
title
:
"文本设置"
},
{
key
:
"style"
,
title
:
"样式模版"
},
];
const
WangEditorCustomer
=
(
props
,
ref
)
=>
{
const
{
chapterId
,
bookId
,
contentId
,
html
,
setHtml
,
saveContent
,
gData
,
nowTitle
,
disabled
,
isView
=
false
,
}
=
props
;
const
dispatch
=
useDispatch
();
// 自动保存时间
const
{
autosaveTime
}
=
useSelector
((
state
)
=>
state
.
editor
);
const
toolbarRef
=
useRef
();
const
[
tabKey
,
setTabKey
]
=
useState
(
"text"
);
// editor 实例
const
[
editor
,
setEditor
]
=
useState
(
null
);
const
[
content
,
setContent
]
=
useState
(
html
);
const
saveRef
=
useRef
();
const
[
titleVisible
,
setTitleVisible
]
=
useState
(
false
);
// 章头
const
[
titleInfo
,
setTitleInfo
]
=
useState
({});
const
[
sectionVisible
,
setSectionVisible
]
=
useState
(
false
);
const
[
sectionInfo
,
setSectionInfo
]
=
useState
({});
// 节头
const
[
practiceOpenVisible
,
setPracticeOpenVisible
]
=
useState
(
false
);
// 练习
const
[
practiceVisible
,
setPracticeVisible
]
=
useState
(
false
);
// 练习答题设置
const
[
galleryVisible
,
setGalleryVisible
]
=
useState
(
false
);
// 画廊
const
[
galleryInfo
,
setGalleryInfo
]
=
useState
({});
const
[
imageVisible
,
setImageVisible
]
=
useState
(
false
);
// 插入图片
const
[
imageInfo
,
setImageInfo
]
=
useState
({});
const
[
tooltipVisible
,
setTooltipVisible
]
=
useState
(
false
);
// 气泡框
const
[
tooltipInfo
,
setTooltipInfo
]
=
useState
({});
// 气泡框
const
[
linkVisible
,
setLinkVisible
]
=
useState
(
false
);
// 气泡框
const
[
linkInfo
,
setLinkInfo
]
=
useState
({});
// 气泡框
const
[
expandVisible
,
setExpandVisible
]
=
useState
(
false
);
// 扩展阅读
const
[
expandInfo
,
setExpandInfo
]
=
useState
({});
// 扩展阅读内容
const
[
priviewVisible
,
setPreviewVisible
]
=
useState
(
false
);
const
[
historyVisible
,
setHistoryVisible
]
=
useState
(
false
);
//点击历史
const
[
selectionSize
,
setSelectionSize
]
=
useState
(
16
);
// 当前字号大小
const
[
isOnline
,
setIsOnline
]
=
useState
(
false
);
const
handleLegacyDialog
=
({
key
,
payload
})
=>
{
if
(
key
===
"chapterTitleNum"
)
{
setTitleInfo
(
payload
);
setTitleVisible
(
true
);
}
else
if
(
key
===
"chapterItemNum"
)
{
setSectionInfo
(
payload
);
setSectionVisible
(
true
);
}
else
if
(
key
===
"practiceNum"
)
{
const
{
practiceNum
,
title
,
theme
}
=
payload
;
dispatch
(
setPracticeRandom
({
practiceNum
,
practiceTitle
:
title
,
practiceTheme
:
theme
}));
setPracticeVisible
(
true
);
}
else
if
(
key
===
"galleryNum"
)
{
setGalleryInfo
(
payload
);
setGalleryVisible
(
true
);
}
else
if
(
key
===
"tooltipNum"
)
{
setTooltipInfo
(
payload
);
setTooltipVisible
(
true
);
}
else
if
(
key
===
"linkNum"
)
{
setLinkInfo
(
payload
);
setLinkVisible
(
true
);
}
else
if
(
key
===
"chapterExpand"
)
{
setExpandInfo
(
payload
);
setExpandVisible
(
true
);
}
};
useEffect
(()
=>
{
if
(
!
editor
)
return
;
editor
.
on
(
"wangeditor-customer:legacy-dialog"
,
handleLegacyDialog
);
return
()
=>
{
editor
.
off
(
"wangeditor-customer:legacy-dialog"
,
handleLegacyDialog
);
};
},
[
editor
]);
const
closePanel
=
()
=>
{
dispatch
(
setPracticeRandom
({
practiceNum
:
null
,
practiceTitle
:
null
}));
setPracticeVisible
(
false
);
};
useImperativeHandle
(
ref
,
()
=>
{
return
{
editor
,
};
});
const
listenNodeStyle
=
(
path
)
=>
{
const
children
=
editor
.
children
;
const
getNodeFontSize
=
(
targetNode
)
=>
{
const
element
=
editor
.
toDOMNode
(
targetNode
);
const
textElement
=
element
.
querySelector
(
"[data-slate-string='true']"
);
const
parentNode
=
editor
.
getParentNode
(
targetNode
);
const
fallbackElement
=
parentNode
?
editor
.
toDOMNode
(
parentNode
)
:
element
;
return
window
.
getComputedStyle
(
textElement
||
fallbackElement
).
fontSize
;
};
let
node
=
null
;
if
(
path
[
1
]
===
0
)
{
node
=
children
[
path
[
0
]];
if
(
node
.
children
.
length
>
0
)
{
setSelectionSize
(
parseInt
(
getNodeFontSize
(
node
)));
}
else
{
node
=
children
[
path
[
0
]].
children
[
0
];
if
(
node
.
fontSize
)
{
setSelectionSize
(
parseInt
(
node
.
fontSize
));
}
else
{
setSelectionSize
(
parseInt
(
getNodeFontSize
(
node
)));
}
}
}
else
{
node
=
children
[
path
[
0
]].
children
[
path
[
1
]];
if
(
node
.
fontSize
)
{
setSelectionSize
(
parseInt
(
node
.
fontSize
));
}
else
{
setSelectionSize
(
parseInt
(
getNodeFontSize
(
node
)));
}
}
};
// 工具栏配置
const
toolbarConfig
=
{
toolbarKeys
:
[
"redo"
,
"undo"
,
"|"
,
"fontFamily"
,
"fontSize"
,
"lineHeight"
,
"color"
,
"bold"
,
"italic"
,
"through"
,
"underline"
,
"bgColor"
,
"bulletedList"
,
"numberedList"
,
"indent"
,
"delIndent"
,
"sub"
,
"sup"
,
"justifyLeft"
,
"justifyCenter"
,
"justifyRight"
,
"justifyJustify"
,
"divider"
,
"|"
,
"ImageAuto"
,
"AIImage"
,
// 'ImageAutoOnline',
"GalleryAuto"
,
// 'GalleryAutoOnline',
"VideoAuto"
,
"AIVideo"
,
"AudioAuto"
,
"insertTable"
,
"Icon"
,
"|"
,
"codeBlock"
,
// 代码块
"blockquote"
,
// 引用
"CustomerLink"
,
"FormulaAuto"
,
"ChapterTitle"
,
"ChapterItem"
,
"Practice"
,
"TooltipAuto"
,
"ExpandRead"
,
"RemoveSpaces"
,
"ConvertTooltipType"
,
"|"
,
"AIRewrite"
,
"AIExpand"
,
"AISummary"
,
"AIPolishing"
,
"AIPunctuation"
,
"AIContentInspect"
,
"|"
,
"AIQuestionSingle"
,
"AIQuestionMultiple"
,
"AIQuestionJudge"
,
"AIQuestionGapFill"
,
"AIQuestionOpenEnded"
,
"|"
,
"AIDigitalHuman"
,
"AITranslate"
,
"AISearch"
,
// 'AIWrite',
// 'AIBaiduSearch',
],
};
const
imageRef
=
useRef
();
const
galleryRef
=
useRef
();
const
videoRef
=
useRef
();
const
audioRef
=
useRef
();
const
formulaRef
=
useRef
();
const
tooltipRef
=
useRef
();
const
chapterTitleRef
=
useRef
();
const
chapterItemRef
=
useRef
();
const
practiceRef
=
useRef
();
const
practiceSettingRef
=
useRef
();
const
previewRef
=
useRef
();
const
jsonContent
=
[
{
type
:
"paragraph"
,
lineHeight
:
"1.5"
,
children
:
[{
text
:
""
,
fontFamily
:
"黑体"
,
fontSize
:
"18px"
}],
},
];
// 编辑器配置
let
editorConfig
=
{
placeholder
:
"请输入内容..."
,
hoverbarKeys
:
{
text
:
{
menuKeys
:
[
"CustomerLink"
,
"bulletedList"
,
"numberedList"
,
"|"
,
"bold"
,
"italic"
,
"through"
,
"underline"
,
"sub"
,
"sup"
,
"|"
,
"color"
,
"bgColor"
,
"clearStyle"
,
"AIChat"
,
],
},
image
:
{
menuKeys
:
[
"imageWidth30"
,
"imageWidth50"
,
"imageWidth100"
,
"ImageEditor"
,
"deleteImage"
],
},
ImageAuto
:
{
menuKeys
:
[
"imageWidthChpater100"
,
"imageWidthChpater50"
,
"imageWidthChpater30"
,
"convertToLinkCard"
,
],
},
},
MENU_CONF
:
{
fontSize
:
{
fontSizeList
:
[
{
name
:
"初号"
,
value
:
"56px"
},
{
name
:
"小初"
,
value
:
"48px"
},
{
name
:
"一号"
,
value
:
"34px"
},
{
name
:
"小一"
,
value
:
"32px"
},
{
name
:
"二号"
,
value
:
"29px"
},
{
name
:
"小二"
,
value
:
"24px"
},
{
name
:
"三号"
,
value
:
"21px"
},
{
name
:
"小三"
,
value
:
"20px"
},
{
name
:
"四号"
,
value
:
"18px"
},
{
name
:
"小四"
,
value
:
"16px"
},
{
name
:
"五号"
,
value
:
"14px"
},
{
name
:
"小五"
,
value
:
"12px"
},
{
name
:
"六号"
,
value
:
"10px"
},
{
name
:
"小六"
,
value
:
"8px"
},
{
name
:
"七号"
,
value
:
"7px"
},
{
name
:
"八号"
,
value
:
"6px"
},
],
},
fontFamily
:
{
fontFamilyList
},
lineHeight
:
{
lineHeightList
},
},
};
// 编辑器按钮重排
const
toolSettingReplace
=
()
=>
{
setTimeout
(()
=>
{
const
editorToolbar
=
document
.
querySelector
(
".editor-toolbar-container"
);
if
(
!
editorToolbar
)
return
;
// 设置菜单模块标题
const
dividerElements
=
editorToolbar
.
querySelectorAll
(
".w-e-bar-divider"
);
const
dividerTitles
=
[
"常用格式"
,
"媒体资源"
,
"高级模块"
,
"AI文本辅助"
,
"AI试题辅助"
,
"更多AI功能"
,
];
dividerElements
.
forEach
((
element
,
index
)
=>
{
element
.
innerHTML
=
dividerTitles
[
index
];
});
// 设置菜单标题
const
menuButtonElements
=
editorToolbar
.
querySelectorAll
(
".w-e-bar-item button"
);
menuButtonElements
.
forEach
((
element
,
index
)
=>
{
if
(
index
>
1
&&
index
<
22
)
return
;
const
width
=
index
===
0
||
index
===
1
?
"50%"
:
"25%"
;
element
.
parentElement
.
style
.
width
=
width
;
element
.
classList
.
add
(
"has-title"
);
const
title
=
element
.
getAttribute
(
"data-tooltip"
);
const
span
=
document
.
createElement
(
"span"
);
span
.
innerHTML
=
title
;
span
.
className
=
"title"
;
element
.
appendChild
(
span
);
});
},
50
);
};
editorConfig
.
onFocus
=
()
=>
{
clearTimeout
(
saveRef
.
current
);
};
editorConfig
.
onBlur
=
(
editor
)
=>
{
// 失焦保存
setHtml
(
editor
.
getHtml
());
setContent
(
editor
.
getHtml
());
};
editorConfig
.
onChange
=
(
editor
)
=>
{
setHtml
(
editor
.
getHtml
());
setContent
(
editor
.
getHtml
());
};
// 及时销毁 editor ,重要!
useEffect
(()
=>
{
if
(
editor
)
{
toolSettingReplace
();
// 图片上传
editor
.
on
(
"ImageMenuClick"
,
()
=>
{
console
.
log
(
"ImageMenuClick"
,
"----"
);
setIsOnline
(
false
);
setImageVisible
(
true
);
});
// 在线图片
editor
.
on
(
"ImageOnlineMenuClick"
,
()
=>
{
console
.
log
(
"ImageOnlineMenuClick"
,
"----"
);
setIsOnline
(
true
);
setImageVisible
(
true
);
});
// 画廊上传
editor
.
on
(
"GalleryMenuClick"
,
()
=>
{
if
(
editor
.
selection
)
{
listenNodeStyle
(
editor
.
selection
.
anchor
.
path
);
}
console
.
log
(
"GalleryMenuClick"
,
"----"
);
// galleryRef.current.setVisible(true);
setIsOnline
(
false
);
setGalleryVisible
(
true
);
});
// 在线画廊
editor
.
on
(
"GalleryOnlineMenuClick"
,
()
=>
{
if
(
editor
.
selection
)
{
listenNodeStyle
(
editor
.
selection
.
anchor
.
path
);
}
console
.
log
(
"GalleryOnlineMenuClick"
,
"----"
);
// galleryRef.current.setVisible(true);
setIsOnline
(
true
);
setGalleryVisible
(
true
);
});
// 视频上传
editor
.
on
(
"VideoMenuClick"
,
()
=>
{
console
.
log
(
"VideoMenuClick"
,
"----"
);
videoRef
.
current
.
setVisible
(
true
);
});
// 音频上传
editor
.
on
(
"AudioMenuClick"
,
()
=>
{
console
.
log
(
"AudioMenuClick"
,
"----"
);
audioRef
.
current
.
setVisible
(
true
);
});
// 章节
editor
.
on
(
"ChapterItemMenuClick"
,
()
=>
{
console
.
log
(
"ChapterItemMenuClick"
,
"----"
);
setSectionVisible
(
true
);
});
// 章头
editor
.
on
(
"ChapterTitleClick"
,
()
=>
{
console
.
log
(
"ChapterTitleClick"
,
"----"
);
setTitleVisible
(
true
);
});
// 交互练习
editor
.
on
(
"PracticeMenuClick"
,
()
=>
{
console
.
log
(
"PracticeMenuClick"
,
"----"
);
setPracticeOpenVisible
(
true
);
});
// 公式
editor
.
on
(
"FormulaMenuClick"
,
()
=>
{
console
.
log
(
"FormulaMenuClick"
,
"----"
);
formulaRef
.
current
.
setVisible
(
true
);
});
// 插入图片
editor
.
on
(
"ImageEditorClick"
,
()
=>
{
console
.
log
(
"ImageEditorClick"
,
"----"
);
const
nodeEntries
=
SlateEditor
.
nodes
(
editor
,
{
match
:
(
node
)
=>
{
// JS syntax
if
(
SlateElement
.
isElement
(
node
))
{
if
(
node
.
type
===
"paragraph"
)
{
return
true
;
// 匹配 paragraph
}
}
return
false
;
},
universal
:
true
,
});
let
info
=
{};
for
(
let
nodeEntry
of
nodeEntries
)
{
const
[
node
,
path
]
=
nodeEntry
;
node
.
children
.
forEach
((
item
)
=>
{
if
(
item
.
type
===
"image"
)
{
info
.
image
=
item
;
}
});
info
.
node
=
node
;
info
.
path
=
path
;
}
setImageInfo
(
info
);
setIsOnline
(
false
);
setImageVisible
(
true
);
});
// 气泡
editor
.
on
(
"TooltipMenuClick"
,
()
=>
{
if
(
editor
.
selection
)
{
listenNodeStyle
(
editor
.
selection
.
anchor
.
path
);
}
setTooltipVisible
(
true
);
});
// 链接
editor
.
on
(
"CustomerLinkClick"
,
()
=>
{
if
(
editor
.
selection
)
{
listenNodeStyle
(
editor
.
selection
.
anchor
.
path
);
}
setLinkVisible
(
true
);
});
// 扩展
editor
.
on
(
"ExpandReadClick"
,
()
=>
{
if
(
editor
.
selection
)
{
listenNodeStyle
(
editor
.
selection
.
anchor
.
path
);
}
setExpandVisible
(
true
);
});
editor
.
addMark
(
"fontSize"
,
"18px"
);
editor
.
addMark
(
"fontFamily"
,
"黑体"
);
editor
.
addMark
(
"lineHeight"
,
1.5
);
setContent
(
html
);
if
(
gData
.
length
>
0
)
{
editor
.
enable
();
editor
.
focus
();
}
else
{
editor
.
disable
();
}
}
return
()
=>
{
if
(
editor
===
null
)
return
;
editor
.
destroy
();
setEditor
(
null
);
};
},
[
editor
]);
useEffect
(()
=>
{
if
(
editor
)
{
if
(
gData
.
length
>
0
&&
!
disabled
)
{
editor
.
enable
();
}
else
{
editor
.
disable
();
}
}
},
[
gData
,
editor
,
disabled
]);
const
tabKeyChange
=
(
key
)
=>
{
if
(
key
===
"text"
)
{
toolSettingReplace
();
}
setTabKey
(
key
);
editor
.
focus
();
};
// 预览
const
previewIt
=
async
()
=>
{
if
(
!
isView
)
{
await
saveContent
();
}
setPreviewVisible
(
true
);
};
// 历史
const
historyIt
=
async
()
=>
{
setHistoryVisible
(
true
);
};
const
colorList
=
[
{
color
:
"#ab1941"
,
name
:
"默认"
},
{
color
:
"#2970f6"
,
name
:
"蓝色"
},
{
color
:
"#2ad882"
,
name
:
"绿色"
},
{
color
:
"#eb3351"
,
name
:
"红色"
},
];
const
setColor
=
(
color
)
=>
{
const
headers
=
document
.
querySelectorAll
(
`.w-e-scroll .chapter-item-header`
);
const
sections
=
document
.
querySelectorAll
(
`.w-e-scroll .chapter-item-section`
);
headers
.
forEach
((
item
)
=>
{
const
node
=
DomEditor
.
toSlateNode
(
editor
,
item
);
const
path
=
DomEditor
.
findPath
(
editor
,
node
);
SlateTransforms
.
setNodes
(
editor
,
{
...
node
,
textColor
:
"#ffffff"
,
bgColor
:
color
},
{
at
:
path
},
);
});
sections
.
forEach
((
item
)
=>
{
const
node
=
DomEditor
.
toSlateNode
(
editor
,
item
);
const
path
=
DomEditor
.
findPath
(
editor
,
node
);
SlateTransforms
.
setNodes
(
editor
,
{
...
node
,
textColor
:
"#ffffff"
,
bgColor
:
color
},
{
at
:
path
},
);
});
};
return
(
<
div
className=
"wangeditor-customer-container"
>
<
div
className=
"editor-content-container"
>
<
div
className=
"title-head"
>
<
div
className=
"left"
>
<
h4
>
{
nowTitle
}
</
h4
>
</
div
>
<
div
className=
"right"
>
<
Space
>
{
!
isView
&&
(
<>
<
div
className=
"save-time"
>
<
div
className=
"img"
>
<
img
src=
{
timesave
}
/>
<
span
>
自动保存
</
span
>
</
div
>
<
span
className=
"time"
>
{
autosaveTime
>
0
?
`上次保存发生在 ${dayjs(autosaveTime).format("YYYY-MM-DD HH:mm:ss")}`
:
""
}
</
span
>
</
div
>
<
Button
type=
"primary"
icon=
{
<
SaveOutlined
/>
}
className=
"view"
onClick=
{
saveContent
}
style=
{
{
color
:
"#fff"
}
}
disabled=
{
disabled
||
!
contentId
}
>
保存
</
Button
>
</>
)
}
<
Button
icon=
{
<
EyeOutlined
/>
}
className=
"history"
onClick=
{
previewIt
}
disabled=
{
!
contentId
}
>
预览
</
Button
>
{
!
isView
&&
(
<
Button
icon=
{
<
HistoryOutlined
/>
}
className=
"history"
onClick=
{
historyIt
}
disabled=
{
disabled
||
!
contentId
}
>
历史
</
Button
>
)
}
</
Space
>
</
div
>
</
div
>
<
Divider
style=
{
{
margin
:
0
}
}
/>
<
Editor
defaultConfig=
{
editorConfig
}
defaultContent=
{
jsonContent
}
value=
{
content
}
onCreated=
{
setEditor
}
mode=
"default"
style=
{
{
height
:
"calc(100vh - 250px)"
,
overflowY
:
"hidden"
}
}
/>
</
div
>
{
!
isView
&&
(
<
div
className=
"menu-tabs-key"
>
<
div
className=
"tabs"
>
{
tabsMenu
&&
tabsMenu
.
length
&&
tabsMenu
.
map
((
item
)
=>
{
return
(
<
div
className=
{
`tabs-item ${item.key === tabKey ? "active" : ""}`
}
key=
{
item
.
key
}
onClick=
{
()
=>
tabKeyChange
(
item
.
key
)
}
>
{
item
.
title
}
<
span
></
span
>
</
div
>
);
})
}
</
div
>
<
div
className=
"menu-tabs-content"
>
{
tabKey
===
"text"
&&
(
<
div
ref=
{
toolbarRef
}
className=
"toolbox-parent"
>
<
Toolbar
editor=
{
editor
}
defaultConfig=
{
toolbarConfig
}
mode=
"default"
className=
"editor-toolbar-container"
></
Toolbar
>
</
div
>
)
}
{
tabKey
===
"style"
&&
(
<
div
className=
"styletem"
>
<
p
>
样式模板
</
p
>
<
ul
>
{
colorList
.
map
((
item
)
=>
{
return
(
<
li
key=
{
item
.
color
}
>
<
div
className=
"left"
>
<
span
className=
"color"
style=
{
{
backgroundColor
:
item
.
color
}
}
></
span
>
<
b
className=
"type"
>
{
item
.
name
}
</
b
>
</
div
>
<
Button
type=
"link"
className=
"use"
onClick=
{
()
=>
setColor
(
item
.
color
)
}
>
使用
</
Button
>
</
li
>
);
})
}
</
ul
>
</
div
>
)
}
</
div
>
</
div
>
)
}
<
Modal
open=
{
imageVisible
}
footer=
{
null
}
centered
destroyOnHidden
title=
"插入图片"
classNames=
{
{
header
:
"editor-header-customer"
,
body
:
"editor-body-customer"
,
wrapper
:
"editor-wrapper-customer"
,
}
}
maskClosable=
{
false
}
onCancel=
{
()
=>
setImageVisible
(
false
)
}
>
<
ImageModal
ref=
{
imageRef
}
isOnline=
{
isOnline
}
editor=
{
editor
}
setImageVisible=
{
setImageVisible
}
imageInfo=
{
imageInfo
}
setImageInfo=
{
setImageInfo
}
/>
</
Modal
>
<
VideoModal
ref=
{
videoRef
}
editor=
{
editor
}
/>
<
Modal
open=
{
galleryVisible
}
footer=
{
null
}
centered
destroyOnHidden
title=
"插入画廊"
classNames=
{
{
header
:
"editor-header-customer"
,
body
:
"editor-body-customer"
,
wrapper
:
"editor-wrapper-customer"
,
}
}
maskClosable=
{
false
}
onCancel=
{
()
=>
setGalleryVisible
(
false
)
}
width=
"800px"
>
<
GalleryModal
ref=
{
galleryRef
}
isOnline=
{
isOnline
}
editor=
{
editor
}
chapterId=
{
chapterId
}
bookId=
{
bookId
}
setGalleryVisible=
{
setGalleryVisible
}
galleryInfo=
{
galleryInfo
}
setGalleryInfo=
{
setGalleryInfo
}
selectionSize=
{
selectionSize
}
/>
</
Modal
>
<
AudioModal
ref=
{
audioRef
}
editor=
{
editor
}
/>
<
Modal
open=
{
titleVisible
}
footer=
{
null
}
centered
destroyOnHidden
title=
"插入章头"
classNames=
{
{
header
:
"editor-header-customer"
,
body
:
"editor-body-customer"
,
wrapper
:
"editor-wrapper-customer"
,
}
}
maskClosable=
{
false
}
onCancel=
{
()
=>
setTitleVisible
(
false
)
}
>
<
ChapterTitleModal
ref=
{
chapterTitleRef
}
editor=
{
editor
}
setTitleVisible=
{
setTitleVisible
}
titleInfo=
{
titleInfo
}
setTitleInfo=
{
setTitleInfo
}
/>
</
Modal
>
<
Modal
open=
{
sectionVisible
}
footer=
{
null
}
centered
destroyOnHidden
title=
"插入节头"
classNames=
{
{
header
:
"editor-header-customer"
,
body
:
"editor-body-customer"
,
wrapper
:
"editor-wrapper-customer"
,
}
}
maskClosable=
{
false
}
onCancel=
{
()
=>
setSectionVisible
(
false
)
}
>
<
ChapterItemModal
ref=
{
chapterItemRef
}
editor=
{
editor
}
setSectionVisible=
{
setSectionVisible
}
sectionInfo=
{
sectionInfo
}
setSectionInfo=
{
setSectionInfo
}
/>
</
Modal
>
{
/* 插入练习 */
}
<
Modal
open=
{
practiceOpenVisible
}
footer=
{
null
}
centered
destroyOnHidden
title=
"插入交互练习"
classNames=
{
{
header
:
"editor-header-customer"
,
body
:
"editor-body-customer"
,
wrapper
:
"editor-wrapper-customer"
,
}
}
maskClosable=
{
false
}
onCancel=
{
()
=>
setPracticeOpenVisible
(
false
)
}
>
<
PracticeModal
ref=
{
practiceRef
}
chapterId=
{
chapterId
}
bookId=
{
bookId
}
editor=
{
editor
}
setPracticeOpenVisible=
{
setPracticeOpenVisible
}
/>
</
Modal
>
<
FormulaModal
ref=
{
formulaRef
}
editor=
{
editor
}
/>
<
Modal
open=
{
tooltipVisible
}
footer=
{
null
}
centered
destroyOnHidden
title=
"气泡"
classNames=
{
{
header
:
"editor-header-customer"
,
body
:
"editor-body-customer"
,
wrapper
:
"editor-wrapper-customer"
,
}
}
maskClosable=
{
false
}
onCancel=
{
()
=>
setTooltipVisible
(
false
)
}
>
<
TooltipModal
ref=
{
tooltipRef
}
editor=
{
editor
}
chapterId=
{
chapterId
}
bookId=
{
bookId
}
tooltipInfo=
{
tooltipInfo
}
setTooltipInfo=
{
setTooltipInfo
}
setTooltipVisible=
{
setTooltipVisible
}
selectionSize=
{
selectionSize
}
/>
</
Modal
>
<
Modal
open=
{
linkVisible
}
footer=
{
null
}
centered
destroyOnHidden
title=
"链接"
classNames=
{
{
header
:
"editor-header-customer"
,
body
:
"editor-body-customer"
,
wrapper
:
"editor-wrapper-customer"
,
}
}
maskClosable=
{
false
}
onCancel=
{
()
=>
setLinkVisible
(
false
)
}
>
<
LinkModal
ref=
{
tooltipRef
}
editor=
{
editor
}
chapterId=
{
chapterId
}
bookId=
{
bookId
}
gData=
{
gData
}
linkInfo=
{
linkInfo
}
setLinkInfo=
{
setLinkInfo
}
setLinkVisible=
{
setLinkVisible
}
selectionSize=
{
selectionSize
}
/>
</
Modal
>
{
/* 扩展 */
}
<
Modal
open=
{
expandVisible
}
footer=
{
null
}
centered
destroyOnHidden
title=
"扩展阅读"
classNames=
{
{
header
:
"editor-header-customer"
,
body
:
"editor-body-customer"
,
wrapper
:
"editor-wrapper-customer"
,
}
}
maskClosable=
{
false
}
onCancel=
{
()
=>
setExpandVisible
(
false
)
}
width=
"70%"
>
<
ExpandModal
ref=
{
previewRef
}
editor=
{
editor
}
chapterId=
{
chapterId
}
bookId=
{
bookId
}
expandInfo=
{
expandInfo
}
setExpandInfo=
{
setExpandInfo
}
setExpandVisible=
{
setExpandVisible
}
selectionSize=
{
selectionSize
}
/>
</
Modal
>
<
Modal
open=
{
practiceVisible
}
footer=
{
null
}
centered
destroyOnHidden
title=
{
null
}
classNames=
{
{
header
:
"practice-topic-header"
,
body
:
"practice-topic-content"
,
wrapper
:
"practice-topic-modal"
,
}
}
maskClosable=
{
false
}
onCancel=
{
closePanel
}
closeIcon=
{
false
}
width=
"90%"
>
<
PracticeSettingModal
ref=
{
practiceSettingRef
}
nodes=
{
practiceRef
.
current
&&
practiceRef
.
current
.
nodes
?
practiceRef
.
current
.
nodes
:
null
}
chapterId=
{
chapterId
}
bookId=
{
bookId
}
editor=
{
editor
}
closePanel=
{
closePanel
}
/>
</
Modal
>
{
/* 预览 */
}
<
Modal
open=
{
priviewVisible
}
header=
{
null
}
footer=
{
null
}
destroyOnHidden
centered
keyboard=
{
false
}
closeIcon=
{
<
CloseOutlined
style=
{
{
fontSize
:
20
,
color
:
"#fff"
}
}
/>
}
maskClosable=
{
false
}
classNames=
{
{
body
:
"phone-body"
,
wrapper
:
"phone-wrapper"
}
}
wrapClassName=
"wrap-phone-privew"
width=
"494px"
onCancel=
{
()
=>
setPreviewVisible
(
false
)
}
>
<
PreviewModal
ref=
{
previewRef
}
gData=
{
gData
}
editor=
{
editor
}
chapterId=
{
chapterId
}
bookId=
{
bookId
}
nowTitle=
{
nowTitle
}
/>
</
Modal
>
{
/* 历史 */
}
<
Modal
open=
{
historyVisible
}
header=
{
null
}
footer=
{
null
}
destroyOnHidden
centered
keyboard=
{
false
}
closeIcon=
{
false
}
maskClosable=
{
false
}
width=
"90%"
onCancel=
{
()
=>
setHistoryVisible
(
false
)
}
wrapClassName=
"history1"
style=
{
{
padding
:
0
}
}
>
<
HistoryModal
setHistoryVisible=
{
setHistoryVisible
}
chapterId=
{
chapterId
}
bookId=
{
bookId
}
editor=
{
editor
}
saveContent=
{
saveContent
}
setContent=
{
setContent
}
/>
</
Modal
>
<
EditorDialogHost
editor=
{
editor
}
registry=
{
editorFeatureRegistry
}
runtime=
{
{
editor
,
chapterId
,
bookId
,
gData
,
nowTitle
,
saveContent
,
setContent
}
}
/>
</
div
>
);
};
export
default
forwardRef
(
WangEditorCustomer
);
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论