799 lines
		
	
	
		
			25 KiB
		
	
	
	
		
			Dart
		
	
	
			
		
		
	
	
			799 lines
		
	
	
		
			25 KiB
		
	
	
	
		
			Dart
		
	
	
| import 'dart:async';
 | ||
| import 'dart:convert';
 | ||
| import 'dart:io';
 | ||
| 
 | ||
| import 'package:flutter/material.dart';
 | ||
| import 'package:qhd_prevention/customWidget/custom_alert_dialog.dart';
 | ||
| import 'package:qhd_prevention/customWidget/promise/promise_page.dart';
 | ||
| import 'package:qhd_prevention/customWidget/toast_util.dart';
 | ||
| import 'package:qhd_prevention/pages/home/scan_page.dart';
 | ||
| import 'package:qhd_prevention/services/auth_service.dart';
 | ||
| import 'package:qhd_prevention/tools/coord_convert.dart';
 | ||
| import 'package:qhd_prevention/tools/update/update_dialogs.dart';
 | ||
| import 'package:shared_preferences/shared_preferences.dart';
 | ||
| import 'package:qhd_prevention/customWidget/ItemWidgetFactory.dart';
 | ||
| import 'package:qhd_prevention/pages/KeyProjects/keyProjects_tab_list.dart';
 | ||
| import 'package:qhd_prevention/pages/badge_manager.dart';
 | ||
| import 'package:qhd_prevention/pages/home/NFC/home_nfc_list_page.dart';
 | ||
| import 'package:qhd_prevention/pages/home/SafeCheck/safeCheck_tab_list.dart';
 | ||
| import 'package:qhd_prevention/pages/home/home_danger_page.dart';
 | ||
| import 'package:qhd_prevention/pages/home/risk/riskControl_page.dart';
 | ||
| import 'package:qhd_prevention/pages/home/study/study_garden_page.dart';
 | ||
| import 'package:qhd_prevention/pages/home/tap/tabList/work_tab_list_page.dart';
 | ||
| import 'package:qhd_prevention/pages/home/userInfo_page.dart';
 | ||
| import 'package:qhd_prevention/pages/app/danger_wait_list_page.dart';
 | ||
| import 'package:qhd_prevention/pages/home/work/laws_regulations_page.dart';
 | ||
| import 'package:qhd_prevention/pages/home/workSet_page.dart';
 | ||
| 
 | ||
| import 'EquipmentInspection/equipment_inspection_list_page.dart';
 | ||
| import 'SafetyCommitment/safety_commitment_page.dart';
 | ||
| import 'Safetymeeting/safety_meeting_list_page.dart';
 | ||
| import 'hidden_roll_widget.dart';
 | ||
| import '../../http/ApiService.dart';
 | ||
| import '../../tools/tools.dart';
 | ||
| 
 | ||
| class HomePage extends StatefulWidget {
 | ||
|   const HomePage({Key? key}) : super(key: key);
 | ||
| 
 | ||
|   @override
 | ||
|   HomePageState createState() => HomePageState();
 | ||
| }
 | ||
| 
 | ||
