2026-04-09 14:55:54 +08:00
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
|
|
import tkinter as tk
|
|
|
|
|
|
from tkinter import ttk, messagebox, scrolledtext
|
|
|
|
|
|
import threading
|
|
|
|
|
|
import sys
|
|
|
|
|
|
import os
|
2026-04-13 15:41:39 +08:00
|
|
|
|
import subprocess
|
2026-04-15 10:24:38 +08:00
|
|
|
|
import re
|
2026-04-09 14:55:54 +08:00
|
|
|
|
|
|
|
|
|
|
# 添加当前目录到路径
|
|
|
|
|
|
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
|
2026-04-10 14:37:23 +08:00
|
|
|
|
from project_screenshot import ProjectScreenshotAutomation, ProjectConfig
|
2026-04-15 10:24:38 +08:00
|
|
|
|
from quark_uploader import QuarkUploader
|
|
|
|
|
|
from baidu_uploader import BaiduUploader
|
2026-04-09 14:55:54 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 = ""
|
2026-04-15 10:24:38 +08:00
|
|
|
|
self.search_zlzt = "全部"
|
2026-04-09 14:55:54 +08:00
|
|
|
|
|
|
|
|
|
|
# 创建界面
|
|
|
|
|
|
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()
|
2026-04-10 14:37:23 +08:00
|
|
|
|
|
|
|
|
|
|
# === Tab 3: 项目运行截图 ===
|
|
|
|
|
|
self.tab_screenshot = ttk.Frame(self.notebook, padding="10")
|
|
|
|
|
|
self.notebook.add(self.tab_screenshot, text="项目运行截图")
|
|
|
|
|
|
self._create_screenshot_tab()
|
|
|
|
|
|
|
2026-04-09 14:55:54 +08:00
|
|
|
|
# === 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))
|
|
|
|
|
|
|
2026-04-13 15:41:39 +08:00
|
|
|
|
# 调用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))
|
|
|
|
|
|
|
2026-04-09 14:55:54 +08:00
|
|
|
|
# === 操作按钮区域 ===
|
|
|
|
|
|
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()
|
2026-04-15 10:24:38 +08:00
|
|
|
|
self.search_entry = ttk.Entry(search_frame, textvariable=self.search_var, width=30)
|
2026-04-09 14:55:54 +08:00
|
|
|
|
self.search_entry.grid(row=0, column=1, sticky=(tk.W, tk.E), padx=(10, 0))
|
|
|
|
|
|
|
2026-04-15 10:24:38 +08:00
|
|
|
|
ttk.Label(search_frame, text="整理状态:").grid(row=0, column=2, sticky=tk.W, padx=(10, 0))
|
|
|
|
|
|
self.search_zlzt_var = tk.StringVar(value="全部")
|
|
|
|
|
|
self.zlzt_combo = ttk.Combobox(
|
|
|
|
|
|
search_frame,
|
|
|
|
|
|
textvariable=self.search_zlzt_var,
|
|
|
|
|
|
values=("全部", "已整理", "可整理"),
|
|
|
|
|
|
state="readonly",
|
|
|
|
|
|
width=10
|
|
|
|
|
|
)
|
|
|
|
|
|
self.zlzt_combo.grid(row=0, column=3, sticky=tk.W, padx=(5, 0))
|
|
|
|
|
|
|
|
|
|
|
|
ttk.Button(search_frame, text="搜索", command=self._search_projects, width=10).grid(row=0, column=4, padx=(10, 0))
|
|
|
|
|
|
ttk.Button(search_frame, text="刷新", command=self._refresh_projects, width=10).grid(row=0, column=5, padx=(10, 0))
|
2026-04-09 14:55:54 +08:00
|
|
|
|
|
|
|
|
|
|
# === 数据表格区域 ===
|
|
|
|
|
|
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))
|
|
|
|
|
|
|
2026-04-15 10:24:38 +08:00
|
|
|
|
row += 1
|
|
|
|
|
|
|
|
|
|
|
|
# === 夸克网盘配置 ===
|
|
|
|
|
|
quark_frame = ttk.LabelFrame(self.settings_frame, text="夸克网盘配置", padding="10")
|
|
|
|
|
|
quark_frame.grid(row=row, column=0, columnspan=2, sticky=(tk.W, tk.E), pady=(0, 15))
|
|
|
|
|
|
quark_frame.columnconfigure(1, weight=1)
|
|
|
|
|
|
|
|
|
|
|
|
ttk.Label(quark_frame, text="Cookie 目录:").grid(row=0, column=0, sticky=tk.W)
|
|
|
|
|
|
self.setting_quark_cookies_var = tk.StringVar(value=self.config.get("quark.cookies_dir", os.path.join(os.getcwd(), "data", "quark_cookies")))
|
|
|
|
|
|
ttk.Entry(quark_frame, textvariable=self.setting_quark_cookies_var, width=50).grid(row=0, column=1, sticky=(tk.W, tk.E), padx=(10, 0))
|
|
|
|
|
|
|
|
|
|
|
|
ttk.Label(quark_frame, text="根目录名称:").grid(row=1, column=0, sticky=tk.W, pady=(10, 0))
|
|
|
|
|
|
self.setting_quark_root_var = tk.StringVar(value=self.config.get("quark.root_path", "精品项目整理"))
|
|
|
|
|
|
ttk.Entry(quark_frame, textvariable=self.setting_quark_root_var, width=40).grid(row=1, column=1, sticky=(tk.W, tk.E), padx=(10, 0), pady=(10, 0))
|
|
|
|
|
|
|
|
|
|
|
|
row += 1
|
|
|
|
|
|
|
|
|
|
|
|
# === 百度网盘配置 ===
|
|
|
|
|
|
baidu_frame = ttk.LabelFrame(self.settings_frame, text="百度网盘配置", padding="10")
|
|
|
|
|
|
baidu_frame.grid(row=row, column=0, columnspan=2, sticky=(tk.W, tk.E), pady=(0, 15))
|
|
|
|
|
|
baidu_frame.columnconfigure(1, weight=1)
|
|
|
|
|
|
|
|
|
|
|
|
ttk.Label(baidu_frame, text="Cookie 目录:").grid(row=0, column=0, sticky=tk.W)
|
|
|
|
|
|
self.setting_baidu_cookies_var = tk.StringVar(value=self.config.get("baidu.cookies_dir", os.path.join(os.getcwd(), "data", "baidu_cookies")))
|
|
|
|
|
|
ttk.Entry(baidu_frame, textvariable=self.setting_baidu_cookies_var, width=50).grid(row=0, column=1, sticky=(tk.W, tk.E), padx=(10, 0))
|
|
|
|
|
|
|
|
|
|
|
|
ttk.Label(baidu_frame, text="根目录名称:").grid(row=1, column=0, sticky=tk.W, pady=(10, 0))
|
|
|
|
|
|
self.setting_baidu_root_var = tk.StringVar(value=self.config.get("baidu.root_path", "精品项目整理"))
|
|
|
|
|
|
ttk.Entry(baidu_frame, textvariable=self.setting_baidu_root_var, width=40).grid(row=1, column=1, sticky=(tk.W, tk.E), padx=(10, 0), pady=(10, 0))
|
|
|
|
|
|
|
2026-04-09 14:55:54 +08:00
|
|
|
|
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))
|
2026-04-13 15:41:39 +08:00
|
|
|
|
|
2026-04-09 14:55:54 +08:00
|
|
|
|
# 获取配置(从配置文件读取)
|
|
|
|
|
|
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()
|
2026-04-13 15:41:39 +08:00
|
|
|
|
use_api = self.use_api_var.get()
|
|
|
|
|
|
|
2026-04-09 14:55:54 +08:00
|
|
|
|
# Step 1
|
|
|
|
|
|
self.root.after(0, lambda: self.log("\n[Step 1] 开始执行..."))
|
2026-04-13 15:41:39 +08:00
|
|
|
|
|
|
|
|
|
|
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] 浏览器方式执行完成!"))
|
|
|
|
|
|
|
2026-04-09 14:55:54 +08:00
|
|
|
|
self.root.after(0, lambda: self.log("[Step 1] 执行完成!"))
|
2026-04-13 15:41:39 +08:00
|
|
|
|
|
2026-04-09 14:55:54 +08:00
|
|
|
|
# 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)
|
|
|
|
|
|
|
2026-04-13 15:41:39 +08:00
|
|
|
|
step2 = Step2Converter(log_callback=log_callback, css_scheme_id=css_scheme_id, project_name=project_name)
|
2026-04-09 14:55:54 +08:00
|
|
|
|
step2.run()
|
|
|
|
|
|
self.root.after(0, lambda: self.log("[Step 2] 执行完成!"))
|
2026-04-13 15:41:39 +08:00
|
|
|
|
|
2026-04-09 14:55:54 +08:00
|
|
|
|
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("成功", "完整流程执行成功!"))
|
2026-04-13 15:41:39 +08:00
|
|
|
|
|
2026-04-09 14:55:54 +08:00
|
|
|
|
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,
|
2026-04-15 10:24:38 +08:00
|
|
|
|
search_name=self.search_keyword if self.search_keyword else None,
|
|
|
|
|
|
zlzt=self.search_zlzt if self.search_zlzt and self.search_zlzt != "全部" else None
|
2026-04-09 14:55:54 +08:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
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()
|
2026-04-15 10:24:38 +08:00
|
|
|
|
self.search_zlzt = self.search_zlzt_var.get()
|
2026-04-09 14:55:54 +08:00
|
|
|
|
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("")
|
2026-04-15 10:24:38 +08:00
|
|
|
|
self.search_zlzt = "全部"
|
|
|
|
|
|
self.search_zlzt_var.set("全部")
|
2026-04-09 14:55:54 +08:00
|
|
|
|
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)
|
|
|
|
|
|
|
|
|
|
|
|
# 变更已整理状态选项
|
2026-04-15 10:24:38 +08:00
|
|
|
|
new_zlzt = "已整理" if current_zlzt != "已整理" else "可整理"
|
2026-04-09 14:55:54 +08:00
|
|
|
|
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()
|
2026-04-15 10:24:38 +08:00
|
|
|
|
},
|
|
|
|
|
|
"quark": {
|
|
|
|
|
|
"cookies_dir": self.setting_quark_cookies_var.get(),
|
|
|
|
|
|
"root_path": self.setting_quark_root_var.get()
|
|
|
|
|
|
},
|
|
|
|
|
|
"baidu": {
|
|
|
|
|
|
"cookies_dir": self.setting_baidu_cookies_var.get(),
|
|
|
|
|
|
"root_path": self.setting_baidu_root_var.get()
|
2026-04-09 14:55:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
# 保存到文件
|
|
|
|
|
|
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"))
|
2026-04-15 10:24:38 +08:00
|
|
|
|
self.setting_quark_cookies_var.set(self.config.get("quark.cookies_dir", os.path.join(os.getcwd(), "data", "quark_cookies")))
|
|
|
|
|
|
self.setting_quark_root_var.set(self.config.get("quark.root_path", "精品项目整理"))
|
|
|
|
|
|
self.setting_baidu_cookies_var.set(self.config.get("baidu.cookies_dir", os.path.join(os.getcwd(), "data", "baidu_cookies")))
|
|
|
|
|
|
self.setting_baidu_root_var.set(self.config.get("baidu.root_path", "精品项目整理"))
|
2026-04-09 14:55:54 +08:00
|
|
|
|
|
2026-04-10 14:37:23 +08:00
|
|
|
|
# ==================== 项目运行截图 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", ""))
|
2026-04-15 10:24:38 +08:00
|
|
|
|
ps_project_path_entry = ttk.Entry(path_frame, textvariable=self.ps_project_path_var, width=50)
|
2026-04-10 14:37:23 +08:00
|
|
|
|
ps_project_path_entry.grid(row=0, column=1, sticky=(tk.W, tk.E), padx=(10, 0))
|
2026-04-15 10:24:38 +08:00
|
|
|
|
|
|
|
|
|
|
btn_path_frame = ttk.Frame(path_frame)
|
|
|
|
|
|
btn_path_frame.grid(row=0, column=2, padx=(10, 0))
|
|
|
|
|
|
ttk.Button(btn_path_frame, text="选择项目", command=self._show_project_selector, width=10).pack(side=tk.LEFT)
|
|
|
|
|
|
ttk.Button(btn_path_frame, text="浏览...", command=self._browse_project_path, width=10).pack(side=tk.LEFT, padx=(5, 0))
|
|
|
|
|
|
|
|
|
|
|
|
# 项目名称 (name2)
|
|
|
|
|
|
ttk.Label(path_frame, text="项目名称:").grid(row=1, column=0, sticky=tk.W, pady=(10, 0))
|
|
|
|
|
|
self.ps_project_name_var = tk.StringVar(value=ps_config.get("project_name2", ""))
|
|
|
|
|
|
ps_project_name_entry = ttk.Entry(path_frame, textvariable=self.ps_project_name_var, width=60)
|
|
|
|
|
|
ps_project_name_entry.grid(row=1, column=1, sticky=(tk.W, tk.E), padx=(10, 0), pady=(10, 0))
|
2026-04-10 14:37:23 +08:00
|
|
|
|
|
|
|
|
|
|
# 桌面路径
|
2026-04-15 10:24:38 +08:00
|
|
|
|
ttk.Label(path_frame, text="桌面路径:").grid(row=2, column=0, sticky=tk.W, pady=(10, 0))
|
2026-04-10 14:37:23 +08:00
|
|
|
|
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)
|
2026-04-15 10:24:38 +08:00
|
|
|
|
ps_desktop_path_entry.grid(row=2, column=1, sticky=(tk.W, tk.E), padx=(10, 0), pady=(10, 0))
|
2026-04-10 14:37:23 +08:00
|
|
|
|
|
|
|
|
|
|
# bat 文件夹名称
|
2026-04-15 10:24:38 +08:00
|
|
|
|
ttk.Label(path_frame, text="bat文件夹:").grid(row=3, column=0, sticky=tk.W, pady=(10, 0))
|
2026-04-10 14:37:23 +08:00
|
|
|
|
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)
|
2026-04-15 10:24:38 +08:00
|
|
|
|
ps_bat_folder_entry.grid(row=3, column=1, sticky=(tk.W, tk.E), padx=(10, 0), pady=(10, 0))
|
2026-04-10 14:37:23 +08:00
|
|
|
|
|
|
|
|
|
|
# 代码目标路径
|
|
|
|
|
|
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))
|
2026-04-10 19:59:18 +08:00
|
|
|
|
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))
|
2026-04-10 14:37:23 +08:00
|
|
|
|
|
|
|
|
|
|
# === 操作按钮区域 ===
|
|
|
|
|
|
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)
|
2026-04-15 10:24:38 +08:00
|
|
|
|
screenshot_frame.pack(fill=tk.X, pady=(0, 10))
|
2026-04-10 14:37:23 +08:00
|
|
|
|
|
|
|
|
|
|
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))
|
|
|
|
|
|
|
2026-04-15 10:24:38 +08:00
|
|
|
|
# 上传按钮
|
|
|
|
|
|
upload_frame = ttk.Frame(button_frame)
|
|
|
|
|
|
upload_frame.pack(fill=tk.X)
|
|
|
|
|
|
|
|
|
|
|
|
ttk.Button(upload_frame, text="整理项目", command=self._ps_organize_project, width=15).pack(side=tk.LEFT, padx=(0, 5))
|
|
|
|
|
|
ttk.Button(upload_frame, text="上传夸克网盘", command=self._ps_upload_quark, width=15).pack(side=tk.LEFT, padx=(5, 5))
|
|
|
|
|
|
ttk.Button(upload_frame, text="上传百度网盘", command=self._ps_upload_baidu, width=15).pack(side=tk.LEFT, padx=(5, 0))
|
|
|
|
|
|
|
2026-04-10 14:37:23 +08:00
|
|
|
|
# === 日志输出区域 ===
|
|
|
|
|
|
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", {})
|
2026-04-10 19:59:18 +08:00
|
|
|
|
|
|
|
|
|
|
# 自动检测是否有前台前端
|
|
|
|
|
|
has_front = self._check_has_front(self.ps_project_path_var.get())
|
|
|
|
|
|
self._ps_log(f"自动检测前台前端: {'有' if has_front else '无'}")
|
|
|
|
|
|
|
2026-04-10 14:37:23 +08:00
|
|
|
|
return ProjectConfig(
|
|
|
|
|
|
project_path=self.ps_project_path_var.get(),
|
|
|
|
|
|
desktop_path=self.ps_desktop_path_var.get(),
|
2026-04-10 19:59:18 +08:00
|
|
|
|
has_front=has_front,
|
2026-04-10 14:37:23 +08:00
|
|
|
|
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()
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2026-04-10 19:59:18 +08:00
|
|
|
|
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
|
|
|
|
|
|
|
2026-04-10 14:37:23 +08:00
|
|
|
|
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()
|
|
|
|
|
|
|
2026-04-15 10:24:38 +08:00
|
|
|
|
def _ps_organize_project(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)
|
|
|
|
|
|
|
|
|
|
|
|
project_name = self.ps_project_name_var.get().strip()
|
|
|
|
|
|
if not project_name:
|
|
|
|
|
|
project_name = None
|
|
|
|
|
|
else:
|
|
|
|
|
|
self._ps_log(f"使用项目名称: {project_name}")
|
|
|
|
|
|
|
|
|
|
|
|
zip_file = automation.organize_and_zip_project(override_name=project_name)
|
|
|
|
|
|
|
|
|
|
|
|
if zip_file:
|
|
|
|
|
|
self._ps_log("=" * 50)
|
|
|
|
|
|
self._ps_log(f"项目整理完成!压缩包已生成:{zip_file}")
|
|
|
|
|
|
self.root.after(0, lambda: messagebox.showinfo("成功", f"项目整理并打包成功!\n压缩包已生成在桌面。"))
|
|
|
|
|
|
else:
|
|
|
|
|
|
self._ps_log("=" * 50)
|
|
|
|
|
|
self._ps_log("项目整理失败,请检查日志。")
|
|
|
|
|
|
self.root.after(0, lambda: messagebox.showerror("错误", "项目整理并打包失败。"))
|
|
|
|
|
|
|
|
|
|
|
|
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_upload_quark(self):
|
|
|
|
|
|
"""仅上传到夸克网盘"""
|
|
|
|
|
|
def run():
|
|
|
|
|
|
try:
|
|
|
|
|
|
self._ps_set_running(True)
|
|
|
|
|
|
self._ps_log("=" * 50)
|
|
|
|
|
|
self._ps_log("准备上传到夸克网盘...")
|
|
|
|
|
|
|
|
|
|
|
|
# 检查压缩包是否存在
|
|
|
|
|
|
project_name = self.ps_project_name_var.get().strip()
|
|
|
|
|
|
desktop_path = self.ps_desktop_path_var.get()
|
|
|
|
|
|
|
|
|
|
|
|
if not project_name:
|
|
|
|
|
|
# 如果没填,尝试从路径拿
|
|
|
|
|
|
project_name = os.path.basename(self.ps_project_path_var.get().rstrip('/\\')).strip()
|
|
|
|
|
|
|
|
|
|
|
|
zip_file = os.path.join(desktop_path, f"{project_name}.zip")
|
|
|
|
|
|
|
|
|
|
|
|
if not os.path.exists(zip_file):
|
|
|
|
|
|
self._ps_log(f"错误:未找到压缩包,请先执行‘整理项目’\n路径:{zip_file}")
|
|
|
|
|
|
self.root.after(0, lambda: messagebox.showwarning("提示", "未找到对应的项目压缩包,请先执行‘整理项目’。"))
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
chrome_path = self.config.get("chrome.path", "")
|
|
|
|
|
|
cookies_dir = self.config.get("quark.cookies_dir", os.path.join(os.getcwd(), "data", "quark_cookies"))
|
|
|
|
|
|
root_path = self.config.get("quark.root_path", "精品项目整理")
|
|
|
|
|
|
|
|
|
|
|
|
uploader = QuarkUploader(chrome_path, cookies_dir, log_callback=self._ps_log)
|
|
|
|
|
|
success = uploader.upload_file(zip_file, project_name, root_path)
|
|
|
|
|
|
|
|
|
|
|
|
if success:
|
|
|
|
|
|
self._ps_log("=" * 50)
|
|
|
|
|
|
self._ps_log("夸克网盘上传成功!")
|
|
|
|
|
|
self.root.after(0, lambda: messagebox.showinfo("成功", "上传夸克网盘成功!"))
|
|
|
|
|
|
else:
|
|
|
|
|
|
self._ps_log("=" * 50)
|
|
|
|
|
|
self._ps_log("夸克网盘上传失败,请查看日志。")
|
|
|
|
|
|
self.root.after(0, lambda: messagebox.showerror("错误", "上传夸克网盘失败。"))
|
|
|
|
|
|
|
|
|
|
|
|
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_upload_baidu(self):
|
|
|
|
|
|
"""上传到百度网盘"""
|
|
|
|
|
|
def run():
|
|
|
|
|
|
try:
|
|
|
|
|
|
self._ps_set_running(True)
|
|
|
|
|
|
self._ps_log("=" * 50)
|
|
|
|
|
|
self._ps_log("准备上传到百度网盘...")
|
|
|
|
|
|
|
|
|
|
|
|
project_name = self.ps_project_name_var.get().strip()
|
|
|
|
|
|
desktop_path = self.ps_desktop_path_var.get()
|
|
|
|
|
|
|
|
|
|
|
|
if not project_name:
|
|
|
|
|
|
project_name = os.path.basename(self.ps_project_path_var.get().rstrip('/\\')).strip()
|
|
|
|
|
|
|
|
|
|
|
|
zip_file = os.path.join(desktop_path, f"{project_name}.zip")
|
|
|
|
|
|
|
|
|
|
|
|
if not os.path.exists(zip_file):
|
|
|
|
|
|
self._ps_log(f"错误:未找到压缩包,请先执行‘整理项目’\n路径:{zip_file}")
|
|
|
|
|
|
self.root.after(0, lambda: messagebox.showwarning("提示", "未找到对应的项目压缩包,请先执行‘整理项目’。"))
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
chrome_path = self.config.get("chrome.path", "")
|
|
|
|
|
|
cookies_dir = self.config.get("baidu.cookies_dir", os.path.join(os.getcwd(), "data", "baidu_cookies"))
|
|
|
|
|
|
root_path = self.config.get("baidu.root_path", "精品项目整理")
|
|
|
|
|
|
|
|
|
|
|
|
uploader = BaiduUploader(chrome_path, cookies_dir, log_callback=self._ps_log)
|
|
|
|
|
|
success = uploader.upload_file(zip_file, project_name, root_path)
|
|
|
|
|
|
|
|
|
|
|
|
if success:
|
|
|
|
|
|
self._ps_log("=" * 50)
|
|
|
|
|
|
self._ps_log("百度网盘上传成功!")
|
|
|
|
|
|
self.root.after(0, lambda: messagebox.showinfo("成功", "上传百度网盘成功!"))
|
|
|
|
|
|
else:
|
|
|
|
|
|
self._ps_log("=" * 50)
|
|
|
|
|
|
self._ps_log("百度网盘上传失败,请查看日志。")
|
|
|
|
|
|
self.root.after(0, lambda: messagebox.showerror("错误", "上传百度网盘失败。"))
|
|
|
|
|
|
|
|
|
|
|
|
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_upload_cloud(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)
|
|
|
|
|
|
|
|
|
|
|
|
# 直接从输入框获取项目名称,不需要再查数据库
|
|
|
|
|
|
project_name = self.ps_project_name_var.get().strip()
|
|
|
|
|
|
original_project_name = project_name # 保持原始名称用于后续更新数据库
|
|
|
|
|
|
if not project_name:
|
|
|
|
|
|
# 如果为空,则让 automation 尝试从路径提取
|
|
|
|
|
|
project_name = None
|
|
|
|
|
|
else:
|
|
|
|
|
|
self._ps_log(f"使用项目名称: {project_name}")
|
|
|
|
|
|
|
|
|
|
|
|
# 1. 执行整理打包
|
|
|
|
|
|
zip_file = automation.organize_and_zip_project(override_name=project_name)
|
|
|
|
|
|
|
|
|
|
|
|
if zip_file:
|
|
|
|
|
|
self._ps_log("=" * 50)
|
|
|
|
|
|
self._ps_log(f"项目整理完成!压缩包已生成:{zip_file}")
|
|
|
|
|
|
|
|
|
|
|
|
# 2. 上传到夸克网盘
|
|
|
|
|
|
self._ps_log("\n开始准备上传到夸克网盘...")
|
|
|
|
|
|
|
|
|
|
|
|
chrome_path = self.config.get("chrome.path", "")
|
|
|
|
|
|
quark_cookies_dir = self.config.get("quark.cookies_dir", os.path.join(os.getcwd(), "data", "quark_cookies"))
|
|
|
|
|
|
quark_root_path = self.config.get("quark.root_path", "精品项目整理")
|
|
|
|
|
|
|
|
|
|
|
|
# 再次确认项目名称(如果之前是 None,automation 会生成一个)
|
|
|
|
|
|
if not project_name:
|
|
|
|
|
|
project_name = os.path.basename(zip_file).replace(".zip", "")
|
|
|
|
|
|
|
|
|
|
|
|
uploader = QuarkUploader(
|
|
|
|
|
|
chrome_path=chrome_path,
|
|
|
|
|
|
cookies_dir=quark_cookies_dir,
|
|
|
|
|
|
log_callback=self._ps_log
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
success = uploader.upload_file(
|
|
|
|
|
|
file_path=zip_file,
|
|
|
|
|
|
target_folder_name=project_name,
|
|
|
|
|
|
root_path=quark_root_path
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
if success:
|
|
|
|
|
|
self._ps_log("=" * 50)
|
|
|
|
|
|
self._ps_log("项目已成功整理并上传到夸克网盘!")
|
|
|
|
|
|
|
|
|
|
|
|
self.root.after(0, lambda: messagebox.showinfo("成功", "项目整理并上传网盘成功!"))
|
|
|
|
|
|
else:
|
|
|
|
|
|
self._ps_log("=" * 50)
|
|
|
|
|
|
self._ps_log("上传网盘失败,请检查日志。")
|
|
|
|
|
|
self.root.after(0, lambda: messagebox.showerror("错误", "上传网盘失败,请检查日志。"))
|
|
|
|
|
|
else:
|
|
|
|
|
|
self._ps_log("=" * 50)
|
|
|
|
|
|
self._ps_log("项目整理失败,请检查日志。")
|
|
|
|
|
|
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 _show_project_selector(self):
|
|
|
|
|
|
"""显示项目选择对话框"""
|
|
|
|
|
|
if not self.db_manager:
|
|
|
|
|
|
messagebox.showwarning("警告", "数据库未连接")
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
selector = tk.Toplevel(self.root)
|
|
|
|
|
|
selector.title("选择项目")
|
|
|
|
|
|
selector.geometry("800x600")
|
|
|
|
|
|
selector.transient(self.root)
|
|
|
|
|
|
selector.grab_set()
|
|
|
|
|
|
|
|
|
|
|
|
# 搜索框
|
|
|
|
|
|
search_frame = ttk.Frame(selector, padding="10")
|
|
|
|
|
|
search_frame.pack(fill=tk.X)
|
|
|
|
|
|
|
|
|
|
|
|
ttk.Label(search_frame, text="项目名称:").pack(side=tk.LEFT)
|
|
|
|
|
|
search_var = tk.StringVar()
|
|
|
|
|
|
search_entry = ttk.Entry(search_frame, textvariable=search_var, width=40)
|
|
|
|
|
|
search_entry.pack(side=tk.LEFT, padx=10)
|
|
|
|
|
|
|
|
|
|
|
|
# 结果列表
|
|
|
|
|
|
table_frame = ttk.Frame(selector, padding="10")
|
|
|
|
|
|
table_frame.pack(fill=tk.BOTH, expand=True)
|
|
|
|
|
|
|
|
|
|
|
|
columns = ("name", "name2", "paths", "zlzt")
|
|
|
|
|
|
tree = ttk.Treeview(table_frame, columns=columns, show="headings")
|
|
|
|
|
|
tree.heading("name", text="项目名称")
|
|
|
|
|
|
tree.heading("name2", text="名称2")
|
|
|
|
|
|
tree.heading("paths", text="项目路径")
|
|
|
|
|
|
tree.heading("zlzt", text="状态")
|
|
|
|
|
|
|
|
|
|
|
|
tree.column("name", width=200)
|
|
|
|
|
|
tree.column("name2", width=150)
|
|
|
|
|
|
tree.column("paths", width=300)
|
|
|
|
|
|
tree.column("zlzt", width=80)
|
|
|
|
|
|
|
|
|
|
|
|
scrollbar = ttk.Scrollbar(table_frame, orient=tk.VERTICAL, command=tree.yview)
|
|
|
|
|
|
tree.configure(yscrollcommand=scrollbar.set)
|
|
|
|
|
|
|
|
|
|
|
|
tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
|
|
|
|
|
|
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
|
|
|
|
|
|
|
|
|
|
|
|
def load_data():
|
|
|
|
|
|
keyword = search_var.get().strip()
|
|
|
|
|
|
# 清空
|
|
|
|
|
|
for item in tree.get_children():
|
|
|
|
|
|
tree.delete(item)
|
|
|
|
|
|
|
|
|
|
|
|
# 简单查询前100条
|
|
|
|
|
|
projects, _ = self.db_manager.get_projects(page=1, page_size=100, search_name=keyword if keyword else None)
|
|
|
|
|
|
for p in projects:
|
|
|
|
|
|
tree.insert("", tk.END, values=(p.name, p.name2, p.paths, p.zlzt))
|
|
|
|
|
|
|
|
|
|
|
|
# 搜索按钮
|
|
|
|
|
|
ttk.Button(search_frame, text="搜索", command=load_data).pack(side=tk.LEFT)
|
|
|
|
|
|
|
|
|
|
|
|
# 确认按钮
|
|
|
|
|
|
def on_select():
|
|
|
|
|
|
selected = tree.selection()
|
|
|
|
|
|
if not selected:
|
|
|
|
|
|
messagebox.showwarning("提示", "请先选择一个项目")
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
values = tree.item(selected[0], "values")
|
|
|
|
|
|
project_name = values[0]
|
|
|
|
|
|
project_name2 = values[1]
|
|
|
|
|
|
project_path = values[2]
|
|
|
|
|
|
|
|
|
|
|
|
# 更新主界面变量
|
|
|
|
|
|
self.ps_project_path_var.set(project_path)
|
|
|
|
|
|
# 使用名称2填入新增的项目名称输入框
|
|
|
|
|
|
self.ps_project_name_var.set(project_name2 if project_name2 else project_name)
|
|
|
|
|
|
|
|
|
|
|
|
# 同时更新发布页面的项目名称,方便后续对应
|
|
|
|
|
|
if project_name2 and '【' in project_name2:
|
|
|
|
|
|
self.project_name_var.set(project_name2)
|
|
|
|
|
|
else:
|
|
|
|
|
|
self.project_name_var.set(project_name)
|
|
|
|
|
|
|
|
|
|
|
|
self._ps_log(f"已选择项目: {project_name}")
|
|
|
|
|
|
self._ps_log(f"更新项目路径: {project_path}")
|
|
|
|
|
|
self._ps_log(f"更新项目名称: {self.ps_project_name_var.get()}")
|
|
|
|
|
|
|
|
|
|
|
|
selector.destroy()
|
|
|
|
|
|
|
|
|
|
|
|
footer_frame = ttk.Frame(selector, padding="10")
|
|
|
|
|
|
footer_frame.pack(fill=tk.X)
|
|
|
|
|
|
ttk.Button(footer_frame, text="确定选择", command=on_select, width=15).pack(side=tk.RIGHT)
|
|
|
|
|
|
ttk.Button(footer_frame, text="取消", command=selector.destroy, width=15).pack(side=tk.RIGHT, padx=10)
|
|
|
|
|
|
|
|
|
|
|
|
# 初始加载
|
|
|
|
|
|
load_data()
|
|
|
|
|
|
|
|
|
|
|
|
# 绑定回车搜索
|
|
|
|
|
|
search_entry.bind("<Return>", lambda e: load_data())
|
|
|
|
|
|
# 绑定双击选择
|
|
|
|
|
|
tree.bind("<Double-1>", lambda e: on_select())
|
|
|
|
|
|
|
2026-04-09 14:55:54 +08:00
|
|
|
|
|
|
|
|
|
|
def main():
|
|
|
|
|
|
"""启动 GUI"""
|
|
|
|
|
|
root = tk.Tk()
|
|
|
|
|
|
app = YidaimaGUI(root)
|
|
|
|
|
|
root.mainloop()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
|
main()
|