From b756e4e809dbe8f271f8debf3c3a3053963d9eaa Mon Sep 17 00:00:00 2001 From: hs <873121290@qq.com> Date: Thu, 14 Aug 2025 15:05:48 +0800 Subject: [PATCH] =?UTF-8?q?=E9=87=8D=E7=82=B9=E5=B7=A5=E7=A8=8B=E7=AE=A1?= =?UTF-8?q?=E7=90=86=E5=AE=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/customWidget/ItemWidgetFactory.dart | 24 +- lib/customWidget/bottom_picker.dart | 4 +- lib/customWidget/photo_picker_row.dart | 106 +- lib/http/ApiService.dart | 120 +- .../KeyProjects/Danger/danger_list_page.dart | 267 +++++ .../Danger/danger_manager_detail_page.dart | 398 +++++++ .../Danger/danger_manager_page.dart | 250 ++++ .../Punishment/PunishmentModalAlert.dart | 335 ++++++ .../Punishment/punishment_list_page.dart | 264 +++++ .../punishment_manager_detail_page.dart | 396 +++++++ .../Punishment/punishment_manager_page.dart | 290 +++++ .../SafeCheck/check_list_page.dart | 4 +- .../custom/MultiTextFieldWithTitle.dart | 234 ++++ .../SafeCheck/custom/safeCheck_table.dart | 156 +++ .../SafeCheck/custom/safe_drawer_page.dart | 703 ++++++++++++ .../SafeCheck/safeCheck_detail.dart | 1003 ++++++++++++++--- .../SafeCheck/safeCheck_list_page.dart | 4 +- .../KeyProjects/keyProjects_tab_list.dart | 15 +- .../home/SafeCheck/safeCheck_tab_list.dart | 130 +++ lib/pages/home/home_page.dart | 4 + lib/pages/home/tap/item_list_widget.dart | 101 +- lib/tools/tools.dart | 8 + 22 files changed, 4592 insertions(+), 224 deletions(-) create mode 100644 lib/pages/KeyProjects/Danger/danger_list_page.dart create mode 100644 lib/pages/KeyProjects/Danger/danger_manager_detail_page.dart create mode 100644 lib/pages/KeyProjects/Danger/danger_manager_page.dart create mode 100644 lib/pages/KeyProjects/Punishment/PunishmentModalAlert.dart create mode 100644 lib/pages/KeyProjects/Punishment/punishment_list_page.dart create mode 100644 lib/pages/KeyProjects/Punishment/punishment_manager_detail_page.dart create mode 100644 lib/pages/KeyProjects/Punishment/punishment_manager_page.dart create mode 100644 lib/pages/KeyProjects/SafeCheck/custom/MultiTextFieldWithTitle.dart create mode 100644 lib/pages/KeyProjects/SafeCheck/custom/safeCheck_table.dart create mode 100644 lib/pages/KeyProjects/SafeCheck/custom/safe_drawer_page.dart create mode 100644 lib/pages/home/SafeCheck/safeCheck_tab_list.dart diff --git a/lib/customWidget/ItemWidgetFactory.dart b/lib/customWidget/ItemWidgetFactory.dart index 77a668a..918f57a 100644 --- a/lib/customWidget/ItemWidgetFactory.dart +++ b/lib/customWidget/ItemWidgetFactory.dart @@ -169,7 +169,6 @@ class ListItemFactory { } /// 类型6:文本和视频上下布局 - static Widget createTextVideoItem({ required String text, required String videoUrl, @@ -195,6 +194,7 @@ class ListItemFactory { ), ), const SizedBox(height: 10), + videoUrl.isNotEmpty ? GestureDetector( onTap: onVideoTapped, child: Container( @@ -212,7 +212,7 @@ class ListItemFactory { ), ), ), - ), + ) : SizedBox(height: 10,) ], ), ); @@ -255,6 +255,7 @@ class ListItemFactory { double horizontalPadding = 10, bool isEdit = true, String text = '', + bool isRequired = false, }) { return Padding( @@ -273,13 +274,18 @@ class ListItemFactory { child: Row( children: [ Expanded( - child: Text( - title, - style: TextStyle( - fontSize: 15, - fontWeight: FontWeight.bold, - color: Colors.black, - ), + child: Row( + children: [ + if (isRequired && isEdit) Text('* ', style: TextStyle(color: Colors.red)), + Text( + title, + style: TextStyle( + fontSize: 15, + fontWeight: FontWeight.bold, + color: Colors.black, + ), + ) + ], ), ), if (isEdit) diff --git a/lib/customWidget/bottom_picker.dart b/lib/customWidget/bottom_picker.dart index 11330bd..99054ee 100644 --- a/lib/customWidget/bottom_picker.dart +++ b/lib/customWidget/bottom_picker.dart @@ -63,14 +63,14 @@ class BottomPicker { FocusScope.of(context).unfocus(); Navigator.of(ctx).pop(); }, - child: const Text('取消'), + child: const Text('取消', style: TextStyle(color: Colors.black54, fontSize: 16),), ), TextButton( onPressed: () { FocusScope.of(context).unfocus(); Navigator.of(ctx).pop(selected); }, - child: const Text('确定'), + child: const Text('确定', style: TextStyle(color: Colors.blue, fontSize: 16),), ), ], ), diff --git a/lib/customWidget/photo_picker_row.dart b/lib/customWidget/photo_picker_row.dart index 43f55f6..04a2f0d 100644 --- a/lib/customWidget/photo_picker_row.dart +++ b/lib/customWidget/photo_picker_row.dart @@ -10,15 +10,7 @@ import 'ItemWidgetFactory.dart'; enum MediaType { image, video } /// 纵向滚动四列的媒体添加组件,支持拍摄、全屏相册多选,以及初始地址列表展示 -/// 使用示例: -/// MediaPickerGrid( -/// maxCount: 4, -/// mediaType: MediaType.video, -/// initialMediaPaths: ['https://...', '/local/path.png'], -/// onChanged: (List medias) {}, -/// onMediaAdded: (String path) {}, -/// onMediaRemoved: (String path) {}, -/// ), +/// 新增 isEdit 属性控制编辑状态 class MediaPickerRow extends StatefulWidget { final int maxCount; final MediaType mediaType; @@ -26,6 +18,8 @@ class MediaPickerRow extends StatefulWidget { final ValueChanged> onChanged; final ValueChanged? onMediaAdded; final ValueChanged? onMediaRemoved; + final ValueChanged? onMediaTapped; // 新增:媒体点击回调 + final bool isEdit; // 新增:控制编辑状态 const MediaPickerRow({ Key? key, @@ -35,6 +29,8 @@ class MediaPickerRow extends StatefulWidget { required this.onChanged, this.onMediaAdded, this.onMediaRemoved, + this.onMediaTapped, // 新增 + this.isEdit = true, // 默认可编辑 }) : super(key: key); @override @@ -59,6 +55,8 @@ class _MediaPickerGridState extends State { } Future _showPickerOptions() async { + if (!widget.isEdit) return; // 不可编辑时直接返回 + showModalBottomSheet( context: context, backgroundColor: Colors.white, @@ -82,7 +80,6 @@ class _MediaPickerGridState extends State { ), ListTile( titleAlignment: ListTileTitleAlignment.center, - leading: Icon( widget.mediaType == MediaType.image ? Icons.photo_library @@ -100,7 +97,6 @@ class _MediaPickerGridState extends State { ), ListTile( titleAlignment: ListTileTitleAlignment.center, - leading: const Icon(Icons.close), title: const Text('取消'), onTap: () => Navigator.of(context).pop(), @@ -112,7 +108,8 @@ class _MediaPickerGridState extends State { } Future _pickCamera() async { - if (_mediaPaths.length >= widget.maxCount) return; + if (!widget.isEdit || _mediaPaths.length >= widget.maxCount) return; + try { XFile? picked; if (widget.mediaType == MediaType.image) { @@ -132,7 +129,8 @@ class _MediaPickerGridState extends State { } Future _pickGallery() async { - if (_mediaPaths.length >= widget.maxCount) return; + if (!widget.isEdit || _mediaPaths.length >= widget.maxCount) return; + final permission = await PhotoManager.requestPermissionExtend(); if (permission != PermissionState.authorized && permission != PermissionState.limited) { @@ -172,6 +170,8 @@ class _MediaPickerGridState extends State { } void _removeMedia(int index) { + if (!widget.isEdit) return; // 不可编辑时不允许删除 + final removed = _mediaPaths[index]; setState(() => _mediaPaths.removeAt(index)); widget.onChanged(_mediaPaths.map((p) => File(p)).toList()); @@ -180,6 +180,9 @@ class _MediaPickerGridState extends State { @override Widget build(BuildContext context) { + final showAddButton = widget.isEdit && _mediaPaths.length < widget.maxCount; + final itemCount = _mediaPaths.length + (showAddButton ? 1 : 0); + return GridView.builder( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), @@ -188,45 +191,51 @@ class _MediaPickerGridState extends State { crossAxisSpacing: 8, mainAxisSpacing: 8, childAspectRatio: 1, - mainAxisExtent: 80, - + // mainAxisExtent: 80, ), - itemCount: _mediaPaths.length < widget.maxCount - ? _mediaPaths.length + 1 - : widget.maxCount, + itemCount: itemCount, itemBuilder: (context, index) { + // 显示媒体项 if (index < _mediaPaths.length) { final path = _mediaPaths[index]; final isNetwork = path.startsWith('http'); - return Stack( - children: [ - ClipRRect( - borderRadius: BorderRadius.circular(5), - child: widget.mediaType == MediaType.image - ? (isNetwork - ? Image.network(path, fit: BoxFit.cover,width: 80, height: 80,) - : Image.file(File(path), width: 80, height: 80, fit: BoxFit.cover)) - : Container( - color: Colors.black12, - child: const Center( - child: Icon( - Icons.videocam, - color: Colors.white70, + + return GestureDetector( + onTap: () => widget.onMediaTapped?.call(path), + child: Stack( + children: [ + ClipRRect( + borderRadius: BorderRadius.circular(5), + child: widget.mediaType == MediaType.image + ? (isNetwork + ? Image.network(path, fit: BoxFit.cover, width: 80, height: 80) + : Image.file(File(path), width: 80, height: 80, fit: BoxFit.cover)) + : Container( + color: Colors.black12, + child: const Center( + child: Icon( + Icons.videocam, + color: Colors.white70, + ), ), ), ), - ), - Positioned( - top: -15, - right: -15, - child: IconButton( - icon: const Icon(Icons.cancel, size: 20, color: Colors.red), - onPressed: () => _removeMedia(index), - ), - ), - ], + // 只在可编辑状态下显示删除按钮 + if (widget.isEdit) + Positioned( + top: -15, + right: -15, + child: IconButton( + icon: const Icon(Icons.cancel, size: 20, color: Colors.red), + onPressed: () => _removeMedia(index), + ), + ), + ], + ), ); - } else { + } + // 显示添加按钮 + else if (showAddButton) { return GestureDetector( onTap: _showPickerOptions, child: Container( @@ -239,6 +248,8 @@ class _MediaPickerGridState extends State { ), ), ); + } else { + return const SizedBox.shrink(); } }, ); @@ -246,6 +257,7 @@ class _MediaPickerGridState extends State { } /// 照片上传区域组件,使用纵向四列Grid展示 +/// 新增 isEdit 属性控制编辑状态 class RepairedPhotoSection extends StatefulWidget { final int maxCount; final MediaType mediaType; @@ -254,11 +266,13 @@ class RepairedPhotoSection extends StatefulWidget { final ValueChanged> onChanged; final ValueChanged? onMediaAdded; final ValueChanged? onMediaRemoved; + final ValueChanged? onMediaTapped; // 新增:媒体点击回调 final VoidCallback onAiIdentify; final bool isShowAI; final double horizontalPadding; final bool isRequired; final bool isShowNum; + final bool isEdit; // 新增:控制编辑状态 const RepairedPhotoSection({ Key? key, @@ -272,8 +286,10 @@ class RepairedPhotoSection extends StatefulWidget { this.horizontalPadding = 5, this.onMediaAdded, this.onMediaRemoved, + this.onMediaTapped, // 新增 this.isRequired = false, this.isShowNum = true, + this.isEdit = true, // 默认可编辑 }) : super(key: key); @override @@ -324,10 +340,12 @@ class _RepairedPhotoSectionState extends State { }, onMediaAdded: widget.onMediaAdded, onMediaRemoved: widget.onMediaRemoved, + onMediaTapped: widget.onMediaTapped, // 传递点击回调 + isEdit: widget.isEdit, // 传递编辑状态 ), ), const SizedBox(height: 20), - if (widget.isShowAI) + if (widget.isShowAI && widget.isEdit) // 只在可编辑状态下显示AI按钮 Padding( padding: EdgeInsets.symmetric(horizontal: widget.horizontalPadding), child: GestureDetector( diff --git a/lib/http/ApiService.dart b/lib/http/ApiService.dart index 0ec6b58..d249122 100644 --- a/lib/http/ApiService.dart +++ b/lib/http/ApiService.dart @@ -1007,7 +1007,7 @@ U6Hzm1ninpWeE+awIDAQAB ); } /// 安全检查发起 - static Future> addSafeCheckReciord(String OUTSOURCED_ID) { + static Future> addSafeCheckRecord(String OUTSOURCED_ID) { return HttpManager().request( basePath, '/app/keyProjects/goEdit', @@ -1018,6 +1018,17 @@ U6Hzm1ninpWeE+awIDAQAB }, ); } + /// 安全检查 + static Future> getSafeCheckGoEdit(String KEYPROJECTCHECK_ID) { + return HttpManager().request( + basePath, + '/app/keyprojectcheck/goEdit', + method: Method.post, + data: { + "KEYPROJECTCHECK_ID":KEYPROJECTCHECK_ID, + }, + ); + } /// 安全检查单位现场负责人列表 static Future> getSafeCheckPersonList(String UNITS_ID, String NOMAIN) { return HttpManager().request( @@ -1055,8 +1066,113 @@ U6Hzm1ninpWeE+awIDAQAB }, ); } + /// 安全检查发起提交 + static Future> safeCheckPunishSubmit(Map data) { + return HttpManager().request( + basePath, + '/app/keyprojectpunish/add', + method: Method.post, + data: { + ...data + }, + ); + } + /// 安全检查发起提交 + static Future> safeKeyprojectCheckSubmit(Map data) { + return HttpManager().request( + basePath, + '/app/keyprojectcheck/add', + method: Method.post, + data: { + ...data + }, + ); + } + /// 重点工程隐患管理列表 + static Future> getKeyprojectDangerList(String url, Map data) { + return HttpManager().request( + basePath, + url, + method: Method.post, + data: { + ...data + }, + ); + } + /// 重点工程隐患详情 + static Future> getKeyprojectDangerFindHidden(String HIDDEN_ID, String OUTSOURCED_ID) { + return HttpManager().request( + basePath, + '/app/keyprojectcheck/findHidden', + method: Method.post, + data: { + 'HIDDEN_ID':HIDDEN_ID, + 'OUTSOURCED_ID':OUTSOURCED_ID + }, + ); + } + /// 重点工程隐患验收 + static Future> checkKeyprojectDanger(String HIDDEN_ID, Map data) { + return HttpManager().request( + basePath, + '/app/keyprojectcheck/check', + method: Method.post, + data: { + "CORPINFO_ID":SessionService.instance.corpinfoId, + "CREATOR":SessionService.instance.loginUserId, + "OPERATOR":SessionService.instance.loginUserId, + 'HIDDEN_ID':HIDDEN_ID, + ...data + }, + ); + } - + /// 上传图片 + static Future> addNormalImgFiles(String imagePath, Map data) async { + final file = File(imagePath); + if (!await file.exists()) { + throw ApiException('file_not_found', '图片不存在:$imagePath'); + } + final fileName = file.path.split(Platform.pathSeparator).last; + return HttpManager().uploadFaceImage( + baseUrl: basePath, + path: '/app/imgfiles/add', + fromData: { + ...data, + 'FFILE': await MultipartFile.fromFile( + file.path, + filename: fileName + ), + } + ); + } + /// 重点工程处罚 + static Future> keyprojectpunishAdd(Map data) { + return HttpManager().request( + basePath, + '/app/keyprojectpunish/add', + method: Method.post, + data: { + "CORPINFO_ID":SessionService.instance.corpinfoId, + "CREATOR":SessionService.instance.loginUserId, + "OPERATOR":SessionService.instance.loginUserId, + ...data + }, + ); + } + /// 重点工程处罚修改隐患 + static Future> keyprojectPunishEdit(Map data, String ISPUNISH) { + return HttpManager().request( + basePath, + '/app/keyprojectcheck/editHiddenIspunish', + method: Method.post, + data: { + "ISPUNISH":ISPUNISH, + "PUNISH_PERSON":SessionService.instance.loginUserId, + ...data + }, + ); + } diff --git a/lib/pages/KeyProjects/Danger/danger_list_page.dart b/lib/pages/KeyProjects/Danger/danger_list_page.dart new file mode 100644 index 0000000..ffd19fc --- /dev/null +++ b/lib/pages/KeyProjects/Danger/danger_list_page.dart @@ -0,0 +1,267 @@ +import 'package:flutter/material.dart'; +import 'package:qhd_prevention/pages/KeyProjects/Danger/danger_manager_page.dart'; +import 'package:qhd_prevention/pages/KeyProjects/KeyProject/keyProject_detail.dart'; +import 'package:qhd_prevention/pages/my_appbar.dart'; +import 'package:qhd_prevention/tools/tools.dart'; +import 'package:qhd_prevention/customWidget/custom_button.dart'; +import 'package:qhd_prevention/customWidget/search_bar_widget.dart'; +import 'package:qhd_prevention/http/ApiService.dart'; + +class DangerListPage extends StatefulWidget { + final String flow; + + const DangerListPage({Key? key, required this.flow}) : super(key: key); + + @override + _DangerListPageState createState() => _DangerListPageState(); +} + +class _DangerListPageState extends State { + // Data and state variables + List list = []; + int currentPage = 1; + int rows = 10; + int totalPage = 1; + bool isLoading = false; + final TextEditingController _searchController = TextEditingController(); + + List> stepList = []; + int sindex = 0; + String searchKeywords = ''; + + final GlobalKey _scaffoldKey = GlobalKey(); + + final ScrollController _scrollController = ScrollController(); + + @override + void initState() { + super.initState(); + _fetchData(); + _scrollController.addListener(_onScroll); + } + + @override + void dispose() { + _scrollController.dispose(); + super.dispose(); + } + + void _onScroll() { + if (_scrollController.position.pixels >= + _scrollController.position.maxScrollExtent && + !isLoading) { + if (currentPage < totalPage) { + currentPage++; + _fetchData(); + } + } + } + + Future _fetchData() async { + if (isLoading) return; + setState(() => isLoading = true); + + try { + final data = {'KEYWORDS': searchKeywords}; + final url = + '/app/keyProjects/listOutsourced?showCount=10¤tPage=$currentPage'; + final response = await ApiService.getKeyProjectList(data, url); + + setState(() { + if (currentPage == 1) { + list = response['varList']; + } else { + list.addAll(response['varList']); + } + Map page = response['page']; + totalPage = page['totalPage'] ?? 1; + isLoading = false; + }); + } catch (e) { + print('Error fetching data: $e'); + setState(() => isLoading = false); + } + } + + void _search() { + searchKeywords = _searchController.text.trim(); + currentPage = 1; + list.clear(); + _fetchData(); + } + + void _goToDetail(Map item) async { + await pushPage(DangerManagerPage(OUTSOURCED_ID: item['OUTSOURCED_ID'] ?? ''),context); + _fetchData(); + } + + Widget _buildListItem(Map item) { + bool showBadge = + (item['STATE'] == '-1' || item['STATE'] == '-2') && + (item['CREATOR'] != null && + item['CREATOR'].toString() == SessionService.instance.loginUserId); + + return Card( + color: Colors.white, + margin: const EdgeInsets.all(8.0), + child: InkWell( + onTap: () => _goToDetail(item), + child: Padding( + padding: const EdgeInsets.all(12.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // 标题 + 红点 + Row( + children: [ + Expanded( + child: Text( + "${item['OUTSOURCED_NAME'] ?? ''}", + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ), + if (showBadge) + Container( + margin: const EdgeInsets.only(left: 8.0), + padding: const EdgeInsets.symmetric( + horizontal: 8.0, + vertical: 2.0, + ), + height: 18, + alignment: Alignment.center, + decoration: BoxDecoration( + color: const Color(0xFFdd514c), + borderRadius: BorderRadius.circular(96), + ), + child: const Text( + '1', + style: TextStyle(color: Colors.white, fontSize: 10), + ), + ), + ], + ), + const SizedBox(height: 8), + + // 根据 IS_CORP_TYPE 显示不同的主管/监管信息(对应 vue 逻辑) + if (item['IS_CORP_TYPE'] == '1') ...[ + Text("主管部门:${item['Q_COMPETENT_DEPT_NAME'] ?? ''}"), + const SizedBox(height: 6), + Text("监督部门:${item['DEPARTMENT_NAME'] ?? ''}"), + ] else if (item['IS_CORP_TYPE'] == '0') ...[ + Text("主管部门:${item['MANAGER_DEPARTMENT_NAME'] ?? ''}"), + const SizedBox(height: 6), + Text("监管部门:${item['DEPARTMENT_NAME'] ?? ''}"), + ], + const SizedBox(height: 8), + + // 相关方单位负责人 + 电话(两列) + Row( + children: [ + Expanded( + child: Text( + "相关方单位负责人:${item['UNITS_PIC_NAME'] ?? ''}", + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + const SizedBox(height: 8), + + Text( + "电话:${item['UNITS_PHONE'] ?? ''}", + ), + const SizedBox(height: 6), + + + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + SizedBox(), + CustomButton( + onPressed: () { _goToDetail(item);}, + text: '查看', + height: 30, + padding: EdgeInsets.symmetric(vertical: 5, horizontal: 15), + textStyle: TextStyle(fontSize: 13, color: Colors.white), + backgroundColor: Colors.blue, + ), + ], + ), + ], + ), + ), + ), + ); + } + + Widget _buildListContent() { + if (isLoading && list.isEmpty) { + // 初始加载时显示居中的加载指示器 + return Center(child: CircularProgressIndicator()); + } else if (list.isEmpty) { + // 没有数据 + return NoDataWidget.show(); + } else { + // 有数据或加载更多 + return ListView.builder( + padding: EdgeInsets.zero, + + controller: _scrollController, + itemCount: list.length + (isLoading ? 1 : 0), + itemBuilder: (context, index) { + if (index >= list.length) { + // 加载更多时在列表底部显示加载指示器 + return Padding( + padding: const EdgeInsets.symmetric(vertical: 16.0), + child: Center(child: CircularProgressIndicator()), + ); + } + return _buildListItem(list[index]); + }, + ); + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + key: _scaffoldKey, + appBar: MyAppbar(title: widget.flow), + + body: SafeArea(child: Column( + children: [ + // Filter bar + Container( + color: Colors.white, + padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 8), + child: Row( + children: [ + Expanded( + flex: 2, + child: SearchBarWidget( + showResetButton: false, + hintText: "请输入关键字", + // isClickableOnly: true, + onSearch: (text) { + _search(); + }, + controller: _searchController, + ), + ), + ], + ), + ), + const Divider(height: 1), + // List + Expanded(child: _buildListContent()), + ], + )), + ); + } +} diff --git a/lib/pages/KeyProjects/Danger/danger_manager_detail_page.dart b/lib/pages/KeyProjects/Danger/danger_manager_detail_page.dart new file mode 100644 index 0000000..802d292 --- /dev/null +++ b/lib/pages/KeyProjects/Danger/danger_manager_detail_page.dart @@ -0,0 +1,398 @@ +import 'dart:io'; + +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/full_screen_video_page.dart'; +import 'package:qhd_prevention/customWidget/photo_picker_row.dart'; +import 'package:qhd_prevention/customWidget/picker/CupertinoDatePicker.dart'; +import 'package:qhd_prevention/customWidget/single_image_viewer.dart'; +import 'package:qhd_prevention/customWidget/toast_util.dart'; +import 'package:qhd_prevention/http/ApiService.dart'; +import 'package:qhd_prevention/pages/home/tap/item_list_widget.dart'; +import 'package:qhd_prevention/pages/my_appbar.dart'; +import 'package:qhd_prevention/tools/tools.dart'; + +class DangerManagerDetailPage extends StatefulWidget { + const DangerManagerDetailPage({super.key, required this.info}); + + final Map info; + + @override + State createState() => + _DangerManagerDetailPageState(); +} + +class _DangerManagerDetailPageState extends State { + Map hiddenForm = {}; + Map punishForm = {}; + List ysImages = []; + + @override + void initState() { + // TODO: implement initState + super.initState(); + _getData(); + } + + Future _getData() async { + final result = await ApiService.getKeyprojectDangerFindHidden( + widget.info['HIDDEN_ID'] ?? '', + widget.info['OUTSOURCED_ID'] ?? '', + ); + try { + setState(() { + hiddenForm = result['pd'] ?? {}; + if (FormUtils.hasValue(hiddenForm, 'punishForm')) { + punishForm = hiddenForm['punishForm']; + } + print(hiddenForm); + }); + } catch (e) { + ToastUtil.showNormal(context, '$e'); + } + } + + List _getServerPath(List paths) { + List p = paths.map((val) => '${val['FILEPATH']}').toList(); + return p; + } + + String _getServerVideoPath(List paths) { + List p = paths.map((val) => '${val['FILEPATH']}').toList(); + if (p.isNotEmpty) { + return '${ApiService.baseImgPath}${p[0]}'; + } + return ''; + } + + Widget _punishFormWidget() { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ListItemFactory.createBuildSimpleSection('处罚信息'), + ItemListWidget.multiLineTitleTextField( + label: '处罚原因:', + isEditable: false, + text: punishForm['REASON'] ?? '', + ), + const Divider(), + ItemListWidget.multiLineTitleTextField( + label: '处罚金额:', + isEditable: false, + text: punishForm['AMOUT'] ?? '', + ), + const Divider(), + ItemListWidget.multiLineTitleTextField( + label: '被处罚单位:', + isEditable: false, + text: punishForm['UNITS_NAME'] ?? '', + ), + const Divider(), + ItemListWidget.multiLineTitleTextField( + label: '被处罚人:', + isEditable: false, + text: punishForm['PERSON_NAME'] ?? '', + ), + const Divider(), + ItemListWidget.multiLineTitleTextField( + label: '下发人:', + isEditable: false, + text: punishForm['CREATOR_NAME'] ?? '', + ), + const Divider(), + ItemListWidget.multiLineTitleTextField( + label: '下发处罚时间:', + isEditable: false, + text: punishForm['DATE'] ?? '', + ), + const Divider(), + if (FormUtils.hasValue(punishForm, 'HANDLE_IMG')) + ListItemFactory.createTextImageItem( + horizontalPadding: 12, + text: '罚款缴纳单', + onImageTapped: (index) { + presentOpaque( + SingleImageViewer( + imageUrl: + '${ApiService.baseImgPath}${_getServerPath(punishForm['HANDLE_IMG'])[index]}', + ), + context, + ); + }, + imageUrls: punishForm['HANDLE_IMG'], + ), + const Divider(), + ItemListWidget.multiLineTitleTextField( + label: '是否缴纳罚款:', + isEditable: false, + text: punishForm['已缴'] == '1' ? '已缴' : '未缴', + ), + const Divider(), + if (punishForm['HANDLED'].toString() == '1') + Column( + children: [ + ItemListWidget.multiLineTitleTextField( + label: '处罚处理人:', + isEditable: false, + text: punishForm['PERSON_NAME'] ?? '', + ), + const Divider(), + ItemListWidget.multiLineTitleTextField( + label: '处罚处理时间:', + isEditable: false, + text: punishForm['HANLDE_TIME'] ?? '', + ), + ], + ), + ], + ); + } + + Future _submit() async { + + if (!FormUtils.hasValue(hiddenForm, 'CHECKTIME')) { + ToastUtil.showNormal(context, '请选择验收时间'); + return; + } + + + LoadingDialogHelper.show(); + final result = await ApiService.checkKeyprojectDanger(widget.info['HIDDEN_ID'] ?? '', hiddenForm); + if (result['result'] == 'success') { + final List files = _getServerPath(hiddenForm['ysImgs']); + for (String p in files) { + final upResult = await ApiService.addNormalImgFiles(p,{'TYPE':'5','FOREIGN_KEY':widget.info['HIDDEN_ID']}); + if (FormUtils.hasValue(upResult, 'network_error')) { + LoadingDialogHelper.hide(); + ToastUtil.showNormal(context, '上传文件出错'); + return; + } + } + Navigator.of(context).pop(); + LoadingDialogHelper.hide(); + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: MyAppbar(title: '隐患管理'), + body: SafeArea( + child: Padding( + padding: EdgeInsets.symmetric(vertical: 12, horizontal: 12), + child: Column( + children: [ + Expanded( + child: Container( + padding: EdgeInsets.symmetric(horizontal: 12), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(5), + ), + child: + hiddenForm.isNotEmpty + ? ListView( + children: [ + ListItemFactory.createTextImageItem( + horizontalPadding: 12, + text: '隐患照片', + onImageTapped: (index) { + presentOpaque( + SingleImageViewer( + imageUrl: + '${ApiService.baseImgPath}${_getServerPath(hiddenForm['hiddenImgs'])[index]}', + ), + context, + ); + }, + imageUrls: _getServerPath( + hiddenForm['hiddenImgs'], + ), + ), + const Divider(), + + ListItemFactory.createTextVideoItem( + horizontalPadding: 12, + onVideoTapped: () { + showDialog( + context: context, + barrierColor: Colors.black54, + builder: + (_) => VideoPlayerPopup( + videoUrl: _getServerVideoPath( + hiddenForm['hiddenVideos'], + ), + ), + ); + }, + text: '隐患视频', + videoUrl: _getServerVideoPath( + hiddenForm['hiddenVideos'], + ), + ), + const Divider(), + ItemListWidget.multiLineTitleTextField( + label: '隐患描述', + isEditable: false, + text: hiddenForm['HIDDENDESCR'], + ), + const Divider(), + ItemListWidget.singleLineTitleText( + label: '隐患部位', + isEditable: false, + text: hiddenForm['HIDDENPART'] ?? '', + ), + const Divider(), + ItemListWidget.singleLineTitleText( + label: '隐患级别', + isEditable: false, + text: hiddenForm['HIDDENLEVEL_NAME'] ?? '', + ), + const Divider(), + ItemListWidget.singleLineTitleText( + label: '隐患类型', + isEditable: false, + text: hiddenForm['HIDDENTYPE_NAME'] ?? '', + ), + const Divider(), + ItemListWidget.singleLineTitleText( + label: '隐患处置', + isEditable: false, + text: '限期整改', + ), + const Divider(), + ItemListWidget.singleLineTitleText( + label: '整改期限', + isEditable: false, + text: hiddenForm['RECTIFICATIONDEADLINE'] ?? '', + ), + const Divider(), + ItemListWidget.singleLineTitleText( + label: '整改部门', + isEditable: false, + text: hiddenForm['RECTIFICATIONDEPT_NAME'] ?? '', + ), + const Divider(), + ItemListWidget.singleLineTitleText( + label: '整改人', + isEditable: false, + text: hiddenForm['RECTIFICATIONOR_NAME'] ?? '', + ), + const Divider(), + if (hiddenForm['STATE'].toString() == '2' || + hiddenForm['STATE'].toString() == '4') + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ItemListWidget.singleLineTitleText( + label: '整改时间', + isEditable: false, + text: + hiddenForm['RECTIFICATIONTIME'] ?? '', + ), + const Divider(), + ListItemFactory.createTextImageItem( + horizontalPadding: 12, + text: '整改照片', + imageUrls: _getServerPath( + hiddenForm['zgImgs'], + ), + onImageTapped: (index) { + presentOpaque( + SingleImageViewer( + imageUrl: + '${ApiService.baseImgPath}${_getServerPath(hiddenForm['zgImgs'])[index]}', + ), + context, + ); + }, + ), + const Divider(), + ], + ), + if (hiddenForm['STATE'].toString() == '4' || + widget.info['TabCur'].toString() == '1') + Column( + crossAxisAlignment: CrossAxisAlignment.start, + + children: [ + ItemListWidget.selectableLineTitleTextRightButton( + label: '验收时间', + isEditable: + widget.info['TabCur'] == '1' + ? true + : false, + text: hiddenForm['CHECKTIME'] ?? '', + onTap: () async { + DateTime? picked = + await BottomDateTimePicker.showDate( + context, + ); + if (picked != null) { + setState(() { + hiddenForm['CHECKTIME'] = + DateFormat( + 'yyyy-MM-dd HH:mm', + ).format(picked); + }); + } + }, + ), + const Divider(), + + Padding(padding: EdgeInsets.symmetric(horizontal: 8),child: RepairedPhotoSection( + title: '验收照片', + maxCount: 4, + mediaType: MediaType.image, + isShowAI: false, + isEdit: widget.info['TabCur'] == '1' + ? true + : false, + isRequired:false, + initialMediaPaths: _getServerPath( + hiddenForm['ysImgs'], + ).map((path) => '${ApiService.baseImgPath}$path').toList(), + onMediaTapped: (p) { + presentOpaque(SingleImageViewer(imageUrl: p), context); + }, + onChanged: + (files) => setState(() { + hiddenForm['ysImgs'] = + files + .map((f) => {'FILEPATH': f.path}) + .toList(); + }), onAiIdentify: () { }, + + ),) + + ], + ), + + if (FormUtils.hasValue(hiddenForm, 'punishForm')) + _punishFormWidget(), + ], + ) + : SizedBox(width: double.maxFinite), + ), + ), + SizedBox(height: 20), + CustomButton( + text: widget.info['TabCur'] == '1' ? '验收' : '返回', + backgroundColor: Colors.blue, + onPressed: () { + if (widget.info['TabCur'] == '1') { + _submit(); + } else { + Navigator.of(context).pop(); + } + }, + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/pages/KeyProjects/Danger/danger_manager_page.dart b/lib/pages/KeyProjects/Danger/danger_manager_page.dart new file mode 100644 index 0000000..5283a7c --- /dev/null +++ b/lib/pages/KeyProjects/Danger/danger_manager_page.dart @@ -0,0 +1,250 @@ +import 'package:flutter/material.dart'; +import 'package:qhd_prevention/customWidget/ItemWidgetFactory.dart'; +import 'package:qhd_prevention/customWidget/custom_button.dart'; +import 'package:qhd_prevention/customWidget/danner_repain_item.dart'; +import 'package:qhd_prevention/http/ApiService.dart'; +import 'package:qhd_prevention/pages/KeyProjects/Danger/danger_manager_detail_page.dart'; +import 'package:qhd_prevention/pages/my_appbar.dart'; +import 'package:qhd_prevention/tools/tools.dart'; + +class DangerManagerPage extends StatefulWidget { + const DangerManagerPage({super.key, required this.OUTSOURCED_ID}); + + final String OUTSOURCED_ID; + + @override + State createState() => _DangerManagerPageState(); +} + +class _DangerManagerPageState extends State + with SingleTickerProviderStateMixin { + late TabController _tabController; + late int _selectedTab = 0; + late List listDates = []; + late int totalPage = 0; + int currentPage = 1; + final ScrollController _scrollController = ScrollController(); + + @override + void dispose() { + _scrollController.dispose(); + super.dispose(); + } + @override + void initState() { + // TODO: implement initState + super.initState(); + _scrollController.addListener(_onScroll); + + _tabController = TabController(length: 2, vsync: this); + _tabController.addListener(() { + if (_tabController.indexIsChanging) { + setState(() { + _selectedTab = _tabController.index; + currentPage = 1; + }); + print('切换到标签:${_tabController.index}'); + + _getDataWithIndex(_tabController.index); + } + }); + _getDataWithIndex(_selectedTab); + + } + void _onScroll() { + if (_scrollController.position.pixels >= + _scrollController.position.maxScrollExtent) { + if (currentPage < totalPage) { + currentPage++; + _getDataWithIndex(_selectedTab); + } + } + } + Future _getDataWithIndex(int index) async { + LoadingDialogHelper.show(); + try { + final data = { + 'KEYWORDS': '', + 'OUTSOURCED_ID': widget.OUTSOURCED_ID, + 'CREATOR': SessionService.instance.loginUserId, + 'ISCHECK': index + 1, + }; + final url = + '/app/keyprojectcheck/listHidden?showCount=10¤tPage=$currentPage'; + final response = await ApiService.getKeyprojectDangerList(url, data); + + setState(() { + if (currentPage == 1) { + listDates = response['varList']; + } else { + listDates.addAll(response['varList']); + } + Map page = response['page']; + totalPage = page['totalPage'] ?? 1; + }); + LoadingDialogHelper.hide(); + } catch (e) { + print('Error fetching data: $e'); + LoadingDialogHelper.show(); + } + } + + String _getState(String state) { + final info = {'1': "未整改", + '2': "已整改", + '4': "已验收",}; + return info[state] as String; + } + + void _goToDetail(Map item, String tabCur) async { + item['TabCur'] = tabCur; + await pushPage(DangerManagerDetailPage(info: item), context); + _getDataWithIndex(_selectedTab); + } + + Widget _itemCell(Map item) { + return Padding( + padding: EdgeInsets.symmetric(horizontal: 12), + child: GestureDetector( + onTap: () { + _goToDetail(item, '2'); + }, + child: Container( + padding: EdgeInsets.only(top: 12, left: 12, right: 12), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(5), + color: Colors.white, + ), + child: Column( + children: [ + ListItemFactory.headerTitle(item['OUTSOURCED_NAME'] ?? ''), + const SizedBox(height: 5,), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text("隐患来源: ${item['SOURCE'] ?? ''}"), + ], + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text("隐患描述: ${item['HIDDENDESCR'] ?? ''}"), + ], + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text("隐患发现人: ${item['CREATOR_NAME'] ?? item['CREATOR_NAMES']}"), + ], + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text("隐患发现时间: ${item['CREATTIME'] ?? ''}"), + ], + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text("隐患整改人: ${item['RECTIFICATIONOR_NAME'] ?? ''}"), + Text("整改时间: ${item['RECTIFICATIONTIME'] ?? ''}"), + ], + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text("隐患验收人: ${item['CREATOR_NAME'] ?? ''}"), + Text("验收时间: ${item['CHECKTIME'] ?? ''}"), + ], + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text("隐患状态: ${_getState(item['STATE'].toString())}"), + Text("是否处罚: ${FormUtils.hasValue(item, 'ISPUNISH') ? (item['ISPUNISH'] == '1' ? '是' : '否'):''}"), + ], + ), + SizedBox(height: 5,), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + SizedBox(), + Row( + children: [ + if (item['CREATOR'] == SessionService.instance.loginUserId && _selectedTab == 0) + CustomButton( + onPressed: () { _goToDetail(item, '1');}, + text: '验收', + height: 30, + padding: EdgeInsets.symmetric(vertical: 5, horizontal: 15), + textStyle: TextStyle(fontSize: 13, color: Colors.white), + backgroundColor: Colors.blue, + ), + CustomButton( + onPressed: () { _goToDetail(item, '2');}, + text: '查看', + height: 30, + padding: EdgeInsets.symmetric(vertical: 5, horizontal: 15), + textStyle: TextStyle(fontSize: 13, color: Colors.white), + backgroundColor: Colors.blue, + ), + ], + ) + ], + ), + SizedBox(height: 10,), + const Divider(height: 1,) + ], + ), + ), + ) + ); + } + + void _handleItemTap(Map item, int index) {} + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: MyAppbar(title: '隐患管理'), + body: SafeArea( + child: Column( + children: [ + Container( + color: Colors.white, + child: TabBar( + controller: _tabController, + labelStyle: TextStyle(fontSize: 16), + indicator: UnderlineTabIndicator( + borderSide: BorderSide(width: 3.0, color: Colors.blue), + insets: EdgeInsets.symmetric(horizontal: 100.0), + ), + labelColor: Colors.blue, + unselectedLabelColor: Colors.grey, + tabs: const [Tab(text: '待验收隐患'), Tab(text: '已验收隐患')], + ), + ), + Expanded( + child: + listDates.isEmpty + ? NoDataWidget.show() + : ListView.separated( + padding: EdgeInsets.only(top: 15), + itemCount: listDates.length, + controller: _scrollController, + separatorBuilder: (_, __) => const SizedBox(), + itemBuilder: (context, index) { + final item = listDates[index]; + return GestureDetector( + onTap: () => _handleItemTap(item, index), + child: _itemCell(item), + ); + }, + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/pages/KeyProjects/Punishment/PunishmentModalAlert.dart b/lib/pages/KeyProjects/Punishment/PunishmentModalAlert.dart new file mode 100644 index 0000000..477bed3 --- /dev/null +++ b/lib/pages/KeyProjects/Punishment/PunishmentModalAlert.dart @@ -0,0 +1,335 @@ +import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; +import 'package:qhd_prevention/customWidget/ItemWidgetFactory.dart'; +import 'package:qhd_prevention/customWidget/picker/CupertinoDatePicker.dart'; +import 'package:qhd_prevention/customWidget/toast_util.dart'; +import 'package:qhd_prevention/pages/home/tap/item_list_widget.dart'; + +/// punlish_modal.dart +/// 把你提供的 uniapp 弹窗翻译为 Flutter 版本。 +/// 提供: +/// - 可复用的 PunishModal Widget +/// - showPunishDialog 辅助函数 +/// - 一个最小的 main() 示例展示如何调用 + +class PunishModal extends StatefulWidget { + /// 初始数据(可选),key 与原 uniapp 对应: + /// "ISPUNISH" : '1' 或 '2' + /// "REASON" + /// "AMOUT" + /// "RECTIFICATIONDEPT_NAME" + /// "RECTIFICATIONOR_NAME" + /// "DATE" (字符串) + final Map? initial; + final void Function(Map form) onSubmit; + + const PunishModal({Key? key, this.initial, required this.onSubmit}) + : super(key: key); + + @override + _PunishModalState createState() => _PunishModalState(); +} + +class _PunishModalState extends State { + late bool isp = false; // 默认否 + late TextEditingController reasonController; + late TextEditingController amountController; + late TextEditingController deptController; + late TextEditingController personController; + String? dateText; + + final FocusNode _amountFocus = FocusNode(); + final _formKey = GlobalKey(); + + @override + void initState() { + super.initState(); + reasonController = TextEditingController( + text: widget.initial?['REASON'] ?? '', + ); + amountController = TextEditingController( + text: widget.initial?['AMOUT'] ?? '', + ); + deptController = TextEditingController( + text: widget.initial?['RECTIFICATIONDEPT_NAME'] ?? '', + ); + personController = TextEditingController( + text: widget.initial?['RECTIFICATIONOR_NAME'] ?? '', + ); + dateText = widget.initial?['DATE']; + isp = widget.initial?['ISPUNISH'] == '1' ? true : false; + + // 相当于 @blur=\"checkNumber\",监听焦点失去时做检查 + _amountFocus.addListener(() { + if (!_amountFocus.hasFocus) { + _checkNumber(); + } + }); + } + + @override + void dispose() { + reasonController.dispose(); + amountController.dispose(); + deptController.dispose(); + personController.dispose(); + _amountFocus.dispose(); + super.dispose(); + } + + void _checkNumber() { + final text = amountController.text.trim(); + if (text.isEmpty) return; + // 允许整数或两位小数 + final reg = RegExp(r"^\d+(?:\.\d{1,2})?$"); + if (!reg.hasMatch(text)) { + // 显示错误提示,并做简单修正(可按需改变) + ScaffoldMessenger.of( + context, + ).showSnackBar(const SnackBar(content: Text('请输入有效的金额(最多两位小数)'))); + // 尝试提取数字部分 + final match = RegExp(r"\d+(?:\.\d{1,2})?").firstMatch(text); + if (match != null) { + amountController.text = match.group(0)!; + } else { + amountController.clear(); + } + } + } + + void _onConfirm() { + if (isp) { + if (reasonController.text.trim().isEmpty) { + ToastUtil.showNormal(context, '请填写处罚原因'); + return; + } + if (amountController.text.trim().isEmpty) { + ToastUtil.showNormal(context, '请填写处罚金额'); + return; + } + if (dateText?.length == 0) { + ToastUtil.showNormal(context, '请选择下发处罚时间'); + return; + } + } + + final result = { + 'ISPUNISH': isp ? "1" : "2", + 'REASON': reasonController.text.trim(), + 'AMOUT': amountController.text.trim(), + 'RECTIFICATIONDEPT_NAME': deptController.text.trim(), + 'RECTIFICATIONOR_NAME': personController.text.trim(), + 'DATE': dateText ?? '', + }; + + widget.onSubmit(result); + Navigator.of(context).pop(); + } + + @override + Widget build(BuildContext context) { + // 我们改为根据内容自动伸缩,但当内容过高时仍然限制最大高度并可滚动。 + final maxBodyHeight = MediaQuery.of(context).size.height * 0.6; // 可按需调整 + + return Material( + color: Colors.transparent, + child: Center( + child: Container( + width: MediaQuery.of(context).size.width * 0.9, + // 不再强制固定高度,使用 Column 的 min size,让弹窗高度随内容变化 + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + // Header + Container( + padding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 10, + ), + decoration: const BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.vertical(top: Radius.circular(8)), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + const Expanded( + child: Text( + '处罚', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.w600, + ), + ), + ), + IconButton( + onPressed: () => Navigator.of(context).pop(), + icon: const Icon(Icons.close, color: Colors.red), + ), + ], + ), + ), + + // Body (表单) + // 使用 ConstrainedBox 限制最大高度,内部使用 SingleChildScrollView,这样当字段少时弹窗高度自动变小,字段多时会出现滚动。 + ConstrainedBox( + constraints: BoxConstraints(maxHeight: maxBodyHeight), + child: SingleChildScrollView( + padding: const EdgeInsets.all(12), + child: Form( + key: _formKey, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // 是否进行罚款 + ListItemFactory.createYesNoSection( + title: '是否进行罚款', + horizontalPadding: 0, + groupValue: isp, + onChanged: (v) => setState(() => isp = v), + ), + + const SizedBox(height: 8), + + // 以下内容仅在 isp == '1' 时显示 + if (isp) ...[ + ItemListWidget.singleLineTitleText( + label: '处罚原因', + isEditable: true, + controller: reasonController, + hintText: '请输入处罚原因', + ), + const Divider(), + ItemListWidget.singleLineTitleText( + label: '处罚金额(元)', + isEditable: true, + keyboardType: const TextInputType.numberWithOptions( + decimal: true, + ), + controller: amountController, + hintText: '请输入处罚金额', + ), + const Divider(), + ItemListWidget.singleLineTitleText( + label: '被处罚单位', + isEditable: false, + text: + widget.initial?['RECTIFICATIONDEPT_NAME'] ?? '', + ), + const Divider(), + ItemListWidget.singleLineTitleText( + label: '被处罚人', + isEditable: false, + text: widget.initial?['RECTIFICATIONOR_NAME'] ?? '', + ), + const Divider(), + ItemListWidget.selectableLineTitleTextRightButton( + label: '下发处罚时间', + isEditable: true, + text: '', + onTap: () async { + DateTime? picked = + await BottomDateTimePicker.showDate( + context, + ); + if (picked != null) { + setState(() { + dateText = + DateFormat( + 'yyyy-MM-dd HH:mm', + ).format(picked); + }); + } + }, + ), + + ], + const SizedBox(height: 12), + ], + ), + ), + ), + ), + + // Footer 按钮 + Container( + padding: const EdgeInsets.all(8), + decoration: const BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.vertical( + bottom: Radius.circular(8), + ), + ), + child: Row( + children: [ + Expanded( + child: OutlinedButton( + onPressed: () => Navigator.of(context).pop(), + child: const Text('关闭'), + ), + ), + const SizedBox(width: 8), + Expanded( + child: ElevatedButton( + style: ElevatedButton.styleFrom( + foregroundColor: Colors.green, + backgroundColor: Colors.white, + side: const BorderSide(color: Colors.green), + ), + onPressed: _onConfirm, + child: const Text( + '确认', + style: TextStyle(color: Colors.green), + ), + ), + ), + ], + ), + ), + ], + ), + ), + ), + ); + } + + Widget _buildLabel(String text) => Padding( + padding: const EdgeInsets.only(bottom: 6), + child: Text(text, style: const TextStyle(fontWeight: FontWeight.w600)), + ); +} + +/// 显示showPunishDialog +Future showPunishDialog( + BuildContext context, { + Map? initial, + required void Function(Map) onSubmit, +}) { + return showDialog( + context: context, + barrierDismissible: false, + builder: (context) { + final viewInsets = MediaQuery.of(context).viewInsets; + return AnimatedPadding( + padding: viewInsets + const EdgeInsets.all(16), + duration: const Duration(milliseconds: 200), + curve: Curves.decelerate, + child: Align( + alignment: Alignment.center, + child: Material( + color: Colors.transparent, + child: Container( + // 保持原来 Dialog 的圆角和背景 + decoration: BoxDecoration(borderRadius: BorderRadius.circular(8)), + child: PunishModal(initial: initial, onSubmit: onSubmit), + ), + ), + ), + ); + }, + ); +} diff --git a/lib/pages/KeyProjects/Punishment/punishment_list_page.dart b/lib/pages/KeyProjects/Punishment/punishment_list_page.dart new file mode 100644 index 0000000..161ac8e --- /dev/null +++ b/lib/pages/KeyProjects/Punishment/punishment_list_page.dart @@ -0,0 +1,264 @@ +import 'package:flutter/material.dart'; +import 'package:qhd_prevention/pages/KeyProjects/Danger/danger_manager_page.dart'; +import 'package:qhd_prevention/pages/KeyProjects/KeyProject/keyProject_detail.dart'; +import 'package:qhd_prevention/pages/KeyProjects/Punishment/punishment_manager_page.dart'; +import 'package:qhd_prevention/pages/my_appbar.dart'; +import 'package:qhd_prevention/tools/tools.dart'; +import 'package:qhd_prevention/customWidget/custom_button.dart'; +import 'package:qhd_prevention/customWidget/search_bar_widget.dart'; +import 'package:qhd_prevention/http/ApiService.dart'; + +class PunishmentListPage extends StatefulWidget { + final String flow; + + const PunishmentListPage({Key? key, required this.flow}) : super(key: key); + + @override + _PunishmentListPageState createState() => _PunishmentListPageState(); +} + +class _PunishmentListPageState extends State { + // Data and state variables + List list = []; + int currentPage = 1; + int rows = 10; + int totalPage = 1; + bool isLoading = false; + final TextEditingController _searchController = TextEditingController(); + + List> stepList = []; + int sindex = 0; + String searchKeywords = ''; + + final GlobalKey _scaffoldKey = GlobalKey(); + + final ScrollController _scrollController = ScrollController(); + + @override + void initState() { + super.initState(); + _fetchData(); + _scrollController.addListener(_onScroll); + } + + @override + void dispose() { + _scrollController.dispose(); + super.dispose(); + } + + void _onScroll() { + if (_scrollController.position.pixels >= + _scrollController.position.maxScrollExtent && + !isLoading) { + if (currentPage < totalPage) { + currentPage++; + _fetchData(); + } + } + } + + Future _fetchData() async { + if (isLoading) return; + setState(() => isLoading = true); + + try { + final data = {'KEYWORDS': searchKeywords, 'PUNISHUser':SessionService.instance.loginUserId,}; + final url = + '/app/keyProjects/getPUNISHlist?showCount=10¤tPage=$currentPage'; + final response = await ApiService.getKeyProjectList(data, url); + + setState(() { + if (currentPage == 1) { + list = response['varList']; + } else { + list.addAll(response['varList']); + } + Map page = response['page']; + totalPage = page['totalPage'] ?? 1; + isLoading = false; + }); + } catch (e) { + print('Error fetching data: $e'); + setState(() => isLoading = false); + } + } + + void _search() { + searchKeywords = _searchController.text.trim(); + currentPage = 1; + list.clear(); + _fetchData(); + } + + void _goToDetail(Map item) async { + await pushPage(PunishmentManagerPage(OUTSOURCED_ID: item['OUTSOURCED_ID'] ?? ''),context); + _fetchData(); + } + + Widget _buildListItem(Map item) { + bool showBadge = item['cfcount'] > 0; + return Card( + color: Colors.white, + margin: const EdgeInsets.all(8.0), + child: InkWell( + onTap: () => _goToDetail(item), + child: Padding( + padding: const EdgeInsets.all(12.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // 标题 + 红点 + Row( + children: [ + Expanded( + child: Text( + "${item['OUTSOURCED_NAME'] ?? ''}", + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ), + if (showBadge) + Container( + margin: const EdgeInsets.only(left: 8.0), + padding: const EdgeInsets.symmetric( + horizontal: 8.0, + vertical: 2.0, + ), + height: 18, + alignment: Alignment.center, + decoration: BoxDecoration( + color: const Color(0xFFdd514c), + borderRadius: BorderRadius.circular(96), + ), + child: const Text( + '1', + style: TextStyle(color: Colors.white, fontSize: 10), + ), + ), + ], + ), + const SizedBox(height: 8), + + // 根据 IS_CORP_TYPE 显示不同的主管/监管信息(对应 vue 逻辑) + if (item['IS_CORP_TYPE'] == '1') ...[ + Text("主管部门:${item['Q_COMPETENT_DEPT_NAME'] ?? ''}"), + const SizedBox(height: 6), + Text("监督部门:${item['DEPARTMENT_NAME'] ?? ''}"), + ] else if (item['IS_CORP_TYPE'] == '0') ...[ + Text("主管部门:${item['MANAGER_DEPARTMENT_NAME'] ?? ''}"), + const SizedBox(height: 6), + Text("监管部门:${item['DEPARTMENT_NAME'] ?? ''}"), + ], + const SizedBox(height: 8), + + // 相关方单位负责人 + 电话(两列) + Row( + children: [ + Expanded( + child: Text( + "相关方单位负责人:${item['UNITS_PIC_NAME'] ?? ''}", + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + const SizedBox(height: 8), + + Text( + "电话:${item['UNITS_PHONE'] ?? ''}", + ), + const SizedBox(height: 6), + + + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + SizedBox(), + CustomButton( + onPressed: () { _goToDetail(item);}, + text: '查看', + height: 30, + padding: EdgeInsets.symmetric(vertical: 5, horizontal: 15), + textStyle: TextStyle(fontSize: 13, color: Colors.white), + backgroundColor: Colors.blue, + ), + ], + ), + ], + ), + ), + ), + ); + } + + Widget _buildListContent() { + if (isLoading && list.isEmpty) { + // 初始加载时显示居中的加载指示器 + return Center(child: CircularProgressIndicator()); + } else if (list.isEmpty) { + // 没有数据 + return NoDataWidget.show(); + } else { + // 有数据或加载更多 + return ListView.builder( + padding: EdgeInsets.zero, + + controller: _scrollController, + itemCount: list.length + (isLoading ? 1 : 0), + itemBuilder: (context, index) { + if (index >= list.length) { + // 加载更多时在列表底部显示加载指示器 + return Padding( + padding: const EdgeInsets.symmetric(vertical: 16.0), + child: Center(child: CircularProgressIndicator()), + ); + } + return _buildListItem(list[index]); + }, + ); + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + key: _scaffoldKey, + appBar: MyAppbar(title: widget.flow), + + body: SafeArea(child: Column( + children: [ + // Filter bar + Container( + color: Colors.white, + padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 8), + child: Row( + children: [ + Expanded( + flex: 2, + child: SearchBarWidget( + showResetButton: false, + hintText: "请输入关键字", + // isClickableOnly: true, + onSearch: (text) { + _search(); + }, + controller: _searchController, + ), + ), + ], + ), + ), + const Divider(height: 1), + // List + Expanded(child: _buildListContent()), + ], + )), + ); + } +} diff --git a/lib/pages/KeyProjects/Punishment/punishment_manager_detail_page.dart b/lib/pages/KeyProjects/Punishment/punishment_manager_detail_page.dart new file mode 100644 index 0000000..448fc8e --- /dev/null +++ b/lib/pages/KeyProjects/Punishment/punishment_manager_detail_page.dart @@ -0,0 +1,396 @@ +import 'dart:io'; + +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/full_screen_video_page.dart'; +import 'package:qhd_prevention/customWidget/photo_picker_row.dart'; +import 'package:qhd_prevention/customWidget/picker/CupertinoDatePicker.dart'; +import 'package:qhd_prevention/customWidget/single_image_viewer.dart'; +import 'package:qhd_prevention/customWidget/toast_util.dart'; +import 'package:qhd_prevention/http/ApiService.dart'; +import 'package:qhd_prevention/pages/home/tap/item_list_widget.dart'; +import 'package:qhd_prevention/pages/my_appbar.dart'; +import 'package:qhd_prevention/tools/tools.dart'; + +class PunishmentManagerDetailPage extends StatefulWidget { + const PunishmentManagerDetailPage({super.key, required this.info}); + + final Map info; + + @override + State createState() => + _PunishmentManagerDetailPageState(); +} + +class _PunishmentManagerDetailPageState extends State { + Map hiddenForm = {}; + Map punishForm = {}; + List ysImages = []; + + @override + void initState() { + // TODO: implement initState + super.initState(); + _getData(); + } + + Future _getData() async { + final result = await ApiService.getKeyprojectDangerFindHidden( + widget.info['HIDDEN_ID'] ?? '', + widget.info['OUTSOURCED_ID'] ?? '', + ); + try { + setState(() { + hiddenForm = result['pd'] ?? {}; + if (FormUtils.hasValue(hiddenForm, 'punishForm')) { + punishForm = hiddenForm['punishForm']; + } + print(hiddenForm); + }); + } catch (e) { + ToastUtil.showNormal(context, '$e'); + } + } + + List _getServerPath(List paths) { + List p = paths.map((val) => '${val['FILEPATH']}').toList(); + return p; + } + + String _getServerVideoPath(List paths) { + List p = paths.map((val) => '${val['FILEPATH']}').toList(); + if (p.isNotEmpty) { + return '${ApiService.baseImgPath}${p[0]}'; + } + return ''; + } + + Widget _punishFormWidget() { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ListItemFactory.createBuildSimpleSection('处罚信息'), + ItemListWidget.multiLineTitleTextField( + label: '处罚原因:', + isEditable: false, + text: punishForm['REASON'] ?? '', + ), + const Divider(), + ItemListWidget.multiLineTitleTextField( + label: '处罚金额:', + isEditable: false, + text: punishForm['AMOUT'] ?? '', + ), + const Divider(), + ItemListWidget.multiLineTitleTextField( + label: '被处罚单位:', + isEditable: false, + text: punishForm['UNITS_NAME'] ?? '', + ), + const Divider(), + ItemListWidget.multiLineTitleTextField( + label: '被处罚人:', + isEditable: false, + text: punishForm['PERSON_NAME'] ?? '', + ), + const Divider(), + ItemListWidget.multiLineTitleTextField( + label: '下发人:', + isEditable: false, + text: punishForm['CREATOR_NAME'] ?? '', + ), + const Divider(), + ItemListWidget.multiLineTitleTextField( + label: '下发处罚时间:', + isEditable: false, + text: punishForm['DATE'] ?? '', + ), + const Divider(), + if (FormUtils.hasValue(punishForm, 'HANDLE_IMG')) + ListItemFactory.createTextImageItem( + horizontalPadding: 12, + text: '罚款缴纳单', + onImageTapped: (index) { + presentOpaque( + SingleImageViewer( + imageUrl: + '${ApiService.baseImgPath}${_getServerPath(punishForm['HANDLE_IMG'])[index]}', + ), + context, + ); + }, + imageUrls: punishForm['HANDLE_IMG'], + ), + const Divider(), + ItemListWidget.multiLineTitleTextField( + label: '是否缴纳罚款:', + isEditable: false, + text: punishForm['已缴'] == '1' ? '已缴' : '未缴', + ), + const Divider(), + if (punishForm['HANDLED'].toString() == '1') + Column( + children: [ + ItemListWidget.multiLineTitleTextField( + label: '处罚处理人:', + isEditable: false, + text: punishForm['PERSON_NAME'] ?? '', + ), + const Divider(), + ItemListWidget.multiLineTitleTextField( + label: '处罚处理时间:', + isEditable: false, + text: punishForm['HANLDE_TIME'] ?? '', + ), + ], + ), + ], + ); + } + + Future _submit() async { + + if (!FormUtils.hasValue(hiddenForm, 'CHECKTIME')) { + ToastUtil.showNormal(context, '请选择验收时间'); + return; + } + + + LoadingDialogHelper.show(); + final result = await ApiService.checkKeyprojectDanger(widget.info['HIDDEN_ID'] ?? '', hiddenForm); + if (result['result'] == 'success') { + final List files = _getServerPath(hiddenForm['ysImgs']); + for (String p in files) { + final upResult = await ApiService.addNormalImgFiles(p,{'TYPE':'5','FOREIGN_KEY':widget.info['HIDDEN_ID']}); + if (FormUtils.hasValue(upResult, 'network_error')) { + LoadingDialogHelper.hide(); + ToastUtil.showNormal(context, '上传文件出错'); + return; + } + } + Navigator.of(context).pop(); + LoadingDialogHelper.hide(); + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: MyAppbar(title: '隐患管理'), + body: SafeArea( + child: Padding( + padding: EdgeInsets.symmetric(vertical: 12, horizontal: 12), + child: Column( + children: [ + Expanded( + child: Container( + padding: EdgeInsets.symmetric(horizontal: 12), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(5), + ), + child: + hiddenForm.isNotEmpty + ? ListView( + children: [ + ListItemFactory.createTextImageItem( + horizontalPadding: 12, + text: '隐患照片', + onImageTapped: (index) { + presentOpaque( + SingleImageViewer( + imageUrl: + '${ApiService.baseImgPath}${_getServerPath(hiddenForm['hiddenImgs'])[index]}', + ), + context, + ); + }, + imageUrls: _getServerPath( + hiddenForm['hiddenImgs'], + ), + ), + const Divider(), + + ListItemFactory.createTextVideoItem( + horizontalPadding: 12, + onVideoTapped: () { + showDialog( + context: context, + barrierColor: Colors.black54, + builder: + (_) => VideoPlayerPopup( + videoUrl: _getServerVideoPath( + hiddenForm['hiddenVideos'], + ), + ), + ); + }, + text: '隐患视频', + videoUrl: _getServerVideoPath( + hiddenForm['hiddenVideos'], + ), + ), + const Divider(), + ItemListWidget.multiLineTitleTextField( + label: '隐患描述', + isEditable: false, + text: hiddenForm['HIDDENDESCR'], + ), + const Divider(), + ItemListWidget.singleLineTitleText( + label: '隐患部位', + isEditable: false, + text: hiddenForm['HIDDENPART'] ?? '', + ), + const Divider(), + ItemListWidget.singleLineTitleText( + label: '隐患级别', + isEditable: false, + text: hiddenForm['HIDDENLEVEL_NAME'] ?? '', + ), + const Divider(), + ItemListWidget.singleLineTitleText( + label: '隐患类型', + isEditable: false, + text: hiddenForm['HIDDENTYPE_NAME'] ?? '', + ), + const Divider(), + ItemListWidget.singleLineTitleText( + label: '隐患处置', + isEditable: false, + text: '限期整改', + ), + const Divider(), + ItemListWidget.singleLineTitleText( + label: '整改期限', + isEditable: false, + text: hiddenForm['RECTIFICATIONDEADLINE'] ?? '', + ), + const Divider(), + ItemListWidget.singleLineTitleText( + label: '整改部门', + isEditable: false, + text: hiddenForm['RECTIFICATIONDEPT_NAME'] ?? '', + ), + const Divider(), + ItemListWidget.singleLineTitleText( + label: '整改人', + isEditable: false, + text: hiddenForm['RECTIFICATIONOR_NAME'] ?? '', + ), + const Divider(), + if (hiddenForm['STATE'].toString() == '2' || + hiddenForm['STATE'].toString() == '4') + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ItemListWidget.singleLineTitleText( + label: '整改时间', + isEditable: false, + text: + hiddenForm['RECTIFICATIONTIME'] ?? '', + ), + const Divider(), + ListItemFactory.createTextImageItem( + horizontalPadding: 12, + text: '整改照片', + imageUrls: _getServerPath( + hiddenForm['zgImgs'], + ), + onImageTapped: (index) { + presentOpaque( + SingleImageViewer( + imageUrl: + '${ApiService.baseImgPath}${_getServerPath(hiddenForm['zgImgs'])[index]}', + ), + context, + ); + }, + ), + const Divider(), + ], + ), + if (hiddenForm['STATE'].toString() == '4' || + widget.info['TabCur'].toString() == '1') + Column( + crossAxisAlignment: CrossAxisAlignment.start, + + children: [ + ItemListWidget.selectableLineTitleTextRightButton( + label: '验收时间', + isEditable: + widget.info['TabCur'] == '1' + ? true + : false, + text: hiddenForm['CHECKTIME'] ?? '', + onTap: () async { + DateTime? picked = + await BottomDateTimePicker.showDate( + context, + ); + if (picked != null) { + setState(() { + hiddenForm['CHECKTIME'] = + DateFormat( + 'yyyy-MM-dd HH:mm', + ).format(picked); + }); + } + }, + ), + const Divider(), + + Padding(padding: EdgeInsets.symmetric(horizontal: 8),child: RepairedPhotoSection( + title: '验收照片', + maxCount: 4, + mediaType: MediaType.image, + isShowAI: false, + isEdit: widget.info['TabCur'] == '1' + ? true + : false, + isRequired:false, + initialMediaPaths: _getServerPath( + hiddenForm['ysImgs'], + ).map((path) => '${ApiService.baseImgPath}$path').toList(), + onMediaTapped: (p) { + presentOpaque(SingleImageViewer(imageUrl: p), context); + }, + onChanged: + (files) => setState(() { + hiddenForm['ysImgs'] = + files + .map((f) => {'FILEPATH': f.path}) + .toList(); + }), onAiIdentify: () { }, + + ),) + + ], + ), + + if (FormUtils.hasValue(hiddenForm, 'punishForm')) + _punishFormWidget(), + ], + ) + : SizedBox(width: double.maxFinite), + ), + ), + SizedBox(height: 20), + CustomButton( + text: '返回', + backgroundColor: Colors.blue, + onPressed: () { + + Navigator.of(context).pop(); + + }, + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/pages/KeyProjects/Punishment/punishment_manager_page.dart b/lib/pages/KeyProjects/Punishment/punishment_manager_page.dart new file mode 100644 index 0000000..07edbe8 --- /dev/null +++ b/lib/pages/KeyProjects/Punishment/punishment_manager_page.dart @@ -0,0 +1,290 @@ +import 'package:flutter/material.dart'; +import 'package:qhd_prevention/customWidget/ItemWidgetFactory.dart'; +import 'package:qhd_prevention/customWidget/custom_button.dart'; +import 'package:qhd_prevention/customWidget/danner_repain_item.dart'; +import 'package:qhd_prevention/customWidget/toast_util.dart'; +import 'package:qhd_prevention/http/ApiService.dart'; +import 'package:qhd_prevention/pages/KeyProjects/Danger/danger_manager_detail_page.dart'; +import 'package:qhd_prevention/pages/KeyProjects/Punishment/PunishmentModalAlert.dart'; +import 'package:qhd_prevention/pages/KeyProjects/Punishment/punishment_manager_detail_page.dart'; +import 'package:qhd_prevention/pages/my_appbar.dart'; +import 'package:qhd_prevention/tools/tools.dart'; + +class PunishmentManagerPage extends StatefulWidget { + const PunishmentManagerPage({super.key, required this.OUTSOURCED_ID}); + + final String OUTSOURCED_ID; + + @override + State createState() => _PunishmentManagerPageState(); +} + +class _PunishmentManagerPageState extends State + with SingleTickerProviderStateMixin { + late TabController _tabController; + late int _selectedTab = 0; + late List listDates = []; + late int totalPage = 0; + int currentPage = 1; + final ScrollController _scrollController = ScrollController(); + + @override + void dispose() { + _scrollController.dispose(); + super.dispose(); + } + + @override + void initState() { + // TODO: implement initState + super.initState(); + _scrollController.addListener(_onScroll); + + _tabController = TabController(length: 2, vsync: this); + _tabController.addListener(() { + if (_tabController.indexIsChanging) { + setState(() { + _selectedTab = _tabController.index; + currentPage = 1; + }); + print('切换到标签:${_tabController.index}'); + + _getDataWithIndex(_tabController.index); + } + }); + _getDataWithIndex(_selectedTab); + } + + void _onScroll() { + if (_scrollController.position.pixels >= + _scrollController.position.maxScrollExtent) { + if (currentPage < totalPage) { + currentPage++; + _getDataWithIndex(_selectedTab); + } + } + } + + Future _getDataWithIndex(int index) async { + LoadingDialogHelper.show(); + try { + final data = { + 'KEYWORDS': '', + 'OUTSOURCED_ID': widget.OUTSOURCED_ID, + 'CREATOR': SessionService.instance.loginUserId, + 'HANDLED': index + 1, + }; + final url = + '/app/keyprojectcheck/punishlist?showCount=10¤tPage=$currentPage'; + final response = await ApiService.getKeyprojectDangerList(url, data); + + setState(() { + if (currentPage == 1) { + listDates = response['varList']; + } else { + listDates.addAll(response['varList']); + } + Map page = response['page']; + totalPage = page['totalPage'] ?? 1; + }); + LoadingDialogHelper.hide(); + } catch (e) { + print('Error fetching data: $e'); + LoadingDialogHelper.show(); + } + } + + void _punishment(Map item) { + showPunishDialog( + context, + initial: { + 'ISPUNISH': '2', + 'RECTIFICATIONDEPT_NAME': item['UNITS_NAME'], + 'RECTIFICATIONOR_NAME': item['RECTIFICATIONOR_NAME'], + }, + onSubmit: (form) { + // 这里接收用户确认后的表单数据 + form['HIDDEN_ID'] = item['HIDDEN_ID']; + _keyprojectPunishAdd(form); + }, + ); + } + + Future _keyprojectPunishAdd(Map form) async { + try { + if (form['ISPUNISH'] == 1) { + final result = await ApiService.keyprojectpunishAdd(form); + if (result['result'] == 'success') { + await ApiService.keyprojectPunishEdit(form, '1'); + _getDataWithIndex(_selectedTab); + } + }else{ + await ApiService.keyprojectPunishEdit(form, '2'); + _getDataWithIndex(_selectedTab); + } + + } catch (e) { + ToastUtil.showNormal(context, '$e'); + } + } + + void _goToDetail(Map item, String tabCur) async { + item['TabCur'] = tabCur; + await pushPage(PunishmentManagerDetailPage(info: item), context); + _getDataWithIndex(_selectedTab); + } + + Widget _itemCell(Map item) { + return Padding( + padding: EdgeInsets.symmetric(horizontal: 12), + child: GestureDetector( + onTap: () { + _goToDetail(item, '2'); + }, + child: Container( + padding: EdgeInsets.only(top: 12, left: 12, right: 12), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(5), + color: Colors.white, + ), + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [Text('${item['HIDDENDESCR'] ?? ''}')], + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text("被处罚单位: ${item['UNITS_NAME'] ?? ''}"), + if (item['ISPUNISH'].toString() == '1' && + item['HANDLED'].toString() == '0') + Text("被处罚人: ${item['PERSON_NAME'] ?? ''}"), + ], + ), + if (item['ISPUNISH'].toString() == '1') + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [Text("处罚原因: ${item['REASON'] ?? ''}")], + ), + if (FormUtils.hasValue(item, 'ISPUNISH')) + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text("下发人: ${item['CREATOR_NAME'] ?? ''}"), + Text("是否处罚: ${item['ISPUNISH'] == '2' ? "否" : "是"}"), + ], + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "处罚处理状态: ${item['ISPUNISH'] == '2' + ? "不处罚" + : item['HANDLED'] == '1' + ? '已完成' + : item['ISPUNISH'] == "1" + ? "待反馈" + : "待处罚"}", + ), + // Text("整改时间: ${item['RECTIFICATIONTIME'] ?? ''}"), + ], + ), + + SizedBox(height: 5), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + SizedBox(), + Row( + children: [ + if (!FormUtils.hasValue(item, 'ISPUNISH')) + CustomButton( + onPressed: () { + _punishment(item); + }, + text: '处罚', + height: 30, + padding: EdgeInsets.symmetric( + vertical: 5, + horizontal: 15, + ), + textStyle: TextStyle( + fontSize: 13, + color: Colors.white, + ), + backgroundColor: Colors.red, + ), + CustomButton( + onPressed: () { + _goToDetail(item, '2'); + }, + text: '查看', + height: 30, + padding: EdgeInsets.symmetric( + vertical: 5, + horizontal: 15, + ), + textStyle: TextStyle(fontSize: 13, color: Colors.white), + backgroundColor: Colors.blue, + ), + ], + ), + ], + ), + SizedBox(height: 10), + const Divider(height: 1), + ], + ), + ), + ), + ); + } + + void _handleItemTap(Map item, int index) {} + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: MyAppbar(title: '处罚管理'), + body: SafeArea( + child: Column( + children: [ + Container( + color: Colors.white, + child: TabBar( + controller: _tabController, + labelStyle: TextStyle(fontSize: 16), + indicator: UnderlineTabIndicator( + borderSide: BorderSide(width: 3.0, color: Colors.blue), + insets: EdgeInsets.symmetric(horizontal: 100.0), + ), + labelColor: Colors.blue, + unselectedLabelColor: Colors.grey, + tabs: const [Tab(text: '待反馈处罚'), Tab(text: '已完成处罚')], + ), + ), + Expanded( + child: + listDates.isEmpty + ? NoDataWidget.show() + : ListView.separated( + padding: EdgeInsets.only(top: 15), + itemCount: listDates.length, + controller: _scrollController, + separatorBuilder: (_, __) => const SizedBox(), + itemBuilder: (context, index) { + final item = listDates[index]; + return GestureDetector( + onTap: () => _handleItemTap(item, index), + child: _itemCell(item), + ); + }, + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/pages/KeyProjects/SafeCheck/check_list_page.dart b/lib/pages/KeyProjects/SafeCheck/check_list_page.dart index ffa289f..cd1681b 100644 --- a/lib/pages/KeyProjects/SafeCheck/check_list_page.dart +++ b/lib/pages/KeyProjects/SafeCheck/check_list_page.dart @@ -204,7 +204,7 @@ class _CheckListPageState extends State { }, child: Text('发起', style: TextStyle(color: Colors.white, fontSize: 16),)) ],), - body: Column( + body: SafeArea(child: Column( children: [ // Filter bar Container( @@ -231,7 +231,7 @@ class _CheckListPageState extends State { // List Expanded(child: _buildListContent()), ], - ), + )), ); } } diff --git a/lib/pages/KeyProjects/SafeCheck/custom/MultiTextFieldWithTitle.dart b/lib/pages/KeyProjects/SafeCheck/custom/MultiTextFieldWithTitle.dart new file mode 100644 index 0000000..3ab0efb --- /dev/null +++ b/lib/pages/KeyProjects/SafeCheck/custom/MultiTextFieldWithTitle.dart @@ -0,0 +1,234 @@ +import 'package:flutter/material.dart'; +import 'package:qhd_prevention/customWidget/custom_alert_dialog.dart'; +import 'package:qhd_prevention/customWidget/custom_button.dart'; + +class MultiTextFieldWithTitle extends StatefulWidget { + final String label; + final List texts; + final bool isEditable; + final String hintText; + final double fontSize; + final bool isRequired; + final ValueChanged> onTextsChanged; + + const MultiTextFieldWithTitle({ + super.key, + required this.label, + required this.isEditable, + required this.hintText, + required this.onTextsChanged, + this.fontSize = 15, + this.texts = const [], + this.isRequired = true, + }); + + @override + State createState() => + _MultiTextFieldWithTitleState(); +} + +class _MultiTextFieldWithTitleState extends State { + final List _controllers = []; + final List _focusNodes = []; + + @override + void initState() { + super.initState(); + // 延迟初始化,避免构建过程中调用 setState + WidgetsBinding.instance.addPostFrameCallback((_) { + if (mounted) { + _addTextField(); + } + }); + } + + @override + void dispose() { + for (var controller in _controllers) { + controller.dispose(); + } + for (var node in _focusNodes) { + node.dispose(); + } + super.dispose(); + } + + void _addTextField() { + setState(() { + final newController = TextEditingController(); + final newFocusNode = FocusNode(); + + newController.addListener(() { + widget.onTextsChanged(_getAllTexts()); + }); + + _controllers.add(newController); + _focusNodes.add(newFocusNode); + widget.onTextsChanged(_getAllTexts()); + }); + } + + void _removeTextField(int index) async { + if (_controllers.length <= 1) return; + await showDialog( + context: context, + builder: + (_) => CustomAlertDialog( + title: '提示', + mode: DialogMode.text, + content: '确定删除检查情况吗?', + cancelText: '取消', + confirmText: '确定', + onConfirm: () { + setState(() { + _controllers[index].dispose(); + _focusNodes[index].dispose(); + + _controllers.removeAt(index); + _focusNodes.removeAt(index); + widget.onTextsChanged(_getAllTexts()); + }); + }, + ), + ); + } + + List _getAllTexts() { + return _controllers.map((c) => c.text).toList(); + } + + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 12), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // 标题行 + InkWell( + child: Row( + children: [ + Flexible( + fit: FlexFit.loose, + child: Row( + children: [ + if (widget.isRequired && widget.isEditable) + Text('* ', style: TextStyle(color: Colors.red)), + Flexible( + child: Text( + widget.label, + style: TextStyle( + fontSize: widget.fontSize, + fontWeight: FontWeight.bold, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + ), + const SizedBox(width: 8), + if (widget.isEditable) + CustomButton( + text: " 添加 ", + height: 30, + padding: const EdgeInsets.symmetric( + vertical: 2, + horizontal: 5, + ), + backgroundColor: Colors.blue, + onPressed: _addTextField, + ), + ], + ), + ), + const SizedBox(height: 8), + + // 输入框区域 - 高度自适应 + Column( + children: [ + // 可编辑状态 + if (widget.isEditable) + ..._controllers.asMap().entries.map((entry) { + final index = entry.key; + return _buildTextFieldWithDelete(index); + }).toList(), + + // 不可编辑状态 + if (!widget.isEditable) + ...widget.texts.map((c) { + return Padding( + padding: const EdgeInsets.only(bottom: 8.0), + child: Container( + padding: const EdgeInsets.all(12), + width: double.maxFinite, + decoration: BoxDecoration( + border: Border.all(color: Colors.grey), + borderRadius: BorderRadius.circular(4), + ), + child: Text( + c, + style: TextStyle( + fontSize: widget.fontSize, + color: Colors.grey[600], + ), + ), + ), + ); + }).toList(), + ], + ), + ], + ), + ); + } + + Widget _buildTextFieldWithDelete(int index) { + return Padding( + padding: const EdgeInsets.only(bottom: 8.0), + child: Stack( + children: [ + // 输入框 + Padding( + padding: EdgeInsets.symmetric(horizontal: 7, vertical: 7), + child: Container( + decoration: BoxDecoration( + border: BoxBorder.all(color: Colors.grey.shade300, width: 1), + borderRadius: BorderRadius.circular(4), + ), + padding: EdgeInsets.symmetric(vertical: 10), + child: TextField( + controller: _controllers[index], + decoration: InputDecoration(hintText: widget.hintText), + focusNode: _focusNodes[index], + keyboardType: TextInputType.multiline, + maxLines: 3, + minLines: 3, + style: TextStyle(fontSize: widget.fontSize), + ), + ), + ), + + // 删除按钮(叠加在左上角) + if (index > 0) + Positioned( + top: 0, + left: 0, + child: GestureDetector( + onTap: () => _removeTextField(index), + child: Container( + decoration: BoxDecoration( + color: Colors.red, + borderRadius: BorderRadius.circular(12), + ), + padding: const EdgeInsets.all(4), + child: const Icon(Icons.close, size: 10, color: Colors.white), + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/pages/KeyProjects/SafeCheck/custom/safeCheck_table.dart b/lib/pages/KeyProjects/SafeCheck/custom/safeCheck_table.dart new file mode 100644 index 0000000..55f1319 --- /dev/null +++ b/lib/pages/KeyProjects/SafeCheck/custom/safeCheck_table.dart @@ -0,0 +1,156 @@ +import 'dart:io'; +import 'package:flutter/material.dart'; + +Widget HiddenListTable({ + required List hiddenList, + required bool forbidEdit, + required String baseImgPath, + required String personSignImg, + required String personSignTime, + required void Function(Map item, int index) showHidden, + required void Function(Map item, int index) removeHidden, + required BuildContext context, +}) { + Widget _buildCell(String text, {bool isHeader = false, Alignment alignment = Alignment.center, double minWidth = 50}) { + return Container( + padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 10), + alignment: alignment, + child: ConstrainedBox( + constraints: BoxConstraints(minWidth: minWidth), + child: Text( + text, + style: TextStyle( + fontWeight: isHeader ? FontWeight.bold : FontWeight.normal, + fontSize: 14, + color: isHeader ? Colors.black87 : Colors.black54, + ), + ), + ), + ); + } + + // 仍保留 Table 的 header 用法(用于有数据时) + TableRow _buildHeader() { + return TableRow( + decoration: BoxDecoration(color: Colors.grey.shade200), + children: [ + _buildCell('序号', isHeader: true, alignment: Alignment.center), + _buildCell('隐患部位', isHeader: true), + _buildCell('隐患描述', isHeader: true), + _buildCell('操作', isHeader: true, alignment: Alignment.center), + ], + ); + } + + TableRow _buildRow(Map item, int index) { + final partName = (item['HIDDENPART_NAME'] ?? '').toString(); + final part = (item['HIDDENPART'] ?? '').toString(); + final descr = (item['HIDDENDESCR'] ?? '').toString(); + + return TableRow( + decoration: BoxDecoration( + border: Border( + bottom: BorderSide(color: Colors.grey.shade300, width: 0.5), + ), + ), + children: [ + _buildCell('${index + 1}', alignment: Alignment.center), + _buildCell(partName.isNotEmpty ? partName : part), + _buildCell(descr), + Padding( + padding: const EdgeInsets.symmetric(vertical: 6), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + IconButton( + icon: const Icon(Icons.info_outline, size: 22, color: Colors.blue), + onPressed: () => showHidden(item, index), + tooltip: '查看', + padding: EdgeInsets.zero, + constraints: const BoxConstraints(), + ), + if (forbidEdit) // 注意:原逻辑保留,如需反向请改为 if (!forbidEdit) + IconButton( + icon: const Icon(Icons.delete_outline, size: 22, color: Colors.red), + onPressed: () => removeHidden(item, index), + tooltip: '删除', + padding: EdgeInsets.zero, + constraints: const BoxConstraints(), + ), + ], + ), + ), + ], + ); + } + + // 当 hiddenList 为空时,单独渲染表头(使用 Row + Expanded 来模仿 Table 的列宽) + Widget _buildHeaderRowWidget() { + return Container( + decoration: BoxDecoration(color: Colors.grey.shade200), + child: Row( + children: [ + Expanded(flex: 2, child: _buildCell('序号', isHeader: true, alignment: Alignment.center)), + Expanded(flex: 3, child: _buildCell('隐患部位', isHeader: true)), + Expanded(flex: 3, child: _buildCell('隐患描述', isHeader: true)), + Expanded(flex: 3, child: _buildCell('操作', isHeader: true, alignment: Alignment.center)), + ], + ), + ); + } + + final tableWidth = MediaQuery.of(context).size.width - 50; + + // 如果没有数据,返回“表头 + 跨列居中显示暂无数据”的布局 + if (hiddenList.isEmpty) { + return SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: SizedBox( + width: tableWidth, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + _buildHeaderRowWidget(), + Container( + height: 56, + decoration: BoxDecoration( + border: Border( + bottom: BorderSide(color: Colors.grey.shade300, width: 0.5), + ), + ), + alignment: Alignment.center, + child: Text( + '暂无数据', + style: TextStyle(fontSize: 14, color: Colors.black54), + ), + ), + ], + ), + ), + ); + } + + // 否则,使用原来的 Table 渲染有数据的行 + return SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: SizedBox( + width: tableWidth, + child: Table( + defaultVerticalAlignment: TableCellVerticalAlignment.middle, + columnWidths: const { + 0: FlexColumnWidth(2), // 序号 + 1: FlexColumnWidth(3), // 隐患部位 + 2: FlexColumnWidth(3), // 隐患描述 + 3: FlexColumnWidth(3), // 操作按钮 + }, + border: TableBorder.symmetric( + inside: BorderSide(color: Colors.grey.shade300, width: 0.5), + ), + children: [ + _buildHeader(), + ...hiddenList.asMap().entries.map((e) => _buildRow(e.value, e.key)), + ], + ), + ), + ); +} diff --git a/lib/pages/KeyProjects/SafeCheck/custom/safe_drawer_page.dart b/lib/pages/KeyProjects/SafeCheck/custom/safe_drawer_page.dart new file mode 100644 index 0000000..b0b4f8a --- /dev/null +++ b/lib/pages/KeyProjects/SafeCheck/custom/safe_drawer_page.dart @@ -0,0 +1,703 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:geolocator/geolocator.dart'; +import 'package:intl/intl.dart'; +import 'package:qhd_prevention/customWidget/ItemWidgetFactory.dart'; +import 'package:qhd_prevention/customWidget/bottom_picker.dart'; +import 'package:qhd_prevention/customWidget/bottom_picker_two.dart'; +import 'package:qhd_prevention/customWidget/custom_button.dart'; +import 'package:qhd_prevention/customWidget/date_picker_dialog.dart'; +import 'package:qhd_prevention/customWidget/department_person_picker.dart'; +import 'package:qhd_prevention/customWidget/department_picker_hidden_type.dart'; +import 'package:qhd_prevention/customWidget/department_picker_two.dart'; +import 'package:qhd_prevention/customWidget/full_screen_video_page.dart'; +import 'package:qhd_prevention/customWidget/picker/CupertinoDatePicker.dart'; +import 'package:qhd_prevention/customWidget/single_image_viewer.dart'; +import 'package:qhd_prevention/customWidget/toast_util.dart'; +import 'package:qhd_prevention/customWidget/video_player_widget.dart'; +import 'package:qhd_prevention/pages/home/tap/item_list_widget.dart'; +import 'package:qhd_prevention/tools/tools.dart'; +import '../../../../customWidget/photo_picker_row.dart'; +import '../../../../http/ApiService.dart'; + +/// 添加 修改 查看 +enum SafeEditType { add, edit, see } + +class SafeDrawerPage extends StatefulWidget { + const SafeDrawerPage({ + super.key, + required this.editType, + this.initialHidden, + required this.toCheckUnitList, + }); + + final SafeEditType editType; + final List toCheckUnitList; + + /// 可选:传入已有的 hiddenForm(用于 edit / see) + final Map? initialHidden; + + @override + State createState() => _SafeDrawerPageState(); +} + +class _SafeDrawerPageState extends State { + // Controllers + final TextEditingController _descCtl = TextEditingController(); + final TextEditingController _partCtl = TextEditingController(); + final TextEditingController _rectifyCtl = TextEditingController(); + + bool _isEdit = false; + + // Data lists + List _hazardLevels = []; + List> _personCache = []; + + // Selected / transient state + Map hiddenForm = {}; + + bool _rectifyClickable = true; // 是否可切换整改方式 + + @override + void initState() { + super.initState(); + _isEdit = widget.editType != SafeEditType.see; + _initHiddenForm(); + _loadHazardLevels(); + } + + void _initHiddenForm() { + hiddenForm = { + 'ISRELEVANT': '2', + 'SOURCE': '5', + 'hiddenImgs': >[], + 'zgImgs': >[], + 'hiddenVideos': >[], + 'RECTIFICATIONTYPE': '2', + }; + + // 如果传入了初始数据(编辑/查看),覆盖默认 + if (widget.initialHidden != null) { + final Map m = Map.from( + widget.initialHidden!, + ); + // normalize lists to List> + m['hiddenImgs'] = + (m['hiddenImgs'] as List?) + ?.map( + (e) => + e is Map + ? Map.from(e) + : {'FILEPATH': e.toString()}, + ) + .toList() ?? + []; + m['zgImgs'] = + (m['zgImgs'] as List?) + ?.map( + (e) => + e is Map + ? Map.from(e) + : {'FILEPATH': e.toString()}, + ) + .toList() ?? + []; + m['hiddenVideos'] = + (m['hiddenVideos'] as List?) + ?.map( + (e) => + e is Map + ? Map.from(e) + : {'FILEPATH': e.toString()}, + ) + .toList() ?? + []; + + hiddenForm.addAll(m); + } + // init controllers and flags + _descCtl.text = hiddenForm['HIDDENDESCR'] ?? ''; + _partCtl.text = hiddenForm['HIDDENPART'] ?? ''; + _rectifyCtl.text = hiddenForm['RECTIFYDESCR'] ?? ''; + + // hidden level selected map is not stored directly; we can set HIDDENLEVEL_NAME/HIDDENLEVEL + } + + @override + void dispose() { + _descCtl.dispose(); + _partCtl.dispose(); + _rectifyCtl.dispose(); + super.dispose(); + } + + Future _loadHazardLevels() async { + try { + final res = await ApiService.getHazardLevel(); + if (res['result'] == 'success') { + setState(() => _hazardLevels = List.from(res['list'] ?? [])); + } + } catch (e) { + // ignore + } + } + + Widget _section(Widget child) => Container( + margin: const EdgeInsets.only(top: 10), + color: Colors.white, + padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 10), + child: child, + ); + + List _getSelectedImages() { + return (hiddenForm['hiddenImgs'] as List>) + .map((e) { + final p = '${e['FILEPATH']}'; + return p.contains('uploadFiles') ? '${ApiService.baseImgPath}$p' : p; + }) + .toList(); + } + + List _getSelectedVideos() { + return (hiddenForm['hiddenVideos'] as List>) + .map((e) => '${e['FILEPATH']}') + .toList(); + } + + @override + Widget build(BuildContext context) { + return SafeArea( + child: Container( + color: Colors.grey[100], + child: Column( + children: [ + // scroll content + Expanded( + child: SingleChildScrollView( + padding: const EdgeInsets.only(bottom: 20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + // photos + _section( + RepairedPhotoSection( + title: '隐患照片', + maxCount: 4, + mediaType: MediaType.image, + isShowAI: true, + isEdit: _isEdit, + isRequired: _isEdit, + initialMediaPaths: _getSelectedImages(), + onMediaTapped: (p) { + presentOpaque(SingleImageViewer(imageUrl: p), context); + }, + onChanged: + (files) => setState(() { + hiddenForm['hiddenImgs'] = + files + .map((f) => {'FILEPATH': f.path}) + .toList(); + }), + onAiIdentify: () { + final imgs = List>.from( + hiddenForm['hiddenImgs'] ?? [], + ); + if (imgs.isEmpty) + return ToastUtil.showNormal(context, '请先上传一张图片'); + if (imgs.length > 1) + return ToastUtil.showNormal( + context, + '识别暂时只能上传一张图片', + ); + final path = imgs.first['FILEPATH']?.toString() ?? ''; + if (path.isNotEmpty) _identifyImage(path); + }, + ), + ), + + // videos + _section( + RepairedPhotoSection( + title: '隐患视频', + maxCount: 1, + onMediaTapped: (p) { + showDialog( + context: context, + barrierColor: Colors.black54, + builder: (_) => VideoPlayerPopup(videoUrl:p), + ); + }, + isEdit: _isEdit, + mediaType: MediaType.video, + initialMediaPaths: _getSelectedVideos(), + onChanged: + (files) => setState(() { + hiddenForm['hiddenVideos'] = + files + .map((f) => {'FILEPATH': f.path}) + .toList(); + }), + onAiIdentify: () {}, + ), + ), + + // description + + ItemListWidget.itemContainer( + + ItemListWidget.multiLineTitleTextField( + + label: '隐患描述', + isEditable: _isEdit, + text: hiddenForm['HIDDENDESCR'] ?? '', + controller: _descCtl, + ),horizontal: 5), + + ItemListWidget.itemContainer( + + ItemListWidget.multiLineTitleTextField( + + label: '隐患部位', + isEditable: _isEdit, + text: hiddenForm['HIDDENPART'] ?? '', + controller: _partCtl, + ),horizontal: 5), + // part + + // hazard level + GestureDetector( + onTap: _isEdit ? _pickHazardLevel : null, + child: _section( + ListItemFactory.createRowSpaceBetweenItem( + leftText: '隐患级别', + rightText: + hiddenForm['HIDDENLEVEL_NAME']?.toString() ?? + '请选择', + isRight: _isEdit, + ), + ), + ), + + // hazard type + GestureDetector( + onTap: _isEdit ? _pickHazardType : null, + child: _section( + ListItemFactory.createRowSpaceBetweenItem( + leftText: '隐患类型', + rightText: + hiddenForm['HIDDENTYPE_NAME']?.toString() ?? + '请选择', + isRight: _isEdit, + ), + ), + ), + // AI identify / disposition header + _section( + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + ListItemFactory.headerTitle('隐患处置'), + Row( + children: [ + Icon( + Icons.check_circle, + size: 20, + color: Colors.blue, + ), + Text( + ' 限期整改', + style: TextStyle(color: Colors.black), + ), + ], + ), + ], + ), + ), + // when immediate rectify + if (hiddenForm['RECTIFICATIONTYPE'] == '1') ...[ + _section( + ListItemFactory.createBuildMultilineInput( + '整改描述', + '请对隐患进行整改描述(必填项)', + _rectifyCtl, + ), + ), + const SizedBox(height: 10), + _section( + RepairedPhotoSection( + title: '整改后图片', + maxCount: 4, + mediaType: MediaType.image, + onChanged: + (files) => setState(() { + hiddenForm['zgImgs'] = + files + .map((f) => {'FILEPATH': f.path}) + .toList(); + }), + onAiIdentify: () {}, + ), + ), + ] else ...[ + // limited rectify: department, person, date + GestureDetector( + onTap: _isEdit ? _pickDept : null, + child: _section( + ListItemFactory.createRowSpaceBetweenItem( + leftText: '整改部门', + rightText: + _isEdit + ? (hiddenForm['RECTIFICATIONDEPT_NAME'] + ?.toString() ?? + '请选择') + : hiddenForm['RECTIFICATIONDEPT_NAME'] + ?.toString() ?? + '', + isRight: _isEdit, + ), + ), + ), + const SizedBox(height: 10), + GestureDetector( + onTap: _isEdit ? _pickResponsible : null, + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 15), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(5), + ), + child: ListItemFactory.createRowSpaceBetweenItem( + leftText: '整改人', + rightText: + hiddenForm['RECTIFICATIONOR_NAME'] + ?.toString() ?? + '请选择', + isRight: _isEdit, + ), + ), + ), + GestureDetector( + onTap: _isEdit ? () => _pickDate(context) : null, + child: _section( + ListItemFactory.createRowSpaceBetweenItem( + leftText: '整改期限', + rightText: + hiddenForm['RECTIFICATIONDEADLINE'] + ?.toString() ?? + '请选择', + isRight: _isEdit, + ), + ), + ), + ], + + const SizedBox(height: 30), + + // buttons + Padding( + padding: const EdgeInsets.symmetric(horizontal: 15), + child: Row( + children: [ + Expanded( + child: CustomButton( + onPressed: cancelHidden, + text: '取消', + textStyle: TextStyle(color: Colors.black87), + backgroundColor: Colors.grey.shade300, + ), + ), + const SizedBox(width: 12), + if (_isEdit) + Expanded( + child: CustomButton( + onPressed: saveHidden, + text: '保存', + backgroundColor: Colors.blue, + ), + ), + ], + ), + ), + const SizedBox(height: 30), + ], + ), + ), + ), + ], + ), + ), + ); + } + + // ----------------- Helpers ----------------- + + void _rectifyImgsFromFiles(List files) => + hiddenForm['zgImgs'] = files.map((f) => {'FILEPATH': f.path}).toList(); + + Future _pickHazardLevel() async { + if (_hazardLevels.isEmpty) return ToastUtil.showNormal(context, '隐患级别数据为空'); + final choice = await BottomPickerTwo.show( + context, + items: _hazardLevels, + itemBuilder: (i) => Text(i['NAME'], textAlign: TextAlign.center), + initialIndex: 0, + ); + FocusHelper.clearFocus(context); + if (choice == null) return; + final found = _hazardLevels.firstWhere( + (e) => e['NAME'] == choice, + orElse: () => null, + ); + if (found != null) { + setState(() { + hiddenForm['HIDDENLEVEL'] = found['BIANMA'] ?? found['id'] ?? ''; + hiddenForm['HIDDENLEVEL_NAME'] = found['NAME'] ?? ''; + hiddenForm['HIDDENLEVEL_INDEX'] = found['id'] ?? ''; + _rectifyClickable = + (found['DICTIONARIES_ID']?.toString() ?? '') != + '5ff9daf78e9a4fb1b40d77980656799d'; + if (!_rectifyClickable) { + hiddenForm['RECTIFICATIONTYPE'] = '2'; + } + }); + } + } + + Future _pickHazardType() async { + showModalBottomSheet( + context: context, + isScrollControlled: true, + barrierColor: Colors.black54, + backgroundColor: Colors.transparent, + builder: + (_) => DepartmentPickerHiddenType( + onSelected: (result) { + try { + final Map m = Map.from(result); + final ids = List.from(m['id'] ?? []); + final names = List.from(m['name'] ?? []); + setState(() { + hiddenForm['HIDDENTYPE'] = ids.isNotEmpty ? ids.first : ''; + hiddenForm['HIDDENTYPE_NAME'] = + names.isNotEmpty ? names.last : ''; + hiddenForm['HIDDENTYPE2'] = ids.length > 1 ? ids[1] : ''; + hiddenForm['HIDDENTYPE2_NAME'] = + names.length > 1 ? names[1] : ''; + }); + } catch (_) {} + }, + ), + ); + } + + Future _pickDept() async { + final choice = await BottomPicker.show( + context, + items: + widget.toCheckUnitList + .map((val) => val['UNITS_NAME'] as String) + .toList(), + itemBuilder: (item) => Text(item, textAlign: TextAlign.center), + initialIndex: 0, + ); + if (choice != null) { + // 用户点击确定并选择了 choice + setState(() { + hiddenForm['RECTIFICATIONDEPT_NAME'] = choice; + Map target = widget.toCheckUnitList.firstWhere( + (item) => item['UNITS_NAME'] == choice, + orElse: () => {}, + ); + hiddenForm['RECTIFICATIONDEPT'] = target?['UNITS_ID'] ?? ''; + _getUnitPerson(); + + FocusHelper.clearFocus(context); + }); + } + } + + Future _getUnitPerson() async { + final res = await ApiService.getSafeCheckPersonList( + hiddenForm['RECTIFICATIONDEPT'], + '1', + ); + setState( + () => + _personCache = List>.from(res['varList'] ?? []), + ); + } + + Future _pickResponsible() async { + if (_personCache.isEmpty) return ToastUtil.showNormal(context, '请先选择部门'); + + final choice = await BottomPicker.show( + context, + items: _personCache.map((val) => val['NAME'] as String).toList(), + itemBuilder: (item) => Text(item, textAlign: TextAlign.center), + initialIndex: 0, + ); + if (choice != null) { + // 用户点击确定并选择了 choice + setState(() { + hiddenForm['RECTIFICATIONOR_NAME'] = choice; + Map? target = widget.toCheckUnitList.firstWhere( + (item) => item['RECTIFICATIONOR_NAME'] == choice, + orElse: () => {}, + ); + hiddenForm['RECTIFICATIONOR'] = target?['PERSONNELMANAGEMENT_ID']; + FocusHelper.clearFocus(context); + }); + } + } + + void _pickDate(BuildContext ctx) async { + DateTime? picked = await BottomDateTimePicker.showDate(context); + if (picked != null) { + setState(() { + final selectData = DateFormat('yyyy-MM-dd HH:mm').format(picked); + hiddenForm['RECTIFICATIONDEADLINE'] = selectData; + }); + FocusHelper.clearFocus(context); + } + } + + /// 保存隐患(这里只返回 hiddenForm 给上层调用者) + void saveHidden() { + // update hiddenForm from controllers before returning + hiddenForm['HIDDENDESCR'] = _descCtl.text.trim(); + hiddenForm['HIDDENPART'] = _partCtl.text.trim(); + hiddenForm['RECTIFYDESCR'] = _rectifyCtl.text.trim(); + // basic validation example + if ((hiddenForm['hiddenImgs'] as List).isEmpty) + return ToastUtil.showNormal(context, '请上传隐患图片'); + if ((hiddenForm['HIDDENDESCR'] ?? '').toString().trim().isEmpty) + return ToastUtil.showNormal(context, '请填写隐患描述'); + if ((hiddenForm['HIDDENPART'] ?? '').toString().trim().isEmpty) + return ToastUtil.showNormal(context, '请填写隐患部位'); + if ((hiddenForm['HIDDENLEVEL_NAME'] ?? '').toString().trim().isEmpty) + return ToastUtil.showNormal(context, '请选择隐患级别'); + if ((hiddenForm['HIDDENTYPE_NAME'] ?? '').toString().trim().isEmpty) + return ToastUtil.showNormal(context, '请选择隐患类型'); + if ((hiddenForm['RECTIFICATIONDEPT_NAME'] ?? '').toString().trim().isEmpty) + return ToastUtil.showNormal(context, '请选择整改部门'); + if ((hiddenForm['RECTIFICATIONOR_NAME'] ?? '').toString().trim().isEmpty) + return ToastUtil.showNormal(context, '请选择整改人'); + if ((hiddenForm['RECTIFICATIONDEADLINE'] ?? '').toString().trim().isEmpty) + return ToastUtil.showNormal(context, '请选择整改期限'); + + // 返回给调用方(抽屉关闭并返回数据) + Navigator.of(context).pop(hiddenForm); + } + + void cancelHidden() { + Navigator.of(context).pop(); + } + + // 图片/视频查看与删除(简单实现) + void viewImage(int index) { + final imgs = List>.from( + hiddenForm['hiddenImgs'] ?? [], + ); + if (index < 0 || index >= imgs.length) return; + final path = imgs[index]['FILEPATH']?.toString() ?? ''; + + presentOpaque( + SingleImageViewer(imageUrl: ApiService.baseImgPath + path), + context, + ); + } + + void delImage(int index) { + setState(() { + final imgs = List>.from( + hiddenForm['hiddenImgs'] ?? [], + ); + if (index >= 0 && index < imgs.length) { + imgs.removeAt(index); + hiddenForm['hiddenImgs'] = imgs; + } + }); + } + + void viewVideo(int index) { + final vids = List>.from( + hiddenForm['hiddenVideos'] ?? [], + ); + if (index < 0 || index >= vids.length) return; + final path = vids[index]['FILEPATH']?.toString() ?? ''; + ToastUtil.showNormal(context, '查看视频:$path'); + } + + void delVideo(int index) { + setState(() { + final vids = List>.from( + hiddenForm['hiddenVideos'] ?? [], + ); + if (index >= 0 && index < vids.length) { + vids.removeAt(index); + hiddenForm['hiddenVideos'] = vids; + } + }); + } + + Future _identifyImage(String path) async { + try { + LoadingDialogHelper.show(); + final raw = await ApiService.identifyImg(path); + if (raw['result'] == 'success') { + final aiList = raw['aiHiddens'] ?? []; + String desc = ''; + String rectify = ''; + for (final item in aiList) { + try { + final m = jsonDecode(item); + desc = + desc.isEmpty + ? (m['hiddenDescr'] ?? '') + : '$desc;${m['hiddenDescr'] ?? ''}'; + rectify = + rectify.isEmpty + ? (m['rectificationSuggestions'] ?? '') + : '$rectify;${m['rectificationSuggestions'] ?? ''}'; + } catch (_) {} + } + setState(() { + _descCtl.text = desc; + _rectifyCtl.text = rectify; + hiddenForm['HIDDENDESCR'] = desc; + hiddenForm['RECTIFYDESCR'] = rectify; + hiddenForm['RECTIFICATIONTYPE'] = '1'; + }); + } else { + ToastUtil.showNormal(context, '识别失败'); + } + } catch (e) { + ToastUtil.showNormal(context, '识别异常:$e'); + } finally { + LoadingDialogHelper.hide(); + } + } + + Future _uploadFile(String path, String type, String id) async { + try { + final r = await ApiService.addImgFiles(path, type, id); + return r['result'] == 'success' ? (r['imgPath'] ?? '') : ''; + } catch (_) { + return ''; + } + } + + Future _determinePosition() async { + if (!await Geolocator.isLocationServiceEnabled()) throw 'location disabled'; + var p = await Geolocator.checkPermission(); + if (p == LocationPermission.denied) + p = await Geolocator.requestPermission(); + if (p == LocationPermission.denied || p == LocationPermission.deniedForever) + throw 'permission denied'; + return await Geolocator.getCurrentPosition( + desiredAccuracy: LocationAccuracy.high, + ); + } +} diff --git a/lib/pages/KeyProjects/SafeCheck/safeCheck_detail.dart b/lib/pages/KeyProjects/SafeCheck/safeCheck_detail.dart index f069e7f..fd1fde0 100644 --- a/lib/pages/KeyProjects/SafeCheck/safeCheck_detail.dart +++ b/lib/pages/KeyProjects/SafeCheck/safeCheck_detail.dart @@ -1,12 +1,19 @@ import 'dart:convert'; import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; import 'package:qhd_prevention/customWidget/ItemWidgetFactory.dart'; import 'package:qhd_prevention/customWidget/bottom_picker.dart'; import 'package:qhd_prevention/customWidget/custom_alert_dialog.dart'; import 'package:qhd_prevention/customWidget/custom_button.dart'; +import 'package:qhd_prevention/customWidget/picker/CupertinoDatePicker.dart'; +import 'package:qhd_prevention/customWidget/single_image_viewer.dart'; import 'package:qhd_prevention/customWidget/toast_util.dart'; import 'package:qhd_prevention/http/ApiService.dart'; +import 'package:qhd_prevention/pages/KeyProjects/SafeCheck/custom/MultiTextFieldWithTitle.dart'; +import 'package:qhd_prevention/pages/KeyProjects/SafeCheck/custom/safeCheck_table.dart'; +import 'package:qhd_prevention/pages/KeyProjects/SafeCheck/custom/safe_drawer_page.dart'; +import 'package:qhd_prevention/pages/app/Danger_paicha/quick_report_page.dart'; import 'package:qhd_prevention/pages/home/tap/item_list_widget.dart'; import 'package:qhd_prevention/pages/my_appbar.dart'; import 'package:qhd_prevention/tools/tools.dart'; @@ -28,114 +35,222 @@ class SafecheckDetail extends StatefulWidget { } class _SafecheckDetailState extends State { - late Map info = {}; /// 被检查单位负责人 late List personList = []; + /// 检查类型 late List typeList = []; + /// 被检查单位 late List toCheckUnitList = []; bool? chooseTitleType = null; - final TextEditingController _unitController = TextEditingController(); - final TextEditingController _locationController = TextEditingController(); + // 存储多行输入的内容 + List multiTexts = []; - final TextEditingController _personController = TextEditingController(); + List delInspectors = []; + List delSituations = []; + List delHiddens = []; + List delHiddenFiles = []; + + // rules 格式: [{ 'name': 'INSPECTION_CATEGORY', 'message': '请填写检查题目' }, ...] + List> rules = [ + {'name': 'INSPECTION_CATEGORY', 'message': '请选择检查题目'}, + {'name': 'UNITS_ID', 'message': '请选择被检查单位'}, + {'name': 'PERSONNELMANAGEMENT_ID', 'message': '请选择被检查单位现场负责人'}, + {'name': 'INSPECTION_TYPE', 'message': '请选择检查类型不能为空'}, + {'name': 'INSPECTION_PLACE', 'message': '请输入检查场所'}, + {'name': 'INSPECTION_TIME_START', 'message': '请选择检查开始时间'}, + {'name': 'INSPECTION_TIME_END', 'message': '请选择作业结束时间'}, + {'name': 'INSPECTION_USERS', 'message': '请输入检查人员'}, + ]; + + Map form = { + 'INSPECTION_USERS': '', + 'KEYPROJECTCHECK_ID': '', // 检查ID + 'OUTSOURCED_ID': '', // 检查ID + 'INSPECTION_CATEGORY': '', // 检查标题 + 'INSPECTION_SOURCE': '5', // 检查来源(4-监管端 5-企业端) + 'INSPECTION_ORIGINATOR_ID': '', // 检查发起人 + 'UNITS_ID': '', // 被检查单位 + 'UNITS_NAME': '', + 'PERSONNELMANAGEMENT_ID': '', // 被检查单位现场负责人 + 'INSPECTED_SITEUSER_INDEX': '', + 'PERSON_NAME': '', + 'INSPECTED_EXPLAIN': '', // 申辩内容 + 'INSPECTED_SITEUSER_SIGN_IMG': '', // 被检查单位现场负责人签字 + 'INSPECTED_SITEUSER_SIGN_TIME': '', // 被检查单位现场负责人签字时间 + 'INSPECTION_TYPE': '', // 检查类型 + 'INSPECTION_TYPE_NAME': '', + 'INSPECTION_PLACE': '', // 检查场所 + 'INSPECTION_TIME_START': '', // 检查开始时间 + 'INSPECTION_TIME_END': '', // 检查结束时间 + 'INSPECTION_STATUS': '0', // 状态 + 'POSITIONDESC': '', // 隐患位置描述 + 'CREATTIME': '', + 'inspectorList': [ + { + 'INSPECTION_INSPECTOR_ID': '', //检查人员主键 + 'INSPECTION_DEPARTMENT_ID': '', //检查人员部门ID + 'INSPECTION_DEPARTMENT_NAME': '', + 'INSPECTION_USER_ID': '', //检查人员ID + 'INSPECTION_USER_INDEX': '', + 'INSPECTION_USER_NAME': '', + }, + ], + 'situationList': [ + {'INSPECTION_SITUATION_ID': '', 'SITUATION': ''}, + ], + 'hiddenList': [ + { + 'ISRELEVANT': '2', + 'HIDDEN_ID': '', // 隐患ID + 'HIDDENDESCR': '', // 隐患描述 + 'HIDDENPART': '', // 隐患部位 + 'HIDDENPART_NAME': '', + 'HIDDENLEVEL': '', // 隐患级别 + 'HIDDENLEVEL_NAME': '', + 'HIDDENTYPE': '', // 隐患类型1 + 'HIDDENTYPE_NAME': '', + 'HIDDENTYPE2': '', // 隐患类型2 + 'HIDDENTYPE2_NAME': '', + 'LONGITUDE': '', // 隐患位置经度 + 'LATITUDE': '', // 隐患位置纬度 + 'DISCOVERYTIME': '', // 隐患发现时间 + 'HIDDENFINDDEPT': '', // 隐患发现部门(隐患责任人部门) + 'HIDDENFINDDEPT_NAME': '', + 'CREATOR': '', // 发现人(隐患责任人) + 'CREATOR_INDEX': '', + 'CREATOR_NAME': '', + 'SOURCE': '5', // 隐患来源 + 'hiddenImgs': [], + 'zgImgs': [], + 'hiddenVideos': [], + 'RECTIFICATIONTYPE': '2', + 'RECTIFICATIONDEADLINE': '', + 'RECTIFYDESCR': '', + 'RECTIFICATIONDEPT_NAME': '', + 'RECTIFICATIONDEPT': '', + 'RECTIFICATIONOR_INDEX': '', + 'HIDDENLEVEL_INDEX': '', + 'RECTIFICATIONOR_NAME': '', + 'RECTIFICATIONOR': '', + 'punishForm': null, + }, + ], + 'INSPECTION_USER_SIGN_TIME': '', + 'INSPECTION_USER_OPINION': '', + }; @override void initState() { - // TODO: implement initState super.initState(); - _getData(); + form['OUTSOURCED_ID'] = widget.OUTSOURCED_ID; + form['KEYPROJECTCHECK_ID'] = widget.KEYPROJECTCHECK_ID; + form['hiddenList'] = []; + WidgetsBinding.instance.addPostFrameCallback((_) { + if (widget.KEYPROJECTCHECK_ID.isNotEmpty) { + _getData(); + } else { + // 只有在没有 KEYPROJECTCHECK_ID 时,才做本地的初始值设置 + setState(() { + form['APPLY_DEPARTMENT_ID'] = SessionService.instance.deptId ?? ''; + form['APPLY_DEPARTMENT_NAME'] = + SessionService.instance.loginUser?['DEPARTMENT_NAME'] ?? ''; + form['APPLY_USER_ID'] = SessionService.instance.loginUserId ?? ''; + form['APPLY_USER'] = SessionService.instance.username ?? ''; + }); + } + // 无论如何都去拉取下拉/基础数据 + _getAllRequest(); + }); } Future _getData() async { - LoadingDialogHelper.show(); - final result = await ApiService.addSafeCheckReciord(widget.OUTSOURCED_ID); - final personData = await ApiService.getSafeCheckPersonList(info['UNITS_ID']??'', '1'); - final typeListData = await ApiService.getSafeCheckTypeList(); - final toUnitListData = await ApiService.getSafeCheckToUnitList(widget.OUTSOURCED_ID); try { + final result = await ApiService.getSafeCheckGoEdit(widget.KEYPROJECTCHECK_ID); + // 在 await 之后检查 mounted,避免页面已经被 pop 导致 setState 报错 + if (!mounted) return; setState(() { - info = result['pd'] ?? {}; - personList = personData['varList']; - typeList = jsonDecode(typeListData['zTreeNodes']); - toCheckUnitList = toUnitListData['varList']; - if (!FormUtils.hasValue(info, 'UNITS_NAME') && toCheckUnitList.isNotEmpty) { - info['UNITS_NAME'] = toCheckUnitList.first['UNITS_NAME']; - } - if (!FormUtils.hasValue(info, 'UNITS_ID') && toCheckUnitList.isNotEmpty) { - info['UNITS_ID'] = toCheckUnitList.first['UNITS_ID']; - } - + form = result['pd'] ?? {}; + _syncMultiTextsFromForm(); }); - } catch (e) { - print('加载数据失败:$e'); - ToastUtil.showNormal(context, '加载数据失败:$e'); - } - - - LoadingDialogHelper.hide(); - } - - Future onUpdateState(String state) async { - // 提示文案 - String content = ''; - if (state == '1') { - content = '确定同意开工吗?'; - } else if (state == '2') { - content = '确定同意结束吗?'; - } else { - content = '确定操作吗?'; - } - final bool confirmed = - await showDialog( - context: context, - builder: - (ctx) => CustomAlertDialog( - title: '提示', - content: content, - onConfirm: () => Navigator.of(ctx).pop(false), - ), - ) ?? - false; - - if (!confirmed) return; - LoadingDialogHelper.show(); - try { - final response = await ApiService.sureKeyProjectState( - widget.OUTSOURCED_ID, - state, - ); - LoadingDialogHelper.hide(); - if (response['result'] == 'success') { - Navigator.of(context).pop(); - } else { - ToastUtil.showNormal(context, '请求失败'); + } catch (e, st) { + print('加载单条数据失败: $e\n$st'); + if (mounted) { + ToastUtil.showNormal(context, '加载数据失败:$e'); } - } catch (e) { - ToastUtil.showNormal(context, '请求异常:$e'); } } - Widget lineItem({ - required String label, - required bool isEditable, - String? text, - }) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - ItemListWidget.singleLineTitleText( - label: label, - isEditable: isEditable, - text: text ?? '', - ), - const Divider(height: 1, thickness: 1), - ], - ); + Future _getAllRequest() async { + try { + // 在网络请求前确保 widget 已经 build 完毕 + LoadingDialogHelper.show(); + + final result = await ApiService.addSafeCheckRecord(widget.OUTSOURCED_ID); + // 若页面已被销毁,就直接返回 + if (!mounted) { + LoadingDialogHelper.hide(); + return; + } + + setState(() { + Map pd = result['pd'] ?? {}; + form['UNITS_NAME'] = pd['UNITS_NAME'] ?? ''; + form['UNITS_ID'] = pd['UNITS_ID'] ?? ''; + form['PERSONNELMANAGEMENT_ID'] = pd['PERSONNELMANAGEMENT_ID'] ?? ''; + form['PERSON_NAME'] = pd['NAME'] ?? ''; + }); + + try { + final personData = await ApiService.getSafeCheckPersonList( + form['UNITS_ID'] ?? '', + '1', + ); + if (!mounted) return; + setState(() { + personList = personData['varList'] ?? []; + }); + } catch (e) { + print('加载 personList 失败: $e'); + } + + try { + final typeListData = await ApiService.getSafeCheckTypeList(); + if (!mounted) return; + setState(() { + typeList = jsonDecode(typeListData['zTreeNodes'] ?? '[]'); + }); + } catch (e) { + print('加载 typeList 失败: $e'); + } + + try { + final toUnitListData = await ApiService.getSafeCheckToUnitList(widget.OUTSOURCED_ID); + if (!mounted) return; + setState(() { + toCheckUnitList = toUnitListData['varList'] ?? []; + // 仅在没有选择时自动回填 + if (!FormUtils.hasValue(form, 'UNITS_NAME') && toCheckUnitList.isNotEmpty) { + form['UNITS_NAME'] = toCheckUnitList.first['UNITS_NAME']; + } + if (!FormUtils.hasValue(form, 'UNITS_ID') && toCheckUnitList.isNotEmpty) { + form['UNITS_ID'] = toCheckUnitList.first['UNITS_ID']; + } + }); + } catch (e) { + print('加载 toCheckUnitList 失败: $e'); + } + } catch (e, st) { + print('总的加载失败: $e\n$st'); + if (mounted) ToastUtil.showNormal(context, '加载数据失败:$e'); + } finally { + LoadingDialogHelper.hide(); + } } + Future _choosePerson() async { final choice = await BottomPicker.show( context, @@ -146,99 +261,669 @@ class _SafecheckDetailState extends State { if (choice != null) { // 用户点击确定并选择了 choice setState(() { - info['PERSON_NAME'] = choice; + form['PERSON_NAME'] = choice; + final data = FormUtils.findMapForKeyValue(personList, 'NAME', choice); + form['PERSONNELMANAGEMENT_ID'] = data['PERSONNELMANAGEMENT_ID']; + form['INSPECTED_SITEUSER_INDEX'] = personList.indexOf(data); + FocusHelper.clearFocus(context); }); } } + + Future _chooseType() async { + final choice = await BottomPicker.show( + context, + items: typeList.map((val) => val['name'] as String).toList(), + itemBuilder: (item) => Text(item, textAlign: TextAlign.center), + initialIndex: 0, + ); + if (choice != null) { + // 用户点击确定并选择了 choice + setState(() { + form['INSPECTION_TYPE_NAME'] = choice; + final data = FormUtils.findMapForKeyValue(typeList, 'name', choice); + form['INSPECTION_TYPE'] = data['id']; + + FocusHelper.clearFocus(context); + }); + } + } + + Future _openDrawer(Map hiddenForm, int index) async { + try { + final result = await openCustomDrawer( + context, + SafeDrawerPage( + initialHidden: hiddenForm, + editType: + widget.isEdit + ? (index < 0 ? SafeEditType.add : SafeEditType.edit) + : SafeEditType.see, + toCheckUnitList: toCheckUnitList, + ), + ); + + if (result != null && mounted) { + setState(() { + if (index < 0) { + // 新增 + form['hiddenList'].add(result); + } else { + // 修改 + form['hiddenList'][index] = result; + } + }); + } + } catch (e) { + print("打开抽屉失败: $e"); + ToastUtil.showNormal(context, "打开抽屉失败"); + } + } + + Future openCustomDrawer(BuildContext context, Widget child) { + return Navigator.of(context).push( + PageRouteBuilder( + opaque: false, + barrierDismissible: true, + barrierColor: Colors.black54, + pageBuilder: (_, __, ___) { + return Align( + alignment: Alignment.centerRight, + child: FractionallySizedBox( + widthFactor: 4 / 5, + child: Material(color: Colors.white, child: child), + ), + ); + }, + transitionsBuilder: (_, anim, __, child) { + return SlideTransition( + position: Tween( + begin: const Offset(1, 0), + end: Offset.zero, + ).animate(CurvedAnimation(parent: anim, curve: Curves.easeOut)), + child: child, + ); + }, + ), + ); + } + + /// 将 form['situationList'](若存在)转换为 List + List _situationListToStrings() { + final List cur = List.from(form['situationList'] ?? []); + if (cur.isEmpty) return ['']; // 保持至少一行,和 uni-app 行为一致 + return cur.map((e) { + if (e is Map && e['SITUATION'] != null) return e['SITUATION'].toString(); + return ''; + }).toList(); + } + + /// 将 List 转换成 uni-app 风格的 situationList(保留已存在的 INSPECTION_SITUATION_ID) + List> _stringsToSituationList(List texts) { + final List existing = List.from( + form['situationList'] ?? [], + ); + final List> out = []; + + for (int i = 0; i < texts.length; i++) { + final s = texts[i] ?? ''; + if (i < existing.length && existing[i] is Map) { + // 保留已有项的其他字段(如 INSPECTION_SITUATION_ID),只覆盖 SITUATION + final Map copy = Map.from( + existing[i], + ); + copy['SITUATION'] = s; + // 若没有 INSPECTION_SITUATION_ID 字段,确保它存在(保持原 uni-app 结构) + copy['INSPECTION_SITUATION_ID'] = copy['INSPECTION_SITUATION_ID'] ?? ''; + out.add(copy); + } else { + // 新增项 + out.add({'INSPECTION_SITUATION_ID': '', 'SITUATION': s}); + } + } + + // 如果用户删除了行,要把超出的 existing id 记录到 delSituations(可选) + // 下面为示例:把被删除的旧 ID 收集到 delSituations,便于编辑时提交删除列表 + if (existing.isNotEmpty && existing.length > texts.length) { + for (int j = texts.length; j < existing.length; j++) { + final ex = existing[j]; + if (ex is Map) { + final id = (ex['INSPECTION_SITUATION_ID'] ?? '').toString(); + if (id.isNotEmpty) { + delSituations.add(id); + } + } + } + } + + return out; + } + + /// 在加载数据之后调用:把已存在的 form['situationList'] 同步到 multiTexts(用于 MultiTextFieldWithTitle) + void _syncMultiTextsFromForm() { + final List arr = _situationListToStrings(); + setState(() { + multiTexts = arr; + }); + } + + // ------------ 提交入口 ------------ + Future _submit() async { + if (!widget.isEdit) { + Navigator.of(context).pop(); + + return; + } + bool required = true; + // 基于 rules 验证 + for (final r in rules) { + final name = r['name'] ?? ''; + final message = r['message'] ?? '请完善表单'; + final v = form[name]; + if (v == null || v.toString().isEmpty || v.toString() == '请选择') { + LoadingDialogHelper.hide(); + ToastUtil.showNormal(context, message); + required = false; + break; + } + } + if (!required) return; + + // situationList 每项 SITUATION 非空 + final situations = (form['situationList'] as List?) ?? []; + for (var i = 0; i < situations.length; i++) { + final s = Map.from(situations[i]); + if ((s['SITUATION'] ?? '').toString().trim().isEmpty) { + LoadingDialogHelper.hide(); + ToastUtil.showNormal(context, '请填写第${i + 1}项检查情况'); + return; + } + } + + // 检查 inspectorList 中是否有重复 INSPECTION_USER_ID + final List> inspectors = form['inspectorList'] ?? []; + final seenIds = {}; + for (final it in inspectors) { + final id = (it as Map)['INSPECTION_USER_ID']?.toString() ?? ''; + if (id.isNotEmpty) { + if (seenIds.contains(id)) { + LoadingDialogHelper.hide(); + ToastUtil.showNormal(context, '检查人重复!请检查数据'); + return; + } + seenIds.add(id); + } + } + + //根据 hiddenList 构建需要上传的文件数组 + final origHiddenList = (form['hiddenList'] as List?) ?? []; + final List>> hiddenFilesPerHidden = []; + + for (var i = 0; i < origHiddenList.length; i++) { + final hidden = Map.from(origHiddenList[i] as Map); + final List> fileList = []; + + // hiddenImgs (多张) + final hiddenImgs = (hidden['hiddenImgs'] as List?) ?? []; + for (var j = 0; j < hiddenImgs.length; j++) { + final img = hiddenImgs[j]; + // 如果是字符串路径 + if (img is String) { + fileList.add({'type': 3, 'FILEPATH': img}); + } else if (img is Map) { + final hasId = (img['IMGFILES_ID'] ?? '').toString().isNotEmpty; + if (!hasId) { + fileList.add({ + 'type': 3, + 'FILEPATH': img['FILEPATH'] ?? img['path'] ?? '', + }); + } + } + } + + // zgImgs (整改图) + final zgImgs = (hidden['zgImgs'] as List?) ?? []; + for (var j = 0; j < zgImgs.length; j++) { + final img = zgImgs[j]; + if (img is String) { + fileList.add({'type': 4, 'FILEPATH': img}); + } else if (img is Map) { + final hasId = (img['IMGFILES_ID'] ?? '').toString().isNotEmpty; + if (!hasId) { + fileList.add({ + 'type': 4, + 'FILEPATH': img['FILEPATH'] ?? img['path'] ?? '', + }); + } + } + } + + // hiddenVideos (只取第一个) + final hiddenVideos = (hidden['hiddenVideos'] as List?) ?? []; + if (hiddenVideos.isNotEmpty) { + final v = hiddenVideos[0]; + if (v is String) { + fileList.add({'type': 102, 'FILEPATH': v}); + } else if (v is Map) { + final hasId = (v['IMGFILES_ID'] ?? '').toString().isNotEmpty; + if (!hasId) { + fileList.add({ + 'type': 102, + 'FILEPATH': v['FILEPATH'] ?? v['path'] ?? '', + }); + } + } + } + + hiddenFilesPerHidden.add(fileList); + } + + // 确保当前登录用户在 inspectorList 中(依据 SessionService) + final loginUser = SessionService.instance.loginUser ?? {}; + final loginUserId = SessionService.instance.loginUserId ?? ''; + final idx = inspectors.indexWhere((item) { + final m = Map.from(item as Map); + return (m['INSPECTION_USER_ID'] ?? '') == + (loginUser['USER_ID'] ?? loginUserId); + }); + if (idx < 0) { + inspectors.add({ + 'INSPECTION_INSPECTOR_ID': '', + 'INSPECTION_DEPARTMENT_ID': loginUser['DEPARTMENT_ID'] ?? '', + 'INSPECTION_DEPARTMENT_NAME': loginUser['DEPARTMENT_NAME'] ?? '', + 'INSPECTION_USER_ID': loginUser['USER_ID'] ?? loginUserId, + 'INSPECTION_USER_INDEX': '', + 'INSPECTION_USER_NAME': loginUser['NAME'] ?? '', + }); + } + + // 准备 form 字段(JSON 字符串等) + form['INSPECTORJSON'] = jsonEncode(inspectors); + form['SITUATIONJSON'] = jsonEncode(situations); + form['HIDDENJSON'] = jsonEncode(origHiddenList); + form['delInspectors'] = delInspectors.join(','); + form['delSituations'] = delSituations.join(','); + form['delHiddens'] = delHiddens.join(','); + form['delHiddenFiles'] = delHiddenFiles.join(','); + form['CREATOR'] = loginUser['USER_ID'] ?? loginUserId; + form['CORPINFO_ID'] = SessionService.instance.corpinfoId ?? ''; + form['ACTION_USER'] = loginUser['NAME'] ?? ''; + LoadingDialogHelper.show(); // 显示 loading + + // 提交主表 + try { + final requestData = { + 'CORPINFO_ID': form['CORPINFO_ID'], + ...form, + }; + final res = await ApiService.safeKeyprojectCheckSubmit(requestData); + // 如果你的 ApiService 返回结构不同,请按实际调整判断 + if (res != null && res['result'] == 'success') { + final pd = res['pd'] ?? {}; + final List returnedHiddenList = pd['hiddenList'] ?? []; + + // 如果没有附件需要上传,直接完成 + final hasFiles = hiddenFilesPerHidden.any((lst) => lst.isNotEmpty); + if (!hasFiles) { + LoadingDialogHelper.hide(); + ToastUtil.showNormal(context, '提交成功'); + Navigator.of(context).pop(); + return; + } + + // 若每个 hidden 有 punishForm,需要先提交罚单 + for (var i = 0; i < returnedHiddenList.length; i++) { + if (i < (form['hiddenList'] as List).length) { + final hidden = Map.from( + (form['hiddenList'] as List)[i], + ); + final punishForm = hidden['punishForm']; + if (punishForm != null) { + final hid = (returnedHiddenList[i]['HIDDEN_ID'] ?? '').toString(); + punishForm['HIDDEN_ID'] = hid; + // await 调用罚单提交(在下面实现) + await fnSubmit(Map.from(punishForm)); + } + } + } + + // 上传所有附件(按隐患对应的 hiddenId) + // 把返回的 hiddenList 转为 Map 列表,确保索引一致 + final returnedHiddenMapList = + returnedHiddenList + .map((e) => Map.from(e)) + .toList(); + await uploadHiddenFiles(hiddenFilesPerHidden, returnedHiddenMapList); + + LoadingDialogHelper.hide(); + ToastUtil.showNormal(context, '提交成功'); + Navigator.of(context).pop(); + } else { + LoadingDialogHelper.hide(); + final msg = + res != null + ? (res['msg'] ?? res['msaesge'] ?? res['message'] ?? '提交失败') + : '提交失败'; + ToastUtil.showNormal(context, msg); + } + } catch (e) { + LoadingDialogHelper.hide(); + ToastUtil.showNormal(context, '提交异常:$e'); + } + } + + // ========== 上传附件的方法 ========== + Future uploadHiddenFiles( + List>> hiddenFilesPerHidden, + List> returnedHiddenList, + ) async { + for (var i = 0; i < hiddenFilesPerHidden.length; i++) { + final filesForHidden = hiddenFilesPerHidden[i]; + if (filesForHidden.isEmpty) continue; + final hiddenId = + i < returnedHiddenList.length + ? (returnedHiddenList[i]['HIDDEN_ID']?.toString() ?? '') + : ''; + if (hiddenId.isEmpty) continue; + + for (final f in filesForHidden) { + final filePath = f['FILEPATH']?.toString() ?? ''; + final type = f['type']?.toString() ?? ''; + if (filePath.isEmpty) continue; + try { + await ApiService.addImgFiles(filePath, type, hiddenId); + } catch (e) { + // 你可以记录失败项或重试,这里先忽略单文件错误 + print('上传文件失败: $e (path=$filePath)'); + } + } + } + } + + // ========== 罚单提交方法(对应原 fnSubmit) ========== + Future fnSubmit(Map? ordForm) async { + if (ordForm == null) return false; + + final Map punishRules = { + 'REASON': '请填写处罚原因', + 'AMOUT': '请填写处罚金额', + 'DATE': '请选择下发处罚时间', + }; + // 校验 + for (final entry in punishRules.entries) { + final key = entry.key; + final msg = entry.value; + final val = ordForm[key]; + if (val == null || val.toString().trim().isEmpty) { + ToastUtil.showNormal(context, msg); + return false; + } + } + + final requestData = Map.from(ordForm); + requestData['CORPINFO_ID'] = SessionService.instance.corpinfoId ?? ''; + requestData['CREATOR'] = SessionService.instance.loginUserId ?? ''; + requestData['OPERATOR'] = SessionService.instance.loginUserId ?? ''; + + try { + LoadingDialogHelper.show(); + final res = await ApiService.safeCheckPunishSubmit(requestData); + LoadingDialogHelper.hide(); + if (FormUtils.hasValue(res, 'result') && res['result'] == 'success') { + return true; + } else { + final msg = + res != null + ? (res['msg'] ?? res['msaesge'] ?? res['message'] ?? '提交失败') + : '提交失败'; + ToastUtil.showNormal(context, msg); + return false; + } + } catch (e) { + LoadingDialogHelper.hide(); + ToastUtil.showNormal(context, '罚单提交异常:$e'); + return false; + } + } + @override Widget build(BuildContext context) { return Scaffold( - appBar: MyAppbar(title: "安全检查发起"), + appBar: MyAppbar(title: "安全检查发起", actions: []), + body: SafeArea( child: Padding( padding: EdgeInsets.symmetric(horizontal: 12, vertical: 12), - child: Column( + child: form.isNotEmpty ? ListView( children: [ - ItemListWidget.itemContainer( - horizontal: 0, - Column( - children: [ - ListItemFactory.createYesNoSection( - title: '检查题目:', - groupValue: chooseTitleType, - yesLabel: '安全', - noLabel: '综合', - horizontalPadding: 5, - verticalPadding: 0, - onChanged: (val) { - setState(() { - chooseTitleType = val; - }); - }, - isEdit: true, - text: info['INSPECTION_CATEGORY'] ?? '', - ), - const Divider(), - ItemListWidget.singleLineTitleText( - label: '被检查单位:', - isEditable: false, - text: info['UNITS_NAME'], - controller: _unitController, - ), - const Divider(), - - ItemListWidget.selectableLineTitleTextRightButton( - label: '被检查单位现场负责人:', - isEditable: widget.isEdit, - onTap: () { - _choosePerson(); - }, - text: info['PERSON_NAME'] ?? '', - ), - const Divider(), - - ItemListWidget.singleLineTitleText( - label: '检查场所:', - isEditable: widget.isEdit, - text: info['INSPECTION_PLACE'], - hintText: '请输入检查场所', - controller: _locationController, - ), - const Divider(), - - ItemListWidget.selectableLineTitleTextRightButton( - label: '检查类型:', - isEditable: widget.isEdit, - text: info['INSPECTION_TYPE_NAME'] ?? '', - ), - const Divider(), - - ItemListWidget.selectableLineTitleTextRightButton( - label: '检查开始时间:', - isEditable: widget.isEdit, - text: info['INSPECTION_TIME_START'] ?? '', - ), - ], - ), - ), - SizedBox(height: 20), - Row( - mainAxisAlignment: MainAxisAlignment.center, + Column( children: [ - SizedBox( - width: 200, - child: CustomButton( - text: '提交', - textStyle: TextStyle(color: Colors.white, fontSize: 17), - backgroundColor: Colors.blue, - onPressed: () => Navigator.pop(context), + ItemListWidget.itemContainer( + horizontal: 0, + Column( + children: [ + ListItemFactory.createYesNoSection( + title: '检查题目:', + groupValue: chooseTitleType, + yesLabel: '安全', + noLabel: '综合', + isEdit: widget.isEdit, + isRequired: true, + horizontalPadding: 5, + verticalPadding: 0, + text: form['INSPECTION_CATEGORY'] ?? '', + onChanged: (val) { + setState(() { + chooseTitleType = val; + form['INSPECTION_CATEGORY'] = + val == true ? '安全' : '综合'; + }); + }, + ), + const Divider(), + ItemListWidget.singleLineTitleText( + label: '被检查单位:', + isEditable: false, + text: form['UNITS_NAME'], + onChanged: (val) { + setState(() { + form['UNITS_NAME'] = val; + }); + }, + ), + const Divider(), + + ItemListWidget.selectableLineTitleTextRightButton( + label: '被检查单位现场负责人:', + isEditable: widget.isEdit, + onTap: () { + _choosePerson(); + }, + text: form['PERSON_NAME'] ?? '', + ), + const Divider(), + + ItemListWidget.singleLineTitleText( + label: '检查场所:', + isEditable: widget.isEdit, + text: form['INSPECTION_PLACE'], + hintText: '请输入检查场所', + onChanged: (val) { + setState(() { + form['INSPECTION_PLACE'] = val; + }); + }, + ), + const Divider(), + + ItemListWidget.selectableLineTitleTextRightButton( + label: '检查类型:', + onTap: () { + _chooseType(); + }, + isEditable: widget.isEdit, + text: form['INSPECTION_TYPE_NAME'] ?? '', + ), + const Divider(), + + ItemListWidget.selectableLineTitleTextRightButton( + label: '检查开始时间:', + isEditable: widget.isEdit, + text: form['INSPECTION_TIME_START'] ?? '', + onTap: () async { + DateTime? picked = + await BottomDateTimePicker.showDate(context); + if (picked != null) { + setState(() { + form['INSPECTION_TIME_START'] = DateFormat( + 'yyyy-MM-dd HH:mm', + ).format(picked); + }); + FocusHelper.clearFocus(context); + } + }, + ), + const Divider(), + + ItemListWidget.selectableLineTitleTextRightButton( + label: '检查结束时间:', + isEditable: widget.isEdit, + text: form['INSPECTION_TIME_END'] ?? '', + onTap: () async { + DateTime? picked = + await BottomDateTimePicker.showDate(context); + if (picked != null) { + setState(() { + form['INSPECTION_TIME_END'] = DateFormat( + 'yyyy-MM-dd HH:mm', + ).format(picked); + }); + FocusHelper.clearFocus(context); + } + }, + ), + + const Divider(), + MultiTextFieldWithTitle( + label: "检查情况", + // 更合适的标题 + isEditable: widget.isEdit, + // 使用父组件的编辑状态 + hintText: "请输入检查情况...", + texts: multiTexts, + onTextsChanged: (texts) { + setState(() { + multiTexts = texts; // 保存到状态变量 + form['situationList'] = _stringsToSituationList( + texts, + ); + }); + }, + ), + + const Divider(), + ItemListWidget.multiLineTitleTextField( + label: '检查人员', + text: form['INSPECTION_USERS'] ?? '', + isEditable: widget.isEdit, + onChanged: (val) { + setState(() { + form['INSPECTION_USERS'] = val; + }); + }, + ), + const Divider(), + ItemListWidget.itemContainer( + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + ListItemFactory.headerTitle('发现问题'), + if (widget.isEdit) + CustomButton( + text: " 添加 ", + height: 30, + padding: const EdgeInsets.symmetric( + vertical: 2, + horizontal: 5, + ), + backgroundColor: Colors.blue, + onPressed: () { + _openDrawer(form, -1); // 添加括号和 await + FocusHelper.clearFocus(context); + }, + ), + ], + ), + ), + HiddenListTable( + hiddenList: form['hiddenList'] ?? [], + forbidEdit: widget.isEdit, + baseImgPath: ApiService.baseImgPath, + personSignImg: form['PERSON_SIGN_IMG'] ?? '', + personSignTime: form['PERSON_SIGN_TIME'] ?? '', + showHidden: (item, idx) { + _openDrawer(item, idx); + }, + removeHidden: (item, idx) { + /* 删除逻辑 */ + }, + context: context, + ), + + if (!widget.isEdit) + Column(children: [ + const Divider(), + ItemListWidget.twoRowTitleAndImages( + title: '签字', + onTapCallBack: (p) { + presentOpaque(SingleImageViewer(imageUrl: p), context); + }, + imageUrls: [ + '${form['PERSON_SIGN_IMG'] ?? ''}', + ], + ), + const Divider(), + ItemListWidget.singleLineTitleText(label: '签字时间', isEditable: false, text: form['PERSON_SIGN_TIME'] ?? '') + ],) + ], ), ), + SizedBox(height: 20), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + if (widget.isEdit) + SizedBox( + width: 150, + child: CustomButton( + text: '返回', + textStyle: TextStyle( + color: Colors.white, + fontSize: 17, + ), + backgroundColor: Colors.black38, + onPressed: () => Navigator.pop(context), + ), + ), + SizedBox( + width: 150, + child: CustomButton( + text: widget.isEdit ? '提交' : '返回', + textStyle: TextStyle( + color: Colors.white, + fontSize: 17, + ), + backgroundColor: Colors.blue, + onPressed: _submit, + ), + ), + ], + ), ], ), ], - ), + ): SizedBox(), ), ), ); diff --git a/lib/pages/KeyProjects/SafeCheck/safeCheck_list_page.dart b/lib/pages/KeyProjects/SafeCheck/safeCheck_list_page.dart index bfcdd40..a9d201d 100644 --- a/lib/pages/KeyProjects/SafeCheck/safeCheck_list_page.dart +++ b/lib/pages/KeyProjects/SafeCheck/safeCheck_list_page.dart @@ -222,7 +222,7 @@ class _SafecheckListPageState extends State { key: _scaffoldKey, appBar: MyAppbar(title: widget.flow), - body: Column( + body: SafeArea(child: Column( children: [ // Filter bar Container( @@ -249,7 +249,7 @@ class _SafecheckListPageState extends State { // List Expanded(child: _buildListContent()), ], - ), + )), ); } } diff --git a/lib/pages/KeyProjects/keyProjects_tab_list.dart b/lib/pages/KeyProjects/keyProjects_tab_list.dart index 6035d52..bf6eca4 100644 --- a/lib/pages/KeyProjects/keyProjects_tab_list.dart +++ b/lib/pages/KeyProjects/keyProjects_tab_list.dart @@ -1,7 +1,9 @@ import 'package:flutter/material.dart'; import 'package:qhd_prevention/customWidget/ItemWidgetFactory.dart'; import 'package:qhd_prevention/http/ApiService.dart'; +import 'package:qhd_prevention/pages/KeyProjects/Danger/danger_list_page.dart'; import 'package:qhd_prevention/pages/KeyProjects/KeyProject/keyProject_list_page.dart'; +import 'package:qhd_prevention/pages/KeyProjects/Punishment/punishment_list_page.dart'; import 'package:qhd_prevention/pages/KeyProjects/SafeCheck/safeCheck_list_page.dart'; import 'package:qhd_prevention/pages/home/tap/tabList/work_tab_icon_grid.dart'; import 'package:qhd_prevention/pages/my_appbar.dart'; @@ -89,8 +91,17 @@ class _KeyprojectsTabListState extends State { await pushPage(SafecheckListPage(flow: title), context); } break; - case 2: title = '隐患管理'; break; - case 3: title = '处罚管理'; break; + case 2: { + title = '隐患管理'; + await pushPage(DangerListPage(flow: title), context); + + } break; + case 3: { + title = '处罚管理'; + await pushPage(PunishmentListPage(flow: title), context); + + } break; + default: print("按钮 $index 被点击"); } diff --git a/lib/pages/home/SafeCheck/safeCheck_tab_list.dart b/lib/pages/home/SafeCheck/safeCheck_tab_list.dart new file mode 100644 index 0000000..8927e2b --- /dev/null +++ b/lib/pages/home/SafeCheck/safeCheck_tab_list.dart @@ -0,0 +1,130 @@ +import 'package:flutter/material.dart'; +import 'package:qhd_prevention/customWidget/ItemWidgetFactory.dart'; +import 'package:qhd_prevention/http/ApiService.dart'; +import 'package:qhd_prevention/pages/KeyProjects/Danger/danger_list_page.dart'; +import 'package:qhd_prevention/pages/KeyProjects/KeyProject/keyProject_list_page.dart'; +import 'package:qhd_prevention/pages/KeyProjects/SafeCheck/safeCheck_list_page.dart'; +import 'package:qhd_prevention/pages/home/tap/tabList/work_tab_icon_grid.dart'; +import 'package:qhd_prevention/pages/my_appbar.dart'; +import 'package:qhd_prevention/tools/tools.dart'; + + +class SafecheckTabList extends StatefulWidget { + const SafecheckTabList({super.key}); + + @override + State createState() => _SafecheckTabListState(); +} + +class _SafecheckTabListState extends State { + late List> buttonInfos = [ + { + "icon": "assets/icon-apps/icon-yxkj-1.png", + "title": "安全检查\n发起", + "unreadCount": '0', + }, + { + "icon": "assets/icon-apps/icon-yxkj-4.png", + "title": "检查人\n确认", + "unreadCount": '0', + }, + { + "icon": "assets/icon-apps/icon-yxkj-2.png", + "title": "被检查人\n签字/申辩", + "unreadCount":'0', + }, + { + "icon": "assets/icon-apps/icon-yxkj-2.png", + "title": "隐患指派\n及验收", + "unreadCount":'0', + }, + { + "icon": "assets/icon-apps/icon-yxkj-2.png", + "title": "申辩记录", + "unreadCount":'0', + }, + + ]; + + @override + void initState() { + super.initState(); + _getData(); + } + Future _getData() async { + LoadingDialogHelper.show(); + final data = await ApiService.getKeyProjectCount(); + LoadingDialogHelper.hide(); + setState(() { + final eight_work_count = data['pd'] ?? {}; + buttonInfos = [ + { + "icon": "assets/icon-apps/icon-yxkj-1.png", + "title": "重点工程管理", + "unreadCount": eight_work_count['GC_COUNT'] ?? '0', + }, + { + "icon": "assets/icon-apps/icon-yxkj-1.png", + "title": "安全检查管理", + "unreadCount": '0', + }, + { + "icon": "assets/icon-apps/icon-yxkj-1.png", + "title": "隐患管理", + "unreadCount": eight_work_count['CF_COUNT'] ?? '0', + }, + { + "icon": "assets/icon-apps/icon-yxkj-1.png", + "title": "处罚管理", + "unreadCount": eight_work_count['HIDDEN_COUNT'] ?? '0', + }, + + ]; + }); + } + void _handleItemPressed(int index) async { + // 根据索引执行不同的导航逻辑 + String title = ''; + switch (index) { + case 0: { + title = '安全检查发起'; + // await pushPage(KeyprojectListPage(flow: title), context); + } break; + case 1: { + title = '安全检查核实'; + // await pushPage(SafecheckListPage(flow: title), context); + + } break; + case 2: { + title = '安全检查确认'; + // await pushPage(DangerListPage(flow: title), context); + + } break; + case 3: title = '隐患指派及验收'; break; + case 4: title = '申辩记录'; break; + + default: + print("按钮 $index 被点击"); + } + + _getData(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: MyAppbar(title: '安全检查管理'), + body: SafeArea( + child: Column( + children: [ + ListItemFactory.createBuildSimpleSection('安全检查管理'), + WorkTabIconGrid( + buttonInfos: buttonInfos, + onItemPressed: _handleItemPressed, + ), + ], + ) + ), + ); + } +} diff --git a/lib/pages/home/home_page.dart b/lib/pages/home/home_page.dart index 21f8ff3..f70c735 100644 --- a/lib/pages/home/home_page.dart +++ b/lib/pages/home/home_page.dart @@ -5,6 +5,7 @@ import 'package:qhd_prevention/customWidget/ItemWidgetFactory.dart'; import 'package:qhd_prevention/pages/KeyProjects/keyProjects_tab_list.dart'; import 'package:qhd_prevention/pages/badge_manager.dart'; import 'package:qhd_prevention/pages/home/NFC/home_nfc_list_page.dart'; +import 'package:qhd_prevention/pages/home/SafeCheck/safeCheck_tab_list.dart'; import 'package:qhd_prevention/pages/home/home_danger_page.dart'; import 'package:qhd_prevention/pages/home/low_page.dart'; import 'package:qhd_prevention/pages/home/risk/riskControl_page.dart'; @@ -342,6 +343,9 @@ class _HomePageState extends State { case 7: pushPage(StudyGardenPage(), context); break; + case 8: // 安全检查 + await pushPage(SafecheckTabList(), context); + break; case 10: pushPage(SafetyMeetingListPage(), context); break; diff --git a/lib/pages/home/tap/item_list_widget.dart b/lib/pages/home/tap/item_list_widget.dart index 58feb2b..b7732fe 100644 --- a/lib/pages/home/tap/item_list_widget.dart +++ b/lib/pages/home/tap/item_list_widget.dart @@ -22,6 +22,7 @@ class ItemListWidget { bool isRequired = true, bool strongRequired = false, ValueChanged? onChanged, + ValueChanged? onFieldSubmitted, /// 强制必选 不受是否可以编译和是否必选影响 TextInputType keyboardType = TextInputType.text, @@ -86,6 +87,8 @@ class ItemListWidget { double height = 110, // 整体高度 bool isRequired = true, String hintText = '请输入', + ValueChanged? onChanged, + }) { return Container( // 统一左右 padding,保证标题和内容在同一左侧基线 @@ -117,6 +120,7 @@ class ItemListWidget { keyboardType: TextInputType.multiline, maxLines: null, expands: true, + onChanged: onChanged, // 垂直顶部对齐 textAlignVertical: TextAlignVertical.top, style: TextStyle(fontSize: fontSize), @@ -693,8 +697,8 @@ class ItemListWidget { return Container( width: 80, height: 80, - color: Colors.grey[200], - child: const Icon(Icons.broken_image, size: 40), + color: Colors.transparent, + child: SizedBox(), ); }, ), @@ -707,6 +711,99 @@ class ItemListWidget { ], ); } + /// 多行垂直布局: + /// 标题+按钮 + /// 编辑框列表,多个编辑框可删除 + static Widget mulRowTitleAndTextField({ + required String label, // 第一行标题 + required bool isEditable, // 是否可编辑 + required String text, // 显示内容或提示 + TextEditingController? controller, // 第二行编辑控制器 + required VoidCallback? onTap, // 第一行点击回调 + required String hintText, + double fontSize = 15, // 字体大小 + double row2Height = 80, // 第二行高度 + bool isRequired = true, + }) { + return Container( + padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 12), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // 第一行:标题 + 按钮 + InkWell( + child: Row( + children: [ + Flexible( + fit: FlexFit.loose, // loose 模式下它可以比最大宽度更小 + child: Row( + children: [ + if (isRequired && isEditable) + Text('* ', style: TextStyle(color: Colors.red)), + Flexible( + child: Text( + label, + style: TextStyle( + fontSize: fontSize, + fontWeight: FontWeight.bold, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + ), + const SizedBox(width: 8), + if (isEditable) + CustomButton( + text: "选择其他", + height: 30, + padding: const EdgeInsets.symmetric( + vertical: 2, + horizontal: 5, + ), + backgroundColor: Colors.green, + onPressed: onTap, + ), + ], + ), + ), + const SizedBox(height: 8), + + 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, + ), + ), + ), + ), + ], + ), + ); + } static Widget itemContainer(Widget child, {double horizontal = 12}) { diff --git a/lib/tools/tools.dart b/lib/tools/tools.dart index 6f07d32..88f7533 100644 --- a/lib/tools/tools.dart +++ b/lib/tools/tools.dart @@ -367,6 +367,14 @@ class FormUtils { // 数字、布尔等其它非空即可 return true; } + /// 在list中根据一个 key,value,找到对应的map + static Map findMapForKeyValue(List list,String key, String value) { + Map target = list.firstWhere( + (item) => item[key] == value, + orElse: () => {}, + ); + return target ?? {}; + } } class NoDataWidget {