Files
web-Iot/src/components/devices/DeviceOverviewPage.tsx
2026-05-11 16:34:44 +08:00

446 lines
27 KiB
TypeScript

import React, { useState, useEffect } from 'react';
import {
Layout, Card, Row, Col, Statistic, Tag, Button, Table,
Typography, Space, Progress, Badge, Timeline, message,
Select, Modal
} from 'antd';
import {
EnvironmentOutlined, RobotOutlined, BranchesOutlined,
ThunderboltOutlined, CheckCircleOutlined, PauseOutlined,
RocketOutlined, SyncOutlined, WarningOutlined,
CloudOutlined, ClockCircleOutlined, ReloadOutlined,
AimOutlined, ApiOutlined, SettingOutlined,
PlusOutlined, ArrowRightOutlined,
EditOutlined, FullscreenOutlined, InfoCircleOutlined
} from '@ant-design/icons';
import { useSelector } from 'react-redux';
import { getDeviceListApi, getLiveStream, getPlaneDeviceList } from '../../api/device.ts';
import { RootState } from '@/src/store';
import DroneLivePlayer from '../VolcRtcPlayer.jsx';
const { Content } = Layout;
const { Text, Title } = Typography;
const taskTimeline = [
{ time: '10:24:18', color: 'blue', text: 'UAV-C01 开始执行清洗航线 C-05', status: '任务开始' },
{ time: '10:21:35', color: 'orange', text: 'RBT-01 开始执行巡检航线 P-02', status: '任务开始' },
{ time: '10:18:02', color: 'green', text: 'GR-02 开始除草作业 G-01', status: '任务开始' },
{ time: '10:15:47', color: 'gray', text: 'UAV-C02 任务待命就绪', status: '待命就绪' },
{ time: '10:12:33', color: 'blue', text: 'UAV-C01 再次返机补水', status: '返机补给' },
{ time: '09:58:10', color: 'green', text: 'RBT-02 完成巡检航线 P-01', status: '任务完成' },
];
const logs = [
{ time: '10:24:35', device: 'UAV-C01', type: '命令', desc: '起飞指令下发,开始作业' },
{ time: '10:21:40', device: 'RBT-01', type: '命令', desc: '开始巡检任务 P-02' },
{ time: '10:20:11', device: 'UAV-C02', type: '命令', desc: '转入待命状态' },
{ time: '10:18:05', device: 'GR-02', type: '命令', desc: '除草作业开始 G-01' },
{ time: '10:12:35', device: 'UAV-C01', type: '告警', desc: '返航补水' },
{ time: '10:05:22', device: 'RBT-01', type: '事件', desc: '电量较低,建议回充', level: '低电量' },
];
// 设备总览组件
export default function DeviceOverviewPage({ devices, planedevices, devicesAll, onViewStatus }) {
const [selectedDevice, setSelectedDevice] = useState(null);
const [planeVideo, setPlaneVideo] = useState([]); // 初始为空数组
const [zoomedVideo, setZoomedVideo] = useState(null); // 放大查看的视频数据
// 动态计算顶部卡片数据
const dynamicTopCards = React.useMemo(() => {
const totalDrones = planedevices?.length || 0;
const onlineDrones = planedevices?.filter(d => d.onlineStatus === 1).length || 0;
const totalRobots = devicesAll?.filter(d => d.productName?.includes('巡检')).length || 0;
const activeRobots = devicesAll?.filter(d => d.productName?.includes('巡检') && d.onlineStatus === 1).length || 0;
const totalMowers = devicesAll?.filter(d => d.productName?.includes('割草机')).length || 0;
const activeMowers = devicesAll?.filter(d => d.productName?.includes('割草机') && d.onlineStatus === 1).length || 0;
return [
{ title: '无人机机场状态', icon: <EnvironmentOutlined />, value: `${onlineDrones} / ${totalDrones}`, desc: onlineDrones > 0 ? '在线' : '离线', sub: `可调度 ${onlineDrones} 架`, color: '#1890ff' },
//{ title: '巡检机器人', icon: <RobotOutlined />, value: `${totalRobots} 台`, desc: '', sub: `执行中 ${activeRobots}`, color: '#722ed1' },
{ title: '机器人', icon: <BranchesOutlined />, value: `${totalMowers} 台`, desc: '', sub: `作业中 ${activeMowers}`, color: '#13c2c2' },
{ title: '无人机', icon: <CloudOutlined />, value: `${totalDrones} 架`, desc: '', sub: `待命 ${planedevices?.filter(d => d.productName?.includes('清洗') && d.onlineStatus === 1).length || 0}`, color: '#eb2f96' },
{ title: '设备平均电量', icon: <ThunderboltOutlined />, value: '86%', desc: '', sub: '', color: '#52c41a' },
{ title: '今日任务执行', icon: <CheckCircleOutlined />, value: '12 / 15', desc: '', sub: '完成率 80%', color: '#2f54eb' },
];
}, [devicesAll, planedevices]);
// 动态生成调度列表
const dynamicScheduleList = React.useMemo(() => {
return devicesAll.map((d, index) => ({
id: d.deviceId || index,
name: d.deviceAlias || d.deviceName || d.callsign || '未知设备',
type: d.productName || '未知类型',
status: d.onlineStatus === 1 ? '在线' : '离线',
mode: d.status === 4 ? '待命' : '作业中',
battery: d.capacity_percent || 100,
position: d.position || '站内',
task: d.status === 4 ? '待命' : '作业中',
comm: d.rssi ? `${d.rssi}dBm` : '良好'
}));
}, [devicesAll]);
const handleStart = () => message.success('任务已启动');
const handlePause = () => message.info('任务已暂停');
const handleReturn = () => message.info('返航指令已下发');
const handleRecharge = () => message.info('回充指令已下发');
const handleEmergencyStop = () => message.error('已触发急停!');
const getLiveDefault = () => {
if (!selectedDevice?.gateway_sn) return;
const data = {
getway_sn: selectedDevice?.gateway_sn,
drone_sn: selectedDevice?.device_sn,
getway_camera_list: selectedDevice?.gateway_camera_list ?? [],
drone_camera_list: selectedDevice?.drone_camera_list ?? [],
video_expire: 7200,
quality_type: "adaptive",
}
getLiveStream(data).then(res => {
if (res.code == 200) {
setPlaneVideo(res.data);
}
})
}
// 数字保留两位小数
const fix2 = (val) => {
if (val === null || val === undefined || isNaN(val)) return '-';
return Number(val).toFixed(2);
};
// 安全取值
const getVal = (val) => val == "no_rain" ? "无" : (val || '-');
// 默认选中第一个
useEffect(() => {
if (devicesAll && devicesAll.length > 0) {
setSelectedDevice(devicesAll[0]); // 🔥 修复:存整个对象
}
}, [devicesAll]);
useEffect(() => {
getLiveDefault();
}, [selectedDevice])
// 切换设备
const handleDeviceChange = (id) => {
const target = devicesAll.find(item => (item.device_sn || item.serialNumber) === id);
setSelectedDevice(target || null); // 🔥 修复:存对象
};
return (
<Content style={{ maxWidth: 1800, margin: '0 auto', }}>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '16px', marginBottom: 20 }}>
{dynamicTopCards.map((card, idx) => (
<div key={idx} style={{ flex: 1 }}>
<Card bordered={false} style={{ borderRadius: 12, boxShadow: '0 2px 8px rgba(0,0,0,0.05)', height: '110px', display: 'flex', alignItems: 'center' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 12, width: '100%' }}>
<div style={{ width: 48, height: 48, borderRadius: 10, background: `${card.color}15`, color: card.color, display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 20 }}>
{card.icon}
</div>
<div style={{ flex: 1 }}>
<div style={{ fontSize: 13, color: '#666' }}>{card.title}</div>
<div style={{ fontSize: 24, fontWeight: 700, color: '#222' }}>{card.value}</div>
{card.sub && <div style={{ fontSize: 12, color: '#999' }}>{card.sub}</div>}
</div>
</div>
</Card>
</div>
))}
</div>
<Row gutter={16}>
<Col xs={24} lg={14}>
<Card title={<><EnvironmentOutlined /> 装备协同态势图</>} extra={<Button type="text" icon={<SettingOutlined />} />} bordered={false} style={{ borderRadius: 12, marginBottom: 16 }} bodyStyle={{ padding: 0 }}>
<div style={{ width: '100%', height: 400, background: '#e6f7ff url(https://picsum.photos/seed/solarfarm/1600/800) center/cover', borderRadius: 12, position: 'relative', overflow: 'hidden' }}>
<div style={{ position: 'absolute', top: 16, left: 16, background: 'rgba(255,255,255,0.95)', borderRadius: 8, padding: 12 }}>
<div style={{ fontSize: 12, fontWeight: 60, marginBottom: 8 }}>图例说明</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: 6, fontSize: 11 }}>
<div><span style={{ color: '#1890ff' }}>———</span> 巡检航线</div>
<div><span style={{ color: '#fa8c16' }}>———</span> 机器人路径</div>
<div><span style={{ color: '#52c41a' }}>———</span> 清洗路径</div>
<div><span style={{ color: '#52c41a' }}>●</span> 设备位置</div>
<div><span style={{ color: '#1890ff' }}>●</span> 补给/充电点</div>
<div><span style={{ color: '#ff4d4f' }}>■</span> 障碍/禁入区</div>
</div>
</div>
</div>
</Card>
<Card title={<><SettingOutlined /> 装备调度列表</>} bordered={false} style={{ borderRadius: 12, marginBottom: 16 }}>
<Table rowKey={(record) => record.id || record.serialNumber || record.device_sn} size="small" pagination={false}
columns={[
{ title: '装备名称', dataIndex: 'name', width: 160 },
{ title: '类型', dataIndex: 'type', width: 100 },
{ title: '当前状态', dataIndex: 'status', width: 80, render: s => <Tag color={s === '作业中' || s === '在线' ? 'success' : 'default'}>{s}</Tag> },
{ title: '模式', dataIndex: 'mode', width: 80 },
{ title: '电量', dataIndex: 'battery', width: 80, render: v => <Progress percent={v} size="small" status={v < 40 ? 'exception' : 'active'} /> },
{ title: '位置', dataIndex: 'position', width: 100 },
{ title: '当前任务', dataIndex: 'task', width: 120 },
{ title: '通讯状态', dataIndex: 'comm', width: 80, render: v => <Badge status={v.includes('-') ? 'warning' : 'success'} text={v} /> },
{ title: '操作', key: 'action', width: 100, render: () => <Space><Button size="small" type="text" icon={<EditOutlined />} /><Button size="small" type="text" icon={<SettingOutlined />} /></Space> }
]}
dataSource={dynamicScheduleList}
scroll={{ x: 'max-content', y: 320 }}
/>
</Card>
<Card title={<><SettingOutlined /> <span >控制日志 / 告警记录</span></>} bordered={false} style={{ borderRadius: 12 }} extra={<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}><Button size="small" style={{ borderRadius: 8, border: '1px solid #d9d9d9' }}>全部级别 ▼</Button></div>}>
<Table rowKey="time" size="small" pagination={false} dataSource={logs} scroll={{ x: 'max-content' }}
columns={[
{ title: '时间', dataIndex: 'time', width: 100, render: t => <span style={{ fontSize: 14 }}>{t}</span> },
{ title: '设备', dataIndex: 'device', width: 120, render: t => <span style={{ fontSize: 14 }}>{t}</span> },
{ title: '类型', dataIndex: 'type', width: 80, render: t => { let c = '#52c41a'; if (t === '告警') c = '#fa8c16'; if (t === 'events') c = '#1890ff'; return <div style={{ display: 'flex', alignItems: 'center', gap: 4 }}><span style={{ width: 12, height: 12, borderRadius: 2, background: c }} /><span style={{ fontSize: 14 }}>{t}</span></div> } },
{ title: '描述', dataIndex: 'desc', render: t => <span style={{ fontSize: 14 }}>{t}</span> },
{ title: '', width: 80, render: (_, r) => r.level ? <Tag color="red" style={{ fontSize: 12 }}>{r.level}</Tag> : null }
]}
/>
<div style={{ textAlign: 'center', marginTop: 12 }}><Button type="link" style={{ fontSize: 16, color: '#1890ff' }}>查看更多日志</Button></div>
</Card>
</Col>
<Col xs={24} lg={10}>
<Card
title={<><SettingOutlined style={{ marginRight: 6, }} /> 设备控制面板</>}
bordered={false}
style={{ borderRadius: 12, marginBottom: 16 }}
bodyStyle={{ padding: 16, paddingBottom: 0 }}
> <Select
style={{ width: 220, marginBottom: 10 }}
placeholder="请选择设备"
value={selectedDevice?.device_sn || selectedDevice?.serialNumber}
onChange={handleDeviceChange}
showSearch // 👈 开启搜索
allowClear // 允许删除/清空
optionFilterProp="label" // 👈 按设备名称搜索
filterOption={(input, option) =>
option.label.toLowerCase().includes(input.toLowerCase())
}
options={devicesAll.map(item => ({
value: item.device_sn || item.serialNumber,
label: item.deviceAlias || item.drone_callsign || item.callsign || item.deviceName || '未知设备'
}))}
/>
{selectedDevice && (
<Row gutter={16}>
<Col span={12}>
<div style={{ marginBottom: 24 }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 16 }}>
<span style={{ fontSize: 24, color: '#1890ff' }}><RocketOutlined /></span>
<span style={{ fontSize: 16, fontWeight: 600 }}>{selectedDevice.deviceAlias || selectedDevice.drone_callsign || selectedDevice.callsign || selectedDevice.deviceName}</span>
<Tag color={selectedDevice.onlineStatus === 1 ? "success" : "default"} size="small">
{selectedDevice.onlineStatus == 1 ? "在线" : "离线"}
</Tag>
<Button
type="primary"
ghost
size="small"
icon={<InfoCircleOutlined />}
style={{ borderRadius: 4, fontSize: 12, marginLeft: 'auto' }}
onClick={() => onViewStatus && onViewStatus(selectedDevice.device_sn || selectedDevice.serialNumber)}
>
详情
</Button>
</div>
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 6 }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
<span style={{ color: '#52c41a' }}><ThunderboltOutlined /></span>
<span style={{ fontSize: 12 }}>电量 {fix2(selectedDevice.capacity_percent || 100)}%</span>
</div>
{selectedDevice.environment_temperature && (
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
<span style={{ fontSize: 12 }}>温度</span>
<span style={{ color: '#faad14' }}>{fix2(selectedDevice.environment_temperature || 25)}℃</span>
</div>
)}
</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: 6, marginBottom: 12, maxHeight: "160px", overflow: "auto", scrollbarWidth: 'thin', scrollbarColor: '#ccc transparent' }}>
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<span style={{ color: '#666', fontSize: 12 }}>产品类型</span>
<span style={{ fontSize: 12 }}>{getVal(selectedDevice.productName)}</span>
</div>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', gap: 8 }}>
<span style={{ color: '#666', fontSize: 12, whiteSpace: 'nowrap' }}>设备SN</span>
<span
style={{
fontSize: 12,
wordBreak: 'break-all', // 强制换行
whiteSpace: 'normal', // 允许换行
textAlign: 'right', // 右对齐
maxWidth: '70%', // 限制宽度,避免撑满
}}
>
{getVal(selectedDevice.device_sn || selectedDevice.serialNumber)}
</span>
</div>
{selectedDevice.activeTime && (
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<span style={{ color: '#666', fontSize: 12 }}>激活时间</span>
<span style={{ fontSize: 12 }}>{selectedDevice.activeTime}</span>
</div>
)}
{selectedDevice.firmwareVersion && (
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<span style={{ color: '#666', fontSize: 12 }}>固件版本</span>
<span style={{ fontSize: 12 }}>v{selectedDevice.firmwareVersion}</span>
</div>
)}
{/* 无人机特定信息 */}
{(selectedDevice.productName?.includes('无人机') || selectedDevice.device_sn) && (
<>
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<span style={{ color: '#666', fontSize: 12 }}>飞行高度</span>
<span style={{ fontSize: 12 }}>{fix2(selectedDevice.height)} m</span>
</div>
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<span style={{ color: '#666', fontSize: 12 }}>飞行风速</span>
<span style={{ fontSize: 12 }}>{fix2(selectedDevice.wind_speed)} m/s</span>
</div>
</>
)}
</div>
<div style={{ display: 'flex', justifyContent: "space-between", flexWrap: 'wrap', gap: 6 }}>
<Button type="primary" size="large" style={{ borderRadius: 8, flex: 1 }} onClick={handleStart}>启动任务</Button>
<Button style={{ borderRadius: 8, flex: 1 }} size="large" onClick={handlePause}>暂停</Button>
<Button type="primary" style={{ borderRadius: 8, flex: 1 }} size="large" onClick={handleReturn}>返航</Button>
<Button style={{ background: '#52c41a', borderColor: '#52c41a', color: "#fff", borderRadius: 8, flex: 1 }} size="large" onClick={handleRecharge}>回充</Button>
</div>
</div>
</Col>
<Col span={12}>
<div style={{ marginBottom: 16 }}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 8 }}>
<span style={{ fontSize: 14, fontWeight: 500, color: "#666" }}>实时画面</span>
<span style={{ fontSize: 13, color: '#999' }}>实时监控</span>
</div>
<div style={{ width: '100%', height: 160, borderRadius: 8, position: 'relative', overflow: 'hidden', background: '#000' }}>
<DroneLivePlayer streamData={planeVideo?.[0] || null} />
{selectedDevice.device_sn && (
<div style={{ position: 'absolute', bottom: 0, left: 0, right: 0, background: 'rgba(0,0,0,0.6)', color: '#fff', display: 'flex', justifyContent: 'space-between', padding: '8px 12px', fontSize: 14 }}>
<span>高度 {fix2(selectedDevice.height)}m</span>
<span>风速 {fix2(selectedDevice.wind_speed)}m/s</span>
</div>
)}
</div>
</div>
<div style={{ display: 'flex', gap: 12, flexWrap: 'wrap' }}>
<Button size="large" style={{ flex: 1, borderRadius: 8 }}>切换手动</Button>
<Button danger size="large" style={{ flex: 1, borderRadius: 8 }} onClick={handleEmergencyStop}>急停</Button>
</div>
</Col>
</Row>
)}
</Card>
<Card title={<><ApiOutlined /> AI分析与操作建议</>} bordered={false} style={{ borderRadius: 12, marginBottom: 16 }}>
<Row gutter={16} style={{ marginBottom: 20 }}>
<Col span={6}><div style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '6px 12px 4px', borderRadius: 12, border: '1px solid #e8e8e8' }}><div style={{ width: 30, height: 30, borderRadius: '50%', background: 'rgba(255,77,79,0.15)', display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#ff4d4f', fontSize: 20 }}><span role="img" aria-label="hotspot">🔥</span></div><div><div style={{ fontSize: 11, color: '#666' }}>热斑疑似</div><div style={{ fontSize: 22, fontWeight: 700, color: '#222' }}>2 <span style={{ fontSize: 12 }}>处</span></div></div></div></Col>
<Col span={6}><div style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '6px 12px 4px', borderRadius: 12, border: '1px solid #e8e8e8' }}><div style={{ width: 30, height: 30, borderRadius: '50%', background: 'rgba(82,196,26,0.15)', display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#52c41a', fontSize: 16 }}><BranchesOutlined /></div><div><div style={{ fontSize: 11, color: '#666' }}>杂草高发区</div><div style={{ fontSize: 22, fontWeight: 700, color: '#222' }}>3 <span style={{ fontSize: 12 }}>块</span></div></div></div></Col>
<Col span={6}><div style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '6px 12px 4px', borderRadius: 12, border: '1px solid #e8e8e8' }}><div style={{ width: 30, height: 30, borderRadius: '50%', background: 'rgba(24,144,255,0.15)', display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#1890ff', fontSize: 16 }}><CloudOutlined /></div><div><div style={{ fontSize: 11, color: '#666' }}>组件风险区</div><div style={{ fontSize: 22, fontWeight: 700, color: '#222' }}>4 <span style={{ fontSize: 12 }}>块</span></div></div></div></Col>
<Col span={6}><div style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '6px 12px 4px', borderRadius: 12, border: '1px solid #e8e8e8' }}><div style={{ width: 30, height: 30, borderRadius: '50%', background: 'rgba(255,77,79,0.15)', display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#ff4d4f', fontSize: 16 }}><WarningOutlined /></div><div><div style={{ fontSize: 11, color: '#666' }}>围栏异常</div><div style={{ fontSize: 22, fontWeight: 700, color: '#ff4d4f' }}>1 <span style={{ fontSize: 12 }}>处</span></div></div></div></Col>
</Row>
<Row gutter={16}>
<Col span={14}>
<div style={{ marginBottom: 12 }}>
<div style={{ fontSize: 16, fontWeight: 600, color: '#1890ff', marginBottom: 12 }}>建议操作</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: 2, padding: '16px', borderRadius: 12, border: '1px solid #e8e8e8' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}><div style={{ display: 'flex', alignItems: 'center', gap: 8 }}><span style={{ color: '#1890ff' }}>⦿</span><span style={{ fontSize: 16 }}>优先执行巡检航线 P-03</span></div><ArrowRightOutlined style={{ color: '#666' }} /></div>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}><div style={{ display: 'flex', alignItems: 'center', gap: 8 }}><span style={{ color: '#1890ff' }}>⦿</span><span style={{ fontSize: 16 }}>先行安排除草任务 G-02</span></div><ArrowRightOutlined style={{ color: '#666' }} /></div>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}><div style={{ display: 'flex', alignItems: 'center', gap: 8 }}><span style={{ color: '#1890ff' }}>⦿</span><span style={{ fontSize: 16 }}>建议生成清洗工单</span></div><ArrowRightOutlined style={{ color: '#666' }} /></div>
</div>
</div>
</Col>
<Col span={10}>
<div style={{ display: 'flex', flexDirection: 'column', paddingBottom: "12px", gap: 16, height: '100%', justifyContent: "end" }}>
<div style={{ display: 'flex', gap: 12, flexWrap: "wrap" }}>
<Button type="primary" size="large" style={{ flex: 1, borderRadius: 8, fontSize: 16 }}>生成任务</Button>
<Button size="large" style={{ flex: 1, borderRadius: 8, fontSize: 16, border: '1px solid #1890ff', color: '#1890ff' }}>AI优化路径</Button>
</div>
<Button type="primary" size="large" style={{ borderRadius: 8, fontSize: 16 }}>一键联动作业</Button>
</div>
</Col>
</Row>
</Card>
{/* 条件渲染:只有无人机显示环境与飞行条件 */}
{(selectedDevice?.productName?.includes('无人机') || selectedDevice?.device_sn) && (
<Card title="环境与飞行条件" bordered={false} style={{ borderRadius: 12, marginBottom: 16 }}>
<div style={{ display: "flex", justifyContent: "space-between", gap: 12, alignItems: "center" }}>
<div style={{ display: "flex", border: "1px solid #e8e8e8", borderRadius: 12, overflow: "hidden", flex: 1 }}>
<div style={{ flex: 1, textAlign: 'center', whiteSpace: "nowrap", padding: "12px 4px", borderRight: "1px solid #e8e8e8" }}><div style={{ fontSize: 11, color: '#666', fontWeight: 600 }}>风速</div><div style={{ fontSize: 14, fontWeight: 700, color: '#222', marginTop: 2 }}>{fix2(selectedDevice?.wind_speed)} m/s</div></div>
<div style={{ flex: 1, textAlign: 'center', whiteSpace: "nowrap", padding: "12px 4px", borderRight: "1px solid #e8e8e8" }}><div style={{ fontSize: 11, color: '#666', fontWeight: 600 }}>温度</div><div style={{ fontSize: 14, fontWeight: 700, color: '#222', marginTop: 2 }}>{fix2(selectedDevice?.environment_temperature)}℃</div></div>
<div style={{ flex: 1, textAlign: 'center', whiteSpace: "nowrap", padding: "12px 4px", borderRight: "1px solid #e8e8e8" }}><div style={{ fontSize: 11, color: '#666', fontWeight: 600 }}>降雨</div><div style={{ fontSize: 14, fontWeight: 700, color: '#222', marginTop: 2 }}>{getVal(selectedDevice?.rainfall)}</div></div>
<div style={{ flex: 1, textAlign: 'center', whiteSpace: "nowrap", padding: "12px 4px" }}><div style={{ fontSize: 11, color: '#666', fontWeight: 600 }}>RTK</div><div style={{ fontSize: 14, fontWeight: 700, color: '#52c41a', marginTop: 2 }}>{selectedDevice?.position_state?.is_fixed === 'fixing_successful' ? '固定' : '未固定'}</div></div>
</div>
<div style={{ width: 110, padding: '12px 8px', borderRadius: 8, background: '#f6ffed', border: '1px solid #b7eb8f', textAlign: 'center', display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', gap: 4 }}>
<div style={{ fontSize: 14, fontWeight: 700, color: '#52c41a' }}>适宜执行</div>
<div style={{ width: 18, height: 18, borderRadius: '50%', background: '#52c41a', display: 'flex', alignItems: 'center', justifyContent: 'center' }}><CheckCircleOutlined style={{ fontSize: 12, color: '#fff' }} /></div>
<div style={{ fontSize: 11, color: '#666' }}>可正常作业</div>
</div>
</div>
</Card>
)}
{/* 条件渲染:机器人显示其余信息(此处以任务详情为例) */}
{(!selectedDevice?.productName?.includes('无人机') && !selectedDevice?.device_sn) && (
<Card title={<><RobotOutlined /> 机器人作业状态</>} bordered={false} style={{ borderRadius: 12, marginBottom: 16 }}>
<Row gutter={16}>
<Col span={8}>
<Statistic title="累计里程" value={128.5} suffix="km" valueStyle={{ fontSize: 20 }} />
</Col>
<Col span={8}>
<Statistic title="本次作业耗时" value={45} suffix="min" valueStyle={{ fontSize: 20 }} />
</Col>
<Col span={8}>
<Statistic title="预计剩余时间" value={12} suffix="min" valueStyle={{ fontSize: 20 }} />
</Col>
</Row>
</Card>
)}
<Card title={<><ClockCircleOutlined /> 任务执行时间线</>} bordered={false} style={{ borderRadius: 12 }} extra={<div style={{ fontSize: 12, color: '#666' }}>今天 2025-05-24</div>}>
<Timeline mode="left">
{taskTimeline.map((item, idx) => (
<Timeline.Item key={idx} color={item.color} label={<div style={{ fontSize: 11, color: '#999' }}>{item.time}</div>}>
<div style={{ fontSize: 13 }}>{item.text}</div>
<div style={{ fontSize: 11, color: '#999' }}>{item.status}</div>
</Timeline.Item>
))}
</Timeline>
<div style={{ textAlign: 'center', marginTop: 12 }}><Button type="link">查看更多</Button></div>
</Card>
</Col>
</Row>
<Modal
title="视频预览"
open={!!zoomedVideo}
onCancel={() => setZoomedVideo(null)}
footer={null}
width={1000}
centered
bodyStyle={{ padding: 0, height: 600, background: '#000' }}
destroyOnClose
>
{zoomedVideo && <DroneLivePlayer streamData={zoomedVideo} videoMode="LARGE" />}
</Modal>
</Content>
);
}