feat:称优化

This commit is contained in:
dzc committed 2025-03-18 20:19:10 +08:00
1 parent 9fd6bfa93a
commit 02bd097e49
15 files changed
+259 -117

No files matched your search

@@ -7,7 +7,8 @@ part 'sub_screen_models.g.dart';
@JsonSerializable()
class SubScreenProductEvent {
// 购物车列表
List<ShopCardEntity> shopList;
// List<ShopCardEntity>
List<Map<String, dynamic>> shopList;
// 系统类型
SysType sysType;
@@ -10,7 +10,7 @@ SubScreenProductEvent _$SubScreenProductEventFromJson(
Map<String, dynamic> json) =>
SubScreenProductEvent(
shopList: (json['shopList'] as List<dynamic>)
.map((e) => ShopCardEntity.fromJson(e as Map<String, dynamic>))
.map((e) => e as Map<String, dynamic>)
.toList(),
sysType: $enumDecode(_$SysTypeEnumMap, json['sysType']),
payUser: json['payUser'] == null
+1
View File
@@ -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';
@@ -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);
@@ -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<bool> 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<bool> 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<bool> 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<void> _executeCommand(String command) async {
final result = await Process.run('sh', [command]);
if (result.exitCode != 0) {
throw Exception('Command failed: ${result.stderr}');
}
}
}
+21 -12
View File
@@ -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(
@@ -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,
);
}
+2 -2
View File
@@ -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;
@@ -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: <Widget>[
Text(supermarketSpu.productName, style: theme.typography.xl),
SizedBox(height: 5.h),
+113 -74
View File
@@ -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<AppControl>();
final appC = Get.find<AppControl>();
// 称重商品
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<SteelyardController>();
final theme = FTheme.of(context);
return <Widget>[
<Widget>[
Text("重量:", style: theme.typography.xl2),
GetBuilder<SteelyardController>(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),
<Widget>[
Text("价格:", style: theme.typography.xl2),
GetBuilder<SteelyardController>(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<SteelyardController>();
final textStyle = theme.typography.xl.copyWith(
color: theme.colorScheme.secondary,
);
const textWidth = 220;
return GetBuilder<SteelyardController>(builder: (controller) {
return <Widget>[
Visibility(
visible: !controller.isWidget.value,
replacement: steelyardEmpty(context),
child: steelyardWidget(context),
).expanded(),
SizedBox(height: 100.h),
<Widget>[
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 <Widget>[
Visibility(
visible: steelyardC.widget.value >= 0.1,
replacement: steelyardEmpty(context),
child: steelyardWidget(context),
).expanded(),
SizedBox(height: 100.h),
<Widget>[
// 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);
}
@@ -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();
+4
View File
@@ -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;
@@ -9,7 +9,7 @@ import 'package:pj_supermarket_cloud/common/index.dart';
class ProductManageController extends GetxController {
static const _tag = "商品管理 Controller";
AppControl appC = Get.find<AppControl>();
final appC = Get.find<AppControl>();
late SysSetting sysSetting;
@@ -267,7 +267,6 @@ Widget _buildCanteenList(BuildContext context) {
// 超市列表
Widget _buildSupermarketList(BuildContext context) {
final theme = FTheme.of(context);
final productC = Get.find<ProductManageController>();
return Obx(
() => GridView.builder(
+2 -3
View File
@@ -239,9 +239,8 @@ class SupermarketOrderPage extends GetView<SupermarketOrderController> {
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,
+24 -13
View File
@@ -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<SubProductPage> {
static const String _tag = "副屏购物车";
late StreamSubscription _streamSubscription;
// 购物车商品列表
@@ -37,18 +40,26 @@ class _SubProductPageState extends State<SubProductPage> {
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<String, dynamic>) {
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<SubProductPage> {
Text(
v.nature == ProductNature.DEFAULT
? v.productNumber.toString()
: v.productWidget.toString(),
: "${v.productWidget}/kg",
textAlign: TextAlign.center,
).center(),
),