You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于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)

总结

  1. 任务丢失的核心解决方法是持久化存储:把任务从内存写到文件/数据库,这是前提。
  2. 用户注册/认证的作用是隔离多用户任务,让每个用户拥有专属的待办清单,同时支持跨设备访问(网页版),但它不能单独解决任务丢失问题。
  3. 作为新手,建议先完成基础的持久化存储,确保任务不会丢失,再逐步添加用户认证功能。

内容的提问来源于stack exchange,提问作者Answere

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.27 18:20:30