220 lines
		
	
	
		
			6.6 KiB
		
	
	
	
		
			Dart
		
	
	
			
		
		
	
	
			220 lines
		
	
	
		
			6.6 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 List<Category> children;
 | |
| 
 | |
|   Category({
 | |
|     required this.id,
 | |
|     required this.name,
 | |
|     this.children = const [],
 | |
|   });
 | |
| 
 | |
|   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(),
 | |
|     );
 | |
|   }
 | |
| }
 | |
| 
 | |
| /// 弹窗回调签名:返回选中项的 id 和 name
 | |
| typedef DeptSelectCallback = void Function(String id, String name);
 | |
| 
 | |
| class DepartmentPicker extends StatefulWidget {
 | |
|   /// 回调,返回选中部门 id 与 name
 | |
|   final DeptSelectCallback onSelected;
 | |
| 
 | |
|   const DepartmentPicker({Key? key, required this.onSelected}) : super(key: key);
 | |
| 
 | |
|   @override
 | |
|   _DepartmentPickerState createState() => _DepartmentPickerState();
 | |
| }
 | |
| 
 | |
| class _DepartmentPickerState extends State<DepartmentPicker> {
 | |
|   String selectedId = '';
 | |
|   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 = '';
 | |
|     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, 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);
 | |
|               }
 | |
|               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);
 | |
|                   },
 | |
|                   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),
 | |
|               ),
 | |
|             ),
 | |
|           ),
 | |
|         ],
 | |
|       ),
 | |
|     );
 | |
|   }
 | |
| }
 |