shaogen1995 2 лет назад
Сommit
8ff8ffcc82
72 измененных файлов с 32501 добавлено и 0 удалено
  1. 23 0
      houtai/.gitignore
  2. 46 0
      houtai/README.md
  3. 10 0
      houtai/config-overrides.js
  4. 29858 0
      houtai/package-lock.json
  5. 63 0
      houtai/package.json
  6. 8 0
      houtai/path.tsconfig.json
  7. BIN
      houtai/public/favicon.ico
  8. 43 0
      houtai/public/index.html
  9. 65 0
      houtai/src/App.tsx
  10. BIN
      houtai/src/assets/img/IMGerror.png
  11. BIN
      houtai/src/assets/img/activeLL.png
  12. BIN
      houtai/src/assets/img/bg.jpg
  13. BIN
      houtai/src/assets/img/inco1.png
  14. BIN
      houtai/src/assets/img/inco1Ac.png
  15. BIN
      houtai/src/assets/img/inco2.png
  16. BIN
      houtai/src/assets/img/inco2Ac.png
  17. BIN
      houtai/src/assets/img/inco3.png
  18. BIN
      houtai/src/assets/img/inco3Ac.png
  19. BIN
      houtai/src/assets/img/inco4.png
  20. BIN
      houtai/src/assets/img/inco4Ac.png
  21. BIN
      houtai/src/assets/img/layoutLeftMain.jpg
  22. BIN
      houtai/src/assets/img/layoutTop.jpg
  23. BIN
      houtai/src/assets/img/loading.gif
  24. BIN
      houtai/src/assets/img/logo.png
  25. BIN
      houtai/src/assets/img/top.jpg
  26. BIN
      houtai/src/assets/img/user.png
  27. 153 0
      houtai/src/assets/styles/base.css
  28. 206 0
      houtai/src/assets/styles/base.less
  29. 21 0
      houtai/src/components/AsyncSpinLoding/index.module.scss
  30. 15 0
      houtai/src/components/AsyncSpinLoding/index.tsx
  31. 32 0
      houtai/src/components/AuthRoute/index.tsx
  32. 37 0
      houtai/src/components/ImageLazy/index.module.scss
  33. 93 0
      houtai/src/components/ImageLazy/index.tsx
  34. 29 0
      houtai/src/components/Message/index.tsx
  35. 26 0
      houtai/src/components/NotFound/index.tsx
  36. 10 0
      houtai/src/components/SpinLoding/index.module.scss
  37. 13 0
      houtai/src/components/SpinLoding/index.tsx
  38. 43 0
      houtai/src/components/UpAsyncLoding/index.module.scss
  39. 38 0
      houtai/src/components/UpAsyncLoding/index.tsx
  40. 52 0
      houtai/src/components/VideoLook/index.module.scss
  41. 37 0
      houtai/src/components/VideoLook/index.tsx
  42. 34 0
      houtai/src/components/VideoLookDom/index.module.scss
  43. 32 0
      houtai/src/components/VideoLookDom/index.tsx
  44. 41 0
      houtai/src/index.tsx
  45. 72 0
      houtai/src/pages/A1Hot/index.module.scss
  46. 181 0
      houtai/src/pages/A1Hot/index.tsx
  47. 5 0
      houtai/src/pages/A2News/index.module.scss
  48. 14 0
      houtai/src/pages/A2News/index.tsx
  49. 5 0
      houtai/src/pages/A3Goods/index.module.scss
  50. 14 0
      houtai/src/pages/A3Goods/index.tsx
  51. 5 0
      houtai/src/pages/A4Venue/index.module.scss
  52. 14 0
      houtai/src/pages/A4Venue/index.tsx
  53. 5 0
      houtai/src/pages/A5Guest/index.module.scss
  54. 14 0
      houtai/src/pages/A5Guest/index.tsx
  55. 197 0
      houtai/src/pages/Layout/index.module.scss
  56. 271 0
      houtai/src/pages/Layout/index.tsx
  57. 135 0
      houtai/src/pages/Login/index.module.scss
  58. 77 0
      houtai/src/pages/Login/index.tsx
  59. 15 0
      houtai/src/store/action/layout.ts
  60. 20 0
      houtai/src/store/index.ts
  61. 13 0
      houtai/src/store/reducer/index.ts
  62. 58 0
      houtai/src/store/reducer/layout.ts
  63. 4 0
      houtai/src/types/api/layot.d.ts
  64. 6 0
      houtai/src/types/declaration.d.ts
  65. 2 0
      houtai/src/types/index.d.ts
  66. 18 0
      houtai/src/utils/domShow.ts
  67. 17 0
      houtai/src/utils/history.ts
  68. 100 0
      houtai/src/utils/http.ts
  69. 50 0
      houtai/src/utils/message.ts
  70. 100 0
      houtai/src/utils/pass.ts
  71. 34 0
      houtai/src/utils/storage.ts
  72. 27 0
      houtai/tsconfig.json

