lanxin 19 часов назад
Родитель
Сommit
5c584921b1
31 измененных файлов с 747 добавлено и 27 удалено
  1. 81 0
      project/public/f-video.js
  2. 2 0
      project/public/index.html
  3. 1 0
      project/public/jsmpeg.min.js
  4. BIN
      project/public/siji.ts
  5. 10 0
      project/public/three/data.js
  6. 3 2
      project/public/tuPu/js/index.js
  7. 4 2
      project/src/App.tsx
  8. BIN
      project/src/assets/A1_sider_bg.png
  9. BIN
      project/src/assets/img/A5_view_bg_M.jpg
  10. BIN
      project/src/assets/img/A5_view_list.png
  11. BIN
      project/src/assets/img/A5_view_tupuBtn.png
  12. BIN
      project/src/assets/img/A6_life_bg_M.jpg
  13. BIN
      project/src/assets/img/arrow_down.png
  14. BIN
      project/src/assets/img/arrow_up.png
  15. 3 3
      project/src/components/Zvideo/index.tsx
  16. 69 4
      project/src/pages/A0base/index.tsx
  17. 26 6
      project/src/pages/A0baseM/index.tsx
  18. 1 1
      project/src/pages/A1home/index.module.scss
  19. 16 2
      project/src/pages/A1home/index.tsx
  20. 1 0
      project/src/pages/A1homeM/PanoHot/index.module.scss
  21. 23 0
      project/src/pages/A5viewM/A5atlas/index.module.scss
  22. 24 0
      project/src/pages/A5viewM/A5atlas/index.tsx
  23. 93 0
      project/src/pages/A5viewM/PanoHot/index.module.scss
  24. 35 0
      project/src/pages/A5viewM/PanoHot/index.tsx
  25. 86 0
      project/src/pages/A5viewM/index.module.scss
  26. 57 0
      project/src/pages/A5viewM/index.tsx
  27. 1 2
      project/src/pages/A6life/Record/index.tsx
  28. 142 0
      project/src/pages/A6lifeM/index.module.scss
  29. 55 0
      project/src/pages/A6lifeM/index.tsx
  30. 10 0
      project/src/pages/A7animationM/index.module.scss
  31. 4 5
      project/src/pages/A7animationM/index.tsx

+ 81 - 0
project/public/f-video.js

@@ -0,0 +1,81 @@
+/* f-video.js */
+let F_Video;
+(function () {
+  F_Video = function (url, option) {
+    const u = window.navigator.userAgent.toLowerCase();
+    const isAndroid = u.indexOf("android") > -1;
+    let player = new Object();
+
+
+ let newCanvas = document.createElement("canvas");
+      let params = {
+        canvas: newCanvas,
+        loop: option.loop || false,
+        autoplay: option.autoplay || false,
+        onEnded: () => {
+          option.onEnded && option.onEnded();
+          player.currentTime = 0;
+        },
+      };
+
+      newCanvas.style.width = "100%";
+      newCanvas.style.height = "100%";
+      newCanvas.style.objectFit = option.objectFit || "cover";
+
+      player = new JSMpeg.Player(url.replace(".mp4", ".ts"), {
+        ...option,
+        ...params,
+      });
+      player.domElement = newCanvas;
+
+    // if (isAndroid) {
+    //   let newCanvas = document.createElement("canvas");
+    //   let params = {
+    //     canvas: newCanvas,
+    //     loop: option.loop || false,
+    //     autoplay: option.autoplay || false,
+    //     onEnded: () => {
+    //       option.onEnded && option.onEnded();
+    //       player.currentTime = 0;
+    //     },
+    //   };
+
+    //   newCanvas.style.width = "100%";
+    //   newCanvas.style.height = "100%";
+    //   newCanvas.style.objectFit = option.objectFit || "cover";
+
+    //   player = new JSMpeg.Player(url.replace(".mp4", ".ts"), {
+    //     ...option,
+    //     ...params,
+    //   });
+    //   player.domElement = newCanvas;
+    // } else {
+    //   let newVideo = document.createElement("video");
+    //   newVideo.setAttribute("x5-video-player-type", "h5");
+    //   newVideo.setAttribute("x-webkit-airplay", "true");
+    //   newVideo.setAttribute("airplay", "allow");
+    //   newVideo.setAttribute("playsinline", "");
+    //   newVideo.setAttribute("webkit-playsinline", "");
+    //   newVideo.setAttribute("src", url);
+    //   option.loop && newVideo.setAttribute("loop", "loop");
+    //   !option.autoplay && newVideo.setAttribute("preload", "auto");
+    //   option.autoplay &&
+    //     window.WeixinJSBridge &&
+    //     window.WeixinJSBridge.invoke("getNetworkType", {}, (e) => {
+    //       player.play();
+    //     });
+
+    //   newVideo.style.width = "100%";
+    //   newVideo.style.height = "100%";
+    //   newVideo.style.objectFit = option.objectFit || "cover";
+
+    //   player = newVideo;
+    //   player.domElement = newVideo;
+
+    //   option.onPlay && player.addEventListener("play", option.onPlay);
+    //   option.onPause && player.addEventListener("pause", option.onPause);
+    //   option.onEnded && player.addEventListener("ended", option.onEnded);
+    // }
+    return player;
+  };
+})();

