Skip to content

Commit a15c1d2

Browse files
committed
feat(app): 新增功能并优化课程表程序
- 新增迷你天气显示组件,可在主窗口旁边显示天气信息(不完善) - 优化课程表程序启动流程,支持命令行参数 - 添加程序重启管理功能,用于应用排版设置等需要重启的功能 - 改进设置窗口,增加重启程序按钮 - 优化时间标签点击事件处理,支持单击和三击不同功能 - 添加高考倒计时彩蛋功能 - 修复部分已知问题并优化用户体验
1 parent 903cbed commit a15c1d2

File tree

9 files changed

+742
-67
lines changed

9 files changed

+742
-67
lines changed

app.py

Lines changed: 83 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from typing import Dict, List, Callable
22
import tkinter as tk
33
import os
4+
import sys
45
import json
56
from datetime import datetime, date
67
from constants import SCHEDULE_FILE, WEEKDAYS
@@ -10,8 +11,9 @@
1011

1112
class CourseScheduler:
1213
"""课程表主应用类"""
13-
def __init__(self):
14+
def __init__(self, startup_action=None):
1415
"""初始化课程表应用"""
16+
self.startup_action = startup_action
1517
try:
1618
logger.log_debug("Initializing CourseScheduler application")
1719
# 先初始化配置
@@ -23,7 +25,9 @@ def __init__(self):
2325
# 创建主窗口并应用配置
2426
self.root = self._create_root_window()
2527
logger.log_debug("Main window created")
28+
# 应用窗口尺寸并强制更新布局
2629
self.root.geometry(self.config_handler.geometry)
30+
self.root.update_idletasks() # 立即应用窗口布局
2731

2832
# 初始化其他成员变量
2933
self.schedule: Dict[str, List[Dict[str, str]]] = {}
@@ -33,13 +37,18 @@ def __init__(self):
3337
self.settings_window = None
3438
self.about_window = None
3539
self.main_menu = None
40+
self.was_iconic = False # 初始化窗口状态跟踪属性
3641

3742
logger.log_debug("Initializing schedule")
3843
self._initialize_schedule()
3944
logger.log_debug("Schedule initialized")
4045
logger.log_debug("Initializing UI")
4146
self._initialize_ui()
4247
logger.log_debug("UI initialized")
48+
49+
# 执行启动动作
50+
if self.startup_action == 'open_settings':
51+
self.open_settings()
4352
except Exception as e:
4453
logger.log_error(e)
4554
raise
@@ -53,11 +62,16 @@ def _create_root_window(self) -> tk.Tk:
5362
root.protocol("WM_DELETE_WINDOW", self.cleanup_resources) # 退出时清理资源
5463

5564
# 设置透明图标和窗口类型
56-
# root.iconbitmap(default='res/icon.ico') # 设置默认图标
57-
root.attributes('-toolwindow', True) # 设置为工具窗口样式
58-
root.attributes('-topmost', True) # 保持窗口在最前
59-
root.after(100, lambda: root.attributes('-topmost', False)) # 短暂置顶后取消
65+
# 使用绝对路径并处理打包后的资源路径
66+
base_path = getattr(sys, '_MEIPASS', os.path.dirname(os.path.abspath(__file__)))
67+
icon_path = os.path.join(base_path, 'res', 'icon.ico')
68+
root.iconbitmap(default=icon_path)
69+
root.wm_iconbitmap(icon_path)
6070

71+
#窗口层级控制
72+
root.attributes('-toolwindow', True) # 设置为工具窗口样式
73+
root.attributes('-topmost', True)
74+
root.after(100, lambda: root.attributes('-topmost', False))
6175
return root
6276

6377
def cleanup_resources(self):
@@ -196,15 +210,57 @@ def _on_time_label_click(self, event):
196210
"""处理时间标签点击事件"""
197211
from tkinter import messagebox
198212
from tools.fullscreen_time import FullscreenTimeWindow
213+
from tools.weather_ui import WeatherUI
214+
from tools.weather import WeatherAPI
199215