+ 23 - 0
houtai/.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
houtai/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
houtai/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)

Разница между файлами не показана из-за своего большого размера
+ 29858 - 0
houtai/package-lock.json


+ 63 - 0
houtai/package.json

@@ -0,0 +1,63 @@
+{
+  "name": "demo",
+  "version": "0.1.0",
+  "private": true,
+  "dependencies": {
+    "@ant-design/cssinjs": "^1.5.6",
+    "@testing-library/jest-dom": "^5.16.5",
+    "@testing-library/react": "^13.4.0",
+    "@testing-library/user-event": "^13.5.0",
+    "@types/jest": "^27.5.2",
+    "@types/node": "^16.18.3",
+    "@types/react": "^18.0.24",
+    "@types/react-dom": "^18.0.8",
+    "antd": "^5.0.4",
+    "axios": "^1.1.3",
+    "dayjs": "^1.11.7",
+    "echarts": "^5.4.0",
+    "js-base64": "^3.7.3",
+    "react": "^18.2.0",
+    "react-dom": "^18.2.0",
+    "react-lazyimg-component": "^1.0.1",
+    "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
houtai/path.tsconfig.json

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

BIN
houtai/public/favicon.ico


+ 43 - 0
houtai/public/index.html

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

+ 65 - 0
houtai/src/App.tsx

@@ -0,0 +1,65 @@
+import "@/assets/styles/base.css";
+// 关于路由
+import React from "react";
+import { Router, Route, Switch } from "react-router-dom";
+import history from "./utils/history";
+import AuthRoute from "./components/AuthRoute";
+import SpinLoding from "./components/SpinLoding";
+import AsyncSpinLoding from "./components/AsyncSpinLoding";
+import { Image } from "antd";
+import { useSelector } from "react-redux";
+import store, { RootState } from "./store";
+import UpAsyncLoding from "./components/UpAsyncLoding";
+import VideoLookDom from "./components/VideoLookDom";
+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.layoutStore.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 />
+
+      {/* 点击预览视频组件 */}
+      <VideoLookDom />
+
+      {/* antd 轻提示 ---兼容360浏览器 */}
+      <MessageCom />
+    </>
+  );
+}

BIN
houtai/src/assets/img/IMGerror.png


BIN
houtai/src/assets/img/activeLL.png


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


BIN
houtai/src/assets/img/inco1.png


BIN
houtai/src/assets/img/inco1Ac.png


BIN
houtai/src/assets/img/inco2.png


BIN
houtai/src/assets/img/inco2Ac.png


BIN
houtai/src/assets/img/inco3.png


BIN
houtai/src/assets/img/inco3Ac.png


BIN
houtai/src/assets/img/inco4.png


BIN
houtai/src/assets/img/inco4Ac.png


BIN
houtai/src/assets/img/layoutLeftMain.jpg


BIN
houtai/src/assets/img/layoutTop.jpg


BIN
houtai/src/assets/img/loading.gif


BIN
houtai/src/assets/img/logo.png


BIN
houtai/src/assets/img/top.jpg


BIN
houtai/src/assets/img/user.png


+ 153 - 0
houtai/src/assets/styles/base.css

@@ -0,0 +1,153 @@
+* {
+  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;
+}
+/* 主题色 */
+:root {
+  --themeColor: #9d6b39;
+}
+/* 找不到页面 */
+.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 a {
+  color: var(--themeColor);
+}
+#root .ant-btn-text {
+  color: #99b8dd;
+}
+#root .ant-btn-text:disabled {
+  cursor: not-allowed;
+  color: rgba(0, 0, 0, 0.25);
+}
+#root .ant-btn-text.ant-btn-dangerous {
+  color: var(--themeColor);
+}
+#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 {
+  position: absolute;
+  top: -5px;
+  left: 0;
+  font-size: 20px;
+  font-weight: 700;
+  color: var(--themeColor);
+}

+ 206 - 0
houtai/src/assets/styles/base.less

@@ -0,0 +1,206 @@
+* {
+  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;
+}
+
+/* 主题色 */
+:root {
+  --themeColor: #9d6b39;
+}
+
+
+
+
+
+/* 找不到页面 */
+.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: #99b8dd;
+  }
+
+  .ant-btn-text:disabled {
+    cursor: not-allowed;
+    color: rgba(0, 0, 0, 0.25);
+  }
+
+  /* 按钮的危险颜色 */
+  .ant-btn-text.ant-btn-dangerous {
+    color: var(--themeColor);
+  }
+
+  /* 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{
+  position: absolute;
+  top: -5px;
+  left: 0;
+  font-size: 20px;
+  font-weight: 700;
+  color: var(--themeColor);
+}

+ 21 - 0
houtai/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
houtai/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
houtai/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",
+              }}
+            />
+          );
+        }
+      }}
+    />
+  );
+}

+ 37 - 0
houtai/src/components/ImageLazy/index.module.scss

@@ -0,0 +1,37 @@
+.ImageLazy{
+  position: relative;
+  :global{
+    .lazyBox{
+      width: 100%;
+      height: 100%;
+      position: relative;
+      .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;
+        }
+      }
+    }
+  }
+
+}

+ 93 - 0
houtai/src/components/ImageLazy/index.tsx

@@ -0,0 +1,93 @@
+import React, { useCallback, useEffect, useState } from "react";
+import styles from "./index.module.scss";
+import Lazyimg from "react-lazyimg-component";
+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";
+
+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 [placeholderUrl, setPlaceholderUrl] = useState(
+    src ? imgLoding : imgErr
+  );
+
+  // 默认不能预览图片,加载成功之后能预览
+  const [lookImg, setLookImg] = useState(false);
+
+  useEffect(() => {
+    if (src) {
+      // 进页面查看图片的加载情况
+      // 创建一个img标签
+      const imgDom = document.createElement("img");
+      imgDom.src = offline ? src : baseURL + src;
+
+      // 不管图片加载成功或者失败,都删除掉,提高性能
+      // 图片加载成功
+      imgDom.onload = function () {
+        setLookImg(true);
+        imgDom.remove();
+      };
+      // 图片加载失败
+      imgDom.onerror = function () {
+        setPlaceholderUrl(imgErr);
+        imgDom.remove();
+      };
+
+      return () => {
+        // 离开页面也删掉掉元素
+        imgDom.remove();
+      };
+    }
+  }, [offline, src]);
+
+  // 点击预览图片
+  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">
+        <Lazyimg
+          src={src ? (offline ? src : baseURL + src) : ""}
+          width={width}
+          height={height}
+          placeholder={placeholderUrl}
+          alt=""
+        />
+
+        {/* 图片预览 */}
+        {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
houtai/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.layoutStore.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
houtai/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
houtai/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
houtai/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
houtai/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
houtai/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.layoutStore.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;

+ 52 - 0
houtai/src/components/VideoLook/index.module.scss

@@ -0,0 +1,52 @@
+.VideoLook {
+  :global {
+    .videoLookBox {
+      cursor: pointer;
+      width: 100%;
+      height: 100%;
+      display: flex;
+      justify-content: center;
+      align-items: center;
+
+      .videoCover {
+        width: 100%;
+        height: 100%;
+        position: relative;
+
+        video {
+          width: 100%;
+          height: 100%;
+        }
+
+        .videoInco {
+          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 {
+          .videoInco {
+            opacity: 1;
+            pointer-events: auto;
+          }
+        }
+      }
+    }
+  }
+}

+ 37 - 0
houtai/src/components/VideoLook/index.tsx

@@ -0,0 +1,37 @@
+import React from "react";
+import { PlayCircleOutlined } from "@ant-design/icons";
+import styles from "./index.module.scss";
+import { baseURL } from "@/utils/http";
+import store from "@/store";
+
+type Props = {
+  width?: number;
+  height?: number;
+  src: string;
+};
+
+function VideoLook({ src, width = 100, height = 100 }: Props) {
+  return (
+    <div className={styles.VideoLook} style={{ width, height }}>
+      <div
+        className="videoLookBox"
+        onClick={() =>
+          store.dispatch({ type: "layout/lookVideo", payload: src })
+        }
+      >
+        <div className="videoCover">
+          <video src={baseURL + src}></video>
+          <div className="videoInco">
+            <PlayCircleOutlined />
+            &nbsp;
+            <div>预览</div>
+          </div>
+        </div>
+      </div>
+    </div>
+  );
+}
+
+const MemoVideoLook = React.memo(VideoLook);
+
+export default MemoVideoLook;

+ 34 - 0
houtai/src/components/VideoLookDom/index.module.scss

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

+ 32 - 0
houtai/src/components/VideoLookDom/index.tsx

@@ -0,0 +1,32 @@
+import React from "react";
+import { CloseCircleOutlined } from "@ant-design/icons";
+import styles from "./index.module.scss";
+import { useSelector } from "react-redux";
+import store, { RootState } from "@/store";
+import { baseURL } from "@/utils/http";
+function VideoLookDom() {
+  const videoSrc = useSelector((state: RootState) => state.layoutStore.videoSrc);
+  return (
+    <div
+      className={styles.VideoLookDom}
+      style={videoSrc ? { opacity: 1, pointerEvents: "auto" } : {}}
+    >
+      <div className="viedoBox">
+        {videoSrc ? (
+          <video autoPlay controls src={baseURL + videoSrc}></video>
+        ) : null}
+      </div>
+
+      <div
+        className="close"
+        onClick={() => store.dispatch({ type: "layout/lookVideo", payload: "" })}
+      >
+        <CloseCircleOutlined />
+      </div>
+    </div>
+  );
+}
+
+const MemoVideoLookDom = React.memo(VideoLookDom);
+
+export default MemoVideoLookDom;

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

+ 72 - 0
houtai/src/pages/A1Hot/index.module.scss

@@ -0,0 +1,72 @@
+.Hot {
+  position: relative;
+  padding-top: 30px;
+
+  :global {
+
+    .hotTopTime {
+      border-radius: 10px;
+      background-color: #fff;
+      height: 50px;
+      display: flex;
+      align-items: center;
+      padding: 10px;
+    }
+
+    .hotMain {
+      margin-top: 15px;
+      width: 100%;
+      height: calc(100% - 65px);
+      display: flex;
+
+      .hotBoxTit {
+        font-size: 18px;
+        padding-left: 10px;
+        font-weight: 600;
+        position: relative;
+
+        .hotBoxTitRight{
+          position: absolute;
+          right: 0;
+          top: 50%;
+          transform: translateY(-50%);
+        }
+
+        &::before {
+          position: absolute;
+          left: 0;
+          top: 50%;
+          transform: translateY(-50%);
+          content: '';
+          width: 4px;
+          height: 20px;
+          background-color: var(--themeColor);
+        }
+      }
+
+      &>div {
+        padding: 10px;
+        height: 100%;
+        border-radius: 10px;
+        background-color: #fff;
+      }
+
+      .hotBox1 {
+        width: 300px;
+        #echarts1{
+          width: 100%;
+          height: calc(100% - 30px);
+        }
+      }
+
+      .hotBox2 {
+        width: calc(50% - 160px);
+        margin: 0 10px;
+      }
+
+      .hotBox3 {
+        width: calc(50% - 160px);
+      }
+    }
+  }
+}

+ 181 - 0
houtai/src/pages/A1Hot/index.tsx

@@ -0,0 +1,181 @@
+import { Select } from "antd";
+import React, { useCallback, useEffect, useState } from "react";
+import { CalendarOutlined } from "@ant-design/icons";
+import styles from "./index.module.scss";
+
+import * as echarts from "echarts/core";
+import {
+  ToolboxComponent,
+  LegendComponent,
+  TooltipComponent,
+} from "echarts/components";
+import { PieChart } from "echarts/charts";
+import { LabelLayout } from "echarts/features";
+import { CanvasRenderer } from "echarts/renderers";
+
+echarts.use([
+  ToolboxComponent,
+  TooltipComponent,
+  LegendComponent,
+  PieChart,
+  CanvasRenderer,
+  LabelLayout,
+]);
+
+function Hot() {
+  // 日期下拉框
+  const [select1, setSelect1] = useState<1 | 7 | 30 | "">(7);
+
+  // 热门场馆下拉
+  const [select2, setSelect2] = useState<"visit" | "star">("visit");
+
+  // 热门馆藏下拉
+  const [select3, setSelect3] = useState<"visit" | "star">("visit");
+
+  // 获取饼图的函数
+  const getEchartsFu = useCallback(
+    (
+      data: {
+        value: number;
+        name: string;
+      }[]
+    ) => {
+      const chartDom = document.getElementById("echarts1")!;
+      const myChart = echarts.init(chartDom);
+      const option = {
+        tooltip: {
+          trigger: "item",
+        },
+        legend: {
+          orient: "vertical",
+          left: "center",
+          bottom: "20%",
+          data,
+          formatter: (name: string) => {
+            let resName = "";
+            data.forEach((v) => {
+              if (name === v.name) {
+                if (name.length > 8)
+                  resName = name.slice(0, 8) + "... " + v.value;
+                else resName = name + " " + v.value;
+              }
+            });
+
+            return resName;
+          },
+        },
+        series: [
+          {
+            name: "",
+            type: "pie",
+            radius: [80, 120],
+            center: ["50%", "30%"],
+            // 设置圆角
+            itemStyle: {
+              borderRadius: 6,
+              borderColor: "#fff",
+              borderWidth: 1,
+            },
+            label: {
+              show: false,
+              position: "center",
+            },
+            emphasis: {
+              label: {
+                show: false,
+              },
+            },
+            labelLine: {
+              show: false,
+            },
+
+            data,
+          },
+        ],
+      };
+      option && myChart.setOption(option);
+    },
+    []
+  );
+
+  // 获取数据
+  const getInfoAPIFu = useCallback(() => {
+    const data = [
+      { value: 124, name: "新闻资讯", icon: "circle" },
+      { value: 100, name: "虚拟漫游", icon: "circle" },
+      { value: 200, name: "馆藏鉴赏", icon: "circle" },
+      { value: 80, name: "场馆导览", icon: "circle" },
+      { value: 70, name: "周边查询", icon: "circle" },
+      { value: 155, name: "游客服务", icon: "circle" },
+    ];
+    getEchartsFu(data);
+  }, [getEchartsFu]);
+
+  useEffect(() => {
+    getInfoAPIFu();
+  }, [getInfoAPIFu]);
+
+  return (
+    <div className={styles.Hot}>
+      <div className="pageTitle">热度统计</div>
+      <div className="hotTopTime">
+        <CalendarOutlined />
+        &emsp;
+        <Select
+          value={select1}
+          style={{ width: 100 }}
+          onChange={(e) => setSelect1(e)}
+          options={[
+            { value: 1, label: "今日" },
+            { value: 7, label: "近七天" },
+            { value: 30, label: "近三十天" },
+            { value: "", label: "全部" },
+          ]}
+        />
+      </div>
+      <div className="hotMain">
+        <div className="hotBox1">
+          <div className="hotBoxTit">热门板块</div>
+          {/* 第一个echarts盒子 */}
+          <div id="echarts1"></div>
+        </div>
+        <div className="hotBox2">
+          <div className="hotBoxTit">
+            热门场馆
+            <div className="hotBoxTitRight">
+              <Select
+                value={select2}
+                style={{ width: 100 }}
+                onChange={(e) => setSelect2(e)}
+                options={[
+                  { value: "visit", label: "按浏览" },
+                  { value: "star", label: "按点赞" },
+                ]}
+              />
+            </div>
+          </div>
+        </div>
+        <div className="hotBox3">
+          <div className="hotBoxTit">
+            热门馆藏
+            <div className="hotBoxTitRight">
+              <Select
+                value={select3}
+                style={{ width: 100 }}
+                onChange={(e) => setSelect3(e)}
+                options={[
+                  { value: "visit", label: "按浏览" },
+                  { value: "star", label: "按点赞" },
+                ]}
+              />
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+  );
+}
+
+const MemoHot = React.memo(Hot);
+
+export default MemoHot;

+ 5 - 0
houtai/src/pages/A2News/index.module.scss

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

+ 14 - 0
houtai/src/pages/A2News/index.tsx

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

+ 5 - 0
houtai/src/pages/A3Goods/index.module.scss

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

+ 14 - 0
houtai/src/pages/A3Goods/index.tsx

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

+ 5 - 0
houtai/src/pages/A4Venue/index.module.scss

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

+ 14 - 0
houtai/src/pages/A4Venue/index.tsx

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

+ 5 - 0
houtai/src/pages/A5Guest/index.module.scss

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

+ 14 - 0
houtai/src/pages/A5Guest/index.tsx

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

+ 197 - 0
houtai/src/pages/Layout/index.module.scss

@@ -0,0 +1,197 @@
+.Layout {
+  width: 100%;
+  height: 100%;
+  display: flex;
+
+  :global {
+
+    .layoutLeft {
+      position: relative;
+      z-index: 10;
+      width: 220px;
+      height: 100%;
+      box-shadow: 0px 0px 5px 3px;
+
+      .layoutLeftTop {
+        height: 60px;
+        background-color: var(--themeColor);
+        padding: 7px 0 0 20px;
+
+        &>h3 {
+          color: #fff;
+        }
+      }
+
+      .layoutLeftMain {
+        height: calc(100% - 60px);
+        padding: 20px;
+        background-image: url('../../assets/img/layoutLeftMain.jpg');
+        background-size: 100% 100%;
+
+        .mainBoxL2Row {
+          padding-left: 60px;
+          cursor: pointer;
+          height: 50px;
+          line-height: 50px;
+          font-size: 16px;
+          position: relative;
+          margin-bottom: 15px;
+
+          .tabImg {
+            z-index: 3;
+            position: absolute;
+            top: 50%;
+            left: 20px;
+            transform: translateY(-50%);
+            width: 20px;
+          }
+
+          .tabImgAc {
+            z-index: 3;
+            position: absolute;
+            top: 50%;
+            left: 20px;
+            transform: translateY(-50%);
+            width: 20px;
+            display: none;
+          }
+
+          &:hover {
+            color: #fff;
+            background-image: url('../../assets/img/activeLL.png');
+            background-size: 100% 100%;
+
+            .tabImgAc {
+              display: block;
+            }
+          }
+        }
+
+
+
+        .active {
+          color: #fff;
+          pointer-events: none;
+          background-image: url('../../assets/img/activeLL.png');
+          background-size: 100% 100%;
+
+          .tabImgAc {
+            display: block;
+          }
+        }
+      }
+
+
+    }
+
+    .layoutRight {
+      width: calc(100% - 220px);
+      height: 100%;
+
+
+      .layoutRightTop {
+        height: 60px;
+        background-image: url('../../assets/img/layoutTop.jpg');
+        background-size: 100% 100%;
+        display: flex;
+        justify-content: flex-end;
+        position: relative;
+        z-index: 11;
+
+        .user {
+          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: #fff;
+
+          .userInco {
+            margin-left: 10px;
+            color: #fff;
+          }
+
+          .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: #ccc;
+
+
+        .mainBoxR {
+          width: 100%;
+          height: 100%;
+          overflow: hidden;
+          &>div {
+            width: 100%;
+            height: 100%;
+          }
+        }
+      }
+
+    }
+
+
+
+  }
+}

+ 271 - 0
houtai/src/pages/Layout/index.tsx

@@ -0,0 +1,271 @@
+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 { useDispatch } from "react-redux";
+import inco1 from "@/assets/img/inco1.png";
+import inco2 from "@/assets/img/inco2.png";
+import inco3 from "@/assets/img/inco3.png";
+import inco4 from "@/assets/img/inco4.png";
+import inco1Ac from "@/assets/img/inco1Ac.png";
+import inco2Ac from "@/assets/img/inco2Ac.png";
+import inco3Ac from "@/assets/img/inco3Ac.png";
+import inco4Ac from "@/assets/img/inco4Ac.png";
+import { MessageFu } from "@/utils/message";
+
+const NotFound = React.lazy(() => import("@/components/NotFound"));
+
+function Layout() {
+  const dispatch = useDispatch();
+
+  const list = useMemo(() => {
+    return [
+      {
+        id: 100,
+        name: "热度统计",
+        path: "/",
+        Com: React.lazy(() => import("../A1Hot")),
+        inco: inco1,
+        incoAc: inco1Ac,
+      },
+      {
+        id: 200,
+        name: "资讯管理",
+        path: "/news",
+        Com: React.lazy(() => import("../A2News")),
+        inco: inco2,
+        incoAc: inco2Ac,
+      },
+      {
+        id: 300,
+        name: "馆藏管理",
+        path: "/goods",
+        Com: React.lazy(() => import("../A3Goods")),
+        inco: inco3,
+        incoAc: inco3Ac,
+      },
+      {
+        id: 400,
+        name: "场馆管理",
+        path: "/venue",
+        Com: React.lazy(() => import("../A4Venue")),
+        inco: inco4,
+        incoAc: inco4Ac,
+      },
+      {
+        id: 500,
+        name: "游客服务",
+        path: "/guest",
+        Com: React.lazy(() => import("../A5Guest")),
+        inco: inco2,
+        incoAc: inco2Ac,
+      },
+    ];
+  }, []);
+
+  // 进页面获取所有下拉信息
+  useEffect(() => {
+    // dispatch(getDictListAPI());
+  }, [dispatch]);
+
+  // 点击跳转
+  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
+              key={v.id}
+              onClick={() => pathCutFu(v.path)}
+              className={classNames(
+                "mainBoxL2Row",
+                v.path === path ? "active" : ""
+              )}
+            >
+              <img className="tabImg" src={v.inco} alt="" />
+              <img className="tabImgAc" src={v.incoAc} alt="" />
+              <div className="txt">{v.name}</div>
+            </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
+          name="basic"
+          labelCol={{ span: 5 }}
+          wrapperCol={{ span: 16 }}
+          onFinish={onFinish}
+          autoComplete="off"
+        >
+          <Form.Item
+            label="旧密码"
+            name="oldPassword"
+            rules={[{ required: true, message: "不能为空!" }]}
+          >
+            <Input.Password maxLength={15} />
+          </Form.Item>
+
+          <Form.Item
+            label="新密码"
+            name="newPassword"
+            rules={[{ required: true, message: "不能为空!" }]}
+          >
+            <Input.Password
+              maxLength={15}
+              onChange={(e) => (oldPasswordValue.current = e.target.value)}
+            />
+          </Form.Item>
+
+          <Form.Item
+            label="确定新密码"
+            name="checkPass"
+            rules={[{ validator: checkPassWord }]}
+          >
+            <Input.Password maxLength={15} />
+          </Form.Item>
+
+          <Form.Item wrapperCol={{ offset: 14, span: 16 }}>
+            <Button onClick={() => setOpen(false)}>取消</Button>
+            &emsp;
+            <Button type="primary" htmlType="submit">
+              确定
+            </Button>
+          </Form.Item>
+        </Form>
+      </Modal>
+    </div>
+  );
+}
+
+// 使用 React.memo 来优化组件,避免组件的无效更新,类似 类组件里面的PureComponent
+const MemoLayout = React.memo(Layout);
+export default MemoLayout;

+ 135 - 0
houtai/src/pages/Login/index.module.scss

@@ -0,0 +1,135 @@
+.Login {
+  width: 100%;
+  height: 100%;
+  background-image: url('../../assets/img/bg.jpg');
+  background-size: cover;
+  position: relative;
+
+  :global {
+
+    .main {
+      border-top: 3px solid rgba(200, 185, 146, .8);
+      border-radius: 6px;
+      position: absolute;
+      top: 50%;
+      right: 240px;
+      transform: translateY(-50%);
+      width: 530px;
+      height: 720px;
+      background-color: rgba(200, 185, 146, 0.40);
+      padding-top: 60px;
+      text-align: center;
+      backdrop-filter: blur(10px);
+
+      .logo {
+        margin: 0 auto;
+        width: 375px;
+        height: 233px;
+        background-image: url('../../assets/img/logo.png');
+        background-size: 100% 100%;
+      }
+
+
+      .inputBox {
+        width: 100%;
+
+        input::-webkit-input-placeholder {
+          /* WebKit browsers */
+          color: rgba(255, 255, 255, .5);
+        }
+
+        input:-moz-placeholder {
+          /* Mozilla Firefox 4 to 18 */
+          color: rgba(255, 255, 255, .5);
+        }
+
+        input::-moz-placeholder {
+          /* Mozilla Firefox 19+ */
+          color: rgba(255, 255, 255, .5);
+        }
+
+        input:-ms-input-placeholder {
+          /* Internet Explorer 10+ */
+          color: rgba(255, 255, 255, .5);
+        }
+
+
+        .inputBoxRow {
+          width: 370px;
+          margin: 50px auto;
+
+          .ant-input-suffix .ant-input-password-icon {
+            color: var(--themeColor);
+            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: #fff !important;
+          background-image: none;
+          -webkit-box-shadow: 0 0 0px 1000px transparent inset !important; //填充阴影,可以用来遮住背景色
+          background-color: transparent;
+          transition: background-color 50000s ease-in-out 0s; //背景色透明  生效时长  过渡效果  启用时延迟的时间
+
+        }
+
+        .ant-input-affix-wrapper {
+          background-color: transparent;
+          padding: 0 11px;
+          width: 100%;
+          height: 60px;
+          border: none;
+          border-bottom: 1px solid var(--themeColor);
+          border-radius: 0;
+          color: var(--themeColor);
+
+          .ant-input {
+            background-color: transparent;
+            width: 100%;
+            height: 60px;
+            color: #fff;
+          }
+        }
+
+        .ant-input-affix-wrapper-focused {
+          box-shadow: none
+        }
+      }
+
+      .loginBtn {
+
+        .ant-btn {
+          border-radius: 25px;
+          font-size: 24px;
+          width: 375px;
+          height: 50px;
+        }
+      }
+
+    }
+  }
+}

+ 77 - 0
houtai/src/pages/Login/index.tsx

@@ -0,0 +1,77 @@
+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("/");
+    }
+  };
+
+  return (
+    <div className={styles.Login}>
+      <div className="main">
+        <div className="logo"></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>
+  );
+}

