lanxin пре 2 недеља
комит
60ff5eb00f
100 измењених фајлова са 11160 додато и 0 уклоњено
  1. 12 0
      .editorconfig
  2. 23 0
      .gitignore
  3. 11 0
      .prettierrc.js
  4. 2 0
      README.md
  5. 21 0
      index.html
  6. 7637 0
      package-lock.json
  7. 68 0
      package.json
  8. 8 0
      path.tsconfig.json
  9. 96 0
      src/App.tsx
  10. BIN
      src/assets/img/2dBg.jpg
  11. BIN
      src/assets/img/3dBg.jpg
  12. BIN
      src/assets/img/3dBg_M.jpg
  13. BIN
      src/assets/img/3dDescBg.jpg
  14. BIN
      src/assets/img/3dDescBg_M.jpg
  15. BIN
      src/assets/img/IMGerror.png
  16. BIN
      src/assets/img/LOGO.png
  17. BIN
      src/assets/img/btn_bg.png
  18. BIN
      src/assets/img/changguan.png
  19. BIN
      src/assets/img/closeBtn.png
  20. BIN
      src/assets/img/detailPannelBg.png
  21. BIN
      src/assets/img/detailPannelBg_M.png
  22. BIN
      src/assets/img/dsiderBg.png
  23. BIN
      src/assets/img/homeBtn.png
  24. BIN
      src/assets/img/home_bg.jpg
  25. BIN
      src/assets/img/home_bg_M.png
  26. BIN
      src/assets/img/introBg.png
  27. BIN
      src/assets/img/introBg_M.png
  28. BIN
      src/assets/img/introBtn.png
  29. BIN
      src/assets/img/landtip.png
  30. BIN
      src/assets/img/layout_bg.jpg
  31. BIN
      src/assets/img/layout_bg_M.jpg
  32. BIN
      src/assets/img/loading.gif
  33. BIN
      src/assets/img/music.png
  34. BIN
      src/assets/img/searchIcon.png
  35. BIN
      src/assets/img/siderBar_bg.png
  36. BIN
      src/assets/img/siderBtnBg.png
  37. BIN
      src/assets/img/siderBtnBgAc.png
  38. BIN
      src/assets/img/siderBtnBgAc_M.png
  39. BIN
      src/assets/img/siderBtnBg_M.png
  40. BIN
      src/assets/img/title.png
  41. BIN
      src/assets/img/title_M.png
  42. BIN
      src/assets/img/wenwu.png
  43. 225 0
      src/assets/styles/base.css
  44. 342 0
      src/assets/styles/base.less
  45. 21 0
      src/components/AsyncSpinLoding/index.module.scss
  46. 15 0
      src/components/AsyncSpinLoding/index.tsx
  47. 51 0
      src/components/ImageLazy/index.module.scss
  48. 63 0
      src/components/ImageLazy/index.tsx
  49. 65 0
      src/components/LookDom/index.module.scss
  50. 52 0
      src/components/LookDom/index.tsx
  51. 29 0
      src/components/Message/index.tsx
  52. 26 0
      src/components/NotFound/index.tsx
  53. 10 0
      src/components/SpinLoding/index.module.scss
  54. 13 0
      src/components/SpinLoding/index.tsx
  55. 43 0
      src/components/UpAsyncLoding/index.module.scss
  56. 38 0
      src/components/UpAsyncLoding/index.tsx
  57. 38 0
      src/index.tsx
  58. 50 0
      src/pages/A1home/index.module.scss
  59. 22 0
      src/pages/A1home/index.tsx
  60. 49 0
      src/pages/A2layout/index.module.scss
  61. 24 0
      src/pages/A2layout/index.tsx
  62. 51 0
      src/pages/A2layoutM/index.module.scss
  63. 27 0
      src/pages/A2layoutM/index.tsx
  64. 212 0
      src/pages/A3detailPannel/index.module.scss
  65. 117 0
      src/pages/A3detailPannel/index.tsx
  66. 197 0
      src/pages/A3detailPannelM/index.module.scss
  67. 134 0
      src/pages/A3detailPannelM/index.tsx
  68. 56 0
      src/pages/A4IntroPanel/index.module.scss
  69. 21 0
      src/pages/A4IntroPanel/index.tsx
  70. 59 0
      src/pages/A4IntroPanelM/index.module.scss
  71. 24 0
      src/pages/A4IntroPanelM/index.tsx
  72. 70 0
      src/pages/A5Intro2D/index.module.scss
  73. 42 0
      src/pages/A5Intro2D/index.tsx
  74. 70 0
      src/pages/A5Intro2DM/index.module.scss
  75. 39 0
      src/pages/A5Intro2DM/index.tsx
  76. 124 0
      src/pages/A6Intro3D/index.module.scss
  77. 50 0
      src/pages/A6Intro3D/index.tsx
  78. 126 0
      src/pages/A6Intro3DM/index.module.scss
  79. 46 0
      src/pages/A6Intro3DM/index.tsx
  80. 12 0
      src/pages/components/LOGOM/index.module.scss
  81. 15 0
      src/pages/components/LOGOM/index.tsx
  82. 5 0
      src/pages/初始化组件/index.module.scss
  83. 14 0
      src/pages/初始化组件/index.tsx
  84. 30 0
      src/store/action/A1list.ts
  85. 28 0
      src/store/action/layout.ts
  86. 20 0
      src/store/index.ts
  87. 26 0
      src/store/reducer/A1list.ts
  88. 15 0
      src/store/reducer/index.ts
  89. 65 0
      src/store/reducer/layout.ts
  90. 31 0
      src/types/api/layot.d.ts
  91. 26 0
      src/types/declaration.d.ts
  92. 1 0
      src/types/index.d.ts
  93. 35 0
      src/utils/domShow.ts
  94. 16 0
      src/utils/history.ts
  95. 109 0
      src/utils/http.ts
  96. 3 0
      src/utils/isMobile.ts
  97. 50 0
      src/utils/message.ts
  98. 100 0
      src/utils/pass.ts
  99. 40 0
      src/utils/storage.ts
  100. 0 0
      src/utils/tableData.ts

+ 12 - 0
.editorconfig

@@ -0,0 +1,12 @@
+root = true # 控制配置文件 .editorconfig 是否生效的字段
+ 
+[**] # 匹配全部文件
+indent_style = space # 缩进风格,可选space|tab
+indent_size = 2 # 缩进的空格数
+charset = utf-8 # 设置字符集
+trim_trailing_whitespace = true # 删除一行中的前后空格
+insert_final_newline = true # 设为true表示使文件以一个空白行结尾
+end_of_line = lf
+ 
+[**.md] # 匹配md文件
+trim_trailing_whitespace = false

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

+ 11 - 0
.prettierrc.js

@@ -0,0 +1,11 @@
+module.exports = {
+  printWidth: 150, // 一行的字符数,如果超过会进行换行
+  tabWidth: 2, // 一个tab代表几个空格数,默认就是2
+  useTabs: false, // 是否启用tab取代空格符缩进,.editorconfig设置空格缩进,所以设置为false
+  semi: false, // 行尾是否使用分号,默认为true
+  singleQuote: true, // 字符串是否使用单引号
+  trailingComma: 'none', // 对象或数组末尾是否添加逗号 none| es5| all
+  jsxSingleQuote: true, // 在jsx里是否使用单引号,你看着办
+  bracketSpacing: true, // 对象大括号直接是否有空格,默认为true,效果:{ foo: bar }
+  arrowParens: 'avoid' // 箭头函数如果只有一个参数则省略括号
+}

+ 2 - 0
README.md

@@ -0,0 +1,2 @@
+1.使用 yarn。 npm有问题
+

+ 21 - 0
index.html

@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html lang="zh">
+  <head>
+    <meta charset="utf-8" />
+    <meta name="viewport" content="width=device-width, initial-scale=1" />
+    <meta name="theme-color" content="#000000" />
+    <meta name="description" content="Web site created using Vite + React" />
+    <meta
+      name="viewport"
+      content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no"
+    />
+    <link rel="apple-touch-icon" href="./logo192.png" />
+    <script src="./static/myData/data.js"></script>
+    <title>集宁博物馆</title>
+  </head>
+  <body>
+    <noscript>You need to enable JavaScript to run this app.</noscript>
+    <div id="root"></div>
+    <script type="module" src="./src/index.tsx"></script>
+  </body>
+</html>

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


+ 68 - 0
package.json

@@ -0,0 +1,68 @@
+{
+  "name": "demo2",
+  "version": "0.1.0",
+  "private": true,
+  "dependencies": {
+    "@amap/amap-jsapi-loader": "^1.0.1",
+    "@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",
+    "@zxing/library": "^0.21.3",
+    "antd": "^5.8.3",
+    "antd-mobile": "^5.30.0",
+    "axios": "^1.1.3",
+    "braft-editor": "^2.3.9",
+    "braft-utils": "^3.0.12",
+    "dayjs": "^1.11.10",
+    "js-base64": "^3.7.3",
+    "react": "^18.2.0",
+    "react-countup": "^6.5.3",
+    "react-dom": "^18.2.0",
+    "react-redux": "^8.0.4",
+    "react-router-dom": "^6.22.0",
+    "react-sortablejs": "^6.1.4",
+    "redux": "^4.2.0",
+    "redux-devtools-extension": "^2.13.9",
+    "redux-thunk": "^2.4.1",
+    "sass": "^1.69.5",
+    "typescript": "^4.8.4",
+    "web-vitals": "^2.1.4"
+  },
+  "scripts": {
+    "dev": "vite --host",
+    "build": "vite build",
+    "preview": "vite preview",
+    "test": "echo \"No test runner configured\" && exit 0"
+  },
+  "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",
+    "query-string": "^8.1.0",
+    "vite": "^4.5.0",
+    "@vitejs/plugin-react": "^3.1.0"
+  },
+  "homepage": "."
+}

+ 8 - 0
path.tsconfig.json

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

+ 96 - 0
src/App.tsx

