Skip to content

Commit 835ebe5

Browse files
committed
feat(view): 增强课表交互性,增加周/日视图切换与预览功能
本次更新显著提升了课程表的交互体验和信息预览能力。 主要更新包括: - **课表滑动切换**:在主界面课表区域左右滑动,可以方便地切换并查看前一天或后一天的课程安排。切换到非当天视图时,会有5秒的自动重置计时器。 - **快捷手势操作**: - **双击**:立即将课表视图重置回当天。 - **三击**:快速打开或关闭全新的“周课表”预览窗口,方便一览整周课程。 - **周课表预览**: - 新增主界面课表区域点三下显示整周课表预览 - **明日课表自动预览**: - 新增“结束后自动预览明天课表”选项(默认关闭)。 - 开启后,在当天所有课程结束后,会自动弹出窗口预览次日课程。 - **UI与代码优化**: - 主界面的时间与星期显示被拆分,布局更清晰,并在预览时以斜体区分。 - 优化了 `config_handler`,移除了重复的配置加载代码。 - 更新了部分分辨率下的窗口位置常量。
1 parent ad86946 commit 835ebe5

File tree

5 files changed

+504
-71
lines changed

5 files changed

+504
-71
lines changed

app.py

Lines changed: 231 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,14 @@ def __init__(self, startup_action=None, geometry_override=None):
5858
self.main_menu = None
5959
self.was_iconic = False # 初始化窗口状态跟踪属性
6060
self.is_dialog_open = False # 防止对话框多开
61+
self.week_preview_window = None # 周课表预览窗口实例
62+
self.tomorrow_preview_shown_for_today = False # 今天是否已显示过明日预览
63+
64+
# --- 课表视图状态管理 ---
65+
self.displayed_weekday = datetime.now().weekday() # 当前显示的星期,0-6
66+
self.view_reset_timer = None # 视图自动重置计时器
67+
self.swipe_start_x = 0 # 滑动起始x坐标
68+
# -------------------------
6169

6270
logger.log_debug("Initializing schedule")
6371
self._initialize_schedule()
@@ -108,9 +116,13 @@ def cleanup_resources(self):
108116
"""清理所有资源"""
109117
try:
110118
# 关闭所有子窗口
111-
for window in [self.editor_window, self.settings_window, self.about_window]:
112-
if window and window.window.winfo_exists():
113-
window.window.destroy()
119+
for window in [self.editor_window, self.settings_window, self.about_window, self.week_preview_window]:
120+
if window and window.winfo_exists():
121+
# 兼容不同窗口对象的销毁方式
122+
if hasattr(window, 'window') and window.window.winfo_exists():
123+
window.window.destroy()
124+
else:
125+
window.destroy()
114126

115127
# 取消所有定时器
116128
for timer_id in getattr(self, 'timer_ids', []):
@@ -225,16 +237,25 @@ def _initialize_ui(self) -> None:
225237

