1399 lines
60 KiB
TypeScript
1399 lines
60 KiB
TypeScript
import { useState, useEffect } from 'react';
|
||
import { Card } from '../ui/card';
|
||
import { Button } from '../ui/button';
|
||
import { Badge } from '../ui/badge';
|
||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '../ui/tabs';
|
||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../ui/select';
|
||
import { Input } from '../ui/input';
|
||
import { Label } from '../ui/label';
|
||
import { Switch } from '../ui/switch';
|
||
import { Slider } from '../ui/slider';
|
||
import { Progress } from '../ui/progress';
|
||
import {
|
||
Droplets,
|
||
Power,
|
||
Settings,
|
||
Activity,
|
||
Gauge,
|
||
Zap,
|
||
Battery,
|
||
Sun,
|
||
ThermometerSun,
|
||
Waves,
|
||
Beaker,
|
||
Flask,
|
||
TestTube,
|
||
TrendingUp,
|
||
Calendar,
|
||
Download,
|
||
RefreshCw,
|
||
Play,
|
||
Pause,
|
||
AlertCircle,
|
||
CheckCircle,
|
||
XCircle,
|
||
Eye,
|
||
BarChart3,
|
||
LineChart as LineChartIcon,
|
||
MapPin,
|
||
Layers,
|
||
} from 'lucide-react';
|
||
import { toast } from 'sonner@2.0.3';
|
||
import {
|
||
LineChart,
|
||
Line,
|
||
AreaChart,
|
||
Area,
|
||
BarChart,
|
||
Bar,
|
||
ComposedChart,
|
||
XAxis,
|
||
YAxis,
|
||
CartesianGrid,
|
||
Tooltip as RechartsTooltip,
|
||
Legend,
|
||
ResponsiveContainer,
|
||
} from 'recharts';
|
||
|
||
interface WaterFertilizerControlProps {
|
||
activePath?: string;
|
||
}
|
||
|
||
type SystemStatus = '运行中' | '已停止' | '待机';
|
||
type ValveStatus = '开启' | '关闭' | '故障';
|
||
|
||
interface FertilizerTank {
|
||
id: string;
|
||
tankNo: string;
|
||
name: string;
|
||
fertilizer: string;
|
||
concentration: number;
|
||
ratio: number;
|
||
currentLevel: number;
|
||
flowRate: number;
|
||
}
|
||
|
||
interface WaterFertilizerMachine {
|
||
id: string;
|
||
name: string;
|
||
ecValue: number;
|
||
ecTarget: number;
|
||
phValue: number;
|
||
phTarget: number;
|
||
status: SystemStatus;
|
||
}
|
||
|
||
interface Valve {
|
||
id: string;
|
||
valveNo: string;
|
||
fieldName: string;
|
||
location: string;
|
||
status: ValveStatus;
|
||
batteryVoltage: number;
|
||
solarVoltage: number;
|
||
pressure: number;
|
||
flowRate: number;
|
||
totalVolume: number;
|
||
}
|
||
|
||
interface SoilData {
|
||
layer: string;
|
||
depth: string;
|
||
temperature: number;
|
||
humidity: number;
|
||
}
|
||
|
||
interface RealtimeData {
|
||
time: string;
|
||
tankALevel: number;
|
||
tankBLevel: number;
|
||
tankCLevel: number;
|
||
tankAFlow: number;
|
||
tankBFlow: number;
|
||
tankCFlow: number;
|
||
ec: number;
|
||
ph: number;
|
||
pressure: number;
|
||
flowRate: number;
|
||
totalVolume: number;
|
||
}
|
||
|
||
interface HistoryData {
|
||
date: string;
|
||
tankAFlow: number;
|
||
tankBFlow: number;
|
||
tankCFlow: number;
|
||
avgEc: number;
|
||
avgPh: number;
|
||
avgPressure: number;
|
||
avgFlowRate: number;
|
||
}
|
||
|
||
export function WaterFertilizerControl({ activePath }: WaterFertilizerControlProps) {
|
||
const [activeTab, setActiveTab] = useState('params');
|
||
const [systemEnabled, setSystemEnabled] = useState(false);
|
||
const [selectedValveId, setSelectedValveId] = useState('valve-1');
|
||
const [historyDateRange, setHistoryDateRange] = useState('7');
|
||
const [selectedMachineId, setSelectedMachineId] = useState('wf-001');
|
||
const [isEditMode, setIsEditMode] = useState(false);
|
||
|
||
// 水肥机设备列表
|
||
const [machines, setMachines] = useState<WaterFertilizerMachine[]>([
|
||
{
|
||
id: 'wf-001',
|
||
name: '1号大棚水肥一体机',
|
||
ecValue: 2.35,
|
||
ecTarget: 2.5,
|
||
phValue: 6.8,
|
||
phTarget: 6.5,
|
||
status: '运行中',
|
||
},
|
||
{
|
||
id: 'wf-002',
|
||
name: '2号大棚水肥一体机',
|
||
ecValue: 2.18,
|
||
ecTarget: 2.3,
|
||
phValue: 6.5,
|
||
phTarget: 6.4,
|
||
status: '待机',
|
||
},
|
||
{
|
||
id: 'wf-003',
|
||
name: '3号田块水肥一体机',
|
||
ecValue: 2.52,
|
||
ecTarget: 2.6,
|
||
phValue: 6.9,
|
||
phTarget: 6.7,
|
||
status: '运行中',
|
||
},
|
||
{
|
||
id: 'wf-004',
|
||
name: '4号田块水肥一体机',
|
||
ecValue: 2.08,
|
||
ecTarget: 2.2,
|
||
phValue: 6.3,
|
||
phTarget: 6.2,
|
||
status: '已停止',
|
||
},
|
||
]);
|
||
|
||
// 肥料桶数据(按水肥机分组)
|
||
const [allTanks, setAllTanks] = useState<(FertilizerTank & { machineId: string })[]>([
|
||
// 1号水肥机
|
||
{ id: 'tank-1-1', machineId: 'wf-001', tankNo: 'A', name: 'A肥料桶', fertilizer: '氮肥溶液', concentration: 15, ratio: 40, currentLevel: 320, flowRate: 8.5 },
|
||
{ id: 'tank-1-2', machineId: 'wf-001', tankNo: 'B', name: 'B肥料桶', fertilizer: '磷肥溶液', concentration: 12, ratio: 30, currentLevel: 180, flowRate: 6.2 },
|
||
{ id: 'tank-1-3', machineId: 'wf-001', tankNo: 'C', name: 'C肥料桶', fertilizer: '钾肥溶液', concentration: 18, ratio: 30, currentLevel: 425, flowRate: 6.8 },
|
||
// 2号水肥机
|
||
{ id: 'tank-2-1', machineId: 'wf-002', tankNo: 'A', name: 'A肥料桶', fertilizer: '氮肥溶液', concentration: 16, ratio: 35, currentLevel: 285, flowRate: 7.2 },
|
||
{ id: 'tank-2-2', machineId: 'wf-002', tankNo: 'B', name: 'B肥料桶', fertilizer: '磷肥溶液', concentration: 14, ratio: 35, currentLevel: 215, flowRate: 5.8 },
|
||
{ id: 'tank-2-3', machineId: 'wf-002', tankNo: 'C', name: 'C肥料桶', fertilizer: '钾肥溶液', concentration: 17, ratio: 30, currentLevel: 380, flowRate: 6.5 },
|
||
// 3号水肥机
|
||
{ id: 'tank-3-1', machineId: 'wf-003', tankNo: 'A', name: 'A肥料桶', fertilizer: '氮肥溶液', concentration: 14, ratio: 42, currentLevel: 295, flowRate: 8.8 },
|
||
{ id: 'tank-3-2', machineId: 'wf-003', tankNo: 'B', name: 'B肥料桶', fertilizer: '磷肥溶液', concentration: 13, ratio: 28, currentLevel: 165, flowRate: 5.5 },
|
||
{ id: 'tank-3-3', machineId: 'wf-003', tankNo: 'C', name: 'C肥料桶', fertilizer: '钾肥溶液', concentration: 19, ratio: 30, currentLevel: 445, flowRate: 7.2 },
|
||
// 4号水肥机
|
||
{ id: 'tank-4-1', machineId: 'wf-004', tankNo: 'A', name: 'A肥料桶', fertilizer: '氮肥溶液', concentration: 15, ratio: 38, currentLevel: 310, flowRate: 0 },
|
||
{ id: 'tank-4-2', machineId: 'wf-004', tankNo: 'B', name: 'B肥料桶', fertilizer: '磷肥溶液', concentration: 12, ratio: 32, currentLevel: 195, flowRate: 0 },
|
||
{ id: 'tank-4-3', machineId: 'wf-004', tankNo: 'C', name: 'C肥料桶', fertilizer: '钾肥溶液', concentration: 18, ratio: 30, currentLevel: 405, flowRate: 0 },
|
||
]);
|
||
|
||
// 阀门数据(按水肥机分组)
|
||
const [allValves, setAllValves] = useState<(Valve & { machineId: string })[]>([
|
||
// 1号水肥机的阀门
|
||
{ id: 'valve-1-1', machineId: 'wf-001', valveNo: 'V-01', fieldName: '1号大棚', location: '东区A01', status: '开启', batteryVoltage: 12.5, solarVoltage: 18.2, pressure: 2.8, flowRate: 25.6, totalVolume: 1450 },
|
||
{ id: 'valve-1-2', machineId: 'wf-001', valveNo: 'V-02', fieldName: '1号大棚-西侧', location: '东区A02', status: '关闭', batteryVoltage: 11.8, solarVoltage: 17.5, pressure: 0, flowRate: 0, totalVolume: 0 },
|
||
// 2号水肥机的阀门
|
||
{ id: 'valve-2-1', machineId: 'wf-002', valveNo: 'V-03', fieldName: '2号大棚', location: '西区B01', status: '开启', batteryVoltage: 12.2, solarVoltage: 18.8, pressure: 2.6, flowRate: 23.2, totalVolume: 1280 },
|
||
{ id: 'valve-2-2', machineId: 'wf-002', valveNo: 'V-04', fieldName: '2号大棚-东侧', location: '西区B02', status: '关闭', batteryVoltage: 12.0, solarVoltage: 18.0, pressure: 0, flowRate: 0, totalVolume: 0 },
|
||
// 3号水肥机的阀门
|
||
{ id: 'valve-3-1', machineId: 'wf-003', valveNo: 'V-05', fieldName: '3号田块', location: '南区C01', status: '开启', batteryVoltage: 12.8, solarVoltage: 19.2, pressure: 2.9, flowRate: 27.8, totalVolume: 1580 },
|
||
{ id: 'valve-3-2', machineId: 'wf-003', valveNo: 'V-06', fieldName: '3号田块-北侧', location: '南区C02', status: '开启', batteryVoltage: 12.3, solarVoltage: 18.5, pressure: 2.7, flowRate: 24.5, totalVolume: 1320 },
|
||
// 4号水肥机的阀门
|
||
{ id: 'valve-4-1', machineId: 'wf-004', valveNo: 'V-07', fieldName: '4号田块', location: '北区D01', status: '故障', batteryVoltage: 10.5, solarVoltage: 15.2, pressure: 0, flowRate: 0, totalVolume: 0 },
|
||
{ id: 'valve-4-2', machineId: 'wf-004', valveNo: 'V-08', fieldName: '4号田块-南侧', location: '北区D02', status: '关闭', batteryVoltage: 11.5, solarVoltage: 17.0, pressure: 0, flowRate: 0, totalVolume: 0 },
|
||
]);
|
||
|
||
// 土壤数据
|
||
const [soilData] = useState<SoilData[]>([
|
||
{ layer: '表层', depth: '0-20cm', temperature: 22.5, humidity: 65 },
|
||
{ layer: '中层', depth: '20-40cm', temperature: 21.8, humidity: 72 },
|
||
{ layer: '深层', depth: '40-60cm', temperature: 21.2, humidity: 78 },
|
||
]);
|
||
|
||
// 实时数据(最近2小时)
|
||
const [realtimeData] = useState<RealtimeData[]>(() => {
|
||
const data: RealtimeData[] = [];
|
||
const now = new Date();
|
||
for (let i = 120; i >= 0; i -= 5) {
|
||
const time = new Date(now.getTime() - i * 60 * 1000);
|
||
data.push({
|
||
time: `${time.getHours().toString().padStart(2, '0')}:${time.getMinutes().toString().padStart(2, '0')}`,
|
||
tankALevel: 300 + Math.random() * 40,
|
||
tankBLevel: 160 + Math.random() * 40,
|
||
tankCLevel: 400 + Math.random() * 40,
|
||
tankAFlow: 7 + Math.random() * 3,
|
||
tankBFlow: 5 + Math.random() * 3,
|
||
tankCFlow: 5.5 + Math.random() * 3,
|
||
ec: 2.2 + Math.random() * 0.5,
|
||
ph: 6.3 + Math.random() * 0.6,
|
||
pressure: 2.3 + Math.random() * 0.8,
|
||
flowRate: 20 + Math.random() * 10,
|
||
totalVolume: 1200 + (120 - i) * 8,
|
||
});
|
||
}
|
||
return data;
|
||
});
|
||
|
||
// 历史数据(最近7天)
|
||
const [historyData] = useState<HistoryData[]>([
|
||
{ date: '10-09', tankAFlow: 180, tankBFlow: 145, tankCFlow: 155, avgEc: 2.4, avgPh: 6.5, avgPressure: 2.5, avgFlowRate: 24.5 },
|
||
{ date: '10-10', tankAFlow: 195, tankBFlow: 160, tankCFlow: 165, avgEc: 2.5, avgPh: 6.6, avgPressure: 2.6, avgFlowRate: 26.2 },
|
||
{ date: '10-11', tankAFlow: 175, tankBFlow: 140, tankCFlow: 150, avgEc: 2.3, avgPh: 6.4, avgPressure: 2.4, avgFlowRate: 23.8 },
|
||
{ date: '10-12', tankAFlow: 205, tankBFlow: 170, tankCFlow: 175, avgEc: 2.6, avgPh: 6.7, avgPressure: 2.7, avgFlowRate: 27.5 },
|
||
{ date: '10-13', tankAFlow: 165, tankBFlow: 130, tankCFlow: 140, avgEc: 2.2, avgPh: 6.3, avgPressure: 2.3, avgFlowRate: 22.8 },
|
||
{ date: '10-14', tankAFlow: 210, tankBFlow: 175, tankCFlow: 180, avgEc: 2.7, avgPh: 6.8, avgPressure: 2.8, avgFlowRate: 28.2 },
|
||
{ date: '10-15', tankAFlow: 190, tankBFlow: 155, tankCFlow: 160, avgEc: 2.5, avgPh: 6.6, avgPressure: 2.6, avgFlowRate: 25.8 },
|
||
]);
|
||
|
||
// 根据路径自动切换Tab
|
||
useEffect(() => {
|
||
if (activePath) {
|
||
if (activePath.includes('/params')) {
|
||
setActiveTab('params');
|
||
} else if (activePath.includes('/tank-realtime')) {
|
||
setActiveTab('tank-realtime');
|
||
} else if (activePath.includes('/valve-control')) {
|
||
setActiveTab('valve-control');
|
||
} else if (activePath.includes('/valve-realtime')) {
|
||
setActiveTab('valve-realtime');
|
||
} else if (activePath.includes('/fertilizer-history')) {
|
||
setActiveTab('fertilizer-history');
|
||
} else if (activePath.includes('/valve-history')) {
|
||
setActiveTab('valve-history');
|
||
}
|
||
}
|
||
}, [activePath]);
|
||
|
||
// 切换水肥机时,更新选中的阀门为当前水肥机的第一个阀门
|
||
useEffect(() => {
|
||
const currentMachineValves = allValves.filter(v => v.machineId === selectedMachineId);
|
||
if (currentMachineValves.length > 0 && !currentMachineValves.find(v => v.id === selectedValveId)) {
|
||
setSelectedValveId(currentMachineValves[0].id);
|
||
}
|
||
}, [selectedMachineId, allValves, selectedValveId]);
|
||
|
||
// 根据选中的水肥机筛选数据
|
||
const tanks = allTanks.filter(tank => tank.machineId === selectedMachineId);
|
||
const valves = allValves.filter(valve => valve.machineId === selectedMachineId);
|
||
const machine = machines.find(m => m.id === selectedMachineId) || machines[0];
|
||
const selectedValve = valves.find(v => v.id === selectedValveId) || valves[0];
|
||
|
||
const getValveStatusColor = (status: ValveStatus) => {
|
||
switch (status) {
|
||
case '开启': return 'bg-success-muted text-success-muted-foreground';
|
||
case '关闭': return 'bg-muted text-muted-foreground';
|
||
case '故障': return 'bg-error-muted text-error-muted-foreground';
|
||
default: return 'bg-muted text-muted-foreground';
|
||
}
|
||
};
|
||
|
||
const handleSystemToggle = (enabled: boolean) => {
|
||
setSystemEnabled(enabled);
|
||
setMachines(machines.map(m =>
|
||
m.id === selectedMachineId
|
||
? { ...m, status: enabled ? '运行中' : '已停止' }
|
||
: m
|
||
));
|
||
toast.success(`${machine.name} ${enabled ? '已启动' : '已停止'}`);
|
||
};
|
||
|
||
const handleTankUpdate = (tankId: string, field: keyof FertilizerTank, value: string | number) => {
|
||
setAllTanks(allTanks.map(t => t.id === tankId ? { ...t, [field]: value } : t));
|
||
};
|
||
|
||
const handleMachineUpdate = (field: keyof WaterFertilizerMachine, value: number) => {
|
||
setMachines(machines.map(m =>
|
||
m.id === selectedMachineId
|
||
? { ...m, [field]: value }
|
||
: m
|
||
));
|
||
};
|
||
|
||
const handleValveToggle = (valveId: string) => {
|
||
const valve = allValves.find(v => v.id === valveId);
|
||
if (!valve) return;
|
||
|
||
if (valve.status === '故障') {
|
||
toast.error('阀门故障,无法操作');
|
||
return;
|
||
}
|
||
|
||
const newStatus: ValveStatus = valve.status === '开启' ? '关闭' : '开启';
|
||
setAllValves(allValves.map(v => v.id === valveId ? { ...v, status: newStatus } : v));
|
||
toast.success(`${valve.valveNo} 已${newStatus === '开启' ? '开启' : '关闭'}`);
|
||
};
|
||
|
||
const handleEditParams = () => {
|
||
setIsEditMode(true);
|
||
toast.info('进入编辑模式', {
|
||
description: '现在可以修改肥料参数配置',
|
||
});
|
||
};
|
||
|
||
const handleSaveParams = () => {
|
||
// 保存肥料参数配置
|
||
setIsEditMode(false);
|
||
toast.success(`${machine.name} 参数配置已保存`, {
|
||
description: '肥料桶参数和EC/PH目标值已更新',
|
||
});
|
||
};
|
||
|
||
const handleCancelEdit = () => {
|
||
setIsEditMode(false);
|
||
toast.info('已取消编辑');
|
||
};
|
||
|
||
return (
|
||
<div className="space-y-6">
|
||
<div className="flex items-center justify-between">
|
||
<div>
|
||
<h2>水肥控制</h2>
|
||
<p className="text-sm text-muted-foreground mt-1">
|
||
施肥参数配置、实时监测、阀门控制、历史数据分析
|
||
</p>
|
||
</div>
|
||
</div>
|
||
|
||
{/* 水肥机选择 */}
|
||
<Card className="p-4">
|
||
<div className="flex items-center gap-4">
|
||
<Label>选择水肥机设备</Label>
|
||
<Select value={selectedMachineId} onValueChange={setSelectedMachineId}>
|
||
<SelectTrigger className="w-96">
|
||
<SelectValue />
|
||
</SelectTrigger>
|
||
<SelectContent>
|
||
{machines.map((machine) => (
|
||
<SelectItem key={machine.id} value={machine.id}>
|
||
{machine.name}
|
||
<Badge className="ml-2 bg-success-muted text-success-muted-foreground" variant="outline">
|
||
{machine.status}
|
||
</Badge>
|
||
</SelectItem>
|
||
))}
|
||
</SelectContent>
|
||
</Select>
|
||
<div className="text-sm text-muted-foreground">
|
||
当前设备:<span className="text-foreground">{machine.name}</span>
|
||
</div>
|
||
</div>
|
||
</Card>
|
||
|
||
{/* 统计卡片 */}
|
||
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
|
||
<Card className="p-6">
|
||
<div className="flex items-center justify-between">
|
||
<div>
|
||
<p className="text-xs text-muted-foreground">系统状态</p>
|
||
<p className="mt-2 text-3xl text-green-600">
|
||
{machine.status === '运行中' ? '运行' : '停止'}
|
||
</p>
|
||
<p className="text-xs text-green-600 mt-1">{machine.name}</p>
|
||
</div>
|
||
<Activity className={`w-12 h-12 ${machine.status === '运行中' ? 'text-green-600 animate-pulse' : 'text-gray-400'} opacity-50`} />
|
||
</div>
|
||
</Card>
|
||
|
||
<Card className="p-6">
|
||
<div className="flex items-center justify-between">
|
||
<div>
|
||
<p className="text-xs text-muted-foreground">当前EC值</p>
|
||
<p className="mt-2 text-3xl text-blue-600">{machine.ecValue}</p>
|
||
<p className="text-xs text-blue-600 mt-1">mS/cm (目标:{machine.ecTarget})</p>
|
||
</div>
|
||
<Zap className="w-12 h-12 text-blue-600 opacity-50" />
|
||
</div>
|
||
</Card>
|
||
|
||
<Card className="p-6">
|
||
<div className="flex items-center justify-between">
|
||
<div>
|
||
<p className="text-xs text-muted-foreground">当前PH值</p>
|
||
<p className="mt-2 text-3xl text-purple-600">{machine.phValue}</p>
|
||
<p className="text-xs text-purple-600 mt-1">pH (目标:{machine.phTarget})</p>
|
||
</div>
|
||
<TestTube className="w-12 h-12 text-purple-600 opacity-50" />
|
||
</div>
|
||
</Card>
|
||
|
||
<Card className="p-6">
|
||
<div className="flex items-center justify-between">
|
||
<div>
|
||
<p className="text-xs text-muted-foreground">开启阀门</p>
|
||
<p className="mt-2 text-3xl text-orange-600">
|
||
{valves.filter(v => v.status === '开启').length}
|
||
</p>
|
||
<p className="text-xs text-orange-600 mt-1">/ {valves.length} 个阀门</p>
|
||
</div>
|
||
<Droplets className="w-12 h-12 text-orange-600 opacity-50" />
|
||
</div>
|
||
</Card>
|
||
</div>
|
||
|
||
<Tabs value={activeTab} onValueChange={setActiveTab}>
|
||
<TabsList className="grid w-full grid-cols-6">
|
||
<TabsTrigger value="params">施肥参数</TabsTrigger>
|
||
<TabsTrigger value="tank-realtime">肥料桶实时</TabsTrigger>
|
||
<TabsTrigger value="valve-control">阀门控制</TabsTrigger>
|
||
<TabsTrigger value="valve-realtime">电动阀实时</TabsTrigger>
|
||
<TabsTrigger value="fertilizer-history">施肥历史</TabsTrigger>
|
||
<TabsTrigger value="valve-history">阀门历史</TabsTrigger>
|
||
</TabsList>
|
||
|
||
{/* 施肥参数设置 */}
|
||
<TabsContent value="params" className="space-y-4">
|
||
<Card className="p-4 bg-gradient-to-r from-green-50 to-teal-50 border-green-200">
|
||
<div className="flex items-start gap-2">
|
||
<Settings className="w-5 h-5 text-green-600 flex-shrink-0 mt-0.5" />
|
||
<div className="text-sm text-green-900">
|
||
<p className="mb-2">施肥参数设置功能:</p>
|
||
<ul className="space-y-1 text-xs">
|
||
<li>• <strong>肥料配置</strong>: 设置各肥料桶名称、浓度、配比比例</li>
|
||
<li>• <strong>EC/PH目标</strong>: 设定水肥机EC电导率和PH目标值</li>
|
||
<li>• <strong>一键启停</strong>: 一键开启/关闭施肥系统</li>
|
||
<li>• <strong>集中管理</strong>: 施肥过程集中化参数管理</li>
|
||
<li>• <strong>快速控制</strong>: 快速启停控制,响应迅速</li>
|
||
<li>• <strong>参数保存</strong>: 配置参数自动保存</li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
</Card>
|
||
|
||
{/* 系统控制 */}
|
||
<Card className="p-6">
|
||
<div className="flex items-center justify-between mb-6">
|
||
<div>
|
||
<h4 className="flex items-center gap-2">
|
||
<Power className={`w-5 h-5 ${systemEnabled ? 'text-green-600' : 'text-gray-400'}`} />
|
||
施肥系统控制
|
||
</h4>
|
||
<p className="text-sm text-muted-foreground mt-1">
|
||
当前状态: <Badge className={systemEnabled ? 'bg-green-100 text-green-700' : 'bg-gray-100 text-gray-700'}>
|
||
{machine.status}
|
||
</Badge>
|
||
</p>
|
||
</div>
|
||
<div className="flex items-center gap-4">
|
||
<Label>系统开关</Label>
|
||
<Switch checked={systemEnabled} onCheckedChange={handleSystemToggle} />
|
||
</div>
|
||
</div>
|
||
</Card>
|
||
|
||
{/* 肥料桶参数 */}
|
||
<Card className="p-6">
|
||
<div className="flex items-center justify-between mb-4">
|
||
<h4 className="flex items-center gap-2">
|
||
<Beaker className="w-5 h-5 text-blue-600" />
|
||
肥料桶参数配置
|
||
</h4>
|
||
<div className="flex items-center gap-3">
|
||
{!isEditMode ? (
|
||
<Button onClick={handleEditParams} variant="outline">
|
||
<Settings className="w-4 h-4 mr-2" />
|
||
编辑参数
|
||
</Button>
|
||
) : (
|
||
<>
|
||
<Button onClick={handleCancelEdit} variant="outline">
|
||
<XCircle className="w-4 h-4 mr-2" />
|
||
取消编辑
|
||
</Button>
|
||
<Button onClick={handleSaveParams} className="bg-green-600 hover:bg-green-700">
|
||
<CheckCircle className="w-4 h-4 mr-2" />
|
||
保存参数配置
|
||
</Button>
|
||
</>
|
||
)}
|
||
</div>
|
||
</div>
|
||
<div className="space-y-4">
|
||
{tanks.map((tank) => (
|
||
<Card key={tank.id} className="p-4 bg-gray-50">
|
||
<div className="flex items-center gap-2 mb-4">
|
||
<Badge variant="outline" className="text-lg px-3 py-1">{tank.tankNo}桶</Badge>
|
||
<h4>{tank.name}</h4>
|
||
</div>
|
||
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
|
||
<div>
|
||
<Label>肥料名称</Label>
|
||
<Input
|
||
value={tank.fertilizer}
|
||
onChange={(e) => handleTankUpdate(tank.id, 'fertilizer', e.target.value)}
|
||
className="mt-2"
|
||
disabled={!isEditMode}
|
||
/>
|
||
</div>
|
||
<div>
|
||
<Label>浓度 (%)</Label>
|
||
<Input
|
||
type="number"
|
||
value={tank.concentration}
|
||
onChange={(e) => handleTankUpdate(tank.id, 'concentration', parseFloat(e.target.value))}
|
||
className="mt-2"
|
||
disabled={!isEditMode}
|
||
/>
|
||
</div>
|
||
<div>
|
||
<Label>配比比例 (%)</Label>
|
||
<Input
|
||
type="number"
|
||
value={tank.ratio}
|
||
onChange={(e) => handleTankUpdate(tank.id, 'ratio', parseFloat(e.target.value))}
|
||
className="mt-2"
|
||
disabled={!isEditMode}
|
||
/>
|
||
</div>
|
||
<div>
|
||
<Label>当前液位 (L)</Label>
|
||
<div className="mt-2 px-3 py-2 bg-white rounded-md border flex items-center justify-between">
|
||
<span>{tank.currentLevel}</span>
|
||
<Badge variant="outline">{Math.round((tank.currentLevel / 500) * 100)}%</Badge>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</Card>
|
||
))}
|
||
|
||
<div className="bg-blue-50 border border-blue-200 rounded-lg p-4">
|
||
<div className="flex items-start gap-2">
|
||
<AlertCircle className="w-5 h-5 text-blue-600 flex-shrink-0 mt-0.5" />
|
||
<div className="text-sm text-blue-900">
|
||
<p className="mb-1">配比说明:</p>
|
||
<p className="text-xs">总配比: {tanks.reduce((sum, t) => sum + t.ratio, 0)}% (建议为100%)</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</Card>
|
||
|
||
{/* 水肥机EC/PH设置 */}
|
||
<Card className="p-6">
|
||
<h4 className="mb-4 flex items-center gap-2">
|
||
<TestTube className="w-5 h-5 text-purple-600" />
|
||
水肥机EC/PH目标值设置
|
||
</h4>
|
||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||
<div>
|
||
<div className="flex items-center justify-between mb-2">
|
||
<Label>EC目标值 (mS/cm)</Label>
|
||
<Badge variant="outline">{machine.ecTarget}</Badge>
|
||
</div>
|
||
<Slider
|
||
value={[machine.ecTarget]}
|
||
onValueChange={(v) => handleMachineUpdate('ecTarget', v[0])}
|
||
min={0.5}
|
||
max={5}
|
||
step={0.1}
|
||
className="mt-2"
|
||
disabled={!isEditMode}
|
||
/>
|
||
<div className="flex justify-between text-xs text-muted-foreground mt-2">
|
||
<span>当前EC: {machine.ecValue}</span>
|
||
<span>偏差: {(machine.ecValue - machine.ecTarget).toFixed(2)}</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div>
|
||
<div className="flex items-center justify-between mb-2">
|
||
<Label>PH目标值</Label>
|
||
<Badge variant="outline">{machine.phTarget}</Badge>
|
||
</div>
|
||
<Slider
|
||
value={[machine.phTarget]}
|
||
onValueChange={(v) => handleMachineUpdate('phTarget', v[0])}
|
||
min={4}
|
||
max={9}
|
||
step={0.1}
|
||
className="mt-2"
|
||
disabled={!isEditMode}
|
||
/>
|
||
<div className="flex justify-between text-xs text-muted-foreground mt-2">
|
||
<span>当前PH: {machine.phValue}</span>
|
||
<span>偏差: {(machine.phValue - machine.phTarget).toFixed(2)}</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="mt-6 grid grid-cols-2 gap-4">
|
||
<Card className="p-4 bg-blue-50">
|
||
<p className="text-xs text-muted-foreground mb-1">EC状态</p>
|
||
<div className="flex items-center gap-2">
|
||
<Zap className="w-5 h-5 text-blue-600" />
|
||
<p className="text-lg">
|
||
{Math.abs(machine.ecValue - machine.ecTarget) <= 0.2 ? '正常' : '偏差'}
|
||
</p>
|
||
<Badge className={Math.abs(machine.ecValue - machine.ecTarget) <= 0.2 ? 'bg-green-100 text-green-700' : 'bg-orange-100 text-orange-700'}>
|
||
{Math.abs(machine.ecValue - machine.ecTarget) <= 0.2 ? '✓' : '!'}
|
||
</Badge>
|
||
</div>
|
||
</Card>
|
||
|
||
<Card className="p-4 bg-purple-50">
|
||
<p className="text-xs text-muted-foreground mb-1">PH状态</p>
|
||
<div className="flex items-center gap-2">
|
||
<TestTube className="w-5 h-5 text-purple-600" />
|
||
<p className="text-lg">
|
||
{Math.abs(machine.phValue - machine.phTarget) <= 0.3 ? '正常' : '偏差'}
|
||
</p>
|
||
<Badge className={Math.abs(machine.phValue - machine.phTarget) <= 0.3 ? 'bg-green-100 text-green-700' : 'bg-orange-100 text-orange-700'}>
|
||
{Math.abs(machine.phValue - machine.phTarget) <= 0.3 ? '✓' : '!'}
|
||
</Badge>
|
||
</div>
|
||
</Card>
|
||
</div>
|
||
</Card>
|
||
</TabsContent>
|
||
|
||
{/* 肥料桶实时监测 */}
|
||
<TabsContent value="tank-realtime" className="space-y-4">
|
||
<Card className="p-4 bg-gradient-to-r from-blue-50 to-cyan-50 border-blue-200">
|
||
<div className="flex items-start gap-2">
|
||
<Activity className="w-5 h-5 text-blue-600 flex-shrink-0 mt-0.5" />
|
||
<div className="text-sm text-blue-900">
|
||
<p className="mb-2">肥料桶实时监测数据:</p>
|
||
<ul className="space-y-1 text-xs">
|
||
<li>• <strong>液位监测</strong>: 实时展示各肥料桶液位高度</li>
|
||
<li>• <strong>流量趋势</strong>: 近2小时累计流量变化趋势</li>
|
||
<li>• <strong>EC监测</strong>: 实时EC值与设定值对比</li>
|
||
<li>• <strong>PH监测</strong>: 实时PH值与设定值对比</li>
|
||
<li>• <strong>组合图表</strong>: 多维度数据综合展示</li>
|
||
<li>• <strong>动态掌握</strong>: 动态掌握施肥状态</li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
</Card>
|
||
|
||
{/* 实时指标卡片 */}
|
||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||
{tanks.map((tank) => (
|
||
<Card key={tank.id} className="p-6">
|
||
<div className="flex items-center justify-between mb-4">
|
||
<h4 className="flex items-center gap-2">
|
||
<Beaker className="w-5 h-5 text-blue-600" />
|
||
{tank.name}
|
||
</h4>
|
||
<Badge variant="outline">{tank.tankNo}桶</Badge>
|
||
</div>
|
||
<div className="space-y-3">
|
||
<div>
|
||
<p className="text-xs text-muted-foreground">当前液位</p>
|
||
<div className="flex items-baseline gap-2 mt-1">
|
||
<p className="text-2xl text-blue-600">{tank.currentLevel}</p>
|
||
<p className="text-sm text-muted-foreground">L</p>
|
||
</div>
|
||
<Progress value={Math.round((tank.currentLevel / 500) * 100)} className="h-2 mt-2" />
|
||
</div>
|
||
<div>
|
||
<p className="text-xs text-muted-foreground">实时流量</p>
|
||
<div className="flex items-baseline gap-2 mt-1">
|
||
<p className="text-2xl text-green-600">{tank.flowRate}</p>
|
||
<p className="text-sm text-muted-foreground">L/h</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</Card>
|
||
))}
|
||
</div>
|
||
|
||
{/* 液位趋势图 */}
|
||
<Card className="p-6">
|
||
<h4 className="mb-4 flex items-center gap-2">
|
||
<LineChartIcon className="w-5 h-5 text-blue-600" />
|
||
各肥料桶液位变化趋势(最近2小时)
|
||
</h4>
|
||
<ResponsiveContainer width="100%" height={300}>
|
||
<LineChart data={realtimeData}>
|
||
<CartesianGrid strokeDasharray="3 3" />
|
||
<XAxis dataKey="time" />
|
||
<YAxis label={{ value: '液位 (L)', angle: -90, position: 'insideLeft' }} />
|
||
<RechartsTooltip />
|
||
<Legend />
|
||
<Line type="monotone" dataKey="tankALevel" stroke="#3b82f6" strokeWidth={2} name="A桶液位" />
|
||
<Line type="monotone" dataKey="tankBLevel" stroke="#a855f7" strokeWidth={2} name="B桶液位" />
|
||
<Line type="monotone" dataKey="tankCLevel" stroke="#10b981" strokeWidth={2} name="C桶液位" />
|
||
</LineChart>
|
||
</ResponsiveContainer>
|
||
</Card>
|
||
|
||
{/* 流量趋势图 */}
|
||
<Card className="p-6">
|
||
<h4 className="mb-4 flex items-center gap-2">
|
||
<Waves className="w-5 h-5 text-green-600" />
|
||
各肥料桶累计流量趋势(最近2小时)
|
||
</h4>
|
||
<ResponsiveContainer width="100%" height={300}>
|
||
<AreaChart data={realtimeData}>
|
||
<CartesianGrid strokeDasharray="3 3" />
|
||
<XAxis dataKey="time" />
|
||
<YAxis label={{ value: '流量 (L/h)', angle: -90, position: 'insideLeft' }} />
|
||
<RechartsTooltip />
|
||
<Legend />
|
||
<Area type="monotone" dataKey="tankAFlow" stackId="1" stroke="#3b82f6" fill="#3b82f6" fillOpacity={0.6} name="A桶流量" />
|
||
<Area type="monotone" dataKey="tankBFlow" stackId="1" stroke="#a855f7" fill="#a855f7" fillOpacity={0.6} name="B桶流量" />
|
||
<Area type="monotone" dataKey="tankCFlow" stackId="1" stroke="#10b981" fill="#10b981" fillOpacity={0.6} name="C桶流量" />
|
||
</AreaChart>
|
||
</ResponsiveContainer>
|
||
</Card>
|
||
|
||
{/* EC/PH监测 */}
|
||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||
<Card className="p-6">
|
||
<h4 className="mb-4 flex items-center gap-2">
|
||
<Zap className="w-5 h-5 text-blue-600" />
|
||
水肥机EC值监测
|
||
</h4>
|
||
<ResponsiveContainer width="100%" height={250}>
|
||
<ComposedChart data={realtimeData}>
|
||
<CartesianGrid strokeDasharray="3 3" />
|
||
<XAxis dataKey="time" />
|
||
<YAxis />
|
||
<RechartsTooltip />
|
||
<Legend />
|
||
<Line type="monotone" dataKey="ec" stroke="#3b82f6" strokeWidth={2} name="实时EC" />
|
||
<Line
|
||
type="monotone"
|
||
dataKey={() => machine.ecTarget}
|
||
stroke="#f97316"
|
||
strokeWidth={2}
|
||
strokeDasharray="5 5"
|
||
name="EC设定值"
|
||
/>
|
||
</ComposedChart>
|
||
</ResponsiveContainer>
|
||
</Card>
|
||
|
||
<Card className="p-6">
|
||
<h4 className="mb-4 flex items-center gap-2">
|
||
<TestTube className="w-5 h-5 text-purple-600" />
|
||
水肥机PH值监测
|
||
</h4>
|
||
<ResponsiveContainer width="100%" height={250}>
|
||
<ComposedChart data={realtimeData}>
|
||
<CartesianGrid strokeDasharray="3 3" />
|
||
<XAxis dataKey="time" />
|
||
<YAxis />
|
||
<RechartsTooltip />
|
||
<Legend />
|
||
<Line type="monotone" dataKey="ph" stroke="#a855f7" strokeWidth={2} name="实时PH" />
|
||
<Line
|
||
type="monotone"
|
||
dataKey={() => machine.phTarget}
|
||
stroke="#f97316"
|
||
strokeWidth={2}
|
||
strokeDasharray="5 5"
|
||
name="PH设定值"
|
||
/>
|
||
</ComposedChart>
|
||
</ResponsiveContainer>
|
||
</Card>
|
||
</div>
|
||
</TabsContent>
|
||
|
||
{/* 阀门控制 */}
|
||
<TabsContent value="valve-control" className="space-y-4">
|
||
<Card className="p-4 bg-gradient-to-r from-purple-50 to-pink-50 border-purple-200">
|
||
<div className="flex items-start gap-2">
|
||
<Droplets className="w-5 h-5 text-purple-600 flex-shrink-0 mt-0.5" />
|
||
<div className="text-sm text-purple-900">
|
||
<p className="mb-2">阀门控制功能:</p>
|
||
<ul className="space-y-1 text-xs">
|
||
<li>• <strong>状态展示</strong>: 展示各地块阀门实时状态</li>
|
||
<li>• <strong>电池监测</strong>: 电池电压实时监控</li>
|
||
<li>• <strong>光伏监测</strong>: 光伏电压实时监控</li>
|
||
<li>• <strong>开关状态</strong>: 开启/关闭/故障状态显示</li>
|
||
<li>• <strong>远程控制</strong>: 支持远程手动开启或关闭</li>
|
||
<li>• <strong>灵活控制</strong>: 实现灌溉区域灵活控制</li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
</Card>
|
||
|
||
{/* 阀门列表 */}
|
||
<div className="grid grid-cols-1 gap-4">
|
||
{valves.map((valve) => (
|
||
<Card key={valve.id} className="p-6">
|
||
<div className="flex items-start justify-between mb-4">
|
||
<div className="flex-1">
|
||
<div className="flex items-center gap-2 mb-2">
|
||
<h4>{valve.valveNo}</h4>
|
||
<Badge variant="outline">{valve.fieldName}</Badge>
|
||
<Badge className={getValveStatusColor(valve.status)}>
|
||
{valve.status}
|
||
</Badge>
|
||
<Badge variant="outline">
|
||
<MapPin className="w-3 h-3 mr-1" />
|
||
{valve.location}
|
||
</Badge>
|
||
</div>
|
||
</div>
|
||
<div className="flex gap-2">
|
||
{valve.status !== '故障' && (
|
||
<Button
|
||
variant={valve.status === '开启' ? 'outline' : 'default'}
|
||
className={valve.status === '关闭' ? 'bg-green-600 hover:bg-green-700' : ''}
|
||
onClick={() => handleValveToggle(valve.id)}
|
||
>
|
||
{valve.status === '开启' ? (
|
||
<>
|
||
<Pause className="w-4 h-4 mr-2" />
|
||
关闭阀门
|
||
</>
|
||
) : (
|
||
<>
|
||
<Play className="w-4 h-4 mr-2" />
|
||
开启阀门
|
||
</>
|
||
)}
|
||
</Button>
|
||
)}
|
||
{valve.status === '故障' && (
|
||
<Button variant="outline" disabled>
|
||
<XCircle className="w-4 h-4 mr-2" />
|
||
故障维修中
|
||
</Button>
|
||
)}
|
||
</div>
|
||
</div>
|
||
|
||
<div className="grid grid-cols-2 md:grid-cols-5 gap-4">
|
||
<Card className="p-4 bg-yellow-50">
|
||
<div className="flex items-center gap-2 mb-2">
|
||
<Battery className="w-4 h-4 text-yellow-600" />
|
||
<p className="text-xs text-muted-foreground">电池电压</p>
|
||
</div>
|
||
<p className="text-lg">{valve.batteryVoltage} V</p>
|
||
<Progress
|
||
value={Math.min((valve.batteryVoltage / 13) * 100, 100)}
|
||
className="h-1 mt-2"
|
||
/>
|
||
</Card>
|
||
|
||
<Card className="p-4 bg-orange-50">
|
||
<div className="flex items-center gap-2 mb-2">
|
||
<Sun className="w-4 h-4 text-orange-600" />
|
||
<p className="text-xs text-muted-foreground">光伏电压</p>
|
||
</div>
|
||
<p className="text-lg">{valve.solarVoltage} V</p>
|
||
<Progress
|
||
value={Math.min((valve.solarVoltage / 20) * 100, 100)}
|
||
className="h-1 mt-2"
|
||
/>
|
||
</Card>
|
||
|
||
<Card className="p-4 bg-blue-50">
|
||
<div className="flex items-center gap-2 mb-2">
|
||
<Gauge className="w-4 h-4 text-blue-600" />
|
||
<p className="text-xs text-muted-foreground">水压</p>
|
||
</div>
|
||
<p className="text-lg">{valve.pressure} bar</p>
|
||
</Card>
|
||
|
||
<Card className="p-4 bg-green-50">
|
||
<div className="flex items-center gap-2 mb-2">
|
||
<Waves className="w-4 h-4 text-green-600" />
|
||
<p className="text-xs text-muted-foreground">流量</p>
|
||
</div>
|
||
<p className="text-lg">{valve.flowRate} L/min</p>
|
||
</Card>
|
||
|
||
<Card className="p-4 bg-purple-50">
|
||
<div className="flex items-center gap-2 mb-2">
|
||
<Droplets className="w-4 h-4 text-purple-600" />
|
||
<p className="text-xs text-muted-foreground">累计水量</p>
|
||
</div>
|
||
<p className="text-lg">{valve.totalVolume} L</p>
|
||
</Card>
|
||
</div>
|
||
</Card>
|
||
))}
|
||
</div>
|
||
</TabsContent>
|
||
|
||
{/* 电动阀实时监测 */}
|
||
<TabsContent value="valve-realtime" className="space-y-4">
|
||
<Card className="p-4 bg-gradient-to-r from-orange-50 to-amber-50 border-orange-200">
|
||
<div className="flex items-start gap-2">
|
||
<Gauge className="w-5 h-5 text-orange-600 flex-shrink-0 mt-0.5" />
|
||
<div className="text-sm text-orange-900">
|
||
<p className="mb-2">电动阀实时监测数据:</p>
|
||
<ul className="space-y-1 text-xs">
|
||
<li>• <strong>多维图表</strong>: 水压、瞬时流量、累计流量变化</li>
|
||
<li>• <strong>时间范围</strong>: 展示近2小时变化趋势</li>
|
||
<li>• <strong>土壤数据</strong>: 同步显示对应地块土层温湿度</li>
|
||
<li>• <strong>分层监测</strong>: 表层、中层、深层三层数据</li>
|
||
<li>• <strong>决策依据</strong>: 为灌溉决策提供全面环境依据</li>
|
||
<li>• <strong>实时更新</strong>: 数据实时刷新显示</li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
</Card>
|
||
|
||
{/* 选择阀门 */}
|
||
<Card className="p-4">
|
||
<div className="flex items-center gap-4">
|
||
<Label>选择电动阀</Label>
|
||
<Select value={selectedValveId} onValueChange={setSelectedValveId}>
|
||
<SelectTrigger className="w-80">
|
||
<SelectValue />
|
||
</SelectTrigger>
|
||
<SelectContent>
|
||
{valves.map((valve) => (
|
||
<SelectItem key={valve.id} value={valve.id}>
|
||
{valve.valveNo} - {valve.fieldName} ({valve.location})
|
||
</SelectItem>
|
||
))}
|
||
</SelectContent>
|
||
</Select>
|
||
<Badge className={getValveStatusColor(selectedValve.status)}>
|
||
{selectedValve.status}
|
||
</Badge>
|
||
</div>
|
||
</Card>
|
||
|
||
{/* 电动阀监测数据 */}
|
||
<Card className="p-6">
|
||
<h4 className="mb-4 flex items-center gap-2">
|
||
<Gauge className="w-5 h-5 text-blue-600" />
|
||
{selectedValve.valveNo} 水压变化趋势(最近2小时)
|
||
</h4>
|
||
<ResponsiveContainer width="100%" height={250}>
|
||
<AreaChart data={realtimeData}>
|
||
<CartesianGrid strokeDasharray="3 3" />
|
||
<XAxis dataKey="time" />
|
||
<YAxis />
|
||
<RechartsTooltip />
|
||
<Area
|
||
type="monotone"
|
||
dataKey="pressure"
|
||
stroke="#3b82f6"
|
||
fill="#3b82f6"
|
||
fillOpacity={0.3}
|
||
name="水压 (bar)"
|
||
/>
|
||
</AreaChart>
|
||
</ResponsiveContainer>
|
||
</Card>
|
||
|
||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||
<Card className="p-6">
|
||
<h4 className="mb-4 flex items-center gap-2">
|
||
<Waves className="w-5 h-5 text-green-600" />
|
||
瞬时流量变化
|
||
</h4>
|
||
<ResponsiveContainer width="100%" height={250}>
|
||
<LineChart data={realtimeData}>
|
||
<CartesianGrid strokeDasharray="3 3" />
|
||
<XAxis dataKey="time" />
|
||
<YAxis />
|
||
<RechartsTooltip />
|
||
<Line
|
||
type="monotone"
|
||
dataKey="flowRate"
|
||
stroke="#10b981"
|
||
strokeWidth={2}
|
||
name="流量 (L/min)"
|
||
/>
|
||
</LineChart>
|
||
</ResponsiveContainer>
|
||
</Card>
|
||
|
||
<Card className="p-6">
|
||
<h4 className="mb-4 flex items-center gap-2">
|
||
<Droplets className="w-5 h-5 text-purple-600" />
|
||
累计流量变化
|
||
</h4>
|
||
<ResponsiveContainer width="100%" height={250}>
|
||
<AreaChart data={realtimeData}>
|
||
<CartesianGrid strokeDasharray="3 3" />
|
||
<XAxis dataKey="time" />
|
||
<YAxis />
|
||
<RechartsTooltip />
|
||
<Area
|
||
type="monotone"
|
||
dataKey="totalVolume"
|
||
stroke="#a855f7"
|
||
fill="#a855f7"
|
||
fillOpacity={0.3}
|
||
name="累计流量 (L)"
|
||
/>
|
||
</AreaChart>
|
||
</ResponsiveContainer>
|
||
</Card>
|
||
</div>
|
||
|
||
{/* 土壤温湿度数据 */}
|
||
<Card className="p-6">
|
||
<h4 className="mb-4 flex items-center gap-2">
|
||
<Layers className="w-5 h-5 text-green-600" />
|
||
{selectedValve.fieldName} 土壤温湿度数据
|
||
</h4>
|
||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||
{soilData.map((soil, index) => (
|
||
<Card key={index} className="p-4 bg-gradient-to-br from-green-50 to-teal-50">
|
||
<div className="flex items-center justify-between mb-3">
|
||
<h4>{soil.layer}</h4>
|
||
<Badge variant="outline">{soil.depth}</Badge>
|
||
</div>
|
||
<div className="space-y-3">
|
||
<div>
|
||
<div className="flex items-center gap-2 mb-1">
|
||
<ThermometerSun className="w-4 h-4 text-orange-600" />
|
||
<p className="text-xs text-muted-foreground">温度</p>
|
||
</div>
|
||
<p className="text-2xl text-orange-600">{soil.temperature}°C</p>
|
||
</div>
|
||
<div>
|
||
<div className="flex items-center gap-2 mb-1">
|
||
<Droplets className="w-4 h-4 text-blue-600" />
|
||
<p className="text-xs text-muted-foreground">湿度</p>
|
||
</div>
|
||
<p className="text-2xl text-blue-600">{soil.humidity}%</p>
|
||
<Progress value={soil.humidity} className="h-2 mt-2" />
|
||
</div>
|
||
</div>
|
||
</Card>
|
||
))}
|
||
</div>
|
||
</Card>
|
||
</TabsContent>
|
||
|
||
{/* 施肥与配比历史数据 */}
|
||
<TabsContent value="fertilizer-history" className="space-y-4">
|
||
<Card className="p-4 bg-gradient-to-r from-green-50 to-teal-50 border-green-200">
|
||
<div className="flex items-start gap-2">
|
||
<BarChart3 className="w-5 h-5 text-green-600 flex-shrink-0 mt-0.5" />
|
||
<div className="text-sm text-green-900">
|
||
<p className="mb-2">施肥与配比历史数据:</p>
|
||
<ul className="space-y-1 text-xs">
|
||
<li>• <strong>流量统计</strong>: 各肥料桶近7天累计流量变化</li>
|
||
<li>• <strong>EC历史</strong>: 水肥机EC历史数据曲线</li>
|
||
<li>• <strong>PH历史</strong>: 水肥机PH历史数据曲线</li>
|
||
<li>• <strong>长期查询</strong>: 支持最长30天历史查询</li>
|
||
<li>• <strong>效果分析</strong>: 助力分析施肥效果</li>
|
||
<li>• <strong>稳定性评估</strong>: 评估系统稳定性</li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
</Card>
|
||
|
||
{/* 时间范围选择 */}
|
||
<Card className="p-4">
|
||
<div className="flex items-center gap-4">
|
||
<Label>查询时段</Label>
|
||
<Select value={historyDateRange} onValueChange={setHistoryDateRange}>
|
||
<SelectTrigger className="w-40">
|
||
<SelectValue />
|
||
</SelectTrigger>
|
||
<SelectContent>
|
||
<SelectItem value="1">最近1天</SelectItem>
|
||
<SelectItem value="3">最近3天</SelectItem>
|
||
<SelectItem value="7">最近7天</SelectItem>
|
||
<SelectItem value="15">最近15天</SelectItem>
|
||
<SelectItem value="30">最近30天</SelectItem>
|
||
</SelectContent>
|
||
</Select>
|
||
<Button variant="outline">
|
||
<Calendar className="w-4 h-4 mr-2" />
|
||
自定义时段
|
||
</Button>
|
||
</div>
|
||
</Card>
|
||
|
||
{/* 统计卡片 */}
|
||
<div className="grid grid-cols-1 md:grid-cols-5 gap-4">
|
||
<Card className="p-4">
|
||
<p className="text-xs text-muted-foreground mb-2">A桶总流量</p>
|
||
<p className="text-2xl text-blue-600">
|
||
{historyData.reduce((sum, d) => sum + d.tankAFlow, 0)} L
|
||
</p>
|
||
<p className="text-xs text-green-600 mt-1 flex items-center gap-1">
|
||
<TrendingUp className="w-3 h-3" />
|
||
稳定
|
||
</p>
|
||
</Card>
|
||
|
||
<Card className="p-4">
|
||
<p className="text-xs text-muted-foreground mb-2">B桶总流量</p>
|
||
<p className="text-2xl text-purple-600">
|
||
{historyData.reduce((sum, d) => sum + d.tankBFlow, 0)} L
|
||
</p>
|
||
<p className="text-xs text-green-600 mt-1 flex items-center gap-1">
|
||
<TrendingUp className="w-3 h-3" />
|
||
稳定
|
||
</p>
|
||
</Card>
|
||
|
||
<Card className="p-4">
|
||
<p className="text-xs text-muted-foreground mb-2">C桶总流量</p>
|
||
<p className="text-2xl text-green-600">
|
||
{historyData.reduce((sum, d) => sum + d.tankCFlow, 0)} L
|
||
</p>
|
||
<p className="text-xs text-green-600 mt-1 flex items-center gap-1">
|
||
<TrendingUp className="w-3 h-3" />
|
||
稳定
|
||
</p>
|
||
</Card>
|
||
|
||
<Card className="p-4">
|
||
<p className="text-xs text-muted-foreground mb-2">平均EC值</p>
|
||
<p className="text-2xl text-orange-600">
|
||
{(historyData.reduce((sum, d) => sum + d.avgEc, 0) / historyData.length).toFixed(2)}
|
||
</p>
|
||
<p className="text-xs text-muted-foreground mt-1">mS/cm</p>
|
||
</Card>
|
||
|
||
<Card className="p-4">
|
||
<p className="text-xs text-muted-foreground mb-2">平均PH值</p>
|
||
<p className="text-2xl text-purple-600">
|
||
{(historyData.reduce((sum, d) => sum + d.avgPh, 0) / historyData.length).toFixed(2)}
|
||
</p>
|
||
<p className="text-xs text-muted-foreground mt-1">pH</p>
|
||
</Card>
|
||
</div>
|
||
|
||
{/* 肥料桶流量历史 */}
|
||
<Card className="p-6">
|
||
<h4 className="mb-4 flex items-center gap-2">
|
||
<BarChart3 className="w-5 h-5 text-blue-600" />
|
||
各肥料桶累计流量历史(最近{historyDateRange}天)
|
||
</h4>
|
||
<ResponsiveContainer width="100%" height={300}>
|
||
<BarChart data={historyData}>
|
||
<CartesianGrid strokeDasharray="3 3" />
|
||
<XAxis dataKey="date" />
|
||
<YAxis label={{ value: '流量 (L)', angle: -90, position: 'insideLeft' }} />
|
||
<RechartsTooltip />
|
||
<Legend />
|
||
<Bar dataKey="tankAFlow" fill="#3b82f6" name="A桶流量" />
|
||
<Bar dataKey="tankBFlow" fill="#a855f7" name="B桶流量" />
|
||
<Bar dataKey="tankCFlow" fill="#10b981" name="C桶流量" />
|
||
</BarChart>
|
||
</ResponsiveContainer>
|
||
</Card>
|
||
|
||
{/* EC/PH历史趋势 */}
|
||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||
<Card className="p-6">
|
||
<h4 className="mb-4 flex items-center gap-2">
|
||
<Zap className="w-5 h-5 text-blue-600" />
|
||
水肥机EC历史数据
|
||
</h4>
|
||
<ResponsiveContainer width="100%" height={250}>
|
||
<LineChart data={historyData}>
|
||
<CartesianGrid strokeDasharray="3 3" />
|
||
<XAxis dataKey="date" />
|
||
<YAxis />
|
||
<RechartsTooltip />
|
||
<Legend />
|
||
<Line type="monotone" dataKey="avgEc" stroke="#3b82f6" strokeWidth={2} name="平均EC (mS/cm)" />
|
||
</LineChart>
|
||
</ResponsiveContainer>
|
||
</Card>
|
||
|
||
<Card className="p-6">
|
||
<h4 className="mb-4 flex items-center gap-2">
|
||
<TestTube className="w-5 h-5 text-purple-600" />
|
||
水肥机PH历史数据
|
||
</h4>
|
||
<ResponsiveContainer width="100%" height={250}>
|
||
<LineChart data={historyData}>
|
||
<CartesianGrid strokeDasharray="3 3" />
|
||
<XAxis dataKey="date" />
|
||
<YAxis />
|
||
<RechartsTooltip />
|
||
<Legend />
|
||
<Line type="monotone" dataKey="avgPh" stroke="#a855f7" strokeWidth={2} name="平均PH" />
|
||
</LineChart>
|
||
</ResponsiveContainer>
|
||
</Card>
|
||
</div>
|
||
</TabsContent>
|
||
|
||
{/* 电动阀历史监测 */}
|
||
<TabsContent value="valve-history" className="space-y-4">
|
||
<Card className="p-4 bg-gradient-to-r from-purple-50 to-pink-50 border-purple-200">
|
||
<div className="flex items-start gap-2">
|
||
<LineChartIcon className="w-5 h-5 text-purple-600 flex-shrink-0 mt-0.5" />
|
||
<div className="text-sm text-purple-900">
|
||
<p className="mb-2">电动阀历史监测数据:</p>
|
||
<ul className="space-y-1 text-xs">
|
||
<li>• <strong>水压历史</strong>: 近7天水压变化趋势</li>
|
||
<li>• <strong>流量历史</strong>: 近7天流量变化趋势</li>
|
||
<li>• <strong>土壤数据</strong>: 对应地块土层温湿度历史</li>
|
||
<li>• <strong>30天查询</strong>: 支持30天内数据查询</li>
|
||
<li>• <strong>灌溉评估</strong>: 用于灌溉效果评估</li>
|
||
<li>• <strong>故障回溯</strong>: 故障追溯与策略优化</li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
</Card>
|
||
|
||
{/* 选择阀门 */}
|
||
<Card className="p-4">
|
||
<div className="flex items-center gap-4">
|
||
<Label>选择电动阀</Label>
|
||
<Select value={selectedValveId} onValueChange={setSelectedValveId}>
|
||
<SelectTrigger className="w-80">
|
||
<SelectValue />
|
||
</SelectTrigger>
|
||
<SelectContent>
|
||
{valves.map((valve) => (
|
||
<SelectItem key={valve.id} value={valve.id}>
|
||
{valve.valveNo} - {valve.fieldName} ({valve.location})
|
||
</SelectItem>
|
||
))}
|
||
</SelectContent>
|
||
</Select>
|
||
<Label>查询时段</Label>
|
||
<Select value={historyDateRange} onValueChange={setHistoryDateRange}>
|
||
<SelectTrigger className="w-40">
|
||
<SelectValue />
|
||
</SelectTrigger>
|
||
<SelectContent>
|
||
<SelectItem value="7">最近7天</SelectItem>
|
||
<SelectItem value="15">最近15天</SelectItem>
|
||
<SelectItem value="30">最近30天</SelectItem>
|
||
</SelectContent>
|
||
</Select>
|
||
</div>
|
||
</Card>
|
||
|
||
{/* 统计卡片 */}
|
||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||
<Card className="p-4">
|
||
<p className="text-xs text-muted-foreground mb-2">平均水压</p>
|
||
<p className="text-2xl text-blue-600">
|
||
{(historyData.reduce((sum, d) => sum + d.avgPressure, 0) / historyData.length).toFixed(2)} bar
|
||
</p>
|
||
<p className="text-xs text-green-600 mt-1 flex items-center gap-1">
|
||
<CheckCircle className="w-3 h-3" />
|
||
正常
|
||
</p>
|
||
</Card>
|
||
|
||
<Card className="p-4">
|
||
<p className="text-xs text-muted-foreground mb-2">平均流量</p>
|
||
<p className="text-2xl text-green-600">
|
||
{(historyData.reduce((sum, d) => sum + d.avgFlowRate, 0) / historyData.length).toFixed(1)} L/min
|
||
</p>
|
||
<p className="text-xs text-green-600 mt-1 flex items-center gap-1">
|
||
<CheckCircle className="w-3 h-3" />
|
||
稳定
|
||
</p>
|
||
</Card>
|
||
|
||
<Card className="p-4">
|
||
<p className="text-xs text-muted-foreground mb-2">运行天数</p>
|
||
<p className="text-2xl text-purple-600">{historyDateRange}</p>
|
||
<p className="text-xs text-muted-foreground mt-1">天</p>
|
||
</Card>
|
||
</div>
|
||
|
||
{/* 水压历史趋势 */}
|
||
<Card className="p-6">
|
||
<h4 className="mb-4 flex items-center gap-2">
|
||
<Gauge className="w-5 h-5 text-blue-600" />
|
||
{selectedValve.valveNo} 水压历史趋势(最近{historyDateRange}天)
|
||
</h4>
|
||
<ResponsiveContainer width="100%" height={300}>
|
||
<AreaChart data={historyData}>
|
||
<CartesianGrid strokeDasharray="3 3" />
|
||
<XAxis dataKey="date" />
|
||
<YAxis label={{ value: '水压 (bar)', angle: -90, position: 'insideLeft' }} />
|
||
<RechartsTooltip />
|
||
<Area
|
||
type="monotone"
|
||
dataKey="avgPressure"
|
||
stroke="#3b82f6"
|
||
fill="#3b82f6"
|
||
fillOpacity={0.3}
|
||
name="平均水压"
|
||
/>
|
||
</AreaChart>
|
||
</ResponsiveContainer>
|
||
</Card>
|
||
|
||
{/* 流量历史趋势 */}
|
||
<Card className="p-6">
|
||
<h4 className="mb-4 flex items-center gap-2">
|
||
<Waves className="w-5 h-5 text-green-600" />
|
||
{selectedValve.valveNo} 流量历史趋势(最近{historyDateRange}天)
|
||
</h4>
|
||
<ResponsiveContainer width="100%" height={300}>
|
||
<LineChart data={historyData}>
|
||
<CartesianGrid strokeDasharray="3 3" />
|
||
<XAxis dataKey="date" />
|
||
<YAxis label={{ value: '流量 (L/min)', angle: -90, position: 'insideLeft' }} />
|
||
<RechartsTooltip />
|
||
<Line
|
||
type="monotone"
|
||
dataKey="avgFlowRate"
|
||
stroke="#10b981"
|
||
strokeWidth={2}
|
||
name="平均流量"
|
||
/>
|
||
</LineChart>
|
||
</ResponsiveContainer>
|
||
</Card>
|
||
|
||
{/* 土壤温湿度历史 */}
|
||
<Card className="p-6">
|
||
<h4 className="mb-4 flex items-center gap-2">
|
||
<ThermometerSun className="w-5 h-5 text-orange-600" />
|
||
{selectedValve.fieldName} 土壤温湿度历史趋势
|
||
</h4>
|
||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 mb-4">
|
||
{soilData.map((soil, index) => (
|
||
<Card key={index} className="p-3 bg-gray-50">
|
||
<div className="flex items-center justify-between">
|
||
<span className="text-sm">{soil.layer}</span>
|
||
<Badge variant="outline" className="text-xs">{soil.depth}</Badge>
|
||
</div>
|
||
</Card>
|
||
))}
|
||
</div>
|
||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||
<ResponsiveContainer width="100%" height={250}>
|
||
<LineChart data={historyData}>
|
||
<CartesianGrid strokeDasharray="3 3" />
|
||
<XAxis dataKey="date" />
|
||
<YAxis />
|
||
<RechartsTooltip />
|
||
<Legend />
|
||
<Line type="monotone" dataKey={() => 22 + Math.random() * 2} stroke="#f97316" strokeWidth={2} name="表层温度 (°C)" />
|
||
<Line type="monotone" dataKey={() => 21 + Math.random() * 2} stroke="#3b82f6" strokeWidth={2} name="中层温度 (°C)" />
|
||
<Line type="monotone" dataKey={() => 20 + Math.random() * 2} stroke="#10b981" strokeWidth={2} name="深层温度 (°C)" />
|
||
</LineChart>
|
||
</ResponsiveContainer>
|
||
|
||
<ResponsiveContainer width="100%" height={250}>
|
||
<LineChart data={historyData}>
|
||
<CartesianGrid strokeDasharray="3 3" />
|
||
<XAxis dataKey="date" />
|
||
<YAxis />
|
||
<RechartsTooltip />
|
||
<Legend />
|
||
<Line type="monotone" dataKey={() => 60 + Math.random() * 10} stroke="#f97316" strokeWidth={2} name="表层湿度 (%)" />
|
||
<Line type="monotone" dataKey={() => 68 + Math.random() * 10} stroke="#3b82f6" strokeWidth={2} name="中层湿度 (%)" />
|
||
<Line type="monotone" dataKey={() => 74 + Math.random() * 10} stroke="#10b981" strokeWidth={2} name="深层湿度 (%)" />
|
||
</LineChart>
|
||
</ResponsiveContainer>
|
||
</div>
|
||
</Card>
|
||
</TabsContent>
|
||
</Tabs>
|
||
</div>
|
||
);
|
||
}
|