PUGE 1 week geleden
bovenliggende
commit
56932d47d0
6 gewijzigde bestanden met toevoegingen van 651 en 105 verwijderingen
  1. 5 5
      87总控.py
  2. 160 6
      三一平台.py
  3. 69 9
      控制vm虚拟机.py
  4. 91 81
      条码处理/SHEIN.py
  5. 4 4
      条码处理/pdf_merge_config.json
  6. 322 0
      条码处理/速卖通.py

+ 5 - 5
87总控.py

@@ -701,7 +701,7 @@ class WindowManagerApp(QMainWindow):
         
         # 激活窗口
         win32gui.SetForegroundWindow(hwnd)
-        time.sleep(2)  # 等待窗口激活
+        time.sleep(1)  # 等待窗口激活
         
         # 步骤1: 左键点击 X+63, Y+81
         self.click_at_position(hwnd, 29, 41, 'left')
@@ -709,9 +709,9 @@ class WindowManagerApp(QMainWindow):
         self.signals.status.emit(f'📍 步骤1: 左键点击 X+63, Y+81')
         self.click_at_position(hwnd, 63, 81, 'left')
         
-        # 等待4
-        self.signals.status.emit(f'⏳ 等待2秒...')
-        time.sleep(6)
+        # 等待5
+        self.signals.status.emit(f'⏳ 等待5秒...')
+        time.sleep(5)
         
         # 步骤2: 右键点击 X+413, Y+168
         self.signals.status.emit(f'📍 步骤2: 右键点击 X+413, Y+168')
@@ -841,7 +841,7 @@ class WindowManagerApp(QMainWindow):
         self.signals.status.emit(f'📍 最后点击: X+871, Y+266')
         self.click_at_position(hwnd, 871, 266, 'left')
 
-        time.sleep(1)
+        time.sleep(0.5)
         self.click_at_position(hwnd, 671, 70, 'right')
 
         time.sleep(1)

+ 160 - 6
三一平台.py

@@ -13,7 +13,7 @@ from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout,
                              QHBoxLayout, QLabel, QLineEdit, QPushButton, 
                              QTableWidget, QTableWidgetItem, QHeaderView, 
                              QCheckBox, QMessageBox, QGroupBox, QTextEdit,
-                             QGridLayout, QSplitter, QComboBox, QFileDialog)
+                             QGridLayout, QSplitter, QComboBox, QFileDialog, QDialog, QDialogButtonBox)
 from PyQt5.QtCore import Qt, QThread, pyqtSignal, QTimer
 import websocket
 import threading
@@ -1508,16 +1508,170 @@ class MainWindow(QMainWindow):
 
 # ==================== 启动 ====================
 
+class CDKVerificationDialog(QDialog):
+    """CDK验证对话框"""
+    def __init__(self, parent=None):
+        super().__init__(parent)
+        self.setWindowTitle("软件授权验证")
+        self.setModal(True)
+        self.setFixedSize(400, 200)
+        
+        layout = QVBoxLayout()
+        
+        # 说明标签
+        info_label = QLabel("请输入您的CDK授权码以继续使用软件:")
+        info_label.setStyleSheet("font-size: 12px; margin: 10px 0;")
+        layout.addWidget(info_label)
+        
+        # CDK输入框
+        self.cdk_input = QLineEdit()
+        self.cdk_input.setPlaceholderText("请输入CDK授权码...")
+        self.cdk_input.setEchoMode(QLineEdit.Password)  # 隐藏输入
+        self.cdk_input.setStyleSheet("padding: 8px; font-size: 12px;")
+        layout.addWidget(self.cdk_input)
+        
+        # 提示标签(用于显示错误信息)
+        self.status_label = QLabel("")
+        self.status_label.setStyleSheet("color: red; font-size: 11px;")
+        layout.addWidget(self.status_label)
+        
+        # 按钮
+        button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
+        button_box.accepted.connect(self.accept)
+        button_box.rejected.connect(self.reject)
+        layout.addWidget(button_box)
+        
+        self.setLayout(layout)
+    
+    def get_cdk(self):
+        """获取输入的CDK"""
+        return self.cdk_input.text().strip()
+
+def verify_cdk(cdk_code, cdk_type="三一平台"):
+    """
+    验证CDK
+    :param cdk_code: CDK码
+    :param cdk_type: CDK类型(默认为"三一平台")
+    :return: (是否通过, 消息)
+    """
+    try:
+        # 构建请求URL
+        url = f"https://user.port.run/cdk/verify/{cdk_type}/{cdk_code}"
+        
+        # 发送GET请求
+        response = requests.get(url, timeout=10)
+        
+        if response.status_code == 200:
+            result = response.json()
+            # 根据接口返回判断:err=0 表示验证通过
+            if result.get('err') == 0:
+                return True, result.get('message', '验证通过')
+            else:
+                return False, result.get('message', '验证失败')
+        else:
+            return False, f"服务器响应异常 (HTTP {response.status_code})"
+            
+    except requests.exceptions.Timeout:
+        return False, "网络请求超时,请检查网络连接"
+    except requests.exceptions.ConnectionError:
+        return False, "无法连接到验证服务器,请检查网络"
+    except requests.exceptions.RequestException as e:
+        return False, f"网络请求失败: {str(e)}"
+    except json.JSONDecodeError:
+        return False, "服务器返回数据格式错误"
+    except Exception as e:
+        return False, f"验证过程发生未知错误: {str(e)}"
+
+def save_cdk_to_file(cdk_code):
+    """保存CDK到本地文件"""
+    try:
+        config = {
+            "cdk": cdk_code,
+            "verified_time": time.time(),
+            "verified_date": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
+        }
+        with open("cdk_config.json", "w", encoding='utf-8') as f:
+            json.dump(config, f, indent=2, ensure_ascii=False)
+        return True
+    except Exception as e:
+        print(f"保存CDK配置失败: {e}")
+        return False
+
+def load_cdk_from_file():
+    """从本地文件加载CDK"""
+    try:
+        if os.path.exists("cdk_config.json"):
+            with open("cdk_config.json", "r", encoding='utf-8') as f:
+                config = json.load(f)
+                return config.get("cdk", "")
+    except Exception as e:
+        print(f"加载CDK配置失败: {e}")
+    return ""
+
 def main():
     app = QApplication(sys.argv)
     
-    # ========== 许可证验证(加在这里) ==========
+    # ========== CDK验证(替换原有的许可证验证) ==========
     try:
-        from license_qt5 import verify_license
-        if not verify_license():
-            sys.exit(1)
+        # 尝试从文件加载之前保存的CDK
+        saved_cdk = load_cdk_from_file()
+        cdk_valid = False
+        
+        # 如果之前有保存的CDK,先尝试自动验证
+        if saved_cdk:
+            is_valid, msg = verify_cdk(saved_cdk)
+            if is_valid:
+                print(f"自动验证通过: {msg}")
+                cdk_valid = True
+            else:
+                print(f"自动验证失败: {msg}")
+        
+        # 如果需要手动输入验证
+        if not cdk_valid:
+            max_attempts = 3
+            attempts = 0
+            
+            while attempts < max_attempts:
+                # 创建验证对话框
+                dialog = CDKVerificationDialog()
+                dialog.setWindowTitle(f"软件授权验证 (尝试 {attempts + 1}/{max_attempts})")
+                
+                if dialog.exec_() == QDialog.Accepted:
+                    cdk_code = dialog.get_cdk()
+                    
+                    if not cdk_code:
+                        QMessageBox.warning(None, "输入错误", "CDK不能为空,请重新输入")
+                        attempts += 1
+                        continue
+                    
+                    # 执行验证
+                    is_valid, msg = verify_cdk(cdk_code)
+                    
+                    if is_valid:
+                        # 验证通过,保存CDK
+                        save_cdk_to_file(cdk_code)
+                        QMessageBox.information(None, "授权成功", f"CDK验证通过!\n{msg}")
+                        cdk_valid = True
+                        break
+                    else:
+                        # 验证失败
+                        attempts += 1
+                        if attempts >= max_attempts:
+                            QMessageBox.critical(None, "授权失败", 
+                                f"CDK验证失败:{msg}\n\n已达到最大尝试次数 ({max_attempts}),程序将退出。")
+                        else:
+                            QMessageBox.warning(None, "授权失败", 
+                                f"CDK验证失败:{msg}\n\n剩余尝试次数: {max_attempts - attempts}")
+                else:
+                    # 用户取消了对话框
+                    QMessageBox.information(None, "操作取消", "您取消了授权验证,程序将退出。")
+                    sys.exit(1)
+            
+            if not cdk_valid:
+                sys.exit(1)
+                
     except Exception as e:
-        QMessageBox.critical(None, "错误", f"许可证验证出错: {e}")
+        QMessageBox.critical(None, "错误", f"CDK验证过程出错: {e}")
         sys.exit(1)
     # ============================================
     

+ 69 - 9
控制vm虚拟机.py

@@ -1,11 +1,12 @@
 import tkinter as tk
-from tkinter import ttk, messagebox
+from tkinter import ttk, messagebox, simpledialog
 import time
 import threading
 import random
 import win32gui
 import win32con
 import win32api
+import requests
 import ctypes
 from ctypes import wintypes
 import platform
@@ -1275,15 +1276,74 @@ if __name__ == "__main__":
     root = tk.Tk()
     root.withdraw()
 
