Browse Source

fix: editor upload image

chenlei 1 năm trước cách đây
mục cha
commit
397b1d9123

+ 1 - 0
package.json

@@ -18,6 +18,7 @@
     "@types/react": "^18.2.16",
     "@types/react-dom": "^18.2.6",
     "antd": "^5.8.2",
+    "axios": "^1.6.2",
     "babel-jest": "^27.4.2",
     "babel-loader": "^8.2.3",
     "babel-plugin-named-asset-import": "^0.3.8",

+ 26 - 0
pnpm-lock.yaml

@@ -50,6 +50,9 @@ dependencies:
   antd:
     specifier: ^5.8.2
     version: 5.8.2(react-dom@18.2.0)(react@18.2.0)
+  axios:
+    specifier: ^1.6.2
+    version: 1.6.2
   babel-jest:
     specifier: ^27.4.2
     version: 27.4.2(@babel/core@7.16.0)
@@ -3508,6 +3511,16 @@ packages:
     engines: {node: '>=4'}
     dev: false
 
+  /axios@1.6.2:
+    resolution: {integrity: sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A==}
+    dependencies:
+      follow-redirects: 1.15.3
+      form-data: 4.0.0
+      proxy-from-env: 1.1.0
+    transitivePeerDependencies:
+      - debug
+    dev: false
+
   /axobject-query@3.2.1:
     resolution: {integrity: sha512-jsyHu61e6N4Vbz/v18DHwWYKK0bSWLqn47eeDSKPB7m8tqMHF9YJ+mhIk2lVteyZrY8tnSj/jHOv4YiTCuCJgg==}
     dependencies:
@@ -5785,6 +5798,15 @@ packages:
       mime-types: 2.1.35
     dev: false
 
+  /form-data@4.0.0:
+    resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==}
+    engines: {node: '>= 6'}
+    dependencies:
+      asynckit: 0.4.0
+      combined-stream: 1.0.8
+      mime-types: 2.1.35
+    dev: false
+
   /forwarded@0.2.0:
     resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==}
     engines: {node: '>= 0.6'}
@@ -8854,6 +8876,10 @@ packages:
       ipaddr.js: 1.9.1
     dev: false
 
+  /proxy-from-env@1.1.0:
+    resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==}
+    dev: false
+
   /psl@1.9.0:
     resolution: {integrity: sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==}
     dev: false

+ 4 - 1
src/App.tsx

@@ -2,7 +2,7 @@ import React from "react";
 import { Route, Routes } from "react-router-dom";
 import { ConfigProvider } from "antd";
 import zhCN from "antd/lib/locale/zh_CN";
-import { MemoSpinLoding } from "./components";
+import { MemoSpinLoding, UpAsyncLoding } from "./components";
 import theme from "./theme.scss";
 import "./App.scss";
 import "./configure";
@@ -33,6 +33,9 @@ function App() {
           </Routes>
         </React.Suspense>
       </ConfigProvider>
+
+      {/* 上传附件的进度条元素 */}
+      <UpAsyncLoding />
     </div>
   );
 }

+ 41 - 0
src/components/UpAsyncLoading/index.module.scss

@@ -0,0 +1,41 @@
+.UpAsyncLoding {
+  opacity: 0;
+  pointer-events: none;
+  position: fixed;
+  z-index: 10000;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  background-color: rgba(0, 0, 0, 0.4);
+
+  :global {
+    .progressBox {
+      position: absolute;
+      top: 60%;
+      left: 50%;
+      transform: translate(-50%, -50%);
+      width: 500px;
+      height: 6px;
+      border-radius: 3px;
+      border: 1px solid var(--primary-color);
+      overflow: hidden;
+
+      #progress {
+        position: absolute;
+        top: 0;
+        left: 0;
+        width: 0%;
+        height: 100%;
+        background-color: var(--primary-color);
+      }
+    }
+
+    .closeUpBtn {
+      position: absolute;
+      top: 70%;
+      left: 50%;
+      transform: translate(-50%, -50%);
+    }
+  }
+}

+ 36 - 0
src/components/UpAsyncLoading/index.tsx

