diff --git a/assets/icons/export.svg b/assets/icons/export.svg new file mode 100644 index 0000000..b23df1a --- /dev/null +++ b/assets/icons/export.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/icons/sale_quotation_formula.svg b/assets/icons/sale_quotation_formula.svg new file mode 100644 index 0000000..96e254b --- /dev/null +++ b/assets/icons/sale_quotation_formula.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/icons/sale_quotation_group.svg b/assets/icons/sale_quotation_group.svg new file mode 100644 index 0000000..1048b03 --- /dev/null +++ b/assets/icons/sale_quotation_group.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/icons/sale_quotation_template.svg b/assets/icons/sale_quotation_template.svg new file mode 100644 index 0000000..17afccf --- /dev/null +++ b/assets/icons/sale_quotation_template.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/lib/apis/api.dart b/lib/apis/api.dart index 7ca0889..92100ca 100644 --- a/lib/apis/api.dart +++ b/lib/apis/api.dart @@ -15,6 +15,33 @@ class Api { ); } + /// 删除报价模板列表 + static Future> deleteSaleQuotationTemplate(int id) { + return DioService.dio + .delete('${Urls.saleQuotationTemplate}/$id'); + } + + /// 获取报价模板列表 + static Future> getSaleQuotationTemplate( + {Map? params}) { + return DioService.dio.get(Urls.saleQuotationTemplate, + queryParameters: {'page': 1, 'pageSize': 99, ...(params ?? {})}); + } + + /// 获取报价配件列表 + static Future> getSaleQuotationComponents( + {Map? params}) { + return DioService.dio.get(Urls.saleQuotationComponent, + queryParameters: {'page': 1, 'pageSize': 99, ...(params ?? {})}); + } + + /// 获取报价分组列表 + static Future> getSaleQuotationGroups( + {Map? params}) { + return DioService.dio.get(Urls.saleQuotationGroup, + queryParameters: {'page': 1, 'pageSize': 99, ...(params ?? {})}); + } + /// 获取权限资源 static Future getResources() { return DioService.dio.get(Urls.accountResources); @@ -61,48 +88,59 @@ class Api { queryParameters: {'page': 1, 'pageSize': 10, ...params}); } -// 分页获取产品列表 + /// 分页获取产品列表 static Future> getProducts(Map params) { return DioService.dio.get(Urls.products, queryParameters: {'page': 1, 'pageSize': 10, ...params}); } -// 分页获取原材料出入库列表 + /// 分页获取原材料出入库列表 static Future> getInventoryInout(Map params) { return DioService.dio.get(Urls.inventoryInout, queryParameters: {'page': 1, 'pageSize': 10, ...(params)}); } -// 获取原材料出入库详情 + /// 获取原材料出入库详情 static Future getInventoryInOutInfo(int id) { return DioService.dio.get( '${Urls.inventoryInout}/$id', ); } -// 创建出入库记录 + /// 创建出入库记录 static Future createInventoryInout(Map params) { return DioService.dio.post(Urls.inventoryInout, data: params); } -// 更新出入库记录 + /// 创建报价计算模板 + static Future createSaleQuotationTemplate(Map params) { + return DioService.dio.post(Urls.saleQuotationTemplate, data: params); + } + + /// 更新报价计算模板 + static Future updateSaleQuotationTemplate(int id, Map params) { + return DioService.dio + .put('${Urls.saleQuotationTemplate}/$id', data: params); + } + + /// 更新出入库记录 static Future updateInventoryInout(int id, Map params) { return DioService.dio.put('${Urls.inventoryInout}/$id', data: params); } -// 分页获取库存列表 + /// 分页获取库存列表 static Future> getInventory(Map params) { return DioService.dio.get(Urls.inventory, queryParameters: {'page': 1, 'pageSize': 10, ...(params)}); } -// 分页获取用户 + /// 分页获取用户 static Future> getUsers(Map params) { return DioService.dio.get(Urls.sysUser, queryParameters: {'page': 1, 'pageSize': 10, ...(params)}); } -// 一次性获取所有的字典类型(不分页) + /// 一次性获取所有的字典类型(不分页) static Future getDictTypeAll(Map params) { return DioService.dio.post(Urls.getDictType, data: params); } diff --git a/lib/app_theme.dart b/lib/app_theme.dart index 70506b6..4fd48e0 100644 --- a/lib/app_theme.dart +++ b/lib/app_theme.dart @@ -4,6 +4,7 @@ import 'package:sk_base_mobile/util/screen_adaper_util.dart'; class AppTheme { AppTheme._(); static const Color primaryColor = Color(0xFFC89607); + static const Color onPrimaryColor = Color(0xFFFEFEFE); static const Color primaryColorLight = Color.fromARGB(255, 255, 206, 70); static const Color primaryColorDark = Color.fromARGB(255, 163, 120, 0); static const Color primaryTextColorWithBg = Color(0x00000000); @@ -27,7 +28,7 @@ class AppTheme { static const Color dividerColor = Color.fromARGB(255, 224, 224, 224); static const Color appbarBgColor = AppTheme.primaryColor; static const Color scaffoldBackgroundColor = Color(0XFFe9f0fd); - static const Color inputFillColor = Color(0xfffefefe); + static const Color inputFillColor = Color(0xFFf5f8ff); } final theme = ThemeData( @@ -63,7 +64,7 @@ final theme = ThemeData( ), dialogBackgroundColor: AppTheme.nearlyWhite, colorScheme: ColorScheme.fromSeed( - onPrimary: AppTheme.nearlyWhite, + onPrimary: AppTheme.onPrimaryColor, seedColor: AppTheme.primaryColor, primary: AppTheme.primaryColor), datePickerTheme: DatePickerThemeData( diff --git a/lib/config.dart b/lib/config.dart index 605fa68..02b0257 100644 --- a/lib/config.dart +++ b/lib/config.dart @@ -2,11 +2,11 @@ // Global config class GloablConfig { - static const BASE_URL = "http://10.0.2.2:8001/api/"; - static const OSS_URL = "http://10.0.2.2:8001"; + // static const BASE_URL = "http://10.0.2.2:8001/api/"; + // static const OSS_URL = "http://10.0.2.2:8001"; - // static const BASE_URL = "http://144.123.43.138:3001/api/"; - // static const OSS_URL = "http://144.123.43.138:3001"; + static const BASE_URL = "http://144.123.43.138:3001/api/"; + static const OSS_URL = "http://144.123.43.138:3001"; // static const BASE_URL = "http://192.168.60.220:8001/api/"; // static const OSS_URL = "http://192.168.60.220:8001"; static const DOMAIN_NAME = "山矿通"; diff --git a/lib/constants/global_url.dart b/lib/constants/global_url.dart index 4f4e398..ba443bc 100644 --- a/lib/constants/global_url.dart +++ b/lib/constants/global_url.dart @@ -17,4 +17,9 @@ class Urls { static String accountResources = 'account/menus'; static String depts = 'system/depts'; static String roles = 'system/roles'; + static String saleQuotationGroup = 'sale_quotation/sale_quotation_group'; + static String saleQuotationComponent = + 'sale_quotation/sale_quotation_component'; + static String saleQuotationTemplate = + 'sale_quotation/sale_quotation_template'; } diff --git a/lib/models/base_search_more_controller.dart b/lib/models/base_search_more_controller.dart index b16d35e..2ea7c08 100644 --- a/lib/models/base_search_more_controller.dart +++ b/lib/models/base_search_more_controller.dart @@ -1,19 +1,38 @@ import 'package:flutter/material.dart'; import 'package:get/get_rx/src/rx_types/rx_types.dart'; +import 'package:get/get_state_manager/src/simple/get_controllers.dart'; import 'package:pull_to_refresh/pull_to_refresh.dart'; import 'package:sk_base_mobile/models/base_search_more.model.dart'; -mixin BaseSearchMoreController { +class BaseSearchMoreController + extends GetxController { RxList list = RxList([]); RxString searchKey = ''.obs; final searchBarTextConroller = TextEditingController(); - RefreshController refreshController = RefreshController(initialRefresh: true); + RefreshController refreshController = + RefreshController(initialRefresh: false); int page = 1; int limit = 15; int total = 0; RxList selectedIndex = RxList([]); + final loading = false.obs; getData({bool isRefresh = false}) {} + @override + onReady() { + super.onReady(); + initData(); + } + + initData() async { + loading.value = true; + try { + await getData(isRefresh: true); + } finally { + loading.value = false; + } + } + Future onRefresh() async { await getData(isRefresh: true).then((_) { refreshController.refreshCompleted(resetFooterState: true); diff --git a/lib/models/sale_quotaion_component.model.dart b/lib/models/sale_quotaion_component.model.dart new file mode 100644 index 0000000..67518ec --- /dev/null +++ b/lib/models/sale_quotaion_component.model.dart @@ -0,0 +1,75 @@ +class SaleQuotationComponentModel { + SaleQuotationComponentModel({ + required this.id, + required this.createdAt, + required this.updatedAt, + required this.name, + required this.componentSpecification, + required this.unitId, + required this.unitPrice, + required this.remark, + required this.isDelete, + required this.unit, + }); + + final int? id; + final DateTime? createdAt; + final DateTime? updatedAt; + final String? name; + final String? componentSpecification; + final int? unitId; + final double? unitPrice; + final String? remark; + final int? isDelete; + final Unit? unit; + + factory SaleQuotationComponentModel.fromJson(Map json) { + return SaleQuotationComponentModel( + id: json["id"], + createdAt: DateTime.tryParse(json["createdAt"] ?? ""), + updatedAt: DateTime.tryParse(json["updatedAt"] ?? ""), + name: json["name"], + componentSpecification: json["componentSpecification"], + unitId: json["unitId"], + unitPrice: double.parse(json["unitPrice"]), + remark: json["remark"], + isDelete: json["isDelete"], + unit: json["unit"] == null ? null : Unit.fromJson(json["unit"]), + ); + } + + Map toJson() => { + "id": id, + "createdAt": createdAt?.toIso8601String(), + "updatedAt": updatedAt?.toIso8601String(), + "name": name, + "componentSpecification": componentSpecification, + "unitId": unitId, + "unitPrice": unitPrice, + "remark": remark, + "isDelete": isDelete, + "unit": unit?.toJson(), + }; +} + +class Unit { + Unit({ + required this.id, + required this.label, + }); + + final int? id; + final String? label; + + factory Unit.fromJson(Map json) { + return Unit( + id: json["id"], + label: json["label"], + ); + } + + Map toJson() => { + "id": id, + "label": label, + }; +} diff --git a/lib/models/sale_quotaion_group.model.dart b/lib/models/sale_quotaion_group.model.dart new file mode 100644 index 0000000..a322499 --- /dev/null +++ b/lib/models/sale_quotaion_group.model.dart @@ -0,0 +1,33 @@ +class SaleQuotationGroupModel { + SaleQuotationGroupModel({ + required this.id, + required this.createdAt, + required this.updatedAt, + required this.name, + required this.isDelete, + }); + + final int? id; + final DateTime? createdAt; + final DateTime? updatedAt; + final String? name; + final int? isDelete; + + factory SaleQuotationGroupModel.fromJson(Map json) { + return SaleQuotationGroupModel( + id: json["id"], + createdAt: DateTime.tryParse(json["createdAt"] ?? ""), + updatedAt: DateTime.tryParse(json["updatedAt"] ?? ""), + name: json["name"], + isDelete: json["isDelete"], + ); + } + + Map toJson() => { + "id": id, + "createdAt": createdAt?.toIso8601String(), + "updatedAt": updatedAt?.toIso8601String(), + "name": name, + "isDelete": isDelete, + }; +} diff --git a/lib/models/sale_quotation.model.dart b/lib/models/sale_quotation.model.dart index 2b37b5e..91397e9 100644 --- a/lib/models/sale_quotation.model.dart +++ b/lib/models/sale_quotation.model.dart @@ -26,9 +26,9 @@ class SaleQuotationItemModel extends BaseSearchMoreModel { final String? unit; final String? remark; // 成本 - int cost; + num cost; int quantity; - int amount; + num amount; SaleQuotationItemModel( {required this.name, diff --git a/lib/models/sale_quotation_template.model.dart b/lib/models/sale_quotation_template.model.dart new file mode 100644 index 0000000..02f41e8 --- /dev/null +++ b/lib/models/sale_quotation_template.model.dart @@ -0,0 +1,64 @@ +import 'package:sk_base_mobile/models/sale_quotation.model.dart'; + +class SaleQuotationTemplateModel { + SaleQuotationTemplateModel({ + this.id, + this.name, + required this.template, + this.isDelete, + }); + + final int? id; + final String? name; + final Template template; + final int? isDelete; + + factory SaleQuotationTemplateModel.fromJson(Map json) { + return SaleQuotationTemplateModel( + id: json["id"], + name: json["name"], + template: Template.fromJson(json["template"]), + isDelete: json["isDelete"], + ); + } + + Map toJson() => { + "id": id, + "name": name, + "template": template.toJson(), + "isDelete": isDelete, + }; +} + +class Template { + Template({ + required this.data, + required this.formula, + required this.totalCost, + required this.totalPrice, + }); + + List data; + String formula; + num? totalCost; + num? totalPrice; + + factory Template.fromJson(Map json) { + return Template( + data: json["data"] == null + ? [] + : List.from( + json["data"]!.map((x) => SaleQuotationModel.fromJson(x))), + formula: json["formula"], + totalCost: json["totalCost"], + totalPrice: json["totalPrice"], + ); + } + + Map toJson() => { + "data": data.map((x) => x.toJson()).toList(), + "formula": formula, + "totalCost": totalCost, + "totalPrice": totalPrice, + }; +} diff --git a/lib/models/workbench.model.dart b/lib/models/workbench.model.dart index 4af6efa..6c3537e 100644 --- a/lib/models/workbench.model.dart +++ b/lib/models/workbench.model.dart @@ -1,7 +1,10 @@ +import 'dart:ui'; + class WorkBenchModel { final String title; final String icon; - final String route; + final String? route; + final VoidCallback? onTap; WorkBenchModel( - {required this.title, required this.route, required this.icon}); + {required this.title, required this.icon, this.route, this.onTap}); } diff --git a/lib/router/router.util.dart b/lib/router/router.util.dart index 4112bc1..0043046 100644 --- a/lib/router/router.util.dart +++ b/lib/router/router.util.dart @@ -1,9 +1,15 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; +import 'package:sk_base_mobile/constants/constants.dart'; import 'package:sk_base_mobile/store/resource.store.dart'; import 'package:sk_base_mobile/util/snack_bar.util.dart'; class RouterUtil { + static List hasPermissionRoute = [ + RouteConfig.hrManage, + RouteConfig.inventory, + RouteConfig.saleQuotation, + ]; static Future toNamed(String routeName, {arguments}) async { //关闭键盘 if (Get.context != null) { @@ -12,12 +18,21 @@ class RouterUtil { bool isExsited = ResourceService.to.resources .firstWhereOrNull((element) => element.path == routeName) != null; - - if (!isExsited) { + bool hasPermission = isExsited || !hasPermissionRoute.contains(routeName); + if (!hasPermission) { SnackBarUtil().info('您没有权限,请联系管理员分配权限,后期将隐藏无权限的菜单'); return null; } - return await Get.toNamed(routeName, arguments: arguments); + return Get.toNamed(routeName, arguments: arguments); + } + + /// 返回 + static Future back({T? result}) async { + //关闭键盘 + if (Get.context != null) { + FocusScope.of(Get.context!).requestFocus(FocusNode()); + } + Get.key.currentState?.pop(result); } } diff --git a/lib/screens/hr_manage/components/edit_userinfo.dart b/lib/screens/hr_manage/components/edit_userinfo.dart index 759134c..6818708 100644 --- a/lib/screens/hr_manage/components/edit_userinfo.dart +++ b/lib/screens/hr_manage/components/edit_userinfo.dart @@ -9,6 +9,7 @@ import 'package:sk_base_mobile/constants/bg_color.dart'; import 'package:sk_base_mobile/constants/enum.dart'; import 'package:sk_base_mobile/models/role.model.dart'; import 'package:sk_base_mobile/models/user_info.model.dart'; +import 'package:sk_base_mobile/router/router.util.dart'; import 'package:sk_base_mobile/screens/hr_manage/components/dept_picker.dart'; import 'package:sk_base_mobile/util/loading_util.dart'; import 'package:sk_base_mobile/util/logger_util.dart'; @@ -323,12 +324,13 @@ class EditUserInfoController extends GetxController { } } - await Api.updateUserInfo(userInfo.value!.id!, data); - final resUser = await Api.getUserInfo(userInfo.value!.id!); + await Api.updateUserInfo(userInfo.value.id!, data); + await LoadingUtil.to.dismiss(); + final resUser = await Api.getUserInfo(userInfo.value.id!); if (resUser.data != null) { userInfo.value = UserInfoModel.fromJson(resUser.data); } - Get.back(result: userInfo.value); + await RouterUtil.back(result: userInfo.value); SnackBarUtil().success( '保存成功', ); diff --git a/lib/screens/hr_manage/hr_manage.dart b/lib/screens/hr_manage/hr_manage.dart index b1b1640..3a78711 100644 --- a/lib/screens/hr_manage/hr_manage.dart +++ b/lib/screens/hr_manage/hr_manage.dart @@ -150,6 +150,7 @@ class HrManagePage extends StatelessWidget { Widget buildUserCard(int index) { return SkInk( + color: AppTheme.nearlyWhite, onTap: () { RouterUtil.toNamed(RouteConfig.employeeDetail, arguments: controller.list[index]); diff --git a/lib/screens/inventory_inout/inventory_inout_controller.dart b/lib/screens/inventory_inout/inventory_inout_controller.dart index d14484a..6661769 100644 --- a/lib/screens/inventory_inout/inventory_inout_controller.dart +++ b/lib/screens/inventory_inout/inventory_inout_controller.dart @@ -5,6 +5,7 @@ import 'package:sk_base_mobile/apis/api.dart'; import 'package:sk_base_mobile/app_theme.dart'; import 'package:sk_base_mobile/constants/enum.dart'; import 'package:sk_base_mobile/db_helper/db_help.dart'; +import 'package:sk_base_mobile/router/router.util.dart'; import 'package:sk_base_mobile/screens/inventory_inout/components/inventory_inout_info.dart'; import 'package:sk_base_mobile/screens/new_inventory_inout/new_inventory_inout.dart'; import 'package:sk_base_mobile/util/date.util.dart'; @@ -76,7 +77,7 @@ class InventoryInoutController extends GetxController { ), child: ElevatedButton( onPressed: () { - Get.back(); + RouterUtil.back(); showInventoryInoutCreateDialog( InventoryInOrOutEnum.In); }, @@ -145,7 +146,7 @@ class InventoryInoutController extends GetxController { ), child: ElevatedButton( onPressed: () { - Get.back(); + RouterUtil.back(); showInventoryInoutCreateDialog( InventoryInOrOutEnum.Out); }, diff --git a/lib/screens/new_inventory_inout/new_inventory_inout.dart b/lib/screens/new_inventory_inout/new_inventory_inout.dart index 1a7b261..6d2fe9a 100644 --- a/lib/screens/new_inventory_inout/new_inventory_inout.dart +++ b/lib/screens/new_inventory_inout/new_inventory_inout.dart @@ -8,6 +8,7 @@ import 'package:sk_base_mobile/constants/dict_enum.dart'; import 'package:sk_base_mobile/models/index.dart'; import 'package:sk_base_mobile/models/inventory.model.dart'; import 'package:sk_base_mobile/models/user_info.model.dart'; +import 'package:sk_base_mobile/router/router.util.dart'; import 'package:sk_base_mobile/screens/new_inventory_inout/components/agent_search.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'; @@ -242,7 +243,7 @@ class NewInventoryInout extends StatelessWidget { if (inOrOut == InventoryInOrOutEnum.In) { ModalUtil.showGeneralDialog(content: ProductSearch( onProductSelected: (ProductModel product) { - Get.back(); + RouterUtil.back(); String productName = '${product.productNumber} ${product.name!}'; controller.payload['productId'] = product.id; @@ -263,7 +264,7 @@ class NewInventoryInout extends StatelessWidget { return (itemData.quantity ?? 0) > 0; }, onInventorySelected: (InventoryModel inventory) { - Get.back(); + RouterUtil.back(); controller.payload['inventoryId'] = inventory.id; String productName = '${inventory.product!.name!}(¥${double.parse('${inventory.unitPrice}')})(${inventory.product?.company?.name})'; @@ -364,7 +365,7 @@ class NewInventoryInout extends StatelessWidget { ModalUtil.showGeneralDialog( content: AgentSearch( onSelected: (UserInfoModel userInfo) { - Get.back(); + RouterUtil.back(); controller.payload['agent'] = userInfo.nickname; controller.agentTextController.text = userInfo.nickname!; }, 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 c404798..4431acd 100644 --- a/lib/screens/new_inventory_inout/new_inventory_inout_controller.dart +++ b/lib/screens/new_inventory_inout/new_inventory_inout_controller.dart @@ -8,6 +8,7 @@ import 'package:sk_base_mobile/apis/api.dart'; import 'package:sk_base_mobile/constants/enum.dart'; import 'package:sk_base_mobile/db_helper/db_help.dart'; import 'package:sk_base_mobile/models/index.dart'; +import 'package:sk_base_mobile/router/router.util.dart'; import 'package:sk_base_mobile/screens/inventory_inout/inventory_inout_controller.dart'; import 'package:sk_base_mobile/util/date.util.dart'; import 'package:sk_base_mobile/util/loading_util.dart'; @@ -190,7 +191,7 @@ class NewInventoryInoutController extends GetxController { await Api.updateInventoryInout( response.data, {'fileIds': uploadRes.map((e) => e!.id).toList()}); } - Get.back(result: true); + await RouterUtil.back(result: true); SnackBarUtil().success( '提交成功', ); @@ -238,7 +239,7 @@ class NewInventoryInoutController extends GetxController { // } // } // // uploadImgFilePath(pickedFile.path); - // Get.back(); + // await RouterUtil.back(); // } catch (e) { // SnackBarUtil().error('上传失败,请重试'); // } finally { @@ -354,7 +355,7 @@ class NewInventoryInoutController extends GetxController { // inventoryInoutlist[dif.inDays].add(value); // Timer(const Duration(seconds: 1), () { // loading.value = false; - // Get.back(); + // await RouterUtil.back(); // SnackBarUtil().success( // 'Successful', // message: 'Task is created', diff --git a/lib/screens/sale_quotation/components/sale_quotation_drawer.dart b/lib/screens/sale_quotation/components/sale_quotation_drawer.dart index c70cd9e..0225047 100644 --- a/lib/screens/sale_quotation/components/sale_quotation_drawer.dart +++ b/lib/screens/sale_quotation/components/sale_quotation_drawer.dart @@ -1,16 +1,27 @@ +import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:get/get.dart'; import 'package:sk_base_mobile/app_theme.dart'; +import 'package:sk_base_mobile/models/sale_quotation_template.model.dart'; +import 'package:sk_base_mobile/router/router.util.dart'; +import 'package:sk_base_mobile/screens/sale_quotation/sale_quotation.controller.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/widgets/gradient_button.dart'; +import 'package:sk_base_mobile/util/snack_bar.util.dart'; +import 'package:sk_base_mobile/widgets/core/sk_flat_button.dart'; +import 'package:sk_base_mobile/widgets/core/sk_ink.dart'; +import 'package:sk_base_mobile/widgets/form_item/sk_text_input.dart'; import 'package:sk_base_mobile/widgets/core/sk_appbar.dart'; class SaleQuotationEndDrawer extends StatelessWidget { - const SaleQuotationEndDrawer({super.key}); + final controller = Get.find(); + SaleQuotationEndDrawer({super.key}); @override Widget build(BuildContext context) { return Scaffold( - appBar: SkAppbar(title: '工具栏', hideLeading: true), + appBar: const SkAppbar(title: '', hideLeading: true), body: buildBody(), ); } @@ -18,24 +29,280 @@ class SaleQuotationEndDrawer extends StatelessWidget { Widget buildBody() { return Column( children: [ - ListTile( - title: Text('选择报价模板'), - onTap: () {}, + buildToolbar(), + Expanded( + child: buildTemplatePicker(), ), - Divider( - color: AppTheme.dividerColor, - height: 1, - ), - Spacer(), - GradientButton( + buildAction() + ], + ); + } + + Widget buildAction() { + return Row( + children: [ + Expanded( + child: SkFlatButton( + color: AppTheme.dangerColor, + onPressed: () { + controller.clearWorkbench(); + RouterUtil.back(); + }, + icon: Icon( + Icons.delete, + color: AppTheme.nearlyWhite, + size: ScreenAdaper.height(40), + ), + buttonText: '清空工作区', + )), + Expanded( + child: SkFlatButton( + onPressed: () async { + if (controller.templateName.value != '默认') { + await RouterUtil.back(); + await controller.saveToDatabase(); + } else { + templateNameDialog(); + } + + // final isSuccessed = await controller.saveToDatabase(); + // if (isSuccessed) await RouterUtil.back(); + }, icon: Icon( Icons.save, color: AppTheme.nearlyWhite, size: ScreenAdaper.height(40), ), - buttonText: '保存为模板', - ) + buttonText: '保存模板', + )) ], ); } + + Widget buildToolbar() { + return Container( + decoration: BoxDecoration( + color: AppTheme.nearlyWhite, + borderRadius: BorderRadius.circular(ScreenAdaper.sp(20))), + margin: EdgeInsets.symmetric( + horizontal: ScreenAdaper.height(20), + vertical: ScreenAdaper.height(20)), + child: Column( + children: [ + buildGroupHeader('工具栏'), + Row( + children: controller.menus + .mapIndexed( + (index, item) => Expanded(child: buildItem(index))) + .toList()) + ], + ), + ); + } + + Widget buildGroupHeader(String text) { + return Container( + padding: EdgeInsets.only( + right: ScreenAdaper.width(20), + left: ScreenAdaper.width(20), + top: ScreenAdaper.height(20)), + alignment: Alignment.centerLeft, + child: Text( + text, + style: TextStyle( + fontSize: ScreenAdaper.height(25), color: Colors.grey[600]), + ), + ); + } + + Widget buildItem(int index) { + return SkInk( + onTap: () { + // final route = RouteConfig.getPages + // .map((e) => e.name) + // .firstWhereOrNull((name) => name == controller.menus[index].route); + // if (route != null) { + // } else { + // SnackBarUtil().info('您没有权限,请联系管理员。后期会隐藏没有权限的功能'); + // } + }, + child: Container( + alignment: Alignment.center, + padding: EdgeInsets.only( + top: ScreenAdaper.height(20), bottom: ScreenAdaper.height(20)), + child: Column( + children: [ + SvgPicture.asset( + 'assets/icons/${controller.menus[index].icon}', + width: ScreenAdaper.width(75), + height: ScreenAdaper.width(75), + ), + SizedBox( + height: ScreenAdaper.height(10), + ), + Text( + controller.menus[index].title, + style: TextStyle( + fontSize: ScreenAdaper.height(22), + ), + ) + ], + ), + ), + ); + } + + Widget buildTemplatePicker() { + return Obx(() => Container( + decoration: BoxDecoration( + color: AppTheme.nearlyWhite, + borderRadius: BorderRadius.circular(ScreenAdaper.sp(20))), + margin: EdgeInsets.only( + left: ScreenAdaper.height(20), + right: ScreenAdaper.height(20), + bottom: ScreenAdaper.height(20)), + child: Column( + children: [ + buildGroupHeader('请选择模板'), + SizedBox( + height: ScreenAdaper.height(10), + ), + Expanded( + child: ListView( + padding: + EdgeInsets.symmetric(horizontal: ScreenAdaper.height(20)), + children: controller.templates + .mapIndexed((int index, element) => + buildTemplateItem(element, index)) + .toList(), + )) + ], + ), + )); + } + + Widget buildTemplateItem(SaleQuotationTemplateModel element, int index) { + return SkInk( + onTap: () async { + await Future.delayed(const Duration(milliseconds: 150)); + controller.changeTemplate(element); + await RouterUtil.back(); + }, + margin: EdgeInsets.symmetric( + horizontal: ScreenAdaper.width(10), + vertical: ScreenAdaper.height(10)), + border: Border.all(color: AppTheme.dividerColor), + borderRadius: BorderRadius.circular(ScreenAdaper.sp(15)), + child: Container( + padding: EdgeInsets.symmetric( + vertical: ScreenAdaper.height(20), + horizontal: ScreenAdaper.width(20)), + child: Row(children: [ + Expanded( + child: Text( + '${element.name}', + style: TextStyle(fontSize: ScreenAdaper.height(25)), + )), + SkInk( + onTap: () { + templateNameDialog(title: element.name); + }, + child: Icon( + Icons.edit, + color: Colors.grey[600], + size: ScreenAdaper.height(40), + ), + ), + SizedBox( + width: ScreenAdaper.width(10), + ), + SkInk( + color: Colors.transparent, + onTap: () { + ModalUtil.alert( + title: '删除模板', + content: Text( + '确定删除模板${element.name}吗?', + style: TextStyle(fontSize: ScreenAdaper.height(30)), + ), + onConfirm: () async { + await controller.deleteTemplate(element.id!); + controller.templates.removeAt(index); + }); + }, + child: Icon( + Icons.delete, + color: Colors.grey[600], + size: ScreenAdaper.height(40), + ), + ), + ]), + ), + ); + } + + void templateNameDialog({String? title = ''}) { + final textController = TextEditingController(text: title); + Get.dialog(AlertDialog( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(ScreenAdaper.sp(20)), + ), + contentPadding: EdgeInsets.only( + top: ScreenAdaper.height(10), + right: ScreenAdaper.height(20), + left: ScreenAdaper.height(20)), + content: Column(mainAxisSize: MainAxisSize.min, children: [ + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + SkInk( + onTap: () { + RouterUtil.back(); + }, + child: const Icon(Icons.close)) + ], + ), + SkTextInput( + height: ScreenAdaper.height(100), + textController: textController, + customLabel: true, + labelText: '模板名称', + ), + Row( + children: [ + Expanded( + child: TextButton( + onPressed: () { + RouterUtil.back(); + }, + child: const Text( + '取消', + style: TextStyle(color: AppTheme.nearlyBlack), + ), + ), + ), + Expanded( + child: TextButton( + onPressed: () async { + if (textController.text.isEmpty) { + SnackBarUtil().error('模板名不能为空'); + return; + } + controller.templateName.value = textController.text; + await RouterUtil.back(); + await controller.saveToLocal(); + await controller.saveToDatabase(); + await controller.getTemplates(); + }, + child: const Text('确定'), + )) + ], + ) + ]), + )); + } } + +// class SaleQuotationDrawerController extends GetxController{ + +// } \ No newline at end of file diff --git a/lib/screens/sale_quotation/components/sale_quotation_group_search.dart b/lib/screens/sale_quotation/components/sale_quotation_group_search.dart index 36f7a0f..ef35c2e 100644 --- a/lib/screens/sale_quotation/components/sale_quotation_group_search.dart +++ b/lib/screens/sale_quotation/components/sale_quotation_group_search.dart @@ -111,34 +111,10 @@ class SaleQuotationGroupSearch extends StatelessWidget { vertical: ScreenAdaper.height(10)), child: Row( children: [ - // ClipRRect( - // borderRadius: BorderRadius.circular(ScreenAdaper.sp(15)), - // child: FadeInCacheImage( - // url: controller.list[index].avatar ?? '', - // width: ScreenAdaper.width(60), - // height: ScreenAdaper.width(60), - // ), - // ), - // SizedBox( - // width: ScreenAdaper.width(20), - // ), Text( - '${controller.list[index].name}', + controller.list[index].name, style: TextStyle(fontSize: ScreenAdaper.height(25)), ), - // if (controller.list[index].dept != null) ...[ - // SizedBox( - // width: ScreenAdaper.width(20), - // ), - // Container( - // padding: EdgeInsets.symmetric( - // horizontal: ScreenAdaper.width(10), - // vertical: ScreenAdaper.height(5)), - // decoration: BoxDecoration( - // color: Colors.grey[300], - // borderRadius: BorderRadius.circular(ScreenAdaper.sp(10))), - // child: Text('${controller.list[index].dept?.name}'), - // ) ], ), ), diff --git a/lib/screens/sale_quotation/sale_quotation.controller.dart b/lib/screens/sale_quotation/sale_quotation.controller.dart index c4ba64b..468f707 100644 --- a/lib/screens/sale_quotation/sale_quotation.controller.dart +++ b/lib/screens/sale_quotation/sale_quotation.controller.dart @@ -4,10 +4,16 @@ import 'package:decimal/decimal.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:math_expressions/math_expressions.dart'; +import 'package:sk_base_mobile/apis/api.dart'; import 'package:sk_base_mobile/models/base_search_more_controller.dart'; +import 'package:sk_base_mobile/models/sale_quotaion_component.model.dart'; +import 'package:sk_base_mobile/models/sale_quotaion_group.model.dart'; import 'package:sk_base_mobile/models/sale_quotation.model.dart'; +import 'package:sk_base_mobile/models/sale_quotation_template.model.dart'; +import 'package:sk_base_mobile/models/workbench.model.dart'; import 'package:sk_base_mobile/screens/sale_quotation/components/sale_quotation_group_search.dart'; import 'package:sk_base_mobile/services/storage.service.dart'; +import 'package:sk_base_mobile/util/snack_bar.util.dart'; import 'package:sk_base_mobile/widgets/form_item/sk_multi_search_more.dart'; import 'package:sk_base_mobile/util/modal.util.dart'; import 'package:sk_base_mobile/util/screen_adaper_util.dart'; @@ -17,39 +23,21 @@ class SaleQuotationController extends GetxController { static SaleQuotationController get to => Get.find(); final RxList editingcell = RxList([null, null, null]); final GlobalKey scaffoldKey = GlobalKey(); - RxList products = RxList([ - SaleQuotationItemModel( - name: '矿用本安型支架控制器', unit: '台', spec: 'ZDYZ-Z', cost: 4700), - SaleQuotationItemModel(name: '矿用本安型电磁阀驱动器', cost: 1200), - SaleQuotationItemModel(name: '矿用隔爆兼本安型电源', cost: 5700), - SaleQuotationItemModel(name: '矿用本安型隔离耦合器', cost: 1200), - SaleQuotationItemModel(name: '钢丝编织橡胶护套连接器', cost: 600), - SaleQuotationItemModel(name: '钢丝编织橡胶护套连接器', remark: '控制器-控制器', cost: 400), - SaleQuotationItemModel(name: '钢丝编织橡胶护套连接器', remark: '控制器-驱动器', cost: 500), - SaleQuotationItemModel( - name: '钢丝编织橡胶护套连接器', remark: '控制器-隔离耦合器', cost: 2000), - SaleQuotationItemModel(name: '矿用本安型支架控制器', cost: 4700), - SaleQuotationItemModel(name: '矿用本安型电磁阀驱动器', cost: 1200), - SaleQuotationItemModel(name: '矿用隔爆兼本安型电源', cost: 5700), - SaleQuotationItemModel( - name: '电液换向阀(10功能10接口)', remark: '中间过渡架主阀组', cost: 13200), - SaleQuotationItemModel( - name: '电液换向阀(20功能20接口)', remark: '端头架主阀组', cost: 26500), - SaleQuotationItemModel( - name: '自动反冲洗过滤装置', remark: '流量:900L/min,过滤精度25μm', cost: 2000), - SaleQuotationItemModel(name: '全自动反冲洗过滤器电缆', remark: '控制器-自动反冲洗'), - SaleQuotationItemModel(name: '矿用本安型位移传感器'), - SaleQuotationItemModel(name: '矿用本安型压力传感器'), - SaleQuotationItemModel(name: '矿用本安型红外发射器'), - SaleQuotationItemModel(name: '矿用本安型LED信号灯'), - SaleQuotationItemModel(name: '倾角传感器'), - SaleQuotationItemModel(name: '各类安装附件'), - ]); RxList groups = RxList([]); - RxInt totalCost = RxInt(0); + RxDouble totalCost = RxDouble(0.0); RxDouble totalPrice = RxDouble(0.0); RxBool isFormulaEditing = false.obs; RxString formula = '成本 * 1.3 / 0.864'.obs; + RxList templates = RxList([]); + final List menus = [ + WorkBenchModel(title: '导出明细', icon: 'export.svg'), + // WorkBenchModel(title: '模板', icon: 'sale_quotation_template.svg'), + WorkBenchModel(title: '配件管理', icon: 'product.svg'), + WorkBenchModel(title: '分组管理', icon: 'sale_quotation_group.svg'), + WorkBenchModel(title: '计算公式', icon: 'sale_quotation_formula.svg'), + ]; + RxString templateName = '默认'.obs; + int? templateId; @override void onReady() { init(); @@ -59,18 +47,43 @@ class SaleQuotationController extends GetxController { Future init() async { String? salesQuotation = StorageService.to.getString('salesQuotation'); if (salesQuotation != null) { - groups.assignAll((jsonDecode(salesQuotation) as List) - .map((e) => SaleQuotationModel.fromJson(e)) - .toList()); - calculateTotal(); + SaleQuotationTemplateModel editTemplate = + SaleQuotationTemplateModel.fromJson(jsonDecode(salesQuotation)); + parseTemplateModel(editTemplate); } + await getTemplates(); } + void parseTemplateModel(SaleQuotationTemplateModel editTemplate) { + groups.assignAll(editTemplate.template.data); + templateName.value = editTemplate.name ?? ''; + formula.value = editTemplate.template.formula; + totalPrice.value = editTemplate.template.totalPrice?.toDouble() ?? 0.0; + totalCost.value = editTemplate.template.totalCost?.toDouble() ?? 0.0; + templateId = editTemplate.id; + } + + /// 切换报价模板 + void changeTemplate(SaleQuotationTemplateModel templateModel) { + parseTemplateModel(templateModel); + saveToLocal(); + } + + /// 获取报价模板 + Future getTemplates() async { + final res = await Api.getSaleQuotationTemplate(); + List newList = res.data!.items + .map((e) => SaleQuotationTemplateModel.fromJson(e)) + .toList(); + templates.assignAll(newList); + } + + /// 实时计算总数 void calculateTotal({String? newFormula}) { //计算groups中所有items中的amout总和 - totalCost.value = groups.fold(0, (previousValue, element) { + totalCost.value = groups.fold(0, (previousValue, element) { return previousValue + - element.items.fold(0, (previousValue, element) { + element.items.fold(0, (previousValue, element) { return previousValue + element.amount; }); }); @@ -100,39 +113,87 @@ class SaleQuotationController extends GetxController { // 入库一般是先输入单价和数量,然后计算单价 if (changedField != 'amount') { Decimal result = cost * quantity; - data.amount = result != Decimal.zero ? result.toBigInt().toInt() : 0; + data.amount = result != Decimal.zero ? result.toDouble() : 0; } if (changedField == 'amount' && quantity != Decimal.zero) { Decimal result = (amount / quantity).toDecimal(scaleOnInfinitePrecision: 10); - data.cost = result != Decimal.zero ? result.toBigInt().toInt() : 0; + data.cost = result != Decimal.zero ? result.toDouble() : 0.0; } else if (changedField != 'amount') { Decimal result = (cost * quantity); - data.amount = result != Decimal.zero ? result.toBigInt().toInt() : 0; + data.amount = result != Decimal.zero ? result.toDouble() : 0; } return data; } - void saveChanges(int groupIndex, int rowIndex, SaleQuotationItemModel data, - String changedField) { + /// 处理行数据变化 + void afterRowChanges(int groupIndex, int rowIndex, + SaleQuotationItemModel data, String changedField) { data = calculateRow(data, changedField); groups[groupIndex].items[rowIndex] = data; calculateTotal(); stopEditing(); } + /// 停止编辑 void stopEditing() { editingcell.assignAll([null, null, null]); - save(); + saveToLocal(); } - Future save() async { - await StorageService.to - .setString('salesQuotation', jsonEncode(groups.toJson())); - // SnackBarUtil().success('已保存到本地'); + /// 保存到本地持久化 + Future saveToLocal() async { + Map data = { + 'name': templateName.value, + 'template': { + 'data': groups.map((e) => e.toJson()).toList(), + 'totalCost': totalCost.value, + 'totalPrice': totalPrice.value, + 'formula': formula.value + } + }; + if (templateId != null) { + data['id'] = templateId; + } + await StorageService.to.setString('salesQuotation', jsonEncode(data)); } + /// 保存到数据库 + Future saveToDatabase() async { + if (templateId == null) { + await Api.createSaleQuotationTemplate({ + 'name': templateName.value, + 'template': { + 'data': groups.toJson(), + 'totalCost': totalCost.value, + 'totalPrice': totalPrice.value, + 'formula': formula.value + } + }); + } else { + await Api.updateSaleQuotationTemplate(templateId!, { + 'name': templateName.value, + 'template': { + 'data': groups.toJson(), + 'totalCost': totalCost.value, + 'totalPrice': totalPrice.value, + 'formula': formula.value + } + }); + } + await getTemplates(); + await SnackBarUtil().success('已保存'); + return true; + } + + /// 删除模板 + Future deleteTemplate(int templateId) async { + await Api.deleteSaleQuotationTemplate(templateId); + SnackBarUtil().success('已删除'); + } + + /// 添加分组 void addGroup() async { final controller = Get.put(GroupSearchMoreController()); // 选择组件 选择分组 @@ -145,7 +206,7 @@ class SaleQuotationController extends GetxController { groups.addAll(controller.list.where((element) { return indexes.contains(controller.list.indexOf(element)); })); - save(); + saveToLocal(); }, leadingBuilder: (index) { return Container( @@ -168,15 +229,16 @@ class SaleQuotationController extends GetxController { calculateTotal(); } + /// 移除分组 void removeGroup(int index) { groups.removeAt(index); calculateTotal(); - save(); + saveToLocal(); } void addItems(int groupIndex) async { final controller = Get.put(ItemSearchMoreController()); - // 选择产品 + // 选择配件 ModalUtil.showGeneralDialog( content: SkMutilSearchMore( controller: controller, @@ -184,10 +246,12 @@ class SaleQuotationController extends GetxController { enablePullDown: true, isDialog: true, onOk: (List indexes) { - groups[groupIndex].items.addAll(products.where((element) { - return indexes.contains(products.indexOf(element)); + groups[groupIndex] + .items + .addAll(controller.list.where((element) { + return indexes.contains(controller.list.indexOf(element)); })); - save(); + saveToLocal(); }, leadingBuilder: (index) { return Container( @@ -250,22 +314,41 @@ class SaleQuotationController extends GetxController { groups[groupIndex].items.removeAt(rowIndex); calculateTotal(); } + + void clearWorkbench() { + groups.value = []; + totalCost.value = 0.0; + totalPrice.value = 0.0; + formula.value = '成本 * 1.3 / 0.864'; + templateName.value = '默认'; + templateId = null; + saveToLocal(); + } } -class GroupSearchMoreController extends GetxController - with BaseSearchMoreController { +class GroupSearchMoreController + extends BaseSearchMoreController { @override Future> getData({bool isRefresh = false}) async { - List newList = [ - SaleQuotationModel(name: '中间过渡架电控部分', items: RxList([])), - SaleQuotationModel(name: '端头架电控部分', items: RxList([])), - SaleQuotationModel(name: '主阀部分', items: RxList([])), - SaleQuotationModel(name: '自动反冲洗过滤器部分', items: RxList([])), - SaleQuotationModel(name: '位移测量部分', items: RxList([])), - SaleQuotationModel(name: '压力检测部分', items: RxList([])), - SaleQuotationModel(name: '煤机定位部分', items: RxList([])), - SaleQuotationModel(name: '姿态检测部分', items: RxList([])), - ]; + await Future.delayed(const Duration(seconds: 1)); + final res = await Api.getSaleQuotationGroups(); + List groups = res.data!.items + .map((e) => SaleQuotationGroupModel.fromJson(e)) + .toList(); + + List newList = groups + .map((e) => SaleQuotationModel(name: e.name!, items: RxList([]))) + .toList(); + // List newList = [ + // SaleQuotationModel(name: '中间过渡架电控部分', items: RxList([])), + // SaleQuotationModel(name: '端头架电控部分', items: RxList([])), + // SaleQuotationModel(name: '主阀部分', items: RxList([])), + // SaleQuotationModel(name: '自动反冲洗过滤器部分', items: RxList([])), + // SaleQuotationModel(name: '位移测量部分', items: RxList([])), + // SaleQuotationModel(name: '压力检测部分', items: RxList([])), + // SaleQuotationModel(name: '煤机定位部分', items: RxList([])), + // SaleQuotationModel(name: '姿态检测部分', items: RxList([])), + // ]; list.assignAll(newList .where((element) => PinyinHelper.getPinyin(element.name, separator: '') .contains(searchKey.value)) @@ -274,42 +357,58 @@ class GroupSearchMoreController extends GetxController } } -class ItemSearchMoreController extends GetxController - with BaseSearchMoreController { +class ItemSearchMoreController + extends BaseSearchMoreController { @override Future> getData({bool isRefresh = false}) async { - List newList = [ - SaleQuotationItemModel( - name: '矿用本安型支架控制器', unit: '台', spec: 'ZDYZ-Z', cost: 4700), - SaleQuotationItemModel(name: '矿用本安型电磁阀驱动器', cost: 1200), - SaleQuotationItemModel(name: '矿用隔爆兼本安型电源', cost: 5700), - SaleQuotationItemModel(name: '矿用本安型隔离耦合器', cost: 1200), - SaleQuotationItemModel(name: '钢丝编织橡胶护套连接器', cost: 600), - SaleQuotationItemModel(name: '钢丝编织橡胶护套连接器', remark: '控制器-控制器', cost: 400), - SaleQuotationItemModel(name: '钢丝编织橡胶护套连接器', remark: '控制器-驱动器', cost: 500), - SaleQuotationItemModel( - name: '钢丝编织橡胶护套连接器', remark: '控制器-隔离耦合器', cost: 2000), - SaleQuotationItemModel(name: '矿用本安型支架控制器', cost: 4700), - SaleQuotationItemModel(name: '矿用本安型电磁阀驱动器', cost: 1200), - SaleQuotationItemModel(name: '矿用隔爆兼本安型电源', cost: 5700), - SaleQuotationItemModel( - name: '电液换向阀(10功能10接口)', remark: '中间过渡架主阀组', cost: 13200), - SaleQuotationItemModel( - name: '电液换向阀(20功能20接口)', remark: '端头架主阀组', cost: 26500), - SaleQuotationItemModel( - name: '自动反冲洗过滤装置', remark: '流量:900L/min,过滤精度25μm', cost: 2000), - SaleQuotationItemModel(name: '全自动反冲洗过滤器电缆', remark: '控制器-自动反冲洗'), - SaleQuotationItemModel(name: '矿用本安型位移传感器'), - SaleQuotationItemModel(name: '矿用本安型压力传感器'), - SaleQuotationItemModel(name: '矿用本安型红外发射器'), - SaleQuotationItemModel(name: '矿用本安型LED信号灯'), - SaleQuotationItemModel(name: '倾角传感器'), - SaleQuotationItemModel(name: '各类安装附件'), - ]; - list.assignAll(newList - .where((element) => PinyinHelper.getPinyin(element.name, separator: '') - .contains(searchKey.value)) - .toList()); - return newList; + try { + final res = await Api.getSaleQuotationComponents(); + List componets = res.data!.items + .map((e) => SaleQuotationComponentModel.fromJson(e)) + .toList(); + List newList = componets + .map((e) => SaleQuotationItemModel( + name: e.name!, + cost: e.unitPrice ?? 0.0, + unit: e.unit?.label ?? '', + remark: e.remark)) + .toList(); + // List newList = [ + // SaleQuotationItemModel( + // name: '矿用本安型支架控制器', unit: '台', spec: 'ZDYZ-Z', cost: 4700), + // SaleQuotationItemModel(name: '矿用本安型电磁阀驱动器', cost: 1200), + // SaleQuotationItemModel(name: '矿用隔爆兼本安型电源', cost: 5700), + // SaleQuotationItemModel(name: '矿用本安型隔离耦合器', cost: 1200), + // SaleQuotationItemModel(name: '钢丝编织橡胶护套连接器', cost: 600), + // SaleQuotationItemModel(name: '钢丝编织橡胶护套连接器', remark: '控制器-控制器', cost: 400), + // SaleQuotationItemModel(name: '钢丝编织橡胶护套连接器', remark: '控制器-驱动器', cost: 500), + // SaleQuotationItemModel( + // name: '钢丝编织橡胶护套连接器', remark: '控制器-隔离耦合器', cost: 2000), + // SaleQuotationItemModel(name: '矿用本安型支架控制器', cost: 4700), + // SaleQuotationItemModel(name: '矿用本安型电磁阀驱动器', cost: 1200), + // SaleQuotationItemModel(name: '矿用隔爆兼本安型电源', cost: 5700), + // SaleQuotationItemModel( + // name: '电液换向阀(10功能10接口)', remark: '中间过渡架主阀组', cost: 13200), + // SaleQuotationItemModel( + // name: '电液换向阀(20功能20接口)', remark: '端头架主阀组', cost: 26500), + // SaleQuotationItemModel( + // name: '自动反冲洗过滤装置', remark: '流量:900L/min,过滤精度25μm', cost: 2000), + // SaleQuotationItemModel(name: '全自动反冲洗过滤器电缆', remark: '控制器-自动反冲洗'), + // SaleQuotationItemModel(name: '矿用本安型位移传感器'), + // SaleQuotationItemModel(name: '矿用本安型压力传感器'), + // SaleQuotationItemModel(name: '矿用本安型红外发射器'), + // SaleQuotationItemModel(name: '矿用本安型LED信号灯'), + // SaleQuotationItemModel(name: '倾角传感器'), + // SaleQuotationItemModel(name: '各类安装附件'), + // ]; + list.assignAll(newList + .where((element) => + PinyinHelper.getPinyin(element.name, separator: '') + .contains(searchKey.value)) + .toList()); + return newList; + } catch (e) { + return []; + } } } diff --git a/lib/screens/sale_quotation/sale_quotation.dart b/lib/screens/sale_quotation/sale_quotation.dart index 4e82895..c3047ef 100644 --- a/lib/screens/sale_quotation/sale_quotation.dart +++ b/lib/screens/sale_quotation/sale_quotation.dart @@ -6,12 +6,11 @@ import 'package:sk_base_mobile/app_theme.dart'; import 'package:sk_base_mobile/models/sale_quotation.model.dart'; import 'package:sk_base_mobile/screens/sale_quotation/components/sale_quotation_drawer.dart'; import 'package:sk_base_mobile/screens/sale_quotation/sale_quotation.controller.dart'; +import 'package:sk_base_mobile/util/common.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/util/snack_bar.util.dart'; import 'package:sk_base_mobile/widgets/form_item/sk_number_input.dart'; import 'package:sk_base_mobile/widgets/form_item/sk_text_input.dart'; -import 'package:sk_base_mobile/widgets/empty.dart'; import 'package:sk_base_mobile/widgets/core/sk_appbar.dart'; import 'package:flutter_sticky_header/flutter_sticky_header.dart'; import 'package:math_expressions/math_expressions.dart'; @@ -22,93 +21,75 @@ class SaleQuotationPage extends StatelessWidget { final quantityWidth = 140.0; final unitPriceWidth = 140.0; final amountWidth = 140.0; - final headerTitleStyle = TextStyle( + final TextStyle? headerTitleStyle = TextStyle( fontSize: ScreenAdaper.height(30), fontWeight: FontWeight.w600, - color: AppTheme.nearlyBlack); - final headerBgcolor = const Color.fromARGB(255, 238, 238, 238); + color: Theme.of(Get.context!).colorScheme.onPrimary); + final headerBgcolor = AppTheme.appbarBgColor; @override Widget build(BuildContext context) { - return Scaffold( - key: controller.scaffoldKey, - endDrawer: const Drawer( - // 从右到左出现 - child: SaleQuotationEndDrawer(), - ), - appBar: SkAppbar( - title: '报价计算', - action: [ - IconButton( - onPressed: () { - controller.scaffoldKey.currentState?.openEndDrawer(); - }, - icon: const Icon(Icons.more_horiz_outlined, color: AppTheme.white), + return Obx(() => Scaffold( + key: controller.scaffoldKey, + backgroundColor: AppTheme.nearlyWhite, + endDrawer: Drawer( + // 从右到左出现 + child: SaleQuotationEndDrawer(), ), - Container( - padding: EdgeInsets.symmetric( - vertical: ScreenAdaper.height(10), - horizontal: ScreenAdaper.width(20)), - child: Row(mainAxisAlignment: MainAxisAlignment.end, children: [ - GestureDetector( - onTap: () { - controller.addGroup(); + appBar: SkAppbar( + title: '报价计算-${controller.templateName}', + action: [ + IconButton( + onPressed: () { + controller.scaffoldKey.currentState?.openEndDrawer(); }, - child: Icon( - Icons.add, - size: ScreenAdaper.height(40), - ), - ) - ]), - ), - ], - ), - body: SafeArea( - bottom: ScreenAdaper.isLandspace() ? false : true, - child: Stack(children: [ - Column( - children: [ - builderHeader(), - Expanded( - child: Obx(() => controller.groups.isEmpty - ? Empty(text: '请先添加分组') - : CustomScrollView( - slivers: controller.groups - .mapIndexed( - (index, e) => buildBody(index)) - .toList(), - ))), - // 当键盘弹起时,不显示 - - SizedBox( - height: ScreenAdaper.height(100), - child: buildTotalCostRow(), + icon: const Icon(Icons.more_horiz_outlined, + color: AppTheme.white), ), - SizedBox( - height: ScreenAdaper.height(100), - child: buildTotalSalesPriceRow(), - ) ], ), - // Positioned( - // bottom: ScreenAdaper.height(220), - // right: ScreenAdaper.height(10), - // child: IconButton( - // padding: EdgeInsets.all(ScreenAdaper.height(20)), - // style: ButtonStyle( - // backgroundColor: MaterialStateProperty.all( - // AppTheme.primaryColorLight)), - // onPressed: () { - // controller.addGroup(); - // }, - // icon: Icon( - // Icons.add, - // size: ScreenAdaper.height(40), - // ), - // )) - ]), - ), - ); + body: SafeArea( + bottom: ScreenAdaper.isLandspace() ? false : true, + child: Stack(children: [ + Column( + children: [ + builderHeader(), + Expanded( + child: Obx(() => controller.groups.isEmpty + ? Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + '请右上角选择模板', + style: TextStyle( + fontSize: ScreenAdaper.height(30)), + ), + Text('或者左上角加号添加分组', + style: TextStyle( + fontSize: ScreenAdaper.height(30))) + ], + ) + : CustomScrollView( + slivers: controller.groups + .mapIndexed( + (index, e) => buildBody(index)) + .toList(), + ))), + // 当键盘弹起时,不显示 + + SizedBox( + height: ScreenAdaper.height(100), + child: buildTotalCostRow(), + ), + SizedBox( + height: ScreenAdaper.height(100), + child: buildTotalSalesPriceRow(), + ) + ], + ), + ]), + ), + )); } Widget buildTotalSalesPriceRow() { @@ -254,6 +235,19 @@ class SaleQuotationPage extends StatelessWidget { bottom: ScreenAdaper.height(10)), child: Row( children: [ + GestureDetector( + onTap: () { + controller.addGroup(); + }, + child: Icon( + Icons.add_circle_outline_outlined, + size: ScreenAdaper.height(40), + color: Theme.of(Get.context!).colorScheme.onPrimary, + ), + ), + SizedBox( + width: ScreenAdaper.width(5), + ), Text( '名称', style: headerTitleStyle, @@ -315,8 +309,13 @@ class SaleQuotationPage extends StatelessWidget { color: AppTheme.nearlyBlack, fontWeight: FontWeight.w600); return Container( + decoration: BoxDecoration( + color: const Color.fromARGB(255, 235, 235, 235) + .withOpacity(1.0 - state.scrollPercentage), + border: const Border( + top: BorderSide(width: 1, color: AppTheme.dividerColor), + bottom: BorderSide(width: 1, color: AppTheme.dividerColor))), height: ScreenAdaper.height(80), - color: headerBgcolor.withOpacity(1.0 - state.scrollPercentage), alignment: Alignment.centerLeft, child: InkWell( onTap: () { @@ -469,7 +468,9 @@ class SaleQuotationPage extends StatelessWidget { ), Expanded( child: Text( - '${controller.groups[groupIndex].items[rowIndex].remark ?? ''}${controller.groups[groupIndex].items[rowIndex].remark ?? ''}${controller.products[rowIndex].remark ?? ''}', + controller.groups[groupIndex].items[rowIndex] + .remark ?? + '', overflow: TextOverflow.ellipsis, style: subTextStyle, ), @@ -496,18 +497,19 @@ class SaleQuotationPage extends StatelessWidget { value: controller.groups[groupIndex].items[rowIndex].quantity, func: (value) { // 取消失去焦点,并且弹窗警告 - bool isValid = value > 0; - if (!isValid) { - SnackBarUtil().error('数量必须>0,若想删除产品0,请左滑'); - } - return isValid; + // bool isValid = (value ?? 0) > 0; + // if (!isValid) { + // SnackBarUtil().error('数量必须>0,若想删除产品0,请左滑'); + // } + return true; }), - buildEditCell( + buildEditCell( Container( alignment: Alignment.center, width: ScreenAdaper.width(unitPriceWidth), child: Text( - '${controller.groups[groupIndex].items[rowIndex].cost}', + CommonUtil.toNumberWithout0( + controller.groups[groupIndex].items[rowIndex].cost), style: TextStyle(fontSize: ScreenAdaper.height(25)), ), ), @@ -516,12 +518,13 @@ class SaleQuotationPage extends StatelessWidget { field: 'cost', inputWidth: ScreenAdaper.width(unitPriceWidth), value: controller.groups[groupIndex].items[rowIndex].cost), - buildEditCell( + buildEditCell( Container( alignment: Alignment.center, width: ScreenAdaper.width(amountWidth), child: Text( - '${controller.groups[groupIndex].items[rowIndex].amount}', + CommonUtil.toNumberWithout0( + controller.groups[groupIndex].items[rowIndex].amount), style: TextStyle(fontSize: ScreenAdaper.height(25)), ), ), @@ -558,22 +561,24 @@ class SaleQuotationPage extends StatelessWidget { onFieldSubmitted: (value) { final editingData = controller.groups[groupIndex].items[rowIndex].toJson(); - editingData[field] = value as T; - controller.saveChanges(groupIndex, rowIndex, + editingData[field] = value; + controller.afterRowChanges(groupIndex, rowIndex, SaleQuotationItemModel.fromJson(editingData), field); }, onTapOutside: (dynamic value) { + value = value ?? 0; final editingData = controller.groups[groupIndex].items[rowIndex].toJson(); editingData[field] = value as T; - controller.saveChanges(groupIndex, rowIndex, + controller.afterRowChanges(groupIndex, rowIndex, SaleQuotationItemModel.fromJson(editingData), field); }, contentPadding: EdgeInsets.symmetric( horizontal: ScreenAdaper.width(20), vertical: ScreenAdaper.height(10)), - textController: - TextEditingController(text: '${value == 0 ? '' : value}')), + textController: TextEditingController( + text: + value == 0 ? '' : CommonUtil.toNumberWithout0(value))), ) : InkWell( onTap: () { diff --git a/lib/screens/workbench/workbench.dart b/lib/screens/workbench/workbench.dart index 7c5095a..9f96a6c 100644 --- a/lib/screens/workbench/workbench.dart +++ b/lib/screens/workbench/workbench.dart @@ -43,7 +43,7 @@ class WorkBenchPage extends StatelessWidget { .map((e) => e.name) .firstWhereOrNull((name) => name == controller.menus[index].route); if (route != null) { - RouterUtil.toNamed(controller.menus[index].route); + RouterUtil.toNamed(controller.menus[index].route!); } else { SnackBarUtil().info('您没有权限,请联系管理员。后期会隐藏没有权限的功能'); } diff --git a/lib/services/app_info.service.dart b/lib/services/app_info.service.dart index c8e9dd9..48ace23 100644 --- a/lib/services/app_info.service.dart +++ b/lib/services/app_info.service.dart @@ -11,6 +11,7 @@ import 'package:sk_base_mobile/apis/api.dart'; import 'package:sk_base_mobile/app_theme.dart'; import 'package:sk_base_mobile/config.dart'; import 'package:sk_base_mobile/models/app_config.dart'; +import 'package:sk_base_mobile/router/router.util.dart'; import 'package:sk_base_mobile/services/service.dart'; import 'package:sk_base_mobile/util/device.util.dart'; import 'package:sk_base_mobile/util/logger_util.dart'; @@ -125,6 +126,7 @@ class AppInfoService extends GetxService { ModalUtil.alert( // title: '有新版本', builder: (_) => UpgradeConfirm( + version: newVersion, onConfirm: () { upgradeApp(newVersion); }, @@ -156,8 +158,7 @@ class AppInfoService extends GetxService { try { /// 下载 downloadProgress.value = 0; - Get.back(); - + await RouterUtil.back(); ModalUtil.alert( barrierDismissible: false, contentPadding: EdgeInsets.symmetric(vertical: 0), @@ -167,7 +168,7 @@ class AppInfoService extends GetxService { Obx( () => Stack( children: [ - Container( + SizedBox( height: ScreenAdaper.height(40), child: LinearProgressIndicator( value: downloadProgress.value, @@ -191,7 +192,7 @@ class AppInfoService extends GetxService { responseType: ResponseType.bytes, followRedirects: false, )); - Get.back(); + await RouterUtil.back(); InstallPlugin.install( file.path, ).then((result) {}).catchError((error) {}); diff --git a/lib/services/dio.service.dart b/lib/services/dio.service.dart index e0f4dd7..5ad7b16 100644 --- a/lib/services/dio.service.dart +++ b/lib/services/dio.service.dart @@ -135,7 +135,7 @@ class DioService extends get_package.GetxService { } if (response.data != null && response.data is Map) { if (response.data['code'] == 200) { - if (GloablConfig.DEBUG) LoggerUtil().info(response.data['data']); + // if (GloablConfig.DEBUG) LoggerUtil().info(response.data['data']); response.data = response.data['data']; // 分页数据处理 if (response.data != null && diff --git a/lib/util/common.util.dart b/lib/util/common.util.dart index 7ecdb39..898c4b2 100644 --- a/lib/util/common.util.dart +++ b/lib/util/common.util.dart @@ -36,6 +36,10 @@ class CommonUtil { } return result; } + + static String toNumberWithout0(num num) { + return num.toStringAsFixed(num.truncateToDouble() == num ? 0 : 2); + } } class TreeNode { diff --git a/lib/util/modal.util.dart b/lib/util/modal.util.dart index 41bbc7e..559fc6a 100644 --- a/lib/util/modal.util.dart +++ b/lib/util/modal.util.dart @@ -33,12 +33,12 @@ class ModalUtil { titlePadding: EdgeInsets.zero, contentPadding: contentPadding ?? EdgeInsets.symmetric( - vertical: ScreenAdaper.height(10), - ), + vertical: ScreenAdaper.height(15), + horizontal: ScreenAdaper.width(25)), title: title != null ? Container( padding: EdgeInsets.symmetric( - vertical: ScreenAdaper.height(20), + vertical: ScreenAdaper.height(10), ), decoration: const BoxDecoration( border: Border( @@ -59,7 +59,7 @@ class ModalUtil { textAlign: TextAlign.center, style: TextStyle( fontWeight: FontWeight.w500, - fontSize: ScreenAdaper.height(30), + fontSize: ScreenAdaper.height(25), color: AppTheme.black, ), ) diff --git a/lib/util/photo_picker_util.dart b/lib/util/photo_picker_util.dart index 7b81aa4..c39a339 100644 --- a/lib/util/photo_picker_util.dart +++ b/lib/util/photo_picker_util.dart @@ -1,6 +1,7 @@ import 'package:flutter/cupertino.dart'; import 'package:get/get.dart'; import 'package:image_picker/image_picker.dart'; +import 'package:sk_base_mobile/router/router.util.dart'; import 'package:sk_base_mobile/util/media_util.dart'; import 'package:sk_base_mobile/app_theme.dart'; @@ -13,7 +14,7 @@ class PhotoPickerUtil { title: Text(title), cancelButton: CupertinoActionSheetAction( onPressed: () { - Get.back(); + RouterUtil.back(); }, child: const Text( '取消', @@ -22,7 +23,7 @@ class PhotoPickerUtil { actions: ['拍照', '相册'] .map((item) => CupertinoActionSheetAction( onPressed: () async { - Get.back(); + await RouterUtil.back(); XFile? pickedFile = item == '拍照' ? await MediaUtil().getImageFromCamera() : await MediaUtil().getImageFromGallery(); diff --git a/lib/util/snack_bar.util.dart b/lib/util/snack_bar.util.dart index 1b958bb..e19c56b 100644 --- a/lib/util/snack_bar.util.dart +++ b/lib/util/snack_bar.util.dart @@ -23,7 +23,7 @@ class SnackBarUtil { } } if (Get.isSnackbarOpen) { - await Get.closeCurrentSnackbar(); + Get.back(); } Get.rawSnackbar( snackPosition: SnackPosition.TOP, @@ -46,7 +46,7 @@ class SnackBarUtil { Future success(String title, {String? message}) async { if (checkIsSnackBarInit()) { if (Get.isSnackbarOpen) { - await Get.closeCurrentSnackbar(); + Get.back(); } Get.rawSnackbar( message: title, diff --git a/lib/widgets/core/sk_base_date_picker.dart b/lib/widgets/core/sk_base_date_picker.dart index d15def2..489f901 100644 --- a/lib/widgets/core/sk_base_date_picker.dart +++ b/lib/widgets/core/sk_base_date_picker.dart @@ -1,6 +1,6 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; -import 'package:get/get.dart'; +import 'package:sk_base_mobile/router/router.util.dart'; import 'package:sk_base_mobile/util/logger_util.dart'; import 'package:sk_base_mobile/util/screen_adaper_util.dart'; @@ -32,7 +32,7 @@ class SkBaseDatePicker extends StatelessWidget { children: [ TextButton( onPressed: () { - Get.back(); + RouterUtil.back(); }, child: Text( '取消', @@ -41,7 +41,7 @@ class SkBaseDatePicker extends StatelessWidget { const Spacer(), TextButton( onPressed: () { - Get.back(); + RouterUtil.back(); final year = 2000 + yearController.selectedItem; String month = '${monthController.selectedItem + 1}'; if (int.parse(month) < 10) { diff --git a/lib/widgets/core/sk_bottomsheet_picker.dart b/lib/widgets/core/sk_bottomsheet_picker.dart index 60c4443..af629b5 100644 --- a/lib/widgets/core/sk_bottomsheet_picker.dart +++ b/lib/widgets/core/sk_bottomsheet_picker.dart @@ -1,7 +1,7 @@ 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/router/router.util.dart'; import 'package:sk_base_mobile/util/screen_adaper_util.dart'; class SkBottomSheetPicker extends StatelessWidget { @@ -39,7 +39,7 @@ class SkBottomSheetPicker extends StatelessWidget { children: [ TextButton( onPressed: () { - Get.back(); + RouterUtil.back(); }, child: Text( '取消', @@ -57,7 +57,7 @@ class SkBottomSheetPicker extends StatelessWidget { )), TextButton( onPressed: () { - Get.back(); + RouterUtil.back(); onChanged!( firstLevelControlller.selectedItem, (secondLevel ?? []).isNotEmpty diff --git a/lib/widgets/core/sk_cascade_picker.dart b/lib/widgets/core/sk_cascade_picker.dart index 19f5838..8d9fc2e 100644 --- a/lib/widgets/core/sk_cascade_picker.dart +++ b/lib/widgets/core/sk_cascade_picker.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/router/router.util.dart'; import 'package:sk_base_mobile/util/screen_adaper_util.dart'; import 'package:sk_base_mobile/widgets/core/sk_ink.dart'; import 'package:sk_base_mobile/widgets/loading_indicator.dart'; @@ -65,7 +66,7 @@ class SkCascadePicker extends StatelessWidget { children: [ SkInk( onTap: () { - Get.back(); + RouterUtil.back(); }, child: Text( '取消', @@ -74,7 +75,7 @@ class SkCascadePicker extends StatelessWidget { const Spacer(), SkInk( onTap: () { - Get.back(); + RouterUtil.back(); if (onConfirm != null) { onConfirm!(cascadeController.selectedTabs .where((e) => @@ -98,7 +99,7 @@ class SkCascadePicker extends StatelessWidget { // GradientButton( // buttonText: '确定', // onPressed: () { - // Get.back(); + // await RouterUtil.back(); // if (onConfirm != null) { // onConfirm!(cascadeController.selectedTabs // .where((e) => diff --git a/lib/widgets/core/sk_dialog_header.dart b/lib/widgets/core/sk_dialog_header.dart index 5613e9d..e154156 100644 --- a/lib/widgets/core/sk_dialog_header.dart +++ b/lib/widgets/core/sk_dialog_header.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; -import 'package:get/get.dart'; import 'package:sk_base_mobile/app_theme.dart'; +import 'package:sk_base_mobile/router/router.util.dart'; import 'package:sk_base_mobile/util/screen_adaper_util.dart'; class SkDialogHeader extends StatelessWidget { @@ -39,11 +39,11 @@ class SkDialogHeader extends StatelessWidget { ), ), trailing ?? - Container( + SizedBox( width: ScreenAdaper.width(100), child: IconButton( onPressed: () { - Get.back(); + RouterUtil.back(); }, icon: Icon( Icons.close, diff --git a/lib/widgets/core/sk_flat_button.dart b/lib/widgets/core/sk_flat_button.dart new file mode 100644 index 0000000..b73d390 --- /dev/null +++ b/lib/widgets/core/sk_flat_button.dart @@ -0,0 +1,56 @@ +import 'package:flutter/material.dart'; +import 'package:loading_animation_widget/loading_animation_widget.dart'; +import 'package:sk_base_mobile/app_theme.dart'; +import 'package:sk_base_mobile/constants/constants.dart'; +import 'package:sk_base_mobile/util/screen_adaper_util.dart'; +import 'package:sk_base_mobile/widgets/core/sk_ink.dart'; + +class SkFlatButton extends StatelessWidget { + final VoidCallback? onPressed; + final bool isLoading; + final String buttonText; + final Icon? icon; + final Color? color; + final BorderRadiusGeometry? borderRadius; + const SkFlatButton( + {super.key, + this.buttonText = TextEnum.createInventoryInOutBtnText, + this.onPressed, + this.icon, + this.color, + this.borderRadius, + this.isLoading = false}); + @override + Widget build(BuildContext context) { + return SkInk( + onTap: onPressed ?? () {}, + color: color ?? AppTheme.primaryColor, + child: SizedBox( + height: ScreenAdaper.height(80), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + if (icon != null) ...[ + icon!, + SizedBox( + width: ScreenAdaper.width(10), + ) + ], + isLoading + ? LoadingAnimationWidget.fourRotatingDots( + color: AppTheme.nearlyWhite, + size: ScreenAdaper.height(40), + ) + : Text( + buttonText, + style: TextStyle( + color: AppTheme.nearlyWhite, + fontWeight: FontWeight.bold, + fontSize: ScreenAdaper.height(25), + ), + ) + ])), + ); + } +} diff --git a/lib/widgets/core/sk_ink.dart b/lib/widgets/core/sk_ink.dart index 9c14a6f..ef84c57 100644 --- a/lib/widgets/core/sk_ink.dart +++ b/lib/widgets/core/sk_ink.dart @@ -29,11 +29,11 @@ class SkInk extends StatelessWidget { child: ClipRRect( borderRadius: borderRadius ?? BorderRadius.zero, child: Material( + color: color ?? Colors.transparent, child: Ink( padding: padding, decoration: BoxDecoration( border: border, - color: color ?? AppTheme.nearlyWhite, gradient: gradient, borderRadius: borderRadius, ), diff --git a/lib/widgets/empty.dart b/lib/widgets/empty.dart index ddb2aa2..c58092d 100644 --- a/lib/widgets/empty.dart +++ b/lib/widgets/empty.dart @@ -1,27 +1,34 @@ -import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; +import 'package:sk_base_mobile/app_theme.dart'; import 'package:sk_base_mobile/util/screen_adaper_util.dart'; class Empty extends StatelessWidget { final String? text; - const Empty({super.key, this.text}); + final Widget? icon; + final void Function()? onTap; + const Empty({super.key, this.text, this.icon, this.onTap}); @override Widget build(BuildContext context) { - return Center( - child: Row(mainAxisAlignment: MainAxisAlignment.center, children: [ - Center( - child: Icon(Icons.error_outline, - size: ScreenAdaper.height(40), color: Colors.red)), - SizedBox( - width: ScreenAdaper.width(10), - ), - Text( - text ?? '', - textAlign: TextAlign.center, - style: TextStyle(fontSize: ScreenAdaper.height(35)), - ), - ]), + return GestureDetector( + onTap: onTap, + child: Center( + child: Row(mainAxisAlignment: MainAxisAlignment.center, children: [ + Center( + child: icon ?? + Icon(Icons.error_outline, + size: ScreenAdaper.height(40), + color: AppTheme.dangerColor)), + SizedBox( + width: ScreenAdaper.width(10), + ), + Text( + text ?? '', + textAlign: TextAlign.center, + style: TextStyle(fontSize: ScreenAdaper.height(35)), + ), + ]), + ), ); } } diff --git a/lib/widgets/form_item/sk_multi_search_more.dart b/lib/widgets/form_item/sk_multi_search_more.dart index 6e97cf0..22c84cb 100644 --- a/lib/widgets/form_item/sk_multi_search_more.dart +++ b/lib/widgets/form_item/sk_multi_search_more.dart @@ -5,10 +5,12 @@ import 'package:sk_base_mobile/app_theme.dart'; import 'package:sk_base_mobile/constants/bg_color.dart'; import 'package:sk_base_mobile/models/base_search_more.model.dart'; import 'package:sk_base_mobile/models/base_search_more_controller.dart'; +import 'package:sk_base_mobile/router/router.util.dart'; import 'package:sk_base_mobile/util/debouncer.dart'; import 'package:sk_base_mobile/util/screen_adaper_util.dart'; import 'package:sk_base_mobile/widgets/empty.dart'; import 'package:sk_base_mobile/widgets/gradient_button.dart'; +import 'package:sk_base_mobile/widgets/loading_indicator.dart'; class SkMutilSearchMore extends StatelessWidget { final Function(List)? onOk; @@ -18,7 +20,6 @@ class SkMutilSearchMore extends StatelessWidget { final bool enablePullUp; final bool enablePullDown; final bool isDialog; - final Function(int)? leadingBuilder; SkMutilSearchMore( {super.key, @@ -33,42 +34,45 @@ class SkMutilSearchMore extends StatelessWidget { TextStyle(fontSize: ScreenAdaper.height(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: [ - buildDialogHeader(), - SizedBox( - height: ScreenAdaper.height(defaultPadding) / 2, - ), - buildSearchBar(), - SizedBox( - height: ScreenAdaper.height(defaultPadding) / 2, - ), - Expanded(child: buildList()), - SizedBox( - height: ScreenAdaper.height(defaultPadding) / 2, - ), - GradientButton( - onPressed: () { - if (onOk != null) { - onOk!(controller.selectedIndex); - } - if (isDialog) { - Get.back(); - } - controller.selectedIndex.clear(); - }, - buttonText: '确定', - ) - ], - )); + return Column( + children: [ + Expanded( + child: Container( + padding: EdgeInsets.symmetric( + horizontal: ScreenAdaper.width(20), + vertical: ScreenAdaper.height(20)), + child: Column( + children: [ + buildDialogHeader(), + SizedBox( + height: ScreenAdaper.height(defaultPadding) / 2, + ), + buildSearchBar(), + SizedBox( + height: ScreenAdaper.height(defaultPadding) / 2, + ), + Expanded(child: buildList()), + ], + )), + ), + GradientButton( + onPressed: () { + if (onOk != null) { + onOk!(controller.selectedIndex); + } + if (isDialog) { + RouterUtil.back(); + } + controller.selectedIndex.clear(); + }, + buttonText: '确定', + ) + ], + ); } Widget buildDialogHeader() { - return Container( + return SizedBox( height: ScreenAdaper.height(60), child: Stack( children: [ @@ -84,7 +88,7 @@ class SkMutilSearchMore extends StatelessWidget { right: 0, child: GestureDetector( onTap: () { - Get.back(); + RouterUtil.back(); }, child: Icon( Icons.close, @@ -135,23 +139,25 @@ class SkMutilSearchMore extends StatelessWidget { } Widget buildList() { - return Obx(() => SmartRefresher( - enablePullDown: enablePullDown, - enablePullUp: enablePullUp, - controller: controller.refreshController, - onLoading: controller.onLoading, - onRefresh: controller.onRefresh, - child: controller.list.isEmpty - ? const Center( - child: Empty(text: '暂无数据'), - ) - : ListView.separated( - separatorBuilder: (context, index) => const Divider( - color: AppTheme.dividerColor, - height: 1, - ), - itemCount: controller.list.length, - itemBuilder: (_, index) => buildItem(index)))); + return Obx(() => controller.loading.value + ? const LoadingIndicator(common: true) + : SmartRefresher( + enablePullDown: enablePullDown, + enablePullUp: enablePullUp, + controller: controller.refreshController, + onLoading: controller.onLoading, + onRefresh: controller.onRefresh, + child: controller.list.isEmpty + ? const Center( + child: Empty(text: '暂无数据'), + ) + : ListView.separated( + separatorBuilder: (context, index) => const Divider( + color: AppTheme.dividerColor, + height: 1, + ), + itemCount: controller.list.length, + itemBuilder: (_, index) => buildItem(index)))); } Widget buildItem(int index) { diff --git a/lib/widgets/form_item/sk_number_input.dart b/lib/widgets/form_item/sk_number_input.dart index 2ff57c9..5106c36 100644 --- a/lib/widgets/form_item/sk_number_input.dart +++ b/lib/widgets/form_item/sk_number_input.dart @@ -7,7 +7,7 @@ class SkNumberInput extends StatefulWidget { final TextEditingController textController; final Function(FocusNode)? onTap; final Function(String)? onChanged; - final Function(dynamic)? onTapOutside; + final void Function(dynamic)? onTapOutside; final Function(dynamic)? validator; final bool isDense; @@ -16,7 +16,7 @@ class SkNumberInput extends StatefulWidget { final bool isRequired; final String labelText; final String? hint; - final ValueChanged? onFieldSubmitted; + final ValueChanged? onFieldSubmitted; const SkNumberInput( {super.key, required this.textController, @@ -76,13 +76,13 @@ class _SkNumberInputState extends State { }, onTapOutside: (event) { if (widget.onTapOutside != null) { - dynamic value; + dynamic value = widget.textController.text; if (T == double) { - value = double.tryParse(widget.textController.text); + value = double.tryParse(widget.textController.text) ?? 0.0; widget.onTapOutside!(value as T); } else { - value = int.tryParse(widget.textController.text); + value = int.tryParse(widget.textController.text) ?? 0; } if (widget.validator != null && !widget.validator!(value)) { return; diff --git a/lib/widgets/form_item/sk_single_search_more.dart b/lib/widgets/form_item/sk_single_search_more.dart index 561706d..acb301d 100644 --- a/lib/widgets/form_item/sk_single_search_more.dart +++ b/lib/widgets/form_item/sk_single_search_more.dart @@ -4,6 +4,7 @@ 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/base_search_more_controller.dart'; +import 'package:sk_base_mobile/router/router.util.dart'; import 'package:sk_base_mobile/util/debouncer.dart'; import 'package:sk_base_mobile/util/screen_adaper_util.dart'; import 'package:sk_base_mobile/widgets/empty.dart'; @@ -52,7 +53,7 @@ class SkSingleSearchMore extends StatelessWidget { } Widget buildDialogHeader() { - return Container( + return SizedBox( height: ScreenAdaper.height(60), child: Stack( children: [ @@ -68,7 +69,7 @@ class SkSingleSearchMore extends StatelessWidget { right: 0, child: GestureDetector( onTap: () { - Get.back(); + RouterUtil.back(); }, child: Icon( Icons.close, diff --git a/lib/widgets/form_item/sk_text_input.dart b/lib/widgets/form_item/sk_text_input.dart index c3664e0..096ec22 100644 --- a/lib/widgets/form_item/sk_text_input.dart +++ b/lib/widgets/form_item/sk_text_input.dart @@ -22,7 +22,7 @@ class SkTextInput extends SkBaseFieldWidget { final InputBorder? border; final FloatingLabelBehavior? floatingLabelBehavior; final TextInputType? keyboardType; - + final double? height; SkTextInput( {super.key, super.customLabel = false, @@ -40,6 +40,7 @@ class SkTextInput extends SkBaseFieldWidget { this.suffixIcon, this.onChanged, this.border, + this.height, this.floatingLabelBehavior = FloatingLabelBehavior.always, this.isTextArea = false, this.contentPadding, @@ -48,69 +49,76 @@ class SkTextInput extends SkBaseFieldWidget { @override Widget build(BuildContext context) { - Widget field = TextFormField( - focusNode: focusNode, - controller: textController, - onChanged: (String value) { - if (onChanged != null) { - onChanged!(value); - } - }, - onTapOutside: (event) { - if (onTapOutside != null) { - onTapOutside!(textController.text); - FocusScope.of(context).unfocus(); - } - }, - maxLines: isTextArea ? 2 : 1, // 添加这行代码 - onTap: () { - if (onTap != null) { - onTap!(focusNode); - } - }, - keyboardType: keyboardType, - onFieldSubmitted: onFieldSubmitted, - autovalidateMode: AutovalidateMode.onUserInteraction, - validator: validator, + Widget field = SizedBox( + height: height, + child: TextFormField( + focusNode: focusNode, + controller: textController, + onChanged: (String value) { + if (onChanged != null) { + onChanged!(value); + } + }, + onTapOutside: (event) { + if (onTapOutside != null) { + onTapOutside!(textController.text); + FocusScope.of(context).unfocus(); + } + }, + maxLines: isTextArea ? 2 : 1, // 添加这行代码 + onTap: () { + if (onTap != null) { + onTap!(focusNode); + } + }, + keyboardType: keyboardType, + onFieldSubmitted: onFieldSubmitted, + autovalidateMode: AutovalidateMode.onUserInteraction, + validator: validator, - decoration: InputDecoration( - prefixIcon: prefix, - suffixIcon: suffixIcon, - fillColor: fillColor, - filled: true, - errorStyle: const TextStyle(fontSize: 0, height: 0.01), - contentPadding: contentPadding, - isDense: isDense, - border: border, - floatingLabelBehavior: floatingLabelBehavior, - label: labelText != null && !customLabel - ? Row( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisSize: MainAxisSize.min, - children: [ - if (isRequired) + decoration: InputDecoration( + prefixIcon: prefix, + suffixIcon: suffixIcon, + fillColor: fillColor, + filled: true, + errorStyle: const TextStyle(fontSize: 0, height: 0.01), + contentPadding: contentPadding, + isDense: isDense, + border: border, + floatingLabelBehavior: floatingLabelBehavior, + label: labelText != null && !customLabel + ? Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + if (isRequired) + Text( + "*", + style: TextStyle( + color: Colors.red, + fontSize: ScreenAdaper.height(30)), + ), Text( - "*", - style: TextStyle( - color: Colors.red, - fontSize: ScreenAdaper.height(30)), + labelText!, + style: TextStyle(fontSize: ScreenAdaper.height(30)), ), - Text( - labelText!, - style: TextStyle(fontSize: ScreenAdaper.height(30)), - ), - ]) - : null, - hintText: hint ?? '请输入', + ]) + : null, + hintText: hint ?? '请输入', + ), ), ); - return SkFormItem( - controller: baseFieldController, - customLabel: customLabel, - labelText: labelText, - isRequired: isRequired, - child: field, - ); + if (customLabel) { + return SkFormItem( + controller: baseFieldController, + customLabel: customLabel, + labelText: labelText, + isRequired: isRequired, + child: field, + ); + } else { + return field; + } } } diff --git a/lib/widgets/gradient_button.dart b/lib/widgets/gradient_button.dart index 363791a..0d7a040 100644 --- a/lib/widgets/gradient_button.dart +++ b/lib/widgets/gradient_button.dart @@ -10,7 +10,7 @@ class GradientButton extends StatelessWidget { final bool isLoading; final String buttonText; final Icon? icon; - final BorderRadiusGeometry? borderRadius; + final BorderRadius? borderRadius; const GradientButton( {super.key, this.buttonText = TextEnum.createInventoryInOutBtnText, @@ -21,6 +21,7 @@ class GradientButton extends StatelessWidget { @override Widget build(BuildContext context) { return SkInk( + borderRadius: borderRadius, onTap: onPressed ?? () {}, gradient: const LinearGradient( colors: [AppTheme.primaryColorLight, AppTheme.primaryColor]), diff --git a/lib/widgets/image_preview.dart b/lib/widgets/image_preview.dart index da5318c..9dfc368 100644 --- a/lib/widgets/image_preview.dart +++ b/lib/widgets/image_preview.dart @@ -1,10 +1,10 @@ import 'package:flutter/material.dart'; -import 'package:get/get.dart'; import 'package:photo_view/photo_view.dart'; import 'package:photo_view/photo_view_gallery.dart'; import 'package:sk_base_mobile/app_theme.dart'; import 'package:sk_base_mobile/config.dart'; import 'package:sk_base_mobile/models/file.model.dart'; +import 'package:sk_base_mobile/router/router.util.dart'; import 'package:sk_base_mobile/util/loading_util.dart'; import 'package:sk_base_mobile/util/media_util.dart'; import 'package:sk_base_mobile/util/screen_adaper_util.dart'; @@ -96,7 +96,7 @@ class _ImagePreivewState extends State { final FileModel item = widget.galleryItems[index]; return PhotoViewGalleryPageOptions( onTapUp: (_, tapUpDetails, value) { - Get.back(); + RouterUtil.back(); }, imageProvider: Image.network('${GloablConfig.OSS_URL}${item.path}').image, initialScale: PhotoViewComputedScale.contained, diff --git a/lib/widgets/my_avatar.dart b/lib/widgets/my_avatar.dart index e73e07d..6944726 100644 --- a/lib/widgets/my_avatar.dart +++ b/lib/widgets/my_avatar.dart @@ -80,7 +80,7 @@ class MyAvatarController extends GetxController { // } // } // uploadImgFilePath(pickedFile.path); - // Get.back(); + // await RouterUtil.back(); } catch (e) { SnackBarUtil().error('Update avatar failed.'); } finally { diff --git a/lib/widgets/upgrade_confirm.dart b/lib/widgets/upgrade_confirm.dart index e5610c1..4f71b8c 100644 --- a/lib/widgets/upgrade_confirm.dart +++ b/lib/widgets/upgrade_confirm.dart @@ -1,13 +1,19 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:sk_base_mobile/app_theme.dart'; +import 'package:sk_base_mobile/router/router.util.dart'; import 'package:sk_base_mobile/util/screen_adaper_util.dart'; import 'package:sk_base_mobile/widgets/gradient_button.dart'; class UpgradeConfirm extends StatelessWidget { final void Function()? onConfirm; final bool forceUpgrade; - const UpgradeConfirm({super.key, this.onConfirm, this.forceUpgrade = true}); + final String version; + const UpgradeConfirm( + {super.key, + this.onConfirm, + this.forceUpgrade = true, + required this.version}); @override Widget build(BuildContext context) { @@ -33,7 +39,7 @@ class UpgradeConfirm extends StatelessWidget { child: Column( children: [ Text( - '有新的版本更新啦', + '有新的版本v_${version}更新啦', style: TextStyle( decoration: TextDecoration.none, fontSize: ScreenAdaper.height(35), @@ -83,7 +89,7 @@ class UpgradeConfirm extends StatelessWidget { child: // 更新内容 IconButton( onPressed: () { - Get.back(); + RouterUtil.back(); }, icon: const Icon(Icons.close)), ), diff --git a/pubspec.yaml b/pubspec.yaml index 3f23455..8fa785f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.devz # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # In Windows, build-name is used as the major, minor, and patch parts # of the product and file versions while build-number is used as the build suffix. -version: 1.0.0+1 +version: 1.0.2+1 environment: sdk: '>=3.2.6 <4.0.0'