braft-editor的基本使用
项目需求
实现照片上传,富文本为空时的提示,官网详见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: () => { } }) } //富文本的值改变时触发 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 }) } //预览 preView = () => { this.props.form.validateFields((error, values) => { // if (!error) { this.setState({ modalVisible: true, title: values.title, content: values.content.toHTML() }) // } }) } //关闭 handleOk = () => { this.setState({ modalVisible: false }) } //发布 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', '请输入内容') 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: '确认发布吗?', okText: '确定', cancelText: '取消', onOk: () => { dispatch({ type: 'notice/publish', payload: submitData, callback: res => { if (res.success) { this.openNotification('success', '发布成功') router.go(-1) } else { this.openNotification('error', '发布失败') } } }) }, 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 // 文件上传到服务端成功后获取地址 // 上传成功后调用param.success并传入上传后的文件地址 param.success({ url, meta: { id: 'xxx', title: 'xxx', alt: 'xxx', loop: true, // 指定音视频是否循环播放 autoPlay: true, // 指定音视频是否自动播放 controls: true, // 指定音视频是否显示控制栏 poster: 'http://xxx/xx.png', // 指定视频播放器的封面 } }) } const progressFn = (event) => { // 上传进度发生变化时调用param.progress param.progress(event.loaded / event.total * 100) } const errorFn = (response) => { // 上传发生错误时调用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="公告标题" {...formItemLayout}> {getFieldDecorator('title', { rules: [{ required: true, message: '请输入公告标题' }, { message: '公告标题不能输入<或>', pattern: new RegExp('^[^<\|^>]+$', 'g') }, { message: '公告标题不能超过30个字符', max: 30 }] })(<Input placeholder="请输入公告标题" style={{ width: 300 }} />)} </FormItem> <FormItem {...formItemLayout} label="标签"> {getFieldDecorator('labelId')( <Select showSearch style={{ width: 300 }} placeholder="请选择标签" 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="内容"> {getFieldDecorator('content', { rules: [{ required: true, message: '请输入正文内容' }], })(<BraftEditor ref={instance => this.editorInstance = instance} className={styles.myEditor} controls={controls} onChange={this.handleChange} forceNewLine={true} placeholder="请输入正文内容" 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}>预览</Button> <Button type="primary" htmlType="submit">发布</Button> </Col> </Row> </FormItem> </Form> </div> {modalVisible && <Modal title="预览" visible={modalVisible} maskClosable={false} width={1000} footer={[ <Button key="submit" type="primary" loading={loading} onClick={this.handleOk}> 关闭 </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 来实现,发现提供一些配置项,因为我这边不需要那些加粗,下划线等等按钮,只需要上传图片,粘贴然后配合 COS 存链接就好了。
遇到的问题
首先我这个是 React 项目,其它项目不太清楚,然后使用 yarn。
在 utils 官方仓库中,有相关 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 安装那个依赖,我已经试过了,并没有效果,不存在那个依赖包。
解决方式
直接少废话,以下是解决方式:
yarn add braft-finder yarn add braft-utils yarn add draft-js-multidecorators yarn add draftjs-utils
然后在你当前需要引入的文件那,同级目录底下创建一个名为 xxx.d.ts 文件,放入以下定义:
declare module 'braft-utils'; declare module 'braft-finder';
弄完之后记得重新 yarn dev ,之后就会出现如下页面,完美解决。
弄完这个问题,还就那个焦头烂额的,不过总算没有 bug 了,在这里记录一下,以免大家踩坑。
以上为个人经验,希望能给大家一个参考,也希望大家多多支持阿兔在线工具。