shaogen1995 2 年之前
當前提交
705ef1994f
共有 100 個文件被更改,包括 61478 次插入0 次删除
  1. 23 0
      后台管理/.gitignore
  2. 46 0
      后台管理/README.md
  3. 10 0
      后台管理/config-overrides.js
  4. 30140 0
      后台管理/package-lock.json
  5. 62 0
      后台管理/package.json
  6. 8 0
      后台管理/path.tsconfig.json
  7. 二進制
      后台管理/public/favicon.ico
  8. 43 0
      后台管理/public/index.html
  9. 62 0
      后台管理/src/App.tsx
  10. 二進制
      后台管理/src/assets/img/IMGerror.png
  11. 二進制
      后台管理/src/assets/img/bg.png
  12. 二進制
      后台管理/src/assets/img/inco1.png
  13. 二進制
      后台管理/src/assets/img/inco2.png
  14. 二進制
      后台管理/src/assets/img/inco3.png
  15. 二進制
      后台管理/src/assets/img/inco4.png
  16. 二進制
      后台管理/src/assets/img/inco5.png
  17. 二進制
      后台管理/src/assets/img/loading.gif
  18. 二進制
      后台管理/src/assets/img/user.png
  19. 180 0
      后台管理/src/assets/styles/base.css
  20. 242 0
      后台管理/src/assets/styles/base.less
  21. 21 0
      后台管理/src/components/AsyncSpinLoding/index.module.scss
  22. 15 0
      后台管理/src/components/AsyncSpinLoding/index.tsx
  23. 32 0
      后台管理/src/components/AuthRoute/index.tsx
  24. 51 0
      后台管理/src/components/ImageLazy/index.module.scss
  25. 68 0
      后台管理/src/components/ImageLazy/index.tsx
  26. 29 0
      后台管理/src/components/Message/index.tsx
  27. 26 0
      后台管理/src/components/NotFound/index.tsx
  28. 10 0
      后台管理/src/components/SpinLoding/index.module.scss
  29. 13 0
      后台管理/src/components/SpinLoding/index.tsx
  30. 43 0
      后台管理/src/components/UpAsyncLoding/index.module.scss
  31. 38 0
      后台管理/src/components/UpAsyncLoding/index.tsx
  32. 84 0
      后台管理/src/components/Z_upFileOne/index.module.scss
  33. 165 0
      后台管理/src/components/Z_upFileOne/index.tsx
  34. 41 0
      后台管理/src/index.tsx
  35. 5 0
      后台管理/src/pages/A1Order/index.module.scss
  36. 14 0
      后台管理/src/pages/A1Order/index.tsx
  37. 19 0
      后台管理/src/pages/A4User/UserAdd/index.module.scss
  38. 144 0
      后台管理/src/pages/A4User/UserAdd/index.tsx
  39. 32 0
      后台管理/src/pages/A4User/index.module.scss
  40. 340 0
      后台管理/src/pages/A4User/index.tsx
  41. 32 0
      后台管理/src/pages/A5Log/index.module.scss
  42. 137 0
      后台管理/src/pages/A5Log/index.tsx
  43. 176 0
      后台管理/src/pages/Layout/index.module.scss
  44. 246 0
      后台管理/src/pages/Layout/index.tsx
  45. 131 0
      后台管理/src/pages/Login/index.module.scss
  46. 78 0
      后台管理/src/pages/Login/index.tsx
  47. 5 0
      后台管理/src/pages/初始化组件/index.module.scss
  48. 14 0
      后台管理/src/pages/初始化组件/index.tsx
  49. 54 0
      后台管理/src/store/action/A4User.ts
  50. 17 0
      后台管理/src/store/action/A5Log.ts
  51. 42 0
      后台管理/src/store/action/layout.ts
  52. 20 0
      后台管理/src/store/index.ts
  53. 28 0
      后台管理/src/store/reducer/A4User.ts
  54. 27 0
      后台管理/src/store/reducer/A5Log.ts
  55. 17 0
      后台管理/src/store/reducer/index.ts
  56. 65 0
      后台管理/src/store/reducer/layout.ts
  57. 35 0
      后台管理/src/types/api/A4User.d.ts
  58. 11 0
      后台管理/src/types/api/A5Log.d.ts
  59. 10 0
      后台管理/src/types/api/layot.d.ts
  60. 7 0
      后台管理/src/types/declaration.d.ts
  61. 3 0
      后台管理/src/types/index.d.ts
  62. 35 0
      后台管理/src/utils/domShow.ts
  63. 17 0
      后台管理/src/utils/history.ts
  64. 96 0
      后台管理/src/utils/http.ts
  65. 50 0
      后台管理/src/utils/message.ts
  66. 100 0
      后台管理/src/utils/pass.ts
  67. 34 0
      后台管理/src/utils/storage.ts
  68. 27 0
      后台管理/tsconfig.json
  69. 23 0
      知识图谱demo/.gitignore
  70. 19 0
      知识图谱demo/README.md
  71. 5 0
      知识图谱demo/babel.config.js
  72. 26772 0
      知识图谱demo/package-lock.json
  73. 28 0
      知识图谱demo/package.json
  74. 47 0
      知识图谱demo/public/data1.json
  75. 52 0
      知识图谱demo/public/data2.json
  76. 56 0
      知识图谱demo/public/data3.json
  77. 二進制
      知识图谱demo/public/dataImg/1.jpg
  78. 二進制
      知识图谱demo/public/dataImg/2.jpg
  79. 二進制
      知识图谱demo/public/dataImg/3.jpg
  80. 二進制
      知识图谱demo/public/dataImg/4.jpg
  81. 二進制
      知识图谱demo/public/favicon.ico
  82. 17 0
      知识图谱demo/public/index.html
  83. 49 0
      知识图谱demo/public/search.json
  84. 36 0
      知识图谱demo/src/App.vue
  85. 5 0
      知识图谱demo/src/assets/base.css
  86. 5 0
      知识图谱demo/src/assets/base.less
  87. 1 0
      知识图谱demo/src/assets/img/close.svg
  88. 1 0
      知识图谱demo/src/assets/img/location.svg
  89. 1 0
      知识图谱demo/src/assets/img/right.svg
  90. 1 0
      知识图谱demo/src/assets/img/scene.svg
  91. 1 0
      知识图谱demo/src/assets/img/search.svg
  92. 1 0
      知识图谱demo/src/assets/img/time.svg
  93. 129 0
      知识图谱demo/src/components/Main.vue
  94. 320 0
      知识图谱demo/src/components/Search.vue
  95. 17 0
      知识图谱demo/src/main.js
  96. 29 0
      知识图谱demo/src/router/index.js
  97. 83 0
      知识图谱demo/src/views/Home.vue
  98. 176 0
      知识图谱demo/src/views/Three.vue
  99. 84 0
      知识图谱demo/src/views/Tow.vue
  100. 0 0
      知识图谱demo/vue.config.js

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

文件差異過大導致無法顯示
+ 30140 - 0
后台管理/package-lock.json


+ 62 - 0
后台管理/package.json

@@ -0,0 +1,62 @@
+{
+  "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.0.4",
+    "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",
+    "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/*"]
+      }
+    }
+  }

二進制
后台管理/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>

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

@@ -0,0 +1,62 @@
+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";
+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 />
+
+
+      {/* antd 轻提示 ---兼容360浏览器 */}
+      <MessageCom />
+    </>
+  );
+}

二進制
后台管理/src/assets/img/IMGerror.png


二進制
后台管理/src/assets/img/bg.png


二進制
后台管理/src/assets/img/inco1.png


二進制
后台管理/src/assets/img/inco2.png


二進制
后台管理/src/assets/img/inco3.png


二進制
后台管理/src/assets/img/inco4.png


