import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { message, theme } from 'antd'; import { Editor, Toolbar } from '@wangeditor/editor-for-react'; import type { IDomEditor, IEditorConfig, IToolbarConfig } from '@wangeditor/editor'; import '@wangeditor/editor/dist/css/style.css'; import { uploadFile } from '@/api/system/sysFile'; import type { ISysFileInfo } from '@/domain/iSysFile'; import { useTranslation } from 'react-i18next'; import type { RichTextEditorProps } from './typings'; /** * 富文本编辑器(wangEditor v5) * - 自定义图片上传:复用系统文件上传接口,校验参照 ImageUploader * - 隐藏视频上传、表情、附件等复杂功能,仅保留基础排版与图片 */ const RichTextEditor: React.FC = ({ value, onChange, disabled = false, height = 400, placeholder, groupId = 0, maxSize = 5, }) => { const { t } = useTranslation(); const { token } = theme.useToken(); const [editor, setEditor] = useState(null); // 自定义图片上传(校验逻辑参照 ImageUploader 组件) const customUpload = useCallback( async (file: File, insertFn: (src: string, alt: string, href: string) => void) => { // 1. 文件类型校验 if (!file.type.startsWith('image/')) { message.error(t('xin.form.richText.error.notImage')); return; } // 2. 文件大小校验 if (file.size / 1024 / 1024 > maxSize) { message.error(t('xin.form.richText.error.sizeExceeded', { maxSize })); return; } // 3. 调用系统文件上传接口 try { const res = await uploadFile(file, groupId); const info = res.data.data as ISysFileInfo; insertFn(info.preview_url || info.file_url || '', info.file_name || '', ''); } catch { message.error(t('xin.form.richText.error.uploadFailed')); } }, [t, groupId, maxSize] ); // 工具栏配置:只保留基础排版 + 图片,隐藏视频/表情/附件等复杂功能 const toolbarConfig: Partial = useMemo( () => ({ toolbarKeys: [ 'headerSelect', 'blockquote', '|', 'bold', 'underline', 'italic', 'through', '|', 'color', 'bgColor', '|', 'bulletedList', 'numberedList', 'todo', '|', 'justifyLeft', 'justifyCenter', 'justifyRight', '|', 'insertLink', 'uploadImage', 'insertTable', '|', 'undo', 'redo', 'fullScreen', ], }), [] ); // 编辑器配置 const editorConfig: Partial = useMemo( () => ({ placeholder, MENU_CONF: { uploadImage: { // 自定义上传:覆盖默认的服务端上传方式 customUpload, allowedFileTypes: ['image/*'], maxFileSize: maxSize * 1024 * 1024, // 禁用 base64 插入,粘贴/拖拽图片一律走自定义上传 base64LimitSize: 0, }, }, }), [placeholder, customUpload, maxSize] ); // 禁用/启用编辑器 useEffect(() => { if (editor == null) return; if (disabled) { editor.disable(); } else { editor.enable(); } }, [editor, disabled]); // 组件卸载时销毁编辑器实例 useEffect( () => () => { if (editor == null) return; editor.destroy(); setEditor(null); }, [editor] ); return (
{!disabled && ( )} onChange?.(ed.getHtml())} mode="default" style={{ height, overflowY: 'hidden', background: disabled ? token.colorBgLayout : undefined, }} />
); }; export default RichTextEditor;