vue 使用wangeditor实现数学公式+富文本编辑器

该文章已生成可运行项目,
需求背景

业务需要

使用技术栈及插件
  • vue3+vit
  • wangEditor编辑器+kityformula函数编辑器
引言

在Web开发中,富文本编辑器是常见的组件之一,它允许用户在网页上编辑并格式化文本。然而,当需要在富文本编辑器中插入数学公式时,就需要借助专门的公式编辑器。wangEditor作为一款轻量、简洁且功能强大的Web富文本编辑器,通过集成kityformula可以很好地满足这一需求。本文将详细介绍如何在wangEditor中集成这款公式编辑器。

wangEditor

wangEditor是一个基于javascript和css开发的Web富文本编辑器,以其轻量、简洁、易用和开源免费的特点受到广大开发者的喜爱。它支持丰富的文本编辑功能,如文字加粗、字号调整、颜色设置、插入图片、表格等。同时,wangEditor也提供了丰富的扩展接口,允许开发者根据需求定制功能。

kityformula

kityformula是一款开源的数学公式编辑器,支持LaTeX语法,可以将LaTeX代码渲染成数学公式。它通常用于网页中嵌入数学公式的编辑和显示。

呈现效果

在这里插入图片描述

插入数学公式

实现过程

参考一位大佬文章wangEditor公式编辑器kityformula + myscript-math-web - 掘金

一、安装依赖
  1. npm install @wangeditor/editor
  2. npm install katex
  3. npm install jquery
二、注册插件
import {Boot, createEditor, createToolbar} from '@wangeditor/editor';
import formulaModule from "@wangeditor/plugin-formula";
// 注册。要在创建编辑器之前注册,且只能注册一次,不可重复注册。
Boot.registerModule(formulaModule);
三、配置编辑器

接下来,配置编辑器和工具栏,使其支持插入公式:

/**
 * 工具栏配置
 * @type {{excludeKeys: string[], insertKeys: {keys: string[], index: number}}}
 */
const toolbarConfig = {
  // 去除不需要的菜单
  excludeKeys: ["fullScreen", "emotion","group-image", "group-video", "insertTable", "|", "insertLink", "codeBlock", "group-more-style", "bulletedList", "todo", "divider", "fontFamily"],
  insertKeys: {
    index: 1, // 自定义
    keys: ['kityFormula'],
  }
}

/**
 * 编辑器配置
 * @type {{placeholder: string, hoverbarKeys: {formula: {menuKeys: string[]}}}}
 */
const editorConfig = {
  // 选中公式时的悬浮菜单
  hoverbarKeys: {
    formula: {
      menuKeys: ['kityFormula'],
    },
  },
  placeholder: '在此输入笔记内容'
}

// 创建编辑器和工具栏
const editor = createEditor({
    selector: '#editor-container',
    config: editorConfig,
    html: ``,
  });

  const toolbar = createToolbar({
    editor,
    selector: '#toolbar-container',
    config: toolbarConfig
  });

四、加入数学公式

创建kityformula.js文件,具体代码如下,注意图标引入路径
图标文件内代码