@@ -0,0 +1,96 @@
+import '@/assets/styles/base.css'
+// 关于路由
+import React, { useCallback, useEffect } from 'react'
+import { HashRouter, Routes, Route } from 'react-router-dom'
+import { isMobileFu } from './utils/history'
+import SpinLoding from './components/SpinLoding'
+import AsyncSpinLoding from './components/AsyncSpinLoding'
+
+import UpAsyncLoding from './components/UpAsyncLoding'
+import MessageCom from './components/Message'
+
+import screenImg from '@/assets/img/landtip.png'
+
+const A1home = React.lazy(() => import('./pages/A1home'))
+const A2layout = React.lazy(() => import('./pages/A2layout'))
+const A2layoutM = React.lazy(() => import('./pages/A2layoutM'))
+const A3detailPannel = React.lazy(() => import('./pages/A3detailPannel'))
+const A3detailPannelM = React.lazy(() => import('./pages/A3detailPannelM'))
+const A4IntroPanel = React.lazy(() => import('./pages/A4IntroPanel'))
+const A4IntroPanelM = React.lazy(() => import('./pages/A4IntroPanelM'))
+const A5Intro2D = React.lazy(() => import('./pages/A5Intro2D'))
+const A5Intro2DM = React.lazy(() => import('./pages/A5Intro2DM'))
+const A6Intro3D = React.lazy(() => import('./pages/A6Intro3D'))
+const A6Intro3DM = React.lazy(() => import('./pages/A6Intro3DM'))
+
+
+
+export default function App() {
+  // 从仓库中获取查看图片的信息
+  // const lookBigImg = useSelector((state: RootState) => state.A0Layout.lookBigImg)
+
+  const rootDomFu = useCallback(() => {
+    const rootDom: HTMLDivElement = document.querySelector('#root')!
+    if (rootDom) {
+      rootDom.style.height = window.innerHeight + 'px'
+    }
+  }, [])
+
+  useEffect(() => {
+    window.addEventListener('resize', rootDomFu, true)
+    rootDomFu()
+  }, [rootDomFu])
+
+  return (
+    <>
+      {/* 关于路由 */}
+      <HashRouter>
+        <React.Suspense fallback={<SpinLoding />}>
+          <Routes>
+            <Route path='/' element={<A1home />} />
+            <Route path='/layout' element={isMobileFu() ? <A2layoutM /> : <A2layout />} />
+            <Route path='/detailP' element={isMobileFu() ? <A3detailPannelM /> : <A3detailPannel />} />
+            <Route path='/introP' element={isMobileFu() ? <A4IntroPanelM /> : <A4IntroPanel />} />
+            <Route path='/intro2D' element={isMobileFu() ? <A5Intro2DM /> : <A5Intro2D />} />
+            <Route path='/intro3D' element={isMobileFu() ? <A6Intro3DM /> : <A6Intro3D />} />
+          </Routes>
+        </React.Suspense>
+      </HashRouter>
+
+      {/* 发送请求的加载组件 */}
+      <AsyncSpinLoding />
+
+      {/* 所有图片点击预览查看大图 */}
+      {/* <Image
+        preview={{
+          visible: lookBigImg.show,
+          src: lookBigImg.url,
+          onVisibleChange: value => {
+            // 清除仓库信息
+            store.dispatch({
+              type: 'layout/lookBigImg',
+              payload: { url: '', show: false }
+            })
+          }
+        }}
+      /> */}
+
+      {/* 上传附件的进度条元素 */}
+      <UpAsyncLoding />
+
+      {/* 查看视频音频 */}
+      {/* <LookDom /> */}
+
+      {/* antd 轻提示 ---兼容360浏览器 */}
+      <MessageCom />
+
+      {/* 横屏提示 */}
+      {isMobileFu() ? (
+        <div id='ScreenChange'>
+          <img src={screenImg} alt='' />
+          <p>请在竖屏模式浏览</p>
+        </div>
+      ) : null}
+    </>
+  )
+}

BIN
src/assets/img/2dBg.jpg


BIN
src/assets/img/3dBg.jpg


BIN
src/assets/img/3dBg_M.jpg


BIN
src/assets/img/3dDescBg.jpg


BIN
src/assets/img/3dDescBg_M.jpg


BIN
src/assets/img/IMGerror.png


BIN
src/assets/img/LOGO.png


BIN
src/assets/img/btn_bg.png


BIN
src/assets/img/changguan.png


BIN
src/assets/img/closeBtn.png


BIN
src/assets/img/detailPannelBg.png


BIN
src/assets/img/detailPannelBg_M.png


BIN
src/assets/img/dsiderBg.png


BIN
src/assets/img/homeBtn.png


BIN
src/assets/img/home_bg.jpg


BIN
src/assets/img/home_bg_M.png


BIN
src/assets/img/introBg.png


BIN
src/assets/img/introBg_M.png


BIN
src/assets/img/introBtn.png


BIN
src/assets/img/landtip.png


BIN
src/assets/img/layout_bg.jpg


BIN
src/assets/img/layout_bg_M.jpg


BIN
src/assets/img/loading.gif


BIN
src/assets/img/music.png


BIN
src/assets/img/searchIcon.png


BIN
src/assets/img/siderBar_bg.png


BIN
src/assets/img/siderBtnBg.png


BIN
src/assets/img/siderBtnBgAc.png


BIN
src/assets/img/siderBtnBgAc_M.png


BIN
src/assets/img/siderBtnBg_M.png


BIN
src/assets/img/title.png


BIN
src/assets/img/title_M.png


BIN
src/assets/img/wenwu.png


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

@@ -0,0 +1,225 @@
+* {
+  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;
+  font-family: Adobe Heiti Std R !important;
+}
+i {
+  font-style: normal;
+}
+img {
+  max-width: 100%;
+  max-height: 100%;
+  vertical-align: middle;
+}
+ul {
+  list-style: none;
+}
+body {
+  overflow: auto;
+  overflow-y: overlay;
+}
+/* 文本域取消下拉 */
+textarea {
+  resize: none !important;
+  min-height: 100px !important;
+}
+/* 主题色 */
+:root {
+  --themeColor: #042b8f;
+  --themeColor2: #69c690;
+}
+/* 找不到页面 */
+.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);
+}
+.ant-popconfirm {
+  width: 240px;
+}
+#root {
+  margin: 0 auto;
+  width: 100vw;
+  height: 100vh;
+  overflow: auto;
+  overflow-y: overlay;
+  /* 普通文字按钮的颜色 */
+  /* 按钮的危险颜色 */
+  /* antd分页器样式 */
+  /* 表格的图片居中 */
+  /* antd图片预览组件 */
+  /* antd表格居中 */
+}
+#root > div {
+  width: 100%;
+  height: 100%;
+}
+#root a {
+  text-decoration: none;
+  color: rgba(196, 38, 29, 1);
+  outline: none;
+}
+#root .iconHoverTit {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+#root .iconHoverTitTxt {
+  background-color: var(--themeColor);
+  color: #fff;
+  width: 16px;
+  height: 16px;
+  line-height: 16px;
+  text-align: center;
+  font-size: 12px;
+  border-radius: 50%;
+}
+#root .ant-btn-text {
+  color: var(--themeColor);
+}
+#root .ant-btn-text:disabled {
+  cursor: not-allowed;
+  color: rgba(0, 0, 0, 0.25);
+}
+#root .ant-btn-text.ant-btn-dangerous {
+  color: #ff4d4d;
+}
+#root .tableImgAuto {
+  display: flex;
+  justify-content: center;
+}
+#root .ant-image {
+  display: none;
+}
+#root .ant-table-cell {
+  text-align: center !important;
+}
+#root #A2Table3 .ant-table-row-expand-icon {
+  background-color: var(--themeColor);
+  color: #fff;
+}
+#root #A2Table3 .ant-table-cell-with-append {
+  display: flex;
+  justify-content: flex-start;
+}
+[hidden] {
+  display: none !important;
+}
+#upInput {
+  display: none;
+}
+#upInput2 {
+  display: none;
+}
+#upInputAudio {
+  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;
+}
+.ant-image-preview-operations {
+  background-color: rgba(0, 0, 0, 0.8) !important;
+}
+.ant-image-preview-mask {
+  z-index: 9999 !important;
+}
+.ant-image-preview-wrap {
+  z-index: 9999 !important;
+}
+.ant-image-preview-operations-wrapper {
+  z-index: 9999 !important;
+}
+.ant-notification-notice {
+  max-height: 500px !important;
+  overflow-y: auto !important;
+}
+.ant-picker-dropdown {
+  text-align: center;
+}
+.ant-picker-selection-item-remove {
+  display: none !important;
+}
+#ScreenChange {
+  position: fixed;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  z-index: 10000;
+  background-color: rgba(0, 0, 0, 0.8);
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+  align-items: center;
+  opacity: 0;
+  pointer-events: none;
+  transition: all 0.5s;
+}
+#ScreenChange > img {
+  width: 200px;
+}
+#ScreenChange > p {
+  margin-top: 20px;
+  color: #fff;
+  font-size: 18px;
+  height: 40px;
+}
+/*横屏*/
+@media screen and (orientation: landscape) {
+  #ScreenChange {
+    opacity: 1;
+    pointer-events: auto;
+  }
+}

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

@@ -0,0 +1,342 @@
+* {
+  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;
+}
+
+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: #69c790;
+  --themeColor: #042b8f;
+  --themeColor2: #69c690;
+}
+
+/* 找不到页面 */
+.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);
+}
+
+// 气泡框闪烁问题
+.ant-popconfirm {
+  width: 240px;
+}
+
+// 重置antd样式
+#root {
+  // max-width: 500px;
+  margin: 0 auto;
+  width: 100vw;
+  height: 100vh;
+  overflow: auto;
+  overflow-y: overlay;
+
+  & > div {
+    width: 100%;
+    height: 100%;
+  }
+
+  a {
+    text-decoration: none;
+    color: black;
+    outline: none;
+  }
+
+  // ?的提示
+  .iconHoverTit {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+  }
+
+  .iconHoverTitTxt {
+    background-color: var(--themeColor);
+    color: #fff;
+    width: 16px;
+    height: 16px;
+    line-height: 16px;
+    text-align: center;
+    font-size: 12px;
+    border-radius: 50%;
+  }
+
+  // a {
+  //   color: var(--themeColor);
+  // }
+
+  /* 普通文字按钮的颜色 */
+  .ant-btn-text {
+    color: var(--themeColor);
+  }
+
+  .ant-btn-text:disabled {
+    cursor: not-allowed;
+    color: rgba(0, 0, 0, 0.25);
+  }
+
+  /* 按钮的危险颜色 */
+  .ant-btn-text.ant-btn-dangerous {
+    color: #ff4d4d;
+  }
+
+  /* antd分页器样式 */
+  // .ant-pagination .ant-pagination-item {
+  //   border-radius: 50%;
+  //   border: 1px solid #999;
+  //   background-color: transparent !important;
+  // }
+
+  // .ant-pagination .ant-pagination-item-active {
+  //   background-color: var(--themeColor) !important;
+  // }
+
+  // .ant-pagination .ant-pagination-item-active a {
+  //   color: #fff !important;
+  // }
+
+  // .ant-pagination .ant-pagination-item:hover {
+  //   background-color: var(--themeColor) !important;
+  // }
+
+  // .ant-pagination .ant-pagination-item:hover a {
+  //   color: #fff !important;
+  // }
+
+  // .ant-pagination-prev {
+  //   border-radius: 50% !important;
+  //   border: 1px solid #999;
+  // }
+
+  // .ant-pagination-prev:hover {
+  //   background-color: var(--themeColor);
+  // }
+
+  // .ant-pagination-prev:hover button {
+  //   color: #fff;
+  // }
+
+  // .ant-pagination-next {
+  //   border-radius: 50% !important;
+  //   border: 1px solid #999;
+  // }
+
+  // .ant-pagination-next:hover {
+  //   background-color: var(--themeColor);
+  // }
+
+  // .ant-pagination-next:hover button {
+  //   color: #fff;
+  // }
+
+  // .ant-pagination-disabled {
+  //   border: 1px solid #ccc;
+  // }
+
+  // .ant-pagination-disabled:hover {
+  //   background-color: transparent;
+  // }
+
+  /* 表格的图片居中 */
+  .tableImgAuto {
+    display: flex;
+    justify-content: center;
+  }
+
+  /* antd图片预览组件 */
+  .ant-image {
+    display: none;
+  }
+
+  /* antd表格居中 */
+
+  .ant-table-cell {
+    text-align: center !important;
+  }
+
+  // 树型 表格 定制化
+  #A2Table3 {
+    .ant-table-row-expand-icon {
+      background-color: var(--themeColor);
+      color: #fff;
+    }
+
+    .ant-table-cell-with-append {
+      display: flex;
+      justify-content: flex-start;
+    }
+  }
+}
+
+[hidden] {
+  display: none !important;
+}
+
+#upInput {
+  display: none;
+}
+
+#upInput2 {
+  display: none;
+}
+
+#upInputAudio {
+  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;
+}
+.ant-image-preview-operations {
+  background-color: rgba(0, 0, 0, 0.8) !important;
+}
+.ant-image-preview-mask {
+  z-index: 9999 !important;
+}
+.ant-image-preview-wrap {
+  z-index: 9999 !important;
+}
+.ant-image-preview-operations-wrapper {
+  z-index: 9999 !important;
+}
+
+.ant-notification-notice {
+  max-height: 500px !important;
+  overflow-y: auto !important;
+}
+
+// 多日期选择器居中 挡住 按钮(即必须失焦才能点击确定)
+.ant-picker-dropdown {
+  text-align: center;
+}
+.ant-picker-selection-item-remove {
+  display: none !important;
+}
+// .ant-picker-year-btn,
+// .ant-picker-header-super-prev-btn,
+// .ant-picker-header-super-next-btn {
+//   display: none;
+// }
+
+// 横屏 竖屏的切换
+#ScreenChange {
+  position: fixed;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  z-index: 10000;
+  background-color: rgba(0, 0, 0, 0.8);
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+  align-items: center;
+  opacity: 0;
+  pointer-events: none;
+  transition: all 0.5s;
+
+  & > img {
+    width: 200px;
+  }
+
+  & > p {
+    margin-top: 20px;
+    color: #fff;
+    font-size: 18px;
+    height: 40px;
+  }
+}
+
+/*横屏*/
+@media screen and (orientation: landscape) {
+  #ScreenChange {
+    opacity: 1;
+    pointer-events: auto;
+  }
+}

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

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

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

@@ -0,0 +1,63 @@
+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
+  srcBig?: string
+  noLook?: boolean
+  offline?: boolean
+}
+
+function ImageLazy({ width = 100, height = 100, src, srcBig, 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 : srcBig ? baseURL + srcBig : baseURL + src, show: true }
+    })
+  }, [offline, src, srcBig])
+
+  return (
+    <div className={styles.ImageLazy} style={{ width: width, height: height }}>
+      <div className='lazyBox'>
+        <Image
+          lazy
+          onLoad={onLoad}
+          src={src ? (offline ? src : baseURL + src) : ''}
+          placeholder={<img src={imgLoding} alt='' />}
+          fallback={<img src={imgErr} alt='' />}
+          fit='cover'
+        />
+
+        {/* 图片预览 */}
+        {noLook || !lookImg ? null : (
+          <div className='lookImg' onClick={lookBigImg}>
+            <EyeOutlined rev={undefined} />
+            &nbsp;
+            <div>预览</div>
+          </div>
+        )}
+      </div>
+    </div>
+  )
+}
+
+const MemoImageLazy = React.memo(ImageLazy)
+
+export default MemoImageLazy

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

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

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

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

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

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

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

@@ -0,0 +1,26 @@
+import { Result } from "antd";
+import { useEffect, useRef } from "react";
+
+export default function NotFound() {
+  const timeRef = useRef(-1);
+
+  useEffect(() => {
+    timeRef.current = window.setTimeout(() => {
+      const dom: HTMLDivElement = document.querySelector(".noFindPage")!;
+      dom.style.opacity = "1";
+    }, 500);
+    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;

+ 38 - 0
src/index.tsx

@@ -0,0 +1,38 @@
+// 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: '#042b8f'
+      }
+    }}
+  >
+    <Provider store={store}>
+      <StyleProvider
+        hashPriority='high'
+        transformers={[legacyLogicalPropertiesTransformer]}
+      >
+        <App />
+      </StyleProvider>
+    </Provider>
+  </ConfigProvider>
+)

+ 50 - 0
src/pages/A1home/index.module.scss

@@ -0,0 +1,50 @@
+.A1home {
+  position: relative;
+  background: url('../../assets/img/home_bg.jpg') no-repeat center center;
+  background-size: 100% 100%;
+  :global {
+    .title {
+      position: absolute;
+      top: 20%;
+      left: 50%;
+      transform: translate(-50%, -46%);
+      width: 52%;
+      height: 200px;
+      background: url('../../assets/img/title.png') no-repeat center center;
+      background-size: contain;
+    }
+    .btn {
+      position: absolute;
+      top: 50%;
+      left: 50%;
+      transform: translate(-50%, 476%);
+      width: 326px;
+      height: 76px;
+      background: url('../../assets/img/btn_bg.png') no-repeat center center;
+      background-size: contain;
+    }
+  }
+}
+
+@media screen and (max-width: 768px) {
+  .A1home {
+    background: url('../../assets/img/home_bg_M.png') no-repeat center center;
+    background-size: 100% 100%;
+    :global {
+      .title {
+        top: 27%;
+        width: 95%;
+        height: 153px;
+        background: url('../../assets/img/title_M.png') no-repeat center center;
+        background-size: 100% 100%;
+      }
+      .btn {
+        width: 200px;
+        height: 66px;
+        transform: translate(-50%, 0);
+        top: auto;
+        bottom: 20px;
+      }
+    }
+  }
+}

+ 22 - 0
src/pages/A1home/index.tsx

@@ -0,0 +1,22 @@
+import React from 'react'
+import styles from './index.module.scss'
+import { useNavigate } from 'react-router-dom'
+import Logo from '@/pages/components/LOGOM'
+import { isMobileFu } from '@/utils/history'
+
+function A1home() {
+  const navigate = useNavigate()
+  return (
+    <div className={styles.A1home} >
+      {isMobileFu() && <Logo />}
+      <div className="title"></div>
+      <div className="btn" onClick={() => {
+        navigate('/layout')
+      }}></div>
+    </div>
+  )
+}
+
+const MemoA1home = React.memo(A1home)
+
+export default MemoA1home

+ 49 - 0
src/pages/A2layout/index.module.scss

@@ -0,0 +1,49 @@
+.A2layout {
+  width: 100%;
+  height: 100%;
+  background-color: #f5f5f5;
+  background-image: url('../../assets/img/layout_bg.jpg');
+  background-size: 100% 100%;
+  :global {
+    .Logo {
+      position: absolute;
+      top: 20px;
+      left: 30px;
+    }
+    .siderBar {
+      position: absolute;
+      right: 0;
+      top: 0;
+      width: 231px;
+      height: 100%;
+      background-color: rgba(141, 84, 95, 0.8);
+      background-image: url('../../assets/img/siderBar_bg.png');
+      background-size: 100% 100%;
+      display: flex;
+      flex-direction: column;
+      justify-content: center;
+      gap: 100px;
+      .detailPanel,
+      .introPanel {
+        width: 100%;
+        height: 200px;
+        text-align: center;
+      }
+    }
+  }
+}
+
+@media screen and (max-width: 768px) {
+  .A2layout {
+    :global {
+      .tabBar {
+        width: 100%;
+        .tabBtn {
+          height: 70px;
+          padding: 10px;
+          line-height: 20px;
+        }
+      }
+    }
+  }
+}

+ 24 - 0
src/pages/A2layout/index.tsx

@@ -0,0 +1,24 @@
+import React, { useState } from 'react'
+import styles from './index.module.scss'
+import IntroPanel from '../A4IntroPanel'
+import DetailPanel from '@/pages/A3detailPannel'
+import wenwu from '@/assets/img/wenwu.png'
+import changguan from '@/assets/img/changguan.png'
+import Logo from '@/assets/img/Logo.png'
+import { useNavigate } from 'react-router-dom'
+
+function A2layout() {
+  const navigate = useNavigate()
+  return (
+    <div className={styles.A2layout}>
+      <div className="siderBar">
+        <div className="detailPanel" onClick={() => navigate('/detailP')}><img src={wenwu} alt="" draggable="false" /></div>
+        <div className="introPanel" onClick={() => navigate('/introP')}><img src={changguan} alt="" draggable="false" /></div>
+      </div>
+    </div>
+  )
+}
+
+const MemoA2layout = React.memo(A2layout)
+
+export default MemoA2layout

+ 51 - 0
src/pages/A2layoutM/index.module.scss

@@ -0,0 +1,51 @@
+.A2layout {
+  width: 100%;
+  height: 100%;
+  background-color: #f5f5f5;
+  background-image: url('../../assets/img/layout_bg_M.jpg');
+  background-size: 100% 100%;
+  :global {
+    .layoutTitle {
+      position: absolute;
+      left: 50%;
+      transform: translate(-50%, 0);
+      top: 17%;
+      width: 95%;
+      height: 153px;
+      background: url('../../assets/img/title_M.png') no-repeat center center;
+      background-size: 100% 100%;
+    }
+    .siderBar {
+      position: absolute;
+      left: 50%;
+      bottom: 20px;
+      transform: translate(-50%, 0);
+      width: 100%;
+      height: 110px;
+      display: flex;
+      justify-content: center;
+      gap: 10%;
+      .detailPanel,
+      .introPanel {
+        width: 40%;
+        height: 100px;
+        text-align: center;
+      }
+    }
+  }
+}
+
+@media screen and (max-width: 768px) {
+  .A2layout {
+    :global {
+      .tabBar {
+        width: 100%;
+        .tabBtn {
+          height: 70px;
+          padding: 10px;
+          line-height: 20px;
+        }
+      }
+    }
+  }
+}

+ 27 - 0
src/pages/A2layoutM/index.tsx

@@ -0,0 +1,27 @@
+import React, { useState } from 'react'
+import styles from './index.module.scss'
+import IntroPanel from '../A4IntroPanel'
+import DetailPanel from '@/pages/A3detailPannel'
+import wenwu from '@/assets/img/wenwu.png'
+import changguan from '@/assets/img/changguan.png'
+import Logo from '@/pages/components/LOGOM'
+import { useNavigate } from 'react-router-dom'
+
+
+function A2layout() {
+  const navigate = useNavigate()
+  return (
+    <div className={styles.A2layout}>
+      <div className="layoutTitle"></div>
+      <div className="siderBar">
+        <div className="detailPanel" onClick={() => navigate('/detailP')}><img src={wenwu} alt="" draggable="false" /></div>
+        <div className="introPanel" onClick={() => navigate('/introP')}><img src={changguan} alt="" draggable="false" /></div>
+      </div>
+
+    </div>
+  )
+}
+
+const MemoA2layout = React.memo(A2layout)
+
+export default MemoA2layout

+ 212 - 0
src/pages/A3detailPannel/index.module.scss

@@ -0,0 +1,212 @@
+.detailPanel {
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  background-color: #ccc;
+  background-image: url(../../assets/img/detailPannelBg.png);
+  background-size: 100% 100%;
+  display: flex;
+  align-items: center;
+  :global {
+    .Logo {
+      position: absolute;
+      top: 20px;
+      left: 30px;
+    }
+    .DsiderBar {
+      width: 400px;
+      height: 100%;
+      background-color: #fff;
+      background-image: url(../../assets/img/dsiderBg.png);
+      background-size: 100% 100%;
+      display: flex;
+      flex-direction: column;
+      align-items: center;
+      gap: 40px;
+      box-shadow: 2px 0px 10px rgba(0, 0, 0, 0.5);
+      .Logo2 {
+        width: 110px;
+        height: 100px;
+        object-fit: contain;
+        margin-top: 40px;
+      }
+      .searchInp {
+        width: 80%;
+        height: 50px;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        margin-bottom: 70px;
+        .leftSearch {
+          width: 70%;
+          height: 100%;
+          display: flex;
+          align-items: center;
+          justify-content: center;
+          background-color: #fff;
+          border-radius: 25px 0 0 25px;
+          & > input {
+            width: 85%;
+            height: 90%;
+            border: none;
+            outline: none;
+            font-size: 18px;
+            &::placeholder {
+              color: rgba(181, 0, 0, 1);
+            }
+          }
+        }
+        .rightSearch {
+          width: 30%;
+          height: 100%;
+          display: flex;
+          align-items: center;
+          justify-content: center;
+          background-color: rgba(196, 38, 29, 1);
+          border-radius: 0 25px 25px 0;
+          cursor: pointer;
+          & > img {
+            width: 100%;
+            height: 26px;
+            object-fit: contain;
+          }
+        }
+      }
+      .level {
+        width: 80%;
+        height: 60px;
+        line-height: 60px;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        font-size: 24px;
+        font-weight: 600;
+        letter-spacing: 10px;
+        color: #fff;
+        background-image: url(../../assets/img/siderBtnBg.png);
+        background-size: 100% 100%;
+        transition: all 0.3s ease-in-out;
+        cursor: pointer;
+      }
+      .levelActive {
+        color: rgba(193, 3, 4, 1);
+        background-image: url(../../assets/img/siderBtnBgAc.png);
+        background-size: 100% 100%;
+        transition: all 0.3s ease-in-out;
+      }
+    }
+    // 右侧区域
+    .rightContainner {
+      width: calc(100% - 400px);
+      height: 100%;
+      position: relative;
+      display: flex;
+      align-items: center;
+      justify-content: flex-end;
+      flex-direction: column;
+      gap: 30px;
+      // 顶部tab
+      .topTab {
+        position: absolute;
+        top: 70px;
+        left: 30px;
+        width: 400px;
+        height: 46px;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        gap: 20px;
+        .tab {
+          width: 160px;
+          height: 46px;
+          font-size: 24px;
+          font-weight: bold;
+          color: rgba(184, 17, 11, 1);
+          border: 1px solid rgba(184, 17, 11, 1);
+          background-color: rgba(255, 243, 237, 1);
+          text-align: center;
+          line-height: 46px;
+          border-radius: 23px;
+          transition: all 0.3s ease-in-out;
+        }
+        .tabActive {
+          background-color: rgba(254, 196, 28, 1);
+          color: rgba(255, 255, 255, 1);
+          transition: all 0.3s ease-in-out;
+        }
+      }
+      // 返回按钮
+      .DbackBtn {
+        position: absolute;
+        top: 70px;
+        right: 80px;
+        width: 48px;
+        height: 48px;
+        cursor: pointer;
+        & > img {
+          width: 100%;
+          height: 100%;
+          object-fit: contain;
+        }
+      }
+      // 内容区域
+      .Dcontent {
+        width: 100%;
+        height: 600px;
+        padding: 0 80px 0 30px;
+        display: flex;
+        align-items: flex-start;
+        flex-wrap: wrap;
+        gap: 2% 1%;
+        .Ditem {
+          width: 24%;
+          height: 43%;
+          background-color: #fff;
+          cursor: pointer;
+          position: relative;
+          & > img {
+            width: 100%;
+            height: 100%;
+            object-fit: contain;
+          }
+        }
+      }
+
+      //antd分页
+      .ant-pagination {
+        height: 80px;
+        .ant-pagination-item-active {
+          background-color: rgba(182, 35, 31, 1);
+          border: none;
+          & > a {
+            color: #fff !important;
+          }
+        }
+      }
+    }
+  }
+}
+
+.mood {
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  background-color: rgba(0, 0, 0, 0.7);
+  transition: all 0.3s ease-in-out;
+  :global {
+    .Dtitle {
+      position: absolute;
+      top: 50%;
+      left: 50%;
+      transform: translate(-50%, -50%);
+      font-size: 20px;
+      font-weight: 600;
+      letter-spacing: 3px;
+      color: #fff;
+    }
+  }
+}

+ 117 - 0
src/pages/A3detailPannel/index.tsx

@@ -0,0 +1,117 @@
+import React, { useState, useEffect } from 'react'
+import styles from './index.module.scss'
+import { useNavigate } from 'react-router-dom'
+import closeBtn from '@/assets/img/closeBtn.png'
+import searchIcon from '@/assets/img/searchIcon.png'
+import { Pagination } from 'antd'
+import Logo from '@/assets/img/Logo.png'
+import Intro2D from '../A5Intro2D'
+import Intro3D from '../A6Intro3D'
+function DetailPanel() {
+  const navigate = useNavigate()
+  // 三维文物0,二维文物1
+  const [showTabIndex, setShowTabIndex] = useState(0)
+  // 1级:1,2级:2,3级:3,未定级:0,全部:-1
+  const [showLevelIndex, setShowLevelIndex] = useState(-1)
+  const [inputVal, setInputVal] = useState('')
+  const [oreData, setOreData] = useState([])
+  const [currentPage, setCurrentPage] = useState(1)
+  // 遮罩层
+  const [showMoodIndex, setShowMoodIndex] = useState(-1)
+  const Mood: React.FC<{ title?: string; showMood?: boolean }> = ({ title, showMood }) => {
+    return (
+      <div className={styles.mood} style={{ opacity: showMood ? '1' : '0' }}>
+        <div className='Dtitle'>{title}</div>
+      </div>
+    )
+  }
+  const handleLevelClick = (index: number) => {
+    console.log(index, showLevelIndex, index === showLevelIndex)
+    if (index === showLevelIndex) {
+      setShowLevelIndex(-1)
+    } else {
+      setShowLevelIndex(index)
+    }
+  }
+  const searchFc = () => {
+    console.log(inputVal)
+  }
+
+  useEffect(() => {
+    setOreData(infoTemo.oreData)
+  }, [])
+
+  useEffect(() => {
+    setCurrentPage(1)
+    setOreData(infoTemo.oreData.filter(item => item.type === showTabIndex && (item.level === showLevelIndex || showLevelIndex === -1) && item.title.includes(inputVal)))
+  }, [showLevelIndex, showTabIndex, inputVal])
+  return (
+    <div className={styles.detailPanel}>
+      {/* 侧边栏 */}
+      <div className='DsiderBar'>
+        <img draggable={false} src={Logo} alt='' className='Logo2' />
+        <div className='searchInp'>
+          <div className='leftSearch'>
+            <input className='search' placeholder='请输入搜索内容' type='text' value={inputVal} onChange={e => setInputVal(e.target.value)} />
+          </div>
+          <div className='rightSearch'>
+            <img draggable={false} src={searchIcon} alt='' />
+          </div>
+        </div>
+        <div className={`level ${showLevelIndex === 1 ? 'levelActive' : ''}`} onClick={() => handleLevelClick(1)}>
+          一级文物
+        </div>
+        <div className={`level ${showLevelIndex === 2 ? 'levelActive' : ''}`} onClick={() => handleLevelClick(2)}>
+          二级文物
+        </div>
+        <div className={`level ${showLevelIndex === 3 ? 'levelActive' : ''}`} onClick={() => handleLevelClick(3)}>
+          三级文物
+        </div>
+        <div className={`level ${showLevelIndex === 0 ? 'levelActive' : ''}`} onClick={() => handleLevelClick(0)}>
+          未定级文物
+        </div>
+      </div>
+      {/* 右侧内容区域 */}
+      <div className='rightContainner'>
+        {/* 顶部tab */}
+        <div className='topTab'>
+          <div className={`tab ${showTabIndex === 0 ? 'tabActive' : ''}`} onClick={() => setShowTabIndex(0)}>
+            三维文物
+          </div>
+          <div className={`tab ${showTabIndex === 1 ? 'tabActive' : ''}`} onClick={() => setShowTabIndex(1)}>
+            二维文物
+          </div>
+        </div>
+        {/* 返回按钮 */}
+        <div className='DbackBtn' onClick={() => navigate(-1)}>
+          <img draggable={false} src={closeBtn} alt='' />
+        </div>
+        {/* 内容区域 */}
+        <div className='Dcontent'>
+          {/* 二维文物 */}
+          {oreData
+            .filter((item, index) => index >= 8 * (currentPage - 1) && index < 8 * currentPage)
+            .map((item, index) => (
+              <div className='Ditem' onClick={() => navigate(item.type === 0 ? `/intro3D?title=${item.title}` : `/intro2D?title=${item.title}`)} onMouseEnter={() => setShowMoodIndex(index)} onMouseLeave={() => setShowMoodIndex(-1)} key={index}>
+                <img src={item.src} alt='' />
+                <Mood title={item.title} showMood={showMoodIndex === index} />
+              </div>
+            ))}
+          {/* 三维文物 */}
+          {/* <div className='Ditem' onMouseEnter={() => setShowMoodIndex(1)} onMouseLeave={() => setShowMoodIndex(-1)} > */}
+          {/* 图片还是三维模型?链接还是js加载? */}
+          {/* <img src='' alt='' /> */}
+          {/* <Mood title='文物名称' showMood={showMoodIndex === 1} /> */}
+          {/* </div> */}
+        </div>
+        {/* 分页 */}
+        <Pagination current={currentPage} defaultCurrent={currentPage} defaultPageSize={8} total={oreData.length} showSizeChanger={false} onChange={setCurrentPage} />
+
+      </div>
+    </div>
+  )
+}
+
+const MemoDetailPanel = React.memo(DetailPanel)
+
+export default MemoDetailPanel

+ 197 - 0
src/pages/A3detailPannelM/index.module.scss

@@ -0,0 +1,197 @@
+.detailPanel {
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  background-color: #ccc;
+  background-image: url(../../assets/img/detailPannelBg_M.png);
+  background-size: 100% 100%;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  :global {
+    .DsiderBar {
+      padding: 0px 16px;
+      padding-top: 26px;
+      width: 100%;
+      height: 160px;
+      display: flex;
+      flex-direction: column;
+      align-items: center;
+      gap: 10px;
+      // 顶部tab
+      .topTab {
+        align-self: flex-end;
+        width: 63%;
+        height: 30px;
+        display: flex;
+        align-items: center;
+        justify-content: flex-end;
+        gap: 8px;
+        .tab {
+          width: 40%;
+          height: 100%;
+          font-size: 14px;
+          font-weight: bold;
+          color: rgba(184, 17, 11, 1);
+          border: 1px solid rgba(184, 17, 11, 1);
+          background-color: rgba(255, 243, 237, 1);
+          text-align: center;
+          line-height: 28px;
+          border-radius: 23px;
+          transition: all 0.3s ease-in-out;
+        }
+        .tabActive {
+          background-color: rgba(254, 196, 28, 1);
+          color: rgba(255, 255, 255, 1);
+          transition: all 0.3s ease-in-out;
+        }
+      }
+      .levelBox {
+        width: 100%;
+        height: 36px;
+        display: flex;
+        align-items: center;
+        justify-content: space-between;
+        gap: 1%;
+        .level {
+          width: 24%;
+          height: 36px;
+          line-height: 36px;
+          padding-bottom: 4px;
+          display: flex;
+          align-items: center;
+          justify-content: center;
+          font-size: 14px;
+          font-weight: 600;
+          color: #fff;
+          background-image: url(../../assets/img/siderBtnBg_M.png);
+          background-size: 100% 100%;
+          transition: all 0.3s ease-in-out;
+          cursor: pointer;
+        }
+        .levelActive {
+          color: rgba(193, 3, 4, 1);
+          background-image: url(../../assets/img/siderBtnBgAc_M.png);
+          background-size: 100% 100%;
+          transition: all 0.3s ease-in-out;
+        }
+      }
+      .searchInp {
+        width: 100%;
+        height: 26px;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        .leftSearch {
+          width: 85%;
+          height: 100%;
+          display: flex;
+          align-items: center;
+          justify-content: center;
+          background-color: #fff;
+          border-radius: 25px 0 0 25px;
+          & > input {
+            width: 85%;
+            height: 90%;
+            border: none;
+            outline: none;
+            font-size: 14px;
+            &::placeholder {
+              color: rgba(181, 0, 0, 1);
+            }
+          }
+        }
+        .rightSearch {
+          width: 15%;
+          height: 100%;
+          display: flex;
+          align-items: center;
+          justify-content: center;
+          background-color: rgba(196, 38, 29, 1);
+          border-radius: 0 25px 25px 0;
+          box-shadow: 0 0 3px;
+          cursor: pointer;
+          & > img {
+            width: 100%;
+            height: 13px;
+            object-fit: contain;
+          }
+        }
+      }
+    }
+    // 右侧区域
+    .rightContainner {
+      width: 100%;
+      height: calc(100% - 160px);
+      padding: 0 16px;
+      padding-bottom: 20px;
+      overflow: auto;
+      &::-webkit-scrollbar {
+        width: 0;
+        height: 0;
+      }
+      // 内容区域
+      .Dcontent {
+        width: 100%;
+        height: fit-content;
+        display: flex;
+        align-items: center;
+        justify-content: space-between;
+        flex-wrap: wrap;
+        gap: 10px 1%;
+        .Ditem {
+          width: 48.3%;
+          height: 300px;
+          background-color: #fff;
+          cursor: pointer;
+          position: relative;
+          border-radius: 5px;
+          & > img {
+            width: 100%;
+            height: 100%;
+            object-fit: contain;
+          }
+        }
+      }
+
+      //antd分页
+      .ant-pagination {
+        height: 80px;
+        .ant-pagination-item-active {
+          background-color: rgba(182, 35, 31, 1);
+          border: none;
+          & > a {
+            color: #fff !important;
+          }
+        }
+      }
+    }
+  }
+}
+
+.mood {
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  background-color: rgba(0, 0, 0, 0.7);
+  transition: all 0.3s ease-in-out;
+  :global {
+    .Dtitle {
+      width: 100%;
+      text-align: center;
+      position: absolute;
+      top: 50%;
+      left: 50%;
+      transform: translate(-50%, -50%);
+      font-size: 20px;
+      font-weight: 600;
+      letter-spacing: 3px;
+      color: #fff;
+      border-radius: 10px;
+    }
+  }
+}

+ 134 - 0
src/pages/A3detailPannelM/index.tsx

