react中braft-editor的基本使用方式

目录
  • braft-editor的基本使用
    • 项目需求
  • 使用braft-editor踩坑记,引用 braft-utils有错误
    • 遇到的问题
    • 解决方式

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)

相关推荐

  • react-pdf 打造在线简历生成器的示例代码

    目录 前言 React-PDF简介 程序实现 初始化项目 实现逻辑 遇到问题 重构 部署 参考 前言 PDF 格式是30年前开发的文件格式,并且是使用最广泛的文件格式之一,我们最喜欢使用它作为简历.合同.发票.电子书等文件的格式,最主要的原因是文档格式可以兼容多种设备和应用程序,而且内容 100%保持相同的格式. React-PDF 简介 React PDF 是一个使用 React 创建 PDF 文件的工具,支持在浏览器.移动设备和服务器上创建PDF文件. 可以用它们轻松地将内容呈现到文档中,我

  • Rust+React创建富文本编辑器

    目录 简介 数据模型 核心逻辑 视图 手动差异化 杂项 总结 简介 在Fiberplane,我们最近遇到了一个有趣的挑战:我们正在使用的富文本编辑器库已经过时了.我们曾经使用Slate.js——一个很好的编辑器——但是当我们为协作编辑实现我们自己的富文本基元时,我们发现我们自己的基元和Slate的数据模型之间的脱节是一个阻碍因素.所以我们开始思考——如果我们建立自己的富文本编辑器(RTE, Rich Text Editor)会怎样? 从一个非常高层次的角度来看,一个富文本编辑器是由两个部分组成的

  • React中使用UEditor百度富文本的方法

    前言 本文将介绍笔者在React的项目中使用百度的富文本编辑器Ueditor的过程.注意本文不提供一条龙式的使用方法,只是将使用过程中的一些实现思路进行总结,供以参考.react项目中导入ueditor,会存在各种不正交的问题,需要注意. 引入 首先在ueditor官网下载最新安装包,然后在项目入口的html中导入(导入方式不一,可以采用import的方式,需要自行度娘.但是无论哪种引入方式,只要想自定义功能,不正交问题就难以避免QAQ).不管三七二十一先跑起来再说.. <!DOCTYPE HT

  • 关于react中组件通信的几种方式详解

    前言 刚入门React可能会因为React的单向数据流的特性而遇到组件间沟通的麻烦,下面这篇文章就来给大家详细介绍下,在开始之前先来看一张图: react组件通信 需要组件之进行通信的几种情况 父组件向子组件通信 子组件向父组件通信 跨级组件通信 没有嵌套关系组件之间的通信 1. 父组件向子组件通信 React数据流动是单向的,父组件向子组件通信也是最常见的;父组件通过props向子组件传递需要的信息 Child.jsx import React from 'react'; import Pro

  • React中使用外部样式的3种方式(小结)

    一.关于css-in-js的认识 1.css-in-js是一种使用 js 编写 css 样式的 css 处理方案.它的实现方案有很多,比如styled-components.polished.glamorous(paypal 开源的,不再维护).radium.emotion等等. 2.其中最成熟的便是styled-components和emotion.它们真正意义上实现了组件化style,可以说是专门为 react 打造的. 二.styled-components 简介 styled-compo

  • React中refs的一些常见用法汇总

    目录 什么是Refs 一.String 类型的 Refs 二.回调 Refs 三.React.createRef() 四.useRef 五.Refs 与函数组件 总结 什么是Refs Refs 提供了一种方式,允许我们访问 DOM 节点或在 render 方法中创建的 React 元素. Ref转发是一项将ref自动通过组件传递到子组件的技巧. 通常用来获取DOM节点或者React元素实例的工具.在React中Refs提供了一种方式,允许用户访问dom节点或者在render方法中创建的React

  • react中braft-editor的基本使用方式

    目录 braft-editor的基本使用 项目需求 使用braft-editor踩坑记,引用 braft-utils有错误 遇到的问题 解决方式 braft-editor的基本使用 项目需求 实现照片上传,富文本为空时的提示,官网详见Braft Editor import React, { PureComponent, Fragment } from 'react'; import { connect } from 'dva'; import BraftEditor from 'braft-ed

  • React中常见的动画实现的几种方式

    现在,用户对于前端页面的要求已经不能满足于实现功能,更要有颜值,有趣味.除了整体 UI 的美观,在合适的地方添加合适的动画效果往往比静态页面更具有表现力,达到更自然的效果.比如,一个简单的 loading 动画或者页面切换效果不仅能缓解用户的等待情绪,甚至通过使用品牌 logo 等形式,默默达到品牌宣传的效果. React 作为最近几年比较流行的前端开发框架,提出了虚拟 DOM 概念,所有 DOM 的变化都先发生在虚拟 DOM 上,通过 DOM diff 来分析网页的实际变化,然后反映在真实 D

  • react中使用css的7中方式(最全总结)

    第一种: 在组件中直接使用style 不需要组件从外部引入css文件,直接在组件中书写. import React, { Component } from "react"; const div1 = { width: "300px", margin: "30px auto", backgroundColor: "#44014C", //驼峰法 minHeight: "200px", boxSizing: &

  • 详解三种方式在React中解决绑定this的作用域问题并传参

    在React中时常会遇到this指向的作用域问题 从而导致undefined报错 先来个Demo: 功能很简单 点击按钮改变文字 import React from 'react'; export default class BindWithThis extends React.Component { constructor(props) { super(props); this.state = { msg:"BindWithThis" } } render() { return &l

  • 详解React中共享组件逻辑的三种方式

    废话少说,这三种方式分别是:render props.高阶组件和自定义Hook.下面依次演示 假设有一个TimeOnPage组件专门用来记录用户在当前页面停留时间,像这样: const TimeOnPage = () => { const [second, setSecond] = useState(0); useEffect(() => { setTimeout(() => { setSecond(second + 1); }, 1000); }, [second]); return

  • react中常见hook的使用方式

    1.什么是hook? react hook是react 16.8推出的方法,能够让函数式组件像类式组件一样拥有state.ref.生命周期等属性. 2.为什么要出现hook? 函数式组件是全局当中一个普通函数,在非严格模式下this指向window,但是react内部开启了严格模式,此时this指向undefined,无法像类式组件一样使用state.ref,函数式组件定义的变量都是局部的,当组件进行更新时会重新定义,也无法存储,所以在hook出现之前,函数式组件有很大的局限性,通常情况下都会使

  • react 项目中引入图片的几种方式

    img标签引入图片 因为react其实是通过js的reader函数渲染的页面,所以直接写src="路径"是无法引入图片 我们可以像引入模块一样引入图片 import img from './../../../../asset/img/user.png' 需要用下面的方式引入 <img src={require('../images/picture.png')} alt="标签"/> 背景图片引入 1 第一种就是常规的 新建一个css文件,然后就可以直接写

随机推荐