Просмотр исходного кода

✨ feat(supply-chain): add SupplyChainModal component with theme color support

- add new SupplyChainModal component with complete popup layer design based on Figma-JSX
- implement dynamic theme color switching for popup layer styles
- add theme color integration for border decoration, title light effect, icons and return button
- add Escape key close functionality and overlay click close support
- update component documentation and export SupplyChainModal in index.ts

📝 docs(prd): update component documentation and popup layer requirements

- add SupplyChainModal to component list in PRD documentation
- document popup layer style theme color integration requirements
- add popup layer implementation status to acceptance criteria
- update version history with popup layer theme color integration record
yourname 2 месяцев назад
Родитель
Сommit
78e6e96152

+ 16 - 1
docs/prd/epic-005-supply-chain-visualization.md

@@ -220,6 +220,7 @@
 - **`DataCard`** - 数据卡片(带渐变背景的数字展示)
 - **`PopupInfoBox`** - 弹出信息框(带箭头指示器)
 - **`KeyMetricsPanel`** - 关键指标面板
+- **`SupplyChainModal`** - 完整弹出层组件(基于Figma-JSX设计,支持主题色动态切换)
 
 #### 6. 布局容器组件 (Layout Container Components)
 - **`HeaderBar`** - 顶部标题栏背景
@@ -308,6 +309,18 @@
 - **弹出框数据**:为所有49个定位点提供了对应的弹出框数据
 - **路由支持**:所有4个组合都有对应的路由配置
 
+### 弹出层样式主题色集成
+- **弹出层设计**:基于Figma-JSX的完整弹出层组件已设计完成
+- **主题色动态切换**:弹出层样式必须使用当前产业的themeColor
+- **样式组件**:
+  - 边框装饰(Union SVG)使用当前产业主题色
+  - 标题光线效果使用当前产业主题色
+  - 图标颜色使用当前产业主题色
+  - 返回按钮样式使用当前产业主题色
+- **实现要求**:
+  - PopupInfoBox组件已支持动态主题色切换 ✅
+  - SupplyChainModal组件已实现完整弹出层,支持动态主题色切换 ✅
+
 ### 数据集成
 - **API端点**:新增供应链相关API
 - **数据模型**:创建供应链实体和关系
@@ -328,6 +341,7 @@
 - [x] 产业图标和颜色系统正确
 - [x] 数据卡片组件统一规范
 - [x] 严格遵循1920*1080分辨率
+- [x] 弹出层样式使用当前产业主题色
 
 ### 组件架构验收
 - [x] 基础组件体系完整实现(图标、文本、导航)
@@ -471,4 +485,5 @@
 | 2025-11-15 | 1.4 | **架构优化**:采用统一入口组件,通过路由参数动态加载数据 | Claude |
 | 2025-11-16 | 1.5 | **核心架构完成**:Story 005.002和005.003实现完成,统一数据Context和动态路由架构已就绪 | Claude |
 | 2025-11-16 | 1.6 | **组件数据流问题识别**:发现SupplyChainMap和KeyMetrics组件使用硬编码数据,需要修改为动态Context数据 | Claude |
-| 2025-11-16 | 1.7 | **重大发现**:故事005.005和005.006已通过组件复用架构完成,所有4套组合数据已在SupplyChainContext中完整实现 | Claude |
+| 2025-11-16 | 1.7 | **重大发现**:故事005.005和005.006已通过组件复用架构完成,所有4套组合数据已在SupplyChainContext中完整实现 | Claude |
+| 2025-11-16 | 1.8 | **弹出层主题色集成**:基于Figma-JSX文档创建SupplyChainModal组件,实现完整弹出层样式和动态主题色切换 | Claude |

+ 16 - 0
docs/stories/005.004.story.md

@@ -18,6 +18,7 @@ Completed
 2. **KeyMetrics组件动态加载关键指标数据** - 组件应从SupplyChainContext中动态加载种业和果蔬的关键指标数据,而不是使用硬编码的默认数据
 3. **组件支持产业切换** - 当用户在种业和果蔬之间切换时,组件应自动更新显示对应产业的数据
 4. **验证数据完整性** - 确保种业和果蔬的所有定位点、关键指标、供应链网络和弹出框数据都能正确显示
