vue2项目集成canvas-editor文本编辑器

Banne Charlie 2024-08-21 14:05:02 阅读 66

vue2项目集成 canvas-editor 富文本编辑器; canvas-ediotr并不是一款开箱即用的插件, 需要通过下载源码来进行手动集成到项目中;

源码地址: https://github.com/Hufe921/canvas-editor

官方文档: https://hufe.club/canvas-editor-docs/guide/schema.html

本地运行结果:

在这里插入图片描述

Canvas-Editor环境配置

canvas-editor通过 vue3 + TypeScript 进行编写的, 考虑到大部分的 vue2项目中并没有集成TypeScript的环境, 要先进行环境的配置,需要配置的环境如下: npm install, 可能会出现报错,若是报错,建议 使用 npm clean 之后, 继续执行 npm install;

<code>package.json

"dependencies": { -- -->

"core-js": "^3.8.3",

"vue": "^2.6.14",

"@hufe921/canvas-editor": "^0.9.86",

"@types/prismjs": "^1.26.0",

"@typescript-eslint/eslint-plugin": "5.62.0",

"@typescript-eslint/parser": "5.62.0",

"css-loader": "^6.5.0",

"style-loader": "^2.0.0",

"ts-loader": "^9.5.1",

"vue-loader": "^15.9.7",

"webpack": "^5.74.0"

}

创建 tsconfig.json 服务于 TypeScript

tsconfig.json

{

"compilerOptions": {

"target": "es5",

"module": "esnext",

"lib": ["es2015","dom"],

"strict": true,

"jsx": "preserve",

"importHelpers": true,

"moduleResolution": "node",

"experimentalDecorators": true,

"allowSyntheticDefaultImports": true,

"esModuleInterop": true,

"sourceMap": true,

"baseUrl": "..",

"paths": {

"@/*": ["src/*"]

}

},

"include": [

"src/**/*.ts",

"src/**/*.tsx",

"src/**/*.vue",

"tests/**/*.ts",

"tests/**/*.tsx",

"tests/**/*.vue"],

"exclude": ["node_modules","dist"]

}

修改 vue.config.js文件

const { defineConfig } = require('@vue/cli-service')

module.exports = defineConfig({

transpileDependencies: true,

// 添加上 configureWebpack 配置

configureWebpack: {

module: {

rules: [

{

test: /\.ts$/,

loader: 'ts-loader',

options: { appendTsSuffixTo: [/\.vue$/] },

exclude: /node_modules/

}

]

},

resolve: {

extensions: ['.ts', '.js', '.vue', '.json'],

alias: {

'@': require('path').resolve(__dirname, 'src')

}

}

}

})

将 canvas-editor 源码中的部分文件, 复制过来

在这里插入图片描述

![[Pasted image 20240722134506.png]]

其中的 canvas.js 和 index.vue 文件是根据源代码中的 main.ts 和 index.html进行了变化, 用于服务当前的项目; components文件夹中只需获取 dialog.css文件 和 signature.css文件即可;

<code>index.vue

<template>

<div class="container">code>

<div class="menu" editor-component="menu">code>

<div class="menu-item disabled-btn" >code>

<div class="menu-item__undo">code>

<i></i>

</div>

<div class="menu-item__redo">code>

<i></i>

</div>

<div class="menu-item__painter" title="格式刷(双击可连续使用)">code>

<i></i>

</div>

<div class="menu-item__format" title="清除格式">code>

<i></i>

</div>

</div>

<div class="menu-divider "></div>code>

<div class="menu-item disabled-btn" >code>

<div class="menu-item__font">code>

<span class="select" title="字体">宋体</span>code>

<div class="options">code>

<ul>

<li data-family="宋体" style="font-family: '宋体';">宋体</li>code>

<li data-family="黑体" style="font-family: '黑体';">黑体</li>code>

<li data-family="Microsoft YaHei" style="font-family:'Microsoft YaHei';">微软雅黑</li>code>

<li data-family="Times New Roman" style="font-family:'Times New Roman';">Times New Roman</li>code>

<li data-family="华文宋体" style="font-family:'华文宋体';">华文宋体</li>code>

<li data-family="华文黑体" style="font-family:'华文黑体';">华文黑体</li>code>

<li data-family="华文仿宋" style="font-family:'华文仿宋';">华文仿宋</li>code>

<li data-family="华文楷体" style="font-family:'华文楷体';">华文楷体</li>code>

<li data-family="华文琥珀" style="font-family:'华文琥珀';">华文琥珀</li>code>

