diff --git a/crop-x/next-env.d.ts b/crop-x/next-env.d.ts index c4b7818..9edff1c 100644 --- a/crop-x/next-env.d.ts +++ b/crop-x/next-env.d.ts @@ -1,6 +1,6 @@ /// /// -import "./.next/dev/types/routes.d.ts"; +import "./.next/types/routes.d.ts"; // NOTE: This file should not be edited // see https://nextjs.org/docs/app/api-reference/config/typescript for more information. diff --git a/crop-x/src/app/(app)/ai-crop-model/support/dashboard/components/DecisionMap.tsx b/crop-x/src/app/(app)/ai-crop-model/support/dashboard/components/DecisionMap.tsx new file mode 100644 index 0000000..f480fd5 --- /dev/null +++ b/crop-x/src/app/(app)/ai-crop-model/support/dashboard/components/DecisionMap.tsx @@ -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 ( + +
+

地块决策分布地图

+ + + {fieldDecisions.length}个地块 + +
+ + {/* 模拟地图区域 */} +
+ {/* 地图背景 */} +
+
+
+ + {/* 地块标记点 */} + {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 ( +
+ {/* 标记点 */} +
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' + }`}> + +
+ + {/* 决策数量标记 */} + {field.decisions.length > 0 && ( +
+ {field.decisions.length} +
+ )} + + {/* 悬浮信息卡片 */} +
+
+
+ + {field.fieldName} +
+
+ {field.cropType} · {field.area}亩 +
+
+ +
+
+ 总决策: + {field.decisions.length}条 +
+ {field.urgentCount > 0 && ( +
+ 紧急: + {field.urgentCount}条 +
+ )} + {field.generatedCount > 0 && ( +
+ 已生成: + {field.generatedCount}条 +
+ )} + {field.executingCount > 0 && ( +
+ 执行中: + {field.executingCount}条 +
+ )} + {field.completedCount > 0 && ( +
+ 已完成: + {field.completedCount}条 +
+ )} +
+ + {/* 最新决策 */} + {field.decisions.length > 0 && ( +
+
最新决策:
+
+ {field.decisions[0].title} +
+
+ )} +
+
+ ); + })} + + {/* 图例 */} +
+
状态图例
+
+
+
+ 有紧急决策 +
+
+
+ 有待执行决策 +
+
+
+ 有执行中决策 +
+
+
+ 全部完成 +
+
+
+
+ + {/* 地块列表 */} +
+ {fieldDecisions.map((field) => ( +
+
+ 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' + }`} /> +
+
{field.fieldName}
+
+ {field.cropType} · {field.area}亩 +
+
+
+
+ + {field.decisions.length}条决策 + + {field.urgentCount > 0 && ( + + {field.urgentCount}紧急 + + )} +
+
+ ))} +
+
+ ); +} \ No newline at end of file diff --git a/crop-x/src/app/(app)/ai-crop-model/support/dashboard/components/DecisionTrends.tsx b/crop-x/src/app/(app)/ai-crop-model/support/dashboard/components/DecisionTrends.tsx new file mode 100644 index 0000000..f93c3c2 --- /dev/null +++ b/crop-x/src/app/(app)/ai-crop-model/support/dashboard/components/DecisionTrends.tsx @@ -0,0 +1,86 @@ +/** + * filekorolheader: 决策趋势图组件 - 决策生成与完成趋势分析 + * 功能:趋势线图、数据可视化、时间序列展示 + * 路径:/ai-crop-model/support/dashboard/components/DecisionTrends + * 规范:遵循crop-x/docs/开发项目规范.md,使用shadcn语义化样式 + */ +import { Card } from '@/components/ui/card'; +import { Badge } from '@/components/ui/badge'; +import { Calendar } from 'lucide-react'; +import { + LineChart, + Line, + XAxis, + YAxis, + CartesianGrid, + Tooltip as RechartsTooltip, + Legend, + ResponsiveContainer, +} from 'recharts'; +import { TrendData } from './aiDecisionDashboardReducer'; + +interface DecisionTrendsProps { + trendData: TrendData[]; +} + +export function DecisionTrends({ trendData }: DecisionTrendsProps) { + return ( + +
+

决策生成与完成趋势

+ + + 近7天 + +
+ +
+ + + + + + + + + + + +
+
+ ); +} \ No newline at end of file diff --git a/crop-x/src/app/(app)/ai-crop-model/support/dashboard/components/LatestDecisions.tsx b/crop-x/src/app/(app)/ai-crop-model/support/dashboard/components/LatestDecisions.tsx new file mode 100644 index 0000000..7d81950 --- /dev/null +++ b/crop-x/src/app/(app)/ai-crop-model/support/dashboard/components/LatestDecisions.tsx @@ -0,0 +1,157 @@ +/** + * filekorolheader: 最新决策建议组件 - 最新决策展示与详情 + * 功能:决策列表、状态标识、优先级显示、详情展示 + * 路径:/ai-crop-model/support/dashboard/components/LatestDecisions + * 规范:遵循crop-x/docs/开发项目规范.md,使用shadcn语义化样式 + */ +import { Card } from '@/components/ui/card'; +import { Badge } from '@/components/ui/badge'; +import { + Info, + Sparkles, + Droplets, + Bug, + Sprout, + Package, + CloudRain, + Layers, + Activity, + CheckCircle, + Clock, + MapPin, + Zap, + CircleDot, +} from 'lucide-react'; +import { DecisionRecord } from './aiDecisionDashboardReducer'; + +interface LatestDecisionsProps { + latestDecisions: DecisionRecord[]; +} + +export function LatestDecisions({ latestDecisions }: LatestDecisionsProps) { + const getTypeBadge = (type: string) => { + const config = { + irrigation: { label: '灌溉', icon: Droplets, className: 'bg-blue-50 dark:bg-blue-950 text-blue-600 dark:text-blue-400 border-blue-200 dark:border-blue-800' }, + fertilizer: { label: '施肥', icon: Sprout, className: 'bg-green-50 dark:bg-green-950 text-green-600 dark:text-green-400 border-green-200 dark:border-green-800' }, + pesticide: { label: '打药', icon: Bug, className: 'bg-yellow-50 dark:bg-yellow-950 text-yellow-600 dark:text-yellow-400 border-yellow-200 dark:border-yellow-800' }, + harvest: { label: '收获', icon: Package, className: 'bg-purple-50 dark:bg-purple-950 text-purple-600 dark:text-purple-400 border-purple-200 dark:border-purple-800' }, + soil: { label: '土壤', icon: Layers, className: 'bg-orange-50 dark:bg-orange-950 text-orange-600 dark:text-orange-400 border-orange-200 dark:border-orange-800' }, + weather: { label: '气象', icon: CloudRain, className: 'bg-cyan-50 dark:bg-cyan-950 text-cyan-600 dark:text-cyan-400 border-cyan-200 dark:border-cyan-800' }, + }; + const { label, icon: Icon, className } = config[type as keyof typeof config]; + return ( + + + {label} + + ); + }; + + const getPriorityBadge = (priority: string) => { + const config = { + urgent: { label: '紧急', className: 'bg-red-50 dark:bg-red-950 text-red-600 dark:text-red-400 border-red-200 dark:border-red-800' }, + high: { label: '高', className: 'bg-orange-50 dark:bg-orange-950 text-orange-600 dark:text-orange-400 border-orange-200 dark:border-orange-800' }, + medium: { label: '中', className: 'bg-yellow-50 dark:bg-yellow-950 text-yellow-600 dark:text-yellow-400 border-yellow-200 dark:border-yellow-800' }, + low: { label: '低', className: 'bg-gray-50 dark:bg-gray-950 text-gray-600 dark:text-gray-400 border-gray-200 dark:border-gray-800' }, + }; + const { label, className } = config[priority as keyof typeof config]; + return {label}; + }; + + const getStatusBadge = (status: string) => { + const config = { + generated: { label: '已生成', icon: Sparkles, className: 'bg-blue-50 dark:bg-blue-950 text-blue-600 dark:text-blue-400 border-blue-200 dark:border-blue-800' }, + executing: { label: '执行中', icon: Activity, className: 'bg-purple-50 dark:bg-purple-950 text-purple-600 dark:text-purple-400 border-purple-200 dark:border-purple-800' }, + completed: { label: '已完成', icon: CheckCircle, className: 'bg-green-50 dark:bg-green-950 text-green-600 dark:text-green-400 border-green-200 dark:border-green-800' }, + expired: { label: '已过期', icon: Clock, className: 'bg-gray-50 dark:bg-gray-950 text-gray-600 dark:text-gray-400 border-gray-200 dark:border-gray-800' }, + }; + const { label, icon: Icon, className } = config[status as keyof typeof config]; + return ( + + + {label} + + ); + }; + + return ( + +
+

最新决策建议

+ + + 最新5条 + +
+ + {latestDecisions.length === 0 ? ( +
+ +
暂无决策建议
+
+ ) : ( +
+ {latestDecisions.map((decision, index) => ( + +
+
+
+ + + #{index + 1} + + {getTypeBadge(decision.type)} + {getPriorityBadge(decision.priority)} + {getStatusBadge(decision.status)} +
+ +

{decision.title}

+ +

+ {decision.description} +

+ +
+
+
地块信息
+
+ + {decision.fieldName} +
+
+
+
作物类型
+
+ + {decision.cropType} +
+
+
+
置信度
+
+ + {(decision.confidence * 100).toFixed(0)}% +
+
+
+
生成时间
+
+ + {decision.createdAt} +
+
+
+
+
+
+ ))} +
+ )} +
+ ); +} \ No newline at end of file diff --git a/crop-x/src/app/(app)/ai-crop-model/support/dashboard/components/StatisticsCards.tsx b/crop-x/src/app/(app)/ai-crop-model/support/dashboard/components/StatisticsCards.tsx new file mode 100644 index 0000000..a1fe25b --- /dev/null +++ b/crop-x/src/app/(app)/ai-crop-model/support/dashboard/components/StatisticsCards.tsx @@ -0,0 +1,101 @@ +/** + * filekorolheader: 统计卡片组件 - AI决策统计数据展示 + * 功能:总决策数、状态分布、优先级统计、置信度展示 + * 路径:/ai-crop-model/support/dashboard/components/StatisticsCards + * 规范:遵循crop-x/docs/开发项目规范.md,使用shadcn语义化样式 + */ +import { Card } from '@/components/ui/card'; +import { Badge } from '@/components/ui/badge'; +import { + LayoutDashboard, + Sparkles, + Activity, + CheckCircle, + AlertCircle, + Zap, +} from 'lucide-react'; +import { DecisionStats } from './aiDecisionDashboardReducer'; + +interface StatisticsCardsProps { + stats: DecisionStats; +} + +export function StatisticsCards({ stats }: StatisticsCardsProps) { + return ( +
+ {/* 总决策数 */} + +
+
总决策数
+ +
+
{stats.total}
+
+ 全部决策 +
+
+ + {/* 已生成 */} + +
+
已生成
+ +
+
{stats.generated}
+
+ 待执行 +
+
+ + {/* 执行中 */} + +
+
执行中
+ +
+
{stats.executing}
+
+ 正在执行 +
+
+ + {/* 已完成 */} + +
+
已完成
+ +
+
{stats.completed}
+
+ 执行完成 +
+
+ + {/* 紧急决策 */} + +
+
紧急决策
+ +
+
{stats.urgent}
+
+ 需优先处理 +
+
+ + {/* 平均置信度 */} + +
+
平均置信度
+ +
+
+ {(stats.avgConfidence * 100).toFixed(0)}% +
+
+ 决策准确性 +
+
+
+ ); +} \ No newline at end of file diff --git a/crop-x/src/app/(app)/ai-crop-model/support/dashboard/components/aiDecisionDashboardReducer.tsx b/crop-x/src/app/(app)/ai-crop-model/support/dashboard/components/aiDecisionDashboardReducer.tsx new file mode 100644 index 0000000..8f9bc17 --- /dev/null +++ b/crop-x/src/app/(app)/ai-crop-model/support/dashboard/components/aiDecisionDashboardReducer.tsx @@ -0,0 +1,446 @@ +/** + * filekorolheader: AI决策看板状态管理 - 决策数据状态管理核心 + * 功能:决策数据管理、统计计算、状态更新、本地存储同步 + * 路径:/ai-crop-model/support/dashboard/components/aiDecisionDashboardReducer + * 规范:遵循crop-x/docs/开发项目规范.md,使用useReducer状态管理模式 + */ + +// 决策类型 +export type DecisionType = 'irrigation' | 'fertilizer' | 'pesticide' | 'harvest' | 'soil' | 'weather'; + +// 决策状态 +export type DecisionStatus = 'generated' | 'executing' | 'completed' | 'expired'; + +// 决策优先级 +export type DecisionPriority = 'urgent' | 'high' | 'medium' | 'low'; + +// 决策记录 +export interface DecisionRecord { + id: string; + type: DecisionType; + title: string; + description: string; + status: DecisionStatus; + priority: DecisionPriority; + fieldId: string; + fieldName: string; + fieldArea: number; + cropType: string; + confidence: number; + createdAt: string; + updatedAt: string; + dueDate: string; + location: { + lat: number; + lng: number; + }; + modelVersion?: string; + ruleCount?: number; + executedAt?: string; + executedBy?: string; + completedAt?: string; +} + +// 地块决策信息 +export interface FieldDecisionInfo { + fieldId: string; + fieldName: string; + location: { + lat: number; + lng: number; + }; + area: number; + cropType: string; + decisions: DecisionRecord[]; + urgentCount: number; + generatedCount: number; + executingCount: number; + completedCount: number; +} + +// 统计数据 +export interface DecisionStats { + total: number; + generated: number; + executing: number; + completed: number; + urgent: number; + avgConfidence: number; +} + +// 趋势数据 +export interface TrendData { + date: string; + generated: number; + completed: number; +} + +// 状态接口 +export interface AIDecisionDashboardState { + decisions: DecisionRecord[]; + fieldDecisions: FieldDecisionInfo[]; + stats: DecisionStats; + trendData: TrendData[]; + latestDecisions: DecisionRecord[]; + lastUpdated: string; +} + +// Action类型 +export type AIDecisionDashboardAction = + | { type: 'SET_DECISIONS'; payload: DecisionRecord[] } + | { type: 'ADD_DECISION'; payload: DecisionRecord } + | { type: 'UPDATE_DECISION'; payload: { id: string; updates: Partial } } + | { type: 'DELETE_DECISION'; payload: string } + | { type: 'REFRESH_DATA' } + | { type: 'LOAD_FROM_STORAGE'; payload: Partial }; + +// 初始决策数据 +const initialDecisions: DecisionRecord[] = [ + { + id: 'dec_001', + type: 'irrigation', + title: '1号大棚番茄开花期灌溉', + description: '土壤湿度35%,低于最佳湿度,建议立即灌溉120升', + status: 'generated', + priority: 'urgent', + fieldId: 'field_001', + fieldName: '1号大棚', + fieldArea: 2.5, + cropType: '番茄', + confidence: 0.89, + createdAt: '2024-10-23 14:30', + updatedAt: '2024-10-23 14:30', + dueDate: '2024-10-23 18:00', + location: { lat: 39.9042, lng: 116.4074 }, + modelVersion: 'v2.1.3', + ruleCount: 2, + }, + { + id: 'dec_002', + type: 'pesticide', + title: '2号大棚早疫病防治', + description: '检测到早疫病轻度感染,建议使用生物防治剂', + status: 'executing', + priority: 'high', + fieldId: 'field_002', + fieldName: '2号大棚', + fieldArea: 3.0, + cropType: '番茄', + confidence: 0.87, + createdAt: '2024-10-23 10:15', + updatedAt: '2024-10-23 11:00', + dueDate: '2024-10-23 17:00', + executedAt: '2024-10-23 11:00', + executedBy: '王五', + location: { lat: 39.9142, lng: 116.4174 }, + modelVersion: 'v3.2.1', + ruleCount: 1, + }, + { + id: 'dec_003', + type: 'fertilizer', + title: '3号地块小麦追肥', + description: '结果期营养需求增加,建议施用复合肥', + status: 'executing', + priority: 'medium', + fieldId: 'field_003', + fieldName: '3号地块', + fieldArea: 5.0, + cropType: '小麦', + confidence: 0.92, + createdAt: '2024-10-23 09:30', + updatedAt: '2024-10-23 10:00', + dueDate: '2024-10-24 12:00', + executedAt: '2024-10-23 10:00', + executedBy: '李四', + location: { lat: 39.8942, lng: 116.3974 }, + modelVersion: 'v2.0.5', + ruleCount: 3, + }, + { + id: 'dec_004', + type: 'soil', + title: '4号地块土壤改良', + description: 'pH值偏低,建议施用石灰调节土壤酸碱度', + status: 'completed', + priority: 'low', + fieldId: 'field_004', + fieldName: '4号地块', + fieldArea: 4.2, + cropType: '玉米', + confidence: 0.85, + createdAt: '2024-10-22 15:00', + updatedAt: '2024-10-22 16:30', + dueDate: '2024-10-25 12:00', + executedAt: '2024-10-22 15:30', + executedBy: '张三', + completedAt: '2024-10-22 16:30', + location: { lat: 39.8842, lng: 116.3874 }, + modelVersion: 'v1.8.2', + ruleCount: 1, + }, + { + id: 'dec_005', + type: 'weather', + title: '5号大棚温度调控', + description: '预计晚间温度降至12℃,建议提前加温', + status: 'generated', + priority: 'high', + fieldId: 'field_005', + fieldName: '5号大棚', + fieldArea: 2.8, + cropType: '黄瓜', + confidence: 0.91, + createdAt: '2024-10-23 16:00', + updatedAt: '2024-10-23 16:00', + dueDate: '2024-10-23 20:00', + location: { lat: 39.9242, lng: 116.4274 }, + modelVersion: 'v2.3.1', + ruleCount: 2, + }, + { + id: 'dec_006', + type: 'harvest', + title: '6号地块水稻收获', + description: '水稻已达成熟期,建议3天内完成收获', + status: 'generated', + priority: 'urgent', + fieldId: 'field_006', + fieldName: '6号地块', + fieldArea: 8.0, + cropType: '水稻', + confidence: 0.94, + createdAt: '2024-10-23 08:00', + updatedAt: '2024-10-23 08:00', + dueDate: '2024-10-26 18:00', + location: { lat: 39.9342, lng: 116.3874 }, + modelVersion: 'v2.5.0', + ruleCount: 3, + }, + { + id: 'dec_007', + type: 'irrigation', + title: '7号大棚茄子补水', + description: '连续3天无降雨,土壤湿度偏低', + status: 'completed', + priority: 'medium', + fieldId: 'field_007', + fieldName: '7号大棚', + fieldArea: 2.2, + cropType: '茄子', + confidence: 0.86, + createdAt: '2024-10-22 18:00', + updatedAt: '2024-10-23 09:00', + dueDate: '2024-10-23 12:00', + executedAt: '2024-10-22 19:00', + executedBy: '赵六', + completedAt: '2024-10-23 09:00', + location: { lat: 39.8742, lng: 116.4174 }, + modelVersion: 'v2.1.3', + ruleCount: 2, + }, +]; + +// 初始趋势数据 +const initialTrendData: TrendData[] = [ + { date: '10-17', generated: 8, completed: 5 }, + { date: '10-18', generated: 12, completed: 8 }, + { date: '10-19', generated: 10, completed: 9 }, + { date: '10-20', generated: 15, completed: 11 }, + { date: '10-21', generated: 13, completed: 10 }, + { date: '10-22', generated: 11, completed: 9 }, + { date: '10-23', generated: 14, completed: 7 }, +]; + +// 计算统计数据 +const calculateStats = (decisions: DecisionRecord[]): DecisionStats => { + return { + total: decisions.length, + generated: decisions.filter(d => d.status === 'generated').length, + executing: decisions.filter(d => d.status === 'executing').length, + completed: decisions.filter(d => d.status === 'completed').length, + urgent: decisions.filter(d => d.priority === 'urgent').length, + avgConfidence: decisions.reduce((sum, d) => sum + d.confidence, 0) / decisions.length, + }; +}; + +// 计算地块决策信息 +const calculateFieldDecisions = (decisions: DecisionRecord[]): FieldDecisionInfo[] => { + const fieldDecisionMap = new Map(); + + decisions.forEach(decision => { + if (!fieldDecisionMap.has(decision.fieldId)) { + fieldDecisionMap.set(decision.fieldId, { + fieldId: decision.fieldId, + fieldName: decision.fieldName, + location: decision.location, + area: decision.fieldArea, + cropType: decision.cropType, + decisions: [], + urgentCount: 0, + generatedCount: 0, + executingCount: 0, + completedCount: 0, + }); + } + const fieldInfo = fieldDecisionMap.get(decision.fieldId)!; + fieldInfo.decisions.push(decision); + if (decision.priority === 'urgent') fieldInfo.urgentCount++; + if (decision.status === 'generated') fieldInfo.generatedCount++; + if (decision.status === 'executing') fieldInfo.executingCount++; + if (decision.status === 'completed') fieldInfo.completedCount++; + }); + + return Array.from(fieldDecisionMap.values()); +}; + +// 计算最新决策 +const calculateLatestDecisions = (decisions: DecisionRecord[]): DecisionRecord[] => { + return [...decisions] + .sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()) + .slice(0, 5); +}; + +// 保存到本地存储 +const saveToStorage = (state: AIDecisionDashboardState) => { + try { + localStorage.setItem('ai-decision-dashboard', JSON.stringify({ + decisions: state.decisions, + lastUpdated: state.lastUpdated, + })); + } catch (error) { + console.warn('Failed to save to localStorage:', error); + } +}; + +// 从本地存储加载 +const loadFromStorage = () => { + try { + const stored = localStorage.getItem('ai-decision-dashboard'); + if (stored) { + const data = JSON.parse(stored); + return { + decisions: data.decisions || initialDecisions, + lastUpdated: data.lastUpdated || new Date().toISOString(), + }; + } + } catch (error) { + console.warn('Failed to load from localStorage:', error); + } + return null; +}; + +// 计算派生状态 +const calculateDerivedState = (decisions: DecisionRecord[]) => { + const stats = calculateStats(decisions); + const fieldDecisions = calculateFieldDecisions(decisions); + const latestDecisions = calculateLatestDecisions(decisions); + + return { + stats, + fieldDecisions, + latestDecisions, + trendData: initialTrendData, + }; +}; + +// 初始状态 +export const initialState: AIDecisionDashboardState = (() => { + const stored = loadFromStorage(); + const decisions = stored?.decisions || initialDecisions; + const derivedState = calculateDerivedState(decisions); + + return { + decisions, + ...derivedState, + lastUpdated: stored?.lastUpdated || new Date().toISOString(), + }; +})(); + +// Reducer +export function AIDecisionDashboardReducer( + state: AIDecisionDashboardState, + action: AIDecisionDashboardAction +): AIDecisionDashboardState { + switch (action.type) { + case 'SET_DECISIONS': { + const derivedState = calculateDerivedState(action.payload); + const newState = { + ...state, + decisions: action.payload, + ...derivedState, + lastUpdated: new Date().toISOString(), + }; + saveToStorage(newState); + return newState; + } + + case 'ADD_DECISION': { + const newDecisions = [...state.decisions, action.payload]; + const derivedState = calculateDerivedState(newDecisions); + const newState = { + ...state, + decisions: newDecisions, + ...derivedState, + lastUpdated: new Date().toISOString(), + }; + saveToStorage(newState); + return newState; + } + + case 'UPDATE_DECISION': { + const newDecisions = state.decisions.map(decision => + decision.id === action.payload.id + ? { ...decision, ...action.payload.updates } + : decision + ); + const derivedState = calculateDerivedState(newDecisions); + const newState = { + ...state, + decisions: newDecisions, + ...derivedState, + lastUpdated: new Date().toISOString(), + }; + saveToStorage(newState); + return newState; + } + + case 'DELETE_DECISION': { + const newDecisions = state.decisions.filter(decision => decision.id !== action.payload); + const derivedState = calculateDerivedState(newDecisions); + const newState = { + ...state, + decisions: newDecisions, + ...derivedState, + lastUpdated: new Date().toISOString(), + }; + saveToStorage(newState); + return newState; + } + + case 'REFRESH_DATA': { + const derivedState = calculateDerivedState(state.decisions); + const newState = { + ...state, + ...derivedState, + lastUpdated: new Date().toISOString(), + }; + saveToStorage(newState); + return newState; + } + + case 'LOAD_FROM_STORAGE': { + const decisions = action.payload.decisions || state.decisions; + const derivedState = calculateDerivedState(decisions); + return { + ...state, + decisions, + ...derivedState, + lastUpdated: action.payload.lastUpdated || state.lastUpdated, + }; + } + + default: + return state; + } +} \ No newline at end of file diff --git a/crop-x/src/app/(app)/ai-crop-model/support/dashboard/page.tsx b/crop-x/src/app/(app)/ai-crop-model/support/dashboard/page.tsx index 6d1bd29..8c60ccc 100644 --- a/crop-x/src/app/(app)/ai-crop-model/support/dashboard/page.tsx +++ b/crop-x/src/app/(app)/ai-crop-model/support/dashboard/page.tsx @@ -1,18 +1,104 @@ +/** + * filekorolheader: AI决策看板 - 智能决策生成与执行监控中心 + * 功能:决策统计展示、地图可视化、趋势分析、决策详情查看、状态管理 + * 路径:/ai-crop-model/support/dashboard + * 规范:遵循crop-x/docs/开发项目规范.md,使用useReducer状态管理,shadcn语义化样式 + */ 'use client'; +import { useReducer } from 'react'; import { Card } from '@/components/ui/card'; +import { Button } from '@/components/ui/button'; +import { Badge } from '@/components/ui/badge'; +import { toast } from 'sonner'; +import { + LayoutDashboard, + RefreshCw, + CheckCircle, + AlertCircle, + TrendingUp, + Droplets, + Bug, + Sprout, + Package, + CloudRain, + Layers, + Activity, + Clock, + Info, + Sparkles, + Calendar, + MapPin, + Zap, + CircleDot, + Map as MapIcon, +} from 'lucide-react'; +import { AIDecisionDashboardReducer, initialState, AIDecisionDashboardState, AIDecisionDashboardAction } from './components/aiDecisionDashboardReducer'; +import { StatisticsCards } from './components/StatisticsCards'; +import { DecisionMap } from './components/DecisionMap'; +import { DecisionTrends } from './components/DecisionTrends'; +import { LatestDecisions } from './components/LatestDecisions'; export default function DashboardPage() { + const [state, dispatch] = useReducer(AIDecisionDashboardReducer, initialState); + + const handleRefresh = () => { + dispatch({ type: 'REFRESH_DATA' }); + toast.success('数据已刷新'); + }; + return (
- -

