這個作業屬於哪個課程 | https://edu.cnblogs.com/campus/fzu/SE2024 |
---|---|
作業要求 | https://edu.cnblogs.com/campus/fzu/SE2024/homework/13302 |
作業的目標 | 團隊合作開發一款個人記賬本軟體 |
團隊名稱 | 福氣滿滿 |
團隊成員學號-名字 | 052203132童瀟劍,102201226陳瀟健,102201235曾煒坤,102201234洪慶楊,102201224 陳博涵,182200311洪閩南,102202156高濤,042201520舒錦城,102201335董雯莉 |
一、組員職責分工
姓名 | 負責模組 |
---|---|
童瀟劍 | 賬本切換模組,使用者名稱,密碼本地儲存模組 |
陳瀟健 | 個人資訊模組 |
曾煒坤 | 主頁和記錄模組 |
洪慶楊 | 刪除記錄模組 |
陳博涵 | 分類選擇模組 |
洪閩南 | 日期檢測模組 |
高濤 | 收支趨勢分析功能 |
舒錦城 | 登入模組、支出收入視覺化 |
董雯莉 | 登入註冊模組 |
二、程式執行環境
作業系統:Windows11
程式語言:Python
程式設計工具:VScode,PyCharm
三、軟體執行截圖和影片
登入介面
主介面
統計資訊介面
收支趨勢圖介面
刪除記錄介面
個人資訊介面
四、關鍵程式碼
def show_login_window(self):
# 設定字型
title_font = font.Font(family='Arial', size=16, weight='bold')
label_font = font.Font(family='Arial', size=12)
"""顯示登入視窗."""
login_window = tk.Toplevel(self.root)
login_window.title("登入")
login_window.geometry("300x300")
login_window.configure(bg='#FFF8F0')
tk.Label(login_window, text="使用者名稱:", font=label_font, bg='#FFF8F0').pack(pady=10)
username_entry = tk.Entry(login_window, font=label_font)
username_entry.pack(pady=10)
tk.Label(login_window, text="密碼:", font=label_font, bg='#FFF8F0').pack(pady=10)
password_entry = tk.Entry(login_window, show="*", font=label_font)
password_entry.pack(pady=10)
def login():
username = username_entry.get()
password = password_entry.get()
if username in self.userf and self.userf[username] == password:
messagebox.showinfo("登入成功", "歡迎回來!")
login_window.destroy()
self.root.deiconify() # 顯示主視窗
else:
messagebox.showerror("登入失敗", "使用者名稱或密碼錯誤。")
login_button = tk.Button(login_window, text="登入", command=login, bg="#4CAF50", fg="white", font=label_font, width=20)
login_button.pack(pady=10)
# 新增註冊按鈕
register_button = tk.Button(login_window, text="註冊", command=self.show_register_window, bg="#4CAF50",
fg="white", font=label_font, width=20)
register_button.pack(pady=10)
def show_register_window(self):
# 設定字型
title_font = font.Font(family='Arial', size=16, weight='bold')
label_font = font.Font(family='Arial', size=12)
"""顯示註冊視窗."""
register_window = tk.Toplevel(self.root)
register_window.title("註冊")
register_window.geometry("300x250")
register_window.configure(bg='#FFF8F0')
tk.Label(register_window, text="使用者名稱:", font=label_font, bg='#FFF8F0').pack(pady=10)
username_entry = tk.Entry(register_window, font=label_font)
username_entry.pack(pady=10)
tk.Label(register_window, text="密碼:", font=label_font, bg='#FFF8F0').pack(pady=10)
password_entry = tk.Entry(register_window, show="*", font=label_font)
password_entry.pack(pady=10)
def register():
username = username_entry.get()
password = password_entry.get()
if username in self.userf:
messagebox.showerror("註冊失敗", "使用者名稱已存在。")
else:
users = {}
users[username] = password
self.userf.update(users)
messagebox.showinfo("註冊成功", "註冊成功!")
with open('data/un_pw.json', 'w') as f:
json.dump(self.userf, f)
register_window.destroy() # 關閉註冊視窗
register_button = tk.Button(register_window, text="註冊", command=register, bg="#4CAF50", fg="white",
font=label_font, width=20)
register_button.pack(pady=10)
def submit_record(self):
date = self.date_entry.get()
amount = float(self.amount_entry.get())
category = self.category_var.get() # 獲取選擇的類別
note = self.note_entry.get()
def is_valid_date(date_str):
try:
datetime.strptime(date_str, '%Y-%m-%d')
return True
except ValueError:
return False
if not is_valid_date(date):
messagebox.showinfo('請正確輸入日期!')
return
add_record(date, amount, category, note)
self.date_entry.delete(0, tk.END)
self.amount_entry.delete(0, tk.END)
self.category_var.set("選擇類別") # 重置類別選擇
self.note_entry.delete(0, tk.END)
messagebox.showinfo("成功", "記錄已新增!")
def create_view_window(self):
view_window = tk.Toplevel()
view_window.title("最近記賬記錄")
view_window.geometry("500x300")
view_window.configure(bg='#FFF8F0')
records = get_recent_records()
if not records:
tk.Label(view_window, text="沒有記錄可顯示。", bg='#FFF8F0', font=('Arial', 12)).pack(pady=10)
else:
tk.Label(view_window, text="最近的記錄:", font=('Arial', 14), bg='#FFF8F0').pack(pady=10)
for record in records:
record_text = f"日期: {record['date']}, 金額: {record['amount']}, 類別: {record['category']}, 備註: {record['note']}"
tk.Label(view_window, text=record_text, bg='#FFF8F0').pack()
def create_delete_window(self):
delete_window = tk.Toplevel()
delete_window.title("刪除記賬記錄")
delete_window.geometry("500x300")
delete_window.configure(bg='#FFF8F0')
tk.Label(delete_window, text="選擇要刪除的記錄:", bg='#FFF8F0', font=('Arial', 12)).pack(pady=10)
self.record_listbox = tk.Listbox(delete_window, width=60, height=10)
records = load_records()
for record in records:
record_text = f"日期: {record['date']}, 金額: {record['amount']}, 類別: {record['category']}, 備註: {record['note']}"
self.record_listbox.insert(tk.END, record_text)
self.record_listbox.pack(pady=10)
delete_button = tk.Button(delete_window, text="刪除選中記錄", command=self.delete_selected_record, bg="#F44336", fg="white")
delete_button.pack(pady=10)
def delete_selected_record(self):
selected_index = self.record_listbox.curselection()
if not selected_index:
messagebox.showwarning("警告", "請先選擇一條記錄。")
return
records = load_records()
selected_record_text = self.record_listbox.get(selected_index)
selected_date = selected_record_text.split(",")[0].split(": ")[1]
success = delete_record(date=selected_date)
if success:
messagebox.showinfo("成功", "記錄已刪除!")
self.record_listbox.delete(selected_index)
else:
messagebox.showerror("錯誤", "未找到指定的記錄!")
def show_statistics(self):
total_income, total_expense, income_categories, expense_categories = calculate_statistics()
stats_window = tk.Toplevel()
stats_window.title("統計資訊")
stats_window.geometry("1000x600")
stats_window.configure(bg='#FFF8F0')
tk.Label(stats_window, text=f"總收入: {total_income}", font=('Arial', 14), bg='#FFF8F0').pack(pady=10)
tk.Label(stats_window, text=f"總支出: {total_expense}", font=('Arial', 14), bg='#FFF8F0').pack(pady=10)
tk.Label(stats_window, text="各類別收入:", font=('Arial', 14), bg='#FFF8F0').pack(pady=10)
for category, amount in income_categories.items():
tk.Label(stats_window, text=f" {category}: {amount}", bg='#FFF8F0').pack()
tk.Label(stats_window, text="各類別支出:", font=('Arial', 14), bg='#FFF8F0').pack(pady=10)
for category, amount in expense_categories.items():
tk.Label(stats_window, text=f" {category}: {amount}", bg='#FFF8F0').pack()
fig, axs = plt.subplots(1, 3, figsize=(15, 5))
if income_categories:
labels = list(income_categories.keys())
sizes = list(income_categories.values())
axs[0].pie(sizes, labels=labels, autopct='%1.1f%%', startangle=90)
axs[0].set_title('各類收入佔比')
if expense_categories:
labels = list(expense_categories.keys())
sizes = list(expense_categories.values())
axs[1].pie(sizes, labels=labels, autopct='%1.1f%%', startangle=90)
axs[1].set_title('各類支出佔比')
if total_income > 0 or total_expense > 0:
sizes = [total_income, total_expense]
labels = ['收入', '支出']
axs[2].pie(sizes, labels=labels, autopct='%1.1f%%', startangle=90)
axs[2].set_title('收入和支出佔比')
canvas = FigureCanvasTkAgg(fig, master=stats_window)
canvas.draw()
canvas.get_tk_widget().pack(pady=20)
def create_info_window(self):
info_window = tk.Toplevel()
info_window.title("個人資訊")
info_window.geometry("400x350") # 增加視窗高度以容納新資訊
info_window.configure(bg='#FFF8F0')
label_font = font.Font(family='Arial', size=12)
tk.Label(info_window, text="姓名:", bg='#FFF8F0', font=label_font).grid(row=0, column=0, padx=5, pady=5)
self.name_entry = tk.Entry(info_window, width=30, font=label_font)
self.name_entry.grid(row=0, column=1, padx=5, pady=5)
tk.Label(info_window, text="郵箱:", bg='#FFF8F0', font=label_font).grid(row=1, column=0, padx=5, pady=5)
self.email_entry = tk.Entry(info_window, width=30, font=label_font)
self.email_entry.grid(row=1, column=1, padx=5, pady=5)
tk.Label(info_window, text="電話:", bg='#FFF8F0', font=label_font).grid(row=2, column=0, padx=5, pady=5)
self.phone_entry = tk.Entry(info_window, width=30, font=label_font)
self.phone_entry.grid(row=2, column=1, padx=5, pady=5)
# 顯示記賬總筆數
total_records = len(load_records()) # 獲取總記錄數
tk.Label(info_window, text="記賬總筆數:", bg='#FFF8F0', font=label_font).grid(row=3, column=0, padx=5, pady=5)
tk.Label(info_window, text=str(total_records), bg='#FFF8F0', font=label_font).grid(row=3, column=1, padx=5, pady=5)
save_button = tk.Button(info_window, text="儲存資訊", command=self.save_info, bg="#4CAF50", fg="white", font=label_font)
save_button.grid(row=4, column=1, padx=5, pady=10)
cj_button = tk.Button(info_window, text="更改賬本", command=self.change_json_window, bg="#4CAF50", fg="white", font=label_font)
cj_button.grid(row=5, column=1, padx=5, pady=10)
def save_info(self):
name = self.name_entry.get()
email = self.email_entry.get()
phone = self.phone_entry.get()
# 這裡可以新增儲存資訊的邏輯,例如儲存到檔案或資料庫
messagebox.showinfo("成功", "個人資訊已儲存!")
在這段程式碼中,我們實現了應用的登入,註冊功能,和基本的記賬,將資料儲存到本地的json,讀取賬目等功能。
五、軟體亮點
收支趨勢分析功能
輸入給定的日期範圍後,軟體將給出這個日期區間內的收支趨勢分析,便於我們更好地規劃消費。
支出收入視覺化
根據支援/收入的分類生成餅圖,讓我們更直觀地看到我們的收入/支出構成。
六、較大收穫的事件
七、組員程式設計體驗
童瀟劍:相互溝通是這次程式設計中重要的一環,經過這次的磨合歷練,我們後續的團隊協作會更加順利
陳瀟健:現場程式設計對大家進行了磨合,之後的協作程式設計會越來越順利。
董雯莉:誰說這程式設計難的,這程式設計太棒了。同時也感謝隊友的辛勤付出!大家都很棒!
陳博涵:軟體工程真是一門美妙的課程,讓我享受到程式設計的樂趣。
洪慶楊:透過實踐,讓我鍛鍊思維,提升技能,增強協作能力
曾煒坤:一下午的團隊程式設計,在與隊友的攜手共進中,深感團隊力量於程式碼世界的奇妙展現。
八、組長打分
學號姓名 | 分數 |
---|---|
102201226陳瀟健 | 96 |
102201235曾煒坤 | 97 |
102201234洪慶楊 | 95 |
102201224 陳博涵 | 96 |
182200311洪閩南 | 95 |
102202156高濤 | 96 |
042201520舒錦城 | 95 |
102201335董雯莉 | 96 |
九、github倉庫地址和commit記錄
倉庫地址
github倉庫地址:https://github.com/GoldenglowBF/CashBook
commit記錄