動機
小伙伴們最近迷戀上羽毛球,組織了個小群,辦了公用的運動卡用于開場,考慮不是每次活動都是全員參與,需要一個計費的系統來計算每個人需要交的費用。商討后決定采用“預充-扣費”的方式,則需要一個系統進行計費和扣費。
技術路線規劃
模塊名 | 語言 | 備注 |
---|---|---|
管理核心 | Python | 使用JSON存儲信息 |
Web后端 | Python | Flask框架 |
Web前端 | HTML | Jinja框架渲染 |
實現
核心模塊——用戶狀態管理
該部分是整個計費系統的核心,用于管理每個用戶的余額。使用一個類表示用戶,需要的屬性為
- 狀態列表(用戶名,ID,使用次數,余額)
需要的方法有:
- 創建用戶(創建新的JSON文件)
- 讀取用戶狀態(從已有的JSON文件中)
- 扣費(使用次數增加1,余額減小)
- 充值(余額增加)
- 保存狀態(將現有的狀態寫入JSON文件)
代碼如下
# -*- 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后端
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前端
Web使用HTML代碼提供GUI,使用Jinja框架分離數據與模板
- index界面
<!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>
超鏈接部分,用于導航
- register界面
<!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
- recharge界面
<!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>
使用下拉菜單提供可供選擇的用戶名,文本輸入充值金額
- pay界面
<!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")
獲取為一個列表
- back界面
<!DOCTYPE html>
<html>
<head>
<title>back</title>
</head>
<body>
<a href="/index">back to index</a>
</body>
</html>
用戶完成充值/注冊/消費時用于返回主頁