feat(app): 全局离线提示横幅 — 网络不可用时显示黄色警告
Some checks failed
Main Merge / backend (push) Has been cancelled
Main Merge / frontend (push) Has been cancelled

- 新增 OfflineBanner widget: 监听 connectivity_plus 自动显示/隐藏
- AnimatedCrossFade 滑入滑出动画 + warmCurve 弹性曲线
- 黄色警告条: wifi_off 图标 + '网络不可用,部分功能受限'
- 嵌入 ResponsiveScaffold 的 body 上方 (手机/平板/桌面三端)
- 只在离线时显示,恢复网络后自动消失
This commit is contained in:
iven
2026-06-07 13:44:40 +08:00
parent 346c751cbb
commit 750605e479
2 changed files with 104 additions and 1 deletions

View 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,
),
),
),
],
),
);
}
}

View File

@@ -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,