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

本文主要是介绍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>

我的实现效果:
在这里插入图片描述

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

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

这篇关于vue项目集成CanvasEditor实现Word在线编辑器的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1090267

相关文章

轻量级在线服装3D定制引擎Myway简介

我写的面向web元宇宙轻量级系列引擎中的另外一个,在线3D定制引擎Myway 3D。 用于在线商品定制,比如个性化服装的定制、日常用品(如杯子)、家装(被套)等物品的在线定制。 特性列表: 可更换衣服款式,按需定制更换模型可实时更改材质颜色可实时添加文本,并可实时修改大小、颜色和角度,支持自定义字体可实时添加艺术图标,并可实时修改大小、颜色和角度,支持翻转、各种对齐可更改衣服图案,按需求定制

[word] word设置上标快捷键 #学习方法#其他#媒体

word设置上标快捷键 办公中,少不了使用word,这个是大家必备的软件,今天给大家分享word设置上标快捷键,希望在办公中能帮到您! 1、添加上标 在录入一些公式,或者是化学产品时,需要添加上标内容,按下快捷键Ctrl+shift++就能将需要的内容设置为上标符号。 word设置上标快捷键的方法就是以上内容了,需要的小伙伴都可以试一试呢!

用Microsoft.Extensions.Hosting 管理WPF项目.

首先引入必要的包: <ItemGroup><PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.2" /><PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0" /><PackageReference Include="Serilog

eclipse运行springboot项目,找不到主类

解决办法尝试了很多种,下载sts压缩包行不通。最后解决办法如图: help--->Eclipse Marketplace--->Popular--->找到Spring Tools 3---->Installed。

vue, 左右布局宽,可拖动改变

1:建立一个draggableMixin.js  混入的方式使用 2:代码如下draggableMixin.js  export default {data() {return {leftWidth: 330,isDragging: false,startX: 0,startWidth: 0,};},methods: {startDragging(e) {this.isDragging = tr

ONLYOFFICE 8.1 版本桌面编辑器测评

在现代办公环境中,办公软件的重要性不言而喻。从文档处理到电子表格分析,再到演示文稿制作,强大且高效的办公软件工具能够极大提升工作效率。ONLYOFFICE 作为一个功能全面且开源的办公软件套件,一直以来都受到广大用户的关注与喜爱。而其最新发布的 ONLYOFFICE 8.1 版本桌面编辑器,更是带来了诸多改进和新特性。本文将详细评测 ONLYOFFICE 8.1 版本桌面编辑器,探讨其在功能、用户

在线装修管理系统的设计

管理员账户功能包括:系统首页,个人中心,管理员管理,装修队管理,用户管理,装修管理,基础数据管理,论坛管理 前台账户功能包括:系统首页,个人中心,公告信息,论坛,装修,装修队 开发系统:Windows 架构模式:B/S JDK版本:Java JDK1.8 开发工具:IDEA(推荐) 数据库版本: mysql5.7 数据库可视化工具: navicat 服务器:SpringBoot自带 ap

通过SSH隧道实现通过远程服务器上外网

搭建隧道 autossh -M 0 -f -D 1080 -C -N user1@remotehost##验证隧道是否生效,查看1080端口是否启动netstat -tuln | grep 1080## 测试ssh 隧道是否生效curl -x socks5h://127.0.0.1:1080 -I http://www.github.com 将autossh 设置为服务,隧道开机启动

SpringBoot集成Netty,Handler中@Autowired注解为空

最近建了个技术交流群,然后好多小伙伴都问关于Netty的问题,尤其今天的问题最特殊,功能大概是要在Netty接收消息时把数据写入数据库,那个小伙伴用的是 Spring Boot + MyBatis + Netty,所以就碰到了Handler中@Autowired注解为空的问题 参考了一些大神的博文,Spring Boot非controller使用@Autowired注解注入为null的问题,得到

时序预测 | MATLAB实现LSTM时间序列未来多步预测-递归预测

时序预测 | MATLAB实现LSTM时间序列未来多步预测-递归预测 目录 时序预测 | MATLAB实现LSTM时间序列未来多步预测-递归预测基本介绍程序设计参考资料 基本介绍 MATLAB实现LSTM时间序列未来多步预测-递归预测。LSTM是一种含有LSTM区块(blocks)或其他的一种类神经网络,文献或其他资料中LSTM区块可能被描述成智能网络单元,因为