Files
yidaima_tools/gui.py

1387 lines
60 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from __future__ import annotations
import tkinter as tk
from tkinter import ttk, messagebox, scrolledtext
import threading
import sys
import os
import subprocess
# 添加当前目录到路径
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from config_loader import get_config, Config
from step1 import Step1FeastCoding
from step2 import Step2Converter
from editor_gui import open_editor
from markdown_editor import ThemeManager
from db_manager import get_db_manager, reset_db_manager, ProjectOrder
from project_screenshot import ProjectScreenshotAutomation, ProjectConfig
class YidaimaGUI:
def __init__(self, root):
self.root = root
self.root.title("自动化工具")
self.root.geometry("1200x900")
self.root.resizable(True, True)
# 设置样式
self.style = ttk.Style()
self.style.configure("TButton", padding=6, relief="flat", background="#007aff")
self.style.configure("TLabel", padding=6, font=("Microsoft YaHei", 10))
self.style.configure("TEntry", padding=6)
# 设置 Treeview 样式,增加行高
self.style.configure("Treeview", rowheight=30)
# 加载配置
self.config = get_config()
# 初始化管理器
self.theme_manager = ThemeManager()
self._init_db_manager()
# 运行状态
self.is_running = False
# 文章管理分页状态
self.current_page = 1
self.page_size = 20
self.total_items = 0
self.search_keyword = ""
# 创建界面
self.create_widgets()
def _init_db_manager(self):
"""初始化数据库管理器"""
try:
db_config = self.config.get("database", {})
self.db_manager = get_db_manager(
host=db_config.get("host", "localhost"),
database=db_config.get("database", "test"),
user=db_config.get("user", "root"),
password=db_config.get("password", "123456"),
port=db_config.get("port", 3306)
)
except Exception as e:
print(f"[GUI] 初始化数据库管理器失败: {e}")
self.db_manager = None
def create_widgets(self):
"""创建主界面"""
# 主框架
main_frame = ttk.Frame(self.root, padding="10")
main_frame.pack(fill=tk.BOTH, expand=True)
# 创建 Notebook (Tab 控件)
self.notebook = ttk.Notebook(main_frame)
self.notebook.pack(fill=tk.BOTH, expand=True)
# === Tab 1: 发布微信公众号 ===
self.tab_publish = ttk.Frame(self.notebook, padding="10")
self.notebook.add(self.tab_publish, text="发布微信公众号")
self._create_publish_tab()
# === Tab 2: 文章发布管理 ===
self.tab_manage = ttk.Frame(self.notebook, padding="10")
self.notebook.add(self.tab_manage, text="文章发布管理")
self._create_manage_tab()
# === Tab 3: 项目运行截图 ===
self.tab_screenshot = ttk.Frame(self.notebook, padding="10")
self.notebook.add(self.tab_screenshot, text="项目运行截图")
self._create_screenshot_tab()
# === Tab 4: 参数设置 ===
self.tab_settings = ttk.Frame(self.notebook, padding="10")
self.notebook.add(self.tab_settings, text="参数设置")
self._create_settings_tab()
def _create_publish_tab(self):
"""创建发布微信公众号 Tab"""
# 配置网格
self.tab_publish.columnconfigure(0, weight=1)
self.tab_publish.rowconfigure(2, weight=1)
# === 配置区域 ===
config_frame = ttk.LabelFrame(self.tab_publish, text="微信公众号配置", padding="10")
config_frame.grid(row=0, column=0, sticky=(tk.W, tk.E), pady=(0, 10))
config_frame.columnconfigure(1, weight=1)
# 项目名称
ttk.Label(config_frame, text="项目名称:").grid(row=0, column=0, sticky=tk.W)
self.project_name_var = tk.StringVar(
value=self.config.get("step1.project_name", "【A173】基于Springboot + vue3实现的学生交流互助平台")
)
self.project_name_entry = ttk.Entry(config_frame, textvariable=self.project_name_var, width=50)
self.project_name_entry.grid(row=0, column=1, sticky=(tk.W, tk.E), padx=(10, 0))
# CSS 样式方案
ttk.Label(config_frame, text="CSS 样式方案:").grid(row=1, column=0, sticky=tk.W, pady=(10, 0))
self.css_scheme_var = tk.StringVar()
scheme_frame = ttk.Frame(config_frame)
scheme_frame.grid(row=1, column=1, sticky=(tk.W, tk.E), padx=(10, 0), pady=(10, 0))
scheme_frame.columnconfigure(0, weight=1)
self.css_scheme_combo = ttk.Combobox(
scheme_frame,
values=self._get_css_scheme_names(),
textvariable=self.css_scheme_var,
state="readonly",
width=50
)
self.css_scheme_combo.grid(row=0, column=0, sticky=(tk.W, tk.E))
self.css_scheme_combo.set("默认")
# 刷新按钮
ttk.Button(scheme_frame, text="刷新", command=self.refresh_css_schemes, width=8).grid(row=0, column=1, padx=(5, 0))
# 调用API方式选项
self.use_api_var = tk.BooleanVar(value=False)
ttk.Checkbutton(
config_frame,
text="调用API方式 (跳过浏览器自动化直接调用API获取文章内容)",
variable=self.use_api_var
).grid(row=2, column=0, columnspan=2, sticky=tk.W, pady=(10, 0))
# === 操作按钮区域 ===
button_frame = ttk.Frame(self.tab_publish)
button_frame.grid(row=1, column=0, pady=(10, 10))
# 发布微信公众号按钮
self.publish_btn = ttk.Button(
button_frame,
text="发布微信公众号",
command=self.run_full_flow,
width=25
)
self.publish_btn.pack(side=tk.LEFT)
# Markdown 编辑器按钮
ttk.Button(
button_frame,
text="Markdown 编辑器",
command=self._open_markdown_editor,
width=15
).pack(side=tk.LEFT, padx=(10, 0))
# === 日志输出区域 ===
log_frame = ttk.LabelFrame(self.tab_publish, text="运行日志", padding="10")
log_frame.grid(row=2, column=0, sticky=(tk.W, tk.E, tk.N, tk.S), pady=(10, 0))
log_frame.columnconfigure(0, weight=1)
log_frame.rowconfigure(0, weight=1)
self.log_text = scrolledtext.ScrolledText(
log_frame,
wrap=tk.WORD,
width=80,
height=15,
font=("Consolas", 9)
)
self.log_text.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
# 进度条
self.progress = ttk.Progressbar(
self.tab_publish,
mode='indeterminate',
length=400
)
self.progress.grid(row=3, column=0, pady=(10, 0), sticky=(tk.W, tk.E))
# 状态标签
self.status_var = tk.StringVar(value="就绪")
self.status_label = ttk.Label(
self.tab_publish,
textvariable=self.status_var,
foreground="#666"
)
self.status_label.grid(row=4, column=0, pady=(10, 0))
def _create_manage_tab(self):
"""创建文章发布管理 Tab"""
# 配置网格
self.tab_manage.columnconfigure(0, weight=1)
self.tab_manage.rowconfigure(2, weight=1)
# === 搜索区域 ===
search_frame = ttk.Frame(self.tab_manage)
search_frame.grid(row=0, column=0, sticky=(tk.W, tk.E), pady=(0, 10))
search_frame.columnconfigure(1, weight=1)
ttk.Label(search_frame, text="搜索名称:").grid(row=0, column=0, sticky=tk.W)
self.search_var = tk.StringVar()
self.search_entry = ttk.Entry(search_frame, textvariable=self.search_var, width=40)
self.search_entry.grid(row=0, column=1, sticky=(tk.W, tk.E), padx=(10, 0))
ttk.Button(search_frame, text="搜索", command=self._search_projects, width=10).grid(row=0, column=2, padx=(10, 0))
ttk.Button(search_frame, text="刷新", command=self._refresh_projects, width=10).grid(row=0, column=3, padx=(10, 0))
# === 数据表格区域 ===
table_frame = ttk.LabelFrame(self.tab_manage, text="项目列表", padding="5")
table_frame.grid(row=1, column=0, rowspan=2, sticky=(tk.W, tk.E, tk.N, tk.S), pady=(0, 10))
table_frame.columnconfigure(0, weight=1)
table_frame.rowconfigure(0, weight=1)
# 创建 Treeview
columns = ("name", "name2", "paths", "zlzt", "sfxxm", "bianhao", "actions")
self.tree = ttk.Treeview(
table_frame,
columns=columns,
show="headings",
height=35
)
# 设置列标题
self.tree.heading("name", text="项目名称")
self.tree.heading("name2", text="名称2")
self.tree.heading("paths", text="路径")
self.tree.heading("zlzt", text="整理状态")
self.tree.heading("sfxxm", text="是否新项目")
self.tree.heading("bianhao", text="编号")
self.tree.heading("actions", text="操作")
# 设置列宽
self.tree.column("name", width=150, anchor=tk.W)
self.tree.column("name2", width=100, anchor=tk.W)
self.tree.column("paths", width=120, anchor=tk.W)
self.tree.column("zlzt", width=80, anchor=tk.CENTER)
self.tree.column("sfxxm", width=80, anchor=tk.CENTER)
self.tree.column("bianhao", width=80, anchor=tk.CENTER)
self.tree.column("actions", width=200, anchor=tk.CENTER)
# 添加滚动条
scrollbar_y = ttk.Scrollbar(table_frame, orient=tk.VERTICAL, command=self.tree.yview)
scrollbar_x = ttk.Scrollbar(table_frame, orient=tk.HORIZONTAL, command=self.tree.xview)
self.tree.configure(yscrollcommand=scrollbar_y.set, xscrollcommand=scrollbar_x.set)
self.tree.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
scrollbar_y.grid(row=0, column=1, sticky=(tk.N, tk.S))
scrollbar_x.grid(row=1, column=0, sticky=(tk.W, tk.E))
# 绑定右键点击事件
self.tree.bind("<Button-3>", self._on_tree_right_click)
# === 分页区域 ===
page_frame = ttk.Frame(self.tab_manage)
page_frame.grid(row=3, column=0, sticky=(tk.W, tk.E), pady=(5, 0))
self.page_info_var = tk.StringVar(value="第 1 页,共 0 页,共 0 条")
ttk.Label(page_frame, textvariable=self.page_info_var).pack(side=tk.LEFT)
ttk.Button(page_frame, text="上一页", command=self._prev_page).pack(side=tk.LEFT, padx=(20, 5))
ttk.Button(page_frame, text="下一页", command=self._next_page).pack(side=tk.LEFT, padx=(5, 0))
# 加载数据(延迟加载,避免初始化时出错)
# self._load_projects()
def _create_settings_tab(self):
"""创建参数设置 Tab"""
# 创建 Canvas 和 Scrollbar 实现滚动
canvas = tk.Canvas(self.tab_settings)
scrollbar = ttk.Scrollbar(self.tab_settings, orient="vertical", command=canvas.yview)
self.settings_frame = ttk.Frame(canvas, padding="10")
self.settings_frame.bind(
"<Configure>",
lambda e: canvas.configure(scrollregion=canvas.bbox("all"))
)
canvas.create_window((0, 0), window=self.settings_frame, anchor="nw")
canvas.configure(yscrollcommand=scrollbar.set)
canvas.pack(side="left", fill="both", expand=True)
scrollbar.pack(side="right", fill="y")
# 配置网格
self.settings_frame.columnconfigure(1, weight=1)
row = 0
# === 网站账号配置 ===
website_frame = ttk.LabelFrame(self.settings_frame, text="网站账号配置", padding="10")
website_frame.grid(row=row, column=0, columnspan=2, sticky=(tk.W, tk.E), pady=(0, 15))
website_frame.columnconfigure(1, weight=1)
ttk.Label(website_frame, text="网站用户名:").grid(row=0, column=0, sticky=tk.W)
self.setting_username_var = tk.StringVar(value=self.config.get("step1.username", "wangpeng"))
ttk.Entry(website_frame, textvariable=self.setting_username_var, width=40).grid(row=0, column=1, sticky=(tk.W, tk.E), padx=(10, 0))
ttk.Label(website_frame, text="网站密码:").grid(row=1, column=0, sticky=tk.W, pady=(10, 0))
self.setting_password_var = tk.StringVar(value=self.config.get("step1.password", ""))
ttk.Entry(website_frame, textvariable=self.setting_password_var, show="*", width=40).grid(row=1, column=1, sticky=(tk.W, tk.E), padx=(10, 0), pady=(10, 0))
row += 1
# === Chrome 配置 ===
chrome_frame = ttk.LabelFrame(self.settings_frame, text="Chrome 配置", padding="10")
chrome_frame.grid(row=row, column=0, columnspan=2, sticky=(tk.W, tk.E), pady=(0, 15))
chrome_frame.columnconfigure(1, weight=1)
ttk.Label(chrome_frame, text="Chrome 路径:").grid(row=0, column=0, sticky=tk.W)
self.setting_chrome_var = tk.StringVar(value=self.config.get("chrome.path", r"C:\Program Files\Google\Chrome\Application\chrome.exe"))
ttk.Entry(chrome_frame, textvariable=self.setting_chrome_var, width=50).grid(row=0, column=1, sticky=(tk.W, tk.E), padx=(10, 0))
row += 1
# === 微信公众号配置 ===
wechat_frame = ttk.LabelFrame(self.settings_frame, text="微信公众号配置", padding="10")
wechat_frame.grid(row=row, column=0, columnspan=2, sticky=(tk.W, tk.E), pady=(0, 15))
wechat_frame.columnconfigure(1, weight=1)
ttk.Label(wechat_frame, text="AppID:").grid(row=0, column=0, sticky=tk.W)
self.setting_appid_var = tk.StringVar(value=self.config.get("wechat.appid", ""))
ttk.Entry(wechat_frame, textvariable=self.setting_appid_var, width=40).grid(row=0, column=1, sticky=(tk.W, tk.E), padx=(10, 0))
ttk.Label(wechat_frame, text="AppSecret:").grid(row=1, column=0, sticky=tk.W, pady=(10, 0))
self.setting_appsecret_var = tk.StringVar(value=self.config.get("wechat.appsecret", ""))
ttk.Entry(wechat_frame, textvariable=self.setting_appsecret_var, show="*", width=40).grid(row=1, column=1, sticky=(tk.W, tk.E), padx=(10, 0), pady=(10, 0))
row += 1
# === 数据库配置 ===
db_frame = ttk.LabelFrame(self.settings_frame, text="数据库配置", padding="10")
db_frame.grid(row=row, column=0, columnspan=2, sticky=(tk.W, tk.E), pady=(0, 15))
db_frame.columnconfigure(1, weight=1)
ttk.Label(db_frame, text="主机地址:").grid(row=0, column=0, sticky=tk.W)
self.setting_db_host_var = tk.StringVar(value=self.config.get("database.host", "localhost"))
ttk.Entry(db_frame, textvariable=self.setting_db_host_var, width=30).grid(row=0, column=1, sticky=(tk.W, tk.E), padx=(10, 0))
ttk.Label(db_frame, text="端口:").grid(row=1, column=0, sticky=tk.W, pady=(10, 0))
self.setting_db_port_var = tk.StringVar(value=str(self.config.get("database.port", "3306")))
ttk.Entry(db_frame, textvariable=self.setting_db_port_var, width=30).grid(row=1, column=1, sticky=(tk.W, tk.E), padx=(10, 0), pady=(10, 0))
ttk.Label(db_frame, text="数据库名:").grid(row=2, column=0, sticky=tk.W, pady=(10, 0))
self.setting_db_name_var = tk.StringVar(value=self.config.get("database.database", "test"))
ttk.Entry(db_frame, textvariable=self.setting_db_name_var, width=30).grid(row=2, column=1, sticky=(tk.W, tk.E), padx=(10, 0), pady=(10, 0))
ttk.Label(db_frame, text="用户名:").grid(row=3, column=0, sticky=tk.W, pady=(10, 0))
self.setting_db_user_var = tk.StringVar(value=self.config.get("database.user", "root"))
ttk.Entry(db_frame, textvariable=self.setting_db_user_var, width=30).grid(row=3, column=1, sticky=(tk.W, tk.E), padx=(10, 0), pady=(10, 0))
ttk.Label(db_frame, text="密码:").grid(row=4, column=0, sticky=tk.W, pady=(10, 0))
self.setting_db_pass_var = tk.StringVar(value=self.config.get("database.password", "123456"))
ttk.Entry(db_frame, textvariable=self.setting_db_pass_var, show="*", width=30).grid(row=4, column=1, sticky=(tk.W, tk.E), padx=(10, 0), pady=(10, 0))
# 测试连接按钮
ttk.Button(db_frame, text="🧪 测试连接", command=self._test_db_connection).grid(row=5, column=1, sticky=tk.W, pady=(10, 0))
row += 1
# === 保存按钮 ===
btn_frame = ttk.Frame(self.settings_frame)
btn_frame.grid(row=row, column=0, columnspan=2, pady=(20, 0))
ttk.Button(btn_frame, text="💾 保存配置", command=self._save_settings, width=20).pack(side=tk.LEFT, padx=(0, 10))
ttk.Button(btn_frame, text="🔄 重置", command=self._reset_settings, width=15).pack(side=tk.LEFT)
# ==================== 发布公众号 Tab 方法 ====================
def _open_markdown_editor(self):
"""打开 Markdown 编辑器"""
try:
open_editor(parent=self.root)
except Exception as e:
messagebox.showerror("错误", f"打开 Markdown 编辑器失败: {e}")
def log(self, message: str):
"""添加日志"""
self.log_text.insert(tk.END, f"{message}\n")
self.log_text.see(tk.END)
self.root.update_idletasks()
def set_running(self, running: bool):
"""设置运行状态"""
self.is_running = running
state = tk.DISABLED if running else tk.NORMAL
self.publish_btn.config(state=state)
if running:
self.progress.start()
self.status_var.set("运行中...")
else:
self.progress.stop()
self.status_var.set("就绪")
def run_full_flow(self):
"""运行完整流程"""
if self.is_running:
return
thread = threading.Thread(target=self._run_full_flow_thread)
thread.daemon = True
thread.start()
def _run_full_flow_thread(self):
"""在后台线程中运行完整流程"""
try:
self.set_running(True)
self.root.after(0, lambda: self.log("=" * 50))
self.root.after(0, lambda: self.log("开始运行完整流程..."))
self.root.after(0, lambda: self.log("=" * 50))
# 获取配置(从配置文件读取)
chrome_path = self.config.get("chrome.path", "")
username = self.config.get("step1.username", "")
password = self.config.get("step1.password", "")
project_name = self.project_name_var.get()
use_api = self.use_api_var.get()
# Step 1
self.root.after(0, lambda: self.log("\n[Step 1] 开始执行..."))
if use_api:
# API 方式获取内容
self.root.after(0, lambda: self.log("[Step 1] 使用 API 方式获取文章内容..."))
import requests
import re
# 从项目名称中提取编号,如"【A167】" -> "A167"
match = re.search(r'【(.+?)】', project_name)
code_name = match.group(1) if match else project_name
api_url = "https://feast.yidaima.cn/prod-api/office/copyTemplate/open/generate"
params = {
"templateName": "FeastCoding",
"codeName": code_name
}
try:
response = requests.get(api_url, params=params, timeout=30)
response.raise_for_status()
json_data = response.json()
# 从 JSON 中提取 msg 字段内容
content = json_data.get("msg", "")
# 将 \\n 替换为空行
content = content.replace("\\n", "\n")
# 设置剪贴板内容
subprocess.run(
["powershell", "-command", f"Set-Clipboard -Value '{content.replace(chr(39), chr(39)+chr(39))}'"],
capture_output=True
)
self.root.after(0, lambda: self.log(f"[Step 1] API 调用成功,已复制到剪贴板,内容长度: {len(content)} 字符"))
except Exception as e:
self.root.after(0, lambda: self.log(f"[Step 1] API 调用失败: {e}"))
raise
else:
# 浏览器方式
self.root.after(0, lambda: self.log("[Step 1] 使用浏览器方式获取文章内容..."))
# 定义日志回调函数,将 step1 的日志输出到 GUI
def step1_log_callback(message: str):
self.root.after(0, lambda msg=message: self.log(msg))
step1 = Step1FeastCoding(chrome_path, username, password, log_callback=step1_log_callback)
step1.project_name = project_name # 设置项目名称
step1.run()
self.root.after(0, lambda: self.log("[Step 1] 浏览器方式执行完成!"))
self.root.after(0, lambda: self.log("[Step 1] 执行完成!"))
# Step 2
self.root.after(0, lambda: self.log("\n[Step 2] 开始执行..."))
# 定义日志回调函数,将 step2 的日志输出到 GUI
def log_callback(message: str):
self.root.after(0, lambda msg=message: self.log(msg))
# 获取 CSS 方案 ID根据名称查找
scheme_name = self.css_scheme_var.get()
css_scheme_id = self._get_scheme_id_by_name(scheme_name)
step2 = Step2Converter(log_callback=log_callback, css_scheme_id=css_scheme_id, project_name=project_name)
step2.run()
self.root.after(0, lambda: self.log("[Step 2] 执行完成!"))
self.root.after(0, lambda: self.log("\n" + "=" * 50))
self.root.after(0, lambda: self.log("完整流程执行成功!"))
self.root.after(0, lambda: self.log("=" * 50))
self.root.after(0, lambda: messagebox.showinfo("成功", "完整流程执行成功!"))
except Exception as e:
error_msg = str(e)
self.root.after(0, lambda: self.log(f"\n[ERROR] {error_msg}"))
self.root.after(0, lambda: messagebox.showerror("错误", f"执行失败: {error_msg}"))
finally:
self.set_running(False)
def _get_css_scheme_names(self):
"""获取 CSS 方案名称列表(只显示名称)"""
schemes = self.theme_manager.get_all_css_schemes()
return ["默认"] + [s.name for s in schemes]
def _get_scheme_id_by_name(self, name: str):
"""根据名称获取方案 ID"""
if name == "默认":
return None
schemes = self.theme_manager.get_all_css_schemes()
for scheme in schemes:
if scheme.name == name:
return scheme.id
return None
def refresh_css_schemes(self):
"""刷新 CSS 方案列表"""
# 重新加载配置
self.theme_manager = ThemeManager()
# 更新下拉框
self.css_scheme_combo['values'] = self._get_css_scheme_names()
messagebox.showinfo("提示", "CSS 方案列表已刷新!")
# ==================== 文章管理 Tab 方法 ====================
def _load_projects(self):
"""加载项目列表"""
if not self.db_manager:
messagebox.showwarning("警告", "数据库未配置或连接失败")
return
try:
projects, total = self.db_manager.get_projects(
page=self.current_page,
page_size=self.page_size,
search_name=self.search_keyword if self.search_keyword else None
)
self.total_items = total
# 清空表格
for item in self.tree.get_children():
self.tree.delete(item)
# 插入数据
for project in projects:
self.tree.insert("", tk.END, values=(
project.name or "",
project.name2 or "",
project.paths or "",
project.zlzt or "",
project.sfxxm or "",
project.bianhao or "",
"右键操作"
))
# 更新分页信息
total_pages = (total + self.page_size - 1) // self.page_size if total > 0 else 1
self.page_info_var.set(f"{self.current_page} 页,共 {total_pages} 页,共 {total}")
except Exception as e:
messagebox.showerror("错误", f"加载数据失败: {e}")
def _search_projects(self):
"""搜索项目"""
try:
self.search_keyword = self.search_var.get().strip()
self.current_page = 1
self._load_projects()
except Exception as e:
messagebox.showerror("错误", f"搜索失败: {e}")
def _refresh_projects(self):
"""刷新项目列表"""
self.search_keyword = ""
self.search_var.set("")
self.current_page = 1
self._load_projects()
def _prev_page(self):
"""上一页"""
if self.current_page > 1:
self.current_page -= 1
self._load_projects()
def _next_page(self):
"""下一页"""
total_pages = (self.total_items + self.page_size - 1) // self.page_size if self.total_items > 0 else 1
if self.current_page < total_pages:
self.current_page += 1
self._load_projects()
def _on_tree_right_click(self, event):
"""处理表格右键点击事件"""
# 获取点击位置对应的项
item = self.tree.identify_row(event.y)
if not item:
return
# 选中当前项
self.tree.selection_set(item)
# 获取选中项的值
values = self.tree.item(item, "values")
if not values:
return
name = values[0]
name2 = values[1]
current_zlzt = values[3] # 整理状态现在在第4列索引3
# 创建操作菜单
menu = tk.Menu(self.root, tearoff=0)
# 变更已整理状态选项
new_zlzt = "已整理" if current_zlzt != "已整理" else "未整理"
menu.add_command(
label=f"变更状态: {new_zlzt}",
command=lambda: self._update_project_zlzt(name, new_zlzt)
)
menu.add_separator()
# 删除选项
menu.add_command(
label="删除",
command=lambda: self._delete_project(name)
)
menu.add_separator()
# 复制路径选项
menu.add_command(
label="复制路径",
command=lambda: self._copy_path(name)
)
menu.add_separator()
# 复制name2名称选项
menu.add_command(
label="复制名称2",
command=lambda: self._copy_name2(name2)
)
# 显示菜单
menu.post(event.x_root, event.y_root)
def _update_project_zlzt(self, name: str, zlzt: str):
"""更新项目整理状态"""
if not self.db_manager:
return
if messagebox.askyesno("确认", f"确定将 '{name}' 标记为 {zlzt} 吗?"):
if self.db_manager.update_zlzt(name, zlzt):
messagebox.showinfo("成功", f"已更新为 {zlzt}")
self._load_projects()
else:
messagebox.showerror("错误", "更新失败")
def _delete_project(self, name: str):
"""删除项目"""
if not self.db_manager:
return
if messagebox.askyesno("确认", f"确定删除 '{name}' 吗?\n此操作不可恢复!"):
if self.db_manager.delete_project(name):
messagebox.showinfo("成功", "删除成功")
self._load_projects()
else:
messagebox.showerror("错误", "删除失败")
def _copy_name2(self, name2: str):
"""复制name2名称到剪贴板"""
if not name2:
messagebox.showwarning("提示", "名称2为空无法复制")
return
self.root.clipboard_clear()
self.root.clipboard_append(name2)
messagebox.showinfo("成功", f"已复制: {name2}")
def _copy_path(self, name: str):
"""复制项目路径到剪贴板"""
if not self.db_manager:
messagebox.showwarning("提示", "数据库未连接,无法获取路径")
return
try:
# 获取项目路径
project = self.db_manager.get_project_by_name(name)
if project and project.paths:
self.root.clipboard_clear()
self.root.clipboard_append(project.paths)
messagebox.showinfo("成功", f"已复制路径: {project.paths}")
else:
messagebox.showwarning("提示", "项目路径为空,无法复制")
except Exception as e:
messagebox.showerror("错误", f"复制路径失败: {e}")
# ==================== 参数设置 Tab 方法 ====================
def _test_db_connection(self):
"""测试数据库连接"""
def test_in_thread():
try:
from db_manager import DatabaseManager
host = self.setting_db_host_var.get()
port_str = self.setting_db_port_var.get()
port = int(port_str) if port_str else 3306
database = self.setting_db_name_var.get()
user = self.setting_db_user_var.get()
password = self.setting_db_pass_var.get()
print(f"[TestDB] 测试连接: {host}:{port}/{database} as {user}")
db = DatabaseManager(host, database, user, password, port)
success, error = db.test_connection()
if success:
self.root.after(0, lambda: messagebox.showinfo("成功", "数据库连接成功!"))
else:
self.root.after(0, lambda: messagebox.showerror("失败", f"连接失败: {error}"))
except Exception as e:
print(f"[TestDB] 测试连接异常: {e}")
self.root.after(0, lambda msg=str(e): messagebox.showerror("错误", f"测试连接出错: {msg}"))
# 在新线程中运行,避免阻塞 GUI
thread = threading.Thread(target=test_in_thread)
thread.daemon = True
thread.start()
def _save_settings(self):
"""保存配置"""
try:
# 构建新配置
new_config = {
"step1": {
"url": "https://feast.yidaima.cn/login",
"username": self.setting_username_var.get(),
"password": self.setting_password_var.get(),
"project_name": self.project_name_var.get(),
"feast_button": "FeastCoding"
},
"step2": {
"url": "http://yidaima.cn:6005/"
},
"chrome": {
"path": self.setting_chrome_var.get()
},
"wechat": {
"appid": self.setting_appid_var.get(),
"appsecret": self.setting_appsecret_var.get()
},
"database": {
"host": self.setting_db_host_var.get(),
"port": int(self.setting_db_port_var.get() or 3306),
"database": self.setting_db_name_var.get(),
"user": self.setting_db_user_var.get(),
"password": self.setting_db_pass_var.get()
}
}
# 保存到文件
import yaml
config_path = os.path.join(os.path.dirname(__file__), "config.yaml")
with open(config_path, 'w', encoding='utf-8') as f:
yaml.dump(new_config, f, allow_unicode=True, sort_keys=False)
# 重新加载配置
global _config_instance
_config_instance = None
self.config = get_config()
# 重置数据库管理器
reset_db_manager()
self._init_db_manager()
# 更新发布页面的值
self.username_var.set(self.config.get("step1.username", ""))
self.password_var.set(self.config.get("step1.password", ""))
self.chrome_path_var.set(self.config.get("chrome.path", ""))
messagebox.showinfo("成功", "配置已保存!")
except Exception as e:
messagebox.showerror("错误", f"保存失败: {e}")
def _reset_settings(self):
"""重置设置"""
if messagebox.askyesno("确认", "确定要重置所有设置吗?"):
self.setting_username_var.set(self.config.get("step1.username", "wangpeng"))
self.setting_password_var.set(self.config.get("step1.password", ""))
self.setting_chrome_var.set(self.config.get("chrome.path", r"C:\Program Files\Google\Chrome\Application\chrome.exe"))
self.setting_appid_var.set(self.config.get("wechat.appid", ""))
self.setting_appsecret_var.set(self.config.get("wechat.appsecret", ""))
self.setting_db_host_var.set(self.config.get("database.host", "localhost"))
self.setting_db_port_var.set(str(self.config.get("database.port", "3306")))
self.setting_db_name_var.set(self.config.get("database.database", "test"))
self.setting_db_user_var.set(self.config.get("database.user", "root"))
self.setting_db_pass_var.set(self.config.get("database.password", "123456"))
# ==================== 项目运行截图 Tab 方法 ====================
def _create_screenshot_tab(self):
"""创建项目运行截图 Tab"""
# 配置网格
self.tab_screenshot.columnconfigure(0, weight=1)
self.tab_screenshot.rowconfigure(3, weight=1)
# 获取项目截图配置
ps_config = self.config.get("project_screenshot", {})
# === 项目路径配置区域 ===
path_frame = ttk.LabelFrame(self.tab_screenshot, text="项目路径配置", padding="10")
path_frame.grid(row=0, column=0, sticky=(tk.W, tk.E), pady=(0, 10))
path_frame.columnconfigure(1, weight=1)
# 项目路径
ttk.Label(path_frame, text="项目路径:").grid(row=0, column=0, sticky=tk.W)
self.ps_project_path_var = tk.StringVar(value=ps_config.get("project_path", ""))
ps_project_path_entry = ttk.Entry(path_frame, textvariable=self.ps_project_path_var, width=60)
ps_project_path_entry.grid(row=0, column=1, sticky=(tk.W, tk.E), padx=(10, 0))
ttk.Button(path_frame, text="浏览...", command=self._browse_project_path, width=10).grid(row=0, column=2, padx=(10, 0))
# 桌面路径
ttk.Label(path_frame, text="桌面路径:").grid(row=1, column=0, sticky=tk.W, pady=(10, 0))
self.ps_desktop_path_var = tk.StringVar(value=ps_config.get("desktop_path", r"C:\Users\南音\Desktop"))
ps_desktop_path_entry = ttk.Entry(path_frame, textvariable=self.ps_desktop_path_var, width=60)
ps_desktop_path_entry.grid(row=1, column=1, sticky=(tk.W, tk.E), padx=(10, 0), pady=(10, 0))
# bat 文件夹名称
ttk.Label(path_frame, text="bat文件夹:").grid(row=2, column=0, sticky=tk.W, pady=(10, 0))
self.ps_bat_folder_var = tk.StringVar(value=ps_config.get("bat_folder", r"C:\Users\南音\Desktop\yidaima\bat"))
ps_bat_folder_entry = ttk.Entry(path_frame, textvariable=self.ps_bat_folder_var, width=60)
ps_bat_folder_entry.grid(row=2, column=1, sticky=(tk.W, tk.E), padx=(10, 0), pady=(10, 0))
# 代码目标路径
ttk.Label(path_frame, text="代码目标路径:").grid(row=3, column=0, sticky=tk.W, pady=(10, 0))
self.ps_code_target_var = tk.StringVar(value=ps_config.get("code_target_path", r"D:\code"))
ps_code_target_entry = ttk.Entry(path_frame, textvariable=self.ps_code_target_var, width=60)
ps_code_target_entry.grid(row=3, column=1, sticky=(tk.W, tk.E), padx=(10, 0), pady=(10, 0))
# 是否显示 CMD 窗口
self.ps_show_cmd_var = tk.BooleanVar(value=ps_config.get("show_cmd_window", True))
ttk.Checkbutton(path_frame, text="显示CMD窗口", variable=self.ps_show_cmd_var).grid(row=4, column=1, sticky=tk.W, padx=(10, 0), pady=(10, 0))
# === 操作按钮区域 ===
button_frame = ttk.LabelFrame(self.tab_screenshot, text="操作按钮", padding="10")
button_frame.grid(row=1, column=0, sticky=(tk.W, tk.E), pady=(0, 10))
# 构建/安装按钮
install_frame = ttk.Frame(button_frame)
install_frame.pack(fill=tk.X, pady=(0, 10))
ttk.Button(install_frame, text="整理代码", command=self._ps_organize_code, width=15).pack(side=tk.LEFT, padx=(0, 5))
ttk.Button(install_frame, text="Maven构建后端", command=self._ps_install_server, width=15).pack(side=tk.LEFT, padx=(5, 5))
ttk.Button(install_frame, text="安装前台依赖", command=self._ps_install_front, width=15).pack(side=tk.LEFT, padx=(5, 5))
ttk.Button(install_frame, text="安装后台依赖", command=self._ps_install_admin, width=15).pack(side=tk.LEFT, padx=(5, 5))
ttk.Button(install_frame, text="导入SQL", command=self._ps_run_sql, width=15).pack(side=tk.LEFT, padx=(5, 0))
# 启动按钮
start_frame = ttk.Frame(button_frame)
start_frame.pack(fill=tk.X, pady=(0, 10))
ttk.Button(start_frame, text="启动后端服务", command=self._ps_start_server, width=15).pack(side=tk.LEFT, padx=(0, 5))
ttk.Button(start_frame, text="启动前台前端", command=self._ps_start_front, width=15).pack(side=tk.LEFT, padx=(5, 5))
ttk.Button(start_frame, text="启动后台前端", command=self._ps_start_admin, width=15).pack(side=tk.LEFT, padx=(5, 5))
ttk.Button(start_frame, text="更换图片", command=self._ps_replace_images, width=15).pack(side=tk.LEFT, padx=(5, 0))
# 截图按钮
screenshot_frame = ttk.Frame(button_frame)
screenshot_frame.pack(fill=tk.X)
ttk.Button(screenshot_frame, text="后台截图", command=self._ps_capture_admin, width=15).pack(side=tk.LEFT, padx=(0, 5))
ttk.Button(screenshot_frame, text="前台截图", command=self._ps_capture_front, width=15).pack(side=tk.LEFT, padx=(5, 5))
ttk.Button(screenshot_frame, text="收尾工作", command=self._ps_finalize_screenshots, width=15).pack(side=tk.LEFT, padx=(5, 5))
ttk.Button(screenshot_frame, text="一键完整流程", command=self._ps_run_full_flow, width=20).pack(side=tk.LEFT, padx=(5, 5))
ttk.Button(screenshot_frame, text="关闭所有CMD", command=self._ps_close_all_cmd, width=15).pack(side=tk.LEFT, padx=(5, 0))
# === 日志输出区域 ===
log_frame = ttk.LabelFrame(self.tab_screenshot, text="运行日志", padding="10")
log_frame.grid(row=2, column=0, sticky=(tk.W, tk.E, tk.N, tk.S), pady=(10, 0))
log_frame.columnconfigure(0, weight=1)
log_frame.rowconfigure(0, weight=1)
self.ps_log_text = scrolledtext.ScrolledText(
log_frame,
wrap=tk.WORD,
width=80,
height=15,
font=("Consolas", 9)
)
self.ps_log_text.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
# 进度条
self.ps_progress = ttk.Progressbar(
self.tab_screenshot,
mode='indeterminate',
length=400
)
self.ps_progress.grid(row=3, column=0, pady=(10, 0), sticky=(tk.W, tk.E))
# 状态标签
self.ps_status_var = tk.StringVar(value="就绪")
self.ps_status_label = ttk.Label(
self.tab_screenshot,
textvariable=self.ps_status_var,
foreground="#666"
)
self.ps_status_label.grid(row=4, column=0, pady=(10, 0))
def _browse_project_path(self):
"""浏览项目路径"""
from tkinter import filedialog
path = filedialog.askdirectory(title="选择项目文件夹")
if path:
self.ps_project_path_var.set(path)
def _ps_log(self, message: str):
"""添加项目截图日志"""
self.ps_log_text.insert(tk.END, f"{message}\n")
self.ps_log_text.see(tk.END)
self.root.update_idletasks()
def _get_ps_config(self) -> ProjectConfig:
"""获取项目截图配置"""
ps_config = self.config.get("project_screenshot", {})
# 自动检测是否有前台前端
has_front = self._check_has_front(self.ps_project_path_var.get())
self._ps_log(f"自动检测前台前端: {'' if has_front else ''}")
return ProjectConfig(
project_path=self.ps_project_path_var.get(),
desktop_path=self.ps_desktop_path_var.get(),
has_front=has_front,
install_server=ps_config.get("scripts", {}).get("install_server", "run_install_server.bat"),
install_front=ps_config.get("scripts", {}).get("install_front", "run_install_front.bat"),
install_admin=ps_config.get("scripts", {}).get("install_admin", "run_install_admin.bat"),
run_server=ps_config.get("scripts", {}).get("run_server", "run_server.bat"),
run_front=ps_config.get("scripts", {}).get("run_front", "run_front.bat"),
run_admin=ps_config.get("scripts", {}).get("run_admin", "run_admin.bat"),
run_sql=ps_config.get("scripts", {}).get("run_sql", "run_sql.bat"),
bat_folder=self.ps_bat_folder_var.get(),
sql_script_path=ps_config.get("sql", {}).get("script_path", "server/db/init.sql"),
backend_url=ps_config.get("services", {}).get("backend", {}).get("url", "http://localhost:8080"),
backend_startup_timeout=ps_config.get("services", {}).get("backend", {}).get("startup_timeout", 120),
front_url=ps_config.get("services", {}).get("front", {}).get("url", "http://localhost:8082"),
front_login_url=ps_config.get("services", {}).get("front", {}).get("login_url", "http://localhost:8082/#/login"),
front_startup_timeout=ps_config.get("services", {}).get("front", {}).get("startup_timeout", 60),
admin_url=ps_config.get("services", {}).get("admin", {}).get("url", "http://localhost:8081"),
admin_login_url=ps_config.get("services", {}).get("admin", {}).get("login_url", "http://localhost:8081/#/login"),
admin_startup_timeout=ps_config.get("services", {}).get("admin", {}).get("startup_timeout", 60),
admin_username=ps_config.get("login", {}).get("admin", {}).get("username", "admin"),
admin_password=ps_config.get("login", {}).get("admin", {}).get("password", "admin"),
front_username=ps_config.get("login", {}).get("front", {}).get("username", "2"),
front_password=ps_config.get("login", {}).get("front", {}).get("password", "123456"),
screenshot_output_dir=ps_config.get("screenshot", {}).get("output_dir", "./screenshots"),
screenshot_delay=ps_config.get("screenshot", {}).get("delay", 2000),
admin_menu_selector=ps_config.get("screenshot", {}).get("menu_selectors", {}).get("admin", ".el-menu-item, .el-sub-menu__title"),
front_menu_selector=ps_config.get("screenshot", {}).get("menu_selectors", {}).get("front", ".nav-item, .menu-item"),
swiper_source_folder=ps_config.get("swiper_images", {}).get("source_folder", "bg_pic"),
swiper_target_subpath=ps_config.get("swiper_images", {}).get("target_subpath", "server/src/main/resources/static/file"),
swiper_target_names=ps_config.get("swiper_images", {}).get("target_names", ["swiperPicture1.jpg", "swiperPicture2.jpg", "swiperPicture3.jpg"]),
swiper_count=ps_config.get("swiper_images", {}).get("count", 3),
login_vue_subpath=ps_config.get("login_background", {}).get("vue_file_subpath", "front/manage_code/src/views/login.vue"),
login_background_images=ps_config.get("login_background", {}).get("images", []),
code_source_folder=ps_config.get("code_source_folder", "code"),
code_target_path=self.ps_code_target_var.get(),
show_cmd_window=self.ps_show_cmd_var.get()
)
def _check_has_front(self, project_path: str) -> bool:
"""
自动检测是否有前台前端
检查项目路径下的源码文件夹中是否包含 client_code 目录
Args:
project_path: 项目路径
Returns:
是否有前台前端
"""
try:
code_dir = os.path.join(project_path, "源码")
if not os.path.exists(code_dir):
return False
# 获取 code 文件夹下的第一个子目录
first_subdir = None
for item in os.listdir(code_dir):
item_path = os.path.join(code_dir, item)
if os.path.isdir(item_path):
first_subdir = item_path
break
if not first_subdir:
return False
# 遍历查找 client_code 目录
for root, dirs, files in os.walk(first_subdir):
if 'client_code' in dirs:
return True
return False
except Exception as e:
print(f"检测前台前端失败: {str(e)}")
return False
def _ps_set_running(self, running: bool):
"""设置项目截图运行状态"""
if running:
self.ps_progress.start()
self.ps_status_var.set("运行中...")
else:
self.ps_progress.stop()
self.ps_status_var.set("就绪")
def _ps_install_server(self):
"""Maven 构建后端"""
def run():
try:
self._ps_set_running(True)
self._ps_log("=" * 50)
self._ps_log("开始 Maven 构建后端...")
config = self._get_ps_config()
automation = ProjectScreenshotAutomation(config, log_callback=self._ps_log)
success, msg = automation.install_server()
if success:
self._ps_log("Maven 构建完成!")
else:
self._ps_log(f"构建失败: {msg}")
except Exception as e:
self._ps_log(f"错误: {str(e)}")
finally:
self._ps_set_running(False)
threading.Thread(target=run, daemon=True).start()
def _ps_install_front(self):
"""安装前台前端依赖"""
def run():
try:
self._ps_set_running(True)
self._ps_log("=" * 50)
self._ps_log("开始安装前台前端依赖...")
config = self._get_ps_config()
automation = ProjectScreenshotAutomation(config, log_callback=self._ps_log)
success, msg = automation.install_front()
if success:
self._ps_log("前台依赖安装完成!")
else:
self._ps_log(f"安装失败: {msg}")
except Exception as e:
self._ps_log(f"错误: {str(e)}")
finally:
self._ps_set_running(False)
threading.Thread(target=run, daemon=True).start()
def _ps_install_admin(self):
"""安装后台前端依赖"""
def run():
try:
self._ps_set_running(True)
self._ps_log("=" * 50)
self._ps_log("开始安装后台前端依赖...")
config = self._get_ps_config()
automation = ProjectScreenshotAutomation(config, log_callback=self._ps_log)
success, msg = automation.install_admin()
if success:
self._ps_log("后台依赖安装完成!")
else:
self._ps_log(f"安装失败: {msg}")
except Exception as e:
self._ps_log(f"错误: {str(e)}")
finally:
self._ps_set_running(False)
threading.Thread(target=run, daemon=True).start()
def _ps_run_sql(self):
"""导入 SQL 脚本"""
def run():
try:
self._ps_set_running(True)
self._ps_log("=" * 50)
self._ps_log("开始导入 SQL...")
config = self._get_ps_config()
automation = ProjectScreenshotAutomation(config, log_callback=self._ps_log)
success, msg = automation.run_sql_script()
if success:
self._ps_log("SQL 导入完成!")
else:
self._ps_log(f"导入失败: {msg}")
except Exception as e:
self._ps_log(f"错误: {str(e)}")
finally:
self._ps_set_running(False)
threading.Thread(target=run, daemon=True).start()
def _ps_start_server(self):
"""启动后端服务"""
def run():
try:
self._ps_set_running(True)
self._ps_log("=" * 50)
self._ps_log("启动后端服务...")
config = self._get_ps_config()
automation = ProjectScreenshotAutomation(config, log_callback=self._ps_log)
automation.start_backend()
if automation.wait_for_service(config.backend_url, config.backend_startup_timeout):
self._ps_log("后端服务启动成功!")
else:
self._ps_log("后端服务启动超时,请手动检查")
except Exception as e:
self._ps_log(f"错误: {str(e)}")
finally:
self._ps_set_running(False)
threading.Thread(target=run, daemon=True).start()
def _ps_start_front(self):
"""启动前台前端"""
def run():
try:
self._ps_set_running(True)
self._ps_log("=" * 50)
self._ps_log("启动前台前端...")
config = self._get_ps_config()
automation = ProjectScreenshotAutomation(config, log_callback=self._ps_log)
automation.start_front()
if automation.wait_for_service(config.front_url, config.front_startup_timeout):
self._ps_log("前台前端启动成功!")
else:
self._ps_log("前台前端启动超时,请手动检查")
except Exception as e:
self._ps_log(f"错误: {str(e)}")
finally:
self._ps_set_running(False)
threading.Thread(target=run, daemon=True).start()
def _ps_organize_code(self):
"""整理项目代码"""
def run():
try:
self._ps_set_running(True)
self._ps_log("=" * 50)
self._ps_log("开始整理项目代码...")
config = self._get_ps_config()
automation = ProjectScreenshotAutomation(config, log_callback=self._ps_log)
sql_filename = automation.organize_project_code()
if sql_filename:
self._ps_log(f"代码整理完成SQL文件名: {sql_filename}")
else:
self._ps_log("代码整理完成但未获取到SQL文件名")
except Exception as e:
self._ps_log(f"错误: {str(e)}")
finally:
self._ps_set_running(False)
threading.Thread(target=run, daemon=True).start()
def _ps_start_admin(self):
"""启动后台前端"""
def run():
try:
self._ps_set_running(True)
self._ps_log("=" * 50)
self._ps_log("启动后台前端...")
config = self._get_ps_config()
automation = ProjectScreenshotAutomation(config, log_callback=self._ps_log)
automation.start_admin()
if automation.wait_for_service(config.admin_url, config.admin_startup_timeout):
self._ps_log("后台前端启动成功!")
else:
self._ps_log("后台前端启动超时,请手动检查")
except Exception as e:
self._ps_log(f"错误: {str(e)}")
finally:
self._ps_set_running(False)
threading.Thread(target=run, daemon=True).start()
def _ps_replace_images(self):
"""更换图片"""
def run():
try:
self._ps_set_running(True)
self._ps_log("=" * 50)
self._ps_log("更换图片...")
config = self._get_ps_config()
automation = ProjectScreenshotAutomation(config, log_callback=self._ps_log)
# 更换前台轮播图片
self._ps_log("更换前台轮播图片...")
result = automation.replace_swiper_images()
self._ps_log(result)
# 更换后台登录背景图
self._ps_log("更换后台登录背景图...")
if automation.update_login_vue_background():
self._ps_log("后台登录背景图更换成功!")
else:
self._ps_log("后台登录背景图更换失败")
except Exception as e:
self._ps_log(f"错误: {str(e)}")
finally:
self._ps_set_running(False)
threading.Thread(target=run, daemon=True).start()
def _ps_capture_admin(self):
"""后台截图"""
def run():
try:
self._ps_set_running(True)
self._ps_log("=" * 50)
self._ps_log("开始后台截图...")
config = self._get_ps_config()
automation = ProjectScreenshotAutomation(config, log_callback=self._ps_log)
import asyncio
# 截图保存到桌面路径下的"截图"文件夹
output_dir = os.path.join(config.desktop_path, "截图")
os.makedirs(output_dir, exist_ok=True)
screenshots = asyncio.run(automation.capture_admin_screenshots_only(output_dir))
self._ps_log(f"后台截图完成!共生成 {len(screenshots)} 张截图,保存到: {output_dir}")
except Exception as e:
self._ps_log(f"错误: {str(e)}")
finally:
self._ps_set_running(False)
threading.Thread(target=run, daemon=True).start()
def _ps_capture_front(self):
"""前台截图"""
def run():
try:
self._ps_set_running(True)
self._ps_log("=" * 50)
self._ps_log("开始前台截图...")
config = self._get_ps_config()
automation = ProjectScreenshotAutomation(config, log_callback=self._ps_log)
import asyncio
# 截图保存到桌面路径下的"截图"文件夹
output_dir = os.path.join(config.desktop_path, "截图")
os.makedirs(output_dir, exist_ok=True)
screenshots = asyncio.run(automation.capture_front_screenshots_only(output_dir))
self._ps_log(f"前台截图完成!共生成 {len(screenshots)} 张截图,保存到: {output_dir}")
except Exception as e:
self._ps_log(f"错误: {str(e)}")
finally:
self._ps_set_running(False)
threading.Thread(target=run, daemon=True).start()
def _ps_finalize_screenshots(self):
"""截图收尾工作"""
def run():
try:
self._ps_set_running(True)
self._ps_log("=" * 50)
self._ps_log("开始执行截图收尾工作...")
config = self._get_ps_config()
automation = ProjectScreenshotAutomation(config, log_callback=self._ps_log)
# 截图保存到桌面路径下的"截图"文件夹
output_dir = os.path.join(config.desktop_path, "截图")
automation.finalize_screenshots(output_dir)
self._ps_log(f"截图收尾工作完成!")
except Exception as e:
self._ps_log(f"错误: {str(e)}")
finally:
self._ps_set_running(False)
threading.Thread(target=run, daemon=True).start()
def _ps_run_full_flow(self):
"""一键完整流程"""
def run():
try:
self._ps_set_running(True)
config = self._get_ps_config()
automation = ProjectScreenshotAutomation(config, log_callback=self._ps_log)
success = automation.run_full_flow()
if success:
self.root.after(0, lambda: messagebox.showinfo("成功", "完整流程执行成功!"))
else:
self.root.after(0, lambda: messagebox.showerror("错误", "流程执行失败,请查看日志"))
except Exception as e:
self._ps_log(f"错误: {str(e)}")
self.root.after(0, lambda: messagebox.showerror("错误", f"执行失败: {str(e)}"))
finally:
self._ps_set_running(False)
threading.Thread(target=run, daemon=True).start()
def _ps_close_all_cmd(self):
"""关闭所有CMD窗口"""
def run():
try:
self._ps_set_running(True)
self._ps_log("=" * 50)
self._ps_log("开始关闭所有CMD窗口...")
config = self._get_ps_config()
# 构建 close_cmd.bat 路径
close_cmd_bat = os.path.join(config.desktop_path, config.bat_folder, "close_cmd.bat")
if not os.path.exists(close_cmd_bat):
self._ps_log(f"错误close_cmd.bat 不存在: {close_cmd_bat}")
return
self._ps_log(f"执行: {close_cmd_bat}")
# 执行 bat 文件弹出CMD窗口
import subprocess
if config.show_cmd_window:
# 显示CMD窗口
startupinfo = subprocess.STARTUPINFO()
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
startupinfo.wShowWindow = 1 # SW_SHOWNORMAL
subprocess.Popen(
['cmd', '/c', close_cmd_bat],
creationflags=subprocess.CREATE_NEW_CONSOLE,
startupinfo=startupinfo,
cwd=config.desktop_path
)
self._ps_log("CMD窗口已弹出执行关闭命令")
else:
# 不显示CMD窗口
result = subprocess.run(
['cmd', '/c', close_cmd_bat],
capture_output=True,
text=True,
encoding='utf-8',
errors='ignore'
)
if result.returncode == 0:
self._ps_log("CMD窗口关闭成功")
if result.stdout:
self._ps_log(result.stdout)
else:
self._ps_log(f"关闭失败: {result.stderr}")
except Exception as e:
self._ps_log(f"错误: {str(e)}")
finally:
self._ps_set_running(False)
threading.Thread(target=run, daemon=True).start()
def main():
"""启动 GUI"""
root = tk.Tk()
app = YidaimaGUI(root)
root.mainloop()
if __name__ == "__main__":
main()