安装使用
参考官网在React中的使用 editor-for-react
yarn add @wangeditor/editor
# 或者 npm install @wangeditor/editor --save
yarn add @wangeditor/editor-for-react
# 或者 npm install @wangeditor/editor-for-react --save
组件封装以及使用
参考官网的customPaste自定义粘贴
import React, { useState, useEffect, useRef, forwardRef, useImperativeHandle } from 'react'
import '@wangeditor/editor/dist/css/style.css' // 引入 css
import styled from 'styled-components'
import { Editor, Toolbar } from '@wangeditor/editor-for-react'
import { IDomEditor, IEditorConfig, IToolbarConfig } from '@wangeditor/editor'
import { message } from 'antd'
import {v4 as uuid} from 'uuid'
import axios from '@/services/index';
const WangEditor = forwardRef((props:any, ref:any) => {
const [editor, setEditor] = useState<IDomEditor | null>(null);
// 编辑器内容
const [html, setHtml] = useState(props.editorContent|| '')
// 模拟 ajax 请求,异步设置 html
useEffect(() => {
}, []) // JS 语法
// 工具栏配置
const toolbarConfig: Partial<IToolbarConfig> = {
} // TS 语法
// 对外暴露方法,可以让父组件调用子组件的方法
// 作用: 减少父组件获取子组件的DOM元素属性,只暴露给父组件需要用到的DOM方法
// 参数1: 父组件传递的ref属性
// 参数2: 返回一个对象,父组件通过ref.current调用对象中方法
useImperativeHandle(ref, () => ({
}));
useEffect(() => {
setHtml(props.editorContent); //设置编辑器内容
}, [props.editorContent]);
// 及时销毁 editor ,重要!
useEffect(() => {
return () => {
if (editor == null) return
if(editor) {
(editor as any).destroy()
}
setEditor(null)
}
}, [editor])
// 编辑器配置
const editorConfig: Partial<IEditorConfig> = { // TS 语法
placeholder: '请输入内容...',
//插入图片
MENU_CONF: {
uploadImage: {
// 单个文件的最大体积限制,默认为 2M
maxFileSize: 4 * 1024 * 1024, // 4M
// 最多可上传几个文件,默认为 100
maxNumberOfFiles: 10,
// 超时时间,默认为 10 秒
timeout: 5 * 1000, // 5 秒
// 用户自定义上传图片
customUpload(file: any, insertFn: any) {
const data = new FormData();
data.append("file", file); // file 即选中的文件 主要就是这个传的参数---看接口要携带什么参数{ key :value}
const hide = message.loading('上传中...', 0);
//这里写自己的接口
axios({
method: 'post',
url: '/upload/uploadAvatar',
headers:{'Content-Type': 'multipart/form-data'},
data:data
}).then((res) => {
const url = res.data.data.url;
insertFn(url); //插入图片,看返回的数据是什么
hide();
}).catch(err=> {
hide();
})
}
}
}
}
// // 自定义粘贴
editorConfig.customPaste = (editor: IDomEditor, event: ClipboardEvent): boolean => { // TS 语法
// event 是 ClipboardEvent 类型,可以拿到粘贴的数据
// 可参考 https://developer.mozilla.org/zh-CN/docs/Web/API/ClipboardEvent
// let html = event && event?.clipboardData?.getData('text/html') // 获取粘贴的 html
const text = event && event?.clipboardData?.getData('text/plain') // 获取粘贴的纯文本
let files = event && event?.clipboardData?.files
event.preventDefault();
if(files && files.length>0) {
const file = files[0]
const type = file.type.split('/')
//对复制粘贴的图片进行重命名,原因是每次复制粘贴的图片名都是一样的,会导致bug,可以自行去掉重现该bug
const newfile = new File([file], 'pictur'+uuid() + '.' + type[1], {type: file.type});
//这里写自己的接口
const data = new FormData();
data.append("file", newfile);
const hide = message.loading('上传中...', 0);
axios({
method: 'post',
url: '/upload/uploadAvatar',
headers:{'Content-Type': 'multipart/form-data'},
data:data
}).then((res) => {
const url = res.data.data.url;
editor.dangerouslyInsertHtml(`<img src="${url}" />`)
hide();
}).catch(err=> {
hide();
})
return false;
}else {
editor.insertText(text || '');
return false;
}
}
return (
<Wrap>
<div style={{ border: '1px solid #ccc', zIndex: 100}}>
<Toolbar
editor={editor}
defaultConfig={toolbarConfig}
mode="default"
style={{ borderBottom: '1px solid #ccc' }}
/>
<Editor
defaultConfig={editorConfig}
value={html}
onCreated={setEditor}
onChange={editor => {
setHtml(editor.getHtml());
props.saveHtmParams(editor.getHtml())
}}
mode="default"
style={{ height: 'calc(100vh - 420px)', overflowY: 'hidden' }}
/>
</div>
</Wrap>
)
})
const Wrap = styled.div`
.w-e-text-container img{
width: 300px;
height: 400px;
}
`;
export default WangEditor;
组件引用封装后的wangEditor
import React, { useRef, useState, forwardRef, useImperativeHandle, useEffect, useCallback } from 'react'
import { Button, Form, Input, InputNumber, message, Modal, Popover, Tree, Radio, Breadcrumb, Card, Select, } from 'antd';
import { useSelector, shallowEqual } from "react-redux";
import { useLocation, useNavigate } from 'react-router-dom';
import WangEditor from '@/components/content/wangEditor'
import Reback from '@/components/content/reBack'
import Save from '@/components/content/save';
import axios from '@/services/index'
const Content = (props:any) => {
const [content, setContent] = useState({})
const [form] = Form.useForm();
//保存按钮父子组件交互使用
let motherMethodRef = useRef<any>(null);
const {state} = useLocation()
useEffect(() => {
if(state.product) {
form.setFieldsValue(state.product)
}
}, [])
const navigate = useNavigate()
const { users } = useSelector(
(state: any) => ({ users: state.login.users }),
shallowEqual
);
//获取保存按钮上传子组件
const saveContent = ()=>{
form.submit();
}
const saveHtmParams = (value: string)=>{
form.setFieldsValue({
content: value
})
}
//弹框点击提交
const onFinish = (values: any) => {
const params = {
id: state?.product?.id,
...values,
}
};
//form组件弹框报错
const onFinishFailed = (errorInfo: any) => {
};
return (
<div className="content">
<Reback/>
<div className="content_form">
<Form
name="basic"
labelCol={{ span: 5 }}
wrapperCol={{ span: 18 }}
onFinish={onFinish}
onFinishFailed={onFinishFailed}
autoComplete="off"
form={form}
>
<Form.Item
label="产品名称"
name="title"
rules={[{ required: true, message: "请输入产品名称" }]}
>
<Input placeholder="请输入产品名称"/>
</Form.Item>
{/* // 表单嵌套 */}
<Form.Item label="产品内容" name="content" initialValue="" rules={[{ required: true, message: "请输入产品内容" }]}>
<WangEditor saveHtmParams={saveHtmParams} editorContent={state?.product?.content} ref={quillMethodRef}/>
</Form.Item>
</Form>
</div>
<Save saveContent={saveContent} ref={motherMethodRef}/>
</div>
)
}
export default Content
写在最后wangEditor在使用和接口文档来说,还是不错的。单从复制粘贴来说,比react-quill要好用。使用react-quill,存在几个问题:
1、复制粘贴找不到当前鼠标下标,一般只能把光标写死,粘贴到最后或者最前
2、复制粘贴时,如果滚动条在最下面,粘贴完成后会直接跑到顶部