import 'package:flutter/material.dart'; import '../core/theme/app_theme.dart'; import '../core/utils/date_util.dart'; /// 年/月/日 三个可编辑下拉框的日期选择组件,替代原生 showDatePicker。 /// /// - 每个下拉是 Material 3 `DropdownMenu`:点开可选,键入数字即过滤定位 /// (年/月/日均为有界域,条目覆盖全范围,等价于「手动输入数字」)。 /// - 改月/年时把超出当月的「日」自动 clamp 回当月最大值。 /// - 值以 `yyyy-MM-dd` 字符串进出([value] / [onChanged]);三者未填齐时回调 null。 class DatePickerField extends StatefulWidget { final String? value; // yyyy-MM-dd final ValueChanged onChanged; final bool isRequired; final String? label; const DatePickerField({ super.key, this.value, required this.onChanged, this.isRequired = false, this.label, }); @override State createState() => _DatePickerFieldState(); } class _DatePickerFieldState extends State { int? _year; int? _month; int? _day; @override void initState() { super.initState(); _parse(widget.value); } @override void didUpdateWidget(DatePickerField old) { super.didUpdateWidget(old); if (old.value != widget.value) { _parse(widget.value); } } void _parse(String? v) { final d = parseYmd(v); _year = d?.year; _month = d?.month; _day = d?.day; } int get _daysInMonth { final y = _year ?? DateTime.now().year; final m = _month ?? 1; return DateTime(y, m + 1, 0).day; // 下月第 0 天 = 当月最后一天 } String? get _composed => composeYmd(_year, _month, _day); void _emit(FormFieldState field) { // 改月/年后把超界的日 clamp 回当月最大值(与 composeYmd 一致,State 同步显示) if (_day != null && _day! > _daysInMonth) { _day = _daysInMonth; } final v = _composed; field.didChange(v); widget.onChanged(v); setState(() {}); } @override Widget build(BuildContext context) { final now = DateTime.now(); final years = [for (var y = now.year - 20; y <= now.year + 5; y++) y]; final months = [for (var m = 1; m <= 12; m++) m]; final days = [for (var d = 1; d <= _daysInMonth; d++) d]; // 每个下拉用 Expanded 撑满分得的宽度(expandedInsets:zero 让 DropdownMenu 填满父级), // trailingIcon 用紧凑小箭头,避免默认大图标按钮挤掉数字(曾导致「2026」被裁成「007」)。 Widget menu({ required String label, required int? value, required List items, required ValueChanged onSel, required FormFieldState field, }) { return DropdownMenu( initialSelection: value, label: Text(label, style: const TextStyle(fontSize: 11)), enableFilter: true, requestFocusOnTap: true, textStyle: const TextStyle(fontSize: 13), menuHeight: 280, expandedInsets: EdgeInsets.zero, trailingIcon: const Icon(Icons.arrow_drop_down, size: 18), selectedTrailingIcon: const Icon(Icons.arrow_drop_up, size: 18), inputDecorationTheme: const InputDecorationTheme( isDense: true, contentPadding: EdgeInsets.symmetric(horizontal: 6, vertical: 8), ), dropdownMenuEntries: [ for (final i in items) DropdownMenuEntry(value: i, label: '$i'), ], onSelected: onSel, ); } return FormField( initialValue: widget.value, validator: widget.isRequired ? (v) => (_composed == null ? '请选择日期' : null) : null, builder: (field) { return Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ Row( children: [ Expanded( flex: 5, child: menu( label: '年', value: _year, items: years, field: field, onSel: (v) { _year = v; _emit(field); }, ), ), const SizedBox(width: 6), Expanded( flex: 4, child: menu( label: '月', value: _month, items: months, field: field, onSel: (v) { _month = v; _emit(field); }, ), ), const SizedBox(width: 6), Expanded( flex: 4, child: menu( label: '日', value: _day, items: days, field: field, onSel: (v) { _day = v; _emit(field); }, ), ), ], ), if (field.errorText != null) Padding( padding: const EdgeInsets.only(left: 12, top: 4), child: Text( field.errorText!, style: const TextStyle(fontSize: 11, color: AppTheme.danger), ), ), ], ); }, ); } }