QinGang_interested/lib/pages/home/home_page.dart

991 lines
31 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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