<li data-family="华文楷体" style="font-family:'华文楷体';">华文楷体</li>code>

<li data-family="华文隶书" style="font-family:'华文隶书';">华文隶书</li>code>

<li data-family="华文新魏" style="font-family:'华文新魏';">华文新魏</li>code>

<li data-family="华文行楷" style="font-family:'华文行楷';">华文行楷</li>code>

<li data-family="华文中宋" style="font-family:'华文中宋';">华文中宋</li>code>

<li data-family="华文彩云" style="font-family:'华文彩云';">华文彩云</li>code>

<li data-family="Arial" style="font-family:'Arial';">Arial</li>code>

<li data-family="Segoe UI" style="font-family:'Segoe UI';">Segoe UI</li>code>

<li data-family="Ink Free" style="font-family:'Ink Free';">Ink Free</li>code>

<li data-family="Fantasy" style="font-family:'Fantasy';">Fantasy</li>code>

</ul>

</div>

</div>

<div class="menu-item__size">code>

<span class="select" title="字体">小四</span>code>

<div class="options">code>

<ul>

<li data-size="56">初号</li>code>

<li data-size="48">小初</li>code>

<li data-size="34">一号</li>code>

<li data-size="32">小一</li>code>

<li data-size="29">二号</li>code>

<li data-size="24">小二</li>code>

<li data-size="21">三号</li>code>

<li data-size="20">小三</li>code>

<li data-size="18">四号</li>code>

<li data-size="16">小四</li>code>

<li data-size="14">五号</li>code>

<li data-size="12">小五</li>code>

<li data-size="10">六号</li>code>

<li data-size="8">小六</li>code>

<li data-size="7">七号</li>code>

<li data-size="6">八号</li>code>

<li data-size="5">5</li>code>

<li data-size="5.5">5.5</li>code>

<li data-size="6.5">6.5</li>code>

<li data-size="7.5">7.5</li>code>

<li data-size="8">8</li>code>

<li data-size="9">9</li>code>

<li data-size="10">10</li>code>

<li data-size="10.5">10.5</li>code>

<li data-size="11">11</li>code>

<li data-size="12">12</li>code>

<li data-size="14">14</li>code>

<li data-size="16">16</li>code>

<li data-size="18">18</li>code>

<li data-size="20">20</li>code>

<li data-size="22">22</li>code>

<li data-size="24">24</li>code>

<li data-size="26">26</li>code>

<li data-size="28">28</li>code>

<li data-size="36">36</li>code>

<li data-size="48">48</li>code>

<li data-size="50">50</li>code>

</ul>

</div>

</div>

<div class="menu-item__size-add">code>

<i></i>

</div>

<div class="menu-item__size-minus">code>

<i></i>

</div>

<div class="menu-item__bold">code>

<i></i>

</div>

<!-- 下划线 TODO -->

<div class="menu-item__italic">code>

<i></i>

</div>

<div class="menu-item__underline">code>

<i></i>

<span class="select"></span>code>

<div class="options">code>

<ul>

<li data-decoration-style='solid'>code>

<i></i>

</li>

<li data-decoration-style='double'>code>

<i></i>

</li>

<li data-decoration-style='dashed'>code>

<i></i>

</li>

<li data-decoration-style='dotted'>code>

<i></i>

</li>

<li data-decoration-style='wavy'>code>

<i></i>

</li>

</ul>

</div>

</div>

<div class="menu-item__strikeout" title="删除线(Ctrl+Shift+X)">code>

<i></i>

</div>

<div class="menu-item__superscript">code>

<i></i>

</div>

<div class="menu-item__subscript">code>

<i></i>

</div>

<div class="menu-item__color" title="字体颜色">code>

<i></i>

<span></span>

<input type="color" id="color" />code>

</div>

<div class="menu-item__highlight" title="高亮">code>

<i></i>

<span></span>

<input type="color" id="highlight">code>

</div>

</div>

<div class="menu-divider "></div>code>

<div class="menu-item disabled-btn">code>

<div class="menu-item__title">code>

<i></i>

<span class="select" title="切换标题">正文</span>code>

<div class="options">code>

<ul>

<li style="font-size:16px;">正文</li>code>

<li data-level="first" style="font-size:26px;">标题1</li>code>

<li data-level="second" style="font-size:24px;">标题2</li>code>

<li data-level="third" style="font-size:22px;">标题3</li>code>

<li data-level="fourth" style="font-size:20px;">标题4</li>code>

<li data-level="fifth" style="font-size:18px;">标题5</li>code>

<li data-level="sixth" style="font-size:16px;">标题6</li>code>

