react项目结合braft-editor实现精致的富文本组件

react项目结合braft-editor实现精致的富文本组件

简介: 基于react的富文本组件本来就不多,braft-editor是react里人气相当高的富文本,这里基于他打造一个精致的富文本。

项目主要依赖:(先安装,步骤略)

create-react-app:3.0.0


{
    "react":"16.8.6" ,
    "react-router-dom":"5.0.0",
    "antd": "^3.19.2",
    "axios": "^0.19.0",
    "braft-editor": "^2.3.6",
    "braft-polyfill": "^0.0.1",
    "prop-types": "^15.7.2"
}

1.组件

文件目录

在这里插入图片描述

src/components/AppEditor/index.jsx

import React, { Component } from "react";
// 添加ie10支持
import "braft-polyfill";
import BraftEditor from "braft-editor";
import "braft-editor/dist/index.css";
import PropTypes from "prop-types";
import axios from "axios";
import { message } from "antd";


import "./style.less"; // 见下文
import config from "./config"; // 见下文
import mediaBaseconfig from "./media"; // 见下文
import {
  uploadHtml,
  uploadImage,
  maxFileSize,
  isImageFile
} from "@/utils/upload"; // 见下文 uploadHtml和uploadImage是定义的上传文件到后端的接口,根据自己的项目更换


class AppEditor extends Component {
  state = {
    // 创建一个空的editorState作为初始值
    editorState: BraftEditor.createEditorState(null)
  };

  static propTypes = {
    // 富文本初始内容
    html: PropTypes.string,
    //  限制图片文件大小
    fileMaxSize: PropTypes.number
  };

  // 默认的props
  static defaultProps = {
    fileMaxSize: 2 // 单位默认是Mb,
  };

  componentWillUnmount() {
    this.setState = (state, callback) => {
      return;
    };
  }

  // 初始化编辑器内容
  async componentWillReceiveProps(props) {
    try {
      const { html } = props;
      const { data } = await axios.get(html);
      this.setState({
        editorState: BraftEditor.createEditorState(data)
      });
      return data;
    } catch (error) {
      console.log(error);
    }
  }

  // 子组件上传方法(供父组件调用)
  handleSubmitContent = async () => {
    const htmlContent = this.state.editorState.toHTML();
    try {
      const url = await uploadHtml(htmlContent);
      return url;
    } catch (error) {
      console.log(error);
    }
  };

  // 监听编辑器内容变化同步内容到state
  handleEditorChange = editorState => {
    this.setState({ editorState });
  };

  // 媒体上传校验
  mediaValidate = file => {
    const { fileMaxSize } = this.props;

    // 类型限制
    if (!isImageFile(file)) {
      return false;
    }

    // 大小限制
    if (fileMaxSize && !maxFileSize(file, fileMaxSize)) {
      return false;
    }

    return true;
  };

  // 媒体上传
  mediaUpload = async ({ file, success, error }) => {
    message.destroy();
    try {
      const url = await uploadImage(file);
      success({ url });
      message.success("上传成功");
    } catch (err) {
      error(err);
      message.error(err.message || "上传失败");
    }
  };

  render() {
    const { editorState } = this.state;

    // 媒体配置
    const media = {
      // 上传校验
      validateFn: this.mediaValidate,
      // 上传
      uploadFn: this.mediaUpload,
      // 基本配置
      ...mediaBaseconfig
    };

    return (
      <div
        className="custom-editor"
        style={{ marginTop: 20, ...this.props.style }}
      >
        <BraftEditor
          style={{ height: 600, overflow: "hidden" }}
          {...config}
          media={media}
          value={editorState}
          onChange={this.handleEditorChange}
        />
      </div>
    );
  }
}

export default AppEditor;



src/components/AppEditor/config.js

const config = {
  placeholder: "请输入内容",

  // 编辑器工具栏的控件列表
  controls: [
    "headings",
    "font-size",
    "letter-spacing",
    "separator", // 分割线

    "text-color",
    "bold",
    "italic",
    "underline",
    "strike-through",
    "remove-styles",
    "separator",
    "text-align",
    "separator",
    "emoji",
    "media"
  ],
  
  // 字号配置
  fontSizes: [12, 14, 16, 18, 20, 24, 28, 30, 32, 36],

  // 图片工具栏的可用控件
  imageControls: [
    "float-left", // 设置图片左浮动
    "float-right", // 设置图片右浮动
    "align-left", // 设置图片居左
    "align-center", // 设置图片居中
    "align-right", // 设置图片居右
    "link", // 设置图片超链接
    "size", // 设置图片尺寸
    "remove" // 删除图片
  ]
};
export default config;

src/components/AppEditor/media.js

