diff --git a/lib/customWidget/department_person_picker.dart b/lib/customWidget/department_person_picker.dart new file mode 100644 index 0000000..25e7461 --- /dev/null +++ b/lib/customWidget/department_person_picker.dart @@ -0,0 +1,135 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:qhd_prevention/customWidget/search_bar_widget.dart'; + +/// 用户数据模型 +class Person { + final String userId; + final String name; + + Person({required this.userId, required this.name}); + + factory Person.fromJson(Map json) { + return Person( + userId: json['USER_ID'] as String, + name: json['NAME'] as String, + ); + } +} + +/// 回调签名,返回选中用户的 USER_ID 和 NAME +typedef PersonSelectCallback = void Function(String userId, String name); + +/// 底部弹窗人员选择器(使用预先传入的原始数据列表,不做接口请求) +class DepartmentPersonPicker { + /// 显示人员选择弹窗 + /// + /// [personsData]: 已拉取并缓存的原始 Map 列表 + /// [onSelected]: 选中后回调 USER_ID 和 NAME + static Future show( + BuildContext context, { + required List> personsData, + required PersonSelectCallback onSelected, + }) async { + // 转换为模型 + final List _all = + personsData.map((e) => Person.fromJson(e)).toList(); + List _filtered = List.from(_all); + String _selectedName = ''; + String _selectedId = ''; + final TextEditingController _searchController = TextEditingController(); + + await showModalBottomSheet( + context: context, + isScrollControlled: true, + backgroundColor: Colors.white, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.vertical(top: Radius.circular(12)), + ), + builder: (ctx) { + return StatefulBuilder( + builder: (BuildContext ctx, StateSetter setState) { + // 搜索逻辑 + void _onSearch(String v) { + final q = v.toLowerCase().trim(); + setState(() { + _filtered = q.isEmpty + ? List.from(_all) + : _all + .where((p) => p.name.toLowerCase().contains(q)) + .toList(); + }); + } + + return SafeArea( + child: SizedBox( + height: MediaQuery.of(ctx).size.height * 0.75, + child: Column( + children: [ + // 顶部:取消、搜索、确定 + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 16, vertical: 8), + child: Row( + children: [ + TextButton( + onPressed: () => Navigator.of(ctx).pop(), + child: const Text('取消',style: TextStyle(fontSize: 16),), + ), + Expanded( + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 8), + child: SearchBarWidget( + controller: _searchController, + onTextChanged: _onSearch, + isShowSearchButton: false, + onSearch: (keyboard) { + }, + ), + ), + ), + TextButton( + onPressed: _selectedId.isEmpty + ? null + : () { + Navigator.of(ctx).pop(); + onSelected(_selectedId, _selectedName); + }, + child: const Text('确定', style: TextStyle(color: Colors.green, fontSize: 16),), + ), + ], + ), + ), + const Divider(height: 1), + // 列表 + Expanded( + child: ListView.separated( + itemCount: _filtered.length, + separatorBuilder: (_, __) => const Divider(height: 1), + itemBuilder: (context, index) { + final person = _filtered[index]; + final selected = person.userId == _selectedId; + return ListTile( + titleAlignment: ListTileTitleAlignment.center, + title: Text(person.name), + trailing: + selected ? const Icon(Icons.check, color: Colors.green) : null, + onTap: () => setState(() { + _selectedId = person.userId; + _selectedName = person.name; + }), + ); + }, + ), + ), + ], + ), + ), + ); + }, + ); + }, + ); + } +} diff --git a/lib/customWidget/department_picker.dart b/lib/customWidget/department_picker.dart index 0cf5743..1ec426b 100644 --- a/lib/customWidget/department_picker.dart +++ b/lib/customWidget/department_picker.dart @@ -181,7 +181,6 @@ class _DepartmentPickerState extends State { child: Padding( padding: const EdgeInsets.symmetric(horizontal: 12), child: SearchBarWidget( - controller: _searchController, isShowSearchButton: false, onSearch: (keyboard) { diff --git a/lib/http/ApiService.dart b/lib/http/ApiService.dart index c25ca32..3151350 100644 --- a/lib/http/ApiService.dart +++ b/lib/http/ApiService.dart @@ -942,7 +942,7 @@ U6Hzm1ninpWeE+awIDAQAB static Future> getListTreePersonList(String DEPARTMENT_ID) { return HttpManager().request( basePath, - 'app/sys/listUser', + '/app/sys/listUser', method: Method.post, data: { "tm":DateTime.now().millisecondsSinceEpoch.toString(), diff --git a/lib/pages/home/tap/item_list_widget.dart b/lib/pages/home/tap/item_list_widget.dart index 9524cb4..882b809 100644 --- a/lib/pages/home/tap/item_list_widget.dart +++ b/lib/pages/home/tap/item_list_widget.dart @@ -101,36 +101,54 @@ class ItemListWidget { /// - 可编辑时:标题 + “请选择”提示 + 右箭头 /// - 不可编辑时:标题 + 文本内容 static Widget selectableLineTitleTextField({ - required String label, // 标题文本 - required bool isEditable, // 是否可点击 - required String text, // 显示内容或提示 - VoidCallback? onTap, // 点击回调 - double fontSize = 15, // 字体大小 + required String label, // 标题文本 + required bool isEditable, // 是否可点击 + required String text, // 显示内容或提示 + VoidCallback? onTap, // 点击回调 + double fontSize = 15, // 字体大小 }) { return InkWell( onTap: isEditable ? onTap : null, child: Container( padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 12), child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ + // 1. 标题 Text( label, - style: TextStyle(fontSize: fontSize, fontWeight: FontWeight.bold), - ), // 显示标题 + style: TextStyle( + fontSize: fontSize, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(width: 8), - Row( - children: [ - Text( - text.length > 0 ? text: '请选择', // 默认提示 - style: TextStyle( - fontSize: fontSize, - color: isEditable ? Colors.black : Colors.grey, // 不可编辑时使用灰色 + + Expanded( + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Flexible( + child: Text( + text.isNotEmpty ? text : '请选择', + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: fontSize, + color: isEditable ? Colors.black : Colors.grey, + ), + ), ), - overflow: TextOverflow.ellipsis, // 超出省略 - ), - if (isEditable) const Icon(Icons.chevron_right), // 只有可编辑时显示箭头 - ], + if (isEditable) + const Padding( + padding: EdgeInsets.only(left: 4), + child: Icon( + Icons.chevron_right, + size: 20, + ), + ), + ], + ), ), ], ), @@ -138,6 +156,8 @@ class ItemListWidget { ); } + + /// 两行垂直布局: /// 第一行:可点击选择(带箭头)或仅显示标题 /// 第二行:多行输入框或多行文本展示 @@ -164,31 +184,28 @@ class ItemListWidget { Text( label, style: TextStyle(fontSize: fontSize, fontWeight: FontWeight.bold), - ), // 显示标题 - const SizedBox(width: 8), + ), Row( children: [ Text( - isEditable ? '请选择' : '', // 显示内容或提示 + isEditable ? (text.isNotEmpty ? text : '请选择') : '', style: TextStyle( fontSize: fontSize, color: isEditable ? Colors.black : Colors.grey, ), + maxLines: 1, overflow: TextOverflow.ellipsis, ), if (isEditable) const Icon(Icons.chevron_right), - ], ) ], ), ), const SizedBox(height: 8), - // 第二行:多行输入或展示 Container( height: row2Height, - padding: const EdgeInsets.all(8), - + padding: const EdgeInsets.symmetric(vertical: 8), child: isEditable ? TextField( controller: controller, @@ -196,11 +213,14 @@ class ItemListWidget { maxLines: null, expands: true, style: TextStyle(fontSize: fontSize), - decoration: const InputDecoration( - hintText: '请输入' + decoration: InputDecoration( + hintText: '请输入', + contentPadding: EdgeInsets.zero, + border: InputBorder.none, ), ) : SingleChildScrollView( + padding: EdgeInsets.zero, child: Text( text, style: TextStyle(fontSize: fontSize, color: Colors.grey), @@ -211,8 +231,9 @@ class ItemListWidget { ), ); } + /// 两行垂直布局: - /// 标题+按钮 + /// 标题 + 按钮 /// 第二行:多行输入框或多行文本展示 static Widget twoRowButtonTitleText({ required String label, // 第一行标题 @@ -229,27 +250,31 @@ class ItemListWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - // 第一行:可点击区域或纯文本标题 + // 第一行:标题 + 按钮 InkWell( onTap: isEditable ? onTap : null, child: Row( - mainAxisAlignment: MainAxisAlignment.start, children: [ Text( label, style: TextStyle(fontSize: fontSize, fontWeight: FontWeight.bold), - ), // 显示标题 + ), const SizedBox(width: 8), - CustomButton(text: "选择其他", height:30, padding: EdgeInsets.symmetric(vertical: 2, horizontal: 5), backgroundColor: Colors.green, onPressed: onTap,) + CustomButton( + text: "选择其他", + height: 30, + padding: const EdgeInsets.symmetric(vertical: 2, horizontal: 5), + backgroundColor: Colors.green, + onPressed: onTap, + ), ], ), ), const SizedBox(height: 8), - // 第二行:多行输入或展示 + Container( height: row2Height, - padding: const EdgeInsets.all(8), - + padding: const EdgeInsets.symmetric(vertical: 8), child: isEditable ? TextField( controller: controller, @@ -258,10 +283,13 @@ class ItemListWidget { expands: true, style: TextStyle(fontSize: fontSize), decoration: InputDecoration( - hintText: hintText + hintText: hintText, + contentPadding: EdgeInsets.zero, + border: InputBorder.none, ), ) : SingleChildScrollView( + padding: EdgeInsets.zero, child: Text( text, style: TextStyle(fontSize: fontSize, color: Colors.grey), @@ -272,4 +300,5 @@ class ItemListWidget { ), ); } + } diff --git a/lib/pages/home/tap/tabList/dh_Wrok/dh_work_detai/hotwork_apply_detail.dart b/lib/pages/home/tap/tabList/dh_Wrok/dh_work_detai/hotwork_apply_detail.dart index 9f7fbdb..bfb9090 100644 --- a/lib/pages/home/tap/tabList/dh_Wrok/dh_work_detai/hotwork_apply_detail.dart +++ b/lib/pages/home/tap/tabList/dh_Wrok/dh_work_detai/hotwork_apply_detail.dart @@ -1,23 +1,34 @@ import 'package:flutter/material.dart'; import 'package:qhd_prevention/customWidget/ItemWidgetFactory.dart'; +import 'package:qhd_prevention/customWidget/custom_button.dart'; +import 'package:qhd_prevention/customWidget/department_person_picker.dart'; import 'package:qhd_prevention/customWidget/department_picker.dart'; +import 'package:qhd_prevention/customWidget/toast_util.dart'; import 'package:qhd_prevention/pages/home/tap/item_list_widget.dart'; +import '../../../../../../customWidget/bottom_picker.dart'; import '../../../../../../http/ApiService.dart'; import '../../../../../my_appbar.dart'; + enum EditUserType { - analyze, - confirm, - guardian, - confess, - acceptconfess, - workstart, - workend, - leader, - audit, - approve, - monitor, - accept, + analyze('分析单位'), + confirm('作业负责人单位'), + guardian('监护人单位'), + confess('安全交底人单位'), + acceptconfess('接受交底人单位'), + workstart('作业开始负责人单位'), + workend('作业结束负责人单位'), + leader('安全管理部门'), + audit('审核部门'), + approve('动火审批单位'), + monitor('动火前在岗部门'), + accept('验收部门'); + + /// 对应的单位显示名 + final String displayName; + + const EditUserType(this.displayName); } + class HotworkApplyDetail extends StatefulWidget { const HotworkApplyDetail({ super.key, @@ -34,8 +45,17 @@ class HotworkApplyDetail extends StatefulWidget { class _HotworkApplyDetailState extends State { final bool isEditable = true; - late String treeJson=""; + /// 编辑还是新增 + late String msg = 'add'; + // 保存不同环节的单位和负责人 + final Map _selectedUnitId = {}; + final Map _selectedUnitName = {}; + final Map _selectedPersonId = {}; + final Map _selectedPersonName = {}; + + // 存储各单位的人员列表 + final Map>> _personCache = {}; Widget _defaultDetail() { return Column( @@ -102,6 +122,7 @@ class _HotworkApplyDetailState extends State { ], ); } + Widget _card(Widget child) { return Container( decoration: BoxDecoration( @@ -111,37 +132,87 @@ class _HotworkApplyDetailState extends State { child: child, ); } - Widget _chooseItem(String unit, String person,EditUserType type) { + + Widget _chooseItem(String unitLabel, String personLabel, EditUserType type) { return Column( children: [ - ItemListWidget.selectableLineTitleTextField(label: unit, isEditable: isEditable, text: '', onTap: () { - chooseUnitHandle(type); - }), + ItemListWidget.selectableLineTitleTextField( + label: unitLabel, + isEditable: isEditable, + text: _selectedUnitName[type] ?? '请选择', + onTap: () => chooseUnitHandle(type), + ), Divider(), - ItemListWidget.selectableLineTitleTextField(label: person, isEditable: isEditable, text: '', onTap: () { - choosePersonHandle(type); - - }), + ItemListWidget.selectableLineTitleTextField( + label: personLabel, + isEditable: isEditable, + text: _selectedPersonName[type] ?? '请选择', + onTap: () => choosePersonHandle(type), + ), ], ); } - /// 弹出单位 + + /// 弹出单位选择 void chooseUnitHandle(EditUserType type) { showModalBottomSheet( context: context, isScrollControlled: true, barrierColor: Colors.black54, backgroundColor: Colors.transparent, - builder: (ctx) => DepartmentPicker(onSelected: (id, name) { - setState(() { - }); - }), + builder: + (_) => DepartmentPicker( + onSelected: (id, name) async { + setState(() { + _selectedUnitId[type] = id; + _selectedUnitName[type] = name; + // 清空已选人员 + _selectedPersonId.remove(type); + _selectedPersonName.remove(type); + }); + // 拉取该单位的人员列表并缓存 + final result = await ApiService.getListTreePersonList(id); + _personCache[type] = List>.from( + result['userList'] as List, + ); + }, + ), ); } - /// 弹出单位对应的人员列表 + + /// 弹出人员选择,需先选择单位 void choosePersonHandle(EditUserType type) { + final unitId = _selectedUnitId[type]; + final personList = _personCache[type] ?? []; + if (unitId == null || personList.isEmpty) { + final unitName = type.displayName; + ToastUtil.showNormal(context, '请先选择$unitName'); + return; + } + DepartmentPersonPicker.show( + context, + personsData: personList, + onSelected: (userId, name) { + setState(() { + _selectedPersonId[type] = userId; + _selectedPersonName[type] = name; + }); + }, + ); + } + Future _submit(String STATUS) async { + // '1'提交 ‘0’暂存 + } + /// 初始化拉取数据 + Future _getData() async { + // '1'提交 ‘0’暂存 + + + } + + @override Widget build(BuildContext context) { return Scaffold( @@ -152,45 +223,49 @@ class _HotworkApplyDetailState extends State { child: Column( children: [ _card(_defaultDetail()), - SizedBox(height: 15,), - _card(_defaultDetail()), - SizedBox(height: 15,), + SizedBox(height: 15), _card(_chooseItem('分析单位', '分析单位负责人', EditUserType.analyze)), - SizedBox(height: 15,), + SizedBox(height: 15), _card(_chooseItem('监护人单位', '监护人', EditUserType.guardian)), - SizedBox(height: 15,), + SizedBox(height: 15), _card(_chooseItem('安全交底人单位', '安全交底人', EditUserType.confess)), - SizedBox(height: 15,), - _card(_chooseItem('接受交底人单位', '接受交底人', EditUserType.acceptconfess)), - SizedBox(height: 15,), - _card(_chooseItem('作业负责人单位', '作业负责人', EditUserType.confirm)), - SizedBox(height: 15,), - _card(_chooseItem('安全管理部门', '所在单位负责人', EditUserType.leader)), - SizedBox(height: 15,), - _card(_chooseItem('动火审批单位', '动火审批负责人', EditUserType.approve)), - SizedBox(height: 15,), - _card(_chooseItem('动火前在岗部门', '动火前在岗班长', EditUserType.monitor)), - SizedBox(height: 15,), - _card(_chooseItem('作业开始负责人单位', '作业开始负责人', EditUserType.workstart)), - SizedBox(height: 15,), - + SizedBox(height: 15), _card( - Column( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - _chooseItem('作业开始负责人单位', '作业开始负责人', EditUserType.workend), - Divider(), - Row( - children: [ - SizedBox(width: 12,), - Text('友情提示:负责填写作业实际开始时间', style: TextStyle(color: Colors.red),), - ], - ), - SizedBox(height: 5,), - ], - ) + _chooseItem('接受交底人单位', '接受交底人', EditUserType.acceptconfess), ), - SizedBox(height: 15,), + SizedBox(height: 15), + _card(_chooseItem('作业负责人单位', '作业负责人', EditUserType.confirm)), + SizedBox(height: 15), + _card(_chooseItem('安全管理部门', '所在单位负责人', EditUserType.leader)), + SizedBox(height: 15), + _card(_chooseItem('动火审批单位', '动火审批负责人', EditUserType.approve)), + SizedBox(height: 15), + _card(_chooseItem('动火前在岗部门', '动火前在岗班长', EditUserType.monitor)), + SizedBox(height: 15), + _card( + _chooseItem('作业开始负责人单位', '作业开始负责人', EditUserType.workstart), + ), + SizedBox(height: 15), + _card( + Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + _chooseItem('作业开始负责人单位', '作业开始负责人', EditUserType.workend), + Divider(), + Row( + children: [ + SizedBox(width: 12), + Text( + '友情提示:负责填写作业实际开始时间', + style: TextStyle(color: Colors.red), + ), + ], + ), + SizedBox(height: 5), + ], + ), + ), + SizedBox(height: 15), _card( Column( mainAxisAlignment: MainAxisAlignment.start, @@ -199,28 +274,63 @@ class _HotworkApplyDetailState extends State { Divider(), Row( children: [ - SizedBox(width: 12,), - Text('友情提示:负责填写作业实际结束时间', style: TextStyle(color: Colors.red),), + SizedBox(width: 12), + Text( + '友情提示:负责填写作业实际结束时间', + style: TextStyle(color: Colors.red), + ), ], ), - SizedBox(height: 5,), + SizedBox(height: 5), ], - ) + ), ), - SizedBox(height: 15,), + SizedBox(height: 15), _card(_chooseItem('验收部门', '验收部门负责人', EditUserType.accept)), + SizedBox(height: 15), + Row( + spacing: 10, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: CustomButton( + height: 45, + textStyle: TextStyle(fontSize: 16, color: Colors.white), + text: '提交', + backgroundColor: Colors.blue, + onPressed: () { + _submit('1'); + }, + ), + ), + Expanded( + child: CustomButton( + textStyle: TextStyle(fontSize: 16, color: Colors.white), + text: '暂存', + backgroundColor: Colors.green, + onPressed: () { + _submit('1'); + }, + ), + ), + ], + ), ], ), ), ), ); } + @override void initState() { // TODO: implement initState super.initState(); + if (widget.HOTWORK_ID.length > 0) { + msg = 'edit'; + _getData(); + } } - } diff --git a/lib/pages/home/tap/tabList/dh_Wrok/dh_work_list_page.dart b/lib/pages/home/tap/tabList/dh_Wrok/dh_work_list_page.dart index ef8b693..9a8111e 100644 --- a/lib/pages/home/tap/tabList/dh_Wrok/dh_work_list_page.dart +++ b/lib/pages/home/tap/tabList/dh_Wrok/dh_work_list_page.dart @@ -116,7 +116,7 @@ class _DhWorkListPageState extends State { /// 申请 void _handleApply() { // 处理申请按钮点击逻辑 - Navigator.pushNamed(context, '/hotwork-apply-detail'); + pushPage(HotworkApplyDetail(HOTWORK_ID: '', flow: widget.flow), context); } /// 打开流程图 Future _openFlowDrawer(String hotworkId) async {