200-
result = messagebox.askyesno(
201-
"全屏时间",
202-
"是否开启全屏时间?"
203-
)
204-
if result:
205-
if not hasattr(self, "fullscreen_time_window"):
206-
self.fullscreen_time_window = FullscreenTimeWindow(self.root, self.config_handler)
207-
self.fullscreen_time_window.show()
216+
# 点击计数器
217+
if not hasattr(self, '_click_count'):
218+
self._click_count = 0
219+
self._click_count += 1
220+
221+
# 重置计数器定时器
222+
if hasattr(self, '_click_timer'):
223+
self.root.after_cancel(self._click_timer)
224+
self._click_timer = self.root.after(500, self._reset_click_count)
225+
226+
# 处理点击逻辑
227+
if self._click_count == 1:
228+
# 单次点击显示迷你天气
229+
# 检查API密钥是否存在
230+
if self.config_handler.heweather_api_key:
231+
if not hasattr(self, "mini_weather"):
232+
from tools.weather_ui import MiniWeatherUI
233+
main_geometry = self.root.geometry()
234+
self.mini_weather = MiniWeatherUI(
235+
WeatherAPI(),
236+
master=self.root
237+
)
238+
elif self._click_count >= 3:
239+
# 三次点击显示全屏时间
240+
self._click_count = 0
241+
if messagebox.askyesno("确认", "是否打开全屏大号时间?"):
242+
if not hasattr(self, "fullscreen_time_window"):
243+
from tools.fullscreen_time import FullscreenTimeWindow
244+
self.fullscreen_time_window = FullscreenTimeWindow(self.root, self.config_handler)
245+
self.fullscreen_time_window.show()
246+
247+
def _reset_click_count(self):
248+
"""重置点击计数器"""
249+
self._click_count = 0
250+
251+
def _show_click_tooltip(self, event):
252+
"""显示点击提示气泡"""
253+
# 创建气泡窗口
254+
tooltip = tk.Toplevel(self.root)
255+
tooltip.wm_overrideredirect(True)
256+
tooltip.geometry(f"+{event.x_root+20}+{event.y_root+20}")
257+
258+
# 气泡内容
259+
label = tk.Label(tooltip, text="待开发功能", bg="yellow", fg="black", padx=8, pady=4)
260+
label.pack()
261+
262+
# 自动关闭定时器
263+
tooltip.after(1500, tooltip.destroy)
208264

209265
def _create_countdown_display(self) -> None:
210266
"""创建倒计时显示区域"""
@@ -263,6 +319,14 @@ def update_display(self) -> None:
263319
self._update_countdown_display(now)
264320
self._update_schedule_display(now)
265321
self._schedule_next_update()
322+
323+
# 检测窗口状态变化
324+
is_iconic = self.root.state() == 'iconic'
325+
if self.was_iconic and not is_iconic:
326+
# 窗口从最小化恢复时重新置顶
327+
self.root.attributes('-topmost', True)
328+
self.root.after(100, lambda: self.root.attributes('-topmost', False))
329+
self.was_iconic = is_iconic
266330
except Exception as e:
267331
logger.log_error(e)
268332

@@ -277,7 +341,11 @@ def _update_time_display(self, now: datetime) -> None:
277341
def _update_countdown_display(self, now: datetime) -> None:
278342
"""更新倒计时显示"""
279343
delta = (self.config_handler.countdown_date.date() - now.date()).days
280-
self.countdown_label2.config(text=str(delta))
344+
# 高考彩蛋:当倒计时名称是高考且天数<=100时显示红色
345+
if self.config_handler.countdown_name == "高考" and delta <= 100:
346+
self.countdown_label2.config(text=str(delta), fg="red")
347+
else:
348+
self.countdown_label2.config(text=str(delta), fg=self.config_handler.font_color)
281349

282350
def _update_schedule_display(self, now: datetime) -> None:
283351
"""更新课程表显示"""
@@ -321,7 +389,7 @@ def _get_course_color(self, now: datetime, course: Dict[str, str]) -> str:
321389
return "yellow" # 正在上的课程为黄色
322390
elif current_time > end_time:
323391
return "green" # 已上完的课程为绿色
324-
return "#ffcccc" # 未上过的课程为浅红色
392+
return "red" # 未上过的课程为红色
325393

326394
def _update_existing_label(self, index: int, course: Dict[str, str], color: str) -> None:
327395
"""更新现有课程标签"""

constants.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,5 @@
88
# 程序信息
99
APP_NAME = "桌面课程表 - Course Scheduler"
1010
AUTHOR = "dong"
11-
VERSION = "0.1.12"
11+
VERSION = "0.1.13"
1212
PROJECT_URL = "https://github.com/dongkid/course_scheduler"

editor.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,10 @@ def _create_notebook(self) -> None:
300300

301301
# 直接加载UI
302302
self.create_day_ui(day_frame, str(i))
303+
304+
# 默认打开当天标签页
305+
current_weekday = datetime.now().weekday()
306+
self.notebook.select(current_weekday)
303307

304308
def _create_save_button(self) -> None:
305309
"""创建保存按钮"""

