react利用wangEditor写评论和@功能

先引入wangeditor写评论功能

import React, { useEffect, useState, useRef, forwardRef, useImperativeHandle } from 'react';
import '@wangeditor/editor/dist/css/style.css';
import { Editor, Toolbar } from '@wangeditor/editor-for-react';
import { Button, Card, Col, Form, List, Row, Select, Tag, message, Mentions } from 'antd';
import { wsPost, wsGet } from '@models/BaseModel';
import { ListItemDataType, fakeList } from '../../List';
import { LikeOutlined, LoadingOutlined, MessageOutlined, StarOutlined } from '@ant-design/icons';
import ArticleListContent from '../../ArticleListContent/index';
import './style.less';
import closeImg from '../../../../image/close.svg';
// import { createToolbar } from '@wangeditor/editor/dist/editor/src';
import { position, offset } from 'caret-pos';
import { IDomEditor, DomEditor, IModalMenu, SlateNode, Boot } from '@wangeditor/editor';
import mentionModule, { MentionElement } from '@wangeditor/plugin-mention';
import PersonModal from './personModal';
// Extend menu

const IconText = ({ type, text }) => {
    switch (type) {
        case 'star-o':
            return (
                
                    
                    {text}
                
            );
        case 'like-o':
            return (
                
                    
                    {text}
                
            );
        case 'message':
            return (
                
                    
                    {text}
                
            );
        default:
            return null;
    }
};

