根据场站 和 管理界面的无人机 加上筛选参数 修改逻辑

This commit is contained in:
mmc
2026-05-26 14:14:57 +08:00
parent ce097f78d6
commit ba272ea3ac
4 changed files with 99 additions and 146 deletions

View File

@@ -8,10 +8,11 @@ export function getDeviceListApi(data) {
});
}
export function getPlaneDeviceList() {
export function getPlaneDeviceList(params) {
return request({
url: '/iot/UAV/getUAVList',
method: 'get',
params
})
}

View File

@@ -63,8 +63,6 @@ const tabs = [
{ key: 'smart', label: '智能装备', filter: (item) => ['CLEANING_DEVICE', 'ROBOT', 'DRONE'].includes(item.type) },
];
export default function DeviceManagementPage() {
const [activeTab, setActiveTab] = useState('all');
const { modal } = App.useApp();
@@ -80,6 +78,15 @@ export default function DeviceManagementPage() {
const [bindForm] = Form.useForm();
const [selectedDroneKeys, setSelectedDroneKeys] = useState([]);
// 无人机分页 + 筛选
const [droneParams, setDroneParams] = useState({
pageNum: 1,
pageSize: 10,
gateway_sn: '',
status: "",
});
const [droneTotal, setDroneTotal] = useState(0);
// 智能装备相关状态
const [smartList, setSmartList] = useState([]);
const [selectedSmartKeys, setSelectedSmartKeys] = useState([]);
@@ -91,23 +98,24 @@ export default function DeviceManagementPage() {
});
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 fetchDroneList = async () => {
setLoading(true);
try {
const res: any = await getPlaneDeviceList();
const res = await getPlaneDeviceList(droneParams);
if (res.code === 200) {
setDroneList(res.data?.detail || res.data || []);
setDroneList(res.rows || []);
setDroneTotal(res.total || res.rows?.length || 0);
}
} catch (err) {
utils.message.error('获取无人机列表失败');
@@ -119,11 +127,10 @@ export default function DeviceManagementPage() {
const fetchSmartList = async () => {
setLoading(true);
try {
const res: any = await getSmartDeviceList(smartParams);
const res = await getSmartDeviceList(smartParams);
if (res.code === 200) {
setSmartList(res.rows || []);
setSmartTotal(res.total || res.rows.length); // 👈 关键:把总数存起来
setSmartTotal(res.total || res.rows.length);
}
} catch (err) {
utils.message.error('获取智能装备列表失败');
@@ -135,7 +142,7 @@ export default function DeviceManagementPage() {
// 获取组织列表
const fetchOrgs = async () => {
try {
const res: any = await getOrgList({});
const res = await getOrgList({});
if (res.code === 200) {
setOrgOptions(res.rows || []);
}
@@ -148,7 +155,7 @@ export default function DeviceManagementPage() {
const fetchSites = async (orgId) => {
setSiteOptions([]);
try {
const res: any = await getSiteByOrgId({ id: orgId });
const res = await getSiteByOrgId({ id: orgId });
if (res.code === 200) {
setSiteOptions(res.data || []);
}
@@ -160,7 +167,7 @@ export default function DeviceManagementPage() {
// 获取用户列表
const fetchUsers = async (siteId) => {
try {
const res: any = await getUser({ siteId, pageNum: 1, pageSize: 1000 });
const res = await getUser({ siteId, pageNum: 1, pageSize: 1000 });
if (res.code === 200) {
setUserOptions(res.rows || []);
}
@@ -169,15 +176,14 @@ export default function DeviceManagementPage() {
}
};
// ✅ 新增:初始化时获取全量数据用于列表匹配
// 初始化时获取全量数据用于列表匹配
const fetchAllDataForMapping = async () => {
try {
const [orgRes, siteRes, userRes]: any = await Promise.all([
const [orgRes, siteRes, userRes] = await Promise.all([
getOrgList({ pageNum: 1, pageSize: 1000 }),
getStationList({ pageNum: 1, pageSize: 1000 }),
getUser({ pageNum: 1, pageSize: 10000 })
]);
if (orgRes.code === 200) setAllOrgOptions(orgRes.rows || []);
if (siteRes.code === 200) setAllSiteOptions(siteRes.rows || []);
if (userRes.code === 200) setAllUserOptions(userRes.rows || []);
@@ -189,20 +195,24 @@ export default function DeviceManagementPage() {
useEffect(() => {
if (activeTab === 'drone' || activeTab === 'smart') {
fetchAllDataForMapping();
if (activeTab === 'drone') fetchDroneList();
if (activeTab === 'smart') fetchSmartList();
}
}, [activeTab]);
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();
// ✅ 修改:不再回显,仅填入已选 SN
bindForm.setFieldsValue({
sns: selectedDroneKeys.join(', '),
orgId: undefined,
@@ -218,10 +228,8 @@ export default function DeviceManagementPage() {
if (selectedSmartKeys.length === 0) {
return utils.message.warning('请先在列表中选中需要绑定的智能装备');
}
setBindSmartModalVisible(true);
await fetchOrgs();
bindSmartForm.setFieldsValue({
deviceIds: selectedSmartKeys.join(', '),
orgId: undefined,
@@ -240,7 +248,7 @@ export default function DeviceManagementPage() {
userId: values.userId,
sns: values.sns.split(',').map(s => s.trim()),
};
const res: any = await bindDrone(payload);
const res = await bindDrone(payload);
if (res.code === 200 && res.data == true) {
utils.message.success('绑定成功');
setBindModalVisible(false);
@@ -263,7 +271,7 @@ export default function DeviceManagementPage() {
userId: values.userId,
deviceIds: values.deviceIds.split(',').map(s => s.trim()),
};
const res: any = await bindSmartDevice(payload);
const res = await bindSmartDevice(payload);
if (res.code === 200) {
utils.message.success('绑定成功');
setBindSmartModalVisible(false);
@@ -282,9 +290,7 @@ export default function DeviceManagementPage() {
if (validIds.length === 0) {
return utils.message.warning('未选择有效设备,无法解绑');
}
const typeText = { 1: '人员', 2: '场站', 3: '组织' }[type];
modal.confirm({
title: '确认解绑',
content: `确定要为选中的智能装备清除${typeText}绑定吗?`,
@@ -292,7 +298,7 @@ export default function DeviceManagementPage() {
cancelText: '取消',
onOk: async () => {
try {
const res: any = await unbindSmartDevice({ deviceIds: validIds, type });
const res = await unbindSmartDevice({ deviceIds: validIds, type });
if (res.code === 200) {
utils.message.success('解绑成功');
fetchSmartList();
@@ -311,27 +317,21 @@ export default function DeviceManagementPage() {
{ title: '设备名称', width: 150, dataIndex: 'deviceAlias', key: 'name' },
{ title: '设备码', width: 280, dataIndex: 'serialNumber', key: 'sn' },
{
title: '所属组织',
width: 150,
key: 'orgName',
title: '所属组织', width: 150, key: 'orgName',
render: (_, record) => {
const org = allOrgOptions.find(o => (o.orgId || o.id) == record.orgId);
return org ? (org.orgName || org.name) : <Text type="secondary">未绑定</Text>;
},
},
{
title: '所属场站',
width: 150,
key: 'siteName',
title: '所属场站', width: 150, key: 'siteName',
render: (_, record) => {
const site = allSiteOptions.find(s => (s.siteId || s.id) == record.siteId);
return site ? (site.siteName || site.name) : <Text type="secondary">未绑定</Text>;
},
},
{
title: '负责人',
width: 150,
key: 'userName',
title: '负责人', width: 150, key: 'userName',
render: (_, record) => {
const user = allUserOptions.find(u => u.userId == record.userId);
return user ? (user.nickName || user.userName) : <Text type="secondary">未绑定</Text>;
@@ -339,10 +339,7 @@ export default function DeviceManagementPage() {
},
{ title: '在线状态', width: 120, dataIndex: 'onlineStatus', key: 'online', render: s => <Tag color={s === 'ONLINE' || s === 1 ? 'green' : 'red'}>{s === 'ONLINE' || s === 1 ? '在线' : '离线'}</Tag> },
{
title: '操作',
width: 280,
key: 'action',
fixed: 'right',
title: '操作', width: 280, key: 'action', fixed: 'right',
render: (_, record) => (
<Space>
<Tooltip title="清除人员绑定">
@@ -360,22 +357,13 @@ export default function DeviceManagementPage() {
];
const handleUnbind = (sns, type) => {
// ✅ 关键:过滤掉空、null、undefined、纯空格的项
const validSns = (sns || []).filter(item =>
item !== null &&
item !== undefined &&
item !== '' &&
item.trim() !== ''
item !== null && item !== undefined && item !== '' && item.trim() !== ''
);
// 如果过滤完没有有效SN,直接提示
if (validSns.length === 0) {
return utils.message.warning('未选择有效设备,无法解绑');
}
const typeText = { 1: '人员', 2: '场站', 3: '组织' }[type];
modal.confirm({
title: '确认解绑',
content: `确定要为选中的无人机清除${typeText}绑定吗?`,
@@ -383,8 +371,7 @@ export default function DeviceManagementPage() {
cancelText: '取消',
onOk: async () => {
try {
// ✅ 传给接口的是过滤后的有效数组
const res: any = await unbindDrone({ sns: validSns, type });
const res = await unbindDrone({ sns: validSns, type });
if (res.code === 200) {
utils.message.success('解绑成功');
fetchDroneList();
@@ -404,40 +391,32 @@ export default function DeviceManagementPage() {
{ title: 'SN码', width: 150, dataIndex: 'device_sn', key: 'sn' },
{ title: '机场SN', width: 150, dataIndex: 'gateway_sn', key: 'gsn' },
{
title: '所属组织',
key: 'orgName',
width: 150,
title: '所属组织', width: 150, key: 'orgName',
render: (_, record) => {
const org = allOrgOptions.find(o => (o.orgId || o.id) == record.orgId);
return org ? (org.orgName || org.name) : <Text type="secondary">未绑定</Text>;
},
},
{
title: '所属场站',
key: 'siteName',
width: 150,
title: '所属场站', width: 150, key: 'siteName',
render: (_, record) => {
const site = allSiteOptions.find(s => (s.siteId || s.id) == record.siteId);
return site ? (site.siteName || site.name) : <Text type="secondary">未绑定</Text>;
},
},
{
title: '负责人',
key: 'userName',
width: 150,
title: '负责人', width: 150, key: 'userName',
render: (_, record) => {
const user = allUserOptions.find(u => u.userId == record.userId);
return user ? (user.nickName || user.userName) : <Text type="secondary">未绑定</Text>;
},
},
{
title: '状态', width: 150,
dataIndex: 'onlineStatus', key: 'status', render: s => <Tag color={s === 1 ? 'green' : 'default'}>{s === 1 ? '在线' : '离线'}</Tag>
title: '状态', width: 150, dataIndex: 'onlineStatus', key: 'status',
render: s => <Tag color={s === 1 ? 'green' : 'default'}>{s === 1 ? '在线' : '离线'}</Tag>
},
{
title: '操作',
key: 'action',
fixed: 'right',
title: '操作', key: 'action', fixed: 'right',
render: (_, record) => (
<Space>
<Tooltip title="人员解绑">
@@ -463,17 +442,13 @@ export default function DeviceManagementPage() {
{ title: '设备名称', dataIndex: 'name', key: 'name' },
{ title: '类型', dataIndex: 'type', key: 'type' },
{
title: '在线',
dataIndex: 'online',
key: 'online',
title: '在线', dataIndex: 'online', key: 'online',
render: (text) => (
<Tag color={text === 'ONLINE' ? 'green' : 'red'}>{text}</Tag>
),
},
{
title: '运行',
dataIndex: 'status',
key: 'status',
title: '运行', dataIndex: 'status', key: 'status',
render: (text) => {
const colorMap = {
NORMAL: 'green',
@@ -485,8 +460,7 @@ export default function DeviceManagementPage() {
},
},
{
title: '操作',
key: 'action',
title: '操作', key: 'action',
render: (_, record) => (
<Space>
<Button type="link" icon={<EditOutlined />} onClick={() => handleEdit(record)}>编辑</Button>
@@ -549,13 +523,10 @@ export default function DeviceManagementPage() {
<Tabs.TabPane key="all" tab="设备管理" />
<Tabs.TabPane key="drone" tab="无人机管理" />
<Tabs.TabPane key="smart" tab="智能装备" />
<Tabs.TabPane key="camera" tab="摄像头管理" />
<Tabs.TabPane key="serialGenerate" tab="机器人生成" />
</Tabs>
{/* ====================== 判断显示内容 ====================== */}
{activeTab === 'serialGenerate' ? (
<SerialNumGenerate />
) : activeTab === 'smart' ? (
@@ -569,17 +540,10 @@ export default function DeviceManagementPage() {
</Space>
</div>
<div style={{
height: 'calc(100vh - 220px)',
overflowY: 'auto',
paddingRight: '4px',
}}>
<div style={{ height: 'calc(100vh - 220px)', overflowY: 'auto', paddingRight: '4px' }}>
<Table
rowSelection={{
selectedRowKeys: selectedSmartKeys,
onChange: setSelectedSmartKeys,
}}
rowKey={(record) => record.deviceId || record.id}
rowSelection={{ selectedRowKeys: selectedSmartKeys, onChange: setSelectedSmartKeys }}
rowKey={(record) => record.serialNumber}
columns={smartColumns}
dataSource={smartList}
loading={loading}
@@ -587,17 +551,13 @@ export default function DeviceManagementPage() {
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'
}}
scroll={{ y: 'auto', x: '700px' }}
/>
</div>
@@ -617,7 +577,6 @@ export default function DeviceManagementPage() {
<Form.Item label="已选装备 ID" name="deviceIds" rules={[{ required: true, message: '请选择装备' }]}>
<Input.TextArea placeholder="例如: ID001, ID002" rows={3} disabled />
</Form.Item>
<Form.Item label="所属组织" name="orgId" rules={[{ required: true, message: '请选择组织' }]}>
<Select
placeholder="请选择组织"
@@ -633,7 +592,6 @@ export default function DeviceManagementPage() {
))}
</Select>
</Form.Item>
<Form.Item label="所属场站" name="siteId" >
<Select
placeholder="请先选择组织"
@@ -649,7 +607,6 @@ export default function DeviceManagementPage() {
))}
</Select>
</Form.Item>
<Form.Item label="负责人" name="userId" >
<Select
placeholder="请先选择场站"
@@ -665,37 +622,50 @@ export default function DeviceManagementPage() {
</>
) : activeTab === 'drone' ? (
<>
<div style={{ marginBottom: 16 }}>
<Space>
<Button type="primary" icon={<LinkOutlined />} onClick={openBindModal}>绑定无人机</Button>
<Button disabled={selectedDroneKeys.length === 0} onClick={() => handleUnbind(selectedDroneKeys, 1)}>批量解绑(用户)</Button>
<Button disabled={selectedDroneKeys.length === 0} onClick={() => handleUnbind(selectedDroneKeys, 2)}>批量解绑(场站)</Button>
<Button disabled={selectedDroneKeys.length === 0} onClick={() => handleUnbind(selectedDroneKeys, 3)}>批量解绑(组)</Button>
</Space>
{/* 无人机搜索栏 */}
<div style={{ marginBottom: 16, display: 'flex', gap: 10, flexWrap: 'wrap' }}>
<Input
placeholder="机场SN"
style={{ width: 200 }}
value={droneParams.gateway_sn}
onChange={(e) => setDroneParams({ ...droneParams, gateway_sn: e.target.value })}
/>
<Select
placeholder="状态"
style={{ width: 120 }}
value={droneParams.status}
allowClear
onChange={(val) => setDroneParams({ ...droneParams, status: val })}
>
<Select.Option value="1">在线</Select.Option>
<Select.Option value="2">离线</Select.Option>
</Select>
<Button icon={<ReloadOutlined />} onClick={() => setDroneParams({ pageNum: 1, pageSize: 10, gateway_sn: '', status: '' })}>重置</Button>
<Button type="primary" icon={<LinkOutlined />} onClick={openBindModal}>绑定无人机</Button>
<Button disabled={selectedDroneKeys.length === 0} onClick={() => handleUnbind(selectedDroneKeys, 1)}>批量解绑(用户)</Button>
<Button disabled={selectedDroneKeys.length === 0} onClick={() => handleUnbind(selectedDroneKeys, 2)}>批量解绑(场站)</Button>
<Button disabled={selectedDroneKeys.length === 0} onClick={() => handleUnbind(selectedDroneKeys, 3)}>批量解绑(组)</Button>
</div>
<div style={{
height: 'calc(100vh - 220px)',
overflowY: 'auto',
paddingRight: '4px',
}}>
<div style={{ height: 'calc(100vh - 220px)', overflowY: 'auto', paddingRight: '4px' }}>
<Table
rowSelection={{
selectedRowKeys: selectedDroneKeys,
onChange: setSelectedDroneKeys,
}}
rowSelection={{ selectedRowKeys: selectedDroneKeys, onChange: setSelectedDroneKeys }}
rowKey="gateway_sn"
columns={droneColumns}
dataSource={droneList}
loading={loading}
pagination={{
current: droneParams.pageNum,
pageSize: droneParams.pageSize,
total: droneTotal,
showSizeChanger: true,
showTotal: t => `共 ${t} 条`
}}
scroll={{
y: 'auto ', // 最大高度,超过才滚动
x: '700px' /* 横向最小宽度,不够自动出滚动条 */
showTotal: t => `共 ${t} 条`,
onChange: (page, pageSize) => {
setDroneParams({ ...droneParams, pageNum: page, pageSize });
},
}}
scroll={{ y: 'auto', x: '700px' }}
/>
</div>
@@ -706,7 +676,7 @@ export default function DeviceManagementPage() {
setBindModalVisible(false);
setSiteOptions([]);
setUserOptions([]);
bindForm.resetFields(); // 加一句重置,避免缓存
bindForm.resetFields();
}}
onOk={() => bindForm.submit()}
width={500}
@@ -715,8 +685,6 @@ export default function DeviceManagementPage() {
<Form.Item label="已选无人机 SN" name="sns" rules={[{ required: true, message: '请输入 SN' }]}>
<Input.TextArea placeholder="例如: SN001, SN002" rows={3} disabled />
</Form.Item>
{/* ========== 修复:组织 ========== */}
<Form.Item label="所属组织" name="orgId" rules={[{ required: true, message: '请选择组织' }]}>
<Select
allowClear
@@ -729,17 +697,12 @@ export default function DeviceManagementPage() {
}}
>
{orgOptions.map(org => (
<Select.Option
key={`org-${org.id}`}
value={org.id}
>
<Select.Option key={`org-${org.id}`} value={org.id}>
{org.name || org.orgName || '未命名组织'}
</Select.Option>
))}
</Select>
</Form.Item>
{/* ========== 修复:场站 ========== */}
<Form.Item label="所属场站" name="siteId" >
<Select
placeholder="请先选择组织"
@@ -751,39 +714,29 @@ export default function DeviceManagementPage() {
}}
>
{siteOptions.map(site => (
<Select.Option
key={`site-${site.id}`}
value={site.id}
>
<Select.Option key={`site-${site.id}`} value={site.id}>
{site.name || site.siteName || '未命名场站'}
</Select.Option>
))}
</Select>
</Form.Item>
{/* ========== 修复:用户 ========== */}
<Form.Item label="负责人" name="userId" >
<Select
placeholder="请先选择场站"
disabled={!userOptions.length}
>
{userOptions.map(user => (
<Select.Option
key={`user-${user.userId}`}
value={user.userId}
>
<Select.Option key={`user-${user.userId}`} value={user.userId}>
{user.nickName || user.userName || user.realName || '未知用户'}
</Select.Option>
))}
</Select>
</Form.Item>
</Form>
</Modal >
</Modal>
</>
) : (
// 显示原来的设备列表
<>
{/* 筛选栏 */}
<div style={{ marginBottom: 16 }}>
<Form form={form} layout="inline">
<Form.Item name="keyword">
@@ -800,8 +753,6 @@ export default function DeviceManagementPage() {
</Form.Item>
</Form>
</div>
{/* 表格 */}
<Table
rowKey="id"
columns={columns}
@@ -816,8 +767,7 @@ export default function DeviceManagementPage() {
}}
/>
</>
)
}
</div >
)}
</div>
);
}

View File

@@ -195,7 +195,9 @@ export default function DeviceOverviewPage({ devices, planedevices, devicesAll,
showSearch
allowClear
optionFilterProp="label"
filterOption={(input, option) => option.label.toLowerCase().includes(input.toLowerCase())}
filterOption={(input, option) =>
String(option?.label || "").toLowerCase().includes(input.toLowerCase())
}
options={[
{ label: <span>机器人</span>, options: devices.map(item => ({ value: item.device_sn || item.serialNumber, label: item.deviceAlias || item.deviceName || '未知设备' })) },
{ label: <span>无人机</span>, options: planedevices.map(item => ({ value: item.device_sn, label: item.drone_callsign || item.callsign || '未知设备' })) },

View File

@@ -48,7 +48,7 @@ export default function DeviceIndexPage() {
const devData = deviceRes?.code === 200 ? (deviceRes.data || []) : [];
// 3. 处理无人机 + 自动补全地图需要的字段(关键改动)
let planeData = planeRes?.code === 200 ? (planeRes.data?.detail || []) : [];
let planeData = planeRes?.code === 200 ? (planeRes?.rows || []) : [];
// ====================== ✅ 核心改造 ======================
planeData = planeData.map(item => ({