shaogen1995 2 лет назад
Родитель
Сommit
0fd382d719

+ 71 - 0
package-lock.json

@@ -26,6 +26,7 @@
         "react-redux": "^8.0.4",
         "react-router-dom": "5.3",
         "react-scripts": "5.0.1",
+        "react-sortablejs": "^6.1.4",
         "redux": "^4.2.0",
         "redux-devtools-extension": "^2.13.9",
         "redux-thunk": "^2.4.1",
@@ -4065,6 +4066,12 @@
         "@types/node": "*"
       }
     },
+    "node_modules/@types/sortablejs": {
+      "version": "1.15.1",
+      "resolved": "https://registry.npmmirror.com/@types/sortablejs/-/sortablejs-1.15.1.tgz",
+      "integrity": "sha512-g/JwBNToh6oCTAwNS8UGVmjO7NLDKsejVhvE4x1eWiPTC3uCuNsa/TD4ssvX3du+MLiM+SHPNDuijp8y76JzLQ==",
+      "peer": true
+    },
     "node_modules/@types/stack-utils": {
       "version": "2.0.1",
       "resolved": "https://registry.npmmirror.com/@types/stack-utils/-/stack-utils-2.0.1.tgz",
@@ -14413,6 +14420,31 @@
         }
       }
     },
+    "node_modules/react-sortablejs": {
+      "version": "6.1.4",
+      "resolved": "https://registry.npmmirror.com/react-sortablejs/-/react-sortablejs-6.1.4.tgz",
+      "integrity": "sha512-fc7cBosfhnbh53Mbm6a45W+F735jwZ1UFIYSrIqcO/gRIFoDyZeMtgKlpV4DdyQfbCzdh5LoALLTDRxhMpTyXQ==",
+      "dependencies": {
+        "classnames": "2.3.1",
+        "tiny-invariant": "1.2.0"
+      },
+      "peerDependencies": {
+        "@types/sortablejs": "1",
+        "react": ">=16.9.0",
+        "react-dom": ">=16.9.0",
+        "sortablejs": "1"
+      }
+    },
+    "node_modules/react-sortablejs/node_modules/classnames": {
+      "version": "2.3.1",
+      "resolved": "https://registry.npmmirror.com/classnames/-/classnames-2.3.1.tgz",
+      "integrity": "sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA=="
+    },
+    "node_modules/react-sortablejs/node_modules/tiny-invariant": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmmirror.com/tiny-invariant/-/tiny-invariant-1.2.0.tgz",
+      "integrity": "sha512-1Uhn/aqw5C6RI4KejVeTg6mIS7IqxnLJ8Mv2tV5rTc0qWobay7pDUz6Wi392Cnc8ak1H0F2cjoRzb2/AW4+Fvg=="
+    },
     "node_modules/read-cache": {
       "version": "1.0.0",
       "resolved": "https://registry.npmmirror.com/read-cache/-/read-cache-1.0.0.tgz",
@@ -15202,6 +15234,12 @@
         "websocket-driver": "^0.7.4"
       }
     },
+    "node_modules/sortablejs": {
+      "version": "1.15.0",
+      "resolved": "https://registry.npmmirror.com/sortablejs/-/sortablejs-1.15.0.tgz",
+      "integrity": "sha512-bv9qgVMjUMf89wAvM6AxVvS/4MX3sPeN0+agqShejLU5z5GX4C75ow1O2e5k4L6XItUyAK3gH6AxSbXrOM5e8w==",
+      "peer": true
+    },
     "node_modules/source-list-map": {
       "version": "2.0.1",
       "resolved": "https://registry.npmmirror.com/source-list-map/-/source-list-map-2.0.1.tgz",
@@ -20071,6 +20109,12 @@
         "@types/node": "*"
       }
     },
+    "@types/sortablejs": {
+      "version": "1.15.1",
+      "resolved": "https://registry.npmmirror.com/@types/sortablejs/-/sortablejs-1.15.1.tgz",
+      "integrity": "sha512-g/JwBNToh6oCTAwNS8UGVmjO7NLDKsejVhvE4x1eWiPTC3uCuNsa/TD4ssvX3du+MLiM+SHPNDuijp8y76JzLQ==",
+      "peer": true
+    },
     "@types/stack-utils": {
       "version": "2.0.1",
       "resolved": "https://registry.npmmirror.com/@types/stack-utils/-/stack-utils-2.0.1.tgz",
@@ -27961,6 +28005,27 @@
         "workbox-webpack-plugin": "^6.4.1"
       }
     },
+    "react-sortablejs": {
+      "version": "6.1.4",
+      "resolved": "https://registry.npmmirror.com/react-sortablejs/-/react-sortablejs-6.1.4.tgz",
+      "integrity": "sha512-fc7cBosfhnbh53Mbm6a45W+F735jwZ1UFIYSrIqcO/gRIFoDyZeMtgKlpV4DdyQfbCzdh5LoALLTDRxhMpTyXQ==",
+      "requires": {
+        "classnames": "2.3.1",
+        "tiny-invariant": "1.2.0"
+      },
+      "dependencies": {
+        "classnames": {
+          "version": "2.3.1",
+          "resolved": "https://registry.npmmirror.com/classnames/-/classnames-2.3.1.tgz",
+          "integrity": "sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA=="
+        },
+        "tiny-invariant": {
+          "version": "1.2.0",
+          "resolved": "https://registry.npmmirror.com/tiny-invariant/-/tiny-invariant-1.2.0.tgz",
+          "integrity": "sha512-1Uhn/aqw5C6RI4KejVeTg6mIS7IqxnLJ8Mv2tV5rTc0qWobay7pDUz6Wi392Cnc8ak1H0F2cjoRzb2/AW4+Fvg=="
+        }
+      }
+    },
     "read-cache": {
       "version": "1.0.0",
       "resolved": "https://registry.npmmirror.com/read-cache/-/read-cache-1.0.0.tgz",
@@ -28584,6 +28649,12 @@
         "websocket-driver": "^0.7.4"
       }
     },