+ 2 - 0
project/public/index.html

@@ -18,4 +18,6 @@
     <noscript>You need to enable JavaScript to run this app.</noscript>
     <div id="root" style="width: 100%; height: 100vh"></div>
   </body>
+  <script src="./jsmpeg.min.js"></script>
+<script src="./f-video.js"></script>
 </html>

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


BIN
project/public/siji.ts


+ 10 - 0
project/public/three/data.js

@@ -7,6 +7,7 @@ const baseOssUrl2 = './'
 const cardNames = [
   {
     id: 1,
+    modelShow: true,
     name: '文物的名称1',
     img: '1.png',
     obj: { 位置: '左右大厅', 构件: '构件1', 装饰: '装饰1', 材质: '材质1' },
@@ -69,6 +70,7 @@ const cardNames = [
   },
   {
     id: 2,
+    modelShow: true,
     name: '文物的名称2',
     img: '2.png',
     obj: { 构件: '构件2', 位置: '位置2', 装饰: '装饰2', 材质: '材质2' },
@@ -105,6 +107,7 @@ const cardNames = [
   },
   {
     id: 3,
+    modelShow: false,
     name: '文物的名称3',
     img: '3.png',
 
@@ -113,6 +116,7 @@ const cardNames = [
   },
   {
     id: 4,
+    modelShow: false,
     name: '文物的名称4',
     img: '4.png',
 
@@ -121,6 +125,7 @@ const cardNames = [
   },
   {
     id: 5,
+    modelShow: false,
     name: '文物的名称5',
     img: '5.png',
 
@@ -129,6 +134,7 @@ const cardNames = [
   },
   {
     id: 6,
+    modelShow: false,
     name: '文物的名称6',
     img: '6.png',
 
@@ -137,6 +143,7 @@ const cardNames = [
   },
   {
     id: 7,
+    modelShow: false,
     name: '文物的名称7',
     img: '7.png',
 
@@ -145,6 +152,7 @@ const cardNames = [
   },
   {
     id: 8,
+    modelShow: false,
     name: '文物的名称8',
     img: '8.png',
 
@@ -153,6 +161,7 @@ const cardNames = [
   },
   {
     id: 9,
+    modelShow: false,
     name: '文物的名称9',
     img: '9.png',
 
@@ -161,6 +170,7 @@ const cardNames = [
   },
   {
     id: 10,
+    modelShow: false,
     name: '文物的名称10',
     img: '10.png',
 

+ 3 - 2
project/public/tuPu/js/index.js

@@ -1,5 +1,6 @@
 function anonymous() {
   const urlAll = window.location.href
+  const isMobile = window.innerWidth <= 500
 
   if (!urlAll.includes('?id=')) return alert('参数错误')
 
@@ -146,8 +147,8 @@ function anonymous() {
         // zoom: 1,
         scaleLimit: {
           // 缩放限制,只在使用 force 布局时有效。
-          min: 0.8 ? 0.8 : 0.5, // 最小缩放比例
-          max: 3 // 最大缩放比例
+          min: isMobile ? 0.5 : 0.8, // 最小缩放比例
+          max: isMobile ? 2 : 3 // 最大缩放比例
         },
         nodeScaleRatio: 0.6, // 鼠标漫游缩放时节点的相应缩放比例,当设为0时节点不随着鼠标的缩放而缩放
         draggable: true, // 节点是否可拖拽,只在使用力引导布局的时候有用。

+ 4 - 2
project/src/App.tsx

@@ -25,7 +25,9 @@ const Scene = React.lazy(() => import('./pages/A2scene'))
 const Architecture = React.lazy(() => import('./pages/A3architecture'))
 const Member = React.lazy(() => import('./pages/A4member'))
 const View = React.lazy(() => import('./pages/A5view'))
+const ViewM = React.lazy(() => import('./pages/A5viewM'))
 const Life = React.lazy(() => import('./pages/A6life'))
+const LifeM = React.lazy(() => import('./pages/A6lifeM'))
 const AnimationM = React.lazy(() => import('./pages/A7animationM'))
 declare global {
   //设置全局属性
@@ -79,8 +81,8 @@ export default function App() {
             <Route path='/scene' component={Scene} exact />
             <Route path='/architecture' component={Architecture} exact />
             <Route path='/member' component={Member} exact />
-            <Route path='/view/:key' component={View} exact />
-            <Route path='/life' component={Life} exact />
+            <Route path='/view/:key' component={isMobiileFu() ? ViewM : View} exact />
+            <Route path='/life' component={isMobiileFu() ? LifeM : Life} exact />
             <Route path='/animation' component={AnimationM} exact />
             <Route path='*' component={NotFound} />
           </Switch>

BIN
project/src/assets/A1_sider_bg.png


BIN
project/src/assets/img/A5_view_bg_M.jpg


BIN
project/src/assets/img/A5_view_list.png


BIN
project/src/assets/img/A5_view_tupuBtn.png


BIN
project/src/assets/img/A6_life_bg_M.jpg


BIN
project/src/assets/img/arrow_down.png


BIN
project/src/assets/img/arrow_up.png


+ 3 - 3
project/src/components/Zvideo/index.tsx

@@ -23,7 +23,7 @@ function Zvideo({ src, objFit = 'fill' ,style}: { src: string; objFit?: string,s
       role="button"
       tabIndex={0}
     >
-      <media-controller class='custom-media-controller' gesturesdisabled={true}>
+      {/* <media-controller class='custom-media-controller' gesturesdisabled={true}> */}
         <video
           ref={videoRef}
           slot='media'
@@ -35,7 +35,7 @@ function Zvideo({ src, objFit = 'fill' ,style}: { src: string; objFit?: string,s
             handleTogglePlay()
           }}
         ></video>
-        <media-control-bar>
+        {/* <media-control-bar>
           <media-play-button></media-play-button>
           <div className='progress'>
             <media-time-range></media-time-range>
@@ -45,7 +45,7 @@ function Zvideo({ src, objFit = 'fill' ,style}: { src: string; objFit?: string,s
             <media-duration-display></media-duration-display>
           </div>
         </media-control-bar>
-      </media-controller>
+      </media-controller> */}
     </div>
   )
 }

+ 69 - 4
project/src/pages/A0base/index.tsx

@@ -1,4 +1,4 @@
-import React, { useEffect, useState, useMemo } from 'react';
+import React, { useEffect, useLayoutEffect, useState, useMemo, useRef, useCallback } from 'react';
 import history from '@/utils/history';// 如果用 react-router 跳转
 import styles from './index.module.scss';
 import A0_home_1 from '@/assets/img/A0_home_1.jpg';
@@ -6,15 +6,79 @@ import A0_home_2 from '@/assets/img/A0_home_2.jpg';
 import A0_home_3 from '@/assets/img/A0_home_3.jpg';
 import homeLogo from '@/assets/img/A0_home_logo.png'; // 推荐直接 import 图片
 
+/** 以「页面上真实 img」为准:load + decode + 双 rAF,避免与独立 new Image() 不同步 */
+function useDomImageReady(
+  ref: React.RefObject<HTMLImageElement | null>,
+  onReady: () => void,
+) {
+  const onReadyRef = useRef(onReady);
+  onReadyRef.current = onReady;
+  const firedRef = useRef(false);
+
+  useLayoutEffect(() => {
+    const el = ref.current;
+    if (!el) return;
+
+    const fire = () => {
+      if (firedRef.current) return;
+      firedRef.current = true;
+      void (async () => {
+        try {
+          if (typeof el.decode === 'function') await el.decode();
+        } catch {
+          /* ignore */
+        }
+        requestAnimationFrame(() => {
+          requestAnimationFrame(() => {
+            onReadyRef.current();
+          });
+        });
+      })();
+    };
+
+    if (el.complete && el.naturalWidth > 0) {
+      fire();
+    } else {
+      el.addEventListener('load', fire, { once: true });
+      el.addEventListener('error', fire, { once: true });
+    }
+
+    return () => {
+      el.removeEventListener('load', fire);
+      el.removeEventListener('error', fire);
+    };
+  }, [ref]);
+}
+
 function A0base() {
   const [currentIndex, setCurrentIndex] = useState(0);
   const [progress, setProgress] = useState(0); // 进度 0 ~ 100
+  const [assetsReady, setAssetsReady] = useState(false);
   const images = useMemo(() => [A0_home_1, A0_home_2, A0_home_3], []);
 
+  const slide0Ref = useRef<HTMLImageElement>(null);
+  const slide1Ref = useRef<HTMLImageElement>(null);
+  const slide2Ref = useRef<HTMLImageElement>(null);
+  const logoRef = useRef<HTMLImageElement>(null);
+  const pendingRef = useRef(4);
+
+  const markOneReady = useCallback(() => {
+    if (pendingRef.current <= 0) return;
+    pendingRef.current -= 1;
+    if (pendingRef.current <= 0) setAssetsReady(true);
+  }, []);
+
+  useDomImageReady(slide0Ref, markOneReady);
+  useDomImageReady(slide1Ref, markOneReady);
+  useDomImageReady(slide2Ref, markOneReady);
+  useDomImageReady(logoRef, markOneReady);
+
   useEffect(() => {
+    if (!assetsReady) return;
+
     const interval = setInterval(() => {
       setProgress(prev => {
-        const next = prev + 1; 
+        const next = prev + 1;
         if (next >= 100) {
           clearInterval(interval);
           setTimeout(() => {
@@ -27,7 +91,7 @@ function A0base() {
     }, 50); // 每 50ms 增加 1%
 
     return () => clearInterval(interval);
-  }, []);
+  }, [assetsReady]);
 
   // 根据进度切换图片
   useEffect(() => {
@@ -45,6 +109,7 @@ function A0base() {
       {images.map((src, index) => (
         <img
           key={src}
+          ref={index === 0 ? slide0Ref : index === 1 ? slide1Ref : slide2Ref}
           src={src}
           alt={`slide ${index + 1}`}
           className={`${styles.slide} ${index === currentIndex ? styles.active : ''}`}
@@ -52,7 +117,7 @@ function A0base() {
       ))}
 
       <div className={styles.homeLogo}>
-        <img src={homeLogo} alt="homeLogo" />
+        <img ref={logoRef} src={homeLogo} alt="homeLogo" />
         <div className={'process'}>
           {progress}%
         </div>

+ 26 - 6
project/src/pages/A0baseM/index.tsx

@@ -7,18 +7,38 @@ import A0_home_3 from '@/assets/img/A0_home_3_M.jpg';
 import homeTitle from '@/assets/img/A0_home_title_M.png';
 import homeBtn from '@/assets/img/A0_home_btn_M.png';
 
+const PROGRESS_DELAY_MS = 2000;
+
 function A0baseM() {
   const [currentIndex, setCurrentIndex] = useState(0);
   const [progress, setProgress] = useState(0); // 进度 0 ~ 100
   const [isShowBtn, setIsShowBtn] = useState(false);
+  const [waitDone, setWaitDone] = useState(false);
   const images = useMemo(() => [A0_home_1, A0_home_2, A0_home_3], []);
 
   useEffect(() => {
-    const interval = setInterval(() => {
-      setProgress(prev => {
-        const next = prev + 1; 
+    images.forEach((src) => {
+      const img = new Image();
+      img.src = src;
+    });
+    const titlePreload = new Image();
+    titlePreload.src = homeTitle;
+    const btnPreload = new Image();
+    btnPreload.src = homeBtn;
+  }, [images]);
+
+  useEffect(() => {
+    const delayId = window.setTimeout(() => setWaitDone(true), PROGRESS_DELAY_MS);
+    return () => clearTimeout(delayId);
+  }, []);
+
+  useEffect(() => {
+    if (!waitDone) return;
+    const intervalId = setInterval(() => {
+      setProgress((prev) => {
+        const next = prev + 1;
         if (next >= 100) {
-          clearInterval(interval);
+          clearInterval(intervalId);
           setTimeout(() => {
             setIsShowBtn(true);
           }, 500);
@@ -28,8 +48,8 @@ function A0baseM() {
       });
     }, 50); // 每 50ms 增加 1%
 
-    return () => clearInterval(interval);
-  }, []);
+    return () => clearInterval(intervalId);
+  }, [waitDone]);
 
   // 根据进度切换图片
   useEffect(() => {

+ 1 - 1
project/src/pages/A1home/index.module.scss

@@ -2,7 +2,7 @@
   position: relative;
   background-color: #ccc;
 
-  .bgVideo {
+  .baseVideo {
     position: absolute;
     top: 0;
     left: 0;

+ 16 - 2
project/src/pages/A1home/index.tsx

@@ -29,6 +29,19 @@ function A1home() {
     }
   }, [])
 
+    // 初始视频
+    useEffect(() => {
+      const params = {
+        objectFit: 'cover', // 视频的object-fit样式, 默认 cover
+        loop: true, // 是否循环, 默认false
+        autoplay: true, // 自动播放, 默认false
+        onSourceEstablished: () => { } //有足够的数据可以播放了
+      }
+      const videoInit = F_Video('siji.ts', params)
+      const dom = document.querySelector('.baseVideo')!
+      dom.append(videoInit.domElement)
+    }, [])
+
   const handleItemClick = (item: any, index: number) => {
     setCurrentSceneIndex(index)
     callIframeFu('setCurrentScene', item.id)
@@ -124,13 +137,14 @@ function A1home() {
       ) : (
         <>
           {/* 背景视频 */}
-          <video
+          {/* <video
             src={`${baseOssUrl}myData/media/siji.mp4`}
             autoPlay
             loop
             muted
             className={styles.bgVideo}
-          />
+          /> */}
+          <div className="baseVideo"></div>
 
           {/* 跳过按钮 */}
           <div className={styles.skipBtn} onClick={() => setIsOpenPano(true)}>

+ 1 - 0
project/src/pages/A1homeM/PanoHot/index.module.scss

@@ -27,6 +27,7 @@
       .desc {
         width: 100%;
         height: 80%;
+        padding-bottom: 10px;
         font-size: 13px;
         line-height: 1.5;
         color: rgba(255, 243, 197, 1);

+ 23 - 0
project/src/pages/A5viewM/A5atlas/index.module.scss

@@ -0,0 +1,23 @@
+.A5atlas {
+  position: relative;
+  width: 100%;
+  height: 100%;
+  :global {
+    .closeBtn {
+      position: absolute;
+      top: 15px;
+      right: 15px;
+      width: 30px;
+      height: 30px;
+      cursor: pointer;
+      & > img {
+        width: 100% ;
+        height: 100% ;
+      }
+    }
+    iframe {
+      width: 100%;
+      height: 100%;
+    }
+  }
+}

+ 24 - 0
project/src/pages/A5viewM/A5atlas/index.tsx

@@ -0,0 +1,24 @@
+import React from 'react'
+import styles from './index.module.scss'
+import { infoPageIDGet } from '@/utils/locStore'
+import history from '@/utils/history'
+import { urlCanRes } from '@/utils/urlCan'
+function A5atlas() {
+  return (
+    <div className={styles.A5atlas}>
+      {/* 返回按钮 */}
+      <div className='closeBtn' onClick={() => {
+          const key = infoPageIDGet()
+          history.replace(`/view/${key}`)
+        }}>
+        <img src={require('../../../assets/img/close_M.png')} alt='' />
+      </div>
+
+      <iframe src={`./tuPu/index.html?id=${urlCanRes()}`} title='tuPu' frameBorder='0'></iframe>
+    </div>
+  )
+}
+
+const MemoA5atlas = React.memo(A5atlas)
+
+export default MemoA5atlas

+ 93 - 0
project/src/pages/A5viewM/PanoHot/index.module.scss

@@ -0,0 +1,93 @@
+    .ViewHot {
+    width: 100%;
+    height: 100%;
+    position: absolute;
+    top: 0;
+    left: 0;
+    z-index: 200;
+    background-color: rgba(70, 47, 9, 0.6);
+    backdrop-filter: blur(5px);
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    .content {
+      width: 100%;
+      height: 25%;
+      border-radius: 10px 10px 0 0;
+      background: linear-gradient(180deg, #A56F2C 0%, #824E0F 41.35%, #4F310C 100%);
+      padding: 20px 20px 10px;
+      .title {
+        width: 100%;
+        height: fit-content;
+        margin-bottom: 10px;
+        font-size: 18px;
+        font-weight: bold;
+        color: rgba(249, 211, 109, 1);
+      }
+      .desc {
+        width: 100%;
+        height: 80%;
+        padding-bottom: 10px;
+        font-size: 13px;
+        line-height: 1.5;
+        color: rgba(255, 243, 197, 1);
+        overflow: auto;
+        &::-webkit-scrollbar {
+          display: none;
+        }
+      }
+    }
+    .top {
+      width: 100%;
+      height: calc(75% - 100px);
+      padding: 0 10px;
+      .img {
+        width: 100%;
+        height: 100%;
+        
+      padding-top: 100px;
+        & > img {
+          width: 100%;
+          height: 100%;
+          object-fit: contain;
+        }
+      }
+    }
+    .bottom {
+      width: 100%;
+      height: 100px;
+      display: flex;
+      justify-content: center;
+      align-items: center;
+      gap: 0.5%;
+      position: relative;
+      
+      .tab {
+        width:25%;
+        height: 50px;
+        background-image: url('../../../assets/img/A5_view_tupuBtn.png');
+        background-size: contain;
+        background-repeat: no-repeat;
+        background-position: center center;
+        cursor: pointer;
+        display: flex;
+        justify-content: center;
+        align-items: center;
+        color: rgba(112, 73, 7, 1);
+        font-size: 14px;
+      }
+    }
+
+    .closeBtn {
+      position: absolute;
+      top: 15px;
+      right: 15px;
+      width: 30px;
+      height: 30px;
+      cursor: pointer;
+      & > img {
+        width: 100% ;
+        height: 100% ;
+      }
+    }
+}

+ 35 - 0
project/src/pages/A5viewM/PanoHot/index.tsx

@@ -0,0 +1,35 @@
+import React from 'react'
+import styles from './index.module.scss'
+import history from '@/utils/history'
+import { baseOssUrl } from '@/utils/http'
+function ViewHot({ activeIndex, closeFn }: { activeIndex: number; closeFn: () => void }) {
+  return (
+    <div className={styles.ViewHot}>
+      <div className={styles.top}>
+        <div className={styles.img}>
+          <img
+            src={`${baseOssUrl}modelSS/img/${cardNames.find(v => v.id === activeIndex)?.img}`}
+            alt=''
+          />
+        </div>
+      </div>
+      <div className={styles.bottom}>
+        <div
+          className={styles.tab}
+          onClick={() => history.replace(`/view/atlas?id=${activeIndex}`)}
+        ></div>
+      </div>
+      <div className={styles.closeBtn} onClick={closeFn}>
+        <img src={require('../../../assets/img/close_M.png')} alt='' />
+      </div>
+      <div className={styles.content}>
+        <div className={styles.title}>{cardNames.find(v => v.id === activeIndex)?.name}</div>
+        <div className={styles.desc}>{cardNames.find(v => v.id === activeIndex)?.text}</div>
+      </div>
+    </div>
+  )
+}
+
+const MemoViewHot = React.memo(ViewHot)
+
+export default MemoViewHot

+ 86 - 0
project/src/pages/A5viewM/index.module.scss

@@ -0,0 +1,86 @@
+.A5viewM{
+  width: 100%;
+  height: 100%;
+  background-image: url('../../assets/img/A5_view_bg_M.jpg');
+  background-size: 100% 100%;
+  background-repeat: no-repeat;
+  background-position: center center;
+  :global{
+    .top{
+      width: 100%;
+      height: 110px;
+      padding: 0 2%;
+      padding-bottom: 20px;
+      display: flex;
+      justify-content: flex-start;
+      align-items: flex-end;
+      gap: 2%;
+      .itemType{
+        width: 19%;
+        height: 30px;
+        border-radius: 20px;
+        border: 1px solid rgba(161, 154, 130, 1);
+        color: rgba(161, 154, 130, 1);
+        font-size: 13px;
+        line-height: 28px;
+        text-align: center;
+      }
+      .itemTypeAc {
+        background-color: rgba(249, 211, 109, 0.9);
+        color: rgba(112, 73, 7, 1);
+      }
+    }
+    .content{
+      width: 100%;
+      height: calc(100% - 110px);
+      padding: 0 2%;
+      display: flex;
+      flex-wrap: wrap;
+      gap: 2%;
+      align-content: flex-start;
+      justify-content: space-between;
+      overflow-y: auto;
+      scrollbar-gutter: stable;
+      scrollbar-color: rgba(255, 243, 197, 1) transparent;
+      &::-webkit-scrollbar {
+        width: 4px;
+      }
+      &::-webkit-scrollbar-track {
+        background: transparent;
+      }
+      &::-webkit-scrollbar-thumb {
+        background: rgba(255, 243, 197, 1);
+        border-radius: 3px;
+      }
+      .item{
+        width: 48%;
+        height: 130px;
+        background-image: url(../../assets/img/A5_view_list.png);
+        background-size: 100% 100%;
+        background-repeat: no-repeat;
+        background-position: center center;
+        .imgBox{
+          width: 100%;
+          height: 78%;
+          padding: 2%;
+          & > img {
+            width: 100%;
+            height: 100%;
+            object-fit: contain;
+          }
+        }
+        .name{
+          width: 100%;
+          height: 22%;
+          line-height: 2;
+          font-size: 13px;
+          padding-left: 4%;
+          color: rgba(255, 243, 197, 1);
+          overflow: hidden;
+          text-overflow: ellipsis;
+          white-space: nowrap;
+        }
+      }
+    }
+  }
+}

+ 57 - 0
project/src/pages/A5viewM/index.tsx

@@ -0,0 +1,57 @@
+import React, { useMemo, useState } from "react";
+import styles from "./index.module.scss";
+import TopBarM from "@/components/TopBarM";
+import classNames from "classnames";
+import { baseOssUrl } from "@/utils/http";
+import ViewHot from "./PanoHot";
+import A5atlas from "./A5atlas";
+import { useParams } from "react-router-dom";
+ function A5viewM () {
+  const { key } = useParams<{ key: any }>()
+  const typeArr = useMemo(() => {
+    const arr: string[] = ["全部"];
+    cardNames.forEach((v) => {
+      if (!arr.includes(v.type)) arr.push(v.type);
+    });
+    return arr;
+  }, []);
+
+  const [currentType, setCurrentType] = useState('全部');
+  const [activeIndex, setActiveIndex] = useState(-1);
+  const [isShowGraph, setIsShowGraph] = useState(false);
+
+  const list = useMemo(() => {
+    let arr = [...cardNames]
+    if (currentType !== '全部') arr = cardNames.filter(v => v.type === currentType)
+    return arr
+  }, [currentType])
+
+  return (
+    <div className={styles.A5viewM}>
+     { key !== 'atlas' ? <>
+      <TopBarM />
+      <div className="top">
+       {typeArr.map((item) => (
+        <div key={item} className={classNames("itemType", currentType === item ? "itemTypeAc" : "")} onClick={() => setCurrentType(item)}>
+          <div className="type">{item}</div>
+        </div>
+       ))}
+      </div>
+      <div className="content">
+      {list.map((item) => (
+        <div className="item" key={item.id} onClick={() => setActiveIndex(item.id)}>
+          <div className="imgBox">
+          <img src={`${baseOssUrl}modelSS/img/${item.id}.png`} alt="" /></div>
+          <div className="name">{item.name}</div>
+        </div>))}
+      </div>
+      {activeIndex !== -1 && <ViewHot activeIndex={activeIndex} closeFn={() => setActiveIndex(-1)} />}
+      </> : <A5atlas />}
+      
+    </div>
+  )
+}
+
+const MemoA5viewM = React.memo(A5viewM);
+
+export default MemoA5viewM;

+ 1 - 2
project/src/pages/A6life/Record/index.tsx

@@ -2,7 +2,6 @@ import React from 'react'
 import styles from './index.module.scss'
 import Zvideo from '@/components/Zvideo'
 import Zback from '@/components/Zback'
-import { baseOssUrl } from '@/utils/http'
 function Record({
   activeIndex,
   setActiveIndex
@@ -12,7 +11,7 @@ function Record({
 }) {
   return (
     <div className={styles.Record}>
-      <Zvideo src={baseOssUrl + myDataTemp.lifeList[activeIndex].videoSrc || ''} />
+      <Zvideo src={myDataTemp.lifeList[activeIndex].videoSrc || ''} />
       <Zback onBack={() => setActiveIndex(activeIndex - 1)} />
     </div>
   )

+ 142 - 0
project/src/pages/A6lifeM/index.module.scss

@@ -0,0 +1,142 @@
+.A6lifeM {
+  width: 100%;
+  height: 100%;
+  background-image: url('../../assets/img/A6_life_bg_M.jpg');
+  background-size: 100% 100%;
+  background-repeat: no-repeat;
+  background-position: center center;
+  display: flex;
+  align-items: center;
+  flex-direction: column;
+  .title {
+    width: 330px;
+    height: 70px;
+    margin-top: 60px;
+    margin-bottom: 20px;
+    background-image: url('../../assets/img/A6_life_intro_title.png');
+    background-size: 100% 100%;
+    background-repeat: no-repeat;
+    background-position: center center;
+    font-size: 18px;
+    line-height: 65px;
+    font-weight: bold;
+    color: rgba(255, 243, 197, 1);
+    text-align: center;
+  }
+  .imgBox {
+    width: 100%;
+    height: 30%;
+    padding: 0 10px;
+    border-radius: 5px;
+    & > img {
+      border-radius: 5px;
+      width: 100%;
+      height: 100%;
+      object-fit: contain;
+    }
+  }
+  .videoBox {
+    width: 100%;
+    height: 30%;
+    padding: 0 10px;
+    border-radius: 5px;
+    & > video {
+      border-radius: 5px;
+      width: 100%;
+      height: 100%;
+      object-fit: contain;
+    }
+  }
+  .txtBox {
+    width: 100%;
+    height: 35%;
+    padding: 0 10px;
+    padding-top: 20px;
+    padding-bottom: 20px;
+    margin-bottom: 10px;
+    font-size: 14px;
+    line-height: 24px;
+    color: rgba(78, 47, 23, 1);
+
+    position: relative;
+    .textTop {
+      height: 100%;
+      overflow-y: auto;
+      &::-webkit-scrollbar {
+        display: none;
+      }
+    }
+    .listBox {
+      position: absolute;
+      bottom: 15px;
+      left: 50%;
+      transform: translate(-50%, 0);
+      width: calc(100% - 20px);
+      height: 90%;
+      background-color: rgba(173, 142, 101, 0.8);
+      backdrop-filter: blur(5px);
+      border-radius: 10px;
+      padding: 10px 0;
+
+      transition: all 0.3s ease;
+
+      .itemBox {
+        width: 100%;
+        height: 100%;
+        overflow-y: auto;
+        &::-webkit-scrollbar {
+          display: none;
+        }
+        .item {
+          width: 100%;
+          height: 30px;
+          .name {
+            padding-left: 16px;
+            font-size: 14px;
+            line-height: 30px;
+            color: rgba(78, 47, 23, 1);
+          }
+        }
+        .itemAc {
+          background: linear-gradient(90deg, #704907 0%, rgba(112, 73, 7, 0.3) 100%);
+          .name {
+            color: rgba(249, 211, 109, 1);
+          }
+        }
+      }
+    }
+    .listHide {
+      height: 0;
+      padding: 0;
+    }
+  }
+  .selectBox {
+    width: 80%;
+    height: 40px;
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    background-color: rgba(173, 142, 101, 0.3);
+    border-radius: 20px;
+    padding: 0 16px;
+    border: 1px solid rgba(112, 73, 7, 1);
+    .name {
+      font-size: 14px;
+      line-height: 40px;
+      color: rgba(112, 73, 7, 1);
+    }
+    .arrow {
+      width: 20px;
+      height: 10px;
+      background-image: url('../../assets/img/arrow_down.png');
+      background-size: contain;
+      background-repeat: no-repeat;
+      background-position: center center;
+      cursor: pointer;
+      transition: all 0.3s ease;
+    }
+    .arrowAc {
+      transform: rotate(180deg);
+    }
+  }
+}

+ 55 - 0
project/src/pages/A6lifeM/index.tsx

@@ -0,0 +1,55 @@
+import React, { useState } from 'react'
+import styles from './index.module.scss'
+import TopBarM from '@/components/TopBarM'
+import { baseOssUrl } from '@/utils/http'
+import classNames from 'classnames'
+function A6lifeM() {
+  const [activeIndex, setActiveIndex] = useState(0)
+  const [isShowlist, setIsShowlist] = useState(false)
+  return (
+    <div className={styles.A6lifeM}>
+      <TopBarM />
+      <div className={styles.title}>{myDataTemp.lifeList[activeIndex].name}</div>
+      {activeIndex !== myDataTemp.lifeList.length - 1 ? (
+        <div className={styles.imgBox}>
+          <img src={baseOssUrl + myDataTemp.lifeList[activeIndex].imgSrc} alt='' />
+        </div>
+      ) : (
+        <div className={styles.videoBox}>
+          <video src={baseOssUrl + myDataTemp.lifeList[activeIndex].videoSrc} controls />
+        </div>
+      )}
+      <div className={styles.txtBox}>
+        <div className={styles.textTop}>{myDataTemp.lifeList[activeIndex].desc}</div>
+        <div className={classNames(styles.listBox, isShowlist ? '' : styles.listHide)}>
+          <div className={styles.itemBox}>
+            {myDataTemp.lifeList.map((item, index) => (
+              <div
+                className={classNames(styles.item, activeIndex === index ? styles.itemAc : '')}
+                key={index}
+                onClick={() => {
+                  setActiveIndex(index)
+                  setIsShowlist(false)
+                }}
+              >
+                <div className={styles.name}>
+                  {item.time}&emsp;{item.name}
+                </div>
+              </div>
+            ))}
+          </div>
+        </div>
+      </div>
+      <div className={styles.selectBox} onClick={() => setIsShowlist(!isShowlist)}>
+        <div className={styles.name}>
+          {myDataTemp.lifeList[activeIndex].time}&emsp;{myDataTemp.lifeList[activeIndex].name}
+        </div>
+        <div className={classNames(styles.arrow, isShowlist ? styles.arrowAc : '')}></div>
+      </div>
+    </div>
+  )
+}
+
+const MemoA6lifeM = React.memo(A6lifeM)
+
+export default MemoA6lifeM

+ 10 - 0
project/src/pages/A7animationM/index.module.scss

@@ -36,6 +36,16 @@
       color: rgba(255, 243, 197, 1);
       text-align: center;
     }
+    .video{
+      width: 100%;
+      height: auto;
+      max-height: 56%;
+      & > video {
+        width: 100%;
+        height: 100%;
+        object-fit: contain;
+      }
+    }
   }
   
 }

+ 4 - 5
project/src/pages/A7animationM/index.tsx

@@ -3,6 +3,7 @@ import styles from './index.module.scss'
 import Swiper3 from './Swiper3'
 import TopBarM from '@/components/TopBarM'
 import Zvideo from '@/components/Zvideo'
+import { baseOssUrl } from '@/utils/http'
 function A7animationM() {
   const [isShowVideo, setIsShowVideo] = useState(false)
   const [currentIndex, setCurrentIndex] = useState(0)
@@ -25,11 +26,9 @@ function A7animationM() {
           </div>
          
           <div className={styles.title}>{myDataTemp.animationList[currentIndex].name}</div>
-          <Zvideo
-            src={myDataTemp.animationList[currentIndex].videoSrc}
-            objFit='contain'
-            style={{ height: 'auto' }}
-          />
+          <div className={styles.video}>
+            <video src={baseOssUrl + myDataTemp.animationList[currentIndex].videoSrc} autoPlay loop controls/>
+          </div>
         </div>
       )}
     </div>