2026-04-08 15:03:56 +08:00
|
|
|
import 'dart:convert';
|
|
|
|
|
import 'package:flutter/material.dart';
|
|
|
|
|
import 'package:qhd_prevention/customWidget/search_bar_widget.dart';
|
|
|
|
|
import 'package:qhd_prevention/customWidget/toast_util.dart';
|
|
|
|
|
import 'package:qhd_prevention/http/ApiService.dart';
|
|
|
|
|
import 'package:qhd_prevention/tools/tools.dart';
|
|
|
|
|
|
|
|
|
|
/// 数据模型
|
|
|
|
|
class CategoryTypeThree {
|
|
|
|
|
final String id;
|
|
|
|
|
final String name;
|
|
|
|
|
final String pdId;
|
|
|
|
|
final Map<String, dynamic> rawJson;
|
|
|
|
|
final List<CategoryTypeThree> children;
|
|
|
|
|
|
|
|
|
|
CategoryTypeThree({
|
|
|
|
|
required this.id,
|
|
|
|
|
required this.name,
|
|
|
|
|
required this.pdId,
|
|
|
|
|
required this.rawJson,
|
|
|
|
|
this.children = const [],
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
factory CategoryTypeThree.fromJson(Map<String, dynamic> json) {
|
|
|
|
|
return CategoryTypeThree(
|
|
|
|
|
id: json['id'] != null ? json['id'].toString() : "",
|
|
|
|
|
name: json['projectName'] != null ? json['projectName'].toString() : "",
|
|
|
|
|
pdId: json['parentId'] != null ? json['parentId'].toString() : "",
|
|
|
|
|
rawJson: Map<String, dynamic>.from(json),
|
|
|
|
|
children: _parseChildren(json['children']),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static List<CategoryTypeThree> _parseChildren(dynamic childrenData) {
|
|
|
|
|
if (childrenData == null) return [];
|
|
|
|
|
if (childrenData is! List) return [];
|
|
|
|
|
|
|
|
|
|
return childrenData
|
|
|
|
|
.whereType<Map<String, dynamic>>()
|
|
|
|
|
.map((e) => CategoryTypeThree.fromJson(e))
|
|
|
|
|
.toList();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// 原来的回调保持不变
|
|
|
|
|
typedef DeptSelectCallback = void Function(String id, String name, String pdId);
|
|
|
|
|
|
|
|
|
|
/// 新增:整条 JSON 回调(可选,不影响旧调用)
|
|
|
|
|
typedef DeptSelectJsonCallback = void Function(
|
|
|
|
|
String id,
|
|
|
|
|
String name,
|
|
|
|
|
String pdId,
|
|
|
|
|
Map<String, dynamic> selectedJson,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
class RelatedPartiesPicker extends StatefulWidget {
|
|
|
|
|
/// 原来的回调,保留不变
|
|
|
|
|
final DeptSelectCallback onSelected;
|
|
|
|
|
|
|
|
|
|
/// 新增:可选回调,回传整条 JSON
|
|
|
|
|
final DeptSelectJsonCallback? onSelectedJson;
|
|
|
|
|
|
|
|
|
|
final listdata;
|
|
|
|
|
|
|
|
|
|
const RelatedPartiesPicker({
|
|
|
|
|
Key? key,
|
|
|
|
|
required this.onSelected,
|
|
|
|
|
required this.listdata,
|
|
|
|
|
this.onSelectedJson,
|
|
|
|
|
}) : super(key: key);
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
_RelatedPartiesPickerState createState() => _RelatedPartiesPickerState();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class _RelatedPartiesPickerState extends State<RelatedPartiesPicker> {
|
|
|
|
|
String selectedId = '';
|
|
|
|
|
String selectedPDId = '';
|
|
|
|
|
String selectedName = '';
|
|
|
|
|
Map<String, dynamic> selectedJson = {};
|
|
|
|
|
|
|
|
|
|
Set<String> expandedSet = {};
|
|
|
|
|
|
|
|
|
|
List<CategoryTypeThree> original = [];
|
|
|
|
|
List<CategoryTypeThree> filtered = [];
|
|
|
|
|
bool loading = true;
|
|
|
|
|
|
|
|
|
|
final TextEditingController _searchController = TextEditingController();
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
void initState() {
|
|
|
|
|
super.initState();
|
|
|
|
|
selectedId = '';
|
|
|
|
|
selectedName = '';
|
|
|
|
|
selectedPDId = '';
|
|
|
|
|
selectedJson = {};
|
|
|
|
|
expandedSet = {};
|
|
|
|
|
_searchController.addListener(_onSearchChanged);
|
|
|
|
|
|
|
|
|
|
_getRelatedPartiesList();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Future<void> _getRelatedPartiesList() async {
|
|
|
|
|
try {
|
2026-04-13 08:59:45 +08:00
|
|
|
final raw = await BasicInfoApi.getCropinfoProjectNameList();
|
2026-04-08 15:03:56 +08:00
|
|
|
if (raw['success']) {
|
|
|
|
|
final newList = raw['data'] ?? [];
|
|
|
|
|
setState(() {
|
|
|
|
|
original = (newList as List<dynamic>)
|
|
|
|
|
.whereType<Map<String, dynamic>>()
|
|
|
|
|
.map<CategoryTypeThree>((json) => CategoryTypeThree.fromJson(json))
|
|
|
|
|
.toList();
|
|
|
|
|
filtered = original;
|
|
|
|
|
loading = false;
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
ToastUtil.showNormal(context, "获取列表失败");
|
|
|
|
|
setState(() {
|
|
|
|
|
original = [];
|
|
|
|
|
filtered = original;
|
|
|
|
|
loading = false;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
} catch (e) {
|
|
|
|
|
print('获取数据失败:$e');
|
|
|
|
|
setState(() {
|
|
|
|
|
original = [];
|
|
|
|
|
filtered = original;
|
|
|
|
|
loading = false;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
void dispose() {
|
|
|
|
|
_searchController.removeListener(_onSearchChanged);
|
|
|
|
|
_searchController.dispose();
|
|
|
|
|
super.dispose();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void _onSearchChanged() {
|
|
|
|
|
final query = _searchController.text.toLowerCase().trim();
|
|
|
|
|
setState(() {
|
|
|
|
|
filtered = query.isEmpty ? original : _filterCategories(original, query);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
List<CategoryTypeThree> _filterCategories(
|
|
|
|
|
List<CategoryTypeThree> list,
|
|
|
|
|
String query,
|
|
|
|
|
) {
|
|
|
|
|
List<CategoryTypeThree> result = [];
|
|
|
|
|
for (var cat in list) {
|
|
|
|
|
final children = _filterCategories(cat.children, query);
|
|
|
|
|
if (cat.name.toLowerCase().contains(query) || children.isNotEmpty) {
|
|
|
|
|
result.add(
|
|
|
|
|
CategoryTypeThree(
|
|
|
|
|
id: cat.id,
|
|
|
|
|
name: cat.name,
|
|
|
|
|
pdId: cat.pdId,
|
|
|
|
|
rawJson: cat.rawJson,
|
|
|
|
|
children: children,
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Widget _buildRow(CategoryTypeThree 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);
|
|
|
|
|
selectedPDId = cat.pdId;
|
|
|
|
|
} else {
|
|
|
|
|
selectedPDId = cat.id;
|
|
|
|
|
}
|
|
|
|
|
selectedId = cat.id;
|
|
|
|
|
selectedName = cat.name;
|
|
|
|
|
selectedJson = cat.rawJson;
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
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.blue,
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
if (hasChildren && isExpanded)
|
|
|
|
|
...cat.children.map((c) => _buildRow(c, indent + 1)),
|
|
|
|
|
],
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void _confirm() {
|
|
|
|
|
Navigator.of(context).pop();
|
|
|
|
|
widget.onSelected(selectedId, selectedName, selectedPDId);
|
|
|
|
|
|
|
|
|
|
// 新增回调:不影响旧调用
|
|
|
|
|
widget.onSelectedJson?.call(
|
|
|
|
|
selectedId,
|
|
|
|
|
selectedName,
|
|
|
|
|
selectedPDId,
|
|
|
|
|
selectedJson,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@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: _confirm,
|
|
|
|
|
child: const Text(
|
|
|
|
|
'确定',
|
|
|
|
|
style: TextStyle(fontSize: 16, color: Colors.blue),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
const 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),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|