diff --git a/lib/apis/api.dart b/lib/apis/api.dart index 8b479c0..bb1fb7c 100644 --- a/lib/apis/api.dart +++ b/lib/apis/api.dart @@ -3,6 +3,7 @@ import 'dart:io'; import 'package:dio/dio.dart'; import 'package:sk_base_mobile/constants/global_url.dart'; import 'package:sk_base_mobile/models/base_response.dart'; +import 'package:sk_base_mobile/models/dict_type.model.dart'; import 'package:sk_base_mobile/services/dio.service.dart'; import '../constants/constants.dart'; @@ -39,6 +40,11 @@ Future> getInventoryInout(Map params) { queryParameters: {'page': 1, 'pageSize': 10, ...(params)}); } +// 一次性获取所有的字典类型(不分页) +Future getDictTypeAll(Map params) { + return DioService.dio.post(Urls.getDictType, data: params); +} + Future logout() { return DioService.dio.post( Urls.logout, diff --git a/lib/constants/dict_enum.dart b/lib/constants/dict_enum.dart new file mode 100644 index 0000000..3b624f4 --- /dev/null +++ b/lib/constants/dict_enum.dart @@ -0,0 +1,5 @@ +class DictTypeEnum { + static const String InventoryRoom = "inventory_room"; + static const String InventoryLine = 'inventory_line'; + static const String InventoryLineLevel = 'inventory_line_level'; +} diff --git a/lib/constants/global_url.dart b/lib/constants/global_url.dart index 0276a32..e184a02 100644 --- a/lib/constants/global_url.dart +++ b/lib/constants/global_url.dart @@ -10,4 +10,5 @@ class Urls { static String getProducts = 'product'; static String getInventoryInout = 'materials-in-out'; static String updateAvatar = 'user/updateAvatar'; + static String getDictType = 'system/dict-type/all'; } diff --git a/lib/global.dart b/lib/global.dart index abaca27..04c44a7 100644 --- a/lib/global.dart +++ b/lib/global.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:get/get.dart'; +import 'package:sk_base_mobile/store/dict.store.dart'; import 'package:sk_base_mobile/util/loading_util.dart'; import 'package:timeago/timeago.dart' as timeago; import 'package:sk_base_mobile/constants/cache_key.dart'; @@ -42,6 +43,9 @@ class Global { /// 依赖注入用户信息 await Get.putAsync(() => AuthStore().init()); + /// 依赖注入字典信息 + await Get.putAsync(() => DictService().init()); + if (StorageService.to.getString(CacheKeys.token, isWithUser: false) != null) { /// 这里做一些缓存初始化后的操作 diff --git a/lib/index.dart b/lib/index.dart index 6fb8887..5d54244 100644 --- a/lib/index.dart +++ b/lib/index.dart @@ -5,7 +5,7 @@ import 'package:sk_base_mobile/config.dart'; import 'package:sk_base_mobile/services/storage.service.dart'; import 'package:sk_base_mobile/app_theme.dart'; import 'package:pull_to_refresh/pull_to_refresh.dart'; -import 'package:sk_base_mobile/widgets/refresh-footer.dart'; +import 'package:sk_base_mobile/widgets/refresh_footer.dart'; import 'package:sk_base_mobile/widgets/refresh_header.dart'; import 'constants/constants.dart'; diff --git a/lib/models/dict_item.model.dart b/lib/models/dict_item.model.dart index dabc91b..cebdb4f 100644 --- a/lib/models/dict_item.model.dart +++ b/lib/models/dict_item.model.dart @@ -1,15 +1,15 @@ class DictItemModel { DictItemModel({ - required this.id, - required this.createdAt, - required this.updatedAt, - required this.creator, - required this.updater, + this.id, + this.createdAt, + this.updatedAt, + this.creator, + this.updater, required this.label, - required this.value, - required this.orderNo, - required this.status, - required this.remark, + this.value, + this.orderNo, + this.status, + this.remark, }); final int? id; @@ -17,7 +17,7 @@ class DictItemModel { final DateTime? updatedAt; final String? creator; final String? updater; - final String? label; + final String label; final String? value; final int? orderNo; final int? status; diff --git a/lib/models/dict_type.model.dart b/lib/models/dict_type.model.dart new file mode 100644 index 0000000..732da42 --- /dev/null +++ b/lib/models/dict_type.model.dart @@ -0,0 +1,58 @@ +import 'dict_item.model.dart'; + +class DictTypeModel { + DictTypeModel({ + required this.id, + required this.createdAt, + required this.updatedAt, + required this.creator, + required this.updater, + required this.name, + required this.code, + required this.status, + required this.remark, + required this.dictItems, + }); + + final int? id; + final DateTime? createdAt; + final DateTime? updatedAt; + final String? creator; + final String? updater; + final String? name; + final String? code; + final int? status; + final String? remark; + final List dictItems; + + factory DictTypeModel.fromJson(Map json) { + return DictTypeModel( + id: json["id"], + createdAt: DateTime.tryParse(json["createdAt"] ?? ""), + updatedAt: DateTime.tryParse(json["updatedAt"] ?? ""), + creator: json["creator"], + updater: json["updater"], + name: json["name"], + code: json["code"], + status: json["status"], + remark: json["remark"], + dictItems: json["dictItems"] == null + ? [] + : List.from( + json["dictItems"]!.map((x) => DictItemModel.fromJson(x))), + ); + } + + Map toJson() => { + "id": id, + "createdAt": createdAt?.toIso8601String(), + "updatedAt": updatedAt?.toIso8601String(), + "creator": creator, + "updater": updater, + "name": name, + "code": code, + "status": status, + "remark": remark, + "dictItems": dictItems.map((x) => x.toJson()).toList(), + }; +} diff --git a/lib/models/product.model.dart b/lib/models/product.model.dart index dbc25e6..41ec9e5 100644 --- a/lib/models/product.model.dart +++ b/lib/models/product.model.dart @@ -4,22 +4,25 @@ import 'package:sk_base_mobile/models/file.model.dart'; import 'company.model.dart'; class ProductModel { - ProductModel({ - required this.id, - required this.createdAt, - required this.updatedAt, - required this.name, - required this.remark, - required this.isDelete, - required this.companyId, - required this.unitId, - required this.namePinyin, - required this.files, - required this.company, - required this.unit, - }); + ProductModel( + {required this.id, + required this.createdAt, + required this.updatedAt, + required this.name, + required this.remark, + required this.isDelete, + required this.companyId, + required this.unitId, + required this.namePinyin, + required this.files, + required this.company, + required this.unit, + this.productNumber, + this.productSpecification}); final int? id; + final String? productNumber; + final String? productSpecification; final DateTime? createdAt; final DateTime? updatedAt; final String? name; @@ -41,6 +44,8 @@ class ProductModel { remark: json["remark"], isDelete: json["isDelete"], companyId: json["companyId"], + productNumber: json['productNumber'], + productSpecification: json['productSpecification'], unitId: json["unitId"], namePinyin: json["namePinyin"], files: json["files"] == null @@ -63,6 +68,8 @@ class ProductModel { "isDelete": isDelete, "companyId": companyId, "unitId": unitId, + 'productNumber': productNumber, + 'productSpecification': productSpecification, "namePinyin": namePinyin, "files": files.map((x) => x?.toJson()).toList(), "company": company?.toJson(), diff --git a/lib/screens/inventory_inout/components/task_page_View.dart b/lib/screens/inventory_inout/components/inventory_inout_page_View.dart similarity index 79% rename from lib/screens/inventory_inout/components/task_page_View.dart rename to lib/screens/inventory_inout/components/inventory_inout_page_View.dart index 105f787..a0f5a09 100644 --- a/lib/screens/inventory_inout/components/task_page_View.dart +++ b/lib/screens/inventory_inout/components/inventory_inout_page_View.dart @@ -3,14 +3,17 @@ import 'package:get/get.dart'; import 'package:sk_base_mobile/screens/inventory_inout/components/task_list.dart'; import 'package:sk_base_mobile/screens/inventory_inout/inventory_inout_controller.dart'; -class TaskPageView extends StatelessWidget { - TaskPageView({super.key}); +class InventoryInoutView extends StatelessWidget { + InventoryInoutView({super.key}); final controller = Get.put(InventoryInoutController()); @override Widget build(BuildContext context) { return PageView( physics: const BouncingScrollPhysics(), reverse: true, + onPageChanged: (index) { + controller.changePage(index); + }, controller: controller.pageController, children: List.generate( controller.daysNum, diff --git a/lib/screens/inventory_inout/components/task_page_holder.dart b/lib/screens/inventory_inout/components/task_page_holder.dart index 6273ac0..084f1f6 100644 --- a/lib/screens/inventory_inout/components/task_page_holder.dart +++ b/lib/screens/inventory_inout/components/task_page_holder.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:sk_base_mobile/screens/inventory_inout/components/change_button_roe.dart'; -import 'package:sk_base_mobile/screens/inventory_inout/components/task_page_View.dart'; +import 'package:sk_base_mobile/screens/inventory_inout/components/inventory_inout_page_View.dart'; import 'package:sk_base_mobile/util/screen_adaper_util.dart'; class TaskPageBody extends StatelessWidget { @@ -38,7 +38,7 @@ class TaskPageBody extends StatelessWidget { Colors.white.withOpacity(.0), Colors.white.withOpacity(.0), ])), - child: TaskPageView(), + child: InventoryInoutView(), )); } } diff --git a/lib/screens/inventory_inout/inventory_inout_controller.dart b/lib/screens/inventory_inout/inventory_inout_controller.dart index c9f5b84..8964f0f 100644 --- a/lib/screens/inventory_inout/inventory_inout_controller.dart +++ b/lib/screens/inventory_inout/inventory_inout_controller.dart @@ -4,6 +4,7 @@ import 'package:sk_base_mobile/app_theme.dart'; import 'package:sk_base_mobile/db_helper/dbHelper.dart'; import 'package:sk_base_mobile/screens/new_inventory_inout/new_inventory_inout.dart'; import 'package:sk_base_mobile/util/date.util.dart'; +import 'package:sk_base_mobile/util/logger_util.dart'; import 'package:sk_base_mobile/util/modal.util.dart'; import 'package:sk_base_mobile/util/screen_adaper_util.dart'; import 'package:sk_base_mobile/apis/api.dart' as Api; @@ -36,6 +37,12 @@ class InventoryInoutController extends GetxController { // getInoutHistory(); } + Future changePage(int index) async { + LoggerUtil() + .info(DateUtil.format(DateTime.now().add(Duration(days: -index)))); + + } + /// 打开出库还是入库选择框 Future showInOrOutPickerDialog() async { showDialog( diff --git a/lib/screens/new_inventory_inout/components/product_search.dart b/lib/screens/new_inventory_inout/components/product_search.dart new file mode 100644 index 0000000..331fd32 --- /dev/null +++ b/lib/screens/new_inventory_inout/components/product_search.dart @@ -0,0 +1,149 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:pull_to_refresh/pull_to_refresh.dart'; +import 'package:sk_base_mobile/app_theme.dart'; +import 'package:sk_base_mobile/models/index.dart'; +import 'package:sk_base_mobile/util/debouncer.dart'; +import 'package:sk_base_mobile/util/screen_adaper_util.dart'; +import 'package:sk_base_mobile/apis/api.dart' as Api; + +class ProductSearch extends StatelessWidget { + ProductSearch({super.key}); + final controller = Get.put(ProductSearchController()); + @override + Widget build(BuildContext context) { + return Container( + padding: EdgeInsets.symmetric( + horizontal: ScreenAdaper.width(20), + vertical: ScreenAdaper.height(20)), + child: Column( + children: [buildSearchBar(), Expanded(child: buildProductList())], + ), + ); + } + + Widget buildSearchBar() { + final doSearch = debouncer((_) { + controller.onRefresh(); + }, delayTime: 500); + return Row( + children: [ + Expanded( + child: TextField( + controller: controller.searchBarController, + onChanged: (value) => doSearch(value), + decoration: InputDecoration( + contentPadding: EdgeInsets.symmetric( + vertical: ScreenAdaper.height(10), + horizontal: ScreenAdaper.width(10)), + hintText: '名称,编号', + floatingLabelBehavior: FloatingLabelBehavior.always, + prefixIcon: const Icon(Icons.search), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(10), + ), + ))), + ], + ); + } + + Widget buildProductList() { + return Obx( + () => SmartRefresher( + enablePullDown: true, + enablePullUp: true, + controller: controller.refreshController, + onLoading: controller.onLoading, + onRefresh: controller.onRefresh, + child: ListView.separated( + itemCount: controller.products.length, + separatorBuilder: (context, index) => const Divider( + height: 1, + color: AppTheme.dividerColor, + ), + itemBuilder: (context, index) { + final itemData = controller.products[index]; + return InkWell( + onTap: () {}, + child: Container( + padding: + EdgeInsets.symmetric(vertical: ScreenAdaper.height(10)), + child: Row( + children: [ + Text( + itemData.productNumber!, + style: TextStyle(fontSize: ScreenAdaper.sp(20)), + ), + SizedBox( + width: ScreenAdaper.width(10), + ), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + '${itemData.name}', + style: TextStyle(fontSize: ScreenAdaper.sp(20)), + ), + Text( + '${itemData.company?.name}', + style: TextStyle( + fontSize: ScreenAdaper.sp(15), + color: AppTheme.grey), + ) + ], + )), + ], + ), + ), + ); + }), + ), + ); + } +} + +class ProductSearchController extends GetxController { + RxList products = RxList([]); + TextEditingController searchBarController = TextEditingController(text: ''); + RefreshController refreshController = RefreshController(initialRefresh: true); + int page = 1; + int limit = 15; + int total = 0; + Future> getData({bool isRefresh = false}) async { + if (isRefresh == true) { + page = 1; + } else { + page++; + } + final res = await Api.getProducts( + {'page': page, 'pageSize': 30, 'keyword': searchBarController.text}); + List newList = + res.data!.items.map((e) => ProductModel.fromJson(e)).toList(); + isRefresh == true ? products.assignAll(newList) : products.addAll(newList); + + return newList; + } + + Future onRefresh() async { + await getData(isRefresh: true).then((_) { + refreshController.refreshCompleted(resetFooterState: true); + }).catchError((_) { + refreshController.refreshFailed(); + }); + } + + Future onLoading() async { + await getData().then((_) { + if (_.isEmpty) { + refreshController.loadNoData(); + } else { + refreshController.loadComplete(); + } + }).catchError((_) { + refreshController.loadFailed(); + }); + } +} diff --git a/lib/screens/new_inventory_inout/new_inventory_inout.dart b/lib/screens/new_inventory_inout/new_inventory_inout.dart index 507b651..9e6bdcd 100644 --- a/lib/screens/new_inventory_inout/new_inventory_inout.dart +++ b/lib/screens/new_inventory_inout/new_inventory_inout.dart @@ -1,17 +1,24 @@ import 'dart:io'; +import 'package:dropdown_search/dropdown_search.dart'; import 'package:flutter/material.dart'; import 'package:flutter_typeahead/flutter_typeahead.dart'; import 'package:get/get.dart'; import 'package:sk_base_mobile/app_theme.dart'; import 'package:sk_base_mobile/constants/constants.dart'; +import 'package:sk_base_mobile/constants/dict_enum.dart'; import 'package:sk_base_mobile/models/index.dart'; import 'package:sk_base_mobile/screens/new_inventory_inout/components/date_time.dart'; +import 'package:sk_base_mobile/screens/new_inventory_inout/components/product_search.dart'; +import 'package:sk_base_mobile/store/dict.store.dart'; import 'package:sk_base_mobile/util/date.util.dart'; +import 'package:sk_base_mobile/util/modal.util.dart'; +import 'package:sk_base_mobile/widgets/core/zt_bottomsheet_picker.dart'; import 'package:sk_base_mobile/widgets/core/zt_number_input.dart'; import 'package:sk_base_mobile/widgets/core/zt_search_select.dart'; import 'package:sk_base_mobile/widgets/core/zk_date_picker.dart'; import 'package:sk_base_mobile/widgets/core/zt_text_input.dart'; +import 'package:sk_base_mobile/widgets/empty.dart'; import 'package:sk_base_mobile/widgets/gradient_button.dart'; import 'package:sk_base_mobile/screens/new_inventory_inout/new_inventory_inout_controller.dart'; import 'package:sk_base_mobile/util/screen_adaper_util.dart'; @@ -21,7 +28,7 @@ class NewInventoryInout extends StatelessWidget { NewInventoryInout({super.key, this.inOrOut = 'in'}); final controller = Get.put(NewInventoryInoutController()); - + final dictService = Get.find(); @override Widget build(BuildContext context) { return SafeArea( @@ -88,12 +95,13 @@ class NewInventoryInout extends StatelessWidget { SizedBox( height: ScreenAdaper.height(formVerticalGap), ), - Row( - children: [Expanded(child: buildRemark())], - ), + buildPositionBottomPicker(), SizedBox( height: ScreenAdaper.height(formVerticalGap), ), + Row( + children: [Expanded(child: buildRemark())], + ), buildImageUploadQueue() ]; @@ -116,16 +124,16 @@ class NewInventoryInout extends StatelessWidget { /// 项目 Widget buildProjectPicker() { - return ZtSearchSelect( + return ZtSearchSelect( isRequired: true, textController: controller.projectTextController, labelText: '项目', - itemBuilder: (itemData) => Container( + itemBuilder: (_, itemData) => Container( padding: EdgeInsets.symmetric( vertical: ScreenAdaper.height(15), horizontal: ScreenAdaper.width(20)), child: Text( - '${itemData?.name}', + '${itemData.name}', style: TextStyle(fontSize: ScreenAdaper.sp(20)), )), suggestionsCallback: (String keyword) { @@ -142,24 +150,168 @@ class NewInventoryInout extends StatelessWidget { /// 产品 Widget buildProductPicker() { - return ZtSearchSelect( - isRequired: true, - textController: controller.productTextController, - labelText: '产品', - itemBuilder: (itemData) => ListTile( - title: Text('${itemData?.name}'), - subtitle: Text('${itemData?.company?.name ?? ''}'), - ), - suggestionsCallback: (String keyword) { - return controller.getProducts(keyword: keyword); - }, - onClear: () { - controller.payload.remove('productId'); - }, - onSelected: (ProductModel product) { - controller.productTextController.text = product.name ?? ''; - controller.payload['productId'] = product.id; + return TextFormField( + controller: controller.positionTextController, + decoration: const InputDecoration( + labelText: '产品', + floatingLabelBehavior: FloatingLabelBehavior.always), + readOnly: true, + onTap: () { + Get.generalDialog( + barrierLabel: "productPicker", + barrierDismissible: true, + transitionDuration: const Duration(milliseconds: 400), + pageBuilder: (_, __, ___) { + return Center( + child: ClipRRect( + borderRadius: BorderRadius.circular(30), + child: Material( + child: ClipRRect( + borderRadius: const BorderRadius.only( + topLeft: Radius.circular( + 30, + ), + topRight: Radius.circular(30)), + child: Container( + height: Get.height - ScreenAdaper.height(200), + width: Get.width - ScreenAdaper.width(200), + child: ProductSearch(), + ), + )))); + }, + transitionBuilder: (_, anim, __, child) { + Tween tween; + tween = Tween(begin: const Offset(0, 1), end: Offset.zero); + return SlideTransition( + position: tween.animate( + CurvedAnimation(parent: anim, curve: Curves.easeInOut), + ), + child: child, + ); + }, + ); }); + // return DropdownSearch( + // asyncItems: (String filter) async { + // return controller.getProducts(keyword: filter); + // }, + + // dropdownDecoratorProps: const DropDownDecoratorProps( + // dropdownSearchDecoration: InputDecoration( + // labelText: "产品", + // hintText: "请选择", + // floatingLabelBehavior: FloatingLabelBehavior.always), + // ), + // popupProps: PopupProps.menu( + // isFilterOnline: true, + // searchFieldProps: TextFieldProps( + // cursorColor: AppTheme.primaryColorLight, + // decoration: InputDecoration( + // contentPadding: EdgeInsets.symmetric( + // vertical: ScreenAdaper.height(15), + // horizontal: ScreenAdaper.width(10)))), + // searchDelay: const Duration(milliseconds: 500), + // emptyBuilder: (context, searchEntry) => const Empty( + // text: '未找到', + // ), + // itemBuilder: (_, itemData, _1) => Container( + // decoration: const BoxDecoration( + // border: Border(top: BorderSide(color: AppTheme.dividerColor))), + // padding: EdgeInsets.symmetric( + // vertical: ScreenAdaper.height(5), + // horizontal: ScreenAdaper.width(10)), + // child: Row( + // children: [ + // Text( + // itemData.productNumber!, + // style: TextStyle(fontSize: ScreenAdaper.sp(20)), + // ), + // SizedBox( + // width: ScreenAdaper.width(10), + // ), + // Expanded( + // child: Column( + // crossAxisAlignment: CrossAxisAlignment.start, + // children: [ + // Text( + // '${itemData.name}', + // style: TextStyle(fontSize: ScreenAdaper.sp(20)), + // ), + // Text( + // '${itemData.company?.name}', + // style: TextStyle( + // fontSize: ScreenAdaper.sp(15), color: AppTheme.grey), + // ) + // ], + // )), + // // SizedBox( + // // width: ScreenAdaper.width(10), + // // ), + // // Text( + // // itemData.productNumber!, + // // style: TextStyle(fontSize: ScreenAdaper.sp(20)), + // // ), + // ], + // ), + // ), + // showSearchBox: true, + // ), + // onChanged: print, + // // selectedItem: "Brazil", + // ); + // return ZtSearchSelect( + // isRequired: true, + // textController: controller.productTextController, + // labelText: '产品', + // itemBuilder: (_, itemData) => Container( + // padding: EdgeInsets.symmetric( + // vertical: ScreenAdaper.height(5), + // horizontal: ScreenAdaper.width(10)), + // child: Row( + // children: [ + // Text( + // itemData.productNumber!, + // style: TextStyle(fontSize: ScreenAdaper.sp(20)), + // ), + // SizedBox( + // width: ScreenAdaper.width(10), + // ), + // Expanded( + // child: Column( + // crossAxisAlignment: CrossAxisAlignment.start, + // children: [ + // Text( + // '${itemData.name}', + // style: TextStyle(fontSize: ScreenAdaper.sp(20)), + // ), + // Text( + // '${itemData.company?.name}', + // style: TextStyle( + // fontSize: ScreenAdaper.sp(15), + // color: AppTheme.grey), + // ) + // ], + // )), + // // SizedBox( + // // width: ScreenAdaper.width(10), + // // ), + // // Text( + // // itemData.productNumber!, + // // style: TextStyle(fontSize: ScreenAdaper.sp(20)), + // // ), + // ], + // ), + // ), + // suggestionsCallback: (String keyword) { + // return controller.getProducts(keyword: keyword); + // }, + // onClear: () { + // controller.payload.remove('productId'); + // }, + // onSelected: (ProductModel product) { + // controller.productTextController.text = product.name ?? ''; + // controller.payload['productId'] = product.id; + // }); } /// 时间 @@ -205,6 +357,73 @@ class NewInventoryInout extends StatelessWidget { ); } + /// 库存位置选择 + Widget buildPositionBottomPicker() { + final firstLevelData = [ + ...dictService.getDictItemsByCode(DictTypeEnum.InventoryRoom) + ]; + final secondLevelData = [ + DictItemModel(label: '未知', value: '未知'), + ...dictService.getDictItemsByCode(DictTypeEnum.InventoryLine) + ]; + final thirdDatas = + dictService.getDictItemsByCode(DictTypeEnum.InventoryLineLevel); + final thirdtLevelData = [ + if (thirdDatas.isNotEmpty) DictItemModel(label: '未知', value: '未知'), + ...thirdDatas + ]; + return TextFormField( + controller: controller.positionTextController, + decoration: const InputDecoration( + labelText: '库存位置', + floatingLabelBehavior: FloatingLabelBehavior.always), + readOnly: true, + onTap: () { + printInfo( + info: firstLevelData + .map((e) => Center(child: (Text(e.label)))) + .toList() + .toString()); + ModalUtil.showBottomSheetPicker( + title: '库存位置', + firstLevel: firstLevelData + .map((e) => Center(child: (Text(e.label)))) + .toList(), + secondLevel: secondLevelData + .map((e) => Center(child: (Text(e.label)))) + .toList(), + thirdLevel: thirdtLevelData + .map((e) => Center(child: (Text(e.label)))) + .toList(), + onChanged: (_0, _1, _2) { + String position = firstLevelData[_0].label; + if (_1 >= 1) { + position += ', ${secondLevelData[_1].label}'; + } + if (_2 >= 1) { + position += ', ${thirdtLevelData[_2].label}'; + } + // , ${secondLevelData[_1].label}, ${thirdtLevelData[_2].label} + controller.positionTextController.text = position; + }); + }, + onChanged: (_) { + printInfo(info: _); + }); + // return ZtTextInput( + // textController: controller.agentTextController, + // labelText: '库存位置', + // isRequired: true, + // onTap: () => ModalUtil.showBottomSheetPicker( + // title: '库存位置', + // firstLevel: dictService + // .getDictItemsByCode(DictTypeEnum.InventoryRoom) + // .map((e) => Text(e.label)) + // .toList(), + // onChanged: () {}), + // ); + } + /// 照片上传 Widget buildImageUploadQueue() { return Container( @@ -217,7 +436,7 @@ class NewInventoryInout extends StatelessWidget { Text( '*请拍照上传产品照片', style: TextStyle( - fontSize: ScreenAdaper.sp(30), + fontSize: ScreenAdaper.sp(25), color: AppTheme.secondPrimaryColor), ), SizedBox( @@ -247,8 +466,8 @@ class NewInventoryInout extends StatelessWidget { children: [ Container( margin: EdgeInsets.symmetric(horizontal: ScreenAdaper.width(5)), - width: ScreenAdaper.width(200), - height: ScreenAdaper.width(200), + width: ScreenAdaper.width(180), + height: ScreenAdaper.width(180), decoration: BoxDecoration( borderRadius: BorderRadius.circular(20), border: Border.all(width: 1.0, color: AppTheme.dividerColor), @@ -303,8 +522,8 @@ class NewInventoryInout extends StatelessWidget { }, child: Container( margin: EdgeInsets.symmetric(horizontal: ScreenAdaper.width(5)), - width: ScreenAdaper.width(200), - height: ScreenAdaper.width(200), + width: ScreenAdaper.width(180), + height: ScreenAdaper.width(180), decoration: BoxDecoration( borderRadius: BorderRadius.circular(20), border: Border.all(width: 1.0, color: AppTheme.dividerColor), 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 7ab8271..a484b19 100644 --- a/lib/screens/new_inventory_inout/new_inventory_inout_controller.dart +++ b/lib/screens/new_inventory_inout/new_inventory_inout_controller.dart @@ -44,6 +44,7 @@ class NewInventoryInoutController extends GetxController { final quantityTextController = TextEditingController(); final agentTextController = TextEditingController(); final remarkTextController = TextEditingController(); + final positionTextController = TextEditingController(); final uploadImgFilesPath = [].obs; final uploadScrollController = ScrollController(); Map payload = {}; @@ -60,7 +61,7 @@ class NewInventoryInoutController extends GetxController { Future> getProducts({String? keyword}) async { final res = - await Api.getProducts({'page': 1, 'pageSize': 10, 'name': keyword}); + await Api.getProducts({'page': 1, 'pageSize': 10, 'keyword': keyword}); if (res.data != null) { products.assignAll( res.data!.items.map((e) => ProductModel.fromJson(e)).toList()); diff --git a/lib/screens/product/product.controller.dart b/lib/screens/product/product.controller.dart new file mode 100644 index 0000000..e69de29 diff --git a/lib/store/auth.store.dart b/lib/store/auth.store.dart index ddcf360..02835b5 100644 --- a/lib/store/auth.store.dart +++ b/lib/store/auth.store.dart @@ -5,7 +5,6 @@ import 'package:get/get.dart'; import 'package:sk_base_mobile/apis/api.dart' as Api; import 'package:sk_base_mobile/util/logger_util.dart'; import 'package:sk_base_mobile/widgets/tap_to_dismiss_keyboard.dart'; -import 'package:sk_base_mobile/config.dart'; import 'package:sk_base_mobile/models/auth.dart'; import 'package:sk_base_mobile/models/user_info.model.dart'; import 'package:sk_base_mobile/services/service.dart'; diff --git a/lib/store/dict.store.dart b/lib/store/dict.store.dart new file mode 100644 index 0000000..11e52a1 --- /dev/null +++ b/lib/store/dict.store.dart @@ -0,0 +1,40 @@ +import 'package:get/get.dart'; +import 'package:get/get_state_manager/get_state_manager.dart'; +import 'package:sk_base_mobile/constants/dict_enum.dart'; +import 'package:sk_base_mobile/models/dict_item.model.dart'; +import 'package:sk_base_mobile/models/dict_type.model.dart'; +import 'package:sk_base_mobile/apis/api.dart' as Api; +import 'package:sk_base_mobile/util/logger_util.dart'; + +const needCachedKey = [ + DictTypeEnum.InventoryRoom, + DictTypeEnum.InventoryLine, + DictTypeEnum.InventoryLineLevel +]; + +class DictService extends GetxService { + Future init() async { + await getDictTypes(); + return this; + } + + RxList dictTypes = RxList([]); + Future getDictTypes() async { + try { + final response = await Api.getDictTypeAll({ + 'storeCodes': needCachedKey, + 'withItems': true, + }); + dictTypes.value = (response.data as List) + .map((item) => DictTypeModel.fromJson(item)) + .toList(); + } catch (e) { + LoggerUtil().error('getDictTypes error: $e'); + } + } + + List getDictItemsByCode(String code) { + return dictTypes.firstWhereOrNull((item) => item.code == code)?.dictItems ?? + []; + } +} diff --git a/lib/util/modal.util.dart b/lib/util/modal.util.dart index 51de6e3..5f7495d 100644 --- a/lib/util/modal.util.dart +++ b/lib/util/modal.util.dart @@ -1,6 +1,8 @@ +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; import 'package:get/get.dart'; +import 'package:sk_base_mobile/widgets/core/zt_bottomsheet_picker.dart'; class ModalUtil { static Future showWarningDialog( @@ -40,4 +42,27 @@ class ModalUtil { ); return confirmed; } + + /// 底部选择器,支持3级 + static Future showBottomSheetPicker( + {title = '请选择', + required onChanged, + required firstLevel, + secondLevel, + thirdLevel, + itemHeight, + popupHeight}) async => + showCupertinoModalPopup( + context: Get.overlayContext!, + builder: (BuildContext context) { + return ZtBottomSheetPicker( + title: title, + onChanged: onChanged, + firstLevel: firstLevel, + secondLevel: secondLevel, + thirdLevel: thirdLevel, + itemHeight: itemHeight ?? 80.0, + popupHeight: popupHeight ?? 400); + }, + ); } diff --git a/lib/widgets/core/zt_base_date_picker copy.dart b/lib/widgets/core/zt_base_date_picker copy.dart new file mode 100644 index 0000000..a81ca25 --- /dev/null +++ b/lib/widgets/core/zt_base_date_picker copy.dart @@ -0,0 +1,101 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:sk_base_mobile/util/logger_util.dart'; +import 'package:sk_base_mobile/util/screen_adaper_util.dart'; + +class ZtBaseDatePicker extends StatelessWidget { + final Function? onDateTimeChanged; + final yearController = + FixedExtentScrollController(initialItem: DateTime.now().year - 2000); + final monthController = + FixedExtentScrollController(initialItem: DateTime.now().month - 1); + final dayController = + FixedExtentScrollController(initialItem: DateTime.now().day - 1); + ZtBaseDatePicker({super.key, this.onDateTimeChanged}); + + @override + Widget build(BuildContext context) { + return Container( + height: ScreenAdaper.height(250), + color: Colors.white, + child: Column( + children: [ + Row( + children: [ + TextButton( + onPressed: () { + Get.back(); + }, + child: Text( + '取消', + style: TextStyle(fontSize: ScreenAdaper.sp(25)), + )), + const Spacer(), + TextButton( + onPressed: () { + Get.back(); + final year = 2000 + yearController.selectedItem; + String month = '${monthController.selectedItem + 1}'; + if (int.parse(month) < 10) { + month = '0$month'; + } + final day = dayController.selectedItem + 1; + if (onDateTimeChanged != null) { + onDateTimeChanged!('$year-$month-$day'); + } + LoggerUtil().info('$year-$month-$day'); + }, + child: Text( + '确定', + style: TextStyle(fontSize: ScreenAdaper.sp(25)), + )) + ], + ), + Expanded( + child: Row( + children: [ + Expanded( + child: CupertinoPicker( + scrollController: yearController, + itemExtent: ScreenAdaper.height(60), + onSelectedItemChanged: (int index) { + // 处理用户选择的年份 + }, + children: List.generate(DateTime.now().year - 1999, + (int index) { + return Center(child: Text('${2000 + index}年')); + }), + ), + ), + Expanded( + child: CupertinoPicker( + scrollController: monthController, + itemExtent: ScreenAdaper.height(60), + onSelectedItemChanged: (int index) { + // 处理用户选择的月份 + }, + children: List.generate(12, (int index) { + return Center(child: Text('${index + 1}月')); + }), + ), + ), + Expanded( + child: CupertinoPicker( + scrollController: dayController, + itemExtent: ScreenAdaper.height(60), + onSelectedItemChanged: (int index) { + // 处理用户选择的日期 + }, + children: List.generate(31, (int index) { + return Center(child: Text('${index + 1}日')); + }), + ), + ), + ], + )) + ], + ), + ); + } +} diff --git a/lib/widgets/core/zt_bottomsheet_picker.dart b/lib/widgets/core/zt_bottomsheet_picker.dart new file mode 100644 index 0000000..0e620b9 --- /dev/null +++ b/lib/widgets/core/zt_bottomsheet_picker.dart @@ -0,0 +1,112 @@ +import 'package:flutter/cupertino.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'; + +class ZtBottomSheetPicker extends StatelessWidget { + final Function? onChanged; + FixedExtentScrollController firstLevelControlller = + FixedExtentScrollController(); + FixedExtentScrollController secondLevelController = + FixedExtentScrollController(); + FixedExtentScrollController thirdLevelController = + FixedExtentScrollController(); + final List firstLevel; + final List? secondLevel; + final List? thirdLevel; + double? itemHeight; + double? popupHeight; + final String title; + ZtBottomSheetPicker( + {super.key, + this.title = '请选择', + required this.onChanged, + required this.firstLevel, + this.secondLevel, + this.thirdLevel, + this.itemHeight = 80.0, + this.popupHeight = 400.0}); + + @override + Widget build(BuildContext context) { + return Container( + height: ScreenAdaper.height(popupHeight!), + color: Colors.white, + child: Column( + children: [ + Row( + children: [ + TextButton( + onPressed: () { + Get.back(); + }, + child: Text( + '取消', + style: TextStyle(fontSize: ScreenAdaper.sp(25)), + )), + Expanded( + child: Center( + child: Text( + title, + style: TextStyle( + color: AppTheme.nearlyBlack, + fontSize: ScreenAdaper.sp(25), + decoration: TextDecoration.none), + ), + )), + TextButton( + onPressed: () { + Get.back(); + onChanged!( + firstLevelControlller.selectedItem, + (secondLevel ?? []).isNotEmpty + ? secondLevelController.selectedItem + : -1, + (thirdLevel ?? []).isNotEmpty + ? thirdLevelController.selectedItem + : -1, + ); + }, + child: Text( + '确定', + style: TextStyle(fontSize: ScreenAdaper.sp(25)), + )) + ], + ), + Expanded( + child: Row( + children: [ + Expanded( + child: CupertinoPicker( + scrollController: firstLevelControlller, + itemExtent: ScreenAdaper.height(itemHeight!), + onSelectedItemChanged: (int index) {}, + children: firstLevel, + ), + ), + if ((secondLevel ?? []).isNotEmpty) + Expanded( + child: CupertinoPicker( + scrollController: secondLevelController, + itemExtent: ScreenAdaper.height(itemHeight!), + onSelectedItemChanged: (int index) {}, + children: secondLevel!, + ), + ), + if ((thirdLevel ?? []).isNotEmpty) + Expanded( + child: CupertinoPicker( + scrollController: thirdLevelController, + itemExtent: ScreenAdaper.height(itemHeight!), + onSelectedItemChanged: (int index) {}, + children: thirdLevel!, + ), + ), + ], + )) + ], + ), + ); + } +} diff --git a/lib/widgets/core/zt_search_select.dart b/lib/widgets/core/zt_search_select.dart index b151159..022554c 100644 --- a/lib/widgets/core/zt_search_select.dart +++ b/lib/widgets/core/zt_search_select.dart @@ -4,14 +4,14 @@ import 'package:get/get.dart'; import 'package:sk_base_mobile/app_theme.dart'; import 'package:sk_base_mobile/util/screen_adaper_util.dart'; -class ZtSearchSelect extends StatelessWidget { +class ZtSearchSelect extends StatelessWidget { final TextEditingController textController; final Function? suggestionsCallback; - final Function? onSelected; + final void Function(T)? onSelected; final VoidCallback? onClear; final String labelText; final bool isRequired; - final Widget Function(dynamic itemData)? itemBuilder; + final Widget Function(BuildContext, T) itemBuilder; const ZtSearchSelect( {super.key, required this.textController, @@ -89,13 +89,13 @@ class ZtSearchSelect extends StatelessWidget { ), ), itemBuilder: (context, itemData) { - return itemBuilder!(itemData); + return itemBuilder(context, itemData as T); }, onSelected: (selectedItemData) { // 键盘关闭 FocusScope.of(context).unfocus(); if (onSelected != null) { - onSelected!(selectedItemData); + onSelected!(selectedItemData as T); } }); } diff --git a/lib/widgets/core/zt_text_input.dart b/lib/widgets/core/zt_text_input.dart index 9147924..dcd38ba 100644 --- a/lib/widgets/core/zt_text_input.dart +++ b/lib/widgets/core/zt_text_input.dart @@ -12,14 +12,15 @@ class ZtTextInput extends StatelessWidget { final String labelText; final String? hint; final bool isTextArea; - const ZtTextInput( - {super.key, - required this.textController, - this.onTap, - this.hint, - this.isRequired = false, - this.labelText = '', - this.isTextArea = false}); + const ZtTextInput({ + super.key, + required this.textController, + this.onTap, + this.hint, + this.isRequired = false, + this.labelText = '', + this.isTextArea = false, + }); @override Widget build(BuildContext context) { return TextFormField( @@ -27,7 +28,7 @@ class ZtTextInput extends StatelessWidget { onTapOutside: (event) { FocusScope.of(context).unfocus(); }, - maxLines: isTextArea ? 3 : 1, // 添加这行代码 + maxLines: isTextArea ? 2 : 1, // 添加这行代码 onTap: onTap ?? () {}, decoration: InputDecoration( floatingLabelBehavior: FloatingLabelBehavior.always, diff --git a/lib/widgets/empty.dart b/lib/widgets/empty.dart index f9f6a54..a159054 100644 --- a/lib/widgets/empty.dart +++ b/lib/widgets/empty.dart @@ -10,10 +10,10 @@ class Empty extends StatelessWidget { Widget build(BuildContext context) { return Center( child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [ - Center( - child: Image( - height: ScreenAdaper.height(130), - image: const AssetImage('assets/images/empty_icon.png'))), + // Center( + // child: Image( + // height: ScreenAdaper.height(130), + // image: const AssetImage('assets/images/empty_icon.png'))), Text( text ?? '', textAlign: TextAlign.center, diff --git a/lib/widgets/loading_indicator.dart b/lib/widgets/loading_indicator.dart new file mode 100644 index 0000000..1034add --- /dev/null +++ b/lib/widgets/loading_indicator.dart @@ -0,0 +1,17 @@ +import 'package:flutter/cupertino.dart'; +import 'package:sk_base_mobile/app_theme.dart'; +import 'package:sk_base_mobile/util/screen_adaper_util.dart'; + +class LoadingIndicator extends StatelessWidget { + final bool animating; + const LoadingIndicator({super.key, this.animating = true}); + + @override + Widget build(BuildContext context) { + return CupertinoActivityIndicator( + animating: animating, + color: AppTheme.primaryColor, + radius: ScreenAdaper.sp(20), + ); + } +} diff --git a/lib/widgets/refresh-footer.dart b/lib/widgets/refresh_footer.dart similarity index 81% rename from lib/widgets/refresh-footer.dart rename to lib/widgets/refresh_footer.dart index 85d8781..92d6b85 100644 --- a/lib/widgets/refresh-footer.dart +++ b/lib/widgets/refresh_footer.dart @@ -1,6 +1,9 @@ import 'package:flutter/cupertino.dart'; +import 'package:loading_animation_widget/loading_animation_widget.dart'; import 'package:pull_to_refresh/pull_to_refresh.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/loading_indicator.dart'; class RefreshFooter extends StatefulWidget { const RefreshFooter({Key? key}) : super(key: key); @@ -32,16 +35,16 @@ class _RefreshFooterState extends State builder: (BuildContext context, LoadStatus? status) { Widget body; if (status == LoadStatus.idle) { - body = const Text("Pull up to get latest messages"); + body = const Text("上拉获取更多"); } else if (status == LoadStatus.loading) { - body = const SizedBox(child: const CupertinoActivityIndicator()); + body = const LoadingIndicator(); } else if (status == LoadStatus.failed) { body = Text( - "Load failed, please try again", + "获取失败,请重试", style: style, ); } else if (status == LoadStatus.canLoading) { - body = const CupertinoActivityIndicator( + body = const LoadingIndicator( animating: false, ); } else { diff --git a/lib/widgets/refresh_header.dart b/lib/widgets/refresh_header.dart index 944d141..95864a2 100644 --- a/lib/widgets/refresh_header.dart +++ b/lib/widgets/refresh_header.dart @@ -1,6 +1,9 @@ import 'package:flutter/cupertino.dart'; +import 'package:loading_animation_widget/loading_animation_widget.dart'; import 'package:pull_to_refresh/pull_to_refresh.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/loading_indicator.dart'; class RefreshHeader extends RefreshIndicator { RefreshHeader() @@ -39,15 +42,17 @@ class RefreshHeaderState extends RefreshIndicatorState TextStyle style = TextStyle(fontSize: ScreenAdaper.sp(18)); Widget body; if (mode == RefreshStatus.refreshing) { - body = const CupertinoActivityIndicator(); + body = const LoadingIndicator(); } else if (mode == RefreshStatus.canRefresh) { - body = const CupertinoActivityIndicator(animating: false); + body = const LoadingIndicator( + animating: false, + ); } else if (mode == RefreshStatus.completed) { body = const SizedBox(); } else if (mode == RefreshStatus.failed) { - body = Text("No more Data)", style: style); + body = Text("没有更多数据了", style: style); } else { - body = const CupertinoActivityIndicator(); + body = const LoadingIndicator(); } return SizedBox( height: ScreenAdaper.height(40), diff --git a/pubspec.lock b/pubspec.lock index 7f273dd..4484b9a 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -177,6 +177,14 @@ packages: url: "https://pub.dev" source: hosted version: "5.4.1" + dropdown_search: + dependency: "direct main" + description: + name: dropdown_search + sha256: "55106e8290acaa97ed15bea1fdad82c3cf0c248dd410e651f5a8ac6870f783ab" + url: "https://pub.dev" + source: hosted + version: "5.0.6" fake_async: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 856c670..f8872f0 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -57,6 +57,7 @@ dependencies: path: ^1.8.3 path_provider: ^2.1.2 flutter_typeahead: ^5.2.0 + dropdown_search: ^5.0.6 dev_dependencies: