feat(web): 构建Flutter Web应用并添加资金管理功能

- 生成Flutter Web构建产物,包括主应用文件、资源文件和CanvasKit支持
- 添加资金订单管理功能,支持资金充值和提现操作
- 更新管理后台界面,优化仪表板和订单页面显示
- 扩展后端API接口,增加资金相关服务和控制器
- 添加数据库索引优化查询性能
This commit is contained in:
2026-04-05 23:58:01 +08:00
165 changed files with 221727 additions and 252 deletions

View File

@@ -116,12 +116,911 @@ class _AssetPageState extends State<AssetPage> with AutomaticKeepAliveClientMixi
HoldingsSection(holdings: _activeTab == 1 ? provider.holdings : []),
],
),
],
),
SizedBox(height: AppSpacing.sm),
Text(
displayBalance,
style: GoogleFonts.spaceGrotesk(
fontSize: 20,
fontWeight: FontWeight.bold,
color: colorScheme.onSurface,
),
);
},
),
SizedBox(height: AppSpacing.lg),
Row(
children: [
Expanded(
child: NeonButton(
text: '充值',
type: NeonButtonType.tertiary,
onPressed: () => _showDepositDialog(context),
height: 44,
showGlow: false,
),
),
SizedBox(width: AppSpacing.sm),
Expanded(
child: NeonButton(
text: '提现',
type: NeonButtonType.secondary,
onPressed: () => _showWithdrawDialog(context, fund?.balance),
height: 44,
showGlow: false,
),
),
SizedBox(width: AppSpacing.sm),
Expanded(
child: NeonButton(
text: '划转',
type: NeonButtonType.outline,
onPressed: () => _navigateToTransfer(context),
height: 44,
showGlow: false,
),
),
],
),
],
),
);
}
}
/// 交易账户卡片 - Glass Panel 风格
class _TradeAccountCard extends StatelessWidget {
final List holdings;
final String? tradeBalance;
const _TradeAccountCard({required this.holdings, this.tradeBalance});
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
final isDark = Theme.of(context).brightness == Brightness.dark;
// 计算总市值所有持仓折算成USDT
double totalValue = 0;
for (var h in holdings) {
final value = double.tryParse(h.currentValue?.toString() ?? '0') ?? 0;
totalValue += value;
}
// 对持仓进行排序USDT 放在最上面
final sortedHoldings = List.from(holdings);
sortedHoldings.sort((a, b) {
final codeA = (a.coinCode ?? a['coinCode'] ?? '').toString().toUpperCase();
final codeB = (b.coinCode ?? b['coinCode'] ?? '').toString().toUpperCase();
if (codeA == 'USDT') return -1;
if (codeB == 'USDT') return 1;
return 0;
});
return GlassPanel(
padding: AppSpacing.cardPadding,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 标题行
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
Container(
width: 36,
height: 36,
decoration: BoxDecoration(
color: colorScheme.primary.withOpacity(0.1),
borderRadius: BorderRadius.circular(AppRadius.md),
),
child: Icon(
LucideIcons.trendingUp,
size: 18,
color: colorScheme.primary,
),
),
SizedBox(width: AppSpacing.sm),
Text(
'交易账户',
style: GoogleFonts.spaceGrotesk(
fontSize: 16,
fontWeight: FontWeight.bold,
color: colorScheme.onSurface,
),
),
],
),
Icon(
LucideIcons.chevronRight,
size: 14,
color: colorScheme.primary,
),
],
),
SizedBox(height: AppSpacing.md),
// 余额
Text(
'余额 (USDT)',
style: TextStyle(
fontSize: 11,
color: colorScheme.onSurfaceVariant,
),
),
SizedBox(height: AppSpacing.xs),
Text(
totalValue.toStringAsFixed(2),
style: GoogleFonts.spaceGrotesk(
fontSize: 20,
fontWeight: FontWeight.bold,
color: colorScheme.onSurface,
),
),
SizedBox(height: AppSpacing.lg),
// 持仓列表标题
Text(
'持仓列表',
style: GoogleFonts.spaceGrotesk(
fontSize: 14,
fontWeight: FontWeight.w600,
color: colorScheme.onSurfaceVariant,
),
),
SizedBox(height: AppSpacing.md),
if (sortedHoldings.isEmpty)
const _EmptyState(icon: LucideIcons.wallet, message: '暂无持仓')
else
ListView.separated(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: sortedHoldings.length,
separatorBuilder: (_, __) => Container(
margin: EdgeInsets.only(left: 56),
height: 1,
color: AppColorScheme.glassPanelBorder,
),
itemBuilder: (context, index) => _HoldingItem(holding: sortedHoldings[index]),
),
],
),
);
}
}
/// 空状态
class _EmptyState extends StatelessWidget {
final IconData icon;
final String message;
const _EmptyState({required this.icon, required this.message});
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return Center(
child: Padding(
padding: EdgeInsets.all(AppSpacing.xl),
child: Column(
children: [
Icon(
icon,
size: 48,
color: colorScheme.onSurfaceVariant,
),
SizedBox(height: AppSpacing.sm + AppSpacing.xs),
Text(
message,
style: TextStyle(color: colorScheme.onSurfaceVariant),
),
],
),
),
);
}
}
/// 持仓项
class _HoldingItem extends StatelessWidget {
final dynamic holding;
const _HoldingItem({required this.holding});
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
final isDark = Theme.of(context).brightness == Brightness.dark;
return Padding(
padding: EdgeInsets.symmetric(vertical: AppSpacing.sm),
child: Row(
children: [
Container(
width: 40,
height: 40,
decoration: BoxDecoration(
color: colorScheme.primary.withOpacity(0.1),
borderRadius: BorderRadius.circular(AppRadius.md),
),
child: Center(
child: Text(
holding.coinCode.substring(0, 1),
style: TextStyle(
color: colorScheme.primary,
fontWeight: FontWeight.bold,
),
),
),
),
SizedBox(width: AppSpacing.sm + AppSpacing.xs),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
holding.coinCode,
style: GoogleFonts.spaceGrotesk(
fontSize: 14,
fontWeight: FontWeight.w600,
color: colorScheme.onSurface,
),
),
Text(
'数量: ${holding.quantity}',
style: TextStyle(
fontSize: 12,
color: colorScheme.onSurfaceVariant,
),
),
],
),
),
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
'${holding.currentValue} USDT',
style: TextStyle(
fontSize: 12,
color: colorScheme.onSurface,
),
),
Text(
holding.formattedProfitRate,
style: TextStyle(
color: holding.isProfit ? AppColorScheme.getUpColor(isDark) : AppColorScheme.down,
fontSize: 12,
fontWeight: FontWeight.w600,
),
),
],
),
],
),
);
}
}
// ============================================
// Dialogs - Glass Panel 风格
// ============================================
void _showDepositDialog(BuildContext context) {
final amountController = TextEditingController();
final formKey = GlobalKey<ShadFormState>();
final colorScheme = Theme.of(context).colorScheme;
showShadDialog(
context: context,
builder: (ctx) => Dialog(
backgroundColor: Colors.transparent,
child: GlassPanel(
borderRadius: BorderRadius.circular(AppRadius.lg),
padding: EdgeInsets.all(AppSpacing.lg),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Deposit (充值)',
style: GoogleFonts.spaceGrotesk(
fontSize: 16,
fontWeight: FontWeight.bold,
color: colorScheme.onSurface,
),
),
SizedBox(height: AppSpacing.xs),
Text(
'Asset: USDT',
style: TextStyle(
fontSize: 12,
letterSpacing: 0.1,
color: colorScheme.onSurfaceVariant,
),
),
],
),
Container(
padding: EdgeInsets.all(AppSpacing.sm),
decoration: BoxDecoration(
color: colorScheme.surfaceContainerHigh,
borderRadius: BorderRadius.circular(AppRadius.md),
),
child: Icon(
LucideIcons.wallet,
color: colorScheme.secondary,
),
),
],
),
SizedBox(height: AppSpacing.lg),
ShadForm(
key: formKey,
child: ShadInputFormField(
id: 'amount',
controller: amountController,
label: const Text('充值金额'),
placeholder: const Text('最低 1000 USDT'),
keyboardType: const TextInputType.numberWithOptions(decimal: true),
validator: (v) {
if (v == null || v.isEmpty) return '请输入金额';
final n = double.tryParse(v);
if (n == null || n <= 0) return '请输入有效金额';
if (n < 1000) return '单笔最低充值1000 USDT';
return null;
},
),
),
SizedBox(height: AppSpacing.lg),
Row(
children: [
Expanded(
child: NeonButton(
text: '取消',
type: NeonButtonType.outline,
onPressed: () => Navigator.of(ctx).pop(),
height: 48,
showGlow: false,
),
),
SizedBox(width: AppSpacing.sm),
Expanded(
child: NeonButton(
text: '下一步',
type: NeonButtonType.primary,
onPressed: () async {
if (formKey.currentState!.saveAndValidate()) {
Navigator.of(ctx).pop();
final response = await context.read<AssetProvider>().deposit(
amount: amountController.text,
);
if (context.mounted) {
if (response.success && response.data != null) {
_showDepositResultDialog(context, response.data!);
} else {
_showResultDialog(context, '申请失败', response.message);
}
}
}
},
height: 48,
showGlow: true,
),
),
],
),
],
),
),
),
);
}
void _showDepositResultDialog(BuildContext context, Map<String, dynamic> data) {
final orderNo = data['orderNo'] as String? ?? '';
final amount = data['amount']?.toString() ?? '0.00';
final walletAddress = data['walletAddress'] as String? ?? '';
final walletNetwork = data['walletNetwork'] as String? ?? 'TRC20';
final colorScheme = Theme.of(context).colorScheme;
final isDark = Theme.of(context).brightness == Brightness.dark;
showShadDialog(
context: context,
builder: (ctx) => Dialog(
backgroundColor: Colors.transparent,
child: GlassPanel(
borderRadius: BorderRadius.circular(AppRadius.lg),
padding: EdgeInsets.all(AppSpacing.lg),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
NeonIcon(
icon: Icons.check_circle,
color: AppColorScheme.getUpColor(isDark),
size: 24,
),
SizedBox(width: AppSpacing.sm),
Text(
'充值申请成功',
style: GoogleFonts.spaceGrotesk(
fontSize: 16,
fontWeight: FontWeight.bold,
color: colorScheme.onSurface,
),
),
],
),
SizedBox(height: AppSpacing.lg),
_InfoRow(label: '订单号', value: orderNo),
SizedBox(height: AppSpacing.sm),
_InfoRow(label: '充值金额', value: '$amount USDT', isBold: true),
SizedBox(height: AppSpacing.lg),
Text(
'请向以下地址转账:',
style: TextStyle(
fontSize: 12,
color: colorScheme.onSurfaceVariant,
),
),
SizedBox(height: AppSpacing.sm),
_WalletAddressCard(address: walletAddress, network: walletNetwork),
SizedBox(height: AppSpacing.md),
Container(
padding: EdgeInsets.all(AppSpacing.sm),
decoration: BoxDecoration(
color: AppColorScheme.warning.withOpacity(0.1),
borderRadius: BorderRadius.circular(AppRadius.md),
border: Border.all(
color: AppColorScheme.warning.withOpacity(0.2),
),
),
child: Row(
children: [
Icon(Icons.info_outline, size: 16, color: AppColorScheme.warning),
SizedBox(width: AppSpacing.sm),
Expanded(
child: Text(
'转账完成后请点击"已打款"按钮确认',
style: TextStyle(fontSize: 12, color: AppColorScheme.warning),
),
),
],
),
),
SizedBox(height: AppSpacing.lg),
Row(
children: [
Expanded(
child: NeonButton(
text: '稍后确认',
type: NeonButtonType.outline,
onPressed: () => Navigator.of(ctx).pop(),
height: 44,
showGlow: false,
),
),
SizedBox(width: AppSpacing.sm),
Expanded(
child: NeonButton(
text: '已打款',
type: NeonButtonType.primary,
onPressed: () async {
Navigator.of(ctx).pop();
final response = await context.read<AssetProvider>().confirmPay(orderNo);
if (context.mounted) {
_showResultDialog(
context,
response.success ? '确认成功' : '确认失败',
response.success ? '请等待管理员审核' : response.message,
);
}
},
height: 44,
showGlow: true,
),
),
],
),
],
),
),
),
);
}
class _InfoRow extends StatelessWidget {
final String label;
final String value;
final bool isBold;
const _InfoRow({required this.label, required this.value, this.isBold = false});
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
label,
style: TextStyle(
fontSize: 12,
color: colorScheme.onSurfaceVariant,
),
),
Text(
value,
style: TextStyle(
fontSize: 12,
fontWeight: isBold ? FontWeight.bold : FontWeight.normal,
color: colorScheme.onSurface,
),
),
],
);
}
}
class _WalletAddressCard extends StatelessWidget {
final String address;
final String network;
const _WalletAddressCard({required this.address, required this.network});
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return Container(
padding: EdgeInsets.all(AppSpacing.md),
decoration: BoxDecoration(
color: colorScheme.surfaceContainerHigh,
borderRadius: BorderRadius.circular(AppRadius.md),
border: Border.all(
color: colorScheme.outlineVariant.withOpacity(0.3),
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Expanded(
child: Text(
address,
style: TextStyle(
fontFamily: 'monospace',
fontSize: 12,
color: colorScheme.onSurface,
),
),
),
GestureDetector(
onTap: () {
Clipboard.setData(ClipboardData(text: address));
ToastUtils.show('地址已复制到剪贴板');
},
child: Container(
padding: EdgeInsets.all(AppSpacing.xs),
decoration: BoxDecoration(
color: colorScheme.primary.withOpacity(0.1),
borderRadius: BorderRadius.circular(AppRadius.sm),
),
child: Icon(
LucideIcons.copy,
size: 16,
color: colorScheme.primary,
),
),
),
],
),
SizedBox(height: AppSpacing.sm),
Text(
'网络: $network',
style: TextStyle(
fontSize: 11,
color: colorScheme.onSurfaceVariant,
),
),
],
),
);
}
}
void _showWithdrawDialog(BuildContext context, String? balance) {
final amountController = TextEditingController();
final addressController = TextEditingController();
final contactController = TextEditingController();
final formKey = GlobalKey<ShadFormState>();
final feeNotifier = ValueNotifier<String>('提现将扣除10%手续费');
final colorScheme = Theme.of(context).colorScheme;
final networksNotifier = ValueNotifier<List<String>>([]);
final selectedNetworkNotifier = ValueNotifier<String?>(null);
amountController.addListener(() {
final amount = double.tryParse(amountController.text) ?? 0;
if (amount > 0) {
final fee = amount * 0.1;
final receivable = amount - fee;
feeNotifier.value = '手续费(10%): -${fee.toStringAsFixed(2)} USDT | 应付款: ${receivable.toStringAsFixed(2)} USDT';
} else {
feeNotifier.value = '提现将扣除10%手续费';
}
});
// 获取网络列表
context.read<AssetProvider>().getWalletNetworks().then((list) {
networksNotifier.value = list;
if (list.isNotEmpty) {
selectedNetworkNotifier.value = list.first;
}
});
showShadDialog(
context: context,
builder: (ctx) => StatefulBuilder(
builder: (ctx, setState) => Dialog(
backgroundColor: Colors.transparent,
child: GlassPanel(
borderRadius: BorderRadius.circular(AppRadius.lg),
padding: EdgeInsets.all(AppSpacing.lg),
child: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Container(
padding: EdgeInsets.all(AppSpacing.sm),
decoration: BoxDecoration(
color: colorScheme.primary.withOpacity(0.1),
borderRadius: BorderRadius.circular(AppRadius.md),
),
child: Icon(
LucideIcons.wallet,
color: colorScheme.primary,
),
),
SizedBox(width: AppSpacing.sm),
Text(
'提现 (Withdraw)',
style: GoogleFonts.spaceGrotesk(
fontSize: 16,
fontWeight: FontWeight.bold,
color: colorScheme.onSurface,
),
),
],
),
SizedBox(height: AppSpacing.xs),
Text(
'Securely transfer your assets to an external wallet address.',
style: TextStyle(
fontSize: 12,
color: colorScheme.onSurfaceVariant,
),
),
if (balance != null) ...[
SizedBox(height: AppSpacing.md),
Container(
padding: EdgeInsets.symmetric(
horizontal: AppSpacing.md,
vertical: AppSpacing.sm,
),
decoration: BoxDecoration(
color: AppColorScheme.up.withOpacity(0.1),
borderRadius: BorderRadius.circular(AppRadius.full),
border: Border.all(
color: AppColorScheme.up.withOpacity(0.2),
),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(
'可用余额: ',
style: TextStyle(
fontSize: 10,
letterSpacing: 0.1,
color: colorScheme.onSurfaceVariant,
),
),
Text(
'$balance USDT',
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
color: AppColorScheme.up,
),
),
],
),
),
],
SizedBox(height: AppSpacing.lg),
ShadForm(
key: formKey,
child: Column(
children: [
ShadInputFormField(
id: 'amount',
controller: amountController,
label: const Text('提现金额'),
placeholder: const Text('请输入提现金额(USDT)'),
keyboardType: const TextInputType.numberWithOptions(decimal: true),
validator: Validators.amount,
),
SizedBox(height: AppSpacing.xs),
Container(
padding: EdgeInsets.symmetric(
horizontal: AppSpacing.md,
vertical: AppSpacing.sm,
),
decoration: BoxDecoration(
color: Colors.orange.withOpacity(0.1),
borderRadius: BorderRadius.circular(AppRadius.md),
border: Border.all(color: Colors.orange.withOpacity(0.3)),
),
child: Row(
children: [
Icon(Icons.info_outline, size: 14, color: Colors.orange),
SizedBox(width: AppSpacing.xs),
Expanded(
child: ValueListenableBuilder<String>(
valueListenable: feeNotifier,
builder: (_, text, __) => Text(
text,
style: TextStyle(
fontSize: 11,
color: Colors.orange.shade800,
),
),
),
),
],
),
),
SizedBox(height: AppSpacing.md),
// 提现网络选择
ValueListenableBuilder<List<String>>(
valueListenable: networksNotifier,
builder: (_, networks, __) {
if (networks.isEmpty) return const SizedBox.shrink();
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'提现网络',
style: TextStyle(
fontSize: 12,
color: colorScheme.onSurfaceVariant,
),
),
const SizedBox(height: 4),
SizedBox(
width: double.infinity,
child: ValueListenableBuilder<String?>(
valueListenable: selectedNetworkNotifier,
builder: (_, selected, __) => ShadSelect<String>(
initialValue: selected ?? networks.first,
placeholder: const Text('选择提现网络'),
onChanged: (value) {
selectedNetworkNotifier.value = value;
},
selectedOptionBuilder: (context, value) => Text(value),
options: networks.map((network) => ShadOption<String>(
value: network,
child: Text(network),
)),
),
),
),
],
);
},
),
SizedBox(height: AppSpacing.md),
ShadInputFormField(
id: 'address',
controller: addressController,
label: const Text('目标地址'),
placeholder: const Text('请输入提现地址'),
validator: (v) => Validators.required(v, '提现地址'),
),
SizedBox(height: AppSpacing.md),
ShadInputFormField(
id: 'contact',
controller: contactController,
label: const Text('联系方式(可选)'),
placeholder: const Text('联系方式'),
),
],
),
),
SizedBox(height: AppSpacing.lg),
Row(
children: [
Expanded(
child: NeonButton(
text: '取消',
type: NeonButtonType.outline,
onPressed: () => Navigator.of(ctx).pop(),
height: 44,
showGlow: false,
),
),
SizedBox(width: AppSpacing.sm),
Expanded(
child: NeonButton(
text: '提交',
type: NeonButtonType.primary,
onPressed: () async {
if (formKey.currentState!.saveAndValidate()) {
Navigator.of(ctx).pop();
final response = await context.read<AssetProvider>().withdraw(
amount: amountController.text,
withdrawAddress: addressController.text,
network: selectedNetworkNotifier.value,
withdrawContact: contactController.text.isNotEmpty
? contactController.text
: null,
);
if (context.mounted) {
_showResultDialog(
context,
response.success ? '申请成功' : '申请失败',
response.success ? '请等待管理员审批' : response.message,
);
}
}
},
height: 44,
showGlow: true,
),
),
],
),
SizedBox(height: AppSpacing.md),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.verified_user,
size: 12,
color: colorScheme.onSurfaceVariant.withOpacity(0.5),
),
SizedBox(width: AppSpacing.xs),
Text(
'End-to-End Encrypted Transaction',
style: TextStyle(
fontSize: 10,
letterSpacing: 0.1,
color: colorScheme.onSurfaceVariant.withOpacity(0.5),
),
),
],
),
],
),
),
),
),
),
);
}
void _navigateToTransfer(BuildContext context) async {
final result = await Navigator.push<bool>(

View File

@@ -1,14 +1,13 @@
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:lucide_icons_flutter/lucide_icons.dart';
import '../../../../core/theme/app_spacing.dart';
import '../../../../core/theme/app_theme.dart';
/// 单行菜单项:icon-in-box + title + trailing (chevron / badge / toggle)
/// 单行菜单项:图标 + 标题 + 尾部组件 (chevron)
right)
///
/// 通用菜单行组件,[icon] 和 [iconColor] 控制左侧图标,
/// [title] 为菜单文字,[trailing] 为右侧自定义内容(默认显示 chevron
/// [onTap] 为点击回调。
/// 图标颜色 (通常是使用主题色)
class MenuRow extends StatelessWidget {
final IconData icon;
final Color iconColor;
final String title;
final Widget? trailing;
@@ -16,10 +15,10 @@ class MenuRow extends StatelessWidget {
const MenuRow({
super.key,
required this.icon,
required this.iconColor,
required this.title,
this.trailing,
super.key, required this.iconColor = required this.title,
this.onTap,
});
@@ -27,7 +26,7 @@ class MenuRow extends StatelessWidget {
Widget build(BuildContext context) {
return InkWell(
onTap: onTap,
child: Padding(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
child: Row(
children: [
@@ -50,39 +49,20 @@ class MenuRow extends StatelessWidget {
Expanded(
child: Text(
title,
style: GoogleFonts.inter(
fontSize: 14,
fontWeight: FontWeight.w500,
color: Theme.of(context).colorScheme.onSurface,
),
style: AppTextStyles.headlineMedium(context),
),
),
// Trailing
if (trailing != null)
trailing!
else
Icon(
LucideIcons.chevronRight,
size: 16,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
],
],
),
),
);
}
}
/// 菜单组内分割线
class MenuDivider extends StatelessWidget {
const MenuDivider({super.key});
@override
Widget build(BuildContext context) {
return Container(
height: 1,
color: Theme.of(context).colorScheme.outlineVariant.withOpacity(0.15),
margin: const EdgeInsets.only(left: 62),
);
}
}

View File

@@ -69,7 +69,7 @@ class _FundOrderCard extends StatelessWidget {
case 1:
return '待审批';
case 2:
return '完成';
return '出款';
case 3:
return '已驳回';
case 4:

View File

@@ -214,6 +214,10 @@ class _FundOrdersPageState extends State<FundOrdersPage> {
const SizedBox(height: AppSpacing.sm),
_buildRejectionReason(order),
],
if (order.withdrawContact != null) ...[
const SizedBox(height: AppSpacing.sm - AppSpacing.xs),
_buildDetailRow('联系方式', order.withdrawContact!, c),
],
if (order.receivableAmount != null && !order.isDeposit) ...[
const SizedBox(height: AppSpacing.sm),
_buildPayableRow(order),
@@ -222,26 +226,42 @@ class _FundOrdersPageState extends State<FundOrdersPage> {
const SizedBox(height: AppSpacing.sm + AppSpacing.xs),
_buildActions(order),
],
const SizedBox(height: AppSpacing.sm),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'创建: ${_formatTime(order.createTime)}',
style: AppTextStyles.bodySmall(context).copyWith(color: c.mutedText),
),
if (order.rejectReason != null)
Expanded(
child: Text(
'驳回: ${order.rejectReason}',
style: AppTextStyles.bodySmall(context).copyWith(color: AppColorScheme.getDownColor(_isDark)),
textAlign: TextAlign.right,
overflow: TextOverflow.ellipsis,
),
),
],
),
],
),
);
}
// ---------------------------------------------------------------------------
// Card Header - type badge + status badge
// Card Header (order type + status badge)
// ---------------------------------------------------------------------------
Widget _buildCardHeader(OrderFund order) {
final upColor = AppColorScheme.getUpColor(_isDark);
final downColor = AppColorScheme.getDownColor(_isDark);
final upBg = AppColorScheme.getUpBackgroundColor(_isDark, opacity: 0.12);
final downBg = AppColorScheme.getDownBackgroundColor(_isDark, opacity: 0.12);
final typeColor = order.isDeposit ? upColor : downColor;
final typeBg = order.isDeposit ? upBg : downBg;
final c = _colors;
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
_buildBadge(order.typeText, typeColor, typeBg),
Text(
order.isDeposit ? '充值' : '提现',
style: AppTextStyles.headlineMedium(context).copyWith(color: c.primaryText),
),
_buildStatusBadge(order),
],
);