Sfoglia il codice sorgente

✨ feat(supply-chain): 重构供应链地图组件

- 提取地图相关代码到SupplyChainMap组件,优化代码结构
- 定义LocationPoint和ConnectionLine接口,规范数据结构
- 实现BasePoint和IndustryChainPoint子组件,封装定位点逻辑
- 添加默认定位点和连接线数据,支持自定义数据传入
- 实现连接线动态渲染功能,根据定位点位置计算连接线角度和长度
- 添加定位点点击事件回调,支持外部处理点击逻辑
- 优化定位点样式,添加hover缩放效果和名称显示
yourname 2 mesi fa
parent
commit
f3ba43aa6c

+ 7 - 87
src/client/home/pages/SupplyChainDashboards/GrainOilDashboard.tsx

@@ -1,4 +1,5 @@
 import React, { useState } from 'react';
+import SupplyChainMap from './components/SupplyChainMap';
 
 // 定义组件接口
 interface GrainOilDashboardProps {
@@ -223,93 +224,12 @@ const GrainOilDashboard: React.FC<GrainOilDashboardProps> = () => {
       <Navigation activeTab={activeTab} onTabChange={setActiveTab} />
 
       {/* 中间地图区域 */}
-      <div className="absolute contents left-[529.88px] top-[calc(50%+64.03px)] translate-y-[-50%]">
-        <div className="absolute contents left-[27.6%] right-[4.17%] top-[calc(50%+33.79px)] translate-y-[-50%]">
-          {/* 地图边框 */}
-          <div className="absolute h-[731.454px] left-[27.6%] right-[4.17%] top-[calc(50%+33.79px)] translate-y-[-50%]">
-            <div className="absolute inset-0">
-              <img
-                alt="地图边框"
-                className="block max-w-none size-full"
-                src="https://www.figma.com/api/mcp/asset/fca4f7a9-10d7-41df-8b54-87e52233ff24"
-              />
-            </div>
-          </div>
-
-          {/* 供应链网络连接线 */}
-          <div className="absolute inset-[19.27%_4.17%_13.01%_27.6%]">
-            {/* 这里可以添加供应链网络连接线的SVG或图片 */}
-            <img
-              alt="供应链网络"
-              className="block max-w-none size-full"
-              src="https://www.figma.com/api/mcp/asset/480a665e-4639-4757-b0f5-ae8c1d4e7f17"
-            />
-          </div>
-
-          {/* 定位点 - 基地 */}
-          <div className="absolute h-[48px] left-[1142.89px] top-[717.84px] w-[40px]">
-            <div className="absolute inset-[12.5%_8.66%]">
-              <img
-                alt="基地定位点"
-                className="block max-w-none size-full"
-                src="https://www.figma.com/api/mcp/asset/05a7ea4e-ca3d-42b4-a448-9693541b81c1"
-              />
-            </div>
-            <GrainIcon className="absolute inset-[27.5%_23%] overflow-clip" />
-          </div>
-
-          {/* 定位点 - 产业链 */}
-          <div className="absolute h-[48px] left-[1273.12px] top-[551.14px] w-[40px]">
-            <div className="absolute inset-[10%_13.79%]">
-              <img
-                alt="产业链定位点"
-                className="block max-w-none size-full"
-                src="https://www.figma.com/api/mcp/asset/b3a56933-87f3-4183-812c-82694fe92db1"
-              />
-            </div>
-            <IndustryChainIcon className="absolute bottom-[40.42%] left-1/4 overflow-clip right-1/4 shadow-[0px_0px_6px_0px_#4593ff] top-[17.92%]" />
-          </div>
-
-          {/* 图例 */}
-          <div className="absolute bottom-[80px] content-stretch flex items-center left-[calc(50%+224.94px)] translate-x-[-50%]">
-            <div className="box-border content-stretch flex gap-[20px] h-[54px] items-center justify-center px-[20px] py-0 relative rounded-[10px] shrink-0 w-[202px]">
-              <div className="h-[50px] relative shrink-0 w-[41.667px]">
-                <div className="absolute inset-[12.5%_8.66%]">
-                  <img
-                    alt="基地图例"
-                    className="block max-w-none size-full"
-                    src="https://www.figma.com/api/mcp/asset/70b2be21-698f-43c3-8ca1-e70d375e55c3"
-                  />
-                </div>
-                <GrainIcon className="absolute inset-[27.5%_23%] overflow-clip" />
-              </div>
-              <div className="box-border content-stretch flex gap-[10px] items-center px-0 py-[4px] relative shrink-0">
-                <div className="flex flex-col font-bold justify-center leading-[0] not-italic relative shrink-0 text-[26px] text-white whitespace-nowrap">
-                  <p className="leading-[32px]">基地</p>
-                </div>
-              </div>
-            </div>
-
-            <div className="box-border content-stretch flex gap-[20px] h-[54px] items-center justify-center px-[20px] py-0 relative rounded-[10px] shrink-0 w-[202px]">
-              <div className="h-[50px] relative shrink-0 w-[41.667px]">
-                <div className="absolute inset-[10%_13.79%]">
-                  <img
-                    alt="产业链图例"
-                    className="block max-w-none size-full"
-                    src="https://www.figma.com/api/mcp/asset/4b244cde-2253-4616-8046-a642a889b316"
-                  />
-                </div>
-                <IndustryChainIcon className="absolute bottom-[40.42%] left-1/4 overflow-clip right-1/4 shadow-[0px_0px_6px_0px_#4593ff] top-[17.92%]" />
-              </div>
-              <div className="box-border content-stretch flex gap-[10px] items-center px-0 py-[4px] relative shrink-0">
-                <div className="flex flex-col font-bold justify-center leading-[0] not-italic relative shrink-0 text-[26px] text-white whitespace-nowrap">
-                  <p className="leading-[32px]">产业链</p>
-                </div>
-              </div>
-            </div>
-          </div>
-        </div>
-      </div>
+      <SupplyChainMap
+        onPointClick={(point) => {
+          console.log('点击定位点:', point);
+          // 这里可以添加点击定位点的处理逻辑
+        }}
+      />
 
       {/* 右侧供应链合作模式 */}
       <div className="absolute content-stretch flex flex-col gap-[10px] items-end right-[80px] top-[150px]">

+ 290 - 0
src/client/home/pages/SupplyChainDashboards/components/SupplyChainMap.tsx

@@ -0,0 +1,290 @@
+import React from 'react';
+
+// 定义定位点类型
+interface LocationPoint {
+  id: string;
+  type: 'base' | 'industryChain';
+  position: { x: number; y: number };
+  name?: string;
+}
+
+// 定义连接线类型
+interface ConnectionLine {
+  id: string;
+  from: string;
+  to: string;
+}
+
+interface SupplyChainMapProps {
+  points?: LocationPoint[];
+  connections?: ConnectionLine[];
+  onPointClick?: (point: LocationPoint) => void;
+}
+
+// 基地定位点组件
+const BasePoint: React.FC<{
+  point: LocationPoint;
+  onClick?: (point: LocationPoint) => void;
+}> = ({ point, onClick }) => {
+  return (
+    <div
+      className="absolute cursor-pointer transition-transform hover:scale-110"
+      style={{
+        left: `${point.position.x}px`,
+        top: `${point.position.y}px`,
+        width: '40px',
+        height: '48px'
+      }}
+      onClick={() => onClick?.(point)}
+    >
+      <div className="absolute inset-[12.5%_8.66%]">
+        <img
+          alt="基地定位点"
+          className="block max-w-none size-full"
+          src="https://www.figma.com/api/mcp/asset/05a7ea4e-ca3d-42b4-a448-9693541b81c1"
+        />
+      </div>
+      <div className="absolute inset-[27.5%_23%] overflow-clip">
+        <img
+          alt="粮食图标"
+          className="block max-w-none size-full"
+          src="https://www.figma.com/api/mcp/asset/74aa7038-2d6c-44fa-8671-4472845f5469"
+        />
+      </div>
+      {point.name && (
+        <div className="absolute -bottom-8 left-1/2 transform -translate-x-1/2 text-white text-sm font-bold whitespace-nowrap">
+          {point.name}
+        </div>
+      )}
+    </div>
+  );
+};
+
+// 产业链定位点组件
+const IndustryChainPoint: React.FC<{
+  point: LocationPoint;
+  onClick?: (point: LocationPoint) => void;
+}> = ({ point, onClick }) => {
+  return (
+    <div
+      className="absolute cursor-pointer transition-transform hover:scale-110"
+      style={{
+        left: `${point.position.x}px`,
+        top: `${point.position.y}px`,
+        width: '40px',
+        height: '48px'
+      }}
+      onClick={() => onClick?.(point)}
+    >
+      <div className="absolute inset-[10%_13.79%]">
+        <img
+          alt="产业链定位点"
+          className="block max-w-none size-full"
+          src="https://www.figma.com/api/mcp/asset/b3a56933-87f3-4183-812c-82694fe92db1"
+        />
+      </div>
+      <div className="absolute bottom-[40.42%] left-1/4 overflow-clip right-1/4 shadow-[0px_0px_6px_0px_#4593ff] top-[17.92%]">
+        <img
+          alt="产业链图标"
+          className="block max-w-none size-full"
+          src="https://www.figma.com/api/mcp/asset/2388ddbc-149e-4662-89ae-e9df0ed9c546"
+        />
+      </div>
+      {point.name && (
+        <div className="absolute -bottom-8 left-1/2 transform -translate-x-1/2 text-white text-sm font-bold whitespace-nowrap">
+          {point.name}
+        </div>
+      )}
+    </div>
+  );
+};
+
+// 连接线组件
+const ConnectionLine: React.FC<{
+  line: ConnectionLine;
+  fromPoint: LocationPoint;
+  toPoint: LocationPoint;
+}> = ({ line, fromPoint, toPoint }) => {
+  // 计算连接线的位置和角度
+  const dx = toPoint.position.x - fromPoint.position.x;
+  const dy = toPoint.position.y - fromPoint.position.y;
+  const length = Math.sqrt(dx * dx + dy * dy);
+  const angle = Math.atan2(dy, dx) * 180 / Math.PI;
+
+  return (
+    <div
+      className="absolute pointer-events-none"
+      style={{
+        left: `${fromPoint.position.x + 20}px`,
+        top: `${fromPoint.position.y + 24}px`,
+        width: `${length}px`,
+        height: '2px',
+        transform: `rotate(${angle}deg)`,
+        transformOrigin: '0 0'
+      }}
+    >
+      <div className="w-full h-full bg-gradient-to-r from-blue-400 to-cyan-400 shadow-[0_0_4px_0_rgba(0,255,255,0.6)]" />
+    </div>
+  );
+};
+
+// 地图图例组件
+const MapLegend: React.FC = () => {
+  return (
+    <div className="absolute bottom-[80px] content-stretch flex items-center left-[calc(50%+224.94px)] translate-x-[-50%]">
+      {/* 基地图例 */}
+      <div className="box-border content-stretch flex gap-[20px] h-[54px] items-center justify-center px-[20px] py-0 relative rounded-[10px] shrink-0 w-[202px]">
+        <div className="h-[50px] relative shrink-0 w-[41.667px]">
+          <div className="absolute inset-[12.5%_8.66%]">
+            <img
+              alt="基地图例"
+              className="block max-w-none size-full"
+              src="https://www.figma.com/api/mcp/asset/70b2be21-698f-43c3-8ca1-e70d375e55c3"
+            />
+          </div>
+          <div className="absolute inset-[27.5%_23%] overflow-clip">
+            <img
+              alt="粮食图标"
+              className="block max-w-none size-full"
+              src="https://www.figma.com/api/mcp/asset/74aa7038-2d6c-44fa-8671-4472845f5469"
+            />
+          </div>
+        </div>
+        <div className="box-border content-stretch flex gap-[10px] items-center px-0 py-[4px] relative shrink-0">
+          <div className="flex flex-col font-bold justify-center leading-[0] not-italic relative shrink-0 text-[26px] text-white whitespace-nowrap">
+            <p className="leading-[32px]">基地</p>
+          </div>
+        </div>
+      </div>
+
+      {/* 产业链图例 */}
+      <div className="box-border content-stretch flex gap-[20px] h-[54px] items-center justify-center px-[20px] py-0 relative rounded-[10px] shrink-0 w-[202px]">
+        <div className="h-[50px] relative shrink-0 w-[41.667px]">
+          <div className="absolute inset-[10%_13.79%]">
+            <img
+              alt="产业链图例"
+              className="block max-w-none size-full"
+              src="https://www.figma.com/api/mcp/asset/4b244cde-2253-4616-8046-a642a889b316"
+            />
+          </div>
+          <div className="absolute bottom-[40.42%] left-1/4 overflow-clip right-1/4 shadow-[0px_0px_6px_0px_#4593ff] top-[17.92%]">
+            <img
+              alt="产业链图标"
+              className="block max-w-none size-full"
+              src="https://www.figma.com/api/mcp/asset/2388ddbc-149e-4662-89ae-e9df0ed9c546"
+            />
+          </div>
+        </div>
+        <div className="box-border content-stretch flex gap-[10px] items-center px-0 py-[4px] relative shrink-0">
+          <div className="flex flex-col font-bold justify-center leading-[0] not-italic relative shrink-0 text-[26px] text-white whitespace-nowrap">
+            <p className="leading-[32px]">产业链</p>
+          </div>
+        </div>
+      </div>
+    </div>
+  );
+};
+
+const SupplyChainMap: React.FC<SupplyChainMapProps> = ({
+  points = [],
+  connections = [],
+  onPointClick
+}) => {
+  // 默认定位点数据
+  const defaultPoints: LocationPoint[] = [
+    { id: 'base1', type: 'base', position: { x: 1142.89, y: 717.84 }, name: '基地1' },
+    { id: 'base2', type: 'base', position: { x: 1664.25, y: 530.82 }, name: '基地2' },
+    { id: 'base3', type: 'base', position: { x: 1435, y: 527.14 }, name: '基地3' },
+    { id: 'base4', type: 'base', position: { x: 1203.07, y: 514.31 }, name: '基地4' },
+    { id: 'chain1', type: 'industryChain', position: { x: 1273.12, y: 551.14 }, name: '产业链1' },
+    { id: 'chain2', type: 'industryChain', position: { x: 1403, y: 597.5 }, name: '产业链2' },
+    { id: 'chain3', type: 'industryChain', position: { x: 1694.25, y: 645.5 }, name: '产业链3' },
+    { id: 'chain4', type: 'industryChain', position: { x: 1237.87, y: 761.84 }, name: '产业链4' }
+  ];
+
+  // 默认连接线数据
+  const defaultConnections: ConnectionLine[] = [
+    { id: 'conn1', from: 'base1', to: 'chain1' },
+    { id: 'conn2', from: 'base2', to: 'chain2' },
+    { id: 'conn3', from: 'base3', to: 'chain3' },
+    { id: 'conn4', from: 'base4', to: 'chain4' },
+    { id: 'conn5', from: 'chain1', to: 'chain2' },
+    { id: 'conn6', from: 'chain2', to: 'chain3' }
+  ];
+
+  const displayPoints = points.length > 0 ? points : defaultPoints;
+  const displayConnections = connections.length > 0 ? connections : defaultConnections;
+
+  // 创建点映射以便快速查找
+  const pointMap = new Map(displayPoints.map(point => [point.id, point]));
+
+  return (
+    <div className="absolute contents left-[529.88px] top-[calc(50%+64.03px)] translate-y-[-50%]">
+      <div className="absolute contents left-[27.6%] right-[4.17%] top-[calc(50%+33.79px)] translate-y-[-50%]">
+        {/* 地图边框 */}
+        <div className="absolute h-[731.454px] left-[27.6%] right-[4.17%] top-[calc(50%+33.79px)] translate-y-[-50%]">
+          <div className="absolute inset-0">
+            <img
+              alt="地图边框"
+              className="block max-w-none size-full"
+              src="https://www.figma.com/api/mcp/asset/fca4f7a9-10d7-41df-8b54-87e52233ff24"
+            />
+          </div>
+        </div>
+
+        {/* 供应链网络连接线 */}
+        <div className="absolute inset-[19.27%_4.17%_13.01%_27.6%]">
+          {/* 静态连接线背景 */}
+          <img
+            alt="供应链网络"
+            className="block max-w-none size-full"
+            src="https://www.figma.com/api/mcp/asset/480a665e-4639-4757-b0f5-ae8c1d4e7f17"
+          />
+
+          {/* 动态连接线 */}
+          {displayConnections.map(connection => {
+            const fromPoint = pointMap.get(connection.from);
+            const toPoint = pointMap.get(connection.to);
+
+            if (!fromPoint || !toPoint) return null;
+
+            return (
+              <ConnectionLine
+                key={connection.id}
+                line={connection}
+                fromPoint={fromPoint}
+                toPoint={toPoint}
+              />
+            );
+          })}
+        </div>
+
+        {/* 定位点 */}
+        {displayPoints.map(point => {
+          if (point.type === 'base') {
+            return (
+              <BasePoint
+                key={point.id}
+                point={point}
+                onClick={onPointClick}
+              />
+            );
+          } else {
+            return (
+              <IndustryChainPoint
+                key={point.id}
+                point={point}
+                onClick={onPointClick}
+              />
+            );
+          }
+        })}
+
+        {/* 图例 */}
+        <MapLegend />
+      </div>
+    </div>
+  );
+};
+
+export default SupplyChainMap;