Files
damai/GUI.py
Jeason b16a782f67
Some checks failed
Deploy static content to Pages / deploy (push) Has been cancelled
new changes
2026-03-13 12:05:03 +08:00

662 lines
28 KiB
Python

import json
import threading
import time
import tkinter as tk
from pathlib import Path
from tkinter import filedialog, messagebox, ttk
from scripts.mock_dependency_manager import (
build_mock_steps,
build_report,
default_dependencies,
)
class TicketHelperGUI:
def __init__(self):
self.window = tk.Tk()
self.window.title("抢票助手 V5.0")
self.window.geometry("1280x900")
self.window.config(bg="#e3f2fd")
self.window.resizable(False, False)
self.style = ttk.Style()
self.style.configure(
"TButton",
font=("Arial", 12),
padding=8,
relief="flat",
background="#3498db",
foreground="white",
)
self.style.map("TButton", background=[("active", "#2980b9")])
self.style.configure("TCheckbutton", font=("Arial", 11), foreground="#333")
self.style.configure("TLabel", font=("Arial", 11), foreground="#333")
self.style.configure("TLabelframe.Label", font=("Arial", 12, "bold"))
self.create_menus()
self.create_widgets()
def create_menus(self):
menu_bar = tk.Menu(self.window)
file_menu = tk.Menu(menu_bar, tearoff=0)
file_menu.add_command(label="保存配置", command=self.save_config)
file_menu.add_command(label="加载配置", command=self.load_config)
file_menu.add_command(label="加载示例配置", command=self.load_demo_config)
file_menu.add_separator()
file_menu.add_command(label="退出", command=self.window.quit)
menu_bar.add_cascade(label="文件", menu=file_menu)
help_menu = tk.Menu(menu_bar, tearoff=0)
help_menu.add_command(label="关于", command=self.show_about)
menu_bar.add_cascade(label="帮助", menu=help_menu)
self.window.config(menu=menu_bar)
def create_widgets(self):
main_frame = tk.Frame(self.window, bg="#e3f2fd")
main_frame.place(relwidth=1, relheight=1)
title = ttk.Label(
main_frame,
text="抢票助手配置中心",
font=("Arial", 26, "bold"),
foreground="#2c3e50",
background="#e3f2fd",
)
title.pack(pady=20)
self.notebook = ttk.Notebook(main_frame)
self.notebook.pack(padx=20, pady=10, fill=tk.BOTH, expand=True)
self.create_global_tab()
self.create_account_tab()
self.create_strategy_tab()
self.create_monitor_tab()
self.create_notification_tab()
self.create_plugin_tab()
self.create_dependency_tab()
control_frame = tk.LabelFrame(
main_frame,
text="任务控制",
font=("Arial", 12),
bg="#ffffff",
fg="#333",
padx=10,
pady=10,
)
control_frame.pack(fill=tk.X, padx=20, pady=5)
self.auto_buy_check_var = tk.BooleanVar(value=True)
ttk.Checkbutton(
control_frame,
text="启用自动抢票",
variable=self.auto_buy_check_var,
).grid(row=0, column=0, padx=10, pady=5, sticky="w")
self.start_button = ttk.Button(control_frame, text="开始抢票", command=self.start_ticket_task)
self.start_button.grid(row=0, column=1, padx=10)
self.stop_button = ttk.Button(
control_frame,
text="停止任务",
command=self.stop_ticket_task,
state=tk.DISABLED,
)
self.stop_button.grid(row=0, column=2, padx=10)
self.restart_button = ttk.Button(
control_frame,
text="重试任务",
command=self.retry_ticket_task,
state=tk.DISABLED,
)
self.restart_button.grid(row=0, column=3, padx=10)
status_frame = tk.Frame(main_frame, bg="#e3f2fd")
status_frame.pack(fill=tk.X, padx=20, pady=5)
self.status_label = ttk.Label(
status_frame,
text="当前状态: 未开始",
font=("Arial", 11),
background="#e3f2fd",
foreground="#2c3e50",
)
self.status_label.grid(row=0, column=0, padx=10)
self.progress_bar = ttk.Progressbar(status_frame, length=240, mode="determinate", maximum=100)
self.progress_bar.grid(row=0, column=1, padx=10)
self.progress_label = ttk.Label(
status_frame,
text="进度: 0%",
font=("Arial", 11),
background="#e3f2fd",
foreground="#2c3e50",
)
self.progress_label.grid(row=0, column=2, padx=10)
log_frame = tk.LabelFrame(
main_frame,
text="日志输出",
font=("Arial", 12),
bg="#ffffff",
fg="#333",
padx=10,
pady=10,
)
log_frame.pack(fill=tk.BOTH, padx=20, pady=10)
self.log_text = tk.Text(
log_frame,
height=8,
width=120,
font=("Arial", 11),
wrap=tk.WORD,
bg="#f8f9fa",
state=tk.DISABLED,
)
self.log_text.grid(row=0, column=0, padx=10, pady=5)
def create_global_tab(self):
tab = tk.Frame(self.notebook, bg="#ffffff")
self.notebook.add(tab, text="全局设置")
self.global_log_level = self._add_labeled_combo(
tab,
0,
"日志等级",
["DEBUG", "INFO", "WARNING", "ERROR"],
default="DEBUG",
)
self.global_timezone = self._add_labeled_entry(tab, 1, "时区", "Asia/Shanghai")
self.global_ntp_servers = self._add_labeled_entry(tab, 2, "NTP服务器(逗号分隔)", "time.google.com,ntp.aliyun.com")
dashboard_frame = tk.LabelFrame(tab, text="Dashboard", bg="#ffffff", padx=10, pady=10)
dashboard_frame.grid(row=3, column=0, columnspan=2, sticky="ew", padx=10, pady=10)
dashboard_frame.columnconfigure(1, weight=1)
self.dashboard_enable = tk.BooleanVar(value=True)
ttk.Checkbutton(dashboard_frame, text="启用可视化面板", variable=self.dashboard_enable).grid(
row=0, column=0, sticky="w", padx=5, pady=5
)
self.dashboard_host = self._add_labeled_entry(dashboard_frame, 1, "面板地址", "0.0.0.0")
self.dashboard_port = self._add_labeled_entry(dashboard_frame, 2, "面板端口", "8765")
def create_account_tab(self):
tab = tk.Frame(self.notebook, bg="#ffffff")
self.notebook.add(tab, text="账户配置")
self.account_platform = self._add_labeled_combo(
tab,
0,
"平台",
["taopiaopiao", "maoyan", "showstart", "piaoxingqiu", "others"],
default="taopiaopiao",
)
self.account_mobile = self._add_labeled_entry(tab, 1, "手机号", "")
self.account_password = self._add_labeled_entry(tab, 2, "密码", "")
self.account_otp = self._add_labeled_entry(tab, 3, "OTP Secret(可选)", "")
target_frame = tk.LabelFrame(tab, text="目标信息", bg="#ffffff", padx=10, pady=10)
target_frame.grid(row=4, column=0, columnspan=2, sticky="ew", padx=10, pady=10)
target_frame.columnconfigure(1, weight=1)
self.target_event_url = self._add_labeled_entry(target_frame, 0, "演出链接", "")
self.target_date_priorities = self._add_labeled_entry(target_frame, 1, "日期优先级(逗号)", "1,2")
self.target_session_priorities = self._add_labeled_entry(target_frame, 2, "场次优先级(逗号)", "1")
self.target_price_range = self._add_labeled_combo(
target_frame,
3,
"票价策略",
["lowest_to_highest", "highest_to_lowest", "custom"],
default="lowest_to_highest",
)
self.target_tickets = self._add_labeled_entry(target_frame, 4, "购票数量", "2")
self.target_viewers = self._add_labeled_entry(target_frame, 5, "观影人索引(逗号)", "0,1")
proxy_frame = tk.LabelFrame(tab, text="代理设置", bg="#ffffff", padx=10, pady=10)
proxy_frame.grid(row=5, column=0, columnspan=2, sticky="ew", padx=10, pady=10)
proxy_frame.columnconfigure(1, weight=1)
self.proxy_type = self._add_labeled_combo(
proxy_frame,
0,
"代理类型",
["socks5", "http", "https"],
default="socks5",
)
self.proxy_addr = self._add_labeled_entry(proxy_frame, 1, "代理地址(user:pass@host:port)", "")
self.proxy_rotate = self._add_labeled_entry(proxy_frame, 2, "轮换间隔(秒)", "300")
def create_strategy_tab(self):
tab = tk.Frame(self.notebook, bg="#ffffff")
self.notebook.add(tab, text="策略设置")
self.strategy_auto = tk.BooleanVar(value=True)
ttk.Checkbutton(tab, text="启用自动出手", variable=self.strategy_auto, background="#ffffff").grid(
row=0, column=0, sticky="w", padx=10, pady=5
)
self.strategy_time = self._add_labeled_entry(tab, 1, "开抢时间(ISO)", "2026-01-25T12:00:00")
self.strategy_preheat = self._add_labeled_entry(tab, 2, "预热阶段(秒,逗号)", "5.0,2.0,0.5")
self.strategy_ai = tk.BooleanVar(value=True)
ttk.Checkbutton(tab, text="启用AI决策", variable=self.strategy_ai, background="#ffffff").grid(
row=3, column=0, sticky="w", padx=10, pady=5
)
self.strategy_ai_model = self._add_labeled_entry(tab, 4, "AI模型路径", "models/lstm_onnx.onnx")
self.strategy_max_retries = self._add_labeled_entry(tab, 5, "最大重试次数", "180")
self.strategy_retry_backoff = self._add_labeled_combo(
tab,
6,
"重试策略",
["exponential", "fixed"],
default="exponential",
)
def create_monitor_tab(self):
tab = tk.Frame(self.notebook, bg="#ffffff")
self.notebook.add(tab, text="监控设置")
self.monitor_enable = tk.BooleanVar(value=True)
ttk.Checkbutton(tab, text="启用库存监控", variable=self.monitor_enable, background="#ffffff").grid(
row=0, column=0, sticky="w", padx=10, pady=5
)
self.monitor_poll_interval = self._add_labeled_entry(tab, 1, "轮询间隔(秒)", "1.5")
trigger_frame = tk.LabelFrame(tab, text="触发条件(每行一条)", bg="#ffffff", padx=10, pady=10)
trigger_frame.grid(row=2, column=0, columnspan=2, sticky="ew", padx=10, pady=10)
self.monitor_triggers = tk.Text(trigger_frame, height=5, width=60, font=("Arial", 10))
self.monitor_triggers.insert(tk.END, "price_drop > 10%\ntickets_added > 0\nstatus_change: soldout -> available")
self.monitor_triggers.pack(fill=tk.X)
def create_notification_tab(self):
tab = tk.Frame(self.notebook, bg="#ffffff")
self.notebook.add(tab, text="通知设置")
telegram_frame = tk.LabelFrame(tab, text="Telegram", bg="#ffffff", padx=10, pady=10)
telegram_frame.grid(row=0, column=0, columnspan=2, sticky="ew", padx=10, pady=10)
telegram_frame.columnconfigure(1, weight=1)
self.telegram_token = self._add_labeled_entry(telegram_frame, 0, "Bot Token", "")
self.telegram_chat = self._add_labeled_entry(telegram_frame, 1, "Chat ID", "")
email_frame = tk.LabelFrame(tab, text="Email", bg="#ffffff", padx=10, pady=10)
email_frame.grid(row=1, column=0, columnspan=2, sticky="ew", padx=10, pady=10)
email_frame.columnconfigure(1, weight=1)
self.email_smtp = self._add_labeled_entry(email_frame, 0, "SMTP地址", "smtp.example.com:465")
self.email_user = self._add_labeled_entry(email_frame, 1, "邮箱账号", "")
self.email_pass = self._add_labeled_entry(email_frame, 2, "邮箱密码", "")
self.email_recipients = self._add_labeled_entry(email_frame, 3, "收件人(逗号)", "user@domain.com")
def create_plugin_tab(self):
tab = tk.Frame(self.notebook, bg="#ffffff")
self.notebook.add(tab, text="插件扩展")
ttk.Label(tab, text="自定义插件(逗号分隔)", background="#ffffff").grid(
row=0, column=0, padx=10, pady=10, sticky="w"
)
self.plugins_custom = ttk.Entry(tab, width=60, font=("Arial", 11))
self.plugins_custom.insert(0, "my_adapter.py,extra_notifier.py")
self.plugins_custom.grid(row=0, column=1, padx=10, pady=10, sticky="ew")
def create_dependency_tab(self):
tab = tk.Frame(self.notebook, bg="#ffffff")
self.notebook.add(tab, text="依赖管理")
description = (
"本项目用于模拟与学习。可在此配置“伪安装”依赖清单,\n"
"点击按钮将以日志方式模拟安装流程,不会真实下载或执行安装。"
)
ttk.Label(tab, text=description, background="#ffffff", foreground="#555").grid(
row=0, column=0, columnspan=2, padx=10, pady=10, sticky="w"
)
deps_frame = tk.LabelFrame(tab, text="依赖清单(每行一条)", bg="#ffffff", padx=10, pady=10)
deps_frame.grid(row=1, column=0, columnspan=2, sticky="ew", padx=10, pady=10)
self.dependency_list = tk.Text(deps_frame, height=8, width=70, font=("Arial", 10))
self.dependency_list.insert(tk.END, "\n".join(default_dependencies()) + "\n")
self.dependency_list.pack(fill=tk.X)
self.dep_auto_install = tk.BooleanVar(value=True)
ttk.Checkbutton(
tab,
text="启动时自动模拟安装",
variable=self.dep_auto_install,
background="#ffffff",
).grid(row=2, column=0, padx=10, pady=5, sticky="w")
install_button = ttk.Button(tab, text="模拟安装依赖", command=self.simulate_dependency_install)
install_button.grid(row=2, column=1, padx=10, pady=5, sticky="e")
export_button = ttk.Button(tab, text="导出模拟安装报告", command=self.export_dependency_report)
export_button.grid(row=3, column=1, padx=10, pady=5, sticky="e")
def _add_labeled_entry(self, parent, row, label, default):
ttk.Label(parent, text=label, background=parent.cget("bg")).grid(
row=row, column=0, padx=10, pady=6, sticky="w"
)
entry = ttk.Entry(parent, width=48, font=("Arial", 11))
entry.grid(row=row, column=1, padx=10, pady=6, sticky="ew")
if default:
entry.insert(0, default)
return entry
def _add_labeled_combo(self, parent, row, label, values, default=None):
ttk.Label(parent, text=label, background=parent.cget("bg")).grid(
row=row, column=0, padx=10, pady=6, sticky="w"
)
combo = ttk.Combobox(parent, values=values, state="readonly", width=45, font=("Arial", 11))
combo.grid(row=row, column=1, padx=10, pady=6, sticky="ew")
if default:
combo.set(default)
elif values:
combo.set(values[0])
return combo
def _parse_list(self, raw_text):
return [item.strip() for item in raw_text.split(",") if item.strip()]
def _parse_int_list(self, raw_text):
return [int(item.strip()) for item in raw_text.split(",") if item.strip().isdigit()]
def _get_text_lines(self, text_widget):
content = text_widget.get("1.0", tk.END).strip()
return [line.strip() for line in content.splitlines() if line.strip()]
def start_ticket_task(self):
if self.dep_auto_install.get():
self.simulate_dependency_install()
self.log("任务开始!")
self.status_label.config(text="当前状态: 抢票进行中")
self.start_button.config(state=tk.DISABLED)
self.stop_button.config(state=tk.NORMAL)
self.restart_button.config(state=tk.NORMAL)
self.task_thread = threading.Thread(target=self.simulate_ticket_task, daemon=True)
self.task_thread.start()
def stop_ticket_task(self):
self.log("任务已停止!")
self.status_label.config(text="当前状态: 任务已停止")
self.start_button.config(state=tk.NORMAL)
self.stop_button.config(state=tk.DISABLED)
self.restart_button.config(state=tk.DISABLED)
def retry_ticket_task(self):
self.log("正在重试任务...")
self.start_ticket_task()
def simulate_ticket_task(self):
for i in range(1, 101):
time.sleep(0.08)
self.progress_bar["value"] = i
self.progress_label.config(text=f"进度: {i}%")
self.window.update_idletasks()
self.log("任务完成!")
self.status_label.config(text="当前状态: 任务完成")
self.start_button.config(state=tk.NORMAL)
self.stop_button.config(state=tk.DISABLED)
self.restart_button.config(state=tk.NORMAL)
def simulate_dependency_install(self):
dependencies = self._get_text_lines(self.dependency_list)
if not dependencies:
self.log("依赖清单为空,已跳过。")
return
self.log("开始模拟安装依赖...")
for step in build_mock_steps(dependencies):
time.sleep(0.03)
self.log(f"[{step.dependency}] {step.detail}")
self.log("依赖模拟安装完成。")
def log(self, message):
self.log_text.config(state=tk.NORMAL)
self.log_text.insert(tk.END, f"{message}\n")
self.log_text.config(state=tk.DISABLED)
self.log_text.yview(tk.END)
def save_config(self):
config_data = {
"version": "4.5",
"global": {
"log_level": self.global_log_level.get(),
"timezone": self.global_timezone.get(),
"ntp_servers": self._parse_list(self.global_ntp_servers.get()),
"dashboard": {
"enable": self.dashboard_enable.get(),
"host": self.dashboard_host.get(),
"port": int(self.dashboard_port.get() or 0),
},
},
"accounts": {
"acc_primary": {
"platform": self.account_platform.get(),
"credentials": {
"mobile": self.account_mobile.get(),
"password": self.account_password.get(),
"otp_secret": self.account_otp.get(),
},
"target": {
"event_url": self.target_event_url.get(),
"priorities": {
"date": self._parse_int_list(self.target_date_priorities.get()),
"session": self._parse_int_list(self.target_session_priorities.get()),
"price_range": self.target_price_range.get(),
},
"tickets": int(self.target_tickets.get() or 0),
"viewers": self._parse_int_list(self.target_viewers.get()),
},
"proxy": {
"type": self.proxy_type.get(),
"addr": self.proxy_addr.get(),
"rotate_interval": f"{self.proxy_rotate.get()}s",
},
}
},
"strategy": {
"auto_strike": self.strategy_auto.get(),
"strike_time": self.strategy_time.get(),
"preheat_stages": [
float(item) for item in self._parse_list(self.strategy_preheat.get())
],
"ai_enabled": self.strategy_ai.get(),
"ai_model_path": self.strategy_ai_model.get(),
"max_retries": int(self.strategy_max_retries.get() or 0),
"retry_backoff": self.strategy_retry_backoff.get(),
},
"monitor": {
"enable": self.monitor_enable.get(),
"poll_interval": f"{self.monitor_poll_interval.get()}s",
"triggers": self._get_text_lines(self.monitor_triggers),
},
"notification": {
"channels": [
{
"telegram": {
"bot_token": self.telegram_token.get(),
"chat_id": self.telegram_chat.get(),
}
},
{
"email": {
"smtp": self.email_smtp.get(),
"user": self.email_user.get(),
"pass": self.email_pass.get(),
"recipients": self._parse_list(self.email_recipients.get()),
}
},
]
},
"plugins": {"custom": self._parse_list(self.plugins_custom.get())},
"dependencies": {
"auto_install": self.dep_auto_install.get(),
"packages": self._get_text_lines(self.dependency_list),
},
}
file_path = filedialog.asksaveasfilename(
defaultextension=".json",
filetypes=[("JSON文件", "*.json")],
initialfile="config.json",
)
if not file_path:
return
with open(file_path, "w", encoding="utf-8") as file:
json.dump(config_data, file, ensure_ascii=False, indent=2)
self.log("配置已保存!")
def load_config(self):
file_path = filedialog.askopenfilename(filetypes=[("JSON文件", "*.json")])
if not file_path:
return
try:
with open(file_path, "r", encoding="utf-8") as file:
config_data = json.load(file)
except (json.JSONDecodeError, OSError) as error:
messagebox.showerror("错误", f"无法读取配置文件: {error}")
return
self.apply_config(config_data)
def load_demo_config(self):
demo_path = Path(__file__).resolve().parent / "config" / "demo_config.json"
if not demo_path.exists():
messagebox.showerror("错误", "未找到示例配置文件!")
return
try:
with open(demo_path, "r", encoding="utf-8") as file:
config_data = json.load(file)
except (json.JSONDecodeError, OSError) as error:
messagebox.showerror("错误", f"无法读取示例配置文件: {error}")
return
self.apply_config(config_data)
def apply_config(self, config_data):
global_cfg = config_data.get("global", {})
self.global_log_level.set(global_cfg.get("log_level", "DEBUG"))
self.global_timezone.delete(0, tk.END)
self.global_timezone.insert(0, global_cfg.get("timezone", ""))
self.global_ntp_servers.delete(0, tk.END)
self.global_ntp_servers.insert(0, ",".join(global_cfg.get("ntp_servers", [])))
dashboard_cfg = global_cfg.get("dashboard", {})
self.dashboard_enable.set(dashboard_cfg.get("enable", True))
self.dashboard_host.delete(0, tk.END)
self.dashboard_host.insert(0, dashboard_cfg.get("host", ""))
self.dashboard_port.delete(0, tk.END)
self.dashboard_port.insert(0, str(dashboard_cfg.get("port", "")))
accounts = config_data.get("accounts", {})
primary = accounts.get("acc_primary", {})
self.account_platform.set(primary.get("platform", "taopiaopiao"))
creds = primary.get("credentials", {})
self.account_mobile.delete(0, tk.END)
self.account_mobile.insert(0, creds.get("mobile", ""))
self.account_password.delete(0, tk.END)
self.account_password.insert(0, creds.get("password", ""))
self.account_otp.delete(0, tk.END)
self.account_otp.insert(0, creds.get("otp_secret", ""))
target = primary.get("target", {})
self.target_event_url.delete(0, tk.END)
self.target_event_url.insert(0, target.get("event_url", ""))
priorities = target.get("priorities", {})
self.target_date_priorities.delete(0, tk.END)
self.target_date_priorities.insert(0, ",".join(map(str, priorities.get("date", []))))
self.target_session_priorities.delete(0, tk.END)
self.target_session_priorities.insert(0, ",".join(map(str, priorities.get("session", []))))
self.target_price_range.set(priorities.get("price_range", "lowest_to_highest"))
self.target_tickets.delete(0, tk.END)
self.target_tickets.insert(0, str(target.get("tickets", "")))
self.target_viewers.delete(0, tk.END)
self.target_viewers.insert(0, ",".join(map(str, target.get("viewers", []))))
proxy = primary.get("proxy", {})
self.proxy_type.set(proxy.get("type", "socks5"))
self.proxy_addr.delete(0, tk.END)
self.proxy_addr.insert(0, proxy.get("addr", ""))
self.proxy_rotate.delete(0, tk.END)
self.proxy_rotate.insert(0, str(proxy.get("rotate_interval", "")).replace("s", ""))
strategy = config_data.get("strategy", {})
self.strategy_auto.set(strategy.get("auto_strike", True))
self.strategy_time.delete(0, tk.END)
self.strategy_time.insert(0, strategy.get("strike_time", ""))
self.strategy_preheat.delete(0, tk.END)
self.strategy_preheat.insert(0, ",".join(map(str, strategy.get("preheat_stages", []))))
self.strategy_ai.set(strategy.get("ai_enabled", True))
self.strategy_ai_model.delete(0, tk.END)
self.strategy_ai_model.insert(0, strategy.get("ai_model_path", ""))
self.strategy_max_retries.delete(0, tk.END)
self.strategy_max_retries.insert(0, str(strategy.get("max_retries", "")))
self.strategy_retry_backoff.set(strategy.get("retry_backoff", "exponential"))
monitor = config_data.get("monitor", {})
self.monitor_enable.set(monitor.get("enable", True))
self.monitor_poll_interval.delete(0, tk.END)
self.monitor_poll_interval.insert(0, str(monitor.get("poll_interval", "")).replace("s", ""))
self.monitor_triggers.delete("1.0", tk.END)
self.monitor_triggers.insert(tk.END, "\n".join(monitor.get("triggers", [])))
notification = config_data.get("notification", {})
channels = notification.get("channels", [])
telegram = next((c.get("telegram") for c in channels if "telegram" in c), {})
email = next((c.get("email") for c in channels if "email" in c), {})
self.telegram_token.delete(0, tk.END)
self.telegram_token.insert(0, telegram.get("bot_token", ""))
self.telegram_chat.delete(0, tk.END)
self.telegram_chat.insert(0, telegram.get("chat_id", ""))
self.email_smtp.delete(0, tk.END)
self.email_smtp.insert(0, email.get("smtp", ""))
self.email_user.delete(0, tk.END)
self.email_user.insert(0, email.get("user", ""))
self.email_pass.delete(0, tk.END)
self.email_pass.insert(0, email.get("pass", ""))
self.email_recipients.delete(0, tk.END)
self.email_recipients.insert(0, ",".join(email.get("recipients", [])))
plugins = config_data.get("plugins", {})
self.plugins_custom.delete(0, tk.END)
self.plugins_custom.insert(0, ",".join(plugins.get("custom", [])))
dependencies = config_data.get("dependencies", {})
self.dep_auto_install.set(dependencies.get("auto_install", True))
self.dependency_list.delete("1.0", tk.END)
self.dependency_list.insert(tk.END, "\n".join(dependencies.get("packages", [])))
self.log("配置已加载!")
def export_dependency_report(self):
dependencies = self._get_text_lines(self.dependency_list)
if not dependencies:
messagebox.showwarning("提示", "依赖清单为空,无法导出报告。")
return
file_path = filedialog.asksaveasfilename(
defaultextension=".txt",
filetypes=[("文本文件", "*.txt")],
initialfile="mock_install_report.txt",
)
if not file_path:
return
report = build_report(dependencies)
try:
with open(file_path, "w", encoding="utf-8") as file:
file.write(report)
except OSError as error:
messagebox.showerror("错误", f"无法写入报告: {error}")
return
self.log(f"模拟安装报告已导出: {file_path}")
def show_about(self):
messagebox.showinfo(
"关于",
"抢票助手 V5.0\n\n交互界面支持 README 中的配置项预设。\n\n说明: 仅研究用途。",
)
if __name__ == "__main__":
app = TicketHelperGUI()
app.window.mainloop()