@@ -0,0 +1,36 @@
+import store, { RootState } from "@/store";
+import { Button } from "antd";
+import React, { useCallback } from "react";
+import { useSelector } from "react-redux";
+import styles from "./index.module.scss";
+function _UpAsyncLoding() {
+  // 从仓库中获取取消上传的函数
+  const closeUpFile = useSelector(
+    (state: RootState) => state.layout.closeUpFile
+  );
+
+  const btnClose = useCallback(() => {
+    closeUpFile.fu();
+
+    setTimeout(() => {
+      store.dispatch({
+        type: "layout/closeUpFile",
+        payload: { fu: () => {}, state: false },
+      });
+    }, 200);
+  }, [closeUpFile]);
+
+  return (
+    <div id="UpAsyncLoding" className={styles.UpAsyncLoding}>
+      <div className="progressBox">
+        <div id="progress"></div>
+      </div>
+      {/* 手动取消上传按钮 */}
+      <div className="closeUpBtn">
+        <Button onClick={btnClose}>取消上传</Button>
+      </div>
+    </div>
+  );
+}
+
+export const UpAsyncLoding = React.memo(_UpAsyncLoding);

+ 1 - 1
src/components/Z_RichText/index.module.scss

@@ -16,7 +16,7 @@
           bottom: 13px;
           right: 15px;
           cursor: pointer;
-          color: var(--themeColor);
+          color: var(--primary-color);
         }
 
         .upImgBoxNo {

+ 46 - 17
src/components/Z_RichText/index.tsx

@@ -6,13 +6,14 @@ import React, {
   useState,
 } from "react";
 import styles from "./index.module.scss";
+import axios from "axios";
 
 // 引入编辑器组件
 
 // 安装---npm install braft-editor --save --force
 // npm install braft-utils --save --force
 // @ts-ignore
-// import { ContentUtils } from "braft-utils";
+import { ContentUtils } from "braft-utils";
 // @ts-ignore
 import BraftEditor from "braft-editor";
 // 引入编辑器样式
@@ -22,6 +23,9 @@ import classNames from "classnames";
 
 import { forwardRef, useImperativeHandle } from "react";
 import { message } from "antd";
+import { domShowFu, fileDomInitialFu, progressDomFu } from "@/utils/domShow";
+import store from "@/store";
+import http from "@/utils/http";
 
 type Props = {
   check?: boolean; //表单校验,为fasle表示不校验
@@ -33,6 +37,28 @@ type Props = {
   onChange?(v: any): void;
 };
 
+const CancelToken = axios.CancelToken;
+
+export const A1_APIupFile = (data: any, url: string) => {
+  domShowFu("#UpAsyncLoding", true);
+
+  return http.post(url, data, {
+    timeout: 0,
+    // 显示进度条
+    onUploadProgress: (e: any) => {
+      const complete = (e.loaded / e.total) * 100 || 0;
+      progressDomFu(complete + "%");
+    },
+    // 取消上传
+    cancelToken: new CancelToken(function executor(c) {
+      store.dispatch({
+        type: "layout/closeUpFile",
+        payload: { fu: c, state: true },
+      });
+    }),
+  });
+};
+
 function RichText(
   { check, dirCode, isLook, myUrl, onChange }: Props,
   ref: any
@@ -126,24 +152,27 @@ function RichText(
         e.target.value = "";
 
         try {
-          console.log(fd, myUrl);
-          // const res = await A1_APIupFile(fd, myUrl);
-          // if (res.code === 0) {
-          //   messageApi.success("上传成功!");
-          //   // 在光标位置插入图片
-          //   const newTxt = ContentUtils.insertMedias(editorValue, [
-          //     {
-          //       type: "IMAGE",
-          //       url: process.env.REACT_APP_API_URL + res.data.filePath,
-          //     },
-          //   ]);
-
-          //   setEditorValue(newTxt);
-          // }
-        } catch (error) {}
+          const res = await A1_APIupFile(fd, myUrl);
+          // @ts-ignore
+          if (res.code === 0) {
+            messageApi.success("上传成功!");
+            // 在光标位置插入图片
+            const newTxt = ContentUtils.insertMedias(editorValue, [
+              {
+                type: "IMAGE",
+                url: `${process.env.REACT_APP_API_URL}${process.env.REACT_APP_IMG_PUBLIC}${res.data.filePath}`,
+              },
+            ]);
+
+            setEditorValue(newTxt);
+          }
+          fileDomInitialFu();
+        } catch (error) {
+          fileDomInitialFu();
+        }
       }
     },
-    [dirCode, myUrl, messageApi]
+    [dirCode, myUrl, messageApi, editorValue]
   );
 
   // 让父组件调用的 回显 富文本

