874 lines
31 KiB
Dart
874 lines
31 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_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),
|
|
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,
|
|
};
|