装备列表优化

This commit is contained in:
2026-05-23 12:43:40 +08:00
parent 1f37d154d2
commit 964bfff91d
2 changed files with 149 additions and 35 deletions

View File

@@ -1,10 +1,22 @@
import req from '/@/api/http/request'
const normalizeDeviceList = (payload) => {
if (Array.isArray(payload)) return payload
if (Array.isArray(payload?.data)) return payload.data
if (Array.isArray(payload?.rows)) return payload.rows
if (Array.isArray(payload?.list)) return payload.list
if (Array.isArray(payload?.records)) return payload.records
if (Array.isArray(payload?.data?.rows)) return payload.data.rows
if (Array.isArray(payload?.data?.list)) return payload.data.list
if (Array.isArray(payload?.data?.records)) return payload.data.records
return []
}
//获取无人设备列表
export const getUVDeviceList = async () => {
let res = await req.get(`/uv/api/devices/query/list`)
return res.data
return normalizeDeviceList(res.data)
}
//修改无人设备
@@ -25,4 +37,4 @@ export const addUVDeviceList = async (data) => {
export const removeUVDeviceList = async (id) => {
let res = await req.delete(`/uv/api/devices/query/remove/${id}`)
return res.data
}
}

View File

@@ -35,15 +35,18 @@
<div class="search-wrapper">
<input type="text" v-model="searchText" placeholder="按名称搜索设备" class="search-input">
</div>
<div class="checkbox-wrapper">
<input type="checkbox" id="online-only" v-model="showOnlineOnly" class="custom-checkbox">
<label for="online-only">只看在线</label>
<div class="checkbox-wrapper" @mousedown.stop @click.stop>
<input type="checkbox" id="online-only" v-model="showOnlineOnly" class="custom-checkbox" @mousedown.stop @click.stop @change.stop="handleOnlineOnlyChange">
<label for="online-only" @mousedown.stop @click.prevent.stop="toggleOnlineOnly">只看在线</label>
</div>
</div>
<!-- 内容列表 -->
<div class="ud-device-content">
<div v-if="filteredDeviceList.length === 0" class="empty-state">
<div v-if="isDeviceListLoading" class="empty-state">
<p>加载设备中...</p>
</div>
<div v-else-if="filteredDeviceList.length === 0" class="empty-state">
<p>没有找到匹配的设备</p>
<span class="tip">请尝试调整筛选条件</span>
</div>
@@ -72,10 +75,11 @@
</div>
<div class="header-actions">
<!-- 预览按钮 (如果满足条件) -->
<span v-if="(item.device.online && item.device.flvUrl)"
<span v-if="canPreviewDevice(item.device)"
class="preview-btn"
title="预览直播"
@click="handlePreview(item.device)"
@mousedown.stop
@click.stop="handlePreview(item.device)"
>
<svg viewBox="0 0 1024 1024" width="20" height="20"><path d="M512 96a416 416 0 1 0 0 832 416 416 0 0 0 0-832z m0 736a320 320 0 1 1 0-640 320 320 0 0 1 0 640z m-160-320a160 160 0 1 0 320 0 160 160 0 0 0-320 0z m160-64a64 64 0 1 1 0 128 64 64 0 0 1 0-128z" fill="currentColor"></path></svg>
</span>
@@ -176,7 +180,8 @@ const tabOptions = ref([
{ label: "无人机", type: 0 },
{ label: "无人车", type: 1 },
{ label: "其他", type: 2 },
]);
]);
const activeTab = ref(0);
const deviceList = ref([
{ "device": { "id": 1, "device_name": "道通龙鱼", "device_type": 0, "device_source": 0, "longitude": 118.099366, "latitude": 32.6895, "online": true, "device_logo": "/3dScreen/img/flyDevices.jpg", "flvUrl": "http://example.com/live/1.flv" }, "props": [ { "prop_name": "高度", "prop_value": "120", "prop_unit": "米" }, { "prop_name": "速度", "prop_value": "15", "prop_unit": "米/秒" }, { "prop_name": "电量", "prop_value": "88", "prop_unit": "%" } ] },
@@ -189,9 +194,70 @@ const deviceList = ref([
//页面装载
onMounted(async () => {
deviceList.value = await getUVDeviceList();
EventBus.on("WebScoket-Message", handlerSocketMessage)
const mockDeviceListSnapshot = JSON.stringify(deviceList.value);
const isDeviceListLoading = ref(false);
let isRealtimeDeviceMode = false;
const getDeviceItems = () => {
return Array.isArray(deviceList.value) ? deviceList.value : [];
};
const createMockDeviceList = () => {
return JSON.parse(mockDeviceListSnapshot);
};
const startRealtimeDeviceMode = () => {
if (isRealtimeDeviceMode) {
return;
}
EventBus.on("WebScoket-Message", handlerSocketMessage);
startOfflineTimer();
startDevuceUploadTimer();
isRealtimeDeviceMode = true;
};
const stopRealtimeDeviceMode = () => {
if (!isRealtimeDeviceMode) {
return;
}
EventBus.off('WebScoket-Message', handlerSocketMessage);
stopOfflineTimer();
stopDevuceUploadTimer();
isRealtimeDeviceMode = false;
};
const loadApiDeviceList = async () => {
isDeviceListLoading.value = true;
deviceList.value = [];
try {
deviceList.value = await getUVDeviceList();
} finally {
isDeviceListLoading.value = false;
}
};
const syncDeviceListSource = async () => {
if (showOnlineOnly.value) {
await loadApiDeviceList();
startRealtimeDeviceMode();
return;
}
stopRealtimeDeviceMode();
isDeviceListLoading.value = false;
deviceList.value = createMockDeviceList();
};
const handleOnlineOnlyChange = async () => {
await syncDeviceListSource();
};
onMounted(() => {
if (!showOnlineOnly.value) {
deviceList.value = createMockDeviceList();
return;
}
deviceList.value = createMockDeviceList();
return;
//开启离线检测
startOfflineTimer();
@@ -202,9 +268,7 @@ onMounted(async () => {
//页面卸载
onUnmounted(() => {
EventBus.off('WebScoket-Message', handlerSocketMessage)
stopOfflineTimer();
stopDevuceUploadTimer();
stopRealtimeDeviceMode();
})
@@ -213,7 +277,7 @@ onUnmounted(() => {
var devuceUploadTimer = null;
const startDevuceUploadTimer =()=>{
devuceUploadTimer = setInterval(()=>{
const onlineDevices = deviceList.value.filter(item => item.device.online === true);
const onlineDevices = getDeviceItems().filter(item => item.device.online === true);
onlineDevices.forEach(item=>{
const { device:deviceInfo, props: deviceProps } = item;
const uploadInfo = {
@@ -241,7 +305,7 @@ var offlineTimer = null;
const startOfflineTimer = ()=>{
offlineTimer = setInterval(()=>{
const currentTime = new Date();
deviceList.value.forEach(item => {
getDeviceItems().forEach(item => {
if (item.device.update_time) {
const updateTime = new Date(item.device.update_time);
if (updateTime < new Date(currentTime - 50000)) {
@@ -473,7 +537,7 @@ const handlerSocketMessage = (message) => {
//找出唯一标识的无人设备
function findUVDeviceByCode(code,type) {
return deviceList.value.find(item => item.device.device_code === code && item.device.device_type === type) || null;
return getDeviceItems().find(item => item.device.device_code === code && item.device.device_type === type) || null;
}
//设置属性值
@@ -500,7 +564,7 @@ function setUVDevicePropsValue(props, data) {
// --- 2. 计算属性动态计算当前Tab的设备总数和在线数 ---
const currentTabCounts = computed(() => {
// 1. 先根据当前激活的Tab过滤出设备
const devicesInCurrentTab = deviceList.value.filter(
const devicesInCurrentTab = getDeviceItems().filter(
item => item.device.device_type === activeTab.value
);
// 2. 计算总数
@@ -515,7 +579,7 @@ const currentTabCounts = computed(() => {
// 计算属性,实现所有筛选逻辑
const filteredDeviceList = computed(() => {
// 1. 先根据Tab、搜索框、复选框进行筛选
const filtered = deviceList.value.filter(item => {
const filtered = getDeviceItems().filter(item => {
const typeMatch = item.device.device_type === activeTab.value;
const onlineMatch = !showOnlineOnly.value || (showOnlineOnly.value && item.device.online);
const searchMatch = !searchText.value || item.device.device_name.toLowerCase().includes(searchText.value.toLowerCase());
@@ -536,26 +600,58 @@ const getSourceText = (source) => {
return sourceMap[source] || "未知";
};
const toggleOnlineOnly = async () => {
showOnlineOnly.value = !showOnlineOnly.value;
await syncDeviceListSource();
};
const getDeviceLiveSn = (device) => {
return device?.device_code || device?.device_sn || "";
};
const getPreviewPlayerSn = (device) => {
return getDeviceLiveSn(device) || (device?.id != null ? String(device.id) : "wrc_sn");
};
const hasDirectPreviewUrl = (device) => {
const flvUrl = typeof device?.flvUrl === "string" ? device.flvUrl.trim() : "";
return flvUrl !== "" && flvUrl !== "Noneed";
};
const canPreviewDevice = (device) => {
if (!device?.online) {
return false;
}
if (hasDirectPreviewUrl(device)) {
return true;
}
return Number(device?.device_type) === 0 && device?.flvUrl === "Noneed" && !!getDeviceLiveSn(device);
};
// 预览按钮点击事件处理
const handlePreview = (device) => {
if (!canPreviewDevice(device)) {
return;
}
//播放flvUrl
if(device.flvUrl && device.flvUrl != "Noneed"){
openLiveFlv("wrc_sn","wrc",device.flvUrl)
if (hasDirectPreviewUrl(device)) {
openLiveFlv(getPreviewPlayerSn(device), "wrc", device.flvUrl)
return;
}
//大疆道通无人机直播
if(device.device_type === 0 && device.flvUrl === "Noneed"){
let deviceSn = device.device_code;
let deviceRcSn = device.parent_sn?device.parent_sn:null;
openLive(deviceSn,deviceRcSn,'dock');
if (Number(device.device_type) === 0 && device.flvUrl === "Noneed") {
const deviceSn = getDeviceLiveSn(device);
const deviceRcSn = device.parent_sn || device.device_sn || null;
if (!deviceSn) {
return;
}
openLive(deviceSn, deviceRcSn, 'dock');
}
};
//无人机直播
const liveContent = reactive({
data: {
showLiveModal: false,
@@ -567,14 +663,17 @@ const liveContent = reactive({
const openLive = (liveSn, rcSn, liveType) => {
liveContent.data.showLiveModal = false
nextTick(() => {
liveContent.data.showLiveModal = true
liveContent.data.liveSn = liveSn
liveContent.data.liveType = liveType
liveContent.data.rcsn = rcSn
liveContent.data.showLiveModal = true
})
}
const closeLive = () => {
liveContent.data.showLiveModal = false
liveContent.data.liveSn = ""
liveContent.data.liveType = ""
liveContent.data.rcsn = ""
}
@@ -592,14 +691,17 @@ const liveContentJgWrc = reactive({
const openLiveFlv = (liveSn,liveType,flvUrl) => {
liveContentJgWrc.data.showLiveModal = false
nextTick(() => {
liveContentJgWrc.data.showLiveModal = true
liveContentJgWrc.data.liveSn = liveSn
liveContentJgWrc.data.liveType = liveType
liveContentJgWrc.data.flvUrl = flvUrl
liveContentJgWrc.data.showLiveModal = true
})
}
const closeLiveFlv = () => {
liveContentJgWrc.data.showLiveModal = false
liveContentJgWrc.data.liveSn = ""
liveContentJgWrc.data.liveType = ""
liveContentJgWrc.data.flvUrl = ""
}
@@ -662,10 +764,10 @@ const editUVDevice = ref(null);
// 处理保存和修改事件
const handleSave = async (newDeviceData) => {
deviceList.value = await getUVDeviceList();
await syncDeviceListSource();
};
const handleUpdate = async (newDeviceData) => {
deviceList.value = await getUVDeviceList();
await syncDeviceListSource();
};
@@ -987,4 +1089,4 @@ $danger-color: #ff4d4f; // antd 危险色
}
}
}
</style>
</style>