mirror of
https://github.com/Sagit-chu/flvx.git
synced 2026-10-04 09:06:38 +08:00
feat(frontend): implement AJAX no-refresh UX improvements (#276)
- Add dashboard auto-refresh with 5s polling and visibility-aware pause - Harden node realtime reconnection with exponential backoff and polling fallback - Implement local state patching for forward/tunnel/user mutations - Add batch operation progress feedback UI - Add shared list-state helpers for replace/remove operations - Preserve derived UI state during server payload merges Closes #276 Entire-Checkpoint: 7d6188355d7f
This commit is contained in:
@@ -65,6 +65,8 @@ import { useNodeRealtime } from "@/pages/node/use-node-realtime";
|
||||
import { useLocalStorageState } from "@/hooks/use-local-storage-state";
|
||||
import { loadStoredOrder, saveOrder } from "@/utils/order-storage";
|
||||
|
||||
const NODE_FALLBACK_REFRESH_INTERVAL_MS = 15000;
|
||||
|
||||
interface Node {
|
||||
id: number;
|
||||
inx?: number;
|
||||
@@ -116,6 +118,21 @@ interface NodeForm {
|
||||
socks: number; // 0 关 1 开
|
||||
}
|
||||
|
||||
const mergeNodeRealtimeState = (
|
||||
incomingNode: Node,
|
||||
existingNode?: Node,
|
||||
): Node => {
|
||||
return {
|
||||
...incomingNode,
|
||||
systemInfo: existingNode?.systemInfo ?? incomingNode.systemInfo ?? null,
|
||||
copyLoading: existingNode?.copyLoading ?? incomingNode.copyLoading ?? false,
|
||||
upgradeLoading:
|
||||
existingNode?.upgradeLoading ?? incomingNode.upgradeLoading ?? false,
|
||||
rollbackLoading:
|
||||
existingNode?.rollbackLoading ?? incomingNode.rollbackLoading ?? false,
|
||||
};
|
||||
};
|
||||
|
||||
const SortableItem = ({
|
||||
id,
|
||||
children,
|
||||
@@ -251,8 +268,13 @@ export default function NodePage() {
|
||||
});
|
||||
|
||||
// 加载节点列表
|
||||
const loadNodes = useCallback(async () => {
|
||||
setLoading(true);
|
||||
const loadNodes = useCallback(async (options?: { silent?: boolean }) => {
|
||||
const silent = options?.silent ?? false;
|
||||
|
||||
if (!silent) {
|
||||
setLoading(true);
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await getNodeList();
|
||||
|
||||
@@ -270,7 +292,13 @@ export default function NodePage() {
|
||||
copyLoading: false,
|
||||
}));
|
||||
|
||||
setNodeList(nodesData);
|
||||
setNodeList((prev) => {
|
||||
const previousById = new Map(prev.map((node) => [node.id, node]));
|
||||
|
||||
return nodesData.map((node) =>
|
||||
mergeNodeRealtimeState(node, previousById.get(node.id)),
|
||||
);
|
||||
});
|
||||
|
||||
// 优先使用数据库中的 inx 字段进行排序,否则回退到本地排序
|
||||
const hasDbOrdering = nodesData.some(
|
||||
@@ -292,12 +320,18 @@ export default function NodePage() {
|
||||
);
|
||||
}
|
||||
} else {
|
||||
toast.error(res.msg || "加载节点列表失败");
|
||||
if (!silent) {
|
||||
toast.error(res.msg || "加载节点列表失败");
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
toast.error("网络错误,请重试");
|
||||
if (!silent) {
|
||||
toast.error("网络错误,请重试");
|
||||
}
|
||||
} finally {
|
||||
setLoading(false);
|
||||
if (!silent) {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
}, []);
|
||||
|
||||
@@ -370,7 +404,7 @@ export default function NodePage() {
|
||||
}
|
||||
};
|
||||
|
||||
const { wsConnected, wsConnecting } = useNodeRealtime({
|
||||
const { wsConnected, wsConnecting, usingPollingFallback } = useNodeRealtime({
|
||||
onMessage: handleWebSocketMessage,
|
||||
});
|
||||
|
||||
@@ -378,6 +412,22 @@ export default function NodePage() {
|
||||
loadNodes();
|
||||
}, [loadNodes]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!usingPollingFallback) {
|
||||
return;
|
||||
}
|
||||
|
||||
void loadNodes({ silent: true });
|
||||
|
||||
const interval = window.setInterval(() => {
|
||||
void loadNodes({ silent: true });
|
||||
}, NODE_FALLBACK_REFRESH_INTERVAL_MS);
|
||||
|
||||
return () => {
|
||||
window.clearInterval(interval);
|
||||
};
|
||||
}, [loadNodes, usingPollingFallback]);
|
||||
|
||||
// 格式化速度
|
||||
const formatSpeed = (bytesPerSecond: number): string => {
|
||||
if (bytesPerSecond === 0) return "0 B/s";
|
||||
@@ -1162,7 +1212,11 @@ export default function NodePage() {
|
||||
className="mb-4"
|
||||
color="warning"
|
||||
description={
|
||||
wsConnecting ? "监控连接中..." : "监控连接已断开,正在重连..."
|
||||
wsConnecting
|
||||
? "监控连接中..."
|
||||
: usingPollingFallback
|
||||
? "监控连接已断开,已切换为列表自动刷新兜底模式。"
|
||||
: "监控连接已断开,正在重连..."
|
||||
}
|
||||
variant="flat"
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user