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

源码地址: https://github.com/Hufe921/canvas-editor
官方文档: https://hufe.club/canvas-editor-docs/guide/schema.html

之前写过一篇, vue2项目集成 cnavas-ediotr 富文本编辑器的文章(文章链接),
当时的 vue2 项目是通过 vue-cli 进行创建, 并且项目中并没有 ts 配置, 当需要我们集成 canvas-editor 时配置 ts ,相较于还是比较麻烦的.
这次通过 vite 创建 vue3 项目, 并且在创建项目的过程中选择好 ts 需要的配置;
注: 本文章只针对于 vite 创建的 vue3 项目, 对于通过 vue-cli 创建的 vue3 项目不适用.

Canvas-Editor环境配置

package.json 从devDependencies和 dependencies 属性中, 选择未具有的依赖; 然后 npm install

{
   
  "name": "vue3-canvas-ediotr",
  "private": true,
  "version": "1.0.0",
  "type": "module",
  "scripts": {
   
    "dev": "vite",
    "build": "vue-tsc -b && vite build",
    "preview": "vite preview"
  },
  "devDependencies": {
   
    "vue": "^3.4.31",

    "@rollup/plugin-typescript": "^10.0.1",
    "@types/node": "16.18.96",
    "@types/prismjs": "^1.26.0",
    "@typescript-eslint/eslint-plugin": "5.62.0",
    "@typescript-eslint/parser": "5.62.0",
    "@vitejs/plugin-vue": "^2.0.4",
    "vite-plugin-vue-setup-extend": "^0.4.0",
    "cypress": "13.6.0",
    "cypress-file-upload": "^5.0.8",
    "eslint": "7.32.0",
    "simple-git-hooks": "^2.8.1",
    "typescript": "4.8.4",
    "vite": "^2.4.2",
    "ts-loader": "^9.5.1",
    "vite-plugin-css-injected-by-js": "^2.1.1",
    "canvas-editor": "^1.0.0",
    "@hufe921/canvas-editor": "^0.9.86"
  },
  "dependencies": {
   
    "prismjs": "^1.27.0"
  }
}

tsconfig.json 在原有的文件基础上直接进行替换 ( 根据你的需要,建议根据你本身项目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"]
}

vite.config.ts 配置文件上 添加了 setup语法糖插件 和 将@表示地址, 指向 'src’文件夹; 建议同步一下

// @ts-ignore
import {
   defineConfig} from "vite";
// @ts-ignore
import vue from "@vitejs/plugin-vue";

// path config
import path from 'path'

// 导入安装的 vite-plugin-vue-setup-extend 插件
// @ts-ignore
import vueSetupExtend from "vite-plugin-vue-setup-extend";

// https://vitejs.dev/config/
export default defineConfig({
   
  plugins: [vue(), vueSetupExtend],

  resolve:{
   
    alias:{
   
      '@' : path.resolve(__dirname,'src')
    }
  }
});

获取文件

将 canvas-editor 源码中的部分文件, 复制过来;
在这里插入图片描述 将复制的文件, 建议放到 src/view/canvas-editor目录下, 并且将 main.ts 改名为 canvas.ts; 而且canvas.ts代码进行了更改, 更改如(将main.ts文件内容全部替换成下面文件内容):
在这里插入图片描述
cnavas.ts

import './style.css'
import prism from 'prismjs'
// @ts-ignore
import Editor, {
   BlockType, Command, ControlType, EditorMode, EditorZone, ElementType, IBlock, ICatalogItem, IElement, KeyMap, ListStyle, ListType, PageMode, PaperDirection, RowFlex, TextDecorationStyle, TitleLevel, splitText} from './editor'
import {
    Dialog } from './components/dialog/Dialog'
import {
    formatPrismToken } from './utils/prism'
// @ts-ignore
import {
    debounce, nextTick, scrollIntoView } from './utils'

export default  function Init (content:any) {
   
  const isApple =
    typeof navigator !== 'undefined' && /Mac OS X/.test(navigator.userAgent)

  // 1. 初始化编辑器
  const container = document.querySelector<HTMLDivElement>('.editor')!

  const instance = new Editor(
    container,
    {
   
      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为遮盖层,
      } // 可选择项

  )
  console.log('实例: ', instance)
  // cypress使用
  Reflect.set(window, 'editor', instance)

  // 菜单弹窗销毁
  window.addEventListener(
    'click',
    evt => {
   
      const visibleDom = document.querySelector('.visible')
      if (!visibleDom || visibleDom.contains(<Node>evt.target)) return
      visibleDom.classList.remove('visible')
    },
    {
   
      capture: true
    }
  )

  // 2. | 撤销 | 重做 | 格式刷 | 清除格式 |
  const undoDom = document.querySelector<HTMLDivElement>('.menu-item__undo')!
  undoDom.title = `撤销(${
     isApple ? '⌘' : 'Ctrl'}+Z)`
  undoDom.onclick = function () {
   
    console.log('undo')
    instance.command.executeUndo()
  }

  const redoDom = document.querySelector<HTMLDivElement>('.menu-item__redo')!
  redoDom.title = `重做(${
     isApple ? '⌘' : 'Ctrl'}+Y)`
  redoDom.onclick = function () {
   
    console.log('redo')
    instance.command.executeRedo()
  }

  const painterDom = document.querySelector<HTMLDivElement>(
    '.menu-item__painter'
  )!

  let isFirstClick = true
  let painterTimeout: number
  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<HTMLDivElement>('.menu-item__format')!.onclick =
    function () {
   
      console.log('format')
      instance.command.executeFormat()
    }

  // 3. | 字体 | 字体变大 | 字体变小 | 加粗 | 斜体 | 下划线 | 删除线 | 上标 | 下标 | 字体颜色 | 背景色 |
  const fontDom = document.querySelector<HTMLDivElement>('.menu-item__font')!
  const fontSelectDom = fontDom.querySelector<HTMLDivElement>('.select')!
  const fontOptionDom = fontDom.querySelector<HTMLDivElement>('.options')!
  fontDom.onclick = function () {
   
    console.log('font')
    fontOptionDom.classList.toggle('visible')
  }
  fontOptionDom.onclick = function (evt) {
   
    const li = evt.target as HTMLLIElement
    instance.command.executeFont(li.dataset.family!)
  }

  const sizeSetDom = document.querySelector<HTMLDivElement>('.menu-item__size')!
  const sizeSelectDom = sizeSetDom.querySelector<HTMLDivElement>('.select')!
  const sizeOptionDom = sizeSetDom.querySelector<HTMLDivElement>('.options')!
  sizeSetDom.title = `设置字号`
  sizeSetDom.onclick = function () {
   
    console.log('size')
    sizeOptionDom.classList.toggle('visible')
  }
  sizeOptionDom.onclick = function (evt) {
   
    const li = evt.target as HTMLLIElement
    instance.command.executeSize(Number(li.dataset.size!))
  }

  const sizeAddDom = document.querySelector<HTMLDivElement>(
    '.menu-item__size-add'
  )!
  sizeAddDom.title = `增大字号(${
     isApple ? '⌘' : 'Ctrl'}+[)`
  sizeAddDom.onclick = function () {
   
    console.log('size-add')
    instance.command.executeSizeAdd()
  }

  const sizeMinusDom = document.querySelector<HTMLDivElement>(
    '.menu-item__size-minus'
  )!
  sizeMinusDom.title = `减小字号(${
     isApple ? '⌘' : 'Ctrl'}+])`
  sizeMinusDom.onclick = function () {
   
    console.log('size-minus')
    instance.command.executeSizeMinus()
  }

  const boldDom = document.querySelector<HTMLDivElement>('.menu-item__bold')!
  boldDom.title = `加粗(${
     isApple ? '⌘' : 'Ctrl'}+B)`
  boldDom.onclick = function () {
   
    console.log('bold')
    instance.command.executeBold()
  }

  const italicDom =
    document.querySelector<HTMLDivElement>('.menu-item__italic')!
  italicDom.title = `斜体(${
     isApple ? '⌘' : 'Ctrl'}+I)`
  italicDom.onclick = function () {
   
    console.log('italic')
    instance.command.executeItalic()
  }

  const underlineDom = document.querySelector<HTMLDivElement>(
    '.menu-item__underline'
  )!
  underlineDom.title = `下划线(${
     isApple ? '⌘' : 'Ctrl'}+U)`
  const underlineOptionDom =
    underlineDom.querySelector<HTMLDivElement>('.options')!
  underlineDom.querySelector<HTMLSpanElement>('.select')!.onclick =
    function () {
   
      underlineOptionDom.classList.toggle('visible')
    }
  underlineDom.querySelector<HTMLElement>('i')!.onclick = function () {
   
    console.log('underline')
    instance.command.executeUnderline()
    underlineOptionDom.classList.remove('visible')
  }
  underlineDom.querySelector<HTMLUListElement>('ul')!.onmousedown = function (
    evt
  ) {
   
    const li = evt.target as HTMLLIElement
    const decorationStyle = <TextDecorationStyle>li.dataset.decorationStyle
    instance.command.executeUnderline({
   
      style: decorationStyle
    })
    underlineOptionDom.classList.remove('visible')
  }

  const strikeoutDom = document.querySelector<HTMLDivElement>(
    '.menu-item__strikeout'
  )!
  strikeoutDom.onclick = function () {
   
    console.log('strikeout')
    instance.command.executeStrikeout()
  }

  const superscriptDom = document.querySelector<HTMLDivElement>(
    '.menu-item__superscript'
  )!
  superscriptDom.title = `上标(${
     isApple ? '⌘' : 'Ctrl'}+Shift+,)`
  superscriptDom.onclick = function () {
   
    console.log('superscript')
    instance.command.executeSuperscript()
  }

  const subscriptDom = document.querySelector<HTMLDivElement>(
    '.menu-item__subscript'
  )!
  subscriptDom.title = `下标(${
     isApple ? '⌘' : 'Ctrl'}+Shift+.)`
  subscriptDom.onclick = function () {
   
    console.log('subscript')
    instance.command.executeSubscript()
  }

  const colorControlDom = document.querySelector<HTMLInputElement>('#color')!
  colorControlDom.oninput = function () {
   
    instance.command.executeColor(colorControlDom.value)
  }
  const colorDom = document.querySelector<HTMLDivElement>('.menu-item__color')!
  const colorSpanDom = colorDom.querySelector('span')!
  colorDom.onclick = function () {
   
    console.log('color')
    colorControlDom.click()
  }

  const highlightControlDom =
    document.querySelector<HTMLInputElement>('#highlight')!
  highlightControlDom.oninput = function () {
   
    instance.command.executeHighlight(highlightControlDom.value)
  }
  const highlightDom = document.querySelector<HTMLDivElement>(
    '.menu-item__highlight'
  )!
  const highlightSpanDom = highlightDom.querySelector('span')!
  highlightDom.onclick = function () {
   
    console.log('highlight')
    highlightControlDom?.click()
  }

  const titleDom = document.querySelector<HTMLDivElement>('.menu-item__title')!
  const titleSelectDom = titleDom.querySelector<HTMLDivElement>('.select')!
  const titleOptionDom = titleDom.querySelector<HTMLDivElement>('.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 as HTMLLIElement
    const level = <TitleLevel>li.dataset.level
    instance.command.executeTitle(level || null)
  }

  const leftDom = document.querySelector<HTMLDivElement>('.menu-item__left')!
  leftDom.title = `左对齐(${
     isApple ? '⌘' : 'Ctrl'}+L)`
  leftDom.onclick = function () {
   
    console.log('left')
    instance.command.executeRowFlex(RowFlex.LEFT)
  }

  const centerDom =
    document.querySelector<HTMLDivElement>('.menu-item__center')!
  centerDom.title = `居中对齐(${
     isApple ? '⌘' : 'Ctrl'}+E)`
  centerDom.onclick = function () {
   
    console.log('center')
    instance.command.executeRowFlex(RowFlex.CENTER)
  }

  const rightDom = document.querySelector<HTMLDivElement>('.menu-item__right')!
  rightDom.title = `右对齐(${
     isApple ? '⌘' : 'Ctrl'}+R)`
  rightDom.onclick = function () {
   
    console.log('right')
    instance.command.executeRowFlex(RowFlex.RIGHT)
  }

  const alignmentDom = document.querySelector<HTMLDivElement>(
    '.menu-item__alignment'
  )!
  alignmentDom.title = `两端对齐(${
     isApple ? '⌘' : 'Ctrl'}+J)`
  alignmentDom.onclick = function () {
   
    console.log('alignment')
    instance.command.executeRowFlex(RowFlex.ALIGNMENT)
  }

  const justifyDom = document.querySelector<HTMLDivElement>(
    '.menu-item__justify'
  )!
  justifyDom.title = `分散对齐(${
     isApple ? '⌘' : 'Ctrl'}+Shift+J)`
  justifyDom.onclick = function () {
   
    console.log('justify')
    instance.command.executeRowFlex(RowFlex.JUSTIFY)
  }

  const rowMarginDom = document.querySelector<HTMLDivElement>(
    '.menu-item__row-margin'
  )!
  const rowOptionDom = rowMarginDom.querySelector<HTMLDivElement>('.options')!
  rowMarginDom.onclick = function () {
   
    console.log('row-margin')
    rowOptionDom.classList.toggle('visible')
  }
  rowOptionDom.onclick = function (evt) {
   
    const li = evt.target as HTMLLIElement
    instance.command.executeRowMargin(Number(li.dataset.rowmargin!))
  }

  const listDom = document.querySelector<HTMLDivElement>('.menu-item__list')!
  listDom.title = `列表(${
     isApple ? '⌘' : 'Ctrl'}+Shift+U)`
  const listOptionDom = listDom.querySelector<HTMLDivElement>('.options')!
  listDom.onclick = function () {
   
    console.log('list')
    listOptionDom.classList.toggle('visible')
  }
  listOptionDom.onclick = function (evt) {
   
    const li = evt.target as HTMLLIElement
    const listType = <ListType>li.dataset.listType || null
    const listStyle = <ListStyle>(<unknown>li.dataset.listStyle)
    instance.command.executeList(listType, listStyle)
  }

  // 4. | 表格 | 图片 | 超链接 | 分割线 | 水印 | 代码块 | 分隔符 | 控件 | 复选框 | LaTeX | 日期选择器
  const tableDom = document.querySelector<HTMLDivElement>('.menu-item__table')!
  const tablePanelContainer = document.querySelector<HTMLDivElement>(
    '.menu-item__table__collapse'
  )!
  const tableClose = document.querySelector<HTMLDivElement>('.table-close')!
  const tableTitle = document.querySelector<HTMLDivElement>('.table-select')!
  const tablePanel = document.querySelector<HTMLDivElement>('.table-panel')!
  // 绘制行列
  const tableCellList: HTMLDivElement[][] = []
  for (let i = 0; i < 10; i++) {
   
    const tr = document.createElement('tr')
    tr.classList.add('table-row')
    const trCellList: HTMLDivElement[] = []
    for (let j = 0; j < 10; j++) {
   
      const td = document.createElement('td')
      td.classList.add
### Vue2 项目集成并自定义 Canvas-Editor #### 集成 Canvas-EditorVue2 项目 为了在 Vue2 项目中成功集成 `canvas-editor`,可以按照以下方法操作: 1. **安装依赖包** 使用 npm 或 yarn 安装官方提供的 `@hufe921/canvas-editor` 包。 ```bash npm install @hufe921/canvas-editor --save ``` 2. **准备 HTML 容器** 在 Vue 组件模板中创建一个用于挂载编辑器的 DOM 节点。 ```html <template> <div ref="editorContainer" class="canvas-editor"></div> </template> <style scoped> .canvas-editor { width: 100%; height: 500px; } </style> ``` 3. **实例化编辑器** 在组件的生命周期钩子函数中初始化 `canvas-editor` 编辑器实例。 ```javascript <script> import Editor from &#39;@hufe921/canvas-editor&#39;; export default { data() { return { editorInstance: null, }; }, mounted() { this.editorInstance = new Editor( this.$refs.editorContainer, // 挂载目标节点 [{ value: &#39;Hello World&#39; }], // 初始化数据结构 {} // 可选参数配置 ); }, beforeDestroy() { if (this.editorInstance && typeof this.editorInstance.destroy === &#39;function&#39;) { this.editorInstance.destroy(); // 销毁编辑器实例 } }, }; </script> ``` --- #### 自定义样式与功能扩展 Canvas-Editor 的样式可以通过覆盖默认 CSS 文件中的类名来实现个性化定制。 1. **加载默认样式文件** 默认情况下,需要引入 `canvas-editor` 提供的基础样式文件。 ```css /* main.css */ @import &#39;~@hufe921/canvas-editor/dist/style.css&#39;; ``` 2. **重写样式** 如果希望调整某些特定样式的显示效果,可以在项目的全局或局部样式表中重新定义对应的类名。例如: ```css /* 修改工具栏背景颜色 */ .ce-toolbar { background-color: #f8f8f8 !important; } /* 修改字体大小 */ .ce-textarea textarea { font-size: 16px !important; } ``` 3. **功能扩展** 对于更复杂的功能需求(如新增按钮、事件监听等),可以直接调用 `canvas-editor` API 进行二次开发。例如,在工具栏中添加一个新的按钮: ```javascript const customToolbarConfig = [ ...defaultToolbarItems, // 默认工具栏项 { type: &#39;button&#39;, name: &#39;customButton&#39;, iconClass: &#39;fa fa-star&#39;, // 图标类名 onClick(editor) { alert(&#39;Custom button clicked!&#39;); }, }, ]; this.editorInstance = new Editor( this.$refs.editorContainer, [{ value: &#39;Hello World&#39; }], { toolbar: customToolbarConfig } // 设置自定义工具栏 ); ``` --- #### 将 API 请求结果应用于 Canvas-Editor 假设有一个接口返回的数据如下所示: ```json [ { "value": "This is a paragraph.", "type": "paragraph" }, { "value": "https://example.com/image.jpg", "type": "image" } ] ``` 可以将其作为初始内容传递给 `canvas-editor` 实例: ```javascript async created() { try { const response = await fetch(&#39;/api/get-content&#39;); const contentData = await response.json(); this.$nextTick(() => { this.editorInstance.setContent(contentData); // 更新编辑器内容 }); } catch (error) { console.error(&#39;Failed to load initial content:&#39;, error); } } ``` 如果需要动态更新内容,则可通过 `setContent` 方法实时刷新编辑器状态。 --- ### 注意事项 - 确保 Node.js 和 NPM 版本满足 `@hufe921/canvas-editor` 插件的要求[^1]。 - 若遇到兼容性问题,可尝试克隆源码仓库并自行编译打包后再集成项目中[^2]。 - 当前版本可能不支持部分高级特性,请查阅官方文档获取最新信息[^3]。 ---
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值