子仓库提交
This commit is contained in:
@@ -0,0 +1,187 @@
|
||||
/**
|
||||
* filekorolheader: 地块决策分布地图组件 - 地理位置决策可视化
|
||||
* 功能:地块标记、状态可视化、悬浮详情、图例说明
|
||||
* 路径:/ai-crop-model/support/dashboard/components/DecisionMap
|
||||
* 规范:遵循crop-x/docs/开发项目规范.md,使用shadcn语义化样式
|
||||
*/
|
||||
import { Card } from '@/components/ui/card';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { MapPin, Map as MapIcon } from 'lucide-react';
|
||||
import { FieldDecisionInfo } from './aiDecisionDashboardReducer';
|
||||
|
||||
interface DecisionMapProps {
|
||||
fieldDecisions: FieldDecisionInfo[];
|
||||
}
|
||||
|
||||
export function DecisionMap({ fieldDecisions }: DecisionMapProps) {
|
||||
return (
|
||||
<Card className="p-6 bg-card">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h3>地块决策分布地图</h3>
|
||||
<Badge variant="outline" className="font-light">
|
||||
<MapIcon className="w-3 h-3 mr-1" />
|
||||
{fieldDecisions.length}个地块
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
{/* 模拟地图区域 */}
|
||||
<div className="relative h-96 bg-gradient-to-br from-green-50 dark:from-green-950 to-blue-50 dark:to-blue-950 rounded-lg border-2 border-green-200 dark:border-green-800 overflow-hidden">
|
||||
{/* 地图背景 */}
|
||||
<div className="absolute inset-0 opacity-20">
|
||||
<div className="w-full h-full" style={{
|
||||
backgroundImage: 'repeating-linear-gradient(0deg, #10b981 0px, #10b981 1px, transparent 1px, transparent 20px), repeating-linear-gradient(90deg, #10b981 0px, #10b981 1px, transparent 1px, transparent 20px)',
|
||||
}}></div>
|
||||
</div>
|
||||
|
||||
{/* 地块标记点 */}
|
||||
{fieldDecisions.map((field, index) => {
|
||||
// 计算标记点位置(模拟分布)
|
||||
const positions = [
|
||||
{ top: '15%', left: '20%' },
|
||||
{ top: '25%', left: '65%' },
|
||||
{ top: '45%', left: '30%' },
|
||||
{ top: '55%', left: '75%' },
|
||||
{ top: '70%', left: '45%' },
|
||||
{ top: '35%', left: '85%' },
|
||||
{ top: '80%', left: '25%' },
|
||||
];
|
||||
const position = positions[index] || { top: '50%', left: '50%' };
|
||||
|
||||
return (
|
||||
<div
|
||||
key={field.fieldId}
|
||||
className="absolute transform -translate-x-1/2 -translate-y-1/2 group cursor-pointer"
|
||||
style={position}
|
||||
>
|
||||
{/* 标记点 */}
|
||||
<div className={`w-10 h-10 rounded-full flex items-center justify-center shadow-lg transition-transform hover:scale-125 ${
|
||||
field.urgentCount > 0 ? 'bg-red-500 dark:bg-red-600' :
|
||||
field.generatedCount > 0 ? 'bg-blue-500 dark:bg-blue-600' :
|
||||
field.executingCount > 0 ? 'bg-purple-500 dark:bg-purple-600' :
|
||||
'bg-green-500 dark:bg-green-600'
|
||||
}`}>
|
||||
<MapPin className="w-5 h-5 text-white" />
|
||||
</div>
|
||||
|
||||
{/* 决策数量标记 */}
|
||||
{field.decisions.length > 0 && (
|
||||
<div className="absolute -top-2 -right-2 w-6 h-6 bg-yellow-500 dark:bg-yellow-600 text-white rounded-full flex items-center justify-center text-xs font-bold shadow-lg">
|
||||
{field.decisions.length}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 悬浮信息卡片 */}
|
||||
<div className="absolute top-full left-1/2 transform -translate-x-1/2 mt-2 w-64 bg-white dark:bg-gray-800 rounded-lg shadow-xl p-4 opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none z-10 border-2 border-green-200 dark:border-green-800">
|
||||
<div className="mb-2">
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<MapPin className="w-4 h-4 text-green-600 dark:text-green-400" />
|
||||
<strong>{field.fieldName}</strong>
|
||||
</div>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
{field.cropType} · {field.area}亩
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-1 text-xs">
|
||||
<div className="flex justify-between">
|
||||
<span className="text-muted-foreground">总决策:</span>
|
||||
<strong>{field.decisions.length}条</strong>
|
||||
</div>
|
||||
{field.urgentCount > 0 && (
|
||||
<div className="flex justify-between text-red-600 dark:text-red-400">
|
||||
<span>紧急:</span>
|
||||
<strong>{field.urgentCount}条</strong>
|
||||
</div>
|
||||
)}
|
||||
{field.generatedCount > 0 && (
|
||||
<div className="flex justify-between text-blue-600 dark:text-blue-400">
|
||||
<span>已生成:</span>
|
||||
<strong>{field.generatedCount}条</strong>
|
||||
</div>
|
||||
)}
|
||||
{field.executingCount > 0 && (
|
||||
<div className="flex justify-between text-purple-600 dark:text-purple-400">
|
||||
<span>执行中:</span>
|
||||
<strong>{field.executingCount}条</strong>
|
||||
</div>
|
||||
)}
|
||||
{field.completedCount > 0 && (
|
||||
<div className="flex justify-between text-green-600 dark:text-green-400">
|
||||
<span>已完成:</span>
|
||||
<strong>{field.completedCount}条</strong>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 最新决策 */}
|
||||
{field.decisions.length > 0 && (
|
||||
<div className="mt-2 pt-2 border-t border-gray-200 dark:border-gray-700">
|
||||
<div className="text-xs text-muted-foreground mb-1">最新决策:</div>
|
||||
<div className="text-xs font-medium line-clamp-2">
|
||||
{field.decisions[0].title}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
||||
{/* 图例 */}
|
||||
<div className="absolute bottom-4 right-4 bg-white/95 dark:bg-gray-800/95 rounded-lg shadow-lg p-3 border border-gray-200 dark:border-gray-700">
|
||||
<div className="text-xs font-medium mb-2">状态图例</div>
|
||||
<div className="space-y-1.5 text-xs">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-3 h-3 rounded-full bg-red-500 dark:bg-red-600"></div>
|
||||
<span>有紧急决策</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-3 h-3 rounded-full bg-blue-500 dark:bg-blue-600"></div>
|
||||
<span>有待执行决策</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-3 h-3 rounded-full bg-purple-500 dark:bg-purple-600"></div>
|
||||
<span>有执行中决策</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-3 h-3 rounded-full bg-green-500 dark:bg-green-600"></div>
|
||||
<span>全部完成</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 地块列表 */}
|
||||
<div className="mt-4 space-y-2 max-h-48 overflow-y-auto">
|
||||
{fieldDecisions.map((field) => (
|
||||
<div key={field.fieldId} className="flex items-center justify-between p-2 bg-muted hover:bg-accent transition-colors rounded">
|
||||
<div className="flex items-center gap-2">
|
||||
<MapPin className={`w-4 h-4 ${
|
||||
field.urgentCount > 0 ? 'text-red-500 dark:text-red-400' :
|
||||
field.generatedCount > 0 ? 'text-blue-500 dark:text-blue-400' :
|
||||
field.executingCount > 0 ? 'text-purple-500 dark:text-purple-400' :
|
||||
'text-green-500 dark:text-green-400'
|
||||
}`} />
|
||||
<div>
|
||||
<div className="text-sm font-medium">{field.fieldName}</div>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
{field.cropType} · {field.area}亩
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Badge variant="outline" className="text-xs font-light">
|
||||
{field.decisions.length}条决策
|
||||
</Badge>
|
||||
{field.urgentCount > 0 && (
|
||||
<Badge variant="outline" className="text-xs font-light bg-red-50 dark:bg-red-950 text-red-600 dark:text-red-400 border-red-200 dark:border-red-800">
|
||||
{field.urgentCount}紧急
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user