diff --git a/lib/apis/api.dart b/lib/apis/api.dart index bb1fb7c..f759aa3 100644 --- a/lib/apis/api.dart +++ b/lib/apis/api.dart @@ -36,7 +36,23 @@ Future> getProducts(Map params) { // 分页获取原材料出入库列表 Future> getInventoryInout(Map params) { - return DioService.dio.get(Urls.getInventoryInout, + return DioService.dio.get(Urls.inventoryInout, + queryParameters: {'page': 1, 'pageSize': 10, ...(params)}); +} + +// 创建出入库记录 +Future createInventoryInout(Map params) { + return DioService.dio.post(Urls.inventoryInout, data: params); +} + +// 更新出入库记录 +Future updateInventoryInout(int id, Map params) { + return DioService.dio.put('${Urls.inventoryInout}/$id', data: params); +} + +// 分页获取库存列表 +Future> getInventory(Map params) { + return DioService.dio.get(Urls.inventory, queryParameters: {'page': 1, 'pageSize': 10, ...(params)}); } @@ -66,27 +82,16 @@ Future saveUserInfo(Map data) { return DioService.dio.post(Urls.saveUserInfo, data: data); } -Future uploadImg(File file) async { - // DateTime dateTime = DateTime.now(); - // String fileName = file.path.split('/').last; - // String format = fileName.split('.').last; - // int imageTimeName = dateTime.millisecondsSinceEpoch + - // (dateTime.microsecondsSinceEpoch ~/ 1000000); - // String imageName = '$imageTimeName.$format'; - // String host = AppInfoService.to.ossPolicy.host!; - // String dir = AppInfoService.to.ossPolicy.dir!; - // final filePath = await MultipartFile.fromFile(file.path, filename: fileName); - // final formData = FormData.fromMap({ - // 'ossaccessKeyId': AppInfoService.to.ossPolicy.accessKeyId, - // 'policy': AppInfoService.to.ossPolicy.policy, - // 'signature': AppInfoService.to.ossPolicy.signature, - // 'callback': AppInfoService.to.ossPolicy.callback, - // 'key': '$dir/$imageName', - // 'file': filePath, - // }); - - // return DioService.dio.post(host, data: formData); - return {} as Response; +Future uploadImg(File file, + {String? bussinessModule, String? bussinessRecordId}) async { + String fileName = file.path.split('/').last; + final filePath = await MultipartFile.fromFile(file.path, filename: fileName); + final formData = FormData.fromMap({ + 'bussinessRecordId': bussinessRecordId, + 'bussinessModule': bussinessModule, + 'file': filePath, + }); + return DioService.dio.post(Urls.uploadAttachemnt, data: formData); } Future updateAvatar(String filename) { diff --git a/lib/constants/enum.dart b/lib/constants/enum.dart index 29af930..4bbafc6 100644 --- a/lib/constants/enum.dart +++ b/lib/constants/enum.dart @@ -4,10 +4,15 @@ enum BackActionEnum { enum LoginEnum { apple, fastLogin } -enum InventoryInOrOutEnum { In, Out } +class InventoryInOrOutEnum { + static const int In = 0; + static const int Out = 1; +} -// 创建一个映射,将 InventoryInOrOutEnum 值与整数关联起来 -const Map InventoryInOrOutEnumValues = { - InventoryInOrOutEnum.In: 0, - InventoryInOrOutEnum.Out: 1, -}; +class StorageBussinessModuleEnum { + static const Contract = 'contract'; + static const Company = 'company'; + static const MaterialsInOut = 'materialsInOut'; + static const Product = 'product'; + static const Project = 'project'; +} diff --git a/lib/constants/global_url.dart b/lib/constants/global_url.dart index e184a02..993c508 100644 --- a/lib/constants/global_url.dart +++ b/lib/constants/global_url.dart @@ -8,7 +8,9 @@ class Urls { static String getUserInfo = 'account/profile'; static String getProjects = 'project'; static String getProducts = 'product'; - static String getInventoryInout = 'materials-in-out'; + static String inventoryInout = 'materials-in-out'; + static String inventory = 'materials-inventory'; static String updateAvatar = 'user/updateAvatar'; static String getDictType = 'system/dict-type/all'; + static String uploadAttachemnt = 'tools/upload'; } diff --git a/lib/constants/text_enum.dart b/lib/constants/text_enum.dart index de88093..60b5534 100644 --- a/lib/constants/text_enum.dart +++ b/lib/constants/text_enum.dart @@ -4,4 +4,5 @@ class TextEnum { static const String backToTodayButtonText = '今天'; static const String createInventoryInOutBtnText = '提交'; static const String inventoryInOutDialogTitle = '出入库登记'; + static const String serverErrorMsg = ' 服务器繁忙,请重试或者联系管理员'; } diff --git a/lib/models/inventory.model.dart b/lib/models/inventory.model.dart new file mode 100644 index 0000000..586c185 --- /dev/null +++ b/lib/models/inventory.model.dart @@ -0,0 +1,72 @@ +import 'package:sk_base_mobile/models/product.model.dart'; +import 'package:sk_base_mobile/models/project.model.dart'; + +class InventoryModel { + InventoryModel({ + required this.id, + required this.createdAt, + required this.updatedAt, + required this.projectId, + required this.productId, + this.position, + required this.quantity, + required this.unitPrice, + this.remark, + required this.isDelete, + required this.inventoryNumber, + required this.project, + required this.product, + }); + + final int? id; + final DateTime? createdAt; + final DateTime? updatedAt; + final int? projectId; + final int? productId; + final String? position; + final int? quantity; + final String? unitPrice; + final dynamic remark; + final int? isDelete; + final String? inventoryNumber; + final ProjectModel? project; + final ProductModel? product; + + factory InventoryModel.fromJson(Map json) { + return InventoryModel( + id: json["id"], + createdAt: DateTime.tryParse(json["createdAt"] ?? ""), + updatedAt: DateTime.tryParse(json["updatedAt"] ?? ""), + projectId: json["projectId"], + productId: json["productId"], + position: json["position"], + quantity: json["quantity"], + unitPrice: json["unitPrice"], + remark: json["remark"], + isDelete: json["isDelete"], + inventoryNumber: json["inventoryNumber"], + project: json["project"] == null + ? null + : ProjectModel.fromJson(json["project"]), + product: json["product"] == null + ? null + : ProductModel.fromJson(json["product"]), + ); + } + + Map toJson() => { + "id": id, + "createdAt": createdAt?.toIso8601String(), + "updatedAt": updatedAt?.toIso8601String(), + "projectId": projectId, + "productId": productId, + "position": position, + "quantity": quantity, + "unitPrice": unitPrice, + "remark": remark, + "isDelete": isDelete, + "inventoryNumber": inventoryNumber, + "project": project?.toJson(), + "product": product?.toJson(), + }; +} diff --git a/lib/models/upload_result.model.dart b/lib/models/upload_result.model.dart new file mode 100644 index 0000000..1c73aaa --- /dev/null +++ b/lib/models/upload_result.model.dart @@ -0,0 +1,21 @@ +class UploadResultModel { + UploadResultModel({ + required this.path, + required this.id, + }); + + final String? path; + final int? id; + + factory UploadResultModel.fromJson(Map json) { + return UploadResultModel( + path: json["path"], + id: json["id"], + ); + } + + Map toJson() => { + "path": path, + "id": id, + }; +} diff --git a/lib/screens/inventory_inout/components/inventory_inout_card.dart b/lib/screens/inventory_inout/components/inventory_inout_card.dart index 1c7675b..2e69563 100644 --- a/lib/screens/inventory_inout/components/inventory_inout_card.dart +++ b/lib/screens/inventory_inout/components/inventory_inout_card.dart @@ -232,14 +232,14 @@ class InventoryInoutCard extends StatelessWidget { } Widget buildInOrOut() { - final bgColor = controller.list[ind][index].inOrOut == - InventoryInOrOutEnumValues[InventoryInOrOutEnum.In] - ? AppTheme.snackbarSuccessBackgroudColor - : AppTheme.snackbarErrorBackgroudColor; - final textInOut = controller.list[ind][index].inOrOut == - InventoryInOrOutEnumValues[InventoryInOrOutEnum.In] - ? '入' - : '出'; + final bgColor = + controller.list[ind][index].inOrOut == InventoryInOrOutEnum.In + ? AppTheme.snackbarSuccessBackgroudColor + : AppTheme.snackbarErrorBackgroudColor; + final textInOut = + controller.list[ind][index].inOrOut == InventoryInOrOutEnum.In + ? '入' + : '出'; return Container( alignment: Alignment.center, height: ScreenAdaper.height(40), diff --git a/lib/screens/inventory_inout/inventory_inout_controller.dart b/lib/screens/inventory_inout/inventory_inout_controller.dart index 8964f0f..cb72e6e 100644 --- a/lib/screens/inventory_inout/inventory_inout_controller.dart +++ b/lib/screens/inventory_inout/inventory_inout_controller.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:sk_base_mobile/app_theme.dart'; +import 'package:sk_base_mobile/constants/enum.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'; @@ -33,14 +34,12 @@ class InventoryInoutController extends GetxController { @override void onInit() { super.onInit(); - getTasks(); - // getInoutHistory(); + getData(); } Future changePage(int index) async { LoggerUtil() .info(DateUtil.format(DateTime.now().add(Duration(days: -index)))); - } /// 打开出库还是入库选择框 @@ -80,7 +79,8 @@ class InventoryInoutController extends GetxController { child: ElevatedButton( onPressed: () { Get.back(); - showInventoryInoutCreateDialog('in'); + showInventoryInoutCreateDialog( + InventoryInOrOutEnum.In); }, style: ButtonStyle( backgroundColor: MaterialStateProperty.all( @@ -147,7 +147,8 @@ class InventoryInoutController extends GetxController { child: ElevatedButton( onPressed: () { Get.back(); - showInventoryInoutCreateDialog('out'); + showInventoryInoutCreateDialog( + InventoryInOrOutEnum.Out); }, style: ButtonStyle( backgroundColor: MaterialStateProperty.all( @@ -204,15 +205,17 @@ class InventoryInoutController extends GetxController { } /// 打开新建库存出入库对话框 - Future showInventoryInoutCreateDialog(String inOrOut) async { + Future showInventoryInoutCreateDialog(int inOrOut) async { final isTablet = Responsive.isTablet(Get.context!); - isTablet + return isTablet ? Get.generalDialog( barrierLabel: "CreateInventoryInout", barrierDismissible: true, transitionDuration: const Duration(milliseconds: 400), pageBuilder: (_, __, ___) { - return NewInventoryInout(); + return NewInventoryInout( + inOrOut: inOrOut, + ); }, transitionBuilder: (_, anim, __, child) { Tween tween; @@ -224,7 +227,11 @@ class InventoryInoutController extends GetxController { child: child, ); }, - ) + ).then((value) { + if (value != null) { + getData(); + } + }) : showModalBottomSheet( elevation: 0, isScrollControlled: true, @@ -250,7 +257,7 @@ class InventoryInoutController extends GetxController { // return queryResult.map((e) => TaskModel.fromMap(e)).toList(); } - getTasks() async { + getData() async { getInoutHistory().then((value) { model.value = value; getSepretLists(); diff --git a/lib/screens/new_inventory_inout/components/inventory_search.dart b/lib/screens/new_inventory_inout/components/inventory_search.dart new file mode 100644 index 0000000..8c66892 --- /dev/null +++ b/lib/screens/new_inventory_inout/components/inventory_search.dart @@ -0,0 +1,408 @@ +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/constants/bg_color.dart'; +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/apis/api.dart' as Api; + +class InventorySearch extends StatelessWidget { + Function(InventoryModel)? onInventorySelected; + Function(InventoryModel)? beforeSelectedCheck; + InventorySearch( + {super.key, this.onInventorySelected, this.beforeSelectedCheck}); + final controller = Get.put(InventorySearchController()); + final listTitleTextStyle = + TextStyle(fontSize: ScreenAdaper.sp(20), fontWeight: FontWeight.w600); + @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, + ), + Expanded(child: buildInventoryList()) + ], + ), + ); + } + + Widget buildSearchBar() { + final doSearch = debouncer((String value) { + controller.searchKey.value = value; + controller.onRefresh(); + }, delayTime: 500); + + return Row( + children: [ + Expanded( + flex: 5, + child: TextField( + controller: controller.searchBarTextConroller, + 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), + // 当searchBarController有值时不显示 + suffixIcon: Obx(() => controller.searchKey.value.isEmpty + ? const SizedBox() + : IconButton( + icon: const Icon(Icons.clear), + onPressed: () { + controller.searchKey.value = ''; + controller.searchBarTextConroller.clear(); + doSearch(''); + }, + )), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(10), + ), + ))), + SizedBox( + width: ScreenAdaper.width(20), + ), + buildHasInventoryPicker() + ], + ); + } + + Widget buildHasInventoryPicker() { + return Container( + width: ScreenAdaper.width(150), + constraints: BoxConstraints(minWidth: ScreenAdaper.width(150)), + child: Obx( + () => DropdownButtonFormField( + value: controller.hasInventoryStatus.value, + items: [ + DropdownMenuItem( + child: Text('全部'), + value: 0, + ), + DropdownMenuItem( + child: const Text('有库存'), + value: 1, + ), + DropdownMenuItem( + child: const Text('无库存'), + value: 2, + ), + ], + onChanged: (value) { + controller.hasInventoryStatus.value = value!; + controller.onRefresh(); + }, + decoration: InputDecoration( + contentPadding: EdgeInsets.symmetric( + vertical: ScreenAdaper.height(15), + horizontal: ScreenAdaper.width(15)), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(ScreenAdaper.sp(15)), + ), + )), + )); + } + + Widget buildInventoryList() { + final textStyle = TextStyle(fontSize: ScreenAdaper.sp(22)); + return Obx(() => SmartRefresher( + enablePullDown: true, + enablePullUp: true, + controller: controller.refreshController, + onLoading: controller.onLoading, + onRefresh: controller.onRefresh, + child: Table(columnWidths: { + 0: FixedColumnWidth(100), + 1: FlexColumnWidth(2), + 2: MinColumnWidth(FixedColumnWidth(200), FixedColumnWidth(200)), + 3: MinColumnWidth(FixedColumnWidth(100), FixedColumnWidth(100)), + 4: MinColumnWidth(FixedColumnWidth(80), FixedColumnWidth(80)), + }, children: [ + // table header + TableRow( + decoration: BoxDecoration( + border: + Border(bottom: BorderSide(color: AppTheme.dividerColor))), + children: [ + TableCell( + verticalAlignment: TableCellVerticalAlignment.middle, + child: Container( + alignment: Alignment.centerLeft, + height: ScreenAdaper.height(60), + child: Text( + '库存编号', + style: listTitleTextStyle, + ), + )), + TableCell( + verticalAlignment: TableCellVerticalAlignment.middle, + child: Text( + '产品名称', + style: listTitleTextStyle, + )), + TableCell( + verticalAlignment: TableCellVerticalAlignment.middle, + child: Text( + '规格', + style: listTitleTextStyle, + ), + ), + TableCell( + verticalAlignment: TableCellVerticalAlignment.middle, + child: Text( + '单价', + style: listTitleTextStyle, + ), + ), + TableCell( + verticalAlignment: TableCellVerticalAlignment.middle, + child: Text( + '数量', + textAlign: TextAlign.right, + style: listTitleTextStyle, + ), + ), + ]), + ...controller.inventories.map((itemData) { + return TableRow( + decoration: BoxDecoration( + border: Border( + bottom: BorderSide(color: AppTheme.dividerColor))), + children: [ + buildTableCell( + Text( + itemData.inventoryNumber!, + style: textStyle, + ), + itemData: itemData), + buildTableCell( + Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + '${itemData.product?.name}', + style: textStyle, + ), + Text( + '${itemData.product?.company?.name}', + style: TextStyle( + fontSize: ScreenAdaper.sp(15), + color: AppTheme.grey), + ) + ], + ), + itemData: itemData), + // 规格 + buildTableCell( + Text(itemData.product?.productSpecification ?? '', + style: textStyle), + itemData: itemData), + // 单价 + buildTableCell( + Text( + '¥${double.parse('${itemData.unitPrice}')}', + style: textStyle, + ), + itemData: itemData), + + // 库存数量 + buildTableCell( + Text( + '${itemData.quantity}${itemData.product?.unit?.label ?? ''}', + textAlign: TextAlign.right, + style: textStyle), + itemData: itemData, + alignment: Alignment.centerRight), + ]); + }).toList() + ]))); + } + + Widget buildInventoryList1() { + final textStyle = TextStyle(fontSize: ScreenAdaper.sp(22)); + return Obx( + () => SmartRefresher( + enablePullDown: true, + enablePullUp: true, + controller: controller.refreshController, + onLoading: controller.onLoading, + onRefresh: controller.onRefresh, + child: ListView.separated( + itemCount: controller.inventories.length, + separatorBuilder: (context, index) => const Divider( + height: 1, + color: AppTheme.dividerColor, + ), + itemBuilder: (context, index) { + final itemData = controller.inventories[index]; + return InkWell( + onTap: () { + if (onInventorySelected != null) + onInventorySelected!(itemData); + }, + child: Container( + padding: + EdgeInsets.symmetric(vertical: ScreenAdaper.height(10)), + child: Row( + children: [ + Container( + //最小宽度 + constraints: + BoxConstraints(minWidth: ScreenAdaper.width(100)), + child: Text( + itemData.inventoryNumber!, + style: textStyle, + ), + ), + SizedBox( + width: ScreenAdaper.width(10), + ), + Expanded( + flex: 8, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + '${itemData.product?.name}', + style: textStyle, + ), + Text( + '${itemData.product?.company?.name}', + style: TextStyle( + fontSize: ScreenAdaper.sp(15), + color: AppTheme.grey), + ) + ], + )), + SizedBox( + width: ScreenAdaper.width(10), + ), + // 规格 + Expanded( + child: Container( + constraints: + BoxConstraints(minWidth: ScreenAdaper.width(100)), + child: Text( + itemData.product?.productSpecification ?? '', + style: textStyle), + )), + SizedBox( + width: ScreenAdaper.width(10), + ), + // 单价 + Container( + constraints: + BoxConstraints(minWidth: ScreenAdaper.width(100)), + child: Text( + '¥${double.parse('${itemData.unitPrice}')}${double.parse('${itemData.unitPrice}')}${double.parse('${itemData.unitPrice}')}${double.parse('${itemData.unitPrice}')}', + style: textStyle), + ), + SizedBox( + width: ScreenAdaper.width(10), + ), + // 库存数量 + Expanded( + child: Container( + alignment: Alignment.centerRight, + constraints: + BoxConstraints(minWidth: ScreenAdaper.width(80)), + child: Text( + '${itemData.quantity}${itemData.quantity}${itemData.product?.unit?.label ?? ''}', + style: textStyle), + )) + ], + ), + ), + ); + }), + ), + ); + } + + Widget buildTableCell(Widget child, + {required InventoryModel itemData, AlignmentGeometry? alignment}) { + return TableCell( + verticalAlignment: TableCellVerticalAlignment.middle, + child: InkWell( + onTap: () { + if (beforeSelectedCheck != null) { + if (!beforeSelectedCheck!(itemData)) { + return; + } + } + if (onInventorySelected != null) onInventorySelected!(itemData); + }, + child: Container( + constraints: BoxConstraints(minHeight: ScreenAdaper.height(80)), + alignment: alignment ?? Alignment.centerLeft, + child: child), + ), + ); + } +} + +class InventorySearchController extends GetxController { + RxList inventories = RxList([]); + RxString searchKey = ''.obs; + final searchBarTextConroller = TextEditingController(); + RefreshController refreshController = RefreshController(initialRefresh: true); + int page = 1; + int limit = 15; + int total = 0; + RxInt hasInventoryStatus = RxInt(1); + Future> getData({bool isRefresh = false}) async { + if (isRefresh == true) { + page = 1; + } else { + page++; + } + final res = await Api.getInventory({ + 'page': page, + 'pageSize': 15, + 'keyword': searchKey.value, + 'isCreateInout': true, + 'isHasInventory': hasInventoryStatus.value, + }); + List newList = + res.data!.items.map((e) => InventoryModel.fromJson(e)).toList(); + isRefresh == true + ? inventories.assignAll(newList) + : inventories.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/components/product_search.dart b/lib/screens/new_inventory_inout/components/product_search.dart index 331fd32..2f45e22 100644 --- a/lib/screens/new_inventory_inout/components/product_search.dart +++ b/lib/screens/new_inventory_inout/components/product_search.dart @@ -4,14 +4,18 @@ 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/constants/bg_color.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}); + Function(ProductModel)? onProductSelected; + ProductSearch({super.key, this.onProductSelected}); final controller = Get.put(ProductSearchController()); + final listTitleTextStyle = + TextStyle(fontSize: ScreenAdaper.sp(20), fontWeight: FontWeight.w600); @override Widget build(BuildContext context) { return Container( @@ -19,20 +23,46 @@ class ProductSearch extends StatelessWidget { horizontal: ScreenAdaper.width(20), vertical: ScreenAdaper.height(20)), child: Column( - children: [buildSearchBar(), Expanded(child: buildProductList())], + children: [ + buildSearchBar(), + SizedBox( + height: ScreenAdaper.height(defaultPadding) / 2, + ), + Row( + children: [ + Text( + '产品编号', + style: listTitleTextStyle, + ), + Expanded( + child: Text( + '名称', + textAlign: TextAlign.center, + style: listTitleTextStyle, + )), + Text( + '规格', + style: listTitleTextStyle, + ) + ], + ), + Expanded(child: buildProductList()) + ], ), ); } Widget buildSearchBar() { - final doSearch = debouncer((_) { + final doSearch = debouncer((String value) { + controller.searchKey.value = value; controller.onRefresh(); }, delayTime: 500); + return Row( children: [ Expanded( child: TextField( - controller: controller.searchBarController, + controller: controller.searchBarTextConroller, onChanged: (value) => doSearch(value), decoration: InputDecoration( contentPadding: EdgeInsets.symmetric( @@ -41,6 +71,17 @@ class ProductSearch extends StatelessWidget { hintText: '名称,编号', floatingLabelBehavior: FloatingLabelBehavior.always, prefixIcon: const Icon(Icons.search), + // 当searchBarController有值时不显示 + suffixIcon: Obx(() => controller.searchKey.value.isEmpty + ? const SizedBox() + : IconButton( + icon: const Icon(Icons.clear), + onPressed: () { + controller.searchKey.value = ''; + controller.searchBarTextConroller.clear(); + doSearch(''); + }, + )), border: OutlineInputBorder( borderRadius: BorderRadius.circular(10), ), @@ -66,15 +107,22 @@ class ProductSearch extends StatelessWidget { itemBuilder: (context, index) { final itemData = controller.products[index]; return InkWell( - onTap: () {}, + onTap: () { + if (onProductSelected != null) onProductSelected!(itemData); + }, child: Container( padding: EdgeInsets.symmetric(vertical: ScreenAdaper.height(10)), child: Row( children: [ - Text( - itemData.productNumber!, - style: TextStyle(fontSize: ScreenAdaper.sp(20)), + Container( + //最小宽度 + constraints: + BoxConstraints(minWidth: ScreenAdaper.width(100)), + child: Text( + itemData.productNumber!, + style: TextStyle(fontSize: ScreenAdaper.sp(20)), + ), ), SizedBox( width: ScreenAdaper.width(10), @@ -95,6 +143,15 @@ class ProductSearch extends StatelessWidget { ) ], )), + SizedBox( + width: ScreenAdaper.width(10), + ), + Container( + //最小宽度 + constraints: + BoxConstraints(minWidth: ScreenAdaper.width(100)), + child: Text(itemData.productSpecification ?? ''), + ) ], ), ), @@ -107,7 +164,8 @@ class ProductSearch extends StatelessWidget { class ProductSearchController extends GetxController { RxList products = RxList([]); - TextEditingController searchBarController = TextEditingController(text: ''); + RxString searchKey = ''.obs; + final searchBarTextConroller = TextEditingController(); RefreshController refreshController = RefreshController(initialRefresh: true); int page = 1; int limit = 15; @@ -119,7 +177,7 @@ class ProductSearchController extends GetxController { page++; } final res = await Api.getProducts( - {'page': page, 'pageSize': 30, 'keyword': searchBarController.text}); + {'page': page, 'pageSize': 30, 'keyword': searchKey.value}); List newList = res.data!.items.map((e) => ProductModel.fromJson(e)).toList(); isRefresh == true ? products.assignAll(newList) : products.addAll(newList); diff --git a/lib/screens/new_inventory_inout/new_inventory_inout.dart b/lib/screens/new_inventory_inout/new_inventory_inout.dart index 9e6bdcd..4fd82d0 100644 --- a/lib/screens/new_inventory_inout/new_inventory_inout.dart +++ b/lib/screens/new_inventory_inout/new_inventory_inout.dart @@ -1,34 +1,32 @@ 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/models/inventory.model.dart'; +import 'package:sk_base_mobile/screens/new_inventory_inout/components/inventory_search.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/util/util.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'; class NewInventoryInout extends StatelessWidget { - final String inOrOut; - NewInventoryInout({super.key, this.inOrOut = 'in'}); - - final controller = Get.put(NewInventoryInoutController()); + final NewInventoryInoutController controller; final dictService = Get.find(); + final int inOrOut; + NewInventoryInout({Key? key, this.inOrOut = InventoryInOrOutEnum.In}) + : controller = Get.put(NewInventoryInoutController(inOrOut: inOrOut)), + super(key: key); + @override Widget build(BuildContext context) { return SafeArea( @@ -51,6 +49,7 @@ class NewInventoryInout extends StatelessWidget { children: [ buildForm(), Obx(() => GradientButton( + buttonText: TextEnum.createInventoryInOutBtnText, onPressed: () => {controller.create()}, isLoading: controller.loading.value, )) @@ -65,15 +64,15 @@ class NewInventoryInout extends StatelessWidget { final children = [ Row(children: [ Expanded( - flex: 2, - child: buildProjectPicker(), + flex: 3, + child: buildProductPicker(), ), SizedBox( width: ScreenAdaper.width(formHorizontalGap), ), Expanded( - flex: 3, - child: buildProductPicker(), + flex: 2, + child: buildProjectPicker(), ), ]), SizedBox( @@ -82,20 +81,36 @@ class NewInventoryInout extends StatelessWidget { Row( children: [ Expanded(flex: 1, child: buildQuantity()), - SizedBox( - width: ScreenAdaper.width(formHorizontalGap), - ), - Expanded(flex: 1, child: buildDatePicker()), - SizedBox( - width: ScreenAdaper.width(formHorizontalGap), - ), - Expanded(flex: 1, child: buildAgent()), + if (inOrOut == InventoryInOrOutEnum.In) ...[ + SizedBox( + width: ScreenAdaper.width(formHorizontalGap), + ), + Expanded(flex: 1, child: buildUnitPrice()), + SizedBox( + width: ScreenAdaper.width(formHorizontalGap), + ), + Expanded(flex: 1, child: buildAmount()) + ], ], ), SizedBox( height: ScreenAdaper.height(formVerticalGap), ), - buildPositionBottomPicker(), + Row( + children: [ + Expanded(flex: 1, child: buildDatePicker()), + SizedBox( + width: ScreenAdaper.width(formHorizontalGap), + ), + Expanded(flex: 1, child: buildAgent()), + if (inOrOut == InventoryInOrOutEnum.In) ...[ + SizedBox( + width: ScreenAdaper.width(formHorizontalGap), + ), + Expanded(flex: 1, child: buildPositionBottomPicker()), + ] + ], + ), SizedBox( height: ScreenAdaper.height(formVerticalGap), ), @@ -128,6 +143,7 @@ class NewInventoryInout extends StatelessWidget { isRequired: true, textController: controller.projectTextController, labelText: '项目', + hintText: inOrOut == InventoryInOrOutEnum.In ? '项目' : '请选择目标项目', itemBuilder: (_, itemData) => Container( padding: EdgeInsets.symmetric( vertical: ScreenAdaper.height(15), @@ -151,167 +167,71 @@ class NewInventoryInout extends StatelessWidget { /// 产品 Widget buildProductPicker() { return TextFormField( - controller: controller.positionTextController, - decoration: const InputDecoration( - labelText: '产品', + controller: controller.productTextController, + decoration: InputDecoration( + hintText: '请选择产品', + label: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + "*", + style: TextStyle( + color: Colors.red, fontSize: ScreenAdaper.sp(30)), + ), + Text( + '产品', + style: TextStyle(fontSize: ScreenAdaper.sp(30)), + ), + ]), 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), + if (inOrOut == InventoryInOrOutEnum.In) { + ModalUtil.showGeneralDialog(content: ProductSearch( + onProductSelected: (ProductModel product) { + Get.back(); + String productName = + '${product.productNumber} ${product.name!}'; + controller.payload['productId'] = product.id; + if ((product.productSpecification ?? '').isNotEmpty) { + productName += ' (${product.productSpecification})'; + } + controller.productTextController.text = productName; + }, + )); + } else { + ModalUtil.showGeneralDialog( + content: InventorySearch( + beforeSelectedCheck: (InventoryModel itemData) { + final isValid = (itemData.quantity ?? 0) > 0; + if (!isValid) { + SnackBarUtil().warning('库存不足'); + } + return (itemData.quantity ?? 0) > 0; + }, + onInventorySelected: (InventoryModel inventory) { + Get.back(); + controller.payload['inventoryId'] = inventory.id; + String productName = + '${inventory.product!.name!}(¥${double.parse('${inventory.unitPrice}')})(${inventory.product?.company?.name})'; + if ((inventory.product?.productSpecification ?? '') + .isNotEmpty) { + productName += + ' (${inventory.product?.productSpecification})'; + } + controller.projectTextController.text = + inventory.project!.name!; + controller.payload['projectId'] = inventory.project?.id; + controller.quantityTextController.text = + '${inventory.quantity}'; + controller.productTextController.text = productName; + controller.payload['productId'] = inventory.product?.id; + }, ), - child: child, - ); - }, - ); + width: Get.width - ScreenAdaper.width(50)); + } }); - // 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; - // }); } /// 时间 @@ -335,10 +255,35 @@ class NewInventoryInout extends StatelessWidget { return ZtNumberInput( textController: controller.quantityTextController, labelText: '数量', + onChanged: (value) { + controller.calculatePrice('quantity'); + }, isRequired: true, ); } + /// 单价 + Widget buildUnitPrice() { + return ZtNumberInput( + textController: controller.unitPriceTextController, + labelText: '单价', + onChanged: (value) { + controller.calculatePrice('unitPrice'); + }, + ); + } + + /// 金额 + Widget buildAmount() { + return ZtNumberInput( + textController: controller.amountTextController, + labelText: '金额', + onChanged: (value) { + controller.calculatePrice('amount'); + }, + ); + } + /// 经办人 Widget buildAgent() { return ZtTextInput( @@ -405,23 +350,9 @@ class NewInventoryInout extends StatelessWidget { } // , ${secondLevelData[_1].label}, ${thirdtLevelData[_2].label} controller.positionTextController.text = position; + controller.payload['position'] = 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: () {}), - // ); } /// 照片上传 @@ -434,7 +365,7 @@ class NewInventoryInout extends StatelessWidget { child: Column( children: [ Text( - '*请拍照上传产品照片', + '*请拍照上传照片', style: TextStyle( fontSize: ScreenAdaper.sp(25), color: AppTheme.secondPrimaryColor), 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 a484b19..7ac2cf2 100644 --- a/lib/screens/new_inventory_inout/new_inventory_inout_controller.dart +++ b/lib/screens/new_inventory_inout/new_inventory_inout_controller.dart @@ -1,26 +1,22 @@ import 'dart:async'; import 'dart:io'; - +import 'package:decimal/decimal.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:image_picker/image_picker.dart'; +import 'package:sk_base_mobile/constants/enum.dart'; import 'package:sk_base_mobile/db_helper/dbHelper.dart'; -import 'package:sk_base_mobile/models/base_response.dart'; -import 'package:sk_base_mobile/models/product.model.dart'; -import 'package:sk_base_mobile/models/project.model.dart'; -import 'package:sk_base_mobile/models/task_model.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/apis/api.dart' as Api; 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/photo_picker_util.dart'; import 'package:sk_base_mobile/util/snack_bar.util.dart'; class NewInventoryInoutController extends GetxController { final formKey = GlobalKey(); - + final int? inOrOut; DateTime? pickedDate; final DbHelper db = DbHelper(); RxInt selectedImage = 0.obs; @@ -42,12 +38,15 @@ class NewInventoryInoutController extends GetxController { final productTextController = TextEditingController(); final dateTextController = TextEditingController(); final quantityTextController = TextEditingController(); + final unitPriceTextController = TextEditingController(); + final amountTextController = TextEditingController(); final agentTextController = TextEditingController(); final remarkTextController = TextEditingController(); final positionTextController = TextEditingController(); final uploadImgFilesPath = [].obs; final uploadScrollController = ScrollController(); Map payload = {}; + NewInventoryInoutController({this.inOrOut}); Future> getProjects({String? keyword}) async { final res = @@ -73,10 +72,47 @@ class NewInventoryInoutController extends GetxController { onReady() { super.onReady(); dateTextController.text = DateUtil.format(DateTime.now()); + payload['time'] = DateUtil.format(DateTime.now()); + payload['inOrOut'] = inOrOut; + } + + /// 实时计算数量,单价,金额之间的关系 + void calculatePrice(String changedField) { + Decimal quantity = Decimal.fromInt(0); + Decimal unitPrice = Decimal.fromInt(0); + Decimal amount = Decimal.fromInt(0); + if (quantityTextController.text.isNotEmpty) { + quantity = Decimal.parse(quantityTextController.text); + } + if (unitPriceTextController.text.isNotEmpty) { + unitPrice = Decimal.parse(unitPriceTextController.text); + } + if (amountTextController.text.isNotEmpty) { + amount = Decimal.parse(amountTextController.text); + } + if (InventoryInOrOutEnum.Out == inOrOut) { + if (changedField != 'amount') { + Decimal result = unitPrice * quantity; + amountTextController.text = + result != Decimal.zero ? result.toString() : ''; + } + } else { + // 入库一般是先输入总价和数量,然后计算单价 + if (changedField != 'unitPrice' && quantity != Decimal.zero) { + Decimal result = + (amount / quantity).toDecimal(scaleOnInfinitePrecision: 10); + unitPriceTextController.text = + result != Decimal.zero ? result.toString() : ''; + } else if (changedField != 'amount') { + Decimal result = (unitPrice * quantity); + amountTextController.text = + result != Decimal.zero ? result.toString() : ''; + } + } } /// 创建出入库记录 - create() { + Future create() async { if (payload['projectId'] == null) { SnackBarUtil().info( '项目不能为空', @@ -109,7 +145,48 @@ class NewInventoryInoutController extends GetxController { ); return; } - LoggerUtil().info(payload); + if (unitPriceTextController.text.isEmpty) { + payload.remove('unitPrice'); + } else { + payload['unitPrice'] = unitPriceTextController.text; + } + if (amountTextController.text.isEmpty) { + payload.remove('amount'); + } else { + payload['amount'] = amountTextController.text; + } + + payload['remark'] = remarkTextController.text; + loading.value = true; + // uploadImgFilesPath + + try { + // final recordId = res.data as int; + // 批量同时上传 + final uploadRes = await Future.wait(uploadImgFilesPath + .map((filePath) => MediaUtil().uploadImg(File(filePath), + bussinessModule: StorageBussinessModuleEnum.MaterialsInOut, + bussinessRecordId: null)) + .toList()); + List fileIds = []; + if (uploadRes.isNotEmpty) { + fileIds = uploadRes.map((e) => e!.id!).toList(); + } + final response = await Api.createInventoryInout(payload); + // 保存照片 + if (fileIds.isNotEmpty) { + await Api.updateInventoryInout( + response.data, {'fileIds': uploadRes.map((e) => e!.id).toList()}); + } + Get.back(result: true); + SnackBarUtil().success( + '提交成功', + ); + } catch (e) { + LoggerUtil().error(e); + } finally { + loading.value = false; + } } Future photoPicker() async { @@ -250,7 +327,7 @@ class NewInventoryInoutController extends GetxController { // .then((value) { // Duration dif = pickedDate!.difference(DateTime( // DateTime.now().year, DateTime.now().month, DateTime.now().day)); - // inventoryInoutController.list[dif.inDays].add(value); + // inventoryInoutlist[dif.inDays].add(value); // Timer(const Duration(seconds: 1), () { // loading.value = false; // Get.back(); diff --git a/lib/services/dio.service.dart b/lib/services/dio.service.dart index 4e2d0c2..ea64f4b 100644 --- a/lib/services/dio.service.dart +++ b/lib/services/dio.service.dart @@ -1,5 +1,4 @@ import 'dart:convert'; - import 'package:get/get.dart' as Get; import 'package:dio/dio.dart'; import 'package:sk_base_mobile/models/base_response.dart'; @@ -42,9 +41,10 @@ class DioService extends Get.GetxService { } if (e.type == DioExceptionType.connectionTimeout) { // It occurs when url is opened timeout. - await SnackBarUtil().error( - 'The server is busy, please try again.', - ); + await SnackBarUtil().error(TextEnum.serverErrorMsg); + } else if (e.type == DioExceptionType.connectionError) { + // It occurs when url is opened timeout. + await SnackBarUtil().error(TextEnum.serverErrorMsg); } else if (e.type == DioExceptionType.sendTimeout) { // It occurs when url is sent timeout. // await SnackBarUtil().error( @@ -134,18 +134,18 @@ class DioService extends Get.GetxService { return; } if (response.data != null) { - try { - if (response.data['code'] == 200) { - if (GloablConfig.DEBUG) LoggerUtil().info(response.data['data']); - response.data = response.data['data']; - - // 分页数据处理 - if (response.data['meta'] != null && response.data['items'] != null) { - response.data = PaginationData.fromJson(response.data); - } + if (response.data['code'] == 200) { + if (GloablConfig.DEBUG) LoggerUtil().info(response.data['data']); + response.data = response.data['data']; + // 分页数据处理 + if (response.data != null && + response.data is Map && + response.data['meta'] != null && + response.data['items'] != null) { + response.data = PaginationData.fromJson(response.data); } - } catch (e) { - printError(info: e.toString()); + } else { + await SnackBarUtil().error(response.data['message']); } } handler.next(response); diff --git a/lib/util/media_util.dart b/lib/util/media_util.dart index fd9baf1..3dde929 100644 --- a/lib/util/media_util.dart +++ b/lib/util/media_util.dart @@ -2,6 +2,7 @@ import 'dart:io'; import 'package:dio/dio.dart' as Dio; import 'package:sk_base_mobile/apis/api.dart' as Api; import 'package:image_picker/image_picker.dart'; +import 'package:sk_base_mobile/models/upload_result.model.dart'; import 'package:sk_base_mobile/services/service.dart'; class MediaUtil { @@ -35,12 +36,13 @@ class MediaUtil { return pickedFile; } - Future uploadImg(File imgfile) async { + Future uploadImg(File imgfile, + {int? bussinessRecordId, String? bussinessModule}) async { Dio.Response response = await Api.uploadImg(imgfile); - if (response.data != null && response.data['data']['filename'] != null) { - return response.data["data"]["filename"]; + if (response.data != null && response.data['filename'] != null) { + return UploadResultModel.fromJson(response.data["filename"]); } - return ''; + return null; } // // 上传一张照片 diff --git a/lib/util/modal.util.dart b/lib/util/modal.util.dart index 5f7495d..96d8e00 100644 --- a/lib/util/modal.util.dart +++ b/lib/util/modal.util.dart @@ -2,6 +2,7 @@ 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/util/screen_adaper_util.dart'; import 'package:sk_base_mobile/widgets/core/zt_bottomsheet_picker.dart'; class ModalUtil { @@ -65,4 +66,34 @@ class ModalUtil { popupHeight: popupHeight ?? 400); }, ); + + static Future showGeneralDialog( + {required Widget content, double? width}) { + return Get.generalDialog( + barrierLabel: "productPicker", + barrierDismissible: true, + transitionDuration: const Duration(milliseconds: 400), + pageBuilder: (_, __, ___) { + return Center( + child: ClipRRect( + borderRadius: BorderRadius.circular(ScreenAdaper.sp(30)), + child: Material( + child: SizedBox( + height: Get.height - ScreenAdaper.height(150), + width: width ?? Get.width - ScreenAdaper.width(150), + child: content, + )))); + }, + 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, + ); + }, + ); + } } diff --git a/lib/widgets/core/zt_number_input.dart b/lib/widgets/core/zt_number_input.dart index c1569de..c086021 100644 --- a/lib/widgets/core/zt_number_input.dart +++ b/lib/widgets/core/zt_number_input.dart @@ -9,13 +9,16 @@ import 'package:sk_base_mobile/util/screen_adaper_util.dart'; class ZtNumberInput extends StatelessWidget { final TextEditingController textController; final VoidCallback? onTap; + final Function(String)? onChanged; + final bool isRequired; final String labelText; final String? hint; - ZtNumberInput( + const ZtNumberInput( {super.key, required this.textController, this.onTap, + this.onChanged, this.hint, this.isRequired = false, this.labelText = ''}); @@ -26,15 +29,16 @@ class ZtNumberInput extends StatelessWidget { onTapOutside: (event) { FocusScope.of(context).unfocus(); }, + onChanged: onChanged ?? (_) {}, onTap: onTap ?? () {}, textAlign: TextAlign.center, - style: TextStyle(fontSize: ScreenAdaper.sp(30)), keyboardType: TextInputType.number, inputFormatters: [ - FilteringTextInputFormatter.digitsOnly // 限制输入内容只能是数字 + // 限制输入内容只能是数字和小数点不限制位数 + FilteringTextInputFormatter.allow(RegExp(r'^\d+\.?\d{0,10}')), ], decoration: InputDecoration( - contentPadding: EdgeInsets.symmetric(vertical: ScreenAdaper.height(18)), + // contentPadding: EdgeInsets.symmetric(vertical: ScreenAdaper.height(18)), floatingLabelBehavior: FloatingLabelBehavior.always, label: Row( crossAxisAlignment: CrossAxisAlignment.center, diff --git a/lib/widgets/core/zt_search_select.dart b/lib/widgets/core/zt_search_select.dart index 022554c..61a6b14 100644 --- a/lib/widgets/core/zt_search_select.dart +++ b/lib/widgets/core/zt_search_select.dart @@ -10,6 +10,8 @@ class ZtSearchSelect extends StatelessWidget { final void Function(T)? onSelected; final VoidCallback? onClear; final String labelText; + final String hintText; + final bool isRequired; final Widget Function(BuildContext, T) itemBuilder; const ZtSearchSelect( @@ -18,6 +20,7 @@ class ZtSearchSelect extends StatelessWidget { required this.labelText, required this.itemBuilder, required this.suggestionsCallback, + this.hintText = '请选择', this.onSelected, this.onClear, this.isRequired = false}); @@ -61,7 +64,7 @@ class ZtSearchSelect extends StatelessWidget { ) : const SizedBox(), floatingLabelBehavior: FloatingLabelBehavior.always, - hintText: '请选择', + hintText: hintText, label: Row( crossAxisAlignment: CrossAxisAlignment.center, mainAxisSize: MainAxisSize.min, diff --git a/lib/widgets/fade_in_cache_image.dart b/lib/widgets/fade_in_cache_image.dart index 3586b3f..d828e56 100644 --- a/lib/widgets/fade_in_cache_image.dart +++ b/lib/widgets/fade_in_cache_image.dart @@ -42,7 +42,7 @@ class _FadeInCacheImageState extends State { theContext = context; if ((widget.url == null || widget.url == '' || widget.url == 'null')) { - return Container( + return SizedBox( width: widget.width, height: widget.height, child: Icon(Icons.image_not_supported, @@ -51,7 +51,6 @@ class _FadeInCacheImageState extends State { } return buildImg(widget.url); - ; } // Future getImg() { @@ -66,13 +65,15 @@ class _FadeInCacheImageState extends State { height: widget.height, fit: widget.fit, placeholder: (context, url) => Container( - decoration: BoxDecoration(color: AppTheme.grey), - child: CupertinoActivityIndicator(), + decoration: const BoxDecoration(color: AppTheme.grey), + child: const CupertinoActivityIndicator(), + ), + errorWidget: (context, error, stackTrace) => SizedBox( + width: widget.width, + height: widget.height, + child: Icon(Icons.image_not_supported, + size: ScreenAdaper.sp(100), color: AppTheme.grey), ), - errorWidget: (context, error, stackTrace) => Image( - image: AssetImage( - 'assets/images/deer_detail_banner1.jpg', - )), ); } } diff --git a/lib/widgets/my_avatar.dart b/lib/widgets/my_avatar.dart index f7e8ef8..dd8f9d9 100644 --- a/lib/widgets/my_avatar.dart +++ b/lib/widgets/my_avatar.dart @@ -3,6 +3,7 @@ import 'package:sk_base_mobile/apis/api.dart' as Api; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:image_picker/image_picker.dart'; +import 'package:sk_base_mobile/models/upload_result.model.dart'; import 'package:sk_base_mobile/store/auth.store.dart'; import 'package:sk_base_mobile/util/media_util.dart'; import 'package:sk_base_mobile/util/photo_picker_util.dart'; @@ -83,15 +84,15 @@ class MyAvatarController extends GetxController { await PhotoPickerUtil().showPicker(callback: (XFile pickedFile) async { await LoadingUtil.to.show(status: 'Uploading...'); try { - String? filename = await MediaUtil().uploadImg(File(pickedFile.path)); - if (filename.isNotEmpty) { - final res = await Api.updateAvatar(filename); - if (res.data != null) { - SnackBarUtil().success('Update avatar successfully'); - } - } - uploadImgFilePath(pickedFile.path); - Get.back(); + // UploadResultModel? res = await MediaUtil().uploadImg(File(pickedFile.path)); + // if (res.pa.isNotEmpty) { + // final res = await Api.updateAvatar(filename); + // if (res.data != null) { + // SnackBarUtil().success('Update avatar successfully'); + // } + // } + // uploadImgFilePath(pickedFile.path); + // Get.back(); } catch (e) { SnackBarUtil().error('Update avatar failed.'); } finally { diff --git a/pubspec.lock b/pubspec.lock index 4484b9a..e6ec4ea 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -169,6 +169,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.7" + decimal: + dependency: "direct main" + description: + name: decimal + sha256: "24a261d5d5c87e86c7651c417a5dbdf8bcd7080dd592533910e8d0505a279f21" + url: "https://pub.dev" + source: hosted + version: "2.3.3" dio: dependency: "direct main" description: @@ -177,14 +185,6 @@ 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: @@ -744,6 +744,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.0" + rational: + dependency: transitive + description: + name: rational + sha256: ba58e9e18df9abde280e8b10051e4bce85091e41e8e7e411b6cde2e738d357cf + url: "https://pub.dev" + source: hosted + version: "2.2.2" rxdart: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index f8872f0..2870902 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -57,7 +57,7 @@ dependencies: path: ^1.8.3 path_provider: ^2.1.2 flutter_typeahead: ^5.2.0 - dropdown_search: ^5.0.6 + decimal: ^2.3.3 dev_dependencies: