Files
jiu/client/lib/widgets/date_picker_field.dart
wangjia fb236018f3 refactor(client): 日期选择统一为扁平组件——输入框+滚轮同宽居中入面板,桌面/移动全覆盖
- wheel_date_picker.dart 重构为统一组件:单日期 showDsDateDropdown(面板=
  可输入框+三列滚轮+今天/确定)、范围 showDateRangeDropdown(起始|结束双窗格
  并排+共用底栏);桌面锚定下拉(透明遮罩不冻结窗口)、窄屏底部 sheet 同内容
  (范围纵排)——同一套窗格两端复用
- 扁平化:输入框与滚轮同宽 248、文字水平垂直居中(mono)、去掉全部阴影/立体
  效果(滚轮外框仅 1px 边框,中心高亮带去描边)
- DsDateCell(录单日期格)与 DatePickerField(登记收支)改为触发器:点选/
  回车打开统一面板,键入在面板内完成;出库工具栏时间 chip 补切到新组件
- 旧 showWheelDatePicker/showWheelDateRange 两步弹窗删除;组件测试改写为
  新契约(触发器+面板键入+词法归一),表单 golden 随形态更新

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 17:49:17 +08:00

100 lines
3.5 KiB
Dart

import 'package:lucide_icons_flutter/lucide_icons.dart';
import 'package:flutter/material.dart';
import '../core/theme/app_dims.g.dart';
import '../core/theme/app_fonts.dart';
import '../core/theme/context_tokens.dart';
import '../core/utils/date_util.dart';
import 'wheel_date_picker.dart';
/// 日期字段(登记收支等表单用):点选打开统一日期下拉(面板内含可输入框 +
/// 三列滚轮,见 wheel_date_picker.dart,桌面锚定下拉 / 窄屏底部 sheet)。
/// 值以 `yyyy-MM-dd` 字符串进出([value] / [onChanged])。
class DatePickerField extends StatefulWidget {
final String? value; // yyyy-MM-dd
final ValueChanged<String?> onChanged;
final bool isRequired;
final String? label;
const DatePickerField({
super.key,
this.value,
required this.onChanged,
this.isRequired = false,
this.label,
});
@override
State<DatePickerField> createState() => _DatePickerFieldState();
}
class _DatePickerFieldState extends State<DatePickerField> {
Future<void> _pick(
BuildContext anchorCtx, FormFieldState<String> field) async {
final d =
await showDsDateDropdown(anchorCtx, initial: parseYmd(widget.value));
if (d != null) {
final v = formatYmd(d);
field.didChange(v);
widget.onChanged(v);
}
}
@override
Widget build(BuildContext context) {
final t = context.tokens;
return FormField<String>(
initialValue: widget.value,
validator: widget.isRequired
? (_) =>
(widget.value == null || widget.value!.isEmpty) ? '请输入日期' : null
: null,
builder: (field) {
final has = widget.value != null && widget.value!.isNotEmpty;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Builder(
builder: (anchorCtx) => InkWell(
onTap: () => _pick(anchorCtx, field),
borderRadius: BorderRadius.circular(AppDims.rMd),
child: Container(
height: 38,
padding: const EdgeInsets.symmetric(horizontal: 10),
decoration: BoxDecoration(
color: t.surface,
border:
Border.all(color: field.hasError ? t.danger : t.border),
borderRadius: BorderRadius.circular(AppDims.rMd),
),
child: Row(children: [
Expanded(
child: Text(
has ? widget.value! : '年-月-日',
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: AppDims.fsBody,
color: has ? t.text : t.faint,
fontFamily: has ? AppFonts.mono : null,
fontFamilyFallback:
has ? AppFonts.monoFallback : null),
),
),
Icon(LucideIcons.calendar, size: 16, color: t.muted),
]),
),
),
),
if (field.errorText != null)
Padding(
padding: const EdgeInsets.only(top: 4, left: 2),
child: Text(field.errorText!,
style: TextStyle(fontSize: AppDims.fsXs, color: t.danger)),
),
],
);
},
);
}
}