shaogen1995 1 год назад
Сommit
c2ce0003ec
64 измененных файлов с 40455 добавлено и 0 удалено
  1. 23 0
      .gitignore
  2. 1 0
      README.md
  3. 10 0
      config-overrides.js
  4. 30301 0
      package-lock.json
  5. 64 0
      package.json
  6. 8 0
      path.tsconfig.json
  7. 6344 0
      public/4dage.js
  8. 53 0
      public/index.html
  9. 64 0
      public/model.html
  10. 78 0
      src/App.tsx
  11. 74 0
      src/AppM.tsx
  12. BIN
      src/assets/img/IMGerror.png
  13. BIN
      src/assets/img/back.png
  14. BIN
      src/assets/img/landtip.png
  15. BIN
      src/assets/img/loading.gif
  16. BIN
      src/assets/img/look.png
  17. BIN
      src/assets/img/name1.png
  18. BIN
      src/assets/img/name2.png
  19. 132 0
      src/assets/styles/base.css
  20. 174 0
      src/assets/styles/base.less
  21. 21 0
      src/components/AsyncSpinLoding/index.module.scss
  22. 15 0
      src/components/AsyncSpinLoding/index.tsx
  23. 51 0
      src/components/ImageLazy/index.module.scss
  24. 68 0
      src/components/ImageLazy/index.tsx
  25. 65 0
      src/components/LookDom/index.module.scss
  26. 52 0
      src/components/LookDom/index.tsx
  27. 29 0
      src/components/Message/index.tsx
  28. 30 0
      src/components/NotFound/index.tsx
  29. 10 0
      src/components/SpinLoding/index.module.scss
  30. 13 0
      src/components/SpinLoding/index.tsx
  31. 70 0
      src/index.tsx
  32. 67 0
      src/pages/A1Home/index.module.scss
  33. 37 0
      src/pages/A1Home/index.tsx
  34. 50 0
      src/pages/A1HomeM/index.module.scss
  35. 38 0
      src/pages/A1HomeM/index.tsx
  36. 21 0
      src/pages/A2VrPage/index.module.scss
  37. 25 0
      src/pages/A2VrPage/index.tsx
  38. 170 0
      src/pages/A3Goods/GoodsInfo/index.module.scss
  39. 224 0
      src/pages/A3Goods/GoodsInfo/index.tsx
  40. 7 0
      src/pages/A3Goods/data.ts
  41. 245 0
      src/pages/A3Goods/index.module.scss
  42. 211 0
      src/pages/A3Goods/index.tsx
  43. 172 0
      src/pages/A3GoodsM/GoodsInfoM/index.module.scss
  44. 226 0
      src/pages/A3GoodsM/GoodsInfoM/index.tsx
  45. 7 0
      src/pages/A3GoodsM/data.ts
  46. 195 0
      src/pages/A3GoodsM/index.module.scss
  47. 230 0
      src/pages/A3GoodsM/index.tsx
  48. 123 0
      src/pages/A4Intro/index.module.scss
  49. 127 0
      src/pages/A4Intro/index.tsx
  50. 113 0
      src/pages/A4IntroM/index.module.scss
  51. 141 0
      src/pages/A4IntroM/index.tsx
  52. 5 0
      src/pages/初始化组件/index.module.scss
  53. 14 0
      src/pages/初始化组件/index.tsx
  54. 20 0
      src/store/index.ts
  55. 14 0
      src/store/reducer/index.ts
  56. 62 0
      src/store/reducer/layout.ts
  57. 31 0
      src/types/api/layot.d.ts
  58. 8 0
      src/types/declaration.d.ts
  59. 2 0
      src/types/index.d.ts
  60. 13 0
      src/utils/domShow.ts
  61. 26 0
      src/utils/history.ts
  62. 4 0
      src/utils/http.ts
  63. 50 0
      src/utils/message.ts
  64. 27 0
      tsconfig.json

+ 23 - 0
.gitignore

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

+ 1 - 0
README.md

@@ -0,0 +1 @@
+本地运行:静态资源里面先运行一个服务 

+ 10 - 0
config-overrides.js

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

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


+ 64 - 0
package.json

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

+ 8 - 0
path.tsconfig.json

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

Разница между файлами не показана из-за своего большого размера
+ 6344 - 0
public/4dage.js


+ 53 - 0
public/index.html

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

+ 64 - 0
public/model.html

@@ -0,0 +1,64 @@
+<!DOCTYPE html>
+<html lang="en">
+
+<head>
+  <meta charset="UTF-8">
+  <meta http-equiv="X-UA-Compatible" content="IE=edge">
+  <meta name="viewport" content="width=device-width, initial-scale=1.0">
+  <script src="./4dage.js"></script>
+  <title>Document</title>
+  <style>
+    html {
+      overflow: hidden;
+    }
+
+    .bacBox {
+      opacity: 1;
+      pointer-events: auto;
+      position: absolute;
+      z-index: 998;
+      top: 0;
+      left: 0;
+      width: 100%;
+      height: 100%;
+      background-color: #cfcfd0;
+      transition: all 1s;
+    }
+  </style>
+</head>
+
+<body>
+  <div id="ui"></div>
+  <div class="bacBox"></div>
+  <script>
+    let number = getQueryVariable("m");
+    let num = getQueryVariable("n");
+    // console.log('ppppppppp',number);
+
+    window.autoRotate = true; // 是否自动旋转
+
+    // 打包配置
+
+    let src = ''
+
+    if (window.location.href.includes('localhost:')) {
+      // 本地环境
+      src = 'http://192.168.20.55:8080/'
+    } else {
+      // 正式环境
+      src = './staticData/'
+    }
+
+    // fdage.embed( number, {
+    fdage.embed(`${src}3Goods/${number}/main${num}.4dage`, {
+      transparentBackground: true,
+      width: 800,
+      height: 600,
+      autoStart: true,
+      fullFrame: true,
+      pagePreset: false
+    });
+  </script>
+</body>
+
+</html>

+ 78 - 0
src/App.tsx

@@ -0,0 +1,78 @@
+import "@/assets/styles/base.css";
+// 关于路由
+import React from "react";
+import { Router, Route, Switch } from "react-router-dom";
+import history from "./utils/history";
+import SpinLoding from "./components/SpinLoding";
+import AsyncSpinLoding from "./components/AsyncSpinLoding";
+import { Image } from "antd";
+import { useSelector } from "react-redux";
+import store, { RootState } from "./store";
+import MessageCom from "./components/Message";
+import NotFound from "./components/NotFound";
+const A1Home = React.lazy(() => import("./pages/A1Home"));
+const A2VrPage = React.lazy(() => import("./pages/A2VrPage"));
+const A3Goods = React.lazy(() => import("./pages/A3Goods"));
+const A4Intro = React.lazy(() => import("./pages/A4Intro"));
+
+export default function App() {
+  // 从仓库中获取查看图片的信息
+  const lookBigImg = useSelector(
+    (state: RootState) => state.A0Layout.lookBigImg
+  );
+
+  return (
+    <>
+      {/* 关于路由 */}
+      <Router history={history}>
+        <React.Suspense fallback={<SpinLoding />}>
+          <Switch>
+            {/* 首页 */}
+            <Route path="/" exact component={A1Home} />
+            {/* 实景vr页面 */}
+            <Route path="/vr" component={A2VrPage} />
+            {/* 文物赏析页面 */}
+            <Route path="/goods" component={A3Goods} />
+            {/* 场馆介绍页面 */}
+            <Route path="/intro" component={A4Intro} />
+
+            {/* 找不到页面 */}
+            <Route path="*" component={NotFound} />
+          </Switch>
+        </React.Suspense>
+      </Router>
+
+      {/* 发送请求的加载组件 */}
+      <AsyncSpinLoding />
+
+      {/* 所有图片点击预览查看大图 */}
+      <Image.PreviewGroup
+        preview={{
+          visible: lookBigImg.show,
+          current: lookBigImg.current,
+          onChange: (e) => {
+            store.dispatch({
+              type: "layout/lookBigImg",
+              payload: {
+                show: true,
+                url: lookBigImg.url,
+                current: e,
+              },
+            });
+          },
+          onVisibleChange: (value) => {
+            // 清除仓库信息
+            store.dispatch({
+              type: "layout/lookBigImg",
+              payload: { url: [], show: false, current: 0 },
+            });
+          },
+        }}
+        items={lookBigImg.url}
+      />
+
+      {/* antd 轻提示 ---兼容360浏览器 */}
+      <MessageCom />
+    </>
+  );
+}

+ 74 - 0
src/AppM.tsx

@@ -0,0 +1,74 @@
+import "@/assets/styles/base.css";
+// 关于路由
+import React, { useCallback, useEffect, useRef } from "react";
+import { Router, Route, Switch } from "react-router-dom";
+import history from "./utils/history";
+import SpinLoding from "./components/SpinLoding";
+import AsyncSpinLoding from "./components/AsyncSpinLoding";
+import NotFound from "./components/NotFound";
+import screenImg from "@/assets/img/landtip.png";
+
+const A1HomeM = React.lazy(() => import("./pages/A1HomeM"));
+const A2VrPage = React.lazy(() => import("./pages/A2VrPage"));
+const A3GoodsM = React.lazy(() => import("./pages/A3GoodsM"));
+const A4IntroM = React.lazy(() => import("./pages/A4IntroM"));
+
+export default function App() {
+  const setFullFu = useCallback(() => {
+    clearTimeout(time.current);
+    time.current = window.setTimeout(() => {
+      const dom: HTMLDivElement | null = document.querySelector("#root");
+      if (dom) {
+        dom.style.height = document.documentElement.clientHeight + "px";
+      }
+    }, 100);
+  }, []);
+
+  const time = useRef(-1);
+
+  useEffect(() => {
+    const dom: HTMLDivElement | null = document.querySelector("#root");
+    if (dom) {
+      dom.style.width = "100vw";
+      dom.style.minHeight = "auto";
+      dom.style.height = document.documentElement.clientHeight + "px";
+      dom.style.minWidth = "auto";
+    }
+    window.addEventListener("resize", setFullFu, true);
+
+    return () => {
+      window.removeEventListener("resize", setFullFu);
+    };
+  }, [setFullFu]);
+
+  return (
+    <>
+      {/* 关于路由 */}
+      <Router history={history}>
+        <React.Suspense fallback={<SpinLoding />}>
+          <Switch>
+            {/* 首页 */}
+            <Route path="/" exact component={A1HomeM} />
+            {/* 实景vr页面 */}
+            <Route path="/vr" component={A2VrPage} />
+            {/* 文物赏析页面 */}
+            <Route path="/goods" component={A3GoodsM} />
+            {/* 场馆介绍页面 */}
+            <Route path="/intro" component={A4IntroM} />
+            {/* 找不到页面 */}
+            <Route path="*" component={NotFound} />
+          </Switch>
+        </React.Suspense>
+      </Router>
+
+      {/* 发送请求的加载组件 */}
+      <AsyncSpinLoding />
+
+      {/* 横屏提示 */}
+      <div id="ScreenChange">
+        <img src={screenImg} alt="" />
+        <p>请在竖屏模式浏览</p>
+      </div>
+    </>
+  );
+}

BIN
src/assets/img/IMGerror.png


BIN
src/assets/img/back.png


BIN
src/assets/img/landtip.png


BIN
src/assets/img/loading.gif


BIN
src/assets/img/look.png


BIN
src/assets/img/name1.png


BIN
src/assets/img/name2.png


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

@@ -0,0 +1,132 @@
+* {
+  margin: 0;
+  padding: 0;
+  box-sizing: border-box;
+}
+html {
+  height: 100%;
+  font-size: 14px;
+  user-select: none;
+}
+body {
+  font: 1em/1.4 'Microsoft Yahei', 'PingFang SC', 'Avenir', 'Segoe UI', 'Hiragino Sans GB', 'STHeiti', 'Microsoft Sans Serif', 'WenQuanYi Micro Hei', sans-serif;
+  height: 100%;
+  color: black;
+}
+a {
+  text-decoration: none;
+  color: black;
+  outline: none;
+}
+i {
+  font-style: normal;
+}
+img {
+  max-width: 100%;
+  max-height: 100%;
+  vertical-align: middle;
+  object-fit: cover;
+}
+ul {
+  list-style: none;
+}
+body {
+  overflow: auto;
+  overflow-y: overlay;
+}
+/* 文本域取消下拉 */
+textarea {
+  resize: none !important;
+  min-height: 100px !important;
+}
+/* 主题色 */
+:root {
+  --themeColor: #bd130d;
+}
+/* 找不到页面 */
+.noFindPage {
+  opacity: 0;
+  transition: opacity 0.5s;
+}
+/* 兼容360浏览器的下拉框 */
+.ant-select-selector {
+  position: relative;
+  background-color: #ffffff;
+  border: 1px solid #d9d9d9;
+  transition: all 0.2s cubic-bezier(0.645, 0.045, 0.355, 1);
+}
+#root {
+  width: 100vw;
+  height: 100vh;
+  min-width: 1600px;
+  min-height: 900px;
+  overflow: auto;
+  overflow-y: overlay;
+  /* antd图片预览组件 */
+}
+#root > div {
+  width: 100%;
+  height: 100%;
+}
+#root .ant-image {
+  display: none;
+}
+[hidden] {
+  display: none !important;
+}
+.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;
+}
+.AppM {
+  width: 100vw;
+  max-width: 500px;
+  margin: 0 auto;
+  overflow: hidden;
+}
+#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;
+  }
+}

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

@@ -0,0 +1,174 @@
+* {
+  margin: 0;
+  padding: 0;
+  box-sizing: border-box;
+}
+
+html {
+  height: 100%;
+  font-size: 14px;
+  user-select: none;
+}
+
+body {
+  font: 1em/1.4 'Microsoft Yahei', 'PingFang SC', 'Avenir', 'Segoe UI', 'Hiragino Sans GB', 'STHeiti', 'Microsoft Sans Serif', 'WenQuanYi Micro Hei', sans-serif;
+  height: 100%;
+  color: black;
+}
+
+a {
+  text-decoration: none;
+  color: black;
+  outline: none;
+}
+
+i {
+  font-style: normal;
+}
+
+img {
+  max-width: 100%;
+  max-height: 100%;
+  vertical-align: middle;
+  object-fit: cover;
+}
+
+ul {
+  list-style: none;
+}
+
+body {
+  overflow: auto;
+  overflow-y: overlay;
+}
+
+/* 文本域取消下拉 */
+textarea {
+  resize: none !important;
+  min-height: 100px !important;
+}
+
+/* 主题色 */
+:root {
+  --themeColor: #bd130d;
+}
+
+
+
+
+
+/* 找不到页面 */
+.noFindPage {
+  opacity: 0;
+  transition: opacity .5s;
+}
+
+
+/* 兼容360浏览器的下拉框 */
+.ant-select-selector {
+  position: relative;
+  background-color: #ffffff;
+  border: 1px solid #d9d9d9;
+  transition: all 0.2s cubic-bezier(0.645, 0.045, 0.355, 1);
+}
+
+
+// 重置antd样式
+#root {
+  width: 100vw;
+  height: 100vh;
+  min-width: 1600px;
+  min-height: 900px;
+  overflow: auto;
+  overflow-y: overlay;
+
+  &>div {
+    width: 100%;
+    height: 100%;
+  }
+
+  // a {
+  //   color: var(--themeColor);
+  // }
+
+
+  /* antd图片预览组件 */
+  .ant-image {
+    display: none;
+  }
+
+}
+
+
+[hidden] {
+  display: none !important;
+}
+
+
+
+// 滚动条
+.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;
+}
+
+
+.AppM {
+  width: 100vw;
+  max-width: 500px;
+  margin: 0 auto;
+  overflow: hidden;
+}
+
+// 横屏 竖屏的切换
+#ScreenChange {
+  position: fixed;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  z-index: 10000;
+  background-color: rgba(0, 0, 0, .8);
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+  align-items: center;
+  opacity: 0;
+  pointer-events: none;
+  transition: all .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;
+        }
+      }
+    }
+  }
+
+}

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

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

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

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

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

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

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

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

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

