diff --git a/crop-x/src/app/(app)/land-information/comparison/chart/components/FieldSelector.tsx b/crop-x/src/app/(app)/land-information/comparison/chart/components/FieldSelector.tsx
new file mode 100644
index 0000000..f578b22
--- /dev/null
+++ b/crop-x/src/app/(app)/land-information/comparison/chart/components/FieldSelector.tsx
@@ -0,0 +1,78 @@
+'use client';
+
+import { useState } from 'react';
+import { Card } from '@/components/ui/card';
+import { Button } from '@/components/ui/button';
+import { Badge } from '@/components/ui/badge';
+import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
+import { Plus, X } from 'lucide-react';
+import { toast } from 'sonner';
+import { FieldData, useChartAnalysis } from './chartAnalysisReducer';
+
+interface FieldSelectorProps {
+ fields: FieldData[];
+}
+
+export function FieldSelector({ fields }: FieldSelectorProps) {
+ const { state, addField, removeField } = useChartAnalysis();
+
+ const availableFields = fields.filter(f => !state.selectedFields.includes(f.id));
+ const comparisonFields = fields.filter(f => state.selectedFields.includes(f.id));
+
+ const handleAddField = (fieldId: string) => {
+ const success = addField(fieldId);
+ if (!success) {
+ toast.error('最多只能同时对比4个地块');
+ }
+ };
+
+ const handleRemoveField = (fieldId: string) => {
+ const success = removeField(fieldId);
+ if (!success) {
+ toast.error('至少需要选择2个地块进行对比');
+ }
+ };
+
+ return (
+
+
+
+
+
+ {comparisonFields.map(field => (
+
+ {field.name}
+
+
+ ))}
+ {availableFields.length > 0 && (
+
+ )}
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/crop-x/src/app/(app)/land-information/comparison/chart/components/MapComparison.tsx b/crop-x/src/app/(app)/land-information/comparison/chart/components/MapComparison.tsx
new file mode 100644
index 0000000..81488c6
--- /dev/null
+++ b/crop-x/src/app/(app)/land-information/comparison/chart/components/MapComparison.tsx
@@ -0,0 +1,78 @@
+'use client';
+
+import { Card } from '@/components/ui/card';
+import { Badge } from '@/components/ui/badge';
+import { Map as MapIcon, MapPin } from 'lucide-react';
+import { FieldData, useChartAnalysis } from './chartAnalysisReducer';
+
+export function MapComparison() {
+ const { state } = useChartAnalysis();
+
+ const comparisonFields = state.fields.filter(f => state.selectedFields.includes(f.id));
+
+ const getGradeColor = (grade: string) => {
+ switch (grade) {
+ case '高度适宜': return 'bg-green-500 text-white';
+ case '一般适宜': return 'bg-yellow-500 text-white';
+ case '不适宜': return 'bg-red-500 text-white';
+ default: return 'bg-gray-500 text-white';
+ }
+ };
+
+ // 如果没有选择地块,显示空状态
+ if (comparisonFields.length === 0) {
+ return (
+
+
+
+ 地块空间分布对比
+
+
+
+
+
请先选择要对比的地块
+
地图将显示各地块的空间分布位置
+
+
+
+ );
+ }
+
+ return (
+
+
+
+ 地块空间分布对比
+
+
+ {comparisonFields.map((field, index) => (
+
+
{field.name}
+
+
+
+ {Array.from({ length: 64 }).map((_, i) => (
+
+ ))}
+
+
+
+
+
{field.location}
+
+ {field.suitabilityGrade}
+
+
+
+
+ ))}
+
+
+ );
+}
\ No newline at end of file
diff --git a/crop-x/src/app/(app)/land-information/comparison/chart/components/NutrientComparison.tsx b/crop-x/src/app/(app)/land-information/comparison/chart/components/NutrientComparison.tsx
new file mode 100644
index 0000000..966002a
--- /dev/null
+++ b/crop-x/src/app/(app)/land-information/comparison/chart/components/NutrientComparison.tsx
@@ -0,0 +1,70 @@
+'use client';
+
+import { Card } from '@/components/ui/card';
+import {
+ ResponsiveContainer,
+ LineChart,
+ Line,
+ XAxis,
+ YAxis,
+ CartesianGrid,
+ Tooltip,
+ Legend,
+} from 'recharts';
+import { Scale } from 'lucide-react';
+import { FieldData, useChartAnalysis } from './chartAnalysisReducer';
+
+export function NutrientComparison() {
+ const { state } = useChartAnalysis();
+
+ const comparisonFields = state.fields.filter(f => state.selectedFields.includes(f.id));
+
+ // 养分对比数据
+ const nutrientData = comparisonFields.map(field => ({
+ name: field.name,
+ 全氮: field.nitrogen,
+ 全磷: field.phosphorus,
+ 全钾: field.potassium,
+ }));
+
+ // 如果没有选择地块,显示空状态
+ if (comparisonFields.length === 0) {
+ return (
+
+
+
+ 土壤养分对比 (g/kg)
+
+
+
+ );
+ }
+
+ return (
+
+
+
+ 土壤养分对比 (g/kg)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/crop-x/src/app/(app)/land-information/comparison/chart/components/RadarChart.tsx b/crop-x/src/app/(app)/land-information/comparison/chart/components/RadarChart.tsx
new file mode 100644
index 0000000..0bac777
--- /dev/null
+++ b/crop-x/src/app/(app)/land-information/comparison/chart/components/RadarChart.tsx
@@ -0,0 +1,122 @@
+'use client';
+
+import { Card } from '@/components/ui/card';
+import {
+ RadarChart,
+ Radar as RechartsRadar,
+ PolarGrid,
+ PolarAngleAxis,
+ PolarRadiusAxis,
+ ResponsiveContainer,
+ Legend,
+} from 'recharts';
+import { Radar } from 'lucide-react';
+import { FieldData, useChartAnalysis } from './chartAnalysisReducer';
+
+export function ChartRadarAnalysis() {
+ const { state } = useChartAnalysis();
+
+ const comparisonFields = state.fields.filter(f => state.selectedFields.includes(f.id));
+
+ // 雷达图数据
+ const radarData = [
+ {
+ indicator: 'pH值',
+ ...comparisonFields.reduce((acc, field) => ({
+ ...acc,
+ [field.name]: (field.ph / 10) * 100,
+ }), {}),
+ },
+ {
+ indicator: '有机质',
+ ...comparisonFields.reduce((acc, field) => ({
+ ...acc,
+ [field.name]: (field.organicMatter / 40) * 100,
+ }), {}),
+ },
+ {
+ indicator: '全氮',
+ ...comparisonFields.reduce((acc, field) => ({
+ ...acc,
+ [field.name]: (field.nitrogen / 2.5) * 100,
+ }), {}),
+ },
+ {
+ indicator: '全磷',
+ ...comparisonFields.reduce((acc, field) => ({
+ ...acc,
+ [field.name]: (field.phosphorus / 2.0) * 100,
+ }), {}),
+ },
+ {
+ indicator: '全钾',
+ ...comparisonFields.reduce((acc, field) => ({
+ ...acc,
+ [field.name]: (field.potassium / 25) * 100,
+ }), {}),
+ },
+ {
+ indicator: '土层厚度',
+ ...comparisonFields.reduce((acc, field) => ({
+ ...acc,
+ [field.name]: (field.soilDepth / 100) * 100,
+ }), {}),
+ },
+ ];
+
+ const colors = ['#10b981', '#3b82f6', '#f59e0b', '#ef4444'];
+
+ // 如果没有选择地块,显示空状态
+ if (comparisonFields.length === 0) {
+ return (
+
+
+
+ 多维度雷达图对比
+
+
+ 综合展示各地块在多个指标上的相对优劣势
+
+
+
+
+
请先选择要对比的地块
+
雷达图将显示各地块的多维度指标对比
+
+
+
+ );
+ }
+
+ return (
+
+
+
+ 多维度雷达图对比
+
+
+ 综合展示各地块在多个指标上的相对优劣势
+
+
+
+
+
+
+
+ {comparisonFields.map((field, index) => (
+
+ ))}
+
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/crop-x/src/app/(app)/land-information/comparison/chart/components/YieldComparison.tsx b/crop-x/src/app/(app)/land-information/comparison/chart/components/YieldComparison.tsx
new file mode 100644
index 0000000..de9e7f8
--- /dev/null
+++ b/crop-x/src/app/(app)/land-information/comparison/chart/components/YieldComparison.tsx
@@ -0,0 +1,103 @@
+'use client';
+
+import { Card } from '@/components/ui/card';
+import {
+ ResponsiveContainer,
+ BarChart,
+ Bar,
+ XAxis,
+ YAxis,
+ CartesianGrid,
+ Tooltip,
+ Legend,
+} from 'recharts';
+import { BarChart3, TrendingUp } from 'lucide-react';
+import { FieldData, useChartAnalysis } from './chartAnalysisReducer';
+
+export function YieldComparison() {
+ const { state } = useChartAnalysis();
+
+ const comparisonFields = state.fields.filter(f => state.selectedFields.includes(f.id));
+
+ // 产量对比数据
+ const yieldData = comparisonFields.map(field => ({
+ name: field.name,
+ 产量: field.yield,
+ 有机质: field.organicMatter,
+ }));
+
+ // 如果没有选择地块,显示空状态
+ if (comparisonFields.length === 0) {
+ return (
+
+
+
+
+ 产量对比 (kg/亩)
+
+
+
+
+
+
+
+ 有机质含量对比 (g/kg)
+
+
+
+
+ );
+ }
+
+ return (
+
+
+
+
+ 产量对比 (kg/亩)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 有机质含量对比 (g/kg)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/crop-x/src/app/(app)/land-information/comparison/chart/components/chartAnalysisReducer.tsx b/crop-x/src/app/(app)/land-information/comparison/chart/components/chartAnalysisReducer.tsx
new file mode 100644
index 0000000..966c3ba
--- /dev/null
+++ b/crop-x/src/app/(app)/land-information/comparison/chart/components/chartAnalysisReducer.tsx
@@ -0,0 +1,110 @@
+'use client';
+
+import { useReducer, useCallback } from 'react';
+
+export interface FieldData {
+ id: string;
+ name: string;
+ area: number;
+ location: string;
+ soilType: string;
+ ph: number;
+ organicMatter: number;
+ nitrogen: number;
+ phosphorus: number;
+ potassium: number;
+ soilDepth: number;
+ slope: number;
+ currentCrop: string;
+ yield: number;
+ suitabilityScore: number;
+ suitabilityGrade: '高度适宜' | '一般适宜' | '不适宜';
+ irrigation: string;
+ drainage: string;
+}
+
+export interface ChartAnalysisState {
+ selectedFields: string[];
+ fields: FieldData[];
+}
+
+export type ChartAnalysisAction =
+ | { type: 'SET_FIELDS'; payload: FieldData[] }
+ | { type: 'ADD_FIELD'; payload: string }
+ | { type: 'REMOVE_FIELD'; payload: string }
+ | { type: 'SET_SELECTED_FIELDS'; payload: string[] };
+
+const initialState: ChartAnalysisState = {
+ selectedFields: ['field-1', 'field-2'],
+ fields: [],
+};
+
+export function chartAnalysisReducer(state: ChartAnalysisState, action: ChartAnalysisAction): ChartAnalysisState {
+ switch (action.type) {
+ case 'SET_FIELDS':
+ return {
+ ...state,
+ fields: action.payload,
+ };
+ case 'ADD_FIELD':
+ if (state.selectedFields.length >= 4) {
+ return state;
+ }
+ return {
+ ...state,
+ selectedFields: [...state.selectedFields, action.payload],
+ };
+ case 'REMOVE_FIELD':
+ if (state.selectedFields.length <= 2) {
+ return state;
+ }
+ return {
+ ...state,
+ selectedFields: state.selectedFields.filter(id => id !== action.payload),
+ };
+ case 'SET_SELECTED_FIELDS':
+ return {
+ ...state,
+ selectedFields: action.payload,
+ };
+ default:
+ return state;
+ }
+}
+
+export function useChartAnalysis() {
+ const [state, dispatch] = useReducer(chartAnalysisReducer, initialState);
+
+ const addField = useCallback((fieldId: string) => {
+ if (state.selectedFields.length >= 4) {
+ return false;
+ }
+ dispatch({ type: 'ADD_FIELD', payload: fieldId });
+ return true;
+ }, [state.selectedFields.length]);
+
+ const removeField = useCallback((fieldId: string) => {
+ if (state.selectedFields.length <= 2) {
+ return false;
+ }
+ dispatch({ type: 'REMOVE_FIELD', payload: fieldId });
+ return true;
+ }, [state.selectedFields.length]);
+
+ const setSelectedFields = useCallback((fieldIds: string[]) => {
+ dispatch({ type: 'SET_SELECTED_FIELDS', payload: fieldIds });
+ }, []);
+
+ const setFields = useCallback((fields: FieldData[]) => {
+ dispatch({ type: 'SET_FIELDS', payload: fields });
+ }, []);
+
+ return {
+ state,
+ dispatch,
+ addField,
+ removeField,
+ setSelectedFields,
+ setFields,
+ };
+}
\ No newline at end of file
diff --git a/crop-x/src/app/(app)/land-information/comparison/chart/page.tsx b/crop-x/src/app/(app)/land-information/comparison/chart/page.tsx
index 3bb7278..0d81786 100644
--- a/crop-x/src/app/(app)/land-information/comparison/chart/page.tsx
+++ b/crop-x/src/app/(app)/land-information/comparison/chart/page.tsx
@@ -1,18 +1,158 @@
'use client';
+import { useEffect } from 'react';
import { Card } from '@/components/ui/card';
+import { toast } from 'sonner';
+import { FieldSelector } from './components/FieldSelector';
+import { ChartRadarAnalysis } from './components/RadarChart';
+import { YieldComparison } from './components/YieldComparison';
+import { NutrientComparison } from './components/NutrientComparison';
+import { MapComparison } from './components/MapComparison';
+import { useChartAnalysis, FieldData } from './components/chartAnalysisReducer';
+
+// 模拟地块数据
+const mockFieldsData: FieldData[] = [
+ {
+ id: 'field-1',
+ name: '东区1号地',
+ area: 50.5,
+ location: '东经120.15°, 北纬30.25°',
+ soilType: '壤土',
+ ph: 6.5,
+ organicMatter: 32,
+ nitrogen: 1.8,
+ phosphorus: 1.2,
+ potassium: 18,
+ soilDepth: 85,
+ slope: 3,
+ currentCrop: '水稻',
+ yield: 750,
+ suitabilityScore: 87,
+ suitabilityGrade: '高度适宜',
+ irrigation: '喷灌',
+ drainage: '良好',
+ },
+ {
+ id: 'field-2',
+ name: '西区2号地',
+ area: 45.2,
+ location: '东经120.12°, 北纬30.28°',
+ soilType: '粘土',
+ ph: 7.8,
+ organicMatter: 22,
+ nitrogen: 1.3,
+ phosphorus: 0.9,
+ potassium: 14,
+ soilDepth: 55,
+ slope: 5,
+ currentCrop: '玉米',
+ yield: 650,
+ suitabilityScore: 72,
+ suitabilityGrade: '一般适宜',
+ irrigation: '滴灌',
+ drainage: '中等',
+ },
+ {
+ id: 'field-3',
+ name: '南区3号地',
+ area: 38.8,
+ location: '东经120.18°, 北纬30.22°',
+ soilType: '砂土',
+ ph: 8.5,
+ organicMatter: 15,
+ nitrogen: 0.8,
+ phosphorus: 0.6,
+ potassium: 10,
+ soilDepth: 42,
+ slope: 8,
+ currentCrop: '小麦',
+ yield: 480,
+ suitabilityScore: 58,
+ suitabilityGrade: '不适宜',
+ irrigation: '漫灌',
+ drainage: '较差',
+ },
+ {
+ id: 'field-4',
+ name: '北区4号地',
+ area: 55.0,
+ location: '东经120.20°, 北纬30.30°',
+ soilType: '壤土',
+ ph: 6.8,
+ organicMatter: 28,
+ nitrogen: 1.6,
+ phosphorus: 1.0,
+ potassium: 16,
+ soilDepth: 75,
+ slope: 2,
+ currentCrop: '大豆',
+ yield: 380,
+ suitabilityScore: 82,
+ suitabilityGrade: '高度适宜',
+ irrigation: '喷灌',
+ drainage: '良好',
+ },
+];
export default function ChartPage() {
+ const { setFields, state } = useChartAnalysis();
+
+ // 初始化数据
+ useEffect(() => {
+ // 直接使用模拟数据,确保数据可用
+ setFields(mockFieldsData);
+ localStorage.setItem('chart-analysis-fields', JSON.stringify(mockFieldsData));
+ }, [setFields]);
+
+ // 如果数据还没有加载,显示loading状态
+ if (state.fields.length === 0) {
+ return (
+
+
+
+
可视化图表分析
+
+ 多维度指标可视化展示与对比分析
+
+
+
+
+
+
+
+ );
+ }
+
return (
-
- 图表对比
-
-
- 页面路径: /land-information/comparison/chart
+
+
+
可视化图表分析
+
+ 多维度指标可视化展示与对比分析
-
+
+
+ {/* 地块选择器 */}
+
+
+ {/* 图表分析区域 */}
+
+ {/* 雷达图 */}
+
+
+ {/* 产量与有机质对比 */}
+
+
+ {/* 养分对比 */}
+
+
+ {/* 地图对比 */}
+
+
);
}
\ No newline at end of file
diff --git a/crop-x/src/app/(app)/land-information/comparison/indicator/components/BasicProperties.tsx b/crop-x/src/app/(app)/land-information/comparison/indicator/components/BasicProperties.tsx
new file mode 100644
index 0000000..97168c1
--- /dev/null
+++ b/crop-x/src/app/(app)/land-information/comparison/indicator/components/BasicProperties.tsx
@@ -0,0 +1,71 @@
+'use client';
+
+import { Card } from '@/components/ui/card';
+import { Badge } from '@/components/ui/badge';
+import { MapPin } from 'lucide-react';
+import { FieldData } from './multiDimensionReducer';
+
+interface BasicPropertiesProps {
+ comparisonFields: FieldData[];
+}
+
+export function BasicProperties({ comparisonFields }: BasicPropertiesProps) {
+ return (
+
+
+
+
基础属性对比
+
+
+
+
+
+ | 指标项 |
+ {comparisonFields.map(field => (
+
+ {field.name}
+ |
+ ))}
+
+
+
+
+ | 面积 (亩) |
+ {comparisonFields.map(field => (
+
+ {field.area}
+ |
+ ))}
+
+
+ | 位置 |
+ {comparisonFields.map(field => (
+
+ {field.location}
+ |
+ ))}
+
+
+ | 土壤类型 |
+ {comparisonFields.map(field => (
+
+ {field.soilType}
+ |
+ ))}
+
+
+ | 坡度 (°) |
+ {comparisonFields.map(field => (
+
+
+ {field.slope}
+
+ |
+ ))}
+
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/crop-x/src/app/(app)/land-information/comparison/indicator/components/FieldSelector.tsx b/crop-x/src/app/(app)/land-information/comparison/indicator/components/FieldSelector.tsx
new file mode 100644
index 0000000..babebff
--- /dev/null
+++ b/crop-x/src/app/(app)/land-information/comparison/indicator/components/FieldSelector.tsx
@@ -0,0 +1,123 @@
+'use client';
+
+import { Card } from '@/components/ui/card';
+import { Badge } from '@/components/ui/badge';
+import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
+import { Plus, X } from 'lucide-react';
+import { FieldData } from './multiDimensionReducer';
+
+interface FieldSelectorProps {
+ selectedFields: string[];
+ fieldsData: FieldData[];
+ onAddField: (fieldId: string) => void;
+ onRemoveField: (fieldId: string) => void;
+}
+
+export function FieldSelector({
+ selectedFields,
+ fieldsData,
+ onAddField,
+ onRemoveField
+}: FieldSelectorProps) {
+ const comparisonFields = fieldsData.filter(f => selectedFields.includes(f.id));
+ const availableFields = fieldsData.filter(f => !selectedFields.includes(f.id));
+
+ const getGradeColor = (grade: string) => {
+ switch (grade) {
+ case '高度适宜': return 'bg-green-500 text-white';
+ case '一般适宜': return 'bg-yellow-500 text-white';
+ case '不适宜': return 'bg-red-500 text-white';
+ default: return 'bg-gray-500 text-white';
+ }
+ };
+
+ return (
+
+
+
+
+
+ {comparisonFields.map(field => (
+
+ {field.name}
+
+
+ ))}
+ {availableFields.length > 0 && (
+
+ )}
+
+ {selectedFields.length < 2 && (
+
+ 至少需要选择2个地块进行对比
+
+ )}
+ {selectedFields.length >= 4 && (
+
+ 最多只能同时对比4个地块
+
+ )}
+
+ {/* 选中地块的简要信息 */}
+ {comparisonFields.length > 0 && (
+
+
已选地块信息:
+
+ {comparisonFields.map(field => (
+
+
+ {field.name}
+
+ {field.suitabilityGrade}
+
+
+
+
+ 面积:
+ {field.area}亩
+
+
+ 作物:
+ {field.currentCrop}
+
+
+ 产量:
+ {field.yield}kg/亩
+
+
+ 评分:
+ {field.suitabilityScore}分
+
+
+
+ ))}
+
+
+ )}
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/crop-x/src/app/(app)/land-information/comparison/indicator/components/ManagementStatus.tsx b/crop-x/src/app/(app)/land-information/comparison/indicator/components/ManagementStatus.tsx
new file mode 100644
index 0000000..11e8446
--- /dev/null
+++ b/crop-x/src/app/(app)/land-information/comparison/indicator/components/ManagementStatus.tsx
@@ -0,0 +1,99 @@
+'use client';
+
+import { Card } from '@/components/ui/card';
+import { Badge } from '@/components/ui/badge';
+import { BarChart3 } from 'lucide-react';
+import { FieldData } from './multiDimensionReducer';
+
+interface ManagementStatusProps {
+ comparisonFields: FieldData[];
+}
+
+export function ManagementStatus({ comparisonFields }: ManagementStatusProps) {
+ const getGradeColor = (grade: string) => {
+ switch (grade) {
+ case '高度适宜': return 'bg-green-500 text-white';
+ case '一般适宜': return 'bg-yellow-500 text-white';
+ case '不适宜': return 'bg-red-500 text-white';
+ default: return 'bg-gray-500 text-white';
+ }
+ };
+
+ const getYieldColor = (yieldValue: number) => {
+ if (yieldValue >= 700) return 'text-green-600';
+ if (yieldValue >= 500) return 'text-yellow-600';
+ return 'text-red-600';
+ };
+
+ return (
+
+
+
+
经营现状对比
+
+
+
+
+
+ | 指标项 |
+ {comparisonFields.map(field => (
+
+ {field.name}
+ |
+ ))}
+
+
+
+
+ | 当前作物 |
+ {comparisonFields.map(field => (
+
+ {field.currentCrop}
+ |
+ ))}
+
+
+ | 产量 (kg/亩) |
+ {comparisonFields.map(field => (
+
+
+ {field.yield}
+
+
+ {field.yield >= 700 ? '高产' : field.yield >= 500 ? '中产' : '低产'}
+
+ |
+ ))}
+
+
+ | 灌溉方式 |
+ {comparisonFields.map(field => (
+
+
+ {field.irrigation}
+
+ |
+ ))}
+
+
+ | 排水状况 |
+ {comparisonFields.map(field => (
+
+
+ {field.drainage}
+
+ |
+ ))}
+
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/crop-x/src/app/(app)/land-information/comparison/indicator/components/NaturalConditions.tsx b/crop-x/src/app/(app)/land-information/comparison/indicator/components/NaturalConditions.tsx
new file mode 100644
index 0000000..a541daf
--- /dev/null
+++ b/crop-x/src/app/(app)/land-information/comparison/indicator/components/NaturalConditions.tsx
@@ -0,0 +1,146 @@
+'use client';
+
+import { Card } from '@/components/ui/card';
+import { Badge } from '@/components/ui/badge';
+import { Sprout } from 'lucide-react';
+import { CheckCircle2, AlertCircle } from 'lucide-react';
+import { FieldData } from './multiDimensionReducer';
+
+interface NaturalConditionsProps {
+ comparisonFields: FieldData[];
+}
+
+export function NaturalConditions({ comparisonFields }: NaturalConditionsProps) {
+ const getPhStatus = (ph: number) => {
+ if (ph >= 6.0 && ph <= 7.5) return 'optimal';
+ return 'deviation';
+ };
+
+ const getOrganicStatus = (organic: number) => {
+ if (organic >= 25) return 'optimal';
+ return 'deviation';
+ };
+
+ const getDepthStatus = (depth: number) => {
+ if (depth >= 60) return 'optimal';
+ return 'deviation';
+ };
+
+ return (
+
+
+
+
自然条件对比
+
+
+
+
+
+ | 指标项 |
+ {comparisonFields.map(field => (
+
+ {field.name}
+ |
+ ))}
+
+
+
+
+ | pH值 |
+ {comparisonFields.map(field => (
+
+
+ {field.ph}
+ {getPhStatus(field.ph) === 'optimal' ? (
+
+ ) : (
+
+ )}
+
+
+ 最佳: 6.0-7.5
+
+ |
+ ))}
+
+
+ | 有机质 (g/kg) |
+ {comparisonFields.map(field => (
+
+
+ {field.organicMatter}
+ {getOrganicStatus(field.organicMatter) === 'optimal' ? (
+
+ ) : (
+
+ )}
+
+
+ 最佳: ≥25
+
+ |
+ ))}
+
+
+ | 全氮 (g/kg) |
+ {comparisonFields.map(field => (
+
+ = 1.5 ? 'text-green-600' : field.nitrogen >= 1.0 ? 'text-yellow-600' : 'text-red-600'}>
+ {field.nitrogen}
+
+
+ 最佳: ≥1.5
+
+ |
+ ))}
+
+
+ | 全磷 (g/kg) |
+ {comparisonFields.map(field => (
+
+ = 1.0 ? 'text-green-600' : field.phosphorus >= 0.6 ? 'text-yellow-600' : 'text-red-600'}>
+ {field.phosphorus}
+
+
+ 最佳: ≥1.0
+
+ |
+ ))}
+
+
+ | 全钾 (g/kg) |
+ {comparisonFields.map(field => (
+
+ = 15 ? 'text-green-600' : field.potassium >= 10 ? 'text-yellow-600' : 'text-red-600'}>
+ {field.potassium}
+
+
+ 最佳: ≥15
+
+ |
+ ))}
+
+
+ | 土层厚度 (cm) |
+ {comparisonFields.map(field => (
+
+
+ {field.soilDepth}
+ {getDepthStatus(field.soilDepth) === 'optimal' ? (
+
+ ) : (
+
+ )}
+
+
+ 最佳: ≥60
+
+ |
+ ))}
+
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/crop-x/src/app/(app)/land-information/comparison/indicator/components/SuitabilityEvaluation.tsx b/crop-x/src/app/(app)/land-information/comparison/indicator/components/SuitabilityEvaluation.tsx
new file mode 100644
index 0000000..9817bee
--- /dev/null
+++ b/crop-x/src/app/(app)/land-information/comparison/indicator/components/SuitabilityEvaluation.tsx
@@ -0,0 +1,149 @@
+'use client';
+
+import { Card } from '@/components/ui/card';
+import { Badge } from '@/components/ui/badge';
+import { Target } from 'lucide-react';
+import { FieldData } from './multiDimensionReducer';
+
+interface SuitabilityEvaluationProps {
+ comparisonFields: FieldData[];
+}
+
+export function SuitabilityEvaluation({ comparisonFields }: SuitabilityEvaluationProps) {
+ const getGradeColor = (grade: string) => {
+ switch (grade) {
+ case '高度适宜': return 'bg-green-500 text-white';
+ case '一般适宜': return 'bg-yellow-500 text-white';
+ case '不适宜': return 'bg-red-500 text-white';
+ default: return 'bg-gray-500 text-white';
+ }
+ };
+
+ const getScoreColor = (score: number) => {
+ if (score >= 80) return 'text-green-600 dark:text-green-400';
+ if (score >= 60) return 'text-yellow-600 dark:text-yellow-400';
+ return 'text-red-600 dark:text-red-400';
+ };
+
+ const getScoreLevel = (score: number) => {
+ if (score >= 80) return '优秀';
+ if (score >= 60) return '良好';
+ return '需改善';
+ };
+
+ return (
+
+
+
+
适宜性评价对比
+
+
+
+
+
+ | 指标项 |
+ {comparisonFields.map(field => (
+
+ {field.name}
+ |
+ ))}
+
+
+
+
+ | 综合评分 |
+ {comparisonFields.map(field => (
+
+
+
+ {field.suitabilityScore}
+
+
+ {getScoreLevel(field.suitabilityScore)}
+
+
+ |
+ ))}
+
+
+ | 适宜性等级 |
+ {comparisonFields.map(field => (
+
+
+
+ {field.suitabilityGrade}
+
+
+ {field.suitabilityGrade === '高度适宜' ? '适合多种作物' :
+ field.suitabilityGrade === '一般适宜' ? '适合耐性作物' : '需系统改良'}
+
+
+ |
+ ))}
+
+
+ | 主要限制因子 |
+ {comparisonFields.map(field => {
+ const limitations = [];
+ if (field.ph < 6.0 || field.ph > 7.5) limitations.push('pH值');
+ if (field.organicMatter < 25) limitations.push('有机质');
+ if (field.soilDepth < 60) limitations.push('土层厚度');
+ if (field.nitrogen < 1.5) limitations.push('全氮');
+ if (field.phosphorus < 1.0) limitations.push('全磷');
+ if (field.potassium < 15) limitations.push('全钾');
+
+ return (
+
+ {limitations.length > 0 ? (
+
+ {limitations.slice(0, 2).map((limit, index) => (
+
+ {limit}
+
+ ))}
+ {limitations.length > 2 && (
+
+ +{limitations.length - 2}个
+
+ )}
+
+ ) : (
+ 无限制因子
+ )}
+ |
+ );
+ })}
+
+
+ | 推荐作物类型 |
+ {comparisonFields.map(field => (
+
+
+ {field.suitabilityGrade === '高度适宜' ? (
+ <>
+
+ 经济作物
+
+
+ 粮食作物
+
+ >
+ ) : field.suitabilityGrade === '一般适宜' ? (
+
+ 耐性作物
+
+ ) : (
+
+ 需改良后种植
+
+ )}
+
+ |
+ ))}
+
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/crop-x/src/app/(app)/land-information/comparison/indicator/components/multiDimensionReducer.tsx b/crop-x/src/app/(app)/land-information/comparison/indicator/components/multiDimensionReducer.tsx
new file mode 100644
index 0000000..572ce5e
--- /dev/null
+++ b/crop-x/src/app/(app)/land-information/comparison/indicator/components/multiDimensionReducer.tsx
@@ -0,0 +1,153 @@
+'use client';
+
+export interface FieldData {
+ id: string;
+ name: string;
+ area: number;
+ location: string;
+ soilType: string;
+ ph: number;
+ organicMatter: number;
+ nitrogen: number;
+ phosphorus: number;
+ potassium: number;
+ soilDepth: number;
+ slope: number;
+ currentCrop: string;
+ yield: number;
+ suitabilityScore: number;
+ suitabilityGrade: '高度适宜' | '一般适宜' | '不适宜';
+ irrigation: string;
+ drainage: string;
+}
+
+export interface MultiDimensionState {
+ selectedFields: string[];
+ fieldsData: FieldData[];
+}
+
+export type MultiDimensionAction =
+ | { type: 'SET_SELECTED_FIELDS'; payload: string[] }
+ | { type: 'ADD_FIELD'; payload: string }
+ | { type: 'REMOVE_FIELD'; payload: string }
+ | { type: 'RESET_FIELDS' };
+
+// 模拟地块数据
+const initialFieldsData: FieldData[] = [
+ {
+ id: 'field-1',
+ name: '东区1号地',
+ area: 50.5,
+ location: '东经120.15°, 北纬30.25°',
+ soilType: '壤土',
+ ph: 6.5,
+ organicMatter: 32,
+ nitrogen: 1.8,
+ phosphorus: 1.2,
+ potassium: 18,
+ soilDepth: 85,
+ slope: 3,
+ currentCrop: '水稻',
+ yield: 750,
+ suitabilityScore: 87,
+ suitabilityGrade: '高度适宜',
+ irrigation: '喷灌',
+ drainage: '良好',
+ },
+ {
+ id: 'field-2',
+ name: '西区2号地',
+ area: 45.2,
+ location: '东经120.12°, 北纬30.28°',
+ soilType: '粘土',
+ ph: 7.8,
+ organicMatter: 22,
+ nitrogen: 1.3,
+ phosphorus: 0.9,
+ potassium: 14,
+ soilDepth: 55,
+ slope: 5,
+ currentCrop: '玉米',
+ yield: 650,
+ suitabilityScore: 72,
+ suitabilityGrade: '一般适宜',
+ irrigation: '滴灌',
+ drainage: '中等',
+ },
+ {
+ id: 'field-3',
+ name: '南区3号地',
+ area: 38.8,
+ location: '东经120.18°, 北纬30.22°',
+ soilType: '砂土',
+ ph: 8.5,
+ organicMatter: 15,
+ nitrogen: 0.8,
+ phosphorus: 0.6,
+ potassium: 10,
+ soilDepth: 42,
+ slope: 8,
+ currentCrop: '小麦',
+ yield: 480,
+ suitabilityScore: 58,
+ suitabilityGrade: '不适宜',
+ irrigation: '漫灌',
+ drainage: '较差',
+ },
+ {
+ id: 'field-4',
+ name: '北区4号地',
+ area: 55.0,
+ location: '东经120.20°, 北纬30.30°',
+ soilType: '壤土',
+ ph: 6.8,
+ organicMatter: 28,
+ nitrogen: 1.6,
+ phosphorus: 1.0,
+ potassium: 16,
+ soilDepth: 75,
+ slope: 2,
+ currentCrop: '大豆',
+ yield: 380,
+ suitabilityScore: 82,
+ suitabilityGrade: '高度适宜',
+ irrigation: '喷灌',
+ drainage: '良好',
+ },
+];
+
+export const initialState: MultiDimensionState = {
+ selectedFields: ['field-1', 'field-2'],
+ fieldsData: initialFieldsData,
+};
+
+export function multiDimensionReducer(
+ state: MultiDimensionState,
+ action: MultiDimensionAction
+): MultiDimensionState {
+ switch (action.type) {
+ case 'SET_SELECTED_FIELDS':
+ return { ...state, selectedFields: action.payload };
+
+ case 'ADD_FIELD':
+ if (state.selectedFields.length >= 4) {
+ return state; // 最多只能同时对比4个地块
+ }
+ return { ...state, selectedFields: [...state.selectedFields, action.payload] };
+
+ case 'REMOVE_FIELD':
+ if (state.selectedFields.length <= 2) {
+ return state; // 至少需要选择2个地块进行对比
+ }
+ return {
+ ...state,
+ selectedFields: state.selectedFields.filter(id => id !== action.payload)
+ };
+
+ case 'RESET_FIELDS':
+ return { ...state, selectedFields: ['field-1', 'field-2'] };
+
+ default:
+ return state;
+ }
+}
\ No newline at end of file
diff --git a/crop-x/src/app/(app)/land-information/comparison/indicator/page.tsx b/crop-x/src/app/(app)/land-information/comparison/indicator/page.tsx
index 5456ea4..99773d7 100644
--- a/crop-x/src/app/(app)/land-information/comparison/indicator/page.tsx
+++ b/crop-x/src/app/(app)/land-information/comparison/indicator/page.tsx
@@ -1,18 +1,235 @@
'use client';
-import { Card } from '@/components/ui/card';
+import { useReducer } from 'react';
+import { toast } from 'sonner';
+import {
+ multiDimensionReducer,
+ initialState,
+ MultiDimensionAction,
+ MultiDimensionState,
+ FieldData
+} from './components/multiDimensionReducer';
+import { FieldSelector } from './components/FieldSelector';
+import { BasicProperties } from './components/BasicProperties';
+import { NaturalConditions } from './components/NaturalConditions';
+import { ManagementStatus } from './components/ManagementStatus';
+import { SuitabilityEvaluation } from './components/SuitabilityEvaluation';
+
+export default function MultiDimensionPage() {
+ const [state, dispatch] = useReducer(multiDimensionReducer, initialState);
+
+ // 获取当前选中的对比地块
+ const comparisonFields = state.fieldsData.filter(f => state.selectedFields.includes(f.id));
+
+ const handleAddField = (fieldId: string) => {
+ if (state.selectedFields.length >= 4) {
+ toast.error('最多只能同时对比4个地块');
+ return;
+ }
+ dispatch({ type: 'ADD_FIELD', payload: fieldId });
+ };
+
+ const handleRemoveField = (fieldId: string) => {
+ if (state.selectedFields.length <= 2) {
+ toast.error('至少需要选择2个地块进行对比');
+ return;
+ }
+ dispatch({ type: 'REMOVE_FIELD', payload: fieldId });
+ };
+
+ // 生成统计分析
+ const generateStatistics = () => {
+ if (comparisonFields.length === 0) return null;
+
+ const totalArea = comparisonFields.reduce((sum, f) => sum + f.area, 0);
+ const avgYield = Math.round(comparisonFields.reduce((sum, f) => sum + f.yield, 0) / comparisonFields.length);
+ const avgScore = Math.round(comparisonFields.reduce((sum, f) => sum + f.suitabilityScore, 0) / comparisonFields.length);
+ const avgOrganic = Math.round(comparisonFields.reduce((sum, f) => sum + f.organicMatter, 0) / comparisonFields.length);
+ const highSuitabilityCount = comparisonFields.filter(f => f.suitabilityGrade === '高度适宜').length;
+ const mediumSuitabilityCount = comparisonFields.filter(f => f.suitabilityGrade === '一般适宜').length;
+ const lowSuitabilityCount = comparisonFields.filter(f => f.suitabilityGrade === '不适宜').length;
+
+ return {
+ totalArea,
+ avgYield,
+ avgScore,
+ avgOrganic,
+ highSuitabilityCount,
+ mediumSuitabilityCount,
+ lowSuitabilityCount,
+ bestField: comparisonFields.sort((a, b) => b.suitabilityScore - a.suitabilityScore)[0],
+ highestYieldField: comparisonFields.sort((a, b) => b.yield - a.yield)[0],
+ bestOrganicField: comparisonFields.sort((a, b) => b.organicMatter - a.organicMatter)[0],
+ };
+ };
+
+ const statistics = generateStatistics();
-export default function IndicatorPage() {
return (
-
- 指标对比
-
-
- 页面路径: /land-information/comparison/indicator
+
+
+
多维度指标看板
+
+ 地块基础属性、自然条件、经营现状与适宜性评价的全面对比分析
-
+
+
+ {/* 地块选择器 */}
+
+
+ {/* 统计概览 */}
+ {statistics && (
+
+
+
+
对比地块总面积
+
+ {statistics.totalArea}
+
+
亩
+
+
+
+
+
+
平均产量
+
+ {statistics.avgYield}
+
+
kg/亩
+
+
+
+
+
+
平均适宜性评分
+
+ {statistics.avgScore}
+
+
分
+
+
+
+
+
+
平均有机质
+
+ {statistics.avgOrganic}
+
+
g/kg
+
+
+
+ )}
+
+ {/* 适宜性分布统计 */}
+ {statistics && (
+
+
+
+
+
高度适宜
+
+ {statistics.highSuitabilityCount}
+
+
+
📊
+
+
+
+
+
+
+
一般适宜
+
+ {statistics.mediumSuitabilityCount}
+
+
+
📈
+
+
+
+
+
+
+
不适宜
+
+ {statistics.lowSuitabilityCount}
+
+
+
⚠️
+
+
+
+ )}
+
+ {/* 详细对比表格 */}
+ {comparisonFields.length > 0 && (
+
+ {/* 基础属性对比 */}
+
+
+ {/* 自然条件对比 */}
+
+
+ {/* 经营现状对比 */}
+
+
+ {/* 适宜性评价对比 */}
+
+
+ )}
+
+ {/* 优秀地块展示 */}
+ {statistics && comparisonFields.length > 0 && (
+
+
+
🏆 最优地块
+
+
{statistics.bestField.name}
+
综合评分: {statistics.bestField.suitabilityScore}分
+
产量: {statistics.bestField.yield}kg/亩
+
+
+
+
+
🌾 产量冠军
+
+
{statistics.highestYieldField.name}
+
产量: {statistics.highestYieldField.yield}kg/亩
+
评分: {statistics.highestYieldField.suitabilityScore}分
+
+
+
+
+
🌱 肥力最佳
+
+
{statistics.bestOrganicField.name}
+
有机质: {statistics.bestOrganicField.organicMatter}g/kg
+
评分: {statistics.bestOrganicField.suitabilityScore}分
+
+
+
+ )}
+
+ {/* 空状态 */}
+ {comparisonFields.length === 0 && (
+
+
+
暂无对比地块
+
+ 请先选择2-4个地块进行多维度指标对比分析
+
+
+
+ )}
);
}
\ No newline at end of file
diff --git a/crop-x/src/app/(app)/land-information/comparison/report/components/FieldSelector.tsx b/crop-x/src/app/(app)/land-information/comparison/report/components/FieldSelector.tsx
new file mode 100644
index 0000000..65f93f2
--- /dev/null
+++ b/crop-x/src/app/(app)/land-information/comparison/report/components/FieldSelector.tsx
@@ -0,0 +1,95 @@
+'use client';
+
+import { Card } from '@/components/ui/card';
+import { Button } from '@/components/ui/button';
+import { Badge } from '@/components/ui/badge';
+import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
+import { Progress } from '@/components/ui/progress';
+import { Plus, X, FileText } from 'lucide-react';
+import { ReportComparisonState, FieldData } from './reportComparisonReducer';
+
+interface FieldSelectorProps {
+ state: ReportComparisonState;
+ onAddField: (fieldId: string) => void;
+ onRemoveField: (fieldId: string) => void;
+ onGenerateReport: () => void;
+ fieldsData: FieldData[];
+}
+
+export function FieldSelector({
+ state,
+ onAddField,
+ onRemoveField,
+ onGenerateReport,
+ fieldsData
+}: FieldSelectorProps) {
+ const availableFields = fieldsData.filter(f => !state.selectedFields.includes(f.id));
+ const comparisonFields = fieldsData.filter(f => state.selectedFields.includes(f.id));
+
+ return (
+
+
+
+
+
+ {comparisonFields.map(field => (
+
+ {field.name}
+
+
+ ))}
+ {availableFields.length > 0 && (
+
+ )}
+
+
+
+
+
+
+
+
+ {state.reportGenerating && (
+
+
+ 生成进度
+ {state.reportProgress}%
+
+
+
+ 正在生成报告,请稍候...
+
+
+ )}
+
+ );
+}
\ No newline at end of file
diff --git a/crop-x/src/app/(app)/land-information/comparison/report/components/ReportList.tsx b/crop-x/src/app/(app)/land-information/comparison/report/components/ReportList.tsx
new file mode 100644
index 0000000..151865d
--- /dev/null
+++ b/crop-x/src/app/(app)/land-information/comparison/report/components/ReportList.tsx
@@ -0,0 +1,93 @@
+'use client';
+
+import { Card } from '@/components/ui/card';
+import { Button } from '@/components/ui/button';
+import { FileText, Clock, MapPin, Download, X } from 'lucide-react';
+import { ComparisonReport } from './reportComparisonReducer';
+
+interface ReportListProps {
+ savedReports: ComparisonReport[];
+ onDownloadPDF: (report: ComparisonReport) => void;
+ onDownloadWord: (report: ComparisonReport) => void;
+ onDeleteReport: (reportId: string) => void;
+}
+
+export function ReportList({
+ savedReports,
+ onDownloadPDF,
+ onDownloadWord,
+ onDeleteReport
+}: ReportListProps) {
+ return (
+
+
+
+ 历史报告列表
+
+
+ {savedReports.length === 0 ? (
+
+
+
暂无保存的报告
+
选择地块后点击"生成报告"创建新报告
+
+ ) : (
+
+ {savedReports.map((report) => (
+
+
+
+
+
+ {report.name}
+
+
+
+
+ 生成时间: {report.generatedTime}
+
+
+
+ 对比地块: {report.comparedFields.join('、')}
+
+
+
+
+
+
+
+
+
+
+ ))}
+
+ )}
+
+ );
+}
\ No newline at end of file
diff --git a/crop-x/src/app/(app)/land-information/comparison/report/components/ReportPreview.tsx b/crop-x/src/app/(app)/land-information/comparison/report/components/ReportPreview.tsx
new file mode 100644
index 0000000..d040245
--- /dev/null
+++ b/crop-x/src/app/(app)/land-information/comparison/report/components/ReportPreview.tsx
@@ -0,0 +1,303 @@
+'use client';
+
+import { Card } from '@/components/ui/card';
+import { Badge } from '@/components/ui/badge';
+import { Button } from '@/components/ui/button';
+import { Eye, Zap, Lightbulb, Target, ArrowRight, CheckCircle2, Download } from 'lucide-react';
+import { FieldData } from './reportComparisonReducer';
+
+interface ReportPreviewProps {
+ comparisonFields: FieldData[];
+ onDownloadPDF: () => void;
+ onDownloadWord: () => void;
+}
+
+export function ReportPreview({
+ comparisonFields,
+ onDownloadPDF,
+ onDownloadWord
+}: ReportPreviewProps) {
+ const getGradeColor = (grade: string) => {
+ switch (grade) {
+ case '高度适宜': return 'bg-green-500 text-white';
+ case '一般适宜': return 'bg-yellow-500 text-white';
+ case '不适宜': return 'bg-red-500 text-white';
+ default: return 'bg-gray-500 text-white';
+ }
+ };
+
+ // 生成智能分析结论
+ const generateAnalysis = () => {
+ const analyses: string[] = [];
+
+ // 对比产量和有机质
+ const sortedByYield = [...comparisonFields].sort((a, b) => b.yield - a.yield);
+ const highestYield = sortedByYield[0];
+ const lowestYield = sortedByYield[sortedByYield.length - 1];
+
+ if (highestYield.organicMatter > lowestYield.organicMatter) {
+ const diff = ((highestYield.organicMatter - lowestYield.organicMatter) / lowestYield.organicMatter * 100).toFixed(1);
+ analyses.push(
+ `${highestYield.name}产量高于${lowestYield.name},主要原因是其有机质含量高出${diff}%,建议${lowestYield.name}增施有机肥。`
+ );
+ }
+
+ // 对比pH值
+ const acidFields = comparisonFields.filter(f => f.ph < 6.0);
+ const alkalineFields = comparisonFields.filter(f => f.ph > 7.5);
+
+ if (acidFields.length > 0) {
+ analyses.push(
+ `${acidFields.map(f => f.name).join('、')}的pH值偏低,建议施用石灰或碱性肥料改良。`
+ );
+ }
+
+ if (alkalineFields.length > 0) {
+ analyses.push(
+ `${alkalineFields.map(f => f.name).join('、')}的pH值偏高,建议施用硫磺或酸性肥料降低pH值。`
+ );
+ }
+
+ // 对比土层厚度
+ const shallowFields = comparisonFields.filter(f => f.soilDepth < 60);
+ if (shallowFields.length > 0) {
+ analyses.push(
+ `${shallowFields.map(f => f.name).join('、')}的土层厚度不足,建议进行深耕深松,提高土壤蓄水保肥能力。`
+ );
+ }
+
+ // 对比适宜性
+ const highSuitability = comparisonFields.filter(f => f.suitabilityScore >= 80);
+ const lowSuitability = comparisonFields.filter(f => f.suitabilityScore < 60);
+
+ if (highSuitability.length > 0 && lowSuitability.length > 0) {
+ analyses.push(
+ `${highSuitability.map(f => f.name).join('、')}综合适宜性较高,可优先种植高价值经济作物;${lowSuitability.map(f => f.name).join('、')}需要进行系统改良。`
+ );
+ }
+
+ return analyses;
+ };
+
+ const smartAnalyses = generateAnalysis();
+
+ return (
+
+ {/* 报告预览 */}
+
+
+
+ 当前对比分析报告预览
+
+
+ {/* 报告标题 */}
+
+
地块对比分析报告
+
+ 预览时间: {new Date().toLocaleString('zh-CN', {
+ year: 'numeric',
+ month: '2-digit',
+ day: '2-digit',
+ hour: '2-digit',
+ minute: '2-digit',
+ hour12: false
+ }).replace(/\//g, '-')} | 对比地块: {comparisonFields.map(f => f.name).join('、')}
+
+
+
+ {/* 执行摘要 */}
+
+
+
+ 执行摘要
+
+
+
+
最优地块
+
+ {[...comparisonFields].sort((a, b) => b.suitabilityScore - a.suitabilityScore)[0].name}
+
+
+ 综合评分: {[...comparisonFields].sort((a, b) => b.suitabilityScore - a.suitabilityScore)[0].suitabilityScore}
+
+
+
+
最高产量
+
+ {[...comparisonFields].sort((a, b) => b.yield - a.yield)[0].name}
+
+
+ {[...comparisonFields].sort((a, b) => b.yield - a.yield)[0].yield} kg/亩
+
+
+
+
最佳有机质
+
+ {[...comparisonFields].sort((a, b) => b.organicMatter - a.organicMatter)[0].name}
+
+
+ {[...comparisonFields].sort((a, b) => b.organicMatter - a.organicMatter)[0].organicMatter} g/kg
+
+
+
+
+
+ {/* 智能分析结论 */}
+
+
+
+ 智能分析结论
+
+
+ {smartAnalyses.map((analysis, index) => (
+
+ ))}
+
+
+
+ {/* 改进建议 */}
+
+
+
+ 改进建议
+
+
+ {comparisonFields.map(field => {
+ const suggestions: string[] = [];
+
+ if (field.ph < 6.0 || field.ph > 7.5) {
+ suggestions.push(
+ field.ph < 6.0
+ ? '施用石灰或碱性肥料提高pH值至6.0-7.5'
+ : '施用硫磺或酸性肥料降低pH值至6.0-7.5'
+ );
+ }
+
+ if (field.organicMatter < 25) {
+ suggestions.push('增施有机肥,提高有机质含量至25 g/kg以上');
+ }
+
+ if (field.soilDepth < 60) {
+ suggestions.push('进行深耕深松,改善土层厚度');
+ }
+
+ if (field.nitrogen < 1.5) {
+ suggestions.push('适量施用氮肥,提高全氮含量');
+ }
+
+ if (suggestions.length === 0) {
+ suggestions.push('土壤条件良好,继续保持现有管理措施');
+ }
+
+ return (
+
+
{field.name}
+
+ {suggestions.map((suggestion, i) => (
+ -
+
+ {suggestion}
+
+ ))}
+
+
+ );
+ })}
+
+
+
+ {/* 数据表格 */}
+
+
详细数据表格
+
+
+
+
+ | 地块名称 |
+ 面积 |
+ pH |
+ 有机质 |
+ 产量 |
+ 适宜性 |
+
+
+
+ {comparisonFields.map(field => (
+
+ | {field.name} |
+ {field.area}亩 |
+ {field.ph} |
+ {field.organicMatter} g/kg |
+ {field.yield} kg/亩 |
+
+
+ {field.suitabilityGrade}
+
+ |
+
+ ))}
+
+
+
+
+
+ {/* 图表插入位置 */}
+
+
+ 📊 报告中将自动插入雷达图、柱状图等可视化图表
+
+
+
+
+ {/* 下载当前报告 */}
+
+
+
+
下载当前对比报告
+
+ 将当前预览的报告导出为PDF或Word格式
+
+
+
+
+
+
+
+
+
+
+
+ 💡 报告内容包含:
+
+
+ - • 执行摘要与关键指标对比
+ - • 多维度指标对比表格
+ - • 雷达图、柱状图等可视化图表
+ - • 基于规则引擎的智能分析结论
+ - • 针对性改进建议与措施
+ - • 地块空间分布对比图
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/crop-x/src/app/(app)/land-information/comparison/report/components/reportComparisonReducer.tsx b/crop-x/src/app/(app)/land-information/comparison/report/components/reportComparisonReducer.tsx
new file mode 100644
index 0000000..147cd80
--- /dev/null
+++ b/crop-x/src/app/(app)/land-information/comparison/report/components/reportComparisonReducer.tsx
@@ -0,0 +1,209 @@
+'use client';
+
+import { useReducer } from 'react';
+import { toast } from 'sonner';
+
+export interface FieldData {
+ id: string;
+ name: string;
+ area: number;
+ location: string;
+ soilType: string;
+ ph: number;
+ organicMatter: number;
+ nitrogen: number;
+ phosphorus: number;
+ potassium: number;
+ soilDepth: number;
+ slope: number;
+ currentCrop: string;
+ yield: number;
+ suitabilityScore: number;
+ suitabilityGrade: '高度适宜' | '一般适宜' | '不适宜';
+ irrigation: string;
+ drainage: string;
+}
+
+export interface ComparisonReport {
+ id: string;
+ name: string;
+ generatedTime: string;
+ comparedFields: string[];
+ fieldData: FieldData[];
+}
+
+export interface ReportComparisonState {
+ selectedFields: string[];
+ reportGenerating: boolean;
+ reportProgress: number;
+ savedReports: ComparisonReport[];
+}
+
+export type ReportComparisonAction =
+ | { type: 'SET_SELECTED_FIELDS'; payload: string[] }
+ | { type: 'ADD_FIELD'; payload: string }
+ | { type: 'REMOVE_FIELD'; payload: string }
+ | { type: 'SET_REPORT_GENERATING'; payload: boolean }
+ | { type: 'SET_REPORT_PROGRESS'; payload: number }
+ | { type: 'ADD_SAVED_REPORT'; payload: ComparisonReport }
+ | { type: 'DELETE_REPORT'; payload: string };
+
+const fieldsData: FieldData[] = [
+ {
+ id: 'field-1',
+ name: '东区1号地',
+ area: 50.5,
+ location: '东经120.15°, 北纬30.25°',
+ soilType: '壤土',
+ ph: 6.5,
+ organicMatter: 32,
+ nitrogen: 1.8,
+ phosphorus: 1.2,
+ potassium: 18,
+ soilDepth: 85,
+ slope: 3,
+ currentCrop: '水稻',
+ yield: 750,
+ suitabilityScore: 87,
+ suitabilityGrade: '高度适宜',
+ irrigation: '喷灌',
+ drainage: '良好',
+ },
+ {
+ id: 'field-2',
+ name: '西区2号地',
+ area: 45.2,
+ location: '东经120.12°, 北纬30.28°',
+ soilType: '粘土',
+ ph: 7.8,
+ organicMatter: 22,
+ nitrogen: 1.3,
+ phosphorus: 0.9,
+ potassium: 14,
+ soilDepth: 55,
+ slope: 5,
+ currentCrop: '玉米',
+ yield: 650,
+ suitabilityScore: 72,
+ suitabilityGrade: '一般适宜',
+ irrigation: '滴灌',
+ drainage: '中等',
+ },
+ {
+ id: 'field-3',
+ name: '南区3号地',
+ area: 38.8,
+ location: '东经120.18°, 北纬30.22°',
+ soilType: '砂土',
+ ph: 8.5,
+ organicMatter: 15,
+ nitrogen: 0.8,
+ phosphorus: 0.6,
+ potassium: 10,
+ soilDepth: 42,
+ slope: 8,
+ currentCrop: '小麦',
+ yield: 480,
+ suitabilityScore: 58,
+ suitabilityGrade: '不适宜',
+ irrigation: '漫灌',
+ drainage: '较差',
+ },
+ {
+ id: 'field-4',
+ name: '北区4号地',
+ area: 55.0,
+ location: '东经120.20°, 北纬30.30°',
+ soilType: '壤土',
+ ph: 6.8,
+ organicMatter: 28,
+ nitrogen: 1.6,
+ phosphorus: 1.0,
+ potassium: 16,
+ soilDepth: 75,
+ slope: 2,
+ currentCrop: '大豆',
+ yield: 380,
+ suitabilityScore: 82,
+ suitabilityGrade: '高度适宜',
+ irrigation: '喷灌',
+ drainage: '良好',
+ },
+];
+
+const initialState: ReportComparisonState = {
+ selectedFields: ['field-1', 'field-2'],
+ reportGenerating: false,
+ reportProgress: 0,
+ savedReports: [
+ {
+ id: 'report-1',
+ name: '东区1号地 vs 西区2号地对比分析',
+ generatedTime: '2024-10-15 14:35:22',
+ comparedFields: ['东区1号地', '西区2号地'],
+ fieldData: [],
+ },
+ {
+ id: 'report-2',
+ name: '东区1号地 vs 西区2号地 vs 南区3号地对比分析',
+ generatedTime: '2024-10-14 09:20:15',
+ comparedFields: ['东区1号地', '西区2号地', '南区3号地'],
+ fieldData: [],
+ },
+ ],
+};
+
+export function reportComparisonReducer(
+ state: ReportComparisonState = initialState,
+ action: ReportComparisonAction
+): ReportComparisonState {
+ switch (action.type) {
+ case 'SET_SELECTED_FIELDS':
+ return {
+ ...state,
+ selectedFields: action.payload,
+ };
+ case 'ADD_FIELD':
+ if (state.selectedFields.length >= 4) {
+ toast.error('最多只能同时对比4个地块');
+ return state;
+ }
+ return {
+ ...state,
+ selectedFields: [...state.selectedFields, action.payload],
+ };
+ case 'REMOVE_FIELD':
+ if (state.selectedFields.length <= 2) {
+ toast.error('至少需要选择2个地块进行对比');
+ return state;
+ }
+ return {
+ ...state,
+ selectedFields: state.selectedFields.filter(id => id !== action.payload),
+ };
+ case 'SET_REPORT_GENERATING':
+ return {
+ ...state,
+ reportGenerating: action.payload,
+ };
+ case 'SET_REPORT_PROGRESS':
+ return {
+ ...state,
+ reportProgress: action.payload,
+ };
+ case 'ADD_SAVED_REPORT':
+ return {
+ ...state,
+ savedReports: [action.payload, ...state.savedReports],
+ };
+ case 'DELETE_REPORT':
+ return {
+ ...state,
+ savedReports: state.savedReports.filter(r => r.id !== action.payload),
+ };
+ default:
+ return state;
+ }
+}
+
+export { fieldsData, initialState };
\ No newline at end of file
diff --git a/crop-x/src/app/(app)/land-information/comparison/report/page.tsx b/crop-x/src/app/(app)/land-information/comparison/report/page.tsx
index 92b6db7..31d73c4 100644
--- a/crop-x/src/app/(app)/land-information/comparison/report/page.tsx
+++ b/crop-x/src/app/(app)/land-information/comparison/report/page.tsx
@@ -1,18 +1,307 @@
'use client';
-import { Card } from '@/components/ui/card';
+import { useReducer } from 'react';
+import { toast } from 'sonner';
+import { Button } from '@/components/ui/button';
+import { FileText, Download } from 'lucide-react';
+import {
+ reportComparisonReducer,
+ initialState,
+ ReportComparisonState,
+ ComparisonReport,
+ fieldsData
+} from './components/reportComparisonReducer';
+import { FieldSelector } from './components/FieldSelector';
+import { ReportList } from './components/ReportList';
+import { ReportPreview } from './components/ReportPreview';
export default function ReportPage() {
+ const [state, dispatch] = useReducer(reportComparisonReducer, initialState);
+
+ const comparisonFields = fieldsData.filter(f => state.selectedFields.includes(f.id));
+
+ const handleAddField = (fieldId: string) => {
+ dispatch({ type: 'ADD_FIELD', payload: fieldId });
+ };
+
+ const handleRemoveField = (fieldId: string) => {
+ dispatch({ type: 'REMOVE_FIELD', payload: fieldId });
+ };
+
+ const handleGenerateReport = () => {
+ dispatch({ type: 'SET_REPORT_GENERATING', payload: true });
+ dispatch({ type: 'SET_REPORT_PROGRESS', payload: 0 });
+
+ let progress = 0;
+ const interval = setInterval(() => {
+ progress += 10;
+ dispatch({ type: 'SET_REPORT_PROGRESS', payload: progress });
+
+ if (progress >= 100) {
+ clearInterval(interval);
+ dispatch({ type: 'SET_REPORT_GENERATING', payload: false });
+
+ // 保存新报告
+ const reportName = comparisonFields.map(f => f.name).join(' vs ') + '对比分析';
+ const newReport: ComparisonReport = {
+ id: `report-${Date.now()}`,
+ name: reportName,
+ generatedTime: new Date().toLocaleString('zh-CN', {
+ year: 'numeric',
+ month: '2-digit',
+ day: '2-digit',
+ hour: '2-digit',
+ minute: '2-digit',
+ second: '2-digit',
+ hour12: false
+ }).replace(/\//g, '-'),
+ comparedFields: comparisonFields.map(f => f.name),
+ fieldData: comparisonFields,
+ };
+
+ dispatch({ type: 'ADD_SAVED_REPORT', payload: newReport });
+ toast.success('对比分析报告已生成!');
+ }
+ }, 200);
+ };
+
+ const handleDownloadPDF = async (report?: ComparisonReport) => {
+ const reportData = report || {
+ name: comparisonFields.map(f => f.name).join(' vs ') + '对比分析',
+ generatedTime: new Date().toLocaleString('zh-CN'),
+ comparedFields: comparisonFields.map(f => f.name),
+ fieldData: comparisonFields,
+ };
+
+ toast.info('正在生成PDF报告,请稍候...');
+
+ try {
+ // 简化的PDF生成逻辑(实际项目中可使用jsPDF等库)
+ const pdfContent = generatePDFContent(reportData);
+ downloadFile(pdfContent, `${reportData.name}_${reportData.generatedTime.replace(/:/g, '-').replace(/\//g, '-')}.pdf`, 'application/pdf');
+ toast.success('PDF报告已下载!');
+ } catch (error) {
+ console.error('PDF生成失败:', error);
+ toast.error('PDF生成失败,请重试');
+ }
+ };
+
+ const handleDownloadWord = async (report?: ComparisonReport) => {
+ const reportData = report || {
+ name: comparisonFields.map(f => f.name).join(' vs ') + '对比分析',
+ generatedTime: new Date().toLocaleString('zh-CN'),
+ comparedFields: comparisonFields.map(f => f.name),
+ fieldData: comparisonFields,
+ };
+
+ toast.info('正在生成Word报告,请稍候...');
+
+ try {
+ const fields = reportData.fieldData.length > 0 ? reportData.fieldData : comparisonFields;
+ const bestField = [...fields].sort((a, b) => b.suitabilityScore - a.suitabilityScore)[0];
+ const highestYield = [...fields].sort((a, b) => b.yield - a.yield)[0];
+ const bestOrganic = [...fields].sort((a, b) => b.organicMatter - a.organicMatter)[0];
+
+ // 创建HTML格式的Word文档
+ const htmlContent = `
+
+
+
+
+
+
+
+ 地块对比分析报告
+
+
+
生成时间:${reportData.generatedTime}
+
对比地块:${reportData.comparedFields.join('、')}
+
+
+ 执行摘要
+
+
+ 最优地块:${bestField?.name || 'N/A'}
+ (综合评分: ${bestField?.suitabilityScore || 0})
+
+
+ 最高产量:${highestYield?.name || 'N/A'}
+ (${highestYield?.yield || 0} kg/亩)
+
+
+ 最佳有机质:${bestOrganic?.name || 'N/A'}
+ (${bestOrganic?.organicMatter || 0} g/kg)
+
+
+
+ 详细数据对比
+
+
+
+ | 地块名称 |
+ 面积(亩) |
+ 土壤类型 |
+ pH值 |
+ 有机质(g/kg) |
+ 产量(kg/亩) |
+ 适宜性 |
+
+
+
+ ${fields.map(field => `
+
+ | ${field.name} |
+ ${field.area} |
+ ${field.soilType} |
+ ${field.ph} |
+ ${field.organicMatter} |
+ ${field.yield} |
+ ${field.suitabilityGrade} |
+
+ `).join('')}
+
+
+
+ 智能分析结论
+
+
• 根据对比分析,${bestField?.name}综合适宜性最高,建议优先发展。
+
• ${highestYield?.name}产量表现最优,可作为高产示范田。
+
• ${bestOrganic?.name}有机质含量最佳,土壤肥力较好。
+
+
+
+
+
+ `;
+
+ const blob = new Blob(['\ufeff', htmlContent], {
+ type: 'application/msword'
+ });
+ const url = URL.createObjectURL(blob);
+ const a = document.createElement('a');
+ a.href = url;
+ a.download = `${reportData.name}_${reportData.generatedTime.replace(/:/g, '-').replace(/\//g, '-')}.doc`;
+ document.body.appendChild(a);
+ a.click();
+ document.body.removeChild(a);
+ URL.revokeObjectURL(url);
+
+ toast.success('Word报告已下载!');
+ } catch (error) {
+ console.error('Word生成失败:', error);
+ toast.error('Word生成失败,请重试');
+ }
+ };
+
+ const handleDeleteReport = (reportId: string) => {
+ dispatch({ type: 'DELETE_REPORT', payload: reportId });
+ toast.success('报告已删除');
+ };
+
+ const generatePDFContent = (reportData: any) => {
+ // 简化的PDF内容生成
+ const fields = reportData.fieldData.length > 0 ? reportData.fieldData : comparisonFields;
+ const bestField = [...fields].sort((a, b) => b.suitabilityScore - a.suitabilityScore)[0];
+ const highestYield = [...fields].sort((a, b) => b.yield - a.yield)[0];
+
+ return `
+地块对比分析报告
+================
+
+生成时间: ${reportData.generatedTime}
+对比地块: ${reportData.comparedFields.join('、')}
+
+执行摘要
+--------
+最优地块: ${bestField?.name || 'N/A'} (综合评分: ${bestField?.suitabilityScore || 0})
+最高产量: ${highestYield?.name || 'N/A'} (${highestYield?.yield || 0} kg/亩)
+
+详细数据
+--------
+${fields.map(field => `
+${field.name}:
+- 面积: ${field.area}亩
+- pH值: ${field.ph}
+- 有机质: ${field.organicMatter} g/kg
+- 产量: ${field.yield} kg/亩
+- 适宜性: ${field.suitabilityGrade}
+`).join('\n')}
+
+分析结论
+--------
+根据对比分析,${bestField?.name}综合适宜性最高,建议优先发展。
+${highestYield?.name}产量表现最优,可作为高产示范田。
+ `.trim();
+ };
+
+ const downloadFile = (content: string, filename: string, contentType: string) => {
+ const blob = new Blob([content], { type: contentType });
+ const url = URL.createObjectURL(blob);
+ const a = document.createElement('a');
+ a.href = url;
+ a.download = filename;
+ document.body.appendChild(a);
+ a.click();
+ document.body.removeChild(a);
+ URL.revokeObjectURL(url);
+ };
+
return (
-
- 报告对比
-
-
- 页面路径: /land-information/comparison/report
+
+
+
报告对比生成
+
+ 智能报告生成、多维度对比分析与一键导出
-
+
+
+
+
+
+ {/* 地块选择器 */}
+
+
+ {/* 历史报告列表 */}
+
+
+ {/* 报告预览 */}
+
handleDownloadPDF()}
+ onDownloadWord={() => handleDownloadWord()}
+ />
);
}
\ No newline at end of file
diff --git a/crop-x/src/app/(app)/land-information/suitability/auto/components/AnalysisCharts.tsx b/crop-x/src/app/(app)/land-information/suitability/auto/components/AnalysisCharts.tsx
new file mode 100644
index 0000000..0ffb2b6
--- /dev/null
+++ b/crop-x/src/app/(app)/land-information/suitability/auto/components/AnalysisCharts.tsx
@@ -0,0 +1,306 @@
+'use client';
+
+import { Card } from '@/components/ui/card';
+import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
+import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, PieChart, Pie, Cell, RadarChart, PolarGrid, PolarAngleAxis, PolarRadiusAxis, Radar, LineChart, Line } from 'recharts';
+import { BarChart3, PieChart as PieChartIcon, Target, TrendingUp } from 'lucide-react';
+import { SpatialAnalysisState, SpatialAnalysisAction } from './spatialAnalysisReducer';
+
+interface AnalysisChartsProps {
+ state: SpatialAnalysisState;
+ dispatch: React.Dispatch;
+}
+
+export default function AnalysisCharts({ state, dispatch }: AnalysisChartsProps) {
+ // 准备图表数据
+ const scoreDistributionData = [
+ { range: '0-20分', count: state.analysisResults.filter(r => r.overallScore < 20).length, color: '#dc2626' },
+ { range: '20-40分', count: state.analysisResults.filter(r => r.overallScore >= 20 && r.overallScore < 40).length, color: '#ea580c' },
+ { range: '40-60分', count: state.analysisResults.filter(r => r.overallScore >= 40 && r.overallScore < 60).length, color: '#f59e0b' },
+ { range: '60-80分', count: state.analysisResults.filter(r => r.overallScore >= 60 && r.overallScore < 80).length, color: '#22c55e' },
+ { range: '80-100分', count: state.analysisResults.filter(r => r.overallScore >= 80).length, color: '#16a34a' }
+ ].filter(item => item.count > 0);
+
+ const factorPerformanceData = state.factors
+ .filter(f => f.enabled)
+ .map(factor => ({
+ name: factor.name,
+ weight: Math.round(factor.weight * 100),
+ category: factor.category
+ }))
+ .sort((a, b) => b.weight - a.weight);
+
+ const categoryPerformanceData = [
+ {
+ category: '土壤条件',
+ avgScore: state.analysisResults.length > 0
+ ? Math.round(state.analysisResults.reduce((sum, r) => sum + r.soilScore, 0) / state.analysisResults.length)
+ : 0,
+ weight: Math.round(state.weightConfig.soil * 100)
+ },
+ {
+ category: '气候条件',
+ avgScore: state.analysisResults.length > 0
+ ? Math.round(state.analysisResults.reduce((sum, r) => sum + r.climateScore, 0) / state.analysisResults.length)
+ : 0,
+ weight: Math.round(state.weightConfig.climate * 100)
+ },
+ {
+ category: '地形条件',
+ avgScore: state.analysisResults.length > 0
+ ? Math.round(state.analysisResults.reduce((sum, r) => sum + r.topographyScore, 0) / state.analysisResults.length)
+ : 0,
+ weight: Math.round(state.weightConfig.topography * 100)
+ },
+ {
+ category: '基础设施',
+ avgScore: state.analysisResults.length > 0
+ ? Math.round(state.analysisResults.reduce((sum, r) => sum + r.infrastructureScore, 0) / state.analysisResults.length)
+ : 0,
+ weight: Math.round(state.weightConfig.infrastructure * 100)
+ }
+ ];
+
+ const cropRecommendationData = state.analysisResults
+ .flatMap(result => result.recommendedCrops.slice(0, 3))
+ .reduce((acc, rec) => {
+ const existing = acc.find(item => item.crop === rec.crop.name);
+ if (existing) {
+ existing.frequency += 1;
+ existing.avgScore = Math.round((existing.avgScore + rec.suitabilityScore) / 2);
+ } else {
+ acc.push({
+ crop: rec.crop.name,
+ frequency: 1,
+ avgScore: rec.suitabilityScore,
+ category: rec.crop.category
+ });
+ }
+ return acc;
+ }, [] as any[])
+ .sort((a, b) => b.frequency - a.frequency)
+ .slice(0, 10);
+
+ const economicReturnData = state.analysisResults
+ .map(result => {
+ const block = state.landBlocks.find(b => b.id === result.blockId);
+ return {
+ name: result.blockName,
+ returnPerAcre: result.economicReturn && block?.area
+ ? Math.round(result.economicReturn / block.area)
+ : 0,
+ totalReturn: result.economicReturn || 0,
+ score: result.overallScore
+ };
+ })
+ .sort((a, b) => b.returnPerAcre - a.returnPerAcre);
+
+ const COLORS = ['#0088FE', '#00C49F', '#FFBB28', '#FF8042', '#8884D8', '#82CA9D'];
+
+ if (state.analysisResults.length === 0) {
+ return (
+
+
+ 暂无图表数据
+
+ 请先运行分析生成结果
+
+
+ );
+ }
+
+ return (
+
+
+
+
+
+ 得分分布
+
+
+
+ 因子分析
+
+
+
+ 作物推荐
+
+
+
+ 经济效益
+
+
+
+ 对比分析
+
+
+
+ {/* 得分分布 */}
+
+
+
+ 得分分布直方图
+
+
+
+
+
+
+
+ {scoreDistributionData.map((entry, index) => (
+ |
+ ))}
+
+
+
+
+
+
+ 等级分布饼图
+
+
+ `${range}: ${count}个`}
+ outerRadius={80}
+ fill="#8884d8"
+ dataKey="count"
+ >
+ {scoreDistributionData.map((entry, index) => (
+ |
+ ))}
+
+
+
+
+
+
+
+
+ {/* 因子分析 */}
+
+
+
+ 评价因子权重分布
+
+
+
+
+
+
+
+
+
+
+
+
+ 各维度得分对比
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* 作物推荐 */}
+
+
+
+ 作物推荐频率
+
+
+
+
+
+
+
+
+
+
+
+
+ 作物适宜性得分
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* 经济效益 */}
+
+
+
+ 地块亩均收益
+
+
+
+
+
+
+
+
+
+
+
+
+ 得分与收益相关性
+
+ a.score - b.score)}>
+
+
+
+
+
+
+
+
+
+
+
+ {/* 对比分析 */}
+
+
+ 地块综合对比
+
+ ({
+ name: result.blockName,
+ 综合得分: result.overallScore,
+ 土壤得分: Math.round(result.soilScore),
+ 气候得分: Math.round(result.climateScore),
+ 地形得分: Math.round(result.topographyScore),
+ 基础设施得分: Math.round(result.infrastructureScore)
+ }))}>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/crop-x/src/app/(app)/land-information/suitability/auto/components/AnalysisControlPanel.tsx b/crop-x/src/app/(app)/land-information/suitability/auto/components/AnalysisControlPanel.tsx
new file mode 100644
index 0000000..5613854
--- /dev/null
+++ b/crop-x/src/app/(app)/land-information/suitability/auto/components/AnalysisControlPanel.tsx
@@ -0,0 +1,285 @@
+'use client';
+
+import { Card } from '@/components/ui/card';
+import { Button } from '@/components/ui/button';
+import { Badge } from '@/components/ui/badge';
+import { Progress } from '@/components/ui/progress';
+import {
+ Settings,
+ Sliders,
+ BarChart3,
+ AlertTriangle,
+ CheckCircle,
+ TrendingUp,
+ Info
+} from 'lucide-react';
+import { SpatialAnalysisState, SpatialAnalysisAction } from './spatialAnalysisReducer';
+
+interface AnalysisControlPanelProps {
+ state: SpatialAnalysisState;
+ dispatch: React.Dispatch;
+}
+
+export default function AnalysisControlPanel({ state, dispatch }: AnalysisControlPanelProps) {
+ const handleQuickAnalysis = () => {
+ // 快速分析,使用默认设置
+ const allBlocks = state.landBlocks.map(b => b.id);
+ dispatch({ type: 'SET_SELECTED_BLOCKS', payload: allBlocks });
+ // 触发分析
+ setTimeout(() => {
+ const event = new CustomEvent('run-analysis');
+ window.dispatchEvent(event);
+ }, 100);
+ };
+
+ const handleResetFilters = () => {
+ dispatch({ type: 'RESET_FILTERS' });
+ };
+
+ const enabledFactorsCount = state.factors.filter(f => f.enabled).length;
+ const totalWeight = state.weightConfig.soil + state.weightConfig.climate +
+ state.weightConfig.topography + state.weightConfig.infrastructure;
+
+ // 获取风险等级
+ const getRiskLevel = () => {
+ if (state.analysisResults.length === 0) return { level: 'unknown', color: 'gray', text: '未分析' };
+
+ const avgScore = state.analysisResults.reduce((sum, r) => sum + r.overallScore, 0) / state.analysisResults.length;
+
+ if (avgScore >= 80) return { level: 'low', color: 'green', text: '低风险' };
+ if (avgScore >= 60) return { level: 'medium', color: 'yellow', text: '中等风险' };
+ return { level: 'high', color: 'red', text: '高风险' };
+ };
+
+ const riskLevel = getRiskLevel();
+
+ return (
+
+ {/* 分析状态 */}
+
+
+
+ 分析状态
+
+
+
+
+ 选择地块
+ 0 ? 'default' : 'secondary'}>
+ {state.selectedBlocks.length} / {state.landBlocks.length}
+
+
+
+
+ 评价因子
+
+ {enabledFactorsCount} / {state.factors.length}
+
+
+
+
+ 分析结果
+ 0 ? 'default' : 'secondary'}>
+ {state.analysisResults.length} 个
+
+
+
+
+
+ {/* 权重配置 */}
+
+
+
+ 权重配置
+
+
+
+
+
+ 土壤条件
+ {Math.round(state.weightConfig.soil * 100)}%
+
+
+
+
+
+
+ 气候条件
+ {Math.round(state.weightConfig.climate * 100)}%
+
+
+
+
+
+
+ 地形条件
+ {Math.round(state.weightConfig.topography * 100)}%
+
+
+
+
+
+
+ 基础设施
+ {Math.round(state.weightConfig.infrastructure * 100)}%
+
+
+
+
+
+
+
+ 总权重: {Math.round(totalWeight * 100)}%
+ {Math.abs(totalWeight - 1) > 0.01 && (
+ (需调整为100%)
+ )}
+
+
+
+
+
+
+ {/* 风险评估 */}
+
+
+
+ 风险评估
+
+
+
+
+ 综合风险等级
+
+ {riskLevel.text}
+
+
+
+ {state.analysisResults.length > 0 && (
+ <>
+
+
+ {Math.round(
+ state.analysisResults.reduce((sum, r) => sum + r.overallScore, 0) /
+ state.analysisResults.length
+ )}分
+
+
平均适宜性得分
+
+
+
+
+ 优秀地块 (≥80分)
+
+ {state.analysisResults.filter(r => r.overallScore >= 80).length} 个
+
+
+
+ 良好地块 (60-79分)
+
+ {state.analysisResults.filter(r => r.overallScore >= 60 && r.overallScore < 80).length} 个
+
+
+
+ 需改进 (<60分)
+
+ {state.analysisResults.filter(r => r.overallScore < 60).length} 个
+
+
+
+ >
+ )}
+
+
+
+ {/* 快速操作 */}
+
+
+
+ 快速操作
+
+
+
+
+
+
+
+
+
+
+
+ {/* 分析建议 */}
+ {state.analysisResults.length > 0 && (
+
+
+
+ 智能建议
+
+
+
+
+
优先推荐作物
+
+ {(() => {
+ const cropCounts = state.analysisResults.flatMap(r =>
+ r.recommendedCrops.slice(0, 2).map(c => c.crop.name)
+ );
+ const topCrops = [...new Set(cropCounts)].slice(0, 3);
+ return topCrops.join('、');
+ })()}
+
+
+
+
+
最佳种植地块
+
+ {state.analysisResults
+ .sort((a, b) => b.overallScore - a.overallScore)
+ .slice(0, 2)
+ .map(r => r.blockName)
+ .join('、')}
+
+
+
+
+ )}
+
+ );
+}
\ No newline at end of file
diff --git a/crop-x/src/app/(app)/land-information/suitability/auto/components/AnalysisReport.tsx b/crop-x/src/app/(app)/land-information/suitability/auto/components/AnalysisReport.tsx
new file mode 100644
index 0000000..fc80c43
--- /dev/null
+++ b/crop-x/src/app/(app)/land-information/suitability/auto/components/AnalysisReport.tsx
@@ -0,0 +1,506 @@
+'use client';
+
+import { Card } from '@/components/ui/card';
+import { Button } from '@/components/ui/button';
+import { Badge } from '@/components/ui/badge';
+import { Progress } from '@/components/ui/progress';
+import {
+ FileText,
+ Download,
+ Calendar,
+ MapPin,
+ BarChart3,
+ Star,
+ AlertTriangle,
+ CheckCircle,
+ TrendingUp,
+ Users
+} from 'lucide-react';
+import { SpatialAnalysisState, SpatialAnalysisAction } from './spatialAnalysisReducer';
+
+interface AnalysisReportProps {
+ state: SpatialAnalysisState;
+ dispatch: React.Dispatch;
+}
+
+export default function AnalysisReport({ state, dispatch }: AnalysisReportProps) {
+ const handleGenerateReport = () => {
+ // 生成分析报告
+ const reportTitle = `地块适宜性分析报告_${new Date().toLocaleDateString('zh-CN')}`;
+ const reportData = generateReportData();
+
+ dispatch({
+ type: 'GENERATE_REPORT',
+ payload: { title: reportTitle }
+ });
+
+ // 设置当前报告
+ dispatch({
+ type: 'SET_CURRENT_REPORT',
+ payload: reportData
+ });
+ };
+
+ const generateReportData = () => {
+ const selectedBlocks = state.landBlocks.filter(b => state.selectedBlocks.includes(b.id));
+ const selectedResults = state.analysisResults.filter(r => state.selectedBlocks.includes(r.blockId));
+
+ // 统计推荐作物
+ const cropStats = selectedResults
+ .flatMap(result => result.recommendedCrops.slice(0, 3))
+ .reduce((acc, rec) => {
+ const existing = acc.find(item => item.crop === rec.crop.name);
+ if (existing) {
+ existing.frequency += 1;
+ existing.totalScore += rec.suitabilityScore;
+ existing.avgScore = Math.round(existing.totalScore / existing.frequency);
+ } else {
+ acc.push({
+ crop: rec.crop.name,
+ category: rec.crop.category,
+ frequency: 1,
+ totalScore: rec.suitabilityScore,
+ avgScore: rec.suitabilityScore,
+ economicValue: rec.crop.economicValue
+ });
+ }
+ return acc;
+ }, [] as any[])
+ .sort((a, b) => b.frequency - a.frequency);
+
+ // 风险统计
+ const riskStats = {
+ low: selectedResults.filter(r => r.overallScore >= 80).length,
+ medium: selectedResults.filter(r => r.overallScore >= 60 && r.overallScore < 80).length,
+ high: selectedResults.filter(r => r.overallScore < 60).length
+ };
+
+ return {
+ id: Date.now().toString(),
+ title: `地块适宜性分析报告_${new Date().toLocaleDateString('zh-CN')}`,
+ createdAt: new Date().toLocaleString('zh-CN'),
+ landBlocks: selectedBlocks,
+ results: selectedResults,
+ factors: state.factors.filter(f => f.enabled),
+ weightConfig: state.weightConfig,
+ summary: {
+ totalBlocks: selectedBlocks.length,
+ averageScore: selectedResults.length > 0
+ ? Math.round(selectedResults.reduce((sum, r) => sum + r.overallScore, 0) / selectedResults.length)
+ : 0,
+ topRecommendedCrops: cropStats.slice(0, 5),
+ riskDistribution: riskStats,
+ totalArea: selectedBlocks.reduce((sum, b) => sum + b.area, 0),
+ estimatedProduction: selectedResults.reduce((sum, r) => sum + (r.estimatedYield || 0), 0),
+ estimatedRevenue: selectedResults.reduce((sum, r) => sum + (r.economicReturn || 0), 0)
+ }
+ };
+ };
+
+ const handleDownloadReport = () => {
+ const report = state.currentReport || generateReportData();
+
+ // 生成HTML报告
+ const htmlContent = `
+
+
+
+
+
+ ${report.title}
+
+
+
+
+
+
+
执行摘要
+
+
+
分析概览
+
地块数量: ${report.summary.totalBlocks} 个
+
总面积: ${report.summary.totalArea} 亩
+
平均得分: ${report.summary.averageScore} 分
+
+
+
经济效益
+
预估产量: ${report.summary.estimatedProduction.toLocaleString()} kg
+
预估收益: ¥${report.summary.estimatedRevenue.toLocaleString()}
+
亩均收益: ¥${Math.round(report.summary.estimatedRevenue / report.summary.totalArea)}
+
+
+
+
+
+
风险分布
+
+
+
低风险地块
+
${report.summary.riskDistribution.low} 个
+
+
+
+
中等风险地块
+
${report.summary.riskDistribution.medium} 个
+
+
+
+
高风险地块
+
${report.summary.riskDistribution.high} 个
+
+
+
+
+
+
+
推荐作物排名
+
+
+
+ | 排名 |
+ 作物名称 |
+ 类别 |
+ 推荐频次 |
+ 平均适宜性 |
+ 经济效益 |
+
+
+
+ ${report.summary.topRecommendedCrops.map((crop, index) => `
+
+ | ${index + 1} |
+ ${crop.crop} |
+ ${crop.category} |
+ ${crop.frequency} 次 |
+ ${crop.avgScore} 分 |
+ ¥${crop.economicValue}/亩 |
+
+ `).join('')}
+
+
+
+
+
+
详细地块分析
+ ${report.results.map(result => {
+ const block = report.landBlocks.find(b => b.id === result.blockId);
+ return `
+
+
${result.blockName}
+
地块信息: ${block?.area} 亩 | ${block?.soilType} | ${block?.irrigation}
+
综合得分: ${result.overallScore} 分
+
推荐作物: ${result.recommendedCrops.slice(0, 3).map(c => c.crop.name).join('、')}
+
风险因素: ${result.riskFactors.join(';') || '无'}
+
改进建议: ${result.improvementSuggestions.join(';')}
+
+ `;
+ }).join('')}
+
+
+
+
分析说明
+
+
评价方法
+
本次分析采用多因子综合评价法,考虑土壤条件、气候条件、地形条件和基础设施四个维度。
+
权重配置:
+
+ - 土壤条件: ${Math.round(report.weightConfig.soil * 100)}%
+ - 气候条件: ${Math.round(report.weightConfig.climate * 100)}%
+ - 地形条件: ${Math.round(report.weightConfig.topography * 100)}%
+ - 基础设施: ${Math.round(report.weightConfig.infrastructure * 100)}%
+
+
+
+
+
+
本报告由智慧农业生产管理系统自动生成
+
生成时间: ${new Date().toLocaleString('zh-CN')}
+
+
+
+ `;
+
+ // 下载HTML文件
+ const blob = new Blob([htmlContent], { type: 'text/html;charset=utf-8;' });
+ const link = document.createElement('a');
+ link.href = URL.createObjectURL(blob);
+ link.download = `${state.currentReport?.title || '分析报告'}.html`;
+ link.click();
+ };
+
+ const currentReport = state.currentReport || generateReportData();
+
+ if (state.analysisResults.length === 0) {
+ return (
+
+
+ 暂无分析数据
+
+ 请先运行分析生成结果
+
+
+
+ );
+ }
+
+ return (
+
+ {/* 报告操作 */}
+
+
+
+
分析报告
+
+
+
+
+
+
+
+ {/* 报告概览 */}
+
+
+
+
{currentReport.title}
+
+ {currentReport.createdAt}
+
+
+
+
+
+
+ {currentReport.summary.totalBlocks}
+
+
分析地块
+
+
+
+
+ {currentReport.summary.averageScore}
+
+
平均得分
+
+
+
+
+ {currentReport.summary.totalArea}
+
+
总面积(亩)
+
+
+
+
+ {Math.round(currentReport.summary.estimatedRevenue / 1000)}K
+
+
预估收益
+
+
+
+
+ {/* 推荐作物排名 */}
+
+
+
+ 推荐作物排名
+
+
+
+ {currentReport.summary.topRecommendedCrops.map((crop, index) => (
+
+
+
+ {index + 1}
+
+
+
{crop.crop}
+
{crop.category}
+
+
+
+
+
+
推荐频次
+
{crop.frequency} 次
+
+
+
适宜性
+
{crop.avgScore} 分
+
+
+
经济效益
+
¥{crop.economicValue}/亩
+
+
+
+ ))}
+
+
+
+ {/* 风险分析 */}
+
+
+
+ 风险分析
+
+
+
+
+
+ 低风险地块
+ {currentReport.summary.riskDistribution.low} 个
+
+
+
+ 综合得分≥80分,适宜种植推荐作物
+
+
+
+
+
+ 中等风险地块
+ {currentReport.summary.riskDistribution.medium} 个
+
+
+
+ 综合得分60-79分,需要一定改进措施
+
+
+
+
+
+ 高风险地块
+ {currentReport.summary.riskDistribution.high} 个
+
+
+
+ 综合得分<60分,需要重点改进
+
+
+
+
+
+ {/* 详细分析结果 */}
+
+
+
+ 详细分析结果
+
+
+
+ {currentReport.results.map(result => {
+ const block = currentReport.landBlocks.find(b => b.id === result.blockId);
+ return (
+
+
+
+
+ {result.blockName}
+
+
+ {result.overallScore} 分
+
+
+
+
+
+ 面积:
+ {block?.area} 亩
+
+
+ 土壤:
+ {block?.soilType}
+
+
+ 灌溉:
+ {block?.irrigation}
+
+
+ 预估收益:
+ ¥{result.economicReturn?.toLocaleString()}
+
+
+
+
+
+
推荐作物:
+
+ {result.recommendedCrops.slice(0, 3).map((rec, index) => (
+
+ {rec.crop.name} ({rec.suitabilityScore}分)
+
+ ))}
+
+
+
+
+
风险因素:
+
+ {result.riskFactors.length > 0 ? result.riskFactors.join(';') : '无'}
+
+
+
+
+ );
+ })}
+
+
+
+ {/* 报告说明 */}
+
+
+
+
+
报告说明:
+
+ - • 本报告基于多因子综合评价模型,结合土壤、气候、地形、基础设施等条件进行分析
+ - • 推荐作物基于各地区的农业实践数据和科学研究成果
+ - • 经济效益估算仅供参考,实际收益受市场价格、管理水平等多种因素影响
+ - • 建议结合实地调研和专家意见,制定最终的种植决策方案
+
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/crop-x/src/app/(app)/land-information/suitability/auto/components/AnalysisResults.tsx b/crop-x/src/app/(app)/land-information/suitability/auto/components/AnalysisResults.tsx
new file mode 100644
index 0000000..693567c
--- /dev/null
+++ b/crop-x/src/app/(app)/land-information/suitability/auto/components/AnalysisResults.tsx
@@ -0,0 +1,334 @@
+'use client';
+
+import { Card } from '@/components/ui/card';
+import { Button } from '@/components/ui/button';
+import { Badge } from '@/components/ui/badge';
+import { Progress } from '@/components/ui/progress';
+import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
+import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
+import {
+ Download,
+ CheckCircle,
+ AlertTriangle,
+ Star,
+ TrendingUp,
+ BarChart3,
+ Filter,
+ ArrowUpDown
+} from 'lucide-react';
+import { SpatialAnalysisState, SpatialAnalysisAction } from './spatialAnalysisReducer';
+
+interface AnalysisResultsProps {
+ state: SpatialAnalysisState;
+ dispatch: React.Dispatch;
+}
+
+export default function AnalysisResults({ state, dispatch }: AnalysisResultsProps) {
+ const getScoreColor = (score: number) => {
+ if (score >= 80) return 'text-green-600';
+ if (score >= 60) return 'text-yellow-600';
+ return 'text-red-600';
+ };
+
+ const getGradeLabel = (score: number) => {
+ if (score >= 80) return '高度适宜';
+ if (score >= 60) return '一般适宜';
+ return '不适宜';
+ };
+
+ const getGradeColor = (score: number) => {
+ if (score >= 80) return 'bg-green-100 text-green-800';
+ if (score >= 60) return 'bg-yellow-100 text-yellow-800';
+ return 'bg-red-100 text-red-800';
+ };
+
+ // 筛选结果
+ const filteredResults = state.analysisResults.filter(result => {
+ if (state.filters.scoreRange) {
+ const [min, max] = state.filters.scoreRange;
+ if (result.overallScore < min || result.overallScore > max) {
+ return false;
+ }
+ }
+ return true;
+ });
+
+ // 排序结果
+ const sortedResults = [...filteredResults].sort((a, b) => {
+ let aValue: number, bValue: number;
+
+ switch (state.sortBy) {
+ case 'name':
+ aValue = a.blockName.charCodeAt(0);
+ bValue = b.blockName.charCodeAt(0);
+ break;
+ case 'area':
+ aValue = a.estimatedYield || 0;
+ bValue = b.estimatedYield || 0;
+ break;
+ case 'score':
+ default:
+ aValue = a.overallScore;
+ bValue = b.overallScore;
+ break;
+ }
+
+ return state.sortOrder === 'asc' ? aValue - bValue : bValue - aValue;
+ });
+
+ const exportResults = () => {
+ // 导出分析结果为CSV
+ const headers = [
+ '地块名称', '综合得分', '适宜性等级', '土壤得分', '气候得分', '地形得分', '基础设施得分',
+ '推荐作物', '预估产量(kg)', '经济效益(元)', '风险因素', '改进建议'
+ ];
+
+ const csvContent = [
+ headers.join(','),
+ ...sortedResults.map(result => {
+ const topCrop = result.recommendedCrops[0];
+ const riskFactors = result.riskFactors.join(';') || '无';
+ const improvements = result.improvementSuggestions.join(';') || '无';
+
+ return [
+ result.blockName,
+ result.overallScore,
+ getGradeLabel(result.overallScore),
+ Math.round(result.soilScore),
+ Math.round(result.climateScore),
+ Math.round(result.topographyScore),
+ Math.round(result.infrastructureScore),
+ topCrop ? topCrop.crop.name : '无',
+ result.estimatedYield?.toLocaleString() || '',
+ result.economicReturn?.toLocaleString() || '',
+ riskFactors,
+ improvements
+ ].join(',');
+ })
+ ].join('\n');
+
+ const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
+ const link = document.createElement('a');
+ link.href = URL.createObjectURL(blob);
+ link.download = `地块适宜性评价结果_${new Date().toLocaleDateString('zh-CN')}.csv`;
+ link.click();
+ };
+
+ if (state.analysisResults.length === 0) {
+ return null;
+ }
+
+ return (
+
+
+
+
+
+ 地块适宜性评价结果
+
+
+ 共 {sortedResults.length} 个地块的评价结果
+
+
+
+
+
+
+
+
+
+
+
+ {/* 统计摘要 */}
+
+
+
+ {sortedResults.filter(r => r.overallScore >= 80).length}
+
+
高度适宜
+
+
+
+ {sortedResults.filter(r => r.overallScore >= 60 && r.overallScore < 80).length}
+
+
一般适宜
+
+
+
+ {sortedResults.filter(r => r.overallScore < 60).length}
+
+
不适宜
+
+
+
+ {Math.round(
+ sortedResults.reduce((sum, r) => sum + r.overallScore, 0) /
+ sortedResults.length
+ )}
+
+
平均得分
+
+
+
+ {/* 结果表格 */}
+
+
+
+
+ 地块名称
+ 综合得分
+ 适宜性等级
+ 土壤得分
+ 气候得分
+ 地形得分
+ 基础设施得分
+ 推荐作物
+ 预估产量
+ 经济效益
+ 风险因素
+ 改进建议
+
+
+
+ {sortedResults.map((result, index) => {
+ const topCrop = result.recommendedCrops[0];
+ return (
+
+ {result.blockName}
+
+
+ {result.overallScore}
+
+
+
+
+
+ {getGradeLabel(result.overallScore)}
+
+
+
+
+ {Math.round(result.soilScore)}
+
+
+
+
+ {Math.round(result.climateScore)}
+
+
+
+
+ {Math.round(result.topographyScore)}
+
+
+
+
+ {Math.round(result.infrastructureScore)}
+
+
+
+ {topCrop && (
+
+
{topCrop.crop.name}
+
+ {topCrop.crop.category}
+
+
+ )}
+
+
+
+
{result.estimatedYield?.toLocaleString()} kg
+
+
+
+
+
¥{result.economicReturn?.toLocaleString()}
+
+
+
+
+ {result.riskFactors.length > 0 ? (
+
+ {result.riskFactors.join('、')}
+
+ ) : (
+
无明显风险
+ )}
+
+
+
+
+
+ {result.improvementSuggestions.join('、')}
+
+
+
+
+ );
+ })}
+
+
+
+
+ {/* 详细统计信息 */}
+
+
+
+
+
统计分析
+
+
+
得分分布
+
+ - • 高度适宜 (80-100分): {sortedResults.filter(r => r.overallScore >= 80).length} 个地块
+ - • 一般适宜 (60-79分): {sortedResults.filter(r => r.overallScore >= 60 && r.overallScore < 80).length} 个地块
+ - • 不适宜 (0-59分): {sortedResults.filter(r => r.overallScore < 60).length} 个地块
+
+
+
+
经济效益
+
+ - • 总预估产量: {sortedResults.reduce((sum, r) => sum + (r.estimatedYield || 0), 0).toLocaleString()} kg
+ - • 总经济效益: ¥{sortedResults.reduce((sum, r) => sum + (r.economicReturn || 0), 0).toLocaleString()}
+ - • 平均亩产: {Math.round(sortedResults.reduce((sum, r) => sum + (r.estimatedYield || 0), 0) / sortedResults.length).toLocaleString()} kg
+
+
+
+
因子表现
+
+ - • 平均土壤得分: {Math.round(sortedResults.reduce((sum, r) => sum + r.soilScore, 0) / sortedResults.length)} 分
+ - • 平均气候得分: {Math.round(sortedResults.reduce((sum, r) => sum + r.climateScore, 0) / sortedResults.length)} 分
+ - • 平均地形得分: {Math.round(sortedResults.reduce((sum, r) => sum + r.topographyScore, 0) / sortedResults.length)} 分
+
+
+
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/crop-x/src/app/(app)/land-information/suitability/auto/components/BatchAnalysisPanel.tsx b/crop-x/src/app/(app)/land-information/suitability/auto/components/BatchAnalysisPanel.tsx
new file mode 100644
index 0000000..2870a99
--- /dev/null
+++ b/crop-x/src/app/(app)/land-information/suitability/auto/components/BatchAnalysisPanel.tsx
@@ -0,0 +1,375 @@
+'use client';
+
+import { Card } from '@/components/ui/card';
+import { Button } from '@/components/ui/button';
+import { Badge } from '@/components/ui/badge';
+import { Progress } from '@/components/ui/progress';
+import {
+ Play,
+ Pause,
+ Square,
+ RotateCcw,
+ Clock,
+ CheckCircle,
+ XCircle,
+ Loader2,
+ Activity,
+ Database,
+ TrendingUp
+} from 'lucide-react';
+import { SpatialAnalysisState, SpatialAnalysisAction } from './spatialAnalysisReducer';
+import BatchAnalysisManager from './batchAnalysisService';
+
+interface BatchAnalysisPanelProps {
+ state: SpatialAnalysisState;
+ dispatch: React.Dispatch;
+}
+
+export default function BatchAnalysisPanel({ state, dispatch }: BatchAnalysisPanelProps) {
+ const manager = new BatchAnalysisManager(dispatch);
+
+ const handleStartBatchAnalysis = async () => {
+ if (state.selectedBlocks.length === 0) {
+ alert('请先选择要分析的地块');
+ return;
+ }
+
+ const selectedBlocksData = state.landBlocks.filter(block =>
+ state.selectedBlocks.includes(block.id)
+ );
+ const enabledFactors = state.factors.filter(factor => factor.enabled);
+
+ try {
+ await manager.startBatchAnalysis(
+ `批量分析_${new Date().toLocaleDateString('zh-CN')}`,
+ selectedBlocksData,
+ enabledFactors,
+ state.weightConfig
+ );
+ } catch (error) {
+ console.error('启动批量分析失败:', error);
+ alert('启动批量分析失败: ' + (error instanceof Error ? error.message : '未知错误'));
+ }
+ };
+
+ const handlePauseAnalysis = () => {
+ manager.pauseAnalysis();
+ };
+
+ const handleResumeAnalysis = () => {
+ manager.resumeAnalysis();
+ };
+
+ const handleCancelAnalysis = () => {
+ if (confirm('确定要取消当前的分析任务吗?')) {
+ manager.cancelAnalysis();
+ }
+ };
+
+ const getStatusIcon = (status: string) => {
+ switch (status) {
+ case 'pending':
+ return ;
+ case 'running':
+ return ;
+ case 'paused':
+ return ;
+ case 'completed':
+ return ;
+ case 'failed':
+ return ;
+ default:
+ return ;
+ }
+ };
+
+ const getStatusText = (status: string) => {
+ switch (status) {
+ case 'pending':
+ return '等待开始';
+ case 'running':
+ return '正在分析';
+ case 'paused':
+ return '已暂停';
+ case 'completed':
+ return '已完成';
+ case 'failed':
+ return '分析失败';
+ default:
+ return '未知状态';
+ }
+ };
+
+ const getStatusColor = (status: string) => {
+ switch (status) {
+ case 'pending':
+ return 'bg-yellow-50 border-yellow-200 dark:bg-yellow-950 dark:border-yellow-800';
+ case 'running':
+ return 'bg-blue-50 border-blue-200 dark:bg-blue-950 dark:border-blue-800';
+ case 'paused':
+ return 'bg-orange-50 border-orange-200 dark:bg-orange-950 dark:border-orange-800';
+ case 'completed':
+ return 'bg-green-50 border-green-200 dark:bg-green-950 dark:border-green-800';
+ case 'failed':
+ return 'bg-red-50 border-red-200 dark:bg-red-950 dark:border-red-800';
+ default:
+ return 'bg-gray-50 border-gray-200 dark:bg-gray-950 dark:border-gray-800';
+ }
+ };
+
+ const currentTask = state.currentTask;
+
+ return (
+
+ {/* 批量分析控制面板 */}
+
+
+
+
+ 批量评价地块
+
+
+ 已选择 {state.selectedBlocks.length} / {state.landBlocks.length} 个地块
+
+
+
+ {/* 任务状态显示 */}
+ {currentTask && (
+
+
+
+ {getStatusIcon(currentTask.status)}
+
{currentTask.name}
+
+
+ {getStatusText(currentTask.status)}
+
+
+
+
+
+
创建时间:
+
{currentTask.createdAt}
+
+
+
总地块:
+
{currentTask.totalBlocks} 个
+
+
+
已处理:
+
{currentTask.processedBlocks} 个
+
+
+
成功率:
+
+ {currentTask.totalBlocks > 0
+ ? Math.round((currentTask.successCount / currentTask.processedBlocks) * 100)
+ : 0}%
+
+
+
+
+ {/* 进度条 */}
+
+
+ 分析进度
+ {currentTask.progress}%
+
+
+
+
+ {/* 当前处理的地块 */}
+ {currentTask.currentProcessingBlock && (
+
+ 正在处理:
+ {currentTask.currentProcessingBlock}
+
+ )}
+
+ {/* 错误信息 */}
+ {currentTask.errorMessage && (
+
+ 错误: {currentTask.errorMessage}
+
+ )}
+
+ )}
+
+ {/* 控制按钮 */}
+
+ {!state.isBatchAnalyzing && !currentTask && (
+
+ )}
+
+ {state.isBatchAnalyzing && currentTask && (
+ <>
+
+
+ >
+ )}
+
+ {currentTask && currentTask.status === 'paused' && (
+ <>
+
+
+ >
+ )}
+
+ {currentTask && currentTask.status === 'completed' && (
+
+ )}
+
+
+
+ {/* 分析配置信息 */}
+
+
+
+ 分析配置
+
+
+
+
+
权重配置
+
+
+ 土壤条件:
+ {Math.round(state.weightConfig.soil * 100)}%
+
+
+ 气候条件:
+ {Math.round(state.weightConfig.climate * 100)}%
+
+
+ 地形条件:
+ {Math.round(state.weightConfig.topography * 100)}%
+
+
+ 基础设施:
+ {Math.round(state.weightConfig.infrastructure * 100)}%
+
+
+
+
+
+
评价因子
+
+
+ 启用因子:
+ {state.factors.filter(f => f.enabled).length} / {state.factors.length}
+
+
+ 土壤因子:
+ {state.factors.filter(f => f.enabled && f.category === 'soil').length} 个
+
+
+ 气候因子:
+ {state.factors.filter(f => f.enabled && f.category === 'climate').length} 个
+
+
+ 地形因子:
+ {state.factors.filter(f => f.enabled && f.category === 'topography').length} 个
+
+
+
+
+
+
+ {/* 地块分析状态概览 */}
+ {state.landBlocks.some(block => block.analysisStatus) && (
+
+
+
+ 地块分析状态
+
+
+
+
+
+ {state.landBlocks.filter(b => b.analysisStatus === 'pending').length}
+
+
等待分析
+
+
+
+ {state.landBlocks.filter(b => b.analysisStatus === 'analyzing').length}
+
+
正在分析
+
+
+
+ {state.landBlocks.filter(b => b.analysisStatus === 'completed').length}
+
+
分析完成
+
+
+
+ {state.landBlocks.filter(b => b.analysisStatus === 'failed').length}
+
+
分析失败
+
+
+
+ )}
+
+ {/* 使用说明 */}
+
+
+
+
+
批量评价说明:
+
+ - • 批量处理: 选择多个地块,系统将自动循环调用空间分析服务
+ - • 因子读取: 自动读取土壤、气候、地形、基础设施等各项因子数据
+ - • 加权计算: 根据配置的权重和因子进行综合评分计算
+ - • 适宜性指数: 生成0-100的适宜性指数并自动更新到数据库
+ - • 进度监控: 实时显示分析进度,支持暂停、继续、取消操作
+ - • 结果保存: 分析完成后自动保存结果,可查看详细报告
+
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/crop-x/src/app/(app)/land-information/suitability/auto/components/BatchEvaluationPanel.tsx b/crop-x/src/app/(app)/land-information/suitability/auto/components/BatchEvaluationPanel.tsx
new file mode 100644
index 0000000..279a213
--- /dev/null
+++ b/crop-x/src/app/(app)/land-information/suitability/auto/components/BatchEvaluationPanel.tsx
@@ -0,0 +1,411 @@
+'use client';
+
+import { Card } from '@/components/ui/card';
+import { Button } from '@/components/ui/button';
+import { Badge } from '@/components/ui/badge';
+import { Progress } from '@/components/ui/progress';
+import { toast } from 'sonner';
+import {
+ Database,
+ CheckCircle2,
+ AlertTriangle,
+ Target,
+ Droplet,
+ Cloud,
+ Sun,
+ ThermometerSun,
+ Zap,
+ RefreshCw,
+ Play,
+ Pause,
+ Square
+} from 'lucide-react';
+import { SpatialAnalysisState, SpatialAnalysisAction, AnalysisResult } from './spatialAnalysisReducer';
+import { useState, useEffect } from 'react';
+import BatchAnalysisManager from './batchAnalysisService';
+
+interface BatchEvaluationPanelProps {
+ state: SpatialAnalysisState;
+ dispatch: React.Dispatch;
+}
+
+export default function BatchEvaluationPanel({ state, dispatch }: BatchEvaluationPanelProps) {
+ const [batchManager, setBatchManager] = useState(null);
+
+ useEffect(() => {
+ setBatchManager(new BatchAnalysisManager(dispatch));
+ }, [dispatch]);
+
+ const totalWeight = Math.round(
+ (state.weightConfig.soil + state.weightConfig.climate +
+ state.weightConfig.topography + state.weightConfig.infrastructure) * 100
+ );
+
+ const handleStartBatchAnalysis = async () => {
+ if (totalWeight !== 100) {
+ toast.error(`权重总和必须为100%才能进行批量分析(当前:${totalWeight}%)`);
+ return;
+ }
+
+ const enabledFactors = state.factors.filter(f => f.enabled);
+ if (enabledFactors.length < 3) {
+ toast.error('至少需要启用3个评价因子才能进行批量分析');
+ return;
+ }
+
+ if (!batchManager) {
+ toast.error('批量分析服务未初始化');
+ return;
+ }
+
+ try {
+ // 创建批量分析任务
+ const taskName = `地块适宜性批量分析_${new Date().toLocaleString('zh-CN')}`;
+
+ // 固定分析68个地块
+ const totalFields = 68;
+ const blockIds = Array.from({ length: totalFields }, (_, i) => `field-${i + 1}`);
+
+ // 开始批量分析
+ await batchManager.startBatchAnalysis(taskName, enabledFactors, state.weightConfig);
+
+ toast.success('批量分析任务已启动,正在处理地块数据...');
+
+ } catch (error) {
+ console.error('启动批量分析失败:', error);
+ toast.error('启动批量分析失败,请重试');
+ }
+ };
+
+ const handlePauseAnalysis = () => {
+ if (batchManager && state.currentTask) {
+ batchManager.pauseAnalysis();
+ toast.info('批量分析已暂停');
+ }
+ };
+
+ const handleResumeAnalysis = () => {
+ if (batchManager && state.currentTask) {
+ batchManager.resumeAnalysis();
+ toast.info('批量分析已恢复');
+ }
+ };
+
+ const handleCancelAnalysis = () => {
+ if (batchManager && state.currentTask) {
+ batchManager.cancelAnalysis();
+ toast.info('批量分析已取消');
+ }
+ };
+
+ const currentTask = state.currentTask;
+
+ // 计算统计结果
+ const resultsForStats = state.analysisResults;
+ const highSuitability = resultsForStats.filter(r => r.overallScore >= 80).length;
+ const mediumSuitability = resultsForStats.filter(r => r.overallScore >= 60 && r.overallScore < 80).length;
+ const lowSuitability = resultsForStats.filter(r => r.overallScore < 60).length;
+
+ return (
+
+ {/* 主要批量分析控制面板 */}
+
+
+
+
+ 批量评价地块
+
+
+
+ 68 个地块
+
+ {currentTask && (
+
+ {currentTask.status === 'pending' && '等待中'}
+ {currentTask.status === 'running' && '分析中'}
+ {currentTask.status === 'completed' && '已完成'}
+ {currentTask.status === 'failed' && '失败'}
+ {currentTask.status === 'paused' && '已暂停'}
+ {currentTask.status === 'cancelled' && '已取消'}
+
+ )}
+
+
+
+ {/* 控制按钮区域 */}
+
+ {!currentTask || currentTask.status === 'completed' || currentTask.status === 'failed' || currentTask.status === 'cancelled' ? (
+
+ ) : (
+ <>
+ {currentTask.status === 'running' && (
+
+ )}
+ {currentTask.status === 'paused' && (
+
+ )}
+
+ >
+ )}
+
+
+ {/* 权重和因子验证警告 */}
+ {totalWeight !== 100 && (
+
+
+ ⚠️ 权重总和必须为100%才能进行批量分析(当前:{totalWeight}%)
+
+
+ )}
+
+ {state.factors.filter(f => f.enabled).length < 3 && (
+
+
+ ⚠️ 至少需要启用3个评价因子才能进行批量分析(当前:{state.factors.filter(f => f.enabled).length}个)
+
+
+ )}
+
+ {/* 分析进行中的状态显示 */}
+ {currentTask && (currentTask.status === 'running' || currentTask.status === 'paused') && (
+
+
+ 分析进度
+ {currentTask.progress}%
+
+
+
+
+
+
当前处理
+
{currentTask.currentProcessingBlock || '准备中...'}
+
+
+
处理进度
+
+ {currentTask.processedBlocks} / {currentTask.totalBlocks} 地块
+
+
+
+
成功数量
+
{currentTask.successCount}
+
+
+
失败数量
+
{currentTask.failedCount}
+
+
+
+
+
✓ 正在从空间分析服务读取因子数据...
+
✓ 正在计算各因子得分(pH、有机质、土层厚度、全氮、全磷、全钾、排水性)...
+
✓ 正在进行加权汇总计算...
+
✓ 正在生成适宜性指数并更新到数据库...
+
+
+ {/* 任务状态信息 */}
+
+
+
+ 任务名称:
+ {currentTask.name}
+
+
+ 创建时间:
+ {currentTask.createdAt}
+
+ {currentTask.startedAt && (
+
+ 开始时间:
+ {currentTask.startedAt}
+
+ )}
+
+ 启用因子:
+ {state.factors.filter(f => f.enabled).length}个
+
+
+
+
+ )}
+
+ {/* 分析完成状态 */}
+ {currentTask && currentTask.status === 'completed' && (
+
+
+ ✓ 批量分析已完成!已为 {currentTask.totalBlocks} 个地块生成适宜性评价结果并更新到数据库
+
+
+
完成时间:{currentTask.completedAt}
+
成功分析:{currentTask.successCount} 个地块
+ {currentTask.failedCount > 0 && (
+
分析失败:{currentTask.failedCount} 个地块
+ )}
+
+
+ )}
+
+ {/* 分析失败状态 */}
+ {currentTask && currentTask.status === 'failed' && (
+
+
+ ✗ 批量分析失败:{currentTask.errorMessage || '未知错误'}
+
+
+
已处理:{currentTask.processedBlocks} 个地块
+
成功:{currentTask.successCount} 个地块
+
+
+ )}
+
+
+ {/* 批量分析结果统计 */}
+ {resultsForStats.length > 0 && (
+
+
+
+
+
高度适宜
+
{highSuitability}
+
+ 地块数量 ({Math.round((highSuitability / resultsForStats.length) * 100)}%)
+
+
+
+
+
+
+
+
+
+
一般适宜
+
{mediumSuitability}
+
+ 地块数量 ({Math.round((mediumSuitability / resultsForStats.length) * 100)}%)
+
+
+
+
+
+
+
+
+
+
不适宜
+
{lowSuitability}
+
+ 地块数量 ({Math.round((lowSuitability / resultsForStats.length) * 100)}%)
+
+
+
+
+
+
+ )}
+
+ {/* 分析说明卡片 */}
+
+
+
+ 空间分析服务说明
+
+
+
+
+
+
+
+
+
土壤因子
+
+ pH值、有机质、土层厚度、全氮、全磷、全钾、排水性
+
+
+
+
+
+
+
+
水分因子
+
+ 地下水位、灌溉保证率、排水能力、土壤含水量
+
+
+
+
+
+
+
+
气候因子
+
+ 年均温度、年降雨量、日照时数、无霜期、积温
+
+
+
+
+
+
+
+
地形因子
+
+ 海拔高度、坡度、坡向、地貌类型、侵蚀程度
+
+
+
+
+
+
+
+
+
批量评价流程:
+
+ - • 数据读取: 自动从空间分析服务读取68个地块的多维度因子数据
+ - • 因子评分: 根据最优值范围计算每个因子的得分(0-100分)
+ - • 加权汇总: 按照配置的权重计算综合适宜性指数
+ - • 等级划分: 根据综合得分确定适宜性等级(高度适宜/一般适宜/不适宜)
+ - • 数据库更新: 将评价结果和适宜性指数自动更新到数据库
+ - • 结果统计: 生成详细的统计分析和可视化报告
+
+
+
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/crop-x/src/app/(app)/land-information/suitability/auto/components/EvaluationModel.tsx b/crop-x/src/app/(app)/land-information/suitability/auto/components/EvaluationModel.tsx
new file mode 100644
index 0000000..fb38abf
--- /dev/null
+++ b/crop-x/src/app/(app)/land-information/suitability/auto/components/EvaluationModel.tsx
@@ -0,0 +1,235 @@
+'use client';
+
+import { Card } from '@/components/ui/card';
+import { Button } from '@/components/ui/button';
+import { Badge } from '@/components/ui/badge';
+import { Switch } from '@/components/ui/switch';
+import { toast } from 'sonner';
+import {
+ Brain,
+ Settings,
+ Eye,
+ EyeOff,
+ CheckCircle2,
+ XCircle,
+ Target,
+ Droplet,
+ Sun,
+ Mountain,
+ Building,
+ Info
+} from 'lucide-react';
+import { SpatialAnalysisState, SpatialAnalysisAction } from './spatialAnalysisReducer';
+
+interface EvaluationModelProps {
+ state: SpatialAnalysisState;
+ dispatch: React.Dispatch;
+}
+
+export default function EvaluationModel({ state, dispatch }: EvaluationModelProps) {
+ const handleToggleFactor = (factorId: string) => {
+ dispatch({ type: 'TOGGLE_FACTOR', payload: factorId });
+ const factor = state.factors.find(f => f.id === factorId);
+ if (factor) {
+ toast.success(`${factor.name} 已${factor.enabled ? '禁用' : '启用'}`);
+ }
+ };
+
+ const getFactorIcon = (category: string) => {
+ const icons = {
+ soil: ,
+ climate: ,
+ topography: ,
+ infrastructure:
+ };
+ return icons[category as keyof typeof icons] || ;
+ };
+
+ const getFactorCategoryColor = (category: string) => {
+ const colors = {
+ soil: 'bg-amber-50 border-amber-200 text-amber-700',
+ climate: 'bg-blue-50 border-blue-200 text-blue-700',
+ topography: 'bg-green-50 border-green-200 text-green-700',
+ infrastructure: 'bg-purple-50 border-purple-200 text-purple-700'
+ };
+ return colors[category as keyof typeof colors] || 'bg-gray-50 border-gray-200 text-gray-700';
+ };
+
+ const enabledFactors = state.factors.filter(f => f.enabled);
+ const enabledFactorCount = enabledFactors.length;
+ const totalFactorCount = state.factors.length;
+
+ const factorCategories = [
+ {
+ key: 'soil',
+ name: '土壤因子',
+ icon: ,
+ factors: state.factors.filter(f => f.category === 'soil')
+ },
+ {
+ key: 'climate',
+ name: '气候因子',
+ icon: ,
+ factors: state.factors.filter(f => f.category === 'climate')
+ },
+ {
+ key: 'topography',
+ name: '地形因子',
+ icon: ,
+ factors: state.factors.filter(f => f.category === 'topography')
+ },
+ {
+ key: 'infrastructure',
+ name: '基础设施因子',
+ icon: ,
+ factors: state.factors.filter(f => f.category === 'infrastructure')
+ }
+ ];
+
+ return (
+
+
+
+
+
+ {enabledFactorCount}/{totalFactorCount} 个因子
+
+
+
+
+
+ {/* 评价因子配置区域 */}
+
+ {factorCategories.map((category) => (
+
+
+ {category.icon}
+
{category.name}
+
+ {category.factors.filter(f => f.enabled).length}/{category.factors.length}
+
+
+
+
+ {category.factors.map((factor) => (
+
+
+
+
+ {getFactorIcon(factor.category)}
+
{factor.name}
+ {factor.enabled ? (
+
+ ) : (
+
+ )}
+
+
+ {factor.description}
+
+
+
+ 权重: {Math.round(factor.weight * 100)}%
+
+ handleToggleFactor(factor.id)}
+ disabled={state.isBatchAnalyzing}
+ />
+
+
+
+
+ ))}
+
+
+ ))}
+
+
+ {/* 模型配置说明 */}
+
+
+
+
+
+ 评价模型说明
+
+
+
+ 多因子加权评价模型采用层次分析法(AHP)和专家评分法,
+ 结合农业生产实际需求,构建科学的评价指标体系。
+
+
+
+
评价因子选择原则:
+
+ - • 主导性:选择对作物生长起决定性作用的因子
+ - • 独立性:避免因子间的重复和高度相关性
+ - • 可获取性:确保数据能够准确获取和更新
+ - • 稳定性:优先选择相对稳定的评价因子
+
+
+
+
权重确定方法:
+
+ - • 专家打分:邀请农业专家进行权重评分
+ - • 层次分析:构建判断矩阵确定权重
+ - • 实地验证:结合实际产量数据校准权重
+ - • 动态调整:根据作物类型和生产目标调整
+
+
+
+
+
+
+
+
+ {/* 启用因子过少警告 */}
+ {enabledFactorCount < 6 && (
+
+
+
+
+
+ 评价因子配置建议
+
+
+ 当前仅启用了 {enabledFactorCount} 个评价因子,建议至少启用 6-8 个因子以获得更准确的评价结果。
+ 确保各类别都有代表性因子参与评价。
+
+
+
+
+ )}
+
+ );
+}
\ No newline at end of file
diff --git a/crop-x/src/app/(app)/land-information/suitability/auto/components/FactorConfiguration.tsx b/crop-x/src/app/(app)/land-information/suitability/auto/components/FactorConfiguration.tsx
new file mode 100644
index 0000000..8c4f40b
--- /dev/null
+++ b/crop-x/src/app/(app)/land-information/suitability/auto/components/FactorConfiguration.tsx
@@ -0,0 +1,253 @@
+'use client';
+
+import { Card } from '@/components/ui/card';
+import { Button } from '@/components/ui/button';
+import { Switch } from '@/components/ui/switch';
+import { Slider } from '@/components/ui/slider';
+import { Badge } from '@/components/ui/badge';
+import { Progress } from '@/components/ui/progress';
+import {
+ Dialog,
+ DialogContent,
+ DialogHeader,
+ DialogTitle,
+ DialogTrigger,
+} from '@/components/ui/dialog';
+import {
+ Settings,
+ Sliders,
+ CheckCircle,
+ XCircle,
+ Info
+} from 'lucide-react';
+import { SpatialAnalysisState, SpatialAnalysisAction } from './spatialAnalysisReducer';
+
+interface FactorConfigurationProps {
+ state: SpatialAnalysisState;
+ dispatch: React.Dispatch;
+ isDialog?: boolean;
+}
+
+export default function FactorConfiguration({ state, dispatch, isDialog = false }: FactorConfigurationProps) {
+ const categories = [
+ { id: 'soil', name: '土壤条件', color: 'blue' },
+ { id: 'climate', name: '气候条件', color: 'green' },
+ { id: 'topography', name: '地形条件', color: 'orange' },
+ { id: 'infrastructure', name: '基础设施', color: 'purple' }
+ ];
+
+ const handleToggleFactor = (factorId: string) => {
+ dispatch({ type: 'TOGGLE_FACTOR', payload: factorId });
+ };
+
+ const handleUpdateWeight = (factorId: string, weight: number) => {
+ dispatch({ type: 'UPDATE_FACTOR_WEIGHT', payload: { id: factorId, weight } });
+ };
+
+ const handleResetToDefaults = () => {
+ // 重置为默认权重
+ const defaultWeights = {
+ 'soil_ph': 0.15,
+ 'soil_organic_matter': 0.12,
+ 'soil_nutrients': 0.18,
+ 'soil_texture': 0.10,
+ 'temperature': 0.15,
+ 'rainfall': 0.12,
+ 'sunlight': 0.10,
+ 'elevation': 0.08,
+ 'slope': 0.06,
+ 'irrigation': 0.12,
+ 'accessibility': 0.05
+ };
+
+ Object.entries(defaultWeights).forEach(([id, weight]) => {
+ dispatch({ type: 'UPDATE_FACTOR_WEIGHT', payload: { id, weight } });
+ });
+ };
+
+ const getTotalWeight = () => {
+ return state.factors.reduce((sum, factor) => sum + factor.weight, 0);
+ };
+
+ const getCategoryTotal = (categoryId: string) => {
+ return state.factors
+ .filter(f => f.category === categoryId && f.enabled)
+ .reduce((sum, f) => sum + f.weight, 0);
+ };
+
+ const categoryColors = {
+ soil: 'blue',
+ climate: 'green',
+ topography: 'orange',
+ infrastructure: 'purple'
+ };
+
+ const content = (
+
+ {/* 权重总览 */}
+
+
+
权重分配总览
+
+ 总权重:
+
+ {Math.round(getTotalWeight() * 100)}%
+
+
+
+
+
+
+ {categories.map(category => {
+ const totalWeight = getCategoryTotal(category.id);
+ return (
+
+
+ {category.name}
+
+ {Math.round(totalWeight * 100)}%
+
+
+
+
+ );
+ })}
+
+
+
+ {/* 因子详细配置 */}
+
+ {categories.map(category => (
+
+
+
+ {category.name}
+
+ {state.factors.filter(f => f.category === category.id).length} 个因子
+
+
+
+
+ {state.factors
+ .filter(factor => factor.category === category.id)
+ .map(factor => (
+
+
+
+
+
{factor.name}
+ {factor.enabled ? (
+
+ ) : (
+
+ )}
+
+
+ {factor.description}
+
+
+
+
+
+
+ {Math.round(factor.weight * 100)}%
+
+
权重
+
+
+
handleToggleFactor(factor.id)}
+ />
+
+
+
+ {/* 权重调节 */}
+ {factor.enabled && (
+
+
+ 权重调节
+ {Math.round(factor.weight * 100)}%
+
+
+ handleUpdateWeight(factor.id, value / 100)
+ }
+ max={50}
+ min={0}
+ step={1}
+ className="w-full"
+ />
+
+ )}
+
+ ))}
+
+
+ ))}
+
+
+ {/* 配置说明 */}
+
+
+
+
+
配置说明:
+
+ - • 权重总和应保持100%,系统会自动计算各因子的影响程度
+ - • 启用/禁用因子来控制评价维度,禁用的因子不参与计算
+ - • 土壤条件、气候条件、地形条件、基础设施为四大评价维度
+ - • 可以根据具体需求调整各因子的权重,建议咨询农业专家
+ - • 点击"重置默认"可以恢复到推荐的标准权重配置
+
+
+
+
+
+ );
+
+ if (isDialog) {
+ return (
+
+ );
+ }
+
+ return (
+
+
+
评价因子配置
+
+
+ 启用 {state.factors.filter(f => f.enabled).length} / {state.factors.length}
+
+
+
+
+
+ {content}
+
+ );
+}
\ No newline at end of file
diff --git a/crop-x/src/app/(app)/land-information/suitability/auto/components/FactorWeights.tsx b/crop-x/src/app/(app)/land-information/suitability/auto/components/FactorWeights.tsx
new file mode 100644
index 0000000..767fd51
--- /dev/null
+++ b/crop-x/src/app/(app)/land-information/suitability/auto/components/FactorWeights.tsx
@@ -0,0 +1,243 @@
+'use client';
+
+import { Card } from '@/components/ui/card';
+import { Button } from '@/components/ui/button';
+import { Slider } from '@/components/ui/slider';
+import { Badge } from '@/components/ui/badge';
+import { Progress } from '@/components/ui/progress';
+import { toast } from 'sonner';
+import {
+ Settings,
+ SlidersHorizontal,
+ TrendingUp,
+ Target,
+ Droplet,
+ Sun,
+ Mountain,
+ Building,
+ RefreshCw
+} from 'lucide-react';
+import { SpatialAnalysisState, SpatialAnalysisAction } from './spatialAnalysisReducer';
+
+interface FactorWeightsProps {
+ state: SpatialAnalysisState;
+ dispatch: React.Dispatch;
+}
+
+export default function FactorWeights({ state, dispatch }: FactorWeightsProps) {
+ const totalWeight = Math.round(
+ (state.weightConfig.soil + state.weightConfig.climate +
+ state.weightConfig.topography + state.weightConfig.infrastructure) * 100
+ );
+
+ const handleWeightChange = (category: keyof SpatialAnalysisState['weightConfig'], value: number[]) => {
+ dispatch({
+ type: 'SET_WEIGHT_CONFIG',
+ payload: {
+ ...state.weightConfig,
+ [category]: value[0]
+ }
+ });
+ };
+
+ const handleResetWeights = () => {
+ dispatch({ type: 'RESET_WEIGHTS' });
+ toast.success('权重配置已重置为默认值');
+ };
+
+ const getWeightColor = (category: string) => {
+ const colors = {
+ soil: 'text-amber-600 bg-amber-50 border-amber-200',
+ climate: 'text-blue-600 bg-blue-50 border-blue-200',
+ topography: 'text-green-600 bg-green-50 border-green-200',
+ infrastructure: 'text-purple-600 bg-purple-50 border-purple-200'
+ };
+ return colors[category as keyof typeof colors] || 'text-gray-600 bg-gray-50 border-gray-200';
+ };
+
+ const getWeightIcon = (category: string) => {
+ const icons = {
+ soil: ,
+ climate: ,
+ topography: ,
+ infrastructure:
+ };
+ return icons[category as keyof typeof icons] || ;
+ };
+
+ const factorCategories = [
+ {
+ key: 'soil' as const,
+ name: '土壤因子',
+ icon: ,
+ description: 'pH值、有机质、全氮、全磷、全钾、土壤质地等',
+ factors: state.factors.filter(f => f.category === 'soil')
+ },
+ {
+ key: 'climate' as const,
+ name: '气候因子',
+ icon: ,
+ description: '年均温度、年降雨量、日照时数、无霜期等',
+ factors: state.factors.filter(f => f.category === 'climate')
+ },
+ {
+ key: 'topography' as const,
+ name: '地形因子',
+ icon: ,
+ description: '海拔高度、坡度、坡向、地貌类型等',
+ factors: state.factors.filter(f => f.category === 'topography')
+ },
+ {
+ key: 'infrastructure' as const,
+ name: '基础设施因子',
+ icon: ,
+ description: '灌溉条件、交通便利性、排水条件等',
+ factors: state.factors.filter(f => f.category === 'infrastructure')
+ }
+ ];
+
+ return (
+
+
+
+
+
+ 总权重: {totalWeight}%
+
+
+
+
+
+ {/* 权重配置区域 */}
+
+ {factorCategories.map((category) => (
+
+
+
+ {getWeightIcon(category.key)}
+
+
{category.name}
+
+ {category.description}
+
+
+
+
+
+ {Math.round(state.weightConfig[category.key] * 100)}%
+
+
+ {category.factors.length} 个因子
+
+
+
+
+
+
+ 权重调整
+ {Math.round(state.weightConfig[category.key] * 100)}%
+
+
handleWeightChange(category.key, value)}
+ max={60}
+ min={5}
+ step={5}
+ className="w-full"
+ disabled={state.isBatchAnalyzing}
+ />
+
+
+
+ {/* 显示该类别下的主要因子 */}
+
+
主要评价因子:
+
+ {category.factors.slice(0, 3).map((factor) => (
+
+ {factor.name}
+
+ ))}
+ {category.factors.length > 3 && (
+
+ +{category.factors.length - 3}
+
+ )}
+
+
+
+ ))}
+
+
+ {/* 权重总和警告 */}
+ {totalWeight !== 100 && (
+
+
+
+
+
+ 权重总和验证
+
+
+ 所有因子权重总和必须为100%才能进行批量分析。
+ 当前总和为 {totalWeight}%,{totalWeight > 100 ? '超出' : '还差'} {Math.abs(totalWeight - 100)}%。
+
+
+ 💡 建议权重分配:土壤因子(35%)、气候因子(30%)、地形因子(20%)、基础设施因子(15%)
+
+
+
+
+ )}
+
+ {/* 权重说明 */}
+
+
+
+
+
+ 权重配置说明
+
+
+ - • 土壤因子:反映地块基础肥力和物理化学性质
+ - • 气候因子:决定光热水的匹配度和作物适应性
+ - • 地形因子:影响水土保持和机械化作业条件
+ - • 基础设施因子:体现生产条件和集约化水平
+
+
+ 权重配置将直接影响最终适宜性指数的计算结果,请根据实际生产需求科学分配。
+
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/crop-x/src/app/(app)/land-information/suitability/auto/components/LandBlockSelector.tsx b/crop-x/src/app/(app)/land-information/suitability/auto/components/LandBlockSelector.tsx
new file mode 100644
index 0000000..76adac1
--- /dev/null
+++ b/crop-x/src/app/(app)/land-information/suitability/auto/components/LandBlockSelector.tsx
@@ -0,0 +1,299 @@
+'use client';
+
+import { Card } from '@/components/ui/card';
+import { Button } from '@/components/ui/button';
+import { Checkbox } from '@/components/ui/checkbox';
+import { Badge } from '@/components/ui/badge';
+import { Progress } from '@/components/ui/progress';
+import { MapPin, CheckCircle, Circle, Loader2, XCircle } from 'lucide-react';
+import { SpatialAnalysisState, SpatialAnalysisAction } from './spatialAnalysisReducer';
+
+interface LandBlockSelectorProps {
+ state: SpatialAnalysisState;
+ dispatch: React.Dispatch;
+ isDialog?: boolean;
+}
+
+export default function LandBlockSelector({ state, dispatch, isDialog = false }: LandBlockSelectorProps) {
+ const handleSelectAll = () => {
+ if (state.selectedBlocks.length === state.landBlocks.length) {
+ dispatch({ type: 'SET_SELECTED_BLOCKS', payload: [] });
+ } else {
+ dispatch({ type: 'SET_SELECTED_BLOCKS', payload: state.landBlocks.map(b => b.id) });
+ }
+ };
+
+ const getBlockScore = (blockId: string) => {
+ const result = state.analysisResults.find(r => r.blockId === blockId);
+ return result?.overallScore || null;
+ };
+
+ const getScoreColor = (score: number) => {
+ if (score >= 80) return 'text-green-600';
+ if (score >= 60) return 'text-yellow-600';
+ return 'text-red-600';
+ };
+
+ const getScoreLabel = (score: number) => {
+ if (score >= 80) return '优秀';
+ if (score >= 60) return '良好';
+ if (score >= 40) return '一般';
+ return '较差';
+ };
+
+ if (isDialog) {
+ // 对话框模式下的简化版本
+ return (
+
+
+
选择分析地块
+
+
+
+
+ {state.landBlocks.map(block => {
+ const isSelected = state.selectedBlocks.includes(block.id);
+ const score = getBlockScore(block.id);
+
+ return (
+
dispatch({ type: 'TOGGLE_BLOCK_SELECTION', payload: block.id })}
+ >
+
+
+
+
+
{block.name}
+
+ {block.area} 亩 · {block.soilType} · {block.irrigation}
+
+
+
+ {score && (
+
+ {score}分
+
+ )}
+
+
+ );
+ })}
+
+
+ );
+ }
+
+ return (
+
+
+
地块选择
+
+
+
+ 已选择 {state.selectedBlocks.length} / {state.landBlocks.length}
+
+
+
+
+ {/* 地块列表 */}
+
+ {state.landBlocks.length === 0 ? (
+
+ ) : (
+ state.landBlocks.map(block => {
+ const isSelected = state.selectedBlocks.includes(block.id);
+ const score = getBlockScore(block.id);
+
+ return (
+
dispatch({ type: 'TOGGLE_BLOCK_SELECTION', payload: block.id })}
+ >
+
+
+
+ {isSelected ? (
+
+ ) : (
+
+ )}
+
+
+
+
+
{block.name}
+ {block.currentCrop && (
+
+ {block.currentCrop}
+
+ )}
+
+
+ {/* 地块基本信息 */}
+
+
+ 面积:
+ {block.area} 亩
+
+
+ 土壤:
+ {block.soilType}
+
+
+ 灌溉:
+ {block.irrigation}
+
+
+ 海拔:
+ {block.elevation}m
+
+
+
+ {/* 土壤指标 */}
+
+
+ pH:
+ {block.pH}
+
+
+ 有机质:
+ {block.organicMatter}%
+
+
+ 氮:
+ {block.nitrogen}
+
+
+ 磷:
+ {block.phosphorus}
+
+
+ 钾:
+ {block.potassium}
+
+
+
+
+
+ {/* 分析状态和适宜性指数 */}
+ {(score || block.analysisStatus) && (
+
+ {block.analysisStatus === 'analyzing' && (
+
+ )}
+ {block.analysisStatus === 'failed' && (
+
+ )}
+ {block.analysisStatus === 'completed' && block.suitabilityIndex && (
+
+
+ {block.suitabilityIndex}
+
+
+ 适宜性指数
+
+
+
+ {block.lastAnalyzed}
+
+
+ )}
+ {score && !block.analysisStatus && (
+
+
+ {score}
+
+
+ {getScoreLabel(score)}
+
+
+
+ )}
+
+ )}
+
+
+ );
+ })
+ )}
+
+
+ {/* 选中地块统计 */}
+ {state.selectedBlocks.length > 0 && (
+
+
选中地块统计
+
+
+ 地块数量:
+ {state.selectedBlocks.length} 个
+
+
+ 总面积:
+
+ {state.landBlocks
+ .filter(b => state.selectedBlocks.includes(b.id))
+ .reduce((sum, b) => sum + b.area, 0)
+ .toFixed(1)} 亩
+
+
+
+ 平均得分:
+
+ {state.analysisResults.length > 0
+ ? Math.round(
+ state.analysisResults
+ .filter(r => state.selectedBlocks.includes(r.blockId))
+ .reduce((sum, r) => sum + r.overallScore, 0) /
+ state.analysisResults.filter(r => state.selectedBlocks.includes(r.blockId)).length
+ )
+ : '--'
+ } 分
+
+
+
+ 土壤类型:
+
+ {[...new Set(
+ state.landBlocks
+ .filter(b => state.selectedBlocks.includes(b.id))
+ .map(b => b.soilType)
+ )].join(', ')}
+
+
+
+
+ )}
+
+ );
+}
\ No newline at end of file
diff --git a/crop-x/src/app/(app)/land-information/suitability/auto/components/SpatialAnalysisContent.tsx b/crop-x/src/app/(app)/land-information/suitability/auto/components/SpatialAnalysisContent.tsx
new file mode 100644
index 0000000..38cd4e6
--- /dev/null
+++ b/crop-x/src/app/(app)/land-information/suitability/auto/components/SpatialAnalysisContent.tsx
@@ -0,0 +1,40 @@
+'use client';
+
+import { SpatialAnalysisState, SpatialAnalysisAction } from './spatialAnalysisReducer';
+import BatchEvaluationPanel from './BatchEvaluationPanel';
+import AnalysisResults from './AnalysisResults';
+import FactorWeights from './FactorWeights';
+import EvaluationModel from './EvaluationModel';
+
+interface SpatialAnalysisContentProps {
+ state: SpatialAnalysisState;
+ dispatch: React.Dispatch;
+}
+
+export default function SpatialAnalysisContent({ state, dispatch }: SpatialAnalysisContentProps) {
+ return (
+
+ {/* 标题区域 */}
+
+
自动化空间分析
+
+ 多因子评价模型,批量生成地块适宜性指数并更新到数据库
+
+
+
+ {/* 权重配置面板 */}
+
+
+ {/* 评价模型配置 */}
+
+
+ {/* 批量评价面板 */}
+
+
+ {/* 分析结果记录 */}
+ {state.analysisResults.length > 0 && (
+
+ )}
+
+ );
+}
\ No newline at end of file
diff --git a/crop-x/src/app/(app)/land-information/suitability/auto/components/SpatialDistribution.tsx b/crop-x/src/app/(app)/land-information/suitability/auto/components/SpatialDistribution.tsx
new file mode 100644
index 0000000..ebf035a
--- /dev/null
+++ b/crop-x/src/app/(app)/land-information/suitability/auto/components/SpatialDistribution.tsx
@@ -0,0 +1,212 @@
+'use client';
+
+import { Card } from '@/components/ui/card';
+import { Badge } from '@/components/ui/badge';
+import { MapPin, Layers, Navigation } from 'lucide-react';
+import { SpatialAnalysisState, SpatialAnalysisAction } from './spatialAnalysisReducer';
+
+interface SpatialDistributionProps {
+ state: SpatialAnalysisState;
+ dispatch: React.Dispatch;
+}
+
+export default function SpatialDistribution({ state, dispatch }: SpatialDistributionProps) {
+ const getScoreColor = (score: number) => {
+ if (score >= 80) return 'bg-green-500';
+ if (score >= 60) return 'bg-yellow-500';
+ if (score >= 40) return 'bg-orange-500';
+ return 'bg-red-500';
+ };
+
+ const getScoreTextColor = (score: number) => {
+ if (score >= 80) return 'text-green-600';
+ if (score >= 60) return 'text-yellow-600';
+ if (score >= 40) return 'text-orange-600';
+ return 'text-red-600';
+ };
+
+ const selectedBlocksWithScores = state.selectedBlocks.map(blockId => {
+ const block = state.landBlocks.find(b => b.id === blockId);
+ const result = state.analysisResults.find(r => r.blockId === blockId);
+ return { block, result };
+ }).filter(item => item.block);
+
+ return (
+
+
+
+
+ 空间分布可视化
+
+
+ {selectedBlocksWithScores.length} 个地块
+
+
+
+ {/* 简化的地图展示 */}
+
+ {selectedBlocksWithScores.length === 0 ? (
+
+ ) : (
+
+ {/* 模拟地图背景 */}
+
+
+ {/* 地块分布 */}
+
+
+ {selectedBlocksWithScores.map((item, index) => {
+ const positions = [
+ { top: '20%', left: '15%' },
+ { top: '25%', left: '60%' },
+ { top: '60%', left: '25%' },
+ { top: '65%', left: '70%' },
+ { top: '40%', left: '40%' },
+ { top: '30%', left: '80%' }
+ ];
+
+ const position = positions[index % positions.length];
+
+ return (
+
+ {/* 地块标记 */}
+
+
+
+
+
+
+ {/* 地块信息卡片 */}
+
+
+
{item.block!.name}
+
+ {item.result?.overallScore || '--'}分
+
+
+
+
+
+ 面积:
+ {item.block!.area} 亩
+
+
+ 土壤:
+ {item.block!.soilType}
+
+ {item.result && (
+ <>
+
+ 土壤得分:
+ {Math.round(item.result.soilScore)}
+
+
+ 气候得分:
+ {Math.round(item.result.climateScore)}
+
+ >
+ )}
+ {item.result?.recommendedCrops[0] && (
+
+ 推荐: {item.result.recommendedCrops[0].crop.name}
+
+ )}
+
+
+
+
+ );
+ })}
+
+
+
+ {/* 图例 */}
+
+
+ {/* 指北针 */}
+
+
+ )}
+
+
+ {/* 统计信息 */}
+ {selectedBlocksWithScores.length > 0 && (
+
+
+
+ {selectedBlocksWithScores.filter(item => (item.result?.overallScore || 0) >= 80).length}
+
+
优秀地块
+
+
+
+ {Math.round(
+ selectedBlocksWithScores.reduce((sum, item) => sum + (item.block?.area || 0), 0)
+ )}
+
+
总面积(亩)
+
+
+
+ {selectedBlocksWithScores.length > 0
+ ? Math.round(
+ selectedBlocksWithScores.reduce((sum, item) => sum + (item.result?.overallScore || 0), 0) /
+ selectedBlocksWithScores.length
+ )
+ : 0}
+
+
平均得分
+
+
+
+ {[...new Set(
+ selectedBlocksWithScores
+ .filter(item => item.result?.recommendedCrops[0])
+ .map(item => item.result!.recommendedCrops[0].crop.name)
+ )].length}
+
+
推荐作物
+
+
+ )}
+
+ );
+}
\ No newline at end of file
diff --git a/crop-x/src/app/(app)/land-information/suitability/auto/components/WeightConfiguration.tsx b/crop-x/src/app/(app)/land-information/suitability/auto/components/WeightConfiguration.tsx
new file mode 100644
index 0000000..899efa1
--- /dev/null
+++ b/crop-x/src/app/(app)/land-information/suitability/auto/components/WeightConfiguration.tsx
@@ -0,0 +1,299 @@
+'use client';
+
+import { Card } from '@/components/ui/card';
+import { Button } from '@/components/ui/button';
+import { Slider } from '@/components/ui/slider';
+import { Badge } from '@/components/ui/badge';
+import { Progress } from '@/components/ui/progress';
+import {
+ Dialog,
+ DialogContent,
+ DialogHeader,
+ DialogTitle,
+} from '@/components/ui/dialog';
+import {
+ Sliders,
+ RotateCcw,
+ Info,
+ Scale
+} from 'lucide-react';
+import { SpatialAnalysisState, SpatialAnalysisAction } from './spatialAnalysisReducer';
+
+interface WeightConfigurationProps {
+ state: SpatialAnalysisState;
+ dispatch: React.Dispatch;
+ isDialog?: boolean;
+}
+
+export default function WeightConfiguration({ state, dispatch, isDialog = false }: WeightConfigurationProps) {
+ const categories = [
+ { id: 'soil', name: '土壤条件', icon: '🌱', color: 'blue' },
+ { id: 'climate', name: '气候条件', icon: '🌤️', color: 'green' },
+ { id: 'topography', name: '地形条件', icon: '⛰️', color: 'orange' },
+ { id: 'infrastructure', name: '基础设施', icon: '🏗️', color: 'purple' }
+ ];
+
+ const handleUpdateWeight = (category: string, value: number) => {
+ const newWeightConfig = {
+ ...state.weightConfig,
+ [category]: value / 100
+ };
+ dispatch({ type: 'SET_WEIGHT_CONFIG', payload: newWeightConfig });
+ };
+
+ const handleResetToDefaults = () => {
+ const defaultConfig = {
+ soil: 0.35,
+ climate: 0.30,
+ topography: 0.20,
+ infrastructure: 0.15
+ };
+ dispatch({ type: 'SET_WEIGHT_CONFIG', payload: defaultConfig });
+ };
+
+ const handleBalanceWeights = () => {
+ // 自动平衡权重,确保总和为100%
+ const total = Object.values(state.weightConfig).reduce((sum, val) => sum + val, 0);
+ if (total > 0) {
+ const balancedConfig = Object.fromEntries(
+ Object.entries(state.weightConfig).map(([key, value]) => [key, value / total])
+ ) as typeof state.weightConfig;
+ dispatch({ type: 'SET_WEIGHT_CONFIG', payload: balancedConfig });
+ }
+ };
+
+ const getTotalWeight = () => {
+ return Object.values(state.weightConfig).reduce((sum, val) => sum + val, 0);
+ };
+
+ const categoryConfigs = categories.map(category => ({
+ ...category,
+ weight: state.weightConfig[category.id as keyof typeof state.weightConfig],
+ percentage: Math.round(state.weightConfig[category.id as keyof typeof state.weightConfig] * 100)
+ }));
+
+ const content = (
+
+ {/* 权重总览 */}
+
+
+
+
+ 权重分配总览
+
+
+
+
+ {Math.round(getTotalWeight() * 100)}%
+
+
总权重
+
+
+
+
+
+
+ {/* 权重可视化 */}
+
+ {categoryConfigs.map(category => (
+
+
+
+ {category.icon}
+ {category.name}
+
+
+ {category.percentage}%
+
+ {category.weight.toFixed(2)}
+
+
+
+
+
+
+ ))}
+
+ {Math.abs(getTotalWeight() - 1) > 0.01 && (
+
+
+ 偏差
+
+ {Math.abs((getTotalWeight() - 1) * 100).toFixed(1)}%
+
+
+
+
+ 权重总和不为100%,建议点击"自动平衡"调整
+
+
+ )}
+
+
+
+ {/* 权重调节面板 */}
+
+ 权重调节
+
+
+ {categoryConfigs.map(category => (
+
+
+
+
{category.icon}
+
+
{category.name}
+
+ {category.id === 'soil' && '包括土壤pH、有机质、养分含量等指标'}
+ {category.id === 'climate' && '包括温度、降水、光照等气象条件'}
+ {category.id === 'topography' && '包括海拔、坡度等地形特征'}
+ {category.id === 'infrastructure' && '包括灌溉、交通等设施条件'}
+
+
+
+
+
+
{category.percentage}%
+
当前权重
+
+
+
+
+
handleUpdateWeight(category.id, value)}
+ max={60}
+ min={5}
+ step={1}
+ className="w-full"
+ />
+
+ 5%
+ 60%
+
+
+
+ ))}
+
+
+
+ {/* 权重配置说明 */}
+
+
+
+
+
权重配置说明:
+
+ - • 土壤条件 (35%): 土壤是农业生产的基础,影响作物生长和养分供应
+ - • 气候条件 (30%): 气候决定了作物的生长环境和适宜种植范围
+ - • 地形条件 (20%): 地形影响水土保持、机械化作业和微气候
+ - • 基础设施 (15%): 灌溉、交通等设施影响生产效率和成本
+ - • 权重总和应保持100%,可根据实际情况调整各维度的重要性
+ - • 建议在农业专家指导下进行权重配置,确保评价结果科学合理
+
+
+
+
+
+ {/* 预设配置 */}
+
+ 快速预设配置
+
+
+
+
+
+
+
+
+
+ );
+
+ if (isDialog) {
+ return (
+
+ );
+ }
+
+ return (
+
+
+
权重配置
+
+
+ 总权重: {Math.round(getTotalWeight() * 100)}%
+
+
+
+
+ {content}
+
+ );
+}
\ No newline at end of file
diff --git a/crop-x/src/app/(app)/land-information/suitability/auto/components/batchAnalysisService.tsx b/crop-x/src/app/(app)/land-information/suitability/auto/components/batchAnalysisService.tsx
new file mode 100644
index 0000000..a5cad72
--- /dev/null
+++ b/crop-x/src/app/(app)/land-information/suitability/auto/components/batchAnalysisService.tsx
@@ -0,0 +1,614 @@
+'use client';
+
+import { SpatialAnalysisState, SpatialAnalysisAction, LandBlock, AnalysisResult, BatchAnalysisTask, EvaluationFactor } from './spatialAnalysisReducer';
+
+// 空间分析服务接口
+interface SpatialAnalysisService {
+ analyzeBlock(block: LandBlock, factors: EvaluationFactor[], weights: any): Promise<{
+ success: boolean;
+ suitabilityIndex: number;
+ result?: AnalysisResult;
+ error?: string;
+ }>;
+}
+
+// 模拟空间分析服务
+class MockSpatialAnalysisService implements SpatialAnalysisService {
+ async analyzeBlock(block: LandBlock, factors: EvaluationFactor[], weights: any): Promise<{
+ success: boolean;
+ suitabilityIndex: number;
+ result?: AnalysisResult;
+ error?: string;
+ }> {
+ // 模拟API调用延迟
+ await new Promise(resolve => setTimeout(resolve, 800 + Math.random() * 1200));
+
+ try {
+ // 模拟分析失败的情况 (10% 概率)
+ if (Math.random() < 0.1) {
+ throw new Error('空间分析服务暂时不可用');
+ }
+
+ // 读取各项因子数据
+ const factorData = await this.readFactorData(block);
+
+ // 进行加权计算
+ const suitabilityIndex = this.calculateWeightedScore(factorData, factors, weights);
+
+ // 生成详细分析结果
+ const result = this.generateAnalysisResult(block, factorData, suitabilityIndex, weights);
+
+ return {
+ success: true,
+ suitabilityIndex,
+ result
+ };
+ } catch (error) {
+ return {
+ success: false,
+ suitabilityIndex: 0,
+ error: error instanceof Error ? error.message : '分析失败'
+ };
+ }
+ }
+
+ private async readFactorData(block: LandBlock): Promise {
+ // 模拟从各种数据源读取因子数据
+ await new Promise(resolve => setTimeout(resolve, 200));
+
+ return {
+ // 土壤因子数据
+ soilFactors: {
+ ph: block.pH || (6.0 + Math.random() * 2),
+ organicMatter: block.organicMatter || (1.5 + Math.random() * 2),
+ nitrogen: block.nitrogen || (60 + Math.random() * 80),
+ phosphorus: block.phosphorus || (30 + Math.random() * 40),
+ potassium: block.potassium || (80 + Math.random() * 80),
+ soilType: block.soilType,
+ texture: this.getSoilTexture(block.soilType)
+ },
+
+ // 气候因子数据 (模拟从气象API获取)
+ climateFactors: {
+ temperature: 15 + Math.random() * 20, // 年均温度
+ rainfall: 400 + Math.random() * 800, // 年降雨量
+ sunlight: 1600 + Math.random() * 800, // 年日照时数
+ humidity: 50 + Math.random() * 30,
+ frostFreeDays: 180 + Math.random() * 60
+ },
+
+ // 地形因子数据 (模拟从地形API获取)
+ topographyFactors: {
+ elevation: block.elevation || (20 + Math.random() * 100),
+ slope: block.slope || (Math.random() * 10),
+ aspect: Math.floor(Math.random() * 360), // 坡向
+ relief: Math.random() * 50, // 地形起伏度
+ drainageIndex: 0.3 + Math.random() * 0.7 // 排水指数
+ },
+
+ // 基础设施数据
+ infrastructureFactors: {
+ irrigationType: block.irrigation,
+ irrigationScore: this.getIrrigationScore(block.irrigation),
+ accessibility: 0.4 + Math.random() * 0.6, // 交通便利性
+ distanceToRoad: Math.random() * 5, // 到道路距离(km)
+ distanceToMarket: Math.random() * 20, // 到市场距离(km)
+ powerSupply: Math.random() > 0.3, // 电力供应
+ waterSupply: Math.random() > 0.2 // 水源供应
+ }
+ };
+ }
+
+ private getSoilTexture(soilType: string): string {
+ const textureMap: { [key: string]: string } = {
+ '壤土': 'loam',
+ '砂壤土': 'sandy_loam',
+ '黏土': 'clay',
+ '砂土': 'sandy',
+ '砾质土': 'gravelly'
+ };
+ return textureMap[soilType] || 'loam';
+ }
+
+ private getIrrigationScore(irrigationType: string): number {
+ const scoreMap: { [key: string]: number } = {
+ '滴灌': 0.95,
+ '喷灌': 0.85,
+ '漫灌': 0.70,
+ '无灌溉': 0.30
+ };
+ return scoreMap[irrigationType] || 0.70;
+ }
+
+ private calculateWeightedScore(factorData: any, factors: EvaluationFactor[], weights: any): number {
+ let totalScore = 0;
+ let totalWeight = 0;
+
+ // 土壤因子得分计算
+ const soilWeight = weights.soil || 0.35;
+ const soilScore = this.calculateSoilScore(factorData.soilFactors, factors);
+ totalScore += soilScore * soilWeight;
+ totalWeight += soilWeight;
+
+ // 气候因子得分计算
+ const climateWeight = weights.climate || 0.30;
+ const climateScore = this.calculateClimateScore(factorData.climateFactors, factors);
+ totalScore += climateScore * climateWeight;
+ totalWeight += climateWeight;
+
+ // 地形因子得分计算
+ const topographyWeight = weights.topography || 0.20;
+ const topographyScore = this.calculateTopographyScore(factorData.topographyFactors, factors);
+ totalScore += topographyScore * topographyWeight;
+ totalWeight += topographyWeight;
+
+ // 基础设施因子得分计算
+ const infrastructureWeight = weights.infrastructure || 0.15;
+ const infrastructureScore = this.calculateInfrastructureScore(factorData.infrastructureFactors, factors);
+ totalScore += infrastructureScore * infrastructureWeight;
+ totalWeight += infrastructureWeight;
+
+ return Math.round(totalWeight > 0 ? (totalScore / totalWeight) * 100 : 0);
+ }
+
+ private calculateSoilScore(soilFactors: any, factors: EvaluationFactor[]): number {
+ let score = 0;
+ let count = 0;
+
+ // pH值评分
+ if (soilFactors.ph >= 6.0 && soilFactors.ph <= 7.5) {
+ score += 90;
+ } else if (soilFactors.ph >= 5.5 && soilFactors.ph <= 8.0) {
+ score += 70;
+ } else {
+ score += 40;
+ }
+ count++;
+
+ // 有机质评分
+ if (soilFactors.organicMatter >= 2.5) {
+ score += 90;
+ } else if (soilFactors.organicMatter >= 1.5) {
+ score += 70;
+ } else {
+ score += 45;
+ }
+ count++;
+
+ // 养分评分 (NPK)
+ const npkScore = Math.min(100, (soilFactors.nitrogen + soilFactors.phosphorus + soilFactors.potassium) / 3);
+ score += npkScore;
+ count++;
+
+ return count > 0 ? score / count : 50;
+ }
+
+ private calculateClimateScore(climateFactors: any, factors: EvaluationFactor[]): number {
+ let score = 0;
+ let count = 0;
+
+ // 温度适宜性
+ if (climateFactors.temperature >= 15 && climateFactors.temperature <= 25) {
+ score += 90;
+ } else if (climateFactors.temperature >= 10 && climateFactors.temperature <= 30) {
+ score += 70;
+ } else {
+ score += 50;
+ }
+ count++;
+
+ // 降雨适宜性
+ if (climateFactors.rainfall >= 500 && climateFactors.rainfall <= 1000) {
+ score += 90;
+ } else if (climateFactors.rainfall >= 300 && climateFactors.rainfall <= 1500) {
+ score += 70;
+ } else {
+ score += 45;
+ }
+ count++;
+
+ // 日照充足性
+ if (climateFactors.sunlight >= 2000) {
+ score += 90;
+ } else if (climateFactors.sunlight >= 1600) {
+ score += 75;
+ } else {
+ score += 60;
+ }
+ count++;
+
+ return count > 0 ? score / count : 50;
+ }
+
+ private calculateTopographyScore(topographyFactors: any, factors: EvaluationFactor[]): number {
+ let score = 0;
+ let count = 0;
+
+ // 坡度评分
+ if (topographyFactors.slope <= 3) {
+ score += 95;
+ } else if (topographyFactors.slope <= 6) {
+ score += 80;
+ } else if (topographyFactors.slope <= 15) {
+ score += 60;
+ } else {
+ score += 30;
+ }
+ count++;
+
+ // 海拔适宜性
+ if (topographyFactors.elevation <= 100) {
+ score += 90;
+ } else if (topographyFactors.elevation <= 300) {
+ score += 80;
+ } else if (topographyFactors.elevation <= 600) {
+ score += 65;
+ } else {
+ score += 45;
+ }
+ count++;
+
+ // 排水条件
+ score += topographyFactors.drainageIndex * 100;
+ count++;
+
+ return count > 0 ? score / count : 50;
+ }
+
+ private calculateInfrastructureScore(infrastructureFactors: any, factors: EvaluationFactor[]): number {
+ let score = 0;
+ let count = 0;
+
+ // 灌溉条件
+ score += infrastructureFactors.irrigationScore * 100;
+ count++;
+
+ // 交通便利性
+ score += infrastructureFactors.accessibility * 100;
+ count++;
+
+ // 基础设施完善度
+ const infrastructureScore =
+ (infrastructureFactors.powerSupply ? 50 : 0) +
+ (infrastructureFactors.waterSupply ? 50 : 0);
+ score += infrastructureScore;
+ count++;
+
+ return count > 0 ? score / count : 50;
+ }
+
+ private generateAnalysisResult(block: LandBlock, factorData: any, suitabilityIndex: number, weights: any): AnalysisResult {
+ const soilScore = this.calculateSoilScore(factorData.soilFactors, []);
+ const climateScore = this.calculateClimateScore(factorData.climateFactors, []);
+ const topographyScore = this.calculateTopographyScore(factorData.topographyFactors, []);
+ const infrastructureScore = this.calculateInfrastructureScore(factorData.infrastructureFactors, []);
+
+ // 生成作物推荐
+ const recommendedCrops = this.generateCropRecommendations(block, factorData, suitabilityIndex);
+
+ // 生成风险因素
+ const riskFactors = this.generateRiskFactors(block, factorData, suitabilityIndex);
+
+ // 生成改进建议
+ const improvementSuggestions = this.generateImprovementSuggestions(block, factorData, soilScore, climateScore, topographyScore, infrastructureScore);
+
+ return {
+ blockId: block.id,
+ blockName: block.name,
+ overallScore: suitabilityIndex,
+ soilScore,
+ climateScore,
+ topographyScore,
+ infrastructureScore,
+ recommendedCrops,
+ riskFactors,
+ improvementSuggestions,
+ estimatedYield: Math.round(block.area * (suitabilityIndex / 100) * (6000 + Math.random() * 4000)),
+ economicReturn: Math.round(block.area * (suitabilityIndex / 100) * (8000 + Math.random() * 6000))
+ };
+ }
+
+ private generateCropRecommendations(block: LandBlock, factorData: any, suitabilityIndex: number): any[] {
+ // 简化的作物推荐逻辑
+ const crops = [
+ { name: '小麦', suitabilityScore: Math.min(95, suitabilityIndex + Math.random() * 10 - 5) },
+ { name: '玉米', suitabilityScore: Math.min(95, suitabilityIndex + Math.random() * 15 - 5) },
+ { name: '水稻', suitabilityScore: factorData.climateFactors.rainfall > 800 ? Math.min(95, suitabilityIndex + Math.random() * 10) : Math.max(40, suitabilityIndex - 20) },
+ { name: '大豆', suitabilityScore: Math.min(95, suitabilityIndex + Math.random() * 8 - 4) },
+ { name: '棉花', suitabilityScore: Math.min(95, suitabilityIndex + Math.random() * 12 - 6) }
+ ];
+
+ return crops
+ .map(crop => ({
+ crop: {
+ id: crop.name.toLowerCase(),
+ name: crop.name,
+ category: '粮食作物',
+ growthCycle: 100 + Math.floor(Math.random() * 50),
+ economicValue: 2000 + Math.floor(Math.random() * 6000),
+ riskLevel: crop.suitabilityScore > 70 ? 'low' : crop.suitabilityScore > 50 ? 'medium' : 'high' as const
+ },
+ suitabilityScore: Math.round(crop.suitabilityScore),
+ matchPercentage: Math.round(crop.suitabilityScore),
+ advantages: crop.suitabilityScore > 70 ? ['土壤条件适宜', '气候匹配度高'] : ['需要改善条件'],
+ disadvantages: crop.suitabilityScore < 60 ? ['土壤需要改良', '气候条件一般'] : [],
+ managementNotes: ['注意合理施肥', '加强田间管理']
+ }))
+ .filter(crop => crop.suitabilityScore > 40)
+ .sort((a, b) => b.suitabilityScore - a.suitabilityScore)
+ .slice(0, 3);
+ }
+
+ private generateRiskFactors(block: LandBlock, factorData: any, suitabilityIndex: number): string[] {
+ const risks = [];
+
+ if (suitabilityIndex < 40) {
+ risks.push('综合条件较差,种植风险较高');
+ }
+
+ if (factorData.soilFactors.ph < 6.0 || factorData.soilFactors.ph > 7.5) {
+ risks.push('土壤pH值偏离理想范围');
+ }
+
+ if (factorData.soilFactors.organicMatter < 1.5) {
+ risks.push('土壤有机质含量偏低');
+ }
+
+ if (factorData.topographyFactors.slope > 8) {
+ risks.push('坡度较大,存在水土流失风险');
+ }
+
+ if (factorData.infrastructureFactors.irrigationScore < 0.5) {
+ risks.push('灌溉条件不足,依赖自然降水');
+ }
+
+ if (factorData.climateFactors.rainfall < 400) {
+ risks.push('降雨量偏少,干旱风险较高');
+ }
+
+ return risks.length > 0 ? risks : ['无明显风险因素'];
+ }
+
+ private generateImprovementSuggestions(block: LandBlock, factorData: any, soilScore: number, climateScore: number, topographyScore: number, infrastructureScore: number): string[] {
+ const suggestions = [];
+
+ if (soilScore < 70) {
+ suggestions.push('增施有机肥,改善土壤结构');
+ }
+
+ if (soilScore < 60) {
+ suggestions.push('进行土壤检测,针对性补充养分');
+ }
+
+ if (climateScore < 70) {
+ suggestions.push('选择适应性更强的作物品种');
+ }
+
+ if (topographyScore < 70) {
+ suggestions.push('修建梯田或等高线种植');
+ }
+
+ if (infrastructureScore < 70) {
+ suggestions.push('完善灌溉和排水系统');
+ }
+
+ if (factorData.infrastructureFactors.accessibility < 0.6) {
+ suggestions.push('改善田间道路条件');
+ }
+
+ return suggestions.length > 0 ? suggestions : ['当前条件良好,继续保持'];
+ }
+}
+
+// 批量分析管理器
+export class BatchAnalysisManager {
+ private service: SpatialAnalysisService;
+ private dispatch: React.Dispatch;
+ private isRunning: boolean = false;
+ private currentTask: BatchAnalysisTask | null = null;
+
+ constructor(dispatch: React.Dispatch) {
+ this.service = new MockSpatialAnalysisService();
+ this.dispatch = dispatch;
+ }
+
+ async startBatchAnalysis(
+ taskName: string,
+ factors: EvaluationFactor[],
+ weightConfig: any
+ ): Promise {
+ if (this.isRunning) {
+ throw new Error('已有分析任务正在运行');
+ }
+
+ // 固定分析68个地块
+ const totalFields = 68;
+ const blockIds = Array.from({ length: totalFields }, (_, i) => `field-${i + 1}`);
+
+ // 创建批量分析任务
+ this.dispatch({
+ type: 'START_BATCH_ANALYSIS',
+ payload: { name: taskName, blockIds }
+ });
+
+ this.isRunning = true;
+
+ try {
+ // 获取创建的任务
+ const task = await this.waitForTaskCreation();
+ if (!task) return;
+
+ this.currentTask = task;
+
+ // 更新任务状态为运行中
+ this.dispatch({
+ type: 'UPDATE_BATCH_TASK',
+ payload: {
+ taskId: task.id,
+ updates: {
+ status: 'running',
+ startedAt: new Date().toLocaleString('zh-CN'),
+ totalBlocks: totalFields
+ }
+ }
+ });
+
+ // 循环处理所有地块
+ const results: AnalysisResult[] = [];
+ let highSuitability = 0;
+ let mediumSuitability = 0;
+ let lowSuitability = 0;
+
+ for (let i = 0; i < totalFields; i++) {
+ if (!this.isRunning) break; // 检查是否被取消
+
+ const fieldId = `field-${i + 1}`;
+ const fieldName = `地块${String.fromCharCode(65 + (i % 26))}${Math.floor(i / 26) + 1}`;
+
+ // 创建临时地块对象
+ const tempBlock: LandBlock = {
+ id: fieldId,
+ name: fieldName,
+ area: 10 + Math.random() * 20, // 随机面积10-30亩
+ location: { lat: 39.9042 + (Math.random() - 0.5) * 0.1, lng: 116.4074 + (Math.random() - 0.5) * 0.1 },
+ soilType: ['壤土', '砂壤土', '黏土'][Math.floor(Math.random() * 3)],
+ elevation: 20 + Math.random() * 100,
+ slope: Math.random() * 10,
+ irrigation: ['滴灌', '喷灌', '漫灌'][Math.floor(Math.random() * 3)],
+ pH: 6.0 + Math.random() * 2,
+ organicMatter: 1.5 + Math.random() * 2,
+ nitrogen: 60 + Math.random() * 80,
+ phosphorus: 30 + Math.random() * 40,
+ potassium: 80 + Math.random() * 80
+ };
+
+ // 更新当前处理的地块
+ this.dispatch({
+ type: 'UPDATE_BATCH_TASK',
+ payload: {
+ taskId: task.id,
+ updates: {
+ currentProcessingBlock: fieldName,
+ processedBlocks: i,
+ progress: Math.round((i / totalFields) * 100)
+ }
+ }
+ });
+
+ try {
+ // 调用空间分析服务
+ const analysisResult = await this.service.analyzeBlock(tempBlock, factors, weightConfig);
+
+ if (analysisResult.success && analysisResult.result) {
+ results.push(analysisResult.result);
+
+ // 统计适宜性等级
+ const score = analysisResult.suitabilityIndex;
+ if (score >= 80) {
+ highSuitability++;
+ } else if (score >= 60) {
+ mediumSuitability++;
+ } else {
+ lowSuitability++;
+ }
+ }
+
+ // 更新任务统计
+ this.dispatch({
+ type: 'UPDATE_BATCH_TASK',
+ payload: {
+ taskId: task.id,
+ updates: {
+ successCount: results.length,
+ failedCount: i + 1 - results.length,
+ progress: Math.round(((i + 1) / totalFields) * 100)
+ }
+ }
+ });
+
+ } catch (error) {
+ console.error(`分析地块 ${fieldName} 失败:`, error);
+ }
+ }
+
+ // 完成批量分析
+ this.dispatch({
+ type: 'COMPLETE_BATCH_ANALYSIS',
+ payload: { taskId: task.id, results }
+ });
+
+ } catch (error) {
+ console.error('批量分析失败:', error);
+ if (this.currentTask) {
+ this.dispatch({
+ type: 'UPDATE_BATCH_TASK',
+ payload: {
+ taskId: this.currentTask.id,
+ updates: {
+ status: 'failed',
+ errorMessage: error instanceof Error ? error.message : '未知错误'
+ }
+ }
+ });
+ }
+ } finally {
+ this.isRunning = false;
+ this.currentTask = null;
+ }
+ }
+
+ pauseAnalysis(): void {
+ if (this.currentTask) {
+ this.dispatch({
+ type: 'PAUSE_BATCH_ANALYSIS',
+ payload: { taskId: this.currentTask.id }
+ });
+ }
+ this.isRunning = false;
+ }
+
+ resumeAnalysis(): void {
+ if (this.currentTask) {
+ this.dispatch({
+ type: 'RESUME_BATCH_ANALYSIS',
+ payload: { taskId: this.currentTask.id }
+ });
+ this.isRunning = true;
+ }
+ }
+
+ cancelAnalysis(): void {
+ if (this.currentTask) {
+ this.dispatch({
+ type: 'CANCEL_BATCH_ANALYSIS',
+ payload: { taskId: this.currentTask.id }
+ });
+ }
+ this.isRunning = false;
+ this.currentTask = null;
+ }
+
+ private async waitForTaskCreation(): Promise {
+ // 等待任务创建完成 - 直接返回一个虚拟任务
+ return new Promise((resolve) => {
+ setTimeout(() => {
+ resolve({
+ id: Date.now().toString(),
+ name: '批量分析任务',
+ status: 'running',
+ createdAt: new Date().toLocaleString('zh-CN'),
+ totalBlocks: 68,
+ processedBlocks: 0,
+ successCount: 0,
+ failedCount: 0,
+ selectedBlockIds: [],
+ weightConfig: {},
+ factorIds: [],
+ progress: 0
+ });
+ }, 100);
+ });
+ }
+}
+
+export default BatchAnalysisManager;
\ No newline at end of file
diff --git a/crop-x/src/app/(app)/land-information/suitability/auto/components/spatialAnalysisReducer.tsx b/crop-x/src/app/(app)/land-information/suitability/auto/components/spatialAnalysisReducer.tsx
new file mode 100644
index 0000000..60b6498
--- /dev/null
+++ b/crop-x/src/app/(app)/land-information/suitability/auto/components/spatialAnalysisReducer.tsx
@@ -0,0 +1,621 @@
+'use client';
+
+// 空间分析数据类型定义
+export interface LandBlock {
+ id: string;
+ name: string;
+ area: number;
+ location: {
+ lat: number;
+ lng: number;
+ };
+ soilType: string;
+ currentCrop?: string;
+ elevation?: number;
+ slope?: number;
+ irrigation?: string;
+ pH?: number;
+ organicMatter?: number;
+ nitrogen?: number;
+ phosphorus?: number;
+ potassium?: number;
+ suitabilityIndex?: number; // 适宜性指数
+ lastAnalyzed?: string; // 最后分析时间
+ analysisStatus?: 'pending' | 'analyzing' | 'completed' | 'failed'; // 分析状态
+}
+
+export interface CropInfo {
+ id: string;
+ name: string;
+ category: string;
+ growthCycle: number;
+ soilRequirements: {
+ pH: { min: number; max: number };
+ organicMatter: { min: number };
+ nitrogen: { min: number };
+ phosphorus: { min: number };
+ potassium: { min: number };
+ };
+ climateRequirements: {
+ temperature: { min: number; max: number };
+ rainfall: { min: number; max: number };
+ sunlight: { min: number };
+ };
+ economicValue: number;
+ riskLevel: 'low' | 'medium' | 'high';
+ suitableRegions: string[];
+}
+
+export interface EvaluationFactor {
+ id: string;
+ name: string;
+ weight: number;
+ enabled: boolean;
+ category: 'soil' | 'climate' | 'topography' | 'infrastructure';
+ description: string;
+}
+
+export interface AnalysisResult {
+ blockId: string;
+ blockName: string;
+ overallScore: number;
+ soilScore: number;
+ climateScore: number;
+ topographyScore: number;
+ infrastructureScore: number;
+ recommendedCrops: CropRecommendation[];
+ riskFactors: string[];
+ improvementSuggestions: string[];
+ estimatedYield?: number;
+ economicReturn?: number;
+}
+
+export interface CropRecommendation {
+ crop: CropInfo;
+ suitabilityScore: number;
+ matchPercentage: number;
+ advantages: string[];
+ disadvantages: string[];
+ managementNotes: string[];
+}
+
+export interface WeightConfig {
+ soil: number;
+ climate: number;
+ topography: number;
+ infrastructure: number;
+}
+
+export interface AnalysisReport {
+ id: string;
+ title: string;
+ createdAt: string;
+ landBlocks: LandBlock[];
+ results: AnalysisResult[];
+ factors: EvaluationFactor[];
+ weightConfig: WeightConfig;
+ summary: {
+ totalBlocks: number;
+ averageScore: number;
+ topRecommendedCrops: Array<{ crop: string; frequency: number; avgScore: number }>;
+ riskDistribution: { low: number; medium: number; high: number };
+ };
+}
+
+// 批量分析任务状态
+export interface BatchAnalysisTask {
+ id: string;
+ name: string;
+ status: 'pending' | 'running' | 'completed' | 'failed' | 'paused';
+ createdAt: string;
+ startedAt?: string;
+ completedAt?: string;
+ totalBlocks: number;
+ processedBlocks: number;
+ successCount: number;
+ failedCount: number;
+ selectedBlockIds: string[];
+ weightConfig: WeightConfig;
+ factorIds: string[];
+ progress: number;
+ currentProcessingBlock?: string;
+ errorMessage?: string;
+}
+
+// 状态管理
+export interface SpatialAnalysisState {
+ // 地块数据
+ landBlocks: LandBlock[];
+ selectedBlocks: string[];
+
+ // 评价因子
+ factors: EvaluationFactor[];
+ weightConfig: WeightConfig;
+
+ // 作物数据库
+ cropDatabase: CropInfo[];
+ selectedCrops: string[];
+
+ // 分析结果
+ analysisResults: AnalysisResult[];
+ currentReport: AnalysisReport | null;
+
+ // 批量分析任务
+ batchTasks: BatchAnalysisTask[];
+ currentTask: BatchAnalysisTask | null;
+ isBatchAnalyzing: boolean;
+
+ // UI状态
+ activeTab: string;
+ isAnalyzing: boolean;
+ showWeightDialog: boolean;
+ showFactorDialog: boolean;
+ showCompareDialog: boolean;
+
+ // 筛选条件
+ filters: {
+ scoreRange: [number, number];
+ soilTypes: string[];
+ riskLevels: string[];
+ cropCategories: string[];
+ };
+
+ // 图表数据
+ chartData: {
+ scoreDistribution: Array<{ range: string; count: number }>;
+ factorPerformance: Array<{ factor: string; score: number; weight: number }>;
+ cropComparison: Array<{ crop: string; suitability: number; economic: number; risk: string }>;
+ };
+}
+
+// 动作类型
+export type SpatialAnalysisAction =
+ | { type: 'SET_LAND_BLOCKS'; payload: LandBlock[] }
+ | { type: 'ADD_LAND_BLOCK'; payload: LandBlock }
+ | { type: 'UPDATE_LAND_BLOCK'; payload: { id: string; updates: Partial } }
+ | { type: 'DELETE_LAND_BLOCK'; payload: string }
+ | { type: 'SET_SELECTED_BLOCKS'; payload: string[] }
+ | { type: 'TOGGLE_BLOCK_SELECTION'; payload: string }
+
+ | { type: 'SET_FACTORS'; payload: EvaluationFactor[] }
+ | { type: 'UPDATE_FACTOR_WEIGHT'; payload: { id: string; weight: number } }
+ | { type: 'TOGGLE_FACTOR'; payload: string }
+ | { type: 'SET_WEIGHT_CONFIG'; payload: WeightConfig }
+
+ | { type: 'SET_CROP_DATABASE'; payload: CropInfo[] }
+ | { type: 'SET_SELECTED_CROPS'; payload: string[] }
+
+ | { type: 'SET_ANALYSIS_RESULTS'; payload: AnalysisResult[] }
+ | { type: 'ADD_ANALYSIS_RESULT'; payload: AnalysisResult }
+ | { type: 'SET_CURRENT_REPORT'; payload: AnalysisReport | null }
+
+ | { type: 'SET_ACTIVE_TAB'; payload: string }
+ | { type: 'SET_ANALYZING'; payload: boolean }
+ | { type: 'TOGGLE_WEIGHT_DIALOG' }
+ | { type: 'TOGGLE_FACTOR_DIALOG' }
+ | { type: 'TOGGLE_COMPARE_DIALOG' }
+
+ | { type: 'SET_FILTERS'; payload: Partial }
+ | { type: 'RESET_FILTERS' }
+
+ | { type: 'SET_CHART_DATA'; payload: Partial }
+
+ | { type: 'RUN_ANALYSIS'; payload: { blockIds?: string[] } }
+ | { type: 'GENERATE_REPORT'; payload: { title: string } }
+
+ // 批量分析相关动作
+ | { type: 'START_BATCH_ANALYSIS'; payload: { name: string; blockIds: string[] } }
+ | { type: 'UPDATE_BATCH_TASK'; payload: { taskId: string; updates: Partial } }
+ | { type: 'SET_CURRENT_TASK'; payload: BatchAnalysisTask | null }
+ | { type: 'COMPLETE_BATCH_ANALYSIS'; payload: { taskId: string; results: AnalysisResult[] } }
+ | { type: 'PAUSE_BATCH_ANALYSIS'; payload: { taskId: string } }
+ | { type: 'RESUME_BATCH_ANALYSIS'; payload: { taskId: string } }
+ | { type: 'CANCEL_BATCH_ANALYSIS'; payload: { taskId: string } }
+ | { type: 'UPDATE_BLOCK_ANALYSIS_STATUS'; payload: { blockId: string; status: LandBlock['analysisStatus']; suitabilityIndex?: number } }
+
+ | { type: 'LOAD_FROM_STORAGE' }
+ | { type: 'SAVE_TO_STORAGE' }
+ | { type: 'RESET_STATE' };
+
+// 默认评价因子
+const defaultFactors: EvaluationFactor[] = [
+ {
+ id: 'soil_ph',
+ name: '土壤pH值',
+ weight: 0.15,
+ enabled: true,
+ category: 'soil',
+ description: '土壤酸碱度对作物生长的影响'
+ },
+ {
+ id: 'soil_organic_matter',
+ name: '有机质含量',
+ weight: 0.12,
+ enabled: true,
+ category: 'soil',
+ description: '土壤肥力的重要指标'
+ },
+ {
+ id: 'soil_nutrients',
+ name: '土壤养分',
+ weight: 0.18,
+ enabled: true,
+ category: 'soil',
+ description: '氮磷钾等营养元素含量'
+ },
+ {
+ id: 'soil_texture',
+ name: '土壤质地',
+ weight: 0.10,
+ enabled: true,
+ category: 'soil',
+ description: '土壤物理结构和透气性'
+ },
+ {
+ id: 'temperature',
+ name: '温度条件',
+ weight: 0.15,
+ enabled: true,
+ category: 'climate',
+ description: '积温和温度适宜性'
+ },
+ {
+ id: 'rainfall',
+ name: '降水条件',
+ weight: 0.12,
+ enabled: true,
+ category: 'climate',
+ description: '降雨量及其分布'
+ },
+ {
+ id: 'sunlight',
+ name: '光照条件',
+ weight: 0.10,
+ enabled: true,
+ category: 'climate',
+ description: '日照时数和光照强度'
+ },
+ {
+ id: 'elevation',
+ name: '海拔高度',
+ weight: 0.08,
+ enabled: true,
+ category: 'topography',
+ description: '海拔对气候和作物的影响'
+ },
+ {
+ id: 'slope',
+ name: '坡度坡向',
+ weight: 0.06,
+ enabled: true,
+ category: 'topography',
+ description: '地形对水土保持的影响'
+ },
+ {
+ id: 'irrigation',
+ name: '灌溉条件',
+ weight: 0.12,
+ enabled: true,
+ category: 'infrastructure',
+ description: '水利设施和灌溉保障'
+ },
+ {
+ id: 'accessibility',
+ name: '交通便利性',
+ weight: 0.05,
+ enabled: true,
+ category: 'infrastructure',
+ description: '道路和运输条件'
+ }
+];
+
+// 默认权重配置
+const defaultWeightConfig: WeightConfig = {
+ soil: 0.35,
+ climate: 0.30,
+ topography: 0.20,
+ infrastructure: 0.15
+};
+
+// 初始状态
+export const initialSpatialAnalysisState: SpatialAnalysisState = {
+ landBlocks: [],
+ selectedBlocks: [],
+ factors: defaultFactors,
+ weightConfig: defaultWeightConfig,
+ cropDatabase: [],
+ selectedCrops: [],
+ analysisResults: [],
+ currentReport: null,
+ batchTasks: [],
+ currentTask: null,
+ isBatchAnalyzing: false,
+ activeTab: 'overview',
+ isAnalyzing: false,
+ showWeightDialog: false,
+ showFactorDialog: false,
+ showCompareDialog: false,
+ filters: {
+ scoreRange: [0, 100],
+ soilTypes: [],
+ riskLevels: [],
+ cropCategories: []
+ },
+ chartData: {
+ scoreDistribution: [],
+ factorPerformance: [],
+ cropComparison: []
+ }
+};
+
+// Reducer
+export function spatialAnalysisReducer(
+ state: SpatialAnalysisState,
+ action: SpatialAnalysisAction
+): SpatialAnalysisState {
+ switch (action.type) {
+ case 'SET_LAND_BLOCKS':
+ return { ...state, landBlocks: action.payload };
+
+ case 'ADD_LAND_BLOCK':
+ return { ...state, landBlocks: [...state.landBlocks, action.payload] };
+
+ case 'UPDATE_LAND_BLOCK':
+ return {
+ ...state,
+ landBlocks: state.landBlocks.map(block =>
+ block.id === action.payload.id
+ ? { ...block, ...action.payload.updates }
+ : block
+ )
+ };
+
+ case 'DELETE_LAND_BLOCK':
+ return {
+ ...state,
+ landBlocks: state.landBlocks.filter(block => block.id !== action.payload),
+ selectedBlocks: state.selectedBlocks.filter(id => id !== action.payload)
+ };
+
+ case 'SET_SELECTED_BLOCKS':
+ return { ...state, selectedBlocks: action.payload };
+
+ case 'TOGGLE_BLOCK_SELECTION':
+ const isSelected = state.selectedBlocks.includes(action.payload);
+ return {
+ ...state,
+ selectedBlocks: isSelected
+ ? state.selectedBlocks.filter(id => id !== action.payload)
+ : [...state.selectedBlocks, action.payload]
+ };
+
+ case 'SET_FACTORS':
+ return { ...state, factors: action.payload };
+
+ case 'UPDATE_FACTOR_WEIGHT':
+ return {
+ ...state,
+ factors: state.factors.map(factor =>
+ factor.id === action.payload.id
+ ? { ...factor, weight: action.payload.weight }
+ : factor
+ )
+ };
+
+ case 'TOGGLE_FACTOR':
+ return {
+ ...state,
+ factors: state.factors.map(factor =>
+ factor.id === action.payload
+ ? { ...factor, enabled: !factor.enabled }
+ : factor
+ )
+ };
+
+ case 'SET_WEIGHT_CONFIG':
+ return { ...state, weightConfig: action.payload };
+
+ case 'SET_CROP_DATABASE':
+ return { ...state, cropDatabase: action.payload };
+
+ case 'SET_SELECTED_CROPS':
+ return { ...state, selectedCrops: action.payload };
+
+ case 'SET_ANALYSIS_RESULTS':
+ return { ...state, analysisResults: action.payload };
+
+ case 'ADD_ANALYSIS_RESULT':
+ return { ...state, analysisResults: [...state.analysisResults, action.payload] };
+
+ case 'SET_CURRENT_REPORT':
+ return { ...state, currentReport: action.payload };
+
+ case 'SET_ACTIVE_TAB':
+ return { ...state, activeTab: action.payload };
+
+ case 'SET_ANALYZING':
+ return { ...state, isAnalyzing: action.payload };
+
+ case 'TOGGLE_WEIGHT_DIALOG':
+ return { ...state, showWeightDialog: !state.showWeightDialog };
+
+ case 'TOGGLE_FACTOR_DIALOG':
+ return { ...state, showFactorDialog: !state.showFactorDialog };
+
+ case 'TOGGLE_COMPARE_DIALOG':
+ return { ...state, showCompareDialog: !state.showCompareDialog };
+
+ case 'SET_FILTERS':
+ return {
+ ...state,
+ filters: { ...state.filters, ...action.payload }
+ };
+
+ case 'RESET_FILTERS':
+ return {
+ ...state,
+ filters: {
+ scoreRange: [0, 100],
+ soilTypes: [],
+ riskLevels: [],
+ cropCategories: []
+ }
+ };
+
+ case 'SET_CHART_DATA':
+ return {
+ ...state,
+ chartData: { ...state.chartData, ...action.payload }
+ };
+
+ case 'RUN_ANALYSIS':
+ // 这里会触发分析逻辑,实际实现在组件中处理
+ return { ...state, isAnalyzing: true };
+
+ case 'GENERATE_REPORT':
+ // 报告生成逻辑在组件中处理
+ return state;
+
+ // 批量分析相关处理
+ case 'START_BATCH_ANALYSIS':
+ const newTask: BatchAnalysisTask = {
+ id: Date.now().toString(),
+ name: action.payload.name,
+ status: 'pending',
+ createdAt: new Date().toLocaleString('zh-CN'),
+ totalBlocks: action.payload.blockIds.length,
+ processedBlocks: 0,
+ successCount: 0,
+ failedCount: 0,
+ selectedBlockIds: action.payload.blockIds,
+ weightConfig: state.weightConfig,
+ factorIds: state.factors.filter(f => f.enabled).map(f => f.id),
+ progress: 0
+ };
+ return {
+ ...state,
+ batchTasks: [...state.batchTasks, newTask],
+ currentTask: newTask,
+ isBatchAnalyzing: true
+ };
+
+ case 'UPDATE_BATCH_TASK':
+ return {
+ ...state,
+ batchTasks: state.batchTasks.map(task =>
+ task.id === action.payload.taskId
+ ? { ...task, ...action.payload.updates }
+ : task
+ ),
+ currentTask: state.currentTask?.id === action.payload.taskId
+ ? { ...state.currentTask, ...action.payload.updates }
+ : state.currentTask
+ };
+
+ case 'SET_CURRENT_TASK':
+ return {
+ ...state,
+ currentTask: action.payload
+ };
+
+ case 'COMPLETE_BATCH_ANALYSIS':
+ return {
+ ...state,
+ batchTasks: state.batchTasks.map(task =>
+ task.id === action.payload.taskId
+ ? {
+ ...task,
+ status: 'completed',
+ completedAt: new Date().toLocaleString('zh-CN'),
+ progress: 100
+ }
+ : task
+ ),
+ analysisResults: action.payload.results,
+ isBatchAnalyzing: false
+ };
+
+ case 'PAUSE_BATCH_ANALYSIS':
+ return {
+ ...state,
+ batchTasks: state.batchTasks.map(task =>
+ task.id === action.payload.taskId
+ ? { ...task, status: 'paused' }
+ : task
+ ),
+ isBatchAnalyzing: false
+ };
+
+ case 'RESUME_BATCH_ANALYSIS':
+ return {
+ ...state,
+ batchTasks: state.batchTasks.map(task =>
+ task.id === action.payload.taskId
+ ? { ...task, status: 'running' }
+ : task
+ ),
+ isBatchAnalyzing: true
+ };
+
+ case 'CANCEL_BATCH_ANALYSIS':
+ return {
+ ...state,
+ batchTasks: state.batchTasks.map(task =>
+ task.id === action.payload.taskId
+ ? { ...task, status: 'failed', errorMessage: '用户取消分析' }
+ : task
+ ),
+ currentTask: null,
+ isBatchAnalyzing: false
+ };
+
+ case 'UPDATE_BLOCK_ANALYSIS_STATUS':
+ return {
+ ...state,
+ landBlocks: state.landBlocks.map(block =>
+ block.id === action.payload.blockId
+ ? {
+ ...block,
+ analysisStatus: action.payload.status,
+ ...(action.payload.suitabilityIndex && { suitabilityIndex: action.payload.suitabilityIndex }),
+ lastAnalyzed: action.payload.status === 'completed' ? new Date().toLocaleString('zh-CN') : block.lastAnalyzed
+ }
+ : block
+ )
+ };
+
+ case 'LOAD_FROM_STORAGE':
+ try {
+ const savedData = localStorage.getItem('spatial-analysis-data');
+ if (savedData) {
+ const parsed = JSON.parse(savedData);
+ return { ...state, ...parsed };
+ }
+ } catch (error) {
+ console.error('Failed to load spatial analysis data:', error);
+ }
+ return state;
+
+ case 'SAVE_TO_STORAGE':
+ try {
+ const dataToSave = {
+ landBlocks: state.landBlocks,
+ selectedBlocks: state.selectedBlocks,
+ factors: state.factors,
+ weightConfig: state.weightConfig,
+ analysisResults: state.analysisResults,
+ filters: state.filters
+ };
+ localStorage.setItem('spatial-analysis-data', JSON.stringify(dataToSave));
+ } catch (error) {
+ console.error('Failed to save spatial analysis data:', error);
+ }
+ return state;
+
+ case 'RESET_STATE':
+ return initialSpatialAnalysisState;
+
+ default:
+ return state;
+ }
+}
\ No newline at end of file
diff --git a/crop-x/src/app/(app)/land-information/suitability/auto/page.tsx b/crop-x/src/app/(app)/land-information/suitability/auto/page.tsx
new file mode 100644
index 0000000..ef3002d
--- /dev/null
+++ b/crop-x/src/app/(app)/land-information/suitability/auto/page.tsx
@@ -0,0 +1,26 @@
+'use client';
+
+import { useReducer, useEffect } from 'react';
+import { Card } from '@/components/ui/card';
+import { spatialAnalysisReducer, initialSpatialAnalysisState, SpatialAnalysisState } from './components/spatialAnalysisReducer';
+import SpatialAnalysisContent from './components/SpatialAnalysisContent';
+
+export default function BatchEvaluationPage() {
+ const [state, dispatch] = useReducer(spatialAnalysisReducer, initialSpatialAnalysisState);
+
+ useEffect(() => {
+ // 加载存储的数据
+ dispatch({ type: 'LOAD_FROM_STORAGE' });
+ }, []);
+
+ useEffect(() => {
+ // 保存数据到localStorage
+ dispatch({ type: 'SAVE_TO_STORAGE' });
+ }, [state.factors, state.weightConfig, state.analysisResults, state.batchTasks]);
+
+ return (
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/crop-x/src/app/(app)/land-information/suitability/batch/page.tsx b/crop-x/src/app/(app)/land-information/suitability/batch/page.tsx
deleted file mode 100644
index b3e2a4f..0000000
--- a/crop-x/src/app/(app)/land-information/suitability/batch/page.tsx
+++ /dev/null
@@ -1,18 +0,0 @@
-'use client';
-
-import { Card } from '@/components/ui/card';
-
-export default function BatchPage() {
- return (
-
-
- 批量适宜性评价
-
-
- 页面路径: /land-information/suitability/batch
-
-
-
-
- );
-}
\ No newline at end of file
diff --git a/crop-x/src/app/(app)/land-information/suitability/comprehensive/page.tsx b/crop-x/src/app/(app)/land-information/suitability/comprehensive/page.tsx
deleted file mode 100644
index 63e3c5f..0000000
--- a/crop-x/src/app/(app)/land-information/suitability/comprehensive/page.tsx
+++ /dev/null
@@ -1,18 +0,0 @@
-'use client';
-
-import { Card } from '@/components/ui/card';
-
-export default function ComprehensivePage() {
- return (
-
-
- 综合适宜性评价
-
-
- 页面路径: /land-information/suitability/comprehensive
-
-
-
-
- );
-}
\ No newline at end of file
diff --git a/crop-x/src/app/(app)/land-information/suitability/crop/page.tsx b/crop-x/src/app/(app)/land-information/suitability/crop/page.tsx
deleted file mode 100644
index 5db96d3..0000000
--- a/crop-x/src/app/(app)/land-information/suitability/crop/page.tsx
+++ /dev/null
@@ -1,18 +0,0 @@
-'use client';
-
-import { Card } from '@/components/ui/card';
-
-export default function CropPage() {
- return (
-
-
- 作物适宜性评价
-
-
- 页面路径: /land-information/suitability/crop
-
-
-
-
- );
-}
\ No newline at end of file
diff --git a/crop-x/src/app/(app)/land-information/suitability/multiFactor/components/MultiFactorEvaluation.tsx b/crop-x/src/app/(app)/land-information/suitability/multiFactor/components/MultiFactorEvaluation.tsx
new file mode 100644
index 0000000..065728a
--- /dev/null
+++ b/crop-x/src/app/(app)/land-information/suitability/multiFactor/components/MultiFactorEvaluation.tsx
@@ -0,0 +1,700 @@
+/**
+ * 多因子综合评价组件
+ * 提供地块适宜性评价、权重配置、批量分析和作物推荐功能
+ */
+
+'use client';
+
+import { useState, useEffect } from 'react';
+import { Card } from '@/components/ui/card';
+import { Button } from '@/components/ui/button';
+import { Badge } from '@/components/ui/badge';
+import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
+import { Input } from '@/components/ui/input';
+import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogTrigger } from '@/components/ui/dialog';
+import { Progress } from '@/components/ui/progress';
+import { Slider } from '@/components/ui/slider';
+import { Textarea } from '@/components/ui/textarea';
+import {
+ Leaf,
+ TrendingUp,
+ Award,
+ AlertTriangle,
+ CheckCircle2,
+ Play,
+ Settings,
+ Download,
+ Eye,
+ Calculator,
+ Database,
+ RefreshCw,
+ Zap,
+ Target,
+ Droplet,
+ Cloud,
+ Sun,
+ ThermometerSun,
+ BookOpen,
+ Beaker,
+ Info,
+ BarChart3,
+ Filter
+} from 'lucide-react';
+import { toast } from 'sonner';
+
+import {
+ EvaluationFactor,
+ SuitabilityResult,
+ FactorWeight,
+ BatchProgress,
+ getGradeColor,
+ getScoreColor,
+ getSuitabilityLevelColor,
+ formatDate,
+ MOCK_FIELDS
+} from './multiFactorTypes';
+import {
+ MultiFactorService
+} from './multiFactorService';
+import {
+ matchCropsForField,
+ cropKnowledgeBase
+} from './cropKnowledgeBase';
+
+export function MultiFactorEvaluation() {
+ const [selectedField, setSelectedField] = useState('field-1');
+ const [showWeightConfig, setShowWeightConfig] = useState(false);
+ const [showKnowledgeBase, setShowKnowledgeBase] = useState(false);
+ const [batchProgress, setBatchProgress] = useState({
+ total: 0,
+ processed: 0,
+ highSuitability: 0,
+ mediumSuitability: 0,
+ lowSuitability: 0,
+ currentField: '',
+ });
+ const [isBatchRunning, setIsBatchRunning] = useState(false);
+ const [batchAnalysisResults, setBatchAnalysisResults] = useState([]);
+
+ // 评价因子权重配置
+ const [factorWeights, setFactorWeights] = useState([
+ { id: 'ph', name: 'pH值', weight: 20, unit: '' },
+ { id: 'organic', name: '有机质含量', weight: 25, unit: 'g/kg' },
+ { id: 'depth', name: '土层厚度', weight: 20, unit: 'cm' },
+ { id: 'nitrogen', name: '全氮', weight: 10, unit: 'g/kg' },
+ { id: 'phosphorus', name: '全磷', weight: 10, unit: 'g/kg' },
+ { id: 'potassium', name: '全钾', weight: 10, unit: 'g/kg' },
+ { id: 'drainage', name: '排水性', weight: 5, unit: '' },
+ ]);
+
+ // 模拟适宜性评价结果
+ const [evaluationResults, setEvaluationResults] = useState(
+ MultiFactorService.generateMockEvaluationData()
+ );
+
+ // 获取当前选中的地块结果
+ const currentResult =
+ evaluationResults.find(r => r.fieldId === selectedField) ||
+ batchAnalysisResults.find(r => r.fieldId === selectedField) ||
+ evaluationResults[0];
+
+ // 批量分析处理函数
+ const handleRunBatchAnalysis = async () => {
+ const validation = MultiFactorService.validateWeights(factorWeights);
+ if (!validation.isValid) {
+ toast.error(`权重总和必须为100%才能进行批量分析(当前:${validation.totalWeight}%)`);
+ return;
+ }
+
+ setIsBatchRunning(true);
+ setBatchProgress({
+ total: 68,
+ processed: 0,
+ highSuitability: 0,
+ mediumSuitability: 0,
+ lowSuitability: 0,
+ currentField: '',
+ });
+ setBatchAnalysisResults([]);
+
+ toast.success('开始批量分析,正在读取地块数据...');
+
+ try {
+ const results = await MultiFactorService.runBatchAnalysis(
+ factorWeights,
+ (progress) => {
+ setBatchProgress(progress);
+ }
+ );
+
+ setBatchAnalysisResults(results);
+ setIsBatchRunning(false);
+ toast.success(`批量分析完成!已为${results.length}个地块生成适宜性评价结果`);
+ } catch (error) {
+ setIsBatchRunning(false);
+ toast.error('批量分析失败');
+ }
+ };
+
+ const handleUpdateWeight = (id: string, newWeight: number) => {
+ setFactorWeights(prev =>
+ MultiFactorService.updateWeight(prev, id, newWeight)
+ );
+ };
+
+ const handleResetWeights = () => {
+ setFactorWeights(MultiFactorService.resetWeights());
+ toast.success('权重已恢复默认值');
+ };
+
+ const handleApplyPreset = (preset: 'grain' | 'economic' | 'default') => {
+ setFactorWeights(MultiFactorService.getWeightPreset(preset));
+ const presetName = preset === 'grain' ? '粮食作物' : preset === 'economic' ? '经济作物' : '默认';
+ toast.success(`已应用${presetName}权重方案`);
+ };
+
+ const totalWeight = factorWeights.reduce((sum, f) => sum + f.weight, 0);
+
+ // 执行地块适宜性评价
+ const handleEvaluateField = () => {
+ const validation = MultiFactorService.validateWeights(factorWeights);
+ if (!validation.isValid) {
+ toast.error(`权重总和必须为100%才能进行评价(当前:${validation.totalWeight}%)`);
+ return;
+ }
+
+ try {
+ const result = MultiFactorService.generateEvaluationResult(selectedField, factorWeights);
+ setEvaluationResults(prev =>
+ prev.map(r => r.fieldId === selectedField ? result : r)
+ );
+ toast.success('评价完成!已应用当前权重配置计算综合得分');
+ } catch (error) {
+ toast.error('评价失败');
+ }
+ };
+
+ const exportResults = () => {
+ const resultsToExport = batchAnalysisResults.length > 0 ? batchAnalysisResults : evaluationResults;
+ toast.success('正在导出评价结果...');
+ // 模拟导出功能
+ setTimeout(() => {
+ toast.success(`已导出${resultsToExport.length}个地块的评价结果`);
+ }, 2000);
+ };
+
+ return (
+
+
+
+
多因子综合评价
+
+ 地块适宜性评价、自动化空间分析与作物适配推荐
+
+
+
+
+
+
+
+
+ {/* 多因子综合评价 */}
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* 评价结果总览 */}
+
+
+
+
+
综合评分
+
+ {currentResult.totalScore}
+
+
+ {currentResult.grade}
+
+
+
+
+
+
+
+
优秀因子
+
+ {currentResult.factors.filter(f => f.score >= 80).length}
+
+
+ / {currentResult.factors.length} 项
+
+
+
+
+
+
+
+
待改善因子
+
+ {currentResult.factors.filter(f => f.score < 70).length}
+
+
需要优化
+
+
+
+
+
+
+
评价时间
+
+ {formatDate(currentResult.timestamp)}
+
+
最近更新
+
+
+
+
+ {/* 因子详细评分 */}
+
+ 评价因子详细分析
+
+ {currentResult.factors.map((factor) => (
+
+
+
+ {factor.name}
+
+ 权重: {factor.weight}%
+
+
+
+
+ 实际值: {factor.value.toFixed(1)}{factor.unit}
+
+
+ 最佳范围: {factor.optimalRange[0]}-{factor.optimalRange[1]}{factor.unit}
+
+
+ {factor.score}分
+
+
+
+
+
+ ))}
+
+
+
+ {/* 加权计算说明 */}
+
+ 层次分析法(AHP)加权计算
+
+
+
计算公式:
+
+ 总分 = Σ(因子得分 × 因子权重)
+
+
+ = ({currentResult.factors.map((f, i) =>
+ `${f.score} × ${f.weight}%${i < currentResult.factors.length - 1 ? ' + ' : ''}`
+ ).join('')})
+
+
+ = {currentResult.totalScore}分
+
+
+
+
+
+
高度适宜 (≥80分)
+
+ - • 土壤条件优秀
+ - • 适合多种作物
+ - • 预期产量高
+
+
+
+
一般适宜 (60-79分)
+
+ - • 部分因子需改善
+ - • 可种植耐性作物
+ - • 需适当改良
+
+
+
+
不适宜 (<60分)
+
+ - • 土壤条件较差
+ - • 需大幅改良
+ - • 风险较高
+
+
+
+
+
+
+ {/* 多因子综合评价功能说明 */}
+
+
+
+
+
多因子综合评价功能说明:
+
+ - • 关键指标整合: 整合pH值、有机质含量、土层厚度、全氮、全磷、全钾、排水性等7项关键土壤指标
+ - • 层次分析法(AHP): 采用层次分析法构建加权评分体系,公式:总分 = Σ(因子得分 × 因子权重)
+ - • 单因子评分: 根据实际值与最佳范围的接近程度计算各因子得分(0-100分),范围内得分85-100分
+ - • 自定义权重: 支持手动调整各指标权重,提供粮食作物和经济作物两种预设方案
+ - • 综合得分: 输出0-100分的适宜性评分,自动计算加权总分
+ - • 三级分级: 高度适宜(≥80分)、一般适宜(60-79分)、不适宜(<60分)
+ - • 可视化展示: 提供进度条、颜色标识等多维度可视化,直观展示各因子评分与最佳范围对比
+ - • 改善建议: 自动识别待改善因子,为土壤改良提供决策依据
+
+
+
+
+
+
+ {/* 权重配置对话框 */}
+
+
+ {/* 作物知识库对话框 */}
+
+
+ );
+}
\ No newline at end of file
diff --git a/crop-x/src/app/(app)/land-information/suitability/multiFactor/components/cropKnowledgeBase.ts b/crop-x/src/app/(app)/land-information/suitability/multiFactor/components/cropKnowledgeBase.ts
new file mode 100644
index 0000000..28efa0b
--- /dev/null
+++ b/crop-x/src/app/(app)/land-information/suitability/multiFactor/components/cropKnowledgeBase.ts
@@ -0,0 +1,565 @@
+/**
+ * 作物知识库服务
+ * 提供作物-环境适配数据和分析功能
+ */
+
+import { Crop, CropRecommendation, FieldFactors, MatchDetail, RiskFactor } from './multiFactorTypes';
+
+// 作物知识库数据
+export const cropKnowledgeBase: Crop[] = [
+ // 粮食作物
+ {
+ id: 'rice',
+ cropName: '水稻',
+ category: '粮食作物',
+ description: '水稻是世界上最重要的粮食作物之一,需要充足的水分和温暖的气候条件。',
+ growthCycle: {
+ days: 120,
+ seasons: ['春季', '夏季']
+ },
+ soilRequirements: {
+ ph: { optimal: [5.5, 6.5], acceptable: [5.0, 7.0] },
+ organicMatter: { optimal: [25, 40], acceptable: [20, 50] },
+ soilDepth: { optimal: [50, 100], acceptable: [30, 120] },
+ nitrogen: { optimal: [1.5, 2.5], acceptable: [1.0, 3.0] },
+ phosphorus: { optimal: [1.0, 2.0], acceptable: [0.5, 2.5] },
+ potassium: { optimal: [15, 25], acceptable: [10, 30] },
+ drainage: { optimal: [60, 80], acceptable: [50, 90] }
+ },
+ climateRequirements: {
+ temperature: { optimal: [25, 32], acceptable: [20, 35] },
+ rainfall: { optimal: [1000, 1500], acceptable: [800, 2000] },
+ sunlight: { optimal: [6, 8], acceptable: [5, 10] }
+ },
+ expectedYield: {
+ high: [600, 800],
+ medium: [400, 600],
+ low: [200, 400]
+ },
+ riskFactors: [
+ {
+ id: 'rice-cold',
+ name: '低温冷害',
+ condition: '温度低于20°C',
+ suggestion: '选择耐寒品种,采用保温育苗技术',
+ severity: 'high',
+ category: 'climate'
+ },
+ {
+ id: 'rice-drought',
+ name: '干旱胁迫',
+ condition: '降雨量低于800mm/年',
+ suggestion: '建设灌溉系统,选用耐旱品种',
+ severity: 'high',
+ category: 'climate'
+ }
+ ]
+ },
+ {
+ id: 'wheat',
+ cropName: '小麦',
+ category: '粮食作物',
+ description: '小麦是温带地区重要的粮食作物,适应性较强,耐寒性较好。',
+ growthCycle: {
+ days: 110,
+ seasons: ['秋季', '春季']
+ },
+ soilRequirements: {
+ ph: { optimal: [6.0, 7.5], acceptable: [5.5, 8.0] },
+ organicMatter: { optimal: [20, 35], acceptable: [15, 40] },
+ soilDepth: { optimal: [60, 120], acceptable: [40, 150] },
+ nitrogen: { optimal: [1.2, 2.0], acceptable: [0.8, 2.5] },
+ phosphorus: { optimal: [0.8, 1.5], acceptable: [0.5, 2.0] },
+ potassium: { optimal: [12, 20], acceptable: [8, 25] },
+ drainage: { optimal: [70, 90], acceptable: [60, 100] }
+ },
+ climateRequirements: {
+ temperature: { optimal: [15, 22], acceptable: [10, 25] },
+ rainfall: { optimal: [400, 600], acceptable: [300, 800] },
+ sunlight: { optimal: [6, 8], acceptable: [5, 10] }
+ },
+ expectedYield: {
+ high: [500, 700],
+ medium: [300, 500],
+ low: [150, 300]
+ },
+ riskFactors: [
+ {
+ id: 'wheat-heat',
+ name: '高温热害',
+ condition: '温度超过25°C',
+ suggestion: '选择耐热品种,调整播种期',
+ severity: 'medium',
+ category: 'climate'
+ }
+ ]
+ },
+ {
+ id: 'corn',
+ cropName: '玉米',
+ category: '粮食作物',
+ description: '玉米是重要的粮食和饲料作物,喜温喜光,需肥量大。',
+ growthCycle: {
+ days: 130,
+ seasons: ['春季', '夏季']
+ },
+ soilRequirements: {
+ ph: { optimal: [6.0, 7.0], acceptable: [5.5, 7.5] },
+ organicMatter: { optimal: [25, 40], acceptable: [20, 50] },
+ soilDepth: { optimal: [80, 150], acceptable: [60, 200] },
+ nitrogen: { optimal: [1.8, 2.8], acceptable: [1.2, 3.5] },
+ phosphorus: { optimal: [1.2, 2.2], acceptable: [0.8, 3.0] },
+ potassium: { optimal: [18, 30], acceptable: [12, 40] },
+ drainage: { optimal: [80, 95], acceptable: [70, 100] }
+ },
+ climateRequirements: {
+ temperature: { optimal: [22, 30], acceptable: [18, 32] },
+ rainfall: { optimal: [500, 800], acceptable: [400, 1000] },
+ sunlight: { optimal: [7, 9], acceptable: [6, 10] }
+ },
+ expectedYield: {
+ high: [700, 900],
+ medium: [450, 700],
+ low: [250, 450]
+ },
+ riskFactors: [
+ {
+ id: 'corn-drought',
+ name: '干旱胁迫',
+ condition: '降雨量低于400mm/年',
+ suggestion: '建设灌溉设施,选用耐旱品种',
+ severity: 'high',
+ category: 'climate'
+ },
+ {
+ id: 'corn-pollination',
+ name: '授粉不良',
+ condition: '开花期温度超过35°C',
+ suggestion: '调整播种期,选择耐热品种',
+ severity: 'medium',
+ category: 'climate'
+ }
+ ]
+ },
+
+ // 经济作物
+ {
+ id: 'cotton',
+ cropName: '棉花',
+ category: '经济作物',
+ description: '棉花是重要的经济作物,喜温喜光,对土壤要求较高。',
+ growthCycle: {
+ days: 150,
+ seasons: ['春季', '夏季', '秋季']
+ },
+ soilRequirements: {
+ ph: { optimal: [6.5, 8.0], acceptable: [6.0, 8.5] },
+ organicMatter: { optimal: [20, 35], acceptable: [15, 40] },
+ soilDepth: { optimal: [100, 180], acceptable: [80, 200] },
+ nitrogen: { optimal: [1.5, 2.5], acceptable: [1.0, 3.0] },
+ phosphorus: { optimal: [1.0, 2.0], acceptable: [0.6, 2.5] },
+ potassium: { optimal: [20, 35], acceptable: [15, 45] },
+ drainage: { optimal: [85, 95], acceptable: [75, 100] }
+ },
+ climateRequirements: {
+ temperature: { optimal: [25, 32], acceptable: [20, 35] },
+ rainfall: { optimal: [400, 600], acceptable: [300, 800] },
+ sunlight: { optimal: [8, 10], acceptable: [7, 12] }
+ },
+ expectedYield: {
+ high: [120, 180],
+ medium: [80, 120],
+ low: [40, 80]
+ },
+ riskFactors: [
+ {
+ id: 'cotton-boll',
+ name: '蕾铃脱落',
+ condition: '湿度过大或温差过大',
+ suggestion: '合理密植,加强田间管理',
+ severity: 'medium',
+ category: 'climate'
+ }
+ ]
+ },
+ {
+ id: 'soybean',
+ cropName: '大豆',
+ category: '经济作物',
+ description: '大豆是重要的油料作物,固氮能力强,适应性较广。',
+ growthCycle: {
+ days: 100,
+ seasons: ['春季', '夏季']
+ },
+ soilRequirements: {
+ ph: { optimal: [6.0, 7.0], acceptable: [5.5, 7.5] },
+ organicMatter: { optimal: [25, 40], acceptable: [20, 50] },
+ soilDepth: { optimal: [60, 120], acceptable: [40, 150] },
+ nitrogen: { optimal: [1.2, 2.0], acceptable: [0.8, 2.5] },
+ phosphorus: { optimal: [1.0, 2.0], acceptable: [0.6, 2.5] },
+ potassium: { optimal: [15, 25], acceptable: [10, 30] },
+ drainage: { optimal: [75, 90], acceptable: [65, 100] }
+ },
+ climateRequirements: {
+ temperature: { optimal: [20, 28], acceptable: [15, 32] },
+ rainfall: { optimal: [500, 700], acceptable: [400, 900] },
+ sunlight: { optimal: [6, 8], acceptable: [5, 10] }
+ },
+ expectedYield: {
+ high: [250, 350],
+ medium: [150, 250],
+ low: [80, 150]
+ },
+ riskFactors: [
+ {
+ id: 'soybean-flower',
+ name: '落花落荚',
+ condition: '高温干旱或连续阴雨',
+ suggestion: '合理施肥,科学管水',
+ severity: 'medium',
+ category: 'climate'
+ }
+ ]
+ },
+
+ // 蔬菜作物
+ {
+ id: 'tomato',
+ cropName: '番茄',
+ category: '蔬菜作物',
+ description: '番茄是重要的蔬菜作物,喜温喜光,对肥水要求较高。',
+ growthCycle: {
+ days: 90,
+ seasons: ['春季', '夏季', '秋季']
+ },
+ soilRequirements: {
+ ph: { optimal: [6.0, 7.0], acceptable: [5.5, 7.5] },
+ organicMatter: { optimal: [30, 45], acceptable: [25, 50] },
+ soilDepth: { optimal: [40, 80], acceptable: [30, 100] },
+ nitrogen: { optimal: [2.0, 3.0], acceptable: [1.5, 3.5] },
+ phosphorus: { optimal: [1.5, 2.5], acceptable: [1.0, 3.0] },
+ potassium: { optimal: [25, 40], acceptable: [20, 50] },
+ drainage: { optimal: [80, 95], acceptable: [70, 100] }
+ },
+ climateRequirements: {
+ temperature: { optimal: [22, 28], acceptable: [18, 32] },
+ rainfall: { optimal: [400, 600], acceptable: [300, 800] },
+ sunlight: { optimal: [7, 9], acceptable: [6, 10] }
+ },
+ expectedYield: {
+ high: [8000, 12000],
+ medium: [5000, 8000],
+ low: [2000, 5000]
+ },
+ riskFactors: [
+ {
+ id: 'tomato-disease',
+ name: '病害高发',
+ condition: '湿度过大,通风不良',
+ suggestion: '加强病虫害防治,合理密植',
+ severity: 'high',
+ category: 'disease'
+ }
+ ]
+ },
+ {
+ id: 'cucumber',
+ cropName: '黄瓜',
+ category: '蔬菜作物',
+ description: '黄瓜是重要的蔬菜作物,喜温喜湿,生长周期短。',
+ growthCycle: {
+ days: 60,
+ seasons: ['春季', '夏季', '秋季']
+ },
+ soilRequirements: {
+ ph: { optimal: [6.0, 7.0], acceptable: [5.5, 7.5] },
+ organicMatter: { optimal: [30, 45], acceptable: [25, 50] },
+ soilDepth: { optimal: [30, 60], acceptable: [20, 80] },
+ nitrogen: { optimal: [2.5, 3.5], acceptable: [2.0, 4.0] },
+ phosphorus: { optimal: [1.8, 2.8], acceptable: [1.2, 3.5] },
+ potassium: { optimal: [30, 45], acceptable: [25, 55] },
+ drainage: { optimal: [85, 95], acceptable: [75, 100] }
+ },
+ climateRequirements: {
+ temperature: { optimal: [22, 30], acceptable: [18, 32] },
+ rainfall: { optimal: [500, 700], acceptable: [400, 900] },
+ sunlight: { optimal: [6, 8], acceptable: [5, 10] }
+ },
+ expectedYield: {
+ high: [6000, 9000],
+ medium: [4000, 6000],
+ low: [2000, 4000]
+ },
+ riskFactors: [
+ {
+ id: 'cucumber-pest',
+ name: '虫害严重',
+ condition: '温度适宜,湿度大',
+ suggestion: '加强虫害监测,及时防治',
+ severity: 'medium',
+ category: 'pest'
+ }
+ ]
+ }
+];
+
+/**
+ * 匹配作物推荐
+ */
+export function matchCropsForField(fieldFactors: FieldFactors): CropRecommendation[] {
+ const recommendations: CropRecommendation[] = [];
+
+ for (const crop of cropKnowledgeBase) {
+ const matchDetails: MatchDetail[] = [];
+ let totalScore = 0;
+ let factorCount = 0;
+
+ // 土壤pH匹配
+ const phScore = calculateFactorScore(
+ fieldFactors.ph,
+ crop.soilRequirements.ph.optimal,
+ crop.soilRequirements.ph.acceptable
+ );
+ matchDetails.push({
+ factor: 'pH',
+ value: fieldFactors.ph,
+ status: getScoreStatus(phScore),
+ score: phScore
+ });
+ totalScore += phScore;
+ factorCount++;
+
+ // 有机质匹配
+ const organicScore = calculateFactorScore(
+ fieldFactors.organic,
+ crop.soilRequirements.organicMatter.optimal,
+ crop.soilRequirements.organicMatter.acceptable
+ );
+ matchDetails.push({
+ factor: '有机质',
+ value: fieldFactors.organic,
+ status: getScoreStatus(organicScore),
+ score: organicScore
+ });
+ totalScore += organicScore;
+ factorCount++;
+
+ // 土层厚度匹配
+ const depthScore = calculateFactorScore(
+ fieldFactors.depth,
+ crop.soilRequirements.soilDepth.optimal,
+ crop.soilRequirements.soilDepth.acceptable
+ );
+ matchDetails.push({
+ factor: '土层厚度',
+ value: fieldFactors.depth,
+ status: getScoreStatus(depthScore),
+ score: depthScore
+ });
+ totalScore += depthScore;
+ factorCount++;
+
+ // 全氮匹配
+ const nitrogenScore = calculateFactorScore(
+ fieldFactors.nitrogen,
+ crop.soilRequirements.nitrogen.optimal,
+ crop.soilRequirements.nitrogen.acceptable
+ );
+ matchDetails.push({
+ factor: '全氮',
+ value: fieldFactors.nitrogen,
+ status: getScoreStatus(nitrogenScore),
+ score: nitrogenScore
+ });
+ totalScore += nitrogenScore;
+ factorCount++;
+
+ // 全磷匹配
+ const phosphorusScore = calculateFactorScore(
+ fieldFactors.phosphorus,
+ crop.soilRequirements.phosphorus.optimal,
+ crop.soilRequirements.phosphorus.acceptable
+ );
+ matchDetails.push({
+ factor: '全磷',
+ value: fieldFactors.phosphorus,
+ status: getScoreStatus(phosphorusScore),
+ score: phosphorusScore
+ });
+ totalScore += phosphorusScore;
+ factorCount++;
+
+ // 全钾匹配
+ const potassiumScore = calculateFactorScore(
+ fieldFactors.potassium,
+ crop.soilRequirements.potassium.optimal,
+ crop.soilRequirements.potassium.acceptable
+ );
+ matchDetails.push({
+ factor: '全钾',
+ value: fieldFactors.potassium,
+ status: getScoreStatus(potassiumScore),
+ score: potassiumScore
+ });
+ totalScore += potassiumScore;
+ factorCount++;
+
+ // 排水性匹配
+ const drainageScore = calculateFactorScore(
+ fieldFactors.drainage,
+ crop.soilRequirements.drainage.optimal,
+ crop.soilRequirements.drainage.acceptable
+ );
+ matchDetails.push({
+ factor: '排水性',
+ value: fieldFactors.drainage,
+ status: getScoreStatus(drainageScore),
+ score: drainageScore
+ });
+ totalScore += drainageScore;
+ factorCount++;
+
+ // 气温匹配(如果有数据)
+ if (fieldFactors.temperature !== undefined) {
+ const tempScore = calculateFactorScore(
+ fieldFactors.temperature,
+ crop.climateRequirements.temperature.optimal,
+ crop.climateRequirements.temperature.acceptable
+ );
+ matchDetails.push({
+ factor: '温度',
+ value: fieldFactors.temperature,
+ status: getScoreStatus(tempScore),
+ score: tempScore
+ });
+ totalScore += tempScore;
+ factorCount++;
+ }
+
+ // 降雨匹配(如果有数据)
+ if (fieldFactors.rainfall !== undefined) {
+ const rainScore = calculateFactorScore(
+ fieldFactors.rainfall,
+ crop.climateRequirements.rainfall.optimal,
+ crop.climateRequirements.rainfall.acceptable
+ );
+ matchDetails.push({
+ factor: '降雨',
+ value: fieldFactors.rainfall,
+ status: getScoreStatus(rainScore),
+ score: rainScore
+ });
+ totalScore += rainScore;
+ factorCount++;
+ }
+
+ // 计算综合匹配分数
+ const averageScore = Math.round(totalScore / factorCount);
+
+ // 确定适宜性等级
+ let suitabilityLevel: '高度推荐' | '推荐' | '谨慎种植' | '不推荐';
+ if (averageScore >= 85) {
+ suitabilityLevel = '高度推荐';
+ } else if (averageScore >= 70) {
+ suitabilityLevel = '推荐';
+ } else if (averageScore >= 50) {
+ suitabilityLevel = '谨慎种植';
+ } else {
+ suitabilityLevel = '不推荐';
+ }
+
+ // 获取适用风险因子
+ const applicableRisks = crop.riskFactors.filter(risk => {
+ if (risk.category === 'climate' && fieldFactors.temperature !== undefined) {
+ return checkRiskCondition(risk.condition, fieldFactors);
+ }
+ // 其他风险因子的判断逻辑
+ return false;
+ });
+
+ // 确定预期产量
+ let expectedYield: [number, number];
+ if (suitabilityLevel === '高度推荐') {
+ expectedYield = crop.expectedYield.high;
+ } else if (suitabilityLevel === '推荐') {
+ expectedYield = crop.expectedYield.medium;
+ } else {
+ expectedYield = crop.expectedYield.low;
+ }
+
+ recommendations.push({
+ crop,
+ matchScore: averageScore,
+ suitabilityLevel,
+ matchDetails,
+ applicableRisks,
+ expectedYield
+ });
+ }
+
+ // 按匹配分数排序
+ return recommendations.sort((a, b) => b.matchScore - a.matchScore);
+}
+
+/**
+ * 计算因子得分
+ */
+function calculateFactorScore(
+ value: number,
+ optimalRange: [number, number],
+ acceptableRange: [number, number]
+): number {
+ // 最佳范围:100分
+ if (value >= optimalRange[0] && value <= optimalRange[1]) {
+ return 100;
+ }
+
+ // 可接受范围:60分
+ if (value >= acceptableRange[0] && value <= acceptableRange[1]) {
+ return 60;
+ }
+
+ // 偏离程度计算
+ const optimalMid = (optimalRange[0] + optimalRange[1]) / 2;
+ const deviation = Math.abs(value - optimalMid);
+ const maxDeviation = Math.max(
+ Math.abs(acceptableRange[0] - optimalMid),
+ Math.abs(acceptableRange[1] - optimalMid)
+ );
+
+ // 根据偏离程度计算分数(0-60分)
+ const scoreRatio = Math.max(0, 1 - deviation / maxDeviation);
+ return Math.round(scoreRatio * 60);
+}
+
+/**
+ * 获取得分状态
+ */
+function getScoreStatus(score: number): 'optimal' | 'acceptable' | 'poor' {
+ if (score >= 90) return 'optimal';
+ if (score >= 60) return 'acceptable';
+ return 'poor';
+}
+
+/**
+ * 检查风险条件
+ */
+function checkRiskCondition(condition: string, fieldFactors: FieldFactors): boolean {
+ // 简化的条件判断逻辑
+ if (condition.includes('温度低于') && fieldFactors.temperature !== undefined) {
+ const threshold = parseInt(condition.match(/\d+/)?.[0] || '0');
+ return fieldFactors.temperature < threshold;
+ }
+
+ if (condition.includes('温度超过') && fieldFactors.temperature !== undefined) {
+ const threshold = parseInt(condition.match(/\d+/)?.[0] || '0');
+ return fieldFactors.temperature > threshold;
+ }
+
+ if (condition.includes('降雨量低于') && fieldFactors.rainfall !== undefined) {
+ const threshold = parseInt(condition.match(/\d+/)?.[0] || '0');
+ return fieldFactors.rainfall < threshold;
+ }
+
+ return false;
+}
\ No newline at end of file
diff --git a/crop-x/src/app/(app)/land-information/suitability/multiFactor/components/multiFactorService.ts b/crop-x/src/app/(app)/land-information/suitability/multiFactor/components/multiFactorService.ts
new file mode 100644
index 0000000..e21d378
--- /dev/null
+++ b/crop-x/src/app/(app)/land-information/suitability/multiFactor/components/multiFactorService.ts
@@ -0,0 +1,328 @@
+/**
+ * 多因子评价服务类
+ * 提供地块适宜性评价、批量分析等功能
+ */
+
+import {
+ EvaluationFactor,
+ SuitabilityResult,
+ FactorWeight,
+ BatchProgress,
+ DEFAULT_FACTORS,
+ WEIGHT_PRESETS,
+ MOCK_FIELDS
+} from './multiFactorTypes';
+
+export class MultiFactorService {
+ /**
+ * 计算单个因子的得分
+ */
+ static calculateFactorScore(value: number, optimalRange: [number, number]): number {
+ const [min, max] = optimalRange;
+ const mid = (min + max) / 2;
+ const range = max - min;
+
+ // 如果在最佳范围内,得分较高
+ if (value >= min && value <= max) {
+ // 越接近中值得分越高 (85-100分)
+ const deviation = Math.abs(value - mid);
+ const deviationRatio = deviation / (range / 2);
+ return Math.max(85, 100 - deviationRatio * 15);
+ }
+
+ // 如果在范围外,根据偏离程度降低分数
+ const deviation = value < min ? min - value : value - max;
+ const deviationRatio = deviation / range;
+
+ // 偏离越大,分数越低 (最低20分)
+ return Math.max(20, 85 - deviationRatio * 65);
+ }
+
+ /**
+ * 计算综合评分(层次分析法AHP加权求和)
+ */
+ static calculateTotalScore(factors: EvaluationFactor[]): number {
+ let totalScore = 0;
+ for (const factor of factors) {
+ totalScore += (factor.score * factor.weight) / 100;
+ }
+ return Math.round(totalScore);
+ }
+
+ /**
+ * 根据总分确定适宜性等级
+ */
+ static getGrade(totalScore: number): '高度适宜' | '一般适宜' | '不适宜' {
+ if (totalScore >= 80) return '高度适宜';
+ if (totalScore >= 60) return '一般适宜';
+ return '不适宜';
+ }
+
+ /**
+ * 从空间分析服务读取地块因子数据
+ */
+ static fetchFieldFactorsFromSpatialService(fieldId: string, weights: FactorWeight[]): EvaluationFactor[] {
+ // 模拟读取地块的土壤因子数据
+ return [
+ {
+ id: 'ph',
+ name: 'pH值',
+ value: 6.0 + Math.random() * 2, // 6.0-8.0
+ weight: weights.find(f => f.id === 'ph')?.weight || 20,
+ unit: '',
+ optimalRange: [6.5, 7.5],
+ score: 0
+ },
+ {
+ id: 'organic',
+ name: '有机质含量',
+ value: 15 + Math.random() * 20, // 15-35 g/kg
+ weight: weights.find(f => f.id === 'organic')?.weight || 25,
+ unit: 'g/kg',
+ optimalRange: [20, 30],
+ score: 0
+ },
+ {
+ id: 'depth',
+ name: '土层厚度',
+ value: 30 + Math.random() * 70, // 30-100 cm
+ weight: weights.find(f => f.id === 'depth')?.weight || 20,
+ unit: 'cm',
+ optimalRange: [50, 80],
+ score: 0
+ },
+ {
+ id: 'nitrogen',
+ name: '全氮',
+ value: 0.8 + Math.random() * 1.5, // 0.8-2.3 g/kg
+ weight: weights.find(f => f.id === 'nitrogen')?.weight || 10,
+ unit: 'g/kg',
+ optimalRange: [1.0, 2.0],
+ score: 0
+ },
+ {
+ id: 'phosphorus',
+ name: '全磷',
+ value: 0.4 + Math.random() * 1.2, // 0.4-1.6 g/kg
+ weight: weights.find(f => f.id === 'phosphorus')?.weight || 10,
+ unit: 'g/kg',
+ optimalRange: [0.6, 1.2],
+ score: 0
+ },
+ {
+ id: 'potassium',
+ name: '全钾',
+ value: 12 + Math.random() * 13, // 12-25 g/kg
+ weight: weights.find(f => f.id === 'potassium')?.weight || 10,
+ unit: 'g/kg',
+ optimalRange: [15, 20],
+ score: 0
+ },
+ {
+ id: 'drainage',
+ name: '排水性',
+ value: 60 + Math.random() * 40, // 60-100分
+ weight: weights.find(f => f.id === 'drainage')?.weight || 5,
+ unit: '',
+ optimalRange: [70, 90],
+ score: 0
+ },
+ ];
+ }
+
+ /**
+ * 生成地块适宜性评价结果
+ */
+ static generateEvaluationResult(fieldId: string, weights: FactorWeight[]): SuitabilityResult {
+ const field = MOCK_FIELDS.find(f => f.id === fieldId);
+ if (!field) {
+ throw new Error(`Field ${fieldId} not found`);
+ }
+
+ // 从空间分析服务读取因子数据
+ const factors = MultiFactorService.fetchFieldFactorsFromSpatialService(fieldId, weights);
+
+ // 计算每个因子的得分
+ const scoredFactors = factors.map(factor => ({
+ ...factor,
+ score: MultiFactorService.calculateFactorScore(factor.value, factor.optimalRange)
+ }));
+
+ // 计算综合得分(加权汇总)
+ const totalScore = MultiFactorService.calculateTotalScore(scoredFactors);
+
+ // 确定适宜性等级
+ const grade = MultiFactorService.getGrade(totalScore);
+
+ return {
+ fieldId,
+ fieldName: field.name,
+ totalScore,
+ grade,
+ factors: scoredFactors,
+ timestamp: new Date().toISOString(),
+ };
+ }
+
+ /**
+ * 批量分析所有地块
+ */
+ static async runBatchAnalysis(
+ weights: FactorWeight[],
+ onProgress?: (progress: BatchProgress) => void
+ ): Promise {
+ const totalFields = MOCK_FIELDS.length;
+ const results: SuitabilityResult[] = [];
+ let highSuitability = 0;
+ let mediumSuitability = 0;
+ let lowSuitability = 0;
+
+ for (let i = 0; i < totalFields; i++) {
+ const field = MOCK_FIELDS[i];
+
+ // 模拟处理延迟
+ await new Promise(resolve => setTimeout(resolve, 100));
+
+ // 生成评价结果
+ const result = MultiFactorService.generateEvaluationResult(field.id, weights);
+ results.push(result);
+
+ // 更新统计数据
+ if (result.grade === '高度适宜') highSuitability++;
+ else if (result.grade === '一般适宜') mediumSuitability++;
+ else lowSuitability++;
+
+ // 更新进度
+ if (onProgress) {
+ onProgress({
+ total: totalFields,
+ processed: i + 1,
+ highSuitability,
+ mediumSuitability,
+ lowSuitability,
+ currentField: field.name
+ });
+ }
+ }
+
+ return results;
+ }
+
+ /**
+ * 验证权重总和
+ */
+ static validateWeights(weights: FactorWeight[]): { isValid: boolean; totalWeight: number } {
+ const totalWeight = weights.reduce((sum, f) => sum + f.weight, 0);
+ return {
+ isValid: totalWeight === 100,
+ totalWeight
+ };
+ }
+
+ /**
+ * 获取预设权重方案
+ */
+ static getWeightPreset(preset: 'grain' | 'economic' | 'default'): FactorWeight[] {
+ switch (preset) {
+ case 'grain':
+ return [...WEIGHT_PRESETS.grain];
+ case 'economic':
+ return [...WEIGHT_PRESETS.economic];
+ default:
+ return [...DEFAULT_FACTORS];
+ }
+ }
+
+ /**
+ * 更新权重配置
+ */
+ static updateWeight(weights: FactorWeight[], factorId: string, newWeight: number): FactorWeight[] {
+ return weights.map(f => f.id === factorId ? { ...f, weight: newWeight } : f);
+ }
+
+ /**
+ * 重置权重到默认值
+ */
+ static resetWeights(): FactorWeight[] {
+ return [...DEFAULT_FACTORS];
+ }
+
+ /**
+ * 生成模拟评价数据(用于演示)
+ */
+ static generateMockEvaluationData(): SuitabilityResult[] {
+ return [
+ {
+ fieldId: 'field-1',
+ fieldName: '东区1号地',
+ totalScore: 87,
+ grade: '高度适宜',
+ factors: [
+ { id: 'ph', name: 'pH值', value: 6.5, weight: 20, unit: '', optimalRange: [6.0, 7.5], score: 95 },
+ { id: 'organic', name: '有机质含量', value: 32, weight: 25, unit: 'g/kg', optimalRange: [25, 40], score: 90 },
+ { id: 'depth', name: '土层厚度', value: 85, weight: 20, unit: 'cm', optimalRange: [60, 100], score: 88 },
+ { id: 'nitrogen', name: '全氮', value: 1.8, weight: 10, unit: 'g/kg', optimalRange: [1.5, 2.5], score: 85 },
+ { id: 'phosphorus', name: '全磷', value: 1.2, weight: 10, unit: 'g/kg', optimalRange: [1.0, 2.0], score: 80 },
+ { id: 'potassium', name: '全钾', value: 18, weight: 10, unit: 'g/kg', optimalRange: [15, 25], score: 82 },
+ { id: 'drainage', name: '排水性', value: 4, weight: 5, unit: '', optimalRange: [3, 5], score: 90 },
+ ],
+ timestamp: '2024-10-15 14:30',
+ },
+ {
+ fieldId: 'field-2',
+ fieldName: '西区2号地',
+ totalScore: 72,
+ grade: '一般适宜',
+ factors: [
+ { id: 'ph', name: 'pH值', value: 7.8, weight: 20, unit: '', optimalRange: [6.0, 7.5], score: 65 },
+ { id: 'organic', name: '有机质含量', value: 22, weight: 25, unit: 'g/kg', optimalRange: [25, 40], score: 70 },
+ { id: 'depth', name: '土层厚度', value: 55, weight: 20, unit: 'cm', optimalRange: [60, 100], score: 68 },
+ { id: 'nitrogen', name: '全氮', value: 1.3, weight: 10, unit: 'g/kg', optimalRange: [1.5, 2.5], score: 72 },
+ { id: 'phosphorus', name: '全磷', value: 0.9, weight: 10, unit: 'g/kg', optimalRange: [1.0, 2.0], score: 75 },
+ { id: 'potassium', name: '全钾', value: 14, weight: 10, unit: 'g/kg', optimalRange: [15, 25], score: 78 },
+ { id: 'drainage', name: '排水性', value: 3, weight: 5, unit: '', optimalRange: [3, 5], score: 85 },
+ ],
+ timestamp: '2024-10-15 14:28',
+ },
+ {
+ fieldId: 'field-3',
+ fieldName: '南区3号地',
+ totalScore: 58,
+ grade: '不适宜',
+ factors: [
+ { id: 'ph', name: 'pH值', value: 8.5, weight: 20, unit: '', optimalRange: [6.0, 7.5], score: 45 },
+ { id: 'organic', name: '有机质含量', value: 15, weight: 25, unit: 'g/kg', optimalRange: [25, 40], score: 52 },
+ { id: 'depth', name: '土层厚度', value: 42, weight: 20, unit: 'cm', optimalRange: [60, 100], score: 55 },
+ { id: 'nitrogen', name: '全氮', value: 0.8, weight: 10, unit: 'g/kg', optimalRange: [1.5, 2.5], score: 60 },
+ { id: 'phosphorus', name: '全磷', value: 0.6, weight: 10, unit: 'g/kg', optimalRange: [1.0, 2.0], score: 65 },
+ { id: 'potassium', name: '全钾', value: 10, weight: 10, unit: 'g/kg', optimalRange: [15, 25], score: 58 },
+ { id: 'drainage', name: '排水性', value: 2, weight: 5, unit: '', optimalRange: [3, 5], score: 70 },
+ ],
+ timestamp: '2024-10-15 14:25',
+ },
+ ];
+ }
+
+ /**
+ * 生成更多模拟地块数据(用于批量分析演示)
+ */
+ static generateExtendedMockFields(): Array<{ id: string; name: string; code: string; area: number }> {
+ const extendedFields = [...MOCK_FIELDS];
+
+ // 生成更多模拟地块
+ for (let i = 4; i <= 68; i++) {
+ const zone = ['东区', '西区', '南区', '北区', '中心区'][Math.floor(Math.random() * 5)];
+ const number = Math.floor(Math.random() * 20) + 1;
+ const area = Math.round((Math.random() * 100 + 50) * 10) / 10;
+
+ extendedFields.push({
+ id: `field-${i}`,
+ name: `${zone}${number}号地`,
+ code: `DB${String(i).padStart(3, '0')}`,
+ area
+ });
+ }
+
+ return extendedFields;
+ }
+}
\ No newline at end of file
diff --git a/crop-x/src/app/(app)/land-information/suitability/multiFactor/components/multiFactorTypes.ts b/crop-x/src/app/(app)/land-information/suitability/multiFactor/components/multiFactorTypes.ts
new file mode 100644
index 0000000..6386593
--- /dev/null
+++ b/crop-x/src/app/(app)/land-information/suitability/multiFactor/components/multiFactorTypes.ts
@@ -0,0 +1,192 @@
+/**
+ * 多因子评价类型定义
+ */
+
+export interface EvaluationFactor {
+ id: string;
+ name: string;
+ value: number;
+ weight: number;
+ unit: string;
+ optimalRange: [number, number];
+ score: number;
+}
+
+export interface SuitabilityResult {
+ fieldId: string;
+ fieldName: string;
+ totalScore: number;
+ grade: '高度适宜' | '一般适宜' | '不适宜';
+ factors: EvaluationFactor[];
+ timestamp: string;
+}
+
+export interface FactorWeight {
+ id: string;
+ name: string;
+ weight: number;
+ unit: string;
+}
+
+export interface BatchProgress {
+ total: number;
+ processed: number;
+ highSuitability: number;
+ mediumSuitability: number;
+ lowSuitability: number;
+ currentField: string;
+}
+
+// 作物相关类型
+export interface Crop {
+ id: string;
+ cropName: string;
+ category: '粮食作物' | '经济作物' | '蔬菜作物' | '果树作物';
+ description: string;
+ growthCycle: {
+ days: number;
+ seasons: string[];
+ };
+ soilRequirements: {
+ ph: { optimal: [number, number]; acceptable: [number, number] };
+ organicMatter: { optimal: [number, number]; acceptable: [number, number] };
+ soilDepth: { optimal: [number, number]; acceptable: [number, number] };
+ nitrogen: { optimal: [number, number]; acceptable: [number, number] };
+ phosphorus: { optimal: [number, number]; acceptable: [number, number] };
+ potassium: { optimal: [number, number]; acceptable: [number, number] };
+ drainage: { optimal: [number, number]; acceptable: [number, number] };
+ };
+ climateRequirements: {
+ temperature: { optimal: [number, number]; acceptable: [number, number] };
+ rainfall: { optimal: [number, number]; acceptable: [number, number] };
+ sunlight: { optimal: [number, number]; acceptable: [number, number] };
+ };
+ expectedYield: {
+ high: [number, number];
+ medium: [number, number];
+ low: [number, number];
+ };
+ riskFactors: RiskFactor[];
+}
+
+export interface RiskFactor {
+ id: string;
+ name: string;
+ condition: string;
+ suggestion: string;
+ severity: 'high' | 'medium' | 'low';
+ category: 'soil' | 'climate' | 'nutrient' | 'disease' | 'pest';
+}
+
+export interface CropRecommendation {
+ crop: Crop;
+ matchScore: number;
+ suitabilityLevel: '高度推荐' | '推荐' | '谨慎种植' | '不推荐';
+ matchDetails: MatchDetail[];
+ applicableRisks: RiskFactor[];
+ expectedYield: [number, number];
+}
+
+export interface MatchDetail {
+ factor: string;
+ value: number;
+ status: 'optimal' | 'acceptable' | 'poor';
+ score: number;
+}
+
+export interface FieldFactors {
+ ph: number;
+ organic: number;
+ depth: number;
+ nitrogen: number;
+ phosphorus: number;
+ potassium: number;
+ drainage: number;
+ temperature?: number;
+ rainfall?: number;
+ sunlight?: number;
+}
+
+// 评价因子配置
+export const DEFAULT_FACTORS: FactorWeight[] = [
+ { id: 'ph', name: 'pH值', weight: 20, unit: '' },
+ { id: 'organic', name: '有机质含量', weight: 25, unit: 'g/kg' },
+ { id: 'depth', name: '土层厚度', weight: 20, unit: 'cm' },
+ { id: 'nitrogen', name: '全氮', weight: 10, unit: 'g/kg' },
+ { id: 'phosphorus', name: '全磷', weight: 10, unit: 'g/kg' },
+ { id: 'potassium', name: '全钾', weight: 10, unit: 'g/kg' },
+ { id: 'drainage', name: '排水性', weight: 5, unit: '' },
+];
+
+// 预设权重方案
+export const WEIGHT_PRESETS = {
+ grain: [
+ { id: 'ph', name: 'pH值', weight: 20, unit: '' },
+ { id: 'organic', name: '有机质含量', weight: 30, unit: 'g/kg' },
+ { id: 'depth', name: '土层厚度', weight: 25, unit: 'cm' },
+ { id: 'nitrogen', name: '全氮', weight: 8, unit: 'g/kg' },
+ { id: 'phosphorus', name: '全磷', weight: 8, unit: 'g/kg' },
+ { id: 'potassium', name: '全钾', weight: 8, unit: 'g/kg' },
+ { id: 'drainage', name: '排水性', weight: 1, unit: '' },
+ ],
+ economic: [
+ { id: 'ph', name: 'pH值', weight: 25, unit: '' },
+ { id: 'organic', name: '有机质含量', weight: 35, unit: 'g/kg' },
+ { id: 'depth', name: '土层厚度', weight: 15, unit: 'cm' },
+ { id: 'nitrogen', name: '全氮', weight: 7, unit: 'g/kg' },
+ { id: 'phosphorus', name: '全磷', weight: 7, unit: 'g/kg' },
+ { id: 'potassium', name: '全钾', weight: 7, unit: 'g/kg' },
+ { id: 'drainage', name: '排水性', weight: 4, unit: '' },
+ ]
+};
+
+// 地块模拟数据
+export const MOCK_FIELDS = [
+ { id: 'field-1', name: '东区1号地', code: 'DB001', area: 85.5 },
+ { id: 'field-2', name: '西区2号地', code: 'DB002', area: 92.3 },
+ { id: 'field-3', name: '南区3号地', code: 'DB003', area: 78.7 },
+];
+
+// 工具函数
+export const getGradeColor = (grade: string): string => {
+ switch (grade) {
+ case '高度适宜': return '#22c55e';
+ case '一般适宜': return '#eab308';
+ case '不适宜': return '#ef4444';
+ default: return '#6b7280';
+ }
+};
+
+export const getScoreColor = (score: number): string => {
+ if (score >= 80) return '#22c55e';
+ if (score >= 60) return '#eab308';
+ return '#ef4444';
+};
+
+export const getSuitabilityLevelColor = (level: string): {
+ bg: string;
+ border: string;
+ text: string;
+} => {
+ switch (level) {
+ case '高度推荐':
+ return { bg: '#22c55e', border: '#22c55e', text: '#22c55e' };
+ case '推荐':
+ return { bg: '#3b82f6', border: '#3b82f6', text: '#3b82f6' };
+ case '谨慎种植':
+ return { bg: '#eab308', border: '#eab308', text: '#eab308' };
+ default:
+ return { bg: '#6b7280', border: '#6b7280', text: '#6b7280' };
+ }
+};
+
+export const formatDate = (dateString: string): string => {
+ const date = new Date(dateString);
+ return date.toLocaleString('zh-CN', {
+ year: 'numeric',
+ month: '2-digit',
+ day: '2-digit',
+ hour: '2-digit',
+ minute: '2-digit'
+ });
+};
\ No newline at end of file
diff --git a/crop-x/src/app/(app)/land-information/suitability/multiFactor/page.tsx b/crop-x/src/app/(app)/land-information/suitability/multiFactor/page.tsx
new file mode 100644
index 0000000..93cef4b
--- /dev/null
+++ b/crop-x/src/app/(app)/land-information/suitability/multiFactor/page.tsx
@@ -0,0 +1,7 @@
+'use client';
+
+import { MultiFactorEvaluation } from './components/MultiFactorEvaluation';
+
+export default function ComprehensivePage() {
+ return ;
+}
\ No newline at end of file
diff --git a/crop-x/src/app/(app)/land-information/suitability/recommend/components/CropRecommendations.tsx b/crop-x/src/app/(app)/land-information/suitability/recommend/components/CropRecommendations.tsx
new file mode 100644
index 0000000..695eae6
--- /dev/null
+++ b/crop-x/src/app/(app)/land-information/suitability/recommend/components/CropRecommendations.tsx
@@ -0,0 +1,441 @@
+'use client';
+
+import { Card } from '@/components/ui/card';
+import { Badge } from '@/components/ui/badge';
+import { Leaf, AlertTriangle, ThermometerSun, Cloud, Sun } from 'lucide-react';
+import { CropRecommendationState, SuitabilityResult } from './cropRecommendReducer';
+
+// 模拟作物知识库数据
+const cropKnowledgeBase = [
+ {
+ id: 'wheat',
+ cropName: '小麦',
+ category: '粮食作物',
+ description: '适应性强的主粮作物,对土壤要求较宽泛,耐寒性好,适合北方地区种植。',
+ growthCycle: {
+ days: 220,
+ seasons: ['春季', '秋季']
+ },
+ soilRequirements: {
+ ph: { optimal: [6.5, 7.5], acceptable: [6.0, 8.0] },
+ organicMatter: { optimal: [25, 35], acceptable: [20, 40] },
+ soilDepth: { optimal: [60, 100], acceptable: [40, 120] },
+ nitrogen: { optimal: [1.5, 2.5], acceptable: [1.0, 3.0] },
+ phosphorus: { optimal: [1.0, 2.0], acceptable: [0.6, 2.5] },
+ potassium: { optimal: [15, 25], acceptable: [10, 30] },
+ drainage: { optimal: [3, 5], acceptable: [2, 5] }
+ },
+ climateRequirements: {
+ temperature: { optimal: [15, 22], acceptable: [10, 25] },
+ rainfall: { optimal: [400, 600], acceptable: [300, 800] },
+ sunlight: { optimal: [6, 8], acceptable: [5, 10] }
+ },
+ expectedYield: {
+ high: [400, 500],
+ medium: [300, 400],
+ low: [200, 300]
+ },
+ riskFactors: [
+ {
+ id: 'wheat-rust',
+ name: '锈病风险',
+ condition: '湿度过高、温度适宜',
+ severity: 'medium' as const,
+ suggestion: '选择抗病品种,合理密植,及时防治'
+ },
+ {
+ id: 'wheat-drought',
+ name: '干旱风险',
+ condition: '降雨量不足400mm',
+ severity: 'high' as const,
+ suggestion: '加强灌溉设施建设,选择抗旱品种'
+ }
+ ]
+ },
+ {
+ id: 'corn',
+ cropName: '玉米',
+ category: '粮食作物',
+ description: '高产作物,对温度要求较高,需水量大,适合水热条件良好的地区。',
+ growthCycle: {
+ days: 120,
+ seasons: ['春季', '夏季']
+ },
+ soilRequirements: {
+ ph: { optimal: [6.0, 7.0], acceptable: [5.5, 7.5] },
+ organicMatter: { optimal: [30, 40], acceptable: [25, 45] },
+ soilDepth: { optimal: [80, 120], acceptable: [60, 150] },
+ nitrogen: { optimal: [2.0, 3.0], acceptable: [1.5, 3.5] },
+ phosphorus: { optimal: [1.2, 2.5], acceptable: [0.8, 3.0] },
+ potassium: { optimal: [20, 30], acceptable: [15, 35] },
+ drainage: { optimal: [3, 5], acceptable: [2, 5] }
+ },
+ climateRequirements: {
+ temperature: { optimal: [20, 28], acceptable: [15, 32] },
+ rainfall: { optimal: [500, 800], acceptable: [400, 1000] },
+ sunlight: { optimal: [7, 9], acceptable: [6, 10] }
+ },
+ expectedYield: {
+ high: [600, 800],
+ medium: [400, 600],
+ low: [250, 400]
+ },
+ riskFactors: [
+ {
+ id: 'corn-borer',
+ name: '玉米螟',
+ condition: '温度适宜、湿度适中',
+ severity: 'medium' as const,
+ suggestion: '生物防治与化学防治结合,适时播种'
+ },
+ {
+ id: 'corn-drought',
+ name: '花期干旱',
+ condition: '开花期降雨不足',
+ severity: 'high' as const,
+ suggestion: '保证花期灌溉,选择耐旱品种'
+ }
+ ]
+ },
+ {
+ id: 'soybean',
+ cropName: '大豆',
+ category: '经济作物',
+ description: '豆科作物,具有固氮能力,对土壤肥力要求较低,适合轮作种植。',
+ growthCycle: {
+ days: 100,
+ seasons: ['春季', '夏季']
+ },
+ soilRequirements: {
+ ph: { optimal: [6.0, 7.0], acceptable: [5.5, 7.5] },
+ organicMatter: { optimal: [25, 35], acceptable: [20, 40] },
+ soilDepth: { optimal: [50, 80], acceptable: [40, 100] },
+ nitrogen: { optimal: [1.0, 2.0], acceptable: [0.5, 2.5] },
+ phosphorus: { optimal: [0.8, 1.8], acceptable: [0.5, 2.5] },
+ potassium: { optimal: [15, 25], acceptable: [10, 30] },
+ drainage: { optimal: [3, 5], acceptable: [2, 5] }
+ },
+ climateRequirements: {
+ temperature: { optimal: [18, 25], acceptable: [15, 28] },
+ rainfall: { optimal: [450, 700], acceptable: [350, 900] },
+ sunlight: { optimal: [6, 8], acceptable: [5, 9] }
+ },
+ expectedYield: {
+ high: [250, 350],
+ medium: [180, 250],
+ low: [120, 180]
+ },
+ riskFactors: [
+ {
+ id: 'soybean-disease',
+ name: '病害风险',
+ condition: '高温高湿环境',
+ severity: 'medium' as const,
+ suggestion: '选择抗病品种,合理轮作,加强田间管理'
+ }
+ ]
+ }
+];
+
+interface CropRecommendationsProps {
+ state: CropRecommendationState;
+ currentResult: SuitabilityResult;
+}
+
+export function CropRecommendations({ state, currentResult }: CropRecommendationsProps) {
+ // 匹配作物推荐
+ const matchCropsForField = (fieldFactors: any) => {
+ return cropKnowledgeBase.map(crop => {
+ let totalScore = 0;
+ let factorCount = 0;
+ const matchDetails: any[] = [];
+
+ // 评估土壤因子匹配度
+ Object.entries(crop.soilRequirements).forEach(([factor, requirements]: [string, any]) => {
+ if (fieldFactors[factor]) {
+ const value = fieldFactors[factor];
+ const { optimal, acceptable } = requirements;
+
+ let score = 0;
+ let status = '偏离';
+
+ if (value >= optimal[0] && value <= optimal[1]) {
+ score = 100;
+ status = '最佳';
+ } else if (value >= acceptable[0] && value <= acceptable[1]) {
+ const deviation = Math.min(
+ Math.abs(value - optimal[0]),
+ Math.abs(value - optimal[1])
+ );
+ const range = optimal[1] - optimal[0];
+ score = Math.max(60, 100 - (deviation / range) * 40);
+ status = '可接受';
+ } else {
+ score = Math.max(0, 60 - Math.min(
+ Math.abs(value - acceptable[0]),
+ Math.abs(value - acceptable[1])
+ ) * 2);
+ }
+
+ totalScore += score;
+ factorCount++;
+
+ matchDetails.push({
+ factor: factor === 'ph' ? 'pH值' :
+ factor === 'organicMatter' ? '有机质' :
+ factor === 'soilDepth' ? '土层厚度' :
+ factor === 'nitrogen' ? '全氮' :
+ factor === 'phosphorus' ? '全磷' :
+ factor === 'potassium' ? '全钾' : '排水性',
+ value,
+ score,
+ status
+ });
+ }
+ });
+
+ // 评估气候因子(简化处理)
+ if (fieldFactors.temperature) {
+ const tempScore = fieldFactors.temperature >= 18 && fieldFactors.temperature <= 25 ? 90 : 70;
+ totalScore += tempScore;
+ factorCount++;
+ }
+
+ if (fieldFactors.rainfall) {
+ const rainScore = fieldFactors.rainfall >= 500 && fieldFactors.rainfall <= 800 ? 90 : 70;
+ totalScore += rainScore;
+ factorCount++;
+ }
+
+ const matchScore = Math.round(totalScore / factorCount);
+
+ // 确定适宜性等级
+ let suitabilityLevel = '不推荐';
+ if (matchScore >= 85) suitabilityLevel = '高度推荐';
+ else if (matchScore >= 70) suitabilityLevel = '推荐';
+ else if (matchScore >= 50) suitabilityLevel = '谨慎种植';
+
+ // 根据适宜性等级选择产量区间
+ let expectedYield = crop.expectedYield.low;
+ if (suitabilityLevel === '高度推荐') expectedYield = crop.expectedYield.high;
+ else if (suitabilityLevel === '推荐') expectedYield = crop.expectedYield.medium;
+
+ // 识别适用风险
+ const applicableRisks = crop.riskFactors.filter(risk => {
+ if (risk.id.includes('drought') && fieldFactors.rainfall < 400) return true;
+ if (risk.id.includes('rust') && fieldFactors.temperature >= 15 && fieldFactors.temperature <= 22) return true;
+ return true; // 简化处理,默认显示所有风险
+ });
+
+ return {
+ crop,
+ matchScore,
+ suitabilityLevel,
+ matchDetails,
+ applicableRisks,
+ expectedYield
+ };
+ }).sort((a, b) => b.matchScore - a.matchScore);
+ };
+
+ // 获取地块因子数据
+ const fieldFactors = {
+ ph: currentResult.factors.find(f => f.id === 'ph')?.value || 0,
+ organic: currentResult.factors.find(f => f.id === 'organic')?.value || 0,
+ depth: currentResult.factors.find(f => f.id === 'depth')?.value || 0,
+ nitrogen: currentResult.factors.find(f => f.id === 'nitrogen')?.value || 0,
+ phosphorus: currentResult.factors.find(f => f.id === 'phosphorus')?.value || 0,
+ potassium: currentResult.factors.find(f => f.id === 'potassium')?.value || 0,
+ drainage: currentResult.factors.find(f => f.id === 'drainage')?.value || 0,
+ temperature: 22, // 模拟年均温度
+ rainfall: 800, // 模拟年均降雨量
+ };
+
+ // 匹配推荐作物
+ const recommendations = matchCropsForField(fieldFactors);
+
+ return (
+
+
+
+
+ 智能作物推荐清单
+
+
+ 基于{cropKnowledgeBase.length}种作物知识库匹配
+
+
+
+
+ {recommendations.map((recommendation, index) => {
+ const { crop, matchScore, suitabilityLevel, matchDetails, applicableRisks, expectedYield } = recommendation;
+
+ // 根据适宜性等级设置颜色
+ const levelColor =
+ suitabilityLevel === '高度推荐' ? { bg: 'bg-green-500', border: '#22c55e', text: 'text-green-600 dark:text-green-400' } :
+ suitabilityLevel === '推荐' ? { bg: 'bg-blue-500', border: '#3b82f6', text: 'text-blue-600 dark:text-blue-400' } :
+ suitabilityLevel === '谨慎种植' ? { bg: 'bg-yellow-500', border: '#eab308', text: 'text-yellow-600 dark:text-yellow-400' } :
+ { bg: 'bg-gray-500', border: '#6b7280', text: 'text-gray-600 dark:text-gray-400' };
+
+ // 只显示高度推荐和推荐的作物
+ if (suitabilityLevel === '不推荐') return null;
+
+ return (
+
+ {/* 标题栏 */}
+
+
+
+
+
+
+
+
{crop.cropName}
+ {crop.category}
+
+
+
+ {suitabilityLevel}
+
+ 匹配度: {matchScore}分
+
+
+
+
+
预期产量区间
+
+ {expectedYield[0]}-{expectedYield[1]} kg/亩
+
+
生长周期: {crop.growthCycle.days}天
+
+
+
+ {/* 作物描述 */}
+
+ {crop.description}
+
+
+ {/* 土壤因子匹配详情 */}
+
+
土壤因子匹配情况:
+
+ {matchDetails.map((detail, i) => (
+
+
{detail.factor}
+
{detail.value.toFixed(1)}
+ {detail.status === '最佳' ? (
+
+ 最佳
+
+ ) : detail.status === '可接受' ? (
+
+ 可接受
+
+ ) : (
+
+ 偏离
+
+ )}
+
+ ))}
+
+
+
+ {/* 气候要求 */}
+
+
+
+
+ 温度要求
+
+
+ {crop.climateRequirements.temperature.optimal[0]}-{crop.climateRequirements.temperature.optimal[1]}°C
+
+
+
+
+
+ 降雨要求
+
+
+ {crop.climateRequirements.rainfall.optimal[0]}-{crop.climateRequirements.rainfall.optimal[1]}mm/年
+
+
+
+
+
+ 光照要求
+
+
+ {crop.climateRequirements.sunlight.optimal[0]}-{crop.climateRequirements.sunlight.optimal[1]}小时/天
+
+
+
+
+ {/* 风险提示 */}
+ {applicableRisks.length > 0 && (
+ r.severity === 'high')
+ ? 'bg-red-50 dark:bg-red-950 border-red-200 dark:border-red-800'
+ : 'bg-orange-50 dark:bg-orange-950 border-orange-200 dark:border-orange-800'
+ }`}>
+
+ r.severity === 'high')
+ ? 'text-red-600 dark:text-red-400'
+ : 'text-orange-600 dark:text-orange-400'
+ }`} />
+ r.severity === 'high') ? 'text-red-900 dark:text-red-100' : 'text-orange-900 dark:text-orange-100'}>
+ 风险提示与应对建议
+
+
+
+ {applicableRisks.map((risk, i) => (
+
+
+
+ {risk.severity === 'high' ? '高风险' : risk.severity === 'medium' ? '中风险' : '低风险'}
+
+
+
r.severity === 'high')
+ ? 'text-red-800 dark:text-red-200'
+ : 'text-orange-800 dark:text-orange-200'
+ }`}>
+ {risk.name}
+
+
触发条件: {risk.condition}
+
r.severity === 'high') ? 'text-red-700 dark:text-red-300' : 'text-orange-700 dark:text-orange-300'}>
+ 💡 {risk.suggestion}
+
+
+
+
+ ))}
+
+
+ )}
+
+ {/* 适宜季节 */}
+
+ 适宜种植季节:
+ {crop.growthCycle.seasons.map((season, i) => (
+
+ {season}
+
+ ))}
+
+
+ );
+ })}
+
+
+ );
+}
\ No newline at end of file
diff --git a/crop-x/src/app/(app)/land-information/suitability/recommend/components/FieldEnvironmentOverview.tsx b/crop-x/src/app/(app)/land-information/suitability/recommend/components/FieldEnvironmentOverview.tsx
new file mode 100644
index 0000000..17b862b
--- /dev/null
+++ b/crop-x/src/app/(app)/land-information/suitability/recommend/components/FieldEnvironmentOverview.tsx
@@ -0,0 +1,152 @@
+'use client';
+
+import { Card } from '@/components/ui/card';
+import { Badge } from '@/components/ui/badge';
+import { ThermometerSun, Leaf, Droplet } from 'lucide-react';
+import { SuitabilityResult } from './cropRecommendReducer';
+
+interface FieldEnvironmentOverviewProps {
+ currentResult: SuitabilityResult;
+}
+
+export function FieldEnvironmentOverview({ currentResult }: FieldEnvironmentOverviewProps) {
+ const getScoreColor = (score: number) => {
+ if (score >= 80) return 'text-green-600 dark:text-green-400';
+ if (score >= 60) return 'text-yellow-600 dark:text-yellow-400';
+ return 'text-red-600 dark:text-red-400';
+ };
+
+ const getGradeColor = (grade: string) => {
+ switch (grade) {
+ case '高度适宜': return 'bg-green-500';
+ case '一般适宜': return 'bg-yellow-500';
+ case '不适宜': return 'bg-red-500';
+ default: return 'bg-gray-500';
+ }
+ };
+
+ return (
+
+
+
+ {currentResult.fieldName} - 环境参数概览
+
+
+
+
+
+
+ pH值
+
+
+ {currentResult.factors.find(f => f.id === 'ph')?.value.toFixed(1)}
+
+
+ 最佳: {currentResult.factors.find(f => f.id === 'ph')?.optimalRange[0]}-{currentResult.factors.find(f => f.id === 'ph')?.optimalRange[1]}
+
+
+
+
+
+
+ 有机质
+
+
+ {currentResult.factors.find(f => f.id === 'organic')?.value.toFixed(1)} g/kg
+
+
+ 最佳: {currentResult.factors.find(f => f.id === 'organic')?.optimalRange[0]}-{currentResult.factors.find(f => f.id === 'organic')?.optimalRange[1]} g/kg
+
+
+
+
+
土层厚度
+
+ {currentResult.factors.find(f => f.id === 'depth')?.value.toFixed(0)} cm
+
+
+ 最佳: {currentResult.factors.find(f => f.id === 'depth')?.optimalRange[0]}-{currentResult.factors.find(f => f.id === 'depth')?.optimalRange[1]} cm
+
+
+
+
+
+
+ 排水性
+
+
+ {currentResult.factors.find(f => f.id === 'drainage')?.value.toFixed(0)}分
+
+
+ 最佳: {currentResult.factors.find(f => f.id === 'drainage')?.optimalRange[0]}-{currentResult.factors.find(f => f.id === 'drainage')?.optimalRange[1]} 分
+
+
+
+
+
+
+
全氮
+
{currentResult.factors.find(f => f.id === 'nitrogen')?.value.toFixed(2)} g/kg
+
+ 最佳: {currentResult.factors.find(f => f.id === 'nitrogen')?.optimalRange[0]}-{currentResult.factors.find(f => f.id === 'nitrogen')?.optimalRange[1]} g/kg
+
+
+
+
全磷
+
{currentResult.factors.find(f => f.id === 'phosphorus')?.value.toFixed(2)} g/kg
+
+ 最佳: {currentResult.factors.find(f => f.id === 'phosphorus')?.optimalRange[0]}-{currentResult.factors.find(f => f.id === 'phosphorus')?.optimalRange[1]} g/kg
+
+
+
+
全钾
+
{currentResult.factors.find(f => f.id === 'potassium')?.value.toFixed(1)} g/kg
+
+ 最佳: {currentResult.factors.find(f => f.id === 'potassium')?.optimalRange[0]}-{currentResult.factors.find(f => f.id === 'potassium')?.optimalRange[1]} g/kg
+
+
+
+
+
+
地块适宜性分析
+
+
+
+ 综合评分:
+
+ {currentResult.totalScore}分
+
+
+
+ {currentResult.grade}
+
+
+
+
+ 优秀因子:
+
+ {currentResult.factors.filter(f => f.score >= 80).length}项
+
+
+
+ 待改善因子:
+
+ {currentResult.factors.filter(f => f.score < 70).length}项
+
+
+
+
+
+ 评价时间:
+ {currentResult.timestamp}
+
+
+ 地块ID:
+ {currentResult.fieldId}
+
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/crop-x/src/app/(app)/land-information/suitability/recommend/components/KnowledgeBaseDialog.tsx b/crop-x/src/app/(app)/land-information/suitability/recommend/components/KnowledgeBaseDialog.tsx
new file mode 100644
index 0000000..c78e041
--- /dev/null
+++ b/crop-x/src/app/(app)/land-information/suitability/recommend/components/KnowledgeBaseDialog.tsx
@@ -0,0 +1,409 @@
+'use client';
+
+import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from '@/components/ui/dialog';
+import { Card } from '@/components/ui/card';
+import { Badge } from '@/components/ui/badge';
+import { Button } from '@/components/ui/button';
+import { BookOpen, Database, Leaf, Target, Droplet, Cloud, Sun, ThermometerSun, TrendingUp, AlertTriangle } from 'lucide-react';
+
+// 作物知识库数据(与CropRecommendations.tsx保持一致)
+const cropKnowledgeBase = [
+ {
+ id: 'wheat',
+ cropName: '小麦',
+ category: '粮食作物',
+ description: '适应性强的主粮作物,对土壤要求较宽泛,耐寒性好,适合北方地区种植。',
+ growthCycle: {
+ days: 220,
+ seasons: ['春季', '秋季']
+ },
+ soilRequirements: {
+ ph: { optimal: [6.5, 7.5], acceptable: [6.0, 8.0] },
+ organicMatter: { optimal: [25, 35], acceptable: [20, 40] },
+ soilDepth: { optimal: [60, 100], acceptable: [40, 120] },
+ nitrogen: { optimal: [1.5, 2.5], acceptable: [1.0, 3.0] },
+ phosphorus: { optimal: [1.0, 2.0], acceptable: [0.6, 2.5] },
+ potassium: { optimal: [15, 25], acceptable: [10, 30] },
+ drainage: { optimal: [3, 5], acceptable: [2, 5] }
+ },
+ climateRequirements: {
+ temperature: { optimal: [15, 22], acceptable: [10, 25] },
+ rainfall: { optimal: [400, 600], acceptable: [300, 800] },
+ sunlight: { optimal: [6, 8], acceptable: [5, 10] }
+ },
+ expectedYield: {
+ high: [400, 500],
+ medium: [300, 400],
+ low: [200, 300]
+ },
+ riskFactors: [
+ {
+ id: 'wheat-rust',
+ name: '锈病风险',
+ condition: '湿度过高、温度适宜',
+ severity: 'medium' as const,
+ suggestion: '选择抗病品种,合理密植,及时防治'
+ },
+ {
+ id: 'wheat-drought',
+ name: '干旱风险',
+ condition: '降雨量不足400mm',
+ severity: 'high' as const,
+ suggestion: '加强灌溉设施建设,选择抗旱品种'
+ }
+ ]
+ },
+ {
+ id: 'corn',
+ cropName: '玉米',
+ category: '粮食作物',
+ description: '高产作物,对温度要求较高,需水量大,适合水热条件良好的地区。',
+ growthCycle: {
+ days: 120,
+ seasons: ['春季', '夏季']
+ },
+ soilRequirements: {
+ ph: { optimal: [6.0, 7.0], acceptable: [5.5, 7.5] },
+ organicMatter: { optimal: [30, 40], acceptable: [25, 45] },
+ soilDepth: { optimal: [80, 120], acceptable: [60, 150] },
+ nitrogen: { optimal: [2.0, 3.0], acceptable: [1.5, 3.5] },
+ phosphorus: { optimal: [1.2, 2.5], acceptable: [0.8, 3.0] },
+ potassium: { optimal: [20, 30], acceptable: [15, 35] },
+ drainage: { optimal: [3, 5], acceptable: [2, 5] }
+ },
+ climateRequirements: {
+ temperature: { optimal: [20, 28], acceptable: [15, 32] },
+ rainfall: { optimal: [500, 800], acceptable: [400, 1000] },
+ sunlight: { optimal: [7, 9], acceptable: [6, 10] }
+ },
+ expectedYield: {
+ high: [600, 800],
+ medium: [400, 600],
+ low: [250, 400]
+ },
+ riskFactors: [
+ {
+ id: 'corn-borer',
+ name: '玉米螟',
+ condition: '温度适宜、湿度适中',
+ severity: 'medium' as const,
+ suggestion: '生物防治与化学防治结合,适时播种'
+ },
+ {
+ id: 'corn-drought',
+ name: '花期干旱',
+ condition: '开花期降雨不足',
+ severity: 'high' as const,
+ suggestion: '保证花期灌溉,选择耐旱品种'
+ }
+ ]
+ },
+ {
+ id: 'soybean',
+ cropName: '大豆',
+ category: '经济作物',
+ description: '豆科作物,具有固氮能力,对土壤肥力要求较低,适合轮作种植。',
+ growthCycle: {
+ days: 100,
+ seasons: ['春季', '夏季']
+ },
+ soilRequirements: {
+ ph: { optimal: [6.0, 7.0], acceptable: [5.5, 7.5] },
+ organicMatter: { optimal: [25, 35], acceptable: [20, 40] },
+ soilDepth: { optimal: [50, 80], acceptable: [40, 100] },
+ nitrogen: { optimal: [1.0, 2.0], acceptable: [0.5, 2.5] },
+ phosphorus: { optimal: [0.8, 1.8], acceptable: [0.5, 2.5] },
+ potassium: { optimal: [15, 25], acceptable: [10, 30] },
+ drainage: { optimal: [3, 5], acceptable: [2, 5] }
+ },
+ climateRequirements: {
+ temperature: { optimal: [18, 25], acceptable: [15, 28] },
+ rainfall: { optimal: [450, 700], acceptable: [350, 900] },
+ sunlight: { optimal: [6, 8], acceptable: [5, 9] }
+ },
+ expectedYield: {
+ high: [250, 350],
+ medium: [180, 250],
+ low: [120, 180]
+ },
+ riskFactors: [
+ {
+ id: 'soybean-disease',
+ name: '病害风险',
+ condition: '高温高湿环境',
+ severity: 'medium' as const,
+ suggestion: '选择抗病品种,合理轮作,加强田间管理'
+ }
+ ]
+ }
+];
+
+interface KnowledgeBaseDialogProps {
+ showKnowledgeBase: boolean;
+ onClose: () => void;
+}
+
+export function KnowledgeBaseDialog({ showKnowledgeBase, onClose }: KnowledgeBaseDialogProps) {
+ return (
+
+ );
+}
\ No newline at end of file
diff --git a/crop-x/src/app/(app)/land-information/suitability/recommend/components/cropRecommendReducer.tsx b/crop-x/src/app/(app)/land-information/suitability/recommend/components/cropRecommendReducer.tsx
new file mode 100644
index 0000000..12e7e1f
--- /dev/null
+++ b/crop-x/src/app/(app)/land-information/suitability/recommend/components/cropRecommendReducer.tsx
@@ -0,0 +1,124 @@
+'use client';
+
+import { useReducer } from 'react';
+import { toast } from 'sonner';
+
+export interface EvaluationFactor {
+ id: string;
+ name: string;
+ value: number;
+ weight: number;
+ unit: string;
+ optimalRange: [number, number];
+ score: number;
+}
+
+export interface SuitabilityResult {
+ fieldId: string;
+ fieldName: string;
+ totalScore: number;
+ grade: '高度适宜' | '一般适宜' | '不适宜';
+ factors: EvaluationFactor[];
+ timestamp: string;
+}
+
+export interface CropRecommendationState {
+ evaluationResults: SuitabilityResult[];
+ selectedField: string;
+ showKnowledgeBase: boolean;
+}
+
+export type CropRecommendAction =
+ | { type: 'SET_SELECTED_FIELD'; payload: string }
+ | { type: 'SET_SHOW_KNOWLEDGE_BASE'; payload: boolean }
+ | { type: 'SET_EVALUATION_RESULTS'; payload: SuitabilityResult[] }
+ | { type: 'UPDATE_EVALUATION_RESULT'; payload: SuitabilityResult };
+
+const initialState: CropRecommendationState = {
+ evaluationResults: [
+ {
+ fieldId: 'field-1',
+ fieldName: '东区1号地',
+ totalScore: 87,
+ grade: '高度适宜',
+ factors: [
+ { id: 'ph', name: 'pH值', value: 6.5, weight: 20, unit: '', optimalRange: [6.0, 7.5], score: 95 },
+ { id: 'organic', name: '有机质含量', value: 32, weight: 25, unit: 'g/kg', optimalRange: [25, 40], score: 90 },
+ { id: 'depth', name: '土层厚度', value: 85, weight: 20, unit: 'cm', optimalRange: [60, 100], score: 88 },
+ { id: 'nitrogen', name: '全氮', value: 1.8, weight: 10, unit: 'g/kg', optimalRange: [1.5, 2.5], score: 85 },
+ { id: 'phosphorus', name: '全磷', value: 1.2, weight: 10, unit: 'g/kg', optimalRange: [1.0, 2.0], score: 80 },
+ { id: 'potassium', name: '全钾', value: 18, weight: 10, unit: 'g/kg', optimalRange: [15, 25], score: 82 },
+ { id: 'drainage', name: '排水性', value: 4, weight: 5, unit: '', optimalRange: [3, 5], score: 90 },
+ ],
+ timestamp: '2024-10-15 14:30',
+ },
+ {
+ fieldId: 'field-2',
+ fieldName: '西区2号地',
+ totalScore: 72,
+ grade: '一般适宜',
+ factors: [
+ { id: 'ph', name: 'pH值', value: 7.8, weight: 20, unit: '', optimalRange: [6.0, 7.5], score: 65 },
+ { id: 'organic', name: '有机质含量', value: 22, weight: 25, unit: 'g/kg', optimalRange: [25, 40], score: 70 },
+ { id: 'depth', name: '土层厚度', value: 55, weight: 20, unit: 'cm', optimalRange: [60, 100], score: 68 },
+ { id: 'nitrogen', name: '全氮', value: 1.3, weight: 10, unit: 'g/kg', optimalRange: [1.5, 2.5], score: 72 },
+ { id: 'phosphorus', name: '全磷', value: 0.9, weight: 10, unit: 'g/kg', optimalRange: [1.0, 2.0], score: 75 },
+ { id: 'potassium', name: '全钾', value: 14, weight: 10, unit: 'g/kg', optimalRange: [15, 25], score: 78 },
+ { id: 'drainage', name: '排水性', value: 3, weight: 5, unit: '', optimalRange: [3, 5], score: 85 },
+ ],
+ timestamp: '2024-10-15 14:28',
+ },
+ {
+ fieldId: 'field-3',
+ fieldName: '南区3号地',
+ totalScore: 58,
+ grade: '不适宜',
+ factors: [
+ { id: 'ph', name: 'pH值', value: 8.5, weight: 20, unit: '', optimalRange: [6.0, 7.5], score: 45 },
+ { id: 'organic', name: '有机质含量', value: 15, weight: 25, unit: 'g/kg', optimalRange: [25, 40], score: 52 },
+ { id: 'depth', name: '土层厚度', value: 42, weight: 20, unit: 'cm', optimalRange: [60, 100], score: 55 },
+ { id: 'nitrogen', name: '全氮', value: 0.8, weight: 10, unit: 'g/kg', optimalRange: [1.5, 2.5], score: 60 },
+ { id: 'phosphorus', name: '全磷', value: 0.6, weight: 10, unit: 'g/kg', optimalRange: [1.0, 2.0], score: 65 },
+ { id: 'potassium', name: '全钾', value: 10, weight: 10, unit: 'g/kg', optimalRange: [15, 25], score: 58 },
+ { id: 'drainage', name: '排水性', value: 2, weight: 5, unit: '', optimalRange: [3, 5], score: 70 },
+ ],
+ timestamp: '2024-10-15 14:25',
+ },
+ ],
+ selectedField: 'field-1',
+ showKnowledgeBase: false,
+};
+
+export function cropRecommendReducer(
+ state: CropRecommendationState = initialState,
+ action: CropRecommendAction
+): CropRecommendationState {
+ switch (action.type) {
+ case 'SET_SELECTED_FIELD':
+ return {
+ ...state,
+ selectedField: action.payload,
+ };
+ case 'SET_SHOW_KNOWLEDGE_BASE':
+ return {
+ ...state,
+ showKnowledgeBase: action.payload,
+ };
+ case 'SET_EVALUATION_RESULTS':
+ return {
+ ...state,
+ evaluationResults: action.payload,
+ };
+ case 'UPDATE_EVALUATION_RESULT':
+ return {
+ ...state,
+ evaluationResults: state.evaluationResults.map(result =>
+ result.fieldId === action.payload.fieldId ? action.payload : result
+ ),
+ };
+ default:
+ return state;
+ }
+}
+
+export { initialState };
\ No newline at end of file
diff --git a/crop-x/src/app/(app)/land-information/suitability/recommend/page.tsx b/crop-x/src/app/(app)/land-information/suitability/recommend/page.tsx
new file mode 100644
index 0000000..21b6c4e
--- /dev/null
+++ b/crop-x/src/app/(app)/land-information/suitability/recommend/page.tsx
@@ -0,0 +1,188 @@
+'use client';
+
+import { useReducer } from 'react';
+import { Button } from '@/components/ui/button';
+import { Badge } from '@/components/ui/badge';
+import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
+import { BookOpen, Target } from 'lucide-react';
+import {
+ cropRecommendReducer,
+ initialState,
+ SuitabilityResult
+} from './components/cropRecommendReducer';
+import { FieldEnvironmentOverview } from './components/FieldEnvironmentOverview';
+import { CropRecommendations } from './components/CropRecommendations';
+import { KnowledgeBaseDialog } from './components/KnowledgeBaseDialog';
+
+export default function CropPage() {
+ const [state, dispatch] = useReducer(cropRecommendReducer, initialState);
+
+ // 获取当前选中的地块结果
+ const currentResult = state.evaluationResults.find(r => r.fieldId === state.selectedField) || state.evaluationResults[0];
+
+ const handleFieldChange = (value: string) => {
+ dispatch({ type: 'SET_SELECTED_FIELD', payload: value });
+ };
+
+ const handleToggleKnowledgeBase = () => {
+ dispatch({ type: 'SET_SHOW_KNOWLEDGE_BASE', payload: !state.showKnowledgeBase });
+ };
+
+ const getGradeColor = (grade: string) => {
+ switch (grade) {
+ case '高度适宜': return 'bg-green-500';
+ case '一般适宜': return 'bg-yellow-500';
+ case '不适宜': return 'bg-red-500';
+ default: return 'bg-gray-500';
+ }
+ };
+
+ return (
+
+
+
+
作物适配推荐
+
+ 智能作物推荐清单、基于知识库的精准匹配
+
+
+
+
+
+
+
+ {/* 地块选择和适宜性概览 */}
+
+
+
+
+
+
+
+
+ {/* 适宜性评分卡片 */}
+
+
+
+
适宜性评分
+
{currentResult.totalScore}
+
+ {currentResult.grade}
+
+
+
+
+ {/* 因子评分统计 */}
+
+
因子评分统计
+
+
+ 优秀因子
+
+ {currentResult.factors.filter(f => f.score >= 80).length}
+
+
+
+ 待改善因子
+
+ {currentResult.factors.filter(f => f.score < 70).length}
+
+
+
+ 评价时间
+ {currentResult.timestamp}
+
+
+
+
+
+
+
+
+
+
+
+ {/* 智能作物推荐 */}
+
+
+ {/* 知识库对话框 */}
+
+
+ {/* 系统说明 */}
+
+
+
+
+
+
+
作物-环境知识库匹配系统
+
+
+
🌾 知识库构成
+
+ - • 作物种类: 多种主要农作物
+ - • 土壤参数: pH、有机质等7项指标
+ - • 气候参数: 温度、降雨、光照
+ - • 最佳范围: 每个因子的要求范围
+
+
+
+
🎯 智能匹配流程
+
+ - • 参数对比: 地块值与要求对比
+ - • 评分计算: 最佳100分,可接受60分
+ - • 综合匹配度: 加权平均所有因子
+ - • 分级推荐: ≥85高度推荐,70-84推荐
+
+
+
+
📊 产量预测机制
+
+ - • 三级产量区间: 高/中/低适宜性
+ - • 历史数据支撑: 基于实测数据模型
+ - • 动态调整: 根据匹配度选择区间
+ - • 品种差异: 考虑作物产量特性
+
+
+
+
⚠️ 风险识别系统
+
+ - • 多维度监测: 土壤、气候、营养
+ - • 条件触发: 自动判断风险存在
+ - • 严重度分级: 高/中/低风险等级
+ - • 应对建议: 每个风险配套措施
+
+
+
+
+
+
+ 💡 系统优势:
+ 本系统整合了土壤科学、作物学、气象学等多学科知识,建立了完整的作物-环境适配知识库。
+ 通过科学的评分体系和智能匹配算法,为农业生产决策提供可靠的数据支持,帮助优化作物布局,
+ 提高土地利用率和经济效益,同时有效规避种植风险。
+
+
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/crop-x/src/app/(app)/land-information/suitability/weight/page.tsx b/crop-x/src/app/(app)/land-information/suitability/weight/page.tsx
deleted file mode 100644
index 456f585..0000000
--- a/crop-x/src/app/(app)/land-information/suitability/weight/page.tsx
+++ /dev/null
@@ -1,18 +0,0 @@
-'use client';
-
-import { Card } from '@/components/ui/card';
-
-export default function WeightPage() {
- return (
-
-
- 权重设置
-
-
- 页面路径: /land-information/suitability/weight
-
-
-
-
- );
-}
\ No newline at end of file
diff --git a/crop-x/src/app/layout.tsx b/crop-x/src/app/layout.tsx
index 2073c90..cdcdbc0 100644
--- a/crop-x/src/app/layout.tsx
+++ b/crop-x/src/app/layout.tsx
@@ -318,17 +318,17 @@ const fieldMessageManagement = {
items: [
{
title: "多因子综合评价",
- url: "/land-information/suitability/comprehensive",
+ url: "/land-information/suitability/multiFactor",
isActive: false
},
{
title: "自动化空间分析",
- url: "/land-information/suitability/batch",
+ url: "/land-information/suitability/auto",
isActive: false
},
{
title: "作物适配推荐",
- url: "/land-information/suitability/crop",
+ url: "/land-information/suitability/recommend",
isActive: false
}
]
diff --git a/crop-x/temp_file.tsx b/crop-x/temp_file.tsx
new file mode 100644
index 0000000..78434b8
--- /dev/null
+++ b/crop-x/temp_file.tsx
@@ -0,0 +1,415 @@
+/**
+ * 多因子综合评价组件
+ * 提供地块适宜性评价、权重配置、批量分析和作物推荐功能
+ */
+
+'use client';
+
+import { useState, useEffect } from 'react';
+import { Card } from '@/components/ui/card';
+import { Button } from '@/components/ui/button';
+import { Badge } from '@/components/ui/badge';
+import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
+import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
+import { Input } from '@/components/ui/input';
+import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogTrigger } from '@/components/ui/dialog';
+import { Progress } from '@/components/ui/progress';
+import { Slider } from '@/components/ui/slider';
+import { Textarea } from '@/components/ui/textarea';
+import {
+ Leaf,
+ TrendingUp,
+ Award,
+ AlertTriangle,
+ CheckCircle2,
+ Play,
+ Settings,
+ Download,
+ Eye,
+ Calculator,
+ Database,
+ RefreshCw,
+ Zap,
+ Target,
+ Droplet,
+ Cloud,
+ Sun,
+ ThermometerSun,
+ BookOpen,
+ Beaker,
+ Info,
+ BarChart3,
+ Filter
+} from 'lucide-react';
+import { toast } from 'sonner';
+
+import {
+ EvaluationFactor,
+ SuitabilityResult,
+ FactorWeight,
+ BatchProgress,
+ getGradeColor,
+ getScoreColor,
+ getSuitabilityLevelColor,
+ formatDate,
+ MOCK_FIELDS
+} from './multiFactorTypes';
+import {
+ MultiFactorService
+} from './multiFactorService';
+import {
+ matchCropsForField,
+ cropKnowledgeBase
+} from './cropKnowledgeBase';
+
+export function MultiFactorEvaluation() {
+ const [selectedField, setSelectedField] = useState('field-1');
+ const [showWeightConfig, setShowWeightConfig] = useState(false);
+ const [showKnowledgeBase, setShowKnowledgeBase] = useState(false);
+ const [batchProgress, setBatchProgress] = useState({
+ total: 0,
+ processed: 0,
+ highSuitability: 0,
+ mediumSuitability: 0,
+ lowSuitability: 0,
+ currentField: '',
+ });
+ const [isBatchRunning, setIsBatchRunning] = useState(false);
+ const [batchAnalysisResults, setBatchAnalysisResults] = useState([]);
+
+ // 评价因子权重配置
+ const [factorWeights, setFactorWeights] = useState([
+ { id: 'ph', name: 'pH值', weight: 20, unit: '' },
+ { id: 'organic', name: '有机质含量', weight: 25, unit: 'g/kg' },
+ { id: 'depth', name: '土层厚度', weight: 20, unit: 'cm' },
+ { id: 'nitrogen', name: '全氮', weight: 10, unit: 'g/kg' },
+ { id: 'phosphorus', name: '全磷', weight: 10, unit: 'g/kg' },
+ { id: 'potassium', name: '全钾', weight: 10, unit: 'g/kg' },
+ { id: 'drainage', name: '排水性', weight: 5, unit: '' },
+ ]);
+
+ // 模拟适宜性评价结果
+ const [evaluationResults, setEvaluationResults] = useState(
+ MultiFactorService.generateMockEvaluationData()
+ );
+
+ // 获取当前选中的地块结果
+ const currentResult =
+ evaluationResults.find(r => r.fieldId === selectedField) ||
+ batchAnalysisResults.find(r => r.fieldId === selectedField) ||
+ evaluationResults[0];
+
+ // 批量分析处理函数
+ const handleRunBatchAnalysis = async () => {
+ const validation = MultiFactorService.validateWeights(factorWeights);
+ if (!validation.isValid) {
+ toast.error(`权重总和必须为100%才能进行批量分析(当前:${validation.totalWeight}%)`);
+ return;
+ }
+
+ setIsBatchRunning(true);
+ setBatchProgress({
+ total: 68,
+ processed: 0,
+ highSuitability: 0,
+ mediumSuitability: 0,
+ lowSuitability: 0,
+ currentField: '',
+ });
+ setBatchAnalysisResults([]);
+
+ toast.success('开始批量分析,正在读取地块数据...');
+
+ try {
+ const results = await MultiFactorService.runBatchAnalysis(
+ factorWeights,
+ (progress) => {
+ setBatchProgress(progress);
+ }
+ );
+
+ setBatchAnalysisResults(results);
+ setIsBatchRunning(false);
+ toast.success(`批量分析完成!已为${results.length}个地块生成适宜性评价结果`);
+ } catch (error) {
+ setIsBatchRunning(false);
+ toast.error('批量分析失败');
+ }
+ };
+
+ const handleUpdateWeight = (id: string, newWeight: number) => {
+ setFactorWeights(prev =>
+ MultiFactorService.updateWeight(prev, id, newWeight)
+ );
+ };
+
+ const handleResetWeights = () => {
+ setFactorWeights(MultiFactorService.resetWeights());
+ toast.success('权重已恢复默认值');
+ };
+
+ const handleApplyPreset = (preset: 'grain' | 'economic' | 'default') => {
+ setFactorWeights(MultiFactorService.getWeightPreset(preset));
+ const presetName = preset === 'grain' ? '粮食作物' : preset === 'economic' ? '经济作物' : '默认';
+ toast.success(`已应用${presetName}权重方案`);
+ };
+
+ const totalWeight = factorWeights.reduce((sum, f) => sum + f.weight, 0);
+
+ // 执行地块适宜性评价
+ const handleEvaluateField = () => {
+ const validation = MultiFactorService.validateWeights(factorWeights);
+ if (!validation.isValid) {
+ toast.error(`权重总和必须为100%才能进行评价(当前:${validation.totalWeight}%)`);
+ return;
+ }
+
+ try {
+ const result = MultiFactorService.generateEvaluationResult(selectedField, factorWeights);
+ setEvaluationResults(prev =>
+ prev.map(r => r.fieldId === selectedField ? result : r)
+ );
+ toast.success('评价完成!已应用当前权重配置计算综合得分');
+ } catch (error) {
+ toast.error('评价失败');
+ }
+ };
+
+ const exportResults = () => {
+ const resultsToExport = batchAnalysisResults.length > 0 ? batchAnalysisResults : evaluationResults;
+ toast.success('正在导出评价结果...');
+ // 模拟导出功能
+ setTimeout(() => {
+ toast.success(`已导出${resultsToExport.length}个地块的评价结果`);
+ }, 2000);
+ };
+
+ return (
+
+
+
+
多因子综合评价
+
+ 地块适宜性评价、自动化空间分析与作物适配推荐
+
+
+
+
+
+
+
+
+ {/* 多因子综合评价 */}
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* 评价结果总览 */}
+
+
+
+
+
综合评分
+
+ {currentResult.totalScore}
+
+
+ {currentResult.grade}
+
+
+
+
+
+
+
+
优秀因子
+
+ {currentResult.factors.filter(f => f.score >= 80).length}
+
+
+ / {currentResult.factors.length} 项
+
+
+
+
+
+
+
+
待改善因子
+
+ {currentResult.factors.filter(f => f.score < 70).length}
+
+
需要优化
+
+
+
+
+
+
+
评价时间
+
+ {formatDate(currentResult.timestamp)}
+
+
最近更新
+
+
+
+
+ {/* 因子详细评分 */}
+
+ 评价因子详细分析
+
+ {currentResult.factors.map((factor) => (
+
+
+
+ {factor.name}
+
+ 权重: {factor.weight}%
+
+
+
+
+ 实际值: {factor.value.toFixed(1)}{factor.unit}
+
+
+ 最佳范围: {factor.optimalRange[0]}-{factor.optimalRange[1]}{factor.unit}
+
+
+ {factor.score}分
+
+
+
+
+
+ ))}
+
+
+
+ {/* 加权计算说明 */}
+
+ 层次分析法(AHP)加权计算
+
+
+
计算公式:
+
+ 总分 = Σ(因子得分 × 因子权重)
+
+
+ = ({currentResult.factors.map((f, i) =>
+ `${f.score} × ${f.weight}%${i < currentResult.factors.length - 1 ? ' + ' : ''}`
+ ).join('')})
+
+
+ = {currentResult.totalScore}分
+
+
+
+
+
+
高度适宜 (≥80分)
+
+ - • 土壤条件优秀
+ - • 适合多种作物
+ - • 预期产量高
+
+
+
+
一般适宜 (60-79分)
+
+ - • 部分因子需改善
+ - • 可种植耐性作物
+ - • 需适当改良
+
+
+
+
不适宜 (<60分)
+
+ - • 土壤条件较差
+ - • 需大幅改良
+ - • 风险较高
+
+
+
+
+
+
+ {/* 多因子综合评价功能说明 */}
+
+
+
+
+
多因子综合评价功能说明:
+
+ - • 关键指标整合: 整合pH值、有机质含量、土层厚度、全氮、全磷、全钾、排水性等7项关键土壤指标
+ - • 层次分析法(AHP): 采用层次分析法构建加权评分体系,公式:总分 = Σ(因子得分 × 因子权重)
+ - • 单因子评分: 根据实际值与最佳范围的接近程度计算各因子得分(0-100分),范围内得分85-100分
+ - • 自定义权重: 支持手动调整各指标权重,提供粮食作物和经济作物两种预设方案
+ - • 综合得分: 输出0-100分的适宜性评分,自动计算加权总分
+ - • 三级分级: 高度适宜(≥80分)、一般适宜(60-79分)、不适宜(<60分)
+ - • 可视化展示: 提供进度条、颜色标识等多维度可视化,直观展示各因子评分与最佳范围对比
+ - • 改善建议: 自动识别待改善因子,为土壤改良提供决策依据
+
+
+
+
+
+