shaogen1995 il y a 2 ans
commit
63645bc976
69 fichiers modifiés avec 31518 ajouts et 0 suppressions
  1. 23 0
      .gitignore
  2. 46 0
      README.md
  3. 10 0
      config-overrides.js
  4. 29730 0
      package-lock.json
  5. 61 0
      package.json
  6. 8 0
      path.tsconfig.json
  7. BIN
      public/favicon.ico
  8. 43 0
      public/index.html
  9. 29 0
      src/App.tsx
  10. BIN
      src/assets/img/login/LOGO.png
  11. BIN
      src/assets/img/login/Statistics.png
  12. BIN
      src/assets/img/login/bg.jpg
  13. BIN
      src/assets/img/login/flower.png
  14. BIN
      src/assets/img/login/homeBg.jpg
  15. BIN
      src/assets/img/login/icon_1.png
  16. BIN
      src/assets/img/login/icon_2.png
  17. BIN
      src/assets/img/login/icon_3.png
  18. BIN
      src/assets/img/login/icon_4.png
  19. BIN
      src/assets/img/login/icon_5.png
  20. BIN
      src/assets/img/login/remind.png
  21. BIN
      src/assets/img/login/user_1.png
  22. 42 0
      src/assets/styles/base.css
  23. 18 0
      src/components/AsyncSpinLoding/index.module.scss
  24. 14 0
      src/components/AsyncSpinLoding/index.tsx
  25. 32 0
      src/components/AuthRoute/index.tsx
  26. 32 0
      src/components/LeftBar/index.module.scss
  27. 37 0
      src/components/LeftBar/index.tsx
  28. 13 0
      src/components/NotFound/index.tsx
  29. 8 0
      src/components/SpinLoding/index.module.scss
  30. 13 0
      src/components/SpinLoding/index.tsx
  31. 23 0
      src/index.tsx
  32. 183 0
      src/pages/Home/index.module.scss
  33. 213 0
      src/pages/Home/index.tsx
  34. 132 0
      src/pages/Layout/index.module.scss
  35. 178 0
      src/pages/Layout/index.tsx
  36. 68 0
      src/pages/Login/index.module.scss
  37. 71 0
      src/pages/Login/index.tsx
  38. 5 0
      src/pages/Object/index.module.scss
  39. 43 0
      src/pages/Object/index.tsx
  40. 5 0
      src/pages/Object1/index.module.scss
  41. 9 0
      src/pages/Object1/index.tsx
  42. 5 0
      src/pages/Object2/index.module.scss
  43. 9 0
      src/pages/Object2/index.tsx
  44. 5 0
      src/pages/Object3/index.module.scss
  45. 9 0
      src/pages/Object3/index.tsx
  46. 5 0
      src/pages/Object4/index.module.scss
  47. 9 0
      src/pages/Object4/index.tsx
  48. 5 0
      src/pages/Object5/index.module.scss
  49. 9 0
      src/pages/Object5/index.tsx
  50. 5 0
      src/pages/Object6/index.module.scss
  51. 9 0
      src/pages/Object6/index.tsx
  52. 6 0
      src/pages/Stores/index.module.scss
  53. 9 0
      src/pages/Stores/index.tsx
  54. 6 0
      src/pages/System/index.module.scss
  55. 9 0
      src/pages/System/index.tsx
  56. 5 0
      src/pages/初始化组件/index.module.scss
  57. 9 0
      src/pages/初始化组件/index.tsx
  58. 26 0
      src/store/action/login.ts
  59. 15 0
      src/store/index.ts
  60. 9 0
      src/store/reducer/index.ts
  61. 22 0
      src/store/reducer/login.ts
  62. 3 0
      src/types/declaration.d.ts
  63. 2 0
      src/types/index.d.ts
  64. 4 0
      src/types/store/login.d.ts
  65. 3 0
      src/utils/history.ts
  66. 69 0
      src/utils/http.ts
  67. 100 0
      src/utils/pass.ts
  68. 35 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*

+ 46 - 0
README.md

@@ -0,0 +1,46 @@
+# Getting Started with Create React App
+
+This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
+
+## Available Scripts
+
+In the project directory, you can run:
+
+### `npm start`
+
+Runs the app in the development mode.\
+Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
+
+The page will reload if you make edits.\
+You will also see any lint errors in the console.
+
+### `npm test`
+
+Launches the test runner in the interactive watch mode.\
+See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
+
+### `npm run build`
+
+Builds the app for production to the `build` folder.\
+It correctly bundles React in production mode and optimizes the build for the best performance.
+
+The build is minified and the filenames include the hashes.\
+Your app is ready to be deployed!
+
+See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
+
+### `npm run eject`
+
+**Note: this is a one-way operation. Once you `eject`, you can’t go back!**
+
+If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
+
+Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.
+
+You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.
+
+## Learn More
+
+You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
+
+To learn React, check out the [React documentation](https://reactjs.org/).

+ 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)

Fichier diff supprimé car celui-ci est trop grand
+ 29730 - 0
package-lock.json


+ 61 - 0
package.json

@@ -0,0 +1,61 @@
+{
+  "name": "demo",
+  "version": "0.1.0",
+  "private": true,
+  "dependencies": {
+    "@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.0.4",
+    "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",
+    "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",
+    "echarts": "^5.4.0"
+  },
+  "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/*"]
+      }
+    }
+  }

BIN
public/favicon.ico


