一片两片姜 發表於 2020-4-10 21:01:00

react引入富文本编辑器TinyMCE

<p>&nbsp;</p>
<p>这周做了一个PC端的service后台需求,要求有富文本编辑器,插入图片、表格,字体字号背景色等等,</p>
<p>最后引入了富文本编辑器TinyMCE</p>
<p>&nbsp;</p>
<p>对于TinyMCE的简介:</p>
<p>TinyMCE是一款易用、且功能强大的所见即所得的富文本编辑器。同类程序有:UEditor、Kindeditor、Simditor、CKEditor、wangEditor、Suneditor、froala等等。</p>
<p>TinyMCE的优势:</p>
<ul>
<li>开源可商用,基于LGPL2.1</li>
<li>插件丰富,自带插件基本涵盖日常所需功能</li>
<li>接口丰富,可扩展性强,有能力可以无限拓展功能</li>
<li>界面好看,符合现代审美</li>
<li>提供经典、内联、沉浸无干扰三种模式</li>
<li>对标准支持优秀(自v5开始)</li>
<li>多语言支持,官网可下载几十种语言。</li>
</ul>
<p><img src="https://img2020.cnblogs.com/blog/1459640/202004/1459640-20200410202834332-749935663.png"></p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;上图为需求中配置的富文本编辑器的内容,我的需求TinyMCE完全可以满足,</p>
<p>&nbsp; TinyMCE&nbsp;官网:www.tiny.cloud</p>
<p>&nbsp;</p>
<p>&nbsp; &nbsp;TinyMCE支持vue、react、angular&nbsp;</p>
<p><img src="https://img2020.cnblogs.com/blog/1459640/202004/1459640-20200410203104435-19581755.png"></p>
<p>&nbsp;</p>
<p>&nbsp;这次改动的工程使用的是react,</p>
<p>在需求中封装了一个适合我们的富文本编辑组件:</p>
<p><img src="https://img2020.cnblogs.com/blog/1459640/202004/1459640-20200410203751666-613077180.png"></p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>editor组件中的内容:</p>
<div class="cnblogs_code">
<pre>import React from 'react'<span style="color: rgba(0, 0, 0, 1)">;
import </span>'./Editor.scss'<span style="color: rgba(0, 0, 0, 1)">;

import {isDev, nginxPrefix} from </span>'@/config'<span style="color: rgba(0, 0, 0, 1)">;

import {Upload, Button, Icon, Popconfirm, Spin, message} from </span>'antd'<span style="color: rgba(0, 0, 0, 1)">;

import _get from </span>'lodash/get'<span style="color: rgba(0, 0, 0, 1)">;
import _uniqueId from </span>'lodash/uniqueId'<span style="color: rgba(0, 0, 0, 1)">;

import PropTypes from </span>'prop-types'<span style="color: rgba(0, 0, 0, 1)">;

</span><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)">
* @props string id? 标识符
* @props number height? 高度
* @props string defaultContent? 初始内容
* @props boolen disabled? 禁用
* @props function onDelete? 删除事件
* @props function onAdd? 添加事件
* @props object uploadConfig? 自定义上传配置
* @event function getEditorContent 获取编辑内容
* @event function setEditorContent 设置编辑内容
* @event function insertContent 插入编辑内容
</span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">
class Editor extends React.Component {
    constructor(props) {
      super(props);

      const tinymceId </span>= `editor-tinymce-${<span style="color: rgba(0, 0, 255, 1)">this</span>.props.id}-${_uniqueId()}-${<span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Date().getTime()}`;

      </span><span style="color: rgba(0, 0, 255, 1)">this</span>.state =<span style="color: rgba(0, 0, 0, 1)"> {
            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 编辑器ID</span>
<span style="color: rgba(0, 0, 0, 1)">            tinymceId,
            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 编辑器实例</span>
            editor: <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">
      };
    }

    componentDidMount() {
      const {height </span>= 300, defaultContent = ''} = <span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.props;
      window.tinymce.init({
            selector: `#${</span><span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.state.tinymceId}`,
            language: </span>'zh_CN'<span style="color: rgba(0, 0, 0, 1)">,
            height: height,
            min_height: </span>200<span style="color: rgba(0, 0, 0, 1)">,
            width: </span>'100%'<span style="color: rgba(0, 0, 0, 1)">,
            resize: </span><span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">,
            default_link_target: </span>'_blank'<span style="color: rgba(0, 0, 0, 1)">,
            init_instance_callback: editor </span>=&gt;<span style="color: rgba(0, 0, 0, 1)"> {
                </span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (defaultContent) {
                  editor.setContent(defaultContent);
                }
            },
            paste_enable_default_filters: </span><span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">,
            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> paste_word_valid_elements: () =&gt; {</span>
            <span style="color: rgba(0, 128, 0, 1)">//
</span>            <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> },</span>
            <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 插件配置</span>
            plugins: 'table image lists link paste'<span style="color: rgba(0, 0, 0, 1)">,
            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 菜单配置</span>
            menubar: 'file edit view insert format'<span style="color: rgba(0, 0, 0, 1)">,
            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 工具栏配置</span>
            <span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)"> eslint-disable </span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">
            toolbar: </span>'undo redo | styleselect | bold italic | alignleft aligncenter alignright alignjustify | outdent indent | blockquote | table | image link | forecolor backcolor | bullist numlist | removeformat'
            <span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)"> eslint-enable </span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">
      }).then(() </span>=&gt; <span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.setState({editor}));
    }

    componentWillUnmount() {
      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 在某些生命周期中,实例并未生成,没有卸载方法。(组件挂载阶段)</span>
      <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> if (this.state.editor !== null) {</span>
      <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">   this.state.editor.destroy();</span>
      <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> }</span>
      window.tinymce.get(<span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.state.tinymceId).destroy();
    }

    getEditorContent </span>= () =&gt;<span style="color: rgba(0, 0, 0, 1)"> {
      </span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.state.editor.getContent();
    }

    setEditorContent </span>= (content) =&gt;<span style="color: rgba(0, 0, 0, 1)"> {
      window.tinymce.get(</span><span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.state.tinymceId).setContent(content);
    }

    insertContent </span>= (content) =&gt;<span style="color: rgba(0, 0, 0, 1)"> {
      </span><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)"> {
            </span><span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.state.editor.insertContent(content);
      } </span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)">(e) {
            window.tinymce.get(</span><span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.state.tinymceId).insertContent(content);
      }
    }

    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 默认上传配置</span>
    uploadConfig =<span style="color: rgba(0, 0, 0, 1)"> {
      name: </span>'file'<span style="color: rgba(0, 0, 0, 1)">,
      action: (isDev </span>? '' : nginxPrefix) + '/admin/common/uploadFile'<span style="color: rgba(0, 0, 0, 1)">,
      headers: {
            authorization: </span>'authorization-text'<span style="color: rgba(0, 0, 0, 1)">,
      },
      onChange: (info) </span>=&gt;<span style="color: rgba(0, 0, 0, 1)"> {
            </span><span style="color: rgba(0, 0, 255, 1)">if</span> (info.file.status === 'done'<span style="color: rgba(0, 0, 0, 1)">) {
                message.success(</span>'图片上传成功'<span style="color: rgba(0, 0, 0, 1)">);
                </span><span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.state.editor.insertContent(
                  `</span>&lt;img src="${_get(info, 'file.response.data.result')}" &gt;<span style="color: rgba(0, 0, 0, 1)">`
                );
            } </span><span style="color: rgba(0, 0, 255, 1)">else</span> <span style="color: rgba(0, 0, 255, 1)">if</span> (info.file.status === 'error'<span style="color: rgba(0, 0, 0, 1)">) {
                message.error(</span>'图片上传失败'<span style="color: rgba(0, 0, 0, 1)">);
            }
      },
      accept: </span>'.jpg,.jpeg,.jpe,.png,.bmp'<span style="color: rgba(0, 0, 0, 1)">
    }

    render() {
      const {uploadConfig} </span>= <span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.props;
      </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> (
            </span>&lt;Spin spinning={<span style="color: rgba(0, 0, 255, 1)">this</span>.props.disabled} indicator={&lt;Icon type="stop" style={{color: '#555'}} /&gt;}&gt;
                &lt;div className="editor-container"&gt;
                  &lt;textarea id={<span style="color: rgba(0, 0, 255, 1)">this</span>.state.tinymceId} /&gt;
                  &lt;div className="btn-bar"&gt;
                        &lt;Upload {...(uploadConfig ? uploadConfig : <span style="color: rgba(0, 0, 255, 1)">this</span>.uploadConfig)}&gt;
                            &lt;Button&gt;&lt;Icon type="upload" /&gt;添加本地图片&lt;/Button&gt;
                        &lt;/Upload&gt;
                        &lt;span&gt;<span style="color: rgba(0, 0, 0, 1)">
                            {
                              </span><span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.props.onAdd
                              </span>&amp;&amp;
                              &lt;Button icon="plus" shape="circle" onClick={<span style="color: rgba(0, 0, 255, 1)">this</span>.props.onAdd} /&gt;
<span style="color: rgba(0, 0, 0, 1)">                            }
                            {
                              </span><span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.props.onDelete
                              </span>&amp;&amp;
                              &lt;<span style="color: rgba(0, 0, 0, 1)">Popconfirm
                                    title</span>="无法撤回,确认删除?"<span style="color: rgba(0, 0, 0, 1)">
                                    onConfirm</span>={<span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.props.onDelete}
                                    okText</span>="确认"<span style="color: rgba(0, 0, 0, 1)">
                                    cancelText</span>="取消"<span style="color: rgba(0, 0, 0, 1)">
                                    placement</span>="leftBottom"
                              &gt;
                                    &lt;<span style="color: rgba(0, 0, 0, 1)">Button
                                        type</span>="danger"<span style="color: rgba(0, 0, 0, 1)">
                                        icon</span>="delete"<span style="color: rgba(0, 0, 0, 1)">
                                        shape</span>="circle"<span style="color: rgba(0, 0, 0, 1)">
                                        style</span>={{marginLeft: '4px'<span style="color: rgba(0, 0, 0, 1)">}}
                                        onClick</span>={() =&gt;<span style="color: rgba(0, 0, 0, 1)"> {
                                          </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 当富文本编辑器中没有内容时,删除按钮不弹窗,直接调用删除方法</span>
                                          const content = <span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.getEditorContent();

                                          </span><span style="color: rgba(0, 0, 255, 1)">if</span> (!<span style="color: rgba(0, 0, 0, 1)">content) {
                                                </span><span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.props.onDelete();
                                          }
                                        }}
                                    </span>/&gt;
                              &lt;/Popconfirm&gt;
<span style="color: rgba(0, 0, 0, 1)">                            }
                        </span>&lt;/span&gt;
                  &lt;/div&gt;
                &lt;/div&gt;
            &lt;/Spin&gt;
<span style="color: rgba(0, 0, 0, 1)">      );
    }
}

