import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:tdesign_flutter/tdesign_flutter.dart'; import '../../core/i18n/app_localizations.dart'; import '../../core/theme/app_colors.dart'; import '../../core/theme/app_colors_extension.dart'; import '../../core/utils/date_utils.dart' as du; import '../../shared/widgets/app_skeletons.dart'; import '../../shared/widgets/form_field_row.dart'; import '../../shared/widgets/form_section.dart'; import 'vehicle_apply_api.dart'; import 'vehicle_apply_list_controller.dart'; import 'vehicle_apply_model.dart'; class VehicleApplyDetailPage extends ConsumerStatefulWidget { final String billNo; const VehicleApplyDetailPage({super.key, required this.billNo}); @override ConsumerState createState() => _VehicleApplyDetailPageState(); } class _VehicleApplyDetailPageState extends ConsumerState { bool _loading = true; String? _error; VehicleApplyModel? _data; @override void initState() { super.initState(); _loadData(); } Future _loadData() async { setState(() { _loading = true; _error = null; }); try { // 先用 mock 数据匹配 billNo final match = mockVehicles.where((e) => e.ycNo == widget.billNo); if (match.isNotEmpty) { if (mounted) { setState(() { _data = match.first; _loading = false; }); } } else { final api = ref.read(vehicleApplyApiProvider); final detail = await api.fetchDetail(widget.billNo); if (mounted) { setState(() { _data = detail; _loading = false; }); } } } catch (e) { if (mounted) { setState(() { _error = e.toString(); _loading = false; }); } } } @override Widget build(BuildContext context) { final colors = Theme.of(context).extension()!; final l10n = AppLocalizations.of(context); if (_loading) return const SkeletonDetailPage(); if (_error != null) { return Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ Icon(Icons.error_outline, size: 48, color: colors.danger), const SizedBox(height: 16), Padding( padding: const EdgeInsets.symmetric(horizontal: 32), child: Text( _error!, textAlign: TextAlign.center, style: TextStyle( fontSize: AppFontSizes.body, color: colors.textSecondary, ), ), ), const SizedBox(height: 16), TDButton( text: l10n.get('retry'), size: TDButtonSize.medium, onTap: _loadData, ), ], ), ); } final app = _data!; return Column( children: [ Expanded( child: SingleChildScrollView( physics: const AlwaysScrollableScrollPhysics(), padding: const EdgeInsets.all(16), child: Column( children: [ _buildBasicInfoSection(app, l10n, colors), const SizedBox(height: 16), _buildVehicleInfoSection(app, l10n, colors), const SizedBox(height: 16), _buildTripInfoSection(app, l10n, colors), const SizedBox(height: 16), _buildPassengerInfoSection(app, l10n, colors), const SizedBox(height: 24), _buildPageFooter(colors), ], ), ), ), ], ); } // ═══ 状态 tag ═══ Widget _buildStatusTag(AppLocalizations l10n) { final colors = Theme.of(context).extension()!; final approved = _data?.isAuditApproved ?? false; final returned = _data?.isReturn ?? false; if (returned) { return Container( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), decoration: BoxDecoration( color: colors.infoText.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(6), border: Border.all( color: colors.infoText.withValues(alpha: 0.3), width: 0.5, ), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon(Icons.assignment_return, size: 18, color: colors.infoText), const SizedBox(width: 4), Text( l10n.get('vehicleReturned'), style: TextStyle( fontSize: 13, fontWeight: FontWeight.w600, color: colors.infoText, ), ), ], ), ); } if (approved) { return Container( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), decoration: BoxDecoration( color: colors.success.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(6), border: Border.all( color: colors.success.withValues(alpha: 0.3), width: 0.5, ), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon(Icons.check_circle_outline, size: 18, color: colors.success), const SizedBox(width: 4), Text( l10n.get('statusApproved'), style: TextStyle( fontSize: 13, fontWeight: FontWeight.w600, color: colors.success, ), ), ], ), ); } return Container( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), decoration: BoxDecoration( color: colors.statusGray.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(6), border: Border.all( color: colors.statusGray.withValues(alpha: 0.3), width: 0.5, ), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon(Icons.access_time, size: 18, color: colors.statusGray), const SizedBox(width: 4), Text( l10n.get('statusPending'), style: TextStyle( fontSize: 13, fontWeight: FontWeight.w600, color: colors.statusGray, ), ), ], ), ); } // ═══ 基本信息 ═══ Widget _buildBasicInfoSection( VehicleApplyModel app, AppLocalizations l10n, AppColorsExtension colors, ) { return FormSection( title: l10n.get('basicInfo'), leadingIcon: Icons.info_outline, trailing: _buildStatusTag(l10n), children: [ FormFieldRow( label: l10n.get('vehicleApplyNo'), value: app.ycNo, readOnly: true, showArrow: false, ), const SizedBox(height: 16), FormFieldRow( label: l10n.get('date'), value: app.ycDd != null ? du.DateUtils.formatDate(app.ycDd!) : '-', readOnly: true, showArrow: false, ), const SizedBox(height: 16), FormFieldRow( label: l10n.get('applicant'), value: app.applicantName.isNotEmpty ? app.applicantName : app.salNo, readOnly: true, showArrow: false, ), const SizedBox(height: 16), FormFieldRow( label: l10n.get('dep'), value: app.dep.isNotEmpty ? '${app.dep}${app.deptName.isNotEmpty ? '/${app.deptName}' : ''}' : '-', readOnly: true, showArrow: false, ), const SizedBox(height: 16), FormFieldRow( label: l10n.get('vehicleReason'), value: app.reason.isNotEmpty ? app.reason : '-', readOnly: true, showArrow: false, bold: true, showMoreOnOverflow: true, ), ], ); } // ═══ 车辆信息 ═══ Widget _buildVehicleInfoSection( VehicleApplyModel app, AppLocalizations l10n, AppColorsExtension colors, ) { return FormSection( title: l10n.get('vehicleInfo'), leadingIcon: Icons.directions_car_outlined, children: [ FormFieldRow( label: l10n.get('licensePlate'), value: app.licensePlate.isNotEmpty ? app.licensePlate : '-', readOnly: true, showArrow: false, bold: true, valueColor: colors.platePrimary, ), const SizedBox(height: 16), FormFieldRow( label: l10n.get('vehicleType'), value: app.vehicleType.isNotEmpty ? _vehicleTypeLabel(app.vehicleType, l10n) : '-', readOnly: true, showArrow: false, ), const SizedBox(height: 16), FormFieldRow( label: l10n.get('brand'), value: app.brand.isNotEmpty ? app.brand : '-', readOnly: true, showArrow: false, ), const SizedBox(height: 16), FormFieldRow( label: l10n.get('seats'), value: app.seats > 0 ? '${app.seats}' : '-', readOnly: true, showArrow: false, ), const SizedBox(height: 16), FormFieldRow( label: l10n.get('odometerBegin'), value: app.odometerBegin > 0 ? '${app.odometerBegin} km' : '-', readOnly: true, showArrow: false, ), const SizedBox(height: 16), FormFieldRow( label: l10n.get('driverName'), value: app.driverName.isNotEmpty ? app.driverName : '-', readOnly: true, showArrow: false, ), ], ); } // ═══ 行程信息 ═══ Widget _buildTripInfoSection( VehicleApplyModel app, AppLocalizations l10n, AppColorsExtension colors, ) { return FormSection( title: l10n.get('tripInfo'), leadingIcon: Icons.route_outlined, children: [ FormFieldRow( label: l10n.get('departTime'), value: app.startTime != null ? du.DateUtils.formatDateTime(app.startTime!) : '-', readOnly: true, showArrow: false, ), const SizedBox(height: 16), FormFieldRow( label: l10n.get('returnTime'), value: app.endTime != null ? du.DateUtils.formatDateTime(app.endTime!) : '-', readOnly: true, showArrow: false, ), const SizedBox(height: 16), FormFieldRow( label: l10n.get('origin'), value: app.originAdr.isNotEmpty ? app.originAdr : '-', readOnly: true, showArrow: false, showMoreOnOverflow: true, ), const SizedBox(height: 16), FormFieldRow( label: l10n.get('destination'), value: app.destAdr.isNotEmpty ? app.destAdr : '-', readOnly: true, showArrow: false, showMoreOnOverflow: true, ), ], ); } // ═══ 同行信息 ═══ Widget _buildPassengerInfoSection( VehicleApplyModel app, AppLocalizations l10n, AppColorsExtension colors, ) { final names = app.passengerName.isNotEmpty ? app.passengerName.split(';').where((n) => n.trim().isNotEmpty).toList() : []; return FormSection( title: l10n.get('companionInfo'), leadingIcon: Icons.people_outline, children: [ if (names.isEmpty) Padding( padding: const EdgeInsets.symmetric(vertical: 8), child: Text( '-', style: TextStyle( fontSize: AppFontSizes.subtitle, color: colors.textPlaceholder, ), ), ) else Wrap( spacing: 10, runSpacing: 10, children: names.map((name) { return Container( padding: const EdgeInsets.symmetric( horizontal: 14, vertical: 10), decoration: BoxDecoration( color: colors.bgCard, borderRadius: BorderRadius.circular(20), border: Border.all(color: colors.border, width: 0.5), boxShadow: [ BoxShadow( color: Colors.black.withValues(alpha: 0.04), blurRadius: 4, offset: const Offset(0, 1), ), ], ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon(Icons.person_outline, size: 16, color: colors.primary), const SizedBox(width: 8), Text( name.trim(), style: TextStyle( fontSize: AppFontSizes.body, color: colors.textPrimary, ), ), ], ), ); }).toList(), ), const SizedBox(height: 16), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( l10n.get('passengerCount'), style: TextStyle( fontSize: AppFontSizes.body, fontWeight: FontWeight.w600, color: colors.textPrimary, ), ), Text( '${app.passengerCount}', style: TextStyle( fontSize: AppFontSizes.subtitle, fontWeight: FontWeight.w700, color: colors.textPrimary, ), ), ], ), ], ); } // ═══ 工具方法 ═══ Widget _buildPageFooter(AppColorsExtension colors) { final l10n = AppLocalizations.of(context); return Center( child: Padding( padding: const EdgeInsets.only(bottom: 16), child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon( Icons.rocket_launch_outlined, size: 16, color: colors.textPlaceholder, ), const SizedBox(width: 6), Text( l10n.get('pageFooter'), style: TextStyle( fontSize: AppFontSizes.caption, color: colors.textPlaceholder, ), ), ], ), ), ); } String _vehicleTypeLabel(String key, AppLocalizations l10n) { switch (key) { case 'sedan': return l10n.get('sedan'); case 'suv': return 'SUV'; case 'mpv': return l10n.get('businessVan'); case 'van': return l10n.get('van'); case 'truck': return l10n.get('truck'); case 'pickup': return l10n.get('pickup'); case 'minibus': return l10n.get('minibus'); case 'bus': return l10n.get('bus'); default: return key; } } }