diff --git a/lib/customWidget/custom_alert_dialog.dart b/lib/customWidget/custom_alert_dialog.dart index 6cc83b8..16399e6 100644 --- a/lib/customWidget/custom_alert_dialog.dart +++ b/lib/customWidget/custom_alert_dialog.dart @@ -12,7 +12,7 @@ class CustomAlertDialog extends StatefulWidget { final VoidCallback? onCancel; final VoidCallback? onConfirm; // 文字模式回调 final ValueChanged? onInputConfirm; // 输入模式回调 - final DialogMode mode; // 新增:对话框模式 + final DialogMode mode; // 对话框模式 const CustomAlertDialog({ Key? key, @@ -24,11 +24,87 @@ class CustomAlertDialog extends StatefulWidget { this.onCancel, this.onConfirm, this.onInputConfirm, - this.mode = DialogMode.text, // 默认文字模式 + this.mode = DialogMode.text, }) : super(key: key); @override _CustomAlertDialogState createState() => _CustomAlertDialogState(); + + // ------------------ 快捷静态方法 ------------------ + + /// 带“取消/确定”的确认弹窗 + /// 返回 true = 确定,false = 取消或关闭 + static Future showConfirm( + BuildContext context, { + required String title, + String content = '', + String cancelText = '取消', + String confirmText = '确定', + bool barrierDismissible = true, + }) async { + final result = await showDialog( + context: context, + barrierDismissible: barrierDismissible, + builder: (_) { + return CustomAlertDialog( + title: title, + content: content, + cancelText: cancelText, + confirmText: confirmText, + mode: DialogMode.text, + ); + }, + ); + return result == true; + } + + /// 只有“确定”按钮的文字提示弹窗(适合提示信息) + static Future showAlert( + BuildContext context, { + required String title, + String content = '', + String confirmText = '确定', + bool barrierDismissible = true, + }) async { + await showDialog( + context: context, + barrierDismissible: barrierDismissible, + builder: (_) { + return CustomAlertDialog( + title: title, + content: content, + cancelText: '', // 隐藏取消按钮使其成为单按钮 + confirmText: confirmText, + mode: DialogMode.text, + ); + }, + ); + } + + /// 输入对话框(带输入框),返回用户输入的字符串;取消或关闭返回 null + static Future showInput( + BuildContext context, { + required String title, + String hintText = '', + String cancelText = '取消', + String confirmText = '确定', + bool barrierDismissible = true, + }) async { + final result = await showDialog( + context: context, + barrierDismissible: barrierDismissible, + builder: (_) { + return CustomAlertDialog( + title: title, + hintText: hintText, + cancelText: cancelText, + confirmText: confirmText, + mode: DialogMode.input, + ); + }, + ); + return result; + } } class _CustomAlertDialogState extends State { @@ -37,7 +113,7 @@ class _CustomAlertDialogState extends State { @override void initState() { super.initState(); - // 输入模式下初始化 TextField + // 输入模式下初始化 TextField 控制器 _controller = TextEditingController(); } @@ -54,28 +130,32 @@ class _CustomAlertDialogState extends State { return Dialog( backgroundColor: Colors.transparent, child: Container( + constraints: const BoxConstraints(minWidth: 280), decoration: BoxDecoration( color: Colors.white, - borderRadius: BorderRadius.circular(5), + borderRadius: BorderRadius.circular(8), ), child: Column( mainAxisSize: MainAxisSize.min, children: [ const SizedBox(height: 20), - Text( - widget.title, - style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold), - textAlign: TextAlign.center, + Padding( + padding: const EdgeInsets.symmetric(horizontal: 20), + child: Text( + widget.title, + style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold), + textAlign: TextAlign.center, + ), ), - const SizedBox(height: 20), + const SizedBox(height: 16), // ★ 根据 mode 决定展示文字还是输入框 ★ if (widget.mode == DialogMode.text) Padding( - padding: const EdgeInsets.symmetric(horizontal: 30), + padding: const EdgeInsets.symmetric(horizontal: 24), child: Text( widget.content, - style: const TextStyle(fontSize: 16, color: Colors.black45), + style: const TextStyle(fontSize: 16, color: Colors.black54), textAlign: TextAlign.center, ), ) @@ -84,7 +164,7 @@ class _CustomAlertDialogState extends State { padding: const EdgeInsets.symmetric(horizontal: 20), child: TextField( controller: _controller, - + autofocus: true, decoration: InputDecoration( hintText: widget.hintText, border: const OutlineInputBorder(), @@ -104,9 +184,7 @@ class _CustomAlertDialogState extends State { const SizedBox(height: 20), const Divider(height: 1), - hasCancel - ? _buildDoubleButtons(context) - : _buildSingleButton(context), + hasCancel ? _buildDoubleButtons(context) : _buildSingleButton(context), ], ), ), @@ -120,8 +198,11 @@ class _CustomAlertDialogState extends State { Expanded( child: InkWell( onTap: () { - Navigator.of(context).pop(); + // 根据模式返回不同值:文本模式返回 false,输入模式返回 null + final ret = widget.mode == DialogMode.text ? false : null; + // 先触发回调(如果开发者传了),再关闭并把结果返回给调用者 widget.onCancel?.call(); + Navigator.of(context).pop(ret); }, child: Container( padding: const EdgeInsets.symmetric(vertical: 12), @@ -130,7 +211,7 @@ class _CustomAlertDialogState extends State { widget.cancelText, style: const TextStyle( fontWeight: FontWeight.w500, - color: Colors.black, + color: Colors.black87, fontSize: 18, ), ), @@ -144,11 +225,13 @@ class _CustomAlertDialogState extends State { Expanded( child: InkWell( onTap: () { - Navigator.of(context).pop(); if (widget.mode == DialogMode.text) { widget.onConfirm?.call(); + Navigator.of(context).pop(true); } else { - widget.onInputConfirm?.call(_controller.text.trim()); + final value = _controller.text.trim(); + widget.onInputConfirm?.call(value); + Navigator.of(context).pop(value); } }, child: Container( @@ -172,11 +255,13 @@ class _CustomAlertDialogState extends State { Widget _buildSingleButton(BuildContext context) { return InkWell( onTap: () { - Navigator.of(context).pop(); if (widget.mode == DialogMode.text) { widget.onConfirm?.call(); + Navigator.of(context).pop(true); } else { - widget.onInputConfirm?.call(_controller.text.trim()); + final value = _controller.text.trim(); + widget.onInputConfirm?.call(value); + Navigator.of(context).pop(value); } }, child: Container( diff --git a/lib/customWidget/department_person_picker.dart b/lib/customWidget/department_person_picker.dart index 27b56ff..25e80e5 100644 --- a/lib/customWidget/department_person_picker.dart +++ b/lib/customWidget/department_person_picker.dart @@ -16,23 +16,31 @@ class Person { } } -/// 回调签名,返回选中用户的 USER_ID 和 NAME +/// 原回调签名(向后兼容) typedef PersonSelectCallback = void Function(String userId, String name); +/// 新回调签名,增加可选 index(int,默认 0) +typedef PersonSelectCallbackWithIndex = void Function(String userId, String name, int index); + /// 底部弹窗人员选择器(使用预先传入的原始数据列表,不做接口请求) class DepartmentPersonPicker { /// 显示人员选择弹窗 /// /// [personsData]: 已拉取并缓存的原始 Map 列表 - /// [onSelected]: 选中后回调 USER_ID 和 NAME + /// [onSelected]: 选中后回调 USER_ID 和 NAME(向后兼容旧代码) + /// [onSelectedWithIndex]: 可选的新回调,额外返回 index(index 为在原始 personsData/_all 中的下标,找不到则为 0) static Future show( BuildContext context, { required List> personsData, - required PersonSelectCallback onSelected, + PersonSelectCallback? onSelected, + PersonSelectCallbackWithIndex? onSelectedWithIndex, }) async { + // 至少传入一个回调(保持对旧调用的兼容) + assert(onSelected != null || onSelectedWithIndex != null, + '请至少传入 onSelected 或 onSelectedWithIndex'); + // 转换为模型 - final List _all = - personsData.map((e) => Person.fromJson(e)).toList(); + final List _all = personsData.map((e) => Person.fromJson(e)).toList(); List _filtered = List.from(_all); String _selectedName = ''; String _selectedId = ''; @@ -54,9 +62,7 @@ class DepartmentPersonPicker { setState(() { _filtered = q.isEmpty ? List.from(_all) - : _all - .where((p) => p.name.toLowerCase().contains(q)) - .toList(); + : _all.where((p) => p.name.toLowerCase().contains(q)).toList(); }); } @@ -67,24 +73,24 @@ class DepartmentPersonPicker { children: [ // 顶部:取消、搜索、确定 Padding( - padding: const EdgeInsets.symmetric( - horizontal: 16, vertical: 8), + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), child: Row( children: [ TextButton( onPressed: () => Navigator.of(ctx).pop(), - child: const Text('取消',style: TextStyle(fontSize: 16),), + child: const Text( + '取消', + style: TextStyle(fontSize: 16), + ), ), Expanded( child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 8), + padding: const EdgeInsets.symmetric(horizontal: 8), child: SearchBarWidget( controller: _searchController, onTextChanged: _onSearch, isShowSearchButton: false, - onSearch: (keyboard) { - }, + onSearch: (keyboard) {}, ), ), ), @@ -93,9 +99,22 @@ class DepartmentPersonPicker { ? null : () { Navigator.of(ctx).pop(); - onSelected(_selectedId, _selectedName); + + // 计算 index(在原始 _all 列表中的下标) + final idx = _all.indexWhere((p) => p.userId == _selectedId); + final validIndex = idx >= 0 ? idx : 0; + + // 优先调用带 index 的回调(新),否则调用旧回调 + if (onSelectedWithIndex != null) { + onSelectedWithIndex(_selectedId, _selectedName, validIndex); + } else if (onSelected != null) { + onSelected(_selectedId, _selectedName); + } }, - child: const Text('确定', style: TextStyle(color: Colors.green, fontSize: 16),), + child: const Text( + '确定', + style: TextStyle(color: Colors.green, fontSize: 16), + ), ), ], ), @@ -112,8 +131,7 @@ class DepartmentPersonPicker { return ListTile( titleAlignment: ListTileTitleAlignment.center, title: Text(person.name), - trailing: - selected ? const Icon(Icons.check, color: Colors.green) : null, + trailing: selected ? const Icon(Icons.check, color: Colors.green) : null, onTap: () => setState(() { _selectedId = person.userId; _selectedName = person.name; @@ -131,4 +149,4 @@ class DepartmentPersonPicker { }, ); } -} +} \ No newline at end of file diff --git a/lib/pages/KeyProjects/SafeCheck/custom/MultiTextFieldWithTitle.dart b/lib/pages/KeyProjects/SafeCheck/custom/MultiTextFieldWithTitle.dart index 6e3b1cb..5be8032 100644 --- a/lib/pages/KeyProjects/SafeCheck/custom/MultiTextFieldWithTitle.dart +++ b/lib/pages/KeyProjects/SafeCheck/custom/MultiTextFieldWithTitle.dart @@ -19,7 +19,7 @@ class MultiTextFieldWithTitle extends StatefulWidget { required this.hintText, required this.onTextsChanged, this.fontSize = 15, - this.texts = const [], + this.texts = const [], this.isRequired = true, }); @@ -35,14 +35,97 @@ class _MultiTextFieldWithTitleState extends State { @override void initState() { super.initState(); - // 延迟初始化,避免构建过程中调用 setState - WidgetsBinding.instance.addPostFrameCallback((_) { - if (mounted) { - _addTextField(); + + // 根据传入的初始 texts 初始化 controllers(可编辑时也赋值) + if (widget.texts.isNotEmpty) { + for (final t in widget.texts) { + final controller = TextEditingController(text: t); + final node = FocusNode(); + controller.addListener(() { + widget.onTextsChanged(_getAllTexts()); + }); + _controllers.add(controller); + _focusNodes.add(node); } + } else { + // 如果没有初始值,至少创建一个空的控制器(可编辑场景需要输入框) + final controller = TextEditingController(); + final node = FocusNode(); + controller.addListener(() { + widget.onTextsChanged(_getAllTexts()); + }); + _controllers.add(controller); + _focusNodes.add(node); + } + + // 触发一次回调,确保父组件拿到初始值 + WidgetsBinding.instance.addPostFrameCallback((_) { + if (mounted) widget.onTextsChanged(_getAllTexts()); }); } + @override + void didUpdateWidget(covariant MultiTextFieldWithTitle oldWidget) { + super.didUpdateWidget(oldWidget); + + // 当外部传入的 texts 发生变化(例如父组件替换了初始列表), + // 且当前 controllers 与之不一致时,进行同步更新。 + // 为避免覆盖用户正在编辑的数据,这里只在长度或内容明显不同时才同步。 + final newTexts = widget.texts; + bool needSync = false; + + if (newTexts.length != _controllers.length) { + needSync = true; + } else { + // 长度相等时比较内容 + for (var i = 0; i < newTexts.length; i++) { + final controllerText = _controllers[i].text; + if (controllerText != newTexts[i]) { + needSync = true; + break; + } + } + } + + if (needSync) { + // 清理旧的 controllers / nodes + for (var c in _controllers) { + c.dispose(); + } + for (var n in _focusNodes) { + n.dispose(); + } + _controllers.clear(); + _focusNodes.clear(); + + // 重新创建 + if (newTexts.isNotEmpty) { + for (final t in newTexts) { + final controller = TextEditingController(text: t); + final node = FocusNode(); + controller.addListener(() { + widget.onTextsChanged(_getAllTexts()); + }); + _controllers.add(controller); + _focusNodes.add(node); + } + } else { + final controller = TextEditingController(); + final node = FocusNode(); + controller.addListener(() { + widget.onTextsChanged(_getAllTexts()); + }); + _controllers.add(controller); + _focusNodes.add(node); + } + + // 通知父组件 + WidgetsBinding.instance.addPostFrameCallback((_) { + if (mounted) widget.onTextsChanged(_getAllTexts()); + }); + } + } + @override void dispose() { for (var controller in _controllers) { @@ -54,9 +137,10 @@ class _MultiTextFieldWithTitleState extends State { super.dispose(); } - void _addTextField() { + // _addTextField 现在支持传入初始文本 + void _addTextField([String initialText = '']) { setState(() { - final newController = TextEditingController(); + final newController = TextEditingController(text: initialText); final newFocusNode = FocusNode(); newController.addListener(() { @@ -66,32 +150,35 @@ class _MultiTextFieldWithTitleState extends State { _controllers.add(newController); _focusNodes.add(newFocusNode); widget.onTextsChanged(_getAllTexts()); + // 自动聚焦到新创建的输入框(延迟到下一帧) + WidgetsBinding.instance.addPostFrameCallback((_) { + if (mounted) { + final idx = _controllers.length - 1; + _focusNodes[idx].requestFocus(); + } + }); }); } 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()); - }); - }, - ), + final confirmed = await CustomAlertDialog.showConfirm( + context, + title: '提示', + content: '确定删除检查情况吗?', + cancelText: '取消', + confirmText: '确定', ); + if (!confirmed) return; + + setState(() { + _controllers[index].dispose(); + _focusNodes[index].dispose(); + + _controllers.removeAt(index); + _focusNodes.removeAt(index); + widget.onTextsChanged(_getAllTexts()); + }); } List _getAllTexts() { @@ -139,7 +226,7 @@ class _MultiTextFieldWithTitleState extends State { horizontal: 5, ), backgroundColor: Colors.blue, - onPressed: _addTextField, + onPressed: () => _addTextField(), ), ], ), @@ -161,20 +248,17 @@ class _MultiTextFieldWithTitleState extends State { ...widget.texts.map((c) { return Padding( padding: const EdgeInsets.only(bottom: 8.0), - child: SizedBox( - width: double.maxFinite, - child: DottedBorderBox( - child: - Text( - c, - style: TextStyle( - fontSize: widget.fontSize, - color: Colors.grey[600], + width: double.maxFinite, + child: DottedBorderBox( + child: Text( + c, + style: TextStyle( + fontSize: widget.fontSize, + color: Colors.grey[600], + ), ), - ), - ), - ) + )), ); }).toList(), ], @@ -193,19 +277,18 @@ class _MultiTextFieldWithTitleState extends State { Padding( padding: EdgeInsets.symmetric(horizontal: 7, vertical: 7), child: SizedBox( - width: double.maxFinite, - child: DottedBorderBox( - 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), - ), - ) - ), + width: double.maxFinite, + child: DottedBorderBox( + 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), + ), + )), ), // 删除按钮(叠加在左上角) diff --git a/lib/pages/home/SafeCheck/CheckPersonSign/safeCheck_start_list_page.dart b/lib/pages/home/SafeCheck/CheckPersonSign/safeCheck_start_list_page.dart new file mode 100644 index 0000000..85fdf8a --- /dev/null +++ b/lib/pages/home/SafeCheck/CheckPersonSign/safeCheck_start_list_page.dart @@ -0,0 +1,473 @@ +import 'package:flutter/material.dart'; +import 'package:qhd_prevention/customWidget/toast_util.dart'; +import 'package:qhd_prevention/pages/home/SafeCheck/Start/safeCheck_start_detail.dart'; +import 'package:qhd_prevention/pages/home/tap/tabList/special_wrok/dh_work/dh_work_detai/hotwork_apply_detail.dart'; +import 'package:qhd_prevention/pages/my_appbar.dart'; +import 'package:qhd_prevention/tools/tools.dart'; +import 'package:qhd_prevention/customWidget/bottom_picker.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 SafecheckStartListPage extends StatefulWidget { + final String flow; + + const SafecheckStartListPage({Key? key, required this.flow}) + : super(key: key); + + @override + _SafecheckStartListPageState createState() => _SafecheckStartListPageState(); +} + +class _SafecheckStartListPageState extends State { + // Data and state variables + List list = []; + int currentPage = 1; + int rows = 10; + int totalPage = 1; + bool isLoading = false; + List stepList = [ + {'id': '', 'name': '请选择'}, + {'id': '0', 'name': '待检查人核实'}, + {'id': '1', 'name': '检查人核实中'}, + {'id': '2', 'name': '待被检查人确认'}, + {'id': '3', 'name': '待指派'}, + {'id': '4', 'name': '指派中'}, + {'id': '5', 'name': '指派完成'}, + {'id': '6', 'name': '检查待验收'}, + {'id': '7', 'name': '检查已验收'}, + {'id': '8', 'name': '已归档'}, + {'id': '-1', 'name': '检查人核实打回'}, + {'id': '-2', 'name': '被检查人申辩'}, + ]; + final TextEditingController _searchController = TextEditingController(); + + int sindex = 0; + String searchKeywords = ''; + + List> flowList = []; + 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 = { + 'INSPECTION_STATUS': sindex > 0 ? stepList[sindex]['id'] : '', + 'KEYWORDS': searchKeywords, + }; + final url = + '/app/safetyenvironmental/list?showCount=-1¤tPage=$currentPage'; + final response = await ApiService.getSafeCheckSearchList(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 _handleApply() { + // 处理申请按钮点击逻辑 + pushPage(SafecheckStartDetail(INSPECTION_ID: '', isEdit: true), context); + } + + /// 打开流程图 + Future _openFlowDrawer(String ID) async { + try { + final response = await ApiService.safeCheckFlowList(ID); + final List? newFlow = response['varList']; + if (newFlow == null || newFlow.isEmpty) { + ToastUtil.showNormal(context, '暂无流程图数据'); + return; + } + + setState(() { + flowList = List>.from(newFlow); + }); + Future.microtask(() { + _scaffoldKey.currentState?.openEndDrawer(); + }); + } catch (e) { + print('Error fetching flow data: $e'); + ScaffoldMessenger.of( + context, + ).showSnackBar(SnackBar(content: Text('获取流程图失败: $e'))); + } + } + + void _goToDetail(Map item) async { + + pushPage(SafecheckStartDetail(INSPECTION_ID: item['INSPECTION_ID'] ?? '', isEdit: false), context); + + setState(() { + _fetchData(); + }); + + } + + Widget _buildFlowStepItem({ + required Map item, + required bool isFirst, + required bool isLast, + }) { + bool status = item['active'] == item['order']; + // 依据状态设色 + final Color dotColor = status ? Colors.blue :Colors.grey; + final Color textColor = status ? Colors.blue :Colors.black54; + + return ListTile( + visualDensity: VisualDensity(vertical: -4), + + contentPadding: const EdgeInsets.symmetric(horizontal: 16), + leading: Container( + width: 24, + alignment: Alignment.center, + child: Column( + mainAxisSize: MainAxisSize.max, + children: [ + // 上方线段或占位 + isFirst + ? SizedBox(height: 6 + 5) + : Expanded(child: Container(width: 1, color: Colors.grey[300])), + // 圆点 + CircleAvatar(radius: 6, backgroundColor: dotColor), + // 下方线段或占位 + isLast + ? SizedBox(height: 6 + 5) + : Expanded(child: Container(width: 1, color: Colors.grey[300])), + ], + ), + ), + title: Text( + item['title'] ?? '', + style: TextStyle(color: textColor, fontSize: 15), + ), + subtitle: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (item['desc'] != null) + Text( + item['desc'], + style: TextStyle(color: textColor, fontSize: 13), + ), + ], + ), + ); + } + + String _checkStatusWithId(String id) { + for (Map item in stepList) { + if (item['id'] == id) { + return item['name']; + } + } + return ''; + } + + Widget _buildListItem(Map item) { + 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( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "安全检查记录", + style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold), + ), + ], + ), + const SizedBox(height: 8), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "检查状态: ${_checkStatusWithId(item['INSPECTION_STATUS'].toString())}", + ), + Text("检查类型: ${item['INSPECTION_TYPE_NAME'] ?? ''}"), + ], + ), + const SizedBox(height: 8), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text("检查人: ${item['INSPECTION_USER_NAME'] ?? ''}"), + ], + ), + const SizedBox(height: 8), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "检查发起人: ${item['INSPECTION_ORIGINATOR_NAME'] ?? ''}", + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ], + ), + const SizedBox(height: 8), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text("被检查人: ${item['INSPECTED_SITEUSER_NAME'] ?? ''}"), + ], + ), + const SizedBox(height: 8), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "检查时间: ${item['INSPECTION_TIME_START'] ?? ''}至${item['INSPECTION_TIME_END'] ?? ''}", + ), + ], + ), + const SizedBox(height: 8), + Row( + children: [ + CustomButton( + text: '流程图', + height: 35, + padding: EdgeInsets.symmetric(horizontal: 12), + margin: EdgeInsets.only(left: 0), + backgroundColor: Colors.blue, + onPressed: () => _openFlowDrawer(item['INSPECTION_ID']), + ), + SizedBox(width: 1), + ], + ), + ], + ), + ), + ), + ); + } + + // 显示底部选择器 + Future _showStepPicker() async { + if (stepList.isEmpty) { + ScaffoldMessenger.of( + context, + ).showSnackBar(SnackBar(content: Text('正在加载步骤数据,请稍后...'))); + if (stepList.isEmpty) { + ScaffoldMessenger.of( + context, + ).showSnackBar(SnackBar(content: Text('无法加载步骤数据'))); + return; + } + } + + // 创建选项列表 + final options = stepList.map((e) => e['name'] as String).toList(); + + // 显示底部选择器 + final choice = await BottomPicker.show( + context, + items: options, + itemBuilder: (item) => Text(item, textAlign: TextAlign.center), + initialIndex: sindex, + ); + + if (choice != null) { + // 找到选择的索引 + final newIndex = options.indexOf(choice); + if (newIndex != -1) { + setState(() { + sindex = newIndex; + }); + _search(); + } + } + } + + 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) { + final int lastDoneIndex = flowList.lastIndexWhere((e) => e['STATUS'] == 1); + + return Scaffold( + key: _scaffoldKey, + appBar: MyAppbar( + title: '${widget.flow}', + actions: [ + TextButton( + onPressed: _handleApply, + child: const Text( + '发起', + style: TextStyle(color: Colors.white, fontSize: 17), + ), + ), + + ], + ), + endDrawer: Drawer( + child: SafeArea( + child: + flowList.isEmpty + ? Center(child: Text('暂无流程图数据')) + : ListView.builder( + padding: const EdgeInsets.symmetric(vertical: 16), + itemCount: flowList.length + 1, // +1 用来放标题 + itemBuilder: (context, i) { + if (i == 0) { + return Padding( + padding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 8, + ), + child: Text( + '查看流程图', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ), + ); + } + final idx = i - 1; + final item = flowList[idx]; + final bool isFirst = idx == 0; + final bool isLast = idx == flowList.length - 1; + + return _buildFlowStepItem( + item: item, + isFirst: isFirst, + isLast: isLast, + ); + }, + ), + ), + ), + + body: SafeArea(child: Column( + children: [ + // Filter bar + Container( + color: Colors.white, + padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 8), + child: Row( + children: [ + // 底部弹窗选择器 + SizedBox( + width: 65, + child: TextButton( + onPressed: _showStepPicker, + style: TextButton.styleFrom( + padding: EdgeInsets.symmetric( + vertical: 12, + horizontal: 5, + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + '筛选', + style: TextStyle(color: Colors.black87, fontSize: 16), + ), + Icon(Icons.arrow_drop_down, color: Colors.grey), + ], + ), + ), + ), + 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/home/SafeCheck/CheckPersonSure/check_person_detail.dart b/lib/pages/home/SafeCheck/CheckPersonSure/check_person_detail.dart new file mode 100644 index 0000000..7f86778 --- /dev/null +++ b/lib/pages/home/SafeCheck/CheckPersonSure/check_person_detail.dart @@ -0,0 +1,367 @@ +import 'dart:convert'; +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/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/department_person_picker.dart'; +import 'package:qhd_prevention/customWidget/department_picker.dart'; +import 'package:qhd_prevention/customWidget/dotted_border_box.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/SafeCheck/SafeCheckFormView.dart'; +import 'package:qhd_prevention/pages/home/SafeCheck/Start/safeCheck_drawer_page.dart'; +import 'package:qhd_prevention/pages/home/tap/item_list_widget.dart'; +import 'package:qhd_prevention/pages/home/tap/tabList/special_wrok/dh_work/dh_work_detai/hotwork_apply_detail.dart'; +import 'package:qhd_prevention/pages/mine/mine_sign_page.dart'; +import 'package:qhd_prevention/pages/my_appbar.dart'; +import 'package:qhd_prevention/tools/tools.dart'; + +class CheckPersonDetail extends StatefulWidget { + const CheckPersonDetail({ + super.key, + required this.INSPECTION_ID, + required this.isEdit, + }); + + final String INSPECTION_ID; + final bool isEdit; + + @override + State createState() => _CheckPersonDetailState(); +} + +class _CheckPersonDetailState extends State { + + String msg = 'add'; + bool _isEdit = false; + + List signImages = []; + List signTimes = []; // 签字时间列表 + + Map form = { + }; + + @override + void initState() { + super.initState(); + _isEdit = widget.isEdit; + + WidgetsBinding.instance.addPostFrameCallback((_) { + _getData(); + }); + + } + + Future _getData() async { + try { + final result = await ApiService.getSafeCheckStartGoEdit( + widget.INSPECTION_ID, + ); + // 在 await 之后检查 mounted,避免页面已经被 pop 导致 setState 报错 + if (!mounted) return; + setState(() { + form = result['pd'] ?? {}; + }); + } catch (e, st) { + print('加载单条数据失败: $e\n$st'); + if (mounted) { + ToastUtil.showNormal(context, '加载数据失败:$e'); + } + } + } + + + Future _openDrawer(Map hiddenForm, int index) async { + try { + final result = await openCustomDrawer( + context, + SafeCheckDrawerPage( + initialHidden: hiddenForm, + editType: + _isEdit + ? (index < 0 ? SafeCheckEditType.add : SafeCheckEditType.edit) + : SafeCheckEditType.see, + toCheckUnitList: [], + ), + ); + + if (result != null && mounted) { + setState(() { + if (index < 0) { + // 新增 + form['hiddenList'].add(result); + print(form); + } 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(); + } + + + + + // ------------ 提交入口 ------------ + Future _submit() async { Future _sign() async { + final path = await Navigator.push( + context, + MaterialPageRoute(builder: (context) => MineSignPage()), + ); + if (path != null) { + final now = DateFormat('yyyy-MM-dd HH:mm').format(DateTime.now()); + + setState(() { + signImages.add(path); + signTimes.add(now); + FocusHelper.clearFocus(context); + }); + } + } + } + + // ========== 罚单提交方法 + 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; + } + } + + /// 签字 + Future _sign() async { + final path = await Navigator.push( + context, + MaterialPageRoute(builder: (context) => MineSignPage()), + ); + if (path != null) { + final now = DateFormat('yyyy-MM-dd HH:mm').format(DateTime.now()); + + setState(() { + signImages.add(path); + signTimes.add(now); + FocusHelper.clearFocus(context); + }); + } + } + Widget _signListWidget() { + return Column( + children: + signImages.map((path) { + return Column( + children: [ + const SizedBox(height: 10), + const Divider(), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + GestureDetector( + child: // 用一个 ConstrainedBox 限制最大尺寸,并改为 BoxFit.contain + ConstrainedBox( + constraints: const BoxConstraints( + maxWidth: 200, + maxHeight: 150, + ), + child: Image.file( + File(path), + // 改为完整显示 + fit: BoxFit.contain, + ), + ), + onTap: () { + presentOpaque( + SingleImageViewer(imageUrl: path), + context, + ); + }, + ), + Column( + children: [ + Container( + padding: const EdgeInsets.only(right: 5), + child: CustomButton( + text: 'X', + height: 30, + padding: const EdgeInsets.symmetric(horizontal: 10), + backgroundColor: Colors.red, + onPressed: () { + setState(() { + signImages.remove(path); + }); + }, + ), + ), + const SizedBox(height: 80), + ], + ), + ], + ), + ], + ); + }).toList(), + ); + } + Widget _mainWidget() { + return ListView( + children: [ + Column( + children: [ + SafeCheckFormView(form: form, isEdit: false, onHiddenShow: (item, index) { + + }), + ItemListWidget.itemContainer( + horizontal: 0, + Column( + children: [ + + + + + if (signImages.isNotEmpty) _signListWidget(), + + ], + ), + ), + SizedBox(height: 20), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + if (_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: _isEdit ? '提交' : '返回', + textStyle: TextStyle( + color: Colors.white, + fontSize: 17, + ), + backgroundColor: Colors.blue, + onPressed: _submit, + ), + ), + ], + ), + ], + ), + ], + ); + } + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: MyAppbar(title: "安全检查发起", actions: []), + + body: SafeArea( + child: Padding( + padding: EdgeInsets.symmetric(horizontal: 12, vertical: 12), + child: + form.isNotEmpty + ? _mainWidget() + : SizedBox(), + ), + ), + ); + } +} diff --git a/lib/pages/home/SafeCheck/CheckPersonSure/check_person_list_page.dart b/lib/pages/home/SafeCheck/CheckPersonSure/check_person_list_page.dart new file mode 100644 index 0000000..6f3238a --- /dev/null +++ b/lib/pages/home/SafeCheck/CheckPersonSure/check_person_list_page.dart @@ -0,0 +1,476 @@ +import 'package:flutter/material.dart'; +import 'package:qhd_prevention/customWidget/toast_util.dart'; +import 'package:qhd_prevention/pages/home/SafeCheck/CheckPersonSure/check_person_detail.dart'; +import 'package:qhd_prevention/pages/home/SafeCheck/Start/safeCheck_start_detail.dart'; +import 'package:qhd_prevention/pages/home/tap/tabList/special_wrok/dh_work/dh_work_detai/hotwork_apply_detail.dart'; +import 'package:qhd_prevention/pages/my_appbar.dart'; +import 'package:qhd_prevention/tools/tools.dart'; +import 'package:qhd_prevention/customWidget/bottom_picker.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 CheckPersonListPage extends StatefulWidget { + final String flow; + + const CheckPersonListPage({Key? key, required this.flow}) + : super(key: key); + + @override + _CheckPersonListPageState createState() => _CheckPersonListPageState(); +} + +class _CheckPersonListPageState extends State { + // Data and state variables + List list = []; + int currentPage = 1; + int rows = 10; + int totalPage = 1; + bool isLoading = false; + List stepList = [ + {'id': '', 'name': '请选择'}, + {'id': '0', 'name': '待检查人核实'}, + {'id': '1', 'name': '检查人核实中'}, + {'id': '2', 'name': '待被检查人确认'}, + {'id': '3', 'name': '待指派'}, + {'id': '4', 'name': '指派中'}, + {'id': '5', 'name': '指派完成'}, + {'id': '6', 'name': '检查待验收'}, + {'id': '7', 'name': '检查已验收'}, + {'id': '8', 'name': '已归档'}, + {'id': '-1', 'name': '检查人核实打回'}, + {'id': '-2', 'name': '被检查人申辩'}, + ]; + final TextEditingController _searchController = TextEditingController(); + + int sindex = 0; + String searchKeywords = ''; + + List> flowList = []; + 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 = { + 'INSPECTION_STATUS': sindex > 0 ? stepList[sindex]['id'] : '', + 'KEYWORDS': searchKeywords, + }; + final url = + '/app/safetyenvironmentalinspector/list?showCount=-1¤tPage=$currentPage'; + final response = await ApiService.getSafeCheckSearchList(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 _handleApply() { + // 处理申请按钮点击逻辑 + pushPage(SafecheckStartDetail(INSPECTION_ID: '', isEdit: true), context); + } + + /// 打开流程图 + Future _openFlowDrawer(String ID) async { + try { + final response = await ApiService.safeCheckFlowList(ID); + final List? newFlow = response['varList']; + if (newFlow == null || newFlow.isEmpty) { + ToastUtil.showNormal(context, '暂无流程图数据'); + return; + } + + setState(() { + flowList = List>.from(newFlow); + }); + Future.microtask(() { + _scaffoldKey.currentState?.openEndDrawer(); + }); + } catch (e) { + print('Error fetching flow data: $e'); + ScaffoldMessenger.of( + context, + ).showSnackBar(SnackBar(content: Text('获取流程图失败: $e'))); + } + } + + void _goToDetail(Map item) async { + + pushPage(CheckPersonDetail(INSPECTION_ID: item['INSPECTION_ID'] ?? '', isEdit: false), context); + + setState(() { + _fetchData(); + }); + + } + + Widget _buildFlowStepItem({ + required Map item, + required bool isFirst, + required bool isLast, + }) { + bool status = item['active'] == item['order']; + // 依据状态设色 + final Color dotColor = status ? Colors.blue :Colors.grey; + final Color textColor = status ? Colors.blue :Colors.black54; + + return ListTile( + visualDensity: VisualDensity(vertical: -4), + + contentPadding: const EdgeInsets.symmetric(horizontal: 16), + leading: Container( + width: 24, + alignment: Alignment.center, + child: Column( + mainAxisSize: MainAxisSize.max, + children: [ + // 上方线段或占位 + isFirst + ? SizedBox(height: 6 + 5) + : Expanded(child: Container(width: 1, color: Colors.grey[300])), + // 圆点 + CircleAvatar(radius: 6, backgroundColor: dotColor), + // 下方线段或占位 + isLast + ? SizedBox(height: 6 + 5) + : Expanded(child: Container(width: 1, color: Colors.grey[300])), + ], + ), + ), + title: Text( + item['title'] ?? '', + style: TextStyle(color: textColor, fontSize: 15), + ), + subtitle: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (item['desc'] != null) + Text( + item['desc'], + style: TextStyle(color: textColor, fontSize: 13), + ), + ], + ), + ); + } + + String _checkStatusWithId(String id) { + for (Map item in stepList) { + if (item['id'] == id) { + return item['name']; + } + } + return ''; + } + + Widget _buildListItem(Map item) { + 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( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "安全检查记录", + style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold), + ), + ], + ), + const SizedBox(height: 8), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "检查状态: ${_checkStatusWithId(item['INSPECTION_STATUS'].toString())}", + ), + Text("检查类型: ${item['INSPECTION_TYPE_NAME'] ?? ''}"), + ], + ), + const SizedBox(height: 8), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text("检查人: ${item['INSPECTION_USER_NAME'] ?? ''}"), + ], + ), + const SizedBox(height: 8), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "检查发起人: ${item['INSPECTION_ORIGINATOR_NAME'] ?? ''}", + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ], + ), + const SizedBox(height: 8), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text("被检查人: ${item['INSPECTED_SITEUSER_NAME'] ?? ''}"), + ], + ), + const SizedBox(height: 8), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "检查时间: ${item['INSPECTION_TIME_START'] ?? ''}至${item['INSPECTION_TIME_END'] ?? ''}", + ), + ], + ), + const SizedBox(height: 8), + if (item['INSPECTION_STATUS'] == '0' || item['INSPECTION_STATUS'] == '1') + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + SizedBox(), + CustomButton( + text: '核实', + height: 32, + padding: EdgeInsets.symmetric(horizontal: 12), + margin: EdgeInsets.only(left: 0), + backgroundColor: Colors.blue, + onPressed: () => _goToDetail(item), + ), + ], + ), + ], + ), + ), + ), + ); + } + + // 显示底部选择器 + Future _showStepPicker() async { + if (stepList.isEmpty) { + ScaffoldMessenger.of( + context, + ).showSnackBar(SnackBar(content: Text('正在加载步骤数据,请稍后...'))); + if (stepList.isEmpty) { + ScaffoldMessenger.of( + context, + ).showSnackBar(SnackBar(content: Text('无法加载步骤数据'))); + return; + } + } + + // 创建选项列表 + final options = stepList.map((e) => e['name'] as String).toList(); + + // 显示底部选择器 + final choice = await BottomPicker.show( + context, + items: options, + itemBuilder: (item) => Text(item, textAlign: TextAlign.center), + initialIndex: sindex, + ); + + if (choice != null) { + // 找到选择的索引 + final newIndex = options.indexOf(choice); + if (newIndex != -1) { + setState(() { + sindex = newIndex; + }); + _search(); + } + } + } + + 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) { + final int lastDoneIndex = flowList.lastIndexWhere((e) => e['STATUS'] == 1); + + return Scaffold( + key: _scaffoldKey, + appBar: MyAppbar( + title: '${widget.flow}', + actions: [ + TextButton( + onPressed: _handleApply, + child: const Text( + '发起', + style: TextStyle(color: Colors.white, fontSize: 17), + ), + ), + + ], + ), + endDrawer: Drawer( + child: SafeArea( + child: + flowList.isEmpty + ? Center(child: Text('暂无流程图数据')) + : ListView.builder( + padding: const EdgeInsets.symmetric(vertical: 16), + itemCount: flowList.length + 1, // +1 用来放标题 + itemBuilder: (context, i) { + if (i == 0) { + return Padding( + padding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 8, + ), + child: Text( + '查看流程图', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ), + ); + } + final idx = i - 1; + final item = flowList[idx]; + final bool isFirst = idx == 0; + final bool isLast = idx == flowList.length - 1; + + return _buildFlowStepItem( + item: item, + isFirst: isFirst, + isLast: isLast, + ); + }, + ), + ), + ), + + body: SafeArea(child: Column( + children: [ + // Filter bar + Container( + color: Colors.white, + padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 8), + child: Row( + children: [ + // 底部弹窗选择器 + SizedBox( + width: 65, + child: TextButton( + onPressed: _showStepPicker, + style: TextButton.styleFrom( + padding: EdgeInsets.symmetric( + vertical: 12, + horizontal: 5, + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + '筛选', + style: TextStyle(color: Colors.black87, fontSize: 16), + ), + Icon(Icons.arrow_drop_down, color: Colors.grey), + ], + ), + ), + ), + 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/home/SafeCheck/DangeCheck/safeCheck_start_list_page.dart b/lib/pages/home/SafeCheck/DangeCheck/safeCheck_start_list_page.dart new file mode 100644 index 0000000..85fdf8a --- /dev/null +++ b/lib/pages/home/SafeCheck/DangeCheck/safeCheck_start_list_page.dart @@ -0,0 +1,473 @@ +import 'package:flutter/material.dart'; +import 'package:qhd_prevention/customWidget/toast_util.dart'; +import 'package:qhd_prevention/pages/home/SafeCheck/Start/safeCheck_start_detail.dart'; +import 'package:qhd_prevention/pages/home/tap/tabList/special_wrok/dh_work/dh_work_detai/hotwork_apply_detail.dart'; +import 'package:qhd_prevention/pages/my_appbar.dart'; +import 'package:qhd_prevention/tools/tools.dart'; +import 'package:qhd_prevention/customWidget/bottom_picker.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 SafecheckStartListPage extends StatefulWidget { + final String flow; + + const SafecheckStartListPage({Key? key, required this.flow}) + : super(key: key); + + @override + _SafecheckStartListPageState createState() => _SafecheckStartListPageState(); +} + +class _SafecheckStartListPageState extends State { + // Data and state variables + List list = []; + int currentPage = 1; + int rows = 10; + int totalPage = 1; + bool isLoading = false; + List stepList = [ + {'id': '', 'name': '请选择'}, + {'id': '0', 'name': '待检查人核实'}, + {'id': '1', 'name': '检查人核实中'}, + {'id': '2', 'name': '待被检查人确认'}, + {'id': '3', 'name': '待指派'}, + {'id': '4', 'name': '指派中'}, + {'id': '5', 'name': '指派完成'}, + {'id': '6', 'name': '检查待验收'}, + {'id': '7', 'name': '检查已验收'}, + {'id': '8', 'name': '已归档'}, + {'id': '-1', 'name': '检查人核实打回'}, + {'id': '-2', 'name': '被检查人申辩'}, + ]; + final TextEditingController _searchController = TextEditingController(); + + int sindex = 0; + String searchKeywords = ''; + + List> flowList = []; + 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 = { + 'INSPECTION_STATUS': sindex > 0 ? stepList[sindex]['id'] : '', + 'KEYWORDS': searchKeywords, + }; + final url = + '/app/safetyenvironmental/list?showCount=-1¤tPage=$currentPage'; + final response = await ApiService.getSafeCheckSearchList(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 _handleApply() { + // 处理申请按钮点击逻辑 + pushPage(SafecheckStartDetail(INSPECTION_ID: '', isEdit: true), context); + } + + /// 打开流程图 + Future _openFlowDrawer(String ID) async { + try { + final response = await ApiService.safeCheckFlowList(ID); + final List? newFlow = response['varList']; + if (newFlow == null || newFlow.isEmpty) { + ToastUtil.showNormal(context, '暂无流程图数据'); + return; + } + + setState(() { + flowList = List>.from(newFlow); + }); + Future.microtask(() { + _scaffoldKey.currentState?.openEndDrawer(); + }); + } catch (e) { + print('Error fetching flow data: $e'); + ScaffoldMessenger.of( + context, + ).showSnackBar(SnackBar(content: Text('获取流程图失败: $e'))); + } + } + + void _goToDetail(Map item) async { + + pushPage(SafecheckStartDetail(INSPECTION_ID: item['INSPECTION_ID'] ?? '', isEdit: false), context); + + setState(() { + _fetchData(); + }); + + } + + Widget _buildFlowStepItem({ + required Map item, + required bool isFirst, + required bool isLast, + }) { + bool status = item['active'] == item['order']; + // 依据状态设色 + final Color dotColor = status ? Colors.blue :Colors.grey; + final Color textColor = status ? Colors.blue :Colors.black54; + + return ListTile( + visualDensity: VisualDensity(vertical: -4), + + contentPadding: const EdgeInsets.symmetric(horizontal: 16), + leading: Container( + width: 24, + alignment: Alignment.center, + child: Column( + mainAxisSize: MainAxisSize.max, + children: [ + // 上方线段或占位 + isFirst + ? SizedBox(height: 6 + 5) + : Expanded(child: Container(width: 1, color: Colors.grey[300])), + // 圆点 + CircleAvatar(radius: 6, backgroundColor: dotColor), + // 下方线段或占位 + isLast + ? SizedBox(height: 6 + 5) + : Expanded(child: Container(width: 1, color: Colors.grey[300])), + ], + ), + ), + title: Text( + item['title'] ?? '', + style: TextStyle(color: textColor, fontSize: 15), + ), + subtitle: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (item['desc'] != null) + Text( + item['desc'], + style: TextStyle(color: textColor, fontSize: 13), + ), + ], + ), + ); + } + + String _checkStatusWithId(String id) { + for (Map item in stepList) { + if (item['id'] == id) { + return item['name']; + } + } + return ''; + } + + Widget _buildListItem(Map item) { + 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( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "安全检查记录", + style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold), + ), + ], + ), + const SizedBox(height: 8), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "检查状态: ${_checkStatusWithId(item['INSPECTION_STATUS'].toString())}", + ), + Text("检查类型: ${item['INSPECTION_TYPE_NAME'] ?? ''}"), + ], + ), + const SizedBox(height: 8), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text("检查人: ${item['INSPECTION_USER_NAME'] ?? ''}"), + ], + ), + const SizedBox(height: 8), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "检查发起人: ${item['INSPECTION_ORIGINATOR_NAME'] ?? ''}", + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ], + ), + const SizedBox(height: 8), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text("被检查人: ${item['INSPECTED_SITEUSER_NAME'] ?? ''}"), + ], + ), + const SizedBox(height: 8), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "检查时间: ${item['INSPECTION_TIME_START'] ?? ''}至${item['INSPECTION_TIME_END'] ?? ''}", + ), + ], + ), + const SizedBox(height: 8), + Row( + children: [ + CustomButton( + text: '流程图', + height: 35, + padding: EdgeInsets.symmetric(horizontal: 12), + margin: EdgeInsets.only(left: 0), + backgroundColor: Colors.blue, + onPressed: () => _openFlowDrawer(item['INSPECTION_ID']), + ), + SizedBox(width: 1), + ], + ), + ], + ), + ), + ), + ); + } + + // 显示底部选择器 + Future _showStepPicker() async { + if (stepList.isEmpty) { + ScaffoldMessenger.of( + context, + ).showSnackBar(SnackBar(content: Text('正在加载步骤数据,请稍后...'))); + if (stepList.isEmpty) { + ScaffoldMessenger.of( + context, + ).showSnackBar(SnackBar(content: Text('无法加载步骤数据'))); + return; + } + } + + // 创建选项列表 + final options = stepList.map((e) => e['name'] as String).toList(); + + // 显示底部选择器 + final choice = await BottomPicker.show( + context, + items: options, + itemBuilder: (item) => Text(item, textAlign: TextAlign.center), + initialIndex: sindex, + ); + + if (choice != null) { + // 找到选择的索引 + final newIndex = options.indexOf(choice); + if (newIndex != -1) { + setState(() { + sindex = newIndex; + }); + _search(); + } + } + } + + 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) { + final int lastDoneIndex = flowList.lastIndexWhere((e) => e['STATUS'] == 1); + + return Scaffold( + key: _scaffoldKey, + appBar: MyAppbar( + title: '${widget.flow}', + actions: [ + TextButton( + onPressed: _handleApply, + child: const Text( + '发起', + style: TextStyle(color: Colors.white, fontSize: 17), + ), + ), + + ], + ), + endDrawer: Drawer( + child: SafeArea( + child: + flowList.isEmpty + ? Center(child: Text('暂无流程图数据')) + : ListView.builder( + padding: const EdgeInsets.symmetric(vertical: 16), + itemCount: flowList.length + 1, // +1 用来放标题 + itemBuilder: (context, i) { + if (i == 0) { + return Padding( + padding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 8, + ), + child: Text( + '查看流程图', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ), + ); + } + final idx = i - 1; + final item = flowList[idx]; + final bool isFirst = idx == 0; + final bool isLast = idx == flowList.length - 1; + + return _buildFlowStepItem( + item: item, + isFirst: isFirst, + isLast: isLast, + ); + }, + ), + ), + ), + + body: SafeArea(child: Column( + children: [ + // Filter bar + Container( + color: Colors.white, + padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 8), + child: Row( + children: [ + // 底部弹窗选择器 + SizedBox( + width: 65, + child: TextButton( + onPressed: _showStepPicker, + style: TextButton.styleFrom( + padding: EdgeInsets.symmetric( + vertical: 12, + horizontal: 5, + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + '筛选', + style: TextStyle(color: Colors.black87, fontSize: 16), + ), + Icon(Icons.arrow_drop_down, color: Colors.grey), + ], + ), + ), + ), + 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/home/SafeCheck/Record/safeCheck_start_list_page.dart b/lib/pages/home/SafeCheck/Record/safeCheck_start_list_page.dart new file mode 100644 index 0000000..85fdf8a --- /dev/null +++ b/lib/pages/home/SafeCheck/Record/safeCheck_start_list_page.dart @@ -0,0 +1,473 @@ +import 'package:flutter/material.dart'; +import 'package:qhd_prevention/customWidget/toast_util.dart'; +import 'package:qhd_prevention/pages/home/SafeCheck/Start/safeCheck_start_detail.dart'; +import 'package:qhd_prevention/pages/home/tap/tabList/special_wrok/dh_work/dh_work_detai/hotwork_apply_detail.dart'; +import 'package:qhd_prevention/pages/my_appbar.dart'; +import 'package:qhd_prevention/tools/tools.dart'; +import 'package:qhd_prevention/customWidget/bottom_picker.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 SafecheckStartListPage extends StatefulWidget { + final String flow; + + const SafecheckStartListPage({Key? key, required this.flow}) + : super(key: key); + + @override + _SafecheckStartListPageState createState() => _SafecheckStartListPageState(); +} + +class _SafecheckStartListPageState extends State { + // Data and state variables + List list = []; + int currentPage = 1; + int rows = 10; + int totalPage = 1; + bool isLoading = false; + List stepList = [ + {'id': '', 'name': '请选择'}, + {'id': '0', 'name': '待检查人核实'}, + {'id': '1', 'name': '检查人核实中'}, + {'id': '2', 'name': '待被检查人确认'}, + {'id': '3', 'name': '待指派'}, + {'id': '4', 'name': '指派中'}, + {'id': '5', 'name': '指派完成'}, + {'id': '6', 'name': '检查待验收'}, + {'id': '7', 'name': '检查已验收'}, + {'id': '8', 'name': '已归档'}, + {'id': '-1', 'name': '检查人核实打回'}, + {'id': '-2', 'name': '被检查人申辩'}, + ]; + final TextEditingController _searchController = TextEditingController(); + + int sindex = 0; + String searchKeywords = ''; + + List> flowList = []; + 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 = { + 'INSPECTION_STATUS': sindex > 0 ? stepList[sindex]['id'] : '', + 'KEYWORDS': searchKeywords, + }; + final url = + '/app/safetyenvironmental/list?showCount=-1¤tPage=$currentPage'; + final response = await ApiService.getSafeCheckSearchList(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 _handleApply() { + // 处理申请按钮点击逻辑 + pushPage(SafecheckStartDetail(INSPECTION_ID: '', isEdit: true), context); + } + + /// 打开流程图 + Future _openFlowDrawer(String ID) async { + try { + final response = await ApiService.safeCheckFlowList(ID); + final List? newFlow = response['varList']; + if (newFlow == null || newFlow.isEmpty) { + ToastUtil.showNormal(context, '暂无流程图数据'); + return; + } + + setState(() { + flowList = List>.from(newFlow); + }); + Future.microtask(() { + _scaffoldKey.currentState?.openEndDrawer(); + }); + } catch (e) { + print('Error fetching flow data: $e'); + ScaffoldMessenger.of( + context, + ).showSnackBar(SnackBar(content: Text('获取流程图失败: $e'))); + } + } + + void _goToDetail(Map item) async { + + pushPage(SafecheckStartDetail(INSPECTION_ID: item['INSPECTION_ID'] ?? '', isEdit: false), context); + + setState(() { + _fetchData(); + }); + + } + + Widget _buildFlowStepItem({ + required Map item, + required bool isFirst, + required bool isLast, + }) { + bool status = item['active'] == item['order']; + // 依据状态设色 + final Color dotColor = status ? Colors.blue :Colors.grey; + final Color textColor = status ? Colors.blue :Colors.black54; + + return ListTile( + visualDensity: VisualDensity(vertical: -4), + + contentPadding: const EdgeInsets.symmetric(horizontal: 16), + leading: Container( + width: 24, + alignment: Alignment.center, + child: Column( + mainAxisSize: MainAxisSize.max, + children: [ + // 上方线段或占位 + isFirst + ? SizedBox(height: 6 + 5) + : Expanded(child: Container(width: 1, color: Colors.grey[300])), + // 圆点 + CircleAvatar(radius: 6, backgroundColor: dotColor), + // 下方线段或占位 + isLast + ? SizedBox(height: 6 + 5) + : Expanded(child: Container(width: 1, color: Colors.grey[300])), + ], + ), + ), + title: Text( + item['title'] ?? '', + style: TextStyle(color: textColor, fontSize: 15), + ), + subtitle: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (item['desc'] != null) + Text( + item['desc'], + style: TextStyle(color: textColor, fontSize: 13), + ), + ], + ), + ); + } + + String _checkStatusWithId(String id) { + for (Map item in stepList) { + if (item['id'] == id) { + return item['name']; + } + } + return ''; + } + + Widget _buildListItem(Map item) { + 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( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "安全检查记录", + style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold), + ), + ], + ), + const SizedBox(height: 8), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "检查状态: ${_checkStatusWithId(item['INSPECTION_STATUS'].toString())}", + ), + Text("检查类型: ${item['INSPECTION_TYPE_NAME'] ?? ''}"), + ], + ), + const SizedBox(height: 8), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text("检查人: ${item['INSPECTION_USER_NAME'] ?? ''}"), + ], + ), + const SizedBox(height: 8), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "检查发起人: ${item['INSPECTION_ORIGINATOR_NAME'] ?? ''}", + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ], + ), + const SizedBox(height: 8), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text("被检查人: ${item['INSPECTED_SITEUSER_NAME'] ?? ''}"), + ], + ), + const SizedBox(height: 8), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "检查时间: ${item['INSPECTION_TIME_START'] ?? ''}至${item['INSPECTION_TIME_END'] ?? ''}", + ), + ], + ), + const SizedBox(height: 8), + Row( + children: [ + CustomButton( + text: '流程图', + height: 35, + padding: EdgeInsets.symmetric(horizontal: 12), + margin: EdgeInsets.only(left: 0), + backgroundColor: Colors.blue, + onPressed: () => _openFlowDrawer(item['INSPECTION_ID']), + ), + SizedBox(width: 1), + ], + ), + ], + ), + ), + ), + ); + } + + // 显示底部选择器 + Future _showStepPicker() async { + if (stepList.isEmpty) { + ScaffoldMessenger.of( + context, + ).showSnackBar(SnackBar(content: Text('正在加载步骤数据,请稍后...'))); + if (stepList.isEmpty) { + ScaffoldMessenger.of( + context, + ).showSnackBar(SnackBar(content: Text('无法加载步骤数据'))); + return; + } + } + + // 创建选项列表 + final options = stepList.map((e) => e['name'] as String).toList(); + + // 显示底部选择器 + final choice = await BottomPicker.show( + context, + items: options, + itemBuilder: (item) => Text(item, textAlign: TextAlign.center), + initialIndex: sindex, + ); + + if (choice != null) { + // 找到选择的索引 + final newIndex = options.indexOf(choice); + if (newIndex != -1) { + setState(() { + sindex = newIndex; + }); + _search(); + } + } + } + + 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) { + final int lastDoneIndex = flowList.lastIndexWhere((e) => e['STATUS'] == 1); + + return Scaffold( + key: _scaffoldKey, + appBar: MyAppbar( + title: '${widget.flow}', + actions: [ + TextButton( + onPressed: _handleApply, + child: const Text( + '发起', + style: TextStyle(color: Colors.white, fontSize: 17), + ), + ), + + ], + ), + endDrawer: Drawer( + child: SafeArea( + child: + flowList.isEmpty + ? Center(child: Text('暂无流程图数据')) + : ListView.builder( + padding: const EdgeInsets.symmetric(vertical: 16), + itemCount: flowList.length + 1, // +1 用来放标题 + itemBuilder: (context, i) { + if (i == 0) { + return Padding( + padding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 8, + ), + child: Text( + '查看流程图', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ), + ); + } + final idx = i - 1; + final item = flowList[idx]; + final bool isFirst = idx == 0; + final bool isLast = idx == flowList.length - 1; + + return _buildFlowStepItem( + item: item, + isFirst: isFirst, + isLast: isLast, + ); + }, + ), + ), + ), + + body: SafeArea(child: Column( + children: [ + // Filter bar + Container( + color: Colors.white, + padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 8), + child: Row( + children: [ + // 底部弹窗选择器 + SizedBox( + width: 65, + child: TextButton( + onPressed: _showStepPicker, + style: TextButton.styleFrom( + padding: EdgeInsets.symmetric( + vertical: 12, + horizontal: 5, + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + '筛选', + style: TextStyle(color: Colors.black87, fontSize: 16), + ), + Icon(Icons.arrow_drop_down, color: Colors.grey), + ], + ), + ), + ), + 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/home/SafeCheck/SafeCheckFormView.dart b/lib/pages/home/SafeCheck/SafeCheckFormView.dart new file mode 100644 index 0000000..a695463 --- /dev/null +++ b/lib/pages/home/SafeCheck/SafeCheckFormView.dart @@ -0,0 +1,230 @@ +// safe_check_form_view.dart +import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; +import 'package:qhd_prevention/customWidget/ItemWidgetFactory.dart'; +import 'package:qhd_prevention/customWidget/custom_alert_dialog.dart'; +import 'package:qhd_prevention/customWidget/custom_button.dart'; +import 'package:qhd_prevention/customWidget/dotted_border_box.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/home/tap/item_list_widget.dart'; + +class SafeCheckFormView extends StatefulWidget { + const SafeCheckFormView({ + Key? key, + required this.form, + required this.isEdit, + required this.onHiddenShow, + }) : super(key: key); + + // === 数据 === + final Map form; + final bool isEdit; + + /// 显示隐患详情(点击) + final void Function(Map item, int idx) onHiddenShow; + + @override + _SafeCheckFormViewState createState() => _SafeCheckFormViewState(); +} + +class _SafeCheckFormViewState extends State { + /// 将 widget.form['situationList'] 转为 MultiTextFieldWithTitle 需要的 List + List _situationListToStrings() { + final List cur = List.from(widget.form['situationList'] ?? []); + if (cur.isEmpty) return ['']; // 保持至少一行,和 uni-app 行为一致 + return cur.map((e) { + if (e is Map && e['SITUATION'] != null) return e['SITUATION'].toString(); + if (e is String) return e; + return ''; + }).toList(); + } + Widget _personUnitItem(Map item, int index) { + return Stack( + children: [ + Container( + padding: EdgeInsets.all(5), + child: DottedBorderBox( + child: SizedBox( + child: Column( + children: [ + ItemListWidget.selectableLineTitleTextRightButton( + isRequired: false, + label: '${index + 1}.检查人员单位:', + isEditable: false, + onTapClean: () { + setState(() {}); + }, + text: item['INSPECTION_DEPARTMENT_NAME'] ?? '请选择', + + ), + Divider(), + ItemListWidget.selectableLineTitleTextRightButton( + isRequired: false, + label: '${index + 1}.检查人员:', + isEditable: false, + text: item['INSPECTION_USER_NAME'] ?? '请选择', + + ), + ], + ), + ), + ), + ), + + ], + ); + // 删除按钮(叠加在左上角) + } + + + @override + Widget build(BuildContext context) { + final form = widget.form; + final isEdit = widget.isEdit; + List inspectorList = widget.form['inspectorList']; + return SizedBox( + // padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12), + child: + Column( + children: [ + // 顶部表单区容器 + ItemListWidget.itemContainer( + Column( + children: [ + // 检查题目选择(示例复用你项目的 ListItemFactory) + ListItemFactory.createYesNoSection( + title: '检查题目:', + groupValue: (form['INSPECTION_CATEGORY']?.toString() ?? '').toLowerCase() == '安全' + ? true + : (form['INSPECTION_CATEGORY']?.toString() ?? '').toLowerCase() == '综合' + ? false + : null, + yesLabel: '安全', + noLabel: '综合', + isEdit: isEdit, + isRequired: true, + horizontalPadding: 5, + verticalPadding: 0, + text:'${form['INSPECTION_SUBJECT'] ?? ''}现场检查记录', + + onChanged: (val) { + // 如果需要把选择写回 form,可以在这里 setState 更新 widget.form + // setState(() { widget.form['INSPECTION_CATEGORY'] = val ? '安全' : '综合'; }); + }, + ), + + const Divider(), + ItemListWidget.singleLineTitleText( + label: '被检查单位:', + isEditable: false, + text: form['INSPECTED_DEPARTMENT_NAMES'] ??'', + ), + const Divider(), + + ItemListWidget.selectableLineTitleTextRightButton( + label: '被检查单位现场负责人:', + isEditable: isEdit, + text: form['INSPECTED_SITEUSER_NAME'] ?? '', + ), + const Divider(), + + ItemListWidget.singleLineTitleText( + label: '检查场所:', + isEditable: isEdit, + text: form['INSPECTION_PLACE'], + hintText: '请输入检查场所', + onChanged: (val) { + // 可选择写回 form + // setState(() { widget.form['INSPECTION_PLACE'] = val; }); + }, + ), + const Divider(), + + ItemListWidget.selectableLineTitleTextRightButton( + label: '检查类型:', + isEditable: isEdit, + text: form['INSPECTION_TYPE_NAME'] ?? '', + ), + const Divider(), + + ItemListWidget.selectableLineTitleTextRightButton( + label: '检查开始时间:', + isEditable: isEdit, + text: form['INSPECTION_TIME_START'] ?? '', + onTap: () { + }, + ), + const Divider(), + + ItemListWidget.selectableLineTitleTextRightButton( + label: '检查结束时间:', + isEditable: isEdit, + text: form['INSPECTION_TIME_END'] ?? '', + onTap: () { + }, + ), + + const Divider(), + + // MultiTextFieldWithTitle(检查情况),外部通过 onMultiTextsChanged 更新 form['situationList'] + MultiTextFieldWithTitle( + label: "检查情况", + isEditable: isEdit, + hintText: "请输入检查情况...", + texts: _situationListToStrings(), + onTextsChanged: (val) { + }, + ), + + const Divider(), + + ListItemFactory.headerTitle('检查人员'), + SizedBox(height: 10), + ListView.builder( + shrinkWrap: true, + physics: + const NeverScrollableScrollPhysics(), + itemCount: inspectorList.length, + itemBuilder: (context, index) { + return _personUnitItem( + inspectorList[index], + index, + ); + }, + ), + const Divider(), + + ItemListWidget.itemContainer( + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + ListItemFactory.headerTitle('发现问题'), + ], + ), + ), + + // HiddenListTable:隐患列表展示(外部通过 onHiddenShow/onHiddenRemove 处理具体交互) + HiddenListTable( + hiddenList: (form['hiddenList'] as List?) ?? [], + forbidEdit: false, + baseImgPath: ApiService.baseImgPath, + personSignImg: '', + personSignTime: '', + showHidden: widget.onHiddenShow, + removeHidden: (item, index){}, + context: context, + ), + + ], + ), + ), + + const SizedBox(height: 20), + ], + ), + + ); + } +} diff --git a/lib/pages/home/SafeCheck/Start/safeCheck_drawer_page.dart b/lib/pages/home/SafeCheck/Start/safeCheck_drawer_page.dart new file mode 100644 index 0000000..3951cbf --- /dev/null +++ b/lib/pages/home/SafeCheck/Start/safeCheck_drawer_page.dart @@ -0,0 +1,565 @@ +import 'dart:convert'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:intl/intl.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/department_picker_hidden_type.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/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 SafeCheckEditType { add, edit, see } + +class SafeCheckDrawerPage extends StatefulWidget { + const SafeCheckDrawerPage({ + super.key, + required this.editType, + this.initialHidden, + required this.toCheckUnitList, + }); + + final SafeCheckEditType editType; + final List toCheckUnitList; + + /// 可选:传入已有的 hiddenForm(用于 edit / see) + final Map? initialHidden; + + @override + State createState() => _SafeCheckDrawerPageState(); +} + +class _SafeCheckDrawerPageState extends State { + // Controllers + final TextEditingController _descCtl = TextEditingController(); + final TextEditingController _partCtl = TextEditingController(); + final TextEditingController _rectifyCtl = TextEditingController(); + + bool _isEdit = false; + + // Data lists + List _hazardLevels = []; + + // Selected / transient state + Map hiddenForm = {}; + + bool _rectifyClickable = true; // 是否可切换整改方式 + + @override + void initState() { + super.initState(); + _isEdit = widget.editType != SafeCheckEditType.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(() { + final list = List.from(res['list'] ?? []); + List rList = []; + for (Map item in list) { + if (item['NAME'] != '重大隐患' && item['NAME'] != '轻微隐患') { + rList.add(item); + } + } + _hazardLevels = rList; + }); + } + } 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(); + } + + ///隐患发现时间 + Future _chooseDatePicker() async { + DateTime? picked = await BottomDateTimePicker.showDate(context); + if (picked != null) { + setState(() { + hiddenForm['CREATTIME'] = DateFormat('yyyy-MM-dd HH:mm').format(picked); + }); + } + FocusHelper.clearFocus(context); + + } + + /// 隐患发现人 + Future _pickDangerPerson() async { + final choice = await BottomPicker.show( + context, + items: [SessionService.instance.username as String], + itemBuilder: (item) => Text(item, textAlign: TextAlign.center), + initialIndex: 0, + ); + FocusHelper.clearFocus(context); + + if (choice != null) { + setState(() { + hiddenForm['CREATOR_INDEX'] = 0; + hiddenForm['CREATOR'] = SessionService.instance.loginUserId; + hiddenForm['CREATOR_NAME'] = SessionService.instance.username; + }); + } + } + + @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, + ), + Container( + color: Colors.white, + padding: EdgeInsets.symmetric(horizontal: 3), + child: Column( + children: [ + ItemListWidget.selectableLineTitleTextRightButton( + label: '隐患级别:', + isEditable: _isEdit, + text: hiddenForm['HIDDENLEVEL_NAME'] ?? '', + onTap: () { + _pickHazardLevel(); + }, + ), + SizedBox(height: 12), + + ItemListWidget.selectableLineTitleTextRightButton( + label: '隐患类型:', + isEditable: _isEdit, + text: hiddenForm['HIDDENTYPE_NAME'] ?? '', + onTap: () { + _pickHazardType(); + }, + ), + SizedBox(height: 12), + + ItemListWidget.selectableLineTitleTextRightButton( + label: '隐患发现时间:', + isEditable: _isEdit, + text: hiddenForm['CREATTIME'] ?? '', + onTap: () { + _chooseDatePicker(); + }, + ), + SizedBox(height: 12), + ItemListWidget.selectableLineTitleTextRightButton( + label: '隐患发现人:', + isEditable: _isEdit, + text: hiddenForm['CREATOR_NAME'] ?? '', + onTap: () { + _pickDangerPerson(); + }, + ), + ], + ), + ), + + 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 ----------------- + 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'] ?? '0'; + _rectifyClickable = + (found['DICTIONARIES_ID']?.toString() ?? '') != + '5ff9daf78e9a4fb1b40d77980656799d'; + if (!_rectifyClickable) { + hiddenForm['RECTIFICATIONTYPE'] = '2'; + } + }); + } + FocusHelper.clearFocus(context); + } + + 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; + hiddenForm['HIDDENTYPE_NAME'] = names.join('/'); + + }); + FocusHelper.clearFocus(context); + + } catch (_) {} + }, + ), + ); + } + + /// 保存隐患(这里只返回 hiddenForm 给上层调用者) + void saveHidden() { + // update hiddenForm from controllers before returning + hiddenForm['HIDDENDESCR'] = _descCtl.text.trim(); + hiddenForm['HIDDENPART'] = _partCtl.text.trim(); + hiddenForm['RECTIFYDESCR'] = _rectifyCtl.text.trim(); + if ((hiddenForm['hiddenImgs'] as List).isEmpty) { + return ToastUtil.showNormal(context, '请上传隐患图片'); + } + // basic validation example + if ((hiddenForm['HIDDENDESCR'] ?? '').toString().trim().isEmpty) { + return ToastUtil.showNormal(context, '输入隐患描述'); + } + if ((hiddenForm['HIDDENPART']?? '').toString().trim().isEmpty) { + return ToastUtil.showNormal(context, '输入隐患部位'); + } + if ((hiddenForm['HIDDENLEVEL'] ?? '').toString().trim().isEmpty) { + return ToastUtil.showNormal(context, '请选择隐患级别'); + } + if ((hiddenForm['HIDDENTYPE'] ?? '').toString().trim().isEmpty) { + return ToastUtil.showNormal(context, '请选择隐患类型'); + } + if ((hiddenForm['CREATTIME'] ?? '').toString().trim().isEmpty) { + return ToastUtil.showNormal(context, '请选择隐患发现时间'); + } + if ((hiddenForm['CREATOR'] ?? '').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(); + } + } + + + +} diff --git a/lib/pages/home/SafeCheck/Start/safeCheck_start_detail.dart b/lib/pages/home/SafeCheck/Start/safeCheck_start_detail.dart index e7df6ab..d297969 100644 --- a/lib/pages/home/SafeCheck/Start/safeCheck_start_detail.dart +++ b/lib/pages/home/SafeCheck/Start/safeCheck_start_detail.dart @@ -1,4 +1,5 @@ import 'dart:convert'; +import 'dart:io'; import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; @@ -6,6 +7,8 @@ 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/department_person_picker.dart'; +import 'package:qhd_prevention/customWidget/department_picker.dart'; import 'package:qhd_prevention/customWidget/dotted_border_box.dart'; import 'package:qhd_prevention/customWidget/picker/CupertinoDatePicker.dart'; import 'package:qhd_prevention/customWidget/single_image_viewer.dart'; @@ -15,7 +18,10 @@ import 'package:qhd_prevention/pages/KeyProjects/SafeCheck/custom/MultiTextField 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/SafeCheck/Start/safeCheck_drawer_page.dart'; import 'package:qhd_prevention/pages/home/tap/item_list_widget.dart'; +import 'package:qhd_prevention/pages/home/tap/tabList/special_wrok/dh_work/dh_work_detai/hotwork_apply_detail.dart'; +import 'package:qhd_prevention/pages/mine/mine_sign_page.dart'; import 'package:qhd_prevention/pages/my_appbar.dart'; import 'package:qhd_prevention/tools/tools.dart'; @@ -34,6 +40,9 @@ class SafecheckStartDetail extends StatefulWidget { } class _SafecheckStartDetailState extends State { + + String msg = 'add'; + bool _isEdit = false; /// 被检查单位负责人 late List personList = []; @@ -46,6 +55,9 @@ class _SafecheckStartDetailState extends State { bool? chooseTitleType = null; late List inspectorList = []; + // 存储各单位的人员列表 + final Map>> _personCache = {}; + // 存储多行输入的内容 List multiTexts = []; @@ -54,16 +66,16 @@ class _SafecheckStartDetailState extends State { List delHiddens = []; List delHiddenFiles = []; - // rules 格式: [{ 'name': 'INSPECTION_CATEGORY', 'message': '请填写检查题目' }, ...] + List signImages = []; + List signTimes = []; // 签字时间列表 List> rules = [ - {'name': 'INSPECTION_CATEGORY', 'message': '请选择检查题目'}, - {'name': 'UNITS_ID', 'message': '请选择被检查单位'}, - {'name': 'PERSONNELMANAGEMENT_ID', 'message': '请选择被检查单位现场负责人'}, + {'name': 'INSPECTION_SUBJECT', 'message': '请选择检查题目'}, + {'name': 'INSPECTED_DEPARTMENT_ID', 'message': '请选择被检查单位'}, + {'name': 'INSPECTED_SITEUSER_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 = { @@ -75,6 +87,8 @@ class _SafecheckStartDetailState extends State { 'RECTIFICATIONTYPE': '2', }, ], + 'INSPECTION_STATUS': '0', // 状态(0.暂存 1.检查人待确认、2.被检查人待确认、3.归档 -1.检查人异议打回 -2被检查人申辩) + 'INSPECTION_USER_SIGN_TIME': '', 'INSPECTION_USER_OPINION': '', }; @@ -82,11 +96,28 @@ class _SafecheckStartDetailState extends State { @override void initState() { super.initState(); + _isEdit = widget.isEdit; form['INSPECTION_ID'] = widget.INSPECTION_ID; form['hiddenList'] = []; - WidgetsBinding.instance.addPostFrameCallback((_) { - _getData(); - }); + if (widget.INSPECTION_ID.isNotEmpty) { + msg = 'edit'; + } + if (_isEdit) { + inspectorList.add({ + 'INSPECTION_INSPECTOR_ID': '', //检查人员主键 + 'INSPECTION_DEPARTMENT_ID': SessionService.instance.loginUser?['DEPARTMENT_ID'] ?? '', //检查人员部门ID + 'INSPECTION_DEPARTMENT_NAME': SessionService.instance.loginUser?['DEPARTMENT_NAME'] ?? '', + 'INSPECTION_USER_ID': SessionService.instance.loginUserId, //检查人员ID + 'INSPECTION_USER_INDEX': '', + 'INSPECTION_USER_NAME': SessionService.instance.username, + }); + form['inspectorList'] = inspectorList; + _getAllRequest(); + } else { + WidgetsBinding.instance.addPostFrameCallback((_) { + _getData(); + }); + } } Future _getData() async { @@ -102,7 +133,12 @@ class _SafecheckStartDetailState extends State { FormUtils.hasValue(form, 'inspectorList') ? form['inspectorList'] : []; + if (FormUtils.hasValue(form, 'INSPECTION_STATUS') && int.parse(form['INSPECTION_STATUS']) < 1) { + _isEdit = true; + } + chooseTitleType = form['INSPECTION_SUBJECT'] == '安全' ? true : false; _syncMultiTextsFromForm(); + }); } catch (e, st) { print('加载单条数据失败: $e\n$st'); @@ -111,24 +147,15 @@ class _SafecheckStartDetailState extends State { } } } - - Future _choosePerson() async { - final choice = await BottomPicker.show( - context, - items: personList.map((val) => val['NAME'] as String).toList(), - itemBuilder: (item) => Text(item, textAlign: TextAlign.center), - initialIndex: 0, - ); - if (choice != null) { - // 用户点击确定并选择了 choice + Future _getAllRequest() async { + try { + final typeListData = await ApiService.getSafeCheckTypeList(); + if (!mounted) return; setState(() { - form['INSPECTED_SITEUSER_INDEX'] = choice; - final data = FormUtils.findMapForKeyValue(personList, 'NAME', choice); - form['INSPECTED_SITEUSER_ID'] = data['USER_ID']; - form['INSPECTED_SITEUSER_NAME'] = data['NAME']; - - FocusHelper.clearFocus(context); + typeList = jsonDecode(typeListData['zTreeNodes'] ?? '[]'); }); + } catch (e) { + print('加载 typeList 失败: $e'); } } @@ -155,12 +182,12 @@ class _SafecheckStartDetailState extends State { try { final result = await openCustomDrawer( context, - SafeDrawerPage( + SafeCheckDrawerPage( initialHidden: hiddenForm, editType: - widget.isEdit - ? (index < 0 ? SafeEditType.add : SafeEditType.edit) - : SafeEditType.see, + _isEdit + ? (index < 0 ? SafeCheckEditType.add : SafeCheckEditType.edit) + : SafeCheckEditType.see, toCheckUnitList: toCheckUnitList, ), ); @@ -170,6 +197,7 @@ class _SafecheckStartDetailState extends State { if (index < 0) { // 新增 form['hiddenList'].add(result); + print(form); } else { // 修改 form['hiddenList'][index] = result; @@ -271,12 +299,11 @@ class _SafecheckStartDetailState extends State { // ------------ 提交入口 ------------ Future _submit() async { - if (!widget.isEdit) { + if (!_isEdit) { Navigator.of(context).pop(); - return; } - bool required = true; + // 基于 rules 验证 for (final r in rules) { final name = r['name'] ?? ''; @@ -285,12 +312,9 @@ class _SafecheckStartDetailState extends State { 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++) { @@ -303,10 +327,11 @@ class _SafecheckStartDetailState extends State { } // 检查 inspectorList 中是否有重复 INSPECTION_USER_ID - final List> inspectors = form['inspectorList'] ?? []; + final List> inspectors = + ((form['inspectorList'] as List?) ?? []).map((e) => Map.from(e)).toList(); final seenIds = {}; for (final it in inspectors) { - final id = (it as Map)['INSPECTION_USER_ID']?.toString() ?? ''; + final id = (it['INSPECTION_USER_ID'] ?? '').toString(); if (id.isNotEmpty) { if (seenIds.contains(id)) { LoadingDialogHelper.hide(); @@ -314,10 +339,15 @@ class _SafecheckStartDetailState extends State { return; } seenIds.add(id); + } else { + // 如果某个检查人没选 ID,也视为错误(对应 uniapp 的校验) + LoadingDialogHelper.hide(); + ToastUtil.showNormal(context, '有检查人未选择数据,请选择'); + return; } } - //根据 hiddenList 构建需要上传的文件数组 + // 根据 hiddenList 构建需要上传的文件数组 final origHiddenList = (form['hiddenList'] as List?) ?? []; final List>> hiddenFilesPerHidden = []; @@ -329,16 +359,12 @@ class _SafecheckStartDetailState extends State { 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'] ?? '', - }); + fileList.add({'type': 3, 'FILEPATH': img['FILEPATH'] ?? img['path'] ?? ''}); } } } @@ -352,10 +378,7 @@ class _SafecheckStartDetailState extends State { } else if (img is Map) { final hasId = (img['IMGFILES_ID'] ?? '').toString().isNotEmpty; if (!hasId) { - fileList.add({ - 'type': 4, - 'FILEPATH': img['FILEPATH'] ?? img['path'] ?? '', - }); + fileList.add({'type': 4, 'FILEPATH': img['FILEPATH'] ?? img['path'] ?? ''}); } } } @@ -369,10 +392,7 @@ class _SafecheckStartDetailState extends State { } else if (v is Map) { final hasId = (v['IMGFILES_ID'] ?? '').toString().isNotEmpty; if (!hasId) { - fileList.add({ - 'type': 102, - 'FILEPATH': v['FILEPATH'] ?? v['path'] ?? '', - }); + fileList.add({'type': 102, 'FILEPATH': v['FILEPATH'] ?? v['path'] ?? ''}); } } } @@ -384,9 +404,7 @@ class _SafecheckStartDetailState extends State { 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); + return (item['INSPECTION_USER_ID'] ?? '') == (loginUser['USER_ID'] ?? loginUserId); }); if (idx < 0) { inspectors.add({ @@ -398,11 +416,12 @@ class _SafecheckStartDetailState extends State { 'INSPECTION_USER_NAME': loginUser['NAME'] ?? '', }); } - + printLongString(origHiddenList.toString()); + String HIDDENJSON = jsonEncode(origHiddenList); // 准备 form 字段(JSON 字符串等) form['INSPECTORJSON'] = jsonEncode(inspectors); form['SITUATIONJSON'] = jsonEncode(situations); - form['HIDDENJSON'] = jsonEncode(origHiddenList); + form['HIDDENJSON'] = HIDDENJSON; form['delInspectors'] = delInspectors.join(','); form['delSituations'] = delSituations.join(','); form['delHiddens'] = delHiddens.join(','); @@ -410,99 +429,162 @@ class _SafecheckStartDetailState extends State { 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') { + // 若需要签字(INSPECTION_USER_OPINION)则使用 multipart 带签字提交 + final needSignature = (form['INSPECTION_USER_OPINION'] ?? '').toString().isNotEmpty; + Map? res; + + if (needSignature) { + if (signImages.isEmpty) { + LoadingDialogHelper.hide(); + ToastUtil.showNormal(context, '您还未签字'); + return; + } + final signPath = signImages[0]; + // 请求接口 + res = await ApiService.SafeCheckStartGoEditMsg(signPath, msg, form); + } else { + // 无签字不需要带上图片 + res = await ApiService.SafeCheckStartGoEditMsg('', msg, form); + } + + 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; - } + // 先处理 punishForm(如果每个隐患有 punishForm) + final returnedHiddenMapList = + returnedHiddenList.map((e) => Map.from(e)).toList(); - // 若每个 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], - ); + for (var i = 0; i < returnedHiddenMapList.length; i++) { + if (i < origHiddenList.length) { + final hidden = Map.from(origHiddenList[i] as Map); final punishForm = hidden['punishForm']; - if (punishForm != null) { - final hid = (returnedHiddenList[i]['HIDDEN_ID'] ?? '').toString(); - punishForm['HIDDEN_ID'] = hid; - // await 调用罚单提交(在下面实现) - await fnSubmit(Map.from(punishForm)); + if (punishForm != null && punishForm is Map) { + final hid = returnedHiddenMapList[i]['HIDDEN_ID']?.toString() ?? ''; + if (hid.isNotEmpty) { + punishForm['HIDDEN_ID'] = hid; + try { + await fnSubmit(Map.from(punishForm)); + } catch (e) { + // 如果罚单提交失败,你可以 choose 抛出或记录后继续 + print('罚单提交失败: $e'); + } + } } } } - // 上传所有附件(按隐患对应的 hiddenId) - // 把返回的 hiddenList 转为 Map 列表,确保索引一致 - final returnedHiddenMapList = - returnedHiddenList - .map((e) => Map.from(e)) - .toList(); - await uploadHiddenFiles(hiddenFilesPerHidden, returnedHiddenMapList); + if (hasFiles) { + 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); + final msgErr = res != null ? (res['msg'] ?? res['msaesge'] ?? res['message'] ?? '提交失败') : '提交失败'; + ToastUtil.showNormal(context, msgErr.toString()); } } catch (e) { LoadingDialogHelper.hide(); ToastUtil.showNormal(context, '提交异常:$e'); + } finally { } } - // ========== 上传附件的方法 ========== +// ---------- 上传附件 ---------- Future uploadHiddenFiles( - List>> hiddenFilesPerHidden, - List> returnedHiddenList, - ) async { + 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() ?? '') - : ''; + 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)'); + + bool uploaded = false; + int tries = 0; + while (!uploaded && tries < 3) { + tries++; + try { + // 直接调用你提供的 addNormalImgFiles,第二个参数传 { 'TYPE': type, 'FOREIGN_KEY': hiddenId } + final resp = await ApiService.addNormalImgFiles(filePath, { + 'TYPE': type, + 'FOREIGN_KEY': hiddenId, + }); + if (resp != null && (resp['result'] == 'success' || resp['result'] == 'ok')) { + uploaded = true; + // 可记录 resp 里的返回信息 + } else { + // 失败但继续重试 + await Future.delayed(Duration(milliseconds: 500)); + } + } catch (e) { + print('上传文件失败 (尝试 $tries): $e -- path=$filePath'); + await Future.delayed(Duration(milliseconds: 500)); + } + } + + if (!uploaded) { + // 若最终仍未上传成功:记录或提示(这里打印日志) + print('最终上传失败: $filePath -> hiddenId=$hiddenId'); + // 你也可以按需求抛出异常中止整个提交 } } } } + Future _sign() async { + final path = await Navigator.push( + context, + MaterialPageRoute(builder: (context) => MineSignPage()), + ); + if (path != null) { + final now = DateFormat('yyyy-MM-dd HH:mm').format(DateTime.now()); - // ========== 罚单提交方法(对应原 fnSubmit) ========== + setState(() { + signImages.add(path); + signTimes.add(now); + FocusHelper.clearFocus(context); + }); + } + } + + /// 移除检查人员 + void _removeCheckPerson(int index) async { + await showDialog( + context: context, + builder: + (_) => CustomAlertDialog( + title: '提示', + content: '确定移除检查人员吗', + cancelText: '取消', + confirmText: '确定', + onConfirm: () async { + setState(() { + inspectorList.removeAt(index); + form['inspectorList'] = inspectorList; + }); + }, + ), + ); + } + + // ========== 罚单提交方法 Future fnSubmit(Map? ordForm) async { if (ordForm == null) return false; @@ -549,32 +631,585 @@ class _SafecheckStartDetailState extends State { } Widget _personUnitItem(Map item, int index) { - return DottedBorderBox( + return Stack( + children: [ + Container( + padding: EdgeInsets.all(5), + child: DottedBorderBox( + child: SizedBox( + child: Column( + children: [ + ItemListWidget.selectableLineTitleTextRightButton( + isRequired: index == 0 ? false : _isEdit, + label: '${index + 1}.检查人员单位:', + isEditable: index == 0 ? false : _isEdit, + onTapClean: () { + setState(() {}); + }, + text: item['INSPECTION_DEPARTMENT_NAME'] ?? '请选择', + onTap: () { + chooseUnitHandle('$index'); + }, + ), + Divider(), + ItemListWidget.selectableLineTitleTextRightButton( + isRequired: index == 0 ? false :_isEdit, + label: '${index + 1}.检查人员:', + isEditable: index == 0 ? false : _isEdit, + text: item['INSPECTION_USER_NAME'] ?? '请选择', + onTap: () { + choosePersonHandle('$index'); + }, + ), + ], + ), + ), + ), + ), + if (index > 0 && _isEdit) + Positioned( + top: 0, + left: 0, + child: GestureDetector( + onTap: () => _removeCheckPerson(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), + ), + ), + ), + ], + ); + // 删除按钮(叠加在左上角) + } + + Widget _personSignItem(Map item, int index) { + return SizedBox( child: Column( children: [ - ItemListWidget.selectableLineTitleTextRightButton( - isRequired: widget.isEdit, - label: '${index + 1}.检查人员单位:', - isEditable: widget.isEdit, - onTapClean: () { - setState(() {}); - }, - text: item['INSPECTION_DEPARTMENT_NAME'] ?? '请选择', - onTap: () {}, + ItemListWidget.multiLineTitleTextField( + label: '检查人意见:', + isEditable: _isEdit, + text: item['opinion'], + hintText: '请输入作业负责人意见', ), - Divider(), - ItemListWidget.selectableLineTitleTextRightButton( - isRequired: widget.isEdit, - label: 'type.personName', - isEditable: widget.isEdit, - text: item['INSPECTION_USER_NAME'] ?? '请选择', - onTap: () {}, + const Divider(), + + ItemListWidget.OneRowImageTitle( + label: '检查人签字', + text: item['time'], + onTapCallBack: (path) { + presentOpaque(SingleImageViewer(imageUrl: path), context); + }, + imgPath: item['img'], ), ], ), ); } + void _delHiddenForm(Map item, int index) async{ + final ok = await CustomAlertDialog.showConfirm( + context, + title: '提示', + content: '确定移除发现问题吗?', + ); + if (ok) { + setState(() { + /* 删除逻辑 */ + final List rawHiddenList = (form['hiddenList'] as List?) ?? []; + final List> hiddenList = + rawHiddenList.map((e) => Map.from(e as Map)).toList(); + hiddenList.removeAt(index); + form['hiddenList'] = hiddenList; + }); + FocusHelper.clearFocus(context); + } + } + /// 弹出单位选择 + void chooseUnitHandle(String typeStr) { + showModalBottomSheet( + context: context, + isScrollControlled: true, + barrierColor: Colors.black54, + backgroundColor: Colors.transparent, + builder: + (_) => DepartmentPicker( + onSelected: (id, name) async { + setState(() { + if (typeStr == '被检查单位') { + form['INSPECTED_DEPARTMENT_ID'] = id; + form['INSPECTED_DEPARTMENT_NAME'] = name; + } else { + final int index = int.parse(typeStr); + inspectorList[index]['INSPECTION_DEPARTMENT_NAME'] = name; + inspectorList[index]['INSPECTION_DEPARTMENT_ID'] = id; + } + }); + FocusHelper.clearFocus(context); + _getPersonListForUnitId(typeStr); + }, + ), + ).then((_) {}); + } + + Future _getPersonListForUnitId(String typeStr) async { + String unitId = form['INSPECTED_DEPARTMENT_ID'] ?? ''; + if (typeStr != '被检查单位') { + final int index = int.parse(typeStr); + unitId = inspectorList[index]['INSPECTION_DEPARTMENT_ID'] ?? ''; + } + + // 拉取该单位的人员列表并缓存 + final result = await ApiService.getListTreePersonList(unitId); + setState(() { + _personCache[typeStr] = List>.from( + result['userList'] as List, + ); + }); + FocusHelper.clearFocus(context); + + } + + /// 弹出人员选择,需先选择单位 + void choosePersonHandle(String typeStr) async { + final personList = _personCache[typeStr]; + if (!FormUtils.hasValue(_personCache, typeStr)) { + ToastUtil.showNormal(context, '请先选择单位'); + return; + } + + DepartmentPersonPicker.show( + context, + personsData: personList!, + onSelectedWithIndex: (userId, name, index) { + setState(() { + if (typeStr == '被检查单位') { + form['INSPECTED_SITEUSER_INDEX'] = index; + form['INSPECTED_SITEUSER_ID'] = userId; + form['INSPECTED_SITEUSER_NAME'] = name; + } else { + final int i = int.tryParse(typeStr.trim()) as int; + Map item = inspectorList[i]; + item['INSPECTION_USER_INDEX'] = '$index'; + item['INSPECTION_USER_NAME'] = name; + item['INSPECTION_USER_ID'] = userId; + } + }); + FocusHelper.clearFocus(context); + + }, + ).then((_) {}); + + } + + /// 添加检查人员 + void _addInspector() { + setState(() { + inspectorList.add({ + 'INSPECTION_INSPECTOR_ID': '', //检查人员主键 + 'INSPECTION_DEPARTMENT_ID': '', //检查人员部门ID + 'INSPECTION_DEPARTMENT_NAME': '', + 'INSPECTION_USER_ID': '', //检查人员ID + 'INSPECTION_USER_INDEX': '', + 'INSPECTION_USER_NAME': '', + }); + form['inspectorList'] = inspectorList; + }); + } + Widget _signListWidget() { + return Column( + children: + signImages.map((path) { + return Column( + children: [ + const SizedBox(height: 10), + const Divider(), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + GestureDetector( + child: // 用一个 ConstrainedBox 限制最大尺寸,并改为 BoxFit.contain + ConstrainedBox( + constraints: const BoxConstraints( + maxWidth: 200, + maxHeight: 150, + ), + child: Image.file( + File(path), + // 改为完整显示 + fit: BoxFit.contain, + ), + ), + onTap: () { + presentOpaque( + SingleImageViewer(imageUrl: path), + context, + ); + }, + ), + Column( + children: [ + Container( + padding: const EdgeInsets.only(right: 5), + child: CustomButton( + text: 'X', + height: 30, + padding: const EdgeInsets.symmetric(horizontal: 10), + backgroundColor: Colors.red, + onPressed: () { + setState(() { + signImages.remove(path); + }); + }, + ), + ), + const SizedBox(height: 80), + ], + ), + ], + ), + ], + ); + }).toList(), + ); + } + Widget _mainWidget() { + return ListView( + children: [ + Column( + children: [ + ItemListWidget.itemContainer( + horizontal: 0, + Column( + children: [ + ListItemFactory.createYesNoSection( + title: '检查题目:', + groupValue: chooseTitleType, + yesLabel: '安全', + noLabel: '综合', + isEdit: _isEdit, + isRequired: true, + horizontalPadding: 5, + verticalPadding: 0, + text: + '${form['INSPECTION_SUBJECT'] ?? ''}现场检查记录', + onChanged: (val) { + setState(() { + chooseTitleType = val; + form['INSPECTION_SUBJECT'] = + val == true ? '安全' : '综合'; + }); + }, + ), + const Divider(), + ItemListWidget.selectableLineTitleTextRightButton( + isRequired: _isEdit, + label: '被检查单位', + isEditable: _isEdit, + + text: + _isEdit + ? form['INSPECTED_DEPARTMENT_NAME'] ?? + '请选择' + : form['INSPECTED_DEPARTMENT_NAMES'] ?? + '', + onTap: () => chooseUnitHandle('被检查单位'), + ), + + const Divider(), + + ItemListWidget.selectableLineTitleTextRightButton( + isRequired: _isEdit, + label: '被检查单位现场负责人', + isEditable: _isEdit, + + text: + form['INSPECTED_SITEUSER_NAME'] ?? '请选择', + onTap: () => choosePersonHandle('被检查单位'), + ), + const Divider(), + + ItemListWidget.singleLineTitleText( + label: '检查场所:', + isEditable: _isEdit, + text: form['INSPECTION_PLACE'], + hintText: '请输入检查场所', + onChanged: (val) { + setState(() { + form['INSPECTION_PLACE'] = val; + }); + }, + ), + const Divider(), + + ItemListWidget.selectableLineTitleTextRightButton( + label: '检查类型:', + onTap: () { + _chooseType(); + }, + isEditable: _isEdit, + text: form['INSPECTION_TYPE_NAME'] ?? '', + ), + const Divider(), + + ItemListWidget.selectableLineTitleTextRightButton( + label: '检查开始时间:', + isEditable: _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: _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); + } + }, + ), + if (!_isEdit) + Column( + children: [ + const Divider(), + ItemListWidget.selectableLineTitleTextRightButton( + label: '记录填写时间:', + isEditable: _isEdit, + text: form['CREATTIME'] ?? '', + ), + ], + ), + + const Divider(), + MultiTextFieldWithTitle( + label: "检查情况:", + // 更合适的标题 + isEditable: _isEdit, + // 使用父组件的编辑状态 + hintText: "请输入检查情况...", + texts: multiTexts, + onTextsChanged: (texts) { + setState(() { + multiTexts = texts; // 保存到状态变量 + form['situationList'] = + _stringsToSituationList(texts); + }); + }, + ), + + const Divider(), + ItemListWidget.itemContainer( + Column( + children: [ + Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + ListItemFactory.headerTitle('检查人员'), + if (_isEdit) + CustomButton( + text: " 添加 ", + height: 30, + padding: + const EdgeInsets.symmetric( + vertical: 2, + horizontal: 5, + ), + backgroundColor: Colors.blue, + onPressed: () { + _addInspector(); + }, + ), + ], + ), + SizedBox(height: 10), + ListView.builder( + shrinkWrap: true, + physics: + const NeverScrollableScrollPhysics(), + itemCount: inspectorList.length, + itemBuilder: (context, index) { + return _personUnitItem( + inspectorList[index], + index, + ); + }, + ), + ], + ), + ), + + const Divider(), + ItemListWidget.itemContainer( + Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + ListItemFactory.headerTitle('发现问题'), + if (_isEdit) + CustomButton( + text: " 添加 ", + height: 30, + padding: const EdgeInsets.symmetric( + vertical: 2, + horizontal: 5, + ), + backgroundColor: Colors.blue, + onPressed: () { + _openDrawer( + {}, + -1, + ); // 添加括号和 await + FocusHelper.clearFocus(context); + }, + ), + ], + ), + ), + HiddenListTable( + hiddenList: form['hiddenList'] ?? [], + forbidEdit: _isEdit, + baseImgPath: ApiService.baseImgPath, + personSignImg: form['PERSON_SIGN_IMG'] ?? '', + personSignTime: + form['PERSON_SIGN_TIME'] ?? '', + showHidden: (item, idx) { + _openDrawer(item, idx); + + }, + removeHidden: (item, idx) { + _delHiddenForm(item, idx); + }, + context: context, + ), + if (_isEdit) + ItemListWidget.multiLineTitleTextField( + label: '核实意见', + isEditable: _isEdit, + isRequired: false, + hintText: '检查人意见', + onChanged: (val) { + form['INSPECTION_USER_OPINION'] = val; + }, + text: form['INSPECTION_USER_OPINION'] ?? '', + ), + if (form['INSPECTION_USER_OPINION'].toString().isNotEmpty && _isEdit) + ItemListWidget.itemContainer(Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + ListItemFactory.headerTitle('签字'), + CustomButton( + text: '手写签字', + height: 36, + backgroundColor: Colors.green, + onPressed: () { + _sign(); + }, + ), + ], + ),), + if (signImages.isNotEmpty) _signListWidget(), + + if (!_isEdit) + Column( + children: [ + const Divider(), + if (inspectorList.isNotEmpty) + Column( + children: [ + ItemListWidget.selectableLineTitleTextRightButton( + label: '检查人员核实结果', + isEditable: _isEdit, + text: ' ', + ), + SizedBox(height: 5,), + ListView.builder( + shrinkWrap: true, + physics: + const NeverScrollableScrollPhysics(), + itemCount: inspectorList.length, + itemBuilder: (context, index) { + return _personSignItem( + inspectorList[index], + index, + ); + }, + ), + ], + ), + + ], + ), + ], + ), + ), + SizedBox(height: 20), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + if (_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: _isEdit ? '提交' : '返回', + textStyle: TextStyle( + color: Colors.white, + fontSize: 17, + ), + backgroundColor: Colors.blue, + onPressed: _submit, + ), + ), + ], + ), + ], + ), + ], + ); + } @override Widget build(BuildContext context) { return Scaffold( @@ -585,261 +1220,7 @@ class _SafecheckStartDetailState extends State { padding: EdgeInsets.symmetric(horizontal: 12, vertical: 12), child: form.isNotEmpty - ? ListView( - children: [ - Column( - children: [ - 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_SUBJECT'] ?? ''}现场检查记录', - onChanged: (val) { - setState(() { - chooseTitleType = val; - form['INSPECTION_SUBJECT'] = - val == true ? '安全' : '综合'; - }); - }, - ), - const Divider(), - ItemListWidget.singleLineTitleText( - label: '被检查单位:', - isEditable: false, - text: form['INSPECTED_DEPARTMENT_NAMES'], - onChanged: (val) { - setState(() { - form['INSPECTED_DEPARTMENT_NAME'] = val; - }); - }, - ), - const Divider(), - - ItemListWidget.selectableLineTitleTextRightButton( - label: '被检查单位现场负责人:', - isEditable: widget.isEdit, - onTap: () { - _choosePerson(); - }, - text: form['INSPECTED_SITEUSER_INDEX'] ?? '', - ), - 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.itemContainer( - Column( - children: [ - ListItemFactory.headerTitle('检查人员'), - Expanded(child: ListView.builder( - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - itemCount: inspectorList.length, - itemBuilder: (context, index) { - return _personUnitItem(inspectorList[index], index); - }, - ),) - ], - ), - ), - - 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, - ), - ), - ], - ), - ], - ), - ], - ) + ? _mainWidget() : SizedBox(), ), ), diff --git a/lib/pages/home/SafeCheck/Start/safeCheck_start_list_page.dart b/lib/pages/home/SafeCheck/Start/safeCheck_start_list_page.dart index b989ef0..85fdf8a 100644 --- a/lib/pages/home/SafeCheck/Start/safeCheck_start_list_page.dart +++ b/lib/pages/home/SafeCheck/Start/safeCheck_start_list_page.dart @@ -154,15 +154,11 @@ class _SafecheckStartListPageState extends State { required Map item, required bool isFirst, required bool isLast, - required int status, // 1 = 完成, 0 = 进行中, -1 = 未到达 }) { + bool status = item['active'] == item['order']; // 依据状态设色 - final Color dotColor = - status == 1 ? Colors.blue : (status == 0 ? Colors.blue : Colors.grey); - final Color textColor = - status == 1 - ? Colors.blue - : (status == 0 ? Colors.blue : Colors.black54); + final Color dotColor = status ? Colors.blue :Colors.grey; + final Color textColor = status ? Colors.blue :Colors.black54; return ListTile( visualDensity: VisualDensity(vertical: -4), @@ -411,21 +407,17 @@ class _SafecheckStartListPageState extends State { final bool isFirst = idx == 0; final bool isLast = idx == flowList.length - 1; - // 根据 lastDoneIndex 自动计算“进行中” - final int status = item['active'] == '1' ? 1 : -1; - return _buildFlowStepItem( item: item, isFirst: isFirst, isLast: isLast, - status: status, ); }, ), ), ), - body: Column( + body: SafeArea(child: Column( children: [ // Filter bar Container( @@ -475,7 +467,7 @@ class _SafecheckStartListPageState extends State { // List Expanded(child: _buildListContent()), ], - ), + )), ); } } diff --git a/lib/pages/home/SafeCheck/safeCheck_tab_list.dart b/lib/pages/home/SafeCheck/safeCheck_tab_list.dart index b6eb385..236ea44 100644 --- a/lib/pages/home/SafeCheck/safeCheck_tab_list.dart +++ b/lib/pages/home/SafeCheck/safeCheck_tab_list.dart @@ -4,6 +4,7 @@ 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/SafeCheck/CheckPersonSure/check_person_list_page.dart'; import 'package:qhd_prevention/pages/home/SafeCheck/Start/safeCheck_start_list_page.dart'; import 'package:qhd_prevention/pages/home/tap/tabList/work_tab_icon_grid.dart'; import 'package:qhd_prevention/pages/my_appbar.dart'; @@ -100,7 +101,7 @@ class _SafecheckTabListState extends State { } break; case 1: { title = '安全检查核实'; - // await pushPage(SafecheckListPage(flow: title), context); + await pushPage(CheckPersonListPage(flow: title), context); } break; case 2: { diff --git a/lib/pages/home/tap/item_list_widget.dart b/lib/pages/home/tap/item_list_widget.dart index 525ea7e..b39b19b 100644 --- a/lib/pages/home/tap/item_list_widget.dart +++ b/lib/pages/home/tap/item_list_widget.dart @@ -207,7 +207,7 @@ class ItemListWidget { Flexible( child: Text( text.isNotEmpty ? text : '请选择', - maxLines: 1, + maxLines: 5, overflow: TextOverflow.ellipsis, style: TextStyle( fontSize: fontSize, @@ -597,6 +597,8 @@ class ItemListWidget { double fontSize = 15, // 字体大小 Color btnColor = Colors.blue, bool isRequired = false, + String text = '', + void Function(String)? onTapCallBack, }) { return Container( padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 12), @@ -614,23 +616,25 @@ class ItemListWidget { ), ], ), - GestureDetector( - onTap: () { - if (imgPath.isNotEmpty) { - SingleImageViewer( - imageUrl: '${ApiService.baseImgPath}${imgPath}', - ); - } - }, - child: + Column( + children: [ + GestureDetector( + onTap: () { + if (onTapCallBack != null) onTapCallBack('${ApiService.baseImgPath}$imgPath'); + }, + child: imgPath.isNotEmpty ? Image.network( - '${ApiService.baseImgPath}${imgPath}', - width: 40, - height: 40, - ) + '${ApiService.baseImgPath}${imgPath}', + width: 50, + height: 50, + ) : SizedBox(), - ), + ), + if (text.isNotEmpty) + Text(text) + ], + ) ], ), ); diff --git a/lib/pages/home/tap/tabList/special_wrok/sxkj_work/tzgl_zytz_work_detail/sxkj_tzgl_detail.dart b/lib/pages/home/tap/tabList/special_wrok/sxkj_work/tzgl_zytz_work_detail/sxkj_tzgl_detail.dart index c5e8118..3080bc8 100644 --- a/lib/pages/home/tap/tabList/special_wrok/sxkj_work/tzgl_zytz_work_detail/sxkj_tzgl_detail.dart +++ b/lib/pages/home/tap/tabList/special_wrok/sxkj_work/tzgl_zytz_work_detail/sxkj_tzgl_detail.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:qhd_prevention/customWidget/remote_file_page.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'; @@ -86,6 +87,9 @@ class _SxkjTzglDetailState extends State { ItemListWidget.OneRowImageTitle( label: '警示标志附件', imgPath: pd['WARNINGSIGNS_PATH'] ?? '', + onTapCallBack: (path) { + presentOpaque(SingleImageViewer(imageUrl: path), context); + } ), Divider(), ItemListWidget.singleLineTitleText(