import 'package:flutter/material.dart'; import 'package:qhd_prevention/customWidget/search_bar_widget.dart'; import 'package:qhd_prevention/customWidget/custom_button.dart'; import 'package:qhd_prevention/http/modules/basic_info_api.dart'; import 'package:qhd_prevention/http/ApiService.dart'; // 数据字典模型 class DictCategory { final String id; final String name; final Map extValues; final String parentId; final String dictValue; final String dictLabel; final List 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 json) { String parseString(dynamic v) { if (v == null) return ''; if (v is String) return v; return v.toString(); } final rawChildren = json['children']; List childrenList = []; if (rawChildren is List) { try { childrenList = rawChildren .where((e) => e != null) .map((e) => DictCategory.fromJson(Map.from(e as Map))) .toList(); } catch (e) { childrenList = []; } } final extRaw = json['extValues']; Map extMap = {}; if (extRaw is Map) { extMap = Map.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 toMap() { return { 'id': id, 'name': name, 'dictValue': dictValue, 'dictLabel': dictLabel, 'parentId': parentId, 'extValues': extValues, }; } } typedef DictSelectCallback = void Function(String id, String name, Map? extraData); class MultiDictValuesPicker extends StatefulWidget { final String dictType; final DictSelectCallback onSelected; final bool showSearch; final String title; final String confirmText; final String cancelText; final bool showIgnoreHidden; final bool isSafeChekDangerLevel; final bool allowSelectParent; const MultiDictValuesPicker({ Key? key, required this.dictType, required this.onSelected, this.showSearch = true, this.title = '请选择', this.confirmText = '确定', this.cancelText = '取消', this.isSafeChekDangerLevel = false, this.showIgnoreHidden = false, this.allowSelectParent = true, }) : super(key: key); @override _MultiDictValuesPickerState createState() => _MultiDictValuesPickerState(); } class _MultiDictValuesPickerState extends State { String selectedId = ''; String selectedName = ''; Map? selectedExtraData; Set expandedSet = {}; List original = []; List filtered = []; bool loading = true; bool error = false; String errorMessage = ''; String dingValueWai = ''; String dingNameWai = ''; final TextEditingController _searchController = TextEditingController(); // id -> node 映射,用于快速查父节点 final Map _idMap = {}; @override void initState() { super.initState(); selectedId = ''; selectedName = ''; expandedSet = {}; _searchController.addListener(_onSearchChanged); _loadDictData(); } @override void dispose() { _searchController.removeListener(_onSearchChanged); _searchController.dispose(); super.dispose(); } Future _loadDictData() async { try { setState(() { loading = true; error = false; }); final result = await BasicInfoApi.getDictValues(widget.dictType); List raw = result['data'] as List; if (!widget.showIgnoreHidden) { if (widget.dictType == 'hiddenLevel') { for (int i = 0; i < raw.length; i++) { if (raw[i]['children'] != null) { raw[i]['children'].removeWhere((child) => child != null && child['dictLabel'] == '忽略隐患'); } } } } late List resultRows = []; if (widget.isSafeChekDangerLevel) { if (widget.dictType == 'hiddenLevel') { for (int i = 0; i < raw.length; i++) { if (raw[i]['children'] != null) { for (int m = 0; m < raw[i]['children'].length; m++) { final ch = raw[i]['children'][m]; if (ch != null && (ch['dictLabel'] == '轻微隐患' || ch['dictLabel'] == '一般隐患')) { resultRows.add(ch); } } } } } else { resultRows = raw; } } else { resultRows = raw; } // 转换为 DictCategory 列表 final loaded = resultRows.map((e) => DictCategory.fromJson(e as Map)).toList(); // 构建 id map(包含所有节点的扁平映射) _idMap.clear(); void buildIdMap(List nodes) { for (final n in nodes) { _idMap[n.id] = n; if (n.children.isNotEmpty) { buildIdMap(n.children); } } } buildIdMap(loaded); setState(() { original = loaded; 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 _getAllExpandableIds(List categories) { Set ids = {}; for (var category in categories) { if (category.children.isNotEmpty) { ids.add(category.id); ids.addAll(_getAllExpandableIds(category.children)); } } return ids; } 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( 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; } // 判断节点是否可选 bool _isSelectableNode(DictCategory category) { if (widget.allowSelectParent) { return true; } return category.children.isEmpty; } // 查找某个 id 的最上层祖先节点(若自身就是顶层则返回自身) DictCategory? _getTopAncestor(String id) { if (id.isEmpty) return null; DictCategory? current = _idMap[id]; if (current == null) return null; // 如果没有 parentId 或 parentId 为空,当前即为顶层 while (current != null && current.parentId.isNotEmpty) { final parent = _idMap[current.parentId]; if (parent == null) break; current = parent; } return current; } Widget _buildRow(DictCategory category, int indent) { final hasChildren = category.children.isNotEmpty; final isExpanded = expandedSet.contains(category.id); final isSelected = category.id == selectedId; final isSelectable = _isSelectableNode(category); return Column( children: [ InkWell( onTap: () { setState(() { if (hasChildren) { isExpanded ? expandedSet.remove(category.id) : expandedSet.add(category.id); } if (isSelectable) { selectedId = category.id; selectedName = category.name; selectedExtraData = category.toMap(); // 计算最上层父节点(如果存在) final top = _getTopAncestor(category.id); if (top != null) { dingValueWai = top.dictValue; dingNameWai = top.name; } else { // 回退到自身值(防御) 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: isSelectable ? Icon( isSelected ? Icons.radio_button_checked : Icons.radio_button_unchecked, color: Colors.blue, ) : null, ), ], ), ), ), 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 存在) final top = _getTopAncestor(selectedId); if (top != null) { selectedExtraData ??= {}; selectedExtraData!['dingValue'] = top.dictValue; selectedExtraData!['dingName'] = top.name; } else { selectedExtraData ??= {}; 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), CustomButton(text: '重试', onPressed: _loadDictData), ], ), ); } 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), ), ); } Widget _buildHintMessage() { if (!widget.allowSelectParent) { return Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), color: Colors.blue[50], child: Row( children: [ Icon(Icons.info_outline, size: 16, color: Colors.blue[700]), const SizedBox(width: 8), Expanded(child: Text('只能选择没有子项的节点', style: TextStyle(fontSize: 12, color: Colors.blue[700]))), ], ), ); } return const SizedBox.shrink(); } @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: [ _buildActionBar(), const Divider(height: 1), Expanded(child: _buildContent()), ], ), ); } }