react-quill 图片上传及图片粘贴功能踩坑记录

Gitlab React-quill:https://github.com/zenoamaro/react-quill

中文文档 Quill:http://doc.quilljs.cn/1409381

官网 Quill:https://quilljs.com/docs/quickstart/

Gitlab Delta:https://github.com/quilljs/delta

需求:

  1. 图片上传由base64改为走后端接口存储服务端
  2. 图片粘贴功能,也走后台接口存储服务端

坑点:

  1. focus,onblur与react-quill的图片上传有冲突导致死循环

一开始的需求是focus点击变成只读状态,onBlur后变成可写状态并调接口保存,查阅文档后用了编辑器自带的

readOnly字段,在没加图片上传的imageHandler的时候还是好好的,但是一加就陷入了一直调保存接口死循环,中间有试图绕过但又中了其他坑最后和产品商量放弃了onBlur去保存采用按钮手动保存

  1. imageHandler在配合onChange方法时会出现输一个单词就失去焦点的问题

这个问题的使用场景是在配合antd form表单时出现,研究了很久发现不配合form的时候也会出现,然后一点点试发现只要是handler里的使用方法都会出现这个问题不只是imageHandler,官方文档的介绍也很少,最后是在React Quill 富文本编辑器中图片上传服务端找到了解决方案,使用useMemo包裹

const modules = React.useMemo(() => ({
    toolbar: {
        container: toolbarContainer,
        handlers: {
            image: imageHandler
        }
    },
}), []);
  1. react-quill setEditorContents使用报错

需要用该方法对编辑器做初始化,但使用时出现了undefined的错误,因为网上没找着具体的函数使用说明所以就以为第一个参数是传内容值,后来点进源码发现第一个参数要传quill的实例,第二个参数才是内容值

reactQuillRef?.current?.setEditorContents?.(
	reactQuillRef?.current?.getEditor(),
    data,
);
  1. 在imageHandler里拿不到ref为null

因为要在imageHandler里调上传接口所以需要获取编辑器的数据,但ref为空了,后来定位发现是ref的使用有问题

// 正确
let reactQuillRef: any = useRef(null);
<ReactQuill
	ref={reactQuillRef}
/>
        
// 错误
let reactQuillRef: any = useRef(null);
<ReactQuill
	ref={e=>{reactQuillRef = e}}
/>        
  1. 粘贴的时候失去焦点取不到光标的定位

原本想粘贴图片的时候去获取光标的位置然后粘在光标处,但始终拿不到光标定位,可能是因为粘贴的时候调用了上传图片的接口然后函数中执行了input.click方法导致失焦,后来直接写死了个最大值然后它就自动定位到末尾了

quill.insertEmbed(9999, 'image', link)
  1. setEditorContents 如果有图片也会莫名其妙的触发粘贴事件

在配合form编辑时初始化表单使用form.setFields发现先触发了粘贴的函数,然后报错导致整个页面挂了,一点点调试后发现用插件的函数setEditorContents也同样会触发,但是在readOnly=true时是正常的,最后实在没有找到方法只能在编辑的时候放弃使用粘贴的功能

Base64转文件流

粘贴的时候需要将base格式转为文件流,采用了以下博主的方法

React-Quill中的图片上传及显示

convertBase64UrlToBlob = (urlData) => {
    //去掉url的头,并转换为byte
    const bytes = window.atob(urlData.split(',')[1]);
    //处理异常,将ascii码小于0的转换为大于0
    const ab = new ArrayBuffer(bytes.length);
    const ia = new Uint8Array(ab);
    ia.forEach((i, index) => {
      ia[index] = bytes.charCodeAt(index);
    });
    return new Blob([ia], { type: urlData.split(',')[0].split(':')[1].split(';')[0] });
};

完整代码:

import React, {
  forwardRef,
  useImperativeHandle,
  useRef,
} from 'react';
import 'react-quill/dist/quill.snow.css';
import ReactQuill from 'react-quill';
import styles from './index.less';
import { connect } from 'dva';
import Utils from '@/utils/utils';

const formats = [
  'header',
  'bold',
  'italic',
  'underline',
  'strike',
  'blockquote',
  'code-block',
  'list',
  'bullet',
  'indent',
  'link',
  'image',
  'color',
  'background',
  'font',
  'align',
  'clean',
];

