feat: 增加(添加人员功能页面完善与接口对接、添加子组织页面完善与接口对接、选择用户页面开发)

This commit is contained in:
liyi 2025-10-09 18:19:14 +08:00
parent 9b4d112825
commit 9e10952bef
25 changed files with 1749 additions and 344 deletions

View File

@ -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";
}

View File

@ -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 {

View File

@ -0,0 +1,81 @@
class CreateNewPerson {
String? departNo;
String? personName;
bool? createUser;
String? phone;
int? sex;
String? position;
String? remark;
List<String>? associateUsers;
int? limitType;
String? limitStartTime;
String? limitEndTime;
String? idCard;
bool? isConfirm;
List<int>? 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<String, dynamic> 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<String>.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<int>.from(json['roleIds'])
: null,
jobNumber: json['jobNumber'] as String?,
);
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
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;
}
}

View File

@ -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<ApiResponse<void>> requestCreateDepart({
required CreateNewDepartRequest request,
}) {
@ -171,4 +172,16 @@ class TeamApiService {
fromJson: (data) {},
);
}
//
Future<ApiResponse<void>> requestAddPerson({
required CreateNewPerson request,
}) {
return _api.makeRequest(
path: ApiPath.createPerson,
method: HttpConstant.post,
data: request.toJson(),
fromJson: (data) {},
);
}
}

View File

@ -68,4 +68,5 @@ class BaseController extends GetxController {
}
super.onClose();
}
}

View File

@ -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";
}

View File

@ -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';
}

View File

@ -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(),
),
];
}

View File

@ -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';

View File

@ -0,0 +1,10 @@
import 'package:get/get.dart';
import 'add_organization_controller.dart';
class AddOrganizationBinding extends Bindings {
@override
void dependencies() {
Get.lazyPut(() => AddOrganizationController());
}
}

View File

@ -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 = <PersonItem>[].obs; //
final teamApi = Get.find<TeamApiService>();
@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<String>() // null String
.toList(),
),
);
if (response.isSuccess) {
showSuccess();
} else {
showError(message: response.errorMsg!);
}
}
}

View File

@ -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<AddOrganizationController> {
@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<PersonItem>) {
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,
),
],
),
),
),
),
);
}
}

View File

@ -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<TeamApiService>();
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 = <RoleListResponse>[].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<int?> 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<int>().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();
}
}

View File

@ -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<AddPersonController> {
@override
Widget build(BuildContext context) {
// 使使 controller
final _ = controller; //
return GestureDetector(
onTap: () {
FocusScope.of(context).requestFocus(FocusNode());
@ -51,136 +54,33 @@ class AddPersonView extends GetView<AddPersonController> {
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<RoleListResponse>) {
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<AddPersonController> {
],
),
),
)
],
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<RoleListResponse>) {
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<AddPersonController> {
),
),
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<AddPersonController> {
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<AddPersonController> {
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<AddPersonController> {
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<AddPersonController> {
),
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),

View File

@ -0,0 +1,10 @@
import 'package:get/get.dart';
import 'edit_validity_controller.dart';
class EditValidityBinding extends Bindings {
@override
void dependencies() {
Get.lazyPut(() => EditValidityController());
}
}

View File

@ -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];
}
}
}

View File

@ -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<EditValidityController> {
@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,
),
],
),
),
),
);
}
}

View File

@ -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);

View File

@ -127,204 +127,219 @@ class PersonnelManageView extends GetView<PersonnelManageController> {
),
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<DepartItem>(
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<DepartItem>(
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<PersonnelManageController> {
),
),
SizedBox(
height: 10.h,
height: 4.h,
),
Container(
margin: EdgeInsets.symmetric(
@ -347,14 +362,17 @@ class PersonnelManageView extends GetView<PersonnelManageController> {
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<PersonnelManageController> {
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<PersonnelManageController> {
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<PersonnelManageController> {
),
),
SizedBox(
height: 10.h,
height: 20.h,
),
],
),

View File

@ -65,7 +65,6 @@ class SelectOrganizationView extends GetView<SelectOrganizationController> {
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);
}

View File

@ -0,0 +1,10 @@
import 'package:get/get.dart';
import 'select_person_controller.dart';
class SelectPersonBinding extends Bindings {
@override
void dependencies() {
Get.lazyPut(() => SelectPersonController());
}
}

View File

@ -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<TeamApiService>();
final Rx<TreeNode?> treeData = Rx<TreeNode?>(null);
TreeViewController? treeViewController;
//
TextEditingController searchInputController = TextEditingController();
var allPersonList = <PersonItem>[].obs;
var selectPersonNoList = <String>[].obs;
//
List<PersonItem>? initialSelectedPersons;
@override
void onReady() {
super.onReady();
//
var arguments = Get.arguments;
if (arguments != null && arguments is List<PersonItem>) {
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<DepartItem> departList) {
//
final root = TreeNode.root();
//
if (departList.isEmpty) {
return root;
}
// 便
final Map<int, TreeNode> 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<PersonItem>.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<PersonItem> extractAllPersons() {
final List<PersonItem> 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();
}
}

View File

@ -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<SelectPersonController> {
@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<PersonItem> 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),
),
),
);
}
}

View File

@ -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:

View File

@ -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