Python实现的大学生生活费记账与数据分析工具

完整代码链接:https://pan.quark.cn/s/b1beb34865c9

一、项目背景与意义

1.1 项目背景

大学生群体普遍面临生活费管理的困惑:每月的生活费花在哪里了?食堂、外卖、交通、购物、娱乐……每一笔看似不大的开支,月末汇总时却常常超支。传统的记账方式(手写记录或手机备忘录)存在记录不便、统计困难、缺乏分析等问题,难以帮助大学生真正掌握自己的消费状况。

随着Web技术的普及,基于浏览器的在线记账工具成为一种理想的解决方案。本项目立足于大学生实际需求,设计并实现一套功能完整的记账与数据分析工具,帮助大学生轻松记录日常收支、直观了解消费结构、合理规划生活预算。

1.2 项目意义

  • 实用价值:为大学生提供简单易用的记账工具,培养良好的理财习惯
  • 数据分析能力:通过可视化图表呈现消费数据,帮助用户发现消费模式和优化空间
  • 技术综合实践:涉及Flask Web开发、SQLAlchemy ORM、ECharts可视化、RESTful API等多项技术
  • 毕设参考价值:项目结构完整、功能链闭环,覆盖数据采集(录入)、存储、处理、分析、展示全流程

实际演示界面

二、系统需求分析

2.1 功能需求

(1)用户管理

  • 用户注册与登录
  • 密码加密存储

(2)账单记录

  • 记一笔:选择类型(支出/收入)、分类、输入金额和备注
  • 账单列表:分页展示,按类型、分类、月份筛选
  • 编辑和删除账单

(3)分类管理

  • 支出分类:餐饮、交通、购物、娱乐等
  • 收入分类:生活费、兼职、红包等
  • 支持自定义添加和删除分类

(4)预算管理

  • 设置每月生活费总额
  • 按支出分类设置预算额度
  • 预算执行进度展示

(5)数据分析

  • 每日支出趋势图
  • 支出分类占比环形图
  • 月度支出趋势折线图
  • 预算执行统计
  • 收支结余概览

2.2 非功能需求

  • 界面美观:绿色渐变主题,响应式布局
  • 操作流畅:表单即时验证,AJAX动态加载分类
  • 数据安全:密码哈希存储,登录会话管理
  • 轻量易部署:SQLite数据库,无需额外配置

三、系统设计

3.1 系统架构

采用B/S三层架构:

  • 表现层:Bootstrap + ECharts,Jinja2模板引擎
  • 业务逻辑层:Flask路由分发,WTForms表单验证
  • 数据访问层:SQLAlchemy ORM,SQLite数据库

3.2 功能模块

模块 功能
用户模块 注册、登录、退出、会话管理
仪表盘 收支概览、每日趋势图、分类占比图、最近记录
账单管理 增删改查、按类型/分类/月份筛选
分类管理 支出/收入分类维护
预算管理 月总预算、分类预算设置与进度跟踪
数据分析 日趋势、分类占比、月趋势、预算执行

3.3 数据库设计

系统使用SQLite,共4张核心表。

用户表(users)

字段 类型 说明
id Integer 主键
username String(64) 用户名,唯一
password_hash String(256) 密码哈希
monthly_budget Float 月生活费预算
created_at DateTime 创建时间

分类表(categories)

字段 类型 说明
id Integer 主键
name String(50) 分类名称
icon String(20) FontAwesome图标
type String(10) income/expense
user_id Integer 外键→users

账单表(transactions)

字段 类型 说明
id Integer 主键
user_id Integer 外键→users
category_id Integer 外键→categories
amount Float 金额
type String(10) income/expense
note String(200) 备注
date Date 日期
created_at DateTime 创建时间

预算表(budgets)

字段 类型 说明
id Integer 主键
user_id Integer 外键→users
category_id Integer 外键→categories
month String(7) 月份,格式2024-01
amount Float 预算金额

3.4 实体关系(ER图)

  • 用户与账单:一对多
  • 用户与分类:一对多
  • 用户与预算:一对多
  • 分类与账单:一对多
  • 分类与预算:一对多

四、系统实现

4.1 项目结构

Python实现的大学生生活费记账与数据分析工具/
├── app.py              # 主应用(路由+启动)
├── config.py           # 配置
├── models.py           # 数据模型
├── forms.py            # WTForms表单
├── requirements.txt    # 依赖
├── static/css/style.css
├── static/js/main.js
└── templates/
    ├── base.html       # 基础模板
    ├── login.html      # 登录
    ├── register.html   # 注册
    ├── dashboard.html  # 仪表盘
    ├── transactions/   # 账单
    ├── categories/     # 分类
    ├── budget/         # 预算
    └── analysis.html   # 分析

4.2 技术选型