+ 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>

+ 29 - 0
src/App.tsx

@@ -0,0 +1,29 @@
+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";
+const Layout = React.lazy(() => import("./pages/Layout"));
+const Login = React.lazy(() => import("./pages/Login"));
+
+export default function App() {
+  return (
+    <>
+      {/* 关于路由 */}
+      <Router history={history}>
+        <React.Suspense fallback={<SpinLoding />}>
+          <Switch>
+            <Route path="/login" component={Login} />
+            <AuthRoute path="/" component={Layout} />
+          </Switch>
+        </React.Suspense>
+      </Router>
+
+      {/* 发送请求的加载组件 */}
+      <AsyncSpinLoding />
+    </>
+  );
+}

BIN
src/assets/img/login/LOGO.png


BIN
src/assets/img/login/Statistics.png


BIN
src/assets/img/login/bg.jpg


BIN
src/assets/img/login/flower.png


BIN
src/assets/img/login/homeBg.jpg


BIN
src/assets/img/login/icon_1.png


BIN
src/assets/img/login/icon_2.png


BIN
src/assets/img/login/icon_3.png


BIN
src/assets/img/login/icon_4.png


BIN
src/assets/img/login/icon_5.png


BIN
src/assets/img/login/remind.png


BIN
src/assets/img/login/user_1.png


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

@@ -0,0 +1,42 @@
+*{
+  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: #333;
+}
+a {
+  text-decoration: none;
+  color: #333;
+  outline: none;
+}
+i {
+  font-style: normal;
+}
+img {
+  max-width: 100%;
+  max-height: 100%;
+  vertical-align: middle;
+}
+ul {
+  list-style: none;
+}
+#root{
+  width: 100vw;
+  height: 100vh;
+  min-width: 1600px;
+  min-height: 900px;
+  overflow-y: auto;
+}
+/* 主题色 */
+:root{
+  --themeColor:#9F1927
+}

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

@@ -0,0 +1,18 @@
+.AsyncSpinLoding {
+  display: none;
+  position: fixed;
+  z-index: 99;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  background-color: rgba(255, 255, 255, .4);
+  :global{
+    .ant-spin-spinning{
+      position: absolute;
+      top: 50%;
+      left: 50%;
+      transform: translate(-50%,-50%);
+    }
+  }
+}

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

@@ -0,0 +1,14 @@
+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 { message } from "antd";
+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 {
+          message.warning("登录失效!");
+          return (
+            <Redirect
+              to={{
+                pathname: "/login",
+              }}
+            />
+          );
+        }
+      }}
+    />
+  );
+}

+ 32 - 0
src/components/LeftBar/index.module.scss

@@ -0,0 +1,32 @@
+.LeftBar{
+  padding-top: 20px;
+  :global{
+    .leftRow{
+      cursor: pointer;
+      width: 100%;
+      height: 70px;
+      font-size: 16px;
+      display: flex;
+      justify-content: center;
+      align-items: center;
+      &>div{
+        opacity: 0;
+      }
+      &:hover{
+        color: #D3B453;
+      }
+    }
+    .active{
+      background-color: var(--themeColor);
+      color: #D3B453;
+      pointer-events: none;
+      &>div{
+        opacity: 1;
+        width: 10px;
+        height: 10px;
+        background-color: #D3B453;
+        border-radius: 50%;
+      }
+    }
+  }
+}

+ 37 - 0
src/components/LeftBar/index.tsx

@@ -0,0 +1,37 @@
+import styles from "./index.module.scss";
+import classNames from "classnames";
+import history from "@/utils/history";
+import { useLocation } from "react-router-dom";
+import { useEffect, useState } from "react";
+export default function LeftBar({ data }: any) {
+  const cutRouter = (path: string) => {
+    history.push(path);
+  };
+  const location = useLocation();
+
+  const [pathId, setPathId] = useState(1);
+  useEffect(() => {
+    const arr = location.pathname.split("/");
+    let id = 1;
+    if (arr[2]) id = Number(arr[2]);
+
+    setPathId(id);
+  }, [location]);
+  return (
+    <div className={styles.LeftBar}>
+      {data.length > 0
+        ? data.map((v: any) => (
+            <div
+              onClick={() => cutRouter(v.path)}
+              className={classNames("leftRow", v.id === pathId ? "active" : "")}
+              key={v.id}
+            >
+              <div></div>
+              &emsp;{v.name}&emsp;
+              <div></div>
+            </div>
+          ))
+        : null}
+    </div>
+  );
+}

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

@@ -0,0 +1,13 @@
+import history from "@/utils/history";
+import { Button, Result } from "antd";
+
+export default function NotFound() {
+  return (
+    <Result
+      status="404"
+      title="404"
+      subTitle="页面找不到,或者没有权限!"
+      extra={<Button onClick={()=>history.push('/')} type="primary">去首页</Button>}
+    />
+  );
+}

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

@@ -0,0 +1,8 @@
+.SpinLoding {
+  width: 100%;
+  height: 100%;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  background-color: rgba(255, 255, 255, .4);
+}

+ 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;

+ 23 - 0
src/index.tsx

