Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
V
vue3_onlineEditor
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
pangchong
vue3_onlineEditor
Commits
fb7284af
Commit
fb7284af
authored
Apr 01, 2025
by
pangchong
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
refactor: 代码优化
parent
8a5c5306
Hide whitespace changes
Inline
Side-by-side
Showing
14 changed files
with
283 additions
and
474 deletions
+283
-474
src/configs/elem-to-html.ts
+1
-1
src/configs/node.config.ts
+0
-0
src/configs/parse-elem-html.ts
+1
-1
src/configs/render-elem.ts
+1
-1
src/lib/XMLProcessor/src/core/Processing.ts
+3
-4
src/lib/XMLProcessor/src/index.ts
+10
-15
src/utils/index.ts
+5
-0
src/views/editor/components/ContentEditor.vue
+0
-327
src/views/editor/components/Tree.vue
+13
-15
src/views/editor/constants/index.ts
+81
-52
src/views/editor/constants/tree.ts
+7
-0
src/views/editor/functions/index.ts
+56
-33
src/views/editor/functions/tree.ts
+48
-0
src/views/editor/index.vue
+57
-25
No files found.
src/
views/editor/function
s/elem-to-html.ts
→
src/
config
s/elem-to-html.ts
View file @
fb7284af
import
{
IModuleConf
,
SlateElement
}
from
'@wangeditor/editor'
import
{
IModuleConf
,
SlateElement
}
from
'@wangeditor/editor'
import
{
nodeSet
}
from
'@/
views/editor/constants/nodeParsed.ts
'
import
{
nodeSet
}
from
'@/
configs/node.config
'
const
elemToHtml
=
(
type
:
string
)
=>
{
const
elemToHtml
=
(
type
:
string
)
=>
{
return
(
elem
:
SlateElement
,
childrenHtml
:
string
):
string
=>
{
return
(
elem
:
SlateElement
,
childrenHtml
:
string
):
string
=>
{
...
...
src/
views/editor/constants/nodeParsed
.ts
→
src/
configs/node.config
.ts
View file @
fb7284af
File moved
src/
views/editor/function
s/parse-elem-html.ts
→
src/
config
s/parse-elem-html.ts
View file @
fb7284af
import
{
IDomEditor
,
IModuleConf
,
SlateDescendant
,
SlateElement
}
from
'@wangeditor/editor'
import
{
IDomEditor
,
IModuleConf
,
SlateDescendant
,
SlateElement
}
from
'@wangeditor/editor'
import
{
nodeSet
}
from
'@/
views/editor/constants/nodeParsed.ts
'
import
{
nodeSet
}
from
'@/
configs/node.config
'
const
parseElemHtml
=
(
type
:
string
)
=>
{
const
parseElemHtml
=
(
type
:
string
)
=>
{
return
(
domElem
:
Element
,
children
:
SlateDescendant
[],
editor
:
IDomEditor
):
SlateElement
=>
{
return
(
domElem
:
Element
,
children
:
SlateDescendant
[],
editor
:
IDomEditor
):
SlateElement
=>
{
...
...
src/
views/editor/function
s/render-elem.ts
→
src/
config
s/render-elem.ts
View file @
fb7284af
import
{
h
,
VNode
}
from
'snabbdom'
import
{
h
,
VNode
}
from
'snabbdom'
import
{
IDomEditor
,
IModuleConf
,
SlateElement
}
from
'@wangeditor/editor'
import
{
IDomEditor
,
IModuleConf
,
SlateElement
}
from
'@wangeditor/editor'
import
{
nodeSet
}
from
'@/
views/editor/constants/nodeParsed.ts
'
import
{
nodeSet
}
from
'@/
configs/node.config
'
const
renderElem
=
(
type
:
string
,
style
=
{
display
:
'block'
})
=>
{
const
renderElem
=
(
type
:
string
,
style
=
{
display
:
'block'
})
=>
{
return
(
elem
:
SlateElement
,
children
:
VNode
[]
|
null
,
editor
:
IDomEditor
):
VNode
=>
{
return
(
elem
:
SlateElement
,
children
:
VNode
[]
|
null
,
editor
:
IDomEditor
):
VNode
=>
{
...
...
src/lib/XMLProcessor/src/core/Processing.ts
View file @
fb7284af
// 引入 TreeRenderResult 类型,该类型定义在 @/lib/XMLProcessor/src/typing 模块中
// 引入 TreeRenderResult 类型,该类型定义在 @/lib/XMLProcessor/src/typing 模块中
import
{
NewTreeModification
,
OldTreeModification
,
TreeRenderResult
,
TreeRenderResultFlatted
}
from
'@/lib/XMLProcessor/src/typing'
import
{
NewTreeModification
,
OldTreeModification
,
TreeRenderResult
,
TreeRenderResultFlatted
}
from
'@/lib/XMLProcessor/src/typing'
// 引入 UUID 类用于生成唯一标识符
import
{
UUID
}
from
'uuidjs'
import
{
md5
}
from
'js-md5'
import
{
md5
}
from
'js-md5'
import
{
getUUID
}
from
'@/utils'
// 定义 Processing 类,用于处理 XML 数据
// 定义 Processing 类,用于处理 XML 数据
export
class
Processing
{
export
class
Processing
{
...
@@ -185,7 +184,7 @@ export class Processing {
...
@@ -185,7 +184,7 @@ export class Processing {
// 生成唯一的 key
// 生成唯一的 key
let
targetKey
=
domNode
.
getAttribute
(
'data-key'
)
let
targetKey
=
domNode
.
getAttribute
(
'data-key'
)
if
(
!
targetKey
)
{
if
(
!
targetKey
)
{
targetKey
=
'g-'
+
UUID
.
generate
()
targetKey
=
getUUID
()
}
}
// 创建树形数据项
// 创建树形数据项
const
treeItem
:
TreeRenderResult
=
{
const
treeItem
:
TreeRenderResult
=
{
...
@@ -227,7 +226,7 @@ export class Processing {
...
@@ -227,7 +226,7 @@ export class Processing {
// 生成唯一的 key
// 生成唯一的 key
let
targetKey
=
node
.
getAttribute
(
'data-key'
)
let
targetKey
=
node
.
getAttribute
(
'data-key'
)
if
(
!
targetKey
)
{
if
(
!
targetKey
)
{
targetKey
=
'g-'
+
UUID
.
generate
()
targetKey
=
getUUID
()
}
}
// 创建树形数据项
// 创建树形数据项
const
treeItem
:
TreeRenderResult
=
{
const
treeItem
:
TreeRenderResult
=
{
...
...
src/lib/XMLProcessor/src/index.ts
View file @
fb7284af
import
{
Processing
}
from
'@/lib/XMLProcessor/src/core/Processing.ts'
import
{
Processing
}
from
'@/lib/XMLProcessor/src/core/Processing.ts'
import
{
Plugin
,
getCurrentInstance
}
from
'vue'
import
{
Plugin
,
getCurrentInstance
}
from
'vue'
import
TextA
from
"@/assets/file/CES-QEC-V250-A.xml?raw"
import
TextA
from
'@/assets/file/CES-QEC-V250-A.xml?raw'
import
TextB
from
"@/assets/file/Trans-Convert.xml?raw"
import
TextB
from
'@/assets/file/Trans-Convert.xml?raw'
import
{
nodeSet
}
from
'@/views/editor/constants/nodeParsed.ts
'
import
{
nodeSet
}
from
'@/configs/node.config
'
const
p
=
new
Processing
()
const
p
=
new
Processing
()
// @ts-ignore
// @ts-ignore
window
.
$p
=
function
()
{
window
.
$p
=
function
()
{
let
nodeV
=
p
.
dualCompareFromString
(
let
nodeV
=
p
.
dualCompareFromString
(
TextA
,
TextB
,
nodeSet
)
TextA
,
TextB
,
nodeSet
)
console
.
log
(
'v-h'
,
nodeV
)
console
.
log
(
'v-h'
,
nodeV
)
}
}
export
const
XMLProcessing
:
Plugin
=
function
(
app
)
{
export
const
XMLProcessing
:
Plugin
=
function
(
app
)
{
app
.
provide
(
'xmlProcessing'
,
p
)
;
app
.
provide
(
'xmlProcessing'
,
p
)
}
}
export
function
useXMLProcessing
():
Processing
{
export
function
useXMLProcessing
():
Processing
{
...
@@ -23,4 +19,4 @@ export function useXMLProcessing(): Processing {
...
@@ -23,4 +19,4 @@ export function useXMLProcessing(): Processing {
return
<
Processing
>
inject
(
'xmlProcessing'
)
return
<
Processing
>
inject
(
'xmlProcessing'
)
}
}
return
p
return
p
}
}
\ No newline at end of file
src/utils/index.ts
0 → 100644
View file @
fb7284af
import
{
UUID
}
from
'uuidjs'
export
const
getUUID
=
()
=>
{
return
'g-'
+
UUID
.
generate
()
}
src/views/editor/components/ContentEditor.vue
deleted
100644 → 0
View file @
8a5c5306
<
template
>
<div
class=
"z-10 h-full flex flex-col"
>
<!-- 工具栏 -->
<div
class=
"flex"
>
<div
class=
"flex-1"
>
<Toolbar
:editor=
"editorRef"
:editorId=
"editorId"
class=
"border-b border-solid border-borderColor"
/>
</div>
<n-button
@
click=
"uploadXml"
class=
"h-full"
>
上传XML
</n-button>
</div>
<!-- 编辑器 -->
<div
class=
"p-[15px] flex flex-1 overflow-hidden"
>
<slot
name=
"left"
></slot>
<n-card
class=
"flex-1"
content-class=
"h-full !p-0"
>
<Editor
v-model=
"valueHtml"
:defaultConfig=
"editorConfig"
:editorId=
"editorId"
:style=
"editorStyle"
@
on-change=
"handleChange"
@
on-created=
"handleCreated"
@
click=
"handleClick"
@
contextmenu
.
prevent=
"handleContextMenu"
/>
<n-dropdown
placement=
"bottom-start"
trigger=
"manual"
:x=
"dropdownConfig.x"
:y=
"dropdownConfig.y"
:options=
"dropdownConfig.options"
:show=
"dropdownConfig.show"
:on-clickoutside=
"onClickoutside"
@
select=
"handleSelect"
/>
</n-card>
</div>
</div>
</
template
>
<
script
setup
lang=
"ts"
>
import
{
IDomEditor
,
IEditorConfig
}
from
'@wangeditor/editor'
// @ts-ignore
import
{
Editor
,
Toolbar
}
from
'@wangeditor/editor-for-vue'
import
{
isNumber
}
from
'lodash'
import
{
Boot
,
IModuleConf
}
from
'@wangeditor/editor'
import
renderElemConf
from
'../functions/render-elem'
import
elemToHtmlConf
from
'../functions/elem-to-html'
import
parseHtmlConf
from
'../functions/parse-elem-html'
import
{
uploadXml
}
from
'../functions'
import
{
ArrowLeft24Filled
,
ArrowRight24Filled
,
Book20Filled
,
TableInsertColumn24Filled
}
from
'@vicons/fluent'
const
module
:
Partial
<
IModuleConf
>
=
{
renderElems
:
renderElemConf
,
elemsToHtml
:
elemToHtmlConf
,
parseElemsHtml
:
parseHtmlConf
}
Boot
.
registerModule
(
module
)
//菜单配置
const
toolbarConfig
=
{
toolbarKeys
:
[
'headerSelect'
,
'bold'
,
'italic'
,
'underline'
,
'through'
,
'color'
,
'fontSize'
,
'lineHeight'
,
'bulletedList'
,
'numberedList'
,
'justifyLeft'
,
'justifyCenter'
,
'justifyRight'
,
'undo'
,
'redo'
,
'uploadImage'
,
'insertLink'
,
'fullScreen'
,
'clearStyle'
]
}
type
InsertFnType
=
(
url
:
string
,
alt
:
string
,
href
:
string
)
=>
void
const
ps
=
defineProps
({
modelValue
:
{
type
:
String
,
default
:
''
},
editorId
:
{
type
:
String
,
default
:
'wangeEditor-1'
},
url
:
{
type
:
String
,
default
:
''
},
height
:
{
type
:
[
String
,
Number
],
default
:
'100%'
},
editorConfig
:
{
type
:
Object
,
default
:
()
=>
{
return
undefined
}
},
readonly
:
{
type
:
Boolean
,
default
:
false
}
})
const
es
=
defineEmits
([
'change'
,
'created'
,
'update:modelValue'
,
'handleClick'
,
'handleContextSelect'
])
// 编辑器实例,必须用 shallowRef
const
editorRef
=
shallowRef
<
IDomEditor
>
()
const
valueHtml
=
ref
(
''
)
watch
(
()
=>
ps
.
modelValue
,
(
val
:
string
)
=>
{
if
(
val
===
unref
(
valueHtml
))
return
valueHtml
.
value
=
val
},
{
immediate
:
true
}
)
// 监听
watch
(
()
=>
valueHtml
.
value
,
(
val
:
string
)
=>
{
es
(
'update:modelValue'
,
val
)
}
)
const
handleCreated
=
(
editor
:
IDomEditor
)
=>
{
editorRef
.
value
=
editor
es
(
'created'
,
editor
)
}
//点击
const
handleClick
=
(
event
:
any
)
=>
{
const
key
=
getKey
(
event
.
target
)
es
(
'handleClick'
,
key
)
}
//获取key
const
getKey
=
(
elem
:
any
):
string
=>
{
if
(
!
elem
)
return
''
const
key
=
elem
.
getAttribute
(
'data-key'
)
if
(
key
&&
elem
!=
null
)
{
return
key
}
else
{
elem
=
elem
.
parentElement
return
getKey
(
elem
)
}
}
//右键菜单
const
dropdownConfig
=
reactive
({
show
:
false
,
x
:
0
,
y
:
0
,
options
:
[
{
label
:
'左缩进'
,
key
:
'left'
,
icon
()
{
return
h
(
NIcon
,
null
,
{
default
:
()
=>
h
(
ArrowLeft24Filled
)
})
}
},
{
label
:
'右缩进'
,
key
:
'right'
,
icon
()
{
return
h
(
NIcon
,
null
,
{
default
:
()
=>
h
(
ArrowRight24Filled
)
})
}
},
{
label
:
'插入'
,
key
:
'insert'
,
icon
()
{
return
h
(
NIcon
,
null
,
{
default
:
()
=>
h
(
TableInsertColumn24Filled
)
})
},
children
:
[
{
label
:
'TaskTitle'
,
key
:
'TaskTitle'
,
icon
()
{
return
h
(
NIcon
,
null
,
{
default
:
()
=>
h
(
Book20Filled
)
})
}
},
{
label
:
'TopicTitle'
,
key
:
'TopicTitle'
,
icon
()
{
return
h
(
NIcon
,
null
,
{
default
:
()
=>
h
(
Book20Filled
)
})
}
}
]
}
]
})
const
handleContextMenu
=
(
event
:
MouseEvent
)
=>
{
dropdownConfig
.
show
=
false
nextTick
(()
=>
{
dropdownConfig
.
show
=
true
dropdownConfig
.
x
=
event
.
clientX
dropdownConfig
.
y
=
event
.
clientY
})
}
const
handleSelect
=
(
key
:
string
|
number
)
=>
{
dropdownConfig
.
show
=
false
editorRef
.
value
?.
restoreSelection
()
es
(
'handleContextSelect'
,
key
)
}
const
onClickoutside
=
()
=>
{
dropdownConfig
.
show
=
false
}
// 编辑器配置
const
message
=
useMessage
()
const
editorConfig
=
computed
(():
IEditorConfig
=>
{
return
Object
.
assign
(
{
placeholder
:
'请输入内容...'
,
readOnly
:
ps
.
readonly
,
customAlert
:
(
s
:
string
,
t
:
string
)
=>
{
switch
(
t
)
{
case
'success'
:
message
.
success
(
s
)
break
case
'info'
:
message
.
info
(
s
)
break
case
'warning'
:
message
.
warning
(
s
)
break
case
'error'
:
message
.
error
(
s
)
break
default
:
message
.
info
(
s
)
break
}
},
autoFocus
:
false
,
scroll
:
true
,
MENU_CONF
:
{
[
'uploadImage'
]:
{
server
:
ps
.
url
,
// 单个文件的最大体积限制,默认为 2M
maxFileSize
:
5
*
1024
*
1024
,
// 最多可上传几个文件,默认为 100
maxNumberOfFiles
:
10
,
// 选择文件时的类型限制,默认为 ['image/*'] 。如不想限制,则设置为 []
allowedFileTypes
:
[
'image/*'
],
// 自定义上传参数,例如传递验证的 token 等。参数会被添加到 formData 中,一起上传到服务端。
meta
:
{
updateSupport
:
0
},
// 将 meta 拼接到 url 参数中,默认 false
metaWithUrl
:
true
,
// 自定义增加 http header
headers
:
{
Accept
:
'*'
,
Authorization
:
`Bearer`
},
// 跨域是否传递 cookie ,默认为 false
withCredentials
:
true
,
// 超时时间,默认为 10 秒
timeout
:
5
*
1000
,
// 5 秒
// form-data fieldName,后端接口参数名称,默认值wangeditor-uploaded-image
fieldName
:
'file'
,
// 上传之前触发
onBeforeUpload
(
file
:
File
)
{
console
.
log
(
file
)
return
file
},
// 上传进度的回调函数
onProgress
(
progress
:
number
)
{
// progress 是 0-100 的数字
console
.
log
(
'progress'
,
progress
)
},
onSuccess
(
file
:
File
,
res
:
any
)
{
console
.
log
(
'onSuccess'
,
file
,
res
)
},
onFailed
(
file
:
File
,
res
:
any
)
{
alert
(
res
.
message
)
console
.
log
(
'onFailed'
,
file
,
res
)
},
onError
(
file
:
File
,
err
:
any
,
res
:
any
)
{
alert
(
err
.
message
)
console
.
error
(
'onError'
,
file
,
err
,
res
)
},
// 自定义插入图片
customInsert
(
res
:
any
,
insertFn
:
InsertFnType
)
{
insertFn
(
res
.
data
,
'image'
,
res
.
data
)
}
}
},
uploadImgShowBase64
:
true
},
ps
.
editorConfig
||
{}
)
})
const
editorStyle
=
computed
(()
=>
{
return
{
height
:
isNumber
(
ps
.
height
)
?
`
${
ps
.
height
}
px`
:
ps
.
height
}
})
// 回调函数
const
handleChange
=
(
editor
:
IDomEditor
)
=>
{
es
(
'change'
,
editor
)
}
// 组件销毁时,及时销毁编辑器
onBeforeUnmount
(()
=>
{
const
editor
=
unref
(
editorRef
.
value
)
// 销毁,并移除 editor
editor
?.
destroy
()
})
defineExpose
({
editorRef
})
</
script
>
<
style
src=
"@wangeditor/editor/dist/css/style.css"
></
style
>
src/views/editor/components/
Content
Tree.vue
→
src/views/editor/components/Tree.vue
View file @
fb7284af
...
@@ -19,22 +19,20 @@
...
@@ -19,22 +19,20 @@
</
template
>
</
template
>
<
script
lang=
"ts"
setup
>
<
script
lang=
"ts"
setup
>
import
{
realComposableData
,
searchKey
,
treeSelectedKeys
,
treeRef
}
from
'../constants'
import
{
treeSelectedKeys
,
treeRef
,
searchKey
,
expandedKeys
,
treeData
}
from
'../constants/tree'
import
type
{
TreeOption
}
from
'naive-ui'
import
{
buildFilteredTree
,
getAllKeys
,
nodeProps
}
from
'../functions/tree'
import
{
nodeProps
}
from
'../functions'
const
expandedKeys
=
ref
<
string
[]
>
([])
function
getAllKeys
(
item
:
TreeOption
[])
{
return
item
.
reduce
(
function
(
q
,
w
)
{
q
.
push
(
w
.
key
as
string
)
if
(
w
.
children
)
{
q
.
push
(...
getAllKeys
(
w
.
children
))
}
return
q
},
[]
as
string
[])
}
const
realComposableData
=
computed
(
function
()
{
if
(
!
searchKey
.
value
)
{
return
treeData
.
value
}
// @ts-ignore
const
result
=
buildFilteredTree
(
treeData
.
value
[
0
],
searchKey
.
value
)
if
(
!
result
)
{
return
[]
}
return
[
result
]
})
watch
(
watch
(
realComposableData
,
realComposableData
,
function
()
{
function
()
{
...
...
src/views/editor/constants/index.ts
View file @
fb7284af
// @ts-nocheck
import
{
ArrowLeft24Filled
,
ArrowRight24Filled
,
Book20Filled
,
TableInsertColumn24Filled
}
from
'@vicons/fluent'
import
type
{
TreeOption
}
from
'naive-ui'
import
{
IDomEditor
}
from
'@wangeditor/editor'
import
{
TreeRenderResult
}
from
'@/lib/XMLProcessor/src/typing'
export
const
showLoading
=
ref
(
false
)
export
const
showLoading
=
ref
(
false
)
// 菜单相关
//编辑器相关配置
export
const
searchKey
=
ref
(
''
)
export
const
editorRef
=
shallowRef
<
IDomEditor
>
()
export
const
treeData
:
Ref
<
TreeOption
[]
>
=
ref
([])
export
const
editorHtml
=
ref
(
''
)
export
const
xmlDOM
:
Ref
<
Document
>
=
ref
()
//菜单配置
export
const
treeSelectedKeys
=
ref
<
string
[]
>
([])
export
const
toolbarConfig
=
{
export
const
xmlContent
:
Ref
<
string
>
=
ref
(
''
)
toolbarKeys
:
[
//编辑器相关
'headerSelect'
,
export
const
editorRef
=
ref
()
'bold'
,
export
const
formData
=
reactive
({
'italic'
,
html
:
''
'underline'
,
})
'through'
,
export
const
treeRef
=
ref
()
'color'
,
// 递归函数:检查树中是否存在满足条件的节点
'fontSize'
,
// 递归函数:检查树中是否存在满足条件的节点,并构建新的树结构
'lineHeight'
,
function
buildFilteredTree
(
tree
:
TreeRenderResult
,
searchString
:
string
):
TreeRenderResult
|
null
{
'bulletedList'
,
// 如果当前节点的 label 包含指定字符串,直接返回当前节点
'numberedList'
,
if
(
tree
.
label
.
includes
(
searchString
))
{
'justifyLeft'
,
return
{
...
tree
}
// 返回当前节点的副本
'justifyCenter'
,
}
'justifyRight'
,
'undo'
,
// 如果当前节点有子节点,递归检查每个子节点
'redo'
,
if
(
tree
.
children
)
{
'uploadImage'
,
const
filteredChildren
:
TreeRenderResult
[]
=
[]
'insertLink'
,
for
(
let
child
of
tree
.
children
)
{
'fullScreen'
,
const
result
=
buildFilteredTree
(
child
,
searchString
)
'clearStyle'
if
(
result
)
{
]
filteredChildren
.
push
(
result
)
// 如果子节点符合条件,加入到当前节点的子节点列表
}
//右键菜单
export
const
dropdownConfig
=
reactive
({
show
:
false
,
x
:
0
,
y
:
0
,
options
:
[
{
label
:
'左缩进'
,
key
:
'left'
,
icon
()
{
return
h
(
NIcon
,
null
,
{
default
:
()
=>
h
(
ArrowLeft24Filled
)
})
}
}
},
{
label
:
'右缩进'
,
key
:
'right'
,
icon
()
{
return
h
(
NIcon
,
null
,
{
default
:
()
=>
h
(
ArrowRight24Filled
)
})
}
},
{
label
:
'插入'
,
key
:
'insert'
,
icon
()
{
return
h
(
NIcon
,
null
,
{
default
:
()
=>
h
(
TableInsertColumn24Filled
)
})
},
children
:
[
{
label
:
'TaskTitle'
,
key
:
'TaskTitle'
,
icon
()
{
return
h
(
NIcon
,
null
,
{
default
:
()
=>
h
(
Book20Filled
)
})
}
},
{
label
:
'TopicTitle'
,
key
:
'TopicTitle'
,
icon
()
{
return
h
(
NIcon
,
null
,
{
default
:
()
=>
h
(
Book20Filled
)
})
}
}
]
}
}
]
// 如果有符合条件的子节点,返回当前节点,并更新子节点列表
if
(
filteredChildren
.
length
>
0
)
{
return
{
...
tree
,
children
:
filteredChildren
}
}
}
// 如果当前节点及其子节点都不满足条件,返回 null
return
null
}
export
const
realComposableData
=
computed
(
function
()
{
if
(
!
searchKey
.
value
)
{
return
treeData
.
value
}
// @ts-ignore
const
result
=
buildFilteredTree
(
treeData
.
value
[
0
],
searchKey
.
value
)
if
(
!
result
)
{
return
[]
}
// @ts-ignore
return
[
result
]
})
})
src/views/editor/constants/tree.ts
0 → 100644
View file @
fb7284af
import
{
TreeOption
}
from
'naive-ui'
export
const
expandedKeys
=
ref
<
string
[]
>
([])
export
const
treeData
:
Ref
<
TreeOption
[]
>
=
ref
([])
export
const
treeSelectedKeys
=
ref
<
string
[]
>
([])
export
const
treeRef
=
ref
()
export
const
searchKey
=
ref
(
''
)
src/views/editor/functions/index.ts
View file @
fb7284af
import
{
showLoading
}
from
'../constants'
import
{
nodeSet
}
from
'@/configs/node.config'
import
{
IDomEditor
}
from
'@wangeditor/editor'
import
{
IDomEditor
}
from
'@wangeditor/editor'
import
{
editorRef
,
formData
,
showLoading
,
treeData
,
treeRef
,
treeSelectedKeys
,
xmlContent
,
xmlDOM
}
from
'../constants'
import
{
treeData
,
treeRef
,
treeSelectedKeys
}
from
'../constants/tree'
import
{
TreeOption
}
from
'naive-ui'
import
{
dropdownConfig
,
editorRef
,
editorHtml
}
from
'../constants'
import
{
nodeSet
}
from
'../constants/nodeParsed'
import
{
UUID
}
from
'uuidjs'
import
{
UUID
}
from
'uuidjs'
import
{
getUUID
}
from
'@/utils'
export
const
handleEditor
=
(
editor
:
IDomEditor
)
=>
{
export
const
handleCreated
=
(
editor
:
IDomEditor
)
=>
{
if
(
editor
.
getHtml
()
==
'<p><br></p>'
)
return
editorRef
.
value
=
editor
const
xmlProcessing
=
useXMLProcessing
()
handleChange
(
editor
)
const
res
=
xmlProcessing
.
processXML
(
editor
.
getHtml
().
replace
(
/<p><br><
\/
p>/g
,
''
),
nodeSet
)
treeData
.
value
=
res
.
treeData
}
}
export
const
handleClick
=
(
event
:
any
)
=>
{
export
const
handleClick
=
(
key
:
string
)
=>
{
const
key
=
getKey
(
event
.
target
)
treeRef
.
value
?.
scrollTo
({
key
})
treeRef
.
value
?.
scrollTo
({
key
})
treeSelectedKeys
.
value
=
[
key
]
treeSelectedKeys
.
value
=
[
key
]
setEditorActive
(
key
)
setEditorActive
(
key
)
}
}
export
const
handleContextSelect
=
(
key
:
string
)
=>
{
if
(
key
==
'TaskTitle'
)
{
const
node
=
{
type
:
'TaskTitle'
,
dataKey
:
'g-'
+
UUID
.
generate
(),
children
:
[{
text
:
'TaskTitle'
}]
}
editorRef
.
value
?.
editorRef
?.
insertNode
(
node
)
}
else
if
(
key
==
'TopicTitle'
)
{
const
node
=
{
type
:
'TopicTitle'
,
dataKey
:
'g-'
+
UUID
.
generate
(),
children
:
[{
text
:
'TopicTitle'
}]
}
editorRef
.
value
?.
editorRef
?.
insertNode
(
node
)
}
}
export
const
nodeProps
=
({
option
}:
{
option
:
TreeOption
})
=>
{
return
{
onClick
()
{
setEditorActive
(
option
.
key
as
string
,
(
id
:
string
)
=>
{
editorRef
.
value
?.
editorRef
.
scrollToElem
([
id
])
})
}
}
}
let
lastFocusedId
=
''
let
lastFocusedId
=
''
export
const
setEditorActive
=
(
key
:
string
,
callBack
?:
Function
)
=>
{
export
const
setEditorActive
=
(
key
:
string
,
callBack
?:
Function
)
=>
{
const
container
=
editorRef
.
value
?.
editorRef
.
getEditableContainer
()
const
container
=
editorRef
.
value
?.
getEditableContainer
()
const
id
=
container
.
querySelector
(
`[data-key="
${
key
}
"]`
)?.
getAttribute
(
'id'
)
const
id
=
container
!
.
querySelector
(
`[data-key="
${
key
}
"]`
)?.
getAttribute
(
'id'
)
as
string
if
(
container
)
{
if
(
container
)
{
if
(
lastFocusedId
)
{
if
(
lastFocusedId
)
{
container
.
querySelector
(
`#
${
lastFocusedId
}
`
)?.
classList
.
remove
(
'bg-primaryColorHover'
)
container
.
querySelector
(
`#
${
lastFocusedId
}
`
)?.
classList
.
remove
(
'bg-primaryColorHover'
)
...
@@ -51,6 +31,49 @@ export const setEditorActive = (key: string, callBack?: Function) => {
...
@@ -51,6 +31,49 @@ export const setEditorActive = (key: string, callBack?: Function) => {
if
(
callBack
)
callBack
(
id
)
if
(
callBack
)
callBack
(
id
)
}
}
//获取节点的key
export
const
getKey
=
(
elem
:
any
):
string
=>
{
if
(
!
elem
)
return
''
const
key
=
elem
.
getAttribute
(
'data-key'
)
if
(
key
&&
elem
!=
null
)
{
return
key
}
else
{
elem
=
elem
.
parentElement
return
getKey
(
elem
)
}
}
// 右键
export
const
handleContextMenu
=
(
event
:
MouseEvent
)
=>
{
dropdownConfig
.
show
=
false
nextTick
(()
=>
{
dropdownConfig
.
show
=
true
dropdownConfig
.
x
=
event
.
clientX
dropdownConfig
.
y
=
event
.
clientY
})
}
// 右键选择
export
const
handleSelect
=
(
key
:
string
|
number
)
=>
{
dropdownConfig
.
show
=
false
editorRef
.
value
?.
restoreSelection
()
if
(
key
==
'TaskTitle'
)
{
const
node
=
{
type
:
'TaskTitle'
,
dataKey
:
getUUID
(),
children
:
[{
text
:
'TaskTitle'
}]
}
editorRef
.
value
?.
insertNode
(
node
)
}
else
if
(
key
==
'TopicTitle'
)
{
const
node
=
{
type
:
'TopicTitle'
,
dataKey
:
getUUID
(),
children
:
[{
text
:
'TopicTitle'
}]
}
editorRef
.
value
?.
insertNode
(
node
)
}
}
export
const
onClickoutside
=
()
=>
{
dropdownConfig
.
show
=
false
}
// 编辑器内容变化
export
const
handleChange
=
(
editor
:
IDomEditor
)
=>
{
if
(
editor
.
getHtml
()
==
'<p><br></p>'
)
return
const
xmlProcessing
=
useXMLProcessing
()
const
res
=
xmlProcessing
.
processXML
(
editor
.
getHtml
().
replace
(
/<p><br><
\/
p>/g
,
''
),
nodeSet
)
treeData
.
value
=
res
.
treeData
}
// 上传xml
export
const
uploadXml
=
async
()
=>
{
export
const
uploadXml
=
async
()
=>
{
const
input
=
document
.
createElement
(
'input'
)
const
input
=
document
.
createElement
(
'input'
)
input
.
type
=
'file'
input
.
type
=
'file'
...
@@ -65,7 +88,7 @@ export const uploadXml = async () => {
...
@@ -65,7 +88,7 @@ export const uploadXml = async () => {
showLoading
.
value
=
true
showLoading
.
value
=
true
const
res
=
await
xmlProcessing
.
processFile
(
file
,
nodeSet
)
const
res
=
await
xmlProcessing
.
processFile
(
file
,
nodeSet
)
showLoading
.
value
=
false
showLoading
.
value
=
false
formData
.
html
=
res
.
xmlContent
editorHtml
.
value
=
res
.
xmlContent
}
}
})
})
}
}
src/views/editor/functions/tree.ts
0 → 100644
View file @
fb7284af
import
{
TreeRenderResult
}
from
'@/lib/XMLProcessor/src/typing'
import
{
setEditorActive
}
from
'.'
import
{
editorRef
}
from
'../constants'
import
{
TreeOption
}
from
'naive-ui'
//点击节点
export
const
nodeProps
=
({
option
}:
{
option
:
TreeOption
})
=>
{
return
{
onClick
()
{
setEditorActive
(
option
.
key
as
string
,
(
id
:
string
)
=>
{
editorRef
.
value
?.
scrollToElem
(
id
)
})
}
}
}
// 递归函数:检查树中是否存在满足条件的节点,并构建新的树结构
export
const
buildFilteredTree
=
(
tree
:
TreeRenderResult
,
searchString
:
string
):
TreeRenderResult
|
null
=>
{
// 如果当前节点的 label 包含指定字符串,直接返回当前节点
if
(
tree
.
label
.
includes
(
searchString
))
{
return
{
...
tree
}
// 返回当前节点的副本
}
// 如果当前节点有子节点,递归检查每个子节点
if
(
tree
.
children
)
{
const
filteredChildren
:
TreeRenderResult
[]
=
[]
for
(
let
child
of
tree
.
children
)
{
const
result
=
buildFilteredTree
(
child
,
searchString
)
if
(
result
)
{
filteredChildren
.
push
(
result
)
// 如果子节点符合条件,加入到当前节点的子节点列表
}
}
// 如果有符合条件的子节点,返回当前节点,并更新子节点列表
if
(
filteredChildren
.
length
>
0
)
{
return
{
...
tree
,
children
:
filteredChildren
}
}
}
// 如果当前节点及其子节点都不满足条件,返回 null
return
null
}
export
const
getAllKeys
=
(
item
:
TreeOption
[])
=>
{
return
item
.
reduce
(
function
(
q
,
w
)
{
q
.
push
(
w
.
key
as
string
)
if
(
w
.
children
)
{
q
.
push
(...
getAllKeys
(
w
.
children
))
}
return
q
},
[]
as
string
[])
}
src/views/editor/index.vue
View file @
fb7284af
<
template
>
<
template
>
<n-spin
class=
"h-full w-full"
content-class=
"h-full w-full"
:show=
"showLoading"
description=
"加载中..."
>
<div
class=
"z-10 h-full flex flex-col"
>
<ContentEditor
<!-- 工具栏 -->
ref=
"editorRef"
<div
class=
"flex"
>
v-model=
"formData.html"
<div
class=
"flex-1"
>
@
change=
"handleEditor"
<Toolbar
:editor=
"editorRef"
editorId=
"wangeEditor-1"
class=
"border-b border-solid border-borderColor"
/>
@
created=
"handleEditor"
</div>
@
handleClick=
"handleClick"
<n-button
@
click=
"uploadXml"
class=
"h-full"
>
上传XML
</n-button>
@
handleContextSelect=
"handleContextSelect"
</div>
>
<!-- 编辑器 -->
<template
#
left
>
<div
class=
"p-[15px] flex flex-1 overflow-hidden"
>
<n-card
class=
"h-full min-w-[300px] max-w-[300px] mr-[15px]"
content-class=
"bg-baseColor !p-0 overflow-hidden"
>
<n-card
class=
"h-full min-w-[300px] max-w-[300px] mr-[15px]"
content-class=
"bg-baseColor !p-0 overflow-hidden"
>
<ContentTree></ContentTree>
<Tree
/>
</n-card>
</n-card>
</
template
>
<n-card
class=
"flex-1"
content-class=
"h-full !p-0"
>
</ContentEditor>
<Editor
</n-spin>
v-model=
"editorHtml"
editorId=
"wangeEditor-1"
:style=
"
{ height: '100%' }"
@on-change="handleChange"
@on-created="handleCreated"
@click="handleClick"
@contextmenu.prevent="handleContextMenu"
/>
<n-dropdown
placement=
"bottom-start"
trigger=
"manual"
:x=
"dropdownConfig.x"
:y=
"dropdownConfig.y"
:options=
"dropdownConfig.options"
:show=
"dropdownConfig.show"
:on-clickoutside=
"onClickoutside"
@
select=
"handleSelect"
/>
</n-card>
</div>
</div>
</
template
>
</
template
>
<
script
setup
lang=
"ts"
>
<
script
setup
lang=
"ts"
>
import
ContentEditor
from
'./components/ContentEditor.vue'
// @ts-ignore
import
ContentTree
from
'./components/ContentTree.vue'
import
{
Editor
,
Toolbar
}
from
'@wangeditor/editor-for-vue'
import
{
editorRef
,
formData
,
showLoading
,
xmlContent
}
from
'./constants'
import
{
Boot
,
IModuleConf
}
from
'@wangeditor/editor'
import
{
handleClick
,
handleEditor
,
handleContextSelect
}
from
'./functions'
import
renderElemConf
from
'@/configs/render-elem'
import
elemToHtmlConf
from
'@/configs/elem-to-html'
import
parseHtmlConf
from
'@/configs/parse-elem-html'
import
{
dropdownConfig
,
editorRef
,
editorHtml
}
from
'./constants/'
import
{
handleChange
,
handleClick
,
handleContextMenu
,
handleCreated
,
handleSelect
,
onClickoutside
,
uploadXml
}
from
'./functions/'
import
Tree
from
'./components/Tree.vue'
onMounted
(()
=>
{
const
module
:
Partial
<
IModuleConf
>
=
{
// nextTick(() => {
renderElems
:
renderElemConf
,
// editorRef.value?.editorRef?.setHtml(xmlContent.value)
elemsToHtml
:
elemToHtmlConf
,
// })
parseElemsHtml
:
parseHtmlConf
}
Boot
.
registerModule
(
module
)
// 组件销毁时,及时销毁编辑器
onBeforeUnmount
(()
=>
{
const
editor
=
unref
(
editorRef
.
value
)
// 销毁,并移除 editor
editor
?.
destroy
()
})
})
</
script
>
</
script
>
<
style
lang=
"less"
scoped
></
style
>
<
style
src=
"@wangeditor/editor/dist/css/style.css"
></
style
>
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment