瀏覽代碼

功能开发完成

lanxin 1 月之前
父節點
當前提交
5990a59f66

+ 2 - 2
public/index.html

@@ -13,12 +13,12 @@
     <script src="./myData/jsmpeg.min.js"></script>
     <script src="./myData/f-video.js"></script>
 
-    <style>
+    <!-- <style>
       @font-face {
         font-family: 'qfk';
         src: url('./myData/qfk.TTF');
       }
-    </style>
+    </style> -->
     <title>程哲碑</title>
   </head>
   <body>

File diff suppressed because it is too large
+ 1046 - 62
public/myData/myData.js


二進制
public/myData/ybwx.ts


二進制
src/assets/img/A6_gen_false.png


二進制
src/assets/img/A6_gen_gesture.png


二進制
src/assets/img/A6_gen_nodeDetailBg.png


二進制
src/assets/img/A6_gen_normal.png


二進制
src/assets/img/A6_sinicize_gesture.png


二進制
src/assets/img/btn_menu.png


二進制
src/assets/img/closeWithTxt2.png


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

@@ -4,7 +4,7 @@
   box-sizing: border-box;
   word-wrap: break-word;
   -webkit-tap-highlight-color: transparent;
-  font-family: qfk;
+  font-family: 'SimHei', 'SimSun', 'Microsoft Yahei', 'PingFang SC', 'Avenir', 'Segoe UI', 'Hiragino Sans GB', 'STHeiti', 'Microsoft Sans Serif', 'WenQuanYi Micro Hei', sans-serif;
 }
 /* 全局css变量 */
 :root {
@@ -329,7 +329,7 @@ textarea {
   height: 100%;
   cursor: pointer;
   display: flex;
-  justify-content: flex-end;
+  justify-content: end;
   align-items: center;
 }
 #root .ant-tooltip .tooltip_MT .top .close > img {
@@ -358,4 +358,4 @@ textarea {
   position: fixed;
   top: 0;
   left: 0;
-}
+}

+ 2 - 1
src/assets/styles/base.less

@@ -4,7 +4,8 @@
   box-sizing: border-box;
   word-wrap: break-word;
   -webkit-tap-highlight-color: transparent;
-  font-family: qfk;
+  font-family: 'SimHei', 'SimSun', 'Microsoft Yahei', 'PingFang SC', 'Avenir', 'Segoe UI', 'Hiragino Sans GB',
+    'STHeiti', 'Microsoft Sans Serif', 'WenQuanYi Micro Hei', sans-serif;
 }
 
 /* 全局css变量 */

+ 6 - 6
src/pages/A2yblm/components/Detail/index.module.scss

@@ -32,8 +32,8 @@
           gap: 10px;
           .item {
             width: 100px;
-            height: 20px;
-            font-size: 10px;
+            height: 27px;
+            font-size: 14px;
             line-height: 15px;
             text-align: right;
             color: rgba(255, 233, 182, 1);
@@ -43,8 +43,8 @@
               width: 100%;
               height: 100%;
               position: absolute;
-              top: 0;
-              left: 39%;
+              top: -7%;
+              left: 33%;
               z-index: -1;
               & > img {
                 width: 100%;
@@ -69,8 +69,8 @@
             border-right: 1px dashed rgba(255, 255, 255, 1);
           }
           .txt {
-            width: 30px;
-            font-size: 10px;
+            width: 40px;
+            font-size: 15px;
             text-align: right;
             color: #fff;
           }

+ 1 - 0
src/pages/A2yblm/components/Detail/index.tsx

@@ -47,6 +47,7 @@ function Detail({ currentTagIndex, setCurrentTagIndex, setIsShowTag }: DetailPro
           {myData.detail_modal.bottom.map((item, index) => (
             <div className={`item ${currentTagIndex === index + myData.detail_modal.top.length ? 'active' : ''}`} onClick={() => handleHot(index + myData.detail_modal.top.length)} key={index}>
               {item.title}
+              {currentTagIndex === index + myData.detail_modal.top.length && <div className="selectedBg"><img src={require('@/assets/img/hotSelected_bg.png')} alt="" /></div>}
             </div>
           ))}
         </div>

+ 82 - 0
src/pages/A6ybwx/A6_2_zxzgh/index.module.scss

@@ -318,3 +318,85 @@
     }
   }
 }
+
+.tip {
+  width: 100%;
+  height: 100%;
+  position: fixed;
+  top: 0;
+  left: 0;
+  background: rgba(0, 0, 0, 0.5);
+  :global {
+    .gesture {
+      position: absolute;
+      top: 20%;
+      right: 3%;
+      width: 71px;
+      height: 100px;
+      display: flex;
+      align-items: center;
+      flex-direction: column;
+      justify-content: center;
+      gap: 2px;
+      font-size: 10px;
+      font-weight: 500;
+      color: #fff;
+      & > img {
+        width: 60px;
+        height: 50px;
+        object-fit: contain;
+      }
+      .txt {
+        text-align: center;
+        width: 100%;
+        height: fit-content;
+        font-size: 8px;
+        font-weight: 500;
+        color: #fff;
+      }
+    }
+    .t1,
+    .t2 {
+      width: 77px;
+      height: 25px;
+      position: absolute;
+      top: 50%;
+      left: 50%;
+      transform: translate(-50%, -50%);
+      font-size: 8px;
+      font-weight: 500;
+      color: #fff;
+      display: flex;
+      flex-direction: column;
+      justify-content: center;
+      .txt {
+        width: 60px;
+        height: 22px;
+      }
+      .lineContainer {
+        width: 100%;
+        height: 5px;
+        display: flex;
+        align-items: center;
+        justify-content: flex-end;
+        .line {
+          width: 60px;
+          height: 0;
+          border-bottom: 1px dashed #fff;
+        }
+        .ball {
+          width: 5px;
+          height: 5px;
+          border-radius: 50%;
+          background-color: #fff;
+        }
+      }
+    }
+    .t1 {
+      transform: translate(317%, 34%);
+    }
+    .t2 {
+      transform: translate(317%, 371%);
+    }
+  }
+}

File diff suppressed because it is too large
+ 124 - 82
src/pages/A6ybwx/A6_2_zxzgh/index.tsx


+ 94 - 0
src/pages/A6ybwx/Genealogy/components/Graph/index.module.scss

@@ -0,0 +1,94 @@
+.Graph {
+  width: 2500px;
+  height: 800px;
+  touch-action: none;
+  pointer-events: auto !important;
+  position: relative;
+  :global {
+    .nodeActiveG {
+      position: absolute;
+      top: 50%;
+      left: 50%;
+      transform: translate(-50%, -50%);
+    }
+  }
+}
+
+.tip {
+  width: 133px;
+  height: 40px;
+  position: absolute;
+  bottom: 100px;
+  left: 20px;
+  display: flex;
+  flex-direction: column;
+  gap: 2px;
+  :global {
+    .t1,
+    .t2 {
+      width: 100%;
+      height: 18px;
+      display: flex;
+      align-items: center;
+      gap: 2px;
+      .txt {
+        line-height: 18px;
+        font-size: 10px;
+        color: rgba(255, 255, 255, 1);
+      }
+    }
+    .t1 {
+      .icon {
+        width: 40px;
+        height: 100%;
+        & > img {
+          position: relative;
+          top: -16%;
+          height: 100%;
+          object-fit: contain;
+        }
+      }
+    }
+    .t2 {
+      .t2_rightLine {
+        width: 40px;
+        height: 100%;
+      }
+    }
+  }
+}
+
+.miniMap {
+  position: fixed;
+  left: 20px;
+  bottom: 20px;
+  width: 250px;
+  height: 80px;
+  background: rgba(0, 0, 0, 0.5);
+  border: 1px solid rgba(255, 233, 182, 0.5);
+  overflow: hidden;
+
+  .viewport {
+    position: absolute;
+    border: 1px solid #ffe9b6;
+    cursor: move;
+    background: rgba(255, 233, 182, 0.1);
+  }
+
+  .miniContent {
+    transform-origin: 0 0;
+    pointer-events: none;
+    width: 2400px;
+    height: 1000px;
+    position: relative;
+    touch-action: none;
+    :global {
+      .nodeActiveG {
+        position: absolute;
+        top: 50%;
+        left: 50%;
+        transform: translate(-50%, -50%);
+      }
+    }
+  }
+}

+ 273 - 0
src/pages/A6ybwx/Genealogy/components/Graph/index.tsx

