shaogen1995 пре 1 година
комит
be1ba892dd
69 измењених фајлова са 33331 додато и 0 уклоњено
  1. 23 0
      .gitignore
  2. 22 0
      README.md
  3. 10 0
      config-overrides.js
  4. 30269 0
      package-lock.json
  5. 63 0
      package.json
  6. 8 0
      path.tsconfig.json
  7. 43 0
      public/index.html
  8. 65 0
      src/App.tsx
  9. BIN
      src/assets/img/IMGerror.png
  10. BIN
      src/assets/img/loading.gif
  11. BIN
      src/assets/img/loginBac.jpg
  12. BIN
      src/assets/img/logo.png
  13. BIN
      src/assets/img/user.png
  14. 203 0
      src/assets/styles/base.css
  15. 275 0
      src/assets/styles/base.less
  16. 21 0
      src/components/AsyncSpinLoding/index.module.scss
  17. 15 0
      src/components/AsyncSpinLoding/index.tsx
  18. 32 0
      src/components/AuthRoute/index.tsx
  19. 51 0
      src/components/ImageLazy/index.module.scss
  20. 68 0
      src/components/ImageLazy/index.tsx
  21. 65 0
      src/components/LookDom/index.module.scss
  22. 52 0
      src/components/LookDom/index.tsx
  23. 29 0
      src/components/Message/index.tsx
  24. 26 0
      src/components/NotFound/index.tsx
  25. 10 0
      src/components/SpinLoding/index.module.scss
  26. 13 0
      src/components/SpinLoding/index.tsx
  27. 43 0
      src/components/UpAsyncLoding/index.module.scss
  28. 38 0
      src/components/UpAsyncLoding/index.tsx
  29. 41 0
      src/index.tsx
  30. 5 0
      src/pages/A1Camera/index.module.scss
  31. 14 0
      src/pages/A1Camera/index.tsx
  32. 5 0
      src/pages/A2Psychz/index.module.scss
  33. 14 0
      src/pages/A2Psychz/index.tsx
  34. 5 0
      src/pages/B1Plan/index.module.scss
  35. 14 0
      src/pages/B1Plan/index.tsx
  36. 5 0
      src/pages/B2Scene/index.module.scss
  37. 14 0
      src/pages/B2Scene/index.tsx
  38. 5 0
      src/pages/B3Push/index.module.scss
  39. 14 0
      src/pages/B3Push/index.tsx
  40. 5 0
      src/pages/C1User/index.module.scss
  41. 14 0
      src/pages/C1User/index.tsx
  42. 32 0
      src/pages/C2Log/index.module.scss
  43. 138 0
      src/pages/C2Log/index.tsx
  44. 76 0
      src/pages/Layout/data.ts
  45. 177 0
      src/pages/Layout/index.module.scss
  46. 262 0
      src/pages/Layout/index.tsx
  47. 112 0
      src/pages/Login/index.module.scss
  48. 95 0
      src/pages/Login/index.tsx
  49. 5 0
      src/pages/初始化组件/index.module.scss
  50. 14 0
      src/pages/初始化组件/index.tsx
  51. 17 0
      src/store/action/C2Log.ts
  52. 42 0
      src/store/action/layout.ts
  53. 20 0
      src/store/index.ts
  54. 27 0
      src/store/reducer/C2Log.ts
  55. 16 0
      src/store/reducer/index.ts
  56. 65 0
      src/store/reducer/layout.ts
  57. 11 0
      src/types/api/C2Log.d.ts
  58. 26 0
      src/types/api/layot.d.ts
  59. 7 0
      src/types/declaration.d.ts
  60. 2 0
      src/types/index.d.ts
  61. 112 0
      src/utils/authFilesLook.ts
  62. 35 0
      src/utils/domShow.ts
  63. 60 0
      src/utils/filesLook.ts
  64. 17 0
      src/utils/history.ts
  65. 112 0
      src/utils/http.ts
  66. 50 0
      src/utils/message.ts
  67. 100 0
      src/utils/pass.ts
  68. 75 0
      src/utils/storage.ts
  69. 27 0
      tsconfig.json

+ 23 - 0
.gitignore

@@ -0,0 +1,23 @@
+# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
+
+# dependencies
+/node_modules
+/.pnp
+.pnp.js
+
+# testing
+/coverage
+
+# production
+/build
+
+# misc
+.DS_Store
+.env.local
+.env.development.local
+.env.test.local
+.env.production.local
+
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*

+ 22 - 0
README.md

@@ -0,0 +1,22 @@
+1.版本类型问题
+
+找到 import { useDispatch } from "react-redux";
+
+按住 Ctrl 点击 useDispatch 出来 useDispatch.d.ts类型声明文件
+(路径node_modules>react-redux>es>hooks>useDispatch.d.ts)
+
+找到最后面的 AnyAction,按住 Ctrl 点击 进入
+(路径node_modules>redux>index.d.ts)
+
+找到
+export interface Action<T = any> {
+  type: T
+}
+
+在type后面加一个 ?   即=>
+
+export interface Action<T = any> {
+  type?: T
+}
+
+

+ 10 - 0
config-overrides.js

@@ -0,0 +1,10 @@
+const path = require('path')
+const { override, addWebpackAlias } = require('customize-cra')
+
+// 添加 @ 别名
+const webpackAlias = addWebpackAlias({
+  '@': path.resolve(__dirname, 'src'),
+})
+
+// 导出要进行覆盖的 webpack 配置
+module.exports = override(webpackAlias)

Разлика између датотеке није приказан због своје велике величине
+ 30269 - 0
package-lock.json


+ 63 - 0
package.json

@@ -0,0 +1,63 @@
+{
+  "name": "demo",
+  "version": "0.1.0",
+  "private": true,
+  "dependencies": {
+    "@ant-design/cssinjs": "^1.5.6",
+    "@testing-library/jest-dom": "^5.16.5",
+    "@testing-library/react": "^13.4.0",
+    "@testing-library/user-event": "^13.5.0",
+    "@types/jest": "^27.5.2",
+    "@types/node": "^16.18.3",
+    "@types/react": "^18.0.24",
+    "@types/react-dom": "^18.0.8",
+    "antd": "^5.8.3",
+    "antd-mobile": "^5.30.0",
+    "axios": "^1.1.3",
+    "dayjs": "^1.11.7",
+    "js-base64": "^3.7.3",
+    "react": "^18.2.0",
+    "react-dom": "^18.2.0",
+    "react-redux": "^8.0.4",
+    "react-router-dom": "5.3",
+    "react-scripts": "5.0.1",
+    "react-sortablejs": "^6.1.4",
+    "redux": "^4.2.0",
+    "redux-devtools-extension": "^2.13.9",
+    "redux-thunk": "^2.4.1",
+    "sass": "^1.55.0",
+    "typescript": "^4.8.4",
+    "web-vitals": "^2.1.4"
+  },
+  "scripts": {
+    "dev": "react-app-rewired start",
+    "build": "react-app-rewired build",
+    "test": "react-app-rewired test",
+    "eject": "react-scripts eject"
+  },
+  "eslintConfig": {
+    "extends": [
+      "react-app",
+      "react-app/jest"
+    ]
+  },
+  "browserslist": {
+    "production": [
+      ">0.2%",
+      "not dead",
+      "not op_mini all"
+    ],
+    "development": [
+      "last 1 chrome version",
+      "last 1 firefox version",
+      "last 1 safari version"
+    ]
+  },
+  "devDependencies": {
+    "@types/history": "^5.0.0",
+    "@types/react-router-dom": "^5.3.3",
+    "customize-cra": "^1.0.0",
+    "react-app-rewired": "^2.2.1"
+  },
+  "homepage": "."
+}

+ 8 - 0
path.tsconfig.json

@@ -0,0 +1,8 @@
+{
+    "compilerOptions": {
+      "baseUrl": "./",
+      "paths": {
+        "@/*": ["src/*"]
+      }
+    }
+  }

+ 43 - 0
public/index.html

@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<html lang="zh">
+  <head>
+    <meta charset="utf-8" />
+    <!-- <link rel="icon" href="%PUBLIC_URL%/favicon.ico" /> -->
+    <meta name="viewport" content="width=device-width, initial-scale=1" />
+    <meta name="theme-color" content="#000000" />
+    <meta
+      name="description"
+      content="Web site created using create-react-app"
+    />
+    <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
+    <!--
+      manifest.json provides metadata used when your web app is installed on a
+      user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
+    -->
+
+    <!--
+      Notice the use of %PUBLIC_URL% in the tags above.
+      It will be replaced with the URL of the `public` folder during the build.
+      Only files inside the `public` folder can be referenced from the HTML.
+
+      Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
+      work correctly both with client-side routing and a non-root public URL.
+      Learn how to configure a non-root public URL by running `npm run build`.
+    -->
+    <title>中国铁塔集团-管理后台</title>
+  </head>
+  <body>
+    <noscript>You need to enable JavaScript to run this app.</noscript>
+    <div id="root"></div>
+    <!--
+      This HTML file is a template.
+      If you open it directly in the browser, you will see an empty page.
+
+      You can add webfonts, meta tags, or analytics to this file.
+      The build step will place the bundled scripts into the <body> tag.
+
+      To begin the development, run `npm start` or `yarn start`.
+      To create a production bundle, use `npm run build` or `yarn build`.
+    -->
+  </body>
+</html>