</ul>

</div>

</div>

<div class="menu-item__left">code>

<i></i>

</div>

<div class="menu-item__center">code>

<i></i>

</div>

<div class="menu-item__right">code>

<i></i>

</div>

<div class="menu-item__alignment">code>

<i></i>

</div>

<div class="menu-item__justify">code>

<i></i>

</div>

<div class="menu-item__row-margin">code>

<i title="行间距"></i>code>

<div class="options options_row-margin">code>

<ul>

<li data-rowmargin='0.5'>0.5</li>code>

<li data-rowmargin='0.75'>0.75</li>code>

<li data-rowmargin='1'>1</li>code>

<li data-rowmargin="1.25">1.25</li>code>

<li data-rowmargin="1.5">1.5</li>code>

<li data-rowmargin="1.75">1.75</li>code>

<li data-rowmargin="2">2</li>code>

<li data-rowmargin="2.5">2.5</li>code>

<li data-rowmargin="3">3</li>code>

</ul>

</div>

</div>

<div class="menu-item__list">code>

<i></i>

<div class="options">code>

<ul>

<li>

<label>取消列表</label>

</li>

<li data-list-type="ol" data-list-style='decimal'>code>

<label>有序列表:</label>

<ol>

<li>________</li>

</ol>

</li>

<li data-list-type="ul" data-list-style='checkbox'>code>

<label>复选框列表:</label>

<ul style="list-style-type: '☑️ ';">code>

<li>________</li>

</ul>

</li>

<li data-list-type="ul" data-list-style='disc'>code>

<label>实心圆点列表:</label>

<ul style="list-style-type: disc;">code>

<li>________</li>

</ul>

</li>

<li data-list-type="ul" data-list-style='circle'>code>

<label>空心圆点列表:</label>

<ul style="list-style-type: circle;">code>

<li>________</li>

</ul>

</li>

<li data-list-type="ul" data-list-style='square'>code>

<label>空心方块列表:</label>

<ul style="list-style-type: square;">code>

<li>________</li>

</ul>

</li>

</ul>

</div>

</div>

</div>

<div class="menu-divider "></div>code>

<div class="menu-item disabled-btn">code>

<div class="menu-item__table">code>

<i title="表格"></i>code>

</div>

<div class="menu-item__table__collapse">code>

<div class="table-close">×</div>code>

<div class="table-title">code>

<span class="table-select">插入</span>code>

<span>表格</span>

</div>

<div class="table-panel"></div>code>

</div>

<div class="menu-item__image">code>

<i title="图片"></i>code>

<input type="file" id="image" accept=".png, .jpg, .jpeg, .svg, .gif">code>

</div>

<div class="menu-item__hyperlink" style="display: none">code>

<i title="超链接"></i>code>

</div>

<div class="menu-item__separator">code>

<i title="分割线"></i>code>

<div class="options options_separator">code>

<ul>

<li data-separator='0,0'>code>

<i></i>

</li>

<li data-separator="1,1">code>

<i></i>

</li>

<li data-separator="3,1">code>

<i></i>

</li>

<li data-separator="4,4">code>

<i></i>

</li>

<li data-separator="7,3,3,3">code>

<i></i>

</li>

<li data-separator="6,2,2,2,2,2">code>

<i></i>

</li>

</ul>

</div>

</div>

<div class="menu-item__watermark" style="display: none">code>

<i title="水印(添加、删除)"></i>code>

<div class="options">code>

<ul>

<li data-menu="add">添加水印</li>code>

<li data-menu="delete">删除水印</li>code>

</ul>

</div>

</div>

<div class="menu-item__codeblock" title="代码块" style="display: none">code>

<i></i>

</div>

<div class="menu-item__page-break" title="分页符">code>

<i></i>

</div>

<div class="menu-item__control" style="display: none">code>

<i title="控件"></i>code>

<div class="options">code>

<ul>

<li data-control='text'>文本</li>code>

<li data-control="select">列举</li>code>

<li data-control="date">日期</li>code>

<li data-control="checkbox">复选框</li>code>

<li data-control="radio">单选框</li>code>

</ul>

</div>

</div>

<div class="menu-item__checkbox" title="复选框">code>

<i></i>

</div>

<div class="menu-item__radio" title="单选框">code>

<i></i>

</div>

<div class="menu-item__latex" title="LateX" style="display: none">code>

<i></i>

</div>

<div class="menu-item__date">code>

<i title="日期"></i>code>

<div class="options options_date">code>

<ul>

<li data-format="yyyy-MM-dd"></li>code>

<li data-format="yyyy-MM-dd hh:mm:ss"></li>code>

</ul>

</div>

</div>

<div class="menu-item__block" title="内容块" style="display: none">code>

<i></i>

</div>

</div>

<div class="menu-divider "></div>code>

<div class="menu-item">code>

<div class="menu-item__search" data-menu="search">code>

<i></i>

</div>

<div class="menu-item__search__collapse " data-menu="search">code>

<div class="menu-item__search__collapse__search">code>

<input type="text" />code>

<label class="search-result"></label>code>

<div class="arrow-left">code>

<i></i>

</div>

<div class="arrow-right">code>

<i></i>

</div>

<span>×</span>

</div>

<div class="menu-item__search__collapse__replace disabled-btn" >code>

<input type="text">code>

<button>替换</button>

</div>

</div>

<div class="menu-item__print" data-menu="print">code>

<i></i>

</div>

</div>

</div>

<!-- 目录进行隐藏 -->

<div class="catalog" editor-component="catalog" style="display: none">code>

<div class="catalog__header">code>

<span>目录</span>

<div class="catalog__header__close">code>

<i></i>

</div>

</div>

<div class="catalog__main"></div>code>

</div>

<div class="canvas-editor editor"></div>code>

<!-- 底部栏进行隐藏 -->

<div class="footer" editor-component="footer" style="display: none;">code>

<div>

<div class="catalog-mode" title="目录">code>

<i></i>

</div>

<div class="page-mode">code>

<i title="页面模式(分页、连页)"></i>code>

<div class="options">code>

<ul>

<li data-page-mode="paging" class="active">分页</li>code>

<li data-page-mode="continuity">连页</li>code>

</ul>

</div>

</div>

<span>可见页码:<span class="page-no-list">1</span></span>code>

<span>页面:<span class="page-no">1</span>/<span class="page-size">1</span></span>code>

<span>字数:<span class="word-count">0</span></span>code>

</div>

<div class="editor-mode" title="编辑模式(编辑、清洁、只读、表单)">编辑模式</div>code>

<div>

<div class="page-scale-minus" title="缩小(Ctrl+-)">code>

<i></i>

</div>

<span class="page-scale-percentage" title="显示比例(点击可复原Ctrl+0)">100%</span>code>

<div class="page-scale-add" title="放大(Ctrl+=)">code>

<i></i>

</div>

<div class="paper-size">code>

<i title="纸张类型"></i>code>

<div class="options">code>

<ul>

<li data-paper-size="794*1123" class="active">A4</li>code>

<li data-paper-size="1593*2251">A2</li>code>

<li data-paper-size="1125*1593">A3</li>code>

<li data-paper-size="565*796">A5</li>code>

<li data-paper-size="412*488">5号信封</li>code>

<li data-paper-size="450*866">6号信封</li>code>

<li data-paper-size="609*862">7号信封</li>code>

<li data-paper-size="862*1221">9号信封</li>code>

<li data-paper-size="813*1266">法律用纸</li>code>

<li data-paper-size="813*1054">信纸</li>code>

</ul>

</div>

</div>

<div class="paper-direction">code>

<i title="纸张方向"></i>code>

<div class="options">code>

<ul>

<li data-paper-direction="vertical" class="active">纵向</li>code>

<li data-paper-direction="horizontal">横向</li>code>

</ul>

</div>

</div>

<div class="paper-margin" title="页边距">code>

<i></i>

</div>

<div class="fullscreen" title="全屏显示">code>

<i></i>

</div>

<div class="editor-option" title="编辑器设置">code>

<i></i>

</div>

</div>

</div>

</div>

</template>

<script>

import { -- --> Init } from './canvas.js';

export default {

name: 'CanvasEditor',

props:{

// 父组件传递的id

parentContent:{

type:Object,

default:null

},

},

data() {

return {

instance: null,

};

},

watch: {

// 子组件监听 parentContent 的变化,获取到父组件数据

parentContent(newVal) {

if (newVal) {

this.instance = Init(newVal);

}

}

},

methods: {

// 向父组件返回添加的数据

saveContent() {

let content = {

data: { }

};

content.data = this.instance.instance.command.getValue().data;

this.$emit('save-content', content);

}

}

};

</script>

<style>

@import url("./style.css");

</style>

<style scoped>

.container {

position: relative; /* 确保子元素可以相对于父元素进行定位 */

width: 100%; /* 设置父元素的宽度 */

height: calc(100vh - 120px);/* 设置父元素的高度 */

overflow: hidden; /* 根据需要设置溢出行为 */

text-align: center;

}

.menu {

position: fixed; /* 确保其相对于最近的已定位祖先元素 */

top: 0;

left: 0;

width: 100%; /* 确保菜单宽度与父元素一致 */

box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2); /* 添加阴影以示区别 */

margin-bottom: 10px;

}

.menu-item .options{

width:120px;

height:300px;

overflow-y: scroll;

}

.menu-item__separator .options_separator{

height: 160px;

}

.menu-item__date .options_date {

width: 200px;

height: 80px;

}

.menu-item__row-margin .options_row-margin{

height: 200px;

}

.canvas-editor {

position: static;

flex-direction: column;

overflow-y: scroll;

background-color: #f2f4f7;

height: 100%;

justify-content: center;

}

.disabled{

pointer-events: none;

opacity: 0.5;

}

</style>

canvas.js

import Editor, { ElementType} from "@hufe921/canvas-editor";

import { debounce, nextTick } from './utils/index.ts'

export function Init (content) {

const isApple =

typeof navigator !== 'undefined' && /Mac OS X/.test(navigator.userAgent);

const editorElement = document.querySelector('.canvas-editor');

if (!editorElement) {

console.error('Element with class .canvas-editor not found.');

return;

}

const RowFlex = {

CENTER: 'center',

LEFT: 'left',

RIGHT: 'right'

};

const commentList = []

const instance = new Editor(

editorElement,

{

header: content.header,

main: content.main,

footer: content.footer,

}, // 数据

{

margins: [50, 50, 50, 50],

watermark: {

data: '',

size: 120

}, // 水印

pageNumber: {

format: '第{pageNo}页/共{pageCount}页'

},

placeholder: {

data: '请输入正文'

},

zone: {

tipDisabled: false

},

maskMargin: [60, 0, 30, 0] // 菜单栏高度60,底部工具栏30为遮盖层

} // 可选择项

);

Reflect.set(window, 'editor', instance);

// 1.菜单弹窗销毁

window.addEventListener('click', function (evt) {

const visibleDom = document.querySelector('.visible');

if (!visibleDom || visibleDom.contains(evt.target)) return;

visibleDom.classList.remove('visible');

}, {

capture: true

});

// 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');

let isFirstClick = true;

let painterTimeout;

painterDom.onclick = function () {

if (isFirstClick) {

isFirstClick = false;

painterTimeout = window.setTimeout(() => {

console.log('painter-click');

isFirstClick = true;

instance.command.executePainter({

isDblclick: false

});

}, 200);

} else {

window.clearTimeout(painterTimeout);

}

};

painterDom.ondblclick = function () {

console.log('painter-dblclick');

isFirstClick = true;

window.clearTimeout(painterTimeout);

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.target;

instance.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.target;

instance.command.executeSize(Number(li.dataset.size));

};

// 增大字号

const sizeAddDom = document.querySelector('.menu-item__size-add');

sizeAddDom.title = `增大字号(${ isApple ? '⌘' : 'Ctrl'}+[)`;

sizeAddDom.onclick = function () {

console.log('size-add');

instance.command.executeSizeAdd();

};

// 减小字号

const sizeMinusDom = document.querySelector('.menu-item__size-minus');

sizeMinusDom.title = `减小字号(${ isApple ? '⌘' : 'Ctrl'}+])`;

sizeMinusDom.onclick = function () {

console.log('size-minus');

instance.command.executeSizeMinus();

};

// 加粗

const boldDom = document.querySelector('.menu-item__bold');

boldDom.title = `加粗(${ isApple ? '⌘' : 'Ctrl'}+B)`;

boldDom.onclick = function () {

console.log('bold');

instance.command.executeBold();

};

// 斜体

const italicDom = document.querySelector('.menu-item__italic');

italicDom.title = `斜体(${ isApple ? '⌘' : 'Ctrl'}+I)`;

italicDom.onclick = function () {

console.log('italic');

instance.command.executeItalic();

};

// 下划线

const underlineDom = document.querySelector('.menu-item__underline');

underlineDom.title = `下划线(${ isApple ? '⌘' : 'Ctrl'}+U)`;

const underlineOptionDom = underlineDom.querySelector('.options');

underlineDom.querySelector('.select').onclick = function () {

underlineOptionDom.classList.toggle('visible');

};

underlineDom.querySelector('i').onclick = function () {

console.log('underline');

instance.command.executeUnderline();

underlineOptionDom.classList.remove('visible');

};

underlineDom.querySelector('ul').onmousedown = function (evt) {

const li = evt.target;

const decorationStyle = li.dataset.decorationStyle;

instance.command.executeUnderline({

style: decorationStyle

});

};

// 删除线

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 = `上标(${ isApple ? '⌘' : 'Ctrl'}+Shift+,)`;

superscriptDom.onclick = function () {

console.log('superscript');

instance.command.executeSuperscript();

};

// 下标

const subscriptDom = document.querySelector('.menu-item__subscript');

subscriptDom.title = `下标(${ 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+${ isApple ? 'Option' : 'Alt'}+${ index}`;

});

titleDom.onclick = function () {

console.log('title');

titleOptionDom.classList.toggle('visible');

};

titleOptionDom.onclick = function (evt) {

const li = evt.target;

const level = li.dataset.level;

instance.command.executeTitle(level || null);

};

// 文本对齐

const leftDom = document.querySelector('.menu-item__left');

leftDom.title = `左对齐(${ isApple ? '⌘' : 'Ctrl'}+L)`;

leftDom.onclick = function () {

console.log('left');

instance.command.executeRowFlex(RowFlex.LEFT);

};

const centerDom = document.querySelector('.menu-item__center');

centerDom.title = `居中对齐(${ isApple ? '⌘' : 'Ctrl'}+E)`;

centerDom.onclick = function () {

console.log('center');

instance.command.executeRowFlex(RowFlex.CENTER);

};

const rightDom = document.querySelector('.menu-item__right');

rightDom.title = `右对齐(${ isApple ? '⌘' : 'Ctrl'}+R)`;

rightDom.onclick = function () {

console.log('right');

instance.command.executeRowFlex(RowFlex.RIGHT);

};

const alignmentDom = document.querySelector('.menu-item__alignment');

alignmentDom.title = `两端对齐(${ isApple ? '⌘' : 'Ctrl'}+J)`;

alignmentDom.onclick = function () {

console.log('alignment');

instance.command.executeRowFlex(RowFlex.ALIGNMENT);

};

const justifyDom = document.querySelector('.menu-item__justify');

justifyDom.title = `分散对齐(${ isApple ? '⌘' : 'Ctrl'}+Shift+J)`;

justifyDom.onclick = function () {

console.log('justify');

instance.command.executeRowFlex('justify');

};

// 行间距

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.target;

instance.command.executeRowMargin(Number(li.dataset.rowmargin));

};

// 列表

const listDom = document.querySelector('.menu-item__list');

