import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import '../../core/theme/app_colors.dart'; import '../shell/nav_bar_config.dart'; import '../../core/utils/date_utils.dart' as du; import '../../shared/widgets/form_section.dart'; import '../../shared/widgets/form_field_row.dart'; import '../../shared/widgets/status_banner.dart'; import '../../shared/widgets/action_bar.dart'; import '../../shared/widgets/approval_timeline.dart'; import 'expense_application_model.dart'; import '../../core/i18n/app_localizations.dart'; import 'expense_application_list_controller.dart'; class ExpenseApplicationDetailPage extends ConsumerWidget { final String id; const ExpenseApplicationDetailPage({super.key, required this.id}); @override Widget build(BuildContext context, WidgetRef ref) { final app = mockExpenseApplications.firstWhere( (e) => e.id == id, orElse: () => mockExpenseApplications.first, ); final l10n = AppLocalizations.of(context); ref .read(navBarConfigProvider.notifier) .update( NavBarConfig( title: l10n.get('expenseApplyDetail'), showBack: true, onBack: () => context.pop(), ), ); return Column( children: [ Expanded( child: SingleChildScrollView( padding: const EdgeInsets.all(16), child: Column( children: [ _buildStatusBanner(app), const SizedBox(height: 4), _buildSubmitTime(app), const SizedBox(height: 16), _buildBasicInfoSection(app, l10n), const SizedBox(height: 16), _buildExpenseDetailSection(app, l10n), const SizedBox(height: 16), _buildAttachmentSection(l10n), const SizedBox(height: 16), if (app.approvalRecords.isNotEmpty || app.approvalChain.isNotEmpty) _buildApprovalSection(l10n, app), const SizedBox(height: 16), ], ), ), ), _buildBottomBar(context, app), ], ); } Widget _buildStatusBanner(ExpenseApplicationModel app) { final (icon, color, label) = switch (app.status) { 'approved' => (Icons.check_circle, AppColors.success, '已通过'), 'rejected' => (Icons.cancel, AppColors.danger, '已拒绝'), 'draft' => (Icons.edit, AppColors.statusGray, '草稿'), _ => (Icons.schedule, AppColors.warning, '审批中'), }; final approverText = switch (app.status) { 'approved' when app.approvalRecords.isNotEmpty => '审批人:${app.approvalRecords.last.approverName}', 'rejected' when app.approvalRecords.isNotEmpty => '拒绝人:${app.approvalRecords.last.approverName}', 'pending' when app.currentApproverId.isNotEmpty => '当前审批人:${app.currentApproverId}', _ => '', }; return StatusBanner( icon: icon, statusText: label, subText: approverText, color: color, ); } Widget _buildSubmitTime(ExpenseApplicationModel app) { return Padding( padding: const EdgeInsets.only(left: 4, top: 4), child: Align( alignment: Alignment.centerLeft, child: Text( '提交时间:${du.DateUtils.formatDateTime(app.createTime)}', style: const TextStyle( fontSize: AppFontSizes.caption, color: AppColors.textPlaceholder, ), ), ), ); } Widget _buildBasicInfoSection(ExpenseApplicationModel app, AppLocalizations l10n) { String urgencyLabel = switch (app.urgency) { 'urgent' => '紧急', 'normal' => '普通', _ => app.urgency, }; return FormSection( title: l10n.get('basicInfo'), children: [ FormFieldRow( label: l10n.get('applicant'), value: app.applicantName, readOnly: true, showArrow: false, ), FormFieldRow( label: l10n.get('department'), value: app.deptName, readOnly: true, showArrow: false, ), FormFieldRow( label: '费用类型', value: app.expenseType, readOnly: true, showArrow: false, ), Container( height: 44, padding: const EdgeInsets.symmetric(horizontal: 0, vertical: 0), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Text( '申请金额', style: TextStyle( fontSize: AppFontSizes.body, color: AppColors.textSecondary, ), ), Text( '¥${app.estimatedAmount.toStringAsFixed(2)}', style: const TextStyle( fontSize: AppFontSizes.subtitle, fontWeight: FontWeight.w700, color: AppColors.amountPrimary, ), ), ], ), ), FormFieldRow( label: '关联项目', value: app.projectName.isNotEmpty ? app.projectName : null, hint: '-', readOnly: true, showArrow: false, ), FormFieldRow( label: '预算科目', value: app.budgetSubjectId.isNotEmpty ? app.budgetSubjectId : null, hint: '-', readOnly: true, showArrow: false, ), FormFieldRow( label: '紧急程度', value: urgencyLabel, readOnly: true, showArrow: false, ), ], ); } Widget _buildExpenseDetailSection(ExpenseApplicationModel app, AppLocalizations l10n) { return FormSection( title: l10n.get('expenseDetails'), children: [ // Table header Container( height: 36, padding: const EdgeInsets.symmetric(horizontal: 8), decoration: BoxDecoration( color: AppColors.bgPage, borderRadius: BorderRadius.circular(4), ), child: Row( children: [ const Expanded( flex: 3, child: Text( '费用项目', style: TextStyle( fontSize: AppFontSizes.caption, fontWeight: FontWeight.w500, color: AppColors.textSecondary, ), ), ), Expanded( flex: 2, child: Text( '金额', textAlign: TextAlign.right, style: const TextStyle( fontSize: AppFontSizes.caption, fontWeight: FontWeight.w500, color: AppColors.textSecondary, ), ), ), ], ), ), if (app.details.isEmpty) const Padding( padding: EdgeInsets.symmetric(vertical: 8), child: Text( '暂无明细数据', style: TextStyle( fontSize: AppFontSizes.body, color: AppColors.textPlaceholder, ), ), ) else ...app.details.map( (d) => SizedBox( height: 28, child: Row( children: [ Expanded( flex: 3, child: Text( d.itemName, style: const TextStyle( fontSize: AppFontSizes.body, color: AppColors.textPrimary, ), ), ), Expanded( flex: 2, child: Text( '¥${d.estimatedAmount.toStringAsFixed(2)}', textAlign: TextAlign.right, style: const TextStyle( fontSize: AppFontSizes.body, fontWeight: FontWeight.w500, color: AppColors.amountPrimary, ), ), ), ], ), ), ), ], ); } Widget _buildAttachmentSection(AppLocalizations l10n) { return FormSection( title: l10n.get('attachments'), children: [ Wrap( spacing: 8, runSpacing: 8, children: List.generate(3, (i) { return Container( width: 80, height: 80, decoration: BoxDecoration( color: AppColors.bgPage, borderRadius: BorderRadius.circular(4), border: Border.all( color: AppColors.border, strokeAlign: BorderSide.strokeAlignInside, ), ), child: const Center( child: Icon( Icons.image_outlined, size: 24, color: AppColors.textPlaceholder, ), ), ); }), ), ], ); } Widget _buildApprovalSection(AppLocalizations l10n, ExpenseApplicationModel app) { return FormSection( title: l10n.get('approvalFlow'), children: [ ApprovalTimeline( records: app.approvalRecords, chain: app.approvalChain, currentApproverId: app.currentApproverId, ), ], ); } Widget _buildBottomBar(BuildContext context, ExpenseApplicationModel app) { final canWithdraw = app.status == 'pending' || app.status == 'draft'; if (!canWithdraw) { return const SizedBox.shrink(); } return ActionBar( showLeft: false, centerLabel: '撤回申请', rightLabel: '提交审批', onCenterTap: () { ScaffoldMessenger.of( context, ).showSnackBar(const SnackBar(content: Text('已撤回'))); context.pop(); }, onRightTap: null, ); } }