const Comments = forwardRef((props, CommentRef) => {
    const [editor, setEditor] = useState(null); // 存储 editor 实例
    const [html, setHtml] = useState();
    const [loading, setLoading] = useState(true);
    const [commentVis, setCommentVis] = useState(false);
    const [commentParentId, setCommentParentId] = useState('0'); //父级id
    const [messageApi, contextHolder] = message.useMessage();
    const [buttonLoading, setButtonLoading] = useState(false);
    const [personList, setPersonList] = useState([]); //人员
    const [isModalVisible, setIsModalVisible] = useState(false);
    const formRef = useRef();

    // const toolbar = DomEditor.getToolbar(editor)

    // const curToolbarConfig = toolbar.getConfig()
    // console.log( curToolbarConfig.toolbarKeys )

    useImperativeHandle(CommentRef, () => ({
        closeHand: () => {
            if (editor == null) return;
            setCommentVis(false);
            editor.clear();
            setEditor(null);
        },
    }));
    const withAttachment = editor => {
        const { isInline, isVoid } = editor;
        const newEditor = editor;
        newEditor.isInline = elem => {
            const type = DomEditor.getNodeType(elem);
            if (type === 'attachment') return true; // 针对 type: attachment ,设置为 inline
            return isInline(elem);
        };

        return newEditor;
    };
    useEffect(() => {
        console.log(props.dataList, props.type, 'nbsp');
    }, [props.dataList]);
    useEffect(() => {
        Boot.registerPlugin(withAttachment);
        Boot.registerModule(mentionModule);
        wsGet({
            url: '/api/problem/getUsers',

            handler: res => {
                const { code, data, msg } = res;
                switch (code) {
                    case 20000: {
                        setPersonList(data);
                        break;
                    }
                    default:
                        message.error(msg);
                        break;
                }
            },
        });
    }, []);
    const toolbarConfig = {
        toolbarKeys: [
            'bold',
            'underline',
            'italic',
            // 'emotion',
            {
                key: 'group-image', // 必填,要以 group 开头
                title: '图片', // 必填
                iconSvg:
                    '', // 可选
                menuKeys: ['uploadImage'], // 下级菜单 key ,必填
            },
            {
                key: 'group-video', // 必填,要以 group 开头
                title: '视频', // 必填
                iconSvg:
                    '', // 可选
                menuKeys: ['uploadVideo'], // 下级菜单 key ,必填
            },
            'codeBlock',
        ],
    };
    const editorConfig = {
        placeholder: '请输入内容...',
        MENU_CONF: {
            uploadImage: {
                server: '/api/problem/uploadimag',
                fieldName: 'files',
                maxFileSize: 20 * 1024 * 1024,
                meta: {
                    ifToken: '1',
                },
                metaWithUrl: true,
                headers: {
                    token: localStorage.getItem('X-Auth-Token'),
                },
                onBeforeUpload() {
                    setButtonLoading(true);
                    message.loading({
                        content: '上传中',
                        duration: 0,
                    });
                },
                onSuccess(file, res) {
                    setButtonLoading(false);
                    message.destroy();
                    console.log(`${file.name} 上传成功`, res);
                },
                onError(file, err, res) {
                    // console.log(`${file.name} 上传出错`, err, res)
                    message.error(res.msg);
                },
                customInsert(res, insertFn) {
                    console.log(res);
                    // 从 res 中找到 url alt href ,然后插入图片
                    insertFn(res.data[0].filePath, res.data[0].fileName, res.data[0].filePath);
                },
            },
            uploadVideo: {
                server: '/api/problem/uploadimag',
                fieldName: 'files',
                maxFileSize: 200 * 1024 * 1024,
                meta: {
                    ifToken: '1',
                },
                metaWithUrl: true,
                headers: {
                    token: localStorage.getItem('X-Auth-Token'),
                },
                timeout: 15 * 1000,
                onBeforeUpload() {
                    console.log(messageApi, 'shipinzou');
                    setButtonLoading(true);
                    message.loading({
                        content: '上传中',
                        duration: 0,
                    });
                },
                onSuccess(file, res) {
                    setButtonLoading(false);
                    message.destroy();
                    console.log(`${file.name} 上传成功`, res);
                },
                onError(file, err, res) {
                    // console.log(`${file.name} 上传出错`, err, res)
                    message.error(res.msg);
                },
                customInsert(res, insertFn) {
                    console.log(res);
                    // 从 res 中找到 url alt href ,然后插入图片
                    insertFn(res.data[0].filePath, res.data[0].fileName, res.data[0].filePath);
                },
            },
        },
        EXTEND_CONF: {
            mentionConfig: {
                showModal, // 必须
                hideModal, // 必须
            },
        },
    };
    function showModal(editor) {
        // 获取光标位置,定位 modal
        const domSelection = document.getSelection();
        const domRange = domSelection.getRangeAt(0);
        if (domRange == null) return;
        const selectionRect = domRange.getBoundingClientRect();

        // 获取编辑区域 DOM 节点的位置,以辅助定位
        const containerRect = editor.getEditableContainer().getBoundingClientRect();

        // 显示 modal 弹框,并定位
        // PS:modal 需要自定义,如 
或 Vue React 组件 setIsModalVisible(true); console.log(selectionRect, containerRect, '展示'); // 当触发某事件(如点击一个按钮)时,插入 mention 节点 } function insertMention(id, name) { const mentionNode = { type: 'mention', // 必须是 'mention' value: name, // 文本 info: { id }, // 其他信息,自定义 children: [{ text: '' }], // 必须有一个空 text 作为 children }; editor.restoreSelection(); // 恢复选区 editor.deleteBackward('character'); // 删除 '@' editor.insertNode(mentionNode); // 插入 mention editor.move(1); // 移动光标 } function hideModal(editor) { setIsModalVisible(false); console.log(editor, '隐藏'); // 隐藏 modal } // 及时销毁 editor useEffect(() => { return () => { if (editor == null) return; editor.destroy(); // editor.MENU_CONF['uploadImage'] = setEditor(null); }; }, [editor]); function extractDataInfoValues(inputString) { const regex = /data-info="([^"]*)"/g; const dataInfoValues = []; let match; while ((match = regex.exec(inputString)) !== null) { const decodedValue = decodeURIComponent(match[1]); dataInfoValues.push(JSON.parse(decodedValue).id); } return dataInfoValues; } function handleText() { // console.log(editor.getHtml(), html, editor.getText(), 'sdsdsds'); const ids = extractDataInfoValues(html); if (editor.isEmpty()) { message.error('内容不可为空'); return; } let commentType = ''; switch (props.type) { case '1': commentType = 'reason'; break; case '2': commentType = 'tempProject'; break; case '3': commentType = 'longProject'; break; case '4': commentType = 'validateProject'; break; case '5': commentType = 'validateSummary'; break; case '6': commentType = 'reviewRecords'; break; case '7': commentType = 'proConclution'; break; } let param = { commentType: commentType, content: html, problemId: props.id, parentId: commentParentId, ids: ids, }; wsPost({ url: '/api/problem/insertComment', data: param, handler: res => { const { code, data, msg } = res; switch (code) { case 20000: { if (editor == null) return; editor.clear(); setCommentVis(false); message.success('新增成功'); props.getQuery(); break; } default: message.error(msg); break; } }, }); } function extractContent(inputString, startSymbol, endSymbol) { const regex = new RegExp(`${startSymbol}(.*?)${endSymbol}(?!\\S)`, 'g'); const matches = inputString.matchAll(regex); const result = Array.from(matches, match => match[1]); return result; } function printHtml() { if (editor == null) return; } const addCommpent = id => { setCommentParentId(id); setCommentVis(true); }; const handleClose = () => { setCommentVis(false); editor.clear(); setEditor(null); }; const changeEditor = editor => { setHtml(editor.getHtml()), console.log(editor.getHtml(), editor.getText(), 'xiugai'); }; return ( <> {commentVis &&
} {commentVis && (
{ changeEditor(editor); }} mode="default" style={{ height: '300px' }} /> {isModalVisible && }
)} {/*
{html}
*/} {/* 渲染html */} {/*
hello world.

testtesttesttest

'`}}>
*/} ); }); export default Comments;

评论递归ArticleListContent,jsx

import { Avatar, List, Space, Card } from 'antd';
import React, { useEffect, useState } from 'react';
import moment from 'moment';
import './index.less';
import { fakeList } from '../List';
import { LikeOutlined, LoadingOutlined, MessageOutlined, StarOutlined } from '@ant-design/icons';

const getMarginLeftNum = num => {
    return 30 * num;
};

const GetContent = props => {
    const [loading, setLoading] = useState(false);
    const IconText = ({ icon, text, id, num }) => {
        if (num <= 1) {
            return (
                
                    
{ props.addCommpent(id); }} > {React.createElement(icon)} {text}
); } return ; }; console.log(props.num, 'props.num'); return (
{props.item.map((o, index) => { return (
( ]}>
{moment(item.createDate).format('YYYY-MM-DD HH:mm')}
)} /> {o.children && }
); })}
); }; const ArticleListContent = props => { const [loading, setLoading] = useState(false); const IconText = ({ icon, text, id }) => (
{ props.addCommpent(id); }} > {React.createElement(icon)} {text}
); return (
{!props.data &&
暂无内容
} {props.data && props.data.map((item, index) => { item, 'item'; if (!item.children) { return (
( ]}>
{moment(item.createDate).format('YYYY-MM-DD HH:mm')}
)} />
); } return (
( ]}>
{moment(item.createDate).format('YYYY-MM-DD HH:mm')}
)} /> {item.children && }
); })}
); }; export default ArticleListContent;

@功能自定义的组件 personModal.jsx

import { Modal, Form, Input, Select, message } from 'antd';
import { ModalForm, ProFormTextArea } from '@ant-design/pro-components';
import { wsPost, wsGet } from '@models/BaseModel';
import React, { ReactDOM, useEffect, useRef, useState } from 'react';
const { Option } = Select;

export default function CsModal(props) {
    const selectRef = useRef();
    const [personList, setPersonList] = useState([]); //人员
    const [topPosition, setTopPosition] = useState('');
    const [leftPosition, setLeftPosition] = useState('');
    useEffect(() => {
        // 获取光标位置
        const domSelection = document.getSelection();
        const domRange = domSelection?.getRangeAt(0);
        if (domRange == null) return;

        const rect = document.getElementById('editcontent').getBoundingClientRect();
        const rect1 = domRange.getBoundingClientRect();

        // // 定位 modal
        console.log(rect, rect1, 'top left');
        setTopPosition(`${rect1.top - rect.top - 5}px`);
        setLeftPosition(`${rect1.left - rect.left + 10}px`);
        // focus input
        selectRef.current.focus();
        wsGet({
            url: '/api/problem/getUsers',

            handler: res => {
                const { code, data, msg } = res;
                switch (code) {
                    case 20000: {
                        setPersonList(data);
                        break;
                    }
                    default:
                        message.error(msg);
                        break;
                }
            },
        });
    }, []);
    const onChangeSelect = e => {
        let name = personList.find(item => item.externalId === e);
        props.insertMention(e, name.name);
        props.hideModal();
    };
    return (
        
    );
}

实现效果
react利用wangEditor写评论和@功能_第1张图片

react利用wangEditor写评论和@功能_第2张图片

你可能感兴趣的:(react)