装备列表优化

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' 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 () => { export const getUVDeviceList = async () => {
let res = await req.get(`/uv/api/devices/query/list`) let res = await req.get(`/uv/api/devices/query/list`)
return res.data return normalizeDeviceList(res.data)
} }
//修改无人设备 //修改无人设备

View File

@@ -35,15 +35,18 @@
<div class="search-wrapper"> <div class="search-wrapper">
<input type="text" v-model="searchText" placeholder="按名称搜索设备" class="search-input"> <input type="text" v-model="searchText" placeholder="按名称搜索设备" class="search-input">
</div> </div>
<div class="checkbox-wrapper"> <div class="checkbox-wrapper" @mousedown.stop @click.stop>
<input type="checkbox" id="online-only" v-model="showOnlineOnly" class="custom-checkbox"> <input type="checkbox" id="online-only" v-model="showOnlineOnly" class="custom-checkbox" @mousedown.stop @click.stop @change.stop="handleOnlineOnlyChange">
<label for="online-only">只看在线</label> <label for="online-only" @mousedown.stop @click.prevent.stop="toggleOnlineOnly">只看在线</label>
</div> </div>
</div> </div>
<!-- 内容列表 --> <!-- 内容列表 -->
<div class="ud-device-content"> <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> <p>没有找到匹配的设备</p>
<span class="tip">请尝试调整筛选条件</span> <span class="tip">请尝试调整筛选条件</span>
</div> </div>
@@ -72,10 +75,11 @@
</div> </div>
<div class="header-actions"> <div class="header-actions">
<!-- 预览按钮 (如果满足条件) --> <!-- 预览按钮 (如果满足条件) -->
<span v-if="(item.device.online && item.device.flvUrl)" <span v-if="canPreviewDevice(item.device)"
class="preview-btn" class="preview-btn"
title="预览直播" 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> <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> </span>
@@ -177,6 +181,7 @@ const tabOptions = ref([
{ label: "无人车", type: 1 }, { label: "无人车", type: 1 },
{ label: "其他", type: 2 }, { label: "其他", type: 2 },
]); ]);
const activeTab = ref(0); const activeTab = ref(0);
const deviceList = ref([ 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": "%" } ] }, { "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 () => { const mockDeviceListSnapshot = JSON.stringify(deviceList.value);
deviceList.value = await getUVDeviceList(); const isDeviceListLoading = ref(false);
EventBus.on("WebScoket-Message", handlerSocketMessage) 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(); startOfflineTimer();
@@ -202,9 +268,7 @@ onMounted(async () => {
//页面卸载 //页面卸载
onUnmounted(() => { onUnmounted(() => {
EventBus.off('WebScoket-Message', handlerSocketMessage) stopRealtimeDeviceMode();
stopOfflineTimer();
stopDevuceUploadTimer();
}) })
@@ -213,7 +277,7 @@ onUnmounted(() => {
var devuceUploadTimer = null; var devuceUploadTimer = null;
const startDevuceUploadTimer =()=>{ const startDevuceUploadTimer =()=>{
devuceUploadTimer = setInterval(()=>{ devuceUploadTimer = setInterval(()=>{
const onlineDevices = deviceList.value.filter(item => item.device.online === true); const onlineDevices = getDeviceItems().filter(item => item.device.online === true);
onlineDevices.forEach(item=>{ onlineDevices.forEach(item=>{
const { device:deviceInfo, props: deviceProps } = item; const { device:deviceInfo, props: deviceProps } = item;
const uploadInfo = { const uploadInfo = {
@@ -241,7 +305,7 @@ var offlineTimer = null;
const startOfflineTimer = ()=>{ const startOfflineTimer = ()=>{
offlineTimer = setInterval(()=>{ offlineTimer = setInterval(()=>{
const currentTime = new Date(); const currentTime = new Date();
deviceList.value.forEach(item => { getDeviceItems().forEach(item => {
if (item.device.update_time) { if (item.device.update_time) {
const updateTime = new Date(item.device.update_time); const updateTime = new Date(item.device.update_time);
if (updateTime < new Date(currentTime - 50000)) { if (updateTime < new Date(currentTime - 50000)) {
@@ -473,7 +537,7 @@ const handlerSocketMessage = (message) => {
//找出唯一标识的无人设备 //找出唯一标识的无人设备
function findUVDeviceByCode(code,type) { 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的设备总数和在线数 --- // --- 2. 计算属性动态计算当前Tab的设备总数和在线数 ---
const currentTabCounts = computed(() => { const currentTabCounts = computed(() => {
// 1. 先根据当前激活的Tab过滤出设备 // 1. 先根据当前激活的Tab过滤出设备
const devicesInCurrentTab = deviceList.value.filter( const devicesInCurrentTab = getDeviceItems().filter(
item => item.device.device_type === activeTab.value item => item.device.device_type === activeTab.value
); );
// 2. 计算总数 // 2. 计算总数
@@ -515,7 +579,7 @@ const currentTabCounts = computed(() => {
// 计算属性,实现所有筛选逻辑 // 计算属性,实现所有筛选逻辑
const filteredDeviceList = computed(() => { const filteredDeviceList = computed(() => {
// 1. 先根据Tab、搜索框、复选框进行筛选 // 1. 先根据Tab、搜索框、复选框进行筛选
const filtered = deviceList.value.filter(item => { const filtered = getDeviceItems().filter(item => {
const typeMatch = item.device.device_type === activeTab.value; const typeMatch = item.device.device_type === activeTab.value;
const onlineMatch = !showOnlineOnly.value || (showOnlineOnly.value && item.device.online); const onlineMatch = !showOnlineOnly.value || (showOnlineOnly.value && item.device.online);
const searchMatch = !searchText.value || item.device.device_name.toLowerCase().includes(searchText.value.toLowerCase()); const searchMatch = !searchText.value || item.device.device_name.toLowerCase().includes(searchText.value.toLowerCase());
@@ -536,26 +600,58 @@ const getSourceText = (source) => {
return sourceMap[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) => { const handlePreview = (device) => {
if (!canPreviewDevice(device)) {
return;
}
//播放flvUrl //播放flvUrl
if(device.flvUrl && device.flvUrl != "Noneed"){ if (hasDirectPreviewUrl(device)) {
openLiveFlv("wrc_sn","wrc",device.flvUrl) openLiveFlv(getPreviewPlayerSn(device), "wrc", device.flvUrl)
return; return;
} }
//大疆道通无人机直播 //大疆道通无人机直播
if(device.device_type === 0 && device.flvUrl === "Noneed"){ if (Number(device.device_type) === 0 && device.flvUrl === "Noneed") {
let deviceSn = device.device_code; const deviceSn = getDeviceLiveSn(device);
let deviceRcSn = device.parent_sn?device.parent_sn:null; const deviceRcSn = device.parent_sn || device.device_sn || null;
openLive(deviceSn,deviceRcSn,'dock'); if (!deviceSn) {
return;
}
openLive(deviceSn, deviceRcSn, 'dock');
} }
}; };
//无人机直播
const liveContent = reactive({ const liveContent = reactive({
data: { data: {
showLiveModal: false, showLiveModal: false,
@@ -567,14 +663,17 @@ const liveContent = reactive({
const openLive = (liveSn, rcSn, liveType) => { const openLive = (liveSn, rcSn, liveType) => {
liveContent.data.showLiveModal = false liveContent.data.showLiveModal = false
nextTick(() => { nextTick(() => {
liveContent.data.showLiveModal = true
liveContent.data.liveSn = liveSn liveContent.data.liveSn = liveSn
liveContent.data.liveType = liveType liveContent.data.liveType = liveType
liveContent.data.rcsn = rcSn liveContent.data.rcsn = rcSn
liveContent.data.showLiveModal = true
}) })
} }
const closeLive = () => { const closeLive = () => {
liveContent.data.showLiveModal = false 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) => { const openLiveFlv = (liveSn,liveType,flvUrl) => {
liveContentJgWrc.data.showLiveModal = false liveContentJgWrc.data.showLiveModal = false
nextTick(() => { nextTick(() => {
liveContentJgWrc.data.showLiveModal = true
liveContentJgWrc.data.liveSn = liveSn liveContentJgWrc.data.liveSn = liveSn
liveContentJgWrc.data.liveType = liveType liveContentJgWrc.data.liveType = liveType
liveContentJgWrc.data.flvUrl = flvUrl liveContentJgWrc.data.flvUrl = flvUrl
liveContentJgWrc.data.showLiveModal = true
}) })
} }
const closeLiveFlv = () => { const closeLiveFlv = () => {
liveContentJgWrc.data.showLiveModal = false 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) => { const handleSave = async (newDeviceData) => {
deviceList.value = await getUVDeviceList(); await syncDeviceListSource();
}; };
const handleUpdate = async (newDeviceData) => { const handleUpdate = async (newDeviceData) => {
deviceList.value = await getUVDeviceList(); await syncDeviceListSource();
}; };