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