297 lines
9.1 KiB
Dart
297 lines
9.1 KiB
Dart
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/services/SessionService.dart';
|
||
import '../tools/tools.dart'; // 包含 SessionService
|
||
|
||
// 数据模型
|
||
class Category {
|
||
final String id;
|
||
final String name;
|
||
final Map<String, dynamic> extValues;
|
||
final String departmentId;
|
||
final String parentId;
|
||
final String corpinfoId; // 新增:和 departmentId 同层级
|
||
final List<Category> childrenList;
|
||
|
||
Category({
|
||
required this.id,
|
||
required this.name,
|
||
required this.childrenList,
|
||
required this.extValues,
|
||
required this.departmentId,
|
||
required this.parentId,
|
||
required this.corpinfoId,
|
||
});
|
||
|
||
factory Category.fromJson(Map<String, dynamic> json) {
|
||
// 安全读取并兼容字符串或数字类型的 id
|
||
String parseString(dynamic v) {
|
||
if (v == null) return '';
|
||
if (v is String) return v;
|
||
return v.toString();
|
||
}
|
||
|
||
final rawChildren = json['childrenList'];
|
||
List<Category> children = [];
|
||
if (rawChildren is List) {
|
||
try {
|
||
children = rawChildren
|
||
.where((e) => e != null)
|
||
.map((e) => Category.fromJson(Map<String, dynamic>.from(e as Map)))
|
||
.toList();
|
||
} catch (e) {
|
||
// 如果内部解析出错,保持 children 为空并继续
|
||
children = [];
|
||
}
|
||
}
|
||
|
||
// extValues 有可能为 null 或不是 Map
|
||
final extRaw = json['extValues'];
|
||
Map<String, dynamic> extMap = {};
|
||
if (extRaw is Map) {
|
||
extMap = Map<String, dynamic>.from(extRaw);
|
||
}
|
||
|
||
return Category(
|
||
id: parseString(json['id']),
|
||
name: parseString(json['name']),
|
||
childrenList: children,
|
||
extValues: extMap,
|
||
departmentId: parseString(json['departmentId']),
|
||
parentId: parseString(json['parentId']),
|
||
corpinfoId: parseString(json['corpinfoId']), // 从 JSON 同层级读取
|
||
);
|
||
}
|
||
}
|
||
|
||
/// 弹窗回调签名:返回选中项的 id 和 name (保持原样,兼容旧代码)
|
||
typedef DeptSelectCallback = void Function(String id, String name);
|
||
|
||
class DepartmentPickerHidden extends StatefulWidget {
|
||
/// 原回调,返回选中部门 id 与 name(保持不变)
|
||
final DeptSelectCallback onSelected;
|
||
|
||
/// 新增可选扩展回调:当需要 corpinfoId 时使用(不影响原有调用)
|
||
final void Function(String id, String name, String? corpinfoId)? onSelectedWithCorp;
|
||
|
||
/// 是否包含所有公司
|
||
final bool includeAllFirm;
|
||
final Map? data;
|
||
|
||
const DepartmentPickerHidden({
|
||
Key? key,
|
||
required this.onSelected,
|
||
this.onSelectedWithCorp,
|
||
this.includeAllFirm = false,
|
||
this.data,
|
||
}) : super(key: key);
|
||
|
||
@override
|
||
_DepartmentPickerHiddenState createState() => _DepartmentPickerHiddenState();
|
||
}
|
||
|
||
class _DepartmentPickerHiddenState extends State<DepartmentPickerHidden> {
|
||
String selectedId = '';
|
||
String selectedName = '';
|
||
String? selectedCorpinfoId; // 记录选中项的 corpinfoId(可为 null)
|
||
Set<String> expandedSet = {};
|
||
|
||
List<Category> original = [];
|
||
List<Category> filtered = [];
|
||
bool loading = true;
|
||
|
||
final TextEditingController _searchController = TextEditingController();
|
||
|
||
@override
|
||
void initState() {
|
||
super.initState();
|
||
// 初始均为空
|
||
selectedId = '';
|
||
selectedName = '';
|
||
selectedCorpinfoId = null;
|
||
expandedSet = {};
|
||
_searchController.addListener(_onSearchChanged);
|
||
_loadData();
|
||
}
|
||
|
||
@override
|
||
void dispose() {
|
||
_searchController.removeListener(_onSearchChanged);
|
||
_searchController.dispose();
|
||
super.dispose();
|
||
}
|
||
|
||
Future<void> _loadData() async {
|
||
try {
|
||
final result = await BasicInfoApi.getDeptFour(widget.includeAllFirm, data: widget.data);
|
||
final raw = result['data'] as List<dynamic>;
|
||
print(raw);
|
||
setState(() {
|
||
original = raw.map((e) => Category.fromJson(e as Map<String, dynamic>)).toList();
|
||
filtered = original;
|
||
loading = false;
|
||
});
|
||
} catch (e) {
|
||
setState(() => loading = false);
|
||
}
|
||
}
|
||
|
||
void _onSearchChanged() {
|
||
final query = _searchController.text.toLowerCase().trim();
|
||
setState(() {
|
||
filtered = query.isEmpty ? original : _filterCategories(original, query);
|
||
});
|
||
}
|
||
|
||
List<Category> _filterCategories(List<Category> list, String query) {
|
||
List<Category> result = [];
|
||
for (var cat in list) {
|
||
final children = _filterCategories(cat.childrenList, query);
|
||
if (cat.name.toLowerCase().contains(query) || children.isNotEmpty) {
|
||
result.add(
|
||
Category(
|
||
id: cat.id,
|
||
name: cat.name,
|
||
childrenList: children,
|
||
extValues: cat.extValues,
|
||
departmentId: cat.departmentId,
|
||
parentId: cat.parentId,
|
||
corpinfoId: cat.corpinfoId, // 保持 corpinfoId 传递
|
||
),
|
||
);
|
||
}
|
||
}
|
||
return result;
|
||
}
|
||
|
||
Widget _buildRow(Category cat, int indent) {
|
||
final hasChildren = cat.childrenList.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);
|
||
}
|
||
selectedId = cat.id;
|
||
selectedName = cat.name;
|
||
selectedCorpinfoId = cat.corpinfoId.isEmpty ? null : cat.corpinfoId;
|
||
});
|
||
},
|
||
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.childrenList.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: () {
|
||
// 关闭弹窗并回调:优先调用扩展回调(带 corpinfoId),否则调用原回调
|
||
Navigator.of(context).pop();
|
||
if (widget.onSelectedWithCorp != null) {
|
||
widget.onSelectedWithCorp!(
|
||
selectedId,
|
||
selectedName,
|
||
selectedCorpinfoId,
|
||
);
|
||
} else {
|
||
widget.onSelected(selectedId, selectedName);
|
||
}
|
||
},
|
||
child: const Text(
|
||
'确定',
|
||
style: TextStyle(fontSize: 16, color: Colors.blue),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
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),
|
||
),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
}
|