技术 版本 用途
Python 3.8+ 开发语言
Flask 2.3.3 Web框架
Flask-SQLAlchemy 3.1.1 ORM
Flask-Login 0.6.3 登录管理
Flask-WTF 1.2.1 表单+CSRF
Bootstrap 5.3.2 UI框架
ECharts 5.4.3 图表可视化

4.3 核心代码实现

4.3.1 数据模型(models.py)
from flask_sqlalchemy import SQLAlchemy
from flask_login import UserMixin
from werkzeug.security import generate_password_hash, check_password_hash
from datetime import datetime

db = SQLAlchemy()


class User(UserMixin, db.Model):
    __tablename__ = 'users'
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(64), unique=True, nullable=False, index=True)
    password_hash = db.Column(db.String(256), nullable=False)
    monthly_budget = db.Column(db.Float, default=2000.0)
    created_at = db.Column(db.DateTime, default=datetime.utcnow)
    transactions = db.relationship('Transaction', backref='user', lazy='dynamic')
    categories = db.relationship('Category', backref='user', lazy='dynamic')
    budgets = db.relationship('Budget', backref='user', lazy='dynamic')

    def set_password(self, password):
        self.password_hash = generate_password_hash(password)

    def check_password(self, password):
        return check_password_hash(self.password_hash, password)


class Category(db.Model):
    __tablename__ = 'categories'
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(50), nullable=False)
    icon = db.Column(db.String(20), default='tag')
    type = db.Column(db.String(10), nullable=False)  # income / expense
    user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
    transactions = db.relationship('Transaction', backref='category', lazy='dynamic')
    budgets = db.relationship('Budget', backref='category', lazy='dynamic')


class Transaction(db.Model):
    __tablename__ = 'transactions'
    id = db.Column(db.Integer, primary_key=True)
    user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
    category_id = db.Column(db.Integer, db.ForeignKey('categories.id'), nullable=False)
    amount = db.Column(db.Float, nullable=False)
    type = db.Column(db.String(10), nullable=False)
    note = db.Column(db.String(200))
    date = db.Column(db.Date, nullable=False)
    created_at = db.Column(db.DateTime, default=datetime.utcnow)


class Budget(db.Model):
    __tablename__ = 'budgets'
    id = db.Column(db.Integer, primary_key=True)
    user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
    category_id = db.Column(db.Integer, db.ForeignKey('categories.id'), nullable=False)
    month = db.Column(db.String(7), nullable=False)
    amount = db.Column(db.Float, nullable=False)

关键设计要点:

  • UserMixin 提供了Flask-Login所需的用户会话方法
  • backref 建立了模型间的双向关系,如 Transaction.user 可获取账单对应的用户对象
  • lazy='dynamic' 表示延迟加载,只有在调用 .all() 或 .count() 时才执行查询
4.3.2 仪表盘聚合查询(app.py)

仪表盘是系统的核心页面,需要在一次请求中完成多项统计查询:

@app.route('/')
@login_required
def dashboard():
    today = date.today()
    first_day = today.replace(day=1)
    if today.month == 12:
        next_month = today.replace(year=today.year + 1, month=1)
    else:
        next_month = today.replace(month=today.month + 1)

    # 本月支出
    month_expense = db.session.query(func.sum(Transaction.amount)).filter(
        Transaction.user_id == current_user.id,
        Transaction.type == 'expense',
        Transaction.date >= first_day,
        Transaction.date < next_month
    ).scalar() or 0

    # 本月每日支出趋势
    monthly_data = db.session.query(
        func.strftime('%Y-%m-%d', Transaction.date).label('day'),
        func.sum(Transaction.amount).label('total')
    ).filter(
        Transaction.user_id == current_user.id,
        Transaction.type == 'expense',
        Transaction.date >= first_day,
        Transaction.date < next_month
    ).group_by('day').order_by('day').all()

    # 本月支出分类排名
    top_categories = db.session.query(
        Category.name, Category.icon,
        func.sum(Transaction.amount).label('total')
    ).join(Transaction, Category.id == Transaction.category_id
    ).filter(
        Transaction.user_id == current_user.id,
        Transaction.type == 'expense',
        Transaction.date >= first_day,
        Transaction.date < next_month
    ).group_by(Category.id).order_by(func.sum(Transaction.amount).desc()).all()
    ...

这里使用了SQLAlchemy的聚合函数:

  • func.sum() 计算总和
  • func.count() 计数
  • func.avg() 求平均值
  • func.strftime() SQLite日期格式化
  • .scalar() 返回单个值
  • .group_by() 分组统计
4.3.3 数据分析页面(analysis.html中的ECharts图表)

以每日支出柱状图为例:

echarts.init(document.getElementById('dailyChart')).setOption({
    tooltip: { trigger: 'axis' },
    xAxis: { type: 'category', data: [...] },
    yAxis: { type: 'value', axisLabel: { formatter: '¥{value}' } },
    series: [{
        type: 'bar',
        data: [...],
        itemStyle: {
            color: {
                type: 'linear', x: 0, y: 0, x2: 0, y2: 1,
                colorStops: [
                    { offset: 0, color: '#f093fb' },
                    { offset: 1, color: '#f5576c' }
                ]
            }
        },
        label: { show: true, position: 'top' }
    }]
});

ECharts配置项说明:

  • tooltip 鼠标悬停提示框
  • xAxis/yAxis 坐标轴配置,axisLabel.formatter 格式化标签前缀
  • series 数据系列,itemStyle.color 设置渐变色
  • label 数据标签,显示在柱状图上方
4.3.4 动态加载分类(AJAX)

在添加账单时,选择类型后自动加载对应的分类选项,提升用户体验:

@app.route('/api/categories/<typ>')
@login_required
def api_categories(typ):
    cats = Category.query.filter_by(user_id=current_user.id, type=typ).all()
    return jsonify([{'id': c.id, 'name': c.name, 'icon': c.icon} for c in cats])
function loadCategories() {
    var type = document.querySelector('select[name="type"]').value;
    fetch('/api/categories/' + type)
        .then(function(r) { return r.json(); })
        .then(function(data) {
            var sel = document.querySelector('select[name="category_id"]');
            sel.innerHTML = '';
            data.forEach(function(c) {
                var opt = document.createElement('option');
                opt.value = c.id;
                opt.text = c.name;
                sel.appendChild(opt);
            });
        });
}

前端通过 fetch 调用后端的 RESTful 接口,获取JSON数据后动态更新下拉选择框,避免了页面刷新。

4.3.5 自动初始化默认分类

用户注册时自动创建常用的支出和收入分类,降低初次使用门槛:

def init_default_categories(user_id):
    defaults = [
        ('餐饮', 'utensils', 'expense'), ('交通', 'car', 'expense'),
        ('购物', 'shopping-bag', 'expense'), ('娱乐', 'gamepad', 'expense'),
        ('学习', 'book', 'expense'), ('医疗', 'heartbeat', 'expense'),
        ('居住', 'home', 'expense'), ('服饰', 'tshirt', 'expense'),
        ('通讯', 'phone', 'expense'), ('其他支出', 'tag', 'expense'),
        ('生活费', 'dollar-sign', 'income'), ('兼职', 'briefcase', 'income'),
        ('红包', 'gift', 'income'), ('其他收入', 'tag', 'income'),
    ]
    for name, icon, typ in defaults:
        if not Category.query.filter_by(user_id=user_id, name=name, type=typ).first():
            db.session.add(Category(name=name, icon=icon, type=typ, user_id=user_id))
    db.session.commit()
4.3.6 注册路由
@app.route('/register', methods=['GET', 'POST'])
def register():
    form = RegisterForm()
    if form.validate_on_submit():
        if User.query.filter_by(username=form.username.data).first():
            flash('用户名已存在', 'danger')
            return render_template('register.html', form=form)
        user = User(username=form.username.data)
        user.set_password(form.password.data)
        db.session.add(user)
        db.session.commit()
        init_default_categories(user.id)  # 初始化默认分类
        flash('注册成功,请登录', 'success')
        return redirect(url_for('login'))
    return render_template('register.html', form=form)

五、系统功能展示

5.1 注册登录页

绿色渐变主题,居中卡片布局。注册时带密码确认验证,登录后自动跳转仪表盘。新用户注册即自动创建14个默认分类,无需手动配置。

5.2 首页仪表盘

  • 顶部概览卡片:本月支出、本月收入、本周支出、今日支出
  • 每日支出趋势折线图:展示本月每日消费变化,面积填充增强视觉效果
  • 支出分类占比环形图:各分类消费占比一目了然
  • 预算概览:进度条展示预算使用情况,超80%变黄色,超100%变红色
  • 最近记录:最近10条账单快速预览

5.3 账单管理

账单列表支持按类型(支出/收入)、分类、月份三个维度筛选。金额用红绿颜色区分支出和收入,表格含编辑和删除操作,分页展示。

5.4 记一笔

选择类型后通过AJAX动态加载对应分类,无需手动切换。默认日期为当天,支持填写备注。

5.5 分类管理

分支出和收入两栏展示,支持自定义添加分类(带FontAwesome图标选择器和类型选择),删除时检查是否已被账单引用。

5.6 预算管理

支持两类预算设置:

  • 月总预算:在用户资料中设置每月生活费总额
  • 分类预算:针对每个支出分类设置当月预算额度

5.7 数据分析

数据分析页面按月选择维度,展示:

  • 关键指标:总支出、总收入、日均支出、结余
  • 每日支出柱状图:渐变粉色柱状,标注金额
  • 分类占比环形图:带百分比标签
  • 月度趋势折线图:近12个月支出走势
  • 预算执行表:进度条展示各分类预算完成情况

六、系统部署

6.1 环境要求

Python 3.8及以上版本,推荐使用虚拟环境。

6.2 安装依赖

在项目根目录执行:

pip install -r requirements.txt

依赖清单:

Flask==2.3.3
Flask-SQLAlchemy==3.1.1
Flask-Login==0.6.3
Flask-WTF==1.2.1
WTForms==3.1.1
Werkzeug==2.3.7

6.3 启动系统

python app.py

访问 http://127.0.0.1:5000,注册账号后即可开始使用。

七、总结与展望

7.1 项目总结

维度 成果
功能完整性 覆盖记账全流程:记录→分类→预算→分析
技术栈 Flask + SQLAlchemy + ECharts + Bootstrap
用户体验 绿色渐变UI、响应式布局、AJAX动态加载
数据安全 Werkzeug密码加密、Flask-Login会话保护

7.2 不足与改进方向

  1. 数据持久化:SQLite适合单用户,多用户场景可迁移至MySQL
  2. 批量操作:可增加Excel导入导出功能,方便数据迁移
  3. 多端适配:可开发微信小程序版本,满足移动端记账需求
  4. 智能分析:引入机器学习算法,基于历史数据预测未来消费趋势
  5. 账单提醒:在预算超支时发送通知或邮件提醒

7.3 附录:完整代码清单

# config.py
import os
basedir = os.path.abspath(os.path.dirname(__file__))

class Config:
    SECRET_KEY = os.environ.get('SECRET_KEY') or 'expense-tracker-secret-2024'
    SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') or \
        'sqlite:///' + os.path.join(basedir, 'expenses.db')
    SQLALCHEMY_TRACK_MODIFICATIONS = False
    WTF_CSRF_ENABLED = True
# forms.py
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, SelectField, FloatField, DateField, SubmitField
from wtforms.validators import DataRequired, Length, Optional, NumberRange, EqualTo


class LoginForm(FlaskForm):
    username = StringField('用户名', validators=[DataRequired(), Length(1, 64)])
    password = PasswordField('密码', validators=[DataRequired()])
    submit = SubmitField('登录')


class RegisterForm(FlaskForm):
    username = StringField('用户名', validators=[DataRequired(), Length(2, 64)])
    password = PasswordField('密码', validators=[DataRequired(), Length(6, 128)])
    confirm = PasswordField('确认密码', validators=[DataRequired(), EqualTo('password', message='两次密码不一致')])
    submit = SubmitField('注册')


class TransactionForm(FlaskForm):
    type = SelectField('类型', choices=[('expense', '支出'), ('income', '收入')])
    category_id = SelectField('分类', coerce=int)
    amount = FloatField('金额', validators=[DataRequired(), NumberRange(0.01, 999999)])
    date = DateField('日期', format='%Y-%m-%d')
    note = StringField('备注', validators=[Optional(), Length(0, 200)])
    submit = SubmitField('保存')


class CategoryForm(FlaskForm):
    name = StringField('分类名称', validators=[DataRequired(), Length(1, 50)])
    type = SelectField('类型', choices=[('expense', '支出'), ('income', '收入')])
    icon = SelectField('图标', choices=[
        ('utensils', '餐饮'), ('car', '交通'), ('shopping-bag', '购物'),
        ('gamepad', '娱乐'), ('book', '学习'), ('heartbeat', '医疗'),
        ('home', '居住'), ('tshirt', '服饰'), ('gift', '礼物'),
        ('phone', '通讯'), ('dollar-sign', '收入'), ('briefcase', '兼职'),
        ('piggy-bank', '储蓄'), ('coffee', '饮品'), ('film', '影视'),
        ('tag', '其他')
    ])
    submit = SubmitField('保存')


class BudgetForm(FlaskForm):
    category_id = SelectField('支出分类', coerce=int)
    amount = FloatField('预算金额', validators=[DataRequired(), NumberRange(0.01, 999999)])
    submit = SubmitField('设置预算')


class ProfileForm(FlaskForm):
    monthly_budget = FloatField('月总预算', validators=[DataRequired(), NumberRange(0, 999999)])
    submit = SubmitField('保存设置')

结语

本系统从大学生实际需求出发,设计并实现了一套完整的生活费记账与数据分析工具。通过Flask框架搭建后端,结合Bootstrap和ECharts构建前端界面,实现了从数据录入、存储、查询到统计分析的全流程闭环。项目代码结构清晰,注释完整,既可作为日常记账工具使用,也为计算机相关专业的毕业设计提供了可参考的完整案例。

更多推荐