+ 65 - 0
src/App.tsx

@@ -0,0 +1,65 @@
+import "@/assets/styles/base.css";
+// 关于路由
+import React from "react";
+import { Router, Route, Switch } from "react-router-dom";
+import history from "./utils/history";
+import AuthRoute from "./components/AuthRoute";
+import SpinLoding from "./components/SpinLoding";
+import AsyncSpinLoding from "./components/AsyncSpinLoding";
+import { Image } from "antd";
+import { useSelector } from "react-redux";
+import store, { RootState } from "./store";
+import UpAsyncLoding from "./components/UpAsyncLoding";
+import MessageCom from "./components/Message";
+import LookDom from "./components/LookDom";
+const Layout = React.lazy(() => import("./pages/Layout"));
+const Login = React.lazy(() => import("./pages/Login"));
+
+export default function App() {
+  // 从仓库中获取查看图片的信息
+  const lookBigImg = useSelector(
+    (state: RootState) => state.A0Layout.lookBigImg
+  );
+
+  return (
+    <>
+      {/* 关于路由 */}
+      <Router history={history}>
+        <React.Suspense fallback={<SpinLoding />}>
+          <Switch>
+            {/* 测试页面 */}
+            <Route path="/login" component={Login} />
+            <AuthRoute path="/" component={Layout} />
+          </Switch>
+        </React.Suspense>
+      </Router>
+
+      {/* 发送请求的加载组件 */}
+      <AsyncSpinLoding />
+
+      {/* 所有图片点击预览查看大图 */}
+      <Image
+        preview={{
+          visible: lookBigImg.show,
+          src: lookBigImg.url,
+          onVisibleChange: (value) => {
+            // 清除仓库信息
+            store.dispatch({
+              type: "layout/lookBigImg",
+              payload: { url: "", show: false },
+            });
+          },
+        }}
+      />
+
+      {/* 上传附件的进度条元素 */}
+      <UpAsyncLoding />
+
+      {/* 查看视频音频 */}
+      <LookDom />
+
+      {/* antd 轻提示 ---兼容360浏览器 */}
+      <MessageCom />
+    </>
+  );
+}

BIN
src/assets/img/IMGerror.png


BIN
src/assets/img/loading.gif


BIN
src/assets/img/loginBac.jpg


BIN
src/assets/img/logo.png


BIN
src/assets/img/user.png


+ 203 - 0
src/assets/styles/base.css

@@ -0,0 +1,203 @@
+* {
+  margin: 0;
+  padding: 0;
+  box-sizing: border-box;
+}
+html {
+  height: 100%;
+  font-size: 14px;
+  user-select: none;
+}
+body {
+  font: 1em/1.4 'Microsoft Yahei', 'PingFang SC', 'Avenir', 'Segoe UI', 'Hiragino Sans GB', 'STHeiti', 'Microsoft Sans Serif', 'WenQuanYi Micro Hei', sans-serif;
+  height: 100%;
+  color: black;
+}
+a {
+  text-decoration: none;
+  color: black;
+  outline: none;
+}
+i {
+  font-style: normal;
+}
+img {
+  max-width: 100%;
+  max-height: 100%;
+  vertical-align: middle;
+  object-fit: cover;
+}
+ul {
+  list-style: none;
+}
+body {
+  overflow: auto;
+  overflow-y: overlay;
+}
+/* 文本域取消下拉 */
+textarea {
+  resize: none !important;
+  min-height: 100px !important;
+}
+/* 主题色 */
+:root {
+  --themeColor: #00b3ec;
+}
+/* 找不到页面 */
+.noFindPage {
+  opacity: 0;
+  transition: opacity 0.5s;
+}
+/* 兼容360浏览器的下拉框 */
+.ant-select-selector {
+  position: relative;
+  background-color: #ffffff;
+  border: 1px solid #d9d9d9;
+  transition: all 0.2s cubic-bezier(0.645, 0.045, 0.355, 1);
+}
+#root {
+  width: 100vw;
+  height: 100vh;
+  min-width: 1600px;
+  min-height: 900px;
+  overflow: auto;
+  overflow-y: overlay;
+  /* 普通文字按钮的颜色 */
+  /* 按钮的危险颜色 */
+  /* antd分页器样式 */
+  /* 表格的图片居中 */
+  /* antd图片预览组件 */
+  /* antd表格居中 */
+}
+#root .iconHoverTit {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+#root .iconHoverTitTxt {
+  background-color: var(--themeColor);
+  color: #fff;
+  width: 16px;
+  height: 16px;
+  line-height: 16px;
+  text-align: center;
+  font-size: 12px;
+  border-radius: 50%;
+}
+#root .ant-btn-text {
+  color: var(--themeColor);
+}
+#root .ant-btn-text:disabled {
+  cursor: not-allowed;
+  color: rgba(0, 0, 0, 0.25);
+}
+#root .ant-btn-text.ant-btn-dangerous {
+  color: #ff4d4d;
+}
+#root .ant-pagination .ant-pagination-item {
+  border-radius: 50%;
+  border: 1px solid #999;
+  background-color: transparent !important;
+}
+#root .ant-pagination .ant-pagination-item-active {
+  background-color: var(--themeColor) !important;
+}
+#root .ant-pagination .ant-pagination-item-active a {
+  color: #fff !important;
+}
+#root .ant-pagination .ant-pagination-item:hover {
+  background-color: var(--themeColor) !important;
+}
+#root .ant-pagination .ant-pagination-item:hover a {
+  color: #fff !important;
+}
+#root .ant-pagination-prev {
+  border-radius: 50% !important;
+  border: 1px solid #999;
+}
+#root .ant-pagination-prev:hover {
+  background-color: var(--themeColor);
+}
+#root .ant-pagination-prev:hover button {
+  color: #fff;
+}
+#root .ant-pagination-next {
+  border-radius: 50% !important;
+  border: 1px solid #999;
+}
+#root .ant-pagination-next:hover {
+  background-color: var(--themeColor);
+}
+#root .ant-pagination-next:hover button {
+  color: #fff;
+}
+#root .ant-pagination-disabled {
+  border: 1px solid #ccc;
+}
+#root .ant-pagination-disabled:hover {
+  background-color: transparent;
+}
+#root .tableImgAuto {
+  display: flex;
+  justify-content: center;
+}
+#root .ant-image {
+  display: none;
+}
+#root .ant-table-cell {
+  text-align: center !important;
+}
+#root #A2Table3 .ant-table-row-expand-icon {
+  background-color: var(--themeColor);
+  color: #fff;
+}
+#root #A2Table3 .ant-table-cell-with-append {
+  display: flex;
+  justify-content: flex-start;
+}
+[hidden] {
+  display: none !important;
+}
+#upInput {
+  display: none;
+}
+#upInput2 {
+  display: none;
+}
+.pageTitle {
+  font-size: 18px;
+  font-weight: 700;
+  position: absolute;
+  z-index: 11;
+  top: -56px;
+  left: -18px;
+  padding-left: 40px;
+}
+.pageTitle::before {
+  position: absolute;
+  left: 20px;
+  top: 50%;
+  transform: translateY(-50%);
+  content: '';
+  width: 6px;
+  height: 20px;
+  background-color: var(--themeColor);
+}
+.mySorrl::-webkit-scrollbar {
+  /*滚动条整体样式*/
+  width: 5px;
+  /*高宽分别对应横竖滚动条的尺寸*/
+  height: 1px;
+}
+.mySorrl::-webkit-scrollbar-thumb {
+  /*滚动条里面小方块*/
+  border-radius: 10px;
+  -webkit-box-shadow: inset 0 0 5px transparent;
+  background: var(--themeColor);
+}
+.mySorrl::-webkit-scrollbar-track {
+  /*滚动条里面轨道*/
+  -webkit-box-shadow: inset 0 0 5px transparent;
+  border-radius: 10px;
+  background: transparent;
+}

+ 275 - 0
src/assets/styles/base.less

