react中braft-editor的基本使用方式
braft-editor的基本使用
項(xiàng)目需求
實(shí)現(xiàn)照片上傳,富文本為空時(shí)的提示,官網(wǎng)詳見(jiàn)Braft Editor
import React, { PureComponent, Fragment } from 'react';
import { connect } from 'dva';
import BraftEditor from 'braft-editor'
import 'braft-editor/dist/index.css'
import moment from 'moment';
import Link from 'umi/link';
import {
Row,
Col,
Card,
Button,
message,
Divider,
Table,
Modal,
Form,
Select,
Input,
notification
} from 'antd';
import styles from './createNotice.less';
import { router } from 'umi';
const FormItem = Form.Item;
const { Option } = Select;
/* eslint react/no-multi-comp:0 */
@connect(({ notice, loading }) => ({
notice,
loading: loading.models.notice,
}))
@Form.create()
class CreateNotice extends PureComponent {
constructor(props) {
super(props)
this.state = {
modalVisible: false,
title: '',
labelId: '',
labelName: '',
content: '',
editorValue: '',
editorState: BraftEditor.createEditorState(null)
}
}
componentDidMount() {
this.requLabel()
}
requLabel() {
const { dispatch } = this.props
dispatch({
type: 'notice/fetchLabel'
})
}
//消息提醒
openNotification = (type, msg) => {
notification[type]({
message: msg,
});
};
//返回
goBack = () => {
Modal.confirm({
title: '提示',
content: '返回將不保存已編輯信息,確定離開(kāi)嗎?',
okText: '確定',
cancelText: '取消',
onOk: () => {
router.go(-1)
},
onCancel: () => { }
})
}
//富文本的值改變時(shí)觸發(fā)
handleChange = (editorState) => {
const { form } = this.props
this.setState({ editorState })
form.setFieldsValue({
content: editorState
})
}
//label的select切換
onChange = (values) => {
this.setState({
labelId: values.key,
labelName: values.label
})
}
//預(yù)覽
preView = () => {
this.props.form.validateFields((error, values) => {
// if (!error) {
this.setState({
modalVisible: true,
title: values.title,
content: values.content.toHTML()
})
// }
})
}
//關(guān)閉
handleOk = () => {
this.setState({
modalVisible: false
})
}
//發(fā)布
handleSubmit = (event) => {
const { dispatch, form } = this.props;
const { labelId, editorState } = this.state
event.preventDefault()
form.validateFields((error, values) => {
if (error) {
return
}
let edit = this.state.editorState.isEmpty() //用isEmpty判斷是否為空
if (edit) {
this.openNotification('warn', '請(qǐng)輸入內(nèi)容')
return
}
if (!error) {
const submitData = {
title: values.title,
infoLabelId: labelId,
content: window.btoa(window.encodeURIComponent(values.content.toHTML())) // or values.content.toRAW()
}
Modal.confirm({
title: '提示',
content: '確認(rèn)發(fā)布嗎?',
okText: '確定',
cancelText: '取消',
onOk: () => {
dispatch({
type: 'notice/publish',
payload: submitData,
callback: res => {
if (res.success) {
this.openNotification('success', '發(fā)布成功')
router.go(-1)
} else {
this.openNotification('error', '發(fā)布失敗')
}
}
})
},
onCancel: () => { }
})
}
})
}
//上傳媒體
uploadPic = (param) => {
//也可以用fetch或者axios,用formData
const token = localStorage.getItem('meiyun-operation-token')
const serverURL = '/meiyun-resource/oss/endpoint/put-file'
const xhr = new XMLHttpRequest
const fd = new FormData()
const successFn = (response) => {
let url = JSON.parse(xhr.responseText).data.link
// 文件上傳到服務(wù)端成功后獲取地址
// 上傳成功后調(diào)用param.success并傳入上傳后的文件地址
param.success({
url,
meta: {
id: 'xxx',
title: 'xxx',
alt: 'xxx',
loop: true, // 指定音視頻是否循環(huán)播放
autoPlay: true, // 指定音視頻是否自動(dòng)播放
controls: true, // 指定音視頻是否顯示控制欄
poster: 'http://xxx/xx.png', // 指定視頻播放器的封面
}
})
}
const progressFn = (event) => {
// 上傳進(jìn)度發(fā)生變化時(shí)調(diào)用param.progress
param.progress(event.loaded / event.total * 100)
}
const errorFn = (response) => {
// 上傳發(fā)生錯(cuò)誤時(shí)調(diào)用param.error
param.error({
msg: '上傳失敗'
})
}
xhr.upload.addEventListener("progress", progressFn, false)
xhr.addEventListener("load", successFn, false)
xhr.addEventListener("error", errorFn, false)
xhr.addEventListener("abort", errorFn, false)
fd.append('file', param.file)
xhr.open('POST', serverURL, true)
xhr.setRequestHeader('Blade-Auth', 'Bearer ' + token);
xhr.send(fd)
}
render() {
const {
form: { getFieldDecorator },
notice: { labelData },
loading,
} = this.props;
const { modalVisible, title, labelName, content } = this.state
const formItemLayout = {
labelCol: { span: 4 },
wrapperCol: { span: 18 }
}
const controls = [
'font-size',
'font-family',
'list-ol',
'list-ul',
'hr',
'text-align', 'bold', 'italic', 'underline', 'text-color', 'separator', 'superscript',
'subscript', 'separator', 'media', 'letter-spacing',
'line-height',
'clear',]
return (
<div className={styles.container}>
<div className={styles.title}>
<span onClick={this.goBack}>< 公告管理</span>
</div>
<div className={styles.formBox}>
<Form onSubmit={this.handleSubmit} layout="horizontal" {...formItemLayout}>
<FormItem label="公告標(biāo)題" {...formItemLayout}>
{getFieldDecorator('title', {
rules: [{ required: true, message: '請(qǐng)輸入公告標(biāo)題' }, { message: '公告標(biāo)題不能輸入<或>', pattern: new RegExp('^[^<\|^>]+$', 'g') }, { message: '公告標(biāo)題不能超過(guò)30個(gè)字符', max: 30 }]
})(<Input placeholder="請(qǐng)輸入公告標(biāo)題" style={{ width: 300 }} />)}
</FormItem>
<FormItem {...formItemLayout} label="標(biāo)簽">
{getFieldDecorator('labelId')(
<Select
showSearch
style={{ width: 300 }}
placeholder="請(qǐng)選擇標(biāo)簽"
labelInValue={true}
optionFilterProp="children"
onChange={this.onChange}
onFocus={this.onFocus}
onBlur={this.onBlur}
filterOption={(input, option) =>
option.children.toLowerCase().indexOf(input.toLowerCase()) >= 0
}
>
{labelData.length && labelData.map(item => {
return (
<Option value={item.id} key={item.id}>{item.labelTitle}</Option>
)
})}
</Select>
)}
</FormItem>
{/* </Col>
</Row> */}
<FormItem {...formItemLayout} label="內(nèi)容">
{getFieldDecorator('content', {
rules: [{
required: true,
message: '請(qǐng)輸入正文內(nèi)容'
}],
})(<BraftEditor
ref={instance => this.editorInstance = instance}
className={styles.myEditor}
controls={controls}
onChange={this.handleChange}
forceNewLine={true}
placeholder="請(qǐng)輸入正文內(nèi)容"
media={{ uploadFn: this.uploadPic }}
/>)}
</FormItem>
<FormItem>
<Row gutter={{ md: 24, lg: 48, xl: 48 }}>
<Col md={18} sm={24}></Col>
<Col md={6} sm={24}>
<Button style={{ marginRight: 20 }} onClick={this.preView}>預(yù)覽</Button>
<Button type="primary" htmlType="submit">發(fā)布</Button>
</Col>
</Row>
</FormItem>
</Form>
</div>
{modalVisible && <Modal
title="預(yù)覽"
visible={modalVisible}
maskClosable={false}
width={1000}
footer={[
<Button key="submit" type="primary" loading={loading} onClick={this.handleOk}>
關(guān)閉
</Button>
]}
onOk={this.handleOk}
onCancel={this.handleOk}>
<div>
<h2 style={{ textAlign: 'center' }}>{title}</h2>
<p>{labelName}</p>
<div dangerouslySetInnerHTML={{ __html: content }}></div>
</div>
</Modal>}
</div>
)
}
}
export default CreateNotice
使用braft-editor踩坑記,引用 braft-utils有錯(cuò)誤
最近接到一個(gè)需求,需要支持在文本輸入框支持圖片粘貼上傳,但是在我們這邊管理頁(yè)面,對(duì)于用戶提的一些問(wèn)題顯示又不支持 Matkdown。
所以選擇 braft-editor 來(lái)實(shí)現(xiàn),發(fā)現(xiàn)提供一些配置項(xiàng),因?yàn)槲疫@邊不需要那些加粗,下劃線等等按鈕,只需要上傳圖片,粘貼然后配合 COS 存鏈接就好了。
遇到的問(wèn)題
首先我這個(gè)是 React 項(xiàng)目,其它項(xiàng)目不太清楚,然后使用 yarn。
在 utils 官方倉(cāng)庫(kù)中,有相關(guān) issues,鏈接在下方:
其中也有人提及了一些解決方案,但是并沒(méi)有解決問(wèn)題,一直報(bào)錯(cuò):
TS7016: Could not find a declaration file for module ‘braft-utils’. ‘xxx/node_modules/braft-utils/dist/index.js’ implicitly has an ‘any’ type.
Try npm i --save-dev @types/braft-utils if it exists or add a new declaration (.d.ts) file containing declare module 'braft-utils';
看這個(gè)報(bào)錯(cuò)信息,有提示,用 npm 安裝那個(gè)依賴,我已經(jīng)試過(guò)了,并沒(méi)有效果,不存在那個(gè)依賴包。
解決方式
直接少?gòu)U話,以下是解決方式:
yarn add braft-finder yarn add braft-utils yarn add draft-js-multidecorators yarn add draftjs-utils
然后在你當(dāng)前需要引入的文件那,同級(jí)目錄底下創(chuàng)建一個(gè)名為 xxx.d.ts 文件,放入以下定義:
declare module 'braft-utils'; declare module 'braft-finder';
弄完之后記得重新 yarn dev ,之后就會(huì)出現(xiàn)如下頁(yè)面,完美解決。

