QinGang_interested/lib/customWidget/DangerPartsPicker.dart

510 lines
14 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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<String, dynamic> extValues;
final String parentId;
final String hiddenregionId;
final String hiddenregion;
final String? responsibleDeptName;
final String? responsibleDeptId;
final String? responsibleUserName;
final String? responsibleUserId;
final int sortindex;
final String? comments;
final String? corpinfoId;
final String? sbdl;
final String? sbmc;
final List<DictCategory>? children;
DictCategory({
required this.id,
required this.name,
required this.extValues,
required this.parentId,
required this.hiddenregionId,
required this.hiddenregion,
this.responsibleDeptName,
this.responsibleDeptId,
this.responsibleUserName,
this.responsibleUserId,
required this.sortindex,
this.comments,
this.corpinfoId,
this.sbdl,
this.sbmc,
this.children,
});
factory DictCategory.fromJson(Map<String, dynamic> json) {
// 安全读取并兼容字符串或数字类型的 id
String parseString(dynamic v) {
if (v == null) return '';
if (v is String) return v;
return v.toString();
}
int parseInt(dynamic v) {
if (v == null) return 0;
if (v is int) return v;
if (v is String) return int.tryParse(v) ?? 0;
return 0;
}
// 处理子节点
final rawChildren = json['children'];
List<DictCategory>? childrenList;
if (rawChildren is List && rawChildren.isNotEmpty) {
try {
childrenList = rawChildren
.where((e) => e != null)
.map((e) => DictCategory.fromJson(Map<String, dynamic>.from(e as Map)))
.toList();
} catch (e) {
childrenList = null;
}
}
// 处理扩展值
final extRaw = json['extValues'];
Map<String, dynamic> extMap = {};
if (extRaw is Map) {
extMap = Map<String, dynamic>.from(extRaw);
}
return DictCategory(
id: parseString(json['id']),
name: parseString(json['hiddenregion']), // 使用 hiddenregion 作为显示名称
extValues: extMap,
parentId: parseString(json['parentId']),
hiddenregionId: parseString(json['hiddenregionId']),
hiddenregion: parseString(json['hiddenregion']),
responsibleDeptName: parseString(json['responsibleDeptName']),
responsibleDeptId: parseString(json['responsibleDeptId']),
responsibleUserName: parseString(json['responsibleUserName']),
responsibleUserId: parseString(json['responsibleUserId']),
sortindex: parseInt(json['sortindex']),
comments: parseString(json['comments']),
corpinfoId: parseString(json['corpinfoId']),
sbdl: parseString(json['sbdl']),
sbmc: parseString(json['sbmc']),
children: childrenList,
);
}
// 转换为Map便于使用
Map<String, dynamic> toMap() {
return {
'id': id,
'name': name,
'hiddenregionId': hiddenregionId,
'hiddenregion': hiddenregion,
'parentId': parentId,
'responsibleDeptName': responsibleDeptName,
'responsibleDeptId': responsibleDeptId,
'responsibleUserName': responsibleUserName,
'responsibleUserId': responsibleUserId,
'sortindex': sortindex,
'comments': comments,
'corpinfoId': corpinfoId,
'sbdl': sbdl,
'sbmc': sbmc,
'extValues': extValues,
};
}
}
/// 数据字典选择器回调签名
typedef DictSelectCallback = void Function(String id, String name, Map<String, dynamic>? extraData);
class DangerPartsPicker extends StatefulWidget {
/// 回调,返回选中项的 id, name 和额外数据
final DictSelectCallback onSelected;
/// 是否显示搜索框
final bool showSearch;
/// 标题
final String title;
/// 确认按钮文本
final String confirmText;
/// 取消按钮文本
final String cancelText;
const DangerPartsPicker({
Key? key,
required this.onSelected,
this.showSearch = true,
this.title = '请选择',
this.confirmText = '确定',
this.cancelText = '取消',
}) : super(key: key);
@override
_DangerPartsPickerState createState() => _DangerPartsPickerState();
}
class _DangerPartsPickerState extends State<DangerPartsPicker> {
String selectedId = '';
String selectedName = '';
Map<String, dynamic>? selectedExtraData;
Set<String> expandedSet = {};
List<DictCategory> original = [];
List<DictCategory> filtered = [];
bool loading = true;
bool error = false;
String errorMessage = '';
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<void> _loadDictData() async {
try {
setState(() {
loading = true;
error = false;
});
final result = await HiddenDangerApi.getHiddenDangerAreas();
final raw = result['data'] as List<dynamic>;
setState(() {
original = raw.map((e) => DictCategory.fromJson(e as Map<String, dynamic>)).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<String> _getAllExpandableIds(List<DictCategory> categories) {
Set<String> ids = {};
for (var category in categories) {
if (category.children != null && category.children!.isNotEmpty) {
ids.add(category.id);
ids.addAll(_getAllExpandableIds(category.children!));
}
}
return ids;
}
List<DictCategory> _filterCategories(List<DictCategory> list, String query) {
List<DictCategory> result = [];
for (var cat in list) {
List<DictCategory>? children;
if (cat.children != null) {
children = _filterCategories(cat.children!, query);
}
if (cat.name.toLowerCase().contains(query) ||
(children != null && children.isNotEmpty)) {
result.add(
DictCategory(
id: cat.id,
name: cat.name,
children: children,
extValues: cat.extValues,
parentId: cat.parentId,
hiddenregionId: cat.hiddenregionId,
hiddenregion: cat.hiddenregion,
responsibleDeptName: cat.responsibleDeptName,
responsibleDeptId: cat.responsibleDeptId,
responsibleUserName: cat.responsibleUserName,
responsibleUserId: cat.responsibleUserId,
sortindex: cat.sortindex,
comments: cat.comments,
corpinfoId: cat.corpinfoId,
sbdl: cat.sbdl,
sbmc: cat.sbmc,
),
);
}
}
return result;
}
Widget _buildRow(DictCategory category, int indent) {
final hasChildren = category.children != null && 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();
});
},
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
: () {
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(),
),
],
),
);
}
}