@@ -0,0 +1,273 @@
+import React, { useCallback, useEffect, useRef, useState } from 'react'
+import styles from './index.module.scss'
+import { NodeTurnRight, NodeRight, NodeBottom, RightLineDash, NodeActive, NodeTurnBottomRight } from '../Utils'
+import { useDrag } from '@use-gesture/react'
+import { myData } from '@/utils/http'
+
+const MAIN_CONTENT_WIDTH = 1920
+const MAIN_CONTENT_HEIGHT = 945
+const MINIMAP_SCALE = 0.045
+
+function Graph({ setCurrentNodeIndex }: { setCurrentNodeIndex: (index: number) => void }) {
+  // 新增拖拽相关状态
+  const [isDragging, setIsDragging] = useState(false)
+  const [startX, setStartX] = useState(0)
+  const [startY, setStartY] = useState(0)
+  const [offsetX, setOffsetX] = useState(0)
+  const [offsetY, setOffsetY] = useState(0)
+
+  // 设置初始位置
+  useEffect(() => {
+    setOffsetX(0)
+    setOffsetY(-200)
+  }, [])
+
+  const startRef = useRef({
+    startX: 0,    // 触摸起始点 X 坐标
+    startY: 0,    // 触摸起始点 Y 坐标
+    startOffsetX: 0, // 元素起始偏移 X
+    startOffsetY: 0  // 元素起始偏移 Y
+  });
+
+  const graphRef = useRef<HTMLDivElement>(null); // 已存在,无需新增
+
+  // 原生事件处理函数(无需 useCallback,因为在 useEffect 内绑定)
+  // const nativeTouchStart = (e: TouchEvent) => {
+  //   const el = graphRef.current;
+  //   if (!el || e.target !== el) return; // 仅容器本身触发
+
+  //   const touch = e.touches[0];
+  //   if (!touch) return;
+
+  //   startRef.current = {
+  //     startX: touch.clientX,
+  //     startY: touch.clientY,
+  //     startOffsetX: offsetX,
+  //     startOffsetY: offsetY,
+  //   };
+  // };
+
+  // const nativeTouchMove = (e: TouchEvent) => {
+  //   const el = graphRef.current;
+  //   if (!el) return;
+
+  //   const touch = e.touches[0];
+  //   if (!touch) return;
+
+  //   const { startX, startY, startOffsetX, startOffsetY } = startRef.current;
+  //   const dx = touch.clientX - startX;
+  //   const dy = touch.clientY - startY;
+
+  //   const newX = startOffsetX + dx;
+  //   const newY = startOffsetY + dy;
+
+  //   // 打印坐标,确认是否持续触发
+  //   console.log('原生touchmove:', touch.clientX, touch.clientY);
+
+  //   setOffsetX(Math.min(0, Math.max(-1640, newX)));
+  //   setOffsetY(Math.min(0, Math.max(-350, newY)));
+  // };
+
+  // useEffect(() => {
+  //   const el = graphRef.current;
+  //   console.log('offsetX, offsetY', el)
+  //   if (!el) return;
+
+  //   // 绑定原生事件(passive: false 可选,这里用了 touchAction: none 不需要)
+  //   el.addEventListener('touchstart', nativeTouchStart);
+  //   el.addEventListener('touchmove', nativeTouchMove);
+
+  //   // 卸载时解绑
+  //   // return () => {
+  //   //   el.removeEventListener('touchstart', nativeTouchStart);
+  //   //   el.removeEventListener('touchmove', nativeTouchMove);
+  //   // };
+  // }, [offsetX, offsetY]);
+
+  // 新增事件处理函数
+  const handleMouseDown = (e: React.MouseEvent) => {
+    if (e.target === e.currentTarget) { // 仅当点击容器本身时触发拖拽
+      e.stopPropagation();
+      setIsDragging(true);
+      setStartX(e.clientX);
+      setStartY(e.clientY);
+      setOffsetX(offsetX);
+      setOffsetY(offsetY);
+    }
+  }
+
+  const handleTouchStart = useCallback((e: React.TouchEvent) => {
+    const touch = e.touches[0]; // 获取第一个触摸点(单指拖拽)
+    console.log(touch.clientX, touch.clientY)
+    // if (!touch) return;
+
+    // 记录触摸起始坐标
+    startRef.current.startX = touch.clientX;
+    startRef.current.startY = touch.clientY;
+
+    // 记录元素当前偏移量(避免拖拽时跳跃)
+    startRef.current.startOffsetX = offsetX;
+    startRef.current.startOffsetY = offsetY;
+
+  }, [offsetX, offsetY])
+
+  const handleMouseMove = (e: React.MouseEvent) => {
+    if (isDragging) {
+      // 改为使用初始点击位置计算总位移
+      const dx = e.clientX - startX
+      const dy = e.clientY - startY
+
+      // 使用函数式更新确保获取最新状态值
+      setOffsetX(prev => {
+        const newX = prev + dx
+        return Math.min(0, Math.max(-1640, newX))
+      })
+
+      setOffsetY(prev => {
+        const newY = prev + dy
+        return Math.min(0, Math.max(-350, newY))
+      })
+
+      // 更新起始坐标为当前鼠标位置(保持相对移动)
+      setStartX(e.clientX)
+      setStartY(e.clientY)
+    }
+  }
+
+  const handleTouchMove = useCallback((e: React.TouchEvent) => {
+    console.log(123)
+    const touch = e.touches[0];
+    if (!touch) return;
+
+    // 计算滑动距离 = 当前触摸位置 - 初始触摸位置
+    const dx = touch.clientY - startRef.current.startY;
+    const dy = -touch.clientX + startRef.current.startX;
+
+    // 更新 X 偏移量(限制范围:-1640 ~ 0,与你原有逻辑一致)
+    setOffsetX(prev => {
+      const newX = startRef.current.startOffsetX + dx; // 基于初始偏移量计算
+      return Math.min(0, Math.max(-1640, newX));
+      // return newX
+    });
+
+    // 更新 Y 偏移量(限制范围:-350 ~ 0)
+    setOffsetY(prev => {
+      const newY = startRef.current.startOffsetY + dy;
+      return Math.min(0, Math.max(-350, newY));
+      // return newY
+    });
+
+  }, [])
+
+  const handleMouseUp = () => {
+    setIsDragging(false)
+  }
+
+  // 新增小地图相关逻辑
+  // const graphRef = useRef<HTMLDivElement>(null)
+  const [contentSize, setContentSize] = useState({ width: 2000, height: 1200 })
+  const miniMapScale = 0.1
+
+  // 动态获取容器尺寸
+  useEffect(() => {
+    if (graphRef.current) {
+      setContentSize({ width: graphRef.current.clientWidth, height: graphRef.current.clientHeight })
+    }
+  }, [])
+
+  // 小地图拖拽绑定
+  const bind = useDrag(({ offset: [x, y] }) => {
+    // 计算视口最大移动范围
+    const maxX = contentSize.width * miniMapScale - MAIN_CONTENT_WIDTH * MINIMAP_SCALE - 2
+    const maxY = contentSize.height * miniMapScale - MAIN_CONTENT_HEIGHT * MINIMAP_SCALE - 4
+    console.log(maxX, maxY, '------------')
+    // 钳制坐标范围
+    const clampedX = Math.max(0, Math.min(x, maxX))
+    const clampedY = Math.max(0, Math.min(y, maxY))
+    setOffsetX(-clampedX / miniMapScale)
+    setOffsetY(-clampedY / miniMapScale)
+  })
+
+  const handleNameClick = (index: number) => {
+    console.log(index, '------------')
+    setCurrentNodeIndex(index)
+  }
+
+  const NodeData = React.memo(() =>
+    <>
+      {myData.genealogyData.map((item, index) => {
+        let res
+        if (item.type === 'active') res = <NodeActive key={index} data={item} style={{ transform: `translate(${item.position.x}px, ${item.position.y}px)` }} nameClick={() => handleNameClick(index)} className='nodeActiveG' />
+        if (item.type === 'nodeRight_n') res = <NodeRight key={index} data={item} style={{ transform: `translate(${item.position.x}px, ${item.position.y}px)` }} type='normal' nameClick={() => handleNameClick(index)} />
+        if (item.type === 'nodeRight_a') res = <NodeRight key={index} data={item} style={{ transform: `translate(${item.position.x}px, ${item.position.y}px)` }} type='active' nameClick={() => handleNameClick(index)} />
+        if (item.type === 'nodeRight_f') res = <NodeRight key={index} data={item} style={{ transform: `translate(${item.position.x}px, ${item.position.y}px)` }} type='false' nameClick={() => handleNameClick(index)} />
+        if (item.type === 'nodeBottom_n') res = <NodeBottom key={index} data={item} style={{ transform: `translate(${item.position.x}px, ${item.position.y}px)` }} type='normal' nameClick={() => handleNameClick(index)} />
+        if (item.type === 'nodeTurnRight_n') res = <NodeTurnRight key={index} data={item} style={{ transform: `translate(${item.position.x}px, ${item.position.y}px)` }} type='normal' nameClick={() => handleNameClick(index)} />
+        if (item.type === 'nodeTurnRight_a') res = <NodeTurnRight key={index} data={item} style={{ transform: `translate(${item.position.x}px, ${item.position.y}px)` }} type='active' nameClick={() => handleNameClick(index)} />
+        if (item.type === 'nodeTurnBottomRight_a') res = <NodeTurnBottomRight key={index} data={item} style={{ transform: `translate(${item.position.x}px, ${item.position.y}px)` }} type='active' nameClick={() => handleNameClick(index)} />
+        return res
+      })}
+    </>
+  )
+
+
+
+  return (
+    <>
+      <div
+        className={styles.Graph}
+        ref={graphRef}
+        onMouseDown={handleMouseDown}
+        onMouseMove={handleMouseMove}
+        onMouseUp={handleMouseUp}
+        onMouseLeave={handleMouseUp}
+        onTouchStart={handleTouchStart}
+        onTouchMove={handleTouchMove}
+        style={{
+          cursor: isDragging ? 'grabbing' : 'grab',
+          transform: `translate(${offsetX}px, ${offsetY}px)`,
+          userSelect: 'none'
+        }}
+      >
+        <NodeData />
+      </div>
+      <div className={styles.tip}>
+        <div className='t1'>
+          <div className='icon'>
+            <img src={require('@/assets/img/A6_gen_false.png')} draggable='false' alt='' />
+          </div>
+          <div className='txt'>为勘误之人</div>
+        </div>
+        <div className='t2'>
+          <RightLineDash className='t2_rightLine' />
+          <div className='txt'>为非直系父子关系</div>
+        </div>
+      </div>
+      <div className={styles.miniMap}>
+        <div
+          className={styles.viewport}
+          {...bind()}
+          style={{
+            transform: `translate(${-offsetX * miniMapScale}px, ${-offsetY * miniMapScale}px)`,
+            width: `${MAIN_CONTENT_WIDTH * MINIMAP_SCALE}px`,
+            height: `${MAIN_CONTENT_HEIGHT * MINIMAP_SCALE}px`
+          }}
+        />
+        <div
+          className={styles.miniContent}
+          style={{
+            transform: `scale(${miniMapScale})`,
+            width: `${contentSize.width}px`,
+            height: `${contentSize.height}px`
+          }}
+        >
+          <NodeData />
+        </div>
+      </div>
+    </>
+  )
+}
+
+const MemoGraph = React.memo(Graph)
+
+export default MemoGraph

+ 268 - 0
src/pages/A6ybwx/Genealogy/components/Utils/index.module.scss

@@ -0,0 +1,268 @@
+.rightLineDash {
+  width: 60px;
+  height: 6px;
+  display: flex;
+  align-items: center;
+  gap: 1px;
+  :global {
+    .line {
+      width: calc(100% - 6px);
+      height: 2px;
+      border-bottom: 1px dashed rgba(255, 233, 182, 1);
+    }
+    .arrow {
+      width: 0;
+      height: 0;
+      border-top: 3px solid transparent;
+      border-bottom: 3px solid transparent;
+      border-left: 4px solid rgba(255, 233, 182, 1);
+    }
+  }
+}
+
+.rightLine {
+  width: 60px;
+  height: 6px;
+  display: flex;
+  align-items: center;
+  :global {
+    .line {
+      width: calc(100% - 5px);
+      height: 1px;
+      background-color: rgba(255, 233, 182, 1);
+    }
+    .arrow {
+      width: 0;
+      height: 0;
+      border-top: 3px solid transparent;
+      border-bottom: 3px solid transparent;
+      border-left: 4px solid rgba(255, 233, 182, 1);
+    }
+  }
+}
+
+.bottomLine {
+  width: 6px;
+  height: 40px;
+  display: flex;
+  align-items: center;
+  flex-direction: column;
+  :global {
+    .line {
+      transform: translateX(-33%);
+      width: 1px;
+      height: calc(100% - 5px);
+      background-color: rgba(255, 233, 182, 1);
+    }
+    .arrow {
+      width: 0;
+      height: 0;
+      border-right: 3px solid transparent;
+      border-left: 3px solid transparent;
+      border-top: 4px solid rgba(255, 233, 182, 1);
+    }
+  }
+}
+
+.turnRightLine {
+  width: 25px;
+  height: 80px;
+  display: flex;
+  :global {
+    .line {
+      align-self: flex-end;
+      width: calc(100% - 5px);
+      height: calc(100% - 3px);
+      border-top: 1px solid rgba(255, 233, 182, 1);
+      border-left: 1px solid rgba(255, 233, 182, 1);
+    }
+    .arrow {
+      align-self: flex-start;
+      width: 0;
+      height: 0;
+      border-top: 3px solid transparent;
+      border-bottom: 3px solid transparent;
+      border-left: 4px solid rgba(255, 233, 182, 1);
+    }
+  }
+}
+
+.turnBottomRightLine {
+  width: 25px;
+  height: 80px;
+  display: flex;
+  :global {
+    .line {
+      align-self: flex-start;
+      width: calc(100% - 5px);
+      height: calc(100% - 3px);
+      border-bottom: 1px solid rgba(255, 233, 182, 1);
+      border-left: 1px solid rgba(255, 233, 182, 1);
+    }
+    .arrow {
+      align-self: flex-end;
+      width: 0;
+      height: 0;
+      border-top: 3px solid transparent;
+      border-bottom: 3px solid transparent;
+      border-left: 4px solid rgba(255, 233, 182, 1);
+    }
+  }
+}
+
+.nodeNormal,
+.nodeActive,
+.nodeFalse {
+  width: 140px;
+  height: fit-content;
+  max-height: 60px;
+  display: flex;
+  flex-direction: column;
+  gap: 2px;
+  :global {
+    .bg {
+      cursor: pointer;
+      width: 97px;
+      height: 22px;
+      line-height: 22px;
+      padding-right: 20px;
+      background: url('../../../../../assets/img/A6_gen_normal.png') no-repeat center center;
+      background-size: 100% 100%;
+      font-size: 13px;
+      font-weight: 500;
+      color: rgba(91, 71, 46, 1);
+      text-align: center;
+    }
+    .txt {
+      width: 100%;
+      height: fit-content;
+      max-height: 29px;
+      line-height: 9px;
+      font-size: 8px;
+      color: rgba(255, 233, 182, 1);
+      padding-left: 10px;
+    }
+    .extra {
+      width: 100%;
+      height: fit-content;
+      max-height: 10px;
+      font-size: 8px;
+      padding-left: 10px;
+      color: #fff;
+    }
+  }
+}
+.nodeActive {
+  :global {
+    .bg {
+      width: 97px;
+      height: 22px;
+      background: url('../../../../../assets/img/A6_gen_active.png') no-repeat center center;
+      background-size: 100% 100%;
+    }
+  }
+}
+.nodeFalse {
+  :global {
+    .bg {
+      width: 97px;
+      height: 22px;
+      background: url('../../../../../assets/img/A6_gen_false.png') no-repeat center center;
+      background-size: 100% 100%;
+    }
+  }
+}
+
+.nodeRight {
+  pointer-events: none;
+  width: 200px;
+  height: 60px;
+  display: flex;
+  position: absolute;
+  top: 50%;
+  left: 50%;
+  transform: translate(-50%, -50%);
+  :global {
+    #rightLine {
+      transform: translate(3px, 8px);
+    }
+    #nodeNormal,
+    #nodeActive,
+    #nodeFalse {
+      .bg {
+        pointer-events: auto !important;
+      }
+    }
+  }
+}
+
+.nodeBottom {
+  pointer-events: none;
+  width: 140px;
+  height: 100px;
+  display: flex;
+  flex-direction: column;
+  position: absolute;
+  top: 50%;
+  left: 50%;
+  transform: translate(-50%, -50%);
+  :global {
+    #bottomLine {
+      transform: translate(40px, -5px);
+    }
+    #nodeNormal,
+    #nodeActive,
+    #nodeFalse {
+      .bg {
+        pointer-events: auto !important;
+      }
+    }
+  }
+}
+
+.nodeTurnRight {
+  pointer-events: none;
+  width: 165px;
+  height: 90px;
+  display: flex;
+  position: absolute;
+  top: 50%;
+  left: 50%;
+  transform: translate(-50%, -50%);
+  :global {
+    #turnRightLine {
+      transform: translate(3px, 8px);
+    }
+    #nodeNormal,
+    #nodeActive,
+    #nodeFalse {
+      .bg {
+        pointer-events: auto !important;
+      }
+    }
+  }
+}
+
+.nodeTurnBottomRight {
+  pointer-events: none;
+  width: 165px;
+  height: 119px;
+  display: flex;
+  position: absolute;
+  top: 50%;
+  left: 50%;
+  transform: translate(-50%, -50%);
+  :global {
+    #turnRightLine {
+      transform: translate(3px, 8px);
+    }
+    #nodeNormal,
+    #nodeActive,
+    #nodeFalse {
+      .bg {
+        pointer-events: auto !important;
+      }
+      align-self: flex-end;
+    }
+  }
+}

