daily_reminder.py 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256
  1. """
  2. 每日提醒工具 - 晚上11:30弹出人物提醒写日报
  3. 显示美化窗口
  4. """
  5. import sys
  6. import os
  7. import subprocess
  8. from pathlib import Path
  9. from datetime import datetime
  10. # 设置控制台编码为UTF-8(Windows)
  11. if sys.platform == 'win32':
  12. import io
  13. sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
  14. sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8')
  15. try:
  16. import tkinter as tk
  17. from tkinter import messagebox
  18. TKINTER_AVAILABLE = True
  19. except ImportError:
  20. TKINTER_AVAILABLE = False
  21. print("❌ 错误: tkinter 未安装(Python应该自带tkinter)")
  22. try:
  23. from PIL import Image, ImageTk
  24. PIL_AVAILABLE = True
  25. except ImportError:
  26. PIL_AVAILABLE = False
  27. print("⚠️ 警告: Pillow 未安装,无法显示图片")
  28. print("💡 请运行: pip install Pillow")
  29. class DailyReminder:
  30. def __init__(self, base_dir=None):
  31. self.base_dir = base_dir or Path(__file__).parent
  32. self.window = None
  33. self.canvas = None
  34. self.photo = None
  35. self.original_image = None
  36. # 窗口设置
  37. self.window_width = 160
  38. self.window_height = 160
  39. self.image_size = (150, 150) # 图片大小适配窗口
  40. def load_image(self):
  41. """加载人物图片"""
  42. # 尝试多种可能的图片路径和格式
  43. image_paths = [
  44. self.base_dir / "assets" / "person.png",
  45. self.base_dir / "assets" / "person.jpg",
  46. self.base_dir / "assets" / "person.jpeg",
  47. self.base_dir / "assets" / "reminder.png",
  48. self.base_dir / "assets" / "reminder.jpg",
  49. ]
  50. for img_path in image_paths:
  51. if img_path.exists():
  52. try:
  53. img = Image.open(img_path)
  54. # 转换为RGBA模式以支持透明背景
  55. if img.mode != 'RGBA':
  56. img = img.convert('RGBA')
  57. # 调整图片大小
  58. img = img.resize(self.image_size, Image.Resampling.LANCZOS)
  59. self.original_image = img
  60. return True
  61. except Exception as e:
  62. print(f"⚠️ 加载图片失败 {img_path}: {e}")
  63. continue
  64. print("❌ 未找到人物图片文件")
  65. print("💡 请将图片放在 assets/ 目录下,命名为 person.png 或 person.jpg")
  66. return False
  67. def show_reminder(self):
  68. """显示提醒窗口"""
  69. if not TKINTER_AVAILABLE:
  70. self.show_system_notification()
  71. return
  72. if not PIL_AVAILABLE:
  73. try:
  74. messagebox.showerror("错误", "Pillow 未安装,无法显示图片\n请运行: pip install Pillow")
  75. except:
  76. print("❌ 错误: Pillow 未安装,无法显示图片\n💡 请运行: pip install Pillow")
  77. return
  78. if not self.load_image():
  79. try:
  80. messagebox.showerror("错误", "未找到人物图片文件\n请将图片放在 assets/ 目录下\n支持的名称: person.png, person.jpg, reminder.png")
  81. except:
  82. print("❌ 错误: 未找到人物图片文件\n💡 请将图片放在 assets/ 目录下")
  83. return
  84. # 创建主窗口
  85. self.window = tk.Toplevel()
  86. self.window.title("📝 写日报提醒")
  87. # 设置窗口属性
  88. self.window.attributes('-topmost', True) # 置顶
  89. self.window.attributes('-alpha', 0.95) # 半透明
  90. # 移除窗口边框(可选,创建无边框窗口)
  91. # self.window.overrideredirect(True)
  92. # 计算窗口位置(屏幕右下角)
  93. screen_width = self.window.winfo_screenwidth()
  94. screen_height = self.window.winfo_screenheight()
  95. x = screen_width - self.window_width - 20 # 距离右边20像素
  96. y = screen_height - self.window_height - 60 # 距离底部60像素(留出任务栏空间)
  97. self.window.geometry(f"{self.window_width}x{self.window_height}+{x}+{y}")
  98. # 设置窗口背景色
  99. self.window.configure(bg='#f0f0f0')
  100. # 创建画布
  101. self.canvas = tk.Canvas(
  102. self.window,
  103. width=self.window_width,
  104. height=self.window_height,
  105. bg='#f0f0f0',
  106. highlightthickness=0
  107. )
  108. self.canvas.pack(fill=tk.BOTH, expand=True)
  109. # 显示图片
  110. self.update_image()
  111. # 绑定点击事件
  112. self.canvas.bind('<Button-1>', self.on_click)
  113. self.window.bind('<Button-1>', self.on_click)
  114. # 淡入动画
  115. self.fade_in()
  116. # 窗口关闭事件
  117. self.window.protocol("WM_DELETE_WINDOW", self.on_close)
  118. def update_image(self):
  119. """更新显示的图片"""
  120. if not self.original_image:
  121. return
  122. # 转换为PhotoImage
  123. self.photo = ImageTk.PhotoImage(self.original_image)
  124. # 清除画布并重新绘制
  125. self.canvas.delete("image")
  126. x = (self.window_width - self.image_size[0]) // 2
  127. y = (self.window_height - self.image_size[1]) // 2 # 居中显示
  128. self.canvas.create_image(x, y, anchor=tk.NW, image=self.photo, tags="image")
  129. def fade_in(self):
  130. """淡入动画"""
  131. if not self.window:
  132. return
  133. alpha = 0.0
  134. step = 0.05
  135. def fade():
  136. nonlocal alpha
  137. if alpha < 0.95:
  138. alpha += step
  139. self.window.attributes('-alpha', alpha)
  140. self.window.after(20, fade)
  141. fade()
  142. def on_click(self, event=None):
  143. """点击事件处理"""
  144. self.on_close()
  145. self.start_write_report()
  146. def on_close(self):
  147. """关闭窗口"""
  148. # 淡出动画
  149. if self.window:
  150. alpha = 0.95
  151. def fade_out():
  152. nonlocal alpha
  153. if alpha > 0:
  154. alpha -= 0.1
  155. try:
  156. self.window.attributes('-alpha', alpha)
  157. self.window.after(30, fade_out)
  158. except:
  159. pass
  160. else:
  161. if self.window:
  162. self.window.destroy()
  163. fade_out()
  164. def start_write_report(self):
  165. """启动写日报"""
  166. try:
  167. write_report_script = self.base_dir / "write_report.py"
  168. if not write_report_script.exists():
  169. error_msg = f"未找到 write_report.py\n路径: {write_report_script}"
  170. try:
  171. messagebox.showerror("错误", error_msg)
  172. except:
  173. print(f"❌ {error_msg}")
  174. return
  175. # 使用subprocess启动写日报脚本,并传递--daily参数
  176. python_exe = sys.executable
  177. subprocess.Popen(
  178. [python_exe, str(write_report_script), "--daily"],
  179. cwd=str(self.base_dir),
  180. creationflags=subprocess.CREATE_NEW_CONSOLE if sys.platform == 'win32' else 0
  181. )
  182. except Exception as e:
  183. error_msg = f"启动写日报失败: {e}"
  184. try:
  185. messagebox.showerror("错误", error_msg)
  186. except:
  187. print(f"❌ {error_msg}")
  188. def show_system_notification(self):
  189. """显示系统通知(备选方案)"""
  190. try:
  191. from plyer import notification
  192. notification.notify(
  193. title="📝 写日报提醒",
  194. message="该写日报啦!点击通知打开写日报。",
  195. timeout=10
  196. )
  197. except:
  198. print("📝 写日报提醒:该写日报啦!")
  199. def main():
  200. """主函数"""
  201. base_dir = Path(__file__).parent
  202. # 检查今天是否已经提醒过(可选功能)
  203. # 这里可以添加检查逻辑,避免重复提醒
  204. reminder = DailyReminder(base_dir)
  205. reminder.show_reminder()
  206. # 运行tkinter主循环
  207. if TKINTER_AVAILABLE:
  208. root = tk.Tk()
  209. root.withdraw() # 隐藏主窗口
  210. root.mainloop()
  211. if __name__ == "__main__":
  212. main()