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}>&lt; 公告管理</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,链接在下方:

引用 braft-utils 有错误 #500

其中也有人提及了一些解决方案,但是并没有解决问题,一直报错:

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 了,在这里记录一下,以免大家踩坑。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持阿兔在线工具。

点赞(0)

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部