@@ -0,0 +1,275 @@
+* {
+  margin: 0;
+  padding: 0;
+  box-sizing: border-box;
+}
+
+html {
+  height: 100%;
+  font-size: 14px;
+  user-select: none;
+}
+
+body {
+  font: 1em/1.4 'Microsoft Yahei', 'PingFang SC', 'Avenir', 'Segoe UI', 'Hiragino Sans GB', 'STHeiti', 'Microsoft Sans Serif', 'WenQuanYi Micro Hei', sans-serif;
+  height: 100%;
+  color: black;
+}
+
+a {
+  text-decoration: none;
+  color: black;
+  outline: none;
+}
+
+i {
+  font-style: normal;
+}
+
+img {
+  max-width: 100%;
+  max-height: 100%;
+  vertical-align: middle;
+  object-fit: cover;
+}
+
+ul {
+  list-style: none;
+}
+
+body {
+  overflow: auto;
+  overflow-y: overlay;
+}
+
+/* 文本域取消下拉 */
+textarea {
+  resize: none !important;
+  min-height: 100px !important;
+}
+
+/* 主题色 */
+:root {
+  --themeColor: #00b3ec;
+}
+
+
+
+
+
+/* 找不到页面 */
+.noFindPage {
+  opacity: 0;
+  transition: opacity .5s;
+}
+
+
+/* 兼容360浏览器的下拉框 */
+.ant-select-selector {
+  position: relative;
+  background-color: #ffffff;
+  border: 1px solid #d9d9d9;
+  transition: all 0.2s cubic-bezier(0.645, 0.045, 0.355, 1);
+}
+
+
+// 重置antd样式
+#root {
+  width: 100vw;
+  height: 100vh;
+  min-width: 1600px;
+  min-height: 900px;
+  overflow: auto;
+  overflow-y: overlay;
+
+  // ?的提示
+  .iconHoverTit {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+  }
+  .iconHoverTitTxt {
+    background-color: var(--themeColor);
+    color: #fff;
+    width: 16px;
+    height: 16px;
+    line-height: 16px;
+    text-align: center;
+    font-size: 12px;
+    border-radius: 50%;
+  }
+
+  // a {
+  //   color: var(--themeColor);
+  // }
+
+  /* 普通文字按钮的颜色 */
+  .ant-btn-text {
+    color: var(--themeColor);
+  }
+
+  .ant-btn-text:disabled {
+    cursor: not-allowed;
+    color: rgba(0, 0, 0, 0.25);
+  }
+
+  /* 按钮的危险颜色 */
+  .ant-btn-text.ant-btn-dangerous {
+    color: #ff4d4d;
+  }
+
+  /* antd分页器样式 */
+  .ant-pagination .ant-pagination-item {
+    border-radius: 50%;
+    border: 1px solid #999;
+    background-color: transparent !important;
+  }
+
+  .ant-pagination .ant-pagination-item-active {
+    background-color: var(--themeColor) !important;
+  }
+
+
+  .ant-pagination .ant-pagination-item-active a {
+    color: #fff !important;
+  }
+
+  .ant-pagination .ant-pagination-item:hover {
+    background-color: var(--themeColor) !important;
+  }
+
+  .ant-pagination .ant-pagination-item:hover a {
+    color: #fff !important;
+  }
+
+  .ant-pagination-prev {
+    border-radius: 50% !important;
+    border: 1px solid #999;
+  }
+
+  .ant-pagination-prev:hover {
+    background-color: var(--themeColor);
+  }
+
+  .ant-pagination-prev:hover button {
+    color: #fff;
+  }
+
+
+
+  .ant-pagination-next {
+    border-radius: 50% !important;
+    border: 1px solid #999;
+  }
+
+
+  .ant-pagination-next:hover {
+    background-color: var(--themeColor);
+  }
+
+  .ant-pagination-next:hover button {
+    color: #fff;
+  }
+
+  .ant-pagination-disabled {
+    border: 1px solid #ccc;
+  }
+
+  .ant-pagination-disabled:hover {
+    background-color: transparent;
+  }
+
+
+  /* 表格的图片居中 */
+  .tableImgAuto {
+    display: flex;
+    justify-content: center;
+  }
+
+  /* antd图片预览组件 */
+  .ant-image {
+    display: none;
+  }
+
+  /* antd表格居中 */
+
+  .ant-table-cell {
+    text-align: center !important;
+  }
+
+
+  // 树型 表格 定制化
+  #A2Table3 {
+    .ant-table-row-expand-icon {
+      background-color: var(--themeColor);
+      color: #fff;
+    }
+
+    .ant-table-cell-with-append {
+      display: flex;
+      justify-content: flex-start;
+    }
+  }
+
+
+}
+
+
+
+[hidden] {
+  display: none !important;
+}
+
+
+
+#upInput {
+  display: none;
+}
+
+#upInput2 {
+  display: none;
+}
+
+// 页面标题
+.pageTitle {
+  font-size: 18px;
+  font-weight: 700;
+  position: absolute;
+  z-index: 11;
+  top: -56px;
+  left: -18px;
+  padding-left: 40px;
+
+  &::before {
+    position: absolute;
+    left: 20px;
+    top: 50%;
+    transform: translateY(-50%);
+    content: '';
+    width: 6px;
+    height: 20px;
+    background-color: var(--themeColor);
+  }
+}
+
+// 滚动条
+.mySorrl::-webkit-scrollbar {
+  /*滚动条整体样式*/
+  width: 5px;
+  /*高宽分别对应横竖滚动条的尺寸*/
+  height: 1px;
+}
+
+.mySorrl::-webkit-scrollbar-thumb {
+  /*滚动条里面小方块*/
+  border-radius: 10px;
+  -webkit-box-shadow: inset 0 0 5px transparent;
+  background: var(--themeColor);
+}
+
+.mySorrl::-webkit-scrollbar-track {
+  /*滚动条里面轨道*/
+  -webkit-box-shadow: inset 0 0 5px transparent;
+  border-radius: 10px;
+  background: transparent;
+}

+ 21 - 0
src/components/AsyncSpinLoding/index.module.scss

@@ -0,0 +1,21 @@
+.AsyncSpinLoding {
+  opacity: 0;
+  pointer-events: none;
+  transition: all .5s;
+  position: fixed;
+  z-index: 9998;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  // background-color: rgba(0, 0, 0, .6);
+  background-color: transparent;
+  :global{
+    .ant-spin-spinning{
+      position: absolute;
+      top: 50%;
+      left: 50%;
+      transform: translate(-50%,-50%);
+    }
+  }
+}

+ 15 - 0
src/components/AsyncSpinLoding/index.tsx

@@ -0,0 +1,15 @@
+import styles from "./index.module.scss";
+import { Spin } from "antd";
+import React from "react";
+
+function AsyncSpinLoding() {
+  return (
+    <div id="AsyncSpinLoding" className={styles.AsyncSpinLoding}>
+      <Spin size="large" />
+    </div>
+  );
+}
+
+const MemoAsyncSpinLoding = React.memo(AsyncSpinLoding);
+
+export default MemoAsyncSpinLoding;

+ 32 - 0
src/components/AuthRoute/index.tsx

@@ -0,0 +1,32 @@
+import { hasToken } from "@//utils/storage";
+import { MessageFu } from "@/utils/message";
+import React from "react";
+import { Redirect, Route } from "react-router-dom";
+
+type AtahType = {
+  path: string;
+  component: React.FC;
+  [x: string]: any;
+};
+
+export default function AuthRoute({ path, component: Com, ...rest }: AtahType) {
+  return (
+    <Route
+      path={path}
+      {...rest}
+      render={() => {
+        if (hasToken()) return <Com />;
+        else {
+          MessageFu.warning("登录失效!");
+          return (
+            <Redirect
+              to={{
+                pathname: "/login",
+              }}
+            />
+          );
+        }
+      }}
+    />
+  );
+}

+ 51 - 0
src/components/ImageLazy/index.module.scss

@@ -0,0 +1,51 @@
+.ImageLazy {
+  position: relative;
+
+  :global {
+    .lazyBox {
+      width: 100%;
+      height: 100%;
+      position: relative;
+
+      .adm-image {
+        width: 100%;
+        height: 100%;
+
+        img {
+          width: 100%;
+          height: 100%;
+        }
+      }
+
+      .lookImg {
+        cursor: pointer;
+        transition: opacity .3s;
+        opacity: 0;
+        pointer-events: none;
+        position: absolute;
+        top: 0;
+        left: 0;
+        width: 100%;
+        height: 100%;
+        display: flex;
+        justify-content: center;
+        align-items: center;
+        font-size: 18px;
+        color: #fff;
+        background-color: rgba(0, 0, 0, .6);
+
+        &>div {
+          font-size: 14px;
+        }
+      }
+
+      &:hover {
+        .lookImg {
+          opacity: 1;
+          pointer-events: auto;
+        }
+      }
+    }
+  }
+
+}

+ 68 - 0
src/components/ImageLazy/index.tsx

@@ -0,0 +1,68 @@
+import React, { useCallback, useState } from "react";
+import styles from "./index.module.scss";
+import { baseURL } from "@/utils/http";
+import imgLoding from "@/assets/img/loading.gif";
+import imgErr from "@/assets/img/IMGerror.png";
+import { EyeOutlined } from "@ant-design/icons";
+import store from "@/store";
+import { Image } from "antd-mobile";
+
+type Props = {
+  width?: number | string;
+  height?: number | string;
+  src: string;
+  noLook?: boolean;
+  offline?: boolean;
+};
+
+function ImageLazy({
+  width = 100,
+  height = 100,
+  src,
+  noLook,
+  offline = false,
+}: Props) {
+  // 默认不能预览图片,加载成功之后能预览
+  const [lookImg, setLookImg] = useState(false);
+
+  // 图片加载完成
+  const onLoad = useCallback(() => {
+    setLookImg(true);
+  }, []);
+
+  // 点击预览图片
+  const lookBigImg = useCallback(() => {
+    store.dispatch({
+      type: "layout/lookBigImg",
+      payload: { url: offline ? src : baseURL + src, show: true },
+    });
+  }, [offline, src]);
+
+  return (
+    <div className={styles.ImageLazy} style={{ width: width, height: height }}>
+      <div className="lazyBox">
+        <Image
+          lazy
+          onLoad={onLoad}
+          src={src ? (offline ? src : baseURL + src) : ""}
+          placeholder={<img src={imgLoding} alt="" />}
+          fallback={<img src={imgErr} alt="" />}
+          fit="cover"
+        />
+
+        {/* 图片预览 */}
+        {noLook || !lookImg ? null : (
+          <div className="lookImg" onClick={lookBigImg}>
+            <EyeOutlined rev={undefined} />
+            &nbsp;
+            <div>预览</div>
+          </div>
+        )}
+      </div>
+    </div>
+  );
+}
+
+const MemoImageLazy = React.memo(ImageLazy);
+
+export default MemoImageLazy;

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

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

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

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

