吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 2201|回复: 24
收起左侧

[Python 原创] 财务管理小程序

[复制链接]
zyp121 发表于 2026-7-28 10:40
本帖最后由 zyp121 于 2026-7-28 14:31 编辑

每到月底都不知道钱花那去了,所以闲暇的时候弄了个小程序,有需要的小伙伴自取,初始密码:000000,支持修改密码。
(按一些小伙伴发现的问题,比如界面放大才能看到全部按键等,防止多开功能等问题做了修改)
[Python] 纯文本查看 复制代码
import tkinter as tk
from tkinter import ttk, messagebox, filedialog
import sqlite3
import pandas as pd
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from datetime import datetime, timedelta
import hashlib
import socket
import sys
import atexit
import re

BG_COLOR = "#E8F5E9"
FRAME_BG = "#F1F8E9"
BTN_BG = "#2C7A47"
BTN_FG = "white"
ENTRY_BG = "#FFFFFF"
LABEL_FG = "#1B5E20"

DB_FILE = "finance.db"
LOCK_PORT = 54321
_single_lock_socket = None

def init_db():
    try:
        conn = sqlite3.connect(DB_FILE)
        cursor = conn.cursor()
        cursor.execute('''CREATE TABLE IF NOT EXISTS records (
                            id INTEGER PRIMARY KEY AUTOINCREMENT,
                            date TEXT NOT NULL,
                            type TEXT NOT NULL,
                            category TEXT NOT NULL,
                            amount REAL NOT NULL,
                            description TEXT)''')
        cursor.execute('''CREATE TABLE IF NOT EXISTS auth (
                            id INTEGER PRIMARY KEY,
                            password TEXT NOT NULL)''')
        cursor.execute("SELECT COUNT(*) FROM auth")
        if cursor.fetchone()[0] == 0:
            default_pwd = hashlib.md5("000000".encode()).hexdigest()
            cursor.execute("INSERT INTO auth (id, password) VALUES (1, ?)", (default_pwd,))
        conn.commit()
    except Exception as e:
        messagebox.showerror("数据库错误", f"初始化数据库失败:{str(e)}")
    finally:
        conn.close()

def verify_password(input_pwd):
    conn = sqlite3.connect(DB_FILE)
    cursor = conn.cursor()
    cursor.execute("SELECT password FROM auth WHERE id=1")
    stored = cursor.fetchone()[0]
    conn.close()
    if stored and '$' in stored:
        salt, hash_val = stored.split('$', 1)
        return hashlib.sha256((salt + input_pwd).encode()).hexdigest() == hash_val
    else:
        return hashlib.md5(input_pwd.encode()).hexdigest() == stored

def change_password(new_pwd):
    salt = hashlib.sha256(str(datetime.now().timestamp()).encode()).hexdigest()[:16]
    hashed = hashlib.sha256((salt + new_pwd).encode()).hexdigest()
    stored = f"{salt}${hashed}"
    conn = sqlite3.connect(DB_FILE)
    cursor = conn.cursor()
    cursor.execute("UPDATE auth SET password = ? WHERE id=1", (stored,))
    conn.commit()
    conn.close()

def add_record(date, type_, category, amount, desc):
    conn = sqlite3.connect(DB_FILE)
    cursor = conn.cursor()
    cursor.execute("INSERT INTO records (date, type, category, amount, description) VALUES (?,?,?,?,?)",
                   (date, type_, category, amount, desc))
    conn.commit()
    conn.close()

def delete_record(record_id):
    conn = sqlite3.connect(DB_FILE)
    cursor = conn.cursor()
    cursor.execute("DELETE FROM records WHERE id=?", (record_id,))
    conn.commit()
    conn.close()

def update_record(record_id, date, type_, category, amount, desc):
    conn = sqlite3.connect(DB_FILE)
    cursor = conn.cursor()
    cursor.execute("UPDATE records SET date=?, type=?, category=?, amount=?, description=? WHERE id=?",
                   (date, type_, category, amount, desc, record_id))
    conn.commit()
    conn.close()

