789 lines
24 KiB
Dart
789 lines
24 KiB
Dart
// pubspec.yaml 需要添加依赖:lpinyin: ^2.0.3
|
||
import 'package:flutter/cupertino.dart';
|
||
import 'package:flutter/material.dart';
|
||
import 'package:lpinyin/lpinyin.dart';
|
||
import 'package:qhd_prevention/customWidget/search_bar_widget.dart';
|
||
|
||
|
||
/// 用户数据模型
|
||
class Person {
|
||
final String userId;
|
||
final String name;
|
||
final String departmentName;
|
||
final String phone;
|
||
final String postName;
|
||
final Map<String, dynamic>? raw;
|
||
|
||
Person({
|
||
required this.userId,
|
||
required this.name,
|
||
required this.departmentName,
|
||
required this.phone,
|
||
this.postName = '',
|
||
this.raw,
|
||
});
|
||
|
||
factory Person.fromJson(dynamic json) {
|
||
if (json is Map) {
|
||
return Person(
|
||
userId: (json['id'] ?? json['actUser'] ?? '').toString(),
|
||
name: (json['name'] ?? json['actUserName'] ??'').toString(),
|
||
departmentName: (json['departmentName'] ?? json['actUserDepartmentName'] ?? '').toString(),
|
||
phone: (json['phone'] ?? '').toString(),
|
||
postName: (json['postName'] ?? '').toString(),
|
||
raw: Map<String, dynamic>.from(json),
|
||
);
|
||
} else {
|
||
final s = json?.toString() ?? '';
|
||
return Person(
|
||
userId: s,
|
||
name: s,
|
||
departmentName: '',
|
||
phone: '',
|
||
postName: '',
|
||
raw: null,
|
||
);
|
||
}
|
||
}
|
||
}
|
||
|
||
/// 回调 typedef(保留原有)
|
||
typedef PersonSelectCallback = void Function(String userId, String name);
|
||
typedef PersonSelectCallbackWithIndex = void Function(String userId, String name, int index);
|
||
typedef PersonSelectCallbackWithData = void Function(String userId, String name, Map<String, dynamic> data);
|
||
typedef PersonSelectCallbackWithIndexAndData = void Function(
|
||
String userId,
|
||
String name,
|
||
int index,
|
||
Map<String, dynamic> data,
|
||
);
|
||
typedef PersonMultiSelectCallback = void Function(List<Map<String, dynamic>> selectedRawList);
|
||
|
||
/// DepartmentAllPersonPicker
|
||
class DepartmentAllPersonPicker {
|
||
/// show 参数说明
|
||
/// - personsData:第一个 Tab(分公司)
|
||
/// - serverData:第二个 Tab(相关方)
|
||
/// - allowXgfFlag:false 时不显示 Tab,仅显示 personsData
|
||
static Future<void> show(
|
||
BuildContext context, {
|
||
required List<dynamic> personsData,
|
||
List<dynamic>? serverData,
|
||
bool allowXgfFlag = true,
|
||
bool multiSelect = false,
|
||
PersonMultiSelectCallback? onMultiSelected,
|
||
PersonSelectCallback? onSelected,
|
||
PersonSelectCallbackWithIndex? onSelectedWithIndex,
|
||
PersonSelectCallbackWithData? onSelectedWithData,
|
||
PersonSelectCallbackWithIndexAndData? onSelectedWithIndexWithData,
|
||
}) async {
|
||
assert(
|
||
(!multiSelect &&
|
||
(onSelected != null ||
|
||
onSelectedWithIndex != null ||
|
||
onSelectedWithData != null ||
|
||
onSelectedWithIndexWithData != null)) ||
|
||
(multiSelect &&
|
||
(onMultiSelected != null ||
|
||
onSelectedWithData != null ||
|
||
onSelectedWithIndexWithData != null ||
|
||
onSelectedWithIndex != null ||
|
||
onSelected != null)),
|
||
'请至少传入一个回调(单选或多选)',
|
||
);
|
||
|
||
await showModalBottomSheet(
|
||
context: context,
|
||
isScrollControlled: true,
|
||
backgroundColor: Colors.transparent,
|
||
builder: (_) => _DepartmentAllPersonPickerSheet(
|
||
personsData: personsData,
|
||
serverData: serverData,
|
||
allowXgfFlag: allowXgfFlag,
|
||
multiSelect: multiSelect,
|
||
onMultiSelected: onMultiSelected,
|
||
onSelected: onSelected,
|
||
onSelectedWithIndex: onSelectedWithIndex,
|
||
onSelectedWithData: onSelectedWithData,
|
||
onSelectedWithIndexWithData: onSelectedWithIndexWithData,
|
||
),
|
||
);
|
||
}
|
||
}
|
||
|
||
class _DepartmentAllPersonPickerSheet extends StatefulWidget {
|
||
final List<dynamic> personsData;
|
||
final List<dynamic>? serverData;
|
||
final bool allowXgfFlag;
|
||
final bool multiSelect;
|
||
|
||
final PersonMultiSelectCallback? onMultiSelected;
|
||
final PersonSelectCallback? onSelected;
|
||
final PersonSelectCallbackWithIndex? onSelectedWithIndex;
|
||
final PersonSelectCallbackWithData? onSelectedWithData;
|
||
final PersonSelectCallbackWithIndexAndData? onSelectedWithIndexWithData;
|
||
|
||
const _DepartmentAllPersonPickerSheet({
|
||
required this.personsData,
|
||
required this.serverData,
|
||
required this.allowXgfFlag,
|
||
required this.multiSelect,
|
||
required this.onMultiSelected,
|
||
required this.onSelected,
|
||
required this.onSelectedWithIndex,
|
||
required this.onSelectedWithData,
|
||
required this.onSelectedWithIndexWithData,
|
||
});
|
||
|
||
@override
|
||
State<_DepartmentAllPersonPickerSheet> createState() => _DepartmentAllPersonPickerSheetState();
|
||
}
|
||
|
||
class _DepartmentAllPersonPickerSheetState extends State<_DepartmentAllPersonPickerSheet>
|
||
with SingleTickerProviderStateMixin {
|
||
late final List<Person> _personAll;
|
||
late final List<Person> _serverAll;
|
||
|
||
List<Person> _personFiltered = [];
|
||
List<Person> _serverFiltered = [];
|
||
|
||
final TextEditingController _searchController = TextEditingController();
|
||
|
||
final ScrollController _personController = ScrollController();
|
||
final ScrollController _serverController = ScrollController();
|
||
|
||
TabController? _tabController;
|
||
|
||
final Map<int, String> _currentLetters = {0: '', 1: ''};
|
||
|
||
String _singleSelectedKey = '';
|
||
String _singleSelectedId = '';
|
||
String _singleSelectedName = '';
|
||
Person? _singleSelectedPerson;
|
||
int _singleSelectedSourceIndex = -1;
|
||
String _singleSelectedSource = '';
|
||
|
||
final Set<String> _multiSelectedKeys = <String>{};
|
||
|
||
static const double _headerHeight = 40.0;
|
||
static const double _itemHeight = 50.0;
|
||
static const double _rightReservedWidth = 30.0;
|
||
static const double _trailingWidth = 56.0;
|
||
|
||
bool get _showTabs => widget.allowXgfFlag;
|
||
|
||
@override
|
||
void initState() {
|
||
super.initState();
|
||
// personsData去重
|
||
final Map<dynamic, dynamic> uniqueMap = {};
|
||
|
||
for (final item in widget.personsData) {
|
||
if (item is Map<String, dynamic>) {
|
||
final key = item['id'] ?? item['actUser']; // id去重
|
||
uniqueMap[key] = item;
|
||
}
|
||
}
|
||
|
||
final List<dynamic> list = uniqueMap.values.toList();
|
||
_personAll = list.map((e) => Person.fromJson(e)).toList();
|
||
|
||
|
||
_serverAll = (widget.serverData ?? []).map((e) => Person.fromJson(e)).toList();
|
||
|
||
_personFiltered = List.from(_personAll);
|
||
_serverFiltered = List.from(_serverAll);
|
||
|
||
if (_showTabs) {
|
||
_tabController = TabController(length: 2, vsync: this);
|
||
_tabController!.addListener(_onTabChanged);
|
||
}
|
||
|
||
_searchController.addListener(_onSearchChanged);
|
||
_personController.addListener(() => _updateCurrentLetter(0));
|
||
_serverController.addListener(() => _updateCurrentLetter(1));
|
||
}
|
||
|
||
@override
|
||
void dispose() {
|
||
_searchController.removeListener(_onSearchChanged);
|
||
_searchController.dispose();
|
||
|
||
_personController.dispose();
|
||
_serverController.dispose();
|
||
|
||
_tabController?.removeListener(_onTabChanged);
|
||
_tabController?.dispose();
|
||
|
||
super.dispose();
|
||
}
|
||
|
||
void _onTabChanged() {
|
||
setState(() {});
|
||
}
|
||
|
||
String _normalize(String? value) => (value ?? '').toLowerCase().trim();
|
||
|
||
bool _matchesQuery(Person p, String q) {
|
||
if (q.isEmpty) return true;
|
||
|
||
final name = _normalize(p.name);
|
||
final phone = _normalize(p.phone);
|
||
final departmentName = _normalize(p.departmentName);
|
||
final postName = _normalize(p.postName);
|
||
|
||
return name.contains(q) ||
|
||
phone.contains(q) ||
|
||
departmentName.contains(q) ||
|
||
postName.contains(q);
|
||
}
|
||
|
||
void _onSearchChanged() {
|
||
final q = _normalize(_searchController.text);
|
||
|
||
setState(() {
|
||
_personFiltered = _personAll.where((p) => _matchesQuery(p, q)).toList();
|
||
_serverFiltered = _serverAll.where((p) => _matchesQuery(p, q)).toList();
|
||
});
|
||
|
||
_updateCurrentLetter(0);
|
||
_updateCurrentLetter(1);
|
||
}
|
||
String _itemKey(String source, String id) => '$source::$id';
|
||
|
||
String _getInitial(String name) {
|
||
final trimmed = name.trim();
|
||
if (trimmed.isEmpty) return '#';
|
||
final first = trimmed[0];
|
||
if (RegExp(r'[A-Za-z]').hasMatch(first)) return first.toUpperCase();
|
||
if (RegExp(r'[\u4e00-\u9fff]').hasMatch(first)) {
|
||
try {
|
||
final short = PinyinHelper.getShortPinyin(trimmed);
|
||
if (short.isNotEmpty) return short[0].toUpperCase();
|
||
} catch (_) {}
|
||
}
|
||
return '#';
|
||
}
|
||
|
||
bool _detectIsRelated(dynamic raw) {
|
||
try {
|
||
if (raw is Map) {
|
||
if (raw.containsKey('isXgf') || raw.containsKey('isxgf')) {
|
||
final v = raw['isXgf'] ?? raw['isxgf'];
|
||
if (v is bool) return v;
|
||
if (v is num) return v != 0;
|
||
if (v is String) return v == '1' || v.toLowerCase() == 'true';
|
||
}
|
||
if (raw.containsKey('isRelated') || raw.containsKey('isrelated') || raw.containsKey('related')) {
|
||
final v = raw['isRelated'] ?? raw['isrelated'] ?? raw['related'];
|
||
if (v is bool) return v;
|
||
if (v is num) return v != 0;
|
||
if (v is String) return v == '1' || v.toLowerCase() == 'true';
|
||
}
|
||
final dept = (raw['departmentName'] ?? raw['deptName'] ?? raw['department'] ?? '').toString();
|
||
if (dept.contains('相关')) return true;
|
||
if (raw.containsKey('allowXgfFlag')) {
|
||
final v = raw['allowXgfFlag'];
|
||
if (v is bool) return !v;
|
||
if (v is num) return v == 0;
|
||
if (v is String) return v == '0' || v.toLowerCase() == 'false';
|
||
}
|
||
}
|
||
} catch (_) {}
|
||
return false;
|
||
}
|
||
|
||
bool _canSelectByAllowFlag(dynamic raw) {
|
||
if (widget.allowXgfFlag) return true;
|
||
return !_detectIsRelated(raw);
|
||
}
|
||
|
||
Map<String, List<Person>> _buildGroupMap(List<Person> source) {
|
||
final Map<String, List<Person>> map = {};
|
||
for (final p in source) {
|
||
final key = _getInitial(p.name);
|
||
map.putIfAbsent(key, () => []).add(p);
|
||
}
|
||
for (final k in map.keys) {
|
||
map[k]!.sort((a, b) => a.name.compareTo(b.name));
|
||
}
|
||
return map;
|
||
}
|
||
|
||
List<String> _orderedKeys(Map<String, List<Person>> groupMap) {
|
||
final alphaKeys = groupMap.keys
|
||
.where((k) => RegExp(r'^[A-Z]$').hasMatch(k))
|
||
.toList()
|
||
..sort();
|
||
final otherKeys = groupMap.keys
|
||
.where((k) => !RegExp(r'^[A-Z]$').hasMatch(k))
|
||
.toList()
|
||
..sort();
|
||
return [...alphaKeys, ...otherKeys];
|
||
}
|
||
|
||
Map<String, double> _computeOffsets(List<String> keys, Map<String, List<Person>> map) {
|
||
final offsets = <String, double>{};
|
||
double cursor = 0.0;
|
||
for (final k in keys) {
|
||
offsets[k] = cursor;
|
||
cursor += _headerHeight + map[k]!.length * _itemHeight;
|
||
}
|
||
return offsets;
|
||
}
|
||
|
||
void _updateCurrentLetter(int tabIndex) {
|
||
final source = tabIndex == 0 ? _personFiltered : _serverFiltered;
|
||
final controller = tabIndex == 0 ? _personController : _serverController;
|
||
|
||
if (!controller.hasClients) return;
|
||
if (source.isEmpty) {
|
||
if (_currentLetters[tabIndex] != '') {
|
||
setState(() => _currentLetters[tabIndex] = '');
|
||
}
|
||
return;
|
||
}
|
||
|
||
final groupMap = _buildGroupMap(source);
|
||
final keys = _orderedKeys(groupMap);
|
||
if (keys.isEmpty) return;
|
||
|
||
final offsets = _computeOffsets(keys, groupMap);
|
||
final pos = controller.position.pixels;
|
||
|
||
String found = keys.first;
|
||
for (final k in keys) {
|
||
final off = offsets[k] ?? double.infinity;
|
||
if (pos >= off) {
|
||
found = k;
|
||
} else {
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (_currentLetters[tabIndex] != found) {
|
||
setState(() {
|
||
_currentLetters[tabIndex] = found;
|
||
});
|
||
}
|
||
}
|
||
|
||
Future<void> _scrollToLetter(
|
||
String letter, {
|
||
required int tabIndex,
|
||
required List<String> keys,
|
||
required Map<String, double> offsets,
|
||
}) async {
|
||
final controller = tabIndex == 0 ? _personController : _serverController;
|
||
final targetOffset = offsets[letter] ?? 0.0;
|
||
|
||
if (!controller.hasClients) return;
|
||
|
||
try {
|
||
await controller.animateTo(
|
||
targetOffset,
|
||
duration: const Duration(milliseconds: 200),
|
||
curve: Curves.easeInOut,
|
||
);
|
||
} catch (_) {}
|
||
|
||
setState(() {
|
||
_currentLetters[tabIndex] = letter;
|
||
});
|
||
}
|
||
|
||
String _buildTitleText(Person person) {
|
||
if (person.postName.isEmpty && person.phone.isEmpty && person.departmentName.isEmpty) return '';
|
||
final postPart = person.postName.isNotEmpty ? '/${person.postName}' : '';
|
||
return '${person.name}-${person.phone}(${person.departmentName}$postPart)';
|
||
}
|
||
|
||
void _onItemTap({
|
||
required Person item,
|
||
required int sourceIndex,
|
||
required String source,
|
||
}) {
|
||
final raw = item.raw;
|
||
if (!_canSelectByAllowFlag(raw)) return;
|
||
|
||
final key = _itemKey(source, item.userId);
|
||
|
||
setState(() {
|
||
if (widget.multiSelect) {
|
||
if (_multiSelectedKeys.contains(key)) {
|
||
_multiSelectedKeys.remove(key);
|
||
} else {
|
||
_multiSelectedKeys.add(key);
|
||
}
|
||
} else {
|
||
_singleSelectedKey = key;
|
||
_singleSelectedId = item.userId;
|
||
_singleSelectedName = item.name;
|
||
_singleSelectedPerson = item;
|
||
_singleSelectedSourceIndex = sourceIndex;
|
||
_singleSelectedSource = source;
|
||
}
|
||
});
|
||
}
|
||
|
||
bool _confirmEnabled() {
|
||
if (widget.multiSelect) {
|
||
return _multiSelectedKeys.isNotEmpty;
|
||
}
|
||
return _singleSelectedKey.isNotEmpty;
|
||
}
|
||
|
||
Map<String, dynamic> _toDataMap(Person person) {
|
||
return person.raw ??
|
||
<String, dynamic>{
|
||
'id': person.userId,
|
||
'name': person.name,
|
||
'departmentName': person.departmentName,
|
||
'phone': person.phone,
|
||
'postName': person.postName,
|
||
};
|
||
}
|
||
|
||
Person? _findPersonByKey(String key) {
|
||
final parts = key.split('::');
|
||
if (parts.length != 2) return null;
|
||
final source = parts[0];
|
||
final id = parts[1];
|
||
|
||
if (source == 'person') {
|
||
for (final p in _personAll) {
|
||
if (p.userId == id) return p;
|
||
}
|
||
} else if (source == 'server') {
|
||
for (final p in _serverAll) {
|
||
if (p.userId == id) return p;
|
||
}
|
||
}
|
||
return null;
|
||
}
|
||
|
||
Widget _buildGroupedList({
|
||
required List<Person> source,
|
||
required ScrollController controller,
|
||
required int tabIndex,
|
||
required String emptyText,
|
||
}) {
|
||
if (source.isEmpty) {
|
||
return Center(
|
||
child: Text(
|
||
emptyText,
|
||
style: const TextStyle(fontSize: 14, color: Colors.grey),
|
||
),
|
||
);
|
||
}
|
||
|
||
final groupMap = _buildGroupMap(source);
|
||
final keys = _orderedKeys(groupMap);
|
||
final offsets = _computeOffsets(keys, groupMap);
|
||
final currentLetter = _currentLetters[tabIndex] ?? '';
|
||
|
||
return Stack(
|
||
children: [
|
||
ListView(
|
||
controller: controller,
|
||
padding: EdgeInsets.zero,
|
||
children: keys.expand((k) {
|
||
final items = groupMap[k]!;
|
||
return <Widget>[
|
||
Container(
|
||
height: _headerHeight,
|
||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||
color: Colors.grey[200],
|
||
alignment: Alignment.centerLeft,
|
||
child: Text(
|
||
k,
|
||
style: const TextStyle(fontWeight: FontWeight.bold),
|
||
),
|
||
),
|
||
...items.asMap().entries.map((entry) {
|
||
final idx = entry.key;
|
||
final item = entry.value;
|
||
final sourceIndex = source.indexWhere((p) => p.userId == item.userId);
|
||
final raw = item.raw;
|
||
final isRelated = !_canSelectByAllowFlag(raw);
|
||
final selectedKey = _itemKey(tabIndex == 0 ? 'person' : 'server', item.userId);
|
||
final selected = widget.multiSelect
|
||
? _multiSelectedKeys.contains(selectedKey)
|
||
: (_singleSelectedKey == selectedKey);
|
||
|
||
return InkWell(
|
||
onTap: isRelated
|
||
? null
|
||
: () => _onItemTap(
|
||
item: item,
|
||
sourceIndex: sourceIndex >= 0 ? sourceIndex : idx,
|
||
source: tabIndex == 0 ? 'person' : 'server',
|
||
),
|
||
child: Container(
|
||
height: _itemHeight,
|
||
padding: const EdgeInsets.only(left: 16, right: _rightReservedWidth),
|
||
alignment: Alignment.centerLeft,
|
||
color: isRelated ? Colors.grey[50] : Colors.white,
|
||
child: Row(
|
||
children: [
|
||
Expanded(
|
||
child: Text(
|
||
_buildTitleText(item),
|
||
style: TextStyle(
|
||
color: isRelated ? Colors.grey : Colors.black,
|
||
),
|
||
),
|
||
),
|
||
SizedBox(
|
||
width: _trailingWidth,
|
||
child: selected
|
||
? const Align(
|
||
alignment: Alignment.centerRight,
|
||
child: Icon(Icons.check, color: Colors.blue),
|
||
)
|
||
: const SizedBox.shrink(),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
);
|
||
}),
|
||
];
|
||
}).toList(),
|
||
),
|
||
if (keys.isNotEmpty)
|
||
Positioned(
|
||
right: 4,
|
||
top: 100,
|
||
bottom: 100,
|
||
child: Column(
|
||
mainAxisSize: MainAxisSize.min,
|
||
children: keys.map((letter) {
|
||
final isActive = letter == currentLetter;
|
||
return GestureDetector(
|
||
onTap: () => _scrollToLetter(
|
||
letter,
|
||
tabIndex: tabIndex,
|
||
keys: keys,
|
||
offsets: offsets,
|
||
),
|
||
child: Container(
|
||
margin: const EdgeInsets.symmetric(vertical: 2),
|
||
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
|
||
child: Text(
|
||
letter,
|
||
style: TextStyle(
|
||
fontSize: 12,
|
||
color: isActive ? Colors.blue : Colors.black54,
|
||
),
|
||
),
|
||
),
|
||
);
|
||
}).toList(),
|
||
),
|
||
),
|
||
],
|
||
);
|
||
}
|
||
|
||
void _confirm() {
|
||
if (!mounted) return;
|
||
|
||
if (widget.multiSelect) {
|
||
final selectedRaw = _multiSelectedKeys.map((key) {
|
||
final person = _findPersonByKey(key);
|
||
if (person != null) {
|
||
return _toDataMap(person);
|
||
}
|
||
final parts = key.split('::');
|
||
final id = parts.length == 2 ? parts[1] : key;
|
||
return <String, dynamic>{
|
||
'id': id,
|
||
'username': '',
|
||
};
|
||
}).toList();
|
||
|
||
if (widget.onMultiSelected != null) {
|
||
widget.onMultiSelected!(selectedRaw);
|
||
return;
|
||
}
|
||
|
||
if (_multiSelectedKeys.isEmpty) return;
|
||
final firstKey = _multiSelectedKeys.first;
|
||
final firstPerson = _findPersonByKey(firstKey);
|
||
final firstId = firstPerson?.userId ?? '';
|
||
final firstName = firstPerson?.name ?? '';
|
||
|
||
if (firstPerson != null) {
|
||
final idx = firstKey.startsWith('person::')
|
||
? _personAll.indexWhere((p) => p.userId == firstId)
|
||
: _serverAll.indexWhere((p) => p.userId == firstId);
|
||
|
||
final dataMap = _toDataMap(firstPerson);
|
||
|
||
if (widget.onSelectedWithIndexWithData != null) {
|
||
widget.onSelectedWithIndexWithData!(firstId, firstName, idx, dataMap);
|
||
return;
|
||
}
|
||
if (widget.onSelectedWithData != null) {
|
||
widget.onSelectedWithData!(firstId, firstName, dataMap);
|
||
return;
|
||
}
|
||
if (widget.onSelectedWithIndex != null) {
|
||
widget.onSelectedWithIndex!(firstId, firstName, idx);
|
||
return;
|
||
}
|
||
if (widget.onSelected != null) {
|
||
widget.onSelected!(firstId, firstName);
|
||
return;
|
||
}
|
||
}
|
||
return;
|
||
}
|
||
|
||
final selectedPerson = _singleSelectedPerson;
|
||
if (selectedPerson == null) return;
|
||
|
||
final dataMap = _toDataMap(selectedPerson);
|
||
|
||
if (widget.onSelectedWithIndexWithData != null) {
|
||
widget.onSelectedWithIndexWithData!(
|
||
selectedPerson.userId,
|
||
selectedPerson.name,
|
||
_singleSelectedSourceIndex,
|
||
dataMap,
|
||
);
|
||
return;
|
||
}
|
||
if (widget.onSelectedWithData != null) {
|
||
widget.onSelectedWithData!(
|
||
selectedPerson.userId,
|
||
selectedPerson.name,
|
||
dataMap,
|
||
);
|
||
return;
|
||
}
|
||
if (widget.onSelectedWithIndex != null) {
|
||
widget.onSelectedWithIndex!(
|
||
selectedPerson.userId,
|
||
selectedPerson.name,
|
||
_singleSelectedSourceIndex,
|
||
);
|
||
return;
|
||
}
|
||
if (widget.onSelected != null) {
|
||
widget.onSelected!(selectedPerson.userId, selectedPerson.name);
|
||
return;
|
||
}
|
||
}
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
final showTabs = _showTabs;
|
||
final tabIndex = _tabController?.index ?? 0;
|
||
|
||
final personGroupMap = _buildGroupMap(_personFiltered);
|
||
final serverGroupMap = _buildGroupMap(_serverFiltered);
|
||
|
||
final personKeys = _orderedKeys(personGroupMap);
|
||
final serverKeys = _orderedKeys(serverGroupMap);
|
||
|
||
final personOffsets = _computeOffsets(personKeys, personGroupMap);
|
||
final serverOffsets = _computeOffsets(serverKeys, serverGroupMap);
|
||
|
||
final personListWidget = _buildGroupedList(
|
||
source: _personFiltered,
|
||
controller: _personController,
|
||
tabIndex: 0,
|
||
emptyText: '暂无数据',
|
||
);
|
||
|
||
final serverListWidget = _buildGroupedList(
|
||
source: _serverFiltered,
|
||
controller: _serverController,
|
||
tabIndex: 1,
|
||
emptyText: '暂无数据',
|
||
);
|
||
|
||
Widget contentWidget;
|
||
if (!showTabs) {
|
||
contentWidget = personListWidget;
|
||
} else {
|
||
contentWidget = TabBarView(
|
||
controller: _tabController,
|
||
children: [
|
||
personListWidget,
|
||
serverListWidget,
|
||
],
|
||
);
|
||
}
|
||
|
||
return SafeArea(
|
||
child: Container(
|
||
width: MediaQuery.of(context).size.width,
|
||
height: MediaQuery.of(context).size.height * 0.82,
|
||
decoration: const BoxDecoration(
|
||
color: Colors.white,
|
||
borderRadius: BorderRadius.vertical(top: Radius.circular(12)),
|
||
),
|
||
child: Column(
|
||
children: [
|
||
Padding(
|
||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||
child: Row(
|
||
children: [
|
||
TextButton(
|
||
onPressed: () => Navigator.of(context).pop(),
|
||
child: const Text('取消', style: TextStyle(fontSize: 16)),
|
||
),
|
||
Expanded(
|
||
child: Padding(
|
||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||
child: SearchBarWidget(
|
||
controller: _searchController,
|
||
isShowSearchButton: false,
|
||
onSearch: (keyboard) {
|
||
_onSearchChanged();
|
||
},
|
||
),
|
||
),
|
||
),
|
||
TextButton(
|
||
onPressed: _confirmEnabled()
|
||
? () {
|
||
Navigator.of(context).pop();
|
||
_confirm();
|
||
}
|
||
: null,
|
||
child: Text(
|
||
'确定',
|
||
style: TextStyle(
|
||
color: _confirmEnabled() ? Colors.blue : Colors.grey,
|
||
fontSize: 16,
|
||
),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
const Divider(height: 1),
|
||
if (showTabs) ...[
|
||
TabBar(
|
||
controller: _tabController,
|
||
tabs: const [
|
||
Tab(text: '分公司'),
|
||
Tab(text: '相关方'),
|
||
],
|
||
indicatorColor: Colors.blue,
|
||
labelColor: Colors.blue,
|
||
unselectedLabelColor: Colors.black54,
|
||
),
|
||
const Divider(height: 1),
|
||
],
|
||
Expanded(child: contentWidget),
|
||
],
|
||
),
|
||
),
|
||
);
|
||
}
|
||
} |