import { useState, useEffect, memo, useMemo, CSSProperties } from "react"; import { Editor, Toolbar } from "@wangeditor/editor-for-react"; import { IDomEditor, IEditorConfig, IToolbarConfig, DomEditor, Boot } from "@wangeditor/editor"; import { merge } from "lodash"; import { getRapidApi } from "../../rapidApi"; import attachmentMenu from "./attachment"; import "@wangeditor/editor/dist/css/style.css"; import "./rich-text-editor-style.css"; import rapidAppDefinition from "../../rapidAppDefinition"; Boot.registerModule(attachmentMenu); interface IProps { hideToolbar?: boolean; style?: CSSProperties; height?: number | string; className?: string; toolbarConfig?: Partial; editorConfig?: Partial; mode?: "edit" | "display"; value?: string; onChange?(value: string): void; } const RapidEditor = memo((props) => { const { height = 500 } = props; const rapidApi = getRapidApi(); const [editor, setEditor] = useState(null); // 工具栏配置 const toolbarConfig: Partial = useMemo>( () => merge, any>( { insertKeys: { index: 24, keys: ["uploadAttachment"], }, excludeKeys: ["insertVideo", "uploadVideo", "editVideoSize", "group-video"], }, props.toolbarConfig || {}, ), [props.toolbarConfig], ); const apiBaseUrl = rapidAppDefinition.getApiBaseUrl(); // 编辑器配置 const editorConfig: Partial = useMemo( () => merge, any>( { placeholder: "请编辑...", readOnly: false, hoverbarKeys: { attachment: { menuKeys: ["downloadAttachment"], // “下载附件”菜单 }, }, MENU_CONF: { uploadAttachment: { customUpload(file, insertFn) { return new Promise((resolve) => { const formData = new FormData(); formData.append("file", file); rapidApi .post("/upload", formData, { headers: { "Content-Type": "multipart/form-data" } }) .then((res) => { const data = res.data; if (res.status >= 200 && res.status < 400) { // 预览(preview):inline=true const url = `${apiBaseUrl}/download/file?fileKey=${data.fileKey}&fileName=${encodeURIComponent(file.name)}`; insertFn(file.name, url); resolve("ok"); } else { throw new Error(data?.message || res.statusText || "附件上传失败"); } }) .catch((err) => { const res = err.response; throw new Error(res?.data?.message || res?.statusText || "附件上传失败"); }); }); }, }, uploadImage: { customUpload(file, insertFn) { return new Promise((resolve) => { const formData = new FormData(); formData.append("file", file); rapidApi .post("/upload", formData, { headers: { "Content-Type": "multipart/form-data" } }) .then((res) => { const data = res.data; if (res.status >= 200 && res.status < 400) { const url = `${apiBaseUrl}/download/file?inline=true&fileKey=${data.fileKey}&fileName=${encodeURIComponent(file.name)}`; insertFn(url, file.name, url); resolve("ok"); } else { throw new Error(data?.message || res.statusText || "图片上传失败"); } }) .catch((err) => { const res = err.response; throw new Error(res?.data?.message || res?.statusText || "图片上传失败"); }); }); }, }, }, }, props.editorConfig || {}, ), [props.editorConfig], ); // 及时销毁 editor ,重要! useEffect(() => { return () => { if (editor == null) return; editor.destroy(); setEditor(null); }; }, [editor]); useEffect(() => { if (editor) { if (props.editorConfig?.readOnly) { editor.disable(); } else { editor.enable(); } } }, [props.editorConfig?.readOnly]); let disabeldClassName = ""; if (props.editorConfig?.readOnly && props.mode !== "display") { disabeldClassName = "rui-rich-text-editor-disabled"; } return (
{!props.hideToolbar && } { const value = editor.getHtml(); props.onChange?.(value === "


" ? undefined : value); }} mode="default" />
); }); export default RapidEditor;