react中braft-editor的基本使用方式
braft-editor的基本使用
項目需求
實現(xiàn)照片上傳,富文本為空時的提示,官網(wǎng)詳見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: '返回將不保存已編輯信息,確定離開嗎?', okText: '確定', cancelText: '取消', onOk: () => { router.go(-1) }, onCancel: () => { } }) } //富文本的值改變時觸發(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', '請輸入內(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, // 指定音視頻是否自動播放 controls: true, // 指定音視頻是否顯示控制欄 poster: 'http://xxx/xx.png', // 指定視頻播放器的封面 } }) } const progressFn = (event) => { // 上傳進度發(fā)生變化時調(diào)用param.progress param.progress(event.loaded / event.total * 100) } const errorFn = (response) => { // 上傳發(fā)生錯誤時調(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: '請輸入公告標(biāo)題' }, { message: '公告標(biāo)題不能輸入<或>', pattern: new RegExp('^[^<\|^>]+$', 'g') }, { message: '公告標(biāo)題不能超過30個字符', max: 30 }] })(<Input placeholder="請輸入公告標(biāo)題" style={{ width: 300 }} />)} </FormItem> <FormItem {...formItemLayout} label="標(biāo)簽"> {getFieldDecorator('labelId')( <Select showSearch style={{ width: 300 }} placeholder="請選擇標(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: '請輸入正文內(nèi)容' }], })(<BraftEditor ref={instance => this.editorInstance = instance} className={styles.myEditor} controls={controls} onChange={this.handleChange} forceNewLine={true} placeholder="請輸入正文內(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有錯誤
最近接到一個需求,需要支持在文本輸入框支持圖片粘貼上傳,但是在我們這邊管理頁面,對于用戶提的一些問題顯示又不支持 Matkdown。
所以選擇 braft-editor 來實現(xiàn),發(fā)現(xiàn)提供一些配置項,因為我這邊不需要那些加粗,下劃線等等按鈕,只需要上傳圖片,粘貼然后配合 COS 存鏈接就好了。
遇到的問題
首先我這個是 React 項目,其它項目不太清楚,然后使用 yarn。
在 utils 官方倉庫中,有相關(guān) issues,鏈接在下方:
其中也有人提及了一些解決方案,但是并沒有解決問題,一直報錯:
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';
看這個報錯信息,有提示,用 npm 安裝那個依賴,我已經(jīng)試過了,并沒有效果,不存在那個依賴包。
解決方式
直接少廢話,以下是解決方式:
yarn add braft-finder yarn add braft-utils yarn add draft-js-multidecorators yarn add draftjs-utils
然后在你當(dāng)前需要引入的文件那,同級目錄底下創(chuàng)建一個名為 xxx.d.ts 文件,放入以下定義:
declare module 'braft-utils'; declare module 'braft-finder';
弄完之后記得重新 yarn dev ,之后就會出現(xiàn)如下頁面,完美解決。
弄完這個問題,還就那個焦頭爛額的,不過總算沒有 bug 了,在這里記錄一下,以免大家踩坑。
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
React學(xué)習(xí)之受控組件與數(shù)據(jù)共享實例分析
這篇文章主要介紹了React學(xué)習(xí)之受控組件與數(shù)據(jù)共享,結(jié)合實例形式分析了React受控組件與組件間數(shù)據(jù)共享相關(guān)原理與使用技巧,需要的朋友可以參考下2020-01-01React antd tabs切換造成子組件重復(fù)刷新
這篇文章主要介紹了React antd tabs切換造成子組件重復(fù)刷新,需要的朋友可以參考下2021-04-04react?app?rewrited替代品craco使用示例
這篇文章主要為大家介紹了react?app?rewrited替代品craco使用示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-11-11一文詳解ReactNative狀態(tài)管理redux-toolkit使用
這篇文章主要為大家介紹了ReactNative狀態(tài)管理redux-toolkit使用詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-03-03