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 'package:qhd_prevention/tools/tools.dart'; // ----- Models ----- class Category { final String ELECTRONIC_FENCE_AREA_ID; final String POSITIONS; final String name; final List children; Category({ required this.ELECTRONIC_FENCE_AREA_ID, required this.name, this.POSITIONS = '', this.children = const [], }); factory Category.fromJson(Map json) { // 保护式读取,避免 null 转 List、String 时抛异常 final id = (json['ELECTRONIC_FENCE_AREA_ID'] ?? '').toString(); final name = (json['name'] ?? '').toString(); final positions = (json['POSITIONS'] ?? '').toString(); // children 可能不存在、为 null、或已经是 List final rawChildren = json['children']; List childrenList = []; if (rawChildren is List) { childrenList = rawChildren .where((e) => e != null) .map((e) { // e 可能已经是 Map 或 dynamic if (e is Map) { return Category.fromJson(e); } else if (e is Map) { return Category.fromJson(Map.from(e)); } else { // 无法解析的项跳过,或返回空占位 return null; } }) .whereType() .toList(); } return Category( ELECTRONIC_FENCE_AREA_ID: id, name: name, POSITIONS: positions, children: childrenList, ); } } typedef DeptSelectCallback = void Function(String id, String POSITIONS, String name); class WorkAreaPicker extends StatefulWidget { final DeptSelectCallback onSelected; const WorkAreaPicker({Key? key, required this.onSelected}) : super(key: key); @override _WorkAreaPickerState createState() => _WorkAreaPickerState(); } class _WorkAreaPickerState extends State { String selectedId = ''; String selectedName = ''; String selected_POSITIONS = ''; Set expandedSet = {}; List original = []; List filtered = []; bool loading = true; final TextEditingController _searchController = TextEditingController(); @override void initState() { super.initState(); // 初始均为空 selectedId = ''; selectedName = ''; selected_POSITIONS = ''; expandedSet = {}; _searchController.addListener(_onSearchChanged); _loadData(); } @override void dispose() { _searchController.removeListener(_onSearchChanged); _searchController.dispose(); super.dispose(); } Future _loadData() async { try { final result = await ApiService.getWorkAreaList(); final dynamic nodesField = result['zTreeNodes']; // nodesField 可能是已经解析过的 List 或者是 String(JSON 字符串) List raw; if (nodesField is String) { raw = json.decode(nodesField) as List; } else if (nodesField is List) { raw = nodesField; } else { raw = []; } // debug 打印(运行中可查看控制台) // print('raw length = ${raw.length}'); setState(() { original = raw .map((e) { if (e is Map) return Category.fromJson(e); if (e is Map) return Category.fromJson(Map.from(e)); return null; }) .whereType() .toList(); filtered = original; loading = false; }); } catch (e, st) { // 打印错误以便调试 // print('loadData error: $e\n$st'); setState(() => loading = false); } } void _onSearchChanged() { final query = _searchController.text.toLowerCase().trim(); setState(() { filtered = query.isEmpty ? original : _filterCategories(original, query); }); } List _filterCategories(List list, String query) { List result = []; for (var cat in list) { final children = _filterCategories(cat.children, query); if (cat.name.toLowerCase().contains(query) || children.isNotEmpty) { result.add( Category( ELECTRONIC_FENCE_AREA_ID: cat.ELECTRONIC_FENCE_AREA_ID, name: cat.name, children: children, ), ); } } return result; } Widget _buildRow(Category cat, int indent) { final hasChildren = cat.children.isNotEmpty; final isExpanded = expandedSet.contains(cat.ELECTRONIC_FENCE_AREA_ID); final isSelected = cat.ELECTRONIC_FENCE_AREA_ID == selectedId; return Column( children: [ InkWell( onTap: () { setState(() { if (hasChildren) { isExpanded ? expandedSet.remove(cat.ELECTRONIC_FENCE_AREA_ID) : expandedSet.add(cat.ELECTRONIC_FENCE_AREA_ID); } selectedId = cat.ELECTRONIC_FENCE_AREA_ID; selectedName = cat.name; selected_POSITIONS = cat.POSITIONS; }); }, 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, selected_POSITIONS, 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), ), ), ), ], ), ); } }