From 9e10952bef21189a554a8fac78b2e863e68420d2 Mon Sep 17 00:00:00 2001 From: liyi Date: Thu, 9 Oct 2025 18:19:14 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=EF=BC=88=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E4=BA=BA=E5=91=98=E5=8A=9F=E8=83=BD=E9=A1=B5=E9=9D=A2?= =?UTF-8?q?=E5=AE=8C=E5=96=84=E4=B8=8E=E6=8E=A5=E5=8F=A3=E5=AF=B9=E6=8E=A5?= =?UTF-8?q?=E3=80=81=E6=B7=BB=E5=8A=A0=E5=AD=90=E7=BB=84=E7=BB=87=E9=A1=B5?= =?UTF-8?q?=E9=9D=A2=E5=AE=8C=E5=96=84=E4=B8=8E=E6=8E=A5=E5=8F=A3=E5=AF=B9?= =?UTF-8?q?=E6=8E=A5=E3=80=81=E9=80=89=E6=8B=A9=E7=94=A8=E6=88=B7=E9=A1=B5?= =?UTF-8?q?=E9=9D=A2=E5=BC=80=E5=8F=91=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/api/api_path.dart | 1 + lib/api/base_api_service.dart | 16 +- .../model/team/request/create_new_person.dart | 81 ++++ lib/api/service/team_api_service.dart | 15 +- lib/base/base_controller.dart | 1 + .../constant/app_view_parameter_keys.dart | 3 + lib/flavors.dart | 6 +- lib/routes/app_pages.dart | 21 + lib/routes/app_routes.dart | 3 + .../add_organization_binding.dart | 10 + .../add_organization_controller.dart | 53 +++ .../add_organization_view.dart | 200 +++++++++ .../team/addPerson/add_person_controller.dart | 78 +++- lib/views/team/addPerson/add_person_view.dart | 377 +++++++++++------ .../editValidity/edit_validity_binding.dart | 10 + .../edit_validity_controller.dart | 26 ++ .../editValidity/edit_validity_view.dart | 289 +++++++++++++ .../personnel_manage_controller.dart | 9 +- .../personnel_manage_view.dart | 393 +++++++++--------- .../select_organization_view.dart | 1 - .../selectPerson/select_person_binding.dart | 10 + .../select_person_controller.dart | 149 +++++++ .../team/selectPerson/select_person_view.dart | 332 +++++++++++++++ pubspec.lock | 8 + pubspec.yaml | 1 + 25 files changed, 1749 insertions(+), 344 deletions(-) create mode 100644 lib/api/model/team/request/create_new_person.dart create mode 100644 lib/views/team/addOrganization/add_organization_binding.dart create mode 100644 lib/views/team/addOrganization/add_organization_controller.dart create mode 100644 lib/views/team/addOrganization/add_organization_view.dart create mode 100644 lib/views/team/addPerson/editValidity/edit_validity_binding.dart create mode 100644 lib/views/team/addPerson/editValidity/edit_validity_controller.dart create mode 100644 lib/views/team/addPerson/editValidity/edit_validity_view.dart create mode 100644 lib/views/team/selectPerson/select_person_binding.dart create mode 100644 lib/views/team/selectPerson/select_person_controller.dart create mode 100644 lib/views/team/selectPerson/select_person_view.dart diff --git a/lib/api/api_path.dart b/lib/api/api_path.dart index 214cfc7..d05c4ef 100644 --- a/lib/api/api_path.dart +++ b/lib/api/api_path.dart @@ -16,4 +16,5 @@ class ApiPath { static const String departCreate = "/v1/team/departCreate"; static const String roleList = "/v1/team/roleList"; static const String roleCreate = "/v1/team/roleCreate"; + static const String createPerson = "/v1/team/personCreate"; } diff --git a/lib/api/base_api_service.dart b/lib/api/base_api_service.dart index df62f8d..7d56d96 100644 --- a/lib/api/base_api_service.dart +++ b/lib/api/base_api_service.dart @@ -56,9 +56,9 @@ class BaseApiService { if (kDebugMode) { final uri = Uri.parse('${dio.options.baseUrl}$path'); final queryString = queryParameters != null ? '?${uri.queryParameters.toString()}' : ''; - debugPrint('🟦 API Request: $method ${uri.toString()}$queryString'); + AppLogger.debug('🟦 API Request: $method ${uri.toString()}$queryString'); if (data != null) { - debugPrint('🟦 Request Body: $data'); + AppLogger.debug('🟦 Request Body: $data'); } } @@ -86,8 +86,8 @@ class BaseApiService { // ✅ 打印响应 if (kDebugMode) { - debugPrint('🟩 API Response [${response.statusCode}] ${response.requestOptions.path}'); - debugPrint('🟩 Response Data: ${response.data}'); + AppLogger.debug('🟩 API Response [${response.statusCode}] ${response.requestOptions.path}'); + AppLogger.debug('🟩 Response Data: ${response.data}'); } if (response.statusCode! >= 200 && response.statusCode! < 300) { @@ -116,10 +116,10 @@ class BaseApiService { } on dioAlias.DioException catch (e) { // ❌ 打印错误 if (kDebugMode) { - debugPrint('🟥 API Error: ${e.type} - ${e.message}'); + AppLogger.error('🟥 API Error: ${e.type} - ${e.message}', error: e); if (e.response != null) { - debugPrint('🟥 Error Response: ${e.response?.data}'); - debugPrint('🟥 Status Code: ${e.response?.statusCode}'); + AppLogger.error('🟥 Error Response: ${e.response?.data}'); + AppLogger.error('🟥 Status Code: ${e.response?.statusCode}'); } } @@ -141,7 +141,7 @@ class BaseApiService { return ApiResponse.error(message, errorCode: statusCode); } catch (e) { if (kDebugMode) { - debugPrint('🟥 Unexpected Error: $e'); + AppLogger.error('🟥 Unexpected Error: $e', error: e); } return ApiResponse.error('Unexpected error: $e'); } finally { diff --git a/lib/api/model/team/request/create_new_person.dart b/lib/api/model/team/request/create_new_person.dart new file mode 100644 index 0000000..7bf5230 --- /dev/null +++ b/lib/api/model/team/request/create_new_person.dart @@ -0,0 +1,81 @@ +class CreateNewPerson { + String? departNo; + String? personName; + bool? createUser; + String? phone; + int? sex; + String? position; + String? remark; + List? associateUsers; + int? limitType; + String? limitStartTime; + String? limitEndTime; + String? idCard; + bool? isConfirm; + List? roleIds; + String? jobNumber; + + CreateNewPerson({ + this.departNo, + this.personName, + this.createUser, + this.phone, + this.sex, + this.position, + this.remark, + this.associateUsers, + this.limitType, + this.limitStartTime, + this.limitEndTime, + this.idCard, + this.isConfirm, + this.roleIds, + this.jobNumber, + }); + + factory CreateNewPerson.fromJson(Map json) { + return CreateNewPerson( + departNo: json['departNo'] as String?, + personName: json['personName'] as String?, + createUser: json['createUser'] as bool?, + phone: json['phone'] as String?, + sex: json['sex'] as int?, + position: json['position'] as String?, + remark: json['remark'] as String?, + associateUsers: json['associateUsers'] != null + ? List.from(json['associateUsers']) + : null, + limitType: json['limitType'] as int?, + limitStartTime: json['limitStartTime'] as String?, + limitEndTime: json['limitEndTime'] as String?, + idCard: json['idCard'] as String?, + isConfirm: json['isConfirm'] as bool?, + roleIds: json['roleIds'] != null + ? List.from(json['roleIds']) + : null, + jobNumber: json['jobNumber'] as String?, + ); + } + + Map toJson() { + final Map data = {}; + + if (departNo != null) data['departNo'] = departNo; + if (personName != null) data['personName'] = personName; + if (createUser != null) data['createUser'] = createUser; + if (phone != null) data['phone'] = phone; + if (sex != null) data['sex'] = sex; + if (position != null) data['position'] = position; + if (remark != null) data['remark'] = remark; + if (associateUsers != null) data['associateUsers'] = associateUsers; + if (limitType != null) data['limitType'] = limitType; + if (limitStartTime != null) data['limitStartTime'] = limitStartTime; + if (limitEndTime != null) data['limitEndTime'] = limitEndTime; + if (idCard != null) data['idCard'] = idCard; + if (isConfirm != null) data['isConfirm'] = isConfirm; + if (roleIds != null) data['roleIds'] = roleIds; + if (jobNumber != null) data['jobNumber'] = jobNumber; + + return data; + } +} diff --git a/lib/api/service/team_api_service.dart b/lib/api/service/team_api_service.dart index 6bc713d..a3aa50b 100644 --- a/lib/api/service/team_api_service.dart +++ b/lib/api/service/team_api_service.dart @@ -5,6 +5,7 @@ import 'package:starwork_flutter/api/base_api_service.dart'; import 'package:starwork_flutter/api/model/team/request/bind_team_star_cloud_account_request.dart'; import 'package:starwork_flutter/api/model/team/request/change_current_team_request.dart'; import 'package:starwork_flutter/api/model/team/request/create_new_depart_request.dart'; +import 'package:starwork_flutter/api/model/team/request/create_new_person.dart'; import 'package:starwork_flutter/api/model/team/request/create_new_role_request.dart'; import 'package:starwork_flutter/api/model/team/request/create_team_request.dart'; import 'package:starwork_flutter/api/model/team/request/get_depart_list_request.dart'; @@ -139,7 +140,7 @@ class TeamApiService { ); } - // 获取组织列表 + // 创建新的组织 Future> requestCreateDepart({ required CreateNewDepartRequest request, }) { @@ -171,4 +172,16 @@ class TeamApiService { fromJson: (data) {}, ); } + + // 添加新的人员 + Future> requestAddPerson({ + required CreateNewPerson request, + }) { + return _api.makeRequest( + path: ApiPath.createPerson, + method: HttpConstant.post, + data: request.toJson(), + fromJson: (data) {}, + ); + } } diff --git a/lib/base/base_controller.dart b/lib/base/base_controller.dart index a8df501..8e3c94d 100644 --- a/lib/base/base_controller.dart +++ b/lib/base/base_controller.dart @@ -68,4 +68,5 @@ class BaseController extends GetxController { } super.onClose(); } + } diff --git a/lib/common/constant/app_view_parameter_keys.dart b/lib/common/constant/app_view_parameter_keys.dart index 0800969..8ada631 100644 --- a/lib/common/constant/app_view_parameter_keys.dart +++ b/lib/common/constant/app_view_parameter_keys.dart @@ -4,5 +4,8 @@ class AppViewParameterKeys { static const String networkInfo = "networkInfo"; static const String teamInfo = "teamInfo"; static const String departItem = "departItem"; + static const String isLongTerm = "isLongTerm"; + static const String startDate = "startDate"; + static const String endDate = "endDate"; } \ No newline at end of file diff --git a/lib/flavors.dart b/lib/flavors.dart index 7da7cca..6b6fb1f 100644 --- a/lib/flavors.dart +++ b/lib/flavors.dart @@ -21,7 +21,7 @@ class F { // Release环境的API地址 switch (appFlavor) { case Flavor.sky: - return 'https://192.168.1.137:8112/api'; // 生产环境API + return 'https://192.168.1.136:8112/api'; // 生产环境API case Flavor.xhj: return 'https://api.xhjcn.ltd/api'; // 生产环境API } @@ -29,7 +29,7 @@ class F { // Debug/Profile环境的API地址(开发环境) switch (appFlavor) { case Flavor.sky: - return 'http://192.168.1.137:8112/api'; + return 'http://192.168.1.136:8112/api'; case Flavor.xhj: return 'https://loacl.work.star-lock.cn/api'; } @@ -67,7 +67,7 @@ class F { // Debug/Profile环境的StarCloud地址(开发环境) switch (appFlavor) { case Flavor.sky: - return 'http://192.168.1.137:8111/sdk'; + return 'http://192.168.1.136:8111/sdk'; case Flavor.xhj: return 'http://local.cloud.star-lock.cn'; } diff --git a/lib/routes/app_pages.dart b/lib/routes/app_pages.dart index 06ad2c1..a43dd0f 100644 --- a/lib/routes/app_pages.dart +++ b/lib/routes/app_pages.dart @@ -34,8 +34,12 @@ import 'package:starwork_flutter/views/login/login_binding.dart'; import 'package:starwork_flutter/views/login/login_view.dart'; import 'package:starwork_flutter/views/main/main_binding.dart'; import 'package:starwork_flutter/views/main/main_view.dart'; +import 'package:starwork_flutter/views/team/addOrganization/add_organization_binding.dart'; +import 'package:starwork_flutter/views/team/addOrganization/add_organization_view.dart'; import 'package:starwork_flutter/views/team/addPerson/add_person_binding.dart'; import 'package:starwork_flutter/views/team/addPerson/add_person_view.dart'; +import 'package:starwork_flutter/views/team/addPerson/editValidity/edit_validity_binding.dart'; +import 'package:starwork_flutter/views/team/addPerson/editValidity/edit_validity_view.dart'; import 'package:starwork_flutter/views/team/addPerson/selectRole/select_role_binding.dart'; import 'package:starwork_flutter/views/team/addPerson/selectRole/select_role_view.dart'; import 'package:starwork_flutter/views/team/addRole/add_role_binding.dart'; @@ -52,6 +56,8 @@ import 'package:starwork_flutter/views/team/roleManage/role_manage_binding.dart' import 'package:starwork_flutter/views/team/roleManage/role_manage_view.dart'; import 'package:starwork_flutter/views/team/selectOrganization/select_organization_binding.dart'; import 'package:starwork_flutter/views/team/selectOrganization/select_organization_view.dart'; +import 'package:starwork_flutter/views/team/selectPerson/select_person_binding.dart'; +import 'package:starwork_flutter/views/team/selectPerson/select_person_view.dart'; import 'package:starwork_flutter/views/team/teamManage/teamInfo/team_info_binding.dart'; import 'package:starwork_flutter/views/team/teamManage/teamInfo/team_info_view.dart'; import 'package:starwork_flutter/views/team/teamManage/team_manage_binding.dart'; @@ -242,5 +248,20 @@ class AppPages { page: () => SelectRoleView(), binding: SelectRoleBinding(), ), + GetPage( + name: AppRoutes.teamAddPersonEditValidity, + page: () => EditValidityView(), + binding: EditValidityBinding(), + ), + GetPage( + name: AppRoutes.teamAddOrganization, + page: () => AddOrganizationView(), + binding: AddOrganizationBinding(), + ), + GetPage( + name: AppRoutes.teamSelectPerson, + page: () => SelectPersonView(), + binding: SelectPersonBinding(), + ), ]; } diff --git a/lib/routes/app_routes.dart b/lib/routes/app_routes.dart index f480351..4b19782 100644 --- a/lib/routes/app_routes.dart +++ b/lib/routes/app_routes.dart @@ -20,9 +20,12 @@ class AppRoutes{ static const String teamAddPerson = '/team/addPerson'; static const String teamRoleManage = '/team/roleManage'; static const String teamSelectOrganization = '/team/selectOrganization'; + static const String teamSelectPerson = '/team/selectPerson'; static const String teamPersonnelManage = '/team/personnelManage'; static const String teamAddRole = '/team/addRole'; static const String teamSelectRole = '/team/selectRole'; + static const String teamAddPersonEditValidity = '/team/addPerson/editValidity'; + static const String teamAddOrganization = '/team/addOrganization'; static const String deviceManage = '/device/deviceManage'; static const String searchDevice = '/device/searchDevice'; static const String confirmPairDevice = '/device/confirmPairDevice'; diff --git a/lib/views/team/addOrganization/add_organization_binding.dart b/lib/views/team/addOrganization/add_organization_binding.dart new file mode 100644 index 0000000..d1d81ee --- /dev/null +++ b/lib/views/team/addOrganization/add_organization_binding.dart @@ -0,0 +1,10 @@ +import 'package:get/get.dart'; + +import 'add_organization_controller.dart'; + +class AddOrganizationBinding extends Bindings { + @override + void dependencies() { + Get.lazyPut(() => AddOrganizationController()); + } +} \ No newline at end of file diff --git a/lib/views/team/addOrganization/add_organization_controller.dart b/lib/views/team/addOrganization/add_organization_controller.dart new file mode 100644 index 0000000..0f67d10 --- /dev/null +++ b/lib/views/team/addOrganization/add_organization_controller.dart @@ -0,0 +1,53 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:starwork_flutter/api/model/team/request/create_new_depart_request.dart'; +import 'package:starwork_flutter/api/model/team/response/depart_list_reponse.dart'; +import 'package:starwork_flutter/api/service/team_api_service.dart'; +import 'package:starwork_flutter/base/base_controller.dart'; +import 'package:starwork_flutter/common/constant/app_view_parameter_keys.dart'; + +class AddOrganizationController extends BaseController { + TextEditingController organizationNameInputController = TextEditingController(); + var selectedDepartItem = DepartItem().obs; // 当前选中的组织 + var selectedPerson = [].obs; // 当前选中的用户 + final teamApi = Get.find(); + + @override + void onReady() { + super.onReady(); + // 读取参数 + final args = Get.arguments; + if (args != null && args.containsKey(AppViewParameterKeys.departItem)) { + final json = args[AppViewParameterKeys.departItem]; + selectedDepartItem.value = DepartItem.fromJson(json); + } + } + + // 获取选中角色的显示文本 + String getSelectedPersonDisplayText() { + if (selectedPerson.isEmpty) { + return '请选择组织负责人(选填)'; // 如果没有选中角色,显示"请选择" + } else { + // 将所有选中角色的名称用逗号连接 + return selectedPerson.map((person) => person.personName ?? '').join('、'); // 使用顿号或逗号分隔 + } + } + + requestCreateDepart() async { + var response = await teamApi.requestCreateDepart( + request: CreateNewDepartRequest( + departName: organizationNameInputController.text.trim(), + parentDepartNo: selectedDepartItem.value.departNo!, + leader: selectedPerson + .map((e) => e.personNo) + .whereType() // 只保留非 null 的 String + .toList(), + ), + ); + if (response.isSuccess) { + showSuccess(); + } else { + showError(message: response.errorMsg!); + } + } +} diff --git a/lib/views/team/addOrganization/add_organization_view.dart b/lib/views/team/addOrganization/add_organization_view.dart new file mode 100644 index 0000000..6379c0d --- /dev/null +++ b/lib/views/team/addOrganization/add_organization_view.dart @@ -0,0 +1,200 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:get/get.dart'; +import 'package:starwork_flutter/api/model/team/response/depart_list_reponse.dart'; +import 'package:starwork_flutter/base/app_logger.dart'; +import 'package:starwork_flutter/common/constant/app_colors.dart'; +import 'package:starwork_flutter/common/widgets/custom_cell_list_widget.dart'; +import 'package:starwork_flutter/common/widgets/custom_cell_widget.dart'; +import 'package:starwork_flutter/common/widgets/custome_app_bar_wdiget.dart'; +import 'package:starwork_flutter/extension/function_extension.dart'; +import 'package:starwork_flutter/routes/app_routes.dart'; +import 'add_organization_controller.dart'; + +class AddOrganizationView extends GetView { + @override + Widget build(BuildContext context) { + // 即使不使用,只是引用一下 controller 就能触发初始化 + final _ = controller; // 添加这一行 + return GestureDetector( + onTap: () { + FocusScope.of(context).requestFocus(FocusNode()); + }, + child: Scaffold( + appBar: CustomAppBarWidget( + title: '添加子组织'.tr, + backgroundColor: AppColors.scaffoldBackgroundColor, + ), + backgroundColor: AppColors.scaffoldBackgroundColor, + body: SafeArea( + child: Container( + padding: EdgeInsets.symmetric(horizontal: 10.w, vertical: 10.h), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8.r), + ), + child: Column( + children: [ + CustomCellListWidget( + children: [ + CustomCellWidget( + onTap: () {}, + leftText: '组织名称'.tr, + leftIcon: Icon( + Icons.circle, + size: 4.w, + color: Colors.red, + ), + rightWidget: Expanded( + flex: 3, + child: TextField( + controller: controller.organizationNameInputController, + keyboardType: TextInputType.text, + textInputAction: TextInputAction.next, + textAlign: TextAlign.end, + style: TextStyle( + fontSize: 14.sp, + ), + decoration: InputDecoration( + isCollapsed: true, + hintText: '请输入组织名称'.tr, + hintStyle: TextStyle( + fontSize: 14.sp, + color: Colors.black54, + fontWeight: FontWeight.w400, + ), + // 设置无边框 + border: InputBorder.none, + contentPadding: EdgeInsets.zero, + // 获取焦点时的边框 + focusedBorder: InputBorder.none, + enabledBorder: InputBorder.none, + ), + ), + ), + ), + CustomCellWidget( + onTap: () async { + final result = await Get.toNamed(AppRoutes.teamSelectOrganization); + if (result != null) { + if (result is DepartItem) { + controller.selectedDepartItem.value = result; + } + } + }, + leftText: '上级组织'.tr, + leftIcon: Icon( + Icons.circle, + size: 4.w, + color: Colors.red, + ), + rightWidget: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Obx( + () => Text( + controller.selectedDepartItem.value.departName ?? '请选择上级组织'.tr, + style: TextStyle( + fontSize: 14.sp, + color: Colors.black54, + fontWeight: FontWeight.w400, + ), + ), + ), + SizedBox( + width: 4.w, + ), + Icon( + Icons.arrow_forward_ios_rounded, + size: 16.sp, + color: Colors.grey[300], + ) + ], + ), + ), + CustomCellWidget( + onTap: () async { + final result = + await Get.toNamed(AppRoutes.teamSelectPerson, arguments: controller.selectedPerson); + if (result != null && result is List) { + controller.selectedPerson.value = result; + } + }, + leftText: '组织负责人'.tr, + rightWidget: Container( + alignment: Alignment.centerRight, + constraints: BoxConstraints( + maxWidth: 200.w, + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Obx( + () => Expanded( + child: Text( + textAlign: TextAlign.end, + controller.getSelectedPersonDisplayText(), + style: TextStyle( + fontSize: 14.sp, + color: Colors.black54, + fontWeight: FontWeight.w400, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ), + ), + SizedBox( + width: 4.w, + ), + Icon( + Icons.arrow_forward_ios_rounded, + size: 16.sp, + color: Colors.grey[300], + ) + ], + ), + ), + ), + ], + ), + const Spacer(), + SizedBox( + width: double.infinity, + child: ElevatedButton( + onPressed: () async { + if (controller.organizationNameInputController.text.isEmpty) { + controller.showToast('请先输入组织名称'.tr); + return; + } + if (controller.selectedDepartItem.value.departNo == null) { + controller.showToast('请先选择上级组织'.tr); + return; + } + await controller.requestCreateDepart(); + }.debounce(), + style: ElevatedButton.styleFrom( + backgroundColor: Colors.blue, + padding: EdgeInsets.symmetric(vertical: 12.h), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8.r)), + ), + child: Text( + '保存'.tr, + style: TextStyle( + fontSize: 16.sp, + color: Colors.white, + fontWeight: FontWeight.w500, + ), + ), + ), + ), + SizedBox( + height: 10.h, + ), + ], + ), + ), + ), + ), + ); + } +} diff --git a/lib/views/team/addPerson/add_person_controller.dart b/lib/views/team/addPerson/add_person_controller.dart index 8c64d63..2d473f0 100644 --- a/lib/views/team/addPerson/add_person_controller.dart +++ b/lib/views/team/addPerson/add_person_controller.dart @@ -2,24 +2,30 @@ import 'package:flutter/widgets.dart'; import 'package:get/get.dart'; import 'package:get/get_rx/get_rx.dart'; import 'package:get/get_rx/src/rx_types/rx_types.dart'; +import 'package:starwork_flutter/api/model/team/request/create_new_person.dart'; import 'package:starwork_flutter/api/model/team/response/depart_list_reponse.dart'; import 'package:starwork_flutter/api/model/team/response/role_list_response.dart'; +import 'package:starwork_flutter/api/service/team_api_service.dart'; import 'package:starwork_flutter/base/base_controller.dart'; import 'package:starwork_flutter/common/constant/app_view_parameter_keys.dart'; class AddPersonController extends BaseController { RxString selectedGender = 'male'.obs; - + final teamApi = Get.find(); var selectedDepartItem = DepartItem().obs; // 当前选中的组织 TextEditingController nameInputController = TextEditingController(); + TextEditingController phoneInputController = TextEditingController(); TextEditingController jobNoInputController = TextEditingController(); // 工号 TextEditingController postInputController = TextEditingController(); // 职务 TextEditingController idCardInputController = TextEditingController(); // 身份证号码 + TextEditingController remarkInputController = TextEditingController(); // 备注 var isOpeningAccount = false.obs; // 是否开通账户 + var isLongTerm = true.obs; // 有效期是否为长期 + var startDate = 0.obs; // 使用时间戳表示开始时间 + var endDate = 0.obs; // 使用时间戳表示结束时间 var selectedRoles = [].obs; - @override void onReady() { super.onReady(); @@ -30,15 +36,77 @@ class AddPersonController extends BaseController { selectedDepartItem.value = DepartItem.fromJson(json); } } + // 获取选中角色的显示文本 String getSelectedRoleDisplayText() { if (selectedRoles.isEmpty) { return '请选择'; // 如果没有选中角色,显示"请选择" } else { // 将所有选中角色的名称用逗号连接 - return selectedRoles - .map((role) => role.roleName ?? '') - .join('、'); // 使用顿号或逗号分隔 + return selectedRoles.map((role) => role.roleName ?? '').join('、'); // 使用顿号或逗号分隔 } } + + /// 保存员工信息 + /// [isSustain] 是否为持续保存 + void savePerson({ + bool isSustain = false, + }) async { + List list = selectedRoles.map((role) => role.id).toList(); + var response = await teamApi.requestAddPerson( + request: CreateNewPerson( + departNo: selectedDepartItem.value.departNo, + personName: nameInputController.text.trim(), + phone: phoneInputController.text.trim(), + sex: selectedGender.value == 'male' ? 1 : 2, + position: postInputController.text.trim(), + idCard: idCardInputController.text.trim(), + remark: remarkInputController.text.trim(), + jobNumber: jobNoInputController.text.trim(), + limitType: isLongTerm.value ? 1 : 2, + limitStartTime: isLongTerm.value ? null : formatTimestamp(startDate.value), + limitEndTime: isLongTerm.value ? null : formatTimestamp(endDate.value), + isConfirm: true, + roleIds: list.whereType().toList(), + ), + ); + if (response.isSuccess) { + showSuccess(); + if (!isSustain) { + Get.back(result: true); + } else { + resetForm(); + } + } else { + showError(message: response.errorMsg!); + } + } + + // 辅助方法:将时间戳转换为日期字符串 + String formatTimestamp(int timestamp) { + if (timestamp <= 0) return '请选择'; + try { + final dateTime = DateTime.fromMillisecondsSinceEpoch(timestamp); + return '${dateTime.year}-${dateTime.month.toString().padLeft(2, '0')}-${dateTime.day.toString().padLeft(2, '0')}'; + } catch (e) { + return '请选择'; + } + } + + // 重置表单 + void resetForm() { + nameInputController.text = ''; + phoneInputController.text = ''; + jobNoInputController.text = ''; + postInputController.text = ''; + idCardInputController.text = ''; + remarkInputController.text = ''; + selectedGender.value = 'male'; + isOpeningAccount.value = false; + isLongTerm.value = true; + startDate.value = 0; + endDate.value = 0; + selectedRoles.clear(); + selectedDepartItem.value = DepartItem(); + } } diff --git a/lib/views/team/addPerson/add_person_view.dart b/lib/views/team/addPerson/add_person_view.dart index 8b7267b..be4b987 100644 --- a/lib/views/team/addPerson/add_person_view.dart +++ b/lib/views/team/addPerson/add_person_view.dart @@ -6,6 +6,7 @@ import 'package:starwork_flutter/api/model/team/response/depart_list_reponse.dar import 'package:starwork_flutter/api/model/team/response/role_list_response.dart'; import 'package:starwork_flutter/base/app_logger.dart'; import 'package:starwork_flutter/common/constant/app_colors.dart'; +import 'package:starwork_flutter/common/constant/app_view_parameter_keys.dart'; import 'package:starwork_flutter/common/widgets/custom_cell_list_widget.dart'; import 'package:starwork_flutter/common/widgets/custom_cell_widget.dart'; import 'package:starwork_flutter/common/widgets/custome_app_bar_wdiget.dart'; @@ -16,6 +17,8 @@ import 'add_person_controller.dart'; class AddPersonView extends GetView { @override Widget build(BuildContext context) { + // 即使不使用,只是引用一下 controller 就能触发初始化 + final _ = controller; // 添加这一行 return GestureDetector( onTap: () { FocusScope.of(context).requestFocus(FocusNode()); @@ -51,136 +54,33 @@ class AddPersonView extends GetView { SizedBox( height: 6.h, ), - CustomCellListWidget( - children: [ - CustomCellWidget( - onTap: () async { - final result = await Get.toNamed(AppRoutes.teamSelectOrganization); - if (result != null) { - AppLogger.highlight('result:${result}'); - if (result is DepartItem) { + Obx( + () => CustomCellListWidget( + children: [ + CustomCellWidget( + onTap: () async { + final result = await Get.toNamed(AppRoutes.teamSelectOrganization); + + if (result != null && result is DepartItem) { controller.selectedDepartItem.value = result; } - } - }, - leftText: '组织'.tr, - leftIcon: Icon( - Icons.circle, - size: 4.w, - color: Colors.red, - ), - rightWidget: Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Obx( - () => Text( - controller.selectedDepartItem.value.departName ?? '请选择', - style: TextStyle( - fontSize: 14.sp, - color: Colors.black54, - fontWeight: FontWeight.w400, - ), - ), - ), - SizedBox( - width: 4.w, - ), - Icon( - Icons.arrow_forward_ios_rounded, - size: 16.sp, - color: Colors.grey[300], - ) - ], - ), - ), - CustomCellWidget( - onTap: () {}, - leftText: '姓名'.tr, - leftIcon: Icon( - Icons.circle, - size: 4.w, - color: Colors.red, - ), - rightWidget: Expanded( - flex: 3, - child: TextField( - controller: controller.nameInputController, - keyboardType: TextInputType.text, - textInputAction: TextInputAction.next, - textAlign: TextAlign.end, - style: TextStyle( - fontSize: 14.sp, - ), - decoration: InputDecoration( - isCollapsed: true, - hintText: '请输入姓名'.tr, - hintStyle: TextStyle( - fontSize: 14.sp, - color: Colors.black54, - fontWeight: FontWeight.w400, - ), - // 设置无边框 - border: InputBorder.none, - contentPadding: EdgeInsets.zero, - // 获取焦点时的边框 - focusedBorder: InputBorder.none, - enabledBorder: InputBorder.none, - ), + }, + leftText: '组织'.tr, + leftIcon: Icon( + Icons.circle, + size: 4.w, + color: Colors.red, ), - ), - ), - CustomCellWidget( - onTap: () {}, - leftText: '开通账号'.tr, - leftSubText: '可登录并使用应用或管理功能', - leftIcon: Icon( - Icons.circle, - size: 4.w, - color: Colors.red, - ), - rightWidget: CupertinoSwitch( - value: true, - onChanged: (bool value) {}, - activeColor: Colors.blue, // 可选:打开时的颜色(iOS 默认为系统蓝色,可自定义) - trackColor: Colors.grey, // 可选:关闭时的背景轨道颜色 - ), - ), - CustomCellWidget( - onTap: () async { - var result = - await Get.toNamed(AppRoutes.teamSelectRole, arguments: controller.selectedRoles); - if (result != null) { - // 处理返回的角色数据 - if (result is List) { - controller.selectedRoles.value = result; - controller.selectedRoles.refresh(); - } - } - }, - leftText: '分配权限'.tr, - leftIcon: Icon( - Icons.circle, - size: 4.w, - color: Colors.red, - ), - rightWidget: Container( - constraints: BoxConstraints( - maxWidth: 200.w, - ), - child: Row( + rightWidget: Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ Obx( - () => Expanded( - child: Text( - controller.getSelectedRoleDisplayText(), - style: TextStyle( - fontSize: 14.sp, - color: Colors.black54, - fontWeight: FontWeight.w400, - ), - maxLines: 1, - overflow: TextOverflow.ellipsis, + () => Text( + controller.selectedDepartItem.value.departName ?? '请选择', + style: TextStyle( + fontSize: 14.sp, + color: Colors.black54, + fontWeight: FontWeight.w400, ), ), ), @@ -195,8 +95,157 @@ class AddPersonView extends GetView { ], ), ), - ) - ], + CustomCellWidget( + onTap: () {}, + leftText: '姓名'.tr, + leftIcon: Icon( + Icons.circle, + size: 4.w, + color: Colors.red, + ), + rightWidget: Expanded( + flex: 3, + child: TextField( + controller: controller.nameInputController, + keyboardType: TextInputType.text, + textInputAction: TextInputAction.next, + textAlign: TextAlign.end, + style: TextStyle( + fontSize: 14.sp, + ), + decoration: InputDecoration( + isCollapsed: true, + hintText: '请输入姓名'.tr, + hintStyle: TextStyle( + fontSize: 14.sp, + color: Colors.black54, + fontWeight: FontWeight.w400, + ), + // 设置无边框 + border: InputBorder.none, + contentPadding: EdgeInsets.zero, + // 获取焦点时的边框 + focusedBorder: InputBorder.none, + enabledBorder: InputBorder.none, + ), + ), + ), + ), + CustomCellWidget( + onTap: () {}, + leftText: '手机号'.tr, + leftIcon: controller.isOpeningAccount.value + ? Icon( + Icons.circle, + size: 4.w, + color: Colors.red, + ) + : null, + rightWidget: Expanded( + flex: 3, + child: TextField( + controller: controller.phoneInputController, + keyboardType: TextInputType.number, + textInputAction: TextInputAction.next, + textAlign: TextAlign.end, + style: TextStyle( + fontSize: 14.sp, + ), + decoration: InputDecoration( + isCollapsed: true, + hintText: controller.isOpeningAccount.value ? '若开通账号则必填'.tr : '请输入手机号'.tr, + hintStyle: TextStyle( + fontSize: 14.sp, + color: Colors.black54, + fontWeight: FontWeight.w400, + ), + // 设置无边框 + border: InputBorder.none, + contentPadding: EdgeInsets.zero, + // 获取焦点时的边框 + focusedBorder: InputBorder.none, + enabledBorder: InputBorder.none, + ), + ), + ), + ), + CustomCellWidget( + onTap: () {}, + leftText: '开通账号'.tr, + leftSubText: '可登录并使用应用或管理功能', + leftIcon: Icon( + Icons.circle, + size: 4.w, + color: Colors.red, + ), + rightWidget: Obx( + () => CupertinoSwitch( + value: controller.isOpeningAccount.value, + onChanged: (bool value) { + controller.isOpeningAccount.value = value; + }, + activeColor: Colors.blue, // 可选:打开时的颜色(iOS 默认为系统蓝色,可自定义) + trackColor: Colors.grey, // 可选:关闭时的背景轨道颜色 + ), + ), + ), + CustomCellWidget( + onTap: () async { + var result = + await Get.toNamed(AppRoutes.teamSelectRole, arguments: controller.selectedRoles); + if (result != null) { + // 处理返回的角色数据 + if (result is List) { + controller.selectedRoles.value = result; + controller.selectedRoles.refresh(); + // 收起键盘 + FocusScope.of(Get.context!).unfocus(); + } + } + }, + leftText: '分配权限'.tr, + leftIcon: Icon( + Icons.circle, + size: 4.w, + color: Colors.red, + ), + rightWidget: Container( + alignment: Alignment.centerRight, + constraints: BoxConstraints( + maxWidth: 200.w, + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Obx( + () => Expanded( + child: Text( + textAlign: TextAlign.end, + controller.getSelectedRoleDisplayText(), + style: TextStyle( + fontSize: 14.sp, + color: Colors.black54, + fontWeight: FontWeight.w400, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ), + ), + SizedBox( + width: 4.w, + ), + Icon( + Icons.arrow_forward_ios_rounded, + size: 16.sp, + color: Colors.grey[300], + ) + ], + ), + ), + ) + ], + ), ), SizedBox( height: 10.h, @@ -275,17 +324,35 @@ class AddPersonView extends GetView { ), ), CustomCellWidget( - onTap: () {}, + onTap: () async { + var result = await Get.toNamed(AppRoutes.teamAddPersonEditValidity, arguments: { + AppViewParameterKeys.isLongTerm: controller.isLongTerm.value, + AppViewParameterKeys.startDate: controller.startDate.value, + AppViewParameterKeys.endDate: controller.endDate.value, + }); + if (result != null) { + controller.isLongTerm.value = result[AppViewParameterKeys.isLongTerm]; + if (controller.isLongTerm.isFalse) { + controller.startDate.value = result[AppViewParameterKeys.startDate]; + controller.endDate.value = result[AppViewParameterKeys.endDate]; + } + } + }, leftText: '有效期'.tr, rightWidget: Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ - Text( - '请选择', - style: TextStyle( - fontSize: 14.sp, - color: Colors.black54, - fontWeight: FontWeight.w400, + Obx( + () => Text( + controller.isLongTerm.value + ? '长期'.tr + : '${controller.formatTimestamp(controller.startDate.value)} - ' + '${controller.formatTimestamp(controller.endDate.value)}', + style: TextStyle( + fontSize: 14.sp, + color: Colors.black54, + fontWeight: FontWeight.w400, + ), ), ), SizedBox( @@ -335,6 +402,7 @@ class AddPersonView extends GetView { rightWidget: Expanded( flex: 3, child: TextField( + controller: controller.remarkInputController, keyboardType: TextInputType.text, textInputAction: TextInputAction.next, textAlign: TextAlign.end, @@ -365,6 +433,7 @@ class AddPersonView extends GetView { rightWidget: Expanded( flex: 3, child: TextField( + controller: controller.idCardInputController, keyboardType: TextInputType.text, textInputAction: TextInputAction.next, textAlign: TextAlign.end, @@ -451,7 +520,26 @@ class AddPersonView extends GetView { children: [ Expanded( child: ElevatedButton( - onPressed: () {}.debounce(), + onPressed: () { + if (controller.selectedDepartItem.value.departNo == null) { + controller.showToast('请先选择组织'.tr); + return; + } + if (controller.nameInputController.text.isEmpty) { + controller.showToast('请输入姓名'.tr); + return; + } + if (controller.phoneInputController.text.isEmpty && + controller.isOpeningAccount.isTrue) { + controller.showToast('请输入手机号'.tr); + return; + } + if (controller.selectedRoles.isEmpty) { + controller.showToast('请先选择角色'.tr); + return; + } + controller.savePerson(); + }.debounce(), style: ElevatedButton.styleFrom( backgroundColor: Colors.grey[100], padding: EdgeInsets.symmetric(vertical: 12.h), @@ -474,7 +562,26 @@ class AddPersonView extends GetView { ), Expanded( child: ElevatedButton( - onPressed: () {}.debounce(), + onPressed: () { + if (controller.selectedDepartItem.value.departNo == null) { + controller.showToast('请先选择组织'.tr); + return; + } + if (controller.nameInputController.text.isEmpty) { + controller.showToast('请输入姓名'.tr); + return; + } + if (controller.phoneInputController.text.isEmpty && + controller.isOpeningAccount.isTrue) { + controller.showToast('请输入手机号'.tr); + return; + } + if (controller.selectedRoles.isEmpty) { + controller.showToast('请先选择角色'.tr); + return; + } + controller.savePerson(isSustain: true); + }.debounce(), style: ElevatedButton.styleFrom( backgroundColor: Colors.blue, padding: EdgeInsets.symmetric(vertical: 12.h), diff --git a/lib/views/team/addPerson/editValidity/edit_validity_binding.dart b/lib/views/team/addPerson/editValidity/edit_validity_binding.dart new file mode 100644 index 0000000..14e0e06 --- /dev/null +++ b/lib/views/team/addPerson/editValidity/edit_validity_binding.dart @@ -0,0 +1,10 @@ +import 'package:get/get.dart'; + +import 'edit_validity_controller.dart'; + +class EditValidityBinding extends Bindings { + @override + void dependencies() { + Get.lazyPut(() => EditValidityController()); + } +} \ No newline at end of file diff --git a/lib/views/team/addPerson/editValidity/edit_validity_controller.dart b/lib/views/team/addPerson/editValidity/edit_validity_controller.dart new file mode 100644 index 0000000..c504379 --- /dev/null +++ b/lib/views/team/addPerson/editValidity/edit_validity_controller.dart @@ -0,0 +1,26 @@ +import 'package:get/get.dart'; +import 'package:starwork_flutter/base/base_controller.dart'; +import 'package:starwork_flutter/common/constant/app_view_parameter_keys.dart'; + +class EditValidityController extends BaseController { + var isLongTerm = true.obs; // 是否开启长期有效 + + var startDate = 0.obs; // 使用时间戳表示开始时间 + var endDate = 0.obs; // 使用时间戳表示结束时间 + + @override + void onInit() { + super.onInit(); + // 读取参数 + final args = Get.arguments; + if (args != null && args.containsKey(AppViewParameterKeys.isLongTerm)) { + isLongTerm.value = args[AppViewParameterKeys.isLongTerm]; + } + if (args != null && args.containsKey(AppViewParameterKeys.startDate)) { + startDate.value = args[AppViewParameterKeys.startDate]; + } + if (args != null && args.containsKey(AppViewParameterKeys.endDate)) { + endDate.value = args[AppViewParameterKeys.endDate]; + } + } +} diff --git a/lib/views/team/addPerson/editValidity/edit_validity_view.dart b/lib/views/team/addPerson/editValidity/edit_validity_view.dart new file mode 100644 index 0000000..26ddf64 --- /dev/null +++ b/lib/views/team/addPerson/editValidity/edit_validity_view.dart @@ -0,0 +1,289 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_datetime_picker_plus/flutter_datetime_picker_plus.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:get/get.dart'; +import 'package:starwork_flutter/common/constant/app_colors.dart'; +import 'package:starwork_flutter/common/constant/app_view_parameter_keys.dart'; +import 'package:starwork_flutter/common/widgets/custom_cell_list_widget.dart'; +import 'package:starwork_flutter/common/widgets/custom_cell_widget.dart'; +import 'package:starwork_flutter/common/widgets/custome_app_bar_wdiget.dart'; +import 'package:starwork_flutter/extension/function_extension.dart'; +import 'edit_validity_controller.dart'; + +class EditValidityView extends GetView { + @override + Widget build(BuildContext context) { + // 即使不使用,只是引用一下 controller 就能触发初始化 + final _ = controller; // 添加这一行 + return Scaffold( + appBar: CustomAppBarWidget( + title: '编辑有效期'.tr, + backgroundColor: AppColors.scaffoldBackgroundColor, + ), + backgroundColor: AppColors.scaffoldBackgroundColor, + body: SafeArea( + child: Container( + padding: EdgeInsets.symmetric(horizontal: 10.w, vertical: 10.h), + child: Column( + children: [ + Container( + padding: EdgeInsets.symmetric(horizontal: 10.w, vertical: 10.h), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.all(Radius.circular(8.r)), + ), + child: Row( + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + '长期有效'.tr, + style: TextStyle( + fontSize: 16.sp, + fontWeight: FontWeight.w500, + ), + ), + SizedBox( + height: 10.h, + ), + Text( + '关闭后可限制通行门禁的生效-失效日期'.tr, + style: TextStyle( + fontSize: 12.sp, + fontWeight: FontWeight.w400, + color: Colors.grey, + ), + ), + ], + ), + const Spacer(), + Obx( + () => CupertinoSwitch( + value: controller.isLongTerm.value, + onChanged: (bool value) { + controller.isLongTerm.value = value; + }, + activeColor: Colors.blue, // 可选:打开时的颜色(iOS 默认为系统蓝色,可自定义) + trackColor: Colors.grey, // 可选:关闭时的背景轨道颜色 + ), + ), + ], + ), + ), + Obx( + () => Visibility( + visible: controller.isLongTerm.value == false, + child: Container( + margin: EdgeInsets.only(top: 10.h), + child: CustomCellListWidget( + children: [ + CustomCellWidget( + onTap: () async { + DatePicker.showDatePicker( + context, + showTitleActions: true, + minTime: DateTime.now(), + maxTime: DateTime.now().add(const Duration(days: 365 * 20)), + onChanged: (date) {}, + onConfirm: (date) { + controller.startDate.value = date.millisecondsSinceEpoch; + }, + currentTime: DateTime.now(), + locale: LocaleType.zh, + ); + }, + leftText: '生效日期'.tr, + rightWidget: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Obx(() { + String displayText = '请选择'; + if (controller.startDate.value > 0) { + displayText = DateTime.fromMillisecondsSinceEpoch(controller.startDate.value) + .toString() + .split(' ')[0]; // 只显示日期部分 + } + return Text( + displayText, + style: TextStyle( + fontSize: 14.sp, + color: Colors.black54, + fontWeight: FontWeight.w400, + ), + ); + }), + SizedBox( + width: 4.w, + ), + Icon( + Icons.arrow_forward_ios_rounded, + size: 16.sp, + color: Colors.grey[300], + ) + ], + ), + ), + CustomCellWidget( + onTap: () async { + DatePicker.showDatePicker( + context, + showTitleActions: true, + minTime: DateTime.now(), + maxTime: DateTime.now().add(const Duration(days: 365 * 20)), + onChanged: (date) {}, + onConfirm: (date) { + controller.endDate.value = date.millisecondsSinceEpoch; + }, + currentTime: DateTime.now(), + locale: LocaleType.zh, + ); + }, + leftText: '失效日期'.tr, + rightWidget: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Obx(() { + String displayText = '请选择'; + if (controller.endDate.value > 0) { + displayText = DateTime.fromMillisecondsSinceEpoch(controller.endDate.value) + .toString() + .split(' ')[0]; // 只显示日期部分 + } + return Text( + displayText, + style: TextStyle( + fontSize: 14.sp, + color: Colors.black54, + fontWeight: FontWeight.w400, + ), + ); + }), + SizedBox( + width: 4.w, + ), + Icon( + Icons.arrow_forward_ios_rounded, + size: 16.sp, + color: Colors.grey[300], + ) + ], + ), + ), + ], + ), + ), + ), + ), + Container( + margin: EdgeInsets.only(top: 10.h), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.all(Radius.circular(8.r)), + ), + padding: EdgeInsets.symmetric(horizontal: 10.w, vertical: 10.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Icon( + Icons.info_outline, + size: 16.sp, + ), + SizedBox( + width: 4.w, + ), + Text( + '有效期为通行门禁的生效~失效日期'.tr, + style: TextStyle( + fontSize: 14.sp, + fontWeight: FontWeight.w600, + ), + ) + ], + ), + SizedBox( + height: 4.h, + ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + '使用场景举例:', + style: TextStyle( + fontSize: 12.sp, + fontWeight: FontWeight.w400, + color: Colors.grey, + ), + ), + Text( + '1.健身卡到期后自动失效无法进入健身房', + style: TextStyle( + fontSize: 12.sp, + fontWeight: FontWeight.w400, + color: Colors.grey, + ), + ), + Text( + '2.医院病房,陪护人员到期后自动失效无法通行', + style: TextStyle( + fontSize: 12.sp, + fontWeight: FontWeight.w400, + color: Colors.grey, + ), + ), + ], + ) + ], + ), + ), + const Spacer(), + SizedBox( + width: double.infinity, + child: ElevatedButton( + onPressed: () { + if (controller.startDate.value == 0 || controller.endDate.value == 0) { + controller.showToast('请先选择有效日期'.tr); + return; + } + if (controller.startDate.value > controller.endDate.value) { + controller.showToast('生效日期不能大于失效日期'.tr); + return; + } + if (controller.startDate.value == controller.endDate.value) { + controller.showToast('生效日期不能等于失效日期'.tr); + return; + } + Get.back(result: { + AppViewParameterKeys.isLongTerm: controller.isLongTerm.value, + AppViewParameterKeys.startDate: controller.startDate.value, + AppViewParameterKeys.endDate: controller.endDate.value, + }); + }.debounce(), + style: ElevatedButton.styleFrom( + backgroundColor: Colors.blue, + padding: EdgeInsets.symmetric(vertical: 12.h), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8.r)), + ), + child: Text( + '确定'.tr, + style: TextStyle( + fontSize: 16.sp, + color: Colors.white, + fontWeight: FontWeight.w500, + ), + ), + ), + ), + SizedBox( + height: 10.h, + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/views/team/personnelManage/personnel_manage_controller.dart b/lib/views/team/personnelManage/personnel_manage_controller.dart index 020b510..e9ab615 100644 --- a/lib/views/team/personnelManage/personnel_manage_controller.dart +++ b/lib/views/team/personnelManage/personnel_manage_controller.dart @@ -36,6 +36,8 @@ class PersonnelManageController extends BaseController { // 转换数据为树形结构 final tree = _convertToTree(response.data!.departList ?? []); treeData.value = tree; + + treeData.refresh(); // 统计总人数 totalPersonCount.value = _calculateTotalPersonCount(response.data!.departList ?? []); @@ -114,12 +116,7 @@ class PersonnelManageController extends BaseController { return root; } - requestCreateDepart() async { - var response = await teamApi.requestCreateDepart( - request: CreateNewDepartRequest(departName: '测试2', parentDepartNo: '-1', leader: ['CY93685899']), - ); - if (response.isSuccess) {} - } + void getCacheUserInfo() async { String? cachedJson = await SharedPreferencesUtils.getString(CacheKeys.userAccountInfo); diff --git a/lib/views/team/personnelManage/personnel_manage_view.dart b/lib/views/team/personnelManage/personnel_manage_view.dart index d1f253a..0df1552 100644 --- a/lib/views/team/personnelManage/personnel_manage_view.dart +++ b/lib/views/team/personnelManage/personnel_manage_view.dart @@ -127,204 +127,219 @@ class PersonnelManageView extends GetView { ), Expanded( child: Obx( - () => Container( - margin: EdgeInsets.symmetric( - horizontal: 10.w, - ), - decoration: const BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.all(Radius.circular(8.0)), - ), - child: TreeView.simple( - tree: controller.treeData.value ?? TreeNode.root(), - showRootNode: false, - expansionIndicatorBuilder: noExpansionIndicatorBuilder, - indentation: const Indentation(style: IndentStyle.roundJoint), - onItemTap: (item) {}, - onTreeReady: (c) { - controller.treeViewController = c; - }, - builder: (context, node) { - String title = ""; - DepartItem departInfo = DepartItem(); - int personNum = 0; - bool hasChildren = node.childrenAsList.isNotEmpty; // 判断是否有子节点 - bool isPersonItem = node.data is PersonItem; // 判断当前节点是否是人员节点 - bool isOneself = false; - bool isRootNode = false; - bool isSuper = false; + () { + return Container( + margin: EdgeInsets.symmetric( + horizontal: 10.w, + ), + decoration: const BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.all(Radius.circular(8.0)), + ), + child: RefreshIndicator( + onRefresh: () async { + await controller.requestDepartList(); + }, + color: const Color(0xFF4A90E2), + backgroundColor: Colors.white, + displacement: 60.0, + edgeOffset: 0.0, + triggerMode: RefreshIndicatorTriggerMode.onEdge, + strokeWidth: 2.5, + semanticsLabel: '下拉刷新首页内容', + semanticsValue: '刷新中...', + child: TreeView.simple( + tree: controller.treeData.value ?? TreeNode.root(), + showRootNode: false, + expansionIndicatorBuilder: noExpansionIndicatorBuilder, + indentation: const Indentation(style: IndentStyle.roundJoint), + onItemTap: (item) {}, + onTreeReady: (c) { + controller.treeViewController = c; + }, + builder: (context, node) { + String title = ""; + DepartItem departInfo = DepartItem(); + int personNum = 0; + bool hasChildren = node.childrenAsList.isNotEmpty; // 判断是否有子节点 + bool isPersonItem = node.data is PersonItem; // 判断当前节点是否是人员节点 + bool isOneself = false; + bool isRootNode = false; + bool isSuper = false; - if (node.data is DepartItem) { - final depart = node.data as DepartItem; - title = depart.departName ?? "未命名部门"; - personNum = depart.personNum ?? 0; - departInfo = depart; - if (hasChildren) { - title = title + "($personNum)"; - } else { - title = title + "(0)"; - } - if (depart.parentId == -1) { - isRootNode = true; - } - } else if (node.data is PersonItem) { - // 处理人员节点 - final person = node.data as PersonItem; - title = person.personName ?? "未命名人员"; - var personUserId = person.userId; - person.roles?.forEach((role) { - if (role.isSuper == 1) { - isSuper = true; + if (node.data is DepartItem) { + final depart = node.data as DepartItem; + title = depart.departName ?? "未命名部门"; + personNum = depart.personNum ?? 0; + departInfo = depart; + if (hasChildren) { + title = title + "($personNum)"; + } else { + title = title + "(0)"; + } + if (depart.parentId == -1) { + isRootNode = true; + } + } else if (node.data is PersonItem) { + // 处理人员节点 + final person = node.data as PersonItem; + title = person.personName ?? "未命名人员"; + var personUserId = person.userId; + person.roles?.forEach((role) { + if (role.isSuper == 1) { + isSuper = true; + } + }); + if (personUserId != null && personUserId == controller.cacheUserInfo.value.id) { + isOneself = true; + } } - }); - if (personUserId != null && personUserId == controller.cacheUserInfo.value.id) { - isOneself = true; - } - } - return Container( - padding: EdgeInsets.symmetric(horizontal: 10.w, vertical: 6.h), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Expanded( - child: Row( - children: [ - !isPersonItem - ? Container( - width: 34.w, - height: 34.w, - decoration: BoxDecoration( - color: Colors.grey[300], - borderRadius: BorderRadius.circular(8.r), + return Container( + padding: EdgeInsets.symmetric(horizontal: 10.w, vertical: 6.h), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Row( + children: [ + !isPersonItem + ? Container( + width: 34.w, + height: 34.w, + decoration: BoxDecoration( + color: Colors.grey[300], + borderRadius: BorderRadius.circular(8.r), + ), + child: ClipRRect( + borderRadius: BorderRadius.circular(8.r), + child: Icon( + Icons.folder, + size: 22.w, + color: Colors.blue, + ), + ), + ) + : Container( + width: 34.w, + height: 34.w, + decoration: BoxDecoration( + color: Colors.grey[200], + borderRadius: BorderRadius.circular(8.r), + ), + child: ClipRRect( + borderRadius: BorderRadius.circular(8.r), + child: Image( + image: const AssetImage(AppImages.defaultAvatar), + width: 22.w, + height: 22.w, + fit: BoxFit.cover, + gaplessPlayback: true, + filterQuality: FilterQuality.medium, + errorBuilder: (context, error, stackTrace) { + return Icon( + Icons.person, + size: 30.sp, + color: Colors.grey[400], + ); + }, + ), + ), + ), + SizedBox( + width: 10.w, + ), + Expanded( + child: Text( + title, + style: TextStyle( + fontSize: 14.sp, + fontWeight: FontWeight.w500, ), - child: ClipRRect( - borderRadius: BorderRadius.circular(8.r), - child: Icon( - Icons.folder, - size: 22.w, + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ), + Visibility( + visible: isOneself, + child: Container( + padding: EdgeInsets.symmetric(horizontal: 6.w, vertical: 2.h), + decoration: BoxDecoration( + color: Colors.blue[50], + borderRadius: BorderRadius.circular(4.r), + border: Border.all( + color: Colors.blue, + width: 1.w, + ), + ), + child: Text( + '自己', + style: TextStyle( + fontSize: 12.sp, + fontWeight: FontWeight.w400, color: Colors.blue, ), ), - ) - : Container( - width: 34.w, - height: 34.w, + ), + ), + Visibility( + visible: isSuper && isOneself, + child: SizedBox( + width: 4.w, + ), + ), + Visibility( + visible: isSuper, + child: Container( + padding: EdgeInsets.symmetric(horizontal: 6.w, vertical: 2.h), decoration: BoxDecoration( - color: Colors.grey[200], - borderRadius: BorderRadius.circular(8.r), + color: Colors.blue[50], + borderRadius: BorderRadius.circular(4.r), + border: Border.all( + color: Colors.blue, + width: 1.w, + ), ), - child: ClipRRect( - borderRadius: BorderRadius.circular(8.r), - child: Image( - image: const AssetImage(AppImages.defaultAvatar), - width: 22.w, - height: 22.w, - fit: BoxFit.cover, - gaplessPlayback: true, - filterQuality: FilterQuality.medium, - errorBuilder: (context, error, stackTrace) { - return Icon( - Icons.person, - size: 30.sp, - color: Colors.grey[400], - ); - }, + child: Text( + '超管', + style: TextStyle( + fontSize: 12.sp, + fontWeight: FontWeight.w400, + color: Colors.blue, ), ), ), - SizedBox( - width: 10.w, - ), - Expanded( - child: Text( - title, - style: TextStyle( - fontSize: 14.sp, - fontWeight: FontWeight.w500, ), - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), + ], ), - Visibility( - visible: isOneself, - child: Container( - padding: EdgeInsets.symmetric(horizontal: 6.w, vertical: 2.h), - decoration: BoxDecoration( - color: Colors.blue[50], - borderRadius: BorderRadius.circular(4.r), - border: Border.all( - color: Colors.blue, - width: 1.w, - ), - ), - child: Text( - '自己', - style: TextStyle( - fontSize: 12.sp, - fontWeight: FontWeight.w400, - color: Colors.blue, - ), - ), - ), - ), - Visibility( - visible: isSuper && isOneself, - child: SizedBox( - width: 4.w, - ), - ), - Visibility( - visible: isSuper, - child: Container( - padding: EdgeInsets.symmetric(horizontal: 6.w, vertical: 2.h), - decoration: BoxDecoration( - color: Colors.blue[50], - borderRadius: BorderRadius.circular(4.r), - border: Border.all( - color: Colors.blue, - width: 1.w, - ), - ), - child: Text( - '超管', - style: TextStyle( - fontSize: 12.sp, - fontWeight: FontWeight.w400, - color: Colors.blue, - ), - ), - ), - ), - ], - ), - ), - Visibility( - visible: !isPersonItem, - child: Obx( - () => Radio( - value: departInfo, - groupValue: controller.selectedDepartItem.value, - activeColor: Colors.blue, - visualDensity: VisualDensity.compact, - onChanged: (value) { - controller.selectedDepartItem.value = value as DepartItem; - controller.selectedDepartItem.refresh(); - }, ), - ), - ) - ], - ), - ); - }, - ), - ), + Visibility( + visible: !isPersonItem, + child: Obx( + () => Radio( + value: departInfo, + groupValue: controller.selectedDepartItem.value, + activeColor: Colors.blue, + visualDensity: VisualDensity.compact, + onChanged: (value) { + controller.selectedDepartItem.value = value as DepartItem; + controller.selectedDepartItem.refresh(); + }, + ), + ), + ) + ], + ), + ); + }, + ), + ), + ); + }, ), ), SizedBox( - height: 10.h, + height: 4.h, ), Obx( () => Text( @@ -337,7 +352,7 @@ class PersonnelManageView extends GetView { ), ), SizedBox( - height: 10.h, + height: 4.h, ), Container( margin: EdgeInsets.symmetric( @@ -347,14 +362,17 @@ class PersonnelManageView extends GetView { children: [ Expanded( child: ElevatedButton( - onPressed: () { + onPressed: () async { if (controller.selectedDepartItem.value.departNo == null) { controller.showToast('请先选择一个组织'); return; } - Get.toNamed(AppRoutes.teamAddPerson, arguments: { + var result = await Get.toNamed(AppRoutes.teamAddPerson, arguments: { AppViewParameterKeys.departItem: controller.selectedDepartItem.value.toJson(), }); + if (result != null && result == true) { + await controller.requestDepartList(); + } }.debounce(), style: ElevatedButton.styleFrom( backgroundColor: Colors.blue, @@ -379,6 +397,9 @@ class PersonnelManageView extends GetView { controller.showToast('请先选择一个组织'); return; } + Get.toNamed(AppRoutes.teamAddOrganization, arguments: { + AppViewParameterKeys.departItem: controller.selectedDepartItem.value.toJson(), + }); }.debounce(), style: ElevatedButton.styleFrom( backgroundColor: Colors.grey[50], @@ -398,7 +419,9 @@ class PersonnelManageView extends GetView { SizedBox(width: 10.w), Expanded( child: ElevatedButton( - onPressed: () {}.debounce(), + onPressed: () { + controller.showFunctionNotOpen(); + }.debounce(), style: ElevatedButton.styleFrom( backgroundColor: Colors.grey[50], padding: EdgeInsets.symmetric(vertical: 10.h), @@ -418,7 +441,7 @@ class PersonnelManageView extends GetView { ), ), SizedBox( - height: 10.h, + height: 20.h, ), ], ), diff --git a/lib/views/team/selectOrganization/select_organization_view.dart b/lib/views/team/selectOrganization/select_organization_view.dart index b1a9014..7a5699a 100644 --- a/lib/views/team/selectOrganization/select_organization_view.dart +++ b/lib/views/team/selectOrganization/select_organization_view.dart @@ -65,7 +65,6 @@ class SelectOrganizationView extends GetView { expansionIndicatorBuilder: noExpansionIndicatorBuilder, indentation: const Indentation(style: IndentStyle.roundJoint), onItemTap: (item) { - AppLogger.highlight('message:${item.data}'); if (item.data is DepartItem) { Get.back(result: item.data); } diff --git a/lib/views/team/selectPerson/select_person_binding.dart b/lib/views/team/selectPerson/select_person_binding.dart new file mode 100644 index 0000000..83e53a5 --- /dev/null +++ b/lib/views/team/selectPerson/select_person_binding.dart @@ -0,0 +1,10 @@ +import 'package:get/get.dart'; + +import 'select_person_controller.dart'; + +class SelectPersonBinding extends Bindings { + @override + void dependencies() { + Get.lazyPut(() => SelectPersonController()); + } +} \ No newline at end of file diff --git a/lib/views/team/selectPerson/select_person_controller.dart b/lib/views/team/selectPerson/select_person_controller.dart new file mode 100644 index 0000000..513ba21 --- /dev/null +++ b/lib/views/team/selectPerson/select_person_controller.dart @@ -0,0 +1,149 @@ +import 'package:animated_tree_view/animated_tree_view.dart'; +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:starwork_flutter/api/model/team/request/get_depart_list_request.dart'; +import 'package:starwork_flutter/api/model/team/response/depart_list_reponse.dart'; +import 'package:starwork_flutter/api/service/team_api_service.dart'; +import 'package:starwork_flutter/base/base_controller.dart'; + +class SelectPersonController extends BaseController { + final teamApi = Get.find(); + final Rx treeData = Rx(null); + TreeViewController? treeViewController; + + // 搜索输入框 + TextEditingController searchInputController = TextEditingController(); + + var allPersonList = [].obs; + var selectPersonNoList = [].obs; + + // 存储从参数传递过来的已选角色 + List? initialSelectedPersons; + + @override + void onReady() { + super.onReady(); + // 获取传递的参数 + var arguments = Get.arguments; + if (arguments != null && arguments is List) { + initialSelectedPersons = arguments; + } + requestDepartList(); + _setInitialSelectedIndexes(); + } + + requestDepartList() async { + var response = await teamApi.requestDepartList( + request: GetDepartListRequest(departNo: ''), + ); + if (response.isSuccess) { + // 转换数据为树形结构 + final tree = _convertToTree(response.data!.departList ?? []); + treeData.value = tree; + // 提取所有人员信息并更新 personList + final allPersons = extractAllPersons(); + allPersonList.value = allPersons; + + treeViewController?.expandAllChildren(treeData.value ?? TreeNode.root()); + } + } + + TreeNode _convertToTree(List departList) { + // 创建根节点 + final root = TreeNode.root(); + + // 如果列表为空,直接返回根节点 + if (departList.isEmpty) { + return root; + } + + // 创建一个映射来存储所有部门节点,方便查找 + final Map departNodeMap = {}; + + // 先创建所有部门节点 + for (final depart in departList) { + final node = TreeNode( + key: depart.id.toString(), + data: depart, + ); + departNodeMap[depart.id!] = node; + } + + // 建立部门间的父子关系 + for (final depart in departList) { + final currentNode = departNodeMap[depart.id]; + if (currentNode == null) continue; + + // 建立部门间的父子关系 + if (depart.parentId == -1) { + // 顶级部门添加到根节点下 + root.add(currentNode); + } else { + // 子部门添加到对应父部门下 + final parentNode = departNodeMap[depart.parentId]; + parentNode?.add(currentNode); + } + } + + // 将人员节点添加到对应的部门节点下(在所有部门结构建立完成后再添加) + for (final depart in departList) { + final currentNode = departNodeMap[depart.id]; + if (currentNode == null) continue; + + // 将该部门下的所有人员作为子节点添加 + if (depart.persons != null && depart.persons!.isNotEmpty) { + // 对人员按某种规则排序(例如按姓名或ID) + final sortedPersons = List.from(depart.persons!); + // 这里可以根据需要添加排序逻辑,例如按姓名排序: + // sortedPersons.sort((a, b) => (a.personName ?? '').compareTo(b.personName ?? '')); + + for (final person in sortedPersons) { + final personNode = TreeNode( + key: 'person_${person.id}', + data: person, + ); + currentNode.add(personNode); + } + } + } + + return root; + } + +// 提取所有人员信息的方法 + List extractAllPersons() { + final List persons = []; + + // 递归遍历树节点 + void traverseTree(TreeNode? node) { + if (node == null) return; + + // 如果当前节点是人员节点,添加到列表中 + if (node.data is PersonItem) { + persons.add(node.data as PersonItem); + } + + // 递归遍历所有子节点 + for (final child in node.childrenAsList) { + traverseTree(child as TreeNode?); + } + } + + // 从根节点开始遍历 + traverseTree(treeData.value); + + return persons; + } + + // 根据初始选中的角色设置对应的索引 + void _setInitialSelectedIndexes() { + if (initialSelectedPersons != null && initialSelectedPersons!.isNotEmpty) { + for (var selectedPerson in initialSelectedPersons!) { + if (!selectPersonNoList.contains(selectedPerson.personNo)) { + selectPersonNoList.add(selectedPerson.personNo!); + } + } + } + selectPersonNoList.refresh(); + } +} diff --git a/lib/views/team/selectPerson/select_person_view.dart b/lib/views/team/selectPerson/select_person_view.dart new file mode 100644 index 0000000..6a1b414 --- /dev/null +++ b/lib/views/team/selectPerson/select_person_view.dart @@ -0,0 +1,332 @@ +import 'package:animated_tree_view/animated_tree_view.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:get/get.dart'; +import 'package:starwork_flutter/api/model/team/response/depart_list_reponse.dart'; +import 'package:starwork_flutter/base/app_logger.dart'; +import 'package:starwork_flutter/common/constant/app_colors.dart'; +import 'package:starwork_flutter/common/constant/app_images.dart'; +import 'package:starwork_flutter/common/widgets/custome_app_bar_wdiget.dart'; +import 'package:starwork_flutter/extension/function_extension.dart'; +import 'select_person_controller.dart'; + +class SelectPersonView extends GetView { + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: AppColors.scaffoldBackgroundColor, + appBar: CustomAppBarWidget( + title: '选择用户'.tr, + backgroundColor: AppColors.scaffoldBackgroundColor, + leading: IconButton( + icon: const Icon( + Icons.clear_rounded, + color: Colors.black, + ), + onPressed: () => Navigator.of(context).pop(), + ), + ), + body: SafeArea( + child: Padding( + padding: EdgeInsets.symmetric( + horizontal: 10.w, + vertical: 10.h, + ), + child: Column( + children: [ + _buildSearchBar(), + SizedBox( + height: 10.h, + ), + Container( + alignment: Alignment.centerLeft, + child: Text( + '组织架构', + textAlign: TextAlign.left, + style: TextStyle( + fontSize: 12.sp, + fontWeight: FontWeight.w500, + color: Colors.grey, + ), + ), + ), + SizedBox( + height: 10.h, + ), + Expanded( + child: Obx( + () { + return Container( + decoration: const BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.all(Radius.circular(8.0)), + ), + child: RefreshIndicator( + onRefresh: () async { + await controller.requestDepartList(); + }, + color: const Color(0xFF4A90E2), + backgroundColor: Colors.white, + displacement: 60.0, + edgeOffset: 0.0, + triggerMode: RefreshIndicatorTriggerMode.onEdge, + strokeWidth: 2.5, + semanticsLabel: '下拉刷新首页内容', + semanticsValue: '刷新中...', + child: TreeView.simple( + tree: controller.treeData.value ?? TreeNode.root(), + showRootNode: false, + expansionIndicatorBuilder: noExpansionIndicatorBuilder, + indentation: const Indentation(style: IndentStyle.roundJoint), + onItemTap: (item) { + if (item.data is PersonItem) { + PersonItem personInfo = item.data; + // 避免重复添加 + if (!controller.selectPersonNoList.contains(personInfo.personNo) && + personInfo.personNo != null) { + controller.selectPersonNoList.add(personInfo.personNo!); + } else { + controller.selectPersonNoList.remove(personInfo.personNo); + } + + controller.selectPersonNoList.refresh(); + } + }, + onTreeReady: (c) { + controller.treeViewController = c; + }, + builder: (context, node) { + String title = ""; + DepartItem departInfo = DepartItem(); + PersonItem personInfo = PersonItem(); + int personNum = 0; + bool hasChildren = node.childrenAsList.isNotEmpty; // 判断是否有子节点 + bool isPersonItem = node.data is PersonItem; // 判断当前节点是否是人员节点 + bool isOneself = false; + bool isRootNode = false; + bool isSuper = false; + + if (node.data is DepartItem) { + final depart = node.data as DepartItem; + title = depart.departName ?? "未命名部门"; + personNum = depart.personNum ?? 0; + departInfo = depart; + if (hasChildren) { + title = title + "($personNum)"; + } else { + title = title + "(0)"; + } + if (depart.parentId == -1) { + isRootNode = true; + } + } else if (node.data is PersonItem) { + // 处理人员节点 + personInfo = node.data as PersonItem; + title = personInfo.personName ?? "未命名人员"; + + personInfo.roles?.forEach((role) { + if (role.isSuper == 1) { + isSuper = true; + } + }); + } + + return Container( + padding: EdgeInsets.symmetric(horizontal: 10.w, vertical: 6.h), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Row( + children: [ + !isPersonItem + ? Container( + width: 34.w, + height: 34.w, + decoration: BoxDecoration( + color: Colors.grey[300], + borderRadius: BorderRadius.circular(8.r), + ), + child: ClipRRect( + borderRadius: BorderRadius.circular(8.r), + child: Icon( + Icons.folder, + size: 22.w, + color: Colors.blue, + ), + ), + ) + : Container( + width: 34.w, + height: 34.w, + decoration: BoxDecoration( + color: Colors.grey[200], + borderRadius: BorderRadius.circular(8.r), + ), + child: ClipRRect( + borderRadius: BorderRadius.circular(8.r), + child: Image( + image: const AssetImage(AppImages.defaultAvatar), + width: 22.w, + height: 22.w, + fit: BoxFit.cover, + gaplessPlayback: true, + filterQuality: FilterQuality.medium, + errorBuilder: (context, error, stackTrace) { + return Icon( + Icons.person, + size: 30.sp, + color: Colors.grey[400], + ); + }, + ), + ), + ), + SizedBox( + width: 10.w, + ), + Expanded( + child: Text( + title, + style: TextStyle( + fontSize: 14.sp, + fontWeight: FontWeight.w500, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ), + Visibility( + visible: isPersonItem, + child: Obx( + () => Checkbox( + value: controller.selectPersonNoList.contains(personInfo.personNo), + activeColor: Colors.blue, + onChanged: (value) { + if (value == true) { + // 避免重复添加 + if (!controller.selectPersonNoList.contains(personInfo.personNo) && + personInfo.id != null) { + controller.selectPersonNoList.add(personInfo.personNo!); + } + } else { + controller.selectPersonNoList.remove(personInfo.personNo); + } + controller.selectPersonNoList.refresh(); + }, + ), + ), + ) + ], + ), + ), + ], + ), + ); + }, + ), + ), + ); + }, + ), + ), + SizedBox( + height: 10.h, + ), + Row( + children: [ + Obx( + () => Text( + '已选择:${controller.selectPersonNoList.length}人', + style: TextStyle( + fontSize: 14.sp, + fontWeight: FontWeight.w500, + color: Colors.blue, + ), + ), + ), + SizedBox( + width: 20.w, + ), + Expanded( + flex: 1, + child: ElevatedButton( + onPressed: () { + if (controller.selectPersonNoList.isEmpty) { + controller.showToast('请先选择一个用户'.tr); + return; + } + List selectPersonList = []; + // 通过controller.selectPersonNoList 判断controoler.allPersonList对应选中的personInfo + controller.selectPersonNoList.forEach((personNo) { + controller.allPersonList.forEach((personInfo) { + if (personInfo.personNo == personNo) { + selectPersonList.add(personInfo); + } + }); + }); + + Get.back(result: selectPersonList); + }.debounce(), + style: ElevatedButton.styleFrom( + backgroundColor: Colors.blue, + padding: EdgeInsets.symmetric(vertical: 12.h), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8.r)), + ), + child: Text( + '确定'.tr, + style: TextStyle( + fontSize: 16.sp, + color: Colors.white, + fontWeight: FontWeight.w500, + ), + ), + ), + ), + ], + ) + ], + ), + ), + ), + ); + } + + _buildSearchBar() { + return TextField( + controller: controller.searchInputController, + textInputAction: TextInputAction.search, + decoration: InputDecoration( + hintText: '请输入设备名称'.tr, + hintStyle: TextStyle( + fontSize: 14.sp, + color: const Color(0xFF999999), + ), + prefixIcon: const Icon( + Icons.search, + color: Color(0xFF999999), + ), + filled: true, + // 启用背景填充 + fillColor: const Color(0xFFf0f0f0), + // 灰色背景(可调整色值) + border: InputBorder.none, + // 设置内边距 + contentPadding: EdgeInsets.symmetric(horizontal: 10.w, vertical: 10.h), + focusedBorder: OutlineInputBorder( + borderSide: const BorderSide( + color: Colors.blue, + width: 1.5, + ), + borderRadius: BorderRadius.circular(8.0.r), + ), + enabledBorder: OutlineInputBorder( + borderSide: const BorderSide(color: Colors.transparent), + borderRadius: BorderRadius.circular(8.0.r), + ), + ), + ); + } +} diff --git a/pubspec.lock b/pubspec.lock index 9bd246b..61ac478 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -230,6 +230,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "5.0.1" + flutter_datetime_picker_plus: + dependency: "direct main" + description: + name: flutter_datetime_picker_plus + sha256: "7d82da02c4e070bb28a9107de119ad195e2319b45c786fecc13482a9ffcc51da" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.2.0" flutter_easyloading: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 9ec6a24..0cf907f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -40,6 +40,7 @@ dependencies: event_bus: ^2.0.1 # 选择器 flutter_picker: ^2.1.0 + flutter_datetime_picker_plus: ^2.2.0 # 星云sdk starcloud: path: ../starcloud-sdk-flutter