Przeglądaj źródła

style: 兼容央视

Co-authored-by: Copilot <copilot@github.com>
chenlei 4 miesięcy temu
rodzic
commit
d02487adb2

Plik diff jest za duży
+ 2236 - 1071
code/package-lock.json


+ 2 - 1
code/package.json

@@ -35,6 +35,7 @@
     "sass": "^1.55.0",
     "three": "^0.180.0",
     "typescript": "^4.8.4",
+    "video.js": "^8.23.7",
     "web-vitals": "^2.1.4"
   },
   "scripts": {
@@ -68,4 +69,4 @@
     "react-app-rewired": "^2.2.1"
   },
   "homepage": "."
-}
+}

+ 1 - 0
code/src/pages/A1home/index.module.scss

@@ -5,6 +5,7 @@
       width: 100%;
       height: 100%;
       background-size: 100% 100%;
+      overflow: hidden;
     }
 
     .A1tit2 {

+ 5 - 0
code/src/pages/A2show/A2info/index.module.scss

@@ -5,6 +5,7 @@
   width: 100%;
   height: 100%;
   z-index: 100;
+  transform: translateZ(1px); /* 创建新层叠上下文并提升层级 */
   background-image: linear-gradient(#1a0202, #2d0c03);
   :global {
     .A2Iback {
@@ -14,6 +15,10 @@
       z-index: 10;
       height: 34px;
       width: auto;
+
+      &.A2IbackGap {
+        left: 54px;
+      }
     }
 
     // 顶部

+ 7 - 1
code/src/pages/A2show/A2info/index.tsx

@@ -3,6 +3,8 @@ import styles from './index.module.scss'
 import { A2RrBtnType, myInfo1, MyInfo1Type } from '../data'
 import classNames from 'classnames'
 import { Swiper, Image, ImageViewer } from 'antd-mobile'
+import { RootState } from '@/store'
+import { useSelector } from 'react-redux'
 
 type Props = {
   acKey: A2RrBtnType
@@ -11,6 +13,7 @@ type Props = {
 
 function A2info({ acKey, closeFu }: Props) {
   const [tabAc, setTabAc] = useState('' as A2RrBtnType)
+  const hasBackIcon = useSelector((state: RootState) => state.A0Layout.hasBackIcon)
 
   useEffect(() => {
     setTabAc(acKey)
@@ -56,7 +59,10 @@ function A2info({ acKey, closeFu }: Props) {
       {/* 返回按钮 */}
       <img
         onClick={closeFu}
-        className='A2Iback'
+        className={classNames(
+          'A2Iback',
+          hasBackIcon ? 'A2IbackGap' : ''
+        )}
         src={`${serverUrl}model/back.png`}
         alt=''
       />

+ 60 - 0
code/src/pages/A4scene/VideoJsPlayer.tsx

@@ -0,0 +1,60 @@
+import React, { useEffect, useRef } from 'react'
+import videojs from 'video.js'
+import 'video.js/dist/video-js.css'
+import type Player from 'video.js/dist/types/player'
+
+type Props = {
+  /** 完整播放地址(含域名) */
+  src: string
+}
+
+function VideoJsPlayer({ src }: Props) {
+  const videoRef = useRef<HTMLVideoElement>(null)
+  const playerRef = useRef<Player | null>(null)
+
+  useEffect(() => {
+    if (!videoRef.current) return
+
+    const player = videojs(videoRef.current, {
+      controls: true,
+      autoplay: 'any',
+      playsinline: true,
+      preload: 'auto',
+      disablePictureInPicture: true,
+      sources: [{ src, type: 'video/mp4' }],
+      controlBar: {
+        pictureInPictureToggle: false,
+        fullscreenToggle: false,
+      },
+    })
+
+    player.ready(() => {
+      const el = player.el()?.querySelector('video')
+      el?.setAttribute(
+        'controlsList',
+        'nodownload nofullscreen noremoteplayback noplaybackrate',
+      )
+    })
+
+    playerRef.current = player
+
+    return () => {
+      if (playerRef.current) {
+        playerRef.current.dispose()
+        playerRef.current = null
+      }
+    }
+  }, [src])
+
+  return (
+    <div data-vjs-player style={{ width: '100%', height: '100%' }}>
+      <video
+        ref={videoRef}
+        className='video-js vjs-big-play-centered'
+        playsInline
+      />
+    </div>
+  )
+}
+
+export default React.memo(VideoJsPlayer)

+ 26 - 9
code/src/pages/A4scene/index.module.scss

@@ -10,6 +10,11 @@
     }
   }
   :global {
+    .video-js .vjs-control-bar {
+      left: 100px;
+      right: 100px;
+      width: unset;
+    }
     .A4sceneCover {
       position: relative;
       margin-top: -64px;
@@ -108,20 +113,32 @@
   overflow: hidden;
 
   :global {
-    video {
-      width: 100vh;
-      height: 100vw;
-      object-fit: contain;
-      transform: rotate(90deg);
-      transform-origin: center center;
-      object-fit: cover;
-    }
     img {
       position: absolute;
       right: 20px;
       bottom: 40px;
-      width: 403x;
+      width: 40px;
       height: 40px;
+      z-index: 2;
+      cursor: pointer;
     }
   }
+}
+
+/** 整块横屏旋转:播放器 UI 与画布一起转正,对齐竖屏容器中“横屏观感”的布局 */
+.A4sceneVideoRotate {
+  flex-shrink: 0;
+  width: 100vh;
+  height: 100vw;
+  transform: rotate(90deg);
+  transform-origin: center center;
+
+  :global(.video-js) {
+    width: 100%;
+    height: 100%;
+  }
+
+  :global(.video-js .vjs-tech) {
+    object-fit: cover;
+  }
 }

+ 4 - 6
code/src/pages/A4scene/index.tsx

@@ -1,5 +1,6 @@
 import React, { useMemo, useState } from 'react'
 import styles from './index.module.scss'
+import VideoJsPlayer from './VideoJsPlayer'
 import Z1titie from '@/components/Z1titie'
 import history from '@/utils/history'
 import { Mask } from 'antd-mobile'
@@ -50,12 +51,9 @@ function A4scene() {
         destroyOnClose
         onMaskClick={() => setVisible(false)}
       >
-        <video 
-          src={serverUrl + detail.videoPath} 
-          controls 
-          autoPlay 
-          playsInline
-        />
+        <div className={styles.A4sceneVideoRotate}>
+          <VideoJsPlayer key={detail.videoPath} src={serverUrl + detail.videoPath} />
+        </div>
 
         <img src={closeIcon} alt='close' onClick={() => setVisible(false)} />
       </Mask>

+ 61 - 2
code/src/pages/A5ip/detail.tsx

@@ -1,11 +1,57 @@
 import Z1titie from '@/components/Z1titie'
-import React from 'react'
+import React, { useRef, useState } from 'react'
+import { ActionSheet } from 'antd-mobile'
 import styles from './index.module.scss'
 import history from '@/utils/history'
 import cardImg from './images/card.png'
 import closeIcon from './images/guanbi.png'
 
+// 保存图片函数
+const saveImage = (imgSrc: string, fileName: string = 'card.png') => {
+  const link = document.createElement('a')
+  link.href = imgSrc
+  link.download = fileName
+  document.body.appendChild(link)
+  link.click()
+  document.body.removeChild(link)
+}
+
 function A5ipDetail() {
+  const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null)
+  const [visible, setVisible] = useState(false)
+
+  const actions = [
+    {
+      key: 'save',
+      text: '保存图片',
+      onClick: () => {
+        saveImage(cardImg, '文创产品.png')
+        setVisible(false)
+      },
+    },
+  ]
+
+  // 桌面右键保存
+  const handleContextMenu = (e: React.MouseEvent<HTMLImageElement>) => {
+    e.preventDefault()
+    saveImage(cardImg, '文创产品.png')
+  }
+
+  // 移动端长按开始
+  const handleTouchStart = () => {
+    timerRef.current = setTimeout(() => {
+      setVisible(true)
+    }, 500)
+  }
+
+  // 移动端长按取消
+  const handleTouchEnd = () => {
+    if (timerRef.current) {
+      clearTimeout(timerRef.current)
+      timerRef.current = null
+    }
+  }
+
   return (
     <div
       style={{ backgroundImage: `url(${serverUrl}ip/bac.jpg)` }}
@@ -16,9 +62,22 @@ function A5ipDetail() {
         toHome={() => history.push('/?mask=1')}
       />
 
-      <img className='A5ipDetailCard' src={cardImg} alt='card' />
+      <img 
+        className='A5ipDetailCard' 
+        src={cardImg} 
+        alt='card'
+        onContextMenu={handleContextMenu}
+        onTouchStart={handleTouchStart}
+        onTouchEnd={handleTouchEnd}
+      />
 
       <img className='A5ipDetailClose' src={closeIcon} alt='close' onClick={() => history.replace('/ip')} />
+
+      <ActionSheet
+        visible={visible}
+        actions={actions}
+        onClose={() => setVisible(false)}
+      />
     </div>
   )
 }

Plik diff jest za duży
+ 722 - 572
code/yarn.lock