fb236018f3
- 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>
100 lines
3.5 KiB
Dart
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)),
|
|
),
|
|
],
|
|
);
|
|
},
|
|
);
|
|
}
|
|
}
|