shaogen1995 10 months ago
parent
commit
1f7fbec347
35 changed files with 7336 additions and 84 deletions
  1. 8 2
      scene/src/views/gui/menu.vue
  2. 2 1
      web/package.json
  3. 92 0
      web/public/myData/myData.js
  4. BIN
      web/src/assets/img/rowBg.png
  5. 37 3
      web/src/assets/styles/base.css
  6. 45 3
      web/src/assets/styles/base.less
  7. 16 0
      web/src/components/LazyImg/index.module.scss
  8. 22 0
      web/src/components/LazyImg/index.tsx
  9. 40 0
      web/src/components/LeftTopLogo/index.module.scss
  10. 27 0
      web/src/components/LeftTopLogo/index.tsx
  11. 77 0
      web/src/components/LookGood/index.module.scss
  12. 58 0
      web/src/components/LookGood/index.tsx
  13. 20 49
      web/src/components/RouterOrder.tsx
  14. 4 3
      web/src/pages/A1home/index.tsx
  15. 5 0
      web/src/pages/A2scene/index.module.scss
  16. 22 3
      web/src/pages/A2scene/index.tsx
  17. 158 0
      web/src/pages/A3goods/index.module.scss
  18. 263 0
      web/src/pages/A3goods/index.tsx
  19. 1 1
      web/src/pages/A2sceneM/index.module.scss
  20. 4 4
      web/src/pages/A2sceneM/index.tsx
  21. 31 0
      web/src/types/api/layot.d.ts
  22. 2 14
      web/src/types/declaration.d.ts
  23. 5 1
      web/src/utils/history.ts
  24. 12 0
      web/yarn.lock
  25. BIN
      web静态资源/staticData/goods/bg.jpg
  26. BIN
      web静态资源/staticData/goods/close.png
  27. BIN
      web静态资源/staticData/goods/iconS.png
  28. BIN
      web静态资源/staticData/goods/img/s1.jpg
  29. BIN
      web静态资源/staticData/goods/img/s2.jpg
  30. BIN
      web静态资源/staticData/goods/model/1.4dage
  31. BIN
      web静态资源/staticData/goods/model/2.4dage
  32. BIN
      web静态资源/staticData/goods/null.png
  33. BIN
      web静态资源/staticData/goods/video/1.mp4
  34. 6352 0
      web静态资源/staticData/modelLoding/4dage.js
  35. 33 0
      web静态资源/staticData/modelLoding/model.html

+ 8 - 2
scene/src/views/gui/menu.vue

