2026-03-09 11:28:49 +08:00
|
|
|
|
import 'package:flutter/material.dart';
|
|
|
|
|
|
import 'package:lpinyin/lpinyin.dart';
|
|
|
|
|
|
import 'package:qhd_prevention/customWidget/search_bar_widget.dart';
|
2026-03-23 08:41:16 +08:00
|
|
|
|
import 'package:qhd_prevention/customWidget/toast_util.dart';
|
|
|
|
|
|
import 'package:qhd_prevention/http/modules/doorAndCar_api.dart';
|
2026-03-09 11:28:49 +08:00
|
|
|
|
import 'package:qhd_prevention/pages/my_appbar.dart';
|
|
|
|
|
|
|
|
|
|
|
|
// 人员模型类
|
|
|
|
|
|
class Person {
|
2026-03-23 08:41:16 +08:00
|
|
|
|
final String employeePersonUserId;
|
|
|
|
|
|
final String employeePersonUserName;
|
2026-03-09 11:28:49 +08:00
|
|
|
|
final String group;
|
|
|
|
|
|
|
2026-03-23 08:41:16 +08:00
|
|
|
|
final String personCorpId;
|
|
|
|
|
|
final String personCorpName;
|
|
|
|
|
|
final String personDepartmentId;
|
|
|
|
|
|
final String personDepartmentName;
|
|
|
|
|
|
final String userFaceUrl;
|
|
|
|
|
|
final String userPhone;
|
|
|
|
|
|
final String userCard;
|
|
|
|
|
|
|
|
|
|
|
|
Person({required this.employeePersonUserId, required this.employeePersonUserName, required this.group,
|
|
|
|
|
|
required this.personCorpId, required this.personCorpName, required this.personDepartmentId,
|
|
|
|
|
|
required this.personDepartmentName, required this.userFaceUrl, required this.userPhone,
|
|
|
|
|
|
required this.userCard, });
|
|
|
|
|
|
|
|
|
|
|
|
// 添加 toJson 方法
|
|
|
|
|
|
Map<String, dynamic> toJson() {
|
|
|
|
|
|
return {
|
|
|
|
|
|
'employeePersonUserId': employeePersonUserId,
|
|
|
|
|
|
'employeePersonUserName': employeePersonUserName,
|
|
|
|
|
|
'group': group,
|
|
|
|
|
|
'personCorpId': personCorpId,
|
|
|
|
|
|
'personCorpName': personCorpName,
|
|
|
|
|
|
'personDepartmentId': personDepartmentId,
|
|
|
|
|
|
'personDepartmentName': personDepartmentName,
|
|
|
|
|
|
'userFaceUrl': userFaceUrl,
|
|
|
|
|
|
'userPhone': userPhone,
|
|
|
|
|
|
'userCard': userCard,
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-09 11:28:49 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-23 08:41:16 +08:00
|
|
|
|
|
2026-03-09 11:28:49 +08:00
|
|
|
|
// 选择结果类
|
|
|
|
|
|
class SelectionPersonResult {
|
|
|
|
|
|
final List<Person> selectedPersons;
|
|
|
|
|
|
final Map<String, List<Person>> groupedSelected;
|
|
|
|
|
|
|
|
|
|
|
|
SelectionPersonResult({
|
|
|
|
|
|
required this.selectedPersons,
|
|
|
|
|
|
required this.groupedSelected,
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
class PersonSelectionPage extends StatefulWidget {
|
2026-03-23 08:41:16 +08:00
|
|
|
|
const PersonSelectionPage(this.oldList,this.id, {super.key,this.isMoreSelect=true,});
|
2026-03-09 11:28:49 +08:00
|
|
|
|
|
|
|
|
|
|
final List<Person> oldList;
|
2026-03-23 08:41:16 +08:00
|
|
|
|
final String id;
|
|
|
|
|
|
final bool isMoreSelect;//是否多选
|
2026-03-09 11:28:49 +08:00
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
|
State<PersonSelectionPage> createState() => _PersonSelectionPageState();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
class _PersonSelectionPageState extends State<PersonSelectionPage> {
|
|
|
|
|
|
// 字母表(将#放在最后)
|
|
|
|
|
|
final List<String> alphabet = [
|
|
|
|
|
|
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
|
|
|
|
|
|
'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '#'
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
// 人员数据
|
|
|
|
|
|
late final Map<String, List<Person>> groupedPersons;
|
|
|
|
|
|
// 存储排序后的分组键列表
|
|
|
|
|
|
late final List<String> sortedGroupKeys;
|
|
|
|
|
|
// 存储每个分组的GlobalKey
|
|
|
|
|
|
final Map<String, GlobalKey> groupKeys = {};
|
2026-03-23 08:41:16 +08:00
|
|
|
|
List< dynamic> list =[];
|
2026-03-09 11:28:49 +08:00
|
|
|
|
|
2026-03-23 08:41:16 +08:00
|
|
|
|
late Future<void> _dataFuture;
|
2026-03-09 11:28:49 +08:00
|
|
|
|
|
|
|
|
|
|
final Map<String, bool> selectedStates = {};
|
|
|
|
|
|
|
|
|
|
|
|
// 分组选中状态
|
|
|
|
|
|
final Map<String, bool> groupSelectedStates = {};
|
|
|
|
|
|
|
|
|
|
|
|
// 列表控制器
|
|
|
|
|
|
final ScrollController _scrollController = ScrollController();
|
|
|
|
|
|
final TextEditingController _searchController = TextEditingController();
|
|
|
|
|
|
|
|
|
|
|
|
// 存储每个分组在列表中的位置索引
|
|
|
|
|
|
late final Map<String, int> _groupIndexMap = {};
|
|
|
|
|
|
|
|
|
|
|
|
// 创建oldList的ID集合,用于快速查找
|
|
|
|
|
|
late final Set<String> _oldSelectedIds;
|
|
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
|
void initState() {
|
|
|
|
|
|
super.initState();
|
|
|
|
|
|
// 创建oldList的ID集合
|
2026-03-23 08:41:16 +08:00
|
|
|
|
_oldSelectedIds = widget.oldList.map((person) => person.employeePersonUserId).toSet();
|
|
|
|
|
|
_dataFuture = _initData();
|
|
|
|
|
|
|
2026-03-09 11:28:49 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-23 08:41:16 +08:00
|
|
|
|
Future<void> _initData() async {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
await _getProJectUserList(widget.id);
|
2026-03-09 11:28:49 +08:00
|
|
|
|
// 根据姓名首字母分组
|
|
|
|
|
|
groupedPersons = {};
|
|
|
|
|
|
|
|
|
|
|
|
for (var item in list) {
|
2026-03-26 15:23:39 +08:00
|
|
|
|
String id = item['userId']??'';
|
2026-03-23 08:41:16 +08:00
|
|
|
|
String name = item['userName']??'';
|
2026-03-09 11:28:49 +08:00
|
|
|
|
|
|
|
|
|
|
// 使用lpinyin获取姓名首字母
|
|
|
|
|
|
String firstLetter = _getFirstLetter(name);
|
|
|
|
|
|
|
|
|
|
|
|
// 创建Person对象
|
|
|
|
|
|
Person person = Person(
|
2026-03-23 08:41:16 +08:00
|
|
|
|
employeePersonUserId: id,
|
|
|
|
|
|
employeePersonUserName: name,
|
2026-03-09 11:28:49 +08:00
|
|
|
|
group: firstLetter,
|
2026-03-23 08:41:16 +08:00
|
|
|
|
|
|
|
|
|
|
personCorpId: item['deptId']??'',
|
|
|
|
|
|
personCorpName: item['deptName']??'',
|
|
|
|
|
|
personDepartmentId: item['deptId']??'',
|
|
|
|
|
|
personDepartmentName: item['deptName']??'',
|
|
|
|
|
|
userFaceUrl: item['deptName']??'',
|
|
|
|
|
|
userPhone: item['phone']??'',
|
|
|
|
|
|
userCard: item['deptName']??'',
|
2026-03-09 11:28:49 +08:00
|
|
|
|
);
|
|
|
|
|
|
|
2026-03-23 08:41:16 +08:00
|
|
|
|
|
2026-03-09 11:28:49 +08:00
|
|
|
|
// 添加到对应分组
|
|
|
|
|
|
if (!groupedPersons.containsKey(firstLetter)) {
|
|
|
|
|
|
groupedPersons[firstLetter] = [];
|
|
|
|
|
|
}
|
|
|
|
|
|
groupedPersons[firstLetter]!.add(person);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 对每个分组内的人员按姓名排序
|
|
|
|
|
|
groupedPersons.forEach((key, value) {
|
2026-03-23 08:41:16 +08:00
|
|
|
|
value.sort((a, b) => a.employeePersonUserName.compareTo(b.employeePersonUserName));
|
2026-03-09 11:28:49 +08:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 对分组键进行排序(确保#在最后)
|
|
|
|
|
|
sortedGroupKeys = groupedPersons.keys.toList()..sort((a, b) {
|
|
|
|
|
|
// 如果a是#,a应该在后面
|
|
|
|
|
|
if (a == '#') return 1;
|
|
|
|
|
|
// 如果b是#,b应该在后面
|
|
|
|
|
|
if (b == '#') return -1;
|
|
|
|
|
|
// 其他情况正常排序
|
|
|
|
|
|
return a.compareTo(b);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 构建分组索引映射
|
|
|
|
|
|
for (int i = 0; i < sortedGroupKeys.length; i++) {
|
|
|
|
|
|
_groupIndexMap[sortedGroupKeys[i]] = i;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 初始化选中状态和为每个分组创建GlobalKey
|
|
|
|
|
|
for (var group in groupedPersons.keys) {
|
|
|
|
|
|
for (var person in groupedPersons[group]!) {
|
|
|
|
|
|
// 检查当前人员是否在oldList中,如果在则设置为true
|
2026-03-23 08:41:16 +08:00
|
|
|
|
selectedStates[person.employeePersonUserId] = _oldSelectedIds.contains(person.employeePersonUserId);
|
2026-03-09 11:28:49 +08:00
|
|
|
|
}
|
|
|
|
|
|
// 初始化分组选中状态
|
|
|
|
|
|
groupSelectedStates[group] = false;
|
|
|
|
|
|
groupKeys[group] = GlobalKey();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 更新每个分组的全选状态
|
|
|
|
|
|
for (var group in groupedPersons.keys) {
|
|
|
|
|
|
_updateGroupSelection(group);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-23 08:41:16 +08:00
|
|
|
|
Future<void> _getProJectUserList(String id) async {
|
|
|
|
|
|
try {
|
|
|
|
|
|
|
|
|
|
|
|
final Map<String, dynamic> result = await DoorAndCarApi.getPeopleinProject(id);
|
|
|
|
|
|
|
|
|
|
|
|
if (result['success'] ) {
|
|
|
|
|
|
setState(() {
|
|
|
|
|
|
list = result['data'];
|
|
|
|
|
|
});
|
|
|
|
|
|
}else{
|
|
|
|
|
|
ToastUtil.showNormal(context, '加载数据失败');
|
|
|
|
|
|
// _showMessage('加载数据失败');
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
print('Error fetching data: $e');
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-03-09 11:28:49 +08:00
|
|
|
|
// 使用lpinyin获取姓名的首字母
|
|
|
|
|
|
String _getFirstLetter(String name) {
|
|
|
|
|
|
if (name.isEmpty) return '#';
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 检查第一个字符是否是英文字母
|
|
|
|
|
|
String firstChar = name.substring(0, 1);
|
|
|
|
|
|
RegExp englishLetter = RegExp(r'^[A-Za-z]$');
|
|
|
|
|
|
if (englishLetter.hasMatch(firstChar)) {
|
|
|
|
|
|
return firstChar.toUpperCase();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查第一个字符是否是数字或特殊字符
|
|
|
|
|
|
RegExp nonChinese = RegExp(r'^[0-9!@#\$%^&*()_+\-=\[\]{};:"\\|,.<>\/?~`]');
|
|
|
|
|
|
if (nonChinese.hasMatch(firstChar)) {
|
|
|
|
|
|
return '#';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取拼音 - 使用WITHOUT_TONE格式获取完整拼音
|
|
|
|
|
|
String pinyin = PinyinHelper.getPinyin(name,
|
|
|
|
|
|
separator: ' ', format: PinyinFormat.WITHOUT_TONE);
|
|
|
|
|
|
|
|
|
|
|
|
if (pinyin.isNotEmpty) {
|
|
|
|
|
|
// 获取第一个字的拼音首字母
|
|
|
|
|
|
List<String> pinyinParts = pinyin.split(' ');
|
|
|
|
|
|
if (pinyinParts.isNotEmpty) {
|
|
|
|
|
|
String firstCharPinyin = pinyinParts[0];
|
|
|
|
|
|
if (firstCharPinyin.isNotEmpty) {
|
|
|
|
|
|
String firstLetter = firstCharPinyin.substring(0, 1).toUpperCase();
|
|
|
|
|
|
|
|
|
|
|
|
// 检查是否是英文字母
|
|
|
|
|
|
if (englishLetter.hasMatch(firstLetter)) {
|
|
|
|
|
|
return firstLetter;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
// 异常处理,返回#
|
|
|
|
|
|
print('拼音转换错误: $e');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 如果无法识别,返回#
|
|
|
|
|
|
return '#';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 滚动到指定分组
|
|
|
|
|
|
void _scrollToGroup(String letter) {
|
|
|
|
|
|
if (!groupedPersons.containsKey(letter)) return;
|
|
|
|
|
|
|
|
|
|
|
|
// 方法1:使用索引跳转(更精确)
|
|
|
|
|
|
int? index = _groupIndexMap[letter];
|
|
|
|
|
|
if (index != null && _scrollController.hasClients) {
|
|
|
|
|
|
// 计算大概的滚动位置(每个分组标题高度 + 每个人item高度)
|
|
|
|
|
|
double position = 0;
|
|
|
|
|
|
for (int i = 0; i < index; i++) {
|
|
|
|
|
|
String group = sortedGroupKeys[i];
|
|
|
|
|
|
// 分组标题高度56,每个人item高度56
|
|
|
|
|
|
position += 56; // 分组标题高度
|
|
|
|
|
|
position += (groupedPersons[group]?.length ?? 0) * 56; // 人员列表高度
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
_scrollController.animateTo(
|
|
|
|
|
|
position,
|
|
|
|
|
|
duration: const Duration(milliseconds: 300),
|
|
|
|
|
|
curve: Curves.easeInOut,
|
|
|
|
|
|
);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 方法2:使用GlobalKey(备选方案)
|
|
|
|
|
|
final key = groupKeys[letter];
|
|
|
|
|
|
if (key?.currentContext != null) {
|
|
|
|
|
|
Scrollable.ensureVisible(
|
|
|
|
|
|
key!.currentContext!,
|
|
|
|
|
|
duration: const Duration(milliseconds: 300),
|
|
|
|
|
|
curve: Curves.easeInOut,
|
|
|
|
|
|
alignment: 0, // 滚动到顶部
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 更新分组的全选状态
|
|
|
|
|
|
void _updateGroupSelection(String group) {
|
|
|
|
|
|
final groupPersons = groupedPersons[group]!;
|
2026-03-23 08:41:16 +08:00
|
|
|
|
final allSelected = groupPersons.every((person) => selectedStates[person.employeePersonUserId] == true);
|
|
|
|
|
|
final anySelected = groupPersons.any((person) => selectedStates[person.employeePersonUserId] == true);
|
2026-03-09 11:28:49 +08:00
|
|
|
|
|
|
|
|
|
|
setState(() {
|
|
|
|
|
|
groupSelectedStates[group] = allSelected;
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 切换分组的全选/全不选
|
|
|
|
|
|
void _toggleGroupSelection(String group) {
|
|
|
|
|
|
final newState = !(groupSelectedStates[group] ?? false);
|
|
|
|
|
|
final groupPersons = groupedPersons[group]!;
|
|
|
|
|
|
|
|
|
|
|
|
setState(() {
|
|
|
|
|
|
groupSelectedStates[group] = newState;
|
|
|
|
|
|
for (var person in groupPersons) {
|
2026-03-23 08:41:16 +08:00
|
|
|
|
selectedStates[person.employeePersonUserId] = newState;
|
2026-03-09 11:28:49 +08:00
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取选中的人员
|
|
|
|
|
|
SelectionPersonResult _getSelectedResult() {
|
|
|
|
|
|
List<Person> selectedPersons = [];
|
|
|
|
|
|
Map<String, List<Person>> groupedSelected = {};
|
|
|
|
|
|
|
|
|
|
|
|
for (var group in groupedPersons.keys) {
|
|
|
|
|
|
final groupPersons = groupedPersons[group]!;
|
|
|
|
|
|
final selectedInGroup = groupPersons
|
2026-03-23 08:41:16 +08:00
|
|
|
|
.where((person) => selectedStates[person.employeePersonUserId] == true)
|
2026-03-09 11:28:49 +08:00
|
|
|
|
.toList();
|
|
|
|
|
|
|
|
|
|
|
|
if (selectedInGroup.isNotEmpty) {
|
|
|
|
|
|
groupedSelected[group] = selectedInGroup;
|
|
|
|
|
|
selectedPersons.addAll(selectedInGroup);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return SelectionPersonResult(
|
|
|
|
|
|
selectedPersons: selectedPersons,
|
|
|
|
|
|
groupedSelected: groupedSelected,
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 确认添加并返回
|
|
|
|
|
|
void _confirmSelection() {
|
|
|
|
|
|
final result = _getSelectedResult();
|
2026-03-23 08:41:16 +08:00
|
|
|
|
|
|
|
|
|
|
// ,如果没有选择人员,提示用户
|
|
|
|
|
|
if (result.selectedPersons.isEmpty) {
|
|
|
|
|
|
ToastUtil.showNormal(context, '请选择一个人');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-09 11:28:49 +08:00
|
|
|
|
Navigator.pop(context, result);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
|
Widget build(BuildContext context) {
|
|
|
|
|
|
return Scaffold(
|
|
|
|
|
|
backgroundColor: Colors.white,
|
|
|
|
|
|
appBar: MyAppbar(title: '选择人员', actions: [
|
|
|
|
|
|
|
|
|
|
|
|
TextButton(
|
|
|
|
|
|
onPressed: () {
|
|
|
|
|
|
_confirmSelection();
|
|
|
|
|
|
},
|
|
|
|
|
|
child: Text(
|
|
|
|
|
|
"确认添加",
|
|
|
|
|
|
style: TextStyle(color: Colors.white, fontSize: 16),
|
|
|
|
|
|
),
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
|
|
|
|
],),
|
2026-03-23 08:41:16 +08:00
|
|
|
|
body:
|
|
|
|
|
|
FutureBuilder(
|
|
|
|
|
|
future: _dataFuture,
|
|
|
|
|
|
builder: (context, snapshot) {
|
|
|
|
|
|
if (snapshot.connectionState != ConnectionState.done) {
|
|
|
|
|
|
return const Center(child: CircularProgressIndicator());
|
|
|
|
|
|
}
|
2026-03-09 11:28:49 +08:00
|
|
|
|
|
2026-03-23 08:41:16 +08:00
|
|
|
|
return Column(
|
|
|
|
|
|
children: [
|
|
|
|
|
|
Container(
|
|
|
|
|
|
color: Colors.white,
|
|
|
|
|
|
padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 8),
|
|
|
|
|
|
child: SearchBarWidget(
|
|
|
|
|
|
showResetButton: true,
|
|
|
|
|
|
hintText: '请输入关键字',
|
|
|
|
|
|
resetButtonText: '重置',
|
|
|
|
|
|
onReset: () {
|
|
|
|
|
|
FocusScope.of(context).unfocus();
|
|
|
|
|
|
_searchController.text = '';
|
|
|
|
|
|
_search();
|
|
|
|
|
|
},
|
|
|
|
|
|
onSearch: (text) {
|
|
|
|
|
|
FocusScope.of(context).unfocus();
|
|
|
|
|
|
_search();
|
|
|
|
|
|
},
|
|
|
|
|
|
controller: _searchController,
|
2026-03-09 11:28:49 +08:00
|
|
|
|
),
|
2026-03-23 08:41:16 +08:00
|
|
|
|
),
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 人员列表
|
|
|
|
|
|
Expanded(
|
|
|
|
|
|
child: Row(
|
|
|
|
|
|
children: [
|
|
|
|
|
|
// 左侧人员列表
|
|
|
|
|
|
Expanded(
|
|
|
|
|
|
child: ListView.builder(
|
|
|
|
|
|
controller: _scrollController,
|
|
|
|
|
|
itemCount: sortedGroupKeys.length,
|
|
|
|
|
|
itemBuilder: (context, index) {
|
|
|
|
|
|
String group = sortedGroupKeys[index];
|
|
|
|
|
|
List<Person> persons = groupedPersons[group]!;
|
|
|
|
|
|
|
|
|
|
|
|
return _buildGroupSection(group, persons);
|
|
|
|
|
|
},
|
|
|
|
|
|
),
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
|
|
|
|
// 右侧字母导航
|
|
|
|
|
|
Container(
|
|
|
|
|
|
width: 30,
|
|
|
|
|
|
color: Colors.grey.shade50,
|
|
|
|
|
|
child: ListView.builder(
|
|
|
|
|
|
itemCount: alphabet.length,
|
|
|
|
|
|
itemBuilder: (context, index) {
|
|
|
|
|
|
final letter = alphabet[index];
|
|
|
|
|
|
final hasGroup = groupedPersons.containsKey(letter);
|
|
|
|
|
|
|
|
|
|
|
|
return GestureDetector(
|
|
|
|
|
|
onTap: hasGroup ? () => _scrollToGroup(letter) : null,
|
|
|
|
|
|
child: Container(
|
|
|
|
|
|
height: 30,
|
|
|
|
|
|
alignment: Alignment.center,
|
|
|
|
|
|
child: Text(
|
|
|
|
|
|
letter,
|
|
|
|
|
|
style: TextStyle(
|
|
|
|
|
|
fontSize: 12,
|
|
|
|
|
|
color: hasGroup
|
|
|
|
|
|
? Colors.blue
|
|
|
|
|
|
: Colors.grey.shade400,
|
|
|
|
|
|
fontWeight: hasGroup
|
|
|
|
|
|
? FontWeight.bold
|
|
|
|
|
|
: FontWeight.normal,
|
|
|
|
|
|
),
|
|
|
|
|
|
),
|
2026-03-09 11:28:49 +08:00
|
|
|
|
),
|
2026-03-23 08:41:16 +08:00
|
|
|
|
);
|
|
|
|
|
|
},
|
|
|
|
|
|
),
|
|
|
|
|
|
),
|
|
|
|
|
|
],
|
2026-03-09 11:28:49 +08:00
|
|
|
|
),
|
2026-03-23 08:41:16 +08:00
|
|
|
|
),
|
|
|
|
|
|
],
|
|
|
|
|
|
);
|
|
|
|
|
|
},
|
2026-03-09 11:28:49 +08:00
|
|
|
|
),
|
2026-03-23 08:41:16 +08:00
|
|
|
|
|
2026-03-09 11:28:49 +08:00
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 构建分组区域
|
|
|
|
|
|
Widget _buildGroupSection(String group, List<Person> persons) {
|
|
|
|
|
|
return Container(
|
|
|
|
|
|
key: groupKeys[group],
|
|
|
|
|
|
child: Column(
|
|
|
|
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
|
|
|
|
children: [
|
|
|
|
|
|
// 分组标题
|
|
|
|
|
|
Container(
|
|
|
|
|
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
|
|
|
|
|
color: Colors.grey.shade100,
|
|
|
|
|
|
height: 56, // 固定高度便于计算
|
|
|
|
|
|
child: Row(
|
|
|
|
|
|
children: [
|
|
|
|
|
|
Text(
|
|
|
|
|
|
group,
|
|
|
|
|
|
style: const TextStyle(
|
|
|
|
|
|
fontSize: 16,
|
|
|
|
|
|
fontWeight: FontWeight.bold,
|
|
|
|
|
|
),
|
|
|
|
|
|
),
|
|
|
|
|
|
const Spacer(),
|
|
|
|
|
|
],
|
|
|
|
|
|
),
|
|
|
|
|
|
),
|
|
|
|
|
|
// 人员列表
|
|
|
|
|
|
...persons.map((person) => _buildPersonItem(person)),
|
|
|
|
|
|
],
|
|
|
|
|
|
),
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 构建人员项
|
|
|
|
|
|
Widget _buildPersonItem(Person person) {
|
|
|
|
|
|
return Container(
|
|
|
|
|
|
decoration: BoxDecoration(
|
|
|
|
|
|
border: Border(
|
|
|
|
|
|
bottom: BorderSide(
|
|
|
|
|
|
color: Colors.grey.shade200,
|
|
|
|
|
|
width: 0.5,
|
|
|
|
|
|
),
|
|
|
|
|
|
),
|
|
|
|
|
|
),
|
|
|
|
|
|
height: 56, // 固定高度便于计算
|
|
|
|
|
|
child: CheckboxListTile(
|
|
|
|
|
|
title: Text(
|
2026-03-23 08:41:16 +08:00
|
|
|
|
person.employeePersonUserName,
|
2026-03-09 11:28:49 +08:00
|
|
|
|
style: const TextStyle(fontSize: 14),
|
|
|
|
|
|
),
|
2026-03-23 08:41:16 +08:00
|
|
|
|
value: selectedStates[person.employeePersonUserId] ?? false,
|
2026-03-09 11:28:49 +08:00
|
|
|
|
onChanged: (bool? value) {
|
|
|
|
|
|
setState(() {
|
2026-03-23 08:41:16 +08:00
|
|
|
|
|
|
|
|
|
|
if(widget.isMoreSelect){
|
|
|
|
|
|
selectedStates[person.employeePersonUserId] = value ?? false;
|
2026-03-26 15:23:39 +08:00
|
|
|
|
_updateGroupSelection(person.group);
|
2026-03-23 08:41:16 +08:00
|
|
|
|
}else{
|
|
|
|
|
|
// 清空所有选中状态
|
|
|
|
|
|
for (var group in groupedPersons.keys) {
|
|
|
|
|
|
for (var p in groupedPersons[group]!) {
|
|
|
|
|
|
selectedStates[p.employeePersonUserId] = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
// 设置当前选中
|
|
|
|
|
|
selectedStates[person.employeePersonUserId] = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-26 15:23:39 +08:00
|
|
|
|
|
2026-03-23 08:41:16 +08:00
|
|
|
|
|
2026-03-09 11:28:49 +08:00
|
|
|
|
});
|
|
|
|
|
|
},
|
|
|
|
|
|
activeColor: Colors.blue,
|
|
|
|
|
|
controlAffinity: ListTileControlAffinity.leading,
|
|
|
|
|
|
contentPadding: const EdgeInsets.symmetric(horizontal: 0),
|
|
|
|
|
|
),
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void _search() {
|
|
|
|
|
|
// searchKeywords = _searchController.text.trim();
|
|
|
|
|
|
// currentPage = 1;
|
|
|
|
|
|
// list.clear();
|
|
|
|
|
|
// _fetchData();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|