|
@@ -11,18 +11,30 @@ import '../../../core/theme/app_colors_extension.dart';
|
|
|
/// 还车登记弹窗。
|
|
/// 还车登记弹窗。
|
|
|
///
|
|
///
|
|
|
/// 从底部滑出,用于登记车辆归还信息。
|
|
/// 从底部滑出,用于登记车辆归还信息。
|
|
|
|
|
+/// 传入 [existingReturnTime] 等已有数据时,对应字段预填,
|
|
|
|
|
+/// 其中实还时间(已有值时)为只读不可修改。
|
|
|
class ReturnCarDialog extends StatefulWidget {
|
|
class ReturnCarDialog extends StatefulWidget {
|
|
|
final String billNo;
|
|
final String billNo;
|
|
|
final int odometerBegin; // 出车里程,用于对比
|
|
final int odometerBegin; // 出车里程,用于对比
|
|
|
final DateTime? recordDd; // 录入日期,归还时间必须大于此值
|
|
final DateTime? recordDd; // 录入日期,归还时间必须大于此值
|
|
|
final Future<bool> Function(Map<String, dynamic>) onSubmit; // 提交回调,返回 true=成功
|
|
final Future<bool> Function(Map<String, dynamic>) onSubmit; // 提交回调,返回 true=成功
|
|
|
|
|
|
|
|
|
|
+ // 已有还车数据(重复还车时预填)
|
|
|
|
|
+ final DateTime? existingReturnTime;
|
|
|
|
|
+ final int existingOdometerEnd;
|
|
|
|
|
+ final double existingAmtn;
|
|
|
|
|
+ final String existingRemark;
|
|
|
|
|
+
|
|
|
const ReturnCarDialog({
|
|
const ReturnCarDialog({
|
|
|
super.key,
|
|
super.key,
|
|
|
required this.billNo,
|
|
required this.billNo,
|
|
|
required this.odometerBegin,
|
|
required this.odometerBegin,
|
|
|
this.recordDd,
|
|
this.recordDd,
|
|
|
required this.onSubmit,
|
|
required this.onSubmit,
|
|
|
|
|
+ this.existingReturnTime,
|
|
|
|
|
+ this.existingOdometerEnd = 0,
|
|
|
|
|
+ this.existingAmtn = 0.0,
|
|
|
|
|
+ this.existingRemark = '',
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
/// 显示弹窗。
|
|
/// 显示弹窗。
|
|
@@ -32,6 +44,10 @@ class ReturnCarDialog extends StatefulWidget {
|
|
|
required int odometerBegin,
|
|
required int odometerBegin,
|
|
|
DateTime? recordDd,
|
|
DateTime? recordDd,
|
|
|
required Future<bool> Function(Map<String, dynamic>) onSubmit,
|
|
required Future<bool> Function(Map<String, dynamic>) onSubmit,
|
|
|
|
|
+ DateTime? existingReturnTime,
|
|
|
|
|
+ int existingOdometerEnd = 0,
|
|
|
|
|
+ double existingAmtn = 0.0,
|
|
|
|
|
+ String existingRemark = '',
|
|
|
}) {
|
|
}) {
|
|
|
FocusScope.of(context).unfocus();
|
|
FocusScope.of(context).unfocus();
|
|
|
Navigator.push(
|
|
Navigator.push(
|
|
@@ -44,6 +60,10 @@ class ReturnCarDialog extends StatefulWidget {
|
|
|
odometerBegin: odometerBegin,
|
|
odometerBegin: odometerBegin,
|
|
|
recordDd: recordDd,
|
|
recordDd: recordDd,
|
|
|
onSubmit: onSubmit,
|
|
onSubmit: onSubmit,
|
|
|
|
|
+ existingReturnTime: existingReturnTime,
|
|
|
|
|
+ existingOdometerEnd: existingOdometerEnd,
|
|
|
|
|
+ existingAmtn: existingAmtn,
|
|
|
|
|
+ existingRemark: existingRemark,
|
|
|
),
|
|
),
|
|
|
),
|
|
),
|
|
|
);
|
|
);
|
|
@@ -76,11 +96,32 @@ class _ReturnCarDialogState extends State<ReturnCarDialog> {
|
|
|
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 =
|
|
|
|
|
- '${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')}';
|
|
|
|
|
|
|
+
|
|
|
|
|
+ // 实还时间:已有值用已有(只读),否则默认当前时间
|
|
|
|
|
+ if (widget.existingReturnTime != null) {
|
|
|
|
|
+ final t = widget.existingReturnTime!;
|
|
|
|
|
+ _returnTime =
|
|
|
|
|
+ '${t.year}-${t.month.toString().padLeft(2, '0')}-${t.day.toString().padLeft(2, '0')} '
|
|
|
|
|
+ '${t.hour.toString().padLeft(2, '0')}:${t.minute.toString().padLeft(2, '0')}';
|
|
|
|
|
+ } else {
|
|
|
|
|
+ final minDt = widget.recordDd ?? now;
|
|
|
|
|
+ final initDt = now.isAfter(minDt) ? now : minDt;
|
|
|
|
|
+ _returnTime =
|
|
|
|
|
+ '${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')}';
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 预填已有值
|
|
|
|
|
+ if (widget.existingOdometerEnd > 0) {
|
|
|
|
|
+ _odometerCtrl.text = '${widget.existingOdometerEnd}';
|
|
|
|
|
+ }
|
|
|
|
|
+ if (widget.existingAmtn > 0) {
|
|
|
|
|
+ _amtnCtrl.text = widget.existingAmtn.toStringAsFixed(2);
|
|
|
|
|
+ }
|
|
|
|
|
+ if (widget.existingRemark.isNotEmpty) {
|
|
|
|
|
+ _remarkCtrl.text = widget.existingRemark;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
_odometerFocus.addListener(() => _ensureVisible(_odometerFocus));
|
|
_odometerFocus.addListener(() => _ensureVisible(_odometerFocus));
|
|
|
_amtnFocus.addListener(() => _ensureVisible(_amtnFocus));
|
|
_amtnFocus.addListener(() => _ensureVisible(_amtnFocus));
|
|
|
_remarkFocus.addListener(() => _ensureVisible(_remarkFocus));
|
|
_remarkFocus.addListener(() => _ensureVisible(_remarkFocus));
|
|
@@ -147,12 +188,16 @@ class _ReturnCarDialogState extends State<ReturnCarDialog> {
|
|
|
setState(() => _submitting = true);
|
|
setState(() => _submitting = true);
|
|
|
LoadingDialog.show(context, text: l10n.get('returning'));
|
|
LoadingDialog.show(context, text: l10n.get('returning'));
|
|
|
|
|
|
|
|
- final ok = await widget.onSubmit({
|
|
|
|
|
- 'returnTime': _returnTime,
|
|
|
|
|
|
|
+ final submitData = <String, dynamic>{
|
|
|
'odometerEnd': odometerEnd,
|
|
'odometerEnd': odometerEnd,
|
|
|
'amtn': amtn,
|
|
'amtn': amtn,
|
|
|
'remark': _remarkCtrl.text.trim(),
|
|
'remark': _remarkCtrl.text.trim(),
|
|
|
- });
|
|
|
|
|
|
|
+ };
|
|
|
|
|
+ // 实还时间已有则不传(后端跳过不修改)
|
|
|
|
|
+ if (widget.existingReturnTime == null) {
|
|
|
|
|
+ submitData['returnTime'] = _returnTime;
|
|
|
|
|
+ }
|
|
|
|
|
+ final ok = await widget.onSubmit(submitData);
|
|
|
|
|
|
|
|
if (!mounted) return;
|
|
if (!mounted) return;
|
|
|
LoadingDialog.hide(context);
|
|
LoadingDialog.hide(context);
|
|
@@ -289,6 +334,14 @@ class _ReturnCarDialogState extends State<ReturnCarDialog> {
|
|
|
|
|
|
|
|
// ── 实际归还时间 ──
|
|
// ── 实际归还时间 ──
|
|
|
Widget _buildReturnTimePicker(AppColorsExtension colors) {
|
|
Widget _buildReturnTimePicker(AppColorsExtension colors) {
|
|
|
|
|
+ final hasExisting = widget.existingReturnTime != null;
|
|
|
|
|
+ if (hasExisting) {
|
|
|
|
|
+ return _readOnlyCard(
|
|
|
|
|
+ label: l10n.get('actualReturnTime'),
|
|
|
|
|
+ value: _returnTime,
|
|
|
|
|
+ colors: colors,
|
|
|
|
|
+ );
|
|
|
|
|
+ }
|
|
|
return _datePickerCard(
|
|
return _datePickerCard(
|
|
|
label: l10n.get('actualReturnTime'),
|
|
label: l10n.get('actualReturnTime'),
|
|
|
value: _returnTime,
|
|
value: _returnTime,
|
|
@@ -461,6 +514,56 @@ class _ReturnCarDialogState extends State<ReturnCarDialog> {
|
|
|
);
|
|
);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ // ── 只读信息卡片(已有时不可编辑) ──
|
|
|
|
|
+ Widget _readOnlyCard({
|
|
|
|
|
+ required String label,
|
|
|
|
|
+ required String value,
|
|
|
|
|
+ required AppColorsExtension colors,
|
|
|
|
|
+ }) {
|
|
|
|
|
+ final tdTheme = TDTheme.of(context);
|
|
|
|
|
+ return Container(
|
|
|
|
|
+ padding: const EdgeInsets.only(left: 16, right: 10, top: 12, bottom: 12),
|
|
|
|
|
+ decoration: BoxDecoration(
|
|
|
|
|
+ color: tdTheme.bgColorContainer,
|
|
|
|
|
+ borderRadius: BorderRadius.circular(tdTheme.radiusDefault),
|
|
|
|
|
+ border: Border.all(color: tdTheme.componentStrokeColor),
|
|
|
|
|
+ ),
|
|
|
|
|
+ child: Row(
|
|
|
|
|
+ children: [
|
|
|
|
|
+ TDText(
|
|
|
|
|
+ label,
|
|
|
|
|
+ maxLines: 1,
|
|
|
|
|
+ overflow: TextOverflow.visible,
|
|
|
|
|
+ font: tdTheme.fontBodyLarge,
|
|
|
|
|
+ fontWeight: FontWeight.w400,
|
|
|
|
|
+ style: const TextStyle(letterSpacing: 0),
|
|
|
|
|
+ ),
|
|
|
|
|
+ const SizedBox(width: 12),
|
|
|
|
|
+ Expanded(
|
|
|
|
|
+ child: Row(
|
|
|
|
|
+ mainAxisAlignment: MainAxisAlignment.end,
|
|
|
|
|
+ children: [
|
|
|
|
|
+ Flexible(
|
|
|
|
|
+ child: TDText(
|
|
|
|
|
+ value,
|
|
|
|
|
+ maxLines: 1,
|
|
|
|
|
+ overflow: TextOverflow.ellipsis,
|
|
|
|
|
+ font: tdTheme.fontBodyLarge,
|
|
|
|
|
+ fontWeight: FontWeight.w400,
|
|
|
|
|
+ textColor: tdTheme.textColorPrimary,
|
|
|
|
|
+ textAlign: TextAlign.end,
|
|
|
|
|
+ ),
|
|
|
|
|
+ ),
|
|
|
|
|
+ const SizedBox(width: 4),
|
|
|
|
|
+ Icon(Icons.lock_outline, size: 16, color: tdTheme.textColorPlaceholder),
|
|
|
|
|
+ ],
|
|
|
|
|
+ ),
|
|
|
|
|
+ ),
|
|
|
|
|
+ ],
|
|
|
|
|
+ ),
|
|
|
|
|
+ );
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
void _pickDateTime(
|
|
void _pickDateTime(
|
|
|
String label,
|
|
String label,
|
|
|
ValueChanged<String> onPick, {
|
|
ValueChanged<String> onPick, {
|
|
@@ -507,7 +610,7 @@ class _ReturnCarDialogState extends State<ReturnCarDialog> {
|
|
|
useDay: true,
|
|
useDay: true,
|
|
|
useHour: true,
|
|
useHour: true,
|
|
|
useMinute: true,
|
|
useMinute: true,
|
|
|
- useSecond: true,
|
|
|
|
|
|
|
+ useSecond: false,
|
|
|
useWeekDay: false,
|
|
useWeekDay: false,
|
|
|
dateStart: [
|
|
dateStart: [
|
|
|
minDt.year,
|
|
minDt.year,
|
|
@@ -515,9 +618,9 @@ class _ReturnCarDialogState extends State<ReturnCarDialog> {
|
|
|
minDt.day,
|
|
minDt.day,
|
|
|
minDt.hour,
|
|
minDt.hour,
|
|
|
minDt.minute,
|
|
minDt.minute,
|
|
|
- minDt.second,
|
|
|
|
|
|
|
+ 0,
|
|
|
],
|
|
],
|
|
|
- dateEnd: [now.year + 1, 12, 31, 23, 59, 59],
|
|
|
|
|
|
|
+ dateEnd: [now.year + 1, 12, 31, 23, 59, 0],
|
|
|
initialDate: [initYear, initMonth, initDay, initHour, initMinute, 0],
|
|
initialDate: [initYear, initMonth, initDay, initHour, initMinute, 0],
|
|
|
onConfirm: (selected) {
|
|
onConfirm: (selected) {
|
|
|
final year = selected['year'];
|
|
final year = selected['year'];
|
|
@@ -525,8 +628,7 @@ class _ReturnCarDialogState extends State<ReturnCarDialog> {
|
|
|
final day = selected['day'].toString().padLeft(2, '0');
|
|
final day = selected['day'].toString().padLeft(2, '0');
|
|
|
final hour = selected['hour']?.toString().padLeft(2, '0') ?? '00';
|
|
final hour = selected['hour']?.toString().padLeft(2, '0') ?? '00';
|
|
|
final minute = selected['minute']?.toString().padLeft(2, '0') ?? '00';
|
|
final minute = selected['minute']?.toString().padLeft(2, '0') ?? '00';
|
|
|
- final second = selected['second']?.toString().padLeft(2, '0') ?? '00';
|
|
|
|
|
- onPick('$year-$month-$day $hour:$minute:$second');
|
|
|
|
|
|
|
+ onPick('$year-$month-$day $hour:$minute');
|
|
|
Navigator.of(context).pop();
|
|
Navigator.of(context).pop();
|
|
|
},
|
|
},
|
|
|
);
|
|
);
|