@@ -0,0 +1,23 @@
+import App from "./App";
+import store from "./store/index";
+import { Provider } from "react-redux";
+
+import zhCN from "antd/es/locale/zh_CN";
+import { ConfigProvider } from "antd";
+
+import { createRoot } from "react-dom/client";
+
+const container = document.getElementById("root") as HTMLElement;
+const root = createRoot(container);
+
+root.render(
+  <ConfigProvider locale={zhCN} theme={{
+    token:{
+      colorPrimary:'#9F1927'
+    }
+  }}>
+    <Provider store={store}>
+      <App />
+    </Provider>
+  </ConfigProvider>
+);

+ 183 - 0
src/pages/Home/index.module.scss

@@ -0,0 +1,183 @@
+.Home {
+
+
+  :global {
+    .homeMain {
+      height: 100%;
+
+      .title {
+        width: 100%;
+        height: 180px;
+        background-color: #fff;
+        border-radius: 5px;
+        padding: 10px 40px;
+        display: flex;
+        justify-content: space-between;
+
+        .titleL {
+          display: flex;
+          flex-direction: column;
+          justify-content: space-between;
+          padding: 10px 20px 10px 0px;
+          width: 240px;
+          border-right: 1px solid #999999;
+
+          &>h3 {
+            font-size: 20px;
+            color: var(--themeColor);
+          }
+        }
+
+        .titleR {
+          width: calc(100% - 270px);
+          display: flex;
+          justify-content: space-around;
+          padding: 20px 0;
+          text-align: center;
+
+          .row {
+            cursor: pointer;
+
+            &>div {
+              width: 80px;
+              height: 80px;
+              background-size: 100% 100%;
+            }
+
+            .bac1 {
+              background-image: url('../../assets/img/login/icon_1.png');
+            }
+
+            .bac2 {
+              background-image: url('../../assets/img/login/icon_2.png');
+            }
+
+            .bac3 {
+              background-image: url('../../assets/img/login/icon_3.png');
+            }
+
+            .bac4 {
+              background-image: url('../../assets/img/login/icon_4.png');
+            }
+
+            .bac5 {
+              background-image: url('../../assets/img/login/icon_5.png');
+            }
+
+            &>p {
+              margin-top: 15px;
+            }
+
+            &:hover {
+              color: #D3B453;
+            }
+          }
+        }
+      }
+
+      .flooBox {
+        margin-top: 35px;
+        width: 100%;
+        height: calc(100% - 220px);
+        border-radius: 5px;
+        display: flex;
+        justify-content: space-between;
+
+        &>div {
+          background-color: #fff;
+          border-radius: 5px;
+          padding: 0px 30px;
+
+          .flooTit {
+            position: relative;
+            padding-left: 50px;
+            width: 100%;
+            height: 80px;
+            display: flex;
+            align-items: center;
+            justify-content: space-between;
+            color: var(--themeColor);
+            font-size: 22px;
+            font-weight: 700;
+            background: url('../../assets/img/login/Statistics.png') no-repeat left center;
+            background-size: 36px 40px;
+
+            &::before {
+              content: '';
+              position: absolute;
+              bottom: 0;
+              left: 0;
+              width: 100%;
+              height: 2px;
+              background-image: linear-gradient(to right, rgba(159, 25, 39, 1), rgba(159, 25, 39, .3));
+            }
+          }
+        }
+
+        .flooBoxL {
+          width: 960px;
+
+          .chartBox {
+            width: 100%;
+            height: calc(100% - 80px);
+            position: relative;
+
+            .chart {
+              width: 100%;
+              height: 100%;
+            }
+
+            .chartTit {
+              position: absolute;
+              z-index: 10;
+              top: 15px;
+              left: 17px;
+            }
+          }
+        }
+
+        .flooBoxR {
+          width: 600px;
+
+          .flooTit {
+            background: url('../../assets/img/login/remind.png') no-repeat left center;
+            background-size: 36px 40px;
+          }
+
+          .doneBox {
+            padding: 20px 35px 0;
+            width: 100%;
+            height: calc(100% - 80px);
+            display: flex;
+            flex-wrap: wrap;
+            margin-bottom: 3%;
+
+            .doneRow {
+              padding: 15px;
+              width: 50%;
+              height: 30%;
+              border-bottom: 1px solid #999999;
+
+              .doneRow_tit {
+                margin-bottom: 30px;
+                font-weight: 700;
+              }
+
+              .doneRow_txt {
+                &>span {
+                  font-weight: 700;
+                  font-size: 30px;
+                  color: var(--themeColor);
+                }
+              }
+            }
+
+            .noneRow {
+              border: none;
+            }
+          }
+        }
+      }
+    }
+  }
+}

+ 213 - 0
src/pages/Home/index.tsx

