部门人员多级选择器

main
hs 2025-07-24 14:49:16 +08:00
parent 824d5a403c
commit 4d3fb2b6e6
6 changed files with 380 additions and 107 deletions

View File

@ -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<String, dynamic> 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<void> show(
BuildContext context, {
required List<Map<String, dynamic>> personsData,
required PersonSelectCallback onSelected,
}) async {
//
final List<Person> _all =
personsData.map((e) => Person.fromJson(e)).toList();
List<Person> _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;
}),
);
},
),
),
],
),
),
);
},
);
},
);
}
}

View File

@ -181,7 +181,6 @@ class _DepartmentPickerState extends State<DepartmentPicker> {
child: Padding( child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 12), padding: const EdgeInsets.symmetric(horizontal: 12),
child: SearchBarWidget( child: SearchBarWidget(
controller: _searchController, controller: _searchController,
isShowSearchButton: false, isShowSearchButton: false,
onSearch: (keyboard) { onSearch: (keyboard) {

View File

@ -942,7 +942,7 @@ U6Hzm1ninpWeE+awIDAQAB
static Future<Map<String, dynamic>> getListTreePersonList(String DEPARTMENT_ID) { static Future<Map<String, dynamic>> getListTreePersonList(String DEPARTMENT_ID) {
return HttpManager().request( return HttpManager().request(
basePath, basePath,
'app/sys/listUser', '/app/sys/listUser',
method: Method.post, method: Method.post,
data: { data: {
"tm":DateTime.now().millisecondsSinceEpoch.toString(), "tm":DateTime.now().millisecondsSinceEpoch.toString(),

View File

@ -112,32 +112,52 @@ class ItemListWidget {
child: Container( child: Container(
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 12), padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 12),
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
// 1.
Text( Text(
label, label,
style: TextStyle(fontSize: fontSize, fontWeight: FontWeight.bold),
), //
const SizedBox(width: 8),
Row(
children: [
Text(
text.length > 0 ? text: '请选择', //
style: TextStyle( style: TextStyle(
fontSize: fontSize, fontSize: fontSize,
color: isEditable ? Colors.black : Colors.grey, // 使 fontWeight: FontWeight.bold,
),
),
const SizedBox(width: 8),
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,
),
),
),
if (isEditable)
const Padding(
padding: EdgeInsets.only(left: 4),
child: Icon(
Icons.chevron_right,
size: 20,
), ),
overflow: TextOverflow.ellipsis, //
), ),
if (isEditable) const Icon(Icons.chevron_right), //
], ],
), ),
),
], ],
), ),
), ),
); );
} }
/// ///
/// ///
/// ///
@ -164,31 +184,28 @@ class ItemListWidget {
Text( Text(
label, label,
style: TextStyle(fontSize: fontSize, fontWeight: FontWeight.bold), style: TextStyle(fontSize: fontSize, fontWeight: FontWeight.bold),
), // ),
const SizedBox(width: 8),
Row( Row(
children: [ children: [
Text( Text(
isEditable ? '请选择' : '', // isEditable ? (text.isNotEmpty ? text : '请选择') : '',
style: TextStyle( style: TextStyle(
fontSize: fontSize, fontSize: fontSize,
color: isEditable ? Colors.black : Colors.grey, color: isEditable ? Colors.black : Colors.grey,
), ),
maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
if (isEditable) const Icon(Icons.chevron_right), if (isEditable) const Icon(Icons.chevron_right),
], ],
) )
], ],
), ),
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
//
Container( Container(
height: row2Height, height: row2Height,
padding: const EdgeInsets.all(8), padding: const EdgeInsets.symmetric(vertical: 8),
child: isEditable child: isEditable
? TextField( ? TextField(
controller: controller, controller: controller,
@ -196,11 +213,14 @@ class ItemListWidget {
maxLines: null, maxLines: null,
expands: true, expands: true,
style: TextStyle(fontSize: fontSize), style: TextStyle(fontSize: fontSize),
decoration: const InputDecoration( decoration: InputDecoration(
hintText: '请输入' hintText: '请输入',
contentPadding: EdgeInsets.zero,
border: InputBorder.none,
), ),
) )
: SingleChildScrollView( : SingleChildScrollView(
padding: EdgeInsets.zero,
child: Text( child: Text(
text, text,
style: TextStyle(fontSize: fontSize, color: Colors.grey), style: TextStyle(fontSize: fontSize, color: Colors.grey),
@ -211,6 +231,7 @@ class ItemListWidget {
), ),
); );
} }
/// ///
/// + /// +
/// ///
@ -229,27 +250,31 @@ class ItemListWidget {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
// // +
InkWell( InkWell(
onTap: isEditable ? onTap : null, onTap: isEditable ? onTap : null,
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [ children: [
Text( Text(
label, label,
style: TextStyle(fontSize: fontSize, fontWeight: FontWeight.bold), style: TextStyle(fontSize: fontSize, fontWeight: FontWeight.bold),
), // ),
const SizedBox(width: 8), 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), const SizedBox(height: 8),
//
Container( Container(
height: row2Height, height: row2Height,
padding: const EdgeInsets.all(8), padding: const EdgeInsets.symmetric(vertical: 8),
child: isEditable child: isEditable
? TextField( ? TextField(
controller: controller, controller: controller,
@ -258,10 +283,13 @@ class ItemListWidget {
expands: true, expands: true,
style: TextStyle(fontSize: fontSize), style: TextStyle(fontSize: fontSize),
decoration: InputDecoration( decoration: InputDecoration(
hintText: hintText hintText: hintText,
contentPadding: EdgeInsets.zero,
border: InputBorder.none,
), ),
) )
: SingleChildScrollView( : SingleChildScrollView(
padding: EdgeInsets.zero,
child: Text( child: Text(
text, text,
style: TextStyle(fontSize: fontSize, color: Colors.grey), style: TextStyle(fontSize: fontSize, color: Colors.grey),
@ -272,4 +300,5 @@ class ItemListWidget {
), ),
); );
} }
} }

View File

@ -1,23 +1,34 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:qhd_prevention/customWidget/ItemWidgetFactory.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/department_picker.dart';
import 'package:qhd_prevention/customWidget/toast_util.dart';
import 'package:qhd_prevention/pages/home/tap/item_list_widget.dart'; import 'package:qhd_prevention/pages/home/tap/item_list_widget.dart';
import '../../../../../../customWidget/bottom_picker.dart';
import '../../../../../../http/ApiService.dart'; import '../../../../../../http/ApiService.dart';
import '../../../../../my_appbar.dart'; import '../../../../../my_appbar.dart';
enum EditUserType { enum EditUserType {
analyze, analyze('分析单位'),
confirm, confirm('作业负责人单位'),
guardian, guardian('监护人单位'),
confess, confess('安全交底人单位'),
acceptconfess, acceptconfess('接受交底人单位'),
workstart, workstart('作业开始负责人单位'),
workend, workend('作业结束负责人单位'),
leader, leader('安全管理部门'),
audit, audit('审核部门'),
approve, approve('动火审批单位'),
monitor, monitor('动火前在岗部门'),
accept, accept('验收部门');
///
final String displayName;
const EditUserType(this.displayName);
} }
class HotworkApplyDetail extends StatefulWidget { class HotworkApplyDetail extends StatefulWidget {
const HotworkApplyDetail({ const HotworkApplyDetail({
super.key, super.key,
@ -34,8 +45,17 @@ class HotworkApplyDetail extends StatefulWidget {
class _HotworkApplyDetailState extends State<HotworkApplyDetail> { class _HotworkApplyDetailState extends State<HotworkApplyDetail> {
final bool isEditable = true; final bool isEditable = true;
late String treeJson=""; ///
late String msg = 'add';
//
final Map<EditUserType, String> _selectedUnitId = {};
final Map<EditUserType, String> _selectedUnitName = {};
final Map<EditUserType, String> _selectedPersonId = {};
final Map<EditUserType, String> _selectedPersonName = {};
//
final Map<EditUserType, List<Map<String, dynamic>>> _personCache = {};
Widget _defaultDetail() { Widget _defaultDetail() {
return Column( return Column(
@ -102,6 +122,7 @@ class _HotworkApplyDetailState extends State<HotworkApplyDetail> {
], ],
); );
} }
Widget _card(Widget child) { Widget _card(Widget child) {
return Container( return Container(
decoration: BoxDecoration( decoration: BoxDecoration(
@ -111,37 +132,87 @@ class _HotworkApplyDetailState extends State<HotworkApplyDetail> {
child: child, child: child,
); );
} }
Widget _chooseItem(String unit, String person,EditUserType type) {
Widget _chooseItem(String unitLabel, String personLabel, EditUserType type) {
return Column( return Column(
children: [ children: [
ItemListWidget.selectableLineTitleTextField(label: unit, isEditable: isEditable, text: '', onTap: () { ItemListWidget.selectableLineTitleTextField(
chooseUnitHandle(type); label: unitLabel,
}), isEditable: isEditable,
text: _selectedUnitName[type] ?? '请选择',
onTap: () => chooseUnitHandle(type),
),
Divider(), Divider(),
ItemListWidget.selectableLineTitleTextField(label: person, isEditable: isEditable, text: '', onTap: () { ItemListWidget.selectableLineTitleTextField(
choosePersonHandle(type); label: personLabel,
isEditable: isEditable,
}), text: _selectedPersonName[type] ?? '请选择',
onTap: () => choosePersonHandle(type),
),
], ],
); );
} }
///
///
void chooseUnitHandle(EditUserType type) { void chooseUnitHandle(EditUserType type) {
showModalBottomSheet( showModalBottomSheet(
context: context, context: context,
isScrollControlled: true, isScrollControlled: true,
barrierColor: Colors.black54, barrierColor: Colors.black54,
backgroundColor: Colors.transparent, backgroundColor: Colors.transparent,
builder: (ctx) => DepartmentPicker(onSelected: (id, name) { builder:
(_) => DepartmentPicker(
onSelected: (id, name) async {
setState(() { setState(() {
_selectedUnitId[type] = id;
_selectedUnitName[type] = name;
//
_selectedPersonId.remove(type);
_selectedPersonName.remove(type);
}); });
}), //
final result = await ApiService.getListTreePersonList(id);
_personCache[type] = List<Map<String, dynamic>>.from(
result['userList'] as List,
);
},
),
); );
} }
///
///
void choosePersonHandle(EditUserType type) { 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<void> _submit(String STATUS) async {
// '1' 0
} }
///
Future<void> _getData() async {
// '1' 0
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
@ -152,28 +223,29 @@ class _HotworkApplyDetailState extends State<HotworkApplyDetail> {
child: Column( child: Column(
children: [ children: [
_card(_defaultDetail()), _card(_defaultDetail()),
SizedBox(height: 15,), SizedBox(height: 15),
_card(_defaultDetail()),
SizedBox(height: 15,),
_card(_chooseItem('分析单位', '分析单位负责人', EditUserType.analyze)), _card(_chooseItem('分析单位', '分析单位负责人', EditUserType.analyze)),
SizedBox(height: 15,), SizedBox(height: 15),
_card(_chooseItem('监护人单位', '监护人', EditUserType.guardian)), _card(_chooseItem('监护人单位', '监护人', EditUserType.guardian)),
SizedBox(height: 15,), SizedBox(height: 15),
_card(_chooseItem('安全交底人单位', '安全交底人', EditUserType.confess)), _card(_chooseItem('安全交底人单位', '安全交底人', EditUserType.confess)),
SizedBox(height: 15,), SizedBox(height: 15),
_card(_chooseItem('接受交底人单位', '接受交底人', EditUserType.acceptconfess)), _card(
SizedBox(height: 15,), _chooseItem('接受交底人单位', '接受交底人', EditUserType.acceptconfess),
),
SizedBox(height: 15),
_card(_chooseItem('作业负责人单位', '作业负责人', EditUserType.confirm)), _card(_chooseItem('作业负责人单位', '作业负责人', EditUserType.confirm)),
SizedBox(height: 15,), SizedBox(height: 15),
_card(_chooseItem('安全管理部门', '所在单位负责人', EditUserType.leader)), _card(_chooseItem('安全管理部门', '所在单位负责人', EditUserType.leader)),
SizedBox(height: 15,), SizedBox(height: 15),
_card(_chooseItem('动火审批单位', '动火审批负责人', EditUserType.approve)), _card(_chooseItem('动火审批单位', '动火审批负责人', EditUserType.approve)),
SizedBox(height: 15,), SizedBox(height: 15),
_card(_chooseItem('动火前在岗部门', '动火前在岗班长', EditUserType.monitor)), _card(_chooseItem('动火前在岗部门', '动火前在岗班长', EditUserType.monitor)),
SizedBox(height: 15,), SizedBox(height: 15),
_card(_chooseItem('作业开始负责人单位', '作业开始负责人', EditUserType.workstart)), _card(
SizedBox(height: 15,), _chooseItem('作业开始负责人单位', '作业开始负责人', EditUserType.workstart),
),
SizedBox(height: 15),
_card( _card(
Column( Column(
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
@ -182,15 +254,18 @@ class _HotworkApplyDetailState extends State<HotworkApplyDetail> {
Divider(), Divider(),
Row( Row(
children: [ children: [
SizedBox(width: 12,), SizedBox(width: 12),
Text('友情提示:负责填写作业实际开始时间', style: TextStyle(color: Colors.red),), Text(
'友情提示:负责填写作业实际开始时间',
style: TextStyle(color: Colors.red),
),
], ],
), ),
SizedBox(height: 5,), SizedBox(height: 5),
], ],
)
), ),
SizedBox(height: 15,), ),
SizedBox(height: 15),
_card( _card(
Column( Column(
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
@ -199,28 +274,63 @@ class _HotworkApplyDetailState extends State<HotworkApplyDetail> {
Divider(), Divider(),
Row( Row(
children: [ children: [
SizedBox(width: 12,), SizedBox(width: 12),
Text('友情提示:负责填写作业实际结束时间', style: TextStyle(color: Colors.red),), Text(
'友情提示:负责填写作业实际结束时间',
style: TextStyle(color: Colors.red),
),
], ],
), ),
SizedBox(height: 5,), SizedBox(height: 5),
], ],
)
), ),
SizedBox(height: 15,), ),
SizedBox(height: 15),
_card(_chooseItem('验收部门', '验收部门负责人', EditUserType.accept)), _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 @override
void initState() { void initState() {
// TODO: implement initState // TODO: implement initState
super.initState(); super.initState();
if (widget.HOTWORK_ID.length > 0) {
msg = 'edit';
_getData();
} }
}
} }

View File

@ -116,7 +116,7 @@ class _DhWorkListPageState extends State<DhWorkListPage> {
/// ///
void _handleApply() { void _handleApply() {
// //
Navigator.pushNamed(context, '/hotwork-apply-detail'); pushPage(HotworkApplyDetail(HOTWORK_ID: '', flow: widget.flow), context);
} }
/// ///
Future<void> _openFlowDrawer(String hotworkId) async { Future<void> _openFlowDrawer(String hotworkId) async {