+ 15 - 0
houtai/src/store/action/layout.ts

@@ -0,0 +1,15 @@
+import http from "@/utils/http";
+
+/**
+ * 用户登录接口
+ */
+export const userLoginAPI = (data: any) => {
+  return http.post("admin/login", { ...data });
+};
+
+/**
+ * 修改密码接口
+ */
+export const passWordEditAPI = (data: any) => {
+  return http.post("sys/user/updatePwd", { ...data });
+};

+ 20 - 0
houtai/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

+ 13 - 0
houtai/src/store/reducer/index.ts

@@ -0,0 +1,13 @@
+// 导入合并reducer的依赖
+import { combineReducers } from 'redux'
+
+// 导入 登录 模块的 reducer
+import layoutReducer from './layout'
+
+// 合并 reducer
+const rootReducer = combineReducers({
+  layoutStore: layoutReducer,
+})
+
+// 默认导出
+export default rootReducer

+ 58 - 0
houtai/src/store/reducer/layout.ts

@@ -0,0 +1,58 @@
+import { MessageType } from "@/utils/message";
+
+// 初始化状态
+const initState: any = {
+  // 所有图片点击预览查看大图
+  lookBigImg: {
+    url: "",
+    show: false,
+  },
+  // 视频的src
+  videoSrc: "",
+  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/lookVideo"; payload: string }
+  | { 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/lookVideo":
+      return { ...state, videoSrc: action.payload };
+    // antd轻提示(兼容360浏览器)
+    case "layout/message":
+      return { ...state, message: action.payload };
+    // 上传文件点击取消
+    case "layout/closeUpFile":
+      return { ...state, closeUpFile: action.payload };
+    default:
+      return state;
+  }
+}

