React中使用富文本编辑器react-draft-wysiwyg
在React中用react-draft-wysiwyg:// react-draft-wysiwyg begin
import { EditorState, convertToRaw, ContentState } from 'draft-js'; import { Editor } from 'react-draft-wysiwyg'; import draftToHtml from 'draftjs-to-html'; import htmlToDraft from 'html-to-draftjs'; import 'react-draft-wysiwyg/dist/react-draft-wysiwyg.css' // react-draft-wysiwyg end
state = { showRichText: false, // react-draft-wysiwyg editorContent: '', // react-draft-wysiwyg editorState: '', // react-draft-wysiwyg };
点击并拖拽以移动 /** * * @react-draft-wysiwyg begin */ handleClearContent = () => { this.setState({ editorState: '' }) } handleGetText = () => { this.setState({ showRichText: true }) } onEditorChange = (editorContent) => { this.setState({ editorContent, }); }; onEditorStateChange = (editorState) => { this.setState({ editorState }); }; // @react-draft-wysiwyg end
提交时将富文本转换为含html的文本:
addModalHandleOk = e => { const _this = this; this.addModalFormRef.current.validateFields() .then(values => { console.log(this.state.editorContent); const params = { title: values.title, content: draftToHtml(convertToRaw(this.state.editorState.getCurrentContent())) }; axios.post(`http://localhost:5555/api/add_blog`, qs.stringify(params)).then((resp) => { if (resp.ret) { console.log(resp.msg); } else { this.addModalFormRef.current.resetFields(); this.setState({ addModalVisible: false }); this.getData(1, 5); } }, (err) => { console.log(err); }); }) .catch(info => { console.log('Validate Failed:', info); }); }
获取时的文本含有html标签,所以在Modal中用“<div dangerouslySetInnerHTML={{__html: this.state.blogDetail}} style={{height: 300, overflow: 'auto'}}/>”转换成html标签。
/** * 显示blog Modal */ handleShowDetailBlog = (id) => { axios.get(`http://localhost:5555/api/get_blog_detail?id=${id}`, {}).then((resp) => { if (resp.data.ret) { this.setState({ showBlogModalVisible: true, blogDetail: resp.data.content }) } else { } }, (err) => { console.log(err); }); }
<Modal title="创建" visible={this.state.addModalVisible} width={660} onOk={this.addModalHandleOk} onCancel={this.addModalHandCancel} okText="确认" cancelText="取消" maskClosable={false} destroyOnClose={true} > <Form {...layout} ref={this.addModalFormRef} name="control-ref" preserve={false}> <Form.Item label="标题" style={{ marginBottom: 0 }}> <Form.Item name="title" style={{ display: 'inline-block', width: 'calc(100% - 8px)', marginRight: 15 }} rules={[ { required: true, message: "标题不能为空" } ]} > <Input /> </Form.Item> </Form.Item> <Form.Item label="内容" style={{ marginBottom: 0 }}> <Form.Item name="content" style={{ display: 'inline-block', width: 'calc(100% - 8px)' }} rules={[ { required: true, message: "内容不能为空" } ]} > <Editor editorState={this.state.editorState} wrapperClassName="demo-wrapper" editorClassName="demo-editor" onEditorStateChange={this.onEditorStateChange} /> {/* <TextArea rows={4} /> */} </Form.Item> </Form.Item> </Form> </Modal>
<Modal title="详情" visible={this.state.showBlogModalVisible} width={660} onOk={this.handleShowBlogModalHandleOk} onCancel={this.handleShowBlogModalHandleCancel} okText="确定" cancelText="取消" maskClosable={false} destroyOnClose={true} footer={null} > <div dangerouslySetInnerHTML={{__html: this.state.blogDetail}} style={{height: 300, overflow: 'auto'}}/> </Modal>
完整代码:
import React, { Component } from 'react' import { Table, Pagination, Card, Modal, Button, Form, Input, InputNumber, Select, Checkbox, Radio, notification } from 'antd' import ReactEcharts from 'echarts-for-react' import axios from 'axios' import qs from 'qs' import { PlusOutlined, MinusOutlined } from '@ant-design/icons'; // react-draft-wysiwyg begin import { EditorState, convertToRaw, ContentState } from 'draft-js'; import { Editor } from 'react-draft-wysiwyg'; import draftToHtml from 'draftjs-to-html'; import htmlToDraft from 'html-to-draftjs'; import 'react-draft-wysiwyg/dist/react-draft-wysiwyg.css' // react-draft-wysiwyg end const { Option } = Select; const { TextArea } = Input; const layout = { labelCol: { span: 3, }, wrapperCol: { span: 19, } }; export default class Home extends Component { state = { showRichText: false, // react-draft-wysiwyg editorContent: '', // react-draft-wysiwyg editorState: '', // react-draft-wysiwyg chartData: [ ['2019-10-10', 200], ['2019-10-11', 400], ['2019-10-12', 650], ['2019-10-13', 500], ['2019-10-14', 250], ['2019-10-15', 300], ['2019-10-16', 450], ['2019-10-17', 300], ['2019-10-18', 200] ], selectedRowKeys: [], // 表格选择项Keys selectedRows: [], // 表格选择项Rows tableData: [], total: 0, // for Pagination columns: [ { title: 'ID', dataIndex: 'id', width: 30, }, { title: '标题', dataIndex: 'title', width: 500, render: (text, record) => <a href="javascript: void(0)" target="_self" onClick={() => this.handleShowDetailBlog(record.id)}>{text}</a> }, { title: '内容', dataIndex: 'content' }, ], addModalVisible: false, showBlogModalVisible: false, blogDetail: '' }; /** * * @react-draft-wysiwyg begin */ handleClearContent = () => { this.setState({ editorState: '' }) } handleGetText = () => { this.setState({ showRichText: true }) } onEditorChange = (editorContent) => { this.setState({ editorContent, }); }; onEditorStateChange = (editorState) => { this.setState({ editorState }); }; // @react-draft-wysiwyg end getSalesVolumeChartData = (chartData) => { return { grid: { left: '3%', right: '3%', bottom: '3%', top: '3%', containLabel: true }, xAxis: { type: 'category', boundaryGap: false }, yAxis: { type: 'value', boundaryGap: [0, '30%'] }, visualMap: { type: 'piecewise', show: false, dimension: 0, seriesIndex: 0, pieces: [{ gt: 1, lt: 3, color: 'rgba(0, 180, 0, 0.5)' }, { gt: 5, lt: 7, color: 'rgba(0, 180, 0, 0.5)' }] }, series: [ { type: 'line', smooth: 0.6, symbol: 'none', lineStyle: { color: 'green', width: 5 }, markLine: { symbol: ['none', 'none'], label: { show: false }, data: [ { xAxis: 1 }, { xAxis: 3 }, { xAxis: 5 }, { xAxis: 7 } ] }, areaStyle: {}, data: chartData } ] }; } onTableSelectChange = (selectedRowKeys, selectedRows) => { console.log('selectedRowKeys changed: ', selectedRowKeys); console.log('selectedRows changed: ', selectedRows); this.setState({ selectedRowKeys, selectedRows }); }; onSelectChange = selectedRowKeys => { console.log('selectedRowKeys changed: ', selectedRowKeys); this.setState({ selectedRowKeys }); }; /** * 表格 */ // 获取表格数据 getData(pageNumber, pageSize) { axios.get(`http://localhost:5555/api/blog_list/?pageSize=${pageSize}&pageNumber=${pageNumber}&sortName=id&sortOrder=desc&_=1595230808893`).then((resp) => { let ajaxData = []; for (let i = 0; i < resp.data.rows.length; i++) { ajaxData.push({ key: resp.data.rows[i].id, id: resp.data.rows[i].id, title: resp.data.rows[i].title, content: resp.data.rows[i].content, }); } this.setState({ tableData: ajaxData, total: resp.data.total }) }, (err) => { console.log(err); }); } onChange = (pageNumber, pageSize) => { this.pageNum = pageNumber; this.getData(pageNumber, pageSize); }; /** * 添加modal */ // for modal showAddModal = () => { this.setState({ addModalVisible: true }) } addModalHandleOk = e => { const _this = this; this.addModalFormRef.current.validateFields() .then(values => { console.log(this.state.editorContent); const params = { title: values.title, content: draftToHtml(convertToRaw(this.state.editorState.getCurrentContent())) }; axios.post(`http://localhost:5555/api/add_blog`, qs.stringify(params)).then((resp) => { if (resp.ret) { console.log(resp.msg); } else { this.addModalFormRef.current.resetFields(); this.setState({ addModalVisible: false }); this.getData(1, 5); } }, (err) => { console.log(err); }); }) .catch(info => { console.log('Validate Failed:', info); }); } addModalHandCancel = e => { this.addModalFormRef.current.resetFields(); this.setState({ addModalVisible: false }) } /** * 删除blog */ onDeleteAdministrators = () => { let len = this.state.selectedRowKeys.length; if (len === 0) { notification['error']({ message: '错误提示', description: '请选择要删除的文章!', }) } else { const params = { idArr: JSON.stringify(this.state.selectedRowKeys) } const _this = this; axios.post(`http://localhost:5555/api/delete_blogs`, qs.stringify(params)).then((resp) => { if (resp.data.ret) { notification['success']({ message: '成功提示', description: resp.data.msg, }) _this.getData(0, 5); } else { notification['error']({ message: '错误提示', description: resp.data.msg, }) } }, (err) => { notification['error']({ message: '错误提示', description: '网络错误,删除失败!' }) }); } } // 表单相关 addModalFormRef = React.createRef(); // 定义一个表单 /** * 显示blog Modal */ handleShowDetailBlog = (id) => { axios.get(`http://localhost:5555/api/get_blog_detail?id=${id}`, {}).then((resp) => { const contentBlock = htmlToDraft(resp.data.content); const contentState = ContentState.createFromBlockArray(contentBlock.contentBlocks); const editorState = EditorState.createWithContent(contentState); console.log("contentState:"); console.log(contentState); if (resp.data.ret) { this.setState({ showBlogModalVisible: true, blogDetail: resp.data.content }) } else { } }, (err) => { console.log(err); }); } handleShowBlogModalHandleOk = () => { this.setState({ showBlogModalVisible: false }) } handleShowBlogModalHandleCancel = () => { this.setState({ showBlogModalVisible: false }) } /** * 钩子函数 */ componentDidMount() { this.getData(1, 5); setInterval(() => { let chartData1 = Math.ceil(Math.random() * 100); let chartData2 = Math.ceil(Math.random() * 100); let chartData3 = Math.ceil(Math.random() * 100); let chartData4 = Math.ceil(Math.random() * 100); let chartData5 = Math.ceil(Math.random() * 100); let chartData6 = Math.ceil(Math.random() * 100); let chartData7 = Math.ceil(Math.random() * 100); let chartData8 = Math.ceil(Math.random() * 100); let chartData9 = Math.ceil(Math.random() * 100); let chartData = [ ['2019-10-10', chartData1], ['2019-10-11', chartData2], ['2019-10-12', chartData3], ['2019-10-13', chartData4], ['2019-10-14', chartData5], ['2019-10-15', chartData6], ['2019-10-16', chartData7], ['2019-10-17', chartData8], ['2019-10-18', chartData9] ]; this.setState({ chartData }); }, 1000) } render() { // 控制表格选择 const rowSelection = { selectedRowKeys: this.state.selectedRowKeys, onChange: this.onTableSelectChange }; return (<> <Card title="Smoothed Line Chart" > <ReactEcharts option={this.getSalesVolumeChartData(this.state.chartData)}></ReactEcharts> </Card> <div style={{ height: 15 }}></div> <Card title="博客列表" extra={<span><Button type="primary" ghost size="small" icon={<PlusOutlined />} style={{ marginRight: 15 }} onClick={this.showAddModal}>添加</Button><Button type="primary" ghost size="small" icon={<MinusOutlined />} onClick={() => {this.onDeleteAdministrators()} }>删除</Button></span>} style={{ width: '100%' }}> <Table onRow={record => { return { onClick: event => { console.log(record) }, // 点击行 onDoubleClick: event => { }, onContextMenu: event => { }, onMouseEnter: event => { }, // 鼠标移入行 onMouseLeave: event => { }, }; }} rowSelection={rowSelection} columns={this.state.columns} dataSource={this.state.tableData} pagination={{ current: this.pageNum, total: this.state.total, pageSizeOptions: [5, 10, 20, 50, 100], defaultPageSize: 5, showSizeChanger: true, showQuickJumper: true, showTotal: (total, range) => `共 ${total} 条`, onChange: this.onChange }} bordered > </Table> <Modal title="创建" visible={this.state.addModalVisible} width={660} onOk={this.addModalHandleOk} onCancel={this.addModalHandCancel} okText="确认" cancelText="取消" maskClosable={false} destroyOnClose={true} > <Form {...layout} ref={this.addModalFormRef} name="control-ref" preserve={false}> <Form.Item label="标题" style={{ marginBottom: 0 }}> <Form.Item name="title" style={{ display: 'inline-block', width: 'calc(100% - 8px)', marginRight: 15 }} rules={[ { required: true, message: "标题不能为空" } ]} > <Input /> </Form.Item> </Form.Item> <Form.Item label="内容" style={{ marginBottom: 0 }}> <Form.Item name="content" style={{ display: 'inline-block', width: 'calc(100% - 8px)' }} rules={[ { required: true, message: "内容不能为空" } ]} > <Editor editorState={this.state.editorState} wrapperClassName="demo-wrapper" editorClassName="demo-editor" onEditorStateChange={this.onEditorStateChange} /> {/* <TextArea rows={4} /> */} </Form.Item> </Form.Item> </Form> </Modal> <Modal title="详情" visible={this.state.showBlogModalVisible} width={660} onOk={this.handleShowBlogModalHandleOk} onCancel={this.handleShowBlogModalHandleCancel} okText="确定" cancelText="取消" maskClosable={false} destroyOnClose={true} footer={null} > <div dangerouslySetInnerHTML={{__html: this.state.blogDetail}} style={{height: 300, overflow: 'auto'}}/> </Modal> </Card> </> ) } } 点击并拖拽以移动