小伙伴们最近迷恋上羽毛球,组织了个小群,办了公用的运动卡用于开场,考虑不是每次活动都是全员参与,需要一个计费的系统来计算每个人需要交的费用。商讨后决定采用“预充-扣费”的方式,则需要一个系统进行计费和扣费。
模块名 | 语言 | 备注 |
---|---|---|
管理核心 | Python | 使用JSON存储信息 |
Web后端 | Python | Flask框架 |
Web前端 | HTML | Jinja框架渲染 |
该部分是整个计费系统的核心,用于管理每个用户的余额。使用一个类表示用户,需要的属性为
需要的方法有:
代码如下
# -*- coding: utf-8 -*-
import json
import os
class UserHanlde(object):
"""docstring for UserHanlde"""
def __init__(self, UserID, UserName=""):
super(UserHanlde, self).__init__()
if self.UserExsist(UserID):
self.UserInfo = self.LoadUserInfo(UserID)
else:
self.UserInfo = self.CreateNewUser(UserName, UserID)
构造函数,若该用户ID存在则读取状态,否则创建
def UserExsist(self, UserID):
return os.path.exists("./Users/%s.json" % UserID)
判断该ID的JSON文件是否存在
def CreateNewUser(self, UserName, UserID):
UserInfo = {
"name": UserName,
"id": UserID,
"num": 0,
"balance": 50
}
with open("./Users/%s.json" % UserID, "w") as jsonfile:
json.dump(UserInfo, jsonfile, ensure_ascii=False, indent=4)
return UserInfo
创建新用户,将初始余额设为50并保存JSON文件
def LoadUserInfo(self, UserID):
with open("./Users/%s.json" % UserID, "r") as jsonfile:
return json.load(jsonfile)
从JSON文件中载入用户状态
def PlayOneTime(self, Pay):
self.UserInfo["num"] += 1
self.UserInfo["balance"] = self.UserInfo["balance"] - Pay
扣费,扣除指定的费用并在将扣费次数+1
def Recharge(self, Pay):
self.UserInfo["balance"] += Pay
充值,费用加上指定值
def DeleteUser(self):
os.remove("./Users/%s.json" % self.UserInfo["id"])
删除用户,删除指定的JSON文件
def SaveInfo(self):
with open("./Users/%s.json" % self.UserInfo["id"], "w") as jsonfile:
json.dump(self.UserInfo, jsonfile, ensure_ascii=False, indent=4)
保存状态,将当前状态写入对应的JSON文件
web后端使用Python的Flask框架构造,代码如下
from flask import Flask, render_template, request
from UserHanlde import UserHanlde
import os
app = Flask(__name__)
def GetUserIDList():
return [x[:-5] for x in os.listdir("./Users") if ".json" in x]
def GetUserInfoList():
UserInfoList = dict()
for UserID in GetUserIDList():
UserData = UserHanlde(UserID)
UserInfoList[UserID] = UserData.UserInfo
return UserInfoList
常用部分的封装:
GetUserIDList()
:返回已经存在的用户ID列表GetUserInfoList()
:返回已经存在的用户状态列表@app.route("/index")
def ViewInfo():
return render_template("index.html", user_list=GetUserInfoList())
@app.route("/recharge")
def GetReChargeInfo():
return render_template("recharge.html", user_list=GetUserInfoList())
@app.route("/recharge_handle", methods=["GET", "POST"])
def Recharge():
UserID = request.values.get("id")
UserRecharge = request.values.get("pay")
if UserRecharge.isdigit() is True:
UserHanlder = UserHanlde(UserID)
UserHanlder.Recharge(int(UserRecharge))
UserHanlder.SaveInfo()
return render_template("back.html")
else:
return "fail"
@app.route("/register")
def GetRegisterInfo():
return render_template("register.html")
@app.route("/register_handle", methods=["GET", "POST"])
def Register():
UserID = request.values.get("id")
UserName = request.values.get("name")
UserHanlder = UserHanlde(UserID, UserName=UserName)
return render_template("back.html")
@app.route("/pay")
def GetPayName():
return render_template("pay.html", user_list=GetUserInfoList())
@app.route("/pay_handle", methods=["GET", "POST"])
def Pay():
UserIDList = request.values.getlist("vehicle")
UserIDPay = request.values.get("pay")
if UserIDPay.isdigit() is True:
PayNum = int(UserIDPay) / len(UserIDList)
for UserID in UserIDList:
UserHanlder = UserHanlde(UserID)
UserHanlder.PlayOneTime(PayNum)
UserHanlder.SaveInfo()
return render_template("back.html")
else:
return "fail"
路由部分
/index
:主页,包括导航和状态显示,所有用户的消费次数和余额将在这里显示/recharge
和/recharge_handle
:充值页面, /recharge
为操作页面,用户在这里填写表单数据,随后表单数据被提交到/recharge_handle
处理充值业务/register
和/register_handle
:注册页面,与/recharge
和/recharge_handle
关系相同/pay
和/pay_handle
:扣费页面,与/recharge
和/recharge_handle
关系相同app.run(host="0.0.0.0")
运行,监听所有IP,这样在局域网就可以访问了
Web使用HTML代码提供GUI,使用Jinja框架分离数据与模板
<!DOCTYPE html>
<html>
<head>
<title>index</title>
</head>
<body>
<div>
<h1>羽毛球运动管理系统</h1>
</div>
<div>
<table border="1">
<thead>
<tr>
<th>用户</th>
<th>次数</th>
<th>余额</th>
</tr>
</thead>
<tbody>
{% for user_id in user_list -%}
<tr>
<td>{{user_list[user_id]["name"]}}</td>
<td>{{user_list[user_id]["num"]}}</td>
<td>{{user_list[user_id]["balance"]}}</td>
</tr>
{%- endfor %}
</tbody>
</table>
</div>
用户状态显示,使用for循环生成表格
<div>
<a href="register">register</a>
<a href="recharge">recharge</a>
<a href="pay">pay</a>
</div>
</body>
</html>
超链接部分,用于导航
<!DOCTYPE html>
<html>
<head>
<title>register</title>
</head>
<body>
<h1>羽毛球运动管理系统--注册</h1>
<div>
<form action="register_handle" method="post" accept-charset="utf-8">
name<input type="text" name="name">
id<input type="text" name="id">
<input type="submit" name="Submit">
</form>
</div>
<a href="/index">back to index</a>
</body>
</html>
使用两个文本输入框表单输入用户名与用户ID
<!DOCTYPE html>
<html>
<head>
<title>recharge</title>
</head>
<body>
<div>
<h1>羽毛球运动管理系统--充值</h1>
</div>
<div>
<form action="recharge_handle" method="post" accept-charset="utf-8">
<select name="id">
{% for userid in user_list -%}
<option value="{{userid}}">{{user_list[userid]["name"]}}</option>
{%- endfor %}
</select>
recharge¥<input type="text" name="pay">
<input type="submit" name="Submit">
</form>
</div>
<a href="/index">back to index</a>
</body>
</html>
使用下拉菜单提供可供选择的用户名,文本输入充值金额
<!DOCTYPE html>
<html>
<head>
<title>pay</title>
</head>
<body>
<h1>羽毛球运动管理系统--消费</h1>
<div>
<form action="pay_handle" method="post" accept-charset="utf-8">
<div>
{%for userid in user_list%}
<input type="checkbox" name="vehicle" value="{{userid}}">{{user_list[userid]["name"]}}<br>
{% endfor %}
</div>
pay¥<input type="text" name="pay">
<input type="submit" name="Submit">
</form>
</div>
<a href="/index">back to index</a>
</body>
</html>
使用复选框列出所有用户提供选择,文本输入总输入金额,复选框这种表单数据在后端使用request.values.getlist("name")
获取为一个列表
<!DOCTYPE html>
<html>
<head>
<title>back</title>
</head>
<body>
<a href="/index">back to index</a>
</body>
</html>
用户完成充值/注册/消费时用于返回主页