二進制
后台管理/src/assets/img/inco5.png


二進制
后台管理/src/assets/img/loading.gif


二進制
后台管理/src/assets/img/user.png


+ 180 - 0
后台管理/src/assets/styles/base.css

@@ -0,0 +1,180 @@
+* {
+  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: #35527D;
+}
+/* 找不到页面 */
+.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 .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;
+}
+[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;
+}

+ 242 - 0
后台管理/src/assets/styles/base.less

@@ -0,0 +1,242 @@
+* {
+  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: #35527D;
+}
+
+
+
+
+
+/* 找不到页面 */
+.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;
+
+  // 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;
+  }
+}
+
+
+
+[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 />
+            &nbsp;
+            <div>预览</div>
+          </div>
+        )}
+      </div>
+    </div>
+  );
+}
+
+const MemoImageLazy = React.memo(ImageLazy);
+
+export default MemoImageLazy;

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

+ 84 - 0
后台管理/src/components/Z_upFileOne/index.module.scss

@@ -0,0 +1,84 @@
+.Z_upFileOne {
+  width: 100%;
+  height: 100%;
+  position: relative;
+
+  :global {
+
+    .file_upIcon {
+      color: #a6a6a6;
+      border-radius: 3px;
+      cursor: pointer;
+      font-size: 30px;
+      width: 100px;
+      height: 100px;
+      border: 1px dashed #797979;
+      display: flex;
+      justify-content: center;
+      align-items: center;
+
+
+    }
+
+    .file_img {
+      width: 100px;
+      height: 126px;
+      position: relative;
+
+      .file_closeBox {
+        position: absolute;
+        right: -10px;
+        top: -10px;
+        z-index: 99;
+        background-color: rgba(0, 0, 0, 0.8);
+        width: 20px;
+        height: 20px;
+        border-radius: 50%;
+        font-size: 16px;
+        color: #fff;
+        display: flex;
+        justify-content: center;
+        align-items: center;
+      }
+
+
+
+      .file_lookBox {
+        width: 100%;
+        background-color: rgba(0, 0, 0, .6);
+        color: #fff;
+        display: flex;
+        justify-content: space-around;
+
+        &>a {
+          color: #fff;
+        }
+
+        font-size: 16px;
+      }
+    }
+
+    .fileBoxRow_r_tit {
+      height: 46px;
+      margin-top: 5px;
+      font-size: 14px;
+      color: rgb(126, 124, 124);
+
+
+    }
+
+    .noUpThumb {
+      position: relative;
+      overflow: hidden;
+      opacity: 0;
+      transition: top .2s;
+      color: #ff4d4f;
+      top: -10px;
+    }
+
+    .noUpThumbAc {
+      top: 0;
+      opacity: 1;
+    }
+  }
+}

+ 165 - 0
后台管理/src/components/Z_upFileOne/index.tsx

@@ -0,0 +1,165 @@
+import React, { useCallback, useRef } from "react";
+import styles from "./index.module.scss";
+import ImageLazy from "@/components/ImageLazy";
+import {
+  PlusOutlined,
+  EyeOutlined,
+  CloseOutlined,
+  DownloadOutlined,
+} from "@ant-design/icons";
+import store from "@/store";
+import { baseURL } from "@/utils/http";
+import classNames from "classnames";
+import { Popconfirm } from "antd";
+import { MessageFu } from "@/utils/message";
+import { fileDomInitialFu } from "@/utils/domShow";
+import { API_upFile } from "@/store/action/layout";
+
+type Props = {
+  cover: string; //封面图
+  setCover: (val: string) => void; //设置封面图
+  isLook: boolean; //是不是查看
+  coverCheck: boolean; //有没有点击过确定
+  size: number; //上传图片大小(M)
+  dirCode: string; //文件的code码
+  myUrl: string;
+  format?: string[]; //上传图片格式
+  formatTxt?: string; //上传图片提示
+  sizeTxt?: string; //后面的建议尺寸信息
+  fromData?: any;
+  checkTxt?: string;
+};
+
+function Z_upFileOne({
+  cover,
+  setCover,
+  isLook,
+  coverCheck,
+  size,
+  dirCode,
+  myUrl,
+  format = ["image/jpeg", "image/png"],
+  formatTxt = "png、jpg和jpeg",
+  checkTxt = "请上传封面图!",
+  sizeTxt,
+  fromData,
+}: Props) {
+  const myInput = useRef<HTMLInputElement>(null);
+
+  // 上传封面图
+  const handeUpPhoto = useCallback(
+    async (e: React.ChangeEvent<HTMLInputElement>) => {
+      if (e.target.files) {
+        // 拿到files信息
+        const filesInfo = e.target.files[0];
+        // 校验格式
+        const type = format;
+        if (!type.includes(filesInfo.type)) {
+          e.target.value = "";
+          return MessageFu.warning(`只支持${formatTxt}格式!`);
+        }
+        // 校验大小
+        if (filesInfo.size > size * 1024 * 1024) {
+          e.target.value = "";
+          return MessageFu.warning(`最大支持${size}M!`);
+        }
+        // 创建FormData对象
+        const fd = new FormData();
+        // 把files添加进FormData对象(‘photo’为后端需要的字段)
+        fd.append("type", "thumb");
+        fd.append("dirCode", dirCode);
+        fd.append("file", filesInfo);
+
+        if (fromData) {
+          for (const k in fromData) {
+            if (fromData[k]) fd.append(k, fromData[k]);
+          }
+        }
+
+        e.target.value = "";
+
+        try {
+          const res = await API_upFile(fd, myUrl);
+          if (res.code === 0) {
+            MessageFu.success("上传成功!");
+            setCover(res.data.filePath);
+          }
+          fileDomInitialFu();
+        } catch (error) {
+          fileDomInitialFu();
+        }
+      }
+    },
+    [dirCode, format, formatTxt, fromData, myUrl, setCover, size]
+  );
+
+  return (
+    <div className={styles.Z_upFileOne}>
+      <input
+        id="upInput"
+        type="file"
+        accept=".png,.jpg,.jpeg"
+        ref={myInput}
+        onChange={(e) => handeUpPhoto(e)}
+      />
+
+      <div
+        hidden={cover !== ""}
+        className="file_upIcon"
+        onClick={() => myInput.current?.click()}
+      >
+        <PlusOutlined />
+      </div>
+
+      <div className="file_img" hidden={cover === ""}>
+        {cover ? (
+          <ImageLazy width={100} height={100} src={cover} noLook />
+        ) : null}
+
+        {/* 删除 */}
+        <div className="file_closeBox" hidden={isLook}>
+          <Popconfirm
+            title="删除后无法恢复,是否删除?"
+            okText="删除"
+            cancelText="取消"
+            onConfirm={() => setCover("")}
+          >
+            <CloseOutlined />
+          </Popconfirm>
+        </div>
+
+        {/* 预览 下载 */}
+        <div className="file_lookBox">
+          <EyeOutlined
+            onClick={() =>
+              store.dispatch({
+                type: "layout/lookBigImg",
+                payload: { url: baseURL + cover, show: true },
+              })
+            }
+          />
+          <a href={baseURL + cover} download target="_blank" rel="noreferrer">
+            <DownloadOutlined />
+          </a>
+        </div>
+      </div>
+      <div className="fileBoxRow_r_tit" hidden={isLook}>
+        支持{formatTxt}的图片格式;最大支持{size}M。
+        {sizeTxt ? `建议尺寸${sizeTxt}` : null}
+        <br />
+        <div
+          className={classNames(
+            "noUpThumb",
+            !cover && coverCheck ? "noUpThumbAc" : ""
+          )}
+        >
+          {checkTxt}
+        </div>
+      </div>
+    </div>
+  );
+}
+
+const MemoZ_upFileOne = React.memo(Z_upFileOne);
+
+export default MemoZ_upFileOne;

+ 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: "#35527D",
+      },
+    }}
+  >
+    <Provider store={store}>
+      <StyleProvider
+        hashPriority="high"
+        transformers={[legacyLogicalPropertiesTransformer]}
+      >
+        <App />
+      </StyleProvider>
+    </Provider>
+  </ConfigProvider>
+);

+ 5 - 0
后台管理/src/pages/A1Order/index.module.scss

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

+ 14 - 0
后台管理/src/pages/A1Order/index.tsx

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

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

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

+ 144 - 0
后台管理/src/pages/A4User/UserAdd/index.tsx

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

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

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

+ 340 - 0
后台管理/src/pages/A4User/index.tsx

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

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

@@ -0,0 +1,32 @@
+.D2Log {
+  :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% - 70px);
+      background-color: #fff;
+
+      .ant-table-body {
+        height: 630px;
+        overflow-y: auto !important;
+        overflow-y: overlay !important;
+
+      }
+    }
+  }
+}

+ 137 - 0
后台管理/src/pages/A5Log/index.tsx

@@ -0,0 +1,137 @@
+import { RootState } from "@/store";
+import { getLogListAPI } from "@/store/action//A5Log";
+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 A5Log() {
+  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.A5Log.tableInfo);
+
+  const columns = useMemo(() => {
+    return [
+      {
+        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.A5Log}>
+      <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 MemoA5Log = React.memo(A5Log);
+
+export default MemoA5Log;

+ 176 - 0
后台管理/src/pages/Layout/index.module.scss

@@ -0,0 +1,176 @@
+.Layout {
+  width: 100%;
+  height: 100%;
+  display: flex;
+
+  :global {
+
+    .layoutLeft {
+      position: relative;
+      z-index: 30;
+      width: 220px;
+      height: 100%;
+      background-color: #131c28;
+
+
+      .layoutLeftTop {
+        height: 60px;
+        color: #fff;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+
+        &>h3 {
+          margin: 0 15px;
+          text-align: center;
+        }
+      }
+
+      .layoutLeftMain {
+        height: calc(100% - 60px);
+        padding: 20px 20px 20px 50px;
+
+        .layoutLRowBox {
+          font-size: 18px;
+          font-weight: 700;
+          margin-bottom: 20px;
+          color: #ccc;
+
+          .layoutLRow {
+            cursor: pointer;
+            padding-left: 38px;
+            font-size: 16px;
+            height: 40px;
+            line-height: 40px;
+            opacity: .4;
+            color: #fff;
+
+            &:first-child {
+              margin-top: 8px;
+            }
+          }
+
+          .active {
+            pointer-events: none;
+            opacity: 1;
+          }
+        }
+
+
+
+      }
+
+
+    }
+
+    .layoutRight {
+      width: calc(100% - 220px);
+      height: 100%;
+
+
+      .layoutRightTop {
+        height: 60px;
+        display: flex;
+        justify-content: flex-end;
+        position: relative;
+        z-index: 100;
+
+        .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 {
+            border-radius: 10px;
+            overflow: hidden;
+            width: 140px;
+            opacity: 0;
+            pointer-events: none;
+            transition: bottom .3s;
+            height: 120px;
+            position: absolute;
+            left: 50%;
+            transform: translateX(-50%);
+            bottom: -80px;
+            padding-top: 20px;
+            color: rgb(226, 223, 223);
+
+            &>span {
+              background-color: var(--themeColor);
+              display: block;
+              width: 100%;
+              height: 50px;
+              line-height: 50px;
+              text-align: center;
+
+              &:first-child {
+                border-radius: 10px 10px 0 0;
+              }
+
+              &:hover {
+                color: #fff;
+              }
+            }
+          }
+
+          &:hover {
+            .userSet {
+              opacity: 1;
+              pointer-events: auto;
+              bottom: -110px;
+            }
+
+            .userInco1 {
+              display: block;
+            }
+
+            .userInco2 {
+              display: none;
+            }
+
+
+          }
+        }
+      }
+
+      .layoutRightMain {
+        height: calc(100% - 60px);
+        padding: 15px 30px 20px;
+        background-color: #ecedf1;
+
+        .mainBoxR {
+          width: 100%;
+          height: 100%;
+          // overflow: hidden;
+          position: relative;
+
+          &>div {
+            width: 100%;
+            height: 100%;
+          }
+        }
+      }
+
+    }
+
+
+
+  }
+}

+ 246 - 0
后台管理/src/pages/Layout/index.tsx

@@ -0,0 +1,246 @@
+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 { RouterType } from "@/types";
+
+const NotFound = React.lazy(() => import("@/components/NotFound"));
+
+function Layout() {
+  const list = useMemo(() => {
+    const arr: RouterType = [
+      {
+        id: 1,
+        name: "预约管理",
+        path: "/",
+        Com: React.lazy(() => import("../A1Order")),
+      },
+    ];
+    return arr;
+  }, []);
+
+  useEffect(() => {
+    // 如果是超级管理员
+    const userInfo = getTokenInfo().user;
+    if (userInfo.isAdmin === 1) {
+      list.push(
+        {
+          id: 4.1,
+          name: "用户管理",
+          path: "/user",
+          Com: React.lazy(() => import("../A4User")),
+        },
+        {
+          id: 4.2,
+          name: "操作日志",
+          path: "/log",
+          Com: React.lazy(() => import("../A5Log")),
+        }
+      );
+    }
+  }, [list]);
+
+  // 点击跳转
+  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");
+  };
+
+  return (
+    <div className={styles.Layout}>
+      {/* 左边 */}
+      <div className="layoutLeft">
+        <div className="layoutLeftTop">
+          <h3>
+            北京珐琅厂景泰蓝博物馆
+            <br />
+            数字化改造
+          </h3>
+        </div>
+        {/* 左边主体 */}
+        <div className="layoutLeftMain">
+          {list.map((v) => (
+            <div
+              className={classNames(
+                "layoutLRowBox",
+                path === v.path ? "active" : ""
+              )}
+              key={v.id}
+              onClick={() => pathCutFu(v.path)}
+            >
+              {v.name}
+            </div>
+          ))}
+        </div>
+      </div>
+      {/* 右边 */}
+      <div className="layoutRight">
+        <div className="layoutRightTop">
+          {/* 用户相关 */}
+          <div className="user">
+            {userInfo.realName}
+            <div className="userInco userInco1">
+              <CaretUpOutlined />
+            </div>
+            <div className="userInco userInco2">
+              <CaretDownOutlined />
+            </div>
+            <div className="userSet">
+              <span onClick={() => setOpen(true)}>修改密码</span>
+              <Popconfirm
+                placement="bottom"
+                title="确定退出吗?"
+                okText="确定"
+                cancelText="取消"
+                onConfirm={loginExit}
+              >
+                退出登录
+              </Popconfirm>
+            </div>
+          </div>
+        </div>
+        {/* 右边主体 */}
+        <div className="layoutRightMain">
+          {/* 二级路由页面 */}
+          <div className="mainBoxR">
+            <React.Suspense fallback={<SpinLoding />}>
+              <Switch>
+                {list.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: "不能为空!" }]}
+          >
+            <Input.Password maxLength={15} />
+          </Form.Item>
+
+          <Form.Item
+            label="新密码"
+            name="newPassword"
+            rules={[
+              { required: true, message: "不能为空!" },
+              { min: 6, max: 15, message: "密码长度为6-15个字符!" },
+            ]}
+          >
+            <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>
+  );
+}
+
+// 使用 React.memo 来优化组件,避免组件的无效更新,类似 类组件里面的PureComponent
+const MemoLayout = React.memo(Layout);
+export default MemoLayout;

