基于Python、Flet的待办应用重启后数据丢失问题及解决方案咨询
问题解答
首先明确:用户注册/认证不能直接解决任务丢失的核心问题,但它能配合持久化存储实现「用户专属任务隔离+跨设备同步」,是解决网页版多用户任务混乱、以及实现个性化任务保存的必要环节。
任务丢失的本质是你当前的应用仅把任务存在内存中,关闭后内存释放就丢失了——先解决基础的持久化存储,再叠加用户认证功能才是正确路径。
第一步:先解决基础任务保存问题(无需用户系统)
不管是桌面版还是网页版,先把任务从内存写到持久化介质里,就能解决关闭后丢失的问题。
方案1:本地JSON文件存储(适合桌面版)
用Python内置的json模块,每次修改任务后写入文件,启动时读取:
import json import flet as ft # 存储任务的JSON文件路径 TASKS_FILE = "tasks.json" def load_tasks(): try: with open(TASKS_FILE, "r", encoding="utf-8") as f: return json.load(f) except FileNotFoundError: return [] # 文件不存在时返回空列表 def save_tasks(tasks): with open(TASKS_FILE, "w", encoding="utf-8") as f: json.dump(tasks, f, ensure_ascii=False, indent=2) def main(page: ft.Page): tasks = load_tasks() task_list = ft.ListView(expand=True, spacing=10) def update_task_display(): task_list.controls.clear() for idx, task in enumerate(tasks): # 每条任务加删除按钮 delete_btn = ft.IconButton(ft.icons.DELETE, on_click=lambda e, i=idx: delete_task(i)) task_item = ft.Row([ft.Text(task), delete_btn]) task_list.controls.append(task_item) page.update() def add_task(e): new_task = task_input.value.strip() if new_task: tasks.append(new_task) save_tasks(tasks) update_task_display() task_input.value = "" page.update() def delete_task(task_idx): tasks.pop(task_idx) save_tasks(tasks) update_task_display() # 构建UI task_input = ft.TextField(hint_text="输入新任务", expand=True) add_btn = ft.ElevatedButton("添加任务", on_click=add_task) page.add(ft.Row([task_input, add_btn]), task_list) update_task_display() if __name__ == "__main__": ft.app(target=main)
⚠️ 注意:网页版用这个方案的话,JSON文件会存在服务器端,所有访问的用户会共享同一份任务,不适合多用户场景。
方案2:SQLite数据库存储(适合桌面+网页版)
用Python内置的sqlite3模块,把任务存在数据库里,后续加用户认证也能无缝衔接:
import sqlite3 import flet as ft def init_db(): conn = sqlite3.connect("todo.db") c = conn.cursor() # 创建任务表(先不关联用户) c.execute('''CREATE TABLE IF NOT EXISTS tasks (id INTEGER PRIMARY KEY AUTOINCREMENT, content TEXT NOT NULL, completed BOOLEAN NOT NULL DEFAULT 0)''') conn.commit() conn.close() def load_tasks(): conn = sqlite3.connect("todo.db") c = conn.cursor() c.execute("SELECT id, content, completed FROM tasks") tasks = c.fetchall() conn.close() return tasks def save_new_task(content): conn = sqlite3.connect("todo.db") c = conn.cursor() c.execute("INSERT INTO tasks (content) VALUES (?)", (content,)) conn.commit() conn.close() def toggle_task_completed(task_id): conn = sqlite3.connect("todo.db") c = conn.cursor() c.execute("UPDATE tasks SET completed = NOT completed WHERE id = ?", (task_id,)) conn.commit() conn.close() # Flet UI逻辑类似方案1,只是数据操作换成数据库方法,这里省略重复代码
第二步:添加用户注册/认证(解决网页版多用户隔离)
如果要让网页版支持多用户,每个用户有自己的任务,就需要加用户系统,核心是让任务和用户ID绑定。
1. 扩展数据库表结构
在之前的SQLite基础上,新增用户表,并修改任务表关联用户ID:
def init_db(): conn = sqlite3.connect("todo.db") c = conn.cursor() # 用户表:存储用户名和加密后的密码(禁止存明文) c.execute('''CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT UNIQUE NOT NULL, password_hash TEXT NOT NULL)''') # 任务表:新增user_id字段关联用户 c.execute('''CREATE TABLE IF NOT EXISTS tasks (id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL, content TEXT NOT NULL, completed BOOLEAN NOT NULL DEFAULT 0, FOREIGN KEY(user_id) REFERENCES users(id))''') conn.commit() conn.close()
2. 实现用户注册/登录逻辑
用hashlib对密码加密,避免明文存储:
import hashlib def hash_password(password): # 用SHA256加密密码 return hashlib.sha256(password.encode("utf-8")).hexdigest() def register_user(username, password): conn = sqlite3.connect("todo.db") c = conn.cursor() try: c.execute("INSERT INTO users (username, password_hash) VALUES (?, ?)", (username, hash_password(password))) conn.commit() return True # 注册成功 except sqlite3.IntegrityError: return False # 用户名已存在 finally: conn.close() def login_user(username, password): conn = sqlite3.connect("todo.db") c = conn.cursor() c.execute("SELECT id, password_hash FROM users WHERE username = ?", (username,)) user = c.fetchone() conn.close() # 验证密码哈希是否匹配 if user and user[1] == hash_password(password): return user[0] # 返回用户ID return None # 登录失败
3. 在Flet中添加登录/注册界面
切换登录状态,加载对应用户的任务:
def main(page: ft.Page): init_db() current_user_id = None # 当前登录用户ID def show_login_page(): page.controls.clear() # 登录表单 username_input = ft.TextField(hint_text="用户名") password_input = ft.TextField(hint_text="密码", password=True) def handle_login(e): nonlocal current_user_id user_id = login_user(username_input.value, password_input.value) if user_id: current_user_id = user_id show_task_page() else: page.add(ft.Text("用户名或密码错误", color=ft.colors.RED)) page.update() page.add( username_input, password_input, ft.ElevatedButton("登录", on_click=handle_login), ft.TextButton("没有账户?去注册", on_click=lambda e: show_register_page()) ) page.update() def show_register_page(): page.controls.clear() # 注册表单 username_input = ft.TextField(hint_text="用户名") password_input = ft.TextField(hint_text="密码", password=True) def handle_register(e): if register_user(username_input.value, password_input.value): page.add(ft.Text("注册成功,请登录", color=ft.colors.GREEN)) show_login_page() else: page.add(ft.Text("用户名已存在", color=ft.colors.RED)) page.update() page.add( username_input, password_input, ft.ElevatedButton("注册", on_click=handle_register), ft.TextButton("已有账户?去登录", on_click=lambda e: show_login_page()) ) page.update() def show_task_page(): page.controls.clear() # 加载当前用户的任务 conn = sqlite3.connect("todo.db") c = conn.cursor() c.execute("SELECT id, content, completed FROM tasks WHERE user_id = ?", (current_user_id,)) tasks = c.fetchall() conn.close() # 任务列表和操作逻辑(类似之前的代码,只是数据操作要带上user_id) task_list = ft.ListView(expand=True) def update_task_list(): task_list.controls.clear() for task_id, content, completed in tasks: check_box = ft.Checkbox(value=completed, on_change=lambda e, tid=task_id: toggle_task(tid)) delete_btn = ft.IconButton(ft.icons.DELETE, on_click=lambda e, tid=task_id: delete_task(tid)) task_item = ft.Row([check_box, ft.Text(content), delete_btn]) task_list.controls.append(task_item) page.update() def add_task(e): new_task = task_input.value.strip() if new_task: conn = sqlite3.connect("todo.db") c = conn.cursor() c.execute("INSERT INTO tasks (user_id, content) VALUES (?, ?)", (current_user_id, new_task)) conn.commit() conn.close() # 重新加载任务 nonlocal tasks conn = sqlite3.connect("todo.db") c = conn.cursor() c.execute("SELECT id, content, completed FROM tasks WHERE user_id = ?", (current_user_id,)) tasks = c.fetchall() conn.close() update_task_list() task_input.value = "" page.update() def toggle_task(task_id): toggle_task_completed(task_id) # 重新加载任务 nonlocal tasks conn = sqlite3.connect("todo.db") c = conn.cursor() c.execute("SELECT id, content, completed FROM tasks WHERE user_id = ?", (current_user_id,)) tasks = c.fetchall() conn.close() update_task_list() def delete_task(task_id): conn = sqlite3.connect("todo.db") c = conn.cursor() c.execute("DELETE FROM tasks WHERE id = ? AND user_id = ?", (task_id, current_user_id)) conn.commit() conn.close() # 重新加载任务 nonlocal tasks conn = sqlite3.connect("todo.db") c = conn.cursor() c.execute("SELECT id, content, completed FROM tasks WHERE user_id = ?", (current_user_id,)) tasks = c.fetchall() conn.close() update_task_list() # 构建任务页面UI task_input = ft.TextField(hint_text="输入新任务", expand=True) add_btn = ft.ElevatedButton("添加任务", on_click=add_task) logout_btn = ft.ElevatedButton("退出登录", on_click=lambda e: show_login_page()) page.add(ft.Row([task_input, add_btn]), logout_btn, task_list) update_task_list() page.update() # 启动时显示登录页面 show_login_page() if __name__ == "__main__": ft.app(target=main)
总结
- 任务丢失的核心解决方法是持久化存储:把任务从内存写到文件/数据库,这是前提。
- 用户注册/认证的作用是隔离多用户任务,让每个用户拥有专属的待办清单,同时支持跨设备访问(网页版),但它不能单独解决任务丢失问题。
- 作为新手,建议先完成基础的持久化存储,确保任务不会丢失,再逐步添加用户认证功能。
内容的提问来源于stack exchange,提问作者Answere
相关产品推荐
相关产品推荐

