index.tsx 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. import React, {
  2. useCallback,
  3. useEffect,
  4. useMemo,
  5. useRef,
  6. useState,
  7. } from "react";
  8. import styles from "./index.module.scss";
  9. // 引入编辑器组件
  10. // 安装---npm install braft-editor --save --force
  11. // npm install braft-utils --save --force
  12. import { ContentUtils } from "braft-utils";
  13. import BraftEditor from "braft-editor";
  14. // 引入编辑器样式
  15. import "braft-editor/dist/index.css";
  16. import classNames from "classnames";
  17. import { MessageFu } from "@/utils/message";
  18. import { fileDomInitialFu } from "@/utils/domShow";
  19. import { baseURL } from "@/utils/http";
  20. import { forwardRef, useImperativeHandle } from "react";
  21. import { API_upFile } from "@/store/action/layout";
  22. type Props = {
  23. check: boolean; //表单校验,为fasle表示不校验
  24. dirCode: string; //文件的code码
  25. isLook: boolean; //是否是查看进来
  26. ref: any; //当前自己的ref,给父组件调用
  27. myUrl: string; //上传的api地址
  28. full?: boolean;
  29. };
  30. function ZRichText({ check, dirCode, isLook, myUrl, full }: Props, ref: any) {
  31. // 添加 上传 图片的dom
  32. useEffect(() => {
  33. if (!isLook) {
  34. setTimeout(() => {
  35. const dom = document.querySelector(".bf-controlbar")!;
  36. const div = document.createElement("div");
  37. div.className = "upImgBox";
  38. // div.title = "上传图片";
  39. div.innerHTML = "上传图片";
  40. div.onclick = async () => {
  41. myInput.current?.click();
  42. };
  43. dom.appendChild(div);
  44. }, 200);
  45. // 监听 富文本 的 class 变化,在全屏的时候会 富文本会添加上 fullscreen 的类
  46. // 修复顶部样式冲突问题
  47. const editorDom = document.querySelector(
  48. ".bf-container"
  49. ) as HTMLDivElement;
  50. const observer = new MutationObserver(() => {
  51. // console.log("change");
  52. const dom = document.querySelector(".layoutRightTop") as HTMLDivElement;
  53. if (editorDom.className.includes("fullscreen")) dom.style.zIndex = "-1";
  54. else dom.style.zIndex = "100";
  55. });
  56. observer.observe(editorDom, {
  57. attributes: true,
  58. });
  59. // 销毁监听
  60. return () => {
  61. observer.disconnect();
  62. };
  63. }
  64. }, [isLook]);
  65. // 编辑器文本
  66. const [editorValue, setEditorValue] = useState(
  67. // 初始内容
  68. BraftEditor.createEditorState("")
  69. );
  70. // 判断 富文本是否为空
  71. const isTxtFlag = useMemo(() => {
  72. const txt: string = editorValue.toHTML();
  73. if (
  74. txt.replaceAll("<p>", "").replaceAll("</p>", "").replaceAll(" ", "") ===
  75. ""
  76. ) {
  77. return true;
  78. } else return false;
  79. }, [editorValue]);
  80. const myInput = useRef<HTMLInputElement>(null);
  81. // 上传图片
  82. const handeUpPhoto = useCallback(
  83. async (e: React.ChangeEvent<HTMLInputElement>) => {
  84. if (e.target.files) {
  85. // 拿到files信息
  86. const filesInfo = e.target.files[0];
  87. // 校验格式
  88. const type = ["image/jpeg", "image/png"];
  89. if (!type.includes(filesInfo.type)) {
  90. e.target.value = "";
  91. return MessageFu.warning("只支持png、jpg和jpeg格式!");
  92. }
  93. // 校验大小
  94. if (filesInfo.size > 5 * 1024 * 1024) {
  95. e.target.value = "";
  96. return MessageFu.warning("最大支持5M!");
  97. }
  98. // 创建FormData对象
  99. const fd = new FormData();
  100. // 把files添加进FormData对象(‘photo’为后端需要的字段)
  101. fd.append("type", "img");
  102. fd.append("dirCode", dirCode);
  103. fd.append("file", filesInfo);
  104. e.target.value = "";
  105. try {
  106. const res = await API_upFile(fd, myUrl);
  107. if (res.code === 0) {
  108. MessageFu.success("上传成功!");
  109. // 在光标位置插入图片
  110. const newTxt = ContentUtils.insertMedias(editorValue, [
  111. {
  112. type: "IMAGE",
  113. url: baseURL + res.data.filePath,
  114. },
  115. ]);
  116. setEditorValue(newTxt);
  117. }
  118. fileDomInitialFu();
  119. } catch (error) {
  120. fileDomInitialFu();
  121. }
  122. }
  123. },
  124. [dirCode, editorValue, myUrl]
  125. );
  126. // 让父组件调用的 回显 富文本
  127. const ritxtShowFu = useCallback((val: string) => {
  128. setEditorValue(BraftEditor.createEditorState(val));
  129. }, []);
  130. // 让父组件调用的返回 富文本信息 和 表单校验
  131. const fatherBtnOkFu = useCallback(() => {
  132. return { val: editorValue.toHTML(), flag: isTxtFlag };
  133. }, [editorValue, isTxtFlag]);
  134. // 可以让父组件调用子组件的方法
  135. useImperativeHandle(ref, () => ({
  136. ritxtShowFu,
  137. fatherBtnOkFu,
  138. }));
  139. return (
  140. <div
  141. className={styles.ZRichText}
  142. style={{ width: full ? "100%" : "", height: full ? "100%" : "" }}
  143. >
  144. <input
  145. id="upInput"
  146. type="file"
  147. accept=".png,.jpg,.jpeg"
  148. ref={myInput}
  149. onChange={(e) => handeUpPhoto(e)}
  150. />
  151. <div className="txtBox" style={{ height: full ? "100%" : "" }}>
  152. <BraftEditor
  153. readOnly={isLook}
  154. placeholder="请输入内容"
  155. value={editorValue}
  156. onChange={(e) => setEditorValue(e)}
  157. imageControls={["remove"]}
  158. />
  159. </div>
  160. <div
  161. className={classNames(
  162. "noUpThumb",
  163. check && isTxtFlag ? "noUpThumbAc" : ""
  164. )}
  165. >
  166. 请输入正文!
  167. </div>
  168. </div>
  169. );
  170. }
  171. // const MemoZRichText = React.memo(ZRichText);
  172. export default forwardRef(ZRichText);