feat(client): 日期范围改锚定下拉——起止并排双滚轮 + 可键入,不再冻结窗口
- 新增 showDateRangeDropdown:锚定触发控件下方(透明遮罩点外关闭,形态同 DsMenu 下拉),起始/结束两列并排,各带可输入框(键入/回车/失焦归一 yyyy-MM-dd)与滚轮,输入↔滚轮双向联动;共用底栏显示当前范围 + 确定, 起止倒置自动交换;窄屏回落既有两步滚轮弹窗 - WheelDatePanel 加 showFooter/onChanged(滚动即回调),供范围面板复用 - 出入库列表 6 处调用切换:工具栏时间 chip「自定义…」、桌面详搜弹窗、 窄屏详搜 sheet 的日期区间字段均锚定在各自控件下方 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -198,8 +198,8 @@ class _StockInListScreenState extends ConsumerState<StockInListScreen> {
|
||||
n.setDetail(const {});
|
||||
}
|
||||
|
||||
Future<void> _pickDateRange() async {
|
||||
final range = await showWheelDateRange(context, initial: _dateRange);
|
||||
Future<void> _pickDateRange(BuildContext anchor) async {
|
||||
final range = await showDateRangeDropdown(anchor, initial: _dateRange);
|
||||
if (range != null) {
|
||||
setState(() {
|
||||
_dateRange = range;
|
||||
@@ -210,9 +210,9 @@ class _StockInListScreenState extends ConsumerState<StockInListScreen> {
|
||||
}
|
||||
|
||||
/// 入库时间预设(对齐原型:全部时间/近7天/近30天/本月/自定义)。
|
||||
void _setDatePreset(String preset) {
|
||||
void _setDatePreset(BuildContext anchor, String preset) {
|
||||
if (preset == 'custom') {
|
||||
_pickDateRange();
|
||||
_pickDateRange(anchor);
|
||||
return;
|
||||
}
|
||||
final now = DateTime.now();
|
||||
@@ -245,26 +245,29 @@ class _StockInListScreenState extends ConsumerState<StockInListScreen> {
|
||||
ref.read(stockInListProvider.notifier).setDateRange(_startDate, _endDate);
|
||||
}
|
||||
|
||||
Widget _dateMenu(BuildContext ctx) => PopupMenuButton<String>(
|
||||
onSelected: _setDatePreset,
|
||||
offset: const Offset(0, 40),
|
||||
color: ctx.tokens.surface,
|
||||
elevation: 8,
|
||||
shape: RoundedRectangleBorder(
|
||||
side: BorderSide(color: ctx.tokens.border),
|
||||
borderRadius: BorderRadius.circular(AppDims.rMd),
|
||||
Widget _dateMenu(BuildContext ctx) => Builder(
|
||||
// anchorCtx = chip 自身位置:自定义范围下拉锚定在 chip 下方
|
||||
builder: (anchorCtx) => PopupMenuButton<String>(
|
||||
onSelected: (v) => _setDatePreset(anchorCtx, v),
|
||||
offset: const Offset(0, 40),
|
||||
color: ctx.tokens.surface,
|
||||
elevation: 8,
|
||||
shape: RoundedRectangleBorder(
|
||||
side: BorderSide(color: ctx.tokens.border),
|
||||
borderRadius: BorderRadius.circular(AppDims.rMd),
|
||||
),
|
||||
itemBuilder: (_) => const [
|
||||
PopupMenuItem(value: 'all', height: 38, child: Text('全部时间')),
|
||||
PopupMenuItem(value: '7', height: 38, child: Text('近 7 天')),
|
||||
PopupMenuItem(value: '30', height: 38, child: Text('近 30 天')),
|
||||
PopupMenuItem(value: 'month', height: 38, child: Text('本月')),
|
||||
PopupMenuItem(value: 'custom', height: 38, child: Text('自定义…')),
|
||||
],
|
||||
child: DsChip(
|
||||
label: '入库时间',
|
||||
value: _datePresetLabel.isEmpty ? null : _datePresetLabel,
|
||||
onClear: () => _setDatePreset(anchorCtx, 'all')),
|
||||
),
|
||||
itemBuilder: (_) => const [
|
||||
PopupMenuItem(value: 'all', height: 38, child: Text('全部时间')),
|
||||
PopupMenuItem(value: '7', height: 38, child: Text('近 7 天')),
|
||||
PopupMenuItem(value: '30', height: 38, child: Text('近 30 天')),
|
||||
PopupMenuItem(value: 'month', height: 38, child: Text('本月')),
|
||||
PopupMenuItem(value: 'custom', height: 38, child: Text('自定义…')),
|
||||
],
|
||||
child: DsChip(
|
||||
label: '入库时间',
|
||||
value: _datePresetLabel.isEmpty ? null : _datePresetLabel,
|
||||
onClear: () => _setDatePreset('all')),
|
||||
);
|
||||
|
||||
Future<void> _openAdvSearch() async {
|
||||
@@ -1649,8 +1652,8 @@ class _AdvSearchDialogState extends ConsumerState<_AdvSearchDialog> {
|
||||
Navigator.of(context).pop(_AdvResult(d, _status, _range));
|
||||
}
|
||||
|
||||
Future<void> _pickDate() async {
|
||||
final range = await showWheelDateRange(context, initial: _range);
|
||||
Future<void> _pickDate(BuildContext anchor) async {
|
||||
final range = await showDateRangeDropdown(anchor, initial: _range);
|
||||
if (range != null) setState(() => _range = range);
|
||||
}
|
||||
|
||||
@@ -1930,33 +1933,35 @@ class _AdvSearchDialogState extends ConsumerState<_AdvSearchDialog> {
|
||||
final label = has
|
||||
? '${_range!.start.toString().substring(0, 10)} ~ ${_range!.end.toString().substring(0, 10)}'
|
||||
: '全部时间';
|
||||
return InkWell(
|
||||
onTap: _pickDate,
|
||||
borderRadius: BorderRadius.circular(AppDims.rMd),
|
||||
child: Container(
|
||||
height: 38,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 11),
|
||||
decoration: BoxDecoration(
|
||||
color: t.surface,
|
||||
border: Border.all(color: t.border),
|
||||
borderRadius: BorderRadius.circular(AppDims.rMd),
|
||||
),
|
||||
child: Row(children: [
|
||||
Expanded(
|
||||
child: Text(label,
|
||||
style: TextStyle(
|
||||
fontSize: AppDims.fsBody, color: has ? t.text : t.faint)),
|
||||
),
|
||||
if (has)
|
||||
GestureDetector(
|
||||
onTap: () => setState(() => _range = null),
|
||||
child: Icon(LucideIcons.x, size: 14, color: t.muted),
|
||||
)
|
||||
else
|
||||
Icon(LucideIcons.calendar, size: 16, color: t.faint),
|
||||
]),
|
||||
),
|
||||
);
|
||||
return Builder(
|
||||
builder: (anchorCtx) => InkWell(
|
||||
onTap: () => _pickDate(anchorCtx),
|
||||
borderRadius: BorderRadius.circular(AppDims.rMd),
|
||||
child: Container(
|
||||
height: 38,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 11),
|
||||
decoration: BoxDecoration(
|
||||
color: t.surface,
|
||||
border: Border.all(color: t.border),
|
||||
borderRadius: BorderRadius.circular(AppDims.rMd),
|
||||
),
|
||||
child: Row(children: [
|
||||
Expanded(
|
||||
child: Text(label,
|
||||
style: TextStyle(
|
||||
fontSize: AppDims.fsBody,
|
||||
color: has ? t.text : t.faint)),
|
||||
),
|
||||
if (has)
|
||||
GestureDetector(
|
||||
onTap: () => setState(() => _range = null),
|
||||
child: Icon(LucideIcons.x, size: 14, color: t.muted),
|
||||
)
|
||||
else
|
||||
Icon(LucideIcons.calendar, size: 16, color: t.faint),
|
||||
]),
|
||||
),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2055,8 +2060,8 @@ class _AdvSearchSheetState extends ConsumerState<_AdvSearchSheet> {
|
||||
Navigator.of(context).pop(_AdvResult(d, _status, _range));
|
||||
}
|
||||
|
||||
Future<void> _pickDate() async {
|
||||
final range = await showWheelDateRange(context, initial: _range);
|
||||
Future<void> _pickDate(BuildContext anchor) async {
|
||||
final range = await showDateRangeDropdown(anchor, initial: _range);
|
||||
if (range != null) setState(() => _range = range);
|
||||
}
|
||||
|
||||
@@ -2171,37 +2176,39 @@ class _AdvSearchSheetState extends ConsumerState<_AdvSearchSheet> {
|
||||
final statusSelIdx = _statusCodes.indexOf(_status);
|
||||
|
||||
final dateHas = _range != null;
|
||||
final dateField = InkWell(
|
||||
onTap: _pickDate,
|
||||
borderRadius: BorderRadius.circular(AppDims.rMd),
|
||||
child: Container(
|
||||
height: 42,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 11),
|
||||
decoration: BoxDecoration(
|
||||
color: t.surface,
|
||||
border: Border.all(color: t.border),
|
||||
borderRadius: BorderRadius.circular(AppDims.rMd),
|
||||
),
|
||||
child: Row(children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
dateHas
|
||||
? '${_range!.start.toString().substring(0, 10)} ~ ${_range!.end.toString().substring(0, 10)}'
|
||||
: '全部时间',
|
||||
style: TextStyle(
|
||||
fontSize: AppDims.fsBody, color: dateHas ? t.text : t.faint),
|
||||
),
|
||||
),
|
||||
if (dateHas)
|
||||
GestureDetector(
|
||||
onTap: () => setState(() => _range = null),
|
||||
child: Icon(LucideIcons.x, size: 14, color: t.muted),
|
||||
)
|
||||
else
|
||||
Icon(LucideIcons.calendar, size: 16, color: t.faint),
|
||||
]),
|
||||
),
|
||||
);
|
||||
final dateField = Builder(
|
||||
builder: (anchorCtx) => InkWell(
|
||||
onTap: () => _pickDate(anchorCtx),
|
||||
borderRadius: BorderRadius.circular(AppDims.rMd),
|
||||
child: Container(
|
||||
height: 42,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 11),
|
||||
decoration: BoxDecoration(
|
||||
color: t.surface,
|
||||
border: Border.all(color: t.border),
|
||||
borderRadius: BorderRadius.circular(AppDims.rMd),
|
||||
),
|
||||
child: Row(children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
dateHas
|
||||
? '${_range!.start.toString().substring(0, 10)} ~ ${_range!.end.toString().substring(0, 10)}'
|
||||
: '全部时间',
|
||||
style: TextStyle(
|
||||
fontSize: AppDims.fsBody,
|
||||
color: dateHas ? t.text : t.faint),
|
||||
),
|
||||
),
|
||||
if (dateHas)
|
||||
GestureDetector(
|
||||
onTap: () => setState(() => _range = null),
|
||||
child: Icon(LucideIcons.x, size: 14, color: t.muted),
|
||||
)
|
||||
else
|
||||
Icon(LucideIcons.calendar, size: 16, color: t.faint),
|
||||
]),
|
||||
),
|
||||
));
|
||||
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
|
||||
@@ -1713,8 +1713,8 @@ class _AdvancedSearchDialogState extends ConsumerState<_AdvancedSearchDialog> {
|
||||
));
|
||||
}
|
||||
|
||||
Future<void> _pickDate() async {
|
||||
final range = await showWheelDateRange(context, initial: _dateRange);
|
||||
Future<void> _pickDate(BuildContext anchor) async {
|
||||
final range = await showDateRangeDropdown(anchor, initial: _dateRange);
|
||||
if (range != null) setState(() => _dateRange = range);
|
||||
}
|
||||
|
||||
@@ -1838,38 +1838,39 @@ class _AdvancedSearchDialogState extends ConsumerState<_AdvancedSearchDialog> {
|
||||
}
|
||||
|
||||
final dateHas = _dateRange != null;
|
||||
Widget dateField() => InkWell(
|
||||
onTap: _pickDate,
|
||||
borderRadius: BorderRadius.circular(AppDims.rMd),
|
||||
child: Container(
|
||||
height: 38,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 11),
|
||||
decoration: BoxDecoration(
|
||||
color: t.surface,
|
||||
border: Border.all(color: t.border),
|
||||
Widget dateField() => Builder(
|
||||
builder: (anchorCtx) => InkWell(
|
||||
onTap: () => _pickDate(anchorCtx),
|
||||
borderRadius: BorderRadius.circular(AppDims.rMd),
|
||||
),
|
||||
child: Row(children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
dateHas
|
||||
? '${DateFormat('yyyy-MM-dd').format(_dateRange!.start)} ~ ${DateFormat('yyyy-MM-dd').format(_dateRange!.end)}'
|
||||
: '全部时间',
|
||||
style: TextStyle(
|
||||
fontSize: AppDims.fsBody,
|
||||
color: dateHas ? t.text : t.faint),
|
||||
child: Container(
|
||||
height: 38,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 11),
|
||||
decoration: BoxDecoration(
|
||||
color: t.surface,
|
||||
border: Border.all(color: t.border),
|
||||
borderRadius: BorderRadius.circular(AppDims.rMd),
|
||||
),
|
||||
child: Row(children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
dateHas
|
||||
? '${DateFormat('yyyy-MM-dd').format(_dateRange!.start)} ~ ${DateFormat('yyyy-MM-dd').format(_dateRange!.end)}'
|
||||
: '全部时间',
|
||||
style: TextStyle(
|
||||
fontSize: AppDims.fsBody,
|
||||
color: dateHas ? t.text : t.faint),
|
||||
),
|
||||
),
|
||||
if (dateHas)
|
||||
GestureDetector(
|
||||
onTap: () => setState(() => _dateRange = null),
|
||||
child: Icon(LucideIcons.x, size: 14, color: t.muted),
|
||||
)
|
||||
else
|
||||
Icon(LucideIcons.calendar, size: 16, color: t.faint),
|
||||
]),
|
||||
),
|
||||
if (dateHas)
|
||||
GestureDetector(
|
||||
onTap: () => setState(() => _dateRange = null),
|
||||
child: Icon(LucideIcons.x, size: 14, color: t.muted),
|
||||
)
|
||||
else
|
||||
Icon(LucideIcons.calendar, size: 16, color: t.faint),
|
||||
]),
|
||||
),
|
||||
);
|
||||
));
|
||||
|
||||
return AlertDialog(
|
||||
backgroundColor: t.surface,
|
||||
@@ -2135,8 +2136,8 @@ class _AdvSearchSheetState extends ConsumerState<_AdvSearchSheet> {
|
||||
));
|
||||
}
|
||||
|
||||
Future<void> _pickDate() async {
|
||||
final range = await showWheelDateRange(context, initial: _dateRange);
|
||||
Future<void> _pickDate(BuildContext anchor) async {
|
||||
final range = await showDateRangeDropdown(anchor, initial: _dateRange);
|
||||
if (range != null) setState(() => _dateRange = range);
|
||||
}
|
||||
|
||||
@@ -2225,37 +2226,39 @@ class _AdvSearchSheetState extends ConsumerState<_AdvSearchSheet> {
|
||||
final statusSelIdx = _statusCodes.indexOf(_status);
|
||||
|
||||
final dateHas = _dateRange != null;
|
||||
final dateField = InkWell(
|
||||
onTap: _pickDate,
|
||||
borderRadius: BorderRadius.circular(AppDims.rMd),
|
||||
child: Container(
|
||||
height: 42,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 11),
|
||||
decoration: BoxDecoration(
|
||||
color: t.surface,
|
||||
border: Border.all(color: t.border),
|
||||
borderRadius: BorderRadius.circular(AppDims.rMd),
|
||||
),
|
||||
child: Row(children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
dateHas
|
||||
? '${DateFormat('yyyy-MM-dd').format(_dateRange!.start)} ~ ${DateFormat('yyyy-MM-dd').format(_dateRange!.end)}'
|
||||
: '全部时间',
|
||||
style: TextStyle(
|
||||
fontSize: AppDims.fsBody, color: dateHas ? t.text : t.faint),
|
||||
),
|
||||
),
|
||||
if (dateHas)
|
||||
GestureDetector(
|
||||
onTap: () => setState(() => _dateRange = null),
|
||||
child: Icon(LucideIcons.x, size: 14, color: t.muted),
|
||||
)
|
||||
else
|
||||
Icon(LucideIcons.calendar, size: 16, color: t.faint),
|
||||
]),
|
||||
),
|
||||
);
|
||||
final dateField = Builder(
|
||||
builder: (anchorCtx) => InkWell(
|
||||
onTap: () => _pickDate(anchorCtx),
|
||||
borderRadius: BorderRadius.circular(AppDims.rMd),
|
||||
child: Container(
|
||||
height: 42,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 11),
|
||||
decoration: BoxDecoration(
|
||||
color: t.surface,
|
||||
border: Border.all(color: t.border),
|
||||
borderRadius: BorderRadius.circular(AppDims.rMd),
|
||||
),
|
||||
child: Row(children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
dateHas
|
||||
? '${DateFormat('yyyy-MM-dd').format(_dateRange!.start)} ~ ${DateFormat('yyyy-MM-dd').format(_dateRange!.end)}'
|
||||
: '全部时间',
|
||||
style: TextStyle(
|
||||
fontSize: AppDims.fsBody,
|
||||
color: dateHas ? t.text : t.faint),
|
||||
),
|
||||
),
|
||||
if (dateHas)
|
||||
GestureDetector(
|
||||
onTap: () => setState(() => _dateRange = null),
|
||||
child: Icon(LucideIcons.x, size: 14, color: t.muted),
|
||||
)
|
||||
else
|
||||
Icon(LucideIcons.calendar, size: 16, color: t.faint),
|
||||
]),
|
||||
),
|
||||
));
|
||||
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
|
||||
Reference in New Issue
Block a user