+ 29 - 0
src/components/Message/index.tsx

@@ -0,0 +1,29 @@
+import React, { useEffect } from "react";
+import { message } from "antd";
+import { useSelector } from "react-redux";
+import { RootState } from "@/store";
+
+function MessageCom() {
+  // 从仓库中获取 antd 轻提示信息
+  const messageReducerInfo = useSelector(
+    (state: RootState) => state.A0Layout.message
+  );
+
+  const [messageApi, contextHolder] = message.useMessage();
+
+  useEffect(() => {
+    if (messageReducerInfo.txt) {
+      messageApi.open({
+        type: messageReducerInfo.type,
+        content: messageReducerInfo.txt,
+        duration: messageReducerInfo.duration,
+      });
+    }
+  }, [messageApi, messageReducerInfo]);
+
+  return <>{contextHolder}</>;
+}
+
+const MemoMessage = React.memo(MessageCom);
+
+export default MemoMessage;

+ 26 - 0
src/components/NotFound/index.tsx

@@ -0,0 +1,26 @@
+import { Result } from "antd";
+import { useEffect, useRef } from "react";
+
+export default function NotFound() {
+  const timeRef = useRef(-1);
+
+  useEffect(() => {
+    timeRef.current = window.setTimeout(() => {
+      const dom: HTMLDivElement = document.querySelector(".noFindPage")!;
+      dom.style.opacity = "1";
+    }, 300);
+    return () => {
+      clearTimeout(timeRef.current);
+    };
+  }, []);
+
+  return (
+    <div className="noFindPage">
+      <Result
+        status="404"
+        title="404"
+        subTitle="找不到页面,或没有权限!"
+      />
+    </div>
+  );
+}

+ 10 - 0
src/components/SpinLoding/index.module.scss

@@ -0,0 +1,10 @@
+.SpinLoding {
+  position: relative;
+  z-index: 9999;
+  width: 100%;
+  height: 100%;
+  background-color: #fff;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+}

+ 13 - 0
src/components/SpinLoding/index.tsx

@@ -0,0 +1,13 @@
+import styles from "./index.module.scss";
+import { Spin } from "antd";
+import React from "react";
+function SpinLoding() {
+  return (
+    <div className={styles.SpinLoding}>
+      <Spin size='large'/>
+    </div>
+  );
+}
+const MemoSpinLoding = React.memo(SpinLoding);
+
+export default MemoSpinLoding;

+ 43 - 0
src/components/UpAsyncLoding/index.module.scss

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

+ 38 - 0
src/components/UpAsyncLoding/index.tsx

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

+ 41 - 0
src/index.tsx

@@ -0,0 +1,41 @@
+// import 'default-passive-events';
+
+import App from "./App";
+import store from "./store/index";
+
+import { Provider } from "react-redux";
+import { createRoot } from "react-dom/client";
+
+import { ConfigProvider } from "antd";
+
+// 兼容360浏览器
+import {
+  StyleProvider,
+  legacyLogicalPropertiesTransformer,
+} from "@ant-design/cssinjs";
+
+import "dayjs/locale/zh-cn";
+import locale from "antd/locale/zh_CN";
+
+const container = document.getElementById("root") as HTMLElement;
+const root = createRoot(container);
+
+root.render(
+  <ConfigProvider
+    locale={locale}
+    theme={{
+      token: {
+        colorPrimary: "#00b3ec",
+      },
+    }}
+  >
+    <Provider store={store}>
+      <StyleProvider
+        hashPriority="high"
+        transformers={[legacyLogicalPropertiesTransformer]}
+      >
+        <App />
+      </StyleProvider>
+    </Provider>
+  </ConfigProvider>
+);

+ 5 - 0
src/pages/A1Camera/index.module.scss

@@ -0,0 +1,5 @@
+.A1Camera{
+  :global{
+    
+  }
+}

+ 14 - 0
src/pages/A1Camera/index.tsx

@@ -0,0 +1,14 @@
+import React from "react";
+import styles from "./index.module.scss";
+ function A1Camera() {
+  
+  return (
+    <div className={styles.A1Camera}>
+      <h1>A1Camera</h1>
+    </div>
+  )
+}
+
+const MemoA1Camera = React.memo(A1Camera);
+
+export default MemoA1Camera;

+ 5 - 0
src/pages/A2Psychz/index.module.scss

@@ -0,0 +1,5 @@
+.A2Psychz{
+  :global{
+    
+  }
+}

+ 14 - 0
src/pages/A2Psychz/index.tsx

@@ -0,0 +1,14 @@
+import React from "react";
+import styles from "./index.module.scss";
+ function A2Psychz() {
+  
+  return (
+    <div className={styles.A2Psychz}>
+      <h1>A2Psychz</h1>
+    </div>
+  )
+}
+
+const MemoA2Psychz = React.memo(A2Psychz);
+
+export default MemoA2Psychz;

+ 5 - 0
src/pages/B1Plan/index.module.scss

@@ -0,0 +1,5 @@
+.B1Plan{
+  :global{
+    
+  }
+}

+ 14 - 0
src/pages/B1Plan/index.tsx

@@ -0,0 +1,14 @@
+import React from "react";
+import styles from "./index.module.scss";
+ function B1Plan() {
+  
+  return (
+    <div className={styles.B1Plan}>
+      <h1>B1Plan</h1>
+    </div>
+  )
+}
+
+const MemoB1Plan = React.memo(B1Plan);
+
+export default MemoB1Plan;

+ 5 - 0
src/pages/B2Scene/index.module.scss

@@ -0,0 +1,5 @@
+.B2Scene{
+  :global{
+    
+  }
+}

+ 14 - 0
src/pages/B2Scene/index.tsx

@@ -0,0 +1,14 @@
+import React from "react";
+import styles from "./index.module.scss";
+ function B2Scene() {
+  
+  return (
+    <div className={styles.B2Scene}>
+      <h1>B2Scene</h1>
+    </div>
+  )
+}
+
+const MemoB2Scene = React.memo(B2Scene);
+
+export default MemoB2Scene;

+ 5 - 0
src/pages/B3Push/index.module.scss

@@ -0,0 +1,5 @@
+.B3Push{
+  :global{
+    
+  }
+}

+ 14 - 0
src/pages/B3Push/index.tsx

@@ -0,0 +1,14 @@
+import React from "react";
+import styles from "./index.module.scss";
+ function B3Push() {
+  
+  return (
+    <div className={styles.B3Push}>
+      <h1>B3Push</h1>
+    </div>
+  )
+}
+
+const MemoB3Push = React.memo(B3Push);
+
+export default MemoB3Push;

+ 5 - 0
src/pages/C1User/index.module.scss

@@ -0,0 +1,5 @@
+.C1User{
+  :global{
+    
+  }
+}

+ 14 - 0
src/pages/C1User/index.tsx

@@ -0,0 +1,14 @@
+import React from "react";
+import styles from "./index.module.scss";
+ function C1User() {
+  
+  return (
+    <div className={styles.C1User}>
+      <h1>C1User</h1>
+    </div>
+  )
+}
+
+const MemoC1User = React.memo(C1User);
+
+export default MemoC1User;

+ 32 - 0
src/pages/C2Log/index.module.scss

@@ -0,0 +1,32 @@
+.C2Log {
+  :global {
+    .logTop {
+      border-radius: 10px;
+      background-color: #fff;
+
+      .tableSelectBox {
+        padding: 15px 24px;
+        display: flex;
+        align-items: center;
+
+        .row {
+          margin-right: 20px;
+        }
+      }
+    }
+
+    .tableMain {
+      border-radius: 10px;
+      margin-top: 15px;
+      height: calc(100% - 75px);
+      background-color: #fff;
+
+      .ant-table-body {
+        height: 630px;
+        overflow-y: auto !important;
+        overflow-y: overlay !important;
+
+      }
+    }
+  }
+}

+ 138 - 0
src/pages/C2Log/index.tsx

@@ -0,0 +1,138 @@
+import { RootState } from "@/store";
+import { getLogListAPI } from "@/store/action/C2Log";
+import { Input, DatePicker, Table } from "antd";
+import React, { useEffect, useMemo, useRef, useState } from "react";
+import { useDispatch, useSelector } from "react-redux";
+
+import styles from "./index.module.scss";
+
+const { RangePicker } = DatePicker;
+
+function C2Log() {
+  const dispatch = useDispatch();
+
+  const pageNumRef = useRef(1);
+  const pagePageRef = useRef(10);
+  // 筛选和分页
+  const [tableSelect, setTableSelect] = useState({
+    searchKey: "",
+    pageSize: 10,
+    pageNum: 1,
+    startTime: "",
+    endTime: "",
+  });
+
+  // 账号的输入
+  const nameTime = useRef(-1);
+  const nameChange = (e: React.ChangeEvent<HTMLInputElement>) => {
+    clearTimeout(nameTime.current);
+    nameTime.current = window.setTimeout(() => {
+      setTableSelect({ ...tableSelect, searchKey: e.target.value, pageNum: 1 });
+    }, 500);
+  };
+  // 时间选择器改变
+  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 });
+  };
+
+  useEffect(() => {
+    pageNumRef.current = tableSelect.pageNum;
+    pagePageRef.current = tableSelect.pageSize;
+    dispatch(getLogListAPI(tableSelect));
+  }, [dispatch, tableSelect]);
+
+  // ---------关于表格
+
+  // 页码变化
+  const paginationChange = (pageNum: number, pageSize: number) => {
+    pageNumRef.current = pageNum;
+    pagePageRef.current = pageSize;
+    setTableSelect({ ...tableSelect, pageNum, pageSize });
+  };
+
+  const results = useSelector((state: RootState) => state.C2Log.tableInfo);
+
+  const columns = useMemo(() => {
+    return [
+      {
+        width: 100,
+        title: "序号",
+        render: (text: any, record: any, index: any) =>
+          index + 1 + (pageNumRef.current - 1) * pagePageRef.current,
+      },
+      {
+        title: "操作者",
+        dataIndex: "userName",
+      },
+      {
+        title: "操作日期",
+        dataIndex: "createTime",
+      },
+      {
+        title: "IP记录",
+        dataIndex: "ip",
+      },
+      {
+        title: "操作模块",
+        dataIndex: "type",
+      },
+      {
+        title: "操作事件",
+        dataIndex: "description",
+      },
+    ];
+  }, []);
+
+  return (
+    <div className={styles.C2Log}>
+      <div className="pageTitle">系统日志</div>
+      <div className="logTop">
+        <div className="tableSelectBox">
+          <div className="row">
+            <span>账号:</span>
+            <Input
+              maxLength={15}
+              style={{ width: 150 }}
+              placeholder="请输入"
+              allowClear
+              onChange={(e) => nameChange(e)}
+            />
+          </div>
+          <div className="row">
+            <span>操作日期:</span>
+            <RangePicker onChange={timeChange} />
+          </div>
+        </div>
+      </div>
+
+      {/* 表格主体 */}
+      <div className="tableMain">
+        <Table
+          scroll={{ y: 630 }}
+          dataSource={results.list}
+          columns={columns}
+          rowKey="id"
+          pagination={{
+            showQuickJumper: true,
+            position: ["bottomCenter"],
+            showSizeChanger: true,
+            current: tableSelect.pageNum,
+            pageSize: tableSelect.pageSize,
+            total: results.total,
+            onChange: paginationChange,
+          }}
+        />
+      </div>
+    </div>
+  );
+}
+
+const MemoC2Log = React.memo(C2Log);
+
+export default MemoC2Log;