+    "sortablejs": {
+      "version": "1.15.0",
+      "resolved": "https://registry.npmmirror.com/sortablejs/-/sortablejs-1.15.0.tgz",
+      "integrity": "sha512-bv9qgVMjUMf89wAvM6AxVvS/4MX3sPeN0+agqShejLU5z5GX4C75ow1O2e5k4L6XItUyAK3gH6AxSbXrOM5e8w==",
+      "peer": true
+    },
     "source-list-map": {
       "version": "2.0.1",
       "resolved": "https://registry.npmmirror.com/source-list-map/-/source-list-map-2.0.1.tgz",

+ 1 - 0
package.json

@@ -21,6 +21,7 @@
     "react-redux": "^8.0.4",
     "react-router-dom": "5.3",
     "react-scripts": "5.0.1",
+    "react-sortablejs": "^6.1.4",
     "redux": "^4.2.0",
     "redux-devtools-extension": "^2.13.9",
     "redux-thunk": "^2.4.1",

+ 3 - 0
src/App.tsx

@@ -11,6 +11,7 @@ import { useSelector } from "react-redux";
 import store, { RootState } from "./store";
 import UpAsyncLoding from "./components/UpAsyncLoding";
 import MessageCom from "./components/Message";
+import LookDom from "./components/LookDom";
 const Layout = React.lazy(() => import("./pages/Layout"));
 const Login = React.lazy(() => import("./pages/Login"));
 
@@ -54,6 +55,8 @@ export default function App() {
       {/* 上传附件的进度条元素 */}
       <UpAsyncLoding />
 
+      {/* 查看视频音频 */}
+      <LookDom />
 
       {/* antd 轻提示 ---兼容360浏览器 */}
       <MessageCom />

+ 65 - 0
src/components/LookDom/index.module.scss

@@ -0,0 +1,65 @@
+.LookDom {
+  transition: opacity .3s;
+  position: fixed;
+  z-index: 9991;
+  opacity: 0;
+  pointer-events: none;
+  top: 0;
+  left: 0;
+  width: 100vw;
+  height: 100vh;
+  background-color: rgba(0, 0, 0, .6);
+
+  :global {
+    .close {
+      color: #fff;
+      position: absolute;
+      right: 70px;
+      top: 70px;
+      font-size: 30px;
+      cursor: pointer;
+      z-index: 10;
+    }
+
+    .viedoBox {
+      position: absolute;
+      top: 50%;
+      left: 50%;
+      transform: translate(-50%, -50%);
+      width: 800px;
+      height: 500px;
+
+      video {
+        width: 100%;
+        height: 100%;
+      }
+    }
+
+    .audioBox {
+      position: absolute;
+      top: 50%;
+      left: 50%;
+      transform: translate(-50%, -50%);
+      width: 500px;
+      height: 60px;
+
+      audio {
+        width: 100%;
+        height: 100%;
+      }
+    }
+
+    .modelBox {
+      position: absolute;
+      top: 0;
+      left: 0;
+      width: 100%;
+      height: 100%;
+
+      iframe {
+        width: 100%;
+        height: 100%;
+      }
+    }
+  }
+}

+ 52 - 0
src/components/LookDom/index.tsx

@@ -0,0 +1,52 @@
+/* eslint-disable jsx-a11y/iframe-has-title */
+import React from "react";
+import { CloseCircleOutlined } from "@ant-design/icons";
+import styles from "./index.module.scss";
+import { useSelector } from "react-redux";
+import store, { RootState } from "@/store";
+import { baseURL } from "@/utils/http";
+function LookDom() {
+  const { src, type } = useSelector(
+    (state: RootState) => state.A0Layout.lookDom
+  );
+  return (
+    <div
+      className={styles.LookDom}
+      style={src ? { opacity: 1, pointerEvents: "auto" } : {}}
+    >
+      {src ? (
+        <>
+          {type === "video" ? (
+            <div className="viedoBox">
+              <video autoPlay controls src={baseURL + src}></video>
+            </div>
+          ) : type === "audio" ? (
+            <div className="audioBox">
+              <audio autoPlay controls src={baseURL + src}></audio>
+            </div>
+          ) : (
+            <div className="modelBox">
+              <iframe src={`model.html?m=${src}`}></iframe>
+            </div>
+          )}
+
+          <div
+            className="close"
+            onClick={() =>
+              store.dispatch({
+                type: "layout/lookDom",
+                payload: { src: "", type: "" },
+              })
+            }
+          >
+            <CloseCircleOutlined />
+          </div>
+        </>
+      ) : null}
+    </div>
+  );
+}
+
+const MemoLookDom = React.memo(LookDom);
+
+export default MemoLookDom;

+ 106 - 0
src/components/Z1upImgs/index.module.scss

@@ -0,0 +1,106 @@
+.Z1upImgs {
+  position: relative;
+  width: 100%;
+  height: 100%;
+
+  :global {
+    .fileBoxRow_up {
+      color: #a6a6a6;
+      border-radius: 6px;
+      cursor: pointer;
+      font-size: 30px;
+      width: 100px;
+      height: 100px;
+      border: 1px dashed #797979;
+      display: flex;
+      justify-content: center;
+      align-items: center;
+    }
+
+    .fileImgListBox {
+      display: flex;
+      flex-wrap: wrap;
+      margin-bottom: 10px;
+
+      &>div {
+        cursor: move;
+        margin: 15px 20px 0px 0;
+      }
+    }
+
+    .fileImgListBoxNo {
+      &>div {
+        cursor: default;
+      }
+    }
+
+    .fileBoxRow_r_img {
+      position: relative;
+
+      .fileImgListLDBox {
+        height: 26px;
+        background-color: rgba(0, 0, 0, .6);
+        color: #fff;
+        display: flex;
+        justify-content: space-around;
+        font-size: 16px;
+
+        &>a {
+          color: #fff;
+        }
+      }
+
+      .clearCover {
+        cursor: pointer;
+        position: absolute;
+        z-index: 99;
+        right: -10px;
+        top: -10px;
+        background-color: rgba(0, 0, 0, .8);
+        width: 20px;
+        height: 20px;
+        border-radius: 50%;
+        font-size: 16px;
+        color: #fff;
+        display: flex;
+        justify-content: center;
+        align-items: center;
+      }
+
+      .isCoverShow {
+        position: absolute;
+        z-index: 99;
+        bottom: 26px;
+        left: 0;
+        width: 100%;
+        background-color: rgba(0, 0, 0, .8);
+        color: #fff;
+        height: 26px;
+        display: flex;
+        justify-content: center;
+        align-items: center;
+      }
+    }
+
+    .fileTit {
+      font-size: 14px;
+      color: rgb(126, 124, 124);
+
+      .noUpThumb {
+        position: relative;
+        overflow: hidden;
+        opacity: 0;
+        transition: top .2s;
+        color: #ff4d4f;
+        top: -10px;
+      }
+
+
+
+      .noUpThumbAc {
+        top: 0;
+        opacity: 1;
+      }
+    }
+  }
+}

+ 222 - 0
src/components/Z1upImgs/index.tsx

@@ -0,0 +1,222 @@
+import React, { useCallback, useRef, useState } from "react";
+import styles from "./index.module.scss";
+import {
+  PlusOutlined,
+  EyeOutlined,
+  CloseOutlined,
+  DownloadOutlined,
+} from "@ant-design/icons";
+import { ReactSortable } from "react-sortablejs";
+import ImageLazy from "@/components/ImageLazy";
+
+import { Popconfirm } from "antd";
+import store from "@/store";
+import { baseURL } from "@/utils/http";
+import { MessageFu } from "@/utils/message";
+import { fileDomInitialFu } from "@/utils/domShow";
+import classNames from "classnames";
+import { API_upFile } from "@/store/action/layout";
+import { FileImgListType } from "@/types";
+import { forwardRef, useImperativeHandle } from "react";
+
+type Props = {
+  max: number; //最多传多少张图片
+  isLook: boolean; //是否是查看
+  ref: any; //当前自己的ref,给父组件调用
+  fileCheck: boolean;
+  size: number; //上传图片大小(M)
+  dirCode: string; //文件的code码
+  myUrl: string;
+  isCoverShow?: boolean; //是否显示第一张为封面
+  format?: string[]; //上传图片格式
+  formatTxt?: string; //上传图片提示
+  sizeTxt?: string; //后面的建议尺寸信息
+  fromData?: any;
+};
+
+function Z1upImgs(
+  {
+    max,
+    isLook,
+    fileCheck,
+    size,
+    dirCode,
+    myUrl,
+    isCoverShow = false,
+    format = ["image/jpeg", "image/png"],
+    formatTxt = "png、jpg和jpeg",
+    sizeTxt,
+    fromData,
+  }: Props,
+  ref: any
+) {
+  const [fileList, setFileList] = useState<FileImgListType[]>([]);
+
+  const myInput = useRef<HTMLInputElement>(null);
+
+  // 上传图片
+  const handeUpPhoto = useCallback(
+    async (e: React.ChangeEvent<HTMLInputElement>) => {
+      if (e.target.files) {
+        // 拿到files信息
+        const filesInfo = e.target.files[0];
+        // 校验格式
+        const type = format;
+        if (!type.includes(filesInfo.type)) {
+          e.target.value = "";
+          return MessageFu.warning(`只支持${formatTxt}格式!`);
+        }
+        // 校验大小
+        if (filesInfo.size > size * 1024 * 1024) {
+          e.target.value = "";
+          return MessageFu.warning(`最大支持${size}M!`);
+        }
+        // 创建FormData对象
+        const fd = new FormData();
+        // 把files添加进FormData对象(‘photo’为后端需要的字段)
+        fd.append("type", "img");
+        fd.append("dirCode", dirCode);
+        fd.append("typePath", "1");
+        fd.append("file", filesInfo);
+
+        if (fromData) {
+          for (const k in fromData) {
+            if (fromData[k]) fd.append(k, fromData[k]);
+          }
+        }
+
+        e.target.value = "";
+
+        try {
+          const res = await API_upFile(fd, myUrl);
+          if (res.code === 0) {
+            MessageFu.success("上传成功!");
+            setFileList([...fileList, res.data]);
+          }
+          fileDomInitialFu();
+        } catch (error) {
+          fileDomInitialFu();
+        }
+      }
+    },
+    [dirCode, fileList, format, formatTxt, fromData, myUrl, setFileList, size]
+  );
+
+  // 列表删除某一张图片
+  const delImgListFu = useCallback(
+    (id: number) => {
+      const newItems = fileList.filter((v) => v.id !== id);
+      setFileList(newItems);
+    },
+    [fileList, setFileList]
+  );
+
+  // 让父组件调用,拿到 附件信息
+  const imgIdsRes = useCallback(() => {
+    return fileList;
+  }, [fileList]);
+
+  // 可以让父组件调用子组件的方法
+  useImperativeHandle(ref, () => ({
+    imgIdsRes,
+  }));
+
+  return (
+    <div className={styles.Z1upImgs}>
+      <input
+        id="upInput"
+        type="file"
+        accept=".png,.jpg,.jpeg"
+        ref={myInput}
+        onChange={(e) => handeUpPhoto(e)}
+      />
+
+      <div
+        hidden={(fileList.length && fileList.length >= max) || isLook}
+        className="fileBoxRow_up"
+        onClick={() => myInput.current?.click()}
+      >
+        <PlusOutlined />
+      </div>
+
+      <ReactSortable
+        disabled={isLook}
+        className={classNames(
+          "fileImgListBox",
+          isLook ? "fileImgListBoxNo" : ""
+        )}
+        list={fileList}
+        setList={setFileList}
+      >
+        {fileList.map((v, i) => (
+          <div className="fileBoxRow_r_img" key={v.id}>
+            {v.filePath ? (
+              <ImageLazy noLook width={100} height={100} src={v.filePath} />
+            ) : null}
+
+            <Popconfirm
+              title="删除后无法恢复,是否删除?"
+              okText="删除"
+              cancelText="取消"
+              onConfirm={() => delImgListFu(v.id!)}
+            >
+              <div className="clearCover" hidden={isLook}>
+                <CloseOutlined />
+              </div>
+            </Popconfirm>
+
+            {/* 第一张为封面 */}
+            {isCoverShow && i === 0 ? (
+              <div className="isCoverShow">封 面</div>
+            ) : null}
+
+            {/* 下面的预览和下载 */}
+            <div className="fileImgListLDBox">
+              <EyeOutlined
+                onClick={() =>
+                  store.dispatch({
+                    type: "layout/lookBigImg",
+                    payload: {
+                      url: baseURL + v.filePath,
+                      show: true,
+                    },
+                  })
+                }
+              />
+              <a
+                href={baseURL + v.filePath}
+                download
+                target="_blank"
+                rel="noreferrer"
+              >
+                <DownloadOutlined />
+              </a>
+            </div>
+          </div>
+        ))}
+      </ReactSortable>
+      <div className="fileTit" hidden={isLook}>
+        {fileList.length && fileList.length >= 2 ? (
+          <>
+            按住鼠标可拖动图片调整顺序。
+            <br />
+          </>
+        ) : null}
+        支持{formatTxt}的图片格式;最大支持{size}M;最多支持{max}
+        张;第一张将作为封面
+        {sizeTxt ? `建议尺寸${sizeTxt}` : null}
+        <br />
+        <div
+          className={classNames(
+            "noUpThumb",
+            fileList.length <= 0 && fileCheck ? "noUpThumbAc" : ""
+          )}
+        >
+          请上传图片!
+        </div>
+      </div>
+    </div>
+  );
+}
+
+export default forwardRef(Z1upImgs);

+ 92 - 0
src/components/Z2upVideos/index.module.scss

@@ -0,0 +1,92 @@
+.Z2upVideos {
+  position: relative;
+  width: 100%;
+  height: 100%;
+
+  :global {
+
+    .fileImgListBox {
+      display: flex;
+      flex-wrap: wrap;
+      margin-bottom: 10px;
+
+      &>div {
+        cursor: move;
+        margin: 15px 20px 0px 0;
+      }
+    }
+
+    .fileImgListBoxNo {
+      &>div {
+        cursor: default;
+      }
+    }
+
+    .fileBoxRow_r_video {
+      position: relative;
+      width: 140px;
+      height: 126px;
+
+      video {
+        width: 100%;
+        height: 100%;
+      }
+
+      .fileImgListLDBox {
+        position: absolute;
+        bottom: 0;
+        left: 0;
+        z-index: 10;
+        width: 100%;
+        height: 26px;
+        background-color: rgba(0, 0, 0, .6);
+        color: #fff;
+        display: flex;
+        justify-content: space-around;
+        font-size: 16px;
+
+        &>a {
+          color: #fff;
+        }
+      }
+
+      .clearCover {
+        cursor: pointer;
+        position: absolute;
+        z-index: 99;
+        right: -10px;
+        top: -10px;
+        background-color: rgba(0, 0, 0, .8);
+        width: 20px;
+        height: 20px;
+        border-radius: 50%;
+        font-size: 16px;
+        color: #fff;
+        display: flex;
+        justify-content: center;
+        align-items: center;
+      }
+    }
+
+    .fileTit {
+      font-size: 14px;
+      color: rgb(126, 124, 124);
+
+      .noUpThumb {
+        position: relative;
+        overflow: hidden;
+        opacity: 0;
+        transition: top .2s;
+        color: #ff4d4f;
+        top: -10px;
+      }
+
+
+
+      .noUpThumbAc {
+        top: 0;
+        opacity: 1;
+      }
+    }
+  }
+}

+ 206 - 0
src/components/Z2upVideos/index.tsx

@@ -0,0 +1,206 @@
+import React, { useCallback, useRef, useState } from "react";
+import styles from "./index.module.scss";
+import { forwardRef, useImperativeHandle } from "react";
+import { FileImgListType } from "@/types";
+import { MessageFu } from "@/utils/message";
+import { API_upFile } from "@/store/action/layout";
+import { fileDomInitialFu } from "@/utils/domShow";
+import { Button, Popconfirm } from "antd";
+import {
+  EyeOutlined,
+  UploadOutlined,
+  CloseOutlined,
+  DownloadOutlined,
+} from "@ant-design/icons";
+import { ReactSortable } from "react-sortablejs";
+import classNames from "classnames";
+import store from "@/store";
+import { baseURL } from "@/utils/http";
+
+type Props = {
+  max: number; //最多传多少个视频
+  isLook: boolean; //是否是查看
+  ref: any; //当前自己的ref,给父组件调用
+  fileCheck: boolean;
+  size: number; //上传视频大小(M)
+  dirCode: string; //文件的code码
+  myUrl: string;
+  format?: string[]; //上传视频格式
+  formatTxt?: string; //上传视频提示
+  fromData?: any;
+};
+
+function Z2upVideos(
+  {
+    max,
+    isLook,
+    fileCheck,
+    size,
+    dirCode,
+    myUrl,
+    format = ["video/mp4"],
+    formatTxt = "mp4",
+    fromData,
+  }: Props,
+  ref: any
+) {
+  const [fileList, setFileList] = useState<FileImgListType[]>([]);
+
+  const myInput = useRef<HTMLInputElement>(null);
+
+  // 上传视频
+  const handeUpPhoto = useCallback(
+    async (e: React.ChangeEvent<HTMLInputElement>) => {
+      if (e.target.files) {
+        // 拿到files信息
+        const filesInfo = e.target.files[0];
+        // 校验格式
+        const type = format;
+        if (!type.includes(filesInfo.type)) {
+          e.target.value = "";
+          return MessageFu.warning(`只支持${formatTxt}格式!`);
+        }
+        // 校验大小
+        if (filesInfo.size > size * 1024 * 1024) {
+          e.target.value = "";
+          return MessageFu.warning(`最大支持${size}M!`);
+        }
+        // 创建FormData对象
+        const fd = new FormData();
+        // 把files添加进FormData对象(‘photo’为后端需要的字段)
+        fd.append("type", "img");
+        fd.append("dirCode", dirCode);
+        fd.append("typePath", "1");
+        fd.append("file", filesInfo);
+
+        if (fromData) {
+          for (const k in fromData) {
+            if (fromData[k]) fd.append(k, fromData[k]);
+          }
+        }
+
+        e.target.value = "";
+
+        try {
+          const res = await API_upFile(fd, myUrl);
+          if (res.code === 0) {
+            MessageFu.success("上传成功!");
+            setFileList([...fileList, res.data]);
+          }
+          fileDomInitialFu();
+        } catch (error) {
+          fileDomInitialFu();
+        }
+      }
+    },
+    [dirCode, fileList, format, formatTxt, fromData, myUrl, setFileList, size]
+  );
+
+  // 列表删除某一个视频
+  const delImgListFu = useCallback(
+    (id: number) => {
+      const newItems = fileList.filter((v) => v.id !== id);
+      setFileList(newItems);
+    },
+    [fileList, setFileList]
+  );
+
+  // 让父组件调用,拿到 附件信息
+  const videoIdsRes = useCallback(() => {
+    return fileList;
+  }, [fileList]);
+
+  // 可以让父组件调用子组件的方法
+  useImperativeHandle(ref, () => ({
+    videoIdsRes,
+  }));
+
+  return (
+    <div className={styles.Z2upVideos}>
+      <input
+        id="upInput"
+        type="file"
+        accept=".mp4"
+        ref={myInput}
+        onChange={(e) => handeUpPhoto(e)}
+      />
+      <div className="Z2Btn">
+        {fileList.length < max ? (
+          <Button
+            onClick={() => myInput.current?.click()}
+            icon={<UploadOutlined />}
+          >
+            上传
+          </Button>
+        ) : null}
+        <ReactSortable
+          disabled={isLook}
+          className={classNames(
+            "fileImgListBox",
+            isLook ? "fileImgListBoxNo" : ""
+          )}
+          list={fileList}
+          setList={setFileList}
+        >
+          {fileList.map((v) => (
+            <div className="fileBoxRow_r_video" key={v.id}>
+              {v.filePath ? <video src={baseURL + v.filePath} /> : null}
+              <Popconfirm
+                title="删除后无法恢复,是否删除?"
+                okText="删除"
+                cancelText="取消"
+                onConfirm={() => delImgListFu(v.id!)}
+              >
+                <div className="clearCover" hidden={isLook}>
+                  <CloseOutlined />
+                </div>
+              </Popconfirm>
+              {/* 下面的预览和下载 */}
+              <div className="fileImgListLDBox">
+                <EyeOutlined
+                  onClick={() =>
+                    store.dispatch({
+                      type: "layout/lookDom",
+                      payload: {
+                        src: v.filePath,
+                        type: "video",
+                      },
+                    })
+                  }
+                />
+                <a
+                  href={baseURL + v.filePath}
+                  download
+                  target="_blank"
+                  rel="noreferrer"
+                >
+                  <DownloadOutlined />
+                </a>
+              </div>
+            </div>
+          ))}
+        </ReactSortable>
+        <div className="fileTit" hidden={isLook}>
+          {fileList.length && fileList.length >= 2 ? (
+            <>
+              按住鼠标可拖动图片调整顺序。
+              <br />
+            </>
+          ) : null}
+          支持{formatTxt}的视频格式;最大支持{size}M;最多支持{max}个
+          <br />
+          <div
+            className={classNames(
+              "noUpThumb",
+              fileList.length <= 0 && fileCheck ? "noUpThumbAc" : ""
+            )}
+          >
+            请上传视频!
+          </div>
+        </div>
+      </div>
+    </div>
+  );
+}
+
+export default forwardRef(Z2upVideos);

Разница между файлами не показана из-за своего большого размера
+ 6287 - 0
src/pages/A1Project/A1Add/data.ts


+ 59 - 0
src/pages/A1Project/A1Add/index.module.scss

@@ -0,0 +1,59 @@
+.A1Add {
+  position: absolute;
+  top: 0;
+  left: 0;
+  z-index: 10;
+  width: 100%;
+  height: 100%;
+  background-color: #fff;
+  border-radius: 10px;
+
+  :global {
+
+    .A1AddMain {
+      width: 100%;
+      height: calc(100% - 60px);
+      overflow-y: auto;
+      padding: 20px 140px 20px 0px;
+    
+      .A1AddBtn {
+        margin-top: 30px;
+        width: 740px;
+        text-align: center;
+        position: absolute;
+        bottom: 10px;
+        left: 0;
+        z-index: 20;
+      }
+
+      .e_row {
+        padding-right: 50px;
+        display: flex;
+        font-size: 14px;
+        margin-bottom: 10px;
+
+        .e_rowL {
+          width: 128px;
+          text-align: right;
+
+          &>span {
+            color: #ff4d4f;
+            position: relative;
+            top: 3px;
+          }
+        }
+        .e_rowL2{
+          position: relative;
+          top: 4px;
+        }
+
+        .e_rowR {
+          width: calc(100% - 130px);
+        }
+
+      }
+    }
+
+
+  }
+}

+ 230 - 0
src/pages/A1Project/A1Add/index.tsx

@@ -0,0 +1,230 @@
+import React, { useCallback, useRef } from "react";
+import styles from "./index.module.scss";
+import {
+  Button,
+  Cascader,
+  DatePicker,
+  Form,
+  FormInstance,
+  Input,
+  InputNumber,
+  Popconfirm,
+  Select,
+} from "antd";
+import mapDataAll from "./data";
+import dayjs from "dayjs";
+import TextArea from "antd/es/input/TextArea";
+import Z1upImgs from "@/components/Z1upImgs";
+import Z2upVideos from "@/components/Z2upVideos";
+
+const { RangePicker } = DatePicker;
+
+const eeeeArr = [
+  {
+    value: "项目状态1",
+    label: "项目状态1",
+  },
+  {
+    value: "项目状态2",
+    label: "项目状态2",
+  },
+];
+
+type Props = {
+  pageType: { txt: string; id: number };
+  closeFu: () => void;
+};
+
+function A1Add({ pageType, closeFu }: Props) {
+  // 表单的ref
+  const FormBoxRef = useRef<FormInstance>(null);
+
+  // 没有通过校验
+  const onFinishFailed = useCallback(() => {}, []);
+
+  // 通过校验点击确定
+  const onFinish = useCallback(async (value: any) => {
+    console.log("通过校验", value);
+
+    if (value.ffff) {
+      console.log(
+        "修改日期格式",
+        dayjs(value.ffff[0]).format("YYYY-MM-DD"),
+        dayjs(value.ffff[1]).format("YYYY-MM-DD")
+      );
+    }
+
+    // 获取图片地址
+    const imgsRes = imgsRef.current.imgIdsRes();
+
+    console.log("获取图片地址", imgsRes);
+
+    // 获取视频地址
+    const videosRes = videosRef.current.videoIdsRes();
+    console.log("获取视频地址", videosRes);
+  }, []);
+
+  // 图片数组的ref
+  const imgsRef = useRef<any>(null);
+
+  // 视频数组的ref
+  const videosRef = useRef<any>(null);
+
+  return (
+    <div className={styles.A1Add}>
+      <div className="A1AddMain">
+        <Form
+          scrollToFirstError={true}
+          ref={FormBoxRef}
+          name="basic"
+          labelCol={{ span: 2 }}
+          onFinish={onFinish}
+          onFinishFailed={onFinishFailed}
+          autoComplete="off"
+        >
+          <Form.Item
+            label="项目编号"
+            name="aaaa"
+            rules={[{ required: true, message: "请输入项目编号!" }]}
+            getValueFromEvent={(e) => e.target.value.replace(/\s+/g, "")}
+          >
+            <Input
+              style={{ width: 600 }}
+              disabled={pageType.txt === "edit"}
+              maxLength={30}
+              showCount
+              placeholder="请输入内容,最多30字;不能重复"
+            />
+          </Form.Item>
+
+          <Form.Item
+            label="项目名称"
+            name="bbbb"
+            rules={[{ required: true, message: "请输入项目名称!" }]}
+            getValueFromEvent={(e) => e.target.value.replace(/\s+/g, "")}
+          >
+            <Input
+              style={{ width: 600 }}
+              maxLength={30}
+              showCount
+              placeholder="请输入内容,最多30字"
+            />
+          </Form.Item>
+
+          <Form.Item label="建设单位" name="cccc">
+            <Input
+              style={{ width: 600 }}
+              maxLength={30}
+              showCount
+              placeholder="请输入内容,最多30字"
+            />
+          </Form.Item>
+
+          <Form.Item label="项目地点" name="dddd">
+            <Cascader
+              style={{ width: 300 }}
+              options={mapDataAll}
+              placeholder="请选择省/市"
+            />
+          </Form.Item>
+
+          <Form.Item
+            label="项目状态"
+            name="eeee"
+            rules={[{ required: true, message: "请选择项目状态!" }]}
+          >
+            <Select
+              placeholder="请选择"
+              style={{ width: 300 }}
+              options={eeeeArr}
+            />
+          </Form.Item>
+
+          <Form.Item label="项目日期" name="ffff">
+            <RangePicker style={{ width: 300 }} />
+          </Form.Item>
+
+          <Form.Item label="项目简介" name="gggg">
+            <TextArea
+              style={{ width: 600 }}
+              autoSize
+              placeholder="请输入内容,最多500字"
+              showCount
+              maxLength={500}
+            />
+          </Form.Item>
+
+          <Form.Item label="项目金额" name="hhhh">
+            <InputNumber
+              style={{ width: 300 }}
+              addonAfter="¥"
+              maxLength={10}
+              placeholder="请输入数字,最多10位"
+            />
+          </Form.Item>
+          <div className="e_row">
+            <div className="e_rowL">
+              <span> </span>项目图片:
+            </div>
+            <div className="e_rowR">
+              <Z1upImgs
+                max={50}
+                isLook={false}
+                ref={imgsRef}
+                fileCheck={false}
+                size={10}
+                isCoverShow={true}
+                dirCode="aaaaaaaaa"
+                myUrl="cms/goods/upload"
+              />
+            </div>
+          </div>
+
+          <div className="e_row">
+            <div className="e_rowL e_rowL2">
+              <span> </span>项目视频:
+            </div>
+            <div className="e_rowR">
+              <Z2upVideos
+                max={10}
+                isLook={false}
+                ref={videosRef}
+                fileCheck={false}
+                size={500}
+                dirCode="aaaaaaaaa"
+                myUrl="cms/goods/upload"
+              />
+            </div>
+          </div>
+
+          {/* 确定和取消按钮 */}
+          <div className="A1AddBtn">
+            {pageType.txt === "look" ? (
+              <Button onClick={closeFu}>关 闭</Button>
+            ) : (
+              <>
+                <Button type="primary" htmlType="submit">
+                  保存
+                </Button>
+                &emsp;&emsp;
+                <Popconfirm
+                  title="放弃编辑后,信息将不会保存!"
+                  okText="放弃"
+                  cancelText="取消"
+                  onConfirm={closeFu}
+                  okButtonProps={{ loading: false }}
+                >
+                  <Button>取消</Button>
+                </Popconfirm>
+              </>
+            )}
+          </div>
+        </Form>
+      </div>
+    </div>
+  );
+}
+
+const MemoA1Add = React.memo(A1Add);
+
+export default MemoA1Add;

+ 38 - 6
src/pages/A1Project/index.module.scss

@@ -1,17 +1,49 @@
 .A1Project {
+  position: relative;
   :global {
-    .top {
-      height: 100px;
+    .A1top {
       border-radius: 10px;
-      padding: 20px 15px 0;
+      padding: 20px 15px 1px;
       background-color: #fff;
-      
+
+      .A1Search {
+        margin-bottom: 15px;
+        display: flex;
+
+
+        .A1SearchRow {
+          margin-right: 30px;
+        }
+      }
+
+      .A1Search2 {
+        width: 1267px;
+        position: relative;
+
+        .A1SearchBtn {
+          position: absolute;
+          right: 0px;
+        }
+      }
     }
-    .main{
+
+    .A1tableBox {
       margin-top: 15px;
       border-radius: 10px;
-      height: calc(100% - 110px);
+      height: calc(100% - 128px);
       background-color: #fff;
+      overflow: hidden;
+
+      .ant-table-body {
+        height: 575px;
+        overflow-y: auto !important;
+
+        .ant-table-row {
+          .ant-table-cell {
+            padding: 10px;
+          }
+        }
+      }
     }
   }
 }

+ 455 - 7
src/pages/A1Project/index.tsx

@@ -1,14 +1,462 @@
-import React from "react";
+import React, {
+  useCallback,
+  useEffect,
+  useMemo,
+  useRef,
+  useState,
+} from "react";
 import styles from "./index.module.scss";
- function A1Project() {
-  
+import { Button, Input, Select, Table } from "antd";
+import { useSelector } from "react-redux";
+import { RootState } from "@/store";
+import { A1TableType } from "@/types";
+import A1Add from "./A1Add";
+
+const bbbbArr = [
+  {
+    value: "1111s",
+    label: "shaogen - 邵根",
+  },
+  {
+    value: "2222s",
+    label: "wuweihao - 吴伟浩",
+  },
+];
+
+const ccccArr = [
+  {
+    value: "ccc1111s",
+    label: "shaogen - 邵根",
+  },
+  {
+    value: "ccc2222s",
+    label: "wuweihao - 吴伟浩",
+  },
+];
+
+const ddddArr = [
+  {
+    value: "",
+    label: "全部",
+  },
+  {
+    value: "项目状态1",
+    label: "项目状态1",
+  },
+  {
+    value: "项目状态2",
+    label: "项目状态2",
+  },
+];
+
+const eeeeArr = [
+  {
+    value: "",
+    label: "全部",
+  },
+  {
+    value: "已完成",
+    label: "已完成",
+  },
+  {
+    value: "未完成",
+    label: "未完成",
+  },
+];
+
+const ffffArr = [
+  {
+    value: "",
+    label: "全部",
+  },
+  {
+    value: "存在待审批的文件",
+    label: "存在待审批的文件",
+  },
+  {
+    value: "存在审批通过的文件",
+    label: "存在审批通过的文件",
+  },
+  {
+    value: "存在审批驳回的文件",
+    label: "存在审批驳回的文件",
+  },
+];
+
+const ggggArr = [
+  {
+    value: "",
+    label: "全部",
+  },
+  {
+    value: "我创建的项目",
+    label: "我创建的项目",
+  },
+  {
+    value: "我参与的项目",
+    label: "我参与的项目",
+  },
+];
+
+function A1Project() {
+  // 顶部的试图切换
+  const [topType, setTopType] = useState<"outer" | "inner">("outer");
+
+  // 表单数据
+  const [fromData, setFromData] = useState({
+    aaaa: "",
+    bbbb: "",
+    cccc: "",
+    dddd: "",
+    eeee: "",
+    ffff: "",
+    gggg: "",
+    pageNum: 1,
+    pageSize: 10,
+  });
+
+  // 封装发送请求的函数
+  const A1getListFu = useCallback(() => {
+    console.log(fromData);
+  }, [fromData]);
+
+  useEffect(() => {
+    A1getListFu();
+  }, [A1getListFu]);
+
+  // 项目编号/项目名称/建设单位 的 输入
+  const aaaaTime = useRef(-1);
+  const aaaaChange = useCallback(
+    (e: React.ChangeEvent<HTMLInputElement>) => {
+      clearTimeout(aaaaTime.current);
+      aaaaTime.current = window.setTimeout(() => {
+        setFromData({
+          ...fromData,
+          aaaa: e.target.value,
+          pageNum: 1,
+        });
+      }, 500);
+    },
+    [fromData]
+  );
+
+  // 项目经理 下拉 搜索 的 改变
+  const bbbbChange = useCallback(
+    (value: string) => {
+      const val = value ? value : "";
+      setFromData({
+        ...fromData,
+        bbbb: val,
+        pageNum: 1,
+      });
+    },
+    [fromData]
+  );
+
+  // 商务经理 下拉 搜索 的 改变
+  const ccccChange = useCallback(
+    (value: string) => {
+      const val = value ? value : "";
+      setFromData({
+        ...fromData,
+        cccc: val,
+        pageNum: 1,
+      });
+    },
+    [fromData]
+  );
+
+  // 点击重置
+  const [inputKey, setInputKey] = useState(1);
+  const resetSelectFu = useCallback(() => {
+    // 把2个输入框和时间选择器清空
+    setInputKey(Date.now());
+    setFromData({
+      aaaa: "",
+      bbbb: "",
+      cccc: "",
+      dddd: "",
+      eeee: "",
+      ffff: "",
+      gggg: "",
+      pageNum: 1,
+      pageSize: 10,
+    });
+  }, []);
+
+  // 点击顶部的切换视图 按钮
+  const topTypeFu = useCallback(
+    (val: "outer" | "inner") => {
+      if (topType === val) return;
+      setTopType(val);
+      resetSelectFu();
+    },
+    [resetSelectFu, topType]
+  );
+
+  // 从仓库获取表格数据
+  const tableInfo = useSelector(
+    (state: RootState) => state.A1Project.tableInfo
+  );
+  // 页码变化
+  const paginationChange = useCallback(
+    () => (pageNum: number, pageSize: number) => {
+      setFromData({ ...fromData, pageNum, pageSize });
+    },
+    [fromData]
+  );
+
+  const columns = useMemo(() => {
+    const arr: any = [
+      {
+        title: "项目编号",
+        dataIndex: "a",
+      },
+      {
+        title: "项目名称",
+        dataIndex: "a",
+      },
+      {
+        title: "建设单位",
+        dataIndex: "a",
+      },
+      {
+        title: "项目地点",
+        dataIndex: "a",
+      },
+      {
+        title: "项目周期",
+        dataIndex: "a",
+      },
+      {
+        title: "项目经理",
+        dataIndex: "a",
+      },
+      {
+        title: "商务经理",
+        dataIndex: "a",
+      },
+      {
+        title: "项目状态",
+        dataIndex: "a",
+      },
+      {
+        title: "最近编辑时间",
+        dataIndex: "a",
+      },
+    ];
+
+    if (topType === "inner") {
+      arr.push(
+        {
+          title: "收集文件类型",
+          dataIndex: "a",
+        },
+        {
+          width: 150,
+          title: "待审批/通过/驳回",
+          dataIndex: "a",
+        }
+      );
+    }
+
+    arr.push({
+      title: "操作",
+      render: (item: A1TableType) => (
+        <>
+          <Button size="small" type="text">
+            查看
+          </Button>
+          {topType === "inner" ? (
+            <Button size="small" type="text">
+              审批
+            </Button>
+          ) : null}
+
+          <Button size="small" type="text">
+            下载
+          </Button>
+          <Button size="small" type="text">
+            删除
+          </Button>
+        </>
+      ),
+    });
+
+    return arr;
+  }, [topType]);
+
+  // 新增和查看
+  const [pageType, setPageType] = useState({ txt: "", id: 0 });
+
   return (
     <div className={styles.A1Project}>
-      <div className="pageTitle">项目管理</div>
-      <div className="top">表格数据搜索内容</div>
-      <div className="main">表格数据</div>
+      <div className="pageTitle">
+        项目管理{pageType.txt === "add" ? " > 新增项目" : null}
+      </div>
+
+      {/* 顶部筛选 */}
+      <div className="A1top">
+        <div className="A1Search">
+          {/* 顶部试图切换 */}
+          <div className="A1SearchRow">
+            <Button
+              onClick={() => topTypeFu("outer")}
+              type={topType === "outer" ? "primary" : "default"}
+            >
+              项目文件视图
+            </Button>
+            <Button
+              onClick={() => topTypeFu("inner")}
+              type={topType === "inner" ? "primary" : "default"}
+            >
+              内控文件视图
+            </Button>
+          </div>
+
+          <div className="A1SearchRow">
+            <span>项目编号/项目名称/建设单位:</span>
+            <Input
+              key={inputKey}
+              maxLength={30}
+              style={{ width: 200 }}
+              placeholder="请输入内容,最多20字"
+              allowClear
+              onChange={(e) => aaaaChange(e)}
+            />
+          </div>
+
+          <div className="A1SearchRow">
+            <span>项目经理:</span>
+            <Select
+              key={inputKey}
+              style={{ width: 200 }}
+              showSearch
+              allowClear
+              placeholder="请选择或输入内容搜索"
+              optionFilterProp="children"
+              onChange={bbbbChange}
+              filterOption={(input, option) =>
+                (option?.label ?? "")
+                  .toLowerCase()
+                  .includes(input.toLowerCase())
+              }
+              options={bbbbArr}
+            />
+          </div>
+
+          <div className="A1SearchRow">
+            <span>商务经理:</span>
+            <Select
+              key={inputKey}
+              style={{ width: 200 }}
+              showSearch
+              allowClear
+              placeholder="请选择或输入内容搜索"
+              optionFilterProp="children"
+              onChange={ccccChange}
+              filterOption={(input, option) =>
+                (option?.label ?? "")
+                  .toLowerCase()
+                  .includes(input.toLowerCase())
+              }
+              options={ccccArr}
+            />
+          </div>
+        </div>
+        <div className="A1Search A1Search2">
+          <div className="A1SearchRow">
+            <span>项目状态:</span>
+            <Select
+              style={{ width: 164 }}
+              value={fromData.dddd}
+              onChange={(e) =>
+                setFromData({ ...fromData, dddd: e, pageNum: 1 })
+              }
+              options={ddddArr}
+            />
+          </div>
+
+          {/* 通过顶部状态 动态渲染 这3个下拉框 */}
+          {topType === "outer" ? null : (
+            <>
+              <div className="A1SearchRow">
+                <span>收集进度:</span>
+                <Select
+                  style={{ width: 164 }}
+                  value={fromData.eeee}
+                  onChange={(e) =>
+                    setFromData({ ...fromData, eeee: e, pageNum: 1 })
+                  }
+                  options={eeeeArr}
+                />
+              </div>
+
+              <div className="A1SearchRow">
+                <span>审批进度:</span>
+                <Select
+                  style={{ width: 164 }}
+                  value={fromData.ffff}
+                  onChange={(e) =>
+                    setFromData({ ...fromData, ffff: e, pageNum: 1 })
+                  }
+                  options={ffffArr}
+                />
+              </div>
+
+              <div className="A1SearchRow">
+                <span>项目角色:</span>
+                <Select
+                  style={{ width: 164 }}
+                  value={fromData.gggg}
+                  onChange={(e) =>
+                    setFromData({ ...fromData, gggg: e, pageNum: 1 })
+                  }
+                  options={ggggArr}
+                />
+              </div>
+            </>
+          )}
+
+          <div className="A1SearchRow A1SearchBtn">
+            <Button
+              type="primary"
+              onClick={() => setPageType({ txt: "add", id: 0 })}
+            >
+              新增项目
+            </Button>
+            &emsp;&emsp;
+            <Button onClick={resetSelectFu}>重置</Button>
+          </div>
+        </div>
+      </div>
+      {/* 表格主体 */}
+      <div className="A1tableBox">
+        <Table
+          scroll={{ y: 575 }}
+          dataSource={tableInfo.list}
+          columns={columns}
+          rowKey="id"
+          pagination={{
+            showQuickJumper: true,
+            position: ["bottomCenter"],
+            showSizeChanger: true,
+            current: fromData.pageNum,
+            pageSize: fromData.pageSize,
+            total: tableInfo.total,
+            onChange: paginationChange(),
+          }}
+        />
+      </div>
+      {/* 新增、查看 */}
+      {pageType.txt === "add" ? (
+        <A1Add
+          pageType={pageType}
+          closeFu={() => setPageType({ txt: "", id: 0 })}
+        />
+      ) : null}
     </div>
-  )
+  );
 }
 
 const MemoA1Project = React.memo(A1Project);

+ 20 - 20
src/pages/Layout/index.module.scss

@@ -86,8 +86,6 @@
           }
 
           .userSet {
-            border-radius: 10px;
-            overflow: hidden;
             width: 140px;
             opacity: 0;
             pointer-events: none;
@@ -97,32 +95,34 @@
             left: 50%;
             transform: translateX(-50%);
             bottom: -80px;
-            padding-top: 20px;
-            // color: rgb(226, 223, 223);
-
-            &>span {
-              background-color: #fff;
-              display: block;
-              width: 100%;
-              height: 50px;
-              line-height: 50px;
-              text-align: center;
 
-              &:first-child {
-                border-radius: 10px 10px 0 0;
-              }
-
-              &:hover {
-                color: var(--themeColor);
+            // color: rgb(226, 223, 223);
+            &>div {
+              box-shadow: 1px 1px 4px 4px #ccc;
+              margin-top: 15px;
+              border-radius: 10px;
+              overflow: hidden;
+              &>span {
+                background-color: #fff;
+                display: block;
+                width: 100%;
+                height: 50px;
+                line-height: 50px;
+                text-align: center;
+
+                &:hover {
+                  color: var(--themeColor);
+                }
               }
             }
+
           }
 
           &:hover {
             .userSet {
               opacity: 1;
               pointer-events: auto;
-              bottom: -110px;
+              bottom: -120px;
             }
 
             .userInco1 {
@@ -140,7 +140,7 @@
 
       .layoutRightMain {
         height: calc(100% - 60px);
-        padding: 15px 30px 20px;
+        padding: 15px;
         background-color: #ecedf1;
 
         .mainBoxR {

+ 13 - 11
src/pages/Layout/index.tsx

@@ -171,17 +171,19 @@ function Layout() {
               <CaretDownOutlined />
             </div>
             <div className="userSet">
-              <span onClick={() => setOpen(true)}>修改密码</span>
-              <Popconfirm
-                placement="bottom"
-                title="确定退出吗?"
-                okText="确定"
-                cancelText="取消"
-                onConfirm={loginExit}
-                okButtonProps={{ loading: false }}
-              >
-                退出登录
-              </Popconfirm>
+              <div>
+                <span onClick={() => setOpen(true)}>修改密码</span>
+                <Popconfirm
+                  placement="bottom"
+                  title="确定退出吗?"
+                  okText="确定"
+                  cancelText="取消"
+                  onConfirm={loginExit}
+                  okButtonProps={{ loading: false }}
+                >
+                  退出登录
+                </Popconfirm>
+              </div>
             </div>
           </div>
         </div>

+ 18 - 0
src/store/action/A1Project.ts

@@ -0,0 +1,18 @@
+import http from "@/utils/http";
+import { AppDispatch } from "..";
+/**
+ * 获取列表
+ */
+export const A1_APIgetList = (data: any) => {
+  return async (dispatch: AppDispatch) => {
+    const res = await http.post("cms/book/pageList", data);
+    if (res.code === 0) {
+      const obj = {
+        list: res.data.records,
+        total: res.data.total,
+      };
+
+      dispatch({ type: "A1/getList", payload: obj });
+    }
+  };
+};

+ 31 - 0
src/store/reducer/A1Project.ts

@@ -0,0 +1,31 @@
+import { A1TableType } from "@/types";
+
+// 初始化状态
+const initState = {
+  // 列表数据
+  tableInfo: {
+    list: [
+      { id: 1, a: 1, b: 2, c: 3, d: 4, e: 5 },
+      { id: 2, a: 1, b: 2, c: 3, d: 4, e: 5 },
+    ] as A1TableType[],
+    total: 2,
+  },
+};
+
+// 定义 action 类型
+type Props = {
+  type: "A1/getList";
+  payload: { list: A1TableType[]; total: number };
+};
+
+// 频道 reducer
+export default function A1Reducer(state = initState, action: Props) {
+  switch (action.type) {
+    // 获取列表数据
+    case "A1/getList":
+      return { ...state, tableInfo: action.payload };
+
+    default:
+      return state;
+  }
+}

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

@@ -3,10 +3,12 @@ import { combineReducers } from "redux";
 
 // 导入 登录 模块的 reducer
 import A0Layout from "./layout";
+import A1Project from "./A1Project";
 
 // 合并 reducer
 const rootReducer = combineReducers({
   A0Layout,
+  A1Project
 });
 
 // 默认导出

+ 1 - 0
src/types/api/A1Project.d.ts

@@ -0,0 +1 @@
+export type A1TableType=any

+ 6 - 0
src/types/api/layot.d.ts

@@ -9,3 +9,9 @@ export type RouterType = {
   Com: React.LazyExoticComponent<React.MemoExoticComponent<() => JSX.Element>>;
   done:boolean
 }[]
+
+export type FileImgListType = {
+  id: number;
+  fileName: string;
+  filePath: string;
+};

+ 1 - 0
src/types/index.d.ts

@@ -1 +1,2 @@
 export * from './api/layot'
+export * from './api/A1Project'

+ 1 - 1
src/utils/http.ts

@@ -10,7 +10,7 @@ export const baseURL =
   // process.env.NODE_ENV === "development"
   //   ? "http://192.168.20.55:8052/api/"
   //   : "";
-  process.env.NODE_ENV === "development" ? "https://sit-jswl2.4dage.com" : "";
+  process.env.NODE_ENV === "development" ? "https://ytxbwg.4dage.com" : "";
 
 // 处理  类型“AxiosResponse<any, any>”上不存在属性“code”
 declare module "axios" {