| 
									
										
										
										
											2025-07-24 11:18:47 +08:00
										 |  |  | import 'dart:convert'; | 
					
						
							| 
									
										
										
										
											2025-07-11 11:03:21 +08:00
										 |  |  | import 'package:flutter/material.dart'; | 
					
						
							| 
									
										
										
										
											2025-07-24 11:18:47 +08:00
										 |  |  | import 'package:qhd_prevention/customWidget/search_bar_widget.dart'; | 
					
						
							|  |  |  | import 'package:qhd_prevention/http/ApiService.dart'; | 
					
						
							|  |  |  | import '../tools/tools.dart'; // 包含 SessionService
 | 
					
						
							| 
									
										
										
										
											2025-07-11 11:03:21 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-24 11:18:47 +08:00
										 |  |  | // 数据模型
 | 
					
						
							| 
									
										
										
										
											2025-07-11 11:03:21 +08:00
										 |  |  | class Category { | 
					
						
							|  |  |  |   final String id; | 
					
						
							| 
									
										
										
										
											2025-07-24 11:18:47 +08:00
										 |  |  |   final String name; | 
					
						
							| 
									
										
										
										
											2025-07-11 11:03:21 +08:00
										 |  |  |   final List<Category> children; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   Category({ | 
					
						
							|  |  |  |     required this.id, | 
					
						
							| 
									
										
										
										
											2025-07-24 11:18:47 +08:00
										 |  |  |     required this.name, | 
					
						
							| 
									
										
										
										
											2025-07-11 11:03:21 +08:00
										 |  |  |     this.children = const [], | 
					
						
							|  |  |  |   }); | 
					
						
							| 
									
										
										
										
											2025-07-24 11:18:47 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |   factory Category.fromJson(Map<String, dynamic> json) { | 
					
						
							|  |  |  |     return Category( | 
					
						
							|  |  |  |       id: json['id'] as String, | 
					
						
							|  |  |  |       name: json['name'] as String, | 
					
						
							|  |  |  |       children: (json['children'] as List<dynamic>) | 
					
						
							|  |  |  |           .map((e) => Category.fromJson(e as Map<String, dynamic>)) | 
					
						
							|  |  |  |           .toList(), | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2025-07-11 11:03:21 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-24 11:18:47 +08:00
										 |  |  | /// 弹窗回调签名:返回选中项的 id 和 name
 | 
					
						
							|  |  |  | typedef DeptSelectCallback = void Function(String id, String name); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-11 11:03:21 +08:00
										 |  |  | class DepartmentPicker extends StatefulWidget { | 
					
						
							| 
									
										
										
										
											2025-07-24 11:18:47 +08:00
										 |  |  |   /// 回调,返回选中部门 id 与 name
 | 
					
						
							|  |  |  |   final DeptSelectCallback onSelected; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   const DepartmentPicker({Key? key, required this.onSelected}) : super(key: key); | 
					
						
							| 
									
										
										
										
											2025-07-11 11:03:21 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |   @override | 
					
						
							|  |  |  |   _DepartmentPickerState createState() => _DepartmentPickerState(); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class _DepartmentPickerState extends State<DepartmentPicker> { | 
					
						
							| 
									
										
										
										
											2025-07-24 11:18:47 +08:00
										 |  |  |   String selectedId = ''; | 
					
						
							|  |  |  |   String selectedName = ''; | 
					
						
							|  |  |  |   Set<String> expandedSet = {}; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   List<Category> original = []; | 
					
						
							|  |  |  |   List<Category> filtered = []; | 
					
						
							|  |  |  |   bool loading = true; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   final TextEditingController _searchController = TextEditingController(); | 
					
						
							| 
									
										
										
										
											2025-07-11 11:03:21 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |   @override | 
					
						
							|  |  |  |   void initState() { | 
					
						
							|  |  |  |     super.initState(); | 
					
						
							| 
									
										
										
										
											2025-07-24 11:18:47 +08:00
										 |  |  |     // 初始均为空
 | 
					
						
							|  |  |  |     selectedId = ''; | 
					
						
							|  |  |  |     selectedName = ''; | 
					
						
							|  |  |  |     expandedSet = {}; | 
					
						
							|  |  |  |     _searchController.addListener(_onSearchChanged); | 
					
						
							|  |  |  |     _loadData(); | 
					
						
							| 
									
										
										
										
											2025-07-11 11:03:21 +08:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-24 11:18:47 +08:00
										 |  |  |   @override | 
					
						
							|  |  |  |   void dispose() { | 
					
						
							|  |  |  |     _searchController.removeListener(_onSearchChanged); | 
					
						
							|  |  |  |     _searchController.dispose(); | 
					
						
							|  |  |  |     super.dispose(); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   Future<void> _loadData() async { | 
					
						
							|  |  |  |     try { | 
					
						
							|  |  |  |       List<dynamic> raw; | 
					
						
							|  |  |  |       if (SessionService.instance.departmentJsonStr?.isNotEmpty ?? false) { | 
					
						
							|  |  |  |         raw = json.decode(SessionService.instance.departmentJsonStr!) as List<dynamic>; | 
					
						
							|  |  |  |       } else { | 
					
						
							|  |  |  |         final result = await ApiService.getHiddenTreatmentListTree(); | 
					
						
							|  |  |  |         final String nodes = result['zTreeNodes'] as String; | 
					
						
							|  |  |  |         SessionService.instance.departmentJsonStr = nodes; | 
					
						
							|  |  |  |         raw = json.decode(nodes) as List<dynamic>; | 
					
						
							| 
									
										
										
										
											2025-08-21 16:44:24 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-24 11:18:47 +08:00
										 |  |  |       } | 
					
						
							|  |  |  |       setState(() { | 
					
						
							|  |  |  |         original = raw.map((e) => Category.fromJson(e as Map<String, dynamic>)).toList(); | 
					
						
							|  |  |  |         filtered = original; | 
					
						
							|  |  |  |         loading = false; | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  |     } catch (e) { | 
					
						
							|  |  |  |       setState(() => loading = false); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   void _onSearchChanged() { | 
					
						
							|  |  |  |     final query = _searchController.text.toLowerCase().trim(); | 
					
						
							|  |  |  |     setState(() { | 
					
						
							|  |  |  |       filtered = query.isEmpty ? original : _filterCategories(original, query); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2025-07-11 11:03:21 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-24 11:18:47 +08:00
										 |  |  |   List<Category> _filterCategories(List<Category> list, String query) { | 
					
						
							|  |  |  |     List<Category> result = []; | 
					
						
							|  |  |  |     for (var cat in list) { | 
					
						
							|  |  |  |       final children = _filterCategories(cat.children, query); | 
					
						
							|  |  |  |       if (cat.name.toLowerCase().contains(query) || children.isNotEmpty) { | 
					
						
							|  |  |  |         result.add(Category(id: cat.id, name: cat.name, children: children)); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     return result; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   Widget _buildRow(Category cat, int indent) { | 
					
						
							|  |  |  |     final hasChildren = cat.children.isNotEmpty; | 
					
						
							|  |  |  |     final isExpanded = expandedSet.contains(cat.id); | 
					
						
							|  |  |  |     final isSelected = cat.id == selectedId; | 
					
						
							| 
									
										
										
										
											2025-07-11 11:03:21 +08:00
										 |  |  |     return Column( | 
					
						
							|  |  |  |       children: [ | 
					
						
							|  |  |  |         InkWell( | 
					
						
							|  |  |  |           onTap: () { | 
					
						
							|  |  |  |             setState(() { | 
					
						
							|  |  |  |               if (hasChildren) { | 
					
						
							| 
									
										
										
										
											2025-07-24 11:18:47 +08:00
										 |  |  |                 isExpanded ? expandedSet.remove(cat.id) : expandedSet.add(cat.id); | 
					
						
							| 
									
										
										
										
											2025-07-11 11:03:21 +08:00
										 |  |  |               } | 
					
						
							|  |  |  |               selectedId = cat.id; | 
					
						
							| 
									
										
										
										
											2025-07-24 11:18:47 +08:00
										 |  |  |               selectedName = cat.name; | 
					
						
							| 
									
										
										
										
											2025-07-11 11:03:21 +08:00
										 |  |  |             }); | 
					
						
							|  |  |  |           }, | 
					
						
							|  |  |  |           child: Container( | 
					
						
							|  |  |  |             color: Colors.white, | 
					
						
							|  |  |  |             child: Row( | 
					
						
							|  |  |  |               children: [ | 
					
						
							|  |  |  |                 SizedBox(width: 16.0 * indent), | 
					
						
							|  |  |  |                 SizedBox( | 
					
						
							|  |  |  |                   width: 24, | 
					
						
							|  |  |  |                   child: hasChildren | 
					
						
							| 
									
										
										
										
											2025-07-24 11:18:47 +08:00
										 |  |  |                       ? Icon(isExpanded ? Icons.arrow_drop_down_rounded : Icons.arrow_right_rounded, | 
					
						
							|  |  |  |                       size: 35, color: Colors.grey[600]) | 
					
						
							| 
									
										
										
										
											2025-07-11 11:03:21 +08:00
										 |  |  |                       : const SizedBox.shrink(), | 
					
						
							|  |  |  |                 ), | 
					
						
							| 
									
										
										
										
											2025-07-24 11:18:47 +08:00
										 |  |  |                 const SizedBox(width: 5), | 
					
						
							| 
									
										
										
										
											2025-07-11 11:03:21 +08:00
										 |  |  |                 Expanded( | 
					
						
							|  |  |  |                   child: Padding( | 
					
						
							|  |  |  |                     padding: const EdgeInsets.symmetric(vertical: 12), | 
					
						
							| 
									
										
										
										
											2025-07-24 11:18:47 +08:00
										 |  |  |                     child: Text(cat.name), | 
					
						
							| 
									
										
										
										
											2025-07-11 11:03:21 +08:00
										 |  |  |                   ), | 
					
						
							|  |  |  |                 ), | 
					
						
							|  |  |  |                 Padding( | 
					
						
							|  |  |  |                   padding: const EdgeInsets.symmetric(horizontal: 16), | 
					
						
							|  |  |  |                   child: Icon( | 
					
						
							| 
									
										
										
										
											2025-07-24 11:18:47 +08:00
										 |  |  |                     isSelected ? Icons.radio_button_checked : Icons.radio_button_unchecked, | 
					
						
							| 
									
										
										
										
											2025-07-11 11:03:21 +08:00
										 |  |  |                     color: Colors.green, | 
					
						
							|  |  |  |                   ), | 
					
						
							|  |  |  |                 ), | 
					
						
							|  |  |  |               ], | 
					
						
							|  |  |  |             ), | 
					
						
							|  |  |  |           ), | 
					
						
							|  |  |  |         ), | 
					
						
							|  |  |  |         if (hasChildren && isExpanded) | 
					
						
							|  |  |  |           ...cat.children.map((c) => _buildRow(c, indent + 1)), | 
					
						
							|  |  |  |       ], | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @override | 
					
						
							|  |  |  |   Widget build(BuildContext context) { | 
					
						
							|  |  |  |     return Container( | 
					
						
							|  |  |  |       width: MediaQuery.of(context).size.width, | 
					
						
							|  |  |  |       height: MediaQuery.of(context).size.height * 0.7, | 
					
						
							| 
									
										
										
										
											2025-07-24 11:18:47 +08:00
										 |  |  |       color: Colors.white, | 
					
						
							| 
									
										
										
										
											2025-07-11 11:03:21 +08:00
										 |  |  |       child: Column( | 
					
						
							|  |  |  |         children: [ | 
					
						
							|  |  |  |           Container( | 
					
						
							|  |  |  |             color: Colors.white, | 
					
						
							| 
									
										
										
										
											2025-07-24 11:18:47 +08:00
										 |  |  |             padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), | 
					
						
							| 
									
										
										
										
											2025-07-11 11:03:21 +08:00
										 |  |  |             child: Row( | 
					
						
							|  |  |  |               children: [ | 
					
						
							|  |  |  |                 GestureDetector( | 
					
						
							|  |  |  |                   onTap: () => Navigator.of(context).pop(), | 
					
						
							|  |  |  |                   child: const Text('取消', style: TextStyle(fontSize: 16)), | 
					
						
							|  |  |  |                 ), | 
					
						
							| 
									
										
										
										
											2025-07-24 11:18:47 +08:00
										 |  |  |                 Expanded( | 
					
						
							|  |  |  |                   child: Padding( | 
					
						
							|  |  |  |                     padding: const EdgeInsets.symmetric(horizontal: 12), | 
					
						
							|  |  |  |                     child: SearchBarWidget( | 
					
						
							|  |  |  |                       controller: _searchController, | 
					
						
							|  |  |  |                       isShowSearchButton: false, | 
					
						
							|  |  |  |                       onSearch: (keyboard) { | 
					
						
							| 
									
										
										
										
											2025-07-30 11:20:11 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-24 11:18:47 +08:00
										 |  |  |                       }, | 
					
						
							|  |  |  |                     ), | 
					
						
							|  |  |  |                   ), | 
					
						
							|  |  |  |                 ), | 
					
						
							| 
									
										
										
										
											2025-07-11 11:03:21 +08:00
										 |  |  |                 GestureDetector( | 
					
						
							| 
									
										
										
										
											2025-07-24 11:18:47 +08:00
										 |  |  |                   onTap: () { | 
					
						
							|  |  |  |                     Navigator.of(context).pop(); | 
					
						
							|  |  |  |                     widget.onSelected(selectedId, selectedName); | 
					
						
							|  |  |  |                   }, | 
					
						
							|  |  |  |                   child: const Text('确定', style: TextStyle(fontSize: 16, color: Colors.green)), | 
					
						
							| 
									
										
										
										
											2025-07-11 11:03:21 +08:00
										 |  |  |                 ), | 
					
						
							|  |  |  |               ], | 
					
						
							|  |  |  |             ), | 
					
						
							|  |  |  |           ), | 
					
						
							| 
									
										
										
										
											2025-07-24 11:18:47 +08:00
										 |  |  |           Divider(), | 
					
						
							| 
									
										
										
										
											2025-07-11 11:03:21 +08:00
										 |  |  |           Expanded( | 
					
						
							| 
									
										
										
										
											2025-07-24 11:18:47 +08:00
										 |  |  |             child: loading | 
					
						
							|  |  |  |                 ? const Center(child: CircularProgressIndicator()) | 
					
						
							|  |  |  |                 : Container( | 
					
						
							| 
									
										
										
										
											2025-07-11 11:03:21 +08:00
										 |  |  |               color: Colors.white, | 
					
						
							|  |  |  |               child: ListView.builder( | 
					
						
							| 
									
										
										
										
											2025-07-24 11:18:47 +08:00
										 |  |  |                 itemCount: filtered.length, | 
					
						
							|  |  |  |                 itemBuilder: (ctx, idx) => _buildRow(filtered[idx], 0), | 
					
						
							| 
									
										
										
										
											2025-07-11 11:03:21 +08:00
										 |  |  |               ), | 
					
						
							|  |  |  |             ), | 
					
						
							|  |  |  |           ), | 
					
						
							|  |  |  |         ], | 
					
						
							|  |  |  |       ), | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } |