diff --git a/assets/icons/help_desk.png b/assets/icons/help_desk.png new file mode 100644 index 0000000..fa971c1 Binary files /dev/null and b/assets/icons/help_desk.png differ diff --git a/assets/icons/usage_guide.svg b/assets/icons/usage_guide.svg new file mode 100644 index 0000000..ae440e5 --- /dev/null +++ b/assets/icons/usage_guide.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/images/sale_quotation_guide1.jpg b/assets/images/sale_quotation_guide1.jpg new file mode 100644 index 0000000..563f894 Binary files /dev/null and b/assets/images/sale_quotation_guide1.jpg differ diff --git a/assets/images/sale_quotation_guide2.jpg b/assets/images/sale_quotation_guide2.jpg new file mode 100644 index 0000000..27ba03f Binary files /dev/null and b/assets/images/sale_quotation_guide2.jpg differ diff --git a/assets/images/sale_quotation_guide3.jpg b/assets/images/sale_quotation_guide3.jpg new file mode 100644 index 0000000..9c4b766 Binary files /dev/null and b/assets/images/sale_quotation_guide3.jpg differ diff --git a/assets/images/sale_quotation_guide4.jpg b/assets/images/sale_quotation_guide4.jpg new file mode 100644 index 0000000..c0adc87 Binary files /dev/null and b/assets/images/sale_quotation_guide4.jpg differ diff --git a/lib/models/sale_quotation.model.dart b/lib/models/sale_quotation.model.dart index 91397e9..45db788 100644 --- a/lib/models/sale_quotation.model.dart +++ b/lib/models/sale_quotation.model.dart @@ -22,37 +22,37 @@ class SaleQuotationModel extends BaseSearchMoreModel { class SaleQuotationItemModel extends BaseSearchMoreModel { final String name; // 规格 - final String? spec; + final String? componentSpecification; final String? unit; final String? remark; // 成本 - num cost; + num unitPrice; int quantity; num amount; SaleQuotationItemModel( {required this.name, - this.spec, + this.componentSpecification, this.unit, this.remark, - this.cost = 0, + this.unitPrice = 0, this.quantity = 0, this.amount = 0}); SaleQuotationItemModel.fromJson(Map json) : name = json['name'], - spec = json['spec'], + componentSpecification = json['componentSpecification'], unit = json['unit'], remark = json['remark'], - cost = json['cost'], + unitPrice = json['unitPrice'], quantity = json['quantity'], amount = json['amount']; // 生成tojson Map toJson() => { 'name': name, - 'spec': spec, + 'componentSpecification': componentSpecification, 'unit': unit, 'remark': remark, - 'cost': cost, + 'unitPrice': unitPrice, 'quantity': quantity, 'amount': amount, }; diff --git a/lib/router/router.dart b/lib/router/router.dart index 9eb24f8..d7a3505 100644 --- a/lib/router/router.dart +++ b/lib/router/router.dart @@ -1,6 +1,8 @@ import 'package:get/get.dart'; import 'package:sk_base_mobile/models/user_info.model.dart'; import 'package:sk_base_mobile/router/auth_middleware.dart'; +import 'package:sk_base_mobile/screens/guide/components/sale_quotation_guide.dart'; +import 'package:sk_base_mobile/screens/guide/guide.dart'; import 'package:sk_base_mobile/screens/hr_manage/components/employee_detail.dart'; import 'package:sk_base_mobile/screens/hr_manage/hr_manage.dart'; import 'package:sk_base_mobile/screens/inventory/inventory.dart'; @@ -18,8 +20,18 @@ class RouteConfig { static const String saleQuotation = '/workbench/sale_quotation'; static const String hrManage = '/workbench/hr_manage'; static const String employeeDetail = '/employee_detail'; + static const String guide = '/guide'; + static const String saleQuotationGuide = '/sale_quotation_guide'; static final List getPages = [ + GetPage( + name: saleQuotationGuide, + page: () => SaleQuotationGuide(), + ), + GetPage( + name: guide, + page: () => GuidePage(), + ), GetPage( name: login, page: () => LoginScreen(), diff --git a/lib/router/router.util.dart b/lib/router/router.util.dart index 0043046..c008498 100644 --- a/lib/router/router.util.dart +++ b/lib/router/router.util.dart @@ -10,6 +10,7 @@ class RouterUtil { RouteConfig.inventory, RouteConfig.saleQuotation, ]; + // static List whiteList = [RouteConfig.saleQuotationGuide]; static Future toNamed(String routeName, {arguments}) async { //关闭键盘 if (Get.context != null) { diff --git a/lib/screens/guide/components/sale_quotation_guide.dart b/lib/screens/guide/components/sale_quotation_guide.dart new file mode 100644 index 0000000..f9c9584 --- /dev/null +++ b/lib/screens/guide/components/sale_quotation_guide.dart @@ -0,0 +1,76 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:sk_base_mobile/app_theme.dart'; +import 'package:sk_base_mobile/util/screen_adaper_util.dart'; +import 'package:sk_base_mobile/widgets/core/sk_appbar.dart'; +import 'package:sk_base_mobile/widgets/custom_contact_icon.dart'; + +class SaleQuotationGuide extends StatelessWidget { + final controller = Get.put(SaleQuotationGuideController()); + SaleQuotationGuide({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: AppTheme.black, + appBar: SkAppbar( + title: '使用教程', + action: [ + const CustomContactIcon(), + SizedBox(width: ScreenAdaper.width(20)) + ], + ), + body: buildBody(), + ); + } + + Widget buildBody() { + return Stack( + alignment: AlignmentDirectional.bottomCenter, + children: [ + Padding( + padding: EdgeInsets.symmetric( + vertical: ScreenAdaper.height(40), + horizontal: ScreenAdaper.width(20)), + child: PageView.builder( + controller: controller.pageController, + itemBuilder: (_, int index) => Container( + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + 'assets/images/sale_quotation_guide${index + 1}.jpg'), + ), + ), + ), + itemCount: 4, + onPageChanged: (index) { + controller.currentPage.value = index; + }, + ), + ), + Obx(() => Row( + mainAxisAlignment: MainAxisAlignment.center, + children: List.generate( + 4, + (index) => Container( + margin: const EdgeInsets.all(4.0), + width: 10.0, + height: 10.0, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: controller.currentPage.value == index + ? AppTheme.primaryColorLight + : Colors.grey, + ), + ), + ), + )), + ], + ); + } +} + +class SaleQuotationGuideController extends GetxController { + final currentPage = 0.obs; + final pageController = PageController(); +} diff --git a/lib/screens/guide/guide.dart b/lib/screens/guide/guide.dart new file mode 100644 index 0000000..f20350b --- /dev/null +++ b/lib/screens/guide/guide.dart @@ -0,0 +1,33 @@ +import 'package:flutter/material.dart'; +import 'package:sk_base_mobile/router/router.dart'; +import 'package:sk_base_mobile/router/router.util.dart'; +import 'package:sk_base_mobile/widgets/core/sk_ink.dart'; + +class GuideModel { + final String title; + final String route; + GuideModel({required this.title, required this.route}); +} + +class GuidePage extends StatelessWidget { + GuidePage({super.key}); + final guides = [ + GuideModel(title: 'Guide 1', route: '/guide/1'), + GuideModel(title: 'Guide 2', route: '/guide/2') + ]; + @override + Widget build(BuildContext context) { + return ListView( + children: guides.map((e) => buildItem(e)).toList(), + ); + } + + Widget buildItem(GuideModel model) { + return SkInk( + onTap: () { + RouterUtil.toNamed(RouteConfig.guide); + }, + child: Text(model.title), + ); + } +} diff --git a/lib/screens/product/product.controller.dart b/lib/screens/new_inventory_inout/product/product.controller.dart similarity index 100% rename from lib/screens/product/product.controller.dart rename to lib/screens/new_inventory_inout/product/product.controller.dart diff --git a/lib/screens/sale_quotation/components/sale_quotation_drawer.dart b/lib/screens/sale_quotation/components/sale_quotation_drawer.dart index faf8f8e..d782058 100644 --- a/lib/screens/sale_quotation/components/sale_quotation_drawer.dart +++ b/lib/screens/sale_quotation/components/sale_quotation_drawer.dart @@ -12,6 +12,7 @@ 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/core/sk_flat_button.dart'; import 'package:sk_base_mobile/widgets/core/sk_ink.dart'; +import 'package:sk_base_mobile/widgets/empty.dart'; import 'package:sk_base_mobile/widgets/form_item/sk_text_input.dart'; import 'package:sk_base_mobile/widgets/core/sk_appbar.dart'; @@ -33,7 +34,7 @@ class SaleQuotationEndDrawer extends StatelessWidget { Expanded( child: buildTemplatePicker(), ), - // buildAction() + buildAction() ], ); } @@ -43,38 +44,72 @@ class SaleQuotationEndDrawer extends StatelessWidget { children: [ Expanded( child: SkFlatButton( - color: AppTheme.dangerColor, - onPressed: () { + color: Colors.green, + onPressed: () async { + // ModalUtil.alert( + // title: '请注意', + // content: Text( + // '之前的模板保存了吗?不保存会丢失。', + // style: TextStyle(fontSize: ScreenAdaper.height(30)), + // ), + // onConfirm: () async { controller.clearWorkbench(); RouterUtil.back(); + // }); }, icon: Icon( - Icons.delete, + Icons.add, color: AppTheme.nearlyWhite, - size: ScreenAdaper.height(40), + size: ScreenAdaper.height(35), ), - buttonText: '清空工作区', + buttonText: '新建模板', + )), + Expanded( + child: SkFlatButton( + color: AppTheme.nearlyWhite, + textColor: AppTheme.nearlyBlack, + onPressed: () async { + controller.openTemplateNameEditPopup( + title: '', + onConfirm: (name) async { + controller.templateName.value = name; + await controller.saveToLocal(); + await controller.saveToDatabase(isSaveAs: true); + await controller.getTemplates(); + }); + }, + icon: Icon( + Icons.save_as, + color: AppTheme.nearlyBlack, + size: ScreenAdaper.height(35), + ), + buttonText: '另存为', )), Expanded( child: SkFlatButton( onPressed: () async { - if (controller.templateName.value != '默认') { - await controller.saveToDatabase(); - await RouterUtil.back(); - } else { - templateNameDialog(); + if (!controller.checkIsValid()) { + return; + } + if (controller.templateName.value != '默认') { + final isSuccessed = await controller.saveToDatabase(); + if (isSuccessed) await RouterUtil.back(); + } else { + controller.openTemplateNameEditPopup(onConfirm: (name) async { + controller.templateName.value = name; + await controller.saveToLocal(); + await controller.saveToDatabase(); + await controller.getTemplates(); + }); } - - // final isSuccessed = await controller.saveToDatabase(); - // if (isSuccessed) await RouterUtil.back(); }, icon: Icon( Icons.save, color: AppTheme.nearlyWhite, - size: ScreenAdaper.height(40), + size: ScreenAdaper.height(35), ), buttonText: '保存模板', - )) + )), ], ); } @@ -117,7 +152,7 @@ class SaleQuotationEndDrawer extends StatelessWidget { Widget buildItem(int index) { return SkInk( - onTap: controller.menus[index].onTap, + onTap: controller.menus[index].onTap ?? () {}, child: Container( alignment: Alignment.center, padding: EdgeInsets.only( @@ -160,14 +195,21 @@ class SaleQuotationEndDrawer extends StatelessWidget { 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(), - )) + child: controller.templates.isEmpty + ? Empty( + onTap: () { + Get.back(); + controller.addGroup(); + }, + text: '暂无模板,点我开始创建') + : ListView( + padding: EdgeInsets.symmetric( + horizontal: ScreenAdaper.height(20)), + children: controller.templates + .mapIndexed((int index, element) => + buildTemplateItem(element, index)) + .toList(), + )) ], ), )); @@ -199,7 +241,14 @@ class SaleQuotationEndDrawer extends StatelessWidget { )), SkInk( onTap: () { - templateNameDialog(title: element.name); + controller.openTemplateNameEditPopup( + title: element.name, + onConfirm: (name) async { + controller.templateName.value = name; + await controller.saveToLocal(); + await controller.saveToDatabase(); + await controller.getTemplates(); + }); }, child: Icon( Icons.edit, @@ -234,67 +283,6 @@ class SaleQuotationEndDrawer extends StatelessWidget { ), )); } - - 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{ diff --git a/lib/screens/sale_quotation/sale_quotation.controller.dart b/lib/screens/sale_quotation/sale_quotation.controller.dart index 34c3299..15a222e 100644 --- a/lib/screens/sale_quotation/sale_quotation.controller.dart +++ b/lib/screens/sale_quotation/sale_quotation.controller.dart @@ -18,16 +18,21 @@ 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/router/router.util.dart'; import 'package:sk_base_mobile/screens/sale_quotation/components/sale_quotation_group_search.dart'; import 'package:sk_base_mobile/services/dio.service.dart'; import 'package:sk_base_mobile/services/storage.service.dart'; +import 'package:sk_base_mobile/util/common.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/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_multi_search_more.dart'; import 'package:sk_base_mobile/util/modal.util.dart'; import 'package:sk_base_mobile/util/screen_adaper_util.dart'; import 'package:pinyin/pinyin.dart'; +import 'package:sk_base_mobile/widgets/form_item/sk_text_input.dart'; import 'package:sk_base_mobile/widgets/loading_indicator.dart'; class SaleQuotationController extends GetxController { @@ -44,6 +49,7 @@ class SaleQuotationController extends GetxController { RxString templateName = '默认'.obs; RxnInt templateId = RxnInt(null); final downloadProgress = RxDouble(0.0); + final RxBool loading = false.obs; @override void onReady() { init(); @@ -52,6 +58,10 @@ class SaleQuotationController extends GetxController { Future export() async { try { + if (templateId.value == null) { + SnackBarUtil().warning('请点击选中模板,若无模板请先创建'); + return; + } final dir = await getDownloadsDirectory(); if (dir != null) { String storagePath = dir.path; @@ -144,9 +154,15 @@ class SaleQuotationController extends GetxController { menus.addAll([ WorkBenchModel(title: '导出明细', icon: 'export.svg', onTap: export), // WorkBenchModel(title: '模板', icon: 'sale_quotation_template.svg'), + WorkBenchModel( + title: '使用教程', + icon: 'usage_guide.svg', + onTap: () { + RouterUtil.toNamed(RouteConfig.saleQuotationGuide); + }), WorkBenchModel(title: '配件管理', icon: 'product.svg'), WorkBenchModel(title: '分组管理', icon: 'sale_quotation_group.svg'), - WorkBenchModel(title: '计算公式', icon: 'sale_quotation_formula.svg'), + // WorkBenchModel(title: '计算公式', icon: 'sale_quotation_formula.svg'), ]); String? salesQuotation = StorageService.to.getString('salesQuotation'); if (salesQuotation != null) { @@ -202,29 +218,29 @@ class SaleQuotationController extends GetxController { SaleQuotationItemModel calculateRow( SaleQuotationItemModel data, String changedField) { Decimal quantity = Decimal.fromInt(0); - Decimal cost = Decimal.fromInt(0); + Decimal unitPrice = Decimal.fromInt(0); Decimal amount = Decimal.fromInt(0); if (data.quantity != 0) { quantity = Decimal.parse('${data.quantity}'); } - if (data.cost != 0) { - cost = Decimal.parse('${data.cost}'); + if (data.unitPrice != 0) { + unitPrice = Decimal.parse('${data.unitPrice}'); } if (data.amount != 0) { amount = Decimal.parse('${data.amount}'); } // 入库一般是先输入单价和数量,然后计算单价 if (changedField != 'amount') { - Decimal result = cost * quantity; + Decimal result = unitPrice * quantity; 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.toDouble() : 0.0; + data.unitPrice = result != Decimal.zero ? result.toDouble() : 0.0; } else if (changedField != 'amount') { - Decimal result = (cost * quantity); + Decimal result = (unitPrice * quantity); data.amount = result != Decimal.zero ? result.toDouble() : 0; } return data; @@ -253,7 +269,7 @@ class SaleQuotationController extends GetxController { 'data': groups.map((e) => e.toJson()).toList(), 'totalCost': totalCost.value, 'totalPrice': totalPrice.value, - 'formula': formula.value + 'formula': formula.value, } }; if (templateId.value != null) { @@ -262,31 +278,52 @@ class SaleQuotationController extends GetxController { await StorageService.to.setString('salesQuotation', jsonEncode(data)); } - /// 保存到数据库 - Future saveToDatabase() async { - if (templateId.value == 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.value!, { - 'name': templateName.value, - 'template': { - 'data': groups.toJson(), - 'totalCost': totalCost.value, - 'totalPrice': totalPrice.value, - 'formula': formula.value - } - }); + /// 检查当前模板是否合法 + bool checkIsValid() { + if (groups.isEmpty) { + SnackBarUtil().warning('至少要创建一个组'); + return false; + } + return true; + } + + /// 保存到数据库 + Future saveToDatabase({isSaveAs = false}) async { + if (groups.isEmpty) { + SnackBarUtil().warning('至少要创建一个组'); + return false; + } + try { + await LoadingUtil.to.show(); + if (templateId.value == null || isSaveAs) { + await Api.createSaleQuotationTemplate({ + 'name': templateName.value, + 'template': { + 'data': groups.toJson(), + 'totalCost': totalCost.value, + 'totalPrice': totalPrice.value, + 'formula': formula.value + } + }); + await SnackBarUtil().success('已生成新的模板'); + await getTemplates(); + changeTemplate(templates.first); + } else { + await Api.updateSaleQuotationTemplate(templateId.value!, { + 'name': templateName.value, + 'template': { + 'data': groups.toJson(), + 'totalCost': totalCost.value, + 'totalPrice': totalPrice.value, + 'formula': formula.value + } + }); + await SnackBarUtil().success('已保存'); + await getTemplates(); + } + } finally { + await LoadingUtil.to.dismiss(); } - await getTemplates(); - await SnackBarUtil().success('已保存'); return true; } @@ -303,6 +340,7 @@ class SaleQuotationController extends GetxController { ModalUtil.showGeneralDialog( content: SkMutilSearchMore( controller: controller, + title: '请选择分组', enablePullUp: false, enablePullDown: true, onOk: (List indexes) { @@ -346,21 +384,30 @@ class SaleQuotationController extends GetxController { content: SkMutilSearchMore( controller: controller, enablePullUp: false, + title: '请选择配件产品', enablePullDown: true, isDialog: true, onOk: (List indexes) { groups[groupIndex] .items .addAll(controller.list.where((element) { - return indexes.contains(controller.list.indexOf(element)); - })); + return indexes.contains(controller.list.indexOf(element)); + }).map((e) { + e.quantity = 1; + + Decimal amount = Decimal.parse(e.unitPrice.toString()) * + Decimal.fromInt(e.quantity); + e.amount = amount.toDouble(); + return e; + })); + calculateTotal(); saveToLocal(); }, leadingBuilder: (index) { return Container( padding: EdgeInsets.symmetric( - horizontal: ScreenAdaper.width(5), - vertical: ScreenAdaper.height(10)), + horizontal: ScreenAdaper.width(5), + ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -369,28 +416,41 @@ class SaleQuotationController extends GetxController { Text( controller.list[index].name, style: TextStyle( - fontSize: ScreenAdaper.height(30)), + fontSize: ScreenAdaper.height(30), + fontWeight: FontWeight.w600), ), ], ), - if (controller.list[index].spec != null) + if (controller.list[index].unit != null) Row( children: [ Text( - '型号:${controller.list[index].spec ?? ''}', + '单位:${controller.list[index].unit ?? ''}', textAlign: TextAlign.start, style: TextStyle( - fontSize: ScreenAdaper.height(25)), + fontSize: ScreenAdaper.height(23)), + ), + ], + ), + if (controller.list[index].componentSpecification != + null) + Row( + children: [ + Text( + '规格、型号及说明:${controller.list[index].componentSpecification ?? ''}', + textAlign: TextAlign.start, + style: TextStyle( + fontSize: ScreenAdaper.height(23)), ), ], ), Row( children: [ Text( - '单价:¥${controller.list[index].cost}', + '单价:¥${controller.list[index].unitPrice}', textAlign: TextAlign.start, style: TextStyle( - fontSize: ScreenAdaper.height(25)), + fontSize: ScreenAdaper.height(23)), ), ], ), @@ -401,7 +461,7 @@ class SaleQuotationController extends GetxController { '备注:${controller.list[index].remark ?? ''}', textAlign: TextAlign.start, style: TextStyle( - fontSize: ScreenAdaper.height(25)), + fontSize: ScreenAdaper.height(23)), ), ], ), @@ -427,6 +487,82 @@ class SaleQuotationController extends GetxController { templateId.value = null; saveToLocal(); } + + void openTemplateNameEditPopup( + {String? title = '', + bool isSaveAs = false, + Function(String)? onConfirm, + Function? onCancel}) { + if (!checkIsValid()) { + return; + } + 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; + } + await RouterUtil.back(); + onConfirm?.call(textController.text); + // templateName.value = textController.text; + // await RouterUtil.back(); + // await saveToLocal(); + // await saveToDatabase(isSaveAs: isSaveAs); + // await getTemplates(); + }, + child: const Text('确定'), + )) + ], + ) + ]), + )); + } + // /// 检查是否已经保存 + // void checkIsSaved() { + // if (templateId.value == null) { + // return false; + // } + // } } class GroupSearchMoreController @@ -472,30 +608,31 @@ class ItemSearchMoreController List newList = componets .map((e) => SaleQuotationItemModel( name: e.name!, - cost: e.unitPrice ?? 0.0, + unitPrice: e.unitPrice ?? 0.0, unit: e.unit?.label ?? '', + componentSpecification: e.componentSpecification, 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), + // name: '矿用本安型支架控制器', unit: '台', componentSpecification: 'ZDYZ-Z', unitPrice: 4700), + // SaleQuotationItemModel(name: '矿用本安型电磁阀驱动器', unitPrice: 1200), + // SaleQuotationItemModel(name: '矿用隔爆兼本安型电源', unitPrice: 5700), + // SaleQuotationItemModel(name: '矿用本安型隔离耦合器', unitPrice: 1200), + // SaleQuotationItemModel(name: '钢丝编织橡胶护套连接器', unitPrice: 600), + // SaleQuotationItemModel(name: '钢丝编织橡胶护套连接器', remark: '控制器-控制器', unitPrice: 400), + // SaleQuotationItemModel(name: '钢丝编织橡胶护套连接器', remark: '控制器-驱动器', unitPrice: 500), // SaleQuotationItemModel( - // name: '钢丝编织橡胶护套连接器', remark: '控制器-隔离耦合器', cost: 2000), - // SaleQuotationItemModel(name: '矿用本安型支架控制器', cost: 4700), - // SaleQuotationItemModel(name: '矿用本安型电磁阀驱动器', cost: 1200), - // SaleQuotationItemModel(name: '矿用隔爆兼本安型电源', cost: 5700), + // name: '钢丝编织橡胶护套连接器', remark: '控制器-隔离耦合器', unitPrice: 2000), + // SaleQuotationItemModel(name: '矿用本安型支架控制器', unitPrice: 4700), + // SaleQuotationItemModel(name: '矿用本安型电磁阀驱动器', unitPrice: 1200), + // SaleQuotationItemModel(name: '矿用隔爆兼本安型电源', unitPrice: 5700), // SaleQuotationItemModel( - // name: '电液换向阀(10功能10接口)', remark: '中间过渡架主阀组', cost: 13200), + // name: '电液换向阀(10功能10接口)', remark: '中间过渡架主阀组', unitPrice: 13200), // SaleQuotationItemModel( - // name: '电液换向阀(20功能20接口)', remark: '端头架主阀组', cost: 26500), + // name: '电液换向阀(20功能20接口)', remark: '端头架主阀组', unitPrice: 26500), // SaleQuotationItemModel( - // name: '自动反冲洗过滤装置', remark: '流量:900L/min,过滤精度25μm', cost: 2000), + // name: '自动反冲洗过滤装置', remark: '流量:900L/min,过滤精度25μm', unitPrice: 2000), // SaleQuotationItemModel(name: '全自动反冲洗过滤器电缆', remark: '控制器-自动反冲洗'), // SaleQuotationItemModel(name: '矿用本安型位移传感器'), // SaleQuotationItemModel(name: '矿用本安型压力传感器'), diff --git a/lib/screens/sale_quotation/sale_quotation.dart b/lib/screens/sale_quotation/sale_quotation.dart index abd5c66..b8054e6 100644 --- a/lib/screens/sale_quotation/sale_quotation.dart +++ b/lib/screens/sale_quotation/sale_quotation.dart @@ -4,6 +4,7 @@ import 'package:flutter_slidable/flutter_slidable.dart'; import 'package:get/get.dart'; import 'package:sk_base_mobile/app_theme.dart'; import 'package:sk_base_mobile/models/sale_quotation.model.dart'; +import 'package:sk_base_mobile/router/router.util.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'; @@ -37,6 +38,32 @@ class SaleQuotationPage extends StatelessWidget { child: SaleQuotationEndDrawer(), ), appBar: SkAppbar( + onPop: () async { + // 在这里判断是否允许用户返回 + // 如果返回 true,用户可以返回 + // 如果返回 false,用户不能返回 + if (controller.templateName.value == '默认' && + controller.groups.isNotEmpty) { + await ModalUtil.alert( + confirmText: '保存', + cancelText: '继续退出', + contentText: '是否保存当前模板?', + onConfirm: () { + controller.openTemplateNameEditPopup( + onConfirm: (title) async { + controller.templateName.value = title; + await controller.saveToLocal(); + await controller.saveToDatabase(); + RouterUtil.back(); + }); + }, + onCancel: () { + RouterUtil.back(); + }); + } else { + RouterUtil.back(); + } + }, title: '报价计算-${controller.templateName}', action: [ IconButton( @@ -378,7 +405,7 @@ class SaleQuotationPage extends StatelessWidget { Widget buildRow(int groupIndex, int rowIndex) { final subTextStyle = - TextStyle(color: AppTheme.grey, fontSize: ScreenAdaper.height(25)); + TextStyle(color: AppTheme.grey, fontSize: ScreenAdaper.height(23)); return Slidable( key: UniqueKey(), endActionPane: ActionPane( @@ -429,16 +456,18 @@ class SaleQuotationPage extends StatelessWidget { ), ], ), - if (controller.groups[groupIndex].items[rowIndex].spec != + if (controller.groups[groupIndex].items[rowIndex] + .componentSpecification != null) Row( children: [ + // Text( + // '规格、型号: ', + // style: subTextStyle, + // ), Text( - '规格、型号: ', - style: subTextStyle, - ), - Text( - controller.groups[groupIndex].items[rowIndex].spec ?? + controller.groups[groupIndex].items[rowIndex] + .componentSpecification ?? '', style: subTextStyle, ), @@ -509,16 +538,16 @@ class SaleQuotationPage extends StatelessWidget { alignment: Alignment.center, width: ScreenAdaper.width(unitPriceWidth), child: Text( - CommonUtil.toNumberWithout0( - controller.groups[groupIndex].items[rowIndex].cost), + CommonUtil.toNumberWithout0(controller + .groups[groupIndex].items[rowIndex].unitPrice), style: TextStyle(fontSize: ScreenAdaper.height(25)), ), ), groupIndex: groupIndex, rowIndex: rowIndex, - field: 'cost', + field: 'unitPrice', inputWidth: ScreenAdaper.width(unitPriceWidth), - value: controller.groups[groupIndex].items[rowIndex].cost), + value: controller.groups[groupIndex].items[rowIndex].unitPrice), buildEditCell( Container( alignment: Alignment.center, diff --git a/lib/util/loading_util.dart b/lib/util/loading_util.dart index 07d97e7..f4a9b55 100644 --- a/lib/util/loading_util.dart +++ b/lib/util/loading_util.dart @@ -6,6 +6,7 @@ import 'package:sk_base_mobile/util/screen_adaper_util.dart'; class LoadingUtil extends GetxService { static LoadingUtil get to => Get.find(); + // must use LoadingUtil.to.show() instead of LoadingUtil().show() OverlayEntry? _loadingOverlay; Future init() async { @@ -17,17 +18,17 @@ class LoadingUtil extends GetxService { } Future dismiss() async { - return hideLoading(); + return _hideLoading(); } showLoading({String? status}) async { - hideLoading(); + _hideLoading(); _loadingOverlay = OverlayEntry( builder: (BuildContext context) { return PopScope( onPopInvoked: (_) { - hideLoading(); + _hideLoading(); }, child: Stack( children: [ @@ -60,7 +61,7 @@ class LoadingUtil extends GetxService { } } - hideLoading() { + _hideLoading() { _loadingOverlay?.remove(); _loadingOverlay = null; } diff --git a/lib/util/modal.util.dart b/lib/util/modal.util.dart index 559fc6a..984ab0e 100644 --- a/lib/util/modal.util.dart +++ b/lib/util/modal.util.dart @@ -10,8 +10,10 @@ class ModalUtil { {String? title, String? contentText, String? confirmText, + String? cancelText, Widget? content, VoidCallback? onConfirm, + VoidCallback? onCancel, Widget Function(BuildContext)? builder, EdgeInsetsGeometry? contentPadding, bool showActions = true, @@ -98,13 +100,14 @@ class ModalUtil { onTap: () { Navigator.of(context).pop(); confirmed = false; + if (onCancel != null) onCancel(); }, child: Container( padding: EdgeInsets.symmetric( vertical: ScreenAdaper.height(15)), alignment: Alignment.center, child: Text( - '取消', + cancelText ?? '取消', style: TextStyle( color: Colors.black, fontSize: ScreenAdaper.height(30)), @@ -113,9 +116,9 @@ class ModalUtil { Expanded( child: InkWell( onTap: () { - if (onConfirm != null) onConfirm(); Navigator.pop(context); confirmed = true; + if (onConfirm != null) onConfirm(); }, child: Container( padding: EdgeInsets.symmetric( diff --git a/lib/widgets/core/sk_appbar.dart b/lib/widgets/core/sk_appbar.dart index 380f95f..f760f2a 100644 --- a/lib/widgets/core/sk_appbar.dart +++ b/lib/widgets/core/sk_appbar.dart @@ -8,6 +8,7 @@ class SkAppbar extends StatelessWidget implements PreferredSizeWidget { final PreferredSizeWidget? bottom; final Color? backgroundColor; final Color? iconAndTextColor; + final Function()? onPop; const SkAppbar( {super.key, required this.title, @@ -15,6 +16,7 @@ class SkAppbar extends StatelessWidget implements PreferredSizeWidget { this.hideLeading = false, this.backgroundColor, this.iconAndTextColor, + this.onPop, this.bottom}); @override @@ -34,8 +36,12 @@ class SkAppbar extends StatelessWidget implements PreferredSizeWidget { size: ScreenAdaper.height(40), color: iconAndTextColor, ), - onPressed: () { - Navigator.pop(context); + onPressed: () async { + if (onPop != null) { + onPop!(); + } else { + Navigator.pop(context); + } }, ), title: Text( diff --git a/lib/widgets/core/sk_avatar.dart b/lib/widgets/core/sk_avatar.dart index fadb486..9f00dfa 100644 --- a/lib/widgets/core/sk_avatar.dart +++ b/lib/widgets/core/sk_avatar.dart @@ -11,11 +11,12 @@ class SkAvatar extends StatelessWidget { @override Widget build(BuildContext context) { return ClipRRect( - borderRadius: const BorderRadius.all(Radius.circular(40)), + borderRadius: BorderRadius.circular(ScreenAdaper.sp(80)), child: Container( decoration: BoxDecoration( + color: Colors.grey[200], border: Border.all(color: AppTheme.dividerColor), - borderRadius: const BorderRadius.all(Radius.circular(40))), + borderRadius: BorderRadius.circular(ScreenAdaper.sp(80))), child: FadeInCacheImage( defaultWidget: Icon(Icons.person_2_outlined, size: ScreenAdaper.height(60), color: AppTheme.grey), diff --git a/lib/widgets/custom_contact_icon.dart b/lib/widgets/custom_contact_icon.dart new file mode 100644 index 0000000..899552f --- /dev/null +++ b/lib/widgets/custom_contact_icon.dart @@ -0,0 +1,20 @@ +import 'package:flutter/material.dart'; +import 'package:sk_base_mobile/config.dart'; +import 'package:sk_base_mobile/util/device.util.dart'; +import 'package:sk_base_mobile/util/screen_adaper_util.dart'; +import 'package:sk_base_mobile/widgets/core/sk_ink.dart'; + +class CustomContactIcon extends StatelessWidget { + const CustomContactIcon({super.key}); + + @override + Widget build(BuildContext context) { + return SkInk( + onTap: () { + DeviceUtil.callPhone(GloablConfig.SUPPORT_PHONE); + }, + child: Image( + height: ScreenAdaper.height(70), + image: AssetImage('assets/icons/help_desk.png'))); + } +} diff --git a/lib/widgets/empty.dart b/lib/widgets/empty.dart index c58092d..56cb8f2 100644 --- a/lib/widgets/empty.dart +++ b/lib/widgets/empty.dart @@ -11,24 +11,27 @@ class Empty extends StatelessWidget { @override Widget build(BuildContext context) { 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)), - ), - ]), - ), - ); + onTap: onTap, + child: Container( + padding: EdgeInsets.symmetric(horizontal: ScreenAdaper.height(10)), + child: Row( + mainAxisSize: MainAxisSize.min, + 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(30)), + ) + ]), + )); } } diff --git a/lib/widgets/form_item/sk_multi_search_more.dart b/lib/widgets/form_item/sk_multi_search_more.dart index 22c84cb..d6faa64 100644 --- a/lib/widgets/form_item/sk_multi_search_more.dart +++ b/lib/widgets/form_item/sk_multi_search_more.dart @@ -21,6 +21,7 @@ class SkMutilSearchMore extends StatelessWidget { final bool enablePullDown; final bool isDialog; final Function(int)? leadingBuilder; + final String? title; SkMutilSearchMore( {super.key, required this.controller, @@ -28,6 +29,7 @@ class SkMutilSearchMore extends StatelessWidget { this.isDialog = true, this.beforeSelectedCheck, this.leadingBuilder, + this.title, this.enablePullDown = true, this.enablePullUp = true}); final listTitleTextStyle = @@ -78,7 +80,7 @@ class SkMutilSearchMore extends StatelessWidget { children: [ Center( child: Text( - '请选择', + title ?? '请选择', style: TextStyle( fontSize: ScreenAdaper.height(30), fontWeight: FontWeight.w600),