From eabb7467383149aebc8836f3d4d263d2a2fee496 Mon Sep 17 00:00:00 2001 From: louis <869322496@qq.com> Date: Tue, 9 Apr 2024 11:44:55 +0800 Subject: [PATCH] feat: cascade picker common widget developed --- lib/apis/{index.dart => api.dart} | 5 + lib/constants/global_url.dart | 1 + lib/models/dept.model.dart | 28 +- .../hr_manage/components/dept_picker.dart | 129 ++- .../hr_manage/components/edit_userinfo.dart | 21 +- .../hr_manage/components/employee_detail.dart | 2 +- lib/screens/hr_manage/hr_manage.dart | 2 +- .../components/inventory_inout_info.dart | 2 +- .../inventory_inout_controller.dart | 2 +- lib/screens/landing/landing_controller.dart | 2 +- lib/screens/login/login.controller.dart | 2 +- .../components/agent_search.dart | 2 +- .../components/inventory_search.dart | 2 +- .../components/product_search.dart | 2 +- .../new_inventory_inout_controller.dart | 2 +- lib/services/app_info.service.dart | 2 +- lib/services/dio.service.dart | 10 +- lib/store/auth.store.dart | 2 +- lib/store/dict.store.dart | 2 +- lib/util/common.util.dart | 14 + lib/util/media_util.dart | 2 +- lib/widgets/core/sk_cascade_picker.dart | 746 +++++++++--------- 22 files changed, 502 insertions(+), 480 deletions(-) rename lib/apis/{index.dart => api.dart} (97%) diff --git a/lib/apis/index.dart b/lib/apis/api.dart similarity index 97% rename from lib/apis/index.dart rename to lib/apis/api.dart index e7e4b45..34e9423 100644 --- a/lib/apis/index.dart +++ b/lib/apis/api.dart @@ -15,6 +15,11 @@ class Api { ); } + /// 获取部门信息 + static Future getDepts() { + return DioService.dio.get(Urls.depts); + } + /// 查询参数配置信息By key static Future getSystemParamConfigByCode(String code) { return DioService.dio.get( diff --git a/lib/constants/global_url.dart b/lib/constants/global_url.dart index 62704a0..dc124c2 100644 --- a/lib/constants/global_url.dart +++ b/lib/constants/global_url.dart @@ -16,4 +16,5 @@ class Urls { static String systemParamConfig = 'system/param-config'; static String accountMenus = 'account/menus'; static String userInfo = 'system/users'; + static String depts = 'system/depts'; } diff --git a/lib/models/dept.model.dart b/lib/models/dept.model.dart index 7f2b074..6e5496c 100644 --- a/lib/models/dept.model.dart +++ b/lib/models/dept.model.dart @@ -1,25 +1,37 @@ -class DeptModel { +import 'package:sk_base_mobile/util/common.util.dart'; + +class DeptModel extends TreeNode { DeptModel({ required this.id, required this.createdAt, required this.updatedAt, required this.name, required this.orderNo, + this.parent, + this.children, }); - final int? id; + final int id; final DateTime? createdAt; final DateTime? updatedAt; - final String? name; - final int? orderNo; + final String name; + final int orderNo; + final DeptModel? parent; + List? children = []; factory DeptModel.fromJson(Map json) { return DeptModel( - id: json["id"], + id: json["id"] ?? 0, createdAt: DateTime.tryParse(json["createdAt"] ?? ""), updatedAt: DateTime.tryParse(json["updatedAt"] ?? ""), - name: json["name"], - orderNo: json["orderNo"], + name: json["name"] ?? "", + orderNo: json["orderNo"] ?? 0, + parent: + json["parent"] == null ? null : DeptModel.fromJson(json["parent"]), + children: json["children"] == null + ? [] + : List.from( + json["children"]!.map((x) => DeptModel.fromJson(x))), ); } @@ -29,5 +41,7 @@ class DeptModel { "updatedAt": updatedAt?.toIso8601String(), "name": name, "orderNo": orderNo, + "parent": parent?.toJson(), + "children": children?.map((x) => x.toJson()).toList(), }; } diff --git a/lib/screens/hr_manage/components/dept_picker.dart b/lib/screens/hr_manage/components/dept_picker.dart index 8189131..84fef8e 100644 --- a/lib/screens/hr_manage/components/dept_picker.dart +++ b/lib/screens/hr_manage/components/dept_picker.dart @@ -1,90 +1,63 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; +import 'package:sk_base_mobile/apis/api.dart'; import 'package:sk_base_mobile/app_theme.dart'; +import 'package:sk_base_mobile/models/dept.model.dart'; +import 'package:sk_base_mobile/util/logger_util.dart'; import 'package:sk_base_mobile/util/screen_adaper_util.dart'; import 'package:sk_base_mobile/widgets/core/sk_cascade_picker.dart'; -import 'package:sk_base_mobile/widgets/gradient_button.dart'; class DeptPicker extends StatelessWidget { - DeptPicker({super.key}); - - final _cascadeController = Get.put(CascadeController()); - + final Function(String)? onSelected; + DeptPicker({super.key, this.onSelected}); @override Widget build(BuildContext context) { - return Container( - decoration: BoxDecoration(color: AppTheme.nearlyWhite), - height: ScreenAdaper.height(400), - child: Column( - children: [ - GradientButton( - buttonText: '确定', - onPressed: () { - if (_cascadeController.isCompleted()) { - // 已选中的titles - List selectedTitles = - _cascadeController.selectedTitles; - print("已选中的titles: $selectedTitles"); - // 已选中的序号 - List selectedIndexes = - _cascadeController.selectedIndexes; - print("已选中的序号:$selectedIndexes"); + return SkCascadePicker( + getData: getData, + initialPageData: (cascadeController) => + cascadeController.treeData.toList(), + nextPageData: (pageCallback, currentPage, selectIndex, currentPageItem, + cascadeController) async { + if (currentPageItem.children != null) { + List>? nextPageData = currentPageItem + .children! + .map>((e) => + CascadeItem(label: e.name, value: e.id, children: e.children)) + .toList(); + if (nextPageData.isNotEmpty) pageCallback(nextPageData); + } + }, + onConfirm: (List> value) { + if (onSelected != null && value.isNotEmpty) { + onSelected!(value.last.label); + } + }, + maxPageNum: 10, + tabTitleStyle: + TextStyle(fontSize: ScreenAdaper.height(26), color: Colors.black), + itemTitleStyle: + TextStyle(fontSize: ScreenAdaper.height(26), color: Colors.black), + selectedIcon: const Icon(Icons.check, color: AppTheme.primaryColorLight), + ); + } - Item item = _cascadeController - .items[selectedIndexes[0]] - .children![selectedIndexes[1]] - .children![selectedIndexes[2]]; - print("已选择item( ${item.name} )"); - } - }, - ), - SizedBox( - height: 10, - ), - Expanded( - child: CascadePicker( - initialPageData: - _cascadeController.items.map((e) => e.name!).toList(), - nextPageData: (pageCallback, currentPage, selectIndex) async { - print("当前选择: 第$currentPage页, 第$selectIndex项"); - if (currentPage == 1) { - // 在第一页选中,返回第二页列表数据 - List? nextPageData = _cascadeController - .items[selectIndex].children - ?.map((e) => e.name!) - .toList(); - if (nextPageData != null) pageCallback(nextPageData); - } else if (currentPage == 2) { - // 在第二页选中,返回第二页列表数据 - // 先获取已选中的序号 - List selectedIndexes = - _cascadeController.selectedIndexes; - // 根据已选中的序号在items中获取下一级页面的列表数据 - List? nextPageData = _cascadeController - .items[selectedIndexes[0]].children?[selectIndex].children - ?.map((e) => e.name!) - .toList(); - if (nextPageData != null) pageCallback(nextPageData); - } - }, - controller: _cascadeController, - maxPageNum: 3, - tabTitleStyle: TextStyle( - fontSize: ScreenAdaper.height(26), color: Colors.black), - itemTitleStyle: TextStyle( - fontSize: ScreenAdaper.height(26), color: Colors.black), - selectedIcon: - Icon(Icons.check, color: AppTheme.primaryColorLight), - )) - ], - )); + Future>> getData() async { + await Future.delayed(const Duration(milliseconds: 500)); + try { + final res = await Api.getDepts(); + if (res.data != null) { + List> result = + res.data.map>((e) { + DeptModel data = DeptModel.fromJson(e); + return CascadeItem( + label: data.name, value: data.id, children: data.children); + }).toList(); + return result; + } + return []; + } catch (e) { + LoggerUtil().error(e); + return []; + } } } - -class Item { - String? name; - String? code; - String? fatherCode; - String? remark; - List? children; -} diff --git a/lib/screens/hr_manage/components/edit_userinfo.dart b/lib/screens/hr_manage/components/edit_userinfo.dart index 10b0fa3..5977bae 100644 --- a/lib/screens/hr_manage/components/edit_userinfo.dart +++ b/lib/screens/hr_manage/components/edit_userinfo.dart @@ -2,7 +2,7 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; -import 'package:sk_base_mobile/apis/index.dart'; +import 'package:sk_base_mobile/apis/api.dart'; import 'package:sk_base_mobile/app_theme.dart'; import 'package:sk_base_mobile/constants/bg_color.dart'; import 'package:sk_base_mobile/models/user_info.model.dart'; @@ -46,6 +46,8 @@ class EditUserInfo extends StatelessWidget { SizedBox( height: ScreenAdaper.height(defaultPadding), ), + + /// 姓名 SkTextInput( isDense: true, textController: controller.nameEditController, @@ -54,19 +56,24 @@ class EditUserInfo extends StatelessWidget { SizedBox( height: ScreenAdaper.height(defaultPadding), ), + + /// 部门 SkTextInput( isDense: true, keyboardType: TextInputType.none, - textController: TextEditingController(), + textController: controller.deptEditController, labelText: '所属部门', onTap: (_) async { - Get.bottomSheet(DeptPicker()) - .then((value) => Get.delete()); + Get.bottomSheet(DeptPicker(onSelected: (String label) { + controller.deptEditController.text = label; + })); }, ), SizedBox( height: ScreenAdaper.height(defaultPadding), ), + + /// 角色 SkTextInput( isDense: true, keyboardType: TextInputType.none, @@ -75,7 +82,8 @@ class EditUserInfo extends StatelessWidget { onTap: (_) async { Get.bottomSheet(Container( height: ScreenAdaper.height(400), - decoration: BoxDecoration(color: AppTheme.nearlyWhite), + decoration: + const BoxDecoration(color: AppTheme.nearlyWhite), child: Column(children: [ Container( padding: EdgeInsets.symmetric( @@ -88,7 +96,7 @@ class EditUserInfo extends StatelessWidget { style: TextStyle( fontSize: ScreenAdaper.height(30)), ), - Spacer(), + const Spacer(), Text( '确定', style: TextStyle( @@ -262,6 +270,7 @@ class EditUserInfoController extends GetxController { int userId; EditUserInfoController(this.userId); final nameEditController = TextEditingController(); + final deptEditController = TextEditingController(); final userInfo = Rxn(); RxList selectedDepts = RxList([]); diff --git a/lib/screens/hr_manage/components/employee_detail.dart b/lib/screens/hr_manage/components/employee_detail.dart index 6a6c92e..74e165e 100644 --- a/lib/screens/hr_manage/components/employee_detail.dart +++ b/lib/screens/hr_manage/components/employee_detail.dart @@ -130,7 +130,7 @@ class EmployeeDetail extends StatelessWidget { text: '考勤记录', ), Tab( - text: '工作安排', + text: '任务进度', ), Tab( text: '相关文件', diff --git a/lib/screens/hr_manage/hr_manage.dart b/lib/screens/hr_manage/hr_manage.dart index 49d76d3..69007ef 100644 --- a/lib/screens/hr_manage/hr_manage.dart +++ b/lib/screens/hr_manage/hr_manage.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:pull_to_refresh/pull_to_refresh.dart'; -import 'package:sk_base_mobile/apis/index.dart'; +import 'package:sk_base_mobile/apis/api.dart'; import 'package:sk_base_mobile/app_theme.dart'; import 'package:sk_base_mobile/config.dart'; import 'package:sk_base_mobile/constants/bg_color.dart'; diff --git a/lib/screens/inventory_inout/components/inventory_inout_info.dart b/lib/screens/inventory_inout/components/inventory_inout_info.dart index 2af6374..49b9793 100644 --- a/lib/screens/inventory_inout/components/inventory_inout_info.dart +++ b/lib/screens/inventory_inout/components/inventory_inout_info.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; -import 'package:sk_base_mobile/apis/index.dart'; +import 'package:sk_base_mobile/apis/api.dart'; import 'package:sk_base_mobile/app_theme.dart'; import 'package:sk_base_mobile/config.dart'; import 'package:sk_base_mobile/constants/bg_color.dart'; diff --git a/lib/screens/inventory_inout/inventory_inout_controller.dart b/lib/screens/inventory_inout/inventory_inout_controller.dart index 05c4225..fe990b9 100644 --- a/lib/screens/inventory_inout/inventory_inout_controller.dart +++ b/lib/screens/inventory_inout/inventory_inout_controller.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:pull_to_refresh/pull_to_refresh.dart'; -import 'package:sk_base_mobile/apis/index.dart'; +import 'package:sk_base_mobile/apis/api.dart'; import 'package:sk_base_mobile/app_theme.dart'; import 'package:sk_base_mobile/constants/enum.dart'; import 'package:sk_base_mobile/db_helper/db_help.dart'; diff --git a/lib/screens/landing/landing_controller.dart b/lib/screens/landing/landing_controller.dart index 31d9097..77197e3 100644 --- a/lib/screens/landing/landing_controller.dart +++ b/lib/screens/landing/landing_controller.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; -import 'package:sk_base_mobile/apis/index.dart'; +import 'package:sk_base_mobile/apis/api.dart'; import 'package:sk_base_mobile/models/app_bottom_nav_item.dart'; import 'package:sk_base_mobile/screens/inventory/inventory.dart'; import 'package:sk_base_mobile/screens/inventory_inout/inventory_inout.dart'; diff --git a/lib/screens/login/login.controller.dart b/lib/screens/login/login.controller.dart index 983cfd8..d1d7a29 100644 --- a/lib/screens/login/login.controller.dart +++ b/lib/screens/login/login.controller.dart @@ -1,6 +1,6 @@ import 'package:flutter/widgets.dart'; import 'package:get/get.dart'; -import 'package:sk_base_mobile/apis/index.dart'; +import 'package:sk_base_mobile/apis/api.dart'; import 'package:sk_base_mobile/util/device.util.dart'; import 'package:sk_base_mobile/util/snack_bar.util.dart'; import '../../constants/constants.dart'; diff --git a/lib/screens/new_inventory_inout/components/agent_search.dart b/lib/screens/new_inventory_inout/components/agent_search.dart index 47e7aab..b351694 100644 --- a/lib/screens/new_inventory_inout/components/agent_search.dart +++ b/lib/screens/new_inventory_inout/components/agent_search.dart @@ -3,7 +3,7 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:pull_to_refresh/pull_to_refresh.dart'; -import 'package:sk_base_mobile/apis/index.dart'; +import 'package:sk_base_mobile/apis/api.dart'; import 'package:sk_base_mobile/app_theme.dart'; import 'package:sk_base_mobile/constants/bg_color.dart'; import 'package:sk_base_mobile/models/user_info.model.dart'; diff --git a/lib/screens/new_inventory_inout/components/inventory_search.dart b/lib/screens/new_inventory_inout/components/inventory_search.dart index 07570b9..9d70e70 100644 --- a/lib/screens/new_inventory_inout/components/inventory_search.dart +++ b/lib/screens/new_inventory_inout/components/inventory_search.dart @@ -3,7 +3,7 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:pull_to_refresh/pull_to_refresh.dart'; -import 'package:sk_base_mobile/apis/index.dart'; +import 'package:sk_base_mobile/apis/api.dart'; import 'package:sk_base_mobile/app_theme.dart'; import 'package:sk_base_mobile/constants/bg_color.dart'; import 'package:sk_base_mobile/models/index.dart'; diff --git a/lib/screens/new_inventory_inout/components/product_search.dart b/lib/screens/new_inventory_inout/components/product_search.dart index 04d6786..2d75542 100644 --- a/lib/screens/new_inventory_inout/components/product_search.dart +++ b/lib/screens/new_inventory_inout/components/product_search.dart @@ -3,7 +3,7 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:pull_to_refresh/pull_to_refresh.dart'; -import 'package:sk_base_mobile/apis/index.dart'; +import 'package:sk_base_mobile/apis/api.dart'; import 'package:sk_base_mobile/app_theme.dart'; import 'package:sk_base_mobile/constants/bg_color.dart'; import 'package:sk_base_mobile/models/index.dart'; diff --git a/lib/screens/new_inventory_inout/new_inventory_inout_controller.dart b/lib/screens/new_inventory_inout/new_inventory_inout_controller.dart index 4e818c1..c7e9c99 100644 --- a/lib/screens/new_inventory_inout/new_inventory_inout_controller.dart +++ b/lib/screens/new_inventory_inout/new_inventory_inout_controller.dart @@ -4,7 +4,7 @@ import 'package:decimal/decimal.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:image_picker/image_picker.dart'; -import 'package:sk_base_mobile/apis/index.dart'; +import 'package:sk_base_mobile/apis/api.dart'; import 'package:sk_base_mobile/constants/enum.dart'; import 'package:sk_base_mobile/db_helper/db_help.dart'; import 'package:sk_base_mobile/models/index.dart'; diff --git a/lib/services/app_info.service.dart b/lib/services/app_info.service.dart index 4323cd7..c8e9dd9 100644 --- a/lib/services/app_info.service.dart +++ b/lib/services/app_info.service.dart @@ -7,7 +7,7 @@ import 'package:get/get.dart'; import 'package:install_plugin/install_plugin.dart'; import 'package:package_info/package_info.dart'; import 'package:permission_handler/permission_handler.dart'; -import 'package:sk_base_mobile/apis/index.dart'; +import 'package:sk_base_mobile/apis/api.dart'; import 'package:sk_base_mobile/app_theme.dart'; import 'package:sk_base_mobile/config.dart'; import 'package:sk_base_mobile/models/app_config.dart'; diff --git a/lib/services/dio.service.dart b/lib/services/dio.service.dart index e0f4dd7..727150c 100644 --- a/lib/services/dio.service.dart +++ b/lib/services/dio.service.dart @@ -118,10 +118,10 @@ class DioService extends get_package.GetxService { options.headers['model'] = StorageService.to .getString(CacheKeys.deviceModel, isWithUser: false); // 设备型号 } - if (GloablConfig.DEBUG && (options.data is! FormData)) { - LoggerUtil().info( - '[Service-dio] url: ${options.path}, params: ${jsonEncode(options.queryParameters)}, body: ${jsonEncode(options.data)}, Header:${jsonEncode(options.headers)}'); - } + // if (GloablConfig.DEBUG && (options.data is! FormData)) { + // LoggerUtil().info( + // '[Service-dio] url: ${options.path}, params: ${jsonEncode(options.queryParameters)}, body: ${jsonEncode(options.data)}, Header:${jsonEncode(options.headers)}'); + // } handler.next(options); } @@ -135,7 +135,7 @@ class DioService extends get_package.GetxService { } if (response.data != null && response.data is Map) { if (response.data['code'] == 200) { - if (GloablConfig.DEBUG) LoggerUtil().info(response.data['data']); + // if (GloablConfig.DEBUG) LoggerUtil().info(response.data['data']); response.data = response.data['data']; // 分页数据处理 if (response.data != null && diff --git a/lib/store/auth.store.dart b/lib/store/auth.store.dart index ddda41b..6d0ac27 100644 --- a/lib/store/auth.store.dart +++ b/lib/store/auth.store.dart @@ -3,7 +3,7 @@ import 'dart:convert'; import 'package:dio/dio.dart' as dio_package; import 'package:flutter/widgets.dart'; import 'package:get/get.dart'; -import 'package:sk_base_mobile/apis/index.dart'; +import 'package:sk_base_mobile/apis/api.dart'; import 'package:sk_base_mobile/store/dict.store.dart'; import 'package:sk_base_mobile/util/logger_util.dart'; import 'package:sk_base_mobile/widgets/tap_to_dismiss_keyboard.dart'; diff --git a/lib/store/dict.store.dart b/lib/store/dict.store.dart index 4b10a09..45dc653 100644 --- a/lib/store/dict.store.dart +++ b/lib/store/dict.store.dart @@ -2,7 +2,7 @@ import 'package:get/get.dart'; import 'package:sk_base_mobile/constants/dict_enum.dart'; import 'package:sk_base_mobile/models/dict_item.model.dart'; import 'package:sk_base_mobile/models/dict_type.model.dart'; -import 'package:sk_base_mobile/apis/index.dart'; +import 'package:sk_base_mobile/apis/api.dart'; import 'package:sk_base_mobile/store/auth.store.dart'; import 'package:sk_base_mobile/util/logger_util.dart'; diff --git a/lib/util/common.util.dart b/lib/util/common.util.dart index 6c6bc1d..7ecdb39 100644 --- a/lib/util/common.util.dart +++ b/lib/util/common.util.dart @@ -26,4 +26,18 @@ class CommonUtil { static String firstUppercase(String text) { return '${text[0].toUpperCase()}${text.substring(1)}'; } + + static List flattenTree(List children) { + List result = children; + for (T child in children) { + if (child.children != null) { + result.addAll(flattenTree(child.children as List)); + } + } + return result; + } +} + +class TreeNode { + List? children; } diff --git a/lib/util/media_util.dart b/lib/util/media_util.dart index 5128e2e..dfb6b11 100644 --- a/lib/util/media_util.dart +++ b/lib/util/media_util.dart @@ -3,7 +3,7 @@ import 'dart:typed_data'; import 'package:dio/dio.dart' as dio_package; import 'package:dio/dio.dart'; import 'package:image_gallery_saver/image_gallery_saver.dart'; -import 'package:sk_base_mobile/apis/index.dart'; +import 'package:sk_base_mobile/apis/api.dart'; import 'package:image_picker/image_picker.dart'; import 'package:sk_base_mobile/config.dart'; import 'package:sk_base_mobile/models/upload_result.model.dart'; diff --git a/lib/widgets/core/sk_cascade_picker.dart b/lib/widgets/core/sk_cascade_picker.dart index d7f30a7..70d1880 100644 --- a/lib/widgets/core/sk_cascade_picker.dart +++ b/lib/widgets/core/sk_cascade_picker.dart @@ -1,59 +1,21 @@ +import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:sk_base_mobile/app_theme.dart'; -import 'package:sk_base_mobile/screens/hr_manage/components/dept_picker.dart'; +import 'package:sk_base_mobile/util/screen_adaper_util.dart'; +import 'package:sk_base_mobile/widgets/gradient_button.dart'; +import 'package:sk_base_mobile/widgets/loading_indicator.dart'; -/// 级联选择器 -/// 使用示例: -/// ```dart -/// CascadePicker的page是ListView,没有约束的情况下它的高度是无限的, -/// 因此需要约束高度。 -/// -/// final _cascadeController = CascadeController(); -/// -/// initialPageData: 第一页的数据 -/// nextPageData: 下一页的数据,点击当前页的选择项后调用该方法加载下一页 -/// - pageCallback: 用于传递下一页的数据给CascadePicker -/// - currentPage: 当前是第几页 -/// - selectIndex: 当前选中第几项 -/// controller: 控制器,用于获取已选择的数据 -/// maxPageNum: 最大页数 -/// selectedIcon: 已选中选项前面的图标,flutter package不能放本地资源文件,因此需要从外部传入,图标在images文件夹下面 -/// -/// Expand( -/// child: CascadePicker( -/// initialPageData: ['a', 'b', 'c', 'd'], -/// nextPageData: (pageCallback, currentPage, selectIndex) async { -/// pageCallback(['one', 'two', 'three']) -/// }, -/// controller: _cascadeController, -/// maxPageNum: 4, -/// selectedIcon: Image.asset("images/ic_select_mark.png", width: 10, height: 10, color: Colors.redAccent,), -/// ) -/// -/// InkBox( -/// child: Container(...) -/// onTap: () { -/// /// 判断是否完成选择 -/// if (_cascadeController.isCompleted()) { -/// List selectedTitles = _cascadeController.selectedTitles; -/// List selectedIndexes = _cascadeController.selectedIndexes; -/// } -/// } -/// ) -/// ``` +class CascadeItem { + late String label; + T? value; + List? children; + CascadeItem({required this.label, this.value, this.children}); +} -/// pageData: 下一页的数据 -/// currentPage: 当前是第几页, -/// selectIndex: 当前页选中第几项 -typedef void NextPageCallback( - Function(List) pageData, int currentPage, int selectIndex); - -class CascadePicker extends StatefulWidget { - final List initialPageData; - final NextPageCallback nextPageData; +class SkCascadePicker extends StatelessWidget { + final SkCascadePickerController cascadeController; final int maxPageNum; - final CascadeController controller; final Color tabColor; final double tabHeight; final TextStyle tabTitleStyle; @@ -62,12 +24,15 @@ class CascadePicker extends StatefulWidget { final Color itemColor; final Color activeColor; final Widget? selectedIcon; + final Function(List>)? onConfirm; - CascadePicker( - {required this.initialPageData, - required this.nextPageData, + SkCascadePicker( + {required Future>> Function() getData, + required List> Function(SkCascadePickerController) + initialPageData, + required NextPageCallback nextPageData, + this.onConfirm, this.maxPageNum = 3, - required this.controller, this.tabHeight = 40, this.activeColor = AppTheme.primaryColor, this.tabColor = Colors.white, @@ -75,394 +40,435 @@ class CascadePicker extends StatefulWidget { this.itemHeight = 40, this.itemColor = Colors.white, this.itemTitleStyle = const TextStyle(color: Colors.black, fontSize: 14), - this.selectedIcon}); + this.selectedIcon}) + : cascadeController = Get.put(SkCascadePickerController( + initialPageData: initialPageData, + nextPageData: nextPageData, + getData: getData)); @override - _CascadePickerState createState() => _CascadePickerState(this.controller); -} + Widget build(BuildContext context) { + return Container( + decoration: const BoxDecoration(color: AppTheme.nearlyWhite), + height: ScreenAdaper.height(400), + width: Get.width, + child: Obx(() => cascadeController.loading.value + ? const LoadingIndicator( + common: true, + ) + : Column(children: [ + GradientButton( + buttonText: '确定', + onPressed: () { + Get.back(); + if (onConfirm != null) { + onConfirm!(cascadeController.selectedTabs + .where((e) => + e.label != SkCascadePickerController.newTabName) + .toList() as List>); + } -class _CascadePickerState extends State - with SingleTickerProviderStateMixin { - static String _newTabName = "请选择"; - - final CascadeController _cascadeController; - - _CascadePickerState(this._cascadeController) { - _cascadeController._setState(this); - } - - late final AnimationController _controller; - late final CurvedAnimation _curvedAnimation; - Animation? _sliderAnimation; - final _sliderFixMargin = ValueNotifier(0.0); - double _sliderWidth = 20; - - PageController _pageController = PageController(initialPage: 0); - - GlobalKey _sliderKey = GlobalKey(); - List _tabKeys = []; - - /// 选择器数据集合 - List> _pagesData = []; - - /// 已选择的title集合 - List _selectedTabs = [_newTabName]; - - /// 已选择的item index集合 - List _selectedIndexes = [-1]; - - /// "请选择"tab宽度,添加新的tab时用到 - double _animTabWidth = 0; - - /// tab添加事件记录,用于隐藏"请选择"tab初始化状态 - bool _isAddTabEvent = false; - - /// tab移动未开始,渲染'请选择'tab时隐藏文本,这时的tab在终点位置 - bool _isAnimateTextHide = false; - - /// 防止_moveSlider重复调用 - bool _isClickAndMoveTab = false; - - /// 当前选择的页面,移动滑块前赋值 - int _currentSelectPage = 0; - - _addTab(int page, int atIndex, String currentPageItem) { - _loadNextPageData(page, atIndex, currentPageItem); - } - - _loadNextPageData(int page, int atIndex, String currentPageItem, - {bool isUpdatePage = false}) { - widget.nextPageData((data) { - final nextPageDataIsEmpty = data.isEmpty; - if (!nextPageDataIsEmpty) { - /// 下一页有数据,更新本页数据或添加新的页面 - setState(() { - if (isUpdatePage) { - /// 更新下一页 - _pagesData[page] = data; - _selectedTabs[page] = _newTabName; - _selectedIndexes[page] = -1; - - /// 清空下下页以后的所有页面和tab数据 - _pagesData.removeRange(page + 1, _pagesData.length); - _selectedIndexes.removeRange(page + 1, _selectedIndexes.length); - _selectedTabs.removeRange(page + 1, _selectedTabs.length); - } else { - /// 添加新的页面 - _isAnimateTextHide = true; - _isAddTabEvent = true; - _pagesData.add(data); - _selectedTabs.add(_newTabName); - _selectedIndexes.add(-1); - } - WidgetsBinding.instance.addPostFrameCallback((timeStamp) { - _moveSlider(page, isAdd: true); - }); - }); - } else { - /// 如果下一页数据为空,那么更新本页数据 - final currentPage = page - 1; - setState(() { - _selectedTabs[currentPage] = currentPageItem; - _selectedIndexes[currentPage] = atIndex; - - /// 下一页数据为空,清空下一页以后的所有页面和tab数据 - _pagesData.removeRange(page, _pagesData.length); - _selectedIndexes.removeRange(page, _selectedIndexes.length); - _selectedTabs.removeRange(page, _selectedTabs.length); - WidgetsBinding.instance.addPostFrameCallback((timeStamp) { - // 调整滑块位置 - _moveSlider(currentPage); - }); - }); - } - }, page, atIndex); - } - - _moveSlider(int page, {bool movePage = true, bool isAdd = false}) { - if (movePage && _currentSelectPage != page) { - /// 上一次选择的页面和本次选择的页面不同时,移动tab标签, - /// 移动时先把_isClickAndMoveTab设为true,防止滑动PageView - /// 时_moveSlider重复调用。 - _isClickAndMoveTab = true; - } - _isAddTabEvent = isAdd; - _currentSelectPage = page; - - if (_controller.isAnimating) { - _controller.stop(); - } - RenderBox slider = - _sliderKey.currentContext?.findRenderObject() as RenderBox; - Offset sliderPosition = slider.localToGlobal(Offset.zero); - RenderBox currentTabBox = - _tabKeys[page].currentContext?.findRenderObject() as RenderBox; - Offset currentTabPosition = currentTabBox.localToGlobal(Offset.zero); - - _animTabWidth = currentTabBox.size.width; - - final begin = sliderPosition.dx - _sliderFixMargin.value; - final end = currentTabPosition.dx + - (currentTabBox.size.width - _sliderWidth) / 2 - - _sliderFixMargin.value; - _sliderAnimation = - Tween(begin: begin, end: end).animate(_curvedAnimation); - _controller.value = 0; - _controller.forward(); - if (movePage) { - _pageController.animateToPage(page, - curve: Curves.linear, duration: Duration(milliseconds: 500)); - } + // 已选中的titles + // List> selectedTitles = + // cascadeController.selectedTabs; + // print("已选中的titles: $selectedTitles"); + // 已选中的序号 + // List selectedIndexes = + // cascadeController.selectedIndexes; + // print("已选中的序号:$selectedIndexes"); + }, + ), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + AnimatedBuilder( + animation: cascadeController.sliderAnimation!, + builder: (context, child) => Stack( + clipBehavior: Clip.hardEdge, + alignment: Alignment.bottomLeft, + children: [ + Container( + padding: EdgeInsets.symmetric( + horizontal: ScreenAdaper.width(20)), + width: MediaQuery.of(context).size.width, + child: Obx( + () => SingleChildScrollView( + controller: cascadeController.scrollController, + scrollDirection: Axis.horizontal, + child: Row(children: _tabWidgets()), + ), + ), + ), + const Divider( + height: 1, + ), + ValueListenableBuilder( + valueListenable: cascadeController.sliderFixMargin, + builder: (_, margin, __) => Positioned( + left: margin + + cascadeController.sliderAnimation!.value, + child: Container( + key: cascadeController.sliderKey, + width: cascadeController.sliderWidth, + height: 2, + decoration: BoxDecoration( + color: activeColor, + borderRadius: BorderRadius.circular(2)), + ), + ), + ) + ], + ), + ), + Expanded( + child: PageView.builder( + itemCount: cascadeController.pagesData.length, + controller: cascadeController.pageController, + itemBuilder: (context, index) => _pageWidget(index), + onPageChanged: (position) { + if (!cascadeController.isClickAndMoveTab) { + cascadeController.moveSlider(position, + movePage: false); + } + if (cascadeController.urrentSelectPage == position) { + cascadeController.isClickAndMoveTab = false; + } + }, + ), + ) + ], + )) + ]))); } /// 注意:tab渲染完成才开始动画,即调用moveSlider,这个方法会在动画执行期间多次调用 Widget _animateTab({required Widget tab}) { return Transform.translate( offset: Offset( - Tween(begin: _isAddTabEvent ? -_animTabWidth : 0, end: 0) - .evaluate(_curvedAnimation), + Tween( + begin: cascadeController.isAddTabEvent + ? -cascadeController.animTabWidth + : 0, + end: 0) + .evaluate(cascadeController.curvedAnimation), 0), child: Opacity( /// 动画未开始前隐藏文本 - opacity: _isAnimateTextHide ? 0 : 1, + opacity: cascadeController.isAnimateTextHide ? 0 : 1, child: tab), ); } List _tabWidgets() { List widgets = []; - _tabKeys.clear(); - for (int i = 0; i < _pagesData.length; i++) { + cascadeController.tabKeys.clear(); + for (int i = 0; i < cascadeController.pagesData.length; i++) { GlobalKey key = GlobalKey(); - _tabKeys.add(key); + cascadeController.tabKeys.add(key); final tab = GestureDetector( child: Container( key: key, - height: widget.tabHeight, - color: widget.tabColor, + height: tabHeight, + color: tabColor, alignment: Alignment.center, - padding: EdgeInsets.symmetric(horizontal: 15), - child: ConstrainedBox( - constraints: BoxConstraints( - maxWidth: - MediaQuery.of(context).size.width / _pagesData.length - 10), + child: Container( + // constraints: BoxConstraints( + // maxWidth: MediaQuery.of(Get.context!).size.width / + // cascadeController.pagesData.length - + // 10), child: Text( - _selectedTabs[i], - style: _currentSelectPage == i - ? widget.tabTitleStyle.copyWith(color: widget.activeColor) - : widget.tabTitleStyle, + cascadeController.selectedTabs[i].label, + style: cascadeController.urrentSelectPage == i + ? tabTitleStyle.copyWith(color: activeColor) + : tabTitleStyle, maxLines: 1, overflow: TextOverflow.ellipsis, ), ), ), onTap: () { - _moveSlider(i); + cascadeController.moveSlider(i); }, ); - if (i == _pagesData.length - 1 && _selectedTabs[i] == _newTabName) { + if (i == cascadeController.pagesData.length - 1 && + cascadeController.selectedTabs[i].label == + SkCascadePickerController.newTabName) { widgets.add(_animateTab(tab: tab)); - _isAnimateTextHide = false; } else { widgets.add(tab); } + cascadeController.isAnimateTextHide = false; + if (i < cascadeController.pagesData.length - 1) { + widgets.add(Container( + // color: Colors.red, + child: Icon( + Icons.arrow_right, + size: ScreenAdaper.height(50), + ), + )); + } } return widgets; } /// 选择项 - Widget _pageItemWidget(int index, int page, String item) { + Widget _pageItemWidget(int index, int page, CascadeItem item) { return GestureDetector( child: Container( alignment: Alignment.centerLeft, - padding: EdgeInsets.symmetric(horizontal: 15), - height: widget.itemHeight, - color: widget.itemColor, - child: Row( - children: [ - item == _selectedTabs[page] - ? Padding( - padding: const EdgeInsets.all(5.0), - child: widget.selectedIcon == null - ? Icon(Icons.chevron_right, - size: 15, color: widget.activeColor) - : widget.selectedIcon, - ) - : SizedBox(), - Text("$item", - style: item == _selectedTabs[page] - ? widget.itemTitleStyle.copyWith(color: widget.activeColor) - : widget.itemTitleStyle), - ], - ), + padding: EdgeInsets.symmetric(horizontal: ScreenAdaper.width(20)), + height: itemHeight, + color: itemColor, + child: Obx(() => Row( + children: [ + item == cascadeController.selectedTabs[page] + ? Padding( + padding: const EdgeInsets.all(5.0), + child: selectedIcon == null + ? Icon(Icons.chevron_right, + size: ScreenAdaper.height(40), + color: activeColor) + : selectedIcon, + ) + : SizedBox(), + Text(item.label, + style: item == cascadeController.selectedTabs[page] + ? itemTitleStyle.copyWith(color: activeColor) + : itemTitleStyle), + ], + )), ), onTap: () { - if (page == widget.maxPageNum - 1) { + if (page == maxPageNum - 1) { /// 当前页是最后一页 - setState(() { - _selectedTabs[page] = item; - _selectedIndexes[page] = index; + cascadeController.selectedTabs[page] = item; + cascadeController.selectedIndexes[page] = index; - /// 调整滑块位置 - WidgetsBinding.instance.addPostFrameCallback((timeStamp) { - _moveSlider(page); - }); + /// 调整滑块位置 + WidgetsBinding.instance.addPostFrameCallback((timeStamp) { + cascadeController.moveSlider(page); }); - } else if (_tabKeys.length >= widget.maxPageNum || - page < _tabKeys.length - 1) { - if (index == _selectedIndexes[page]) { + } else if (cascadeController.tabKeys.length >= maxPageNum || + page < cascadeController.tabKeys.length - 1) { + if (index == cascadeController.selectedIndexes[page]) { /// 选择相同的item - _moveSlider(page + 1); + cascadeController.moveSlider(page + 1); } else { /// 选择不同的item,更新tab renderBox - setState(() { - _selectedTabs[page] = item; - _selectedIndexes[page] = index; -// _selectedIndexes.removeRange(page + 1, _selectedIndexes.length); - }); - _loadNextPageData(page + 1, index, item, isUpdatePage: true); + cascadeController.selectedTabs[page] = item; + cascadeController.selectedIndexes[page] = index; +// selectedIndexes.removeRange(page + 1, selectedIndexes.length); + cascadeController.loadNextPageData(page + 1, index, item, + isUpdatePage: true); } } else { /// 添加新tab页面 - /// page == _tabKeys.length - 1 && _tabKeys.length == widget.maxPageNum - _selectedTabs[page] = item; - _selectedIndexes[page] = index; - _addTab(page + 1, index, item); + /// page == tabKeys.length - 1 && tabKeys.length == widget.maxPageNum + cascadeController.selectedTabs[page] = item; + cascadeController.selectedIndexes[page] = index; + cascadeController.addTab(page + 1, index, item); } }, ); } Widget _pageWidget(int page) { - return ListView.builder( - padding: EdgeInsets.zero, - itemCount: _pagesData[page].length, - itemBuilder: (context, index) => - _pageItemWidget(index, page, _pagesData[page][index]), + return Obx(() => ListView.builder( + padding: EdgeInsets.zero, + itemCount: cascadeController.pagesData[page].length, + itemBuilder: (context, index) => _pageItemWidget( + index, page, cascadeController.pagesData[page][index]), // separatorBuilder: (context, index) => Divider(height: 0.3, thickness: 0.3, color: Color(0xffdddddd), indent: 15, endIndent: 15,), - ); - } - - @override - void initState() { - super.initState(); - _pagesData.add(widget.initialPageData); - - _controller = AnimationController( - duration: const Duration(milliseconds: 500), vsync: this); - - _curvedAnimation = CurvedAnimation(parent: _controller, curve: Curves.ease) - ..addStatusListener((state) {}); - - _sliderAnimation = - Tween(begin: 0, end: 10).animate(_curvedAnimation); - - WidgetsBinding.instance.addPostFrameCallback((timeStamp) { - RenderBox tabBox = - _tabKeys.first.currentContext?.findRenderObject() as RenderBox; - _sliderFixMargin.value = (tabBox.size.width - _sliderWidth) / 2; - }); - } - - @override - Widget build(BuildContext context) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - AnimatedBuilder( - animation: _sliderAnimation!, - builder: (context, child) => Stack( - clipBehavior: Clip.hardEdge, - alignment: Alignment.bottomLeft, - children: [ - Container( - width: MediaQuery.of(context).size.width, - child: Row( - children: _tabWidgets(), - ), - ), - ValueListenableBuilder( - valueListenable: _sliderFixMargin, - builder: (_, margin, __) => Positioned( - left: margin + _sliderAnimation!.value, - child: Container( - key: _sliderKey, - width: _sliderWidth, - height: 2, - decoration: BoxDecoration( - color: widget.activeColor, - borderRadius: BorderRadius.circular(2)), - ), - ), - ) - ], - ), - ), - Expanded( - child: PageView.builder( - itemCount: _pagesData.length, - controller: _pageController, - itemBuilder: (context, index) => _pageWidget(index), - onPageChanged: (position) { - if (!_isClickAndMoveTab) { - _moveSlider(position, movePage: false); - } - if (_currentSelectPage == position) { - _isClickAndMoveTab = false; - } - }, - ), - ) - ], - ); + )); } } -class CascadeController extends GetxController { - late List items = []; +class SkCascadePickerController extends GetxController + with GetSingleTickerProviderStateMixin { + RxList> treeData = RxList([]); + final Future>> Function() getData; + final List> Function(SkCascadePickerController) + initialPageData; + final NextPageCallback nextPageData; + final RxBool loading = false.obs; + final scrollController = ScrollController(); + SkCascadePickerController( + {required this.initialPageData, + required this.nextPageData, + required this.getData}); @override void onInit() { - items = []; - for (int i = 0; i < 5; i++) { - Item item0 = Item(); - item0.name = "name_$i"; - item0.code = "code_$i"; - List children1 = []; - for (int j = 0; j < 3; j++) { - Item item1 = Item(); - item1.name = "name_${i}_$j"; - item1.code = "code_${i}_$j"; - List children2 = []; - for (int k = 0; k < 7; k++) { - Item item2 = Item(); - item2.name = "name_${i}_${j}_$k"; - item2.code = "code_${i}_${j}_$k"; - // 第3页没有子数据列表 - item2.children = []; - children2.add(item2); + init(); + animateController = AnimationController( + duration: const Duration(milliseconds: 500), vsync: this); + curvedAnimation = + CurvedAnimation(parent: animateController, curve: Curves.ease) + ..addStatusListener((state) {}); + sliderAnimation = Tween(begin: 0, end: 10).animate(curvedAnimation); + + super.onInit(); + } + + Future init() async { + loading.value = true; + treeData.assignAll(await getData()); + // 树形结构展评 + selectedTabs.add(CascadeItem(label: newTabName)); + pagesData.add(initialPageData(this)); + WidgetsBinding.instance.addPostFrameCallback((timeStamp) { + RenderBox tabBox = + tabKeys.first.currentContext?.findRenderObject() as RenderBox; + sliderFixMargin.value = (tabBox.size.width - sliderWidth) / 2; + }); + loading.value = false; + } + + bool isCompleted() => + !selectedTabs.map((item) => item.label).contains(newTabName); + + static String newTabName = "请选择"; + + late final AnimationController animateController; + late final CurvedAnimation curvedAnimation; + Animation? sliderAnimation; + final sliderFixMargin = ValueNotifier(0.0); + double sliderWidth = 20; + + PageController pageController = PageController(initialPage: 0); + + GlobalKey sliderKey = GlobalKey(); + List tabKeys = []; + + /// 选择器数据集合 + RxList>> pagesData = RxList([]); + + /// 已选择的title集合 + RxList> selectedTabs = RxList([]); + + /// 已选择的item index集合 + RxList selectedIndexes = RxList([-1]); + + /// "请选择"tab宽度,添加新的tab时用到 + double animTabWidth = 0; + + /// tab添加事件记录,用于隐藏"请选择"tab初始化状态 + bool isAddTabEvent = false; + + /// tab移动未开始,渲染'请选择'tab时隐藏文本,这时的tab在终点位置 + bool isAnimateTextHide = false; + + /// 防止moveSlider重复调用 + bool isClickAndMoveTab = false; + + /// 当前选择的页面,移动滑块前赋值 + int urrentSelectPage = 0; + + addTab(int page, int atIndex, CascadeItem currentPageItem) { + loadNextPageData(page, atIndex, currentPageItem); + } + + loadNextPageData(int page, int atIndex, CascadeItem currentPageItem, + {bool isUpdatePage = false}) { + nextPageData((data) { + final nextPageDataIsEmpty = data.isEmpty; + if (!nextPageDataIsEmpty) { + /// 下一页有数据,更新本页数据或添加新的页面 + if (isUpdatePage) { + /// 更新下一页 + pagesData[page] = data; + selectedTabs[page] = CascadeItem(label: newTabName); + selectedIndexes[page] = -1; + + /// 清空下下页以后的所有页面和tab数据 + pagesData.removeRange(page + 1, pagesData.length); + selectedIndexes.removeRange(page + 1, selectedIndexes.length); + selectedTabs.removeRange(page + 1, selectedTabs.length); + } else { + /// 添加新的页面 + isAnimateTextHide = true; + isAddTabEvent = true; + pagesData.add(data); + selectedTabs.add(CascadeItem(label: newTabName)); + selectedIndexes.add(-1); } - // 第2页的子数据列表 - item1.children = children2; - children1.add(item1); + WidgetsBinding.instance.addPostFrameCallback((timeStamp) { + moveSlider(page, isAdd: true); + }); + } else { + /// 如果下一页数据为空,那么更新本页数据 + final currentPage = page - 1; + selectedTabs[currentPage] = currentPageItem; + selectedIndexes[currentPage] = atIndex; + + /// 下一页数据为空,清空下一页以后的所有页面和tab数据 + pagesData.removeRange(page, pagesData.length); + selectedIndexes.removeRange(page, selectedIndexes.length); + selectedTabs.removeRange(page, selectedTabs.length); + WidgetsBinding.instance.addPostFrameCallback((timeStamp) { + // 调整滑块位置 + moveSlider(currentPage); + }); } - // 第1页的子数据列表 - item0.children = children1; - items.add(item0); - super.onInit(); + // 滚动到最底部 + Future.delayed(const Duration(milliseconds: 500), () { + scrollController.jumpTo(scrollController.position.maxScrollExtent); + }); + }, page, atIndex, currentPageItem, this); + } + + moveSlider(int page, {bool movePage = true, bool isAdd = false}) { + if (movePage && urrentSelectPage != page) { + /// 上一次选择的页面和本次选择的页面不同时,移动tab标签, + /// 移动时先把isClickAndMoveTab设为true,防止滑动PageView + /// 时moveSlider重复调用。 + isClickAndMoveTab = true; + } + isAddTabEvent = isAdd; + urrentSelectPage = page; + + if (animateController.isAnimating) { + animateController.stop(); + } + RenderBox slider = + sliderKey.currentContext?.findRenderObject() as RenderBox; + Offset sliderPosition = slider.localToGlobal(Offset.zero); + RenderBox currentTabBox = + tabKeys[page].currentContext?.findRenderObject() as RenderBox; + Offset currentTabPosition = currentTabBox.localToGlobal(Offset.zero); + + animTabWidth = currentTabBox.size.width; + + final begin = sliderPosition.dx - sliderFixMargin.value; + final end = currentTabPosition.dx + + (currentTabBox.size.width - sliderWidth) / 2 - + sliderFixMargin.value; + sliderAnimation = + Tween(begin: begin, end: end).animate(curvedAnimation); + animateController.value = 0; + animateController.forward(); + if (movePage) { + pageController.animateToPage(page, + curve: Curves.linear, duration: Duration(milliseconds: 500)); } } - late final _CascadePickerState _state; - - _setState(_CascadePickerState state) { - _state = state; + @override + void onClose() { + animateController.dispose(); + pageController.dispose(); + scrollController.dispose(); + super.onClose(); } - - List get selectedTitles => _state._selectedTabs; - - List get selectedIndexes => _state._selectedIndexes; - - bool isCompleted() => - !_state._selectedTabs.contains(_CascadePickerState._newTabName); } + +/// pageData: 下一页的数据 +/// currentPage: 当前是第几页, +/// selectIndex: 当前页选中第几项 +typedef void NextPageCallback( + Function(List>) pageData, + int currentPage, + int selectIndex, + CascadeItem currentPageItem, + SkCascadePickerController cascadeController);