PUGE 1 lună în urmă
părinte
comite
5f50d203d1
3 a modificat fișierele cu 418 adăugiri și 302 ștergeri
  1. 16 31
      test.py
  2. 161 0
      test2.py
  3. 241 271
      飞书提交数据.py

+ 16 - 31
test.py

@@ -5,8 +5,6 @@ import hashlib
 import hmac
 from urllib.parse import quote
 
-
-
 # ==================== 配置区域 ====================
 CLIENT_ID = "amzn1.application-oa2-client.9f775d35077a4c7598116e5a89b60222"
 CLIENT_SECRET = "amzn1.oa2-cs.v1.6909777baa82b796555a431cd23709342e8e01434abcad1f8b59cadf048b51c3"
@@ -19,6 +17,7 @@ AWS_REGION = "us-east-1"
 SELLER_ID = "A1H50Z0WYYLEGQ"
 SKU = "bar grey 03"
 MARKETPLACE_ID = "ATVPDKIKX0DER"
+NEW_IMAGE_URL = "https://cunchu.site/temp/f1ae4d03-e59e-45c5-9a3b-15dc3775396a.png"
 
 # ==================== 工具函数 ====================
 def sha256_hash(data): 
@@ -44,14 +43,10 @@ def get_access_token():
     resp.raise_for_status()
     return resp.json()["access_token"]
 
-# ==================== 带 AWS 签名的请求 ====================
 def spapi_request(method, path, query, body=None, access_token=None):
     service = "execute-api"
     host = "sellingpartnerapi-na.amazon.com"
-    
-    # path 已经包含编码后的 SKU,直接用
     uri = path
-    
     payload = json.dumps(body, separators=(",", ":")) if body else ""
     
     t = datetime.datetime.now(datetime.timezone.utc)
@@ -98,22 +93,13 @@ def spapi_request(method, path, query, body=None, access_token=None):
     url = f"https://{host}{uri}?{query}"
     return requests.request(method, url, headers=headers, data=payload or None)
 
