54H*@@M$PIB8#bLb%lnyMZh(vf=`1jPpl@1o)|v&zZg9Ys=>z8G
z=
z)|=k!BknINw|F(SIC$Z0v(+q*Etx$A4xr?g2S6j8tFS>u-ZYcW9a+4tKaKDn>-STzm@xTbSw&B?k%_za<|?@svoNWJ0)K1xYd3Rrhbp9
z_q6!Bqq-itBVvVot73Nq-IANMYIdMh`Q;TYtNKD-wFYyu8h{xmsZ=X9l>{LBw(H{b
zs#>D18_fhs69GBJO6jHBL>8yV+1e4Zvj$nfZ~y(7tiOgfFB(PulOBKej@&{
z{-cak*Ek&xE<4hXr__+3?S?^Zt}ZNg`D*>=3;-s
z?>{4a(@N+VqhfdGL)!@)va74RD@@>DClvJCk^c^a9)zDGNZlw>(h=oBCh$}C68c~?
zkz`BpTOeT3X4nlg^dW{f!p@4dRYAdTYVh>fyOpo)P`#ZSLK(#>UcBjsJAMKz1#2o1
zVY1mj4xcHEj>|7UQQCK^IC+}gg`4CNb|Ew(ya$j90+h!0l%76a8aoLs6^8N)kC(*Q
v#ka@V&&2whw~?pBOE>q_jRRxBGTq_GXpr%B8phc#MDs1r5}$bYmiGSxaq4}b
delta 3787
zcmb7Hdu)@}760z{eYRuA@gsI(=l$Rj970|VfhOrnSYd!B{z;_J2nboOX#>F_*Iy`3
zuj?`>TQvxwS3FdRW>a9OtiT#pQAW4^HQKK2Kzp^;q)pqBj&3Vb)v^7tsypX?4w$q|
zS`z4DGJ;S*hM5w6A0CG@zc0zg>ghD6s$~PO5l6
zQY?206Wohi)Rt0-LUZEI9%W=4MC7KFc)sDJ0iM4XPI&%=%I3=}7HL*+QbRFD9=C&1
zA8;0N-!_-z^(EbK+t2+IBsxxuxj(7U5?Y#4QB%ACrSJk;h8m582T8mp75iypHGP%TU2kcX$=jn4i0{a$NTHNdJ?nO2*6&C#
zOToNJ?OsG8IkT60lt{0x7!ZrtcZm`Y5@l=!olkRyeDNZAdjT&>`csfBi0Hyu$wmPW
zLX2ESVKP8#Fls%@;~4AUGr>s0VG@l>qr%-hk9va2c7=^g<~(G^bb$X?xYtC;rwY_3
z)J()8SrsAf0;Sft%qlN7gUjuPbr*QeJ~ph}{s^e-QlPpnbM-N0)1`O`*9s@R
zRIU{%kf&O@IHlr_^rwz%P;yIVqX8k*M$V04q=MYZK>)XNo#&*i=u;*(TI|$U#-^T^
zfUE{H8ept0jZ8otLpjK>nO0{7Co49PyE|iz+yrjM|{5O7N=6i02|J_yIosmRhFYzZ
z9Txv+s3yD%2*ngbKGF12%RPA%sv1kaq86!vo=Hv~OT|G-9w=I(O%A)aYa3=;)A<
z_Q^%m=zk*Iw{(dtn5zyhw8HBCs4L?V0g#Q{z$AZ
z(!V3jp200EWrMrIESW&G*kCxCc7z!lW_^Q^t;6DaXeoI~d>*PU>XfG
zoDC+FA$0`cVFF{~hWN0t)UN8pMRZc_5IwcwRjRJ$sHMbDtg1?a#6xmODXCRe)k=bD
zKCJ6Cm00Dz3$RXez{>y6`}ag+Q@Q6N>UtgFIKm0Bxv7S%6#JXDSysfIWABK6H7#f$
z+jaIC6rE1pSnKfq$u?UuN4@}i5*}&h1+>9)%Z+_1x|)|JNHP!l0*UAHTxiJU^0trX
zU}rSBp_h=6a}mEh@#6_CfIj2Ju9@j2a5FXz<(nt_ETQ>Bt~fq&ofb;|fZ@c!DSJr}
z@8KDD;q*~yQ0!Kqhq5wLVDVov&^S4VGE94~ULHDIGWf}JkbRR~;?u^O3RGQqDvAO)NwP4)+*50RB
zW#5`rEV^3SNrgDnQk3v=3#~i|V_wc8ZG=LMl2V}-+niCjUz$_(B|T2=piUes&^*^Y
zFnid)awoLd8j$9Nh-0(VsRyKa_YzUF^x?#QBOm|7pacex#?H#~YMBX`mlyM{nEW*a2k
zGUMH7#v6+<)HL;W>tW(c;`{AHIDn7>5Iz0EFUjYA%vb9K@mJ5T~XSU3C$aRKv;vza7vhU1C#5KeR^i(~h94MaBMD
zNESjD^sC~7jxut5>Q2X5m0X>gd~_4BN?)(P(785OdY3W1yK~+$*%O}vooqM4O@!+J
zX>F7ZrENR=V?)FJl)a1lXg_-u;j*~US!VemkX_l+is-d2&bpI
zR=%cMe~$E9!oBJ=Nr}s=YO3GHjdu`!0kArxvvat34*~y$VDF31R|TxUMDm6xUVS4W
ztuuYWO_`cHq-45&g;YI)l=Ew(euMB^ge=E#c`uK@gJsBLrtQH#;GK%Q(P1Cp`a^`d
zLBT4BiI=~zXN$gmefRnhfuGeOFWZ33k0bOV{0u=FMv;;uU3zg6mT7%xFlw+SJb3{D
zMYbxCvl`kAb|H7G23W^hoO{CX!Za4)j>73DMn90fg5-dlt
zo^QQ!Xy(Ah>8VR>BXX#_TG=ijSQWxGfYBhp%*p3xj$E2Kc;WW71Jl=@o&Lf1X2ik2
sym6AfA&j+85FviQwzpjVfndu_cqS|UEJ~R|cvEz*dl3rn_PWOZ0^juGZU6uP
diff --git a/app/amazon/__pycache__/price_match.cpython-39.pyc b/app/amazon/__pycache__/price_match.cpython-39.pyc
index b9ca6b69a7b726c46620ed331a307183eaaef221..e5afa4bcbf5e33049c0a8468bc2f4f83a7930630 100644
GIT binary patch
delta 12954
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 0:
- # self.log("重试前先杀掉浏览器进程...")
- # kill_process("v6")
- # kill_process("v5")
- # time.sleep(2)
-
- # 组装用户信息并创建驱动
- user_info = {
- **self.user_info,
- "company": company_name
- }
- driver = AmzoneApprove(user_info)
- browser = driver.open_shop(shop_name)
-
- if browser and browser != "店铺不存在":
- self.log(f"成功打开店铺 {shop_name}")
- # break
- else:
- self.log(f"打开店铺失败: {browser}", "WARNING")
- driver = None
- continue
-
- # 判断是否需要登录
- need_login = driver.need_login()
- print("【是否需要登录】:",need_login)
- if need_login:
- self.log(f"店铺 {shop_name} 需要登录,正在登录...")
- # 获取店铺凭证
- response = get_shop_info(shop_name)
- print("【获取店铺凭证返回】:",response.text)
- shop_data = response.json()
- if not shop_data:
- mes = f"获取店铺凭证失败,响应数据: {shop_data.get('message', '未知错误')}"
- self.log(mes, "ERROR")
- show_notification(mes, "ERROR")
- continue
-
- password = shop_data["data"]["password"]
-
- login_success = driver.login(password)
- if login_success:
- self.log(f"店铺 {shop_name} 登录成功,正在重新打开店铺...")
- browser = driver.open_shop(shop_name)
- if browser and browser != "店铺不存在":
- self.log(f"成功打开店铺 {shop_name} 登录后")
- break
- else:
- self.log(f"登录后打开店铺失败: {browser}", "WARNING")
- driver = None
- else:
- self.log(f"店铺 {shop_name} 登录失败", "WARNING")
- driver = None
- else:
- break
+ self.process_country(driver, country_code, task_id, shop_name,risk_listing_filter)
except Exception as e:
import traceback
- self.log(f"打开店铺异常: {traceback.format_exc()}", "INFO")
- driver = None
- error_info = str(e)
- time.sleep(10)
-
- # 如果还有重试机会,等待后继续
- if retry < max_retries - 1:
- time.sleep(3)
-
- # 检查是否成功打开
- if not driver or not browser or browser == "店铺不存在":
- error_msg = f"店铺 {shop_name} 打开失败,已重试 {max_retries} 次,跳过该店铺,{error_info}"
- self.log(error_msg, "ERROR")
- # 从执行列表中移除
- if shop_name in runing_shop:
- del runing_shop[shop_name]
- return
-
+ self.log(f"处理国家 {country_code} 失败: {str(e)}", "ERROR")
+ self.log(traceback.format_exc(), "ERROR")
+ if "与页面的连接已断开" in e:
+ iskill = True
+
+ # 更新已处理国家数
+ if task_id in runing_task:
+ runing_task[task_id]["processed_countries"] += 1
+ # 最后回传,标记完成
try:
- # 处理每个国家
- for country_code in country_codes:
- # 检查是否收到暂停请求
- if task_id in runing_task and runing_task[task_id].get("stop_requested", False):
- self.log(f"检测到任务 {task_id} 的暂停请求,停止处理国家", "WARNING")
- break
-
- try:
- self.process_country(driver, country_code, task_id, shop_name,risk_listing_filter)
- except Exception as e:
- import traceback
- self.log(f"处理国家 {country_code} 失败: {str(e)}", "ERROR")
- self.log(traceback.format_exc(), "ERROR")
- # 最后回传,标记完成
- try:
- self.post_result(task_id, shop_name, country_code, "", "", is_done=True)
- except Exception as e:
- self.log(f"回传结果失败: {str(e)}", "ERROR")
- finally:
- # 关闭店铺
- try:
- if driver:
- self.log(f"关闭店铺 {shop_name}")
- driver.close_store()
- time.sleep(2)
- except Exception as e:
- self.log(f"关闭店铺失败: {str(e)}", "WARNING")
-
- # 从正在执行中的店铺列表中移除
- if shop_name in runing_shop:
- del runing_shop[shop_name]
- self.log(f"店铺 {shop_name} 已从执行列表中移除")
+ self.post_result(task_id, shop_name, country_code, "", "", is_done=True)
+ except Exception as e:
+ self.log(f"回传结果失败: {str(e)}", "ERROR")
+
+ # 从正在执行中的店铺列表中移除
+ if shop_name in runing_shop:
+ del runing_shop[shop_name]
+ self.log(f"店铺 {shop_name} 已从执行列表中移除")
def process_country(self, driver: AmzoneApprove, country_code: str, task_id: int, shop_name: str, risk_listing_filter: str):
"""处理单个国家的审批任务
@@ -1176,42 +1185,33 @@ class ApproveTask:
self.log(f"国家 {country_name} 有 {len(sku_ls)} 个需要审批的商品,开始处理...")
# 处理所有需要审批的商品(通过yield获取结果)
- try:
- for asin, status in driver.run_page_action():
- # 检查是否收到暂停请求
- if task_id in runing_task and runing_task[task_id].get("stop_requested", False):
- self.log(f"检测到任务 {task_id} 的暂停请求,停止处理ASIN", "WARNING")
- break
-
- self.log(f"ASIN {asin} 处理结果: {status}")
-
- # 更新任务状态
- if task_id in runing_task:
- runing_task[task_id]["current_asin"] = asin
- runing_task[task_id]["processed_asins"] += 1
-
- if status == "处理完成":
- runing_task[task_id]["success_count"] += 1
- elif status in ["请求批准", "添加缺失的商品详情"]:
- # 请求批准的商品也算作成功(因为不需要处理)
- runing_task[task_id]["success_count"] += 1
- else:
- runing_task[task_id]["failed_count"] += 1
-
- # 回传结果到API
- try:
- self.post_result(task_id, shop_name, country_code, asin, status)
- except Exception as e:
- self.log(f"回传结果失败: {str(e)}", "ERROR")
-
- except Exception as e:
- import traceback
- self.log(f"处理审批商品异常: {str(e)}", "ERROR")
- self.log(traceback.format_exc(), "ERROR")
-
- # 更新已处理国家数
- if task_id in runing_task:
- runing_task[task_id]["processed_countries"] += 1
+ for asin, status in driver.run_page_action():
+ # 检查是否收到暂停请求
+ if task_id in runing_task and runing_task[task_id].get("stop_requested", False):
+ self.log(f"检测到任务 {task_id} 的暂停请求,停止处理ASIN", "WARNING")
+ break
+
+ self.log(f"ASIN {asin} 处理结果: {status}")
+
+ # 更新任务状态
+ if task_id in runing_task:
+ runing_task[task_id]["current_asin"] = asin
+ runing_task[task_id]["processed_asins"] += 1
+
+ if status == "处理完成":
+ runing_task[task_id]["success_count"] += 1
+ elif status in ["请求批准", "添加缺失的商品详情"]:
+ # 请求批准的商品也算作成功(因为不需要处理)
+ runing_task[task_id]["success_count"] += 1
+ else:
+ runing_task[task_id]["failed_count"] += 1
+
+ # 回传结果到API
+ try:
+ self.post_result(task_id, shop_name, country_code, asin, status)
+ except Exception as e:
+ self.log(f"回传结果失败: {str(e)}", "ERROR")
+
self.log(f"国家 {country_name} 处理完成")
diff --git a/app/amazon/del_brand.py b/app/amazon/del_brand.py
index fd0a311..af7c8ee 100644
--- a/app/amazon/del_brand.py
+++ b/app/amazon/del_brand.py
@@ -169,7 +169,7 @@ class ZiniaoDriver:
"cookieTypeLoad": 0,
"cookieTypeSave": cookieTypeSave,
"runMode": "1",
- "isLoadUserPlugin": False,
+ "isLoadUserPlugin": True,
"pluginIdType": 1,
"privacyMode": isprivacy
}
diff --git a/app/amazon/match_action.py b/app/amazon/match_action.py
index 0eb6319..2a4d3e0 100644
--- a/app/amazon/match_action.py
+++ b/app/amazon/match_action.py
@@ -308,7 +308,89 @@ class MatchTak:
if task_id in runing_task:
runing_task[task_id]["status"] = "failed"
runing_task[task_id]["error"] = str(e)
-
+
+ def open_shop(self,max_retries,company_name,shop_name,iskill=False):
+ error_info = ""
+ driver = None
+ for retry in range(max_retries):
+ try:
+ self.log(f"尝试打开店铺 {shop_name} (第 {retry + 1}/{max_retries} 次)")
+
+ if iskill:
+ self.log("重试前先杀掉浏览器进程...")
+ kill_process("v6")
+ kill_process("v5")
+ time.sleep(2)
+
+ # 组装用户信息并创建驱动
+ user_info = {
+ **self.user_info,
+ "company": company_name
+ }
+ driver = AmzoneMatchAction(user_info)
+ browser = driver.open_shop(shop_name)
+
+ if browser and browser != "店铺不存在":
+ self.log(f"成功打开店铺 {shop_name}")
+ else:
+ self.log(f"打开店铺失败: {browser}", "WARNING")
+ driver = None
+ continue
+
+ # 判断是否需要登录
+ need_login = driver.need_login()
+ print("【是否需要登录】:", need_login)
+ if need_login:
+ self.log(f"店铺 {shop_name} 需要登录,正在登录...")
+ # 获取店铺凭证
+ response = get_shop_info(shop_name)
+ print("【获取店铺凭证返回】:", response.text)
+ shop_data = response.json()
+ if not shop_data:
+ mes = f"获取店铺凭证失败,响应数据: {shop_data.get('message', '未知错误')}"
+ self.log(mes, "ERROR")
+ show_notification(mes, "ERROR")
+ continue
+
+ password = shop_data["data"]["password"]
+
+ login_success = driver.login(password)
+ if login_success:
+ self.log(f"店铺 {shop_name} 登录成功,正在重新打开店铺...")
+ browser = driver.open_shop(shop_name)
+ if browser and browser != "店铺不存在":
+ self.log(f"成功打开店铺 {shop_name} 登录后")
+ break
+ else:
+ self.log(f"登录后打开店铺失败: {browser}", "WARNING")
+ driver = None
+ else:
+ self.log(f"店铺 {shop_name} 登录失败", "WARNING")
+ driver = None
+ else:
+ break
+
+ except Exception as e:
+ import traceback
+ self.log(f"打开店铺异常: {traceback.format_exc()}", "INFO")
+ driver = None
+ error_info = str(e)
+ time.sleep(10)
+
+ # 如果还有重试机会,等待后继续
+ if retry < max_retries - 1:
+ time.sleep(3)
+
+ # 检查是否成功打开
+ if not driver or not browser or browser == "店铺不存在":
+ error_msg = f"店铺 {shop_name} 打开失败,已重试 {max_retries} 次,跳过该店铺,{error_info}"
+ self.log(error_msg, "ERROR")
+ # 从执行列表中移除
+ if shop_name in runing_shop:
+ del runing_shop[shop_name]
+ return driver
+ return driver
+
# def process_shop(self, shop_item: dict, country_codes: list, task_id: int, risk_listing_filter: str):
def process_shop(self, shop_item: dict, country_codes: list, task_id: int, risk_listing_filter: str,
user_id=None, stage_index=None, final_stage: bool = True,limit:str=None):
@@ -339,124 +421,52 @@ class MatchTak:
# 店铺打开重试最多3次
driver = None
max_retries = 3
-
- error_info = ""
- for retry in range(max_retries):
+ iskill = False
+
+ # 处理每个国家
+ for country_code in country_codes:
+ # 检查是否收到暂停请求
+ if task_id in runing_task and runing_task[task_id].get("stop_requested", False):
+ self.log(f"检测到任务 {task_id} 的暂停请求,停止处理国家", "WARNING")
+ break
+
+ # 打开店铺
+ driver = self.open_shop(max_retries=max_retries, company_name=company_name,
+ shop_name=shop_name, iskill=iskill)
+ if driver is None:
+ self.log(f"任务 {task_id} 启动店铺失败,结束任务", "ERROR")
+ for country_code in country_codes:
+ self.post_result(task_id, shop_name, country_code, "", "", is_done=True)
+ return
+
try:
- self.log(f"尝试打开店铺 {shop_name} (第 {retry + 1}/{max_retries} 次)")
-
- # 如果不是第一次尝试,先杀进程
- # if retry > 0:
- # self.log("重试前先杀掉浏览器进程...")
- # kill_process("v6")
- # kill_process("v5")
- # time.sleep(2)
-
- # 组装用户信息并创建驱动
- user_info = {
- **self.user_info,
- "company": company_name
- }
- driver = AmzoneMatchAction(user_info)
- browser = driver.open_shop(shop_name)
-
- if browser and browser != "店铺不存在":
- self.log(f"成功打开店铺 {shop_name}")
- else:
- self.log(f"打开店铺失败: {browser}", "WARNING")
- driver = None
- continue
-
- # 判断是否需要登录
- need_login = driver.need_login()
- print("【是否需要登录】:",need_login)
- if need_login:
- self.log(f"店铺 {shop_name} 需要登录,正在登录...")
- # 获取店铺凭证
- response = get_shop_info(shop_name)
- print("【获取店铺凭证返回】:",response.text)
- shop_data = response.json()
- if not shop_data:
- mes = f"获取店铺凭证失败,响应数据: {shop_data.get('message', '未知错误')}"
- self.log(mes, "ERROR")
- show_notification(mes, "ERROR")
- continue
-
- password = shop_data["data"]["password"]
-
- login_success = driver.login(password)
- if login_success:
- self.log(f"店铺 {shop_name} 登录成功,正在重新打开店铺...")
- browser = driver.open_shop(shop_name)
- if browser and browser != "店铺不存在":
- self.log(f"成功打开店铺 {shop_name} 登录后")
- break
- else:
- self.log(f"登录后打开店铺失败: {browser}", "WARNING")
- driver = None
- else:
- self.log(f"店铺 {shop_name} 登录失败", "WARNING")
- driver = None
- else:
- break
-
+ self.process_country(driver, country_code, task_id, shop_name,risk_listing_filter,limit)
except Exception as e:
import traceback
- self.log(f"打开店铺异常: {traceback.format_exc()}", "INFO")
- driver = None
- error_info = str(e)
- time.sleep(10)
-
- # 如果还有重试机会,等待后继续
- if retry < max_retries - 1:
- time.sleep(3)
-
- # 检查是否成功打开
- if not driver or not browser or browser == "店铺不存在":
- error_msg = f"店铺 {shop_name} 打开失败,已重试 {max_retries} 次,跳过该店铺,{error_info}"
- self.log(error_msg, "ERROR")
- # 从执行列表中移除
- if shop_name in runing_shop:
- del runing_shop[shop_name]
- return
-
+ self.log(f"处理国家 {country_code} 失败: {str(e)}", "ERROR")
+ self.log(traceback.format_exc(), "ERROR")
+ if "与页面的连接已断开" in e:
+ iskill = True
+
+ # 更新已处理国家数
+ if task_id in runing_task:
+ runing_task[task_id]["processed_countries"] += 1
+
+ # 最后回传,标记完成
try:
- # 处理每个国家
- for country_code in country_codes:
- # 检查是否收到暂停请求
- if task_id in runing_task and runing_task[task_id].get("stop_requested", False):
- self.log(f"检测到任务 {task_id} 的暂停请求,停止处理国家", "WARNING")
- break
-
- try:
- self.process_country(driver, country_code, task_id, shop_name,risk_listing_filter,limit)
- except Exception as e:
- import traceback
- self.log(f"处理国家 {country_code} 失败: {str(e)}", "ERROR")
- self.log(traceback.format_exc(), "ERROR")
- # 最后回传,标记完成
- try:
- # self.post_result(task_id, shop_name, country_code, "", "", is_done=True)
- if final_stage:
- self.post_result(task_id, shop_name, country_code, "", "", is_done=True)
- else:
- self.post_stage_finished(task_id, user_id, stage_index)
- except Exception as e:
- self.log(f"回传结果失败: {str(e)}", "ERROR")
- finally:
- # 关闭店铺
- try:
- if driver:
- self.log(f"关闭店铺 {shop_name}")
- driver.close_store()
- time.sleep(2)
- except Exception as e:
- self.log(f"关闭店铺失败: {str(e)}", "WARNING")
-
- # 从正在执行中的店铺列表中移除
- if shop_name in runing_shop:
- del runing_shop[shop_name]
- self.log(f"店铺 {shop_name} 已从执行列表中移除")
+ # self.post_result(task_id, shop_name, country_code, "", "", is_done=True)
+ if final_stage:
+ self.post_result(task_id, shop_name, country_code, "", "", is_done=True)
+ else:
+ self.post_stage_finished(task_id, user_id, stage_index)
+ except Exception as e:
+ self.log(f"回传结果失败: {str(e)}", "ERROR")
+
+
+ # 从正在执行中的店铺列表中移除
+ if shop_name in runing_shop:
+ del runing_shop[shop_name]
+ self.log(f"店铺 {shop_name} 已从执行列表中移除")
def process_country(self, driver: AmzoneMatchAction, country_code: str, task_id: int, shop_name: str, risk_listing_filter: str,limit:str=None):
"""处理单个国家的审批任务
@@ -487,7 +497,16 @@ class MatchTak:
for retry in range(max_retries):
try:
self.log(f"尝试切换到国家 {country_name} (第 {retry + 1}/{max_retries} 次)")
-
+ if retry > 1:
+ # 刷新不行就重新打开店铺
+ self.log("重试前重新打开店铺...")
+ try:
+ driver.close_store()
+ time.sleep(3)
+ driver.open_shop(shop_name)
+ except Exception as e:
+ self.log(f"关闭重新打开店铺: {str(e)}", "WARNING")
+
# 如果不是第一次尝试,先刷新页面
if retry > 0:
self.log("重试前刷新页面...")
@@ -559,36 +578,28 @@ class MatchTak:
self.log(f"国家 {country_name} 搜索出 {len(sku_ls)} 商品,开始处理...")
# 处理所有需要审批的商品(通过yield获取结果)
- try:
- for asin, status in driver.run_page_action():
- # 检查是否收到暂停请求
- if task_id in runing_task and runing_task[task_id].get("stop_requested", False):
- self.log(f"检测到任务 {task_id} 的暂停请求,停止处理ASIN", "WARNING")
- break
-
- self.log(f"ASIN {asin} 处理结果: {status}")
-
- # 更新任务状态
- if task_id in runing_task:
- runing_task[task_id]["current_asin"] = asin
- runing_task[task_id]["processed_asins"] += 1
+ for asin, status in driver.run_page_action():
+ # 检查是否收到暂停请求
+ if task_id in runing_task and runing_task[task_id].get("stop_requested", False):
+ self.log(f"检测到任务 {task_id} 的暂停请求,停止处理ASIN", "WARNING")
+ break
+
+ self.log(f"ASIN {asin} 处理结果: {status}")
+
+ # 更新任务状态
+ if task_id in runing_task:
+ runing_task[task_id]["current_asin"] = asin
+ runing_task[task_id]["processed_asins"] += 1
+
+ runing_task[task_id]["failed_count"] += 1
+
+ # 回传结果到API
+ try:
+ self.post_result(task_id, shop_name, country_code, asin, status)
+ except Exception as e:
+ self.log(f"回传结果失败: {str(e)}", "ERROR")
- runing_task[task_id]["failed_count"] += 1
-
- # 回传结果到API
- try:
- self.post_result(task_id, shop_name, country_code, asin, status)
- except Exception as e:
- self.log(f"回传结果失败: {str(e)}", "ERROR")
-
- except Exception as e:
- import traceback
- self.log(f"处理审批商品异常: {str(e)}", "ERROR")
- self.log(traceback.format_exc(), "ERROR")
-
- # 更新已处理国家数
- if task_id in runing_task:
- runing_task[task_id]["processed_countries"] += 1
+
self.log(f"国家 {country_name} 处理完成")
diff --git a/app/amazon/price_match.py b/app/amazon/price_match.py
index 08e393f..73ee741 100644
--- a/app/amazon/price_match.py
+++ b/app/amazon/price_match.py
@@ -114,6 +114,30 @@ def calculate_standard_competitor_pricing(my_current_price, target_competitor_pr
class ChromeAmzone:
mark_name = "亚马逊详情采集"
+ country_info = {
+ "英国": {
+ "url": "https://www.amazon.co.uk/dp/B0CJ8SNXXV",
+ "zip_code": "SW1A 1AA",
+ "mark": "SW1A 1"
+ },
+ "德国": {
+ "url": "https://www.amazon.de/dp/B0CC8CW9G2?th=1",
+ "zip_code": "10115"
+ },
+ "法国": {
+ "url": "https://www.amazon.fr/dp/B0FRG1MJ8H?th=1",
+ "zip_code": "75001"
+ },
+ "西班牙": {
+ "url": "https://www.amazon.es/dp/B08ZXVNYNN",
+ "zip_code": "28001"
+ },
+ "意大利": {
+ "url": "https://www.amazon.it/dp/B0D1P17T2Q",
+ "zip_code": "20121"
+ }
+ }
+
def __init__(self,tab):
self.tab = tab
@@ -136,8 +160,92 @@ class ChromeAmzone:
if len(accept_btn) > 0:
accept_btn[0].click()
+ def _set_zip_code(self, zip_code, mark=None):
+ """
+ 设置邮编
- def run(self):
+ Args:
+ zip_code: 目标邮编
+ """
+ try:
+ # 检查当前邮编
+ zip_display = self.tab.ele('xpath://div[@id="glow-ingress-block"]', timeout=10)
+
+ if zip_display:
+ current_text = zip_display.text
+ # print(f"当前地址信息: {current_text}")
+ # 先检查标识
+ if mark is not None and mark in current_text:
+ print(f"邮编检测到标识: {mark},无需修改")
+ return True
+ # 检查是否已经包含目标邮编
+ if zip_code in current_text:
+ print(f"邮编已经设置为: {zip_code},无需修改")
+ return True
+
+ # 需要设置邮编
+ print(f"正在设置邮编为: {zip_code}")
+
+ # 点击地址选择按钮
+ location_link = self.tab.ele('xpath://a[@id="nav-global-location-popover-link"]', timeout=10)
+ if not location_link:
+ print("找不到地址设置按钮")
+ return False
+
+ location_link.click()
+ time.sleep(1)
+
+ # 等待邮编输入框出现
+ zip_input = self.tab.ele('xpath://input[@id="GLUXZipUpdateInput"]', timeout=10)
+ if not zip_input:
+ print("找不到邮编输入框")
+ return False
+
+ # 输入邮编
+ zip_input.input(zip_code, clear=True)
+ time.sleep(0.5)
+
+ # 点击提交按钮
+ submit_btn = self.tab.ele('xpath://input[@aria-labelledby="GLUXZipUpdate-announce"]', timeout=10)
+ if not submit_btn:
+ print("找不到提交按钮")
+ return False
+
+ submit_btn.click()
+
+ continue_btn = self.tab.eles('xpath://div[@class="a-popover-footer"]//input[@id="GLUXConfirmClose"]',
+ timeout=10)
+ if len(continue_btn) > 0:
+ continue_btn[0].click()
+
+ # 等待提交按钮消失(表示请求已发送)
+ print("等待邮编更新...")
+ time.sleep(2)
+
+ # 等待页面加载完成
+ self.tab.wait.doc_loaded(timeout=30, raise_err=False)
+ time.sleep(2)
+
+ # 验证邮编是否设置成功
+ zip_display_after = self.tab.ele('xpath://div[@id="glow-ingress-block"]', timeout=10)
+ if zip_display_after:
+ updated_text = zip_display_after.text
+ # print(f"更新后的地址信息: {updated_text}")
+
+ if zip_code in updated_text:
+ print(f"邮编设置成功: {zip_code}")
+ return True
+ else:
+ # print(f"邮编设置可能失败,当前显示: {updated_text}")
+ return False
+
+ return True
+
+ except Exception as e:
+ print(f"设置邮编时出错: {traceback.format_exc()}")
+ return False
+
+ def run(self,country):
"""
运行亚马逊详情采集任务
@@ -149,6 +257,20 @@ class ChromeAmzone:
dict: 包含采集到的数据
"""
try:
+ if country not in self.country_info:
+ error_msg = f"不支持的国家: {country},支持的国家有: {list(self.country_info.keys())}"
+ print(error_msg)
+ show_notification(error_msg, "error")
+ return None
+ # 获取国家配置
+ country_config = self.country_info[country]
+ zip_code = country_config["zip_code"]
+ mark = country_config.get("mark")
+
+ self._set_zip_code(zip_code,mark)
+
+ self.tab.wait.doc_loaded(timeout=5,raise_err=False)
+
self.tab.wait.doc_loaded(timeout=30, raise_err=False)
@@ -379,7 +501,15 @@ class AmzonePriceMatch(AmamzonBase):
print("当前操作的tab_id",self.tab.tab_id)
self.browser.close_tabs(close_tab)
- def run_page_action(self,current_shop_name:str,appoint_asin:str=None,skip_asin:list=[]):
+ def run_page_action(self,current_shop_name:str,mode:str,
+ appoint_asin:str=None,skip_asin:list=[],miniprice_info:dict={}):
+ """
+
+ miniprice_info :
+ {
+ "B0DSJGJBWV" : "19.81" # asin : 最低价
+ }
+ """
print(f"【{self.mark_name}】,开始执行")
num = 0
retry_num = 0
@@ -453,6 +583,24 @@ class AmzonePriceMatch(AmamzonBase):
if len(current_price_ele) > 0:
current_price = current_price_ele[0].attr("value")
print("获取到当前价格为 ->",current_price)
+ # 如果紫鸟里当前价格低于最低价则删除,否则跳过
+ if mode == "status" and miniprice_info.get(asin):
+ backend_price = float(miniprice_info.get(asin))
+ if float(current_price) < backend_price:
+ yield (asin, {
+ "statu": "删除(当前价格低于最低价)",
+ "deleteSkipAsin": True,
+ "removeAsin": asin,
+ })
+ continue
+ else:
+ yield (asin, {
+ "statu": "跳过(当前价格不低于最低价)",
+ "deleteSkipAsin": True,
+ "removeAsin": asin,
+ })
+ continue
+
else:
print(f"{self.mark_name} 没有获取到当前价格")
yield (asin, {
@@ -476,10 +624,16 @@ class AmzonePriceMatch(AmamzonBase):
time.sleep(1)
pass
chrome = ChromeAmzone(tab=new_tab)
- front_end_data = chrome.run()
+ front_end_data = chrome.run(country=current_country)
chrome.close()
print(self.mark_name,"亚马逊前台抓取到数据",front_end_data)
- #
+ if front_end_data is None or len(front_end_data.get("top_sellers")) == 0:
+ yield (asin, {
+ "statu": "失败(第一名获取失败,请检查卖家精灵是否启用)",
+ "currentPrice": current_price,
+ })
+ continue
+
cart_seller = front_end_data.get("cart_seller")
if cart_seller == current_shop_name and len(front_end_data.get("top_sellers"))< 2:
yield (asin, {
@@ -769,6 +923,8 @@ class PriceTask:
shopMallName = shop_item.get("shopMallName","")
skip_asins_by_country = shop_item.get("skip_asins_by_country",{})
asin_rows_by_country = shop_item.get("asin_rows_by_country",{})
+ skip_asin_details_by_country = shop_item.get("skip_asin_details_by_country",{})
+ mode = shop_item.get("mode")
if not company_name:
self.log(f"店铺 {shop_name} 的公司名称为空,跳过", "WARNING")
@@ -785,56 +941,61 @@ class PriceTask:
# 店铺打开重试最多3次
driver = None
max_retries = 3
+ iskill = False
+ # 处理每个国家
+ for country_code in country_codes:
+ # 打开店铺
+ driver = self.open_shop(max_retries=max_retries, company_name=company_name,
+ shop_name=shop_name,iskill=iskill)
+ if driver is None:
+ self.log(f"任务 {task_id} 启动店铺失败,结束任务", "ERROR")
+ for country_code in country_codes:
+ self.post_result(task_id, shop_name, country_code, "", {}, shopMallName, is_done=True)
+ return
- # 打开店铺
- driver = self.open_shop(max_retries=max_retries,company_name=company_name,shop_name=shop_name)
- if driver is None:
- self.log(f"任务 {task_id} 启动店铺失败,结束任务", "ERROR")
- for country_code in country_codes:
- self.post_result(task_id, shop_name, country_code, "", {}, shopMallName, is_done=True)
- return
- try:
- # 处理每个国家
- for country_code in country_codes:
- # 检查是否收到暂停请求
- if task_id in runing_task and runing_task[task_id].get("stop_requested", False):
- self.log(f"检测到任务 {task_id} 的暂停请求,停止处理国家", "WARNING")
- break
- skip_asin = skip_asins_by_country.get(country_code,[]) #需要跳过的asin
- appoint_asin = asin_rows_by_country.get(country_code,[])
- try:
- self.process_country(driver, country_code, task_id, shop_name, risk_listing_filter,
- shopMallName=shopMallName,skip_asin=skip_asin, limit=limit,appoint_asin=appoint_asin)
- except Exception as e:
- import traceback
- self.log(f"处理国家 {country_code} 失败: {str(e)}", "ERROR")
- self.log(traceback.format_exc(), "ERROR")
- # 最后回传,标记完成
+ # 检查是否收到暂停请求
+ if task_id in runing_task and runing_task[task_id].get("stop_requested", False):
+ self.log(f"检测到任务 {task_id} 的暂停请求,停止处理国家", "WARNING")
+ break
+ skip_asin = skip_asins_by_country.get(country_code,[]) #需要跳过的asin
+ appoint_asin = asin_rows_by_country.get(country_code,[])
+ miniprice_info = {i.get('asin'):i.get('minimumPrice') for i in skip_asin_details_by_country.get(country_code,[])}
try:
- # self.post_result(task_id, shop_name, country_code, "", "", is_done=True)
- if final_stage:
- self.post_result(task_id, shop_name, country_code, "", {},shopMallName,is_done=True)
- else:
- self.post_stage_finished(task_id, user_id, stage_index)
+ self.process_country(driver, country_code, task_id, shop_name, risk_listing_filter,
+ shopMallName=shopMallName,skip_asin=skip_asin, limit=limit,
+ appoint_asin=appoint_asin,mode=mode,miniprice_info=miniprice_info)
except Exception as e:
- self.log(f"回传结果失败: {str(e)}", "ERROR")
- finally:
+ import traceback
+ self.log(f"处理国家 {country_code} 失败: {str(e)}", "ERROR")
+ self.log(traceback.format_exc(), "ERROR")
+ if "与页面的连接已断开" in e:
+ iskill = True
+ # 更新已处理国家数
+ if task_id in runing_task:
+ runing_task[task_id]["processed_countries"] += 1
+
# 关闭店铺
- try:
- if driver:
- self.log(f"关闭店铺 {shop_name}")
- driver.close_store()
- time.sleep(2)
- except Exception as e:
- self.log(f"关闭店铺失败: {str(e)}", "WARNING")
+ driver.close_store()
+
+ # 最后回传,标记完成
+ try:
+ # self.post_result(task_id, shop_name, country_code, "", "", is_done=True)
+ if final_stage:
+ self.post_result(task_id, shop_name, country_code, "", {},shopMallName,is_done=True)
+ else:
+ self.post_stage_finished(task_id, user_id, stage_index)
+ except Exception as e:
+ self.log(f"回传结果失败: {str(e)}", "ERROR")
- # 从正在执行中的店铺列表中移除
- if shop_name in runing_shop:
- del runing_shop[shop_name]
- self.log(f"店铺 {shop_name} 已从执行列表中移除")
+ # 从正在执行中的店铺列表中移除
+ if shop_name in runing_shop:
+ del runing_shop[shop_name]
+ self.log(f"店铺 {shop_name} 已从执行列表中移除")
def process_country(self, driver: AmzonePriceMatch, country_code: str, task_id: int, shop_name: str,
- risk_listing_filter: str,shopMallName:str,skip_asin:list,appoint_asin:list,limit: str = None):
+ risk_listing_filter: str,shopMallName:str,skip_asin:list,appoint_asin:list,mode:str,
+ miniprice_info:dict={},
+ limit: str = None):
"""处理单个国家的审批任务
Args:
@@ -864,7 +1025,7 @@ class PriceTask:
self.log(f"尝试切换到国家 {country_name} (第 {retry + 1}/{max_retries} 次)")
if retry > 1:
# 刷新不行就重新打开店铺
- self.log("重试前刷新页面...")
+ self.log("重试前重新打开店铺...")
try:
driver.close_store()
time.sleep(3)
@@ -946,49 +1107,41 @@ class PriceTask:
self.log(f"国家 {country_name} 搜索出 {len(sku_ls)} 商品,开始处理...")
# 处理所有需要审批的商品(通过yield获取结果)
- try:
- # 指定 asin
- max_range = max(1,len(appoint_asin))
- for i in range(max_range):
- if len(appoint_asin) > i:
- _shopMallName = appoint_asin[i]["shopMallName"]
- ap_asin = appoint_asin[i]["shopMallName"]
- else:
- _shopMallName = shopMallName
- ap_asin = None
- for asin, status in driver.run_page_action(
- current_shop_name=_shopMallName,
- appoint_asin=ap_asin,skip_asin=skip_asin
- ):
- # 检查是否收到暂停请求
- if task_id in runing_task and runing_task[task_id].get("stop_requested", False):
- self.log(f"检测到任务 {task_id} 的暂停请求,停止处理ASIN", "WARNING")
- break
+ # 指定 asin
+ max_range = max(1,len(appoint_asin))
+ for i in range(max_range):
+ if len(appoint_asin) > i:
+ _shopMallName = appoint_asin[i]["shopMallName"]
+ ap_asin = appoint_asin[i]["shopMallName"]
+ else:
+ _shopMallName = shopMallName
+ ap_asin = None
- self.log(f"ASIN {asin} 处理结果: {status}")
+ for asin, status in driver.run_page_action(
+ current_shop_name=_shopMallName,
+ appoint_asin=ap_asin,skip_asin=skip_asin,mode=mode,
+ miniprice_info=miniprice_info
+ ):
+ # 检查是否收到暂停请求
+ if task_id in runing_task and runing_task[task_id].get("stop_requested", False):
+ self.log(f"检测到任务 {task_id} 的暂停请求,停止处理ASIN", "WARNING")
+ break
- # 更新任务状态
- if task_id in runing_task:
- runing_task[task_id]["current_asin"] = asin
- runing_task[task_id]["processed_asins"] += 1
+ self.log(f"ASIN {asin} 处理结果: {status}")
- runing_task[task_id]["failed_count"] += 1
+ # 更新任务状态
+ if task_id in runing_task:
+ runing_task[task_id]["current_asin"] = asin
+ runing_task[task_id]["processed_asins"] += 1
- # 回传结果到API
- try:
- self.post_result(task_id, shop_name, country_code, asin, status,shopMallName)
- except Exception as e:
- self.log(f"回传结果失败: {str(e)}", "ERROR")
+ runing_task[task_id]["failed_count"] += 1
- except Exception as e:
- import traceback
- self.log(f"处理审批商品异常: {str(e)}", "ERROR")
- self.log(traceback.format_exc(), "ERROR")
-
- # 更新已处理国家数
- if task_id in runing_task:
- runing_task[task_id]["processed_countries"] += 1
+ # 回传结果到API
+ try:
+ self.post_result(task_id, shop_name, country_code, asin, status,shopMallName)
+ except Exception as e:
+ self.log(f"回传结果失败: {str(e)}", "ERROR")
self.log(f"国家 {country_name} 处理完成")
@@ -1073,6 +1226,8 @@ class PriceTask:
"secondPlace": status.get("secondPlace") if status.get("secondPlace") else "",
"cartShopName": status.get("cartShopName") if status.get("cartShopName") else "",
"priceChangeStatus": "UPDATED",
+ "deleteSkipAsin": status.get("deleteSkipAsin",False),
+ "removeAsin": status.get("removeAsin") if status.get("removeAsin") else "",
# "modifyCount": "2",
"status": status.get("statu")
}
diff --git a/app/assets/convert.js b/app/assets/convert.js
index 9363954..5222ea0 100644
--- a/app/assets/convert.js
+++ b/app/assets/convert.js
@@ -1 +1 @@
-import{d as A,o as H,a as n,c as o,b as z,B as K,e as s,F as w,r as F,t as r,f as _,n as E,w as M,s as j,u as q,g as u,h as T,x as J,i as k,E as i,y as L,z as O,A as G,_ as Q,p as W,q as X}from"./pywebview-DuyK2jB1.js";import{e as Y}from"./brand-CKgrwtni.js";const Z={class:"page-shell module-page"},ee={class:"main-content"},se={class:"left-panel"},te={class:"upload-zone"},ae={class:"selected-files clean-placeholder"},le={key:0,class:"more-line"},ne={key:1},oe={class:"option-group"},ie=["value"],re={class:"template-item-content"},ce={class:"label"},ue={key:0,class:"template-tag"},de={key:1,class:"template-tag muted"},ve={key:2,class:"template-tag custom"},pe={class:"desc"},_e={key:0,class:"empty-option"},fe={class:"option-group"},me={class:"radio-item active"},he={class:"desc"},ye={class:"run-row"},ge=["disabled"],be={class:"loading-msg"},we={class:"right-panel"},ke={class:"task-list-wrap"},Ce={class:"clean-result-summary"},xe={class:"summary-card"},Fe={class:"summary-card"},Pe={class:"summary-card"},Ee={class:"result-list-wrap"},Te={key:0,class:"empty-tasks"},Ie={key:1,class:"task-list clean-result-list"},Be={class:"left"},Ne=["title"],$e={key:0,class:"files"},De={key:1,class:"files"},Re={class:"task-right"},Se=["onClick"],Ue=["onClick"],Ve=A({__name:"BrandConvertTab",setup(He){const v=u([]),g=u(""),f=u([]),m=u(!1),b=u([]),h=u({total:0,successCount:0,failedCount:0,items:[]}),d=u([]),c=u(""),C=T(()=>v.value.slice(0,8)),I=T(()=>d.value.find(a=>a.id===c.value)||null);async function P(a){const e=k();if(!e?.upload_file_to_java)throw new Error("当前桌面端未提供文件上传桥接能力");const t=[];for(const l of a){const p=typeof l=="string"?l:l.absolutePath,V=typeof l=="string"?void 0:l.relativePath,y=await e.upload_file_to_java(p,V);if(!y?.success||!y.data)throw new Error(y?.error||y?.message||`上传失败:${p}`);t.push(y.data)}return t}async function B(a){const e=await J();d.value=e||[];const t=d.value.find(p=>p.id===c.value);if(t){c.value=t.id;return}const l=d.value.find(p=>p.isDefault);if(l){c.value=l.id;return}c.value=d.value[0]?.id||""}async function N(a,e){v.value=a,g.value="",f.value=await P(a),i.success(e)}async function $(){const a=k();if(!a?.select_brand_xlsx_files){i.warning("当前环境不支持文件选择,请在本机客户端中打开");return}try{const e=await a.select_brand_xlsx_files();if(!e?.length)return;await N(e,`已选择 ${e.length} 个待转换文件`)}catch(e){i.error(e instanceof Error?e.message:"选择失败")}}async function D(){const a=k();if(!a?.select_brand_folder){i.warning("当前环境不支持文件夹选择,请在本机客户端中打开");return}try{const e=await a.select_brand_folder();if(!e)return;g.value=e.split(/[/\\]/).filter(Boolean).pop()||"";const t=await Y(e);if(t.success&&t.items?.length){v.value=t.items.map(l=>l.relativePath),f.value=await P(t.items),i.success(`已选择文件夹内 ${t.items.length} 个 xlsx 文件`);return}i.warning(t.error||"该文件夹下没有 xlsx 文件")}catch(e){i.error(e instanceof Error?e.message:"选择失败")}}async function R(){if(!f.value.length){i.warning("请先选择待转换 Excel 文件或文件夹");return}if(!c.value){i.warning("请先选择模板");return}try{m.value=!0;const a=await L({files:f.value.map(e=>({fileKey:e.fileKey,originalFilename:e.originalFilename,relativePath:e.relativePath})),templateId:c.value,archiveName:f.value.some(e=>!!e.relativePath)&&g.value?g.value:void 0});h.value=a,b.value=a.items||[],await x(),i.success("格式转换完成")}catch(a){i.error(a instanceof Error?a.message:"格式转换失败")}finally{m.value=!1}}async function x(){try{const a=await O();b.value=a.items||[],h.value={total:a.items?.length||0,successCount:a.items?.filter(e=>e.success).length||0,failedCount:a.items?.filter(e=>!e.success).length||0,items:a.items||[]}}catch{}}async function S(a){try{await G(a),await x(),i.success("已删除")}catch(e){i.error(e instanceof Error?e.message:"删除失败")}}async function U(a){const e=k();if(!a.downloadUrl){i.warning("当前结果没有下载地址");return}const t=a.outputFilename||`${new Date().toISOString().slice(0,10).replace(/-/g,"")}.txt`;if(e?.save_file_from_url_new){const l=await e.save_file_from_url_new(a.downloadUrl,t);l.success?i.success(`已保存:${l.path||t}`):l.error&&l.error!=="用户取消"&&i.error(l.error);return}}return H(()=>{B().catch(()=>{}),x().catch(()=>{})}),(a,e)=>(n(),o("div",Z,[z(K,{active:"convert"}),s("div",ee,[s("aside",se,[e[5]||(e[5]=s("div",{class:"section-title"},"选择文件",-1)),s("div",te,[e[1]||(e[1]=s("div",{class:"hint"},"选择需要转换的 Excel 文件或文件夹,将按所选模板生成 txt 文件。",-1)),s("div",{class:"btns"},[s("button",{type:"button",class:"opt-btn",onClick:$},"选择 Excel 文件"),s("button",{type:"button",class:"opt-btn",onClick:D},"选择文件夹")]),s("div",ae,[v.value.length?(n(),o(w,{key:0},[(n(!0),o(w,null,F(C.value,t=>(n(),o("span",{key:t},r(t),1))),128)),v.value.length>C.value.length?(n(),o("span",le," 还有 "+r(v.value.length-C.value.length)+" 个文件未展开显示 ",1)):_("",!0)],64)):(n(),o("span",ne,"暂未选择待转换文件"))])]),e[6]||(e[6]=s("div",{class:"section-title"},"模板选择",-1)),s("div",oe,[(n(!0),o(w,null,F(d.value,t=>(n(),o("label",{key:t.id,class:E(["radio-item",{active:c.value===t.id}])},[M(s("input",{"onUpdate:modelValue":e[0]||(e[0]=l=>c.value=l),type:"radio",value:t.id,name:"convert-template"},null,8,ie),[[j,c.value]]),s("div",re,[s("div",null,[s("span",ce,[q(r(t.templateName)+" ",1),t.isDefault?(n(),o("span",ue,"默认")):t.builtIn?(n(),o("span",de,"内置")):(n(),o("span",ve,"自定义"))]),s("div",pe,"输出文件:"+r(t.outputFilename),1)])])],2))),128)),d.value.length?_("",!0):(n(),o("div",_e,"暂无可用模板"))]),e[7]||(e[7]=s("div",{class:"section-title"},"转换说明",-1)),s("div",fe,[s("label",me,[e[3]||(e[3]=s("input",{type:"checkbox",checked:"",disabled:""},null,-1)),s("div",null,[e[2]||(e[2]=s("span",{class:"label"},"按当前模板输出",-1)),s("div",he,"当前模板:"+r(I.value?.templateName||"未加载模板"),1)])]),e[4]||(e[4]=s("label",{class:"radio-item active"},[s("input",{type:"checkbox",checked:"",disabled:""}),s("div",null,[s("span",{class:"label"},"SKU 使用雪花ID-序号"),s("div",{class:"desc"},"例如 1773816146796-1")])],-1))]),s("div",ye,[s("button",{type:"button",class:"btn-run",disabled:m.value,onClick:R},r(m.value?"转换中...":"开始转换"),9,ge),s("span",be,r(m.value?"正在生成并上传结果,请稍候…":"选择文件后即可执行格式转换,结果会显示在右侧供下载"),1)])]),s("section",we,[e[12]||(e[12]=s("div",{class:"panel-header"},"转换结果",-1)),s("div",ke,[s("div",Ce,[s("div",xe,[e[8]||(e[8]=s("span",{class:"summary-label"},"已处理文件",-1)),s("strong",null,r(h.value.total),1)]),s("div",Fe,[e[9]||(e[9]=s("span",{class:"summary-label"},"成功结果",-1)),s("strong",null,r(h.value.successCount),1)]),s("div",Pe,[e[10]||(e[10]=s("span",{class:"summary-label"},"失败文件",-1)),s("strong",null,r(h.value.failedCount),1)])]),s("div",Ee,[e[11]||(e[11]=s("div",{class:"result-list-header"},[s("span",null,"生成的结果压缩包列表")],-1)),b.value.length===0?(n(),o("div",Te," 暂无转换结果,完成格式转换后会在这里展示结果压缩包 ")):(n(),o("ul",Ie,[(n(!0),o(w,null,F(b.value,t=>(n(),o("li",{key:`${t.resultId||t.outputFilename||t.sourceFilename}`,class:"task-item"},[s("div",Be,[s("span",{class:"id",title:t.sourceFilename},r(t.sourceFilename||"-"),9,Ne),t.outputFilename?(n(),o("div",$e,"压缩包文件:"+r(t.outputFilename),1)):_("",!0),t.error?(n(),o("div",De,"错误信息:"+r(t.error),1)):_("",!0)]),s("div",Re,[s("span",{class:E(["status",t.success?"success":"failed"])},r(t.success?"已完成":"失败"),3),t.success&&t.downloadUrl?(n(),o("button",{key:0,type:"button",class:"download",onClick:l=>U(t)}," 下载压缩包 ",8,Se)):_("",!0),t.resultId?(n(),o("button",{key:1,type:"button",class:"btn-delete",onClick:l=>S(t.resultId)}," 删除 ",8,Ue)):_("",!0)])]))),128))]))])])])])]))}}),Ae=Q(Ve,[["__scopeId","data-v-7c280b44"]]);W(Ae).use(X).mount("#app");
+import{d as A,o as H,a as n,c as o,b as z,B as K,e as s,F as w,r as F,t as r,f as _,n as E,w as M,s as j,u as q,g as u,h as T,x as J,i as k,E as i,y as L,z as O,A as G,_ as Q,p as W,q as X}from"./pywebview-9YBa--7x.js";import{e as Y}from"./brand-CRmRvyGD.js";const Z={class:"page-shell module-page"},ee={class:"main-content"},se={class:"left-panel"},te={class:"upload-zone"},ae={class:"selected-files clean-placeholder"},le={key:0,class:"more-line"},ne={key:1},oe={class:"option-group"},ie=["value"],re={class:"template-item-content"},ce={class:"label"},ue={key:0,class:"template-tag"},de={key:1,class:"template-tag muted"},ve={key:2,class:"template-tag custom"},pe={class:"desc"},_e={key:0,class:"empty-option"},fe={class:"option-group"},me={class:"radio-item active"},he={class:"desc"},ye={class:"run-row"},ge=["disabled"],be={class:"loading-msg"},we={class:"right-panel"},ke={class:"task-list-wrap"},Ce={class:"clean-result-summary"},xe={class:"summary-card"},Fe={class:"summary-card"},Pe={class:"summary-card"},Ee={class:"result-list-wrap"},Te={key:0,class:"empty-tasks"},Ie={key:1,class:"task-list clean-result-list"},Be={class:"left"},Ne=["title"],$e={key:0,class:"files"},De={key:1,class:"files"},Re={class:"task-right"},Se=["onClick"],Ue=["onClick"],Ve=A({__name:"BrandConvertTab",setup(He){const v=u([]),g=u(""),f=u([]),m=u(!1),b=u([]),h=u({total:0,successCount:0,failedCount:0,items:[]}),d=u([]),c=u(""),C=T(()=>v.value.slice(0,8)),I=T(()=>d.value.find(a=>a.id===c.value)||null);async function P(a){const e=k();if(!e?.upload_file_to_java)throw new Error("当前桌面端未提供文件上传桥接能力");const t=[];for(const l of a){const p=typeof l=="string"?l:l.absolutePath,V=typeof l=="string"?void 0:l.relativePath,y=await e.upload_file_to_java(p,V);if(!y?.success||!y.data)throw new Error(y?.error||y?.message||`上传失败:${p}`);t.push(y.data)}return t}async function B(a){const e=await J();d.value=e||[];const t=d.value.find(p=>p.id===c.value);if(t){c.value=t.id;return}const l=d.value.find(p=>p.isDefault);if(l){c.value=l.id;return}c.value=d.value[0]?.id||""}async function N(a,e){v.value=a,g.value="",f.value=await P(a),i.success(e)}async function $(){const a=k();if(!a?.select_brand_xlsx_files){i.warning("当前环境不支持文件选择,请在本机客户端中打开");return}try{const e=await a.select_brand_xlsx_files();if(!e?.length)return;await N(e,`已选择 ${e.length} 个待转换文件`)}catch(e){i.error(e instanceof Error?e.message:"选择失败")}}async function D(){const a=k();if(!a?.select_brand_folder){i.warning("当前环境不支持文件夹选择,请在本机客户端中打开");return}try{const e=await a.select_brand_folder();if(!e)return;g.value=e.split(/[/\\]/).filter(Boolean).pop()||"";const t=await Y(e);if(t.success&&t.items?.length){v.value=t.items.map(l=>l.relativePath),f.value=await P(t.items),i.success(`已选择文件夹内 ${t.items.length} 个 xlsx 文件`);return}i.warning(t.error||"该文件夹下没有 xlsx 文件")}catch(e){i.error(e instanceof Error?e.message:"选择失败")}}async function R(){if(!f.value.length){i.warning("请先选择待转换 Excel 文件或文件夹");return}if(!c.value){i.warning("请先选择模板");return}try{m.value=!0;const a=await L({files:f.value.map(e=>({fileKey:e.fileKey,originalFilename:e.originalFilename,relativePath:e.relativePath})),templateId:c.value,archiveName:f.value.some(e=>!!e.relativePath)&&g.value?g.value:void 0});h.value=a,b.value=a.items||[],await x(),i.success("格式转换完成")}catch(a){i.error(a instanceof Error?a.message:"格式转换失败")}finally{m.value=!1}}async function x(){try{const a=await O();b.value=a.items||[],h.value={total:a.items?.length||0,successCount:a.items?.filter(e=>e.success).length||0,failedCount:a.items?.filter(e=>!e.success).length||0,items:a.items||[]}}catch{}}async function S(a){try{await G(a),await x(),i.success("已删除")}catch(e){i.error(e instanceof Error?e.message:"删除失败")}}async function U(a){const e=k();if(!a.downloadUrl){i.warning("当前结果没有下载地址");return}const t=a.outputFilename||`${new Date().toISOString().slice(0,10).replace(/-/g,"")}.txt`;if(e?.save_file_from_url_new){const l=await e.save_file_from_url_new(a.downloadUrl,t);l.success?i.success(`已保存:${l.path||t}`):l.error&&l.error!=="用户取消"&&i.error(l.error);return}}return H(()=>{B().catch(()=>{}),x().catch(()=>{})}),(a,e)=>(n(),o("div",Z,[z(K,{active:"convert"}),s("div",ee,[s("aside",se,[e[5]||(e[5]=s("div",{class:"section-title"},"选择文件",-1)),s("div",te,[e[1]||(e[1]=s("div",{class:"hint"},"选择需要转换的 Excel 文件或文件夹,将按所选模板生成 txt 文件。",-1)),s("div",{class:"btns"},[s("button",{type:"button",class:"opt-btn",onClick:$},"选择 Excel 文件"),s("button",{type:"button",class:"opt-btn",onClick:D},"选择文件夹")]),s("div",ae,[v.value.length?(n(),o(w,{key:0},[(n(!0),o(w,null,F(C.value,t=>(n(),o("span",{key:t},r(t),1))),128)),v.value.length>C.value.length?(n(),o("span",le," 还有 "+r(v.value.length-C.value.length)+" 个文件未展开显示 ",1)):_("",!0)],64)):(n(),o("span",ne,"暂未选择待转换文件"))])]),e[6]||(e[6]=s("div",{class:"section-title"},"模板选择",-1)),s("div",oe,[(n(!0),o(w,null,F(d.value,t=>(n(),o("label",{key:t.id,class:E(["radio-item",{active:c.value===t.id}])},[M(s("input",{"onUpdate:modelValue":e[0]||(e[0]=l=>c.value=l),type:"radio",value:t.id,name:"convert-template"},null,8,ie),[[j,c.value]]),s("div",re,[s("div",null,[s("span",ce,[q(r(t.templateName)+" ",1),t.isDefault?(n(),o("span",ue,"默认")):t.builtIn?(n(),o("span",de,"内置")):(n(),o("span",ve,"自定义"))]),s("div",pe,"输出文件:"+r(t.outputFilename),1)])])],2))),128)),d.value.length?_("",!0):(n(),o("div",_e,"暂无可用模板"))]),e[7]||(e[7]=s("div",{class:"section-title"},"转换说明",-1)),s("div",fe,[s("label",me,[e[3]||(e[3]=s("input",{type:"checkbox",checked:"",disabled:""},null,-1)),s("div",null,[e[2]||(e[2]=s("span",{class:"label"},"按当前模板输出",-1)),s("div",he,"当前模板:"+r(I.value?.templateName||"未加载模板"),1)])]),e[4]||(e[4]=s("label",{class:"radio-item active"},[s("input",{type:"checkbox",checked:"",disabled:""}),s("div",null,[s("span",{class:"label"},"SKU 使用雪花ID-序号"),s("div",{class:"desc"},"例如 1773816146796-1")])],-1))]),s("div",ye,[s("button",{type:"button",class:"btn-run",disabled:m.value,onClick:R},r(m.value?"转换中...":"开始转换"),9,ge),s("span",be,r(m.value?"正在生成并上传结果,请稍候…":"选择文件后即可执行格式转换,结果会显示在右侧供下载"),1)])]),s("section",we,[e[12]||(e[12]=s("div",{class:"panel-header"},"转换结果",-1)),s("div",ke,[s("div",Ce,[s("div",xe,[e[8]||(e[8]=s("span",{class:"summary-label"},"已处理文件",-1)),s("strong",null,r(h.value.total),1)]),s("div",Fe,[e[9]||(e[9]=s("span",{class:"summary-label"},"成功结果",-1)),s("strong",null,r(h.value.successCount),1)]),s("div",Pe,[e[10]||(e[10]=s("span",{class:"summary-label"},"失败文件",-1)),s("strong",null,r(h.value.failedCount),1)])]),s("div",Ee,[e[11]||(e[11]=s("div",{class:"result-list-header"},[s("span",null,"生成的结果压缩包列表")],-1)),b.value.length===0?(n(),o("div",Te," 暂无转换结果,完成格式转换后会在这里展示结果压缩包 ")):(n(),o("ul",Ie,[(n(!0),o(w,null,F(b.value,t=>(n(),o("li",{key:`${t.resultId||t.outputFilename||t.sourceFilename}`,class:"task-item"},[s("div",Be,[s("span",{class:"id",title:t.sourceFilename},r(t.sourceFilename||"-"),9,Ne),t.outputFilename?(n(),o("div",$e,"压缩包文件:"+r(t.outputFilename),1)):_("",!0),t.error?(n(),o("div",De,"错误信息:"+r(t.error),1)):_("",!0)]),s("div",Re,[s("span",{class:E(["status",t.success?"success":"failed"])},r(t.success?"已完成":"失败"),3),t.success&&t.downloadUrl?(n(),o("button",{key:0,type:"button",class:"download",onClick:l=>U(t)}," 下载压缩包 ",8,Se)):_("",!0),t.resultId?(n(),o("button",{key:1,type:"button",class:"btn-delete",onClick:l=>S(t.resultId)}," 删除 ",8,Ue)):_("",!0)])]))),128))]))])])])])]))}}),Ae=Q(Ve,[["__scopeId","data-v-7c280b44"]]);W(Ae).use(X).mount("#app");
diff --git a/app/assets/dedupe.js b/app/assets/dedupe.js
index fd8a71d..8819953 100644
--- a/app/assets/dedupe.js
+++ b/app/assets/dedupe.js
@@ -1 +1 @@
-import{d as z,o as W,a as n,c as o,b as q,B as J,e as s,F as C,r as F,t as i,f as b,n as I,w as P,v as B,g as u,h as L,i as x,E as r,j as O,k as G,l as Q,m as X,_ as Y,p as Z,q as ee}from"./pywebview-DuyK2jB1.js";import{e as se}from"./brand-CKgrwtni.js";const te={class:"page-shell module-page"},le={class:"main-content"},ae={class:"left-panel"},ne={class:"upload-zone"},oe={class:"selected-files clean-placeholder"},re={key:0,class:"more-line"},ie={key:1},ue={class:"option-group column-option-group"},ce={class:"column-toolbar"},de={class:"column-count"},ve={key:0,class:"column-grid"},pe=["value"],fe={key:1,class:"empty-column-state"},_e={class:"option-group"},me={class:"run-row"},he=["disabled"],ge={class:"loading-msg"},ye={class:"right-panel"},be={class:"task-list-wrap"},we={class:"clean-result-summary"},ke={class:"summary-card"},Ce={class:"summary-card"},Ie={class:"summary-card"},xe={class:"result-list-wrap"},Ee={key:0,class:"empty-tasks"},De={key:1,class:"task-list clean-result-list"},Fe={class:"left"},Pe=["title"],Be={key:0,class:"files"},Se={key:1,class:"files"},Ue={class:"task-right"},Ne=["onClick"],$e=["onClick"],Ae=z({__name:"BrandDedupeTab",setup(Ke){const p=u([]),c=u([]),f=u([]),w=u(""),d=u([]),S=u(!1),_=u(!0),m=u(!0),h=u(!1),k=u([]),g=u({total:0,successCount:0,failedCount:0,items:[]}),E=L(()=>f.value.slice(0,8));function $(){c.value=[...p.value]}function A(){c.value=[]}async function U(l){const e=x();if(!e?.upload_file_to_java)throw new Error("当前桌面端未提供文件上传桥接能力");const t=[];for(const a of l){const v=typeof a=="string"?a:a.absolutePath,j=typeof a=="string"?void 0:a.relativePath,y=await e.upload_file_to_java(v,j);if(!y?.success||!y.data)throw new Error(y?.error||y?.message||`上传失败:${v}`);t.push(y.data)}return t}async function N(l){const e=await X(l);if(!e.headers?.length){r.error("读取 Excel 表头失败"),p.value=[],c.value=[];return}const t=["id","ASIN","国家","价格","卖家名称"],a=[...t.filter(v=>e.headers?.includes(v)),...(e.headers||[]).filter(v=>!t.includes(v))];p.value=a,c.value=t.filter(v=>a.includes(v))}async function H(l,e){f.value=l,w.value="",d.value=await U(l),d.value.length>0&&await N(d.value[0].fileKey),r.success(e)}async function K(){const l=x();if(!l?.select_brand_xlsx_files){r.warning("当前环境不支持文件选择,请在本机客户端中打开");return}try{const e=await l.select_brand_xlsx_files();if(!e?.length)return;await H(e,`已选择 ${e.length} 个待清洗文件`)}catch(e){r.error(e instanceof Error?e.message:"选择失败")}}async function R(){const l=x();if(!l?.select_brand_folder){r.warning("当前环境不支持文件夹选择,请在本机客户端中打开");return}try{const e=await l.select_brand_folder();if(!e)return;w.value=e.split(/[/\\]/).filter(Boolean).pop()||"";const t=await se(e);if(t.success&&t.items?.length){f.value=t.items.map(a=>a.relativePath),d.value=await U(t.items),d.value.length>0&&await N(d.value[0].fileKey),r.success(`已选择文件夹内 ${t.items.length} 个 xlsx 文件`);return}r.warning(t.error||"该文件夹下没有 xlsx 文件")}catch(e){r.error(e instanceof Error?e.message:"选择失败")}}async function V(){if(!d.value.length){r.warning("请先选择待清洗 Excel 文件或文件夹");return}if(!c.value.length){r.warning("请至少选择一列保留列");return}if(!S.value&&!_.value&&!m.value){r.warning("请至少选择一种 ID 保留规则");return}try{h.value=!0;const l=await O({files:d.value.map(e=>({fileKey:e.fileKey,originalFilename:e.originalFilename,relativePath:e.relativePath})),selectedColumns:c.value,keepIntegerIds:S.value,keepUnderscoreIds:_.value,keepIntegerMainIdsWhenNoSubIds:m.value,archiveName:d.value.some(e=>!!e.relativePath)&&w.value?w.value:void 0});g.value=l,k.value=l.items||[],await D(),r.success("数据去重完成")}catch(l){r.error(l instanceof Error?l.message:"去重失败")}finally{h.value=!1}}async function D(){try{const l=await G();k.value=l.items||[],g.value={total:l.items?.length||0,successCount:l.items?.filter(e=>e.success).length||0,failedCount:l.items?.filter(e=>!e.success).length||0,items:l.items||[]}}catch{}}async function M(l){try{await Q(l),await D(),r.success("已删除")}catch(e){r.error(e instanceof Error?e.message:"删除失败")}}async function T(l){const e=x();if(!l.downloadUrl){r.warning("当前结果没有下载地址");return}const t=l.outputFilename||`${new Date().toISOString().slice(0,10).replace(/-/g,"")}_cleaned.xlsx`;if(e?.save_file_from_url_new){const a=await e.save_file_from_url_new(l.downloadUrl,t);a.success?r.success(`已保存:${a.path||t}`):a.error&&a.error!=="用户取消"&&r.error(a.error);return}}return W(()=>{D().catch(()=>{})}),(l,e)=>(n(),o("div",te,[q(J,{active:"dedupe"}),s("div",le,[s("aside",ae,[e[7]||(e[7]=s("div",{class:"section-title"},"选择文件",-1)),s("div",ne,[e[3]||(e[3]=s("div",{class:"hint"},"选择需要清洗的 Excel 文件或文件夹,后续将按所选规则输出处理结果。",-1)),s("div",{class:"btns"},[s("button",{type:"button",class:"opt-btn",onClick:K},"选择 Excel 文件"),s("button",{type:"button",class:"opt-btn",onClick:R},"选择文件夹")]),s("div",oe,[f.value.length?(n(),o(C,{key:0},[(n(!0),o(C,null,F(E.value,t=>(n(),o("span",{key:t},i(t),1))),128)),f.value.length>E.value.length?(n(),o("span",re," 还有 "+i(f.value.length-E.value.length)+" 个文件未展开显示 ",1)):b("",!0)],64)):(n(),o("span",ie,"暂未选择待清洗文件"))])]),e[8]||(e[8]=s("div",{class:"section-title"},"保留列设置",-1)),s("div",ue,[s("div",ce,[s("span",de,"已选择 "+i(c.value.length)+" / "+i(p.value.length)+" 列",1),s("div",{class:"column-actions"},[s("button",{type:"button",class:"opt-btn small",onClick:$},"全选"),s("button",{type:"button",class:"opt-btn small",onClick:A},"清空")])]),p.value.length?(n(),o("div",ve,[(n(!0),o(C,null,F(p.value,t=>(n(),o("label",{key:t,class:I(["column-item",{active:c.value.includes(t)}])},[P(s("input",{"onUpdate:modelValue":e[0]||(e[0]=a=>c.value=a),type:"checkbox",value:t},null,8,pe),[[B,c.value]]),s("span",null,i(t),1)],2))),128))])):(n(),o("div",fe,"请选择 Excel 文件后读取表头并填充可保留列")),e[4]||(e[4]=s("div",{class:"desc"},"读取首个 Excel 文件的第一行作为表头,用于动态选择需要保留的列。",-1))]),e[9]||(e[9]=s("div",{class:"section-title"},"ID 保留规则",-1)),s("div",_e,[s("label",{class:I(["radio-item",{active:_.value}])},[P(s("input",{"onUpdate:modelValue":e[1]||(e[1]=t=>_.value=t),type:"checkbox"},null,512),[[B,_.value]]),e[5]||(e[5]=s("div",null,[s("span",{class:"label"},"保留子链接"),s("div",{class:"desc"},"例如 1_1、2_3 这种带下划线的子项 ID")],-1))],2),s("label",{class:I(["radio-item",{active:m.value}])},[P(s("input",{"onUpdate:modelValue":e[2]||(e[2]=t=>m.value=t),type:"checkbox"},null,512),[[B,m.value]]),e[6]||(e[6]=s("div",null,[s("span",{class:"label"},"仅剩主链接无子链接"),s("div",{class:"desc"},"当某个主链接只有 13 这类主 ID、没有 13_1 这类对应子 ID 时,自动保留该主 ID")],-1))],2)]),s("div",me,[s("button",{type:"button",class:"btn-run",disabled:h.value,onClick:V},i(h.value?"清洗中...":"开始清洗"),9,he),s("span",ge,i(h.value?"正在处理文件并上传结果,请稍候…":"选择文件、列和 ID 规则后即可执行清洗,结果会显示在右侧供下载"),1)])]),s("section",ye,[e[14]||(e[14]=s("div",{class:"panel-header"},"去重结果",-1)),s("div",be,[s("div",we,[s("div",ke,[e[10]||(e[10]=s("span",{class:"summary-label"},"已处理文件",-1)),s("strong",null,i(g.value.total),1)]),s("div",Ce,[e[11]||(e[11]=s("span",{class:"summary-label"},"成功结果",-1)),s("strong",null,i(g.value.successCount),1)]),s("div",Ie,[e[12]||(e[12]=s("span",{class:"summary-label"},"失败文件",-1)),s("strong",null,i(g.value.failedCount),1)])]),s("div",xe,[e[13]||(e[13]=s("div",{class:"result-list-header"},[s("span",null,"处理后的 Excel 列表")],-1)),k.value.length===0?(n(),o("div",Ee," 暂无去重结果,完成数据去重后会在这里展示输出文件 ")):(n(),o("ul",De,[(n(!0),o(C,null,F(k.value,t=>(n(),o("li",{key:`${t.resultId||t.outputFilename||t.sourceFilename}`,class:"task-item"},[s("div",Fe,[s("span",{class:"id",title:t.sourceFilename},i(t.sourceFilename||"-"),9,Pe),t.outputFilename?(n(),o("div",Be,"输出文件:"+i(t.outputFilename),1)):b("",!0),t.error?(n(),o("div",Se,"错误信息:"+i(t.error),1)):b("",!0)]),s("div",Ue,[s("span",{class:I(["status",t.success?"success":"failed"])},i(t.success?"已完成":"失败"),3),t.success&&t.downloadUrl?(n(),o("button",{key:0,type:"button",class:"download",onClick:a=>T(t)}," 下载文件 ",8,Ne)):b("",!0),t.resultId?(n(),o("button",{key:1,type:"button",class:"btn-delete",onClick:a=>M(t.resultId)}," 删除 ",8,$e)):b("",!0)])]))),128))]))])])])])]))}}),He=Y(Ae,[["__scopeId","data-v-bdaefe1c"]]);Z(He).use(ee).mount("#app");
+import{d as z,o as W,a as n,c as o,b as q,B as J,e as s,F as C,r as F,t as i,f as b,n as I,w as P,v as B,g as u,h as L,i as x,E as r,j as O,k as G,l as Q,m as X,_ as Y,p as Z,q as ee}from"./pywebview-9YBa--7x.js";import{e as se}from"./brand-CRmRvyGD.js";const te={class:"page-shell module-page"},le={class:"main-content"},ae={class:"left-panel"},ne={class:"upload-zone"},oe={class:"selected-files clean-placeholder"},re={key:0,class:"more-line"},ie={key:1},ue={class:"option-group column-option-group"},ce={class:"column-toolbar"},de={class:"column-count"},ve={key:0,class:"column-grid"},pe=["value"],fe={key:1,class:"empty-column-state"},_e={class:"option-group"},me={class:"run-row"},he=["disabled"],ge={class:"loading-msg"},ye={class:"right-panel"},be={class:"task-list-wrap"},we={class:"clean-result-summary"},ke={class:"summary-card"},Ce={class:"summary-card"},Ie={class:"summary-card"},xe={class:"result-list-wrap"},Ee={key:0,class:"empty-tasks"},De={key:1,class:"task-list clean-result-list"},Fe={class:"left"},Pe=["title"],Be={key:0,class:"files"},Se={key:1,class:"files"},Ue={class:"task-right"},Ne=["onClick"],$e=["onClick"],Ae=z({__name:"BrandDedupeTab",setup(Ke){const p=u([]),c=u([]),f=u([]),w=u(""),d=u([]),S=u(!1),_=u(!0),m=u(!0),h=u(!1),k=u([]),g=u({total:0,successCount:0,failedCount:0,items:[]}),E=L(()=>f.value.slice(0,8));function $(){c.value=[...p.value]}function A(){c.value=[]}async function U(l){const e=x();if(!e?.upload_file_to_java)throw new Error("当前桌面端未提供文件上传桥接能力");const t=[];for(const a of l){const v=typeof a=="string"?a:a.absolutePath,j=typeof a=="string"?void 0:a.relativePath,y=await e.upload_file_to_java(v,j);if(!y?.success||!y.data)throw new Error(y?.error||y?.message||`上传失败:${v}`);t.push(y.data)}return t}async function N(l){const e=await X(l);if(!e.headers?.length){r.error("读取 Excel 表头失败"),p.value=[],c.value=[];return}const t=["id","ASIN","国家","价格","卖家名称"],a=[...t.filter(v=>e.headers?.includes(v)),...(e.headers||[]).filter(v=>!t.includes(v))];p.value=a,c.value=t.filter(v=>a.includes(v))}async function H(l,e){f.value=l,w.value="",d.value=await U(l),d.value.length>0&&await N(d.value[0].fileKey),r.success(e)}async function K(){const l=x();if(!l?.select_brand_xlsx_files){r.warning("当前环境不支持文件选择,请在本机客户端中打开");return}try{const e=await l.select_brand_xlsx_files();if(!e?.length)return;await H(e,`已选择 ${e.length} 个待清洗文件`)}catch(e){r.error(e instanceof Error?e.message:"选择失败")}}async function R(){const l=x();if(!l?.select_brand_folder){r.warning("当前环境不支持文件夹选择,请在本机客户端中打开");return}try{const e=await l.select_brand_folder();if(!e)return;w.value=e.split(/[/\\]/).filter(Boolean).pop()||"";const t=await se(e);if(t.success&&t.items?.length){f.value=t.items.map(a=>a.relativePath),d.value=await U(t.items),d.value.length>0&&await N(d.value[0].fileKey),r.success(`已选择文件夹内 ${t.items.length} 个 xlsx 文件`);return}r.warning(t.error||"该文件夹下没有 xlsx 文件")}catch(e){r.error(e instanceof Error?e.message:"选择失败")}}async function V(){if(!d.value.length){r.warning("请先选择待清洗 Excel 文件或文件夹");return}if(!c.value.length){r.warning("请至少选择一列保留列");return}if(!S.value&&!_.value&&!m.value){r.warning("请至少选择一种 ID 保留规则");return}try{h.value=!0;const l=await O({files:d.value.map(e=>({fileKey:e.fileKey,originalFilename:e.originalFilename,relativePath:e.relativePath})),selectedColumns:c.value,keepIntegerIds:S.value,keepUnderscoreIds:_.value,keepIntegerMainIdsWhenNoSubIds:m.value,archiveName:d.value.some(e=>!!e.relativePath)&&w.value?w.value:void 0});g.value=l,k.value=l.items||[],await D(),r.success("数据去重完成")}catch(l){r.error(l instanceof Error?l.message:"去重失败")}finally{h.value=!1}}async function D(){try{const l=await G();k.value=l.items||[],g.value={total:l.items?.length||0,successCount:l.items?.filter(e=>e.success).length||0,failedCount:l.items?.filter(e=>!e.success).length||0,items:l.items||[]}}catch{}}async function M(l){try{await Q(l),await D(),r.success("已删除")}catch(e){r.error(e instanceof Error?e.message:"删除失败")}}async function T(l){const e=x();if(!l.downloadUrl){r.warning("当前结果没有下载地址");return}const t=l.outputFilename||`${new Date().toISOString().slice(0,10).replace(/-/g,"")}_cleaned.xlsx`;if(e?.save_file_from_url_new){const a=await e.save_file_from_url_new(l.downloadUrl,t);a.success?r.success(`已保存:${a.path||t}`):a.error&&a.error!=="用户取消"&&r.error(a.error);return}}return W(()=>{D().catch(()=>{})}),(l,e)=>(n(),o("div",te,[q(J,{active:"dedupe"}),s("div",le,[s("aside",ae,[e[7]||(e[7]=s("div",{class:"section-title"},"选择文件",-1)),s("div",ne,[e[3]||(e[3]=s("div",{class:"hint"},"选择需要清洗的 Excel 文件或文件夹,后续将按所选规则输出处理结果。",-1)),s("div",{class:"btns"},[s("button",{type:"button",class:"opt-btn",onClick:K},"选择 Excel 文件"),s("button",{type:"button",class:"opt-btn",onClick:R},"选择文件夹")]),s("div",oe,[f.value.length?(n(),o(C,{key:0},[(n(!0),o(C,null,F(E.value,t=>(n(),o("span",{key:t},i(t),1))),128)),f.value.length>E.value.length?(n(),o("span",re," 还有 "+i(f.value.length-E.value.length)+" 个文件未展开显示 ",1)):b("",!0)],64)):(n(),o("span",ie,"暂未选择待清洗文件"))])]),e[8]||(e[8]=s("div",{class:"section-title"},"保留列设置",-1)),s("div",ue,[s("div",ce,[s("span",de,"已选择 "+i(c.value.length)+" / "+i(p.value.length)+" 列",1),s("div",{class:"column-actions"},[s("button",{type:"button",class:"opt-btn small",onClick:$},"全选"),s("button",{type:"button",class:"opt-btn small",onClick:A},"清空")])]),p.value.length?(n(),o("div",ve,[(n(!0),o(C,null,F(p.value,t=>(n(),o("label",{key:t,class:I(["column-item",{active:c.value.includes(t)}])},[P(s("input",{"onUpdate:modelValue":e[0]||(e[0]=a=>c.value=a),type:"checkbox",value:t},null,8,pe),[[B,c.value]]),s("span",null,i(t),1)],2))),128))])):(n(),o("div",fe,"请选择 Excel 文件后读取表头并填充可保留列")),e[4]||(e[4]=s("div",{class:"desc"},"读取首个 Excel 文件的第一行作为表头,用于动态选择需要保留的列。",-1))]),e[9]||(e[9]=s("div",{class:"section-title"},"ID 保留规则",-1)),s("div",_e,[s("label",{class:I(["radio-item",{active:_.value}])},[P(s("input",{"onUpdate:modelValue":e[1]||(e[1]=t=>_.value=t),type:"checkbox"},null,512),[[B,_.value]]),e[5]||(e[5]=s("div",null,[s("span",{class:"label"},"保留子链接"),s("div",{class:"desc"},"例如 1_1、2_3 这种带下划线的子项 ID")],-1))],2),s("label",{class:I(["radio-item",{active:m.value}])},[P(s("input",{"onUpdate:modelValue":e[2]||(e[2]=t=>m.value=t),type:"checkbox"},null,512),[[B,m.value]]),e[6]||(e[6]=s("div",null,[s("span",{class:"label"},"仅剩主链接无子链接"),s("div",{class:"desc"},"当某个主链接只有 13 这类主 ID、没有 13_1 这类对应子 ID 时,自动保留该主 ID")],-1))],2)]),s("div",me,[s("button",{type:"button",class:"btn-run",disabled:h.value,onClick:V},i(h.value?"清洗中...":"开始清洗"),9,he),s("span",ge,i(h.value?"正在处理文件并上传结果,请稍候…":"选择文件、列和 ID 规则后即可执行清洗,结果会显示在右侧供下载"),1)])]),s("section",ye,[e[14]||(e[14]=s("div",{class:"panel-header"},"去重结果",-1)),s("div",be,[s("div",we,[s("div",ke,[e[10]||(e[10]=s("span",{class:"summary-label"},"已处理文件",-1)),s("strong",null,i(g.value.total),1)]),s("div",Ce,[e[11]||(e[11]=s("span",{class:"summary-label"},"成功结果",-1)),s("strong",null,i(g.value.successCount),1)]),s("div",Ie,[e[12]||(e[12]=s("span",{class:"summary-label"},"失败文件",-1)),s("strong",null,i(g.value.failedCount),1)])]),s("div",xe,[e[13]||(e[13]=s("div",{class:"result-list-header"},[s("span",null,"处理后的 Excel 列表")],-1)),k.value.length===0?(n(),o("div",Ee," 暂无去重结果,完成数据去重后会在这里展示输出文件 ")):(n(),o("ul",De,[(n(!0),o(C,null,F(k.value,t=>(n(),o("li",{key:`${t.resultId||t.outputFilename||t.sourceFilename}`,class:"task-item"},[s("div",Fe,[s("span",{class:"id",title:t.sourceFilename},i(t.sourceFilename||"-"),9,Pe),t.outputFilename?(n(),o("div",Be,"输出文件:"+i(t.outputFilename),1)):b("",!0),t.error?(n(),o("div",Se,"错误信息:"+i(t.error),1)):b("",!0)]),s("div",Ue,[s("span",{class:I(["status",t.success?"success":"failed"])},i(t.success?"已完成":"失败"),3),t.success&&t.downloadUrl?(n(),o("button",{key:0,type:"button",class:"download",onClick:a=>T(t)}," 下载文件 ",8,Ne)):b("",!0),t.resultId?(n(),o("button",{key:1,type:"button",class:"btn-delete",onClick:a=>M(t.resultId)}," 删除 ",8,$e)):b("",!0)])]))),128))]))])])])])]))}}),He=Y(Ae,[["__scopeId","data-v-bdaefe1c"]]);Z(He).use(ee).mount("#app");
diff --git a/app/assets/delete-brand.js b/app/assets/delete-brand.js
index ffe6881..95d635d 100644
--- a/app/assets/delete-brand.js
+++ b/app/assets/delete-brand.js
@@ -1 +1 @@
-import{d as Ye,o as Ze,I as es,a as d,c as f,b as ss,B as ts,e as l,F as H,r as fe,t as v,f as h,J as ns,K as De,n as Me,g as y,h as J,L as as,i as G,E as p,M as ls,N as rs,O as os,P as us,Q as is,_ as cs,p as ds,q as fs}from"./pywebview-DuyK2jB1.js";import{e as vs}from"./brand-CKgrwtni.js";const hs={class:"page-shell module-page"},ps={class:"main-content"},_s={class:"left-panel"},ys={class:"upload-zone"},gs={class:"selected-files clean-placeholder split-selected-files"},ms={key:0,class:"more-line"},ks={key:1},ws={class:"run-row"},Is=["disabled"],bs=["disabled"],Ss={class:"loading-msg"},Ts={key:0,class:"queue-debug-card"},Cs={key:0,class:"queue-debug-line"},Es={key:1,class:"queue-debug-payload"},Fs={class:"right-panel"},Ps={class:"task-list-wrap"},Rs={class:"clean-result-summary"},xs={class:"summary-card"},Ns={class:"summary-card"},$s={class:"summary-card"},As={class:"result-list-wrap"},Ds={key:0,class:"empty-tasks"},Ms={key:0,class:"result-subsection"},Bs={class:"task-list clean-result-list"},qs={class:"left split-result-main"},Ls=["title"],Us={class:"files"},Os={class:"files"},Ks={key:0,class:"files"},Hs={key:1,class:"files"},Js={key:2,class:"files"},Gs={key:3,class:"time"},js={key:4,class:"delete-brand-progress-block"},zs={class:"delete-brand-progress-header"},Vs={class:"delete-brand-progress-bar"},Qs={class:"files delete-brand-progress"},Xs={key:5,class:"files split-entry-list delete-brand-preview"},Ws={key:6,class:"files"},Ys={class:"task-right split-result-actions"},Zs=["onClick"],et=["onClick"],st={key:1,class:"result-subsection"},tt={class:"task-list clean-result-list"},nt={class:"left split-result-main"},at=["title"],lt={class:"files"},rt={class:"files"},ot={key:0,class:"files"},ut={key:1,class:"files"},it={key:2,class:"time"},ct={key:3,class:"delete-brand-progress-block"},dt={class:"delete-brand-progress-header"},ft={class:"delete-brand-progress-bar"},vt={class:"files delete-brand-progress"},ht={key:4,class:"files split-entry-list delete-brand-preview"},pt={key:5,class:"files"},_t={class:"task-right split-result-actions"},yt=["onClick"],gt=["onClick"],mt=Ye({__name:"BrandDeleteBrandTab",setup(wt){function $(){return`delete-brand:current-tasks:${typeof window<"u"&&window.localStorage.getItem("uid")||"0"}`}const A=y([]),j=y([]),B=y(!1),ve=y([]),i=y([]),q=y([]),z=y({total:0,successCount:0,failedCount:0,items:[]}),b=y(""),S=y(""),g=y({}),T=y(null),ee=y(!1),V=y(!1),E=y(!1),D=y(""),Q=y(!1),L=y(null),se=y(!1),te=J(()=>A.value.slice(0,10)),F=J(()=>{const e=[];return i.value.forEach(s=>{e.push(...s.items)}),e});function Be(){try{const e=typeof window<"u"?window.localStorage.getItem($()):null;if(e){if(i.value=JSON.parse(e),i.value.length>0){const s=i.value[i.value.length-1];b.value=s.queuePushResult||"",S.value=s.queuePayloadText||""}}else i.value=[]}catch{i.value=[]}}function ne(e,s,t,n){if(!s?.length)return;const a=i.value.findIndex(c=>c.taskId===e),o=a>=0?i.value[a]:null,r=s.map(c=>{const _=o?.items.find(We=>M(We,c)),k=Array.isArray(c.countries)?c.countries:void 0,m=Array.isArray(_?.countries)?_?.countries:void 0,w=k&&k.length>0?k:m&&m.length>0?m:k??m,I=Array.isArray(c.previewRows)?c.previewRows:void 0,C=Array.isArray(_?.previewRows)?_?.previewRows:void 0,de=I&&I.length>0?I:C&&C.length>0?C:I??C;return{...c,countries:w,previewRows:de,_pushed:_?._pushed??c._pushed??!1,_completed:_?._completed??c._completed??!1}}),u={taskId:e,items:r,createdAt:o?.createdAt||Date.now(),queuePushResult:t??o?.queuePushResult,queuePayloadText:n??o?.queuePayloadText};a>=0?i.value[a]=u:i.value.push(u),typeof window<"u"&&window.localStorage.setItem($(),JSON.stringify(i.value))}function he(e){const s=i.value.length;i.value=i.value.filter(t=>t.taskId!==e),i.value.length!==s&&typeof window<"u"&&window.localStorage.setItem($(),JSON.stringify(i.value))}function pe(e){return e==="SUCCESS"||e==="FAILED"||e==="COMPLETED"}function qe(e){if(!e?.length)return!1;const s=new Set;if(e.forEach(n=>{n.taskId&&pe(n.taskStatus)&&s.add(n.taskId)}),!s.size)return!1;const t=i.value.length;return i.value=i.value.filter(n=>!s.has(n.taskId)),i.value.length!==t?(ae(),!0):!1}const X=J(()=>q.value.filter(e=>!F.value.some(s=>M(s,e)))),Le=J(()=>F.value.length>0||X.value.length>0),Ue=J(()=>F.value.filter(e=>xe(e)&&P(e)==="未入队"));function ae(){typeof window<"u"&&(i.value.length===0?window.localStorage.removeItem($()):window.localStorage.setItem($(),JSON.stringify(i.value)))}function R(e){return e?(i.value.find(t=>t.taskId===e)?.items.length||0)>1:!1}function le(e){return e&&i.value.find(s=>s.taskId===e)||null}function M(e,s){return!e||!s?!1:e.resultId!=null&&s.resultId!=null?e.resultId===s.resultId:e.fileKey&&s.fileKey?e.fileKey===s.fileKey:e.sourceFilename===s.sourceFilename}function x(e,s){const t=le(e);return t&&t.items.find(n=>M(n,s))||null}function _e(e){if(!e)return!1;const s=le(e);if(!s)return!1;let t=!1;const n=g.value[e]?.line_progress?.info,a=R(e);if(n?.file_name&&n.current_line!=null&&n.total_lines!=null&&n.total_lines>0&&n.current_line>=n.total_lines){const o=s.items.find(r=>r.sourceFilename===n.file_name);o&&!o._completed&&(o._completed=!0,t=!0)}if(!a){const o=g.value[e]?.task?.status;if((o==="SUCCESS"||o==="FAILED")&&s.items.length===1){const r=s.items[0];r&&!r._completed&&(r._completed=!0,t=!0)}}return t&&ae(),t}function ye(e){return e&&g.value[e]?.task?.status||""}function W(e){if(e.error)return e.error;if(e.matchStatus&&e.matchStatus!=="MATCHED")return e.matchMessage||"";const s=e.taskId;return s&&g.value[s]?.task?.errorMessage||""}function ge(e){const s=e.taskId;if(!s)return!1;const t=g.value[s]?.line_progress,n=ye(s),a=R(s),o=P(e);if(a){const r=x(s,e),u=K(e)===D.value,c=!!(t?.has_progress&&t?.info?.file_name===e.sourceFilename);return u||c||r?._pushed||(n==="RUNNING"||n==="PENDING"||!n)&&r?._completed?!0:{success:!1,retryable:!0}}return o==="本文件解析完毕"||o==="已被全局判定为终态"?!1:t?.has_progress||t?.info?!0:o==="已入队"||o==="处理中"}function me(e){return e.map(s=>s.matchStatus==="MATCHED"?{...s,_pushed:!1,_completed:!1}:{...s,matched:!1,openStoreUrl:void 0,error:s.error||void 0,_pushed:!1,_completed:!1})}function ke(e){return e.matchStatus==="MATCHED"?`已匹配 ${e.shopId||""}`.trim():e.matchMessage?e.matchMessage:e.matchStatus==="CONFLICT"?"存在多个同名店铺,请人工确认":e.matchStatus==="PENDING"||e.matchStatus==="INDEX_STALE"?"店铺索引暂未就绪":e.matchStatus?e.matchStatus:e.matched?`已匹配 ${e.shopId||""}`.trim():"待匹配"}function Oe(){ve.value=[...F.value,...X.value]}function N(){Oe(),Ke(ve.value)}function Ke(e){z.value={total:e.length,successCount:e.filter(s=>s.success).length,failedCount:e.filter(s=>!s.success).length,items:e}}function re(e){const s=e.taskId;return s&&(g.value[s]?.fileProgress||[]).find(a=>a.fileKey&&e.fileKey?a.fileKey===e.fileKey:a.sourceFilename&&e.sourceFilename?a.sourceFilename===e.sourceFilename:!1)||null}function we(e){const s=e.taskId;if(!s)return"";const t=g.value[s],n=t?.line_progress?.info,a=re(e);if(R(s)){const u=x(s,e),c=t?.task?.status,_=!!(n?.file_name&&n.file_name===e.sourceFilename);let k=u?._completed?"COMPLETED":(u?._pushed,"PENDING"),m=0,w=Math.max(0,e.totalRows??a?.totalRows??0);c==="SUCCESS"||c==="FAILED"&&u?._completed?(k="COMPLETED",m=w):_?(k="RUNNING",m=Math.max(0,Math.min(w||Number.MAX_SAFE_INTEGER,n?.current_line??0)),!w&&n?.total_lines&&n.total_lines>0&&(w=n.total_lines)):u?._completed?(k="COMPLETED",m=w):u?._pushed&&(k="PENDING",m=0);const I=[];if(I.push(`文件状态:${k}`),I.push(`文件进度:${m}/${w}`),n?.finished_files!=null){const C=n.file_total&&n.file_total>0?`/${n.file_total}`:"";I.push(`已完成文件:${n.finished_files}${C}`)}return n?.phase&&I.push(`阶段:${n.phase}`),I.join("|")}if(a){const u=[],c=a.processedRows??0,_=a.totalRows??0;if(a.status&&u.push(`文件状态:${a.status}`),u.push(`文件进度:${c}/${_}`),n?.finished_files!=null){const k=n.file_total&&n.file_total>0?`/${n.file_total}`:"";u.push(`已完成文件:${n.finished_files}${k}`)}return n?.phase&&u.push(`阶段:${n.phase}`),u.join("|")}if(!t?.line_progress?.has_progress||!n){const u=t?.task?.status;return u?`任务状态:${u}`:""}const r=[];if(n.phase&&r.push(`阶段:${n.phase}`),n.file_index&&n.file_total&&r.push(`文件:${n.file_index}/${n.file_total}`),n.file_name&&r.push(`当前文件:${n.file_name}`),n.current_line!=null&&n.total_lines!=null&&r.push(`${R(s)?"当前文件进度":"进度"}:${n.current_line}/${n.total_lines}`),n.current_country&&r.push(`国家:${n.current_country}`),n.current_asin&&r.push(`ASIN:${n.current_asin}`),n.finished_files!=null){const u=n.file_total&&n.file_total>0?`/${n.file_total}`:"";r.push(`已完成文件:${n.finished_files}${u}`)}return r.join("|")}function Y(e){const s=e.taskId;if(!s)return 0;const t=g.value[s]?.line_progress?.info;if(R(s)){const r=x(s,e);if(g.value[s]?.task?.status==="SUCCESS"||r?._completed)return 100;const c=Math.max(0,e.totalRows??re(e)?.totalRows??0);if(!!(t?.file_name&&t.file_name===e.sourceFilename)){if(t?.current_line!=null&&t?.total_lines&&t.total_lines>0)return Math.max(0,Math.min(100,Math.round(t.current_line/t.total_lines*100)));if(t?.current_line!=null&&c>0)return Math.max(0,Math.min(100,Math.round(t.current_line/c*100)))}return 0}const a=re(e);return a?Math.max(0,Math.min(100,a.percent??0)):t?t.current_line!=null&&t.total_lines&&t.total_lines>0?Math.max(0,Math.min(100,Math.round(t.current_line/t.total_lines*100))):t.finished_files!=null&&t.file_total&&t.file_total>0?Math.max(0,Math.min(100,Math.round(t.finished_files/t.file_total*100))):g.value[s]?.task?.status==="SUCCESS"?100:0:0}function Ie(e){const s=g.value[e]?.task?.status;return pe(s)}function U(){const e=new Set;return i.value.forEach(s=>{s.items.some(t=>t._pushed)&&e.add(s.taskId)}),Array.from(e)}function P(e){const s=e.taskId;if(!s||!le(s))return"";const n=x(s,e),a=ye(s),o=g.value[s]?.line_progress,r=R(s);if(!r&&(a==="SUCCESS"||a==="FAILED"||e.taskStatus==="SUCCESS"||e.taskStatus==="FAILED"))return"已被全局判定为终态";if(n?._completed)return"本文件解析完毕";if(a==="RUNNING"&&o?.has_progress&&o?.info?.file_name===e.sourceFilename)return"处理中";if(r&&!n?._pushed&&!n?._completed)return"未入队";if(r&&a==="SUCCESS")return"本文件解析完毕";if(!r){const u=g.value[s];if(u?.items&&u.items.find(_=>M(_,e))&&a==="SUCCESS")return"本文件解析完毕"}return n?._pushed?"已入队":"未入队"}function be(e){if(e.downloadUrl)return!0;const s=e.taskId;return s?g.value[s]?.task?.status==="SUCCESS":{success:!1,retryable:!1}}function He(e){const s=e?.task?.id;if(!s)return!1;const t=i.value.find(r=>r.taskId===s);if(!t)return{success:!1,retryable:!1};const n=e?.items||[];let a=!1;const o=t.items.map(r=>{const u=n.find(de=>M(de,r));if(!u)return r;const c=Array.isArray(u.countries)?u.countries:void 0,_=Array.isArray(r.countries)?r.countries:void 0,k=c&&c.length>0?c:_&&_.length>0?_:c??_,m=Array.isArray(u.previewRows)?u.previewRows:void 0,w=Array.isArray(r.previewRows)?r.previewRows:void 0,I=m&&m.length>0?m:w&&w.length>0?w:m??w,C={...r,...u,countries:k,previewRows:I,_pushed:r._pushed,_completed:r._completed};return JSON.stringify(C)!==JSON.stringify(r)&&(a=!0),C});for(const r of n)o.some(u=>M(u,r))||(o.push({...r,_pushed:!1,_completed:!1}),a=!0);return JSON.stringify(o)!==JSON.stringify(t.items)&&(t.items=o,a=!0),a&&ae(),a}async function oe(e){const s=e||U();if(s.length)try{const t=await as(s);se.value&&E.value&&(b.value="后端已恢复,继续执行当前任务并自动衔接后续任务..."),se.value=!1;let n=!1;for(const a of t.items||[]){const o=a?.task?.id;typeof o=="number"&&o>0&&(g.value[o]=a,He(a)&&(n=!0),_e(o)&&(n=!0))}n&&N()}catch(t){const n=(t instanceof Error?t.message:String(t||"")).toLowerCase();["无法连接到后端服务","bad gateway","gateway timeout","network error","timeout","502","503","504"].some(o=>n.includes(o.toLowerCase()))&&E.value&&(se.value=!0,b.value="当前任务运行中,正在等待后端服务恢复...")}}function Je(){return is()}function ue(e=!1){if(T.value){if(!e)return;clearTimeout(T.value),T.value=null}const s=async()=>{if(T.value=null,ee.value){ue();return}const t=U();if(!t.length)return;ee.value=!0;let n=!1,a=!1;try{await oe(t),await Ae();for(const o of t)Ie(o)&&(he(o),n=!0,a=!0)}finally{ee.value=!1}a?await O():n&&N(),U().length>0&&ue()};e?s():T.value=window.setTimeout(s,Je())}function ie(e=!1){T.value&&!e||ue(e)}function Z(e){const s=e.taskId?g.value[e.taskId]?.task?.status:null;return s==="SUCCESS"?{className:"success",text:"已完成"}:s==="FAILED"?{className:"failed",text:"失败"}:s==="RUNNING"?{className:"running",text:"执行中"}:e.taskStatus==="SUCCESS"?{className:"success",text:"已完成"}:e.taskStatus==="FAILED"?{className:"failed",text:"失败"}:e.taskStatus==="RUNNING"?{className:"running",text:"执行中"}:e.success?{className:"success",text:"已完成"}:{className:"failed",text:"失败"}}function Se(){T.value&&(window.clearTimeout(T.value),T.value=null),ce()}async function Te(e){const s=G();if(!s?.upload_file_to_java)throw new Error("当前桌面端未提供文件上传桥接能力");const t=[];for(const n of e){const a=typeof n=="string"?n:n.absolutePath,o=typeof n=="string"?void 0:n.relativePath,r=await s.upload_file_to_java(a,o);if(!r?.success||!r.data)throw new Error(r?.error||r?.message||`上传失败:${a}`);t.push(r.data)}return t}async function Ge(){const e=G();if(!e?.select_brand_xlsx_files){p.warning("当前环境不支持文件选择,请在本机客户端中打开");return}try{const s=await e.select_brand_xlsx_files();if(!s?.length)return;A.value=s,j.value=await Te(s),p.success(`已选择 ${s.length} 个删除品牌文件`)}catch(s){p.error(s instanceof Error?s.message:"选择失败")}}async function je(){const e=G();if(!e?.select_brand_folder){p.warning("当前环境不支持文件夹选择,请在本机客户端中打开");return}try{const s=await e.select_brand_folder();if(!s)return;const t=await vs(s);if(!t.success||!t.items?.length){p.warning(t.error||"该文件夹下没有 xlsx 文件");return}A.value=t.items.map(n=>n.relativePath||n.absolutePath),j.value=await Te(t.items),p.success(`已选择文件夹内 ${t.items.length} 个 xlsx 文件`)}catch(s){p.error(s instanceof Error?s.message:"选择失败")}}async function ze(){if(!j.value.length){p.warning("请先选择待处理文件");return}try{B.value=!0;const e=await ls({files:j.value.map(a=>({fileKey:a.fileKey,originalFilename:a.originalFilename,relativePath:a.relativePath}))}),s=me(e.items||[]),t=s.some(a=>a.matchStatus==="MATCHED"),n=s.some(a=>a.matchStatus&&a.matchStatus!=="MATCHED");b.value=t?"解析完成,可在左侧点击“推送到 Python 队列”开始串行处理":"解析完成,当前没有可推送的已匹配文件",S.value="",s.length&&s[0].taskId&&ne(s[0].taskId,s,b.value,S.value),N(),n?p.warning("部分文件尚未命中可用店铺索引,已保留状态信息,请等待后台刷新后重试。"):t?p.success("删除品牌解析完成,请在左侧推送到 Python 队列"):p.warning("当前没有可推送的已匹配文件,请先等待店铺索引刷新。"),await O()}catch(e){p.error(e instanceof Error?e.message:"执行失败")}finally{B.value=!1}}async function Ce(e){const s=e.taskId;if(!s){p.warning("未找到任务ID");return}const t=G(),n=rs(s),a=e.outputFilename||e.sourceFilename||`delete-brand_${s}.xlsx`;if(t?.save_file_from_url_new){const o=await t.save_file_from_url_new(n,a);o.success?p.success(`已保存:${o.path||a}`):o.error&&o.error!=="用户取消"&&p.error(o.error);return}}async function O(){try{const e=await os();q.value=me(e.items||[]),qe(q.value),N(),await oe(),ie(!0)}catch(e){const s=(e instanceof Error?e.message:String(e||"")).toLowerCase();["无法连接到后端服务","bad gateway","gateway timeout","network error","timeout","502","503","504"].some(n=>s.includes(n.toLowerCase()))||console.error("loadHistory error:",e)}}function Ee(e){i.value.forEach(t=>{t.items=t.items.filter(n=>n.resultId!==e)});const s=i.value.length;i.value=i.value.filter(t=>t.items.length>0),i.value.length!==s&&typeof window<"u"&&window.localStorage.setItem($(),JSON.stringify(i.value)),q.value=q.value.filter(t=>t.resultId!==e),N(),U().length===0&&Se()}async function Fe(e){try{await us(e),Ee(e),await O(),p.success("已删除")}catch(s){const t=s instanceof Error?s.message:"删除失败";if(typeof t=="string"&&t.includes("记录不存在")){Ee(e),p.success("已删除");return}p.error(t)}}function Pe(e){const s=e.slice(0,5).map(n=>`${n.country}:${n.asin}${n.status?`(${n.status})`:""}`);return e.length-s.length>0?`${s.join("、")} 等 ${e.length} 条`:s.join("、")}function It(e,s){}function ce(){L.value&&(window.clearTimeout(L.value),L.value=null)}function Ve(e=3e3){ce(),L.value=window.setTimeout(()=>{L.value=null,Ae().catch(()=>{})},e)}function Qe(e){return e.includes("当前店铺正在执行中")}function Re(){for(const e of i.value)for(const s of e.items){const t=P(s);if(t==="已入队"||t==="处理中")return!0}return!1}function K(e){return e.resultId!=null?`result:${e.resultId}`:`task:${e.taskId||0}:${e.sourceFilename||""}`}function xe(e){return!!(e.taskId&&(e.matchStatus==="MATCHED"||e.matched))}function Ne(){return F.value.find(e=>!xe(e)||K(e)===D.value||x(e.taskId,e)?._completed?!1:P(e)==="未入队")}async function $e(e,s){if(s?.auto&&Re())return e.taskId,e.sourceFilename,{success:!1,retryable:!0};const t=G(),n=e.taskId;if(!n)return{success:!1,retryable:!1};const a=i.value.find(c=>c.taskId===n);if(!a)return{success:!1,retryable:!1};if(!t?.enqueue_json)return p.error("当前环境未启用 pywebview enqueue_json(浏览器环境不会推送)"),{success:!1,retryable:!1};const o={type:"delete-brand-run",ts:Date.now(),data:{taskId:n,items:[e]}};S.value=JSON.stringify(o,null,2);const r=await t.enqueue_json(o);let u="";if(r?.success){u=`已推送文件 ${e.sourceFilename},当前队列长度:${r.queue_size??"-"}`;const c=x(n,e);return c&&(c._pushed=!0,c._completed=!1),D.value=K(e),ne(n,a.items,u,S.value),ie(!0),b.value=u,{success:!0,retryable:!1}}else{const c=r?.error||"未知错误";if(u=`推送到 Python 队列失败:${c}`,b.value=u,ne(n,a.items,u,S.value),s?.auto&&Qe(c))return{success:!1,retryable:!0}}return b.value=u,{success:!1,retryable:!1}}async function Xe(){const e=Ne();if(!e){p.warning("当前没有可推送的已匹配文件");return}V.value=!0,E.value=!0,ce();const s=await $e(e);if(V.value=!1,!s.success){E.value=!1,D.value="";return}p.success("已开始按顺序推送到 Python 队列")}async function Ae(){if(!E.value||Q.value){E.value,Q.value;return}if(Re())return;const e=D.value;if(e){const t=F.value.find(n=>K(n)===e);if(t){const n=t.taskId,a=R(n);a&&(_e(n),P(t),void 0);const o=P(t);if(o==="已入队"||o==="处理中"){t.sourceFilename;return}if(a){const r=x(n,t);if(r&&!r._completed){t.sourceFilename,r._pushed,r._completed;return}}}}const s=Ne();if(!s){D.value="",E.value=!1;return}Q.value=!0;try{s.taskId,s.sourceFilename,K(s);const t=await $e(s,{auto:!0});if(!t.success){if(t.retryable){s.taskId,s.sourceFilename,Ve();return}s.taskId,s.sourceFilename,E.value=!1}}finally{Q.value=!1}}return Ze(()=>{Be();const e=U();e.length>0?oe(e).then(async()=>{let s=!1,t=!1;for(const n of e)Ie(n)&&(he(n),s=!0,t=!0);t?await O():s&&N(),ie(!0)}):N(),O().catch(()=>{})}),es(()=>{Se()}),(e,s)=>(d(),f("div",hs,[ss(ts,{active:"delete-brand"}),l("div",ps,[l("aside",_s,[s[2]||(s[2]=l("div",{class:"section-title"},"选择文件",-1)),l("div",ys,[s[0]||(s[0]=l("div",{class:"hint"},"上传删除品牌 Excel;无论是直接上传文件还是上传文件夹批量导入,都会按每个 Excel 文件名作为店铺名。",-1)),l("div",{class:"btns"},[l("button",{type:"button",class:"opt-btn",onClick:Ge},"选择 Excel 文件"),l("button",{type:"button",class:"opt-btn",onClick:je},"选择文件夹")]),l("div",gs,[A.value.length?(d(),f(H,{key:0},[(d(!0),f(H,null,fe(te.value,t=>(d(),f("span",{key:t},v(t),1))),128)),A.value.length>te.value.length?(d(),f("span",ms," 还有 "+v(A.value.length-te.value.length)+" 个文件未展开显示 ",1)):h("",!0)],64)):(d(),f("span",ks,"暂未选择删除品牌文件"))])]),s[3]||(s[3]=ns('执行说明
',2)),l("div",ws,[l("button",{type:"button",class:"btn-run",disabled:B.value,onClick:ze},v(B.value?"解析中...":"开始解析"),9,Is),l("button",{type:"button",class:"btn-run btn-queue",disabled:V.value||!Ue.value.length,onClick:Xe},v(V.value?"推送中...":"推送到 Python 队列"),9,bs),l("span",Ss,v(B.value?"正在读取删除品牌数据,请稍候…":"解析后会在右侧展示按国家分组的去重结果"),1)]),b.value||S.value?(d(),f("div",Ts,[s[1]||(s[1]=l("div",{class:"section-title queue-debug-title"},"Python 队列推送结果",-1)),b.value?(d(),f("div",Cs,v(b.value),1)):h("",!0),S.value?(d(),f("pre",Es,v(S.value),1)):h("",!0)])):h("",!0)]),l("section",Fs,[s[12]||(s[12]=l("div",{class:"panel-header"},"删除品牌结果",-1)),l("div",Ps,[l("div",Rs,[l("div",xs,[s[4]||(s[4]=l("span",{class:"summary-label"},"已处理文件",-1)),l("strong",null,v(z.value.total),1)]),l("div",Ns,[s[5]||(s[5]=l("span",{class:"summary-label"},"成功结果",-1)),l("strong",null,v(z.value.successCount),1)]),l("div",$s,[s[6]||(s[6]=l("span",{class:"summary-label"},"失败文件",-1)),l("strong",null,v(z.value.failedCount),1)])]),l("div",As,[s[11]||(s[11]=l("div",{class:"result-list-header"},[l("span",null,"删除品牌结果列表")],-1)),Le.value?(d(),f(H,{key:1},[F.value.length?(d(),f("div",Ms,[s[8]||(s[8]=l("div",{class:"result-subsection-title"},"当前任务",-1)),l("ul",Bs,[(d(!0),f(H,null,fe(F.value,t=>(d(),f("li",{key:`current-${t.taskId||t.resultId||t.sourceFilename}`,class:"task-item split-result-item"},[l("div",qs,[l("span",{class:"id",title:t.sourceFilename},v(t.sourceFilename||"-"),9,Ls),l("div",Us,"店铺名:"+v(t.shopName||"-"),1),l("div",Os,"匹配结果:"+v(ke(t)),1),t.platform?(d(),f("div",Ks,"平台:"+v(t.platform),1)):h("",!0),P(t)?(d(),f("div",Hs,"队列状态:"+v(P(t)),1)):h("",!0),t.countryCount!==void 0&&t.matched?(d(),f("div",Js,"国家数:"+v(t.countryCount),1)):h("",!0),t.totalRows!==void 0&&t.matched?(d(),f("div",Gs,"去重后 "+v(t.totalRows)+" 条 ",1)):h("",!0),ge(t)?(d(),f("div",js,[l("div",zs,[s[7]||(s[7]=l("span",null,"任务进度",-1)),l("span",null,v(Y(t))+"%",1)]),l("div",Vs,[l("div",{class:"delete-brand-progress-bar-fill",style:De({width:`${Y(t)}%`})},null,4)]),l("div",Qs,v(we(t)),1)])):h("",!0),t.previewRows?.length?(d(),f("div",Xs,v(Pe(t.previewRows)),1)):h("",!0),W(t)?(d(),f("div",Ws,"错误信息:"+v(W(t)),1)):h("",!0)]),l("div",Ys,[l("span",{class:Me(["status",Z(t).className])},v(Z(t).text),3),be(t)?(d(),f("button",{key:0,type:"button",class:"download",onClick:n=>Ce(t)}," 下载 ",8,Zs)):h("",!0),t.resultId?(d(),f("button",{key:1,type:"button",class:"btn-delete",onClick:n=>Fe(t.resultId)}," 删除 ",8,et)):h("",!0)])]))),128))])])):h("",!0),X.value.length?(d(),f("div",st,[s[10]||(s[10]=l("div",{class:"result-subsection-title"},"历史记录",-1)),l("ul",tt,[(d(!0),f(H,null,fe(X.value,t=>(d(),f("li",{key:`history-${t.taskId||t.resultId||t.sourceFilename}`,class:"task-item split-result-item"},[l("div",nt,[l("span",{class:"id",title:t.sourceFilename},v(t.sourceFilename||"-"),9,at),l("div",lt,"店铺名:"+v(t.shopName||"-"),1),l("div",rt,"匹配结果:"+v(ke(t)),1),t.platform?(d(),f("div",ot,"平台:"+v(t.platform),1)):h("",!0),t.countryCount!==void 0&&t.matched?(d(),f("div",ut,"国家数:"+v(t.countryCount),1)):h("",!0),t.totalRows!==void 0&&t.matched?(d(),f("div",it,"去重后 "+v(t.totalRows)+" 条 ",1)):h("",!0),ge(t)?(d(),f("div",ct,[l("div",dt,[s[9]||(s[9]=l("span",null,"任务进度",-1)),l("span",null,v(Y(t))+"%",1)]),l("div",ft,[l("div",{class:"delete-brand-progress-bar-fill",style:De({width:`${Y(t)}%`})},null,4)]),l("div",vt,v(we(t)),1)])):h("",!0),t.previewRows?.length?(d(),f("div",ht,v(Pe(t.previewRows)),1)):h("",!0),W(t)?(d(),f("div",pt,"错误信息:"+v(W(t)),1)):h("",!0)]),l("div",_t,[l("span",{class:Me(["status",Z(t).className])},v(Z(t).text),3),be(t)?(d(),f("button",{key:0,type:"button",class:"download",onClick:n=>Ce(t)}," 下载 ",8,yt)):h("",!0),t.resultId?(d(),f("button",{key:1,type:"button",class:"btn-delete",onClick:n=>Fe(t.resultId)}," 删除 ",8,gt)):h("",!0)])]))),128))])])):h("",!0)],64)):(d(),f("div",Ds," 暂无删除品牌结果,完成解析后会在这里展示按国家分组的去重结果 "))])])])])]))}}),kt=cs(mt,[["__scopeId","data-v-64ee024e"]]);ds(kt).use(fs).mount("#app");
+import{d as Ye,o as Ze,I as es,a as d,c as f,b as ss,B as ts,e as l,F as H,r as fe,t as v,f as h,J as ns,K as De,n as Me,g as y,h as J,L as as,i as G,E as p,M as ls,N as rs,O as os,P as us,Q as is,_ as cs,p as ds,q as fs}from"./pywebview-9YBa--7x.js";import{e as vs}from"./brand-CRmRvyGD.js";const hs={class:"page-shell module-page"},ps={class:"main-content"},_s={class:"left-panel"},ys={class:"upload-zone"},gs={class:"selected-files clean-placeholder split-selected-files"},ms={key:0,class:"more-line"},ks={key:1},ws={class:"run-row"},Is=["disabled"],bs=["disabled"],Ss={class:"loading-msg"},Ts={key:0,class:"queue-debug-card"},Cs={key:0,class:"queue-debug-line"},Es={key:1,class:"queue-debug-payload"},Fs={class:"right-panel"},Ps={class:"task-list-wrap"},Rs={class:"clean-result-summary"},xs={class:"summary-card"},Ns={class:"summary-card"},$s={class:"summary-card"},As={class:"result-list-wrap"},Ds={key:0,class:"empty-tasks"},Ms={key:0,class:"result-subsection"},Bs={class:"task-list clean-result-list"},qs={class:"left split-result-main"},Ls=["title"],Us={class:"files"},Os={class:"files"},Ks={key:0,class:"files"},Hs={key:1,class:"files"},Js={key:2,class:"files"},Gs={key:3,class:"time"},js={key:4,class:"delete-brand-progress-block"},zs={class:"delete-brand-progress-header"},Vs={class:"delete-brand-progress-bar"},Qs={class:"files delete-brand-progress"},Xs={key:5,class:"files split-entry-list delete-brand-preview"},Ws={key:6,class:"files"},Ys={class:"task-right split-result-actions"},Zs=["onClick"],et=["onClick"],st={key:1,class:"result-subsection"},tt={class:"task-list clean-result-list"},nt={class:"left split-result-main"},at=["title"],lt={class:"files"},rt={class:"files"},ot={key:0,class:"files"},ut={key:1,class:"files"},it={key:2,class:"time"},ct={key:3,class:"delete-brand-progress-block"},dt={class:"delete-brand-progress-header"},ft={class:"delete-brand-progress-bar"},vt={class:"files delete-brand-progress"},ht={key:4,class:"files split-entry-list delete-brand-preview"},pt={key:5,class:"files"},_t={class:"task-right split-result-actions"},yt=["onClick"],gt=["onClick"],mt=Ye({__name:"BrandDeleteBrandTab",setup(wt){function $(){return`delete-brand:current-tasks:${typeof window<"u"&&window.localStorage.getItem("uid")||"0"}`}const A=y([]),j=y([]),B=y(!1),ve=y([]),i=y([]),q=y([]),z=y({total:0,successCount:0,failedCount:0,items:[]}),b=y(""),S=y(""),g=y({}),T=y(null),ee=y(!1),V=y(!1),E=y(!1),D=y(""),Q=y(!1),L=y(null),se=y(!1),te=J(()=>A.value.slice(0,10)),F=J(()=>{const e=[];return i.value.forEach(s=>{e.push(...s.items)}),e});function Be(){try{const e=typeof window<"u"?window.localStorage.getItem($()):null;if(e){if(i.value=JSON.parse(e),i.value.length>0){const s=i.value[i.value.length-1];b.value=s.queuePushResult||"",S.value=s.queuePayloadText||""}}else i.value=[]}catch{i.value=[]}}function ne(e,s,t,n){if(!s?.length)return;const a=i.value.findIndex(c=>c.taskId===e),o=a>=0?i.value[a]:null,r=s.map(c=>{const _=o?.items.find(We=>M(We,c)),k=Array.isArray(c.countries)?c.countries:void 0,m=Array.isArray(_?.countries)?_?.countries:void 0,w=k&&k.length>0?k:m&&m.length>0?m:k??m,I=Array.isArray(c.previewRows)?c.previewRows:void 0,C=Array.isArray(_?.previewRows)?_?.previewRows:void 0,de=I&&I.length>0?I:C&&C.length>0?C:I??C;return{...c,countries:w,previewRows:de,_pushed:_?._pushed??c._pushed??!1,_completed:_?._completed??c._completed??!1}}),u={taskId:e,items:r,createdAt:o?.createdAt||Date.now(),queuePushResult:t??o?.queuePushResult,queuePayloadText:n??o?.queuePayloadText};a>=0?i.value[a]=u:i.value.push(u),typeof window<"u"&&window.localStorage.setItem($(),JSON.stringify(i.value))}function he(e){const s=i.value.length;i.value=i.value.filter(t=>t.taskId!==e),i.value.length!==s&&typeof window<"u"&&window.localStorage.setItem($(),JSON.stringify(i.value))}function pe(e){return e==="SUCCESS"||e==="FAILED"||e==="COMPLETED"}function qe(e){if(!e?.length)return!1;const s=new Set;if(e.forEach(n=>{n.taskId&&pe(n.taskStatus)&&s.add(n.taskId)}),!s.size)return!1;const t=i.value.length;return i.value=i.value.filter(n=>!s.has(n.taskId)),i.value.length!==t?(ae(),!0):!1}const X=J(()=>q.value.filter(e=>!F.value.some(s=>M(s,e)))),Le=J(()=>F.value.length>0||X.value.length>0),Ue=J(()=>F.value.filter(e=>xe(e)&&P(e)==="未入队"));function ae(){typeof window<"u"&&(i.value.length===0?window.localStorage.removeItem($()):window.localStorage.setItem($(),JSON.stringify(i.value)))}function R(e){return e?(i.value.find(t=>t.taskId===e)?.items.length||0)>1:!1}function le(e){return e&&i.value.find(s=>s.taskId===e)||null}function M(e,s){return!e||!s?!1:e.resultId!=null&&s.resultId!=null?e.resultId===s.resultId:e.fileKey&&s.fileKey?e.fileKey===s.fileKey:e.sourceFilename===s.sourceFilename}function x(e,s){const t=le(e);return t&&t.items.find(n=>M(n,s))||null}function _e(e){if(!e)return!1;const s=le(e);if(!s)return!1;let t=!1;const n=g.value[e]?.line_progress?.info,a=R(e);if(n?.file_name&&n.current_line!=null&&n.total_lines!=null&&n.total_lines>0&&n.current_line>=n.total_lines){const o=s.items.find(r=>r.sourceFilename===n.file_name);o&&!o._completed&&(o._completed=!0,t=!0)}if(!a){const o=g.value[e]?.task?.status;if((o==="SUCCESS"||o==="FAILED")&&s.items.length===1){const r=s.items[0];r&&!r._completed&&(r._completed=!0,t=!0)}}return t&&ae(),t}function ye(e){return e&&g.value[e]?.task?.status||""}function W(e){if(e.error)return e.error;if(e.matchStatus&&e.matchStatus!=="MATCHED")return e.matchMessage||"";const s=e.taskId;return s&&g.value[s]?.task?.errorMessage||""}function ge(e){const s=e.taskId;if(!s)return!1;const t=g.value[s]?.line_progress,n=ye(s),a=R(s),o=P(e);if(a){const r=x(s,e),u=K(e)===D.value,c=!!(t?.has_progress&&t?.info?.file_name===e.sourceFilename);return u||c||r?._pushed||(n==="RUNNING"||n==="PENDING"||!n)&&r?._completed?!0:{success:!1,retryable:!0}}return o==="本文件解析完毕"||o==="已被全局判定为终态"?!1:t?.has_progress||t?.info?!0:o==="已入队"||o==="处理中"}function me(e){return e.map(s=>s.matchStatus==="MATCHED"?{...s,_pushed:!1,_completed:!1}:{...s,matched:!1,openStoreUrl:void 0,error:s.error||void 0,_pushed:!1,_completed:!1})}function ke(e){return e.matchStatus==="MATCHED"?`已匹配 ${e.shopId||""}`.trim():e.matchMessage?e.matchMessage:e.matchStatus==="CONFLICT"?"存在多个同名店铺,请人工确认":e.matchStatus==="PENDING"||e.matchStatus==="INDEX_STALE"?"店铺索引暂未就绪":e.matchStatus?e.matchStatus:e.matched?`已匹配 ${e.shopId||""}`.trim():"待匹配"}function Oe(){ve.value=[...F.value,...X.value]}function N(){Oe(),Ke(ve.value)}function Ke(e){z.value={total:e.length,successCount:e.filter(s=>s.success).length,failedCount:e.filter(s=>!s.success).length,items:e}}function re(e){const s=e.taskId;return s&&(g.value[s]?.fileProgress||[]).find(a=>a.fileKey&&e.fileKey?a.fileKey===e.fileKey:a.sourceFilename&&e.sourceFilename?a.sourceFilename===e.sourceFilename:!1)||null}function we(e){const s=e.taskId;if(!s)return"";const t=g.value[s],n=t?.line_progress?.info,a=re(e);if(R(s)){const u=x(s,e),c=t?.task?.status,_=!!(n?.file_name&&n.file_name===e.sourceFilename);let k=u?._completed?"COMPLETED":(u?._pushed,"PENDING"),m=0,w=Math.max(0,e.totalRows??a?.totalRows??0);c==="SUCCESS"||c==="FAILED"&&u?._completed?(k="COMPLETED",m=w):_?(k="RUNNING",m=Math.max(0,Math.min(w||Number.MAX_SAFE_INTEGER,n?.current_line??0)),!w&&n?.total_lines&&n.total_lines>0&&(w=n.total_lines)):u?._completed?(k="COMPLETED",m=w):u?._pushed&&(k="PENDING",m=0);const I=[];if(I.push(`文件状态:${k}`),I.push(`文件进度:${m}/${w}`),n?.finished_files!=null){const C=n.file_total&&n.file_total>0?`/${n.file_total}`:"";I.push(`已完成文件:${n.finished_files}${C}`)}return n?.phase&&I.push(`阶段:${n.phase}`),I.join("|")}if(a){const u=[],c=a.processedRows??0,_=a.totalRows??0;if(a.status&&u.push(`文件状态:${a.status}`),u.push(`文件进度:${c}/${_}`),n?.finished_files!=null){const k=n.file_total&&n.file_total>0?`/${n.file_total}`:"";u.push(`已完成文件:${n.finished_files}${k}`)}return n?.phase&&u.push(`阶段:${n.phase}`),u.join("|")}if(!t?.line_progress?.has_progress||!n){const u=t?.task?.status;return u?`任务状态:${u}`:""}const r=[];if(n.phase&&r.push(`阶段:${n.phase}`),n.file_index&&n.file_total&&r.push(`文件:${n.file_index}/${n.file_total}`),n.file_name&&r.push(`当前文件:${n.file_name}`),n.current_line!=null&&n.total_lines!=null&&r.push(`${R(s)?"当前文件进度":"进度"}:${n.current_line}/${n.total_lines}`),n.current_country&&r.push(`国家:${n.current_country}`),n.current_asin&&r.push(`ASIN:${n.current_asin}`),n.finished_files!=null){const u=n.file_total&&n.file_total>0?`/${n.file_total}`:"";r.push(`已完成文件:${n.finished_files}${u}`)}return r.join("|")}function Y(e){const s=e.taskId;if(!s)return 0;const t=g.value[s]?.line_progress?.info;if(R(s)){const r=x(s,e);if(g.value[s]?.task?.status==="SUCCESS"||r?._completed)return 100;const c=Math.max(0,e.totalRows??re(e)?.totalRows??0);if(!!(t?.file_name&&t.file_name===e.sourceFilename)){if(t?.current_line!=null&&t?.total_lines&&t.total_lines>0)return Math.max(0,Math.min(100,Math.round(t.current_line/t.total_lines*100)));if(t?.current_line!=null&&c>0)return Math.max(0,Math.min(100,Math.round(t.current_line/c*100)))}return 0}const a=re(e);return a?Math.max(0,Math.min(100,a.percent??0)):t?t.current_line!=null&&t.total_lines&&t.total_lines>0?Math.max(0,Math.min(100,Math.round(t.current_line/t.total_lines*100))):t.finished_files!=null&&t.file_total&&t.file_total>0?Math.max(0,Math.min(100,Math.round(t.finished_files/t.file_total*100))):g.value[s]?.task?.status==="SUCCESS"?100:0:0}function Ie(e){const s=g.value[e]?.task?.status;return pe(s)}function U(){const e=new Set;return i.value.forEach(s=>{s.items.some(t=>t._pushed)&&e.add(s.taskId)}),Array.from(e)}function P(e){const s=e.taskId;if(!s||!le(s))return"";const n=x(s,e),a=ye(s),o=g.value[s]?.line_progress,r=R(s);if(!r&&(a==="SUCCESS"||a==="FAILED"||e.taskStatus==="SUCCESS"||e.taskStatus==="FAILED"))return"已被全局判定为终态";if(n?._completed)return"本文件解析完毕";if(a==="RUNNING"&&o?.has_progress&&o?.info?.file_name===e.sourceFilename)return"处理中";if(r&&!n?._pushed&&!n?._completed)return"未入队";if(r&&a==="SUCCESS")return"本文件解析完毕";if(!r){const u=g.value[s];if(u?.items&&u.items.find(_=>M(_,e))&&a==="SUCCESS")return"本文件解析完毕"}return n?._pushed?"已入队":"未入队"}function be(e){if(e.downloadUrl)return!0;const s=e.taskId;return s?g.value[s]?.task?.status==="SUCCESS":{success:!1,retryable:!1}}function He(e){const s=e?.task?.id;if(!s)return!1;const t=i.value.find(r=>r.taskId===s);if(!t)return{success:!1,retryable:!1};const n=e?.items||[];let a=!1;const o=t.items.map(r=>{const u=n.find(de=>M(de,r));if(!u)return r;const c=Array.isArray(u.countries)?u.countries:void 0,_=Array.isArray(r.countries)?r.countries:void 0,k=c&&c.length>0?c:_&&_.length>0?_:c??_,m=Array.isArray(u.previewRows)?u.previewRows:void 0,w=Array.isArray(r.previewRows)?r.previewRows:void 0,I=m&&m.length>0?m:w&&w.length>0?w:m??w,C={...r,...u,countries:k,previewRows:I,_pushed:r._pushed,_completed:r._completed};return JSON.stringify(C)!==JSON.stringify(r)&&(a=!0),C});for(const r of n)o.some(u=>M(u,r))||(o.push({...r,_pushed:!1,_completed:!1}),a=!0);return JSON.stringify(o)!==JSON.stringify(t.items)&&(t.items=o,a=!0),a&&ae(),a}async function oe(e){const s=e||U();if(s.length)try{const t=await as(s);se.value&&E.value&&(b.value="后端已恢复,继续执行当前任务并自动衔接后续任务..."),se.value=!1;let n=!1;for(const a of t.items||[]){const o=a?.task?.id;typeof o=="number"&&o>0&&(g.value[o]=a,He(a)&&(n=!0),_e(o)&&(n=!0))}n&&N()}catch(t){const n=(t instanceof Error?t.message:String(t||"")).toLowerCase();["无法连接到后端服务","bad gateway","gateway timeout","network error","timeout","502","503","504"].some(o=>n.includes(o.toLowerCase()))&&E.value&&(se.value=!0,b.value="当前任务运行中,正在等待后端服务恢复...")}}function Je(){return is()}function ue(e=!1){if(T.value){if(!e)return;clearTimeout(T.value),T.value=null}const s=async()=>{if(T.value=null,ee.value){ue();return}const t=U();if(!t.length)return;ee.value=!0;let n=!1,a=!1;try{await oe(t),await Ae();for(const o of t)Ie(o)&&(he(o),n=!0,a=!0)}finally{ee.value=!1}a?await O():n&&N(),U().length>0&&ue()};e?s():T.value=window.setTimeout(s,Je())}function ie(e=!1){T.value&&!e||ue(e)}function Z(e){const s=e.taskId?g.value[e.taskId]?.task?.status:null;return s==="SUCCESS"?{className:"success",text:"已完成"}:s==="FAILED"?{className:"failed",text:"失败"}:s==="RUNNING"?{className:"running",text:"执行中"}:e.taskStatus==="SUCCESS"?{className:"success",text:"已完成"}:e.taskStatus==="FAILED"?{className:"failed",text:"失败"}:e.taskStatus==="RUNNING"?{className:"running",text:"执行中"}:e.success?{className:"success",text:"已完成"}:{className:"failed",text:"失败"}}function Se(){T.value&&(window.clearTimeout(T.value),T.value=null),ce()}async function Te(e){const s=G();if(!s?.upload_file_to_java)throw new Error("当前桌面端未提供文件上传桥接能力");const t=[];for(const n of e){const a=typeof n=="string"?n:n.absolutePath,o=typeof n=="string"?void 0:n.relativePath,r=await s.upload_file_to_java(a,o);if(!r?.success||!r.data)throw new Error(r?.error||r?.message||`上传失败:${a}`);t.push(r.data)}return t}async function Ge(){const e=G();if(!e?.select_brand_xlsx_files){p.warning("当前环境不支持文件选择,请在本机客户端中打开");return}try{const s=await e.select_brand_xlsx_files();if(!s?.length)return;A.value=s,j.value=await Te(s),p.success(`已选择 ${s.length} 个删除品牌文件`)}catch(s){p.error(s instanceof Error?s.message:"选择失败")}}async function je(){const e=G();if(!e?.select_brand_folder){p.warning("当前环境不支持文件夹选择,请在本机客户端中打开");return}try{const s=await e.select_brand_folder();if(!s)return;const t=await vs(s);if(!t.success||!t.items?.length){p.warning(t.error||"该文件夹下没有 xlsx 文件");return}A.value=t.items.map(n=>n.relativePath||n.absolutePath),j.value=await Te(t.items),p.success(`已选择文件夹内 ${t.items.length} 个 xlsx 文件`)}catch(s){p.error(s instanceof Error?s.message:"选择失败")}}async function ze(){if(!j.value.length){p.warning("请先选择待处理文件");return}try{B.value=!0;const e=await ls({files:j.value.map(a=>({fileKey:a.fileKey,originalFilename:a.originalFilename,relativePath:a.relativePath}))}),s=me(e.items||[]),t=s.some(a=>a.matchStatus==="MATCHED"),n=s.some(a=>a.matchStatus&&a.matchStatus!=="MATCHED");b.value=t?"解析完成,可在左侧点击“推送到 Python 队列”开始串行处理":"解析完成,当前没有可推送的已匹配文件",S.value="",s.length&&s[0].taskId&&ne(s[0].taskId,s,b.value,S.value),N(),n?p.warning("部分文件尚未命中可用店铺索引,已保留状态信息,请等待后台刷新后重试。"):t?p.success("删除品牌解析完成,请在左侧推送到 Python 队列"):p.warning("当前没有可推送的已匹配文件,请先等待店铺索引刷新。"),await O()}catch(e){p.error(e instanceof Error?e.message:"执行失败")}finally{B.value=!1}}async function Ce(e){const s=e.taskId;if(!s){p.warning("未找到任务ID");return}const t=G(),n=rs(s),a=e.outputFilename||e.sourceFilename||`delete-brand_${s}.xlsx`;if(t?.save_file_from_url_new){const o=await t.save_file_from_url_new(n,a);o.success?p.success(`已保存:${o.path||a}`):o.error&&o.error!=="用户取消"&&p.error(o.error);return}}async function O(){try{const e=await os();q.value=me(e.items||[]),qe(q.value),N(),await oe(),ie(!0)}catch(e){const s=(e instanceof Error?e.message:String(e||"")).toLowerCase();["无法连接到后端服务","bad gateway","gateway timeout","network error","timeout","502","503","504"].some(n=>s.includes(n.toLowerCase()))||console.error("loadHistory error:",e)}}function Ee(e){i.value.forEach(t=>{t.items=t.items.filter(n=>n.resultId!==e)});const s=i.value.length;i.value=i.value.filter(t=>t.items.length>0),i.value.length!==s&&typeof window<"u"&&window.localStorage.setItem($(),JSON.stringify(i.value)),q.value=q.value.filter(t=>t.resultId!==e),N(),U().length===0&&Se()}async function Fe(e){try{await us(e),Ee(e),await O(),p.success("已删除")}catch(s){const t=s instanceof Error?s.message:"删除失败";if(typeof t=="string"&&t.includes("记录不存在")){Ee(e),p.success("已删除");return}p.error(t)}}function Pe(e){const s=e.slice(0,5).map(n=>`${n.country}:${n.asin}${n.status?`(${n.status})`:""}`);return e.length-s.length>0?`${s.join("、")} 等 ${e.length} 条`:s.join("、")}function It(e,s){}function ce(){L.value&&(window.clearTimeout(L.value),L.value=null)}function Ve(e=3e3){ce(),L.value=window.setTimeout(()=>{L.value=null,Ae().catch(()=>{})},e)}function Qe(e){return e.includes("当前店铺正在执行中")}function Re(){for(const e of i.value)for(const s of e.items){const t=P(s);if(t==="已入队"||t==="处理中")return!0}return!1}function K(e){return e.resultId!=null?`result:${e.resultId}`:`task:${e.taskId||0}:${e.sourceFilename||""}`}function xe(e){return!!(e.taskId&&(e.matchStatus==="MATCHED"||e.matched))}function Ne(){return F.value.find(e=>!xe(e)||K(e)===D.value||x(e.taskId,e)?._completed?!1:P(e)==="未入队")}async function $e(e,s){if(s?.auto&&Re())return e.taskId,e.sourceFilename,{success:!1,retryable:!0};const t=G(),n=e.taskId;if(!n)return{success:!1,retryable:!1};const a=i.value.find(c=>c.taskId===n);if(!a)return{success:!1,retryable:!1};if(!t?.enqueue_json)return p.error("当前环境未启用 pywebview enqueue_json(浏览器环境不会推送)"),{success:!1,retryable:!1};const o={type:"delete-brand-run",ts:Date.now(),data:{taskId:n,items:[e]}};S.value=JSON.stringify(o,null,2);const r=await t.enqueue_json(o);let u="";if(r?.success){u=`已推送文件 ${e.sourceFilename},当前队列长度:${r.queue_size??"-"}`;const c=x(n,e);return c&&(c._pushed=!0,c._completed=!1),D.value=K(e),ne(n,a.items,u,S.value),ie(!0),b.value=u,{success:!0,retryable:!1}}else{const c=r?.error||"未知错误";if(u=`推送到 Python 队列失败:${c}`,b.value=u,ne(n,a.items,u,S.value),s?.auto&&Qe(c))return{success:!1,retryable:!0}}return b.value=u,{success:!1,retryable:!1}}async function Xe(){const e=Ne();if(!e){p.warning("当前没有可推送的已匹配文件");return}V.value=!0,E.value=!0,ce();const s=await $e(e);if(V.value=!1,!s.success){E.value=!1,D.value="";return}p.success("已开始按顺序推送到 Python 队列")}async function Ae(){if(!E.value||Q.value){E.value,Q.value;return}if(Re())return;const e=D.value;if(e){const t=F.value.find(n=>K(n)===e);if(t){const n=t.taskId,a=R(n);a&&(_e(n),P(t),void 0);const o=P(t);if(o==="已入队"||o==="处理中"){t.sourceFilename;return}if(a){const r=x(n,t);if(r&&!r._completed){t.sourceFilename,r._pushed,r._completed;return}}}}const s=Ne();if(!s){D.value="",E.value=!1;return}Q.value=!0;try{s.taskId,s.sourceFilename,K(s);const t=await $e(s,{auto:!0});if(!t.success){if(t.retryable){s.taskId,s.sourceFilename,Ve();return}s.taskId,s.sourceFilename,E.value=!1}}finally{Q.value=!1}}return Ze(()=>{Be();const e=U();e.length>0?oe(e).then(async()=>{let s=!1,t=!1;for(const n of e)Ie(n)&&(he(n),s=!0,t=!0);t?await O():s&&N(),ie(!0)}):N(),O().catch(()=>{})}),es(()=>{Se()}),(e,s)=>(d(),f("div",hs,[ss(ts,{active:"delete-brand"}),l("div",ps,[l("aside",_s,[s[2]||(s[2]=l("div",{class:"section-title"},"选择文件",-1)),l("div",ys,[s[0]||(s[0]=l("div",{class:"hint"},"上传删除品牌 Excel;无论是直接上传文件还是上传文件夹批量导入,都会按每个 Excel 文件名作为店铺名。",-1)),l("div",{class:"btns"},[l("button",{type:"button",class:"opt-btn",onClick:Ge},"选择 Excel 文件"),l("button",{type:"button",class:"opt-btn",onClick:je},"选择文件夹")]),l("div",gs,[A.value.length?(d(),f(H,{key:0},[(d(!0),f(H,null,fe(te.value,t=>(d(),f("span",{key:t},v(t),1))),128)),A.value.length>te.value.length?(d(),f("span",ms," 还有 "+v(A.value.length-te.value.length)+" 个文件未展开显示 ",1)):h("",!0)],64)):(d(),f("span",ks,"暂未选择删除品牌文件"))])]),s[3]||(s[3]=ns('执行说明
',2)),l("div",ws,[l("button",{type:"button",class:"btn-run",disabled:B.value,onClick:ze},v(B.value?"解析中...":"开始解析"),9,Is),l("button",{type:"button",class:"btn-run btn-queue",disabled:V.value||!Ue.value.length,onClick:Xe},v(V.value?"推送中...":"推送到 Python 队列"),9,bs),l("span",Ss,v(B.value?"正在读取删除品牌数据,请稍候…":"解析后会在右侧展示按国家分组的去重结果"),1)]),b.value||S.value?(d(),f("div",Ts,[s[1]||(s[1]=l("div",{class:"section-title queue-debug-title"},"Python 队列推送结果",-1)),b.value?(d(),f("div",Cs,v(b.value),1)):h("",!0),S.value?(d(),f("pre",Es,v(S.value),1)):h("",!0)])):h("",!0)]),l("section",Fs,[s[12]||(s[12]=l("div",{class:"panel-header"},"删除品牌结果",-1)),l("div",Ps,[l("div",Rs,[l("div",xs,[s[4]||(s[4]=l("span",{class:"summary-label"},"已处理文件",-1)),l("strong",null,v(z.value.total),1)]),l("div",Ns,[s[5]||(s[5]=l("span",{class:"summary-label"},"成功结果",-1)),l("strong",null,v(z.value.successCount),1)]),l("div",$s,[s[6]||(s[6]=l("span",{class:"summary-label"},"失败文件",-1)),l("strong",null,v(z.value.failedCount),1)])]),l("div",As,[s[11]||(s[11]=l("div",{class:"result-list-header"},[l("span",null,"删除品牌结果列表")],-1)),Le.value?(d(),f(H,{key:1},[F.value.length?(d(),f("div",Ms,[s[8]||(s[8]=l("div",{class:"result-subsection-title"},"当前任务",-1)),l("ul",Bs,[(d(!0),f(H,null,fe(F.value,t=>(d(),f("li",{key:`current-${t.taskId||t.resultId||t.sourceFilename}`,class:"task-item split-result-item"},[l("div",qs,[l("span",{class:"id",title:t.sourceFilename},v(t.sourceFilename||"-"),9,Ls),l("div",Us,"店铺名:"+v(t.shopName||"-"),1),l("div",Os,"匹配结果:"+v(ke(t)),1),t.platform?(d(),f("div",Ks,"平台:"+v(t.platform),1)):h("",!0),P(t)?(d(),f("div",Hs,"队列状态:"+v(P(t)),1)):h("",!0),t.countryCount!==void 0&&t.matched?(d(),f("div",Js,"国家数:"+v(t.countryCount),1)):h("",!0),t.totalRows!==void 0&&t.matched?(d(),f("div",Gs,"去重后 "+v(t.totalRows)+" 条 ",1)):h("",!0),ge(t)?(d(),f("div",js,[l("div",zs,[s[7]||(s[7]=l("span",null,"任务进度",-1)),l("span",null,v(Y(t))+"%",1)]),l("div",Vs,[l("div",{class:"delete-brand-progress-bar-fill",style:De({width:`${Y(t)}%`})},null,4)]),l("div",Qs,v(we(t)),1)])):h("",!0),t.previewRows?.length?(d(),f("div",Xs,v(Pe(t.previewRows)),1)):h("",!0),W(t)?(d(),f("div",Ws,"错误信息:"+v(W(t)),1)):h("",!0)]),l("div",Ys,[l("span",{class:Me(["status",Z(t).className])},v(Z(t).text),3),be(t)?(d(),f("button",{key:0,type:"button",class:"download",onClick:n=>Ce(t)}," 下载 ",8,Zs)):h("",!0),t.resultId?(d(),f("button",{key:1,type:"button",class:"btn-delete",onClick:n=>Fe(t.resultId)}," 删除 ",8,et)):h("",!0)])]))),128))])])):h("",!0),X.value.length?(d(),f("div",st,[s[10]||(s[10]=l("div",{class:"result-subsection-title"},"历史记录",-1)),l("ul",tt,[(d(!0),f(H,null,fe(X.value,t=>(d(),f("li",{key:`history-${t.taskId||t.resultId||t.sourceFilename}`,class:"task-item split-result-item"},[l("div",nt,[l("span",{class:"id",title:t.sourceFilename},v(t.sourceFilename||"-"),9,at),l("div",lt,"店铺名:"+v(t.shopName||"-"),1),l("div",rt,"匹配结果:"+v(ke(t)),1),t.platform?(d(),f("div",ot,"平台:"+v(t.platform),1)):h("",!0),t.countryCount!==void 0&&t.matched?(d(),f("div",ut,"国家数:"+v(t.countryCount),1)):h("",!0),t.totalRows!==void 0&&t.matched?(d(),f("div",it,"去重后 "+v(t.totalRows)+" 条 ",1)):h("",!0),ge(t)?(d(),f("div",ct,[l("div",dt,[s[9]||(s[9]=l("span",null,"任务进度",-1)),l("span",null,v(Y(t))+"%",1)]),l("div",ft,[l("div",{class:"delete-brand-progress-bar-fill",style:De({width:`${Y(t)}%`})},null,4)]),l("div",vt,v(we(t)),1)])):h("",!0),t.previewRows?.length?(d(),f("div",ht,v(Pe(t.previewRows)),1)):h("",!0),W(t)?(d(),f("div",pt,"错误信息:"+v(W(t)),1)):h("",!0)]),l("div",_t,[l("span",{class:Me(["status",Z(t).className])},v(Z(t).text),3),be(t)?(d(),f("button",{key:0,type:"button",class:"download",onClick:n=>Ce(t)}," 下载 ",8,yt)):h("",!0),t.resultId?(d(),f("button",{key:1,type:"button",class:"btn-delete",onClick:n=>Fe(t.resultId)}," 删除 ",8,gt)):h("",!0)])]))),128))])])):h("",!0)],64)):(d(),f("div",Ds," 暂无删除品牌结果,完成解析后会在这里展示按国家分组的去重结果 "))])])])])]))}}),kt=cs(mt,[["__scopeId","data-v-64ee024e"]]);ds(kt).use(fs).mount("#app");
diff --git a/app/assets/split.js b/app/assets/split.js
index a5c8e4d..6e5da81 100644
--- a/app/assets/split.js
+++ b/app/assets/split.js
@@ -1 +1 @@
-import{d as G,o as J,a,c as n,b as L,B as O,e,F as S,r as $,t as i,f as v,n as x,w as k,s as V,C as M,g as c,h as Q,i as F,E as r,D as W,G as X,m as Y,v as Z,H as ss,_ as es,p as ts,q as ls}from"./pywebview-DuyK2jB1.js";import{e as as}from"./brand-CKgrwtni.js";const ns={class:"page-shell module-page"},os={class:"main-content"},is={class:"left-panel"},rs={class:"upload-zone"},us={class:"selected-files clean-placeholder split-selected-files"},cs={key:0,class:"more-line"},ds={key:1},ps={class:"option-group column-option-group"},vs={class:"column-toolbar"},fs={class:"column-count"},_s={class:"column-actions"},ms={key:0,class:"column-grid"},ys=["value"],gs={key:1,class:"empty-column-state"},hs={class:"option-group"},bs={class:"radio-item active"},ws={class:"desc"},ks={class:"option-group"},Cs={class:"option-group"},Ss={key:0,class:"split-input-card"},xs={key:1,class:"split-input-card"},Fs={class:"run-row"},Es=["disabled"],Ps={class:"loading-msg"},$s={class:"right-panel"},Bs={class:"task-list-wrap"},Rs={class:"clean-result-summary"},Is={class:"summary-card"},Us={class:"summary-card"},Vs={class:"summary-card"},Ms={class:"result-list-wrap"},Ts={key:0,class:"empty-tasks"},Ds={key:1,class:"task-list clean-result-list"},Hs={class:"left split-result-main"},Ns=["title"],js={key:0,class:"files"},As={key:1,class:"time"},Ks={key:2,class:"files"},zs={key:3,class:"files split-entry-list"},qs={key:4,class:"files"},Gs={class:"task-right split-result-actions"},Js=["onClick"],Ls=["onClick"],Os=G({__name:"BrandSplitTab",setup(Ws){const f=c([]),C=c(""),d=c([]),_=c([]),p=c([]),B=c(0),u=c("rows_per_file"),m=c(2e3),y=c(5),g=c(!1),h=c([]),b=c({total:0,successCount:0,failedCount:0,items:[]}),E=Q(()=>f.value.slice(0,8));function T(l){b.value={total:l.length,successCount:l.filter(s=>s.success).length,failedCount:l.filter(s=>!s.success).length,items:l}}async function R(l){const s=F();if(!s?.upload_file_to_java)throw new Error("当前桌面端未提供文件上传桥接能力");const t=[];for(const o of l){const U=typeof o=="string"?o:o.absolutePath,q=typeof o=="string"?void 0:o.relativePath,w=await s.upload_file_to_java(U,q);if(!w?.success||!w.data)throw new Error(w?.error||w?.message||`上传失败:${U}`);t.push(w.data)}return t}async function I(l){const s=await Y(l);_.value=s.headers||[],p.value=[...s.headers||[]],B.value=s.totalRows||0}async function D(l,s){f.value=l,C.value="",d.value=await R(l),d.value.length>0&&await I(d.value[0].fileKey),r.success(s)}async function H(){const l=F();if(!l?.select_brand_xlsx_files){r.warning("当前环境不支持文件选择,请在本机客户端中打开");return}try{const s=await l.select_brand_xlsx_files();if(!s?.length)return;await D(s,`已选择 ${s.length} 个待拆分文件`)}catch(s){r.error(s instanceof Error?s.message:"选择失败")}}async function N(){const l=F();if(!l?.select_brand_folder){r.warning("当前环境不支持文件夹选择,请在本机客户端中打开");return}try{const s=await l.select_brand_folder();if(!s)return;C.value=s.split(/[/\\]/).filter(Boolean).pop()||"";const t=await as(s);if(t.success&&t.items?.length){f.value=t.items.map(o=>o.relativePath),d.value=await R(t.items),d.value.length>0&&await I(d.value[0].fileKey),r.success(`已选择文件夹内 ${t.items.length} 个 xlsx 文件`);return}r.warning(t.error||"该文件夹下没有 xlsx 文件")}catch(s){r.error(s instanceof Error?s.message:"选择失败")}}async function j(){if(!d.value.length){r.warning("请先选择待拆分 Excel 文件或文件夹");return}if(!p.value.length){r.warning("请至少选择一列保留列");return}if(u.value==="rows_per_file"&&(!m.value||m.value<=0)){r.warning("请输入有效的每份条数");return}if(u.value==="parts"&&(!y.value||y.value<=0)){r.warning("请输入有效的拆分份数");return}try{g.value=!0;const l=await W({files:d.value.map(s=>({fileKey:s.fileKey,originalFilename:s.originalFilename,relativePath:s.relativePath})),selectedColumns:p.value,splitMode:u.value,rowsPerFile:u.value==="rows_per_file"&&m.value||void 0,parts:u.value==="parts"&&y.value||void 0,archiveName:d.value.some(s=>!!s.relativePath)&&C.value?C.value:void 0});b.value=l,h.value=l.items||[],await P(),r.success("数据拆分完成")}catch(l){r.error(l instanceof Error?l.message:"数据拆分失败")}finally{g.value=!1}}async function P(){try{const l=await X();h.value=l.items||[],T(h.value)}catch{}}async function A(l){try{await ss(l),await P(),r.success("已删除")}catch(s){r.error(s instanceof Error?s.message:"删除失败")}}function K(l){const s=l.slice(0,4).map(o=>o.rowCount!==void 0?`${o.filename}(${o.rowCount}条)`:o.filename);return l.length-s.length>0?`${s.join("、")} 等 ${l.length} 个文件`:s.join("、")}async function z(l){const s=F();if(!l.downloadUrl){r.warning("当前结果没有下载地址");return}const t=l.outputFilename||`${new Date().toISOString().slice(0,10).replace(/-/g,"")}.zip`;if(s?.save_file_from_url_new){const o=await s.save_file_from_url_new(l.downloadUrl,t);o.success?r.success(`已保存:${o.path||t}`):o.error&&o.error!=="用户取消"&&r.error(o.error);return}}return J(()=>{P().catch(()=>{})}),(l,s)=>(a(),n("div",ns,[L(O,{active:"split"}),e("div",os,[e("aside",is,[s[14]||(s[14]=e("div",{class:"section-title"},"选择文件",-1)),e("div",rs,[s[7]||(s[7]=e("div",{class:"hint"},"选择需要拆分的 Excel 文件或文件夹,按规则拆成多个 Excel 文件。",-1)),e("div",{class:"btns"},[e("button",{type:"button",class:"opt-btn",onClick:H},"选择 Excel 文件"),e("button",{type:"button",class:"opt-btn",onClick:N},"选择文件夹")]),e("div",us,[f.value.length?(a(),n(S,{key:0},[(a(!0),n(S,null,$(E.value,t=>(a(),n("span",{key:t},i(t),1))),128)),f.value.length>E.value.length?(a(),n("span",cs," 还有 "+i(f.value.length-E.value.length)+" 个文件未展开显示 ",1)):v("",!0)],64)):(a(),n("span",ds,"暂未选择待拆分文件"))])]),s[15]||(s[15]=e("div",{class:"section-title"},"保留列设置",-1)),e("div",ps,[e("div",vs,[e("span",fs,"已选择 "+i(p.value.length)+" / "+i(_.value.length)+" 列",1),e("div",_s,[e("button",{type:"button",class:"opt-btn small",onClick:s[0]||(s[0]=t=>p.value=[..._.value])},"全选"),e("button",{type:"button",class:"opt-btn small",onClick:s[1]||(s[1]=t=>p.value=[])},"清空")])]),_.value.length?(a(),n("div",ms,[(a(!0),n(S,null,$(_.value,t=>(a(),n("label",{key:t,class:x(["column-item",{active:p.value.includes(t)}])},[k(e("input",{"onUpdate:modelValue":s[2]||(s[2]=o=>p.value=o),type:"checkbox",value:t},null,8,ys),[[Z,p.value]]),e("span",null,i(t),1)],2))),128))])):(a(),n("div",gs,"请选择 Excel 文件后读取表头并填充可保留列"))]),s[16]||(s[16]=e("div",{class:"section-title"},"总条数",-1)),e("div",hs,[e("label",bs,[s[9]||(s[9]=e("input",{type:"checkbox",checked:"",disabled:""},null,-1)),e("div",null,[s[8]||(s[8]=e("span",{class:"label"},"当前数据总条数",-1)),e("div",ws,i(B.value)+" 条(不含表头)",1)])])]),s[17]||(s[17]=e("div",{class:"section-title"},"拆分模式",-1)),e("div",ks,[e("label",{class:x(["radio-item",{active:u.value==="rows_per_file"}])},[k(e("input",{"onUpdate:modelValue":s[3]||(s[3]=t=>u.value=t),type:"radio",value:"rows_per_file",name:"split-mode"},null,512),[[V,u.value]]),s[10]||(s[10]=e("div",null,[e("span",{class:"label"},"按每份条数"),e("div",{class:"desc"},"例如每份 2000 条,最后一份放剩余数据")],-1))],2),e("label",{class:x(["radio-item",{active:u.value==="parts"}])},[k(e("input",{"onUpdate:modelValue":s[4]||(s[4]=t=>u.value=t),type:"radio",value:"parts",name:"split-mode"},null,512),[[V,u.value]]),s[11]||(s[11]=e("div",null,[e("span",{class:"label"},"按份数平均拆分"),e("div",{class:"desc"},"例如拆 5 份,按总条数尽量平均分配")],-1))],2)]),s[18]||(s[18]=e("div",{class:"section-title"},"拆分参数",-1)),e("div",Cs,[u.value==="rows_per_file"?(a(),n("label",Ss,[s[12]||(s[12]=e("span",{class:"label"},"每份条数",-1)),k(e("input",{"onUpdate:modelValue":s[5]||(s[5]=t=>m.value=t),class:"split-number-input",type:"number",min:"1",placeholder:"例如 2000"},null,512),[[M,m.value,void 0,{number:!0}]])])):(a(),n("label",xs,[s[13]||(s[13]=e("span",{class:"label"},"拆分份数",-1)),k(e("input",{"onUpdate:modelValue":s[6]||(s[6]=t=>y.value=t),class:"split-number-input",type:"number",min:"1",placeholder:"例如 5"},null,512),[[M,y.value,void 0,{number:!0}]])]))]),e("div",Fs,[e("button",{type:"button",class:"btn-run",disabled:g.value,onClick:j},i(g.value?"拆分中...":"开始拆分"),9,Es),e("span",Ps,i(g.value?"正在拆分并上传结果,请稍候…":"选择文件、保留列和拆分规则后即可执行拆分,结果会显示在右侧供下载"),1)])]),e("section",$s,[s[23]||(s[23]=e("div",{class:"panel-header"},"拆分结果",-1)),e("div",Bs,[e("div",Rs,[e("div",Is,[s[19]||(s[19]=e("span",{class:"summary-label"},"已处理文件",-1)),e("strong",null,i(b.value.total),1)]),e("div",Us,[s[20]||(s[20]=e("span",{class:"summary-label"},"成功结果",-1)),e("strong",null,i(b.value.successCount),1)]),e("div",Vs,[s[21]||(s[21]=e("span",{class:"summary-label"},"失败文件",-1)),e("strong",null,i(b.value.failedCount),1)])]),e("div",Ms,[s[22]||(s[22]=e("div",{class:"result-list-header"},[e("span",null,"拆分结果压缩包列表")],-1)),h.value.length===0?(a(),n("div",Ts," 暂无拆分结果,完成数据拆分后会在这里展示压缩包文件 ")):(a(),n("ul",Ds,[(a(!0),n(S,null,$(h.value,t=>(a(),n("li",{key:`${t.resultId||t.outputFilename||t.sourceFilename}-${t.rowCount||0}`,class:"task-item split-result-item"},[e("div",Hs,[e("span",{class:"id",title:t.sourceFilename},i(t.sourceFilename||"-"),9,Ns),t.outputFilename?(a(),n("div",js,"压缩包文件:"+i(t.outputFilename),1)):v("",!0),t.rowCount!==void 0?(a(),n("div",As,"压缩包共包含 "+i(t.rowCount)+" 条数据",1)):v("",!0),t.entryCount&&t.entryCount>0?(a(),n("div",Ks,"压缩包内共 "+i(t.entryCount)+" 个拆分文件 ",1)):v("",!0),t.entries&&t.entries.length?(a(),n("div",zs," 压缩包内容:"+i(K(t.entries)),1)):v("",!0),t.error?(a(),n("div",qs,"错误信息:"+i(t.error),1)):v("",!0)]),e("div",Gs,[e("span",{class:x(["status",t.success?"success":"failed"])},i(t.success?"已完成":"失败"),3),t.success&&t.downloadUrl?(a(),n("button",{key:0,type:"button",class:"download",onClick:o=>z(t)}," 下载压缩包 ",8,Js)):v("",!0),t.resultId?(a(),n("button",{key:1,type:"button",class:"btn-delete",onClick:o=>A(t.resultId)}," 删除 ",8,Ls)):v("",!0)])]))),128))]))])])])])]))}}),Qs=es(Os,[["__scopeId","data-v-c5a98d6b"]]);ts(Qs).use(ls).mount("#app");
+import{d as G,o as J,a,c as n,b as L,B as O,e,F as S,r as $,t as i,f as v,n as x,w as k,s as V,C as M,g as c,h as Q,i as F,E as r,D as W,G as X,m as Y,v as Z,H as ss,_ as es,p as ts,q as ls}from"./pywebview-9YBa--7x.js";import{e as as}from"./brand-CRmRvyGD.js";const ns={class:"page-shell module-page"},os={class:"main-content"},is={class:"left-panel"},rs={class:"upload-zone"},us={class:"selected-files clean-placeholder split-selected-files"},cs={key:0,class:"more-line"},ds={key:1},ps={class:"option-group column-option-group"},vs={class:"column-toolbar"},fs={class:"column-count"},_s={class:"column-actions"},ms={key:0,class:"column-grid"},ys=["value"],gs={key:1,class:"empty-column-state"},hs={class:"option-group"},bs={class:"radio-item active"},ws={class:"desc"},ks={class:"option-group"},Cs={class:"option-group"},Ss={key:0,class:"split-input-card"},xs={key:1,class:"split-input-card"},Fs={class:"run-row"},Es=["disabled"],Ps={class:"loading-msg"},$s={class:"right-panel"},Bs={class:"task-list-wrap"},Rs={class:"clean-result-summary"},Is={class:"summary-card"},Us={class:"summary-card"},Vs={class:"summary-card"},Ms={class:"result-list-wrap"},Ts={key:0,class:"empty-tasks"},Ds={key:1,class:"task-list clean-result-list"},Hs={class:"left split-result-main"},Ns=["title"],js={key:0,class:"files"},As={key:1,class:"time"},Ks={key:2,class:"files"},zs={key:3,class:"files split-entry-list"},qs={key:4,class:"files"},Gs={class:"task-right split-result-actions"},Js=["onClick"],Ls=["onClick"],Os=G({__name:"BrandSplitTab",setup(Ws){const f=c([]),C=c(""),d=c([]),_=c([]),p=c([]),B=c(0),u=c("rows_per_file"),m=c(2e3),y=c(5),g=c(!1),h=c([]),b=c({total:0,successCount:0,failedCount:0,items:[]}),E=Q(()=>f.value.slice(0,8));function T(l){b.value={total:l.length,successCount:l.filter(s=>s.success).length,failedCount:l.filter(s=>!s.success).length,items:l}}async function R(l){const s=F();if(!s?.upload_file_to_java)throw new Error("当前桌面端未提供文件上传桥接能力");const t=[];for(const o of l){const U=typeof o=="string"?o:o.absolutePath,q=typeof o=="string"?void 0:o.relativePath,w=await s.upload_file_to_java(U,q);if(!w?.success||!w.data)throw new Error(w?.error||w?.message||`上传失败:${U}`);t.push(w.data)}return t}async function I(l){const s=await Y(l);_.value=s.headers||[],p.value=[...s.headers||[]],B.value=s.totalRows||0}async function D(l,s){f.value=l,C.value="",d.value=await R(l),d.value.length>0&&await I(d.value[0].fileKey),r.success(s)}async function H(){const l=F();if(!l?.select_brand_xlsx_files){r.warning("当前环境不支持文件选择,请在本机客户端中打开");return}try{const s=await l.select_brand_xlsx_files();if(!s?.length)return;await D(s,`已选择 ${s.length} 个待拆分文件`)}catch(s){r.error(s instanceof Error?s.message:"选择失败")}}async function N(){const l=F();if(!l?.select_brand_folder){r.warning("当前环境不支持文件夹选择,请在本机客户端中打开");return}try{const s=await l.select_brand_folder();if(!s)return;C.value=s.split(/[/\\]/).filter(Boolean).pop()||"";const t=await as(s);if(t.success&&t.items?.length){f.value=t.items.map(o=>o.relativePath),d.value=await R(t.items),d.value.length>0&&await I(d.value[0].fileKey),r.success(`已选择文件夹内 ${t.items.length} 个 xlsx 文件`);return}r.warning(t.error||"该文件夹下没有 xlsx 文件")}catch(s){r.error(s instanceof Error?s.message:"选择失败")}}async function j(){if(!d.value.length){r.warning("请先选择待拆分 Excel 文件或文件夹");return}if(!p.value.length){r.warning("请至少选择一列保留列");return}if(u.value==="rows_per_file"&&(!m.value||m.value<=0)){r.warning("请输入有效的每份条数");return}if(u.value==="parts"&&(!y.value||y.value<=0)){r.warning("请输入有效的拆分份数");return}try{g.value=!0;const l=await W({files:d.value.map(s=>({fileKey:s.fileKey,originalFilename:s.originalFilename,relativePath:s.relativePath})),selectedColumns:p.value,splitMode:u.value,rowsPerFile:u.value==="rows_per_file"&&m.value||void 0,parts:u.value==="parts"&&y.value||void 0,archiveName:d.value.some(s=>!!s.relativePath)&&C.value?C.value:void 0});b.value=l,h.value=l.items||[],await P(),r.success("数据拆分完成")}catch(l){r.error(l instanceof Error?l.message:"数据拆分失败")}finally{g.value=!1}}async function P(){try{const l=await X();h.value=l.items||[],T(h.value)}catch{}}async function A(l){try{await ss(l),await P(),r.success("已删除")}catch(s){r.error(s instanceof Error?s.message:"删除失败")}}function K(l){const s=l.slice(0,4).map(o=>o.rowCount!==void 0?`${o.filename}(${o.rowCount}条)`:o.filename);return l.length-s.length>0?`${s.join("、")} 等 ${l.length} 个文件`:s.join("、")}async function z(l){const s=F();if(!l.downloadUrl){r.warning("当前结果没有下载地址");return}const t=l.outputFilename||`${new Date().toISOString().slice(0,10).replace(/-/g,"")}.zip`;if(s?.save_file_from_url_new){const o=await s.save_file_from_url_new(l.downloadUrl,t);o.success?r.success(`已保存:${o.path||t}`):o.error&&o.error!=="用户取消"&&r.error(o.error);return}}return J(()=>{P().catch(()=>{})}),(l,s)=>(a(),n("div",ns,[L(O,{active:"split"}),e("div",os,[e("aside",is,[s[14]||(s[14]=e("div",{class:"section-title"},"选择文件",-1)),e("div",rs,[s[7]||(s[7]=e("div",{class:"hint"},"选择需要拆分的 Excel 文件或文件夹,按规则拆成多个 Excel 文件。",-1)),e("div",{class:"btns"},[e("button",{type:"button",class:"opt-btn",onClick:H},"选择 Excel 文件"),e("button",{type:"button",class:"opt-btn",onClick:N},"选择文件夹")]),e("div",us,[f.value.length?(a(),n(S,{key:0},[(a(!0),n(S,null,$(E.value,t=>(a(),n("span",{key:t},i(t),1))),128)),f.value.length>E.value.length?(a(),n("span",cs," 还有 "+i(f.value.length-E.value.length)+" 个文件未展开显示 ",1)):v("",!0)],64)):(a(),n("span",ds,"暂未选择待拆分文件"))])]),s[15]||(s[15]=e("div",{class:"section-title"},"保留列设置",-1)),e("div",ps,[e("div",vs,[e("span",fs,"已选择 "+i(p.value.length)+" / "+i(_.value.length)+" 列",1),e("div",_s,[e("button",{type:"button",class:"opt-btn small",onClick:s[0]||(s[0]=t=>p.value=[..._.value])},"全选"),e("button",{type:"button",class:"opt-btn small",onClick:s[1]||(s[1]=t=>p.value=[])},"清空")])]),_.value.length?(a(),n("div",ms,[(a(!0),n(S,null,$(_.value,t=>(a(),n("label",{key:t,class:x(["column-item",{active:p.value.includes(t)}])},[k(e("input",{"onUpdate:modelValue":s[2]||(s[2]=o=>p.value=o),type:"checkbox",value:t},null,8,ys),[[Z,p.value]]),e("span",null,i(t),1)],2))),128))])):(a(),n("div",gs,"请选择 Excel 文件后读取表头并填充可保留列"))]),s[16]||(s[16]=e("div",{class:"section-title"},"总条数",-1)),e("div",hs,[e("label",bs,[s[9]||(s[9]=e("input",{type:"checkbox",checked:"",disabled:""},null,-1)),e("div",null,[s[8]||(s[8]=e("span",{class:"label"},"当前数据总条数",-1)),e("div",ws,i(B.value)+" 条(不含表头)",1)])])]),s[17]||(s[17]=e("div",{class:"section-title"},"拆分模式",-1)),e("div",ks,[e("label",{class:x(["radio-item",{active:u.value==="rows_per_file"}])},[k(e("input",{"onUpdate:modelValue":s[3]||(s[3]=t=>u.value=t),type:"radio",value:"rows_per_file",name:"split-mode"},null,512),[[V,u.value]]),s[10]||(s[10]=e("div",null,[e("span",{class:"label"},"按每份条数"),e("div",{class:"desc"},"例如每份 2000 条,最后一份放剩余数据")],-1))],2),e("label",{class:x(["radio-item",{active:u.value==="parts"}])},[k(e("input",{"onUpdate:modelValue":s[4]||(s[4]=t=>u.value=t),type:"radio",value:"parts",name:"split-mode"},null,512),[[V,u.value]]),s[11]||(s[11]=e("div",null,[e("span",{class:"label"},"按份数平均拆分"),e("div",{class:"desc"},"例如拆 5 份,按总条数尽量平均分配")],-1))],2)]),s[18]||(s[18]=e("div",{class:"section-title"},"拆分参数",-1)),e("div",Cs,[u.value==="rows_per_file"?(a(),n("label",Ss,[s[12]||(s[12]=e("span",{class:"label"},"每份条数",-1)),k(e("input",{"onUpdate:modelValue":s[5]||(s[5]=t=>m.value=t),class:"split-number-input",type:"number",min:"1",placeholder:"例如 2000"},null,512),[[M,m.value,void 0,{number:!0}]])])):(a(),n("label",xs,[s[13]||(s[13]=e("span",{class:"label"},"拆分份数",-1)),k(e("input",{"onUpdate:modelValue":s[6]||(s[6]=t=>y.value=t),class:"split-number-input",type:"number",min:"1",placeholder:"例如 5"},null,512),[[M,y.value,void 0,{number:!0}]])]))]),e("div",Fs,[e("button",{type:"button",class:"btn-run",disabled:g.value,onClick:j},i(g.value?"拆分中...":"开始拆分"),9,Es),e("span",Ps,i(g.value?"正在拆分并上传结果,请稍候…":"选择文件、保留列和拆分规则后即可执行拆分,结果会显示在右侧供下载"),1)])]),e("section",$s,[s[23]||(s[23]=e("div",{class:"panel-header"},"拆分结果",-1)),e("div",Bs,[e("div",Rs,[e("div",Is,[s[19]||(s[19]=e("span",{class:"summary-label"},"已处理文件",-1)),e("strong",null,i(b.value.total),1)]),e("div",Us,[s[20]||(s[20]=e("span",{class:"summary-label"},"成功结果",-1)),e("strong",null,i(b.value.successCount),1)]),e("div",Vs,[s[21]||(s[21]=e("span",{class:"summary-label"},"失败文件",-1)),e("strong",null,i(b.value.failedCount),1)])]),e("div",Ms,[s[22]||(s[22]=e("div",{class:"result-list-header"},[e("span",null,"拆分结果压缩包列表")],-1)),h.value.length===0?(a(),n("div",Ts," 暂无拆分结果,完成数据拆分后会在这里展示压缩包文件 ")):(a(),n("ul",Ds,[(a(!0),n(S,null,$(h.value,t=>(a(),n("li",{key:`${t.resultId||t.outputFilename||t.sourceFilename}-${t.rowCount||0}`,class:"task-item split-result-item"},[e("div",Hs,[e("span",{class:"id",title:t.sourceFilename},i(t.sourceFilename||"-"),9,Ns),t.outputFilename?(a(),n("div",js,"压缩包文件:"+i(t.outputFilename),1)):v("",!0),t.rowCount!==void 0?(a(),n("div",As,"压缩包共包含 "+i(t.rowCount)+" 条数据",1)):v("",!0),t.entryCount&&t.entryCount>0?(a(),n("div",Ks,"压缩包内共 "+i(t.entryCount)+" 个拆分文件 ",1)):v("",!0),t.entries&&t.entries.length?(a(),n("div",zs," 压缩包内容:"+i(K(t.entries)),1)):v("",!0),t.error?(a(),n("div",qs,"错误信息:"+i(t.error),1)):v("",!0)]),e("div",Gs,[e("span",{class:x(["status",t.success?"success":"failed"])},i(t.success?"已完成":"失败"),3),t.success&&t.downloadUrl?(a(),n("button",{key:0,type:"button",class:"download",onClick:o=>z(t)}," 下载压缩包 ",8,Js)):v("",!0),t.resultId?(a(),n("button",{key:1,type:"button",class:"btn-delete",onClick:o=>A(t.resultId)}," 删除 ",8,Ls)):v("",!0)])]))),128))]))])])])])]))}}),Qs=es(Os,[["__scopeId","data-v-c5a98d6b"]]);ts(Qs).use(ls).mount("#app");
diff --git a/app/new_web_source/convert.html b/app/new_web_source/convert.html
index bfb015f..0be538b 100644
--- a/app/new_web_source/convert.html
+++ b/app/new_web_source/convert.html
@@ -5,8 +5,8 @@
格式转换 - 数富AI
-
-
+
+
diff --git a/app/new_web_source/dedupe.html b/app/new_web_source/dedupe.html
index faae410..0afd015 100644
--- a/app/new_web_source/dedupe.html
+++ b/app/new_web_source/dedupe.html
@@ -5,8 +5,8 @@
数据去重 - 数富AI
-
-
+
+
diff --git a/app/new_web_source/delete-brand.html b/app/new_web_source/delete-brand.html
index 8f767f0..dd75db4 100644
--- a/app/new_web_source/delete-brand.html
+++ b/app/new_web_source/delete-brand.html
@@ -5,8 +5,8 @@
删除品牌 - 数富AI
-
-
+
+
diff --git a/app/new_web_source/split.html b/app/new_web_source/split.html
index e099223..ce77955 100644
--- a/app/new_web_source/split.html
+++ b/app/new_web_source/split.html
@@ -5,8 +5,8 @@
数据拆分 - 数富AI
-
-
+
+