152 lines
6.1 KiB
Dart
152 lines
6.1 KiB
Dart
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,
|
||
);
|
||
}
|
||
}
|
||
|
||
/// 原回调签名(向后兼容)
|
||
typedef PersonSelectCallback = void Function(String userId, String name);
|
||
|
||
/// 新回调签名,增加可选 index(int,默认 0)
|
||
typedef PersonSelectCallbackWithIndex = void Function(String userId, String name, int index);
|
||
|
||
/// 底部弹窗人员选择器(使用预先传入的原始数据列表,不做接口请求)
|
||
class DepartmentPersonPicker {
|
||
/// 显示人员选择弹窗
|
||
///
|
||
/// [personsData]: 已拉取并缓存的原始 Map 列表
|
||
/// [onSelected]: 选中后回调 USER_ID 和 NAME(向后兼容旧代码)
|
||
/// [onSelectedWithIndex]: 可选的新回调,额外返回 index(index 为在原始 personsData/_all 中的下标,找不到则为 0)
|
||
static Future<void> show(
|
||
BuildContext context, {
|
||
required List<Map<String, dynamic>> personsData,
|
||
PersonSelectCallback? onSelected,
|
||
PersonSelectCallbackWithIndex? onSelectedWithIndex,
|
||
}) async {
|
||
// 至少传入一个回调(保持对旧调用的兼容)
|
||
assert(onSelected != null || onSelectedWithIndex != null,
|
||
'请至少传入 onSelected 或 onSelectedWithIndex');
|
||
|
||
// 转换为模型
|
||
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();
|
||
|
||
// 计算 index(在原始 _all 列表中的下标)
|
||
final idx = _all.indexWhere((p) => p.userId == _selectedId);
|
||
final validIndex = idx >= 0 ? idx : 0;
|
||
|
||
// 优先调用带 index 的回调(新),否则调用旧回调
|
||
if (onSelectedWithIndex != null) {
|
||
onSelectedWithIndex(_selectedId, _selectedName, validIndex);
|
||
} else if (onSelected != null) {
|
||
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;
|
||
}),
|
||
);
|
||
},
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
);
|
||
},
|
||
);
|
||
},
|
||
);
|
||
}
|
||
} |