listDom.title = `列表(${ 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.target;

const listType = li.dataset.listType || null;

const 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');

// Draw rows and columns

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.appendChild(td);

trCellList.push(td);

}

tablePanel.appendChild(tr);

tableCellList.push(trCellList);

}

let colIndex = 0;

let rowIndex = 0;

// Remove all table cell selections

function removeAllTableCellSelect() {

tableCellList.forEach(tr => {

tr.forEach(td => td.classList.remove('active'));

});

}

// Set table title content

function setTableTitle(payload) {

tableTitle.innerText = payload;

}

// Restore initial state

function recoveryTable() {

removeAllTableCellSelect();

setTableTitle('插入');

colIndex = 0;

rowIndex = 0;

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 = fileReader.result;

image.src = value;

image.onload = function () {

instance.command.executeImage({

value,

width: image.width,

height: image.height

});

imageFileDom.value = '';

};

};

};

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.target;

const 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 checkboxDom = document.querySelector('.menu-item__checkbox');

checkboxDom.onclick = function () {

console.log('checkbox');

instance.command.executeInsertElementList([

{

type: ElementType.CHECKBOX,

checkbox: {

value: false

},

value: ''

}

]);

};

const radioDom = document.querySelector('.menu-item__radio');

radioDom.onclick = function () {

console.log('radio');

instance.command.executeInsertElementList([

{

type: ElementType.RADIO,

checkbox: {

value: false

},

value: ''

}

]);

};

const dateDom = document.querySelector('.menu-item__date');

const dateDomOptionDom = dateDom.querySelector('.options');

dateDom.onclick = function () {

console.log('date');

dateDomOptionDom.classList.toggle('visible');

// Adjust position

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';

}

// Current date

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()

}

]

}

]);

};

// 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} = result;

searchResultDom.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.value;

const replaceValue = replaceInputDom.value;

if (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');

// Render

const catalogItemContentDom = document.createElement('div');

catalogItemContentDom.classList.add('catalog-item__content');

const catalogItemContentSpanDom = document.createElement('span');

catalogItemContentSpanDom.innerText = catalogItem.name;

catalogItemContentDom.append(catalogItemContentSpanDom);

// Location

catalogItemContentDom.onclick = () => {

instance.command.executeLocationCatalog(catalogItem.id);

};

catalogItemDom.append(catalogItemContentDom);

if (catalogItem.subCatalog && catalogItem.subCatalog.length) {

appendCatalog(catalogItemDom, catalogItem.subCatalog);

}

// Append

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 = () => {

isCatalogShow = !isCatalogShow;

if (isCatalogShow) {

catalogDom.style.display = 'block';

updateCatalog();

} else {

catalogDom.style.display = 'none';

}

};

catalogModeDom.onclick = switchCatalog;

catalogHeaderCloseDom.onclick = switchCatalog;

const pageModeDom = document.querySelector('.page-mode');

const pageModeOptionsDom = pageModeDom.querySelector('.options');

pageModeDom.onclick = function () {

pageModeOptionsDom.classList.toggle('visible');

};

pageModeOptionsDom.onclick = function (evt) {

const li = evt.target;

instance.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();

};

// Paper Size

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.target;

const paperType = li.dataset.paperSize;

const [width, height] = paperType.split('*').map(Number);

instance.command.executePaperSize(width, height);

// Paper status echo

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.target;

const paperDirection = li.dataset.paperDirection;

instance.command.executePaperDirection(paperDirection);

// 纸张方向状态回显

paperDirectionDomOptionsDom.querySelectorAll('li').forEach(child => child.classList.remove('active'));

li.classList.add('active');

};

// 全屏

const fullscreenDom = document.querySelector('.fullscreen');

fullscreenDom.onclick = toggleFullscreen;

window.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 = 0;

const modeList = [

{

mode: 'EDIT', // EditorMode.EDIT

name: '编辑模式'

},

{

mode: 'CLEAN', // EditorMode.CLEAN

name: '清洁模式'

},

{

mode: 'READONLY', // EditorMode.READONLY

name: '只读模式'

},

{

mode: 'FORM', // EditorMode.FORM

name: '表单模式'

},

{

mode: 'PRINT', // EditorMode.PRINT

name: '打印模式'

}

];

const modeElement = document.querySelector('.editor-mode');

modeElement.onclick = function () {

// 模式选择循环

modeIndex === modeList.length - 1 ? (modeIndex = 0) : modeIndex++;

// 设置模式

const { name, mode} = modeList[modeIndex];

modeElement.innerText = name;

instance.command.executeMode(mode);

// 设置菜单栏权限视觉反馈

const isReadonly = mode === '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');

async function updateComment() {

const groupIds = await instance.command.getGroupIds();

for (const comment of commentList) {

const activeCommentDom = commentDom.querySelector(`.comment-item[data-id='${ comment.id}']`);code>

// 编辑器是否存在对应成组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 === 'SUBSCRIPT'

? subscriptDom.classList.add('active')

: subscriptDom.classList.remove('active');

payload.type === 'SUPERSCRIPT'

? superscriptDom.classList.add('active')

: superscriptDom.classList.remove('active');

payload.type === 'SEPARATOR'

? separatorDom.classList.add('active')

: separatorDom.classList.remove('active');

separatorOptionDom.querySelectorAll('li').forEach(li => li.classList.remove('active'));

if (payload.type === 'SEPARATOR') {

const separator = payload.dashArray.join(',') || '0,0';

const curSeparatorDom = separatorOptionDom.querySelector(`[data-separator='${ separator}']`);code>

if (curSeparatorDom) { -- -->

curSeparatorDom.classList.add('active');

}

}

// 富文本

fontOptionDom.querySelectorAll('li').forEach(li => li.classList.remove('active'));

const curFontDom = fontOptionDom.querySelector(`[data-family='${ payload.font}']`);code>

if (curFontDom) { -- -->

fontSelectDom.innerText = curFontDom.innerText;

fontSelectDom.style.fontFamily = payload.font;

curFontDom.classList.add('active');

}

sizeOptionDom.querySelectorAll('li').forEach(li => li.classList.remove('active'));

const curSizeDom = sizeOptionDom.querySelector(`[data-size='${ payload.size}']`);code>

if (curSizeDom) { -- -->

sizeSelectDom.innerText = curSizeDom.innerText;

curSizeDom.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.color;

colorSpanDom.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.highlight;

highlightSpanDom.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');

justifyDom.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 if (payload.rowFlex && payload.rowFlex === 'justify') {

justifyDom.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}']`);code>

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}']`);code>

titleSelectDom.innerText = curTitleDom.innerText;

