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
fc1aca98
Commit
fc1aca98
authored
Mar 28, 2025
by
pangchong
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: 富文本渲染
parent
58321083
Show whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
193 additions
and
62 deletions
+193
-62
components.d.ts
+3
-0
src/views/editor/components/ContentEditor.vue
+6
-6
src/views/editor/components/ContentTree.vue
+40
-28
src/views/editor/functions/elem-to-html.ts
+43
-8
src/views/editor/functions/index.ts
+13
-1
src/views/editor/functions/parse-elem-html.ts
+43
-7
src/views/editor/functions/render-elem.ts
+40
-7
src/views/editor/index.vue
+5
-5
No files found.
components.d.ts
View file @
fc1aca98
...
@@ -7,6 +7,7 @@ export {}
...
@@ -7,6 +7,7 @@ export {}
declare
module
'vue'
{
declare
module
'vue'
{
export
interface
GlobalComponents
{
export
interface
GlobalComponents
{
NButton
:
typeof
import
(
'naive-ui'
)[
'NButton'
]
NCard
:
typeof
import
(
'naive-ui'
)[
'NCard'
]
NCard
:
typeof
import
(
'naive-ui'
)[
'NCard'
]
NConfigProvider
:
typeof
import
(
'naive-ui'
)[
'NConfigProvider'
]
NConfigProvider
:
typeof
import
(
'naive-ui'
)[
'NConfigProvider'
]
NInput
:
typeof
import
(
'naive-ui'
)[
'NInput'
]
NInput
:
typeof
import
(
'naive-ui'
)[
'NInput'
]
...
@@ -15,6 +16,8 @@ declare module 'vue' {
...
@@ -15,6 +16,8 @@ declare module 'vue' {
NLayoutFooter
:
typeof
import
(
'naive-ui'
)[
'NLayoutFooter'
]
NLayoutFooter
:
typeof
import
(
'naive-ui'
)[
'NLayoutFooter'
]
NLayoutHeader
:
typeof
import
(
'naive-ui'
)[
'NLayoutHeader'
]
NLayoutHeader
:
typeof
import
(
'naive-ui'
)[
'NLayoutHeader'
]
NMessageProvider
:
typeof
import
(
'naive-ui'
)[
'NMessageProvider'
]
NMessageProvider
:
typeof
import
(
'naive-ui'
)[
'NMessageProvider'
]
NResult
:
typeof
import
(
'naive-ui'
)[
'NResult'
]
NSpace
:
typeof
import
(
'naive-ui'
)[
'NSpace'
]
NTree
:
typeof
import
(
'naive-ui'
)[
'NTree'
]
NTree
:
typeof
import
(
'naive-ui'
)[
'NTree'
]
RouterLink
:
typeof
import
(
'vue-router'
)[
'RouterLink'
]
RouterLink
:
typeof
import
(
'vue-router'
)[
'RouterLink'
]
RouterView
:
typeof
import
(
'vue-router'
)[
'RouterView'
]
RouterView
:
typeof
import
(
'vue-router'
)[
'RouterView'
]
...
...
src/views/editor/components/ContentEditor.vue
View file @
fc1aca98
...
@@ -25,14 +25,14 @@ import { IDomEditor, IEditorConfig } from '@wangeditor/editor'
...
@@ -25,14 +25,14 @@ import { IDomEditor, IEditorConfig } from '@wangeditor/editor'
import
{
Editor
,
Toolbar
}
from
'@wangeditor/editor-for-vue'
import
{
Editor
,
Toolbar
}
from
'@wangeditor/editor-for-vue'
import
{
isNumber
}
from
'lodash'
import
{
isNumber
}
from
'lodash'
import
{
Boot
,
IModuleConf
}
from
'@wangeditor/editor'
import
{
Boot
,
IModuleConf
}
from
'@wangeditor/editor'
import
{
renderElemConf
}
from
'../functions/render-elem'
import
renderElemConf
from
'../functions/render-elem'
import
{
elemToHtmlConf
}
from
'../functions/elem-to-html'
import
elemToHtmlConf
from
'../functions/elem-to-html'
import
{
parseHtmlConf
}
from
'../functions/parse-elem-html'
import
parseHtmlConf
from
'../functions/parse-elem-html'
const
module
:
Partial
<
IModuleConf
>
=
{
const
module
:
Partial
<
IModuleConf
>
=
{
renderElems
:
[
renderElemConf
]
,
renderElems
:
renderElemConf
,
elemsToHtml
:
[
elemToHtmlConf
]
,
elemsToHtml
:
elemToHtmlConf
,
parseElemsHtml
:
[
parseHtmlConf
]
parseElemsHtml
:
parseHtmlConf
}
}
Boot
.
registerModule
(
module
)
Boot
.
registerModule
(
module
)
...
...
src/views/editor/components/ContentTree.vue
View file @
fc1aca98
...
@@ -4,58 +4,70 @@
...
@@ -4,58 +4,70 @@
<n-input
v-model:value=
"searchKey"
placeholder=
"搜索"
/>
<n-input
v-model:value=
"searchKey"
placeholder=
"搜索"
/>
</div>
</div>
<div
class=
"flex-auto overflow-auto"
>
<div
class=
"flex-auto overflow-auto"
>
<n-tree
v-model:expanded-keys=
"expandedKeys"
ref=
"treeRef"
:pattern=
"searchKey"
:data=
"realComposableData"
block-line
/>
<n-tree
v-model:expanded-keys=
"expandedKeys"
ref=
"treeRef"
:pattern=
"searchKey"
:data=
"realComposableData"
block-line
:node-props=
"nodeProps"
/>
</div>
</div>
</div>
</div>
</
template
>
</
template
>
<
script
setup
lang=
"ts"
>
<
script
setup
lang=
"ts"
>
import
{
searchKey
,
treeData
,
realComposableData
,
xmlDOM
}
from
'../constants'
import
{
searchKey
,
treeData
,
realComposableData
,
xmlDOM
}
from
'../constants'
import
FileXML
from
"@/assets/file/CES-QEC-V250-A.xml?raw"
import
FileXML
from
'@/assets/file/CES-QEC-V250-A.xml?raw'
import
type
{
TreeOption
}
from
'naive-ui'
import
type
{
TreeOption
}
from
'naive-ui'
import
{
nodeProps
}
from
'../functions'
const
xmlProcessing
=
useXMLProcessing
()
const
xmlProcessing
=
useXMLProcessing
()
const
nodeSet
=
[
const
nodeSet
=
[
"JOBCARD"
,
'JOBCARD'
,
"CEP"
,
'CEP'
,
"TITLE"
,
'TITLE'
,
"WARNING"
,
'WARNING'
,
"TFMATR"
,
'TFMATR'
,
"PRETOPIC"
,
'PRETOPIC'
,
"PARA"
,
'PARA'
,
"LIST1"
,
'LIST1'
,
"L1ITEM"
,
'L1ITEM'
,
"TABLE"
,
'TABLE'
,
"TOPIC"
,
'TOPIC'
,
"SUBTASK"
,
'SUBTASK'
,
"NOTE"
,
'NOTE'
,
"LIST2"
,
'LIST2'
,
"L2ITEM"
,
'L2ITEM'
,
"LIST3"
,
'LIST3'
,
"L3ITEM"
,
'L3ITEM'
,
"LIST4"
,
'LIST4'
,
"L4ITEM"
,
'L4ITEM'
,
"STEP"
,
'STEP'
,
"RECORD-LINE"
'RECORD-LINE'
]
]
const
treeRef
=
ref
()
const
treeRef
=
ref
()
const
expandedKeys
=
ref
<
string
[]
>
([])
const
expandedKeys
=
ref
<
string
[]
>
([])
onMounted
(
function
()
{
onMounted
(
function
()
{
const
res
=
xmlProcessing
.
processXML
(
FileXML
,
nodeSet
)
const
res
=
xmlProcessing
.
processXML
(
FileXML
,
nodeSet
)
treeData
.
value
=
res
.
treeData
treeData
.
value
=
res
.
treeData
xmlDOM
.
value
=
res
.
xmlDOM
xmlDOM
.
value
=
res
.
xmlDOM
console
.
log
(
'all-node'
,
xmlDOM
.
value
)
console
.
log
(
'all-node'
,
xmlDOM
.
value
)
})
})
function
getAllKeys
(
item
:
TreeOption
[])
{
function
getAllKeys
(
item
:
TreeOption
[])
{
return
item
.
reduce
(
function
(
q
,
w
)
{
return
item
.
reduce
(
function
(
q
,
w
)
{
q
.
push
(
w
.
key
as
string
)
q
.
push
(
w
.
key
as
string
)
if
(
w
.
children
)
{
if
(
w
.
children
)
{
q
.
push
(...
getAllKeys
(
w
.
children
))
q
.
push
(...
getAllKeys
(
w
.
children
))
}
}
return
q
;
return
q
},
[]
as
string
[])
},
[]
as
string
[])
}
}
watch
(
realComposableData
,
function
()
{
watch
(
realComposableData
,
function
()
{
expandedKeys
.
value
=
getAllKeys
(
realComposableData
.
value
)
expandedKeys
.
value
=
getAllKeys
(
realComposableData
.
value
)
},
{
flush
:
'post'
,
deep
:
true
})
},
{
flush
:
'post'
,
deep
:
true
}
)
</
script
>
</
script
>
src/views/editor/functions/elem-to-html.ts
View file @
fc1aca98
import
{
SlateElement
}
from
'@wangeditor/editor'
import
{
SlateElement
,
IModuleConf
}
from
'@wangeditor/editor'
function
JOBCARDToHtml
(
elem
:
SlateElement
,
childrenHtml
:
string
):
string
{
const
elemToHtml
=
(
type
:
string
)
=>
{
return
`<JOBCARD
return
(
elem
:
SlateElement
,
childrenHtml
:
string
):
string
=>
{
data-w-e-type="JOBCARD"
const
dataKey
=
(
elem
as
any
).
dataKey
||
''
return
`<
${
type
}
data-w-e-type="
${
type
}
"
data-w-e-is-void
data-w-e-is-void
data-w-e-is-inline
data-w-e-is-inline
>
${
childrenHtml
}
</JOBCARD>`
data-key="
${
dataKey
}
"
>
${
childrenHtml
}
</
${
type
}
>`
}
}
}
export
const
elemToHtmlConf
=
{
const
createElemConfig
=
(
types
:
string
|
string
[])
=>
{
type
:
'JOBCARD'
,
const
configs
:
IModuleConf
[
'elemsToHtml'
]
=
[]
elemToHtml
:
JOBCARDToHtml
const
typeArray
=
Array
.
isArray
(
types
)
?
types
:
[
types
]
typeArray
.
forEach
((
type
)
=>
{
configs
.
push
({
type
,
elemToHtml
:
elemToHtml
(
type
)
})
})
return
configs
}
}
export
default
createElemConfig
([
'EOTK-HEADER'
,
'JOBCARD'
,
'CEP'
,
'TITLE'
,
'WARNING'
,
'TFMATR'
,
'PRETOPIC'
,
'PARA'
,
'LIST1'
,
'L1ITEM'
,
'TABLE'
,
'TOPIC'
,
'SUBTASK'
,
'NOTE'
,
'LIST2'
,
'L2ITEM'
,
'LIST3'
,
'L3ITEM'
,
'LIST4'
,
'L4ITEM'
,
'STEP'
,
'RECORD-LINE'
])
src/views/editor/functions/index.ts
View file @
fc1aca98
import
{
IDomEditor
}
from
'@wangeditor/editor'
import
{
IDomEditor
}
from
'@wangeditor/editor'
import
{
editorRef
}
from
'../constants'
import
{
TreeOption
}
from
'naive-ui'
export
const
handleEditor
=
(
editor
:
IDomEditor
)
=>
{
export
const
handleEditor
=
(
editor
:
IDomEditor
)
=>
{
console
.
log
(
editor
)
console
.
log
(
editor
.
getHtml
())
// console.log(editor.getElemsByTypePrefix('JOBCARD'))
// console.log(editor)
// const headers = editor.getElemsByTypePrefix('header')
// const headers = editor.getElemsByTypePrefix('header')
// const menu = headers.map((header: any) => {
// const menu = headers.map((header: any) => {
// const title = SlateNode.string(header)
// const title = SlateNode.string(header)
...
@@ -13,3 +17,11 @@ export const handleEditor = (editor: IDomEditor) => {
...
@@ -13,3 +17,11 @@ export const handleEditor = (editor: IDomEditor) => {
// })
// })
// treeData.value = buildTree(menu)
// treeData.value = buildTree(menu)
}
}
export
const
nodeProps
=
({
option
}:
{
option
:
TreeOption
})
=>
{
return
{
onClick
()
{
console
.
log
(
editorRef
.
value
?.
editorRef
.
getEditableContainer
())
editorRef
.
value
?.
editorRef
.
scrollToElem
([
'w-e-element-124'
])
}
}
}
src/views/editor/functions/parse-elem-html.ts
View file @
fc1aca98
import
{
IDomEditor
,
SlateDescendant
,
SlateElement
}
from
'@wangeditor/editor'
import
{
IDomEditor
,
SlateDescendant
,
SlateElement
,
IModuleConf
}
from
'@wangeditor/editor'
function
parseJOBCARDHtml
(
domElem
:
Element
,
children
:
SlateDescendant
[],
editor
:
IDomEditor
):
SlateElement
{
const
parseElemHtml
=
(
type
:
string
)
=>
{
return
(
domElem
:
Element
,
children
:
SlateDescendant
[],
editor
:
IDomEditor
):
SlateElement
=>
{
const
dataKey
=
domElem
.
getAttribute
(
'data-key'
)
||
''
const
myResume
=
{
const
myResume
=
{
type
:
'JOBCARD'
,
type
,
children
:
[{
text
:
(
children
[
0
]
as
any
).
text
}]
// void node 必须有 children ,其中有一个空字符串,重要!!!
dataKey
,
children
}
}
return
myResume
return
myResume
}
}
}
export
const
parseHtmlConf
=
{
const
createParseConfig
=
(
types
:
string
|
string
[])
=>
{
selector
:
'JOBCARD[data-w-e-type="JOBCARD"]'
,
// CSS 选择器,匹配特定的 HTML 标签
const
configs
:
IModuleConf
[
'parseElemsHtml'
]
=
[]
parseElemHtml
:
parseJOBCARDHtml
const
typeArray
=
Array
.
isArray
(
types
)
?
types
:
[
types
]
typeArray
.
forEach
((
type
)
=>
{
configs
.
push
({
selector
:
`
${
type
}
[data-w-e-type="
${
type
}
"]`
,
parseElemHtml
:
parseElemHtml
(
type
)
})
})
return
configs
}
}
export
default
createParseConfig
([
'EOTK-HEADER'
,
'JOBCARD'
,
'CEP'
,
'TITLE'
,
'WARNING'
,
'TFMATR'
,
'PRETOPIC'
,
'PARA'
,
'LIST1'
,
'L1ITEM'
,
'TABLE'
,
'TOPIC'
,
'SUBTASK'
,
'NOTE'
,
'LIST2'
,
'L2ITEM'
,
'LIST3'
,
'L3ITEM'
,
'LIST4'
,
'L4ITEM'
,
'STEP'
,
'RECORD-LINE'
])
src/views/editor/functions/render-elem.ts
View file @
fc1aca98
import
{
h
,
VNode
}
from
'snabbdom'
import
{
h
,
VNode
}
from
'snabbdom'
import
{
IDomEditor
,
SlateElement
}
from
'@wangeditor/editor'
import
{
IDomEditor
,
IModuleConf
,
SlateElement
}
from
'@wangeditor/editor'
function
renderJOBCARD
(
elem
:
SlateElement
,
children
:
VNode
[]
|
null
,
editor
:
IDomEditor
):
VNode
{
const
renderElem
=
(
type
:
string
,
style
=
{
display
:
'block'
})
=>
{
return
h
(
'JOBCARD'
,
{
style
:
{
display
:
'block'
}
},
children
)
return
(
elem
:
SlateElement
,
children
:
VNode
[]
|
null
,
editor
:
IDomEditor
):
VNode
=>
{
const
dataKey
=
(
elem
as
any
).
dataKey
return
h
(
type
,
{
style
,
props
:
{
id
:
dataKey
}
},
children
)
}
}
}
const
createRenderConfig
=
(
types
:
string
|
string
[])
=>
{
export
const
renderElemConf
=
{
const
configs
:
IModuleConf
[
'renderElems'
]
=
[]
type
:
'JOBCARD'
,
const
typeArray
=
Array
.
isArray
(
types
)
?
types
:
[
types
]
renderElem
:
renderJOBCARD
typeArray
.
forEach
((
type
)
=>
{
configs
.
push
({
type
,
renderElem
:
renderElem
(
type
)
})
})
return
configs
}
}
export
default
createRenderConfig
([
'EOTK-HEADER'
,
'JOBCARD'
,
'CEP'
,
'TITLE'
,
'WARNING'
,
'TFMATR'
,
'PRETOPIC'
,
'PARA'
,
'LIST1'
,
'L1ITEM'
,
'TABLE'
,
'TOPIC'
,
'SUBTASK'
,
'NOTE'
,
'LIST2'
,
'L2ITEM'
,
'LIST3'
,
'L3ITEM'
,
'LIST4'
,
'L4ITEM'
,
'STEP'
,
'RECORD-LINE'
])
src/views/editor/index.vue
View file @
fc1aca98
...
@@ -11,15 +11,15 @@
...
@@ -11,15 +11,15 @@
<
script
setup
lang=
"ts"
>
<
script
setup
lang=
"ts"
>
import
ContentEditor
from
'./components/ContentEditor.vue'
import
ContentEditor
from
'./components/ContentEditor.vue'
import
ContentTree
from
'./components/ContentTree.vue'
import
ContentTree
from
'./components/ContentTree.vue'
import
{
editorRef
,
formData
}
from
'./constants'
import
{
editorRef
,
formData
,
xmlDOM
}
from
'./constants'
import
{
handleEditor
}
from
'./functions'
import
{
handleEditor
}
from
'./functions'
onMounted
(()
=>
{
onMounted
(()
=>
{
nextTick
(()
=>
{
nextTick
(()
=>
{
editorRef
.
value
?.
editorRef
?.
setHtml
(
'<JOBCARD data-w-e-type="JOBCARD">Custom Content</JOBCARD>'
)
xmlDOM
.
value
.
documentElement
.
querySelectorAll
(
':not([data-key])'
).
forEach
((
node
)
=>
node
.
remove
()
)
const
node
=
{
type
:
'JOBCARD'
,
children
:
[{
text
:
'simple text'
}]
}
const
serializer
=
new
XMLSerializer
()
editorRef
.
value
?.
editorRef
?.
insertNode
(
node
)
const
xmlString
=
serializer
.
serializeToString
(
xmlDOM
.
value
.
documentElement
)
console
.
log
(
editorRef
.
value
?.
editorRef
.
getHtml
()
)
editorRef
.
value
?.
editorRef
?.
setHtml
(
xmlString
)
})
})
})
})
</
script
>
</
script
>
...
...
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