feat(app): 全局离线提示横幅 — 网络不可用时显示黄色警告
- 新增 OfflineBanner widget: 监听 connectivity_plus 自动显示/隐藏 - AnimatedCrossFade 滑入滑出动画 + warmCurve 弹性曲线 - 黄色警告条: wifi_off 图标 + '网络不可用,部分功能受限' - 嵌入 ResponsiveScaffold 的 body 上方 (手机/平板/桌面三端) - 只在离线时显示,恢复网络后自动消失
This commit is contained in:
102
app/lib/widgets/offline_banner.dart
Normal file
102
app/lib/widgets/offline_banner.dart
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
// 全局离线提示横幅 — 监听 connectivity_plus 显示/隐藏
|
||||||
|
//
|
||||||
|
// 放在 Scaffold body 上方,离线时显示黄色警告横幅
|
||||||
|
// 使用: 在 responsive_scaffold 的 body 上方嵌套
|
||||||
|
|
||||||
|
import 'package:connectivity_plus/connectivity_plus.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import '../core/constants/design_tokens.dart';
|
||||||
|
import '../core/theme/app_colors.dart';
|
||||||
|
|
||||||
|
/// 全局离线提示横幅 — 自动监听网络状态
|
||||||
|
class OfflineBanner extends StatefulWidget {
|
||||||
|
const OfflineBanner({super.key, required this.child});
|
||||||
|
|
||||||
|
final Widget child;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<OfflineBanner> createState() => _OfflineBannerState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _OfflineBannerState extends State<OfflineBanner> {
|
||||||
|
bool _isOffline = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
// 初始检查
|
||||||
|
Connectivity().checkConnectivity().then((result) {
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
_isOffline = result.every((r) => r == ConnectivityResult.none);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// 监听变化
|
||||||
|
Connectivity().onConnectivityChanged.listen((result) {
|
||||||
|
if (mounted) {
|
||||||
|
final offline = result.every((r) => r == ConnectivityResult.none);
|
||||||
|
if (offline != _isOffline) {
|
||||||
|
setState(() => _isOffline = offline);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
// 离线横幅 — 带动画滑入/滑出
|
||||||
|
AnimatedCrossFade(
|
||||||
|
firstChild: const SizedBox.shrink(),
|
||||||
|
secondChild: _OfflineBar(),
|
||||||
|
crossFadeState: _isOffline
|
||||||
|
? CrossFadeState.showSecond
|
||||||
|
: CrossFadeState.showFirst,
|
||||||
|
duration: DesignTokens.animNormal,
|
||||||
|
sizeCurve: DesignTokens.warmCurve,
|
||||||
|
),
|
||||||
|
// 正常内容
|
||||||
|
Expanded(child: widget.child),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 离线横幅条
|
||||||
|
class _OfflineBar extends StatelessWidget {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
width: double.infinity,
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: AppColors.warning.withValues(alpha: 0.15),
|
||||||
|
border: Border(
|
||||||
|
bottom: BorderSide(
|
||||||
|
color: AppColors.warning.withValues(alpha: 0.3),
|
||||||
|
width: 1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Icon(Icons.wifi_off_rounded, size: 16, color: AppColors.warning),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
'网络不可用,部分功能受限',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 13,
|
||||||
|
color: AppColors.warning,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,6 +9,7 @@ import '../core/constants/breakpoints.dart';
|
|||||||
import '../core/theme/app_colors.dart';
|
import '../core/theme/app_colors.dart';
|
||||||
import '../core/theme/app_typography.dart';
|
import '../core/theme/app_typography.dart';
|
||||||
import '../core/theme/app_radius.dart';
|
import '../core/theme/app_radius.dart';
|
||||||
|
import 'offline_banner.dart';
|
||||||
|
|
||||||
/// 导航项数量(不含中心 FAB)
|
/// 导航项数量(不含中心 FAB)
|
||||||
const int kNavItemCount = 4;
|
const int kNavItemCount = 4;
|
||||||
@@ -167,7 +168,7 @@ class _MobileLayout extends StatelessWidget {
|
|||||||
appBar: appBarTitle != null
|
appBar: appBarTitle != null
|
||||||
? AppBar(title: Text(appBarTitle!))
|
? AppBar(title: Text(appBarTitle!))
|
||||||
: null,
|
: null,
|
||||||
body: body,
|
body: OfflineBanner(child: body),
|
||||||
extendBody: true, // 允许内容延伸到 Tab 栏下面(圆角透明效果)
|
extendBody: true, // 允许内容延伸到 Tab 栏下面(圆角透明效果)
|
||||||
bottomNavigationBar: _BottomNavBar(
|
bottomNavigationBar: _BottomNavBar(
|
||||||
selectedIndex: selectedIndex,
|
selectedIndex: selectedIndex,
|
||||||
|
|||||||
Reference in New Issue
Block a user