feat: develop base field form item widget
This commit is contained in:
parent
eabb746738
commit
865035e17f
|
@ -15,7 +15,13 @@ class Api {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 获取部门信息
|
/// 获取分页角色列表
|
||||||
|
static Future<Response<PaginationData>> getRoles(Map params) {
|
||||||
|
return DioService.dio.get<PaginationData>(Urls.roles,
|
||||||
|
queryParameters: {'page': 1, 'pageSize': 10, ...params});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 获取部门树
|
||||||
static Future<Response> getDepts() {
|
static Future<Response> getDepts() {
|
||||||
return DioService.dio.get(Urls.depts);
|
return DioService.dio.get(Urls.depts);
|
||||||
}
|
}
|
||||||
|
|
|
@ -117,6 +117,10 @@ final theme = ThemeData(
|
||||||
color: AppTheme.primaryColor,
|
color: AppTheme.primaryColor,
|
||||||
fontSize: ScreenAdaper.height(30),
|
fontSize: ScreenAdaper.height(30),
|
||||||
),
|
),
|
||||||
|
hintStyle: TextStyle(
|
||||||
|
color: AppTheme.grey,
|
||||||
|
fontSize: ScreenAdaper.height(25),
|
||||||
|
),
|
||||||
fillColor: AppTheme.inputFillColor,
|
fillColor: AppTheme.inputFillColor,
|
||||||
filled: true,
|
filled: true,
|
||||||
labelStyle: TextStyle(
|
labelStyle: TextStyle(
|
||||||
|
|
|
@ -17,4 +17,5 @@ class Urls {
|
||||||
static String accountMenus = 'account/menus';
|
static String accountMenus = 'account/menus';
|
||||||
static String userInfo = 'system/users';
|
static String userInfo = 'system/users';
|
||||||
static String depts = 'system/depts';
|
static String depts = 'system/depts';
|
||||||
|
static String roles = 'system/roles';
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ class RoleModel {
|
||||||
final int? id;
|
final int? id;
|
||||||
final DateTime? createdAt;
|
final DateTime? createdAt;
|
||||||
final DateTime? updatedAt;
|
final DateTime? updatedAt;
|
||||||
final String? name;
|
final String name;
|
||||||
final String? value;
|
final String? value;
|
||||||
final String? remark;
|
final String? remark;
|
||||||
final int? status;
|
final int? status;
|
||||||
|
|
|
@ -42,7 +42,6 @@ class DeptPicker extends StatelessWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<CascadeItem<int, DeptModel>>> getData() async {
|
Future<List<CascadeItem<int, DeptModel>>> getData() async {
|
||||||
await Future.delayed(const Duration(milliseconds: 500));
|
|
||||||
try {
|
try {
|
||||||
final res = await Api.getDepts();
|
final res = await Api.getDepts();
|
||||||
if (res.data != null) {
|
if (res.data != null) {
|
||||||
|
|
|
@ -5,12 +5,16 @@ import 'package:get/get.dart';
|
||||||
import 'package:sk_base_mobile/apis/api.dart';
|
import 'package:sk_base_mobile/apis/api.dart';
|
||||||
import 'package:sk_base_mobile/app_theme.dart';
|
import 'package:sk_base_mobile/app_theme.dart';
|
||||||
import 'package:sk_base_mobile/constants/bg_color.dart';
|
import 'package:sk_base_mobile/constants/bg_color.dart';
|
||||||
|
import 'package:sk_base_mobile/models/role.model.dart';
|
||||||
import 'package:sk_base_mobile/models/user_info.model.dart';
|
import 'package:sk_base_mobile/models/user_info.model.dart';
|
||||||
import 'package:sk_base_mobile/screens/hr_manage/components/dept_picker.dart';
|
import 'package:sk_base_mobile/screens/hr_manage/components/dept_picker.dart';
|
||||||
|
import 'package:sk_base_mobile/util/logger_util.dart';
|
||||||
import 'package:sk_base_mobile/util/screen_adaper_util.dart';
|
import 'package:sk_base_mobile/util/screen_adaper_util.dart';
|
||||||
import 'package:sk_base_mobile/util/snack_bar.util.dart';
|
import 'package:sk_base_mobile/util/snack_bar.util.dart';
|
||||||
import 'package:sk_base_mobile/widgets/core/sk_cascade_picker.dart';
|
import 'package:sk_base_mobile/widgets/core/sk_cascade_picker.dart';
|
||||||
import 'package:sk_base_mobile/widgets/core/sk_dialog_header.dart';
|
import 'package:sk_base_mobile/widgets/core/sk_dialog_header.dart';
|
||||||
|
import 'package:sk_base_mobile/widgets/core/sk_multi_picker.dart';
|
||||||
|
import 'package:sk_base_mobile/widgets/core/sk_multi_picker_dialog.dart';
|
||||||
import 'package:sk_base_mobile/widgets/core/sk_text_input.dart';
|
import 'package:sk_base_mobile/widgets/core/sk_text_input.dart';
|
||||||
import 'package:sk_base_mobile/widgets/gradient_button.dart';
|
import 'package:sk_base_mobile/widgets/gradient_button.dart';
|
||||||
|
|
||||||
|
@ -51,6 +55,7 @@ class EditUserInfo extends StatelessWidget {
|
||||||
SkTextInput(
|
SkTextInput(
|
||||||
isDense: true,
|
isDense: true,
|
||||||
textController: controller.nameEditController,
|
textController: controller.nameEditController,
|
||||||
|
customLabel: true,
|
||||||
labelText: '姓名',
|
labelText: '姓名',
|
||||||
),
|
),
|
||||||
SizedBox(
|
SizedBox(
|
||||||
|
@ -60,6 +65,7 @@ class EditUserInfo extends StatelessWidget {
|
||||||
/// 部门
|
/// 部门
|
||||||
SkTextInput(
|
SkTextInput(
|
||||||
isDense: true,
|
isDense: true,
|
||||||
|
customLabel: true,
|
||||||
keyboardType: TextInputType.none,
|
keyboardType: TextInputType.none,
|
||||||
textController: controller.deptEditController,
|
textController: controller.deptEditController,
|
||||||
labelText: '所属部门',
|
labelText: '所属部门',
|
||||||
|
@ -74,81 +80,48 @@ class EditUserInfo extends StatelessWidget {
|
||||||
),
|
),
|
||||||
|
|
||||||
/// 角色
|
/// 角色
|
||||||
SkTextInput(
|
SkMultiPickerDropdown(
|
||||||
isDense: true,
|
isDense: true,
|
||||||
|
customLabel: true,
|
||||||
keyboardType: TextInputType.none,
|
keyboardType: TextInputType.none,
|
||||||
textController: TextEditingController(),
|
textController: controller.roleEditController,
|
||||||
labelText: '角色',
|
labelText: '角色',
|
||||||
|
hint: '选择角色',
|
||||||
onTap: (_) async {
|
onTap: (_) async {
|
||||||
Get.bottomSheet(Container(
|
Get.bottomSheet(SkMutiPickerDialog(
|
||||||
height: ScreenAdaper.height(400),
|
onSelected: (List<PickerItem> selectedData) {
|
||||||
decoration:
|
controller.roleEditController.text;
|
||||||
const BoxDecoration(color: AppTheme.nearlyWhite),
|
}, getData: () async {
|
||||||
child: Column(children: [
|
try {
|
||||||
Container(
|
final res =
|
||||||
padding: EdgeInsets.symmetric(
|
await Api.getRoles({'page': 1, 'pageSize': 30});
|
||||||
vertical: ScreenAdaper.height(20),
|
if (res.data != null) {
|
||||||
horizontal: ScreenAdaper.width(20)),
|
List<PickerItem<int>> result =
|
||||||
child: Row(
|
res.data!.items.map<PickerItem<int>>((e) {
|
||||||
children: [
|
RoleModel data = RoleModel.fromJson(e);
|
||||||
Text(
|
return PickerItem(
|
||||||
'取消',
|
label: data.name,
|
||||||
style: TextStyle(
|
value: data.id!,
|
||||||
fontSize: ScreenAdaper.height(30)),
|
subLabel: data.remark,
|
||||||
),
|
checked: false);
|
||||||
const Spacer(),
|
}).toList();
|
||||||
Text(
|
return result;
|
||||||
'确定',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: ScreenAdaper.height(30),
|
|
||||||
color: AppTheme.primaryColor),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: SingleChildScrollView(
|
|
||||||
child: ListBody(
|
|
||||||
children: <String>[
|
|
||||||
'角色1',
|
|
||||||
'角色2',
|
|
||||||
'角色3',
|
|
||||||
'角色4',
|
|
||||||
'角色5',
|
|
||||||
'角色6',
|
|
||||||
'角色7',
|
|
||||||
'角色8',
|
|
||||||
'角色9'
|
|
||||||
].map((String text) {
|
|
||||||
return Obx(
|
|
||||||
() => CheckboxListTile(
|
|
||||||
contentPadding: EdgeInsets.symmetric(
|
|
||||||
vertical: ScreenAdaper.height(0),
|
|
||||||
horizontal: ScreenAdaper.width(20)),
|
|
||||||
title: Text(text),
|
|
||||||
value:
|
|
||||||
controller.selectedDepts.contains(text),
|
|
||||||
onChanged: (bool? value) {
|
|
||||||
if (value == true) {
|
|
||||||
controller.selectedDepts.add(text);
|
|
||||||
} else {
|
|
||||||
controller.selectedDepts.remove(text);
|
|
||||||
}
|
}
|
||||||
},
|
return [];
|
||||||
),
|
} catch (e) {
|
||||||
);
|
LoggerUtil().error(e);
|
||||||
}).toList(),
|
return [];
|
||||||
),
|
}
|
||||||
))
|
})).then(
|
||||||
]),
|
(value) => Get.delete<SkMutiPickerDialogController>());
|
||||||
));
|
|
||||||
}),
|
}),
|
||||||
SizedBox(
|
SizedBox(
|
||||||
height: ScreenAdaper.height(defaultPadding),
|
height: ScreenAdaper.height(defaultPadding),
|
||||||
),
|
),
|
||||||
SkTextInput(
|
SkTextInput(
|
||||||
isDense: true,
|
isDense: true,
|
||||||
textController: TextEditingController(),
|
textController: controller.nickNameEditController,
|
||||||
|
customLabel: true,
|
||||||
labelText: '登录用户名',
|
labelText: '登录用户名',
|
||||||
),
|
),
|
||||||
SizedBox(
|
SizedBox(
|
||||||
|
@ -156,9 +129,10 @@ class EditUserInfo extends StatelessWidget {
|
||||||
),
|
),
|
||||||
SkTextInput(
|
SkTextInput(
|
||||||
isDense: true,
|
isDense: true,
|
||||||
|
customLabel: true,
|
||||||
textController: TextEditingController(),
|
textController: TextEditingController(),
|
||||||
labelText: '手机号',
|
labelText: '手机号',
|
||||||
)
|
),
|
||||||
]),
|
]),
|
||||||
),
|
),
|
||||||
)),
|
)),
|
||||||
|
@ -271,8 +245,9 @@ class EditUserInfoController extends GetxController {
|
||||||
EditUserInfoController(this.userId);
|
EditUserInfoController(this.userId);
|
||||||
final nameEditController = TextEditingController();
|
final nameEditController = TextEditingController();
|
||||||
final deptEditController = TextEditingController();
|
final deptEditController = TextEditingController();
|
||||||
|
final roleEditController = TextEditingController();
|
||||||
|
final nickNameEditController = TextEditingController();
|
||||||
final userInfo = Rxn<UserInfoModel>();
|
final userInfo = Rxn<UserInfoModel>();
|
||||||
RxList selectedDepts = RxList([]);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
onReady() {
|
onReady() {
|
||||||
|
|
|
@ -0,0 +1,43 @@
|
||||||
|
import 'dart:math';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
|
||||||
|
abstract class SkBaseFieldWidget extends StatelessWidget {
|
||||||
|
FocusNode focusNode = FocusNode();
|
||||||
|
late SkBaseFieldController baseFieldController;
|
||||||
|
final bool customLabel;
|
||||||
|
final String? labelText;
|
||||||
|
|
||||||
|
SkBaseFieldWidget({
|
||||||
|
super.key,
|
||||||
|
this.customLabel = false,
|
||||||
|
this.labelText,
|
||||||
|
autoFocus = false,
|
||||||
|
}) {
|
||||||
|
baseFieldController = Get.put(
|
||||||
|
SkBaseFieldController(autoFocus: autoFocus, focusNode: focusNode),
|
||||||
|
tag: '${labelText ?? Random().nextInt(1000)}',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SkBaseFieldController extends GetxController {
|
||||||
|
RxBool isFocus = false.obs;
|
||||||
|
final bool autoFocus;
|
||||||
|
FocusNode? focusNode;
|
||||||
|
SkBaseFieldController({required this.autoFocus, this.focusNode});
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onReady() {
|
||||||
|
if (autoFocus) {
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
focusNode?.requestFocus();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
focusNode?.addListener(() {
|
||||||
|
isFocus.value = focusNode?.hasFocus ?? false;
|
||||||
|
});
|
||||||
|
super.onReady();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,56 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get_state_manager/src/rx_flutter/rx_obx_widget.dart';
|
||||||
|
import 'package:sk_base_mobile/app_theme.dart';
|
||||||
|
import 'package:sk_base_mobile/util/screen_adaper_util.dart';
|
||||||
|
import 'package:sk_base_mobile/widgets/core/sk_base_field.dart';
|
||||||
|
|
||||||
|
class SkFormItem extends StatelessWidget {
|
||||||
|
final Widget child;
|
||||||
|
final bool customLabel;
|
||||||
|
final bool isRequired;
|
||||||
|
final String? labelText;
|
||||||
|
final SkBaseFieldController controller;
|
||||||
|
const SkFormItem(
|
||||||
|
{super.key,
|
||||||
|
required this.child,
|
||||||
|
required this.customLabel,
|
||||||
|
required this.controller,
|
||||||
|
this.labelText,
|
||||||
|
this.isRequired = false});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
if (customLabel) {
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
if (isRequired)
|
||||||
|
Text(
|
||||||
|
"*",
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.red, fontSize: ScreenAdaper.height(25)),
|
||||||
|
),
|
||||||
|
Obx(() => Text(
|
||||||
|
labelText ?? '',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: ScreenAdaper.height(25),
|
||||||
|
color: controller.isFocus.value
|
||||||
|
? AppTheme.primaryColor
|
||||||
|
: AppTheme.nearlyBlack),
|
||||||
|
)),
|
||||||
|
]),
|
||||||
|
SizedBox(
|
||||||
|
height: ScreenAdaper.height(5),
|
||||||
|
),
|
||||||
|
child
|
||||||
|
],
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return child;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,147 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:sk_base_mobile/app_theme.dart';
|
||||||
|
import 'package:sk_base_mobile/util/screen_adaper_util.dart';
|
||||||
|
import 'package:sk_base_mobile/widgets/core/sk_base_field.dart';
|
||||||
|
import 'package:sk_base_mobile/widgets/core/sk_form_item.dart';
|
||||||
|
import 'package:sk_base_mobile/widgets/core/sk_tag.dart';
|
||||||
|
import 'package:multi_dropdown/multiselect_dropdown.dart';
|
||||||
|
|
||||||
|
class SkMultiPickerDropdown<T> extends SkBaseFieldWidget {
|
||||||
|
final TextEditingController textController;
|
||||||
|
final Function(FocusNode)? onTap;
|
||||||
|
final bool isRequired;
|
||||||
|
final String? labelText;
|
||||||
|
final String? hint;
|
||||||
|
final bool isTextArea;
|
||||||
|
final bool isDense;
|
||||||
|
final Function(String)? onTapOutside;
|
||||||
|
final Function(String)? onChanged;
|
||||||
|
final String? Function(String?)? validator;
|
||||||
|
final EdgeInsetsGeometry? contentPadding;
|
||||||
|
final ValueChanged<String>? onFieldSubmitted;
|
||||||
|
final Icon? prefix;
|
||||||
|
final Widget? suffixIcon;
|
||||||
|
final InputBorder? border;
|
||||||
|
final FloatingLabelBehavior? floatingLabelBehavior;
|
||||||
|
final TextInputType? keyboardType;
|
||||||
|
SkMultiPickerDropdown(
|
||||||
|
{super.key,
|
||||||
|
required this.textController,
|
||||||
|
super.customLabel = false,
|
||||||
|
super.autoFocus = false,
|
||||||
|
this.onTap,
|
||||||
|
this.hint,
|
||||||
|
this.onFieldSubmitted,
|
||||||
|
this.isRequired = false,
|
||||||
|
this.onTapOutside,
|
||||||
|
this.keyboardType,
|
||||||
|
this.labelText,
|
||||||
|
this.prefix,
|
||||||
|
this.suffixIcon,
|
||||||
|
this.onChanged,
|
||||||
|
this.border,
|
||||||
|
this.floatingLabelBehavior = FloatingLabelBehavior.always,
|
||||||
|
this.isTextArea = false,
|
||||||
|
this.contentPadding,
|
||||||
|
this.isDense = false,
|
||||||
|
this.validator});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return SkFormItem(
|
||||||
|
child: MultiSelectDropDown<int>(
|
||||||
|
focusNode: focusNode,
|
||||||
|
onOptionSelected: (List<ValueItem> selectedOptions) {},
|
||||||
|
options: <ValueItem<int>>[
|
||||||
|
ValueItem(label: 'Option 1', value: 1),
|
||||||
|
ValueItem(label: 'Option 2', value: 2),
|
||||||
|
ValueItem(label: 'Option 3', value: 3),
|
||||||
|
ValueItem(label: 'Option 4', value: 4),
|
||||||
|
ValueItem(label: 'Option 5', value: 5),
|
||||||
|
ValueItem(label: 'Option 6', value: 6),
|
||||||
|
],
|
||||||
|
borderColor: AppTheme.nearlyBlack,
|
||||||
|
fieldBackgroundColor: AppTheme.inputFillColor,
|
||||||
|
borderWidth: 1,
|
||||||
|
borderRadius: ScreenAdaper.sp(15),
|
||||||
|
focusedBorderWidth: 2,
|
||||||
|
focusedBorderColor: AppTheme.primaryColorLight,
|
||||||
|
hint: '请选择',
|
||||||
|
hintStyle: Theme.of(Get.context!).inputDecorationTheme.hintStyle,
|
||||||
|
hintPadding: EdgeInsets.symmetric(horizontal: ScreenAdaper.width(5)),
|
||||||
|
selectionType: SelectionType.multi,
|
||||||
|
chipConfig: const ChipConfig(wrapType: WrapType.scroll),
|
||||||
|
optionTextStyle: const TextStyle(fontSize: 16),
|
||||||
|
selectedOptionIcon: const Icon(Icons.check_circle),
|
||||||
|
),
|
||||||
|
customLabel: customLabel,
|
||||||
|
labelText: labelText,
|
||||||
|
isRequired: isRequired,
|
||||||
|
controller: baseFieldController);
|
||||||
|
|
||||||
|
// return DropdownButtonFormField<String>(
|
||||||
|
// focusNode: focusNode,
|
||||||
|
// onTap: () {
|
||||||
|
// if (widget.onTap != null) {
|
||||||
|
// widget.onTap!(focusNode);
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
// icon: Icon(Icons.arrow_drop_down),
|
||||||
|
// selectedItemBuilder: (context) => [
|
||||||
|
// Container(
|
||||||
|
// width: ScreenAdaper.width(200),
|
||||||
|
// child: SingleChildScrollView(
|
||||||
|
// scrollDirection: Axis.horizontal,
|
||||||
|
// child: Row(mainAxisSize: MainAxisSize.min, children: [
|
||||||
|
// SkTag(text: '角色', color: AppTheme.primaryColor),
|
||||||
|
// SkTag(text: '角色', color: AppTheme.primaryColor),
|
||||||
|
// SkTag(text: '角色', color: AppTheme.primaryColor),
|
||||||
|
// SkTag(text: '角色', color: AppTheme.primaryColor),
|
||||||
|
// SkTag(text: '角色', color: AppTheme.primaryColor),
|
||||||
|
// SkTag(text: '角色', color: AppTheme.primaryColor),
|
||||||
|
// SkTag(text: '角色', color: AppTheme.primaryColor),
|
||||||
|
// SkTag(text: '角色', color: AppTheme.primaryColor),
|
||||||
|
// SkTag(text: '角色', color: AppTheme.primaryColor),
|
||||||
|
// SkTag(text: '角色', color: AppTheme.primaryColor),
|
||||||
|
// ]),
|
||||||
|
// ),
|
||||||
|
// )
|
||||||
|
// ],
|
||||||
|
// autovalidateMode: AutovalidateMode.onUserInteraction,
|
||||||
|
// decoration: InputDecoration(
|
||||||
|
// prefixIcon: widget.prefix,
|
||||||
|
// errorStyle: const TextStyle(fontSize: 0, height: 0.01),
|
||||||
|
// contentPadding: widget.contentPadding,
|
||||||
|
// isDense: widget.isDense,
|
||||||
|
// border: widget.border,
|
||||||
|
// floatingLabelBehavior: widget.floatingLabelBehavior,
|
||||||
|
// label: widget.labelText != null
|
||||||
|
// ? Row(
|
||||||
|
// crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
// mainAxisSize: MainAxisSize.min,
|
||||||
|
// children: [
|
||||||
|
// if (widget.isRequired)
|
||||||
|
// Text(
|
||||||
|
// "*",
|
||||||
|
// style: TextStyle(
|
||||||
|
// color: Colors.red,
|
||||||
|
// fontSize: ScreenAdaper.height(30)),
|
||||||
|
// ),
|
||||||
|
// Text(
|
||||||
|
// widget.labelText!,
|
||||||
|
// style: TextStyle(fontSize: ScreenAdaper.height(30)),
|
||||||
|
// ),
|
||||||
|
// ])
|
||||||
|
// : null,
|
||||||
|
// focusedBorder: OutlineInputBorder(
|
||||||
|
// borderSide:
|
||||||
|
// const BorderSide(color: AppTheme.primaryColorLight, width: 2),
|
||||||
|
// borderRadius: BorderRadius.circular(ScreenAdaper.sp(15))),
|
||||||
|
// hintText: widget.hint ?? '请输入',
|
||||||
|
// ),
|
||||||
|
// items: [],
|
||||||
|
// onChanged: (Object? value) {},
|
||||||
|
// );
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,136 @@
|
||||||
|
import 'package:collection/collection.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:sk_base_mobile/app_theme.dart';
|
||||||
|
import 'package:sk_base_mobile/util/screen_adaper_util.dart';
|
||||||
|
import 'package:sk_base_mobile/util/snack_bar.util.dart';
|
||||||
|
import 'package:sk_base_mobile/widgets/loading_indicator.dart';
|
||||||
|
|
||||||
|
class SkMutiPickerDialog<T> extends StatelessWidget {
|
||||||
|
final SkMutiPickerDialogController controller;
|
||||||
|
Function(List<PickerItem> data)? onSelected;
|
||||||
|
SkMutiPickerDialog(
|
||||||
|
{super.key,
|
||||||
|
required Future<List<PickerItem<T>>> Function() getData,
|
||||||
|
this.onSelected})
|
||||||
|
: controller = Get.put(SkMutiPickerDialogController<T>(getData: getData));
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
height: ScreenAdaper.height(400),
|
||||||
|
decoration: const BoxDecoration(color: AppTheme.nearlyWhite),
|
||||||
|
child: Column(children: [
|
||||||
|
Container(
|
||||||
|
padding: EdgeInsets.symmetric(
|
||||||
|
vertical: ScreenAdaper.height(20),
|
||||||
|
horizontal: ScreenAdaper.width(20)),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'取消',
|
||||||
|
style: TextStyle(fontSize: ScreenAdaper.height(30)),
|
||||||
|
),
|
||||||
|
const Spacer(),
|
||||||
|
GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
if (onSelected != null) {
|
||||||
|
final selectedData = controller.pickData
|
||||||
|
.where((element) => element.checked)
|
||||||
|
.toList();
|
||||||
|
if (selectedData.isEmpty) {
|
||||||
|
SnackBarUtil().warning('请最少选择一个“员工”角色作为基础角色');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
onSelected!(selectedData);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: Text(
|
||||||
|
'确定',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: ScreenAdaper.height(30),
|
||||||
|
color: AppTheme.primaryColor),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Obx(
|
||||||
|
() => controller.loading.value
|
||||||
|
? const LoadingIndicator(
|
||||||
|
common: true,
|
||||||
|
)
|
||||||
|
: SingleChildScrollView(
|
||||||
|
child: ListBody(
|
||||||
|
children: controller.pickData.mapIndexed((
|
||||||
|
int index,
|
||||||
|
PickerItem item,
|
||||||
|
) {
|
||||||
|
return CheckboxListTile(
|
||||||
|
fillColor: MaterialStateProperty.resolveWith<Color?>(
|
||||||
|
(Set<MaterialState> states) {
|
||||||
|
if (states.contains(MaterialState.selected)) {
|
||||||
|
return AppTheme.primaryColor;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}),
|
||||||
|
dense: true,
|
||||||
|
visualDensity: VisualDensity.compact,
|
||||||
|
contentPadding: EdgeInsets.symmetric(
|
||||||
|
vertical: ScreenAdaper.height(0),
|
||||||
|
horizontal: ScreenAdaper.width(20)),
|
||||||
|
title: Text(item.label,
|
||||||
|
style: TextStyle(fontSize: ScreenAdaper.height(25))),
|
||||||
|
subtitle: item.subLabel == null
|
||||||
|
? null
|
||||||
|
: Text(
|
||||||
|
item.subLabel!,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: ScreenAdaper.height(20),
|
||||||
|
color: AppTheme.grey),
|
||||||
|
),
|
||||||
|
value: controller.pickData[index].checked,
|
||||||
|
onChanged: (bool? value) {
|
||||||
|
PickerItem item = controller.pickData[index];
|
||||||
|
item.checked = value ?? false;
|
||||||
|
controller.pickData[index] = item;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
)),
|
||||||
|
))
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SkMutiPickerDialogController<T> extends GetxController {
|
||||||
|
RxList<PickerItem<T>> pickData = RxList([]);
|
||||||
|
Future<List<PickerItem<T>>> Function() getData;
|
||||||
|
SkMutiPickerDialogController({required this.getData});
|
||||||
|
RxBool loading = false.obs;
|
||||||
|
@override
|
||||||
|
void onReady() {
|
||||||
|
init();
|
||||||
|
super.onReady();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> init() async {
|
||||||
|
loading.value = true;
|
||||||
|
pickData.assignAll(await getData());
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class PickerItem<T> {
|
||||||
|
final String label;
|
||||||
|
final String? subLabel;
|
||||||
|
final T value;
|
||||||
|
bool checked;
|
||||||
|
PickerItem(
|
||||||
|
{required this.label,
|
||||||
|
required this.value,
|
||||||
|
required this.checked,
|
||||||
|
this.subLabel});
|
||||||
|
}
|
|
@ -1,12 +1,13 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:sk_base_mobile/app_theme.dart';
|
|
||||||
import 'package:sk_base_mobile/util/screen_adaper_util.dart';
|
import 'package:sk_base_mobile/util/screen_adaper_util.dart';
|
||||||
|
import 'package:sk_base_mobile/widgets/core/sk_base_field.dart';
|
||||||
|
import 'package:sk_base_mobile/widgets/core/sk_form_item.dart';
|
||||||
|
|
||||||
class SkTextInput extends StatefulWidget {
|
class SkTextInput extends SkBaseFieldWidget {
|
||||||
final TextEditingController textController;
|
final TextEditingController textController;
|
||||||
final Function(FocusNode)? onTap;
|
final Function(FocusNode)? onTap;
|
||||||
final bool isRequired;
|
final bool isRequired;
|
||||||
final String? labelText;
|
|
||||||
final String? hint;
|
final String? hint;
|
||||||
final bool isTextArea;
|
final bool isTextArea;
|
||||||
final bool isDense;
|
final bool isDense;
|
||||||
|
@ -14,15 +15,19 @@ class SkTextInput extends StatefulWidget {
|
||||||
final Function(String)? onChanged;
|
final Function(String)? onChanged;
|
||||||
final String? Function(String?)? validator;
|
final String? Function(String?)? validator;
|
||||||
final EdgeInsetsGeometry? contentPadding;
|
final EdgeInsetsGeometry? contentPadding;
|
||||||
final bool autoFocus;
|
|
||||||
final ValueChanged<String>? onFieldSubmitted;
|
final ValueChanged<String>? onFieldSubmitted;
|
||||||
final Icon? prefix;
|
final Icon? prefix;
|
||||||
final Widget? suffixIcon;
|
final Widget? suffixIcon;
|
||||||
final InputBorder? border;
|
final InputBorder? border;
|
||||||
final FloatingLabelBehavior? floatingLabelBehavior;
|
final FloatingLabelBehavior? floatingLabelBehavior;
|
||||||
final TextInputType? keyboardType;
|
final TextInputType? keyboardType;
|
||||||
const SkTextInput(
|
|
||||||
|
SkTextInput(
|
||||||
{super.key,
|
{super.key,
|
||||||
|
super.customLabel = false,
|
||||||
|
super.autoFocus = false,
|
||||||
|
super.labelText,
|
||||||
required this.textController,
|
required this.textController,
|
||||||
this.onTap,
|
this.onTap,
|
||||||
this.hint,
|
this.hint,
|
||||||
|
@ -30,76 +35,57 @@ class SkTextInput extends StatefulWidget {
|
||||||
this.isRequired = false,
|
this.isRequired = false,
|
||||||
this.onTapOutside,
|
this.onTapOutside,
|
||||||
this.keyboardType,
|
this.keyboardType,
|
||||||
this.labelText,
|
|
||||||
this.prefix,
|
this.prefix,
|
||||||
this.suffixIcon,
|
this.suffixIcon,
|
||||||
this.onChanged,
|
this.onChanged,
|
||||||
this.border,
|
this.border,
|
||||||
this.floatingLabelBehavior = FloatingLabelBehavior.always,
|
this.floatingLabelBehavior = FloatingLabelBehavior.always,
|
||||||
this.isTextArea = false,
|
this.isTextArea = false,
|
||||||
this.autoFocus = false,
|
|
||||||
this.contentPadding,
|
this.contentPadding,
|
||||||
this.isDense = false,
|
this.isDense = false,
|
||||||
this.validator});
|
this.validator});
|
||||||
|
|
||||||
@override
|
|
||||||
State<SkTextInput> createState() => _SkTextInputState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _SkTextInputState extends State<SkTextInput> {
|
|
||||||
late FocusNode focusNode = FocusNode();
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
if (widget.autoFocus) {
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
||||||
focusNode.requestFocus();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return TextFormField(
|
Widget field = TextFormField(
|
||||||
focusNode: focusNode,
|
focusNode: focusNode,
|
||||||
controller: widget.textController,
|
controller: textController,
|
||||||
onChanged: (String value) {
|
onChanged: (String value) {
|
||||||
if (widget.onChanged != null) {
|
if (onChanged != null) {
|
||||||
widget.onChanged!(value);
|
onChanged!(value);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onTapOutside: (event) {
|
onTapOutside: (event) {
|
||||||
if (widget.onTapOutside != null) {
|
if (onTapOutside != null) {
|
||||||
widget.onTapOutside!(widget.textController.text);
|
onTapOutside!(textController.text);
|
||||||
FocusScope.of(context).unfocus();
|
FocusScope.of(context).unfocus();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
maxLines: widget.isTextArea ? 2 : 1, // 添加这行代码
|
maxLines: isTextArea ? 2 : 1, // 添加这行代码
|
||||||
onTap: () {
|
onTap: () {
|
||||||
if (widget.onTap != null) {
|
if (onTap != null) {
|
||||||
widget.onTap!(focusNode);
|
onTap!(focusNode);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
keyboardType: widget.keyboardType,
|
keyboardType: keyboardType,
|
||||||
onFieldSubmitted: widget.onFieldSubmitted,
|
onFieldSubmitted: onFieldSubmitted,
|
||||||
autovalidateMode: AutovalidateMode.onUserInteraction,
|
autovalidateMode: AutovalidateMode.onUserInteraction,
|
||||||
validator: widget.validator,
|
validator: validator,
|
||||||
|
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
prefixIcon: widget.prefix,
|
prefixIcon: prefix,
|
||||||
suffixIcon: widget.suffixIcon,
|
suffixIcon: suffixIcon,
|
||||||
errorStyle: const TextStyle(fontSize: 0, height: 0.01),
|
errorStyle: const TextStyle(fontSize: 0, height: 0.01),
|
||||||
contentPadding: widget.contentPadding,
|
contentPadding: contentPadding,
|
||||||
isDense: widget.isDense,
|
isDense: isDense,
|
||||||
border: widget.border,
|
border: border,
|
||||||
floatingLabelBehavior: widget.floatingLabelBehavior,
|
floatingLabelBehavior: floatingLabelBehavior,
|
||||||
label: widget.labelText != null
|
label: labelText != null && !customLabel
|
||||||
? Row(
|
? Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
if (widget.isRequired)
|
if (isRequired)
|
||||||
Text(
|
Text(
|
||||||
"*",
|
"*",
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
|
@ -107,16 +93,28 @@ class _SkTextInputState extends State<SkTextInput> {
|
||||||
fontSize: ScreenAdaper.height(30)),
|
fontSize: ScreenAdaper.height(30)),
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
widget.labelText!,
|
labelText!,
|
||||||
style: TextStyle(fontSize: ScreenAdaper.height(30)),
|
style: TextStyle(fontSize: ScreenAdaper.height(30)),
|
||||||
),
|
),
|
||||||
])
|
])
|
||||||
: null,
|
: null,
|
||||||
focusedBorder: OutlineInputBorder(
|
hintText: hint ?? '请输入',
|
||||||
borderSide: BorderSide(color: AppTheme.primaryColorLight, width: 2),
|
|
||||||
borderRadius: BorderRadius.circular(ScreenAdaper.sp(15))),
|
|
||||||
hintText: widget.hint ?? '请输入',
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
return SkFormItem(
|
||||||
|
controller: baseFieldController,
|
||||||
|
customLabel: customLabel,
|
||||||
|
labelText: labelText,
|
||||||
|
isRequired: isRequired,
|
||||||
|
child: field,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// class SkTextInputController extends GetxController {
|
||||||
|
// RxBool isFocus = false.obs;
|
||||||
|
// @override
|
||||||
|
// void onReady() {
|
||||||
|
// super.onReady();
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
|
@ -608,6 +608,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.5"
|
version: "1.0.5"
|
||||||
|
multi_dropdown:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: multi_dropdown
|
||||||
|
sha256: b63ff339fcc875d667f8688c8ef62853545b580dd2b6fe78b73339783268afd8
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.4"
|
||||||
octo_image:
|
octo_image:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
|
@ -68,6 +68,7 @@ dependencies:
|
||||||
math_expressions: ^2.4.0
|
math_expressions: ^2.4.0
|
||||||
install_plugin: ^2.1.0
|
install_plugin: ^2.1.0
|
||||||
url_launcher: ^6.2.5
|
url_launcher: ^6.2.5
|
||||||
|
multi_dropdown: ^2.1.4
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
|
Loading…
Reference in New Issue