vue项目集成CanvasEditor实现Word在线编辑器

CanvasEditor实现Word在线编辑器
官网文档:https://hufe.club/canvas-editor-docs/guide/schema.html
源码地址:https://github.com/Hufe921/canvas-editor

在这里插入图片描述

前提声明:
由于CanvasEditor目前不支持vue、react
等框架开箱即用版,所以需要我们去Git下载源码,拿到其中两个主要文件,集成到我们自己的项目中

第一步:项目安装CanvasEditor

在你需要集成编辑器的项目中,安装CanvasEditor

npm i CanvasEditor

第二步:主要文件集成

1、下载源码,目录如下:红色框就是我们需要在自己项目中引用的
在这里插入图片描述

2、在你的vue项目项目中,新建一个文件夹,叫CanvasEditor,这个文件夹内,就放集成的相关文件(代码以及目录结构贴在后边)

  1. 新建个index.vue文件,作为后边集成封装的CanvasEditor组件引入的入口
  2. index.vuetemplate的html部分,就把CanvasEditor源码的html文件中粘过来
  3. 结合文档,把main.ts相关的工具函数内容放到你的index.vuescriptmethod部分(某些引入报错就把相关报错的文件找到),并引入到自己的CanvasEditor文件夹下(由于我的项目用的js所以我复用main.ts的相关方法时转换为了js)
  4. 新建componments文件夹,把源码中的dialogsignature文件夹放到里面(记得文件引用的assets文件粘到自己项目中,且更改引用路径,否则会报错)
  5. option.js是参照官网的相关样式默认配置
  6. style.css也是源码同名文件粘过来的
  7. 缺失的相关静态资源,根据报错从源码中粘过来即可,
  8. 封装完成后,自定义传值、调用接口等逻辑,就根据自己需要自主发挥吧

集成组件目录结构:如图
在这里插入图片描述

index.vue文件如下:

说明:
1、有很多注释的代码,是因为我不需要而已,自己需要的话 自己放开注释即可;
2、为了更好的作为子组件引入,我更改了样式在style标签内,可自行根据需要修改
3、由于我的项目用的js所以我复用main.ts的相关方法时转换为了js

 
<style lang="scss" scoped>
.footer {position: static;
}
.menu {position: absolute;top: 0;left: 0;
}
.el-footer {background-color: #f2f4f7;text-align: center;line-height: 30px!important; /* 调整footer高度 */
}
.content {position: static;display: flex;flex-direction: column;height: calc(100% - 90px); /* 减去header和footer的高度 */background-color: #f2f4f7;overflow-y: auto; /* 仅让Main区域可滚动 */flex-grow: 1; /* 让Main区域占据剩余空间 */
}
@media (max-width: 1220px) {.menu {display: flex;align-items: center;flex-wrap: wrap;justify-content: flex-start;}
}
#canvasEditor {display: flex;justify-content: center;background: #f2f4f7;
}
.el-header {position: relative;
}
.canvas-container {height: 100%;margin-top: 10px;
}
@import url('./components/dialog/dialog.css');
@import url('./components/signature/signature.css');
@import url('./style.css');</style>
<template><div class="canvas-container"><el-container style="height: 100%;"><el-header><div class="menu" editor-component="menu"><div class="menu-item"><div class="menu-item__save" title="保存"><i class="el-icon-s-claim" style="color:#646464"></i></div><div class="menu-item__undo"><i></i></div><div class="menu-item__redo"><i></i></div><div class="menu-item__painter" title="格式刷(双击可连续使用)"><i></i></div><div class="menu-item__format" title="清除格式"><i></i></div></div><div class="menu-divider"></div><div class="menu-item"><div class="menu-item__font"><span class="select" title="字体">微软雅黑</span><div class="options"><ul><li data-family="Microsoft YaHei" style="font-family:'Microsoft YaHei';">微软雅黑</li><li data-family="宋体" style="font-family:'宋体';">宋体</li><li data-family="黑体" style="font-family:'黑体';">黑体</li><li data-family="仿宋" style="font-family:'仿宋';">仿宋</li><li data-family="楷体" style="font-family:'楷体';">楷体</li><li data-family="等线" style="font-family:'等线';">等线</li><!-- <li data-family="华文琥珀" style="font-family:'华文琥珀';">华文琥珀</li><li data-family="华文楷体" style="font-family:'华文楷体';">华文楷体</li><li data-family="华文隶书" style="font-family:'华文隶书';">华文隶书</li><li data-family="华文新魏" style="font-family:'华文新魏';">华文新魏</li><li data-family="华文行楷" style="font-family:'华文行楷';">华文行楷</li><li data-family="华文中宋" style="font-family:'华文中宋';">华文中宋</li><li data-family="华文彩云" style="font-family:'华文彩云';">华文彩云</li> --><li data-family="Arial" style="font-family:'Arial';">Arial</li><li data-family="Segoe UI" style="font-family:'Segoe UI';">Segoe UI</li><li data-family="Ink Free" style="font-family:'Ink Free';">Ink Free</li><li data-family="Fantasy" style="font-family:'Fantasy';">Fantasy</li></ul></div></div><div class="menu-item__size"><span class="select" title="字体">小四</span><div class="options"><ul><li data-size="56">初号</li><li data-size="48">小初</li><li data-size="34">一号</li><li data-size="32">小一</li><li data-size="29">二号</li><li data-size="24">小二</li><li data-size="21">三号</li><li data-size="20">小三</li><li data-size="18">四号</li><li data-size="16">小四</li><li data-size="14">五号</li><li data-size="12">小五</li><li data-size="10">六号</li><li data-size="8">小六</li><li data-size="7">七号</li><li data-size="6">八号</li></ul></div></div><div class="menu-item__size-add"><i></i></div><div class="menu-item__size-minus"><i></i></div><div class="menu-item__bold"><i></i></div><div class="menu-item__italic"><i></i></div><div class="menu-item__underline"><i></i></div><div class="menu-item__strikeout" title="删除线(Ctrl+Shift+X)"><i></i></div><div class="menu-item__superscript"><i></i></div><div class="menu-item__subscript"><i></i></div><div class="menu-item__color" title="字体颜色"><i></i><span></span><input type="color" id="color" /></div><div class="menu-item__highlight" title="高亮"><i></i><span></span><input type="color" id="highlight"></div></div><div class="menu-divider"></div><div class="menu-item"><div class="menu-item__title"><i></i><span class="select" title="切换标题">正文</span><div class="options"><ul><li style="font-size:16px;">正文</li><li data-level="first" style="font-size:26px;">标题1</li><li data-level="second" style="font-size:24px;">标题2</li><li data-level="third" style="font-size:22px;">标题3</li><li data-level="fourth" style="font-size:20px;">标题4</li><li data-level="fifth" style="font-size:18px;">标题5</li><li data-level="sixth" style="font-size:16px;">标题6</li></ul></div></div><div class="menu-item__left"><i></i></div><div class="menu-item__center"><i></i></div><div class="menu-item__right"><i></i></div><div class="menu-item__alignment"><i></i></div><div class="menu-item__row-margin"><i title="行间距"></i><div class="options"><ul><li data-rowmargin='1'>1</li><li data-rowmargin="1.25">1.25</li><li data-rowmargin="1.5">1.5</li><li data-rowmargin="1.75">1.75</li><li data-rowmargin="2">2</li><li data-rowmargin="2.5">2.5</li><li data-rowmargin="3">3</li></ul></div></div><div class="menu-item__list"><i></i><div class="options"><ul><li><label>取消列表</label></li><li data-list-type="ol" data-list-style='decimal'><label>有序列表:</label><ol><li>________</li></ol></li><li data-list-type="ul" data-list-style='disc'><label>实心圆点列表:</label><ul style="list-style-type: disc;"><li>________</li></ul></li><li data-list-type="ul" data-list-style='circle'><label>空心圆点列表:</label><ul style="list-style-type: circle;"><li>________</li></ul></li><li data-list-type="ul" data-list-style='square'><label>空心方块列表:</label><ul style="list-style-type: square;"><li>________</li></ul></li></ul></div></div></div><div class="menu-divider"></div><div class="menu-item"><!-- <div class="menu-item__table"><i title="表格"></i></div> --><!-- <div class="menu-item__table__collapse"><div class="table-close">×</div><div class="table-title"><span class="table-select">插入</span><span>表格</span></div><div class="table-panel"></div></div> --><!-- <div class="menu-item__image"><i title="图片"></i><input type="file" id="image" accept=".png, .jpg, .jpeg, .svg, .gif"></div> --><div class="menu-item__hyperlink"><i title="超链接"></i></div><div class="menu-item__separator"><i title="分割线"></i><div class="options"><ul><li data-separator='0,0'><i></i></li><li data-separator="1,1"><i></i></li><li data-separator="3,1"><i></i></li><li data-separator="4,4"><i></i></li><li data-separator="7,3,3,3"><i></i></li><li data-separator="6,2,2,2,2,2"><i></i></li></ul></div></div><!-- <div class="menu-item__watermark"><i title="水印(添加、删除)"></i><div class="options"><ul><li data-menu="add">添加水印</li><li data-menu="delete">删除水印</li></ul></div></div><div class="menu-item__codeblock" title="代码块"><i></i></div><div class="menu-item__page-break" title="分页符"><i></i></div><div class="menu-item__control"><i title="控件"></i><div class="options"><ul><li data-control='text'>文本</li><li data-control="select">列举</li><li data-control="checkbox">复选框</li></ul></div></div><div class="menu-item__checkbox" title="复选框"><i></i></div><div class="menu-item__latex" title="LateX"><i></i></div><div class="menu-item__date"><i title="日期"></i><div class="options"><ul><li data-format="yyyy-MM-dd"></li><li data-format="yyyy-MM-dd hh:mm:ss"></li></ul></div></div><div class="menu-item__block" title="内容块"><i></i></div> --></div><div class="menu-divider"></div><div class="menu-item"><div class="menu-item__search" data-menu="search"><i></i></div><div class="menu-item__search__collapse" data-menu="search"><div class="menu-item__search__collapse__search"><input type="text" /><label class="search-result"></label><div class="arrow-left"><i></i></div><div class="arrow-right"><i></i></div><span>×</span></div><div class="menu-item__search__collapse__replace"><input type="text"><button>替换</button></div></div><!-- <div class="menu-item__print" data-menu="print"><i></i></div> --></div></div></el-header><el-main class="content"><!-- <div class="catalog" editor-component="catalog"><div class="catalog__header"><span>目录</span><div class="catalog__header__close"><i></i></div></div><div class="catalog__main"></div></div> --><div id="canvasEditor" class="canvas-editor" editor-component="main"></div><!-- <div class="comment" editor-component="comment"></div> --></el-main><el-footer style="height: 30px;"> <div class="footer" editor-component="footer"><div><!-- <div class="catalog-mode" title="目录"><i></i></div> --><div class="page-mode"><i title="页面模式(分页、连页)"></i><div class="options"><ul><li data-page-mode="paging" class="active">分页</li><li data-page-mode="continuity">连页</li></ul></div></div><span>可见页码:<span class="page-no-list">1</span></span><span>页面:<span class="page-no">1</span>/<span class="page-size">1</span></span><span>字数:<span class="word-count">0</span></span></div><div class="editor-mode" title="编辑模式(编辑、清洁、只读、表单)">编辑模式</div><div><div class="page-scale-minus" title="缩小(Ctrl+-)"><i></i></div><span class="page-scale-percentage" title="显示比例(点击可复原Ctrl+0)">100%</span><div class="page-scale-add" title="放大(Ctrl+=)"><i></i></div><div class="paper-size"><i title="纸张类型"></i><div class="options"><ul><li data-paper-size="794*1123" class="active">A4</li><li data-paper-size="1593*2251">A2</li><li data-paper-size="1125*1593">A3</li><li data-paper-size="565*796">A5</li><li data-paper-size="412*488">5号信封</li><li data-paper-size="450*866">6号信封</li><li data-paper-size="609*862">7号信封</li><li data-paper-size="862*1221">9号信封</li><li data-paper-size="813*1266">法律用纸</li><li data-paper-size="813*1054">信纸</li></ul></div></div><div class="paper-direction"><i title="纸张方向"></i><div class="options"><ul><li data-paper-direction="vertical" class="active">纵向</li><li data-paper-direction="horizontal">横向</li></ul></div></div><div class="paper-margin" title="页边距"><i></i></div><div class="fullscreen" title="全屏显示"><i></i></div></div></div></el-footer></el-container></div>
</template><script>
import Editor from '@hufe921/canvas-editor'
import { Dialog } from './components/dialog/Dialog'
import { Signature } from './components/signature/Signature'
import  {IEditorOption, ITableOption,IHeader, IFooter } from './options'import {BlockType,Command,ControlType,EditorMode,EditorZone,ElementType,IBlock,ICatalogItem,IElement,KeyMap,ListStyle,ListType,PageMode,PaperDirection,RowFlex,TitleLevel,splitText } from '@hufe921/canvas-editor'export default {data() {return {editorRef: null,isApple: typeof navigator !== 'undefined' && /Mac OS X/.test(navigator.userAgent),// 编辑模式modeList: [{mode: EditorMode.READONLY,name: '只读模式'},{mode: EditorMode.EDIT,name: '编辑模式'}],header: [],// 页眉配置main: [],// 主要编辑内容footer: [],// 页脚信息options: IEditorOption,// 批注 TODOcommentList: []};},props: {// 编辑模式editMode: {type: String},// html数据htmlData: {type: String},// 后端传过来的保存html的JSON数据(用于回显)docJson: {type: String}},watch: {// 监听父组件传过来的编辑模式,设置模式editMode: {handler (val) {if(this.editorRef) {this.editorRef.command.executeMode(val)// 设置模式const modeElement = document.querySelector('.editor-mode')modeElement.innerText = this.modeList.filter((item) => item.mode == val).map((data) => data.name) || ''// 设置菜单栏权限视觉反馈const isReadonly = val === EditorMode.READONLYconst enableMenuList = ['search', 'print']document.querySelectorAll('.menu-item>div').forEach(dom => {const menu = dom.dataset.menuisReadonly && (!menu || !enableMenuList.includes(menu))? dom.classList.add('disable'): dom.classList.remove('disable')})}},deep: true}},methods:{debounce(func, delay) {let timer;return function(...args) {if (timer) {window.clearTimeout(timer);}timer = window.setTimeout(() => {func.apply(this, args);}, delay);};}},mounted () {const isApple = typeof navigator !== 'undefined' && /Mac OS X/.test(navigator.userAgent)const instance =  new Editor(document.querySelector('.canvas-editor'),{header: this.header,main: this.main,footer: this.footer},this.options)this.editorRef = instance;// cypress使用Reflect.set(window, 'editor', instance)// 回显编辑器数据if(this.docJson !== null) {// 通过getValue来的数据回显页面(因为用html回显页面会丢掉font-family,官网git issue有解释)instance.command.executeSetValue({main: JSON.parse(this.docJson)})} else {// 处理后端返回的html字符串// 先替换 \r\n 为 空格,以统一处理空格问题let step1 = this.htmlData.replace(/\r\n/g, ' ');// 然后替换 \\\" 为 \" ,确保样式字符串内的引号正确let step2 = step1.replace(/\\\"/g, '"');// 接着替换 \\ 为 空字符,去掉其他不必要的转义let cleanedHtml = step2.replace(/\\+/g, '');// 设置Word模板数据instance.command.executeSetHTML({main: cleanedHtml})}// 菜单弹窗销毁window.addEventListener('click',evt => {const visibleDom = document.querySelector('.visible')if (!visibleDom || visibleDom.contains(evt.target)) returnvisibleDom.classList.remove('visible')},{capture: true})/*工具栏方法*/// 1.保存(自定义)const saveDom = document.querySelector('.menu-item__save');saveDom.title = `保存(${this.isApple ? '⌘' : 'Ctrl'}+S)`;saveDom.onclick = () => {const value = instance.command.getValue(this.options)const htmlVal = instance.command.getHTML()this.$emit('save', htmlVal)// 保存数据传给父组件};// 快捷键保存instance.listener.saved = (payload) => {console.log('elementList: ', payload)this.$emit('save', htmlVal)// 保存数据传给父组件}// 2. | 撤销 | 重做 | 格式刷 | 清除格式 |const undoDom = document.querySelector('.menu-item__undo')undoDom.title = `撤销(${isApple ? '⌘' : 'Ctrl'}+Z)`undoDom.onclick = function () {console.log('undo')instance.command.executeUndo()}const redoDom = document.querySelector('.menu-item__redo')redoDom.title = `重做(${isApple ? '⌘' : 'Ctrl'}+Y)`redoDom.onclick = function () {console.log('redo')instance.command.executeRedo()}const painterDom = document.querySelector('.menu-item__painter')painterDom.onclick = function () {console.log('painter')instance.command.executePainter({isDblclick: false})}painterDom.ondblclick = function () {console.log('painter')instance.command.executePainter({isDblclick: true})}document.querySelector('.menu-item__format').onclick =function () {console.log('format')instance.command.executeFormat()}// 3. | 字体 | 字体变大 | 字体变小 | 加粗 | 斜体 | 下划线 | 删除线 | 上标 | 下标 | 字体颜色 | 背景色 |const fontDom = document.querySelector('.menu-item__font')const fontSelectDom = fontDom.querySelector('.select')const fontOptionDom = fontDom.querySelector('.options')fontDom.onclick = function () {console.log('font')fontOptionDom.classList.toggle('visible')}fontOptionDom.onclick = function (evt) {const li = evt.targetinstance.command.executeFont(li.dataset.family)}const sizeSetDom = document.querySelector('.menu-item__size')const sizeSelectDom = sizeSetDom.querySelector('.select')const sizeOptionDom = sizeSetDom.querySelector('.options')sizeSetDom.title = `设置字号`sizeSetDom.onclick = function () {console.log('size')sizeOptionDom.classList.toggle('visible')}sizeOptionDom.onclick = function (evt) {const li = evt.targetinstance.command.executeSize(Number(li.dataset.size))}const sizeAddDom = document.querySelector('.menu-item__size-add')sizeAddDom.title = `增大字号(${this.isApple ? '⌘' : 'Ctrl'}+[)`sizeAddDom.onclick = function () {console.log('size-add')instance.command.executeSizeAdd()}const sizeMinusDom = document.querySelector('.menu-item__size-minus')sizeMinusDom.title = `减小字号(${this.isApple ? '⌘' : 'Ctrl'}+])`sizeMinusDom.onclick = function () {console.log('size-minus')instance.command.executeSizeMinus()}const boldDom = document.querySelector('.menu-item__bold')boldDom.title = `加粗(${this.isApple ? '⌘' : 'Ctrl'}+B)`boldDom.onclick = function () {console.log('bold')instance.command.executeBold()}const italicDom =document.querySelector('.menu-item__italic')italicDom.title = `斜体(${this.isApple ? '⌘' : 'Ctrl'}+I)`italicDom.onclick = function () {console.log('italic')instance.command.executeItalic()}const underlineDom = document.querySelector('.menu-item__underline')underlineDom.title = `下划线(${this.isApple ? '⌘' : 'Ctrl'}+U)`underlineDom.onclick = function () {console.log('underline')instance.command.executeUnderline()}const strikeoutDom = document.querySelector('.menu-item__strikeout')strikeoutDom.onclick = function () {console.log('strikeout')instance.command.executeStrikeout()}const superscriptDom = document.querySelector('.menu-item__superscript')superscriptDom.title = `上标(${this.isApple ? '⌘' : 'Ctrl'}+Shift+,)`superscriptDom.onclick = function () {console.log('superscript')instance.command.executeSuperscript()}const subscriptDom = document.querySelector('.menu-item__subscript')subscriptDom.title = `下标(${this.isApple ? '⌘' : 'Ctrl'}+Shift+.)`subscriptDom.onclick = function () {console.log('subscript')instance.command.executeSubscript()}const colorControlDom = document.querySelector('#color')colorControlDom.oninput = function () {instance.command.executeColor(colorControlDom.value)}const colorDom = document.querySelector('.menu-item__color')const colorSpanDom = colorDom.querySelector('span')colorDom.onclick = function () {console.log('color')colorControlDom.click()}const highlightControlDom =document.querySelector('#highlight')highlightControlDom.oninput = function () {instance.command.executeHighlight(highlightControlDom.value)}const highlightDom = document.querySelector('.menu-item__highlight')const highlightSpanDom = highlightDom.querySelector('span')highlightDom.onclick = function () {console.log('highlight')highlightControlDom?.click()}const titleDom = document.querySelector('.menu-item__title')const titleSelectDom = titleDom.querySelector('.select')const titleOptionDom = titleDom.querySelector('.options')titleOptionDom.querySelectorAll('li').forEach((li, index) => {li.title = `Ctrl+${this.isApple ? 'Option' : 'Alt'}+${index}`})titleDom.onclick = function () {console.log('title')titleOptionDom.classList.toggle('visible')}titleOptionDom.onclick = function (evt) {const li = evt.targetconst level = li.dataset.levelinstance.command.executeTitle(level || null)}const leftDom = document.querySelector('.menu-item__left')leftDom.title = `左对齐(${this.isApple ? '⌘' : 'Ctrl'}+L)`leftDom.onclick = function () {console.log('left')instance.command.executeRowFlex(RowFlex.LEFT)}const centerDom =document.querySelector('.menu-item__center')centerDom.title = `居中对齐(${this.isApple ? '⌘' : 'Ctrl'}+E)`centerDom.onclick = function () {console.log('center')instance.command.executeRowFlex(RowFlex.CENTER)}const rightDom = document.querySelector('.menu-item__right')rightDom.title = `右对齐(${this.isApple ? '⌘' : 'Ctrl'}+R)`rightDom.onclick = function () {console.log('right')instance.command.executeRowFlex(RowFlex.RIGHT)}const alignmentDom = document.querySelector('.menu-item__alignment')alignmentDom.title = `两端对齐(${this.isApple ? '⌘' : 'Ctrl'}+J)`alignmentDom.onclick = function () {console.log('alignment')instance.command.executeRowFlex(RowFlex.ALIGNMENT)}const rowMarginDom = document.querySelector('.menu-item__row-margin')const rowOptionDom = rowMarginDom.querySelector('.options')rowMarginDom.onclick = function () {console.log('row-margin')rowOptionDom.classList.toggle('visible')}rowOptionDom.onclick = function (evt) {const li = evt.targetinstance.command.executeRowMargin(Number(li.dataset.rowmargin))}const listDom = document.querySelector('.menu-item__list')listDom.title = `列表(${this.isApple ? '⌘' : 'Ctrl'}+Shift+U)`const listOptionDom = listDom.querySelector('.options')listDom.onclick = function () {console.log('list')listOptionDom.classList.toggle('visible')}listOptionDom.onclick = function (evt) {const li = evt.targetconst listType = li.dataset.listType || nullconst listStyle = (li.dataset.listStyle)instance.command.executeList(listType, listStyle)}// 4. | 表格 | 图片 | 超链接 | 分割线 | 水印 | 代码块 | 分隔符 | 控件 | 复选框 | LaTeX | 日期选择器// const tableDom = document.querySelector('.menu-item__table')// const tablePanelContainer = document.querySelector(//   '.menu-item__table__collapse'// )// const tableClose = document.querySelector('.table-close')// const tableTitle = document.querySelector('.table-select')// const tablePanel = document.querySelector('.table-panel')// // 绘制行列// const tableCellList = []// for (let i = 0; i < 10; i++) {//   const tr = document.createElement('tr')//   tr.classList.add('table-row')//   const trCellList = []//   for (let j = 0; j < 10; j++) {//     const td = document.createElement('td')//     td.classList.add('table-cel')//     tr.append(td)//     trCellList.push(td)//   }//   tablePanel.append(tr)//   tableCellList.push(trCellList)// }// let colIndex = 0// let rowIndex = 0// // 移除所有格选择// function removeAllTableCellSelect() {//   tableCellList.forEach(tr => {//     tr.forEach(td => td.classList.remove('active'))//   })// }// // 设置标题内容// function setTableTitle(payload) {//   tableTitle.innerText = payload// }// // 恢复初始状态// function recoveryTable() {//   // 还原选择样式、标题、选择行列//   removeAllTableCellSelect()//   setTableTitle('插入')//   colIndex = 0//   rowIndex = 0//   // 隐藏panel//   tablePanelContainer.style.display = 'none'// }// tableDom.onclick = function () {//   console.log('table')//   tablePanelContainer.style.display = 'block'// }// tablePanel.onmousemove = function (evt) {//   const celSize = 16//   const rowMarginTop = 10//   const celMarginRight = 6//   const { offsetX, offsetY } = evt//   // 移除所有选择//   removeAllTableCellSelect()//   colIndex = Math.ceil(offsetX / (celSize + celMarginRight)) || 1//   rowIndex = Math.ceil(offsetY / (celSize + rowMarginTop)) || 1//   // 改变选择样式//   tableCellList.forEach((tr, trIndex) => {//     tr.forEach((td, tdIndex) => {//       if (tdIndex < colIndex && trIndex < rowIndex) {//         td.classList.add('active')//       }//     })//   })//   // 改变表格标题//   setTableTitle(`${rowIndex}×${colIndex}`)// }// tableClose.onclick = function () {//   recoveryTable()// }// tablePanel.onclick = function () {//   // 应用选择//   instance.command.executeInsertTable(rowIndex, colIndex)//   recoveryTable()// }// const imageDom = document.querySelector('.menu-item__image')// const imageFileDom = document.querySelector('#image')// imageDom.onclick = function () {//   imageFileDom.click()// }// imageFileDom.onchange = function () {//   const file = imageFileDom.files[0]//   const fileReader = new FileReader()//   fileReader.readAsDataURL(file)//   fileReader.onload = function () {//     // 计算宽高//     const image = new Image()//     const value = String(fileReader.result)//     image.src = value//     image.onload = function () {//       instance.command.executeImage({//         value,//         width: image.width,//         height: image.height//       })//       imageFileDom.value = ''//     }//   }// }const hyperlinkDom = document.querySelector('.menu-item__hyperlink')hyperlinkDom.onclick = function () {console.log('hyperlink')new Dialog({title: '超链接',data: [{type: 'text',label: '文本',name: 'name',required: true,placeholder: '请输入文本',value: instance.command.getRangeText()},{type: 'text',label: '链接',name: 'url',required: true,placeholder: '请输入链接'}],onConfirm: payload => {const name = payload.find(p => p.name === 'name')?.valueif (!name) returnconst url = payload.find(p => p.name === 'url')?.valueif (!url) returninstance.command.executeHyperlink({type: ElementType.HYPERLINK,value: '',url,valueList: splitText(name).map(n => ({value: n,size: 16}))})}})}const separatorDom = document.querySelector('.menu-item__separator')const separatorOptionDom =separatorDom.querySelector('.options')separatorDom.onclick = function () {console.log('separator')separatorOptionDom.classList.toggle('visible')}separatorOptionDom.onmousedown = function (evt) {let payload = []const li = evt.targetconst separatorDash = li.dataset.separator?.split(',').map(Number)if (separatorDash) {const isSingleLine = separatorDash.every(d => d === 0)if (!isSingleLine) {payload = separatorDash}}instance.command.executeSeparator(payload)}// const pageBreakDom = document.querySelector(//   '.menu-item__page-break'// )// pageBreakDom.onclick = function () {//   console.log('pageBreak')//   instance.command.executePageBreak()// }// const watermarkDom = document.querySelector(//   '.menu-item__watermark'// )// const watermarkOptionDom =//   watermarkDom.querySelector('.options')// watermarkDom.onclick = function () {//   console.log('watermark')//   watermarkOptionDom.classList.toggle('visible')// }// watermarkOptionDom.onmousedown = function (evt) {//   const li = evt.target//   const menu = li.dataset.menu//   watermarkOptionDom.classList.toggle('visible')//   if (menu === 'add') {//     new Dialog({//       title: '水印',//       data: [//         {//           type: 'text',//           label: '内容',//           name: 'data',//           required: true,//           placeholder: '请输入内容'//         },//         {//           type: 'color',//           label: '颜色',//           name: 'color',//           required: true,//           value: '#AEB5C0'//         },//         {//           type: 'number',//           label: '字体大小',//           name: 'size',//           required: true,//           value: '120'//         }//       ],//       onConfirm: payload => {//         const nullableIndex = payload.findIndex(p => !p.value)//         if (~nullableIndex) return//         const watermark = payload.reduce((pre, cur) => {//           pre[cur.name] = cur.value//           return pre//         }, {})//         instance.command.executeAddWatermark({//           data: watermark.data,//           color: watermark.color,//           size: Number(watermark.size)//         })//       }//     })//   } else {//     instance.command.executeDeleteWatermark()//   }// }// const codeblockDom = document.querySelector(//   '.menu-item__codeblock'// )// codeblockDom.onclick = function () {//   console.log('codeblock')//   new Dialog({//     title: '代码块',//     data: [//       {//         type: 'textarea',//         name: 'codeblock',//         placeholder: '请输入代码',//         width: 500,//         height: 300//       }//     ],//     onConfirm: payload => {//       const codeblock = payload.find(p => p.name === 'codeblock')?.value//       if (!codeblock) return//       const tokenList = prism.tokenize(codeblock, prism.languages.javascript)//       const formatTokenList = formatPrismToken(tokenList)//       const elementList = []//       for (let i = 0; i < formatTokenList.length; i++) {//         const formatToken = formatTokenList[i]//         const tokenStringList = splitText(formatToken.content)//         for (let j = 0; j < tokenStringList.length; j++) {//           const value = tokenStringList[j]//           const element = {//             value//           }//           if (formatToken.color) {//             element.color = formatToken.color//           }//           if (formatToken.bold) {//             element.bold = true//           }//           if (formatToken.italic) {//             element.italic = true//           }//           elementList.push(element)//         }//       }//       elementList.unshift({//         value: '\n'//       })//       instance.command.executeInsertElementList(elementList)//     }//   })// }// const controlDom = document.querySelector(//   '.menu-item__control'// )// const controlOptionDom = controlDom.querySelector('.options')// controlDom.onclick = function () {//   console.log('control')//   controlOptionDom.classList.toggle('visible')// }// controlOptionDom.onmousedown = function (evt) {//   controlOptionDom.classList.toggle('visible')//   const li = evt.target//   const type = li.dataset.control//   switch (type) {//     case ControlType.TEXT://       new Dialog({//         title: '文本控件',//         data: [//           {//             type: 'text',//             label: '占位符',//             name: 'placeholder',//             required: true,//             placeholder: '请输入占位符'//           },//           {//             type: 'text',//             label: '默认值',//             name: 'value',//             placeholder: '请输入默认值'//           }//         ],//         onConfirm: payload => {//           const placeholder = payload.find(//             p => p.name === 'placeholder'//           )?.value//           if (!placeholder) return//           const value = payload.find(p => p.name === 'value')?.value || ''//           instance.command.executeInsertElementList([//             {//               type: ElementType.CONTROL,//               value: '',//               control: {//                 type,//                 value: value//                   ? [//                       {//                         value//                       }//                     ]//                   : null,//                 placeholder//               }//             }//           ])//         }//       })//       break//     case ControlType.SELECT://       new Dialog({//         title: '列举控件',//         data: [//           {//             type: 'text',//             label: '占位符',//             name: 'placeholder',//             required: true,//             placeholder: '请输入占位符'//           },//           {//             type: 'text',//             label: '默认值',//             name: 'code',//             placeholder: '请输入默认值'//           },//           {//             type: 'textarea',//             label: '值集',//             name: 'valueSets',//             required: true,//             height: 100,//             placeholder: `请输入值集JSON,例:\n[{\n"value":"有",\n"code":"98175"\n}]`//           }//         ],//         onConfirm: payload => {//           const placeholder = payload.find(//             p => p.name === 'placeholder'//           )?.value//           if (!placeholder) return//           const valueSets = payload.find(p => p.name === 'valueSets')?.value//           if (!valueSets) return//           const code = payload.find(p => p.name === 'code')?.value//           instance.command.executeInsertElementList([//             {//               type: ElementType.CONTROL,//               value: '',//               control: {//                 type,//                 code,//                 value: null,//                 placeholder,//                 valueSets: JSON.parse(valueSets)//               }//             }//           ])//         }//       })//       break//     case ControlType.CHECKBOX://       new Dialog({//         title: '复选框控件',//         data: [//           {//             type: 'text',//             label: '默认值',//             name: 'code',//             placeholder: '请输入默认值,多个值以英文逗号分割'//           },//           {//             type: 'textarea',//             label: '值集',//             name: 'valueSets',//             required: true,//             height: 100,//             placeholder: `请输入值集JSON,例:\n[{\n"value":"有",\n"code":"98175"\n}]`//           }//         ],//         onConfirm: payload => {//           const valueSets = payload.find(p => p.name === 'valueSets')?.value//           if (!valueSets) return//           const code = payload.find(p => p.name === 'code')?.value//           instance.command.executeInsertElementList([//             {//               type: ElementType.CONTROL,//               value: '',//               control: {//                 type,//                 code,//                 value: null,//                 valueSets: JSON.parse(valueSets)//               }//             }//           ])//         }//       })//       break//     default://       break//   }// }// const checkboxDom = document.querySelector(//   '.menu-item__checkbox'// )// checkboxDom.onclick = function () {//   console.log('checkbox')//   instance.command.executeInsertElementList([//     {//       type: ElementType.CHECKBOX,//       checkbox: {//         value: false//       },//       value: ''//     }//   ])// }// const latexDom = document.querySelector('.menu-item__latex')// latexDom.onclick = function () {//   console.log('LaTeX')//   new Dialog({//     title: 'LaTeX',//     data: [//       {//         type: 'textarea',//         height: 100,//         name: 'value',//         placeholder: '请输入LaTeX文本'//       }//     ],//     onConfirm: payload => {//       const value = payload.find(p => p.name === 'value')?.value//       if (!value) return//       instance.command.executeInsertElementList([//         {//           type: ElementType.LATEX,//           value//         }//       ])//     }//   })// }// const dateDom = document.querySelector('.menu-item__date')// const dateDomOptionDom = dateDom.querySelector('.options')// dateDom.onclick = function () {//   console.log('date')//   dateDomOptionDom.classList.toggle('visible')//   // 定位调整//   const bodyRect = document.body.getBoundingClientRect()//   const dateDomOptionRect = dateDomOptionDom.getBoundingClientRect()//   if (dateDomOptionRect.left + dateDomOptionRect.width > bodyRect.width) {//     dateDomOptionDom.style.right = '0px'//     dateDomOptionDom.style.left = 'unset'//   } else {//     dateDomOptionDom.style.right = 'unset'//     dateDomOptionDom.style.left = '0px'//   }//   // 当前日期//   const date = new Date()//   const year = date.getFullYear().toString()//   const month = (date.getMonth() + 1).toString().padStart(2, '0')//   const day = date.getDate().toString().padStart(2, '0')//   const hour = date.getHours().toString().padStart(2, '0')//   const minute = date.getMinutes().toString().padStart(2, '0')//   const second = date.getSeconds().toString().padStart(2, '0')//   const dateString = `${year}-${month}-${day}`//   const dateTimeString = `${dateString} ${hour}:${minute}:${second}`//   dateDomOptionDom.querySelector('li:first-child').innerText = dateString//   dateDomOptionDom.querySelector('li:last-child').innerText = dateTimeString// }// dateDomOptionDom.onmousedown = function (evt) {//   const li = evt.target//   const dateFormat = li.dataset.format//   dateDomOptionDom.classList.toggle('visible')//   instance.command.executeInsertElementList([//     {//       type: ElementType.DATE,//       value: '',//       dateFormat,//       valueList: [//         {//           value: li.innerText.trim()//         }//       ]//     }//   ])// }// const blockDom = document.querySelector('.menu-item__block')// blockDom.onclick = function () {//   console.log('block')//   new Dialog({//     title: '内容块',//     data: [//       {//         type: 'select',//         label: '类型',//         name: 'type',//         value: 'iframe',//         required: true,//         options: [//           {//             label: '网址',//             value: 'iframe'//           },//           {//             label: '视频',//             value: 'video'//           }//         ]//       },//       {//         type: 'number',//         label: '宽度',//         name: 'width',//         placeholder: '请输入宽度(默认页面内宽度)'//       },//       {//         type: 'number',//         label: '高度',//         name: 'height',//         required: true,//         placeholder: '请输入高度'//       },//       {//         type: 'textarea',//         label: '地址',//         height: 100,//         name: 'value',//         required: true,//         placeholder: '请输入地址'//       }//     ],//     onConfirm: payload => {//       const type = payload.find(p => p.name === 'type')?.value//       if (!type) return//       const value = payload.find(p => p.name === 'value')?.value//       if (!value) return//       const width = payload.find(p => p.name === 'width')?.value//       const height = payload.find(p => p.name === 'height')?.value//       if (!height) return//       const block = {//         type: null//       }//       if (block.type === BlockType.IFRAME) {//         block.iframeBlock = {//           src: value//         }//       } else if (block.type === BlockType.VIDEO) {//         block.videoBlock = {//           src: value//         }//       }//       const blockElemen = {//         type: ElementType.BLOCK,//         value: '',//         height: Number(height),//         block//       }//       if (width) {//         blockElement.width = Number(width)//       }//       instance.command.executeInsertElementList([blockElement])//     }//   })// }// 5. | 搜索&替换 | 打印 |const searchCollapseDom = document.querySelector('.menu-item__search__collapse')const searchInputDom = document.querySelector('.menu-item__search__collapse__search input')const replaceInputDom = document.querySelector('.menu-item__search__collapse__replace input')const searchDom =document.querySelector('.menu-item__search')searchDom.title = `搜索与替换(${isApple ? '⌘' : 'Ctrl'}+F)`const searchResultDom =searchCollapseDom.querySelector('.search-result')function setSearchResult() {const result = instance.command.getSearchNavigateInfo()if (result) {const { index, count } = resultsearchResultDom.innerText = `${index}/${count}`} else {searchResultDom.innerText = ''}}searchDom.onclick = function () {console.log('search')searchCollapseDom.style.display = 'block'const bodyRect = document.body.getBoundingClientRect()const searchRect = searchDom.getBoundingClientRect()const searchCollapseRect = searchCollapseDom.getBoundingClientRect()if (searchRect.left + searchCollapseRect.width > bodyRect.width) {searchCollapseDom.style.right = '0px'searchCollapseDom.style.left = 'unset'} else {searchCollapseDom.style.right = 'unset'}searchInputDom.focus()}searchCollapseDom.querySelector('span').onclick =function () {searchCollapseDom.style.display = 'none'searchInputDom.value = ''replaceInputDom.value = ''instance.command.executeSearch(null)setSearchResult()}searchInputDom.oninput = function () {instance.command.executeSearch(searchInputDom.value || null)setSearchResult()}searchInputDom.onkeydown = function (evt) {if (evt.key === 'Enter') {instance.command.executeSearch(searchInputDom.value || null)setSearchResult()}}searchCollapseDom.querySelector('button').onclick =function () {const searchValue = searchInputDom.valueconst replaceValue = replaceInputDom.valueif (searchValue && replaceValue && searchValue !== replaceValue) {instance.command.executeReplace(replaceValue)}}searchCollapseDom.querySelector('.arrow-left').onclick =function () {instance.command.executeSearchNavigatePre()setSearchResult()}searchCollapseDom.querySelector('.arrow-right').onclick =function () {instance.command.executeSearchNavigateNext()setSearchResult()}// const printDom = document.querySelector('.menu-item__print')// printDom.title = `打印(${isApple ? '⌘' : 'Ctrl'}+P)`// printDom.onclick = function () {//   console.log('print')//   instance.command.executePrint()// }// 6. 目录显隐 | 页面模式 | 纸张缩放 | 纸张大小 | 纸张方向 | 页边距 | 全屏// async function updateCatalog() {//   const catalog = await instance.command.getCatalog()//   const catalogMainDom =//     document.querySelector('.catalog__main')//   catalogMainDom.innerHTML = ''//   if (catalog) {//     const appendCatalog = (//       parent,//       catalogItems//     ) => {//       for (let c = 0; c < catalogItems.length; c++) {//         const catalogItem = catalogItems[c]//         const catalogItemDom = document.createElement('div')//         catalogItemDom.classList.add('catalog-item')//         // 渲染//         const catalogItemContentDom = document.createElement('div')//         catalogItemContentDom.classList.add('catalog-item__content')//         const catalogItemContentSpanDom = document.createElement('span')//         catalogItemContentSpanDom.innerText = catalogItem.name//         catalogItemContentDom.append(catalogItemContentSpanDom)//         // 定位//         catalogItemContentDom.onclick = () => {//           instance.command.executeLocationCatalog(catalogItem.id)//         }//         catalogItemDom.append(catalogItemContentDom)//         if (catalogItem.subCatalog && catalogItem.subCatalog.length) {//           appendCatalog(catalogItemDom, catalogItem.subCatalog)//         }//         // 追加//         parent.append(catalogItemDom)//       }//     }//     appendCatalog(catalogMainDom, catalog)//   }// }// let isCatalogShow = true// const catalogDom = document.querySelector('.catalog')// const catalogModeDom =//   document.querySelector('.catalog-mode')// const catalogHeaderCloseDom = document.querySelector(//   '.catalog__header__close'// )// const switchCatalog = () => {//   console.log('目录', isCatalogShow)//   isCatalogShow = !isCatalogShow//   if (isCatalogShow) {//     console.log('目录', isCatalogShow)//     catalogDom.style.display = 'block'//     updateCatalog()//   } else {//     catalogDom.style.display = 'none'//   }// }// catalogModeDom.onclick = switchCatalog// catalogHeaderCloseDom.onclick = switchCatalogconst pageModeDom = document.querySelector('.page-mode')const pageModeOptionsDom =pageModeDom.querySelector('.options')pageModeDom.onclick = function () {pageModeOptionsDom.classList.toggle('visible')}pageModeOptionsDom.onclick = function (evt) {const li = evt.targetinstance.command.executePageMode(li.dataset.pageMode)}document.querySelector('.page-scale-percentage').onclick =function () {console.log('page-scale-recovery')instance.command.executePageScaleRecovery()}document.querySelector('.page-scale-minus').onclick =function () {console.log('page-scale-minus')instance.command.executePageScaleMinus()}document.querySelector('.page-scale-add').onclick =function () {console.log('page-scale-add')instance.command.executePageScaleAdd()}// 纸张大小const paperSizeDom = document.querySelector('.paper-size')const paperSizeDomOptionsDom =paperSizeDom.querySelector('.options')paperSizeDom.onclick = function () {paperSizeDomOptionsDom.classList.toggle('visible')}paperSizeDomOptionsDom.onclick = function (evt) {const li = evt.targetconst paperType = li.dataset.paperSizeconst [width, height] = paperType.split('*').map(Number)instance.command.executePaperSize(width, height)// 纸张状态回显paperSizeDomOptionsDom.querySelectorAll('li').forEach(child => child.classList.remove('active'))li.classList.add('active')}// 纸张方向const paperDirectionDom =document.querySelector('.paper-direction')const paperDirectionDomOptionsDom =paperDirectionDom.querySelector('.options')paperDirectionDom.onclick = function () {paperDirectionDomOptionsDom.classList.toggle('visible')}paperDirectionDomOptionsDom.onclick = function (evt) {const li = evt.targetconst paperDirection = li.dataset.paperDirectioninstance.command.executePaperDirection(paperDirection)// 纸张方向状态回显paperDirectionDomOptionsDom.querySelectorAll('li').forEach(child => child.classList.remove('active'))li.classList.add('active')}// 页面边距const paperMarginDom =document.querySelector('.paper-margin')paperMarginDom.onclick = function () {const [topMargin, rightMargin, bottomMargin, leftMargin] =instance.command.getPaperMargin()new Dialog({title: '页边距',data: [{type: 'text',label: '上边距',name: 'top',required: true,value: `${topMargin}`,placeholder: '请输入上边距'},{type: 'text',label: '下边距',name: 'bottom',required: true,value: `${bottomMargin}`,placeholder: '请输入下边距'},{type: 'text',label: '左边距',name: 'left',required: true,value: `${leftMargin}`,placeholder: '请输入左边距'},{type: 'text',label: '右边距',name: 'right',required: true,value: `${rightMargin}`,placeholder: '请输入右边距'}],onConfirm: payload => {const top = payload.find(p => p.name === 'top')?.valueif (!top) returnconst bottom = payload.find(p => p.name === 'bottom')?.valueif (!bottom) returnconst left = payload.find(p => p.name === 'left')?.valueif (!left) returnconst right = payload.find(p => p.name === 'right')?.valueif (!right) returninstance.command.executeSetPaperMargin([Number(top),Number(right),Number(bottom),Number(left)])}})}// 全屏const fullscreenDom = document.querySelector('.fullscreen')fullscreenDom.onclick = toggleFullscreenwindow.addEventListener('keydown', evt => {if (evt.key === 'F11') {toggleFullscreen()evt.preventDefault()}})document.addEventListener('fullscreenchange', () => {fullscreenDom.classList.toggle('exist')})function toggleFullscreen() {console.log('fullscreen')if (!document.fullscreenElement) {document.documentElement.requestFullscreen()} else {document.exitFullscreen()}}// 7. 编辑器使用模式let modeIndex = 0const modeList = [{mode: EditorMode.READONLY,name: '只读模式'},{mode: EditorMode.EDIT,name: '编辑模式'},{mode: EditorMode.CLEAN,name: '清洁模式'},{mode: EditorMode.FORM,name: '表单模式'},{mode: EditorMode.PRINT,name: '打印模式'}]const modeElement = document.querySelector('.editor-mode')// 初始设置只读模式const { name, mode } = modeList[modeIndex]modeElement.innerText = nameinstance.command.executeMode(mode)// 设置菜单栏权限视觉反馈const isReadonly = mode === EditorMode.READONLYconst enableMenuList = ['search', 'print']document.querySelectorAll('.menu-item>div').forEach(dom => {const menu = dom.dataset.menuisReadonly && (!menu || !enableMenuList.includes(menu))? dom.classList.add('disable'): dom.classList.remove('disable')})// modeElement.onclick = function () {//   // 模式选择循环//   modeIndex === modeList.length - 1 ? (modeIndex = 0) : modeIndex++//   // 设置模式//   const { name, mode } = modeList[modeIndex]//   modeElement.innerText = name//   console.log(1212, name)//   instance.command.executeMode(mode)//   // 设置菜单栏权限视觉反馈//   const isReadonly = mode === EditorMode.READONLY//   const enableMenuList = ['search', 'print']//   document.querySelectorAll('.menu-item>div').forEach(dom => {//     const menu = dom.dataset.menu//     isReadonly && (!menu || !enableMenuList.includes(menu))//       ? dom.classList.add('disable')//       : dom.classList.remove('disable')//   })// }// 模拟批注// const commentDom = document.querySelector('.comment')// const updateComment = async() => {//   const groupIds = await instance.command.getGroupIds()//   for (const comment of this.commentList) {//     const activeCommentDom = commentDom.querySelector(//       `.comment-item[data-id='${comment.id}']`//     )//     // 编辑器是否存在对应成组id//     if (!groupIds.includes(comment.id)) {//       // 当前dom是否存在-不存在则追加//       if (!activeCommentDom) {//         const commentItem = document.createElement('div')//         commentItem.classList.add('comment-item')//         commentItem.setAttribute('data-id', comment.id)//         commentItem.onclick = () => {//           instance.command.executeLocationGroup(comment.id)//         }//         commentDom.append(commentItem)//         // 选区信息//         const commentItemTitle = document.createElement('div')//         commentItemTitle.classList.add('comment-item__title')//         commentItemTitle.append(document.createElement('span'))//         const commentItemTitleContent = document.createElement('span')//         commentItemTitleContent.innerText = comment.rangeText//         commentItemTitle.append(commentItemTitleContent)//         const closeDom = document.createElement('i')//         closeDom.onclick = () => {//           instance.command.executeDeleteGroup(comment.id)//         }//         commentItemTitle.append(closeDom)//         commentItem.append(commentItemTitle)//         // 基础信息//         const commentItemInfo = document.createElement('div')//         commentItemInfo.classList.add('comment-item__info')//         const commentItemInfoName = document.createElement('span')//         commentItemInfoName.innerText = comment.userName//         const commentItemInfoDate = document.createElement('span')//         commentItemInfoDate.innerText = comment.createdDate//         commentItemInfo.append(commentItemInfoName)//         commentItemInfo.append(commentItemInfoDate)//         commentItem.append(commentItemInfo)//         // 详细评论//         const commentItemContent = document.createElement('div')//         commentItemContent.classList.add('comment-item__content')//         commentItemContent.innerText = comment.content//         commentItem.append(commentItemContent)//         commentDom.append(commentItem)//       }//     } else {//       // 编辑器内不存在对应成组id则dom则移除//       activeCommentDom?.remove()//     }//   }// }// 8. 内部事件监听instance.listener.rangeStyleChange = function (payload) {// 控件类型payload.type === ElementType.SUBSCRIPT? subscriptDom.classList.add('active'): subscriptDom.classList.remove('active')payload.type === ElementType.SUPERSCRIPT? superscriptDom.classList.add('active'): superscriptDom.classList.remove('active')payload.type === ElementType.SEPARATOR? separatorDom.classList.add('active'): separatorDom.classList.remove('active')separatorOptionDom.querySelectorAll('li').forEach(li => li.classList.remove('active'))if (payload.type === ElementType.SEPARATOR) {const separator = payload.dashArray.join(',') || '0,0'const curSeparatorDom = separatorOptionDom.querySelector(`[data-separator='${separator}']`)if (curSeparatorDom) {curSeparatorDom.classList.add('active')}}// 富文本fontOptionDom.querySelectorAll('li').forEach(li => li.classList.remove('active'))const curFontDom = fontOptionDom.querySelector(`[data-family='${payload.font}']`)if (curFontDom) {fontSelectDom.innerText = curFontDom.innerTextfontSelectDom.style.fontFamily = payload.fontcurFontDom.classList.add('active')}sizeOptionDom.querySelectorAll('li').forEach(li => li.classList.remove('active'))const curSizeDom = sizeOptionDom.querySelector(`[data-size='${payload.size}']`)if (curSizeDom) {sizeSelectDom.innerText = curSizeDom.innerTextcurSizeDom.classList.add('active')} else {sizeSelectDom.innerText = `${payload.size}`}payload.bold? boldDom.classList.add('active'): boldDom.classList.remove('active')payload.italic? italicDom.classList.add('active'): italicDom.classList.remove('active')payload.underline? underlineDom.classList.add('active'): underlineDom.classList.remove('active')payload.strikeout? strikeoutDom.classList.add('active'): strikeoutDom.classList.remove('active')if (payload.color) {colorDom.classList.add('active')colorControlDom.value = payload.colorcolorSpanDom.style.backgroundColor = payload.color} else {colorDom.classList.remove('active')colorControlDom.value = '#000000'colorSpanDom.style.backgroundColor = '#000000'}if (payload.highlight) {highlightDom.classList.add('active')highlightControlDom.value = payload.highlighthighlightSpanDom.style.backgroundColor = payload.highlight} else {highlightDom.classList.remove('active')highlightControlDom.value = '#ffff00'highlightSpanDom.style.backgroundColor = '#ffff00'}// 行布局leftDom.classList.remove('active')centerDom.classList.remove('active')rightDom.classList.remove('active')alignmentDom.classList.remove('active')if (payload.rowFlex && payload.rowFlex === 'right') {rightDom.classList.add('active')} else if (payload.rowFlex && payload.rowFlex === 'center') {centerDom.classList.add('active')} else if (payload.rowFlex && payload.rowFlex === 'alignment') {alignmentDom.classList.add('active')} else {leftDom.classList.add('active')}// 行间距rowOptionDom.querySelectorAll('li').forEach(li => li.classList.remove('active'))const curRowMarginDom = rowOptionDom.querySelector(`[data-rowmargin='${payload.rowMargin}']`)curRowMarginDom.classList.add('active')// 功能payload.undo? undoDom.classList.remove('no-allow'): undoDom.classList.add('no-allow')payload.redo? redoDom.classList.remove('no-allow'): redoDom.classList.add('no-allow')payload.painter? painterDom.classList.add('active'): painterDom.classList.remove('active')// 标题titleOptionDom.querySelectorAll('li').forEach(li => li.classList.remove('active'))if (payload.level) {const curTitleDom = titleOptionDom.querySelector(`[data-level='${payload.level}']`)titleSelectDom.innerText = curTitleDom.innerTextcurTitleDom.classList.add('active')} else {titleSelectDom.innerText = '正文'titleOptionDom.querySelector('li:first-child').classList.add('active')}// 列表listOptionDom.querySelectorAll('li').forEach(li => li.classList.remove('active'))if (payload.listType) {listDom.classList.add('active')const listType = payload.listTypeconst listStyle =payload.listType === ListType.OL ? ListStyle.DECIMAL : payload.listTypeconst curListDom = listOptionDom.querySelector(`[data-list-type='${listType}'][data-list-style='${listStyle}']`)if (curListDom) {curListDom.classList.add('active')}} else {listDom.classList.remove('active')}// 批注// commentDom//   .querySelectorAll('.comment-item')//   .forEach(commentItemDom => {//     commentItemDom.classList.remove('active')//   })// if (payload.groupIds) {//   const [id] = payload.groupIds//   const activeCommentDom = commentDom.querySelector(//     `.comment-item[data-id='${id}']`//   )//   if (activeCommentDom) {//     activeCommentDom.classList.add('active')//     scrollIntoView(commentDom, activeCommentDom)//   }// }}instance.listener.visiblePageNoListChange = function (payload) {const text = payload.map(i => i + 1).join('、')document.querySelector('.page-no-list').innerText = text}instance.listener.pageSizeChange = function (payload) {if(document.querySelector('.page-size')) {document.querySelector('.page-size').innerText = payload.toString()}}instance.listener.intersectionPageNoChange = function (payload) {document.querySelector('.page-no').innerText = `${payload + 1}`}instance.listener.pageScaleChange = function (payload) {document.querySelector('.page-scale-percentage').innerText = `${Math.floor(payload * 10 * 10)}%`}instance.listener.controlChange = function (payload) {const disableMenusInControlContext = ['table','hyperlink','separator','page-break']// 菜单操作权限disableMenusInControlContext.forEach(menu => {const menuDom = document.querySelector(`.menu-item__${menu}`)payload? menuDom.classList.add('disable'): menuDom.classList.remove('disable')})}instance.listener.pageModeChange = function (payload) {const activeMode = pageModeOptionsDom.querySelector(`[data-page-mode='${payload}']`)pageModeOptionsDom.querySelectorAll('li').forEach(li => li.classList.remove('active'))activeMode.classList.add('active')}const handleContentChange = async () => {this.$emit('isSave', true)// 字数const wordCount = await instance.command.getWordCount()document.querySelector('.word-count').innerText = `${wordCount || 0}`// 目录// if (isCatalogShow) {//   this.$nextTick(() => {//     updateCatalog()//   })// }// // 批注// this.$nextTick(() => {//   updateComment()// })}instance.listener.contentChange = this.debounce(handleContentChange, 200)handleContentChange()// 9. 右键菜单注册instance.register.contextMenuList([{name: '批注',when: payload => {return (!payload.isReadonly &&payload.editorHasSelection &&payload.zone === EditorZone.MAIN)},callback: (command) => {new Dialog({title: '批注',data: [{type: 'textarea',label: '批注',height: 100,name: 'value',required: true,placeholder: '请输入批注'}],onConfirm: payload => {const value = payload.find(p => p.name === 'value')?.valueif (!value) returnconst groupId = command.executeSetGroup()if (!groupId) returncommentList.push({id: groupId,content: value,userName: 'Hufe',rangeText: command.getRangeText(),createdDate: new Date().toLocaleString()})}})}},{name: '签名',icon: 'signature',when: payload => {return !payload.isReadonly && payload.editorTextFocus},callback: (command) => {new Signature({onConfirm(payload) {if (!payload) returnconst { value, width, height } = payloadif (!value || !width || !height) returncommand.executeInsertElementList([{value,width,height,type: ElementType.IMAGE}])}})}},{name: '格式整理',icon: 'word-tool',when: payload => {return !payload.isReadonly},callback: (command) => {command.executeWordTool()}}])// 10. 快捷键注册instance.register.shortcutList([{key: KeyMap.P,mod: true,isGlobal: true,callback: (command) => {command.executePrint()}},{key: KeyMap.F,mod: true,isGlobal: true,callback: (command) => {const text = command.getRangeText()searchDom.click()if (text) {searchInputDom.value = textinstance.command.executeSearch(text)setSearchResult()}}},{key: KeyMap.MINUS,ctrl: true,isGlobal: true,callback: (command) => {command.executePageScaleMinus()}},{key: KeyMap.EQUAL,ctrl: true,isGlobal: true,callback: (command) => {command.executePageScaleAdd()}},{key: KeyMap.ZERO,ctrl: true,isGlobal: true,callback: (command) => {command.executePageScaleRecovery()}}])  }
};
</script>

我的实现效果:
在这里插入图片描述
3、组件封装完成后,在其他父组件中使用

import CanvasEditor from '@/components/CanvasEditor/index';<CanvasEditor ref="wordEditor":editMode="type" :htmlData="htmlData" :docJson="docJson" :key="keys" @save="save" @isSave="getSave"></CanvasEditor>
htmlData: '', // 编辑器html数据
docJson: null, // 编辑器getValue数据
keys: new Date().getTime() // 给编辑器赋值不刷新的时候,改变key

我的完整CanvasEditor封装组件源码已附上,仅供参考!!!
可根据以上步骤自定义集成,多查阅官方文档即可。

end~
希望记录的问题能帮助到你~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/365177.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

直流无刷电机无感转子位置检测

1 无位置传感控制驱动工作原理 1.1 无刷直流电机工作原理(图 1) 电动机和电子驱动电路两部分形成了无刷直流电机。 电动机部分与传统的交流永磁同步电机基本相似。根据 驱动需求,无刷直流电机还需要位置传感器 1.2 无位置传感控制驱动 无刷直流电机的无位置传感控制驱动…

python解锁图片相似度的神奇力量

在这个信息爆炸的时代,图片成为了我们传递信息、表达情感和记录生活的重要方式。然而,面对海量的图片资源,如何快速准确地找到相似的图片,成为了一个亟待解决的问题。现在,让我们为您揭开图片相似度的神秘面纱,带您领略这一创新技术的魅力! 图片相似度技术,就像是一位…

10款好用不火的PC软件,真的超好用!

AI视频生成&#xff1a;小说文案智能分镜智能识别角色和场景批量Ai绘图自动配音添加音乐一键合成视频https://aitools.jurilu.com/市场上有很多软件&#xff0c;除了那些常见的大众化软件&#xff0c;还有很多不为人知的小众软件&#xff0c;它们的作用非常强大&#xff0c;简洁…

Vue.js 和 Node.js 全栈项目的运行与部署指南

Vue.js 和 Node.js 全栈项目的运行与部署指南 前言具体运行方式导入数据库初始化安装配置nodejs启动server后端启动client前端确保前后端正确连接 前言 本博客用来介绍一下一个包含前端和后端代码的全栈项目MoreMall&#xff0c;前端部分使用了 Vue.js&#xff0c;后端部分使用…

springboot 缓存框架Cache整合redis组成二级缓存

springboot 缓存框架Cache整合redis组成二级缓存 项目性能优化的解决方案除开硬件外的方案无非就是优化sql&#xff0c;减少sql 的执行时间&#xff0c;合理运用缓存让同样的请求和数据库之间的连接尽量减少&#xff0c;内存的处理速度肯定比直接查询数据库来的要快一些。今天就…

逻辑这回事(七)---- 器件基础

Xilinx FPGA创建了先进的硅模块(ASMBL)架构,以实现FPGA具有针对不同应用程序领域优化的各种功能组合的平台。通过这一创新,Xilinx提供了更多的设备选择,使客户能够为其特定设计选择具有正确的功能和功能组合的FPGA。ASMBL体系结构通过以下方式突破了传统的设计障碍:消除几…

使用Llama3/Qwen2等开源大模型,部署团队私有化Code Copilot和使用教程

目前市面上有不少基于大模型的 Code Copilot 产品&#xff0c;部分产品对于个人开发者来说可免费使用&#xff0c;比如阿里的通义灵码、百度的文心快码等。这些免费的产品均通过 API 的方式提供服务&#xff0c;因此调用时均必须联网、同时需要把代码、提示词等内容作为 API 的…

数据倾斜优化:Hive性能提升的核心

文章目录 1. 定义2. 数据倾斜2.1 Map2.2 Join2.3 Reduce 3. 写在最后 1. 定义 数据倾斜&#xff0c;也称为Data Skew&#xff0c;是在分布式计算环境中&#xff0c;由于数据分布不均匀导致某些任务处理的数据量远大于其他任务&#xff0c;从而形成性能瓶颈的现象。这种情况在H…

springboot 3.x相比之前版本有什么区别

Spring Boot 3.x相比之前的版本&#xff08;尤其是Spring Boot 2.x&#xff09;&#xff0c;主要存在以下几个显著的区别和新特性&#xff1a; Java版本要求&#xff1a; Spring Boot 3.x要求至少使用Java 17作为最低版本&#xff0c;同时已经通过了Java 19的测试&#xff0c;…

可信和可解释的大语言模型推理-RoG

大型语言模型&#xff08;LLM&#xff09;在复杂任务中表现出令人印象深刻的推理能力。然而&#xff0c;LLM在推理过程中缺乏最新的知识和经验&#xff0c;这可能导致不正确的推理过程&#xff0c;降低他们的表现和可信度。知识图谱(Knowledge graphs, KGs)以结构化的形式存储了…

马斯克的SpaceX发展历史:从濒临破产到全球领先

本文首发于公众号“AntDream”&#xff0c;欢迎微信搜索“AntDream”或扫描文章底部二维码关注&#xff0c;和我一起每天进步一点点 Space Exploration Technologies Corp.&#xff0c;简称SpaceX&#xff0c;是由埃隆马斯克&#xff08;Elon Musk&#xff09;于2002年创办的一…

百度Agent初体验(制作步骤+感想)

现在AI Agent很火&#xff0c;最近注册了一个百度Agent体验了一下&#xff0c;并做了个小实验&#xff0c;拿它和零一万物&#xff08;Yi Large&#xff09;和文心一言&#xff08;ERNIE-4.0-8K-latest&#xff09;阅读了相同的一篇网页资讯&#xff0c;输出资讯摘要&#xff0…

运维锅总详解Prometheus

本文尝试从Prometheus简介、架构、各重要组件详解、relable_configs最佳实践、性能能优化及常见高可用解决方案等方面对Prometheus进行详细阐述。希望对您有所帮助&#xff01; 一、Prometheus简介 Prometheus 是一个开源的系统监控和报警工具&#xff0c;最初由 SoundCloud …

[深入理解DDR] 总目录

依公知及经验整理&#xff0c;原创保护&#xff0c;禁止转载。 专栏 《深入理解DDR》 蓝色的是传送门&#xff0c;点击链接即可到达指定文章。 图。 DDR 分类 导论 [RAM] DRAM 导论&#xff1a;DDR4 | DDR5 | LPDDR5 | GDRR6 | HBM 应运而生 运存与内存&#xff1f;内存与存…

UE5蓝图快速实现打开网页与加群

蓝图节点&#xff1a;启动URL 直接将对应的网址输入&#xff0c;并使用即可快速打开对应的网页&#xff0c;qq、discord等群聊的加入也可以直接通过该节点来完成。 使用后会直接打开浏览器。

pc端制作一个顶部固定的菜单栏

效果 hsl颜色 hsl颜色在css中比较方便 https://www.w3school.com.cn/css/css_colors_hsl.asp 色相&#xff08;hue&#xff09;是色轮上从 0 到 360 的度数。0 是红色&#xff0c;120 是绿色&#xff0c;240 是蓝色。饱和度&#xff08;saturation&#xff09;是一个百分比值…

帮助你简易起步一个BLOG(博客搭建)项目

Blog项目 后端项目结构1. 项目初始化2. 详细步骤3.postman测试 前端1. 项目初始化2. 详细步骤 本章节是为了帮助你起步一个完整的前后端分离项目。 前端技术栈&#xff1a; react、vite、mantine、tailwind CSS、zustand、rxjs、threejs 后端技术栈&#xff1a;nodemon、nodej…

Django项目部署:uwsgi+daphne+nginx+vue部署

一、项目情况 项目根目录&#xff1a;/mnt/www/alert 虚拟环境目录&#xff1a;/mnt/www/venv/alert 激活虚拟环境&#xff1a;source /mnt/www/venv/alert/bin/activate 二、具体配置 1、uwsgi启动配置 根目录下&#xff1a;新增 uwsgi.ini 注意&#xff1a;使用9801端…

redis实战-添加商户缓存

为什么要使用缓存 言简意赅&#xff1a;速度快&#xff0c;好用缓存数据存储于代码中&#xff0c;而代码运行在内存中&#xff0c;内存的读写性能远高于磁盘&#xff0c;缓存可以大大降低用户访问并发量带来的服务器读写压力实际开发中&#xff0c;企业的数据量&#xff0c;少…

短视频矩阵系统:打造品牌影响力的新方式

一、短视频矩阵概念 短视频营销革命&#xff1a;一站式解决策略&#xff01;短视频矩阵系统是一款专为企业营销设计的高效工具&#xff0c;旨在通过整合和优化众多短视频平台资源&#xff0c;为企业呈现一个全面的短视频营销策略。该系统致力于协助企业以迅速且高效的方式制作…