|
@@ -0,0 +1,638 @@
|
|
|
|
|
+import React, { createContext, useContext, useState, ReactNode, useEffect } from 'react';
|
|
|
|
|
+import { useQuery, useQueryClient } from '@tanstack/react-query';
|
|
|
|
|
+
|
|
|
|
|
+// 继承自现有ThemeContext的产业类型
|
|
|
|
|
+export type IndustryType = "粮食" | "油脂" | "种业" | "果蔬" | "畜牧" | "水产" | "鲜食" | "泛盐";
|
|
|
|
|
+
|
|
|
|
|
+// 组合类型定义
|
|
|
|
|
+export type DashboardType = 'grain-oil' | 'seed-fruit' | 'livestock-aquaculture' | 'fresh-food-salt';
|
|
|
|
|
+
|
|
|
|
|
+// 产业主题色映射
|
|
|
|
|
+const industryColors: Record<IndustryType, string> = {
|
|
|
|
|
+ "粮食": "#C5FF92",
|
|
|
|
|
+ "油脂": "#f6b02e",
|
|
|
|
|
+ "种业": "#5def8b",
|
|
|
|
|
+ "果蔬": "#fff586",
|
|
|
|
|
+ "畜牧": "#f593a5",
|
|
|
|
|
+ "水产": "#6be9ff",
|
|
|
|
|
+ "鲜食": "#de7e66",
|
|
|
|
|
+ "泛盐": "#92a5fe"
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+// 定位点坐标接口
|
|
|
|
|
+interface LocationPoint {
|
|
|
|
|
+ id: string;
|
|
|
|
|
+ type: 'base' | 'chain';
|
|
|
|
|
+ x: number;
|
|
|
|
|
+ y: number;
|
|
|
|
|
+ industry: IndustryType;
|
|
|
|
|
+ name?: string;
|
|
|
|
|
+ data?: Record<string, any>;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 关键指标数据接口
|
|
|
|
|
+interface MetricData {
|
|
|
|
|
+ id: string;
|
|
|
|
|
+ label: string;
|
|
|
|
|
+ value: string | number;
|
|
|
|
|
+ unit?: string;
|
|
|
|
|
+ industry: IndustryType;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 供应链网络数据接口
|
|
|
|
|
+interface SupplyChainNetwork {
|
|
|
|
|
+ connections: Array<{
|
|
|
|
|
+ from: string;
|
|
|
|
|
+ to: string;
|
|
|
|
|
+ type: string;
|
|
|
|
|
+ }>;
|
|
|
|
|
+ nodes: Record<string, LocationPoint>;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 弹出框数据接口
|
|
|
|
|
+interface PopupData {
|
|
|
|
|
+ id: string;
|
|
|
|
|
+ title: string;
|
|
|
|
|
+ content: string;
|
|
|
|
|
+ position: { x: number; y: number };
|
|
|
|
|
+ industry: IndustryType;
|
|
|
|
|
+ metrics?: Array<{
|
|
|
|
|
+ label: string;
|
|
|
|
|
+ value: string;
|
|
|
|
|
+ unit: string;
|
|
|
|
|
+ }>;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 供应链数据接口
|
|
|
|
|
+interface SupplyChainData {
|
|
|
|
|
+ // 组合名称
|
|
|
|
|
+ name: string;
|
|
|
|
|
+ // 支持的产业
|
|
|
|
|
+ industries: IndustryType[];
|
|
|
|
|
+ // 定位点数据
|
|
|
|
|
+ mapPoints: Partial<Record<IndustryType, LocationPoint[]>>;
|
|
|
|
|
+ // 关键指标数据
|
|
|
|
|
+ keyMetrics: Partial<Record<IndustryType, MetricData[]>>;
|
|
|
|
|
+ // 供应链网络数据
|
|
|
|
|
+ supplyChainNetwork: Partial<Record<IndustryType, SupplyChainNetwork>>;
|
|
|
|
|
+ // 弹出框数据
|
|
|
|
|
+ popupData: Partial<Record<IndustryType, PopupData[]>>;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 扩展的Context接口
|
|
|
|
|
+interface SupplyChainContextType {
|
|
|
|
|
+ // 当前组合类型
|
|
|
|
|
+ currentDashboard: DashboardType;
|
|
|
|
|
+ // 当前产业
|
|
|
|
|
+ currentIndustry: IndustryType;
|
|
|
|
|
+ // 主题色
|
|
|
|
|
+ themeColor: string;
|
|
|
|
|
+ // 当前组合数据
|
|
|
|
|
+ currentData: SupplyChainData | null;
|
|
|
|
|
+ // 加载状态
|
|
|
|
|
+ isLoading: boolean;
|
|
|
|
|
+ // 错误信息
|
|
|
|
|
+ error: string | null;
|
|
|
|
|
+ // 设置方法
|
|
|
|
|
+ setDashboard: (dashboard: DashboardType) => void;
|
|
|
|
|
+ setIndustry: (industry: IndustryType) => void;
|
|
|
|
|
+ // 数据加载方法
|
|
|
|
|
+ loadDashboardData: (dashboard: DashboardType) => Promise<void>;
|
|
|
|
|
+ // 刷新数据
|
|
|
|
|
+ refreshData: () => Promise<void>;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 创建Context
|
|
|
|
|
+const SupplyChainContext = createContext<SupplyChainContextType | undefined>(undefined);
|
|
|
|
|
+
|
|
|
|
|
+// Provider Props接口
|
|
|
|
|
+interface SupplyChainProviderProps {
|
|
|
|
|
+ children: ReactNode;
|
|
|
|
|
+ defaultDashboard?: DashboardType;
|
|
|
|
|
+ defaultIndustry?: IndustryType;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 静态数据定义 - 粮食-油脂组合
|
|
|
|
|
+const grainOilData: SupplyChainData = {
|
|
|
|
|
+ name: '粮食-油脂组合',
|
|
|
|
|
+ industries: ['粮食', '油脂'],
|
|
|
|
|
+ mapPoints: {
|
|
|
|
|
+ '粮食': [
|
|
|
|
|
+ { id: 'grain-base1', type: 'base', x: 1142.89, y: 717.84, industry: '粮食' },
|
|
|
|
|
+ { id: 'grain-base2', type: 'base', x: 1664.25, y: 530.82, industry: '粮食' },
|
|
|
|
|
+ { id: 'grain-base3', type: 'base', x: 1435, y: 527.14, industry: '粮食' },
|
|
|
|
|
+ { id: 'grain-base4', type: 'base', x: 1203.07, y: 514.31, industry: '粮食' },
|
|
|
|
|
+ { id: 'grain-chain1', type: 'chain', x: 1273.12, y: 551.14, industry: '粮食' },
|
|
|
|
|
+ { id: 'grain-chain2', type: 'chain', x: 1403, y: 597.5, industry: '粮食' },
|
|
|
|
|
+ { id: 'grain-chain3', type: 'chain', x: 1694.25, y: 645.5, industry: '粮食' },
|
|
|
|
|
+ { id: 'grain-chain4', type: 'chain', x: 1237.87, y: 761.84, industry: '粮食' }
|
|
|
|
|
+ ],
|
|
|
|
|
+ '油脂': [
|
|
|
|
|
+ { id: 'oil-base1', type: 'base', x: 985, y: 610.22, industry: '油脂' },
|
|
|
|
|
+ { id: 'oil-base2', type: 'base', x: 1125.81, y: 409.52, industry: '油脂' },
|
|
|
|
|
+ { id: 'oil-base3', type: 'base', x: 1229.57, y: 570, industry: '油脂' },
|
|
|
|
|
+ { id: 'oil-base4', type: 'base', x: 1445, y: 560.35, industry: '油脂' },
|
|
|
|
|
+ { id: 'oil-chain1', type: 'chain', x: 949.08, y: 680.31, industry: '油脂' },
|
|
|
|
|
+ { id: 'oil-chain2', type: 'chain', x: 1178.36, y: 418.4, industry: '油脂' },
|
|
|
|
|
+ { id: 'oil-chain3', type: 'chain', x: 1307.42, y: 551.47, industry: '油脂' },
|
|
|
|
|
+ { id: 'oil-chain4', type: 'chain', x: 1403.37, y: 603.77, industry: '油脂' }
|
|
|
|
|
+ ]
|
|
|
|
|
+ },
|
|
|
|
|
+ keyMetrics: {
|
|
|
|
|
+ '粮食': [
|
|
|
|
|
+ { id: 'grain-metric1', label: '加工能力达', value: '200', unit: '万吨/年', industry: '粮食' },
|
|
|
|
|
+ { id: 'grain-metric2', label: '自建优质水稻基地', value: '15', unit: '万亩', industry: '粮食' },
|
|
|
|
|
+ { id: 'grain-metric3', label: '辐射带动面积', value: '20', unit: '万亩', industry: '粮食' }
|
|
|
|
|
+ ],
|
|
|
|
|
+ '油脂': [
|
|
|
|
|
+ { id: 'oil-metric1', label: '加工能力达', value: '150', unit: '万吨/年', industry: '油脂' },
|
|
|
|
|
+ { id: 'oil-metric2', label: '自建油料基地', value: '10', unit: '万亩', industry: '油脂' },
|
|
|
|
|
+ { id: 'oil-metric3', label: '辐射带动面积', value: '18', unit: '万亩', industry: '油脂' }
|
|
|
|
|
+ ]
|
|
|
|
|
+ },
|
|
|
|
|
+ supplyChainNetwork: {
|
|
|
|
|
+ '粮食': {
|
|
|
|
|
+ connections: [
|
|
|
|
|
+ { from: 'grain-base1', to: 'grain-chain1', type: 'supply' },
|
|
|
|
|
+ { from: 'grain-base2', to: 'grain-chain2', type: 'supply' },
|
|
|
|
|
+ { from: 'grain-base3', to: 'grain-chain3', type: 'supply' },
|
|
|
|
|
+ { from: 'grain-base4', to: 'grain-chain4', type: 'supply' }
|
|
|
|
|
+ ],
|
|
|
|
|
+ nodes: {
|
|
|
|
|
+ 'grain-base1': { id: 'grain-base1', type: 'base', x: 1142.89, y: 717.84, industry: '粮食' },
|
|
|
|
|
+ 'grain-base2': { id: 'grain-base2', type: 'base', x: 1664.25, y: 530.82, industry: '粮食' },
|
|
|
|
|
+ 'grain-base3': { id: 'grain-base3', type: 'base', x: 1435, y: 527.14, industry: '粮食' },
|
|
|
|
|
+ 'grain-base4': { id: 'grain-base4', type: 'base', x: 1203.07, y: 514.31, industry: '粮食' },
|
|
|
|
|
+ 'grain-chain1': { id: 'grain-chain1', type: 'chain', x: 1273.12, y: 551.14, industry: '粮食' },
|
|
|
|
|
+ 'grain-chain2': { id: 'grain-chain2', type: 'chain', x: 1403, y: 597.5, industry: '粮食' },
|
|
|
|
|
+ 'grain-chain3': { id: 'grain-chain3', type: 'chain', x: 1694.25, y: 645.5, industry: '粮食' },
|
|
|
|
|
+ 'grain-chain4': { id: 'grain-chain4', type: 'chain', x: 1237.87, y: 761.84, industry: '粮食' }
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ '油脂': {
|
|
|
|
|
+ connections: [
|
|
|
|
|
+ { from: 'oil-base1', to: 'oil-chain1', type: 'supply' },
|
|
|
|
|
+ { from: 'oil-base2', to: 'oil-chain2', type: 'supply' },
|
|
|
|
|
+ { from: 'oil-base3', to: 'oil-chain3', type: 'supply' },
|
|
|
|
|
+ { from: 'oil-base4', to: 'oil-chain4', type: 'supply' }
|
|
|
|
|
+ ],
|
|
|
|
|
+ nodes: {
|
|
|
|
|
+ 'oil-base1': { id: 'oil-base1', type: 'base', x: 985, y: 610.22, industry: '油脂' },
|
|
|
|
|
+ 'oil-base2': { id: 'oil-base2', type: 'base', x: 1125.81, y: 409.52, industry: '油脂' },
|
|
|
|
|
+ 'oil-base3': { id: 'oil-base3', type: 'base', x: 1229.57, y: 570, industry: '油脂' },
|
|
|
|
|
+ 'oil-base4': { id: 'oil-base4', type: 'base', x: 1445, y: 560.35, industry: '油脂' },
|
|
|
|
|
+ 'oil-chain1': { id: 'oil-chain1', type: 'chain', x: 949.08, y: 680.31, industry: '油脂' },
|
|
|
|
|
+ 'oil-chain2': { id: 'oil-chain2', type: 'chain', x: 1178.36, y: 418.4, industry: '油脂' },
|
|
|
|
|
+ 'oil-chain3': { id: 'oil-chain3', type: 'chain', x: 1307.42, y: 551.47, industry: '油脂' },
|
|
|
|
|
+ 'oil-chain4': { id: 'oil-chain4', type: 'chain', x: 1403.37, y: 603.77, industry: '油脂' }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ popupData: {
|
|
|
|
|
+ '粮食': [
|
|
|
|
|
+ {
|
|
|
|
|
+ id: 'grain-popup1',
|
|
|
|
|
+ title: '源头',
|
|
|
|
|
+ content: '江汉大米核心示范基地荆门/荆州/黄冈/孝感',
|
|
|
|
|
+ position: { x: 717.28, y: 273.13 },
|
|
|
|
|
+ industry: '粮食',
|
|
|
|
|
+ metrics: [
|
|
|
|
|
+ { label: '辐射带动:', value: '>20', unit: '万亩' },
|
|
|
|
|
+ { label: '自建基地规模:', value: '6~15', unit: '万亩' }
|
|
|
|
|
+ ]
|
|
|
|
|
+ }
|
|
|
|
|
+ ],
|
|
|
|
|
+ '油脂': [
|
|
|
|
|
+ {
|
|
|
|
|
+ id: 'oil-popup1',
|
|
|
|
|
+ title: '源头',
|
|
|
|
|
+ content: '油脂核心示范基地',
|
|
|
|
|
+ position: { x: 814.45, y: 464.02 },
|
|
|
|
|
+ industry: '油脂',
|
|
|
|
|
+ metrics: [
|
|
|
|
|
+ { label: '辐射带动:', value: '>18', unit: '万亩' },
|
|
|
|
|
+ { label: '自建基地规模:', value: '5~10', unit: '万亩' }
|
|
|
|
|
+ ]
|
|
|
|
|
+ }
|
|
|
|
|
+ ]
|
|
|
|
|
+ }
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+// 其他组合的静态数据定义(简化版,实际使用时需要完整定义)
|
|
|
|
|
+const seedFruitData: SupplyChainData = {
|
|
|
|
|
+ name: '种业-果蔬组合',
|
|
|
|
|
+ industries: ['种业', '果蔬'],
|
|
|
|
|
+ mapPoints: {
|
|
|
|
|
+ '种业': [
|
|
|
|
|
+ { id: 'seed-base1', type: 'base', x: 1142.89, y: 717.84, industry: '种业' },
|
|
|
|
|
+ { id: 'seed-base2', type: 'base', x: 1664.25, y: 530.82, industry: '种业' },
|
|
|
|
|
+ { id: 'seed-base3', type: 'base', x: 1435, y: 527.14, industry: '种业' },
|
|
|
|
|
+ { id: 'seed-base4', type: 'base', x: 1203.07, y: 514.31, industry: '种业' },
|
|
|
|
|
+ { id: 'seed-chain1', type: 'chain', x: 1273.12, y: 551.14, industry: '种业' },
|
|
|
|
|
+ { id: 'seed-chain2', type: 'chain', x: 1403, y: 597.5, industry: '种业' },
|
|
|
|
|
+ { id: 'seed-chain3', type: 'chain', x: 1694.25, y: 645.5, industry: '种业' },
|
|
|
|
|
+ { id: 'seed-chain4', type: 'chain', x: 1237.87, y: 761.84, industry: '种业' }
|
|
|
|
|
+ ],
|
|
|
|
|
+ '果蔬': [
|
|
|
|
|
+ { id: 'fruit-base1', type: 'base', x: 985, y: 610.22, industry: '果蔬' },
|
|
|
|
|
+ { id: 'fruit-base2', type: 'base', x: 1125.81, y: 409.52, industry: '果蔬' },
|
|
|
|
|
+ { id: 'fruit-base3', type: 'base', x: 1229.57, y: 570, industry: '果蔬' },
|
|
|
|
|
+ { id: 'fruit-base4', type: 'base', x: 1445, y: 560.35, industry: '果蔬' },
|
|
|
|
|
+ { id: 'fruit-chain1', type: 'chain', x: 949.08, y: 680.31, industry: '果蔬' }
|
|
|
|
|
+ ]
|
|
|
|
|
+ },
|
|
|
|
|
+ keyMetrics: {
|
|
|
|
|
+ '种业': [
|
|
|
|
|
+ { id: 'seed-metric1', label: '良种繁育能力', value: '50', unit: '万公斤/年', industry: '种业' },
|
|
|
|
|
+ { id: 'seed-metric2', label: '自建育种基地', value: '8', unit: '万亩', industry: '种业' },
|
|
|
|
|
+ { id: 'seed-metric3', label: '辐射带动面积', value: '15', unit: '万亩', industry: '种业' }
|
|
|
|
|
+ ],
|
|
|
|
|
+ '果蔬': [
|
|
|
|
|
+ { id: 'fruit-metric1', label: '加工能力达', value: '80', unit: '万吨/年', industry: '果蔬' },
|
|
|
|
|
+ { id: 'fruit-metric2', label: '自建果蔬基地', value: '12', unit: '万亩', industry: '果蔬' },
|
|
|
|
|
+ { id: 'fruit-metric3', label: '辐射带动面积', value: '25', unit: '万亩', industry: '果蔬' }
|
|
|
|
|
+ ]
|
|
|
|
|
+ },
|
|
|
|
|
+ supplyChainNetwork: {
|
|
|
|
|
+ '种业': {
|
|
|
|
|
+ connections: [
|
|
|
|
|
+ { from: 'seed-base1', to: 'seed-chain1', type: 'supply' },
|
|
|
|
|
+ { from: 'seed-base2', to: 'seed-chain2', type: 'supply' },
|
|
|
|
|
+ { from: 'seed-base3', to: 'seed-chain3', type: 'supply' },
|
|
|
|
|
+ { from: 'seed-base4', to: 'seed-chain4', type: 'supply' }
|
|
|
|
|
+ ],
|
|
|
|
|
+ nodes: {
|
|
|
|
|
+ 'seed-base1': { id: 'seed-base1', type: 'base', x: 1142.89, y: 717.84, industry: '种业' },
|
|
|
|
|
+ 'seed-base2': { id: 'seed-base2', type: 'base', x: 1664.25, y: 530.82, industry: '种业' },
|
|
|
|
|
+ 'seed-base3': { id: 'seed-base3', type: 'base', x: 1435, y: 527.14, industry: '种业' },
|
|
|
|
|
+ 'seed-base4': { id: 'seed-base4', type: 'base', x: 1203.07, y: 514.31, industry: '种业' },
|
|
|
|
|
+ 'seed-chain1': { id: 'seed-chain1', type: 'chain', x: 1273.12, y: 551.14, industry: '种业' },
|
|
|
|
|
+ 'seed-chain2': { id: 'seed-chain2', type: 'chain', x: 1403, y: 597.5, industry: '种业' },
|
|
|
|
|
+ 'seed-chain3': { id: 'seed-chain3', type: 'chain', x: 1694.25, y: 645.5, industry: '种业' },
|
|
|
|
|
+ 'seed-chain4': { id: 'seed-chain4', type: 'chain', x: 1237.87, y: 761.84, industry: '种业' }
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ '果蔬': {
|
|
|
|
|
+ connections: [
|
|
|
|
|
+ { from: 'fruit-base1', to: 'fruit-chain1', type: 'supply' },
|
|
|
|
|
+ { from: 'fruit-base2', to: 'fruit-chain1', type: 'supply' },
|
|
|
|
|
+ { from: 'fruit-base3', to: 'fruit-chain1', type: 'supply' },
|
|
|
|
|
+ { from: 'fruit-base4', to: 'fruit-chain1', type: 'supply' }
|
|
|
|
|
+ ],
|
|
|
|
|
+ nodes: {
|
|
|
|
|
+ 'fruit-base1': { id: 'fruit-base1', type: 'base', x: 985, y: 610.22, industry: '果蔬' },
|
|
|
|
|
+ 'fruit-base2': { id: 'fruit-base2', type: 'base', x: 1125.81, y: 409.52, industry: '果蔬' },
|
|
|
|
|
+ 'fruit-base3': { id: 'fruit-base3', type: 'base', x: 1229.57, y: 570, industry: '果蔬' },
|
|
|
|
|
+ 'fruit-base4': { id: 'fruit-base4', type: 'base', x: 1445, y: 560.35, industry: '果蔬' },
|
|
|
|
|
+ 'fruit-chain1': { id: 'fruit-chain1', type: 'chain', x: 949.08, y: 680.31, industry: '果蔬' }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ popupData: {
|
|
|
|
|
+ '种业': [
|
|
|
|
|
+ {
|
|
|
|
|
+ id: 'seed-popup1',
|
|
|
|
|
+ title: '源头',
|
|
|
|
|
+ content: '种业核心示范基地',
|
|
|
|
|
+ position: { x: 717.28, y: 273.13 },
|
|
|
|
|
+ industry: '种业',
|
|
|
|
|
+ metrics: [
|
|
|
|
|
+ { label: '辐射带动:', value: '>15', unit: '万亩' },
|
|
|
|
|
+ { label: '自建基地规模:', value: '4~8', unit: '万亩' }
|
|
|
|
|
+ ]
|
|
|
|
|
+ }
|
|
|
|
|
+ ],
|
|
|
|
|
+ '果蔬': [
|
|
|
|
|
+ {
|
|
|
|
|
+ id: 'fruit-popup1',
|
|
|
|
|
+ title: '源头',
|
|
|
|
|
+ content: '果蔬核心示范基地',
|
|
|
|
|
+ position: { x: 814.45, y: 464.02 },
|
|
|
|
|
+ industry: '果蔬',
|
|
|
|
|
+ metrics: [
|
|
|
|
|
+ { label: '辐射带动:', value: '>25', unit: '万亩' },
|
|
|
|
|
+ { label: '自建基地规模:', value: '8~12', unit: '万亩' }
|
|
|
|
|
+ ]
|
|
|
|
|
+ }
|
|
|
|
|
+ ]
|
|
|
|
|
+ }
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+const livestockAquacultureData: SupplyChainData = {
|
|
|
|
|
+ name: '畜牧-水产组合',
|
|
|
|
|
+ industries: ['畜牧', '水产'],
|
|
|
|
|
+ mapPoints: {
|
|
|
|
|
+ '畜牧': [
|
|
|
|
|
+ { id: 'livestock-base1', type: 'base', x: 1203.07, y: 514.31, industry: '畜牧' },
|
|
|
|
|
+ { id: 'livestock-chain1', type: 'chain', x: 1273.12, y: 551.14, industry: '畜牧' },
|
|
|
|
|
+ { id: 'livestock-chain2', type: 'chain', x: 1403, y: 597.5, industry: '畜牧' },
|
|
|
|
|
+ { id: 'livestock-chain3', type: 'chain', x: 1694.25, y: 645.5, industry: '畜牧' },
|
|
|
|
|
+ { id: 'livestock-chain4', type: 'chain', x: 1237.87, y: 761.84, industry: '畜牧' }
|
|
|
|
|
+ ],
|
|
|
|
|
+ '水产': [
|
|
|
|
|
+ { id: 'aquaculture-base1', type: 'base', x: 1445, y: 560.35, industry: '水产' },
|
|
|
|
|
+ { id: 'aquaculture-chain1', type: 'chain', x: 949.08, y: 680.31, industry: '水产' },
|
|
|
|
|
+ { id: 'aquaculture-chain2', type: 'chain', x: 1178.36, y: 418.4, industry: '水产' },
|
|
|
|
|
+ { id: 'aquaculture-chain3', type: 'chain', x: 1307.42, y: 551.47, industry: '水产' },
|
|
|
|
|
+ { id: 'aquaculture-chain4', type: 'chain', x: 1403.37, y: 603.77, industry: '水产' }
|
|
|
|
|
+ ]
|
|
|
|
|
+ },
|
|
|
|
|
+ keyMetrics: {
|
|
|
|
|
+ '畜牧': [
|
|
|
|
|
+ { id: 'livestock-metric1', label: '养殖规模', value: '100', unit: '万头/年', industry: '畜牧' },
|
|
|
|
|
+ { id: 'livestock-metric2', label: '自建养殖基地', value: '20', unit: '万亩', industry: '畜牧' },
|
|
|
|
|
+ { id: 'livestock-metric3', label: '辐射带动面积', value: '30', unit: '万亩', industry: '畜牧' }
|
|
|
|
|
+ ],
|
|
|
|
|
+ '水产': [
|
|
|
|
|
+ { id: 'aquaculture-metric1', label: '养殖规模', value: '50', unit: '万吨/年', industry: '水产' },
|
|
|
|
|
+ { id: 'aquaculture-metric2', label: '自建养殖基地', value: '15', unit: '万亩', industry: '水产' },
|
|
|
|
|
+ { id: 'aquaculture-metric3', label: '辐射带动面积', value: '22', unit: '万亩', industry: '水产' }
|
|
|
|
|
+ ]
|
|
|
|
|
+ },
|
|
|
|
|
+ supplyChainNetwork: {
|
|
|
|
|
+ '畜牧': {
|
|
|
|
|
+ connections: [
|
|
|
|
|
+ { from: 'livestock-base1', to: 'livestock-chain1', type: 'supply' },
|
|
|
|
|
+ { from: 'livestock-base1', to: 'livestock-chain2', type: 'supply' },
|
|
|
|
|
+ { from: 'livestock-base1', to: 'livestock-chain3', type: 'supply' },
|
|
|
|
|
+ { from: 'livestock-base1', to: 'livestock-chain4', type: 'supply' }
|
|
|
|
|
+ ],
|
|
|
|
|
+ nodes: {
|
|
|
|
|
+ 'livestock-base1': { id: 'livestock-base1', type: 'base', x: 1203.07, y: 514.31, industry: '畜牧' },
|
|
|
|
|
+ 'livestock-chain1': { id: 'livestock-chain1', type: 'chain', x: 1273.12, y: 551.14, industry: '畜牧' },
|
|
|
|
|
+ 'livestock-chain2': { id: 'livestock-chain2', type: 'chain', x: 1403, y: 597.5, industry: '畜牧' },
|
|
|
|
|
+ 'livestock-chain3': { id: 'livestock-chain3', type: 'chain', x: 1694.25, y: 645.5, industry: '畜牧' },
|
|
|
|
|
+ 'livestock-chain4': { id: 'livestock-chain4', type: 'chain', x: 1237.87, y: 761.84, industry: '畜牧' }
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ '水产': {
|
|
|
|
|
+ connections: [
|
|
|
|
|
+ { from: 'aquaculture-base1', to: 'aquaculture-chain1', type: 'supply' },
|
|
|
|
|
+ { from: 'aquaculture-base1', to: 'aquaculture-chain2', type: 'supply' },
|
|
|
|
|
+ { from: 'aquaculture-base1', to: 'aquaculture-chain3', type: 'supply' },
|
|
|
|
|
+ { from: 'aquaculture-base1', to: 'aquaculture-chain4', type: 'supply' }
|
|
|
|
|
+ ],
|
|
|
|
|
+ nodes: {
|
|
|
|
|
+ 'aquaculture-base1': { id: 'aquaculture-base1', type: 'base', x: 1445, y: 560.35, industry: '水产' },
|
|
|
|
|
+ 'aquaculture-chain1': { id: 'aquaculture-chain1', type: 'chain', x: 949.08, y: 680.31, industry: '水产' },
|
|
|
|
|
+ 'aquaculture-chain2': { id: 'aquaculture-chain2', type: 'chain', x: 1178.36, y: 418.4, industry: '水产' },
|
|
|
|
|
+ 'aquaculture-chain3': { id: 'aquaculture-chain3', type: 'chain', x: 1307.42, y: 551.47, industry: '水产' },
|
|
|
|
|
+ 'aquaculture-chain4': { id: 'aquaculture-chain4', type: 'chain', x: 1403.37, y: 603.77, industry: '水产' }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ popupData: {
|
|
|
|
|
+ '畜牧': [
|
|
|
|
|
+ {
|
|
|
|
|
+ id: 'livestock-popup1',
|
|
|
|
|
+ title: '源头',
|
|
|
|
|
+ content: '畜牧核心示范基地',
|
|
|
|
|
+ position: { x: 717.28, y: 273.13 },
|
|
|
|
|
+ industry: '畜牧',
|
|
|
|
|
+ metrics: [
|
|
|
|
|
+ { label: '辐射带动:', value: '>30', unit: '万亩' },
|
|
|
|
|
+ { label: '自建基地规模:', value: '15~20', unit: '万亩' }
|
|
|
|
|
+ ]
|
|
|
|
|
+ }
|
|
|
|
|
+ ],
|
|
|
|
|
+ '水产': [
|
|
|
|
|
+ {
|
|
|
|
|
+ id: 'aquaculture-popup1',
|
|
|
|
|
+ title: '源头',
|
|
|
|
|
+ content: '水产核心示范基地',
|
|
|
|
|
+ position: { x: 814.45, y: 464.02 },
|
|
|
|
|
+ industry: '水产',
|
|
|
|
|
+ metrics: [
|
|
|
|
|
+ { label: '辐射带动:', value: '>22', unit: '万亩' },
|
|
|
|
|
+ { label: '自建基地规模:', value: '10~15', unit: '万亩' }
|
|
|
|
|
+ ]
|
|
|
|
|
+ }
|
|
|
|
|
+ ]
|
|
|
|
|
+ }
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+const freshFoodSaltData: SupplyChainData = {
|
|
|
|
|
+ name: '鲜食-泛盐组合',
|
|
|
|
|
+ industries: ['鲜食', '泛盐'],
|
|
|
|
|
+ mapPoints: {
|
|
|
|
|
+ '鲜食': [
|
|
|
|
|
+ { id: 'fresh-base1', type: 'base', x: 1203.07, y: 514.31, industry: '鲜食' },
|
|
|
|
|
+ { id: 'fresh-chain1', type: 'chain', x: 1273.12, y: 551.14, industry: '鲜食' },
|
|
|
|
|
+ { id: 'fresh-chain2', type: 'chain', x: 1403, y: 597.5, industry: '鲜食' },
|
|
|
|
|
+ { id: 'fresh-chain3', type: 'chain', x: 1694.25, y: 645.5, industry: '鲜食' },
|
|
|
|
|
+ { id: 'fresh-chain4', type: 'chain', x: 1237.87, y: 761.84, industry: '鲜食' }
|
|
|
|
|
+ ],
|
|
|
|
|
+ '泛盐': [
|
|
|
|
|
+ { id: 'salt-base1', type: 'base', x: 1445, y: 560.35, industry: '泛盐' },
|
|
|
|
|
+ { id: 'salt-chain1', type: 'chain', x: 949.08, y: 680.31, industry: '泛盐' },
|
|
|
|
|
+ { id: 'salt-chain2', type: 'chain', x: 1178.36, y: 418.4, industry: '泛盐' },
|
|
|
|
|
+ { id: 'salt-chain3', type: 'chain', x: 1319.74, y: 441.51, industry: '泛盐' },
|
|
|
|
|
+ { id: 'salt-chain4', type: 'chain', x: 1403.37, y: 603.77, industry: '泛盐' }
|
|
|
|
|
+ ]
|
|
|
|
|
+ },
|
|
|
|
|
+ keyMetrics: {
|
|
|
|
|
+ '鲜食': [
|
|
|
|
|
+ { id: 'fresh-metric1', label: '加工能力达', value: '60', unit: '万吨/年', industry: '鲜食' },
|
|
|
|
|
+ { id: 'fresh-metric2', label: '自建鲜食基地', value: '8', unit: '万亩', industry: '鲜食' },
|
|
|
|
|
+ { id: 'fresh-metric3', label: '辐射带动面积', value: '18', unit: '万亩', industry: '鲜食' }
|
|
|
|
|
+ ],
|
|
|
|
|
+ '泛盐': [
|
|
|
|
|
+ { id: 'salt-metric1', label: '加工能力达', value: '40', unit: '万吨/年', industry: '泛盐' },
|
|
|
|
|
+ { id: 'salt-metric2', label: '自建盐业基地', value: '6', unit: '万亩', industry: '泛盐' },
|
|
|
|
|
+ { id: 'salt-metric3', label: '辐射带动面积', value: '12', unit: '万亩', industry: '泛盐' }
|
|
|
|
|
+ ]
|
|
|
|
|
+ },
|
|
|
|
|
+ supplyChainNetwork: {
|
|
|
|
|
+ '鲜食': {
|
|
|
|
|
+ connections: [
|
|
|
|
|
+ { from: 'fresh-base1', to: 'fresh-chain1', type: 'supply' },
|
|
|
|
|
+ { from: 'fresh-base1', to: 'fresh-chain2', type: 'supply' },
|
|
|
|
|
+ { from: 'fresh-base1', to: 'fresh-chain3', type: 'supply' },
|
|
|
|
|
+ { from: 'fresh-base1', to: 'fresh-chain4', type: 'supply' }
|
|
|
|
|
+ ],
|
|
|
|
|
+ nodes: {
|
|
|
|
|
+ 'fresh-base1': { id: 'fresh-base1', type: 'base', x: 1203.07, y: 514.31, industry: '鲜食' },
|
|
|
|
|
+ 'fresh-chain1': { id: 'fresh-chain1', type: 'chain', x: 1273.12, y: 551.14, industry: '鲜食' },
|
|
|
|
|
+ 'fresh-chain2': { id: 'fresh-chain2', type: 'chain', x: 1403, y: 597.5, industry: '鲜食' },
|
|
|
|
|
+ 'fresh-chain3': { id: 'fresh-chain3', type: 'chain', x: 1694.25, y: 645.5, industry: '鲜食' },
|
|
|
|
|
+ 'fresh-chain4': { id: 'fresh-chain4', type: 'chain', x: 1237.87, y: 761.84, industry: '鲜食' }
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ '泛盐': {
|
|
|
|
|
+ connections: [
|
|
|
|
|
+ { from: 'salt-base1', to: 'salt-chain1', type: 'supply' },
|
|
|
|
|
+ { from: 'salt-base1', to: 'salt-chain2', type: 'supply' },
|
|
|
|
|
+ { from: 'salt-base1', to: 'salt-chain3', type: 'supply' },
|
|
|
|
|
+ { from: 'salt-base1', to: 'salt-chain4', type: 'supply' }
|
|
|
|
|
+ ],
|
|
|
|
|
+ nodes: {
|
|
|
|
|
+ 'salt-base1': { id: 'salt-base1', type: 'base', x: 1445, y: 560.35, industry: '泛盐' },
|
|
|
|
|
+ 'salt-chain1': { id: 'salt-chain1', type: 'chain', x: 949.08, y: 680.31, industry: '泛盐' },
|
|
|
|
|
+ 'salt-chain2': { id: 'salt-chain2', type: 'chain', x: 1178.36, y: 418.4, industry: '泛盐' },
|
|
|
|
|
+ 'salt-chain3': { id: 'salt-chain3', type: 'chain', x: 1319.74, y: 441.51, industry: '泛盐' },
|
|
|
|
|
+ 'salt-chain4': { id: 'salt-chain4', type: 'chain', x: 1403.37, y: 603.77, industry: '泛盐' }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ popupData: {
|
|
|
|
|
+ '鲜食': [
|
|
|
|
|
+ {
|
|
|
|
|
+ id: 'fresh-popup1',
|
|
|
|
|
+ title: '源头',
|
|
|
|
|
+ content: '鲜食核心示范基地',
|
|
|
|
|
+ position: { x: 717.28, y: 273.13 },
|
|
|
|
|
+ industry: '鲜食',
|
|
|
|
|
+ metrics: [
|
|
|
|
|
+ { label: '辐射带动:', value: '>18', unit: '万亩' },
|
|
|
|
|
+ { label: '自建基地规模:', value: '5~8', unit: '万亩' }
|
|
|
|
|
+ ]
|
|
|
|
|
+ }
|
|
|
|
|
+ ],
|
|
|
|
|
+ '泛盐': [
|
|
|
|
|
+ {
|
|
|
|
|
+ id: 'salt-popup1',
|
|
|
|
|
+ title: '源头',
|
|
|
|
|
+ content: '泛盐核心示范基地',
|
|
|
|
|
+ position: { x: 814.45, y: 464.02 },
|
|
|
|
|
+ industry: '泛盐',
|
|
|
|
|
+ metrics: [
|
|
|
|
|
+ { label: '辐射带动:', value: '>12', unit: '万亩' },
|
|
|
|
|
+ { label: '自建基地规模:', value: '3~6', unit: '万亩' }
|
|
|
|
|
+ ]
|
|
|
|
|
+ }
|
|
|
|
|
+ ]
|
|
|
|
|
+ }
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+// 静态数据加载函数
|
|
|
|
|
+const loadStaticData = async (dashboardType: DashboardType): Promise<SupplyChainData> => {
|
|
|
|
|
+ // 模拟API延迟
|
|
|
|
|
+ await new Promise(resolve => setTimeout(resolve, 500));
|
|
|
|
|
+
|
|
|
|
|
+ // 返回对应组合的静态数据
|
|
|
|
|
+ switch (dashboardType) {
|
|
|
|
|
+ case 'grain-oil':
|
|
|
|
|
+ return grainOilData;
|
|
|
|
|
+ case 'seed-fruit':
|
|
|
|
|
+ return seedFruitData;
|
|
|
|
|
+ case 'livestock-aquaculture':
|
|
|
|
|
+ return livestockAquacultureData;
|
|
|
|
|
+ case 'fresh-food-salt':
|
|
|
|
|
+ return freshFoodSaltData;
|
|
|
|
|
+ default:
|
|
|
|
|
+ throw new Error(`Unknown dashboard type: ${dashboardType}`);
|
|
|
|
|
+ }
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+// SupplyChainProvider组件
|
|
|
|
|
+export const SupplyChainProvider: React.FC<SupplyChainProviderProps> = ({
|
|
|
|
|
+ children,
|
|
|
|
|
+ defaultDashboard = 'grain-oil',
|
|
|
|
|
+ defaultIndustry = '粮食'
|
|
|
|
|
+}) => {
|
|
|
|
|
+ const [currentDashboard, setCurrentDashboard] = useState<DashboardType>(defaultDashboard);
|
|
|
|
|
+ const [currentIndustry, setCurrentIndustry] = useState<IndustryType>(defaultIndustry);
|
|
|
|
|
+ const [currentData, setCurrentData] = useState<SupplyChainData | null>(null);
|
|
|
|
|
+ const [isLoading, setIsLoading] = useState(false);
|
|
|
|
|
+ const [error, setError] = useState<string | null>(null);
|
|
|
|
|
+
|
|
|
|
|
+ const themeColor = industryColors[currentIndustry];
|
|
|
|
|
+
|
|
|
|
|
+ // 使用React Query加载数据
|
|
|
|
|
+ const queryClient = useQueryClient();
|
|
|
|
|
+
|
|
|
|
|
+ const { data, isLoading: queryLoading, error: queryError } = useQuery({
|
|
|
|
|
+ queryKey: ['supply-chain', currentDashboard],
|
|
|
|
|
+ queryFn: () => loadStaticData(currentDashboard),
|
|
|
|
|
+ enabled: !!currentDashboard,
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ // 同步数据到状态
|
|
|
|
|
+ useEffect(() => {
|
|
|
|
|
+ if (data) {
|
|
|
|
|
+ setCurrentData(data);
|
|
|
|
|
+ }
|
|
|
|
|
+ }, [data]);
|
|
|
|
|
+
|
|
|
|
|
+ // 合并加载状态
|
|
|
|
|
+ useEffect(() => {
|
|
|
|
|
+ setIsLoading(queryLoading);
|
|
|
|
|
+ }, [queryLoading]);
|
|
|
|
|
+
|
|
|
|
|
+ // 合并错误状态
|
|
|
|
|
+ useEffect(() => {
|
|
|
|
|
+ if (queryError) {
|
|
|
|
|
+ setError(queryError instanceof Error ? queryError.message : '数据加载失败');
|
|
|
|
|
+ } else {
|
|
|
|
|
+ setError(null);
|
|
|
|
|
+ }
|
|
|
|
|
+ }, [queryError]);
|
|
|
|
|
+
|
|
|
|
|
+ // 设置组合
|
|
|
|
|
+ const handleSetDashboard = (dashboard: DashboardType) => {
|
|
|
|
|
+ setCurrentDashboard(dashboard);
|
|
|
|
|
+ // 重置当前产业为组合的第一个产业
|
|
|
|
|
+ if (currentData && currentData.industries.length > 0) {
|
|
|
|
|
+ setCurrentIndustry(currentData.industries[0]);
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ // 设置产业
|
|
|
|
|
+ const handleSetIndustry = (industry: IndustryType) => {
|
|
|
|
|
+ setCurrentIndustry(industry);
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ // 加载数据
|
|
|
|
|
+ const handleLoadDashboardData = async (dashboard: DashboardType) => {
|
|
|
|
|
+ try {
|
|
|
|
|
+ setIsLoading(true);
|
|
|
|
|
+ setError(null);
|
|
|
|
|
+ const data = await loadStaticData(dashboard);
|
|
|
|
|
+ setCurrentData(data);
|
|
|
|
|
+ setCurrentDashboard(dashboard);
|
|
|
|
|
+ // 设置默认产业
|
|
|
|
|
+ if (data.industries.length > 0) {
|
|
|
|
|
+ setCurrentIndustry(data.industries[0]);
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (err) {
|
|
|
|
|
+ setError(err instanceof Error ? err.message : '数据加载失败');
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ setIsLoading(false);
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ // 刷新数据
|
|
|
|
|
+ const handleRefreshData = async () => {
|
|
|
|
|
+ await queryClient.invalidateQueries({ queryKey: ['supply-chain', currentDashboard] });
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ const contextValue: SupplyChainContextType = {
|
|
|
|
|
+ currentDashboard,
|
|
|
|
|
+ currentIndustry,
|
|
|
|
|
+ themeColor,
|
|
|
|
|
+ currentData,
|
|
|
|
|
+ isLoading,
|
|
|
|
|
+ error,
|
|
|
|
|
+ setDashboard: handleSetDashboard,
|
|
|
|
|
+ setIndustry: handleSetIndustry,
|
|
|
|
|
+ loadDashboardData: handleLoadDashboardData,
|
|
|
|
|
+ refreshData: handleRefreshData,
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ return (
|
|
|
|
|
+ <SupplyChainContext.Provider value={contextValue}>
|
|
|
|
|
+ {children}
|
|
|
|
|
+ </SupplyChainContext.Provider>
|
|
|
|
|
+ );
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+// 使用SupplyChainContext的Hook
|
|
|
|
|
+export const useSupplyChain = (): SupplyChainContextType => {
|
|
|
|
|
+ const context = useContext(SupplyChainContext);
|
|
|
|
|
+ if (!context) {
|
|
|
|
|
+ throw new Error('useSupplyChain must be used within a SupplyChainProvider');
|
|
|
|
|
+ }
|
|
|
|
|
+ return context;
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+export default SupplyChainContext;
|