Files
smart-crop-ui/crop-x/temp_file.tsx

416 lines
17 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 多因子综合评价组件
* 提供地块适宜性评价、权重配置、批量分析和作物推荐功能
*/
'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">