美文网首页react.js(阿里ice)
Ueditor踩坑之旅(react)

Ueditor踩坑之旅(react)

作者: 前端喵 | 来源:发表于2018-04-24 22:28 被阅读342次

    2018年4月23日:

    前言:


    其实市面上的富文本编辑器还是不少的,在react项目里我们一般会用wangEditor,最大的原因是他已经集成在npm里面了,用起来比较方便,但是功能方面没有那么齐全,最近项目上有个问题,客户要求富文本编辑器里可以设置行间距,可以在编辑器里调整图片大小,搜了一下,可以设置行间距的不少,可以调整图片大小的可能只有百度的Ueditor了。于是入坑百度Ueditor。
    官网地址: http://ueditor.baidu.com/website/
    不建议下完整版的代码,需要自己编译,很可能花大把的时间编译不成功。

    image.png
    本文代码github地址:https://github.com/AMM9394/ueditorTest.git

    1、用create-react-app脚手架创建react项目


    将下载好的文件夹放在public文件夹下。这里说一下为什么要用create-react-app,我们下好的源码是要放在跟index.html同一文件夹下的,打包的时候该文件夹下的文件不会被打包,同时要将Ueditor所在的文件夹复制到打包后的文件夹中,这个我还没有去细看到底webpack做了什么操作,但是creact-react-app产生的打包文件可以做到上述事情。

    2018.6.7更新,如果不是create-react-app创建的项目,在开发时和前面说的一样要把文件放在和index.html同一个文件夹下,一般都是public,然后修改一下webpack相关的配置。研究了一下create-react-app里面关于webpack的配置,首先说一下create-react-app的start,build文件都在react-scripts包里面,需要在node-modules里面找到react-scripts才能找到start.js,build.js等。webpack的配置在build.js里面,他多做的事情就是吧public文件夹复制了一份,如图所示。如果需要自己配置的话可以参考一下create-react-app的build.js文件,代码贴在了文章末尾

    public.png

    2、引入及初始化


    将下载好的文件夹放入到public文件下,与index.html同级。
    并在index.html里面加入以下代码

        <!-- 配置文件 -->
        <script type="text/javascript" src="ueditor/ueditor.config.js"></script>
        <!-- 编辑器源码文件 -->
        <script type="text/javascript" src="ueditor/ueditor.all.js"></script>
    

    此时window对象里面会有UE对象。
    在APP.js里面从初始化编辑器

    ...
    componentDidMount(){
        let UE = window.UE;
        UE.getEditor('container', {
          autoHeightEnabled: true,
          autoFloatEnabled: true
        });
      }
    ...
    render(){
      return(div>
        ...
        <textarea id="container"></textarea>
    </div>);
    }
    

    展示效果如图


    image.png

    3、封装组件


    新建ueditor.js文件封装udeitor

    import React from 'react';
    
    export default class Ueditor extends React.Component {
      constructor(props){
        super(props);
        this.state={
          id:this.props.id?this.props.id:null,
          ueditor :null,
        }
      }
      componentDidMount(){
        let UE = window.UE;
        let {id} = this.state;
        if(id){
          try {
            /*加载之前先执行删除操作,否则如果存在页面切换,
            再切回带编辑器页面重新加载时不刷新无法渲染出编辑器*/
            UE.delEditor(id);
          }catch (e) {}
          let  ueditor = UE.getEditor(id, {
            autoHeightEnabled: true,
            autoFloatEnabled: true
          });
          this.setState({ueditor });
        }
      }
      render(){
        let {id} = this.state;
        return (
          <div>
            <textarea id={id} />
          </div>
        );
      }
    }
    

    在需要编辑器的页面引入组件并使用

    import Ueditor from './component/ueditor';
    ...
    render(){
      return(div>
        ...
        <Ueditor id={'myeditor'}/>
    </div>);
    

    4、各种坑


    4.1、全屏问题


    全屏的处理函数在ueditor.all.js里面对函数进行修改即可实现全屏
    源代码中的设置编辑器放大是根据父节点来放大的,但是既然是放大当然全屏比较high啊。so,改代码吧。
    修改后的代码如下

    _updateFullScreen:function () {
                if (this._fullscreen) {
                    var vpRect = uiUtils.getViewportRect();
                    //修改为无论什么情况都相对于浏览器窗口全屏
                  //此处的height是整个编辑器的高度
                    this.getDom().style.cssText = 'border:0;position:fixed;left:0;top:0'  + 'px;width:' + window.innerWidth + 'px;height:' + window.innerHeight + 'px;z-index:' + (this.getDom().style.zIndex * 1 + 1000);
                    // uiUtils.setViewportOffset(this.getDom(), { left:0, top:this.editor.options.topOffset || 0 });
                  //设置编辑器内容的高度
                  this.editor.setHeight(window.innerHeight - this.getDom('toolbarbox').offsetHeight - this.getDom('bottombar').offsetHeight - (this.editor.options.topOffset || 0),true);
                    //不手动调一下,会导致全屏失效
                    if(browser.gecko){
                        try{
                            window.onresize();
                        }catch(e){
    
                        }
    
                    }
                }
            },
    

    主要修改的地方如下图:


    image.png

    图中修改的position:fixed,如果编辑器父元素设置了transform属性的话编辑器还是只相对于父节点放大

    4.2、Ueditor图片保存问题


    在使用的过程中发现了一个很尴尬的问题,就是无论你在编辑器中给图片什么格式,保存的时候他都会把格式去掉,也就是说我们保存的富文本的html里面所有的img标签都没有style,真的是调试了很久才发现问题在哪里。在编辑器的配置文件ueditor.config.js里面有一个配置whitList,修改这个配置项里面img的配置(在整个文件中搜索img即可找到),如图所示将style放进去,保存时img标签的style属性才会被保存,否则该标签会被移除,也就是说不管图片格式怎么设置均不可能保存成功


    image.png

    //2018年5月2日更新

    4.3、Ueditor父组件props改变编辑器内容不渲染问题


    这个要用到react 的componentWillReceiveProps这一函数

     componentWillReceiveProps(newProps){
            let {editor} = this.state;
            if(newProps.content){
                    //有内容需要更新时,newProps.content是你需要更新的编辑器的内容,content是自己设置的prop
                editor.addListener("ready", function () {
                    // editor准备好之后才可以使用
                 editor.setContent(newProps.content);
                    });
            }
        },
    

    //2018年6月8日更新

    4.4、一个页面实例化多个Ueditor编辑器


    这个问题其实不算问题,主要是代码习惯,之前后台的小朋友把ueditor作为了全局变量,导致了实例化两个ueditor时,每次更改只保存了最后一个编辑器的内容,所以,必须要把实例化的编辑器放在state里面,通过state来更新并使用编辑器。同时在使用页面两个编辑器的id要区分开。



    creact-react-app中build.js的内容

    // @remove-on-eject-begin
    /**
     * Copyright (c) 2015-present, Facebook, Inc.
     *
     * This source code is licensed under the MIT license found in the
     * LICENSE file in the root directory of this source tree.
     */
    // @remove-on-eject-end
    'use strict';
    
    // Do this as the first thing so that any code reading it knows the right env.
    process.env.BABEL_ENV = 'production';
    process.env.NODE_ENV = 'production';
    
    // Makes the script crash on unhandled rejections instead of silently
    // ignoring them. In the future, promise rejections that are not handled will
    // terminate the Node.js process with a non-zero exit code.
    process.on('unhandledRejection', err => {
      throw err;
    });
    
    // Ensure environment variables are read.
    require('../config/env');
    
    const path = require('path');
    const chalk = require('chalk');
    const fs = require('fs-extra');
    const webpack = require('webpack');
    const config = require('../config/webpack.config.prod');
    const paths = require('../config/paths');
    const checkRequiredFiles = require('react-dev-utils/checkRequiredFiles');
    const formatWebpackMessages = require('react-dev-utils/formatWebpackMessages');
    const printHostingInstructions = require('react-dev-utils/printHostingInstructions');
    const FileSizeReporter = require('react-dev-utils/FileSizeReporter');
    const printBuildError = require('react-dev-utils/printBuildError');
    
    const measureFileSizesBeforeBuild =
      FileSizeReporter.measureFileSizesBeforeBuild;
    const printFileSizesAfterBuild = FileSizeReporter.printFileSizesAfterBuild;
    const useYarn = fs.existsSync(paths.yarnLockFile);
    
    // These sizes are pretty large. We'll warn for bundles exceeding them.
    const WARN_AFTER_BUNDLE_GZIP_SIZE = 512 * 1024;
    const WARN_AFTER_CHUNK_GZIP_SIZE = 1024 * 1024;
    
    // Warn and crash if required files are missing
    if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) {
      process.exit(1);
    }
    
    // First, read the current file sizes in build directory.
    // This lets us display how much they changed later.
    measureFileSizesBeforeBuild(paths.appBuild)
      .then(previousFileSizes => {
        // Remove all content but keep the directory so that
        // if you're in it, you don't end up in Trash
        fs.emptyDirSync(paths.appBuild);
        // Merge with the public folder
        copyPublicFolder();
        // Start the webpack build
        return build(previousFileSizes);
      })
      .then(
        ({ stats, previousFileSizes, warnings }) => {
          if (warnings.length) {
            console.log(chalk.yellow('Compiled with warnings.\n'));
            console.log(warnings.join('\n\n'));
            console.log(
              '\nSearch for the ' +
                chalk.underline(chalk.yellow('keywords')) +
                ' to learn more about each warning.'
            );
            console.log(
              'To ignore, add ' +
                chalk.cyan('// eslint-disable-next-line') +
                ' to the line before.\n'
            );
          } else {
            console.log(chalk.green('Compiled successfully.\n'));
          }
    
          console.log('File sizes after gzip:\n');
          printFileSizesAfterBuild(
            stats,
            previousFileSizes,
            paths.appBuild,
            WARN_AFTER_BUNDLE_GZIP_SIZE,
            WARN_AFTER_CHUNK_GZIP_SIZE
          );
          console.log();
    
          const appPackage = require(paths.appPackageJson);
          const publicUrl = paths.publicUrl;
          const publicPath = config.output.publicPath;
          const buildFolder = path.relative(process.cwd(), paths.appBuild);
          printHostingInstructions(
            appPackage,
            publicUrl,
            publicPath,
            buildFolder,
            useYarn
          );
        },
        err => {
          console.log(chalk.red('Failed to compile.\n'));
          printBuildError(err);
          process.exit(1);
        }
      );
    
    // Create the production build and print the deployment instructions.
    function build(previousFileSizes) {
      console.log('Creating an optimized production build...');
    
      let compiler = webpack(config);
      return new Promise((resolve, reject) => {
        compiler.run((err, stats) => {
          if (err) {
            return reject(err);
          }
          const messages = formatWebpackMessages(stats.toJson({}, true));
          if (messages.errors.length) {
            // Only keep the first error. Others are often indicative
            // of the same problem, but confuse the reader with noise.
            if (messages.errors.length > 1) {
              messages.errors.length = 1;
            }
            return reject(new Error(messages.errors.join('\n\n')));
          }
          if (
            process.env.CI &&
            (typeof process.env.CI !== 'string' ||
              process.env.CI.toLowerCase() !== 'false') &&
            messages.warnings.length
          ) {
            console.log(
              chalk.yellow(
                '\nTreating warnings as errors because process.env.CI = true.\n' +
                  'Most CI servers set it automatically.\n'
              )
            );
            return reject(new Error(messages.warnings.join('\n\n')));
          }
          return resolve({
            stats,
            previousFileSizes,
            warnings: messages.warnings,
          });
        });
      });
    }
    
    function copyPublicFolder() {
      fs.copySync(paths.appPublic, paths.appBuild, {
        dereference: true,
        filter: file => file !== paths.appHtml,
      });
    }
    
    

    相关文章

      网友评论

      • 虎牙666:您好,您上传图片写成功了吗?是怎么配置的?
        前端喵:@huya_666 能否分享一下解决办法呢。嘻嘻
        虎牙666:@前端喵 好的,感谢,我已经解决了!:smile:
        前端喵:@huya_666 上传图片我这边是靠后台实现的。在那个php文件夹里,config.json
      • 丿阿晨丶:有没有源码 贴一份~~~~
        前端喵:@丿阿晨丶 ,抱歉看到的比较晚,不知道是否还需要。https://github.com/AMM9394/ueditor-test.git
      • QUINCYE:按照上述配置后,没有正确加载到对应js文件,找不到getEditor 方法
        joinfunny:4.3、Ueditor父组件props改变编辑器内容不渲染问题
        这个解决方案是有问题的,addEventLister是注册一个事件,每次有newProps的时候都要重新注册一个ready事件吗?更何况,你注册这么多次,他也紧紧会触发一次。纠其原因是因为当前这个实例还没有初始化完成,导致setContent找不到对应的dom节点导致的报错。
        所以我们需要在初始化的时候注册ready事件,然后在ready事件内将得到的editor赋值为我们组建的一个私有属性
        前端喵:你先检查一下ueditor.all.js是不是存在,如果存在,你把你的文件怎么引怎么配的发来我看一下吧。

      本文标题:Ueditor踩坑之旅(react)

      本文链接:https://www.haomeiwen.com/subject/slkrlftx.html