+ 181 - 0
src/pages/A6ybwx/Genealogy/components/Utils/index.tsx

@@ -0,0 +1,181 @@
+import React from 'react'
+import styles from './index.module.scss'
+
+interface dataType {
+  name: string;
+  type: string;
+  addTxt: string;
+  extra: string;
+  position: {
+    x: number;
+    y: number;
+  };
+  text1: {
+    title: string;
+    content: string;
+  };
+}
+
+interface nodeProps {
+  type?: string
+  className?: string
+  nameClick?: () => void
+  style?: React.CSSProperties
+  data: dataType
+}
+
+const RightLine = () => {
+  return (
+    <div className={`${styles.rightLine}`} id='rightLine'>
+      <div className='line'></div>
+      <div className='arrow'></div>
+    </div>
+  )
+}
+
+const RightLineDash = ({ className }: { className?: string }) => {
+  return (
+    <div className={`${styles.rightLineDash} ${className}`} id='rightLineDash'>
+      <div className='line'></div>
+      <div className='arrow'></div>
+    </div>
+  )
+}
+
+const BottomLine = () => {
+  return (
+    <div className={styles.bottomLine} id='bottomLine'>
+      <div className='line'></div>
+      <div className='arrow'></div>
+    </div>
+  )
+}
+
+const TurnRightLine = () => {
+  return (
+    <div className={styles.turnRightLine} id='turnRightLine'>
+      <div className='line'></div>
+      <div className='arrow'></div>
+    </div>
+  )
+}
+
+const TurnBottomRightLine = () => {
+  return (
+    <div className={styles.turnBottomRightLine} id='turnBottomRightLine'>
+      <div className='line'></div>
+      <div className='arrow'></div>
+    </div>
+  )
+}
+
+const NodeNormal = ({ className, nameClick, style, data }: nodeProps) => {
+  return (
+    <div className={`${styles.nodeNormal} ${className}`} style={style} id='nodeNormal'>
+      <div className='bg' onClick={(e) => { nameClick?.(); console.log(123) }}>{data.name}</div>
+      <div className='txt'>{data.text1.content}</div>
+      <div className='extra'>{data.extra}</div>
+    </div>
+  )
+}
+const NodeActive = ({ className, nameClick, style, data }: nodeProps) => {
+  return (
+    <div className={`${styles.nodeActive} ${className}`} style={style} id='nodeActive'>
+      <div className='bg' onClick={(e) => { e.stopPropagation(); nameClick?.() }}>{data.name}</div>
+      <div className='txt'>{data.text1.content}</div>
+      <div className='extra'>{data.extra}</div>
+    </div>
+  )
+}
+const NodeFalse = ({ className, nameClick, style, data }: nodeProps) => {
+  return (
+    <div className={`${styles.nodeFalse} ${className}`} style={style} id='nodeFalse'>
+      <div className='bg' onClick={(e) => { e.stopPropagation(); nameClick?.() }}>{data.name}</div>
+      <div className='txt'>{data.text1.content}</div>
+      <div className='extra'>{data.extra}</div>
+    </div>
+  )
+}
+
+const NodeRight = ({
+  type,
+  className,
+  nameClick,
+  style,
+  data
+}: nodeProps) => {
+  return (
+    <div className={`${styles.nodeRight} ${className}`} id='nodeRight' style={style}>
+      <RightLine />
+      {type === 'normal' && <NodeNormal nameClick={nameClick} data={data} />}
+      {type === 'active' && <NodeActive nameClick={nameClick} data={data} />}
+      {type === 'false' && <NodeFalse nameClick={nameClick} data={data} />}
+    </div>
+  )
+}
+
+const NodeBottom = ({
+  type,
+  className,
+  nameClick,
+  style,
+  data
+}: nodeProps) => {
+  return (
+    <div className={`${styles.nodeBottom} ${className}`} id='nodeBottom' style={style}>
+      <BottomLine />
+      {type === 'normal' && <NodeNormal nameClick={nameClick} data={data} />}
+      {type === 'active' && <NodeActive nameClick={nameClick} data={data} />}
+      {type === 'false' && <NodeFalse nameClick={nameClick} data={data} />}
+    </div>
+  )
+}
+
+const NodeTurnRight = ({
+  type,
+  className,
+  nameClick,
+  style,
+  data
+}: nodeProps) => {
+  return (
+    <div className={`${styles.nodeTurnRight} ${className}`} id='nodeTurnRight' style={style}>
+      <TurnRightLine />
+      {type === 'normal' && <NodeNormal nameClick={nameClick} data={data} />}
+      {type === 'active' && <NodeActive nameClick={nameClick} data={data} />}
+      {type === 'false' && <NodeFalse nameClick={nameClick} data={data} />}
+    </div>
+  )
+}
+
+const NodeTurnBottomRight = ({
+  type,
+  className,
+  nameClick,
+  style,
+  data
+}: nodeProps) => {
+  return (
+    <div className={`${styles.nodeTurnBottomRight} ${className}`} id='nodeTurnBottomRight' style={style}>
+      <TurnBottomRightLine />
+      {type === 'normal' && <NodeNormal nameClick={nameClick} data={data} />}
+      {type === 'active' && <NodeActive nameClick={nameClick} data={data} />}
+      {type === 'false' && <NodeFalse nameClick={nameClick} data={data} />}
+    </div>
+  )
+}
+
+export {
+
+  NodeTurnBottomRight,
+  NodeTurnRight,
+  NodeRight,
+  NodeBottom,
+  RightLine,
+  RightLineDash,
+  BottomLine,
+  TurnRightLine,
+  NodeNormal,
+  NodeActive,
+  NodeFalse,
+}