const Editor = (props: any) => {
  const {
    refInstance,
    readOnly = false,
    closeClipboardImg = false,
    ...otherProps
  } = props;

  let reactQuillRef: any = useRef(null);

  // 上传图片
  const imageHandler = async () => {
    const input = document.createElement('input');
    input.setAttribute('type', 'file');
    input.setAttribute('accept', 'image/*');
    input.setAttribute('multiple', 'multiple');
    input.click();
    input.onchange = async () => {
      Array.from(input.files).forEach((item) => {
        const formData = new FormData();
        formData.append('files', item);
        // 上传图片
        props.dispatch({
          type: 'attachment/addAttachment',
          payload: formData,
          callback: (url: any) => {
            // 获取url
            let quill = reactQuillRef?.current?.getEditor(); //获取到编辑器本身
            const cursorPosition = quill.getSelection().index; //获取当前光标位置
            const link = url;
            quill.insertEmbed(cursorPosition, 'image', link); //插入图片
            quill.setSelection(cursorPosition + 1); //光标位置加1
          },
        });
      });
    };
  };

  // 对外暴露
  useImperativeHandle(refInstance, () => ({
    focus: (param = {}) => {
      reactQuillRef?.current?.focus?.();
    },
    getEditorContents: (param = {}) => {
      return reactQuillRef?.current?.getEditorContents?.();
    },
    setEditorContents: (data: any) => {
      reactQuillRef?.current?.setEditorContents?.(
        reactQuillRef?.current?.getEditor(),
        data,
      );
    },
    moveMouseEnd: () => {
      // 将光标移动到最后
      let editor = reactQuillRef?.current?.getEditor();
      editor.setSelection(9999, 0);
    },
  }));

  // 粘贴图片
  const customImgForPaste = async (node: any, delta: any) => {
    delta.forEach((op: any) => {
      let file = Utils.base64toFile(op?.insert?.image);
      const formData = new FormData();
      formData.append('files', file);
      // 上传图片
      props.dispatch({
        type: 'attachment/addAttachment',
        payload: formData,
        callback: (url: any) => {
          // 获取url
          let quill = reactQuillRef?.current?.getEditor(); //获取到编辑器本身
          const cursorPosition = 99999; //粘贴的时候失去焦点拿不到光标位置
          const link = url;
          quill.insertEmbed(cursorPosition, 'image', link); //插入图片
          quill.setSelection(cursorPosition); //光标位置
        },
      });
    });
  };

  const toolbar = React.useMemo(
    () => ({
      toolbar: {
        container: [
          [{ header: [1, 2, 3, 4, 5, 6, false] }],
          ['bold', 'italic', 'underline', 'strike', 'blockquote', 'code-block'],
          [
            { list: 'ordered' },
            { list: 'bullet' },
            { indent: '-1' },
            { indent: '+1' },
          ],
          ['link', 'image'],
          [{ color: [] }, { background: [] }, { align: [] }],
          ['clean'],
        ],
        handlers: {
          image: imageHandler,
        },
      },
      clipboard: {
        matchers: [
          // 粘贴事件和setEditorContents有冲突报错,但在readOnly=true的时候可以正常使用
          // 临时处理方式:需要setEditorContents的地方先将readOnly=true再使用,或者先去掉粘贴功能比如表单编辑的时候
          closeClipboardImg ? [] : ['img', customImgForPaste],
        ],
      },
      // imageDrop: true,
    }),
    [],
  );

  const defaultProps = {
    theme: 'snow',
    modules: toolbar,
    formats,
    placeholder: '请输入',
    className: styles.default_style,
  };

  const readOnlyProps = readOnly
    ? {
        readOnly: true,
        modules: {},
        formats: {},
        theme: '',
      }
    : {};

  return (
    <>
      <ReactQuill
        ref={reactQuillRef}
        {...defaultProps}
        {...otherProps}
        {...readOnlyProps}
      />
    </>
  );
};

const Ed = connect()(Editor);

export default forwardRef((props, ref) => <Ed {...props} refInstance={ref} />);

  • 8
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
以下是使用react-quill上传图片的步骤: 1. 首先,你需要安装react-quill和相关的依赖包。你可以使用以下命令来安装: ```shell npm install react-quill axios ``` 2. 在你的React组件中引入react-quill和axios: ```javascript import ReactQuill from 'react-quill'; import 'react-quill/dist/quill.snow.css'; import axios from 'axios'; ``` 3. 在你的组件中创建一个状态来存储编辑器的内容和上传的图片URL: ```javascript const [editorHtml, setEditorHtml] = useState(''); const [imageUrl, setImageUrl] = useState(''); ``` 4. 创建一个函数来处理图片上传: ```javascript const handleImageUpload = async (file) => { const formData = new FormData(); formData.append('image', file); try { const response = await axios.post('YOUR_UPLOAD_URL', formData); const imageUrl = response.data.imageUrl; setImageUrl(imageUrl); } catch (error) { console.error('Error uploading image', error); } }; ``` 请将`YOUR_UPLOAD_URL`替换为你的图片上传接口的URL。 5. 在你的组件中渲染react-quill编辑器,并配置相关的属性和事件处理函数: ```javascript <ReactQuill value={editorHtml} onChange={setEditorHtml} modules={{ toolbar: { container: [ [{ header: [1, 2, false] }], ['bold', 'italic', 'underline', 'strike', 'blockquote'], [{ list: 'ordered' }, { list: 'bullet' }], ['link', 'image'], ['clean'], ], handlers: { image: () => { const input = document.createElement('input'); input.setAttribute('type', 'file'); input.setAttribute('accept', 'image/*'); input.click(); input.onchange = async () => { const file = input.files[0]; const range = quillRef.current.getEditor().getSelection(true); quillRef.current.getEditor().insertEmbed(range.index, 'image', 'data:image', 'user'); quillRef.current.getEditor().setSelection(range.index + 1); handleImageUpload(file); }; }, }, }, }} /> ``` 请将`YOUR_UPLOAD_URL`替换为你的图片上传接口的URL。 6. 最后,你可以在编辑器中插入图片并将其上传到服务器。上传成功后,你可以将图片的URL设置为`imageUrl`状态,并在编辑器中显示图片: ```javascript <ReactQuill value={editorHtml} onChange={setEditorHtml} modules={...} formats={...} style={{ height: '300px' }} /> {imageUrl && <img src={imageUrl} alt="Uploaded" />} ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值