Commit fc1aca98 by pangchong

feat: 富文本渲染

parent 58321083
...@@ -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']
......
...@@ -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)
......
...@@ -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>
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'
])
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'])
}
}
}
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'
])
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'
])
...@@ -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>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment