gcx/frontend/admin-app/lib/features/finance/presentation/pages/finance_page.dart

349 lines
12 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import 'package:flutter/material.dart';
import '../../../../app/theme/app_colors.dart';
import '../../../../app/router.dart';
import '../../../../app/i18n/app_localizations.dart';
/// 财务管理页面
///
/// 法币展示,不暴露链上稳定币细节
/// 包含销售收入、Breakage收入、手续费、保证金、冻结款、提现、对账报表
class FinancePage extends StatelessWidget {
const FinancePage({super.key});
@override
Widget build(BuildContext context) {
return DefaultTabController(
length: 3,
child: Scaffold(
appBar: AppBar(
title: Text(context.t('finance_title')),
actions: [
IconButton(
icon: const Icon(Icons.download_rounded),
onPressed: () => _showExportDialog(context),
),
],
bottom: TabBar(
tabs: [
Tab(text: context.t('finance_tab_overview')),
Tab(text: context.t('finance_tab_transactions')),
Tab(text: context.t('finance_tab_reconciliation')),
],
),
),
body: const TabBarView(
children: [
_OverviewTab(),
_TransactionDetailTab(),
_ReconciliationTab(),
],
),
),
);
}
void _showExportDialog(BuildContext context) {
showDialog(
context: context,
builder: (ctx) => SimpleDialog(
title: Text(context.t('finance_export_title')),
children: [
SimpleDialogOption(onPressed: () => Navigator.pop(ctx), child: Text(context.t('finance_export_csv'))),
SimpleDialogOption(onPressed: () => Navigator.pop(ctx), child: Text(context.t('finance_export_excel'))),
SimpleDialogOption(onPressed: () => Navigator.pop(ctx), child: Text(context.t('finance_export_pdf'))),
],
),
);
}
}
class _OverviewTab extends StatelessWidget {
const _OverviewTab();
@override
Widget build(BuildContext context) {
return SingleChildScrollView(
padding: const EdgeInsets.all(20),
child: Column(
children: [
// Balance Card
Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
gradient: AppColors.primaryGradient,
borderRadius: BorderRadius.circular(16),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(context.t('finance_withdrawable'), style: TextStyle(fontSize: 13, color: Colors.white.withValues(alpha: 0.7))),
const SizedBox(height: 4),
const Text('\$42,300.00', style: TextStyle(fontSize: 32, fontWeight: FontWeight.w700, color: Colors.white)),
const SizedBox(height: 16),
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: () {
// TODO: Show withdrawal dialog or navigate to withdrawal flow
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.white,
foregroundColor: AppColors.primary,
),
child: Text(context.t('finance_withdraw')),
),
),
],
),
),
const SizedBox(height: 20),
// Financial Stats
_buildFinanceStatsGrid(context),
const SizedBox(height: 20),
// Guarantee Fund
_buildGuaranteeFundCard(context),
const SizedBox(height: 20),
// Revenue Trend
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: AppColors.surface,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: AppColors.borderLight),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(context.t('finance_revenue_trend'), style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w600)),
const SizedBox(height: 16),
Container(
height: 160,
decoration: BoxDecoration(
color: AppColors.gray50,
borderRadius: BorderRadius.circular(8),
),
child: Center(child: Text(context.t('finance_revenue_chart'), style: const TextStyle(color: AppColors.textTertiary))),
),
],
),
),
],
),
);
}
Widget _buildFinanceStatsGrid(BuildContext context) {
final stats = [
(context.t('finance_sales_income'), '\$125,800', AppColors.success),
(context.t('finance_breakage_income'), '\$8,200', AppColors.info),
(context.t('finance_platform_fee'), '-\$1,510', AppColors.error),
(context.t('finance_pending_settlement'), '\$15,400', AppColors.warning),
(context.t('finance_withdrawn'), '\$66,790', AppColors.textSecondary),
(context.t('finance_total_income'), '\$132,490', AppColors.primary),
];
return GridView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
mainAxisSpacing: 12,
crossAxisSpacing: 12,
childAspectRatio: 2,
),
itemCount: stats.length,
itemBuilder: (context, index) {
final (label, value, color) = stats[index];
return Container(
padding: const EdgeInsets.all(14),
decoration: BoxDecoration(
color: AppColors.surface,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: AppColors.borderLight),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(label, style: const TextStyle(fontSize: 12, color: AppColors.textSecondary)),
Text(value, style: TextStyle(fontSize: 17, fontWeight: FontWeight.w700, color: color)),
],
),
);
},
);
}
Widget _buildGuaranteeFundCard(BuildContext context) {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: AppColors.surface,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: AppColors.borderLight),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
const Icon(Icons.shield_rounded, color: AppColors.info, size: 20),
const SizedBox(width: 8),
Text(context.t('finance_guarantee_title'), style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w600)),
],
),
const SizedBox(height: 16),
_buildRow(context.t('finance_guarantee_deposit'), '\$10,000'),
_buildRow(context.t('finance_frozen_sales'), '\$5,200'),
_buildRow(context.t('finance_frozen_ratio'), '20%'),
const SizedBox(height: 12),
SwitchListTile(
title: Text(context.t('finance_auto_freeze'), style: const TextStyle(fontSize: 14)),
subtitle: Text(context.t('finance_auto_freeze_desc')),
value: true,
onChanged: (_) {
// TODO: Toggle auto-freeze setting
},
activeColor: AppColors.primary,
contentPadding: EdgeInsets.zero,
),
],
),
);
}
Widget _buildRow(String label, String value) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 4),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(label, style: const TextStyle(fontSize: 13, color: AppColors.textSecondary)),
Text(value, style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500)),
],
),
);
}
}
class _TransactionDetailTab extends StatelessWidget {
const _TransactionDetailTab();
@override
Widget build(BuildContext context) {
final transactions = [
('售出 ¥25 礼品卡 x5', '+\$106.25', '今天 14:32', AppColors.success),
('核销结算 ¥100 购物券 x2', '+\$200.00', '今天 12:15', AppColors.success),
('平台手续费', '-\$3.19', '今天 14:32', AppColors.error),
('退款 ¥25 礼品卡', '-\$21.25', '今天 10:08', AppColors.warning),
('售出 ¥50 生活券 x3', '+\$127.50', '昨天 18:45', AppColors.success),
('提现至银行账户', '-\$5,000.00', '昨天 16:00', AppColors.info),
];
return ListView.separated(
padding: const EdgeInsets.all(20),
itemCount: transactions.length,
separatorBuilder: (_, __) => const Divider(height: 1),
itemBuilder: (context, index) {
final (desc, amount, time, color) = transactions[index];
return ListTile(
contentPadding: const EdgeInsets.symmetric(vertical: 6),
title: Text(desc, style: const TextStyle(fontSize: 14)),
subtitle: Text(time, style: const TextStyle(fontSize: 12, color: AppColors.textTertiary)),
trailing: Text(
amount,
style: TextStyle(fontSize: 15, fontWeight: FontWeight.w600, color: color),
),
);
},
);
}
}
class _ReconciliationTab extends StatelessWidget {
const _ReconciliationTab();
@override
Widget build(BuildContext context) {
final reports = [
('2026年1月对账单', '总收入: \$28,450 | 总支出: \$3,210', '已生成'),
('2025年12月对账单', '总收入: \$32,100 | 总支出: \$4,080', '已生成'),
('2025年11月对账单', '总收入: \$25,800 | 总支出: \$2,900', '已生成'),
];
return ListView(
padding: const EdgeInsets.all(20),
children: [
// Generate New
OutlinedButton.icon(
onPressed: () {
// TODO: Trigger reconciliation report generation
},
icon: const Icon(Icons.add_rounded),
label: Text(context.t('finance_generate_report')),
style: OutlinedButton.styleFrom(
minimumSize: const Size(double.infinity, 48),
),
),
const SizedBox(height: 20),
...reports.map((r) {
final (title, summary, status) = r;
return Container(
margin: const EdgeInsets.only(bottom: 12),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: AppColors.surface,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: AppColors.borderLight),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(title, style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w600)),
Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
decoration: BoxDecoration(
color: AppColors.successLight,
borderRadius: BorderRadius.circular(999),
),
child: Text(status, style: const TextStyle(fontSize: 11, color: AppColors.success)),
),
],
),
const SizedBox(height: 6),
Text(summary, style: const TextStyle(fontSize: 12, color: AppColors.textSecondary)),
const SizedBox(height: 12),
Row(
children: [
TextButton.icon(
onPressed: () {
// TODO: Navigate to reconciliation detail view
},
icon: const Icon(Icons.visibility_rounded, size: 16),
label: Text(context.t('view')),
),
TextButton.icon(
onPressed: () {
// TODO: Export reconciliation report
},
icon: const Icon(Icons.download_rounded, size: 16),
label: Text(context.t('export')),
),
],
),
],
),
);
}),
],
);
}
}