React中使用富文本编辑器wangEditor(好用)

546 阅读4分钟

安装

yarn add @wangeditor/editor
# 或者 npm install @wangeditor/editor --save

yarn add @wangeditor/editor-for-react
# 或者 npm install @wangeditor/editor-for-react --save

引入

import '@wangeditor/editor/dist/css/style.css'; // 引入 css 必须
import { Editor, Toolbar } from '@wangeditor/editor-for-react';

使用

  <div style={{ border: '1px solid #ccc', zIndex: 100 }} className={style.container}>
    <Toolbar editor={editor} defaultConfig={toolbarConfig} mode="default" style={{ borderBottom: '1px solid #ccc' }} />
    <Editor
        defaultConfig={editorConfig}
        value={html}
        onCreated={setEditor}
        onChange={(editor) => {
            setHtml(editor.getHtml());
        }}
        mode="default"
        style={{ height: '500px', overflowY: 'hidden' }}
    />
  </div>

配置

工具栏配置

// 工具栏配置
    const toolbarConfig = {
         insertKeys:{
                index: 5, // 插入的位置,基于当前的 toolbarKeys
                keys: ['menu-key1', 'menu-key2'],
           }
           //... 其他
    };

菜单配置

此处没有使用自带的上传,而是自定义上传到阿里云

// 编辑器配置
    const editorConfig = {
        placeholder: '请输入内容',
        MENU_CONF: {
            uploadImage: {
                server: '/apiupload',
                allowedFileTypes: ['image/*'],
                async customUpload(file, insertFn) {
                    richerEditorUpload(file, 15, insertFn, 'img');
                },
            },
            uploadVideo: {
                server: '/apiupload',
                // 选择文件时的类型限制,默认为 ['video/*'] 。如不想限制,则设置为 []
                allowedFileTypes: ['video/*'],
                async customUpload(file, insertFn) {
                    richerEditorUpload(file, 16, insertFn, 'video');
                },
            },
        },
    };

上传部分的逻辑——封装的ossUploader类

//封装的ossUploader类
import CryptoJS from 'crypto-js';
import Base64 from 'base-64';
import { getCurrentUser } from './authority';
import { getOss } from '@/services/oss';

const OSS = require('ali-oss');
const HOST = '/apiupload';
const LASTHOST = 'oss-cn-hangzhou';
let ACCESSKEYID = 'LTAI5tPCHQJLGnNA3NeHM7jV';
const ACCESSSECRET = 'uQw3w6OKJdcq2DMB71i3XlgHhKp6FJ';
// JavaScript AES解密函数
const customKey = 'CareBay=KxhSpxFWetnTjqMHYFiyBook'; // 必须是16字节

const getLastPoker = () => {
    // if (process.env.NODE_ENV === 'development') {
    //     return "carebay-saas-test"
    // } else if (window.location.hostname.includes('saas')) {
    //     return "carebay-saas-test"
    // } else {
    //     return "carebay-saas"
    // }
    return localStorage.getItem('bucketName');
};
const BUCKET = getLastPoker();
console.log('BUCKET', BUCKET);
const POLICYTEXT = {
    expiration: '2028-01-01T12:00:00.000Z', // 设置该Policy的失效时间,
    conditions: [
        ['content-length-range', 0, 1048576000], // 设置上传文件的大小限制
    ],
};
const client = new OSS({
    // 填写Bucket所在地域。以华东1(杭州)为例,Region填写为oss-cn-hangzhou。
    region: LASTHOST,
    // 阿里云账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM用户进行API访问或日常运维,请登录RAM控制台创建RAM用户。
    accessKeyId: localStorage.getItem('ACCESSKEYID') || ACCESSKEYID,
    // accessKeySecret: ACCESSSECRET,
    accessKeySecret: localStorage.getItem('secretKey') || ACCESSSECRET,
    // 填写存储空间名称。
    bucket: BUCKET,
});
export default class ossUpload {
    #state = {
        todayKey: '',
        host: HOST,
        target: 1,
        cardMap: {
            1: '/account/', // 用户头像
            2: '/contract/', // 合同
            3: '/sales-literature/', // 销售子来哦,房间,套餐等等
            4: '/nurse-literature/', // 专户师,房间,套餐等等
            5: '/invoice-upload/', // 开票上传
            6: '/warm-thanks-upload/', // 温馨答谢上传
            7: '/customer-attachments/', // 客户附件上传
            8: '/mallManagement-products/', // 商品图片上传
            9: '/mallManagement-productDetail/', // 商品详情上传
            10: '/mallManagement-ImageUploader/', // 商品详情价格和库存上传
            11: '/addNewStore-ImageUploader/', //  新增门店图片上传
            12: '/addGroupBuy-ImageUploader/', //  添加团购图片上传
            13: '/homeManagement-ImageUploader/', //  首页管理图片上传
            14: '/classificationOfProduction-ImageUploader/', //  首页管理图片上传
            15: '/contentManagementImageUploader/', //  内容管理管理图片上传
            16: '/contentManagementVideoUploader/', //  内容管理管理视频上传
        },
        // accessKeyId: ACCESSKEYID,
        accessKeyId: localStorage.getItem('ACCESSKEYID') || ACCESSKEYID,

