Flutter for OpenHarmony 个人理财管理App实战 - 账户列表页面
本文介绍了理财应用中账户管理功能的实现方案。该功能通过账户列表页面展示用户所有账户和资产概览,包含资产卡片、账户列表、添加入口等核心组件。资产概览显示总资产、净资产和负债,采用主题色强调重要数据;账户列表提供详细账户信息,支持点击查看详情、长按操作和滑动删除等交互。代码实现基于Flutter框架,使用GetX进行状态管理,通过响应式设计自动更新UI。页面布局层次分明,将用户最关心的资产信息置于顶部
账户管理是理财应用的核心功能之一。用户可能有多个账户:现金、银行卡、信用卡、支付宝、微信等。本篇将实现账户列表页面,展示所有账户和资产概览,让用户对自己的财务状况一目了然。
功能设计
账户列表页面包含以下功能:
- 资产概览卡片(总资产、净资产、负债)
- 账户列表(显示每个账户的余额)
- 添加账户入口
- 点击账户查看详情
- 长按账户显示操作菜单
- 滑动删除账户
这种设计让用户既能看到整体资产状况,也能管理单个账户。资产概览放在顶部最显眼的位置,账户列表提供详细的分项信息。
完整代码实现
创建 account_list_page.dart,完整代码如下:
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import '../../core/services/account_service.dart';
import '../../core/services/storage_service.dart';
import '../../data/models/account_model.dart';
import '../../routes/app_pages.dart';
const _primaryColor = Color(0xFF2E7D32);
const _incomeColor = Color(0xFF4CAF50);
const _expenseColor = Color(0xFFE53935);
const _textSecondary = Color(0xFF757575);
class AccountListPage extends StatelessWidget {
const AccountListPage({super.key});
Widget build(BuildContext context) {
final accountService = Get.find<AccountService>();
final storage = Get.find<StorageService>();
这段代码导入了 Flutter 核心库、屏幕适配库 flutter_screenutil、状态管理库 GetX,以及项目内部的服务和模型。AccountService 负责账户数据的增删改查和资产计算,StorageService 提供货币符号等全局设置。四个颜色常量定义了页面的视觉风格:主题绿色用于强调元素,收入绿色和支出红色用于区分交易类型,次要文字灰色用于辅助信息。这些颜色在整个应用中保持一致,形成统一的视觉语言。
return Scaffold(
appBar: AppBar(
title: const Text('账户管理'),
actions: [
IconButton(
icon: const Icon(Icons.add),
onPressed: () => Get.toNamed(Routes.accountEdit),
tooltip: '添加账户',
),
],
),
body: RefreshIndicator(
onRefresh: () async {
accountService.refresh();
},
这段代码构建了页面的基础结构。Scaffold 是 Material Design 的页面容器,提供了 AppBar 和 body 两个主要区域。AppBar 显示"账户管理"标题,右侧的 IconButton 点击后跳转到账户编辑页面,不传参数表示新增模式。tooltip 属性提供长按提示,增强可访问性。RefreshIndicator 包裹整个页面内容,实现下拉刷新功能,当用户下拉时会调用 accountService.refresh() 刷新数据。这种交互方式符合移动端用户的使用习惯。
child: SingleChildScrollView(
physics: const AlwaysScrollableScrollPhysics(),
padding: EdgeInsets.all(16.w),
child: Column(
children: [
_buildAssetOverview(accountService, storage),
SizedBox(height: 16.h),
_buildAccountList(accountService, storage),
],
),
),
),
);
}
SingleChildScrollView 让整个页面内容可以垂直滚动,适应不同数量的账户。AlwaysScrollableScrollPhysics 是一个重要的设置,它确保即使内容不足一屏高度也能触发下拉刷新,提升用户体验。padding 设为 16.w 在页面四周留出边距,和其他页面保持一致。Column 将页面分为两个主要区域:顶部的资产概览卡片和下方的账户列表,中间用 SizedBox 添加 16.h 的间距。这种布局让信息层次分明,用户首先看到整体资产状况,然后可以查看具体的账户明细。
Widget _buildAssetOverview(
AccountService accountService,
StorageService storage
) {
return Card(
color: _primaryColor,
child: Padding(
padding: EdgeInsets.all(20.w),
child: Column(
children: [
Text(
'总资产',
style: TextStyle(color: Colors.white70, fontSize: 14.sp)
),
SizedBox(height: 8.h),
资产概览卡片使用主题绿色作为背景色,和白色文字形成强烈的视觉对比,让这个最重要的信息区域非常突出。Card 组件自带圆角和阴影效果,padding 设为 20.w 比普通卡片稍大,让内容有更多的呼吸空间。"总资产"标签使用半透明白色(white70),既清晰可见又不会太抢眼。Column 垂直排列标签、金额和下方的净资产/负债信息。这种设计让用户一打开页面就能立即看到自己的总资产情况,符合用户最关心的信息优先展示的原则。
Obx(() => Text(
'${storage.currency}${accountService.totalAssets.toStringAsFixed(2)}',
style: TextStyle(
color: Colors.white,
fontSize: 28.sp,
fontWeight: FontWeight.bold
)
)),
SizedBox(height: 16.h),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
_buildAssetInfo('净资产', accountService.netWorth, storage.currency),
_buildAssetInfo('负债', accountService.totalLiabilities, storage.currency),
],
),
Obx 是 GetX 的响应式组件,它会监听 accountService.totalAssets 的变化并自动重建 UI。当用户添加、删除或修改账户时,总资产会自动更新显示。28.sp 是页面上最大的字号,配合 bold 粗体,强调总资产的重要性。toStringAsFixed(2) 保留两位小数,让金额显示更规范专业。Row 组件让净资产和负债水平排列,mainAxisAlignment.spaceAround 让它们在可用空间中均匀分布。净资产 = 总资产 - 负债,对于有信用卡等负债账户的用户来说,这个数字比总资产更有参考价值。
],
),
),
);
}
Widget _buildAssetInfo(String label, double value, String currency) {
return Column(
children: [
Text(label, style: TextStyle(color: Colors.white70, fontSize: 12.sp)),
SizedBox(height: 4.h),
Obx(() => Text(
'$currency${value.toStringAsFixed(2)}',
style: TextStyle(
color: Colors.white,
fontSize: 16.sp,
fontWeight: FontWeight.w500
)
)),
],
);
}
_buildAssetInfo 方法构建单个资产信息项,采用垂直布局。标签使用小字号(12.sp)和半透明白色(white70),金额使用中等字号(16.sp)和纯白色,这种大小和颜色的对比让金额数字更加突出。fontWeight.w500 是中等粗细,比普通文字稍粗但不会太重。Obx 包裹确保当账户数据变化时,净资产和负债能够自动重新计算并更新显示。这种响应式设计是 GetX 的核心优势,让开发者无需手动管理状态更新,UI 会自动跟随数据变化。
Widget _buildAccountList(
AccountService accountService,
StorageService storage
) {
return Card(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: EdgeInsets.all(16.w),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('我的账户', style: TextStyle(fontSize: 16.sp, fontWeight: FontWeight.w600)),
账户列表卡片的头部包含标题和账户数量统计。"我的账户"使用 16.sp 字号和 w600 粗细,和其他卡片标题保持一致的样式。Row 组件让标题和账户数量左右分布,mainAxisAlignment.spaceBetween 让它们分别靠左和靠右。账户数量用灰色小字显示,Obx 包裹确保当账户数量变化时能自动更新。Divider 是一条细分隔线,height 设为 1 让它尽量细,视觉上分隔标题和列表内容。这种设计让用户一眼就能看到自己有多少个账户,同时保持界面的整洁。
Obx(() => Text(
'${accountService.allAccounts.length}个',
style: TextStyle(fontSize: 14.sp, color: _textSecondary),
)),
],
),
),
const Divider(height: 1),
Obx(() {
final accounts = accountService.allAccounts;
if (accounts.isEmpty) {
return _buildEmptyState();
}
Obx 包裹整个列表区域,监听 accountService.allAccounts 的变化。当账户列表为空时,显示空状态引导用户添加账户,这比单纯显示"暂无数据"更友好。map 方法遍历账户列表,为每个账户对象调用 _buildAccountItem 方法创建对应的列表项。toList() 将 map 返回的 Iterable 转换为 List,因为 Column 的 children 参数需要 List 类型。空状态使用大尺寸(64.sp)的浅灰色图标,配合提示文字和操作按钮,形成完整的引导流程,帮助新用户快速上手。
return Column(
children: accounts.map((account) =>
_buildAccountItem(account, accountService, storage)
).toList(),
);
}),
],
),
);
}
Widget _buildEmptyState() {
return Padding(
padding: EdgeInsets.all(32.w),
child: Column(
children: [
Icon(Icons.account_balance_wallet_outlined, size: 64.sp, color: Colors.grey[300]),
SizedBox(height: 16.h),
空状态的设计非常重要,它不仅告诉用户当前没有数据,更重要的是引导用户如何添加第一个账户。padding 设为 32.w 让空状态内容有足够的视觉空间,不会显得拥挤。Icon 使用 account_balance_wallet_outlined 图标,size 设为 64.sp 足够大能引起注意,浅灰色让它看起来柔和不刺眼。两行提示文字分别说明当前状态和操作方法,字号和颜色有所区别形成层次。ElevatedButton.icon 提供明确的操作入口,点击后跳转到账户编辑页面。这种友好的空状态设计能有效降低用户的学习成本,提升首次使用体验。
Text('还没有添加账户', style: TextStyle(fontSize: 16.sp, color: _textSecondary)),
SizedBox(height: 8.h),
Text(
'点击右上角 + 添加您的第一个账户',
style: TextStyle(fontSize: 14.sp, color: _textSecondary),
),
SizedBox(height: 24.h),
ElevatedButton.icon(
onPressed: () => Get.toNamed(Routes.accountEdit),
icon: const Icon(Icons.add),
label: const Text('添加账户'),
),
],
),
);
}
空状态提供了清晰的操作指引和便捷的操作按钮。第一行文字"还没有添加账户"说明当前状态,第二行文字"点击右上角 + 添加您的第一个账户"告诉用户如何操作。ElevatedButton.icon 同时显示图标和文字,提供了第二个添加入口,让用户有多种方式完成操作。这种设计考虑到了不同用户的使用习惯,有的用户习惯点击顶部按钮,有的用户更倾向于点击页面中的大按钮。24.h 的间距让按钮和上方文字保持适当距离,视觉上更舒适。整个空状态的设计遵循了"引导而非阻碍"的原则。
Widget _buildAccountItem(
AccountModel account,
AccountService accountService,
StorageService storage
) {
return Dismissible(
key: Key(account.id),
direction: DismissDirection.endToStart,
background: Container(
alignment: Alignment.centerRight,
padding: EdgeInsets.only(right: 20.w),
color: _expenseColor,
child: const Icon(Icons.delete, color: Colors.white),
),
Dismissible 组件实现了滑动删除功能,这是移动端常见的交互方式。key 必须是唯一的,这里使用账户 id 确保每个列表项都有唯一标识。direction 设为 endToStart 表示只允许从右向左滑动,这是 iOS 和 Android 用户都熟悉的删除手势。background 定义了滑动时显示的背景,红色背景配合白色删除图标,清晰地提示用户这是删除操作。alignment.centerRight 让图标显示在右侧,padding 让图标不会紧贴边缘。这种视觉反馈让用户在滑动过程中就能明确知道即将执行的操作。
confirmDismiss: (_) => _confirmDelete(account),
child: ListTile(
leading: CircleAvatar(
backgroundColor: account.color.withOpacity(0.2),
child: Icon(account.icon, color: account.color, size: 20.sp),
),
title: Text(account.name),
subtitle: Text(_getAccountTypeName(account.type)),
trailing: Obx(() => Text(
'${storage.currency}${account.balance.toStringAsFixed(2)}',
style: TextStyle(
fontSize: 16.sp,
fontWeight: FontWeight.w600,
color: account.balance >= 0 ? _incomeColor : _expenseColor
)
)),
confirmDismiss 是一个重要的安全机制,它在滑动完成前弹出确认对话框,防止用户误删除。只有当用户在对话框中点击确认,返回 true 时,才会真正执行删除操作。ListTile 是 Material Design 的标准列表项组件,它自动处理了点击效果、内边距等细节。leading 位置显示账户图标,CircleAvatar 创建圆形背景,背景色是账户颜色的 20% 透明度,图标使用账户的完整颜色。title 显示账户名称,subtitle 显示账户类型。trailing 显示账户余额,正余额用绿色表示资产,负余额用红色表示负债(如信用卡欠款)。这种颜色编码让用户一眼就能区分账户的财务状况。
onTap: () => Get.toNamed(Routes.accountDetail, arguments: account),
onLongPress: () => _showAccountOptions(account, accountService),
),
);
}
String _getAccountTypeName(AccountType type) {
switch (type) {
case AccountType.cash: return '现金账户';
case AccountType.bank: return '银行卡';
case AccountType.creditCard: return '信用卡';
case AccountType.alipay: return '支付宝';
case AccountType.wechat: return '微信';
case AccountType.investment: return '投资账户';
case AccountType.other: return '其他账户';
}
}
ListTile 提供了两种交互方式:onTap 和 onLongPress。onTap 点击跳转到账户详情页面,通过 arguments 参数传递完整的账户对象,详情页可以获取并显示账户的所有信息。onLongPress 长按显示操作菜单,提供编辑、调整余额、查看交易记录、删除等更多操作选项。这种设计让常用操作(查看详情)只需一次点击,而不常用的操作(编辑、删除)需要长按,既提高了效率又防止了误操作。_getAccountTypeName 方法使用 switch 语句根据枚举值返回对应的中文名称,使用枚举而不是字符串可以避免拼写错误,也方便 IDE 提供代码补全和类型检查。
void _showAccountOptions(AccountModel account, AccountService accountService) {
Get.bottomSheet(
Container(
padding: EdgeInsets.all(16.w),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.vertical(top: Radius.circular(16.r)),
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
width: 40.w,
height: 4.h,
decoration: BoxDecoration(
color: Colors.grey[300],
borderRadius: BorderRadius.circular(2.r),
),
),
_showAccountOptions 方法使用 Get.bottomSheet 显示底部弹窗,这是移动端常见的交互模式。Container 设置白色背景和顶部圆角(16.r),BorderRadius.vertical 只设置顶部圆角,底部保持直角贴合屏幕边缘。顶部的小横条(40.w × 4.h)是拖动指示器,使用浅灰色,暗示用户可以通过下拉手势关闭弹窗。mainAxisSize.min 让弹窗高度自适应内容,不会占据整个屏幕。头部显示账户名称,使用 18.sp 字号和粗体,让用户明确知道当前操作的是哪个账户,避免误操作。这种设计既美观又实用,符合现代移动应用的交互规范。
SizedBox(height: 16.h),
Text(account.name, style: TextStyle(fontSize: 18.sp, fontWeight: FontWeight.bold)),
SizedBox(height: 16.h),
ListTile(
leading: const Icon(Icons.edit),
title: const Text('编辑账户'),
onTap: () {
Get.back();
Get.toNamed(Routes.accountEdit, arguments: account);
},
),
ListTile(
leading: const Icon(Icons.sync),
title: const Text('调整余额'),
弹窗提供了四个操作选项,每个选项都使用 ListTile 组件,保持一致的样式和交互。编辑选项点击后先调用 Get.back() 关闭弹窗,再跳转到账户编辑页面,通过 arguments 传递账户对象。这个顺序很重要,如果先跳转再关闭,会导致页面叠加。调整余额功能让用户可以手动修正账户余额,这在实际使用中很有用,比如银行卡余额和实际不符时,用户可以快速调整而不需要添加交易记录。查看交易记录跳转到交易列表页面,通过 arguments 传递 accountId 参数,列表页会根据这个参数筛选显示该账户的所有交易。这种设计让用户可以快速查看账户的详细使用情况。
onTap: () {
Get.back();
_showAdjustBalanceDialog(account, accountService);
},
),
ListTile(
leading: const Icon(Icons.history),
title: const Text('查看交易记录'),
onTap: () {
Get.back();
Get.toNamed(Routes.transactionList, arguments: {'accountId': account.id});
},
),
ListTile(
leading: Icon(Icons.delete, color: _expenseColor),
title: Text('删除账户', style: TextStyle(color: _expenseColor)),
删除选项使用红色图标和文字,在视觉上强调这是一个危险操作,提醒用户谨慎操作。点击删除后的流程是:先关闭弹窗,然后调用 _confirmDelete 显示确认对话框,等待用户确认。使用 await 关键字等待对话框返回结果,如果用户点击确认(返回 true),才调用 accountService.deleteAccount 真正删除账户,并显示成功提示。这种多层确认机制(长按 → 点击删除 → 确认对话框)有效防止了误删除,因为删除账户是不可逆的操作,一旦删除,相关的交易记录也会失去账户关联。这种设计体现了"防错"原则,在用户可能犯错的地方提供多重保护。
onTap: () async {
Get.back();
final confirmed = await _confirmDelete(account);
if (confirmed == true) {
accountService.deleteAccount(account.id);
Get.snackbar('成功', '账户已删除');
}
},
),
SizedBox(height: 16.h),
],
),
),
);
}
删除操作需要二次确认,这是处理危险操作的最佳实践。_confirmDelete 方法返回 Future<bool?>,通过 Get.dialog 显示确认对话框并等待用户选择。content 部分详细说明了删除的后果:“删除后相关的交易记录将失去账户关联”,让用户在做出决定前充分了解操作的影响。这种"知情同意"的设计原则非常重要,特别是对于不可逆的操作。取消按钮返回 false 表示不删除,确认按钮返回 true 表示确认删除。确认按钮使用红色文字,再次强调这是危险操作。Get.back(result: true/false) 在关闭对话框的同时返回结果,调用方可以根据这个结果决定是否执行删除。
Future<bool?> _confirmDelete(AccountModel account) {
return Get.dialog<bool>(
AlertDialog(
title: const Text('删除账户'),
content: Text(
'确定要删除账户"${account.name}"吗?\n\n'
'注意:删除后相关的交易记录将失去账户关联。'
),
actions: [
TextButton(
onPressed: () => Get.back(result: false),
child: const Text('取消'),
),
TextButton(
onPressed: () => Get.back(result: true),
child: Text('删除', style: TextStyle(color: _expenseColor)),
),
],
),
);
}
删除确认对话框使用 AlertDialog 组件,这是 Material Design 的标准对话框样式。content 使用多行文本,第一行说明要删除的账户名称,第二行用换行符分隔后说明删除的后果。这种分层次的信息展示让用户能够快速理解操作的影响。actions 数组包含两个按钮,取消按钮使用 TextButton 样式,点击后返回 false 表示取消操作。确认按钮也使用 TextButton 但文字颜色设为红色,点击后返回 true 表示确认删除。这个返回值会被 confirmDismiss 或长按菜单的删除选项接收,根据返回值决定是否真正执行删除。整个流程体现了"防错"和"容错"的设计原则。
void _showAdjustBalanceDialog(AccountModel account, AccountService accountService) {
final controller = TextEditingController(text: account.balance.toStringAsFixed(2));
final storage = Get.find<StorageService>();
Get.dialog(
AlertDialog(
title: const Text('调整余额'),
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('当前余额: ${storage.currency}${account.balance.toStringAsFixed(2)}'),
SizedBox(height: 16.h),
调整余额对话框让用户可以手动修正账户余额,这在实际使用中非常实用。TextEditingController 初始化为当前余额(保留两位小数),用户可以在此基础上修改。Column 的 mainAxisSize 设为 min 让对话框高度自适应内容,crossAxisAlignment 设为 start 让内容左对齐。第一行显示当前余额,让用户知道原来的值是多少,方便对比。TextField 的 keyboardType 设置为 numberWithOptions,decimal: true 支持小数输入,signed: true 支持负数输入(信用卡余额可能是负数)。prefixText 显示货币符号,OutlineInputBorder 提供带边框的输入框样式,视觉上更清晰。
TextField(
controller: controller,
keyboardType: const TextInputType.numberWithOptions(decimal: true, signed: true),
decoration: InputDecoration(
labelText: '新余额',
prefixText: storage.currency,
border: const OutlineInputBorder(),
),
),
],
),
actions: [
TextButton(onPressed: () => Get.back(), child: const Text('取消')),
ElevatedButton(
onPressed: () {
final newBalance = double.tryParse(controller.text);
确定按钮的点击处理包含了输入验证逻辑。double.tryParse 尝试将输入文本解析为数字,如果解析成功返回数字,失败返回 null。这种方式比 double.parse 更安全,因为后者在解析失败时会抛出异常。验证通过后,调用 accountService.updateBalance 更新账户余额,这个方法会使用 copyWith 创建新的账户对象,保持数据的不可变性。然后关闭对话框并显示成功提示。如果验证失败(用户输入了非数字内容),显示错误提示但不关闭对话框,让用户可以重新输入。这种即时反馈的设计让用户能够快速纠正错误,提升了用户体验。整个流程体现了"容错"原则,即使用户输入错误也能优雅地处理。
if (newBalance != null) {
accountService.updateBalance(account.id, newBalance);
Get.back();
Get.snackbar('成功', '余额已更新');
} else {
Get.snackbar('错误', '请输入有效金额');
}
},
child: const Text('确定'),
),
],
),
);
}
}
确定按钮验证输入是否为有效数字,验证通过后更新余额并关闭对话框。验证失败显示错误提示但不关闭对话框。
AccountService 实现
账户服务管理账户数据和计算资产:
class AccountService extends GetxService {
final _accounts = <AccountModel>[].obs;
List<AccountModel> get allAccounts => _accounts;
double get totalAssets => _accounts
.where((a) => a.balance > 0)
.fold(0.0, (sum, a) => sum + a.balance);
_accounts 是响应式列表,数据变化时会自动通知监听者。allAccounts 是只读属性,返回账户列表供外部使用。totalAssets 计算所有正余额账户的总和,where 筛选正余额账户,fold 累加金额。这种响应式设计让 UI 能自动更新。
负债和净资产计算:
double get totalLiabilities => _accounts
.where((a) => a.balance < 0)
.fold(0.0, (sum, a) => sum + a.balance.abs());
double get netWorth => totalAssets - totalLiabilities;
AccountModel? getAccountById(String id) =>
_accounts.firstWhereOrNull((a) => a.id == id);
totalLiabilities 计算所有负余额账户的绝对值总和,主要是信用卡等负债账户。netWorth 是净资产,等于总资产减去负债,这个数字对用户来说更有参考价值。getAccountById 根据 id 查找账户,找不到返回 null,使用 firstWhereOrNull 避免抛出异常。
账户增删改方法:
void addAccount(AccountModel account) {
_accounts.add(account);
}
void updateAccount(AccountModel account) {
final index = _accounts.indexWhere((a) => a.id == account.id);
if (index != -1) {
_accounts[index] = account;
}
}
addAccount 添加新账户到列表。updateAccount 更新已有账户,先用 indexWhere 查找索引,找到后替换整个对象。这种方式确保响应式列表能检测到变化并通知监听者更新 UI。
删除和更新余额方法:
void deleteAccount(String id) {
_accounts.removeWhere((a) => a.id == id);
}
void updateBalance(String id, double newBalance) {
final index = _accounts.indexWhere((a) => a.id == id);
if (index != -1) {
_accounts[index] = _accounts[index].copyWith(balance: newBalance);
}
}
void refresh() {
_accounts.refresh();
}
}
deleteAccount 根据 id 删除账户。updateBalance 更新指定账户的余额,使用 copyWith 创建新对象保持不可变性。refresh 方法触发列表刷新,用于下拉刷新场景。
设计要点总结
账户列表的设计考虑了以下几点:
- 资产概览放在顶部,一目了然,用户最关心的信息最先看到
- 使用颜色区分正负余额,绿色表示正余额,红色表示负余额
- 每个账户有独特的图标和颜色,方便识别
- 点击查看详情,长按显示更多选项,滑动可以删除
- 空状态引导用户添加账户,不会让用户感到困惑
- 调整余额功能方便用户手动修正数据
小结
账户列表页面让用户可以全面了解自己的资产状况。通过资产概览卡片,用户可以快速看到总资产、净资产和负债。账户列表展示每个账户的详细信息,支持多种操作方式。下一篇将实现账户详情页面,展示单个账户的完整信息和交易记录。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐


所有评论(0)