+ 131 - 0
后台管理/src/pages/Login/index.module.scss

@@ -0,0 +1,131 @@
+.Login {
+  width: 100%;
+  height: 100%;
+  background-image: url('../../assets/img/bg.png');
+  background-size: cover;
+  position: relative;
+
+  &::before {
+    content: '';
+    position: absolute;
+    top: 0;
+    left: 0;
+    width: 100%;
+    height: 100%;
+    background-color: rgba(0, 0, 0, .4);
+  }
+
+  :global {
+
+    .main {
+      border-radius: 6px;
+      position: absolute;
+      top: 50%;
+      left: 50%;
+      transform: translate(-50%,-50%);
+      width: 600px;
+      padding: 60px;
+      text-align: center;
+      background-color: rgba(238, 244, 248, 0.70);
+
+
+      .mainTitle{
+        padding-left: 50px;
+        font-size: 24px;
+        font-weight: 700;
+        color: #131C28;
+        text-align: left;
+        position: relative;
+        margin-bottom: 20px;
+        &::before{
+          content: '';
+          position: absolute;
+          top: 0;
+          left: 0;
+          width: 6px;
+          height: 100%;
+          background-color: #131C28;
+        }
+      }
+
+      .inputBox {
+        width: 100%;
+        padding:  0 40px;
+        .inputBoxRow {
+          width: 100%;
+          margin:0 auto 40px;
+
+          .ant-input-suffix .ant-input-password-icon {
+            color: #131C28;
+            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: 60px;
+          line-height: 60px;
+          background-clip: content-box;
+        }
+
+        input:-webkit-autofill {
+          font-size: 18px !important;
+          -webkit-text-fill-color: #131C28 !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 {
+          background-color: transparent;
+          padding: 0 11px;
+          width: 100%;
+          height: 60px;
+          border: none;
+          border-bottom: 1px solid #131C28;
+          border-radius: 0;
+          color: #131C28;
+
+          .ant-input {
+            background-color: transparent;
+            width: 100%;
+            height: 60px;
+          }
+        }
+
+        .ant-input-affix-wrapper-focused {
+          box-shadow: none
+        }
+      }
+
+      .loginBtn {
+        margin-top: 50px;
+        padding: 0 40px;
+        .ant-btn {
+          font-size: 24px;
+          width: 100%;
+          height: 50px;
+        }
+      }
+
+    }
+  }
+}

+ 78 - 0
后台管理/src/pages/Login/index.tsx

@@ -0,0 +1,78 @@
+import styles from "./index.module.scss";
+
+import { Input, Button } from "antd";
+import { UserOutlined, LockOutlined } from "@ant-design/icons";
+import { 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";
+
+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 === "") 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("登录成功");
+      // 用户信息存到本地
+      setTokenInfo(res.data);
+      history.push("/");
+    } else if (res.code === 3014)
+      MessageFu.warning("用户名不存在或密码错误,请联系管理员!");
+  };
+
+  return (
+    <div className={styles.Login}>
+      <div className="main">
+        <div className="mainTitle">登 录</div>
+        {/* 账号密码输入框 */}
+        <div className="inputBox">
+          <div className="inputBoxRow">
+            <Input
+              onKeyUp={(e) => keyUpEntFu(e)}
+              value={userName}
+              onChange={(e) => setUserName(e.target.value.trim())}
+              prefix={<UserOutlined />}
+              placeholder="请输入用户名"
+              maxLength={15}
+            />
+          </div>
+          <div className="inputBoxRow">
+            <Input.Password
+              onKeyUp={(e) => keyUpEntFu(e)}
+              value={passWord}
+              onChange={(e) => setPassWord(e.target.value.trim())}
+              prefix={<LockOutlined />}
+              placeholder="请输入用户密码"
+              maxLength={15}
+            />
+          </div>
+        </div>
+
+        {/* 登录按钮 */}
+        <div className="loginBtn">
+          <Button type="primary" size="large" onClick={loginClickFu}>
+            登 录
+          </Button>
+        </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;

+ 54 - 0
后台管理/src/store/action/A4User.ts

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

+ 17 - 0
后台管理/src/store/action/A5Log.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

+ 28 - 0
后台管理/src/store/reducer/A4User.ts

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

+ 27 - 0
后台管理/src/store/reducer/A5Log.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;
+  }
+}

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

@@ -0,0 +1,17 @@
+// 导入合并reducer的依赖
+import { combineReducers } from "redux";
+
+// 导入 登录 模块的 reducer
+import A0Layout from "./layout";
+import A4User from "./A4User";
+import A5Log from "./A5Log";
+
+// 合并 reducer
+const rootReducer = combineReducers({
+  A0Layout,
+  A4User,
+  A5Log,
+});
+
+// 默认导出
+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;
+  }
+}

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

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

+ 11 - 0
后台管理/src/types/api/A5Log.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;
+}

+ 10 - 0
后台管理/src/types/api/layot.d.ts

@@ -0,0 +1,10 @@
+export type LookDomType = {
+  src: string;
+  type: "video" | "audio" | "model" | "";
+};
+export type RouterType = {
+  id: number;
+  name: string;
+  path: string;
+  Com: React.LazyExoticComponent<React.MemoExoticComponent<() => JSX.Element>>;
+}[]

+ 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 "js-export-excel";
+declare module 'braft-utils';

+ 3 - 0
后台管理/src/types/index.d.ts

@@ -0,0 +1,3 @@
+export * from './api/layot'
+export * from './api/A4User'
+export * from './api/A5Log'

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

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

+ 96 - 0
后台管理/src/utils/http.ts

