import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import 'package:tdesign_flutter/tdesign_flutter.dart'; import '../../core/i18n/app_localizations.dart'; import '../../core/navigation/host_app_channel.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/bill_status_bar.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; BillStatusBar? _billStatusBar; Map? _billStatus; @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; }); } // 获取单据状态(非致命) try { final status = await api.getBillStatus(widget.billNo); if (mounted) { setState(() { _billStatus = status; _billStatusBar = BillStatusBar( billStatus: _billStatus, onEdit: () { GoRouter.of( context, ).push('/vehicle-apply/edit/${widget.billNo}').then((result) { if (result == true && mounted) _loadData(); }); }, onSubmit: () { final dd = _data?.ycDd != null ? du.DateUtils.formatDate(_data!.ycDd!) : ''; api.shSubmit(bilId: 'YC', bilNo: widget.billNo, bilDd: dd); }, onCancelSubmit: () { final dd = _data?.ycDd != null ? du.DateUtils.formatDate(_data!.ycDd!) : ''; api.shSubmit( bilId: 'YC', bilNo: widget.billNo, bilDd: dd, isCancel: true, ); }, onTapStatusTag: () => _showAuditTrail('YC'), ); }); } } catch (_) {} } } 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(sectionRows: [5, 6, 4, 1]); 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), ], ), ), ), _billStatusBar?.buildActions(context) ?? const SizedBox.shrink(), ], ); } // ═══ 基本信息 ═══ Widget _buildBasicInfoSection( VehicleApplyModel app, AppLocalizations l10n, AppColorsExtension colors, ) { return FormSection( title: l10n.get('basicInfo'), leadingIcon: Icons.info_outline, trailing: _billStatusBar?.buildStatusTag(context) ?? const SizedBox.shrink(), 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, ), ), ], ), ], ); } // ═══ 审批轨迹 ═══ Future _showAuditTrail(String billId) async { await HostAppChannel.showAuditTrail(billId, widget.billNo); } // ═══ 工具方法 ═══ 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; } } }