2026-03-12 10:23:21 +08:00
|
|
|
|
// home_page.dart (适配新菜单配置)
|
2025-12-12 09:11:30 +08:00
|
|
|
|
import 'dart:async';
|
|
|
|
|
|
import 'dart:convert';
|
|
|
|
|
|
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';
|
2026-02-28 14:38:07 +08:00
|
|
|
|
import 'package:qhd_prevention/customWidget/custom_alert_dialog.dart';
|
2025-12-12 09:11:30 +08:00
|
|
|
|
import 'package:qhd_prevention/customWidget/custom_button.dart';
|
2026-03-12 10:23:21 +08:00
|
|
|
|
import 'package:qhd_prevention/customWidget/toast_util.dart';
|
2026-02-28 14:38:07 +08:00
|
|
|
|
import 'package:qhd_prevention/http/ApiService.dart';
|
|
|
|
|
|
import 'package:qhd_prevention/pages/home/Study/study_tab_list_page.dart';
|
2026-04-14 16:24:31 +08:00
|
|
|
|
import 'package:qhd_prevention/pages/home/Tap/work_tab_list_page.dart';
|
2026-03-25 16:09:17 +08:00
|
|
|
|
import 'package:qhd_prevention/pages/home/doorAndCar/doorCar_tab_page.dart';
|
2026-04-10 17:25:59 +08:00
|
|
|
|
import 'package:qhd_prevention/pages/home/keyTasks/key_tasks_tab_page.dart';
|
2025-12-12 09:11:30 +08:00
|
|
|
|
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';
|
2026-02-28 14:38:07 +08:00
|
|
|
|
import 'package:qhd_prevention/pages/mine/onboarding_full_page.dart';
|
2025-12-24 16:07:53 +08:00
|
|
|
|
import 'package:qhd_prevention/pages/user/choose_userFirm_page.dart';
|
2025-12-12 09:11:30 +08:00
|
|
|
|
import 'package:qhd_prevention/pages/user/firm_list_page.dart';
|
2026-02-28 14:38:07 +08:00
|
|
|
|
import 'package:qhd_prevention/services/auth_service.dart';
|
2026-03-12 10:23:21 +08:00
|
|
|
|
import 'package:qhd_prevention/services/scan_service.dart';
|
2025-12-12 09:11:30 +08:00
|
|
|
|
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();
|
2026-03-12 10:23:21 +08:00
|
|
|
|
bool _isShowCheckLogin = false;
|
2025-12-12 09:11:30 +08:00
|
|
|
|
|
|
|
|
|
|
int _currentPage = 0;
|
2026-03-12 10:23:21 +08:00
|
|
|
|
bool _isMobileSelected = true;
|
2025-12-12 09:11:30 +08:00
|
|
|
|
|
2026-02-28 14:38:07 +08:00
|
|
|
|
void startScan() async {
|
2026-04-23 17:49:42 +08:00
|
|
|
|
final result = await pushPage(ScanPage(type: ScanType.Onboarding), context);
|
2026-03-12 10:23:21 +08:00
|
|
|
|
if (result == null) return;
|
|
|
|
|
|
ScanService.scan(context, result);
|
2025-12-12 09:11:30 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static const String _hiddenCacheKey = 'hidden_roll_cache';
|
|
|
|
|
|
|
2026-03-12 10:23:21 +08:00
|
|
|
|
// 上面按钮显示状态(与 buttonInfos 顺序对应)
|
2025-12-12 09:11:30 +08:00
|
|
|
|
List<bool> _buttonVisibility = [];
|
|
|
|
|
|
|
2026-03-12 10:23:21 +08:00
|
|
|
|
// 页面模块可见性(由路由驱动)
|
|
|
|
|
|
bool _showNotificationBar = true;
|
|
|
|
|
|
bool _showWorkStats = true;
|
|
|
|
|
|
bool _showCheckList = true;
|
|
|
|
|
|
|
2025-12-12 09:11:30 +08:00
|
|
|
|
List totalList = [];
|
|
|
|
|
|
|
2026-03-25 16:09:17 +08:00
|
|
|
|
// 工作统计(示例)
|
|
|
|
|
|
Map<String, int> workStats = {'total': 0, 'processed': 0, 'pending': 0};
|
|
|
|
|
|
|
|
|
|
|
|
// 检查清单示例
|
|
|
|
|
|
List<dynamic> checkLists = [];
|
2026-04-23 17:49:42 +08:00
|
|
|
|
|
2026-03-25 16:09:17 +08:00
|
|
|
|
// 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),
|
|
|
|
|
|
// },
|
|
|
|
|
|
// ];
|
2025-12-12 09:11:30 +08:00
|
|
|
|
|
|
|
|
|
|
final List<String> _notifications = [
|
|
|
|
|
|
"系统通知:今晚20:00 将进行系统维护,请提前保存数据。",
|
|
|
|
|
|
"安全提示:施工区请佩戴安全帽并系好安全带。",
|
|
|
|
|
|
"公告:本周五集团例会在多功能厅召开,9:00准时开始。",
|
|
|
|
|
|
"提醒:请尽快完成隐患整改清单中的待办项。",
|
|
|
|
|
|
];
|
|
|
|
|
|
int _notifIndex = 0;
|
|
|
|
|
|
late final PageController _notifPageController;
|
|
|
|
|
|
Timer? _notifTimer;
|
|
|
|
|
|
|
2026-03-12 10:23:21 +08:00
|
|
|
|
// 图标按钮定义(顺序固定)
|
2025-12-12 09:11:30 +08:00
|
|
|
|
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},
|
2026-04-23 17:49:42 +08:00
|
|
|
|
{"icon": "assets/images/ico4.png", "title": "隐患治理", "unreadCount": 0},
|
|
|
|
|
|
// 原“隐患处理”改为“隐患治理”
|
2026-03-12 10:23:21 +08:00
|
|
|
|
{"icon": "assets/images/ico5.png", "title": "重点作业", "unreadCount": 0},
|
2025-12-12 09:11:30 +08:00
|
|
|
|
{"icon": "assets/images/ico6.png", "title": "口门门禁", "unreadCount": 0},
|
|
|
|
|
|
{"icon": "assets/images/ico7.png", "title": "入港培训", "unreadCount": 0},
|
|
|
|
|
|
];
|
|
|
|
|
|
|
2026-03-12 10:23:21 +08:00
|
|
|
|
// 浮动 AppBar
|
2025-12-12 09:11:30 +08:00
|
|
|
|
bool _showFloatingAppBar = false;
|
2026-03-12 10:23:21 +08:00
|
|
|
|
static const double _triggerOffset = 30.0;
|
2025-12-12 09:11:30 +08:00
|
|
|
|
static const double _floatingBarHeight = 56.0;
|
|
|
|
|
|
|
2026-03-12 10:23:21 +08:00
|
|
|
|
// 按钮标题 -> 权限标识映射(基于新菜单配置)
|
|
|
|
|
|
final Map<String, String> _titleToPerm = {
|
|
|
|
|
|
"单位管理": "dashboard-Unit-Management",
|
|
|
|
|
|
"现场监管": "dashboard-Site-Supervision",
|
2026-04-23 17:49:42 +08:00
|
|
|
|
"危险作业": "dashboard-Hazardous-Work",
|
2026-03-12 10:23:21 +08:00
|
|
|
|
"隐患治理": "dashboard-Hazard-Management",
|
2026-04-10 17:25:59 +08:00
|
|
|
|
"重点作业": "dashboard-Hazard-Management", // 无对应,暂时留空
|
2026-03-12 10:23:21 +08:00
|
|
|
|
"口门门禁": "dashboard-Gate-Access-Control",
|
|
|
|
|
|
"入港培训": "dashboard-Study-Training",
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 模块权限映射(基于新菜单配置)
|
|
|
|
|
|
final Map<String, String> _modulePerms = {
|
|
|
|
|
|
"notification": "dashboard-roll-notice",
|
|
|
|
|
|
"todoStats": "dashboard-todo-sort",
|
|
|
|
|
|
"checklist": "dashboard-todo-list",
|
|
|
|
|
|
"scan": "dashboard-scan",
|
2026-04-01 17:53:42 +08:00
|
|
|
|
"joinFirm": "dashboard-start-work",
|
2026-03-12 10:23:21 +08:00
|
|
|
|
};
|
2026-02-28 14:38:07 +08:00
|
|
|
|
|
2026-04-23 17:49:42 +08:00
|
|
|
|
int pcType = 1;
|
2026-03-25 16:09:17 +08:00
|
|
|
|
|
2025-12-12 09:11:30 +08:00
|
|
|
|
@override
|
|
|
|
|
|
void initState() {
|
|
|
|
|
|
super.initState();
|
2026-02-28 14:38:07 +08:00
|
|
|
|
_isShowCheckLogin = widget.isChooseFirm;
|
2025-12-12 09:11:30 +08:00
|
|
|
|
|
2026-03-12 10:23:21 +08:00
|
|
|
|
// 初始按钮全隐藏,避免闪烁
|
|
|
|
|
|
_buttonVisibility = List<bool>.filled(buttonInfos.length, false);
|
2025-12-12 09:11:30 +08:00
|
|
|
|
|
2026-03-12 10:23:21 +08:00
|
|
|
|
_notifPageController = PageController(initialPage: 0);
|
2025-12-12 09:11:30 +08:00
|
|
|
|
_notifTimer = Timer.periodic(const Duration(seconds: 3), (timer) {
|
|
|
|
|
|
if (!mounted) return;
|
|
|
|
|
|
final next = (_notifIndex + 1) % _notifications.length;
|
|
|
|
|
|
_notifIndex = next;
|
|
|
|
|
|
try {
|
|
|
|
|
|
_notifPageController.animateToPage(
|
|
|
|
|
|
next,
|
|
|
|
|
|
duration: const Duration(milliseconds: 400),
|
|
|
|
|
|
curve: Curves.easeInOut,
|
|
|
|
|
|
);
|
|
|
|
|
|
} catch (_) {}
|
2026-02-28 14:38:07 +08:00
|
|
|
|
_getNeedSafetyCommitment();
|
2025-12-12 09:11:30 +08:00
|
|
|
|
setState(() {});
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
_scrollController.addListener(_onScroll);
|
|
|
|
|
|
WidgetsBinding.instance.addObserver(this);
|
2026-03-12 10:23:21 +08:00
|
|
|
|
RouteService().addListener(onRouteConfigLoaded);
|
|
|
|
|
|
Future.microtask(() {
|
|
|
|
|
|
_updateModuleAndButtonVisibility();
|
2025-12-12 09:11:30 +08:00
|
|
|
|
});
|
2026-04-23 17:49:42 +08:00
|
|
|
|
if (_isShowCheckLogin) {
|
2026-03-25 16:09:17 +08:00
|
|
|
|
_getToDoWorkList(pcType);
|
|
|
|
|
|
}
|
2026-03-12 10:23:21 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
|
void dispose() {
|
|
|
|
|
|
_notifTimer?.cancel();
|
|
|
|
|
|
_notifPageController.dispose();
|
|
|
|
|
|
_pageController.dispose();
|
|
|
|
|
|
_scrollController.removeListener(_onScroll);
|
|
|
|
|
|
_scrollController.dispose();
|
|
|
|
|
|
WidgetsBinding.instance.removeObserver(this);
|
|
|
|
|
|
try {
|
|
|
|
|
|
RouteService().removeListener(onRouteConfigLoaded);
|
|
|
|
|
|
} catch (_) {}
|
|
|
|
|
|
super.dispose();
|
|
|
|
|
|
}
|
2025-12-12 09:11:30 +08:00
|
|
|
|
|
2026-03-12 10:23:21 +08:00
|
|
|
|
void onRouteConfigLoaded() {
|
|
|
|
|
|
if (!mounted) return;
|
|
|
|
|
|
_updateModuleAndButtonVisibility();
|
2026-03-05 16:12:47 +08:00
|
|
|
|
}
|
2026-03-06 16:15:20 +08:00
|
|
|
|
|
2026-03-12 10:23:21 +08:00
|
|
|
|
/// 根据路由配置更新按钮和模块可见性
|
|
|
|
|
|
void _updateModuleAndButtonVisibility() {
|
|
|
|
|
|
final routeService = RouteService();
|
|
|
|
|
|
final mainTabs = routeService.mainTabs;
|
|
|
|
|
|
if (mainTabs.isEmpty) {
|
|
|
|
|
|
// 路由未加载,保持全隐藏
|
2026-02-28 14:38:07 +08:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
2026-03-12 10:23:21 +08:00
|
|
|
|
|
|
|
|
|
|
// 更新按钮可见性
|
2026-04-23 17:49:42 +08:00
|
|
|
|
final List<bool> newVisibility = List<bool>.filled(
|
|
|
|
|
|
buttonInfos.length,
|
|
|
|
|
|
false,
|
|
|
|
|
|
);
|
2026-03-12 10:23:21 +08:00
|
|
|
|
for (int i = 0; i < buttonInfos.length; i++) {
|
|
|
|
|
|
final title = buttonInfos[i]['title'] as String;
|
|
|
|
|
|
final perm = _titleToPerm[title];
|
|
|
|
|
|
if (perm == null || perm.isEmpty) continue;
|
|
|
|
|
|
try {
|
|
|
|
|
|
newVisibility[i] = routeService.hasPerm(perm);
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
debugPrint('权限检查异常: $perm, $e');
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 更新模块可见性
|
|
|
|
|
|
final bool showNotif = routeService.hasPerm(_modulePerms['notification']!);
|
|
|
|
|
|
final bool showTodo = routeService.hasPerm(_modulePerms['todoStats']!);
|
|
|
|
|
|
final bool showChecklist = routeService.hasPerm(_modulePerms['checklist']!);
|
|
|
|
|
|
|
|
|
|
|
|
setState(() {
|
|
|
|
|
|
_buttonVisibility = newVisibility;
|
|
|
|
|
|
_showNotificationBar = showNotif;
|
|
|
|
|
|
_showWorkStats = showTodo;
|
|
|
|
|
|
_showCheckList = showChecklist;
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Future<void> _getNeedSafetyCommitment() async {
|
|
|
|
|
|
if (_isShowCheckLogin) return;
|
2026-02-28 14:38:07 +08:00
|
|
|
|
final prefs = await SharedPreferences.getInstance();
|
|
|
|
|
|
final phone = prefs.getString('savePhone') ?? '';
|
|
|
|
|
|
final pwd = prefs.getString('savePass') ?? '';
|
|
|
|
|
|
final result = await AuthApi.userLoginCheckFirm({'phone': phone});
|
|
|
|
|
|
if (result['success']) {
|
|
|
|
|
|
final resData = result['data'];
|
|
|
|
|
|
List firmList = resData['userCorpInfoCOList'] ?? [];
|
|
|
|
|
|
if (firmList.isNotEmpty) {
|
|
|
|
|
|
_isShowCheckLogin = true;
|
|
|
|
|
|
CustomAlertDialog.showAlert(
|
|
|
|
|
|
context,
|
|
|
|
|
|
title: '温馨提示',
|
|
|
|
|
|
content: '您的入职申请已通过',
|
|
|
|
|
|
confirmText: '立即入职',
|
|
|
|
|
|
onConfirm: () async {
|
|
|
|
|
|
Map data = {'unitId': firmList.first['corpinfoId'] ?? ''};
|
|
|
|
|
|
final res = await AuthService.gbsLogin(phone, pwd, data);
|
|
|
|
|
|
LoadingDialogHelper.hide();
|
|
|
|
|
|
if (res['success'] == true) {
|
|
|
|
|
|
Navigator.pushReplacement(
|
|
|
|
|
|
context,
|
|
|
|
|
|
MaterialPageRoute(
|
|
|
|
|
|
builder: (_) => const MainPage(isChooseFirm: true),
|
|
|
|
|
|
),
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
_isShowCheckLogin = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-12 09:11:30 +08:00
|
|
|
|
void _onScroll() {
|
2026-04-23 17:49:42 +08:00
|
|
|
|
final offset =
|
|
|
|
|
|
_scrollController.hasClients ? _scrollController.offset : 0.0;
|
2025-12-12 09:11:30 +08:00
|
|
|
|
final shouldShow = offset >= _triggerOffset;
|
|
|
|
|
|
if (shouldShow != _showFloatingAppBar) {
|
2026-03-12 10:23:21 +08:00
|
|
|
|
setState(() => _showFloatingAppBar = shouldShow);
|
2025-12-12 09:11:30 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
|
Future<void> onVisible() async {
|
|
|
|
|
|
final current = CurrentTabNotifier.of(context)?.currentIndex ?? 0;
|
|
|
|
|
|
const myIndex = 0;
|
2026-03-12 10:23:21 +08:00
|
|
|
|
if (current != myIndex) return;
|
|
|
|
|
|
// 可在此刷新角标等
|
2025-12-12 09:11:30 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-12 10:23:21 +08:00
|
|
|
|
Future<void> _onRefresh() async {
|
|
|
|
|
|
await Future.delayed(const Duration(seconds: 1));
|
2025-12-12 09:11:30 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@override
|
2026-03-12 10:23:21 +08:00
|
|
|
|
Widget build(BuildContext context) {
|
|
|
|
|
|
const double notificationHeightConst = 60.0;
|
|
|
|
|
|
double bannerHeight = 738 / 1125 * screenWidth(context);
|
2025-12-12 09:11:30 +08:00
|
|
|
|
|
2026-03-12 10:23:21 +08:00
|
|
|
|
// 计算可见按钮列表
|
|
|
|
|
|
final visibleButtons = <Map<String, dynamic>>[];
|
|
|
|
|
|
for (int i = 0; i < buttonInfos.length; i++) {
|
|
|
|
|
|
if (i < _buttonVisibility.length && _buttonVisibility[i]) {
|
|
|
|
|
|
visibleButtons.add(buttonInfos[i]);
|
2025-12-12 09:11:30 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-12 10:23:21 +08:00
|
|
|
|
final double iconSectionHeight =
|
2026-04-23 17:49:42 +08:00
|
|
|
|
visibleButtons.isEmpty
|
|
|
|
|
|
? 0.0
|
|
|
|
|
|
: (visibleButtons.length <= 4 ? 120.0 : 220.0);
|
2025-12-12 09:11:30 +08:00
|
|
|
|
|
2026-03-12 10:23:21 +08:00
|
|
|
|
final double notificationHeight =
|
2026-04-23 17:49:42 +08:00
|
|
|
|
(_showNotificationBar && iconSectionHeight >= 0)
|
|
|
|
|
|
? notificationHeightConst
|
|
|
|
|
|
: 0.0;
|
2025-12-12 09:11:30 +08:00
|
|
|
|
|
2026-03-12 10:23:21 +08:00
|
|
|
|
final double extraSpacing = _showNotificationBar ? 60.0 : 12.0;
|
2025-12-12 09:11:30 +08:00
|
|
|
|
final double stackBottom =
|
2026-04-23 17:49:42 +08:00
|
|
|
|
bannerHeight -
|
|
|
|
|
|
(iconSectionHeight * 0.4) +
|
|
|
|
|
|
iconSectionHeight +
|
|
|
|
|
|
extraSpacing;
|
2025-12-12 09:11:30 +08:00
|
|
|
|
final double statusBar = MediaQuery.of(context).padding.top;
|
2026-03-12 10:23:21 +08:00
|
|
|
|
|
|
|
|
|
|
final topPadding = Platform.isIOS ? 0 : 14;
|
2025-12-12 09:11:30 +08:00
|
|
|
|
|
|
|
|
|
|
return PopScope(
|
|
|
|
|
|
canPop: false,
|
|
|
|
|
|
child: Scaffold(
|
|
|
|
|
|
extendBodyBehindAppBar: true,
|
|
|
|
|
|
body: Stack(
|
|
|
|
|
|
children: [
|
|
|
|
|
|
RefreshIndicator(
|
2026-03-12 10:23:21 +08:00
|
|
|
|
backgroundColor: Colors.white,
|
|
|
|
|
|
color: Colors.blue,
|
2025-12-12 09:11:30 +08:00
|
|
|
|
onRefresh: _onRefresh,
|
|
|
|
|
|
child: ListView(
|
|
|
|
|
|
controller: _scrollController,
|
|
|
|
|
|
physics: const AlwaysScrollableScrollPhysics(),
|
|
|
|
|
|
padding: const EdgeInsets.all(0),
|
|
|
|
|
|
children: [
|
|
|
|
|
|
SizedBox(
|
|
|
|
|
|
height: stackBottom,
|
|
|
|
|
|
child: Stack(
|
|
|
|
|
|
clipBehavior: Clip.none,
|
|
|
|
|
|
children: [
|
2026-03-12 10:23:21 +08:00
|
|
|
|
// Banner
|
2025-12-12 09:11:30 +08:00
|
|
|
|
Positioned(
|
|
|
|
|
|
top: 0,
|
|
|
|
|
|
left: 0,
|
|
|
|
|
|
right: 0,
|
|
|
|
|
|
height: bannerHeight,
|
|
|
|
|
|
child: _buildBannerSection(bannerHeight),
|
|
|
|
|
|
),
|
|
|
|
|
|
|
2026-03-12 10:23:21 +08:00
|
|
|
|
// 通知栏
|
|
|
|
|
|
if (_showNotificationBar)
|
|
|
|
|
|
Positioned(
|
|
|
|
|
|
left: 10,
|
|
|
|
|
|
right: 10,
|
2026-04-23 17:49:42 +08:00
|
|
|
|
top:
|
|
|
|
|
|
(bannerHeight - (iconSectionHeight * 0.4)) +
|
|
|
|
|
|
iconSectionHeight -
|
|
|
|
|
|
10,
|
2026-03-12 10:23:21 +08:00
|
|
|
|
height: notificationHeight,
|
2026-04-23 17:49:42 +08:00
|
|
|
|
child: _buildNotificationBar(
|
|
|
|
|
|
notificationHeight - 2,
|
|
|
|
|
|
),
|
2026-03-12 10:23:21 +08:00
|
|
|
|
),
|
2025-12-12 09:11:30 +08:00
|
|
|
|
|
2026-03-12 10:23:21 +08:00
|
|
|
|
// 图标区
|
2025-12-12 09:11:30 +08:00
|
|
|
|
Positioned(
|
|
|
|
|
|
left: 10,
|
|
|
|
|
|
right: 10,
|
2026-03-12 10:23:21 +08:00
|
|
|
|
top: bannerHeight - (iconSectionHeight * 0.4),
|
2025-12-12 09:11:30 +08:00
|
|
|
|
height: iconSectionHeight,
|
2026-04-23 17:49:42 +08:00
|
|
|
|
child: _buildIconSection(
|
|
|
|
|
|
context,
|
|
|
|
|
|
visibleButtons,
|
|
|
|
|
|
iconSectionHeight,
|
|
|
|
|
|
),
|
2025-12-12 09:11:30 +08:00
|
|
|
|
),
|
|
|
|
|
|
],
|
|
|
|
|
|
),
|
|
|
|
|
|
),
|
2026-03-12 10:23:21 +08:00
|
|
|
|
|
|
|
|
|
|
if (widget.isChooseFirm && _showWorkStats) ...[
|
2025-12-12 09:11:30 +08:00
|
|
|
|
Padding(
|
2026-04-23 17:49:42 +08:00
|
|
|
|
padding: const EdgeInsets.symmetric(
|
|
|
|
|
|
horizontal: 10,
|
|
|
|
|
|
vertical: 10,
|
|
|
|
|
|
),
|
2025-12-12 09:11:30 +08:00
|
|
|
|
child: _buildWorkStatsSection(),
|
|
|
|
|
|
),
|
2026-03-12 10:23:21 +08:00
|
|
|
|
],
|
2025-12-12 09:11:30 +08:00
|
|
|
|
|
2026-03-12 10:23:21 +08:00
|
|
|
|
if (widget.isChooseFirm && _showCheckList) ...[
|
2025-12-12 09:11:30 +08:00
|
|
|
|
Padding(
|
|
|
|
|
|
padding: const EdgeInsets.symmetric(horizontal: 10),
|
|
|
|
|
|
child: _buildCheckListSection(),
|
|
|
|
|
|
),
|
|
|
|
|
|
const SizedBox(height: 20),
|
2026-02-28 14:38:07 +08:00
|
|
|
|
],
|
2025-12-12 09:11:30 +08:00
|
|
|
|
],
|
|
|
|
|
|
),
|
|
|
|
|
|
),
|
2026-03-12 10:23:21 +08:00
|
|
|
|
|
|
|
|
|
|
// 浮动 AppBar
|
2025-12-12 09:11:30 +08:00
|
|
|
|
Positioned(
|
|
|
|
|
|
top: 0,
|
|
|
|
|
|
left: 0,
|
|
|
|
|
|
right: 0,
|
|
|
|
|
|
child: AnimatedContainer(
|
|
|
|
|
|
duration: const Duration(milliseconds: 0),
|
2026-04-23 17:49:42 +08:00
|
|
|
|
height:
|
|
|
|
|
|
_showFloatingAppBar ? (statusBar + _floatingBarHeight) : 0,
|
2025-12-12 09:11:30 +08:00
|
|
|
|
decoration: BoxDecoration(
|
2026-04-23 17:49:42 +08:00
|
|
|
|
color:
|
|
|
|
|
|
_showFloatingAppBar
|
|
|
|
|
|
? h_AppBarColor()
|
|
|
|
|
|
: Colors.transparent,
|
|
|
|
|
|
boxShadow:
|
|
|
|
|
|
_showFloatingAppBar
|
|
|
|
|
|
? [
|
|
|
|
|
|
BoxShadow(
|
|
|
|
|
|
color: Colors.black.withOpacity(0.08),
|
|
|
|
|
|
blurRadius: 6,
|
|
|
|
|
|
),
|
|
|
|
|
|
]
|
|
|
|
|
|
: [],
|
2025-12-12 09:11:30 +08:00
|
|
|
|
),
|
|
|
|
|
|
child: SafeArea(
|
|
|
|
|
|
bottom: false,
|
|
|
|
|
|
child: Opacity(
|
|
|
|
|
|
opacity: _showFloatingAppBar ? 1.0 : 0.0,
|
2026-04-23 17:49:42 +08:00
|
|
|
|
child: SizedBox(
|
|
|
|
|
|
height: _floatingBarHeight,
|
|
|
|
|
|
child: const SizedBox(),
|
|
|
|
|
|
),
|
2025-12-12 09:11:30 +08:00
|
|
|
|
),
|
|
|
|
|
|
),
|
|
|
|
|
|
),
|
|
|
|
|
|
),
|
2026-03-12 10:23:21 +08:00
|
|
|
|
|
|
|
|
|
|
// 顶部中央标题
|
2025-12-12 09:11:30 +08:00
|
|
|
|
Positioned(
|
2026-03-12 10:23:21 +08:00
|
|
|
|
top: statusBar + topPadding,
|
2025-12-12 09:11:30 +08:00
|
|
|
|
width: screenWidth(context),
|
2026-02-28 14:38:07 +08:00
|
|
|
|
child: Center(
|
|
|
|
|
|
child: Text(
|
|
|
|
|
|
'秦港-相关方安全管理',
|
2026-04-23 17:49:42 +08:00
|
|
|
|
style: TextStyle(
|
|
|
|
|
|
fontSize: 19,
|
|
|
|
|
|
fontWeight: FontWeight.w600,
|
|
|
|
|
|
color: Colors.white,
|
|
|
|
|
|
),
|
2025-12-12 09:11:30 +08:00
|
|
|
|
),
|
2026-02-28 14:38:07 +08:00
|
|
|
|
),
|
2025-12-12 09:11:30 +08:00
|
|
|
|
),
|
2026-03-12 10:23:21 +08:00
|
|
|
|
|
|
|
|
|
|
// 右上角图标(扫码、加入企业)
|
2025-12-12 09:11:30 +08:00
|
|
|
|
_buildFixedTopIcons(context),
|
|
|
|
|
|
],
|
|
|
|
|
|
),
|
|
|
|
|
|
),
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-24 16:07:53 +08:00
|
|
|
|
Future<void> _joinFirm() async {
|
2026-02-28 14:38:07 +08:00
|
|
|
|
pushPage(FirmListPage(isBack: true), context);
|
2025-12-24 16:07:53 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-12 09:11:30 +08:00
|
|
|
|
Widget _buildFixedTopIcons(BuildContext context) {
|
|
|
|
|
|
final double statusBar = MediaQuery.of(context).padding.top;
|
|
|
|
|
|
final double topOffset = statusBar + 12;
|
|
|
|
|
|
|
2026-03-12 10:23:21 +08:00
|
|
|
|
final routeService = RouteService();
|
|
|
|
|
|
final bool showScan = routeService.hasPerm(_modulePerms['scan']!);
|
2026-04-01 17:53:42 +08:00
|
|
|
|
final bool showJoin = routeService.hasPerm(_modulePerms['joinFirm']!);
|
2026-03-12 10:23:21 +08:00
|
|
|
|
|
|
|
|
|
|
final List<Widget> children = [];
|
|
|
|
|
|
if (showScan) {
|
|
|
|
|
|
children.add(
|
|
|
|
|
|
GestureDetector(
|
|
|
|
|
|
onTap: startScan,
|
|
|
|
|
|
child: Container(
|
|
|
|
|
|
width: 30,
|
|
|
|
|
|
height: 30,
|
|
|
|
|
|
alignment: Alignment.center,
|
2026-04-23 17:49:42 +08:00
|
|
|
|
child: Image.asset(
|
|
|
|
|
|
"assets/icon-apps/home_saoyisao.png",
|
|
|
|
|
|
width: 20,
|
|
|
|
|
|
height: 20,
|
|
|
|
|
|
),
|
2026-03-12 10:23:21 +08:00
|
|
|
|
),
|
|
|
|
|
|
),
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
if (showJoin) {
|
|
|
|
|
|
if (children.isNotEmpty) children.add(const SizedBox(width: 8));
|
|
|
|
|
|
children.add(
|
|
|
|
|
|
GestureDetector(
|
|
|
|
|
|
onTap: _joinFirm,
|
|
|
|
|
|
child: Container(
|
|
|
|
|
|
width: 30,
|
|
|
|
|
|
height: 30,
|
|
|
|
|
|
alignment: Alignment.center,
|
2026-04-23 17:49:42 +08:00
|
|
|
|
child: Image.asset(
|
|
|
|
|
|
"assets/icon-apps/home_add.png",
|
|
|
|
|
|
width: 20,
|
|
|
|
|
|
height: 20,
|
|
|
|
|
|
),
|
2026-03-12 10:23:21 +08:00
|
|
|
|
),
|
|
|
|
|
|
),
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-23 17:49:42 +08:00
|
|
|
|
return Positioned(top: topOffset, right: 8, child: Row(children: children));
|
2025-12-12 09:11:30 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Widget _buildNotificationBar(double notificationHeight) {
|
|
|
|
|
|
return Material(
|
|
|
|
|
|
color: Colors.transparent,
|
|
|
|
|
|
child: Container(
|
|
|
|
|
|
height: notificationHeight,
|
|
|
|
|
|
decoration: BoxDecoration(
|
2026-03-12 10:23:21 +08:00
|
|
|
|
color: const Color(0xFFE6F5FF),
|
2025-12-12 09:11:30 +08:00
|
|
|
|
borderRadius: BorderRadius.circular(5),
|
2026-03-12 10:23:21 +08:00
|
|
|
|
border: Border.all(color: Colors.white, width: 1),
|
2025-12-12 09:11:30 +08:00
|
|
|
|
boxShadow: [
|
2026-04-23 17:49:42 +08:00
|
|
|
|
BoxShadow(
|
|
|
|
|
|
color: Colors.black.withOpacity(0.05),
|
|
|
|
|
|
blurRadius: 6,
|
|
|
|
|
|
offset: const Offset(0, 2),
|
|
|
|
|
|
),
|
2025-12-12 09:11:30 +08:00
|
|
|
|
],
|
|
|
|
|
|
),
|
2026-03-12 10:23:21 +08:00
|
|
|
|
child: Row(
|
2025-12-12 09:11:30 +08:00
|
|
|
|
children: [
|
2026-03-12 10:23:21 +08:00
|
|
|
|
const SizedBox(width: 12),
|
|
|
|
|
|
Image.asset('assets/images/ico8.png', width: 30, height: 25),
|
|
|
|
|
|
const SizedBox(width: 12),
|
|
|
|
|
|
Expanded(
|
|
|
|
|
|
child: SizedBox(
|
|
|
|
|
|
height: notificationHeight,
|
|
|
|
|
|
child: PageView.builder(
|
|
|
|
|
|
controller: _notifPageController,
|
|
|
|
|
|
scrollDirection: Axis.vertical,
|
|
|
|
|
|
physics: const NeverScrollableScrollPhysics(),
|
|
|
|
|
|
itemCount: _notifications.length,
|
|
|
|
|
|
itemBuilder: (context, index) {
|
|
|
|
|
|
return Align(
|
|
|
|
|
|
alignment: Alignment.centerLeft,
|
|
|
|
|
|
child: Padding(
|
|
|
|
|
|
padding: const EdgeInsets.only(right: 8.0),
|
|
|
|
|
|
child: Text(
|
|
|
|
|
|
_notifications[index],
|
|
|
|
|
|
maxLines: 1,
|
|
|
|
|
|
overflow: TextOverflow.ellipsis,
|
2026-04-23 17:49:42 +08:00
|
|
|
|
style: const TextStyle(
|
|
|
|
|
|
color: Colors.black87,
|
|
|
|
|
|
fontSize: 14,
|
|
|
|
|
|
),
|
2026-03-12 10:23:21 +08:00
|
|
|
|
),
|
|
|
|
|
|
),
|
|
|
|
|
|
);
|
|
|
|
|
|
},
|
2025-12-12 09:11:30 +08:00
|
|
|
|
),
|
2026-03-12 10:23:21 +08:00
|
|
|
|
),
|
2025-12-12 09:11:30 +08:00
|
|
|
|
),
|
2026-03-12 10:23:21 +08:00
|
|
|
|
const Icon(Icons.chevron_right, color: Colors.black26),
|
|
|
|
|
|
const SizedBox(width: 8),
|
2025-12-12 09:11:30 +08:00
|
|
|
|
],
|
|
|
|
|
|
),
|
|
|
|
|
|
),
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Widget _buildBannerSection(double bannerHeight) {
|
2026-03-12 10:23:21 +08:00
|
|
|
|
return Image.asset(
|
|
|
|
|
|
"assets/images/banner.png",
|
|
|
|
|
|
width: MediaQuery.of(context).size.width,
|
|
|
|
|
|
height: bannerHeight,
|
|
|
|
|
|
fit: BoxFit.cover,
|
2025-12-12 09:11:30 +08:00
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-23 17:49:42 +08:00
|
|
|
|
Widget _buildIconSection(
|
|
|
|
|
|
BuildContext context,
|
|
|
|
|
|
List<Map<String, dynamic>> visibleButtons,
|
|
|
|
|
|
double height,
|
|
|
|
|
|
) {
|
2026-03-12 10:23:21 +08:00
|
|
|
|
if (RouteService().mainTabs.isNotEmpty && visibleButtons.isEmpty) {
|
|
|
|
|
|
return Container(
|
|
|
|
|
|
padding: const EdgeInsets.symmetric(vertical: 30, horizontal: 5),
|
|
|
|
|
|
decoration: BoxDecoration(
|
|
|
|
|
|
color: Colors.white,
|
|
|
|
|
|
borderRadius: BorderRadius.circular(12),
|
2026-04-23 17:49:42 +08:00
|
|
|
|
boxShadow: const [
|
|
|
|
|
|
BoxShadow(
|
|
|
|
|
|
color: Colors.black12,
|
|
|
|
|
|
blurRadius: 6,
|
|
|
|
|
|
offset: Offset(0, 2),
|
|
|
|
|
|
),
|
|
|
|
|
|
],
|
2026-03-12 10:23:21 +08:00
|
|
|
|
),
|
|
|
|
|
|
child: const Center(child: Text('暂无权限访问的功能')),
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
final hasSecondRow = visibleButtons.length > 4;
|
|
|
|
|
|
final double containerMinHeight = height > 0 ? height : 120.0;
|
2025-12-12 09:11:30 +08:00
|
|
|
|
|
|
|
|
|
|
return Container(
|
|
|
|
|
|
padding: const EdgeInsets.symmetric(vertical: 18, horizontal: 5),
|
2026-03-12 10:23:21 +08:00
|
|
|
|
constraints: BoxConstraints(minHeight: containerMinHeight),
|
2025-12-12 09:11:30 +08:00
|
|
|
|
decoration: BoxDecoration(
|
|
|
|
|
|
color: Colors.white,
|
|
|
|
|
|
borderRadius: BorderRadius.circular(12),
|
2026-04-23 17:49:42 +08:00
|
|
|
|
boxShadow: const [
|
|
|
|
|
|
BoxShadow(color: Colors.black12, blurRadius: 6, offset: Offset(0, 2)),
|
2025-12-12 09:11:30 +08:00
|
|
|
|
],
|
|
|
|
|
|
),
|
2026-04-23 17:49:42 +08:00
|
|
|
|
child:
|
|
|
|
|
|
widget.isChooseFirm
|
|
|
|
|
|
? Column(
|
|
|
|
|
|
mainAxisSize: MainAxisSize.min,
|
|
|
|
|
|
children: [
|
|
|
|
|
|
Row(
|
|
|
|
|
|
children: List.generate(4, (i) {
|
|
|
|
|
|
if (i < visibleButtons.length) {
|
|
|
|
|
|
return Expanded(
|
|
|
|
|
|
child: Center(
|
|
|
|
|
|
child: _buildIconButton(visibleButtons[i], context),
|
|
|
|
|
|
),
|
|
|
|
|
|
);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
return const Expanded(child: SizedBox());
|
|
|
|
|
|
}
|
|
|
|
|
|
}),
|
|
|
|
|
|
),
|
|
|
|
|
|
if (hasSecondRow) const SizedBox(height: 20),
|
|
|
|
|
|
if (hasSecondRow)
|
|
|
|
|
|
Row(
|
|
|
|
|
|
children: List.generate(4, (i) {
|
|
|
|
|
|
final idx = 4 + i;
|
|
|
|
|
|
if (idx < visibleButtons.length) {
|
|
|
|
|
|
return Expanded(
|
|
|
|
|
|
child: Center(
|
|
|
|
|
|
child: _buildIconButton(
|
|
|
|
|
|
visibleButtons[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(isBack: true), context),
|
|
|
|
|
|
),
|
|
|
|
|
|
],
|
|
|
|
|
|
),
|
|
|
|
|
|
),
|
2025-12-12 09:11:30 +08:00
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Widget _buildIconButton(Map<String, dynamic> info, BuildContext context) {
|
2026-04-23 17:49:42 +08:00
|
|
|
|
final int badgeNum = info['unreadCount'] as int;
|
|
|
|
|
|
|
2025-12-12 09:11:30 +08:00
|
|
|
|
return GestureDetector(
|
2026-04-23 17:49:42 +08:00
|
|
|
|
onTap: () {
|
|
|
|
|
|
_handleIconTap(info['title']);
|
|
|
|
|
|
},
|
2025-12-12 09:11:30 +08:00
|
|
|
|
child: Column(
|
2026-04-23 17:49:42 +08:00
|
|
|
|
mainAxisSize: MainAxisSize.min,
|
2025-12-12 09:11:30 +08:00
|
|
|
|
children: [
|
2026-04-23 17:49:42 +08:00
|
|
|
|
Stack(
|
|
|
|
|
|
clipBehavior: Clip.none,
|
|
|
|
|
|
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),
|
|
|
|
|
|
),
|
|
|
|
|
|
),
|
|
|
|
|
|
if (badgeNum > 0)
|
|
|
|
|
|
Positioned(
|
|
|
|
|
|
top: -5,
|
|
|
|
|
|
right: -5,
|
|
|
|
|
|
child: Container(
|
|
|
|
|
|
padding:
|
|
|
|
|
|
const EdgeInsets.symmetric(horizontal: 4, vertical: 2),
|
|
|
|
|
|
decoration: const BoxDecoration(
|
|
|
|
|
|
color: Colors.red,
|
|
|
|
|
|
shape: BoxShape.circle,
|
|
|
|
|
|
),
|
|
|
|
|
|
constraints:
|
|
|
|
|
|
const BoxConstraints(minWidth: 16, minHeight: 16),
|
|
|
|
|
|
child: Center(
|
|
|
|
|
|
child: Text(
|
|
|
|
|
|
badgeNum > 99 ? '99+' : badgeNum.toString(),
|
|
|
|
|
|
style: const TextStyle(
|
|
|
|
|
|
color: Colors.white, fontSize: 10, height: 1),
|
|
|
|
|
|
),
|
|
|
|
|
|
),
|
|
|
|
|
|
),
|
|
|
|
|
|
),
|
|
|
|
|
|
],
|
2025-12-12 09:11:30 +08:00
|
|
|
|
),
|
|
|
|
|
|
const SizedBox(height: 6),
|
2026-04-23 17:49:42 +08:00
|
|
|
|
Padding(
|
|
|
|
|
|
padding: const EdgeInsets.symmetric(horizontal: 6.0),
|
2025-12-12 09:11:30 +08:00
|
|
|
|
child: Text(
|
|
|
|
|
|
info['title'],
|
2026-04-23 17:49:42 +08:00
|
|
|
|
style: const TextStyle(fontSize: 13, color: Colors.black87),
|
2025-12-12 09:11:30 +08:00
|
|
|
|
textAlign: TextAlign.center,
|
|
|
|
|
|
maxLines: 2,
|
|
|
|
|
|
overflow: TextOverflow.ellipsis,
|
|
|
|
|
|
),
|
|
|
|
|
|
),
|
|
|
|
|
|
],
|
|
|
|
|
|
),
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-23 17:59:31 +08:00
|
|
|
|
Future<void> _handleIconTap(String title) async {
|
2025-12-12 09:11:30 +08:00
|
|
|
|
switch (title) {
|
|
|
|
|
|
case "单位管理":
|
2026-04-23 17:59:31 +08:00
|
|
|
|
await pushPage(UnitTabPage(), context);
|
2025-12-12 09:11:30 +08:00
|
|
|
|
break;
|
|
|
|
|
|
case "入港培训":
|
2026-04-23 17:59:31 +08:00
|
|
|
|
await pushPage(StudyTabListPage(), context);
|
2025-12-12 09:11:30 +08:00
|
|
|
|
break;
|
2026-03-25 16:09:17 +08:00
|
|
|
|
case "口门门禁":
|
2026-04-23 17:59:31 +08:00
|
|
|
|
await pushPage(DoorcarTabPage(), context);
|
2026-03-25 16:09:17 +08:00
|
|
|
|
break;
|
2026-04-10 17:25:59 +08:00
|
|
|
|
case "重点作业":
|
2026-04-23 17:59:31 +08:00
|
|
|
|
await pushPage(KeyTasksTabPage(), context);
|
2026-04-23 17:49:42 +08:00
|
|
|
|
break;
|
2026-04-23 17:59:31 +08:00
|
|
|
|
case "危险作业":
|
|
|
|
|
|
await pushPage(WorkTabListPage(), context);
|
|
|
|
|
|
break;
|
2025-12-12 09:11:30 +08:00
|
|
|
|
default:
|
2026-03-12 10:23:21 +08:00
|
|
|
|
ToastUtil.showNormal(context, '功能开发中...');
|
2025-12-12 09:11:30 +08:00
|
|
|
|
break;
|
|
|
|
|
|
}
|
2026-04-23 17:49:42 +08:00
|
|
|
|
_getToDoWorkList(pcType);
|
2026-04-23 17:59:31 +08:00
|
|
|
|
// _getHiddenIssuesNum();
|
2025-12-12 09:11:30 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Widget _buildWorkStatsSection() {
|
|
|
|
|
|
return Container(
|
2026-03-25 16:09:17 +08:00
|
|
|
|
padding: const EdgeInsets.all(15),
|
2025-12-12 09:11:30 +08:00
|
|
|
|
decoration: BoxDecoration(
|
|
|
|
|
|
color: Colors.white,
|
|
|
|
|
|
borderRadius: BorderRadius.circular(12),
|
2026-03-25 16:09:17 +08:00
|
|
|
|
boxShadow: const [
|
|
|
|
|
|
BoxShadow(color: Colors.black12, blurRadius: 6, offset: Offset(0, 2)),
|
|
|
|
|
|
],
|
2025-12-12 09:11:30 +08:00
|
|
|
|
),
|
|
|
|
|
|
child: Column(
|
|
|
|
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
|
|
|
|
children: [
|
2026-03-25 16:09:17 +08:00
|
|
|
|
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),
|
2025-12-12 09:11:30 +08:00
|
|
|
|
Container(
|
|
|
|
|
|
width: screenWidth(context),
|
2026-03-25 16:09:17 +08:00
|
|
|
|
height: 36,
|
|
|
|
|
|
decoration: BoxDecoration(
|
|
|
|
|
|
color: Colors.white,
|
|
|
|
|
|
borderRadius: BorderRadius.circular(20),
|
|
|
|
|
|
border: Border.all(color: Colors.grey[300]!, width: 1),
|
|
|
|
|
|
),
|
2025-12-12 09:11:30 +08:00
|
|
|
|
child: Row(
|
|
|
|
|
|
children: [
|
|
|
|
|
|
Expanded(
|
|
|
|
|
|
child: GestureDetector(
|
2026-03-25 16:09:17 +08:00
|
|
|
|
onTap: () {
|
|
|
|
|
|
setState(() {
|
|
|
|
|
|
_isMobileSelected = true;
|
|
|
|
|
|
});
|
2026-04-23 17:49:42 +08:00
|
|
|
|
pcType = 1;
|
2026-03-25 16:09:17 +08:00
|
|
|
|
_getToDoWorkList(pcType);
|
|
|
|
|
|
},
|
2025-12-12 09:11:30 +08:00
|
|
|
|
child: Container(
|
|
|
|
|
|
decoration: BoxDecoration(
|
2026-03-25 16:09:17 +08:00
|
|
|
|
color:
|
2026-04-23 17:49:42 +08:00
|
|
|
|
_isMobileSelected
|
|
|
|
|
|
? const Color(0xFF2A75F8)
|
|
|
|
|
|
: Colors.white,
|
2026-03-25 16:09:17 +08:00
|
|
|
|
borderRadius: const BorderRadius.only(
|
|
|
|
|
|
topLeft: Radius.circular(20),
|
|
|
|
|
|
bottomLeft: Radius.circular(20),
|
|
|
|
|
|
),
|
2025-12-12 09:11:30 +08:00
|
|
|
|
),
|
|
|
|
|
|
child: Center(
|
|
|
|
|
|
child: Text(
|
|
|
|
|
|
"手机端",
|
|
|
|
|
|
style: TextStyle(
|
|
|
|
|
|
fontSize: 14,
|
2026-04-23 17:49:42 +08:00
|
|
|
|
color:
|
|
|
|
|
|
_isMobileSelected
|
|
|
|
|
|
? Colors.white
|
|
|
|
|
|
: Colors.black87,
|
2025-12-12 09:11:30 +08:00
|
|
|
|
fontWeight: FontWeight.w500,
|
|
|
|
|
|
),
|
|
|
|
|
|
),
|
|
|
|
|
|
),
|
|
|
|
|
|
),
|
|
|
|
|
|
),
|
|
|
|
|
|
),
|
|
|
|
|
|
Expanded(
|
|
|
|
|
|
child: GestureDetector(
|
2026-03-25 16:09:17 +08:00
|
|
|
|
onTap: () {
|
|
|
|
|
|
setState(() {
|
|
|
|
|
|
_isMobileSelected = false;
|
|
|
|
|
|
});
|
2026-04-23 17:49:42 +08:00
|
|
|
|
pcType = 2;
|
2026-03-25 16:09:17 +08:00
|
|
|
|
_getToDoWorkList(pcType);
|
|
|
|
|
|
},
|
2025-12-12 09:11:30 +08:00
|
|
|
|
child: Container(
|
|
|
|
|
|
decoration: BoxDecoration(
|
2026-04-23 17:49:42 +08:00
|
|
|
|
color:
|
|
|
|
|
|
!_isMobileSelected
|
|
|
|
|
|
? const Color(0xFF2A75F8)
|
|
|
|
|
|
: Colors.white,
|
2026-03-25 16:09:17 +08:00
|
|
|
|
borderRadius: const BorderRadius.only(
|
|
|
|
|
|
topRight: Radius.circular(20),
|
|
|
|
|
|
bottomRight: Radius.circular(20),
|
|
|
|
|
|
),
|
2025-12-12 09:11:30 +08:00
|
|
|
|
),
|
|
|
|
|
|
child: Center(
|
|
|
|
|
|
child: Text(
|
|
|
|
|
|
"电脑端",
|
|
|
|
|
|
style: TextStyle(
|
2026-03-25 16:09:17 +08:00
|
|
|
|
fontSize: 14,
|
2026-04-23 17:49:42 +08:00
|
|
|
|
color:
|
|
|
|
|
|
!_isMobileSelected
|
|
|
|
|
|
? Colors.white
|
|
|
|
|
|
: Colors.black87,
|
2025-12-12 09:11:30 +08:00
|
|
|
|
fontWeight: FontWeight.w500,
|
|
|
|
|
|
),
|
|
|
|
|
|
),
|
|
|
|
|
|
),
|
|
|
|
|
|
),
|
|
|
|
|
|
),
|
|
|
|
|
|
),
|
|
|
|
|
|
],
|
|
|
|
|
|
),
|
|
|
|
|
|
),
|
2026-03-25 16:09:17 +08:00
|
|
|
|
// 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: const 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: const TextStyle(
|
|
|
|
|
|
// fontSize: 14,
|
|
|
|
|
|
// color: Colors.orange,
|
|
|
|
|
|
// fontWeight: FontWeight.bold,
|
|
|
|
|
|
// ),
|
|
|
|
|
|
// ),
|
|
|
|
|
|
// TextSpan(
|
|
|
|
|
|
// text: "个",
|
|
|
|
|
|
// style: TextStyle(fontSize: 14, color: Colors.black87),
|
|
|
|
|
|
// ),
|
|
|
|
|
|
// ],
|
|
|
|
|
|
// ),
|
|
|
|
|
|
// ),
|
|
|
|
|
|
// ],
|
|
|
|
|
|
// ),
|
2025-12-12 09:11:30 +08:00
|
|
|
|
],
|
|
|
|
|
|
),
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-25 16:09:17 +08:00
|
|
|
|
// Widget _buildCheckListSection() {
|
|
|
|
|
|
// return Column(
|
|
|
|
|
|
// children: [
|
|
|
|
|
|
// Row(
|
|
|
|
|
|
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
|
|
|
|
// children: const [
|
|
|
|
|
|
// Text(' 待办清单', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
|
|
|
|
|
|
// SizedBox(),
|
|
|
|
|
|
// ],
|
|
|
|
|
|
// ),
|
|
|
|
|
|
// const 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))]),
|
|
|
|
|
|
// ),
|
|
|
|
|
|
// ],
|
|
|
|
|
|
// );
|
|
|
|
|
|
// }
|
|
|
|
|
|
|
2025-12-12 09:11:30 +08:00
|
|
|
|
Widget _buildCheckListSection() {
|
2026-03-25 16:09:17 +08:00
|
|
|
|
return 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)).toList(),
|
|
|
|
|
|
),
|
2025-12-12 09:11:30 +08:00
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Widget _buildCheckListItem(Map<String, dynamic> item) {
|
2026-03-25 16:09:17 +08:00
|
|
|
|
return GestureDetector(
|
|
|
|
|
|
onTap: () async {
|
2026-04-23 17:49:42 +08:00
|
|
|
|
if (pcType == 2) {
|
2026-03-25 16:09:17 +08:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
2026-04-23 17:49:42 +08:00
|
|
|
|
switch (item['appName']) {
|
2026-03-30 16:21:06 +08:00
|
|
|
|
case "隐患治理":
|
|
|
|
|
|
case "隐患管理":
|
|
|
|
|
|
case "风险管控应用":
|
|
|
|
|
|
// await pushPage(ApplicationPageTwo(), context);
|
|
|
|
|
|
break;
|
|
|
|
|
|
case "消防检查":
|
|
|
|
|
|
// await pushPage(FireManagementTabPage(), context);
|
|
|
|
|
|
break;
|
2026-04-02 10:11:58 +08:00
|
|
|
|
case "口门门禁管理":
|
2026-03-30 16:21:06 +08:00
|
|
|
|
case "一级口门管理":
|
|
|
|
|
|
await pushPage(DoorcarTabPage(), context);
|
|
|
|
|
|
break;
|
|
|
|
|
|
case "安全环保检查":
|
|
|
|
|
|
// await pushPage(SafecheckTabList(), context);
|
|
|
|
|
|
break;
|
2026-04-23 17:59:31 +08:00
|
|
|
|
case "特殊作业":
|
|
|
|
|
|
await pushPage(WorkTabListPage(), context);
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-03-30 16:21:06 +08:00
|
|
|
|
}
|
2026-03-25 16:09:17 +08:00
|
|
|
|
_getToDoWorkList(pcType);
|
2026-04-23 17:59:31 +08:00
|
|
|
|
// _getHiddenIssuesNum();
|
2026-03-25 16:09:17 +08:00
|
|
|
|
},
|
|
|
|
|
|
child: Container(
|
|
|
|
|
|
padding: const EdgeInsets.all(15),
|
2026-04-23 17:49:42 +08:00
|
|
|
|
decoration: BoxDecoration(
|
|
|
|
|
|
border: Border(
|
|
|
|
|
|
bottom: BorderSide(color: Colors.grey[200]!, width: 1),
|
|
|
|
|
|
),
|
|
|
|
|
|
),
|
2026-03-25 16:09:17 +08:00
|
|
|
|
child: Row(
|
|
|
|
|
|
children: [
|
|
|
|
|
|
Expanded(
|
|
|
|
|
|
child: Column(
|
|
|
|
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
|
|
|
|
children: [
|
2026-04-02 17:34:28 +08:00
|
|
|
|
// Row(
|
|
|
|
|
|
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
|
|
|
|
// children: [
|
2026-04-23 17:49:42 +08:00
|
|
|
|
Text(
|
|
|
|
|
|
item['title'],
|
|
|
|
|
|
style: const TextStyle(
|
|
|
|
|
|
fontSize: 15,
|
|
|
|
|
|
fontWeight: FontWeight.bold,
|
|
|
|
|
|
color: Colors.black87,
|
|
|
|
|
|
),
|
|
|
|
|
|
),
|
|
|
|
|
|
SizedBox(height: 4),
|
|
|
|
|
|
Text(
|
|
|
|
|
|
item['content'],
|
|
|
|
|
|
style: TextStyle(fontSize: 12, color: Colors.grey[500]),
|
|
|
|
|
|
),
|
2026-04-02 17:34:28 +08:00
|
|
|
|
// ],
|
|
|
|
|
|
// ),
|
2026-03-25 16:09:17 +08:00
|
|
|
|
const Divider(),
|
|
|
|
|
|
Row(
|
|
|
|
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
|
|
|
|
children: [
|
|
|
|
|
|
Row(
|
|
|
|
|
|
children: [
|
2026-04-23 17:49:42 +08:00
|
|
|
|
Image.asset(
|
|
|
|
|
|
'assets/images/mine1.png',
|
|
|
|
|
|
width: 15,
|
|
|
|
|
|
height: 15,
|
|
|
|
|
|
),
|
2026-03-25 16:09:17 +08:00
|
|
|
|
Container(
|
2026-04-23 17:49:42 +08:00
|
|
|
|
padding: const EdgeInsets.symmetric(
|
|
|
|
|
|
horizontal: 6,
|
|
|
|
|
|
vertical: 2,
|
|
|
|
|
|
),
|
|
|
|
|
|
decoration: BoxDecoration(
|
|
|
|
|
|
color: Colors.grey[100],
|
|
|
|
|
|
borderRadius: BorderRadius.circular(4),
|
|
|
|
|
|
),
|
|
|
|
|
|
child: Text(
|
|
|
|
|
|
"类型:${item['appName']}",
|
|
|
|
|
|
style: const TextStyle(
|
|
|
|
|
|
fontSize: 12,
|
|
|
|
|
|
color: Colors.blue,
|
|
|
|
|
|
),
|
|
|
|
|
|
),
|
2026-03-25 16:09:17 +08:00
|
|
|
|
),
|
|
|
|
|
|
],
|
2025-12-12 09:11:30 +08:00
|
|
|
|
),
|
2026-04-23 17:49:42 +08:00
|
|
|
|
Text(
|
|
|
|
|
|
"时间:${item['createTime']}",
|
|
|
|
|
|
style: const TextStyle(
|
|
|
|
|
|
fontSize: 12,
|
|
|
|
|
|
color: Colors.grey,
|
|
|
|
|
|
),
|
|
|
|
|
|
),
|
2026-03-25 16:09:17 +08:00
|
|
|
|
],
|
|
|
|
|
|
),
|
|
|
|
|
|
],
|
|
|
|
|
|
),
|
2025-12-12 09:11:30 +08:00
|
|
|
|
),
|
2026-03-25 16:09:17 +08:00
|
|
|
|
],
|
|
|
|
|
|
),
|
2025-12-12 09:11:30 +08:00
|
|
|
|
),
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
2026-03-25 16:09:17 +08:00
|
|
|
|
|
|
|
|
|
|
// 获取待办事项
|
|
|
|
|
|
void _getToDoWorkList(int type) async {
|
|
|
|
|
|
Map data = {
|
2026-04-23 17:49:42 +08:00
|
|
|
|
"eqAppFlag": type == 1 ? "1" : "",
|
|
|
|
|
|
"eqPcFlag": type == 1 ? "" : "1",
|
2026-03-25 16:09:17 +08:00
|
|
|
|
"eqStatus": "1",
|
|
|
|
|
|
"pageIndex": '1',
|
|
|
|
|
|
"pageSize": '999',
|
|
|
|
|
|
};
|
|
|
|
|
|
final result = await TodoApi.getTodoList(data);
|
2026-04-23 17:49:42 +08:00
|
|
|
|
final specialWork = await SpecialWorkApi.specialWorkTaskLogTotalCount();
|
|
|
|
|
|
int specialWorkNum = 0;
|
|
|
|
|
|
try {
|
|
|
|
|
|
if (specialWork['success']) {
|
|
|
|
|
|
List<dynamic> specialWorkList = specialWork['data'] ?? [];
|
|
|
|
|
|
for (var item in specialWorkList) {
|
|
|
|
|
|
if (item is Map) {
|
|
|
|
|
|
final total = item['todoCount'];
|
|
|
|
|
|
specialWorkNum += int.tryParse('$total') ?? 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
setState(() {
|
|
|
|
|
|
for (var section in buttonInfos) {
|
|
|
|
|
|
if (section['title'] == '危险作业') {
|
|
|
|
|
|
section['unreadCount'] = specialWorkNum;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
} catch (e) {}
|
2026-03-25 16:09:17 +08:00
|
|
|
|
if (result['success']) {
|
|
|
|
|
|
setState(() {
|
2026-04-23 17:49:42 +08:00
|
|
|
|
workStats['total'] = result['totalCount'];
|
|
|
|
|
|
checkLists = result['data'];
|
2026-03-25 16:09:17 +08:00
|
|
|
|
// checkLists = result['data'];
|
2026-04-23 17:49:42 +08:00
|
|
|
|
int m = checkLists.length;
|
2026-03-25 16:09:17 +08:00
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-04-23 17:49:42 +08:00
|
|
|
|
}
|