Files
video/app/lib/screens/assets/assets_screen.dart
T
2026-10-15 20:36:59 +08:00

347 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 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import '../../core/theme/app_theme.dart';
import '../../providers/asset_provider.dart';
import '../../providers/auth_provider.dart';
import '../../services/asset_service.dart';
/// 资产数字展示:整数不带小数点,否则保留两位
String fmtAssets(double v) =>
v == v.roundToDouble() ? v.toInt().toString() : v.toStringAsFixed(2);
/// 收益资产页(《03-接口文档》第 9 章):
/// 资产余额(10 资产 = 1 元)/ 累计收益·提现 / 收益明细(筛选 + 触底分页)/ 提现入口
class AssetsScreen extends ConsumerWidget {
const AssetsScreen({super.key});
static const List<Map<String, dynamic>> _filters = [
{'label': '全部', 'value': null},
{'label': '充值返利', 'value': 1},
{'label': '观看收益', 'value': 2},
{'label': '点赞收益', 'value': 3},
{'label': '提现', 'value': 4},
{'label': '提现退回', 'value': 5},
{'label': '返利扣回', 'value': 6},
{'label': '后台调整', 'value': 7},
];
@override
Widget build(BuildContext context, WidgetRef ref) {
final isLoggedIn = ref.watch(authProvider).isLoggedIn;
if (!isLoggedIn) {
return Scaffold(
appBar: AppBar(title: const Text('收益资产')),
body: _GuestGuide(onLogin: () => context.push('/login')),
);
}
final state = ref.watch(assetProvider);
return Scaffold(
appBar: AppBar(title: const Text('收益资产')),
body: state.isLoading && state.wallet == null
? const Center(child: CircularProgressIndicator())
: state.loadFailed && state.wallet == null
? _LoadFailed(
onRetry: () => ref.read(assetProvider.notifier).load(),
)
: RefreshIndicator(
onRefresh: () => ref.read(assetProvider.notifier).load(),
child: NotificationListener<ScrollNotification>(
onNotification: (n) {
if (n.metrics.pixels >= n.metrics.maxScrollExtent - 200) {
ref.read(assetProvider.notifier).loadMore();
}
return false;
},
child: ListView(
padding: const EdgeInsets.all(16),
children: [
_BalanceCard(wallet: state.wallet),
const SizedBox(height: 12),
// 提现入口
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: () => context
.push('/assets/withdraw')
.then((_) =>
ref.read(assetProvider.notifier).load()),
style: ElevatedButton.styleFrom(
minimumSize: const Size(0, 44),
),
child: const Text('申请提现'),
),
),
if (state.wallet?.hasPendingWithdraw ?? false) ...[
const SizedBox(height: 8),
const Text(
'有一笔提现审核中,审核完成后可再次申请',
textAlign: TextAlign.center,
style: TextStyle(
color: AppColors.textWeak,
fontSize: 12,
),
),
],
const SizedBox(height: 20),
// 明细筛选
SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
children: _filters.map((f) {
final active = state.bizType == f['value'];
return Padding(
padding: const EdgeInsets.only(right: 8),
child: ChoiceChip(
label: Text(f['label'] as String),
selected: active,
onSelected: (_) => ref
.read(assetProvider.notifier)
.setBizType(f['value'] as int?),
selectedColor: AppColors.primary,
backgroundColor: AppColors.surface,
labelStyle: TextStyle(
color: active
? Colors.white
: AppColors.textSecondary,
),
),
);
}).toList(),
),
),
const SizedBox(height: 8),
if (state.records.isEmpty)
const Padding(
padding: EdgeInsets.symmetric(vertical: 40),
child: Center(
child: Text('暂无收益明细',
style:
TextStyle(color: AppColors.textWeak)),
),
)
else ...[
for (final r in state.records) _RecordTile(record: r),
if (state.hasMore)
const Padding(
padding: EdgeInsets.all(16),
child: Center(
child: SizedBox(
width: 20,
height: 20,
child:
CircularProgressIndicator(strokeWidth: 2),
),
),
),
],
],
),
),
),
);
}
}
/// 资产卡:余额 + 折合 + 累计
class _BalanceCard extends StatelessWidget {
const _BalanceCard({required this.wallet});
final AssetWallet? wallet;
@override
Widget build(BuildContext context) {
final w = wallet;
return Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
gradient: AppColors.goldGradient,
borderRadius: BorderRadius.circular(12),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'资产余额',
style: TextStyle(color: Colors.black54, fontSize: 13),
),
const SizedBox(height: 4),
Text(
fmtAssets(w?.assets ?? 0),
style: const TextStyle(
color: Colors.black,
fontSize: 32,
fontWeight: FontWeight.w800,
),
),
Text(
'约 ¥${(w?.assetsValueYuan ?? 0).toStringAsFixed(2)}(10 资产 = 1 元)',
style: const TextStyle(color: Colors.black54, fontSize: 12),
),
const SizedBox(height: 16),
Row(
children: [
Expanded(
child: _Stat(label: '累计收益', value: fmtAssets(w?.totalEarned ?? 0)),
),
Expanded(
child:
_Stat(label: '累计提现', value: fmtAssets(w?.totalWithdrawn ?? 0)),
),
Expanded(
child: _Stat(
label: '提现门槛',
value: '${w?.minWithdrawAssets ?? 100} 资产',
),
),
],
),
],
),
);
}
}
class _Stat extends StatelessWidget {
const _Stat({required this.label, required this.value});
final String label;
final String value;
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
value,
style: const TextStyle(
color: Colors.black,
fontSize: 15,
fontWeight: FontWeight.w700,
),
),
Text(
label,
style: const TextStyle(color: Colors.black54, fontSize: 11),
),
],
);
}
}
class _RecordTile extends StatelessWidget {
const _RecordTile({required this.record});
final AssetRecord record;
@override
Widget build(BuildContext context) {
final isExpense = record.isExpense;
return Padding(
padding: const EdgeInsets.symmetric(vertical: 10),
child: Row(
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(record.bizTypeLabel, style: const TextStyle(fontSize: 15)),
if (record.remark.isNotEmpty)
Text(
record.remark,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
color: AppColors.textWeak,
fontSize: 12,
),
),
Text(
record.createdAt,
style: const TextStyle(
color: AppColors.textWeak,
fontSize: 11,
),
),
],
),
),
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
'${isExpense ? '' : '+'}${fmtAssets(record.changeAmount)}',
style: TextStyle(
fontSize: 15,
fontWeight: FontWeight.w700,
color: isExpense ? Colors.white : AppColors.gold,
),
),
Text(
'余额 ${fmtAssets(record.balanceAfter)}',
style: const TextStyle(
color: AppColors.textWeak,
fontSize: 11,
),
),
],
),
],
),
);
}
}
class _GuestGuide extends StatelessWidget {
const _GuestGuide({required this.onLogin});
final VoidCallback onLogin;
@override
Widget build(BuildContext context) {
return Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(Icons.savings_outlined,
size: 64, color: AppColors.textWeak),
const SizedBox(height: 12),
const Text('登录后查看收益资产',
style: TextStyle(color: AppColors.textSecondary)),
const SizedBox(height: 16),
ElevatedButton(
onPressed: onLogin,
style: ElevatedButton.styleFrom(
minimumSize: const Size(160, 44),
),
child: const Text('去登录'),
),
],
),
);
}
}
class _LoadFailed extends StatelessWidget {
const _LoadFailed({required this.onRetry});
final VoidCallback onRetry;
@override
Widget build(BuildContext context) {
return Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Text('加载失败', style: TextStyle(color: AppColors.textSecondary)),
const SizedBox(height: 12),
TextButton(onPressed: onRetry, child: const Text('点击重试')),
],
),
);
}
}