关于wangeditor的自定义组件和元素

需求

需求是:我要在富文本中新建一个下拉框,点击下拉框让页面中出现类似于按钮的样式

参考链接官网:www.wangeditor.com/v5/developm...

实现

注册新菜单
js 复制代码
class MyButtonMenu {
    constructor(vueInstance) {
        this.vueInstance = vueInstance;
        this.title = '按钮'; // 自定义菜单标题
        this.iconSvg = '<svg></svg>'; // 可选 菜单图标
        this.tag = 'select'; // 自定义菜单的 HTML 标签
        this.showModal = true
        this.modalWidth = 300
    }

    // 获取菜单执行时的 value,返回空字符串或 false
    getValue(editor) {
        return '1'; // 默认返回空
    }
    getOptions(editor) { 
        const options = [
            { value: '1',text: '入口核对',styleForRenderMenuList: {},},
            { value: '2', text: '出口核对', selected: false },
            // { value: 'shenzhen', text: '深圳' },
        ]
        return options
    }
    // 菜单是否需要激活(如选中加粗文本,"加粗"菜单会激活),用不到则返回 false
    isActive(editor) {
        return false; // 默认不激活
    }

    // 菜单是否需要禁用(如选中 H1,"引用"菜单被禁用),用不到则返回 false
    isDisabled(editor) {
        return false; // 默认不禁用
    }

    // 点击菜单时触发的函数
    exec(editor, value) {
        if (this.isDisabled(editor)) return;
        let name = "按钮1"
        if (value != 1) {
            name = "按钮2"
        }
        //插入元素,因为是要自定义插入样式
        editor.insertNode({
            type: "shiftButton",
            fileName: name,
            children: [{
                text: "", 
                style: {
                    display: 'inline-block', // inline
                    marginLeft: '3px',
                    marginRight: '3px',
                    fontSize: '14px',
                    color: '#409eff',
                    border: '2px solid #409eff',
                    borderRadius: '3px',
                    padding: '2px 3px',
                    backgroundColor: '#fff',
                }
            }],
        });
        //会报错
        // this.vueInstance.editHTML(value);
    }
}
// 注册菜单
const shiftButton = {
    key: "shiftButton", // 定义唯一的 menu key
    factory: () => new MyButtonMenu(this) // 使用箭头函数以避免 `this` 问题
};
export default shiftButton;
劫持编辑器事件和操作
js 复制代码
/**
 * @description shiftButton plugin
 * @author 
 */

import { DomEditor } from '@wangeditor/editor'

// 定义 inline 和 void
function withAttachment(editor) {                  
    const { isInline, isVoid } = editor
    const newEditor = editor

    newEditor.isInline = (elem) => {
        const type = DomEditor.getNodeType(elem)
        if (type === 'shiftButton') return true // 针对 type: attachment ,设置为 inline
        return isInline(elem)
    }

    newEditor.isVoid = (elem) => {
        const type = DomEditor.getNodeType(elem)
        if (type === 'shiftButton') return true // 针对 type: attachment ,设置为 void
        return isVoid(elem)
    }

    return newEditor // 返回 newEditor ,重要!!!
}

export default withAttachment
定义新元素
在编辑器里渲染新元素
js 复制代码
// 注册一个新元素到页面中
import { DomEditor, IDomEditor } from '@wangeditor/editor'
import { Boot, IModuleConf } from '@wangeditor/editor'


import { h, VNode } from 'snabbdom'
import { SlateElement } from '@wangeditor/editor'

// 定义节点结构
const resume = {
    type: 'uploadattachment',
    fileName: 'resume.pdf',
    link: 'https://xxx.com/files/resume.pdf',
    children: [{ text: '' }]  // void 元素必须有一个 children ,其中只有一个空字符串,重要!!!
}

/**
 * 渲染"附件"元素到编辑器
 * @param elem 附件元素,即上文的 myResume
 * @param children 元素子节点,void 元素可忽略
 * @param editor 编辑器实例
 * @returns vnode 节点(通过 snabbdom.js 的 h 函数生成)
 */
