fix:核销bug修改
This commit is contained in:
1 parent
d517f45f38
commit
88ff8988fb
8 files changed
+339
-112
No files matched your search
@@ -161,15 +161,15 @@ abstract class HardApi {
|
||||
required List<int> orderIds,
|
||||
}) async {
|
||||
final setting = await AppSettingTool.getSysSetting();
|
||||
final hardId = setting.serviceId;
|
||||
if (hardId == null || hardId.isEmpty) {
|
||||
throw StateError('当前业务终端尚未绑定');
|
||||
}
|
||||
final res = await HttpManage().dio.post(
|
||||
'/hard/hard-api/ad/reservation-menu/cancel-batch',
|
||||
data: {'orderIds': orderIds},
|
||||
options: dio.Options(
|
||||
headers: {
|
||||
if (setting.serviceId != null) 'DEVICE-ID': setting.serviceId,
|
||||
},
|
||||
),
|
||||
);
|
||||
'/hard/hard-api/ad/reservation-menu/cancel-batch',
|
||||
data: {'orderIds': orderIds},
|
||||
options: dio.Options(headers: {'DEVICE-ID': hardId}),
|
||||
);
|
||||
return res.data == true;
|
||||
}
|
||||
|
||||
|
||||
@@ -13,17 +13,25 @@ import 'package:pj_supermarket_cloud/common/index.dart';
|
||||
abstract class AppNetworkTool {
|
||||
static const _tag = "网络工具类";
|
||||
|
||||
/// 文件中心对外读取 OSS 对象的统一网关路径。
|
||||
static const _ossFilePath = '/admin/sys-file/oss/file';
|
||||
|
||||
static StreamSubscription<List<ConnectivityResult>>? subscription;
|
||||
|
||||
static bool isNetworkOssImage(String imagePath) {
|
||||
return imagePath.contains("?fileName=");
|
||||
final uri = Uri.tryParse(imagePath.trim());
|
||||
return uri?.queryParameters.containsKey('fileName') == true;
|
||||
}
|
||||
|
||||
// 是否是网络图片
|
||||
static bool isNetworkImage(String imagePath) {
|
||||
if (isNetworkOssImage(imagePath)) return true;
|
||||
final networkImagePattern = RegExp(r'^(http|https)://');
|
||||
return networkImagePattern.hasMatch(imagePath);
|
||||
final value = imagePath.trim();
|
||||
if (value.startsWith('//')) return true;
|
||||
final uri = Uri.tryParse(value);
|
||||
return uri != null &&
|
||||
(uri.scheme == 'http' || uri.scheme == 'https') &&
|
||||
uri.host.isNotEmpty;
|
||||
}
|
||||
|
||||
// 手动判断有没有要网络 todo 不建议使用
|
||||
@@ -118,10 +126,7 @@ abstract class AppNetworkTool {
|
||||
/// 从网络中获取图片
|
||||
static Future<imglib.Image?> loadImageFromNetwork(String url) async {
|
||||
try {
|
||||
String networkImage = url;
|
||||
if (AppNetworkTool.isNetworkOssImage(networkImage)) {
|
||||
networkImage = "${Constants.ossUrl}$networkImage";
|
||||
}
|
||||
final networkImage = serverImageUrl(url);
|
||||
final response = await http.get(Uri.parse(networkImage));
|
||||
if (response.statusCode == 200) {
|
||||
final Uint8List imageData = response.bodyBytes;
|
||||
@@ -140,10 +145,7 @@ abstract class AppNetworkTool {
|
||||
|
||||
// 从网络中下载图片
|
||||
static Future<Uint8List?> downloadImage(String url) async {
|
||||
String networkImage = url;
|
||||
if (AppNetworkTool.isNetworkOssImage(networkImage)) {
|
||||
networkImage = "${Constants.ossUrl}$networkImage";
|
||||
}
|
||||
final networkImage = serverImageUrl(url);
|
||||
try {
|
||||
final client = http.Client();
|
||||
try {
|
||||
@@ -166,7 +168,39 @@ abstract class AppNetworkTool {
|
||||
}
|
||||
}
|
||||
|
||||
/// 将服务端图片字段统一转换为图片组件可直接加载的绝对地址。
|
||||
///
|
||||
/// 完整 HTTP(S) 地址保持不变;文件中心相对地址拼到 [Constants.ossUrl]
|
||||
/// 配置的部署前缀后;只有 OSS 对象名时,通过标准文件读取接口访问。
|
||||
static String serverImageUrl(String src) {
|
||||
final value = src.trim();
|
||||
if (value.isEmpty) return value;
|
||||
|
||||
final sourceUri = Uri.tryParse(value);
|
||||
if (sourceUri != null &&
|
||||
(sourceUri.scheme == 'http' || sourceUri.scheme == 'https') &&
|
||||
sourceUri.host.isNotEmpty) {
|
||||
return value;
|
||||
}
|
||||
|
||||
final baseUri = Uri.parse(Constants.ossUrl);
|
||||
final origin = baseUri.origin;
|
||||
final base = Constants.ossUrl.replaceFirst(RegExp(r'/+$'), '');
|
||||
if (value.startsWith('//')) return '${baseUri.scheme}:$value';
|
||||
if (value.startsWith('/api/')) return '$origin$value';
|
||||
if (value.startsWith('api/')) return '$origin/$value';
|
||||
if (value.startsWith('/admin/')) return '$base$value';
|
||||
if (value.startsWith('admin/')) return '$base/$value';
|
||||
|
||||
final endpoint = Uri.parse('$base$_ossFilePath');
|
||||
if (value.startsWith('?')) {
|
||||
return endpoint.replace(query: value.substring(1)).toString();
|
||||
}
|
||||
return endpoint.replace(queryParameters: {'fileName': value}).toString();
|
||||
}
|
||||
|
||||
/// 兼容既有调用名称,所有 OSS 图片地址统一委托给全局解析方法。
|
||||
static String ossImage(String src) {
|
||||
return "${Constants.ossUrl}$src";
|
||||
return serverImageUrl(src);
|
||||
}
|
||||
}
|
||||
+3
-5
@@ -600,7 +600,7 @@ class VerificationOrderDetailDrawer extends StatelessWidget {
|
||||
EdgeInsets.symmetric(horizontal: 16.w, vertical: 8.h),
|
||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: constraints.maxWidth >= 860.w ? 2 : 1,
|
||||
mainAxisExtent: 224.h,
|
||||
mainAxisExtent: 248.h,
|
||||
crossAxisSpacing: 10.w,
|
||||
mainAxisSpacing: 10.h,
|
||||
),
|
||||
@@ -944,13 +944,11 @@ class VerificationOrderDetailDrawer extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
/// 根据服务端地址类型补全 OSS 前缀,完整 HTTP 地址保持原样。
|
||||
/// 使用全局服务端图片规则补齐文件中心地址。
|
||||
String? _networkImageUrl(String? source) {
|
||||
final value = source?.trim();
|
||||
if (value == null || value.isEmpty) return null;
|
||||
return AppNetworkTool.isNetworkImage(value)
|
||||
? value
|
||||
: AppNetworkTool.ossImage(value);
|
||||
return AppNetworkTool.serverImageUrl(value);
|
||||
}
|
||||
|
||||
/// 将打印类型编码转换为 PC 页面相同的中文名称。
|
||||
|
||||
@@ -4,8 +4,15 @@ import 'package:forui/forui.dart';
|
||||
|
||||
import 'package:pj_supermarket_cloud/common/index.dart';
|
||||
|
||||
/// 人工兑换券重打原因允许提交的最大字符数,与服务端错误摘要边界保持明显余量。
|
||||
const int voucherReprintReasonMaxLength = 200;
|
||||
/// 终端操作员可直接选择的兑换券重打原因,避免触屏设备输入文字。
|
||||
const List<String> voucherReprintReasons = [
|
||||
'首次打印未正常出纸',
|
||||
'纸张破损',
|
||||
'打印字迹不清',
|
||||
'打印内容不完整',
|
||||
'卡纸或打印机故障',
|
||||
'原兑换券遗失',
|
||||
];
|
||||
|
||||
/// 收集人工重新打印兑换券原因,取消、遮罩和系统返回均不会提交打印任务。
|
||||
Future<String?> showVoucherReprintDialog(BuildContext context) {
|
||||
@@ -17,28 +24,25 @@ Future<String?> showVoucherReprintDialog(BuildContext context) {
|
||||
);
|
||||
}
|
||||
|
||||
/// 兑换券重打原因弹框,由自身状态持有输入控制器直到退场动画结束。
|
||||
/// 兑换券重打原因弹框,由自身状态持有单选控制器直到退场动画结束。
|
||||
class _VoucherReprintDialog extends StatefulWidget {
|
||||
/// 创建不可绕过必填校验的兑换券重打原因弹框。
|
||||
/// 创建必须明确选择原因的兑换券重打弹框。
|
||||
const _VoucherReprintDialog();
|
||||
|
||||
/// 创建输入状态并接管文本控制器生命周期。
|
||||
/// 创建原因选择状态。
|
||||
@override
|
||||
State<_VoucherReprintDialog> createState() => _VoucherReprintDialogState();
|
||||
}
|
||||
|
||||
/// 兑换券重打原因弹框的输入、校验与关闭状态。
|
||||
/// 兑换券重打原因弹框的选择与关闭状态。
|
||||
class _VoucherReprintDialogState extends State<_VoucherReprintDialog> {
|
||||
/// 当前原因表单的校验键。
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
|
||||
/// 操作员输入的人工重打原因。
|
||||
final _reasonController = TextEditingController();
|
||||
/// 操作员当前选择的人工重打原因。
|
||||
final _reasonController = FRadioSelectGroupController<String>();
|
||||
|
||||
/// 是否已经触发关闭,避免快速连续点击重复弹出路由。
|
||||
bool _closing = false;
|
||||
|
||||
/// 释放输入控制器,避免弹框关闭后继续持有编辑资源。
|
||||
/// 释放单选控制器,避免弹框关闭后继续持有监听资源。
|
||||
@override
|
||||
void dispose() {
|
||||
_reasonController.dispose();
|
||||
@@ -48,25 +52,26 @@ class _VoucherReprintDialogState extends State<_VoucherReprintDialog> {
|
||||
/// 取消本次操作并返回空结果。
|
||||
void _cancel() => _close();
|
||||
|
||||
/// 校验非空及长度后返回去除首尾空白的人工原因。
|
||||
/// 返回操作员明确选中的原因;未选择时确认按钮不可用。
|
||||
void _submit() {
|
||||
if (_closing || _formKey.currentState?.validate() != true) return;
|
||||
_close(_reasonController.text.trim());
|
||||
final reasons = _reasonController.value;
|
||||
if (_closing || reasons.isEmpty) return;
|
||||
_close(reasons.first);
|
||||
}
|
||||
|
||||
/// 只执行一次路由关闭,并在关闭前收起软键盘。
|
||||
/// 只执行一次路由关闭。
|
||||
void _close([String? result]) {
|
||||
if (_closing) return;
|
||||
_closing = true;
|
||||
FocusManager.instance.primaryFocus?.unfocus();
|
||||
Navigator.of(context).pop(result);
|
||||
}
|
||||
|
||||
/// 构建带风险说明、必填输入和标准触控按钮的 Forui 弹框。
|
||||
/// 构建带风险说明、大触控单选项和标准操作按钮的 Forui 弹框。
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = FTheme.of(context);
|
||||
final viewport = MediaQuery.sizeOf(context);
|
||||
final actionWidth = 180.w.clamp(120.0, 180.0).toDouble();
|
||||
return AppDialog(
|
||||
title: Text('重新打印兑换券', style: theme.typography.xl2),
|
||||
subtitle: Text(
|
||||
@@ -82,42 +87,54 @@ class _VoucherReprintDialogState extends State<_VoucherReprintDialog> {
|
||||
maxWidth: viewport.width * 0.52,
|
||||
onClose: _cancel,
|
||||
semanticLabel: '重新打印兑换券原因弹框',
|
||||
content: Form(
|
||||
key: _formKey,
|
||||
child: FTextField(
|
||||
controller: _reasonController,
|
||||
autofocus: true,
|
||||
label: Text('重打原因', style: theme.typography.base),
|
||||
description: Text(
|
||||
'原因会随打印记录上传,便于后续追溯。',
|
||||
style: theme.typography.sm,
|
||||
),
|
||||
hint: '例如:纸张破损、字迹不清',
|
||||
minLines: 2,
|
||||
maxLines: 3,
|
||||
validator: (value) {
|
||||
final reason = value?.trim() ?? '';
|
||||
if (reason.isEmpty) return '请输入重打原因';
|
||||
if (reason.length > voucherReprintReasonMaxLength) {
|
||||
return '重打原因不能超过$voucherReprintReasonMaxLength个字符';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
content: FSelectGroup<String>(
|
||||
controller: _reasonController,
|
||||
label: Text('重打原因(必选)', style: theme.typography.base),
|
||||
description: Text(
|
||||
'请选择最符合现场情况的一项,原因会随打印记录上传。',
|
||||
style: theme.typography.sm,
|
||||
),
|
||||
style: theme.selectGroupStyle.copyWith(
|
||||
labelLayoutStyle: theme.selectGroupStyle.labelLayoutStyle.copyWith(
|
||||
childPadding: EdgeInsets.symmetric(horizontal: 8.w),
|
||||
),
|
||||
radioStyle: theme.selectGroupStyle.radioStyle.copyWith(
|
||||
padding: EdgeInsets.symmetric(vertical: 4.h),
|
||||
),
|
||||
),
|
||||
items: voucherReprintReasons
|
||||
.map(
|
||||
(reason) => FSelectGroupItem.radio(
|
||||
key: ValueKey('voucher-reprint-reason-$reason'),
|
||||
value: reason,
|
||||
semanticLabel: '重打原因:$reason',
|
||||
label: SizedBox(
|
||||
height: 44,
|
||||
child: Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(reason, style: theme.typography.base),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
.toList(growable: false),
|
||||
).padding(vertical: 20.h),
|
||||
actions: [
|
||||
FButton(
|
||||
style: FButtonStyle.outline,
|
||||
label: Text('取消', style: theme.typography.base),
|
||||
onPress: _cancel,
|
||||
).width(180.w),
|
||||
FButton(
|
||||
label: Text(
|
||||
'确认重打',
|
||||
style: theme.typography.base.copyWith(color: Colors.white),
|
||||
),
|
||||
onPress: _submit,
|
||||
).width(180.w),
|
||||
).width(actionWidth),
|
||||
ListenableBuilder(
|
||||
listenable: _reasonController,
|
||||
builder: (_, __) => FButton(
|
||||
label: Text(
|
||||
'确认重打',
|
||||
style: theme.typography.base.copyWith(color: Colors.white),
|
||||
),
|
||||
onPress: _reasonController.value.isEmpty ? null : _submit,
|
||||
).width(actionWidth),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
+37
-37
@@ -50,10 +50,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: async
|
||||
sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63
|
||||
sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.12.0"
|
||||
version: "2.11.0"
|
||||
background_downloader:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -82,10 +82,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: boolean_selector
|
||||
sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea"
|
||||
sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
version: "2.1.1"
|
||||
build:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -178,10 +178,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: characters
|
||||
sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803
|
||||
sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.4.0"
|
||||
version: "1.3.0"
|
||||
charcode:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -202,10 +202,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: clock
|
||||
sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b
|
||||
sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.1.2"
|
||||
version: "1.1.1"
|
||||
code_builder:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -218,10 +218,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: collection
|
||||
sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
|
||||
sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.19.1"
|
||||
version: "1.19.0"
|
||||
common_utils:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -393,10 +393,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: fake_async
|
||||
sha256: "6a95e56b2449df2273fd8c45a662d6947ce1ebb7aafe80e550a3f68297f3cacc"
|
||||
sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.3.2"
|
||||
version: "1.3.1"
|
||||
ffi:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -709,18 +709,18 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker
|
||||
sha256: c35baad643ba394b40aac41080300150a4f08fd0fd6a10378f8f7c6bc161acec
|
||||
sha256: "7bb2830ebd849694d1ec25bf1f44582d6ac531a57a365a803a6034ff751d2d06"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "10.0.8"
|
||||
version: "10.0.7"
|
||||
leak_tracker_flutter_testing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker_flutter_testing
|
||||
sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573
|
||||
sha256: "9491a714cca3667b60b5c420da8217e6de0d1ba7a5ec322fab01758f6998f379"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "3.0.9"
|
||||
version: "3.0.8"
|
||||
leak_tracker_testing:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -781,10 +781,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: matcher
|
||||
sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
|
||||
sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "0.12.17"
|
||||
version: "0.12.16+1"
|
||||
material_color_utilities:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -797,10 +797,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: meta
|
||||
sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c
|
||||
sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.16.0"
|
||||
version: "1.15.0"
|
||||
mime:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -901,10 +901,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path
|
||||
sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
|
||||
sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.9.1"
|
||||
version: "1.9.0"
|
||||
path_parsing:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1186,10 +1186,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: source_span
|
||||
sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c"
|
||||
sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.10.1"
|
||||
version: "1.10.0"
|
||||
sprintf:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1266,18 +1266,18 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: stack_trace
|
||||
sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1"
|
||||
sha256: "9f47fd3630d76be3ab26f0ee06d213679aa425996925ff3feffdec504931c377"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.12.1"
|
||||
version: "1.12.0"
|
||||
stream_channel:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: stream_channel
|
||||
sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d"
|
||||
sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.1.4"
|
||||
version: "2.1.2"
|
||||
stream_transform:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1290,10 +1290,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: string_scanner
|
||||
sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43"
|
||||
sha256: "688af5ed3402a4bde5b3a6c15fd768dbf2621a614950b17f04626c431ab3c4c3"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.4.1"
|
||||
version: "1.3.0"
|
||||
strings:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1338,18 +1338,18 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: term_glyph
|
||||
sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e"
|
||||
sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.2.2"
|
||||
version: "1.2.1"
|
||||
test_api:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd
|
||||
sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "0.7.4"
|
||||
version: "0.7.3"
|
||||
timing:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1434,10 +1434,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vm_service
|
||||
sha256: "0968250880a6c5fe7edc067ed0a13d4bae1577fe2771dcf3010d52c4a9d3ca14"
|
||||
sha256: f6be3ed8bd01289b34d679c2b62226f63c0e69f9fd2e50a6b3c1c729a961041b
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "14.3.1"
|
||||
version: "14.3.0"
|
||||
watcher:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1503,5 +1503,5 @@ packages:
|
||||
source: hosted
|
||||
version: "3.1.3"
|
||||
sdks:
|
||||
dart: ">=3.7.0-0 <4.0.0"
|
||||
dart: ">=3.6.0 <4.0.0"
|
||||
flutter: ">=3.27.0"
|
||||
@@ -0,0 +1,44 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import 'package:pj_supermarket_cloud/common/index.dart';
|
||||
|
||||
void main() {
|
||||
final ossBase = Constants.ossUrl.replaceFirst(RegExp(r'/+$'), '');
|
||||
|
||||
test('服务端图片完整地址保持不变', () {
|
||||
const source = 'https://cdn.example.com/evidence/face.png';
|
||||
|
||||
expect(AppNetworkTool.serverImageUrl(source), source);
|
||||
});
|
||||
|
||||
test('文件中心相对地址补齐网关前缀', () {
|
||||
const source = '/admin/sys-file/oss/file?fileName=evidence.png';
|
||||
|
||||
expect(
|
||||
AppNetworkTool.serverImageUrl(source),
|
||||
'$ossBase/admin/sys-file/oss/file?fileName=evidence.png',
|
||||
);
|
||||
});
|
||||
|
||||
test('已经携带 API 部署前缀的相对地址不会重复拼接', () {
|
||||
const source = '/api/admin/sys-file/oss/file?fileName=evidence.png';
|
||||
|
||||
expect(
|
||||
AppNetworkTool.serverImageUrl(source),
|
||||
'${Uri.parse(Constants.ossUrl).origin}$source',
|
||||
);
|
||||
});
|
||||
|
||||
test('纯 OSS 文件名通过标准图片接口访问并正确编码', () {
|
||||
const source = 'receipt/voucher 1.png';
|
||||
final resolved = Uri.parse(AppNetworkTool.serverImageUrl(source));
|
||||
|
||||
expect(resolved.origin, Uri.parse(Constants.ossUrl).origin);
|
||||
expect(
|
||||
resolved.path,
|
||||
'${Uri.parse(Constants.ossUrl).path.replaceFirst(RegExp(r'/+$'), '')}'
|
||||
'/admin/sys-file/oss/file',
|
||||
);
|
||||
expect(resolved.queryParameters['fileName'], source);
|
||||
});
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:ducafe_ui_core/ducafe_ui_core.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
@@ -233,6 +234,7 @@ void main() {
|
||||
retryVersion: 0,
|
||||
attemptCount: 1,
|
||||
status: '2',
|
||||
pic: 'voucher.png',
|
||||
printedAt: DateTime(2026, 8, 31, 16, 41, 12),
|
||||
createTime: DateTime(2026, 8, 31, 16, 41, 9),
|
||||
),
|
||||
@@ -240,6 +242,17 @@ void main() {
|
||||
printErrorMessage: '',
|
||||
evidenceStatus: OrderRecordDetailLoadStatus.success,
|
||||
evidenceImages: [
|
||||
ServiceOrderEvidenceImage(
|
||||
id: '199',
|
||||
evidenceType: OrderEvidenceType.faceRecognition.code,
|
||||
evidenceRole: OrderEvidenceRole.faceReference.code,
|
||||
sequenceNo: 0,
|
||||
status: 'AVAILABLE',
|
||||
imageUrl:
|
||||
'/admin/sys-file/oss/file?fileName=face-reference.png',
|
||||
hardId: '10',
|
||||
capturedAt: DateTime(2026, 8, 31, 16, 40, 56),
|
||||
),
|
||||
ServiceOrderEvidenceImage(
|
||||
id: '200',
|
||||
evidenceType: OrderEvidenceType.faceRecognition.code,
|
||||
@@ -268,19 +281,40 @@ void main() {
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.tap(find.text('打印与核销凭证(1)'));
|
||||
await tester.pumpAndSettle();
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(milliseconds: 500));
|
||||
expect(find.text('打印与核销凭证'), findsOneWidget);
|
||||
expect(find.text('兑换券'), findsOneWidget);
|
||||
expect(find.text('重新打印兑换券'), findsOneWidget);
|
||||
final printImage = tester.widget<CachedNetworkImage>(
|
||||
find.byType(CachedNetworkImage),
|
||||
);
|
||||
final printImageUri = Uri.parse(printImage.imageUrl);
|
||||
expect(
|
||||
printImageUri.path,
|
||||
'${Uri.parse(Constants.ossUrl).path.replaceFirst(RegExp(r'/+$'), '')}'
|
||||
'/admin/sys-file/oss/file',
|
||||
);
|
||||
expect(printImageUri.queryParameters['fileName'], 'voucher.png');
|
||||
await tester.tap(find.text('重新打印兑换券'));
|
||||
expect(requestedReprint, isTrue);
|
||||
|
||||
await tester.tap(find.text('识别凭证(1)'));
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(find.text('识别凭证(2)'));
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(milliseconds: 500));
|
||||
expect(find.text('识别凭证'), findsOneWidget);
|
||||
expect(find.text('人脸参考图'), findsOneWidget);
|
||||
expect(find.text('现场识别图'), findsOneWidget);
|
||||
expect(find.textContaining('92.34%'), findsOneWidget);
|
||||
expect(find.textContaining('终端现场图片已过保留期'), findsOneWidget);
|
||||
final evidenceImage = tester.widget<CachedNetworkImage>(
|
||||
find.byType(CachedNetworkImage),
|
||||
);
|
||||
expect(
|
||||
evidenceImage.imageUrl,
|
||||
'${Constants.ossUrl.replaceFirst(RegExp(r'/+$'), '')}'
|
||||
'/admin/sys-file/oss/file?fileName=face-reference.png',
|
||||
);
|
||||
expect(tester.takeException(), isNull);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
import 'package:ducafe_ui_core/ducafe_ui_core.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:forui/forui.dart';
|
||||
|
||||
import 'package:pj_supermarket_cloud/pages/terminal/verification_records/widgets/voucher_reprint_dialog.dart';
|
||||
|
||||
void main() {
|
||||
TestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
testWidgets('兑换券重打使用固定原因单选且不会显示文字输入框', (tester) async {
|
||||
await tester.binding.setSurfaceSize(const Size(1280, 720));
|
||||
addTearDown(() => tester.binding.setSurfaceSize(null));
|
||||
String? selectedReason;
|
||||
|
||||
await tester.pumpWidget(
|
||||
_testApp(
|
||||
Builder(
|
||||
builder: (context) => FButton(
|
||||
label: const Text('打开重打'),
|
||||
onPress: () async {
|
||||
selectedReason = await showVoucherReprintDialog(context);
|
||||
},
|
||||
).width(160),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.tap(find.text('打开重打'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.byType(FTextField), findsNothing);
|
||||
expect(find.byType(EditableText), findsNothing);
|
||||
for (final reason in voucherReprintReasons) {
|
||||
expect(find.text(reason), findsOneWidget);
|
||||
}
|
||||
expect(
|
||||
tester.getSize(find.byType(FRadio).first).height,
|
||||
greaterThanOrEqualTo(44),
|
||||
);
|
||||
final confirmBeforeSelection = tester.widget<FButton>(
|
||||
find.widgetWithText(FButton, '确认重打'),
|
||||
);
|
||||
expect(confirmBeforeSelection.onPress, isNull);
|
||||
|
||||
const reason = '打印字迹不清';
|
||||
await tester.tap(find.text(reason));
|
||||
await tester.pump();
|
||||
|
||||
final confirmAfterSelection = tester.widget<FButton>(
|
||||
find.widgetWithText(FButton, '确认重打'),
|
||||
);
|
||||
expect(confirmAfterSelection.onPress, isNotNull);
|
||||
await tester.tap(find.text('确认重打'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(selectedReason, reason);
|
||||
expect(find.text('重新打印兑换券'), findsNothing);
|
||||
expect(tester.takeException(), isNull);
|
||||
});
|
||||
|
||||
testWidgets('取消兑换券重打不会返回原因', (tester) async {
|
||||
await tester.binding.setSurfaceSize(const Size(1280, 720));
|
||||
addTearDown(() => tester.binding.setSurfaceSize(null));
|
||||
String? selectedReason = '未关闭';
|
||||
|
||||
await tester.pumpWidget(
|
||||
_testApp(
|
||||
Builder(
|
||||
builder: (context) => FButton(
|
||||
label: const Text('打开重打'),
|
||||
onPress: () async {
|
||||
selectedReason = await showVoucherReprintDialog(context);
|
||||
},
|
||||
).width(160),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.tap(find.text('打开重打'));
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(find.text('取消'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(selectedReason, isNull);
|
||||
expect(tester.takeException(), isNull);
|
||||
});
|
||||
}
|
||||
|
||||
Widget _testApp(Widget child) {
|
||||
return ScreenUtilInit(
|
||||
designSize: const Size(1920, 1080),
|
||||
builder: (_, __) => MaterialApp(
|
||||
home: FTheme(
|
||||
data: FThemes.zinc.light,
|
||||
child: Scaffold(body: Center(child: child)),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user