首頁 > 軟體

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

2022-09-14 22:02:52

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 了,在這裡記錄一下,以免大家踩坑。

以上為個人經驗,希望能給大家一個參考,也希望大家多多支援it145.com。


IT145.com E-mail:sddin#qq.com