+5. **点击popup时弹出完整SupplyChainModal** - 当用户点击地图上的定位点时,应显示完整的SupplyChainModal弹出层,包含标题、图片和自定义内容区域
 
 ## Tasks / Subtasks
 - [x] 修改SupplyChainMap组件使用动态数据 (AC: #1, #3, #4)
@@ -49,6 +50,13 @@ Completed
     - [x] 第一个款式以右下角为定位基准
     - [x] 第二个款式以右上角为定位基准
   - [x] 验证所有组合的款式切换正确性
+- [ ] 实现点击popup时弹出完整SupplyChainModal
+  - [ ] 在SupplyChainMap组件中添加弹出层状态管理
+  - [ ] 修改定位点点击事件,支持显示完整弹出层
+  - [ ] 集成SupplyChainModal组件,传递当前定位点的数据
+  - [ ] 实现弹出层关闭功能
+  - [ ] 验证弹出层主题色正确应用
+  - [ ] 测试ESC键和遮罩层点击关闭功能
 - [x] 验证种业-果蔬组合路由功能 (AC: #3, #4)
   - [x] 测试路由`/supply-chain/seed-fruit`正确加载种业-果蔬数据
   - [x] 验证组合内产业切换功能(种业↔果蔬)
@@ -328,6 +336,9 @@ const KeyMetrics: React.FC<KeyMetricsProps> = ({ title, subtitle }) => {
 - 关键指标数据正确展示
 - 供应链网络连接线正确绘制
 - 主题色正确应用
+- 点击定位点弹出完整SupplyChainModal
+- 弹出层主题色正确应用
+- ESC键和遮罩层点击关闭功能正常
 
 ### 测试用例
 - 验证seed-fruit组合数据正确加载
@@ -337,11 +348,16 @@ const KeyMetrics: React.FC<KeyMetricsProps> = ({ title, subtitle }) => {
 - 验证关键指标数据准确性
 - 测试路由参数解析
 - 验证React Query缓存机制
+- 测试点击定位点弹出SupplyChainModal功能
+- 验证弹出层主题色正确应用
+- 测试ESC键关闭弹出层功能
+- 测试遮罩层点击关闭弹出层功能
 
 ## Change Log
 | Date | Version | Description | Author |
 |------|---------|-------------|--------|
 | 2025-11-16 | 1.0 | 初始故事创建,基于Epic 005需求 | Bob (SM) |
+| 2025-11-16 | 1.1 | 添加点击popup时弹出完整SupplyChainModal的任务和验收标准 | Claude |
 
 ## Dev Agent Record
 

+ 49 - 2
src/client/home/pages/SupplyChainDashboards/SupplyChainDashboard.tsx

@@ -6,6 +6,7 @@ import SupplyChainMap from './components/SupplyChainMap';
 import SupplyChainModel from './components/SupplyChainModel';
 import KeyMetrics from './components/KeyMetrics';
 import PopupInfoBox from './components/PopupInfoBox';
+import { SupplyChainModal } from './components';
 import Navigation from './components/layout/Navigation';
 import SupplyChainBackground from './components/layout/SupplyChainBackground';
 import BackgroundGrid from './components/layout/BackgroundGrid';
@@ -19,15 +20,25 @@ interface PopupState {
   pointId?: string;
 }
 
+// 定义完整弹出层状态
+interface ModalState {
+  isOpen: boolean;
+  title?: string;
+  imageUrl?: string;
+}
+
 // 内部组件,使用SupplyChainContext
 const DashboardContent: React.FC = () => {
   const [popupState, setPopupState] = React.useState<PopupState>({
     isVisible: false,
     position: { x: 650, y: 250 }
   });
+  const [modalState, setModalState] = React.useState<ModalState>({
+    isOpen: false
+  });
   const { currentIndustry, setIndustry, currentDashboard, currentData } = useSupplyChain();
 
-  // 处理定位点点击
+  // 处理定位点点击 - 显示小弹出框
   const handlePointClick = (point: any) => {
     console.log('点击定位点:', point);
 
@@ -35,7 +46,7 @@ const DashboardContent: React.FC = () => {
     const hasPopupData = currentData?.popupData?.[currentIndustry] && currentData.popupData[currentIndustry].length > 0;
 
     if (hasPopupData) {
-      // 显示弹出框,PopupInfoBox组件会自己从context获取数据
+      // 显示弹出框
       setPopupState({
         isVisible: true,
         position: { x: 650, y: 250 },
@@ -53,11 +64,38 @@ const DashboardContent: React.FC = () => {
     }
   };
 
+  // 处理小弹出框点击 - 显示完整弹出层
+  const handlePopupClick = (pointId: string) => {
+    console.log('点击小弹出框:', pointId);
+
+    // 根据定位点ID找到对应的弹出框数据
+    const popupDataList = currentData?.popupData?.[currentIndustry] || [];
+    const popupId = pointId.replace(/(base|chain)/, 'popup-$1');
+    const popupData = popupDataList.find(popup => popup.id === popupId);
+
+    if (popupData) {
+      // 显示完整SupplyChainModal
+      setModalState({
+        isOpen: true,
+        title: popupData.title,
+        imageUrl: "https://placehold.co/960x640" // 默认图片,后续可以根据数据动态设置
+      });
+
+      // 关闭小弹出框
+      setPopupState(prev => ({ ...prev, isVisible: false }));
+    }
+  };
+
   // 关闭弹出框
   const handleClosePopup = () => {
     setPopupState(prev => ({ ...prev, isVisible: false }));
   };
 
+  // 关闭完整弹出层
+  const handleCloseModal = () => {
+    setModalState(prev => ({ ...prev, isOpen: false }));
+  };
+
   return (
     <div className="h-[1080px] w-[1920px] bg-[#0a1a3a] relative overflow-hidden">
       {/* 背景 */}
@@ -93,9 +131,18 @@ const DashboardContent: React.FC = () => {
         <PopupInfoBox
           position={popupState.position}
           onClose={handleClosePopup}
+          onPopupClick={handlePopupClick}
           pointId={popupState.pointId}
         />
       )}
+
+      {/* 完整弹出层 */}
+      <SupplyChainModal
+        isOpen={modalState.isOpen}
+        onClose={handleCloseModal}
+        title={modalState.title}
+        imageUrl={modalState.imageUrl}
+      />
     </div>
   );
 };

+ 11 - 1
src/client/home/pages/SupplyChainDashboards/components/PopupInfoBox.tsx

@@ -20,6 +20,7 @@ interface PopupData {
 interface PopupInfoBoxProps {
   position?: { x: number; y: number };
   onClose?: () => void;
+  onPopupClick?: (pointId: string) => void; // 新增:点击弹出框的回调
   pointId?: string;
   variant?: 'first' | 'second'; // 款式选择:first-第一款,second-第二款
   industry?: string; // 产业类型,用于确定款式
@@ -28,6 +29,7 @@ interface PopupInfoBoxProps {
 const PopupInfoBox: React.FC<PopupInfoBoxProps> = ({
   position = { x: 717.28, y: 273.13 },
   onClose,
+  onPopupClick,
   pointId,
   variant
 }) => {
@@ -93,15 +95,23 @@ const PopupInfoBox: React.FC<PopupInfoBoxProps> = ({
     return null; // 其他情况返回null,使用默认值
   }
 
+  // 处理弹出框点击
+  const handlePopupClick = () => {
+    if (onPopupClick && pointId) {
+      onPopupClick(pointId);
+    }
+  };
+
   return (
     <div
-      className="absolute overflow-clip"
+      className="absolute overflow-clip cursor-pointer"
       style={{
         left: `${finalPosition.x}px`,
         top: `${finalPosition.y}px`,
         width: '573px',
         height: '320px'
       }}
+      onClick={handlePopupClick}
     >
 
       {/* 弹出框主体 */}

+ 214 - 0
src/client/home/pages/SupplyChainDashboards/components/SupplyChainModal.tsx

@@ -0,0 +1,214 @@
+import React from 'react';
+import { useSupplyChain } from '../context/SupplyChainContext';
+
+interface SupplyChainModalProps {
+  isOpen: boolean;
+  onClose: () => void;
+  title?: string;
+  imageUrl?: string;
+  children?: React.ReactNode;
+}
+
+const SupplyChainModal: React.FC<SupplyChainModalProps> = ({
+  isOpen,
+  onClose,
+  title = "江汉大米优质水稻种植核心示范基地",
+  imageUrl = "https://placehold.co/960x640",
+  children
+}) => {
+  const { themeColor } = useSupplyChain();
+
+  if (!isOpen) return null;
+
+  // ESC键关闭弹窗
+  React.useEffect(() => {
+    const handleEsc = (event: KeyboardEvent) => {
+      if (event.key === 'Escape') {
+        onClose();
+      }
+    };
+
+    if (isOpen) {
+      document.addEventListener('keydown', handleEsc);
+      return () => document.removeEventListener('keydown', handleEsc);
+    }
+  }, [isOpen, onClose]);
+
+  return (
+    <div
+      data-layer="操控屏-1-粮食•油脂-粮食首页-基地弹出数据"
+      className="fixed w-[1920px] h-[1080px] left-0 top-0 z-50 flex items-center justify-center"
+    >
+      {/* 遮罩层 */}
+      <div
+        data-layer="遮罩层"
+        className="absolute inset-0 bg-stone-800/50 backdrop-blur-[5px]"
+        onClick={onClose}
+      />
+
+      {/* 弹出框样式1 */}
+      <div data-layer="弹出框样式1" className="relative w-[1600px] h-[900px] bg-stone-800/40">
+        {/* 边框装饰 - Union SVG */}
+        <div data-svg-wrapper data-layer="Union" className="absolute left-[21.96px] top-[29.88px] pointer-events-none">
+          <svg width="1557" height="841" viewBox="0 0 1557 841" fill="none" xmlns="http://www.w3.org/2000/svg">
+            <g opacity="0.5">
+              <path d="M10.9804 837.02H1545.1V496.309H1548.24V840.238H7.84314V496.309H10.9804V837.02Z" fill={themeColor}/>
+              <path d="M18.8235 485.721L0 497.91V281.743L18.8235 269.696V485.721Z" fill={themeColor}/>
+              <path d="M1556.08 281.884V497.91L1537.25 485.863V269.696L1556.08 281.884Z" fill={themeColor}/>
+              <path d="M428.474 19.2889H395.637L399.084 9.58322H10.9804V269.94H7.84314V6.36579H400.227L402.488 0H436.422L428.474 19.2889Z" fill={themeColor}/>
+              <path d="M1157.82 6.36579H1548.24V269.94H1545.1V9.58322H1156.49L1152.49 19.2889H1119.66L1126.51 0H1160.44L1157.82 6.36579Z" fill={themeColor}/>
+            </g>
+          </svg>
+        </div>
+
+        {/* 删除icon */}
+        <button
+          data-svg-wrapper data-layer="删除icon"
+          className="absolute left-[1731px] top-[80px] cursor-pointer hover:opacity-80 transition-opacity"
+          onClick={onClose}
+        >
+          <svg width="41" height="41" viewBox="0 0 41 41" fill="none" xmlns="http://www.w3.org/2000/svg">
+            <g filter="url(#filter0_i_2019_17214)">
+              <circle cx="20.0476" cy="20.0476" r="19.0476" fill={themeColor} fillOpacity="0.09"/>
+            </g>
+            <circle cx="20.0476" cy="20.0476" r="19.5476" stroke={themeColor}/>
+            <g filter="url(#filter1_d_2019_17214)">
+              <path d="M14.2929 12.5142C14.151 12.5094 14.0129 12.5612 13.9092 12.6579L13.1261 13.3862C12.9107 13.5873 12.8993 13.9242 13.1005 14.1395L18.6218 20.0477L13.1005 25.955C13.0036 26.0584 12.9515 26.1962 12.9564 26.3377C12.9612 26.4792 13.0223 26.6131 13.1261 26.7097L13.9092 27.4375C14.013 27.5341 14.1514 27.5857 14.2933 27.5807C14.4351 27.5757 14.5691 27.5146 14.6658 27.411L20.0485 21.6521L25.4308 27.411C25.5275 27.5146 25.6615 27.5757 25.8033 27.5807C25.9452 27.5857 26.0835 27.5341 26.1874 27.4375L26.9705 26.7097C27.0743 26.6131 27.1354 26.4792 27.1402 26.3377C27.1451 26.1962 27.093 26.0584 26.9961 25.955L21.4743 20.0477L26.9961 14.1395C27.1973 13.9243 27.1858 13.5873 26.9705 13.3862L26.1874 12.6579C26.0837 12.5612 25.9456 12.5095 25.8037 12.5142C25.6619 12.519 25.5277 12.58 25.4308 12.6835L20.0485 18.441L14.6658 12.6835C14.5689 12.58 14.4347 12.519 14.2929 12.5142Z" fill={themeColor}/>
+            </g>
+            <defs>
+              <filter id="filter0_i_2019_17214" x="0" y="0" width="40.0957" height="40.0952" filterUnits="userSpaceOnUse" colorInterpolationFilters="sRGB">
+                <feFlood floodOpacity="0" result="BackgroundImageFix"/>
+                <feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
+                <feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
+                <feOffset/>
+                <feGaussianBlur stdDeviation="6.5"/>
+                <feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
+                <feColorMatrix type="matrix" values="0 0 0 0 0.788235 0 0 0 0 0.866667 0 0 0 0 1 0 0 0 0.5 0"/>
+                <feBlend mode="normal" in2="shape" result="effect1_innerShadow_2019_17214"/>
+              </filter>
+              <filter id="filter1_d_2019_17214" x="3.95605" y="3.51392" width="32.1846" height="33.0671" filterUnits="userSpaceOnUse" colorInterpolationFilters="sRGB">
+                <feFlood floodOpacity="0" result="BackgroundImageFix"/>
+                <feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
+                <feOffset/>
+                <feGaussianBlur stdDeviation="4.5"/>
+                <feColorMatrix type="matrix" values="0 0 0 0 0.788235 0 0 0 0 0.866667 0 0 0 0 1 0 0 0 0.501961 0"/>
+                <feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_2019_17214"/>
+                <feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_2019_17214" result="shape"/>
+              </filter>
+            </defs>
+          </svg>
+        </button>
+
+        {/* 图片样式1 */}
+        <div data-layer="图片样式1" className="absolute w-[1357px] h-[640px] left-[281.50px] top-[268.72px] rounded-[20px] overflow-hidden">
+          <div data-layer="Frame 1321316691" className="absolute left-[199px] top-0 inline-flex justify-start items-center gap-5">
+            <div data-layer="图片1" className="w-[960px] h-[640px] relative rounded-[10px] overflow-hidden">
+              <img
+                data-layer="省储备粮咸宁库 1"
+                className="w-[959.88px] h-[640px] left-[0.06px] top-0 absolute object-cover"
+                src={imageUrl}
+                alt={title}
+              />
+            </div>
+          </div>
+        </div>
+
+        {/* 标题 */}
+        <div data-layer="标题" className="absolute w-[1200px] h-32 left-[360px] top-[123.22px] inline-flex justify-center items-center gap-7">
+          {/* 标题元素 - 光线左侧 */}
+          <div data-svg-wrapper data-layer="标题元素" data-property-1="光线左侧" className="relative">
+            <svg width="82" height="22" viewBox="0 0 82 22" fill="none" xmlns="http://www.w3.org/2000/svg">
+              <g filter="url(#filter0_d_2019_17227)">
+                <path fillRule="evenodd" clipRule="evenodd" d="M9 11C9 11 69.8954 9 71 9C72.1046 9 73 9.89543 73 11C73 12.1046 72.1046 13 71 13C69.8954 13 9 11 9 11Z" fill="url(#paint0_linear_2019_17227)"/>
+              </g>
+              <defs>
+                <filter id="filter0_d_2019_17227" x="0" y="0" width="82" height="22" filterUnits="userSpaceOnUse" colorInterpolationFilters="sRGB">
+                  <feFlood floodOpacity="0" result="BackgroundImageFix"/>
+                  <feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
+                  <feOffset/>
+                  <feGaussianBlur stdDeviation="4.5"/>
+                  <feColorMatrix type="matrix" values="0 0 0 0 0.426042 0 0 0 0 0.697115 0 0 0 0 0.187685 0 0 0 1 0"/>
+                  <feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_2019_17227"/>
+                  <feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_2019_17227" result="shape"/>
+                </filter>
+                <linearGradient id="paint0_linear_2019_17227" x1="9" y1="13" x2="73" y2="13" gradientUnits="userSpaceOnUse">
+                  <stop stopColor={themeColor} stopOpacity="0.01"/>
+                  <stop offset="0.643405" stopColor={themeColor}/>
+                  <stop offset="1" stopColor={themeColor}/>
+                </linearGradient>
+              </defs>
+            </svg>
+          </div>
+
+          {/* 标题文本 */}
+          <div data-layer="text" data-位置="居中对齐" data-字号="30px" data-字重="特粗" className="py-1 flex justify-center items-center gap-2.5">
+            <div className="text-center justify-center text-white text-4xl font-bold font-['HarmonyOS_Sans_SC'] leading-8 tracking-[8px]">
+              {title}
+            </div>
+          </div>
+
+          {/* 标题元素 - 光线右侧 */}
+          <div data-svg-wrapper data-layer="标题元素" data-property-1="光线右侧" className="relative">
+            <svg width="82" height="22" viewBox="0 0 82 22" fill="none" xmlns="http://www.w3.org/2000/svg">
+              <g filter="url(#filter0_d_2019_17231)">
+                <path fillRule="evenodd" clipRule="evenodd" d="M73 11C73 11 12.1046 9 11 9C9.89542 9 9 9.89543 9 11C9 12.1046 9.89542 13 11 13C12.1046 13 73 11 73 11Z" fill="url(#paint0_linear_2019_17231)"/>
+              </g>
+              <defs>
+                <filter id="filter0_d_2019_17231" x="0" y="0" width="82" height="22" filterUnits="userSpaceOnUse" colorInterpolationFilters="sRGB">
+                  <feFlood floodOpacity="0" result="BackgroundImageFix"/>
+                  <feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
+                  <feOffset/>
+                  <feGaussianBlur stdDeviation="4.5"/>
+                  <feColorMatrix type="matrix" values="0 0 0 0 0.426042 0 0 0 0 0.697115 0 0 0 0 0.187685 0 0 0 1 0"/>
+                  <feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_2019_17231"/>
+                  <feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_2019_17231" result="shape"/>
+                </filter>
+                <linearGradient id="paint0_linear_2019_17231" x1="73" y1="9" x2="9" y2="9" gradientUnits="userSpaceOnUse">
+                  <stop stopColor={themeColor} stopOpacity="0.01"/>
+                  <stop offset="0.643405" stopColor={themeColor} stopOpacity="0.372549"/>
+                  <stop offset="1" stopColor={themeColor}/>
+                </linearGradient>
+              </defs>
+            </svg>
+          </div>
+        </div>
+
+        {/* 返回icon */}
+        <button
+          data-layer="返回icon"
+          className="absolute left-[1860px] top-[990px] size-24 cursor-pointer hover:opacity-80 transition-opacity origin-top-left rotate-180"
+          onClick={onClose}
+        >
+          <div data-layer="Ellipse 4119" className="absolute left-[87.86px] top-[87.86px] size-20 origin-top-left rotate-180 bg-blue-200/10 rounded-full shadow-[inset_0px_0px_13px_0px_rgba(201,221,255,0.50)] outline outline-1 outline-blue-200" />
+          <div data-svg-wrapper data-layer="路径" className="absolute left-[35.53px] top-[28.05px]">
+            <svg width="37" height="52" viewBox="0 0 37 52" fill="none" xmlns="http://www.w3.org/2000/svg">
+              <g filter="url(#filter0_d_2019_17235)">
+                <path fillRule="evenodd" clipRule="evenodd" d="M24.9389 42.9001C25.2582 42.9114 25.5689 42.7956 25.8025 42.5782L27.5649 40.9403C27.7984 40.7228 27.9357 40.4217 27.9466 40.1032C27.9574 39.7847 27.8409 39.475 27.6227 39.2422L15.199 25.9511L27.6227 12.6576C28.0755 12.1732 28.0496 11.4146 27.5649 10.962L25.8025 9.32397C25.5692 9.10633 25.2587 8.99009 24.9394 9.0009C24.6201 9.01171 24.3183 9.14867 24.1003 9.3816L10.1959 24.2555L9.38209 25.012C9.12173 25.2542 8.98239 25.599 9.00166 25.9535C8.98385 26.3064 9.12306 26.6492 9.38209 26.8902L10.2007 27.6467L24.1003 42.5182C24.3179 42.7515 24.6196 42.8889 24.9389 42.9001Z" fill={themeColor}/>
+              </g>
+              <defs>
+                <filter id="filter0_d_2019_17235" x="0" y="0" width="36.9473" height="51.9009" filterUnits="userSpaceOnUse" colorInterpolationFilters="sRGB">
+                  <feFlood floodOpacity="0" result="BackgroundImageFix"/>
+                  <feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
+                  <feOffset/>
+                  <feGaussianBlur stdDeviation="4.5"/>
+                  <feColorMatrix type="matrix" values="0 0 0 0 0.788235 0 0 0 0 0.866667 0 0 0 0 1 0 0 0 0.501961 0"/>
+                  <feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_2019_17235"/>
+                  <feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_2019_17235" result="shape"/>
+                </filter>
+              </defs>
+            </svg>
+          </div>
+        </button>
+
+        {/* 自定义内容区域 */}
+        {children && (
+          <div className="absolute left-[281.50px] top-[268.72px] w-[1357px] h-[640px] rounded-[20px] overflow-hidden">
+            {children}
+          </div>
+        )}
+      </div>
+    </div>
+  );
+};
+
+export default SupplyChainModal;

+ 4 - 1
src/client/home/pages/SupplyChainDashboards/components/index.ts

@@ -13,4 +13,7 @@ export * from './layout';
 // export * from './map';
 
 // 数据组件 (待实现)
-// export * from './data';
+// export * from './data';
+
+// 弹出层组件
+export { default as SupplyChainModal } from './SupplyChainModal';