diff --git a/src/components/SystemSetting/AllDeviceManagement.tsx b/src/components/SystemSetting/AllDeviceManagement.tsx new file mode 100644 index 0000000..b69ab47 --- /dev/null +++ b/src/components/SystemSetting/AllDeviceManagement.tsx @@ -0,0 +1,150 @@ +import React, { useState } from 'react'; +import { + Table, + Input, + Button, + Form, + Select, + Space, + Tag, + Typography, + App, +} from 'antd'; +const { Text } = Typography; +import { + SearchOutlined, + ReloadOutlined, + PlusOutlined, + EditOutlined, + DeleteOutlined, +} from '@ant-design/icons'; + +const allDeviceList = [ + { id: 1, code: 'CAM-01', name: '摄像头 CAM-01', type: 'CAMERA', online: 'ONLINE', status: 'NORMAL' }, + { id: 2, code: 'CAM-02', name: '摄像头 CAM-02', type: 'CAMERA', online: 'OFFLINE', status: 'FAULT' }, + { id: 3, code: 'CAM-03', name: '摄像头 CAM-03', type: 'CAMERA', online: 'ONLINE', status: 'NORMAL' }, + { id: 4, code: 'CLEAN-01', name: '清洗机器人 CLEAN-01', type: 'CLEANING_DEVICE', online: 'ONLINE', status: 'IDLE' }, + { id: 5, code: 'CMB-A01', name: '汇流箱 CMB-A01', type: 'COMBINER_BOX', online: 'ONLINE', status: 'WARNING' }, + { id: 6, code: 'CMB-A02', name: '汇流箱 CMB-A02', type: 'COMBINER_BOX', online: 'ONLINE', status: 'NORMAL' }, + { id: 7, code: 'INV-A01', name: '逆变器 INV-A01', type: 'INVERTER', online: 'ONLINE', status: 'NORMAL' }, + { id: 8, code: 'INV-A02', name: '逆变器 INV-A02', type: 'INVERTER', online: 'ONLINE', status: 'WARNING' }, + { id: 9, code: 'INV-B01', name: '逆变器 INV-B01', type: 'INVERTER', online: 'ONLINE', status: 'NORMAL' }, + { id: 10, code: 'METER-01', name: '电表 METER-01', type: 'METER', online: 'ONLINE', status: 'NORMAL' }, + { id: 11, code: 'ROBOT-01', name: '运维机器人 ROBOT-01', type: 'ROBOT', online: 'ONLINE', status: 'IDLE' }, + { id: 12, code: 'DRONE-01', name: '无人机 DRONE-01', type: 'DRONE', online: 'OFFLINE', status: 'FAULT' }, + { id: 13, code: 'WEATHER-01', name: '气象站 WEATHER-01', type: 'WEATHER_STATION', online: 'ONLINE', status: 'NORMAL' }, +]; + +export default function AllDeviceManagement() { + const { modal } = App.useApp(); + const [form] = Form.useForm(); + const [loading, setLoading] = useState(false); + const [deviceList, setDeviceList] = useState(allDeviceList); + + const columns = [ + { title: '设备编码', dataIndex: 'code', key: 'code' }, + { title: '设备名称', dataIndex: 'name', key: 'name' }, + { title: '类型', dataIndex: 'type', key: 'type' }, + { + title: '在线', dataIndex: 'online', key: 'online', + render: (text) => ( + {text} + ), + }, + { + title: '运行', dataIndex: 'status', key: 'status', + render: (text) => { + const colorMap: Record = { + NORMAL: 'green', + FAULT: 'red', + WARNING: 'orange', + IDLE: 'blue', + }; + return {text}; + }, + }, + { + title: '操作', key: 'action', + render: (_, record: any) => ( + + + + + ), + }, + ]; + + const handleSearch = () => { + form.validateFields().then(values => { + const keyword = values.keyword?.toLowerCase() || ''; + setLoading(true); + setTimeout(() => { + const filtered = allDeviceList.filter(item => + item.name.toLowerCase().includes(keyword) || item.code.toLowerCase().includes(keyword) + ); + setDeviceList(filtered); + setLoading(false); + }, 300); + }); + }; + + const handleReset = () => { + form.resetFields(); + setDeviceList(allDeviceList); + }; + + const handleAdd = () => { + App.useApp(); + }; + + const handleEdit = (record) => { + modal.info({ + title: '编辑设备', + content: `编辑设备:${record.name}`, + }); + }; + + const handleDelete = (record) => { + modal.confirm({ + title: '确认删除', + content: `确定要删除设备 ${record.name} 吗?`, + onOk: () => { + setDeviceList(prev => prev.filter((item: any) => item.id !== record.id)); + }, + }); + }; + + return ( + <> +
+
+ + + + + + + + + + + + +
+
+ `共 ${total} 条`, + }} + /> + + ); +} diff --git a/src/components/SystemSetting/CameraManagement.tsx b/src/components/SystemSetting/CameraManagement.tsx new file mode 100644 index 0000000..ab8c29e --- /dev/null +++ b/src/components/SystemSetting/CameraManagement.tsx @@ -0,0 +1,449 @@ +import React, { useState } from 'react'; +import { + Table, + Input, + Button, + Form, + Select, + Space, + Tag, + Typography, + App, + Card, + Row, + Col, + Modal, + InputNumber, + Switch, +} from 'antd'; +const { Text } = Typography; +import { + SearchOutlined, + ReloadOutlined, + PlusOutlined, + EditOutlined, + DeleteOutlined, + VideoCameraOutlined, + EnvironmentOutlined, +} from '@ant-design/icons'; + +const mockCameraList = [ + { + id: 1, + code: 'CAM-01', + name: '东门入口摄像头', + ip: '192.168.1.101', + online: 'ONLINE', + status: 'NORMAL', + location: '园区东门', + type: '高清球机', + manufacturer: '海康威视', + resolution: '4K', + ptz: true, + nightVision: true, + }, + { + id: 2, + code: 'CAM-02', + name: '西门入口摄像头', + ip: '192.168.1.102', + online: 'OFFLINE', + status: 'FAULT', + location: '园区西门', + type: '高清枪机', + manufacturer: '大华', + resolution: '1080P', + ptz: false, + nightVision: true, + }, + { + id: 3, + code: 'CAM-03', + name: '停车场摄像头', + ip: '192.168.1.103', + online: 'ONLINE', + status: 'NORMAL', + location: '地下停车场B1', + type: '高清球机', + manufacturer: '海康威视', + resolution: '1080P', + ptz: true, + nightVision: true, + }, + { + id: 4, + code: 'CAM-04', + name: '办公楼大堂摄像头', + ip: '192.168.1.104', + online: 'ONLINE', + status: 'WARNING', + location: '办公楼1层大堂', + type: '半球摄像机', + manufacturer: '宇视', + resolution: '4K', + ptz: false, + nightVision: false, + }, + { + id: 5, + code: 'CAM-05', + name: '仓库监控摄像头', + ip: '192.168.1.105', + online: 'ONLINE', + status: 'NORMAL', + location: '物料仓库', + type: '高清枪机', + manufacturer: '海康威视', + resolution: '4K', + ptz: false, + nightVision: true, + }, +]; + +export default function CameraManagement() { + const { modal } = App.useApp(); + const [form] = Form.useForm(); + const [loading, setLoading] = useState(false); + const [cameraList, setCameraList] = useState(mockCameraList); + const [editModalVisible, setEditModalVisible] = useState(false); + const [editForm] = Form.useForm(); + const [currentCamera, setCurrentCamera] = useState(null); + + const columns = [ + { title: '设备编码', dataIndex: 'code', key: 'code', width: 120 }, + { title: '设备名称', dataIndex: 'name', key: 'name', width: 180 }, + { title: 'IP地址', dataIndex: 'ip', key: 'ip', width: 140 }, + { title: '摄像头类型', dataIndex: 'type', key: 'type', width: 120 }, + { title: '品牌', dataIndex: 'manufacturer', key: 'manufacturer', width: 120 }, + { title: '分辨率', dataIndex: 'resolution', key: 'resolution', width: 100 }, + { + title: '位置', dataIndex: 'location', key: 'location', + render: (text) => ( + + + {text} + + ), + }, + { + title: '云台', dataIndex: 'ptz', key: 'ptz', width: 80, + render: (ptz) => ptz ? 支持 : 不支持 + }, + { + title: '夜视', dataIndex: 'nightVision', key: 'nightVision', width: 80, + render: (nv) => nv ? 支持 : 不支持 + }, + { + title: '在线', dataIndex: 'online', key: 'online', width: 100, + render: (text) => ( + {text === 'ONLINE' ? '在线' : '离线'} + ), + }, + { + title: '状态', dataIndex: 'status', key: 'status', width: 100, + render: (text) => { + const colorMap: Record = { + NORMAL: 'green', + FAULT: 'red', + WARNING: 'orange', + }; + const textMap: Record = { + NORMAL: '正常', + FAULT: '故障', + WARNING: '警告', + }; + return {textMap[text] || text}; + }, + }, + { + title: '操作', key: 'action', width: 200, fixed: 'right' as const, + render: (_, record: any) => ( + + + + + + ), + }, + ]; + + const handleSearch = () => { + form.validateFields().then(values => { + const keyword = values.keyword?.toLowerCase() || ''; + const status = values.status; + setLoading(true); + setTimeout(() => { + let filtered = mockCameraList.filter(item => + item.name.toLowerCase().includes(keyword) || + item.code.toLowerCase().includes(keyword) || + item.ip.includes(keyword) + ); + if (status) { + filtered = filtered.filter(item => item.status === status); + } + setCameraList(filtered); + setLoading(false); + }, 300); + }); + }; + + const handleReset = () => { + form.resetFields(); + setCameraList(mockCameraList); + }; + + const handleAdd = () => { + setCurrentCamera(null); + editForm.resetFields(); + setEditModalVisible(true); + }; + + const handleView = (record) => { + modal.info({ + title: `${record.name} - 视频预览`, + width: 720, + content: ( +
+ + + 视频预览占位 - {record.name} + IP: {record.ip} + +
+ ), + }); + }; + + const handleEdit = (record) => { + setCurrentCamera(record); + editForm.setFieldsValue(record); + setEditModalVisible(true); + }; + + const handleDelete = (record) => { + modal.confirm({ + title: '确认删除', + content: `确定要删除摄像头 ${record.name} 吗?`, + onOk: () => { + setCameraList(prev => prev.filter((item: any) => item.id !== record.id)); + }, + }); + }; + + const handleSave = (values) => { + if (currentCamera) { + setCameraList(prev => prev.map((item: any) => + item.id === currentCamera.id ? { ...item, ...values } : item + )); + } else { + const newId = Math.max(...cameraList.map((i: any) => i.id)) + 1; + setCameraList(prev => [...prev, { id: newId, ...values, online: 'ONLINE', status: 'NORMAL' }]); + } + setEditModalVisible(false); + editForm.resetFields(); + }; + + return ( + <> + +
+ + +
+
摄像头总数
+
{cameraList.length}
+
+
+ +
+
+
+ + + + +
+
在线
+
+ {cameraList.filter((c: any) => c.online === 'ONLINE').length} +
+
+
+ ✔ +
+
+
+ + + + +
+
离线/故障
+
+ {cameraList.filter((c: any) => c.online === 'OFFLINE' || c.status === 'FAULT').length} +
+
+
+ ⚠ +
+
+
+ + + +
+
+ + } /> + + + + + + + + + + + + + + +
+ +
`共 ${total} 条`, + }} + scroll={{ x: 1400 }} + /> + + { + setEditModalVisible(false); + editForm.resetFields(); + }} + onOk={() => editForm.submit()} + width={600} + > +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + } /> + + + + + + + + + + + + + + + + + ); +} diff --git a/src/components/SystemSetting/DeviceManagement.tsx b/src/components/SystemSetting/DeviceManagement.tsx index 3f3c852..15c94f4 100644 --- a/src/components/SystemSetting/DeviceManagement.tsx +++ b/src/components/SystemSetting/DeviceManagement.tsx @@ -1,362 +1,25 @@ import React, { useState, useEffect } from 'react'; import { - Table, - Input, - Button, - Form, - Select, - Space, - Tag, - message, - Modal, Tabs, - Card, - InputNumber, - Checkbox, - Tooltip, - Row, - Col, - App, - Typography, - Slider, } from 'antd'; -const { Text } = Typography; -import { - SearchOutlined, - ReloadOutlined, - PlusOutlined, - EditOutlined, - DeleteOutlined, - LinkOutlined, - DisconnectOutlined, -} from '@ant-design/icons'; - - import SerialNumGenerate from "./SerialNumGenerate" -import DeviceParamModal from '../devices/DeviceParamModal'; -import { getPlaneDeviceList, bindDrone, unbindDrone, bindSmartDevice, unbindSmartDevice, getAllDevice, deviceCommmand, getDeviceAuth, hasPermission } from '@/api/device'; import { getOrgList } from '@/api/organization/index'; -import { getSiteByOrgId, getStationList, deviceRunParamSelectByDeviceId, deviceRunParamSave, deviceRunParamDelete } from '@/api/stationManage/index'; +import { getStationList } from '@/api/stationManage/index'; import { getUser } from '@/api/user/index'; -import { useSelector } from 'react-redux'; -import { RootState } from '@/src/store'; -import utils from '@/lib/utils'; -// 模拟数据 -const allDeviceList = [ - { id: 1, code: 'CAM-01', name: '摄像头 CAM-01', type: 'CAMERA', online: 'ONLINE', status: 'NORMAL' }, - { id: 2, code: 'CAM-02', name: '摄像头 CAM-02', type: 'CAMERA', online: 'OFFLINE', status: 'FAULT' }, - { id: 3, code: 'CAM-03', name: '摄像头 CAM-03', type: 'CAMERA', online: 'ONLINE', status: 'NORMAL' }, - { id: 4, code: 'CLEAN-01', name: '清洗机器人 CLEAN-01', type: 'CLEANING_DEVICE', online: 'ONLINE', status: 'IDLE' }, - { id: 5, code: 'CMB-A01', name: '汇流箱 CMB-A01', type: 'COMBINER_BOX', online: 'ONLINE', status: 'WARNING' }, - { id: 6, code: 'CMB-A02', name: '汇流箱 CMB-A02', type: 'COMBINER_BOX', online: 'ONLINE', status: 'NORMAL' }, - { id: 7, code: 'INV-A01', name: '逆变器 INV-A01', type: 'INVERTER', online: 'ONLINE', status: 'NORMAL' }, - { id: 8, code: 'INV-A02', name: '逆变器 INV-A02', type: 'INVERTER', online: 'ONLINE', status: 'WARNING' }, - { id: 9, code: 'INV-B01', name: '逆变器 INV-B01', type: 'INVERTER', online: 'ONLINE', status: 'NORMAL' }, - { id: 10, code: 'METER-01', name: '电表 METER-01', type: 'METER', online: 'ONLINE', status: 'NORMAL' }, - { id: 11, code: 'ROBOT-01', name: '运维机器人 ROBOT-01', type: 'ROBOT', online: 'ONLINE', status: 'IDLE' }, - { id: 12, code: 'DRONE-01', name: '无人机 DRONE-01', type: 'DRONE', online: 'OFFLINE', status: 'FAULT' }, - { id: 13, code: 'WEATHER-01', name: '气象站 WEATHER-01', type: 'WEATHER_STATION', online: 'ONLINE', status: 'NORMAL' }, -]; - -// Tab 配置 -const tabs = [ - { key: 'all', label: '设备管理', filter: () => true }, - { key: 'camera', label: '摄像头管理', filter: (item) => item.type === 'CAMERA' }, - { key: 'smart', label: '智能装备', filter: (item) => ['CLEANING_DEVICE', 'ROBOT', 'DRONE'].includes(item.type) }, -]; +import DroneManagement from './DroneManagement'; +import SmartDeviceManagement from './SmartDeviceManagement'; +import AllDeviceManagement from './AllDeviceManagement'; +import CameraManagement from './CameraManagement'; export default function DeviceManagementPage() { const [activeTab, setActiveTab] = useState('all'); - const { modal } = App.useApp(); - const [form] = Form.useForm(); - const [loading, setLoading] = useState(false); - const [deviceList, setDeviceList] = useState(allDeviceList); - - // 无人机相关状态 - const { userInfo } = useSelector((state: RootState) => state.user); - const { stationId, currentStation } = useSelector((state: RootState) => state.station); - const [droneList, setDroneList] = useState([]); - const [bindModalVisible, setBindModalVisible] = useState(false); - const [bindForm] = Form.useForm(); - const [selectedDroneKeys, setSelectedDroneKeys] = useState([]); - - // 批量增益参数弹窗 - const [batchParamModalVisible, setBatchParamModalVisible] = useState(false); - const [batchParamForm] = Form.useForm(); - - - // 无人机分页 + 筛选 - const [droneParams, setDroneParams] = useState({ - pageNum: 1, - pageSize: 20, - sn: '', - status: "" - }); - const [droneTotal, setDroneTotal] = useState(0); - - // 智能装备相关状态 - const [smartList, setSmartList] = useState([]); - const [selectedSmartKeys, setSelectedSmartKeys] = useState([]); - const [bindSmartModalVisible, setBindSmartModalVisible] = useState(false); - const [bindSmartForm] = Form.useForm(); - const [smartParams, setSmartParams] = useState({ - pageNum: 1, - pageSize: 20, - serialNumber: '', - }); - const [smartTotal, setSmartTotal] = useState(0); - - // 下拉数据状态 - const [orgOptions, setOrgOptions] = useState([]); - const [siteOptions, setSiteOptions] = useState([]); - const [userOptions, setUserOptions] = useState([]); - - // 全局匹配用的全量数据状态 const [allOrgOptions, setAllOrgOptions] = useState([]); const [allSiteOptions, setAllSiteOptions] = useState([]); const [allUserOptions, setAllUserOptions] = useState([]); - - const [editSmartModalVisible, setEditSmartModalVisible] = useState(false); - const [editSmartForm] = Form.useForm(); - const [currentEditSmart, setCurrentEditSmart] = useState(null); - - // 设备参数管理相关状态(由 DeviceParamModal 通用组件内部管理 API/表单逻辑) - const [paramModalVisible, setParamModalVisible] = useState(false); - const [currentParamDevice, setCurrentParamDevice] = useState(null); - - - const [paramModalVisible1, setParamModalVisible1] = useState(false); - const [currentParamDevice1, setCurrentParamDevice1] = useState(null); - const [paramData1, setParamData1] = useState(null); - const [paramForm1] = Form.useForm(); - - // 设备参数表单字段配置(仅其他弹窗/增益单独弹窗/批量弹窗用,主参数弹窗已封装至通用组件) - const paramFieldConfigs = [ - { key: 'leftForwardGain', label: '左轮前进增益', type: 'InputNumber', editable: true, min: 0, max: 1, step: 0.01 }, - { key: 'leftBackwardGain', label: '左轮后退增益', type: 'InputNumber', editable: true, min: 0, max: 1, step: 0.01 }, - { key: 'rightForwardGain', label: '右轮前进增益', type: 'InputNumber', editable: true, min: 0, max: 1, step: 0.01 }, - { key: 'rightBackwardGain', label: '右轮后退增益', type: 'InputNumber', editable: true, min: 0, max: 1, step: 0.01 }, - { key: 'runSpeed', label: '运行速度', type: 'InputNumber', editable: true }, - - { key: 'chipUidSign', label: '芯片UID固化标志位', type: 'InputNumber', editable: false }, - { key: 'chipUid', label: '芯片UID', type: 'Input', editable: false }, - { key: 'remoteConfig', label: '遥控器通道配置', type: 'InputNumber', editable: false }, - { key: 'knifeMotorMode', label: '割刀电机模式', type: 'InputNumber', editable: false }, - { key: 'walkMotorMode', label: '行走电机模式', type: 'InputNumber', editable: false }, - { key: 'leftMotorReverse', label: '左轮电机方向极性', type: 'InputNumber', editable: false }, - { key: 'rightMotorReverse', label: '右轮电机方向极性', type: 'InputNumber', editable: false }, - { key: 'swapChannel', label: '左右轮通道互换标志', type: 'InputNumber', editable: false }, - { key: 'use4G', label: '联网标志位', type: 'InputNumber', editable: false }, - { key: 'forwardSpeedLimit', label: '前进速度限制', type: 'InputNumber', editable: false }, - { key: 'turnSpeedLimit', label: '转向速度限制', type: 'InputNumber', editable: false }, - { key: 'knifePolarity', label: '割刀通道极性', type: 'InputNumber', editable: false }, - { key: 'fanPolarity', label: '风门通道极性', type: 'InputNumber', editable: false }, - { key: 'throttlePolarity', label: '油门通道极性', type: 'InputNumber', editable: false }, - { key: 'liftProtectTime', label: '底盘升降电机保护时间', type: 'InputNumber', editable: false }, - { key: 'dualRtk', label: 'RTK配置', type: 'InputNumber', editable: false }, - { key: 'knifeChannel', label: '割刀通道配置', type: 'InputNumber', editable: false }, - { key: 'fanChannel', label: '风门通道配置', type: 'InputNumber', editable: false }, - { key: 'throttleChannel', label: '油门通道配置', type: 'InputNumber', editable: false }, - { key: 'remoteType', label: '遥控器类型', type: 'InputNumber', editable: false }, - { key: 'relayBoard', label: '是否搭载继电器板', type: 'Checkbox', editable: false }, - { key: 'liftChannel', label: '底盘升通道配置', type: 'InputNumber', editable: false }, - { key: 'chassisChannel', label: '底盘降通道配置', type: 'InputNumber', editable: false }, - { key: 'armChannel', label: '抱闸通道配置', type: 'InputNumber', editable: false }, - { key: 'fuelPumpChannel', label: '燃油励磁通道配置', type: 'InputNumber', editable: false }, - { key: 'wifiName', label: 'wifi名称', type: 'Input', editable: false }, - { key: 'wifiPassword', label: 'wifi密码', type: 'Input', editable: false }, - { key: 'batteryType', label: '电池类型', type: 'InputNumber', editable: false }, - { key: 'driveType', label: '行走驱动配置', type: 'InputNumber', editable: false }, - { key: 'gearRatio', label: '转速比', type: 'InputNumber', editable: false, step: 0.01 }, - { key: 'robotLength', label: '机器人长度', type: 'InputNumber', editable: false, step: 0.01 }, - { key: 'robotWidth', label: '机器人宽度', type: 'InputNumber', editable: false, step: 0.01 }, - { key: 'robotHeight', label: '机器人高度', type: 'InputNumber', editable: false, step: 0.01 }, - { key: 'knifeWidth', label: '割刀宽度', type: 'InputNumber', editable: false, step: 0.01 }, - { key: 'tyreSize', label: '轮胎尺寸', type: 'InputNumber', editable: false, step: 0.01 }, - { key: 'firmwareVersion', label: '固件版本', type: 'Input', editable: false }, - { key: 'crc16', label: 'CRC16', type: 'InputNumber', editable: false }, - { key: 'tail1', label: 'tail1', type: 'InputNumber', editable: false }, - { key: 'tail2', label: 'tail2', type: 'InputNumber', editable: false }, - { key: 'remark', label: '备注', type: 'Input', editable: false }, - { key: 'delFlag', label: '删除标志', type: 'Checkbox', editable: false }, - ]; - - - // 获取无人机列表(带分页 + 筛选) - const fetchDroneList = async () => { - setLoading(true); - try { - const res = await getPlaneDeviceList(droneParams); - if (res.code === 200) { - setDroneList(res.rows || []); - setDroneTotal(res.total || res.rows?.length || 0); - } - } catch (err) { - utils.message.error('获取无人机列表失败'); - } finally { - setLoading(false); - } - }; - - // 打开智能装备编辑弹窗 - const openEditSmartModal = (record) => { - setCurrentEditSmart(record); - setEditSmartModalVisible(true); - editSmartForm.setFieldsValue({ - deviceAlias: record.deviceAlias, - serialNumber: record.serialNumber, - onlineStatus: record.onlineStatus, - }); - }; - - // 保存智能装备编辑 - const saveEditSmart = (values) => { - setSmartList(prev => prev.map(item => { - if (item.serialNumber === currentEditSmart.serialNumber) { - return { ...item, ...values }; - } - return item; - })); - utils.message.success('装备编辑成功'); - setEditSmartModalVisible(false); - editSmartForm.resetFields(); - setCurrentEditSmart(null); - }; - - // 打开设备参数管理弹窗(通用组件内部自动读取/保存参数) - const openParamModal = async (record) => { - getDevivePermisstion(record); - - }; - const openParamModal1 = async (record) => { - setCurrentParamDevice1(record); - setParamModalVisible1(true); - }; - const getDevivePermisstion = (data) => { - const params = { - deviceId: data.serialNumber, - } - - - hasPermission(params).then(res => { - if (res.code == 200) { - if (res.data) { - setCurrentParamDevice(data); - setParamModalVisible(true); - } else { - utils.message.error('无权限'); - } - } - }) - } - - - const saveParam1 = async (values) => { - try { - const payload = { - deviceId: currentParamDevice1.serialNumber, - duration: values?.minute, - }; - console.log(payload, "payload"); - const res = await getDeviceAuth(payload); - console.log(res, "res"); - if (res.code === 200 && res.data == true) { - utils.message.success('保存参数成功'); - setParamModalVisible1(false); - //await fetchParamData(currentParamDevice.serialNumber); - } else { - utils.message.error(res.msg || '授权失败'); - } - } catch (err) { - utils.message.error('操作异常'); - } - }; - - // 批量保存增益参数 - const saveBatchParam = async (values) => { - try { - // 选中的所有设备serialNumber数组 - const deviceIdList = [...selectedSmartKeys]; - const payload = { - params: { - leftForwardGain: Math.max(0.1, Math.min(1, values.leftForwardGain >= 10 ? values.leftForwardGain / 10 : values.leftForwardGain)), - leftBackwardGain: Math.max(0.1, Math.min(1, values.leftBackwardGain >= 10 ? values.leftBackwardGain / 10 : values.leftBackwardGain)), - rightForwardGain: Math.max(0.1, Math.min(1, values.rightForwardGain >= 10 ? values.rightForwardGain / 10 : values.rightForwardGain)), - rightBackwardGain: Math.max(0.1, Math.min(1, values.rightBackwardGain >= 10 ? values.rightBackwardGain / 10 : values.rightBackwardGain)), - }, - commandType: "WheelGainCoefficients", - deviceId: deviceIdList, // 批量传设备数组 - }; - const res = await deviceCommmand(payload); - if (res.code === 200) { - utils.message.success(`已批量为${deviceIdList.length}台设备更新增益参数`); - setBatchParamModalVisible(false); - batchParamForm.resetFields(); - setSelectedSmartKeys([]); // 清空选中 - } else { - utils.message.error(res.msg || '批量修改参数失败'); - } - } catch (err) { - utils.message.error('操作异常'); - console.error(err); - } - }; - - - const fetchSmartList = async () => { - setLoading(true); - try { - const res = await getAllDevice(smartParams); - if (res.code === 200) { - setSmartList(res.rows || []); - setSmartTotal(res.total || res.rows.length); - } - } catch (err) { - utils.message.error('获取智能装备列表失败'); - } finally { - setLoading(false); - } - }; - - // 获取组织列表 - const fetchOrgs = async () => { - try { - const res = await getOrgList({}); - if (res.code === 200) { - setOrgOptions(res.rows || []); - } - } catch (err) { - console.error('获取组织列表失败', err); - } - }; - - // 根据组织获取场站 - const fetchSites = async (orgId) => { - setSiteOptions([]); - try { - const res = await getSiteByOrgId({ id: orgId }); - if (res.code === 200) { - setSiteOptions(res.data || []); - } - } catch (err) { - console.error('获取场站列表失败', err); - } - }; - - // 获取用户列表 - const fetchUsers = async (siteId) => { - try { - const res = await getUser({ siteId, pageNum: 1, pageSize: 1000 }); - if (res.code === 200) { - setUserOptions(res.rows || []); - } - } catch (err) { - console.error('获取用户列表失败', err); - } - }; + const [dataRefreshKey, setDataRefreshKey] = useState(0); // 初始化时获取全量数据用于列表匹配 const fetchAllDataForMapping = async () => { @@ -375,333 +38,41 @@ export default function DeviceManagementPage() { }; useEffect(() => { - if (activeTab === 'drone' || activeTab === 'smart') { - fetchAllDataForMapping(); - } - }, [activeTab]); + fetchAllDataForMapping(); + }, [activeTab, dataRefreshKey]); - useEffect(() => { - if (activeTab === 'drone') fetchDroneList(); - }, [activeTab, droneParams]); - - useEffect(() => { - if (activeTab === 'smart') fetchSmartList(); - }, [activeTab, smartParams]); - - // 打开绑定弹窗 - const openBindModal = async () => { - if (selectedDroneKeys.length === 0) { - return utils.message.warning('请先在列表中选中需要绑定的无人机'); - } - setBindModalVisible(true); - await fetchOrgs(); - bindForm.setFieldsValue({ - sns: selectedDroneKeys.join(', '), - orgId: userInfo?.orgId || undefined, - siteId: userInfo?.siteId || undefined, - userId: undefined, - }); - - await fetchSites(userInfo?.orgId); - - //setSiteOptions([]); - setUserOptions([]); + const handleAllDataNeedRefresh = () => { + setDataRefreshKey(prev => prev + 1); }; - // 打开智能装备绑定弹窗 - const openBindSmartModal = async () => { - if (selectedSmartKeys.length === 0) { - return utils.message.warning('请先在列表中选中需要绑定的智能装备'); - } - setBindSmartModalVisible(true); - await fetchOrgs(); - bindSmartForm.setFieldsValue({ - deviceIds: selectedSmartKeys.join(', '), - orgId: userInfo?.orgId || undefined, - siteId: userInfo?.siteId || undefined, - userId: undefined, - }); - await fetchSites(userInfo?.orgId); - //setSiteOptions([]); - setUserOptions([]); - }; - - const handleBind = async (values) => { - try { - const payload = { - siteId: values.siteId, - orgId: values.orgId, - userId: values.userId, - sns: values.sns.split(',').map(s => s.trim()), - }; - const res = await bindDrone(payload); - if (res.code === 200 && res.data == true) { - utils.message.success('绑定成功'); - setBindModalVisible(false); - setSelectedDroneKeys([]); - bindForm.resetFields(); - fetchDroneList(); - } else { - utils.message.error(res.msg || '绑定失败'); - } - } catch (err) { - utils.message.error('操作异常'); - } - }; - - const handleBindSmart = async (values) => { - try { - const payload = { - siteId: values.siteId, - orgId: values.orgId, - userId: values.userId, - deviceIds: values.deviceIds.split(',').map(s => s.trim()), - }; - const res = await bindSmartDevice(payload); - if (res.code === 200 && res.data == true) { - utils.message.success('绑定成功'); - setBindSmartModalVisible(false); - bindSmartForm.resetFields(); - fetchSmartList(); - } else { - utils.message.error('绑定失败'); - } - } catch (err) { - utils.message.error('操作异常'); - } - }; - - const handleUnbindSmart = (deviceIds, type) => { - const validIds = (deviceIds || []).filter(id => id); - if (validIds.length === 0) { - return utils.message.warning('未选择有效设备,无法解绑'); - } - const typeText = { 1: '人员', 2: '场站', 3: '组织' }[type]; - modal.confirm({ - title: '确认解绑', - content: `确定要为选中的智能装备清除${typeText}绑定吗?`, - okText: '确定', - cancelText: '取消', - onOk: async () => { - try { - const res = await unbindSmartDevice({ deviceIds: validIds, type }); - if (res.code === 200) { - utils.message.success('解绑成功'); - fetchSmartList(); - setSelectedSmartKeys([]); - } else { - utils.message.error(res.msg || '解绑失败'); - } - } catch (err) { - utils.message.error('操作异常'); - } - } - }); - }; - - const smartColumns = [ - { title: '设备名称', width: 150, dataIndex: 'deviceAlias', key: 'name' }, - { title: '设备码', width: 280, dataIndex: 'serialNumber', key: 'sn' }, - { - title: '所属组织', width: 150, key: 'orgName', - render: (_, record) => { - const org = allOrgOptions.find(o => (o.orgId || o.id) == record.orgId); - return org ? (org.orgName || org.name) : 未绑定; - }, - }, - { - title: '所属场站', width: 150, key: 'siteName', - render: (_, record) => { - const site = allSiteOptions.find(s => (s.siteId || s.id) == record.siteId); - return site ? (site.siteName || site.name) : 未绑定; - }, - }, - { - title: '负责人', width: 150, key: 'userName', - render: (_, record) => { - const user = allUserOptions.find(u => u.userId == record.userId); - return user ? (user.nickName || user.userName) : 未绑定; - }, - }, - { title: '在线状态', width: 120, dataIndex: 'onlineStatus', key: 'online', render: s => {s === 'ONLINE' || s === 1 ? '在线' : '离线'} }, - { - title: '操作', width: 280, key: 'action', fixed: 'right', - render: (_, record) => ( - - - - - - - - - - - - - {userInfo?.userId == 1 && } - - - - ), - }, - ]; - - const handleUnbind = (sns, type) => { - const validSns = (sns || []).filter(item => - item !== null && item !== undefined && item !== '' && item.trim() !== '' - ); - if (validSns.length === 0) { - return utils.message.warning('未选择有效设备,无法解绑'); - } - const typeText = { 1: '人员', 2: '场站', 3: '组织' }[type]; - modal.confirm({ - title: '确认解绑', - content: `确定要为选中的无人机清除${typeText}绑定吗?`, - okText: '确定', - cancelText: '取消', - onOk: async () => { - try { - const res = await unbindDrone({ sns: validSns, type }); - if (res.code === 200) { - utils.message.success('解绑成功'); - fetchDroneList(); - setSelectedDroneKeys([]); - } else { - utils.message.error(res.msg || '解绑失败'); - } - } catch (err) { - utils.message.error('操作异常'); - } - } - }); - }; - - const droneColumns = [ - { title: '设备名称', width: 150, dataIndex: 'drone_callsign', key: 'name' }, - { title: 'SN码', width: 180, dataIndex: 'device_sn', key: 'sn' }, - { title: '机场SN', width: 150, dataIndex: 'gateway_sn', key: 'gsn' }, - { - title: '所属组织', width: 150, key: 'orgName', - render: (_, record) => { - const org = allOrgOptions.find(o => (o.orgId || o.id) == record.orgId); - return org ? (org.orgName || org.name) : 未绑定; - }, - }, - { - title: '所属场站', width: 150, key: 'siteName', - render: (_, record) => { - const site = allSiteOptions.find(s => (s.siteId || s.id) == record.siteId); - return site ? (site.siteName || site.name) : 未绑定; - }, - }, - { - title: '负责人', width: 150, key: 'userName', - render: (_, record) => { - const user = allUserOptions.find(u => u.userId == record.userId); - return user ? (user.nickName || user.userName) : 未绑定; - }, - }, - { - title: '状态', width: 150, dataIndex: 'onlineStatus', key: 'status', - render: s => {s === 1 ? '在线' : '离线'} - }, - { - title: '操作', key: 'action', fixed: 'right', width: 250, - render: (_, record) => ( - - - - - - - - - - - - ), - }, - ]; - - // 根据 Tab 过滤数据 - const filteredByTab = deviceList.filter(tabs.find(t => t.key === activeTab)?.filter || (() => false)); - - // 表格列定义 - const columns = [ - { title: '设备编码', dataIndex: 'code', key: 'code' }, - { title: '设备名称', dataIndex: 'name', key: 'name' }, - { title: '类型', dataIndex: 'type', key: 'type' }, - { - title: '在线', dataIndex: 'online', key: 'online', - render: (text) => ( - {text} - ), - }, - { - title: '运行', dataIndex: 'status', key: 'status', - render: (text) => { - const colorMap = { - NORMAL: 'green', - FAULT: 'red', - WARNING: 'orange', - IDLE: 'blue', - }; - return {text}; - }, - }, - { - title: '操作', key: 'action', - render: (_, record) => ( - - - - - ), - }, - ]; - - // 查询 - const handleSearch = () => { - form.validateFields().then(values => { - const keyword = values.keyword?.toLowerCase() || ''; - setLoading(true); - setTimeout(() => { - const filtered = allDeviceList.filter(item => - item.name.toLowerCase().includes(keyword) || item.code.toLowerCase().includes(keyword) + const renderTabContent = () => { + switch (activeTab) { + case 'serialGenerate': + return ; + case 'smart': + return ( + ); - setDeviceList(filtered); - setLoading(false); - }, 300); - }); - }; - - // 重置 - const handleReset = () => { - form.resetFields(); - setDeviceList(allDeviceList); - }; - - // 新增 - const handleAdd = () => { - message.info('打开新增设备弹窗'); - }; - - // 编辑 - const handleEdit = (record) => { - message.info(`编辑设备:${record.name}`); - }; - - // 删除 - const handleDelete = (record) => { - modal.confirm({ - title: '确认删除', - content: `确定要删除设备 ${record.name} 吗?`, - onOk: () => { - message.success(`设备 ${record.name} 已删除`); - setDeviceList(prev => prev.filter(item => item.id !== record.id)); - }, - }); + case 'drone': + return ( + + ); + case 'camera': + return ; + case 'all': + default: + return ; + } }; return ( @@ -717,390 +88,7 @@ export default function DeviceManagementPage() { - - {activeTab === 'serialGenerate' ? ( - - ) : activeTab === 'smart' ? ( - <> -
- - setSmartParams({ ...smartParams, serialNumber: e.target.value.trim() })} - /> - - - {/**/} - - - - -
- -
-
record.serialNumber} - columns={smartColumns} - dataSource={smartList} - loading={loading} - pagination={{ - current: smartParams.pageNum, - pageSize: smartParams.pageSize, - total: smartTotal, - showSizeChanger: true, - showTotal: (t) => `共 ${t} 条`, - onChange: (page, pageSize) => { - setSmartParams({ pageNum: page, pageSize }); - }, - }} - scroll={{ y: 'auto', x: '700px' }} - /> - - - { - setBindSmartModalVisible(false); - setSiteOptions([]); - setUserOptions([]); - bindSmartForm.resetFields(); - }} - onOk={() => bindSmartForm.submit()} - width={500} - > -
- - - - - - - - - - - - - -
- - {/* 智能装备编辑弹窗 */} - { - setEditSmartModalVisible(false); - setCurrentEditSmart(null); - editSmartForm.resetFields(); - }} - onOk={() => editSmartForm.submit()} - width={480} - > -
- - - - - - - - - - -
- {/* 设备增益参数 */} - - - - 设备授权 - - - {currentParamDevice1?.deviceAlias || currentParamDevice1?.serialNumber} - - - } - open={paramModalVisible1} - onCancel={() => { - setParamModalVisible1(false); - setCurrentParamDevice1(null); - setParamData1(null); - }} - footer={null} - width={560} - > -
- -
- 💡 - 设置设备操作授权,授权到期后设备将失去对应操作权限 -
-
- - 授权时间(min)} - name="minute" - rules={[ - { required: true, message: '请输入授权剩余时间' }, - { type: 'number', min: 0, message: '授权时间不能小于0' } - ]} - > - - - - - - -
- - -
-
- -
- - - {/* 设备参数管理弹窗 */} - { - setParamModalVisible(false); - setCurrentParamDevice(null); - }} - onSuccess={() => { - }} - /> - - - - - - ) : activeTab === 'drone' ? ( - <> - {/* 无人机搜索栏 */} -
- setDroneParams({ ...droneParams, sn: e.target.value })} - /> - - - - - - - -
- -
-
`共 ${t} 条`, - onChange: (page, pageSize) => { - setDroneParams({ ...droneParams, pageNum: page, pageSize }); - }, - }} - scroll={{ y: 'auto', x: '700px' }} - /> - - - { - setBindModalVisible(false); - setSiteOptions([]); - setUserOptions([]); - bindForm.resetFields(); - }} - onOk={() => bindForm.submit()} - width={500} - > -
- - - - - - - - - - - - - -
- - ) : ( - <> -
-
- - - - - - - - - - - - - -
-
`共 ${total} 条`, - }} - /> - - ) - } + {renderTabContent()} ); } diff --git a/src/components/SystemSetting/DroneManagement.tsx b/src/components/SystemSetting/DroneManagement.tsx new file mode 100644 index 0000000..05e7b0b --- /dev/null +++ b/src/components/SystemSetting/DroneManagement.tsx @@ -0,0 +1,456 @@ +import React, { useState, useEffect } from 'react'; +import { + Table, + Input, + Button, + Form, + Select, + Space, + Tag, + Modal, + Checkbox, + Tooltip, + Typography, + App, +} from 'antd'; +const { Text } = Typography; +import { + ReloadOutlined, + LinkOutlined, + DisconnectOutlined, +} from '@ant-design/icons'; + +import { + getPlaneDeviceList, + bindDrone, + unbindDrone, +} from '@/api/device'; +import { getOrgList } from '@/api/organization/index'; +import { getSiteByOrgId, getStationList } from '@/api/stationManage/index'; +import { getUser } from '@/api/user/index'; +import { useSelector } from 'react-redux'; +import { RootState } from '@/src/store'; +import utils from '@/lib/utils'; + +interface DroneManagementProps { + allOrgOptions: any[]; + allSiteOptions: any[]; + allUserOptions: any[]; + onAllDataNeedRefresh?: () => void; +} + +export default function DroneManagement({ + allOrgOptions, + allSiteOptions, + allUserOptions, + onAllDataNeedRefresh, +}: DroneManagementProps) { + const { modal } = App.useApp(); + const { userInfo } = useSelector((state: RootState) => state.user); + const { stationId, currentStation } = useSelector((state: RootState) => state.station); + + const [loading, setLoading] = useState(false); + const [droneList, setDroneList] = useState([]); + const [bindModalVisible, setBindModalVisible] = useState(false); + const [bindForm] = Form.useForm(); + const [selectedDroneKeys, setSelectedDroneKeys] = useState([]); + + const [droneParams, setDroneParams] = useState({ + pageNum: 1, + pageSize: 20, + sn: '', + status: '' + }); + const [droneTotal, setDroneTotal] = useState(0); + + const [droneOrgOptions, setDroneOrgOptions] = useState([]); + const [droneSiteOptions, setDroneSiteOptions] = useState([]); + const [droneUserOptions, setDroneUserOptions] = useState([]); + + const isAdmin = userInfo?.roleKey === 'admin'; + const isManager = userInfo?.roleKey === 'manager' || userInfo?.roleKey === 'Manager'; + const orgFieldDisabled = !isAdmin; + const siteFieldDisabled = !(isAdmin || isManager); + const userFieldDisabled = false; + + const defaultOrgId = currentStation?.orgId || userInfo?.orgId; + const defaultSiteId = stationId || currentStation?.siteId || currentStation?.id; + const defaultUserId = userInfo?.userId; + + const fetchDroneList = async () => { + setLoading(true); + try { + const res = await getPlaneDeviceList(droneParams); + if (res.code === 200) { + setDroneList(res.rows || []); + setDroneTotal(res.total || res.rows?.length || 0); + } + } catch (err) { + utils.message.error('获取无人机列表失败'); + } finally { + setLoading(false); + } + }; + + const fetchOrgs = async () => { + try { + const res = await getOrgList({}); + if (res.code === 200) { + setDroneOrgOptions(res.rows || []); + } + return res; + } catch (err) { + console.error('获取组织列表失败', err); + return null; + } + }; + + const fetchSites = async (orgId) => { + try { + const res = await getSiteByOrgId({ id: orgId }); + if (res.code === 200) { + setDroneSiteOptions(res.data || []); + } + return res; + } catch (err) { + console.error(err); + return null; + } + }; + + const fetchUsers = async (siteId) => { + try { + const res = await getUser({ + siteId, + pageNum: 1, + pageSize: 1000 + }); + if (res.code === 200) { + setDroneUserOptions(res.rows || []); + } + return res; + } catch (err) { + console.error(err); + return null; + } + }; + + useEffect(() => { + fetchDroneList(); + }, [droneParams]); + + const openBindModal = async () => { + if (!selectedDroneKeys.length) { + return utils.message.warning('请先在列表中选中需要绑定的无人机'); + } + bindForm.resetFields(); + setBindModalVisible(true); + setDroneOrgOptions([]); + setDroneSiteOptions([]); + setDroneUserOptions([]); + }; + + useEffect(() => { + if (!bindModalVisible) { + bindForm.resetFields(); + return; + } + const fetchAndSetForm = async () => { + const orgRes = await fetchOrgs(); + const orgList = orgRes?.code === 200 ? (orgRes?.rows || []) : []; + setDroneOrgOptions(orgList); + + const finalOrgId = defaultOrgId && orgList.some(o => (o.id || o.orgId) == defaultOrgId) + ? defaultOrgId + : (orgList[0]?.id || orgList[0]?.orgId); + + let siteList = []; + if (finalOrgId) { + const siteRes = await fetchSites(finalOrgId); + siteList = siteRes?.code === 200 ? (siteRes?.data || []) : []; + } + setDroneSiteOptions(siteList); + + const finalSiteId = defaultSiteId && siteList.some(s => (s.id || s.siteId) == defaultSiteId) + ? defaultSiteId + : (siteList[0]?.id || siteList[0]?.siteId); + + let userList = []; + if (finalSiteId) { + const userRes = await fetchUsers(finalSiteId); + userList = userRes?.code === 200 ? (userRes?.rows || []) : []; + } + setDroneUserOptions(userList); + + const finalUserId = defaultUserId && userList.some(u => u.userId == defaultUserId) + ? defaultUserId + : (userList[0]?.userId); + + requestAnimationFrame(() => { + bindForm.setFieldsValue({ + sns: selectedDroneKeys.join(', '), + orgId: finalOrgId, + siteId: finalSiteId, + userId: finalUserId, + }); + }); + }; + fetchAndSetForm(); + }, [bindModalVisible, defaultOrgId, defaultSiteId, defaultUserId, selectedDroneKeys]); + + const handleBind = async (values) => { + try { + const payload = { + siteId: values.siteId, + orgId: values.orgId, + userId: values.userId, + sns: values.sns.split(',').map(s => s.trim()), + }; + const res = await bindDrone(payload); + if (res.code === 200 && res.data == true) { + utils.message.success('绑定成功'); + setBindModalVisible(false); + setSelectedDroneKeys([]); + bindForm.resetFields(); + fetchDroneList(); + onAllDataNeedRefresh?.(); + } else { + utils.message.error(res.msg || '绑定失败'); + } + } catch (err) { + utils.message.error('操作异常'); + } + }; + + const handleUnbind = (sns, type) => { + const validSns = (sns || []).filter(item => + item !== null && item !== undefined && item !== '' && item.trim() !== '' + ); + if (validSns.length === 0) { + return utils.message.warning('未选择有效设备,无法解绑'); + } + const typeText = { 1: '人员', 2: '场站', 3: '组织' }[type]; + modal.confirm({ + title: '确认解绑', + content: `确定要为选中的无人机清除${typeText}绑定吗?`, + okText: '确定', + cancelText: '取消', + onOk: async () => { + try { + const res = await unbindDrone({ sns: validSns, type }); + if (res.code === 200) { + utils.message.success('解绑成功'); + fetchDroneList(); + setSelectedDroneKeys([]); + onAllDataNeedRefresh?.(); + } else { + utils.message.error(res.msg || '解绑失败'); + } + } catch (err) { + utils.message.error('操作异常'); + } + } + }); + }; + + const droneColumns = [ + { title: '设备名称', width: 150, dataIndex: 'drone_callsign', key: 'name' }, + { title: 'SN码', width: 180, dataIndex: 'device_sn', key: 'sn' }, + { title: '机场SN', width: 150, dataIndex: 'gateway_sn', key: 'gsn' }, + { + title: '所属组织', width: 150, key: 'orgName', + render: (_, record) => { + const org = allOrgOptions.find(o => (o.orgId || o.id) == record.orgId); + return org ? (org.orgName || org.name) : 未绑定; + }, + }, + { + title: '所属场站', width: 150, key: 'siteName', + render: (_, record) => { + const site = allSiteOptions.find(s => (s.siteId || s.id) == record.siteId); + return site ? (site.siteName || site.name) : 未绑定; + }, + }, + { + title: '负责人', width: 150, key: 'userName', + render: (_, record) => { + const user = allUserOptions.find(u => u.userId == record.userId); + return user ? (user.nickName || user.userName) : 未绑定; + }, + }, + { + title: '状态', width: 150, dataIndex: 'onlineStatus', key: 'status', + render: s => {s === 1 ? '在线' : '离线'} + }, + { + title: '操作', key: 'action', fixed: 'right' as const, width: 250, + render: (_, record: any) => ( + + + + + + + + + + + + ), + }, + ]; + + const selectorgId = Form.useWatch('orgId', bindForm); + const selectsiteId = Form.useWatch('siteId', bindForm); + + useEffect(() => { + if (selectorgId) { + fetchSites(selectorgId); + } + }, [selectorgId]); + + useEffect(() => { + if (selectsiteId) { + fetchUsers(selectsiteId); + } + }, [selectsiteId]); + + return ( + <> +
+ setDroneParams({ ...droneParams, sn: e.target.value })} + /> + + + + + + +
+ +
+
`共 ${t} 条`, + onChange: (page, pageSize) => { + setDroneParams({ ...droneParams, pageNum: page, pageSize }); + }, + }} + scroll={{ y: 'auto', x: '700px' }} + /> + + + + + 绑定无人机 + + } + open={bindModalVisible} + onCancel={() => { + setBindModalVisible(false); + setDroneOrgOptions([]); + setDroneSiteOptions([]); + setDroneUserOptions([]); + bindForm.resetFields(); + }} + destroyOnClose={false} + onOk={() => bindForm.submit()} + width={540} + > +
+ 已选无人机({selectedDroneKeys.length}台)SN} name="sns" rules={[{ required: true, message: '请输入 SN' }]}> + + + + 所属组织 + + } + name="orgId" rules={[{ required: true, message: '请选择组织' }]} + > + + + + 所属场站 + + } + name="siteId" rules={[{ required: !siteFieldDisabled, message: '请选择场站' }]} + > + + + + + + +
+ + ); +} diff --git a/src/components/SystemSetting/SmartDeviceManagement.tsx b/src/components/SystemSetting/SmartDeviceManagement.tsx new file mode 100644 index 0000000..fecd59e --- /dev/null +++ b/src/components/SystemSetting/SmartDeviceManagement.tsx @@ -0,0 +1,608 @@ +import React, { useState, useEffect } from 'react'; +import { + Table, + Input, + Button, + Form, + Select, + Space, + Tag, + Modal, + Checkbox, + Tooltip, + Typography, + App, + Card, + InputNumber, +} from 'antd'; +const { Text } = Typography; +import { + ReloadOutlined, + LinkOutlined, + DisconnectOutlined, + EditOutlined, +} from '@ant-design/icons'; + +import { + getAllDevice, + bindSmartDevice, + unbindSmartDevice, + getDeviceAuth, + hasPermission, +} from '@/api/device'; +import { getOrgList } from '@/api/organization/index'; +import { getSiteByOrgId } from '@/api/stationManage/index'; +import { getUser } from '@/api/user/index'; +import { useSelector } from 'react-redux'; +import { RootState } from '@/src/store'; +import utils from '@/lib/utils'; +import DeviceParamModal from '../devices/DeviceParamModal'; + +interface SmartDeviceManagementProps { + allOrgOptions: any[]; + allSiteOptions: any[]; + allUserOptions: any[]; + onAllDataNeedRefresh?: () => void; +} + +export default function SmartDeviceManagement({ + allOrgOptions, + allSiteOptions, + allUserOptions, + onAllDataNeedRefresh, +}: SmartDeviceManagementProps) { + const { modal } = App.useApp(); + const { userInfo } = useSelector((state: RootState) => state.user); + const { stationId, currentStation } = useSelector((state: RootState) => state.station); + + const [loading, setLoading] = useState(false); + const [smartList, setSmartList] = useState([]); + const [selectedSmartKeys, setSelectedSmartKeys] = useState([]); + const [bindSmartModalVisible, setBindSmartModalVisible] = useState(false); + const [bindSmartForm] = Form.useForm(); + const [smartParams, setSmartParams] = useState({ + pageNum: 1, + pageSize: 20, + serialNumber: '', + }); + const [smartTotal, setSmartTotal] = useState(0); + + const [smartOrgOptions, setSmartOrgOptions] = useState([]); + const [smartSiteOptions, setSmartSiteOptions] = useState([]); + const [smartUserOptions, setSmartUserOptions] = useState([]); + + const [editSmartModalVisible, setEditSmartModalVisible] = useState(false); + const [editSmartForm] = Form.useForm(); + const [currentEditSmart, setCurrentEditSmart] = useState(null); + + const [paramModalVisible, setParamModalVisible] = useState(false); + const [currentParamDevice, setCurrentParamDevice] = useState(null); + + const [paramModalVisible1, setParamModalVisible1] = useState(false); + const [currentParamDevice1, setCurrentParamDevice1] = useState(null); + const [paramForm1] = Form.useForm(); + + const isAdmin = userInfo?.roleKey === 'admin'; + const isManager = userInfo?.roleKey === 'manager' || userInfo?.roleKey === 'Manager'; + const orgFieldDisabled = !isAdmin; + const siteFieldDisabled = !(isAdmin || isManager); + + const defaultOrgId = currentStation?.orgId || userInfo?.orgId; + const defaultSiteId = stationId || currentStation?.siteId || currentStation?.id; + const defaultUserId = userInfo?.userId; + + const fetchSmartList = async () => { + setLoading(true); + try { + const res = await getAllDevice(smartParams); + if (res.code === 200) { + setSmartList(res.rows || []); + setSmartTotal(res.total || res.rows.length); + } + } catch (err) { + utils.message.error('获取智能装备列表失败'); + } finally { + setLoading(false); + } + }; + + const fetchOrgs = async () => { + try { + const res = await getOrgList({}); + if (res.code === 200) { + setSmartOrgOptions(res.rows || []); + } + return res; + } catch (err) { + console.error('获取组织列表失败', err); + return null; + } + }; + + const fetchSites = async (orgId) => { + try { + const res = await getSiteByOrgId({ id: orgId }); + if (res.code === 200) { + setSmartSiteOptions(res.data || []); + } + return res; + } catch (err) { + console.error(err); + return null; + } + }; + + const fetchUsers = async (siteId) => { + try { + const res = await getUser({ + siteId, + pageNum: 1, + pageSize: 1000 + }); + if (res.code === 200) { + setSmartUserOptions(res.rows || []); + } + return res; + } catch (err) { + console.error(err); + return null; + } + }; + + useEffect(() => { + fetchSmartList(); + }, [smartParams]); + + const openEditSmartModal = (record) => { + setCurrentEditSmart(record); + setEditSmartModalVisible(true); + editSmartForm.setFieldsValue({ + deviceAlias: record.deviceAlias, + serialNumber: record.serialNumber, + onlineStatus: record.onlineStatus, + }); + }; + + const saveEditSmart = (values) => { + setSmartList(prev => prev.map((item: any) => { + if (item.serialNumber === (currentEditSmart as any)?.serialNumber) { + return { ...item, ...values }; + } + return item; + })); + utils.message.success('装备编辑成功'); + setEditSmartModalVisible(false); + editSmartForm.resetFields(); + setCurrentEditSmart(null); + }; + + const openParamModal = async (record) => { + getDevivePermisstion(record); + }; + + const openParamModal1 = async (record) => { + setCurrentParamDevice1(record); + setParamModalVisible1(true); + }; + + const getDevivePermisstion = (data) => { + const params = { + deviceId: data.serialNumber, + }; + hasPermission(params).then(res => { + if (res.code == 200) { + if (res.data) { + setCurrentParamDevice(data); + setParamModalVisible(true); + } else { + utils.message.error('无权限'); + } + } + }); + }; + + const saveParam1 = async (values) => { + try { + const payload = { + deviceId: (currentParamDevice1 as any)?.serialNumber, + duration: values?.minute, + }; + const res = await getDeviceAuth(payload); + if (res.code === 200 && res.data == true) { + utils.message.success('保存参数成功'); + setParamModalVisible1(false); + } else { + utils.message.error(res.msg || '授权失败'); + } + } catch (err) { + utils.message.error('操作异常'); + } + }; + + const openBindSmartModal = async () => { + if (!selectedSmartKeys.length) { + return utils.message.warning('请先在列表中选中需要绑定的智能装备'); + } + bindSmartForm.resetFields(); + setBindSmartModalVisible(true); + setSmartOrgOptions([]); + setSmartSiteOptions([]); + setSmartUserOptions([]); + const orgRes = await fetchOrgs(); + const orgList = orgRes?.code === 200 ? (orgRes?.rows || []) : []; + const finalOrgId = defaultOrgId && orgList.some(o => (o.id || o.orgId) == defaultOrgId) + ? defaultOrgId + : (orgList[0]?.id || orgList[0]?.orgId); + let siteList = []; + if (finalOrgId) { + const siteRes = await fetchSites(finalOrgId); + siteList = siteRes?.code === 200 ? (siteRes?.data || []) : []; + } + const finalSiteId = defaultSiteId && siteList.some(s => (s.id || s.siteId) == defaultSiteId) + ? defaultSiteId + : (siteList[0]?.id || siteList[0]?.siteId); + let userList = []; + if (finalSiteId) { + const userRes = await fetchUsers(finalSiteId); + userList = userRes?.code === 200 ? (userRes.rows || []) : []; + } + const finalUserId = defaultUserId && userList.some(u => u.userId == defaultUserId) + ? defaultUserId + : (userList[0]?.userId); + setTimeout(() => { + bindSmartForm.setFieldsValue({ + deviceIds: selectedSmartKeys.join(', '), + orgId: finalOrgId, + siteId: finalSiteId, + userId: Number(finalUserId) || '', + }); + }, 0); + }; + + const handleBindSmart = async (values) => { + try { + const payload = { + siteId: values.siteId, + orgId: values.orgId, + userId: values.userId, + deviceIds: values.deviceIds.split(',').map(s => s.trim()), + }; + const res = await bindSmartDevice(payload); + if (res.code === 200 && res.data == true) { + utils.message.success('绑定成功'); + setBindSmartModalVisible(false); + bindSmartForm.resetFields(); + fetchSmartList(); + onAllDataNeedRefresh?.(); + } else { + utils.message.error('绑定失败'); + } + } catch (err) { + utils.message.error('操作异常'); + } + }; + + const handleUnbindSmart = (deviceIds, type) => { + const validIds = (deviceIds || []).filter(id => id); + if (validIds.length === 0) { + return utils.message.warning('未选择有效设备,无法解绑'); + } + const typeText = { 1: '人员', 2: '场站', 3: '组织' }[type as number]; + modal.confirm({ + title: '确认解绑', + content: `确定要为选中的智能装备清除${typeText}绑定吗?`, + okText: '确定', + cancelText: '取消', + onOk: async () => { + try { + const res = await unbindSmartDevice({ deviceIds: validIds, type }); + if (res.code === 200) { + utils.message.success('解绑成功'); + fetchSmartList(); + setSelectedSmartKeys([]); + onAllDataNeedRefresh?.(); + } else { + utils.message.error(res.msg || '解绑失败'); + } + } catch (err) { + utils.message.error('操作异常'); + } + } + }); + }; + + const smartColumns = [ + { title: '设备名称', width: 150, dataIndex: 'deviceAlias', key: 'name' }, + { title: '设备码', width: 280, dataIndex: 'serialNumber', key: 'sn' }, + { + title: '所属组织', width: 150, key: 'orgName', + render: (_, record: any) => { + const org = allOrgOptions.find(o => (o.orgId || o.id) == record.orgId); + return org ? (org.orgName || org.name) : 未绑定; + }, + }, + { + title: '所属场站', width: 150, key: 'siteName', + render: (_, record: any) => { + const site = allSiteOptions.find(s => (s.siteId || s.id) == record.siteId); + return site ? (site.siteName || site.name) : 未绑定; + }, + }, + { + title: '负责人', width: 150, key: 'userName', + render: (_, record: any) => { + const user = allUserOptions.find(u => u.userId == record.userId); + return user ? (user.nickName || user.userName) : 未绑定; + }, + }, + { title: '在线状态', width: 120, dataIndex: 'onlineStatus', key: 'online', render: s => {s === 'ONLINE' || s === 1 ? '在线' : '离线'} }, + { + title: '操作', width: 280, key: 'action', fixed: 'right' as const, + render: (_, record: any) => ( + + + + + + + + + + + + {userInfo?.userId == 1 && } + + ), + }, + ]; + + const selectorgId = Form.useWatch('orgId', bindSmartForm); + const selectsiteId = Form.useWatch('siteId', bindSmartForm); + + useEffect(() => { + if (selectorgId) { + fetchSites(selectorgId); + } + }, [selectorgId]); + + useEffect(() => { + if (selectsiteId) { + fetchUsers(selectsiteId); + } + }, [selectsiteId]); + + return ( + <> +
+ + setSmartParams({ ...smartParams, serialNumber: e.target.value.trim() })} + /> + + + + + + +
+ +
+
record.serialNumber} + columns={smartColumns} + dataSource={smartList} + loading={loading} + pagination={{ + current: smartParams.pageNum, + pageSize: smartParams.pageSize, + total: smartTotal, + showSizeChanger: true, + showTotal: (t) => `共 ${t} 条`, + onChange: (page, pageSize) => { + setSmartParams({ pageNum: page, pageSize }); + }, + }} + scroll={{ y: 'auto', x: '700px' }} + /> + + + + + 绑定智能装备 + + } + open={bindSmartModalVisible} + onCancel={() => { + setBindSmartModalVisible(false); + setSmartOrgOptions([]); + setSmartSiteOptions([]); + setSmartUserOptions([]); + bindSmartForm.resetFields(); + }} + destroyOnClose={false} + onOk={() => bindSmartForm.submit()} + width={540} + > +
+ 已选智能装备({selectedSmartKeys.length}台)ID} name="deviceIds" rules={[{ required: true, message: '请选择装备' }]}> + + + + 所属组织 + + } + name="orgId" rules={[{ required: true, message: '请选择组织' }]} + > + + + + 所属场站 + + } + name="siteId" rules={[{ required: !siteFieldDisabled, message: '请选择场站' }]} + > + + + + + + +
+ + { + setEditSmartModalVisible(false); + setCurrentEditSmart(null); + editSmartForm.resetFields(); + }} + onOk={() => editSmartForm.submit()} + width={480} + > +
+ + + + + + + + + + +
+ + + + 设备授权 + + - {(currentParamDevice1 as any)?.deviceAlias || (currentParamDevice1 as any)?.serialNumber} + + + } + open={paramModalVisible1} + onCancel={() => { + setParamModalVisible1(false); + setCurrentParamDevice1(null); + }} + footer={null} + width={560} + > +
+ +
+ 💡 + 设置设备操作授权,授权到期后设备将失去对应操作权限 +
+
+ 授权时间(min)} + name="minute" + rules={[ + { required: true, message: '请输入授权剩余时间' }, + { type: 'number', min: 0, message: '授权时间不能小于0' } + ]} + > + + + +
+ + +
+
+ +
+ + { + setParamModalVisible(false); + setCurrentParamDevice(null); + }} + onSuccess={() => { + }} + /> + + ); +} diff --git a/src/components/devices/DeviceParamModal.tsx b/src/components/devices/DeviceParamModal.tsx index 53212e7..95a0e60 100644 --- a/src/components/devices/DeviceParamModal.tsx +++ b/src/components/devices/DeviceParamModal.tsx @@ -2,22 +2,20 @@ import React, { useState, useEffect } from 'react'; import { Modal, Form, - Card, Row, Col, InputNumber, Input, Checkbox, Slider, - Button, - Space, + Spin, } from 'antd'; import { useSelector } from 'react-redux'; import { RootState } from '@/store'; import { deviceRunParamSelectByDeviceId, deviceRunParamSave } from '@/api/stationManage/index'; import utils from '@/lib/utils'; -const GainSliderInput = ({ value = 0.5, onChange, disabled, min = 0.1, max = 1, step = 0.01 }) => { +const GainSliderInput = ({ value, onChange, disabled, min = 0.1, max = 1, step = 0.01 }) => { const handleSliderChange = (val) => { onChange?.(val); }; @@ -165,11 +163,13 @@ const DeviceParamModal: React.FC = ({ open, device, onCan setParamData(res.data); paramForm.setFieldsValue({ ...res.data }); } else { + utils.message.error('获取参数失败'); + setParamData(null); paramForm.resetFields(); } } else { - utils.message.error(res.msg || '获取参数失败'); + utils.message.error('获取参数失败'); } } catch (err) { utils.message.error('获取参数失败'); @@ -179,6 +179,7 @@ const DeviceParamModal: React.FC = ({ open, device, onCan }; const saveParam = async (values) => { + setLoading(true); try { const payload = { leftForwardGain: Math.max(0.1, Math.min(1, values.leftForwardGain >= 10 ? values.leftForwardGain / 10 : values.leftForwardGain)), @@ -200,6 +201,8 @@ const DeviceParamModal: React.FC = ({ open, device, onCan } } catch (err) { utils.message.error('操作异常'); + } finally { + setLoading(false); } }; @@ -231,85 +234,80 @@ const DeviceParamModal: React.FC = ({ open, device, onCan onOk={() => paramForm.submit()} confirmLoading={loading} width={780} - bodyStyle={{ padding: '16px 24px 0px' }} destroyOnClose > -
-
+ {/* 加载时表单区域简单遮罩提示(打开弹窗拉取参数时的loading反馈) */} +
+ + - - {editableKeys.map((key) => { - const field = paramFieldConfigs.find(f => f.key === key); - if (!field) return null; - const isGainSliderField = ['leftForwardGain', 'leftBackwardGain', 'rightForwardGain', 'rightBackwardGain'].includes(key); - const disabled = !field.editable; - return ( -
- {field.label}} - name={field.key} - > - {isGainSliderField ? ( - - ) : ( - - )} - - - ); - })} - + + {editableKeys.map((key) => { + const field = paramFieldConfigs.find(f => f.key === key); + if (!field) return null; + const isGainSliderField = ['leftForwardGain', 'leftBackwardGain', 'rightForwardGain', 'rightBackwardGain'].includes(key); + const disabled = !field.editable; + return ( + + {field.label}} + name={field.key} + > + {isGainSliderField ? ( + + ) : ( + + )} + + + ); + })} + - - {paramFieldConfigs.filter(f => !editableKeys.includes(f.key)).map((field) => { - const disabled = !field.editable; - return ( - - - {field.type === 'InputNumber' ? ( - - ) : field.type === 'Checkbox' ? ( - - ) : ( - - )} - - - ); - })} - - - -
- - -
-
- + + {paramFieldConfigs.filter(f => !editableKeys.includes(f.key)).map((field) => { + const disabled = !field.editable; + return ( + + + {field.type === 'InputNumber' ? ( + + ) : field.type === 'Checkbox' ? ( + + ) : ( + + )} + + + ); + })} + + + );