-    # 许可证验证(直接在 root 上显示)
-    try:
-        from license_manager import verify_license
-        if not verify_license(root):
+    # --- 新增:CDK 验证逻辑 ---
+    def verify_cdk(cdk_code):
+        """调用远程接口验证CDK"""
+        try:
+            # 接口URL,注意替换为实际地址,这里的"学习通"是类型参数
+            url = f"https://user.port.run/cdk/verify/控制虚拟机/{cdk_code}"
+            response = requests.get(url, timeout=10)  # 设置超时
+            if response.status_code == 200:
+                result = response.json()
+                # 根据接口返回判断:err=0 表示验证通过
+                if result.get('err') == 0:
+                    return True, result.get('message', '验证通过')
+                else:
+                    return False, result.get('message', '验证失败')
+            else:
+                return False, f"服务器响应异常 (HTTP {response.status_code})"
+        except requests.exceptions.RequestException as e:
+            return False, f"网络请求失败: {str(e)}"
+        except json.JSONDecodeError:
+            return False, "服务器返回数据格式错误"
+        except Exception as e:
+            return False, f"验证过程发生未知错误: {str(e)}"
+
+    # 循环直到输入有效CDK或用户取消
+    while True:
+        # 弹出输入对话框让用户输入CDK
+        cdk_input = simpledialog.askstring(
+            "软件授权",
+            "请输入您的CDK授权码:",
+            parent=root,
+            show='*'  # 可选:用星号隐藏输入内容
+        )
+        
+        # 用户点击了取消或关闭对话框
+        if cdk_input is None:
+            messagebox.showwarning("授权取消", "您取消了授权验证,程序将退出。")
             sys.exit(1)
-    except Exception as e:
-        from tkinter import messagebox
-        messagebox.showerror("错误", f"许可证验证出错: {e}")
-        sys.exit(1)
+        
+        # 用户输入了空字符串
+        if not cdk_input.strip():
+            messagebox.showwarning("输入错误", "CDK不能为空,请重新输入。")
+            continue
+        
+        # 执行验证
+        is_valid, msg = verify_cdk(cdk_input.strip())
+        
+        if is_valid:
+            # 验证通过,保存CDK(可选)
+            try:
+                with open("cdk_config.json", "w") as f:
+                    json.dump({"cdk": cdk_input.strip(), "verified_time": time.time()}, f)
+            except Exception as e:
+                print(f"保存CDK配置失败: {e}")
+            
+            messagebox.showinfo("授权成功", f"CDK验证通过!\n{msg}")
+            break  # 跳出循环,继续运行程序
+        else:
+            # 验证失败,提示并让用户重试或退出
+            retry = messagebox.askretrycancel(
+                "授权失败",
+                f"CDK验证失败:{msg}\n\n是否重新输入CDK?"
+            )
+            if not retry:
+                # 用户选择取消,退出程序
+                sys.exit(1)
+            # 否则继续循环,重新输入
+
+    # --- 原有验证代码替换结束 ---
 
     # 验证通过,恢复主窗口
     root.deiconify()

+ 91 - 81
条码处理/app.py → 条码处理/SHEIN.py

@@ -1,7 +1,7 @@
 #!/usr/bin/env python3
 # -*- coding: utf-8 -*-
 """
-PDF 条码合成工具(带缩放与偏移配置
+PDF 条码合成工具(单文件输入,每页独立输出
 """
 
 import tkinter as tk
@@ -29,9 +29,9 @@ class PDFMergeApp:
         self.output_var = tk.StringVar()
 
         # 缩放与偏移变量
-        self.scale_var = tk.StringVar(value="100")   # 相对于模板宽度的百分比
-        self.offset_x_var = tk.StringVar(value="0")  # X 偏移(点)
-        self.offset_y_var = tk.StringVar(value="10")  # Y 偏移(点)
+        self.scale_var = tk.StringVar(value="100")
+        self.offset_x_var = tk.StringVar(value="0")
+        self.offset_y_var = tk.StringVar(value="10")
 
         self.build_ui()
         self.load_config()
@@ -44,8 +44,8 @@ class PDFMergeApp:
         self.root.rowconfigure(0, weight=1)
         main_frame.columnconfigure(1, weight=1)
 
-        # 输入文件
-        ttk.Label(main_frame, text="输入文件:").grid(row=0, column=0, sticky=tk.W, pady=6)
+        # 输入文件
+        ttk.Label(main_frame, text="输入文件:").grid(row=0, column=0, sticky=tk.W, pady=6)
         ttk.Entry(main_frame, textvariable=self.input_var).grid(row=0, column=1, sticky=(tk.W, tk.E), padx=6)
         ttk.Button(main_frame, text="浏览…", width=10, command=self.select_input).grid(row=0, column=2, padx=4)
 
@@ -64,8 +64,7 @@ class PDFMergeApp:
         param_frame.grid(row=3, column=0, columnspan=3, sticky=(tk.W, tk.E), pady=10)
 
         ttk.Label(param_frame, text="缩放比例(% 模板宽):").grid(row=0, column=0, sticky=tk.W, padx=4)
-        scale_entry = ttk.Entry(param_frame, textvariable=self.scale_var, width=10)
-        scale_entry.grid(row=0, column=1, sticky=tk.W, padx=4)
+        ttk.Entry(param_frame, textvariable=self.scale_var, width=10).grid(row=0, column=1, sticky=tk.W, padx=4)
         ttk.Label(param_frame, text="例:100 = 宽度填满模板").grid(row=0, column=2, sticky=tk.W, padx=4)
 
         ttk.Label(param_frame, text="X 偏移(点):").grid(row=1, column=0, sticky=tk.W, padx=4, pady=6)
