1843 lines
71 KiB
TypeScript
1843 lines
71 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 { 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>
|
||
);
|
||
}
|