From 8a06486f933361ddabd6478f737dc9fbc35abfed Mon Sep 17 00:00:00 2001 From: louis <869322496@qq.com> Date: Fri, 29 Mar 2024 14:42:15 +0800 Subject: [PATCH] feat: project filter for inventory management --- lib/screens/login/login.controller.dart | 5 +- lib/screens/login/login.dart | 7 - .../components/inventory_search.dart | 147 ++++++++++++++++-- .../new_inventory_inout.dart | 26 ++-- .../new_inventory_inout_controller.dart | 34 ++-- lib/util/loading_util.dart | 3 +- lib/widgets/core/zt_search_select.dart | 47 +++--- 7 files changed, 194 insertions(+), 75 deletions(-) diff --git a/lib/screens/login/login.controller.dart b/lib/screens/login/login.controller.dart index cf683c2..0fa2d7e 100644 --- a/lib/screens/login/login.controller.dart +++ b/lib/screens/login/login.controller.dart @@ -1,5 +1,6 @@ import 'package:flutter/widgets.dart'; import 'package:get/get.dart'; +import 'package:sk_base_mobile/util/snack_bar.util.dart'; import '../../store/auth.store.dart'; // import 'package:sentry/sentry.dart'; @@ -11,10 +12,10 @@ class LoginController extends GetxController { String password = ''; bool loading = false; Future doLogin() async { - if (!formKey.currentState!.validate()) { + if (username.isEmpty || password.isEmpty) { + SnackBarUtil().warning('请填写用户名和密码'); return; } - // 拿出form中的数据 AuthStore.to.login(username: username, password: password); } diff --git a/lib/screens/login/login.dart b/lib/screens/login/login.dart index d9023eb..e8b24be 100644 --- a/lib/screens/login/login.dart +++ b/lib/screens/login/login.dart @@ -180,11 +180,6 @@ class LoginScreen extends StatelessWidget { Icons.person_2_outlined, size: ScreenAdaper.height(40), ), - errorStyle: TextStyle(fontSize: ScreenAdaper.height(20)), - contentPadding: EdgeInsets.symmetric( - vertical: ScreenAdaper.height(10), - horizontal: ScreenAdaper.width(30), - ), hintText: '用户名', hintStyle: TextStyle(fontSize: ScreenAdaper.height(25)), border: InputBorder.none, @@ -197,7 +192,6 @@ class LoginScreen extends StatelessWidget { onChanged: (value) { _controller.username = value; }, - validator: (value) => value!.isEmpty ? '用户名不能为空' : null, ); } @@ -225,7 +219,6 @@ class LoginScreen extends StatelessWidget { _controller.password = value; }, autovalidateMode: AutovalidateMode.onUserInteraction, - validator: (value) => value!.isEmpty ? '密码不能为空' : null, ); } diff --git a/lib/screens/new_inventory_inout/components/inventory_search.dart b/lib/screens/new_inventory_inout/components/inventory_search.dart index 57053ca..8353837 100644 --- a/lib/screens/new_inventory_inout/components/inventory_search.dart +++ b/lib/screens/new_inventory_inout/components/inventory_search.dart @@ -10,6 +10,7 @@ import 'package:sk_base_mobile/models/index.dart'; import 'package:sk_base_mobile/models/inventory.model.dart'; import 'package:sk_base_mobile/util/debouncer.dart'; import 'package:sk_base_mobile/util/screen_adaper_util.dart'; +import 'package:sk_base_mobile/widgets/core/zt_search_select.dart'; import 'package:sk_base_mobile/widgets/empty.dart'; class InventorySearch extends StatelessWidget { @@ -23,19 +24,23 @@ class InventorySearch extends StatelessWidget { @override Widget build(BuildContext context) { return Container( - padding: EdgeInsets.symmetric( - horizontal: ScreenAdaper.width(20), - vertical: ScreenAdaper.height(20)), - child: Column( - children: [ - buildSearchBar(), - SizedBox( - height: ScreenAdaper.height(defaultPadding) / 2, + padding: EdgeInsets.symmetric( + horizontal: ScreenAdaper.width(20), + vertical: ScreenAdaper.height(20)), + child: GestureDetector( + onTap: () { + FocusScope.of(context).unfocus(); + }, + child: Column( + children: [ + buildSearchBar(), + SizedBox( + height: ScreenAdaper.height(defaultPadding) / 2, + ), + Expanded(child: buildInventoryList()) + ], ), - Expanded(child: buildInventoryList()) - ], - ), - ); + )); } Widget buildSearchBar() { @@ -74,13 +79,76 @@ class InventorySearch extends StatelessWidget { ), ))), SizedBox( - width: ScreenAdaper.width(20), + width: ScreenAdaper.width(5), + ), + buildProjectPicker(), + SizedBox( + width: ScreenAdaper.width(5), ), buildHasInventoryPicker() ], ); } + // 项目选择 + Widget buildProjectPicker() { + return Container( + width: ScreenAdaper.width(250), + constraints: BoxConstraints(minWidth: ScreenAdaper.width(250)), + child: Obx( + () => DropdownButtonFormField( + value: controller.projectId.value, + isExpanded: true, + items: [ + DropdownMenuItem( + child: Text('所有项目'), + value: 0, + ), + ...controller.projects.map((element) => DropdownMenuItem( + child: Text('${element.name}'), value: element.id)) + ], + onChanged: (value) { + controller.projectId.value = value; + controller.refreshController.requestRefresh(); + }, + decoration: InputDecoration( + contentPadding: EdgeInsets.symmetric( + vertical: ScreenAdaper.height(15), + horizontal: ScreenAdaper.width(15)), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(ScreenAdaper.sp(15)), + ), + )), + )); + // return Container( + // width: ScreenAdaper.width(200), + // constraints: BoxConstraints(minWidth: ScreenAdaper.width(200)), + // child: ZtSearchSelect( + // isRequired: true, + // contentPadding: + // EdgeInsets.symmetric(horizontal: ScreenAdaper.width(15)), + // textController: controller.projectTextController, + // hintText: '请选择所属项目', + // itemBuilder: (_, itemData) => Container( + // padding: EdgeInsets.symmetric( + // vertical: ScreenAdaper.height(15), + // horizontal: ScreenAdaper.width(20)), + // child: Text( + // '${itemData.name}', + // style: TextStyle(fontSize: ScreenAdaper.height(20)), + // )), + // suggestionsCallback: (String keyword) { + // return controller.getProjects(keyword: keyword); + // }, + // onClear: () { + // controller.projectId = null; + // }, + // onSelected: (ProjectModel project) { + // controller.projectTextController.text = project.name ?? ''; + // controller.projectId = project.id; + // })); + } + Widget buildHasInventoryPicker() { return Container( width: ScreenAdaper.width(200), @@ -134,14 +202,19 @@ class InventorySearch extends StatelessWidget { : Table(columnWidths: { 0: MinColumnWidth( FixedColumnWidth(80), FixedColumnWidth(80)), - 1: FlexColumnWidth(ScreenAdaper.screenShortDistance() / 4), - 2: FlexColumnWidth(ScreenAdaper.screenShortDistance() / 4), - 3: MinColumnWidth( + 1: MinColumnWidth( FixedColumnWidth( ScreenAdaper.screenShortDistance() / 5), FixedColumnWidth( ScreenAdaper.screenShortDistance() / 5)), + 2: FlexColumnWidth(ScreenAdaper.screenShortDistance() / 4), + 3: FlexColumnWidth(ScreenAdaper.screenShortDistance() / 4), 4: MinColumnWidth( + FixedColumnWidth( + ScreenAdaper.screenShortDistance() / 5), + FixedColumnWidth( + ScreenAdaper.screenShortDistance() / 5)), + 5: MinColumnWidth( FixedColumnWidth( ScreenAdaper.screenShortDistance() / 6), FixedColumnWidth( @@ -165,6 +238,13 @@ class InventorySearch extends StatelessWidget { style: listTitleTextStyle, ), )), + TableCell( + verticalAlignment: + TableCellVerticalAlignment.middle, + child: Text( + '所属项目', + style: listTitleTextStyle, + )), TableCell( verticalAlignment: TableCellVerticalAlignment.middle, @@ -211,6 +291,21 @@ class InventorySearch extends StatelessWidget { style: textStyle, ), itemData: itemData), + + // 入库时所属项目 + buildTableCell( + Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + '${itemData.project?.name}', + style: textStyle, + ), + ], + ), + itemData: itemData), + // 产品 buildTableCell( Column( mainAxisAlignment: MainAxisAlignment.center, @@ -282,6 +377,7 @@ class InventorySearch extends StatelessWidget { class InventorySearchController extends GetxController { RxList inventories = RxList([]); + final projectTextController = TextEditingController(); RxString searchKey = ''.obs; final searchBarTextConroller = TextEditingController(); RefreshController refreshController = RefreshController(initialRefresh: true); @@ -289,6 +385,14 @@ class InventorySearchController extends GetxController { int limit = 15; int total = 0; RxInt hasInventoryStatus = RxInt(1); + RxList projects = [].obs; + RxnInt projectId = RxnInt(0); + @override + onReady() { + super.onReady(); + getProjects(); + } + Future> getData({bool isRefresh = false}) async { if (isRefresh == true) { page = 1; @@ -299,6 +403,7 @@ class InventorySearchController extends GetxController { 'page': page, 'pageSize': 15, 'keyword': searchKey.value, + 'projectId': projectId.value == 0 ? null : projectId.value, 'isCreateInout': true, 'isHasInventory': hasInventoryStatus.value, }); @@ -311,6 +416,16 @@ class InventorySearchController extends GetxController { return newList; } + Future> getProjects({String? keyword}) async { + final res = + await Api.getProjects({'page': 1, 'pageSize': 10, 'name': keyword}); + if (res.data != null) { + projects.assignAll( + res.data!.items.map((e) => ProjectModel.fromJson(e)).toList()); + } + return projects; + } + Future onRefresh() async { await getData(isRefresh: true).then((_) { refreshController.refreshCompleted(resetFooterState: true); diff --git a/lib/screens/new_inventory_inout/new_inventory_inout.dart b/lib/screens/new_inventory_inout/new_inventory_inout.dart index d118929..befcf33 100644 --- a/lib/screens/new_inventory_inout/new_inventory_inout.dart +++ b/lib/screens/new_inventory_inout/new_inventory_inout.dart @@ -49,11 +49,10 @@ class NewInventoryInout extends StatelessWidget { child: Column( children: [ buildForm(), - Obx(() => GradientButton( - buttonText: TextEnum.createInventoryInOutBtnText, - onPressed: () => {controller.create()}, - isLoading: controller.loading.value, - )) + GradientButton( + buttonText: TextEnum.createInventoryInOutBtnText, + onPressed: () => {controller.create()}, + ) ], ), )))); @@ -455,15 +454,14 @@ class NewInventoryInout extends StatelessWidget { height: ScreenAdaper.height(10), ), Obx(() => SingleChildScrollView( - controller: controller.uploadScrollController, + controller: controller.productUploadScrollController, scrollDirection: Axis.horizontal, child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ - ...controller.uploadProductImgFilesPath - .map((String path) => - builderImagePreview(path, 'product')) - .toList(), + ...controller.uploadProductImgFilesPath.map( + (String path) => + builderImagePreview(path, 'product')), buildImageUploader('product') ], ), @@ -483,15 +481,13 @@ class NewInventoryInout extends StatelessWidget { height: ScreenAdaper.height(10), ), Obx(() => SingleChildScrollView( - controller: controller.uploadScrollController, + controller: controller.agentUploadScrollController, scrollDirection: Axis.horizontal, child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ - ...controller.uploadAgentImgFilesPath - .map((String path) => - builderImagePreview(path, 'agent')) - .toList(), + ...controller.uploadAgentImgFilesPath.map( + (String path) => builderImagePreview(path, 'agent')), buildImageUploader('agent') ], ), diff --git a/lib/screens/new_inventory_inout/new_inventory_inout_controller.dart b/lib/screens/new_inventory_inout/new_inventory_inout_controller.dart index d89e469..5193b93 100644 --- a/lib/screens/new_inventory_inout/new_inventory_inout_controller.dart +++ b/lib/screens/new_inventory_inout/new_inventory_inout_controller.dart @@ -10,6 +10,7 @@ import 'package:sk_base_mobile/db_helper/dbHelper.dart'; import 'package:sk_base_mobile/models/index.dart'; import 'package:sk_base_mobile/screens/inventory_inout/inventory_inout_controller.dart'; import 'package:sk_base_mobile/util/date.util.dart'; +import 'package:sk_base_mobile/util/loading_util.dart'; import 'package:sk_base_mobile/util/logger_util.dart'; import 'package:sk_base_mobile/util/media_util.dart'; import 'package:sk_base_mobile/util/snack_bar.util.dart'; @@ -27,7 +28,6 @@ class NewInventoryInoutController extends GetxController { RxString selectedDate = ''.obs; RxString startTime = ''.obs; RxString endTime = ''.obs; - RxBool loading = false.obs; final inventoryInoutController = Get.find(); final label = TextEditingController().obs; final description = TextEditingController().obs; @@ -46,7 +46,8 @@ class NewInventoryInoutController extends GetxController { final uploadProductImgFilesPath = [].obs; final uploadAgentImgFilesPath = [].obs; // final uploadImgFilesPath = [].obs - final uploadScrollController = ScrollController(); + final productUploadScrollController = ScrollController(); + final agentUploadScrollController = ScrollController(); Map payload = {}; NewInventoryInoutController({this.inOrOut}); @@ -171,7 +172,7 @@ class NewInventoryInoutController extends GetxController { } payload['remark'] = remarkTextController.text; - loading.value = true; + await LoadingUtil.to.show(status: '提交中请稍后...'); // uploadImgFilesPath try { @@ -202,7 +203,7 @@ class NewInventoryInoutController extends GetxController { } catch (e) { LoggerUtil().error(e); } finally { - loading.value = false; + LoadingUtil.to.dismiss(); } } @@ -212,17 +213,25 @@ class NewInventoryInoutController extends GetxController { if (pickedFile != null) { if (type == 'product') { uploadProductImgFilesPath.add(pickedFile.path); + Future.delayed(const Duration(milliseconds: 100), () { + // 滚动到最右边 + productUploadScrollController.animateTo( + productUploadScrollController.position.maxScrollExtent, + duration: const Duration(milliseconds: 200), + curve: Curves.easeOut, + ); + }); } else { uploadAgentImgFilesPath.add(pickedFile.path); + Future.delayed(const Duration(milliseconds: 100), () { + // 滚动到最右边 + agentUploadScrollController.animateTo( + agentUploadScrollController.position.maxScrollExtent, + duration: const Duration(milliseconds: 200), + curve: Curves.easeOut, + ); + }); } - Future.delayed(const Duration(milliseconds: 100), () { - // 滚动到最右边 - uploadScrollController.animateTo( - uploadScrollController.position.maxScrollExtent, - duration: const Duration(milliseconds: 200), - curve: Curves.easeOut, - ); - }); } // await PhotoPickerUtil().showPicker(callback: (XFile pickedFile) async { // await LoadingUtil.to.show(status: '上传中...'); @@ -331,7 +340,6 @@ class NewInventoryInoutController extends GetxController { picEndTime(context); return; } - loading.value = true; // db // .insert(TaskModel( diff --git a/lib/util/loading_util.dart b/lib/util/loading_util.dart index 8093150..161b9cd 100644 --- a/lib/util/loading_util.dart +++ b/lib/util/loading_util.dart @@ -43,8 +43,9 @@ class LoadingUtil extends GetxService { if (status != null) Text(status, style: TextStyle( + decoration: TextDecoration.none, color: AppTheme.primaryColor, - fontSize: ScreenAdaper.height(20))) + fontSize: ScreenAdaper.height(25))) ], ), ) diff --git a/lib/widgets/core/zt_search_select.dart b/lib/widgets/core/zt_search_select.dart index 3ab8025..c4222ff 100644 --- a/lib/widgets/core/zt_search_select.dart +++ b/lib/widgets/core/zt_search_select.dart @@ -9,26 +9,27 @@ class ZtSearchSelect extends StatelessWidget { final Function? suggestionsCallback; final void Function(T)? onSelected; final VoidCallback? onClear; - final String labelText; + final String? labelText; final String hintText; - + final EdgeInsetsGeometry? contentPadding; final bool isRequired; final Widget Function(BuildContext, T) itemBuilder; const ZtSearchSelect( {super.key, required this.textController, - required this.labelText, required this.itemBuilder, required this.suggestionsCallback, this.hintText = '请选择', this.onSelected, + this.labelText, this.onClear, + this.contentPadding, this.isRequired = false}); @override Widget build(BuildContext context) { return TypeAheadField( - hideOnUnfocus: false, + hideOnUnfocus: true, controller: textController, suggestionsCallback: (String keyword) { return suggestionsCallback != null @@ -44,6 +45,7 @@ class ZtSearchSelect extends StatelessWidget { focusNode: focusNode, controller: _, decoration: InputDecoration( + contentPadding: contentPadding, focusedBorder: OutlineInputBorder( borderSide: const BorderSide( color: AppTheme.primaryColorLight, width: 2), @@ -59,25 +61,28 @@ class ZtSearchSelect extends StatelessWidget { } }, ) - : const SizedBox(), + : null, floatingLabelBehavior: FloatingLabelBehavior.always, hintText: hintText, - label: Row( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisSize: MainAxisSize.min, - children: [ - if (isRequired) - Text( - "*", - style: TextStyle( - color: Colors.red, - fontSize: ScreenAdaper.height(30)), - ), - Text( - labelText, - style: TextStyle(fontSize: ScreenAdaper.height(30)), - ), - ]))); + label: labelText != null + ? Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + if (isRequired) + Text( + "*", + style: TextStyle( + color: Colors.red, + fontSize: ScreenAdaper.height(30)), + ), + Text( + labelText ?? '', + style: TextStyle( + fontSize: ScreenAdaper.height(30)), + ), + ]) + : SizedBox())); }, emptyBuilder: (_) => Container( alignment: Alignment.center,