部门人员多级选择器

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(
padding: const EdgeInsets.symmetric(horizontal: 12),
child: SearchBarWidget(
controller: _searchController,
isShowSearchButton: false,
onSearch: (keyboard) {

View File

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

View File

@ -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 {
),
);
}
}

View File

@ -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<HotworkApplyDetail> {
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() {
return Column(
@ -102,6 +122,7 @@ class _HotworkApplyDetailState extends State<HotworkApplyDetail> {
],
);
}
Widget _card(Widget child) {
return Container(
decoration: BoxDecoration(
@ -111,37 +132,87 @@ class _HotworkApplyDetailState extends State<HotworkApplyDetail> {
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<Map<String, dynamic>>.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<void> _submit(String STATUS) async {
// '1' 0
}
///
Future<void> _getData() async {
// '1' 0
}
@override
Widget build(BuildContext context) {
return Scaffold(
@ -152,45 +223,49 @@ class _HotworkApplyDetailState extends State<HotworkApplyDetail> {
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<HotworkApplyDetail> {
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();
}
}
}

View File

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