@@ -0,0 +1,134 @@
+import React, { useState, useEffect, useRef } from 'react'
+import styles from './index.module.scss'
+import closeBtn from '@/assets/img/closeBtn.png'
+import searchIcon from '@/assets/img/searchIcon.png'
+import { Pagination } from 'antd'
+import { useNavigate } from 'react-router-dom'
+import Logo from '../components/LOGOM'
+import Intro2D from '../A5Intro2D'
+import Intro3D from '../A6Intro3D'
+function DetailPanel() {
+  const navigate = useNavigate()
+  // 三维文物0,二维文物1
+  const [showTabIndex, setShowTabIndex] = useState(0)
+  // 1级:1,2级:2,3级:3,未定级:0,全部:-1
+  const [showLevelIndex, setShowLevelIndex] = useState(-1)
+  const [inputVal, setInputVal] = useState('')
+  const [oreData, setOreData] = useState([])
+  const [currentItem, setCurrentItem] = useState(null)
+  const [currentPage, setCurrentPage] = useState(1)
+  const [visibleCount, setVisibleCount] = useState(8)
+
+  // 滚动加载更多
+  const handleScroll = (e: React.UIEvent<HTMLDivElement>) => {
+    const el = e.currentTarget
+    const { scrollTop, clientHeight, scrollHeight } = el
+    console.log(scrollTop + clientHeight, scrollHeight)
+    if (scrollTop + clientHeight >= scrollHeight - 2) {
+      setVisibleCount(prev => Math.min(prev + 8, oreData.length))
+    }
+  }
+  // 遮罩层
+  const [showMoodIndex, setShowMoodIndex] = useState(-1)
+  const Mood: React.FC<{ title?: string; showMood?: boolean }> = ({ title, showMood }) => {
+    return (
+      <div className={styles.mood} style={{ opacity: showMood ? '1' : '0' }}>
+        <div className='Dtitle'>{title}</div>
+      </div>
+    )
+  }
+  const handleLevelClick = (index: number) => {
+    console.log(index, showLevelIndex, index === showLevelIndex)
+    if (index === showLevelIndex) {
+      setShowLevelIndex(-1)
+    } else {
+      setShowLevelIndex(index)
+    }
+  }
+  const searchFc = () => {
+    console.log(inputVal)
+  }
+
+
+
+  useEffect(() => {
+    setOreData(infoTemo.oreData)
+  }, [])
+
+  useEffect(() => {
+    setCurrentPage(1)
+    setVisibleCount(8)
+    setOreData(infoTemo.oreData.filter(item => item.type === showTabIndex && (item.level === showLevelIndex || showLevelIndex === -1) && item.title.includes(inputVal)))
+  }, [showLevelIndex, showTabIndex, inputVal])
+  return (
+    <div className={styles.detailPanel}>
+      {/* 顶部 */}
+      <div className='DsiderBar'>
+        <Logo />
+        {/* 顶部tab */}
+        <div className='topTab'>
+          <div className={`tab ${showTabIndex === 0 ? 'tabActive' : ''}`} onClick={() => setShowTabIndex(0)}>
+            三维文物
+          </div>
+          <div className={`tab ${showTabIndex === 1 ? 'tabActive' : ''}`} onClick={() => setShowTabIndex(1)}>
+            二维文物
+          </div>
+        </div>
+        {/* 级别 */}
+        <div className="levelBox">
+          <div className={`level ${showLevelIndex === 1 ? 'levelActive' : ''}`} onClick={() => handleLevelClick(1)}>
+            一级文物
+          </div>
+          <div className={`level ${showLevelIndex === 2 ? 'levelActive' : ''}`} onClick={() => handleLevelClick(2)}>
+            二级文物
+          </div>
+          <div className={`level ${showLevelIndex === 3 ? 'levelActive' : ''}`} onClick={() => handleLevelClick(3)}>
+            三级文物
+          </div>
+          <div className={`level ${showLevelIndex === 0 ? 'levelActive' : ''}`} onClick={() => handleLevelClick(0)}>
+            未定级文物
+          </div>
+        </div>
+
+
+        <div className='searchInp'>
+          <div className='leftSearch'>
+            <input className='search' placeholder='请输入搜索内容' type='text' value={inputVal} onChange={e => setInputVal(e.target.value)} />
+          </div>
+          <div className='rightSearch'>
+            <img draggable={false} src={searchIcon} alt='' />
+          </div>
+        </div>
+      </div>
+      {/* 底部内容区域 */}
+      <div className='rightContainner' onScroll={handleScroll}>
+
+        {/* 内容区域 */}
+        <div className='Dcontent'>
+          {/* 二维文物 */}
+          {oreData
+            .filter((item, index) => index >= 8 * (currentPage - 1) && index < 8 * currentPage)
+            .map((item, index) => (
+              <div className='Ditem' onClick={() => navigate(item.type === 0 ? `/intro3D?title=${item.title}` : `/intro2D?title=${item.title}`)} onTouchStart={() => setShowMoodIndex(index)} key={index}>
+                <img src={item.src} alt='' />
+                <Mood title={item.title} showMood={showMoodIndex === index} />
+              </div>
+            ))}
+          {/* 三维文物 */}
+          {/* <div className='Ditem' onMouseEnter={() => setShowMoodIndex(1)} onMouseLeave={() => setShowMoodIndex(-1)} > */}
+          {/* 图片还是三维模型?链接还是js加载? */}
+          {/* <img src='' alt='' /> */}
+          {/* <Mood title='文物名称' showMood={showMoodIndex === 1} /> */}
+          {/* </div> */}
+        </div>
+        {/* 分页 */}
+        {/* <Pagination defaultCurrent={currentPage} defaultPageSize={8} total={oreData.length} showSizeChanger={false} onChange={setCurrentPage} /> */}
+
+      </div>
+    </div>
+  )
+}
+
+const MemoDetailPanel = React.memo(DetailPanel)
+
+export default MemoDetailPanel

+ 56 - 0
src/pages/A4IntroPanel/index.module.scss

@@ -0,0 +1,56 @@
+.introPanel {
+  position: absolute;
+  top: 0;
+  right: 0;
+  width: 100%;
+  height: 100%;
+  background-color: rgba(0, 0, 0, 0.7);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  :global {
+    .IcloseBtn {
+      position: absolute;
+      top: 36px;
+      right: 54px;
+      width: 48px;
+      height: 48px;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      cursor: pointer;
+      & > img {
+        width: 100%;
+        height: 100%;
+        object-fit: contain;
+      }
+    }
+    .Icontent {
+      width: 60%;
+      height: 95%;
+      background-image: url(../../assets/img/introBg.png);
+      background-size: contain;
+      display: flex;
+      flex-direction: column;
+      justify-content: center;
+      .Ititle {
+        text-align: center;
+        width: 100%;
+        height: 100px;
+        padding: 30px 130px;
+        font-size: 30px;
+        font-weight: bold;
+        color: rgba(226, 16, 2, 1);
+      }
+      .Itxt {
+        width: 100%;
+        padding: 10px 130px;
+        text-indent: 2em;
+        font-size: 18px;
+        line-height: 45px;
+        font-weight: bold;
+        color: rgba(21, 21, 21, 1);
+      }
+    }
+  }
+}

+ 21 - 0
src/pages/A4IntroPanel/index.tsx

@@ -0,0 +1,21 @@
+import React from "react";
+import styles from "./index.module.scss";
+import closeBtn from '@/assets/img/closeBtn.png'
+import { useNavigate } from 'react-router-dom';
+
+function IntroPanel() {
+  const navigate = useNavigate();
+  return (
+    <div className={styles.introPanel}>
+      <div className="IcloseBtn" onClick={() => navigate(-1)}><img src={closeBtn} alt="" draggable={false} /></div>
+      <div className="Icontent">
+        <div className="Ititle">场馆简介</div>
+        <div className="Itxt">      这是一段简介这是一段简介这是一段简介这是一段简介这是一段简介这是一段简介这是一段简介这是这是一段简介这是这是一段简介这是这是一段简介这是这是一段简介这是这是一段简介这是这是一段简介这是这是一段简介这是这是一段简介这是这是一段简介这是一段简介这是一段简介这是一段简介这是一段简介这是一段简介这是一段简介</div>
+      </div>
+    </div>
+  )
+}
+
+const MemoA4IntroPanel = React.memo(IntroPanel);
+
+export default MemoA4IntroPanel;  

+ 59 - 0
src/pages/A4IntroPanelM/index.module.scss

@@ -0,0 +1,59 @@
+.introPanel {
+  position: absolute;
+  top: 0;
+  right: 0;
+  width: 100%;
+  height: 100%;
+  background-color: rgba(0, 0, 0, 0.7);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  :global {
+    .IcloseBtn {
+      position: absolute;
+      top: 20px;
+      right: 20px;
+      width: 38px;
+      height: 38px;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      cursor: pointer;
+      & > img {
+        width: 100%;
+        height: 100%;
+        object-fit: contain;
+      }
+    }
+    .Icontent {
+      width: 97%;
+      height: 80%;
+      background-image: url(../../assets/img/introBg_M.png);
+      background-size: 100% 100%;
+      background-repeat: no-repeat;
+      display: flex;
+      flex-direction: column;
+      align-items: center;
+      .Ititle {
+        text-align: center;
+        margin-top: 60px;
+        width: 100%;
+        height: 40px;
+        font-size: 18px;
+        letter-spacing: 2px;
+        line-height: 20px;
+        font-weight: bold;
+        color: rgba(226, 16, 2, 1);
+      }
+      .Itxt {
+        width: 100%;
+        text-indent: 2em;
+        font-size: 14px;
+        line-height: 25px;
+        padding: 0 30px;
+        font-weight: bold;
+        color: rgba(21, 21, 21, 1);
+      }
+    }
+  }
+}

+ 24 - 0
src/pages/A4IntroPanelM/index.tsx

@@ -0,0 +1,24 @@
+import React from "react";
+import styles from "./index.module.scss";
+import closeBtn from '@/assets/img/closeBtn.png'
+import { useNavigate } from 'react-router-dom';
+import Logo from "../components/LOGOM";
+
+function IntroPanelM() {
+  const navigate = useNavigate();
+
+  return (
+    <div className={styles.introPanel}>
+      <Logo />
+      <div className="IcloseBtn" onClick={() => navigate(-1)}><img src={closeBtn} alt="" draggable={false} /></div>
+      <div className="Icontent">
+        <div className="Ititle">场馆简介</div>
+        <div className="Itxt">      这是一段简介这是一段简介这是一段简介这是一段简介这是一段简介这是一段简介这是一段简介这是这是一段简介这是这是一段简介这是这是一段简介这是这是一段简介这是这是一段简介这是这是一段简介这是这是一段简介这是这是一段简介这是这是一段简介这是一段简介这是一段简介这是一段简介这是一段简介这是一段简介这是一段简介</div>
+      </div>
+    </div>
+  )
+}
+
+const MemoA4IntroPanel = React.memo(IntroPanelM);
+
+export default MemoA4IntroPanel;  

+ 70 - 0
src/pages/A5Intro2D/index.module.scss

