991 lines
31 KiB
Dart
991 lines
31 KiB
Dart
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,
|
||
),
|
||
),
|
||
],
|
||
),
|
||
],
|
||
),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
}
|