@@ -276,7 +276,6 @@ export default {
   computed: {},
   mounted() {
     if (window.location.href.includes("?h=1")) {
-
       this.musicTime = setInterval(() => {
         try {
           this.switchBGM(true);
@@ -290,10 +289,17 @@ export default {
   methods: {
     // 回到首页
     toHomeFu() {
-      window.location.href = window.location.href.replace("zsScene", "zsWeb");
+
+      if(window.location.href.includes("?h=1")){
+        window.parent.toHomeFu()
+      }else {
+        window.location.href = window.location.href.replace("zsScene", "zsWeb");
       setTimeout(() => {
         location.reload();
       }, 200);
+      }
+
+  
     },
 
     switchBGM(flag) {

+ 2 - 1
web/package.json

@@ -26,6 +26,7 @@
     "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"
   },
@@ -61,4 +62,4 @@
     "react-app-rewired": "^2.2.1"
   },
   "homepage": "."
-}
+}

+ 92 - 0
web/public/myData/myData.js

@@ -1,6 +1,7 @@
 // 本地开发静态资源目录
 const baseUrlLoc = 'http://192.168.20.55:8080/staticData/'
 
+// 场景基地址
 const myBaseUrl = 'https://houseoss.4dkankan.com/project/zsWeb/'
 
 // Build开发资源目录
@@ -8,6 +9,7 @@ const baseUrlAtl = myBaseUrl + 'staticData/'
 
 // 所有数据
 const myDataTemp = {
+  // 首页
   home: {
     // 背景图
     bg: 'home/bg.jpg',
@@ -39,5 +41,95 @@ const myDataTemp = {
         path: '/expert'
       }
     ]
+  },
+  // 馆藏
+  goods: {
+    // 背景图
+    bg: 'goods/bg.jpg',
+    img: {
+      name: '二维图片',
+      name2: 'Picture',
+      rowArr: [
+        {
+          type: '分类1',
+          name: '图片1',
+          coveImg: 'goods/img/s1.jpg',
+          lookSrc: 'goods/img/s1.jpg'
+        },
+        {
+          type: '分类1',
+          name: '图片11',
+          coveImg: 'goods/img/s2.jpg',
+          lookSrc: 'goods/img/s2.jpg'
+        },
+        {
+          type: '分类1',
+          name: '图片111',
+          coveImg: 'goods/img/s1.jpg',
+          lookSrc: 'goods/img/s1.jpg'
+        },
+        {
+          type: '分类2',
+          name: '图片2',
+          coveImg: 'goods/img/s2.jpg',
+          lookSrc: 'goods/img/s2.jpg'
+        },
+
+        {
+          type: '分类3',
+          name: '图片3',
+          coveImg: 'goods/img/s1.jpg',
+          lookSrc: 'goods/img/s1.jpg'
+        },
+        {
+          type: '分类4',
+          name: '图片4',
+          coveImg: 'goods/img/s2.jpg',
+          lookSrc: 'goods/img/s2.jpg'
+        },
+        {
+          type: '分类5',
+          name: '图片5',
+          coveImg: 'goods/img/s1.jpg',
+          lookSrc: 'goods/img/s1.jpg'
+        },
+        {
+          type: '分类6',
+          name: '图片6',
+          coveImg: 'goods/img/s2.jpg',
+          lookSrc: 'goods/img/s2.jpg'
+        }
+      ]
+    },
+    model: {
+      name: '三维模型',
+      name2: 'Model',
+      rowArr: [
+        {
+          type: '分类1',
+          name: '模型1',
+          coveImg: 'goods/img/s1.jpg',
+          lookSrc: 'goods/model/1.4dage'
+        },
+        {
+          type: '分类1',
+          name: '模型11',
+          coveImg: 'goods/img/s1.jpg',
+          lookSrc: 'goods/model/2.4dage'
+        }
+      ]
+    },
+    video: {
+      name: '影音资料',
+      name2: 'Audiovisual',
+      rowArr: [
+        {
+          type: '分类1',
+          name: '视频1',
+          coveImg: 'goods/img/s1.jpg',
+          lookSrc: 'goods/video/1.mp4'
+        }
+      ]
+    }
   }
 }

BIN
web/src/assets/img/rowBg.png


+ 37 - 3
web/src/assets/styles/base.css

@@ -69,6 +69,8 @@ textarea {
   height: 100%;
 }
 #root #AppM {
+  width: 100%;
+  height: 100%;
   overflow: hidden;
   max-width: 500px;
   margin: 0 auto;
@@ -86,9 +88,27 @@ textarea {
 [hidden] {
   display: none !important;
 }
+.mySorrlNo::-webkit-scrollbar {
+  /*滚动条整体样式*/
+  width: 0px;
+  /*高宽分别对应横竖滚动条的尺寸*/
+  height: 0px;
+}
+.mySorrlNo::-webkit-scrollbar-thumb {
+  /*滚动条里面小方块*/
+  border-radius: 10px;
+  -webkit-box-shadow: inset 0 0 5px transparent;
+  background: transparent;
+}
+.mySorrlNo::-webkit-scrollbar-track {
+  /*滚动条里面轨道*/
+  -webkit-box-shadow: inset 0 0 5px transparent;
+  border-radius: 10px;
+  background: transparent;
+}
 .mySorrl::-webkit-scrollbar {
   /*滚动条整体样式*/
-  width: 1px;
+  width: 3px;
   /*高宽分别对应横竖滚动条的尺寸*/
   height: 1px;
 }
@@ -96,13 +116,13 @@ textarea {
   /*滚动条里面小方块*/
   border-radius: 10px;
   -webkit-box-shadow: inset 0 0 5px transparent;
-  background: transparent;
+  background: #f4945a;
 }
 .mySorrl::-webkit-scrollbar-track {
   /*滚动条里面轨道*/
   -webkit-box-shadow: inset 0 0 5px transparent;
   border-radius: 10px;
-  background: transparent;
+  background: rgba(0, 0, 0, 0.2);
 }
 #root {
   /*横屏*/
@@ -193,3 +213,17 @@ textarea {
     right: 0px;
   }
 }
+#LookGood {
+  animation: LookGood 0.5s linear forwards;
+}
+@keyframes LookGood {
+  0% {
+    opacity: 0.1;
+  }
+  100% {
+    opacity: 1;
+  }
+}
+.ant-image-preview-operations {
+  background-color: rgba(0, 0, 0, 0.6) !important;
+}

+ 45 - 3
web/src/assets/styles/base.less

@@ -84,6 +84,8 @@ textarea {
 
   // 移动端AppM组件的样式
   #AppM {
+    width: 100%;
+    height: 100%;
     overflow: hidden;
     max-width: 500px;
     margin: 0 auto;
@@ -107,9 +109,31 @@ textarea {
 }
 
 // 滚动条
+.mySorrlNo::-webkit-scrollbar {
+  /*滚动条整体样式*/
+  width: 0px;
+  /*高宽分别对应横竖滚动条的尺寸*/
+  height: 0px;
+}
+
+.mySorrlNo::-webkit-scrollbar-thumb {
+  /*滚动条里面小方块*/
+  border-radius: 10px;
+  -webkit-box-shadow: inset 0 0 5px transparent;
+  background: transparent;
+}
+
+.mySorrlNo::-webkit-scrollbar-track {
+  /*滚动条里面轨道*/
+  -webkit-box-shadow: inset 0 0 5px transparent;
+  border-radius: 10px;
+  background: transparent;
+}
+
+// 滚动条
 .mySorrl::-webkit-scrollbar {
   /*滚动条整体样式*/
-  width: 1px;
+  width: 3px;
   /*高宽分别对应横竖滚动条的尺寸*/
   height: 1px;
 }
@@ -118,14 +142,14 @@ textarea {
   /*滚动条里面小方块*/
   border-radius: 10px;
   -webkit-box-shadow: inset 0 0 5px transparent;
-  background: transparent;
+  background: #f4945a;
 }
 
 .mySorrl::-webkit-scrollbar-track {
   /*滚动条里面轨道*/
   -webkit-box-shadow: inset 0 0 5px transparent;
   border-radius: 10px;
-  background: transparent;
+  background: rgba(0, 0, 0, 0.2);
 }
 
 #root {
@@ -238,3 +262,21 @@ textarea {
     right: 0px;
   }
 }
+
+#LookGood {
+  animation: LookGood 0.5s linear forwards;
+}
+
+@keyframes LookGood {
+  0% {
+    opacity: 0.1;
+  }
+
+  100% {
+    opacity: 1;
+  }
+}
+
+.ant-image-preview-operations {
+  background-color: rgba(0, 0, 0, 0.6) !important;
+}

+ 16 - 0
web/src/components/LazyImg/index.module.scss

@@ -0,0 +1,16 @@
+.LazyImg {
+  width: 100% !important;
+  height: 100% !important;
+  :global {
+    .lodingTxt {
+      font-size: 20px;
+      width: 100%;
+      height: 100%;
+      min-height: 120px;
+      display: flex;
+      justify-content: center;
+      align-items: center;
+      color: var(--themeColor2);
+    }
+  }
+}

+ 22 - 0
web/src/components/LazyImg/index.tsx

@@ -0,0 +1,22 @@
+import React from 'react'
+import styles from './index.module.scss'
+import { Image } from 'antd-mobile'
+
+type Props = {
+  src: string
+}
+
+function LazyImg({ src }: Props) {
+  return (
+    <Image
+      className={styles.LazyImg}
+      lazy
+      src={src}
+      placeholder={<div className='lodingTxt'>加载中...</div>}
+    />
+  )
+}
+
+const MemoLazyImg = React.memo(LazyImg)
+
+export default MemoLazyImg

+ 40 - 0
web/src/components/LeftTopLogo/index.module.scss

@@ -0,0 +1,40 @@
+.LeftTopLogo {
+  pointer-events: none;
+  position: fixed;
+  z-index: 9998;
+  top: 20px;
+  left: 20px;
+  display: flex;
+  :global {
+    .leftTopLogo1 {
+      height: 40px;
+    }
+    .leftTopLogo2 {
+      margin: 0 15px;
+      background: linear-gradient(
+        rgba(245, 133, 67, 0.15),
+        rgba(245, 133, 67, 1),
+        rgba(245, 133, 67, 0.15)
+      );
+      width: 1px;
+      height: 40px;
+    }
+    .leftTopLogo3 {
+      display: flex;
+      flex-direction: column;
+      justify-content: space-between;
+      & > p {
+        img {
+          width: 20px;
+        }
+        & > span {
+          font-weight: 700;
+          position: relative;
+          top: 1px;
+          display: inline-block;
+          margin: 0 10px;
+        }
+      }
+    }
+  }
+}

+ 27 - 0
web/src/components/LeftTopLogo/index.tsx

@@ -0,0 +1,27 @@
+import React from 'react'
+import styles from './index.module.scss'
+import { baseURL, myData } from '@/utils/history'
+import { useSelector } from 'react-redux'
+import { RootState } from '@/store'
+function LeftTopLogo() {
+  const { visitSum } = useSelector((state: RootState) => state.A0Layout)
+
+  return (
+    <div className={styles.LeftTopLogo}>
+      <img className='leftTopLogo1' src={baseURL + myData.home.logo} alt='' />
+
+      <div className='leftTopLogo2'></div>
+      <div className='leftTopLogo3'>
+        <p>欢迎您的到来,您是第{visitSum}位参观者!</p>
+        <p>
+          <img src={baseURL + myData.home.yan} alt='' />
+          <span>{visitSum}</span>浏览量
+        </p>
+      </div>
+    </div>
+  )
+}
+
+const MemoLeftTopLogo = React.memo(LeftTopLogo)
+
+export default MemoLeftTopLogo

+ 77 - 0
web/src/components/LookGood/index.module.scss

@@ -0,0 +1,77 @@
+.LookGood {
+  position: absolute;
+  z-index: 10;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  opacity: 0.1;
+  transition: opacity 0.5s;
+  :global {
+    .Lcolse {
+      z-index: 10;
+      cursor: pointer;
+      position: absolute;
+      top: 20px;
+      right: 20px;
+      width: 50px;
+    }
+    .Lmain {
+      position: absolute;
+      top: 50%;
+      left: 50%;
+      transform: translate(-50%, -50%);
+      width: 860px;
+      height: 560px;
+      .LmainCen {
+        width: 100%;
+        height: 490px;
+        .Limg {
+          width: 100%;
+          height: 100%;
+          cursor: zoom-in;
+          & > img {
+            pointer-events: none;
+            width: 100%;
+            height: 100%;
+            object-fit: contain !important;
+          }
+        }
+        video {
+          width: 100%;
+          height: 100%;
+        }
+      }
+      .LmTxt {
+        font-size: 22px;
+        font-weight: 700;
+        display: flex;
+        align-items: flex-end;
+        justify-content: center;
+        height: 70px;
+      }
+    }
+    .LmainFull {
+      top: 0;
+      left: 0;
+      width: 100%;
+      height: 100%;
+      transform: translate(0, 0);
+      .LmainCen {
+        height: 100%;
+        overflow: hidden;
+        iframe {
+          width: 100%;
+          height: 100%;
+        }
+      }
+      .LmTxt {
+        position: absolute;
+        z-index: 5;
+        bottom: 40px;
+        width: 100%;
+        left: 0;
+      }
+    }
+  }
+}

+ 58 - 0
web/src/components/LookGood/index.tsx

@@ -0,0 +1,58 @@
+import React, { useState } from 'react'
+import styles from './index.module.scss'
+import classNames from 'classnames'
+import { GoodsRow } from '@/types'
+import { baseURL } from '@/utils/history'
+import { Image } from 'antd'
+
+type Props = {
+  info: GoodsRow
+  closeFu: () => void
+  type: 'img' | 'model' | 'video'
+}
+
+function LookGood({ info, closeFu, type }: Props) {
+  const [imgLook, setImgLook] = useState('')
+
+  return (
+    <div className={classNames(styles.LookGood)} id='LookGood'>
+      {/* 关闭按钮 */}
+      <img onClick={closeFu} className='Lcolse' src={baseURL + 'goods/close.png'} alt='' />
+
+      <div className={classNames('Lmain', type === 'model' ? 'LmainFull' : '')}>
+        <div className='LmainCen'>
+          {type === 'img' ? (
+            <div className='Limg' onClick={() => setImgLook(baseURL + info.lookSrc)}>
+              <img src={baseURL + info.lookSrc} alt='' />
+            </div>
+          ) : null}
+          {type === 'model' ? (
+            <iframe
+              title={'模型'}
+              src={`${baseURL}modelLoding/model.html?u=${info.lookSrc}`}
+              frameBorder='0'
+            ></iframe>
+          ) : null}
+          {type === 'video' ? <video src={baseURL + info.lookSrc} controls /> : null}
+        </div>
+
+        <div className='LmTxt'>{info.name}</div>
+      </div>
+
+      {/* pc端查看图片 */}
+      {imgLook ? (
+        <Image
+          preview={{
+            visible: !!imgLook,
+            src: imgLook,
+            onVisibleChange: () => setImgLook('')
+          }}
+        />
+      ) : null}
+    </div>
+  )
+}
+
+const MemoLookGood = React.memo(LookGood)
+
+export default MemoLookGood

+ 20 - 49
web/src/components/RouterOrder.tsx

@@ -7,6 +7,11 @@ import NotFound from '@/components/NotFound'
 const A1home = React.lazy(() => import('@/pages/A1home'))
 const A1homeM = React.lazy(() => import('@/pages/A1homeM'))
 
+const A2scene = React.lazy(() => import('@/pages/A2scene'))
+
+const A3goods = React.lazy(() => import('@/pages/A3goods'))
+const A3goodsM = React.lazy(() => import('@/pages/A3goodsM'))
+
 const routerArr = [
   {
     id: 1,
@@ -15,55 +20,21 @@ const routerArr = [
     exact: true,
     Com: isMobileFu() ? A1homeM : A1home
   },
-  // {
-  //   id: 2,
-  //   name: "村落",
-  //   path: "/village",
-  //   exact: false,
-  //   Com: isMobileFu() ? B1VillageM : B1Village,
-  // },
-  // {
-  //   id: 3,
-  //   name: "建筑",
-  //   path: "/architec",
-  //   exact: false,
-  //   Com: isMobileFu() ? C1ArchitecM : C1Architec,
-  // },
-  // {
-  //   id: 4,
-  //   name: "建筑详情",
-  //   path: "/architecInfo",
-  //   exact: false,
-  //   Com: C2ArchitecInfo,
-  // },
-  // {
-  //   id: 5,
-  //   name: "构件",
-  //   path: "/build",
-  //   exact: false,
-  //   Com: isMobileFu() ? D1BuildM : D1Build,
-  // },
-  // {
-  //   id: 6,
-  //   name: "构件详情",
-  //   path: "/buildInfo",
-  //   exact: false,
-  //   Com: D2BuildInfo,
-  // },
-  // {
-  //   id: 7,
-  //   name: "场景",
-  //   path: "/scene",
-  //   exact: false,
-  //   Com: Z2Scene,
-  // },
-  // {
-  //   id: 8,
-  //   name: "搜索",
-  //   path: "/search",
-  //   exact: false,
-  //   Com: Z1Search,
-  // },
+  {
+    id: 2,
+    name: '展馆漫游',
+    path: '/scene',
+    exact: false,
+    Com: A2scene
+  },
+  {
+    id: 3,
+    name: '精品典藏',
+    path: '/goods',
+    exact: false,
+    Com: isMobileFu() ? A3goodsM : A3goods
+  },
+
   {
     id: 9,
     name: '找不到页面',

+ 4 - 3
web/src/pages/A1home/index.tsx

@@ -1,9 +1,10 @@
 import React, { useEffect, useRef, useState } from 'react'
 import styles from './index.module.scss'
-import history, { baseURL, isMobileFu, myData } from '@/utils/history'
+import history, { baseURL, myData } from '@/utils/history'
 import { useSelector } from 'react-redux'
 import { RootState } from '@/store'
 import classNames from 'classnames'
+
 function A1home() {
   const { visitSum } = useSelector((state: RootState) => state.A0Layout)
 
@@ -56,7 +57,7 @@ function A1home() {
         <div className='A1Left'>
           <img src={baseURL + myData.home.logo} alt='' />
           <div
-            className='A1LeftTxt mySorrl'
+            className='A1LeftTxt mySorrlNo'
             dangerouslySetInnerHTML={{ __html: myData.home.txt }}
           ></div>
         </div>
@@ -67,7 +68,7 @@ function A1home() {
             <div
               className={classNames('A1rRow', 'A1rRow' + i, bacFlag[i] ? 'A1rRowAc' : '')}
               key={v.id}
-              onClick={() => history.push(`${v.path}${isMobileFu() ? 'M' : ''}`)}
+              onClick={() => history.push(v.path)}
             >
               <div className='A1rRowOne'>
                 {/* 反的 */}

+ 5 - 0
web/src/pages/A2scene/index.module.scss

@@ -1,4 +1,9 @@
 .A2scene {
+  overflow: hidden;
   :global {
+    iframe {
+      width: 100%;
+      height: 100%;
+    }
   }
 }

+ 22 - 3
web/src/pages/A2scene/index.tsx

@@ -1,10 +1,29 @@
-import React from 'react'
+import React, { useEffect } from 'react'
 import styles from './index.module.scss'
+import history, { sceneBaseUrl } from '@/utils/history'
+
+declare global {
+  //设置全局属性
+  interface Window {
+    //window对象属性
+    toHomeFu: () => void
+  }
+}
+
 function A2scene() {
+  useEffect(() => {
+    window.toHomeFu = () => {
+      history.replace('/')
+    }
+  }, [])
+
   return (
     <div className={styles.A2scene}>
-      <h1>中山大学附属肿瘤医院线上院史馆</h1>
-      <h2>移动端开发中,敬请期待.</h2>
+      <iframe
+        src={sceneBaseUrl.replace('Web', 'Scene') + 'index.html#/?h=1'}
+        frameBorder='0'
+        title='场景'
+      ></iframe>
     </div>
   )
 }

+ 158 - 0
web/src/pages/A3goods/index.module.scss

@@ -0,0 +1,158 @@
+.A3goods {
+  background-size: 100% 100%;
+  :global {
+    .A2main {
+      position: absolute;
+      top: 50%;
+      left: 50%;
+      transform: translate(-50%, -50%);
+      width: 80%;
+      height: 70%;
+      display: flex;
+      justify-content: space-around;
+      & > div {
+        width: 26%;
+        background-image: url('../../assets/img/rowBg.png');
+        background-size: 100% 100%;
+        .A2top {
+          padding: 4% 6% 0;
+          height: 94px;
+          .A2top1 {
+            display: flex;
+            align-items: flex-end;
+            color: #fff;
+            & > h1 {
+              font-size: 20px;
+              font-weight: 600;
+              margin-right: 12px;
+            }
+          }
+          .A2top2 {
+            margin-top: 10px;
+            margin-bottom: 15px;
+            height: calc(100% - 32px);
+            width: 100%;
+            .swiper-slide {
+              width: auto !important;
+            }
+
+            .topRow {
+              cursor: pointer;
+              display: inline-block;
+              font-size: 16px;
+              color: #fff;
+              padding: 4px 14px;
+            }
+            .active {
+              border-radius: 15px;
+              background-color: #f58543;
+            }
+          }
+        }
+        .A2info {
+          padding: 20px 15px 20px 30px;
+          background-color: #faf3e6;
+          height: calc(100% - 134px);
+
+          & > div {
+            padding-right: 15px;
+            overflow-y: auto;
+            height: 100%;
+            width: 100%;
+            .A2Irow {
+              cursor: pointer;
+              height: 40%;
+              width: 100%;
+              border-radius: 10px;
+              overflow: hidden;
+              margin-bottom: 15px;
+              background-color: #fff;
+              .A2Irow1 {
+                width: 100%;
+                height: calc(100% - 36px);
+                img {
+                  object-fit: cover !important;
+                }
+              }
+              .A2Irow2 {
+                position: relative;
+                height: 36px;
+                line-height: 36px;
+                padding-right: 40px;
+                padding-left: 10px;
+                overflow: hidden;
+                text-overflow: ellipsis;
+                white-space: nowrap;
+                & > img {
+                  position: absolute;
+                  height: 26px;
+                  top: 5px;
+                  right: 10px;
+                }
+              }
+            }
+
+            // 没有搜到东西
+            .A2IrowNull {
+              width: 100%;
+              height: 100%;
+              display: flex;
+              justify-content: center;
+              align-items: center;
+              flex-direction: column;
+              text-align: center;
+              & > img {
+                width: 50%;
+              }
+              & > p {
+                font-size: 16px;
+                margin: 5px 0;
+              }
+            }
+          }
+        }
+        .A2Search {
+          width: 100%;
+          height: 38px;
+          color: #fff;
+          display: flex;
+          justify-content: center;
+          align-items: center;
+          padding: 0 20px;
+          .A2SearchIcon {
+            cursor: pointer;
+            color: #fff;
+            font-size: 24px;
+          }
+          .adm-input {
+            width: 80%;
+            height: 100%;
+            input {
+              text-align: center;
+              color: #fff;
+            }
+            input::-webkit-input-placeholder {
+              /* WebKit browsers */
+              color: rgba(255, 255, 255, 0.6);
+            }
+
+            input:-moz-placeholder {
+              /* Mozilla Firefox 4 to 18 */
+              color: rgba(255, 255, 255, 0.6);
+            }
+
+            input::-moz-placeholder {
+              /* Mozilla Firefox 19+ */
+              color: rgba(255, 255, 255, 0.6);
+            }
+
+            input:-ms-input-placeholder {
+              /* Internet Explorer 10+ */
+              color: rgba(255, 255, 255, 0.6);
+            }
+          }
+        }
+      }
+    }
+  }
+}

+ 263 - 0
web/src/pages/A3goods/index.tsx

@@ -0,0 +1,263 @@
+import React, { useCallback, useEffect, useState } from 'react'
+import styles from './index.module.scss'
+import LeftTopLogo from '@/components/LeftTopLogo'
+import { baseURL, myData } from '@/utils/history'
+import { GoodsInfo, GoodsRow } from '@/types'
+import { SearchOutline } from 'antd-mobile-icons'
+
+import { Swiper, SwiperSlide } from 'swiper/react'
+
+import { FreeMode, Mousewheel } from 'swiper'
+// Import Swiper styles
+import 'swiper/css'
+import 'swiper/css/free-mode'
+import 'swiper/css/mousewheel'
+
+import classNames from 'classnames'
+import LazyImg from '@/components/LazyImg'
+import { Input } from 'antd-mobile'
+import LookGood from '@/components/LookGood'
+
+function A3goods() {
+  const [data1, setData1] = useState(myData.goods.img.rowArr)
+  const [ac1, setAc1] = useState('全部')
+  const [rowArr1, setrowArr1] = useState<string[]>([])
+  const [txt1, setTxt1] = useState('')
+  const [txtFlag1, setTxtFlag1] = useState(false)
+
+  const [data2, setData2] = useState(myData.goods.model.rowArr)
+  const [ac2, setAc2] = useState('全部')
+  const [rowArr2, setrowArr2] = useState<string[]>([])
+  const [txt2, setTxt2] = useState('')
+  const [txtFlag2, setTxtFlag2] = useState(false)
+
+  const [data3, setData3] = useState(myData.goods.video.rowArr)
+  const [ac3, setAc3] = useState('全部')
+  const [rowArr3, setrowArr3] = useState<string[]>([])
+  const [txt3, setTxt3] = useState('')
+  const [txtFlag3, setTxtFlag3] = useState(false)
+
+  useEffect(() => {
+    const types1 = myData.goods.img.rowArr.map(v => v.type)
+    setrowArr1(['全部', ...Array.from(new Set(types1))])
+
+    const types2 = myData.goods.model.rowArr.map(v => v.type)
+    setrowArr2(['全部', ...Array.from(new Set(types2))])
+
+    const types3 = myData.goods.video.rowArr.map(v => v.type)
+    setrowArr3(['全部', ...Array.from(new Set(types3))])
+  }, [])
+
+  // 滚动到顶部
+  const sollrTop = useCallback((classKey: string) => {
+    const dom: HTMLDivElement = document.querySelector('.' + classKey)!
+    if (dom) dom.scrollTop = 0
+  }, [])
+
+  // 切换顶部
+  const topCut = useCallback(
+    (name: string, acTxt: string, index: 1 | 2 | 3, classKey: string) => {
+      if (name === acTxt) return
+      index === 1 ? setAc1(name) : index === 2 ? setAc2(name) : setAc3(name)
+
+      index === 1 ? setTxt1('') : index === 2 ? setTxt2('') : setTxt3('')
+
+      const dataAll =
+        index === 1
+          ? myData.goods.img.rowArr
+          : index === 2
+          ? myData.goods.model.rowArr
+          : myData.goods.video.rowArr
+
+      let resData: GoodsRow[] = []
+
+      if (name === '全部') resData = dataAll
+      else resData = dataAll.filter(v => v.type === name)
+
+      index === 1 ? setData1(resData) : index === 2 ? setData2(resData) : setData3(resData)
+
+      sollrTop(classKey)
+    },
+    [sollrTop]
+  )
+
+  // 点击搜索
+  const searchFu = useCallback(
+    (val: string, index: 1 | 2 | 3, classKey: string) => {
+      const dataAllTemp =
+        index === 1
+          ? myData.goods.img.rowArr
+          : index === 2
+          ? myData.goods.model.rowArr
+          : myData.goods.video.rowArr
+      let resData: GoodsRow[] = []
+
+      const acTxt = index === 1 ? ac1 : index === 2 ? ac2 : ac3
+
+      const dataAll = dataAllTemp.filter(v => v.type === acTxt)
+
+      if (val === '') resData = dataAll
+      else resData = dataAll.filter(v => v.name.includes(val))
+
+      index === 1 ? setData1(resData) : index === 2 ? setData2(resData) : setData3(resData)
+
+      sollrTop(classKey)
+    },
+    [ac1, ac2, ac3, sollrTop]
+  )
+
+  // 打开详情
+  const [opInfo, setOpInfo] = useState({} as GoodsRow)
+  const [opType, setOpType] = useState<any>('')
+
+  const rowBox = useCallback(
+    (info: GoodsInfo, index: 1 | 2 | 3) => {
+      const rowArr = index === 1 ? rowArr1 : index === 2 ? rowArr2 : rowArr3
+      const acTxt = index === 1 ? ac1 : index === 2 ? ac2 : ac3
+      const data = index === 1 ? data1 : index === 2 ? data2 : data3
+      const txtFlag = index === 1 ? txtFlag1 : index === 2 ? txtFlag2 : txtFlag3
+
+      return (
+        <div>
+          <div className='A2top'>
+            <div className='A2top1'>
+              <h1>{info.name}</h1> {info.name2}
+            </div>
+            <div className='A2top2'>
+              <Swiper
+                modules={[FreeMode, Mousewheel]}
+                className='appSw'
+                spaceBetween={0}
+                slidesPerView='auto'
+                freeMode={true}
+                mousewheel={true}
+                // onSlideChange={(e) => console.log("slide change", e)}
+                // onSwiper={(swiper) => (mySwiperRef.current = swiper)}
+              >
+                {rowArr.map(v => (
+                  <SwiperSlide key={v}>
+                    <div
+                      onClick={() => topCut(v, acTxt, index, 'A2Sorrl' + index)}
+                      className={classNames('topRow', acTxt === v ? 'active' : '')}
+                    >
+                      <span>{v}</span>
+                    </div>
+                  </SwiperSlide>
+                ))}
+              </Swiper>
+            </div>
+          </div>
+
+          <div className='A2info'>
+            <div className={classNames('mySorrl', 'A2Sorrl' + index)}>
+              {data.length ? (
+                <>
+                  {data.map(v => (
+                    <div
+                      className='A2Irow'
+                      onClick={() => {
+                        setOpInfo(v)
+                        setOpType(index === 1 ? 'img' : index === 2 ? 'model' : 'video')
+                      }}
+                      key={v.name}
+                      title={v.name}
+                    >
+                      <div className='A2Irow1'>
+                        <LazyImg src={baseURL + v.coveImg} />
+                      </div>
+                      <div className='A2Irow2'>
+                        {v.name}
+                        <img src={baseURL + 'goods/iconS.png'} alt='' />
+                      </div>
+                    </div>
+                  ))}
+                </>
+              ) : (
+                <div className='A2IrowNull'>
+                  <img src={baseURL + 'goods/null.png'} alt='' />
+                  <p>暂时没有数据</p>
+                  <p>请试一下其他关键字</p>
+                </div>
+              )}
+            </div>
+          </div>
+
+          {/* 底部搜索 */}
+          <div className='A2Search'>
+            {txtFlag ? (
+              <Input
+                onEnterPress={() =>
+                  searchFu(index === 1 ? txt1 : index === 2 ? txt2 : txt3, index, 'A2Sorrl' + index)
+                }
+                placeholder='请输入搜索内容'
+                value={index === 1 ? txt1 : index === 2 ? txt2 : txt3}
+                onChange={value => {
+                  const txtRes = value.replace(/\s+/g, '')
+                  index === 1 ? setTxt1(txtRes) : index === 2 ? setTxt2(txtRes) : setTxt3(txtRes)
+                }}
+              />
+            ) : null}
+
+            <div
+              className='A2SearchIcon'
+              onClick={() => {
+                if (!txtFlag) {
+                  index === 1
+                    ? setTxtFlag1(true)
+                    : index === 2
+                    ? setTxtFlag2(true)
+                    : setTxtFlag3(true)
+                } else
+                  searchFu(index === 1 ? txt1 : index === 2 ? txt2 : txt3, index, 'A2Sorrl' + index)
+              }}
+            >
+              <SearchOutline />
+            </div>
+          </div>
+        </div>
+      )
+    },
+    [
+      ac1,
+      ac2,
+      ac3,
+      data1,
+      data2,
+      data3,
+      searchFu,
+      rowArr1,
+      rowArr2,
+      rowArr3,
+      topCut,
+      txt1,
+      txt2,
+      txt3,
+      txtFlag1,
+      txtFlag2,
+      txtFlag3
+    ]
+  )
+
+  return (
+    <div
+      className={styles.A3goods}
+      style={{ backgroundImage: `url(${baseURL + myData.goods.bg})` }}
+    >
+      <LeftTopLogo />
+
+      <div className='A2main' hidden={!!opInfo.name}>
+        {rowBox(myData.goods.img, 1)}
+        {rowBox(myData.goods.model, 2)}
+        {rowBox(myData.goods.video, 3)}
+      </div>
+
+      {opInfo.name ? (
+        <LookGood info={opInfo} closeFu={() => setOpInfo({} as GoodsRow)} type={opType} />
+      ) : null}
+    </div>
+  )
+}
+
+const MemoA3goods = React.memo(A3goods)
+
+export default MemoA3goods

+ 1 - 1
web/src/pages/A2sceneM/index.module.scss

@@ -1,4 +1,4 @@
-.A2sceneM {
+.A3goodsM {
   :global {
   }
 }

+ 4 - 4
web/src/pages/A2sceneM/index.tsx

@@ -1,14 +1,14 @@
 import React from 'react'
 import styles from './index.module.scss'
-function A2sceneM() {
+function A3goodsM() {
   return (
-    <div className={styles.A2sceneM}>
+    <div className={styles.A3goodsM}>
       <h1>中山大学附属肿瘤医院线上院史馆</h1>
       <h2>移动端开发中,敬请期待.</h2>
     </div>
   )
 }
 
-const MemoA2sceneM = React.memo(A2sceneM)
+const MemoA3goodsM = React.memo(A3goodsM)
 
-export default MemoA2sceneM
+export default MemoA3goodsM

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

@@ -1 +1,32 @@
+export type MyDataType = {
+  home: {
+    bg: string
+    logo: string
+    yan: string
+    txt: string
+    arr: {
+      id: number
+      name: string
+      path: string
+    }[]
+  }
+  goods: {
+    bg: string
+    img: GoodsInfo
+    model: GoodsInfo
+    video: GoodsInfo
+  }
+}
 
+export type GoodsRow = {
+  type: string
+  name: string
+  coveImg: string
+  lookSrc: string
+}
+
+export type GoodsInfo = {
+  name: string
+  name2: string
+  rowArr: GoodsRow[]
+}

+ 2 - 14
web/src/types/declaration.d.ts

@@ -10,17 +10,5 @@ declare module 'braft-utils'
 // 一些在pubilc/index里面直接定义的数据类型声明
 declare const baseUrlLoc: string
 declare const baseUrlAtl: string
-declare const myDataTemp: MyDataType
-type MyDataType = {
-  home: {
-    bg: string
-    logo: string
-    yan: string
-    txt: string
-    arr: {
-      id: number
-      name: string
-      path: string
-    }[]
-  }
-}
+declare const myBaseUrl: string
+declare const myDataTemp: any

+ 5 - 1
web/src/utils/history.ts

@@ -1,3 +1,4 @@
+import { MyDataType } from '@/types'
 import { createHashHistory } from 'history'
 const history = createHashHistory()
 export default history
@@ -19,4 +20,7 @@ export const isLoc = process.env.NODE_ENV === 'development'
 export const baseURL = isLoc ? baseUrlLoc : baseUrlAtl
 
 // 所有数据
-export const myData = myDataTemp
+export const myData: MyDataType = myDataTemp
+
+// 场景基地址
+export const sceneBaseUrl = myBaseUrl

+ 12 - 0
web/yarn.lock

@@ -9420,6 +9420,11 @@ sprintf-js@~1.0.2:
   resolved "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
   integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==
 
+ssr-window@^4.0.2:
+  version "4.0.2"
+  resolved "https://registry.npmmirror.com/ssr-window/-/ssr-window-4.0.2.tgz#dc6b3ee37be86ac0e3ddc60030f7b3bc9b8553be"
+  integrity sha512-ISv/Ch+ig7SOtw7G2+qkwfVASzazUnvlDTwypdLoPoySv+6MqlOV10VwPSE6EWkGjhW50lUmghPmpYZXMu/+AQ==
+
 stable@^0.1.8:
   version "0.1.8"
   resolved "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf"
@@ -9714,6 +9719,13 @@ svgo@^2.7.0:
     picocolors "^1.0.0"
     stable "^0.1.8"
 
+swiper@^9.1.0:
+  version "9.4.1"
+  resolved "https://registry.npmmirror.com/swiper/-/swiper-9.4.1.tgz#2f48bcd6ab4b4fcf4ae93eaee53980531d42fd42"
+  integrity sha512-1nT2T8EzUpZ0FagEqaN/YAhRj33F2x/lN6cyB0/xoYJDMf8KwTFT3hMOeoB8Tg4o3+P/CKqskP+WX0Df046fqA==
+  dependencies:
+    ssr-window "^4.0.2"
+
 symbol-tree@^3.2.4:
   version "3.2.4"
   resolved "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2"

BIN
web静态资源/staticData/goods/bg.jpg


BIN
web静态资源/staticData/goods/close.png


BIN
web静态资源/staticData/goods/iconS.png


BIN
web静态资源/staticData/goods/img/s1.jpg


BIN
web静态资源/staticData/goods/img/s2.jpg


BIN
web静态资源/staticData/goods/model/1.4dage


BIN
web静态资源/staticData/goods/model/2.4dage


BIN
web静态资源/staticData/goods/null.png


BIN
web静态资源/staticData/goods/video/1.mp4


File diff suppressed because it is too large
+ 6352 - 0
web静态资源/staticData/modelLoding/4dage.js


+ 33 - 0
web静态资源/staticData/modelLoding/model.html

@@ -0,0 +1,33 @@
+<!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;
+      }
+    </style>
+  </head>
+
+  <body>
+    <div id="ui"></div>
+    <script>
+      let src = getQueryVariable('u')
+    
+
+      // fdage.embed( number, {
+      fdage.embed(`../${src}`, {
+        transparentBackground: true,
+        width: 800,
+        height: 600,
+        autoStart: true,
+        fullFrame: true,
+        pagePreset: false
+      })
+    </script>
+  </body>
+</html>