+ 131 - 0
src/pages/A6ybwx/Genealogy/index.module.scss

@@ -9,6 +9,7 @@
       width: 60px;
       height: 30px;
       position: absolute;
+      z-index: 1;
       top: 3%;
       left: 4%;
       cursor: pointer;
@@ -17,11 +18,65 @@
         object-fit: contain;
       }
     }
+    .icon1 {
+      width: 25px;
+      height: 25px;
+      position: absolute;
+      top: 4%;
+      left: 14%;
+      cursor: pointer;
+      & > img {
+        height: 100%;
+        object-fit: contain;
+      }
+    }
+    .miniGraph {
+      position: relative;
+      width: 200px;
+      height: 100px;
+      bottom: 5%;
+      left: 5%;
+    }
+
+    .hide {
+      opacity: 0;
+      pointer-events: none;
+      transition: all 0.3s ease-in-out 0.3s;
+      .sider {
+        transition: all 0.3s ease-in-out;
+        transform: translateX(-100%);
+      }
+    }
+    .show {
+      opacity: 1;
+      pointer-events: auto;
+      transition: all 0.3s ease-in-out;
+      .sider {
+        transform: translateX(0);
+        transition: all 0.3s ease-in-out 0.3s;
+      }
+    }
+  }
+}
+
+.gesture {
+  position: absolute;
+  width: 55px;
+  height: 50px;
+  top: 50%;
+  left: 56%;
+  font-size: 9px;
+  color: rgba(255, 255, 255, 1);
+  text-align: center;
+  & > img {
+    width: 100%;
+    object-fit: contain;
   }
 }
 
 .intro {
   position: fixed;
+  z-index: 3;
   top: 0;
   left: 0;
   width: 100%;
@@ -33,6 +88,7 @@
   align-items: center;
   justify-content: center;
   gap: 10px;
+  backdrop-filter: blur(3px);
   :global {
     .title {
       width: 60%;
@@ -89,3 +145,78 @@
     }
   }
 }