| class HomePageState extends State<HomePage> {
 | ||
|   int _eight_work_count = 0;
 | ||
|   int _safetyEnvironmentalInspection = 0;
 | ||
|   void startScan() {
 | ||
|     Navigator.push(
 | ||
|       context,
 | ||
|       MaterialPageRoute(builder: (_) => ScanPage(totalList: totalList)),
 | ||
|     );
 | ||
|   }
 | ||
|   // 缓存 key
 | ||
|   static const String _hiddenCacheKey = 'hidden_roll_cache';
 | ||
| 
 | ||
|   String _workKey(int idx) {
 | ||
|     switch (idx) {
 | ||
|       case 1:
 | ||
|         return 'dpc'; // 待排查
 | ||
|       case 2:
 | ||
|         return 'dzg'; // 待整改
 | ||
|       case 3:
 | ||
|         return 'ycq'; // 已超期
 | ||
|       case 4:
 | ||
|         return 'dys'; // 待验收
 | ||
|       case 5:
 | ||
|         return 'yys'; // 已验收
 | ||
|       default:
 | ||
|         return '';
 | ||
|     }
 | ||
|   }
 | ||
|   List totalList = [];
 | ||
|   /// 按钮信息
 | ||
|   List<Map<String, dynamic>> buttonInfos = [
 | ||
|     {
 | ||
|       "icon": "assets/icon-apps/home-base.png",
 | ||
|       "title": "人员信息",
 | ||
|       "unreadCount": 0,
 | ||
|     },
 | ||
|     {
 | ||
|       "icon": "assets/icon-apps/home-rili.png",
 | ||
|       "title": "工作安排",
 | ||
|       "unreadCount": 0,
 | ||
|     },
 | ||
|     {
 | ||
|       "icon": "assets/icon-apps/home-risk.png",
 | ||
|       "title": "风险分布",
 | ||
|       "unreadCount": 0,
 | ||
|     },
 | ||
|     {"icon": "assets/icon-apps/home-fl.png", "title": "法律法规", "unreadCount": 0},
 | ||
|     {"icon": "assets/icon-apps/home-gw.png", "title": "特殊作业", "unreadCount": 0},
 | ||
|     {
 | ||
|       "icon": "assets/icon-apps/home-zdgcgl.jpg",
 | ||
|       "title": "重点工程管理",
 | ||
|       "unreadCount": 0,
 | ||
|     },
 | ||
|     {
 | ||
|       "icon": "assets/icon-apps/home-cns.png",
 | ||
|       "title": "安全承诺",
 | ||
|       "unreadCount": 0,
 | ||
|     },
 | ||
|     {
 | ||
|       "icon": "assets/icon-apps/home-study.png",
 | ||
|       "title": "学习园地",
 | ||
|       "unreadCount": 0,
 | ||
|     },
 | ||
|     {
 | ||
|       "icon": "assets/icon-apps/home-base.png",
 | ||
|       "title": "安全检查",
 | ||
|       "unreadCount": 0,
 | ||
|     },
 | ||
|     {
 | ||
|       "icon": "assets/icon-apps/home-speEquip.jpg",
 | ||
|       "title": "设备巡检",
 | ||
|       "unreadCount": 0,
 | ||
|     },
 | ||
|     {
 | ||
|       "icon": "assets/icon-apps/safetymeeting.png",
 | ||
|       "title": "安全例会",
 | ||
|       "unreadCount": 0,
 | ||
|     },
 | ||
|     // {"icon": "assets/icon-apps/home-xj.png", "title": "燃气巡检", "unreadCount": 0},
 | ||
|   ];
 | ||
| 
 | ||
|   /// 我的工作
 | ||
|   List<Map<String, dynamic>> workInfos = [
 | ||
|     {
 | ||
|       "icon": "assets/icon-apps/jobico1.png",
 | ||
|       "index": 1,
 | ||
|       "detail": "待排查",
 | ||
|       "num": '0',
 | ||
|     },
 | ||
|     {
 | ||
|       "icon": "assets/icon-apps/jobico2.png",
 | ||
|       "index": 2,
 | ||
|       "detail": "待整改",
 | ||
|       "num": '0',
 | ||
|     },
 | ||
|     {
 | ||
|       "icon": "assets/icon-apps/jobico3.png",
 | ||
|       "index": 3,
 | ||
|       "detail": "已超期",
 | ||
|       "num": '0',
 | ||
|     },
 | ||
|     {
 | ||
|       "icon": "assets/icon-apps/jobico4.png",
 | ||
|       "index": 4,
 | ||
|       "detail": "待验收",
 | ||
|       "num": '0',
 | ||
|     },
 | ||
|     {
 | ||
|       "icon": "assets/icon-apps/jobico5.png",
 | ||
|       "index": 5,
 | ||
|       "detail": "已验收",
 | ||
|       "num": '0',
 | ||
|     },
 | ||
|   ];
 | ||
| 
 | ||
|   /// 排查数据
 | ||
|   List<Map<String, dynamic>> pcData = [
 | ||
|     {
 | ||
|       "index": 1,
 | ||
|       "detail": {"jiancha": '0', "yinhuan": '0', "yanshou": '0'},
 | ||
|     },
 | ||
|     {
 | ||
|       "index": 2,
 | ||
|       "detail": {"jiancha": '0', "yinhuan": '0', "yanshou": '0'},
 | ||
|     },
 | ||
|     {
 | ||
|       "index": 3,
 | ||
|       "detail": {"jiancha": '0', "yinhuan": '0', "yanshou": '0'},
 | ||
|     },
 | ||
|   ];
 | ||
| 
 | ||
|   /// 隐患播报列表及状态
 | ||
|   List<Map<String, dynamic>> hiddenList = [];
 | ||
|   bool _initialLoadingHidden = true;
 | ||
| 
 | ||
|   final List<List<String>> tagLabels = [
 | ||
|     ['我', '的'],
 | ||
|     ['部', '门'],
 | ||
|     ['监', '管'],
 | ||
|   ];
 | ||
| 
 | ||
|   @override
 | ||
|   void initState() {
 | ||
|     super.initState();
 | ||
|     // 使用初始化加载:先恢复缓存(若存在则直接显示),然后再发起网络请求(成功则覆盖缓存)
 | ||
|     BadgeManager().initAllModules();
 | ||
|     _initialLoad();
 | ||
| 
 | ||
|   }
 | ||
| 
 | ||
|   /// 首次加载:先恢复缓存(如果有),然后在后台去刷新(只有当无缓存时才显示 loading)
 | ||
|   Future<void> _initialLoad() async {
 | ||
|     final result = await AuthService.checkUpdate();
 | ||
|     try{
 | ||
| 
 | ||
|       if (FormUtils.hasValue(result, 'pd')) {
 | ||
|         Map pd = result['pd'];
 | ||
|         final versionInfo = await getAppVersion();
 | ||
|         bool isWifi = true;
 | ||
|         if (versionInfo.versionName != pd['VERSION']) {
 | ||
|           //有更新 提示更新
 | ||
|           final ok = await CustomAlertDialog.showConfirm(
 | ||
|               context,
 | ||
|               barrierDismissible:false,
 | ||
|               title: '更新通知',
 | ||
|               content: isWifi ? '发现新版本,是否更新?为了更好的体验,请更新到最新版本。' : '发现新版本,检查到您当前使用的是移动网络,是否更新?更新时请注意流量消耗。为了更好的体验,请更新到最新版本。',
 | ||
|               cancelText: pd['ISUPDATE'] == '1' ? '' : '稍后更新',
 | ||
|               confirmText: '立即更新'
 | ||
|           );
 | ||
|           if (ok) {
 | ||
|             if (Platform.isIOS) {
 | ||
|               openAppStore();
 | ||
|             }else{
 | ||
|               final apkUrl = pd['FILEURL'] ?? '';
 | ||
|               await showUpdateConfirm(context, apkUrl: apkUrl);
 | ||
|             }
 | ||
| 
 | ||
|           }
 | ||
|           return;
 | ||
|         }
 | ||
| 
 | ||
|       }
 | ||
|     }catch(_){}
 | ||
| 
 | ||
| 
 | ||
|     final corppromiseData = await ApiService.checkSafeCorppromise();
 | ||
|     if (corppromiseData['ISSIGN'] == 1) {
 | ||
|       // 承诺
 | ||
|       final confirmed = await CustomAlertDialog.showConfirm(
 | ||
|         context,
 | ||
|         title: '温馨提示',
 | ||
|         content: '有未签署的安全承诺,点击确认前往签署',
 | ||
|         force: true,
 | ||
| 
 | ||
|       );
 | ||
|       if (confirmed) {
 | ||
|         Navigator.pushReplacement(
 | ||
|           context,
 | ||
|           MaterialPageRoute(builder: (_) => const PromisePage()),
 | ||
|         );
 | ||
|       }
 | ||
|       return;
 | ||
|     }
 | ||
| 
 | ||
|      _loadHiddenCache();
 | ||
|     // 拉取其他数据 + 隐患列表(当 hiddenList 为空时显示 loading,否则不显示)
 | ||
|     _fetchData();
 | ||
|    _fetchHiddenList(showLoading: hiddenList.isEmpty);
 | ||
|     fetchAndSaveBd09(context);
 | ||
|     /// 清单列表
 | ||
|     final data = await ApiService.getListData();
 | ||
|     if (data['result'] == 'success') {
 | ||
|       final content = data['varList'] ?? [];
 | ||
|       for (Map item in content) {
 | ||
|         if (item['checkCount'] == 0) {
 | ||
|           totalList.add(item);
 | ||
|         }
 | ||
|       }
 | ||
|     }
 | ||
|   }
 | ||
| 
 | ||
|   Future<void> _onRefresh() async {
 | ||
|     await BadgeManager().initAllModules();
 | ||
|     await Future.wait([
 | ||
|       _fetchData(),
 | ||
|       // 下拉刷新时不展示 loading(因为用户主动触发),但仍会更新缓存(成功时)
 | ||
|       _fetchHiddenList(showLoading: false),
 | ||
|     ]);
 | ||
|   }
 | ||
| 
 | ||
|   /// 从 SharedPreferences 读取缓存并设置 hiddenList(如果存在)
 | ||
|   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; // 有缓存则不显示首次loading
 | ||
|         });
 | ||