/** 键入公式 */
export const formulaIcon =
    '<svg t="1682415575656" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="8100" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M902.383 461.687c6.417-6.417 12.834-12.8 19.217-12.8h89.6c6.383 0 12.8-6.382 12.8-12.765v-102.23c0-6.417-6.417-12.8-12.8-12.8H908.8c-6.417 0-19.183 6.383-19.183 12.8L780.8 448.922c-6.383 6.348-12.8 6.348-12.8 0l-57.583-115.03c-6.417-6.417-12.8-12.8-19.217-12.8H524.8c-6.383 0-12.8 6.383-12.8 12.8v102.23c0 6.383 6.417 12.765 12.8 12.765h102.4c6.417 0 12.8 6.383 19.183 12.8l38.4 83.081v19.149l-115.2 134.178c-6.383 0-12.766 6.417-19.183 6.417h-89.6c-6.383 0-12.8 6.383-12.8 12.766v102.23c0 6.382 6.417 12.8 12.8 12.8h102.4c6.383 0 19.183-6.418 19.183-12.8L729.6 653.38c6.417-6.383 12.8-6.383 12.8 0l83.183 166.127c0 6.383 12.8 12.8 19.217 12.8h102.4a13.756 13.756 0 0 0 12.8-12.8v-102.23a13.756 13.756 0 0 0-12.8-12.765h-38.4c-6.417 0-12.8-6.417-19.183-12.8l-64.034-127.795v-19.149l76.8-83.08zM377.617 65.502C345.6 91.068 313.583 129.399 294.4 193.297l-31.983 127.795H76.8a13.756 13.756 0 0 0-12.8 12.8v102.23c0 6.383 6.383 12.765 12.8 12.765h153.6l-96.017 383.42C115.2 908.971 64 896.205 64 896.205H0V1024h64c51.2 0 102.4-6.383 128-38.332 32.017-31.949 51.2-89.463 64-153.36l96.017-383.42H499.2c6.383 0 12.8-6.383 12.8-12.766v-102.23c0-6.417-6.417-12.8-12.8-12.8H384l32.017-121.412c6.383-19.149 38.4-51.098 57.583-63.898C543.983 84.651 640 116.6 704 129.4V20.753c-64-12.766-204.8-57.48-326.383 44.715z" p-id="8101"></path></svg>';

/** 手写公式 */
export const mathIcon =
    '<svg t="1686650329387" class="icon" viewBox="0 0 1204 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="12977" width="200" height="200"><path d="M713.908706 138.541176v84.329412H146.974118c-21.684706 0-41.321412 19.636706-44.212706 46.019765l-0.361412 6.686118v593.317647c0 27.407059 17.950118 49.152 39.152941 52.284235l5.421177 0.421647h910.757647c21.684706 0 41.321412-19.636706 44.212706-46.019765l0.361411-6.686117v-593.317647c0-27.407059-17.950118-49.152-39.152941-52.284236l-5.421176-0.421647h-80.474353V138.541176h80.474353c68.728471 0 123.904 57.103059 128.602353 127.397648l0.301176 9.637647v593.317647c0 71.378824-52.163765 131.614118-119.627294 136.734117l-9.276235 0.301177H146.974118c-68.728471 0-123.904-57.103059-128.602353-127.397647l-0.301177-9.637647v-593.317647c0-71.378824 52.224-131.614118 119.627294-136.734118L146.974118 138.541176h566.934588z" p-id="12978"></path><path d="M843.294118 18.070588c-88.907294 0-162.635294 63.126588-162.635294 145.227294v498.56753c0 20.239059 7.830588 39.634824 21.684705 54.512941l75.715765 81.739294c32.948706 35.538824 90.352941 36.201412 124.205177 1.626353l80.594823-82.522353c14.697412-14.998588 23.070118-34.936471 23.070118-55.898353V163.237647C1005.929412 81.197176 932.201412 18.070588 843.294118 18.070588z m8.854588 84.690824c40.357647 3.433412 69.451294 30.298353 69.451294 60.53647v495.917177l-79.570824 81.498353-1.084235 0.180706-1.024-0.120471-74.932706-80.835765V163.297882c0-32.406588 33.310118-60.897882 78.305883-60.897882l8.854588 0.361412z" p-id="12979"></path><path d="M150.588235 543.683765h-60.235294c-16.082824 0-30.117647 13.312-30.117647 28.551529s14.034824 28.551529 30.117647 28.55153h60.235294c16.082824 0 30.117647-13.312 30.117647-28.55153s-14.034824-28.551529-30.117647-28.551529M150.588235 664.154353h-60.235294c-16.082824 0-30.117647 13.312-30.117647 28.551529s14.034824 28.551529 30.117647 28.55153h60.235294c16.082824 0 30.117647-13.312 30.117647-28.55153s-14.034824-28.551529-30.117647-28.551529M150.588235 423.213176h-60.235294c-16.082824 0-30.117647 13.312-30.117647 28.55153s14.034824 28.551529 30.117647 28.551529h60.235294c16.082824 0 30.117647-13.312 30.117647-28.551529s-14.034824-28.551529-30.117647-28.55153" p-id="12980"></path></svg>';

