PUGE 3 týždňov pred
rodič
commit
a8eb5ccfd1
2 zmenil súbory, kde vykonal 719 pridanie a 45 odobranie
  1. 114 45
      主播工具.py
  2. 605 0
      莫高窟.py

+ 114 - 45
主播工具.py

@@ -193,7 +193,7 @@ class WebSocketClient(QObject):
 class MainWindow(QMainWindow):
     def __init__(self):
         super().__init__()
-        self.setWindowTitle("三栏布局 - 用户独立话术管理 (快捷键F1-F5)")
+        self.setWindowTitle("三栏布局 - 用户独立话术管理 (快捷键F1-F12)")
         self.setGeometry(100, 100, 1300, 750)
         
         # 数据存储:用户话术字典 {用户名: [话术列表]}
@@ -229,7 +229,8 @@ class MainWindow(QMainWindow):
         self.log_text.setPlaceholderText("日志信息将显示在这里...")
         self.log_text.append("[系统] 程序启动")
         self.log_text.append("[系统] 界面初始化完成")
-        self.log_text.append("[系统] 快捷键: F1-F5 快速切换用户")
+        self.log_text.append("[系统] 快捷键: F1-F12 快速切换用户")
+        self.log_text.append("[系统] 快捷键: Ctrl+Q/W/E/R/T/Y/U/I/O 发送第1-9条话术")
         left_layout.addWidget(self.log_text)
         
         log_btn_layout = QHBoxLayout()
@@ -286,7 +287,7 @@ class MainWindow(QMainWindow):
         
         # 顶部:用户选择下拉框
         user_select_layout = QHBoxLayout()
-        user_select_layout.addWidget(QLabel("选择用户 (快捷键F1-F5):"))
+        user_select_layout.addWidget(QLabel("选择用户 (快捷键F1-F12):"))
         self.user_combo = QComboBox()
         self.user_combo.currentTextChanged.connect(self.on_user_changed)
         user_select_layout.addWidget(self.user_combo)
@@ -351,9 +352,12 @@ class MainWindow(QMainWindow):
         main_splitter.addWidget(right_widget)
         main_splitter.setSizes([300, 450, 450])
         
-        # ---------- 设置快捷键 F1-F5 ----------
+        # ---------- 设置快捷键 F1-F12 ----------
         self.setup_shortcuts()
         