|       } else {
 | ||
|         // 无缓存:保持 _initialLoadingHidden = true(只在需要显示 loading 时 setState)
 | ||
|         setState(() {
 | ||
|           _initialLoadingHidden = true;
 | ||
|         });
 | ||
|       }
 | ||
|     } catch (e) {
 | ||
|       debugPrint('加载 hidden cache 失败: $e');
 | ||
|       // 出错时认为无缓存
 | ||
|       setState(() {
 | ||
|         _initialLoadingHidden = true;
 | ||
|       });
 | ||
|     }
 | ||
|   }
 | ||
| 
 | ||
|   /// 保存 hiddenList 到 SharedPreferences(调用时确保传入最新 list)
 | ||
|   Future<void> _saveHiddenCache(List<Map<String, dynamic>> list) async {
 | ||
|     try {
 | ||
|       final prefs = await SharedPreferences.getInstance();
 | ||
|       final jsonStr = jsonEncode(list);
 | ||
|       await prefs.setString(_hiddenCacheKey, jsonStr);
 | ||
|     } catch (e) {
 | ||
|       debugPrint('保存 hidden cache 失败: $e');
 | ||
|     }
 | ||
|   }
 | ||
| 
 | ||
|   /// 获取隐患播报列表(带缓存/加载控制逻辑)
 | ||
