diff --git a/lib/common/models/plugs/sub_screen_models.dart b/lib/common/models/plugs/sub_screen_models.dart index 55a0b7f..b45f273 100644 --- a/lib/common/models/plugs/sub_screen_models.dart +++ b/lib/common/models/plugs/sub_screen_models.dart @@ -7,7 +7,8 @@ part 'sub_screen_models.g.dart'; @JsonSerializable() class SubScreenProductEvent { // 购物车列表 - List shopList; + // List + List> shopList; // 系统类型 SysType sysType; diff --git a/lib/common/models/plugs/sub_screen_models.g.dart b/lib/common/models/plugs/sub_screen_models.g.dart index 46456c0..17d4411 100644 --- a/lib/common/models/plugs/sub_screen_models.g.dart +++ b/lib/common/models/plugs/sub_screen_models.g.dart @@ -10,7 +10,7 @@ SubScreenProductEvent _$SubScreenProductEventFromJson( Map json) => SubScreenProductEvent( shopList: (json['shopList'] as List) - .map((e) => ShopCardEntity.fromJson(e as Map)) + .map((e) => e as Map) .toList(), sysType: $enumDecode(_$SysTypeEnumMap, json['sysType']), payUser: json['payUser'] == null diff --git a/lib/common/utils/index.dart b/lib/common/utils/index.dart index 17e2a12..ef60da6 100644 --- a/lib/common/utils/index.dart +++ b/lib/common/utils/index.dart @@ -19,6 +19,7 @@ export './keyboard/keyboard_tool.dart'; export './logger/logger_tool.dart'; export './manage/barcode_scan_manage.dart'; +export './manage/cash_box_manage.dart'; export './manage/base/serial_base.dart'; export './manage/base/base_facility_manage.dart'; export './manage/tool/serial_read_card_tool.dart'; diff --git a/lib/common/utils/manage/barcode_scan_manage.dart b/lib/common/utils/manage/barcode_scan_manage.dart index 1aaed01..751c0c8 100644 --- a/lib/common/utils/manage/barcode_scan_manage.dart +++ b/lib/common/utils/manage/barcode_scan_manage.dart @@ -51,7 +51,7 @@ class BarcodeScanManage { // 重置定时器 _timer?.cancel(); // 处理回车键,表示扫码结束 - if (event.logicalKey == LogicalKeyboardKey.enter) { + if (event.logicalKey.keyId == 0x10000000d) { if (_buffer.isNotEmpty) { logger.i("|$_tag|扫码结果: $_buffer"); _scanController.add(_buffer); diff --git a/lib/common/utils/manage/cash_box_manage.dart b/lib/common/utils/manage/cash_box_manage.dart new file mode 100644 index 0000000..5696cf9 --- /dev/null +++ b/lib/common/utils/manage/cash_box_manage.dart @@ -0,0 +1,74 @@ +import 'dart:io'; + +import 'package:pj_supermarket_cloud/common/index.dart'; + +class CashBoxManage { + static const String _tag = "钱箱管理"; + + // 钱箱端口定义 + static const int portCT1 = 237; + static const int portCT2 = 227; + static const int portDET = 231; + + // 打开钱箱 CT1 + static Future openCT1() async { + try { + // 设置 GPIO 导出 + await _executeCommand('echo $portCT1 > /sys/class/gpio/export'); + // 设置输出方向 + await _executeCommand( + 'echo out > /sys/class/gpio/gpio$portCT1/direction', + ); + // 写入值 + await _executeCommand('echo 1 > /sys/class/gpio/gpio$portCT1/value'); + return true; + } catch (e) { + print('$_tag 打开钱箱CT1失败: $e'); + return false; + } + } + + // 打开钱箱 CT2 + static Future openCT2() async { + try { + await _executeCommand( + 'echo $portCT2 > /sys/class/gpio/export', + ); + await _executeCommand( + 'echo out > /sys/class/gpio/gpio$portCT2/direction', + ); + await _executeCommand( + 'echo 1 > /sys/class/gpio/gpio$portCT2/value', + ); + return true; + } catch (e) { + logger.e('|$_tag|打开钱箱CT2失败error|$e|'); + return false; + } + } + + // 检查钱箱状态 + static Future checkStatus() async { + try { + await _executeCommand( + 'echo $portDET > /sys/class/gpio/export', + ); + // 读取状态 + final result = await Process.run( + 'cat', + ['/sys/class/gpio/gpio$portDET/value'], + ); + return result.stdout.toString().trim() == '1'; + } catch (e) { + logger.e('|$_tag|检查钱箱状态失败error|$e|'); + return false; + } + } + + static Future _executeCommand(String command) async { + final result = await Process.run('sh', [command]); + if (result.exitCode != 0) { + throw Exception('Command failed: ${result.stderr}'); + } + } +} diff --git a/lib/common/widgets/app/app_img.dart b/lib/common/widgets/app/app_img.dart index 532e4ce..a10794f 100644 --- a/lib/common/widgets/app/app_img.dart +++ b/lib/common/widgets/app/app_img.dart @@ -9,14 +9,16 @@ import 'package:get/get.dart'; import 'package:pj_supermarket_cloud/common/index.dart'; -Widget appEmptyIcon(BuildContext context) { +Widget appEmptyIcon( + BuildContext context, { + required double height, +}) { final theme = FTheme.of(context); - double size = 60; return FIcon( FAssets.icons.globeLock, color: theme.colorScheme.error, size: theme.typography.base.fontSize, - ).center().width(size).height(size); + ).center().width(height).height(height); } Widget appNetworkImg( @@ -26,13 +28,20 @@ Widget appNetworkImg( double imgHeight = 60, BoxFit fit = BoxFit.cover, }) { - return CachedNetworkImage( - imageUrl: AppNetworkTool.ossImage(src), - placeholder: (context, url) => - CircularProgressIndicator().width(50.w).height(50.w), - errorWidget: (context, url, error) { - return appEmptyIcon(context); - }, + return SizedBox( + width: imgWidth, + height: imgHeight, + child: CachedNetworkImage( + imageUrl: AppNetworkTool.ossImage(src), + fit: fit, + placeholder: (context, url) => CircularProgressIndicator() + .center() + .width(imgHeight) + .height(imgHeight), + errorWidget: (context, url, error) { + return appEmptyIcon(context, height: imgHeight); + }, + ), ); } @@ -49,7 +58,7 @@ Widget appFileImg( width: imgWidth, height: imgHeight, errorBuilder: (BuildContext context, Object error, StackTrace? stackTrace) { - return appEmptyIcon(context); + return appEmptyIcon(context, height: imgHeight); }, ); } @@ -62,7 +71,7 @@ Widget appImg( BoxFit fit = BoxFit.contain, }) { if (ObjectUtil.isEmpty(src)) { - return appEmptyIcon(Get.context!); + return appEmptyIcon(Get.context!, height: height); } else { return AppNetworkTool.isNetworkImage(src!) ? appNetworkImg( diff --git a/lib/common/widgets/order/mixin/order_mixin.dart b/lib/common/widgets/order/mixin/order_mixin.dart index 3c0d2bf..711e2d6 100644 --- a/lib/common/widgets/order/mixin/order_mixin.dart +++ b/lib/common/widgets/order/mixin/order_mixin.dart @@ -430,15 +430,17 @@ mixin OrderMixin { } void sendSubScreen() { + if (shopCardList.isEmpty) return; + final json = SubScreenProductEvent( + shopList: shopCardList.map((e) => e.toJson()).toList(), + sysType: _sysType, + payUser: payUser.value, + totalPrice: totalPrice.value, + amountMealPrice: amountMealPrice.value, + ).toJson(); SubScreenPlugin.sendMsgToViceScreen( SubScreenAction.updateCard.name, - params: SubScreenProductEvent( - shopList: shopCardList, - sysType: _sysType, - payUser: payUser.value, - totalPrice: totalPrice.value, - amountMealPrice: amountMealPrice.value, - ).toJson(), + params: json, ); } diff --git a/lib/common/widgets/order/order_common.dart b/lib/common/widgets/order/order_common.dart index 9ca1d4b..ab350cd 100644 --- a/lib/common/widgets/order/order_common.dart +++ b/lib/common/widgets/order/order_common.dart @@ -72,8 +72,8 @@ Widget orderSelectPanel( showWidget = false, }) { final theme = FTheme.of(context); - const btnWidth = 60; - const textWidth = 80; + const btnWidth = 50; + const textWidth = 120; return LayoutBuilder( builder: (BuildContext context, BoxConstraints constraints) { final maxWidth = constraints.maxWidth; diff --git a/lib/common/widgets/order/order_supermarket_common.dart b/lib/common/widgets/order/order_supermarket_common.dart index 04728d5..ec810db 100644 --- a/lib/common/widgets/order/order_supermarket_common.dart +++ b/lib/common/widgets/order/order_supermarket_common.dart @@ -18,7 +18,8 @@ Widget orderSupermarketSpuCard( padding: EdgeInsets.all(0), ), ), - image: appImg(supermarketSpu.picUrl, width: double.infinity, height: 100.h), + image: appImg(supermarketSpu.picUrl, width: double.infinity, height: 100.h) + .center(), subtitle: [ Text(supermarketSpu.productName, style: theme.typography.xl), SizedBox(height: 5.h), diff --git a/lib/common/widgets/order/steelyard_container.dart b/lib/common/widgets/order/steelyard_container.dart index aae398f..86d5449 100644 --- a/lib/common/widgets/order/steelyard_container.dart +++ b/lib/common/widgets/order/steelyard_container.dart @@ -1,5 +1,6 @@ import 'package:common_utils/common_utils.dart'; import 'package:ducafe_ui_core/ducafe_ui_core.dart'; +import 'package:flutter_easyloading/flutter_easyloading.dart'; import 'package:forui/forui.dart'; import 'package:get/get.dart'; import 'package:flutter/material.dart'; @@ -21,7 +22,7 @@ class SteelyardController extends GetxController { static const _tag = "称重视图组件"; - final app = Get.find(); + final appC = Get.find(); // 称重商品 SupermarketSpuEntity? _product; @@ -35,46 +36,66 @@ class SteelyardController extends GetxController { // 价格 RxInt price = RxInt(0); - // 是否开始称重 - RxBool isWidget = RxBool(false); + // 在误差之内的重量 + int _stableCount = 0; + // 上一下的重量 + double _lastWeight = 0; + // 波动范围 + static const double _weightTolerance = 0.05; + // 稳定次数 + static const int _requiredStableCount = 5; // 点击|开始称重| void weightStartCallback() { - if (isWidget.value) { - Toast.error(msg: "正在称重中,请勿重复点击"); - return; - } + EasyLoading.show( + status: '准备中...', + maskType: EasyLoadingMaskType.black, + ); widget.value = 0; callbackWidget.value = 0; price.value = 0; - isWidget.value = true; _listenSteelyard(); onWeightStart?.call(); + TtsManage.speak("开始称重"); + EasyLoading.dismiss(); } // 点击|确定重量| void weightSubmitCallback() { - isWidget.value = false; + EasyLoading.show( + status: '确定中...', + maskType: EasyLoadingMaskType.black, + ); _closeSerial(); onWeightSubmit?.call(); + EasyLoading.dismiss(); } // 点击|取消| void weightCancelCallback() { - isWidget.value = false; + EasyLoading.show( + status: '取消中...', + maskType: EasyLoadingMaskType.black, + ); _closeSerial(); onWeightCancel?.call(); + EasyLoading.dismiss(); } // 打开称重 bool open(SupermarketSpuEntity current) { final isOpen = _openSerial(); + if (_product != null) { + Toast.error(msg: "正在称重中,请勿重复点击"); + return false; + } if (!isOpen) { _closeStatus(); return false; } _closeStatus(); _product = current; + weightStartCallback(); return true; } @@ -89,16 +110,17 @@ class SteelyardController extends GetxController { widget.value = 0; callbackWidget.value = 0; price.value = 0; - isWidget.value = false; + _stableCount = 0; + _lastWeight = 0; } // 打开称串口 bool _openSerial() { - if (app.srialSteelyardManage.isConnect) { - app.srialSteelyardManage.close(); + if (appC.srialSteelyardManage.isConnect) { + appC.srialSteelyardManage.close(); } - app.srialSteelyardManage.connect(); - if (app.srialSteelyardManage.isConnect) { + appC.srialSteelyardManage.connect(); + if (appC.srialSteelyardManage.isConnect) { logger.i("|$_tag|打开称串口成功"); return true; } @@ -108,8 +130,8 @@ class SteelyardController extends GetxController { // 关闭串口 void _closeSerial() { - app.srialSteelyardManage.close(); - if (app.srialSteelyardManage.isConnect) { + appC.srialSteelyardManage.close(); + if (appC.srialSteelyardManage.isConnect) { logger.e("|$_tag|关闭串口失败"); return; } @@ -118,25 +140,42 @@ class SteelyardController extends GetxController { // 监听串口 void _listenSteelyard() { - app.srialSteelyardManage.readerClose(); - app.srialSteelyardManage.listen((event) { - final w = app.srialSteelyardManage.parseWidget(event); - if (ObjectUtil.isNotEmpty(w)) { + try { + if (_product == null) return; + appC.srialSteelyardManage.readerClose(); + appC.srialSteelyardManage.listen((event) { + final w = appC.srialSteelyardManage.parseWidget(event); + if (ObjectUtil.isEmpty(w)) return; callbackWidget.value = w!; - } - }); - interval( - callbackWidget, - (callback) { - if (_product != null) { + }); + interval( + callbackWidget, + (callback) { + if (_product == null) return; widget.value = callbackWidget.value; - final p = callbackWidget.value * _product!.retailPrice; - price.value = int.parse(p.toStringAsFixed(2)); - logger.i("|$_tag|重量|${widget.value}|价格|${_product!.retailPrice}|"); - } - }, - time: const Duration(seconds: 1), - ); + final p = (callbackWidget.value * _product!.retailPrice).ceil(); + price.value = p; + if ((widget.value - _lastWeight).abs() <= _weightTolerance) { + _stableCount++; + if (_stableCount >= _requiredStableCount) { + weightSubmitCallback(); + return; + } + } else { + _stableCount = 0; + } + _lastWeight = widget.value; + logger.i( + "|$_tag|重量|${widget.value}|单价|${_product!.retailPrice}|总价|${price.value}|稳定次数|$_stableCount|", + ); + }, + time: const Duration(milliseconds: 100), + ); + } catch (e) { + _closeStatus(); + _closeStatus(); + logger.e("|$_tag|打开并监听称串口失败error|$e|"); + } } } @@ -163,61 +202,61 @@ Widget steelyardEmpty(BuildContext context) { } Widget steelyardWidget(BuildContext context) { + final steelyardC = Get.find(); final theme = FTheme.of(context); return [ [ Text("重量:", style: theme.typography.xl2), - GetBuilder(builder: (controller) { - return Text( - "${controller.widget.value}/kg", + Obx( + () => Text( + "${steelyardC.widget.value}/kg", style: theme.typography.xl2, - ); - }), - ].toRow().alignLeft().width(200.w), + ), + ), + ].toRow().alignLeft().width(300.w), [ Text("价格:", style: theme.typography.xl2), - GetBuilder(builder: (controller) { - return appPrice(context, controller.price.value); - }), + Obx( + () => appPrice(context, steelyardC.price.value), + ), ].toRow().alignLeft().width(200.w), ].toColumn(mainAxisAlignment: MainAxisAlignment.center); } Widget steelyardContainer(BuildContext context) { final theme = FTheme.of(context); + final steelyardC = Get.find(); final textStyle = theme.typography.xl.copyWith( color: theme.colorScheme.secondary, ); const textWidth = 220; - return GetBuilder(builder: (controller) { - return [ - Visibility( - visible: !controller.isWidget.value, - replacement: steelyardEmpty(context), - child: steelyardWidget(context), - ).expanded(), - SizedBox(height: 100.h), - [ - FButton( - label: Text("开始称重", style: textStyle), - onPress: () => controller.weightStartCallback(), - ).width(textWidth.w), - FButton( - label: Text("确定重量", style: textStyle), - onPress: () => controller.weightSubmitCallback(), - ).width(textWidth.w), - FButton( - style: FButtonStyle.destructive, - label: Text("取消", style: textStyle), - onPress: () => controller.weightCancelCallback(), - ).width(textWidth.w), - ] - .toRowSpace( - space: 50.w, - mainAxisSize: MainAxisSize.max, - mainAxisAlignment: MainAxisAlignment.center, - ) - .marginOnly(top: 20.h), - ].toColumn(mainAxisAlignment: MainAxisAlignment.center); - }); + return [ + Visibility( + visible: steelyardC.widget.value >= 0.1, + replacement: steelyardEmpty(context), + child: steelyardWidget(context), + ).expanded(), + SizedBox(height: 100.h), + [ + // FButton( + // label: Text("开始称重", style: textStyle), + // onPress: () => controller.weightStartCallback(), + // ).width(textWidth.w), + // FButton( + // label: Text("确定重量", style: textStyle), + // onPress: () => controller.weightSubmitCallback(), + // ).width(textWidth.w), + FButton( + style: FButtonStyle.destructive, + label: Text("取消", style: textStyle), + onPress: () => steelyardC.weightCancelCallback(), + ).width(textWidth.w), + ] + .toRowSpace( + space: 50.w, + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.center, + ) + .marginOnly(top: 20.h), + ].toColumn(mainAxisAlignment: MainAxisAlignment.center); } diff --git a/lib/pages/canteen_product/controller.dart b/lib/pages/canteen_product/controller.dart index 3278b72..75216b5 100644 --- a/lib/pages/canteen_product/controller.dart +++ b/lib/pages/canteen_product/controller.dart @@ -59,6 +59,8 @@ class CanteenProductController extends GetxController with OrderMixin { hourTypeC.value = {hourType.value.code}; sysType = SysType.CANTEEN; _listenPayTypeSelectCallback(); + // 更新分类和商品 + await SyncProductTool.updateDatabase(); // 获取分类和商品 await getClassify(); await getProduct(); diff --git a/lib/pages/panel/controller.dart b/lib/pages/panel/controller.dart index 1fe70b0..836dd86 100644 --- a/lib/pages/panel/controller.dart +++ b/lib/pages/panel/controller.dart @@ -101,6 +101,10 @@ class PanelController extends GetxController { Get.offAndToNamed(RouteNames.login); break; case PanelType.test: + await CashBoxManage.openCT1(); + await CashBoxManage.openCT2(); + final status = await CashBoxManage.checkStatus(); + logger.e(status); break; default: break; diff --git a/lib/pages/setting/widgets/setting_product.dart b/lib/pages/setting/widgets/setting_product.dart index 4587d3b..933c204 100644 --- a/lib/pages/setting/widgets/setting_product.dart +++ b/lib/pages/setting/widgets/setting_product.dart @@ -9,7 +9,7 @@ import 'package:pj_supermarket_cloud/common/index.dart'; class ProductManageController extends GetxController { static const _tag = "商品管理 Controller"; - AppControl appC = Get.find(); + final appC = Get.find(); late SysSetting sysSetting; @@ -267,7 +267,6 @@ Widget _buildCanteenList(BuildContext context) { // 超市列表 Widget _buildSupermarketList(BuildContext context) { - final theme = FTheme.of(context); final productC = Get.find(); return Obx( () => GridView.builder( diff --git a/lib/pages/supermarket_order/view.dart b/lib/pages/supermarket_order/view.dart index 9fdd4fc..90c4b85 100644 --- a/lib/pages/supermarket_order/view.dart +++ b/lib/pages/supermarket_order/view.dart @@ -239,9 +239,8 @@ class SupermarketOrderPage extends GetView { return orderSelectPanel( Get.context!, label: current.productName, - price: current.nature == ProductNature.DEFAULT - ? current.productPrice - : current.totalPrice, + price: current.productPrice, + widget: current.productWidget, total: current.productNumber, index: index, showPlus: current.nature == ProductNature.DEFAULT, diff --git a/lib/sub_screen/page/sub_product.dart b/lib/sub_screen/page/sub_product.dart index 73faef8..3b2245c 100644 --- a/lib/sub_screen/page/sub_product.dart +++ b/lib/sub_screen/page/sub_product.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:convert'; import 'package:ducafe_ui_core/ducafe_ui_core.dart'; import 'package:flutter/material.dart'; @@ -16,6 +17,8 @@ class SubProductPage extends StatefulWidget { } class _SubProductPageState extends State { + static const String _tag = "副屏购物车"; + late StreamSubscription _streamSubscription; // 购物车商品列表 @@ -37,18 +40,26 @@ class _SubProductPageState extends State { void initState() { super.initState(); _streamSubscription = SubScreenPlugin.viceStream.listen((event) { - dynamic params = event.arguments; - final action = SubScreenAction.values.fromCode(event.method); - if (action == null) return; - if (action == SubScreenAction.updateCard) { - final subScreenEvent = SubScreenProductEvent.fromJson(params); - setState(() { - _shopList = subScreenEvent.shopList; - _sysType = subScreenEvent.sysType; - _payUser = subScreenEvent.payUser; - _totalPrice = subScreenEvent.totalPrice; - _amountMealPrice = subScreenEvent.amountMealPrice; - }); + try { + dynamic params = event.arguments; + final action = event.method; + if (action == SubScreenAction.updateCard.name) { + if (params is! Map) { + params = json.decode(json.encode(params)); + } + final subScreenEvent = SubScreenProductEvent.fromJson(params); + setState(() { + _shopList = subScreenEvent.shopList + .map((v) => ShopCardEntity.fromJson(v)) + .toList(); + _sysType = subScreenEvent.sysType; + _payUser = subScreenEvent.payUser; + _totalPrice = subScreenEvent.totalPrice; + _amountMealPrice = subScreenEvent.amountMealPrice; + }); + } + } catch (e) { + logger.e("|$_tag|接收主屏消息错误error|$e|"); } }); } @@ -111,7 +122,7 @@ class _SubProductPageState extends State { Text( v.nature == ProductNature.DEFAULT ? v.productNumber.toString() - : v.productWidget.toString(), + : "${v.productWidget}/kg", textAlign: TextAlign.center, ).center(), ),