        // accessSecret: ACCESSSECRET, || ACCESSKEYID

        accessSecret: localStorage.getItem('secretKey') || ACCESSSECRET,

        policyText: POLICYTEXT,
        policy: '',
        signature: '',
        baseurl: 'tenant/' + getCurrentUser().tenantId,
    };
    constructor () {
        this.#init();
    }
    #init () {
        this.#initPolicy();
        this.#initSignature();
    }
    #initPolicy () {
        this.#state.policy = Base64.encode(JSON.stringify(this.#state.policyText));
    }
    #initSignature () {
        this.#state.signature = CryptoJS.HmacSHA1(this.#state.policy, this.#state.accessSecret, { asBytes: true }).toString(CryptoJS.enc.Base64);
    }
    setName (name, target) {
        this.#state.todayKey = name;
        this.#state.target = target;
    }
    getLastUrlByKey (key) {
        return client.signatureUrl(this.#state.baseurl + key, { expires: 3600 });
    }
    getLastUrl () {
        if (!this.#state.todayKey) {
            console.error('请先执行ossUpload类中,setName方法(在Upload组件中的beforeUpload函数里获取File里的name),用于初始化key');
            return {
                success: false,
            };
        }
        return {
            success: true,
            key: this.#state.baseurl + this.#state.cardMap[this.#state.target] + this.#state.todayKey,
            value: client.signatureUrl(this.#state.baseurl + this.#state.cardMap[this.#state.target] + this.#state.todayKey, { expires: 3600 }),
        };
    }

    getAccount (name) {
        console.log(name, 'name');
        if (name) {
            let arrr = client.signatureUrl(name, { expires: 3600 });
            console.log(arrr);
            return arrr;
        }
        return null;
    }

    getUpLoad () {
        // JavaScript AES解密函数

        return {
            action: this.#state.host,
            headers: {
                'X-Requested-With': null,
            },
            data: {
                key: this.#state.baseurl + this.#state.cardMap[this.#state.target] + this.#state.todayKey,
                policy: this.#state.policy,
                // OSSAccessKeyId: this.#state.accessKeyId,
                OSSAccessKeyId: localStorage.getItem('ACCESSKEYID'),
                success_action_status: 200,
                signature: this.#state.signature,
            },
        };
    }
}


上传部分的逻辑——封装的wangEditor上传逻辑,可以上传视频或图片

// 富文本编辑器的视频或图片上传
export const richerEditorUpload = async (file, targetCode, insertFn, type = 'img') => {
    // file 即选中的文件
    const uploader = new ossUpload();
    uploader.setName(file.name, targetCode);
    const { action, data } = uploader.getUpLoad();
    const formData = new FormData();
    Object.keys(data).forEach((key) => {
        formData.append(key, data[key]);
    });
    formData.append('file', file);
    const res = await fetch(action, {
        method: 'POST',
        body: formData,
    });
    if (res.status === 200) {
        const urlInfo = uploader.getLastUrl();
        const url = urlInfo?.value;
        const paramsMap = {
            img: file.name,
            // video: generateVideoCover(url), //前端自己生成视频封面
        };
        insertFn(url, paramsMap?.[type]);
    }
};

图片和视频上传效果预览

image.png

视频上传的预览封面(小难点)

正常情况下,经过视频剪辑软件输出的视频文件是自带封面的,但手机直接拍的视频是没有的,上传后无法浏览器预览封面,可以通过如下方法截取视频指定时刻的“帧”生成封面

function generateVideoCover (videoUrl) {
    const video = document.createElement('video');
    const canvas = document.createElement('canvas');
    const ctx = canvas.getContext('2d');

    video.src = videoUrl;
    video.crossOrigin = 'anonymous'; // 解决跨域问题(如果视频允许)

    video.addEventListener('loadeddata', () => {
        // 跳转到第 1 秒(可调整)
        video.currentTime = 1;
    });

    video.addEventListener('seeked', () => {
        // 绘制当前帧到 Canvas
        canvas.width = video.videoWidth;
        canvas.height = video.videoHeight;
        ctx.drawImage(video, 0, 0, canvas.width, canvas.height);

        // 获取封面 Base64
        return canvas.toDataURL('image/jpeg');
    });
}