const mediaBaseconfig = {
  // 文件限制
  accepts: {
    image: "image/png,image/jpeg,image/gif,image/webp,image/apng,image/svg",
    video: false,
    audio: false
  },

  //   允许插入的外部媒体的类型
  externals: {
    // 是否允许插入外部图片,
    image: false,
    //    是否允许插入外部视频,
    video: false,
    //    是否允许插入外部视频,
    audio: false,
    //    是否允许插入嵌入式媒体,例如embed和iframe标签等,
    embed: false
  }
};

export default mediaBaseconfig;


src/components/AppEditor/style.less

:global(.custom-editor .bf-controlbar) {
  background-color: #fff;
}

:global(.custom-editor .bf-container) {
  border: 1px solid #c5c5c5;
}

:global(.custom-editor .bf-content) {
  min-height: 600px;
}




src/utils/upload.js

import { message } from "antd";

/**
 *
 * @param {file} file 源文件
 * @desc 限制为图片文件
 * @retutn 是图片文件返回true否则返回false
 */

export const isImageFile = (file, fileTypes) => {
  const types = fileTypes || [
    "image/png",
    "image/gif",
    "image/jpeg",
    "image/jpg",
    "image/bmp",
    "image/x-icon",
    "image/webp",
    "image/apng",
    "image/svg"
  ];

  const isImage = types.includes(file.type);
  if (!isImage) {
    message.error("上传文件非图片格式!");
    return false;
  }

  return true;
};

/**
 *
 * @param {file} file 源文件
 * @param {number} fileMaxSize  图片限制大小单位(MB)
 * @desc 限制为文件上传大小
 * @retutn 在限制内返回true否则返回false
 */

export const maxFileSize = (file, fileMaxSize = 2) => {
  const isMaxSize = file.size / 1024 / 1024 < fileMaxSize;
  if (!isMaxSize) {
    message.error("上传头像图片大小不能超过 " + fileMaxSize + "MB!");
    return false;
  }
  return true;
};

/**
 *
 * @param {file} file 源文件
 * @desc 读取图片文件为base64文件格式
 * @retutn 返回base64文件
 */
// 读取文件
export const readFile = file => {
  return new Promise((resolve, reject) => {
    const reader = new FileReader();
    reader.onload = e => {
      const data = e.target.result;
      resolve(data);
    };
    reader.onerror = () => {
      const err = new Error("读取图片失败");
      reject(err.message);
    };

    reader.readAsDataURL(file);
  });
};

/**
 *
 * @param {string} src  图片地址
 * @desc 加载真实图片
 * @return 读取成功返回图片真实宽高对象 ag: {width:100,height:100}
 */

export const loadImage = src => {
  return new Promise((resolve, reject) => {
    const image = new Image();
    image.src = src;
    image.onload = () => {
      const data = {
        width: image.width,
        height: image.height
      };
      resolve(data);
    };
    image.onerror = () => {
      const err = new Error("加载图片失败");
      reject(err);
    };
  });
};

/**
 *
 * @param {file} file 源文件
 * @param {object} props   文件分辨率的宽和高   ag: props={width:100, height :100}
 * @desc  判断图片文件的分辨率是否在限定范围之内
 * @throw  分辨率不在限定范围之内则抛出异常
 *
 */
export const isAppropriateResolution = async (file, props) => {
  try {
    const { width, height } = props;
    const base64 = await readFile(file);
    const image = await loadImage(base64);
    if (image.width !== width || image.height !== height) {
      throw new Error("上传图片的分辨率必须为" + width + "*" + height);
    }
  } catch (error) {
    throw error;
  }
};

// 上传图片 根据自己项目更换
export const uploadImage = (file) => {
  return new Promise((resolve, reject) => {
   
  });
};

// 上传html  根据自己项目更换
export const uploadHtml = (editorContent) => {
 return new Promise((resolve, reject) => {
   
  });
 
};


2.使用

import AppEditor from "@/components/AppEditor";
import React, { Component } from "react";
export default class HotelSetting extends Component {
  state = {
    html: ''
  };
  
  componentDidMount() { 
    this.setState({html:'https://hotel-1259479103.cos.ap-chengdu.myqcloud.com/admin/html/r2yyo8dyjrla71bn8qm5g-2019-07-11-10-34-19.html'})
  }

  handleSubmit = async () => {
    try {
    const res = await this.editor.handleSubmitContent();
    console.log(res)
    } catch (error) {
    console.log(error)
    }
  };

  render() {
    const { html } = this.state;
    return (
        <AppEditor
            html={html}
            style={{ marginLeft: 100 }}
            ref={e => {
                this.editor = e;
            }}
        />
    );
  }
}


3.使用效果

在这里插入图片描述

在这里插入图片描述

  • 3
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

xiaofei0627

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值