This commit is contained in:
dzc committed 2026-08-05 18:38:18 +08:00
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();
}
}