@@ -0,0 +1,213 @@
+import { getTokenInfo } from "@/utils/storage";
+import { useEffect, useMemo, useRef, useState } from "react";
+import styles from "./index.module.scss";
+import dayjs from "dayjs";
+import { Button } from "antd";
+import classNames from "classnames";
+import * as echarts from "echarts/core";
+import { TooltipComponent, GridComponent } from "echarts/components";
+import { BarChart } from "echarts/charts";
+import { CanvasRenderer } from "echarts/renderers";
+
+echarts.use([TooltipComponent, GridComponent, BarChart, CanvasRenderer]);
+
+export default function Home() {
+  // 实时时间
+  const [nowTime, setNowTime] = useState(
+    dayjs(Date.now()).format("YYYY年MM月DD HH:mm")
+  );
+
+  // 头部右侧
+  const tabList = useMemo(() => {
+    return [
+      { id: 1, path: "/", name: "藏品登记" },
+      { id: 2, path: "/object", name: "藏品总账" },
+      { id: 3, path: "/stores", name: "入库管理" },
+      { id: 4, path: "/system", name: "出库管理" },
+      { id: 5, path: "/system", name: "藏品注销" },
+    ];
+  }, []);
+
+  const timeRef = useRef(-1);
+  useEffect(() => {
+    timeRef.current = window.setInterval(() => {
+      setNowTime(() => {
+        return dayjs(Date.now()).format("YYYY年MM月DD HH:mm");
+      });
+    }, 1000);
+    return () => {
+      clearInterval(timeRef.current);
+    };
+  }, []);
+
+  const userInfo = useMemo(() => {
+    return getTokenInfo().user;
+  }, []);
+
+  // -----------图表
+  useEffect(() => {
+    const chartDom: any = document.querySelector(".chart");
+    const myChart = echarts.init(chartDom);
+    const option = {
+      color: ["#9F1927"],
+      tooltip: {
+        trigger: "axis",
+        axisPointer: {
+          type: "shadow",
+        },
+      },
+      grid: {
+        left: "3%",
+        right: "4%",
+        bottom: "3%",
+        containLabel: true,
+      },
+      xAxis: [
+        {
+          type: "category",
+          data: ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"],
+          axisTick: {
+            show: false,
+            alignWithLabel: false,
+          },
+          axisLabel: {
+            textStyle: {
+              color: "#000", //坐标值得具体的颜色
+            },
+          },
+          axisLine: {
+            //Y轴坐标轴
+            show: true,
+            // 坐标的颜色和宽度
+            lineStyle: {
+              width: 2,
+              color: "#9F1927",
+            },
+          },
+        },
+      ],
+      yAxis: [
+        {
+          type: "value",
+          axisLabel: {
+            textStyle: {
+              color: "#000", //坐标值得具体的颜色
+            },
+          },
+          axisLine: {
+            //Y轴坐标轴
+            show: true,
+            lineStyle: {
+              width: 2,
+              color: "#9F1927",
+            },
+          },
+          // 隐藏背景坐标线段
+          splitLine: {
+            show: false,
+          },
+        },
+      ],
+      series: [
+        {
+          name: "",
+          type: "bar",
+          barWidth: "60%",
+          data: [10, 52, 200, 334, 390, 330, 220],
+          itemStyle: {
+            normal: {
+              color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+                { offset: 0, color: "rgba(159, 25, 39, .5)" }, //渐变头部色
+                { offset: 1, color: "rgba(159, 25, 39, 1)" },
+              ]),
+
+              barBorderRadius: 2,
+              label: {
+                show: true, //开启显示
+                position: "top", //在上方显示
+                textStyle: {
+                  //数值样式
+                  color: "#9F1927",
+                  fontSize: 16,
+                },
+              },
+            },
+          },
+        },
+      ],
+    };
+    option && myChart.setOption(option);
+  }, []);
+
+  // -------代办提醒
+  // 头部右侧
+  const doneList = useMemo(() => {
+    return [
+      { id: 1, num: 0, name: "藏品登记" },
+      { id: 2, num: 7, name: "入库管理" },
+      { id: 3, num: 5, name: "出库管理" },
+      { id: 4, num: 0, name: "藏品修改" },
+      { id: 5, num: 21, name: "藏品注销" },
+    ];
+  }, []);
+
+  return (
+    <div className={styles.Home}>
+      <div className="homeMain">
+        {/* 顶部页面 */}
+        <div className="title">
+          <div className="titleL">
+            <h3>
+              欢迎 {userInfo.userName} 进入
+              <br />
+              乐山大佛博物馆
+              <br />
+              馆藏管理系统!
+            </h3>
+            <p>{nowTime}</p>
+          </div>
+          <div className="titleR">
+            {tabList.map((v) => (
+              <div className="row" key={v.id}>
+                <div className={`bac${v.id}`}></div>
+                <p>{v.name}</p>
+              </div>
+            ))}
+          </div>
+        </div>
+        {/* 下面数据 */}
+        <div className="flooBox">
+          <div className="flooBoxL">
+            <div className="flooTit">
+              <div>藏馆统计</div>
+              <Button>查看更多</Button>
+            </div>
+            {/* 图表 */}
+            <div className="chartBox">
+              <div className="chartTit">(件)</div>
+              <div className="chart"></div>
+            </div>
+          </div>
+          <div className="flooBoxR">
+            <div className="flooTit">
+              <div>代办提醒</div>
+            </div>
+            <div className="doneBox">
+              {doneList.map((v, i) => (
+                <div
+                  className={classNames("doneRow", i >= 4 ? "noneRow" : "")}
+                  key={v.id}
+                >
+                  <div className="doneRow_tit">{v.name}</div>
+                  <div className="doneRow_txt">
+                    共有&emsp;<span>{v.num}</span>&emsp;代办事项
+                  </div>
+                </div>
+              ))}
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+  );
+}

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