+ 4 - 0
houtai/src/types/api/layot.d.ts

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

+ 6 - 0
houtai/src/types/declaration.d.ts

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

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

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

+ 18 - 0
houtai/src/utils/domShow.ts

@@ -0,0 +1,18 @@
+// 加载和上传的盒子的显示隐藏
+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;
+};

+ 17 - 0
houtai/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 {};
+};

+ 100 - 0
houtai/src/utils/http.ts

@@ -0,0 +1,100 @@
+import axios from "axios";
+import history from "./history";
+import { getTokenInfo, removeTokenInfo } from "./storage";
+import store from "@/store";
+import { MessageFu } from "./message";
+import { domShowFu, progressDomFu } from "./domShow";
+// 请求基地址
+export const baseURL =
+  // 线下的图片地址需要加上/api/
+  process.env.NODE_ENV === "development"
+    ? "http://192.168.20.55:8041/api/"
+    : "";
+// process.env.NODE_ENV === "development" ? "https://xuzhouwall.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("用户名不存在或密码错误,请联系管理员!");
+    else 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().layoutStore.closeUpFile.state)
+          MessageFu.warning("网络超时或中止上传!");
+        else MessageFu.error("网络繁忙,请稍后重试!");
+      } else {
+        MessageFu.error("响应错误,请联系管理员!");
+      }
+    }, 100);
+
+    // 响应错误也要取消 上传文件的进度条
+    domShowFu("#UpAsyncLoding", false);
+    progressDomFu("0%");
+
+    return Promise.reject(err);
+  }
+);
+
+// 导出 axios 实例
+export default http;

+ 50 - 0
houtai/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
houtai/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
houtai/src/utils/storage.ts

@@ -0,0 +1,34 @@
+// ------------------------------------token的本地存储------------------------------------
+
+// 用户 Token 的本地缓存键名,自己定义
+const TOKEN_KEY = 'BBSBWG_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
houtai/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"
+  ]
+}