Files
biz_xfj_cloud/lib/pages/terminal/setting/widgets/setting_page.dart
T

878 lines
32 KiB
Dart

import 'package:ducafe_ui_core/ducafe_ui_core.dart';
import 'package:flutter/material.dart';
import 'package:forui/forui.dart';
import 'package:get/get.dart';
import 'package:pj_supermarket_cloud/common/index.dart';
import '../controller.dart';
import 'setting_components.dart';
import 'setting_menu_query_shortcut.dart';
import 'setting_pay_type_editor.dart';
/// 页面设置采用与“基本信息”一致的分组表面,并用二级菜单承载各业务页配置。
Widget settingPage(BuildContext context) {
final controller = Get.find<SettingController>();
// 二级菜单和内容只依赖当前页面类型,切换时不会触发设置页其他区域重建。
return <Widget>[
Obx(
() => _PageSettingMenu(
types: controller.configurablePageTypes,
selected: controller.pageSettingType.value,
onSelect: controller.selectPageSettingType,
),
).width(240.w),
Obx(
() => _PageSettingContent(
type: controller.pageSettingType.value,
controller: controller,
),
).expanded(),
].toRowSpace(
crossAxisAlignment: CrossAxisAlignment.stretch,
space: XfjDesignTokens.cardGap.w,
);
}
class _PageSettingMenu extends StatelessWidget {
const _PageSettingMenu({
required this.types,
required this.selected,
required this.onSelect,
});
final List<TerminalMenuItemType> types;
final TerminalMenuItemType selected;
final ValueChanged<TerminalMenuItemType> onSelect;
@override
Widget build(BuildContext context) {
final theme = FTheme.of(context);
return Container(
padding: EdgeInsets.all(10.w),
decoration: BoxDecoration(
color: XfjDesignTokens.canvas,
border: Border.all(color: XfjDesignTokens.line),
borderRadius: BorderRadius.circular(XfjDesignTokens.cardRadius),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Padding(
padding: EdgeInsets.fromLTRB(10.w, 4.h, 10.w, 10.h),
child: Text(
'业务页面',
style: theme.typography.sm.copyWith(
color: XfjDesignTokens.muted,
fontWeight: FontWeight.w700,
),
),
),
Expanded(
child: ListView.separated(
padding: EdgeInsets.zero,
itemCount: types.length,
separatorBuilder: (_, __) => SizedBox(height: 6.h),
itemBuilder: (context, index) {
final type = types[index];
final active = selected == type;
return Semantics(
button: true,
selected: active,
label: '${type.title}页面设置',
child: Material(
color: active
? XfjDesignTokens.primarySoft
: Colors.transparent,
borderRadius: BorderRadius.circular(
XfjDesignTokens.controlRadius,
),
child: InkWell(
onTap: () => onSelect(type),
borderRadius: BorderRadius.circular(
XfjDesignTokens.controlRadius,
),
child: Container(
constraints: BoxConstraints(minHeight: 52.h),
padding: EdgeInsets.symmetric(horizontal: 12.w),
decoration: BoxDecoration(
border: Border.all(
color: active
? XfjDesignTokens.primary
.withValues(alpha: 0.35)
: Colors.transparent,
),
borderRadius: BorderRadius.circular(
XfjDesignTokens.controlRadius,
),
),
child: Row(
children: [
FIcon(
type.icon,
color: active
? XfjDesignTokens.primary
: XfjDesignTokens.muted,
size: 20.sp,
),
SizedBox(width: 10.w),
Expanded(
child: Text(
_pageTitle(type),
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: theme.typography.sm.copyWith(
color: active
? XfjDesignTokens.primary
: XfjDesignTokens.ink,
fontWeight: FontWeight.w700,
),
),
),
if (active)
FIcon(
FAssets.icons.chevronRight,
color: XfjDesignTokens.primary,
size: 17.sp,
),
],
),
),
),
),
);
},
),
),
],
),
);
}
}
class _PageSettingContent extends StatelessWidget {
const _PageSettingContent({
required this.type,
required this.controller,
});
final TerminalMenuItemType type;
final SettingController controller;
@override
Widget build(BuildContext context) {
return switch (type) {
TerminalMenuItemType.menuQuery => _ReservationQuerySettings(
controller: controller,
),
TerminalMenuItemType.cashier => _CashierSettings(controller: controller),
_ => _PendingPageSettings(type: type),
};
}
}
class _ReservationQuerySettings extends StatelessWidget {
const _ReservationQuerySettings({required this.controller});
final SettingController controller;
/// 组合查询方式、自动识别、隐私、核销、餐段和快捷小键盘设置,并保持各区域独立响应式刷新。
@override
Widget build(BuildContext context) => <Widget>[
_queryMethodSettings(controller),
_autoRecognitionSettings(controller),
_nameMaskSettings(controller),
_verificationSettings(controller),
_mealPeriodSettings(controller),
SettingMenuQueryShortcutKeypad(controller: controller),
SettingNotice(
text:
'查询顺序、默认项、自动识别、持续识别、姓名脱敏、核销、快速核销、餐段修改权限和快捷按键绑定配置会在下次进入“查询菜单”时读取。',
icon: FAssets.icons.circleAlert,
),
].toColumnSpace(space: XfjDesignTokens.cardGap.h).scrollable();
}
/// 查询方式与默认项共享同一组数据,只重建排序和默认选择区域。
Widget _queryMethodSettings(SettingController controller) => Obx(() {
final order = controller.reservationQueryMethodOrder.toList();
final defaultMethod = controller.reservationQueryDefaultMethod.value;
return <Widget>[
SettingGroup(
title: '查询菜单',
child: <Widget>[
_PageSectionHeader(
onReset: () {
controller.resetReservationQueryPreferences();
Toast.success(msg: '查询菜单设置已还原');
},
),
const _QueryMethodColumnHeader(),
_queryMethodList(
controller: controller,
order: order,
defaultMethod: defaultMethod,
),
]
.toColumnSpace(
crossAxisAlignment: CrossAxisAlignment.start,
space: 12.h,
)
.padding(left: 20.w, top: 14.h, right: 20.w, bottom: 20.h),
),
SettingGroup(
title: '默认选项',
child: SettingSegmentedSelector<ReservationRecognitionMethod>(
label: '进入页面后默认选中',
value: defaultMethod,
options: {for (final method in order) method: method.title},
showDivider: false,
onChanged: controller.setReservationQueryDefaultMethod,
),
),
].toColumnSpace(space: XfjDesignTokens.cardGap.h);
});
/// 自动识别及其持续运行策略在同一最小响应区内渐进展开,避免未启用自动模式时暴露无效配置。
Widget _autoRecognitionSettings(SettingController controller) => Obx(() {
final enabled = controller.reservationQueryAutoRecognitionEnabled.value;
final continuousEnabled =
controller.reservationQueryContinuousRecognitionEnabled.value;
final rows = <Widget>[
SettingInfoRow(
icon: enabled ? FAssets.icons.scan : FAssets.icons.circleX,
label: '自动识别查询',
subtitle: enabled
? '进入查询菜单或切换方式后,自动等待饭卡或启动人脸识别。'
: '保留“开始识别”按钮,由用户手动启动当前识别方式。',
value: enabled ? '已开启' : '已关闭',
showDivider: enabled,
trailing: FSwitch(
semanticLabel: '查询菜单自动识别查询',
value: enabled,
onChange: controller.setReservationQueryAutoRecognitionEnabled,
),
),
if (enabled)
SettingInfoRow(
icon: continuousEnabled
? FAssets.icons.refreshCw
: FAssets.icons.clock,
label: '持续自动识别',
subtitle: continuousEnabled
? '饭卡等待超时后自动开启下一轮;人脸会持续等待。设备异常或主动取消仍会暂停。'
: '饭卡等待 30 秒后停止,可在查询页手动重试;人脸保持现有持续等待行为。',
value: continuousEnabled ? '持续等待' : '单轮等待',
showDivider: false,
trailing: FSwitch(
semanticLabel: '查询菜单持续自动识别',
value: continuousEnabled,
onChange:
controller.setReservationQueryContinuousRecognitionEnabled,
),
),
];
return SettingGroup(
title: '识别设置',
child: rows.toColumn(),
);
});
/// 姓名展示配置独立观察,切换时不会重建查询方式排序区域。
Widget _nameMaskSettings(SettingController controller) => Obx(() {
final enabled = controller.reservationQueryNameMaskEnabled.value;
return SettingGroup(
title: '隐私设置',
child: SettingInfoRow(
icon: enabled ? FAssets.icons.shieldCheck : FAssets.icons.user,
label: '姓名信息脱敏',
subtitle:
enabled ? '查询结果仅展示脱敏后的用户姓名。' : '查询结果展示完整用户姓名,请确认当前终端使用环境安全。',
value: enabled ? '已开启' : '已关闭',
showDivider: false,
trailing: FSwitch(
semanticLabel: '查询菜单姓名信息脱敏',
value: enabled,
onChange: controller.setReservationQueryNameMaskEnabled,
),
),
);
});
/// 核销配置独立观察自己的 Rx,开关变化不会重建上方排序列表。
Widget _verificationSettings(SettingController controller) => Obx(() {
final enabled = controller.reservationQueryVerificationEnabled.value;
final quickEnabled =
controller.reservationQueryQuickVerificationEnabled.value;
final dateScope = controller.reservationQueryVerificationDateScope.value;
final rows = <Widget>[
SettingInfoRow(
icon: enabled ? FAssets.icons.circleCheck : FAssets.icons.circleX,
label: '显示核销按钮',
subtitle: '开启后显示“核销”并允许提交;关闭后显示“查询详情”,只能查看订单商品。',
value: enabled ? '已开启' : '已关闭',
showDivider: enabled,
trailing: FSwitch(
semanticLabel: '显示预约核销按钮',
value: enabled,
onChange: controller.setReservationQueryVerificationEnabled,
),
),
if (enabled)
SettingInfoRow(
icon: quickEnabled ? FAssets.icons.zap : FAssets.icons.zapOff,
label: '是否开启快速核销',
subtitle: quickEnabled
? '识别成功后直接打开今天预选餐段的核销弹框。'
: '保留查询结果页,由操作员点击对应餐段的“核销”。',
value: quickEnabled ? '已开启' : '已关闭',
showDivider: true,
trailing: FSwitch(
semanticLabel: '查询菜单快速核销',
value: quickEnabled,
onChange: controller.setReservationQueryQuickVerificationEnabled,
),
),
if (enabled)
SettingSegmentedSelector<ReservationVerificationDateScope>(
label: '核销日期范围',
value: dateScope,
options: const {
ReservationVerificationDateScope.today: '今天',
ReservationVerificationDateScope.all: '所有日期',
},
showDivider: false,
onChanged: controller.setReservationQueryVerificationDateScope,
),
];
return SettingGroup(title: '核销设置', child: rows.toColumn());
});
/// 查询菜单餐段修改权限独立观察,开关变化不会重建其他查询配置区域。
Widget _mealPeriodSettings(SettingController controller) => Obx(() {
final allowed =
controller.reservationQueryAllowManualMealPeriodChange.value;
return SettingGroup(
title: '餐段设置',
child: SettingInfoRow(
icon: allowed ? FAssets.icons.sun : FAssets.icons.clock,
label: '允许主动修改餐段',
subtitle: allowed
? '查询菜单可手动预选餐段;与餐段配置时间不符时仍会弹框提示确认。'
: '查询菜单餐段严格按“餐段配置”的时间自动切换,不允许手动修改。',
value: allowed ? '允许' : '不允许',
showDivider: false,
trailing: FSwitch(
semanticLabel: '查询菜单允许主动修改餐段',
value: allowed,
onChange: controller.setReservationQueryAllowManualMealPeriodChange,
),
),
);
});
/// 排序行由查询方式数据驱动,拖动和上下移动最终都进入同一个 Controller 方法。
Widget _queryMethodList({
required SettingController controller,
required List<ReservationRecognitionMethod> order,
required ReservationRecognitionMethod defaultMethod,
}) =>
ReorderableListView.builder(
shrinkWrap: true,
primary: false,
physics: const NeverScrollableScrollPhysics(),
buildDefaultDragHandles: false,
itemCount: order.length,
onReorder: controller.reorderReservationQueryMethod,
proxyDecorator: (child, _, animation) => AnimatedBuilder(
animation: animation,
builder: (context, child) => Material(
color: Colors.transparent,
elevation: 6 * animation.value,
borderRadius: BorderRadius.circular(XfjDesignTokens.controlRadius),
child: child,
),
child: child,
),
itemBuilder: (context, index) {
final method = order[index];
return _QueryMethodOrderRow(
method: method,
index: index,
isDefault: method == defaultMethod,
onMoveUp: index == 0
? null
: () => controller.reorderReservationQueryMethod(
index,
index - 1,
),
onMoveDown: index == order.length - 1
? null
: () => controller.reorderReservationQueryMethod(
index,
index + 2,
),
).padding(
// ReorderableListView 校验的是直接子项,Key 必须放在扩展生成的 Padding 上。
key: ValueKey(method.code),
bottom: index == order.length - 1 ? 0 : XfjDesignTokens.cardGap.h,
);
},
);
class _CashierSettings extends StatelessWidget {
const _CashierSettings({required this.controller});
final SettingController controller;
@override
Widget build(BuildContext context) => Obx(() {
final sys = controller.sysSetting.value;
if (sys == null) return const SettingEmpty(message: '暂无收银页面配置');
final enabled = sys.usePayDisplayType ?? OrderPayDisplayType.values;
final defaultType = sys.defaultPayDisplayType ?? enabled.firstOrNull;
return <Widget>[
SettingGroup(
title: '收银',
child: SettingPayTypeEditor(
order: controller.payTypeOrder.toList(),
visibleTypes: enabled.toSet(),
reidentifiableTypes:
controller.cashierReidentifiablePayTypes.toSet(),
defaultType: defaultType,
onReset: () {
controller.resetPayTypes();
controller.resetCashierPagePreferences();
Toast.success(msg: '收银页面设置已还原');
},
onReorder: controller.reorderPayType,
onVisibilityToggle: controller.togglePayType,
onReidentifiableToggle: controller.togglePayTypeReidentification,
),
),
SettingGroup(
title: '默认支付方式',
child: defaultType == null
? const SettingEmpty(message: '请先显示至少一种支付方式')
: SettingSegmentedSelector<OrderPayDisplayType>(
label: '收银默认项',
value: defaultType,
options: {
for (final type in enabled) type: _defaultPayLabel(type),
},
showDivider: false,
onChanged: controller.setDefaultPayType,
),
),
_cashierActionSettings(controller),
_cashierMealPeriodSettings(controller),
SettingNotice(
text:
'支付方式顺序、右下角快捷操作和餐段修改权限都会同步到本机收银页;隐藏仅影响界面展示,不改变服务端权限、钱包范围和渠道支付能力。',
icon: FAssets.icons.circleAlert,
),
].toColumnSpace(space: XfjDesignTokens.cardGap.h).scrollable();
});
}
/// 快捷操作显隐独立观察,避免切换按钮时重建上方支付方式拖动列表。
Widget _cashierActionSettings(SettingController controller) => Obx(() {
final showHold = controller.cashierShowHoldAction.value;
final showMore = controller.cashierShowMoreAction.value;
return SettingGroup(
title: '右下角快捷操作',
child: <Widget>[
SettingInfoRow(
icon: FAssets.icons.package,
label: '显示“挂单”',
subtitle: '开启后在支持挂单的收银模式显示快捷入口;关闭只隐藏入口,不清理已有挂单。',
value: showHold ? '已显示' : '已隐藏',
trailing: FSwitch(
semanticLabel: '显示收银挂单按钮',
value: showHold,
onChange: controller.setCashierShowHoldAction,
),
),
SettingInfoRow(
icon: FAssets.icons.circleEllipsis,
label: '显示“更多”',
subtitle: '开启后在支持更多操作的收银模式显示入口,原有操作内容和权限保持不变。',
value: showMore ? '已显示' : '已隐藏',
showDivider: false,
trailing: FSwitch(
semanticLabel: '显示收银更多按钮',
value: showMore,
onChange: controller.setCashierShowMoreAction,
),
),
].toColumn(),
);
});
/// 收银餐段修改权限独立观察,开关变化不会重建上方支付方式拖动列表。
Widget _cashierMealPeriodSettings(SettingController controller) => Obx(() {
final allowed = controller.cashierAllowManualMealPeriodChange.value;
return SettingGroup(
title: '餐段设置',
child: SettingInfoRow(
icon: allowed ? FAssets.icons.sun : FAssets.icons.clock,
label: '允许主动修改餐段',
subtitle: allowed
? '收银页可手动切换餐段;与餐段配置时间不符时仍会弹框提示确认。'
: '收银页餐段严格按“餐段配置”的时间自动切换,不允许手动修改。',
value: allowed ? '允许' : '不允许',
showDivider: false,
trailing: FSwitch(
semanticLabel: '收银允许主动修改餐段',
value: allowed,
onChange: controller.setCashierAllowManualMealPeriodChange,
),
),
);
});
String _defaultPayLabel(OrderPayDisplayType type) {
return switch (type) {
OrderPayDisplayType.qrCodeScan => '扫一扫',
OrderPayDisplayType.qrCode => '二维码',
OrderPayDisplayType.walletCard => '饭卡',
OrderPayDisplayType.face => '人脸',
OrderPayDisplayType.cash => '现金',
OrderPayDisplayType.zyy => '中医院',
OrderPayDisplayType.rmyy => '人民医院',
OrderPayDisplayType.csxfk => '超市消费卡',
};
}
class _PendingPageSettings extends StatelessWidget {
const _PendingPageSettings({required this.type});
final TerminalMenuItemType type;
@override
Widget build(BuildContext context) => SettingGroup(
title: _pageTitle(type),
child: <Widget>[
FIcon(type.icon, color: XfjDesignTokens.muted, size: 44.sp),
Text(
'当前页面暂无可调整项',
style: FTheme.of(context).typography.lg.copyWith(
color: XfjDesignTokens.ink,
fontWeight: FontWeight.w700,
),
),
Text(
'后续新增与该页面直接相关的显示或操作配置时,将集中放在这里。',
textAlign: TextAlign.center,
style: FTheme.of(context).typography.sm.copyWith(
color: XfjDesignTokens.muted,
),
),
]
.toColumnSpace(
mainAxisSize: MainAxisSize.min,
space: 10.h,
)
.padding(horizontal: 24.w, vertical: 56.h),
);
}
class _PageSectionHeader extends StatelessWidget {
const _PageSectionHeader({
required this.onReset,
});
final VoidCallback onReset;
@override
Widget build(BuildContext context) {
final theme = FTheme.of(context);
return Row(
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'查询方式',
style: theme.typography.base.copyWith(
color: XfjDesignTokens.ink,
fontWeight: FontWeight.w700,
),
),
SizedBox(height: 4.h),
Text(
'拖动手柄调整顺序;进入页面后只选中默认项,点击“开始识别”才会调用设备。',
style: theme.typography.sm.copyWith(
color: XfjDesignTokens.muted,
),
),
],
),
),
Row(
mainAxisSize: MainAxisSize.min,
children: [
Container(
padding: EdgeInsets.symmetric(
horizontal: 12.w * XfjDesignTokens.buttonSizeScale,
vertical: 8.h * XfjDesignTokens.buttonSizeScale,
),
decoration: BoxDecoration(
color: XfjDesignTokens.primarySoft,
borderRadius: BorderRadius.circular(
XfjDesignTokens.controlRadius,
),
),
child: Row(
children: [
FIcon(
FAssets.icons.gripVertical,
color: XfjDesignTokens.primary,
size: 18.sp,
),
SizedBox(width: 8.w),
Text(
'拖动手柄',
style: theme.typography.sm.copyWith(
color: XfjDesignTokens.primary,
fontWeight: FontWeight.w700,
),
),
],
),
),
SizedBox(width: 8.w),
FButton(
style: FButtonStyle.outline,
prefix: FIcon(
FAssets.icons.rotateCcw,
color: XfjDesignTokens.ink,
size: 18.sp,
),
label: Text(
'还原',
style: theme.typography.sm.copyWith(
color: XfjDesignTokens.ink,
fontWeight: FontWeight.w700,
),
),
onPress: onReset,
),
],
),
],
);
}
}
class _QueryMethodColumnHeader extends StatelessWidget {
const _QueryMethodColumnHeader();
@override
Widget build(BuildContext context) {
final style = FTheme.of(context).typography.sm.copyWith(
color: XfjDesignTokens.muted,
fontWeight: FontWeight.w700,
);
return Padding(
padding: EdgeInsets.symmetric(horizontal: 12.w),
child: Row(
children: [
SizedBox(width: 48.w),
SizedBox(width: 8.w),
SizedBox(width: 34.w),
SizedBox(width: 12.w),
SizedBox(width: 42.w),
SizedBox(width: 12.w),
Expanded(child: Text('查询名称', style: style)),
SizedBox(
width: 88.w,
child: Text('默认项', textAlign: TextAlign.center, style: style),
),
SizedBox(width: 112.w),
],
),
);
}
}
class _QueryMethodOrderRow extends StatelessWidget {
const _QueryMethodOrderRow({
required this.method,
required this.index,
required this.isDefault,
this.onMoveUp,
this.onMoveDown,
});
final ReservationRecognitionMethod method;
final int index;
final bool isDefault;
final VoidCallback? onMoveUp;
final VoidCallback? onMoveDown;
@override
Widget build(BuildContext context) {
final theme = FTheme.of(context);
return Container(
constraints: BoxConstraints(minHeight: 64.h),
padding: EdgeInsets.symmetric(horizontal: 12.w),
decoration: BoxDecoration(
color: XfjDesignTokens.surface,
border: Border.all(color: XfjDesignTokens.line),
borderRadius: BorderRadius.circular(XfjDesignTokens.controlRadius),
),
child: Row(
children: [
ReorderableDragStartListener(
index: index,
child: SizedBox(
width: 48.w,
height: 48.h,
child: FIcon(
FAssets.icons.gripVertical,
color: XfjDesignTokens.muted,
size: 22.sp,
).center(),
),
),
SizedBox(width: 8.w),
Container(
width: 34.w,
height: 34.w,
alignment: Alignment.center,
decoration: BoxDecoration(
color: XfjDesignTokens.primarySoft,
borderRadius: BorderRadius.circular(
XfjDesignTokens.controlRadius,
),
),
child: Text(
'${index + 1}',
style: theme.typography.sm.copyWith(
color: XfjDesignTokens.primary,
fontWeight: FontWeight.w700,
fontFeatures: const [FontFeature.tabularFigures()],
),
),
),
SizedBox(width: 12.w),
Container(
width: 42.w,
height: 42.w,
alignment: Alignment.center,
decoration: BoxDecoration(
color: XfjDesignTokens.control,
borderRadius: BorderRadius.circular(
XfjDesignTokens.controlRadius,
),
),
child: FIcon(
method == ReservationRecognitionMethod.card
? FAssets.icons.idCard
: FAssets.icons.scanFace,
color: XfjDesignTokens.ink,
size: 22.sp,
),
),
SizedBox(width: 12.w),
Expanded(
child: Text(
'${method.title}查询',
style: theme.typography.base.copyWith(
color: XfjDesignTokens.ink,
fontWeight: FontWeight.w700,
),
),
),
SizedBox(
width: 88.w,
child: isDefault
? Container(
padding: EdgeInsets.symmetric(
horizontal: 10.w,
vertical: 5.h,
),
decoration: BoxDecoration(
color: XfjDesignTokens.primarySoft,
borderRadius: BorderRadius.circular(
XfjDesignTokens.controlRadius,
),
),
child: Text(
'默认',
textAlign: TextAlign.center,
style: theme.typography.sm.copyWith(
color: XfjDesignTokens.primary,
fontWeight: FontWeight.w700,
),
),
)
: const SizedBox.shrink(),
),
SizedBox(width: 16.w),
_MoveButton(
semanticLabel: '${method.title}查询上移',
icon: FAssets.icons.chevronUp,
onTap: onMoveUp,
),
SizedBox(width: 8.w),
_MoveButton(
semanticLabel: '${method.title}查询下移',
icon: FAssets.icons.chevronDown,
onTap: onMoveDown,
),
],
),
);
}
}
class _MoveButton extends StatelessWidget {
const _MoveButton({
required this.semanticLabel,
required this.icon,
this.onTap,
});
final String semanticLabel;
final SvgAsset icon;
final VoidCallback? onTap;
@override
Widget build(BuildContext context) {
final enabled = onTap != null;
return Semantics(
button: true,
enabled: enabled,
label: semanticLabel,
child: Material(
color: enabled ? XfjDesignTokens.control : XfjDesignTokens.canvas,
borderRadius: BorderRadius.circular(XfjDesignTokens.controlRadius),
child: InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(XfjDesignTokens.controlRadius),
child: SizedBox(
width: 48.w,
height: 48.h,
child: FIcon(
icon,
color: enabled ? XfjDesignTokens.ink : XfjDesignTokens.line,
size: 18.sp,
).center(),
),
),
),
);
}
}
String _pageTitle(TerminalMenuItemType type) => switch (type) {
TerminalMenuItemType.transactions => '交易查询',
TerminalMenuItemType.verificationRecords => '核销记录',
TerminalMenuItemType.statistics => '交易统计',
TerminalMenuItemType.card => '开卡管理',
_ => type.title,
};