+ 76 - 0
src/pages/Layout/data.ts

@@ -0,0 +1,76 @@
+import { RouterType } from "@/types";
+import React from "react";
+
+const tabLeftArr: RouterType = [
+  {
+    id: "1",
+    name: "业务配置",
+    son: [
+      {
+        id: "1.1",
+        name: "相机管理",
+        path: "/",
+        Com: React.lazy(() => import("../A1Camera")),
+        done: true,
+      },
+      {
+        id: "1.2",
+        name: "机房管理",
+        path: "/psychz",
+        Com: React.lazy(() => import("../A2Psychz")),
+        done: true,
+      },
+    ],
+  },
+
+  {
+    id: "2",
+    name: "采集管理",
+    son: [
+      {
+        id: "2.1",
+        name: "进度统计",
+        path: "/plan",
+        Com: React.lazy(() => import("../B1Plan")),
+        done: true,
+      },
+      {
+        id: "2.2",
+        name: "场景审核",
+        path: "/scene",
+        Com: React.lazy(() => import("../B2Scene")),
+        done: true,
+      },
+      {
+        id: "2.3",
+        name: "推送管理",
+        path: "/push",
+        Com: React.lazy(() => import("../B3Push")),
+        done: true,
+      },
+    ],
+  },
+
+  {
+    id: "3",
+    name: "系统管理",
+    son: [
+      {
+        id: "3.1",
+        name: "用户管理",
+        path: "/user",
+        Com: React.lazy(() => import("../C1User")),
+        done: true,
+      },
+      {
+        id: "3.2",
+        name: "系统日志",
+        path: "/log",
+        Com: React.lazy(() => import("../C2Log")),
+        done: true,
+      },
+    ],
+  },
+];
+
+export default tabLeftArr;

+ 177 - 0
src/pages/Layout/index.module.scss

@@ -0,0 +1,177 @@
+.Layout {
+  width: 100%;
+  height: 100%;
+  display: flex;
+
+  :global {
+
+    .layoutLeft {
+      position: relative;
+      z-index: 30;
+      width: 220px;
+      height: 100%;
+      background-color: #f6f8f9;
+      box-shadow: 0px 0px 6px 2px #ccc;
+
+
+      .layoutLeftTop {
+        text-align: center;
+        padding: 20px 0 20px;
+
+        &>img {
+          width: 100px;
+        }
+      }
+
+      .layoutLeftMain {
+        .layoutLRowBox {
+          .layoutLRowBoxTxt {
+            font-size: 18px;
+            font-weight: 700;
+            height: 60px;
+            line-height: 60px;
+            padding-left: 40px;
+          }
+
+          .layoutLRowBoxRow {
+            text-align: center;
+            cursor: pointer;
+
+            font-size: 16px;
+            height: 50px;
+            line-height: 50px;
+
+            &:hover {
+              background-color: var(--themeColor);
+              color: #fff;
+            }
+          }
+
+          .active {
+            pointer-events: none;
+            background-color: var(--themeColor);
+            color: #fff;
+          }
+        }
+
+
+
+
+
+      }
+
+
+    }
+
+    .layoutRight {
+      width: calc(100% - 220px);
+      height: 100%;
+
+
+      .layoutRightTop {
+        height: 60px;
+        display: flex;
+        justify-content: flex-end;
+        position: relative;
+        z-index: 1000;
+
+        .user {
+          margin-right: 40px;
+          padding-right: 40px;
+          display: flex;
+          align-items: center;
+          padding-left: 55px;
+          cursor: pointer;
+          position: relative;
+          background: url('../../assets/img/user.png') no-repeat left center;
+          background-size: 40px 40px;
+          font-size: 16px;
+          color: black;
+
+          .userInco {
+            margin-left: 10px;
+            color: black;
+          }
+
+          .userInco1 {
+            display: none;
+          }
+
+          .userSet {
+            width: 140px;
+            opacity: 0;
+            pointer-events: none;
+            transition: bottom .3s;
+            height: 120px;
+            position: absolute;
+            left: 50%;
+            transform: translateX(-50%);
+            bottom: -80px;
+
+            // color: rgb(226, 223, 223);
+            &>div {
+              box-shadow: 1px 1px 4px 4px #ccc;
+              margin-top: 15px;
+              border-radius: 10px;
+              overflow: hidden;
+
+              &>span {
+                background-color: #fff;
+                display: block;
+                width: 100%;
+                height: 50px;
+                line-height: 50px;
+                text-align: center;
+
+                &:hover {
+                  color: var(--themeColor);
+                }
+              }
+            }
+
+          }
+
+          &:hover {
+            .userSet {
+              opacity: 1;
+              pointer-events: auto;
+              bottom: -120px;
+            }
+
+            .userInco1 {
+              display: block;
+            }
+
+            .userInco2 {
+              display: none;
+            }
+
+
+          }
+        }
+      }
+
+      .layoutRightMain {
+        height: calc(100% - 60px);
+        padding: 15px;
+        background-color: #ecedf1;
+
+        .mainBoxR {
+          width: 100%;
+          height: 100%;
+          // overflow: hidden;
+          position: relative;
+
+          &>div {
+            width: 100%;
+            height: 100%;
+          }
+        }
+      }
+
+    }
+
+
+
+  }
+}

+ 262 - 0
src/pages/Layout/index.tsx