Editor.defaultProps </span>=<span style="color: rgba(0, 0, 0, 1)"> {
    id: </span>'no-props-id'<span style="color: rgba(0, 0, 0, 1)">,
    height: </span>300<span style="color: rgba(0, 0, 0, 1)">,
    defaultContent: </span>''<span style="color: rgba(0, 0, 0, 1)">,
    onDelete: </span><span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">,
    onAdd: </span><span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">,
    disabled: </span><span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">,
    uploadConfig: </span><span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">
};

Editor.propTypes </span>=<span style="color: rgba(0, 0, 0, 1)"> {
    id: PropTypes.string,
    height: PropTypes.number,
    defaultContent: PropTypes.string,
    onDelete: PropTypes.func,
    onAdd: PropTypes.func,
    disabled: PropTypes.bool
};

export </span><span style="color: rgba(0, 0, 255, 1)">default</span> Editor;</pre>
</div>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>组件最终提交的是一段HTML,图片只是一个URL,大小非常小,非常实用</p>
<p>这次的需求中富文本编辑框是录入案例,</p>
<p>当然有录入就有展示,对于展示也很简单,在展示的地方加了个图片放大的功能</p>
<p>展示的时候有个问题,就是图片的大小我们想控制的很小,这样整体的内容都可以看到,图片具体内容可以点击图片放大,</p>
<p><img src="https://img2020.cnblogs.com/blog/1459640/202004/1459640-20200410204724983-1171601361.png"></p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p><img src="https://img2020.cnblogs.com/blog/1459640/202004/1459640-20200410205949644-1506475277.png"></p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;<img src="https://img2020.cnblogs.com/blog/1459640/202004/1459640-20200410205528152-214627256.png"></p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>来来来,展示一下我男神图片点击放大之后的帅图:</p>
<p><img src="https://img2020.cnblogs.com/blog/1459640/202004/1459640-20200410205737542-1344833408.png"></p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p><img src="https://img2020.cnblogs.com/blog/1459640/202004/1459640-20200410205831631-1998709057.png"></p>
<p>&nbsp;</p><br><br>
来源:https://www.cnblogs.com/katydids/p/12676111.html
頁: [1]
查看完整版本: react引入富文本编辑器TinyMCE