|
@@ -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;
|