Files
smart-crop-ui/src/components/irrigation/WaterFertilizerManagement.tsx

1843 lines
71 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

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

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 { Textarea } from '../ui/textarea';
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter } from '../ui/dialog';
import { Label } from '../ui/label';
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '../ui/table';
import { Switch } from '../ui/switch';
import { WaterFertilizerDevice } from './WaterFertilizerDevice';
import { WaterFertilizerComponent } from './WaterFertilizerComponent';
import { WaterFertilizerParameter } from './WaterFertilizerParameter';
import { WaterFertilizerMapping } from './WaterFertilizerMapping';
import {
Search,
Plus,
Edit,
Eye,
Trash2,
Download,
Upload,
Filter,
RefreshCw,
Settings,
CheckCircle,
XCircle,
AlertCircle,
Activity,
Cpu,
Zap,
Gauge,
Droplets,
Wifi,
MapPin,
Link,
GitBranch,
Layers,
Server,
HardDrive,
Signal,
Power,
Info,
Copy,
} from 'lucide-react';
import { toast } from 'sonner@2.0.3';
interface WaterFertilizerManagementProps {
activePath?: string;
}
type DeviceStatus = '在线' | '离线' | '故障' | '维护';
type ComponentType = '泵体' | '传感器' | '控制器' | '阀门' | '流量计';
type ParameterType = '压力' | '流量' | 'EC值' | 'PH值' | '温度' | '湿度';
type ParameterUnit = 'bar' | 'L/min' | 'mS/cm' | 'pH' | '℃' | '%';
type ProtocolType = 'Modbus-RTU' | 'Modbus-TCP' | 'HTTP' | 'MQTT' | 'OPC-UA';
interface WFDevice {
id: string;
deviceNo: string;
deviceName: string;
model: string;
manufacturer: string;
status: DeviceStatus;
fieldName: string;
location: string;
installDate: string;
ipAddress?: string;
port?: number;
protocol: ProtocolType;
lastOnlineTime: string;
workingHours: number;
notes?: string;
}
interface WFComponent {
id: string;
componentNo: string;
componentName: string;
type: ComponentType;
deviceName: string;
specification: string;
range: string;
accuracy: string;
unit: string;
manufacturer: string;
installDate: string;
status: '正常' | '异常' | '维护';
notes?: string;
}
interface WFParameter {
id: string;
parameterNo: string;
parameterName: string;
type: ParameterType;
unit: ParameterUnit;
minValue: number;
maxValue: number;
defaultValue: number;
warningThreshold?: number;
alarmThreshold?: number;
deviceName: string;
description: string;
isEnabled: boolean;
}
interface DeviceMapping {
id: string;
mappingNo: string;
logicName: string;
logicType: '阀门' | '泵' | '施肥机' | '传感器';
physicalDevice: string;
deviceAddress: string;
protocol: ProtocolType;
controlPoint: string;
readAddress?: string;
writeAddress?: string;
dataType: 'Boolean' | 'Int16' | 'Int32' | 'Float' | 'String';
status: '已映射' | '未映射' | '异常';
testResult?: '成功' | '失败';
lastTestTime?: string;
notes?: string;
}
export function WaterFertilizerManagement({ activePath }: WaterFertilizerManagementProps) {
// 如果是水肥机设备页面,直接渲染专门的设备管理组件
if (activePath?.includes('/device')) {
return <WaterFertilizerDevice />;
}
// 如果是水肥机部件配置页面,直接渲染专门的部件配置组件
if (activePath?.includes('/component')) {
return <WaterFertilizerComponent />;
}
// 如果是水肥机参数配置页面,直接渲染专门的参数配置组件
if (activePath?.includes('/parameter')) {
return <WaterFertilizerParameter />;
}
// 如果是水肥设备映射页面,直接渲染专门的映射管理组件
if (activePath?.includes('/mapping')) {
return <WaterFertilizerMapping />;
}
const [activeTab, setActiveTab] = useState('device');
const [showDeviceDialog, setShowDeviceDialog] = useState(false);
const [showComponentDialog, setShowComponentDialog] = useState(false);
const [showParameterDialog, setShowParameterDialog] = useState(false);
const [showMappingDialog, setShowMappingDialog] = useState(false);
const [showDetailDialog, setShowDetailDialog] = useState(false);
const [editingItem, setEditingItem] = useState<any>(null);
const [viewingItem, setViewingItem] = useState<any>(null);
// 根据路径自动切换Tab
useEffect(() => {
if (activePath) {
if (activePath.includes('/component')) {
setActiveTab('component');
} else if (activePath.includes('/parameter')) {
setActiveTab('parameter');
} else if (activePath.includes('/mapping')) {
setActiveTab('mapping');
}
}
}, [activePath]);
// 水肥机设备数据
const [devices, setDevices] = useState<WFDevice[]>([
{
id: 'dev-1',
deviceNo: 'WF-2024-001',
deviceName: '1号大棚水肥机',
model: 'SF-3000Pro',
manufacturer: '智农科技',
status: '在线',
fieldName: '1号大棚',
location: '大棚东南角',
installDate: '2024-03-15',
ipAddress: '192.168.1.101',
port: 502,
protocol: 'Modbus-TCP',
lastOnlineTime: '2024-10-15 18:30:25',
workingHours: 2580,
notes: '主力设备,性能稳定',
},
{
id: 'dev-2',
deviceNo: 'WF-2024-002',
deviceName: '2号田块水肥机',
model: 'SF-2000',
manufacturer: '农智通',
status: '在线',
fieldName: '2号田块',
location: '田块中央',
installDate: '2024-05-20',
ipAddress: '192.168.1.102',
port: 502,
protocol: 'Modbus-TCP',
lastOnlineTime: '2024-10-15 18:28:10',
workingHours: 1850,
},
{
id: 'dev-3',
deviceNo: 'WF-2024-003',
deviceName: '3号田块水肥机',
model: 'SF-3000Pro',
manufacturer: '智农科技',
status: '离线',
fieldName: '3号田块',
location: '田块北侧',
installDate: '2024-06-10',
ipAddress: '192.168.1.103',
port: 502,
protocol: 'Modbus-TCP',
lastOnlineTime: '2024-10-15 16:45:00',
workingHours: 1420,
notes: '通信异常,需检查',
},
]);
// 水肥机部件数据
const [components, setComponents] = useState<WFComponent[]>([
{
id: 'comp-1',
componentNo: 'COMP-001',
componentName: '主水泵',
type: '泵体',
deviceName: '1号大棚水肥机',
specification: '1.5kW/220V',
range: '0-50L/min',
accuracy: '±2%',
unit: 'L/min',
manufacturer: '格兰富',
installDate: '2024-03-15',
status: '正常',
},
{
id: 'comp-2',
componentNo: 'COMP-002',
componentName: 'EC传感器',
type: '传感器',
deviceName: '1号大棚水肥机',
specification: 'EC-200',
range: '0-5 mS/cm',
accuracy: '±0.01 mS/cm',
unit: 'mS/cm',
manufacturer: '梅特勒-托利多',
installDate: '2024-03-15',
status: '正常',
},
{
id: 'comp-3',
componentNo: 'COMP-003',
componentName: 'PH传感器',
type: '传感器',
deviceName: '1号大棚水肥机',
specification: 'PH-300',
range: '0-14 pH',
accuracy: '±0.02 pH',
unit: 'pH',
manufacturer: '梅特勒-托利多',
installDate: '2024-03-15',
status: '正常',
},
{
id: 'comp-4',
componentNo: 'COMP-004',
componentName: 'PLC控制器',
type: '控制器',
deviceName: '1号大棚水肥机',
specification: 'S7-1200',
range: '-',
accuracy: '-',
unit: '-',
manufacturer: '西门子',
installDate: '2024-03-15',
status: '正常',
},
{
id: 'comp-5',
componentNo: 'COMP-005',
componentName: '流量计',
type: '流量计',
deviceName: '2号田块水肥机',
specification: 'FM-100',
range: '0-100L/min',
accuracy: '±1%',
unit: 'L/min',
manufacturer: '艾默生',
installDate: '2024-05-20',
status: '正常',
},
]);
// 水肥机参数数据
const [parameters, setParameters] = useState<WFParameter[]>([
{
id: 'param-1',
parameterNo: 'PARAM-001',
parameterName: '系统压力',
type: '压力',
unit: 'bar',
minValue: 0,
maxValue: 10,
defaultValue: 2.5,
warningThreshold: 8,
alarmThreshold: 9.5,
deviceName: '1号大棚水肥机',
description: '系统主管道压力正常工作范围2-6bar',
isEnabled: true,
},
{
id: 'param-2',
parameterNo: 'PARAM-002',
parameterName: '灌溉流量',
type: '流量',
unit: 'L/min',
minValue: 0,
maxValue: 50,
defaultValue: 20,
warningThreshold: 45,
alarmThreshold: 48,
deviceName: '1号大棚水肥机',
description: '主管道流量建议设置20-30L/min',
isEnabled: true,
},
{
id: 'param-3',
parameterNo: 'PARAM-003',
parameterName: '溶液EC值',
type: 'EC值',
unit: 'mS/cm',
minValue: 0,
maxValue: 5,
defaultValue: 2.0,
warningThreshold: 4.5,
alarmThreshold: 4.8,
deviceName: '1号大棚水肥机',
description: '营养液电导率番茄适宜范围1.8-2.5 mS/cm',
isEnabled: true,
},
{
id: 'param-4',
parameterNo: 'PARAM-004',
parameterName: '溶液PH值',
type: 'PH值',
unit: 'pH',
minValue: 0,
maxValue: 14,
defaultValue: 6.5,
warningThreshold: 8,
alarmThreshold: 9,
deviceName: '1号大棚水肥机',
description: '营养液酸碱度大多数作物适宜范围5.5-7.0',
isEnabled: true,
},
{
id: 'param-5',
parameterNo: 'PARAM-005',
parameterName: '水温',
type: '温度',
unit: '℃',
minValue: 0,
maxValue: 50,
defaultValue: 20,
warningThreshold: 35,
alarmThreshold: 40,
deviceName: '2号田块水肥机',
description: '灌溉水温度建议15-25℃',
isEnabled: true,
},
]);
// 设备映射数据
const [mappings, setMappings] = useState<DeviceMapping[]>([
{
id: 'map-1',
mappingNo: 'MAP-001',
logicName: '1号主阀门',
logicType: '阀门',
physicalDevice: '1号大棚水肥机-电磁阀1',
deviceAddress: '192.168.1.101:502',
protocol: 'Modbus-TCP',
controlPoint: 'DO0',
readAddress: '40001',
writeAddress: '40001',
dataType: 'Boolean',
status: '已映射',
testResult: '成功',
lastTestTime: '2024-10-15 10:30:00',
},
{
id: 'map-2',
mappingNo: 'MAP-002',
logicName: '1号主水泵',
logicType: '泵',
physicalDevice: '1号大棚水肥机-水泵1',
deviceAddress: '192.168.1.101:502',
protocol: 'Modbus-TCP',
controlPoint: 'DO1',
readAddress: '40002',
writeAddress: '40002',
dataType: 'Boolean',
status: '已映射',
testResult: '成功',
lastTestTime: '2024-10-15 10:30:00',
},
{
id: 'map-3',
mappingNo: 'MAP-003',
logicName: '1号施肥机-A通道',
logicType: '施肥机',
physicalDevice: '1号大棚水肥机-施肥泵A',
deviceAddress: '192.168.1.101:502',
protocol: 'Modbus-TCP',
controlPoint: 'AO0',
readAddress: '40010',
writeAddress: '40010',
dataType: 'Float',
status: '已映射',
testResult: '成功',
lastTestTime: '2024-10-15 10:30:00',
notes: '控制施肥量单位mL/min',
},
{
id: 'map-4',
mappingNo: 'MAP-004',
logicName: 'EC传感器',
logicType: '传感器',
physicalDevice: '1号大棚水肥机-EC传感器',
deviceAddress: '192.168.1.101:502',
protocol: 'Modbus-TCP',
controlPoint: 'AI0',
readAddress: '30001',
dataType: 'Float',
status: '已映射',
testResult: '成功',
lastTestTime: '2024-10-15 10:30:00',
},
{
id: 'map-5',
mappingNo: 'MAP-005',
logicName: '3号主阀门',
logicType: '阀门',
physicalDevice: '3号田块水肥机-电磁阀1',
deviceAddress: '192.168.1.103:502',
protocol: 'Modbus-TCP',
controlPoint: 'DO0',
readAddress: '40001',
writeAddress: '40001',
dataType: 'Boolean',
status: '异常',
testResult: '失败',
lastTestTime: '2024-10-15 16:45:00',
notes: '通信超时',
},
]);
const getStatusColor = (status: DeviceStatus) => {
switch (status) {
case '在线': return 'bg-green-100 text-green-700';
case '离线': return 'bg-gray-100 text-gray-700';
case '故障': return 'bg-red-100 text-red-700';
case '维护': return 'bg-yellow-100 text-yellow-700';
default: return 'bg-gray-100 text-gray-700';
}
};
const getComponentStatusColor = (status: string) => {
switch (status) {
case '正常': return 'bg-green-100 text-green-700';
case '异常': return 'bg-red-100 text-red-700';
case '维护': return 'bg-yellow-100 text-yellow-700';
default: return 'bg-gray-100 text-gray-700';
}
};
const getMappingStatusColor = (status: string) => {
switch (status) {
case '已映射': return 'bg-green-100 text-green-700';
case '未映射': return 'bg-gray-100 text-gray-700';
case '异常': return 'bg-red-100 text-red-700';
default: return 'bg-gray-100 text-gray-700';
}
};
const handleAddDevice = () => {
setEditingItem(null);
setShowDeviceDialog(true);
};
const handleEditDevice = (device: WFDevice) => {
setEditingItem(device);
setShowDeviceDialog(true);
};
const handleDeleteDevice = (deviceNo: string) => {
setDevices(devices.filter(d => d.deviceNo !== deviceNo));
toast.success(`设备 ${deviceNo} 已删除`);
};
const handleAddComponent = () => {
setEditingItem(null);
setShowComponentDialog(true);
};
const handleEditComponent = (component: WFComponent) => {
setEditingItem(component);
setShowComponentDialog(true);
};
const handleDeleteComponent = (componentNo: string) => {
setComponents(components.filter(c => c.componentNo !== componentNo));
toast.success(`部件 ${componentNo} 已删除`);
};
const handleAddParameter = () => {
setEditingItem(null);
setShowParameterDialog(true);
};
const handleEditParameter = (parameter: WFParameter) => {
setEditingItem(parameter);
setShowParameterDialog(true);
};
const handleDeleteParameter = (parameterNo: string) => {
setParameters(parameters.filter(p => p.parameterNo !== parameterNo));
toast.success(`参数 ${parameterNo} 已删除`);
};
const handleAddMapping = () => {
setEditingItem(null);
setShowMappingDialog(true);
};
const handleEditMapping = (mapping: DeviceMapping) => {
setEditingItem(mapping);
setShowMappingDialog(true);
};
const handleDeleteMapping = (mappingNo: string) => {
setMappings(mappings.filter(m => m.mappingNo !== mappingNo));
toast.success(`映射 ${mappingNo} 已删除`);
};
const handleTestMapping = (mappingNo: string) => {
toast.success(`映射 ${mappingNo} 连接测试成功`);
};
const handleViewDetail = (item: any, type: string) => {
setViewingItem({ ...item, _type: type });
setShowDetailDialog(true);
};
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 className="flex gap-2">
<Button variant="outline">
<Download className="w-4 h-4 mr-2" />
</Button>
<Button variant="outline">
<Upload className="w-4 h-4 mr-2" />
</Button>
</div>
</div>
{/* 统计卡片 */}
<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-blue-600">{devices.length}</p>
<p className="text-xs text-blue-600 mt-1">
线: {devices.filter(d => d.status === '在线').length}
</p>
</div>
<Server 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"></p>
<p className="mt-2 text-3xl text-green-600">{components.length}</p>
<p className="text-xs text-green-600 mt-1">
: {components.filter(c => c.status === '正常').length}
</p>
</div>
<Cpu className="w-12 h-12 text-green-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-purple-600">{parameters.length}</p>
<p className="text-xs text-purple-600 mt-1">
: {parameters.filter(p => p.isEnabled).length}
</p>
</div>
<Settings 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">{mappings.length}</p>
<p className="text-xs text-orange-600 mt-1">
: {mappings.filter(m => m.status === '已映射').length}
</p>
</div>
<Link 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-4">
<TabsTrigger value="device"></TabsTrigger>
<TabsTrigger value="component"></TabsTrigger>
<TabsTrigger value="parameter"></TabsTrigger>
<TabsTrigger value="mapping"></TabsTrigger>
</TabsList>
{/* 水肥机设备 */}
<TabsContent value="device" 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">
<Server 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>: </li>
<li> <strong></strong>: 线/线线</li>
<li> <strong></strong>: IP地址</li>
<li> <strong></strong>: </li>
<li> <strong></strong>: </li>
</ul>
</div>
</div>
</Card>
<Card className="p-4">
<div className="flex items-center justify-between mb-4">
<div className="flex gap-4 flex-1">
<div className="flex-1">
<Input placeholder="搜索设备名称、编号..." prefix={<Search className="w-4 h-4" />} />
</div>
<Select defaultValue="all-status">
<SelectTrigger className="w-40">
<SelectValue placeholder="设备状态" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all-status"></SelectItem>
<SelectItem value="online">线</SelectItem>
<SelectItem value="offline">线</SelectItem>
<SelectItem value="fault"></SelectItem>
<SelectItem value="maintenance"></SelectItem>
</SelectContent>
</Select>
<Select defaultValue="all-field">
<SelectTrigger className="w-40">
<SelectValue placeholder="所属地块" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all-field"></SelectItem>
<SelectItem value="field-1">1</SelectItem>
<SelectItem value="field-2">2</SelectItem>
<SelectItem value="field-3">3</SelectItem>
</SelectContent>
</Select>
</div>
<Button className="ml-4" onClick={handleAddDevice}>
<Plus className="w-4 h-4 mr-2" />
</Button>
</div>
</Card>
<Card>
<Table>
<TableHeader>
<TableRow>
<TableHead></TableHead>
<TableHead>/</TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
</TableRow>
</TableHeader>
<TableBody>
{devices.map((device) => (
<TableRow key={device.id}>
<TableCell>
<div>
<div className="font-medium">{device.deviceName}</div>
<div className="text-xs text-muted-foreground">#{device.deviceNo}</div>
</div>
</TableCell>
<TableCell>
<div className="text-sm">
<div>{device.model}</div>
<div className="text-xs text-muted-foreground">{device.manufacturer}</div>
</div>
</TableCell>
<TableCell>
<Badge className={getStatusColor(device.status)}>
{device.status}
</Badge>
</TableCell>
<TableCell>
<div className="text-sm">
<div>{device.fieldName}</div>
<div className="text-xs text-muted-foreground flex items-center gap-1">
<MapPin className="w-3 h-3" />
{device.location}
</div>
</div>
</TableCell>
<TableCell>
<div className="text-xs space-y-1">
<div className="flex items-center gap-1">
<Wifi className="w-3 h-3" />
{device.ipAddress}:{device.port}
</div>
<div>
<Badge variant="outline" className="text-xs">{device.protocol}</Badge>
</div>
</div>
</TableCell>
<TableCell>
<div className="text-xs space-y-1">
<div>: {device.workingHours}</div>
<div className="text-muted-foreground">
{device.lastOnlineTime.substring(5, 16)}
</div>
</div>
</TableCell>
<TableCell>
<div className="flex gap-2">
<Button size="sm" variant="outline" onClick={() => handleViewDetail(device, 'device')}>
<Eye className="w-3 h-3" />
</Button>
<Button size="sm" variant="outline" onClick={() => handleEditDevice(device)}>
<Edit className="w-3 h-3" />
</Button>
<Button size="sm" variant="outline" onClick={() => handleDeleteDevice(device.deviceNo)}>
<Trash2 className="w-3 h-3" />
</Button>
</div>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</Card>
</TabsContent>
{/* 部件配置 */}
<TabsContent value="component" 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">
<Cpu 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></strong>: </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 justify-between mb-4">
<div className="flex gap-4 flex-1">
<div className="flex-1">
<Input placeholder="搜索部件名称、编号..." prefix={<Search className="w-4 h-4" />} />
</div>
<Select defaultValue="all-type">
<SelectTrigger className="w-40">
<SelectValue placeholder="部件类型" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all-type"></SelectItem>
<SelectItem value="pump"></SelectItem>
<SelectItem value="sensor"></SelectItem>
<SelectItem value="controller"></SelectItem>
<SelectItem value="valve"></SelectItem>
<SelectItem value="flowmeter"></SelectItem>
</SelectContent>
</Select>
<Select defaultValue="all-device">
<SelectTrigger className="w-40">
<SelectValue placeholder="所属设备" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all-device"></SelectItem>
<SelectItem value="dev-1">1</SelectItem>
<SelectItem value="dev-2">2</SelectItem>
<SelectItem value="dev-3">3</SelectItem>
</SelectContent>
</Select>
</div>
<Button className="ml-4" onClick={handleAddComponent}>
<Plus className="w-4 h-4 mr-2" />
</Button>
</div>
</Card>
<Card>
<Table>
<TableHeader>
<TableRow>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
</TableRow>
</TableHeader>
<TableBody>
{components.map((component) => (
<TableRow key={component.id}>
<TableCell>
<div>
<div className="font-medium">{component.componentName}</div>
<div className="text-xs text-muted-foreground">#{component.componentNo}</div>
</div>
</TableCell>
<TableCell>
<Badge variant="outline">{component.type}</Badge>
</TableCell>
<TableCell className="text-sm">{component.deviceName}</TableCell>
<TableCell>
<div className="text-xs space-y-1">
<div>: {component.specification}</div>
<div>: {component.range}</div>
<div>: {component.accuracy}</div>
</div>
</TableCell>
<TableCell className="text-sm">{component.manufacturer}</TableCell>
<TableCell>
<Badge className={getComponentStatusColor(component.status)}>
{component.status}
</Badge>
</TableCell>
<TableCell>
<div className="flex gap-2">
<Button size="sm" variant="outline" onClick={() => handleViewDetail(component, 'component')}>
<Eye className="w-3 h-3" />
</Button>
<Button size="sm" variant="outline" onClick={() => handleEditComponent(component)}>
<Edit className="w-3 h-3" />
</Button>
<Button size="sm" variant="outline" onClick={() => handleDeleteComponent(component.componentNo)}>
<Trash2 className="w-3 h-3" />
</Button>
</div>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</Card>
</TabsContent>
{/* 参数配置 */}
<TabsContent value="parameter" 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">
<Settings 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>: EC/PH标定值等</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>
<Card className="p-4">
<div className="flex items-center justify-between mb-4">
<div className="flex gap-4 flex-1">
<div className="flex-1">
<Input placeholder="搜索参数名称、编号..." prefix={<Search className="w-4 h-4" />} />
</div>
<Select defaultValue="all-type">
<SelectTrigger className="w-40">
<SelectValue placeholder="参数类型" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all-type"></SelectItem>
<SelectItem value="pressure"></SelectItem>
<SelectItem value="flow"></SelectItem>
<SelectItem value="ec">EC值</SelectItem>
<SelectItem value="ph">PH值</SelectItem>
<SelectItem value="temperature"></SelectItem>
</SelectContent>
</Select>
<Select defaultValue="all-device">
<SelectTrigger className="w-40">
<SelectValue placeholder="所属设备" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all-device"></SelectItem>
<SelectItem value="dev-1">1</SelectItem>
<SelectItem value="dev-2">2</SelectItem>
</SelectContent>
</Select>
</div>
<Button className="ml-4" onClick={handleAddParameter}>
<Plus className="w-4 h-4 mr-2" />
</Button>
</div>
</Card>
<Card>
<Table>
<TableHeader>
<TableRow>
<TableHead></TableHead>
<TableHead>/</TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
</TableRow>
</TableHeader>
<TableBody>
{parameters.map((parameter) => (
<TableRow key={parameter.id}>
<TableCell>
<div>
<div className="font-medium">{parameter.parameterName}</div>
<div className="text-xs text-muted-foreground">#{parameter.parameterNo}</div>
</div>
</TableCell>
<TableCell>
<div className="text-sm space-y-1">
<Badge variant="outline">{parameter.type}</Badge>
<div className="text-xs text-muted-foreground">{parameter.unit}</div>
</div>
</TableCell>
<TableCell>
<div className="text-xs space-y-1">
<div>: {parameter.minValue} {parameter.unit}</div>
<div>: {parameter.maxValue} {parameter.unit}</div>
<div className="text-blue-600">: {parameter.defaultValue} {parameter.unit}</div>
</div>
</TableCell>
<TableCell>
<div className="text-xs space-y-1">
{parameter.warningThreshold && (
<div className="text-yellow-600">
: {parameter.warningThreshold} {parameter.unit}
</div>
)}
{parameter.alarmThreshold && (
<div className="text-red-600">
: {parameter.alarmThreshold} {parameter.unit}
</div>
)}
</div>
</TableCell>
<TableCell className="text-sm">{parameter.deviceName}</TableCell>
<TableCell>
<Switch checked={parameter.isEnabled} />
</TableCell>
<TableCell>
<div className="flex gap-2">
<Button size="sm" variant="outline" onClick={() => handleViewDetail(parameter, 'parameter')}>
<Eye className="w-3 h-3" />
</Button>
<Button size="sm" variant="outline" onClick={() => handleEditParameter(parameter)}>
<Edit className="w-3 h-3" />
</Button>
<Button size="sm" variant="outline" onClick={() => handleDeleteParameter(parameter.parameterNo)}>
<Trash2 className="w-3 h-3" />
</Button>
</div>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</Card>
</TabsContent>
{/* 设备映射 */}
<TabsContent value="mapping" 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">
<Link 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>: </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 justify-between mb-4">
<div className="flex gap-4 flex-1">
<div className="flex-1">
<Input placeholder="搜索逻辑名称、物理设备..." prefix={<Search className="w-4 h-4" />} />
</div>
<Select defaultValue="all-type">
<SelectTrigger className="w-40">
<SelectValue placeholder="逻辑类型" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all-type"></SelectItem>
<SelectItem value="valve"></SelectItem>
<SelectItem value="pump"></SelectItem>
<SelectItem value="fertilizer"></SelectItem>
<SelectItem value="sensor"></SelectItem>
</SelectContent>
</Select>
<Select defaultValue="all-status">
<SelectTrigger className="w-40">
<SelectValue placeholder="映射状态" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all-status"></SelectItem>
<SelectItem value="mapped"></SelectItem>
<SelectItem value="unmapped"></SelectItem>
<SelectItem value="error"></SelectItem>
</SelectContent>
</Select>
</div>
<Button className="ml-4" onClick={handleAddMapping}>
<Plus className="w-4 h-4 mr-2" />
</Button>
</div>
</Card>
<Card>
<Table>
<TableHeader>
<TableRow>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
</TableRow>
</TableHeader>
<TableBody>
{mappings.map((mapping) => (
<TableRow key={mapping.id}>
<TableCell>
<div>
<div className="font-medium">{mapping.logicName}</div>
<Badge variant="outline" className="text-xs mt-1">{mapping.logicType}</Badge>
</div>
</TableCell>
<TableCell>
<div className="text-sm">
<div>{mapping.physicalDevice}</div>
<div className="text-xs text-muted-foreground mt-1">
: {mapping.controlPoint}
</div>
</div>
</TableCell>
<TableCell>
<div className="text-xs space-y-1">
<div className="flex items-center gap-1">
<Signal className="w-3 h-3" />
{mapping.deviceAddress}
</div>
<Badge variant="outline" className="text-xs">{mapping.protocol}</Badge>
</div>
</TableCell>
<TableCell>
<div className="text-xs space-y-1">
{mapping.readAddress && <div>: {mapping.readAddress}</div>}
{mapping.writeAddress && <div>: {mapping.writeAddress}</div>}
<Badge variant="outline" className="text-xs">{mapping.dataType}</Badge>
</div>
</TableCell>
<TableCell>
{mapping.testResult && (
<div className="text-xs space-y-1">
<Badge className={mapping.testResult === '成功' ? 'bg-green-100 text-green-700' : 'bg-red-100 text-red-700'}>
{mapping.testResult}
</Badge>
{mapping.lastTestTime && (
<div className="text-muted-foreground">
{mapping.lastTestTime.substring(5, 16)}
</div>
)}
</div>
)}
</TableCell>
<TableCell>
<Badge className={getMappingStatusColor(mapping.status)}>
{mapping.status}
</Badge>
</TableCell>
<TableCell>
<div className="flex gap-2">
<Button size="sm" variant="outline" onClick={() => handleTestMapping(mapping.mappingNo)}>
<Zap className="w-3 h-3" />
</Button>
<Button size="sm" variant="outline" onClick={() => handleViewDetail(mapping, 'mapping')}>
<Eye className="w-3 h-3" />
</Button>
<Button size="sm" variant="outline" onClick={() => handleEditMapping(mapping)}>
<Edit className="w-3 h-3" />
</Button>
<Button size="sm" variant="outline" onClick={() => handleDeleteMapping(mapping.mappingNo)}>
<Trash2 className="w-3 h-3" />
</Button>
</div>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</Card>
</TabsContent>
</Tabs>
{/* 设备对话框 */}
<Dialog open={showDeviceDialog} onOpenChange={setShowDeviceDialog}>
<DialogContent className="max-w-2xl">
<DialogHeader>
<DialogTitle>{editingItem ? '编辑设备' : '新增设备'}</DialogTitle>
<DialogDescription>
</DialogDescription>
</DialogHeader>
<div className="space-y-4">
<div className="grid grid-cols-2 gap-4">
<div>
<Label></Label>
<Input placeholder="输入设备名称" defaultValue={editingItem?.deviceName} />
</div>
<div>
<Label></Label>
<Input placeholder="自动生成" defaultValue={editingItem?.deviceNo} disabled />
</div>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<Label></Label>
<Input placeholder="输入设备型号" defaultValue={editingItem?.model} />
</div>
<div>
<Label></Label>
<Input placeholder="输入厂商名称" defaultValue={editingItem?.manufacturer} />
</div>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<Label></Label>
<Select defaultValue={editingItem?.fieldName}>
<SelectTrigger>
<SelectValue placeholder="选择地块" />
</SelectTrigger>
<SelectContent>
<SelectItem value="1号大棚">1</SelectItem>
<SelectItem value="2号田块">2</SelectItem>
<SelectItem value="3号田块">3</SelectItem>
</SelectContent>
</Select>
</div>
<div>
<Label></Label>
<Input placeholder="输入安装位置" defaultValue={editingItem?.location} />
</div>
</div>
<div className="grid grid-cols-3 gap-4">
<div>
<Label>IP地址</Label>
<Input placeholder="192.168.1.101" defaultValue={editingItem?.ipAddress} />
</div>
<div>
<Label></Label>
<Input placeholder="502" defaultValue={editingItem?.port} />
</div>
<div>
<Label></Label>
<Select defaultValue={editingItem?.protocol}>
<SelectTrigger>
<SelectValue placeholder="选择协议" />
</SelectTrigger>
<SelectContent>
<SelectItem value="Modbus-RTU">Modbus-RTU</SelectItem>
<SelectItem value="Modbus-TCP">Modbus-TCP</SelectItem>
<SelectItem value="HTTP">HTTP</SelectItem>
<SelectItem value="MQTT">MQTT</SelectItem>
<SelectItem value="OPC-UA">OPC-UA</SelectItem>
</SelectContent>
</Select>
</div>
</div>
<div>
<Label></Label>
<Textarea placeholder="输入备注信息" defaultValue={editingItem?.notes} rows={3} />
</div>
</div>
<DialogFooter>
<Button variant="outline" onClick={() => setShowDeviceDialog(false)}>
</Button>
<Button onClick={() => {
toast.success(editingItem ? '设备信息已更新' : '设备已添加');
setShowDeviceDialog(false);
}}>
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
{/* 部件对话框 */}
<Dialog open={showComponentDialog} onOpenChange={setShowComponentDialog}>
<DialogContent className="max-w-2xl">
<DialogHeader>
<DialogTitle>{editingItem ? '编辑部件' : '新增部件'}</DialogTitle>
<DialogDescription>
</DialogDescription>
</DialogHeader>
<div className="space-y-4">
<div className="grid grid-cols-2 gap-4">
<div>
<Label></Label>
<Input placeholder="输入部件名称" defaultValue={editingItem?.componentName} />
</div>
<div>
<Label></Label>
<Select defaultValue={editingItem?.type}>
<SelectTrigger>
<SelectValue placeholder="选择类型" />
</SelectTrigger>
<SelectContent>
<SelectItem value="泵体"></SelectItem>
<SelectItem value="传感器"></SelectItem>
<SelectItem value="控制器"></SelectItem>
<SelectItem value="阀门"></SelectItem>
<SelectItem value="流量计"></SelectItem>
</SelectContent>
</Select>
</div>
</div>
<div>
<Label></Label>
<Select defaultValue={editingItem?.deviceName}>
<SelectTrigger>
<SelectValue placeholder="选择设备" />
</SelectTrigger>
<SelectContent>
<SelectItem value="1号大棚水肥机">1</SelectItem>
<SelectItem value="2号田块水肥机">2</SelectItem>
<SelectItem value="3号田块水肥机">3</SelectItem>
</SelectContent>
</Select>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<Label></Label>
<Input placeholder="输入规格型号" defaultValue={editingItem?.specification} />
</div>
<div>
<Label></Label>
<Input placeholder="输入厂商名称" defaultValue={editingItem?.manufacturer} />
</div>
</div>
<div className="grid grid-cols-3 gap-4">
<div>
<Label></Label>
<Input placeholder="例如: 0-50L/min" defaultValue={editingItem?.range} />
</div>
<div>
<Label></Label>
<Input placeholder="例如: ±2%" defaultValue={editingItem?.accuracy} />
</div>
<div>
<Label></Label>
<Input placeholder="例如: L/min" defaultValue={editingItem?.unit} />
</div>
</div>
<div>
<Label></Label>
<Textarea placeholder="输入备注信息" defaultValue={editingItem?.notes} rows={3} />
</div>
</div>
<DialogFooter>
<Button variant="outline" onClick={() => setShowComponentDialog(false)}>
</Button>
<Button onClick={() => {
toast.success(editingItem ? '部件信息已更新' : '部件已添加');
setShowComponentDialog(false);
}}>
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
{/* 参数对话框 */}
<Dialog open={showParameterDialog} onOpenChange={setShowParameterDialog}>
<DialogContent className="max-w-2xl">
<DialogHeader>
<DialogTitle>{editingItem ? '编辑参数' : '新增参数'}</DialogTitle>
<DialogDescription>
</DialogDescription>
</DialogHeader>
<div className="space-y-4">
<div className="grid grid-cols-2 gap-4">
<div>
<Label></Label>
<Input placeholder="输入参数名称" defaultValue={editingItem?.parameterName} />
</div>
<div>
<Label></Label>
<Select defaultValue={editingItem?.type}>
<SelectTrigger>
<SelectValue placeholder="选择类型" />
</SelectTrigger>
<SelectContent>
<SelectItem value="压力"></SelectItem>
<SelectItem value="流量"></SelectItem>
<SelectItem value="EC值">EC值</SelectItem>
<SelectItem value="PH值">PH值</SelectItem>
<SelectItem value="温度"></SelectItem>
<SelectItem value="湿度">湿</SelectItem>
</SelectContent>
</Select>
</div>
</div>
<div>
<Label></Label>
<Select defaultValue={editingItem?.deviceName}>
<SelectTrigger>
<SelectValue placeholder="选择设备" />
</SelectTrigger>
<SelectContent>
<SelectItem value="1号大棚水肥机">1</SelectItem>
<SelectItem value="2号田块水肥机">2</SelectItem>
</SelectContent>
</Select>
</div>
<div className="grid grid-cols-4 gap-4">
<div>
<Label></Label>
<Select defaultValue={editingItem?.unit}>
<SelectTrigger>
<SelectValue placeholder="单位" />
</SelectTrigger>
<SelectContent>
<SelectItem value="bar">bar</SelectItem>
<SelectItem value="L/min">L/min</SelectItem>
<SelectItem value="mS/cm">mS/cm</SelectItem>
<SelectItem value="pH">pH</SelectItem>
<SelectItem value="℃"></SelectItem>
<SelectItem value="%">%</SelectItem>
</SelectContent>
</Select>
</div>
<div>
<Label></Label>
<Input type="number" placeholder="0" defaultValue={editingItem?.minValue} />
</div>
<div>
<Label></Label>
<Input type="number" placeholder="100" defaultValue={editingItem?.maxValue} />
</div>
<div>
<Label></Label>
<Input type="number" placeholder="50" defaultValue={editingItem?.defaultValue} />
</div>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<Label></Label>
<Input type="number" placeholder="输入预警阈值" defaultValue={editingItem?.warningThreshold} />
</div>
<div>
<Label></Label>
<Input type="number" placeholder="输入报警阈值" defaultValue={editingItem?.alarmThreshold} />
</div>
</div>
<div>
<Label></Label>
<Textarea placeholder="输入参数说明" defaultValue={editingItem?.description} rows={3} />
</div>
</div>
<DialogFooter>
<Button variant="outline" onClick={() => setShowParameterDialog(false)}>
</Button>
<Button onClick={() => {
toast.success(editingItem ? '参数信息已更新' : '参数已添加');
setShowParameterDialog(false);
}}>
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
{/* 映射对话框 */}
<Dialog open={showMappingDialog} onOpenChange={setShowMappingDialog}>
<DialogContent className="max-w-2xl">
<DialogHeader>
<DialogTitle>{editingItem ? '编辑映射' : '新增映射'}</DialogTitle>
<DialogDescription>
</DialogDescription>
</DialogHeader>
<div className="space-y-4">
<div className="grid grid-cols-2 gap-4">
<div>
<Label></Label>
<Input placeholder="输入逻辑名称" defaultValue={editingItem?.logicName} />
</div>
<div>
<Label></Label>
<Select defaultValue={editingItem?.logicType}>
<SelectTrigger>
<SelectValue placeholder="选择类型" />
</SelectTrigger>
<SelectContent>
<SelectItem value="阀门"></SelectItem>
<SelectItem value="泵"></SelectItem>
<SelectItem value="施肥机"></SelectItem>
<SelectItem value="传感器"></SelectItem>
</SelectContent>
</Select>
</div>
</div>
<div>
<Label></Label>
<Input placeholder="输入物理设备名称" defaultValue={editingItem?.physicalDevice} />
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<Label></Label>
<Input placeholder="例如: 192.168.1.101:502" defaultValue={editingItem?.deviceAddress} />
</div>
<div>
<Label></Label>
<Select defaultValue={editingItem?.protocol}>
<SelectTrigger>
<SelectValue placeholder="选择协议" />
</SelectTrigger>
<SelectContent>
<SelectItem value="Modbus-RTU">Modbus-RTU</SelectItem>
<SelectItem value="Modbus-TCP">Modbus-TCP</SelectItem>
<SelectItem value="HTTP">HTTP</SelectItem>
<SelectItem value="MQTT">MQTT</SelectItem>
<SelectItem value="OPC-UA">OPC-UA</SelectItem>
</SelectContent>
</Select>
</div>
</div>
<div className="grid grid-cols-3 gap-4">
<div>
<Label></Label>
<Input placeholder="例如: DO0" defaultValue={editingItem?.controlPoint} />
</div>
<div>
<Label></Label>
<Input placeholder="例如: 40001" defaultValue={editingItem?.readAddress} />
</div>
<div>
<Label></Label>
<Input placeholder="例如: 40001" defaultValue={editingItem?.writeAddress} />
</div>
</div>
<div>
<Label></Label>
<Select defaultValue={editingItem?.dataType}>
<SelectTrigger>
<SelectValue placeholder="选择数据类型" />
</SelectTrigger>
<SelectContent>
<SelectItem value="Boolean">Boolean</SelectItem>
<SelectItem value="Int16">Int16</SelectItem>
<SelectItem value="Int32">Int32</SelectItem>
<SelectItem value="Float">Float</SelectItem>
<SelectItem value="String">String</SelectItem>
</SelectContent>
</Select>
</div>
<div>
<Label></Label>
<Textarea placeholder="输入备注信息" defaultValue={editingItem?.notes} rows={3} />
</div>
</div>
<DialogFooter>
<Button variant="outline" onClick={() => setShowMappingDialog(false)}>
</Button>
<Button onClick={() => {
toast.success(editingItem ? '映射信息已更新' : '映射已添加');
setShowMappingDialog(false);
}}>
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
{/* 详情对话框 */}
<Dialog open={showDetailDialog} onOpenChange={setShowDetailDialog}>
<DialogContent className="max-w-3xl max-h-[90vh] overflow-y-auto">
<DialogHeader>
<DialogTitle></DialogTitle>
</DialogHeader>
{viewingItem && (
<div className="space-y-4">
{viewingItem._type === 'device' && (
<>
<div className="grid grid-cols-2 gap-4">
<div>
<Label></Label>
<div className="field-value">{viewingItem.deviceNo}</div>
</div>
<div>
<Label></Label>
<div className="field-value">{viewingItem.deviceName}</div>
</div>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<Label></Label>
<div className="field-value">{viewingItem.model}</div>
</div>
<div>
<Label></Label>
<div className="field-value">{viewingItem.manufacturer}</div>
</div>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<Label></Label>
<div className="field-value">{viewingItem.fieldName}</div>
</div>
<div>
<Label></Label>
<div className="field-value">{viewingItem.location}</div>
</div>
</div>
<div className="grid grid-cols-3 gap-4">
<div>
<Label>IP地址</Label>
<div className="field-value">{viewingItem.ipAddress}</div>
</div>
<div>
<Label></Label>
<div className="field-value">{viewingItem.port}</div>
</div>
<div>
<Label></Label>
<div className="field-value">{viewingItem.protocol}</div>
</div>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<Label></Label>
<div className="field-value">
<Badge className={getStatusColor(viewingItem.status)}>
{viewingItem.status}
</Badge>
</div>
</div>
<div>
<Label></Label>
<div className="field-value">{viewingItem.workingHours} </div>
</div>
</div>
{viewingItem.notes && (
<div>
<Label></Label>
<div className="field-value">{viewingItem.notes}</div>
</div>
)}
</>
)}
{viewingItem._type === 'component' && (
<>
<div className="grid grid-cols-2 gap-4">
<div>
<Label></Label>
<div className="field-value">{viewingItem.componentNo}</div>
</div>
<div>
<Label></Label>
<div className="field-value">{viewingItem.componentName}</div>
</div>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<Label></Label>
<div className="field-value">{viewingItem.type}</div>
</div>
<div>
<Label></Label>
<div className="field-value">{viewingItem.deviceName}</div>
</div>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<Label></Label>
<div className="field-value">{viewingItem.specification}</div>
</div>
<div>
<Label></Label>
<div className="field-value">{viewingItem.manufacturer}</div>
</div>
</div>
<div className="grid grid-cols-3 gap-4">
<div>
<Label></Label>
<div className="field-value">{viewingItem.range}</div>
</div>
<div>
<Label></Label>
<div className="field-value">{viewingItem.accuracy}</div>
</div>
<div>
<Label></Label>
<div className="field-value">{viewingItem.unit}</div>
</div>
</div>
</>
)}
{viewingItem._type === 'parameter' && (
<>
<div className="grid grid-cols-2 gap-4">
<div>
<Label></Label>
<div className="field-value">{viewingItem.parameterNo}</div>
</div>
<div>
<Label></Label>
<div className="field-value">{viewingItem.parameterName}</div>
</div>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<Label></Label>
<div className="field-value">{viewingItem.type}</div>
</div>
<div>
<Label></Label>
<div className="field-value">{viewingItem.unit}</div>
</div>
</div>
<div className="grid grid-cols-3 gap-4">
<div>
<Label></Label>
<div className="field-value">{viewingItem.minValue} {viewingItem.unit}</div>
</div>
<div>
<Label></Label>
<div className="field-value">{viewingItem.maxValue} {viewingItem.unit}</div>
</div>
<div>
<Label></Label>
<div className="field-value">{viewingItem.defaultValue} {viewingItem.unit}</div>
</div>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<Label></Label>
<div className="field-value">{viewingItem.warningThreshold} {viewingItem.unit}</div>
</div>
<div>
<Label></Label>
<div className="field-value">{viewingItem.alarmThreshold} {viewingItem.unit}</div>
</div>
</div>
<div>
<Label></Label>
<div className="field-value">{viewingItem.description}</div>
</div>
</>
)}
{viewingItem._type === 'mapping' && (
<>
<div className="grid grid-cols-2 gap-4">
<div>
<Label></Label>
<div className="field-value">{viewingItem.mappingNo}</div>
</div>
<div>
<Label></Label>
<div className="field-value">{viewingItem.logicName}</div>
</div>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<Label></Label>
<div className="field-value">{viewingItem.logicType}</div>
</div>
<div>
<Label></Label>
<div className="field-value">{viewingItem.physicalDevice}</div>
</div>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<Label></Label>
<div className="field-value">{viewingItem.deviceAddress}</div>
</div>
<div>
<Label></Label>
<div className="field-value">{viewingItem.protocol}</div>
</div>
</div>
<div className="grid grid-cols-3 gap-4">
<div>
<Label></Label>
<div className="field-value">{viewingItem.controlPoint}</div>
</div>
<div>
<Label></Label>
<div className="field-value">{viewingItem.readAddress || '-'}</div>
</div>
<div>
<Label></Label>
<div className="field-value">{viewingItem.writeAddress || '-'}</div>
</div>
</div>
<div>
<Label></Label>
<div className="field-value">{viewingItem.dataType}</div>
</div>
{viewingItem.testResult && (
<div className="grid grid-cols-2 gap-4">
<div>
<Label></Label>
<div className="field-value">
<Badge className={viewingItem.testResult === '成功' ? 'bg-green-100 text-green-700' : 'bg-red-100 text-red-700'}>
{viewingItem.testResult}
</Badge>
</div>
</div>
<div>
<Label></Label>
<div className="field-value">{viewingItem.lastTestTime}</div>
</div>
</div>
)}
</>
)}
</div>
)}
<DialogFooter>
<Button variant="outline" onClick={() => setShowDetailDialog(false)}>
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</div>
);
}