import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../widgets/page_scaffold.dart'; import '../../widgets/data_table_card.dart'; import '../../core/theme/app_theme.dart'; import '../../widgets/form_dialog.dart'; class FinanceScreen extends ConsumerStatefulWidget { const FinanceScreen({super.key}); @override ConsumerState createState() => _FinanceScreenState(); } class _FinanceScreenState extends ConsumerState { int _page = 1; String _monthFilter = '2026-04'; String _typeFilter = '全部'; final List> _transactions = [ { 'no': 'FN20260401001', 'type': '应付账款', 'partner': '贵州茅台酒股份有限公司', 'orderNo': 'RK20260401001', 'amount': '85000.00', 'paid': '85000.00', 'balance': '0.00', 'date': '2026-04-01', 'dueDate': '2026-05-01', 'status': '已结清', }, { 'no': 'FN20260401002', 'type': '应付账款', 'partner': '四川五粮液股份有限公司', 'orderNo': 'RK20260401002', 'amount': '42500.00', 'paid': '0.00', 'balance': '42500.00', 'date': '2026-04-01', 'dueDate': '2026-05-01', 'status': '未付款', }, { 'no': 'FN20260402001', 'type': '应付账款', 'partner': '江苏洋河酒厂股份有限公司', 'orderNo': 'RK20260402001', 'amount': '36800.00', 'paid': '20000.00', 'balance': '16800.00', 'date': '2026-04-02', 'dueDate': '2026-05-17', 'status': '部分付款', }, { 'no': 'FN20260402002', 'type': '应付账款', 'partner': '泸州老窖股份有限公司', 'orderNo': 'RK20260403001', 'amount': '55200.00', 'paid': '55200.00', 'balance': '0.00', 'date': '2026-04-03', 'dueDate': '2026-05-03', 'status': '已结清', }, { 'no': 'FN20260403001', 'type': '应付账款', 'partner': '法国拉菲集团中国总代理', 'orderNo': 'RK20260403002', 'amount': '124800.00', 'paid': '0.00', 'balance': '124800.00', 'date': '2026-04-03', 'dueDate': '2026-06-02', 'status': '未付款', }, { 'no': 'FN20260404001', 'type': '应付账款', 'partner': '人头马轩尼诗(中国)有限公司', 'orderNo': 'RK20260404001', 'amount': '30240.00', 'paid': '0.00', 'balance': '30240.00', 'date': '2026-04-04', 'dueDate': '2026-06-03', 'status': '未付款', }, { 'no': 'FN20260404002', 'type': '费用报销', 'partner': '张三', 'orderNo': 'BX20260404001', 'amount': '1280.00', 'paid': '1280.00', 'balance': '0.00', 'date': '2026-04-04', 'dueDate': '2026-04-04', 'status': '已结清', }, ]; // Summary stats double get _totalPayable => _transactions .where((t) => t['type'] == '应付账款') .fold(0.0, (s, t) => s + (double.tryParse(t['balance'] as String) ?? 0)); double get _totalPaid => _transactions.fold( 0.0, (s, t) => s + (double.tryParse(t['paid'] as String) ?? 0)); double get _totalAmount => _transactions.fold( 0.0, (s, t) => s + (double.tryParse(t['amount'] as String) ?? 0)); @override Widget build(BuildContext context) { return PageScaffold( title: '财务管理', tabs: const [ Tab(text: '应付账款'), Tab(text: '付款记录'), Tab(text: '财务报表'), ], tabViews: [ _buildPayableList(), _buildPaymentHistory(), _buildFinanceReport(), ], ); } Widget _buildPayableList() { final filtered = _typeFilter == '全部' ? _transactions : _transactions.where((t) => t['type'] == _typeFilter).toList(); return Column( children: [ // Summary bar Container( color: AppTheme.background, padding: const EdgeInsets.all(12), child: Row( children: [ _FinanceSummaryCard( title: '本月采购总额', value: '¥${(_totalAmount / 10000).toStringAsFixed(1)}万', icon: Icons.shopping_bag, color: AppTheme.primary, ), const SizedBox(width: 12), _FinanceSummaryCard( title: '已付款', value: '¥${(_totalPaid / 10000).toStringAsFixed(1)}万', icon: Icons.check_circle, color: AppTheme.success, ), const SizedBox(width: 12), _FinanceSummaryCard( title: '未付款', value: '¥${(_totalPayable / 10000).toStringAsFixed(1)}万', icon: Icons.pending_actions, color: AppTheme.danger, ), const SizedBox(width: 12), _FinanceSummaryCard( title: '即将到期(7天)', value: '¥16.8万', icon: Icons.alarm, color: AppTheme.accent, ), ], ), ), const Divider(height: 1), Expanded( child: DataTableCard( totalCount: filtered.length, page: _page, onPageChanged: (p) => setState(() => _page = p), toolbar: Row( children: [ ElevatedButton.icon( onPressed: () => _showPaymentDialog(context), icon: const Icon(Icons.payment, size: 16), label: const Text('登记付款'), ), const SizedBox(width: 8), OutlinedButton.icon( onPressed: () {}, icon: const Icon(Icons.file_download_outlined, size: 16), label: const Text('导出对账单'), ), const Spacer(), // Month filter _MonthSelector( value: _monthFilter, onChanged: (v) => setState(() => _monthFilter = v), ), const SizedBox(width: 8), _DropdownFilter( value: _typeFilter, items: ['全部', '应付账款', '费用报销'], onChanged: (v) => setState(() => _typeFilter = v!), hint: '类型', ), ], ), columns: const [ DataColumn(label: Text('凭证号')), DataColumn(label: Text('类型')), DataColumn(label: Text('往来单位')), DataColumn(label: Text('关联单号')), DataColumn(label: Text('应付金额'), numeric: true), DataColumn(label: Text('已付金额'), numeric: true), DataColumn(label: Text('余额'), numeric: true), DataColumn(label: Text('到期日')), DataColumn(label: Text('状态')), DataColumn(label: Text('操作')), ], rows: filtered .map((t) => DataRow( color: WidgetStateProperty.resolveWith((states) { if (t['status'] == '未付款') { final due = DateTime.tryParse(t['dueDate'] as String); if (due != null && due.difference(DateTime.now()).inDays <= 7) { return AppTheme.accent.withOpacity(0.05); } } return null; }), cells: [ DataCell(Text(t['no'] as String, style: const TextStyle( fontFamily: 'monospace', fontSize: 11, color: AppTheme.textSecondary))), DataCell(Text(t['type'] as String)), DataCell(SizedBox( width: 160, child: Text(t['partner'] as String, overflow: TextOverflow.ellipsis), )), DataCell(Text(t['orderNo'] as String, style: const TextStyle( fontFamily: 'monospace', fontSize: 11, color: AppTheme.primary))), DataCell(Text('¥${t['amount']}', style: const TextStyle( fontWeight: FontWeight.w500))), DataCell(Text('¥${t['paid']}', style: const TextStyle( color: AppTheme.success))), DataCell(Text( '¥${t['balance']}', style: TextStyle( color: (t['balance'] as String) != '0.00' ? AppTheme.danger : AppTheme.textSecondary, fontWeight: FontWeight.w600, ), )), DataCell(Text(t['dueDate'] as String)), DataCell(_PaymentStatusBadge(t['status'] as String)), DataCell(Row( mainAxisSize: MainAxisSize.min, children: [ TextButton( onPressed: () {}, child: const Text('详情', style: TextStyle(fontSize: 12))), if (t['status'] != '已结清') TextButton( onPressed: () => _showPaymentDialog(context), child: const Text('付款', style: TextStyle( color: AppTheme.success, fontSize: 12))), ], )), ], )) .toList(), ), ), ], ); } Widget _buildPaymentHistory() { final payments = [ { 'date': '2026-04-01', 'no': 'ZF20260401001', 'partner': '贵州茅台酒股份有限公司', 'amount': '85000.00', 'method': '银行转账', 'bank': '招商银行', 'remark': '4月货款结清', }, { 'date': '2026-04-02', 'no': 'ZF20260402001', 'partner': '泸州老窖股份有限公司', 'amount': '55200.00', 'method': '银行转账', 'bank': '工商银行', 'remark': '4月货款', }, { 'date': '2026-04-02', 'no': 'ZF20260402002', 'partner': '江苏洋河酒厂股份有限公司', 'amount': '20000.00', 'method': '银行转账', 'bank': '建设银行', 'remark': '部分预付', }, { 'date': '2026-04-04', 'no': 'ZF20260404001', 'partner': '张三', 'amount': '1280.00', 'method': '现金', 'bank': '-', 'remark': '差旅报销', }, ]; return DataTableCard( totalCount: payments.length, page: 1, toolbar: Row( children: [ ElevatedButton.icon( onPressed: () => _showPaymentDialog(context), icon: const Icon(Icons.add, size: 16), label: const Text('新增付款'), ), const SizedBox(width: 8), OutlinedButton.icon( onPressed: () {}, icon: const Icon(Icons.file_download_outlined, size: 16), label: const Text('导出'), ), const Spacer(), _MonthSelector( value: _monthFilter, onChanged: (v) => setState(() => _monthFilter = v), ), ], ), columns: const [ DataColumn(label: Text('付款日期')), DataColumn(label: Text('付款单号')), DataColumn(label: Text('收款方')), DataColumn(label: Text('金额'), numeric: true), DataColumn(label: Text('付款方式')), DataColumn(label: Text('银行')), DataColumn(label: Text('备注')), DataColumn(label: Text('操作')), ], rows: payments .map((p) => DataRow(cells: [ DataCell(Text(p['date'] as String)), DataCell(Text(p['no'] as String, style: const TextStyle( fontFamily: 'monospace', fontSize: 11, color: AppTheme.textSecondary))), DataCell(SizedBox( width: 160, child: Text(p['partner'] as String, overflow: TextOverflow.ellipsis), )), DataCell(Text( '¥${p['amount']}', style: const TextStyle( color: AppTheme.danger, fontWeight: FontWeight.w600), )), DataCell(Text(p['method'] as String)), DataCell(Text(p['bank'] as String)), DataCell(Text(p['remark'] as String)), DataCell(TextButton( onPressed: () {}, child: const Text('查看', style: TextStyle(fontSize: 12)))), ])) .toList(), ); } Widget _buildFinanceReport() { return SingleChildScrollView( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Month/period selector Row( children: [ const Text('报表周期:', style: TextStyle(fontSize: 14)), const SizedBox(width: 8), _MonthSelector( value: _monthFilter, onChanged: (v) => setState(() => _monthFilter = v), ), const SizedBox(width: 16), ElevatedButton.icon( onPressed: () {}, icon: const Icon(Icons.picture_as_pdf, size: 16), label: const Text('导出报表'), ), ], ), const SizedBox(height: 16), // Metrics row Row( children: [ _ReportCard( title: '本月采购总额', value: '¥${(_totalAmount / 10000).toStringAsFixed(2)}万', change: '+12.5%', positive: false, ), const SizedBox(width: 12), _ReportCard( title: '本月付款总额', value: '¥${(_totalPaid / 10000).toStringAsFixed(2)}万', change: '+8.3%', positive: true, ), const SizedBox(width: 12), _ReportCard( title: '应付账款余额', value: '¥${(_totalPayable / 10000).toStringAsFixed(2)}万', change: '-5.2%', positive: true, ), const SizedBox(width: 12), _ReportCard( title: '库存总价值', value: '¥103.17万', change: '+3.8%', positive: false, ), ], ), const SizedBox(height: 16), // Category breakdown Card( child: Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text('按品类采购金额', style: TextStyle( fontSize: 14, fontWeight: FontWeight.w600)), const SizedBox(height: 16), ...[ ('白酒', 238600.0, AppTheme.primary), ('葡萄酒', 124800.0, const Color(0xFF7B1FA2)), ('洋酒', 30240.0, AppTheme.accent), ('啤酒', 1970.0, const Color(0xFF0097A7)), ].map((item) { final total = 395610.0; final pct = item.$2 / total; return Padding( padding: const EdgeInsets.only(bottom: 12), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ SizedBox( width: 60, child: Text(item.$1, style: const TextStyle(fontSize: 13))), const SizedBox(width: 8), Expanded( child: LinearProgressIndicator( value: pct, backgroundColor: AppTheme.border, color: item.$3, minHeight: 8, borderRadius: BorderRadius.circular(4), ), ), const SizedBox(width: 8), SizedBox( width: 100, child: Text( '¥${(item.$2 / 10000).toStringAsFixed(1)}万 (${(pct * 100).toStringAsFixed(1)}%)', style: const TextStyle(fontSize: 12), textAlign: TextAlign.right, ), ), ], ), ], ), ); }), ], ), ), ), ], ), ); } void _showPaymentDialog(BuildContext context) { showDialog( context: context, builder: (ctx) => FormDialog( title: '登记付款', width: 480, onConfirm: () { Navigator.of(ctx).pop(); ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('付款记录已登记'), backgroundColor: AppTheme.success, ), ); }, content: Column( children: [ _DialogRow( children: [ _DialogField( label: '付款方式', child: DropdownButtonFormField( value: '银行转账', items: ['银行转账', '现金', '支票', '网银'] .map((s) => DropdownMenuItem( value: s, child: Text(s, style: const TextStyle(fontSize: 13)))) .toList(), onChanged: (_) {}, decoration: const InputDecoration(), )), _DialogField( label: '付款金额', required: true, child: TextFormField( decoration: const InputDecoration( hintText: '0.00', prefixText: '¥'), keyboardType: const TextInputType.numberWithOptions(decimal: true))), ], ), const SizedBox(height: 12), _DialogRow( children: [ _DialogField( label: '付款银行', child: DropdownButtonFormField( value: '招商银行', items: ['招商银行', '工商银行', '建设银行', '农业银行', '中国银行'] .map((s) => DropdownMenuItem( value: s, child: Text(s, style: const TextStyle(fontSize: 13)))) .toList(), onChanged: (_) {}, decoration: const InputDecoration(), )), _DialogField( label: '付款日期', child: InputDecorator( decoration: const InputDecoration(), child: const Text('2026-04-04', style: TextStyle(fontSize: 13)), )), ], ), const SizedBox(height: 12), _DialogField( label: '备注', fullWidth: true, child: TextFormField( maxLines: 2, decoration: const InputDecoration(hintText: '付款说明'), ), ), ], ), ), ); } } class _DialogRow extends StatelessWidget { final List children; const _DialogRow({required this.children}); @override Widget build(BuildContext context) { return Row( children: children .expand((child) => [ Expanded(child: child), if (child != children.last) const SizedBox(width: 12), ]) .toList(), ); } } class _DialogField extends StatelessWidget { final String label; final Widget child; final bool required; final bool fullWidth; const _DialogField({ required this.label, required this.child, this.required = false, this.fullWidth = false, }); @override Widget build(BuildContext context) { Widget content = Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row(children: [ if (required) const Text('* ', style: TextStyle(color: AppTheme.danger, fontSize: 13)), Text(label, style: const TextStyle( fontSize: 13, color: AppTheme.textSecondary)), ]), const SizedBox(height: 6), child, ], ); return fullWidth ? SizedBox(width: double.infinity, child: content) : content; } } class _FinanceSummaryCard extends StatelessWidget { final String title; final String value; final IconData icon; final Color color; const _FinanceSummaryCard({ required this.title, required this.value, required this.icon, required this.color, }); @override Widget build(BuildContext context) { return Expanded( child: Container( height: 72, padding: const EdgeInsets.symmetric(horizontal: 16), decoration: BoxDecoration( color: AppTheme.surface, borderRadius: BorderRadius.circular(4), border: Border.all(color: AppTheme.border, width: 0.5), ), child: Row( children: [ Container( width: 40, height: 40, decoration: BoxDecoration( color: color.withOpacity(0.1), borderRadius: BorderRadius.circular(8), ), child: Icon(icon, color: color, size: 22), ), const SizedBox(width: 12), Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.center, children: [ Text(title, style: const TextStyle( fontSize: 12, color: AppTheme.textSecondary)), const SizedBox(height: 4), Text(value, style: TextStyle( fontSize: 18, fontWeight: FontWeight.w700, color: color)), ], ), ], ), ), ); } } class _PaymentStatusBadge extends StatelessWidget { final String status; const _PaymentStatusBadge(this.status); @override Widget build(BuildContext context) { Color bg, fg; switch (status) { case '已结清': bg = const Color(0xFFE8F5E9); fg = AppTheme.success; break; case '部分付款': bg = const Color(0xFFFFF3E0); fg = AppTheme.accent; break; case '未付款': bg = const Color(0xFFFFEBEE); fg = AppTheme.danger; break; default: bg = const Color(0xFFF5F5F5); fg = AppTheme.textSecondary; } return Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), decoration: BoxDecoration(color: bg, borderRadius: BorderRadius.circular(3)), child: Text(status, style: TextStyle( color: fg, fontSize: 12, fontWeight: FontWeight.w500)), ); } } class _ReportCard extends StatelessWidget { final String title; final String value; final String change; final bool positive; const _ReportCard({ required this.title, required this.value, required this.change, required this.positive, }); @override Widget build(BuildContext context) { return Expanded( child: Container( height: 88, padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: AppTheme.surface, borderRadius: BorderRadius.circular(4), border: Border.all(color: AppTheme.border, width: 0.5), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(title, style: const TextStyle( fontSize: 12, color: AppTheme.textSecondary)), const SizedBox(height: 8), Row( crossAxisAlignment: CrossAxisAlignment.end, children: [ Text(value, style: const TextStyle( fontSize: 18, fontWeight: FontWeight.w700)), const Spacer(), Text( change, style: TextStyle( fontSize: 12, color: positive ? AppTheme.success : AppTheme.danger, fontWeight: FontWeight.w500, ), ), ], ), ], ), ), ); } } class _MonthSelector extends StatelessWidget { final String value; final ValueChanged onChanged; const _MonthSelector({required this.value, required this.onChanged}); @override Widget build(BuildContext context) { final months = [ '2026-04', '2026-03', '2026-02', '2026-01', '2025-12', '2025-11', ]; return Container( height: 36, padding: const EdgeInsets.symmetric(horizontal: 8), decoration: BoxDecoration( border: Border.all(color: AppTheme.border), borderRadius: BorderRadius.circular(4), color: AppTheme.surface, ), child: DropdownButtonHideUnderline( child: DropdownButton( value: value, items: months .map((m) => DropdownMenuItem( value: m, child: Text(m, style: const TextStyle(fontSize: 13)))) .toList(), onChanged: (v) => onChanged(v!), style: const TextStyle( fontSize: 13, color: AppTheme.textPrimary), ), ), ); } } class _DropdownFilter extends StatelessWidget { final String value; final List items; final ValueChanged onChanged; final String hint; const _DropdownFilter({ required this.value, required this.items, required this.onChanged, required this.hint, }); @override Widget build(BuildContext context) { return Container( height: 36, padding: const EdgeInsets.symmetric(horizontal: 8), decoration: BoxDecoration( border: Border.all(color: AppTheme.border), borderRadius: BorderRadius.circular(4), color: AppTheme.surface, ), child: DropdownButtonHideUnderline( child: DropdownButton( value: value, items: items .map((s) => DropdownMenuItem( value: s, child: Text(s, style: const TextStyle(fontSize: 13)))) .toList(), onChanged: onChanged, style: const TextStyle( fontSize: 13, color: AppTheme.textPrimary), ), ), ); } }