@@ -0,0 +1,132 @@
+.Layout {
+  width: 100%;
+  height: 100%;
+
+  :global {
+    .topTitle {
+      width: 100%;
+      height: 90px;
+      background-color: var(--themeColor);
+
+      .main {
+        width: 1580px;
+        height: 100%;
+        margin: 0 auto;
+        display: flex;
+        align-items: center;
+        justify-content: space-between;
+
+        &>div {
+          height: 100%;
+        }
+
+        .logo {
+          height: 58px;
+          width: 230px;
+          background-image: url('../../assets/img/login/LOGO.png');
+          background-size: 100% 100%;
+        }
+
+        .tabCut {
+          width: calc(100% - 800px);
+          display: flex;
+          align-items: center;
+          justify-content: space-around;
+
+          .row {
+            line-height: 90px;
+            cursor: pointer;
+            font-size: 18px;
+            color: #fff;
+            height: 100%;
+            &:hover{
+              color: #D3B453;
+            }
+          }
+
+          .active {
+            pointer-events: none;
+            color: #D3B453;
+            position: relative;
+
+            &::before {
+              content: '';
+              position: absolute;
+              bottom: 0;
+              left: 50%;
+              transform: translateX(-50%);
+              width: 56px;
+              height: 24px;
+              background-image: url('../../assets/img/login/flower.png');
+              background-size: 56px 24px;
+            }
+          }
+        }
+
+        .user {
+          cursor: pointer;
+          position: relative;
+          line-height: 90px;
+          padding-left: 60px;
+          background: url('../../assets/img/login/user_1.png') no-repeat left center;
+          background-size: 40px 40px;
+          font-size: 16px;
+          color: #fff;
+
+          .userSet {
+            width: 115px;
+            opacity: 0;
+            pointer-events: none;
+            transition: bottom .3s;
+            height: 100px;
+            background-color: var(--themeColor);
+            position: absolute;
+            left: 0;
+            bottom: -80px;
+            &>span{
+              display: block;
+              width: 100%;
+              height: 50%;
+              line-height: 50px;
+              text-align: center;
+              &:hover{
+                color: #D3B453;
+              }
+            }
+          }
+          &:hover{
+            .userSet{
+              opacity: 1;
+              pointer-events: auto;
+              bottom: -100px;
+            }
+          }
+        }
+      }
+    }
+    .pageMain{
+      width: 100%;
+      height: calc(100% - 90px);
+      background-image: url('../../assets/img/login/homeBg.jpg');
+      background-size: cover;
+      &>div{
+        width: 1600px;
+        margin: 0 auto;
+        padding-top: 35px;
+        height: calc(100% - 30px);
+        .leftBar{
+          float: left;
+          width: 220px;
+          height: 100%;
+          background-color: #fff;
+          border-radius: 5px;
+        }
+        .rightMain{
+          width: calc(100% - 250px);
+          height: 100%;
+          float: right;
+        }
+      }
+    }
+  }
+}

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

@@ -0,0 +1,178 @@
+import { getTokenInfo, removeTokenInfo } from "@/utils/storage";
+import styles from "./index.module.scss";
+import classNames from "classnames";
+import { useEffect, useMemo, useRef, useState } from "react";
+import { Popconfirm, Button, Modal, Form, Input, message } from "antd";
+import React from "react";
+import { Route, Switch, useLocation } from "react-router-dom";
+import history from "@/utils/history";
+import SpinLoding from "@/components/SpinLoding";
+import AuthRoute from "@/components/AuthRoute";
+import { Base64 } from "js-base64";
+import encodeStr from "@/utils/pass";
+import { passWordEditAPI } from "@/store/action/login";
+
+const Home = React.lazy(() => import("../Home"));
+const Object = React.lazy(() => import("../Object"));
+const Stores = React.lazy(() => import("../Stores"));
+const System = React.lazy(() => import("../System"));
+const NotFound = React.lazy(() => import("../../components/NotFound"));
+
+export default function Layout() {
+  const location = useLocation();
+  const [path, setPath] = useState("");
+
+  useEffect(() => {
+    setPath(location.pathname);
+  }, [location]);
+
+  const userInfo = useMemo(() => {
+    return getTokenInfo().user;
+  }, []);
+
+  const tabList = useMemo(() => {
+    return [
+      { id: 1, path: "/", name: "首页" },
+      { id: 2, path: "/object", name: "馆藏管理" },
+      { id: 3, path: "/stores", name: "库房管理" },
+      { id: 4, path: "/system", name: "系统管理" },
+    ];
+  }, []);
+
+  // 顶部路由跳转
+  const pathCutFu = (path: string) => {
+    history.push(path);
+  };
+
+  // 修改密码相关
+  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 message.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) {
+      message.success("修改成功!");
+      loginExit();
+    }
+  };
+
+  // 点击退出登录
+  const loginExit = () => {
+    removeTokenInfo();
+    history.push("/login");
+  };
+  return (
+    <div className={styles.Layout}>
+      <div className="topTitle">
+        <div className="main">
+          <div className="logo"></div>
+          <div className="tabCut">
+            {tabList.map((v) => (
+              <div
+                onClick={() => pathCutFu(v.path)}
+                className={classNames("row", v.path === path ? "active" : "")}
+                key={v.id}
+              >
+                {v.name}
+              </div>
+            ))}
+          </div>
+          <div className="user">
+            {userInfo.userName}
+            <div className="userSet">
+              <span onClick={() => setOpen(true)}>修改密码</span>
+              <Popconfirm
+                title="确定退出吗?"
+                okText="确定"
+                cancelText="取消"
+                onConfirm={loginExit}
+              >
+                退出登录
+              </Popconfirm>
+            </div>
+          </div>
+        </div>
+      </div>
+      {/* 二级路由页面 */}
+      <div className="pageMain">
+        <React.Suspense fallback={<SpinLoding />}>
+          <Switch>
+            <AuthRoute exact path="/" component={Home} />
+            <AuthRoute path="/object" component={Object} />
+            <AuthRoute path="/stores" component={Stores} />
+            <AuthRoute path="/system" component={System} />
+            <Route path="*" component={NotFound} />
+          </Switch>
+        </React.Suspense>
+      </div>
+      {/* 点击修改密码打开的对话框 */}
+      <Modal
+        destroyOnClose
+        open={open}
+        title="修改密码"
+        onCancel={() => setOpen(false)}
+        footer={
+          [] // 设置footer为空,去掉 取消 确定默认按钮
+        }
+      >
+        <Form
+          name="basic"
+          labelCol={{ span: 5 }}
+          wrapperCol={{ span: 16 }}
+          onFinish={onFinish}
+          autoComplete="off"
+        >
+          <Form.Item
+            label="旧密码"
+            name="oldPassword"
+            rules={[{ required: true, message: "不能为空!" }]}
+          >
+            <Input.Password maxLength={15} />
+          </Form.Item>
+
+          <Form.Item
+            label="新密码"
+            name="newPassword"
+            rules={[{ required: true, message: "不能为空!" }]}
+          >
+            <Input.Password
+              maxLength={15}
+              onChange={(e) => (oldPasswordValue.current = e.target.value)}
+            />
+          </Form.Item>
+
+          <Form.Item
+            label="确定新密码"
+            name="checkPass"
+            rules={[{ validator: checkPassWord }]}
+          >
+            <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>
+  );
+}

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

