From d192d96d954e5d54fa00427265a6a09ac7032b81 Mon Sep 17 00:00:00 2001 From: wangjia <809946525@qq.com> Date: Tue, 30 Jun 2026 08:05:32 +0800 Subject: [PATCH] =?UTF-8?q?fix(client):=20=E8=AE=BE=E5=A4=87=E9=A1=B5?= =?UTF-8?q?=E6=AF=8F=2015s=20=E9=9D=99=E9=BB=98=E5=88=B7=E6=96=B0=E5=88=97?= =?UTF-8?q?=E8=A1=A8,=E5=85=B6=E4=BB=96=E8=AE=BE=E5=A4=87=E7=8A=B6?= =?UTF-8?q?=E6=80=81=E5=8F=98=E5=8C=96=20~15s=20=E5=86=85=E5=8F=AF?= =?UTF-8?q?=E8=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 原设备列表打开时加载一次就不动(感觉十几分钟才同步)。加透传式轮询包装器 _DevicesAutoRefresh:在设备页期间每 15s 调 DevicesNotifier.refresh()(静默、不闪 loading、 失败保留旧列表)。配合服务端会话轮询刷 last_seen + 90s 窗口,其他设备上/下线 ~15s 内反映。 Co-Authored-By: Claude Opus 4.8 --- client/lib/state/account_providers.dart | 12 +++++++++ client/lib/widgets/account_screens.dart | 34 ++++++++++++++++++++++++- 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/client/lib/state/account_providers.dart b/client/lib/state/account_providers.dart index 1aea2af..8cf5fe0 100644 --- a/client/lib/state/account_providers.dart +++ b/client/lib/state/account_providers.dart @@ -89,6 +89,18 @@ class DevicesNotifier extends AsyncNotifier> { await future; } + /// 静默刷新(不闪 loading):重新拉列表、成功才替换。供设备页 ~15s 轮询,让其他设备的 + /// 在线/状态变化 ~15s 内可见(配合服务端会话轮询刷 last_seen)。 + Future refresh() async { + if (!ref.read(authProvider).isLoggedIn) return; + try { + final list = await ref.read(accountApiProvider).devices(); + state = AsyncData(list); + } catch (_) { + // 失败保留旧列表,不闪错。 + } + } + /// 强制退出(吊销该设备会话,设备保留)后刷新列表。 Future forceLogout(String uuid) async { await ref.read(accountApiProvider).forceLogout(uuid); diff --git a/client/lib/widgets/account_screens.dart b/client/lib/widgets/account_screens.dart index 9ac2a0e..c5b3903 100644 --- a/client/lib/widgets/account_screens.dart +++ b/client/lib/widgets/account_screens.dart @@ -2,6 +2,8 @@ // // 文案全部经 AppText(l10n,单显)。套餐数字以 design/CLAUDE.md §7 为准。 // App 内无任何支付表单——购买仅引导至外部渠道。 +import 'dart:async'; + import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; @@ -162,7 +164,8 @@ class DevicesScreen extends ConsumerWidget { ]), ); - return _SubScaffold(title: t.myDevices, onBack: onBack, embedded: embedded, child: content()); + return _SubScaffold( + title: t.myDevices, onBack: onBack, embedded: embedded, child: _DevicesAutoRefresh(child: content())); } Widget _row(BuildContext context, WidgetRef ref, PangolinScheme c, Device d, bool isLocal, bool divider) { @@ -493,3 +496,32 @@ class ContactScreen extends StatelessWidget { ); } } + +/// 透传式轮询包装器:在设备页期间每 15s 静默刷新设备列表(不闪 loading), +/// 让其他设备的在线/状态变化 ~15s 内可见。原样返回 child,不改布局。 +class _DevicesAutoRefresh extends ConsumerStatefulWidget { + const _DevicesAutoRefresh({required this.child}); + final Widget child; + @override + ConsumerState<_DevicesAutoRefresh> createState() => _DevicesAutoRefreshState(); +} + +class _DevicesAutoRefreshState extends ConsumerState<_DevicesAutoRefresh> { + Timer? _poll; + @override + void initState() { + super.initState(); + _poll = Timer.periodic(const Duration(seconds: 15), (_) { + if (mounted) ref.read(devicesProvider.notifier).refresh(); + }); + } + + @override + void dispose() { + _poll?.cancel(); + super.dispose(); + } + + @override + Widget build(BuildContext context) => widget.child; +}