shaogen1995 2 år sedan
förälder
incheckning
2b758f52e7
36 ändrade filer med 3042 tillägg och 87 borttagningar
  1. 0 1
      管理后台/src/App.tsx
  2. 76 0
      管理后台/src/pages/A1Goods/GoodsAdd/index.module.scss
  3. 45 27
      管理后台/src/pages/A1Goods/GoodsAdd/index.tsx
  4. 17 3
      管理后台/src/pages/A1Goods/index.tsx
  5. 185 0
      管理后台/src/pages/A2Wall/WallAdd/index.module.scss
  6. 477 0
      管理后台/src/pages/A2Wall/WallAdd/index.tsx
  7. 32 0
      管理后台/src/pages/A2Wall/WallLook/index.module.scss
  8. 66 0
      管理后台/src/pages/A2Wall/WallLook/index.tsx
  9. 46 0
      管理后台/src/pages/A2Wall/WallTable/index.module.scss
  10. 297 0
      管理后台/src/pages/A2Wall/WallTable/index.tsx
  11. 47 3
      管理后台/src/pages/A2Wall/index.module.scss
  12. 162 5
      管理后台/src/pages/A2Wall/index.tsx
  13. 19 0
      管理后台/src/pages/A3User/UserAdd/index.module.scss
  14. 159 0
      管理后台/src/pages/A3User/UserAdd/index.tsx
  15. 30 3
      管理后台/src/pages/A3User/index.module.scss
  16. 340 5
      管理后台/src/pages/A3User/index.tsx
  17. 70 0
      管理后台/src/pages/A4Role/RoleAdd/index.module.scss
  18. 201 0
      管理后台/src/pages/A4Role/RoleAdd/index.tsx
  19. 28 1
      管理后台/src/pages/A4Role/index.module.scss
  20. 228 5
      管理后台/src/pages/A4Role/index.tsx
  21. 76 28
      管理后台/src/pages/Layout/index.tsx
  22. 7 1
      管理后台/src/pages/Login/index.tsx
  23. 88 0
      管理后台/src/store/action/A2Wall.ts
  24. 68 0
      管理后台/src/store/action/A3user.ts
  25. 58 0
      管理后台/src/store/action/A4Role.ts
  26. 21 0
      管理后台/src/store/reducer/A2Wall.ts
  27. 44 0
      管理后台/src/store/reducer/A3User.ts
  28. 27 0
      管理后台/src/store/reducer/A4Role.ts
  29. 6 0
      管理后台/src/store/reducer/index.ts
  30. 8 2
      管理后台/src/store/reducer/layout.ts
  31. 1 1
      管理后台/src/types/api/A1Goods.d.ts
  32. 36 0
      管理后台/src/types/api/A2Wall.d.ts
  33. 35 0
      管理后台/src/types/api/A3User.d.ts
  34. 29 0
      管理后台/src/types/api/A4Role.d.ts
  35. 9 1
      管理后台/src/types/api/layot.d.ts
  36. 4 1
      管理后台/src/types/index.d.ts

+ 0 - 1
管理后台/src/App.tsx

@@ -58,7 +58,6 @@ export default function App() {
       {/* 点击预览视频、音频、模型组件 */}
       <LookDom />
 
-
       {/* antd 轻提示 ---兼容360浏览器 */}
       <MessageCom />
     </>

+ 76 - 0
管理后台/src/pages/A1Goods/GoodsAdd/index.module.scss

@@ -249,5 +249,81 @@
         }
       }
     }
+
+    // 查看的情况
+    .nolyLookMain {
+
+      .ant-row {
+        pointer-events: none;
+      }
+
+      .ant-form-item-control-input {
+        pointer-events: none;
+
+        .ant-input-affix-wrapper {
+          border: transparent;
+
+          .ant-input-show-count-suffix {
+            opacity: 0;
+          }
+        }
+
+        .ant-select-selector {
+          border: transparent;
+        }
+
+        .ant-select-arrow {
+          opacity: 0;
+        }
+
+        .ant-input {
+          border: transparent;
+        }
+
+        .ant-input-data-count {
+          opacity: 0;
+        }
+
+      }
+
+      .ant-checkbox-group {
+        pointer-events: none;
+      }
+
+      .myformBox3 .upImgBox .fileImgListBox>div {
+        margin: 0px 20px 15px 0;
+        cursor: default;
+      }
+
+      .closeLook {
+        .ant-form-item-control-input {
+          pointer-events: auto;
+        }
+      }
+
+      textarea {
+        min-height: 40px !important;
+      }
+
+      textarea,
+      input {
+        &::-webkit-input-placeholder {
+          color: black;
+        }
+
+        &:-moz-placeholder {
+          color: black;
+        }
+
+        &::-moz-placeholder {
+          color: black;
+        }
+
+        &:-ms-input-placeholder {
+          color: black;
+        }
+      }
+
+    }
   }
 }

+ 45 - 27
管理后台/src/pages/A1Goods/GoodsAdd/index.tsx

@@ -55,7 +55,7 @@ function GoodsAdd({ id, closeMoalFu, addListFu, editListFu, lookFlag }: Props) {
   });
 
   // 附件拖动的拆分数组
-  const [fileImgList, setFileImgList] = useState<FileImgListType>([]);
+  const [fileImgList, setFileImgList] = useState<FileImgListType[]>([]);
 
   // 表单的ref
   const FormBoxRef = useRef<FormInstance>(null);
@@ -77,7 +77,7 @@ function GoodsAdd({ id, closeMoalFu, addListFu, editListFu, lookFlag }: Props) {
       video: {} as FileListType,
     };
 
-    const fileImgListTemp = [] as FileImgListType;
+    const fileImgListTemp = [] as FileImgListType[];
 
     data.forEach((v) => {
       if (v.type === "img") fileImgListTemp.push(v as any);
@@ -304,7 +304,8 @@ function GoodsAdd({ id, closeMoalFu, addListFu, editListFu, lookFlag }: Props) {
         });
       }
       // 简介的处理
