2024秋軟體工程現場程式設計作業

SANAVY發表於2024-11-02
這個作業屬於哪個課程 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記錄


相關文章