+
+.nodeDetail {
+  position: fixed;
+  z-index: 3;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  backdrop-filter: blur(1px);
+  :global {
+    .sider {
+      position: relative;
+      width: 40%;
+      height: 100%;
+      background: url('../../../assets/img/A6_gen_nodeDetailBg.png') no-repeat center center;
+      background-size: 100% 100%;
+      display: flex;
+      align-items: center;
+      .name {
+        writing-mode: vertical-rl;
+        text-orientation: upright; // 保持文字直立
+        display: flex;
+        align-items: center;
+        letter-spacing: 10px;
+        padding-top: 40px;
+        width: 20%;
+        height: 100%;
+        font-size: 20px;
+        color: rgba(255, 233, 182, 1);
+      }
+      .info {
+        width: 80%;
+        height: 80%;
+        padding: 0 30px;
+        display: flex;
+        flex-direction: column;
+        align-items: center;
+        gap: 10px;
+        .infoitem {
+          width: 100%;
+          height: fit-content;
+          max-height: 50%;
+          display: flex;
+          flex-direction: column;
+          gap: 6px;
+          .title {
+            width: 100%;
+            height: 20px;
+            font-size: 15px;
+            font-weight: bold;
+            color: rgba(124, 75, 54, 1);
+          }
+          .txt {
+            width: 100%;
+            height: fit-content;
+            font-size: 15px;
+            color: rgba(93, 96, 96, 1);
+          }
+        }
+      }
+      .close {
+        position: absolute;
+        bottom: 4%;
+        right: 9%;
+        cursor: pointer;
+        width: 55px;
+        height: 35px;
+        & > img {
+          height: 100%;
+          object-fit: contain;
+        }
+      }
+    }
+  }
+}

