diff --git a/assets/icon-apps/home-xj.png b/assets/icon-apps/home-xj.png new file mode 100644 index 0000000..f045124 Binary files /dev/null and b/assets/icon-apps/home-xj.png differ diff --git a/assets/images/xj_finish.png b/assets/images/xj_finish.png new file mode 100644 index 0000000..9ac3c18 Binary files /dev/null and b/assets/images/xj_finish.png differ diff --git a/assets/images/xj_top.png b/assets/images/xj_top.png new file mode 100644 index 0000000..15b1d2e Binary files /dev/null and b/assets/images/xj_top.png differ diff --git a/assets/images/xj_wait.png b/assets/images/xj_wait.png new file mode 100644 index 0000000..27cba55 Binary files /dev/null and b/assets/images/xj_wait.png differ diff --git a/assets/images/xjjd_top.png b/assets/images/xjjd_top.png new file mode 100644 index 0000000..4e2d8b4 Binary files /dev/null and b/assets/images/xjjd_top.png differ diff --git a/lib/http/ApiService.dart b/lib/http/ApiService.dart index b9496e6..860badf 100644 --- a/lib/http/ApiService.dart +++ b/lib/http/ApiService.dart @@ -704,6 +704,21 @@ U6Hzm1ninpWeE+awIDAQAB }, ); } + /// 安全措施确认 + static Future> listSignSureAllMeasures(String homeWorkId) { + final String tm = DateTime.now().millisecondsSinceEpoch.toString(); + return HttpManager().request( + basePath, + '/app/hotwork/listAllMeasuresForSign?tm=$tm', + method: Method.post, + data: { + "CORPINFO_ID":SessionService.instance.corpinfoId, + "HOTWORK_ID": homeWorkId, + "USER_ID":SessionService.instance.loginUserId, + }, + ); + } + /// 关联的特殊作业列表 static Future> getEightWorkStartList(Map data) { @@ -763,7 +778,28 @@ U6Hzm1ninpWeE+awIDAQAB fromData: data, ); } + /// 保存安全措施确认 + static Future> saveSafeFunctionSure ( + Map formData, + List filePaths, + ) async { + // 复制一份 formData + final data = Map.from(formData); + // 把文件路径填成 MultipartFile + for (var i = 0; i < filePaths.length; i++) { + final path = filePaths[i]; + data['file$i'] = await MultipartFile.fromFile( + path, + filename: path.split(Platform.pathSeparator).last, + ); + } + return HttpManager().uploadFaceImage( + baseUrl: basePath, + path: '/app/hotwork/nextStep', + fromData: data, + ); + } diff --git a/lib/pages/home/NFC/home_nfc_check_page.dart b/lib/pages/home/NFC/home_nfc_check_page.dart new file mode 100644 index 0000000..e69de29 diff --git a/lib/pages/home/NFC/home_nfc_detail_page.dart b/lib/pages/home/NFC/home_nfc_detail_page.dart new file mode 100644 index 0000000..22a7178 --- /dev/null +++ b/lib/pages/home/NFC/home_nfc_detail_page.dart @@ -0,0 +1,360 @@ +import 'package:flutter/material.dart'; +import 'package:qhd_prevention/pages/my_appbar.dart'; + +class HomeNfcDetailPage extends StatefulWidget { + const HomeNfcDetailPage({super.key}); + + @override + State createState() => _HomeNfcDetailPageState(); +} + +class _HomeNfcDetailPageState extends State { + + Map info = { + 'title': '设备巡检 A', + 'status': '专项巡检', + 'department': '安全部', + 'owner': '张三', + 'unType': 'UN1001', + 'cycle': '7天', + 'points': '3/5', + }; + final List demoData = const [ + ProgressItem(status: '未查', location: '到期哦i维护经费欺废气阀我废费欺废气阀我废费欺废气阀我废气阀', code: 'XJ1001'), + ProgressItem(status: '已查', location: 'B区-配电室', code: 'XJ1002', checkTime: '2025-07-28 15:32'), + ProgressItem(status: '已查', location: 'B区-配电室', code: 'XJ1002', checkTime: '2025-07-28 15:32'), + ProgressItem(status: '已查', location: 'B区-配电室', code: 'XJ1002', checkTime: '2025-07-28 15:32'), + ProgressItem(status: '已查', location: 'B区-配电室', code: 'XJ1002', checkTime: '2025-07-28 15:32'), + ProgressItem(status: '已查', location: 'B区-配电室', code: 'XJ1002', checkTime: '2025-07-28 15:32'), + ProgressItem(status: '已查', location: 'B区-配电室', code: 'XJ1002', checkTime: '2025-07-28 15:32'), + ProgressItem(status: '已查', location: 'B区-配电室', code: 'XJ1002', checkTime: '2025-07-28 15:32'), + ProgressItem(status: '已查', location: 'B区-配电室', code: 'XJ1002', checkTime: '2025-07-28 15:32'), + ProgressItem(status: '已查', location: 'B区-配电室', code: 'XJ1002', checkTime: '2025-07-28 15:32'), + + ]; + Widget _pendingTopCard(Map item) { + return SizedBox( + height: 180, + child: Stack( + clipBehavior: Clip.none, + children: [ + ClipRRect( + borderRadius: BorderRadius.circular(10), + child: Image.asset( + 'assets/images/xj_top.png', + height: 70, + width: double.infinity, + fit: BoxFit.cover, + ), + ), + + // 标题 & 状态标签 + Positioned( + top: 12, + left: 12, + child: Text( + item['title']!, + style: const TextStyle( + color: Colors.black87, + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ), + ), + Positioned( + top: 12, + right: 12, + child: Container( + height: 30, + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + decoration: BoxDecoration( + color: Colors.blue.withOpacity(0.7), + borderRadius: BorderRadius.circular(15), + ), + child: Center( + child: Text( + item['status']!, + style: const TextStyle(color: Colors.white, fontSize: 14), + ), + ), + ), + ), + + // 白色信息区域(盖住图片部分) + Positioned( + left: 0, + right: 0, + top: 50, // 盖住图片底部 + child: Container( + margin: const EdgeInsets.symmetric(horizontal: 0), + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(10), + boxShadow: [ + BoxShadow( + color: Colors.black12, + blurRadius: 4, + offset: Offset(0, 1), + ), + ], + ), + child: _buildInfoGrid(item), + ), + ), + ], + ), + ); + } + /// 构建信息网格 + Widget _buildInfoGrid(Map item) { + return Row( + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('负责部门:${item['department']}'), + const SizedBox(height: 8), + Text('负责人:${item['owner']}'), + const SizedBox(height: 8), + Text('UN件类型:${item['unType']}'), + ], + ), + ), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Text('巡检周期:${item['cycle']}'), + const SizedBox(height: 8), + Text('已巡点位:${item['points']}'), + Text('涉及管道区域:${item['department']}') + ], + ), + ), + ], + ); + } + + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: MyAppbar(title: '任务详情'), + body: SafeArea(child: Padding(padding: EdgeInsets.all(16), child: Column( + children: [ + _pendingTopCard(info), + Expanded(child: Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(10), + boxShadow: [ + BoxShadow( + color: Colors.black12, + blurRadius: 4, + offset: Offset(0, 1), + ), + ], + ), + + child: SingleChildScrollView( + child: ProgressList( + items: demoData, + onStartCheck: (idx) { + print('开始检查第 $idx 项'); + }, + ), + ) + )) + ], + ),)), + ); + } +} +/// 单条进度数据模型 +class ProgressItem { + final String status; // “未查” 或 “已查” + final String location; // 地点 + final String code; // 编码 + final String? checkTime; // 检查时间(已查时有值) + + const ProgressItem({ + required this.status, + required this.location, + required this.code, + this.checkTime, + }); +} + +/// 进度列表组件 +class ProgressList extends StatelessWidget { + /// 数据源 + final List items; + + /// 点击“开始检查”后的回调,index 对应 items 的下标 + final void Function(int index) onStartCheck; + + const ProgressList({ + Key? key, + required this.items, + required this.onStartCheck, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + // 整体用 Column 包裹标题和列表 + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // 标题 + const Padding( + padding: EdgeInsets.all(16), + child: Text( + '已查点位1/5', + style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), + ), + ), + const Divider(height: 1), + + // 列表 + ListView.builder( + physics: const NeverScrollableScrollPhysics(), // 让父级滚动 + shrinkWrap: true, + itemCount: items.length, + itemBuilder: (ctx, idx) { + return _ProgressListItem( + item: items[idx], + onStart: () => onStartCheck(idx), + ); + }, + ), + ], + ); + } +} + +/// 单行进度条目 +class _ProgressListItem extends StatelessWidget { + final ProgressItem item; + final VoidCallback onStart; + + const _ProgressListItem({ + Key? key, + required this.item, + required this.onStart, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + final bool unchecked = item.status == '未查'; + + return Container( + height: 100, + margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + // 左侧状态区 + Column(children: [ + Stack( + alignment: Alignment.center, + children: [ + Image.asset( + unchecked + ? 'assets/images/xj_wait.png' + : 'assets/images/xj_finish.png', + width: 50, + height: 30, + fit: BoxFit.cover, + ), + Positioned( + top: 2, + child: Text( + item.status, + style: const TextStyle(color: Colors.white, fontSize: 14), + ), + ), + + ], + ), + SizedBox(height: 15,), + Positioned( + // bottom: 0, + // right: 17, + child: Image.asset( + 'assets/images/xjjd_top.png', + width: 15, + height: 30, + ), + ), + ],), + + + const SizedBox(width: 20), + + // 中间详情 + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + // mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + item.location, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + maxLines: 1, // 最多一行 + overflow: TextOverflow.ellipsis, // 超出省略号 + ), + const SizedBox(height: 4), + Text('NFC编码:${item.code}'), + + unchecked + ? InkWell( + onTap: onStart, + child: Column( + children: [ + SizedBox(height: 5,), + Container( + height: 35, + alignment: Alignment.center, + padding: const EdgeInsets.symmetric(vertical: 1), + decoration: BoxDecoration( + gradient: const LinearGradient( + colors: [Color(0xFFFFA726), Color(0xFFFF7043)], + ), + borderRadius: BorderRadius.circular(5), + ), + child: const Text( + '开始检查', + style: TextStyle(color: Colors.white, fontSize: 14), + ), + ), + ], + ) + ) + : Text( + '检查时间:' + (item.checkTime ?? ''), + style: const TextStyle(fontSize: 14), + textAlign: TextAlign.center, + ), + ], + ), + ), + + // 操作区或时间 + const SizedBox(width: 8), + Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const Icon(Icons.chevron_right, color: Colors.grey), + SizedBox() + ], + ) + ], + ), + ); + } +} \ No newline at end of file diff --git a/lib/pages/home/NFC/home_nfc_list_page.dart b/lib/pages/home/NFC/home_nfc_list_page.dart new file mode 100644 index 0000000..a734f9e --- /dev/null +++ b/lib/pages/home/NFC/home_nfc_list_page.dart @@ -0,0 +1,263 @@ +import 'package:flutter/material.dart'; +import 'package:qhd_prevention/pages/home/NFC/home_nfc_detail_page.dart'; +import 'package:qhd_prevention/pages/my_appbar.dart'; +import 'package:qhd_prevention/tools/tools.dart'; + +class HomeNfcListPage extends StatefulWidget { + const HomeNfcListPage({super.key}); + + @override + State createState() => _HomeNfcListPageState(); +} + +class _HomeNfcListPageState extends State + with SingleTickerProviderStateMixin { + late TabController _tabController; + + // 测试数据 + final List> _pendingList = [ + { + 'title': '设备巡检 A', + 'status': '待巡检', + 'department': '安全部', + 'owner': '张三', + 'unType': 'UN1001', + 'cycle': '7天', + 'points': '3/5', + }, + { + 'title': '设备巡检 B', + 'status': '待巡检', + 'department': '维护部', + 'owner': '李四', + 'unType': 'UN1002', + 'cycle': '30天', + 'points': '1/2', + }, + { + 'title': '设备巡检 C', + 'status': '待巡检', + 'department': '维护部', + 'owner': '李四', + 'unType': 'UN1002', + 'cycle': '30天', + 'points': '1/2', + }, + ]; + + final List> _recordList = [ + { + 'title': '设备巡检 A', + 'status': '待巡检', + 'department': '安全部', + 'owner': '张三', + 'unType': 'UN1001', + 'cycle': '7天', + 'points': '3/5', + }, + + ]; + + @override + void initState() { + super.initState(); + _tabController = TabController(length: 2, vsync: this); + } + + @override + void dispose() { + _tabController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: MyAppbar(title: '巡检列表'), + body: SafeArea( + child: Column( + children: [ + // Tab bar + TabBar( + controller: _tabController, + labelStyle: const TextStyle(fontSize: 16), + indicator: const UnderlineTabIndicator( + borderSide: BorderSide(width: 3.0, color: Colors.blue), + insets: EdgeInsets.symmetric(horizontal: 50.0), + ), + labelColor: Colors.blue, + unselectedLabelColor: Colors.grey, + tabs: const [Tab(text: '待巡检'), Tab(text: '巡检记录')], + ), + const SizedBox(height: 8), + // Tab 内容 + Expanded( + child: TabBarView( + controller: _tabController, + children: [_buildPendingList(), _buildRecordList()], + ), + ), + ], + ), + ), + ); + } + + Widget _buildPendingList() { + if (_pendingList.isEmpty) { + return NoDataWidget.show(); // 无数据提示 + } + + return ListView.builder( + + itemCount: _pendingList.length, + padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 0), + itemBuilder: (context, index) { + final item = _pendingList[index]; + return Container( + margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 0), + child: GestureDetector( + onTap: (){ + pushPage(HomeNfcDetailPage(), context); + }, + child: _pendingCard(item, false), + ), + ); + }, + ); + } + + Widget _buildRecordList() { + if (_recordList.isEmpty) { + return NoDataWidget.show(); // 无数据提示 + } + return ListView.builder( + itemCount: _recordList.length, + padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 0), + itemBuilder: (context, index) { + final item = _recordList[index]; + return Container( + margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 0), + child: _pendingCard(item, true), + ); + }, + ); + } + + /// 构建待巡检卡片 + Widget _pendingCard(Map item, bool isFinish) { + return SizedBox( + height: 180, + child: Stack( + clipBehavior: Clip.none, + children: [ + // 背景卡片阴影 & 图片 + ClipRRect( + borderRadius: BorderRadius.circular(10), + child: Image.asset( + 'assets/images/xj_top.png', + height: 70, + width: double.infinity, + fit: BoxFit.cover, + ), + ), + + // 标题 & 状态标签 + Positioned( + top: 12, + left: 12, + child: Text( + item['title']!, + style: const TextStyle( + color: Colors.black87, + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ), + ), + if (!isFinish) + Positioned( + top: 12, + right: 12, + child: Container( + height: 30, + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + decoration: BoxDecoration( + color: Colors.blue.withOpacity(0.7), + borderRadius: BorderRadius.circular(15), + ), + child: Center( + child: Text( + item['status']!, + style: const TextStyle(color: Colors.white, fontSize: 14), + ), + ), + ), + ), + + // 白色信息区域(盖住图片部分) + Positioned( + left: 0, + right: 0, + top: 50, // 盖住图片底部 + child: Container( + margin: const EdgeInsets.symmetric(horizontal: 0), + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(10), + topRight: Radius.circular(10), + bottomLeft: Radius.circular(10), + bottomRight: Radius.circular(10), + ), + boxShadow: [ + BoxShadow( + color: Colors.black12, + blurRadius: 4, + offset: Offset(0, 1), + ), + ], + ), + child: _buildInfoGrid(item, isFinish), + ), + ), + ], + ), + ); + } + + /// 构建信息网格 + Widget _buildInfoGrid(Map item, bool isFinish) { + return Row( + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('负责部门:${item['department']}'), + const SizedBox(height: 8), + Text('负责人:${item['owner']}'), + const SizedBox(height: 8), + Text('UN件类型:${item['unType']}'), + ], + ), + ), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Text('巡检周期:${item['cycle']}'), + const SizedBox(height: 8), + Text('已巡点位:${item['points']}'), + isFinish + ? Text('涉及管道区域:${item['department']}') + : const SizedBox(height: 25), + ], + ), + ), + ], + ); + } + +} diff --git a/lib/pages/home/home_page.dart b/lib/pages/home/home_page.dart index aada2ee..0e7974c 100644 --- a/lib/pages/home/home_page.dart +++ b/lib/pages/home/home_page.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:qhd_prevention/customWidget/ItemWidgetFactory.dart'; +import 'package:qhd_prevention/pages/home/NFC/home_nfc_list_page.dart'; import 'package:qhd_prevention/pages/home/low_page.dart'; import 'package:qhd_prevention/pages/home/risk/riskControl_page.dart'; import 'package:qhd_prevention/pages/home/study/study_garden_page.dart'; @@ -196,6 +197,8 @@ class _HomePageState extends State { pushPage(WorkTabListPage(), context); } else if (index == 7) { pushPage(StudyGardenPage(), context); + } else if (index == 11) { + pushPage(HomeNfcListPage(), context); } }, ); @@ -513,6 +516,11 @@ class _HomePageState extends State { "title": "安全例会", "unreadCount": 0, }, + { + "icon": "assets/icon-apps/home-xj.png", + "title": "燃气巡检", + "unreadCount": 0, + }, ]; }); diff --git a/lib/pages/home/tap/item_list_widget.dart b/lib/pages/home/tap/item_list_widget.dart index 079e26b..abbaf30 100644 --- a/lib/pages/home/tap/item_list_widget.dart +++ b/lib/pages/home/tap/item_list_widget.dart @@ -19,9 +19,10 @@ class ItemListWidget { return Container( padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 12), child: Row( - mainAxisAlignment: isEditable - ? MainAxisAlignment.start - : MainAxisAlignment.spaceBetween, + mainAxisAlignment: + isEditable + ? MainAxisAlignment.start + : MainAxisAlignment.spaceBetween, children: [ Text( label, @@ -30,23 +31,23 @@ class ItemListWidget { const SizedBox(width: 8), isEditable ? Expanded( - child: TextField( - autofocus: false, - controller: controller, - style: TextStyle(fontSize: fontSize), - maxLines: 1, - decoration: InputDecoration( - isDense: true, - hintText: hintText, - contentPadding: EdgeInsets.symmetric(vertical: 8), - ), - ), - ) + child: TextField( + autofocus: false, + controller: controller, + style: TextStyle(fontSize: fontSize), + maxLines: 1, + decoration: InputDecoration( + isDense: true, + hintText: hintText, + contentPadding: EdgeInsets.symmetric(vertical: 8), + ), + ), + ) : Text( - text ?? '', - style: TextStyle(fontSize: fontSize, color: detailtextColor), - overflow: TextOverflow.ellipsis, // 超出省略 - ), + text ?? '', + style: TextStyle(fontSize: fontSize, color: detailtextColor), + overflow: TextOverflow.ellipsis, // 超出省略 + ), ], ), ); @@ -76,47 +77,53 @@ class ItemListWidget { ), const SizedBox(height: 8), Expanded( - child: isEditable - ? TextField( - autofocus: false, - controller: controller, - keyboardType: TextInputType.multiline, - maxLines: null, - expands: true, - // 垂直顶部对齐 - textAlignVertical: TextAlignVertical.top, - style: TextStyle(fontSize: fontSize), - decoration: InputDecoration( - hintText: '请输入', - // 去掉 TextField 默认内边距 - contentPadding: EdgeInsets.zero, - border: InputBorder.none, - ), - ) - : SingleChildScrollView( - // 去掉多余的 padding - padding: EdgeInsets.zero, - child: Text( - text ?? '', - style: TextStyle(fontSize: fontSize, color: detailtextColor), - ), - ), + child: + isEditable + ? TextField( + autofocus: false, + controller: controller, + keyboardType: TextInputType.multiline, + maxLines: null, + expands: true, + // 垂直顶部对齐 + textAlignVertical: TextAlignVertical.top, + style: TextStyle(fontSize: fontSize), + decoration: InputDecoration( + hintText: '请输入', + // 去掉 TextField 默认内边距 + contentPadding: EdgeInsets.zero, + border: InputBorder.none, + ), + ) + : SingleChildScrollView( + // 去掉多余的 padding + padding: EdgeInsets.zero, + child: Text( + text ?? '', + style: TextStyle( + fontSize: fontSize, + color: detailtextColor, + ), + ), + ), ), ], ), ); } - /// 单行可点击选择: /// - 可编辑时:标题 + “请选择”提示 + 右箭头 /// - 不可编辑时:标题 + 文本内容 static Widget selectableLineTitleTextField({ - required String label, // 标题文本 - required bool isEditable, // 是否可点击 - required String text, // 显示内容或提示 - VoidCallback? onTap, // 点击回调 - double fontSize = 15, // 字体大小 + required String label, // 标题文本 + required bool isEditable, // 是否可点击 + required String text, // 显示内容或提示 + VoidCallback? onTap, // 点击回调 + double fontSize = 15, // 字体大小 + bool isClean = false, + VoidCallback? onTapClean, // 清除回调 + }) { return InkWell( onTap: isEditable ? onTap : null, @@ -127,14 +134,24 @@ class ItemListWidget { // 1. 标题 Text( label, - style: TextStyle( - fontSize: fontSize, - fontWeight: FontWeight.bold, - ), + style: TextStyle(fontSize: fontSize, fontWeight: FontWeight.bold), ), - + if (isClean) + Column( + children: [ + CustomButton( + text: '清除', + height: 20, + padding: EdgeInsets.symmetric(horizontal: 10, vertical: 0), + textStyle: TextStyle(fontSize: 11, color: Colors.white), + borderRadius: 10, + backgroundColor: Colors.red, + onPressed: onTapClean, + ), + SizedBox(height: 20), + ], + ), const SizedBox(width: 8), - Expanded( child: Row( mainAxisAlignment: MainAxisAlignment.end, @@ -153,10 +170,7 @@ class ItemListWidget { if (isEditable) const Padding( padding: EdgeInsets.only(left: 4), - child: Icon( - Icons.chevron_right, - size: 20, - ), + child: Icon(Icons.chevron_right, size: 20), ), ], ), @@ -167,8 +181,6 @@ class ItemListWidget { ); } - - /// 两行垂直布局: /// 第一行:可点击选择(带箭头)或仅显示标题 /// 第二行:多行输入框或多行文本展示 @@ -194,7 +206,10 @@ class ItemListWidget { children: [ Text( label, - style: TextStyle(fontSize: fontSize, fontWeight: FontWeight.bold), + style: TextStyle( + fontSize: fontSize, + fontWeight: FontWeight.bold, + ), ), Row( children: [ @@ -209,7 +224,7 @@ class ItemListWidget { ), if (isEditable) const Icon(Icons.chevron_right), ], - ) + ), ], ), ), @@ -217,27 +232,31 @@ class ItemListWidget { Container( height: row2Height, padding: const EdgeInsets.symmetric(vertical: 8), - child: isEditable - ? TextField( - autofocus: false, - controller: controller, - keyboardType: TextInputType.multiline, - maxLines: null, - expands: true, - style: TextStyle(fontSize: fontSize), - decoration: InputDecoration( - hintText: '请输入', - contentPadding: EdgeInsets.zero, - border: InputBorder.none, - ), - ) - : SingleChildScrollView( - padding: EdgeInsets.zero, - child: Text( - text, - style: TextStyle(fontSize: fontSize, color: detailtextColor), - ), - ), + child: + isEditable + ? TextField( + autofocus: false, + controller: controller, + keyboardType: TextInputType.multiline, + maxLines: null, + expands: true, + style: TextStyle(fontSize: fontSize), + decoration: InputDecoration( + hintText: '请输入', + contentPadding: EdgeInsets.zero, + border: InputBorder.none, + ), + ) + : SingleChildScrollView( + padding: EdgeInsets.zero, + child: Text( + text, + style: TextStyle( + fontSize: fontSize, + color: detailtextColor, + ), + ), + ), ), ], ), @@ -269,7 +288,10 @@ class ItemListWidget { Flexible( child: Text( label, - style: TextStyle(fontSize: fontSize, fontWeight: FontWeight.bold), + style: TextStyle( + fontSize: fontSize, + fontWeight: FontWeight.bold, + ), maxLines: 1, overflow: TextOverflow.ellipsis, ), @@ -279,7 +301,10 @@ class ItemListWidget { CustomButton( text: "选择其他", height: 30, - padding: const EdgeInsets.symmetric(vertical: 2, horizontal: 5), + padding: const EdgeInsets.symmetric( + vertical: 2, + horizontal: 5, + ), backgroundColor: Colors.green, onPressed: onTap, ), @@ -291,32 +316,37 @@ class ItemListWidget { Container( height: row2Height, padding: const EdgeInsets.symmetric(vertical: 8), - child: isEditable - ? TextField( - autofocus: false, - controller: controller, - keyboardType: TextInputType.multiline, - maxLines: null, - expands: true, - style: TextStyle(fontSize: fontSize), - decoration: InputDecoration( - hintText: hintText, - contentPadding: EdgeInsets.zero, - border: InputBorder.none, - ), - ) - : SingleChildScrollView( - padding: EdgeInsets.zero, - child: Text( - text, - style: TextStyle(fontSize: fontSize, color: detailtextColor), - ), - ), + child: + isEditable + ? TextField( + autofocus: false, + controller: controller, + keyboardType: TextInputType.multiline, + maxLines: null, + expands: true, + style: TextStyle(fontSize: fontSize), + decoration: InputDecoration( + hintText: hintText, + contentPadding: EdgeInsets.zero, + border: InputBorder.none, + ), + ) + : SingleChildScrollView( + padding: EdgeInsets.zero, + child: Text( + text, + style: TextStyle( + fontSize: fontSize, + color: detailtextColor, + ), + ), + ), ), ], ), ); } + /// 单行布局: /// 标题 + 文字 + 按钮 static Widget OneRowButtonTitleText({ @@ -330,23 +360,31 @@ class ItemListWidget { child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Expanded(child: Row( - children: [ - Text( - label, - style: TextStyle(fontSize: fontSize, fontWeight: FontWeight.bold), - ), - SizedBox(width: 15,), - Expanded( - child: Text( - text, - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: TextStyle(fontSize: fontSize, color: detailtextColor), + Expanded( + child: Row( + children: [ + Text( + label, + style: TextStyle( + fontSize: fontSize, + fontWeight: FontWeight.bold, + ), ), - ), - ], - ),), + SizedBox(width: 15), + Expanded( + child: Text( + text, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: fontSize, + color: detailtextColor, + ), + ), + ), + ], + ), + ), CustomButton( text: "分析详情", height: 30, @@ -355,9 +393,10 @@ class ItemListWidget { onPressed: onTap, ), ], - ) + ), ); } + /// 单行布局: /// 标题 + 按钮 static Widget OneRowButtonTitle({ @@ -366,27 +405,25 @@ class ItemListWidget { required VoidCallback? onTap, // 第一行点击回调 double fontSize = 15, // 字体大小 Color btnColor = Colors.blue, - }) { return Container( - padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 12), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - label, - style: TextStyle(fontSize: fontSize, fontWeight: FontWeight.bold), - ), - CustomButton( - text: buttonText, - height: 30, - padding: const EdgeInsets.symmetric(vertical: 2, horizontal: 5), - backgroundColor: btnColor, - onPressed: onTap, - ), - ], - ) + padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 12), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + label, + style: TextStyle(fontSize: fontSize, fontWeight: FontWeight.bold), + ), + CustomButton( + text: buttonText, + height: 30, + padding: const EdgeInsets.symmetric(vertical: 2, horizontal: 5), + backgroundColor: btnColor, + onPressed: onTap, + ), + ], + ), ); } - } diff --git a/lib/pages/home/tap/tabList/special_wrok/WorkDetailFormWidget.dart b/lib/pages/home/tap/tabList/special_wrok/WorkDetailFormWidget.dart new file mode 100644 index 0000000..f104d59 --- /dev/null +++ b/lib/pages/home/tap/tabList/special_wrok/WorkDetailFormWidget.dart @@ -0,0 +1,163 @@ +import 'package:flutter/material.dart'; + +import '../../../../../tools/tools.dart'; +import '../../item_list_widget.dart'; +import '../special_Wrok/dh_work_detai/MeasuresListWidget.dart'; + +/// 通用明细表单组件 +/// 编辑模式时需传入对应的 TextEditingController;非编辑模式可不传 +class WorkDetailFormWidget extends StatelessWidget { + final Map pd; + final bool isEditable; + final VoidCallback onChooseLevel; + final VoidCallback onChooseHotworkUser; + final VoidCallback onAnalyzeTap; + + /// 编辑模式下需提供以下控制器,非编辑可不传 + final TextEditingController? contentController; + final TextEditingController? locationController; + final TextEditingController? methodController; + final TextEditingController? hotworkPersonController; + final TextEditingController? relatedController; + final TextEditingController? riskController; + + const WorkDetailFormWidget({ + Key? key, + required this.pd, + required this.isEditable, + required this.onChooseLevel, + required this.onChooseHotworkUser, + required this.onAnalyzeTap, + this.contentController, + this.locationController, + this.methodController, + this.hotworkPersonController, + this.relatedController, + this.riskController, + }) : assert( + !isEditable || (contentController != null && locationController != null && methodController != null && hotworkPersonController != null && relatedController != null && riskController != null), + 'Editable mode requires all TextEditingController parameters', + ), + super(key: key); + + @override + Widget build(BuildContext context) { + final pd = this.pd; + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + ItemListWidget.singleLineTitleText( + label: '申请单位:', + isEditable: false, + text: pd['APPLY_DEPARTMENT_NAME'] ?? '', + ), + const Divider(), + ItemListWidget.singleLineTitleText( + label: '申请人:', + isEditable: false, + text: pd['APPLY_USER_NAME'] ?? '', + ), + if (FormUtils.hasValue(pd, 'CHECK_NO')) ...[ + const Divider(), + ItemListWidget.singleLineTitleText( + label: '编号:', + isEditable: false, + text: pd['CHECK_NO'] ?? '', + ), + ], + const Divider(), + ItemListWidget.multiLineTitleTextField( + label: '作业内容:', + isEditable: isEditable, + controller: contentController, + text: pd['WORK_CONTENT'] ?? '', + ), + const Divider(), + ItemListWidget.singleLineTitleText( + label: '动火地点及动火部位:', + isEditable: isEditable, + controller: locationController, + text: pd['WORK_PLACE'] ?? '', + ), + const Divider(), + ItemListWidget.selectableLineTitleTextField( + label: '动火作业级别:', + isEditable: isEditable, + onTap: onChooseLevel, + text: pd['WORK_LEVEL'] ?? '', + ), + const Divider(), + ItemListWidget.singleLineTitleText( + label: '动火方式:', + isEditable: isEditable, + controller: methodController, + text: pd['WORK_FUNCTION'] ?? '', + ), + if (pd['WORK_START_DATE']?.toString().isNotEmpty == true) ...[ + const Divider(), + ItemListWidget.singleLineTitleText( + label: '动火作业实施时间:', + isEditable: isEditable, + text: '${pd['WORK_START_DATE']} 至 ${pd['WORK_END_DATE'] ?? '--'}', + ), + ], + const Divider(), + ItemListWidget.twoRowSelectableTitleText( + label: '动火人及证书编号:', + isEditable: isEditable, + onTap: onChooseHotworkUser, + text: pd['WORK_USER'] ?? '', + controller: hotworkPersonController, + ), + const Divider(), + ItemListWidget.twoRowButtonTitleText( + label: '关联其他特殊作业及安全作业票:', + isEditable: isEditable, + onTap: () async { + final val = await showDialog( + context: context, + builder: (_) => SelectionPopup( + type: 'assignments', + initialValue: pd['SPECIAL_WORK'] ?? '', + onConfirm: (v) => Navigator.of(context).pop(v), + ), + ); + if (val != null) pd['SPECIAL_WORK'] = val; + FocusHelper.clearFocus(context); + }, + hintText: '请输入关联的其他特殊作业及安全作业票编号', + controller: relatedController, + text: pd['SPECIAL_WORK'] ?? '', + ), + const Divider(), + ItemListWidget.twoRowButtonTitleText( + label: '风险辨识结果:', + isEditable: isEditable, + onTap: () async { + final val = await showDialog( + context: context, + builder: (_) => SelectionPopup( + type: 'identification', + initialValue: pd['RISK_IDENTIFICATION'] ?? '', + onConfirm: (v) => Navigator.of(context).pop(v), + ), + ); + if (val != null) pd['RISK_IDENTIFICATION'] = val; + FocusHelper.clearFocus(context); + }, + hintText: '请输入风险辨识结果', + controller: riskController, + text: pd['RISK_IDENTIFICATION'] ?? '', + ), + if (FormUtils.hasValue(pd, 'ANALYZE_TIME')) ...[ + const Divider(), + ItemListWidget.OneRowButtonTitleText( + label: '分析人:', + text: pd['ANALYZE_USER_NAME'] ?? '', + onTap: onAnalyzeTap, + ), + ], + ], + ); + } +} diff --git a/lib/pages/home/tap/tabList/special_wrok/aqcs_work_detail/hotwork_safe_func_sure.dart b/lib/pages/home/tap/tabList/special_wrok/aqcs_work_detail/hotwork_safe_func_sure.dart new file mode 100644 index 0000000..0e459ad --- /dev/null +++ b/lib/pages/home/tap/tabList/special_wrok/aqcs_work_detail/hotwork_safe_func_sure.dart @@ -0,0 +1,532 @@ +import 'dart:convert'; +import 'dart:io'; +import 'dart:math'; + +import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; +import 'package:qhd_prevention/customWidget/ItemWidgetFactory.dart'; +import 'package:qhd_prevention/customWidget/custom_button.dart'; +import 'package:qhd_prevention/customWidget/department_person_picker.dart'; +import 'package:qhd_prevention/customWidget/department_picker.dart'; +import 'package:qhd_prevention/customWidget/toast_util.dart'; +import 'package:qhd_prevention/pages/home/tap/item_list_widget.dart'; +import 'package:qhd_prevention/tools/tools.dart'; +import '../../../../../../customWidget/bottom_picker.dart'; +import '../../../../../../customWidget/custom_alert_dialog.dart'; +import '../../../../../../customWidget/single_image_viewer.dart'; +import '../../../../../../http/ApiService.dart'; +import '../../../../../mine/mine_sign_page.dart'; +import '../../../../../my_appbar.dart'; +import '../../special_Wrok/dh_work_detai/MeasuresListWidget.dart'; +import '../../special_Wrok/qtfx_work_detail/hotwork_gas_list.dart'; +import '../WorkDetailFormWidget.dart'; +/// 安全措施确认 +class HotworkSafeFuncSure extends StatefulWidget { + const HotworkSafeFuncSure({ + super.key, + required this.HOTWORK_ID, + required this.flow, + }); + + final String HOTWORK_ID; + final String flow; + + @override + State createState() => _HotworkSafeFuncSureState(); +} + +class _HotworkSafeFuncSureState extends State { + late bool isEditable = false; + final levelList = ["特级", "一级", "二级"]; + + /// 编辑还是新增 + late String msg = 'add'; + + /// 详情 + late Map pd = {}; + late List> measuresList = []; + + /// 动火人及证书编号 + late List workUserList = []; + + /// 安全防护措施列表 + late List measuresListCopy = []; + List imagePaths = []; + List signTimes = []; // 签字时间列表 + @override + void initState() { + super.initState(); + _getData(); + _getHotWorkNameList(); + addMeasuresListCopy(); + } + + String measuresListToJson() { + final List> jsonList = + measuresListCopy.map((item) => item.toJson()).toList(); + return jsonEncode(jsonList); + } + + Widget _card(Widget child) { + return Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(5), + ), + child: child, + ); + } + + + + /// 弹出单位选择 + void chooseUnitHandle(MeasureItem item) { + showModalBottomSheet( + context: context, + isScrollControlled: true, + barrierColor: Colors.black54, + backgroundColor: Colors.transparent, + builder: + (_) => DepartmentPicker( + onSelected: (id, name) async { + setState(() { + item.DEPARTMENT_ID = id; + item.DEPARTMENT_NAME = name; + }); + _getPersonListForUnitId(item); + }, + ), + ).then((_) {}); + } + + Future _getPersonListForUnitId(MeasureItem item) async { + // 拉取该单位的人员列表并缓存 + final result = await ApiService.getListTreePersonList(item.DEPARTMENT_ID); + setState(() { + item.userList = List>.from( + result['userList'] ?? >[], + ); + }); + } + + /// 弹出人员选择,需先选择单位 + void choosePersonHandle(MeasureItem item) async { + String unitId = item.DEPARTMENT_ID; + final personList = item.userList; + if (!unitId.isNotEmpty) { + ToastUtil.showNormal(context, '请先选择确认单位'); + return; + } + if (personList.isEmpty) { + // 一般这种情况是因为重新编辑没有缓存对应部门的负责人,所以先拉取一下接口 + await _getPersonListForUnitId(item); + final list = item.userList; + + if (list.isEmpty) { + // 如果还是没数据,说明该部门没有可选的人 + ToastUtil.showNormal(context, '暂无数据,请选择其他单位'); + } else { + choosePersonHandle(item); + } + return; + } + DepartmentPersonPicker.show( + context, + personsData: personList, + onSelected: (userId, name) { + setState(() { + item.USER_NAME = name; + item.USER_ID = userId; + print(json.encode(measuresListCopy)); + }); + }, + ).then((_) {}); + } + + /// 签字 + Future _sign() async { + final path = await Navigator.push( + context, + MaterialPageRoute(builder: (context) => MineSignPage()), + ); + if (path != null) { + final now = DateFormat('yyyy-MM-dd HH:mm').format(DateTime.now()); + + setState(() { + imagePaths.add(path); + signTimes.add(now); + FocusHelper.clearFocus(context); + }); + } + } + + Widget _signListWidget() { + return Column( + children: + imagePaths.map((path) { + return Column( + children: [ + const SizedBox(height: 10), + const Divider(), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + GestureDetector( + child: // 用一个 ConstrainedBox 限制最大尺寸,并改为 BoxFit.contain + ConstrainedBox( + constraints: const BoxConstraints( + maxWidth: 200, + maxHeight: 150, + ), + child: Image.file( + File(path), + // 改为完整显示 + fit: BoxFit.contain, + ), + ), + onTap: () { + presentOpaque( + SingleImageViewer(imageUrl: path), + context, + ); + }, + ), + Column( + children: [ + Container( + padding: const EdgeInsets.only(right: 5), + child: CustomButton( + text: 'X', + height: 30, + padding: const EdgeInsets.symmetric(horizontal: 10), + backgroundColor: Colors.red, + onPressed: () { + setState(() { + imagePaths.remove(path); + }); + }, + ), + ), + const SizedBox(height: 80), + ], + ), + ], + ), + ], + ); + }).toList(), + ); + } + + /// 提交 1 提交 0暂存 + Future _submit(String status) async { + if (imagePaths.isEmpty) { + ToastUtil.showNormal(context, '请签字'); + return; + } + List> signers = []; + String reasonText = ''; + + if (status == '1') { + int index = 0; + for (var item in measuresListCopy) { + if (item.USER_ID.isEmpty) { + ToastUtil.showNormal( + context, + '第${index + 1}项未设置确认人', + ); + return; + } + if (item.selectMeasures.isEmpty) { + ToastUtil.showNormal( + context, + '第${index + 1}项未选择安全措施', + ); + return; + } + final userId = item.USER_ID; + final selectMeasures = item.selectMeasures as List? ?? []; + + for (var item in selectMeasures) { + signers.add({ + 'BUS_HOTWORK_MEASURES_ID': item['BUS_HOTWORK_MEASURES_ID'], + 'USER_ID': userId, + }); + } + // 检查长度是否一致 + if (signers.length != measuresList.length) { + // 使用 ScaffoldMessenger 弹出提示 + ToastUtil.showNormal(context, '请为每个安全措施选择确认人'); + return; + } + index++; + } + } else { + await showDialog( + context: context, + builder: + (_) => CustomAlertDialog( + title: '作废原因', + mode: DialogMode.input, + hintText: '请输入作废原因', + cancelText: '取消', + confirmText: '确定', + onInputConfirm: (text) { + reasonText = text; + }, + ), + ); + if (reasonText.isEmpty) { + ToastUtil.showNormal(context, '请填写作废原因'); + return; + } + } + + final Map formData = {}; + // 提交参数 + formData['HOTWORK_ID'] = widget.HOTWORK_ID; + formData['SIGNTIME'] = signTimes.join(','); + formData['USER_ID'] = SessionService.instance.loginUserId; + formData['APPLY_STATUS'] = status; + formData['STEP_REASON'] = reasonText; + formData['PREPARERS'] = json.encode(signers); + + await showDialog( + context: context, + builder: + (_) => CustomAlertDialog( + title: '提示', + content: '请确认' + (status == '1' ? "通过" : "作废") + '本作业票?', + cancelText: '取消', + confirmText: '确定', + onConfirm: () async { + LoadingDialogHelper.show(context); + try { + final result = await ApiService.saveSafeFunctionSure( + formData, + imagePaths, + ); + LoadingDialogHelper.hide(context); + if (result['result'] == 'success') { + ToastUtil.showSuccess( + context, + status == '1' ? '提交成功' : '已暂存', + ); + Navigator.pop(context); + } + } catch (e) { + LoadingDialogHelper.hide(context); + ToastUtil.showNormal(context, '操作失败:$e'); + } + }, + ), + ); + } + + void printLongString(String text, {int chunkSize = 800}) { + final pattern = RegExp('.{1,$chunkSize}'); // 每 chunkSize 个字符一组 + for (final match in pattern.allMatches(text)) { + print(match.group(0)); + } + } + + Future _getHotWorkNameList() async { + final result = await ApiService.getHotWorkNameList(); + setState(() { + workUserList = result['varList'] ?? ''; + List names = + workUserList.map((item) => item['NAME'] as String).toList(); + }); + } + + /// 初始化拉取数据 + Future _getData() async { + final data = await ApiService.getHomeworkFindById(widget.HOTWORK_ID); + setState(() { + pd = data['pd']; + _getMeasures(); + }); + } + + Future _getMeasures() async { + final data = await ApiService.listSignSureAllMeasures(widget.HOTWORK_ID); + setState(() { + measuresList = List>.from( + data['measuresForSignList'] ?? >[], + ); + }); + } + + void removeMeasuresListCopy(int index) { + setState(() { + measuresListCopy.removeAt(index); + }); + } + + void addMeasuresListCopy() { + setState(() { + measuresListCopy.add( + MeasureItem( + id: Random().nextDouble(), + DEPARTMENT_ID: '', + DEPARTMENT_NAME: '', + USER_ID: '', + USER_NAME: '', + userList: [], + userIndex: -1, + selectMeasures: [], + ), + ); + }); + } + + /// 安全防护措施 + Widget _setSafeDetailWidget() { + return Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + ), + padding: EdgeInsets.symmetric(horizontal: 5), + child: Column( + children: [ + if (measuresList.length > 0) + Column( + children: [ + SizedBox(height: 20), + ListItemFactory.createBuildSimpleSection('安全防护措施'), + Container( + color: Colors.white, + child: MeasuresListWidget( + measuresList: + measuresList, // List> + baseImgPath: ApiService.baseImgPath, + ), + ), + ], + ), + SizedBox(height: 20), + + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + SizedBox(), + CustomButton( + text: '新增手写签字', + height: 36, + backgroundColor: Colors.green, + onPressed: () { + _sign(); + }, + ), + ], + ), + SizedBox(height: 10), + if (imagePaths.isNotEmpty) _signListWidget(), + ], + ), + ); + } + + /// 底部按钮 + Widget _bottomButtons() { + return Row( + spacing: 10, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: CustomButton( + height: 45, + textStyle: TextStyle(fontSize: 16, color: Colors.white), + text: '作废', + backgroundColor: Colors.red, + onPressed: () { + _submit('-1'); + }, + ), + ), + Expanded( + child: CustomButton( + textStyle: TextStyle(fontSize: 16, color: Colors.white), + text: '通过', + backgroundColor: Colors.green, + onPressed: () { + _submit('1'); + }, + ), + ), + ], + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: MyAppbar(title: '安全措施确认人意见'), + body: SafeArea( + child: SingleChildScrollView( + padding: EdgeInsets.all(12), + child: Column( + children: [ + // _card(_defaultDetail()), + _card( + WorkDetailFormWidget( + pd: pd, + isEditable: false, + onChooseLevel: (){}, + onChooseHotworkUser: (){}, + onAnalyzeTap: () { + pushPage( + HotworkGasList(HOTWORK_ID: widget.HOTWORK_ID), + context, + ); + }, + ), + ), + SizedBox(height: 20), + _setSafeDetailWidget(), + SizedBox(height: 20), + _bottomButtons(), + ], + ), + ), + ), + ); + } +} + +class MeasureItem { + final double id; + String DEPARTMENT_ID; + String DEPARTMENT_NAME; + String USER_ID; + String USER_NAME; + List> userList; + int userIndex; + List> selectMeasures; + + MeasureItem({ + required this.id, + this.DEPARTMENT_ID = '', + this.DEPARTMENT_NAME = '', + this.USER_ID = '', + this.USER_NAME = '', + List>? userList, + this.userIndex = -1, + List>? selectMeasures, + }) : userList = userList ?? [], + selectMeasures = selectMeasures ?? []; + + Map toJson() { + return { + 'id': id, + 'DEPARTMENT_ID': DEPARTMENT_ID, + 'DEPARTMENT_NAME': DEPARTMENT_NAME, + 'USER_ID': USER_ID, + 'USER_NAME': USER_NAME, + 'userList': userList, + 'userIndex': userIndex, + 'selectMeasures': selectMeasures, + }; + } +} diff --git a/lib/pages/home/tap/tabList/special_wrok/dh_work_detai/MeasuresListWidget.dart b/lib/pages/home/tap/tabList/special_wrok/dh_work_detai/MeasuresListWidget.dart index a9b26bb..e1af6dd 100644 --- a/lib/pages/home/tap/tabList/special_wrok/dh_work_detai/MeasuresListWidget.dart +++ b/lib/pages/home/tap/tabList/special_wrok/dh_work_detai/MeasuresListWidget.dart @@ -50,7 +50,6 @@ class MeasuresListWidget extends StatelessWidget { border: TableBorder( horizontalInside: BorderSide(color: Colors.grey.shade300), verticalInside: BorderSide(color: Colors.grey.shade300), - ), children: [ // 表头 @@ -155,26 +154,32 @@ class MeasuresListWidget extends StatelessWidget { padding: const EdgeInsets.only(top: 8), child: Column( children: [ - Padding(padding: EdgeInsets.symmetric(horizontal: 12), child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - '$question: ', - style: const TextStyle(fontWeight: FontWeight.w800), - ), - Text(answer.isNotEmpty ? answer : '0') - ], - ),), - Divider() + Padding( + padding: EdgeInsets.symmetric(horizontal: 12), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + '$question: ', + style: const TextStyle(fontWeight: FontWeight.w800), + ), + Text(answer.isNotEmpty ? answer : '0'), + ], + ), + ), + Divider(), ], - ) + ), ); } /// 构造一组图片 + 可选时间文本行 - List _buildImageRows(BuildContext context, List paths, String time) { - + List _buildImageRows( + BuildContext context, + List paths, + String time, + ) { return paths.map((p) { return Padding( padding: const EdgeInsets.only(top: 8), @@ -182,16 +187,16 @@ class MeasuresListWidget extends StatelessWidget { children: [ GestureDetector( onTap: () { - Navigator.of(context).push(PageRouteBuilder( - opaque: false, - pageBuilder: (_, __, ___) => SingleImageViewer(imageUrl: '$baseImgPath$p'), - )); + Navigator.of(context).push( + PageRouteBuilder( + opaque: false, + pageBuilder: + (_, __, ___) => + SingleImageViewer(imageUrl: '$baseImgPath$p'), + ), + ); }, - child: Image.network( - '$baseImgPath$p', - width: 80, - height: 80, - ), + child: Image.network('$baseImgPath$p', width: 80, height: 80), ), if (time.isNotEmpty) ...[const SizedBox(width: 8), Text(time)], ], @@ -200,6 +205,7 @@ class MeasuresListWidget extends StatelessWidget { }).toList(); } } + /// 其他安全防护措施表格组件 class OtherMeasuresWidget extends StatelessWidget { /// 其他安全防护措施数据列表 @@ -214,17 +220,17 @@ class OtherMeasuresWidget extends StatelessWidget { @override Widget build(BuildContext context) { - final list = (otherMeasures ?? []) - .where((e) => e is Map) - .map((e) => e as Map) - .toList(); + final list = + (otherMeasures ?? []) + .where((e) => e is Map) + .map((e) => e as Map) + .toList(); if (list.isEmpty) { return const SizedBox.shrink(); } return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Container( margin: const EdgeInsets.symmetric(horizontal: 10), decoration: BoxDecoration( @@ -244,13 +250,19 @@ class OtherMeasuresWidget extends StatelessWidget { Padding( padding: EdgeInsets.all(8), child: Center( - child: Text('其他安全措施', style: TextStyle(fontWeight: FontWeight.bold)), + child: Text( + '其他安全措施', + style: TextStyle(fontWeight: FontWeight.bold), + ), ), ), Padding( padding: EdgeInsets.all(8), child: Center( - child: Text('签字', style: TextStyle(fontWeight: FontWeight.bold)), + child: Text( + '签字', + style: TextStyle(fontWeight: FontWeight.bold), + ), ), ), ], @@ -291,29 +303,35 @@ class OtherMeasuresWidget extends StatelessWidget { return Wrap( spacing: 8, runSpacing: 8, - children: paths.map((p) { - return GestureDetector( - onTap: () { - Navigator.of(context).push(MaterialPageRoute( - builder: (_) => SingleImageViewer(imageUrl: '$baseImgPath$p'), - )); - }, - child: Image.network( - '$baseImgPath$p', - width: 60, - height: 60, - errorBuilder: (c, o, s) => const Icon(Icons.broken_image), - ), - ); - }).toList(), + children: + paths.map((p) { + return GestureDetector( + onTap: () { + Navigator.of(context).push( + MaterialPageRoute( + builder: + (_) => SingleImageViewer(imageUrl: '$baseImgPath$p'), + ), + ); + }, + child: Image.network( + '$baseImgPath$p', + width: 60, + height: 60, + errorBuilder: (c, o, s) => const Icon(Icons.broken_image), + ), + ); + }).toList(), ); } } + /// 各角色签名展示组件 class SignaturesListWidget extends StatelessWidget { final Map? signs; final Map? pd; final String baseImgPath; + const SignaturesListWidget({ Key? key, this.signs, @@ -327,22 +345,96 @@ class SignaturesListWidget extends StatelessWidget { final safePd = pd ?? {}; return Column( children: [ - _buildSection(context, '监护人', safeSigns['GUARDIAN'], safePd['GUARDIAN_USER_NAME'], EditUserType.GUARDIAN), - _buildSection(context, '安全交底人', safeSigns['CONFESS'], safePd['CONFESS_USER_NAME'], EditUserType.CONFESS), - _buildSection(context, '接受交底人', safeSigns['ACCEPT_CONFESS'], safePd['ACCEPT_CONFESS_USER_NAME'], EditUserType.ACCEPT_CONFESS), - _buildTextareaWithSigns(context, '作业负责人意见', safeSigns['CONFIRM'], safePd['CONFIRM_USER_NAME'], EditUserType.CONFIRM), - _buildTextareaWithSigns(context, '所在单位意见', safeSigns['LEADER'], safePd['LEADER_USER_NAME'], EditUserType.LEADER), - _buildTextareaWithSigns(context, '安全管理部门意见', safeSigns['AUDIT'], safePd['AUDIT_USER_NAME'], EditUserType.AUDIT), - _buildTextareaWithSigns(context, '动火审批人意见', safeSigns['APPROVE'], safePd['APPROVE_USER_NAME'], EditUserType.APPROVE), - _buildTextareaWithSigns(context, '动火前在岗班长意见', safeSigns['MONITOR'], safePd['MONITOR_USER_NAME'], EditUserType.MONITOR), - _buildSection(context, '作业开始负责人', safeSigns['WORK_START'], safePd['WORK_START_USER_NAME'], EditUserType.WORK_START), - _buildSection(context, '作业结束负责人', safeSigns['WORK_END'], safePd['WORK_END_USER_NAME'], EditUserType.WORK_END), - _buildTextareaWithSigns(context, '完工验收', safeSigns['ACCEPT'], safePd['ACCEPT_USER_NAME'], EditUserType.ACCEPT, timeKey: 'ACCEPT_TIME'), + _buildSection( + context, + '监护人', + safeSigns['GUARDIAN'], + safePd['GUARDIAN_USER_NAME'], + EditUserType.GUARDIAN, + ), + _buildSection( + context, + '安全交底人', + safeSigns['CONFESS'], + safePd['CONFESS_USER_NAME'], + EditUserType.CONFESS, + ), + _buildSection( + context, + '接受交底人', + safeSigns['ACCEPT_CONFESS'], + safePd['ACCEPT_CONFESS_USER_NAME'], + EditUserType.ACCEPT_CONFESS, + ), + _buildTextareaWithSigns( + context, + '作业负责人意见', + safeSigns['CONFIRM'], + safePd['CONFIRM_USER_NAME'], + EditUserType.CONFIRM, + ), + _buildTextareaWithSigns( + context, + '所在单位意见', + safeSigns['LEADER'], + safePd['LEADER_USER_NAME'], + EditUserType.LEADER, + ), + _buildTextareaWithSigns( + context, + '安全管理部门意见', + safeSigns['AUDIT'], + safePd['AUDIT_USER_NAME'], + EditUserType.AUDIT, + ), + _buildTextareaWithSigns( + context, + '动火审批人意见', + safeSigns['APPROVE'], + safePd['APPROVE_USER_NAME'], + EditUserType.APPROVE, + ), + _buildTextareaWithSigns( + context, + '动火前在岗班长意见', + safeSigns['MONITOR'], + safePd['MONITOR_USER_NAME'], + EditUserType.MONITOR, + ), + _buildSection( + context, + '作业开始负责人', + safeSigns['WORK_START'], + safePd['WORK_START_USER_NAME'], + EditUserType.WORK_START, + ), + _buildSection( + context, + '作业结束负责人', + safeSigns['WORK_END'], + safePd['WORK_END_USER_NAME'], + EditUserType.WORK_END, + ), + _buildTextareaWithSigns( + context, + '完工验收', + safeSigns['ACCEPT'], + safePd['ACCEPT_USER_NAME'], + EditUserType.ACCEPT, + timeKey: 'ACCEPT_TIME', + ), ], ); } - Widget _buildSection(BuildContext context, String title, dynamic rawList, dynamic userName, EditUserType type, {bool showImages = false}) { + Widget _buildSection( + BuildContext context, + String title, + dynamic rawList, + dynamic userName, + EditUserType type, { + bool showImages = false, + }) { if (rawList is! List || rawList.isEmpty) return const SizedBox.shrink(); final list = rawList.cast>(); final first = list.first; @@ -364,20 +456,38 @@ class SignaturesListWidget extends StatelessWidget { return Container( margin: const EdgeInsets.only(top: 20), - decoration: BoxDecoration(color: Colors.white,border: Border.symmetric(vertical: BorderSide(color: Colors.grey.shade300))), + decoration: BoxDecoration( + color: Colors.white, + border: Border.symmetric( + vertical: BorderSide(color: Colors.grey.shade300), + ), + ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( padding: const EdgeInsets.all(8), - child: Text('$title: $name', style: const TextStyle(fontWeight: FontWeight.bold)), + child: Text( + '$title: $name', + style: const TextStyle(fontWeight: FontWeight.bold), + ), ), if (showImages && first['IMG_PATH'] is List) Padding( padding: const EdgeInsets.symmetric(horizontal: 20), child: Wrap( spacing: 8, - children: (first['IMG_PATH'] as List).cast().map((img) => Image.network('$baseImgPath$img', width: 50, height: 50)).toList(), + children: + (first['IMG_PATH'] as List) + .cast() + .map( + (img) => Image.network( + '$baseImgPath$img', + width: 50, + height: 50, + ), + ) + .toList(), ), ), for (var i = 0; i < signPaths.length; i++) @@ -386,11 +496,27 @@ class SignaturesListWidget extends StatelessWidget { child: Row( children: [ GestureDetector( - onTap: () => Navigator.of(context).push(MaterialPageRoute(builder: (_) => SingleImageViewer(imageUrl: '$baseImgPath${signPaths[i]}'))), - child: Image.network('$baseImgPath${signPaths[i]}', width: 100, height: 100, errorBuilder: (_, __, ___) => const Icon(Icons.broken_image)), + onTap: + () => Navigator.of(context).push( + MaterialPageRoute( + builder: + (_) => SingleImageViewer( + imageUrl: '$baseImgPath${signPaths[i]}', + ), + ), + ), + child: Image.network( + '$baseImgPath${signPaths[i]}', + width: 100, + height: 100, + errorBuilder: + (_, __, ___) => const Icon(Icons.broken_image), + ), ), const SizedBox(width: 16), - Expanded(child: Text(i < signTimes.length ? signTimes[i] : '')), + Expanded( + child: Text(i < signTimes.length ? signTimes[i] : ''), + ), ], ), ), @@ -399,14 +525,20 @@ class SignaturesListWidget extends StatelessWidget { ); } - Widget _buildTextareaWithSigns(BuildContext context, String label, dynamic rawList, dynamic userName, EditUserType type, {String? timeKey}) { + Widget _buildTextareaWithSigns( + BuildContext context, + String label, + dynamic rawList, + dynamic userName, + EditUserType type, { + String? timeKey, + }) { if (rawList is! List || rawList.isEmpty) return const SizedBox.shrink(); final first = (rawList as List).cast>().first; final descr = first['DESCR'] is String ? first['DESCR'] as String : ''; final name = pd?['${type.name}_USER_NAME']; final personDes = type.personName; - final signPaths = []; final signTimes = []; if (first['SIGN_PATH'] != null) { @@ -422,7 +554,12 @@ class SignaturesListWidget extends StatelessWidget { return Container( margin: const EdgeInsets.only(top: 20), - decoration: BoxDecoration(color: Colors.white,border: Border.symmetric(vertical: BorderSide(color: Colors.grey.shade300))), + decoration: BoxDecoration( + color: Colors.white, + border: Border.symmetric( + vertical: BorderSide(color: Colors.grey.shade300), + ), + ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -431,9 +568,17 @@ class SignaturesListWidget extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text(label, style: const TextStyle(fontWeight: FontWeight.bold)), + Text( + label, + style: const TextStyle(fontWeight: FontWeight.bold), + ), const SizedBox(height: 4), - TextField(controller: TextEditingController(text: descr), maxLines: null, readOnly: true, decoration: const InputDecoration(border: InputBorder.none)), + TextField( + controller: TextEditingController(text: descr), + maxLines: null, + readOnly: true, + decoration: const InputDecoration(border: InputBorder.none), + ), ], ), ), @@ -447,11 +592,27 @@ class SignaturesListWidget extends StatelessWidget { child: Row( children: [ GestureDetector( - onTap: () => Navigator.of(context).push(MaterialPageRoute(builder: (_) => SingleImageViewer(imageUrl: '$baseImgPath${signPaths[i]}'))), - child: Image.network('$baseImgPath${signPaths[i]}', width: 100, height: 100, errorBuilder: (_, __, ___) => const Icon(Icons.broken_image)), + onTap: + () => Navigator.of(context).push( + MaterialPageRoute( + builder: + (_) => SingleImageViewer( + imageUrl: '$baseImgPath${signPaths[i]}', + ), + ), + ), + child: Image.network( + '$baseImgPath${signPaths[i]}', + width: 100, + height: 100, + errorBuilder: + (_, __, ___) => const Icon(Icons.broken_image), + ), ), const SizedBox(width: 16), - Expanded(child: Text(i < signTimes.length ? signTimes[i] : '')), + Expanded( + child: Text(i < signTimes.length ? signTimes[i] : ''), + ), ], ), ), @@ -460,12 +621,15 @@ class SignaturesListWidget extends StatelessWidget { ); } } + ///动火安全申请里的’选择其他‘弹窗 class SelectionPopup extends StatefulWidget { /// 类型: 'assignments' 或 'identification' final String type; + /// 初始选中值,以逗号分隔 final String initialValue; + /// 确认回调 final void Function(String) onConfirm; @@ -514,22 +678,20 @@ class _SelectionPopupState extends State { } Future _pickDate() async { - showDialog( context: context, builder: (_) => HDatePickerDialog( - initialDate: DateTime.now(), - onCancel: () => Navigator.of(context).pop(), - onConfirm: (selected) { - Navigator.of(context).pop(); - setState(() { - selectedDate = selected; - }); - }, - ), + initialDate: DateTime.now(), + onCancel: () => Navigator.of(context).pop(), + onConfirm: (selected) { + Navigator.of(context).pop(); + setState(() { + selectedDate = selected; + }); + }, + ), ); - } Future _getData() async { @@ -538,7 +700,8 @@ class _SelectionPopupState extends State { if (widget.type == 'assignments') { params = { 'WORK_TYPE': selectedWorkType, - 'KEYWORDS': selectedDate == null ? '' : selectedDate!.toString().split(' ')[0], + 'KEYWORDS': + selectedDate == null ? '' : selectedDate!.toString().split(' ')[0], 'CORPINFO_ID': SessionService.instance.corpinfoId, }; } else { @@ -567,14 +730,12 @@ class _SelectionPopupState extends State { item['CHECK_NO'] = (prefixMap[type] ?? '') + ' ' + no; } }); - - }else{ + } else { final result = await ApiService.getEightWorkInfo(params); setState(() { - list = (result['accidentType'] as List).cast>(); + list = (result['accidentType'] as List).cast>(); }); } - } catch (e) { ToastUtil.showError(context, '$e'); } @@ -582,15 +743,16 @@ class _SelectionPopupState extends State { void _reset() { setState(() { - selectedWorkType = ''; - selectedWorkName = '请选择'; - selectedDate = null; + // 回到下拉列表的初始选项(第 0 项) + selectedWorkType = workList[0]['WORK_TYPE']!; + selectedWorkName = workList[0]['WORK_NAME']!; + + selectedDate = null; list.clear(); selectValue.clear(); }); _getData(); } - void _determine() { // 合并选中 final result = selectValue.join(','); @@ -610,51 +772,76 @@ class _SelectionPopupState extends State { children: [ // 筛选栏 if (widget.type == 'assignments') - Padding( - padding: const EdgeInsets.all(12), - child: Row( - children: [ - // 作业类型下拉 - Expanded( - child: DropdownButton( - dropdownColor: Colors.white, - style: TextStyle(), - isExpanded: true, - value: selectedWorkName, - items: workList - .map((e) => DropdownMenuItem( - value: e['WORK_NAME'], - child: Text(e['WORK_NAME']!, style: TextStyle(color: Colors.black87),), - )) - .toList(), - onChanged: (v) { - final idx = workList.indexWhere((e) => e['WORK_NAME'] == v); - if (idx >= 0) { - setState(() { - selectedWorkType = workList[idx]['WORK_TYPE']!; - selectedWorkName = v!; - }); - _getData(); - } - }, + Padding( + padding: const EdgeInsets.all(12), + child: Row( + children: [ + // 作业类型下拉 + Expanded( + child: DropdownButton( + dropdownColor: Colors.white, + style: TextStyle(), + isExpanded: true, + value: selectedWorkName, + items: + workList + .map( + (e) => DropdownMenuItem( + value: e['WORK_NAME'], + child: Text( + e['WORK_NAME']!, + style: TextStyle(color: Colors.black87), + ), + ), + ) + .toList(), + onChanged: (v) { + final idx = workList.indexWhere( + (e) => e['WORK_NAME'] == v, + ); + if (idx >= 0) { + setState(() { + selectedWorkType = workList[idx]['WORK_TYPE']!; + selectedWorkName = v!; + }); + _getData(); + } + }, + ), ), - ), - const SizedBox(width: 12), + const SizedBox(width: 12), - TextButton(onPressed: _pickDate, child: Row( - children: [ - Text(selectedDate == null - ? '选择作业申请时间' - : selectedDate!.toString().split(' ')[0],style: TextStyle(color: Colors.blue),), - SizedBox(width: 5,), - Icon(Icons.arrow_drop_down, color: Colors.grey, size: 20,), ], - )), + TextButton( + onPressed: _pickDate, + child: Row( + children: [ + Text( + selectedDate == null + ? '选择作业申请时间' + : selectedDate!.toString().split(' ')[0], + style: TextStyle(color: Colors.blue), + ), + SizedBox(width: 5), + Icon( + Icons.arrow_drop_down, + color: Colors.grey, + size: 20, + ), + ], + ), + ), - // 清空 - CustomButton(text: '清空',padding: EdgeInsets.symmetric(horizontal: 15),height: 35, backgroundColor: Colors.blue, onPressed: _reset,) - ], + // 清空 + CustomButton( + text: '清空', + padding: EdgeInsets.symmetric(horizontal: 15), + height: 35, + backgroundColor: Colors.blue, + onPressed: _reset, + ), + ], + ), ), - ), const Divider(), // 列表多选 Expanded( @@ -664,9 +851,10 @@ class _SelectionPopupState extends State { itemBuilder: (c, i) { final item = list[i]; final value = selectValue; - final key = widget.type == 'assignments' - ? item['CHECK_NO'] as String? ?? '' - : item['NAME'] as String? ?? ''; + final key = + widget.type == 'assignments' + ? item['CHECK_NO'] as String? ?? '' + : item['NAME'] as String? ?? ''; final checked = value.contains(key); return CheckboxListTile( activeColor: Colors.blue, @@ -700,11 +888,22 @@ class _SelectionPopupState extends State { child: Row( children: [ Expanded( - child:CustomButton(text: '确定', height: 40, backgroundColor: Colors.blue, onPressed: _determine,) + child: CustomButton( + text: '确定', + height: 40, + backgroundColor: Colors.blue, + onPressed: _determine, + ), ), const SizedBox(width: 12), Expanded( - child: CustomButton(text: '关闭', height: 40, backgroundColor: Colors.grey.shade300, textStyle: TextStyle(color: Colors.grey.shade600), onPressed: () => Navigator.of(context).pop(),) + child: CustomButton( + text: '关闭', + height: 40, + backgroundColor: Colors.grey.shade300, + textStyle: TextStyle(color: Colors.grey.shade600), + onPressed: () => Navigator.of(context).pop(), + ), ), ], ), diff --git a/lib/pages/home/tap/tabList/special_wrok/dh_work_detai/hotwork_apply_detail.dart b/lib/pages/home/tap/tabList/special_wrok/dh_work_detai/hotwork_apply_detail.dart index 7f4e596..a81cf5c 100644 --- a/lib/pages/home/tap/tabList/special_wrok/dh_work_detai/hotwork_apply_detail.dart +++ b/lib/pages/home/tap/tabList/special_wrok/dh_work_detai/hotwork_apply_detail.dart @@ -7,6 +7,7 @@ import 'package:qhd_prevention/customWidget/department_person_picker.dart'; import 'package:qhd_prevention/customWidget/department_picker.dart'; import 'package:qhd_prevention/customWidget/toast_util.dart'; import 'package:qhd_prevention/pages/home/tap/item_list_widget.dart'; +import 'package:qhd_prevention/pages/home/tap/tabList/special_wrok/WorkDetailFormWidget.dart'; import 'package:qhd_prevention/tools/tools.dart'; import '../../../../../../customWidget/bottom_picker.dart'; import '../../../../../../http/ApiService.dart'; @@ -159,12 +160,10 @@ class _HotworkApplyDetailState extends State { pd['WORK_LEVEL'] = choice; FocusHelper.clearFocus(context); }); - } } - Future _chooseHorkUser() async{ - + Future _chooseHorkUser() async { final choice = await BottomPicker.show( context, items: workUserList.map((item) => item['NAME'] as String).toList(), @@ -176,7 +175,7 @@ class _HotworkApplyDetailState extends State { pd['WORK_USER'] = choice; _hotworkPersonController.text = choice; Map result = workUserList.firstWhere( - (item) => item['NAME'] == choice, + (item) => item['NAME'] == choice, orElse: () => {}, // 避免找不到时报错 ); if (FormUtils.hasValue(result, 'USER_ID')) { @@ -184,172 +183,9 @@ class _HotworkApplyDetailState extends State { } FocusHelper.clearFocus(context); }); - } } - Widget _defaultDetail() { - return Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - ItemListWidget.singleLineTitleText( - label: '申请单位:', - isEditable: false, - text: pd['APPLY_DEPARTMENT_NAME'] ?? '', - ), - Divider(), - ItemListWidget.singleLineTitleText( - label: '申请人:', - isEditable: false, - text: pd['APPLY_USER_NAME'] ?? '', - ), - if (FormUtils.hasValue(pd, 'CHECK_NO')) - Column( - children: [ - Divider(), - ItemListWidget.singleLineTitleText( - label: '编号:', - isEditable: false, - text: pd['CHECK_NO'] ?? '', - ), - ], - ), - - Divider(), - ItemListWidget.multiLineTitleTextField( - label: '作业内容:', - isEditable: isEditable, - controller: _contentController, - text: pd['WORK_CONTENT'] ?? '', - ), - Divider(), - ItemListWidget.singleLineTitleText( - label: '动火地点及动火部位:', - isEditable: isEditable, - controller: _locationController, - text: pd['WORK_PLACE'] ?? '', - ), - Divider(), - ItemListWidget.selectableLineTitleTextField( - label: '动火作业级别', - isEditable: isEditable, - onTap: () { - _chooseLevel(); - }, - text: pd['WORK_LEVEL'] ?? '', - ), - Divider(), - ItemListWidget.singleLineTitleText( - label: '动火方式:', - isEditable: isEditable, - controller: _methodController, - text: pd['WORK_FUNCTION'] ?? '', - ), - if (pd['WORK_START_DATE'] != null && - pd['WORK_START_DATE'].toString().isNotEmpty) - Column( - children: [ - Divider(), - ItemListWidget.singleLineTitleText( - label: '动火作业\n实施时间:', - isEditable: isEditable, - controller: _methodController, - text: - pd['WORK_START_DATE'] ?? - '' + - '至' + - (pd['WORK_END_DATE'] - ? pd['WORK_END_DATE'] ?? '' - : '--') ?? - '', - ), - ], - ), - Divider(), - ItemListWidget.twoRowSelectableTitleText( - label: '动火人及证书编号:', - isEditable: isEditable, - onTap: () { - _chooseHorkUser(); - }, - controller: _hotworkPersonController, - text: pd['WORK_USER'] ?? '', - ), - Divider(), - ItemListWidget.twoRowButtonTitleText( - label: '关联的其他特殊作业及安全作业票编号', - isEditable: isEditable, - onTap: () { - showDialog( - context: context, - builder: (_) => SelectionPopup( - type: 'assignments', - initialValue: pd['SPECIAL_WORK'] ?? '', - onConfirm: (val) { - // val 为逗号分隔的选中值 - setState(() { - pd['SPECIAL_WORK'] = val; - _relatedController.text = val; - }); - }, - ), - ).then((_) { - FocusHelper.clearFocus(context); - }); - // identification - }, - hintText: '请输入关联的其他特殊作业及安全作业票编号', - controller: _relatedController, - text: pd['SPECIAL_WORK'] ?? '', - ), - Divider(), - ItemListWidget.twoRowButtonTitleText( - label: '风险辨识结果', - isEditable: isEditable, - onTap: () { - - showDialog( - context: context, - builder: (_) => SelectionPopup( - type: 'identification', - initialValue: pd['RISK_IDENTIFICATION'] ?? '', - onConfirm: (val) { - // val 为逗号分隔的选中值 - setState(() { - pd['RISK_IDENTIFICATION'] = val; - _riskController.text = val; - }); - }, - ), - ).then((_) { - FocusHelper.clearFocus(context); - - }); - }, - hintText: '请输入风险辨识结果', - controller: _riskController, - text: pd['RISK_IDENTIFICATION'] ?? '', - ), - if (FormUtils.hasValue(pd, 'ANALYZE_TIME')) - Column( - children: [ - Divider(), - ItemListWidget.OneRowButtonTitleText( - label: '分析人', - text: pd['ANALYZE_USER_NAME'] ?? '', - onTap: () { - pushPage( - HotworkGasList(HOTWORK_ID: widget.HOTWORK_ID), - context, - ); - }, - ), - ], - ), - ], - ); - } - Widget _card(Widget child) { return Container( decoration: BoxDecoration( @@ -361,11 +197,32 @@ class _HotworkApplyDetailState extends State { } Widget _chooseItem(EditUserType type) { + bool isClean = false; + + if (isEditable) { + if (type == EditUserType.AUDIT && (pd['WORK_LEVEL'] ?? '') == '二级') { + isClean = true; + } + if (type == EditUserType.APPROVE && + ((pd['WORK_LEVEL'] ?? '') == '二级' || + (pd['WORK_LEVEL'] ?? '') == '一级')) { + isClean = true; + } + } + return Column( children: [ ItemListWidget.selectableLineTitleTextField( label: type.displayName, isEditable: isEditable, + isClean: isClean, + onTapClean: () { + setState(() { + pd['${type.name}_DEPARTMENT_NAME'] = ''; + pd['${type.name}_USER_NAME'] = ''; + _personCache.remove(type); + }); + }, text: pd['${type.name}_DEPARTMENT_NAME'] ?? '请选择', onTap: () => chooseUnitHandle(type), ), @@ -416,7 +273,7 @@ class _HotworkApplyDetailState extends State { } /// 弹出人员选择,需先选择单位 - void choosePersonHandle(EditUserType type) async{ + void choosePersonHandle(EditUserType type) async { FocusHelper.clearFocus(context); String unitId = get_pd_DEPARTMENT_ID(type); @@ -426,17 +283,22 @@ class _HotworkApplyDetailState extends State { ToastUtil.showNormal(context, '请先选择$unitName'); return; } - if (personList.isEmpty) { // 一般这种情况是因为重新编辑没有缓存对应部门的负责人,所以先拉取一下接口 - await _getPersonListForUnitId(unitId, type); - final list = _personCache[type] ?? []; - - if (list.isEmpty) { // 如果还是没数据,说明该部门没有可选的人 - ToastUtil.showNormal(context, '暂无数据,请选择其他单位'); - }else{ - choosePersonHandle(type); - } + if (personList.isEmpty) { + // 如果还是没数据,说明该部门没有可选的人 + ToastUtil.showNormal(context, '请先选择' + type.displayName); return; } + // if (personList.isEmpty) { // 一般这种情况是因为重新编辑没有缓存对应部门的负责人,所以先拉取一下接口 + // await _getPersonListForUnitId(unitId, type); + // final list = _personCache[type] ?? []; + // + // if (list.isEmpty) { // 如果还是没数据,说明该部门没有可选的人 + // ToastUtil.showNormal(context, '暂无数据,请选择其他单位'); + // }else{ + // choosePersonHandle(type); + // } + // return; + // } DepartmentPersonPicker.show( context, personsData: personList, @@ -467,6 +329,7 @@ class _HotworkApplyDetailState extends State { ]; final level = pd['WORK_LEVEL'] ?? ''; print('---level-$level'); + /// 各项负责人校验 final unitRules = [ EditUserType.ANALYZE, @@ -529,11 +392,11 @@ class _HotworkApplyDetailState extends State { pd['TASK_ID'] = taskId; pd['HOTWORK_ID'] = widget.HOTWORK_ID; pd['APPLY_DEPARTMENT_ID'] = SessionService.instance.deptId; - pd['APPLY_DEPARTMENT_NAME'] = SessionService.instance.loginUser?['DEPARTMENT_NAME'] ?? ''; + pd['APPLY_DEPARTMENT_NAME'] = + SessionService.instance.loginUser?['DEPARTMENT_NAME'] ?? ''; pd['APPLY_USER_ID'] = SessionService.instance.loginUserId; pd['APPLY_USER_NAME'] = SessionService.instance.username; pd['USER_ID'] = SessionService.instance.loginUserId; - } LoadingDialogHelper.show(context); @@ -552,20 +415,23 @@ class _HotworkApplyDetailState extends State { ToastUtil.showNormal(context, '操作失败:$e'); } } + void printLongString(String text, {int chunkSize = 800}) { final pattern = RegExp('.{1,$chunkSize}'); // 每 chunkSize 个字符一组 for (final match in pattern.allMatches(text)) { print(match.group(0)); } } + Future _getHotWorkNameList() async { final result = await ApiService.getHotWorkNameList(); setState(() { workUserList = result['varList'] ?? ''; - List names = workUserList.map((item) => item['NAME'] as String).toList(); - + List names = + workUserList.map((item) => item['NAME'] as String).toList(); }); } + /// 初始化拉取数据 Future _getData() async { final data = await ApiService.getHomeworkFindById(widget.HOTWORK_ID); @@ -584,7 +450,6 @@ class _HotworkApplyDetailState extends State { _hotworkPersonController.text = pd['WORK_USER'] ?? ''; _relatedController.text = pd['SPECIAL_WORK'] ?? ''; _riskController.text = pd['RISK_IDENTIFICATION'] ?? ''; - }); // final data = await ApiService.getHomeworkFindById(widget.HOTWORK_ID); // setState(() { @@ -622,7 +487,26 @@ class _HotworkApplyDetailState extends State { padding: EdgeInsets.all(12), child: Column( children: [ - _card(_defaultDetail()), + _card( + WorkDetailFormWidget( + pd: pd, + isEditable: isEditable, + contentController: _contentController, + locationController: _locationController, + methodController: _methodController, + hotworkPersonController: _hotworkPersonController, + relatedController: _relatedController, + riskController: _riskController, + onChooseLevel: _chooseLevel, + onChooseHotworkUser: _chooseHorkUser, + onAnalyzeTap: () { + pushPage( + HotworkGasList(HOTWORK_ID: widget.HOTWORK_ID), + context, + ); + }, + ), + ), if (isEditable) Column( children: [ diff --git a/lib/pages/home/tap/tabList/special_wrok/home_gas_test_page.dart b/lib/pages/home/tap/tabList/special_wrok/home_gas_test_page.dart index 838afe4..063804c 100644 --- a/lib/pages/home/tap/tabList/special_wrok/home_gas_test_page.dart +++ b/lib/pages/home/tap/tabList/special_wrok/home_gas_test_page.dart @@ -12,6 +12,7 @@ import 'package:qhd_prevention/pages/mine/mine_sign_page.dart'; import '../../../../../customWidget/custom_alert_dialog.dart'; import '../../../../../customWidget/picker/CupertinoDatePicker.dart'; +import '../../../../../customWidget/single_image_viewer.dart'; class HomeGasTestPage extends StatefulWidget { const HomeGasTestPage({Key? key, required this.HOTWORK_ID}) : super(key: key); @@ -68,21 +69,29 @@ class _HomeGasTestPageState extends State { return Column( children: [ const SizedBox(height: 10), - // const DottedLine( - // dashLength: 6.0, - // dashGapLength: 4.0, - // lineThickness: 0.5, - // dashColor: Colors.grey, - // ), const Divider(), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Image.file( - File(path), - width: 200, - height: 150, - fit: BoxFit.cover, + GestureDetector( + child: // 用一个 ConstrainedBox 限制最大尺寸,并改为 BoxFit.contain + ConstrainedBox( + constraints: const BoxConstraints( + maxWidth: 200, + maxHeight: 150, + ), + child: Image.file( + File(path), + // 改为完整显示 + fit: BoxFit.contain, + ), + ), + onTap: () { + presentOpaque( + SingleImageViewer(imageUrl: path), + context, + ); + }, ), Column( children: [ diff --git a/lib/pages/home/tap/tabList/special_wrok/special_work_list_page.dart b/lib/pages/home/tap/tabList/special_wrok/special_work_list_page.dart index 268735a..cce2cf0 100644 --- a/lib/pages/home/tap/tabList/special_wrok/special_work_list_page.dart +++ b/lib/pages/home/tap/tabList/special_wrok/special_work_list_page.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:qhd_prevention/customWidget/toast_util.dart'; import 'package:qhd_prevention/pages/home/tap/tabList/special_Wrok/dh_work_detai/hotwork_apply_detail.dart'; +import 'package:qhd_prevention/pages/home/tap/tabList/special_wrok/aqcs_work_detail/hotwork_safe_func_sure.dart'; import 'package:qhd_prevention/pages/home/tap/tabList/special_wrok/szaq_work_detail/hotwork_set_safe_detail.dart'; import 'package:qhd_prevention/pages/my_appbar.dart'; import 'package:qhd_prevention/tools/tools.dart'; @@ -196,7 +197,7 @@ class _SpecialWorkListPageState extends State { await pushPage(HotworkSetSafeDetail(HOTWORK_ID: item['HOTWORK_ID'], flow: widget.flow), context); break; case '安全措施确认': - routeName = '/hotwork-measures-confirm-detail'; + await pushPage(HotworkSafeFuncSure(HOTWORK_ID: item['HOTWORK_ID'], flow: widget.flow), context); break; case '监护人签字': routeName = '/hotwork-guardian-detail'; diff --git a/lib/pages/home/tap/tabList/special_wrok/szaq_work_detail/SafeFunctionDialog.dart b/lib/pages/home/tap/tabList/special_wrok/szaq_work_detail/SafeFunctionDialog.dart index ea5bbd0..a801349 100644 --- a/lib/pages/home/tap/tabList/special_wrok/szaq_work_detail/SafeFunctionDialog.dart +++ b/lib/pages/home/tap/tabList/special_wrok/szaq_work_detail/SafeFunctionDialog.dart @@ -3,15 +3,20 @@ import 'package:qhd_prevention/customWidget/custom_button.dart'; /// 安全措施弹窗 class SafeFunctionDialog extends StatefulWidget { + /// 原始数据列表,每个元素为包含 "PROTECTIVE_MEASURES" 键的 Map final List> data; - /// 点击确认回调,返回选中的措施列表 - final void Function(List selectedMeasures) onConfirm; + /// 已选中的初始数据列表 + final List> initialSelectedItems; - /// 构造函数 + /// 点击确认回调,返回选中的 Map 列表 + final void Function(List> selectedItems) onConfirm; + + /// 构造函数,支持传入已选中的数据 const SafeFunctionDialog({ Key? key, required this.data, + this.initialSelectedItems = const [], required this.onConfirm, }) : super(key: key); @@ -20,19 +25,19 @@ class SafeFunctionDialog extends StatefulWidget { } class _SafeFunctionDialogState extends State { - /// 存放所有可选项的文字 - late final List _allMeasures; + /// 存放所有可选项的 Map 列表 + late final List> _allItems; - /// 存放当前被选中的文字 - final List _selectedMeasures = []; + /// 存放当前被选中的 Map 列表 + late final List> _selectedItems; @override void initState() { super.initState(); - _allMeasures = - widget.data - .map((e) => e['PROTECTIVE_MEASURES'] as String? ?? '') - .toList(); + // 初始化所有选项 + _allItems = widget.data; + // 初始化已选中项,克隆一份避免修改原数据 + _selectedItems = List>.from(widget.initialSelectedItems); } @override @@ -41,32 +46,30 @@ class _SafeFunctionDialogState extends State { insetPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 24), content: SizedBox( width: MediaQuery.of(context).size.width - 24, - height: - MediaQuery.of(context).size.height - + height: MediaQuery.of(context).size.height - MediaQuery.of(context).padding.top - MediaQuery.of(context).padding.bottom, child: Column( children: [ Expanded( child: ListView.builder( - itemCount: _allMeasures.length, + itemCount: _allItems.length, itemBuilder: (ctx, index) { - final measure = _allMeasures[index]; - final checked = _selectedMeasures.contains(measure); + final item = _allItems[index]; + final label = item['PROTECTIVE_MEASURES'] as String? ?? ''; + final checked = _selectedItems.contains(item); return CheckboxListTile( - // 将复选框移到左侧 controlAffinity: ListTileControlAffinity.leading, - // 调整左右间隙 contentPadding: const EdgeInsets.symmetric(horizontal: 0), activeColor: Colors.blue, - title: Text(measure, style: const TextStyle(fontSize: 14)), + title: Text(label, style: const TextStyle(fontSize: 14)), value: checked, onChanged: (value) { setState(() { if (value == true) { - _selectedMeasures.add(measure); + _selectedItems.add(item); } else { - _selectedMeasures.remove(measure); + _selectedItems.remove(item); } }); }, @@ -83,7 +86,7 @@ class _SafeFunctionDialogState extends State { height: 40, backgroundColor: Colors.blue, onPressed: () { - widget.onConfirm(_selectedMeasures); + widget.onConfirm(_selectedItems); Navigator.of(context).pop(); }, ), @@ -107,14 +110,21 @@ class _SafeFunctionDialogState extends State { } } +/// 显示安全措施弹窗 +/// [initialSelected] 可选,指定弹窗打开时的已选项 Future showSafeFunctionDialog( - BuildContext context, - List> data, - void Function(List) onConfirm, -) { + BuildContext context, + List> data, + void Function(List>) onConfirm, { + List> initialSelected = const [], + }) { return showDialog( context: context, barrierDismissible: false, - builder: (_) => SafeFunctionDialog(data: data, onConfirm: onConfirm), + builder: (_) => SafeFunctionDialog( + data: data, + initialSelectedItems: initialSelected, + onConfirm: onConfirm, + ), ); -} +} \ No newline at end of file diff --git a/lib/pages/home/tap/tabList/special_wrok/szaq_work_detail/hotwork_set_safe_detail.dart b/lib/pages/home/tap/tabList/special_wrok/szaq_work_detail/hotwork_set_safe_detail.dart index 7e0d9e8..96d0f1e 100644 --- a/lib/pages/home/tap/tabList/special_wrok/szaq_work_detail/hotwork_set_safe_detail.dart +++ b/lib/pages/home/tap/tabList/special_wrok/szaq_work_detail/hotwork_set_safe_detail.dart @@ -12,15 +12,16 @@ import 'package:qhd_prevention/customWidget/toast_util.dart'; import 'package:qhd_prevention/pages/home/tap/item_list_widget.dart'; import 'package:qhd_prevention/tools/tools.dart'; import '../../../../../../customWidget/bottom_picker.dart'; +import '../../../../../../customWidget/custom_alert_dialog.dart'; import '../../../../../../customWidget/single_image_viewer.dart'; import '../../../../../../http/ApiService.dart'; import '../../../../../mine/mine_sign_page.dart'; import '../../../../../my_appbar.dart'; import '../../special_Wrok/dh_work_detai/MeasuresListWidget.dart'; import '../../special_Wrok/qtfx_work_detail/hotwork_gas_list.dart'; +import '../WorkDetailFormWidget.dart'; import 'SafeFunctionDialog.dart'; - - +/// 设置安全措施确认人 class HotworkSetSafeDetail extends StatefulWidget { const HotworkSetSafeDetail({ super.key, @@ -46,7 +47,6 @@ class _HotworkSetSafeDetailState extends State { late Map pd = {}; late List> measuresList = []; - /// 动火人及证书编号 late List workUserList = []; @@ -60,128 +60,14 @@ class _HotworkSetSafeDetailState extends State { _getData(); _getHotWorkNameList(); addMeasuresListCopy(); - } String measuresListToJson() { final List> jsonList = - measuresListCopy.map((item) => item.toJson()).toList(); + measuresListCopy.map((item) => item.toJson()).toList(); return jsonEncode(jsonList); } - - Widget _defaultDetail() { - return Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - ItemListWidget.singleLineTitleText( - label: '申请单位:', - isEditable: false, - text: pd['APPLY_DEPARTMENT_NAME'] ?? '', - ), - Divider(), - ItemListWidget.singleLineTitleText( - label: '申请人:', - isEditable: false, - text: pd['APPLY_USER_NAME'] ?? '', - ), - if (FormUtils.hasValue(pd, 'CHECK_NO')) - Column( - children: [ - Divider(), - ItemListWidget.singleLineTitleText( - label: '编号:', - isEditable: false, - text: pd['CHECK_NO'] ?? '', - ), - ], - ), - - Divider(), - ItemListWidget.multiLineTitleTextField( - label: '作业内容:', - isEditable: isEditable, - text: pd['WORK_CONTENT'] ?? '', - ), - Divider(), - ItemListWidget.singleLineTitleText( - label: '动火地点及动火部位:', - isEditable: isEditable, - text: pd['WORK_PLACE'] ?? '', - ), - Divider(), - ItemListWidget.selectableLineTitleTextField( - label: '动火作业级别', - isEditable: isEditable, - text: pd['WORK_LEVEL'] ?? '', - ), - Divider(), - ItemListWidget.singleLineTitleText( - label: '动火方式:', - isEditable: isEditable, - text: pd['WORK_FUNCTION'] ?? '', - ), - if (pd['WORK_START_DATE'] != null && - pd['WORK_START_DATE'].toString().isNotEmpty) - Column( - children: [ - Divider(), - ItemListWidget.singleLineTitleText( - label: '动火作业\n实施时间:', - isEditable: isEditable, - text: - pd['WORK_START_DATE'] ?? - '' + - '至' + - (pd['WORK_END_DATE'] - ? pd['WORK_END_DATE'] ?? '' - : '--') ?? - '', - ), - ], - ), - Divider(), - ItemListWidget.twoRowSelectableTitleText( - label: '动火人及证书编号:', - isEditable: isEditable, - text: pd['WORK_USER'] ?? '', - ), - Divider(), - ItemListWidget.twoRowButtonTitleText( - label: '关联的其他特殊作业及安全作业票编号', - isEditable: isEditable, - onTap: ()=>{}, - hintText: '请输入关联的其他特殊作业及安全作业票编号', - text: pd['SPECIAL_WORK'] ?? '', - ), - Divider(), - ItemListWidget.twoRowButtonTitleText( - label: '风险辨识结果', - isEditable: isEditable, - onTap: () {}, - hintText: '请输入风险辨识结果', - text: pd['RISK_IDENTIFICATION'] ?? '', - ), - if (FormUtils.hasValue(pd, 'ANALYZE_TIME')) - Column( - children: [ - Divider(), - ItemListWidget.OneRowButtonTitleText( - label: '分析人', - text: pd['ANALYZE_USER_NAME'] ?? '', - onTap: () { - pushPage( - HotworkGasList(HOTWORK_ID: widget.HOTWORK_ID), - context, - ); - }, - ), - ], - ), - ], - ); - } - Widget _card(Widget child) { return Container( decoration: BoxDecoration( @@ -214,10 +100,16 @@ class _HotworkSetSafeDetailState extends State { label: '安全措施:', buttonText: '选择安全措施', onTap: () { - showSafeFunctionDialog(context, measuresList, (selected) { - // 在这里处理用户的选择结果 - debugPrint('用户选择了:' + json.encode(selected)); - }); + showSafeFunctionDialog( + context, + measuresList, + initialSelected: item.selectMeasures, + (selected) { + setState(() { + item.selectMeasures = selected; + }); + }, + ); }, ), ], @@ -226,7 +118,6 @@ class _HotworkSetSafeDetailState extends State { /// 弹出单位选择 void chooseUnitHandle(MeasureItem item) { - showModalBottomSheet( context: context, isScrollControlled: true, @@ -242,16 +133,13 @@ class _HotworkSetSafeDetailState extends State { _getPersonListForUnitId(item); }, ), - ).then((_) { - - }); + ).then((_) {}); } Future _getPersonListForUnitId(MeasureItem item) async { // 拉取该单位的人员列表并缓存 final result = await ApiService.getListTreePersonList(item.DEPARTMENT_ID); setState(() { - item.userList = List>.from( result['userList'] ?? >[], ); @@ -260,12 +148,10 @@ class _HotworkSetSafeDetailState extends State { /// 弹出人员选择,需先选择单位 void choosePersonHandle(MeasureItem item) async { - String unitId = item.DEPARTMENT_ID; final personList = item.userList; if (!unitId.isNotEmpty) { - final unitName = item.DEPARTMENT_NAME; - ToastUtil.showNormal(context, '请先选择$unitName'); + ToastUtil.showNormal(context, '请先选择确认单位'); return; } if (personList.isEmpty) { @@ -291,10 +177,9 @@ class _HotworkSetSafeDetailState extends State { print(json.encode(measuresListCopy)); }); }, - ).then((_) { - - }); + ).then((_) {}); } + /// 签字 Future _sign() async { final path = await Navigator.push( @@ -311,115 +196,168 @@ class _HotworkSetSafeDetailState extends State { }); } } + Widget _signListWidget() { return Column( - children: imagePaths.map((path) { - return Column( - children: [ - const SizedBox(height: 10), - const Divider(), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: + imagePaths.map((path) { + return Column( children: [ - GestureDetector( - child: Image.file( - File(path), - width: 200, - height: 150, - fit: BoxFit.cover, - ), - onTap: () { - presentOpaque( - SingleImageViewer(imageUrl:path), - context, - ); - }, - ), - Column( + const SizedBox(height: 10), + const Divider(), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Container( - padding: const EdgeInsets.only(right: 5), - child: CustomButton( - text: 'X', - height: 30, - padding: const EdgeInsets.symmetric(horizontal: 10), - backgroundColor: Colors.red, - onPressed: () { - setState(() { - imagePaths.remove(path); - }); - }, + GestureDetector( + child: // 用一个 ConstrainedBox 限制最大尺寸,并改为 BoxFit.contain + ConstrainedBox( + constraints: const BoxConstraints( + maxWidth: 200, + maxHeight: 150, + ), + child: Image.file( + File(path), + // 改为完整显示 + fit: BoxFit.contain, + ), ), + onTap: () { + presentOpaque( + SingleImageViewer(imageUrl: path), + context, + ); + }, + ), + Column( + children: [ + Container( + padding: const EdgeInsets.only(right: 5), + child: CustomButton( + text: 'X', + height: 30, + padding: const EdgeInsets.symmetric(horizontal: 10), + backgroundColor: Colors.red, + onPressed: () { + setState(() { + imagePaths.remove(path); + }); + }, + ), + ), + const SizedBox(height: 80), + ], ), - const SizedBox(height: 80), ], ), ], - ), - ], - ); - }).toList(), + ); + }).toList(), ); } + /// 提交 1 提交 0暂存 Future _submit(String status) async { - // 通用文本字段校验规则 + if (imagePaths.isEmpty) { + ToastUtil.showNormal(context, '请签字'); + return; + } + List> signers = []; + String reasonText = ''; if (status == '1') { - // // 文本校验 - // for (var rule in textRules) { - // if ((rule['value'] as String).isEmpty) { - // ToastUtil.showNormal(context, rule['message']); - // return; - // } - // } - // // 级别校验 - // if (level.length == 0) { - // ToastUtil.showNormal(context, '请选择动火级别'); - // return; - // } - // - // for (MeasureItem item in measuresListCopy) { - // if (item.USER_ID.isNotEmpty) { - // ToastUtil.showNormal(context, '请选择确认人'); - // return; - // } - // } - } - // LoadingDialogHelper.show(context); + int index = 0; + for (var item in measuresListCopy) { + if (item.USER_ID.isEmpty) { + ToastUtil.showNormal( + context, + '第${index + 1}项未设置确认人', + ); + return; + } + if (item.selectMeasures.isEmpty) { + ToastUtil.showNormal( + context, + '第${index + 1}项未选择安全措施', + ); + return; + } + final userId = item.USER_ID; + final selectMeasures = item.selectMeasures as List? ?? []; - // 提交参数 - if (msg == 'add') { - pd['CORPINFO_ID'] = SessionService.instance.corpinfoId; - pd['CREATOR'] = SessionService.instance.loginUserId; - pd['OPERATOR'] = SessionService.instance.loginUserId; - pd['ACTION_USER'] = SessionService.instance.username; - pd['APPLY_STATUS'] = status; - pd['STEP_ID'] = status; - pd['HOTWORK_ID'] = widget.HOTWORK_ID; - pd['APPLY_DEPARTMENT_ID'] = SessionService.instance.deptId; - pd['APPLY_DEPARTMENT_NAME'] = - SessionService.instance.loginUser?['DEPARTMENT_NAME'] ?? ''; - pd['APPLY_USER_ID'] = SessionService.instance.loginUserId; - pd['APPLY_USER_NAME'] = SessionService.instance.username; - pd['USER_ID'] = SessionService.instance.loginUserId; - } - - LoadingDialogHelper.show(context); - String jsonStr = jsonEncode(pd); - printLongString(jsonStr); - try { - String url = "/app/hotwork/" + msg; - final result = await ApiService.submitHotwork(url, pd); - LoadingDialogHelper.hide(context); - if (result['result'] == 'success') { - ToastUtil.showSuccess(context, status == '1' ? '提交成功' : '已暂存'); - Navigator.pop(context); + for (var item in selectMeasures) { + signers.add({ + 'BUS_HOTWORK_MEASURES_ID': item['BUS_HOTWORK_MEASURES_ID'], + 'USER_ID': userId, + }); + } + // 检查长度是否一致 + if (signers.length != measuresList.length) { + // 使用 ScaffoldMessenger 弹出提示 + ToastUtil.showNormal(context, '请为每个安全措施选择确认人'); + return; + } + index++; + } + } else { + await showDialog( + context: context, + builder: + (_) => CustomAlertDialog( + title: '作废原因', + mode: DialogMode.input, + hintText: '请输入作废原因', + cancelText: '取消', + confirmText: '确定', + onInputConfirm: (text) { + reasonText = text; + }, + ), + ); + if (reasonText.isEmpty) { + ToastUtil.showNormal(context, '请填写作废原因'); + return; } - } catch (e) { - LoadingDialogHelper.hide(context); - ToastUtil.showNormal(context, '操作失败:$e'); } + + final Map formData = {}; + // 提交参数 + formData['HOTWORK_ID'] = widget.HOTWORK_ID; + formData['SIGNTIME'] = signTimes.join(','); + formData['USER_ID'] = SessionService.instance.loginUserId; + formData['APPLY_STATUS'] = status; + formData['STEP_REASON'] = reasonText; + formData['PREPARERS'] = json.encode(signers); + + await showDialog( + context: context, + builder: + (_) => CustomAlertDialog( + title: '提示', + content: '请确认' + (status == '1' ? "通过" : "作废") + '本作业票?', + cancelText: '取消', + confirmText: '确定', + onConfirm: () async { + LoadingDialogHelper.show(context); + try { + final result = await ApiService.saveSafeFunctionSure( + formData, + imagePaths, + ); + LoadingDialogHelper.hide(context); + if (result['result'] == 'success') { + ToastUtil.showSuccess( + context, + status == '1' ? '提交成功' : '已暂存', + ); + Navigator.pop(context); + } + } catch (e) { + LoadingDialogHelper.hide(context); + ToastUtil.showNormal(context, '操作失败:$e'); + } + }, + ), + ); } void printLongString(String text, {int chunkSize = 800}) { @@ -444,13 +382,7 @@ class _HotworkSetSafeDetailState extends State { setState(() { pd = data['pd']; _getMeasures(); - }); - // final data = await ApiService.getHomeworkFindById(widget.HOTWORK_ID); - // setState(() { - // pd = data['pd']; - // }); - // LoadingDialogHelper.hide(context); } Future _getMeasures() async { @@ -535,9 +467,14 @@ class _HotworkSetSafeDetailState extends State { ...item.selectMeasures.asMap().entries.map((e) { int idx = e.key; - String txt = e.value; + String txt = e.value['PROTECTIVE_MEASURES']; return Padding( - padding: EdgeInsets.only(top: 4), + padding: EdgeInsets.only( + top: 4, + left: 12, + right: 12, + bottom: 12, + ), child: Text('${idx + 1}. $txt'), ); }), @@ -589,7 +526,6 @@ class _HotworkSetSafeDetailState extends State { ), SizedBox(height: 10), if (imagePaths.isNotEmpty) _signListWidget(), - ], ), ); @@ -608,7 +544,7 @@ class _HotworkSetSafeDetailState extends State { text: '作废', backgroundColor: Colors.red, onPressed: () { - _submit('1'); + _submit('-1'); }, ), ), @@ -618,7 +554,7 @@ class _HotworkSetSafeDetailState extends State { text: '通过', backgroundColor: Colors.green, onPressed: () { - _submit('0'); + _submit('1'); }, ), ), @@ -635,8 +571,21 @@ class _HotworkSetSafeDetailState extends State { padding: EdgeInsets.all(12), child: Column( children: [ - _card(_defaultDetail()), - + // _card(_defaultDetail()), + _card( + WorkDetailFormWidget( + pd: pd, + isEditable: false, + onChooseLevel: (){}, + onChooseHotworkUser: (){}, + onAnalyzeTap: () { + pushPage( + HotworkGasList(HOTWORK_ID: widget.HOTWORK_ID), + context, + ); + }, + ), + ), SizedBox(height: 20), _setSafeDetailWidget(), SizedBox(height: 20), @@ -657,7 +606,7 @@ class MeasureItem { String USER_NAME; List> userList; int userIndex; - List selectMeasures; + List> selectMeasures; MeasureItem({ required this.id, @@ -667,9 +616,10 @@ class MeasureItem { this.USER_NAME = '', List>? userList, this.userIndex = -1, - List? selectMeasures, + List>? selectMeasures, }) : userList = userList ?? [], selectMeasures = selectMeasures ?? []; + Map toJson() { return { 'id': id,