|
@@ -3,6 +3,8 @@
|
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter/material.dart';
|
|
|
import 'package:flutter/services.dart';
|
|
import 'package:flutter/services.dart';
|
|
|
import 'package:tdesign_flutter/tdesign_flutter.dart';
|
|
import 'package:tdesign_flutter/tdesign_flutter.dart';
|
|
|
|
|
+import '../../../core/i18n/app_localizations.dart';
|
|
|
|
|
+import '../../../shared/widgets/loading_dialog.dart';
|
|
|
import '../../../core/theme/app_colors.dart';
|
|
import '../../../core/theme/app_colors.dart';
|
|
|
import '../../../core/theme/app_colors_extension.dart';
|
|
import '../../../core/theme/app_colors_extension.dart';
|
|
|
|
|
|
|
@@ -12,12 +14,14 @@ import '../../../core/theme/app_colors_extension.dart';
|
|
|
class ReturnCarDialog extends StatefulWidget {
|
|
class ReturnCarDialog extends StatefulWidget {
|
|
|
final String billNo;
|
|
final String billNo;
|
|
|
final int odometerBegin; // 出车里程,用于对比
|
|
final int odometerBegin; // 出车里程,用于对比
|
|
|
- final Function(Map<String, dynamic>) onSubmit; // 提交回调
|
|
|
|
|
|
|
+ final DateTime? recordDd; // 录入日期,归还时间必须大于此值
|
|
|
|
|
+ final Future<bool> Function(Map<String, dynamic>) onSubmit; // 提交回调,返回 true=成功
|
|
|
|
|
|
|
|
const ReturnCarDialog({
|
|
const ReturnCarDialog({
|
|
|
super.key,
|
|
super.key,
|
|
|
required this.billNo,
|
|
required this.billNo,
|
|
|
required this.odometerBegin,
|
|
required this.odometerBegin,
|
|
|
|
|
+ this.recordDd,
|
|
|
required this.onSubmit,
|
|
required this.onSubmit,
|
|
|
});
|
|
});
|
|
|
|
|
|
|
@@ -26,7 +30,8 @@ class ReturnCarDialog extends StatefulWidget {
|
|
|
BuildContext context, {
|
|
BuildContext context, {
|
|
|
required String billNo,
|
|
required String billNo,
|
|
|
required int odometerBegin,
|
|
required int odometerBegin,
|
|
|
- required Function(Map<String, dynamic>) onSubmit,
|
|
|
|
|
|
|
+ DateTime? recordDd,
|
|
|
|
|
+ required Future<bool> Function(Map<String, dynamic>) onSubmit,
|
|
|
}) {
|
|
}) {
|
|
|
FocusScope.of(context).unfocus();
|
|
FocusScope.of(context).unfocus();
|
|
|
Navigator.push(
|
|
Navigator.push(
|
|
@@ -37,6 +42,7 @@ class ReturnCarDialog extends StatefulWidget {
|
|
|
builder: (_) => ReturnCarDialog(
|
|
builder: (_) => ReturnCarDialog(
|
|
|
billNo: billNo,
|
|
billNo: billNo,
|
|
|
odometerBegin: odometerBegin,
|
|
odometerBegin: odometerBegin,
|
|
|
|
|
+ recordDd: recordDd,
|
|
|
onSubmit: onSubmit,
|
|
onSubmit: onSubmit,
|
|
|
),
|
|
),
|
|
|
),
|
|
),
|
|
@@ -64,13 +70,17 @@ class _ReturnCarDialogState extends State<ReturnCarDialog> {
|
|
|
// 提交中
|
|
// 提交中
|
|
|
bool _submitting = false;
|
|
bool _submitting = false;
|
|
|
|
|
|
|
|
|
|
+ AppLocalizations get l10n => AppLocalizations.of(context);
|
|
|
|
|
+
|
|
|
@override
|
|
@override
|
|
|
void initState() {
|
|
void initState() {
|
|
|
super.initState();
|
|
super.initState();
|
|
|
final now = DateTime.now();
|
|
final now = DateTime.now();
|
|
|
|
|
+ final minDt = widget.recordDd ?? now;
|
|
|
|
|
+ final initDt = now.isAfter(minDt) ? now : minDt;
|
|
|
_returnTime =
|
|
_returnTime =
|
|
|
- '${now.year}-${now.month.toString().padLeft(2, '0')}-${now.day.toString().padLeft(2, '0')} '
|
|
|
|
|
- '${now.hour.toString().padLeft(2, '0')}:${now.minute.toString().padLeft(2, '0')}:${now.second.toString().padLeft(2, '0')}';
|
|
|
|
|
|
|
+ '${initDt.year}-${initDt.month.toString().padLeft(2, '0')}-${initDt.day.toString().padLeft(2, '0')} '
|
|
|
|
|
+ '${initDt.hour.toString().padLeft(2, '0')}:${initDt.minute.toString().padLeft(2, '0')}:${initDt.second.toString().padLeft(2, '0')}';
|
|
|
_odometerFocus.addListener(() => _ensureVisible(_odometerFocus));
|
|
_odometerFocus.addListener(() => _ensureVisible(_odometerFocus));
|
|
|
_amtnFocus.addListener(() => _ensureVisible(_amtnFocus));
|
|
_amtnFocus.addListener(() => _ensureVisible(_amtnFocus));
|
|
|
_remarkFocus.addListener(() => _ensureVisible(_remarkFocus));
|
|
_remarkFocus.addListener(() => _ensureVisible(_remarkFocus));
|
|
@@ -115,16 +125,17 @@ class _ReturnCarDialogState extends State<ReturnCarDialog> {
|
|
|
super.dispose();
|
|
super.dispose();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- void _confirm() {
|
|
|
|
|
|
|
+ Future<void> _confirm() async {
|
|
|
|
|
+ FocusScope.of(context).unfocus();
|
|
|
final odometerText = _odometerCtrl.text.trim();
|
|
final odometerText = _odometerCtrl.text.trim();
|
|
|
if (odometerText.isEmpty) {
|
|
if (odometerText.isEmpty) {
|
|
|
- TDToast.showText('请输入还车里程', context: context);
|
|
|
|
|
|
|
+ TDToast.showText(l10n.get('enterReturnOdometer'), context: context);
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
final odometerEnd = int.tryParse(odometerText) ?? 0;
|
|
final odometerEnd = int.tryParse(odometerText) ?? 0;
|
|
|
if (odometerEnd <= widget.odometerBegin) {
|
|
if (odometerEnd <= widget.odometerBegin) {
|
|
|
TDToast.showText(
|
|
TDToast.showText(
|
|
|
- '还车里程必须大于出车里程(${widget.odometerBegin} km)',
|
|
|
|
|
|
|
+ '${l10n.get('returnOdometerCheck')}(${widget.odometerBegin} km)',
|
|
|
context: context,
|
|
context: context,
|
|
|
);
|
|
);
|
|
|
return;
|
|
return;
|
|
@@ -134,13 +145,26 @@ class _ReturnCarDialogState extends State<ReturnCarDialog> {
|
|
|
final amtn = double.tryParse(amtnText) ?? 0;
|
|
final amtn = double.tryParse(amtnText) ?? 0;
|
|
|
|
|
|
|
|
setState(() => _submitting = true);
|
|
setState(() => _submitting = true);
|
|
|
|
|
+ LoadingDialog.show(context, text: l10n.get('returning'));
|
|
|
|
|
|
|
|
- widget.onSubmit({
|
|
|
|
|
|
|
+ final ok = await widget.onSubmit({
|
|
|
'returnTime': _returnTime,
|
|
'returnTime': _returnTime,
|
|
|
'odometerEnd': odometerEnd,
|
|
'odometerEnd': odometerEnd,
|
|
|
'amtn': amtn,
|
|
'amtn': amtn,
|
|
|
'remark': _remarkCtrl.text.trim(),
|
|
'remark': _remarkCtrl.text.trim(),
|
|
|
});
|
|
});
|
|
|
|
|
+
|
|
|
|
|
+ if (!mounted) return;
|
|
|
|
|
+ LoadingDialog.hide(context);
|
|
|
|
|
+ if (ok) {
|
|
|
|
|
+ // 延迟一帧关闭弹窗,等待焦点从 loading 遮罩归还完毕,
|
|
|
|
|
+ // 避免 Navigator.pop 被键盘/焦点恢复打断
|
|
|
|
|
+ WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
|
|
|
+ if (mounted) Navigator.pop(context);
|
|
|
|
|
+ });
|
|
|
|
|
+ } else {
|
|
|
|
|
+ setState(() => _submitting = false);
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
@override
|
|
@@ -236,7 +260,7 @@ class _ReturnCarDialogState extends State<ReturnCarDialog> {
|
|
|
Expanded(
|
|
Expanded(
|
|
|
child: Center(
|
|
child: Center(
|
|
|
child: Text(
|
|
child: Text(
|
|
|
- '还车登记',
|
|
|
|
|
|
|
+ l10n.get('returnCarRegister'),
|
|
|
style: TextStyle(
|
|
style: TextStyle(
|
|
|
fontSize: AppFontSizes.title,
|
|
fontSize: AppFontSizes.title,
|
|
|
fontWeight: FontWeight.w600,
|
|
fontWeight: FontWeight.w600,
|
|
@@ -266,9 +290,9 @@ class _ReturnCarDialogState extends State<ReturnCarDialog> {
|
|
|
// ── 实际归还时间 ──
|
|
// ── 实际归还时间 ──
|
|
|
Widget _buildReturnTimePicker(AppColorsExtension colors) {
|
|
Widget _buildReturnTimePicker(AppColorsExtension colors) {
|
|
|
return _datePickerCard(
|
|
return _datePickerCard(
|
|
|
- label: '实际归还时间',
|
|
|
|
|
|
|
+ label: l10n.get('actualReturnTime'),
|
|
|
value: _returnTime,
|
|
value: _returnTime,
|
|
|
- hint: '请选择归还时间',
|
|
|
|
|
|
|
+ hint: l10n.get('selectReturnTimeHint'),
|
|
|
colors: colors,
|
|
colors: colors,
|
|
|
required: true,
|
|
required: true,
|
|
|
onPick: (d) => setState(() => _returnTime = d),
|
|
onPick: (d) => setState(() => _returnTime = d),
|
|
@@ -278,10 +302,10 @@ class _ReturnCarDialogState extends State<ReturnCarDialog> {
|
|
|
// ── 还车里程 ──
|
|
// ── 还车里程 ──
|
|
|
Widget _buildOdometerInput(AppColorsExtension colors) {
|
|
Widget _buildOdometerInput(AppColorsExtension colors) {
|
|
|
return _inputCard(
|
|
return _inputCard(
|
|
|
- label: '还车里程',
|
|
|
|
|
|
|
+ label: l10n.get('returnOdometer'),
|
|
|
required: true,
|
|
required: true,
|
|
|
controller: _odometerCtrl,
|
|
controller: _odometerCtrl,
|
|
|
- hintText: '> ${widget.odometerBegin} km',
|
|
|
|
|
|
|
+ hintText: '> ${widget.odometerBegin}',
|
|
|
suffixText: 'km',
|
|
suffixText: 'km',
|
|
|
keyboardType: TextInputType.number,
|
|
keyboardType: TextInputType.number,
|
|
|
inputFormatters: [FilteringTextInputFormatter.allow(RegExp(r'^\d*$'))],
|
|
inputFormatters: [FilteringTextInputFormatter.allow(RegExp(r'^\d*$'))],
|
|
@@ -292,11 +316,10 @@ class _ReturnCarDialogState extends State<ReturnCarDialog> {
|
|
|
// ── 实际费用 ──
|
|
// ── 实际费用 ──
|
|
|
Widget _buildAmtnInput(AppColorsExtension colors) {
|
|
Widget _buildAmtnInput(AppColorsExtension colors) {
|
|
|
return _inputCard(
|
|
return _inputCard(
|
|
|
- label: '实际费用',
|
|
|
|
|
|
|
+ label: l10n.get('amtn'),
|
|
|
required: false,
|
|
required: false,
|
|
|
controller: _amtnCtrl,
|
|
controller: _amtnCtrl,
|
|
|
- hintText: '路桥费/停车费等',
|
|
|
|
|
- suffixText: '元',
|
|
|
|
|
|
|
+ hintText: l10n.get('enterActualCost'),
|
|
|
keyboardType: const TextInputType.numberWithOptions(decimal: true),
|
|
keyboardType: const TextInputType.numberWithOptions(decimal: true),
|
|
|
inputFormatters: [
|
|
inputFormatters: [
|
|
|
FilteringTextInputFormatter.allow(RegExp(r'^\d*\.?\d{0,2}$')),
|
|
FilteringTextInputFormatter.allow(RegExp(r'^\d*\.?\d{0,2}$')),
|
|
@@ -311,8 +334,8 @@ class _ReturnCarDialogState extends State<ReturnCarDialog> {
|
|
|
return TDTextarea(
|
|
return TDTextarea(
|
|
|
controller: _remarkCtrl,
|
|
controller: _remarkCtrl,
|
|
|
focusNode: _remarkFocus,
|
|
focusNode: _remarkFocus,
|
|
|
- label: '费用明细备注',
|
|
|
|
|
- hintText: '请输入备注',
|
|
|
|
|
|
|
+ label: l10n.get('costRemark'),
|
|
|
|
|
+ hintText: l10n.get('enterRemark'),
|
|
|
maxLines: 3,
|
|
maxLines: 3,
|
|
|
minLines: 1,
|
|
minLines: 1,
|
|
|
maxLength: 500,
|
|
maxLength: 500,
|
|
@@ -332,7 +355,7 @@ class _ReturnCarDialogState extends State<ReturnCarDialog> {
|
|
|
children: [
|
|
children: [
|
|
|
Expanded(
|
|
Expanded(
|
|
|
child: TDButton(
|
|
child: TDButton(
|
|
|
- text: '取消',
|
|
|
|
|
|
|
+ text: l10n.get('cancel'),
|
|
|
size: TDButtonSize.large,
|
|
size: TDButtonSize.large,
|
|
|
type: TDButtonType.outline,
|
|
type: TDButtonType.outline,
|
|
|
shape: TDButtonShape.rectangle,
|
|
shape: TDButtonShape.rectangle,
|
|
@@ -343,7 +366,7 @@ class _ReturnCarDialogState extends State<ReturnCarDialog> {
|
|
|
const SizedBox(width: 12),
|
|
const SizedBox(width: 12),
|
|
|
Expanded(
|
|
Expanded(
|
|
|
child: TDButton(
|
|
child: TDButton(
|
|
|
- text: '确认还车',
|
|
|
|
|
|
|
+ text: l10n.get('confirmReturnCar'),
|
|
|
size: TDButtonSize.large,
|
|
size: TDButtonSize.large,
|
|
|
type: TDButtonType.fill,
|
|
type: TDButtonType.fill,
|
|
|
shape: TDButtonShape.rectangle,
|
|
shape: TDButtonShape.rectangle,
|
|
@@ -445,6 +468,7 @@ class _ReturnCarDialogState extends State<ReturnCarDialog> {
|
|
|
}) {
|
|
}) {
|
|
|
final now = DateTime.now();
|
|
final now = DateTime.now();
|
|
|
final colors = Theme.of(context).extension<AppColorsExtension>()!;
|
|
final colors = Theme.of(context).extension<AppColorsExtension>()!;
|
|
|
|
|
+ final minDt = widget.recordDd ?? DateTime(2020);
|
|
|
|
|
|
|
|
int initYear = now.year;
|
|
int initYear = now.year;
|
|
|
int initMonth = now.month;
|
|
int initMonth = now.month;
|
|
@@ -463,10 +487,20 @@ class _ReturnCarDialogState extends State<ReturnCarDialog> {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ // 确保初始值不早于 RECORD_DD
|
|
|
|
|
+ final initDt = DateTime(initYear, initMonth, initDay, initHour, initMinute);
|
|
|
|
|
+ if (initDt.isBefore(minDt)) {
|
|
|
|
|
+ initYear = minDt.year;
|
|
|
|
|
+ initMonth = minDt.month;
|
|
|
|
|
+ initDay = minDt.day;
|
|
|
|
|
+ initHour = minDt.hour;
|
|
|
|
|
+ initMinute = minDt.minute;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
FocusManager.instance.primaryFocus?.unfocus();
|
|
FocusManager.instance.primaryFocus?.unfocus();
|
|
|
TDPicker.showDatePicker(
|
|
TDPicker.showDatePicker(
|
|
|
context,
|
|
context,
|
|
|
- title: '选择日期',
|
|
|
|
|
|
|
+ title: l10n.get('selectDate'),
|
|
|
backgroundColor: colors.bgCard,
|
|
backgroundColor: colors.bgCard,
|
|
|
useYear: true,
|
|
useYear: true,
|
|
|
useMonth: true,
|
|
useMonth: true,
|
|
@@ -475,7 +509,14 @@ class _ReturnCarDialogState extends State<ReturnCarDialog> {
|
|
|
useMinute: true,
|
|
useMinute: true,
|
|
|
useSecond: true,
|
|
useSecond: true,
|
|
|
useWeekDay: false,
|
|
useWeekDay: false,
|
|
|
- dateStart: const [2020, 1, 1, 0, 0, 0],
|
|
|
|
|
|
|
+ dateStart: [
|
|
|
|
|
+ minDt.year,
|
|
|
|
|
+ minDt.month,
|
|
|
|
|
+ minDt.day,
|
|
|
|
|
+ minDt.hour,
|
|
|
|
|
+ minDt.minute,
|
|
|
|
|
+ minDt.second,
|
|
|
|
|
+ ],
|
|
|
dateEnd: [now.year + 1, 12, 31, 23, 59, 59],
|
|
dateEnd: [now.year + 1, 12, 31, 23, 59, 59],
|
|
|
initialDate: [initYear, initMonth, initDay, initHour, initMinute, 0],
|
|
initialDate: [initYear, initMonth, initDay, initHour, initMinute, 0],
|
|
|
onConfirm: (selected) {
|
|
onConfirm: (selected) {
|