import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:qhd_prevention/customWidget/search_bar_widget.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) { // 安全读取并兼容字符串或数字类型的 id 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,便于使用 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; /// 回调,返回选中项的 id, name 和额外数据 final DictSelectCallback onSelected; /// 是否显示搜索框 final bool showSearch; /// 标题 final String title; /// 确认按钮文本 final String confirmText; /// 取消按钮文本 final String cancelText; const MultiDictValuesPicker({ Key? key, required this.dictType, required this.onSelected, this.showSearch = true, this.title = '请选择', this.confirmText = '确定', this.cancelText = '取消', }) : 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(); @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); final raw = result['data'] as List; setState(() { original = raw.map((e) => DictCategory.fromJson(e as Map)).toList(); 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; } Widget _buildRow(DictCategory category, int indent) { final hasChildren = category.children.isNotEmpty; final isExpanded = expandedSet.contains(category.id); final isSelected = category.id == selectedId; return Column( children: [ InkWell( onTap: () { setState(() { if (hasChildren) { isExpanded ? expandedSet.remove(category.id) : expandedSet.add(category.id); } selectedId = category.id; selectedName = category.name; selectedExtraData = category.toMap(); if(indent==0){ 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: Icon( isSelected ? Icons.radio_button_checked : Icons.radio_button_unchecked, color: Colors.blue, ), ), ], ), ), ), 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?['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), ElevatedButton( onPressed: _loadDictData, child: const Text('重试'), ), ], ), ); } 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), ), ); } @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: [ // 标题行 // _buildTitleBar(), // 操作行(取消、搜索、确定) _buildActionBar(), const Divider(height: 1), // 内容区域 Expanded( child: _buildContent(), ), ], ), ); } }