QinGang_interested/lib/customWidget/MultiDictValuesPicker.dart

475 lines
13 KiB
Dart
Raw Permalink Normal View History

2025-12-12 09:11:30 +08:00
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:qhd_prevention/customWidget/search_bar_widget.dart';
import 'package:qhd_prevention/http/ApiService.dart';
// 数据字典模型
class DictCategory {
final String id;
final String name;
final Map<String, dynamic> extValues;
final String parentId;
final String dictValue;
final String dictLabel;
final List<DictCategory> children;
late final String dingValue;
late final String dingName;
DictCategory({
required this.id,
required this.name,
required this.children,
required this.extValues,
required this.parentId,
required this.dictValue,
required this.dictLabel,
required this.dingValue,
required this.dingName,
});
factory DictCategory.fromJson(Map<String, dynamic> json) {
// 安全读取并兼容字符串或数字类型的 id
String parseString(dynamic v) {
if (v == null) return '';
if (v is String) return v;
return v.toString();
}
// 处理子节点
final rawChildren = json['children'];
List<DictCategory> childrenList = [];
if (rawChildren is List) {
try {
childrenList = rawChildren
.where((e) => e != null)
.map((e) => DictCategory.fromJson(Map<String, dynamic>.from(e as Map)))
.toList();
} catch (e) {
childrenList = [];
}
}
// 处理扩展值
final extRaw = json['extValues'];
Map<String, dynamic> extMap = {};
if (extRaw is Map) {
extMap = Map<String, dynamic>.from(extRaw);
}
return DictCategory(
id: parseString(json['id']),
name: parseString(json['dictLabel'] ?? json['name']), // 兼容新旧字段
children: childrenList,
extValues: extMap,
parentId: parseString(json['parentId']),
dictValue: parseString(json['dictValue']),
dictLabel: parseString(json['dictLabel'] ?? json['name']),
dingValue: "",
dingName: "",
);
}
// 转换为Map便于使用
Map<String, dynamic> toMap() {
return {
'id': id,
'name': name,
'dictValue': dictValue,
'dictLabel': dictLabel,
'parentId': parentId,
'extValues': extValues,
};
}
}
/// 数据字典选择器回调签名
typedef DictSelectCallback = void Function(String id, String name, Map<String, dynamic>? extraData);
class MultiDictValuesPicker extends StatefulWidget {
/// 字典类型
final String dictType;
/// 回调,返回选中项的 id, name 和额外数据
final DictSelectCallback onSelected;
/// 是否显示搜索框
final bool showSearch;
/// 标题
final String title;
/// 确认按钮文本
final String confirmText;
/// 取消按钮文本
final String cancelText;
const MultiDictValuesPicker({
Key? key,
required this.dictType,
required this.onSelected,
this.showSearch = true,
this.title = '请选择',
this.confirmText = '确定',
this.cancelText = '取消',
}) : super(key: key);
@override
_MultiDictValuesPickerState createState() => _MultiDictValuesPickerState();
}
class _MultiDictValuesPickerState extends State<MultiDictValuesPicker> {
String selectedId = '';
String selectedName = '';
Map<String, dynamic>? selectedExtraData;
Set<String> expandedSet = {};
List<DictCategory> original = [];
List<DictCategory> filtered = [];
bool loading = true;
bool error = false;
String errorMessage = '';
String dingValueWai = '';
String dingNameWai = '';
final TextEditingController _searchController = TextEditingController();
@override
void initState() {
super.initState();
selectedId = '';
selectedName = '';
expandedSet = {};
_searchController.addListener(_onSearchChanged);
_loadDictData();
}
@override
void dispose() {
_searchController.removeListener(_onSearchChanged);
_searchController.dispose();
super.dispose();
}
Future<void> _loadDictData() async {
try {
setState(() {
loading = true;
error = false;
});
final result = await BasicInfoApi.getDictValues(widget.dictType);
final raw = result['data'] as List<dynamic>;
setState(() {
original = raw.map((e) => DictCategory.fromJson(e as Map<String, dynamic>)).toList();
filtered = original;
loading = false;
});
} catch (e) {
setState(() {
loading = false;
error = true;
errorMessage = e.toString();
});
}
}
void _onSearchChanged() {
final query = _searchController.text.toLowerCase().trim();
setState(() {
filtered = query.isEmpty ? original : _filterCategories(original, query);
// 搜索时展开所有节点以便查看结果
if (query.isNotEmpty) {
expandedSet.addAll(_getAllExpandableIds(filtered));
}
});
}
Set<String> _getAllExpandableIds(List<DictCategory> categories) {
Set<String> ids = {};
for (var category in categories) {
if (category.children.isNotEmpty) {
ids.add(category.id);
ids.addAll(_getAllExpandableIds(category.children));
}
}
return ids;
}
List<DictCategory> _filterCategories(List<DictCategory> list, String query) {
List<DictCategory> result = [];
for (var cat in list) {
final children = _filterCategories(cat.children, query);
if (cat.name.toLowerCase().contains(query) || children.isNotEmpty) {
result.add(
DictCategory(
id: cat.id,
name: cat.name,
children: children,
extValues: cat.extValues,
parentId: cat.parentId,
dictValue: cat.dictValue,
dictLabel: cat.dictLabel,
dingValue: "",
dingName: "",
),
);
}
}
return result;
}
Widget _buildRow(DictCategory category, int indent) {
final hasChildren = category.children.isNotEmpty;
final isExpanded = expandedSet.contains(category.id);
final isSelected = category.id == selectedId;
return Column(
children: [
InkWell(
onTap: () {
setState(() {
if (hasChildren) {
isExpanded
? expandedSet.remove(category.id)
: expandedSet.add(category.id);
}
selectedId = category.id;
selectedName = category.name;
selectedExtraData = category.toMap();
if(indent==0){
dingValueWai =category.dictValue;
dingNameWai =category.name;
}
});
},
child: Container(
color: Colors.white,
child: Row(
children: [
SizedBox(width: 16.0 * indent),
SizedBox(
width: 24,
child: hasChildren
? Icon(
isExpanded
? Icons.arrow_drop_down_rounded
: Icons.arrow_right_rounded,
size: 35,
color: Colors.grey[600],
)
: const SizedBox.shrink(),
),
const SizedBox(width: 5),
Expanded(
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
category.name,
style: TextStyle(
fontWeight: isSelected ? FontWeight.bold : FontWeight.normal,
color: Colors.black,
),
),
],
),
),
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Icon(
isSelected
? Icons.radio_button_checked
: Icons.radio_button_unchecked,
color: Colors.blue,
),
),
],
),
),
),
if (hasChildren && isExpanded)
...category.children.map((child) => _buildRow(child, indent + 1)),
],
);
}
Widget _buildTitleBar() {
return Container(
color: Colors.white,
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
child: Center(
child: Text(
widget.title,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
),
);
}
Widget _buildActionBar() {
return Container(
color: Colors.white,
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
child: Row(
children: [
// 取消按钮
GestureDetector(
onTap: () => Navigator.of(context).pop(),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
child: Text(
widget.cancelText,
style: const TextStyle(fontSize: 16, color: Colors.grey),
),
),
),
// 搜索框(如果有搜索功能)
if (widget.showSearch) ...[
Expanded(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 12),
child: SearchBarWidget(
controller: _searchController,
isShowSearchButton: false,
onSearch: (keyboard) {},
),
),
),
] else ...[
const Expanded(child: SizedBox()),
],
// 确定按钮
GestureDetector(
onTap: selectedId.isEmpty
? null
: () {
selectedExtraData?['dingValue']=dingValueWai ;
selectedExtraData?['dingName']=dingNameWai ;
Navigator.of(context).pop();
widget.onSelected(selectedId, selectedName, selectedExtraData);
},
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
child: Text(
widget.confirmText,
style: TextStyle(
fontSize: 16,
color: selectedId.isEmpty ? Colors.grey : Colors.blue,
fontWeight: FontWeight.bold,
),
),
),
),
],
),
);
}
Widget _buildContent() {
if (loading) {
return const Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
CircularProgressIndicator(),
SizedBox(height: 16),
Text('加载中...'),
],
),
);
}
if (error) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.error_outline, size: 48, color: Colors.red),
const SizedBox(height: 16),
const Text('加载失败', style: TextStyle(fontSize: 16)),
const SizedBox(height: 8),
Text(
errorMessage,
style: const TextStyle(fontSize: 12, color: Colors.grey),
textAlign: TextAlign.center,
),
const SizedBox(height: 16),
ElevatedButton(
onPressed: _loadDictData,
child: const Text('重试'),
),
],
),
);
}
if (filtered.isEmpty) {
return const Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.search_off, size: 48, color: Colors.grey),
SizedBox(height: 16),
Text('暂无数据', style: TextStyle(fontSize: 16, color: Colors.grey)),
],
),
);
}
return Container(
color: Colors.white,
child: ListView.builder(
itemCount: filtered.length,
itemBuilder: (ctx, idx) => _buildRow(filtered[idx], 0),
),
);
}
@override
Widget build(BuildContext context) {
return Container(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height * 0.7,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(12),
topRight: Radius.circular(12),
),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 10,
offset: const Offset(0, -2),
),
],
),
child: Column(
children: [
// 标题行
// _buildTitleBar(),
// 操作行(取消、搜索、确定)
_buildActionBar(),
const Divider(height: 1),
// 内容区域
Expanded(
child: _buildContent(),
),
],
),
);
}
}