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_typography.dart';
|
||||
import '../core/theme/app_radius.dart';
|
||||
import 'offline_banner.dart';
|
||||
|
||||
/// 导航项数量(不含中心 FAB)
|
||||
const int kNavItemCount = 4;
|
||||
@@ -167,7 +168,7 @@ class _MobileLayout extends StatelessWidget {
|
||||
appBar: appBarTitle != null
|
||||
? AppBar(title: Text(appBarTitle!))
|
||||
: null,
|
||||
body: body,
|
||||
body: OfflineBanner(child: body),
|
||||
extendBody: true, // 允许内容延伸到 Tab 栏下面(圆角透明效果)
|
||||
bottomNavigationBar: _BottomNavBar(
|
||||
selectedIndex: selectedIndex,
|
||||
|
||||
Reference in New Issue
Block a user