QinGang_interested/lib/pages/home/doorAndCar/person_selection_page.dart

496 lines
14 KiB
Dart
Raw Permalink Normal View History

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';
import 'package:qhd_prevention/pages/my_appbar.dart';
// 人员模型类
class Person {
final String id;
final String name;
final String group;
Person({required this.id, required this.name, required this.group});
}
// 选择结果类
class SelectionPersonResult {
final List<Person> selectedPersons;
final Map<String, List<Person>> groupedSelected;
SelectionPersonResult({
required this.selectedPersons,
required this.groupedSelected,
});
}
class PersonSelectionPage extends StatefulWidget {
const PersonSelectionPage(this.oldList, {super.key});
final List<Person> oldList;
@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 = {};
final List<Map<String, dynamic>> list = [
{
'id': 'p1',
'name': '',
},
{
'id': 'p2',
'name': '',
},
{
'id': 'p3',
'name': '',
},
{
'id': 'p4',
'name': '',
},
{
'id': 'p5',
'name': '',
},
{
'id': 'p6',
'name': '',
},
{
'id': 'p7',
'name': '',
},
{
'id': 'p8',
'name': '',
},
{
'id': 'p9',
'name': '',
},
{
'id': 'p10',
'name': '饿',
},
{
'id': 'p11',
'name': '',
},
{
'id': 'p12',
'name': '',
},
{
'id': 'p13',
'name': '123', // 测试数字
},
{
'id': 'p14',
'name': '@@@', // 测试特殊字符
},
];
// 选中状态
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集合
_oldSelectedIds = widget.oldList.map((person) => person.id).toSet();
_initData();
}
void _initData() {
// 根据姓名首字母分组
groupedPersons = {};
for (var item in list) {
String name = item['name'];
String id = item['id'];
// 使用lpinyin获取姓名首字母
String firstLetter = _getFirstLetter(name);
// 创建Person对象
Person person = Person(
id: id,
name: name,
group: firstLetter,
);
// 添加到对应分组
if (!groupedPersons.containsKey(firstLetter)) {
groupedPersons[firstLetter] = [];
}
groupedPersons[firstLetter]!.add(person);
}
// 对每个分组内的人员按姓名排序
groupedPersons.forEach((key, value) {
value.sort((a, b) => a.name.compareTo(b.name));
});
// 对分组键进行排序(确保#在最后)
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
selectedStates[person.id] = _oldSelectedIds.contains(person.id);
}
// 初始化分组选中状态
groupSelectedStates[group] = false;
groupKeys[group] = GlobalKey();
}
// 更新每个分组的全选状态
for (var group in groupedPersons.keys) {
_updateGroupSelection(group);
}
}
// 使用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]!;
final allSelected = groupPersons.every((person) => selectedStates[person.id] == true);
final anySelected = groupPersons.any((person) => selectedStates[person.id] == true);
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) {
selectedStates[person.id] = newState;
}
});
}
// 获取选中的人员
SelectionPersonResult _getSelectedResult() {
List<Person> selectedPersons = [];
Map<String, List<Person>> groupedSelected = {};
for (var group in groupedPersons.keys) {
final groupPersons = groupedPersons[group]!;
final selectedInGroup = groupPersons
.where((person) => selectedStates[person.id] == true)
.toList();
if (selectedInGroup.isNotEmpty) {
groupedSelected[group] = selectedInGroup;
selectedPersons.addAll(selectedInGroup);
}
}
return SelectionPersonResult(
selectedPersons: selectedPersons,
groupedSelected: groupedSelected,
);
}
// 确认添加并返回
void _confirmSelection() {
final result = _getSelectedResult();
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),
),
),
],),
body: 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,
),
),
// 人员列表
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,
),
),
),
);
},
),
),
],
),
),
],
),
);
}
// 构建分组区域
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(
person.name,
style: const TextStyle(fontSize: 14),
),
value: selectedStates[person.id] ?? false,
onChanged: (bool? value) {
setState(() {
selectedStates[person.id] = value ?? false;
_updateGroupSelection(person.group);
});
},
activeColor: Colors.blue,
controlAffinity: ListTileControlAffinity.leading,
contentPadding: const EdgeInsets.symmetric(horizontal: 0),
),
);
}
void _search() {
// searchKeywords = _searchController.text.trim();
// currentPage = 1;
// list.clear();
// _fetchData();
}
}