import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:tdesign_flutter/tdesign_flutter.dart'; import 'package:go_router/go_router.dart'; import '../../core/theme/app_colors.dart'; import '../shell/nav_bar_config.dart'; import '../../core/utils/responsive.dart'; import '../../shared/widgets/form_section.dart'; import '../../shared/widgets/form_field_row.dart'; import '../../shared/widgets/action_bar.dart'; import 'expense_apply_controller.dart'; import '../../core/i18n/app_localizations.dart'; import 'expense_model.dart'; class ExpenseApplyPage extends ConsumerStatefulWidget { final String? editId; const ExpenseApplyPage({super.key, this.editId}); @override ConsumerState createState() => _ExpenseApplyPageState(); } class _ExpenseApplyPageState extends ConsumerState { final _remarkController = TextEditingController(); final _purposeController = TextEditingController(); final _bankNameController = TextEditingController(text: '中国银行'); final _accountNameController = TextEditingController(text: '张三'); @override void dispose() { _remarkController.dispose(); _purposeController.dispose(); _bankNameController.dispose(); _accountNameController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { final controller = ref.watch(expenseApplyProvider(widget.editId).notifier); final state = ref.watch(expenseApplyProvider(widget.editId)); final r = ResponsiveHelper.of(context); final l10n = AppLocalizations.of(context); ref .read(navBarConfigProvider.notifier) .update( NavBarConfig( title: widget.editId != null ? l10n.get('editExpense') : l10n.get('expenseApply'), showBack: true, onBack: () => context.pop(), ), ); return Column( children: [ Expanded( child: Align( alignment: Alignment.topCenter, child: ConstrainedBox( constraints: BoxConstraints(maxWidth: r.formMaxWidth), child: SingleChildScrollView( padding: const EdgeInsets.all(16), child: Column( children: [ _buildImportLink(), const SizedBox(height: 16), _buildBasicInfoSection(controller, state), const SizedBox(height: 16), _buildAccountSection(controller, state), const SizedBox(height: 16), _buildDetailSection(controller, state), const SizedBox(height: 16), _buildInvoiceSection(controller, state), ], ), ), ), ), ), _buildBottomButtons(controller, state), ], ); } Widget _buildImportLink() { return GestureDetector( onTap: () { ScaffoldMessenger.of( context, ).showSnackBar(const SnackBar(content: Text('一键导入功能开发中'))); }, child: Container( height: 44, decoration: BoxDecoration( color: AppColors.primaryLight, borderRadius: BorderRadius.circular(8), ), child: const Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.download, size: 14, color: AppColors.primary), SizedBox(width: 8), Text( '一键导入已通过的事前申请', style: TextStyle( fontSize: AppFontSizes.body, color: AppColors.primary, ), ), ], ), ), ); } Widget _buildBasicInfoSection( ExpenseApplyController controller, ExpenseApplyState state, ) { final expense = state.expense; return FormSection( title: '基本信息', children: [ FormFieldRow(label: '报销事由', hint: '请输入报销事由'), FormFieldRow( label: '关联项目', value: expense.projectName.isNotEmpty ? expense.projectName : null, hint: '请选择项目', onTap: () { ScaffoldMessenger.of( context, ).showSnackBar(const SnackBar(content: Text('项目选择功能开发中'))); }, ), FormFieldRow( label: '预算科目', value: expense.budgetSubjectId.isNotEmpty ? expense.budgetSubjectId : null, hint: '请选择科目', onTap: () { ScaffoldMessenger.of( context, ).showSnackBar(const SnackBar(content: Text('预算科目功能开发中'))); }, ), FormFieldRow( label: '成本中心', value: expense.costCenterId.isNotEmpty ? expense.costCenterId : null, hint: '请选择成本中心', onTap: () { ScaffoldMessenger.of( context, ).showSnackBar(const SnackBar(content: Text('成本中心功能开发中'))); }, ), 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( '¥${expense.totalAmount.toStringAsFixed(2)}', style: const TextStyle( fontSize: AppFontSizes.subtitle, fontWeight: FontWeight.w700, color: AppColors.amountPrimary, ), ), ], ), ), ], ); } Widget _buildAccountSection( ExpenseApplyController controller, ExpenseApplyState state, ) { return FormSection( title: '收款账户', children: [ FormFieldRow( label: '开户银行', value: _bankNameController.text.isNotEmpty ? _bankNameController.text : null, hint: '请选择开户银行', onTap: () { ScaffoldMessenger.of( context, ).showSnackBar(const SnackBar(content: Text('选择开户银行功能开发中'))); }, ), FormFieldRow( label: '账户名称', value: _accountNameController.text, readOnly: true, showArrow: false, ), FormFieldRow( label: '银行账号', hint: '请输入银行账号', onTap: () { ScaffoldMessenger.of( context, ).showSnackBar(const SnackBar(content: Text('银行账号输入功能开发中'))); }, ), ], ); } Widget _buildDetailSection( ExpenseApplyController controller, ExpenseApplyState state, ) { return FormSection( title: '费用明细', showAction: state.expense.details.isNotEmpty, actionText: '添加', onActionTap: () => _showAddDetailDialog(controller), children: [ if (state.expense.details.isEmpty) ...[ GestureDetector( onTap: () => _showAddDetailDialog(controller), child: Container( padding: const EdgeInsets.symmetric(vertical: 12), decoration: BoxDecoration( border: Border.all( color: AppColors.border, strokeAlign: BorderSide.strokeAlignInside, ), borderRadius: BorderRadius.circular(4), color: AppColors.bgPage, ), child: const Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.add, size: 16, color: AppColors.primary), SizedBox(width: 4), Text( '添加费用明细', style: TextStyle( fontSize: AppFontSizes.body, color: AppColors.primary, ), ), ], ), ), ), ] else ...state.expense.details.asMap().entries.map((entry) { final d = entry.value; return Container( height: 38, padding: const EdgeInsets.symmetric(vertical: 4), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Row( children: [ Icon( Icons.receipt_long, size: 14, color: AppColors.textSecondary, ), const SizedBox(width: 8), Text( d.expenseDesc, style: const TextStyle( fontSize: AppFontSizes.body, color: AppColors.textPrimary, ), ), ], ), Row( mainAxisSize: MainAxisSize.min, children: [ Text( '¥${d.totalAmount.toStringAsFixed(2)}', style: const TextStyle( fontSize: AppFontSizes.body, fontWeight: FontWeight.w500, color: AppColors.amountPrimary, ), ), const SizedBox(width: 8), GestureDetector( onTap: () { controller.removeDetail(entry.key); controller.recalculateAmount(); }, child: const Icon( Icons.close, size: 16, color: AppColors.textPlaceholder, ), ), ], ), ], ), ); }), if (state.expense.details.isNotEmpty) ...[ Container(height: 1, color: AppColors.border), Container( height: 36, padding: const EdgeInsets.symmetric(vertical: 8), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Text( '合计', style: TextStyle( fontSize: AppFontSizes.body, fontWeight: FontWeight.w600, color: AppColors.textPrimary, ), ), Text( '¥${state.expense.totalAmount.toStringAsFixed(2)}', style: const TextStyle( fontSize: AppFontSizes.subtitle, fontWeight: FontWeight.w700, color: AppColors.amountPrimary, ), ), ], ), ), ], ], ); } Widget _buildInvoiceSection( ExpenseApplyController controller, ExpenseApplyState state, ) { return FormSection( title: '发票上传', children: [ const Text( '最多上传9张发票', style: TextStyle( fontSize: AppFontSizes.caption, color: AppColors.textPlaceholder, ), ), const SizedBox(height: 8), Wrap( spacing: 8, runSpacing: 8, children: List.generate(6, (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: i == 0 ? const Center( child: Icon( Icons.add, size: 24, color: AppColors.textPlaceholder, ), ) : const SizedBox.shrink(), ); }), ), ], ); } Widget _buildBottomButtons( ExpenseApplyController controller, ExpenseApplyState state, ) { return ActionBar( leftLabel: '重置', centerLabel: '存为草稿', rightLabel: '提交审批', onLeftTap: () { setState(() { _purposeController.clear(); _remarkController.clear(); }); }, onCenterTap: state.isSubmitting ? null : () async { await controller.saveDraft(); if (context.mounted) context.pop(); }, onRightTap: state.isSubmitting ? null : () async { final ok = await controller.submit(); if (context.mounted && ok) context.pop(); }, showLeft: true, ); } void _showAddDetailDialog(ExpenseApplyController controller) { final nameCtrl = TextEditingController(); final amountCtrl = TextEditingController(); final descCtrl = TextEditingController(); showDialog( context: context, builder: (_) => TDAlertDialog( title: '添加明细', contentWidget: Column( mainAxisSize: MainAxisSize.min, children: [ TDInput(controller: nameCtrl, hintText: '费用名称'), const SizedBox(height: 8), TDInput( controller: amountCtrl, hintText: '金额', inputType: TextInputType.number, ), const SizedBox(height: 8), TDInput(controller: descCtrl, hintText: '描述'), ], ), leftBtn: TDDialogButtonOptions( title: '取消', action: () => Navigator.pop(context), ), rightBtn: TDDialogButtonOptions( title: '添加', action: () { final amount = double.tryParse(amountCtrl.text) ?? 0.0; controller.addDetail( ExpenseDetailModel( id: DateTime.now().millisecondsSinceEpoch.toString(), expenseId: '', expenseDate: DateTime.now(), expenseType: '', expenseDesc: nameCtrl.text, amount: amount, totalAmount: amount, remark: descCtrl.text, ), ); controller.recalculateAmount(); Navigator.pop(context); }, ), ), ); } }