flutter_integrated_whb/lib/pages/home/home_page.dart

734 lines
23 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 '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/services/auth_service.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;
// 缓存 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<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();
// 使用初始化加载:先恢复缓存(若存在则直接显示),然后再发起网络请求(成功则覆盖缓存)
_initialLoad();
BadgeManager().initAllModules();
}
/// 首次加载:先恢复缓存(如果有),然后在后台去刷新(只有当无缓存时才显示 loading
Future<void> _initialLoad() async {
final result = await AuthService.checkUpdate();
if (FormUtils.hasValue(result, 'pd')) {
// 有更新 提示更新
Map pd = result['pd'];
CustomAlertDialog.showAlert(
context,
title: '更新通知',
content: pd['UPLOAD_CONTENT'] ?? '',
onConfirm: () async{
final apkUrl = 'http://192.168.1.191:8888/app-release.apk';
await showUpdateConfirmDialog(context, apkUrl: apkUrl);
},
);
return;
}
final corppromiseData = await ApiService.checkSafeCorppromise();
if (corppromiseData['ISSIGN'] == 1) {
// 承诺
CustomAlertDialog.showConfirm(
context,
title: '温馨提示',
content: '有未签署的安全承诺,点击确认前往签署',
force: true,
onConfirm: () {
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (_) => const PromisePage()),
);
},
);
return;
}
await _loadHiddenCache();
// 拉取其他数据 + 隐患列表(当 hiddenList 为空时显示 loading否则不显示
await _fetchData();
await _fetchHiddenList(showLoading: hiddenList.isEmpty);
}
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>? ?? {};
// 告知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(() {
// ———— 更新 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 拿最新的本页角标 ————
_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 9:
pushPage(EquipmentInspectionListPage(), context);
break;
case 8: // 安全检查
await pushPage(SafecheckTabList(), context);
break;
case 10:
pushPage(SafetyMeetingListPage(), context);
break;
case 11:
pushPage(HomeNfcListPage(), context);
break;
}
_onRefresh();
},
);
}).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: 14)),
],
),
),
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 > 99 ? '99+' : '$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: () {
if (index == 1) {
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: 16,
fontWeight: FontWeight.bold,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 4),
Text(
subtitle,
style: const TextStyle(fontSize: 14, color: Colors.black),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
],
),
),
],
),
),
);
}
}