-      const txt = value.description.replaceAll(" ", "").replaceAll("\n", "");
+      let txt = value.description ? value.description : "";
+      if (txt) txt = txt.replaceAll(" ", "").replaceAll("\n", "");
 
       const obj = {
         ...value,
@@ -341,7 +342,9 @@ function GoodsAdd({ id, closeMoalFu, addListFu, editListFu, lookFlag }: Props) {
 
   return (
     <div className={styles.GoodsAdd}>
-      <div className="main mySorrl">
+      <div
+        className={classNames("main mySorrl", lookFlag ? "nolyLookMain" : "")}
+      >
         <Form
           ref={FormBoxRef}
           name="basic"
@@ -364,7 +367,11 @@ function GoodsAdd({ id, closeMoalFu, addListFu, editListFu, lookFlag }: Props) {
             name="num"
             getValueFromEvent={(e) => e.target.value.replace(/\s+/g, "")}
           >
-            <Input maxLength={25} showCount placeholder="请输入内容" />
+            <Input
+              maxLength={25}
+              showCount
+              placeholder={lookFlag ? "(空)" : "请输入内容"}
+            />
           </Form.Item>
 
           <Form.Item
@@ -422,7 +429,7 @@ function GoodsAdd({ id, closeMoalFu, addListFu, editListFu, lookFlag }: Props) {
           >
             <TextArea
               autoSize
-              placeholder="请输入内容"
+              placeholder={lookFlag ? "(空)" : "请输入内容"}
               showCount
               maxLength={200}
             />
@@ -470,7 +477,7 @@ function GoodsAdd({ id, closeMoalFu, addListFu, editListFu, lookFlag }: Props) {
 
                 <div className="clearCover2">
                   {/* 封面图预览 删除 下载 */}
-                  <div className="clearCover2DelBox">
+                  <div className="clearCover2DelBox" hidden={lookFlag}>
                     <Popconfirm
                       title="删除后无法恢复,是否删除?"
                       okText="删除"
@@ -501,7 +508,7 @@ function GoodsAdd({ id, closeMoalFu, addListFu, editListFu, lookFlag }: Props) {
                   </div>
                 </div>
               </div>
-              <div className="fileBoxRow_r_tit">
+              <div className="fileBoxRow_r_tit" hidden={lookFlag}>
                 支持png、jpg和jpeg的图片格式;最大支持20M。
                 <br />
                 <div
@@ -571,7 +578,7 @@ function GoodsAdd({ id, closeMoalFu, addListFu, editListFu, lookFlag }: Props) {
                   cancelText="取消"
                   onConfirm={() => setFileList({ ...fileList, model: {} })}
                 >
-                  <div className="clearCover">
+                  <div className="clearCover" hidden={lookFlag}>
                     <CloseOutlined />
                   </div>
                 </Popconfirm>
@@ -604,7 +611,10 @@ function GoodsAdd({ id, closeMoalFu, addListFu, editListFu, lookFlag }: Props) {
               <div className="fileBoxRow_r">
                 <div className="upImgBox">
                   <div
-                    hidden={!!fileImgList.length && fileImgList.length >= 30}
+                    hidden={
+                      (!!fileImgList.length && fileImgList.length >= 30) ||
+                      lookFlag
+                    }
                     className="fileBoxRow_up"
                     onClick={() => upFileFu("img")}
                   >
@@ -612,6 +622,7 @@ function GoodsAdd({ id, closeMoalFu, addListFu, editListFu, lookFlag }: Props) {
                   </div>
 
                   <ReactSortable
+                    disabled={lookFlag}
                     className="fileImgListBox"
                     list={fileImgList}
                     setList={setFileImgList}
@@ -633,7 +644,7 @@ function GoodsAdd({ id, closeMoalFu, addListFu, editListFu, lookFlag }: Props) {
                           cancelText="取消"
                           onConfirm={() => delImgListFu(v.id!)}
                         >
-                          <div className="clearCover">
+                          <div className="clearCover" hidden={lookFlag}>
                             <CloseOutlined />
                           </div>
                         </Popconfirm>
@@ -663,7 +674,7 @@ function GoodsAdd({ id, closeMoalFu, addListFu, editListFu, lookFlag }: Props) {
                     ))}
                   </ReactSortable>
                 </div>
-                <div className="fileTit">
+                <div className="fileTit" hidden={lookFlag}>
                   {fileImgList.length && fileImgList.length >= 2 ? (
                     <>
                       按住鼠标可拖动图片调整顺序。
@@ -716,7 +727,7 @@ function GoodsAdd({ id, closeMoalFu, addListFu, editListFu, lookFlag }: Props) {
                   cancelText="取消"
                   onConfirm={() => setFileList({ ...fileList, audio: {} })}
                 >
-                  <div className="clearCover">
+                  <div className="clearCover" hidden={lookFlag}>
                     <CloseOutlined />
                   </div>
                 </Popconfirm>
@@ -771,13 +782,14 @@ function GoodsAdd({ id, closeMoalFu, addListFu, editListFu, lookFlag }: Props) {
                 >
                   <DownloadOutlined />
                 </a>
+                {/* 视频删除 */}
                 <Popconfirm
                   title="删除后无法恢复,是否删除?"
                   okText="删除"
                   cancelText="取消"
                   onConfirm={() => setFileList({ ...fileList, video: {} })}
                 >
-                  <div className="clearCover">
+                  <div className="clearCover" hidden={lookFlag}>
                     <CloseOutlined />
                   </div>
                 </Popconfirm>
@@ -809,19 +821,25 @@ function GoodsAdd({ id, closeMoalFu, addListFu, editListFu, lookFlag }: Props) {
 
           {/* 确定和取消按钮 */}
           <br />
-          <Form.Item wrapperCol={{ offset: 9, span: 16 }}>
-            <Button type="primary" htmlType="submit">
-              提交
-            </Button>
-            &emsp;
-            <Popconfirm
-              title="放弃编辑后,信息将不会保存!"
-              okText="放弃"
-              cancelText="取消"
-              onConfirm={closeMoalFu}
-            >
-              <Button>取消</Button>
-            </Popconfirm>
+          <Form.Item wrapperCol={{ offset: 9, span: 16 }} className="closeLook">
+            {lookFlag ? (
+              <Button onClick={closeMoalFu}>关 闭</Button>
+            ) : (
+              <>
+                <Button type="primary" htmlType="submit">
+                  提交
+                </Button>
+                &emsp;
+                <Popconfirm
+                  title="放弃编辑后,信息将不会保存!"
+                  okText="放弃"
+                  cancelText="取消"
+                  onConfirm={closeMoalFu}
+                >
+                  <Button>取消</Button>
+                </Popconfirm>
+              </>
+            )}
           </Form.Item>
         </Form>
       </div>

+ 17 - 3
管理后台/src/pages/A1Goods/index.tsx

@@ -149,7 +149,10 @@ function A1Goods() {
         title: "名称",
         dataIndex: "name",
       },
-
+      {
+        title: "登记编号",
+        render: (item: GoodsTableType) => (item.num ? item.num : "(空)"),
+      },
       {
         title: "类别",
         dataIndex: "dictTexture",
@@ -159,6 +162,14 @@ function A1Goods() {
         dataIndex: "dictAge",
       },
       {
+        title: "级别",
+        dataIndex: "dictLevel",
+      },
+      {
+        title: "来源",
+        dataIndex: "dictSource",
+      },
+      {
         title: "简介",
         render: (item: GoodsTableType) =>
           item.description ? (
@@ -174,7 +185,7 @@ function A1Goods() {
           ),
       },
       {
-        title: "图片",
+        title: "封面",
         render: (item: GoodsTableType) => (
           <div className="tableImgAuto">
             <ImageLazy width={60} height={60} src={item.thumb!} />
@@ -185,7 +196,10 @@ function A1Goods() {
         title: "最近编辑时间",
         dataIndex: "updateTime",
       },
-
+      {
+        title: "编辑用户",
+        dataIndex: "creatorName",
+      },
       {
         title: "操作",
         render: (item: GoodsTableType) => (

+ 185 - 0
管理后台/src/pages/A2Wall/WallAdd/index.module.scss

@@ -0,0 +1,185 @@
+.WallAdd {
+  width: 100%;
+  height: 100%;
+  border-radius: 10px;
+  background-color: #fff;
+
+  :global {
+    .main {
+      width: 800px;
+      padding: 24px;
+
+      .myformBox {
+        display: flex;
+        margin-bottom: 20px;
+
+        .label {
+          width: 94px;
+          text-align: right;
+
+          &>span {
+            position: relative;
+            top: 2px;
+            color: #ff4d4f;
+          }
+        }
+
+        .myformBoxR {
+          width: calc(100% - 94px);
+
+          .fileTit {
+            font-size: 14px;
+            color: rgb(126, 124, 124);
+          }
+
+        }
+
+        .fileBoxRow_r {
+          position: relative;
+
+          .fileBoxRow_up {
+            color: #a6a6a6;
+            border-radius: 3px;
+            cursor: pointer;
+            font-size: 30px;
+            width: 100px;
+            height: 100px;
+            border: 1px dashed #797979;
+            display: flex;
+            justify-content: center;
+            align-items: center;
+
+
+          }
+
+          .upImgBox {
+            display: flex;
+            flex-wrap: wrap;
+
+            .fileBoxRow_up {
+              margin: 0 15px 15px 0;
+            }
+
+            .fileBoxRow_r_img {
+              position: relative;
+              cursor: move;
+
+              .clearCover {
+                right: -10px;
+                top: -10px;
+                transform: translate(0, 0);
+                background-color: rgba(0, 0, 0, .8);
+                width: 20px;
+                height: 20px;
+                border-radius: 50%;
+                font-size: 16px;
+                color: #fff;
+              }
+            }
+          }
+
+          .fileBoxRow_r_img {
+            width: 100px;
+            height: 126px;
+            position: relative;
+
+            .clearCover {
+              cursor: pointer;
+              z-index: 10;
+              position: absolute;
+              width: 50px;
+              height: 50px;
+              top: 50%;
+              transform: translateY(-50%);
+              right: -50px;
+              display: flex;
+              justify-content: center;
+              align-items: center;
+              font-size: 24px;
+            }
+          }
+
+
+        }
+
+
+      }
+
+      .myformBox2 {
+        line-height: 40px;
+        margin-bottom: 0;
+
+        .myformBoxR {
+          display: flex;
+          align-items: center;
+
+          .fileTit {
+            margin-left: 20px;
+          }
+
+          .fileInfo {
+            display: flex;
+
+            .upSuccTxt {
+              font-size: 16px;
+            }
+
+            .clearCover {
+              cursor: pointer;
+              font-size: 16px;
+              margin-left: 20px;
+            }
+
+            &>a {
+              color: black;
+            }
+          }
+        }
+      }
+
+      .myformBox0 {
+        margin-bottom: 5px;
+      }
+
+      // 附件拖动
+      .fileImgListBox {
+        display: flex;
+
+        &>div {
+          margin: 0 15px 15px 0;
+        }
+
+        .fileImgListLDBox {
+          height: 26px;
+          background-color: rgba(0, 0, 0, .6);
+          color: #fff;
+          display: flex;
+          justify-content: space-around;
+          font-size: 16px;
+
+          &>a {
+            color: #fff;
+          }
+        }
+      }
+
+      .noUpThumb {
+        position: relative;
+        overflow: hidden;
+        opacity: 0;
+        transition: top .2s;
+        color: #ff4d4f;
+        top: -10px;
+        padding-left: 94px;
+      }
+
+      .noUpThumbAc {
+        top: 0;
+        opacity: 1;
+      }
+    }
+
+  }
+
+
+}

+ 477 - 0
管理后台/src/pages/A2Wall/WallAdd/index.tsx

@@ -0,0 +1,477 @@
+import { MessageFu } from "@/utils/message";
+import { Button, Form, FormInstance, Input, Popconfirm, Radio } from "antd";
+import React, {
+  useCallback,
+  useEffect,
+  useMemo,
+  useRef,
+  useState,
+} from "react";
+import classNames from "classnames";
+import styles from "./index.module.scss";
+
+import ImageLazy from "@/components/ImageLazy";
+import { ImgListType, WallSaveAPIType } from "@/types";
+import WallLook from "../WallLook";
+import {
+  getWallDetailAPI,
+  wallUploadAPI,
+  setWallSave,
+} from "@/store/action/A2Wall";
+import { fileDomInitialFu } from "@/utils/domShow";
+import store from "@/store";
+import { baseURL } from "@/utils/http";
+import {
+  PlusOutlined,
+  EyeOutlined,
+  UploadOutlined,
+  CloseOutlined,
+  DownloadOutlined,
+} from "@ant-design/icons";
+import { ReactSortable } from "react-sortablejs";
+
+type Props = {
+  id: number;
+  closeMoalFu: (txt: string) => void;
+};
+
+function WallAdd({ id, closeMoalFu }: Props) {
+  const getInfoFu = useCallback(async (id: number) => {
+    const res = await getWallDetailAPI(id);
+    FormBoxRef.current?.setFieldsValue({ name: res.data.entity.name });
+    setImgNum(Number(res.data.entity.layout) as 1 | 2);
+
+    setTopType(res.data.entity.type)
+
+    if (res.data.entity.type === "img") {
+      const imgListRes = res.data.file;
+      setImgList(imgListRes);
+      imgListRef.current = imgListRes;
+    } else setVideoFile(res.data.file[0]);
+  }, []);
+
+  useEffect(() => {
+    if (id > 0) getInfoFu(id);
+  }, [getInfoFu, id]);
+
+  // 类型的选择
+  const [topType, setTopType] = useState<"img" | "video">("img");
+
+  // 视频的上传
+  const [videoFile, setVideoFile] = useState<ImgListType>({
+    id: 0,
+    fileName: "",
+    filePath: "",
+  });
+
+  // 表单的ref
+  const FormBoxRef = useRef<FormInstance>(null);
+  // 上传封面图的ref
+  const myInput = useRef<HTMLInputElement>(null);
+
+  // 版式的选择
+  const [imgNum, setImgNum] = useState<1 | 2>(1);
+
+  // 上传图片的校验
+  const [imgCheck, setImgCheck] = useState(false);
+
+  // 上传图片的全部数据(最多8张来进行切割)
+  const imgListRef = useRef<ImgListType[]>([]);
+
+  // 在页面展示的图片
+  const [imgList, setImgList] = useState<ImgListType[]>([]);
+
+  // 版式的选择改变,切割数组
+  useEffect(() => {
+    if (imgListRef.current.length) {
+      const newData = imgListRef.current.slice(0, imgNum);
+      setImgList(newData);
+    }
+  }, [imgNum]);
+
+  // 删除某一张图片
+  const delImgListFu = useCallback(
+    (id: number) => {
+      const newItems = imgList.filter((v) => v.id !== id);
+      setImgList(newItems);
+      imgListRef.current = imgListRef.current.filter((v) => v.id !== id);
+    },
+    [imgList]
+  );
+
+  // 附件的校验
+  const btnOkCheck = useMemo(() => {
+    let flag = false;
+    if (topType === "img") {
+      if (imgList.length < imgNum) flag = true;
+    } else {
+      if (!videoFile.filePath) flag = true;
+    }
+    return flag;
+  }, [imgList.length, imgNum, topType, videoFile.filePath]);
+
+  // 没有通过校验
+  const onFinishFailed = useCallback(() => {
+    setImgCheck(true);
+  }, []);
+
+  // 上传封面图
+  const handeUpPhoto = useCallback(
+    async (e: React.ChangeEvent<HTMLInputElement>) => {
+      if (e.target.files) {
+        // 拿到files信息
+        const filesInfo = e.target.files[0];
+
+        let anType = ["image/jpeg", "image/png"];
+        let anTit1 = "只支持png、jpg和jpeg格式!";
+        let anTit2 = "最大支持30M!";
+        let anSize = 30 * 1024 * 1024;
+
+        if (topType === "video") {
+          anType = ["video/mp4"];
+          anTit1 = "只支持mp4格式!";
+          anTit2 = "最大支持500M!";
+          anSize = 500 * 1024 * 1024;
+        }
+
+        // 校验格式
+        if (!anType.includes(filesInfo.type)) {
+          e.target.value = "";
+          return MessageFu.warning(anTit1);
+        }
+        // 校验大小
+        if (filesInfo.size > anSize) {
+          e.target.value = "";
+          return MessageFu.warning(anTit2);
+        }
+        // 创建FormData对象
+        const fd = new FormData();
+        // 把files添加进FormData对象(‘photo’为后端需要的字段)
+        fd.append("type", "img");
+        fd.append("file", filesInfo);
+
+        e.target.value = "";
+
+        try {
+          const res = await wallUploadAPI(fd);
+          if (res.code === 0) {
+            MessageFu.success("上传成功!");
+            if (topType === "img") {
+              setImgList([...imgList, res.data]);
+              imgListRef.current.unshift(res.data);
+            } else setVideoFile(res.data);
+          }
+          fileDomInitialFu();
+        } catch (error) {
+          fileDomInitialFu();
+        }
+      }
+    },
+    [imgList, topType]
+  );
+
+  // 通过校验点击确定
+  const onFinish = useCallback(
+    async (value: { name: string }) => {
+      console.log("通过校验,点击确定");
+      setImgCheck(true);
+
+      if (topType === "img" && imgList.length < imgNum) return;
+      if (topType === "video" && !videoFile.filePath) return;
+
+      const fileIds =
+        topType === "img"
+          ? imgList.map((v) => v.id).join(",")
+          : videoFile.id + "";
+
+      const obj: WallSaveAPIType = {
+        id: id > 0 ? id : null,
+        fileIds,
+        name: value.name,
+        type: topType,
+        layout: imgNum,
+      };
+
+      const res = await setWallSave(obj);
+
+      if (res.code === 0) {
+        MessageFu.success(id > 0 ? "编辑成功!" : "新增成功!");
+        closeMoalFu(id > 0 ? "编辑" : "新增");
+      }
+    },
+    [
+      closeMoalFu,
+      id,
+      imgList,
+      imgNum,
+      topType,
+      videoFile.filePath,
+      videoFile.id,
+    ]
+  );
+
+  // 点击预览效果
+  const [lookImg, setLookImg] = useState(false);
+
+  return (
+    <div className={styles.WallAdd}>
+      <div className="main">
+        <Form
+          ref={FormBoxRef}
+          name="basic"
+          labelCol={{ span: 3 }}
+          onFinish={onFinish}
+          onFinishFailed={onFinishFailed}
+          autoComplete="off"
+        >
+          <div className="myformBox">
+            <div className="label">
+              <span>*</span> 类型:
+            </div>
+            <div className="myformBoxR">
+              <Radio.Group
+                value={topType}
+                onChange={(e) => setTopType(e.target.value)}
+              >
+                <Radio value="img">海报</Radio>
+                <Radio value="video">视频</Radio>
+              </Radio.Group>
+            </div>
+          </div>
+
+          <Form.Item
+            label="名称"
+            name="name"
+            rules={[{ required: true, message: "请输入名称!" }]}
+            getValueFromEvent={(e) => e.target.value.replace(/\s+/g, "")}
+          >
+            <Input maxLength={25} showCount placeholder="请输入内容" />
+          </Form.Item>
+
+          <input
+            id="upInput"
+            type="file"
+            accept={topType === "img" ? ".png,.jpg,.jpeg" : ".mp4"}
+            ref={myInput}
+            onChange={(e) => handeUpPhoto(e)}
+          />
+
+          {topType === "img" ? (
+            <>
+              <div className="myformBox">
+                <div className="label">
+                  <span>*</span> 版式:
+                </div>
+                <div className="myformBoxR">
+                  <Radio.Group
+                    onChange={(e) => setImgNum(e.target.value)}
+                    value={imgNum}
+                  >
+                    <Radio value={1}>1</Radio>
+                    <Radio value={2}>2</Radio>
+                  </Radio.Group>
+                </div>
+              </div>
+
+              {/* 图片上传 */}
+              <div className="myformBox myformBox0">
+                <div className="label"></div>
+                <div className="myformBoxR">
+                  <div className="fileBoxRow_r">
+                    <div className="upImgBox">
+                      <div
+                        hidden={imgList.length >= imgNum}
+                        className="fileBoxRow_up"
+                        onClick={() => myInput.current?.click()}
+                      >
+                        <PlusOutlined />
+                      </div>
+                      <ReactSortable
+                        list={imgList}
+                        setList={setImgList}
+                        className="fileImgListBox"
+                      >
+                        {imgList.map((v) => (
+                          <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">
+                                <CloseOutlined />
+                              </div>
+                            </Popconfirm>
+
+                            {/* 下面的预览和下载 */}
+                            <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>
+                    <div className="fileTit">
+                      {imgList.length && imgList.length >= 2 ? (
+                        <>
+                          按住鼠标可拖动图片调整顺序。
+                          <br />
+                        </>
+                      ) : null}
+                      {/* 建议尺寸:{12960 / imgNum}*1920 */}
+                      支持png、jpg和jpeg的图片格式;最大支持30M。
+                    </div>
+                  </div>
+                </div>
+              </div>
+            </>
+          ) : (
+            <>
+              {/* 视频上传 */}
+              <div className="myformBox myformBox2">
+                <div className="label">
+                  <span>*</span> 附件:
+                </div>
+                <div className="myformBoxR">
+                  {videoFile.filePath ? (
+                    <div className="fileInfo">
+                      <div className="upSuccTxt">{videoFile.fileName}</div>
+                      {/* 视频预览 */}
+                      <div
+                        className="clearCover"
+                        hidden={!videoFile.filePath}
+                        onClick={() =>
+                          store.dispatch({
+                            type: "layout/lookDom",
+                            payload: {
+                              src: videoFile.filePath!,
+                              type: "video",
+                            },
+                          })
+                        }
+                      >
+                        <EyeOutlined />
+                      </div>
+                      {/* 视频下载 */}
+                      <a
+                        href={baseURL + videoFile.filePath}
+                        download
+                        target="_blank"
+                        className="clearCover"
+                        rel="noreferrer"
+                      >
+                        <DownloadOutlined />
+                      </a>
+                      {/* 视频删除 */}
+                      <Popconfirm
+                        title="删除后无法恢复,是否删除?"
+                        okText="删除"
+                        cancelText="取消"
+                        onConfirm={() =>
+                          setVideoFile({
+                            id: 0,
+                            fileName: "",
+                            filePath: "",
+                          })
+                        }
+                      >
+                        <div className="clearCover">
+                          <CloseOutlined />
+                        </div>
+                      </Popconfirm>
+                    </div>
+                  ) : (
+                    <>
+                      <Button
+                        onClick={() => myInput.current?.click()}
+                        icon={<UploadOutlined />}
+                      >
+                        上传
+                      </Button>
+
+                      <div className="fileTit">
+                        仅支持MP4格式的视频文件,大小不得超过500MB。
+                      </div>
+                    </>
+                  )}
+                </div>
+              </div>
+            </>
+          )}
+
+          {/* 校验提示 */}
+          <div
+            className={classNames(
+              "noUpThumb",
+              btnOkCheck && imgCheck ? "noUpThumbAc" : ""
+            )}
+          >
+            {topType === "img" ? `请上传 ${imgNum} 张图片!` : "请上传附件"}
+          </div>
+
+          {/* 确定和取消按钮 */}
+          <br />
+          <Form.Item wrapperCol={{ offset: 9, span: 16 }}>
+            <Button type="primary" htmlType="submit">
+              提交
+            </Button>
+            &emsp;
+            <Button
+              disabled={!imgList.length}
+              onClick={() => setLookImg(true)}
+              hidden={topType === "video"}
+            >
+              预览效果
+            </Button>
+            &emsp;
+            <Popconfirm
+              title="放弃编辑后,信息将不会保存!"
+              okText="放弃"
+              cancelText="取消"
+              onConfirm={() => closeMoalFu("取消")}
+            >
+              <Button>取消</Button>
+            </Popconfirm>
+          </Form.Item>
+        </Form>
+      </div>
+
+      {/* 点击预览效果出来的页面 */}
+      {lookImg ? (
+        <WallLook imgList={imgList} closeMoalFu={() => setLookImg(false)} />
+      ) : null}
+    </div>
+  );
+}
+
+const MemoWallAdd = React.memo(WallAdd);
+
+export default MemoWallAdd;

+ 32 - 0
管理后台/src/pages/A2Wall/WallLook/index.module.scss

@@ -0,0 +1,32 @@
+.WallLook {
+  :global {
+    .ant-modal-close {
+      display: none;
+    }
+
+    .main {
+      border-top: 1px solid #999999;
+      padding-top: 15px;
+      width: 100%;
+
+      .imgListBox {
+        width: 100%;
+        display: flex;
+        justify-content: center;
+
+        .imgListRow {
+          width: 200px;
+          height: 200px;
+
+          img {
+            object-fit: contain;
+          }
+        }
+      }
+    }
+
+    .button {
+      text-align: center;
+    }
+  }
+}

+ 66 - 0
管理后台/src/pages/A2Wall/WallLook/index.tsx

@@ -0,0 +1,66 @@
+import ImageLazy from "@/components/ImageLazy";
+import { getWallDetailAPI } from "@/store/action/A2Wall";
+import { ImgListType } from "@/types";
+import { Button, Modal } from "antd";
+import React, { useCallback, useEffect, useState } from "react";
+import styles from "./index.module.scss";
+
+type Props = {
+  id?: number;
+  imgList?: ImgListType[];
+  closeMoalFu: () => void;
+};
+
+function WallLook({ id, imgList, closeMoalFu }: Props) {
+  const [imgShowList, setImgShowList] = useState<ImgListType[]>([]);
+
+  const getImgInfo = useCallback(async (id: number) => {
+    const res = await getWallDetailAPI(id);
+    const imgListRes = res.data.file;
+    setImgShowList(imgListRes);
+  }, []);
+
+  useEffect(() => {
+    if (id) getImgInfo(id);
+    else setImgShowList(imgList!);
+  }, [getImgInfo, id, imgList]);
+
+  // 动态修改模态框的宽度
+  useEffect(() => {
+    const dom: any = document.querySelector(".WallLook .ant-modal");
+    if (dom && imgShowList && imgShowList.length > 2)
+      dom.style.width = imgShowList.length * 200 + "px";
+  }, [imgShowList]);
+
+  return (
+    <Modal
+      wrapClassName={styles.WallLook}
+      destroyOnClose
+      open={true}
+      title="预览效果"
+      footer={
+        [] // 设置footer为空,去掉 取消 确定默认按钮
+      }
+    >
+      <div className="main">
+        {/* 渲染图片 */}
+        <div className="imgListBox">
+          {imgShowList.map((v) => (
+            <div className="imgListRow" key={v.id}>
+              <ImageLazy src={v.filePath!} width="100%" height="100%" />
+            </div>
+          ))}
+        </div>
+
+        <br />
+        <div className="button">
+          <Button onClick={closeMoalFu}>关闭</Button>
+        </div>
+      </div>
+    </Modal>
+  );
+}
+
+const MemoWallLook = React.memo(WallLook);
+
+export default MemoWallLook;

+ 46 - 0
管理后台/src/pages/A2Wall/WallTable/index.module.scss

@@ -0,0 +1,46 @@
+.WallTable {
+  border-radius: 10px;
+  background-color: #fff;
+  padding: 20px 15px 0;
+  height: calc(100% - 120px);
+
+  :global {
+    .titleTxt {
+      width: 1000px;
+      position: relative;
+
+      .tableBtn {
+        position: absolute;
+        right: 0;
+        top: 50%;
+        transform: translateY(-50%);
+      }
+    }
+
+    .table {
+      margin-top: 20px;
+      width: 1000px;
+
+      .ant-table-body {
+        height: 565px;
+        overflow-y: auto !important;
+        overflow-y: overlay !important;
+        .ant-table-row{
+          .ant-table-cell{
+            padding: 8px;
+          }
+        }
+      }
+    }
+
+    // 表头拖拽样式
+    .drop-over-downward td {
+      border-bottom: 2px dashed var(--themeColor2) !important;
+    }
+
+    .drop-over-upward td {
+      border-top: 2px dashed var(--themeColor2) !important;
+    }
+
+  }
+}

+ 297 - 0
管理后台/src/pages/A2Wall/WallTable/index.tsx

@@ -0,0 +1,297 @@
+import { Button, Popconfirm, Switch, Table, Tooltip } from "antd";
+import React, {
+  useCallback,
+  useEffect,
+  useMemo,
+  useRef,
+  useState,
+} from "react";
+import { ExclamationCircleFilled } from "@ant-design/icons";
+import styles from "./index.module.scss";
+
+// 表格拖动排序-----------------
+import { DndProvider, useDrag, useDrop } from "react-dnd";
+import { HTML5Backend } from "react-dnd-html5-backend";
+import { MessageFu } from "@/utils/message";
+import { WallTableListType } from "@/types";
+import { useDispatch, useSelector } from "react-redux";
+import store, { RootState } from "@/store";
+import {
+  awllDisplayAPI,
+  getWallDetailAPI,
+  getWallTableListAPI,
+  wallRemoveAPI,
+  wallSortAPI,
+} from "@/store/action/A2Wall";
+import WallLook from "../WallLook";
+
+type Props = {
+  tablePageIdFu: (id: number, scrollNum: number) => void;
+  scrollNumInfo: {
+    txt: string;
+    num: number;
+  };
+};
+
+function WallTable({ tablePageIdFu, scrollNumInfo }: Props) {
+  const dispatch = useDispatch();
+
+  useEffect(() => {
+    // 如果是编辑,或者是进新增/编辑页面点击了取消。控制表格滚动到之前的位置
+    if (scrollNumInfo.txt === "取消" || scrollNumInfo.txt === "编辑") {
+      if (scrollNumInfo.num > 0) {
+        const dom: any = document.querySelector("#wallTable .ant-table-body");
+        dom.scrollTop = scrollNumInfo.num;
+      }
+    }
+
+    dispatch(getWallTableListAPI());
+  }, [dispatch, scrollNumInfo]);
+
+  // 从仓库中获取列表数据
+  const results = useSelector((state: RootState) => state.A2Wall.list);
+
+  const editTableFu = useCallback(
+    (id: number) => {
+      if (id === -1 && results.length >= 20)
+        return MessageFu.warning("最多支持上传20个内容!");
+
+      // 获取当前表格的滚动位置
+      const dom: any = document.querySelector("#wallTable .ant-table-body");
+      tablePageIdFu(id, dom.scrollTop);
+    },
+    [results.length, tablePageIdFu]
+  );
+
+  // 点击删除
+  const delTableFu = useCallback(
+    async (id: number) => {
+      const res: any = await wallRemoveAPI(id);
+      if (res.code === 0) {
+        MessageFu.success("删除成功!");
+        dispatch(getWallTableListAPI());
+      }
+    },
+    [dispatch]
+  );
+
+  // 点击预览
+  const tableLookFu = useCallback(async (id: number, type: string) => {
+    if (type === "img") setLookImgId(id);
+    else {
+      // 通过id拿到视频地址
+      const res = await getWallDetailAPI(id);
+      if (res.code === 0) {
+        store.dispatch({
+          type: "layout/lookDom",
+          payload: {
+            src: res.data.file[0].filePath,
+            type: "video",
+          },
+        });
+      }
+    }
+  }, []);
+
+  // 切换表格中的启用停用状态
+  const isEnabledClickFu = useCallback(
+    async (val: boolean, id: number) => {
+      const isDisable = val ? 1 : 0;
+      const res: any = await awllDisplayAPI(id, isDisable);
+      if (res.code === 0) dispatch(getWallTableListAPI());
+    },
+    [dispatch]
+  );
+
+  const columns = useMemo(() => {
+    return [
+      {
+        width: 80,
+        title: "序号",
+        render: (text: any, record: any, index: any) => index + 1,
+      },
+      {
+        title: "内容类型",
+        render: (item: WallTableListType) =>
+          item.type === "img" ? "海报" : "视频",
+      },
+      {
+        title: "名称",
+        dataIndex: "name",
+      },
+      {
+        title: "展示状态",
+        render: (item: WallTableListType) => (
+          <Switch
+            checkedChildren="启用"
+            unCheckedChildren="停用"
+            checked={item.display === 1}
+            onChange={(val) => isEnabledClickFu(val, item.id)}
+          />
+        ),
+      },
+      {
+        title: "操作",
+        render: (item: WallTableListType) => (
+          <>
+            <Button
+              size="small"
+              type="text"
+              onClick={() => editTableFu(item.id)}
+            >
+              编辑
+            </Button>
+            <Button
+              size="small"
+              type="text"
+              onClick={() => tableLookFu(item.id, item.type)}
+            >
+              预览
+            </Button>
+            <Popconfirm
+              title="删除后无法恢复,是否删除?"
+              okText="删除"
+              cancelText="取消"
+              onConfirm={() => delTableFu(item.id)}
+            >
+              <Button size="small" type="text" danger>
+                删除
+              </Button>
+            </Popconfirm>
+          </>
+        ),
+      },
+    ];
+  }, [delTableFu, editTableFu, isEnabledClickFu, tableLookFu]);
+
+  // 表格拖动排序-----------------
+  interface DraggableBodyRowProps
+    extends React.HTMLAttributes<HTMLTableRowElement> {
+    index: number;
+    moveRow: (dragIndex: number, hoverIndex: number) => void;
+  }
+
+  const type = "DraggableBodyRow";
+
+  const DraggableBodyRow = useCallback(
+    ({
+      index,
+      moveRow,
+      className,
+      style,
+      ...restProps
+    }: DraggableBodyRowProps) => {
+      // eslint-disable-next-line react-hooks/rules-of-hooks
+      const ref = useRef<HTMLTableRowElement>(null);
+      // eslint-disable-next-line react-hooks/rules-of-hooks
+      const [{ isOver, dropClassName }, drop] = useDrop({
+        accept: type,
+        collect: (monitor) => {
+          const { index: dragIndex } = monitor.getItem() || {};
+          if (dragIndex === index) {
+            return {};
+          }
+          return {
+            isOver: monitor.isOver(),
+            dropClassName:
+              dragIndex < index ? " drop-over-downward" : " drop-over-upward",
+          };
+        },
+        drop: (item: { index: number }) => {
+          moveRow(item.index, index);
+        },
+      });
+      // eslint-disable-next-line react-hooks/rules-of-hooks
+      const [, drag] = useDrag({
+        type,
+        item: { index },
+        collect: (monitor) => ({
+          isDragging: monitor.isDragging(),
+        }),
+      });
+      drop(drag(ref));
+
+      return (
+        <tr
+          ref={ref}
+          className={`${className}${isOver ? dropClassName : ""}`}
+          style={{ cursor: "move", ...style }}
+          {...restProps}
+        />
+      );
+    },
+    []
+  );
+
+  const components = {
+    body: {
+      row: DraggableBodyRow,
+    },
+  };
+
+  const moveRow = useCallback(
+    async (dragIndex: number, hoverIndex: number) => {
+      if (dragIndex === hoverIndex) return;
+      // 交互位置-之前的id
+      const beforeId = results[dragIndex].id;
+      const afterId = results[hoverIndex].id;
+
+      const res = await wallSortAPI(beforeId, afterId);
+
+      if (res.code === 0) dispatch(getWallTableListAPI());
+    },
+    [dispatch, results]
+  );
+
+  // 点击预览
+  const [lookImgId, setLookImgId] = useState(0);
+
+  return (
+    <div className={styles.WallTable}>
+      <div className="titleTxt">
+        内容管理
+        <Tooltip title="按住鼠标可拖动表格调整顺序">
+          <div className="inco" hidden={results.length < 2}>
+            <ExclamationCircleFilled />
+          </div>
+        </Tooltip>
+        <div className="tableBtn">
+          <Button type="primary" onClick={() => editTableFu(-1)}>
+            新增
+          </Button>
+        </div>
+      </div>
+
+      {/* 表格主体 */}
+      <div className="table">
+        <DndProvider backend={HTML5Backend}>
+          <Table
+            id="wallTable"
+            scroll={{ y: 565 }}
+            columns={columns}
+            dataSource={results}
+            components={components}
+            rowKey="id"
+            pagination={false}
+            onRow={(_, index) => {
+              const attr = {
+                index,
+                moveRow,
+              };
+              return attr as React.HTMLAttributes<any>;
+            }}
+          />
+        </DndProvider>
+      </div>
+
+      {/* 点击预览打开的盒子 */}
+      {lookImgId ? (
+        <WallLook closeMoalFu={() => setLookImgId(0)} id={lookImgId} />
+      ) : null}
+    </div>
+  );
+}
+
+const MemoWallTable = React.memo(WallTable);
+
+export default MemoWallTable;

+ 47 - 3
管理后台/src/pages/A2Wall/index.module.scss

@@ -1,5 +1,49 @@
-.A2Wall{
-  :global{
-    
+.A2Wall {
+  :global {
+    .titleTxt {
+      position: relative;
+      z-index: 10;
+      font-weight: 700;
+      font-size: 16px;
+      display: flex;
+      align-items: center;
+
+      .inco {
+        cursor: pointer;
+        margin-left: 10px;
+        font-size: 14px;
+        color: #7e8293;
+      }
+
+      &::before {
+        content: '';
+        position: absolute;
+        left: 0;
+        top: 0;
+        width: 5px;
+        height: 22px;
+        background-color: var(--themeColor2);
+      }
+    }
+
+    .wallTopBox {
+      border-radius: 10px;
+      background-color: #fff;
+      padding: 15px;
+      margin-bottom: 15px;
+
+      .txt {
+        height: 60px;
+        display: flex;
+        align-items: center;
+      }
+
+      .edit {
+        height: 60px;
+        display: flex;
+        align-items: center;
+      }
+
+    }
   }
 }

+ 162 - 5
管理后台/src/pages/A2Wall/index.tsx

@@ -1,12 +1,169 @@
-import React from "react";
+import { Button, Radio, Tooltip, DatePicker, Popconfirm } from "antd";
+import React, { useCallback, useEffect, useRef, useState } from "react";
+import { ExclamationCircleFilled } from "@ant-design/icons";
 import styles from "./index.module.scss";
- function A2Wall() {
-  
+import dayjs from "dayjs";
+import { editWallAutoApi, getWallAutoApi } from "@/store/action/A2Wall";
+import { MessageFu } from "@/utils/message";
+import WallTable from "./WallTable";
+import WallAdd from "./WallAdd";
+const { RangePicker } = DatePicker;
+
+function A2Wall() {
+  // 获取自动播放信息
+  const getWallAutoApiFu = useCallback(async () => {
+    const res = await getWallAutoApi();
+    if (res.code === 0) {
+      const data = JSON.parse(res.data.content);
+      setValue0(data.isAuto);
+      setTime0(data.startTime + "," + data.endTime);
+    }
+  }, []);
+
+  useEffect(() => {
+    getWallAutoApiFu();
+  }, [getWallAutoApiFu]);
+
+  // 场景管理的编辑
+  const [edit, setEdit] = useState(false);
+
+  // 自动轮播开启关闭的展示
+  const [value0, setValue0] = useState(-1);
+
+  // 自动轮播开启关闭的修改
+  const [value1, setValue1] = useState(0);
+
+  // 自动轮播时间的展示
+  const [time0, setTime0] = useState(" , ");
+
+  // 自动轮播时间的修改
+  const [time1, setTime1] = useState<string[]>([]);
+
+  // 点击修改
+  const editAutoPlay = useCallback(() => {
+    setValue1(value0);
+    setTime1(time0.split(","));
+    setEdit(true);
+  }, [time0, value0]);
+
+  // 时间选择器改变
+  const timeChange = (date: any, dateString: any) => {
+    setTime1(dateString);
+  };
+
+  // 点击确定
+  const btnOk = useCallback(async () => {
+    const obj = {
+      endTime: time1[1],
+      startTime: time1[0],
+      isAuto: value1,
+    };
+
+    const res: any = await editWallAutoApi({ content: JSON.stringify(obj) });
+    if (res.code === 0) {
+      MessageFu.success("修改成功!");
+      getWallAutoApiFu();
+    }
+    setEdit(false);
+  }, [getWallAutoApiFu, time1, value1]);
+
+  // 点击新增或者编辑
+  const [editId, setEditId] = useState(0);
+
+  const scrollRef = useRef({
+    txt: "",
+    num: 0,
+  });
+
+  const tablePageIdFu = useCallback((id: number, scrollNum: number) => {
+    scrollRef.current = { ...scrollRef.current, num: scrollNum };
+    setEditId(id);
+  }, []);
+
+  // 从新增/编辑页面点击取消或者提交
+  const closeWallAddFu = useCallback((txt: string) => {
+    scrollRef.current = { ...scrollRef.current, txt };
+    setEditId(0);
+  }, []);
+
   return (
     <div className={styles.A2Wall}>
-      <h1>A2Wall</h1>
+      <div className="pageTitle">
+        {editId === 0 ? "万物墙管理" : editId > 0 ? "编辑海报" : "新增海报"}
+      </div>
+
+      {editId === 0 ? (
+        // 初始页面盒子
+        <>
+          <div className="wallTopBox">
+            <div className="titleTxt">
+              自动播放
+              <Tooltip title="当超过15秒未操作时,将按顺序自动播放下列内容">
+                <div className="inco">
+                  <ExclamationCircleFilled />
+                </div>
+              </Tooltip>
+            </div>
+            {edit ? (
+              // 修改信息
+              <div className="edit">
+                <div>自动播放:</div>
+                <Radio.Group
+                  onChange={(e) => setValue1(e.target.value)}
+                  value={value1}
+                >
+                  <Radio value={0}>关闭</Radio>
+                  <Radio value={1}>开启</Radio>
+                </Radio.Group>
+                &emsp;
+                <RangePicker
+                  allowClear={false}
+                  defaultValue={[
+                    dayjs(time1[0], "YYYY/MM/DD"),
+                    dayjs(time1[1], "YYYY/MM/DD"),
+                  ]}
+                  onChange={timeChange}
+                />
+                &emsp;
+                <Button type="primary" onClick={btnOk}>
+                  确定
+                </Button>
+                &emsp;
+                <Popconfirm
+                  title="放弃编辑后,信息将不会保存!"
+                  okText="放弃"
+                  cancelText="取消"
+                  onConfirm={() => setEdit(false)}
+                >
+                  <Button>取消</Button>
+                </Popconfirm>
+              </div>
+            ) : (
+              // 展示信息
+              <div className="txt">
+                <div>自动播放:</div>
+                <div>{value0 ? "开启" : "关闭"}&emsp;|&emsp;</div>
+                <div>
+                  {time0.split(",")[0]} 至 {time0.split(",")[1]}
+                </div>
+                &emsp;&emsp;
+                <Button type="primary" onClick={editAutoPlay}>
+                  修改
+                </Button>
+              </div>
+            )}
+          </div>
+          {/* 表格相关 */}
+          <WallTable
+            tablePageIdFu={tablePageIdFu}
+            scrollNumInfo={scrollRef.current}
+          />
+        </>
+      ) : (
+        <WallAdd id={editId} closeMoalFu={closeWallAddFu} />
+      )}
     </div>
-  )
+  );
 }
 
 const MemoA2Wall = React.memo(A2Wall);

+ 19 - 0
管理后台/src/pages/A3User/UserAdd/index.module.scss

@@ -0,0 +1,19 @@
+.userAdd {
+  :global {
+    .ant-modal-close {
+      display: none;
+    }
+
+    .userAddMain {
+      border-top: 1px solid #999999;
+      padding-top: 15px;
+      width: 100%;
+
+      .passTit {
+        color: #ff4d4f;
+        font-size: 14px;
+        padding-left: 98px;
+      }
+    }
+  }
+}

+ 159 - 0
管理后台/src/pages/A3User/UserAdd/index.tsx

@@ -0,0 +1,159 @@
+import { RootState } from "@/store";
+import { getUserInfoByIdAPI, userSaveAPI } from "@/store/action/A3user";
+import { SaveUserType } from "@/types";
+import { MessageFu } from "@/utils/message";
+import {
+  Button,
+  Form,
+  FormInstance,
+  Input,
+  Modal,
+  Popconfirm,
+  Select,
+} from "antd";
+import React, { useCallback, useEffect, useRef } from "react";
+import { useSelector } from "react-redux";
+import styles from "./index.module.scss";
+
+type Props = {
+  id: any;
+  closePage: () => void;
+  upTableList: () => void;
+  addTableList: () => void;
+};
+
+function UserAdd({ id, closePage, upTableList, addTableList }: Props) {
+  // 设置表单初始数据(区分编辑和新增)
+  const FormBoxRef = useRef<FormInstance>(null);
+
+  const getInfoInAPIFu = useCallback(async (id: number) => {
+    const res = await getUserInfoByIdAPI(id);
+    FormBoxRef.current?.setFieldsValue(res.data);
+    console.log("是编辑,在这里发请求拿数据", res);
+  }, []);
+
+  // 没有通过校验
+  const onFinishFailed = useCallback(() => {
+    // return MessageFu.warning("有表单不符号规则!");
+  }, []);
+
+  useEffect(() => {
+    if (id) getInfoInAPIFu(id);
+    else {
+      FormBoxRef.current?.setFieldsValue({});
+    }
+  }, [getInfoInAPIFu, id]);
+
+  // 从仓库获取角色下拉列表信息
+  const roleList = useSelector(
+    (state: RootState) => state.A3User.roleList
+  );
+
+  // 通过校验点击确定
+  const onFinish = useCallback(
+    async (values: any) => {
+      const obj: SaveUserType = {
+        ...values,
+        id: id ? id : null,
+      };
+
+      const res: any = await userSaveAPI(obj);
+
+      if (res.code === 0) {
+        MessageFu.success(id ? "编辑成功!" : "新增成功!");
+        if (id) upTableList();
+        else addTableList();
+
+        closePage();
+      }
+      console.log("通过校验,点击确定");
+    },
+    [addTableList, closePage, id, upTableList]
+  );
+
+  return (
+    <Modal
+      wrapClassName={styles.userAdd}
+      destroyOnClose
+      open={true}
+      title={id ? "编辑用户" : "新增用户"}
+      footer={
+        [] // 设置footer为空,去掉 取消 确定默认按钮
+      }
+    >
+      <div className="userAddMain">
+        <Form
+          ref={FormBoxRef}
+          name="basic"
+          labelCol={{ span: 5 }}
+          onFinish={onFinish}
+          onFinishFailed={onFinishFailed}
+          autoComplete="off"
+        >
+          <Form.Item
+            label="账号名"
+            name="userName"
+            rules={[{ required: true, message: "请输入账号名!" }]}
+            getValueFromEvent={(e) => e.target.value.replace(/\s+/g, "")}
+          >
+            <Input
+              disabled={id}
+              maxLength={15}
+              showCount
+              placeholder="请输入内容"
+            />
+          </Form.Item>
+
+          <Form.Item
+            label="用户昵称"
+            name="nickName"
+            rules={[{ required: true, message: "请输入用户昵称!" }]}
+            getValueFromEvent={(e) => e.target.value.replace(/\s+/g, "")}
+          >
+            <Input maxLength={8} showCount placeholder="请输入内容" />
+          </Form.Item>
+
+          <Form.Item
+            label="用户角色"
+            name="roleId"
+            rules={[{ required: true, message: "请选择角色!" }]}
+          >
+            <Select placeholder="请选择" options={roleList} />
+          </Form.Item>
+
+          <Form.Item
+            label="真实姓名"
+            name="realName"
+            rules={[{ required: true, message: "请输入真实姓名!" }]}
+            getValueFromEvent={(e) => e.target.value.replace(/\s+/g, "")}
+          >
+            <Input maxLength={8} showCount placeholder="请输入内容" />
+          </Form.Item>
+
+          {id ? null : <div className="passTit">* 默认密码 123456</div>}
+
+          {/* 确定和取消按钮 */}
+          <br />
+          <Form.Item wrapperCol={{ offset: 9, span: 16 }}>
+            <Button type="primary" htmlType="submit">
+              提交
+            </Button>
+            &emsp;
+            <Popconfirm
+              title="放弃编辑后,信息将不会保存!"
+              okText="放弃"
+              cancelText="取消"
+              onConfirm={closePage}
+            >
+              <Button>取消</Button>
+            </Popconfirm>
+          </Form.Item>
+        </Form>
+      </div>
+    </Modal>
+  );
+}
+
+const MemoUserAdd = React.memo(UserAdd);
+
+export default MemoUserAdd;

+ 30 - 3
管理后台/src/pages/A3User/index.module.scss

@@ -1,5 +1,32 @@
-.A3User{
-  :global{
-    
+.A3User {
+  :global {
+    .selectBox {
+      border-radius: 10px;
+      padding: 20px 15px;
+      background-color: #fff;
+      display: flex;
+      .selectBoxRow{
+        margin-right: 30px;
+      }
+    }
+    .tableBox {
+      border-radius: 10px;
+      overflow: hidden;
+      margin-top: 15px;
+      height: calc(100% - 80px);
+      background-color: #fff;
+
+      .ant-table-body {
+        height: 617px;
+        overflow-y: auto !important;
+        overflow-y: overlay !important;
+
+        .ant-table-row {
+          .ant-table-cell {
+            padding: 10px;
+          }
+        }
+      }
+    }
   }
 }

+ 340 - 5
管理后台/src/pages/A3User/index.tsx

@@ -1,12 +1,347 @@
-import React from "react";
+import { RootState } from "@/store";
+import {
+  getUserListAPI,
+  getUserRoleAPI,
+  userDisplayAPI,
+  userPassResetAPI,
+  userRemoveAPI,
+} from "@/store/action/A3user";
+import { UserTableAPIType, UserTableListType } from "@/types";
+import { MessageFu } from "@/utils/message";
+import { Input, DatePicker, Button, Table, Switch, Popconfirm } from "antd";
+import React, {
+  useCallback,
+  useEffect,
+  useMemo,
+  useRef,
+  useState,
+} from "react";
+import { useDispatch, useSelector } from "react-redux";
 import styles from "./index.module.scss";
- function A3User() {
-  
+import UserAdd from "./UserAdd";
+const { RangePicker } = DatePicker;
+
+function A3User() {
+  const dispatch = useDispatch();
+
+  const pageNumRef = useRef(1);
+  const pagePageRef = useRef(10);
+
+  // 顶部筛选
+  const [tableSelect, setTableSelect] = useState<UserTableAPIType>({
+    startTime: "",
+    endTime: "",
+    nickName: "",
+    pageNum: 1,
+    pageSize: 10,
+    realName: "",
+    searchKey: "",
+  });
+
+  // 进来用户管理页面获取角色的下拉列表
+  useEffect(() => {
+    dispatch(getUserRoleAPI());
+  }, [dispatch]);
+
+  // 封装发送请求的函数
+
+  const getList = useCallback(async () => {
+    const data = {
+      ...tableSelect,
+      pageNum: pageNumRef.current,
+    };
+    dispatch(getUserListAPI(data));
+  }, [dispatch, tableSelect]);
+
+  // 当前页码统一
+  useEffect(() => {
+    pageNumRef.current = tableSelect.pageNum;
+    pagePageRef.current = tableSelect.pageSize;
+  }, [tableSelect.pageNum, tableSelect.pageSize]);
+
+  // 防止发送了2次请求来对应页码
+
+  const getListRef = useRef(-1);
+
+  useEffect(() => {
+    clearTimeout(getListRef.current);
+    getListRef.current = window.setTimeout(() => {
+      getList();
+    }, 100);
+  }, [getList, tableSelect]);
+
+  // 用户昵称的输入
+  const nameTime = useRef(-1);
+  const nameChange = useCallback(
+    (e: React.ChangeEvent<HTMLInputElement>) => {
+      clearTimeout(nameTime.current);
+      nameTime.current = window.setTimeout(() => {
+        setTableSelect({
+          ...tableSelect,
+          nickName: e.target.value,
+          pageNum: 1,
+        });
+      }, 500);
+    },
+    [tableSelect]
+  );
+
+  // 真实姓名的输入
+  const realNameTime = useRef(-1);
+  const realNameChange = useCallback(
+    (e: React.ChangeEvent<HTMLInputElement>) => {
+      clearTimeout(realNameTime.current);
+      realNameTime.current = window.setTimeout(() => {
+        setTableSelect({
+          ...tableSelect,
+          realName: e.target.value,
+          pageNum: 1,
+        });
+      }, 500);
+    },
+    [tableSelect]
+  );
+
+  // 时间选择器改变
+  const timeChange = (date: any, dateString: any) => {
+    let startTime = "";
+    let endTime = "";
+    if (dateString[0] && dateString[1]) {
+      startTime = dateString[0] + " 00:00:00";
+      endTime = dateString[1] + " 23:59:59";
+    }
+    setTableSelect({ ...tableSelect, startTime, endTime, pageNum: 1 });
+  };
+
+  // 点击重置
+  const [inputKey, setInputKey] = useState(1);
+  const resetSelectFu = useCallback(() => {
+    // 把2个输入框和时间选择器清空
+    setInputKey(Date.now());
+    setTableSelect({
+      startTime: "",
+      endTime: "",
+      nickName: "",
+      pageNum: 1,
+      pageSize: 10,
+      realName: "",
+      searchKey: "",
+    });
+  }, []);
+
+  // 从仓库中获取表格数据
+  const tableInfo = useSelector((state: RootState) => state.A3User.tableInfo);
+
+  // 页码变化
+  const paginationChange = useCallback(
+    () => (pageNum: number, pageSize: number) => {
+      pageNumRef.current = pageNum;
+      pagePageRef.current = pageSize;
+      setTableSelect({ ...tableSelect, pageNum, pageSize });
+    },
+    [tableSelect]
+  );
+
+  // 切换表格中的启用停用状态
+  const isEnabledClickFu = useCallback(
+    async (val: boolean, id: number) => {
+      const isDisable = val ? 1 : 0;
+      const res: any = await userDisplayAPI(id, isDisable);
+      if (res.code === 0) getList();
+    },
+    [getList]
+  );
+
+  // 点击删除
+  const delTableFu = useCallback(
+    async (id: number) => {
+      const res: any = await userRemoveAPI(id);
+      if (res.code === 0) {
+        MessageFu.success("删除成功!");
+        getList();
+      }
+    },
+    [getList]
+  );
+
+  // 点击重置密码
+  const resetPassFu = useCallback(async (id: number) => {
+    const res: any = await userPassResetAPI(id);
+    if (res.code === 0) MessageFu.success("重置成功!");
+  }, []);
+
+  // 0------------点击新增或者编辑出来的页面
+  const [editPageShow, setEditPageShow] = useState(false);
+  const editId = useRef(0);
+
+  const openEditPageFu = useCallback(
+    (id: number) => {
+      if (id === 0 && tableInfo.list.length >= 50)
+        return MessageFu.warning("最多支持50个用户!");
+
+      editId.current = id;
+      setEditPageShow(true);
+    },
+    [tableInfo.list.length]
+  );
+
+  const columns = useMemo(() => {
+    return [
+      // {
+      //   width: 80,
+      //   title: "序号",
+      //   render: (text: any, record: any, index: any) =>
+      //     index + 1 + (pageNumRef.current - 1) * pagePageRef.current,
+      // },
+      {
+        title: "账号名",
+        dataIndex: "userName",
+      },
+      {
+        title: "用户昵称",
+        dataIndex: "nickName",
+      },
+      {
+        title: "用户角色",
+        dataIndex: "roleName",
+      },
+      {
+        title: "真实姓名",
+        dataIndex: "realName",
+      },
+      {
+        title: "注册时间",
+        dataIndex: "createTime",
+      },
+
+      {
+        title: "启用状态",
+        render: (item: UserTableListType) => (
+          <Switch
+            disabled={item.isAdmin === 1}
+            checkedChildren="启用"
+            unCheckedChildren="停用"
+            checked={item.isEnabled === 1}
+            onChange={(val) => isEnabledClickFu(val, item.id!)}
+          />
+        ),
+      },
+
+      {
+        title: "操作",
+        render: (item: UserTableListType) => {
+          return item.isAdmin === 1 ? (
+            "-"
+          ) : (
+            <>
+              <Popconfirm
+                title="密码重制后为123456,是否重置?"
+                okText="重置"
+                cancelText="取消"
+                onConfirm={() => resetPassFu(item.id!)}
+              >
+                <Button size="small" type="text">
+                  重置密码
+                </Button>
+              </Popconfirm>
+
+              <Button
+                size="small"
+                type="text"
+                onClick={() => openEditPageFu(item.id!)}
+              >
+                编辑
+              </Button>
+              <Popconfirm
+                title="删除后无法恢复,是否删除?"
+                okText="删除"
+                cancelText="取消"
+                onConfirm={() => delTableFu(item.id!)}
+              >
+                <Button size="small" type="text" danger>
+                  删除
+                </Button>
+              </Popconfirm>
+            </>
+          );
+        },
+      },
+    ];
+  }, [delTableFu, isEnabledClickFu, openEditPageFu, resetPassFu]);
+
   return (
     <div className={styles.A3User}>
-      <h1>A3User</h1>
+      <div className="pageTitle">用户管理</div>
+      <div className="userTop">
+        <div className="selectBox">
+          <div className="selectBoxRow">
+            <span>用户昵称:</span>
+            <Input
+              key={inputKey}
+              maxLength={8}
+              style={{ width: 150 }}
+              placeholder="请输入"
+              allowClear
+              onChange={(e) => nameChange(e)}
+            />
+          </div>
+
+          <div className="selectBoxRow">
+            <span>真实姓名:</span>
+            <Input
+              key={inputKey}
+              maxLength={8}
+              style={{ width: 150 }}
+              placeholder="请输入"
+              allowClear
+              onChange={(e) => realNameChange(e)}
+            />
+          </div>
+
+          <div className="selectBoxRow">
+            <span>注册日期:</span>
+            <RangePicker key={inputKey} onChange={timeChange} />
+          </div>
+
+          <div className="selectBoxRow">
+            &emsp;&emsp;<Button onClick={resetSelectFu}>重置</Button>
+            &emsp;&emsp;
+            <Button type="primary" onClick={() => openEditPageFu(0)}>
+              新增
+            </Button>
+          </div>
+        </div>
+      </div>
+      {/* 表格主体 */}
+      <div className="tableBox">
+        <Table
+          scroll={{ y: 617 }}
+          dataSource={tableInfo.list}
+          columns={columns}
+          rowKey="id"
+          pagination={{
+            showQuickJumper: true,
+            position: ["bottomCenter"],
+            showSizeChanger: true,
+            current: tableSelect.pageNum,
+            pageSize: tableSelect.pageSize,
+            total: tableInfo.total,
+            onChange: paginationChange(),
+          }}
+        />
+      </div>
+
+      {/* 点击新增或者编辑 */}
+      {editPageShow ? (
+        <UserAdd
+          id={editId.current}
+          closePage={() => setEditPageShow(false)}
+          upTableList={getList}
+          addTableList={resetSelectFu}
+        />
+      ) : null}
     </div>
-  )
+  );
 }
 
 const MemoA3User = React.memo(A3User);

+ 70 - 0
管理后台/src/pages/A4Role/RoleAdd/index.module.scss

@@ -0,0 +1,70 @@
+.roleAdd {
+  :global {
+    .ant-modal-close {
+      display: none;
+    }
+
+    .roleAddMain {
+      border-top: 1px solid #999999;
+      padding-top: 15px;
+      width: 100%;
+
+      .row {
+        margin-bottom: 20px;
+        position: relative;
+        display: flex;
+        padding: 0 24px 0 0px;
+        text-align: right;
+
+        .rowSpan {
+          display: inline-block;
+          width: 80px;
+          line-height: 32px;
+
+          &>span {
+            position: relative;
+            top: 2px;
+            color: #ff4d4f;
+          }
+        }
+
+        .bs {
+          &::before {
+            content: '*';
+            position: absolute;
+            top: 2px;
+            left: 1px;
+            z-index: 10;
+            color: #ff4d4f;
+          }
+        }
+
+        .inputBox {
+          width: calc(100% - 90px);
+
+        }
+
+        .inputBoxCheck {
+          width: 155px;
+
+          .rowCheck {
+            display: block;
+            display: flex;
+            margin-left: 4px;
+            margin-top: 5px;
+          }
+        }
+      }
+
+      .roleAddButton {
+        text-align: center;
+      }
+    }
+
+    .lookRole {
+      .row {
+        pointer-events: none;
+      }
+    }
+  }
+}

+ 201 - 0
管理后台/src/pages/A4Role/RoleAdd/index.tsx

@@ -0,0 +1,201 @@
+import { getRoleInfoByIdAPI, roleSaveAPI } from "@/store/action/A4Role";
+
+import { Button, Checkbox, Input, Modal, Popconfirm } from "antd";
+import React, { useCallback, useEffect, useState } from "react";
+import classNames from "classnames";
+import styles from "./index.module.scss";
+import { AddRoleType, PermissionsAPIType, RoleTableType } from "@/types";
+import { MessageFu } from "@/utils/message";
+const { TextArea } = Input;
+
+type Props = {
+  id: any;
+  closePage: () => void;
+  upTableList: () => void;
+  addTableList: () => void;
+};
+
+function RoleAdd({ id, closePage, upTableList, addTableList }: Props) {
+  // 角色名称
+  const [roleName, setRoleName] = useState("");
+
+  // 角色描述
+  const [roleDesc, setRoleDesc] = useState("");
+
+  // 页面权限的选择
+  const [list, setList] = useState<PermissionsAPIType[]>([
+    {
+      id: 100,
+      name: "馆藏管理",
+      authority: true,
+    },
+    {
+      id: 200,
+      name: "万物墙管理",
+      authority: true,
+    },
+  ]);
+
+  const checkChangeFu = useCallback(
+    (checked: boolean, id: number) => {
+      const newList = list.map((v) => {
+        return {
+          ...v,
+          authority: v.id === id ? checked : v.authority,
+        };
+      });
+
+      setList(newList);
+    },
+    [list]
+  );
+
+  const getRoleInfoByIdFu = useCallback(async (id: number) => {
+    const res = await getRoleInfoByIdAPI(id);
+    const info: RoleTableType = res.data.role;
+    const authPageArr: PermissionsAPIType[] = res.data.permission;
+    setRoleName(info.roleName);
+    setRoleDesc(info.roleDesc);
+    setList(authPageArr);
+  }, []);
+  // 如果是编辑
+  useEffect(() => {
+    if (id) getRoleInfoByIdFu(id);
+  }, [getRoleInfoByIdFu, id]);
+
+  // 点击提交
+  const btnOkFu = useCallback(async () => {
+    if (roleName === "") return MessageFu.warning("请输入角色名称!");
+
+    let temp1 = true;
+
+    const resources: number[] = [];
+
+    list.forEach((v) => {
+      if (v.authority) {
+        temp1 = false;
+        resources.push(v.id);
+      }
+    });
+
+    if (temp1) return MessageFu.warning("至少勾选一个页面权限!");
+
+    const obj: AddRoleType = {
+      id: id ? id : null,
+      resources,
+      roleDesc: roleDesc,
+      roleName: roleName,
+    };
+    const res: any = await roleSaveAPI(obj);
+
+    if (res.code === 0) {
+      MessageFu.success(id ? "编辑成功!" : "新增成功!");
+      closePage();
+      if (id) addTableList();
+      else upTableList();
+    }
+  }, [addTableList, closePage, id, list, roleDesc, roleName, upTableList]);
+
+  return (
+    <Modal
+      wrapClassName={styles.roleAdd}
+      destroyOnClose
+      open={true}
+      title={id ? (id === 1 ? "查看角色" : "编辑角色") : "新增角色"}
+      footer={
+        [] // 设置footer为空,去掉 取消 确定默认按钮
+      }
+    >
+      <div className={classNames("roleAddMain", id === 1 ? "lookRole" : "")}>
+        <div className="row">
+          <span className="bs rowSpan">角色名称:</span>
+          <div className="inputBox">
+            <Input
+              maxLength={8}
+              value={roleName}
+              onChange={(e) => setRoleName(e.target.value.replace(/\s+/g, ""))}
+              showCount
+              placeholder="请输入内容"
+            />
+          </div>
+        </div>
+
+        <div className="row">
+          <span className="rowSpan">角色描述:</span>
+          <div className="inputBox">
+            <TextArea
+              rows={4}
+              placeholder="请输入内容"
+              maxLength={100}
+              showCount
+              value={roleDesc}
+              onChange={(e) => setRoleDesc(e.target.value.replace(/\s+/g, ""))}
+            />
+          </div>
+        </div>
+
+        <div className="row">
+          <span className="rowSpan">
+            <span>*</span> 权限设置:
+          </span>
+          <div className="inputBox inputBoxCheck">
+            {list.map((v) => (
+              <Checkbox
+                className="rowCheck"
+                key={v.id}
+                checked={v.authority}
+                onChange={(e) => checkChangeFu(e.target.checked, v.id)}
+              >
+                {v.name}
+              </Checkbox>
+            ))}
+          </div>
+        </div>
+
+        {id === 1 ? (
+          <div className="row">
+            <span className="rowSpan">
+              <span>*</span>系统管理:
+            </span>
+            <div className="inputBox inputBoxCheck">
+              <Checkbox className="rowCheck" defaultChecked={true}>
+                用户管理
+              </Checkbox>
+              <Checkbox className="rowCheck" defaultChecked={true}>
+                角色管理
+              </Checkbox>
+              <Checkbox className="rowCheck" defaultChecked={true}>
+                操作日志
+              </Checkbox>
+            </div>
+          </div>
+        ) : null}
+
+        <div className="roleAddButton">
+          {id !== 1 ? (
+            <>
+              <Button type="primary" onClick={btnOkFu}>
+                提交
+              </Button>
+              &emsp;
+              <Popconfirm
+                title="放弃编辑后,信息将不会保存!"
+                okText="放弃"
+                cancelText="取消"
+                onConfirm={closePage}
+              >
+                <Button>取消</Button>
+              </Popconfirm>
+            </>
+          ) : (
+            <Button onClick={closePage}>关闭</Button>
+          )}
+        </div>
+      </div>
+    </Modal>
+  );
+}
+
+const MemoRoleAdd = React.memo(RoleAdd);
+
+export default MemoRoleAdd;

+ 28 - 1
管理后台/src/pages/A4Role/index.module.scss

@@ -1,5 +1,32 @@
 .A4Role{
   :global{
-    
+    .searchTop{
+      border-radius: 10px;
+      padding: 20px 15px;
+      background-color: #fff;
+      display: flex;
+      .searchTopRow{
+        margin-right: 30px;
+      }
+    }
+        .tableBox {
+      border-radius: 10px;
+      overflow: hidden;
+      margin-top: 15px;
+      height: calc(100% - 80px);
+      background-color: #fff;
+
+      .ant-table-body {
+        height: 617px;
+        overflow-y: auto !important;
+        overflow-y: overlay !important;
+
+        .ant-table-row {
+          .ant-table-cell {
+            padding: 10px;
+          }
+        }
+      }
+    }
   }
 }

+ 228 - 5
管理后台/src/pages/A4Role/index.tsx

@@ -1,12 +1,235 @@
-import React from "react";
+import { RootState } from "@/store";
+import {
+  getRoleListAPI,
+  roleDisplayAPI,
+  roleRemoveAPI,
+} from "@/store/action/A4Role";
+import { RoleTableType } from "@/types";
+import { MessageFu } from "@/utils/message";
+import { Button, Input, Popconfirm, Switch, Table } from "antd";
+import React, {
+  useCallback,
+  useEffect,
+  useMemo,
+  useRef,
+  useState,
+} from "react";
+import { useDispatch, useSelector } from "react-redux";
 import styles from "./index.module.scss";
- function A4Role() {
-  
+import RoleAdd from "./RoleAdd";
+function A4Role() {
+  const dispatch = useDispatch();
+
+  // 顶部筛选
+
+  type TableListType = {
+    pageNum: number;
+    pageSize: number;
+    searchKey: string;
+  };
+
+  const [tableSelect, setTableSelect] = useState<TableListType>({
+    pageNum: 1,
+    pageSize: 10,
+    searchKey: "",
+  });
+
+  useEffect(() => {
+    dispatch(getRoleListAPI(tableSelect));
+  }, [dispatch, tableSelect]);
+
+  // 角色名的输入
+  const nameTime = useRef(-1);
+  const nameChange = useCallback(
+    (e: React.ChangeEvent<HTMLInputElement>) => {
+      clearTimeout(nameTime.current);
+      nameTime.current = window.setTimeout(() => {
+        setTableSelect({
+          ...tableSelect,
+          searchKey: e.target.value,
+          pageNum: 1,
+        });
+      }, 500);
+    },
+    [tableSelect]
+  );
+
+  // 角色名的重置
+  const [inputKey, setInputKey] = useState(1);
+  const resetSelectFu = useCallback(() => {
+    // 把2个输入框和时间选择器清空
+    setInputKey(Date.now());
+    setTableSelect({
+      pageNum: 1,
+      pageSize: 10,
+      searchKey: "",
+    });
+  }, []);
+
+  // 切换表格中的启用停用状态
+  const isEnabledClickFu = useCallback(
+    async (val: boolean, id: number) => {
+      const isDisable = val ? 1 : 0;
+      const res: any = await roleDisplayAPI(id, isDisable);
+      if (res.code === 0) dispatch(getRoleListAPI(tableSelect));
+    },
+    [dispatch, tableSelect]
+  );
+
+  // 点击删除
+  const delTableFu = useCallback(
+    async (id: number) => {
+      const res: any = await roleRemoveAPI(id);
+      if (res.code === 0) {
+        MessageFu.success("删除成功!");
+        dispatch(getRoleListAPI(tableSelect));
+      }
+    },
+    [dispatch, tableSelect]
+  );
+
+  // 点击新增和编辑
+  const [editPageShow, setEditPageShow] = useState(false);
+  const editId = useRef(0);
+  const openEditPageFu = useCallback((id: number) => {
+    editId.current = id;
+    setEditPageShow(true);
+  }, []);
+
+  // 从仓库中获取表格数据
+  const tableInfo = useSelector(
+    (state: RootState) => state.A4Role.tableInfo
+  );
+
+  // 页码变化
+  const paginationChange = useCallback(
+    () => (pageNum: number, pageSize: number) => {
+      setTableSelect({ ...tableSelect, pageNum, pageSize });
+    },
+    [tableSelect]
+  );
+
+  const columns = useMemo(() => {
+    return [
+      // {
+      //   width: 80,
+      //   title: "序号",
+      //   render: (text: any, record: any, index: any) =>
+      //     index + 1 + (pageNumRef.current - 1) * pagePageRef.current,
+      // },
+      {
+        title: "角色名称",
+        dataIndex: "roleName",
+      },
+      {
+        title: "角色描述",
+        render: (item: RoleTableType) =>
+          item.roleDesc ? item.roleDesc : "(空)",
+      },
+      {
+        title: "成员数量",
+        dataIndex: "count",
+      },
+      {
+        title: "最后编辑时间",
+        dataIndex: "updateTime",
+      },
+
+      {
+        title: "启用状态",
+        render: (item: RoleTableType) => (
+          <Switch
+            disabled={item.roleKey === "sys_admin"}
+            checkedChildren="启用"
+            unCheckedChildren="停用"
+            checked={item.isEnabled === 1}
+            onChange={(val) => isEnabledClickFu(val, item.id!)}
+          />
+        ),
+      },
+
+      {
+        title: "操作",
+        render: (item: RoleTableType) => {
+          return (
+            <>
+              <Button
+                size="small"
+                type="text"
+                onClick={() => openEditPageFu(item.id!)}
+              >
+                {item.roleKey === "sys_admin" ? "查看" : "编辑"}
+              </Button>
+              {item.roleKey !== "sys_admin" ? (
+                <Popconfirm
+                  title="删除后无法恢复,是否删除?"
+                  okText="删除"
+                  cancelText="取消"
+                  onConfirm={() => delTableFu(item.id!)}
+                >
+                  <Button size="small" type="text" danger>
+                    删除
+                  </Button>
+                </Popconfirm>
+              ) : null}
+            </>
+          );
+        },
+      },
+    ];
+  }, [delTableFu, isEnabledClickFu, openEditPageFu]);
+
   return (
     <div className={styles.A4Role}>
-      <h1>A4Role</h1>
+      <div className="pageTitle">角色管理</div>
+      <div className="searchTop">
+        <div className="searchTopRow">
+          <span>角色名:</span>
+          <Input
+            key={inputKey}
+            maxLength={8}
+            style={{ width: 200 }}
+            placeholder="请输入"
+            allowClear
+            onChange={(e) => nameChange(e)}
+          />
+        </div>
+        <div className="searchTopRow">
+          &emsp;&emsp;
+          <Button type="primary" onClick={() => openEditPageFu(0)}>
+            新增
+          </Button>
+        </div>
+      </div>
+
+      <div className="tableBox">
+        <Table
+          scroll={{ y: 617 }}
+          dataSource={tableInfo.list}
+          columns={columns}
+          rowKey="id"
+          pagination={{
+            showQuickJumper: true,
+            position: ["bottomCenter"],
+            showSizeChanger: true,
+            current: tableSelect.pageNum,
+            pageSize: tableSelect.pageSize,
+            total: tableInfo.total,
+            onChange: paginationChange(),
+          }}
+        />
+      </div>
+      {/* 点击新增或者编辑 */}
+      {editPageShow ? (
+        <RoleAdd
+          id={editId.current}
+          closePage={() => setEditPageShow(false)}
+          upTableList={() => dispatch(getRoleListAPI(tableSelect))}
+          addTableList={resetSelectFu}
+        />
+      ) : null}
     </div>
-  )
+  );
 }
 
 const MemoA4Role = React.memo(A4Role);

+ 76 - 28
管理后台/src/pages/Layout/index.tsx

@@ -17,7 +17,7 @@ import { Base64 } from "js-base64";
 import encodeStr from "@/utils/pass";
 import { getDictListAPI, passWordEditAPI } from "@/store/action/layout";
 import { getTokenInfo, removeTokenInfo } from "@/utils/storage";
-import { useDispatch } from "react-redux";
+import { useDispatch, useSelector } from "react-redux";
 import inco1 from "@/assets/img/inco1.png";
 import inco2 from "@/assets/img/inco2.png";
 import inco3 from "@/assets/img/inco3.png";
@@ -25,20 +25,24 @@ import inco4 from "@/assets/img/inco4.png";
 import inco5 from "@/assets/img/inco5.png";
 import logonImg from "@/assets/img/logo.png";
 import { MessageFu } from "@/utils/message";
+import { RouterType } from "@/types";
+import { RootState } from "@/store";
+import { getPermissionsAPI } from "@/store/action/A4Role";
 
 const NotFound = React.lazy(() => import("@/components/NotFound"));
 
 function Layout() {
   const dispatch = useDispatch();
 
-  const list = useMemo(() => {
-    return [
+  const listTemp = useMemo(() => {
+    const arr: RouterType = [
       {
         id: 100,
         name: "馆藏管理",
         path: "/",
         Com: React.lazy(() => import("../A1Goods")),
         inco: inco1,
+        done: false,
       },
       {
         id: 200,
@@ -46,34 +50,79 @@ function Layout() {
         path: "/wall",
         Com: React.lazy(() => import("../A2Wall")),
         inco: inco2,
-      },
-      {
-        id: 300,
-        name: "用户管理",
-        path: "/user",
-        Com: React.lazy(() => import("../A3User")),
-        inco: inco3,
-      },
-      {
-        id: 400,
-        name: "角色管理",
-        path: "/role",
-        Com: React.lazy(() => import("../A4Role")),
-        inco: inco4,
-      },
-      {
-        id: 500,
-        name: "操作日志",
-        path: "/log",
-        Com: React.lazy(() => import("../A5Log")),
-        inco: inco5,
+        done: false,
       },
     ];
+    return arr;
   }, []);
 
-  // 进页面获取所有下拉信息
+  // 从仓库中获取页面权限数据
+  const authPageArr = useSelector(
+    (state: RootState) => state.A0Layout.authPageArr
+  );
+
+  useEffect(() => {
+    // 如果是超级管理员
+    const userInfo = getTokenInfo().user;
+    if (userInfo.isAdmin === 1) {
+      listTemp.push(
+        {
+          id: 300,
+          name: "用户管理",
+          path: "/user",
+          Com: React.lazy(() => import("../A3User")),
+          inco: inco3,
+          done: true,
+        },
+        {
+          id: 400,
+          name: "角色管理",
+          path: "/role",
+          Com: React.lazy(() => import("../A4Role")),
+          inco: inco4,
+          done: true,
+        },
+        {
+          id: 500,
+          name: "操作日志",
+          path: "/log",
+          Com: React.lazy(() => import("../A5Log")),
+          inco: inco5,
+          done: true,
+        }
+      );
+    }
+  }, [listTemp]);
+
+  // 权限的数据和页面判断
+  useEffect(() => {
+    if (authPageArr && authPageArr.length) {
+      authPageArr.forEach((v) => {
+        if (v.authority) {
+          listTemp.forEach((v2) => {
+            if (v.id === v2.id) v2.done = true;
+          });
+        }
+      });
+      const newList = listTemp.filter((v) => v.done);
+      setList(newList);
+    }
+  }, [authPageArr, listTemp]);
+
+  const [list, setList] = useState(listTemp);
+
+  // 进页面看看第一个页面有权限的是哪一个
+  useEffect(() => {
+    const userInfo = getTokenInfo().user;
+    if (userInfo.isAdmin !== 1) {
+      if (list[0] && list[0].id !== 100) history.replace(list[0].path);
+    }
+  }, [list]);
+
+  // 进页面获取所有下拉信息和权限信息
   useEffect(() => {
     dispatch(getDictListAPI());
+    dispatch(getPermissionsAPI());
   }, [dispatch]);
 
   // 点击跳转
@@ -127,9 +176,7 @@ function Layout() {
   // 点击退出登录
   const loginExit = () => {
     removeTokenInfo();
-    // history.push("/login");
-    // 跳到前台页面
-    window.location.href = "/web/index.html";
+    history.push("/login");
   };
 
   return (
@@ -144,6 +191,7 @@ function Layout() {
         <div className="layoutLeftMain">
           {list.map((v) => (
             <div
+              hidden={!v.done}
               key={v.id}
               onClick={() => pathCutFu(v.path)}
               className={classNames(

+ 7 - 1
管理后台/src/pages/Login/index.tsx

@@ -2,15 +2,21 @@ import styles from "./index.module.scss";
 
 import { Input, Button } from "antd";
 import { UserOutlined, LockOutlined } from "@ant-design/icons";
-import { useState } from "react";
+import { useEffect, useState } from "react";
 import { Base64 } from "js-base64";
 import encodeStr from "@/utils/pass";
 import { setTokenInfo } from "@/utils/storage";
 import history from "@/utils/history";
 import { MessageFu } from "@/utils/message";
 import { userLoginAPI } from "@/store/action/layout";
+import store from "@/store";
 
 export default function Login() {
+  // 进登录页面把权限的信息初始化,防止登录成功之后进到首页,数据渲染问题
+  useEffect(() => {
+    store.dispatch({ type: "layout/setAuthPageArr", payload: [] });
+  }, []);
+
   // 账号密码
   const [userName, setUserName] = useState("");
   const [passWord, setPassWord] = useState("");

+ 88 - 0
管理后台/src/store/action/A2Wall.ts

@@ -0,0 +1,88 @@
+import { WallSaveAPIType } from "@/types";
+import { domShowFu, progressDomFu } from "@/utils/domShow";
+import http from "@/utils/http";
+import axios from "axios";
+import store, { AppDispatch } from "..";
+
+/**
+ * 获取万物墙自动播放数据
+ */
+export const getWallAutoApi = () => {
+  return http.get("cms/wall/getConfig");
+};
+
+/**
+ * 修改万物墙自动播放数据
+ */
+export const editWallAutoApi = (data: { content: string }) => {
+  return http.post("cms/wall/setConfig", data);
+};
+
+/**
+ * 获取列表数据
+ */
+export const getWallTableListAPI = () => {
+  return async (dispatch: AppDispatch) => {
+    const res = await http.get("cms/wall/list");
+    if (res.code === 0) dispatch({ type: "wall/getList", payload: res.data });
+  };
+};
+
+/**
+ * 内容-是否显示
+ */
+export const awllDisplayAPI = (id: number, display: number) => {
+  return http.get(`cms/wall/display/${id}/${display}`);
+};
+
+/**
+ * 内容-删除
+ */
+export const wallRemoveAPI = (id: number) => {
+  return http.get(`cms/wall/remove/${id}`);
+};
+
+/**
+ * 内容-排序
+ */
+export const wallSortAPI = (id1: number, id2: number) => {
+  return http.get(`cms/wall/sort/${id1}/${id2}`);
+};
+
+/**
+ * 内容-新增|编辑
+ */
+export const setWallSave = (data: WallSaveAPIType) => {
+  return http.post("cms/wall/save", data);
+};
+
+const CancelToken = axios.CancelToken;
+/**
+ * 上传封面图和附件
+ */
+export const wallUploadAPI = (data: any) => {
+  domShowFu("#UpAsyncLoding", true);
+
+  return http.post("cms/wall/upload", 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 },
+      });
+    }),
+  });
+};
+
+/**
+ * 内容-新增|编辑
+ */
+export const getWallDetailAPI = (id: number) => {
+  return http.get(`cms/wall/detail/${id}`);
+};

+ 68 - 0
管理后台/src/store/action/A3user.ts

@@ -0,0 +1,68 @@
+import { RoleTableType, SaveUserType, UserTableAPIType } from "@/types";
+import http from "@/utils/http";
+import { AppDispatch } from "..";
+/**
+ * 获取用户管理表格列表
+ */
+export const getUserListAPI = (data: UserTableAPIType) => {
+  return async (dispatch: AppDispatch) => {
+    const res = await http.post("sys/user/list", data);
+    if (res.code === 0) {
+      const obj = {
+        list: res.data.records,
+        total: res.data.total,
+      };
+
+      dispatch({ type: "user/getList", payload: obj });
+    }
+  };
+};
+
+/**
+ * 获取用户管理-角色列表
+ */
+export const getUserRoleAPI = () => {
+  return async (dispatch: AppDispatch) => {
+    const res = await http.get("sys/user/getRole");
+    if (res.code === 0) {
+      const data: RoleTableType[] = res.data;
+      const newData = data.map((v) => ({ label: v.roleName, value: v.id }));
+      dispatch({ type: "user/getRoleList", payload: newData });
+    }
+  };
+};
+
+/**
+ * 用户-是否显示
+ */
+export const userDisplayAPI = (id: number, display: number) => {
+  return http.get(`sys/user/editStatus/${id}/${display}`);
+};
+
+/**
+ * 删除用户
+ */
+export const userRemoveAPI = (id: number) => {
+  return http.get(`sys/user/removes/${id}`);
+};
+
+/**
+ * 重置密码
+ */
+export const userPassResetAPI = (id: number) => {
+  return http.get(`sys/user/resetPass/${id}`);
+};
+
+/**
+ * 新增/修改用户信息
+ */
+export const userSaveAPI = (data: SaveUserType) => {
+  return http.post("sys/user/save", data);
+};
+
+/**
+ * 通过id获取角色详情
+ */
+export const getUserInfoByIdAPI = (id: number) => {
+  return http.get(`sys/user/detail/${id}`);
+};

+ 58 - 0
管理后台/src/store/action/A4Role.ts

@@ -0,0 +1,58 @@
+import { AddRoleType } from "@/types";
+import http from "@/utils/http";
+import { AppDispatch } from "..";
+/**
+ * 获取角色表格列表数据
+ */
+export const getRoleListAPI = (data: any) => {
+  return async (dispatch: AppDispatch) => {
+    const res = await http.post("sys/role/listCountPage", data);
+    if (res.code === 0) {
+      const obj = {
+        list: res.data.records,
+        total: res.data.total,
+      };
+
+      dispatch({ type: "Role/getList", payload: obj });
+    }
+  };
+};
+
+/**
+ * 删除角色
+ */
+export const roleRemoveAPI = (id: number) => {
+  return http.get(`sys/role/remove/${id}`);
+};
+
+/**
+ * 用户-是否显示
+ */
+export const roleDisplayAPI = (id: number, display: number) => {
+  return http.get(`sys/role/editStatus/${id}/${display}`);
+};
+
+/**
+ * 获取用户的权限信息
+ */
+export const getPermissionsAPI = () => {
+  return async (dispatch: AppDispatch) => {
+    const res = await http.get("sys/resource/getTreePermissions");
+    if (res.code === 0)
+      dispatch({ type: "layout/setAuthPageArr", payload: res.data });
+  };
+};
+
+/**
+ * 新增或修改角色
+ */
+export const roleSaveAPI = (data: AddRoleType) => {
+  return http.post("sys/role/save", data);
+};
+
+/**
+ * 通过id获取角色详情
+ */
+export const getRoleInfoByIdAPI = (id: number) => {
+  return http.get(`sys/role/detail/${id}`);
+};

+ 21 - 0
管理后台/src/store/reducer/A2Wall.ts

@@ -0,0 +1,21 @@
+import { WallTableListType } from "@/types";
+
+// 初始化状态
+const initState = {
+  // 列表数据
+  list: [] as WallTableListType[],
+};
+
+// 定义 action 类型
+type Props = { type: "wall/getList"; payload: WallTableListType[] };
+
+// 频道 reducer
+export default function wallReducer(state = initState, action: Props) {
+  switch (action.type) {
+    // 获取列表数据
+    case "wall/getList":
+      return { ...state, list: action.payload };
+    default:
+      return state;
+  }
+}

+ 44 - 0
管理后台/src/store/reducer/A3User.ts

@@ -0,0 +1,44 @@
+import { UserTableListType } from "@/types";
+
+// 初始化状态
+const initState = {
+  // 列表数据
+  tableInfo: {
+    list: [] as UserTableListType[],
+    total: 0,
+  },
+  // 角色列表数据
+  roleList: [] as {
+    label: string;
+    value: number;
+  }[],
+};
+
+// 定义 action 类型
+type Props =
+  | {
+      type: "user/getList";
+      payload: { list: UserTableListType[]; total: number };
+    }
+  | {
+      type: "user/getRoleList";
+      payload: {
+        label: string;
+        value: number;
+      }[];
+    };
+
+// 频道 reducer
+export default function userReducer(state = initState, action: Props) {
+  switch (action.type) {
+    // 获取列表数据
+    case "user/getList":
+      return { ...state, tableInfo: action.payload };
+    // 获取角色列表数据
+    case "user/getRoleList":
+      return { ...state, roleList: action.payload };
+
+    default:
+      return state;
+  }
+}

+ 27 - 0
管理后台/src/store/reducer/A4Role.ts

@@ -0,0 +1,27 @@
+import { RoleTableType } from "@/types";
+
+// 初始化状态
+const initState = {
+  // 列表数据
+  tableInfo: {
+    list: [] as RoleTableType[],
+    total: 0,
+  },
+};
+
+// 定义 action 类型
+type Props = {
+  type: "Role/getList";
+  payload: { list: RoleTableType[]; total: number };
+};
+
+// 频道 reducer
+export default function RoleReducer(state = initState, action: Props) {
+  switch (action.type) {
+    // 获取列表数据
+    case "Role/getList":
+      return { ...state, tableInfo: action.payload };
+    default:
+      return state;
+  }
+}

+ 6 - 0
管理后台/src/store/reducer/index.ts

@@ -5,12 +5,18 @@ import { combineReducers } from 'redux'
 // 导入 登录 模块的 reducer
 import A0Layout from './layout'
 import A1Goods from './A1Goods'
+import A2Wall from './A2Wall'
+import A3User from './A3User'
+import A4Role from './A4Role'
 import A5Log from './A5Log'
 
 // 合并 reducer
 const rootReducer = combineReducers({
   A0Layout,
   A1Goods,
+  A2Wall,
+  A3User,
+  A4Role,
   A5Log
 })
 

+ 8 - 2
管理后台/src/store/reducer/layout.ts

@@ -1,4 +1,4 @@
-import { DictListTypeObj, LookDomType } from "@/types";
+import { DictListTypeObj, LookDomType, PermissionsAPIType } from "@/types";
 import { MessageType } from "@/utils/message";
 
 // 初始化状态
@@ -21,7 +21,9 @@ const initState = {
     level: [],
     source: [],
   } as DictListTypeObj,
-
+  // 有关权限的信息
+  authPageArr: [] as PermissionsAPIType[],
+  // antd轻提示(兼容360浏览器)
   message: {
     txt: "",
     type: "info",
@@ -40,6 +42,7 @@ type LayoutActionType =
   | { type: "layout/lookDom"; payload: LookDomType }
   | { type: "layout/message"; payload: MessageType }
   | { type: "layout/getDictList"; payload: DictListTypeObj }
+  | { type: "layout/setAuthPageArr"; payload: PermissionsAPIType[] }
   | {
       type: "layout/closeUpFile";
       payload: {
@@ -67,6 +70,9 @@ export default function layoutReducer(
     // antd轻提示(兼容360浏览器)
     case "layout/message":
       return { ...state, message: action.payload };
+    // 有关权限的信息
+    case "layout/setAuthPageArr":
+      return { ...state, authPageArr: action.payload };
     // 上传文件点击取消
     case "layout/closeUpFile":
       return { ...state, closeUpFile: action.payload };

+ 1 - 1
管理后台/src/types/api/A1Goods.d.ts

@@ -35,4 +35,4 @@ export type FileImgListType = {
   id: number;
   fileName: string;
   filePath: string;
-}[];
+};

+ 36 - 0
管理后台/src/types/api/A2Wall.d.ts

@@ -0,0 +1,36 @@
+
+export type WallTableListType = {
+  createTime: string;
+  creatorId: number;
+  creatorName: string;
+  display: number;
+  fileName: string;
+  filePath: string;
+  id: number;
+  name: string;
+  sort: number;
+  type: string;
+  updateTime: string;
+};
+
+export type WallUpSaveType ={
+  fileName: string;
+  filePath: string;
+  id: number|null;
+  name: string;
+  type: string;
+}
+
+export type ImgListType = {
+  fileName?: string;
+  filePath?: string;
+  id?: number;
+};
+
+export type WallSaveAPIType ={
+  fileIds:string
+  id?:number|null
+  name:string
+  layout:1|2
+  type:'img'|'video'
+}

+ 35 - 0
管理后台/src/types/api/A3User.d.ts

@@ -0,0 +1,35 @@
+export type UserTableAPIType={
+  startTime:string
+  endTime:string
+  nickName:string
+  pageNum:number
+  pageSize:number
+  realName:string
+  searchKey:string
+}
+
+export type UserTableListType={
+  createTime: string;
+  creatorId: null;
+  creatorName: string;
+  id: number;
+  isAdmin: number;
+  isEnabled: number;
+  nickName: string;
+  phone: string;
+  realName: string;
+  roleId: null;
+  roleName: string;
+  sex: string;
+  thumb: string;
+  updateTime: string;
+  userName: string;
+}
+
+export type SaveUserType ={
+  id:number|null
+  userName:string
+  nickName:string
+  roleId:number
+  realName:string
+}

+ 29 - 0
管理后台/src/types/api/A4Role.d.ts

@@ -0,0 +1,29 @@
+export type RoleTableType = {
+  count: number;
+  createTime: string;
+  creatorId: null;
+  creatorName: string;
+  id: number;
+  isEnabled: number;
+  roleDesc: string;
+  roleKey: string;
+  roleName: string;
+  sort: string;
+  updateTime: string;
+};
+
+
+export type PermissionsAPIType = {
+  authority: boolean;
+  id: number;
+  name: string;
+  parentId?: null;
+  resourceType?: string;
+}
+
+export type AddRoleType ={
+  id:number|null
+  roleName:string
+  roleDesc:string
+  resources:number[]
+}

+ 9 - 1
管理后台/src/types/api/layot.d.ts

@@ -28,4 +28,12 @@ export type ImgListType = {
 export type LookDomType ={
   src: string;
   type: 'video'|'audio'|'model'|''
-}
+}
+export type RouterType ={
+  id: number;
+  name: string;
+  path: string;
+  Com: React.LazyExoticComponent<React.MemoExoticComponent<() => JSX.Element>>;
+  inco: any;
+  done: boolean;
+}[]

+ 4 - 1
管理后台/src/types/index.d.ts

@@ -1,3 +1,6 @@
 export * from './api/layot'
-export * from './api/A5Log'
 export * from './api/A1Goods'
+export * from './api/A2Wall'
+export * from './api/A3User'
+export * from './api/A4Role'
+export * from './api/A5Log'