# -*- coding: utf-8 -*-
import tkinter as tk
from tkinter import ttk, messagebox
import json
import os
from datetime import datetime, timedelta
import threading
import sys
class SmartStickyNote:
def __init__(self, root):
self.root = root
self.root.title("智能便签")
# 初始化设置
self.settings = {
'view_mode': 'all', # 视图模式: all/pinned/completed
'theme': 'light', # 主题: light/dark
'opacity': 0.9, # 窗口不透明度: 0.0-1.0
'font_size': 10, # 默认字体大小
'sort_by': 'created' # 排序方式: created/modified/urgency
}
# 初始化颜色方案
self.init_colors()
# 初始化样式
self.setup_styles()
# 初始化数据
self.notes = []
self.recycle_bin = []
# 创建UI元素
self.create_widgets()
# 加载保存的数据
self.load_data()
# 渲染笔记列表
self.render_notes()
# 启动后台任务
self.start_background_tasks()
def init_colors(self):
"""初始化颜色方案"""
# 浅色主题
self.bg_color = '#FFFFFF' # 背景色
self.secondary_color = '#F5F5F5' # 次要背景色
self.text_color = '#000000' # 文本颜色
self.accent_color = '#42A5F5' # 强调色
# 可以根据主题切换颜色
if hasattr(self, 'settings') and self.settings.get('theme') == 'dark':
self.bg_color = '#212121'
self.secondary_color = '#424242'
self.text_color = '#FFFFFF'
self.accent_color = '#BB86FC'
def setup_styles(self):
"""配置自定义样式"""
style = ttk.Style()
# 基础样式
style.theme_use('clam') # 使用一个支持样式修改的主题
# 紧急程度按钮样式
urgency_colors = {
0: ('#E8F5E9', '低'), # 绿色
1: ('#E3F2FD', '普通'), # 蓝色
2: ('#FFE0B2', '重要'), # 橙色
3: ('#FFCDD2', '紧急') # 红色
}
for level, (color, text) in urgency_colors.items():
style.configure(
f'Urgency.TButton.{level}',
background=color,
foreground='black',
font=('Microsoft YaHei', 8),
padding=2,
borderwidth=1,
relief='raised'
)
style.map(
f'Urgency.TButton.{level}',
background=[('active', color), ('disabled', '#F5F5F5')],
relief=[('pressed', 'sunken'), ('!pressed', 'raised')]
)
# 框架样式
style.configure('Note.TFrame',
background=self.bg_color,
bordercolor='#E0E0E0',
lightcolor='#F5F5F5',
darkcolor='#E0E0E0')
style.configure('Pinned.TFrame',
background='#FFF9C4',
bordercolor='#FFD54F')
style.configure('Completed.TFrame',
background='#E8F5E9',
bordercolor='#A5D6A7')
style.configure('Deleted.TFrame',
background='#F5F5F5',
bordercolor='#BDBDBD')
style.configure('UrgentFlash.TFrame',
background='#FFEBEE',
bordercolor='#EF9A9A')
# 按钮样式
style.configure('Accent.TButton',
font=('Microsoft YaHei', 9, 'bold'),
foreground='#FFFFFF',
background='#42A5F5',
padding=5)
style.map('Accent.TButton',
background=[('active', '#1E88E5'), ('disabled', '#BBDEFB')])
# 标签样式
style.configure('Title.TLabel',
font=('Microsoft YaHei', 10, 'bold'),
foreground='#212121')
style.configure('Subtitle.TLabel',
font=('Microsoft YaHei', 8),
foreground='#616161')
def load_data(self):
"""加载保存的数据"""
try:
if os.path.exists(self.data_file):
with open(self.data_file, 'r', encoding='utf-8') as f:
data = json.load(f)
self.notes = data.get('notes', [])
self.settings = {**self.settings, **data.get('settings', {})}
if os.path.exists(self.recycle_file):
with open(self.recycle_file, 'r', encoding='utf-8') as f:
self.recycle_bin = json.load(f)
except Exception as e:
messagebox.showerror("错误", f"加载数据失败: {str(e)}")
try:
with open('notes_data.json', 'r') as f:
data = json.load(f)
self.notes = data.get('notes', [])
self.recycle_bin = data.get('recycle_bin', [])
# 合并设置,保留默认值
self.settings = {**self.settings, **data.get('settings', {})}
except FileNotFoundError:
# 文件不存在时使用默认设置
pass
def save_data(self):
"""保存数据到文件"""
data = {
'notes': self.notes,
'recycle_bin': self.recycle_bin,
'settings': self.settings
}
try:
with open(self.data_file, 'w', encoding='utf-8') as f:
json.dump(data, f, ensure_ascii=False, indent=2)
with open(self.recycle_file, 'w', encoding='utf-8') as f:
json.dump(self.recycle_bin, f, ensure_ascii=False, indent=2)
except Exception as e:
messagebox.showerror("错误", f"保存数据失败: {str(e)}")
def create_widgets(self):
"""创建界面组件"""
# 主框架
self.main_frame = ttk.Frame(self.root)
self.main_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
# 工具栏
self.create_toolbar()
# 笔记列表容器
self.create_notes_list()
# 新建笔记表单 (初始隐藏)
self.create_new_note_form()
# 设置窗口透明度
self.root.attributes('-alpha', self.settings['opacity'])
# 设置窗口置顶
self.root.attributes('-topmost', self.settings['always_on_top'])
def create_toolbar(self):
"""创建工具栏"""
toolbar = ttk.Frame(self.root, padding=5)
toolbar.pack(fill=tk.X)
# 不透明度控制
opacity_frame = ttk.Frame(toolbar)
opacity_frame.pack(side=tk.RIGHT, padx=5)
ttk.Label(opacity_frame, text="不透明度:").pack(side=tk.LEFT)
# 使用 get() 方法获取 opacity 设置,提供默认值
self.opacity = tk.DoubleVar(value=self.settings.get('opacity', 0.9))
opacity_slider = ttk.Scale(
opacity_frame,
from_=0.3,
to=1.0,
variable=self.opacity,
command=lambda v: self.root.attributes('-alpha', float(v))
)
opacity_slider.pack(side=tk.LEFT, padx=5)
# 搜索框
search_frame = ttk.Frame(toolbar)
search_frame.pack(side=tk.LEFT, padx=5, fill=tk.X, expand=True)
self.search_var = tk.StringVar()
search_entry = ttk.Entry(
search_frame,
textvariable=self.search_var,
style='Search.TEntry'
)
search_entry.pack(side=tk.LEFT, fill=tk.X, expand=True)
search_entry.bind('<KeyRelease>', lambda e: self.render_notes())
search_btn = ttk.Button(
search_frame,
text="🔍",
width=3,
command=self.show_advanced_search
)
search_btn.pack(side=tk.LEFT, padx=(2, 0))
# 视图模式选择
self.view_mode = tk.StringVar(value=self.settings['view_mode'])
view_options = ttk.Combobox(
toolbar,
textvariable=self.view_mode,
values=['active', 'pinned', 'all'],
width=8,
state='readonly'
)
view_options.pack(side=tk.LEFT, padx=2)
view_options.bind('<<ComboboxSelected>>', self.change_view_mode)
# 历史记录按钮
history_btn = ttk.Button(
toolbar,
text="📅",
width=3,
command=lambda: self.show_history('week')
)
history_btn.pack(side=tk.LEFT, padx=2)
# 新建按钮
new_btn = ttk.Button(
toolbar,
text="+ 新建",
style='Accent.TButton',
command=self.show_new_note_form
)
new_btn.pack(side=tk.RIGHT, padx=2)
# 透明度滑块
self.opacity = tk.DoubleVar(value=self.settings['opacity'])
opacity_slider = ttk.Scale(
toolbar,
from_=0.5,
to=1.0,
variable=self.opacity,
command=self.change_opacity,
length=80
)
opacity_slider.pack(side=tk.RIGHT, padx=5)
ttk.Label(toolbar, text="透明度:").pack(side=tk.RIGHT)
# 置顶按钮
self.topmost_btn = ttk.Button(
toolbar,
text="📌" if self.settings['always_on_top'] else "📋",
width=3,
command=self.toggle_topmost
)
self.topmost_btn.pack(side=tk.RIGHT, padx=2)
def create_notes_list(self):
"""创建笔记列表区域"""
# 笔记列表容器
self.notes_canvas = tk.Canvas(
self.main_frame,
bg=self.bg_color,
highlightthickness=0
)
self.notes_canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
# 滚动条
scrollbar = ttk.Scrollbar(
self.main_frame,
orient=tk.VERTICAL,
command=self.notes_canvas.yview
)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
self.notes_canvas.configure(yscrollcommand=scrollbar.set)
self.notes_canvas.bind('<Configure>', lambda e: self.notes_canvas.configure(
scrollregion=self.notes_canvas.bbox("all")
))
# 笔记框架容器
self.notes_frame = ttk.Frame(self.notes_canvas)
self.notes_canvas.create_window((0, 0), window=self.notes_frame, anchor="nw")
def create_new_note_form(self):
"""创建新建笔记表单"""
self.new_note_frame = ttk.LabelFrame(
self.main_frame,
text="新建笔记",
padding=10
)
# 标题输入框
ttk.Label(self.new_note_frame, text="标题:").pack(anchor=tk.W, padx=5, pady=2)
self.note_title = ttk.Entry(self.new_note_frame)
self.note_title.pack(fill=tk.X, padx=5, pady=2)
# 内容文本框
ttk.Label(self.new_note_frame, text="内容:").pack(anchor=tk.W, padx=5, pady=2)
self.note_content = tk.Text(
self.new_note_frame,
height=5,
wrap=tk.WORD,
bg=self.secondary_color,
fg=self.text_color,
insertbackground=self.text_color,
relief=tk.FLAT,
padx=5,
pady=5
)
self.note_content.pack(fill=tk.BOTH, expand=True, padx=5, pady=2)
# 紧急程度选择
urgency_frame = ttk.Frame(self.new_note_frame)
urgency_frame.pack(fill=tk.X, padx=5, pady=5)
ttk.Label(urgency_frame, text="紧急程度:").pack(side=tk.LEFT)
self.urgency = tk.IntVar(value=1) # 默认普通
urgency_levels = [
("低", 0),
("普通", 1),
("重要", 2),
("紧急", 3)
]
for text, level in urgency_levels:
btn = ttk.Radiobutton(
urgency_frame,
text=text,
variable=self.urgency,
value=level
)
btn.pack(side=tk.LEFT, padx=2)
# 按钮区域
btn_frame = ttk.Frame(self.new_note_frame)
btn_frame.pack(fill=tk.X, padx=5, pady=5)
ttk.Button(
btn_frame,
text="取消",
command=self.hide_new_note_form
).pack(side=tk.RIGHT, padx=2)
ttk.Button(
btn_frame,
text="保存",
style='Accent.TButton',
command=self.save_new_note
).pack(side=tk.RIGHT, padx=2)
def start_timers(self):
"""启动定时检查任务"""
self.root.after(1000, self.check_note_status)
self.root.after(600000, self.check_note_status) # 每10分钟检查一次
self.root.after(3600000, self.check_urgent_tasks) # 每小时检查紧急任务
self.root.after(86400000, self.empty_recycle_bin) # 每天检查回收站
def render_notes(self):
"""渲染笔记列表"""
for widget in self.notes_frame.winfo_children():
widget.destroy()
filtered_notes = self.filter_notes()
if not filtered_notes:
ttk.Label(
self.notes_frame,
text="暂无笔记" if self.view_mode.get() != 'pinned' else "无固定笔记",
font=('Microsoft YaHei', 10, 'italic')
).pack(pady=20)
return
filtered_notes.sort(key=lambda x: (
-x.get('pinned', False),
-x['urgency'],
x['created_at']
))
for note in filtered_notes:
self.create_note_widget(note)
self.notes_frame.update_idletasks()
self.notes_canvas.config(scrollregion=self.notes_canvas.bbox("all"))
def filter_notes(self):
"""根据视图模式和搜索条件过滤笔记"""
view_mode = self.view_mode.get()
search_text = self.search_var.get().lower()
filtered = []
for note in self.notes:
if note['status'] == 'completed' and view_mode != 'all':
continue
if view_mode == 'pinned' and not note.get('pinned', False):
continue
if search_text and search_text not in note['title'].lower() and search_text not in note['content'].lower():
continue
filtered.append(note)
return filtered
def create_note_widget(self, note):
"""创建笔记组件"""
urgency_colors = {
0: ('#E8F5E9', '低'), # 绿色
1: ('#E3F2FD', '普通'), # 蓝色
2: ('#FFE0B2', '重要'), # 橙色
3: ('#FFCDD2', '紧急') # 红色
}
status_styles = {
'pending': ('○', '#FFFFFF'),
'ongoing': ('◔', '#F8F8F8'),
'completed': ('✓', '#E8F5E9')
}
status_icon, bg_color = status_styles.get(note['status'], ('?', '#FFFFFF'))
urgency_color, urgency_text = urgency_colors.get(note['urgency'], ('#E3F2FD', '普通'))
frame_style = 'Pinned.TFrame' if note.get('pinned', False) else 'Note.TFrame'
if note['status'] == 'completed':
frame_style = 'Completed.TFrame'
frame = ttk.Frame(
self.notes_frame,
borderwidth=2,
relief=tk.RAISED,
padding=(10, 8),
style=frame_style
)
frame.pack(fill=tk.X, pady=5, padx=2)
# 标题区域
title_frame = ttk.Frame(frame)
title_frame.pack(fill=tk.X, pady=(0, 5))
# 状态和紧急程度
status_label = ttk.Label(
title_frame,
text=status_icon,
font=('Arial', 12),
width=2
)
status_label.pack(side=tk.LEFT, padx=(0, 5))
urgency_label = ttk.Label(
title_frame,
text=urgency_text,
font=('Microsoft YaHei', 8, 'bold'),
background=urgency_color,
foreground='black',
padding=(3, 0),
borderwidth=1,
relief=tk.SOLID
)
urgency_label.pack(side=tk.LEFT, padx=(0, 5))
# 笔记标题
title_label = ttk.Label(
title_frame,
text=note['title'] or '无标题',
font=('Microsoft YaHei', 10, 'bold'),
anchor=tk.W
)
title_label.pack(side=tk.LEFT, fill=tk.X, expand=True)
# 内容区域
content_frame = ttk.Frame(frame)
content_frame.pack(fill=tk.X, pady=(0, 8))
# 动态高度的文本框
content = tk.Text(
content_frame,
wrap=tk.WORD,
font=('Microsoft YaHei', 9),
background=bg_color,
foreground=self.text_color,
borderwidth=0,
highlightthickness=0,
padx=5,
pady=3,
height=1
)
content.insert(tk.END, note['content'])
content.config(state=tk.DISABLED)
content.pack(fill=tk.BOTH, expand=True)
# 动态调整高度
line_count = content.count('1.0', 'end', 'displaylines')[0]
content.configure(height=min(max(line_count, 1), 10))
# 元信息区域
meta_frame = ttk.Frame(frame)
meta_frame.pack(fill=tk.X)
# 创建时间
created_time = datetime.fromisoformat(note['created_at']).strftime('%m/%d %H:%M')
ttk.Label(
meta_frame,
text=f"🕒 {created_time}",
font=('TkDefaultFont', 8),
foreground='#666666'
).pack(side=tk.LEFT)
# 操作按钮区域
btn_frame = ttk.Frame(frame)
btn_frame.pack(fill=tk.X, pady=(5, 0))
# 左侧按钮组 - 紧急程度调整
urgency_frame = ttk.Frame(btn_frame)
urgency_frame.pack(side=tk.LEFT, fill=tk.X, expand=True)
for level, (color, text) in urgency_colors.items():
btn = ttk.Button(
urgency_frame,
text=text,
style=f'Urgency.TButton.{level}',
command=lambda l=level, nid=note['id']: self.change_urgency(nid, l)
)
btn.pack(side=tk.LEFT, padx=1)
# 右侧按钮组 - 操作按钮
action_frame = ttk.Frame(btn_frame)
action_frame.pack(side=tk.RIGHT)
# 固定按钮
pin_btn = ttk.Button(
action_frame,
text="📍" if note.get('pinned', False) else "📌",
width=3,
command=lambda nid=note['id']: self.toggle_pin(nid)
)
pin_btn.pack(side=tk.LEFT, padx=2)
# 状态切换按钮
status_btn = ttk.Button(
action_frame,
text="✓" if note['status'] != 'completed' else "↩",
width=3,
command=lambda nid=note['id']: self.toggle_note_status(nid)
)
status_btn.pack(side=tk.LEFT, padx=2)
# 删除按钮
del_btn = ttk.Button(
action_frame,
text="🗑",
width=3,
command=lambda nid=note['id']: self.delete_note(nid)
)
del_btn.pack(side=tk.LEFT, padx=2)
# 编辑按钮
edit_btn = ttk.Button(
action_frame,
text="✏️",
width=3,
command=lambda nid=note['id']: self.edit_note(nid)
)
edit_btn.pack(side=tk.LEFT, padx=2)
# 为笔记组件添加ID标识
frame.note_id = note['id']
def edit_note(self, note_id):
"""编辑现有笔记"""
note = next((n for n in self.notes if n['id'] == note_id), None)
if not note:
return
# 创建编辑窗口
edit_window = tk.Toplevel(self.root)
edit_window.title("编辑笔记")
edit_window.geometry("400x500")
# 标题输入框
ttk.Label(edit_window, text="标题:").pack(anchor=tk.W, padx=5, pady=2)
title_entry = ttk.Entry(edit_window)
title_entry.insert(0, note['title'])
title_entry.pack(fill=tk.X, padx=5, pady=2)
# 内容文本框
ttk.Label(edit_window, text="内容:").pack(anchor=tk.W, padx=5, pady=2)
content_text = tk.Text(
edit_window,
height=10,
wrap=tk.WORD,
bg=self.secondary_color,
fg=self.text_color,
insertbackground=self.text_color,
padx=5,
pady=5
)
content_text.insert(tk.END, note['content'])
content_text.pack(fill=tk.BOTH, expand=True, padx=5, pady=2)
# 紧急程度选择
urgency_frame = ttk.Frame(edit_window)
urgency_frame.pack(fill=tk.X, padx=5, pady=5)
ttk.Label(urgency_frame, text="紧急程度:").pack(side=tk.LEFT)
urgency_var = tk.IntVar(value=note['urgency'])
urgency_levels = [
("低", 0),
("普通", 1),
("重要", 2),
("紧急", 3)
]
for text, level in urgency_levels:
btn = ttk.Radiobutton(
urgency_frame,
text=text,
variable=urgency_var,
value=level
)
btn.pack(side=tk.LEFT, padx=2)
# 按钮区域
btn_frame = ttk.Frame(edit_window)
btn_frame.pack(fill=tk.X, padx=5, pady=5)
def save_changes():
"""保存编辑内容"""
note['title'] = title_entry.get().strip()
note['content'] = content_text.get("1.0", tk.END).strip()
note['urgency'] = urgency_var.get()
note['modified_at'] = datetime.now().isoformat()
self.save_data()
self.render_notes()
edit_window.destroy()
messagebox.showinfo("提示", "笔记已更新")
ttk.Button(
btn_frame,
text="保存",
style='Accent.TButton',
command=save_changes
).pack(side=tk.RIGHT, padx=2)
ttk.Button(
btn_frame,
text="取消",
command=edit_window.destroy
).pack(side=tk.RIGHT, padx=2)
def delete_note(self, note_id):
"""删除笔记到回收站"""
if not messagebox.askyesno("确认", "确定要删除这个笔记吗?"):
return
for i, note in enumerate(self.notes):
if note['id'] == note_id:
deleted_note = self.notes.pop(i)
deleted_note['deleted_at'] = datetime.now().isoformat()
self.recycle_bin.append(deleted_note)
break
self.save_data()
self.render_notes()
messagebox.showinfo("提示", "笔记已移到回收站")
def show_new_note_form(self):
"""显示新建笔记表单"""
self.new_note_frame.pack(fill=tk.BOTH, expand=True, pady=5)
self.note_title.focus()
def hide_new_note_form(self):
"""隐藏新建笔记表单"""
self.new_note_frame.pack_forget()
self.note_title.delete(0, tk.END)
self.note_content.delete('1.0', tk.END)
self.urgency.set(1) # 重置为普通
def save_new_note(self):
"""保存新笔记"""
title = self.note_title.get().strip()
content = self.note_content.get('1.0', tk.END).strip()
urgency = self.urgency.get()
if not title and not content:
messagebox.showwarning("提示", "请输入标题或内容")
return
new_note = {
'id': str(datetime.now().timestamp()),
'title': title or "无标题",
'content': content,
'urgency': urgency,
'status': 'pending',
'created_at': datetime.now().isoformat(),
'modified_at': None,
'completed_at': None,
'pinned': False
}
self.notes.append(new_note)
self.save_data()
self.hide_new_note_form()
self.render_notes()
messagebox.showinfo("提示", "笔记已保存")
def toggle_pin(self, note_id):
"""切换固定状态"""
for note in self.notes:
if note['id'] == note_id:
note['pinned'] = not note.get('pinned', False)
break
self.save_data()
self.render_notes()
def toggle_note_status(self, note_id):
"""切换笔记完成状态"""
for note in self.notes:
if note['id'] == note_id:
if note['status'] == 'completed':
note['status'] = 'pending'
note['completed_at'] = None
else:
note['status'] = 'completed'
note['completed_at'] = datetime.now().isoformat()
break
self.save_data()
self.render_notes()
def change_urgency(self, note_id, urgency_level):
"""修改笔记紧急程度"""
for note in self.notes:
if note['id'] == note_id:
note['urgency'] = urgency_level
break
self.save_data()
self.render_notes()
def change_view_mode(self, event=None):
"""改变视图模式"""
self.settings['view_mode'] = self.view_mode.get()
self.save_data()
self.render_notes()
def show_history(self, period='week'):
"""显示历史记录窗口"""
history_window = tk.Toplevel(self.root)
history_window.title(f"{period.capitalize()}历史记录")
history_window.geometry("500x600")
# 创建时间范围
now = datetime.now()
if period == 'week':
cutoff = now - timedelta(weeks=1)
elif period == 'month':
cutoff = now - timedelta(days=30)
elif period == 'year':
cutoff = now - timedelta(days=365)
else:
cutoff = now - timedelta(weeks=1)
# 过滤历史笔记
history_notes = [
n for n in self.notes + self.recycle_bin
if n.get('completed_at') or n.get('deleted_at')
]
# 按时间排序
history_notes.sort(key=lambda x: x.get('completed_at') or x.get('deleted_at'), reverse=True)
# 创建容器
canvas = tk.Canvas(history_window, bg=self.bg_color, highlightthickness=0)
canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
scrollbar = ttk.Scrollbar(history_window, orient=tk.VERTICAL, command=canvas.yview)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
canvas.configure(yscrollcommand=scrollbar.set)
canvas.bind('<Configure>', lambda e: canvas.configure(scrollregion=canvas.bbox("all")))
history_frame = ttk.Frame(canvas)
canvas.create_window((0, 0), window=history_frame, anchor="nw")
# 时间段选择
period_frame = ttk.Frame(history_window)
period_frame.pack(fill=tk.X, padx=5, pady=5)
ttk.Button(
period_frame,
text="最近一周",
command=lambda: self.update_history_view(history_window, 'week')
).pack(side=tk.LEFT, padx=2)
ttk.Button(
period_frame,
text="最近一月",
command=lambda: self.update_history_view(history_window, 'month')
).pack(side=tk.LEFT, padx=2)
ttk.Button(
period_frame,
text="最近一年",
command=lambda: self.update_history_view(history_window, 'year')
).pack(side=tk.LEFT, padx=2)
# 渲染历史笔记
for note in history_notes:
self.create_history_note_widget(history_frame, note)
def create_history_note_widget(self, parent, note):
"""创建历史笔记组件"""
# 样式配置
bg_color = '#F0F0F0' if note.get('deleted_at') else '#E8F5E9'
border_color = '#D0D0D0' if note.get('deleted_at') else '#C8E6C9'
frame = ttk.Frame(
parent,
borderwidth=2,
relief=tk.RAISED,
padding=5,
style='Deleted.TFrame' if note.get('deleted_at') else 'Completed.TFrame'
)
frame.pack(fill=tk.X, pady=2, padx=2)
# 标题区域
title_frame = ttk.Frame(frame)
title_frame.pack(fill=tk.X)
# 状态图标
status_icon = "🗑" if note.get('deleted_at') else "✓"
ttk.Label(
title_frame,
text=status_icon,
font=('Arial', 12)
).pack(side=tk.LEFT, padx=(0, 5))
# 笔记标题
ttk.Label(
title_frame,
text=note['title'] or "无标题",
font=('Microsoft YaHei', 10, 'bold'),
anchor=tk.W
).pack(side=tk.LEFT, fill=tk.X, expand=True)
# 时间信息
time_frame = ttk.Frame(frame)
time_frame.pack(fill=tk.X, pady=(2, 0))
created_time = datetime.fromisoformat(note['created_at']).strftime('%Y/%m/%d %H:%M')
ttk.Label(
time_frame,
text=f"创建: {created_time}",
font=('TkDefaultFont', 8),
foreground='#666666'
).pack(side=tk.LEFT)
if note.get('completed_at'):
completed_time = datetime.fromisoformat(note['completed_at']).strftime('%Y/%m/%d %H:%M')
ttk.Label(
time_frame,
text=f"完成: {completed_time}",
font=('TkDefaultFont', 8),
foreground='#666666'
).pack(side=tk.LEFT, padx=(10, 0))
if note.get('deleted_at'):
deleted_time = datetime.fromisoformat(note['deleted_at']).strftime('%Y/%m/%d %H:%M')
ttk.Label(
time_frame,
text=f"删除: {deleted_time}",
font=('TkDefaultFont', 8),
foreground='#666666'
).pack(side=tk.LEFT, padx=(10, 0))
# 内容预览
content_preview = note['content'][:100] + "..." if len(note['content']) > 100 else note['content']
ttk.Label(
frame,
text=content_preview,
font=('Microsoft YaHei', 9),
wraplength=400,
anchor=tk.W
).pack(fill=tk.X, pady=(5, 0))
# 恢复按钮(仅回收站项目)
if note.get('deleted_at'):
btn_frame = ttk.Frame(frame)
btn_frame.pack(fill=tk.X, pady=(5, 0))
ttk.Button(
btn_frame,
text="恢复",
command=lambda nid=note['id']: self.restore_note(nid)
).pack(side=tk.LEFT)
ttk.Button(
btn_frame,
text="永久删除",
command=lambda nid=note['id']: self.permanent_delete(nid)
).pack(side=tk.RIGHT)
def update_history_view(self, window, period):
"""更新历史记录视图"""
window.destroy()
self.show_history(period)
def restore_note(self, note_id):
"""从回收站恢复笔记"""
for i, note in enumerate(self.recycle_bin):
if note['id'] == note_id:
restored_note = self.recycle_bin.pop(i)
restored_note.pop('deleted_at', None)
self.notes.append(restored_note)
break
self.save_data()
messagebox.showinfo("提示", "笔记已恢复")
self.show_history('week') # 刷新历史视图
def permanent_delete(self, note_id):
"""永久删除笔记"""
if not messagebox.askyesno("确认", "确定要永久删除这个笔记吗?此操作不可恢复!"):
return
for i, note in enumerate(self.recycle_bin):
if note['id'] == note_id:
self.recycle_bin.pop(i)
break
self.save_data()
messagebox.showinfo("提示", "笔记已永久删除")
self.show_history('week') # 刷新历史视图
def empty_recycle_bin(self):
"""清空超过7天的回收站内容"""
cutoff = datetime.now() - timedelta(days=7)
self.recycle_bin = [item for item in self.recycle_bin
if datetime.fromisoformat(item['deleted_at']) > cutoff]
self.save_data()
self.root.after(86400000, self.empty_recycle_bin) # 每天检查一次
def check_note_status(self):
"""检查笔记状态(定时任务)"""
def _check():
try:
now = datetime.now()
for note in self.notes:
if note['status'] == 'pending' and note['urgency'] >= 2:
created_time = datetime.fromisoformat(note['created_at'])
time_diff = now - created_time
if time_diff.days >= 1 and note['urgency'] == 2: # 重要任务超过1天
self.root.after(0, lambda: self.notify_urgent_task(note))
elif time_diff.total_seconds() >= 10800 and note['urgency'] == 3: # 紧急任务超过3小时(10800秒)
self.root.after(0, lambda: self.notify_urgent_task(note))
except Exception as e:
print(f"笔记状态检查出错: {e}")
finally:
self.root.after(600000, self.check_note_status) # 每十分钟检查一次
threading.Thread(target=_check, daemon=True).start()
def check_urgent_tasks(self):
"""检查紧急任务(定时任务)"""
def _check():
try:
now = datetime.now()
urgent_notes = []
for note in self.notes:
if note['status'] == 'pending' and note['urgency'] >= 2:
created_time = datetime.fromisoformat(note['created_at'])
time_diff = now - created_time
if (note['urgency'] == 2 and time_diff.days >= 1) or \
(note['urgency'] == 3 and time_diff.total_seconds() >= 7200):
urgent_notes.append(note)
if urgent_notes:
self.root.after(0, lambda: self.show_reminder_window(urgent_notes))
except Exception as e:
print(f"紧急任务检查出错: {e}")
finally:
self.root.after(3600000, self.check_urgent_tasks) # 每小时检查一次
threading.Thread(target=_check, daemon=True).start()
def notify_urgent_task(self, note):
"""通知紧急任务"""
messagebox.showwarning(
"待办提醒",
f"【{note['title']}】\n\n{note['content'][:100]}...\n\n该任务尚未完成!",
parent=self.root
)
def show_reminder_window(self, notes):
"""显示提醒窗口"""
if hasattr(self, 'reminder_window') and self.reminder_window.winfo_exists():
return
self.reminder_window = tk.Toplevel(self.root)
self.reminder_window.title("⚠️ 紧急任务提醒")
self.reminder_window.geometry("500x400")
self.reminder_window.protocol("WM_DELETE_WINDOW", self.on_reminder_close)
# 顶部提示
header = ttk.Frame(self.reminder_window)
header.pack(fill=tk.X, padx=10, pady=10)
ttk.Label(
header,
text=f"您有 {len(notes)} 个紧急任务待处理",
style='Title.TLabel',
font=('Microsoft YaHei', 10, 'bold')
).pack(side=tk.LEFT)
ttk.Button(
header,
text="关闭",
command=self.on_reminder_close
).pack(side=tk.RIGHT)
# 笔记列表区域
canvas = tk.Canvas(self.reminder_window)
scrollbar = ttk.Scrollbar(self.reminder_window, orient="vertical", command=canvas.yview)
scrollable_frame = ttk.Frame(canvas)
scrollable_frame.bind(
"<Configure>",
lambda e: canvas.configure(scrollregion=canvas.bbox("all"))
)
canvas.create_window((0, 0), window=scrollable_frame, anchor="nw")
canvas.configure(yscrollcommand=scrollbar.set)
canvas.pack(side="left", fill="both", expand=True)
scrollbar.pack(side="right", fill="y")
# 添加紧急任务列表
for note in notes:
frame = ttk.Frame(
scrollable_frame,
borderwidth=1,
relief="solid",
padding=10,
style='UrgentFlash.TFrame'
)
frame.pack(fill="x", padx=5, pady=5, ipady=5)
# 标题和紧急程度
title_frame = ttk.Frame(frame)
title_frame.pack(fill="x", pady=(0, 5))
ttk.Label(
title_frame,
text=f"【{note['urgency']}级】{note['title']}",
font=('Microsoft YaHei', 10, 'bold'),
foreground='#D32F2F'
).pack(side="left")
# 创建时间
created_time = datetime.fromisoformat(note['created_at']).strftime('%m/%d %H:%M')
ttk.Label(
title_frame,
text=f"创建于: {created_time}",
font=('TkDefaultFont', 8),
foreground='#666666'
).pack(side="right")
# 内容预览
content_preview = note['content'][:200] + "..." if len(note['content']) > 200 else note['content']
ttk.Label(
frame,
text=content_preview,
font=('Microsoft YaHei', 9),
wraplength=400
).pack(anchor="w")
# 操作按钮
btn_frame = ttk.Frame(frame)
btn_frame.pack(fill="x", pady=(5, 0))
ttk.Button(
btn_frame,
text="标记为已完成",
command=lambda nid=note['id']: self.complete_and_close(nid)
).pack(side="left", padx=2)
ttk.Button(
btn_frame,
text="查看详情",
command=lambda nid=note['id']: self.focus_note(nid)
).pack(side="left", padx=2)
ttk.Button(
btn_frame,
text="稍后提醒",
command=lambda: self.reminder_window.after(1800000, self.show_reminder_window, [note]) # 30分钟后再次提醒
).pack(side="right", padx=2)
def complete_and_close(self, note_id):
"""标记为已完成并关闭提醒窗口"""
for note in self.notes:
if note['id'] == note_id:
note['status'] = 'completed'
note['completed_at'] = datetime.now().isoformat()
break
self.save_data()
self.on_reminder_close()
self.render_notes()
messagebox.showinfo("提示", "任务已标记为已完成")
def focus_note(self, note_id):
"""聚焦到指定笔记"""
self.on_reminder_close()
self.render_notes() # 确保笔记列表是最新的
# 滚动到指定笔记
for widget in self.notes_frame.winfo_children():
if hasattr(widget, 'note_id') and widget.note_id == note_id:
self.notes_canvas.yview_moveto(widget.winfo_y() / self.notes_frame.winfo_height())
# 高亮显示
widget.configure(style='UrgentFlash.TFrame')
self.root.after(3000, lambda: widget.configure(style='Note.TFrame'))
break
def on_reminder_close(self):
"""关闭提醒窗口"""
if hasattr(self, 'reminder_window') and self.reminder_window.winfo_exists():
self.reminder_window.destroy()
delattr(self, 'reminder_window')
def show_advanced_search(self):
"""显示高级搜索窗口"""
search_window = tk.Toplevel(self.root)
search_window.title("高级搜索")
search_window.geometry("400x300")
# 搜索条件框架
condition_frame = ttk.LabelFrame(search_window, text="搜索条件", padding=10)
condition_frame.pack(fill="both", expand=True, padx=10, pady=10)
# 关键词搜索
ttk.Label(condition_frame, text="关键词:").grid(row=0, column=0, sticky="w", pady=2)
keyword_entry = ttk.Entry(condition_frame)
keyword_entry.grid(row=0, column=1, sticky="ew", pady=2)
# 紧急程度
ttk.Label(condition_frame, text="紧急程度:").grid(row=1, column=0, sticky="w", pady=2)
urgency_var = tk.StringVar(value="any")
urgency_options = ttk.Combobox(
condition_frame,
textvariable=urgency_var,
values=["任何", "低", "普通", "重要", "紧急"],
state="readonly"
)
urgency_options.grid(row=1, column=1, sticky="ew", pady=2)
# 时间范围
ttk.Label(condition_frame, text="创建时间:").grid(row=2, column=0, sticky="w", pady=2)
time_frame = ttk.Frame(condition_frame)
time_frame.grid(row=2, column=1, sticky="ew", pady=2)
time_var = tk.StringVar(value="any")
ttk.Radiobutton(time_frame, text="任何时间", variable=time_var, value="any").pack(side="left")
ttk.Radiobutton(time_frame, text="最近7天", variable=time_var, value="week").pack(side="left")
ttk.Radiobutton(time_frame, text="最近30天", variable=time_var, value="month").pack(side="left")
# 按钮区域
button_frame = ttk.Frame(search_window)
button_frame.pack(fill="x", padx=10, pady=(0, 10))
ttk.Button(
button_frame,
text="搜索",
style='Accent.TButton',
command=lambda: self.apply_advanced_search(
keyword_entry.get(),
urgency_var.get(),
time_var.get(),
search_window
)
).pack(side="right", padx=5)
ttk.Button(
button_frame,
text="取消",
command=search_window.destroy
).pack(side="right", padx=5)
def apply_advanced_search(self, keyword, urgency, time_range, window):
"""应用高级搜索条件"""
# 转换紧急程度
urgency_map = {"任何": None, "低": 0, "普通": 1, "重要": 2, "紧急": 3}
urgency_level = urgency_map.get(urgency)
# 转换时间范围
now = datetime.now()
if time_range == "week":
cutoff = now - timedelta(days=7)
elif time_range == "month":
cutoff = now - timedelta(days=30)
else:
cutoff = None
# 构建搜索字符串
search_parts = []
if keyword:
search_parts.append(keyword.lower())
if urgency_level is not None:
search_parts.append(f"urgency:{urgency_level}")
if cutoff:
search_parts.append(f"after:{cutoff.strftime('%Y-%m-%d')}")
self.search_var.set(" ".join(search_parts))
window.destroy()
self.render_notes()
def toggle_topmost(self):
"""切换窗口置顶状态"""
self.settings['always_on_top'] = not self.settings['always_on_top']
self.root.attributes('-topmost', self.settings['always_on_top'])
self.topmost_btn.config(text="📌" if self.settings['always_on_top'] else "📋")
self.save_data()
def change_opacity(self, value):
"""改变窗口透明度"""
opacity = float(value)
self.root.attributes('-alpha', opacity)
self.settings['opacity'] = opacity
self.save_data()
def run(self):
"""运行主循环"""
self.root.mainloop()
if __name__ == "__main__":
root = tk.Tk()
# 设置DPI感知
if sys.platform == 'win32':
from ctypes import windll
windll.shcore.SetProcessDpiAwareness(1)
app = SmartStickyNote(root)
app.run()
再次检查这些代码,包括所有的初始化的值,所有的都能被初始化都能被调用,最重要确保代码能够正确执行,请将修复后的完整的代码给我