0%

react 富文本控件使用

项目需要用到富文本编辑器,了解几个后选择了react-draft-wysiwyg

富文本编辑的基本使用

默认模式使用比较简单

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import { Editor } from 'react-draft-wysiwyg';
import 'react-draft-wysiwyg/dist/react-draft-wysiwyg.css';
import draftToHtml from 'draftjs-to-html';
import { EditorState, convertToRaw, ContentState } from 'draft-js';
.
.
.
//设置初始状态,这个是默认值是空的状态,因为我的需求中需要对已有的文本进行编辑,故而还是要给状态值后面好根据fetch结果改变。
state={
pointTitleEditorState: EditorState.createEmpty(),
}

//状态值改变后的接收方法,设置到state变量里。
onPonitTitleEditorStateChange = pointTitleEditorState => {
this.setState({
pointTitleEditorState,
});
};

<Editor
localization={{ locale: 'zh' }} //中文工具栏
editorState={this.state.pointTitleEditorState}
editorClassName={styles.editor_class}
placeholder="请输入测试点标题"
onEditorStateChange={this.onPonitTitleEditorStateChange}
/>
.
.
.

自定义工具栏

需求总是想法比较多的,以上这种基本使用基本是不可能满足需求的。我们还需自定义工具栏。
这里要用到react-draft-wysiwyg 的toolbar自定义属性。
先搞个我们需要的工具栏组件,这里用到了react-color,可以搞的东西还是很多的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/* eslint-disable react/sort-comp,react/require-default-props */
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { TwitterPicker } from 'react-color';

export default class ColorPic extends Component {
static propTypes = {
onChange: PropTypes.func,
currentState: PropTypes.object,
};

stopPropagation = event => {
event.stopPropagation();
};

onChange = color => {
const { onChange } = this.props; //调用富文本编辑器的方法
onChange('color', color.hex);
};

render() {
const { color } = this.props.currentState;
return (
<div onClick={this.stopPropagation}>
<TwitterPicker
color={color}
onChangeComplete={this.onChange}
triangle="hide"
colors={['#000000', '#FF0000', '#0000FF']}
width={100}
/>
</div>
);
}
}

这个库有个坑,它的样式没给暴露出属性自定义,翻了好久issues未果,我是自己改了它的\node_modules.2.14.1@react-color\lib\components\twitter\Twitter.js源码实现的需求。如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
var styles = (0, _reactcss2.default)({
'default': {
card: {
width: width,
background: '#fff',
border: '0 solid rgba(0,0,0,0.25)',
// boxShadow: '0 1px 4px rgba(0,0,0,0.25)',//去掉外围阴影
borderRadius: '4px',
position: 'relative'
},
body: {
// padding: '15px 9px 9px 15px',//去掉外围边距
},
.
.
.
hash: {
background: '#F0F0F0',
height: '30px',
width: '30px',
borderRadius: '4px 0 0 4px',
float: 'left',
color: '#98A1A4',
display: 'flex',
alignItems: 'center',
display:'none', //干掉输入框
justifyContent: 'center'
},
input: {
width: '100px',
fontSize: '14px',
color: '#666',
border: '0px',
display:'none', //干掉输入框
outline: 'none',
height: '28px',
boxShadow: 'inset 0 0 0 1px #F0F0F0',
boxSizing: 'content-box',
borderRadius: '0 4px 4px 0',
float: 'left',
paddingLeft: '8px'
},
swatch: {
width: '20px',//设置图片大小
height: '20px',//设置图片大小
float: 'left',
borderRadius: '4px',
margin: '0 6px 6px 0'
},
},

Ok,要的东西准备好了,接下来设置下富文本控件的toolbar属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import ColorPic from '../EditRichCustomToolbar';
.
.
.
<Editor
editorState={editorState}
localization={{ locale: 'zh' }}
wrapperClassName={styles.wrapper_class}
editorClassName={styles.editor_class}
onEditorStateChange={this.onEditorStateChange}
onBlur={this.check}
toolbarClassName="toolbar-class"
toolbar={{
options: ['inline', 'colorPicker'],
inline: {
options: ['bold'],
},
colorPicker: { component: ColorPic },
}}
/>

自动获取焦点

我们的编辑需要能够自动获取焦点,这个富文本编辑封装的东西太多,没法一个autofocus解决。需要通过ref来做,步骤如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// 构造
constructor(props) {
super(props);
// 初始状态
this.state = {
.
.
.
};
this.setDomEditorRef = ref => (this.domEditor = ref);
}

//这是我的一个触发方法,随你放哪。
edit = () => {
this.setState({ editable: true }, () => {
this.domEditor.focusEditor();
});
};
.
.
.
<Editor
ref={this.setDomEditorRef}
editorState={editorState}
localization={{ locale: 'zh' }}
wrapperClassName={styles.wrapper_class}
editorClassName={styles.editor_class}
onEditorStateChange={this.onEditorStateChange}
onBlur={this.check}
toolbarClassName="toolbar-class"
toolbar={{
options: ['inline', 'colorPicker'],
inline: {
options: ['bold'],
},
colorPicker: { component: ColorPic },
}}
/>

全局样式调整

这个富文本的默认行距很大,老板不愿意,可以通过设置全局的样式来控制。
新建一个CSS文件

1
2
3
.public-DraftStyleDefault-block > div {
margin: 1px;
}

导入并按如下配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import cssStyles from './index.css';
.
.
.
customBlockStyleFn = contentBlock => {
return cssStyles['public-DraftStyleDefault-block'];
};

<Editor
ref={this.setDomEditorRef}
editorState={editorState}
localization={{ locale: 'zh' }}
wrapperClassName={styles.wrapper_class}
editorClassName={styles.editor_class}
blockStyleFn={this.customBlockStyleFn}
onEditorStateChange={this.onEditorStateChange}
onBlur={this.check}
toolbarClassName="toolbar-class"
toolbar={{
options: ['inline', 'colorPicker'],
inline: {
options: ['bold'],
},
colorPicker: { component: ColorPic },
}}
/>

富文本的展示样式调整

我们编辑后的富文本,最终要通过div展示出来,我用的是antd table去展示的,列表上的可以通过global样式去设置行间距,直接改lineHeight会有重叠问题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
:global {
.ant-table-row p {
margin: 0;
}
.ant-table-row p:last-child {
margin-bottom: 0;
}
.ant-table-thead > tr > th,
.ant-table-tbody > tr > td {
padding: 5px 16px 5px 16px;
}
.ant-table-thead > tr:first-child > th:first-child {
text-align: left;
}
.ant-table .ant-table-tbody > tr > td {
text-align: left;
}
}

对于富文本的展示,需要用到draftToHtml库,接收的参数是个json,如下:

1
2
3
4
5
import draftToHtml from 'draftjs-to-html';
.
.
.
<div dangerouslySetInnerHTML={{ __html: draftToHtml(this.props.editorState) }} />

这里我展示时遇到个问题,有时页面刚加载,传给富文本控件的JSON是空的,导致会报错。于是在渲染时做了个catch:

1
2
3
4
5
6
7
8
9
10
11
12
13
componentWillMount() {
try {
this.setState({
editorState: EditorState.createWithContent(
ContentState.createFromBlockArray(convertFromHTML(draftToHtml(this.props.editorState)))
),
});
} catch (e) {
this.setState({
editorState: EditorState.createEmpty(),
});
}
}

自定义控件封装

需要的是一个点击后可以进行富文本编辑的控件,
控件代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
/* eslint-disable no-return-assign,react/sort-comp */
import { Table, Input, Icon, Button, Popconfirm } from 'antd';
import React from 'react';
import styles from './index.less';
import cssStyles from './index.css';
import { Editor } from 'react-draft-wysiwyg';
import { EditorState, convertToRaw, convertFromRaw, convertFromHTML, ContentState } from 'draft-js';
import draftToHtml from 'draftjs-to-html';
import 'react-draft-wysiwyg/dist/react-draft-wysiwyg.css';
import ColorPic from '../EditRichCustomToolbar';

export default class EditableRichCell extends React.Component {
// 构造
constructor(props) {
super(props);
// 初始状态
this.state = {
value: this.props.value,
editorState: EditorState.createEmpty(),
editable: false,
};
this.setDomEditorRef = ref => (this.domEditor = ref);
}

componentWillMount() {
try {
this.setState({
editorState: EditorState.createWithContent(
ContentState.createFromBlockArray(convertFromHTML(draftToHtml(this.props.editorState)))
),
});
} catch (e) {
this.setState({
editorState: EditorState.createEmpty(),
});
}
}

customBlockStyleFn = contentBlock => {
return cssStyles['public-DraftStyleDefault-block'];
};

onEditorStateChange = editorState => {
this.setState({
editorState,
});
};

check = () => {
this.setState({ editable: false });
if (this.props.onChange) {
this.props.onChange(convertToRaw(this.state.editorState.getCurrentContent()));
}
};
edit = () => {
this.setState({ editable: true }, () => {
this.domEditor.focusEditor();
});
};
render() {
const { value, editable, editorState } = this.state;
return (
<div className={styles.editable_cell}>
{editable ? (
<div
style={{
border: '1px solid #d9d9d9',
borderRadius: 5,
marginTop: 10,
backgroundColor: '#fff',
}}
>
<Editor
ref={this.setDomEditorRef}
editorState={editorState}
localization={{ locale: 'zh' }}
wrapperClassName={styles.wrapper_class}
editorClassName={styles.editor_class}
blockStyleFn={this.customBlockStyleFn}
onEditorStateChange={this.onEditorStateChange}
onBlur={this.check}
toolbarClassName="toolbar-class"
toolbar={{
options: ['inline', 'colorPicker'],
inline: {
options: ['bold'],
},
colorPicker: { component: ColorPic },
}}
/>
{/*<Icon type="check" onClick={this.check} />*/}
</div>
) : (
<div
style={{ display: 'flex', height: '100%', width: '100%', minHeight: 20 }}
onDoubleClick={this.edit}
>
{this.props.editorState.length > 1 || this.props.editorState.blocks[0].text ? (
<div dangerouslySetInnerHTML={{ __html: draftToHtml(this.props.editorState) }} />
) : (
<div style={{ color: '#cfcfcf', fontSize: 14, cursor: 'pointer' }}>双击此处编辑</div>
)}
{/*<Icon type="edit" className={styles.editable_cell_icon} onClick={this.edit} />*/}
</div>
)}
</div>
);
}
}

调用方法:

1
2
3
4
5
6
7
8
9
10
11
onCellChange = (record, dataIndex, value) => {
const pointId = record.id;
...
};
.
.
.
<EditableRichCell
editorState={record.pointDetail}
onChange={value => this.onCellChange(record, 'xxxx', value)}
/>