@@ -0,0 +1,68 @@
+.Login{
+  width: 100%;
+  height: 100%;
+  background-image: url('../../assets/img/login/bg.jpg');
+  background-size: cover;
+  position: relative;
+  :global{
+    .main{
+      padding-top: 20px;
+      position: absolute;
+      top: 50%;
+      transform: translateY(-50%);
+      width: 100%;
+      height: 380px;
+      background-color: rgba(255,255,255,.8);
+      backdrop-filter: blur(4px);
+      text-align: center;
+      &>h1{
+        font-size: 44px;
+        color: var(--themeColor);
+      }
+      &>p{
+        margin: 10px 0 40px;
+        font-size: 18px;
+      }
+      .inputBox{
+        width: 1000px;
+        margin: 0 auto;
+        display: flex;
+        justify-content: space-between;
+        .ant-input-prefix{
+          margin-right: 10px;
+          .anticon {
+            width: 24px;
+            height: 24px;
+            svg{
+              width: 100%;
+              height: 100%;
+            }
+          }
+        }
+        .ant-input{
+          font-size: 18px;
+          width: 45%;
+          height: 60px;
+          line-height: 60px;
+        }
+        .ant-input-affix-wrapper{
+          padding:0 11px;
+          width: 45%;
+          height: 60px;
+          .ant-input{
+            background-color: transparent;
+            width: 100%;
+            height: 60px;
+          }
+        }
+      }
+      .loginBtn{
+        margin-top: 50px;
+        .ant-btn{
+          width: 400px;
+          height: 60px;
+        }
+      }
+    }
+  }
+}

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

@@ -0,0 +1,71 @@
+import styles from "./index.module.scss";
+
+import { Input, Button, message } from "antd";
+import { UserOutlined, LockOutlined } from "@ant-design/icons";
+import { useState } from "react";
+import { Base64 } from "js-base64";
+import encodeStr from "@/utils/pass";
+import { userLoginAPI } from "@/store/action/login";
+import { setTokenInfo } from "@/utils/storage";
+import history from "@/utils/history";
+
+export default function Login() {
+  // 账号密码
+  const [userName, setUserName] = useState("");
+  const [passWord, setPassWord] = useState("");
+
+  // 键盘按下回车事件
+  const keyUpEntFu = (e: React.KeyboardEvent<HTMLInputElement>) => {
+    if (e.key === "Enter") loginClickFu();
+  };
+  // 点击登录
+  const loginClickFu = async () => {
+    // 非空判断
+    if (userName === "" || passWord === "") return message.warning("不能为空!");
+    const obj = {
+      userName,
+      passWord: encodeStr(Base64.encode(passWord)),
+    };
+    const res: any = await userLoginAPI(obj);
+    if (res.code === 0) {
+      message.success("登录成功");
+      setTokenInfo(res.data);
+      history.push("/");
+    }
+  };
+
+  return (
+    <div className={styles.Login}>
+      <div className="main">
+        <h1>乐山大佛博物馆馆藏管理系统</h1>
+        <p>LESHAN DAFO MUSEUMCOLLECTION MANAGEMENT SYSTEM</p>
+        {/* 账号密码输入框 */}
+        <div className="inputBox">
+          <Input
+            onKeyUp={(e) => keyUpEntFu(e)}
+            value={userName}
+            onChange={(e) => setUserName(e.target.value.trim())}
+            prefix={<UserOutlined />}
+            placeholder="请输入用户名称"
+            maxLength={15}
+          />
+          <Input.Password
+            onKeyUp={(e) => keyUpEntFu(e)}
+            value={passWord}
+            onChange={(e) => setPassWord(e.target.value.trim())}
+            prefix={<LockOutlined />}
+            placeholder="请输入登录密码"
+            maxLength={15}
+          />
+        </div>
+
+        {/* 登录按钮 */}
+        <div className="loginBtn">
+          <Button type="primary" size="large" onClick={loginClickFu}>
+            登 录
+          </Button>
+        </div>
+      </div>
+    </div>
+  );
+}

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

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

