shaogen1995 před 1 týdnem
rodič
revize
b5455fcdf6

+ 1 - 1
code/public/myData/data.js

@@ -1,2 +1,2 @@
-// const serverUrlTemp = 'http://192.168.20.55:8080/staticData/'
+// const serverUrl = 'https://houseoss.4dkankan.com/project/chuGuoYuJian/staticData/'
 const serverUrl = 'http://192.168.20.55:8080/staticData/'

+ 2 - 8
code/src/App.tsx

@@ -11,7 +11,7 @@ import MessageCom from './components/Message'
 import screenImg from '@/assets/img/landtip.png'
 import A0model from './pages/A0model'
 
-const A1home = React.lazy(() => import('./pages/A1home'))
+const A0base = React.lazy(() => import('./pages/A0base'))
 
 export default function App() {
   // 从仓库中获取查看图片的信息
@@ -32,14 +32,8 @@ export default function App() {
   const router = createBrowserRouter([
     {
       path: '/',
-      element: <A1home />
-      // loader: homeLoaderFunction,
+      element: <A0base />
     }
-    // {
-    //   path: '/codeSucc/:id',
-    //   element: <A3codeSucc />,
-    //   // loader: codeSuccLoaderFunction, // 同样可以配置数据加载器
-    // },
   ])
 
   return (

+ 21 - 0
code/src/components/Z1titie/index.module.scss

@@ -0,0 +1,21 @@
+.Z1titie {
+  padding-top: 30px;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+
+  :global {
+    img {
+      width: auto;
+      height: 34px;
+    }
+    div {
+      margin: 0 10px;
+      font-size: 22px;
+      background-size: 130% 130%;
+      background-position: center center;
+      color: var(--themeColor);
+      letter-spacing: 2px;
+    }
+  }
+}

+ 20 - 0
code/src/components/Z1titie/index.tsx

@@ -0,0 +1,20 @@
+import React from 'react'
+import styles from './index.module.scss'
+
+type Props = {
+  title: string
+}
+
+function Z1titie({ title }: Props) {
+  return (
+    <div className={styles.Z1titie}>
+      <img className='Z1_1' src={`${serverUrl}home/titlel.png`} alt='' />
+      <div style={{ backgroundImage: `url(${serverUrl}home/titleBac.png)` }}>{title}</div>
+      <img className='Z1_2' src={`${serverUrl}home/titler.png`} alt='' />
+    </div>
+  )
+}
+
+const MemoZ1titie = React.memo(Z1titie)
+
+export default MemoZ1titie

+ 1 - 1
code/src/index.tsx

@@ -22,7 +22,7 @@ root.render(
     locale={locale}
     theme={{
       token: {
-        colorPrimary: '#FCE9AC'
+        colorPrimary: '#f5cd4bff'
       }
     }}
   >

+ 21 - 0
code/src/pages/A0base.tsx

@@ -0,0 +1,21 @@
+import { RootState } from '@/store'
+import React from 'react'
+import { useSelector } from 'react-redux'
+import A1home from './A1home'
+import A2show from './A2show'
+import A3wear from './A3wear'
+function A0base() {
+  const pageKey = useSelector((state: RootState) => state.A0model.pageKey)
+
+  return (
+    <>
+      {pageKey === 'home' ? <A1home /> : null}
+      {pageKey === 'show' ? <A2show /> : null}
+      {pageKey === 'wear' ? <A3wear /> : null}
+    </>
+  )
+}
+
+const MemoA0base = React.memo(A0base)
+
+export default MemoA0base

+ 11 - 7
code/src/pages/A0model/A0btn/index.tsx

@@ -1,21 +1,25 @@
 import React from 'react'
 import styles from './index.module.scss'
-function A0btn() {
+
+type Props = {
+  cutModelFu: (val: string) => void
+}
+
+function A0btn({ cutModelFu }: Props) {
   return (
     <div className={styles.A0btn}>
-      4546
-      {/* <button onClick={() => focusOnPart('bs1')} style={{ padding: '10px' }}>
+      <button onClick={() => cutModelFu('yu_01')} style={{ padding: '10px' }}>
         显示 BS1
       </button>
-      <button onClick={() => focusOnPart('bs2')} style={{ padding: '10px' }}>
+      <button onClick={() => cutModelFu('jian_02')} style={{ padding: '10px' }}>
         显示 BS2
       </button>
-      <button onClick={() => focusOnPart('bs3')} style={{ padding: '10px' }}>
+      <button onClick={() => cutModelFu('yu_03')} style={{ padding: '10px' }}>
         显示 BS3
       </button>
-      <button onClick={showAllModels} style={{ padding: '10px' }}>
+      <button onClick={() => cutModelFu('')} style={{ padding: '10px' }}>
         显示全部
-      </button> */}
+      </button>
     </div>
   )
 }

+ 107 - 60
code/src/pages/A0model/Model.tsx

@@ -1,16 +1,73 @@
-import { useThree } from '@react-three/fiber'
+import { useFrame, useThree } from '@react-three/fiber'
 import React, { useCallback, useEffect, useRef, useState } from 'react'
 import * as THREE from 'three'
 import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader'
 import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'
+import { forwardRef, useImperativeHandle } from 'react'
+import { OrbitControls } from '@react-three/drei'
+import store from '@/store'
+import { envFlag } from '@/utils/http'
+
+function Model(_: any, ref: any) {
+  // 轨道控制器
+  const controlsRef = useRef<any>(null)
+  useFrame(() => {
+    if (controlsRef.current) {
+      controlsRef.current.update() // 启用阻尼效果必须每帧更新控制器
+    }
+  })
+  // 轨道控制器
 
-function Model() {
   const groupRef = useRef<THREE.Group>(null)
-  const [loadingProgress, setLoadingProgress] = useState(0)
+  const { camera, scene } = useThree()
+
+  // 移动模型到中心位置
+  const moveCentenFu = useCallback(
+    (partName: string, selectedPart: any) => {
+      // 计算选中部分的包围盒
+      const bbox = new THREE.Box3().setFromObject(selectedPart)
+
+      // 检查包围盒是否有效
+      if (bbox.isEmpty()) {
+        console.warn(`Selected part "${partName}" has an empty bounding box.`)
+        return
+      }
+
+      // 获取包围盒的中心点
+      const center = new THREE.Vector3()
+      bbox.getCenter(center)
+
+      // 获取包围盒的尺寸(可用于计算相机距离)
+      const size = new THREE.Vector3()
+      bbox.getSize(size)
+
+      // 计算相机应该移动到的位置(可以根据需要调整偏移量)
+      const maxDim = Math.max(size.x, size.y, size.z)
+      const distance = maxDim * 1.5 // 根据模型大小计算相机距离
+
+      // 设置相机位置并看向中心点
+      camera.position.copy(center.clone().add(new THREE.Vector3(0, distance, 0)))
+      camera.lookAt(center)
+
+      // 如果有轨道控制器,更新其目标点
+
+      if (controlsRef.current) {
+        controlsRef.current.target.copy(center)
+        controlsRef.current.update()
+      }
+    },
+    [camera]
+  )
+
   const [model, setModel] = useState<THREE.Group | null>(null)
+
+  useEffect(() => {
+    if (model) {
+      moveCentenFu('all', model)
+    }
+  }, [model, moveCentenFu])
+
   const [modelParts, setModelParts] = useState<{ [name: string]: THREE.Object3D }>({})
-  const [activePart, setActivePart] = useState<string | null>(null)
-  const { camera, scene } = useThree()
 
   const handleModelLoaded = useCallback(
     (loadedModel: THREE.Group, parts: { [name: string]: THREE.Object3D }) => {
@@ -25,12 +82,10 @@ function Model() {
     [scene]
   )
 
+  // 存储加载过程中遇到的最大 itemsTotal
+
   useEffect(() => {
     const manager = new THREE.LoadingManager()
-    manager.onProgress = (url, itemsLoaded, itemsTotal) => {
-      setLoadingProgress((itemsLoaded / itemsTotal) * 100)
-    }
-
     const dracoLoader = new DRACOLoader()
     dracoLoader.setDecoderPath('https://www.gstatic.com/draco/v1/decoders/')
     dracoLoader.setDecoderConfig({ type: 'js' })
@@ -39,19 +94,13 @@ function Model() {
     loader.setDRACOLoader(dracoLoader)
 
     loader.load(
-      `${serverUrl}/0/model.glb`, // 替换为你的GLB模型路径
+      envFlag
+        ? 'https://houseoss.4dkankan.com/project/chuGuoYuJian/staticData/0/model.glb'
+        : `${serverUrl}model/model.glb`, // 替换为你的GLB模型路径
       gltf => {
         const model = gltf.scene
-
-        // 设置模型初始位置 (x, y, z)
-        model.position.set(0, 0, 0) // 设置为场景原点
-
         // // 设置模型初始缩放 (x, y, z)
         model.scale.set(0.003, 0.003, 0.003) // 原始大小设
-
-        // // 设置模型初始旋转(单位:弧度)(x, y, z)
-        model.rotation.set(0, 0, 0) // 例如,不旋转。绕Y轴旋转90度可设为(0, Math.PI / 2, 0)
-
         model.traverse((child: any) => {
           if (child.isMesh) {
             child.castShadow = true
@@ -59,10 +108,10 @@ function Model() {
           }
         })
 
-        // 3. 假设模型中的三个部分分别命名为'bs1', 'bs2', 'bs3'
+        // 3. 假设模型中的三个部分分别命名为'yu_01', 'jian_02', 'yu_03'
         const parts: { [name: string]: THREE.Object3D } = {}
         model.children.forEach(child => {
-          if (['bs1', 'bs2', 'bs3'].includes(child.name)) {
+          if (['yu_01', 'jian_02', 'yu_03'].includes(child.name)) {
             parts[child.name] = child
           }
         })
@@ -71,7 +120,12 @@ function Model() {
         handleModelLoaded(model, parts)
       },
       xhr => {
-        // 加载进度回调已在LoadingManager中处理
+        if (xhr.lengthComputable) {
+          const percentComplete = (xhr.loaded / xhr.total) * 100
+          const baiNum = percentComplete.toFixed(0)
+
+          store.dispatch({ type: 'A1/plan', payload: Number(baiNum) })
+        }
       },
       error => {
         console.error('加载模型出错:', error)
@@ -89,25 +143,13 @@ function Model() {
         part.visible = name === partName
       })
 
-      // 获取选中部分的包围盒并居中相机
-      const bbox = new THREE.Box3().setFromObject(modelParts[partName])
-      const center = bbox.getCenter(new THREE.Vector3())
-      const size = bbox.getSize(new THREE.Vector3())
-
-      // 计算相机位置,使其能够完整看到模型
-      const maxDim = Math.max(size.x, size.y, size.z)
-      const fov =
-        camera instanceof THREE.PerspectiveCamera ? (camera.fov * Math.PI) / 180 : 0
-      const cameraDistance = maxDim / (2 * Math.tan(fov / 2))
-
-      camera.position.copy(
-        center.clone().add(new THREE.Vector3(0, 0, cameraDistance * 1.5))
-      )
-      camera.lookAt(center)
+      // 获取选中部分的引用
+      const selectedPart = modelParts[partName]
 
-      setActivePart(partName)
+      // 移动到中心点
+      moveCentenFu(partName, selectedPart)
     },
-    [camera, model, modelParts]
+    [model, modelParts, moveCentenFu]
   )
 
   // 显示全部模型
@@ -119,27 +161,32 @@ function Model() {
       part.visible = true
     })
 
-    // 将相机定位到整个模型的中心
-    const bbox = new THREE.Box3().setFromObject(model)
-    const center = bbox.getCenter(new THREE.Vector3())
-    const size = bbox.getSize(new THREE.Vector3())
-
-    const maxDim = Math.max(size.x, size.y, size.z)
-    const fov =
-      camera instanceof THREE.PerspectiveCamera ? (camera.fov * Math.PI) / 180 : 0
-    const cameraDistance = maxDim / (2 * Math.tan(fov / 2))
-
-    camera.position.copy(
-      center.clone().add(new THREE.Vector3(0, 0, cameraDistance * 1.5))
-    )
-    camera.lookAt(center)
-
-    setActivePart(null)
-  }, [camera, model, modelParts])
-
-  return <>{groupRef.current ? <primitive object={groupRef.current} /> : null}</>
+    // 移动到中心点
+    moveCentenFu('all', model)
+  }, [model, modelParts, moveCentenFu])
+
+  // 可以让父组件调用子组件的方法
+  useImperativeHandle(ref, () => ({
+    focusOnPart,
+    showAllModels
+  }))
+
+  return (
+    <>
+      <ambientLight intensity={3} />
+      <directionalLight position={[10, 10, 10]} intensity={2} castShadow />
+      <OrbitControls
+        ref={controlsRef}
+        enableDamping={true} // 启用阻尼效果
+        dampingFactor={0.05} // 阻尼系数[7](@ref)
+        maxDistance={2} // 根据初始缩放比例调整
+        minDistance={0.3} // 根据初始缩放比例调整[9](@ref)
+        screenSpacePanning={true} // 移动端友好的平移方式
+      />
+
+      {groupRef.current ? <primitive object={groupRef.current} /> : null}
+    </>
+  )
 }
 
-const MemoModel = React.memo(Model)
-
-export default MemoModel
+export default forwardRef(Model)

+ 0 - 30
code/src/pages/A0model/SceneSetup.tsx

@@ -1,30 +0,0 @@
-import React, { useRef } from 'react'
-import { useFrame } from '@react-three/fiber'
-import { OrbitControls } from '@react-three/drei'
-function SceneSetup() {
-  const controlsRef = useRef<any>(null)
-  useFrame(() => {
-    if (controlsRef.current) {
-      controlsRef.current.update() // 启用阻尼效果必须每帧更新控制器
-    }
-  })
-
-  return (
-    <>
-      <ambientLight intensity={4} />
-      <directionalLight position={[10, 10, 10]} intensity={2} castShadow />
-      <OrbitControls
-        ref={controlsRef}
-        enableDamping={true} // 启用阻尼效果
-        dampingFactor={0.05} // 阻尼系数[7](@ref)
-        maxDistance={2} // 根据初始缩放比例调整
-        minDistance={0.5} // 根据初始缩放比例调整[9](@ref)
-        screenSpacePanning={true} // 移动端友好的平移方式
-      />
-    </>
-  )
-}
-
-const MemoSceneSetup = React.memo(SceneSetup)
-
-export default MemoSceneSetup

+ 27 - 0
code/src/pages/A0model/index.module.scss

@@ -9,11 +9,38 @@
   height: 100%;
   z-index: 99;
   background-size: 100% 100%;
+
   :global {
+    .A0main {
+      width: 100%;
+      height: calc(100% - 100px);
+      & > div {
+        pointer-events: none !important;
+        & > div {
+          pointer-events: none !important;
+          canvas {
+            pointer-events: none !important;
+          }
+        }
+      }
+    }
   }
 }
 
 .A0modelShow {
   opacity: 1;
   pointer-events: auto;
+  :global {
+    .A0main {
+      & > div {
+        pointer-events: auto !important;
+        & > div {
+          pointer-events: auto !important;
+          canvas {
+            pointer-events: auto !important;
+          }
+        }
+      }
+    }
+  }
 }

+ 29 - 21
code/src/pages/A0model/index.tsx

@@ -1,41 +1,49 @@
-import React, { Suspense } from 'react'
+import React, { Suspense, useCallback, useRef } from 'react'
 import styles from './index.module.scss'
 import { useSelector } from 'react-redux'
 import { RootState } from '@/store'
 import classNames from 'classnames'
 // import * as THREE from 'three'
 import { Canvas } from '@react-three/fiber'
-import SceneSetup from './SceneSetup'
 import Model from './Model'
 import A0btn from './A0btn'
 
 function A0model() {
   const { isShow } = useSelector((state: RootState) => state.A0model)
 
+  const modelRef = useRef<any>(null)
+
+  const cutModelFu = useCallback((val: string) => {
+    if (val) {
+      modelRef.current.focusOnPart(val)
+    } else modelRef.current.showAllModels()
+  }, [])
+
   return (
     <div
-      style={{ backgroundImage: `url(${serverUrl}0/0_bac.jpg)` }}
+      style={{ backgroundImage: `url(${serverUrl}model/bac.jpg)` }}
       className={classNames(styles.A0model, isShow ? styles.A0modelShow : '')}
     >
-      <Canvas
-        shadows
-        camera={{ position: [0, 1000, 5], fov: 50 }}
-        gl={{
-          antialias: true, // 开启抗锯齿,使边缘更平滑
-          alpha: true // 允许 Canvas 背景透明
-        }}
-        // onCreated={({ gl }) => {
-        //   gl.setClearColor(new THREE.Color(0x000000))
-        // }}
-      >
-        <Suspense>
-          {/* 场景与控制器 */}
-          <SceneSetup />
-          <Model />
-        </Suspense>
-      </Canvas>
+      <div className='A0main'>
+        <Canvas
+          shadows
+          camera={{ position: [0, 1000, 5], fov: 50 }}
+          gl={{
+            antialias: true, // 开启抗锯齿,使边缘更平滑
+            alpha: true // 允许 Canvas 背景透明
+          }}
+          // onCreated={({ gl }) => {
+          //   gl.setClearColor(new THREE.Color(0x000000))
+          // }}
+        >
+          <Suspense>
+            {/* 场景与控制器 */}
+            <Model ref={modelRef} />
+          </Suspense>
+        </Canvas>
+      </div>
 
-      <A0btn />
+      <A0btn cutModelFu={val => cutModelFu(val)} />
     </div>
   )
 }

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

@@ -1,4 +1,75 @@
 .A1home {
+  color: var(--themeColor);
   :global {
+    .A1base {
+      position: absolute;
+      top: 0;
+      left: 0;
+      width: 100%;
+      height: 100%;
+      z-index: 10;
+      background-size: 100% 100%;
+      padding: 10% 0;
+      display: flex;
+      justify-content: center;
+      transition: all 1s;
+      opacity: 1;
+      pointer-events: auto;
+
+      & > div {
+        position: absolute;
+        top: 48%;
+        left: 42%;
+        width: 16%;
+        height: 34%;
+        font-size: 24px;
+        display: flex;
+        flex-direction: column;
+        justify-content: center;
+        align-items: center;
+        padding-bottom: 5%;
+        & > p {
+          margin-bottom: 20%;
+          &:last-child {
+            font-size: 18px;
+          }
+        }
+      }
+    }
+    .A1baseHide {
+      opacity: 0;
+      pointer-events: none;
+    }
+
+    .A1start {
+      width: 100%;
+      height: 100%;
+      background-size: 100% 100%;
+    }
+
+    .A1tit2 {
+      margin-top: 8px;
+      text-align: center;
+      letter-spacing: 10px;
+      font-size: 18px;
+    }
+
+    .A1jian {
+      display: block;
+      margin: 40px auto;
+      height: calc(100% - 276px);
+    }
+
+    .A1btn {
+      margin: 0 auto;
+      width: 280px;
+      height: 54px;
+      background-size: 100% 100%;
+      line-height: 54px;
+      text-align: center;
+      font-size: 22px;
+      letter-spacing: 6px;
+      color: #faf6d3;
+    }
   }
 }

+ 40 - 1
code/src/pages/A1home/index.tsx

@@ -1,9 +1,48 @@
 import React from 'react'
 import styles from './index.module.scss'
+import { useSelector } from 'react-redux'
+import store, { RootState } from '@/store'
+import classNames from 'classnames'
+import Z1titie from '@/components/Z1titie'
+
 function A1home() {
+  const { plan } = useSelector((state: RootState) => state.A0model)
+
   return (
     <div className={styles.A1home}>
-      <h1>A1home</h1>
+      {/* 加载中 */}
+      <div
+        className={classNames('A1base', plan >= 100 ? 'A1baseHide' : '')}
+        style={{ backgroundImage: `url(${serverUrl}home/bac.jpg)` }}
+      >
+        <img src={`${serverUrl}home/bs.png`} alt='' />
+        <div>
+          <p>加</p>
+          <p>载</p>
+          <p>中</p>
+          <p>{plan}%</p>
+        </div>
+      </div>
+
+      {/* 点击开始 */}
+      <div
+        className='A1start'
+        style={{ backgroundImage: `url(${serverUrl}home/start.jpg)` }}
+      >
+        {/* 顶部标题 */}
+        <Z1titie title='楚风剑韵·玉柄流光' />
+        <div className='A1tit2'>楚国第一玉剑数字展</div>
+
+        <img className='A1jian' src={`${serverUrl}home/jian.png`} alt='' />
+
+        <div
+          onClick={() => store.dispatch({ type: 'A1/pageKey', payload: 'show' })}
+          style={{ backgroundImage: `url(${serverUrl}home/btnBac.png)` }}
+          className='A1btn'
+        >
+          点击开始
+        </div>
+      </div>
     </div>
   )
 }

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

@@ -0,0 +1,4 @@
+.A2show {
+  :global {
+  }
+}

+ 13 - 0
code/src/pages/A2show/index.tsx

@@ -0,0 +1,13 @@
+import React from 'react'
+import styles from './index.module.scss'
+function A2show() {
+  return (
+    <div className={styles.A2show}>
+      <h1>A2show</h1>
+    </div>
+  )
+}
+
+const MemoA2show = React.memo(A2show)
+
+export default MemoA2show

+ 4 - 0
code/src/pages/A3wear/index.module.scss

@@ -0,0 +1,4 @@
+.A3wear {
+  :global {
+  }
+}

+ 13 - 0
code/src/pages/A3wear/index.tsx

@@ -0,0 +1,13 @@
+import React from 'react'
+import styles from './index.module.scss'
+function A3wear() {
+  return (
+    <div className={styles.A3wear}>
+      <h1>A3wear</h1>
+    </div>
+  )
+}
+
+const MemoA3wear = React.memo(A3wear)
+
+export default MemoA3wear

+ 0 - 248
code/src/pages/aaaa.tsx

@@ -1,248 +0,0 @@
-// `#{serverUrl}0/model.glb`
-// <div className={classNames(styles.A0model, isShow ? styles.A0modelShow : '')}>
-import React, { useRef, useState, useEffect, Suspense } from 'react'
-import * as THREE from 'three'
-import { Canvas, useThree, useFrame } from '@react-three/fiber'
-import { OrbitControls, useProgress, Html, Loader } from '@react-three/drei'
-import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'
-import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader'
-
-// 1. 加载进度条组件
-function LoadProgress() {
-  const { progress } = useProgress()
-  return (
-    <Html center>
-      <div style={{ color: 'white', fontSize: '14px' }}>
-        加载中: {progress.toFixed(2)}%
-      </div>
-    </Html>
-  )
-}
-// 2. 模型组件
-interface ModelProps {
-  onLoaded: (model: THREE.Group, parts: { [name: string]: THREE.Object3D }) => void
-  onProgress: (progress: number) => void
-}
-
-const Model: React.FC<ModelProps> = ({ onLoaded, onProgress }) => {
-  const groupRef = useRef<THREE.Group>(null)
-  const [modelParts, setModelParts] = useState<{ [name: string]: THREE.Object3D }>({})
-
-  useEffect(() => {
-    const manager = new THREE.LoadingManager()
-    manager.onProgress = (url, itemsLoaded, itemsTotal) => {
-      onProgress((itemsLoaded / itemsTotal) * 100)
-    }
-
-    const dracoLoader = new DRACOLoader()
-    dracoLoader.setDecoderPath('https://www.gstatic.com/draco/v1/decoders/')
-    dracoLoader.setDecoderConfig({ type: 'js' })
-
-    const loader = new GLTFLoader(manager)
-    loader.setDRACOLoader(dracoLoader)
-
-    loader.load(
-      '/path/to/your/model.glb', // 替换为你的GLB模型路径
-      gltf => {
-        const model = gltf.scene
-        model.traverse((child: any) => {
-          if (child.isMesh) {
-            child.castShadow = true
-            child.receiveShadow = true
-          }
-        })
-
-        // 3. 假设模型中的三个部分分别命名为'bs1', 'bs2', 'bs3'
-        const parts: { [name: string]: THREE.Object3D } = {}
-        model.children.forEach(child => {
-          if (['bs1', 'bs2', 'bs3'].includes(child.name)) {
-            parts[child.name] = child
-          }
-        })
-
-        setModelParts(parts)
-        onLoaded(model, parts)
-      },
-      xhr => {
-        // 加载进度回调已在LoadingManager中处理
-      },
-      error => {
-        console.error('加载模型出错:', error)
-      }
-    )
-  }, [onLoaded, onProgress])
-
-  return groupRef.current ? <primitive object={groupRef.current} /> : null
-}
-
-// 4. 场景设置与控制器组件
-function SceneSetup() {
-  const { camera, gl } = useThree()
-  const controlsRef = useRef<any>(null)
-
-  useFrame(() => {
-    if (controlsRef.current) {
-      controlsRef.current.update() // 启用阻尼效果必须每帧更新控制器
-    }
-  })
-
-  return (
-    <>
-      <ambientLight intensity={0.5} />
-      <directionalLight position={[10, 10, 5]} intensity={1} castShadow />
-      <OrbitControls
-        ref={controlsRef}
-        enableDamping={true} // 启用阻尼效果
-        dampingFactor={0.05} // 阻尼系数[7](@ref)
-        maxDistance={2} // 根据初始缩放比例调整
-        minDistance={0.5} // 根据初始缩放比例调整[9](@ref)
-        screenSpacePanning={true} // 移动端友好的平移方式
-      />
-    </>
-  )
-}
-
-// 5. 主组件
-const GLBModelViewer: React.FC = () => {
-  const [loadingProgress, setLoadingProgress] = useState(0)
-  const [model, setModel] = useState<THREE.Group | null>(null)
-  const [modelParts, setModelParts] = useState<{ [name: string]: THREE.Object3D }>({})
-  const [activePart, setActivePart] = useState<string | null>(null)
-  const { camera, scene } = useThree()
-
-  const handleModelLoaded = (
-    loadedModel: THREE.Group,
-    parts: { [name: string]: THREE.Object3D }
-  ) => {
-    setModel(loadedModel)
-    setModelParts(parts)
-    // 初始状态下显示所有模型
-    Object.values(parts).forEach(part => {
-      part.visible = true
-    })
-    scene.add(loadedModel)
-  }
-
-  const focusOnPart = (partName: string) => {
-    if (!model || !modelParts[partName]) return
-
-    // 隐藏所有部分,然后显示选中的部分
-    Object.entries(modelParts).forEach(([name, part]) => {
-      part.visible = name === partName
-    })
-
-    // 获取选中部分的包围盒并居中相机
-    const bbox = new THREE.Box3().setFromObject(modelParts[partName])
-    const center = bbox.getCenter(new THREE.Vector3())
-    const size = bbox.getSize(new THREE.Vector3())
-
-    // 计算相机位置,使其能够完整看到模型
-    const maxDim = Math.max(size.x, size.y, size.z)
-    const fov =
-      camera instanceof THREE.PerspectiveCamera ? (camera.fov * Math.PI) / 180 : 0
-    const cameraDistance = maxDim / (2 * Math.tan(fov / 2))
-
-    camera.position.copy(
-      center.clone().add(new THREE.Vector3(0, 0, cameraDistance * 1.5))
-    )
-    camera.lookAt(center)
-
-    setActivePart(partName)
-  }
-
-  const showAllModels = () => {
-    if (!model) return
-
-    // 显示所有模型部分
-    Object.values(modelParts).forEach(part => {
-      part.visible = true
-    })
-
-    // 将相机定位到整个模型的中心
-    const bbox = new THREE.Box3().setFromObject(model)
-    const center = bbox.getCenter(new THREE.Vector3())
-    const size = bbox.getSize(new THREE.Vector3())
-
-    const maxDim = Math.max(size.x, size.y, size.z)
-    const fov =
-      camera instanceof THREE.PerspectiveCamera ? (camera.fov * Math.PI) / 180 : 0
-    const cameraDistance = maxDim / (2 * Math.tan(fov / 2))
-
-    camera.position.copy(
-      center.clone().add(new THREE.Vector3(0, 0, cameraDistance * 1.5))
-    )
-    camera.lookAt(center)
-
-    setActivePart(null)
-  }
-
-  return (
-    <div style={{ width: '100vw', height: '100vh', position: 'relative' }}>
-      <Canvas
-        shadows
-        camera={{ position: [0, 0, 5], fov: 50 }}
-        onCreated={({ gl }) => {
-          gl.setClearColor(new THREE.Color(0x000000))
-        }}
-      >
-        <Suspense fallback={<LoadProgress />}>
-          <SceneSetup />
-          <Model onLoaded={handleModelLoaded} onProgress={setLoadingProgress} />
-        </Suspense>
-      </Canvas>
-
-      {/* 6. 控制按钮 UI */}
-      <div
-        style={{
-          position: 'absolute',
-          bottom: '20px',
-          left: 0,
-          right: 0,
-          display: 'flex',
-          justifyContent: 'center',
-          gap: '10px'
-        }}
-      >
-        <button onClick={() => focusOnPart('bs1')} style={{ padding: '10px' }}>
-          显示 BS1
-        </button>
-        <button onClick={() => focusOnPart('bs2')} style={{ padding: '10px' }}>
-          显示 BS2
-        </button>
-        <button onClick={() => focusOnPart('bs3')} style={{ padding: '10px' }}>
-          显示 BS3
-        </button>
-        <button onClick={showAllModels} style={{ padding: '10px' }}>
-          显示全部
-        </button>
-      </div>
-
-      {/* 加载进度条 */}
-      {loadingProgress < 100 && (
-        <div
-          style={{
-            position: 'absolute',
-            top: '50%',
-            left: '20%',
-            right: '20%',
-            height: '20px',
-            background: 'rgba(255, 255, 255, 0.2)',
-            borderRadius: '10px'
-          }}
-        >
-          <div
-            style={{
-              height: '100%',
-              width: `${loadingProgress}%`,
-              background: 'linear-gradient(90deg, #4facfe 0%, #00f2fe 100%)',
-              borderRadius: '10px',
-              transition: 'width 0.3s ease'
-            }}
-          />
-        </div>
-      )}
-    </div>
-  )
-}
-
-export default GLBModelViewer

+ 13 - 2
code/src/store/reducer/A0model.ts

@@ -1,7 +1,12 @@
+export type PageKeyType = 'home' | 'show' | 'wear'
 // 初始化状态
 const initState = {
-  isShow: true,
-  plan: 0
+  // 是否显示模型页面
+  isShow: false,
+  // 模型加载进度条
+  plan: 0,
+  // 当前页面
+  pageKey: 'home' as PageKeyType
 }
 
 // 定义 action 类型
@@ -14,6 +19,10 @@ type Props =
       type: 'A1/plan'
       payload: number
     }
+  | {
+      type: 'A1/pageKey'
+      payload: PageKeyType
+    }
 
 // reducer
 export default function Reducer(state = initState, action: Props) {
@@ -22,6 +31,8 @@ export default function Reducer(state = initState, action: Props) {
       return { ...state, isShow: action.payload }
     case 'A1/plan':
       return { ...state, plan: action.payload }
+    case 'A1/pageKey':
+      return { ...state, pageKey: action.payload }
 
     default:
       return state

+ 1 - 1
code/src/utils/http.ts

@@ -5,7 +5,7 @@ import store from '@/store'
 import { MessageFu } from './message'
 import { domShowFu } from './domShow'
 
-const envFlag = process.env.NODE_ENV === 'development'
+export const envFlag = process.env.NODE_ENV === 'development'
 
 const baseUrlTemp = 'https://sit-cctvyunzhan.4dage.com' // 测试环境
 // const baseUrlTemp = 'http://192.168.20.61:8097' // 线下环境

binární
静态资源/staticData/1/1_bs.png


静态资源/staticData/1/1_bac.jpg → 静态资源/staticData/home/bac.jpg


binární
静态资源/staticData/home/bs.png


binární
静态资源/staticData/home/btnBac.png


binární
静态资源/staticData/home/jian.png


binární
静态资源/staticData/home/start.jpg


binární
静态资源/staticData/home/titleBac.png


binární
静态资源/staticData/home/titlel.png


binární
静态资源/staticData/home/titler.png


静态资源/staticData/0/0_bac.jpg → 静态资源/staticData/model/bac.jpg


静态资源/staticData/0/model.glb → 静态资源/staticData/model/model.glb