import $ from "jquery";
import { formulaIcon } from "../../../assets/svg/svg-icon.ts";

class MyKityFormulaMenu {
    constructor() {
        this.title = "编辑公式";
        this.iconSvg = formulaIcon;
        this.tag = "button";
        this.showModal = true;
        this.modalWidth = 900;
        this.modalHeight = 600;
    }

    // 菜单是否需要激活(如选中加粗文本,“加粗”菜单会激活),用不到则返回 false
    isActive(editor) {
        return false;
    }

    // 获取菜单执行时的 value ,用不到则返回空 字符串或 false
    getValue(editor) {
        return "";
    }

    // 菜单是否需要禁用(如选中 H1 ,“引用”菜单被禁用),用不到则返回 false
    isDisabled(editor) {
        return false;
    }
    // 点击菜单时触发的函数
    exec(editor, value) {
        // Modal menu ,这个函数不用写,空着即可
    }

    // 弹出框 modal 的定位:1. 返回某一个 SlateNode; 2. 返回 null (根据当前选区自动定位)
    getModalPositionNode(editor) {
        return null; // modal 依据选区定位
    }

    // 定义 modal 内部的 DOM Element
    getModalContentElem(editor) {
        // panel 中需要用到的id
        const inputIFrameId = "kityformula_" + Math.ceil(Math.random() * 10);
        const btnOkId = "kityformula-btn" + Math.ceil(Math.random() * 10);

        const $content = $(`
    <div>
      <iframe id="${inputIFrameId}" class="iframe" height="500px" width="100%" frameborder="0" scrolling="no" src="/kityformula/index.html"></iframe>
    </div>`);
        const $button = $(
            `<button id="${btnOkId}" class="right" style='margin: 5px 0'>
        确认插入
      </button>`
        );
        $content.append($button);

        $button.on("click", () => {
            // 执行插入公式
            const node = document.getElementById(inputIFrameId);
            const kfe = node.contentWindow.kfe;

            kfe.execCommand("get.image.data", function(data) {
                // 获取base64
                // console.log(data.img);
            });

            let latex = kfe.execCommand("get.source");
            latex = latex.replace(/\s/g, ""); // 去掉空格

            const formulaNode = {
                type: "paragraph",
                children: [
                    {
                        type: "formula",
                        value: latex,
                        children: [
                            {
                                text: "",
                            },
                        ],
                    },
                ],
            };
            editor.insertNode(formulaNode);
            editor.hidePanelOrModal();
        });

        return $content[0]; // 返回 DOM Element 类型

        // PS:也可以把 $content 缓存下来,这样不用每次重复创建、重复绑定事件,优化性能
    }
}
const menuConf = {
    key: "kityFormula", // menu key ,唯一。注册之后,需通过 toolbarKeys 配置到工具栏
    factory() {
        return new MyKityFormulaMenu();
    },
};

export default menuConf;

4.1 在pulic文件下添加数学公式相关代码

具体代码
具体代码可在 wangEditor-plugin-formula项目中拿到,项目地址:wangEditor-plugin-formula

4.2 将插件注册到编辑器内
import kityformula from "./core/kityformula.js";
// 插件注入
  Boot.registerMenu(kityformula);
  /**
 * 工具栏配置
 * @type {{excludeKeys: string[], insertKeys: {keys: string[], index: number}}}
 */
