• React wangEditor5 使用说明


    1、支持包安装

    yarn add @wangeditor/editor
    # 或者 npm install @wangeditor/editor --save
    
    yarn add @wangeditor/editor-for-react
    # 或者 npm install @wangeditor/editor-for-react --save
    
    • 1
    • 2
    • 3
    • 4
    • 5

    2、使用

    import '@wangeditor/editor/dist/css/style.css' // 引入 css
    
    import { useState, useEffect } from 'react'
    import { Editor, Toolbar } from '@wangeditor/editor-for-react'
    import { IDomEditor, IEditorConfig, IToolbarConfig } from '@wangeditor/editor'
    
    type InsertImgType = (url: string, alt: string, href: string) => void;
    type InsertVideoType = (url: string, poster?: string) => void;
    
    const MyEditor: FunctionComponent = () => {
        // editor 实例
        const [editor, setEditor] = useState<IDomEditor | null>(null);
        // 编辑器内容
        const [html, setHtml] = useState('

    hello

    '
    ) // 模拟 ajax 请求,异步设置 html useEffect(() => { setTimeout(() => { setHtml('

    hello world

    '
    ) }, 1500) }, []) // 工具栏配置 const toolbarConfig: Partial<IToolbarConfig> = { excludeKeys: ['group-video'] }; // 编辑器配置 const editorConfig: Partial<IEditorConfig> = { placeholder: '请输入内容...', readOnly: false, MENU_CONF: { uploadImage: { // 自定义上传 -- 图片 customUpload: (file: File, insertFn: InsertImgType) => { if(file.type.startsWith('image/')) { // file 即选中的文件 // 自己实现上传,并得到图片 url alt href // 最后插入图片 insertFn(url, alt, href) } else { // 错误提示 } } }, uploadVideo: { // 自定义上传 -- 视频 customUpload: (file: File, insertFn: InsertVideoType) => { // file 即选中的文件 // 自己实现上传,并得到视频 url poster // 最后插入视频 insertFn(url, poster) } } } } useEffect(() => { // 修改弹窗位置为编译器居中 editor?.on('modalOrPanelShow', modalOrPanel => { if (modalOrPanel.type !== 'modal') return const { $elem } = modalOrPanel; // modal element const width = $elem.width(); const height = $elem.height(); // set modal position z-index $elem.css({ left: '50%', top: '50%', bottom: 'auto', // 需要修改底部间距,不然会受组件自身计算影响 marginLeft: `-${width / 2}px`, marginTop: `-${height / 2}px`, zIndex: 1000 }); }); // 及时销毁 editor ,重要! return () => { if (editor == null) return editor.destroy() setEditor(null) } }, [editor]) return ( <> <div style={{ border: '1px solid #ccc', zIndex: 100}}> <Toolbar editor={editor} defaultConfig={toolbarConfig} mode="default" style={{ borderBottom: '1px solid #ccc' }} /> <Editor defaultConfig={editorConfig} value={html} onCreated={setEditor} onChange={editor => setHtml(editor.getHtml())} mode="default" style={{ height: '500px', overflowY: 'hidden' }} /> </div> </> ) } export default MyEditor;
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106

    3、自定义菜单

    1. 添加自定义菜单弹窗

    import { DomEditor, IDomEditor, IModalMenu, SlateNode, SlateTransforms, t } from '@wangeditor/editor';
    import { DOMElement } from '@wangeditor/editor/dist/editor/src/utils/dom';
    import { genModalButtonElems, genModalInputElems } from './utils';
    
    class EditImageSize implements IModalMenu {
      showModal: boolean;
      modalWidth: number;
      title: string;
      iconSvg?: string;
      hotkey?: string;
      alwaysEnable?: boolean;
      tag: string;
      width?: number;
      private $content: DOMElement | null = null;
      private getSelectedImageNode(editor: IDomEditor): SlateNode | null {
        return DomEditor.getSelectedNodeByType(editor, 'image')
      }
    
      constructor() {
        this.title = t('videoModule.editSize');
        // this.iconSvg = '...';
        this.tag = 'button';
        this.showModal = true;
        this.modalWidth = 320;
      }
    
      // 菜单是否需要激活(如选中加粗文本,“加粗”菜单会激活),用不到则返回 false
      isActive(): boolean {
        // 任何时候,都不用激活 menu
        return false
      }
    
      // 获取菜单执行时的 value ,用不到则返回空 字符串或 false
      getValue(): string | boolean {
        // 插入菜单,不需要 value
        return ''
      }
    
      // 菜单是否需要禁用(如选中 H1 ,“引用”菜单被禁用),用不到则返回 false
      isDisabled(editor: IDomEditor): boolean {
        if (editor.selection == null) return true
    
        const videoNode = this.getSelectedImageNode(editor)
        if (videoNode == null) {
          // 选区未处于 image node ,则禁用
          return true
        }
        return false
      }
    
      // 点击菜单时触发的函数
      exec() {
        // 点击菜单时,弹出 modal 之前,不需要执行其他代码
        // 此处空着即可
      }
    
      // 弹出框 modal 的定位:1. 返回某一个 SlateNode; 2. 返回 null (根据当前选区自动定位)
      getModalPositionNode(editor: IDomEditor): SlateNode | null {
        return this.getSelectedImageNode(editor);
      }
    
      // 定义 modal 内部的 DOM Element
      getModalContentElem(editor: IDomEditor): DOMElement {
        const $content = this.$content || document.createElement('div');
        const [inputWidthContainerElem, inputWidthElem] = genModalInputElems(
          t('videoModule.width'),
          `input-width-${Math.random().toString(36).slice(2)}`,
          'auto'
        );
        const [inputHeightContainerElem, inputHeightElem] = genModalInputElems(
          t('videoModule.height'),
          `input-height-${Math.random().toString(36).slice(2)}`,
          'auto'
        );
        const buttonContainerElem = genModalButtonElems(
          `button-${Math.random().toString(36).slice(2)}`,
          t('videoModule.ok')
        );
        $content.append(inputWidthContainerElem);
        $content.append(inputHeightContainerElem);
        $content.append(buttonContainerElem);
    
        const imageNode = this.getSelectedImageNode(editor) as unknown as HTMLElement;
    
        // 绑定事件(第一次渲染时绑定,不要重复绑定)
        if (this.$content == null) {
          buttonContainerElem.onclick = () => {
            const width = Number(inputWidthElem.value);
            const height = Number(inputHeightElem.value);
            console.log(editor, isNaN(width) ? inputWidthElem.value : width ? width +'px' : 'auto', isNaN(height) ? inputHeightElem.value : height ? height +'px' : 'auto')
            editor.restoreSelection();
    
            // 修改尺寸
            SlateTransforms.setNodes(
              editor,
              {
                style: {
                  width: isNaN(width) ? inputWidthElem.value : width ? width +'px' : 'auto',
                  height: isNaN(height) ? inputHeightElem.value : height ? height +'px' : 'auto',
                }
              } as any,
              {
                match: n => DomEditor.checkNodeType(n, 'image'),
              }
            )
            editor.hidePanelOrModal(); // 隐藏 modal
          }
        }
    
        if (imageNode == null) return $content;
        // 初始化 input 值
        const { width = 'auto', height = 'auto' } = imageNode.style;
        inputWidthElem.value = width || 'auto';
        inputHeightElem.value = height || 'auto';
        setTimeout(() => {
          inputWidthElem.focus()
        });
    
        return $content // 返回 DOM Element 类型
        // PS:也可以把 $content 缓存下来,这样不用每次重复创建、重复绑定事件,优化性能
      }
    }
    
    export const EditImageSizeConf = {
      key: 'editImageSize', // 定义 menu key :要保证唯一、不重复(重要)
      factory() {
        return new EditImageSize() // 把 `YourMenuClass` 替换为你菜单的 class
      },
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129

    公用工具utils

    // 生成输入框
    export const genModalInputElems = (label: string, id: string, val: string): [HTMLLabelElement, HTMLInputElement] => {
      const $label = document.createElement('label');
      $label.className = 'babel-container';
      const $span = document.createElement('span');
      $span.textContent = label;
      const $input = document.createElement('input');
      $input.type = 'text';
      $input.id = id;
      $input.value = val;
      $label.append($span);
      $label.append($input);
      return [$label, $input];
    };
    
    // 生成按钮
    export const genModalButtonElems = (id: string, text: string) => {
      const $content = document.createElement('div');
      $content.className = 'button-container';
      const $button = document.createElement('button');
      $button.id = id;
      $button.textContent = text;
      $content.append($button);
      return $content;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    2. 注册自定义菜单

      // 注册自定义菜单
      useEffect(() => {
        try  {
          Boot.registerMenu(EditImageSizeConf);
        } catch (e) {}
      }, [])
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    3. 挂载到工具栏

      // 工具栏配置
      const toolbarConfig: Partial<IToolbarConfig> = {
        insertKeys: {
          index: 5, // 插入的位置,基于当前的 toolbarKeys
          keys: ['editImageSize']
        }
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    4. 挂载到组件hover菜单

      // 编辑器配置
      const editorConfig: Partial<IEditorConfig> = {
        hoverbarKeys: {
          image: {
            menuKeys: ['editImageSize']  // 注意:要保留原有的菜单需加上之前的菜单key
          }
        }
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
  • 相关阅读:
    第71步 时间序列建模实战:ARIMA建模(Python)
    微信答题小程序产品研发-系统架构设计
    高可用架构,去中心化有多重要?
    1474_AURIX TC275 WDT的运行模式
    PG SQL 问题:Character with value 0x0a must be escaped
    React自定义Hook函数:高效组件开发的秘密武器
    JAVA算法练习(10):绳圈
    淘宝API关键词搜索接口调用示例
    Bellman-Ford算法与SPFA算法详解
    Vue3 - 全局 API(相比 Vue2 有什么变化?具体怎么使用?)
  • 原文地址:https://blog.csdn.net/qq_36306693/article/details/133351322