决策仪表盘

-
-

- 页面路径: /ai-crop-model/support/dashboard -

+ {/* 页面标题 */} + +
+
+ +
+

AI决策看板

+

+ 实时展示AI决策生成与执行统计,监控决策效果和趋势 +

+
+ + + 智能生成 + + + + 实时监控 + + + + 趋势分析 + + + + 地图可视化 + +
+
+
+
+ +
+ + {/* 核心统计卡片 */} + + + {/* 地图可视化 + 决策趋势图 */} +
+ + +
+ + {/* 最新决策建议 */} +
); } \ No newline at end of file diff --git a/crop-x/src/app/(app)/ai-crop-model/support/detail/page.tsx b/crop-x/src/app/(app)/ai-crop-model/support/detail/page.tsx index 2aa962c..ed5ea44 100644 --- a/crop-x/src/app/(app)/ai-crop-model/support/detail/page.tsx +++ b/crop-x/src/app/(app)/ai-crop-model/support/detail/page.tsx @@ -1,18 +1,1684 @@ +/** + * filekorolheader: 决策详情页面 - AI决策完整信息展示与追溯管理 + * 功能:决策列表展示、筛选查询、详情查看、数据快照、模型分析、规则匹配、执行结果追溯 + * 路径:/ai-crop-model/support/detail + * 规范:遵循crop-x/docs/开发项目规范.md,使用useReducer状态管理,shadcn语义化样式,完整依赖引用实现 + */ 'use client'; +import { useReducer, useEffect, useState } from 'react'; import { Card } from '@/components/ui/card'; +import { Button } from '@/components/ui/button'; +import { Badge } from '@/components/ui/badge'; +import { Input } from '@/components/ui/input'; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; +import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter } from '@/components/ui/dialog'; +import { Progress } from '@/components/ui/progress'; +import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; +import { toast } from 'sonner'; +import { + FileText, + Database, + Brain, + BookOpen, + CheckCircle, + AlertCircle, + Clock, + Download, + ArrowRight, + Info, + Zap, + Target, + Activity, + Settings, + ChevronRight, + ChevronDown, + Calendar, + User, + MapPin, + Leaf, + Eye, + Search, + RefreshCw, + Box, + Sparkles, + Filter, + Power, + Timer, + Droplets, + Bug, + Sprout, + Package, + Layers, + CloudRain, +} from 'lucide-react'; + +// ==================== 类型定义 ==================== + +// 决策来源 +type DecisionSource = 'auto' | 'manual'; + +// 决策状态 +type DecisionStatus = 'generated' | 'executing' | 'completed' | 'expired'; + +// 决策级别 +type DecisionLevel = 'critical' | 'important' | 'normal' | 'suggestion'; + +// 决策类型 +type DecisionType = 'irrigation' | 'fertilizer' | 'pesticide' | 'harvest' | 'soil' | 'weather'; + +// 数据源 +interface DataSource { + source: string; + timestamp: string; + type: string; + values: { + [key: string]: any; + }; +} + +// 模型分析 +interface ModelAnalysis { + modelId: string; + modelName: string; + modelVersion: string; + provider: string; + callTime: number; + rawOutput: { + [key: string]: any; + }; + confidence: number; + factors: { + [key: string]: number; + }; +} + +// 规则匹配 +interface RuleMatch { + ruleId: string; + ruleName: string; + category: string; + priority: number; + matched: boolean; + confidence: number; + conditions: { + expression: string; + value: any; + expected: any; + result: boolean; + }[]; + calculation?: { + formula: string; + variables: { + [key: string]: any; + }; + steps: string[]; + result: any; + }; + actions: string[]; +} + +// 决策列表项 +interface DecisionListItem { + id: string; + decisionNo: string; + title: string; + type: DecisionType; + source: DecisionSource; + status: DecisionStatus; + level: DecisionLevel; + fieldName: string; + cropType: string; + confidence: number; + createdAt: string; + createdBy: string; + description?: string; + triggerCondition?: { + device: string; + parameter: string; + operator: string; + value: string; + }; + execution?: { + device: string; + action: 'open' | 'close'; + duration: number; + }; +} + +// 决策详情(自动生成) +interface AutoDecisionDetail { + id: string; + decisionNo: string; + title: string; + type: DecisionType; + source: 'auto'; + status: DecisionStatus; + level: DecisionLevel; + createdAt: string; + updatedAt: string; + createdBy: string; + + fieldInfo: { + fieldId: string; + fieldName: string; + area: number; + cropType: string; + growthStage: string; + location: string; + }; + + dataSnapshot: DataSource[]; + modelAnalysis: ModelAnalysis[]; + rulesMatched: RuleMatch[]; + + triggerCondition: { + device: string; + parameter: string; + operator: string; + value: string; + }; + execution: { + device: string; + action: 'open' | 'close'; + duration: number; + }; + + finalDecision: { + recommendation: string; + parameters: { + [key: string]: any; + }; + expectedEffect: { + [key: string]: string; + }; + riskAssessment: string; + alternatives: string[]; + }; + + executionResult?: { + startTime: string; + endTime: string; + actualEffect: { + [key: string]: string; + }; + deviation: string; + evaluation: string; + }; +} + +// 决策详情(手动添加) +interface ManualDecisionDetail { + id: string; + decisionNo: string; + title: string; + type: DecisionType; + source: 'manual'; + status: DecisionStatus; + level: DecisionLevel; + createdAt: string; + updatedAt: string; + createdBy: string; + + manualInput: { + confidence: number; + executionMode: 'manual' | 'auto'; + recommendation: string; + explanation?: string; + actionItems?: string; + }; + + triggerCondition?: { + device: string; + parameter: string; + operator: string; + value: string; + }; + + execution?: { + device: string; + action: 'open' | 'close'; + duration?: number; + }; + + executionResult?: { + startTime: string; + endTime: string; + actualEffect: { + [key: string]: string; + }; + evaluation: string; + }; +} + +// 筛选条件 +interface FilterCondition { + source: string; + type: string; + status: string; + level: string; + field: string; + cropType: string; + dateRange: string; + searchText: string; +} + +// 状态接口 +interface DecisionDetailState { + decisions: DecisionListItem[]; + autoDecisionDetail: AutoDecisionDetail | null; + manualDecisionDetail: ManualDecisionDetail | null; + filters: FilterCondition; + selectedDecision: DecisionListItem | null; + showDetailDialog: boolean; + expandedSections: Set; + loading: boolean; +} + +// Action类型 +type DecisionDetailAction = + | { type: 'SET_DECISIONS'; payload: DecisionListItem[] } + | { type: 'SET_AUTO_DETAIL'; payload: AutoDecisionDetail } + | { type: 'SET_MANUAL_DETAIL'; payload: ManualDecisionDetail } + | { type: 'SET_FILTERS'; payload: FilterCondition } + | { type: 'UPDATE_FILTER'; payload: { key: keyof FilterCondition; value: any } } + | { type: 'SET_SELECTED_DECISION'; payload: DecisionListItem | null } + | { type: 'TOGGLE_DETAIL_DIALOG' } + | { type: 'TOGGLE_SECTION'; payload: string } + | { type: 'SET_LOADING'; payload: boolean }; + +// ==================== Reducer ==================== + +const initialState: DecisionDetailState = { + decisions: [], + autoDecisionDetail: null, + manualDecisionDetail: null, + filters: { + source: 'all', + type: 'all', + status: 'all', + level: 'all', + field: 'all', + cropType: 'all', + dateRange: '7days', + searchText: '', + }, + selectedDecision: null, + showDetailDialog: false, + expandedSections: new Set(['snapshot', 'model', 'rules', 'decision']), + loading: false, +}; + +function decisionDetailReducer(state: DecisionDetailState, action: DecisionDetailAction): DecisionDetailState { + switch (action.type) { + case 'SET_DECISIONS': + return { ...state, decisions: action.payload }; + + case 'SET_AUTO_DETAIL': + return { ...state, autoDecisionDetail: action.payload }; + + case 'SET_MANUAL_DETAIL': + return { ...state, manualDecisionDetail: action.payload }; + + case 'SET_FILTERS': + return { ...state, filters: action.payload }; + + case 'UPDATE_FILTER': + return { + ...state, + filters: { ...state.filters, [action.payload.key]: action.payload.value }, + }; + + case 'SET_SELECTED_DECISION': + return { ...state, selectedDecision: action.payload }; + + case 'TOGGLE_DETAIL_DIALOG': + return { ...state, showDetailDialog: !state.showDetailDialog }; + + case 'TOGGLE_SECTION': + const newSections = new Set(state.expandedSections); + if (newSections.has(action.payload)) { + newSections.delete(action.payload); + } else { + newSections.add(action.payload); + } + return { ...state, expandedSections: newSections }; + + case 'SET_LOADING': + return { ...state, loading: action.payload }; + + default: + return state; + } +} + +// ==================== 组件实现 ==================== export default function DetailPage() { + const [state, dispatch] = useReducer(decisionDetailReducer, initialState); + + // 加载测试数据 + useEffect(() => { + loadTestData(); + }, []); + + const loadTestData = () => { + dispatch({ type: 'SET_LOADING', payload: true }); + + // 模拟决策列表数据 + const decisions: DecisionListItem[] = [ + { + id: 'dec_001', + decisionNo: 'DEC-20241023-001', + title: '1号大棚番茄开花期灌溉', + description: '土壤湿度35%,低于最佳湿度,建议立即灌溉120升', + type: 'irrigation', + source: 'auto', + status: 'completed', + level: 'important', + fieldName: '1号大棚', + cropType: '番茄', + confidence: 0.89, + createdAt: '2024-10-23 14:30', + createdBy: '系统自动生成', + triggerCondition: { + device: '土壤传感器-01', + parameter: '土壤湿度', + operator: '<', + value: '30', + }, + execution: { + device: '水肥机-01', + action: 'open', + duration: 45, + }, + }, + { + id: 'dec_002', + decisionNo: 'DEC-20241023-002', + title: '2号大棚温度控制', + description: '预计下午温度过高,需要开启通风设备降温', + type: 'weather', + source: 'manual', + status: 'executing', + level: 'normal', + fieldName: '2号大棚', + cropType: '番茄', + confidence: 0.86, + createdAt: '2024-10-23 12:15', + createdBy: '张三', + triggerCondition: { + device: '温度传感器-02', + parameter: '空气温度', + operator: '>', + value: '35', + }, + execution: { + device: '排风扇-02', + action: 'open', + duration: 20, + }, + }, + { + id: 'dec_003', + decisionNo: 'DEC-20241022-003', + title: '3号地块小麦结实期追肥', + description: '检测到土壤氮磷钾含量偏低,建议施用复合肥提升产量', + type: 'fertilizer', + source: 'auto', + status: 'completed', + level: 'important', + fieldName: '3号地块', + cropType: '小麦', + confidence: 0.92, + createdAt: '2024-10-22 09:30', + createdBy: '系统自动生成', + }, + { + id: 'dec_004', + decisionNo: 'DEC-20241021-004', + title: '4号地块土壤pH调节', + description: 'pH值偏低,建议施用石灰调节土壤酸碱度', + type: 'soil', + source: 'manual', + status: 'completed', + level: 'normal', + fieldName: '4号地块', + cropType: '玉米', + confidence: 0.85, + createdAt: '2024-10-21 15:00', + createdBy: '李四', + }, + { + id: 'dec_005', + decisionNo: 'DEC-20241023-005', + title: '5号地块水稻收获时机', + description: '水稻已达到成熟期,湿度和天气条件适宜,建议3天内完成收获', + type: 'harvest', + source: 'auto', + status: 'generated', + level: 'critical', + fieldName: '5号地块', + cropType: '水稻', + confidence: 0.94, + createdAt: '2024-10-23 08:00', + createdBy: '系统自动生成', + }, + ]; + + // 自动生成决策详情 + const autoDecisionDetail: AutoDecisionDetail = { + id: 'dec_001', + decisionNo: 'DEC-20241023-001', + title: '1号大棚番茄开花期灌溉', + type: 'irrigation', + source: 'auto', + status: 'completed', + level: 'important', + createdAt: '2024-10-23 14:30:25', + updatedAt: '2024-10-23 17:10:00', + createdBy: '系统自动生成', + + fieldInfo: { + fieldId: 'field_001', + fieldName: '1号大棚', + area: 2.5, + cropType: '番茄', + growthStage: '开花期', + location: '北京市昌平区', + }, + + dataSnapshot: [ + { + source: '土壤传感器-01', + timestamp: '2024-10-23 14:28:00', + type: '传感器数据', + values: { + 土壤湿度: '35%', + 土壤温度: '18℃', + 土壤EC值: '1.2 mS/cm', + 土壤pH值: '6.8', + }, + }, + { + source: '气象站-001', + timestamp: '2024-10-23 14:28:00', + type: '气象数据', + values: { + 气温: '28℃', + 空气湿度: '65%', + 风速: '2.5 m/s', + 光照强度: '15000 lux', + }, + }, + ], + + modelAnalysis: [ + { + modelId: 'model_001', + modelName: '作物需水预测模型', + modelVersion: 'v2.1.3', + provider: 'AgriAI', + callTime: 0.8, + rawOutput: { + 预测需水量: '120升', + 日均耗水量: '40升', + 最佳湿度: '65%', + 当前缺水: '30%', + 紧急程度: '高', + }, + confidence: 0.92, + factors: { + 生长阶段: 0.85, + 天气条件: 0.78, + 土壤状态: 0.88, + 历史模式: 0.90, + }, + }, + ], + + rulesMatched: [ + { + ruleId: 'rule_001', + ruleName: '开花期灌溉规则', + category: '生长期管理', + priority: 9, + matched: true, + confidence: 0.95, + conditions: [ + { + expression: '生长阶段 == "开花期"', + value: '开花期', + expected: '开花期', + result: true, + }, + { + expression: '土壤湿度 < 40%', + value: 35, + expected: '< 40', + result: true, + }, + ], + calculation: { + formula: '灌溉量 = (目标湿度 - 当前湿度) × 地块面积 × 系数', + variables: { + 目标湿度: 65, + 当前湿度: 35, + 地块面积: 2.5, + 系数: 0.8, + }, + steps: [ + '1. 计算湿度差:65 - 35 = 30', + '2. 乘以地块面积:30 × 2.5 = 75', + '3. 乘以调整系数:75 × 0.8 = 60', + '4. 考虑安全裕度:60 × 2 = 120升', + ], + result: '120升', + }, + actions: [ + '开启灌溉系统', + '控制灌溉量为120升', + '选择傍晚时段(17:00-18:00)', + '监控土壤湿度变化', + ], + }, + ], + + triggerCondition: { + device: '土壤传感器-01', + parameter: '土壤湿度', + operator: '<', + value: '30', + }, + execution: { + device: '水肥机-01', + action: 'open', + duration: 45, + }, + + finalDecision: { + recommendation: '建议在今日17:00开启灌溉系统,灌溉量120升,预计45分钟完成', + parameters: { + 灌溉量: '120升', + 灌溉时间: '17:00-17:45', + 灌溉方式: '滴灌', + 流量: '2.67升/分钟', + }, + expectedEffect: { + 土壤湿度: '从35%提升至65%', + 土壤温度: '保持在18-20℃', + 作物状态: '缓解水分胁迫,促进开花结果', + 预计产量提升: '8-12%', + }, + riskAssessment: '风险较低。天气条件良好,蒸发损失可控。需注意监控土壤湿度,避免过度灌溉导致根部缺氧。', + alternatives: [ + '方案B:分两次灌溉,每次60升,间隔12小时', + '方案C:等待明日早晨灌溉,但可能影响当天作物生长', + '方案D:减少灌溉量至80升,2天后再次评估', + ], + }, + + executionResult: { + startTime: '2024-10-23 17:00:00', + endTime: '2024-10-23 17:43:00', + actualEffect: { + 土壤湿度: '从35%提升至63%(目标65%)', + 实际灌溉量: '118升', + 吸收率: '92%', + 蒸发损失: '2升(1.7%)', + }, + deviation: '实际湿度提升至63%,略低于目标65%,但在可接受范围内', + evaluation: '执行效果良好。实际用水118升,比预期节省2升。土壤湿度达到预期的97%,满足番茄开花期需求。建议2天后再次评估。', + }, + }; + + // 手动添加决策详情 + const manualDecisionDetail: ManualDecisionDetail = { + id: 'dec_002', + decisionNo: 'DEC-20241023-002', + title: '2号大棚温度控制', + type: 'weather', + source: 'manual', + status: 'executing', + level: 'normal', + createdAt: '2024-10-23 12:15:30', + updatedAt: '2024-10-23 12:15:30', + createdBy: '张三', + + manualInput: { + confidence: 0.86, + executionMode: 'auto', + recommendation: '预计下午温度过高,建议立即启动通风降温系统,打开排风扇进行强制通风,同时启用遮阳网减少光照强度', + explanation: `当前情况分析: + +天气预报显示今日下午最高温度将达到37℃,远超番茄生长适宜温度(22-28℃),可能导致高温胁迫。 + +当前观察情况: +- 大棚内温度:32℃(上午11:00测量) +- 外界温度:35℃ +- 空气湿度:55% +- 部分叶片出现卷曲现象 +- 花朵开始萎蔫 + +风险评估: +如不及时降温,可能导致花粉活力下降、落花落果,影响产量10-20%。高温持续超过2小时可能造成不可逆损伤。 + +预期效果: +通过排风扇通风和遮阳网遮阳,预计可将大棚温度降至28℃左右的适宜范围`, + actionItems: `立即开启排风扇进行通风降温,设置3档最大风量 +拉开遮阳网,遮阳率设置为60% +适当喷雾增湿,降低叶面温度,每30分钟喷雾一次 +持续监控温度变化,每15分钟记录一次 +如温度仍高于30℃,补充喷灌降温 +运行20分钟后评估效果,根据情况调整`, + }, + + triggerCondition: { + device: '温度传感器-02', + parameter: '空气温度', + operator: '>', + value: '35', + }, + execution: { + device: '排风扇-02', + action: 'open', + duration: 20, + }, + + executionResult: { + startTime: '2024-10-23 12:30:00', + endTime: '2024-10-23 12:50:00', + actualEffect: { + 大棚温度: '从32℃降至28℃', + 空气湿度: '从55%提升至62%', + 叶片状态: '卷曲现象明显改善', + 花朵状态: '恢复正常', + }, + evaluation: '降温措施有效,大棚温度成功控制在适宜范围内。叶片和花朵状态恢复良好,避免了高温胁迫损害。', + }, + }; + + dispatch({ type: 'SET_DECISIONS', payload: decisions }); + dispatch({ type: 'SET_AUTO_DETAIL', payload: autoDecisionDetail }); + dispatch({ type: 'SET_MANUAL_DETAIL', payload: manualDecisionDetail }); + dispatch({ type: 'SET_LOADING', payload: false }); + + toast.success('决策数据加载完成'); + }; + + // 事件处理函数 + const handleFilterChange = (key: keyof FilterCondition, value: any) => { + dispatch({ type: 'UPDATE_FILTER', payload: { key, value } }); + }; + + const handleViewDetail = (decision: DecisionListItem) => { + dispatch({ type: 'SET_SELECTED_DECISION', payload: decision }); + dispatch({ type: 'TOGGLE_DETAIL_DIALOG' }); + }; + + const handleRefresh = () => { + loadTestData(); + }; + + const handleExport = () => { + toast.success('决策详情导出成功'); + }; + + const toggleSection = (section: string) => { + dispatch({ type: 'TOGGLE_SECTION', payload: section }); + }; + + // 筛选决策列表 + const filteredDecisions = state.decisions.filter(decision => { + if (state.filters.source !== 'all' && decision.source !== state.filters.source) return false; + if (state.filters.type !== 'all' && decision.type !== state.filters.type) return false; + if (state.filters.status !== 'all' && decision.status !== state.filters.status) return false; + if (state.filters.level !== 'all' && decision.level !== state.filters.level) return false; + if (state.filters.field !== 'all' && decision.fieldName !== state.filters.field) return false; + if (state.filters.cropType !== 'all' && decision.cropType !== state.filters.cropType) return false; + if (state.filters.searchText && !decision.title.toLowerCase().includes(state.filters.searchText.toLowerCase())) return false; + + return true; + }); + + // 辅助函数 + const getSourceBadge = (source: DecisionSource) => { + const config = { + auto: { label: '自动生成', className: 'bg-accent-muted text-accent-foreground border-accent', icon: Sparkles }, + manual: { label: '手动添加', className: 'bg-info-muted text-info-muted-foreground border-info', icon: User }, + }; + const { label, className, icon: Icon } = config[source]; + return ( + + + {label} + + ); + }; + + const getTypeBadge = (type: DecisionType) => { + const config = { + irrigation: { label: '灌溉', icon: Droplets, className: 'bg-info-muted text-info-muted-foreground' }, + fertilizer: { label: '施肥', icon: Sprout, className: 'bg-success-muted text-success-muted-foreground' }, + pesticide: { label: '打药', icon: Bug, className: 'bg-warning-muted text-warning-muted-foreground' }, + harvest: { label: '收获', icon: Package, className: 'bg-accent-muted text-accent-foreground' }, + soil: { label: '土壤', icon: Layers, className: 'bg-warning-muted text-warning-muted-foreground' }, + weather: { label: '气象', icon: CloudRain, className: 'bg-info-muted text-info-muted-foreground' }, + }; + const { label, icon: Icon, className } = config[type]; + return ( + + + {label} + + ); + }; + + const getStatusBadge = (status: DecisionStatus) => { + const config = { + generated: { label: '已生成', icon: Sparkles, className: 'bg-info-muted text-info-muted-foreground border-info' }, + executing: { label: '执行中', icon: Activity, className: 'bg-accent-muted text-accent-foreground border-accent' }, + completed: { label: '已完成', icon: CheckCircle, className: 'bg-success-muted text-success-muted-foreground border-success' }, + expired: { label: '已过期', icon: Clock, className: 'bg-muted text-muted-foreground border-border' }, + }; + const { label, icon: Icon, className } = config[status]; + return ( + + + {label} + + ); + }; + + const getLevelBadge = (level: DecisionLevel) => { + const config = { + critical: { label: '紧急', className: 'bg-error-muted text-error-foreground border-error' }, + important: { label: '重要', className: 'bg-warning-muted text-warning-foreground border-warning' }, + normal: { label: '一般', className: 'bg-info-muted text-info-foreground border-info' }, + suggestion: { label: '建议', className: 'bg-muted text-muted-foreground border-border' }, + }; + const { label, className } = config[level]; + return {label}; + }; + + // 统计数据 + const stats = { + total: state.decisions.length, + auto: state.decisions.filter(d => d.source === 'auto').length, + manual: state.decisions.filter(d => d.source === 'manual').length, + executing: state.decisions.filter(d => d.status === 'executing').length, + completed: state.decisions.filter(d => d.status === 'completed').length, + }; + + if (state.loading) { + return ( +
+
+ +

加载决策数据中...

+
+
+ ); + } + return (
- -

决策详情

-
-

- 页面路径: /ai-crop-model/support/detail -

+ {/* 页面标题 */} + +
+
+ +
+

决策详情

+

+ 查看AI自动生成和用户手动添加的决策完整信息,包括数据来源、分析过程和执行结果 +

+
+ + + AI自动生成 + + + + 用户手动添加 + + + + 完整追溯 + + + + 效果评估 + +
+
+
+
+ +
+ + {/* 统计卡片 */} +
+ +
+
总决策数
+ +
+
{stats.total}
+
+ + +
+
自动生成
+ +
+
{stats.auto}
+
+ + +
+
手动添加
+ +
+
{stats.manual}
+
+ + +
+
执行中
+ +
+
{stats.executing}
+
+ + +
+
已完成
+ +
+
{stats.completed}
+
+
+ + {/* 筛选工具栏 */} + +
+ +

筛选条件

+
+
+ {/* 搜索 */} +
+
+ + handleFilterChange('searchText', e.target.value)} + className="pl-10" + /> +
+
+ + {/* 决策来源 */} +
+ +
+ + {/* 决策类型 */} +
+ +
+ + {/* 决策状态 */} +
+ +
+ + {/* 决策级别 */} +
+ +
+
+ + {/* 筛选结果提示 */} +
+ + 共找到 {filteredDecisions.length} 条决策 +
+
+ + {/* 决策列表 */} + +

