- 新增 safeLocalStorage 工具函数增强本地存储安全性 - 简化认证流程,重构用户数据结构和 token 管理 - 修复多个模块的 TypeScript 类型错误和导入问题 - 优化状态管理,重构各模块 store 结构 - 清理冗余代码,移除未使用的组件和函数 - 改进错误处理和边界情况处理 - 更新配置文件以支持最新的类型检查 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
444 lines
12 KiB
TypeScript
444 lines
12 KiB
TypeScript
/**
|
||
* filekorolheader: AI决策看板状态管理 - 决策数据状态管理核心
|
||
* 功能:决策数据管理、统计计算、状态更新、本地存储同步
|
||
* 路径:/ai-crop-model/support/dashboard/components/aiDecisionDashboardReducer
|
||
* 规范:遵循crop-x/docs/开发项目规范.md,使用useReducer状态管理模式
|
||
*/
|
||
|
||
import { safeLocalStorage } from '@/utils/storage';
|
||
|
||
// 决策类型
|
||
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<DecisionRecord> } }
|
||
| { type: 'DELETE_DECISION'; payload: string }
|
||
| { type: 'REFRESH_DATA' }
|
||
| { type: 'LOAD_FROM_STORAGE'; payload: Partial<AIDecisionDashboardState> };
|
||
|
||
// 初始决策数据
|
||
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<string, FieldDecisionInfo>();
|
||
|
||
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) => {
|
||
safeLocalStorage.setItem('ai-decision-dashboard', JSON.stringify({
|
||
decisions: state.decisions,
|
||
lastUpdated: state.lastUpdated,
|
||
}));
|
||
};
|
||
|
||
// 从本地存储加载
|
||
const loadFromStorage = () => {
|
||
const stored = safeLocalStorage.getItem('ai-decision-dashboard');
|
||
if (stored) {
|
||
try {
|
||
const data = JSON.parse(stored);
|
||
return {
|
||
decisions: data.decisions || initialDecisions,
|
||
lastUpdated: data.lastUpdated || new Date().toISOString(),
|
||
};
|
||
} catch (error) {
|
||
console.warn('Failed to parse stored data:', 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;
|
||
}
|
||
} |