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.使用效果

在這裏插入圖片描述

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章