Flutter for OpenHarmony社团管理App实战:预算管理实现

社团运营过程中,财务管理是一个绑不开的话题。作为社团负责人,你肯定遇到过这样的情况:活动结束后才发现钱花超了,或者到了学期末才知道预算还剩一大半没用完。这时候,一个好用的预算管理功能就显得特别重要了。

今天我们就来实现一个预算管理页面,让社团的每一分钱都花得明明白白。这个页面主要解决三个问题:能看到社团有哪些预算项目、每个预算用了多少还剩多少一目了然、快超支的时候要有提醒。

开始写代码

首先把需要的包引进来:

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

相关推荐
wuhen_n2 小时前
JavaScript内存管理与执行上下文
前端·javascript
Hi_kenyon2 小时前
理解vue中的ref
前端·javascript·vue.js
jin1233223 小时前
基于React Native鸿蒙跨平台地址管理是许多电商、外卖、物流等应用的重要功能模块,实现了地址的添加、编辑、删除和设置默认等功能
javascript·react native·react.js·ecmascript·harmonyos
2601_949975793 小时前
Flutter for OpenHarmony艺考真题题库+帮助中心实现
flutter
2501_920931703 小时前
React Native鸿蒙跨平台医疗健康类的血压记录,包括收缩压、舒张压、心率、日期、时间、备注和状态
javascript·react native·react.js·ecmascript·harmonyos
王泰虎4 小时前
安卓开发日记,因为JCenter 关闭导致加载不了三方库应该怎么办
android
橙露4 小时前
React Hooks 深度解析:从基础使用到自定义 Hooks 的封装技巧
javascript·react.js·ecmascript
2501_920931704 小时前
React Native鸿蒙跨平台使用useState管理健康记录和过滤状态,支持多种健康数据类型(血压、体重等)并实现按类型过滤功能
javascript·react native·react.js·ecmascript·harmonyos
Ulyanov5 小时前
从静态到沉浸:打造惊艳的Web技术发展历程3D时间轴
前端·javascript·html5·gui开发