| New file | 
 |  |  | 
 |  |  | const areaData = [ | 
 |  |  |   { | 
 |  |  |     x: -500, | 
 |  |  |     y: -400, | 
 |  |  |     width: 700, | 
 |  |  |     height: 800, | 
 |  |  |     name: 'selected-rect1684723196047', | 
 |  |  |     type: 'area', | 
 |  |  |     strokeColor: 'rgb(255, 0, 14)', | 
 |  |  |     areaNumber: 'A1', | 
 |  |  |     textHeight: 400, | 
 |  |  |   }, | 
 |  |  | ]; | 
 |  |  |  | 
 |  |  | export default areaData; | 
 
| New file | 
 |  |  | 
 |  |  | import React, { useRef, useState } from 'react'; | 
 |  |  | import { useFrame, useThree } from '@react-three/fiber'; | 
 |  |  | import * as THREE from 'three'; | 
 |  |  | import { CameraControls } from '@react-three/drei'; | 
 |  |  | import Text2 from './text'; | 
 |  |  | // import Annotation, { IAnnotationDataItem, IAnnotationRef } from './annotation'; | 
 |  |  |  | 
 |  |  | const Y = 1; | 
 |  |  | const Area = ({ | 
 |  |  |   x, | 
 |  |  |   y, | 
 |  |  |   width, | 
 |  |  |   height, | 
 |  |  |   areaNumber, | 
 |  |  |   textHeight, | 
 |  |  |   strokeColor, | 
 |  |  | }) => { | 
 |  |  |   const [hovered, setHover] = useState(false); | 
 |  |  |   const [clicked, setClicked] = useState(false); | 
 |  |  |   const meshRef = useRef(null); | 
 |  |  |   const { controls } = useThree((state) => ({ | 
 |  |  |     controls: state.controls, | 
 |  |  |   })); | 
 |  |  |  | 
 |  |  |   // 转换为x轴和z轴组成的坐标系中的位置 | 
 |  |  |   const position = new THREE.Vector3(x + width / 2, Y, y + height / 2); | 
 |  |  |   const size = [width, 1, height]; | 
 |  |  |   textHeight ||= Y + 50; | 
 |  |  |  | 
 |  |  |   // 相机移动到区域的位置 | 
 |  |  |   const handleClick = () => { | 
 |  |  |     if (clicked) return; | 
 |  |  |     setClicked(true); | 
 |  |  |     // controls.fitToBox(meshRef.current!, true); | 
 |  |  |     const mesh = meshRef.current; | 
 |  |  |     const { x, y, z } = mesh.position; | 
 |  |  |  | 
 |  |  |     const box = new THREE.Box3().setFromCenterAndSize( | 
 |  |  |       new THREE.Vector3(x, (y + textHeight) / 2, z), | 
 |  |  |       new THREE.Vector3(width, textHeight, height) | 
 |  |  |     ); | 
 |  |  |     controls.fitToBox(box, true); | 
 |  |  |  | 
 |  |  |     // annotationRef.current?.show(); | 
 |  |  |   }; | 
 |  |  |  | 
 |  |  |   // 相机移动回原来的位置 | 
 |  |  |   const handleDoubleClick = () => { | 
 |  |  |     setClicked(false); | 
 |  |  |   }; | 
 |  |  |  | 
 |  |  |   // 监听鼠标移入和移出事件,改变hover状态 | 
 |  |  |   const handlePointerOver = () => setHover(true); | 
 |  |  |   const handlePointerOut = () => setHover(false); | 
 |  |  |  | 
 |  |  |   // 每帧更新边框的颜色和粗细 | 
 |  |  |   useFrame(() => { | 
 |  |  |     const box = meshRef.current; | 
 |  |  |     if (box) { | 
 |  |  |       const color = hovered || clicked ? strokeColor : 'white'; | 
 |  |  |       const thickness = clicked ? 0.5 : 0.2; | 
 |  |  |       const material = box.material; | 
 |  |  |       material.color.set(color); | 
 |  |  |       material.linewidth = thickness; | 
 |  |  |     } | 
 |  |  |   }); | 
 |  |  |  | 
 |  |  |   // const annotationRef = useRef(null); | 
 |  |  |   const annotationData = [ | 
 |  |  |     { | 
 |  |  |       label: '长', | 
 |  |  |       value: width + '米', | 
 |  |  |     }, | 
 |  |  |     { | 
 |  |  |       label: '宽', | 
 |  |  |       value: height + '米', | 
 |  |  |     }, | 
 |  |  |   ]; | 
 |  |  |  | 
 |  |  |   return ( | 
 |  |  |     <group | 
 |  |  |       onClick={handleClick} | 
 |  |  |       onPointerOver={handlePointerOver} | 
 |  |  |       onPointerOut={handlePointerOut} | 
 |  |  |       onDoubleClick={handleDoubleClick} | 
 |  |  |       onPointerMissed={() => setClicked(false)} | 
 |  |  |     > | 
 |  |  |       <mesh ref={meshRef} position={position}> | 
 |  |  |         <boxGeometry attach="geometry" args={size} /> | 
 |  |  |         <lineSegments> | 
 |  |  |           <edgesGeometry attach="geometry" args={[new THREE.BoxGeometry(...size)]} /> | 
 |  |  |           <lineBasicMaterial attach="material" color={strokeColor} linewidth={0.2} /> | 
 |  |  |         </lineSegments> | 
 |  |  |         <meshBasicMaterial attach="material" color={strokeColor} transparent opacity={0.2} /> | 
 |  |  |       </mesh> | 
 |  |  |  | 
 |  |  |       {areaNumber && ( | 
 |  |  |         <Text2 | 
 |  |  |           position={new THREE.Vector3(position.x, textHeight, position.z)} | 
 |  |  |           text={areaNumber} | 
 |  |  |           scale={new THREE.Vector3(100, 100, 100)} | 
 |  |  |           fontSize={100} | 
 |  |  |         /> | 
 |  |  |       )} | 
 |  |  |       {/* <Annotation | 
 |  |  |         ref={annotationRef} | 
 |  |  |         title={areaNumber} | 
 |  |  |         position={position} | 
 |  |  |         data={annotationData} | 
 |  |  |       ></Annotation> */} | 
 |  |  |     </group> | 
 |  |  |   ); | 
 |  |  | }; | 
 |  |  |  | 
 |  |  | export default Area; | 
 