File diff suppressed because it is too large
+ 63 - 16
src/pages/A6ybwx/Genealogy/index.tsx


+ 4 - 4
src/pages/A6ybwx/index.tsx

@@ -28,7 +28,8 @@ function A6ybwx() {
       onPlay: () => { }, // 触发播放事件
       onPause: () => { }, // 触发暂停事件
       onEnded: () => {
-        setIsShowBtn(true)
+        setIsEnter(true)
+        playerRef.current.destroy()
       }, // 触发播放结束事件
       onSourceEstablished: () => {
         setVideoOk(true)
@@ -41,7 +42,7 @@ function A6ybwx() {
 
   const timeRR = useRef(-1)
   useEffect(() => {
-    if (videoOk) {
+    if (videoOk && !isEnter) {
       clearInterval(timeRR.current)
       timeRR.current = window.setInterval(() => {
         console.log('播放视频')
@@ -50,7 +51,7 @@ function A6ybwx() {
         return
       }, 50)
     }
-  }, [videoOk])
+  }, [isEnter, videoOk])
 
   return (
     <div className={styles.A6ybwx}>
@@ -61,7 +62,6 @@ function A6ybwx() {
 
         {/* ------------微信浏览器不让视频自动播放,用ts的方式*/}
         <div className='A6video' style={{ opacity: 1 }}></div>
-        {isShowBtn && <div className="enterBtn" onClick={() => { setIsEnter(true); setIsShowBtn(false) }}></div>}
         <iframe style={{ zIndex: isEnter ? 2 : -1 }} title="A6ybwx" src="https://app.4dage.com/projects/Chenzhebei-ShanxiMuseum/RoomScene/index.html" />
 
         <div className="tabBar" style={{ display: isEnter ? 'flex' : 'none' }}>

+ 10 - 0
src/types/declaration.d.ts

@@ -47,4 +47,14 @@ type MyDataType = {
   weijie: string
   // 造像中国化
   sinicizeData: { time: string; bgPath: string }[]
+  // 家族谱系
+  genealogyData: {
+    name: string
+    type: string
+    addTxt: string
+    extra: string
+    position: { x: number; y: number }
+    text1: { title: string; content: string }
+    text2: { title: string; content: string }
+  }[]
 }