@@ -0,0 +1,262 @@
+import React, {
+  useCallback,
+  useEffect,
+  useMemo,
+  useRef,
+  useState,
+} from "react";
+import { CaretUpOutlined, CaretDownOutlined } from "@ant-design/icons";
+import styles from "./index.module.scss";
+import SpinLoding from "@/components/SpinLoding";
+import { Route, Switch, useLocation } from "react-router-dom";
+import AuthRoute from "@/components/AuthRoute";
+import classNames from "classnames";
+import history from "@/utils/history";
+import { Button, Form, Input, Modal, Popconfirm } from "antd";
+import { Base64 } from "js-base64";
+import encodeStr from "@/utils/pass";
+import { passWordEditAPI } from "@/store/action/layout";
+import { getTokenInfo, removeTokenInfo } from "@/utils/storage";
+import { MessageFu } from "@/utils/message";
+import logoImg from "@/assets/img/logo.png";
+import { useDispatch } from "react-redux";
+import NotFound from "@/components/NotFound";
+
+import { RouterType, RouterTypeRow } from "@/types";
+import tabLeftArr from "./data";
+
+function Layout() {
+  const dispatch = useDispatch();
+
+  useEffect(() => {}, [dispatch]);
+
+  // 左侧菜单 和 路由 信息
+  const [list, setList] = useState([] as RouterType);
+
+  useEffect(() => {
+    setList(
+      tabLeftArr.map((v) => ({
+        ...v,
+        son: v.son.filter((v2) => v2.done),
+      }))
+    );
+  }, []);
+
+  // 点击跳转
+  const pathCutFu = useCallback((path: string) => {
+    history.push(path);
+  }, []);
+
+  // 当前路径选中的左侧菜单
+  const location = useLocation();
+  const [path, setPath] = useState("");
+
+  useEffect(() => {
+    const arr = location.pathname.split("/");
+    let pathTemp = "/";
+    if (arr[1]) pathTemp = "/" + arr[1];
+
+    setPath(pathTemp);
+  }, [location]);
+
+  const userInfo = useMemo(() => {
+    return getTokenInfo().user;
+  }, []);
+
+  // 修改密码相关
+  const [open, setOpen] = useState(false);
+
+  // 拿到新密码的输入框的值
+  const oldPasswordValue = useRef("");
+
+  const checkPassWord = (rule: any, value: any = "") => {
+    if (value !== oldPasswordValue.current)
+      return Promise.reject("新密码不一致!");
+    else return Promise.resolve(value);
+  };
+
+  const onFinish = async (values: any) => {
+    // 通过校验之后发送请求
+    if (values.oldPassword === values.newPassword)
+      return MessageFu.warning("新旧密码不能相同!");
+    const obj = {
+      oldPassword: encodeStr(Base64.encode(values.oldPassword)),
+      newPassword: encodeStr(Base64.encode(values.newPassword)),
+    };
+    const res: any = await passWordEditAPI(obj);
+    if (res.code === 0) {
+      MessageFu.success("修改成功!");
+      loginExit();
+    }
+  };
+
+  // 点击退出登录
+  const loginExit = () => {
+    removeTokenInfo();
+    history.push("/login");
+  };
+
+  // 路由信息(过滤之后的)
+  const RouterCom = useMemo(() => {
+    const arr: RouterTypeRow = [];
+    list.forEach((v) => {
+      if (v.son && v.son[0]) {
+        v.son.forEach((v2) => {
+          if (v2.done) arr.push(v2);
+        });
+      }
+    });
+    return arr;
+  }, [list]);
+
+  // 第一个页面不是 项目 管理 的时候 动态 跳转
+  useEffect(() => {
+    if (RouterCom && RouterCom[0] && RouterCom[0].id !== "1.1")
+      history.replace(RouterCom[0].path);
+  }, [RouterCom]);
+
+  return (
+    <div className={styles.Layout}>
+      {/* 左边 */}
+      <div className="layoutLeft">
+        <div className="layoutLeftTop">
+          <img src={logoImg} alt="" />
+        </div>
+        {/* 左边主体 */}
+        <div className="layoutLeftMain">
+          {list.map((v) => (
+            <div
+              className={classNames("layoutLRowBox")}
+              key={v.id}
+              hidden={v.son.every((c) => !c.done)}
+            >
+              <div className="layoutLRowBoxTxt">{v.name}</div>
+              {v.son.map((v2) => (
+                <div
+                  key={v2.id}
+                  className={classNames(
+                    "layoutLRowBoxRow",
+                    path === v2.path ? "active" : ""
+                  )}
+                  onClick={() => pathCutFu(v2.path)}
+                >
+                  {v2.name}
+                </div>
+              ))}
+            </div>
+          ))}
+        </div>
+      </div>
+      {/* 右边 */}
+      <div className="layoutRight">
+        <div className="layoutRightTop">
+          {/* 用户相关 */}
+          <div className="user">
+            {userInfo.realName}
+            <div className="userInco userInco1">
+              <CaretUpOutlined rev={undefined} />
+            </div>
+            <div className="userInco userInco2">
+              <CaretDownOutlined rev={undefined} />
+            </div>
+            <div className="userSet">
+              <div>
+                <span onClick={() => setOpen(true)}>修改密码</span>
+                <Popconfirm
+                  placement="bottom"
+                  title="确定退出吗?"
+                  okText="确定"
+                  cancelText="取消"
+                  onConfirm={loginExit}
+                  okButtonProps={{ loading: false }}
+                >
+                  退出登录
+                </Popconfirm>
+              </div>
+            </div>
+          </div>
+        </div>
+        {/* 右边主体 */}
+        <div className="layoutRightMain">
+          {/* 二级路由页面 */}
+          <div className="mainBoxR">
+            <React.Suspense fallback={<SpinLoding />}>
+              <Switch>
+                {RouterCom.map((v) => (
+                  <AuthRoute key={v.id} exact path={v.path} component={v.Com} />
+                ))}
+
+                <Route path="*" component={NotFound} />
+              </Switch>
+            </React.Suspense>
+          </div>
+        </div>
+      </div>
+
+      {/* 点击修改密码打开的对话框 */}
+      <Modal
+        destroyOnClose
+        open={open}
+        title="修改密码"
+        onCancel={() => setOpen(false)}
+        footer={
+          [] // 设置footer为空,去掉 取消 确定默认按钮
+        }
+      >
+        <Form
+          scrollToFirstError={true}
+          name="basic"
+          labelCol={{ span: 5 }}
+          wrapperCol={{ span: 16 }}
+          onFinish={onFinish}
+          autoComplete="off"
+        >
+          <Form.Item
+            label="旧密码"
+            name="oldPassword"
+            rules={[{ required: true, message: "不能为空!" }]}
+            getValueFromEvent={(e) => e.target.value.replace(/\s+/g, "")}
+          >
+            <Input.Password maxLength={20} />
+          </Form.Item>
+
+          <Form.Item
+            label="新密码"
+            name="newPassword"
+            rules={[
+              { required: true, message: "不能为空!" },
+              { min: 6, max: 15, message: "密码长度为6-15个字符!" },
+            ]}
+            getValueFromEvent={(e) => e.target.value.replace(/\s+/g, "")}
+          >
+            <Input.Password
+              maxLength={15}
+              onChange={(e) => (oldPasswordValue.current = e.target.value)}
+            />
+          </Form.Item>
+
+          <Form.Item
+            label="确定新密码"
+            name="checkPass"
+            rules={[{ validator: checkPassWord }]}
+            getValueFromEvent={(e) => e.target.value.replace(/\s+/g, "")}
+          >
+            <Input.Password maxLength={15} />
+          </Form.Item>
+
+          <Form.Item wrapperCol={{ offset: 14, span: 16 }}>
+            <Button onClick={() => setOpen(false)}>取消</Button>
+            &emsp;
+            <Button type="primary" htmlType="submit">
+              确定
+            </Button>
+          </Form.Item>
+        </Form>
+      </Modal>
+    </div>
+  );
+}
+
+// 使用 React.memo 来优化组件,避免组件的无效更新,类似 类组件里面的PureComponent
+const MemoLayout = React.memo(Layout);
+export default MemoLayout;

+ 112 - 0
src/pages/Login/index.module.scss

@@ -0,0 +1,112 @@
+.Login {
+  width: 100%;
+  height: 100%;
+  display: flex;
+
+  :global {
+
+    .mainLeft {
+      width: 50%;
+      background-image: url("../../assets/img/loginBac.jpg");
+      background-size: 100% 100%;
+    }
+
+
+    .mainRight {
+      width: 50%;
+      background-color: #eff3f8;
+      display: flex;
+      justify-content: center;
+      align-items: center;
+
+      .rightMain {
+        border-radius: 6px;
+        box-shadow: 0px 0px 5px 2px #ccc;
+        width: 70%;
+        background-color: #fff;
+        text-align: center;
+        padding: 0 100px 70px;
+
+        .loginLogo {
+          margin-top: 40px;
+          width: 150px;
+        }
+
+        .mainTitle {
+          font-size: 24px;
+          position: relative;
+          margin: 30px 0;
+          margin-bottom: 40px;
+          letter-spacing: 4px;
+        }
+
+        .inputBox {
+          width: 100%;
+          padding: 0 40px;
+
+
+          .inputBoxRow {
+            width: 100%;
+            margin: 0 auto 40px;
+
+
+
+            .ant-input-suffix .ant-input-password-icon {
+              font-size: 22px;
+            }
+          }
+
+          .ant-input-prefix {
+            margin-right: 10px;
+
+            .anticon {
+              padding-right: 10px;
+              width: 36px;
+              height: 36px;
+
+              svg {
+                width: 100%;
+                height: 100%;
+              }
+            }
+          }
+
+          .ant-input {
+            font-size: 18px;
+            width: 45%;
+            height: 50px;
+            line-height: 50px;
+            background-clip: content-box;
+
+          }
+
+          input:-webkit-autofill {
+            font-size: 18px !important;
+            // -webkit-text-fill-color: #fff !important;
+            background-image: none;
+            -webkit-box-shadow: 0 0 0px 1000px transparent inset !important; //填充阴影,可以用来遮住背景色
+            background-color: transparent;
+            transition: background-color 50000s ease-in-out 0s; //背景色透明  生效时长  过渡效果  启用时延迟的时间
+
+          }
+
+          .ant-input-affix-wrapper {
+            padding: 0 11px;
+            width: 100%;
+            height: 50px;
+
+            .ant-input {
+              background-color: transparent;
+              width: 100%;
+              height: 50px;
+            }
+          }
+        }
+
+        .loginBtn {
+          margin-top: 50px;
+        }
+      }
+    }
+  }
+}

+ 95 - 0
src/pages/Login/index.tsx