| New file | 
 |  |  | 
 |  |  | import * as THREE from 'three'; | 
 |  |  | import { useEffect, useState } from 'react'; | 
 |  |  |  | 
 |  |  |  | 
 |  |  | function generateSprite(text, color, fontSize = 50) { | 
 |  |  |   const canvas = document.createElement('canvas'); | 
 |  |  |   canvas.width = 300; | 
 |  |  |   canvas.height = 300; | 
 |  |  |   const context = canvas.getContext('2d'); | 
 |  |  |   context.beginPath(); | 
 |  |  |   context.font = `${fontSize}px Microsoft YaHei`; | 
 |  |  |   context.fillStyle = color; | 
 |  |  |   context.textAlign = 'center'; | 
 |  |  |   context.textBaseline = 'middle'; | 
 |  |  |   context.fillText(text, canvas.width / 2, canvas.height / 2); | 
 |  |  |   context.fill(); | 
 |  |  |   context.stroke(); | 
 |  |  |   return canvas; | 
 |  |  | } | 
 |  |  |  | 
 |  |  | const Text2 = ({ | 
 |  |  |   text, | 
 |  |  |   position, | 
 |  |  |   scale = new THREE.Vector3(100, 100, 100), | 
 |  |  |   color = '#00D1D1', | 
 |  |  |   fontSize = 50, | 
 |  |  | }) => { | 
 |  |  |   const [material, setMaterial] = useState(undefined); | 
 |  |  |   useEffect(() => { | 
 |  |  |     const material = new THREE.SpriteMaterial({ | 
 |  |  |       map: new THREE.CanvasTexture(generateSprite(text, color, fontSize)), | 
 |  |  |       blending: THREE.AdditiveBlending, | 
 |  |  |     }); | 
 |  |  |     setMaterial(material); | 
 |  |  |   }, [text, color, fontSize]); | 
 |  |  |  | 
 |  |  |   return <sprite material={material} position={position} scale={scale} />; | 
 |  |  | } | 
 |  |  |  | 
 |  |  | export default Text2; | 
 
 |  |  | 
 |  |  | import { useFrame } from '@react-three/fiber'; | 
 |  |  | import * as THREE from 'three'; | 
 |  |  | import Tunnel from '../components/tunnel'; | 
 |  |  | import Area from '../components/area'; | 
 |  |  | import Shelf from '../components/shelf'; | 
 |  |  | import Box from '../components/box'; | 
 |  |  | import Agv from '../components/agv'; | 
 |  |  |  | 
 |  |  | import tunnelData from '@/assets/data/tunnel'; | 
 |  |  | import areaData from '@/assets/data/area'; | 
 |  |  | import shelfData from '@/assets/data/shelf'; | 
 |  |  | import agvRealDataList from '@/assets/data/agv'; | 
 |  |  | import { INTERVAL_TIME } from '@/config/setting' | 
 |  |  | 
 |  |  |         return tunnelData.map((data, index) => <Tunnel key={index} {...data} />) | 
 |  |  |     }, []); | 
 |  |  |  | 
 |  |  |     const areaEl = useMemo(() => { | 
 |  |  |         return areaData.map((area, index) => <Area key={index} {...area} />) | 
 |  |  |     }, []); | 
 |  |  |  | 
 |  |  |     const shelfEl = useMemo(() => { | 
 |  |  |         return shelfData.map((data, index) => <Shelf key={index} {...data} />) | 
 |  |  |     }, []); | 
 |  |  | 
 |  |  |         <> | 
 |  |  |             <group> | 
 |  |  |                 {tunnelEl} | 
 |  |  |                 {areaEl} | 
 |  |  |                 {shelfEl} | 
 |  |  |                 {/* {shelfEl1} */} | 
 |  |  |                 {agvEl} |