227 lines
		
	
	
		
			6.9 KiB
		
	
	
	
		
			Dart
		
	
	
		
		
			
		
	
	
			227 lines
		
	
	
		
			6.9 KiB
		
	
	
	
		
			Dart
		
	
	
|  | import 'dart:convert'; | ||
|  | import 'package:flutter/material.dart'; | ||
|  | import 'package:qhd_prevention/customWidget/search_bar_widget.dart'; | ||
|  | import 'package:qhd_prevention/http/ApiService.dart'; | ||
|  | import '../tools/tools.dart'; // 包含 SessionService
 | ||
|  | 
 | ||
|  | // 数据模型
 | ||
|  | class Category { | ||
|  |   final String id; | ||
|  |   final String name; | ||
|  |   final String pdId; | ||
|  |   final List<Category> children; | ||
|  | 
 | ||
|  |   Category({ | ||
|  |     required this.id, | ||
|  |     required this.name, | ||
|  |     required this.pdId, | ||
|  |     this.children = const [], | ||
|  |   }); | ||
|  | 
 | ||
|  |   factory Category.fromJson(Map<String, dynamic> json) { | ||
|  |     return Category( | ||
|  |       id: json['id'] as String, | ||
|  |       name: json['name'] as String, | ||
|  |       pdId: json['PARENT_ID'] as String, | ||
|  |       children: (json['children'] as List<dynamic>) | ||
|  |           .map((e) => Category.fromJson(e as Map<String, dynamic>)) | ||
|  |           .toList(), | ||
|  |     ); | ||
|  |   } | ||
|  | } | ||
|  | 
 | ||
|  | /// 弹窗回调签名:返回选中项的 id 和 name
 | ||
|  | typedef DeptSelectCallback = void Function(String id, String name,String pdId); | ||
|  | 
 | ||
|  | class DepartmentPickerTwo extends StatefulWidget { | ||
|  |   /// 回调,返回选中部门 id 与 name
 | ||
|  |   final DeptSelectCallback onSelected; | ||
|  | 
 | ||
|  |   const DepartmentPickerTwo({Key? key, required this.onSelected}) : super(key: key); | ||
|  | 
 | ||
|  |   @override | ||
|  |   _DepartmentPickerTwoState createState() => _DepartmentPickerTwoState(); | ||
|  | } | ||
|  | 
 | ||
|  | class _DepartmentPickerTwoState extends State<DepartmentPickerTwo> { | ||
|  |   String selectedId = ''; | ||
|  |   String selectedPDId = ''; | ||
|  |   String selectedName = ''; | ||
|  |   Set<String> expandedSet = {}; | ||
|  | 
 | ||
|  |   List<Category> original = []; | ||
|  |   List<Category> filtered = []; | ||
|  |   bool loading = true; | ||
|  | 
 | ||
|  |   final TextEditingController _searchController = TextEditingController(); | ||
|  | 
 | ||
|  |   @override | ||
|  |   void initState() { | ||
|  |     super.initState(); | ||
|  |     // 初始均为空
 | ||
|  |     selectedId = ''; | ||
|  |     selectedName = ''; | ||
|  |     selectedPDId = ''; | ||
|  |     expandedSet = {}; | ||
|  |     _searchController.addListener(_onSearchChanged); | ||
|  |     _loadData(); | ||
|  |   } | ||
|  | 
 | ||
|  |   @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>; | ||
|  |       } | ||
|  |       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); | ||
|  |     }); | ||
|  |   } | ||
|  | 
 | ||
|  |   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,pdId:cat.pdId, 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; | ||
|  |     return Column( | ||
|  |       children: [ | ||
|  |         InkWell( | ||
|  |           onTap: () { | ||
|  |             setState(() { | ||
|  |               if (hasChildren) { | ||
|  |                 isExpanded ? expandedSet.remove(cat.id) : expandedSet.add(cat.id); | ||
|  |                 selectedPDId=cat.pdId; | ||
|  |               }else{ | ||
|  |                 selectedPDId=cat.id; | ||
|  |               } | ||
|  |               selectedId = cat.id; | ||
|  |               selectedName = cat.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: Text(cat.name), | ||
|  |                   ), | ||
|  |                 ), | ||
|  |                 Padding( | ||
|  |                   padding: const EdgeInsets.symmetric(horizontal: 16), | ||
|  |                   child: Icon( | ||
|  |                     isSelected ? Icons.radio_button_checked : Icons.radio_button_unchecked, | ||
|  |                     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, | ||
|  |       color: Colors.white, | ||
|  |       child: Column( | ||
|  |         children: [ | ||
|  |           Container( | ||
|  |             color: Colors.white, | ||
|  |             padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), | ||
|  |             child: Row( | ||
|  |               children: [ | ||
|  |                 GestureDetector( | ||
|  |                   onTap: () => Navigator.of(context).pop(), | ||
|  |                   child: const Text('取消', style: TextStyle(fontSize: 16)), | ||
|  |                 ), | ||
|  |                 Expanded( | ||
|  |                   child: Padding( | ||
|  |                     padding: const EdgeInsets.symmetric(horizontal: 12), | ||
|  |                     child: SearchBarWidget( | ||
|  |                       controller: _searchController, | ||
|  |                       isShowSearchButton: false, | ||
|  |                       onSearch: (keyboard) { | ||
|  |                       }, | ||
|  |                     ), | ||
|  |                   ), | ||
|  |                 ), | ||
|  |                 GestureDetector( | ||
|  |                   onTap: () { | ||
|  |                     Navigator.of(context).pop(); | ||
|  |                     widget.onSelected(selectedId, selectedName,selectedPDId); | ||
|  |                   }, | ||
|  |                   child: const Text('确定', style: TextStyle(fontSize: 16, color: Colors.green)), | ||
|  |                 ), | ||
|  |               ], | ||
|  |             ), | ||
|  |           ), | ||
|  |           Divider(), | ||
|  |           Expanded( | ||
|  |             child: loading | ||
|  |                 ? const Center(child: CircularProgressIndicator()) | ||
|  |                 : Container( | ||
|  |               color: Colors.white, | ||
|  |               child: ListView.builder( | ||
|  |                 itemCount: filtered.length, | ||
|  |                 itemBuilder: (ctx, idx) => _buildRow(filtered[idx], 0), | ||
|  |               ), | ||
|  |             ), | ||
|  |           ), | ||
|  |         ], | ||
|  |       ), | ||
|  |     ); | ||
|  |   } | ||
|  | } |