
社团运营过程中,财务管理是一个绑不开的话题。作为社团负责人,你肯定遇到过这样的情况:活动结束后才发现钱花超了,或者到了学期末才知道预算还剩一大半没用完。这时候,一个好用的预算管理功能就显得特别重要了。
今天我们就来实现一个预算管理页面,让社团的每一分钱都花得明明白白。这个页面主要解决三个问题:能看到社团有哪些预算项目、每个预算用了多少还剩多少一目了然、快超支的时候要有提醒。
开始写代码
首先把需要的包引进来:
dart
import 'package:flutter/material.dart';
Material是Flutter的基础UI库,提供了各种常用的组件和主题样式。
基本上每个Flutter页面都要引入这个包,没它啥也干不了。
dart
import 'package:provider/provider.dart';
Provider是状态管理的老朋友了,用它来获取和监听预算数据的变化。
数据变了页面自动刷新,不用手动setState,省心。
dart
import 'package:percent_indicator/percent_indicator.dart';
这个库专门用来画环形进度条,在pubspec.yaml里加上依赖就能用。
可视化预算使用率全靠它,效果还挺好看的。
dart
import '../../providers/app_provider.dart';
这是我们自己写的Provider,里面存着预算数据。
路径根据你项目结构来,别照抄。
页面基本结构
dart
class BudgetPage extends StatelessWidget {
const BudgetPage({super.key});
用StatelessWidget就够了,因为这个页面不需要自己维护什么状态。
数据都是从Provider那边拿的,Provider数据变了页面自动刷新。
dart
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('预算管理')
),
Scaffold是页面的基本骨架,AppBar放个标题就行。
不搞那些花里胡哨的,简简单单最好。
获取预算数据
dart
body: Consumer<AppProvider>(
builder: (context, provider, _) {
final budgets = provider.budgets
.where((b) => b.clubId == 'club001')
.toList();
Consumer监听Provider的变化,数据一更新页面就自动刷新。
where过滤出当前社团的预算,实际项目中clubId应该是动态获取的。
空状态处理
dart
if (budgets.isEmpty) {
return const Center(
child: Text(
'暂无预算',
style: TextStyle(
color: Colors.grey
)
)
);
}
没有预算数据的时候总得显示点什么,不能让用户对着空白页面发呆。
一行灰色的提示文字,简单但是够用了。
预算列表构建
dart
return ListView.builder(
padding: const EdgeInsets.all(16),
itemCount: budgets.length,
itemBuilder: (context, index) {
final budget = budgets[index];
ListView.builder是处理列表的标准姿势,比直接用Column性能好多了。
padding给列表加点边距,看着不那么挤。
dart
final isOverBudget = budget.usageRate > 1;
这个变量很关键,用来判断有没有超支。
usageRate大于1就说明花的钱比预算还多,得标红提醒用户。
预算卡片容器
dart
return Card(
margin: const EdgeInsets.only(
bottom: 16
),
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
每个预算项目用Card包起来,自带阴影和圆角,看着清爽。
margin只设bottom让卡片之间有间距,padding是内部留白。
预算标题区域
dart
Row(
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
budget.name,
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16
)
),
标题行左边是预算名称,用粗体突出显示。
Expanded让左边内容自动占满剩余空间,右边状态标签不会被挤掉。
预算周期信息
dart
const SizedBox(
height: 4
),
Text(
budget.period,
style: const TextStyle(
color: Colors.grey,
fontSize: 12
)
),
],
),
),
周期信息用小号灰色字体,比如显示"2024年度"这样的文字。
作为次要信息,不需要太显眼。
状态标签样式
dart
Container(
padding: const EdgeInsets.symmetric(
horizontal: 8,
vertical: 4
),
decoration: BoxDecoration(
color: budget.status == '进行中'
? Colors.green.withOpacity(0.1)
: Colors.grey.withOpacity(0.1),
borderRadius: BorderRadius.circular(4),
),
状态标签用Container自己画,可以控制背景色和圆角。
进行中用绿色,已结束用灰色,withOpacity让背景色很淡不扎眼。
状态标签文字
dart
child: Text(
budget.status,
style: TextStyle(
color: budget.status == '进行中'
? Colors.green
: Colors.grey,
fontSize: 12
)
),
),
],
),
文字颜色和背景色呼应,都是绿色或灰色系。
字号12px小巧精致,不抢主标题的风头。
环形进度条组件
dart
const SizedBox(
height: 20
),
Center(
child: CircularPercentIndicator(
radius: 60,
lineWidth: 10,
percent: budget.usageRate.clamp(
0.0,
1.0
),
这是percent_indicator库的核心组件,radius是半径,lineWidth是圆环粗细。
percent用clamp限制在0到1之间,不然超支时会报错。
进度条中心百分比
dart
center: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'${(budget.usageRate * 100).toStringAsFixed(1)}%',
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 18,
color: isOverBudget
? Colors.red
: const Color(0xFF4A90E2)
)
),
环形进度条中间显示百分比数字,超支变红色,正常是蓝色。
toStringAsFixed(1)保留一位小数,比如显示75.5%这样。
进度条说明文字
dart
const Text(
'已使用',
style: TextStyle(
fontSize: 12,
color: Colors.grey
)
),
],
),
百分比下面加个已使用的说明,让用户知道这数字啥意思。
不加的话光秃秃一个数字,新用户可能会懵。
进度条颜色配置
dart
progressColor: isOverBudget
? Colors.red
: const Color(0xFF4A90E2),
backgroundColor: Colors.grey[200]!,
),
),
进度条颜色跟着超支状态变化,超支变红正常是蓝。
背景色用浅灰色,和进度色形成对比。
金额统计行
dart
const SizedBox(
height: 20
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
_buildBudgetItem(
'总预算',
'${budget.totalAmount.toStringAsFixed(0)}',
const Color(0xFF4A90E2)
),
进度条下面是三个金额数据,spaceAround让它们均匀分布。
总预算用蓝色,是个中性的颜色。
已使用金额
dart
_buildBudgetItem(
'已使用',
'${budget.usedAmount.toStringAsFixed(0)}',
Colors.orange
),
已使用的金额用橙色,表示这是需要关注的数据。
toStringAsFixed(0)不要小数,显示整数就行。
剩余金额显示
dart
_buildBudgetItem(
'剩余',
'${budget.remainingAmount.toStringAsFixed(0)}',
budget.remainingAmount >= 0
? Colors.green
: Colors.red
),
],
),
],
),
),
);
},
);
},
),
);
}
剩余金额颜色根据正负来定,正数绿色表示有余额,负数红色表示超支。
这个小细节能让用户一眼看出预算健康状况。
金额项组件封装
dart
Widget _buildBudgetItem(
String label,
String value,
Color color
) {
return Column(
children: [
Text(
value,
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16,
color: color
)
),
把金额项抽成方法避免重复代码,上面是金额数字用彩色粗体。
三个金额项结构一样,只是颜色和内容不同。
金额标签文字
dart
const SizedBox(
height: 4
),
Text(
label,
style: const TextStyle(
color: Colors.grey,
fontSize: 12
)
),
],
);
}
}
下面是标签说明用灰色小字,结构清晰一目了然。
整个组件就这么简单,但是很实用。
关于数据模型
预算数据模型大概长这样:
dart
class Budget {
final String id;
final String clubId;
final String name;
final String period;
final double totalAmount;
final double usedAmount;
final String status;
Budget({
required this.id,
required this.clubId,
required this.name,
required this.period,
required this.totalAmount,
required this.usedAmount,
required this.status,
});
字段都挺直白的,id是唯一标识,clubId关联社团。
name是预算名称比如"活动经费",period是周期比如"2024年度"。
计算属性
dart
double get usageRate =>
totalAmount > 0
? usedAmount / totalAmount
: 0;
double get remainingAmount =>
totalAmount - usedAmount;
}
usageRate是使用率,用已使用除以总预算。
remainingAmount是剩余金额,总预算减去已使用。
Provider中的预算数据
dart
class AppProvider extends ChangeNotifier {
List<Budget> _budgets = [];
List<Budget> get budgets => _budgets;
void setBudgets(List<Budget> budgets) {
_budgets = budgets;
notifyListeners();
}
Provider里存着预算列表,通过getter暴露出去。
setBudgets更新数据后调用notifyListeners通知监听者。
添加预算方法
dart
void addBudget(Budget budget) {
_budgets.add(budget);
notifyListeners();
}
void updateBudget(Budget budget) {
final index = _budgets.indexWhere(
(b) => b.id == budget.id
);
if (index != -1) {
_budgets[index] = budget;
notifyListeners();
}
}
}
addBudget添加新预算,updateBudget更新已有预算。
都要调用notifyListeners让UI刷新。
测试数据初始化
dart
void initTestData(AppProvider provider) {
provider.setBudgets([
Budget(
id: 'budget001',
clubId: 'club001',
name: '年度活动经费',
period: '2024年度',
totalAmount: 50000,
usedAmount: 32500,
status: '进行中',
),
开发阶段先用测试数据,方便调试页面效果。
这个预算用了65%,还算健康。
更多测试数据
dart
Budget(
id: 'budget002',
clubId: 'club001',
name: '迎新活动专项',
period: '2024年秋季',
totalAmount: 8000,
usedAmount: 9200,
status: '已结束',
),
]);
}
这个预算超支了,usedAmount比totalAmount还大。
页面上会显示红色警告,提醒管理者注意。
页面路由配置
dart
MaterialPageRoute(
builder: (context) => const BudgetPage(),
)
从其他页面跳转过来就这么写。
BudgetPage是const构造函数,性能好一点。
在财务模块入口添加
dart
ListTile(
leading: const Icon(
Icons.account_balance_wallet,
color: Color(0xFF4A90E2)
),
title: const Text('预算管理'),
trailing: const Icon(Icons.chevron_right),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (_) => const BudgetPage()
),
);
},
),
一般在财务概览页面放个入口,点击跳转到预算管理。
leading放个图标,trailing放个箭头,标准的列表项样式。
关于颜色选择
dart
const Color primaryBlue = Color(0xFF4A90E2);
const Color warningOrange = Colors.orange;
const Color successGreen = Colors.green;
const Color dangerRed = Colors.red;
项目里最好把常用颜色定义成常量,方便统一管理。
这几个颜色在财务模块用得比较多,蓝色中性、橙色警告、绿色正常、红色危险。
关于数字格式化
dart
String formatMoney(double amount) {
if (amount >= 10000) {
return '${(amount / 10000).toStringAsFixed(1)}万';
}
return '${amount.toStringAsFixed(0)}';
}
金额大的时候可以显示成"万"为单位,看着更清爽。
这个方法可以抽到工具类里复用。
关于进度条动画
dart
CircularPercentIndicator(
animation: true,
animationDuration: 1000,
// 其他参数...
)
percent_indicator支持动画效果,页面加载时进度条会有个填充动画。
animationDuration控制动画时长,1000毫秒就是1秒。
小结
这个预算管理页面虽然代码不算多,但该有的功能都有了。环形进度条让预算使用情况一目了然,颜色变化提醒超支风险,三个金额数据满足日常查看需求。
代码里有几个小技巧值得记住:clamp防止数值越界、withOpacity做浅色背景、条件表达式控制颜色变化。这些都是日常开发中经常用到的套路,掌握了写起来就顺手多了。
实际项目中你可能还需要加上预算编辑、新增预算这些功能,但那是另一个页面的事了。这个列表页面专注于展示,做好展示这一件事就够了。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net