生产管理系统前端 - 地块对比分析,地块适宜性评价开发

This commit is contained in:
2025-10-30 15:03:05 +08:00
parent 2aa93f941e
commit 77bf48f88a
51 changed files with 11252 additions and 96 deletions

415
crop-x/temp_file.tsx Normal file
View File

@@ -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<BatchProgress>({
total: 0,
processed: 0,
highSuitability: 0,
mediumSuitability: 0,
lowSuitability: 0,
currentField: '',
});
const [isBatchRunning, setIsBatchRunning] = useState(false);
const [batchAnalysisResults, setBatchAnalysisResults] = useState<SuitabilityResult[]>([]);
// 评价因子权重配置
const [factorWeights, setFactorWeights] = useState<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: '' },
]);
// 模拟适宜性评价结果
const [evaluationResults, setEvaluationResults] = useState<SuitabilityResult[]>(
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 (
<div className="space-y-6">
<div className="flex items-center justify-between">
<div>
<h2 className="text-green-800 dark:text-green-200"></h2>
<p className="text-muted-foreground">
</p>
</div>
<div className="flex gap-2">
<Button variant="outline" onClick={() => setShowKnowledgeBase(true)}>
<BookOpen className="w-4 h-4 mr-2" />
</Button>
<Button variant="outline" onClick={() => setShowWeightConfig(true)}>
<Settings className="w-4 h-4 mr-2" />
</Button>
</div>
</div>
{/* 多因子综合评价 */}
<div className="space-y-4">
<Card className="p-4 bg-card">
<div className="flex items-center gap-4">
<div className="flex-1">
<label className="text-xs text-muted-foreground mb-2 block"></label>
<Select value={selectedField} onValueChange={setSelectedField}>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
{evaluationResults.map((result) => (
<SelectItem key={result.fieldId} value={result.fieldId}>
{result.fieldName}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div className="flex gap-2 items-end">
<Button
className="bg-green-600 hover:bg-green-700"
onClick={handleEvaluateField}
>
<Play className="w-4 h-4 mr-2" />
</Button>
</div>
</div>
</Card>
{/* 评价结果总览 */}
<div className="grid grid-cols-4 gap-4">
<Card className="p-6 bg-gradient-to-br from-green-50 to-green-100 dark:from-green-950 dark:to-green-900">
<div className="text-center">
<Award className="w-12 h-12 text-green-600 dark:text-green-400 mx-auto mb-3" />
<p className="text-xs text-muted-foreground mb-2"></p>
<p
className="text-4xl mb-2"
style={{ color: getGradeColor(currentResult.grade) }}
>
{currentResult.totalScore}
</p>
<Badge
className="text-white font-light"
style={{ backgroundColor: getGradeColor(currentResult.grade) }}
>
{currentResult.grade}
</Badge>
</div>
</Card>
<Card className="p-6 bg-card">
<div className="text-center">
<CheckCircle2 className="w-12 h-12 text-blue-600 dark:text-blue-400 mx-auto mb-3" />
<p className="text-xs text-muted-foreground mb-2"></p>
<p className="text-4xl text-blue-600 dark:text-blue-400 mb-2">
{currentResult.factors.filter(f => f.score >= 80).length}
</p>
<p className="text-xs text-muted-foreground">
/ {currentResult.factors.length}
</p>
</div>
</Card>
<Card className="p-6 bg-card">
<div className="text-center">
<AlertTriangle className="w-12 h-12 text-yellow-600 dark:text-yellow-400 mx-auto mb-3" />
<p className="text-xs text-muted-foreground mb-2"></p>
<p className="text-4xl text-yellow-600 dark:text-yellow-400 mb-2">
{currentResult.factors.filter(f => f.score < 70).length}
</p>
<p className="text-xs text-muted-foreground"></p>
</div>
</Card>
<Card className="p-6 bg-card">
<div className="text-center">
<TrendingUp className="w-12 h-12 text-purple-600 dark:text-purple-400 mx-auto mb-3" />
<p className="text-xs text-muted-foreground mb-2"></p>
<p className="text-sm text-purple-600 dark:text-purple-400 mb-2">
{formatDate(currentResult.timestamp)}
</p>
<p className="text-xs text-muted-foreground"></p>
</div>
</Card>
</div>
{/* 因子详细评分 */}
<Card className="p-6 bg-card">
<h3 className="mb-4"></h3>
<div className="space-y-4">
{currentResult.factors.map((factor) => (
<div key={factor.id} className="space-y-2">
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<span className="text-sm font-medium">{factor.name}</span>
<Badge variant="outline" className="text-xs font-light">
: {factor.weight}%
</Badge>
</div>
<div className="flex items-center gap-4">
<span className="text-sm text-muted-foreground">
: {factor.value.toFixed(1)}{factor.unit}
</span>
<span className="text-sm text-muted-foreground">
: {factor.optimalRange[0]}-{factor.optimalRange[1]}{factor.unit}
</span>
<span
className="text-sm font-medium"
style={{ color: getScoreColor(factor.score) }}
>
{factor.score}
</span>
</div>
</div>
<div className="relative">
<Progress value={factor.score} className="h-2" />
<div className="absolute top-0 left-0 h-2 w-full flex items-center">
<div
className="absolute h-3 w-1 bg-blue-500"
style={{
left: `${((factor.optimalRange[0] - 0) / (factor.optimalRange[1] * 1.5)) * 100}%`
}}
/>
<div
className="absolute h-3 w-1 bg-blue-500"
style={{
left: `${((factor.optimalRange[1] - 0) / (factor.optimalRange[1] * 1.5)) * 100}%`
}}
/>
</div>
</div>
</div>
))}
</div>
</Card>
{/* 加权计算说明 */}
<Card className="p-6 bg-card">
<h3 className="mb-4">(AHP)</h3>
<div className="space-y-4">
<div className="p-4 bg-blue-50 dark:bg-blue-950 rounded-lg">
<p className="text-sm text-blue-900 dark:text-blue-100 mb-2"></p>
<code className="text-xs text-blue-800 dark:text-blue-200 block mb-2">
= Σ( × )
</code>
<p className="text-xs text-blue-800 dark:text-blue-200">
= ({currentResult.factors.map((f, i) =>
`${f.score} × ${f.weight}%${i < currentResult.factors.length - 1 ? ' + ' : ''}`
).join('')})
</p>
<p className="text-xs text-blue-800 dark:text-blue-200 mt-2">
= {currentResult.totalScore}
</p>
</div>
<div className="grid grid-cols-3 gap-4">
<div className="p-4 bg-green-50 dark:bg-green-950 rounded-lg">
<h4 className="text-green-900 dark:text-green-100 mb-2"> (80)</h4>
<ul className="text-sm text-green-800 dark:text-green-200 space-y-1">
<li> </li>
<li> </li>
<li> </li>
</ul>
</div>
<div className="p-4 bg-yellow-50 dark:bg-yellow-950 rounded-lg">
<h4 className="text-yellow-900 dark:text-yellow-100 mb-2"> (60-79)</h4>
<ul className="text-sm text-yellow-800 dark:text-yellow-200 space-y-1">
<li> </li>
<li> </li>
<li> </li>
</ul>
</div>
<div className="p-4 bg-red-50 dark:bg-red-950 rounded-lg">
<h4 className="text-red-900 dark:text-red-100 mb-2"> (&lt;60)</h4>
<ul className="text-sm text-red-800 dark:text-red-200 space-y-1">
<li> </li>
<li> </li>
<li> </li>
</ul>
</div>
</div>
</div>
</Card>
{/* 多因子综合评价功能说明 */}
<Card className="p-4 bg-blue-50 dark:bg-blue-950 border-blue-200 dark:border-blue-800">
<div className="flex items-start gap-2">
<Zap className="w-5 h-5 text-blue-600 dark:text-blue-400 flex-shrink-0 mt-0.5" />
<div className="text-sm text-blue-800 dark:text-blue-200">
<p className="mb-2"></p>
<ul className="space-y-1 text-xs">
<li> <strong></strong>: pH值7</li>
<li> <strong>(AHP)</strong>: = Σ( × )</li>
<li> <strong></strong>: 0-10085-100</li>
<li> <strong></strong>: </li>
<li> <strong></strong>: 0-100</li>
<li> <strong></strong>: 8060-79&lt;60</li>
<li> <strong></strong>: </li>
<li> <strong></strong>: </li>
</ul>
</div>
</div>
</Card>
<Card className="p-4 bg-card">
<div className="flex items-center gap-4">