11
This commit is contained in:
1 parent
9933ce84a0
commit
dfdbc5a3aa
1 file changed
-176
@@ -1,176 +0,0 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:ducafe_ui_core/ducafe_ui_core.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:forui/forui.dart';
|
||||
|
||||
import 'package:pj_supermarket_cloud/common/index.dart';
|
||||
|
||||
/// 查询菜单的低强调餐段选择器,仅在操作员主动点开时展示完整餐段选项。
|
||||
class QueryMealSlotSelector extends StatefulWidget {
|
||||
/// 创建餐段选择器;餐段状态与持久化由页面 Controller 负责,组件只处理展示和浮层交互。
|
||||
const QueryMealSlotSelector({
|
||||
super.key,
|
||||
required this.mealSlots,
|
||||
required this.selected,
|
||||
required this.enabled,
|
||||
required this.onSelected,
|
||||
});
|
||||
|
||||
/// 查询前允许选择的餐段列表,顺序遵循后端稳定业务配置。
|
||||
final List<ReservationMealSlot> mealSlots;
|
||||
|
||||
/// 当前已选择并在触发器中持续可见的餐段。
|
||||
final ReservationMealSlot selected;
|
||||
|
||||
/// 查询请求进行中为 `false`,此时关闭浮层并禁止改变当前轮次的目标餐段。
|
||||
final bool enabled;
|
||||
|
||||
/// 操作员选择餐段后的回调,状态更新及本机缓存写入由 Controller 统一处理。
|
||||
final ValueChanged<ReservationMealSlot> onSelected;
|
||||
|
||||
/// 创建管理 Forui 浮层生命周期的组件状态。
|
||||
@override
|
||||
State<QueryMealSlotSelector> createState() => _QueryMealSlotSelectorState();
|
||||
}
|
||||
|
||||
/// 餐段选择浮层状态,确保查询锁定或组件销毁时不会遗留终端 Overlay。
|
||||
class _QueryMealSlotSelectorState extends State<QueryMealSlotSelector>
|
||||
with SingleTickerProviderStateMixin {
|
||||
/// Forui 浮层控制器,动画时长遵循终端 150-300ms 的轻量交互节奏。
|
||||
late final FPopoverController _popoverController;
|
||||
|
||||
/// 初始化并持有当前组件唯一的浮层控制器。
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_popoverController = FPopoverController(
|
||||
vsync: this,
|
||||
animationDuration: const Duration(milliseconds: 160),
|
||||
);
|
||||
}
|
||||
|
||||
/// 查询状态切换为禁用时主动收起浮层,避免仍可点击旧餐段选项。
|
||||
@override
|
||||
void didUpdateWidget(covariant QueryMealSlotSelector oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (oldWidget.enabled && !widget.enabled && _popoverController.shown) {
|
||||
unawaited(_popoverController.hide());
|
||||
}
|
||||
}
|
||||
|
||||
/// 构建单行弱提示触发器,并通过 Forui 浮层渐进展示完整餐段列表。
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final trigger = <Widget>[
|
||||
Text(
|
||||
'餐段',
|
||||
style: TextStyle(
|
||||
color: XfjDesignTokens.muted,
|
||||
fontSize: 12.sp,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
Text(
|
||||
widget.selected.title,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
color: widget.enabled ? XfjDesignTokens.ink : XfjDesignTokens.muted,
|
||||
fontSize: 14.sp,
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
),
|
||||
FIcon(
|
||||
FAssets.icons.chevronsUpDown,
|
||||
color: XfjDesignTokens.muted,
|
||||
size: 16.w,
|
||||
),
|
||||
]
|
||||
.toRowSpace(space: 6.w)
|
||||
.padding(horizontal: 12.w)
|
||||
// 外观保持紧凑,但完整行仍满足 Android 终端 48dp 的触控下限。
|
||||
.constrained(minHeight: 48)
|
||||
.decorated(
|
||||
color: XfjDesignTokens.canvas,
|
||||
border: Border.all(color: XfjDesignTokens.line),
|
||||
borderRadius: BorderRadius.circular(XfjDesignTokens.controlRadius),
|
||||
);
|
||||
|
||||
return FPopoverMenu(
|
||||
popoverController: _popoverController,
|
||||
menuAnchor: Alignment.topRight,
|
||||
childAnchor: Alignment.bottomRight,
|
||||
directionPadding: true,
|
||||
semanticLabel: '预查询餐段选项',
|
||||
divider: FTileDivider.none,
|
||||
menu: <FTileGroupMixin<FTileMixin>>[
|
||||
FTileGroup(
|
||||
divider: FTileDivider.none,
|
||||
children: <FTileMixin>[
|
||||
...widget.mealSlots.map(_mealSlotTile),
|
||||
],
|
||||
),
|
||||
],
|
||||
child: Semantics(
|
||||
key: const ValueKey('reservation-meal-slot-trigger'),
|
||||
button: true,
|
||||
enabled: widget.enabled,
|
||||
label: '预查询餐段',
|
||||
value: widget.selected.title,
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
onTap: widget.enabled && widget.mealSlots.isNotEmpty
|
||||
? _popoverController.toggle
|
||||
: null,
|
||||
borderRadius: BorderRadius.circular(
|
||||
XfjDesignTokens.controlRadius,
|
||||
),
|
||||
child: trigger,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// 构建浮层中的单个餐段选项,勾选图标与读屏文案共同表达当前选择。
|
||||
FTile _mealSlotTile(ReservationMealSlot slot) {
|
||||
final selected = slot == widget.selected;
|
||||
return FTile(
|
||||
key: ValueKey('reservation-meal-slot-option-${slot.code}'),
|
||||
semanticLabel: selected ? '${slot.title},已选择' : '选择${slot.title}',
|
||||
title: Text(
|
||||
slot.title,
|
||||
style: TextStyle(
|
||||
color: selected ? XfjDesignTokens.primary : XfjDesignTokens.ink,
|
||||
fontSize: 14.sp,
|
||||
fontWeight: selected ? FontWeight.w700 : FontWeight.w600,
|
||||
),
|
||||
).alignment(Alignment.centerLeft).constrained(minHeight: 48),
|
||||
suffixIcon: selected
|
||||
? FIcon(
|
||||
FAssets.icons.check,
|
||||
color: XfjDesignTokens.primary,
|
||||
size: 18.w,
|
||||
)
|
||||
: SizedBox(width: 18.w),
|
||||
onPress: () => _select(slot),
|
||||
);
|
||||
}
|
||||
|
||||
/// 提交本次选择并立即收起浮层;相同餐段也收起但不触发重复缓存写入。
|
||||
void _select(ReservationMealSlot slot) {
|
||||
if (!widget.enabled) return;
|
||||
if (slot != widget.selected) widget.onSelected(slot);
|
||||
unawaited(_popoverController.hide());
|
||||
}
|
||||
|
||||
/// 释放动画资源,避免多次进出查询菜单后累积 Ticker。
|
||||
@override
|
||||
void dispose() {
|
||||
_popoverController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user