|   Future<void> _fetchHiddenList({bool showLoading = true}) async {
 | ||
|     // 仅在需要展示 loading 且当前没有数据时 setState 显示 loading indicator
 | ||
|     if (showLoading && hiddenList.isEmpty) {
 | ||
|       setState(() {
 | ||
|         _initialLoadingHidden = true;
 | ||
|       });
 | ||
|     }
 | ||
| 
 | ||
|     try {
 | ||
|       final res = await ApiService.getHiddenRoll().timeout(
 | ||
|         const Duration(seconds: 10),
 | ||
|       );
 | ||
|       final list = (res['hiddenList'] as List).cast<Map<String, dynamic>>();
 | ||
| 
 | ||
|       // 请求成功:覆盖 UI 与缓存(始终覆盖)
 | ||
|       setState(() {
 | ||
|         hiddenList = list;
 | ||
|         _initialLoadingHidden = false;
 | ||
|       });
 | ||
| 
 | ||
|       // 保存缓存(覆盖)
 | ||
|       await _saveHiddenCache(list);
 | ||
|     } catch (e) {
 | ||
|       debugPrint('fetchHiddenList error: $e');
 | ||
| 
 | ||
|       // 请求失败:如果已有缓存(hiddenList 非空),就保留缓存(什么也不做)
 | ||
|       // 如果没有缓存(首次且失败),需要把 loading 关掉(避免一直转圈)
 | ||
|       if (hiddenList.isEmpty) {
 | ||
|         setState(() {
 | ||
|           _initialLoadingHidden = false;
 | ||
|         });
 | ||
|       }
 | ||
|     }
 | ||
|   }
 | ||
| 
 | ||
|   Future<void> _fetchData() async {
 | ||
|     try {
 | ||
|       // “我的工作”数据
 | ||
|       final data = await ApiService.getWork();
 | ||
|       final hidCount = data['hidCount'] as Map<String, dynamic>? ?? {};
 | ||
|       setState(() {
 | ||
|         // ———— 更新 workInfos ————
 | ||
|         workInfos =
 | ||
|             workInfos.map((info) {
 | ||
|               final idx = info['index'] as int;
 | ||
|               final key = _workKey(idx);
 | ||
|               final num = (hidCount[key] ?? 0).toString();
 | ||
|               return {...info, 'num': num};
 | ||
|             }).toList();
 | ||
|       });
 | ||
|       // // 告知BadgeManager去更新“安全巡检”和“八项作业”角标
 | ||
|       // BadgeManager().updateEnvInspectCount();
 | ||
|       // BadgeManager().updateEightWorkCount();
 | ||
| 
 | ||
|       // 拉取其他数据
 | ||
|       final results = await Future.wait([
 | ||
|         ApiService.getUserData(),
 | ||
|         ApiService.getDeptData(),
 | ||
|         ApiService.getSuperviseDeptData(),
 | ||
|       ]);
 | ||
|       final myData = results[0];
 | ||
|       final deptData = results[1];
 | ||
|       final superDeptData = results[2];
 | ||
| 
 | ||
|       //更新页面状态
 | ||
|       setState(() {
 | ||
|         // ———— 从 BadgeManager 拿最新的本页角标 ————
 | ||
|         _safetyEnvironmentalInspection = BadgeManager().envInspectCount;
 | ||
|         _eight_work_count = BadgeManager().eightWorkCount;
 | ||
| 
 | ||
|         // ———— 更新 buttonInfos 中两个角标位置 ————
 | ||
|         buttonInfos =
 | ||
|             buttonInfos.map((info) {
 | ||
|               switch (info['title'] as String) {
 | ||
|                 case '特殊作业':
 | ||
|                   info['unreadCount'] = _eight_work_count;
 | ||
|                   break;
 | ||
|                 case '安全检查':
 | ||
|                   info['unreadCount'] = _safetyEnvironmentalInspection;
 | ||
|                   break;
 | ||
|                 default:
 | ||
|                   // 其它保持原样
 | ||
|                   break;
 | ||
|               }
 | ||
|               return info;
 | ||
|             }).toList();
 | ||
| 
 | ||
|         // ———— 更新排查数据 pcData ————
 | ||
|         pcData =
 | ||
|             [myData, deptData, superDeptData].asMap().entries.map((entry) {
 | ||
|               final idx = entry.key;
 | ||
|               final pdMap = (entry.value['pd'] as Map<String, dynamic>?) ?? {};
 | ||
|               return {
 | ||
|                 'index': idx + 1,
 | ||
|                 'detail': {
 | ||
|                   'jiancha': pdMap['check_count']?.toString() ?? '0',
 | ||
|                   'yinhuan': pdMap['hidden_count']?.toString() ?? '0',
 | ||
|                   'yanshou': pdMap['rectify_count']?.toString() ?? '0',
 | ||
|                 },
 | ||
|               };
 | ||
|             }).toList();
 | ||
|       });
 | ||
|     } catch (e) {
 | ||
|       debugPrint('加载首页数据失败:$e');
 | ||
|     }
 | ||
|   }
 | ||
| 
 | ||
|   @override
 | ||
|   Widget build(BuildContext context) {
 | ||
|     return Scaffold(
 | ||
|       body: RefreshIndicator(
 | ||
|         onRefresh: _onRefresh,
 | ||
|         child: ListView(
 | ||
|           physics: const AlwaysScrollableScrollPhysics(),
 | ||
|           padding: const EdgeInsets.all(10),
 | ||
|           children: [
 | ||
|             ClipRRect(
 | ||
|               borderRadius: BorderRadius.circular(5),
 | ||
|               child: Image.asset('assets/images/banner.jpg', fit: BoxFit.cover),
 | ||
|             ),
 | ||
|             const SizedBox(height: 10),
 | ||
|             _buildIconSection(context),
 | ||
|             const SizedBox(height: 10),
 | ||
|             _buildWorkSection(context),
 | ||
|             const SizedBox(height: 10),
 | ||
|             Container(
 | ||
|               decoration: BoxDecoration(
 | ||
|                 borderRadius: BorderRadius.circular(5),
 | ||
|                 color: Colors.white,
 | ||
|               ),
 | ||
|               child: Column(
 | ||
|                 children: [
 | ||
|                   ListItemFactory.createBuildSimpleSection("隐患播报"),
 | ||
|                   hiddenList.isEmpty
 | ||
|                       ? SizedBox(
 | ||
|                         height: 150,
 | ||
|                         child: Center(child: CircularProgressIndicator()),
 | ||
|                       )
 | ||
|                       : HiddenRollWidget(hiddenList: hiddenList),
 | ||
|                 ],
 | ||
|               ),
 | ||
|             ),
 | ||
|             const SizedBox(height: 10),
 | ||
|             _buildPCDataSection(),
 | ||
|             SizedBox(height: 20),
 | ||
|           ],
 | ||
|         ),
 | ||
|       ),
 | ||
|     );
 | ||
|   }
 | ||
| 
 | ||
