From 25ce9f74b8e6aace80e7a25d1cea606d790a09c9 Mon Sep 17 00:00:00 2001
From: super <2903208875@qq.com>
Date: Mon, 30 Mar 2026 23:15:08 +0800
Subject: [PATCH] =?UTF-8?q?=E5=A4=84=E7=90=86=E8=BF=99=E4=B8=AApython?=
=?UTF-8?q?=E6=89=93=E5=BC=80=E7=B4=AB=E9=B8=9F?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
app/assets/delete-brand.js | 2 +-
app/main.py | 18 +-
app/new_web_source/delete-brand.html | 2 +-
.../brand/components/BrandDeleteBrandTab.vue | 649 ++++++++++++++----
frontend-vue/src/shared/bridges/pywebview.ts | 1 +
5 files changed, 543 insertions(+), 129 deletions(-)
diff --git a/app/assets/delete-brand.js b/app/assets/delete-brand.js
index dc0038a..6a3dad9 100644
--- a/app/assets/delete-brand.js
+++ b/app/assets/delete-brand.js
@@ -1 +1 @@
-import{d as ye,o as ke,J as be,a as l,c as o,b as we,B as Ie,e as a,F as C,r as z,t as r,f as c,K as me,L as le,n as oe,g as _,h as A,M as Se,i as x,E as d,j as Ce,N as xe,O as $e,P as Ne,Q as Fe,_ as Pe,q as Ee,s as Te}from"./pywebview-Dee3nQjE.js";const De={class:"page-shell module-page"},Re={class:"main-content"},qe={class:"left-panel"},Be={class:"upload-zone"},Ue={class:"selected-files clean-placeholder split-selected-files"},Ae={key:0,class:"more-line"},Me={key:1},je={class:"run-row"},Le=["disabled"],Je={class:"loading-msg"},Ve={key:0,class:"queue-debug-card"},ze={key:0,class:"queue-debug-line"},Oe={key:1,class:"queue-debug-payload"},He={class:"right-panel"},Ke={class:"task-list-wrap"},Ge={class:"clean-result-summary"},Qe={class:"summary-card"},We={class:"summary-card"},Xe={class:"summary-card"},Ye={class:"result-list-wrap"},Ze={key:0,class:"empty-tasks"},es={key:0,class:"result-subsection"},ss={class:"task-list clean-result-list"},ts={class:"left split-result-main"},as=["title"],ns={class:"files"},ls={class:"files"},os={key:0,class:"files"},rs={key:1,class:"files"},is={key:2,class:"files"},us={key:3,class:"time"},cs={key:4,class:"delete-brand-progress-block"},ds={class:"delete-brand-progress-header"},fs={class:"delete-brand-progress-bar"},vs={class:"files delete-brand-progress"},hs={key:5,class:"files split-entry-list delete-brand-preview"},_s={key:6,class:"files"},ps={class:"task-right split-result-actions"},gs=["onClick"],ys=["onClick"],ks=["onClick"],bs={key:1,class:"result-subsection"},ws={class:"task-list clean-result-list"},Is={class:"left split-result-main"},ms=["title"],Ss={class:"files"},Cs={class:"files"},xs={key:0,class:"files"},$s={key:1,class:"files"},Ns={key:2,class:"time"},Fs={key:3,class:"delete-brand-progress-block"},Ps={class:"delete-brand-progress-header"},Es={class:"delete-brand-progress-bar"},Ts={class:"files delete-brand-progress"},Ds={key:4,class:"files split-entry-list delete-brand-preview"},Rs={key:5,class:"files"},qs={class:"task-right split-result-actions"},Bs=["onClick"],Us=["onClick"],As=ye({__name:"BrandDeleteBrandTab",setup(js){function $(){return`delete-brand:current-tasks:${typeof window<"u"&&window.localStorage.getItem("uid")||"0"}`}const b=_([]),N=_([]),I=_(!1),O=_([]),i=_([]),F=_([]),P=_({total:0,successCount:0,failedCount:0,items:[]}),k=_(""),y=_(""),p=_({}),g=_(null),M=_(!1),j=A(()=>b.value.slice(0,10)),m=A(()=>{const t=[];return i.value.forEach(e=>{t.push(...e.items)}),t});function re(){try{const t=typeof window<"u"?window.localStorage.getItem($()):null;if(t){if(i.value=JSON.parse(t),i.value.length>0){const e=i.value[i.value.length-1];k.value=e.queuePushResult||"",y.value=e.queuePayloadText||""}}else i.value=[]}catch{i.value=[]}}function H(t,e,s,n){const u=e.filter(h=>h.matched!==!1&&h.success!==!1);if(u.length===0)return;const f={taskId:t,items:u,createdAt:Date.now(),queuePushResult:s,queuePayloadText:n},v=i.value.findIndex(h=>h.taskId===t);v>=0?i.value[v]=f:i.value.push(f),typeof window<"u"&&window.localStorage.setItem($(),JSON.stringify(i.value))}function K(t){const e=i.value.length;i.value=i.value.filter(s=>s.taskId!==t),i.value.length!==e&&typeof window<"u"&&window.localStorage.setItem($(),JSON.stringify(i.value))}const E=A(()=>F.value.filter(t=>!m.value.some(e=>e.taskId===t.taskId||e.resultId&&e.resultId===t.resultId))),ie=A(()=>m.value.length>0||E.value.length>0);function ue(t){return t&&p.value[t]?.task?.status||""}function T(t){if(t.error)return t.error;const e=t.taskId;return e&&p.value[e]?.task?.errorMessage||""}function G(t){const e=R(t);return e==="已入队"||e==="处理中"}function Q(t){return t.map(e=>!e.success&&e.matched===!1?{...e,error:e.error||"未匹配到紫鸟店铺",openStoreUrl:void 0}:e)}function ce(){O.value=[...m.value,...E.value]}function w(){ce(),de(O.value)}function de(t){P.value={total:t.length,successCount:t.filter(e=>e.success).length,failedCount:t.filter(e=>!e.success).length,items:t}}function W(t){if(!t)return"";const e=p.value[t],s=e?.line_progress?.info;if(!e?.line_progress?.has_progress||!s){const u=e?.task?.status;return u?`任务状态:${u}`:""}const n=[];return s.phase&&n.push(`阶段:${s.phase}`),s.file_index&&s.file_total&&n.push(`文件:${s.file_index}/${s.file_total}`),s.file_name&&n.push(`当前文件:${s.file_name}`),s.current_line!=null&&s.total_lines!=null&&n.push(`进度:${s.current_line}/${s.total_lines}`),s.current_country&&n.push(`国家:${s.current_country}`),s.current_asin&&n.push(`ASIN:${s.current_asin}`),s.finished_files!=null&&n.push(`已完成文件:${s.finished_files}`),n.join("|")}function D(t){if(!t)return 0;const e=p.value[t]?.line_progress?.info;return e?e.current_line!=null&&e.total_lines&&e.total_lines>0?Math.max(0,Math.min(100,Math.round(e.current_line/e.total_lines*100))):e.finished_files!=null&&e.file_total&&e.file_total>0?Math.max(0,Math.min(100,Math.round(e.finished_files/e.file_total*100))):p.value[t]?.task?.status==="SUCCESS"?100:0:0}function X(t){const e=p.value[t]?.task?.status;return e==="SUCCESS"||e==="FAILED"}function S(){const t=new Set;return i.value.forEach(e=>{e.items.some(s=>s._pushed)&&t.add(e.taskId)}),Array.from(t)}function R(t){const e=t.taskId;if(!e)return"";const s=i.value.find(h=>h.taskId===e);if(!s)return"";const n=ue(e);if(n==="SUCCESS"||n==="FAILED"||t.taskStatus==="SUCCESS"||t.taskStatus==="FAILED")return"已被全局判定为终态";const u=s.items.find(h=>h.resultId===t.resultId||h.sourceFilename===t.sourceFilename),f=p.value[e]?.line_progress;if(n==="RUNNING"&&f?.has_progress&&f?.info?.file_name===t.sourceFilename)return"处理中";const v=p.value[e];return v?.items&&v.items.find(U=>U.sourceFilename===t.sourceFilename)?"本文件解析完毕":u&&u._pushed?"已入队":"未入队"}function Y(t){if(t.downloadUrl||t.success)return!0;const e=t.taskId;return e?p.value[e]?.task?.status==="SUCCESS":!1}async function L(t){const e=t||S();if(e.length)try{const s=await Se(e);for(const n of s.items||[]){const u=n?.task?.id;typeof u=="number"&&u>0&&(p.value[u]=n)}}catch{}}function fe(){return document.visibilityState==="visible"?4500:1e4}function J(t=!1){if(g.value){if(!t)return;clearTimeout(g.value),g.value=null}const e=async()=>{if(g.value=null,M.value){J();return}const s=S();if(!s.length)return;M.value=!0;let n=!1;try{await L(s);for(const u of s)X(u)&&(K(u),n=!0)}finally{M.value=!1}n&&(B(),w()),S().length>0&&J()};t?e():g.value=window.setTimeout(e,fe())}function V(t=!1){g.value&&!t||J(t)}function q(t){const e=t.taskId?p.value[t.taskId]?.task?.status:null;return e==="SUCCESS"?{className:"success",text:"已完成"}:e==="FAILED"?{className:"failed",text:"失败"}:e==="RUNNING"?{className:"running",text:"执行中"}:t.taskStatus==="SUCCESS"?{className:"success",text:"已完成"}:t.taskStatus==="FAILED"?{className:"failed",text:"失败"}:t.taskStatus==="RUNNING"?{className:"running",text:"执行中"}:t.success?{className:"success",text:"已完成"}:{className:"failed",text:"失败"}}function Z(){g.value&&(window.clearTimeout(g.value),g.value=null)}async function ee(t){const e=x();if(!e?.upload_file_to_java)throw new Error("当前桌面端未提供文件上传桥接能力");const s=[];for(const n of t){const u=typeof n=="string"?n:n.absolutePath,f=typeof n=="string"?void 0:n.relativePath,v=await e.upload_file_to_java(u,f);if(!v?.success||!v.data)throw new Error(v?.error||v?.message||`上传失败:${u}`);s.push(v.data)}return s}async function ve(){const t=x();if(!t?.select_brand_xlsx_files){d.warning("当前环境不支持文件选择,请在本机客户端中打开");return}try{const e=await t.select_brand_xlsx_files();if(!e?.length)return;b.value=e,N.value=await ee(e),d.success(`已选择 ${e.length} 个删除品牌文件`)}catch(e){d.error(e instanceof Error?e.message:"选择失败")}}async function he(){const t=x();if(!t?.select_brand_folder){d.warning("当前环境不支持文件夹选择,请在本机客户端中打开");return}try{const e=await t.select_brand_folder();if(!e)return;const s=await Ce(e);if(!s.success||!s.items?.length){d.warning(s.error||"该文件夹下没有 xlsx 文件");return}b.value=s.items.map(n=>n.relativePath||n.absolutePath),N.value=await ee(s.items),d.success(`已选择文件夹内 ${s.items.length} 个 xlsx 文件`)}catch(e){d.error(e instanceof Error?e.message:"选择失败")}}async function _e(){if(!N.value.length){d.warning("请先选择待处理文件");return}try{I.value=!0;const t=await xe({files:N.value.map(n=>({fileKey:n.fileKey,originalFilename:n.originalFilename,relativePath:n.relativePath}))}),e=Q(t.items||[]),s=e.some(n=>!n.matched);k.value="解析完成,等待手动点击打开紫鸟进行单任务处理",y.value="",e.length&&e[0].taskId&&H(e[0].taskId,e,k.value,y.value),w(),s?d.error("存在未匹配到紫鸟店铺的文件,请检查报错内容。这些文件已自动归档至历史记录。"):d.success("删除品牌解析完成,请手动推送"),await B()}catch(t){d.error(t instanceof Error?t.message:"执行失败")}finally{I.value=!1}}async function se(t){const e=t.taskId;if(!e){d.warning("未找到任务ID");return}const s=x(),n=$e(e),u=t.outputFilename||t.sourceFilename||`delete-brand_${e}.xlsx`;if(s?.save_file_from_url){const f=await s.save_file_from_url(n,u);f.success?d.success(`已保存:${f.path||u}`):f.error&&f.error!=="用户取消"&&d.error(f.error);return}window.open(n,"_blank")}async function B(){try{const t=await Ne();F.value=Q(t.items||[]),w(),await L(),V(!0)}catch(t){console.error("loadHistory error:",t)}}function te(t){i.value.forEach(s=>{s.items=s.items.filter(n=>n.resultId!==t)});const e=i.value.length;i.value=i.value.filter(s=>s.items.length>0),i.value.length!==e&&typeof window<"u"&&window.localStorage.setItem($(),JSON.stringify(i.value)),F.value=F.value.filter(s=>s.resultId!==t),w(),S().length===0&&Z()}async function ae(t){try{await Fe(t),te(t),await B(),d.success("已删除")}catch(e){const s=e instanceof Error?e.message:"删除失败";if(typeof s=="string"&&s.includes("记录不存在")){te(t),d.success("已删除");return}d.error(s)}}function ne(t){const e=t.slice(0,5).map(n=>`${n.country}:${n.asin}${n.status?`(${n.status})`:""}`);return t.length-e.length>0?`${e.join("、")} 等 ${t.length} 条`:e.join("、")}function pe(){for(const t of i.value)for(const e of t.items){const s=R(e);if(s==="已入队"||s==="处理中")return!0}return!1}async function ge(t){if(pe()){d.warning("当前存在正在处理的任务,请等待其完成后再推送下一条!");return}t.openStoreUrl&&window.open(t.openStoreUrl,"_blank");const e=t.taskId;if(!e)return;const s=i.value.find(h=>h.taskId===e);if(!s)return;const n=x();if(!n?.enqueue_json){d.error("当前环境未启用 pywebview enqueue_json(浏览器环境不会推送)");return}const u={type:"delete-brand-run",ts:Date.now(),data:{taskId:e,items:[t]}};y.value=JSON.stringify(u,null,2);const f=await n.enqueue_json(u);let v="";if(f?.success){v=`已单独推送文件 ${t.sourceFilename},当前队列长度:${f.queue_size??"-"}`;const h=s.items.find(U=>U.resultId===t.resultId||U.sourceFilename===t.sourceFilename);h&&(h._pushed=!0),H(e,s.items,v,y.value),V(!0)}else v=`推送到 Python 队列失败:${f?.error||"未知错误"}`;k.value=v}return ke(()=>{re();const t=S();t.length>0?L(t).then(()=>{let e=!1;for(const s of t)X(s)&&(K(s),e=!0);e&&w(),V(!0)}):w(),B().catch(()=>{})}),be(()=>{Z()}),(t,e)=>(l(),o("div",De,[we(Ie,{active:"delete-brand"}),a("div",Re,[a("aside",qe,[e[2]||(e[2]=a("div",{class:"section-title"},"选择文件",-1)),a("div",Be,[e[0]||(e[0]=a("div",{class:"hint"},"上传删除品牌 Excel;无论是直接上传文件还是上传文件夹批量导入,都会按每个 Excel 文件名作为店铺名。",-1)),a("div",{class:"btns"},[a("button",{type:"button",class:"opt-btn",onClick:ve},"选择 Excel 文件"),a("button",{type:"button",class:"opt-btn",onClick:he},"选择文件夹")]),a("div",Ue,[b.value.length?(l(),o(C,{key:0},[(l(!0),o(C,null,z(j.value,s=>(l(),o("span",{key:s},r(s),1))),128)),b.value.length>j.value.length?(l(),o("span",Ae," 还有 "+r(b.value.length-j.value.length)+" 个文件未展开显示 ",1)):c("",!0)],64)):(l(),o("span",Me,"暂未选择删除品牌文件"))])]),e[3]||(e[3]=me('
执行说明
',2)),a("div",je,[a("button",{type:"button",class:"btn-run",disabled:I.value,onClick:_e},r(I.value?"解析中...":"开始解析"),9,Le),a("span",Je,r(I.value?"正在读取删除品牌数据,请稍候…":"解析后会在右侧展示按国家分组的去重结果"),1)]),k.value||y.value?(l(),o("div",Ve,[e[1]||(e[1]=a("div",{class:"section-title queue-debug-title"},"Python 队列推送结果",-1)),k.value?(l(),o("div",ze,r(k.value),1)):c("",!0),y.value?(l(),o("pre",Oe,r(y.value),1)):c("",!0)])):c("",!0)]),a("section",He,[e[12]||(e[12]=a("div",{class:"panel-header"},"删除品牌结果",-1)),a("div",Ke,[a("div",Ge,[a("div",Qe,[e[4]||(e[4]=a("span",{class:"summary-label"},"已处理文件",-1)),a("strong",null,r(P.value.total),1)]),a("div",We,[e[5]||(e[5]=a("span",{class:"summary-label"},"成功结果",-1)),a("strong",null,r(P.value.successCount),1)]),a("div",Xe,[e[6]||(e[6]=a("span",{class:"summary-label"},"失败文件",-1)),a("strong",null,r(P.value.failedCount),1)])]),a("div",Ye,[e[11]||(e[11]=a("div",{class:"result-list-header"},[a("span",null,"删除品牌结果列表")],-1)),ie.value?(l(),o(C,{key:1},[m.value.length?(l(),o("div",es,[e[8]||(e[8]=a("div",{class:"result-subsection-title"},"当前任务",-1)),a("ul",ss,[(l(!0),o(C,null,z(m.value,s=>(l(),o("li",{key:`current-${s.taskId||s.resultId||s.sourceFilename}`,class:"task-item split-result-item"},[a("div",ts,[a("span",{class:"id",title:s.sourceFilename},r(s.sourceFilename||"-"),9,as),a("div",ns,"店铺名:"+r(s.shopName||"-"),1),a("div",ls,"匹配结果:"+r(s.matched?`已匹配 ${s.shopId||""}`:"未匹配到紫鸟店铺"),1),s.platform?(l(),o("div",os,"平台:"+r(s.platform),1)):c("",!0),R(s)?(l(),o("div",rs,"队列状态:"+r(R(s)),1)):c("",!0),s.countryCount!==void 0&&s.matched?(l(),o("div",is,"国家数:"+r(s.countryCount),1)):c("",!0),s.totalRows!==void 0&&s.matched?(l(),o("div",us,"去重后 "+r(s.totalRows)+" 条",1)):c("",!0),G(s)?(l(),o("div",cs,[a("div",ds,[e[7]||(e[7]=a("span",null,"任务进度",-1)),a("span",null,r(D(s.taskId))+"%",1)]),a("div",fs,[a("div",{class:"delete-brand-progress-bar-fill",style:le({width:`${D(s.taskId)}%`})},null,4)]),a("div",vs,r(W(s.taskId)),1)])):c("",!0),s.previewRows?.length?(l(),o("div",hs,r(ne(s.previewRows)),1)):c("",!0),T(s)?(l(),o("div",_s,"错误信息:"+r(T(s)),1)):c("",!0)]),a("div",ps,[a("span",{class:oe(["status",q(s).className])},r(q(s).text),3),s.openStoreUrl?(l(),o("button",{key:0,type:"button",class:"download",onClick:n=>ge(s)}," 打开紫鸟 ",8,gs)):c("",!0),Y(s)?(l(),o("button",{key:1,type:"button",class:"download",onClick:n=>se(s)}," 下载 ",8,ys)):c("",!0),s.resultId?(l(),o("button",{key:2,type:"button",class:"btn-delete",onClick:n=>ae(s.resultId)}," 删除 ",8,ks)):c("",!0)])]))),128))])])):c("",!0),E.value.length?(l(),o("div",bs,[e[10]||(e[10]=a("div",{class:"result-subsection-title"},"历史记录",-1)),a("ul",ws,[(l(!0),o(C,null,z(E.value,s=>(l(),o("li",{key:`history-${s.taskId||s.resultId||s.sourceFilename}`,class:"task-item split-result-item"},[a("div",Is,[a("span",{class:"id",title:s.sourceFilename},r(s.sourceFilename||"-"),9,ms),a("div",Ss,"店铺名:"+r(s.shopName||"-"),1),a("div",Cs,"匹配结果:"+r(s.matched?`已匹配 ${s.shopId||""}`:"未匹配到紫鸟店铺"),1),s.platform?(l(),o("div",xs,"平台:"+r(s.platform),1)):c("",!0),s.countryCount!==void 0&&s.matched?(l(),o("div",$s,"国家数:"+r(s.countryCount),1)):c("",!0),s.totalRows!==void 0&&s.matched?(l(),o("div",Ns,"去重后 "+r(s.totalRows)+" 条",1)):c("",!0),G(s)?(l(),o("div",Fs,[a("div",Ps,[e[9]||(e[9]=a("span",null,"任务进度",-1)),a("span",null,r(D(s.taskId))+"%",1)]),a("div",Es,[a("div",{class:"delete-brand-progress-bar-fill",style:le({width:`${D(s.taskId)}%`})},null,4)]),a("div",Ts,r(W(s.taskId)),1)])):c("",!0),s.previewRows?.length?(l(),o("div",Ds,r(ne(s.previewRows)),1)):c("",!0),T(s)?(l(),o("div",Rs,"错误信息:"+r(T(s)),1)):c("",!0)]),a("div",qs,[a("span",{class:oe(["status",q(s).className])},r(q(s).text),3),Y(s)?(l(),o("button",{key:0,type:"button",class:"download",onClick:n=>se(s)}," 下载 ",8,Bs)):c("",!0),s.resultId?(l(),o("button",{key:1,type:"button",class:"btn-delete",onClick:n=>ae(s.resultId)}," 删除 ",8,Us)):c("",!0)])]))),128))])])):c("",!0)],64)):(l(),o("div",Ze," 暂无删除品牌结果,完成解析后会在这里展示按国家分组的去重结果 "))])])])])]))}}),Ms=Pe(As,[["__scopeId","data-v-1ad349b9"]]);Ee(Ms).use(Te).mount("#app");
+import{d as ye,o as ke,J as we,a as l,c as o,b as be,B as Ie,e as a,F as x,r as z,t as r,f as c,K as me,L as le,n as oe,g as _,h as A,M as Se,i as C,E as d,j as xe,N as Ce,O as $e,P as Ne,Q as Fe,_ as Pe,q as Ee,s as Te}from"./pywebview-Dee3nQjE.js";const Re={class:"page-shell module-page"},De={class:"main-content"},Ue={class:"left-panel"},qe={class:"upload-zone"},Be={class:"selected-files clean-placeholder split-selected-files"},Ae={key:0,class:"more-line"},Me={key:1},je={class:"run-row"},Le=["disabled"],Je={class:"loading-msg"},Ve={key:0,class:"queue-debug-card"},ze={key:0,class:"queue-debug-line"},Oe={key:1,class:"queue-debug-payload"},He={class:"right-panel"},Ke={class:"task-list-wrap"},Ge={class:"clean-result-summary"},Qe={class:"summary-card"},We={class:"summary-card"},Xe={class:"summary-card"},Ye={class:"result-list-wrap"},Ze={key:0,class:"empty-tasks"},es={key:0,class:"result-subsection"},ss={class:"task-list clean-result-list"},ts={class:"left split-result-main"},as=["title"],ns={class:"files"},ls={class:"files"},os={key:0,class:"files"},rs={key:1,class:"files"},is={key:2,class:"files"},us={key:3,class:"time"},cs={key:4,class:"delete-brand-progress-block"},ds={class:"delete-brand-progress-header"},fs={class:"delete-brand-progress-bar"},vs={class:"files delete-brand-progress"},hs={key:5,class:"files split-entry-list delete-brand-preview"},_s={key:6,class:"files"},ps={class:"task-right split-result-actions"},gs=["onClick"],ys=["onClick"],ks=["onClick"],ws={key:1,class:"result-subsection"},bs={class:"task-list clean-result-list"},Is={class:"left split-result-main"},ms=["title"],Ss={class:"files"},xs={class:"files"},Cs={key:0,class:"files"},$s={key:1,class:"files"},Ns={key:2,class:"time"},Fs={key:3,class:"delete-brand-progress-block"},Ps={class:"delete-brand-progress-header"},Es={class:"delete-brand-progress-bar"},Ts={class:"files delete-brand-progress"},Rs={key:4,class:"files split-entry-list delete-brand-preview"},Ds={key:5,class:"files"},Us={class:"task-right split-result-actions"},qs=["onClick"],Bs=["onClick"],As=ye({__name:"BrandDeleteBrandTab",setup(js){function $(){return`delete-brand:current-tasks:${typeof window<"u"&&window.localStorage.getItem("uid")||"0"}`}const w=_([]),N=_([]),I=_(!1),O=_([]),i=_([]),F=_([]),P=_({total:0,successCount:0,failedCount:0,items:[]}),k=_(""),y=_(""),p=_({}),g=_(null),M=_(!1),j=A(()=>w.value.slice(0,10)),m=A(()=>{const t=[];return i.value.forEach(s=>{t.push(...s.items)}),t});function re(){try{const t=typeof window<"u"?window.localStorage.getItem($()):null;if(t){if(i.value=JSON.parse(t),i.value.length>0){const s=i.value[i.value.length-1];k.value=s.queuePushResult||"",y.value=s.queuePayloadText||""}}else i.value=[]}catch{i.value=[]}}function H(t,s,e,n){const u=s.filter(f=>f.matched!==!1&&f.success!==!1);if(u.length===0)return;const v={taskId:t,items:u,createdAt:Date.now(),queuePushResult:e,queuePayloadText:n},h=i.value.findIndex(f=>f.taskId===t);h>=0?i.value[h]=v:i.value.push(v),typeof window<"u"&&window.localStorage.setItem($(),JSON.stringify(i.value))}function K(t){const s=i.value.length;i.value=i.value.filter(e=>e.taskId!==t),i.value.length!==s&&typeof window<"u"&&window.localStorage.setItem($(),JSON.stringify(i.value))}const E=A(()=>F.value.filter(t=>!m.value.some(s=>s.taskId===t.taskId||s.resultId&&s.resultId===t.resultId))),ie=A(()=>m.value.length>0||E.value.length>0);function ue(t){return t&&p.value[t]?.task?.status||""}function T(t){if(t.error)return t.error;const s=t.taskId;return s&&p.value[s]?.task?.errorMessage||""}function G(t){const s=D(t);return s==="已入队"||s==="处理中"}function Q(t){return t.map(s=>!s.success&&s.matched===!1?{...s,error:s.error||"未匹配到紫鸟店铺",openStoreUrl:void 0}:s)}function ce(){O.value=[...m.value,...E.value]}function b(){ce(),de(O.value)}function de(t){P.value={total:t.length,successCount:t.filter(s=>s.success).length,failedCount:t.filter(s=>!s.success).length,items:t}}function W(t){if(!t)return"";const s=p.value[t],e=s?.line_progress?.info;if(!s?.line_progress?.has_progress||!e){const u=s?.task?.status;return u?`任务状态:${u}`:""}const n=[];return e.phase&&n.push(`阶段:${e.phase}`),e.file_index&&e.file_total&&n.push(`文件:${e.file_index}/${e.file_total}`),e.file_name&&n.push(`当前文件:${e.file_name}`),e.current_line!=null&&e.total_lines!=null&&n.push(`进度:${e.current_line}/${e.total_lines}`),e.current_country&&n.push(`国家:${e.current_country}`),e.current_asin&&n.push(`ASIN:${e.current_asin}`),e.finished_files!=null&&n.push(`已完成文件:${e.finished_files}`),n.join("|")}function R(t){if(!t)return 0;const s=p.value[t]?.line_progress?.info;return s?s.current_line!=null&&s.total_lines&&s.total_lines>0?Math.max(0,Math.min(100,Math.round(s.current_line/s.total_lines*100))):s.finished_files!=null&&s.file_total&&s.file_total>0?Math.max(0,Math.min(100,Math.round(s.finished_files/s.file_total*100))):p.value[t]?.task?.status==="SUCCESS"?100:0:0}function X(t){const s=p.value[t]?.task?.status;return s==="SUCCESS"||s==="FAILED"}function S(){const t=new Set;return i.value.forEach(s=>{s.items.some(e=>e._pushed)&&t.add(s.taskId)}),Array.from(t)}function D(t){const s=t.taskId;if(!s)return"";const e=i.value.find(f=>f.taskId===s);if(!e)return"";const n=ue(s);if(n==="SUCCESS"||n==="FAILED"||t.taskStatus==="SUCCESS"||t.taskStatus==="FAILED")return"已被全局判定为终态";const u=e.items.find(f=>f.resultId===t.resultId||f.sourceFilename===t.sourceFilename),v=p.value[s]?.line_progress;if(n==="RUNNING"&&v?.has_progress&&v?.info?.file_name===t.sourceFilename)return"处理中";const h=p.value[s];return h?.items&&h.items.find(B=>B.sourceFilename===t.sourceFilename)?"本文件解析完毕":u&&u._pushed?"已入队":"未入队"}function Y(t){if(t.downloadUrl||t.success)return!0;const s=t.taskId;return s?p.value[s]?.task?.status==="SUCCESS":!1}async function L(t){const s=t||S();if(s.length)try{const e=await Se(s);for(const n of e.items||[]){const u=n?.task?.id;typeof u=="number"&&u>0&&(p.value[u]=n)}}catch{}}function fe(){return document.visibilityState==="visible"?4500:1e4}function J(t=!1){if(g.value){if(!t)return;clearTimeout(g.value),g.value=null}const s=async()=>{if(g.value=null,M.value){J();return}const e=S();if(!e.length)return;M.value=!0;let n=!1;try{await L(e);for(const u of e)X(u)&&(K(u),n=!0)}finally{M.value=!1}n&&(q(),b()),S().length>0&&J()};t?s():g.value=window.setTimeout(s,fe())}function V(t=!1){g.value&&!t||J(t)}function U(t){const s=t.taskId?p.value[t.taskId]?.task?.status:null;return s==="SUCCESS"?{className:"success",text:"已完成"}:s==="FAILED"?{className:"failed",text:"失败"}:s==="RUNNING"?{className:"running",text:"执行中"}:t.taskStatus==="SUCCESS"?{className:"success",text:"已完成"}:t.taskStatus==="FAILED"?{className:"failed",text:"失败"}:t.taskStatus==="RUNNING"?{className:"running",text:"执行中"}:t.success?{className:"success",text:"已完成"}:{className:"failed",text:"失败"}}function Z(){g.value&&(window.clearTimeout(g.value),g.value=null)}async function ee(t){const s=C();if(!s?.upload_file_to_java)throw new Error("当前桌面端未提供文件上传桥接能力");const e=[];for(const n of t){const u=typeof n=="string"?n:n.absolutePath,v=typeof n=="string"?void 0:n.relativePath,h=await s.upload_file_to_java(u,v);if(!h?.success||!h.data)throw new Error(h?.error||h?.message||`上传失败:${u}`);e.push(h.data)}return e}async function ve(){const t=C();if(!t?.select_brand_xlsx_files){d.warning("当前环境不支持文件选择,请在本机客户端中打开");return}try{const s=await t.select_brand_xlsx_files();if(!s?.length)return;w.value=s,N.value=await ee(s),d.success(`已选择 ${s.length} 个删除品牌文件`)}catch(s){d.error(s instanceof Error?s.message:"选择失败")}}async function he(){const t=C();if(!t?.select_brand_folder){d.warning("当前环境不支持文件夹选择,请在本机客户端中打开");return}try{const s=await t.select_brand_folder();if(!s)return;const e=await xe(s);if(!e.success||!e.items?.length){d.warning(e.error||"该文件夹下没有 xlsx 文件");return}w.value=e.items.map(n=>n.relativePath||n.absolutePath),N.value=await ee(e.items),d.success(`已选择文件夹内 ${e.items.length} 个 xlsx 文件`)}catch(s){d.error(s instanceof Error?s.message:"选择失败")}}async function _e(){if(!N.value.length){d.warning("请先选择待处理文件");return}try{I.value=!0;const t=await Ce({files:N.value.map(n=>({fileKey:n.fileKey,originalFilename:n.originalFilename,relativePath:n.relativePath}))}),s=Q(t.items||[]),e=s.some(n=>!n.matched);k.value="解析完成,等待手动点击打开紫鸟进行单任务处理",y.value="",s.length&&s[0].taskId&&H(s[0].taskId,s,k.value,y.value),b(),e?d.error("存在未匹配到紫鸟店铺的文件,请检查报错内容。这些文件已自动归档至历史记录。"):d.success("删除品牌解析完成,请手动推送"),await q()}catch(t){d.error(t instanceof Error?t.message:"执行失败")}finally{I.value=!1}}async function se(t){const s=t.taskId;if(!s){d.warning("未找到任务ID");return}const e=C(),n=$e(s),u=t.outputFilename||t.sourceFilename||`delete-brand_${s}.xlsx`;if(e?.save_file_from_url){const v=await e.save_file_from_url(n,u);v.success?d.success(`已保存:${v.path||u}`):v.error&&v.error!=="用户取消"&&d.error(v.error);return}window.open(n,"_blank")}async function q(){try{const t=await Ne();F.value=Q(t.items||[]),b(),await L(),V(!0)}catch(t){console.error("loadHistory error:",t)}}function te(t){i.value.forEach(e=>{e.items=e.items.filter(n=>n.resultId!==t)});const s=i.value.length;i.value=i.value.filter(e=>e.items.length>0),i.value.length!==s&&typeof window<"u"&&window.localStorage.setItem($(),JSON.stringify(i.value)),F.value=F.value.filter(e=>e.resultId!==t),b(),S().length===0&&Z()}async function ae(t){try{await Fe(t),te(t),await q(),d.success("已删除")}catch(s){const e=s instanceof Error?s.message:"删除失败";if(typeof e=="string"&&e.includes("记录不存在")){te(t),d.success("已删除");return}d.error(e)}}function ne(t){const s=t.slice(0,5).map(n=>`${n.country}:${n.asin}${n.status?`(${n.status})`:""}`);return t.length-s.length>0?`${s.join("、")} 等 ${t.length} 条`:s.join("、")}function pe(){for(const t of i.value)for(const s of t.items){const e=D(s);if(e==="已入队"||e==="处理中")return!0}return!1}async function ge(t){if(pe()){d.warning("当前存在正在处理的任务,请等待其完成后再推送下一条!");return}const s=C();if(t.openStoreUrl)if(s?.open_external_url){const f=await s.open_external_url(t.openStoreUrl);if(!f?.success){d.error(f?.error||"打开紫鸟失败");return}}else window.open(t.openStoreUrl,"_self");const e=t.taskId;if(!e)return;const n=i.value.find(f=>f.taskId===e);if(!n)return;if(!s?.enqueue_json){d.error("当前环境未启用 pywebview enqueue_json(浏览器环境不会推送)");return}const u={type:"delete-brand-run",ts:Date.now(),data:{taskId:e,items:[t]}};y.value=JSON.stringify(u,null,2);const v=await s.enqueue_json(u);let h="";if(v?.success){h=`已单独推送文件 ${t.sourceFilename},当前队列长度:${v.queue_size??"-"}`;const f=n.items.find(B=>B.resultId===t.resultId||B.sourceFilename===t.sourceFilename);f&&(f._pushed=!0),H(e,n.items,h,y.value),V(!0)}else h=`推送到 Python 队列失败:${v?.error||"未知错误"}`;k.value=h}return ke(()=>{re();const t=S();t.length>0?L(t).then(()=>{let s=!1;for(const e of t)X(e)&&(K(e),s=!0);s&&b(),V(!0)}):b(),q().catch(()=>{})}),we(()=>{Z()}),(t,s)=>(l(),o("div",Re,[be(Ie,{active:"delete-brand"}),a("div",De,[a("aside",Ue,[s[2]||(s[2]=a("div",{class:"section-title"},"选择文件",-1)),a("div",qe,[s[0]||(s[0]=a("div",{class:"hint"},"上传删除品牌 Excel;无论是直接上传文件还是上传文件夹批量导入,都会按每个 Excel 文件名作为店铺名。",-1)),a("div",{class:"btns"},[a("button",{type:"button",class:"opt-btn",onClick:ve},"选择 Excel 文件"),a("button",{type:"button",class:"opt-btn",onClick:he},"选择文件夹")]),a("div",Be,[w.value.length?(l(),o(x,{key:0},[(l(!0),o(x,null,z(j.value,e=>(l(),o("span",{key:e},r(e),1))),128)),w.value.length>j.value.length?(l(),o("span",Ae," 还有 "+r(w.value.length-j.value.length)+" 个文件未展开显示 ",1)):c("",!0)],64)):(l(),o("span",Me,"暂未选择删除品牌文件"))])]),s[3]||(s[3]=me('执行说明
',2)),a("div",je,[a("button",{type:"button",class:"btn-run",disabled:I.value,onClick:_e},r(I.value?"解析中...":"开始解析"),9,Le),a("span",Je,r(I.value?"正在读取删除品牌数据,请稍候…":"解析后会在右侧展示按国家分组的去重结果"),1)]),k.value||y.value?(l(),o("div",Ve,[s[1]||(s[1]=a("div",{class:"section-title queue-debug-title"},"Python 队列推送结果",-1)),k.value?(l(),o("div",ze,r(k.value),1)):c("",!0),y.value?(l(),o("pre",Oe,r(y.value),1)):c("",!0)])):c("",!0)]),a("section",He,[s[12]||(s[12]=a("div",{class:"panel-header"},"删除品牌结果",-1)),a("div",Ke,[a("div",Ge,[a("div",Qe,[s[4]||(s[4]=a("span",{class:"summary-label"},"已处理文件",-1)),a("strong",null,r(P.value.total),1)]),a("div",We,[s[5]||(s[5]=a("span",{class:"summary-label"},"成功结果",-1)),a("strong",null,r(P.value.successCount),1)]),a("div",Xe,[s[6]||(s[6]=a("span",{class:"summary-label"},"失败文件",-1)),a("strong",null,r(P.value.failedCount),1)])]),a("div",Ye,[s[11]||(s[11]=a("div",{class:"result-list-header"},[a("span",null,"删除品牌结果列表")],-1)),ie.value?(l(),o(x,{key:1},[m.value.length?(l(),o("div",es,[s[8]||(s[8]=a("div",{class:"result-subsection-title"},"当前任务",-1)),a("ul",ss,[(l(!0),o(x,null,z(m.value,e=>(l(),o("li",{key:`current-${e.taskId||e.resultId||e.sourceFilename}`,class:"task-item split-result-item"},[a("div",ts,[a("span",{class:"id",title:e.sourceFilename},r(e.sourceFilename||"-"),9,as),a("div",ns,"店铺名:"+r(e.shopName||"-"),1),a("div",ls,"匹配结果:"+r(e.matched?`已匹配 ${e.shopId||""}`:"未匹配到紫鸟店铺"),1),e.platform?(l(),o("div",os,"平台:"+r(e.platform),1)):c("",!0),D(e)?(l(),o("div",rs,"队列状态:"+r(D(e)),1)):c("",!0),e.countryCount!==void 0&&e.matched?(l(),o("div",is,"国家数:"+r(e.countryCount),1)):c("",!0),e.totalRows!==void 0&&e.matched?(l(),o("div",us,"去重后 "+r(e.totalRows)+" 条 ",1)):c("",!0),G(e)?(l(),o("div",cs,[a("div",ds,[s[7]||(s[7]=a("span",null,"任务进度",-1)),a("span",null,r(R(e.taskId))+"%",1)]),a("div",fs,[a("div",{class:"delete-brand-progress-bar-fill",style:le({width:`${R(e.taskId)}%`})},null,4)]),a("div",vs,r(W(e.taskId)),1)])):c("",!0),e.previewRows?.length?(l(),o("div",hs,r(ne(e.previewRows)),1)):c("",!0),T(e)?(l(),o("div",_s,"错误信息:"+r(T(e)),1)):c("",!0)]),a("div",ps,[a("span",{class:oe(["status",U(e).className])},r(U(e).text),3),e.openStoreUrl?(l(),o("button",{key:0,type:"button",class:"download",onClick:n=>ge(e)}," 打开紫鸟 ",8,gs)):c("",!0),Y(e)?(l(),o("button",{key:1,type:"button",class:"download",onClick:n=>se(e)}," 下载 ",8,ys)):c("",!0),e.resultId?(l(),o("button",{key:2,type:"button",class:"btn-delete",onClick:n=>ae(e.resultId)}," 删除 ",8,ks)):c("",!0)])]))),128))])])):c("",!0),E.value.length?(l(),o("div",ws,[s[10]||(s[10]=a("div",{class:"result-subsection-title"},"历史记录",-1)),a("ul",bs,[(l(!0),o(x,null,z(E.value,e=>(l(),o("li",{key:`history-${e.taskId||e.resultId||e.sourceFilename}`,class:"task-item split-result-item"},[a("div",Is,[a("span",{class:"id",title:e.sourceFilename},r(e.sourceFilename||"-"),9,ms),a("div",Ss,"店铺名:"+r(e.shopName||"-"),1),a("div",xs,"匹配结果:"+r(e.matched?`已匹配 ${e.shopId||""}`:"未匹配到紫鸟店铺"),1),e.platform?(l(),o("div",Cs,"平台:"+r(e.platform),1)):c("",!0),e.countryCount!==void 0&&e.matched?(l(),o("div",$s,"国家数:"+r(e.countryCount),1)):c("",!0),e.totalRows!==void 0&&e.matched?(l(),o("div",Ns,"去重后 "+r(e.totalRows)+" 条 ",1)):c("",!0),G(e)?(l(),o("div",Fs,[a("div",Ps,[s[9]||(s[9]=a("span",null,"任务进度",-1)),a("span",null,r(R(e.taskId))+"%",1)]),a("div",Es,[a("div",{class:"delete-brand-progress-bar-fill",style:le({width:`${R(e.taskId)}%`})},null,4)]),a("div",Ts,r(W(e.taskId)),1)])):c("",!0),e.previewRows?.length?(l(),o("div",Rs,r(ne(e.previewRows)),1)):c("",!0),T(e)?(l(),o("div",Ds,"错误信息:"+r(T(e)),1)):c("",!0)]),a("div",Us,[a("span",{class:oe(["status",U(e).className])},r(U(e).text),3),Y(e)?(l(),o("button",{key:0,type:"button",class:"download",onClick:n=>se(e)}," 下载 ",8,qs)):c("",!0),e.resultId?(l(),o("button",{key:1,type:"button",class:"btn-delete",onClick:n=>ae(e.resultId)}," 删除 ",8,Bs)):c("",!0)])]))),128))])])):c("",!0)],64)):(l(),o("div",Ze," 暂无删除品牌结果,完成解析后会在这里展示按国家分组的去重结果 "))])])])])]))}}),Ms=Pe(As,[["__scopeId","data-v-401c4349"]]);Ee(Ms).use(Te).mount("#app");
diff --git a/app/main.py b/app/main.py
index 721bd10..b1225f5 100644
--- a/app/main.py
+++ b/app/main.py
@@ -16,6 +16,7 @@ import webview
import threading
import time
import requests
+import subprocess
with open("version.txt","w",encoding="utf-8") as file:
@@ -234,6 +235,21 @@ class WindowAPI:
except Exception as e:
return {'success': False, 'error': str(e)}
+ def open_external_url(self, url):
+ if not url or not str(url).strip():
+ return {'success': False, 'error': '链接为空'}
+ target = str(url).strip()
+ try:
+ if sys.platform.startswith('win'):
+ os.startfile(target)
+ elif sys.platform == 'darwin':
+ subprocess.Popen(['open', target])
+ else:
+ subprocess.Popen(['xdg-open', target])
+ return {'success': True}
+ except Exception as e:
+ return {'success': False, 'error': str(e)}
+
def enqueue_json(self, data):
"""保存任务到队列"""
try:
@@ -350,7 +366,7 @@ def main():
)
api = WindowAPI(window)
window.expose(api.close, api.minimize, api.maximize, api.toggle_maximize, generate_images, api.save_image, api.save_image_to_folder, api.select_folder, api.select_brand_xlsx_files, api.select_brand_folder,
- api.save_file_from_url, api.save_template_xlsx,api.save_template_zip,api.upload_file_to_java,api.enqueue_json)
+ api.save_file_from_url, api.save_template_xlsx,api.save_template_zip,api.upload_file_to_java,api.open_external_url,api.enqueue_json)
webview.start(
debug=True,
storage_path=cache_path,
diff --git a/app/new_web_source/delete-brand.html b/app/new_web_source/delete-brand.html
index 2e6dead..8135c9f 100644
--- a/app/new_web_source/delete-brand.html
+++ b/app/new_web_source/delete-brand.html
@@ -7,7 +7,7 @@
-
+
diff --git a/frontend-vue/src/pages/brand/components/BrandDeleteBrandTab.vue b/frontend-vue/src/pages/brand/components/BrandDeleteBrandTab.vue
index b07ab72..2db5a03 100644
--- a/frontend-vue/src/pages/brand/components/BrandDeleteBrandTab.vue
+++ b/frontend-vue/src/pages/brand/components/BrandDeleteBrandTab.vue
@@ -99,27 +99,27 @@
当前任务
- -
+ class="task-item split-result-item">
{{ item.sourceFilename || '-' }}
店铺名:{{ item.shopName || '-' }}
匹配结果:{{ item.matched ? `已匹配 ${item.shopId || ''}` : '未匹配到紫鸟店铺' }}
平台:{{ item.platform }}
队列状态:{{ getQueueStatus(item) }}
-
国家数:{{ item.countryCount }}
-
去重后 {{ item.totalRows }} 条
+
国家数:{{ item.countryCount
+ }}
+
去重后 {{ item.totalRows }} 条
+
任务进度
{{ formatProgressPercent(item.taskId!) }}%
{{ formatProgress(item.taskId!) }}
@@ -133,28 +133,15 @@
{{ getTaskStatusInfo(item).text }}
-
@@ -165,26 +152,26 @@
历史记录
- -
+ class="task-item split-result-item">
{{ item.sourceFilename || '-' }}
店铺名:{{ item.shopName || '-' }}
匹配结果:{{ item.matched ? `已匹配 ${item.shopId || ''}` : '未匹配到紫鸟店铺' }}
平台:{{ item.platform }}
-
国家数:{{ item.countryCount }}
-
去重后 {{ item.totalRows }} 条
+
国家数:{{ item.countryCount
+ }}
+
去重后 {{ item.totalRows }} 条
+
任务进度
{{ formatProgressPercent(item.taskId!) }}%
{{ formatProgress(item.taskId!) }}
@@ -199,20 +186,12 @@
{{ getTaskStatusInfo(item).text }}
-
+
下载
-
+
删除
@@ -303,9 +282,9 @@ function saveSessionTask(taskId: number, items: DeleteBrandResultItem[], pushRes
const validItems = items.filter(i => i.matched !== false && i.success !== false)
if (validItems.length === 0) return
- const newTask: StoredCurrentTask = {
- taskId,
- items: validItems,
+ const newTask: StoredCurrentTask = {
+ taskId,
+ items: validItems,
createdAt: Date.now(),
queuePushResult: pushResult,
queuePayloadText: payloadText
@@ -454,7 +433,7 @@ function getQueueStatus(item: DeleteBrandResultItem) {
if (!taskId) return ''
const sessionTask = sessionTasks.value.find(t => t.taskId === taskId)
if (!sessionTask) return ''
-
+
const status = getTaskStatus(taskId)
if (status === 'SUCCESS' || status === 'FAILED' || item.taskStatus === 'SUCCESS' || item.taskStatus === 'FAILED') {
return '已被全局判定为终态'
@@ -463,7 +442,7 @@ function getQueueStatus(item: DeleteBrandResultItem) {
const sessionItem = sessionTask.items.find(i => i.resultId === item.resultId || i.sourceFilename === item.sourceFilename)
const info = taskDetails.value[taskId]?.line_progress
-
+
// 此时这个特定文件是否正好被后端报了进度名
if (status === 'RUNNING' && info?.has_progress && info?.info?.file_name === item.sourceFilename) {
return '处理中'
@@ -489,7 +468,7 @@ function getQueueStatus(item: DeleteBrandResultItem) {
function canDownloadTaskResult(item: DeleteBrandResultItem) {
// 核心修复:只要单个项目已经成功(后端返回了下载地址或标记为成功),就允许下载,哪怕整包任务还在“执行中”
if (item.downloadUrl || item.success) return true
-
+
const taskId = item.taskId
if (!taskId) return false
const status = taskDetails.value[taskId]?.task?.status
@@ -523,7 +502,7 @@ function scheduleNextPoll(immediate = false) {
clearTimeout(pollTimer.value)
pollTimer.value = null
}
-
+
const executePoll = async () => {
pollTimer.value = null
if (pollingInFlight.value) {
@@ -723,7 +702,7 @@ async function loadHistory() {
const response = await getDeleteBrandHistory()
historyItems.value = normalizeDeleteBrandItems(response.items || [])
syncResultState()
-
+
// 关键:页面加载历史后,立刻拉取详情触发状态机
await refreshTaskDetails()
ensurePolling(true)
@@ -793,8 +772,17 @@ async function triggerItemRun(item: DeleteBrandResultItem) {
return
}
+ const api = getPywebviewApi()
if (item.openStoreUrl) {
- window.open(item.openStoreUrl, '_blank')
+ if (api?.open_external_url) {
+ const openResult = await api.open_external_url(item.openStoreUrl)
+ if (!openResult?.success) {
+ ElMessage.error(openResult?.error || '打开紫鸟失败')
+ return
+ }
+ } else {
+ window.open(item.openStoreUrl, '_self')
+ }
}
const taskId = item.taskId
@@ -803,7 +791,6 @@ async function triggerItemRun(item: DeleteBrandResultItem) {
const sessionTask = sessionTasks.value.find(t => t.taskId === taskId)
if (!sessionTask) return
- const api = getPywebviewApi()
if (!api?.enqueue_json) {
ElMessage.error('当前环境未启用 pywebview enqueue_json(浏览器环境不会推送)')
return
@@ -812,7 +799,7 @@ async function triggerItemRun(item: DeleteBrandResultItem) {
const payload = {
type: 'delete-brand-run',
ts: Date.now(),
- data: {
+ data: {
taskId: taskId,
items: [item]
},
@@ -823,14 +810,14 @@ async function triggerItemRun(item: DeleteBrandResultItem) {
let qpr = ''
if (pushResult?.success) {
qpr = `已单独推送文件 ${item.sourceFilename},当前队列长度:${pushResult.queue_size ?? '-'}`
-
+
// Mark the item as pushed
const sessionItem = sessionTask.items.find(i => i.resultId === item.resultId || i.sourceFilename === item.sourceFilename)
if (sessionItem) {
(sessionItem as any)._pushed = true
}
saveSessionTask(taskId, sessionTask.items, qpr, queuePayloadText.value)
-
+
ensurePolling(true)
} else {
qpr = `推送到 Python 队列失败:${pushResult?.error || '未知错误'}`
@@ -872,71 +859,481 @@ onUnmounted(() => {
diff --git a/frontend-vue/src/shared/bridges/pywebview.ts b/frontend-vue/src/shared/bridges/pywebview.ts
index 2a08292..fe4dc0f 100644
--- a/frontend-vue/src/shared/bridges/pywebview.ts
+++ b/frontend-vue/src/shared/bridges/pywebview.ts
@@ -21,6 +21,7 @@ export interface PywebviewApi {
save_template_xlsx?: () => Promise<{ success: boolean; path?: string; error?: string }>
save_template_zip?: () => Promise<{ success: boolean; path?: string; error?: string }>
enqueue_json?: (data: unknown) => Promise<{ success: boolean; queue_size?: number; error?: string }>
+ open_external_url?: (url: string) => Promise<{ success: boolean; error?: string }>
}
declare global {