def get_all_records():
    conn = sqlite3.connect(DB_FILE)
    cursor = conn.cursor()
    cursor.execute("SELECT id, date, type, category, amount, description FROM records ORDER BY date DESC")
    rows = cursor.fetchall()
    conn.close()
    return rows

def get_stats():
    conn = sqlite3.connect(DB_FILE)
    cursor = conn.cursor()
    cursor.execute("SELECT SUM(amount) FROM records WHERE type='income'")
    income = cursor.fetchone()[0] or 0.0
    cursor.execute("SELECT SUM(amount) FROM records WHERE type='expense'")
    expense = cursor.fetchone()[0] or 0.0
    conn.close()
    return income, expense, income - expense

def plot_yearly_monthly_trend():
    conn = sqlite3.connect(DB_FILE)
    query = """
        SELECT strftime('%Y-%m', date) as month,
               SUM(CASE WHEN type='income' THEN amount ELSE 0 END) as income,
               SUM(CASE WHEN type='expense' THEN amount ELSE 0 END) as expense
        FROM records
        WHERE date >= date('now', '-10 years')
        GROUP BY month
        ORDER BY month
    """
    df = pd.read_sql_query(query, conn)
    conn.close()
    if df.empty:
        messagebox.showinfo("提示", "没有足够数据绘制10年趋势图,请先添加记录。")
        return
    end_date = datetime.now().replace(day=1)
    start_date = end_date - timedelta(days=365*10)
    all_months = pd.date_range(start=start_date, end=end_date, freq='MS').strftime('%Y-%m')
    df_full = pd.DataFrame({'month': all_months})
    df_full = df_full.merge(df, on='month', how='left').fillna(0)
    fig, ax = plt.subplots(figsize=(14, 6), facecolor=BG_COLOR)
    ax.set_facecolor(FRAME_BG)
    x = range(len(df_full))
    width = 0.35
    ax.bar([i - width/2 for i in x], df_full['income'], width, label='收入', color='#2E7D32', edgecolor='white')
    ax.bar([i + width/2 for i in x], df_full['expense'], width, label='支出', color='#C62828', edgecolor='white')
    ax.set_xlabel('月份', fontsize=11)
    ax.set_ylabel('金额 (元)', fontsize=11)
    ax.set_title('近10年收支月度趋势', fontsize=13, fontweight='bold')
    ax.legend()
    ticks = range(0, len(df_full), 12)
    labels = [df_full['month'].iloc[i][:7] for i in ticks]
    ax.set_xticks(ticks)
    ax.set_xticklabels(labels, rotation=45)
    plt.tight_layout()

    top = tk.Toplevel()
    top.title("10年收支趋势图")
    top.configure(bg=BG_COLOR)
    canvas = FigureCanvasTkAgg(fig, master=top)
    canvas.draw()
    canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True)

class FinanceApp:
    def __init__(self, root):
        self.root = root
        self.root.title("个人财务管理系统V1.01")
        self.root.geometry("1100x700")
        self.root.minsize(900, 600)
        self.root.configure(bg=BG_COLOR)
        self.root.resizable(True, True)

        self.style = ttk.Style()
        self.style.theme_use('clam')
        self.style.configure('TFrame', background=BG_COLOR)
        self.style.configure('TLabel', background=BG_COLOR, foreground=LABEL_FG, font=('微软雅黑', 10))
        self.style.configure('TButton', background=BTN_BG, foreground=BTN_FG, font=('微软雅黑', 9), borderwidth=0, focuscolor='none')
        self.style.map('TButton', background=[('active', '#1B5E20')])
        self.style.configure('Treeview', background=ENTRY_BG, foreground='#1E2A1E', fieldbackground=ENTRY_BG, font=('微软雅黑', 9))
        self.style.configure('Treeview.Heading', font=('微软雅黑', 10, 'bold'), foreground=LABEL_FG)

        self.login_frame = tk.Frame(self.root, bg=BG_COLOR)
        self.main_frame = tk.Frame(self.root, bg=BG_COLOR)
        self.show_login()

    def show_login(self):
        if hasattr(self, 'main_frame'):
            self.main_frame.destroy()
        self.main_frame = tk.Frame(self.root, bg=BG_COLOR)
        self.main_frame.pack_forget()
        self.login_frame.pack(fill="both", expand=True)
        tk.Label(self.login_frame, text="个人财务管理系统", font=("微软雅黑", 24, "bold"), bg=BG_COLOR, fg=LABEL_FG).pack(pady=40)
        tk.Label(self.login_frame, text="密码:", font=("微软雅黑", 12), bg=BG_COLOR, fg=LABEL_FG).pack(pady=5)
        self.pwd_entry = tk.Entry(self.login_frame, show="*", width=20, font=("微软雅黑", 11), relief=tk.FLAT, bg=ENTRY_BG)
        self.pwd_entry.pack(pady=5, ipady=4)
        self.pwd_entry.bind("<Return>", lambda e: self.do_login())
        btn_login = tk.Button(self.login_frame, text="登录", command=self.do_login, width=12, height=1,
                              bg=BTN_BG, fg=BTN_FG, font=("微软雅黑", 11, "bold"), relief=tk.FLAT, bd=0)
        btn_login.pack(pady=20)
        self.login_error = tk.Label(self.login_frame, text="", fg="red", bg=BG_COLOR, font=("微软雅黑", 10))
        self.login_error.pack()

    def do_login(self):
        pwd = self.pwd_entry.get()
        if verify_password(pwd):
            self.login_frame.pack_forget()
            self.init_main_ui()
            self.main_frame.pack(fill="both", expand=True)
            self.refresh_record_list()
            self.refresh_stats()
        else:
            self.login_error.config(text="密码错误,初始密码: 000000")

    def init_main_ui(self):
        toolbar = tk.Frame(self.main_frame, bg=BG_COLOR, pady=8)
        toolbar.pack(fill=tk.X, padx=10)
        btn_style = {'bg': BTN_BG, 'fg': BTN_FG, 'font': ('微软雅黑', 9), 'relief': tk.FLAT, 'bd': 0, 'padx': 12, 'pady': 4}
        tk.Button(toolbar, text="修改密码", command=self.change_pwd, **btn_style).pack(side=tk.LEFT, padx=5)
        tk.Button(toolbar, text="添加记录", command=self.add_record_dialog, **btn_style).pack(side=tk.LEFT, padx=5)
        tk.Button(toolbar, text="导出Excel", command=self.export_excel, **btn_style).pack(side=tk.LEFT, padx=5)
        tk.Button(toolbar, text="导入Excel", command=self.import_excel, **btn_style).pack(side=tk.LEFT, padx=5)
        tk.Button(toolbar, text="10年趋势图", command=plot_yearly_monthly_trend, **btn_style).pack(side=tk.LEFT, padx=5)
        tk.Button(toolbar, text="退出登录", command=self.logout, **btn_style).pack(side=tk.RIGHT, padx=5)

        stats_card = tk.Frame(self.main_frame, bg=FRAME_BG, relief=tk.RAISED, bd=1, highlightthickness=0)
        stats_card.pack(fill=tk.X, padx=15, pady=10)
        self.income_label = tk.Label(stats_card, text="总收入: &#165;0.00", font=("微软雅黑", 12, "bold"), bg=FRAME_BG, fg="#2E7D32")
        self.income_label.pack(side=tk.LEFT, padx=25, pady=8, expand=True)
        self.expense_label = tk.Label(stats_card, text="总支出: &#165;0.00", font=("微软雅黑", 12, "bold"), bg=FRAME_BG, fg="#C62828")
        self.expense_label.pack(side=tk.LEFT, padx=25, pady=8, expand=True)
        self.balance_label = tk.Label(stats_card, text="总余额: &#165;0.00", font=("微软雅黑", 12, "bold"), bg=FRAME_BG, fg="#0D47A1")
        self.balance_label.pack(side=tk.LEFT, padx=25, pady=8, expand=True)

        filter_frame = tk.Frame(self.main_frame, bg=BG_COLOR)
        filter_frame.pack(fill=tk.X, padx=15, pady=5)
        tk.Label(filter_frame, text="类型筛选:", bg=BG_COLOR, fg=LABEL_FG, font=("微软雅黑", 10)).pack(side=tk.LEFT)
        self.type_var = tk.StringVar(value="全部")
        type_combo = ttk.Combobox(filter_frame, textvariable=self.type_var, values=["全部", "收入", "支出"], width=8, state="readonly")
        type_combo.pack(side=tk.LEFT, padx=5)
        type_combo.bind("<<ComboboxSelected>>", lambda e: self.refresh_record_list())
        tk.Label(filter_frame, text="关键词:", bg=BG_COLOR, fg=LABEL_FG, font=("微软雅黑", 10)).pack(side=tk.LEFT, padx=(15,0))
        self.keyword_entry = tk.Entry(filter_frame, width=15, font=("微软雅黑", 10), bg=ENTRY_BG, relief=tk.FLAT)
        self.keyword_entry.pack(side=tk.LEFT, padx=5, ipady=2)
        self.keyword_entry.bind("<KeyRelease>", lambda e: self.refresh_record_list())
        tk.Label(filter_frame, text="年份-月份:", bg=BG_COLOR, fg=LABEL_FG, font=("微软雅黑", 10)).pack(side=tk.LEFT, padx=(15,0))
        self.month_entry = tk.Entry(filter_frame, width=10, font=("微软雅黑", 10), bg=ENTRY_BG, relief=tk.FLAT)
        self.month_entry.pack(side=tk.LEFT, padx=5, ipady=2)
        self.month_entry.bind("<KeyRelease>", lambda e: self.refresh_record_list())
        tk.Label(filter_frame, text="(例: 2025-03)", fg="#6A8E6A", bg=BG_COLOR, font=("微软雅黑", 9)).pack(side=tk.LEFT, padx=2)

        columns = ("ID", "日期", "类型", "类别", "金额", "备注")
        self.tree = ttk.Treeview(self.main_frame, columns=columns, show="headings", height=18)
        for col in columns:
            self.tree.heading(col, text=col, command=lambda c=col: self.sort_treeview(c))
            self.tree.column(col, width=100 if col != "备注" else 200, anchor="center")
        self.tree.pack(fill=tk.BOTH, expand=True, padx=15, pady=5)

        self.context_menu = tk.Menu(self.root, tearoff=0, bg=ENTRY_BG, fg=LABEL_FG)
        self.context_menu.add_command(label="编辑", command=self.edit_record)
        self.context_menu.add_command(label="删除", command=self.delete_record)
        self.tree.bind("<Button-3>", self.show_context_menu)

        self.sort_column = None
        self.sort_reverse = False

    def sort_treeview(self, col):
        if self.sort_column == col:
            self.sort_reverse = not self.sort_reverse
        else:
            self.sort_column = col
            self.sort_reverse = False
        data = [(self.tree.item(item)['values'], item) for item in self.tree.get_children('')]
        col_index = {"ID":0, "日期":1, "类型":2, "类别":3, "金额":4, "备注":5}[col]
        def try_float(v):
            try:
                return float(str(v).replace('&#165;','').replace(',',''))
            except:
                return v
        data.sort(key=lambda x: try_float(x[0][col_index]), reverse=self.sort_reverse)
        for idx, (values, item) in enumerate(data):
            self.tree.move(item, '', idx)

    def refresh_stats(self):
        inc, exp, bal = get_stats()
        self.income_label.config(text=f"总收入: &#165;{inc:.2f}")
        self.expense_label.config(text=f"总支出: &#165;{exp:.2f}")
        self.balance_label.config(text=f"总余额: &#165;{bal:.2f}")

    def refresh_record_list(self):
        for row in self.tree.get_children():
            self.tree.delete(row)
        records = get_all_records()
        type_filter = self.type_var.get()
        keyword = self.keyword_entry.get().strip().lower()
        month_filter = self.month_entry.get().strip()
        for rec in records:
            rec_id, date, typ, cat, amt, desc = rec
            if type_filter != "全部":
                if type_filter == "收入" and typ != "income":
                    continue
                if type_filter == "支出" and typ != "expense":
                    continue
            if keyword and not (keyword in cat.lower() or keyword in desc.lower()):
                continue
            if month_filter and not date.startswith(month_filter):
                continue
            typ_display = "收入" if typ == "income" else "支出"
            self.tree.insert("", tk.END, iid=rec_id, values=(rec_id, date, typ_display, cat, f"&#165;{amt:.2f}", desc))

    def show_context_menu(self, event):
        item = self.tree.identify_row(event.y)
        if item:
            self.tree.selection_set(item)
            self.context_menu.post(event.x_root, event.y_root)

    def add_record_dialog(self):
        self._record_form(is_edit=False)

    def edit_record(self):
        selected = self.tree.selection()
        if not selected:
            messagebox.showwarning("警告", "请先选择一条记录")
            return
        rec_id = int(selected[0])
        conn = sqlite3.connect(DB_FILE)
        cursor = conn.cursor()
        cursor.execute("SELECT date, type, category, amount, description FROM records WHERE id=?", (rec_id,))
        row = cursor.fetchone()
        conn.close()
        if row:
            type_display = "收入" if row[1] == "income" else "支出"
            self._record_form(is_edit=True, rec_id=rec_id, default_date=row[0], default_type=type_display,
                              default_cat=row[2], default_amt=row[3], default_desc=row[4])

    def _record_form(self, is_edit, rec_id=None, default_date="", default_type="收入", default_cat="餐饮", default_amt=0.0, default_desc=""):
        dialog = tk.Toplevel(self.root)
        dialog.title("编辑记录" if is_edit else "新增记录")
        dialog.geometry("420x450")
        dialog.configure(bg=BG_COLOR)
        dialog.grab_set()

        tk.Label(dialog, text="日期 (YYYY-MM-DD):", bg=BG_COLOR, fg=LABEL_FG, font=("微软雅黑", 10)).pack(pady=(15,5))
        date_entry = tk.Entry(dialog, font=("微软雅黑", 10), bg=ENTRY_BG, relief=tk.FLAT, width=25)
        date_entry.insert(0, default_date or datetime.today().strftime("%Y-%m-%d"))
        date_entry.pack(ipady=3)

        tk.Label(dialog, text="类型:", bg=BG_COLOR, fg=LABEL_FG, font=("微软雅黑", 10)).pack(pady=(10,5))
        type_var = tk.StringVar(value=default_type)
        type_combo = ttk.Combobox(dialog, textvariable=type_var, values=["收入", "支出"], state="readonly", width=20)
        type_combo.pack()

        tk.Label(dialog, text="类别:", bg=BG_COLOR, fg=LABEL_FG, font=("微软雅黑", 10)).pack(pady=(10,5))
        cat_entry = tk.Entry(dialog, font=("微软雅黑", 10), bg=ENTRY_BG, relief=tk.FLAT, width=25)
        cat_entry.insert(0, default_cat)
        cat_entry.pack(ipady=3)

        tk.Label(dialog, text="金额 (数字):", bg=BG_COLOR, fg=LABEL_FG, font=("微软雅黑", 10)).pack(pady=(10,5))
        amt_entry = tk.Entry(dialog, font=("微软雅黑", 10), bg=ENTRY_BG, relief=tk.FLAT, width=25)
        amt_entry.insert(0, str(default_amt))
        amt_entry.pack(ipady=3)

        def validate_amount(char):
            return char.isdigit() or char == '.' or char == ''
        vcmd = (dialog.register(validate_amount), '%S')
        amt_entry.config(validate="key", validatecommand=vcmd)

        tk.Label(dialog, text="备注:", bg=BG_COLOR, fg=LABEL_FG, font=("微软雅黑", 10)).pack(pady=(10,5))
        desc_entry = tk.Entry(dialog, font=("微软雅黑", 10), bg=ENTRY_BG, relief=tk.FLAT, width=25)
        desc_entry.insert(0, default_desc)
        desc_entry.pack(ipady=3)

        def save():
            date = date_entry.get().strip()
            try:
                datetime.strptime(date, "%Y-%m-%d")
            except ValueError:
                messagebox.showerror("错误", "日期格式不正确,请使用 YYYY-MM-DD")
                return
            typ_cn = type_var.get()
            typ_en = "income" if typ_cn == "收入" else "expense"
            cat = cat_entry.get().strip()
            amt_str = amt_entry.get().strip()
            if not amt_str:
                messagebox.showerror("错误", "金额不能为空")
                return
            try:
                amt = float(amt_str)
            except:
                messagebox.showerror("错误", "金额必须是数字")
                return
            desc = desc_entry.get().strip()
            if not cat:
                messagebox.showerror("错误", "类别不能为空")
                return
            if is_edit:
                update_record(rec_id, date, typ_en, cat, amt, desc)
                messagebox.showinfo("成功", "修改成功")
            else:
                add_record(date, typ_en, cat, amt, desc)
                messagebox.showinfo("成功", "添加成功")
            dialog.destroy()
            self.refresh_record_list()
            self.refresh_stats()

        btn_save = tk.Button(dialog, text="保存", command=save, bg=BTN_BG, fg=BTN_FG, font=("微软雅黑", 10, "bold"),
                             relief=tk.FLAT, padx=20, pady=5)
        btn_save.pack(pady=20)

    def delete_record(self):
        selected = self.tree.selection()
        if not selected:
            return
        if messagebox.askyesno("确认", "确定删除选中的记录吗?"):
            for item in selected:
                rec_id = int(item)
                delete_record(rec_id)
            self.refresh_record_list()
            self.refresh_stats()

    def export_excel(self):
        records = get_all_records()
        if not records:
            messagebox.showinfo("提示", "无数据可导出")
            return
        data = []
        for r in records:
            data.append({
                "日期": r[1],
                "类型": "收入" if r[2] == "income" else "支出",
                "类别": r[3],
                "金额": r[4],
                "备注": r[5]
            })
        df = pd.DataFrame(data)
        file_path = filedialog.asksaveasfilename(defaultextension=".xlsx", filetypes=[("Excel文件", "*.xlsx")])
        if file_path:
            try:
                df.to_excel(file_path, index=False)
                messagebox.showinfo("成功", f"已导出到 {file_path}")
            except Exception as e:
                messagebox.showerror("导出失败", str(e))

    def import_excel(self):
        file_path = filedialog.askopenfilename(filetypes=[("Excel文件", "*.xlsx *.xls")])
        if not file_path:
            return
        try:
            df = pd.read_excel(file_path)
            required = ["日期", "类型", "类别", "金额"]
            for col in required:
                if col not in df.columns:
                    messagebox.showerror("错误", f"Excel缺少必要列: {col}\n请确保有:日期、类型、类别、金额、备注(可选)")
                    return
            conn = sqlite3.connect(DB_FILE)
            cursor = conn.cursor()
            try:
                cursor.execute("BEGIN")
                count = 0
                for _, row in df.iterrows():
                    date = str(row["日期"])[:10]
                    try:
                        datetime.strptime(date, "%Y-%m-%d")
                    except:
                        continue
                    typ_raw = str(row["类型"])
                    typ = "income" if "收入" in typ_raw else "expense"
                    category = str(row["类别"])
                    try:
                        amount = float(row["金额"])
                    except:
                        continue
                    desc = str(row.get("备注", "")) if pd.notna(row.get("备注")) else ""
                    cursor.execute("INSERT INTO records (date, type, category, amount, description) VALUES (?,?,?,?,?)",
                                   (date, typ, category, amount, desc))
                    count += 1
                conn.commit()
                messagebox.showinfo("成功", f"成功导入 {count} 条记录")
            except Exception as e:
                conn.rollback()
                messagebox.showerror("导入失败", f"数据写入错误:{str(e)}")
            finally:
                conn.close()
            self.refresh_record_list()
            self.refresh_stats()
        except Exception as e:
            messagebox.showerror("导入失败", str(e))

    def change_pwd(self):
        dialog = tk.Toplevel(self.root)
        dialog.title("修改密码")
        dialog.geometry("320x280")
        dialog.configure(bg=BG_COLOR)
        dialog.grab_set()
        tk.Label(dialog, text="原密码:", bg=BG_COLOR, fg=LABEL_FG, font=("微软雅黑", 10)).pack(pady=(15,5))
        old_entry = tk.Entry(dialog, show="*", font=("微软雅黑", 10), bg=ENTRY_BG, relief=tk.FLAT, width=20)
        old_entry.pack(ipady=3)
        tk.Label(dialog, text="新密码:", bg=BG_COLOR, fg=LABEL_FG, font=("微软雅黑", 10)).pack(pady=(10,5))
        new_entry = tk.Entry(dialog, show="*", font=("微软雅黑", 10), bg=ENTRY_BG, relief=tk.FLAT, width=20)
        new_entry.pack(ipady=3)
        tk.Label(dialog, text="确认新密码:", bg=BG_COLOR, fg=LABEL_FG, font=("微软雅黑", 10)).pack(pady=(10,5))
        confirm_entry = tk.Entry(dialog, show="*", font=("微软雅黑", 10), bg=ENTRY_BG, relief=tk.FLAT, width=20)
        confirm_entry.pack(ipady=3)

        def do_change():
            old = old_entry.get()
            if not verify_password(old):
                messagebox.showerror("错误", "原密码错误")
                return
            new_pwd = new_entry.get()
            if len(new_pwd) < 4:
                messagebox.showerror("错误", "新密码长度至少4位")
                return
            if new_pwd != confirm_entry.get():
                messagebox.showerror("错误", "两次输入的新密码不一致")
                return
            change_password(new_pwd)
            messagebox.showinfo("成功", "密码已修改,下次登录生效")
            dialog.destroy()

        btn_confirm = tk.Button(dialog, text="确认修改", command=do_change, bg=BTN_BG, fg=BTN_FG,
                                font=("微软雅黑", 10), relief=tk.FLAT, padx=15, pady=4)
        btn_confirm.pack(pady=20)

    def logout(self):
        if messagebox.askyesno("退出", "确定退出登录吗?"):
            self.main_frame.pack_forget()
            self.show_login()
            self.pwd_entry.delete(0, tk.END)
            self.login_error.config(text="")

def acquire_single_instance():
    global _single_lock_socket
    try:
        _single_lock_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        _single_lock_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        _single_lock_socket.bind(('127.0.0.1', LOCK_PORT))
        _single_lock_socket.listen(1)
        return True
    except socket.error:
        return False

def release_single_instance():
    global _single_lock_socket
    if _single_lock_socket:
        try:
            _single_lock_socket.close()
        except:
            pass

atexit.register(release_single_instance)

if __name__ == "__main__":
    init_db()
    root = tk.Tk()
    root.withdraw()
    if not acquire_single_instance():
        messagebox.showerror("启动失败", "程序已经在运行中,请不要重复打开!")
        root.destroy()
        sys.exit(0)
    root.deiconify()
    app = FinanceApp(root)
    root.mainloop()




修改后封装版:链接:https://pan.baidu.com/s/1cReKf5yf6orJMfuD5Rwwqw?pwd=jti9 提取码:jti9



密码修改界面

密码修改界面

记录新增界面

记录新增界面

免费评分

参与人数 1吾爱币 +7 热心值 +1 收起 理由
苏紫方璇 + 7 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!

查看全部评分

发帖前要善用【论坛搜索】功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

landury 发表于 2026-7-28 11:06
我来看看怎么个事儿
landury 发表于 2026-7-28 11:15
Tyler9 发表于 2026-7-28 11:19
shzjz123 发表于 2026-7-28 11:24
应该加入阻止多开功能
 楼主| zyp121 发表于 2026-7-28 11:29
landury 发表于 2026-7-28 11:15
无法添加新增记录

可能是界面原因,要放大才能看到最下面的保存键
WELIANG 发表于 2026-7-28 11:41
没截图,不是很了解怎么用
zhufuan 发表于 2026-7-28 11:48
只有只有最大化界面,才能看到完整的功能,希望改正一下谢谢
lianxiang1122 发表于 2026-7-28 11:54
最好截个图看看是啥样的
and11 发表于 2026-7-28 12:18
能够自动理财涨钱吗
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - 52pojie.cn ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2026-11-5 08:09

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表