|   Widget _buildWorkSection(BuildContext context) {
 | ||
|     return Container(
 | ||
|       decoration: BoxDecoration(
 | ||
|         color: Colors.white,
 | ||
|         borderRadius: BorderRadius.circular(8),
 | ||
|       ),
 | ||
|       child: Column(
 | ||
|         crossAxisAlignment: CrossAxisAlignment.stretch,
 | ||
|         children: [
 | ||
|           ListItemFactory.createBuildSimpleSection("我的工作"),
 | ||
|           Padding(
 | ||
|             padding: const EdgeInsets.fromLTRB(10, 0, 10, 10),
 | ||
|             child: LayoutBuilder(
 | ||
|               builder: (context, constraints) {
 | ||
|                 const spacing = 10.0;
 | ||
|                 final totalWidth = constraints.maxWidth;
 | ||
|                 final itemWidth = (totalWidth - spacing * 2) / 3;
 | ||
|                 return Wrap(
 | ||
|                   spacing: spacing,
 | ||
|                   runSpacing: spacing,
 | ||
|                   children:
 | ||
|                       workInfos.map((info) {
 | ||
|                         return SizedBox(
 | ||
|                           width: itemWidth,
 | ||
|                           child: _buildGridItem(
 | ||
|                             context,
 | ||
|                             icon: info["icon"]!,
 | ||
|                             title: info["num"]!,
 | ||
|                             subtitle: info["detail"]!,
 | ||
|                             index: info["index"]!,
 | ||
|                           ),
 | ||
|                         );
 | ||
|                       }).toList(),
 | ||
|                 );
 | ||
|               },
 | ||
|             ),
 | ||
|           ),
 | ||
|         ],
 | ||
|       ),
 | ||
|     );
 | ||
|   }
 | ||
| 
 | ||
|   Widget _buildPCDataSection() {
 | ||
|     return Container(
 | ||
|       decoration: BoxDecoration(
 | ||
|         color: Colors.white,
 | ||
|         borderRadius: BorderRadius.circular(8),
 | ||
|       ),
 | ||
|       child: Column(
 | ||
|         crossAxisAlignment: CrossAxisAlignment.stretch,
 | ||
|         children: [
 | ||
|           ListItemFactory.createBuildSimpleSection("排查数据"),
 | ||
|           ...pcData.map(_widgetPCDataItem),
 | ||
|         ],
 | ||
|       ),
 | ||
|     );
 | ||
|   }
 | ||
| 
 | ||
|   double _itemWidth(BuildContext context) =>
 | ||
|       (MediaQuery.of(context).size.width - 20) / 4;
 | ||
| 
 | ||
|   Widget _buildIconSection(BuildContext context) {
 | ||
|     return Container(
 | ||
|       padding: const EdgeInsets.symmetric(vertical: 10),
 | ||
|       decoration: BoxDecoration(
 | ||
|         color: Colors.white,
 | ||
|         borderRadius: BorderRadius.circular(8),
 | ||
|       ),
 | ||
|       child: Wrap(
 | ||
|         runSpacing: 16,
 | ||
|         children:
 | ||
|             buttonInfos.asMap().entries.map((entry) {
 | ||
|               final idx = entry.key;
 | ||
|               final info = entry.value;
 | ||
|               return _buildIconButton(
 | ||
|                 context: context,
 | ||
|                 iconPath: info["icon"] as String,
 | ||
|                 label: info["title"] as String,
 | ||
|                 unreadCount: info["unreadCount"] as int,
 | ||
|                 onPressed: () async {
 | ||
|                   switch (idx) {
 | ||
|                     case 0: // 人员信息
 | ||
|                       pushPage(UserinfoPage(), context);
 | ||
|                       break;
 | ||
|                     case 1: // 工作安排
 | ||
|                       pushPage(WorkSetPage(), context);
 | ||
|                       break;
 | ||
|                     case 2: // 风险布控
 | ||
|                       pushPage(RiskControlPage(), context);
 | ||
|                       break;
 | ||
|                     case 3: // 法律法规
 | ||
|                       pushPage(LawsRegulationsPage(), context);
 | ||
|                       break;
 | ||
|                     case 4: // 八项作业
 | ||
|                       await pushPage(WorkTabListPage(), context);
 | ||
|                       break;
 | ||
|                     case 5: // 重点工程管理
 | ||
|                       await pushPage(KeyprojectsTabList(), context);
 | ||
|                       break;
 | ||
|                     case 6: //安全承诺
 | ||
|                       pushPage(SafetyCommitmentPage(), context);
 | ||
|                       break;
 | ||
|                     case 7: // 学习园地
 | ||
|                       pushPage(StudyGardenPage(), context);
 | ||
|                       break;
 | ||
|                     case 8: // 安全检查
 | ||
|                       await pushPage(SafecheckTabList(), context);
 | ||
|                       break;
 | ||
|                     case 9: // 设备巡检
 | ||
|                       pushPage(EquipmentInspectionListPage(), context);
 | ||
|                       break;
 | ||
|                     case 10: // 安全例会
 | ||
|                       pushPage(SafetyMeetingListPage(), context);
 | ||
|                       break;
 | ||
|                     case 11: // NFC巡检
 | ||
|                       pushPage(HomeNfcListPage(), context);
 | ||
|                       break;
 | ||
|                   }
 | ||
| 
 | ||
|                 },
 | ||
|               );
 | ||
|             }).toList(),
 | ||
|       ),
 | ||
|     );
 | ||
|   }
 | ||
