Skip to content
项目
群组
代码片段
帮助
当前项目
正在载入...
登录 / 注册
切换导航面板
C
center-book
项目
项目
详情
活动
周期分析
仓库
仓库
文件
提交
分支
标签
贡献者
图表
比较
统计图
议题
0
议题
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
CI / CD
CI / CD
流水线
作业
日程
统计图
Wiki
Wiki
代码片段
代码片段
成员
成员
折叠边栏
关闭边栏
活动
图像
聊天
创建新问题
作业
提交
问题看板
Open sidebar
EzijingWeb
center-book
Commits
d8033dd5
提交
d8033dd5
authored
8月 24, 2026
作者:
王鹏飞
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
feat: 添加编辑器实例销毁逻辑,优化图片加载处理
上级
fe79c9e1
隐藏空白字符变更
内嵌
并排
正在显示
7 个修改的文件
包含
62 行增加
和
139 行删除
+62
-139
editor-lifecycle.test.tsx
...r/wangeditor-customer/__tests__/editor-lifecycle.test.tsx
+22
-1
customer.jsx
...editor-customer/features/practice/components/customer.jsx
+1
-1
index.less
...dmin/src/components/editor/wangeditor-customer/index.less
+14
-123
WangEditorCustomer.jsx
...s/editor/wangeditor-customer/shell/WangEditorCustomer.jsx
+2
-2
useEditorLifecycle.js
...ts/editor/wangeditor-customer/shell/useEditorLifecycle.js
+9
-0
ManagementListView.tsx
...src/modules/books/management/views/ManagementListView.tsx
+7
-1
oss.js
apps/admin/src/utils/oss.js
+7
-11
没有找到文件。
apps/admin/src/components/editor/wangeditor-customer/__tests__/editor-lifecycle.test.tsx
浏览文件 @
d8033dd5
...
@@ -2,11 +2,32 @@ import { render } from '@testing-library/react'
...
@@ -2,11 +2,32 @@ import { render } from '@testing-library/react'
import
{
createElement
}
from
'react'
import
{
createElement
}
from
'react'
import
{
afterEach
,
describe
,
expect
,
it
,
vi
}
from
'vitest'
import
{
afterEach
,
describe
,
expect
,
it
,
vi
}
from
'vitest'
import
{
useEditorLifecycle
}
from
'@/components/editor/wangeditor-customer/shell/useEditorLifecycle'
import
{
destroyEditorInstance
,
useEditorLifecycle
,
}
from
'@/components/editor/wangeditor-customer/shell/useEditorLifecycle'
describe
(
'useEditorLifecycle'
,
()
=>
{
describe
(
'useEditorLifecycle'
,
()
=>
{
afterEach
(()
=>
vi
.
unstubAllGlobals
())
afterEach
(()
=>
vi
.
unstubAllGlobals
())
it
(
'releases Slate operations and history after destroying an editor'
,
()
=>
{
const
editor
=
{
isDestroyed
:
false
,
operations
:
[{
type
:
'remove_node'
}],
history
:
{
undos
:
[[{
type
:
'remove_node'
}]],
redos
:
[[{
type
:
'insert_node'
}]],
},
destroy
:
vi
.
fn
(),
}
destroyEditorInstance
(
editor
)
expect
(
editor
.
destroy
).
toHaveBeenCalledOnce
()
expect
(
editor
.
operations
).
toEqual
([])
expect
(
editor
.
history
).
toEqual
({
undos
:
[],
redos
:
[]
})
})
it
(
'does not crash while the textarea is still registering'
,
()
=>
{
it
(
'does not crash while the textarea is still registering'
,
()
=>
{
const
frames
=
[]
const
frames
=
[]
vi
.
stubGlobal
(
'requestAnimationFrame'
,
(
callback
)
=>
{
vi
.
stubGlobal
(
'requestAnimationFrame'
,
(
callback
)
=>
{
...
...
apps/admin/src/components/editor/wangeditor-customer/features/practice/components/customer.jsx
浏览文件 @
d8033dd5
...
@@ -344,7 +344,7 @@ const CustomerTopic = (props, ref) => {
...
@@ -344,7 +344,7 @@ const CustomerTopic = (props, ref) => {
// other config options
// other config options
}
}
}
}
mode=
'default'
mode=
'default'
style=
{
{
height
:
'
2
00px'
,
overflowY
:
'hidden'
}
}
style=
{
{
height
:
'
3
00px'
,
overflowY
:
'hidden'
}
}
/>
/>
</
div
>
</
div
>
</
Spin
>
</
Spin
>
...
...
apps/admin/src/components/editor/wangeditor-customer/index.less
浏览文件 @
d8033dd5
...
@@ -35,33 +35,11 @@
...
@@ -35,33 +35,11 @@
.w-e-text-container [data-slate-editor] h2.chapter-item-section {
.w-e-text-container [data-slate-editor] h2.chapter-item-section {
margin: 0 !important;
margin: 0 !important;
}
}
.ant-spin,
.ant-spin-container {
height: 100%;
min-height: 0;
}
.w-e-text-container {
flex: 1;
min-height: 0;
height: auto;
[data-w-e-textarea='true'] {
box-sizing: border-box;
min-height: 100%;
}
}
.w-e-text-container [data-slate-editor] p:not(:empty) {
margin: 15px 0;
span[data-slate-node='text'] {
padding-top: 0px;
}
}
.w-e-text-container [data-slate-editor] p {
.w-e-text-container [data-slate-editor] p {
line-height: 1.5;
line-height: 1.5;
font-size: 18px;
font-size: 18px;
font-family: '黑体';
font-family: '黑体';
margin
-block:
0;
margin
: 15px
0;
span[data-slate-node='text'],
span[data-slate-node='text'],
span[data-slate-node='element'] {
span[data-slate-node='element'] {
...
@@ -69,9 +47,19 @@
...
@@ -69,9 +47,19 @@
height: 100%;
height: 100%;
line-height: 100%;
line-height: 100%;
}
}
span[data-slate-node='text'] {
}
padding-top: 3px;
}
// 块级 void 节点前的 Slate 光标占位段落不应产生一整行空白
.w-e-text-container
[data-slate-editor]
> p:has(> span:only-child > span:only-child > span[data-slate-zero-width]):has(
+ [data-slate-void='true']
) {
height: 0;
margin: 0;
padding: 0;
line-height: 0;
overflow: hidden;
}
}
.w-e-text-container [data-slate-editor] .w-e-image-container {
.w-e-text-container [data-slate-editor] .w-e-image-container {
...
@@ -84,7 +72,6 @@
...
@@ -84,7 +72,6 @@
.w-e-text-container [data-slate-editor] img[alt='icon-inline'] {
.w-e-text-container [data-slate-editor] img[alt='icon-inline'] {
vertical-align: middle;
vertical-align: middle;
display: inline-block;
}
}
// 插入图标后(图片节点 + 文本节点)强制整行居中
// 插入图标后(图片节点 + 文本节点)强制整行居中
...
@@ -114,15 +101,6 @@
...
@@ -114,15 +101,6 @@
display: inline-block;
display: inline-block;
width: 2px;
width: 2px;
}
}
> span:last-child {
span[data-slate-zero-width] {
display: inline-block;
width: 2px;
// &:not(:empty) {
// display: inline;
// }
}
}
> span:first-child {
> span:first-child {
span[data-slate-zero-width] {
span[data-slate-zero-width] {
display: inline;
display: inline;
...
@@ -216,9 +194,6 @@
...
@@ -216,9 +194,6 @@
box-sizing: border-box;
box-sizing: border-box;
padding-bottom: 10px;
padding-bottom: 10px;
align-items: center;
align-items: center;
.ant-divider {
margin-top: 0;
}
.left {
.left {
flex: 1;
flex: 1;
width: calc(100% - 480px);
width: calc(100% - 480px);
...
@@ -235,8 +210,6 @@
...
@@ -235,8 +210,6 @@
}
}
.right {
.right {
display: flex;
display: flex;
right: 0;
top: 0;
align-items: center;
align-items: center;
flex: 0 0 480px;
flex: 0 0 480px;
justify-content: flex-end;
justify-content: flex-end;
...
@@ -247,12 +220,9 @@
...
@@ -247,12 +220,9 @@
.img {
.img {
color: #ab1941;
color: #ab1941;
font-size: 12px;
font-size: 12px;
display: flex;
align-items: center;
height: 22px;
height: 22px;
line-height: 22px;
line-height: 22px;
text-align: right;
text-align: right;
display: block;
img {
img {
width: 20px;
width: 20px;
margin-right: 8px;
margin-right: 8px;
...
@@ -310,10 +280,6 @@
...
@@ -310,10 +280,6 @@
.ant-spin-container {
.ant-spin-container {
width: 100%;
width: 100%;
}
}
.toolbar-customer {
flex: 1;
}
.menu-tabs-key {
.menu-tabs-key {
flex: 0 0 306px;
flex: 0 0 306px;
background: #fafafa;
background: #fafafa;
...
@@ -390,18 +356,6 @@
...
@@ -390,18 +356,6 @@
width: 22px;
width: 22px;
height: 22px;
height: 22px;
border-radius: 3px;
border-radius: 3px;
&.color1 {
background-color: #ab1941;
}
&.color2 {
background-color: #2970f6;
}
&.color3 {
background-color: #2ad882;
}
&.color4 {
background-color: #eb3351;
}
}
}
.type {
.type {
font-size: 14px;
font-size: 14px;
...
@@ -421,62 +375,6 @@
...
@@ -421,62 +375,6 @@
}
}
}
}
}
}
.toolbox-parent {
position: relative;
.custom-bar-box-input {
position: absolute;
z-index: 101;
height: 48px;
padding: 10px 15px;
width: 298px;
top: 165px;
box-sizing: border-box;
.box {
justify-content: space-around;
display: flex;
width: 268px;
}
.customer-box-input-item {
box-sizing: border-box;
text-align: center;
width: 60px;
p {
width: 60px;
text-align: center;
}
.ant-input-number-outlined {
border: none;
outline: none;
width: 60px;
&:hover,
&:focus {
border: none;
outline: none;
}
}
input {
width: 60px;
color: #333;
text-align: center;
border: 1px solid #d9d9d9;
border-radius: 5px;
height: 30px;
line-height: 30px;
background-color: #fff;
&:hover,
&:focus {
border-color: #b83956;
}
}
.text {
color: #999;
font-size: 12px;
margin-top: 4px;
}
}
}
}
}
}
}
}
}
}
...
@@ -539,10 +437,3 @@
...
@@ -539,10 +437,3 @@
pointer-events: none;
pointer-events: none;
}
}
}
}
/*
['bold', 'underline', 'italic', 'through', 'code', 'sub', 'sup', 'clearStyle', 'color', 'bgColor', 'fontSize', 'fontFamily', 'indent', 'delIndent', 'justifyLeft', 'justifyRight', 'justifyCenter', 'justifyJustify', 'lineHeight', 'insertImage', 'deleteImage', 'editImage', 'viewImageLink', 'imageWidth30', 'imageWidth50', 'imageWidth100', 'divider', 'emotion', 'insertLink', 'editLink', 'unLink', 'viewLink', 'codeBlock', 'blockquote', 'headerSelect', 'header1', 'header2', 'header3', 'header4', 'header5', 'todo', 'redo', 'undo', 'fullScreen', 'enter', 'bulletedList', 'numberedList', 'insertTable', 'deleteTable', 'insertTableRow', 'deleteTableRow', 'insertTableCol', 'deleteTableCol', 'tableHeader', 'tableFullWidth', 'insertVideo', 'uploadVideo', 'editVideoSize', 'uploadImage', 'codeSelectLang']
['', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', 'deleteImage', 'editImage', 'viewImageLink', 'imageWidth30', 'imageWidth50', 'imageWidth100', '', '', '', 'editLink', 'unLink', 'viewLink', '', '', 'headerSelect', 'header1', 'header2', 'header3', 'header4', 'header5', '', '', '', '', '', '', '', '', 'deleteTable', 'insertTableRow', 'deleteTableRow', 'insertTableCol', 'deleteTableCol', 'tableHeader', 'tableFullWidth', '', 'codeSelectLang', 'editVideoSize', '', '']
*/
apps/admin/src/components/editor/wangeditor-customer/shell/WangEditorCustomer.jsx
浏览文件 @
d8033dd5
...
@@ -16,7 +16,7 @@ import '@wangeditor/editor/dist/css/style.css' // 引入 css
...
@@ -16,7 +16,7 @@ import '@wangeditor/editor/dist/css/style.css' // 引入 css
import
{
EditorHeader
}
from
'./EditorHeader'
import
{
EditorHeader
}
from
'./EditorHeader'
import
{
EditorSidebar
}
from
'./EditorSidebar'
import
{
EditorSidebar
}
from
'./EditorSidebar'
import
{
useToolbarPresentation
}
from
'./useToolbarPresentation'
import
{
useToolbarPresentation
}
from
'./useToolbarPresentation'
import
{
useEditorLifecycle
}
from
'./useEditorLifecycle'
import
{
destroyEditorInstance
,
useEditorLifecycle
}
from
'./useEditorLifecycle'
import
{
createEditorConfig
,
defaultEditorContent
,
toolbarConfig
}
from
'./editor-config'
import
{
createEditorConfig
,
defaultEditorContent
,
toolbarConfig
}
from
'./editor-config'
import
{
createAutosaveScheduler
}
from
'./autosave'
import
{
createAutosaveScheduler
}
from
'./autosave'
import
{
getEmptyParagraphPath
,
normalizeEmptyParagraph
}
from
'./paragraph-behavior'
import
{
getEmptyParagraphPath
,
normalizeEmptyParagraph
}
from
'./paragraph-behavior'
...
@@ -154,7 +154,7 @@ const WangEditorCustomer = (props, ref) => {
...
@@ -154,7 +154,7 @@ const WangEditorCustomer = (props, ref) => {
editor
.
addMark
(
'fontFamily'
,
'黑体'
)
editor
.
addMark
(
'fontFamily'
,
'黑体'
)
editor
.
addMark
(
'lineHeight'
,
1.5
)
editor
.
addMark
(
'lineHeight'
,
1.5
)
return
()
=>
{
return
()
=>
{
editor
.
destroy
(
)
destroyEditorInstance
(
editor
)
setEditor
(
null
)
setEditor
(
null
)
}
}
},
[
editor
])
},
[
editor
])
...
...
apps/admin/src/components/editor/wangeditor-customer/shell/useEditorLifecycle.js
浏览文件 @
d8033dd5
import
{
useEffect
}
from
'react'
import
{
useEffect
}
from
'react'
export
const
destroyEditorInstance
=
(
editor
)
=>
{
if
(
!
editor
)
return
if
(
!
editor
.
isDestroyed
)
editor
.
destroy
()
if
(
Array
.
isArray
(
editor
.
operations
))
editor
.
operations
.
length
=
0
if
(
Array
.
isArray
(
editor
.
history
?.
undos
))
editor
.
history
.
undos
.
length
=
0
if
(
Array
.
isArray
(
editor
.
history
?.
redos
))
editor
.
history
.
redos
.
length
=
0
}
export
const
useEditorLifecycle
=
({
editor
,
gData
,
disabled
})
=>
{
export
const
useEditorLifecycle
=
({
editor
,
gData
,
disabled
})
=>
{
useEffect
(()
=>
{
useEffect
(()
=>
{
if
(
!
editor
)
return
if
(
!
editor
)
return
...
...
apps/admin/src/modules/books/management/views/ManagementListView.tsx
浏览文件 @
d8033dd5
...
@@ -83,7 +83,13 @@ export default function ManagementListView() {
...
@@ -83,7 +83,13 @@ export default function ManagementListView() {
align
:
'center'
,
align
:
'center'
,
dataIndex
:
'img'
,
dataIndex
:
'img'
,
render
:
(
_
,
record
)
=>
(
render
:
(
_
,
record
)
=>
(
<
Image
alt=
'书籍图片'
width=
{
80
}
height=
{
80
}
src=
{
record
.
img
}
fallback=
{
IMAGE_FALLBACK
}
/>
<
Image
alt=
'书籍图片'
width=
{
80
}
height=
{
80
}
src=
{
record
.
img
||
IMAGE_FALLBACK
}
fallback=
{
IMAGE_FALLBACK
}
/>
),
),
},
},
{
title
:
'作者'
,
key
:
'authors'
,
align
:
'center'
,
dataIndex
:
'authors'
},
{
title
:
'作者'
,
key
:
'authors'
,
align
:
'center'
,
dataIndex
:
'authors'
},
...
...
apps/admin/src/utils/oss.js
浏览文件 @
d8033dd5
...
@@ -9,7 +9,7 @@ const clients = new Map()
...
@@ -9,7 +9,7 @@ const clients = new Map()
async
function
getOSSConfig
()
{
async
function
getOSSConfig
()
{
const
{
data
}
=
await
getSTSToken
()
const
{
data
}
=
await
getSTSToken
()
return
{
return
{
...
data
,
endpoint
:
data
.
endpoint
,
accessKeyId
:
data
.
AccessKeyId
,
accessKeyId
:
data
.
AccessKeyId
,
accessKeySecret
:
data
.
AccessKeySecret
,
accessKeySecret
:
data
.
AccessKeySecret
,
stsToken
:
data
.
SecurityToken
,
stsToken
:
data
.
SecurityToken
,
...
@@ -24,7 +24,10 @@ export async function getOSSClient(bucket = 'zxts-book-file') {
...
@@ -24,7 +24,10 @@ export async function getOSSClient(bucket = 'zxts-book-file') {
bucket
,
bucket
,
...
config
,
...
config
,
timeout
:
180000
,
timeout
:
180000
,
refreshSTSToken
:
()
=>
getOSSConfig
(),
refreshSTSToken
:
async
()
=>
{
const
{
accessKeyId
,
accessKeySecret
,
stsToken
}
=
await
getOSSConfig
()
return
{
accessKeyId
,
accessKeySecret
,
stsToken
}
},
refreshSTSTokenInterval
:
14
*
60
*
1000
,
refreshSTSTokenInterval
:
14
*
60
*
1000
,
})
})
clients
.
set
(
bucket
,
client
)
clients
.
set
(
bucket
,
client
)
...
@@ -35,17 +38,10 @@ function getExtension(file) {
...
@@ -35,17 +38,10 @@ function getExtension(file) {
const
name
=
typeof
file
.
name
===
'string'
?
file
.
name
:
''
const
name
=
typeof
file
.
name
===
'string'
?
file
.
name
:
''
const
fromName
=
name
.
includes
(
'.'
)
?
`.
${
name
.
split
(
'.'
).
pop
().
toLowerCase
()}
`
:
''
const
fromName
=
name
.
includes
(
'.'
)
?
`.
${
name
.
split
(
'.'
).
pop
().
toLowerCase
()}
`
:
''
if
(
fromName
)
return
fromName
if
(
fromName
)
return
fromName
return
(
return
{
'image/jpeg'
:
'.jpg'
,
'image/png'
:
'.png'
,
'video/mp4'
:
'.mp4'
}[(
file
.
type
||
''
).
toLowerCase
()]
||
'.bin'
{
'image/jpeg'
:
'.jpg'
,
'image/png'
:
'.png'
,
'video/mp4'
:
'.mp4'
}[
(
file
.
type
||
''
).
toLowerCase
()
]
||
'.bin'
)
}
}
function
buildObjectName
(
function
buildObjectName
(
file
,
{
pathPrefix
=
dayjs
().
format
(
'YYYY/MM/DD'
),
filenamePrefix
=
'file'
}
=
{})
{
file
,
{
pathPrefix
=
dayjs
().
format
(
'YYYY/MM/DD'
),
filenamePrefix
=
'file'
}
=
{},
)
{
const
filename
=
`
${
filenamePrefix
}
-
${
Date
.
now
()}
-
${
Math
.
random
().
toString
(
36
).
slice
(
2
)}${
getExtension
(
file
)}
`
const
filename
=
`
${
filenamePrefix
}
-
${
Date
.
now
()}
-
${
Math
.
random
().
toString
(
36
).
slice
(
2
)}${
getExtension
(
file
)}
`
return
pathPrefix
?
`
${
pathPrefix
.
replace
(
/
\/
$/
,
''
)}
/
${
filename
}
`
:
filename
return
pathPrefix
?
`
${
pathPrefix
.
replace
(
/
\/
$/
,
''
)}
/
${
filename
}
`
:
filename
}
}
...
...
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论