1. 项目概述
在剧本杀组队App中,钱包功能是用户资金管理的核心模块。作为一名Flutter开发者,我最近为OpenHarmony平台实现了一套完整的钱包解决方案。这个功能模块不仅需要处理用户的充值、消费和提现操作,还要确保交易记录的完整性和实时性。
整个钱包模块采用MVVM架构设计,使用GetX作为状态管理方案。数据层定义了交易记录和充值选项的模型,业务逻辑层处理资金流转和状态变更,UI层则负责展示余额信息和交易历史。这种分层设计使得代码结构清晰,各模块职责明确,便于后期维护和扩展。
提示:在实际开发中,金融类功能需要特别注意安全性。虽然本文示例使用了模拟数据,但生产环境必须对接正规支付渠道,并实现完整的安全校验机制。
2. 核心数据模型设计
2.1 交易记录模型
交易记录模型是整个钱包系统的基础数据结构,我设计了如下字段:
dart复制class TransactionRecord {
final String id; // 交易唯一标识
final String type; // 交易类型:充值/消费/提现
final double amount; // 交易金额(正数表示收入,负数表示支出)
final String description; // 交易描述
final DateTime time; // 交易时间
final TransactionStatus status; // 交易状态
// 构造函数...
}
enum TransactionStatus {
success, // 交易成功
pending, // 处理中
failed // 交易失败
}
这个模型设计考虑了以下几个关键点:
- 使用独立ID而非自增ID,便于分布式系统处理
- 金额字段使用double类型,但实际存储时应转为分单位(如1元存为100)避免浮点精度问题
- 时间字段精确到毫秒,用于排序和查询
- 状态枚举覆盖了交易全生命周期
2.2 充值选项模型
针对充值功能,我设计了专门的充值选项模型:
dart复制class RechargeOption {
final int amount; // 充值金额
final int bonus; // 赠送金额
final bool isHot; // 是否热门推荐
// 计算总到账金额
int get totalAmount => amount + bonus;
// 构造函数...
}
这里有几个设计考量:
- 提供阶梯式充值选项,刺激用户充值更高金额
- 赠送金额作为营销手段,提升用户粘性
- 标记热门选项,在UI上突出显示
- 计算属性自动返回实际到账金额
3. 业务逻辑实现
3.1 钱包控制器
使用GetX的Controller管理钱包状态:
dart复制class WalletController extends GetxController {
// 响应式状态变量
final balance = 0.0.obs; // 账户余额
final frozenAmount = 0.0.obs; // 冻结金额
final points = 0.obs; // 积分余额
final records = <TransactionRecord>[].obs; // 交易记录
final isLoading = false.obs; // 加载状态
// 充值选项配置
final rechargeOptions = [
RechargeOption(amount: 50, bonus: 0),
RechargeOption(amount: 100, bonus: 5, isHot: true),
// 更多选项...
];
// 初始化加载数据
@override
void onInit() {
loadTransactionRecords();
super.onInit();
}
}
3.1.1 充值逻辑实现
充值流程包含以下关键步骤:
dart复制Future<void> recharge(double amount) async {
// 1. 金额校验
if (amount <= 0) {
showError('请输入有效金额');
return;
}
// 2. 显示加载状态
showLoading();
try {
// 3. 模拟网络请求
await Future.delayed(2.seconds);
// 4. 计算赠送金额
final bonus = rechargeOptions
.firstWhere((o) => o.amount == amount)
?.bonus ?? 0;
// 5. 更新余额
balance.value += amount + bonus;
// 6. 添加交易记录
records.insert(0, TransactionRecord(
id: 'tx_${DateTime.now().millisecondsSinceEpoch}',
type: '充值',
amount: amount + bonus,
description: bonus > 0 ? '充值(赠¥$bonus)' : '充值',
time: DateTime.now(),
status: TransactionStatus.success
));
// 7. 显示成功提示
showSuccess('成功充值¥${amount + bonus}');
} catch (e) {
showError('充值失败: ${e.toString()}');
} finally {
dismissLoading();
}
}
注意:实际项目中应该:
- 对接支付平台API(如微信/支付宝)
- 实现支付结果回调验证
- 记录详细的支付日志
- 考虑网络异常等边界情况
3.1.2 提现逻辑实现
提现流程比充值更复杂,需要更多校验:
dart复制Future<void> withdraw(double amount) async {
// 1. 多重校验
if (amount <= 0) {
showError('金额必须大于零');
return;
}
if (amount < 10) {
showError('最低提现10元');
return;
}
if (amount > availableBalance) {
showError('超出可用余额');
return;
}
// 2. 显示加载
showLoading();
try {
// 3. 模拟审核流程
await Future.delayed(2.seconds);
// 4. 扣减余额
balance.value -= amount;
// 5. 添加待审核记录
records.insert(0, TransactionRecord(
id: 'tx_${DateTime.now().millisecondsSinceEpoch}',
type: '提现',
amount: -amount,
description: '提现到微信',
time: DateTime.now(),
status: TransactionStatus.pending
));
// 6. 提示审核中
showInfo('提现申请已提交,预计1-3个工作日到账');
} catch (e) {
showError('提现失败: ${e.toString()}');
} finally {
dismissLoading();
}
}
3.2 状态管理技巧
在GetX控制器中,我使用了几个实用技巧:
- 计算属性:
dart复制double get availableBalance => balance.value - frozenAmount.value;
- 金额格式化:
dart复制String formatAmount(double amount) {
return amount >= 0
? '+¥${amount.toStringAsFixed(2)}'
: '-¥${(-amount).toStringAsFixed(2)}';
}
- 记录排序:
dart复制void sortRecords() {
records.sort((a, b) => b.time.compareTo(a.time));
}
4. UI界面实现
4.1 钱包主页结构
钱包页面采用经典的三段式布局:
dart复制Scaffold(
appBar: AppBar(title: Text('我的钱包')),
body: RefreshIndicator(
onRefresh: controller.loadRecords,
child: SingleChildScrollView(
child: Column(
children: [
_buildBalanceCard(), // 余额卡片
_buildQuickActions(), // 快捷操作区
_buildRecords(), // 交易记录
],
),
),
),
)
4.1.1 余额卡片设计
余额卡片使用渐变背景提升视觉冲击力:
dart复制Container(
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [Colors.purple[700]!, Colors.purple[500]!],
),
borderRadius: BorderRadius.vertical(bottom: Radius.circular(24))
),
child: Column(
children: [
Text('账户余额', style: TextStyle(color: Colors.white70)),
Obx(() => Text(
'¥${controller.balance.value.toStringAsFixed(2)}',
style: TextStyle(fontSize: 40, color: Colors.white),
)),
Obx(() => Text(
'可用¥${controller.availableBalance.toStringAsFixed(2)}',
style: TextStyle(color: Colors.white70),
)),
],
),
)
4.1.2 快捷操作区
使用图标按钮布局常见操作:
dart复制Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
_buildActionButton(Icons.add, '充值', onRecharge),
_buildActionButton(Icons.remove, '提现', onWithdraw),
// 更多按钮...
],
)
4.2 交易记录列表
交易记录使用ListView.separated实现:
dart复制Obx(() {
if (controller.isLoading.value) {
return CircularProgressIndicator();
}
return ListView.separated(
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemCount: controller.records.length,
separatorBuilder: (_, __) => Divider(height: 1),
itemBuilder: (_, index) => _buildRecordItem(controller.records[index]),
);
})
记录项设计要点:
- 使用颜色区分收支(绿色/红色)
- 显示完整交易时间和描述
- 状态图标直观展示交易结果
dart复制Row(
children: [
Icon(
record.amount >= 0 ? Icons.arrow_upward : Icons.arrow_downward,
color: record.amount >= 0 ? Colors.green : Colors.red,
),
SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(record.description),
Text(formatDate(record.time)),
],
),
),
Text(
controller.formatAmount(record.amount),
style: TextStyle(
color: record.amount >= 0 ? Colors.green : Colors.red,
),
),
],
)
5. 弹窗交互设计
5.1 充值弹窗
使用BottomSheet实现充值选项:
dart复制void showRechargeSheet() {
Get.bottomSheet(
Container(
padding: EdgeInsets.all(20),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text('选择充值金额', style: TextStyle(fontSize: 18)),
Wrap(
spacing: 12,
runSpacing: 12,
children: controller.rechargeOptions.map((option) {
return GestureDetector(
onTap: () => controller.selectAmount(option.amount),
child: Container(
padding: EdgeInsets.all(16),
decoration: BoxDecoration(
border: Border.all(
color: controller.selectedAmount == option.amount
? Colors.purple
: Colors.grey,
),
borderRadius: BorderRadius.circular(8),
),
child: Column(
children: [
Text('¥${option.amount}'),
if (option.bonus > 0)
Text('送¥${option.bonus}', style: TextStyle(color: Colors.orange)),
],
),
),
);
}).toList(),
),
ElevatedButton(
onPressed: controller.confirmRecharge,
child: Text('确认充值'),
),
],
),
),
);
}
5.2 提现弹窗
提现弹窗需要输入金额:
dart复制void showWithdrawSheet() {
final amountController = TextEditingController();
Get.bottomSheet(
Container(
padding: EdgeInsets.all(20),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text('输入提现金额', style: TextStyle(fontSize: 18)),
TextField(
controller: amountController,
keyboardType: TextInputType.number,
decoration: InputDecoration(
prefixText: '¥',
hintText: '最低10元',
),
),
Row(
children: [
Text('可用¥${controller.availableBalance}'),
TextButton(
onPressed: () {
amountController.text = controller.availableBalance.toString();
},
child: Text('全部提现'),
),
],
),
ElevatedButton(
onPressed: () {
final amount = double.tryParse(amountController.text) ?? 0;
controller.withdraw(amount);
Get.back();
},
child: Text('提交申请'),
),
],
),
),
);
}
6. 性能优化与调试
6.1 列表性能优化
交易记录列表使用这些优化技巧:
- 设置
shrinkWrap: true避免无限高度 - 使用
NeverScrollableScrollPhysics禁止嵌套滚动 - 为列表项添加
const构造函数 - 实现
itemExtent提高渲染效率
6.2 常见问题排查
-
金额显示异常:
- 检查是否统一使用分单位存储
- 验证toStringAsFixed(2)格式化
- 确保计算时类型一致
-
状态不更新:
- 确认变量使用.obs声明
- 检查是否在Obx/GetX中包裹
- 验证update()是否被调用
-
弹窗关闭异常:
- 确保每个Get.back()都有对应Get.to/dialog
- 检查屏障是否可点击
- 验证上下文是否正确
7. 安全注意事项
-
敏感操作验证:
- 提现必须二次确认
- 关键操作需要密码/生物识别验证
-
数据加密:
- 敏感信息加密存储
- 使用HTTPS传输数据
- 避免日志记录敏感信息
-
防重复提交:
- 按钮添加防抖处理
- 交易添加唯一ID
- 后端校验重复请求
-
金额校验:
- 前端校验格式和范围
- 后端校验业务逻辑
- 重要操作添加审计日志
8. 扩展功能建议
- 资金密码:关键操作需要额外验证
- 账单导出:支持PDF/Excel格式导出
- 消费统计:按周/月展示消费趋势
- 充值优惠:限时活动专属优惠
- 多币种支持:国际化场景需求
- 资金冻结:争议处理机制
在实现这个钱包模块的过程中,我发现GetX的状态管理非常适合于这类金融场景。它的响应式特性让UI能实时反映资金变动,而轻量级的Controller设计又保持了代码的整洁性。对于更复杂的交易场景,可以考虑结合GetX的Middleware实现操作日志和回滚机制。