+        # ---------- 设置快捷键 Ctrl+Q/W/E/R/T/Y/U/I/O 发送话术 ----------
+        self.setup_send_shortcuts()
+        
         # 窗口样式
         self.setStyleSheet("""
             QGroupBox {
@@ -382,6 +386,88 @@ class MainWindow(QMainWindow):
         
         # 连接WebSocket
         self.connect_websocket()
+        
+        # 加载保存的话术配置
+        self.load_scripts_from_file()
+    
+    # ---------- 加载保存的话术 ----------
+    def load_scripts_from_file(self):
+        """从文件加载话术配置"""
+        try:
+            with open("user_scripts_backup.json", "r", encoding="utf-8") as f:
+                saved_scripts = json.load(f)
+                self.user_scripts.update(saved_scripts)
+                self.log_text.append("[系统] 已加载保存的话术配置")
+        except FileNotFoundError:
+            self.log_text.append("[系统] 未找到保存的话术配置文件,使用默认配置")
+        except Exception as e:
+            self.log_text.append(f"[系统] 加载话术配置失败: {e}")
+    
+    # ---------- 快捷键设置 ----------
+    def setup_send_shortcuts(self):
+        """设置指定快捷键发送话术"""
+        # 快捷键映射:键位 -> 话术索引
+        shortcuts_map = [
+            (Qt.Key_Z, 0),   # Ctrl+Z -> 第1条
+            (Qt.Key_X, 1),   # Ctrl+X -> 第2条
+            (Qt.Key_C, 2),   # Ctrl+C -> 第3条
+            (Qt.Key_V, 3),   # Ctrl+V -> 第4条
+            (Qt.Key_B, 4),   # Ctrl+B -> 第5条
+            (Qt.Key_N, 5),   # Ctrl+N -> 第6条
+            (Qt.Key_M, 6),   # Ctrl+M -> 第7条
+            (Qt.Key_A, 7),   # Ctrl+A -> 第8条
+            (Qt.Key_S, 8),   # Ctrl+S -> 第9条
+        ]
+        
+        for key, index in shortcuts_map:
+            shortcut = QShortcut(QKeySequence("Ctrl+" + chr(key)), self)
+            shortcut.activated.connect(lambda idx=index: self.send_script_by_index(idx))
+    
+    def send_script_by_index(self, index):
+        """通过索引发送话术给当前选中的用户"""
+        # 检查当前是否有选中的用户
+        if not self.current_user:
+            self.log_text.append(f"[快捷键] 当前没有选中任何用户")
+            QMessageBox.warning(self, "提示", "请先选择一个用户!")
+            return
+        
+        # 检查当前用户是否有话术
+        if self.current_user not in self.user_scripts:
+            self.log_text.append(f"[快捷键] 用户 '{self.current_user}' 没有话术")
+            QMessageBox.warning(self, "提示", f"用户 '{self.current_user}' 没有话术!")
+            return
+        
+        # 检查索引是否有效
+        scripts = self.user_scripts[self.current_user]
+        if index >= len(scripts):
+            self.log_text.append(f"[快捷键] 用户 '{self.current_user}' 只有 {len(scripts)} 条话术,无法发送第 {index+1} 条")
+            QMessageBox.warning(self, "提示", f"用户 '{self.current_user}' 只有 {len(scripts)} 条话术!")
+            return
+        
+        # 获取话术
+        script = scripts[index]
+        
+        # 发送话术给当前用户
+        self.send_script_to_user(script, self.current_user)
+    
+    def send_script_to_user(self, script, user):
+        """发送话术给指定用户"""
+        if not script.strip():
+            QMessageBox.warning(self, "提示", "话术内容为空!")
+            return
+        
+        # 记录日志
+        self.log_text.append(f"[任务] 发送话术: {script}")
+        self.log_text.append(f"[任务] 发送给用户: {user}")
+        
+        # 通过WebSocket发送任务
+        if self.ws_client and self.ws_client.is_connected:
+            if self.ws_client.send("sendmessage", script, user):
+                self.log_text.append(f"[任务] 已成功发送给用户: {user}")
+            else:
+                self.log_text.append(f"[任务] 发送给用户 {user} 失败")
+        else:
+            self.log_text.append("[任务] WebSocket未连接,无法发送")
     
     # ---------- WebSocket连接管理 ----------
     def connect_websocket(self):
@@ -453,7 +539,6 @@ class MainWindow(QMainWindow):
     def update_user_list(self, user_list):
         """更新用户列表UI"""
         # 保存当前选中的用户
-        selected_users = self.get_selected_users()
         current_combo_text = self.user_combo.currentText()
         
         # 清空列表
@@ -464,7 +549,9 @@ class MainWindow(QMainWindow):
             self.add_user_item(user_id)
             # 如果用户不存在,初始化话术数据
             if user_id not in self.user_scripts:
-                self.user_scripts[user_id] = [f"{user_id},666"]
+                self.user_scripts[user_id] = [f"话术1: 您好{user_id},请问有什么可以帮您?", 
+                                             f"话术2: {user_id},感谢您的反馈,我们会尽快处理。",
+                                             f"话术3: {user_id},请您稍等,我为您转接专员。"]
         
         # 更新下拉框
         self.user_combo.clear()
@@ -476,16 +563,6 @@ class MainWindow(QMainWindow):
         elif user_list:
             self.user_combo.setCurrentIndex(0)
         
-        # 恢复复选框选中状态
-        for user in selected_users:
-            if user in user_list:
-                for i in range(self.user_list_widget.count()):
-                    item = self.user_list_widget.item(i)
-                    widget = self.user_list_widget.itemWidget(item)
-                    if isinstance(widget, QCheckBox) and widget.text() == user:
-                        widget.setChecked(True)
-                        break
-        
         # 更新人数
         self.user_count_label.setText(f"当前在线人数: {len(user_list)}")
     
@@ -546,15 +623,22 @@ class MainWindow(QMainWindow):
         elif current_users:
             self.user_combo.setCurrentIndex(0)
     
-    # ---------- 快捷键设置 ----------
+    # ---------- 快捷键设置 F1-F12 ----------
     def setup_shortcuts(self):
-        """设置F1-F5快捷键切换用户"""
+        """设置F1-F12快捷键切换用户"""
         shortcuts = [
             (Qt.Key_F1, 0),
             (Qt.Key_F2, 1),
             (Qt.Key_F3, 2),
             (Qt.Key_F4, 3),
             (Qt.Key_F5, 4),
+            (Qt.Key_F6, 5),
+            (Qt.Key_F7, 6),
+            (Qt.Key_F8, 7),
+            (Qt.Key_F9, 8),
+            (Qt.Key_F10, 9),
+            (Qt.Key_F11, 10),
+            (Qt.Key_F12, 11),
         ]
         
         for key, index in shortcuts:
@@ -562,14 +646,15 @@ class MainWindow(QMainWindow):
             shortcut.activated.connect(lambda idx=index: self.switch_to_user_by_index(idx))
     
     def switch_to_user_by_index(self, index):
-        """根据索引切换到对应用户"""
+        """根据索引切换到对应用户,并同步更新下拉框"""
         if index < self.user_combo.count():
             user = self.user_combo.itemText(index)
             if user:
+                # 设置下拉框当前选中,这会触发 on_user_changed 自动加载话术
                 self.user_combo.setCurrentText(user)
-                self.log_text.append(f"[快捷键] 切换到用户: {user}")
+                self.log_text.append(f"[快捷键 F{index+1}] 切换到用户: {user}")
         else:
-            self.log_text.append(f"[快捷键] 用户索引 {index} 不存在")
+            self.log_text.append(f"[快捷键] 用户索引 {index} 不存在(当前共 {self.user_combo.count()} 个用户)")
     
     # ---------- 话术管理 (用户独立) ----------
     def load_user_scripts(self, username):
@@ -658,15 +743,15 @@ class MainWindow(QMainWindow):
     def clear_log(self):
         self.log_text.clear()
         self.log_text.append("[系统] 日志已清空")
-        self.log_text.append("[系统] 快捷键: F1-F5 快速切换用户")
+        self.log_text.append("[系统] 快捷键: F1-F12 快速切换用户")
+        self.log_text.append("[系统] 快捷键: Ctrl+Z/X/C/V/B/N/M/A/S 发送第1-9条话术")
     
     # ---------- 发送任务 ----------
     def send_task(self):
-        """发送任务到选中的用户"""
-        selected_users = self.get_selected_users()
-        
-        if not selected_users:
-            QMessageBox.warning(self, "提示", "请至少选择一个用户!")
+        """发送任务到当前选中的用户(使用当前选中的话术)"""
+        # 检查当前是否有选中的用户
+        if not self.current_user:
+            QMessageBox.warning(self, "提示", "请先选择一个用户!")
             return
         
         # 获取当前选中的话术
@@ -677,24 +762,8 @@ class MainWindow(QMainWindow):
         
         selected_script = self.script_list_widget.item(current_row).text()
         
-        # 记录日志
-        self.log_text.append(f"[任务] 发送话术: {selected_script}")
-        self.log_text.append(f"[任务] 发送给 {len(selected_users)} 个用户:")
-        
-        # 通过WebSocket发送任务
-        if self.ws_client and self.ws_client.is_connected:
-            success_count = 0
-            for user in selected_users:
-                # 发送给指定用户,路由为 "sendmessage"
-                if self.ws_client.send("sendmessage", selected_script, user):
-                    success_count += 1
-                    self.log_text.append(f"[任务] 已发送给用户: {user}")
-                else:
-                    self.log_text.append(f"[任务] 发送给用户 {user} 失败")
-            
-            self.log_text.append(f"[任务] 发送完成,成功发送给 {success_count}/{len(selected_users)} 个用户")
-        else:
-            self.log_text.append("[任务] WebSocket未连接,无法发送")
+        # 发送话术给当前用户
+        self.send_script_to_user(selected_script, self.current_user)
     
     # ---------- 窗口关闭时保存 ----------
     def closeEvent(self, event):

+ 605 - 0
莫高窟.py

@@ -0,0 +1,605 @@
+import tkinter as tk
+from tkinter import ttk, scrolledtext
+import requests
+import json
+import threading
+import time
+from datetime import datetime
+import winsound
+import os
+import random
+import string
+
+class TicketMonitor:
+    def __init__(self, root):
+        self.root = root
+        self.root.title("余票监控系统")
+        self.root.geometry("1000x850")
+        
+        # 监控状态
+        self.is_monitoring = False
+        self.monitor_thread = None
+        
+        # 默认API配置
+        self.api_url = "https://newmanage.mgk.org.cn/campaign/noTokenapi/listCinemaNumByDay?corpCode=0001"
+        self.headers_template = {
+            'Connection': 'keep-alive',
+            'content-type': 'application/json',
+            'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 26_5_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 MicroMessenger/8.0.75(0x18004b3c) NetType/WIFI Language/zh_CN',
+            'Referer': 'https://servicewechat.com/wx66fdc5f2e062a173/163/page-frame.html'
+        }
+        self.payload = {
+            "corpCode": "0001",
+            "orderFlag": "C"
+        }
+        
+        # 生成固定的Token前缀 + 随机6位
+        self.token_prefix = "eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiIxODU5MTc3NzI4MSIsImNvcnBOYW1lIjoi5rKz5Y2X5bmz5Y-w6YKu5Lu35Yy6IiwiY29ycENvZGUiOiIwMDAxIiwiZXhwIjoxNzU1NDE1MTY3LCJpYXQiOjE3MjM4NzkxNjcsImp0aSI6IjY2Y2NmODVkLTUzMDEtNDdmMS1hZmI4LWM5NDU5YjI3YzdmOSJ9."
+        
+        # 生成随机6位字符(字母+数字)
+        self.random_suffix = ''.join(random.choices(string.ascii_letters + string.digits, k=6))
+        self.token = self.token_prefix + self.random_suffix
+        
+        # 请求计数器
+        self.request_count = 0
+        self.auto_refresh_interval = 30  # 每30次请求自动刷新Token
+        
+        # 用于记录已通知的日期
+        self.notified_dates = set()
+        
+        # 日期选择相关变量
+        self.selected_dates = set()  # 选中的日期
+        
+        # 初始化UI
+        self.setup_ui()
+        
+        # 显示生成的Token信息(仅日志)
+        self.log(f"Token已自动生成,后缀: {self.random_suffix}", "info")
+        
+    def setup_ui(self):
+        # 创建主框架
+        main_frame = ttk.Frame(self.root, padding="10")
+        main_frame.pack(fill=tk.BOTH, expand=True)
+        
+        # ========== 监控设置区域 ==========
+        settings_frame = ttk.LabelFrame(main_frame, text="监控设置", padding="10")
+        settings_frame.pack(fill=tk.X, pady=(0, 10))
+        
+        # 第一行:基本设置
+        basic_frame = ttk.Frame(settings_frame)
+        basic_frame.pack(fill=tk.X, pady=5)
+        
+        ttk.Label(basic_frame, text="监控间隔(秒):").pack(side=tk.LEFT, padx=5)
+        self.interval_var = tk.StringVar(value="10")
+        interval_spinbox = ttk.Spinbox(basic_frame, from_=3, to=60, textvariable=self.interval_var, width=5)
+        interval_spinbox.pack(side=tk.LEFT, padx=5)
+        
+        # 应急票监控开关
+        ttk.Label(basic_frame, text="应急票监控:").pack(side=tk.LEFT, padx=(20, 5))
+        self.monitor_emergency_var = tk.BooleanVar(value=True)
+        emergency_switch = ttk.Checkbutton(basic_frame, text="开启", variable=self.monitor_emergency_var)
+        emergency_switch.pack(side=tk.LEFT, padx=5)
+        
+        # 只提醒一次
+        self.notify_once_var = tk.BooleanVar(value=True)
+        ttk.Checkbutton(basic_frame, text="📌 只提醒一次", variable=self.notify_once_var).pack(side=tk.LEFT, padx=20)
+        
+        # 声音提醒
+        self.sound_var = tk.BooleanVar(value=True)
+        ttk.Checkbutton(basic_frame, text="🔊 声音提醒", variable=self.sound_var).pack(side=tk.LEFT, padx=5)
+        
+        # 第二行:日期选择模式
+        date_mode_frame = ttk.Frame(settings_frame)
+        date_mode_frame.pack(fill=tk.X, pady=5)
+        
+        ttk.Label(date_mode_frame, text="监控模式:", width=15).pack(side=tk.LEFT, padx=5)
+        
+        self.monitor_mode = tk.StringVar(value="all")
+        ttk.Radiobutton(date_mode_frame, text="所有日期", variable=self.monitor_mode, 
+                       value="all", command=self.on_mode_change).pack(side=tk.LEFT, padx=5)
+        ttk.Radiobutton(date_mode_frame, text="选择日期", variable=self.monitor_mode, 
+                       value="selected", command=self.on_mode_change).pack(side=tk.LEFT, padx=5)
+        
+        # 快捷选择按钮
+        self.select_all_btn = ttk.Button(date_mode_frame, text="全选", command=self.select_all_dates, state=tk.DISABLED)
+        self.select_all_btn.pack(side=tk.LEFT, padx=5)
+        
+        self.deselect_all_btn = ttk.Button(date_mode_frame, text="取消全选", command=self.deselect_all_dates, state=tk.DISABLED)
+        self.deselect_all_btn.pack(side=tk.LEFT, padx=5)
+        
+        # Token状态显示(替代原来的刷新按钮)
+        token_status_frame = ttk.Frame(date_mode_frame)
+        token_status_frame.pack(side=tk.RIGHT, padx=5)
+        
+        self.token_status_label = ttk.Label(token_status_frame, text=f"Token: {self.random_suffix} (0次)")
+        self.token_status_label.pack()
+        
+        # 控制按钮
+        control_frame = ttk.Frame(settings_frame)
+        control_frame.pack(fill=tk.X, pady=10)
+        
+        self.start_btn = ttk.Button(control_frame, text="▶ 开始监控", command=self.start_monitoring, width=12)
+        self.start_btn.pack(side=tk.LEFT, padx=5)
+        
+        self.stop_btn = ttk.Button(control_frame, text="■ 停止监控", command=self.stop_monitoring, state=tk.DISABLED, width=12)
+        self.stop_btn.pack(side=tk.LEFT, padx=5)
+        
+        ttk.Button(control_frame, text="🔍 立即查询", command=self.manual_check, width=12).pack(side=tk.LEFT, padx=5)
+        
+        # ========== 状态显示 ==========
+        status_frame = ttk.LabelFrame(main_frame, text="状态信息", padding="5")
+        status_frame.pack(fill=tk.X, pady=(0, 10))
+        
+        status_left = ttk.Frame(status_frame)
+        status_left.pack(side=tk.LEFT, fill=tk.X, expand=True)
+        
+        self.status_label = ttk.Label(status_left, text="⏹ 未开始监控", foreground="gray")
+        self.status_label.pack(side=tk.LEFT, padx=5)
+        
+        status_right = ttk.Frame(status_frame)
+        status_right.pack(side=tk.RIGHT)
+        
+        self.last_update_label = ttk.Label(status_right, text="最后更新: --")
+        self.last_update_label.pack(side=tk.RIGHT, padx=5)
+        
+        self.count_label = ttk.Label(status_right, text="发现票数: 0")
+        self.count_label.pack(side=tk.RIGHT, padx=20)
+        
+        # ========== 日期选择区域 ==========
+        date_select_frame = ttk.LabelFrame(main_frame, text="日期选择 (点击选择/取消选择日期)", padding="10")
+        date_select_frame.pack(fill=tk.X, pady=(0, 10))
+        
+        # 创建日期选择框架
+        date_canvas_frame = ttk.Frame(date_select_frame)
+        date_canvas_frame.pack(fill=tk.X, pady=5)
+        
+        self.date_checkboxes = {}  # 存储日期和对应的Checkbutton变量
+        self.date_check_vars = {}  # 存储BooleanVar
+        
+        # 创建日期选择容器
+        self.date_container = ttk.Frame(date_canvas_frame)
+        self.date_container.pack(fill=tk.X)
+        
+        # 先创建默认的日期占位
+        self.init_date_checkboxes()
+        
+        # ========== 结果显示区域 ==========
+        result_frame = ttk.LabelFrame(main_frame, text="余票信息", padding="5")
+        result_frame.pack(fill=tk.BOTH, expand=True, pady=(0, 10))
+        
+        # 创建Treeview显示数据
+        columns = ("日期", "常规票(A)", "应急票(H)", "状态")
+        self.tree = ttk.Treeview(result_frame, columns=columns, show="headings", height=12)
+        
+        # 设置列标题和宽度
+        column_widths = {"日期": 120, "常规票(A)": 100, "应急票(H)": 100, "状态": 100}
+        for col in columns:
+            self.tree.heading(col, text=col)
+            self.tree.column(col, width=column_widths.get(col, 100), anchor="center")
+        
+        # 添加滚动条
+        scrollbar = ttk.Scrollbar(result_frame, orient=tk.VERTICAL, command=self.tree.yview)
+        self.tree.configure(yscrollcommand=scrollbar.set)
+        
+        self.tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
+        scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
+        
+        # ========== 日志区域 ==========
+        log_frame = ttk.LabelFrame(main_frame, text="日志", padding="5")
+        log_frame.pack(fill=tk.BOTH, expand=True)
+        
+        self.log_text = scrolledtext.ScrolledText(log_frame, height=6, state=tk.DISABLED)
+        self.log_text.pack(fill=tk.BOTH, expand=True)
+        
+        # 配置日志颜色标签
+        self.log_text.tag_configure("error", foreground="red")
+        self.log_text.tag_configure("success", foreground="green")
+        self.log_text.tag_configure("info", foreground="blue")
+        self.log_text.tag_configure("warning", foreground="orange")
+    
+    def init_date_checkboxes(self):
+        """初始化日期复选框"""
+        # 清空现有复选框
+        for widget in self.date_container.winfo_children():
+            widget.destroy()
+        
+        self.date_checkboxes.clear()
+        self.date_check_vars.clear()
+        
+        # 创建示例日期(实际使用时会从API获取真实日期)
+        example_dates = []
+        
+        row_frame = None
+        for i, date in enumerate(example_dates):
+            if i % 8 == 0:
+                row_frame = ttk.Frame(self.date_container)
+                row_frame.pack(fill=tk.X, pady=2)
+            
+            var = tk.BooleanVar(value=True)
+            self.date_check_vars[date] = var
+            cb = ttk.Checkbutton(row_frame, text=date, variable=var, 
+                               command=lambda d=date: self.on_date_toggle(d))
+            cb.pack(side=tk.LEFT, padx=10)
+            self.date_checkboxes[date] = cb
+            
+            # 默认全选
+            self.selected_dates.add(date)
+    
+    def update_date_checkboxes(self, dates):
+        """更新日期复选框列表"""
+        # 清空现有复选框
+        for widget in self.date_container.winfo_children():
+            widget.destroy()
+        
+        self.date_checkboxes.clear()
+        self.date_check_vars.clear()
+        self.selected_dates.clear()
+        
+        # 按日期排序
+        sorted_dates = sorted(dates)
+        
+        row_frame = None
+        for i, date in enumerate(sorted_dates):
+            if i % 8 == 0:
+                row_frame = ttk.Frame(self.date_container)
+                row_frame.pack(fill=tk.X, pady=2)
+            
+            var = tk.BooleanVar(value=True)  # 默认选中
+            self.date_check_vars[date] = var
+            cb = ttk.Checkbutton(row_frame, text=date, variable=var, 
+                               command=lambda d=date: self.on_date_toggle(d))
+            cb.pack(side=tk.LEFT, padx=10)
+            self.date_checkboxes[date] = cb
+            
+            # 添加到选中集合
+            self.selected_dates.add(date)
+    
+    def on_date_toggle(self, date):
+        """日期选择切换"""
+        if date in self.date_check_vars:
+            if self.date_check_vars[date].get():
+                self.selected_dates.add(date)
+                self.log(f"已选择日期: {date}", "info")
+            else:
+                self.selected_dates.discard(date)
+                self.log(f"已取消选择日期: {date}", "warning")
+    
+    def select_all_dates(self):
+        """全选日期"""
+        for date, var in self.date_check_vars.items():
+            var.set(True)
+            self.selected_dates.add(date)
+        self.log("已全选所有日期", "info")
+    
+    def deselect_all_dates(self):
+        """取消全选"""
+        for date, var in self.date_check_vars.items():
+            var.set(False)
+            self.selected_dates.discard(date)
+        self.log("已取消全选所有日期", "warning")
+    
+    def on_mode_change(self):
+        """监控模式切换"""
+        if self.monitor_mode.get() == "selected":
+            self.select_all_btn.config(state=tk.NORMAL)
+            self.deselect_all_btn.config(state=tk.NORMAL)
+            self.log("切换到日期选择模式,请选择需要监控的日期", "info")
+        else:
+            self.select_all_btn.config(state=tk.DISABLED)
+            self.deselect_all_btn.config(state=tk.DISABLED)
+            self.log("切换到所有日期模式", "info")
+    
+    def regenerate_token(self):
+        """重新生成Token(随机6位后缀)"""
+        self.random_suffix = ''.join(random.choices(string.ascii_letters + string.digits, k=6))
+        self.token = self.token_prefix + self.random_suffix
+        self.log(f"Token已重新生成,新后缀: {self.random_suffix}", "info")
+        # 更新Token状态显示
+        self.token_status_label.config(text=f"Token: {self.random_suffix} ({self.request_count}次)")
+    
+    def get_headers(self):
+        """获取请求头"""
+        headers = self.headers_template.copy()
+        headers['access-token'] = self.token
+        return headers
+    
+    def log(self, message, tag=None):
+        """添加日志消息"""
+        self.log_text.config(state=tk.NORMAL)
+        timestamp = datetime.now().strftime("%H:%M:%S")
+        
+        # 添加时间戳
+        self.log_text.insert(tk.END, f"[{timestamp}] ", "info")
+        
+        # 添加消息(可带颜色标签)
+        if tag:
+            self.log_text.insert(tk.END, f"{message}\n", tag)
+        else:
+            self.log_text.insert(tk.END, f"{message}\n")
+        
+        self.log_text.see(tk.END)
+        self.log_text.config(state=tk.DISABLED)
+    
+    def update_status(self, message, color="black"):
+        """更新状态显示"""
+        self.status_label.config(text=message, foreground=color)
+    
+    def fetch_ticket_data(self):
+        """获取余票数据"""
+        # 增加请求计数并自动刷新Token
+        self.request_count += 1
+        
+        # 每30次请求自动刷新Token
+        if self.request_count % self.auto_refresh_interval == 0:
+            self.regenerate_token()
+            self.log(f"🔄 已自动刷新Token (第{self.request_count}次请求)", "info")
+        
+        headers = self.get_headers()
+        
+        try:
+            response = requests.post(
+                self.api_url, 
+                headers=headers, 
+                json=self.payload, 
+                timeout=10
+            )
+            if response.status_code == 200:
+                data = response.json()
+                if data.get('status') == 200:
+                    return data.get('data', [])
+                else:
+                    self.log(f"API返回错误: {data}", "error")
+                    return None
+            else:
+                self.log(f"HTTP请求失败: {response.status_code}", "error")
+                return None
+        except requests.exceptions.Timeout:
+            self.log("请求超时,请检查网络连接", "error")
+            return None
+        except requests.exceptions.ConnectionError:
+            self.log("网络连接失败,请检查网络", "error")
+            return None
+        except Exception as e:
+            self.log(f"请求异常: {str(e)}", "error")
+            return None
+    
+    def should_monitor_date(self, date):
+        """检查是否应该监控该日期"""
+        if self.monitor_mode.get() == "all":
+            return True
+        else:
+            return date in self.selected_dates
+    
+    def check_and_notify(self, ticket_data):
+        """检查余票并发送通知"""
+        new_notified = set()
+        total_tickets = 0
+        
+        # 更新日期复选框(如果有新日期)
+        dates_in_data = [item.get('DAY', '') for item in ticket_data if item.get('DAY')]
+        if dates_in_data:
+            # 获取当前显示的日期列表
+            current_dates = set(self.date_check_vars.keys())
+            new_dates = set(dates_in_data) - current_dates
+            
+            if new_dates:
+                # 如果有新日期,更新复选框
+                self.update_date_checkboxes(dates_in_data)
+                self.log(f"更新日期列表,新增 {len(new_dates)} 个日期", "info")
+        
+        for item in ticket_data:
+            day = item.get('DAY', '')
+            A_num = int(item.get('A_num', 0))
+            H_num = int(item.get('H_num', 0))
+            
+            # 检查是否应该监控该日期
+            if not self.should_monitor_date(day):
+                continue
+            
+            # 检查是否监控应急票
+            monitor_emergency = self.monitor_emergency_var.get()
+            
+            # 统计有票的日期(根据设置决定是否检查应急票)
+            has_ticket = False
+            if monitor_emergency:
+                has_ticket = A_num > 0 or H_num > 0
+            else:
+                has_ticket = A_num > 0  # 只监控常规票
+            
+            if has_ticket:
+                total_tickets += 1
+                
+                # 判断是否应该提醒
+                should_notify = False
+                if self.notify_once_var.get():
+                    if day not in self.notified_dates:
+                        should_notify = True
+                        new_notified.add(day)
+                else:
+                    should_notify = True
+                    new_notified.add(day)
+                
+                # 发送提醒
+                if should_notify:
+                    ticket_parts = []
+                    if A_num > 0:
+                        ticket_parts.append(f"常规票{A_num}张")
+                    if monitor_emergency and H_num > 0:
+                        ticket_parts.append(f"应急票{H_num}张")
+                    
+                    msg = f"🎫 {day} 有票了!{' + '.join(ticket_parts)}"
+                    self.log(msg, "success")
+                    self.update_status(f"🎉 发现余票!{day}", "green")
+                    
+                    # 声音提醒
+                    if self.sound_var.get():
+                        self.play_sound()
+        
+        # 更新已通知日期集合
+        if self.notify_once_var.get():
+            self.notified_dates.update(new_notified)
+        else:
+            self.notified_dates = set()
+        
+        # 更新计数
+        self.count_label.config(text=f"发现票数: {total_tickets}")
+        
+        return total_tickets
+    
+    def play_sound(self):
+        """播放声音提醒"""
+        try:
+            winsound.MessageBeep(winsound.MB_ICONASTERISK)
+        except Exception as e:
+            pass
+    
+    def update_display(self, ticket_data):
+        """更新表格显示"""
+        # 清空现有数据
+        for item in self.tree.get_children():
+            self.tree.delete(item)
+        
+        if not ticket_data:
+            self.log("没有获取到数据", "warning")
+            return
+        
+        # 添加新数据
+        for item in ticket_data:
+            day = item.get('DAY', '')
+            A_num = item.get('A_num', '0')
+            H_num = item.get('H_num', '0')
+            sumNum = item.get('sumNum', '无票')
+            
+            # 检查是否应该监控该日期
+            if not self.should_monitor_date(day):
+                continue
+            
+            values = (day, A_num, H_num, sumNum)
+            item_id = self.tree.insert("", tk.END, values=values)
+            
+            # 修改颜色逻辑:常规票有票时显示绿色
+            monitor_emergency = self.monitor_emergency_var.get()
+            
+            # 检查常规票(A)是否有票
+            has_regular_ticket = int(A_num) > 0
+            
+            # 检查应急票(H)是否有票
+            has_emergency_ticket = monitor_emergency and int(H_num) > 0
+            
+            # 只要有常规票,就显示绿色
+            if has_regular_ticket:
+                self.tree.tag_configure('has_regular_ticket', background='#90EE90')  # 亮绿色
+                self.tree.item(item_id, tags=('has_regular_ticket',))
+            # 如果只有应急票,显示浅蓝色
+            elif has_emergency_ticket:
+                self.tree.tag_configure('has_emergency_ticket', background='#B0E0E6')  # 浅蓝色
+                self.tree.item(item_id, tags=('has_emergency_ticket',))
+            # 无票不显示特殊颜色
+    
+    def manual_check(self):
+        """手动查询"""
+        self.log("手动查询中...", "info")
+        self.update_status("查询中...", "orange")
+        
+        data = self.fetch_ticket_data()
+        if data:
+            self.update_display(data)
+            total = self.check_and_notify(data)
+            self.last_update_label.config(text=f"最后更新: {datetime.now().strftime('%H:%M:%S')}")
+            self.update_status(f"查询完成,发现 {total} 天有票", "blue")
+            self.log(f"查询完成,共 {len(data)} 天数据", "info")
+        else:
+            self.update_status("查询失败", "red")
+    
+    def monitoring_loop(self):
+        """监控循环"""
+        self.log("开始监控...", "info")
+        self.update_status("🔄 监控运行中", "green")
+        
+        check_count = 0
+        while self.is_monitoring:
+            try:
+                check_count += 1
+                self.log(f"第 {check_count} 次检测...", "info")
+                
+                # 获取数据
+                data = self.fetch_ticket_data()
+                if data:
+                    self.update_display(data)
+                    self.check_and_notify(data)
+                    self.last_update_label.config(text=f"最后更新: {datetime.now().strftime('%H:%M:%S')}")
+                    self.update_status(f"🔄 监控运行中 (第{check_count}次)", "green")
+                    
+                    # 更新Token状态显示
+                    self.token_status_label.config(text=f"Token: {self.random_suffix} ({self.request_count}次)")
+                else:
+                    self.update_status("⚠️ 监控运行中 (获取数据失败)", "orange")
+                
+                # 等待指定的间隔
+                interval = int(self.interval_var.get())
+                for _ in range(interval):
+                    if not self.is_monitoring:
+                        break
+                    time.sleep(1)
+            
+            except Exception as e:
+                self.log(f"监控循环错误: {str(e)}", "error")
+                time.sleep(5)
+        
+        self.log("监控已停止", "info")
+        self.update_status("⏹ 已停止", "gray")
+    
+    def start_monitoring(self):
+        """开始监控"""
+        if self.is_monitoring:
+            return
+        
+        self.is_monitoring = True
+        self.start_btn.config(state=tk.DISABLED)
+        self.stop_btn.config(state=tk.NORMAL)
+        
+        # 重置计数器
+        self.request_count = 0
+        self.token_status_label.config(text=f"Token: {self.random_suffix} (0次)")
+        
+        # 清空已通知列表
+        self.notified_dates.clear()
+        
+        # 在新线程中运行监控
+        self.monitor_thread = threading.Thread(target=self.monitoring_loop, daemon=True)
+        self.monitor_thread.start()
+    
+    def stop_monitoring(self):
+        """停止监控"""
+        self.is_monitoring = False
+        self.start_btn.config(state=tk.NORMAL)
+        self.stop_btn.config(state=tk.DISABLED)
+        
+        if self.monitor_thread:
+            self.monitor_thread.join(timeout=2)
+        
+        self.update_status("⏹ 已停止", "gray")
+
+def main():
+    root = tk.Tk()
+    app = TicketMonitor(root)
+    
+    # 窗口关闭处理
+    def on_closing():
+        app.stop_monitoring()
+        root.destroy()
+    
+    root.protocol("WM_DELETE_WINDOW", on_closing)
+    
+    # 居中显示
+    root.update_idletasks()
+    width = root.winfo_width()
+    height = root.winfo_height()
+    x = (root.winfo_screenwidth() // 2) - (width // 2)
+    y = (root.winfo_screenheight() // 2) - (height // 2)
+    root.geometry(f'+{x}+{y}')
+    
+    root.mainloop()
+
+if __name__ == "__main__":
+    main()