@@ -0,0 +1,70 @@
+.intro2D {
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  background-color: #ccc;
+  background-image: url(../../assets/img/2dBg.jpg);
+  background-size: 100% 100%;
+  display: flex;
+  align-items: center;
+  :global {
+    .iClose {
+      position: absolute;
+      top: 70px;
+      right: 80px;
+      width: 48px;
+      height: 48px;
+      & > img {
+        width: 100%;
+        height: 100%;
+        object-fit: contain;
+      }
+    }
+    .iLeft {
+      width: 45%;
+      height: 100%;
+      & > img {
+        padding: 0 60px;
+        width: 100%;
+        height: 100%;
+        object-fit: contain;
+      }
+    }
+    .iRight {
+      width: 55%;
+      height: 100%;
+      display: flex;
+      justify-content: center;
+      flex-direction: column;
+      gap: 20px;
+      .ititle {
+        font-size: 48px;
+        font-weight: 700;
+        letter-spacing: 2px;
+        color: rgba(226, 16, 2, 1);
+      }
+      .idate,
+      .itype,
+      .ilevel {
+        font-size: 18px;
+        font-weight: 500;
+        color: rgba(21, 21, 21, 1);
+      }
+      .imusic {
+        width: 90px;
+        height: 32px;
+        background-image: url(../../assets/img/music.png);
+        background-size: 100% 100%;
+        cursor: pointer;
+        margin: 20px 0;
+      }
+      .idesc {
+        text-indent: 2em;
+        font-size: 16px;
+        color: rgba(21, 21, 21, 1);
+      }
+    }
+  }
+}

+ 42 - 0
src/pages/A5Intro2D/index.tsx

@@ -0,0 +1,42 @@
+import React, { useEffect, useRef, useState } from "react";
+import closeBtn from '@/assets/img/closeBtn.png'
+import styles from "./index.module.scss";
+import { useNavigate } from 'react-router-dom'
+function Intro2D() {
+  const navigate = useNavigate()
+  const [item, setItem] = useState(null)
+  useEffect(() => {
+    const title = new URLSearchParams(window.location.href.split('?')[1]).get('title')
+    setItem(infoTemo.oreData.find(item => item.title === title))
+  }, [])
+  const audioRef = useRef<HTMLAudioElement>(null);
+  const handleMusicClick = () => {
+    if (audioRef.current) {
+      if (audioRef.current.paused) {
+        audioRef.current.play();
+      } else {
+        audioRef.current.pause();
+      }
+    }
+  }
+  return (
+    <div className={styles.intro2D}>
+      <div className="iClose">
+        <img draggable={false} src={closeBtn} alt='' onClick={() => navigate(-1)} />
+      </div>
+      <div className="iLeft"><img src={item?.src} alt="" /></div>
+      <div className="iRight">
+        <div className="ititle">{item?.title}</div>
+        <div className="idate">年代:{item?.date}</div>
+        <div className="itype">类型:{item?.type}</div>
+        <div className="ilevel">级别:{item?.level}</div>
+        <div className="imusic" onClick={handleMusicClick}><audio ref={audioRef} style={{ width: '0', height: '0' }} src={item?.musicLink} controls></audio></div>
+        <div className="idesc" dangerouslySetInnerHTML={{ __html: item?.desc }}></div>
+      </div>
+    </div>
+  )
+}
+
+const MemoIntro2D = React.memo(Intro2D);
+
+export default MemoIntro2D;

+ 70 - 0
src/pages/A5Intro2DM/index.module.scss

@@ -0,0 +1,70 @@
+.intro2D {
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  background-color: #ccc;
+  background-image: url(../../assets/img/2dBg.jpg);
+  background-size: 100% 100%;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  padding: 30px;
+  gap: 10px;
+  :global {
+    .iLeft {
+      width: 100%;
+      height: 55%;
+      & > img {
+        width: 100%;
+        height: 100%;
+        object-fit: contain;
+      }
+    }
+    .iRight {
+      width: 100%;
+      height: 45%;
+      display: flex;
+      flex-direction: column;
+      gap: 4px;
+      .ititle {
+        font-size: 28px;
+        font-weight: bold;
+        letter-spacing: 1px;
+        color: rgba(226, 16, 2, 1);
+        margin-bottom: 20px;
+      }
+      .idate,
+      .itype,
+      .ilevel {
+        font-size: 12px;
+        font-weight: 500;
+        color: rgba(21, 21, 21, 1);
+      }
+      .imusic {
+        width: 60px;
+        height: 22px;
+        background-image: url(../../assets/img/music.png);
+        background-size: contain;
+        background-repeat: no-repeat;
+        cursor: pointer;
+        margin: 5px 0;
+      }
+      .idesc {
+        text-indent: 2em;
+        font-size: 12px;
+        letter-spacing: 1px;
+        line-height: 20px;
+        color: rgba(21, 21, 21, 1);
+        height: fit-content;
+        max-height: 300px;
+        overflow: auto;
+        &::-webkit-scrollbar {
+          width: 0px;
+          height: 0px;
+        }
+      }
+    }
+  }
+}

+ 39 - 0
src/pages/A5Intro2DM/index.tsx

@@ -0,0 +1,39 @@
+import React, { useEffect, useRef, useState } from "react";
+import closeBtn from '@/assets/img/closeBtn.png'
+import styles from "./index.module.scss";
+import { useNavigate } from 'react-router-dom'
+function Intro2D() {
+  const navigate = useNavigate()
+  const [item, setItem] = useState(null)
+  useEffect(() => {
+    const title = new URLSearchParams(window.location.href.split('?')[1]).get('title')
+    setItem(infoTemo.oreData.find(item => item.title === title))
+  }, [])
+  const audioRef = useRef<HTMLAudioElement>(null);
+  const handleMusicClick = () => {
+    if (audioRef.current) {
+      if (audioRef.current.paused) {
+        audioRef.current.play();
+      } else {
+        audioRef.current.pause();
+      }
+    }
+  }
+  return (
+    <div className={styles.intro2D}>
+      <div className="iLeft"><img src={item?.src} alt="" /></div>
+      <div className="iRight">
+        <div className="ititle">{item?.title}</div>
+        <div className="idate">年代:{item?.date}</div>
+        <div className="itype">类型:{item?.type}</div>
+        <div className="ilevel">级别:{item?.level}</div>
+        <div className="imusic" onClick={handleMusicClick}><audio ref={audioRef} style={{ width: '0', height: '0' }} src={item?.musicLink} controls></audio></div>
+        <div className="idesc" dangerouslySetInnerHTML={{ __html: item?.desc }}></div>
+      </div>
+    </div>
+  )
+}
+
+const MemoIntro2D = React.memo(Intro2D);
+
+export default MemoIntro2D;

+ 124 - 0
src/pages/A6Intro3D/index.module.scss

@@ -0,0 +1,124 @@
+.Intro3D {
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  background-image: url(../../assets/img/3dBg.jpg);
+  background-size: 100% 100%;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  :global {
+    .Logo {
+      position: absolute;
+      top: 20px;
+      left: 30px;
+    }
+    .iClose {
+      position: absolute;
+      top: 70px;
+      right: 80px;
+      width: 48px;
+      height: 48px;
+      cursor: pointer;
+      & > img {
+        width: 100%;
+        height: 100%;
+        object-fit: contain;
+      }
+    }
+    .Intro3DContent {
+      width: 80%;
+      height: 90%;
+      display: flex;
+      flex-direction: column;
+      align-items: center;
+      justify-content: center;
+      gap: 20px;
+      .Intro3DContentTitle {
+        font-size: 30px;
+        color: rgba(235, 207, 153, 1);
+        font-weight: 700;
+      }
+      .Intro3DContentLocation {
+        font-size: 20px;
+        line-height: 29px;
+        color: rgba(255, 255, 255, 1);
+      }
+      .Intro3DContentImg {
+        width: 66%;
+        height: 80%;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        margin: 40px 0;
+        iframe {
+          width: 100%;
+          height: 100%;
+          border: none;
+        }
+      }
+      .Intro3DContentBtns {
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        margin: 30px 0;
+        gap: 80px;
+        .homeBtn,
+        .detailBtn {
+          width: 48px;
+          height: 78px;
+          font-size: 20px;
+          line-height: 30px;
+          color: rgba(255, 255, 255, 1);
+          cursor: pointer;
+          text-align: center;
+        }
+        img {
+          width: 48px;
+          height: 48px;
+          object-fit: contain;
+        }
+      }
+    }
+    .Intro3DContentDesc {
+      position: absolute;
+      top: 50%;
+      left: 50%;
+      transform: translate(-50%, -50%);
+      width: 100%;
+      height: 100%;
+      background-color: rgba(0, 0, 0, 0.7);
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      .Intro3DContentDescContent {
+        width: 80%;
+        height: 83%;
+        background-image: url(../../assets/img/3dDescBg.jpg);
+        background-size: 100% 100%;
+        display: flex;
+        flex-direction: column;
+        align-items: center;
+        justify-content: center;
+        gap: 50px;
+        .Intro3DContentDescTitle {
+          font-size: 30px;
+          font-weight: 500;
+          line-height: 30px;
+          color: rgba(226, 16, 2, 1);
+        }
+        .Intro3DContentDescText {
+          width: 80%;
+          text-indent: 2em;
+          font-weight: bold;
+          font-size: 18px;
+          line-height: 45px;
+          color: rgba(21, 21, 21, 1);
+        }
+      }
+    }
+  }
+}

+ 50 - 0
src/pages/A6Intro3D/index.tsx

@@ -0,0 +1,50 @@
+import React, { useState, useEffect } from "react";
+import styles from "./index.module.scss";
+import Logo from '@/assets/img/Logo.png'
+import closeBtn from '@/assets/img/closeBtn.png'
+import homeBtn from '@/assets/img/homeBtn.png'
+import introBtn from '@/assets/img/introBtn.png'
+import { useNavigate } from 'react-router-dom'
+function Intro3D() {
+  const [item, setItem] = useState(null)
+  useEffect(() => {
+    const title = new URLSearchParams(window.location.href.split('?')[1]).get('title')
+    setItem(infoTemo.oreData.find(item => item.title === title))
+  }, [])
+  const navigate = useNavigate()
+  const [showDesc, setShowDesc] = useState(false);
+  return (
+    <div className={styles.Intro3D}>
+      <div className="Logo"><img src={Logo} alt="" /></div>
+      <div className="iClose">
+        <img draggable={false} src={closeBtn} alt='' onClick={() => navigate(-1)} />
+      </div>
+      <div className="Intro3DContent">
+        <div className="Intro3DContentTitle">{item?.title}</div>
+        <div className="Intro3DContentLocation">{item?.location}</div>
+        <div className="Intro3DContentImg"><iframe src={item?.modalLink} title="item?.title"></iframe></div>
+        <div className="Intro3DContentBtns">
+          <div className="homeBtn" onClick={() => navigate('/layout')}> <img src={homeBtn} alt="" /> 首页</div>
+          <div className="detailBtn" onClick={() => setShowDesc(true)}> <img src={introBtn} alt="" /> 简介</div>
+        </div>
+      </div>
+      {showDesc && <div className="Intro3DContentDesc">
+        <div className="iClose" onClick={() => setShowDesc(false)}>
+          <img draggable={false} src={closeBtn} />
+        </div>
+        <div className="Intro3DContentDescContent">
+          <div className="Intro3DContentDescTitle">
+            {item.title}
+          </div>
+          <div className="Intro3DContentDescText" dangerouslySetInnerHTML={{ __html: item.desc }}>
+          </div>
+        </div>
+
+      </div>}
+    </div>
+  )
+}
+
+const MemoIntro3D = React.memo(Intro3D);
+
+export default MemoIntro3D;