决策列表

+ + {filteredDecisions.length === 0 ? ( +
+ +
暂无符合条件的决策
+
+ ) : ( +
+ {filteredDecisions.map((decision) => ( + +
+
+
+ {getSourceBadge(decision.source)} + {getTypeBadge(decision.type)} + {getStatusBadge(decision.status)} + {getLevelBadge(decision.level)} +
+ +

{decision.title}

+ + {decision.description && ( +

+ {decision.description} +

+ )} + +
+
+ 决策编号: + {decision.decisionNo} +
+
+ 地块: + {decision.fieldName} +
+
+ 作物: + {decision.cropType} +
+
+ 创建时间: + {decision.createdAt} +
+
+ 创建人: + {decision.createdBy} +
+ {decision.source === 'auto' && ( +
+ 置信度: + {(decision.confidence * 100).toFixed(0)}% +
+ )} +
+ + {/* 触发条件和执行设置 */} + {decision.triggerCondition && decision.execution && ( +
+
+
+
触发条件
+
+ {decision.triggerCondition.device} - {decision.triggerCondition.parameter} {decision.triggerCondition.operator} {decision.triggerCondition.value} +
+
+
+
执行设置
+
+ {decision.execution.device} - {decision.execution.action === 'open' ? '开启' : '关闭'} ({decision.execution.duration}分钟) +
+
+
+
+ )} +
+ +
+ +
+
+
+ ))} +
+ )} +
+ + {/* 决策详情对话框 */} + dispatch({ type: 'TOGGLE_DETAIL_DIALOG' })}> + + + + {state.selectedDecision && ( +
+ {state.selectedDecision.title} + {getSourceBadge(state.selectedDecision.source)} +
+ )} +
+ + 查看决策的完整分析过程和执行详情 + +
+ + {state.selectedDecision && ( + + + + 自动生成详情 + + + 手动添加详情 + + + + {/* 自动生成决策详情 */} + + {state.autoDecisionDetail && ( + <> + {/* 基本信息 */} + +

基本信息

+
+
+
决策编号
+
{state.autoDecisionDetail.decisionNo}
+
+
+
地块名称
+
{state.autoDecisionDetail.fieldInfo.fieldName}
+
+
+
作物类型
+
{state.autoDecisionDetail.fieldInfo.cropType}
+
+
+
生长阶段
+
{state.autoDecisionDetail.fieldInfo.growthStage}
+
+
+
地块面积
+
{state.autoDecisionDetail.fieldInfo.area}亩
+
+
+
位置
+
{state.autoDecisionDetail.fieldInfo.location}
+
+
+
+ + {/* 数据快照 */} + + + {state.expandedSections.has('snapshot') && ( +
+ {state.autoDecisionDetail.dataSnapshot.map((data, idx) => ( +
+
+
+ + {data.source} +
+ {data.type} +
+
{data.timestamp}
+
+ {Object.entries(data.values).map(([key, value]) => ( +
+ {key}: + {String(value)} +
+ ))} +
+
+ ))} +
+ )} +
+ + {/* 模型分析 */} + + + {state.expandedSections.has('model') && ( +
+ {state.autoDecisionDetail.modelAnalysis.map((model, idx) => ( +
+
+
+ + {model.modelName} +
+
+ + 版本: {model.modelVersion} + + + 耗时: {model.callTime}s + + + 置信度: {(model.confidence * 100).toFixed(0)}% + +
+
+ +
+
+
模型输出
+
+ {Object.entries(model.rawOutput).map(([key, value]) => ( +
+ {key}: + {String(value)} +
+ ))} +
+
+
+
影响因子
+
+ {Object.entries(model.factors).map(([key, value]) => ( +
+
+ {key} + {(value * 100).toFixed(0)}% +
+ +
+ ))} +
+
+
+
+ ))} +
+ )} +
+ + {/* 规则匹配 */} + + + {state.expandedSections.has('rules') && ( +
+ {state.autoDecisionDetail.rulesMatched.map((rule, idx) => ( +
+
+
+ + {rule.ruleName} +
+
+ {rule.category} + 优先级: {rule.priority} + + 置信度: {(rule.confidence * 100).toFixed(0)}% + +
+
+ +
+
匹配条件
+
+ {rule.conditions.map((cond, i) => ( +
+ {cond.result ? ( + + ) : ( + + )} + {cond.expression} + + 值: {String(cond.value)} + +
+ ))} +
+
+ + {rule.calculation && ( +
+
计算过程
+
+
{rule.calculation.formula}
+
变量:
+
+ {Object.entries(rule.calculation.variables).map(([key, value]) => ( +
+ {key} = {String(value)} +
+ ))} +
+
步骤:
+
+ {rule.calculation.steps.map((step, i) => ( +
{step}
+ ))} +
+
+ 结果: {String(rule.calculation.result)} +
+
+
+ )} + +
+
执行动作
+
+ {rule.actions.map((action, i) => ( + + + {action} + + ))} +
+
+
+ ))} +
+ )} +
+ + {/* 最终决策 */} + + + {state.expandedSections.has('decision') && ( +
+
+
+ + 决策建议 +
+

{state.autoDecisionDetail.finalDecision.recommendation}

+
+ +
+
+
执行参数
+
+ {Object.entries(state.autoDecisionDetail.finalDecision.parameters).map(([key, value]) => ( +
+ {key}: + {String(value)} +
+ ))} +
+
+
+
预期效果
+
+ {Object.entries(state.autoDecisionDetail.finalDecision.expectedEffect).map(([key, value]) => ( +
+ {key}: + {String(value)} +
+ ))} +
+
+
+ +
+
风险评估
+
+

{state.autoDecisionDetail.finalDecision.riskAssessment}

+
+
+ +
+
备选方案
+
+ {state.autoDecisionDetail.finalDecision.alternatives.map((alt, i) => ( +
+ {alt} +
+ ))} +
+
+
+ )} +
+ + {/* 执行结果 */} + {state.autoDecisionDetail.executionResult && ( + +

执行结果

+
+
+
+ 开始时间: + {state.autoDecisionDetail.executionResult.startTime} +
+
+ 结束时间: + {state.autoDecisionDetail.executionResult.endTime} +
+
+ +
+
实际效果
+
+ {Object.entries(state.autoDecisionDetail.executionResult.actualEffect).map(([key, value]) => ( +
+ {key}: + {String(value)} +
+ ))} +
+
+ +
+
+ + 偏差分析 +
+

{state.autoDecisionDetail.executionResult.deviation}

+
+ +
+
+ + 效果评价 +
+

{state.autoDecisionDetail.executionResult.evaluation}

+
+
+
+ )} + + )} +
+ + {/* 手动添加决策详情 */} + + {state.manualDecisionDetail && ( + <> + {/* 基本信息 */} + +

基本信息

+
+
+
决策编号
+
{state.manualDecisionDetail.decisionNo}
+
+
+
决策名称
+
{state.manualDecisionDetail.title}
+
+
+
决策类型
+
+ {getTypeBadge(state.manualDecisionDetail.type)} +
+
+
+
决策级别
+
+ {getLevelBadge(state.manualDecisionDetail.level)} +
+
+
+
置信度
+
{(state.manualDecisionDetail.manualInput.confidence * 100).toFixed(0)}%
+
+
+
创建人
+
{state.manualDecisionDetail.createdBy}
+
+
+
创建时间
+
{state.manualDecisionDetail.createdAt}
+
+
+
+ + {/* 执行设置 */} + +

执行设置

+
+
+
执行模式
+
+ {state.manualDecisionDetail.manualInput.executionMode === 'manual' ? ( + + + 手动执行 + + ) : ( + + + 自动执行 + + )} +
+
+ + {/* 触发条件(自动执行时) */} + {state.manualDecisionDetail.manualInput.executionMode === 'auto' && state.manualDecisionDetail.triggerCondition && ( +
+
触发条件(阈值触发)
+
+
+ 设备: + {state.manualDecisionDetail.triggerCondition.device} +
+
+ 参数: + {state.manualDecisionDetail.triggerCondition.parameter} +
+
+ 条件: + + {state.manualDecisionDetail.triggerCondition.operator} {state.manualDecisionDetail.triggerCondition.value} + +
+
+ 当条件满足时将自动执行此决策 +
+
+
+ )} + + {/* 执行目标 */} + {state.manualDecisionDetail.execution && ( +
+
执行目标
+
+
+ 控制设备: + {state.manualDecisionDetail.execution.device} +
+
+ 执行动作: + + {state.manualDecisionDetail.execution.action === 'open' ? '打开' : '关闭'} + +
+ {state.manualDecisionDetail.execution.duration && ( +
+ 持续时长: + {state.manualDecisionDetail.execution.duration}分钟 +
+ )} +
+
+ )} +
+
+ + {/* 决策内容 */} + +

决策内容

+
+
+
推荐建议
+
+

{state.manualDecisionDetail.manualInput.recommendation}

+
+
+ + {state.manualDecisionDetail.manualInput.explanation && ( +
+
详细说明
+
+

{state.manualDecisionDetail.manualInput.explanation}

+
+
+ )} + + {state.manualDecisionDetail.manualInput.actionItems && ( +
+
执行步骤
+
+
    + {state.manualDecisionDetail.manualInput.actionItems.split('\n').map((item, idx) => ( + item.trim() &&
  1. {item.trim()}
  2. + ))} +
+
+
+ )} +
+
+ + {/* 执行结果 */} + {state.manualDecisionDetail.executionResult && ( + +

执行结果

+
+
+
+ 开始时间: + {state.manualDecisionDetail.executionResult.startTime} +
+
+ 结束时间: + {state.manualDecisionDetail.executionResult.endTime} +
+
+ +
+
实际效果
+
+ {Object.entries(state.manualDecisionDetail.executionResult.actualEffect).map(([key, value]) => ( +
+ {key}: + {String(value)} +
+ ))} +
+
+ +
+
+ + 效果评价 +
+

{state.manualDecisionDetail.executionResult.evaluation}

+
+
+
+ )} + + )} +
+
+ )} + + + + + +
+
); } \ No newline at end of file