installer.py

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
import os
2+
import sys
3+
import shutil
4+
import tkinter as tk
5+
from tkinter import messagebox
6+
import logging
7+
import subprocess
8+
import atexit
9+
from logger import logger
10+
11+
def is_compiled():
12+
"""判断是否已打包为exe"""
13+
return hasattr(sys, 'frozen')
14+
15+
def get_executable_path():
16+
"""获取可执行文件路径"""
17+
if is_compiled():
18+
return sys.executable
19+
return os.path.abspath(sys.argv[0])
20+
21+
def get_target_dir():
22+
"""获取推荐安装目录"""
23+
appdata = os.getenv('APPDATA')
24+
return os.path.join(appdata, 'CourseScheduler')
25+
26+
def is_desktop_path(path):
27+
"""检查是否在桌面目录"""
28+
desktop = os.path.expanduser('~/Desktop')
29+
return os.path.normpath(path).lower() == os.path.normpath(desktop).lower()
30+
31+
def create_shortcut(target, shortcut_path):
32+
"""创建快捷方式(需要pywin32)"""
33+
try:
34+
import win32com.client
35+
shell = win32com.client.Dispatch("WScript.Shell")
36+
shortcut = shell.CreateShortCut(shortcut_path)
37+
shortcut.Targetpath = target
38+
shortcut.WorkingDirectory = os.path.dirname(target)
39+
shortcut.save()
40+
except Exception as e:
41+
logger.log_error(f"快捷方式创建失败: {str(e)}")
42+
messagebox.showerror("错误", "无法创建快捷方式,请手动创建")
43+
44+
def check_installation():
45+
"""主安装检查逻辑"""
46+
if not is_compiled():
47+
return # 开发模式不处理
48+
49+
# 检查必要文件是否存在
50+
config_files_exist = all(
51+
os.path.exists(f)
52+
for f in ['config.json', 'schedule.json']
53+
)
54+
55+
if config_files_exist:
56+
return # 非首次运行
57+
58+
current_path = os.path.dirname(get_executable_path())
59+
if not is_desktop_path(current_path):
60+
return # 已经在正确目录
61+
62+
# 弹出安装提示
63+
choice = messagebox.askyesno(
64+
"安装提示",
65+
"检测到程序运行在桌面目录,这可能导致数据丢失!\n"
66+
"是否要安装到推荐目录并创建快捷方式?",
67+
icon='warning'
68+
)
69+
70+
if not choice:
71+
return
72+
73+
# 开始安装流程
74+
target_dir = get_target_dir()
75+
exe_name = os.path.basename(get_executable_path())
76+
target_path = os.path.join(target_dir, exe_name)
77+
78+
try:
79+
# 创建目标目录
80+
os.makedirs(target_dir, exist_ok=True)
81+
82+
# 复制文件
83+
shutil.copy2(get_executable_path(), target_path)
84+
logger.log_info(f"程序已复制到: {target_path}")
85+
86+
# 创建快捷方式
87+
desktop = os.path.expanduser('~/Desktop')
88+
shortcut_path = os.path.join(desktop, "桌面课程表.lnk")
89+
create_shortcut(target_path, shortcut_path)
90+
91+
# 启动新实例并删除旧文件
92+
subprocess.Popen([target_path])
93+
94+
# 注册退出时删除原文件
95+
def cleanup():
96+
try:
97+
# 删除原程序文件
98+
os.remove(get_executable_path())
99+
# 清理临时配置文件
100+
temp_dir = os.path.join(os.path.dirname(get_executable_path()), 'temp')
101+
if os.path.exists(temp_dir):
102+
shutil.rmtree(temp_dir)
103+
logger.log_info("原文件及临时目录已清理")
104+
except Exception as e:
105+
logger.log_error(f"清理失败: {str(e)}")
106+
107+
atexit.register(cleanup)
108+
sys.exit(0)
109+
110+
except Exception as e:
111+
logger.log_error(f"安装失败: {str(e)}")
112+
messagebox.showerror("安装错误", f"安装过程中发生错误: {str(e)}")
113+
sys.exit(1)

main.py

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,37 @@
11
from app import CourseScheduler
22
from auto_start import check_and_generate_files
33
from logger import logger
4+
from restart_manager import RestartManager
45
import socket
56
import tkinter as tk
67
import sys
78

89
if __name__ == "__main__":
9-
check_and_generate_files()
10+
# 安装检查(必须在最前面)
11+
from installer import check_installation
12+
check_installation()
1013

14+
# 解析命令行参数
15+
import argparse
16+
parser = argparse.ArgumentParser(description='课程表程序')
17+
parser.add_argument('--open-settings', action='store_true',
18+
help='启动时打开设置窗口')
19+
args = parser.parse_args()
20+
1121
# 单实例检查
1222
try:
1323
s = socket.socket()
1424
s.bind(('127.0.0.1', 49152))
1525
except OSError:
1626
tk.messagebox.showwarning("警告", "程序已经在运行中")
1727
sys.exit(1)
28+
29+
#初始化文件
30+
check_and_generate_files()
1831

19-
app = CourseScheduler()
32+
app = CourseScheduler(startup_action=args.open_settings and 'open_settings' or None)
33+
# 清理可能存在的临时重启资源
34+
RestartManager.cleanup_restart_manager_resources()
2035
try:
2136
app.root.mainloop()
2237
finally:

0 commit comments

Comments
 (0)