function renderAttachment(elem, children, editor) {                                                // JS 语法
  // 当前节点是否选中
  const selected = DomEditor.isNodeSelected(editor, elem)
  const isDisabled = editor.isDisabled()
    // 获取"附件"的数据,参考上文 myResume 数据结构
    const { fileName = '', link = '' } = elem

    // icon 图标 vnode
    const iconVnode = h(
        // HTML tag
        'img',
        // HTML 属性
        {
            props: {
                src: ''//如果需要的话,传入base64格式,参考官网
            },
            style: {
                width: '1em',
                marginRight: '0.1em',
                minWidth: '0',
                minHeight: '0',
            },
        }
        // img 没有子节点,所以第三个参数不用写
    )

    // 元素 vnode
    const attachVnode = h(
        // HTML tag
        'span',
        {
            props: {
                contentEditable: false, // 不可编辑
            },
            style: {
                display: 'inline-block', // inline
                marginLeft: '3px',
                marginRight: '3px',
                fontSize:'14px',
                color:'#409eff',
                border:
                    selected && !isDisabled
                        ? '2px solid var(--w-e-textarea-selected-border-color)' // wangEditor 提供了 css var https://www.wangeditor.com/v5/theme.html
                        : '2px solid #409eff',
                borderRadius: '3px',
                padding: '2px 3px',
                backgroundColor: '#fff',
                cursor: isDisabled ? 'pointer' : 'inherit',
            },
            on: {
                click() {
                    if (!isDisabled) return
                },
            },
        },
        // 子节点
        [ fileName]
    )

    return attachVnode
}

export const renderElemConf = {
    type: 'shiftButton', // 新元素 type ,重要!!!
    renderElem: renderAttachment,
}
把新元素转换为 HTML
js 复制代码
import { SlateElement } from '@wangeditor/editor'

/**
 * 生成"附件"元素的 HTML
 * @param elem 附件元素,即上文的 myResume
 * @param childrenHtml 子节点的 HTML 代码,void 元素可忽略
 * @returns "附件"元素的 HTML 字符串
 */
function attachmentToHtml(elem, childrenHtml) {                             // JS 语法

  // 获取附件元素的数据
  const { link = '', fileName = '', children } = elem
  const style = JSON.stringify(children[0].style);
  // let newStr = style.replace(/\"/g, "'");
  // 生成 HTML 代码
  const html = `<span
        data-w-e-type="shiftButton"
        data-w-e-is-void
        style=" 
            display: inline-block;
            margin-left: 3px;
            margin-right: 3px;
            font-size:14px;
            color:#3e6df1;border:
            2px solid #3e6df1;
          border-radius: 3px;
          padding: 2px 3px;
          cursor: pointer;"
        class="clickable"
        data-fileName="${fileName}"
    >
    ${fileName}</span>`
  return html
}

const elemToHtmlConf = {
  type: 'shiftButton', // 新元素的 type ,重要!!!
  elemToHtml: attachmentToHtml,
}
export default elemToHtmlConf;

解析新元素到页面

js 复制代码
/**
 * @description 解析新元素到html中
 * @author 
 */

function parseHtml(elem, children, editor) {
    const buttonName = elem.getAttribute('data-filename') || '';
    return {
        type: 'shiftButton',
        fileName:buttonName,
        children: [{ text: '' }], // void node 必须有一个空白 text
    }
}

const parseHtmlConf = {
    selector: 'span[data-w-e-type="shiftButton"]',
    parseElemHtml: parseHtml,
}

export default parseHtmlConf
相关推荐
passerby60611 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了1 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅1 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅1 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅2 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment2 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅2 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊2 小时前
jwt介绍
前端
爱敲代码的小鱼2 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax
Cobyte3 小时前
AI全栈实战:使用 Python+LangChain+Vue3 构建一个 LLM 聊天应用
前端·后端·aigc