@@ -0,0 +1,96 @@
+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.55:8050/api/"
+  //   : "";
+  process.env.NODE_ENV === "development"
+    ? "https://wxfalangchang.4dage.com"
+    : "";
+
+// 处理  类型“AxiosResponse<any, any>”上不存在属性“code”
+declare module "axios" {
+  interface AxiosResponse {
+    code: number;
+    // 这里追加你的参数
+  }
+}
+
+// 创建 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) {
+      clearTimeout(timeId);
+      timeId = window.setTimeout(() => {
+        removeTokenInfo();
+        MessageFu.warning("登录失效!");
+        history.push("/login");
+      }, 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 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;

+ 34 - 0
后台管理/src/utils/storage.ts

@@ -0,0 +1,34 @@
+// ------------------------------------token的本地存储------------------------------------
+
+// 用户 Token 的本地缓存键名,自己定义
+const TOKEN_KEY = 'SUSWLT2_HT_USER_INFO'
+
+/**
+ * 从本地缓存中获取 Token 信息
+ */
+export const getTokenInfo = (): any => {
+  return JSON.parse(localStorage.getItem(TOKEN_KEY) || '{}')
+}
+
+/**
+ * 将 Token 信息存入缓存
+ * @param {Object} tokenInfo 从后端获取到的 Token 信息
+ */
+export const setTokenInfo = (tokenInfo: any): void => {
+  localStorage.setItem(TOKEN_KEY, JSON.stringify(tokenInfo))
+}
+
+/**
+ * 删除本地缓存中的 Token 信息
+ */
+export const removeTokenInfo = (): void => {
+  localStorage.removeItem(TOKEN_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"
+  ]
+}

+ 23 - 0
知识图谱demo/.gitignore

@@ -0,0 +1,23 @@
+.DS_Store
+node_modules
+/dist
+
+
+# local env files
+.env.local
+.env.*.local
+
+# Log files
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+
+# Editor directories and files
+.idea
+.vscode
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?

+ 19 - 0
知识图谱demo/README.md

@@ -0,0 +1,19 @@
+# demo
+
+## Project setup
+```
+npm install
+```
+
+### Compiles and hot-reloads for development
+```
+npm run serve
+```
+
+### Compiles and minifies for production
+```
+npm run build
+```
+
+### Customize configuration
+See [Configuration Reference](https://cli.vuejs.org/config/).

+ 5 - 0
知识图谱demo/babel.config.js

@@ -0,0 +1,5 @@
+module.exports = {
+  presets: [
+    '@vue/cli-plugin-babel/preset'
+  ]
+}

文件差異過大導致無法顯示
+ 26772 - 0
知识图谱demo/package-lock.json


+ 28 - 0
知识图谱demo/package.json

@@ -0,0 +1,28 @@
+{
+  "name": "demo",
+  "version": "0.1.0",
+  "private": true,
+  "scripts": {
+    "serve": "vue-cli-service serve",
+    "build": "vue-cli-service build"
+  },
+  "dependencies": {
+    "core-js": "^3.6.5",
+    "element-ui": "^2.15.13",
+    "vue": "^2.6.11",
+    "vue-router": "^3.2.0"
+  },
+  "devDependencies": {
+    "@vue/cli-plugin-babel": "~4.5.13",
+    "@vue/cli-plugin-router": "~4.5.13",
+    "@vue/cli-service": "~4.5.13",
+    "less": "^3.0.4",
+    "less-loader": "^5.0.0",
+    "vue-template-compiler": "^2.6.11"
+  },
+  "browserslist": [
+    "> 1%",
+    "last 2 versions",
+    "not dead"
+  ]
+}

+ 47 - 0
知识图谱demo/public/data1.json

@@ -0,0 +1,47 @@
+[{
+    "id": 1,
+    "name": "全部 55",
+    "opacity": 1,
+    "size": 100,
+    "font": 16,
+    "left": 50,
+    "top": 50
+  },
+  {
+    "id": 2,
+    "name": "name2",
+    "opacity": 0.8,
+    "size": 70,
+    "font": 14,
+    "left": 58,
+    "top": 42,
+    "color": "red"
+  },
+  {
+    "id": 3,
+    "name": "name3",
+    "opacity": 0.8,
+    "size": 80,
+    "font": 14,
+    "left": 60,
+    "top": 55
+  },
+  {
+    "id": 4,
+    "name": "name4",
+    "opacity": 0.8,
+    "size": 60,
+    "font": 14,
+    "left": 40,
+    "top": 60
+  },
+  {
+    "id": 5,
+    "name": "快乐 1",
+    "opacity": 0.6,
+    "size": 40,
+    "font": 12,
+    "left": 30,
+    "top": 50
+  }
+]

+ 52 - 0
知识图谱demo/public/data2.json

@@ -0,0 +1,52 @@
+[{
+    "id": 1,
+    "uid": 1,
+    "name": "全部 55",
+    "opacity": 1,
+    "size": 100,
+    "font": 16,
+    "left": 50,
+    "top": 50
+  },
+  {
+    "id": 2,
+    "uid": 1,
+    "name": "name2",
+    "opacity": 0.8,
+    "size": 70,
+    "font": 14,
+    "left": 58,
+    "top": 42,
+    "color": "red"
+  },
+  {
+    "id": 3,
+    "uid": 1,
+    "name": "name3",
+    "opacity": 0.8,
+    "size": 80,
+    "font": 14,
+    "left": 60,
+    "top": 55
+  },
+  {
+    "id": 4,
+    "uid": 2,
+    "name": "name4",
+    "opacity": 0.8,
+    "size": 60,
+    "font": 14,
+    "left": 40,
+    "top": 60
+  },
+  {
+    "id": 5,
+    "uid": 2,
+    "name": "快乐 1",
+    "opacity": 0.6,
+    "size": 40,
+    "font": 12,
+    "left": 30,
+    "top": 50
+  }
+]

文件差異過大導致無法顯示
+ 56 - 0
知识图谱demo/public/data3.json


二進制
知识图谱demo/public/dataImg/1.jpg


二進制
知识图谱demo/public/dataImg/2.jpg


二進制
知识图谱demo/public/dataImg/3.jpg


二進制
知识图谱demo/public/dataImg/4.jpg


二進制
知识图谱demo/public/favicon.ico


+ 17 - 0
知识图谱demo/public/index.html

@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html lang="">
+  <head>
+    <meta charset="utf-8">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge">
+    <meta name="viewport" content="width=device-width,initial-scale=1.0">
+    <link rel="icon" href="<%= BASE_URL %>favicon.ico">
+    <title>知识图谱</title>
+  </head>
+  <body>
+    <noscript>
+      <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
+    </noscript>
+    <div id="app"></div>
+    <!-- built files will be auto injected -->
+  </body>
+</html>

+ 49 - 0
知识图谱demo/public/search.json

@@ -0,0 +1,49 @@
+[{
+    "id": 1,
+    "name": "紫禁城与大运河1",
+    "tit": ["京航大运河", "紫禁城", "历史"],
+    "time": "2021-06-16 - 2023-09-22",
+    "loc": "扬州中国大运河博物馆",
+    "type": "",
+    "scene": {
+      "name": "扬州中国大运河博物馆",
+      "link": "https://leifengbwg.4dage.com/web/#/?m=1359"
+    }
+  },
+  {
+    "id": 2,
+    "name": "紫禁城与大运河2",
+    "tit": ["京航大运河"],
+    "time": "2021-06-16 - 2023-09-22",
+    "loc": "扬州中国大运河博物馆",
+    "type": "",
+    "scene": {
+      "name": "扬州中国大运河博物馆",
+      "link": "https://leifengbwg.4dage.com/web/#/?m=1359"
+    }
+  },
+  {
+    "id": 3,
+    "name": "紫禁城与大运河3",
+    "tit": ["京航大运河", "紫禁城", "历史"],
+    "time": "2021-06-16 - 2023-09-22",
+    "loc": "扬州中国大运河博物馆",
+    "type": "",
+    "scene": {
+      "name": "扬州中国大运河博物馆",
+      "link": "https://leifengbwg.4dage.com/web/#/?m=1359"
+    }
+  },
+  {
+    "id": 4,
+    "name": "紫禁城与大运河4",
+    "tit": ["京航大运河", "紫禁城", "历史"],
+    "time": "2021-06-16 - 2023-09-22",
+    "loc": "扬州中国大运河博物馆",
+    "type": "",
+    "scene": {
+      "name": "扬州中国大运河博物馆",
+      "link": "https://leifengbwg.4dage.com/web/#/?m=1359"
+    }
+  }
+]

+ 36 - 0
知识图谱demo/src/App.vue

@@ -0,0 +1,36 @@
+<template>
+  <div id="app">
+    <Router-view />
+  </div>
+</template>
+
+<script>
+export default {
+  components: {},
+  data() {
+    return {};
+  },
+  computed: {},
+  watch: {},
+  methods: {},
+  created() {},
+  mounted() {},
+  beforeCreate() {}, //生命周期 - 创建之前
+  beforeMount() {}, //生命周期 - 挂载之前
+  beforeUpdate() {}, //生命周期 - 更新之前
+  updated() {}, //生命周期 - 更新之后
+  beforeDestroy() {}, //生命周期 - 销毁之前
+  destroyed() {}, //生命周期 - 销毁完成
+  activated() {}, //如果页面有keep-alive缓存功能,这个函数会触发
+};
+</script>
+<style lang='less' scoped>
+#app {
+  width: 100vw;
+  height: 100vh;
+  & > div {
+    width: 100%;
+    height: 100%;
+  }
+}
+</style>

+ 5 - 0
知识图谱demo/src/assets/base.css

@@ -0,0 +1,5 @@
+* {
+  margin: 0;
+  padding: 0;
+  box-sizing: border-box;
+}

+ 5 - 0
知识图谱demo/src/assets/base.less

@@ -0,0 +1,5 @@
+*{
+  margin: 0;
+  padding: 0;
+  box-sizing: border-box;
+}

文件差異過大導致無法顯示
+ 1 - 0
知识图谱demo/src/assets/img/close.svg


文件差異過大導致無法顯示
+ 1 - 0
知识图谱demo/src/assets/img/location.svg


文件差異過大導致無法顯示
+ 1 - 0
知识图谱demo/src/assets/img/right.svg


文件差異過大導致無法顯示
+ 1 - 0
知识图谱demo/src/assets/img/scene.svg


文件差異過大導致無法顯示
+ 1 - 0
知识图谱demo/src/assets/img/search.svg


文件差異過大導致無法顯示
+ 1 - 0
知识图谱demo/src/assets/img/time.svg


+ 129 - 0
知识图谱demo/src/components/Main.vue

@@ -0,0 +1,129 @@
+<template>
+  <div
+    class="Main"
+    :style="{
+      backgroundColor: styleInfo.bac,
+      boxShadow: `0px 0px 80px 80px ${styleInfo.bac}`,
+    }"
+  >
+    <div class="xian" :style="{ border: `1px dashed ${styleInfo.borC}` }"></div>
+    <div
+      class="xian xian2"
+      :style="{ border: `1px dashed ${styleInfo.borC}` }"
+    ></div>
+    <div
+      class="xian xian3"
+      :style="{ border: `1px dashed ${styleInfo.borC}` }"
+    ></div>
+    <div
+      @click="clickFu(item)"
+      class="qiu"
+      v-for="item in data"
+      :key="item.id"
+      :style="{
+        top: item.top + '%',
+        left: item.left + '%',
+        width: item.size + 'px',
+        height: item.size + 'px',
+        fontSize: item.font + 'px',
+        opacity: item.opacity,
+        backgroundColor: item.color ? item.color : styleInfo.qBac,
+        color: styleInfo.qColor,
+      }"
+    >
+      {{ item.name }}
+    </div>
+  </div>
+</template>
+
+<script>
+export default {
+  components: {},
+  props: {
+    data: {
+      type: Array,
+      default: () => [],
+    },
+    clickPath: {
+      type: String,
+      default: "",
+    },
+    styleInfo: {
+      type: Object,
+      default: () => ({}),
+    },
+    isThree: {
+      type: Boolean,
+      default: false,
+    },
+  },
+  data() {
+    return {};
+  },
+  computed: {},
+  watch: {},
+  methods: {
+    clickFu(item) {
+      if (this.isThree) this.$emit("lookInfoFu", item);
+      else this.$router.push(this.clickPath + item.id);
+    },
+  },
+  created() {},
+  mounted() {},
+  beforeCreate() {}, //生命周期 - 创建之前
+  beforeMount() {}, //生命周期 - 挂载之前
+  beforeUpdate() {}, //生命周期 - 更新之前
+  updated() {}, //生命周期 - 更新之后
+  beforeDestroy() {}, //生命周期 - 销毁之前
+  destroyed() {}, //生命周期 - 销毁完成
+  activated() {}, //如果页面有keep-alive缓存功能,这个函数会触发
+};
+</script>
+<style lang='less' scoped>
+.Main {
+  border-radius: 50%;
+  position: absolute;
+  width: 100vh;
+  height: 100vh;
+  top: 50%;
+  left: 50%;
+  transform: translate(-50%, -50%);
+  .xian {
+    pointer-events: none;
+    position: absolute;
+    top: 50%;
+    left: 50%;
+    transform: translate(-50%, -50%);
+    width: 160px;
+    height: 160px;
+    border-radius: 50%;
+  }
+  .xian2 {
+    width: 240px;
+    height: 240px;
+  }
+  .xian3 {
+    width: 320px;
+    height: 320px;
+  }
+  .qiu {
+    cursor: pointer;
+    position: absolute;
+    top: 50%;
+    left: 50%;
+    transform: translate(-50%, -50%);
+    z-index: 10;
+    width: 100px;
+    height: 100px;
+    border-radius: 50%;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    font-size: 14px;
+    transition: all 0.3s;
+    &:hover {
+      transform: translate(-50%, -50%) scale(1.1);
+    }
+  }
+}
+</style>