curTitleDom.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.listType === 'OL' ? 'DECIMAL' : payload.listType;

const curListDom = listOptionDom.querySelector(`[data-list-type='${ listType}'][data-list-style='${ listType}']`);code>

if (curListDom) { -- -->

curListDom.classList.add('active');

}

} else {

listDom.classList.remove('active');

}

}

// 控件变更监听

instance.listener.controlChange = function (payload) {

const disableMenusInControlContext = [

'table',

'hyperlink',

'separator',

'page-break',

'control'

];

// 菜单操作权限

disableMenusInControlContext.forEach(menu => {

const menuDom = document.querySelector(`.menu-item__${ menu}`);

if (menuDom) {

payload

? menuDom.classList.add('disable')

: menuDom.classList.remove('disable');

}

});

};

// 页面模式变更监听

instance.listener.pageModeChange = function (payload) {

const activeMode = pageModeOptionsDom.querySelector(`[data-page-mode='${ payload}']`);code>

if (activeMode) { -- -->

pageModeOptionsDom.querySelectorAll('li').forEach(li => li.classList.remove('active'));

activeMode.classList.add('active');

}

};

// 内容变更处理函数

const handleContentChange = async function () {

// 字数

const wordCount = await instance.command.getWordCount();

const wordCountDom = document.querySelector('.word-count');

if (wordCountDom) {

wordCountDom.innerText = `${ wordCount || 0}`;

}

// 目录

if (isCatalogShow) {

nextTick(() => {

updateCatalog();

});

}

// 批注

nextTick(() => {

updateComment();

});

};

// 内容变更监听,使用防抖函数

instance.listener.contentChange = debounce(handleContentChange, 200);

handleContentChange();

// 保存监听

instance.listener.saved = function (payload) {

console.log('elementList: ', payload);

};

// 快捷键注册

instance.register.shortcutList([

{

key: 'P',

mod: true,

isGlobal: true,

callback: (command) => {

command.executePrint();

}

},

{

key: 'F',

mod: true,

isGlobal: true,

callback: (command) => {

const text = command.getRangeText();

searchDom.click();

if (text) {

searchInputDom.value = text;

instance.command.executeSearch(text);

setSearchResult();

}

}

},

{

key: 'Minus',

ctrl: true,

isGlobal: true,

callback: (command) => {

command.executePageScaleMinus();

}

},

{

key: 'Equal',

ctrl: true,

isGlobal: true,

callback: (command) => {

command.executePageScaleAdd();

}

},

{

key: 'Zero',

ctrl: true,

isGlobal: true,

callback: (command) => {

command.executePageScaleRecovery();

}

}

]);

return { instance};

}

将我提供的canvas.js 和 index.vue 文件进行复制放入到上图中 canvaseditor文件夹中, 即可在项目中集成 canvas-editor编辑器; 在其他文件中以子组件的形式引入 index.vue ,查看效果如下:

ParentTest.vue 模拟实现 父组件 ParentTest 和 子组件 CanvasEditor 实现通信, 以及数据的展示和存储

<template>

<div>

<CanvasEditor ref="canvasEditor" :parentContent="parentContent" @save-content="handleSaveCanvasEditorContent"/>code>

<button style="width: 160px;height: 80px; border: 2px solid #2b4b6b;margin-right: 20px; float: right" @click="handleSaveContent">保 存</button>code>

</div>

</template>

<script>

import CanvasEditor from "@/view/canvas-editor/index.vue";

export default { -- -->

name: 'ParentComponent',

components: {

CanvasEditor

},

data() {

return {

parentContent:undefined, // 存放父组件传递的数据

content:undefined, // 存放子组件数据

}

},

mounted() {

console.log("模拟父组件向后端请求数据, 传递给子组件");

this.parentContent = {

header:[

{

value: "父类传递的数据",

size: 12,

bold: false,

color: "rgb(33, 53, 71)",

italic: false,

},

],

main:[

{

value: "父类传递的数据 通过后端获取",

size: 40,

bold: true,

}

]

}

},

methods:{

handleSaveContent(){

console.log("父组件保存数据时即触发点击事件,执行 saveContent 方法获取子组件的数据");

this.$refs.canvasEditor.saveContent();

// 将获取到的子组件数据 this.content 入库处理

},

handleSaveCanvasEditorContent(data){

console.log("从子组件接收到的数据:", data);

// 将data数据转换为 json 格式的数据, 方便入库处理

this.content = JSON.stringify(data);

console.log("转换后的数据 this.content 为: ", this.content)

}

},

}

</script>

在这里插入图片描述



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。