+ 43 - 0
src/pages/Object/index.tsx

@@ -0,0 +1,43 @@
+import styles from "./index.module.scss";
+import LeftBar from "@/components/LeftBar";
+import React from "react";
+import SpinLoding from "@/components/SpinLoding";
+import { Route, Switch } from "react-router-dom";
+import AuthRoute from "@/components/AuthRoute";
+import NotFound from "@/components/NotFound";
+const Object1 = React.lazy(() => import("../Object1"));
+const Object2 = React.lazy(() => import("../Object2"));
+
+const data = [
+  { id: 1, name: "藏品登记", path: "/object" },
+  { id: 2, name: "入库管理", path: "/object/2" },
+  { id: 3, name: "出库管理", path: "/object3" },
+  { id: 4, name: "藏品总账", path: "/object4" },
+  { id: 5, name: "藏品盘核", path: "/object5" },
+  { id: 6, name: "藏品注销", path: "/object6" },
+];
+
+export default function Object() {
+  return (
+    <div className={styles.Object}>
+      <div className="leftBar">
+        <LeftBar data={data} />
+      </div>
+      {/* 三级路由页面 */}
+      <div className="rightMain">
+        <React.Suspense fallback={<SpinLoding />}>
+          <Switch>
+            <AuthRoute path="/object/2" component={Object2} myInd={{ id: 2 }} />
+            <AuthRoute
+              exact
+              path="/object"
+              component={Object1}
+              myInd={{ id: 1 }}
+            />
+            <Route path="*" component={NotFound} />
+          </Switch>
+        </React.Suspense>
+      </div>
+    </div>
+  );
+}

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

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

+ 9 - 0
src/pages/Object1/index.tsx

@@ -0,0 +1,9 @@
+import styles from "./index.module.scss";
+export default function Object1() {
+  
+  return (
+    <div className={styles.Object1}>
+      <h1>Object1</h1>
+    </div>
+  )
+}

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

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

+ 9 - 0
src/pages/Object2/index.tsx

@@ -0,0 +1,9 @@
+import styles from "./index.module.scss";
+export default function Object2() {
+  
+  return (
+    <div className={styles.Object2}>
+      <h1>Object2</h1>
+    </div>
+  )
+}

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

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

+ 9 - 0
src/pages/Object3/index.tsx

@@ -0,0 +1,9 @@
+import styles from "./index.module.scss";
+export default function Object3() {
+  
+  return (
+    <div className={styles.Object3}>
+      <h1>Object3</h1>
+    </div>
+  )
+}

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

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

+ 9 - 0
src/pages/Object4/index.tsx

@@ -0,0 +1,9 @@
+import styles from "./index.module.scss";
+export default function Object4() {
+  
+  return (
+    <div className={styles.Object4}>
+      <h1>Object4</h1>
+    </div>
+  )
+}

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

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

+ 9 - 0
src/pages/Object5/index.tsx

@@ -0,0 +1,9 @@
+import styles from "./index.module.scss";
+export default function Object5() {
+  
+  return (
+    <div className={styles.Object5}>
+      <h1>Object5</h1>
+    </div>
+  )
+}

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

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

+ 9 - 0
src/pages/Object6/index.tsx

@@ -0,0 +1,9 @@
+import styles from "./index.module.scss";
+export default function Object6() {
+  
+  return (
+    <div className={styles.Object6}>
+      <h1>Object6</h1>
+    </div>
+  )
+}

+ 6 - 0
src/pages/Stores/index.module.scss

@@ -0,0 +1,6 @@
+.Stores{
+  background-color: rebeccapurple;
+  :global{
+    
+  }
+}

+ 9 - 0
src/pages/Stores/index.tsx

@@ -0,0 +1,9 @@
+import styles from "./index.module.scss";
+export default function Stores() {
+  
+  return (
+    <div className={styles.Stores}>
+      <h1>Stores</h1>
+    </div>
+  )
+}

+ 6 - 0
src/pages/System/index.module.scss

@@ -0,0 +1,6 @@
+.System{
+  background-color: rebeccapurple;
+  :global{
+    
+  }
+}

+ 9 - 0
src/pages/System/index.tsx

@@ -0,0 +1,9 @@
+import styles from "./index.module.scss";
+export default function System() {
+  
+  return (
+    <div className={styles.System}>
+      <h1>System</h1>
+    </div>
+  )
+}

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

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

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

@@ -0,0 +1,9 @@
+import styles from "./index.module.scss";
+export default function Layout() {
+  
+  return (
+    <div className={styles.Layout}>
+      <h1>123</h1>
+    </div>
+  )
+}

+ 26 - 0
src/store/action/login.ts

@@ -0,0 +1,26 @@
+// import store, { AppDispatch } from ".."
+import http from "@/utils/http";
+
+/**
+ * 用户登录接口
+ */
+export const userLoginAPI = (data:any) => {
+  return http.post("admin/login", { ...data });
+};
+
+/**
+ * 修改密码接口
+ */
+export const passWordEditAPI = (data:any) => {
+  return http.post("sys/user/updatePwd", { ...data });
+};
+
+// export const addNunAction = () => {
+
+//   return async (dispatch: AppDispatch) => {
+//     // const res = await http.get(`http://geek.itheima.net/v1_0/sms/codes/${18702025090}`)
+//     // console.log('----', res);
+//     const { num } = store.getState().loginStore
+//     dispatch({ type: 'login/addNum', payload: num + 1 })
+//   }
+// }

+ 15 - 0
src/store/index.ts

@@ -0,0 +1,15 @@
+import { applyMiddleware, legacy_createStore as createStore } from 'redux'
+import rootReducer from './reducer'
+import { composeWithDevTools } from 'redux-devtools-extension'
+import thunk from 'redux-thunk'
+
+// 创建仓库实例
+const store = createStore(rootReducer, composeWithDevTools(applyMiddleware(thunk)))
+
+
+export type RootState = ReturnType<typeof store.getState>
+
+
+export type AppDispatch = typeof store.dispatch
+// 导出仓库实例
+export default store

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

@@ -0,0 +1,9 @@
+import { combineReducers } from 'redux'
+import loginReducer from './login'
+
+// 合并 reducer
+const rootReducer = combineReducers({
+  loginStore: loginReducer,
+})
+
+export default rootReducer

+ 22 - 0
src/store/reducer/login.ts

@@ -0,0 +1,22 @@
+import { LoginType } from "@/types"
+
+
+
+// 初始化状态应用注解
+const initState: LoginType = {
+  num: 0,
+  active: 0,
+}
+
+type LoginActionType =
+  { type: 'login/addNum', payload: number }
+
+// 频道 reducer
+export default function loginReducer(state = initState, action: LoginActionType) {
+  switch (action.type) {
+    case 'login/addNum':
+      return { ...state, num: action.payload }
+    default:
+      return state
+  }
+}

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

@@ -0,0 +1,3 @@
+declare module 'history'
+declare module '*.scss';
+declare module '*.png';

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

@@ -0,0 +1,2 @@
+export * from './store/login'
+

+ 4 - 0
src/types/store/login.d.ts

@@ -0,0 +1,4 @@
+export type LoginType = {
+  num: number;
+  active: number;
+}

+ 3 - 0
src/utils/history.ts

@@ -0,0 +1,3 @@
+import { createHashHistory  } from 'history'
+const history = createHashHistory()
+export default history

+ 69 - 0
src/utils/http.ts

@@ -0,0 +1,69 @@
+import axios from "axios";
+import history from "./history";
+import { getTokenInfo, removeTokenInfo } from "./storage";
+import { message } from "antd";
+// 请求基地址
+export const baseURL =
+  process.env.NODE_ENV === "development" ? "https://hnbwg.4dage.com" : "";
+
+// 创建 axios 实例
+const http = axios.create({
+  baseURL: baseURL + "/api/",
+  timeout: 5000,
+});
+
+let axajInd = 0;
+const lodingDom: any = document.querySelector("#AsyncSpinLoding");
+
+// 请求拦截器
+http.interceptors.request.use(
+  function (config: any) {
+    // 发请求前打开加载提示
+    lodingDom.style.display = "block";
+    axajInd++;
+
+    const { token } = getTokenInfo();
+    if (token) config.headers.token = token;
+    return config;
+  },
+  function (err) {
+    return Promise.reject(err);
+  }
+);
+
+// 响应拦截器
+http.interceptors.response.use(
+  function (response) {
+    // 请求回来的关闭加载提示
+    axajInd--;
+    if (axajInd === 0) lodingDom.style.display = "none";
+
+    if (response.data.code === 5001 || response.data.code === 5002) {
+      removeTokenInfo();
+      message.warning("登录失效!");
+      history.push("/login");
+    } else if (response.data.code === 0) {
+      // message.success(response.data.msg);
+    } else message.warning(response.data.msg);
+    return response.data;
+  },
+  async function (err) {
+    // 如果因为网络原因,response没有,给提示消息
+    if (!err.response) {
+      message.warning("网络繁忙,请稍后重试");
+    }
+    // else {
+    //   // 网络没问题,后台返回了有数据
+
+    //   // token过期
+    //   if (err.response.status === 401) {
+    //     history.push("/Login");
+    //   }
+    // }
+
+    return Promise.reject(err);
+  }
+);
+
+// 导出 axios 实例
+export default http;

+ 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;

+ 35 - 0
src/utils/storage.ts

@@ -0,0 +1,35 @@
+// ------------------------------------token的本地存储------------------------------------
+
+// 用户信息的本地缓存键名(包括token)
+const USER_KEY = "LSDFGC_USER_INFO";
+
+/**
+ * 从本地缓存中获取用户信息
+ */
+export const getTokenInfo = (): any => {
+  return JSON.parse(localStorage.getItem(USER_KEY) || "{}");
+};
+
+/**
+ * 将用户信息存入缓存
+ * @param {Object} tokenInfo 从后端获取到的 Token 信息
+ */
+export const setTokenInfo = (tokenInfo: any): void => {
+  localStorage.setItem(USER_KEY, JSON.stringify(tokenInfo));
+};
+
+/**
+ * 删除本地缓存中的用户信息
+ */
+export const removeTokenInfo = (): void => {
+  localStorage.removeItem(USER_KEY);
+};
+
+/**
+ * 判断本地缓存中是否存在 Token 信息
+ */
+export const hasToken = (): boolean => {
+  return Boolean(getTokenInfo().token);
+};
+
+

+ 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"
+  ]
+}