226238
def _create_time_display(self) -> None:
227239
"""创建时间显示区域"""
228-
self.time_label = tk.Label(
229-
self.root,
240+
self.time_date_label = tk.Label(
241+
self.root,
230242
font=("微软雅黑", self.config_handler.time_display_size, "bold"),
231243
fg=self.config_handler.font_color,
232244
bg="#ecf0f1"
233245
)
234-
self.time_label.pack(pady=self.config_handler.vertical_padding, fill=tk.X)
235-
246+
self.time_date_label.pack(fill=tk.X)
247+
248+
self.weekday_label = tk.Label(
249+
self.root,
250+
font=("微软雅黑", self.config_handler.time_display_size, "bold"),
251+
fg=self.config_handler.font_color,
252+
bg="#ecf0f1"
253+
)
254+
self.weekday_label.pack(fill=tk.X)
255+
236256
# 添加点击事件
237-
self.time_label.bind("<Button-1>", self._on_time_label_click)
257+
self.time_date_label.bind("<Button-1>", self._on_time_label_click)
258+
self.weekday_label.bind("<Button-1>", self._on_time_label_click)
238259

239260
def _on_time_label_click(self, event):
240261
"""处理时间标签点击事件"""
@@ -332,6 +353,7 @@ def _create_schedule_display(self) -> None:
332353
self.schedule_frame.pack(padx=self.config_handler.horizontal_padding,
333354
pady=self.config_handler.vertical_padding,
334355
fill=tk.BOTH, expand=True)
356+
self._bind_schedule_events()
335357

336358
def _start_update_loop(self) -> None:
337359
"""启动界面更新循环"""
@@ -350,10 +372,25 @@ def update_display(self) -> None:
350372

351373
# 只有秒数变化时才更新UI
352374
if current_second != self.last_second:
353-
self._update_time_display(now)
354375
self._update_countdown_display(now)
355-
self._update_schedule_display(now)
376+
377+
# 根据当前是显示当天还是预览来更新时间/星期显示
378+
if self.displayed_weekday == now.weekday():
379+
# 正常更新时间、秒数和星期
380+
self._update_time_display(now)
381+
# 课表内容也只在显示当天时才随时间更新状态
382+
self._update_schedule_display(now.weekday())
383+
else:
384+
# 保持预览状态的显示(日期不变,星期为斜体)
385+
self.time_date_label.config(text=now.strftime("%Y-%m-%d"))
386+
self.weekday_label.config(
387+
text=f"星期{WEEKDAYS[self.displayed_weekday]}",
388+
font=("微软雅黑", self.config_handler.time_display_size, "bold italic")
389+
)
390+
356391
self.last_second = current_second
392+
393+
self._check_and_show_tomorrow_preview(now)
357394

358395
self._schedule_next_update()
359396

@@ -369,11 +406,14 @@ def update_display(self) -> None:
369406

370407
def _update_time_display(self, now: datetime) -> None:
371408
"""更新时间显示"""
372-
weekday = now.weekday()
373-
self.time_label.config(
374-
text=now.strftime("%Y-%m-%d\n%H:%M:%S\n") +
375-
f"星期{WEEKDAYS[weekday]}"
409+
self.time_date_label.config(text=now.strftime("%Y-%m-%d\n%H:%M:%S"))
410+
self.weekday_label.config(
411+
text=f"星期{WEEKDAYS[now.weekday()]}",
412+
font=("微软雅黑", self.config_handler.time_display_size, "bold")
376413
)
414+
# 如果是新的一天,重置预览标志
415+
if now.hour == 0 and now.minute == 0 and now.second == 0:
416+
self.tomorrow_preview_shown_for_today = False
377417

378418
def _update_countdown_display(self, now: datetime) -> None:
379419
"""更新倒计时显示"""
@@ -384,13 +424,30 @@ def _update_countdown_display(self, now: datetime) -> None:
384424
else:
385425
self.countdown_label2.config(text=str(delta), fg=self.config_handler.font_color)
386426

387-
def _update_schedule_display(self, now: datetime) -> None:
388-
"""更新课程表显示"""
427+
def _update_schedule_display(self, weekday_to_show: int) -> None:
428+
"""更新课程表显示
429+
Args:
430+
weekday_to_show (int): 要显示的星期 (0-6).
431+
"""
389432
if not hasattr(self, 'course_labels'):
390433
self.course_labels = []
391-
392-
weekday = str(now.weekday())
393-
today_schedule = self.schedule["schedules"][self.schedule["current_schedule"]].get(weekday, [])
434+
435+
now = datetime.now()
436+
weekday_str = str(weekday_to_show)
437+
438+
# 更新时间标签以反映当前显示的星期
439+
displayed_day_str = f"星期{WEEKDAYS[weekday_to_show]}"
440+
if weekday_to_show != now.weekday():
441+
self.time_date_label.config(text=now.strftime("%Y-%m-%d"))
442+
self.weekday_label.config(
443+
text=displayed_day_str,
444+
font=("微软雅黑", self.config_handler.time_display_size, "bold italic")
445+
)
446+
else:
447+
# 仅在显示当天时才更新秒数
448+
self._update_time_display(now)
449+
450+
schedule_for_day = self.schedule["schedules"][self.schedule["current_schedule"]].get(weekday_str, [])
394451

395452
# 在更新前清除所有课程时间缓存
396453
self._course_time_cache.clear()
@@ -399,7 +456,7 @@ def _update_schedule_display(self, now: datetime) -> None:
399456
self.course_labels = [label for label in self.course_labels if label.winfo_exists()]
400457

401458
# 根据当前课表重新排列所有标签
402-
for i, course in enumerate(today_schedule):
459+
for i, course in enumerate(schedule_for_day):
403460
color = self._get_course_color(now, course)
404461
if i < len(self.course_labels):
405462
# 强制更新标签颜色状态
@@ -411,7 +468,7 @@ def _update_schedule_display(self, now: datetime) -> None:
411468
self._create_new_label(course, color, row=i)
412469

413470
# 移除多余的标签
414-
self._remove_extra_labels(today_schedule)
471+
self._remove_extra_labels(schedule_for_day)
415472

416473
def _update_course_labels(self, now: datetime, schedule: List[Dict[str, str]]) -> None:
417474
"""更新或创建课程标签"""
@@ -426,6 +483,10 @@ def _update_course_labels(self, now: datetime, schedule: List[Dict[str, str]]) -
426483

427484
def _get_course_color(self, now: datetime, course: Dict[str, str]) -> str:
428485
"""根据课程时间获取显示颜色"""
486+
# 如果显示的不是当天的课表,则所有课程都显示为“未开始”状态
487+
if self.displayed_weekday != now.weekday():
488+
return "red"
489+
429490
# 使用课程名称+时间作为缓存键
430491
cache_key = f"{course['name']}_{course['start_time']}_{course['end_time']}"
431492

@@ -474,10 +535,9 @@ def _update_existing_label(self, index: int, course: Dict[str, str], color: str,
474535
def _update_font_settings(self) -> None:
475536
"""更新所有UI组件的字体设置"""
476537
# 更新时间显示
477-
self.time_label.config(
478-
font=("微软雅黑", self.config_handler.font_size, "bold"),
479-
fg=self.config_handler.font_color
480-
)
538+
font_config = ("微软雅黑", self.config_handler.font_size, "bold")
539+
self.time_date_label.config(font=font_config, fg=self.config_handler.font_color)
540+
self.weekday_label.config(font=font_config, fg=self.config_handler.font_color)
481541

482542
# 更新课程标签色块尺寸
483543
if hasattr(self, 'course_labels'):
@@ -543,6 +603,10 @@ def _create_new_label(self, course: Dict[str, str], color: str, row: int) -> Non
543603
fg=self.config_handler.font_color,
544604
anchor='w'
545605
)
606+
607+
# --- 事件绑定 ---
608+
self._bind_events_to_widget(label)
609+
self._bind_events_to_widget(course_frame)
546610
label.pack(side=tk.LEFT, fill=tk.X, expand=True)
547611

548612
# 根据字体大小计算色块尺寸
@@ -563,6 +627,110 @@ def _create_new_label(self, course: Dict[str, str], color: str, row: int) -> Non
563627

564628
self.schedule_frame.grid_columnconfigure(0, weight=1)
565629

630+
def _bind_schedule_events(self):
631+
"""为课表框架及其所有子控件绑定事件"""
632+
self._bind_events_to_widget(self.schedule_frame)
633+
for child in self.schedule_frame.winfo_children():
634+
self._bind_events_to_widget(child)
635+
if isinstance(child, tk.Frame):
636+
for grandchild in child.winfo_children():
637+
self._bind_events_to_widget(grandchild)
638+
639+
def _bind_events_to_widget(self, widget):
640+
"""辅助函数:为单个控件绑定所有需要的事件"""
641+
widget.bind("<Button-1>", self._on_schedule_press)
642+
widget.bind("<B1-Motion>", self._on_schedule_drag)
643+
widget.bind("<Double-Button-1>", self._on_schedule_double_click)
644+
widget.bind("<Triple-Button-1>", self._on_schedule_triple_click)
645+
646+
def _on_schedule_press(self, event):
647+
"""处理课表区域的鼠标按下事件"""
648+
self.swipe_start_x = event.x
649+
# 如果有重置计时器在运行,则取消它
650+
if self.view_reset_timer:
651+
self.root.after_cancel(self.view_reset_timer)
652+
self.view_reset_timer = None
653+
654+
def _on_schedule_drag(self, event):
655+
"""处理课表区域的拖动事件(滑动)"""
656+
if not self.swipe_start_x:
657+
return
658+
659+
delta_x = event.x - self.swipe_start_x
660+
# 设置一个滑动阈值,防止过于敏感
661+
if abs(delta_x) > 30:
662+
direction = 'left' if delta_x < 0 else 'right'
663+
self._handle_swipe(direction)
664+
self.swipe_start_x = 0 # 重置起始位置,防止一次长滑动触发多次
665+
666+
def _on_schedule_double_click(self, event):
667+
"""处理课表区域的双击事件,立即重置视图"""
668+
self._reset_schedule_view_to_today()
669+
670+
def _handle_swipe(self, direction: str):
671+
"""处理滑动逻辑,切换显示的星期"""
672+
if direction == 'left':
673+
self.displayed_weekday = (self.displayed_weekday + 1) % 7
674+
else:
675+
self.displayed_weekday = (self.displayed_weekday - 1 + 7) % 7
676+
677+
self._update_schedule_display(self.displayed_weekday)
678+
self._start_view_reset_timer()
679+
680+
def _start_view_reset_timer(self):
681+
"""启动一个计时器,在5秒后将视图重置回当天"""
682+
# 如果已有计时器,先取消
683+
if self.view_reset_timer:
684+
self.root.after_cancel(self.view_reset_timer)
685+
686+
# 启动新的5秒计时器
687+
self.view_reset_timer = self.root.after(5000, self._reset_schedule_view_to_today)
688+
689+
def _on_schedule_double_click(self, event):
690+
"""处理课表区域的双击事件,立即重置视图"""
691+
self._reset_schedule_view_to_today()
692+
693+
def _on_schedule_triple_click(self, event):
694+
"""处理课表区域的三击事件,开关周课表预览"""
695+
from tools.week_preview import WeekPreviewWindow
696+
697+
# 如果窗口已存在,则销毁它
698+
if self.week_preview_window and self.week_preview_window.winfo_exists():
699+
self.week_preview_window.destroy()
700+
self.week_preview_window = None
701+
else:
702+
# 创建并显示新窗口
703+
self.week_preview_window = WeekPreviewWindow(self.root, self)
704+
self.week_preview_window.show()
705+
706+
def _handle_swipe(self, direction: str):
707+
"""处理滑动逻辑,切换显示的星期"""
708+
if direction == 'left':
709+
self.displayed_weekday = (self.displayed_weekday + 1) % 7
710+
else:
711+
self.displayed_weekday = (self.displayed_weekday - 1 + 7) % 7
712+
713+
self._update_schedule_display(self.displayed_weekday)
714+
self._start_view_reset_timer()
715+
716+
def _start_view_reset_timer(self):
717+
"""启动一个计时器,在5秒后将视图重置回当天"""
718+
# 如果已有计时器,先取消
719+
if self.view_reset_timer:
720+
self.root.after_cancel(self.view_reset_timer)
721+
722+
# 启动新的5秒计时器
723+
self.view_reset_timer = self.root.after(5000, self._reset_schedule_view_to_today)
724+
725+
def _reset_schedule_view_to_today(self):
726+
"""将课表视图重置为显示当天的课程"""
727+
self.view_reset_timer = None
728+
today_weekday = datetime.now().weekday()
729+
if self.displayed_weekday != today_weekday:
730+
self.displayed_weekday = today_weekday
731+
self._update_schedule_display(self.displayed_weekday)
732+
733+
566734
def _remove_extra_labels(self, schedule: List[Dict[str, str]]) -> None:
567735
"""移除多余的课程标签"""
568736
# 移除所有超出当前课程数量的标签
@@ -670,3 +838,41 @@ def _create_updater_instance(self, Updater):
670838
if self.updater is None:
671839
self.updater = Updater(self.root)
672840
self.updater.start_background_check()
841+
842+
def _check_and_show_tomorrow_preview(self, now: datetime):
843+
"""检查是否需要显示明日课表预览"""
844+
if not self.config_handler.auto_preview_tomorrow_enabled:
845+
return
846+
if self.tomorrow_preview_shown_for_today:
847+
return
848+
if self.week_preview_window and self.week_preview_window.winfo_exists():
849+
return
850+
851+
# 检查当天的所有课程是否都已结束
852+
today_weekday_str = str(now.weekday())
853+
current_schedule_name = self.schedule.get("current_schedule", "default")
854+
schedule_data = self.schedule.get("schedules", {}).get(current_schedule_name, {})
855+
courses_today = schedule_data.get(today_weekday_str, [])
856+
857+
if not courses_today:
858+
return # 今天没课,不触发
859+
860+
all_courses_finished = True
861+
last_course_end_time = None
862+
for course in courses_today:
863+
try:
864+
end_time = datetime.strptime(course['end_time'], "%H:%M").time()
865+
if last_course_end_time is None or end_time > last_course_end_time:
866+
last_course_end_time = end_time
867+
if now.time() <= end_time:
868+
all_courses_finished = False
869+
break
870+
except (ValueError, KeyError):
871+
continue # 忽略格式错误的课程
872+
873+
# 如果所有课程都结束了,且当前时间在最后一节课结束后,则触发
874+
if all_courses_finished and now.time() > last_course_end_time:
875+
from tools.week_preview import WeekPreviewWindow
876+
self.week_preview_window = WeekPreviewWindow(self.root, self, day_offset=1)
877+
self.week_preview_window.show()
878+
self.tomorrow_preview_shown_for_today = True

0 commit comments

Comments
 (0)