+ 1 - 0
src/components/index.ts

@@ -1,2 +1,3 @@
 export * from "./SpinLoding";
 export * from "./FormPageFooter";
+export * from "./UpAsyncLoading";

+ 5 - 1
src/pages/Information/index.tsx

@@ -85,8 +85,12 @@ export default function InformationPage() {
     () =>
       debounce((changedVal: unknown, vals: any) => {
         setParams({ ...params, ...vals });
+        navigate("/information", {
+          replace: true,
+          state: vals,
+        });
       }, 500),
-    [params]
+    [params, navigate]
   );
 
   const paginationChange = useCallback(

+ 2 - 0
src/store/reducer/index.ts

@@ -1,10 +1,12 @@
 // 导入合并reducer的依赖
 import { combineReducers } from "redux";
 import base from "./base";
+import layout from "./layout";
 
 // 合并 reducer
 const rootReducer = combineReducers({
   base,
+  layout,
 });
 
 // 默认导出

+ 73 - 0
src/store/reducer/layout.ts

@@ -0,0 +1,73 @@
+export type LookDomType = {
+  src: string;
+  type: "video" | "audio" | "model" | "";
+};
+
+export type MessageType = {
+  txt: string;
+  type: "info" | "success" | "error" | "warning";
+  duration: number;
+};
+
+// 初始化状态
+const initState = {
+  // 所有图片点击预览查看大图
+  lookBigImg: {
+    url: "",
+    show: false,
+  },
+  // 查看视频、音频、模型
+  lookDom: {
+    src: "",
+    type: "",
+  } as LookDomType,
+
+  // antd轻提示(兼容360浏览器)
+  message: {
+    txt: "",
+    type: "info",
+    duration: 3,
+  } as MessageType,
+  // 上传文件点击取消
+  closeUpFile: {
+    fu: () => {},
+    state: false,
+  },
+};
+
+// 定义 action 类型
+type LayoutActionType =
+  | { type: "layout/lookBigImg"; payload: { url: string; show: boolean } }
+  | { type: "layout/lookDom"; payload: LookDomType }
+  | { type: "layout/message"; payload: MessageType }
+  | {
+      type: "layout/closeUpFile";
+      payload: {
+        fu: () => void;
+        state: boolean;
+      };
+    };
+
+// 频道 reducer
+export default function layoutReducer(
+  state = initState,
+  action: LayoutActionType
+) {
+  switch (action.type) {
+    // 所有图片点击预览查看大图
+    case "layout/lookBigImg":
+      return { ...state, lookBigImg: action.payload };
+    // 查看视频
+    case "layout/lookDom":
+      return { ...state, lookDom: action.payload };
+
+    // antd轻提示(兼容360浏览器)
+    case "layout/message":
+      return { ...state, message: action.payload };
+    // 上传文件点击取消
+    case "layout/closeUpFile":
+      return { ...state, closeUpFile: action.payload };
+    default:
+      return state;
+  }
+}

+ 35 - 0
src/utils/domShow.ts

@@ -0,0 +1,35 @@
+import store from "@/store";
+
+// 加载和上传的盒子的显示隐藏
+export const domShowFu = (ele: string, val: boolean) => {
+  const dom: HTMLDivElement = document.querySelector(ele)!;
+  if (val) {
+    dom.style.opacity = "1";
+    dom.style.pointerEvents = "auto";
+  } else {
+    dom.style.opacity = "0";
+    dom.style.pointerEvents = "none";
+  }
+};
+
+// 上传附件的进度条
+let progressDom: HTMLDivElement = document.querySelector("#progress")!;
+export const progressDomFu = (val: string) => {
+  if (!progressDom) progressDom = document.querySelector("#progress")!;
+  progressDom.style.width = val;
+};
+
+// 上传附件的dom操作
+export const fileDomInitialFu = () => {
+  // 隐藏进度条的dom
+  domShowFu("#UpAsyncLoding", false);
+  progressDomFu("0%");
+  // 初始化 上传附件 的状态
+  setTimeout(() => {
+    if (store.getState().layout.closeUpFile.state)
+      store.dispatch({
+        type: "layout/closeUpFile",
+        payload: { fu: () => {}, state: false },
+      });
+  }, 200);
+};

+ 79 - 0
src/utils/http.ts

@@ -0,0 +1,79 @@
+import axios from "axios";
+import store from "@/store";
+import { getTokenInfo, removeTokenInfo } from "@dage/pc-components";
+import { MessageFu } from "./message";
+
+// 处理  类型“AxiosResponse<any, any>”上不存在属性“code”
+declare module "axios" {
+  interface AxiosResponse {
+    code: number;
+    // 这里追加你的参数
+  }
+}
+
+// 创建 axios 实例
+const http = axios.create({
+  // --------线下的地址不用加/api/
+  // baseURL: baseURL,
+
+  // --------打包或线上环境接口需要加上api/
+  baseURL: process.env.REACT_APP_API_URL,
+  timeout: 5000,
+});
+
+let axajInd = 0;
+
+// 请求拦截器
+http.interceptors.request.use(
+  function (config: any) {
+    axajInd++;
+
+    const { token } = getTokenInfo();
+    if (token) config.headers.token = token;
+    return config;
+  },
+  function (err) {
+    return Promise.reject(err);
+  }
+);
+
+let timeId = -1;
+
+// 响应拦截器
+http.interceptors.response.use(
+  function (response) {
+    // 请求回来的关闭加载提示
+    axajInd--;
+    if (axajInd === 0) {
+    }
+    if (response.data.code === 5001 || response.data.code === 5002) {
+      clearTimeout(timeId);
+      timeId = window.setTimeout(() => {
+        removeTokenInfo();
+        MessageFu.warning("登录失效!");
+      }, 200);
+    } else if (response.data.code === 0) {
+      // MessageFu.success(response.data.msg);
+    } else if (response.data.code !== 3014)
+      MessageFu.warning(response.data.msg);
+
+    return response.data;
+  },
+  async function (err) {
+    clearTimeout(timeId);
+    timeId = window.setTimeout(() => {
+      axajInd = 0;
+      // 如果因为网络原因,response没有,给提示消息
+      if (!err.response) {
+        if (store.getState().layout.closeUpFile.state)
+          MessageFu.warning("取消上传!");
+        else MessageFu.error("网络繁忙,请稍后重试!");
+      } else MessageFu.error("响应错误,请联系管理员!");
+    }, 100);
+
+    return Promise.reject(err);
+  }
+);
+
+// 导出 axios 实例
+export default http;

+ 50 - 0
src/utils/message.ts

@@ -0,0 +1,50 @@
+import store from "@/store";
+
+export type MessageType = {
+  txt: string;
+  type: "info" | "success" | "error" | "warning";
+  duration: number;
+};
+
+export const MessageFu = {
+  info: (txt: string, duration?: number) => {
+    store.dispatch({
+      type: "layout/message",
+      payload: {
+        txt,
+        type: "info",
+        duration: duration === undefined ? 3 : duration,
+      },
+    });
+  },
+  success: (txt: string, duration?: number) => {
+    store.dispatch({
+      type: "layout/message",
+      payload: {
+        txt,
+        type: "success",
+        duration: duration === undefined ? 3 : duration,
+      },
+    });
+  },
+  error: (txt: string, duration?: number) => {
+    store.dispatch({
+      type: "layout/message",
+      payload: {
+        txt,
+        type: "error",
+        duration: duration === undefined ? 3 : duration,
+      },
+    });
+  },
+  warning: (txt: string, duration?: number) => {
+    store.dispatch({
+      type: "layout/message",
+      payload: {
+        txt,
+        type: "warning",
+        duration: duration === undefined ? 3 : duration,
+      },
+    });
+  },
+};