QinGang_interested/lib/pages/home/home_page.dart

991 lines
31 KiB
Dart
Raw Permalink Normal View History

2025-12-12 09:11:30 +08:00
import 'dart:async';
import 'dart:convert';
import 'dart:ffi';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:qhd_prevention/common/route_aware_state.dart';
import 'package:qhd_prevention/common/route_service.dart';
import 'package:qhd_prevention/customWidget/custom_button.dart';
import 'package:qhd_prevention/pages/home/scan_page.dart';
import 'package:qhd_prevention/pages/home/unit/unit_tab_page.dart';
import 'package:qhd_prevention/pages/main_tab.dart';
import 'package:qhd_prevention/pages/user/firm_list_page.dart';
import 'package:qhd_prevention/tools/h_colors.dart';
import 'package:qhd_prevention/tools/tools.dart';
import 'package:shared_preferences/shared_preferences.dart';
class HomePage extends StatefulWidget {
const HomePage({Key? key, required this.isChooseFirm}) : super(key: key);
final bool isChooseFirm;
@override
HomePageState createState() => HomePageState();
}
class HomePageState extends RouteAwareState<HomePage>
with WidgetsBindingObserver, SingleTickerProviderStateMixin {
final PageController _pageController = PageController();
final ScrollController _scrollController = ScrollController();
int _currentPage = 0;
bool _isMobileSelected = true; // 切换按钮状态
void startScan() {
Navigator.push(
context,
MaterialPageRoute(builder: (_) => ScanPage(type: ScanType.Onboarding)),
);
}
// 缓存 key
static const String _hiddenCacheKey = 'hidden_roll_cache';
// 上面按钮显示状态
List<bool> _buttonVisibility = [];
// 我的工作子项显示状态
List<bool> _workItemVisibility = [];
List totalList = [];
// 工作统计数据
Map<String, int> workStats = {'total': 36, 'processed': 30, 'pending': 6};
// 检查清单数据
List<Map<String, dynamic>> checkLists = [
{
"title": "电工班车间清单",
"type": "隐患排查",
"time": "2025-11-17",
"color": Color(0xFF4CAF50),
},
{
"title": "工地区消防点检清单",
"type": "消防点检",
"time": "2025-11-17",
"color": Color(0xFF2196F3),
},
{
"title": "消防专项检查清单",
"type": "安环检查",
"time": "2025-11-17",
"color": Color(0xFFFF9800),
},
{
"title": "二车间防护网",
"type": "隐患处理",
"time": "2025-11-17",
"color": Color(0xFFF44336),
},
{
"title": "二车间防护网",
"type": "隐患处理",
"time": "2025-11-17",
"color": Color(0xFFF44336),
},
{
"title": "二车间防护网",
"type": "隐患处理",
"time": "2025-11-17",
"color": Color(0xFFF44336),
},
{
"title": "二车间防护网",
"type": "隐患处理",
"time": "2025-11-17",
"color": Color(0xFFF44336),
},
{
"title": "二车间防护网",
"type": "隐患处理",
"time": "2025-11-17",
"color": Color(0xFFF44336),
},
];
// 通知相关
final List<String> _notifications = [
"系统通知今晚20:00 将进行系统维护,请提前保存数据。",
"安全提示:施工区请佩戴安全帽并系好安全带。",
"公告本周五集团例会在多功能厅召开9:00准时开始。",
"提醒:请尽快完成隐患整改清单中的待办项。",
];
int _notifIndex = 0;
late final PageController _notifPageController;
Timer? _notifTimer;
List<Map<String, dynamic>> buttonInfos = [
{"icon": "assets/images/ico1.png", "title": "单位管理", "unreadCount": 0},
{"icon": "assets/images/ico2.png", "title": "现场监管", "unreadCount": 0},
{"icon": "assets/images/ico3.png", "title": "危险作业", "unreadCount": 0},
{"icon": "assets/images/ico4.png", "title": "隐患处理", "unreadCount": 0},
{"icon": "assets/images/ico5.png", "title": "安环检查", "unreadCount": 0},
{"icon": "assets/images/ico6.png", "title": "口门门禁", "unreadCount": 0},
{"icon": "assets/images/ico7.png", "title": "入港培训", "unreadCount": 0},
];
// 浮动 AppBar滚动触发
bool _showFloatingAppBar = false;
static const double _triggerOffset = 30.0; // 滚动触发距离
static const double _floatingBarHeight = 56.0;
// 更新模块和按钮显示状态的方法
void _updateModuleAndButtonVisibility() {
final routeService = RouteService();
final homeRoutes =
routeService.mainTabs.isNotEmpty
? routeService.getRoutesForTab(routeService.mainTabs[0])
: [];
setState(() {
_buttonVisibility = List.filled(buttonInfos.length, false);
// 根据路由标题匹配并设置显示状态(目前保留)
for (final route in homeRoutes) {
final routeTitle = route.title ?? '';
}
});
}
/// 隐患播报列表及状态
List<Map<String, dynamic>> hiddenList = [];
bool _initialLoadingHidden = true;
bool _firstLoad = false;
@override
void initState() {
super.initState();
// _getNeedSafetyCommitment();
_buttonVisibility = List.filled(buttonInfos.length, true);
// 通知滚动 PageController
_notifPageController = PageController(initialPage: 0);
// 启动定时器:每 3 秒切换一条通知
_notifTimer = Timer.periodic(const Duration(seconds: 3), (timer) {
if (!mounted) return;
final next = (_notifIndex + 1) % _notifications.length;
_notifIndex = next;
// animateToPage 可能抛异常(在 dispose 中),所以包在 try/catch
try {
_notifPageController.animateToPage(
next,
duration: const Duration(milliseconds: 400),
curve: Curves.easeInOut,
);
} catch (_) {}
setState(() {});
});
// 监听主列表滚动以控制浮动 AppBar 显示
_scrollController.addListener(_onScroll);
WidgetsBinding.instance.addObserver(this);
Future.delayed(const Duration(seconds: 1), () {
_firstLoad = true;
});
}
void _onScroll() {
final offset =
_scrollController.hasClients ? _scrollController.offset : 0.0;
final shouldShow = offset >= _triggerOffset;
if (shouldShow != _showFloatingAppBar) {
setState(() {
_showFloatingAppBar = shouldShow;
});
}
}
@override
void dispose() {
_pageController.dispose();
_notifTimer?.cancel();
_notifPageController.dispose();
_scrollController.removeListener(_onScroll);
_scrollController.dispose();
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
@override
Future<void> onVisible() async {
final current = CurrentTabNotifier.of(context)?.currentIndex ?? 0;
const myIndex = 0;
if (current != myIndex) {
return;
}
if (_firstLoad) {}
}
void onRouteConfigLoaded() {
if (mounted) {
setState(() {
_updateModuleAndButtonVisibility();
});
}
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.resumed) {
// App 回到前台时刷新数据
}
}
void _onBadgeUpdated() {
if (mounted) {
setState(() {});
}
}
/// 更新按钮角标
void _updateButtonBadges() {
setState(() {
// 可以在这里更新角标数据
});
}
/// 从 SharedPreferences 读取缓存
Future<void> _loadHiddenCache() async {
try {
final prefs = await SharedPreferences.getInstance();
final jsonStr = prefs.getString(_hiddenCacheKey);
if (jsonStr != null && jsonStr.isNotEmpty) {
final parsed = jsonDecode(jsonStr) as List<dynamic>;
final list =
parsed.map((e) => Map<String, dynamic>.from(e as Map)).toList();
setState(() {
hiddenList = list;
_initialLoadingHidden = false;
});
} else {
setState(() {
_initialLoadingHidden = true;
});
}
} catch (e) {
debugPrint('加载 hidden cache 失败: $e');
setState(() {
_initialLoadingHidden = true;
});
}
}
Future<void> _onRefresh() async {
// 刷新数据
await Future.delayed(const Duration(seconds: 1));
}
@override
Widget build(BuildContext context) {
const double notificationHeight = 60.0; // 通知栏高度
double bannerHeight = 738 / 1125 * screenWidth(context);
const double iconSectionHeight = 220.0;
const double iconOverlapBanner = 90.0; // 图标区覆盖 banner 的高度
const double iconOverlapNotification = -10.0; // 图标区覆盖通知栏的高度
final double stackBottom =
bannerHeight - iconOverlapBanner + iconSectionHeight + 60;
final double statusBar = MediaQuery.of(context).padding.top;
return PopScope(
canPop: false,
child: Scaffold(
extendBodyBehindAppBar: true,
body: Stack(
children: [
RefreshIndicator(
onRefresh: _onRefresh,
child: ListView(
controller: _scrollController,
physics: const AlwaysScrollableScrollPhysics(),
padding: const EdgeInsets.all(0),
children: [
SizedBox(
height: stackBottom,
child: Stack(
clipBehavior: Clip.none,
children: [
// Banner顶部
Positioned(
top: 0,
left: 0,
right: 0,
height: bannerHeight,
child: _buildBannerSection(bannerHeight),
),
// 通知栏(
Positioned(
left: 10,
right: 10,
top:
(bannerHeight - iconOverlapBanner) +
iconSectionHeight -
iconOverlapNotification,
height: notificationHeight,
child: _buildNotificationBar(notificationHeight - 2),
),
// 图标区(覆盖 banner 底部 overlap
Positioned(
left: 10,
right: 10,
top: bannerHeight - iconOverlapBanner,
height: iconSectionHeight,
child: _buildIconSection(context),
),
],
),
),
if (widget.isChooseFirm) ...[
// 工作统计区域
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 10,
vertical: 20,
),
child: _buildWorkStatsSection(),
),
// 检查清单区域
Padding(
padding: const EdgeInsets.symmetric(horizontal: 10),
child: _buildCheckListSection(),
),
const SizedBox(height: 20),
]
],
),
),
// 浮动 AppBar平滑出现/隐藏),放在 ListView 之上
Positioned(
top: 0,
left: 0,
right: 0,
child: AnimatedContainer(
duration: const Duration(milliseconds: 0),
height:
_showFloatingAppBar ? (statusBar + _floatingBarHeight) : 0,
decoration: BoxDecoration(
color:
_showFloatingAppBar
? h_AppBarColor()
: Colors.transparent,
boxShadow:
_showFloatingAppBar
? [
BoxShadow(
color: Colors.black.withOpacity(0.08),
blurRadius: 6,
),
]
: [],
),
child: SafeArea(
bottom: false,
child: Opacity(
opacity: _showFloatingAppBar ? 1.0 : 0.0,
child: SizedBox(
height: _floatingBarHeight,
child: const SizedBox()
),
),
),
),
),
Positioned(
top: statusBar + 14,
width: screenWidth(context),
child: Center(child:
Text(
'秦港-相关方安全管理',
style: TextStyle(
fontSize: 19,
fontWeight: FontWeight.w600,
color: Colors.white,
),
),)
),
// 固定在最上层的图标(位于 AppBar 之上),保证它们不会随滚动移动
_buildFixedTopIcons(context),
],
),
),
);
}
// 固定在屏幕右上角的图标(不会随页面滚动)
Widget _buildFixedTopIcons(BuildContext context) {
final double statusBar = MediaQuery.of(context).padding.top;
// 固定图标距离顶部的偏移(在 banner 内时可调小)
final double topOffset = statusBar + 12;
return Positioned(
top: topOffset,
right: 12,
child: Row(
children: [
GestureDetector(
onTap: startScan,
child: Container(
width: 38,
height: 38,
alignment: Alignment.center,
child: Image.asset(
"assets/icon-apps/home_saoyisao.png",
width: 22,
height: 22,
),
),
),
],
),
);
}
Widget _buildNotificationBar(double notificationHeight) {
return Material(
color: Colors.transparent,
child: Container(
height: notificationHeight,
decoration: BoxDecoration(
color: const Color(0xFFE6F5FF), // 浅蓝
borderRadius: BorderRadius.circular(5),
border: Border.all(color: Colors.white, width: 1), // 白色边框
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 6,
offset: const Offset(0, 2),
),
],
),
child: Column(
children: [
// const SizedBox(height: 40),
Row(
children: [
const SizedBox(width: 12),
// 左侧图标
Image.asset('assets/images/ico8.png', width: 30, height: 25),
const SizedBox(width: 12),
// 中间可滚动的文本区域(使用垂直 PageView
Expanded(
child: SizedBox(
height: notificationHeight,
child: PageView.builder(
controller: _notifPageController,
scrollDirection: Axis.vertical,
physics: const NeverScrollableScrollPhysics(),
itemCount: _notifications.length,
itemBuilder: (context, index) {
final text = _notifications[index];
return Align(
alignment: Alignment.centerLeft,
child: Padding(
padding: const EdgeInsets.only(right: 8.0),
child: Text(
text,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
color: Colors.black87,
fontSize: 14,
),
),
),
);
},
),
),
),
// 右侧箭头
const Icon(Icons.chevron_right, color: Colors.black26),
const SizedBox(width: 8),
],
),
],
),
),
);
}
// 构建顶部 Banner
Widget _buildBannerSection(double bannerHeight) {
return Stack(
children: [
// 背景图片
Image.asset(
"assets/images/banner.png",
width: MediaQuery.of(context).size.width,
height: bannerHeight,
fit: BoxFit.cover,
),
// 这里保留 banner 内的额外装饰(如果需要)
],
);
}
Widget _buildIconSection(BuildContext context) {
// 判断是否有第二行
final hasSecondRow = buttonInfos.length > 4;
return Container(
padding: const EdgeInsets.symmetric(vertical: 18, horizontal: 5),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
boxShadow: const [
BoxShadow(color: Colors.black12, blurRadius: 6, offset: Offset(0, 2)),
],
),
child: widget.isChooseFirm
? Column(
children: [
// 第一行4 列等分)
Row(
children: List.generate(4, (i) {
final idx = i;
if (idx < buttonInfos.length) {
return Expanded(
child: Center(child: _buildIconButton(buttonInfos[idx], context)),
);
} else {
return const Expanded(child: SizedBox());
}
}),
),
if (hasSecondRow) const SizedBox(height: 20),
// 第二行(仍然 4 列等分;不足的用占位填充)
if (hasSecondRow)
Row(
children: List.generate(4, (i) {
final idx = 4 + i; // 第二行从索引4开始
if (idx < buttonInfos.length) {
return Expanded(
child: Center(child: _buildIconButton(buttonInfos[idx], context)),
);
} else {
return const Expanded(child: SizedBox());
}
}),
),
],
)
: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Image.asset('assets/images/ico1.png', width: 100, height: 100),
const SizedBox(height: 10),
CustomButton(
text: '点击入职企业',
onPressed: () {
pushPage(FirmListPage(), context);
},
)
],
),
),
);
}
// 构建单个图标按钮(保持原样)
Widget _buildIconButton(Map<String, dynamic> info, BuildContext context) {
return GestureDetector(
onTap: () {
_handleIconTap(info['title']);
},
child: Column(
children: [
Container(
width: 50,
height: 50,
decoration: BoxDecoration(
color: const Color(0xFFE8F4FD),
borderRadius: BorderRadius.circular(25),
),
child: Center(
child: Image.asset(info['icon'], width: 30, height: 30),
),
),
const SizedBox(height: 6),
SizedBox(
width: 70,
child: Text(
info['title'],
style: const TextStyle(fontSize: 12, color: Colors.black87),
textAlign: TextAlign.center,
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
),
],
),
);
}
// 处理图标点击
void _handleIconTap(String title) {
switch (title) {
case "单位管理":
pushPage(UnitTabPage(), context);
break;
case "现场监管":
break;
case "危险作业":
break;
case "隐患处理":
break;
case "安环检查":
break;
case "口门门禁":
break;
case "入港培训":
break;
default:
break;
}
}
// 构建工作统计区域 - 新版设计
Widget _buildWorkStatsSection() {
double buttonHeight = 45.0;
return Container(
padding: const EdgeInsets.all(1),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
boxShadow: const [
BoxShadow(color: Colors.black12, blurRadius: 6, offset: Offset(0, 2)),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 切换按钮
Container(
width: screenWidth(context),
height: buttonHeight,
decoration: BoxDecoration(
// color: Colors.white,
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(12),
topRight: Radius.circular(12),
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
// 手机端按钮
Expanded(
child: GestureDetector(
onTap: () {
setState(() {
_isMobileSelected = true;
});
},
child: Container(
decoration: BoxDecoration(
color:
_isMobileSelected
? const Color(0xFFE9F4FE)
: Colors.white,
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(12),
),
),
child: Center(
child: Text(
"手机端",
style: TextStyle(
fontSize: 14,
color:
_isMobileSelected ? Colors.black : Colors.blue,
fontWeight: FontWeight.w500,
),
),
),
),
),
),
Image.asset(
'assets/images/${_isMobileSelected ? 'img2.png' : 'img1.png'}',
fit: BoxFit.cover,
height: buttonHeight,
width: 30,
),
// 电脑端按钮
Expanded(
child: GestureDetector(
onTap: () {
setState(() {
_isMobileSelected = false;
});
},
child: Container(
decoration: BoxDecoration(
color:
!_isMobileSelected
? const Color(0xFFE9F4FE)
: Colors.white,
borderRadius: const BorderRadius.only(
topRight: Radius.circular(12),
),
),
child: Center(
child: Text(
"电脑端",
style: TextStyle(
fontSize: 15,
color:
!_isMobileSelected ? Colors.black : Colors.blue,
fontWeight: FontWeight.w500,
),
),
),
),
),
),
],
),
),
Padding(
padding: EdgeInsets.all(15),
child: Column(
children: [
// 今日工作项
Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(vertical: 0),
child: RichText(
text: TextSpan(
children: [
TextSpan(
text: "今日有工作项",
style: TextStyle(fontSize: 16, color: Colors.black87),
),
TextSpan(
text: " ${workStats['total']}",
style: const TextStyle(
fontSize: 16,
color: Color(0xFF2A75F8),
fontWeight: FontWeight.bold,
),
),
TextSpan(
text: "",
style: TextStyle(fontSize: 16, color: Colors.black87),
),
],
),
),
),
const SizedBox(height: 15),
// 第三行:已处理和待处理工作项
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
RichText(
text: TextSpan(
children: [
TextSpan(
text: "已处理工作项:",
style: TextStyle(
fontSize: 14,
color: Colors.black87,
),
),
TextSpan(
text: " ${workStats['processed']}",
style: TextStyle(
fontSize: 14,
color: Colors.greenAccent,
fontWeight: FontWeight.bold,
),
),
TextSpan(
text: "",
style: TextStyle(
fontSize: 14,
color: Colors.black87,
),
),
],
),
),
RichText(
text: TextSpan(
children: [
TextSpan(
text: "待处理工作项:",
style: TextStyle(
fontSize: 14,
color: Colors.black87,
),
),
TextSpan(
text: " ${workStats['processed']}",
style: TextStyle(
fontSize: 14,
color: Colors.orange,
fontWeight: FontWeight.bold,
),
),
TextSpan(
text: "",
style: TextStyle(
fontSize: 14,
color: Colors.black87,
),
),
],
),
),
],
),
],
),
),
],
),
);
}
// 构建检查清单区域
Widget _buildCheckListSection() {
return Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
' 待办清单',
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
),
const SizedBox(),
],
),
SizedBox(height: 8),
Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
boxShadow: const [
BoxShadow(
color: Colors.black12,
blurRadius: 6,
offset: Offset(0, 2),
),
],
),
child: Column(
children: [...checkLists.map((item) => _buildCheckListItem(item))],
),
),
],
);
}
// 构建检查清单项
Widget _buildCheckListItem(Map<String, dynamic> item) {
return Container(
padding: const EdgeInsets.all(15),
decoration: BoxDecoration(
border: Border(bottom: BorderSide(color: Colors.grey[200]!, width: 1)),
),
child: Row(
children: [
// 内容区域 - 使用Expanded确保不溢出
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 标题行
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
// 标题使用Flexible防止溢出
Flexible(
child: Text(
item['title'],
style: const TextStyle(
fontSize: 15,
fontWeight: FontWeight.bold,
color: Colors.black87,
),
overflow: TextOverflow.ellipsis, // 添加省略号
maxLines: 1, // 限制为1行
),
),
const SizedBox(width: 8), // 添加间距
Text(
item['type'],
style: TextStyle(fontSize: 12, color: Colors.grey[500]),
),
],
),
const SizedBox(height: 8), // 替代Divider的间距
// 底部信息行
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
// 类型标签使用Flexible
Flexible(
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: 6,
vertical: 2,
),
decoration: BoxDecoration(
color: Colors.grey[100],
borderRadius: BorderRadius.circular(4),
),
child: Text(
"类型:${item['type']}",
style: const TextStyle(
fontSize: 12,
color: Colors.blue,
),
overflow: TextOverflow.ellipsis,
),
),
),
const SizedBox(width: 8), // 添加间距
// 时间使用Text
Flexible(
child: Text(
"时间:${item['time']}",
style: const TextStyle(
fontSize: 12,
color: Colors.grey,
),
overflow: TextOverflow.ellipsis,
),
),
],
),
],
),
),
],
),
);
}
}