+ 320 - 0
知识图谱demo/src/components/Search.vue

@@ -0,0 +1,320 @@
+<template>
+  <div class="Search">
+    <div class="incoM" @click="searchShow = true">
+      <img src="../assets/img/search.svg" alt="" />
+    </div>
+
+    <div class="mainTop" :class="{ mainTopShow: searchShow }">
+      <div class="mainCen">
+        <div class="top">
+          <div class="title">搜 索</div>
+          <div class="right" @keyup.enter="searchFu">
+            <el-select v-model="value" placeholder="请选择">
+              <el-option
+                v-for="item in options"
+                :key="item.value"
+                :label="item.label"
+                :value="item.value"
+              >
+              </el-option>
+            </el-select>
+            &emsp;
+            <el-input
+              placeholder="请输入关键词"
+              suffix-icon="el-icon-search"
+              v-model="input1"
+              :maxlength="20"
+            >
+            </el-input>
+            <div class="searchIcon" @click="searchFu"></div>
+          </div>
+        </div>
+        <div class="main">
+          <div class="noInfo" v-if="dataAll.length && data.length === 0">
+            暂无信息
+          </div>
+          <div class="row" v-else v-for="item in data" :key="item.id">
+            <img :src="`dataImg/${item.id}.jpg`" alt="" />
+            <div class="infoBox">
+              <div class="name">{{ item.name }}</div>
+              <div class="tagBox">
+                <div class="tag" v-for="(val, ind) in item.tit" :key="ind">
+                  {{ val }}
+                </div>
+              </div>
+              <div class="txtBox">
+                <div class="inco">
+                  <img
+                    src="../assets/img/time.svg"
+                    alt=""
+                  />&nbsp;线下展览时间:
+                </div>
+                <div class="txt" :title="item.time">{{ item.time }}</div>
+              </div>
+              <div class="txtBox">
+                <div class="inco">
+                  <img
+                    src="../assets/img/location.svg"
+                    alt=""
+                  />&nbsp;展览地点:
+                </div>
+                <div class="txt" :title="item.loc">{{ item.loc }}</div>
+              </div>
+              <div class="txtBox">
+                <div class="inco">
+                  <img src="../assets/img/scene.svg" alt="" />&nbsp;参展博物馆:
+                </div>
+                <div
+                  class="txt link"
+                  :title="item.scene.link"
+                  @click="openUrlFu(item.scene.link)"
+                >
+                  {{ item.scene.name }}
+                </div>
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+      <img
+        @click="searchShow = false"
+        class="searchClose"
+        src="../assets/img/close.svg"
+        alt=""
+      />
+    </div>
+  </div>
+</template>
+
+<script>
+export default {
+  components: {},
+  data() {
+    return {
+      searchShow: false,
+
+      input1: "",
+      value: "全部",
+      options: [
+        {
+          value: "全部",
+          label: "全部",
+        },
+        {
+          value: "选项1",
+          label: "选项1",
+        },
+        {
+          value: "选项2",
+          label: "选项2",
+        },
+      ],
+      dataAll: [],
+      data: [],
+    };
+  },
+  computed: {},
+  watch: {},
+  methods: {
+    searchFu() {
+      if (this.input1.trim() === "") this.data = [...this.dataAll];
+      else this.data = this.dataAll.filter((v) => v.name.includes(this.input1));
+    },
+    openUrlFu(url) {
+      window.open(url);
+    },
+  },
+  created() {},
+  mounted() {
+    fetch("search.json")
+      .then((res) => {
+        return res.json();
+      })
+      .then((data) => {
+        this.dataAll = data;
+        this.data = data;
+      });
+  },
+  beforeCreate() {}, //生命周期 - 创建之前
+  beforeMount() {}, //生命周期 - 挂载之前
+  beforeUpdate() {}, //生命周期 - 更新之前
+  updated() {}, //生命周期 - 更新之后
+  beforeDestroy() {}, //生命周期 - 销毁之前
+  destroyed() {}, //生命周期 - 销毁完成
+  activated() {}, //如果页面有keep-alive缓存功能,这个函数会触发
+};
+</script>
+<style lang='less' scoped>
+.Search {
+  position: fixed;
+  z-index: 11;
+  right: 20px;
+  bottom: 50px;
+  height: 40px;
+  width: 54px;
+  background-color: #e3e3e3;
+  border-radius: 20px;
+
+  .incoM {
+    cursor: pointer;
+    width: 100%;
+    height: 100%;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    img {
+      width: 30px;
+    }
+  }
+
+  .mainTop {
+    position: fixed;
+    width: 100%;
+    height: 100%;
+    z-index: 22;
+    top: 0;
+    left: 0;
+    opacity: 0;
+    pointer-events: none;
+    transition: all 0.3s;
+    background-color: rgba(0, 0, 0, 0.8);
+
+    .mainCen {
+      position: absolute;
+      width: 86%;
+      height: 76%;
+      top: 50%;
+      left: 50%;
+      transform: translate(-50%, -50%);
+      background-color: #fff;
+      border-radius: 10px;
+      padding: 20px 5px 0px 30px;
+      .top {
+        display: flex;
+        justify-content: space-between;
+        .title {
+          font-weight: 700;
+          font-size: 24px;
+          color: #7a7a7a;
+        }
+        .right {
+          padding-right: 35px;
+          display: flex;
+          position: relative;
+          .searchIcon {
+            cursor: pointer;
+            position: absolute;
+            z-index: 10;
+            right: 0;
+            top: 0;
+            width: 28px;
+            height: 40px;
+          }
+        }
+      }
+      .main {
+        margin-top: 20px;
+        width: 100%;
+        height: calc(100% - 80px);
+        &::-webkit-scrollbar {
+          /*滚动条整体样式*/
+          width: 5px; /*高宽分别对应横竖滚动条的尺寸*/
+          height: 1px;
+        }
+        &::-webkit-scrollbar-thumb {
+          /*滚动条里面小方块*/
+          border-radius: 10px;
+          -webkit-box-shadow: inset 0 0 5px transparent;
+          background: black;
+        }
+        &::-webkit-scrollbar-track {
+          /*滚动条里面轨道*/
+          -webkit-box-shadow: inset 0 0 5px transparent;
+          border-radius: 10px;
+          background: transparent;
+        }
+        display: flex;
+        flex-wrap: wrap;
+        overflow-y: auto;
+        padding-bottom: 20px;
+        .row {
+          width: 23%;
+          height: 400px;
+          border-radius: 6px;
+          overflow: hidden;
+          margin-right: 2%;
+          box-shadow: 0px 0px 6px 3px #ccc;
+          margin-bottom: 2%;
+          transition: all 0.3s;
+          &:hover {
+            box-shadow: 0px 0px 6px 6px #ccc;
+          }
+          & > img {
+            width: 100%;
+            height: 200px;
+            object-fit: cover;
+          }
+          .infoBox {
+            padding: 10px;
+            .tagBox {
+              margin: 14px 0;
+              display: flex;
+              .tag {
+                background-color: #e8e2d9;
+                padding: 3px 6px;
+                border-radius: 4px;
+                border: 1px solid #a1670a;
+                color: #a1670a;
+                font-size: 12px;
+                margin-right: 6px;
+              }
+            }
+            .txtBox {
+              font-size: 14px;
+              display: flex;
+              margin-bottom: 15px;
+              .inco {
+                display: flex;
+                & > img {
+                  width: 20px;
+                }
+              }
+              .txt {
+                width: calc(100% - 130px);
+                cursor: pointer;
+                overflow: hidden;
+                text-overflow: ellipsis;
+                white-space: nowrap;
+              }
+              .link {
+                color: #409eff;
+              }
+            }
+          }
+        }
+        .noInfo {
+          display: flex;
+          justify-content: center;
+          align-items: center;
+          width: 100%;
+          height: 100%;
+          font-size: 24px;
+          color: #a1670a;
+        }
+      }
+    }
+    .searchClose {
+      position: absolute;
+      cursor: pointer;
+      bottom: 30px;
+      left: 50%;
+      transform: translateX(-50%);
+      width: 40px;
+    }
+  }
+  .mainTopShow {
+    opacity: 1;
+    pointer-events: auto;
+  }
+}
+</style>

+ 17 - 0
知识图谱demo/src/main.js

@@ -0,0 +1,17 @@
+import Vue from 'vue'
+import App from './App.vue'
+import router from './router'
+
+Vue.config.productionTip = false
+
+import ElementUI from 'element-ui';
+import 'element-ui/lib/theme-chalk/index.css';
+Vue.use(ElementUI);
+
+
+import '@/assets/base.css'
+
+new Vue({
+  router,
+  render: h => h(App)
+}).$mount('#app')

+ 29 - 0
知识图谱demo/src/router/index.js

@@ -0,0 +1,29 @@
+import Vue from 'vue'
+import VueRouter from 'vue-router'
+
+Vue.use(VueRouter)
+
+const routes = [{
+    path: '/',
+    name: 'Home',
+    component: () => import('../views/Home.vue')
+  },
+  {
+    path: '/tow/:id',
+    name: 'Tow',
+    component: () => import('../views/Tow.vue')
+  },
+  {
+    path: '/three/:id',
+    name: 'Three',
+    component: () => import('../views/Three.vue')
+  },
+]
+
+const router = new VueRouter({
+  // mode: 'history',
+  base: process.env.BASE_URL,
+  routes
+})
+
+export default router

+ 83 - 0
知识图谱demo/src/views/Home.vue

@@ -0,0 +1,83 @@
+<template>
+  <div class="Home">
+    <div class="leftTitle">首页</div>
+
+    <Main
+      :data="data"
+      clickPath="/tow/"
+      :styleInfo="{ bac: '#fff', borC: 'black',qBac:'#5a5a5a',qColor:'#fff' }"
+    />
+
+    <div class="floorIght">
+      <div class="btn active">小学语文</div>
+      <div class="btn">初中历史</div>
+      <Search />
+    </div>
+  </div>
+</template>
+
+<script>
+import Search from "../components/Search.vue";
+import Main from "../components/Main.vue";
+
+export default {
+  components: { Search, Main },
+  data() {
+    return {
+      data: [],
+    };
+  },
+  computed: {},
+  watch: {},
+  methods: {},
+  created() {},
+  mounted() {
+    fetch("data1.json")
+      .then((res) => {
+        return res.json();
+      })
+      .then((data) => {
+        this.data = data;
+      });
+  },
+  beforeCreate() {}, //生命周期 - 创建之前
+  beforeMount() {}, //生命周期 - 挂载之前
+  beforeUpdate() {}, //生命周期 - 更新之前
+  updated() {}, //生命周期 - 更新之后
+  beforeDestroy() {}, //生命周期 - 销毁之前
+  destroyed() {}, //生命周期 - 销毁完成
+  activated() {}, //如果页面有keep-alive缓存功能,这个函数会触发
+};
+</script>
+<style lang='less' scoped>
+.Home {
+  background-color: #eaeaea;
+  position: relative;
+  .leftTitle {
+    position: absolute;
+    top: 20px;
+    left: 20px;
+    font-size: 20px;
+  }
+
+  .floorIght {
+    position: absolute;
+    right: 20px;
+    bottom: 50px;
+    display: flex;
+    padding-right: 50px;
+    .btn {
+      margin-right: 15px;
+      border-radius: 20px;
+      height: 40px;
+      background-color: #e3e3e3;
+      padding: 0 30px;
+      line-height: 40px;
+    }
+    .active {
+      background-color: #a8a8a8;
+      color: #fff;
+    }
+  }
+}
+</style>

