React富文本编辑器实现图片上传至后台的完整指南

一、选择合适的富文本编辑器

在React生态中,有多种富文本编辑器可供选择,如react-quillreact-draft-wysiwygckeditor等。每种编辑器都有其独特的特点和优势,本文将以react-quill为例进行讲解。

1.1 安装react-quill

首先,需要在项目中安装react-quillquill

npm install react-quill quill

1.2 引入并使用react-quill

在React组件中引入react-quill,并简单配置:

import React from 'react';
import ReactQuill from 'react-quill';
import 'react-quill/dist/quill.snow.css'; // 引入样式

const MyEditor = () => {
  const [content, setContent] = React.useState('');

  const handleChange = (value) => {
    setContent(value);
  };

  return <ReactQuill value={content} onChange={handleChange} />;
};

export default MyEditor;

二、实现图片上传功能

2.1 自定义图片上传处理

2.1.1 创建自定义Image模块

首先,创建一个自定义的Image模块:

const CustomImage = Quill.import('formats/image');
const ImageBlot = Quill.import('blots/image');

class CustomImageBlot extends ImageBlot {
  static create(value) {
    let node = super.create();
    node.setAttribute('src', value.src);
    return node;
  }

  static value(node) {
    return { src: node.getAttribute('src') };
  }
}

Quill.register(CustomImageBlot, true);

2.1.2 处理图片上传

const MyEditor = () => {
  const [content, setContent] = React.useState('');
  const quillRef = React.useRef(null);

  const handleChange = (value) => {
    setContent(value);
  };

  const handleImageUpload = (e) => {
    const input = e.target;
    const file = input.files[0];
    const formData = new FormData();
    formData.append('image', file);

    // 发送图片至后台
    fetch('/api/upload', {
      method: 'POST',
      body: formData,
    })
    .then(response => response.json())
    .then(data => {
      const quill = quillRef.current.getEditor();
      const range = quill.getSelection(true);
      quill.insertEmbed(range.index, 'image', { src: data.url });
      quill.setSelection(range.index + 1);
    })
    .catch(error => {
      console.error('Error uploading image:', error);
    });
  };

  React.useEffect(() => {
    const quill = quillRef.current.getEditor();
    const toolbar = quill.getModule('toolbar');
    toolbar.addHandler('image', () => {
      const input = document.createElement('input');
      input.setAttribute('type', 'file');
      input.setAttribute('accept', 'image/*');
      input.click();
      input.onchange = handleImageUpload;
    });
  }, []);

  return <ReactQuill ref={quillRef} value={content} onChange={handleChange} />;
};

export default MyEditor;

2.2 后台处理图片上传

const express = require('express');
const multer = require('multer');
const app = express();

const storage = multer.diskStorage({
  destination: function (req, file, cb) {
    cb(null, 'uploads/');
  },
  filename: function (req, file, cb) {
    cb(null, Date.now() + '-' + file.originalname);
  },
});

const upload = multer({ storage: storage });

app.post('/api/upload', upload.single('image'), (req, res) => {
  const file = req.file;
  if (file) {
    res.json({ url: `/uploads/${file.filename}` });
  } else {
    res.status(400).send('No file uploaded.');
  }
});

app.listen(3000, () => {
  console.log('Server is running on port 3000');
});

三、优化用户体验

3.1 图片上传进度提示

const handleImageUpload = (e) => {
  const input = e.target;
  const file = input.files[0];
  const formData = new FormData();
  formData.append('image', file);

  // 显示上传进度
  const quill = quillRef.current.getEditor();
  const range = quill.getSelection(true);
  quill.insertText(range.index, '上传中...', 'api');
  quill.setSelection(range.index + 1);

  fetch('/api/upload', {
    method: 'POST',
    body: formData,
  })
  .then(response => response.json())
  .then(data => {
    quill.deleteText(range.index, '上传中...'.length);
    quill.insertEmbed(range.index, 'image', { src: data.url });
    quill.setSelection(range.index + 1);
  })
  .catch(error => {
    quill.deleteText(range.index, '上传中...'.length);
    quill.insertText(range.index, '上传失败', 'api');
    console.error('Error uploading image:', error);
  });
};

3.2 图片压缩与优化

npm install compressorjs

然后在handleImageUpload函数中添加压缩逻辑:

import Compressor from 'compressorjs';

const handleImageUpload = (e) => {
  const input = e.target;
  const file = input.files[0];

  new Compressor(file, {
    quality: 0.8,
    success(result) {
      const formData = new FormData();
      formData.append('image', result);

      fetch('/api/upload', {
        method: 'POST',
        body: formData,
      })
      .then(response => response.json())
      .then(data => {
        const quill = quillRef.current.getEditor();
        const range = quill.getSelection(true);
        quill.insertEmbed(range.index, 'image', { src: data.url });
        quill.setSelection(range.index + 1);
      })
      .catch(error => {
        console.error('Error uploading image:', error);
      });
    },
    error(err) {
      console.error('Error compressing image:', err.message);
    },
  });
};

四、总结

在实际开发中,你可能需要根据具体需求进行更多的定制和优化,但本文提供的基础框架和思路可以作为你进一步探索的起点。祝你开发顺利!