| 
 | ||
|   Widget _buildIconButton({
 | ||
|     required BuildContext context,
 | ||
|     required String iconPath,
 | ||
|     required String label,
 | ||
|     required VoidCallback onPressed,
 | ||
|     int unreadCount = 0,
 | ||
|   }) {
 | ||
|     final w = _itemWidth(context);
 | ||
|     return InkWell(
 | ||
|       onTap: onPressed,
 | ||
|       borderRadius: BorderRadius.circular(8),
 | ||
|       child: SizedBox(
 | ||
|         width: w,
 | ||
|         child: Stack(
 | ||
|           clipBehavior: Clip.none,
 | ||
|           children: [
 | ||
|             Align(
 | ||
|               alignment: Alignment.topCenter,
 | ||
|               child: Column(
 | ||
|                 mainAxisSize: MainAxisSize.min,
 | ||
|                 children: [
 | ||
|                   Image.asset(iconPath, width: 40, height: 40),
 | ||
|                   const SizedBox(height: 5),
 | ||
|                   Text(label, style: const TextStyle(fontSize: 13)),
 | ||
|                 ],
 | ||
|               ),
 | ||
|             ),
 | ||
|             if (unreadCount > 0)
 | ||
|               Positioned(
 | ||
|                 right: 20,
 | ||
|                 top: -5,
 | ||
|                 child: Container(
 | ||
|                   padding: const EdgeInsets.symmetric(
 | ||
|                     horizontal: 4,
 | ||
|                     vertical: 2,
 | ||
|                   ),
 | ||
|                   decoration: BoxDecoration(
 | ||
|                     color: Colors.red,
 | ||
|                     borderRadius: BorderRadius.circular(10),
 | ||
|                   ),
 | ||
|                   constraints: const BoxConstraints(
 | ||
|                     minWidth: 16,
 | ||
|                     minHeight: 16,
 | ||
|                   ),
 | ||
|                   child: Center(
 | ||
|                     child: Text(
 | ||
|                       '$unreadCount',
 | ||
|                       style: const TextStyle(
 | ||
|                         color: Colors.white,
 | ||
|                         fontSize: 11,
 | ||
|                         height: 1,
 | ||
|                       ),
 | ||
|                       textAlign: TextAlign.center,
 | ||
|                     ),
 | ||
|                   ),
 | ||
|                 ),
 | ||
|               ),
 | ||
|           ],
 | ||
|         ),
 | ||
|       ),
 | ||
|     );
 | ||
|   }
 | ||
| 
 | ||
|   Widget _widgetPCDataItem(Map<String, dynamic> item) {
 | ||
|     final detail = item["detail"] as Map<String, String>;
 | ||
|     final values = [detail["jiancha"]!, detail["yinhuan"]!, detail["yanshou"]!];
 | ||
|     const labels = ["检查数", "发现隐患数", "已验收隐患数"];
 | ||
|     const colors = [Color(0xFF3283FF), Color(0xFFF37B1D), Color(0xFF41B852)];
 | ||
|     final idx = int.tryParse(item["index"].toString())! - 1;
 | ||
| 
 | ||
|     return Padding(
 | ||
|       padding: const EdgeInsets.fromLTRB(15, 0, 10, 15),
 | ||
|       child: Column(
 | ||
|         children: [
 | ||
|           Row(
 | ||
|             mainAxisAlignment: MainAxisAlignment.spaceBetween,
 | ||
|             children: [
 | ||
|               for (var i = 0; i < 3; i++)
 | ||
|                 Column(
 | ||
|                   children: [
 | ||
|                     Text(
 | ||
|                       values[i],
 | ||
|                       style: TextStyle(
 | ||
|                         color: colors[i],
 | ||
|                         fontSize: 18,
 | ||
|                         fontWeight: FontWeight.bold,
 | ||
|                       ),
 | ||
|                     ),
 | ||
|                     const SizedBox(height: 4),
 | ||
|                     Text(
 | ||
|                       labels[i],
 | ||
|                       style: TextStyle(color: Colors.black38, fontSize: 15),
 | ||
|                     ),
 | ||
|                   ],
 | ||
|                 ),
 | ||
|               Container(
 | ||
|                 color: idx == 0 ? Colors.red : Colors.blue,
 | ||
|                 width: 20,
 | ||
|                 height: 40,
 | ||
|                 child: Column(
 | ||
|                   mainAxisAlignment: MainAxisAlignment.center,
 | ||
|                   children: [
 | ||
|                     Text(
 | ||
|                       tagLabels[idx][0],
 | ||
|                       style: TextStyle(color: Colors.white, fontSize: 12),
 | ||
|                     ),
 | ||
|                     Text(
 | ||
|                       tagLabels[idx][1],
 | ||
|                       style: TextStyle(color: Colors.white, fontSize: 12),
 | ||
|                     ),
 | ||
|                   ],
 | ||
|                 ),
 | ||
|               ),
 | ||
|             ],
 | ||
|           ),
 | ||
|           SizedBox(height: 10),
 | ||
|           Divider(height: 1, color: Colors.black12),
 | ||
|         ],
 | ||
|       ),
 | ||
|     );
 | ||
|   }
 | ||
