Flutter for OpenHarmony 剧本杀组队App实战24:钱包功能实现
钱包功能实现包括数据模型、控制器和UI三部分。数据模型定义了交易记录(TransactionRecord)和充值选项(RechargeOption),包含金额、状态等关键属性。控制器(WalletController)使用GetX管理状态,处理充值、提现等核心逻辑,包含金额验证、状态更新和交易记录维护。UI部分采用响应式设计,通过GetX自动同步数据变化,包含余额显示、交易记录列表和操作按钮。整个
引言
钱包功能是用户资金管理的核心模块,支持充值、消费、提现等操作。本篇将实现一个功能完善的钱包页面。
数据模型定义
import 'package:flutter/material.dart';
import 'package:get/get.dart';
/// 交易记录模型
class TransactionRecord {
final String id;
final String type;
final double amount;
final String description;
final DateTime time;
final TransactionStatus status;
TransactionRecord({
required this.id,
required this.type,
required this.amount,
required this.description,
required this.time,
required this.status,
});
}
double get totalAmount => amount + bonus;
TransactionRecord类定义了交易记录的基本属性,包括唯一标识id、交易类型type、金额amount、描述description、时间time和状态status。TransactionStatus枚举定义了三种交易状态:成功、待处理和失败。RechargeOption类定义了充值选项,包含充值金额amount、赠送金额bonus和是否热门标记isHot。totalAmount计算属性返回充值后的总金额。这种数据模型设计使代码结构清晰,易于维护和扩展。
钱包控制器实现
class WalletController extends GetxController {
final balance = 168.00.obs;
final frozenAmount = 20.00.obs;
final points = 1580.obs;
final records = <TransactionRecord>[].obs;
final isLoading = false.obs;
final selectedRechargeAmount = 0.0.obs;
final rechargeOptions = <RechargeOption>[
RechargeOption(amount: 50, bonus: 0),
RechargeOption(amount: 100, bonus: 5, isHot: true),
RechargeOption(amount: 200, bonus: 15),
RechargeOption(amount: 500, bonus: 50),
RechargeOption(amount: 1000, bonus: 120),
];
void onInit() {
super.onInit();
loadTransactionRecords();
}
Future<void> loadTransactionRecords() async {
isLoading.value = true;
await Future.delayed(const Duration(milliseconds: 800));
records.value = [
TransactionRecord(
id: '1',
type: '充值',
amount: 100.00,
description: '余额充值',
time: DateTime.now().subtract(const Duration(days: 1)),
status: TransactionStatus.success,
),
TransactionRecord(
id: '2',
type: '消费',
amount: -88.00,
description: '《年轮》剧本杀组队',
time: DateTime.now().subtract(const Duration(days: 2)),
status: TransactionStatus.success,
),
];
isLoading.value = false;
}
WalletController使用GetX的响应式变量obs来管理状态。balance存储可用余额,frozenAmount存储冻结金额,points存储积分余额。records列表存储交易记录,isLoading标记加载状态。rechargeOptions定义了多个充值档位,部分档位有赠送金额。onInit方法在控制器初始化时自动加载交易记录。loadTransactionRecords方法模拟从服务器获取交易记录,实际项目中应该调用API接口。这种设计使状态管理清晰,数据与UI自动同步。
Future<void> recharge(double amount) async {
if (amount <= 0) {
Get.snackbar('提示', '请选择充值金额');
return;
}
Get.dialog(
const Center(child: CircularProgressIndicator(color: Color(0xFF6B4EFF))),
barrierDismissible: false,
);
await Future.delayed(const Duration(seconds: 2));
Get.back();
final option = rechargeOptions.firstWhereOrNull((o) => o.amount == amount);
final bonus = option?.bonus ?? 0;
balance.value += amount + bonus;
records.insert(0, TransactionRecord(
id: DateTime.now().millisecondsSinceEpoch.toString(),
type: '充值',
amount: amount + bonus,
description: bonus > 0 ? '余额充值(含赠送¥$bonus)' : '余额充值',
time: DateTime.now(),
status: TransactionStatus.success,
));
Get.snackbar('充值成功', '已到账¥${amount + bonus}');
}
recharge方法处理充值逻辑,首先验证金额是否有效,然后显示加载对话框模拟网络请求。充值成功后根据选择的金额查找对应的赠送金额,更新账户余额,并在交易记录列表头部插入新记录。如果有赠送金额,描述中会标注赠送部分。最后显示成功提示。这种设计确保了充值流程的完整性和用户反馈的及时性。
Future<void> withdraw(double amount) async {
if (amount <= 0 || amount > balance.value || amount < 10) {
Get.snackbar('提示', '请输入有效的提现金额');
return;
}
Get.dialog(
const Center(child: CircularProgressIndicator(color: Color(0xFF6B4EFF))),
barrierDismissible: false,
);
await Future.delayed(const Duration(seconds: 2));
Get.back();
balance.value -= amount;
records.insert(0, TransactionRecord(
id: DateTime.now().millisecondsSinceEpoch.toString(),
type: '提现',
amount: -amount,
description: '提现到微信',
time: DateTime.now(),
status: TransactionStatus.pending,
));
Get.snackbar('提现申请已提交', '预计1-3个工作日到账');
}
double get availableBalance => balance.value - frozenAmount.value;
String formatAmount(double amount) {
return amount >= 0 ? '+${amount.toStringAsFixed(2)}' : amount.toStringAsFixed(2);
}
}
withdraw方法处理提现逻辑,包含多重验证:金额必须大于0、不能超过余额、最低提现10元。这些验证确保提现操作的合法性。提现成功后扣减余额,添加状态为pending的交易记录。提现通常需要人工审核,所以状态不是立即成功。availableBalance计算属性返回可用余额,formatAmount方法格式化金额显示。这种设计提高了代码的健壮性和用户体验。
钱包页面UI实现
class WalletPage extends StatelessWidget {
WalletPage({super.key});
final WalletController controller = Get.put(WalletController());
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: const Color(0xFFF5F5F5),
appBar: AppBar(
title: const Text('我的钱包'),
backgroundColor: const Color(0xFF6B4EFF),
foregroundColor: Colors.white,
elevation: 0,
),
body: RefreshIndicator(
onRefresh: () => controller.loadTransactionRecords(),
color: const Color(0xFF6B4EFF),
child: SingleChildScrollView(
physics: const AlwaysScrollableScrollPhysics(),
child: Column(
children: [
_buildBalanceCard(),
const SizedBox(height: 16),
_buildQuickActions(),
const SizedBox(height: 16),
_buildRecordSection(),
],
),
),
),
);
}
WalletPage使用GetX的Get.put注入控制器。页面主体使用RefreshIndicator支持下拉刷新,SingleChildScrollView包裹内容实现滚动。AlwaysScrollableScrollPhysics确保即使内容不足也能触发下拉刷新。页面包含余额卡片、快捷操作和交易记录三个部分。这种结构清晰,用户能快速了解账户状态并进行相关操作。
Widget _buildBalanceCard() {
return Container(
width: double.infinity,
padding: const EdgeInsets.all(24),
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [Color(0xFF6B4EFF), Color(0xFF9D4EDD)],
),
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(24),
bottomRight: Radius.circular(24),
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('账户余额(元)', style: TextStyle(color: Colors.white70, fontSize: 14)),
const SizedBox(height: 12),
Obx(() => Text(
'¥ ${controller.balance.value.toStringAsFixed(2)}',
style: const TextStyle(
color: Colors.white,
fontSize: 40,
fontWeight: FontWeight.bold,
),
)),
const SizedBox(height: 8),
Obx(() => Row(
children: [
Text('可用 ¥${controller.availableBalance.toStringAsFixed(2)}',
style: const TextStyle(color: Colors.white70, fontSize: 12)),
const SizedBox(width: 16),
Text('冻结 ¥${controller.frozenAmount.value.toStringAsFixed(2)}',
style: const TextStyle(color: Colors.white70, fontSize: 12)),
],
)),
],
),
);
}
余额卡片使用渐变背景,从紫色过渡到浅紫色,底部圆角设计与页面背景形成自然过渡。余额数字使用大号加粗白色字体,是卡片的视觉焦点。下方显示可用余额和冻结金额,帮助用户了解资金状态。Obx包裹响应式变量,当数据变化时自动更新UI。这种设计既美观又实用,提升了用户体验。
Widget _buildQuickActions() {
return Container(
margin: const EdgeInsets.symmetric(horizontal: 16),
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
boxShadow: [BoxShadow(color: Colors.black.withOpacity(0.05), blurRadius: 10)],
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
_buildActionButton(Icons.add_circle_outline, '充值', () => _showRechargeSheet()),
_buildActionButton(Icons.account_balance_wallet_outlined, '提现', () => _showWithdrawSheet()),
_buildActionButton(Icons.credit_card, '银行卡', () => Get.snackbar('提示', '功能开发中')),
_buildActionButton(Icons.help_outline, '帮助', () => Get.snackbar('提示', '功能开发中')),
],
),
);
}
Widget _buildActionButton(IconData icon, String label, VoidCallback onTap) {
return GestureDetector(
onTap: onTap,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
width: 50,
height: 50,
decoration: BoxDecoration(
color: const Color(0xFF6B4EFF).withOpacity(0.1),
borderRadius: BorderRadius.circular(25),
),
child: Icon(icon, color: const Color(0xFF6B4EFF), size: 24),
),
const SizedBox(height: 8),
Text(label, style: const TextStyle(fontSize: 12, color: Colors.black87)),
],
),
);
}
快捷操作区域使用白色卡片,添加轻微阴影提升层次感。按钮水平排列,使用spaceAround均匀分布。充值和提现是最常用的操作,放在显眼位置。_buildActionButton是可复用的按钮组件,包含圆形图标背景和文字标签。图标使用浅紫色背景和紫色图标,与主题色保持一致。这种设计既美观又易于使用,提升了操作效率。
Widget _buildRecordSection() {
return Container(
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
boxShadow: [BoxShadow(color: Colors.black.withOpacity(0.05), blurRadius: 10)],
),
child: Column(
children: [
Padding(
padding: const EdgeInsets.all(16),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text('交易记录', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
GestureDetector(
onTap: () => _showAllRecords(),
child: const Text('全部 >', style: TextStyle(color: Colors.grey, fontSize: 14)),
),
],
),
),
const Divider(height: 1),
Obx(() {
if (controller.isLoading.value) {
return const Padding(
padding: EdgeInsets.all(40),
child: CircularProgressIndicator(color: Color(0xFF6B4EFF)),
);
}
if (controller.records.isEmpty) {
return const Padding(
padding: EdgeInsets.all(40),
child: Text('暂无交易记录', style: TextStyle(color: Colors.grey)),
);
}
return ListView.separated(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: controller.records.length > 5 ? 5 : controller.records.length,
separatorBuilder: (_, __) => const Divider(height: 1),
itemBuilder: (context, index) => _buildRecordItem(controller.records[index]),
);
}),
],
),
);
}
交易记录区域顶部显示标题和"全部"入口,方便用户查看完整的交易历史。使用白色卡片背景,与其他区域保持视觉一致性。使用Obx监听加载状态和记录列表变化。加载中显示进度指示器,无记录时显示空状态提示。交易记录列表最多显示5条,更多记录需要点击"全部"查看。这种设计既节省空间又提供了完整的功能。
Widget _buildRecordItem(TransactionRecord record) {
final isPositive = record.amount >= 0;
return Padding(
padding: const EdgeInsets.all(16),
child: Row(
child: Icon(
isPositive ? Icons.add_circle : Icons.remove_circle,
color: isPositive ? Colors.green : Colors.red,
size: 22,
),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(record.description, style: const TextStyle(fontWeight: FontWeight.w500, fontSize: 14)),
const SizedBox(height: 4),
Text(
'${record.time.month}月${record.time.day}日 ${record.time.hour.toString().padLeft(2, '0')}:${record.time.minute.toString().padLeft(2, '0')}',
style: TextStyle(color: Colors.grey[600], fontSize: 12),
),
],
),
),
),
}
交易记录项左侧显示类型图标,收入使用绿色,支出使用红色,直观区分交易方向。中间区域显示交易描述和时间,格式化为"X月X日 HH:mm"的中文格式。右侧显示金额,使用红绿色区分收支。这种设计清晰易读,用户能快速了解交易信息。
充值和提现弹窗
void _showRechargeSheet() {
Get.bottomSheet(
Container(
padding: const EdgeInsets.all(20),
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
),
const SizedBox(height: 24),
SizedBox(
width: double.infinity,
height: 50,
child: Obx(() => ElevatedButton(
onPressed: controller.selectedRechargeAmount.value > 0
? () {
Get.back();
controller.recharge(controller.selectedRechargeAmount.value);
}
: null,
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF6B4EFF),
disabledBackgroundColor: Colors.grey[300],
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(25)),
),
child: Text(
controller.selectedRechargeAmount.value > 0
? '确认充值 ¥${controller.selectedRechargeAmount.value.toInt()}'
: '请选择充值金额',
style: const TextStyle(color: Colors.white, fontSize: 16),
),
)),
),
),
),
}
充值弹窗使用GetX的bottomSheet方法,从底部弹出。充值金额选项使用Wrap布局,每行显示3个选项。选中状态使用紫色边框和浅紫色背景,未选中使用灰色背景。每个选项显示金额,如果有赠送金额则显示"送¥X"的橙色文字。确认按钮根据是否选择金额动态显示文字和状态。未选择时按钮禁用显示"请选择充值金额",选择后显示"确认充值 ¥X"。这种设计既美观又易于使用。
void _showWithdrawSheet() {
final amountController = TextEditingController();
Get.bottomSheet(
Container(
padding: const EdgeInsets.all(20),
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text('提现', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
IconButton(icon: const Icon(Icons.close), onPressed: () => Get.back()),
],
),
const SizedBox(height: 12),
Text(
'最低提现金额10元,预计1-3个工作日到账',
style: TextStyle(color: Colors.grey[600], fontSize: 12),
),
const SizedBox(height: 24),
SizedBox(
width: double.infinity,
height: 50,
child: ElevatedButton(
onPressed: () {
final amount = double.tryParse(amountController.text) ?? 0;
Get.back();
controller.withdraw(amount);
},
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF6B4EFF),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(25)),
),
),
],
),
),
);
}
提现弹窗结构与充值弹窗类似,使用bottomSheet从底部弹出。顶部显示可提现余额,使用浅紫色背景突出显示。金额输入框使用数字键盘,前缀显示人民币符号。右侧"全部提现"按钮方便用户快速填入全部可用余额,提升操作效率。提示文字说明最低提现金额和预计到账时间,让用户了解提现规则。确认提现按钮点击后解析输入金额,关闭弹窗并调用控制器的withdraw方法。_showAllRecords方法跳转到完整的交易记录页面,展示所有历史记录。
总结
本篇实现了一个功能完善的钱包页面,包括余额展示、充值提现、交易记录等功能。通过合理的UI设计和状态管理,提供了良好的用户体验。钱包功能是用户资金管理的核心,设计时需要注重安全性和易用性。清晰的余额展示、便捷的充值提现操作、完整的交易记录,这些都是提升用户体验的关键要素。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐
所有评论(0)