feat(device): 在线状态每 30 秒批量刷新
抽离 refreshOnlineStatus 复用批量接口,页面定时刷新当前页 SN 在线状态,SSE 不可用时仍能保持准确。
This commit is contained in:
@@ -1,19 +1,14 @@
|
|||||||
import DeviceAPI from "@/api/device";
|
import DeviceAPI from "@/api/device";
|
||||||
import type { DeviceItem, DeviceQueryParams } from "@/api/device/types";
|
import type { DeviceItem, DeviceQueryParams } from "@/api/device/types";
|
||||||
import type { CrudTableConfig } from "@/components/Crud/types";
|
import type { CrudTableConfig } from "@/components/Crud/types";
|
||||||
import { syncOnlineStatus, updateOnlineStatus } from "./online";
|
import { refreshOnlineStatus, syncOnlineStatus } from "./online";
|
||||||
|
|
||||||
/** 列表接口不保证带在线状态,加载后批量补齐 */
|
/** 列表接口不保证带在线状态,加载后批量补齐 */
|
||||||
async function fetchDevices(params: DeviceQueryParams) {
|
async function fetchDevices(params: DeviceQueryParams) {
|
||||||
const page = await DeviceAPI.getPage(params);
|
const page = await DeviceAPI.getPage(params);
|
||||||
syncOnlineStatus(page.list);
|
syncOnlineStatus(page.list);
|
||||||
const sns = page.list.map((item) => item.serialno).filter((sn): sn is string => !!sn);
|
// 在线状态后补,不阻塞列表首屏
|
||||||
if (sns.length) {
|
void refreshOnlineStatus();
|
||||||
// 在线状态后补,不阻塞列表首屏
|
|
||||||
void DeviceAPI.getOnlineStatusBatch(sns)
|
|
||||||
.then((onlineMap) => Object.values(onlineMap ?? {}).forEach(updateOnlineStatus))
|
|
||||||
.catch(() => undefined);
|
|
||||||
}
|
|
||||||
return page;
|
return page;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,40 @@
|
|||||||
|
import DeviceAPI from "@/api/device";
|
||||||
import type { DeviceItem, DeviceOnlineVO } from "@/api/device/types";
|
import type { DeviceItem, DeviceOnlineVO } from "@/api/device/types";
|
||||||
|
|
||||||
/** 设备在线状态:分页接口提供当前页初值,SSE 推送实时覆盖 */
|
/** 设备在线状态:分页接口提供当前页初值,批量查询与 SSE 推送覆盖 */
|
||||||
export const onlineStatus = reactive<Record<string, number>>({});
|
export const onlineStatus = reactive<Record<string, number>>({});
|
||||||
|
|
||||||
|
/** 当前页设备 SN,供定时批量刷新使用 */
|
||||||
|
let pageSns: string[] = [];
|
||||||
|
|
||||||
export function syncOnlineStatus(list: DeviceItem[]) {
|
export function syncOnlineStatus(list: DeviceItem[]) {
|
||||||
|
pageSns = list.map((item) => item.serialno).filter((sn): sn is string => !!sn);
|
||||||
list.forEach((item) => {
|
list.forEach((item) => {
|
||||||
if (item.serialno) onlineStatus[item.serialno] = item.online ?? 0;
|
// 列表接口未返回 online 时保留已有值,避免把批量/SSE 结果覆盖成离线
|
||||||
|
if (item.serialno && item.online !== undefined && item.online !== null) {
|
||||||
|
onlineStatus[item.serialno] = item.online;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function updateOnlineStatus(data: DeviceOnlineVO) {
|
export function updateOnlineStatus(data: DeviceOnlineVO) {
|
||||||
if (data?.serialno) onlineStatus[data.serialno] = data.online ?? 0;
|
if (data?.serialno) onlineStatus[data.serialno] = data.online ?? 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量刷新当前页在线状态。
|
||||||
|
* 设备每 30 秒上报心跳,页面按同一频率刷新,不依赖列表接口是否返回 online。
|
||||||
|
*/
|
||||||
|
export async function refreshOnlineStatus(): Promise<void> {
|
||||||
|
const sns = pageSns;
|
||||||
|
if (!sns.length) return;
|
||||||
|
try {
|
||||||
|
const onlineMap = await DeviceAPI.getOnlineStatusBatch(sns);
|
||||||
|
// 以返回的 SN 作为键,兼容 value 中缺失 serialno 的情况
|
||||||
|
Object.entries(onlineMap ?? {}).forEach(([sn, info]) => {
|
||||||
|
if (info) onlineStatus[sn] = info.online ?? 0;
|
||||||
|
});
|
||||||
|
} catch {
|
||||||
|
// 刷新失败保留上一次状态,不打扰用户
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ import DeviceSnOverView from "./components/DeviceSnOverView.vue";
|
|||||||
import addFormConfig from "./config/add";
|
import addFormConfig from "./config/add";
|
||||||
import contentConfig from "./config/content";
|
import contentConfig from "./config/content";
|
||||||
import editFormConfig from "./config/edit";
|
import editFormConfig from "./config/edit";
|
||||||
import { onlineStatus, updateOnlineStatus } from "./config/online";
|
import { onlineStatus, refreshOnlineStatus, updateOnlineStatus } from "./config/online";
|
||||||
import { initOptions } from "./config/options";
|
import { initOptions } from "./config/options";
|
||||||
import searchConfig from "./config/search";
|
import searchConfig from "./config/search";
|
||||||
|
|
||||||
@@ -198,6 +198,9 @@ async function handleRowAction({ key, row }: { key: string; row: CrudRecord }) {
|
|||||||
*/
|
*/
|
||||||
let unsubscribeDeviceOnlineStatus: (() => void) | null = null;
|
let unsubscribeDeviceOnlineStatus: (() => void) | null = null;
|
||||||
|
|
||||||
|
/** 定时刷新在线状态(与设备 30s 心跳同频),SSE 不可用时仍能保持状态准确 */
|
||||||
|
let onlineStatusTimer: ReturnType<typeof setInterval> | null = null;
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
initOptions();
|
initOptions();
|
||||||
// 监听 SSE 推送的设备在线状态,实时更新表格对应行
|
// 监听 SSE 推送的设备在线状态,实时更新表格对应行
|
||||||
@@ -206,6 +209,7 @@ onMounted(() => {
|
|||||||
"device-online-status",
|
"device-online-status",
|
||||||
updateOnlineStatus,
|
updateOnlineStatus,
|
||||||
);
|
);
|
||||||
|
onlineStatusTimer = setInterval(refreshOnlineStatus, 30000);
|
||||||
});
|
});
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
@@ -214,6 +218,10 @@ onBeforeUnmount(() => {
|
|||||||
unsubscribeDeviceOnlineStatus();
|
unsubscribeDeviceOnlineStatus();
|
||||||
unsubscribeDeviceOnlineStatus = null;
|
unsubscribeDeviceOnlineStatus = null;
|
||||||
}
|
}
|
||||||
|
if (onlineStatusTimer) {
|
||||||
|
clearInterval(onlineStatusTimer);
|
||||||
|
onlineStatusTimer = null;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user