| 
 | ||
|   Widget _buildGridItem(
 | ||
|     BuildContext context, {
 | ||
|     required String icon,
 | ||
|     required String title,
 | ||
|     required String subtitle,
 | ||
|     required int index,
 | ||
|   }) {
 | ||
|     return GestureDetector(
 | ||
|       onTap: () async {
 | ||
|         if (index == 1) {
 | ||
|           LoadingDialogHelper.show();
 | ||
|           bool isRest= await _getIsRest();
 | ||
|           LoadingDialogHelper.hide();
 | ||
|           if(isRest){
 | ||
|             ToastUtil.showNormal(context, "您已经处于离岗状态中");
 | ||
|             return;
 | ||
|           }
 | ||
|           pushPage(HomeDangerPage(), context);
 | ||
|         } else if (index == 2) {
 | ||
|           pushPage(DangerWaitListPage(DangerType.wait, 2), context);
 | ||
|         } else if (index == 3) {
 | ||
|           pushPage(DangerWaitListPage(DangerType.expired, 3), context);
 | ||
|         } else if (index == 4) {
 | ||
|           pushPage(DangerWaitListPage(DangerType.waitAcceptance, 4), context);
 | ||
|         } else if (index == 5) {
 | ||
|           pushPage(DangerWaitListPage(DangerType.acceptance, 5), context);
 | ||
|         }
 | ||
|       },
 | ||
|       child: Container(
 | ||
|         padding: const EdgeInsets.all(8),
 | ||
|         decoration: BoxDecoration(
 | ||
|           color: const Color(0xfbf9f9ff),
 | ||
|           borderRadius: BorderRadius.circular(6),
 | ||
|         ),
 | ||
|         child: Row(
 | ||
|           children: [
 | ||
|             Image.asset(icon, width: 35, height: 35),
 | ||
|             const SizedBox(width: 5),
 | ||
|             Expanded(
 | ||
|               child: Column(
 | ||
|                 crossAxisAlignment: CrossAxisAlignment.start,
 | ||
|                 children: [
 | ||
|                   Text(
 | ||
|                     title,
 | ||
|                     style: const TextStyle(
 | ||
|                       fontSize: 13,
 | ||
|                       fontWeight: FontWeight.bold,
 | ||
|                     ),
 | ||
|                     maxLines: 1,
 | ||
|                     overflow: TextOverflow.ellipsis,
 | ||
|                   ),
 | ||
|                   const SizedBox(height: 2),
 | ||
|                   Text(
 | ||
|                     subtitle,
 | ||
|                     style: const TextStyle(fontSize: 13, color: Colors.black),
 | ||
|                     maxLines: 1,
 | ||
|                     overflow: TextOverflow.ellipsis,
 | ||
|                   ),
 | ||
|                 ],
 | ||
|               ),
 | ||
|             ),
 | ||
|           ],
 | ||
|         ),
 | ||
|       ),
 | ||
|     );
 | ||
|   }
 | ||
| 
 | ||
| 
 | ||
|   Future<bool> _getIsRest( )async {
 | ||
|     try {
 | ||
|       final raw = await ApiService.getIsRest();
 | ||
|       if (raw['result'] == 'success') {
 | ||
|         if(  raw['ISREST']=="1"){
 | ||
|           return true;
 | ||
|         }else{
 | ||
|           return false;
 | ||
|         }
 | ||
|       }else{
 | ||
|         return false;
 | ||
|       }
 | ||
|     } catch (e) {
 | ||
|       // 出错时可以 Toast 或者在页面上显示错误状态
 | ||
|       print('加载头像数据失败:$e');
 | ||
|       return false;
 | ||
|     }
 | ||
|   }
 | ||
| 
 | ||
| }
 |