@@ -90,15 +89,15 @@ class PDFMergeApp:
         self.log_text.grid(row=6, column=0, columnspan=3, sticky=(tk.W, tk.E, tk.N, tk.S), pady=4)
         main_frame.rowconfigure(6, weight=1)
 
-        # 绑定参数变更自动保存
+        # 参数变更自动保存
         for var in (self.scale_var, self.offset_x_var, self.offset_y_var):
             var.trace_add("write", lambda *args: self.save_config())
 
     # ---------- 路径选择 ----------
     def select_input(self):
-        folder = filedialog.askdirectory()
-        if folder:
-            self.input_var.set(folder)
+        file_path = filedialog.askopenfilename(filetypes=[("PDF 文件", "*.pdf")])
+        if file_path:
+            self.input_var.set(file_path)
             self.save_config()
 
     def select_template(self):
@@ -116,7 +115,7 @@ class PDFMergeApp:
     # ---------- 配置持久化 ----------
     def save_config(self):
         config = {
-            "input_dir": self.input_var.get(),
+            "input_file": self.input_var.get(),
             "template": self.template_var.get(),
             "output_dir": self.output_var.get(),
             "scale_percent": self.scale_var.get(),
@@ -135,7 +134,7 @@ class PDFMergeApp:
         try:
             with open(CONFIG_FILE, "r", encoding="utf-8") as f:
                 config = json.load(f)
-            self.input_var.set(config.get("input_dir", ""))
+            self.input_var.set(config.get("input_file", ""))
             self.template_var.set(config.get("template", ""))
             self.output_var.set(config.get("output_dir", ""))
             self.scale_var.set(config.get("scale_percent", "100"))
@@ -153,7 +152,6 @@ class PDFMergeApp:
     # ---------- 文字提取 ----------
     def extract_text(self, page):
         text = page.get_text().strip()
-        print(f"Extracted text: {text}")  # Debug print
         if len(text) >= 2:
             return text
         try:
@@ -168,12 +166,36 @@ class PDFMergeApp:
         except Exception:
             return ""
 
-    def sanitize_filename(self, text):
+    def get_filename_from_text(self, text):
+        """
+        取第一段 + 第四段文字作为文件名
+        如果第四段不存在,只用第一段
+        如果第一段也不存在,返回空字符串
+        """
         if not text:
             return ""
         lines = [ln.strip() for ln in text.splitlines() if ln.strip()]
-        name = lines[0] if lines else text
-        name = name[:60]
+        if not lines:
+            return ""
+        first = lines[0]
+        fourth = lines[4] if len(lines) >= 5 else ""
+        fourth = fourth.replace("¨", "0")
+        fourth = fourth.replace("©", "1")
+        fourth = fourth.replace("ª", "2")
+        fourth = fourth.replace("«", "3")
+        fourth = fourth.replace("°2", "4")
+        fourth = fourth.replace("°2", "5")
+        fourth = fourth.replace("®", "6")
+        fourth = fourth.replace("°2", "7")
+        fourth = fourth.replace("°", "8")
+        fourth = fourth.replace("°2", "9")
+        name = f"{first}_{fourth}" if fourth else first
+        return self.sanitize_filename(name)
+
+    def sanitize_filename(self, text):
+        if not text:
+            return ""
+        name = text[:80]  # 限制长度
         name = re.sub(r'[\\/:*?"<>|\s]+', "_", name)
         return name.strip("._")
 
@@ -186,7 +208,7 @@ class PDFMergeApp:
         self.start_btn.config(state=tk.DISABLED)
         self.log_text.delete(1.0, tk.END)
 
-        input_dir = self.input_var.get()
+        input_file = self.input_var.get()
         template_file = self.template_var.get()
         output_dir = self.output_var.get()
 
@@ -200,12 +222,12 @@ class PDFMergeApp:
             self.start_btn.config(state=tk.NORMAL)
             return
 
-        if not all([input_dir, template_file, output_dir]):
-            self.log("❌ 错误:请完整填写输入文件、模板文件和输出文件夹")
+        if not all([input_file, template_file, output_dir]):
+            self.log("❌ 错误:请完整填写输入文件、模板文件和输出文件夹")
             self.start_btn.config(state=tk.NORMAL)
             return
-        if not os.path.isdir(input_dir):
-            self.log(f"❌ 错误:输入文件夹不存在:{input_dir}")
+        if not os.path.isfile(input_file):
+            self.log(f"❌ 错误:输入文件不存在:{input_file}")
             self.start_btn.config(state=tk.NORMAL)
             return
         if not os.path.isfile(template_file):
@@ -215,13 +237,7 @@ class PDFMergeApp:
         if not os.path.exists(output_dir):
             os.makedirs(output_dir, exist_ok=True)
 
-        pdf_files = [f for f in os.listdir(input_dir) if f.lower().endswith(".pdf")]
-        if not pdf_files:
-            self.log("⚠ 输入文件夹中没有找到 PDF 文件")
-            self.start_btn.config(state=tk.NORMAL)
-            return
-
-        self.log(f"📁 共找到 {len(pdf_files)} 个 PDF 文件")
+        self.log(f"📄 输入文件:{os.path.basename(input_file)}")
         self.log(f"⚙ 参数:缩放 {scale_percent}% 宽,偏移 ({offset_x}, {offset_y})\n")
 
         try:
@@ -236,72 +252,66 @@ class PDFMergeApp:
             template_w = template_rect.width
             template_h = template_rect.height
 
-            for pdf_name in pdf_files:
-                pdf_path = os.path.join(input_dir, pdf_name)
-                self.log(f"📄 正在处理:{pdf_name}")
-
-                try:
-                    doc = fitz.open(pdf_path)
-                    odd_indices = list(range(0, len(doc), 2))
-
-                    if not odd_indices:
-                        self.log("   ⚠ 该文件没有奇数页,已跳过")
-                        doc.close()
-                        continue
+            doc = fitz.open(input_file)
+            odd_indices = list(range(0, len(doc), 2))
 
-                    safe_name = self.sanitize_filename(self.extract_text(doc[odd_indices[0]]))
-                    if not safe_name:
-                        safe_name = os.path.splitext(pdf_name)[0]
-
-                    new_doc = fitz.open()
-
-                    for idx in odd_indices:
-                        page = doc[idx]
-                        page_num = idx + 1
+            if not odd_indices:
+                self.log("⚠ 该文件没有奇数页,已跳过")
+                doc.close()
+                template_doc.close()
+                self.start_btn.config(state=tk.NORMAL)
+                return
 
-                        new_page = new_doc.new_page(width=template_w, height=template_h)
+            self.log(f"📑 共 {len(doc)} 页,提取奇数页 {len(odd_indices)} 页\n")
 
-                        # 1) 模板背景
-                        new_page.show_pdf_page(template_rect, template_doc, 0, overlay=False)
+            for idx in odd_indices:
+                page = doc[idx]
+                page_num = idx + 1
 
-                        # 2) 计算输入页目标矩形
-                        input_rect = page.rect
-                        # 按模板宽度的百分比计算目标宽度
-                        target_w = template_w * (scale_percent / 100.0)
-                        # 等比例缩放高度
-                        scale = target_w / input_rect.width
-                        target_h = input_rect.height * scale
+                # 从该页提取文字作为文件名
+                page_text = self.extract_text(page)
+                safe_name = self.get_filename_from_text(page_text)
+                if not safe_name:
+                    base = os.path.splitext(os.path.basename(input_file))[0]
+                    safe_name = f"{base}_page{page_num}"
 
-                        target_rect = fitz.Rect(offset_x, offset_y, offset_x + target_w, offset_y + target_h)
+                # 创建单页新 PDF
+                new_doc = fitz.open()
+                new_page = new_doc.new_page(width=template_w, height=template_h)
 
-                        # 3) 叠加输入页(前景)
-                        new_page.show_pdf_page(target_rect, doc, idx, overlay=True)
+                # 1) 模板背景
+                new_page.show_pdf_page(template_rect, template_doc, 0, overlay=False)
 
-                        self.log(f"   ✔ 已合成第 {page_num} 页  →  尺寸 {target_w:.1f}×{target_h:.1f}  位置 ({offset_x}, {offset_y})")
+                # 2) 计算输入页目标矩形
+                input_rect = page.rect
+                target_w = template_w * (scale_percent / 100.0)
+                scale = target_w / input_rect.width
+                target_h = input_rect.height * scale
+                target_rect = fitz.Rect(offset_x, offset_y, offset_x + target_w, offset_y + target_h)
 
-                    # 保存
-                    out_name = f"{safe_name}.pdf"
-                    out_path = os.path.join(output_dir, out_name)
-                    counter = 1
-                    while os.path.exists(out_path):
-                        stem, ext = os.path.splitext(out_name)
-                        out_path = os.path.join(output_dir, f"{stem}_{counter}{ext}")
-                        counter += 1
+                # 3) 叠加输入页(前景)
+                new_page.show_pdf_page(target_rect, doc, idx, overlay=True)
 
-                    new_doc.save(out_path)
-                    new_doc.close()
-                    doc.close()
+                # 保存(自动处理重名)
+                out_name = f"{safe_name}.pdf"
+                out_path = os.path.join(output_dir, out_name)
+                counter = 1
+                while os.path.exists(out_path):
+                    stem, ext = os.path.splitext(out_name)
+                    out_path = os.path.join(output_dir, f"{stem}_{counter}{ext}")
+                    counter += 1
 
-                    self.log(f"   💾 已保存:{os.path.basename(out_path)}\n")
+                new_doc.save(out_path)
+                new_doc.close()
 
-                except Exception as e:
-                    self.log(f"   ❌ 处理失败:{str(e)}\n")
+                self.log(f"✔ 第 {page_num} 页  →  {os.path.basename(out_path)}")
 
+            doc.close()
             template_doc.close()
-            self.log("🎉 全部处理完成!")
+            self.log(f"\n🎉 全部完成!共导出 {len(odd_indices)} 个 PDF")
 
         except Exception as e:
-            self.log(f"❌ 发生错误:{str(e)}")
+            self.log(f"\n❌ 发生错误:{str(e)}")
 
         self.start_btn.config(state=tk.NORMAL)
 

+ 4 - 4
条码处理/pdf_merge_config.json

@@ -1,8 +1,8 @@
 {
-  "input_dir": "C:/Users/mail/Downloads/条码测试",
-  "template": "C:/Users/mail/Downloads/SHEIN通用条码模板 (2).pdf",
-  "output_dir": "C:/Users/mail/Downloads",
+  "input_file": "C:/Users/mail/Downloads/速卖通 条码测试/速卖通 隆利.pdf",
+  "template": "C:/Users/mail/Downloads/速卖通 条码测试/速卖通模板.png",
+  "output_dir": "C:/Users/mail/Downloads/速卖通 条码测试",
   "scale_percent": "100",
   "offset_x": "0",
-  "offset_y": "10"
+  "offset_y": "105"
 }

+ 322 - 0
条码处理/速卖通.py

@@ -0,0 +1,322 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+"""
+PDF 条码合成工具(单文件输入,每页独立输出)
+模板为图片,嵌入到输入文件每一页的指定位置
+"""
+
+import tkinter as tk
+from tkinter import ttk, filedialog, scrolledtext
+import os
+import json
+import re
+import threading
+import fitz  # PyMuPDF
+from PIL import Image
+
+CONFIG_FILE = "pdf_merge_config.json"
+
+
+class PDFMergeApp:
+    def __init__(self, root):
+        self.root = root
+        self.root.title("PDF 条码合成工具")
+        self.root.geometry("780x600")
+        self.root.minsize(700, 500)
+
+        # 路径变量
+        self.input_var = tk.StringVar()
+        self.template_var = tk.StringVar()
+        self.output_var = tk.StringVar()
+
+        # 缩放与偏移变量
+        self.scale_var = tk.StringVar(value="100")
+        self.offset_x_var = tk.StringVar(value="0")
+        self.offset_y_var = tk.StringVar(value="110")
+
+        self.build_ui()
+        self.load_config()
+
+    def build_ui(self):
+        main_frame = ttk.Frame(self.root, padding="12")
+        main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
+
+        self.root.columnconfigure(0, weight=1)
+        self.root.rowconfigure(0, weight=1)
+        main_frame.columnconfigure(1, weight=1)
+
+        # 输入文件
+        ttk.Label(main_frame, text="输入文件:").grid(row=0, column=0, sticky=tk.W, pady=6)
+        ttk.Entry(main_frame, textvariable=self.input_var).grid(row=0, column=1, sticky=(tk.W, tk.E), padx=6)
+        ttk.Button(main_frame, text="浏览…", width=10, command=self.select_input).grid(row=0, column=2, padx=4)
+
+        # 模板文件(图片)
+        ttk.Label(main_frame, text="模板图片:").grid(row=1, column=0, sticky=tk.W, pady=6)
+        ttk.Entry(main_frame, textvariable=self.template_var).grid(row=1, column=1, sticky=(tk.W, tk.E), padx=6)
+        ttk.Button(main_frame, text="浏览…", width=10, command=self.select_template).grid(row=1, column=2, padx=4)
+
+        # 输出文件夹
+        ttk.Label(main_frame, text="输出文件夹:").grid(row=2, column=0, sticky=tk.W, pady=6)
+        ttk.Entry(main_frame, textvariable=self.output_var).grid(row=2, column=1, sticky=(tk.W, tk.E), padx=6)
+        ttk.Button(main_frame, text="浏览…", width=10, command=self.select_output).grid(row=2, column=2, padx=4)
+
+        # 参数配置区域
+        param_frame = ttk.LabelFrame(main_frame, text="合成参数(自动保存)", padding="10")
+        param_frame.grid(row=3, column=0, columnspan=3, sticky=(tk.W, tk.E), pady=10)
+
+        ttk.Label(param_frame, text="缩放比例(% 页面宽):").grid(row=0, column=0, sticky=tk.W, padx=4)
+        ttk.Entry(param_frame, textvariable=self.scale_var, width=10).grid(row=0, column=1, sticky=tk.W, padx=4)
+        ttk.Label(param_frame, text="例:100 = 模板宽度填满页面").grid(row=0, column=2, sticky=tk.W, padx=4)
+
+        ttk.Label(param_frame, text="X 偏移(点):").grid(row=1, column=0, sticky=tk.W, padx=4, pady=6)
+        ttk.Entry(param_frame, textvariable=self.offset_x_var, width=10).grid(row=1, column=1, sticky=tk.W, padx=4, pady=6)
+        ttk.Label(param_frame, text="左上角水平偏移,0 表示贴左边").grid(row=1, column=2, sticky=tk.W, padx=4, pady=6)
+
+        ttk.Label(param_frame, text="Y 偏移(点):").grid(row=2, column=0, sticky=tk.W, padx=4)
+        ttk.Entry(param_frame, textvariable=self.offset_y_var, width=10).grid(row=2, column=1, sticky=tk.W, padx=4)
+        ttk.Label(param_frame, text="左上角垂直偏移,0 表示贴顶部").grid(row=2, column=2, sticky=tk.W, padx=4)
+
+        # 开始按钮
+        btn_frame = ttk.Frame(main_frame)
+        btn_frame.grid(row=4, column=0, columnspan=3, pady=10)
+        self.start_btn = ttk.Button(btn_frame, text="▶ 开始处理", command=self.start_process)
+        self.start_btn.pack()
+
+        # 日志区域
+        ttk.Label(main_frame, text="处理日志:").grid(row=5, column=0, sticky=tk.W, pady=(8, 0))
+        self.log_text = scrolledtext.ScrolledText(
+            main_frame, height=18, wrap=tk.WORD, state=tk.NORMAL, font=("Consolas", 10)
+        )
+        self.log_text.grid(row=6, column=0, columnspan=3, sticky=(tk.W, tk.E, tk.N, tk.S), pady=4)
+        main_frame.rowconfigure(6, weight=1)
+
+        # 参数变更自动保存
+        for var in (self.scale_var, self.offset_x_var, self.offset_y_var):
+            var.trace_add("write", lambda *args: self.save_config())
+
+    # ---------- 路径选择 ----------
+    def select_input(self):
+        file_path = filedialog.askopenfilename(filetypes=[("PDF 文件", "*.pdf")])
+        if file_path:
+            self.input_var.set(file_path)
+            self.save_config()
+
+    def select_template(self):
+        file_path = filedialog.askopenfilename(
+            filetypes=[
+                ("图片文件", "*.png *.jpg *.jpeg *.bmp *.gif *.tiff *.webp"),
+                ("PNG 图片", "*.png"),
+                ("JPEG 图片", "*.jpg *.jpeg"),
+                ("所有文件", "*.*")
+            ]
+        )
+        if file_path:
+            self.template_var.set(file_path)
+            self.save_config()
+
+    def select_output(self):
+        folder = filedialog.askdirectory()
+        if folder:
+            self.output_var.set(folder)
+            self.save_config()
+
+    # ---------- 配置持久化 ----------
+    def save_config(self):
+        config = {
+            "input_file": self.input_var.get(),
+            "template": self.template_var.get(),
+            "output_dir": self.output_var.get(),
+            "scale_percent": self.scale_var.get(),
+            "offset_x": self.offset_x_var.get(),
+            "offset_y": self.offset_y_var.get(),
+        }
+        try:
+            with open(CONFIG_FILE, "w", encoding="utf-8") as f:
+                json.dump(config, f, ensure_ascii=False, indent=2)
+        except Exception:
+            pass
+
+    def load_config(self):
+        if not os.path.exists(CONFIG_FILE):
+            return
+        try:
+            with open(CONFIG_FILE, "r", encoding="utf-8") as f:
+                config = json.load(f)
+            self.input_var.set(config.get("input_file", ""))
+            self.template_var.set(config.get("template", ""))
+            self.output_var.set(config.get("output_dir", ""))
+            self.scale_var.set(config.get("scale_percent", "100"))
+            self.offset_x_var.set(config.get("offset_x", "0"))
+            self.offset_y_var.set(config.get("offset_y", "0"))
+        except Exception:
+            pass
+
+    # ---------- 日志 ----------
+    def log(self, msg):
+        self.log_text.insert(tk.END, msg + "\n")
+        self.log_text.see(tk.END)
+        self.root.update_idletasks()
+
+    # ---------- 文字提取 ----------
+    def extract_text(self, page):
+        text = page.get_text().strip()
+        if len(text) >= 2:
+            return text
+        try:
+            import pytesseract
+            pix = page.get_pixmap(dpi=300)
+            mode = "RGBA" if pix.alpha else "RGB"
+            img = Image.frombytes(mode, [pix.width, pix.height], pix.samples)
+            if mode == "RGBA":
+                img = img.convert("RGB")
+            text = pytesseract.image_to_string(img, lang="chi_sim+eng").strip()
+            return text
+        except Exception:
+            return ""
+
+    def get_filename_from_text(self, text):
+        """
+        取第一段 + 第四段文字作为文件名
+        如果第四段不存在,只用第一段
+        如果第一段也不存在,返回空字符串
+        """
+        if not text:
+            return ""
+        lines = [ln.strip() for ln in text.splitlines() if ln.strip()]
+        if not lines:
+            return ""
+        first = lines[0]
+        fourth = lines[4] if len(lines) >= 5 else ""
+        fourth = fourth.replace("¨", "0")
+        fourth = fourth.replace("©", "1")
+        fourth = fourth.replace("ª", "2")
+        fourth = fourth.replace("«", "3")
+        fourth = fourth.replace("°2", "4")
+        fourth = fourth.replace("°2", "5")
+        fourth = fourth.replace("®", "6")
+        fourth = fourth.replace("°2", "7")
+        fourth = fourth.replace("°", "8")
+        fourth = fourth.replace("°2", "9")
+        name = f"{first}_{fourth}" if fourth else first
+        return self.sanitize_filename(name)
+
+    def sanitize_filename(self, text):
+        if not text:
+            return ""
+        name = text[:80]  # 限制长度
+        name = re.sub(r'[\\/:*?"<>|\s]+', "_", name)
+        return name.strip("._")
+
+    # ---------- 核心处理 ----------
+    def start_process(self):
+        t = threading.Thread(target=self.process, daemon=True)
+        t.start()
+
+    def process(self):
+        self.start_btn.config(state=tk.DISABLED)
+        self.log_text.delete(1.0, tk.END)
+
+        input_file = self.input_var.get()
+        template_file = self.template_var.get()
+        output_dir = self.output_var.get()
+
+        # 解析参数
+        try:
+            scale_percent = float(self.scale_var.get().strip() or "100")
+            offset_x = float(self.offset_x_var.get().strip() or "0")
+            offset_y = float(self.offset_y_var.get().strip() or "0")
+        except ValueError:
+            self.log("❌ 错误:缩放比例、X 偏移、Y 偏移必须是有效数字")
+            self.start_btn.config(state=tk.NORMAL)
+            return
+
+        if not all([input_file, template_file, output_dir]):
+            self.log("❌ 错误:请完整填写输入文件、模板图片和输出文件夹")
+            self.start_btn.config(state=tk.NORMAL)
+            return
+        if not os.path.isfile(input_file):
+            self.log(f"❌ 错误:输入文件不存在:{input_file}")
+            self.start_btn.config(state=tk.NORMAL)
+            return
+        if not os.path.isfile(template_file):
+            self.log(f"❌ 错误:模板图片不存在:{template_file}")
+            self.start_btn.config(state=tk.NORMAL)
+            return
+        if not os.path.exists(output_dir):
+            os.makedirs(output_dir, exist_ok=True)
+
+        self.log(f"📄 输入文件:{os.path.basename(input_file)}")
+        self.log(f"🖼 模板图片:{os.path.basename(template_file)}")
+        self.log(f"⚙ 参数:缩放 {scale_percent}% 宽,偏移 ({offset_x}, {offset_y})\n")
+
+        try:
+            # 获取模板图片尺寸
+            with Image.open(template_file) as template_img:
+                template_w, template_h = template_img.size
+
+            doc = fitz.open(input_file)
+            total_pages = len(doc)
+
+            if total_pages == 0:
+                self.log("⚠ 该文件没有页面,已跳过")
+                doc.close()
+                self.start_btn.config(state=tk.NORMAL)
+                return
+
+            self.log(f"📑 共 {total_pages} 页,全部处理\n")
+
+            for idx in range(total_pages):
+                page = doc[idx]
+                page_num = idx + 1
+
+                # 从该页提取文字作为文件名
+                page_text = self.extract_text(page)
+                safe_name = self.get_filename_from_text(page_text)
+                if not safe_name:
+                    base = os.path.splitext(os.path.basename(input_file))[0]
+                    safe_name = f"{base}_page{page_num}"
+
+                # 创建单页新 PDF
+                new_doc = fitz.open()
+                page_rect = page.rect
+                new_page = new_doc.new_page(width=page_rect.width, height=page_rect.height)
+
+                # 1) 输入页作为背景
+                new_page.show_pdf_page(page_rect, doc, idx, overlay=False)
+
+                # 2) 计算模板目标矩形(缩放模板)
+                target_w = page_rect.width * (scale_percent / 100.0)
+                scale = target_w / template_w
+                target_h = template_h * scale
+                target_rect = fitz.Rect(offset_x, offset_y, offset_x + target_w, offset_y + target_h)
+
+                # 3) 叠加模板图片(前景)
+                new_page.insert_image(target_rect, filename=template_file)
+
+                # 保存(自动处理重名)
+                out_name = f"{safe_name}.pdf"
+                out_path = os.path.join(output_dir, out_name)
+                counter = 1
+                while os.path.exists(out_path):
+                    stem, ext = os.path.splitext(out_name)
+                    out_path = os.path.join(output_dir, f"{stem}_{counter}{ext}")
+                    counter += 1
+
+                new_doc.save(out_path)
+                new_doc.close()
+
+                self.log(f"✔ 第 {page_num} 页  →  {os.path.basename(out_path)}")
+
+            doc.close()
+            self.log(f"\n🎉 全部完成!共导出 {total_pages} 个 PDF")
+
+        except Exception as e:
+            self.log(f"\n❌ 发生错误:{str(e)}")
+
+        self.start_btn.config(state=tk.NORMAL)
+
+
+if __name__ == "__main__":
+    root = tk.Tk()
+    app = PDFMergeApp(root)
+    root.mainloop()