弄完這個(gè)問(wèn)題,還就那個(gè)焦頭爛額的,不過(guò)總算沒(méi)有 bug 了,在這里記錄一下,以免大家踩坑。
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
手把手教你從零開(kāi)始react+antd搭建項(xiàng)目
本文將從最基礎(chǔ)的項(xiàng)目搭建開(kāi)始講起,做一個(gè)基于react和antd的后臺(tái)管理系統(tǒng),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-06-06
React學(xué)習(xí)之受控組件與數(shù)據(jù)共享實(shí)例分析
這篇文章主要介紹了React學(xué)習(xí)之受控組件與數(shù)據(jù)共享,結(jié)合實(shí)例形式分析了React受控組件與組件間數(shù)據(jù)共享相關(guān)原理與使用技巧,需要的朋友可以參考下2020-01-01
react native 文字輪播的實(shí)現(xiàn)示例
這篇文章主要介紹了react native 文字輪播的實(shí)現(xiàn)示例,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-07-07
React antd tabs切換造成子組件重復(fù)刷新
這篇文章主要介紹了React antd tabs切換造成子組件重復(fù)刷新,需要的朋友可以參考下2021-04-04
react?app?rewrited替代品craco使用示例
這篇文章主要為大家介紹了react?app?rewrited替代品craco使用示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-11-11
Ant?Design?組件庫(kù)按鈕實(shí)現(xiàn)示例詳解
這篇文章主要介紹了Ant?Design?組件庫(kù)按鈕實(shí)現(xiàn)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪</P><P><BR>2022-08-08
一文詳解ReactNative狀態(tài)管理redux-toolkit使用
這篇文章主要為大家介紹了ReactNative狀態(tài)管理redux-toolkit使用詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-03-03