@@ -0,0 +1,30 @@
+import history from "@/utils/history";
+import { Button, Result } from "antd";
+import { useEffect, useRef } from "react";
+
+export default function NotFound() {
+  const timeRef = useRef(-1);
+
+  useEffect(() => {
+    timeRef.current = window.setTimeout(() => {
+      const dom: HTMLDivElement = document.querySelector(".noFindPage")!;
+      dom.style.opacity = "1";
+    }, 300);
+    return () => {
+      clearTimeout(timeRef.current);
+    };
+  }, []);
+
+  return (
+    <div className="noFindPage">
+      <Result status="404" title="404" subTitle="找不到页面" />
+      <div style={{ display: "flex", justifyContent: "center" }}>
+        <Button type="primary" onClick={() => history.push("/")}>
+          首页
+        </Button>
+        &emsp;
+        <Button onClick={() => history.go(-1)}>返回</Button>
+      </div>
+    </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;

+ 70 - 0
src/index.tsx

@@ -0,0 +1,70 @@
+// import 'default-passive-events';
+import { baseURL } from "./utils/http";
+
+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";
+import { isMobiileFu } from "./utils/history";
+import AppM from "./AppM";
+
+const container = document.getElementById("root") as HTMLElement;
+const root = createRoot(container);
+
+console.log("静态资源地址:", baseURL);
+// @ts-ignore
+store.dispatch({ type: "layout/setDataAll", payload: dataAll });
+
+if (isMobiileFu()) {
+  root.render(
+    <ConfigProvider
+      locale={locale}
+      theme={{
+        token: {
+          colorPrimary: "#bd130d",
+        },
+      }}
+    >
+      <Provider store={store}>
+        <StyleProvider
+          hashPriority="high"
+          transformers={[legacyLogicalPropertiesTransformer]}
+        >
+          <AppM />
+        </StyleProvider>
+      </Provider>
+    </ConfigProvider>
+  );
+} else {
+  root.render(
+    <ConfigProvider
+      locale={locale}
+      theme={{
+        token: {
+          colorPrimary: "#bd130d",
+        },
+      }}
+    >
+      <Provider store={store}>
+        <StyleProvider
+          hashPriority="high"
+          transformers={[legacyLogicalPropertiesTransformer]}
+        >
+          <App />
+        </StyleProvider>
+      </Provider>
+    </ConfigProvider>
+  );
+}

+ 67 - 0
src/pages/A1Home/index.module.scss

@@ -0,0 +1,67 @@
+.A1Home {
+  background-size: 100% 100%;
+  position: relative;
+
+  :global {
+    .nameImg {
+      position: absolute;
+      z-index: 10;
+      left: 50px;
+      top: 30px;
+    }
+
+    .txtImg {
+      position: absolute;
+      z-index: 10;
+      top: 230px;
+      left: 100px;
+      width: 80%;
+    }
+
+    .txt2Img {
+      position: absolute;
+      z-index: 10;
+      bottom: 45px;
+      left: 50px;
+    }
+
+    .txt2Iccmg {
+      height: 100%;
+    }
+
+    .rightBacBox {
+      position: absolute;
+      z-index: 10;
+      right: 0;
+      top: 0;
+      height: 100%;
+      width: 306px;
+      background-size: 100% 100%;
+      padding: 20px 10px 20px 145px;
+      display: flex;
+      flex-direction: column;
+      justify-content: space-around;
+
+      &>div {
+        cursor: pointer;
+        width: 150px;
+        height: 150px;
+        border: 1px solid red;
+        border-radius: 50%;
+        overflow: hidden;
+
+        &>img {
+          width: 100%;
+          height: 100%;
+          object-fit: contain;
+          transition: all .2s;
+        }
+        &:hover{
+          &>img{
+            transform: scale(1.1);
+          }
+        }
+      }
+    }
+  }
+}

+ 37 - 0
src/pages/A1Home/index.tsx

@@ -0,0 +1,37 @@
+import React from "react";
+import styles from "./index.module.scss";
+import { baseURL } from "@/utils/http";
+import history from "@/utils/history";
+
+const rightList = [
+  { id: 1, name: "实景VR", path: "/vr" },
+  { id: 2, name: "文物赏析", path: "/goods" },
+  { id: 3, name: "场馆介绍", path: "/intro" },
+];
+
+function A1Home() {
+  return (
+    <div
+      className={styles.A1Home}
+      style={{ backgroundImage: `url(${baseURL}/Home/pc/bac.jpg)` }}
+    >
+      <img className="nameImg" src={`${baseURL}/Home/pc/name.png`} alt="" />
+      <img className="txtImg" src={`${baseURL}/Home/pc/txt.png`} alt="" />
+      <img className="txt2Img" src={`${baseURL}/Home/pc/txt2.png`} alt="" />
+      <div
+        className="rightBacBox"
+        style={{ backgroundImage: `url(${baseURL}/Home/pc/rightBac.png)` }}
+      >
+        {rightList.map((v) => (
+          <div onClick={() => history.push(v.path)} key={v.id}>
+            <img src={`${baseURL}/Home/pc/icon${v.id}.png`} alt="" />
+          </div>
+        ))}
+      </div>
+    </div>
+  );
+}
+
+const MemoA1Home = React.memo(A1Home);
+
+export default MemoA1Home;

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

@@ -0,0 +1,50 @@
+.A1HomeM {
+  background-size: 100% 100%;
+  position: relative;
+
+  :global {
+    .nameImg {
+      position: absolute;
+      z-index: 3;
+      top: 10px;
+      left: 10px;
+      width: 40px;
+    }
+
+    .txtImg {
+      position: absolute;
+      z-index: 3;
+      top: 70px;
+      left: 50%;
+      transform: translateX(-50%);
+      width: 94%;
+      margin: 0 auto;
+    }
+
+    .txt2Img {
+      position: absolute;
+      z-index: 3;
+      bottom: 150px;
+      left: 20px;
+      width: 80%;
+    }
+
+    .rightBacBox {
+      position: absolute;
+      left: 0;
+      width: 100%;
+      bottom: 0;
+      height: 150px;
+      background-size: 100% 100%;
+      display: flex;
+      align-items: flex-end;
+      justify-content: space-around;
+      padding-bottom: 10px;
+
+      &>div {
+        width: 80px;
+        height: 80px;
+      }
+    }
+  }
+}

+ 38 - 0
src/pages/A1HomeM/index.tsx

@@ -0,0 +1,38 @@
+import React from "react";
+import styles from "./index.module.scss";
+import { baseURL } from "@/utils/http";
+import history from "@/utils/history";
+
+const rightList = [
+  { id: 1, name: "实景VR", path: "/vr" },
+  { id: 2, name: "文物赏析", path: "/goods" },
+  { id: 3, name: "场馆介绍", path: "/intro" },
+];
+
+function A1HomeM() {
+  return (
+    <div
+      className={styles.A1HomeM}
+      style={{ backgroundImage: `url(${baseURL}/Home/mobile/bac.jpg)` }}
+    >
+      <img className="nameImg" src={`${baseURL}/Home/pc/name.png`} alt="" />
+      <img className="txtImg" src={`${baseURL}/Home/mobile/txt.png`} alt="" />
+      <img className="txt2Img" src={`${baseURL}/Home/mobile/txt2.png`} alt="" />
+
+      <div
+        className="rightBacBox"
+        style={{ backgroundImage: `url(${baseURL}/Home/mobile/rightBac.png)` }}
+      >
+        {rightList.map((v) => (
+          <div onClick={() => history.push(v.path)} key={v.id}>
+            <img src={`${baseURL}/Home/pc/icon${v.id}.png`} alt="" />
+          </div>
+        ))}
+      </div>
+    </div>
+  );
+}
+
+const MemoA1HomeM = React.memo(A1HomeM);
+
+export default MemoA1HomeM;

+ 21 - 0
src/pages/A2VrPage/index.module.scss

@@ -0,0 +1,21 @@
+.A2VrPage{
+  position: relative;
+  :global{
+    iframe{
+      position: absolute;
+      top: 0;
+      left: 0;
+      z-index: 2;
+      width: 100%;
+      height: 100%;
+    }
+    .vrBack{
+      cursor: pointer;
+      position: absolute;
+      top: 10px;
+      left: 40px;
+      z-index: 3;
+      width: 40px;
+    }
+  }
+}

+ 25 - 0
src/pages/A2VrPage/index.tsx

@@ -0,0 +1,25 @@
+/* eslint-disable jsx-a11y/iframe-has-title */
+import React from "react";
+import styles from "./index.module.scss";
+import { useSelector } from "react-redux";
+import { RootState } from "@/store";
+import backImg from "@/assets/img/back.png";
+import history from "@/utils/history";
+
+function A2VrPage() {
+  const data = useSelector((state: RootState) => state.A0Layout.dataAll.Home);
+
+  return (
+    <div className={styles.A2VrPage}>
+      {data.vr ? <iframe src={data.vr} frameBorder="0"></iframe> : null}
+      {/* 返回按钮 */}
+      <div className="vrBack" onClick={() => history.push("/")}>
+        <img src={backImg} alt="" />
+      </div>
+    </div>
+  );
+}
+
+const MemoA2VrPage = React.memo(A2VrPage);
+
+export default MemoA2VrPage;

+ 170 - 0
src/pages/A3Goods/GoodsInfo/index.module.scss

@@ -0,0 +1,170 @@
+.GoodsInfo {
+  position: absolute;
+  z-index: 99;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  background-size: 100% 100%;
+  padding: 140px 125px 180px;
+  overflow: hidden;
+
+  :global {
+    .A3IBack {
+      width: 48px;
+      cursor: pointer;
+      position: absolute;
+      z-index: 3;
+      top: 20px;
+      left: 40px;
+    }
+
+    .A3Imain {
+      width: 100%;
+      height: 100%;
+      display: flex;
+      justify-content: space-between;
+
+      .A3Ileft {
+        width: 54%;
+        background-color: #8e9596;
+        border-radius: 30px;
+        position: relative;
+
+        iframe {
+          border-radius: 30px;
+          width: 100%;
+          height: 100%;
+        }
+
+        .A3IleftBtn {
+          position: absolute;
+          bottom: 15px;
+          right: 12px;
+          z-index: 10;
+          display: flex;
+
+          &>div {
+            width: 40px;
+            height: 40px;
+            border-radius: 50%;
+            background-color: rgba(0, 0, 0, .6);
+            margin-left: 18px;
+            display: flex;
+            justify-content: center;
+            align-items: center;
+            color: #fff;
+            font-size: 24px;
+            cursor: pointer;
+          }
+        }
+
+        .A3ItoBtn {
+          cursor: pointer;
+          position: absolute;
+          z-index: 10;
+          top: 50%;
+          transform: translateY(-50%);
+          left: -50px;
+          width: 41px;
+          height: 81px;
+          background-size: 100% 100%;
+
+        }
+
+        .A3ItoRight {
+          left: auto;
+          right: -50px;
+        }
+
+        .noneToBtn {
+          opacity: .5;
+          pointer-events: none;
+        }
+      }
+
+      // 全屏
+      .A3IleftFull {
+        position: absolute;
+        top: 0;
+        left: 0;
+        width: 100%;
+        height: 100%;
+        z-index: 40;
+
+        iframe {
+          border-radius: 0;
+        }
+      }
+
+      .A3IRight {
+        width: 45%;
+        padding: 40px 0px 0;
+
+        .A3IRtit {
+          display: flex;
+          justify-content: center;
+
+          &>img {
+            width: 140px;
+            height: 44px;
+            position: relative;
+            top: -18px;
+          }
+
+          &>div {
+            color: #FBF1AD;
+            font-size: 22px;
+            font-weight: 700;
+            margin: 0 10px;
+            max-width: calc(100% - 300px);
+            text-align: center;
+            letter-spacing: 4px;
+            margin-bottom: 15px;
+          }
+        }
+
+        .A3IRtit2 {
+          color: #FBF1AD;
+          font-size: 18px;
+          text-align: center;
+          letter-spacing: 4px;
+          margin-bottom: 40px;
+        }
+
+        .A3IRtitRow {
+          padding: 0 0 0 240px;
+          position: relative;
+          margin-top: 30px;
+          font-size: 18px;
+          color: #fff;
+          letter-spacing: 2px;
+
+          &>span {
+            font-weight: 700;
+          }
+
+          .A3IRtitRowYuan {
+            position: absolute;
+            left: 210px;
+            top: 5px;
+            width: 14px;
+            height: 14px;
+            border-radius: 50%;
+            border: 1px solid #fff;
+
+            &>div {
+              position: relative;
+              top: 2px;
+              left: 2px;
+              width: 8px;
+              height: 8px;
+              background-color: #fff;
+              border-radius: 50%;
+            }
+          }
+        }
+      }
+    }
+  }
+}

+ 224 - 0
src/pages/A3Goods/GoodsInfo/index.tsx

@@ -0,0 +1,224 @@
+/* eslint-disable jsx-a11y/iframe-has-title */
+import React, { useMemo, useRef, useState } from "react";
+import styles from "./index.module.scss";
+import { Goods } from "@/types";
+import { baseURL } from "@/utils/http";
+import { type1Arr1 } from "../data";
+import backImg from "@/assets/img/back.png";
+import {
+  PlusOutlined,
+  MinusOutlined,
+  FullscreenOutlined,
+  FullscreenExitOutlined,
+} from "@ant-design/icons";
+import classNames from "classnames";
+
+import name1Img from "@/assets/img/name1.png";
+import name2Img from "@/assets/img/name2.png";
+
+type Props = {
+  info: Goods;
+  colseFu: () => void;
+  type: string;
+};
+
+function GoodsInfo({ info, colseFu, type }: Props) {
+  // 背景图
+  const imgBac = useMemo(() => {
+    const info = type1Arr1.find((v) => v.name === type);
+    if (info) return `info${info.id}Bac.jpg`;
+    else return "";
+  }, [type]);
+
+  // 看看有几个模型
+  const modelNum = useMemo(() => {
+    const arr = [1];
+
+    if (info.modelNum && info.modelNum > 1) {
+      for (let i = 0; i < info.modelNum; i++) {
+        if (i !== 0) arr.push(i + 1);
+      }
+    }
+    return arr;
+  }, [info.modelNum]);
+
+  // 当前显示的模型
+  const [modelShow, setModelShow] = useState(1);
+
+  // 模型的全屏
+  const [full, setFull] = useState(false);
+
+  // 控制模型放大缩小和复位
+  const ifrBoxRef = useRef<any>(null);
+  const modelChangeFu = (val: number) => {
+    const dom = ifrBoxRef.current;
+
+    if (dom && dom.contentWindow && dom.contentWindow.webview) {
+      if (val === 1) dom.contentWindow.webview.zoomIn(); // 放大
+      else if (val === 2) dom.contentWindow.webview.zoomOut(); // 缩小
+      else dom.contentWindow.webview.resetView(); // 复位
+    }
+  };
+
+  return (
+    <div
+      className={styles.GoodsInfo}
+      style={{
+        backgroundImage: `url(${baseURL}/3Goods/pc/${imgBac})`,
+      }}
+    >
+      <img onClick={colseFu} className="A3IBack" src={backImg} alt="" />
+
+      <div className="A3Imain">
+        {/* 左边模型 */}
+        <div
+          className={classNames("A3Ileft", full ? "A3IleftFull" : "")}
+          key={modelShow}
+        >
+          {modelNum.map((v) =>
+            modelShow === v ? (
+              <iframe
+                key={v}
+                ref={ifrBoxRef}
+                src={`model.html?m=${info.id}&n=${v}`}
+                frameBorder="no"
+              ></iframe>
+            ) : null
+          )}
+
+          {/* 左右按钮 */}
+          <div
+            onClick={() => setModelShow(modelShow - 1)}
+            hidden={modelNum.length <= 1}
+            style={{ backgroundImage: `url(${baseURL}/4Intro/pc/left.png)` }}
+            className={classNames(
+              "A3ItoBtn",
+              "A3ItoLeft",
+              modelShow === 1 ? "noneToBtn" : ""
+            )}
+          ></div>
+          <div
+            onClick={() => setModelShow(modelShow + 1)}
+            hidden={modelNum.length <= 1}
+            style={{ backgroundImage: `url(${baseURL}/4Intro/pc/right.png)` }}
+            className={classNames(
+              "A3ItoBtn",
+              "A3ItoRight",
+              modelShow === modelNum.length ? "noneToBtn" : ""
+            )}
+          ></div>
+
+          {/* 右下角的按钮 */}
+          <div className="A3IleftBtn">
+            <div onClick={() => modelChangeFu(1)}>
+              <PlusOutlined rev={undefined} />
+            </div>
+            <div onClick={() => modelChangeFu(2)}>
+              <MinusOutlined rev={undefined} />
+            </div>
+            {full ? (
+              <div onClick={() => setFull(false)}>
+                <FullscreenExitOutlined rev={undefined} />
+              </div>
+            ) : (
+              <div onClick={() => setFull(true)}>
+                <FullscreenOutlined rev={undefined} />
+              </div>
+            )}
+          </div>
+        </div>
+
+        {/* 右边简介 */}
+        <div className="A3IRight">
+          <div className="A3IRtit">
+            <img src={name1Img} alt="" />
+            <div> {info.name}</div>
+
+            <img src={name2Img} alt="" />
+          </div>
+          <div className="A3IRtit2">存放于{info.type1}</div>
+
+          {info.name2 ? (
+            <div className="A3IRtitRow">
+              <div className="A3IRtitRowYuan">
+                <div></div>
+              </div>
+              <span>名称:</span>
+              {info.name2}
+            </div>
+          ) : null}
+
+          {info.num ? (
+            <div className="A3IRtitRow">
+              <div className="A3IRtitRowYuan">
+                <div></div>
+              </div>
+              <span>编号:</span>
+              {info.num}
+            </div>
+          ) : null}
+
+          {info.type2 ? (
+            <div className="A3IRtitRow">
+              <div className="A3IRtitRowYuan">
+                <div></div>
+              </div>
+              <span>类别:</span>
+              {info.type2}
+            </div>
+          ) : null}
+
+          {info.age ? (
+            <div className="A3IRtitRow">
+              <div className="A3IRtitRowYuan">
+                <div></div>
+              </div>
+              <span>年代:</span>
+              {info.age}
+            </div>
+          ) : null}
+          {info.grain ? (
+            <div className="A3IRtitRow">
+              <div className="A3IRtitRowYuan">
+                <div></div>
+              </div>
+              <span>质地:</span>
+              {info.grain}
+            </div>
+          ) : null}
+          {info.size ? (
+            <div className="A3IRtitRow">
+              <div className="A3IRtitRowYuan">
+                <div></div>
+              </div>
+              <span>尺寸:</span>
+              {info.size}
+            </div>
+          ) : null}
+          {info.level ? (
+            <div className="A3IRtitRow">
+              <div className="A3IRtitRowYuan">
+                <div></div>
+              </div>
+              <span>级别:</span>
+              {info.level}
+            </div>
+          ) : null}
+          {info.state ? (
+            <div className="A3IRtitRow">
+              <div className="A3IRtitRowYuan">
+                <div></div>
+              </div>
+              <span>状态:</span>
+              {info.state}
+            </div>
+          ) : null}
+        </div>
+      </div>
+    </div>
+  );
+}
+
+const MemoGoodsInfo = React.memo(GoodsInfo);
+
+export default MemoGoodsInfo;

+ 7 - 0
src/pages/A3Goods/data.ts

@@ -0,0 +1,7 @@
+export const type1Arr1 = [
+  { id: 0, name: "全部" },
+  { id: 1, name: "董必武纪念馆" },
+  { id: 2, name: "李先念纪念馆" },
+  { id: 3, name: "历史馆" },
+  { id: 4, name: "烈士馆" },
+];

+ 245 - 0
src/pages/A3Goods/index.module.scss

@@ -0,0 +1,245 @@
+.A3Goods {
+  background-size: 100% 100%;
+  position: relative;
+
+  :global {
+    .A3top {
+      width: 100%;
+      height: 160px;
+      background-size: 100% 100%;
+      position: relative;
+      padding: 18px 40px 0 118px;
+      display: flex;
+      justify-content: space-between;
+
+      .A3Back {
+        width: 48px;
+        cursor: pointer;
+        position: absolute;
+        z-index: 3;
+        top: 20px;
+        left: 40px;
+      }
+
+      .A3leftBox {
+        border-radius: 25px;
+        width: 702px;
+        height: 50px;
+        display: flex;
+        background-color: rgba(255, 255, 255, .9);
+
+        &>div {
+          cursor: pointer;
+          height: 50px;
+          line-height: 48px;
+          padding: 0 30px;
+          font-size: 20px;
+          font-weight: 700;
+          color: var(--themeColor);
+        }
+
+        .active {
+          background-color: #f1cead;
+          border-radius: 30px;
+          border: 1px solid var(--themeColor);
+        }
+      }
+
+      .A3RightBox {
+        width: 630px;
+        display: flex;
+
+        .A3RightBox1 {
+          height: 50px;
+
+          .ant-select {
+            height: 50px;
+
+            .ant-select-selector {
+
+              height: 100%;
+              background-color: rgba(255, 255, 255, .9);
+              border-radius: 25px;
+
+              .ant-select-selection-item {
+                line-height: 48px;
+                font-size: 20px;
+                font-weight: 700;
+                color: var(--themeColor);
+                text-align: center;
+              }
+            }
+
+            .ant-select-arrow {
+              line-height: 48px;
+              font-size: 20px;
+              font-weight: 700;
+              color: var(--themeColor);
+            }
+          }
+        }
+
+        .A3RightBox2 {
+          margin-left: 30px;
+          width: 400px;
+          height: 50px;
+          position: relative;
+
+          input::-webkit-input-placeholder {
+            /* WebKit browsers */
+            color: var(--themeColor);
+            opacity: .8;
+          }
+
+          input:-moz-placeholder {
+            /* Mozilla Firefox 4 to 18 */
+            color: var(--themeColor);
+            opacity: .8;
+          }
+
+          input::-moz-placeholder {
+            /* Mozilla Firefox 19+ */
+            color: var(--themeColor);
+            opacity: .8;
+          }
+
+          input:-ms-input-placeholder {
+            /* Internet Explorer 10+ */
+            color: var(--themeColor);
+            opacity: .8;
+          }
+
+          .ant-input {
+            font-weight: 700;
+            height: 50px;
+            line-height: 48px;
+            font-size: 20px;
+            padding-right: 40px;
+            padding-left: 20px;
+            border-radius: 25px;
+            background-color: rgba(255, 255, 255, .9);
+            color: var(--themeColor);
+          }
+
+          .A3searchIcon {
+            position: absolute;
+            top: 0;
+            right: 0;
+            width: 40px;
+            height: 100%;
+            cursor: pointer;
+            text-align: center;
+            line-height: 48px;
+            font-size: 20px;
+            color: var(--themeColor);
+          }
+
+        }
+      }
+    }
+
+
+    .noRow {
+      width: 100%;
+      height: calc(100% - 160px);
+      display: flex;
+      justify-content: center;
+      align-items: center;
+      font-size: 40px;
+      color: #fff;
+      padding-bottom: 80px;
+    }
+
+    .A3Main {
+      width: 100%;
+      height: calc(100% - 160px);
+      padding-bottom: 100px;
+      display: flex;
+      flex-wrap: wrap;
+      padding-left: 2%;
+      position: relative;
+
+      .A3Row {
+        width: 23%;
+        height: 48%;
+        margin-right: 2%;
+        margin-bottom: 2%;
+        position: relative;
+        border-radius: 6px;
+        overflow: hidden;
+        background-color: #fff;
+
+        &:nth-of-type(4n) {
+          margin-right: 0;
+        }
+
+        &>img {
+          width: 100%;
+          height: 100%;
+          object-fit: cover;
+        }
+
+        .A3RowHo {
+          opacity: 0;
+          transition: all 0.8s;
+          cursor: pointer;
+          position: absolute;
+          top: 0;
+          left: 0;
+          z-index: 30;
+          width: 100%;
+          height: 100%;
+          background-color: rgba(0, 0, 0, .7);
+          display: flex;
+          justify-content: center;
+          align-items: center;
+          font-size: 18px;
+          color: #fff;
+          padding: 20px;
+          text-align: center;
+
+          &:hover {
+            opacity: 1;
+          }
+        }
+      }
+
+      .A3page {
+        position: absolute;
+        left: 0;
+        bottom: 30px;
+        width: 100%;
+        display: flex;
+        justify-content: center;
+
+        .ant-pagination-item a {
+          color: #fff;
+        }
+
+        .ant-pagination-item-active a {
+          color: var(--themeColor);
+        }
+
+        .ant-pagination-prev button {
+          color: #fff !important;
+        }
+
+        .ant-pagination-next button {
+          color: #fff !important;
+        }
+
+        .ant-pagination-disabled {
+          opacity: .5;
+        }
+
+        .ant-pagination-options-quick-jumper {
+          color: #fff;
+
+          &>input {
+            color: var(--themeColor);
+          }
+        }
+      }
+    }
+  }
+}

+ 211 - 0
src/pages/A3Goods/index.tsx

@@ -0,0 +1,211 @@
+import React, {
+  useCallback,
+  useEffect,
+  useMemo,
+  useRef,
+  useState,
+} from "react";
+import styles from "./index.module.scss";
+import { useSelector } from "react-redux";
+import { RootState } from "@/store";
+import backImg from "@/assets/img/back.png";
+import { baseURL } from "@/utils/http";
+import history from "@/utils/history";
+import classNames from "classnames";
+import { Input, Pagination, Select } from "antd";
+import { SearchOutlined } from "@ant-design/icons";
+import ImageLazy from "@/components/ImageLazy";
+import _ from "lodash";
+import GoodsInfo from "./GoodsInfo";
+import { Goods } from "@/types";
+import { type1Arr1 } from "./data";
+
+function A3Goods() {
+  const dataTemp = useSelector(
+    (state: RootState) => state.A0Layout.dataAll.goods
+  );
+
+  const data = useMemo(() => {
+    if (dataTemp) return dataTemp;
+    else return [];
+  }, [dataTemp]);
+
+  // 左侧馆的筛选
+  const [type1, setType1] = useState("全部");
+
+  // 背景图
+  const imgBac = useMemo(() => {
+    const info = type1Arr1.find((v) => v.name === type1);
+    if (info) return `tab${info.id}Bac.jpg`;
+    else return "";
+  }, [type1]);
+
+  // 右侧类别的筛选
+  const [type2, setType2] = useState("全部类别");
+
+  // 右侧类别的下拉框
+  const type2Arr = useMemo(() => {
+    const arr = ["全部类别"];
+    data.forEach((v) => {
+      if (!arr.includes(v.type2)) arr.push(v.type2);
+    });
+    return arr;
+  }, [data]);
+
+  // 输入框
+  const inputRef = useRef<any>(null);
+  const [searchKey, setSearchKey] = useState(-1);
+
+  // 页码
+  const [page, setPage] = useState(1);
+
+  // 在页面渲染的数据
+  const dataRes = useMemo(() => {
+    console.log(searchKey);
+
+    // 所有数据
+    let arr = [...data];
+
+    // 筛选 左侧
+    if (type1 !== "全部") arr = arr.filter((v) => v.type1 === type1);
+
+    // 筛选 右侧
+    if (type2 !== "全部类别") arr = arr.filter((v) => v.type2 === type2);
+
+    if (inputRef.current) {
+      const val = inputRef.current.input.value.trim();
+      arr = arr.filter((v) => v.name.includes(val) || !val);
+    }
+    // 分页(8条一页)
+    const arrs = _.chunk(arr, 8);
+
+    return { arrs, total: arr.length };
+  }, [data, searchKey, type1, type2]);
+
+  // 筛选和输入框变化的时候,page变成1
+  useEffect(() => {
+    setPage(1);
+  }, [type1, type2, searchKey]);
+
+  // 分页器
+  const pageChangeFu = useCallback((pageNum: number) => {
+    setPage(pageNum);
+  }, []);
+
+  // 查看文物详情
+  const [goodsInfo, setGoodsInfo] = useState({} as Goods);
+
+  return (
+    <div
+      className={styles.A3Goods}
+      style={{
+        backgroundImage: `url(${baseURL}/3Goods/pc/${imgBac})`,
+      }}
+    >
+      {/* 顶部 */}
+      <div
+        className="A3top"
+        style={{ backgroundImage: `url(${baseURL}/3Goods/pc/top.png)` }}
+      >
+        <img
+          onClick={() => history.push("/")}
+          className="A3Back"
+          src={backImg}
+          alt=""
+        />
+
+        {/* 左侧筛选 */}
+        <div className="A3leftBox">
+          {type1Arr1.map((v) => (
+            <div
+              onClick={() => setType1(v.name)}
+              className={classNames(v.name === type1 ? "active" : "")}
+              key={v.id}
+            >
+              {v.name}
+            </div>
+          ))}
+        </div>
+
+        {/* 右侧筛选 */}
+        <div className="A3RightBox">
+          <div className="A3RightBox1">
+            <Select
+              style={{ width: 200 }}
+              value={type2}
+              onChange={(e) => setType2(e)}
+              options={type2Arr.map((v) => ({ value: v, label: v }))}
+            />
+          </div>
+          <div
+            className="A3RightBox2"
+            onKeyUp={(e) => {
+              if (e.key === "Enter") setSearchKey(Date.now());
+            }}
+          >
+            <Input ref={inputRef} placeholder="搜索文物" maxLength={15} />
+            {/* 搜索图标 */}
+            <div
+              className="A3searchIcon"
+              onClick={() => setSearchKey(Date.now())}
+            >
+              <SearchOutlined rev={undefined} />
+            </div>
+          </div>
+        </div>
+      </div>
+      {/* 主体 */}
+      {dataRes.arrs &&
+      dataRes.arrs[page - 1] &&
+      dataRes.arrs[page - 1].length ? (
+        <div className="A3Main">
+          {dataRes.arrs[page - 1].map((v) => (
+            <div className="A3Row" key={v.id}>
+              <ImageLazy
+                src={`${baseURL}/3Goods/${v.id}/main.jpg`}
+                width="100%"
+                height="100%"
+                noLook
+                offline
+              />
+              <div
+                className="A3RowHo"
+                title={v.name}
+                onClick={() => setGoodsInfo(v)}
+              >
+                {v.name}
+              </div>
+            </div>
+          ))}
+          {/* 分页器 */}
+          <div className="A3page">
+            <Pagination
+              showQuickJumper
+              current={page}
+              total={dataRes.total}
+              pageSize={8}
+              hideOnSinglePage={true}
+              onChange={pageChangeFu}
+              showSizeChanger={false}
+            />
+          </div>
+        </div>
+      ) : (
+        <div className="noRow">暂无数据</div>
+      )}
+
+      {/* 查看文物详情 */}
+      {goodsInfo.id ? (
+        <GoodsInfo
+          type={type1}
+          info={goodsInfo}
+          colseFu={() => setGoodsInfo({} as Goods)}
+        />
+      ) : null}
+    </div>
+  );
+}
+
+const MemoA3Goods = React.memo(A3Goods);
+
+export default MemoA3Goods;

+ 172 - 0
src/pages/A3GoodsM/GoodsInfoM/index.module.scss

@@ -0,0 +1,172 @@
+.GoodsInfoM {
+  position: absolute;
+  z-index: 99;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  background-size: 100% 100%;
+  padding: 56px 15px 15px 15px;
+  overflow: hidden;
+
+  :global {
+
+    .GoodsInfoMmian {
+      width: 100%;
+      height: 100%;
+      overflow-y: auto;
+
+      &::-webkit-scrollbar {
+        width: 2px;
+      }
+
+    }
+
+    .A3IBack {
+      position: absolute;
+      z-index: 30;
+      top: 15px;
+      left: 15px;
+      width: 40px;
+    }
+
+    .A3IRtit {
+      display: flex;
+      justify-content: center;
+      position: relative;
+
+      &>img {
+        width: 80px;
+        height: 30px;
+        position: relative;
+        top: -12px;
+      }
+
+      &>div {
+        color: #FBF1AD;
+        font-size: 18px;
+        font-weight: 700;
+        text-align: center;
+        letter-spacing: 2px;
+        padding: 0 10px;
+        max-width: calc(100% - 160px);
+      }
+    }
+
+    .A3IRtit2 {
+      color: #FBF1AD;
+      font-size: 16px;
+      text-align: center;
+      letter-spacing: 2px;
+      margin-top: 8px;
+      margin-bottom: 20px;
+    }
+
+    .ifrBox {
+      width: calc(100% - 10px);
+      height: 300px;
+      padding: 0 30px;
+      position: relative;
+
+      .ifrBoxson {
+        width: 100%;
+        height: 100%;
+        background-color: #8e9596;
+        position: relative;
+
+        iframe {
+          width: 100%;
+          height: 100%;
+        }
+
+        .A3IleftBtn {
+          position: absolute;
+          bottom: 10px;
+          right: 10px;
+          z-index: 10;
+          display: flex;
+
+          &>div {
+            width: 30px;
+            height: 30px;
+            border-radius: 50%;
+            background-color: rgba(0, 0, 0, .6);
+            margin-left: 10px;
+            display: flex;
+            justify-content: center;
+            align-items: center;
+            color: #fff;
+            font-size: 20px;
+            cursor: pointer;
+          }
+        }
+      }
+
+      .A3IleftFull {
+        position: fixed;
+        top: 0;
+        left: 0;
+        width: 100%;
+        height: 100%;
+        z-index: 40;
+      }
+
+      .A3ItoBtn {
+        cursor: pointer;
+        position: absolute;
+        z-index: 10;
+        top: 50%;
+        transform: translateY(-50%);
+        left: 0px;
+        width: 30px;
+        height: 60px;
+        background-size: 100% 100%;
+
+      }
+
+      .A3ItoRight {
+        left: auto;
+        right: 0px;
+      }
+
+      .noneToBtn {
+        opacity: .5;
+        pointer-events: none;
+      }
+    }
+
+    .A3IRtitRow {
+      padding: 0 20px 0 50px;
+      position: relative;
+      margin-top: 15px;
+      font-size: 16px;
+      color: var(--themeColor);
+      letter-spacing: 2px;
+
+      &>span {
+        font-weight: 700;
+      }
+
+      .A3IRtitRowYuan {
+        position: absolute;
+        left: 30px;
+        top: 3px;
+        width: 14px;
+        height: 14px;
+        border-radius: 50%;
+        border: 1px solid var(--themeColor);
+
+        &>div {
+          position: relative;
+          top: 2px;
+          left: 2px;
+          width: 8px;
+          height: 8px;
+          background-color: var(--themeColor);
+          border-radius: 50%;
+        }
+      }
+    }
+
+  }
+}

+ 226 - 0
src/pages/A3GoodsM/GoodsInfoM/index.tsx

@@ -0,0 +1,226 @@
+/* eslint-disable jsx-a11y/iframe-has-title */
+import React, { useMemo, useRef, useState } from "react";
+import styles from "./index.module.scss";
+import { Goods } from "@/types";
+import { type1Arr1M } from "../data";
+import { baseURL } from "@/utils/http";
+import backImg from "@/assets/img/back.png";
+import name1Img from "@/assets/img/name1.png";
+import name2Img from "@/assets/img/name2.png";
+import classNames from "classnames";
+import {
+  PlusOutlined,
+  MinusOutlined,
+  FullscreenOutlined,
+  FullscreenExitOutlined,
+} from "@ant-design/icons";
+
+type Props = {
+  info: Goods;
+  colseFu: () => void;
+  type: string;
+};
+
+function GoodsInfoM({ info, colseFu, type }: Props) {
+  // 背景图
+  const imgBac = useMemo(() => {
+    const info = type1Arr1M.find((v) => v.name === type);
+    if (info) return `info${info.id}Bac.jpg`;
+    else return "";
+  }, [type]);
+
+  // 看看有几个模型
+  const modelNum = useMemo(() => {
+    const arr = [1];
+
+    if (info.modelNum && info.modelNum > 1) {
+      for (let i = 0; i < info.modelNum; i++) {
+        if (i !== 0) arr.push(i + 1);
+      }
+    }
+    return arr;
+  }, [info.modelNum]);
+
+  // 当前显示的模型
+  const [modelShow, setModelShow] = useState(1);
+
+  // 模型的全屏
+  const [full, setFull] = useState(false);
+
+  // 控制模型放大缩小和复位
+  const ifrBoxRef = useRef<any>(null);
+  const modelChangeFu = (val: number) => {
+    const dom = ifrBoxRef.current;
+
+    if (dom && dom.contentWindow && dom.contentWindow.webview) {
+      if (val === 1) dom.contentWindow.webview.zoomIn(); // 放大
+      else if (val === 2) dom.contentWindow.webview.zoomOut(); // 缩小
+      else dom.contentWindow.webview.resetView(); // 复位
+    }
+  };
+
+  return (
+    <div
+      className={styles.GoodsInfoM}
+      style={{
+        backgroundImage: `url(${baseURL}/3Goods/mobile/${imgBac})`,
+      }}
+    >
+      <div className="GoodsInfoMmian mySorrl">
+        {/* 返回  */}
+        <img onClick={colseFu} className="A3IBack" src={backImg} alt="" />
+
+        {/* 标题 */}
+        <div className="A3IRtit">
+          <img src={name1Img} alt="" />
+          <div> {info.name}</div>
+
+          <img src={name2Img} alt="" />
+        </div>
+        <div className="A3IRtit2">存放于{info.type1}</div>
+
+        {/* 模型盒子 */}
+
+        <div className="ifrBox">
+          <div
+            className={classNames("ifrBoxson", full ? "A3IleftFull" : "")}
+            key={modelShow}
+          >
+            {modelNum.map((v) =>
+              modelShow === v ? (
+                <iframe
+                  key={v}
+                  ref={ifrBoxRef}
+                  src={`model.html?m=${info.id}&n=${v}`}
+                  frameBorder="no"
+                ></iframe>
+              ) : null
+            )}
+
+            {/* 右下角的按钮 */}
+            <div className="A3IleftBtn">
+              <div onClick={() => modelChangeFu(1)}>
+                <PlusOutlined rev={undefined} />
+              </div>
+              <div onClick={() => modelChangeFu(2)}>
+                <MinusOutlined rev={undefined} />
+              </div>
+              {full ? (
+                <div onClick={() => setFull(false)}>
+                  <FullscreenExitOutlined rev={undefined} />
+                </div>
+              ) : (
+                <div onClick={() => setFull(true)}>
+                  <FullscreenOutlined rev={undefined} />
+                </div>
+              )}
+            </div>
+          </div>
+
+          {/* 左右按钮 */}
+          <div
+            onClick={() => setModelShow(modelShow - 1)}
+            hidden={modelNum.length <= 1}
+            style={{ backgroundImage: `url(${baseURL}/4Intro/pc/left.png)` }}
+            className={classNames(
+              "A3ItoBtn",
+              "A3ItoLeft",
+              modelShow === 1 ? "noneToBtn" : ""
+            )}
+          ></div>
+          <div
+            onClick={() => setModelShow(modelShow + 1)}
+            hidden={modelNum.length <= 1}
+            style={{ backgroundImage: `url(${baseURL}/4Intro/pc/right.png)` }}
+            className={classNames(
+              "A3ItoBtn",
+              "A3ItoRight",
+              modelShow === modelNum.length ? "noneToBtn" : ""
+            )}
+          ></div>
+        </div>
+
+        {/* 下面简介 */}
+        {info.name2 ? (
+          <div className="A3IRtitRow">
+            <div className="A3IRtitRowYuan">
+              <div></div>
+            </div>
+            <span>名称:</span>
+            {info.name2}
+          </div>
+        ) : null}
+
+        {info.num ? (
+          <div className="A3IRtitRow">
+            <div className="A3IRtitRowYuan">
+              <div></div>
+            </div>
+            <span>编号:</span>
+            {info.num}
+          </div>
+        ) : null}
+
+        {info.type2 ? (
+          <div className="A3IRtitRow">
+            <div className="A3IRtitRowYuan">
+              <div></div>
+            </div>
+            <span>类别:</span>
+            {info.type2}
+          </div>
+        ) : null}
+
+        {info.age ? (
+          <div className="A3IRtitRow">
+            <div className="A3IRtitRowYuan">
+              <div></div>
+            </div>
+            <span>年代:</span>
+            {info.age}
+          </div>
+        ) : null}
+        {info.grain ? (
+          <div className="A3IRtitRow">
+            <div className="A3IRtitRowYuan">
+              <div></div>
+            </div>
+            <span>质地:</span>
+            {info.grain}
+          </div>
+        ) : null}
+        {info.size ? (
+          <div className="A3IRtitRow">
+            <div className="A3IRtitRowYuan">
+              <div></div>
+            </div>
+            <span>尺寸:</span>
+            {info.size}
+          </div>
+        ) : null}
+        {info.level ? (
+          <div className="A3IRtitRow">
+            <div className="A3IRtitRowYuan">
+              <div></div>
+            </div>
+            <span>级别:</span>
+            {info.level}
+          </div>
+        ) : null}
+        {info.state ? (
+          <div className="A3IRtitRow">
+            <div className="A3IRtitRowYuan">
+              <div></div>
+            </div>
+            <span>状态:</span>
+            {info.state}
+          </div>
+        ) : null}
+      </div>
+    </div>
+  );
+}
+
+const MemoGoodsInfoM = React.memo(GoodsInfoM);
+
+export default MemoGoodsInfoM;

+ 7 - 0
src/pages/A3GoodsM/data.ts

@@ -0,0 +1,7 @@
+export const type1Arr1M = [
+  { id: 0, name: "全部馆藏" },
+  { id: 1, name: "董必武纪念馆" },
+  { id: 2, name: "李先念纪念馆" },
+  { id: 3, name: "历史馆" },
+  { id: 4, name: "烈士馆" },
+];

+ 195 - 0
src/pages/A3GoodsM/index.module.scss

@@ -0,0 +1,195 @@
+.A3GoodsM {
+  background-size: 100% 100%;
+
+  :global {
+    .A3Gtop {
+      width: 100%;
+      height: 130px;
+      background-size: 100% 100%;
+      padding: 10px 15px;
+      position: relative;
+
+      .A3Back {
+        position: absolute;
+        z-index: 10;
+        left: 10px;
+        top: 10px;
+        width: 34px;
+      }
+
+      .A3RightBox2 {
+        padding-left: 50px;
+        width: 100%;
+        height: 32px;
+        position: relative;
+
+        input::-webkit-input-placeholder {
+          /* WebKit browsers */
+          color: var(--themeColor);
+          opacity: .8;
+        }
+
+        input:-moz-placeholder {
+          /* Mozilla Firefox 4 to 18 */
+          color: var(--themeColor);
+          opacity: .8;
+        }
+
+        input::-moz-placeholder {
+          /* Mozilla Firefox 19+ */
+          color: var(--themeColor);
+          opacity: .8;
+        }
+
+        input:-ms-input-placeholder {
+          /* Internet Explorer 10+ */
+          color: var(--themeColor);
+          opacity: .8;
+        }
+
+        .ant-input {
+          font-weight: 700;
+          height: 32px;
+          line-height: 30px;
+          font-size: 16px;
+          padding-right: 40px;
+          padding-left: 20px;
+          border-radius: 18px;
+          background-color: rgba(255, 255, 255, .8);
+          color: var(--themeColor);
+        }
+
+        .A3searchIcon {
+          position: absolute;
+          top: 0;
+          right: 0;
+          width: 40px;
+          height: 100%;
+          text-align: center;
+          line-height: 30px;
+          font-size: 20px;
+          color: var(--themeColor);
+        }
+
+      }
+
+      .A3Gtop1 {
+        margin-top: 10px;
+        display: flex;
+        justify-content: space-between;
+        height: 32px;
+
+        .A3Gtop1_1 {
+          width: 48%;
+          height: 32px;
+
+          .ant-select {
+            height: 32px;
+
+            .ant-select-selector {
+
+              height: 100%;
+              background-color: rgba(255, 255, 255, .8);
+              border-radius: 18px;
+
+              .ant-select-selection-item {
+                line-height: 30px;
+                font-size: 16px;
+                font-weight: 700;
+                color: var(--themeColor);
+                text-align: center;
+              }
+            }
+
+            .ant-select-arrow {
+              line-height: 30px;
+              font-size: 16px;
+              font-weight: 700;
+              color: var(--themeColor);
+            }
+          }
+        }
+      }
+
+    }
+
+    .noRow {
+      width: 100%;
+      height: calc(100% - 130px);
+      padding-bottom: 200px;
+      color: #fff;
+      display: flex;
+      justify-content: center;
+      align-items: center;
+      font-size: 20px;
+      letter-spacing: 4px;
+    }
+
+    .A3Main {
+      width: 100%;
+      height: calc(100% - 130px);
+      overflow-y: auto;
+      display: flex;
+      flex-wrap: wrap;
+      padding: 0 15px 15px;
+
+      .A3Row {
+        width: 48%;
+        height: 200px;
+        background-color: #fff;
+        border-radius: 6px;
+        margin-right: 4%;
+        margin-bottom: 4%;
+        padding: 8px;
+
+        &:nth-of-type(2n) {
+          margin-right: 0;
+        }
+
+        .A3RowHo {
+          margin-top: 6px;
+          font-weight: 700;
+          overflow: hidden;
+          text-overflow: ellipsis;
+          white-space: nowrap;
+        }
+
+        .lookImg {
+          text-align: right;
+
+          &>img {
+            width: 60px;
+          }
+        }
+      }
+    }
+
+    .A3ToTop {
+      position: fixed;
+      z-index: 10;
+      bottom: 20px;
+      right: 10px;
+      width: 40px;
+
+      .A3ToTop1 {
+        width: 100%;
+        height: 34px;
+        background-color: var(--themeColor);
+        text-align: center;
+        color: #fff;
+        line-height: 34px;
+        font-weight: 700;
+        font-size: 28px;
+      }
+
+      .A3ToTop2 {
+        padding: 10px 10px 15px;
+        font-size: 20px;
+        text-align: center;
+        background-color: #fff;
+        color: var(--themeColor);
+        font-weight: 700;
+      }
+    }
+  }
+}

+ 230 - 0
src/pages/A3GoodsM/index.tsx

@@ -0,0 +1,230 @@
+import React, {
+  useCallback,
+  useEffect,
+  useMemo,
+  useRef,
+  useState,
+} from "react";
+import styles from "./index.module.scss";
+import { baseURL } from "@/utils/http";
+import { type1Arr1M } from "./data";
+import { Input, Select } from "antd";
+import { SearchOutlined } from "@ant-design/icons";
+import backImg from "@/assets/img/back.png";
+import history from "@/utils/history";
+import { useSelector } from "react-redux";
+import { RootState } from "@/store";
+import ImageLazy from "@/components/ImageLazy";
+import { Goods } from "@/types";
+// import lookImg from "@/assets/img/look.png";
+import { UpOutlined } from "@ant-design/icons";
+import GoodsInfoM from "./GoodsInfoM";
+
+function A3GoodsM() {
+  const dataTemp = useSelector(
+    (state: RootState) => state.A0Layout.dataAll.goods
+  );
+
+  const data = useMemo(() => {
+    if (dataTemp) return dataTemp;
+    else return [];
+  }, [dataTemp]);
+
+  const [type1, setType1] = useState("全部馆藏");
+
+  // 背景图
+  const imgBac = useMemo(() => {
+    const info = type1Arr1M.find((v) => v.name === type1);
+    if (info) return `tab${info.id}Bac.jpg`;
+    else return "";
+  }, [type1]);
+
+  // 输入框
+  const inputRef = useRef<any>(null);
+  const [searchKey, setSearchKey] = useState(-1);
+
+  // 右侧类别的筛选
+  const [type2, setType2] = useState("全部类别");
+
+  // 右侧类别的下拉框
+  const type2Arr = useMemo(() => {
+    const arr = ["全部类别"];
+    data.forEach((v) => {
+      if (!arr.includes(v.type2)) arr.push(v.type2);
+    });
+    return arr;
+  }, [data]);
+
+  // 在页面渲染的数据
+  const dataRes = useMemo(() => {
+    console.log(searchKey);
+
+    // 所有数据
+    let arr = [...data];
+
+    // 筛选 左侧
+    if (type1 !== "全部馆藏") arr = arr.filter((v) => v.type1 === type1);
+
+    // 筛选 右侧
+    if (type2 !== "全部类别") arr = arr.filter((v) => v.type2 === type2);
+
+    if (inputRef.current) {
+      const val = inputRef.current.input.value.trim();
+      arr = arr.filter((v) => v.name.includes(val) || !val);
+    }
+
+    return arr;
+  }, [data, searchKey, type1, type2]);
+
+  // 每次变化的时候 滚动到顶部
+  const fullTopFu = useCallback(() => {
+    if (scrollRef.current) {
+      scrollRef.current.scrollTop = 0;
+      window.setTimeout(() => {
+        if (scrollRef.current) scrollRef.current.scrollTop = 1;
+      }, 100);
+    }
+  }, []);
+
+  useEffect(() => {
+    fullTopFu();
+  }, [fullTopFu, searchKey, type1, type2]);
+
+  // 查看文物详情
+  const [goodsInfo, setGoodsInfo] = useState({} as Goods);
+
+  // 手机打开输入框的时候 下面主体变形
+  useEffect(() => {
+    const num = document.documentElement.clientHeight;
+    const dom: HTMLDivElement | null = document.querySelector("#A3GoodsM");
+    if (dom) dom.style.height = num + "px";
+  }, []);
+
+  // 滚动距离
+
+  const [scrollShow, setScrollShow] = useState(false);
+
+  const scrollRef = useRef<HTMLDivElement>(null);
+
+  const scrollFu = useCallback(() => {
+    if (scrollRef.current) {
+      const num = scrollRef.current.scrollTop;
+      setScrollShow(num >= 400);
+    }
+  }, []);
+
+  useEffect(() => {
+    window.addEventListener("scroll", scrollFu, true);
+
+    return () => {
+      window.removeEventListener("scroll", scrollFu);
+    };
+  }, [scrollFu]);
+
+  return (
+    <div
+      id="A3GoodsM"
+      className={styles.A3GoodsM}
+      style={{
+        backgroundImage: `url(${baseURL}/3Goods/mobile/${imgBac})`,
+      }}
+    >
+      <div
+        className="A3Gtop"
+        style={{ backgroundImage: `url(${baseURL}/3Goods/mobile/top.png)` }}
+      >
+        <img
+          onClick={() => history.push("/")}
+          className="A3Back"
+          src={backImg}
+          alt=""
+        />
+
+        {/* 输入框 */}
+        <div
+          className="A3RightBox2"
+          onKeyUp={(e) => {
+            if (e.key === "Enter") setSearchKey(Date.now());
+          }}
+        >
+          <Input ref={inputRef} placeholder="搜索文物" maxLength={15} />
+          {/* 搜索图标 */}
+          <div
+            className="A3searchIcon"
+            onClick={() => setSearchKey(Date.now())}
+          >
+            <SearchOutlined rev={undefined} />
+          </div>
+        </div>
+
+        {/* 返回按钮和下拉框 */}
+        <div className="A3Gtop1">
+          <div className="A3Gtop1_1">
+            <Select
+              style={{ width: "100%" }}
+              value={type1}
+              onChange={(e) => setType1(e)}
+              options={type1Arr1M.map((v) => ({
+                value: v.name,
+                label: v.name,
+              }))}
+            />
+          </div>
+          <div className="A3Gtop1_1">
+            <Select
+              style={{ width: "100%" }}
+              value={type2}
+              onChange={(e) => setType2(e)}
+              options={type2Arr.map((v) => ({ value: v, label: v }))}
+            />
+          </div>
+        </div>
+      </div>
+
+      {/* 主体 */}
+      {dataRes && dataRes.length ? (
+        <div className="A3Main" ref={scrollRef}>
+          {dataRes.map((v) => (
+            <div className="A3Row" key={v.id} onClick={() => setGoodsInfo(v)}>
+              <ImageLazy
+                src={`${baseURL}/3Goods/${v.id}/main.jpg`}
+                width="100%"
+                height={160}
+                noLook
+                offline
+              />
+              <div className="A3RowHo">{v.name}</div>
+              {/* 查看的小图片 */}
+              {/* <div className="lookImg">
+                <img src={lookImg} alt="" />
+              </div> */}
+            </div>
+          ))}
+        </div>
+      ) : (
+        <div className="noRow">暂无数据</div>
+      )}
+
+      {/* 返回顶部 */}
+      <div className="A3ToTop" hidden={!scrollShow} onClick={fullTopFu}>
+        <div className="A3ToTop1">
+          <UpOutlined rev={undefined} />
+        </div>
+        <div className="A3ToTop2">回到顶部</div>
+      </div>
+
+      {/* 查看文物详情 */}
+      {goodsInfo.id ? (
+        <GoodsInfoM
+          type={type1}
+          info={goodsInfo}
+          colseFu={() => setGoodsInfo({} as Goods)}
+        />
+      ) : null}
+    </div>
+  );
+}
+
+const MemoA3GoodsM = React.memo(A3GoodsM);
+
+export default MemoA3GoodsM;

+ 123 - 0
src/pages/A4Intro/index.module.scss

@@ -0,0 +1,123 @@
+.A4Intro {
+  position: relative;
+  background-size: 100% 100%;
+
+  :global {
+    .introBack {
+      position: absolute;
+      z-index: 3;
+      top: 20px;
+      left: 30px;
+      cursor: pointer;
+    }
+
+    .introTxt1 {
+      position: absolute;
+      z-index: 3;
+      top: 54px;
+      left: 200px;
+    }
+
+    .txtBox {
+      width: 976px;
+      height: 622px;
+      background-size: 100% 100%;
+      position: absolute;
+      top: 52px;
+      left: 578px;
+      padding: 120px 110px 120px 120px;
+
+      .txtBoxMain {
+        height: 100%;
+        padding-right: 10px;
+        overflow-y: auto;
+        font-size: 16px;
+        letter-spacing: 4px;
+        line-height: 24px;
+      }
+    }
+
+    .introSwBox {
+      position: absolute;
+      width: 1350px;
+      z-index: 3;
+      bottom: 40px;
+      height: 170px;
+      left: 248px;
+      display: flex;
+      align-items: center;
+
+      .introSw1 {
+        width: 262px;
+        height: 80px;
+        margin-right: 18px;
+        background-size: 100% 100%;
+        font-size: 30px;
+        line-height: 80px;
+        text-align: center;
+        font-weight: 700;
+        color: var(--themeColor);
+      }
+
+      .introSw2 {
+        position: relative;
+        width: calc(100% - 280px);
+        height: 120px;
+        padding: 0 60px;
+
+        .swiper {
+          .swiper-slide {
+            border: 3px solid #fff;
+            border-radius: 4px;
+            height: 120px;
+            cursor: pointer;
+          }
+
+
+        }
+
+        .swiper-button-prev {
+          position: absolute;
+          left: 4px;
+          width: 41px;
+          height: 81px;
+          background-size: 100% 100%;
+          top: 50%;
+          transform: translateY(-50%);
+          margin-top: 0;
+
+          &::after {
+            opacity: 0;
+          }
+        }
+
+        .swiper-button-next {
+          position: absolute;
+          right: 4px;
+          width: 41px;
+          height: 81px;
+          background-size: 100% 100%;
+          top: 50%;
+          transform: translateY(-50%);
+          margin-top: 0;
+
+          &::after {
+            opacity: 0;
+          }
+        }
+
+        .sw-disable {
+          opacity: .5;
+          pointer-events: none;
+          width: 41px;
+          height: 81px;
+          background-size: 100% 100%;
+          top: 50%;
+          transform: translateY(-50%);
+          margin-top: 0;
+        }
+
+      }
+    }
+  }
+}

+ 127 - 0
src/pages/A4Intro/index.tsx

@@ -0,0 +1,127 @@
+import React, { useCallback, useMemo } from "react";
+import styles from "./index.module.scss";
+import backImg from "@/assets/img/back.png";
+import history from "@/utils/history";
+import { baseURL } from "@/utils/http";
+import { useSelector } from "react-redux";
+import store, { RootState } from "@/store";
+
+// 轮播图
+import { Swiper, SwiperSlide } from "swiper/react";
+import { Navigation } from "swiper";
+import "swiper/css";
+import "swiper/css/navigation";
+import ImageLazy from "@/components/ImageLazy";
+
+function A4Intro() {
+  const data = useSelector((state: RootState) => state.A0Layout.dataAll.Home);
+
+  const dataRes = data.introduce || {};
+
+  const imgArr = useMemo(() => {
+    const num = dataRes.imgNum || 0;
+
+    const dom: number[] = [];
+
+    for (let i = 0; i < num; i++) {
+      dom.push(i);
+    }
+    return dom;
+  }, [dataRes.imgNum]);
+
+  const lookArr = useCallback(() => {
+    const arr = [] as string[];
+
+    imgArr.forEach((v, i) => {
+      arr.push(`${baseURL}/4Intro/pc/${i + 1}.jpg`);
+    });
+
+    return arr;
+  }, [imgArr]);
+
+  return (
+    <div
+      className={styles.A4Intro}
+      style={{ backgroundImage: `url(${baseURL}/4Intro/pc/bac.jpg)` }}
+    >
+      {/* 返回按钮 */}
+      <div className="introBack" onClick={() => history.push("/")}>
+        <img src={backImg} alt="" />
+      </div>
+
+      <img className="introTxt1" src={`${baseURL}/4Intro/pc/txt1.png`} alt="" />
+
+      <div
+        className="txtBox"
+        style={{ backgroundImage: `url(${baseURL}/4Intro/pc/txtBac.png)` }}
+      >
+        <div
+          className="txtBoxMain mySorrl"
+          dangerouslySetInnerHTML={{ __html: dataRes.txt }}
+        ></div>
+      </div>
+
+      {/* 底部轮播图 */}
+      <div className="introSwBox">
+        <div
+          className="introSw1"
+          style={{ backgroundImage: `url(${baseURL}/4Intro/pc/txt2.png)` }}
+        >
+          场 馆 风 采
+        </div>
+        <div className="introSw2">
+          <Swiper
+            modules={[Navigation]}
+            spaceBetween={30}
+            slidesPerView={4}
+            navigation={{
+              nextEl: ".swiper-button-next",
+              prevEl: ".swiper-button-prev",
+              disabledClass: "sw-disable", // 当导航按钮变为不可用时添加的class,也就是当swiper索引为0时上一张没有prevEl的class类名就会添加一个disable,也就是.swiper-button-prev .disable
+            }}
+            // onSlideChange={() => console.log("slide change")}
+            // onSwiper={(swiper) => console.log(swiper)}
+          >
+            {imgArr.map((v, i) => (
+              <SwiperSlide
+                key={v}
+                onClick={() =>
+                  store.dispatch({
+                    type: "layout/lookBigImg",
+                    payload: {
+                      show: true,
+                      url: lookArr(),
+                      current: i,
+                    },
+                  })
+                }
+              >
+                <ImageLazy
+                  noLook={true}
+                  offline={true}
+                  width="100%"
+                  height="100%"
+                  src={`${baseURL}/4Intro/pc/${v + 1}.jpg`}
+                />
+              </SwiperSlide>
+            ))}
+          </Swiper>
+
+          {/* 左右按钮 */}
+          <div
+            className="swiper-button-prev"
+            style={{ backgroundImage: `url(${baseURL}/4Intro/pc/left.png)` }}
+          ></div>
+          <div
+            className="swiper-button-next"
+            style={{ backgroundImage: `url(${baseURL}/4Intro/pc/right.png)` }}
+          ></div>
+        </div>
+      </div>
+    </div>
+  );
+}
+
+const MemoA4Intro = React.memo(A4Intro);
+
+export default MemoA4Intro;

+ 113 - 0
src/pages/A4IntroM/index.module.scss

@@ -0,0 +1,113 @@
+.A4IntroM{
+  background-size: 100% 100%;
+  padding: 20px;
+  :global{
+    .introBack{
+      width: 40px;
+    }
+    .txtBox{
+      width: 100%;
+      height: calc(100% - 220px);
+      background-size: 100% 100%;
+      padding: 30% 12% 15% 15%;
+      position: relative;
+      .txtBoxTit{
+        position: absolute;
+        width: 100%;
+        top: 30px;
+        left: 0;
+        text-align: center;
+        &>img{
+          width: 160px;
+        }
+      }
+      .txtBoxMain{
+        width: 100%;
+        height: 100%;
+        overflow-y: auto;
+        padding-right: 20px;
+        letter-spacing: 2px;
+        padding-bottom: 10px;
+        &::-webkit-scrollbar{
+          width: 2px;
+        }
+      }
+    }
+    .introSw1{
+      margin: 10px auto 8px;
+      width: 160px;
+      height: 40px;
+      background-size: 100% 100%;
+      display: flex;
+      justify-content: center;
+      align-items: center;
+      font-size: 18px;
+      font-weight: 700;
+      color: var(--themeColor);
+    }
+    .introSw2{
+      width: 100%;
+      height: 130px;
+      padding: 0 60px;
+      position: relative;
+      .swiper {
+        height: 100%;
+        .swiper-slide {
+          border: 3px solid #fff;
+          border-radius: 4px;
+
+          .swImg {
+            width: 100%;
+            height: 100%;
+          }
+        }
+
+
+      }
+      .swiper-button-prev {
+        position: absolute;
+        outline: none;
+        cursor: default;
+        left: 4px;
+        width: 30px;
+        height: 60px;
+        background-size: 100% 100%;
+        top: 50%;
+        transform: translateY(-50%);
+        margin-top: 0;
+
+        &::after {
+          opacity: 0;
+        }
+      }
+
+      .swiper-button-next {
+        position: absolute;
+        outline: none;
+        cursor: default;
+        right: 4px;
+        width: 30px;
+        height: 60px;
+        background-size: 100% 100%;
+        top: 50%;
+        transform: translateY(-50%);
+        margin-top: 0;
+
+        &::after {
+          opacity: 0;
+        }
+      }
+
+      .sw-disable {
+        opacity: .5;
+        pointer-events: none;
+        width: 30px;
+        height: 60px;
+        background-size: 100% 100%;
+        top: 50%;
+        transform: translateY(-50%);
+        margin-top: 0;
+      }
+    }
+  }
+}

+ 141 - 0
src/pages/A4IntroM/index.tsx

@@ -0,0 +1,141 @@
+import React, { useCallback, useMemo } from "react";
+import styles from "./index.module.scss";
+import { useSelector } from "react-redux";
+import { RootState } from "@/store";
+import { baseURL } from "@/utils/http";
+import backImg from "@/assets/img/back.png";
+import history from "@/utils/history";
+
+// 轮播图
+import { Swiper, SwiperSlide } from "swiper/react";
+import { Navigation } from "swiper";
+import "swiper/css";
+import "swiper/css/navigation";
+import { ImageViewer } from "antd-mobile";
+
+function A4IntroM() {
+  const data = useSelector((state: RootState) => state.A0Layout.dataAll.Home);
+
+  const dataRes = data.introduce || {};
+
+  const imgArr = useMemo(() => {
+    const num = dataRes.imgNum || 0;
+
+    const dom: number[] = [];
+
+    for (let i = 0; i < num; i++) {
+      dom.push(i);
+    }
+    return dom;
+  }, [dataRes.imgNum]);
+
+  const lookArr = useCallback(() => {
+    const arr = [] as string[];
+
+    imgArr.forEach((v, i) => {
+      arr.push(`${baseURL}/4Intro/pc/${i + 1}.jpg`);
+    });
+
+    return arr;
+  }, [imgArr]);
+
+  const lookBigImgFu = useCallback(
+    (i: number) => {
+      ImageViewer.Multi.show({
+        images: lookArr(),
+        defaultIndex: i,
+      });
+    },
+    [lookArr]
+  );
+
+  return (
+    <div
+      className={styles.A4IntroM}
+      style={{ backgroundImage: `url(${baseURL}/4Intro/mobile/bac.jpg)` }}
+    >
+      {/* 返回按钮 */}
+      <div className="introBack" onClick={() => history.push("/")}>
+        <img src={backImg} alt="" />
+      </div>
+
+      {/* 文字主体 */}
+      <div
+        className="txtBox"
+        style={{ backgroundImage: `url(${baseURL}/4Intro/mobile/txtBac.png)` }}
+      >
+        {/* 场馆介绍 */}
+        <div className="txtBoxTit">
+          <img src={`${baseURL}/4Intro/mobile/txt1.png`} alt="" />
+        </div>
+
+        <div
+          className="txtBoxMain mySorrl"
+          dangerouslySetInnerHTML={{ __html: dataRes.txt }}
+        ></div>
+      </div>
+
+      {/* 轮播图 */}
+      <div
+        className="introSw1"
+        style={{ backgroundImage: `url(${baseURL}/4Intro/pc/txt2.png)` }}
+      >
+        场 馆 风 采
+      </div>
+
+      <div className="introSw2">
+        <Swiper
+          modules={[Navigation]}
+          spaceBetween={0}
+          slidesPerView={1}
+          // noSwipingClass='swImg'
+          navigation={{
+            nextEl: ".swiper-button-next",
+            prevEl: ".swiper-button-prev",
+            disabledClass: "sw-disable", // 当导航按钮变为不可用时添加的class,也就是当swiper索引为0时上一张没有prevEl的class类名就会添加一个disable,也就是.swiper-button-prev .disable
+          }}
+          // onSlideChange={() => console.log("slide change")}
+          // onSwiper={(swiper) => console.log(swiper)}
+        >
+          {imgArr.map((v, i) => (
+            <SwiperSlide
+              key={v}
+              onClick={
+                () => lookBigImgFu(i)
+
+                // store.dispatch({
+                //   type: "layout/lookBigImg",
+                //   payload: {
+                //     show: true,
+                //     url: lookArr(),
+                //     current: i,
+                //   },
+                // })
+              }
+            >
+              <img
+                className="swImg"
+                src={`${baseURL}/4Intro/pc/${v + 1}.jpg`}
+                alt=""
+              />
+            </SwiperSlide>
+          ))}
+        </Swiper>
+
+        {/* 左右按钮 */}
+        <div
+          className="swiper-button-prev"
+          style={{ backgroundImage: `url(${baseURL}/4Intro/pc/left.png)` }}
+        ></div>
+        <div
+          className="swiper-button-next"
+          style={{ backgroundImage: `url(${baseURL}/4Intro/pc/right.png)` }}
+        ></div>
+      </div>
+    </div>
+  );
+}
+
+const MemoA4IntroM = React.memo(A4IntroM);
+
+export default MemoA4IntroM;

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

+ 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

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

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

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

@@ -0,0 +1,62 @@
+import { DataAllType, LookDomType } from "@/types";
+import { MessageType } from "@/utils/message";
+
+type lookType = { url: string[]; show: boolean; current: number };
+
+// 初始化状态
+const initState = {
+  // 所有图片点击预览查看大图
+  lookBigImg: {
+    url: [],
+    show: false,
+    current: 0,
+  } as lookType,
+  // 查看视频、音频、模型
+  lookDom: {
+    src: "",
+    type: "",
+  } as LookDomType,
+
+  // antd轻提示(兼容360浏览器)
+  message: {
+    txt: "",
+    type: "info",
+    duration: 3,
+  } as MessageType,
+
+  // 所有数据
+  dataAll: { Home: {} } as DataAllType,
+};
+
+// 定义 action 类型
+type LayoutActionType =
+  | { type: "layout/lookBigImg"; payload: lookType }
+  | { type: "layout/lookDom"; payload: LookDomType }
+  | { type: "layout/message"; payload: MessageType }
+  | { type: "layout/setDataAll"; payload: DataAllType };
+
+// 频道 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/setDataAll":
+      return { ...state, dataAll: 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 Goods = {
+  id: number;
+  name: string;
+  name2:string
+  type1: string;
+  type2: string;
+  num: string;
+  age: string;
+  grain: string;
+  size: string;
+  level: string;
+  state: string;
+  modelNum?:number
+};
+
+export type DataAllType = {
+  Home: {
+    vr: string;
+    introduce: {
+      txt: string;
+      imgNum: number;
+    };
+  };
+  goods: Goods[];
+};

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

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

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

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

+ 13 - 0
src/utils/domShow.ts

@@ -0,0 +1,13 @@
+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";
+  }
+};

+ 26 - 0
src/utils/history.ts

@@ -0,0 +1,26 @@
+import { createHashHistory  } from 'history'
+const history = createHashHistory()
+export default history
+
+export const urlParameter = (data: string) => {
+  if (data) {
+    const query = data.substring(data.indexOf("?") + 1);
+    const arr = query.split("&");
+    const params = {} as any;
+    arr.forEach((v) => {
+      const key = v.substring(0, v.indexOf("="));
+      const val = v.substring(v.indexOf("=") + 1);
+      params[key] = val;
+    });
+    return params;
+  } else return {};
+};
+
+// 判断是手机端还是pc端
+export const isMobiileFu = () => {
+  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
+}

+ 4 - 0
src/utils/http.ts

@@ -0,0 +1,4 @@
+// 打包配置=》静态资源里面的data.js
+// @ts-ignore
+export const baseURL = baseUrlRes;
+

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

+ 27 - 0
tsconfig.json

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