@@ -0,0 +1,95 @@
+import styles from "./index.module.scss";
+
+import { Input, Button } from "antd";
+import { UserOutlined, LockOutlined } from "@ant-design/icons";
+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 loginLogoImg from "@/assets/img/logo.png";
+
+export default function Login() {
+  // 账号密码
+  const [userName, setUserName] = useState("");
+  const [passWord, setPassWord] = useState("");
+
+  useEffect(() => {
+    //进入登录页 重置 权限信息为空
+  }, []);
+
+  // 键盘按下回车事件
+  const keyUpEntFu = (e: React.KeyboardEvent<HTMLInputElement>) => {
+    if (e.key === "Enter") loginClickFu();
+  };
+  // 点击登录
+  const loginClickFu = async () => {
+    // 非空判断
+    if (userName === "") return MessageFu.warning("请输入账号!");
+    else if (passWord === "") return MessageFu.warning("请输入密码!");
+    const obj = {
+      userName,
+      passWord: encodeStr(Base64.encode(passWord)),
+    };
+    const res: any = await userLoginAPI(obj);
+    if (res.code === 0) {
+      MessageFu.success("登录成功");
+
+      // 检查密码是不是默认密码,是的话给提示
+      if (passWord === userName + "4dage") {
+        window.setTimeout(() => {
+          MessageFu.warning("您的密码还是默认密码,请尽快修改!");
+        }, 1000);
+      }
+
+      // 用户信息存到本地
+      setTokenInfo(res.data);
+      history.push("/");
+    } else if (res.code === 3014)
+      MessageFu.warning("账号不存在或密码错误,请联系管理员!");
+  };
+
+  return (
+    <div className={styles.Login}>
+      <div className="mainLeft"></div>
+      <div className="mainRight">
+        <div className="rightMain">
+          <img className="loginLogo" src={loginLogoImg} alt="" />
+          <div className="mainTitle">铁塔3D数据管理</div>
+          {/* 账号密码输入框 */}
+          <div className="inputBox">
+            <div className="inputBoxRow">
+              <Input
+                onKeyUp={(e) => keyUpEntFu(e)}
+                value={userName}
+                onChange={(e) => setUserName(e.target.value.trim())}
+                prefix={<UserOutlined rev={undefined} />}
+                placeholder="请输入账号"
+                maxLength={15}
+              />
+            </div>
+            <div className="inputBoxRow">
+              <Input.Password
+                onKeyUp={(e) => keyUpEntFu(e)}
+                value={passWord}
+                onChange={(e) => setPassWord(e.target.value.trim())}
+                prefix={<LockOutlined rev={undefined} />}
+                placeholder="请输入密码"
+                maxLength={20}
+              />
+            </div>
+          </div>
+
+          {/* 登录按钮 */}
+          <div className="loginBtn">
+            <Button type="primary" size="large" onClick={loginClickFu}>
+              &emsp;&emsp;登&emsp;&emsp;录&emsp;&emsp;
+            </Button>
+          </div>
+        </div>
+      </div>
+    </div>
+  );
+}

+ 5 - 0
src/pages/初始化组件/index.module.scss

@@ -0,0 +1,5 @@
+.AAAAA{
+  :global{
+    
+  }
+}

+ 14 - 0
src/pages/初始化组件/index.tsx

@@ -0,0 +1,14 @@
+import React from "react";
+import styles from "./index.module.scss";
+ function AAAAA() {
+  
+  return (
+    <div className={styles.AAAAA}>
+      <h1>AAAAA</h1>
+    </div>
+  )
+}
+
+const MemoAAAAA = React.memo(AAAAA);
+
+export default MemoAAAAA;

+ 17 - 0
src/store/action/C2Log.ts

@@ -0,0 +1,17 @@
+import http from "@/utils/http";
+import { AppDispatch } from "..";
+/**
+ * 获取日志表格列表
+ */
+export const getLogListAPI = (data: any) => {
+  return async (dispatch: AppDispatch) => {
+    const res = await http.post("sys/log/list", data);
+    if (res.code === 0) {
+      const obj = {
+        list: res.data.records,
+        total: res.data.total,
+      };
+      dispatch({ type: "log/getList", payload: obj });
+    }
+  };
+};

+ 42 - 0
src/store/action/layout.ts

@@ -0,0 +1,42 @@
+import { domShowFu, progressDomFu } from "@/utils/domShow";
+import http from "@/utils/http";
+import axios from "axios";
+import store from "..";
+
+/**
+ * 用户登录接口
+ */
+export const userLoginAPI = (data: any) => {
+  return http.post("admin/login", { ...data });
+};
+
+/**
+ * 修改密码接口
+ */
+export const passWordEditAPI = (data: any) => {
+  return http.post("sys/user/updatePwd", { ...data });
+};
+
+const CancelToken = axios.CancelToken;
+/**
+ * 上传封面图和附件
+ */
+export const API_upFile = (data: any, url: string) => {
+  domShowFu("#UpAsyncLoding", true);
+
+  return http.post(url, data, {
+    timeout: 0,
+    // 显示进度条
+    onUploadProgress: (e: any) => {
+      const complete = (e.loaded / e.total) * 100 || 0;
+      progressDomFu(complete + "%");
+    },
+    // 取消上传
+    cancelToken: new CancelToken(function executor(c) {
+      store.dispatch({
+        type: "layout/closeUpFile",
+        payload: { fu: c, state: true },
+      });
+    }),
+  });
+};

+ 20 - 0
src/store/index.ts

@@ -0,0 +1,20 @@
+// 导入 redux
+import { applyMiddleware, legacy_createStore as createStore } from 'redux'
+// 导入自己封装的  rootReducer 
+import rootReducer from './reducer'
+// 导入调试工具和 异步的 redux(用来发送异步请求)
+// 调试工具需要下载谷歌 扩展程序 我用的是 Redux DevTools 3.0.17
+import { composeWithDevTools } from 'redux-devtools-extension'
+import thunk from 'redux-thunk'
+
+// 创建仓库实例
+const store = createStore(rootReducer, composeWithDevTools(applyMiddleware(thunk)))
+
+// 声明 RootState,在使用仓库的时候用来使用
+export type RootState = ReturnType<typeof store.getState>
+
+// 声明 AppDispatch,在异步请求的时候来使用
+export type AppDispatch = typeof store.dispatch
+
+// 导出仓库实例
+export default store

+ 27 - 0
src/store/reducer/C2Log.ts

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

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

@@ -0,0 +1,16 @@
+// 导入合并reducer的依赖
+import { combineReducers } from "redux";
+
+// 导入 登录 模块的 reducer
+import A0Layout from "./layout";
+
+import C2Log from "./C2Log";
+
+// 合并 reducer
+const rootReducer = combineReducers({
+  A0Layout,
+  C2Log,
+});
+
+// 默认导出
+export default rootReducer;

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

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

+ 11 - 0
src/types/api/C2Log.d.ts

@@ -0,0 +1,11 @@
+export type LogTableType = {
+  createTime: string;
+  creatorId: null;
+  creatorName: string;
+  description: string;
+  id: number;
+  ip: string;
+  type: string;
+  updateTime: null;
+  userName: string;
+}

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

@@ -0,0 +1,26 @@
+export type LookDomType = {
+  src: string;
+  type: "video" | "audio" | "model" | "";
+  flag?: boolean;
+};
+
+export type RouterTypeRow = {
+  id: string;
+  name: string;
+  path: string;
+  Com: React.LazyExoticComponent<React.MemoExoticComponent<() => JSX.Element>>;
+  done: boolean;
+}[];
+
+export type RouterType = {
+  id: string;
+  name: string;
+  son: RouterTypeRow;
+}[];
+
+export type FileImgListType = {
+  id: number;
+  fileName: string;
+  filePath: string;
+  type: "img" | "video" | "doc";
+};

+ 7 - 0
src/types/declaration.d.ts

@@ -0,0 +1,7 @@
+declare module "history";
+declare module "*.scss";
+declare module "*.png";
+declare module "*.jpg";
+declare module "*.gif";
+declare module "*.svg";
+declare module "js-export-excel";

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

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

+ 112 - 0
src/utils/authFilesLook.ts