-# ==================== 查询商品 ====================
-def get_listings_item(access_token):
-    # 对 SKU 进行 URL 编码:空格 → %20
-    encoded_sku = quote(SKU, safe="")
-    path = f"/listings/2021-08-01/items/{SELLER_ID}/{encoded_sku}"
-    query = f"marketplaceIds={MARKETPLACE_ID}&includedData=summaries,attributes,issues,offers"
-    return spapi_request("GET", path, query, access_token=access_token)
-
-# ==================== 修改主图 ====================
 def patch_main_image(access_token, image_url):
     encoded_sku = quote(SKU, safe="")
     path = f"/listings/2021-08-01/items/{SELLER_ID}/{encoded_sku}"
     query = f"marketplaceIds={MARKETPLACE_ID}"
     
     body = {
-        "productType": "PRODUCT",
+        "productType": "TABLE",  # ← 查询返回的是 TABLE,必须填这个
         "patches": [
             {
                 "op": "replace",
@@ -135,9 +121,12 @@ if __name__ == "__main__":
     token = get_access_token()
     print("✅ 令牌获取成功")
     
-    print(f"\n📤 查询商品信息(SKU: {SKU})...")
-    resp = get_listings_item(token)
-    print(f"状态码: {resp.status_code}")
+    print(f"\n🖼️ 正在修改主图...")
+    print(f"   SKU: {SKU}")
+    print(f"   新图片: {NEW_IMAGE_URL}")
+    
+    resp = patch_main_image(token, NEW_IMAGE_URL)
+    print(f"\n📤 状态码: {resp.status_code}")
     
     try:
         data = resp.json()
@@ -146,15 +135,11 @@ if __name__ == "__main__":
         print(resp.text)
     
     if resp.status_code == 200:
-        print("\n📋 关键信息:")
-        print(f"  productType: {data.get('productType', 'N/A')}")
-        summaries = data.get('summaries', [])
-        if summaries:
-            print(f"  标题: {summaries[0].get('itemName', 'N/A')}")
-        
-        attrs = data.get('attributes', {})
-        main_img = attrs.get('main_product_image_locator', [])
-        if main_img:
-            print(f"  当前主图: {main_img[0].get('value', {}).get('link', 'N/A')}")
-        else:
-            print("  当前主图: 未设置")
+        print("\n✅ 主图修改请求已提交成功!")
+        print("   注意:亚马逊处理图片需要几分钟到几小时,前台更新可能有延迟。")
+    elif resp.status_code == 400:
+        print("\n⚠️ 请求参数有误,请检查 productType 或图片 URL 格式。")
+    elif resp.status_code == 403:
+        print("\n⛔ 权限不足,请确认 Product Listing 角色已审批通过。")
+    else:
+        print(f"\n⚠️ 未知状态码: {resp.status_code}")

+ 161 - 0
test2.py

@@ -0,0 +1,161 @@
+import requests
+import json
+import datetime
+import hashlib
+import hmac
+import time
+from urllib.parse import quote
+
+# ==================== 配置区域 ====================
+CLIENT_ID = "amzn1.application-oa2-client.9f775d35077a4c7598116e5a89b60222"
+CLIENT_SECRET = "amzn1.oa2-cs.v1.6909777baa82b796555a431cd23709342e8e01434abcad1f8b59cadf048b51c3"
+REFRESH_TOKEN = "Atzr|IwEBICh_fojh7qAfrePzSjOZhQxTnHRZCYOuWNvSAHGaBs1WfkjR4qjC1Dmv0uK3oyq_L-R9SRKK4cYmPwF1MQXQdqKlpzOAGItWeXnNzh-SyhHHISv0_vCt3D6hluVmIEmF2kA7ro_XF-PslD41g3Eq6bv0Qq4y4pQbkDj-9dudDuz7nku8UL-nc4EWgJCrHCJTAs8TK48Yvrfi3E35uv97tYhs6qb8D9d4dR9C7daaC49eoDqP88qyUHThQV1Kqon8xukx4AT2_wLqWNcabpADzuK-5w7D-vvFFtvzfkbmtfzoPeEdHPulG6scc59BPFTyBlGU7a3d1JSFg2ROHIlrljRm"
+
+AWS_ACCESS_KEY = "AKIA24CLQSVTW2K4S6RP"       # ← 填你的
+AWS_SECRET_KEY = "CwBJqlsYATNC920A+U6iv80WnP6Eso+ECfIalQ1W"             # ← 填你的
+AWS_REGION = "us-east-1"
+
+SELLER_ID = "A1H50Z0WYYLEGQ"
+SKU = "sofa table black"
+MARKETPLACE_ID = "ATVPDKIKX0DER"
+NEW_IMAGE_URL = "https://cunchu.site/temp/f1ae4d03-e59e-45c5-9a3b-15dc3775396a.jpg"
+
+# ==================== 工具函数 ====================
+def sha256_hash(data): 
+    return hashlib.sha256(data.encode("utf-8")).hexdigest()
+
+def hmac_sha256(key, msg): 
+    return hmac.new(key, msg.encode("utf-8"), hashlib.sha256).digest()
+
+def get_signature_key(secret_key, date_stamp, region, service):
+    k_date = hmac_sha256(("AWS4" + secret_key).encode("utf-8"), date_stamp)
+    k_region = hmac_sha256(k_date, region)
+    k_service = hmac_sha256(k_region, service)
+    return hmac_sha256(k_service, "aws4_request")
+
+def get_access_token():
+    url = "https://api.amazon.com/auth/o2/token"
+    resp = requests.post(url, data={
+        "grant_type": "refresh_token",
+        "refresh_token": REFRESH_TOKEN,
+        "client_id": CLIENT_ID,
+        "client_secret": CLIENT_SECRET,
+    })
+    resp.raise_for_status()
+    return resp.json()["access_token"]
+
+def spapi_request(method, path, query, body=None, access_token=None):
+    service = "execute-api"
+    host = "sellingpartnerapi-na.amazon.com"
+    payload = json.dumps(body, separators=(",", ":")) if body else ""
+    
+    t = datetime.datetime.now(datetime.timezone.utc)
+    amz_date = t.strftime("%Y%m%dT%H%M%SZ")
+    date_stamp = t.strftime("%Y%m%d")
+    
+    canonical_headers = (
+        f"host:{host}\n"
+        f"x-amz-access-token:{access_token}\n"
+        f"x-amz-date:{amz_date}\n"
+    )
+    signed_headers = "host;x-amz-access-token;x-amz-date"
+    payload_hash = sha256_hash(payload)
+    
+    canonical_request = (
+        f"{method}\n{path}\n{query}\n"
+        f"{canonical_headers}\n{signed_headers}\n{payload_hash}"
+    )
+    
+    algorithm = "AWS4-HMAC-SHA256"
+    credential_scope = f"{date_stamp}/{AWS_REGION}/{service}/aws4_request"
+    string_to_sign = (
+        f"{algorithm}\n{amz_date}\n{credential_scope}\n"
+        f"{sha256_hash(canonical_request)}"
+    )
+    
+    signing_key = get_signature_key(AWS_SECRET_KEY, date_stamp, AWS_REGION, service)
+    signature = hmac.new(signing_key, string_to_sign.encode("utf-8"), hashlib.sha256).hexdigest()
+    
+    auth_header = (
+        f"{algorithm} Credential={AWS_ACCESS_KEY}/{credential_scope}, "
+        f"SignedHeaders={signed_headers}, Signature={signature}"
+    )
+    
+    headers = {
+        "Host": host,
+        "x-amz-access-token": access_token,
+        "x-amz-date": amz_date,
+        "Authorization": auth_header,
+    }
+    if body:
+        headers["Content-Type"] = "application/json"
+    
+    url = f"https://{host}{path}?{query}"
+    return requests.request(method, url, headers=headers, data=payload or None)
+
+# ==================== 通用图片修改函数 ====================
+def patch_image(access_token, image_type, image_url):
+    """
+    image_type 可选值:
+      "main"              → 主图
+      "other_1" ~ "other_8" → 副图1~8
+      "swatch"            → 变体图
+    """
+    encoded_sku = quote(SKU, safe="")
+    path = f"/listings/2021-08-01/items/{SELLER_ID}/{encoded_sku}"
+    query = f"marketplaceIds={MARKETPLACE_ID}"
+    
+    # 根据类型生成 path
+    if image_type == "main":
+        attr_path = "/attributes/main_product_image_locator"
+    elif image_type.startswith("other_") and 1 <= int(image_type.split("_")[1]) <= 8:
+        n = image_type.split("_")[1]
+        attr_path = f"/attributes/other_product_image_locator_{n}"
+    elif image_type == "swatch":
+        attr_path = "/attributes/swatch_product_image_locator"
+    else:
+        raise ValueError(f"不支持的 image_type: {image_type}")
+    
+    body = {
+        "productType": "TABLE",
+        "patches": [
+            {
+                "op": "replace",
+                "path": attr_path,
+                "value": [
+                    {
+                        "marketplace_id": MARKETPLACE_ID,
+                        "media_location": image_url
+                    }
+                ]
+            }
+        ]
+    }
+    return spapi_request("PATCH", path, query, body=body, access_token=access_token)
+
+# ==================== 主程序 ====================
+if __name__ == "__main__":
+    print("🔑 获取 Access Token...")
+    token = get_access_token()
+    print("✅ 令牌获取成功")
+    
+    # 示例:增加/修改副图第3张
+    new_images = {
+        "other_1": "https://cunchu.site/temp/cd3a846e-f3b4-4858-a643-e1edc60cbe95.png"
+    }
+    
+    for img_type, img_url in new_images.items():
+        print(f"\n🖼️ 正在设置 {img_type}: {img_url}")
+        resp = patch_image(token, img_type, img_url)
+        print(f"📤 状态码: {resp.status_code}")
+        try:
+            timestamp = int(time.time())
+            print(timestamp)
+            print(json.dumps(resp.json(), indent=2, ensure_ascii=False))
+        except Exception:
+            print(resp.text)
+        
+        if resp.status_code == 200:
+            print(f"✅ {img_type} 提交成功")
+        else:
+            print(f"⚠️ {img_type} 提交失败")

+ 241 - 271
飞书提交数据.py

@@ -21,14 +21,21 @@ class FeishuSubmitApp:
     def __init__(self, root):
         self.root = root
         self.root.title("飞书表格数据提交工具 - 自动提取图片")
-        self.root.geometry("1100x800")
+        self.root.geometry("1200x900")
         
-        # 状态变量
+        # ========== 状态变量 ==========
         self.is_running = False
         self.stop_flag = False
         self.excel_file = ""
         self.cookie_string = ""
         
+        # 新增:暂停与阈值状态
+        self.is_paused = False
+        self.sent_count = 0          # 本次运行已成功发送计数
+        self.pause_threshold = 0     # 0表示不限制
+        self.schedule_running = False
+        self.schedule_thread = None
+        
         # 图片缓存
         self.image_cache = {}  # {行号: [图片数据列表]}
         self.image_extract_folder = None
@@ -60,6 +67,7 @@ class FeishuSubmitApp:
         
         self.setup_ui()
         self.load_config()
+        self.start_schedule()  # 启动定时调度器
         
     def setup_ui(self):
         """构建界面"""
@@ -95,27 +103,52 @@ class FeishuSubmitApp:
         ttk.Button(cookie_btn_frame, text="保存配置", command=self.save_config).pack(side=tk.LEFT, padx=(0, 10))
         ttk.Button(cookie_btn_frame, text="加载默认", command=self.load_default_cookie).pack(side=tk.LEFT)
         
-        # ========== 第三行:发送控制 ==========
-        control_frame = ttk.LabelFrame(main_frame, text="⚙️ 发送控制", padding="10")
+        # ========== 第三行:发送控制 + 定时 + 阈值 ==========
+        control_frame = ttk.LabelFrame(main_frame, text="⚙️ 发送控制 / ⏰ 定时调度 / 🚦 阈值限制", padding="10")
         control_frame.pack(fill=tk.X, pady=(0, 10))
         
-        # 间隔配置
+        # --- 3.1 基础间隔配置 ---
         ttk.Label(control_frame, text="最小间隔(秒):").grid(row=0, column=0, sticky=tk.W)
         self.min_interval_var = tk.StringVar(value="20")
-        ttk.Entry(control_frame, textvariable=self.min_interval_var, width=10).grid(row=0, column=1, padx=(5, 15), sticky=tk.W)
+        ttk.Entry(control_frame, textvariable=self.min_interval_var, width=8).grid(row=0, column=1, padx=(5, 15), sticky=tk.W)
         
         ttk.Label(control_frame, text="最大间隔(秒):").grid(row=0, column=2, sticky=tk.W)
         self.max_interval_var = tk.StringVar(value="50")
-        ttk.Entry(control_frame, textvariable=self.max_interval_var, width=10).grid(row=0, column=3, padx=(5, 15), sticky=tk.W)
+        ttk.Entry(control_frame, textvariable=self.max_interval_var, width=8).grid(row=0, column=3, padx=(5, 15), sticky=tk.W)
         
         ttk.Label(control_frame, text="起始行:").grid(row=0, column=4, sticky=tk.W)
         self.start_row_var = tk.StringVar(value="2")
         ttk.Entry(control_frame, textvariable=self.start_row_var, width=8).grid(row=0, column=5, padx=(5, 15), sticky=tk.W)
         ttk.Label(control_frame, text="(表头在第1行)").grid(row=0, column=6, sticky=tk.W)
         
-        # 操作按钮
+        # --- 3.2 定时自动启停 ---
+        ttk.Separator(control_frame, orient=tk.HORIZONTAL).grid(row=1, column=0, columnspan=7, sticky=tk.EW, pady=(12, 8))
+        
+        ttk.Label(control_frame, text="⏰ 自动启动时间:").grid(row=2, column=0, sticky=tk.W)
+        self.start_time_var = tk.StringVar(value="")
+        ttk.Entry(control_frame, textvariable=self.start_time_var, width=10).grid(row=2, column=1, padx=(5, 5), sticky=tk.W)
+        ttk.Label(control_frame, text="(HH:MM, 留空则不自动启动)").grid(row=2, column=2, sticky=tk.W, columnspan=2)
+        
+        ttk.Label(control_frame, text="自动关闭时间:").grid(row=2, column=4, sticky=tk.W)
+        self.stop_time_var = tk.StringVar(value="")
+        ttk.Entry(control_frame, textvariable=self.stop_time_var, width=10).grid(row=2, column=5, padx=(5, 5), sticky=tk.W)
+        ttk.Label(control_frame, text="(HH:MM, 留空则不自动关闭)").grid(row=2, column=6, sticky=tk.W)
+        
+        # --- 3.3 阈值限制 ---
+        ttk.Separator(control_frame, orient=tk.HORIZONTAL).grid(row=3, column=0, columnspan=7, sticky=tk.EW, pady=(12, 8))
+        
+        ttk.Label(control_frame, text="🚦 录入阈值(条):").grid(row=4, column=0, sticky=tk.W)
+        self.threshold_var = tk.StringVar(value="0")
+        ttk.Entry(control_frame, textvariable=self.threshold_var, width=10).grid(row=4, column=1, padx=(5, 5), sticky=tk.W)
+        ttk.Label(control_frame, text="(0=不限制, 达到后自动暂停)").grid(row=4, column=2, sticky=tk.W, columnspan=2)
+        
+        # 已发送计数显示
+        self.count_display_var = tk.StringVar(value="本次已发送: 0 条")
+        ttk.Label(control_frame, textvariable=self.count_display_var, foreground="green", font=("Microsoft YaHei", 9, "bold")).grid(row=4, column=4, columnspan=3, sticky=tk.W)
+        
+        # --- 3.4 操作按钮 ---
         btn_frame = ttk.Frame(control_frame)
-        btn_frame.grid(row=1, column=0, columnspan=7, pady=(15, 0), sticky=tk.W)
+        btn_frame.grid(row=5, column=0, columnspan=7, pady=(15, 0), sticky=tk.W)
         
         self.start_btn = ttk.Button(btn_frame, text="🚀 开始发送", command=self.start_send)
         self.start_btn.pack(side=tk.LEFT, padx=(0, 10))
@@ -123,12 +156,16 @@ class FeishuSubmitApp:
         self.stop_btn = ttk.Button(btn_frame, text="⏹ 停止", command=self.stop_send, state=tk.DISABLED)
         self.stop_btn.pack(side=tk.LEFT, padx=(0, 10))
         
+        # 新增:暂停/继续按钮
+        self.pause_btn = ttk.Button(btn_frame, text="⏸ 暂停", command=self.toggle_pause, state=tk.DISABLED)
+        self.pause_btn.pack(side=tk.LEFT, padx=(0, 10))
+        
         ttk.Button(btn_frame, text="清空日志", command=self.clear_log).pack(side=tk.LEFT, padx=(0, 10))
         ttk.Button(btn_frame, text="📸 提取图片", command=self.extract_images).pack(side=tk.LEFT)
         
         # 状态显示
-        self.status_var = tk.StringVar(value="就绪")
-        ttk.Label(control_frame, textvariable=self.status_var, foreground="blue").grid(row=2, column=0, columnspan=7, pady=(10, 0), sticky=tk.W)
+        self.status_var = tk.StringVar(value="就绪 | 定时调度: 运行中")
+        ttk.Label(control_frame, textvariable=self.status_var, foreground="blue").grid(row=6, column=0, columnspan=7, pady=(10, 0), sticky=tk.W)
         
         # ========== 日志显示 ==========
         log_frame = ttk.LabelFrame(main_frame, text="📋 发送日志", padding="10")
@@ -143,12 +180,116 @@ class FeishuSubmitApp:
         self.log_text.tag_config("info", foreground="blue")
         self.log_text.tag_config("warning", foreground="orange")
         self.log_text.tag_config("debug", foreground="purple")
+        self.log_text.tag_config("pause", foreground="#FF6600")
+        self.log_text.tag_config("schedule", foreground="#0066CC")
         
         # ========== 底部状态栏 ==========
         status_bar = ttk.Frame(self.root)
         status_bar.pack(fill=tk.X, side=tk.BOTTOM)
         ttk.Label(status_bar, textvariable=self.status_var, relief=tk.SUNKEN, anchor=tk.W).pack(fill=tk.X, padx=5, pady=2)
         
+    # ==================== 定时调度相关 ====================
+    
+    def start_schedule(self):
+        """启动后台定时调度线程"""
+        self.schedule_running = True
+        self.schedule_thread = threading.Thread(target=self._schedule_worker, daemon=True)
+        self.schedule_thread.start()
+        self.log("⏰ 定时调度器已启动,将按设定时间自动启停", "schedule")
+    
+    def stop_schedule(self):
+        """停止定时调度线程"""
+        self.schedule_running = False
+        if self.schedule_thread and self.schedule_thread.is_alive():
+            # 由于线程在sleep,无法直接中断,设置标志即可
+            pass
+    
+    def _schedule_worker(self):
+        """后台线程:每秒检查一次时间,触发自动启停"""
+        while self.schedule_running:
+            try:
+                now = datetime.now()
+                current_time_str = now.strftime("%H:%M")
+                
+                start_time = self.start_time_var.get().strip()
+                stop_time = self.stop_time_var.get().strip()
+                
+                # 自动启动检查
+                if start_time and not self.is_running and not self.is_paused:
+                    if current_time_str == start_time and now.second < 2:  # 在目标时间的0-1秒内触发
+                        self.log(f"⏰ 到达自动启动时间 {start_time},即将开始发送...", "schedule")
+                        # 在主线程中调用启动(通过after确保线程安全)
+                        self.root.after(0, self._safe_auto_start)
+                
+                # 自动关闭检查
+                if stop_time and self.is_running:
+                    if current_time_str == stop_time and now.second < 2:
+                        self.log(f"⏰ 到达自动关闭时间 {stop_time},即将停止发送...", "schedule")
+                        self.root.after(0, self._safe_auto_stop)
+                
+                time.sleep(1)
+            except Exception as e:
+                time.sleep(1)
+    
+    def _safe_auto_start(self):
+        """线程安全的自动启动(在主线程执行)"""
+        if not self.is_running:
+            self.start_send()
+    
+    def _safe_auto_stop(self):
+        """线程安全的自动停止(在主线程执行)"""
+        if self.is_running:
+            self.stop_send()
+    
+    # ==================== 暂停/继续 ====================
+    
+    def toggle_pause(self):
+        """暂停/继续切换"""
+        if not self.is_running:
+            return
+        
+        if self.is_paused:
+            # 继续
+            self.is_paused = False
+            self.pause_btn.config(text="⏸ 暂停")
+            self.log("▶️ 已继续发送", "pause")
+            self.status_var.set("🔄 发送中... (已继续)")
+        else:
+            # 暂停
+            self.is_paused = True
+            self.pause_btn.config(text="▶️ 继续")
+            self.log("⏸ 已暂停发送(当前请求完成后暂停)", "pause")
+            self.status_var.set("⏸ 已暂停")
+    
+    def _check_threshold(self):
+        """检查是否达到录入阈值"""
+        threshold_str = self.threshold_var.get().strip()
+        if not threshold_str or threshold_str == "0":
+            return False
+        
+        try:
+            threshold = int(threshold_str)
+            if threshold <= 0:
+                return False
+            
+            if self.sent_count >= threshold:
+                self.is_paused = True
+                self.pause_btn.config(text="▶️ 继续")
+                self.log(f"🚦 已达到录入阈值 {threshold} 条,自动暂停!", "pause")
+                self.status_var.set(f"⏸ 已达阈值 {threshold} 条,已自动暂停")
+                return True
+        except ValueError:
+            pass
+        return False
+    
+    def reset_count(self):
+        """重置已发送计数"""
+        self.sent_count = 0
+        self.count_display_var.set("本次已发送: 0 条")
+        self.log("🔄 已重置发送计数", "info")
+    
+    # ==================== 原有方法(修改部分标注) ====================
+    
     def select_file(self):
         """选择Excel文件"""
         file_path = filedialog.askopenfilename(
@@ -160,7 +301,6 @@ class FeishuSubmitApp:
             self.excel_file = file_path
             self.log(f"已选择文件: {file_path}", "info")
             
-            # 尝试自动识别工作表
             try:
                 xl = pd.ExcelFile(file_path)
                 sheets = xl.sheet_names
@@ -249,15 +389,12 @@ class FeishuSubmitApp:
     def _get_csrf_token(self):
         """从cookie中提取csrf_token"""
         cookie_text = self.cookie_text.get(1.0, tk.END).strip()
-        # 匹配 _csrf_token=xxx 或 csrf_token=xxx
         match = re.search(r'_csrf_token=([^;]+)', cookie_text)
         if match:
             return match.group(1)
-        # 也尝试匹配 x-csrftoken
         match = re.search(r'x-csrftoken[=:]\s*([^;\s]+)', cookie_text, re.IGNORECASE)
         if match:
             return match.group(1)
-        # 返回默认值
         return "83aae12269bced10680d3ccf817f82262305e7d0-1781579108"
     
     def _get_mime_type(self, filename):
@@ -274,9 +411,7 @@ class FeishuSubmitApp:
         return mime_map.get(ext, 'image/jpeg')
     
     def upload_image(self, image_data, filename="image.jpg"):
-        """
-        完整上传图片到飞书,返回file_token信息
-        """
+        """完整上传图片到飞书,返回file_token信息"""
         try:
             cookie_string = self.cookie_text.get(1.0, tk.END).strip()
             if not cookie_string:
@@ -289,7 +424,6 @@ class FeishuSubmitApp:
             self.log(f"  🔍 使用的CSRF token: {csrf_token[:30] if csrf_token else 'None'}...", "debug")
             self.log(f"  🔍 图片大小: {len(image_data)} bytes", "debug")
             
-            # 构建通用请求头
             base_headers = {
                 "accept": "application/json, text/plain, */*",
                 "accept-language": "zh-CN,zh;q=0.9",
@@ -300,7 +434,7 @@ class FeishuSubmitApp:
                 "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
             }
             
-            # ========== 步骤1: 获取uploadCode ==========
+            # 步骤1: 获取uploadCode
             self.log(f"  📤 [1/4] 获取上传code...", "info")
             code_url = "https://xhd.feishu.cn/space/api/bitable/external/share/uploadCode"
             code_params = {
@@ -310,16 +444,10 @@ class FeishuSubmitApp:
                 "mountPoint": "bitable_tmp_point"
             }
             
-            self.log(f"  📋 请求URL: {code_url}", "debug")
-            self.log(f"  📋 请求参数: {json.dumps(code_params, ensure_ascii=False)}", "debug")
-            
             code_response = requests.get(code_url, params=code_params, headers=base_headers, cookies=cookies, timeout=30)
             
-            self.log(f"  📡 响应状态码: {code_response.status_code}", "debug")
-            
             if code_response.status_code != 200:
                 self.log(f"  ❌ 获取code失败: HTTP {code_response.status_code}", "error")
-                self.log(f"  ❌ 响应内容: {code_response.text}", "error")
                 return None
             
             try:
@@ -331,12 +459,11 @@ class FeishuSubmitApp:
             upload_code = code_data.get('data', {}).get('uploadCode')
             if not upload_code:
                 self.log(f"  ❌ 响应中没有uploadCode字段", "error")
-                self.log(f"  ❌ data内容: {code_data.get('data', {})}", "error")
                 return None
             
-            self.log(f"  ✅ 获取uploadCode成功: {upload_code[:30]}...", "success")
+            self.log(f"  ✅ 获取uploadCode成功", "success")
             
-            # ========== 步骤2: 准备上传 ==========
+            # 步骤2: 准备上传
             self.log(f"  📤 [2/4] 准备上传,获取upload_id...", "info")
             prepare_url = "https://xhd.feishu.cn/space/api/box/upload/prepare/authcode/"
             prepare_headers = base_headers.copy()
@@ -349,15 +476,10 @@ class FeishuSubmitApp:
                 "mount_node_token": self.share_token
             }
             
-            self.log(f"  📋 请求体: {json.dumps(prepare_body, ensure_ascii=False)}", "debug")
-            
             prepare_response = requests.post(prepare_url, headers=prepare_headers, cookies=cookies, json=prepare_body, timeout=30)
             
-            self.log(f"  📡 响应状态码: {prepare_response.status_code}", "debug")
-            
             if prepare_response.status_code != 200:
                 self.log(f"  ❌ 准备上传失败: HTTP {prepare_response.status_code}", "error")
-                self.log(f"  ❌ 响应内容: {prepare_response.text}", "error")
                 return None
             
             try:
@@ -367,55 +489,45 @@ class FeishuSubmitApp:
                 return None
             
             if prepare_data.get('code') != 0:
-                self.log(f"  ❌ 准备上传失败: code={prepare_data.get('code')}, msg={prepare_data.get('msg', '未知错误')}", "error")
+                self.log(f"  ❌ 准备上传失败: code={prepare_data.get('code')}", "error")
                 return None
             
             upload_id = prepare_data.get('data', {}).get('upload_id')
             if not upload_id:
                 self.log(f"  ❌ 响应中没有upload_id字段", "error")
-                self.log(f"  ❌ data内容: {prepare_data.get('data', {})}", "error")
                 return None
             
             self.log(f"  ✅ 获取upload_id成功: {upload_id}", "success")
             
+            # 步骤3: 上传文件内容
             self.log(f"  📤 [3/4] 上传文件内容 ({len(image_data)} bytes)...", "info")
-
-            # 使用 Adler-32 计算 checksum(飞书使用的算法)
-            import zlib
             checksum = zlib.adler32(image_data) & 0xffffffff
-
+            
             upload_url = f"https://internal-api-drive-stream.feishu.cn/space/api/box/stream/upload/merge_block/?upload_id={upload_id}"
             upload_headers = base_headers.copy()
             upload_headers["content-type"] = "application/octet-stream"
             upload_headers["x-command"] = "space.api.box.stream.upload.merge_block"
             upload_headers["x-seq-list"] = "0"
-            upload_headers["x-block-list-checksum"] = str(checksum)  # 注意:是数字字符串,不是十六进制
+            upload_headers["x-block-list-checksum"] = str(checksum)
             upload_headers["x-block-origin-size"] = str(len(image_data))
-
-            self.log(f"  📋 文件大小: {len(image_data)} bytes, checksum(Adler-32): {checksum}", "debug")
             
             upload_response = requests.post(upload_url, headers=upload_headers, cookies=cookies, data=image_data, timeout=60)
             
-            self.log(f"  📡 响应状态码: {upload_response.status_code}", "debug")
-            self.log(f"  📡 响应内容: {upload_response.text[:500]}", "debug")
-            
             if upload_response.status_code != 200:
                 self.log(f"  ❌ 上传文件失败: HTTP {upload_response.status_code}", "error")
-                self.log(f"  ❌ 响应内容: {upload_response.text}", "error")
                 return None
             
-            # 检查上传是否真正成功
             try:
                 upload_result = upload_response.json()
                 if upload_result.get('code') != 0:
-                    self.log(f"  ❌ 上传文件失败: code={upload_result.get('code')}, msg={upload_result.get('message', '未知错误')}", "error")
+                    self.log(f"  ❌ 上传文件失败: code={upload_result.get('code')}", "error")
                     return None
             except:
                 pass
             
             self.log(f"  ✅ 文件上传成功", "success")
             
-            # ========== 步骤4: 完成上传 ==========
+            # 步骤4: 完成上传
             self.log(f"  📤 [4/4] 完成上传,获取file_token...", "info")
             finish_url = "https://xhd.feishu.cn/space/api/box/upload/finish/"
             finish_headers = base_headers.copy()
@@ -428,16 +540,10 @@ class FeishuSubmitApp:
                 "push_open_history_record": 0
             }
             
-            self.log(f"  📋 请求体: {json.dumps(finish_body, ensure_ascii=False)}", "debug")
-            
             finish_response = requests.post(finish_url, headers=finish_headers, cookies=cookies, json=finish_body, timeout=30)
             
-            self.log(f"  📡 响应状态码: {finish_response.status_code}", "debug")
-            self.log(f"  📡 响应内容: {finish_response.text[:500]}", "debug")
-            
             if finish_response.status_code != 200:
                 self.log(f"  ❌ 完成上传失败: HTTP {finish_response.status_code}", "error")
-                self.log(f"  ❌ 响应内容: {finish_response.text}", "error")
                 return None
             
             try:
@@ -447,21 +553,16 @@ class FeishuSubmitApp:
                 return None
             
             if finish_data.get('code') != 0:
-                self.log(f"  ❌ 完成上传失败: code={finish_data.get('code')}, msg={finish_data.get('message', '未知错误')}", "error")
-                self.log(f"  ❌ 完整响应: {json.dumps(finish_data, ensure_ascii=False, indent=2)}", "error")
+                self.log(f"  ❌ 完成上传失败: code={finish_data.get('code')}", "error")
                 return None
             
             file_token = finish_data.get('data', {}).get('file_token')
             if not file_token:
                 self.log(f"  ❌ 响应中没有file_token字段", "error")
-                self.log(f"  ❌ data内容: {finish_data.get('data', {})}", "error")
                 return None
             
-            # 获取图片信息
             width, height = 0, 0
             try:
-                from PIL import Image
-                import io
                 pil_img = Image.open(io.BytesIO(image_data))
                 width, height = pil_img.size
             except:
@@ -491,10 +592,7 @@ class FeishuSubmitApp:
             return None
     
     def extract_images_to_cache(self, file_path, sheet_name):
-        """
-        提取Excel中的所有图片到缓存
-        返回: dict {行号: [图片数据列表]}
-        """
+        """提取Excel中的所有图片到缓存"""
         image_cache = {}
         try:
             wb = load_workbook(file_path, data_only=True)
@@ -504,7 +602,6 @@ class FeishuSubmitApp:
                 self.log(f"  📸 开始提取 {len(ws._images)} 张图片到缓存...", "info")
                 for idx, img in enumerate(ws._images):
                     try:
-                        # 获取位置信息
                         if hasattr(img, 'anchor') and hasattr(img.anchor, '_from'):
                             anchor_from = img.anchor._from
                             row = anchor_from.row + 1
@@ -513,11 +610,7 @@ class FeishuSubmitApp:
                             row = idx + 1
                             col = 0
                         
-                        # 获取图片数据
                         image_data = None
-                        img_format = 'jpg'
-                        filename = f"image_{row}_{idx}.jpg"
-                        
                         if hasattr(img, '_data') and callable(img._data):
                             image_data = img._data()
                         elif hasattr(img, '_data') and not callable(img._data):
@@ -531,10 +624,9 @@ class FeishuSubmitApp:
                         if image_data is None:
                             continue
                         
-                        # 检测图片格式
+                        img_format = 'jpg'
+                        filename = f"image_{row}_{idx}.jpg"
                         try:
-                            from PIL import Image
-                            import io
                             pil_img = Image.open(io.BytesIO(image_data))
                             img_format = pil_img.format if pil_img.format else 'jpg'
                             filename = f"image_{row}_{idx}.{img_format.lower()}"
@@ -566,7 +658,6 @@ class FeishuSubmitApp:
     def build_request_body(self, row_dict, row_index):
         """根据Excel行数据构建请求体"""
         try:
-            # 获取各列数据
             name = str(row_dict.get("姓名", "")) if pd.notna(row_dict.get("姓名")) else ""
             gender = str(row_dict.get("性别", "")) if pd.notna(row_dict.get("性别")) else ""
             phone = str(row_dict.get("手机号码", "")) if pd.notna(row_dict.get("手机号码")) else ""
@@ -580,130 +671,58 @@ class FeishuSubmitApp:
             userId = str(row_dict.get("用户ID", "")) if pd.notna(row_dict.get("用户ID")) else ""
             channel_code = str(row_dict.get("渠道代码", "")) if pd.notna(row_dict.get("渠道代码")) else ""
             userImg = str(row_dict.get("人员头像", "")) if pd.notna(row_dict.get("人员头像")) else ""
-            # 去掉数字的小数点
             phone = phone.split('.')[0]
             self.log(f"  📝 提取数据: 姓名={name}, 手机={phone}, 渠道代码={channel_code}, 意向学校={target_school_text}, 咨询项目={consulting_text}, 产品={product_text}", "debug")
             
-            # 意向校区映射字典
             school_mapping = {
-                "上海": "optqw2jaJ5",
-                "徐汇": "optTD4UQsM",
-                "人广": "opt7hQjSZQ",
-                "长宁": "opt7A8WBtE",
-                "浦东": "optnyoxnTK",
-                "杨浦": "optgArj17r",
-                "闵行": "optCIwo1J1",
-                "松江": "optuZzXsYm",
-                "锦秋个人学校": "optLQYDW3S",
-                "封闭学院": "optitJpd5G",
-                "大学生-市区": "optHlvKsKy",
-                "大学生-曹崇杨": "optdcoQXBt",
-                "大学生-临港": "opt3i4fOKJ",
-                "大学生-松江": "optG7eGFUg",
-                "大学生-奉贤": "optJJqnLgY",
+                "上海": "optqw2jaJ5", "徐汇": "optTD4UQsM", "人广": "opt7hQjSZQ",
+                "长宁": "opt7A8WBtE", "浦东": "optnyoxnTK", "杨浦": "optgArj17r",
+                "闵行": "optCIwo1J1", "松江": "optuZzXsYm", "锦秋个人学校": "optLQYDW3S",
+                "封闭学院": "optitJpd5G", "大学生-市区": "optHlvKsKy", "大学生-曹崇杨": "optdcoQXBt",
+                "大学生-临港": "opt3i4fOKJ", "大学生-松江": "optG7eGFUg", "大学生-奉贤": "optJJqnLgY",
                 "常州": "opt5wuiYjW"
             }
             
-            # 咨询项目映射字典
             consulting_mapping = {
-                "语培": "opt96MRele",
-                "留学": "optQFQsXIe",
-                "考研": "opti3wbRF7",
-                "AF艺术": "optaZC2VCL",
-                "美行音乐": "optzSqwgLp",
-                "锦秋个人学校": "opt9N4AGbJ",
-                "研学考团": "optdYBLknN",
-                "尚思": "optf8lTDcV"
+                "语培": "opt96MRele", "留学": "optQFQsXIe", "考研": "opti3wbRF7",
+                "AF艺术": "optaZC2VCL", "美行音乐": "optzSqwgLp", "锦秋个人学校": "opt9N4AGbJ",
+                "研学考团": "optdYBLknN", "尚思": "optf8lTDcV"
             }
             
-            # 语培产品映射字典(完整版)
             product_mapping = {
-                "ACT": "optlGkVm5n",
-                "AEAS": "optqX3b1rJ",
-                "ALEVEL": "optgMNcV8D",
-                "AP": "optWKWYQgo",
-                "BC": "opt8huX4Nv",
-                "DSE": "optxFBYmUs",
-                "ESL": "optaYj9RZM",
-                "GMAT": "optgwu03Nf",
-                "GRE": "optEOyf5TR",
-                "IB": "optBhWIw7x",
-                "IGCSE": "optIuoUFpo",
-                "KET": "optjcLdrsB",
-                "PET": "optY42DNFo",
-                "PTE": "optOrHJvTX",
-                "SAT": "optTxT8NEu",
-                "SSAT": "optJ2a0tkB",
-                "Toefl Junior": "optEGInE0d",
-                "国际备考": "optgwn1azI",
-                "国际研学及考团": "optAmxtqGN",
-                "国家地理": "opttXZvoj3",
-                "剑桥英语": "optQkkhzD5",
-                "锦秋竞赛": "optUgXXmpF",
-                "考研": "optgQrGHTS",
-                "留学预备": "optWxtOU8b",
-                "美高": "opt36E5u0t",
-                "硕果": "optNKeSnho",
-                "硕果竞赛": "optMzk3TpL",
-                "腾飞计划": "optYTD9h04",
-                "团企培": "optW5UDF6G",
-                "托福": "optKuZ2zCq",
-                "雅思": "optWsal1MB",
-                "用英语讲中国故事": "optinda27s",
-                "在线": "opt2X07PY4",
-                "浙大国际本科": "optZFmVTTC",
-                "语培待定/未知": "optTNitLA5",
-                "大学四级": "optAqYPFmN",
-                "大学六级": "optpdfKgdl",
-                "菁英树": "optBFhBHFX",
-                "其它": "opt698ww1H"
+                "ACT": "optlGkVm5n", "AEAS": "optqX3b1rJ", "ALEVEL": "optgMNcV8D",
+                "AP": "optWKWYQgo", "BC": "opt8huX4Nv", "DSE": "optxFBYmUs",
+                "ESL": "optaYj9RZM", "GMAT": "optgwu03Nf", "GRE": "optEOyf5TR",
+                "IB": "optBhWIw7x", "IGCSE": "optIuoUFpo", "KET": "optjcLdrsB",
+                "PET": "optY42DNFo", "PTE": "optOrHJvTX", "SAT": "optTxT8NEu",
+                "SSAT": "optJ2a0tkB", "Toefl Junior": "optEGInE0d", "国际备考": "optgwn1azI",
+                "国际研学及考团": "optAmxtqGN", "国家地理": "opttXZvoj3", "剑桥英语": "optQkkhzD5",
+                "锦秋竞赛": "optUgXXmpF", "考研": "optgQrGHTS", "留学预备": "optWxtOU8b",
+                "美高": "opt36E5u0t", "硕果": "optNKeSnho", "硕果竞赛": "optMzk3TpL",
+                "腾飞计划": "optYTD9h04", "团企培": "optW5UDF6G", "托福": "optKuZ2zCq",
+                "雅思": "optWsal1MB", "用英语讲中国故事": "optinda27s", "在线": "opt2X07PY4",
+                "浙大国际本科": "optZFmVTTC", "语培待定/未知": "optTNitLA5", "大学四级": "optAqYPFmN",
+                "大学六级": "optpdfKgdl", "菁英树": "optBFhBHFX", "其它": "opt698ww1H"
             }
 
-            
-            # 处理意向学校:根据文本查找对应的ID
             target_school_id = None
             if target_school_text:
-                # 精确匹配
                 if target_school_text in school_mapping:
                     target_school_id = school_mapping[target_school_text]
-                    self.log(f"  🏫 意向学校: {target_school_text} -> {target_school_id}", "debug")
                 else:
-                    # 尝试模糊匹配(包含关系)
-                    matched = False
                     for school_name, school_id in school_mapping.items():
                         if school_name in target_school_text or target_school_text in school_name:
                             target_school_id = school_id
-                            matched = True
-                            self.log(f"  🏫 意向学校模糊匹配: {target_school_text} -> {school_name} ({target_school_id})", "debug")
                             break
-                    
-                    if not matched:
-                        self.log(f"  ⚠️ 未找到匹配的意向学校: {target_school_text}", "warning")
             
-            # 处理咨询项目:根据文本查找对应的ID(支持多选)
             consulting_ids = []
             if consulting_text:
-                # 如果是多个项目,用分隔符分割(如逗号、顿号等)
                 consulting_items = re.split(r'[,,、/ ]+', consulting_text)
                 for item in consulting_items:
                     item = item.strip()
-                    if item:
-                        if item in consulting_mapping:
-                            consulting_ids.append(consulting_mapping[item])
-                            self.log(f"  📚 咨询项目: {item} -> {consulting_mapping[item]}", "debug")
-                        else:
-                            # 尝试模糊匹配
-                            matched = False
-                            for consulting_name, consulting_id in consulting_mapping.items():
-                                if consulting_name in item or item in consulting_name:
-                                    consulting_ids.append(consulting_id)
-                                    matched = True
-                                    self.log(f"  📚 咨询项目模糊匹配: {item} -> {consulting_name} ({consulting_id})", "debug")
-                                    break
-                            if not matched:
-                                self.log(f"  ⚠️ 未找到匹配的咨询项目: {item}", "warning")
+                    if item and item in consulting_mapping:
+                        consulting_ids.append(consulting_mapping[item])
             
-            # 处理产品:根据文本查找对应的ID(支持多选)
             product_ids = []
             if product_text:
                 product_items = re.split(r'[,,、/ ]+', product_text)
@@ -712,38 +731,25 @@ class FeishuSubmitApp:
                     if item:
                         if item in product_mapping:
                             product_ids.append(product_mapping[item])
-                            self.log(f"  📦 产品: {item} -> {product_mapping[item]}", "debug")
                         else:
-                            # 尝试模糊匹配(支持大小写不敏感)
-                            matched = False
                             item_lower = item.lower()
                             for product_name, product_id in product_mapping.items():
-                                # 支持大小写不敏感的匹配
                                 if product_name.lower() == item_lower or product_name in item or item in product_name:
                                     product_ids.append(product_id)
-                                    matched = True
-                                    self.log(f"  📦 产品模糊匹配: {item} -> {product_name} ({product_id})", "debug")
                                     break
-                            if not matched:
-                                self.log(f"  ⚠️ 未找到匹配的产品: {item}", "warning")
             
-            # 构建备注文本
             remark_text = f"姓名:{name},性别:{gender},手机号码:{phone}"
             if remark:
                 remark_text += f",{remark}"
             
-            # 构建data JSON
             data_obj = {
                 "fld43Khnuw": {
                     "type": 1,
                     "value": [{"type": "text", "text": remark_text}]
                 },
                 "fldexDPwcE": {"type": 3, "value": "optmlZt00z"} if institution else {"type": 3},
-                # 意向学校 - 使用映射后的ID
                 "fldWt6JrgK": {"type": 3, "value": target_school_id} if target_school_id else {"type": 3},
-                # 咨询项目 - 使用映射后的ID列表(多选)
                 "fldirDDoLV": {"type": 4, "value": consulting_ids} if consulting_ids else {"type": 4},
-                # 语培产品 - 使用映射后的ID列表(多选)
                 "fldudfcNVO": {"type": 4, "value": product_ids} if product_ids else {"type": 4},
                 "fldS90QCXo": {"type": 1, "value": [{"type": "text", "text": name}]} if name else {"type": 1},
                 "fldsA4YdS6": {"type": 3},
@@ -760,57 +766,39 @@ class FeishuSubmitApp:
                     "type": 11,
                     "value": {
                         "users": [{
-                            "userId":userId,
-                            "name":market_person,
-                            "avatarUrl":userImg,
-                            "enName":market_person,
-                            "notify":False,
+                            "userId": userId,
+                            "name": market_person,
+                            "avatarUrl": userImg,
+                            "enName": market_person,
+                            "notify": False,
                         }]
                     }
                 } if market_person else {"type": 11},
-                # ====== 渠道代码 - 使用映射后的ID ======
                 "flde7thxT7": {"type": 3, "value": channel_code},
                 "fldaxavu52": {"type": 3, "value": "optHsn4zKU"},
-                "fldMpwPaI1": {"type": 17}  # 图片字段,默认空
+                "fldMpwPaI1": {"type": 17}
             }
             
-            # ========== 检查并处理图片 ==========
             if self.image_cache and row_index in self.image_cache:
                 images = self.image_cache[row_index]
-                self.log(f"  🖼️ 检测到 {len(images)} 个嵌入图片在第 {row_index} 行", "info")
-                
-                # 上传第一张图片
                 if images:
                     img_info = images[0]
-                    
-                    # 检查是否已经上传过
                     if row_index in self.uploaded_cache:
                         img_value = self.uploaded_cache[row_index]
-                        self.log(f"  ♻️ 使用已上传的图片: {img_value.get('attachmentToken')}", "debug")
                     else:
-                        # 上传图片
-                        self.log(f"  📤 开始上传图片...", "info")
                         uploaded = self.upload_image(img_info['data'], img_info['filename'])
                         if uploaded:
                             self.uploaded_cache[row_index] = uploaded
                             img_value = uploaded
-                            self.log(f"  ✅ 图片上传成功,token: {uploaded.get('attachmentToken')}", "success")
                         else:
-                            self.log(f"  ⚠️ 图片上传失败,跳过该图片", "warning")
                             img_value = None
                     
-                    # 如果上传成功,添加到data_obj
                     if img_value:
                         data_obj["fldMpwPaI1"] = {
                             "type": 17,
                             "value": [img_value]
                         }
-                        self.log(f"  📎 图片已添加到请求中", "debug")
-            else:
-                # 没有图片,确保图片字段为空
-                data_obj["fldMpwPaI1"] = {"type": 17}
             
-            # 构建完整的请求体
             body = {
                 "shareToken": self.share_token,
                 "data": json.dumps(data_obj, ensure_ascii=False),
@@ -835,27 +823,19 @@ class FeishuSubmitApp:
                 self.log("❌ Cookie为空,请先配置Cookie", "error")
                 return False, "Cookie为空"
             
-            # 构建请求体
             body = self.build_request_body(row_data, row_index)
             if body is None:
                 self.log("❌ 构建请求体失败", "error")
                 return False, "构建请求体失败"
             
-            # 记录请求体大小
             body_str = json.dumps(body, ensure_ascii=False)
             self.log(f"📦 请求体大小: {len(body_str)} 字节", "debug")
             
-            # 准备请求
             headers = self.default_headers.copy()
             cookies = self.parse_cookie(cookie_string)
-            
-            # 更新动态头
             headers["x-csrftoken"] = self._get_csrf_token()
             headers["f-version"] = "docs-6-17-1781628518399"
             
-            self.log(f"🌐 发送请求到: {self.url}", "debug")
-            
-            # 发送请求
             response = requests.post(
                 self.url,
                 headers=headers,
@@ -864,47 +844,33 @@ class FeishuSubmitApp:
                 timeout=30
             )
             
-            self.log(f"📡 响应状态码: {response.status_code}", "debug")
-            
-            # 解析响应
             try:
                 resp_data = response.json()
                 resp_text = json.dumps(resp_data, ensure_ascii=False, indent=2)
-                
-                if isinstance(resp_data, dict):
-                    if resp_data.get('code') != 0 and resp_data.get('code') is not None:
-                        self.log(f"⚠️ 服务器返回错误码: {resp_data.get('code')}", "warning")
-                        self.log(f"  错误信息: {resp_data.get('msg', '未知错误')}", "error")
-                    if 'data' in resp_data:
-                        self.log(f"  📊 响应数据: {json.dumps(resp_data['data'], ensure_ascii=False)[:200]}...", "debug")
             except:
                 resp_text = response.text
-                self.log("⚠️ 响应不是有效的JSON格式", "warning")
             
             if response.status_code == 200:
                 self.log(f"✅ 第{row_index}行 发送成功", "success")
                 return True, resp_text
             else:
                 self.log(f"❌ 第{row_index}行 发送失败 (HTTP {response.status_code})", "error")
-                self.log(f"  响应内容: {resp_text[:500]}...", "error")
                 return False, resp_text
                 
         except requests.exceptions.Timeout:
             self.log(f"⏱️ 第{row_index}行 请求超时", "error")
             return False, "请求超时"
         except requests.exceptions.ConnectionError:
-            self.log(f"🔌 第{row_index}行 连接失败,请检查网络", "error")
+            self.log(f"🔌 第{row_index}行 连接失败", "error")
             return False, "连接失败"
         except Exception as e:
             self.log(f"💥 第{row_index}行 异常: {str(e)}", "error")
-            import traceback
-            self.log(f"详细错误: {traceback.format_exc()}", "debug")
             return False, str(e)
         finally:
             self.log(f"{'='*50}\n", "info")
     
     def send_all(self):
-        """发送所有数据的主循环"""
+        """发送所有数据的主循环(已增加暂停和阈值检查)"""
         try:
             excel_file = self.file_path_var.get()
             sheet_name = self.sheet_var.get()
@@ -917,29 +883,23 @@ class FeishuSubmitApp:
                 self.status_var.set("❌ 请先选择Excel文件")
                 return
             
-            # 清空上次上传缓存
+            # 初始化计数
+            self.sent_count = 0
+            self.count_display_var.set("本次已发送: 0 条")
             self.uploaded_cache = {}
             
-            # ========== 自动提取所有图片到缓存 ==========
             self.log(f"🔍 正在自动提取Excel中的嵌入图片...", "info")
             self.image_cache = self.extract_images_to_cache(excel_file, sheet_name)
             
             if self.image_cache:
                 total_images = sum(len(imgs) for imgs in self.image_cache.values())
                 self.log(f"📊 共提取 {total_images} 张图片,分布在 {len(self.image_cache)} 行", "info")
-                rows_with_images = sorted(self.image_cache.keys())
-                if len(rows_with_images) <= 20:
-                    self.log(f"📋 有图片的行: {rows_with_images}", "debug")
-                else:
-                    self.log(f"📋 有图片的行: {rows_with_images[:20]} ... (共{len(rows_with_images)}行)", "debug")
             else:
                 self.log(f"📷 未检测到任何嵌入图片", "info")
             
-            # ========== 读取Excel数据 ==========
             try:
                 df = pd.read_excel(excel_file, sheet_name=sheet_name)
                 self.log(f"📊 成功读取Excel: {len(df)} 行数据", "info")
-                self.log(f"📋 列名: {', '.join(df.columns.tolist())}", "info")
             except Exception as e:
                 self.log(f"❌ 读取Excel失败: {str(e)}", "error")
                 self.status_var.set(f"❌ 读取失败: {str(e)}")
@@ -947,7 +907,6 @@ class FeishuSubmitApp:
             
             if len(df) == 0:
                 self.log("⚠️ Excel中没有数据", "warning")
-                self.status_var.set("⚠️ 没有数据")
                 return
             
             df = df.iloc[start_row:]
@@ -959,26 +918,42 @@ class FeishuSubmitApp:
             self.status_var.set(f"🔄 发送中... 0/{total}")
             
             for idx, (_, row) in enumerate(df.iterrows()):
+                # ========== 暂停检查(核心新增)==========
+                while self.is_paused and not self.stop_flag:
+                    self.status_var.set(f"⏸ 已暂停 | 成功:{success_count} 失败:{fail_count}")
+                    time.sleep(0.5)
+                
                 if self.stop_flag:
                     self.log("⏹️ 已停止发送", "warning")
                     self.status_var.set("⏹️ 已停止")
                     break
                 
-                # ===== 关键修复:计算Excel实际行号 =====
-                # DataFrame的索引 + 2(因为第1行是表头,DataFrame从0开始)
-                # 再加上start_row(起始行的偏移)
                 excel_row_number = idx + 2 + start_row
                 
-                self.log(f"  📍 DataFrame索引: {idx}, Excel行号: {excel_row_number}", "debug")
-                
-                # 检查该行是否有图片(使用Excel实际行号)
                 if self.image_cache and excel_row_number in self.image_cache:
                     img_count = len(self.image_cache[excel_row_number])
-                    self.log(f"  🖼️ 第{excel_row_number}行有 {img_count} 个嵌入图片,将自动上传并添加", "info")
+                    self.log(f"  🖼️ 第{excel_row_number}行有 {img_count} 个嵌入图片", "info")
                 
                 success, resp_text = self.send_request(row.to_dict(), excel_row_number)
                 if success:
                     success_count += 1
+                    self.sent_count += 1
+                    self.count_display_var.set(f"本次已发送: {self.sent_count} 条")
+                    
+                    # ========== 阈值检查(核心新增)==========
+                    if self._check_threshold():
+                        self.log(f"⏸ 因达到阈值暂停,等待手动继续...", "pause")
+                        # 进入暂停等待循环
+                        while self.is_paused and not self.stop_flag:
+                            self.status_var.set(f"⏸ 已达阈值暂停 | 成功:{success_count} 失败:{fail_count}")
+                            time.sleep(0.5)
+                        
+                        if self.stop_flag:
+                            break
+                        # 继续后重置计数(可选:如果想累计不清零,注释掉下面这行)
+                        # self.sent_count = 0
+                        # self.count_display_var.set(f"本次已发送: {self.sent_count} 条")
+                
                 else:
                     fail_count += 1
                 
@@ -999,6 +974,8 @@ class FeishuSubmitApp:
             self.status_var.set(f"❌ 异常: {str(e)}")
         finally:
             self.is_running = False
+            self.is_paused = False
+            self.pause_btn.config(text="⏸ 暂停", state=tk.DISABLED)
             self.start_btn.config(state=tk.NORMAL)
             self.stop_btn.config(state=tk.DISABLED)
     
@@ -1020,14 +997,27 @@ class FeishuSubmitApp:
             float(self.min_interval_var.get())
             float(self.max_interval_var.get())
             int(self.start_row_var.get())
+            threshold_str = self.threshold_var.get().strip()
+            if threshold_str and threshold_str != "0":
+                int(threshold_str)
         except ValueError:
-            messagebox.showerror("错误", "间隔或行号请输入有效数字")
+            messagebox.showerror("错误", "间隔、行号或阈值请输入有效数字")
             return
         
+        # 验证时间格式
+        for time_var, name in [(self.start_time_var, "启动时间"), (self.stop_time_var, "关闭时间")]:
+            t = time_var.get().strip()
+            if t:
+                if not re.match(r'^([01]\d|2[0-3]):([0-5]\d)$', t):
+                    messagebox.showerror("错误", f"{name} 格式错误,请使用 HH:MM 格式")
+                    return
+        
         self.is_running = True
         self.stop_flag = False
+        self.is_paused = False
         self.start_btn.config(state=tk.DISABLED)
         self.stop_btn.config(state=tk.NORMAL)
+        self.pause_btn.config(text="⏸ 暂停", state=tk.NORMAL)
         self.status_var.set("🔄 正在发送...")
         
         thread = threading.Thread(target=self.send_all, daemon=True)
@@ -1036,10 +1026,11 @@ class FeishuSubmitApp:
     def stop_send(self):
         """停止发送"""
         self.stop_flag = True
+        self.is_paused = False
         self.log("⏹️ 正在停止...", "warning")
         self.stop_btn.config(state=tk.DISABLED)
+        self.pause_btn.config(state=tk.DISABLED)
 
-    # ========== 提取图片功能(手动) ==========
     def extract_images_to_folder(self, file_path, sheet_name, output_folder=None):
         """提取Excel中的所有图片并保存到文件夹"""
         try:
@@ -1065,11 +1056,8 @@ class FeishuSubmitApp:
                 f.write(f"工作表: {sheet_name}\n")
                 f.write(f"提取时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
                 f.write("="*60 + "\n\n")
-                f.write("行号\t列号\t列字母\t文件名\t尺寸\t格式\n")
-                f.write("-"*60 + "\n")
                 
                 if hasattr(ws, '_images') and ws._images:
-                    self.log(f"  📸 开始提取 {len(ws._images)} 张图片...", "debug")
                     for idx, img in enumerate(ws._images):
                         try:
                             if hasattr(img, 'anchor') and hasattr(img.anchor, '_from'):
@@ -1097,12 +1085,9 @@ class FeishuSubmitApp:
                                 error_count += 1
                                 continue
                             
-                            # 检测格式并保存
                             img_format = 'jpg'
                             width, height = 0, 0
                             try:
-                                from PIL import Image
-                                import io
                                 pil_img = Image.open(io.BytesIO(image_data))
                                 img_format = pil_img.format if pil_img.format else 'jpg'
                                 width, height = pil_img.size
@@ -1115,27 +1100,12 @@ class FeishuSubmitApp:
                                 with open(filepath, 'wb') as f_out:
                                     f_out.write(image_data)
                             
-                            f.write(f"{row}\t{col}\t{col_letter}\t{filename}\t{width}x{height}\t{img_format}\n")
                             image_count += 1
-                            
-                            if image_count % 50 == 0:
-                                self.log(f"  📸 已提取 {image_count} 张图片...", "debug")
                         except Exception as e:
                             error_count += 1
-                            self.log(f"  ⚠️ 提取图片失败: {str(e)}", "warning")
             
             wb.close()
-            
-            with open(mapping_file, 'a', encoding='utf-8') as f:
-                f.write("\n" + "="*60 + "\n")
-                f.write(f"提取完成: 成功 {image_count} 张, 失败 {error_count} 张\n")
-                f.write("="*60 + "\n")
-            
             self.log(f"✅ 成功提取 {image_count} 张图片到: {output_folder}", "success")
-            if error_count > 0:
-                self.log(f"⚠️ 有 {error_count} 张图片提取失败", "warning")
-            self.log(f"📋 映射文件: {mapping_file}", "info")
-            
             return output_folder
             
         except Exception as e: