ADD file via upload

main
hnu202501120115 5 months ago
parent 02abc93254
commit 523f05ffc9

@ -0,0 +1,621 @@
import tkinter as tk
import customtkinter as ctk
from tkinter import messagebox
from datetime import datetime
import tkinter.scrolledtext as scrolledtext
from base_manager import BaseManager
"""
待办事项管理模块
该模块提供了待办事项管理的UI界面包括显示待办事项列表查看待办详情创建新待办标记完成和删除待办等功能
使用tkinter和customtkinter库实现现代化的用户界面
"""
class TodoManager(BaseManager):
"""待办事项管理类
负责待办事项管理界面的创建和交互逻辑包括
1. 显示用户待办事项列表
2. 查看待办事项详细信息
3. 创建新待办事项
4. 标记待办事项为完成
5. 删除待办事项
属性:
app: 主应用程序对象
root: 主窗口对象
auth: 用户认证对象
current_session_id: 当前会话ID
todo_listbox: 待办事项列表框控件
todo_detail_text: 待办事项详情文本框控件
"""
def __init__(self, app):
"""初始化待办事项管理类
Args:
app: 主应用程序对象包含rootauth等核心组件
"""
self.app = app
self.root = app.root # 主窗口
self.auth = app.auth # 用户认证对象
super().__init__(app, app.root) # 调用基类初始化
self.logger = getattr(app, 'logger_manager', None) # 日志记录对象(使用正确的属性名)
self.current_session_id = app.current_session_id # 当前会话ID
self.todo_listbox = None # 待办事项列表框
self.todo_detail_text = None # 待办事项详情文本框
def update_session_id(self, session_id):
"""更新当前会话ID
更新应用程序和当前类实例的会话ID确保操作使用正确的用户上下文
Args:
session_id (str): 新的会话ID
"""
self.app.current_session_id = session_id # 更新应用程序的会话ID
self.current_session_id = session_id # 更新当前实例的会话ID
if session_id and self.logger:
# 获取用户名
username = session_id
if self.auth:
try:
valid, username = self.auth.validate_session(session_id)
if not valid:
username = session_id.split("_")[0] if "_" in session_id else session_id
except Exception as e:
username = session_id.split("_")[0] if "_" in session_id else session_id
self.logger.log("info", f"更新待办事项管理器会话: {session_id}", username)
def show_todos(self, parent_frame=None):
"""显示我的待办事项窗口
创建并显示待办事项管理界面包括
1. 左侧待办事项列表
2. 右侧待办事项详情
3. 创建标记完成和删除待办事项按钮
如果用户未登录显示提示信息
如果用户没有待办事项数据添加示例数据
Args:
parent_frame (CTkFrame, optional): 要显示待办的父框架如果为None则创建新窗口兼容旧代码
"""
try:
# 调用基类的show方法保存聊天历史记录
super().show(parent_frame)
# 决定使用哪个框架作为父容器
if parent_frame:
# 清空父框架中的所有组件
for widget in parent_frame.winfo_children():
widget.destroy()
container = parent_frame
else:
# 兼容旧代码:创建新窗口
container = ctk.CTkToplevel(self.root)
container.title("我的待办事项")
container.geometry("800x600")
container.configure(fg_color="#1e1e1e")
# 检查用户是否登录
current_user_info = self.app.auth.get_current_user(self.app.current_session_id)
if not current_user_info:
ctk.CTkLabel(container, text="请先登录", text_color="#ffffff").pack(pady=20)
if not parent_frame:
container.destroy()
return
# 验证会话有效性
valid, username = self.app.auth.validate_session(self.app.current_session_id)
if not valid:
ctk.CTkLabel(container, text="请先登录", text_color="#ffffff").pack(pady=20)
if not parent_frame:
container.destroy()
return
# 更新当前会话ID
self.update_session_id(self.app.current_session_id)
# 加载用户待办事项
todos = self.app.auth.load_todos(self.app.current_session_id)
# 如果没有待办事项数据,添加一些示例数据
if not todos:
# 创建示例待办事项
example_todos = [
{
"id": "example-1",
"title": "完成项目报告",
"description": "完成季度项目进展报告",
"due_date": "2025-12-15",
"priority": "high",
"status": "pending",
"created_at": datetime.now().isoformat()
},
{
"id": "example-2",
"title": "购买生活用品",
"description": "购买牛奶、鸡蛋、面包",
"due_date": "2025-12-14",
"priority": "medium",
"status": "pending",
"created_at": datetime.now().isoformat()
},
{
"id": "example-3",
"title": "阅读技术文档",
"description": "阅读新的API文档",
"due_date": "2025-12-16",
"priority": "low",
"status": "pending",
"created_at": datetime.now().isoformat()
}
]
# 保存示例待办事项
self.app.auth.save_todos(self.app.current_session_id, example_todos)
todos = example_todos
# 按优先级排序(高->中->低)
priority_order = {"high": 0, "medium": 1, "low": 2}
todos.sort(key=lambda x: priority_order.get(x["priority"], 1))
# 创建左侧待办事项列表区域
left_frame = ctk.CTkFrame(container, width=300, corner_radius=0)
left_frame.pack(side="left", fill="y", padx=(0, 10), pady=10)
# 待办事项列表标题
list_title = ctk.CTkLabel(left_frame, text="我的待办事项", font=ctk.CTkFont(size=18, weight="bold"))
list_title.pack(pady=10)
# 创建待办事项列表
self.todo_listbox = tk.Listbox(left_frame, selectmode=tk.SINGLE, bg="#1e1e1e", fg="#ffffff",
selectbackground="#3a3a3a", selectforeground="#ffffff",
font=ctk.CTkFont(size=27))
self.todo_listbox.pack(fill="both", expand=True, padx=10, pady=(0, 10))
# 添加待办事项到列表
for i, todo in enumerate(todos):
status = "" if todo["status"] == "completed" else "" # 状态图标
priority_color = "🔴" if todo["priority"] == "high" else "🟡" if todo["priority"] == "medium" else "🟢" # 优先级颜色
display_text = f"{status} {priority_color} {i+1}. {todo['title']}"
if todo['due_date']:
display_text += f" - {todo['due_date']}"
self.todo_listbox.insert(tk.END, display_text)
# 创建右侧待办事项详情区域
right_frame = ctk.CTkFrame(container, corner_radius=0)
right_frame.pack(side="right", fill="both", expand=True, padx=(0, 10), pady=10)
# 待办事项详情标题
detail_title = ctk.CTkLabel(right_frame, text="待办事项详情", font=ctk.CTkFont(size=18, weight="bold"))
detail_title.pack(pady=10)
except Exception as e:
# 记录错误日志
username = self._get_username()
if self.logger:
self.logger.log("error", f"显示待办事项界面失败: {e}", username)
messagebox.showerror("错误", f"显示待办事项界面失败: {str(e)}")
# 创建待办事项详情滚动框架(气泡式显示)
self.todo_detail_frame = ctk.CTkScrollableFrame(right_frame, bg_color="#252525", fg_color="#252525",
corner_radius=10)
self.todo_detail_frame.pack(fill="both", expand=True, padx=10, pady=(0, 10))
# 绑定列表选择事件
self.todo_listbox.bind("<<ListboxSelect>>", lambda e: self._on_todo_select(e, todos))
# 创建操作按钮框架
button_frame = ctk.CTkFrame(right_frame)
button_frame.pack(pady=10, padx=10, fill="x")
# 创建新待办按钮
create_button = ctk.CTkButton(button_frame, text="创建新待办",
command=lambda: self._create_new_todo(todo_window),
fg_color="#009688", hover_color="#00796b")
create_button.pack(side="left", expand=True, padx=5)
# 标记为完成按钮
complete_button = ctk.CTkButton(button_frame, text="标记为完成",
command=lambda: self._complete_todo(todos, todo_window),
fg_color="#34a853", hover_color="#2d9d48")
complete_button.pack(side="left", expand=True, padx=5)
# 编辑按钮
edit_button = ctk.CTkButton(button_frame, text="编辑待办",
command=lambda: self._edit_todo(todos, todo_window),
fg_color="#ff9800", hover_color="#f57c00")
edit_button.pack(side="left", expand=True, padx=5)
# 删除待办事项按钮
delete_button = ctk.CTkButton(button_frame, text="删除选中项",
command=lambda: self._delete_todo(todos, todo_window),
fg_color="#d13438", hover_color="#b32d30")
delete_button.pack(side="right", expand=True, padx=5)
def _get_username(self):
"""获取当前会话的用户名
Returns:
str: 用户名或会话ID
"""
if not self.current_session_id:
return "unknown"
username = self.current_session_id
if self.auth:
try:
valid, username = self.auth.validate_session(self.current_session_id)
if not valid:
username = self.current_session_id.split("_")[0] if "_" in self.current_session_id else self.current_session_id
except Exception as e:
username = self.current_session_id.split("_")[0] if "_" in self.current_session_id else self.current_session_id
return username
def _create_new_todo(self, parent_window):
"""创建新待办事项的弹窗
创建一个模态窗口用于输入新待办事项的详细信息包括
- 标题
- 描述
- 截止日期
- 优先级
Args:
parent_window: 父窗口对象用于在创建成功后刷新待办事项列表
"""
# 创建新窗口
create_window = ctk.CTkToplevel(parent_window)
create_window.title("创建新待办事项")
create_window.geometry("500x500")
create_window.configure(fg_color="#1e1e1e")
create_window.transient(parent_window) # 设置为父窗口的子窗口
create_window.grab_set() # 模态窗口,阻止操作其他窗口
# 标题标签和输入框
title_label = ctk.CTkLabel(create_window, text="标题:", font=ctk.CTkFont(size=14))
title_label.pack(pady=(20, 5), padx=20, anchor="w")
title_entry = ctk.CTkEntry(create_window, width=400, height=40, font=ctk.CTkFont(size=14))
title_entry.pack(pady=(0, 15), padx=20)
# 描述标签和文本框
desc_label = ctk.CTkLabel(create_window, text="描述:", font=ctk.CTkFont(size=14))
desc_label.pack(pady=(10, 5), padx=20, anchor="w")
desc_text = scrolledtext.ScrolledText(create_window, width=50, height=10,
bg="#252525", fg="#ffffff",
font=ctk.CTkFont(size=14), wrap="word")
desc_text.pack(pady=(0, 15), padx=20)
# 截止日期标签和输入框
due_label = ctk.CTkLabel(create_window, text="截止日期 (YYYY-MM-DD):", font=ctk.CTkFont(size=14))
due_label.pack(pady=(10, 5), padx=20, anchor="w")
due_entry = ctk.CTkEntry(create_window, width=400, height=40, font=ctk.CTkFont(size=14))
due_entry.insert(0, datetime.now().strftime("%Y-%m-%d")) # 默认当前日期
due_entry.pack(pady=(0, 15), padx=20)
# 优先级标签和下拉菜单
priority_label = ctk.CTkLabel(create_window, text="优先级:", font=ctk.CTkFont(size=14))
priority_label.pack(pady=(10, 5), padx=20, anchor="w")
priority_var = tk.StringVar(value="medium") # 默认中优先级
priority_option = ctk.CTkOptionMenu(create_window, width=400, height=40, font=ctk.CTkFont(size=14),
variable=priority_var, values=["high", "medium", "low"])
priority_option.pack(pady=(0, 25), padx=20)
# 保存按钮的回调函数
def save_todo():
# 获取输入数据
title = title_entry.get()
description = desc_text.get(1.0, tk.END).strip()
due_date = due_entry.get()
priority = priority_var.get()
# 验证必填字段
if not title:
messagebox.showerror("错误", "标题不能为空")
return
if due_date:
try:
# 验证日期格式
datetime.strptime(due_date, "%Y-%m-%d")
except ValueError:
messagebox.showerror("错误", "日期格式不正确请使用YYYY-MM-DD格式")
return
# 创建待办事项数据
todo_data = {
"title": title,
"description": description,
"due_date": due_date,
"priority": priority
}
# 保存待办事项
success, todo = self.auth.create_todo(self.current_session_id, todo_data)
if success:
messagebox.showinfo("成功", "待办事项创建成功")
create_window.destroy()
# 重新加载待办事项列表
parent_window.destroy()
self.show_todos()
else:
messagebox.showerror("错误", "待办事项创建失败")
# 创建保存按钮
save_button = ctk.CTkButton(create_window, text="保存待办事项",
command=save_todo, fg_color="#009688", hover_color="#00796b")
save_button.pack(pady=10, padx=20, fill="x")
def _edit_todo(self, todos, parent_window):
"""编辑待办事项的弹窗
创建一个模态窗口用于编辑现有待办事项的详细信息包括
- 标题
- 描述
- 截止日期
- 优先级
Args:
todos: 待办事项列表数据
parent_window: 父窗口对象用于在编辑成功后刷新待办事项列表
"""
# 获取选中项的索引
selection = self.todo_listbox.curselection()
if not selection:
messagebox.showinfo("提示", "请先选择一个要编辑的待办事项")
return
index = selection[0]
todo = todos[index]
# 创建新窗口
edit_window = ctk.CTkToplevel(parent_window)
edit_window.title("编辑待办事项")
edit_window.geometry("500x500")
edit_window.configure(fg_color="#1e1e1e")
edit_window.transient(parent_window) # 设置为父窗口的子窗口
edit_window.grab_set() # 模态窗口,阻止操作其他窗口
# 标题标签和输入框
title_label = ctk.CTkLabel(edit_window, text="标题:", font=ctk.CTkFont(size=14))
title_label.pack(pady=(20, 5), padx=20, anchor="w")
title_entry = ctk.CTkEntry(edit_window, width=400, height=40, font=ctk.CTkFont(size=14))
title_entry.insert(0, todo["title"]) # 预先填充现有标题
title_entry.pack(pady=(0, 15), padx=20)
# 描述标签和文本框
desc_label = ctk.CTkLabel(edit_window, text="描述:", font=ctk.CTkFont(size=14))
desc_label.pack(pady=(10, 5), padx=20, anchor="w")
desc_text = scrolledtext.ScrolledText(edit_window, width=50, height=10,
bg="#252525", fg="#ffffff",
font=ctk.CTkFont(size=14), wrap="word")
desc_text.insert(1.0, todo["description"]) # 预先填充现有描述
desc_text.pack(pady=(0, 15), padx=20)
# 截止日期标签和输入框
due_label = ctk.CTkLabel(edit_window, text="截止日期 (YYYY-MM-DD):", font=ctk.CTkFont(size=14))
due_label.pack(pady=(10, 5), padx=20, anchor="w")
due_entry = ctk.CTkEntry(edit_window, width=400, height=40, font=ctk.CTkFont(size=14))
due_entry.insert(0, todo["due_date"] if todo["due_date"] else "") # 预先填充现有截止日期
due_entry.pack(pady=(0, 15), padx=20)
# 优先级标签和下拉菜单
priority_label = ctk.CTkLabel(edit_window, text="优先级:", font=ctk.CTkFont(size=14))
priority_label.pack(pady=(10, 5), padx=20, anchor="w")
priority_var = tk.StringVar(value=todo["priority"]) # 预先选择现有优先级
priority_option = ctk.CTkOptionMenu(edit_window, width=400, height=40, font=ctk.CTkFont(size=14),
variable=priority_var, values=["high", "medium", "low"])
priority_option.pack(pady=(0, 25), padx=20)
# 保存按钮的回调函数
def save_todo():
# 获取输入数据
title = title_entry.get()
description = desc_text.get(1.0, tk.END).strip()
due_date = due_entry.get()
priority = priority_var.get()
# 验证必填字段
if not title:
messagebox.showerror("错误", "标题不能为空")
return
if due_date:
try:
# 验证日期格式
datetime.strptime(due_date, "%Y-%m-%d")
except ValueError:
messagebox.showerror("错误", "日期格式不正确请使用YYYY-MM-DD格式")
return
# 创建待办事项数据
todo_data = {
"title": title,
"description": description,
"due_date": due_date,
"priority": priority
}
# 更新待办事项
success = self.auth.update_todo(self.current_session_id, todo["id"], todo_data)
if success:
messagebox.showinfo("成功", "待办事项编辑成功")
edit_window.destroy()
# 重新加载待办事项列表
if content_parent:
self.show_todos(content_parent)
else:
parent_window.destroy()
self.show_todos()
else:
messagebox.showerror("错误", "待办事项编辑失败")
# 创建保存按钮
save_button = ctk.CTkButton(edit_window, text="保存修改",
command=save_todo, fg_color="#ff9800", hover_color="#f57c00")
save_button.pack(pady=10, padx=20, fill="x")
def _on_todo_select(self, event, todos):
"""待办事项列表选择事件处理
当待办事项列表中的项被选中时在右侧详情区域显示该待办事项的详细信息
Args:
event: 事件对象
todos: 待办事项列表数据
"""
# 获取选中项的索引
selection = self.todo_listbox.curselection()
if selection:
index = selection[0]
todo = todos[index]
# 清空当前详情框架中的所有组件
for widget in self.todo_detail_frame.winfo_children():
widget.destroy()
# 转换状态和优先级为中文显示
status = "已完成" if todo["status"] == "completed" else "待处理"
priority = "" if todo["priority"] == "high" else "" if todo["priority"] == "medium" else ""
# 待办事项参数列表
todo_params = [
("标题", todo['title']),
("状态", status),
("优先级", priority),
("截止日期", todo['due_date']),
("描述", todo['description']),
("创建时间", todo['created_at'])
]
# 创建气泡式显示
for param_name, param_value in todo_params:
if param_value: # 只显示有值的参数
# 创建参数名称气泡(左对齐,浅灰色)
param_name_frame = ctk.CTkFrame(self.todo_detail_frame, fg_color="#3a3a3a", corner_radius=10)
param_name_frame.pack(fill="x", padx=10, pady=(10, 0), anchor="w")
param_name_label = ctk.CTkLabel(param_name_frame, text=param_name, font=ctk.CTkFont(size=14, weight="bold"),
fg_color="transparent", text_color="#ffffff")
param_name_label.pack(padx=10, pady=5)
# 创建参数值气泡(右对齐,深灰色)
param_value_frame = ctk.CTkFrame(self.todo_detail_frame, fg_color="#4a4a4a", corner_radius=10)
param_value_frame.pack(fill="x", padx=10, pady=(0, 10), anchor="e")
param_value_label = ctk.CTkLabel(param_value_frame, text=param_value, font=ctk.CTkFont(size=14),
fg_color="transparent", text_color="#ffffff")
param_value_label.pack(padx=10, pady=5)
def _complete_todo(self, todos, window, content_parent=None):
"""标记待办事项为完成
将用户选中的待办事项标记为已完成状态并重新加载待办事项列表
Args:
todos: 待办事项列表数据
window: 父窗口对象
content_parent: 内容父框架用于在操作完成后刷新待办事项列表
"""
try:
# 获取选中项的索引
selection = self.todo_listbox.curselection()
if not selection:
messagebox.showinfo("提示", "请先选择一个要标记为已完成的待办事项")
return
index = selection[0]
todo = todos[index]
todo_id = todo['id']
# 确认标记为完成
if messagebox.askyesno("确认", f"确定要标记待办事项 '{todo['title']}' 为完成吗?"):
# 调用认证对象的标记完成方法
success = self.auth.complete_todo(self.app.current_session_id, todo_id)
if success:
# 记录日志
username = self._get_username()
if self.logger:
self.logger.log("info", f"标记待办事项为已完成: {todo['title']}", username)
messagebox.showinfo("成功", "待办事项已标记为完成")
# 重新加载待办事项列表
if content_parent:
self.show_todos(content_parent)
else:
window.destroy()
self.show_todos()
else:
# 记录日志
username = self._get_username()
if self.logger:
self.logger.log("error", f"标记待办事项为已完成失败: {todo['title']}", username)
messagebox.showerror("错误", "操作失败")
except Exception as e:
# 记录错误日志
username = self._get_username()
if self.logger:
self.logger.log("error", f"标记待办事项为已完成时发生错误: {e}", username)
messagebox.showerror("错误", f"标记待办事项为已完成时发生错误: {str(e)}")
def _delete_todo(self, todos, window, content_parent=None):
"""删除选中的待办事项
删除用户选中的待办事项并重新加载待办事项列表
Args:
todos: 待办事项列表数据
window: 父窗口对象
content_parent: 内容父框架用于在删除后刷新待办事项列表
"""
try:
# 获取选中项的索引
selection = self.todo_listbox.curselection()
if not selection:
messagebox.showinfo("提示", "请先选择一个要删除的待办事项")
return
index = selection[0]
todo = todos[index]
todo_id = todo['id']
# 确认删除
if messagebox.askyesno("确认", f"确定要删除待办事项 '{todo['title']}' 吗?"):
# 调用认证对象的删除待办事项方法
success = self.auth.delete_todo(self.app.current_session_id, todo_id)
if success:
# 记录日志
username = self._get_username()
if self.logger:
self.logger.log("info", f"删除待办事项成功: {todo['title']}", username)
messagebox.showinfo("成功", "待办事项删除成功")
# 重新加载待办事项列表
if content_parent:
self.show_todos(content_parent)
else:
window.destroy()
self.show_todos()
else:
# 记录日志
username = self._get_username()
if self.logger:
self.logger.log("error", f"删除待办事项失败: {todo['title']}", username)
messagebox.showerror("错误", "删除失败")
except Exception as e:
# 记录错误日志
username = self._get_username()
if self.logger:
self.logger.log("error", f"删除待办事项时发生错误: {e}", username)
messagebox.showerror("错误", f"删除待办事项时发生错误: {str(e)}")
Loading…
Cancel
Save