@@ -0,0 +1,112 @@
+import store from "@/store";
+import { baseUpUrl } from "./http";
+import { getTokenFu } from "./storage";
+import { domShowFu } from "./domShow";
+
+// 携带token,转换成文件流
+export const urlChangeFu = (
+  url: string,
+  flag: boolean,
+  type?: "img" | "video" | "audio" | "pdf",
+  name?: string
+) => {
+  // 打开加载中
+  domShowFu("#AsyncSpinLoding", true);
+
+  // flag true 为 生成 a标签 下载
+
+  let xhr = new XMLHttpRequest();
+
+  xhr.open("get", baseUpUrl.replace("api/", "api") + url, true);
+
+  xhr.responseType = "blob";
+  xhr.onload = function (res) {
+    // 取消加载中状态
+    domShowFu("#AsyncSpinLoding", false);
+
+    if (this.status === 200) {
+      let blob = this.response;
+
+      // 转为为Blob格式的
+      const srcRes = window.URL.createObjectURL(blob);
+
+      if (flag) {
+        // 创建a标签下载
+        let link = document.createElement("a"); //创建a标签
+        link.style.display = "none"; //使其隐藏
+        link.href = srcRes; //赋予文件下载地址
+        link.setAttribute("download", name!); //设置下载属性 以及文件名
+        document.body.appendChild(link); //a标签插至页面中
+        link.click(); //强制触发a标签事件
+        document.body.removeChild(link);
+      } else {
+        if (type === "img") {
+          store.dispatch({
+            type: "layout/lookBigImg",
+            payload: {
+              url: srcRes,
+              show: true,
+            },
+          });
+        } else if (type === "pdf") {
+          window.open(srcRes);
+        } else if (type === "audio" || type === "video") {
+          store.dispatch({
+            type: "layout/lookDom",
+            payload: {
+              src: srcRes,
+              type,
+              flag: true,
+            },
+          });
+        }
+      }
+    }
+  };
+
+  // 携带token
+  xhr.setRequestHeader("token", getTokenFu());
+
+  xhr.send();
+};
+
+// 查看 权限 图片 /视频 、音频
+export const authFilesLookFu = (name: string, url?: string) => {
+  let flag = false;
+
+  const nameRes = name ? name : "";
+
+  if (nameRes.toLowerCase().endsWith(".pdf")) {
+    if (url) urlChangeFu(url, false, "pdf");
+    flag = true;
+  }
+
+  const arr1 = [".png", ".jpg", ".jpeg", ".gif"];
+  arr1.forEach((v) => {
+    if (nameRes.toLowerCase().endsWith(v)) {
+      if (url) urlChangeFu(url, false, "img");
+
+      flag = true;
+    }
+  });
+
+  let type: "" | "video" | "audio" = "";
+
+  const arr2 = [".mp3", ".wav"];
+
+  arr2.forEach((v) => {
+    if (nameRes.toLowerCase().endsWith(v)) {
+      type = "audio";
+      flag = true;
+    }
+  });
+
+  if (nameRes.toLowerCase().endsWith(".mp4")) {
+    type = "video";
+    flag = true;
+  }
+
+  if (type && url) urlChangeFu(url, false, type);
+
+  return flag;
+};

+ 35 - 0
src/utils/domShow.ts

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

+ 60 - 0
src/utils/filesLook.ts

@@ -0,0 +1,60 @@
+import store from "@/store";
+import { baseURL } from "./http";
+
+const filesLookFu = (name: string, url?: string) => {
+  let flag = false;
+
+  const nameRes = name ? name : "";
+
+  if (nameRes.toLowerCase().endsWith(".pdf")) {
+    if (url) window.open(baseURL + url);
+    flag = true;
+  }
+
+  const arr1 = [".png", ".jpg", ".jpeg", ".gif"];
+  arr1.forEach((v) => {
+    if (nameRes.toLowerCase().endsWith(v)) {
+      if (url) {
+        store.dispatch({
+          type: "layout/lookBigImg",
+          payload: {
+            url: baseURL + url,
+            show: true,
+          },
+        });
+      }
+
+      flag = true;
+    }
+  });
+
+  let type: "" | "video" | "audio" = "";
+
+  const arr2 = [".mp3", ".wav"];
+
+  arr2.forEach((v) => {
+    if (nameRes.toLowerCase().endsWith(v)) {
+      type = "audio";
+      flag = true;
+    }
+  });
+
+  if (nameRes.toLowerCase().endsWith(".mp4")) {
+    type = "video";
+    flag = true;
+  }
+
+  if (type && url) {
+    store.dispatch({
+      type: "layout/lookDom",
+      payload: {
+        src: url,
+        type,
+      },
+    });
+  }
+
+  return flag;
+};
+
+export default filesLookFu;

+ 17 - 0
src/utils/history.ts

@@ -0,0 +1,17 @@
+import { createHashHistory  } from 'history'
+const history = createHashHistory()
+export default history
+
+export const urlParameter = (data: string) => {
+  if (data) {
+    const query = data.substring(data.indexOf("?") + 1);
+    const arr = query.split("&");
+    const params = {} as any;
+    arr.forEach((v) => {
+      const key = v.substring(0, v.indexOf("="));
+      const val = v.substring(v.indexOf("=") + 1);
+      params[key] = val;
+    });
+    return params;
+  } else return {};
+};

+ 112 - 0
src/utils/http.ts

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

+ 50 - 0
src/utils/message.ts

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

+ 100 - 0
src/utils/pass.ts

@@ -0,0 +1,100 @@
+function randomWord(randomFlag: boolean, min: number, max: number = 15) {
+  let str = "";
+  let range = min;
+  const arr = [
+    "0",
+    "1",
+    "2",
+    "3",
+    "4",
+    "5",
+    "6",
+    "7",
+    "8",
+    "9",
+    "a",
+    "b",
+    "c",
+    "d",
+    "e",
+    "f",
+    "g",
+    "h",
+    "i",
+    "j",
+    "k",
+    "l",
+    "m",
+    "n",
+    "o",
+    "p",
+    "q",
+    "r",
+    "s",
+    "t",
+    "u",
+    "v",
+    "w",
+    "x",
+    "y",
+    "z",
+    "A",
+    "B",
+    "C",
+    "D",
+    "E",
+    "F",
+    "G",
+    "H",
+    "I",
+    "J",
+    "K",
+    "L",
+    "M",
+    "N",
+    "O",
+    "P",
+    "Q",
+    "R",
+    "S",
+    "T",
+    "U",
+    "V",
+    "W",
+    "X",
+    "Y",
+    "Z",
+  ];
+  // 随机产生
+  if (randomFlag) {
+    range = Math.round(Math.random() * (max - min)) + min;
+  }
+  for (var i = 0; i < range; i++) {
+    const pos = Math.round(Math.random() * (arr.length - 1));
+    str += arr[pos];
+  }
+  return str;
+}
+
+const encodeStr = (str: string, strv = "") => {
+  const NUM = 2;
+  const front = randomWord(false, 8);
+  const middle = randomWord(false, 8);
+  const end = randomWord(false, 8);
+
+  const str1 = str.substring(0, NUM);
+  const str2 = str.substring(NUM);
+
+  if (strv) {
+    const strv1 = strv.substring(0, NUM);
+    const strv2 = strv.substring(NUM);
+    return [
+      front + str2 + middle + str1 + end,
+      front + strv2 + middle + strv1 + end,
+    ];
+  }
+
+  return front + str2 + middle + str1 + end;
+};
+
+export default encodeStr;

+ 75 - 0
src/utils/storage.ts

@@ -0,0 +1,75 @@
+// ------------------------------------token的本地存储------------------------------------
+
+// 用户 Token 的本地缓存键名,自己定义
+const TOKEN_KEY = "SWSDNBWJ_HT_USER_INFO";
+
+/**
+ * 从本地缓存中获取 用户 信息
+ */
+export const getTokenInfo = (): any => {
+  return JSON.parse(localStorage.getItem(TOKEN_KEY) || "{}");
+};
+
+/**
+ * 将 用户 信息存入缓存
+ * @param {Object} tokenInfo 从后端获取到的 Token 信息
+ */
+export const setTokenInfo = (tokenInfo: any): void => {
+  localStorage.setItem(TOKEN_KEY, JSON.stringify(tokenInfo));
+};
+
+/**
+ * 删除本地缓存中的 用户 信息
+ */
+export const removeTokenInfo = (): void => {
+  localStorage.removeItem(TOKEN_KEY);
+};
+
+/**
+ * 判断本地缓存中是否存在 Token 信息
+ */
+export const hasToken = (): boolean => {
+  return Boolean(getTokenInfo().token);
+};
+
+/**
+ * 获取本地缓存中是否存在 Token 信息
+ */
+export const getTokenFu = (): string => {
+  return getTokenInfo().token;
+};
+
+// ---------------------------------------- 功能权限 的本地存储 --------------------
+const A4_KEY = "SWSDNBWJ_HT_A4_INFO";
+
+/**
+ * 从本地缓存中获取 功能权限 信息
+ */
+export const A4getStorage = (): any => {
+  return JSON.parse(localStorage.getItem(A4_KEY) || "[]");
+};
+
+/**
+ *  功能权限 信息 存入本地缓存
+ */
+export const A4setStorage = (info: any): void => {
+  localStorage.setItem(A4_KEY, JSON.stringify(info));
+};
+
+// ---------------------------------------- 项目管理-内控文件视图-下载 的本地存储 --------------------
+
+const A1_DOWN = "SWSDNBWJ_HT_A1_DOWNIDS";
+
+/**
+ * 从本地缓存中获取 项目管理-内控文件-下载 信息
+ */
+export const A1getStorage = (): number[] => {
+  return JSON.parse(localStorage.getItem(A1_DOWN) || "[]");
+};
+
+/**
+ *  项目管理-内控文件-下载 信息 存入本地缓存
+ */
+export const A1setStorage = (info: any): void => {
+  localStorage.setItem(A1_DOWN, JSON.stringify(info));
+};

+ 27 - 0
tsconfig.json

@@ -0,0 +1,27 @@
+{
+  "extends": "./path.tsconfig.json",
+  "compilerOptions": {
+    "target": "es5",
+    "lib": [
+      "dom",
+      "dom.iterable",
+      "esnext"
+    ],
+    "allowJs": true,
+    "skipLibCheck": true,
+    "esModuleInterop": true,
+    "allowSyntheticDefaultImports": true,
+    "strict": true,
+    "forceConsistentCasingInFileNames": true,
+    "noFallthroughCasesInSwitch": true,
+    "module": "esnext",
+    "moduleResolution": "node",
+    "resolveJsonModule": true,
+    "isolatedModules": true,
+    "noEmit": true,
+    "jsx": "react-jsx"
+  },
+  "include": [
+    "src"
+  ]
+}