+ 126 - 0
src/pages/A6Intro3DM/index.module.scss

@@ -0,0 +1,126 @@
+.Intro3D {
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  background-image: url(../../assets/img/3dBg_M.jpg);
+  background-size: 100% 100%;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  padding: 30px;
+  :global {
+    .Intro3DContent {
+      width: 100%;
+      height: 100%;
+      display: flex;
+      flex-direction: column;
+      align-items: center;
+      justify-content: center;
+      gap: 6px;
+      .Intro3DContentTitle {
+        width: 100%;
+        text-align: center;
+        height: fit-content;
+        line-height: 20px;
+        font-size: 14px;
+        color: rgba(235, 207, 153, 1);
+        font-weight: 500;
+        letter-spacing: 2px;
+      }
+      .Intro3DContentLocation {
+        width: 100%;
+        text-align: center;
+        height: fit-content;
+        line-height: 20px;
+        font-size: 14px;
+        color: rgba(255, 255, 255, 1);
+        margin-bottom: 20px;
+      }
+      .Intro3DContentImg {
+        width: 100%;
+        height: 50%;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        margin: 40px 0;
+        iframe {
+          width: 100%;
+          height: 100%;
+          border: none;
+        }
+      }
+      .Intro3DContentBtns {
+        width: 100%;
+        height: 60px;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        gap: 50px;
+        .homeBtn,
+        .detailBtn {
+          width: 38px;
+          height: 58px;
+          font-size: 14px;
+          line-height: 20px;
+          color: rgba(255, 255, 255, 1);
+          cursor: pointer;
+          text-align: center;
+        }
+        img {
+          width: 48px;
+          height: 48px;
+          object-fit: contain;
+        }
+      }
+    }
+    .Intro3DContentDesc {
+      position: absolute;
+      top: 50%;
+      left: 50%;
+      transform: translate(-50%, -50%);
+      width: 100%;
+      height: 100%;
+      background-color: rgba(0, 0, 0, 0.7);
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      .iClose {
+        position: absolute;
+        top: 6%;
+        right: 6%;
+        width: 30px;
+        height: 30px;
+        cursor: pointer;
+      }
+      .Intro3DContentDescContent {
+        width: 80%;
+        height: 83%;
+        background-image: url(../../assets/img/3dDescBg_M.jpg);
+        background-size: 100% 100%;
+        background-repeat: no-repeat;
+        display: flex;
+        flex-direction: column;
+        align-items: center;
+        gap: 10px;
+        .Intro3DContentDescTitle {
+          margin-top: 70px;
+          font-size: 20px;
+          letter-spacing: 2px;
+          font-weight: 500;
+          line-height: 30px;
+          color: rgba(226, 16, 2, 1);
+        }
+        .Intro3DContentDescText {
+          width: 80%;
+          text-indent: 2em;
+          font-size: 14px;
+          line-height: 30px;
+          color: rgba(21, 21, 21, 1);
+        }
+      }
+    }
+  }
+}

+ 46 - 0
src/pages/A6Intro3DM/index.tsx

@@ -0,0 +1,46 @@
+import React, { useState, useEffect } from "react";
+import styles from "./index.module.scss";
+import Logo from '@/assets/img/Logo.png'
+import closeBtn from '@/assets/img/closeBtn.png'
+import homeBtn from '@/assets/img/homeBtn.png'
+import introBtn from '@/assets/img/introBtn.png'
+import { useNavigate } from 'react-router-dom'
+function Intro3D() {
+  const [item, setItem] = useState(null)
+  useEffect(() => {
+    const title = new URLSearchParams(window.location.href.split('?')[1]).get('title')
+    setItem(infoTemo.oreData.find(item => item.title === title))
+  }, [])
+  const navigate = useNavigate()
+  const [showDesc, setShowDesc] = useState(false);
+  return (
+    <div className={styles.Intro3D}>
+      <div className="Intro3DContent">
+        <div className="Intro3DContentImg"><iframe src={item?.modalLink} title="item?.title"></iframe></div>
+        <div className="Intro3DContentTitle">{item?.title}</div>
+        <div className="Intro3DContentLocation">{item?.location}</div>
+        <div className="Intro3DContentBtns">
+          <div className="homeBtn" onClick={() => navigate('/layout')}> <img src={homeBtn} alt="" /> 首页</div>
+          <div className="detailBtn" onClick={() => setShowDesc(true)}> <img src={introBtn} alt="" /> 简介</div>
+        </div>
+      </div>
+      {showDesc && <div className="Intro3DContentDesc">
+        <div className="iClose" onClick={() => setShowDesc(false)}>
+          <img draggable={false} src={closeBtn} />
+        </div>
+        <div className="Intro3DContentDescContent">
+          <div className="Intro3DContentDescTitle">
+            {item.title}
+          </div>
+          <div className="Intro3DContentDescText" dangerouslySetInnerHTML={{ __html: item.desc }}>
+          </div>
+        </div>
+
+      </div>}
+    </div>
+  )
+}
+
+const MemoIntro3D = React.memo(Intro3D);
+
+export default MemoIntro3D;

+ 12 - 0
src/pages/components/LOGOM/index.module.scss

@@ -0,0 +1,12 @@
+.Logo {
+  position: absolute;
+  top: 10px;
+  left: 20px;
+  width: 88px;
+  height: 48px;
+}
+.LogoImg {
+  width: 100%;
+  height: 100%;
+  object-fit: contain;
+}

+ 15 - 0
src/pages/components/LOGOM/index.tsx

@@ -0,0 +1,15 @@
+import React from "react";
+import styles from "./index.module.scss";
+import Logo from '@/assets/img/Logo.png'
+function LOGO() {
+
+  return (
+    <div className={styles.Logo}>
+      <img className={styles.LogoImg} src={Logo} alt="" />
+    </div>
+  )
+}
+
+const MemoLogo = React.memo(LOGO);
+
+export default MemoLogo;  

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

+ 30 - 0
src/store/action/A1list.ts

@@ -0,0 +1,30 @@
+/**
+ * 展馆预约记录-列表
+ */
+
+import http from '@/utils/http'
+
+// export const A1_APIgetList = (data: any): any => {
+//   return async (dispatch: AppDispatch) => {
+//     const res = await http.post('cms/applyExhibition/pageList', data)
+//     if (res.code === 0) {
+//       const arr: A1tableType[] = res.data.records
+//       arr.forEach(v => {
+//         v.cityStr = v.province + '-' + v.city
+//       })
+
+//       const obj = {
+//         list: arr,
+//         total: res.data.total
+//       }
+//       dispatch({ type: 'A1/getList', payload: obj })
+//     }
+//   }
+// }
+
+/**
+ * 获取全部访问量
+ */
+export const A1_APIgetNumList = () => {
+  return http.get('visit/getList')
+}

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

@@ -0,0 +1,28 @@
+import { domShowFu, progressDomFu } from '@/utils/domShow'
+import http from '@/utils/http'
+import axios from 'axios'
+import store from '..'
+
+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

+ 26 - 0
src/store/reducer/A1list.ts

@@ -0,0 +1,26 @@
+// 初始化状态
+const initState = {
+  // 列表数据
+  tableInfo: {
+    list: [] as any[],
+    total: 0
+  }
+}
+
+// 定义 action 类型
+type Props = {
+  type: 'A1/getList'
+  payload: { list: any[]; total: number }
+}
+
+// reducer
+export default function Reducer(state = initState, action: Props) {
+  switch (action.type) {
+    // 获取列表数据
+    case 'A1/getList':
+      return { ...state, tableInfo: action.payload }
+
+    default:
+      return state
+  }
+}

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

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

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

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

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

@@ -0,0 +1,26 @@
+declare module 'history'
+declare module '*.scss'
+declare module '*.png'
+declare module '*.mp3'
+declare module '*.jpg'
+declare module '*.gif'
+declare module '*.svg'
+declare module 'js-export-excel'
+declare module 'braft-utils'
+
+type MyInfoType = {
+  oreData: {
+    title: string
+    date: string
+    type: string
+    oreType: string
+    level: string
+    desc: string
+    location: string
+    musicLink: string
+    src: string
+  }[]
+}
+
+
+declare const infoTemo: MyInfoType

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

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

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

+ 16 - 0
src/utils/history.ts

@@ -0,0 +1,16 @@
+import { createHashHistory } from 'history'
+const history = createHashHistory()
+export default history
+
+// 判断是手机端还是pc端
+export const isMobileFu = () => {
+  if (
+    window.navigator.userAgent.match(
+      /(phone|pad|pod|iPhone|iPod|ios|iPad|Android|Mobile|BlackBerry|IEMobile|MQQBrowser|JUC|Fennec|wOSBrowser|BrowserNG|WebOS|Symbian|Windows Phone)/i
+    )
+  ) {
+    return true
+  } else return false
+}
+
+

+ 109 - 0
src/utils/http.ts

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

+ 3 - 0
src/utils/isMobile.ts

@@ -0,0 +1,3 @@
+export default function isMobile() {
+  return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)
+}

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

+ 40 - 0
src/utils/storage.ts

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

+ 0 - 0
src/utils/tableData.ts


Неке датотеке нису приказане због велике количине промена