+ 176 - 0
知识图谱demo/src/views/Three.vue

@@ -0,0 +1,176 @@
+<template>
+  <div class="Three">
+    <div class="leftTitle">三级页面</div>
+
+    <Main
+      :data="data"
+      :isThree="true"
+      @lookInfoFu="lookInfoFu"
+      :styleInfo="{
+        bac: '#bbbbbb',
+        borC: '#fff',
+        qBac: '#fff',
+        qColor: 'black',
+      }"
+    />
+
+    <div class="floorIght">
+      <div class="btn" @click="$router.push('/')">去首页</div>
+      <div class="btn" @click="$router.go(-1)">返回上一页</div>
+    </div>
+
+    <!-- 右边的简介 -->
+    <div class="info" :class="{ infoShow: info.title }">
+      <div class="infoMain">
+        <div class="intoTitle"><span>关于</span> {{ info.title }}</div>
+        <div class="infoTxt" v-html="info.txt"></div>
+      </div>
+      <!-- 收起 -->
+      <div class="hideInfo" @click="info = {}">
+        <img src="../assets/img/right.svg" alt="" />
+      </div>
+    </div>
+    <Search />
+  </div>
+</template>
+
+<script>
+import Search from "../components/Search.vue";
+import Main from "../components/Main.vue";
+
+export default {
+  components: { Search, Main },
+  data() {
+    return {
+      data: [],
+      info: {},
+    };
+  },
+  computed: {},
+  watch: {
+    info(val) {
+      if (val.title) {
+        const dom = document.querySelector(".infoMain");
+        if (dom) {
+          dom.scrollTop = 0;
+        }
+      }
+    },
+  },
+  methods: {
+    lookInfoFu(item) {
+      if (item.link) window.open(item.link);
+      else if (this.info.title && this.info.title === item.name) this.info = {};
+      else this.info = { title: item.name, txt: item.txt };
+    },
+  },
+  created() {},
+  mounted() {
+    fetch("data3.json")
+      .then((res) => {
+        return res.json();
+      })
+      .then((data) => {
+        this.data = data.filter((v) => v.uid == this.$route.params.id);
+      });
+  },
+  beforeCreate() {}, //生命周期 - 创建之前
+  beforeMount() {}, //生命周期 - 挂载之前
+  beforeUpdate() {}, //生命周期 - 更新之前
+  updated() {}, //生命周期 - 更新之后
+  beforeDestroy() {}, //生命周期 - 销毁之前
+  destroyed() {}, //生命周期 - 销毁完成
+  activated() {}, //如果页面有keep-alive缓存功能,这个函数会触发
+};
+</script>
+<style lang='less' scoped>
+.Three {
+  background-color: #dbdbdb;
+  position: relative;
+  overflow: hidden;
+  .leftTitle {
+    position: absolute;
+    top: 20px;
+    left: 20px;
+    font-size: 20px;
+  }
+
+  .floorIght {
+    position: absolute;
+    left: 50%;
+    transform: translateX(-50%);
+    bottom: 50px;
+    display: flex;
+    & > div {
+      cursor: pointer;
+      margin-right: 15px;
+      border-radius: 20px;
+      height: 40px;
+      background-color: #e3e3e3;
+      padding: 0 30px;
+      line-height: 40px;
+    }
+  }
+  .info {
+    transition: all 0.3s;
+    position: absolute;
+    right: -400px;
+    top: 0;
+    z-index: 20;
+    background-color: rgba(0, 0, 0, 0.8);
+    width: 400px;
+    height: 100%;
+    border-radius: 10px 0 0 10px;
+    color: #fff;
+    padding: 80px 10px 80px 20px;
+    .infoMain {
+      height: 100%;
+      padding-right: 10px;
+      overflow-y: auto;
+      &::-webkit-scrollbar {
+        /*滚动条整体样式*/
+        width: 5px; /*高宽分别对应横竖滚动条的尺寸*/
+        height: 1px;
+      }
+      &::-webkit-scrollbar-thumb {
+        /*滚动条里面小方块*/
+        border-radius: 10px;
+        -webkit-box-shadow: inset 0 0 5px transparent;
+        background: #fff;
+      }
+      &::-webkit-scrollbar-track {
+        /*滚动条里面轨道*/
+        -webkit-box-shadow: inset 0 0 5px transparent;
+        border-radius: 10px;
+        background: transparent;
+      }
+      .intoTitle {
+        font-weight: 700;
+        font-size: 30px;
+        margin-bottom: 20px;
+        & > span {
+          font-weight: 400;
+          font-size: 16px;
+        }
+      }
+      .infoTxt {
+        line-height: 24px;
+        letter-spacing: 4px;
+      }
+    }
+    .hideInfo {
+      cursor: pointer;
+      position: absolute;
+      z-index: 10;
+      bottom: 10px;
+      left: 20px;
+      & > img {
+        width: 30px;
+      }
+    }
+  }
+  .infoShow {
+    right: 0;
+  }
+}
+</style>

+ 84 - 0
知识图谱demo/src/views/Tow.vue

@@ -0,0 +1,84 @@
+<template>
+  <div class="Tow">
+    <div class="leftTitle">二级页面</div>
+
+    <Main
+      :data="data"
+      clickPath="/three/"
+      :styleInfo="{
+        bac: '#bbbbbb',
+        borC: '#fff',
+        qBac: '#fff',
+        qColor: 'black',
+      }"
+    />
+
+    <div class="floorIght">
+      <div class="btn" @click="$router.push('/')">去首页</div>
+    </div>
+    <Search />
+  </div>
+</template>
+
+<script>
+import Search from "../components/Search.vue";
+import Main from "../components/Main.vue";
+
+export default {
+  components: { Search, Main },
+  data() {
+    return {
+      data: [],
+    };
+  },
+  computed: {},
+  watch: {},
+  methods: {},
+  created() {},
+  mounted() {
+    fetch("data2.json")
+      .then((res) => {
+        return res.json();
+      })
+      .then((data) => {
+        this.data = data.filter((v) => v.uid == this.$route.params.id);
+      });
+  },
+  beforeCreate() {}, //生命周期 - 创建之前
+  beforeMount() {}, //生命周期 - 挂载之前
+  beforeUpdate() {}, //生命周期 - 更新之前
+  updated() {}, //生命周期 - 更新之后
+  beforeDestroy() {}, //生命周期 - 销毁之前
+  destroyed() {}, //生命周期 - 销毁完成
+  activated() {}, //如果页面有keep-alive缓存功能,这个函数会触发
+};
+</script>
+<style lang='less' scoped>
+.Tow {
+  background-color: #dbdbdb;
+  position: relative;
+  .leftTitle {
+    position: absolute;
+    top: 20px;
+    left: 20px;
+    font-size: 20px;
+  }
+
+  .floorIght {
+    position: absolute;
+    left: 50%;
+    transform: translateX(-50%);
+    bottom: 50px;
+    display: flex;
+    & > div {
+      cursor: pointer;
+      margin-right: 15px;
+      border-radius: 20px;
+      height: 40px;
+      background-color: #e3e3e3;
+      padding: 0 30px;
+      line-height: 40px;
+    }
+  }
+}
+</style>

+ 0 - 0
知识图谱demo/vue.config.js


部分文件因文件數量過多而無法顯示