const toolbarConfig = {
  insertKeys: {
    index: 1, // 自定义
    keys: ['kityFormula'], // 公式菜单
  }
}
五、完整页面代码
<script setup>
import "@wangeditor/editor/dist/css/style.css";
import {onBeforeUnmount, ref, shallowRef, onMounted} from "vue";
import {Boot, createEditor, createToolbar} from '@wangeditor/editor';
import kityformula from "./core/kityformula.js";
import {useOtherStore} from "@/store/other.js";
import formulaModule from "@wangeditor/plugin-formula";

const otherStore = useOtherStore();

/**
 * 根据全局变量判断自定义按钮是否已被注入
 */
if (!otherStore.wangEditorRegisterMenu) {
  Boot.registerModule(formulaModule);
  // 插件注入
  Boot.registerMenu(kityformula);
  otherStore.setWangEditorRegisterMenu(true);
}

// 编辑器实例,必须用 shallowRef,重要!
const editorRef = ref();

/**
 * 工具栏配置
 * @type {{excludeKeys: string[], insertKeys: {keys: string[], index: number}}}
 */
const toolbarConfig = {
  // 去除不需要的菜单
  excludeKeys: ["fullScreen", "emotion","group-image", "group-video", "insertTable", "|", "insertLink", "codeBlock", "group-more-style", "bulletedList", "todo", "divider", "fontFamily"],
  insertKeys: {
    index: 1, // 自定义
    keys: ['kityFormula'],
  }
}

/**
 * 编辑器配置
 * @type {{placeholder: string, hoverbarKeys: {formula: {menuKeys: string[]}}}}
 */
const editorConfig = {
  // 选中公式时的悬浮菜单
  hoverbarKeys: {
    formula: {
      menuKeys: ['kityFormula'], // “编辑公式”菜单
    },
  },
  placeholder: '在此输入笔记内容'
}

/**
 * 注销编辑器
 */
const destroy = () => {
  const editor = editorRef.value;
  if (editor == null) return;
  editor.destroy();
}

/**
 * 组件销毁时,也及时销毁编辑器
 */
onBeforeUnmount(() => {
  destroy();
});

/**
 * 组件初始化
 */
onMounted(() => {
  const editor = createEditor({
    selector: '#editor-container',
    config: editorConfig,
    html: ``,
  });

  editorRef.value = editor;

  const toolbar = createToolbar({
    editor,
    selector: '#toolbar-container',
    config: toolbarConfig
  });

  // console.log('toolbar', toolbar.getConfig().toolbarKeys);
  console.log('toolbar',toolbar,editorRef)
});

/**
 * 获取数据
 * @returns {string}
 */
const getHtml = () => {
  const editor = editorRef.value;
  return editor.getHtml();
}

/**
 * 设置数据
 * @param html
 * @returns {*|void}
 */
const setHtml = (html) => {
  const editor = editorRef.value;
  return editor.setHtml(html);
}

defineExpose({getHtml,setHtml});
</script>

<template>
  <div id="editor-container" ref="editorRef">
    <div id="toolbar-container"><!-- 工具栏 --></div>
    <div id="editor-container"><!-- 编辑器 --></div>
  </div>
</template>

<style scoped lang="scss">
:deep(.w-e-modal) {
  padding-top: 30px;
}

:deep(.kf-editor-toolbar) {
  z-index: 1;
  padding: 0;
  .kf-editor-ui-button {
    z-index: 1;
  }
}
#editor-container {
  border-radius: 16px;
  border: 1px solid #EAF1FE;

  #toolbar-container {
    border-top-left-radius: 16px;
    border-top-right-radius: 16px;

    :deep(.w-e-toolbar) {
      border-top-left-radius: 16px;
      border-top-right-radius: 16px;
    }
  }

  #editor-container {
    border-bottom-left-radius: 16px;
    border-bottom-right-radius: 16px;

    :deep(.w-e-text-container) {
      height: 200px;
      border-bottom-left-radius: 16px;
      border-bottom-right-radius: 16px;
    }
  }
}
</style>

文章到此结束,希望对你有所帮助~

本文章已经生成可运行项目
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值