From 6eb0f334218808ceab7ad79c811498d266e74bab Mon Sep 17 00:00:00 2001 From: super <2903208875@qq.com> Date: Tue, 31 Mar 2026 23:12:46 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=A0=E9=99=A4=E6=A8=A1=E5=9D=97=20=20?= =?UTF-8?q?=E5=8F=96=E6=B6=88=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/main.py | 18 +-- .../aiimage-backend.log.2026-03-30.0.gz | Bin 0 -> 37214 bytes .../controller/DeleteBrandRunController.java | 10 ++ .../vo/DeleteBrandTaskDeletionStatusVo.java | 17 +++ .../service/DeleteBrandRunService.java | 103 ++++++++++++++---- .../model/vo/ZiniaoShopMatchResultVo.java | 30 +++++ .../service/ZiniaoShopSwitchService.java | 53 +++++++++ .../brand/components/BrandDeleteBrandTab.vue | 88 +++++++++++---- frontend-vue/src/shared/bridges/pywebview.ts | 1 - 9 files changed, 260 insertions(+), 60 deletions(-) create mode 100644 backend-java/aiimage-backend.log.2026-03-30.0.gz create mode 100644 backend-java/src/main/java/com/nanri/aiimage/modules/deletebrand/model/vo/DeleteBrandTaskDeletionStatusVo.java create mode 100644 backend-java/src/main/java/com/nanri/aiimage/modules/ziniao/model/vo/ZiniaoShopMatchResultVo.java create mode 100644 backend-java/src/main/java/com/nanri/aiimage/modules/ziniao/service/ZiniaoShopSwitchService.java diff --git a/app/main.py b/app/main.py index b1225f5..721bd10 100644 --- a/app/main.py +++ b/app/main.py @@ -16,7 +16,6 @@ import webview import threading import time import requests -import subprocess with open("version.txt","w",encoding="utf-8") as file: @@ -235,21 +234,6 @@ 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: @@ -366,7 +350,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.open_external_url,api.enqueue_json) + api.save_file_from_url, api.save_template_xlsx,api.save_template_zip,api.upload_file_to_java,api.enqueue_json) webview.start( debug=True, storage_path=cache_path, diff --git a/backend-java/aiimage-backend.log.2026-03-30.0.gz b/backend-java/aiimage-backend.log.2026-03-30.0.gz new file mode 100644 index 0000000000000000000000000000000000000000..f900a6e603bf3b590f7b65af1381e719b0d83f39 GIT binary patch literal 37214 zcmb5VbyQrz*CiT3IuN8GxYM|Ma6)6jHMlqK?h>F04#6El(BSUw?oM!bcYB@R_sz_j zS@YI)~&8Ob?>RFv-dtl5(aqlpTF0m7A;!}$poq!e)wr$yQ^|V1~T(~9Ck-X z3k#OwW+xMRcKXlE=}9FF0AipV8y%=Kds(30Na#bSMa?_$6mKB5y0yso62F-#KE5!% ze%3SlV(IJ)GEvng@kentxB!`$b7L}vK#F~ws+2)Q>oUp9l+bgg&~xhRq5SL6>g)OIeWve2{_BbS>x16Q`RVIUzVCI}Z3|oG zr{yC9uKvQ}+lYo;EDat!l@WONv7&(mD5iw&&`gl0wIr8 z$tIFYMcdQ9D+=#b>7pxdPD0;%vH1&5ajK%Wyx(Q+8B@zgl5NEo8&>CGFDJf_o2RcM zB(M3t_kC^8LQFnG<1M#4R%!98Uaw~pU3JVP%a^)sUN=`G0;e6W%s!WUSkD({6IBwn z#Lsi9t-NmMXF;C2Q9@Voj|Ue7tDIj{TO^0jPRbs9o^LxP2A=zICi9to_Z3QhUK*0C zRJ`7U%%-D1n7RwDAm12W#(T4(FE{tY)fmOQUi!KHj1r&DdG@+uDrxL%c?uRh|2A8~ z92I%u9#CBjkdXAvy%9d;U~G95hl|D*CimxFL~|yjJYI`K8taI7xCe!et;cwt!)x#U z8a*77ke^hHdho#hHhWq4Hx=IQm6-};QRihBbNy0DB#W-e(b_itdWx#s^6O21#20X@8p*x+hn*#8|V?_kz`(@RTT;iGc-z4B~**AL8`?WzA=ZRRasls z&jy1qErb^BEa6B?$k1dHC<)?F{sd0seAbOZ_IT9iC%$Sz?LOR;oEegrkiU59aw69- zBesbQ;$UuZiCs2C=G=CBR%|;^et!*BN1#AGeFtdacflNc_Yvy^QT!`1BrnCr$4*reds5D1LUV+e+6M-p`yu{R(`pOc%#Wj? z0}?^c*&m{aEVYRi4C;3U@*N(hr-;MiOarrPAyTI-nc*I{{3&JZu2sdMK6T{pb7^NH zi!Ke1Jr~%**RNT9Qg^YwrAPK*cQcHuX_V<@Z0=*49Q+N6A|`3}zUl2S$I|P}7RnkS zo}yutWY+!8HbrCnnJD52r4s|!RvqcI!dh}H6VXdNN@0mOZ@F08pJT!TXfBZ!Dx-4$ zGe^4FUTDRb$nrO@NJ!cApwaj%k+3twT!uFa{l53sC925cFWst&aq(`E3_R9Xu?QtD z5_8fh5AzAWy3l-)rebFAF&dw{EneaXGi}|1&RMC!5V)z8h1*sk+UmE5z^K|?6ZvoibF=DsNt3uDR?3PpIb0Yg3p$I?l+7*loHXRXk> z>0Gp`P?Ph@lhgN_TH3fo(8D3-)~AhlFgYJ^DZ*^A-bN%DCB)R(gL?&f99F24Z z@zXw>mYHkvl{v}r$eLkI4Kxqb?@ApRsmSeMVG&WXTwEW`D2` z82T=@%A~8v_E4pociWl^-}4hqd{pF!NW$VPiDKdxM|mz1UXes3zV2Z4_C~4mv3nR9 z`Kfor_i*Oa{aJ!mL*RMh(&}rWS;cHEGb%K$Qr2Ht>1)1*1V8?DFOPYR>GMR;1s3xF z!+``z$!PWuRI~XD5Y#9x7n%(j>8*G^E_&pUkW*5M(5sm{X$Hl}VhR(5sK#Q8{EPl^^+ClV4X@z^`y?vb7hh@9Ii_*}WXUPZmG%fGJs zKJWY9MZNB4zVr#b6bU_DeSUo=d3{-Z*qUoSHqA`B0;)HU1$Y!Tx`X+6Xnfw zcgNii*Jc7ndd6+(i2_@NaqAwOi##R$h!hEO-7ArZ?u(vwKK2_-N3|=C zSbu(%_DdF~7oNV(Ka&3KG>$_RjV3}h(~mV_*z3D2<{kCqM@MaR!5BjJxS@Fcd0gt| z|F{r^T~yhIqNHRdEAZr_Z1Ri);(Rf7=#>8qzK9##%S#tlMf=-WuKN;)tmlx~r2U8B zPk7s=z3%k8n##%cBxyS9oj>c6b#K3fO*A`*6l&-fLmgNO`Al7}r>@7JIKEs<2<_H* z>vRY+6UExixG_yPHeJ9Y(tfx;7B(z-sZMz298aRD=S>NSbar(rLeBI7cAgBH)*rZy zq~KWLsL~H#{%!k@Mk;Zk@mG3!9#% z9UJ3qUz}AGU3vn95s8)EmqWSZ|)njAdiqA-_SWMSA`U^8355_ zkfja};?Y~yxgc~^_+6?YYg@9B+otYiEur{6(fw9r8NiXW694q#J~2g)L*2h*4P-nm z6vTiAk-6lW1k#LH zQ$4Q)P3T0`TwS5278=^d?^kt8wA`O~1~vmx5bRk7B}}`#lyv{PsgJADlPn#SWM-yK zhXp(R#VI>Hy#BEj>ouNzeP*s{bnRi)sx#~D$GV&rUmZ{Ira@A^QdhA_@$ke)KasQa zbyv%9;e9h*M6Lh%B`~}pWcTVy1r{MapA3eW9>i+Bm>q*#hRM{Jo!SVTs}eOTMD1%< zN_2i(F6n9+JRL#pf0As+O7S}i?JTW#&MZzhWQWF-AUrh(p7@8AB)Rg))hrFSEUq`t zm}qrq5)9cV#P~60)RUYZC3vzvd$p<)&u?BeG0K-Wmn^?8DonvVFplp@w63rT^rZj0 ztA4QQd0aGJqnc6GS|w!P{Ou5Sz;j8M89IegIB%4Y*V%$W4Kvkl)3316tG@Y0jd@mrnbbm$yGgU+Rl0ug~^U4vO^aFi{VeQeV)T7`w?oQ*j%aa&8xb$v-68T)}i0o$~-Hu0{79k}= z5lQBW#U6JS%H6fDv#F9%%AR=w^nWoRg?(Co*QAM!bWT_QC_n8^(3kXleyeb5brD#6 zC(Fw{kkX$z>&bcIw(NiSC%~eebC^NdmjpZ>>R(dL~)h!^gj7k&dGPcuA9myuB$$AfDVo0`oo4%IR*u_tXUwBr{x5>4#P~3_0H_Dz)(@~ z2Uc@(I40c^d4xKwy$r`_1mQJ96#q5KK8vm0+!4Lqix@8Vjf)hPq<#9a2Z_EZnxD&* zbvS4d42(0B6ie;S*W++vF2RTMQ}T30#*B}l@8-KA|B1(TcpA{*8X+PQI(vl{#)E9 zyi~7Kc1Ogtl%=QIsydL;lfR9~WVEZ3F8c%D-AG4MyW{F5?R#|~XcjHweUXqVkJtri zdK}a4_?Te3YpL}C+Ov&P1og~eD832Jg+14Wt6mP%JrJ=_=XiTLu!w(5USxchW|!H@ z>P3%<-n?=0c{+wEL(vK-j$*FY(Yl^Anbmacld&4-R7h(iwB;m5Ke9fumF zfL-dJ@lwsckzDX*$_&`1p-D2Zw)lomIxz2>mM*b>uBg=$pH!fDo?0Abx|O`Ca5*)f zk84?MZ-*}K)f2@@j?$l#-Dso;0w#0Gk2j98f#Q0_@Cy^El7YQ7)W{(pPg$^10!W_^ zjleAoSSgc<`QWfF)rgY7UfDnw6tB7@q!RJizZ!Jazht+iQsY+ZHVdqz2YtKn##`7h6d(UXXmrVzX9#^*Ch-`%U*G-G30PI;j!rw1-JU(`WcKKE#1nrdTR z2$bcx-=x~%xme~b)3{V}3=-JhdCp$Y{@v#O+S%SuqmX6uN`>{enqm3o^FGa_0pTF2 zT<48#==JWbyc8AHne#8%`Y)D-ZIZ~ZRj~rOULtyN-BiI#ZL~58Mz!6~R55&zHtB=e z^PIp?!8)JGED}U2B?DZ>`#kPQp(!ZX zeLA|zh^&eBiG$ip-)P;+4(i4DVc(=`3yI}CV9>(4P|aLUBM_CuxPKc_IP1gKDIYU- zl$tWcaVvhjnzN}D*FwOeswI?LYoH}I<}*1`c#)-Z^|IDLawSRqeDzdS-1dl%eP=J| z6OCGB`*PX#x|I2{L-KML<-2BiW!4|nYybOeSJ_Pb+Y!A-;jhwp((3DZLqmKANOZf@(f)Yz3I&VRv6gZP|^D^KN9pZJ4!Sy}~Su**jNW@C$e9yyas zd(xqQ*R%m=#jAoVbspK)A}TSRvf&xlKOE620;4)^*$Q%$=*FHsHkrAnA+7`SlgFKn zCG!)ZY<)+I9T<3us3|(cC17K!oK_TJzM->m+px5JPWqFK(fEmy^g1SDTI(-Oeq%&# zmkYG7#PhP|vOt2|prnKDv6d8hd&Y=klQ*T|JU|W0)QFxp-9`kp`9dVqdcLLY3e^%c zN&K7#JoP<(4ovhWY~u9vW>hCUMm0!dP*@Tn`zVS|V+WmV{F4Rjt?(1a4Qj}j>=jIowFrq^`fW0r3j<&df?a)|v*9g0pX45aq8L5dgl~Z1+wj$!VeM55B z2BaU&YCLk2$4tm+3a6|o}4zg+1T0#VJRvJhHkpT_Z z=y9A(^J82zRs9hDaAat5WQgp{Vwx;G+Eb&^9dAL< z36N!N6{OGwl6iL&4ZdVM^Facsyge)4MaQ@E#tnJXjB-o{uYZ~*f*X`tZatfp$XS-? z*HeLu)fAD(2;mNp)@bA+z;HBe7iN0n#g6|lOz*346(61eM~7cAN%1u zKQ3Fz?6a0cX3FTcn*S}{CCZw6(N}VnwcpBY=5t$8p1WPcjjz4WVcXr#acZCEPEnc9 zJ*^tM#C=Xn^ief zG@_z{k=9j*Y`NXlUK2-+WMi%aR-K1Q|7*c!L&m#b}KPL>q%JYPq&I?17@)C7twy^$7LW&?AJJx3-d6=q-l zq`1DOuoj$B3LZ_Rzw4EqntaxHHDmBx$V`>n;v*@IT)BS;ELS6l>ua;^SF3eL2x=~Q z1qKejZu7d7!uMU=61+?OR-7^vQg8Zx_K%4~#WU04bebNY%lVwZA!_AzziOgF*w^v1 z!};x_o`P}SKmL0S`{j^)k)WD>#DJ9Z3$F(+JD;w+&2l*MCh1JGepl>dSr3vtQKM<% zp!XQ>&dc@2(M(L&!&woRJcr_&oZFeUA-Hpy-%8u44V|$70a~Hv^`)>rgAsIxyjL`lO-ii@IpG6pc zFuI`g?8HcviBRyemr`6;`fL3igdfLZS?B&8yJ=vNTdNQdLm6}IGPCpx{Ot98^FMIn_c^w4o0U|^l`jPv1|(ad{|)-BTv)HwV4>7(S3;hQs1!HtQS=OIoo{Q zCql4ZW|_IP7?gGvnE1V^O{8`>RA%uy#gENQmbT4sgGo zS$A&E()RB^?6`gQ$8o)Jd)}+5_z{fEexhn4==vY%xG7W8^ysHETmO=28)A&cfKg!! zzsZjCee7Zzsuut361x$Zsn&31n|sEF{kDF!ZqbTf z{m_zK(1qgeS1+HZNp_NtkGIEWJF)AEwfpatwfd049jJ$f@wqWyvDW$u(k)NI&Uf*{ z-wq}5yTx|Jr<$z`9@j3>kL+;+VL?9_hWwv%M}us!))gxpzB%p_Fz^)_xfHR~b@H4x z&`USXy3I2mpJzy%OdtO@VC>UM(wbNr_c>0{%2jj=lkP4;P5ef!tsgWbL*~`1Z}6Rg zUojJ`-n5;lT9LD-zkI!#PqGA1_3l3ULm`+cp5W`v5;4{*P?Ik;#F~j++Eb@xWFF6x zXoEt(sB^A5H1KQ0$KF#m@e@HA<)cg;yC*|ME!Aj9P%+l|Qw|?QA!BtZxu{Y0w`%iB z|8a_`|Iyox?CHt?y9sSz=1TFY@&8ZAc&JcjoL+I)za6=3Eumcl(rsc-zfnJQ0GTXO z77@2DeyTW5zc!hqQW$4s>sPL22C*p1poa=k4(&Zkg2d!c0+-kb0&fN;8sOlRa#~ zch&l+W7%;%J*9aT=ovP;d+%U+GuE%lpY%MGhhi?J*W58QH+wg%IxK%tNpyA}p(&-B zZXi^Fmaw6yINwh~K&$Gk*CZHAf%#8Rm z*qO=q)T&yAS*Q})q7fTs=HxU+kYl`kZI<_2(URm!Zfdg0R#cyTlY%@r`&pq+aT0;) zlV7fjd3(gyBL=}B=@T&=yYv9wGGAT>6GeNeJdHQEuZC=NNewl(iP96Iek=xFINDdk zh30q!IFgJ6qysu{G{8i7503`rr;p%dOZKkCPq>V(Jn}PTl142o=&fJ5_q&Jk(FE8m zYG48oq|#U3nb7zO5TchHugvs!H2HSw^z|fv^|iwxzEo{`PP$d#yzKm#*;L4=WUeX4SmF4wF-O4uWT0{;H zT6T?RMXNuX>+ZW2X~^C3tn4=@GIp7jMx8J5mmS6RE-0J$joJ_2vZrEnd*1DxcPyFO z5~iu1Q}T1I?l_R8F5T{?h~j>4A=d2f?jpr_hob1EmDqw70YO^<_IQ-v4t;|BkB zS2DSY-D*VlQw(Z4#H`eSc75ni948MnY5y-5eDVW?cc2*iGzCx3N$Zm1t|5Bq_r?Wk| zL4T1*vHt9|;c5|oe=VY8-9Nj5Rph*2Ge)mjeBCY^peAWw)Ff=CtjqA;+@{D{wZw`r zFk|8uZMh}%ZHboRSL9Ag02ZCj!>25*vG<@HR?-QelnPxREtu~QIjCBc`&Y7iK44qi z%%s$xC1KuyCa_G{QC%J=UT{UM46W!)5NX2hp5+QvMW3%lVGQuHf@EzP|TNcXD4|ENh5#zyj(Xj|0v)cFW|MV_L%@V&^D)5%1|HIt? z4LdLWlbu~$l+Du5B}^n>oxrW`;?=jR(v+it4-Ik%7y(;~W7U3^u4i4m*40PdTT;q9SQ35FJ0*)v+~YKpFuPI^&%Y)X7@@H1+|Y*+K6UxZeW0y#;MBLJd;e~M2g zi;1Cy3IdWf)f0hH97SF5I=l5japZ8BiMiuzj-j7ETmS*b+F=-O(c9>Bfh863EcjXw zOCTwUtP_B;lCP629McL~afW$RL5LjU1qZta9qDp)Mt#=_LnUkUHiL=_-kBDU34=&J zI09nPjb|;-LveSpgo%exvtrsI3uGKfxrs}_ALL(FN*w^kAFVp@QRBLZX@RxYo9KgY z{=rYx_RGrkFFZ$Far1o#gy3|2kcv@<;TWxyXul6Z5c;zJH*(>dP+ZqFBp#<8G8F_Z z5(|T$B2p1L6G8oc4yak+6;4M$Ru@ya&IBUTeAhm@RK)U-cz3&}QuOI)=o^wJ2o#}L zQ2Q!W9qI@8E^r*K@NSGYeEiKRza}&b@D4UIqz}zV`p}AQV%nRpWGBUa0MOdEH=yYc zrPdJOA`6F=H;vdQ{h_Q+ykC+N6Uk)0i>NVxDZy*y+TsP@pM%2D`vk2d3I+tbXXjIn zM67tGjYtgZ3}kM|PGBH8j*Pbxmz!{soP*HeT?D$jP$1C4$a+%=y9n2fb5JtgKuMt5 zO^o{wo1H&Myx=FJx?JH$uNA7oSUO0Q@zEAP%%fL0!?eNrt8dEoh&llayX}(kA~2J( z9^eP5tU)wHl#4`}$n4(O zEOmoBi*pjl!aF!?fp0HPNQ&4PkMtmD4Mp6=T4AZ#Rswl%`K$+;hewi>Va>0bdh4?8BT%70SsV^B>NHm@CfG zGU@_hcU^-sD?bu0k=$lCyQ|wf_JGuR@TlUZPz;`rftXX|W3*{W*xV#Spzxn+SEytT z9u57u4A+z{1d9;P`Gf!g)qs);%Gy)u%L@D>T?8T3?Bq)%E!1P!ox28dpx0R+8At!2 zaiR+67gWNn?rPRrYfO>+y)X`vqK?`58+8;9g5LH|B+-7ip}0h;@gUM+fkK%-0u|!0o!LSOWUz>#uiL_fxaNp4VGL zl*{!GMJT~`){uRVaUlNI7rcLc3j&SC_dRe8KDNqLI0?*STQN{vgpdZYAm~ZrRT7qa zF8thna}?kV;AE-szvA&nn0qjUIlpiH9##e5SOcidV+2K--)#q=l#g|gcguBj0&-!i zRaP*JlljM0S>uKPlHx*uz3m2r*jb+h|FJyb-^U>AV3f4pA3&;hI7{rTh+iGEK}ait z)j*E<2L+gq;YR|=CB|96?~f>J0oHIRyxa+_%}o|3 z-kkv5sxR#2>KV}0_u4!)CaF5no%}NL!I1YPEI>{J%e?NvCM^QB_gPINSsn@TYQI3w zy+yJ^QWf>GlfDmL_;7o+2Ebw*D|shZ|~`Q&Rd)52AgFv#2DB>@j`lgGhH+^L<|5 zz0q`PLfdIee@o@>vO|xNt`!5RmbfUmXAmsiV{CFzt0^Ndf%>YZta4SZva(P}nC0nV z<0KT(S4Mu)+xTkka7KWCKeR^))nmUtWwx>D>1`>ac2#B2vxAlLg;CGpgA}uE=`qua zQy^liq+r&a$2bGMK*${9lf7e&yRY`vaxp_HlSaK>Hrkdm{oZ!lU_yNAoubZB)P7KP z!K68>OHMtpbq4bxc2Z04a$Xrq32hZo8tXx9hq|nfMP5kh1lc8(RMLY-I>F+qFxmJ6U?8{~Pa@%zv&9r$H$K&BW&Ndq&E~UQCRdGM*J;xr|Kv6 z6p&&JB*7WMx|X264=Qd=Jgyb$E;QF^%54eX$0;cw4?o@ci{DRMVp0b!88g0gKUB7~ znpnAWdZ{{%>T_px8sXPm#0!hkKAB&3>HfyIQNuT79#zBi*)UJ1@?gD47Pz#cVM`?- zXWb4~t*7i&iK?sbNaXKtTxnCzzZ=U|(+u;~iRMheZgH(1#Wn4*orEoKV~0&8w-$fT z8{y}a;>UVW$Yx@SP2 zvR@)Q`-R5xf2znpvfckukwaMi?<#W8|E?nA=Kr^fO!~i6oX`%XOHPpFB<Ws#@*L5GtYNO`xraNRGIywjSu%eDbdPAZV}xBtZ*uLH#RoX zHnndZwL0bzhOGn79Wi?UmG0Sy?fs{;(c8Kf)(XF@eR-jiZ!SObLG9lSM-ynC zcnG~UDRBa1!zJBMkxhSK@hlWcqL>F!VgT7vt3?V#K1H;AG{7*d%lc#h7=|99)1B!i zbkbNsrQptBTYoKDd@$!taeiP~g{MZ?*Z1sQd{9lu*pMa#Yi)0&7%t@2fg!G=wgRFV zVQ5JpQlw~Q4Kd?HVhEaiApB)25X3SIlWv@Jqj8C{!u`~;V_L{zL61=xz=c4w48Yqt zk2pI&S(@lgGXDV6Up`pza6>f>54*lnV0h{#kgMzg;yY>yDKyIGAsQj$eHqtr3TqDv z9|DfjV_jMZMfU)?-L-_(!O_n_{~9@qS79KJD6uxof)$_|ylahN`v+qp6#5@wSVNM_ zew0l8l;Vd2WTEuBAmu;oQLL2;XTC$m_PrbPJTgJQH|tuJ`Q}dW^u2kZE7z?pGd0Uf zQL&(f`22s;shf3GC-qLQe!He5taytkG!FlNZ?PTO#{VPsvv@B47d%(lD0WQLYiODFaQ3Mx6B0D)Nuu0t>xnggS`jyh0)r+u*x3iA5zEES^Tqik`>k!+v|A+6)Wm z>%I_yogRNC%)fOZz^_Du30+wg;nqCrXh^lSl54x$(4B4Bc`nPw4-uE%WQbNg(D8MJ zAKrTH4=`P@-;WJ7>%3x#rTZq78;aqcy+js{<-#n{DS?`02LNq#UH^lzJh;1$Nadyi zw7=2kVcMWGv;-~p>!+KqisYh#cIz-3TNH!FNz0VO`Bsd}JvFB2-YhwclOssrBCqN% zaov0cl$#E~Fo@dQ9}I|AOTEDWuXIDG_)RszVcmJh+)zkxyMz?^m3VWvNdxbLY}6OIBn0FG&{-^9j}Lgc4-Lw-E)OL~Zox=JPpDp6uK4-xYmzL32)W zYF@@^NQ`11z0va6*Z-NR)bMB9r$J$Zl({P7PScI~vhv6k!Su#v;dBBLJIST^e@j*0 zNfo;vr385S!$RP8gm^LBFQ#7&p>*AxM|fFp1+@)eU>HvK9W10K6fy#e8!4>I)r7N5 z)|SBAN4mtV~} z&sV<!7l0LNL5wg2>)qm_t*5 z*TU_?(Rl)>Kr%nAt-v&5xCUdiT=H~h9t&hL@79#zpb8=A?#%6EaCH?Qvfj$o83Gpr zuI9OFu?Hn2%--pbVO;W%J`xh*_?7u_){5Z1yG5lH=xJu08GZrD&qRSTdYdVxE-{qu5b1e8Pz3Y$Y;9 z^Us3~mkaf|XKZID!+*fW;;oO2yb|jm-8t{6Pk*7rNWkODeX_4d<8UE!Tm4{_Z-T0} z3pHcalvFi;hZ$R+ccs)kVax?15FT4o*mU- zwKV6>(KhCamJBaaV#g{lpScHxfbju!=gpQW} zTITYrZ1v+iTpDa-`^fb+iEqk;F&V|OPcR}@Zq4IslOCKz7Lpp3ZeGZ71xNHB}MgS*+_yO1a0AxRa%CF{H9g^p8yBJ6~_r>M!M zMu{GawCx6bGT86f@Hwp|K={7QMs91g5Dypp`Dl(!6)5NG)X_|6X??gk1{MkD2H;f51gH1hQWfXde zU-;y^By30N?;|BSkJ7tf=^SwVFCtwlJV!OU;ohO`zybuao3E^3|Di14x%kt(rAI;% z6q^SGh#l{AxP^ZdbWH9X9YX1J1F`-&1bu{}d17P>(-_?A;~CJ9&nv^BbmAsh#{P=1`Ak|xl6Mg1+2Xji;(3f z03%i)>(sD}A8g=}jab0HY#IFq0B%+m54;wHU=60>li4t_!r19Akf=E5?9jYp=m;D9 z@(|b1CoAG&1cT~m$MVCwR(vSFXNd$LHezc6Cg=6HGON5A1hD*w(AJ0gokd?~!FKrP zpy95N;tHZ6%IY9?qgS#Ub=1Hg5j0llROQrDxCRuRaBk_HRz)zz%0w$LBHI+I1ci~Q zI{{)h5I$&cUxEnyF!2;=T)9;d3fOEcMkLrXAqi*!>=Y;C1bhn@I5aoV-+YH_5Pq!T z0LTSP1gEy80rB;HQvcbA83TK(gs`_jKM8vaW8SQtqR{e*4(Pc3SMU-SDw$21N&t&r zguDUNFZidt;WR@cEFMMwVC97b`X~k^}YLy%a<7_BB;IcpRO_`?v{0x67)<_Zr) zOI>OZ#_iaVu|&6+|2vA(2?t|Ylfj=WKR4!`h9!ex+YwuD!(VhhenG^6m;}C&|qvm8z)K%f^QgL_TgFL zW#zcz&HL`5i+{LDGjv>jf2vx~u=myq;S?duf4k8Z9e*CSCn$uy(S}%NwM&E|m{1!) zD*!MHI6TG1fmCbOW|!3vSX?w-evgJ{K<;H1k^a*>JOn~s%+zZw zgw|$yr*r_y3rQW=(<3bV;ez#TSm=>XD81;!DKBNRK;w)M|nHM@CE}x=OlL;_{?j;1|z-BxumQyhvjBXZ?9@csrg`Vuo^REG167hoe5@yDa7d(hN zC2u0^z30mNSid2%P$8G^zQEFaK_z=2+>-DDgeY6+>x-$ce5LNSsiz5{#v z`PPP?{hG_D@1MxHz=uL`#I!M>Vn1twfnBr-PoB3s1Sk}lLU0+%uxs~;vF8hVQz8iH zAGHk&)}Df3astUv%&Z5D@`jEscEG-n%!MB$@T{Y+@^=k!t#!6p31YLZ$qCvT|M6Q# z-)_8ZPew%if0Tl0VGJ*X|XfOi6TY0$V2;DUUWH(X4bwaPG(O=O_*;>%RUuqx zj*(4cXf277N4K{!_h|v?ta##bDqtBT-~SQeTgq^#S#YCd1AwfS?#Q}MiIr?u07%~j zLp<1q$1M8IuF%J%bU5xJ0+33-{ae&5;hUv@zHtxD5usUMVnM9{QqAXUBncUVlDFor zNMIY~E+HX!k6(KD%%4!=6djx?UIHc49f0*W7gQ zLi@G9hNJT;7i?;uL162Z7+fhDfp7m4?HM3i{(udP z;x$Uk83C#M5*Y*Vd&th*>WNankSu}I%D@ZBjq3yjIv`3WRv~KB#-KFa4yNv&N!uU^KhT8VZhpeOAZ{o(` z;&8t+qqOb2sxIo@PdqC=%^y1T1niIGcb%`5p?Ytp%B>x@_P2PYVf|@Q{x9%Wv2PHM zg@Z)6Wju?ihkRzwK2D-h{B)tsZs1Vw zFEdB(wh|GIH*B7LLrwo8+RW!@H5@B~kkI>!ux9SxT>IleN)wy@QkY|7tu?-_g7igJ zcYBb}1WUHdx8kuKYeUR03?LW!MsgKCW?)9 z_*j8aY;3`X$g)Y1b~GoI^Ga83zkR{JrhyvOa_K}^j!jHnL)lKW(=3zLRjp+JQVK+T z#NpITZa_O&0*b+hk@HpSkrUq_KXg2I9PIvDqBIvIv8T^iJ2}tpDLk?bi2EU@EYBz= zD>U36$M!PZzX^0z&*?hiU=|E^q@)`haFel6Z?>5xNi~F3d*k$qR^L_YB5t1Ov8M(;tX}; z_(mQPTb!);%A~n^POe-c1W@PbZ_aa4KH2=`O=I_MbofoGTb5={(#peBS#3H}fG|0F zwW4iIzw*cOR%=>9>zEN<0sgcgyVB5AV_%rGOy?i*DhPpPYccJ=&yT(mleb#iDb@>49D_xA#xrI6WsY4gc|RNvxldI%Vm;b??vCG|J}HWy?Qjf3af8S&_BTmGK6>M%EHN_;k zk<86Fd49EV(s}HW=H2rDQ1;egQN7>0FeS{;AzjiP(ulyvK|xwVT4d<%EBf{ljr)&z}A4{lt3KeXn~h3U}g^70Rjc$NN5) z8!}w>*HwwzL~t(^2J0<5KM-g>z}D;jwQbb9F!h8@vzi!W6NR|f;#ooWHk!_Q`HOpB z8N4#Ckz&{5Dzz?gtuFI?*;1amKh?O#UN96s`>Ca$tj=I5$^LO#eR7wLN7$5Xu8p^Y zx&6o(NaySu+peu9m8{XSBX6;EGxPo()0gHoP%oPoKiAdHw_wb}IJn8ud^qpcdDi6= z8#&IH(HqTA9;*u0dKUh$e%!kjv> zE=qFpSwOMg-nlXSRNF?-qRzcV#jCGKmCX6O-kPvx_;}nvHqzi0`P275t*@dq8 z)S}UUHHWKeH%sh&t1panZ$Rmpph9** zR2+GggX``;b!q+AjhB4hofd`a-zSP>t)JDrQwjFqz_ACHxdF$JAZ$ zH&AIOSymdi73(#tu3z)LMwL(WSik2E?;&@5_4~KEQ$MY6Maxke=ym8k_?F;rkb3$R z%DMwd6M3u?*Ds2SJn{s;Y4(R~EgXJlqKgnZe#RX7%4WqxN@?k9AVH`2TH7mhdV4p> zq->O=;RB>vEBtYkCqD}QZ!<(MhbAS10E6Ss2P~!szO}ENEs2hwx%DAm%$UrEkijp@ z@p#%ZldFyc#~B3M2brf0PD@*4z6NrnP#~Y-r@QBRp*nje0St=X0BF6GLT=YwFRA+@ z6ju9Xww6VqMP+zJ=cRHAFdNAQQ|E|RftSvVxX#!ZyTmi-Y@dM}D?Tv>5=(-PbCqK^ z(Z>}9+94?!p+wfuLc2=kwJ)*L{rK>5idc+benJN4UI%5;@Wp=A>gDdy49s4@P$wMQ zQ83*!(+pZE3b9kbOn(^PbBUn@w1eLWlpq5TB*|YfL^={pe;mLx*4{rIX#-^X_&ljR z-qch;KBgz2Z;PW9&fZpSR5j9ypo`#_FWuxPWALQzhEcjAFvVX}$InvP?)5{$GSt)h zY-2Sr1yXsQwnGf?dEQ`_{*CFg1qkFze}LTA01i1=-p3bGuQx#w4A2(EsJ?sxp3X@u zewL99-{X0q6u<6003blM<{F~6SMbxj=VSmGnM(r=m91}u?MfQfd0yk z5$Lt7PvI#8WIFT!6#3#O0pyc^!^m3kAi3k!^VaP$Fi8eEfbZBx%>)iQbU@O!5{Rf) z5;ZqI=%5>wK$sF*{cU9_957bB|8(>cm0h=bB~dUqyYe$VziG>^#kA1q25YV@cQqF+w*q;w45}3IY%yxrb`@y&Ht&SR~^4 zhy7135Ypp1K3U5cKYZ|Gw4N(|7?h^33y@0GyADL0tLQyT0iy`iOptF%zy@Mz1Z>Aox)>$^ z)73|(vM*9er(%0E;w=V3KU zZR0I=|HBND4)8<4V==@N48sRv2=kPDrqO3FI!b`sH%C5T0D{QV7e+tt{1WkA?!&+-g?JwF5sIv(m;$Zm^Z>9S zJUTsbGRdt(3LAG1&8c8ZMMilHrhDP3Au&8y;B#g&Eo>*AB8r9WAAq0p7>ttaWFdaU zL8&44#2)c|R@=F(N?y_Ksw-gII!8;ELlh90o=y9Nu;GCxdFjKOwUT{t?WgkF;DRO`+In5VC z^fr>lrG^AhqJTBkUu_ERZl1!^EFC`M=q7oS7ja37BBysa>r;gz;RzD<%LLWbIbG12=P zIOD@ofNQB9*on^wX7pZ{np2@A$%KxeSaipO-b0ZB4Y3>_78Qcu&htN%;svhaC916+KvJm+?ds_B-5Me4lb|+pd+zY(}0o;^(>Cg_`6>4MFC1EKiaei z_H_n2uq;e>EP{A}`fv5vR~i{X)aNwpzH7`4!7b;k0+Cw=R6$^z@K{_2fZ3jWe%JAksLnEYp531n<^=kwH(!Ys1_H<~+~U1N!GKM)Pz1K->LoOo zAmhO&BH7l$a?p;#Ct$}QRQLV*cqQT$szo#$ftkXf2QK5-%qPp+BU6z~-s&VA%^mE*GnMjZ$`T4VM)bW@kA+b_U^B+cH--s2wMP#9Uf0RqeYhFmP1%4a z^V1B7PfxEbS9GJjd{+RWGc6*5N#j}6^Hi#L)Df`%`_is7LL>J12rRcz{J$Vg5Uk$2( zKVsadU>p!aWC%urwI;a!c%1OpZy7M=VC!IpmOj|iasI6O8pM@><%8`|(054b8140g z;PQz#K63WMFdUdRI6e18p@m;5n33S+`V|g+N=F)zEb4uvZ(`+oa9}+sLt|Quo||7k{(EYO z<7N!l(Pr~W1@YRLo=chifW!{pbB#1eNwbepgiXl@92yI{s*_+DP9=l$fxes>_qaIK zI1d?l9Psu5*kj_=g~vx&r@Z&P#3nIQL#7;Lz`|PWPqKn|yeP5GW7vNkK!*I+rUlHV zQGf@l)}@DYqaDW*k7vBM6Ypfi9126GSV8V75ZM*7LZlnEHy_*M;!q9HVV=~il`3tS z8pQBY?Hmz;e9WW{$7St(7f(svuem`do+C$juchgmq%t*|Ze7oqhozMmwq;Hl&enSh z3487ctVYRI7otP05}SwEf;$G14Yz(9@qLy~cYcbdbFT5838915i9b7!1>=jhE4a{u zQxMI4Erid}51wiMpQ@T=mwy^{_E4XrZc$NH|Ni2N@kHfjvh4ELuFSQB7P98X^Y2}4 z@4MVX?`58`@n>55ky!CLe|G%X4*Yv%o0{l%4=y!()MTS^$I5egdF(#v4)0}nfr@8G z{?~W(Ut^ojKUxF0@17oi{$u$MwBr1Za)&vFO3Dt=~qq~I6!`cxa1{Ag<3GYj1{ zeI*=y;M4N8x)Pd`NME&Y0pm!aAu zNA$Yx=aO>6mZv@AYy%T@1oV8{gSEhh5M140V3%5qFTL8TaF zqTIokGu64jz>_|yCCTFNP6fUiiBoJYWj{1u1!_)w@b3#(8u^Frd@S3eIQais{>*Cv zlt23n4Ib!V5pM9?4o=%`!O9NgZl>Q@bal)~zPP-RezmBYUY$YIW;@g8Eop6FeUnYt2+R(}=k5qdSGXpMb*t;Iu{%dA@dOo}efK0B4^ z^w;Q$lIJ_+@j0(AEc^y;&1VSt}9k8-*?n^f^5T!5q zD%hQBmnqacvBmV4^0+v?tzpqBG0FZ1(K;#sz{Js^7y{GFqvymJ^B4r0W~oB?WtZL= z+pDcmsDT8Ark?{mANRRT?bS9aLI!xg1N9F!I?XPUX3vxm2{xM>6GS@vM*nm}p;t}6 zRiX+XzF~4%PG-%-O{M+JvX!Wt@gJ$+?uG1LHtiJshhBWCbuR*4S^!4$p`XaKLW$aj zBcP53srT(!TEBJ)H?~PfaO>>EuGPL61^fE~O2jLstPK|snJ}37NNE~7+$SG3hz>j> z@LGM(_Lp^wOLX0=%y?>hkJ`*cs*C5qqPRl!&;*BwN zk9Ac)igjmdOoVWU3WGcz27v-Xrsr8255aci{1v#h)igo?l>yT=f+D!a-or!ne5S&X zo7vG{2m}oFUWwbQNw?DV%RcY@H+XggHrTM}ChV60sQ}M25o8^a5V4Jz=w7$02F#B< z?p0Xf_5{KbAH&BzJp7=kh==zWNB9?KD}xdcMzOa;?)4~~o(N2_juPW2eNV)Q#0vhZ zxxi6L^sA33v(X2H3Sff8t31A>^0 z`F`L^yZDIvYti2;Ya(JQ+#~>;0#{*e#V!BwxEGcMZu2z|n^EnvGxrQr1pq|P%zwkd z6oI~Tk48*yVn9#ltfo8@X}9k{NmEM)F(RsY&?4}CF;lW$i(ibRh$}2IB=l0U;chdc zfGG6+k+Ee4gw*0TVK<2EVoSH#%WyDmWbET3m%v6k4ib4-LChL7$0&y-fxtE};eyAk zYtYGv;64e89|$`uuo*wKGX(ZxsY=V}}8Nfr6wK@NCJf>oU(E2dhHhYr_Z3 zjK^g?&Wwzv_<~!t%B5AxFfwk=cE@E4%vnM=LlEOzfq)34Tdoj4q?oh&K;}AnPEq_T?DlLnT#Gy^#AVN_<)y6>tfwd~jDkOmbi!XFs4aNKm|a3X7?sXk0NMtZ^bXJ1LbC)WZ=!8K4FaKmP}lks2= zd|>6zERBYT9>bW>jwino5sUcOJ^R`=?TzVqnM9-)rN96BAI68a`*uMit=(xv?J@Td zO#^n}h&JpUWz`!h`FQD3gzt(t3qXt@T=P5Eh^dYO3E*J~T8b*R2)%6(zbqIHUgm|X zO>p~?VWW>aB>|@Yats$Irvgw~CG?fz1p7HA9L?U3gQ%TJlL+WS16+*xVLWJ#I9fQn zAUv&SIb#4M5}iiF-enN@Y#J2;JE{!Z73cL0JomFpmh1>XOJc;xBI(F#ex3je(b`)kTqe(%e-;7nT7-P$<^Uy1}Sa- z(UPuIWTKj%nj*p=F&+-IFlagt@)<>kvojIw9$q$?zzPSn|2pf+eBHVPuNWQ71yS4%}$s-RYv3Hv70heWG`V5KR2q0Cp&@;JT!%x#J2 zduWxFOv;a?3`R#PbxX(D_Z~9;&7KsFhzWi z6BAI)x9kY5p+#k1_;NRkDDv(`am!sb&tA+O5sc?3(VS(?W&N81qFmrF#b{YlQ2Et% zlgESYOVi2T8S{DjKmX0e<9iu_-L&|h>d8!PKL2+YPnG@u<>EE|>D4d!`em8--@InN z1FgIGft)mgZ*ENj1&3atyRGhJ7JKL47jPO4-BVOBU!Tr2Uxr`*;G0{)VEnjM&&amA z69#kbDzME+zT@N@qT9&n_%oX8XjvFQ^CgiqX&sLX{Co#a5 zvn0t!{W`TlSL;QKzE@YJW{HIUfBZYc|LNcLAN-%I+Y%`r>u7h~r$C1eK42G&RCgaA z?Ol4rU8zI&zfzz_nI@`3twgz!9|?2nJf9=VXC!$o#k@=Gun_H`+w(o3@KJzo@Q*`n zKha#)S^K&fU9X1u;C0f((`sUMzVV;th>W>{A5vNvE2QTsa?#vN3(|63 z0^@_BCI!Xb#Z}ol8a`ER*ul^|o|D6s$#6_9g}+H_cB!NU6`ThIKlDiGl)m4>2#AaM zTCRSD*QgBBnB_$9YE=DK>J4>#v)EwxQnLk`Kwh>?)i(W)rA#G)2A@xzqCSCVcig-3 z(s8u<6X_;D2TtSC&>EX*pFF_Zh_{W24i)l$*pu&)H-1l#a4II4l{&l_7Dt^q|XG+JK{qtT< zuOQ<-SNdg&4zFb*f2pCk^puCitx)RI6ZWwwv=@IWHLczX2f$J zx?Zh^-8Ga^!2ZsWp)8NO8E04Pz2ramaVVR${^-)jn{I<;3YVAhEBK3|Zi%aNA!BY4Wd&1CF5MK)Fy?+ekR>1 z3QFmZWlM#WAN--EG)Wa`QVL#d>UdcoIji`@hc-1$a&nKERc%ov&Rv?oWI)(pttN$4 zTH7R|S!_$jJDtDV0NiZJ+EB#fkVhqU*M^5oBX z$!Dokpgo^xy@T_2tX}?E`2W(B%ZGrnn^W1I%GiB_g}%EWXXWC3s_v@~BH@AGUnh&) z7+0BazRA$8)Y%J6-+B2|PPZmizw@~8_TV4acMqj6o-pf;r>lOe`onoa&_L_Qo*bwI zlIMr=o2oIL5k$!sicN}tB-Hc;*RnKv^6Mt+1aCH4T*xnN{dRZuv5sn06ZI80GA|h< zeBzRNmQ4k|>=u(-oc)z7@&9FYl>SIdKmkoppEV4B&Z+w%U=44LMz72~Vbe)!tEB|% zDwAvV!65ze?e@FBHPUy3(zhq{e_N+_Mq}tB*PnNBHd}0Oufd>|8IfDk;?4#RwGqR> z6Uj*Y0NAmLHlou_)GSX^M6FT-9w6Cvd5xQ&uCObDl;>e>K^XdgH;FiF)j#W*jb@0k z`&8BUC=GatF+_j~pO>EjTza6(h5&3MBL*;bQZMKq$5T1F`+*-<5I?@xd*QhrUiY5J zFvxu(f$fGvd~#pf`m=}gKN4T)%qTa+WnvO)6?f)ofAxHYiRIzr3u{f;_MU3H{XnfN zkqDMEYt1P+3M7$&(G&HdZ_xP3>HCc)pnI2Z613zw6#u)0*?4M)wVoz9AJq{G#ctRh(_`iLTjTW z9l)&G>_11e>dsLhWdRY|+-ONMXFk{zFmjlWz}A~5ggqGe)d~UroxLiyo(Tyo7$_|% zT|p!&*RUG|JYHdg+z`FJXY-)~#Bhit9|?>FX-CcR;)Cyx2|%Nf*@Dagt$D}iV@ewn z|Eb_tC!VV zF?S!SAuVt9Za$gi3E8D`P$q>Pa^6Zoz8os-9_?mo`lw@-4Jysf$NxbdV)%<*{QY|| zWLFc()qgYnHlR@k34dhYI`{3np9rDRL;5+o&eT>F))aYXHAY1;laF2IYSoDr6h@h! zkG9SE@dgBy1H8g@vDD*Us3U1+ZzZxuBVV#AzHXTdt(soi+Ze+l@^7&eyZo!l%j|Dr zvtKXde8g~Tn8iy<<3Z}*@(<*Q@isHB%fT!!LZl>d2B)PqUzLu0dFdukj3B7l?r)>E z881(kl1SpbGWQ?ivU<9;2AqMnXtU{<^_&VVhHO6Li{(O_vP^@6nA>~fx7{6OmqhVL z&XzyDDMU)`xJ_^IVJEw>>P-Zmmzg>moV3T{$%J~Q)R}~^n-aEB9LZz(48l2FUs)yu z(eFZjc``FoiLZp&!)7?r$?_E_#Sgw9=*0iNQ0EgeqBb<W`T9XG!6cpVH6e?lJa`&`W7zMVA&BY{C*?)=YHJbwfZ1K zgQI(FzC=sj%Ml+JcQwS>)a$VlXZOBc(f-oBsvi^#EGh{onl{Eh-#vE5%8hEnyve7F zgxiL<5Hi%6ep}C6P7k@ItpBvxNL|ogRbjlv%b?v*j-mc3dHbij?nfKTG7lHYUEUM& z%}u+h;*ZMgmTOm2=@`MqBC84)QrKkE0@;Y!^~1qWn(6;YNxp(*ISz9s@}thTWN4;; z%eA}qLc-X%SCGXZxw8B?GD#(Ip) zj!a4ae%pf#=(N1wXDTsPMW|${Tmdob%8zlCpXjeqz1g~ilLB(c-wa+n#LBSHf|58k zo3Iuty@uDo%?$7g#Ub#*z-?z9qLbFy$wWfEAky}g(rxF+qP8h@am-mXUId+P4;i4+ zy?PKsnk#8RIeysHo$fO?33?_MfvR)CBk0pBn!wDreSo45nyrwiFt|eX=`cwIt1H7Q zaas?}s^#G}_@kYlH1}sRW9-yte({g;qWJK#k;F7X$j$>xzbK2Gs|c{#S)CBUqW8BS zfq)9*lDr-D@tXis(tty=Ax;#7{2%gm>DWHX46xAsFa`usZCq0PoCu5K8w-|7B7M3k zR@41d^>o|}wh{5Nt4I2S2!8XGy8X^IjcgK_PlQC(mC^9S>MIl>Yw=d4LMW$(q0%Ca zB%Gl9fdORH1GVk>iot>sg>sm0I|o7pzBd!=el}y5q69UFm4ot>hvpK7eA1z-snmkN zi_4}rZGTef)0wZwN-_uYGsx*klwFCr!a{w=co9V6Y#}B#FDO5IsN0^jEsa)mVcp;+ z@kq_2q3ipWs)o(3q4N^_z9rO|!wCEQZ0P|;UuwwHdTivp zym0Csq3p_cS*2cKma;FA!XhX~xd{H}Mh%&STM>&PQLfbcZm9!29)M2&M@Ai2PIA9d5m;JM*`@&s`d9oLsgy0F*nGjHM?bpOO zV&!ZhgilQQ&={0@-F%7XWw|yoC}2CKEFwg*8v~wUb~!A^1{W;z8feGgdl0s&ZHXcZ zmVui{Vp$`+B&dxBn$IE^^1;yiVg#~%F_M$Pxf+ADG7r-*IT0)>d@Z0e-7XJ`!g&QF zumvw0+&fA;$`6`(edgrhB^*o4j$yQxY{5V<^8}}!Sa#*w8%#&2ZPRNQPR5&8P?^p} zJ}!oGzQ-i28tB`dz@(LVob_#d(&Sto(rZId*5lWL{l`xs?+r?2bF^U_ zPcxKW+9+TcY9K~fSLzH3l(9W03np2EB{gPP!5voSp;(Y>wi}@94_cSWqsqNwhocnV zArX|mZ`>um5BSi+2=)~V5zyL09ujX2RJjfgWdF=uvM5moe+sr3c)BN!2V61!A0#yx zcSC^z0d1;^SlIShKRbfh1`c52laaC?6@zn?m3O9~~c zzX&Dv-ne=Q$hbccTh+j!m#hf7-w8S(W1vzHDJoq1^sF0QTU4tZLGy(UEUH-BMLsC2-KP&qc1l?IyfMK9@GBTB)Qr~zlv=2e-(~_aI6c*{PSfD%7=rXhC z%z6hHS3&pgxkn-#8xd8o4+b|f(462YJ20V&Oe&PI#ce_fF-=Q+7zQZ4v)Ug^&D7(~ z=f=3CVl%i#eP$-3H_iHz`&g=#SR$;e$K+B#-qzGB1q{fQ=5q-0p#R>itfL`Dn6fHy zKqr#Q+ZKI|5yk=rMK=P$pEzT;qZDkbN(2kFMI~?lknx*YUP~0HhhRG(jt?vqFk01G z3B6PExrrfQw+zVYA5G|$%79t6d8m(KL>INm#>X;|$C$|(mhl(Vf;gPO`3_jSW5kHCWt@(0jNKW`GM0-90(S(tGm>ZDAYcm^E~E8~ zdc>ea>B+m@&@5EVytp;%D(f+qrP7o0Opx9!-a?M;6n<&_>+7Cj27^b>R`S;Qp1XOJ zEMMAe@6$N?%XF{PLWxvph5woIbmS%H_4Y5R2dG1Gc&ij9B7qy1sIYfWH??RD+ejRq?GlH4=Ap~Y>LPu9#i7N|VtTBKt;yO?2g*Hc!*cQT1Zi#I?g9%psei8J zyt_}6cqv8wRNu^<3eLlw9QAImYyMuHJG{%6<*ja*%o1&C@C*0&ThEo&=6hDov(pzU zH21Oj(=rEs`eTCM@3Zs=8nQV<4&W{VnS6W=`nOgmeOXhhIvR6|VzkOh$kfz8uQ`NcT>D0$*-Pz(-KV^R!(EnYY zH7pzC^;n{T3ojn0X0ydc^zn6##clcC*4d8i_Q{Kp`~$~{tkZ}OsZTEd1clx#`<)l9 z6w)?#lC;mCJ~j9dEBcC8NI}|?mE_NRouB1mhsq~0`sM6==viYdfL|Q^d>|U5KpE4Y zi?x+#HP|STH)__LNWWZz#Z5d)I4B4pT8N|B1yx`$Hfb9ZYw^a*SE%EK?`rPkX(!asHNN`9PUc3|J~ z0+0VpmPn;1_tMSj$7+=B5Ij|U_b+?9foCl~=3%z}6dpn5z7yxSH`~&q74&@bzuF0H zqY36#W2lnn{e|7fIfu7tc_@9f_U+l_pT0&P6L-St{4C$=J`cu@teSZD z?r@68;I=+nA!(jG=J2;}6|0Nc5t}Rg+va>DKE|%l|K8tsV zw#?30w(<%x?liWNh5a&8E4vP-7gPOreSWjOU!PEm90beWmfNwara_Ac zE6kXxEA(yn-qb}+(=z**=Dwm53c_*LL1$G4<()-Ug}Fy)G1VI%@_gN5FU#;XSGkqGQ>`Y(R|jnjWOa|x<32L>bi#f80;#hz;kRQi zD}-VMq8IE1n0XQ~>53qCvnv}!4$4ExRpTsi+fB#gY|-HkM}8V3MyZto*7E6#1`DbtdgpNzhEc}Syvx~)2^k%k7d8EEKjF}Rh`#cA z!cUg~%c6+mQcUXL?-P-rtij<9bShViw^Dgi*P%}mykaz}pkpls)pg$CCP8H92ABK3 zULM!sy6lo$Z@;alN4+==zT4&dwMr*llA7^HlO}R!wXk%7!>}?xI_=L+_>^$R6oHB2 z-xdw-V&A8?*&ml?4ZQZ8@%QFFhy^W=$U5}NJO4U9W@F)wlJk2P`f#cxv$AQ&kWE9q zYkupBbtVT|h}q0^u}aBxa#=ZC$NTG3cK+m5xHr0w>FYz;S$XTQh%ZNXeMYi zr?~#_Cx!3weLGD4EVgKrio~$HydF5O_fW{I7L}AYre+}h?;OD2@Ebg%8 z{e^3f@^j_Ae~ucnIMXY5xYAxzUvres9tL^mZ}wDP%()$D#?qbOyX8>7(wc7m0SkR2 zadwqx@4iEO8g-Qq^B>`Aq7mLM>B?VoKnBvgjhCdaUxfd?8-DlqeEHAbyFWKa*OYJc z_P#teS0(nJFHZ3FM6K?=iR15>y3>Fa>xTZ6C}qwyqt76$G5%jKH7rFp^I( z5|raa3X|GK=CL4yij-=ZBTDO@B#e9;#uP#Z4+@qscD5DBlpXhBxKuXdL9iw%j1JNJgqWPU(rGh?eHCjVJF97uc9ir@8~GG~tK!N^@Mmlk`00Mr=tDHxGy6w1j}iDH z$@6ji?dDxt?WR8vJa%zTrKy2Q2&f2F3$e4xvwg#%*3_T4FO^wt9eFspRxd)&-=R7@Oj)Q?k=B46S2aDeyrayFy zh$fbu`QESa(gyn}G7``Tdj_kSz?;Nm%N1r>wZ>Q2@re6$2c(TN9#PP`A<`#E^ovg# zum#*pg&oVuj!C1`k#u5%IZu;S=(>vUc6G%SKm7BZK=l@(WlMyl#(QY%lAl5fykLzVsMU#DU3{X91JT(5>)9On3?zSmxeE@=R#7N9FP_rC4kuEdD67c|X*ok37tDVo| zM%uLy`ox$kw;~EDWy8&4=wzOJ+O2MXL3cDJ+i0|rJlPl2VT@QoBuzbZg@yas%OFrw zOUcp`&=1YPXic^xmcGAG(tw5gM)4p}#i&%Eg1^Mwd7RW+br%!(HD_E>*K-3dy{o$A zQTJwGJF&ww){*h;hUXdIVpQjBBI98W9Ut}`G{WBiZ zh_h|*1BI{viKB$+(D5=2-JM#NrxRT{#o87x2}cM?;=MxQ$T9%Zg_Tm2@o2KL0Xzt* zA!glo>UC28PwVR%3H)f=tETNg66n0&Y>9DPiQu-La44jpS3cbk<1m#D)mzUk?Sb17 zS9gW3=f)I~s@SUcW?HF$I~T^na)@_MYanRCV*>7V{DL2gJ0I|gXNtEcSRXXziB-wN zYZz*ke|%HM{zT|4`cue*{@8v3eDdLyn2SW)o4LQ1;iZbpB|KWj;XT&U9uy2duEdYI zvM+B`AxMuLSW3)2VmXw#5FmwNEr$TWHzHq1S!*Bv(xuf={A@9p4DDZtTCG8DoW6tZTz88w_LEnOD*b6n0jjv0NpO;Uc8q6c*Q& zhEdxw73Pa61WhnvpNJWOKT9G5K?(+AhQVNf9s)qai2)Z129G@#A*oSYj(KJnofES% z&(ARZ2SOZ-VX&!cG;wL%~5;2A%Z(t4XK_kvQBzhaSq5s8c znD(2*fr(}l3=*R6QBbuObM=)bKvOUTzJ3pDJ48h%ehcM(ZIStu^f;-5Ii^2G<2YZ+ z+r|1|Um_pTn@OPr{?!&_5;OW3157_flM=%$;%HhxtY>snPK+5^;Ku>SpCO)y>C*vD z!$*P(jy7c6c~2zTd=$hND}N1TI*!VOHgU9ROA^r179vnRTM7}BGY!$ujwtTv`PtkA zeiXTC0XSlsAg8RgN+6McV&)nyL|MB=O2WGH9j>qwXyeQQFR#saQA$CYv;uNMCI^_7 zx5kXFUpN9K-4aK1Ee{lb%0DadR#(Mtz~eFrlw?liH_9Sbl;p656|)8N*v;hAUJpD2 z=h&3e^aU&FEEnZCHBL*U4d?sBtJP?K5lHko*mE!acpbh=I(W+^S-g9px+O<$^e&d* z5_9pS@;~0nR@Y~wc8f-%r7FGf-6(mHO-K=achaAC`8o!_J+EGnHdWO9n%boKj+gPr zTs%7c_sUC-n;iS^fBhv^B%DM4zc*oezebG=!UbxNW8Qh;5^e_DR-8aWSx1=RVIZjq zUcG4;h4N>0|36(mq9upt132wyy-##K2Ml$2kSy{O2kQp4CF+*zn^WyOcJl_zBZRJxwIm zviytqR$##cG@99wIAHsFuj15-pnTF5NUXQ9enKUQP=>Y*(->HBU+@M%f;g_S|^GS@MO=4APsDOAGXyw390UNlZP9?GTN~O3$O2GIQKT`ONQVbc&rgU!t zCvo-X#MT3Fia6oVtCyaXr7>Uuxc>s*hp4?Y-Z6-WM@ps09PD>R<~sD;Md{?0&5p$r~E*5CuW!+NN14y_?|@) zhsqEZq&_gbrzWkCgoj)EdruIQ88KRt8yZA_e2B%lx4Q&E35r2%uD!dGK}m`rVq?II z*ML`NKy{I}ru0vgEuJ1P9-wVCn0zWA;K3InlXsKcn;vbBwOT0foKZg_g9%JBXO@^|GYU?s`?@h+WKHIC8qxie0bB=+VrmsV9{0|!Zu(9&wDq= z04)!A5sSyffW@gJ%r&5LR``eEIqzsOf(V!a@O?ZPHnMAJj|kqnlurnk<<%gavDRWb zl8x6J4A?Fwgt3{bq#=nKlDSMkwUS`|N91Hm$COkgz9HSYlvIH%)r71Y6(!7C6&Uxl zwM>*cAXsR=WOiDI_+GFe$bOs3L(qTKDaYvuZdCAr`OOj*%&!S%BA4wVYf61Ph>%@* zlc#kgCCLHW)$DtmsU^np-i!5EK>fgBQ5y>PZR3eg;p)4*oH2YYUuX^pd>G^nwiX2a ziuX}^1dVM!P&opv%T5v@sbtRP8crb(%Qu5>t#7}IDl@Sf8x0hxS?XClw1 z>=O9VLA?{y>NNPCi25{`>0FlY zY+0V)+hSoW{1F>$aFbIswZ)Xx7hP_Htrj1J$-MF=jD=;Ed2}4R{3z?7;iO* z570mg5@q5Mu~lwblb*`!%bV_3&ZGBdQhB#9f>t$oC^XAWUmze z$N+Zp_%>950ubIAfBB#7g=YLEycq~QrDyEXnlO%Um+CPGPVg293vjr+r@|(2Yl*)Q z%bG?%V$%}zgl$}VD^SF)=VpNEJZ;8+NCNw1jK>-9Z#u%|tJcrDjH_B8JjHCH=Cv31gS{r{QZfx=mih4^iJI_T~GEp4owA~ zHvC;&+biDL9`qGrPa-D|#j8>geSAiLy36*rB$ZrW+TwSD+*!5iJV4lYz9-nSC*<@? z&1>yh>B)m{vf8t1d@fG<19hAF|CFW<4QG1$oIbd&^zIK`q(2`wR|&Gaus!K3)%SPz zIajRndYyMGVc$Ygn0}~Yy_+QZuJ+dT_vN^*sO63SEDP5%-wxMhH35bDJ1fye(PVe0 z`K)yELEEK_#Sn{+XFI>H{YTNrrN`}Y93rb5x4mlV>n8;?SSlADjtkA@xmF2XLf&kgl9BGpc!^i3B(OH61tPt&Rbj?v+qtmcoGPp|*D?V7xwgjj0iR{pPE9ML=TsU|7m^{p$TnFT$mm6L2#huL|q>RZp6FAdu7hLRW8pWC`VrLE!0;$wI_n6_K=^TJ_4TYNEb?NBe{M$iyj zbkzRp!Y<_?k!kD60D0D*h`+_OOPn=9FSU7`rmbrVx=*y&UpJlC+G94tc5f{byY{mN zqU&dOl5wJI`R%5C>r&)JC(&!Pnnf2Vr@ij3yF)stQufZDzmdyOgV&hrMtl_HxzJZ% zzB!pQuU$*)(rHu}_VrM`6Qle4ay@oKMIx0?2(vjN6 z#B^;L({_8G>ihba%ZkN5x zVhy@TcdvftSahdF|H`h&$B!n0e!+;wW4Ix{?ze)!fqkf`)SItQj&`5@&cV1X#ag76 zv#scti?SFyX!n>qqW?166Ij(GBw z%=5g^7k`}`-_b1SSKzvm)cKRwPY#`~t<3zf{B52^EoJZgH^9WXZrJX6ysy=FnQs4- zPc=>c)ZoGD>1<*{+VU+9twf92$zOJUY5bvZHV?mHpU9h>W^sxd0@fh=8Glh~2Py=0 zu?ERj53+{I=O8IhUB9J+XU#3Y|1}+N>G)>5@s-Q~NM zWxJgEpDeuIEVMjNm&kanRH;rxaz)bIBz?#-sc*3=s#9y6a~oRAYf7#PgfpKt_^qF- zUPsP%7j;YR;)UjH=cOvu|6Zfetb$&jJty~{V$5u$F?hv3+|uB-JGv)3LHe$lrbho7 zqpV@q<+`KKrKw8FQpjtz$&!4zs%H9aZnl+vli8i*(9+IFxrvjztD->}dR`nK_I>%X ze%yYEmq{s!2+hI2{utNLL#9rJ5%0T#G2Zu|)OeFSLr3nvU7Fq+ie{eq-(cNku(Zu) z`3igcyOLoz`*chfs4!6^24akKY9i>n}C$zTHu_4;#rg z3#9UDm(#lrs5s)oBm9#r{(0)!z@J()ej8^B$0$7cobja=`c_6?!55z9<#*R^YPj_# zr;4Z7!dUK+hC&swsbh*%{u+nb#wDK2^ru)pq<5!q!L^t=FYTCzlz3JiuW^Skzb+hJ zRsP+Z!SxrBCLqAv@;#H1%8dHkMwcX&2WPi`w@6CcXRk|tolP0tpzXRG!V}=I;Iwdl zFmgNlaM{&Q`UyqWubMl{m&u>bXcT51-VudyUSB$$UKfnQCFp5ulsv}L+=^nAi?!cB z_ZX*gb4hW2O0i!o{4^4;CeGJVu1KvbP~z(vzJ7;EvKe8!C9=asGQT5K#o`5mtZpZe z5OZUU43?s33;Q39oM|-M=^n>BmzH!?YnzlJV~b&ILoHJYZG>*N?yXp=NGz$X)TNA; z(5AJET1(Sf#+X&-uUj ze!rjZ^SCd6r4{NyQ1qTeFSpRpMXtmrHmUMA@4D-u39K}nOpX0U?C zMwp)x4PSUDA(E+6Lhug$A+mi;XgAO=O)P3&<79H@ zDwMZk+~q0Gh6>iJTV!w~d6h&{2kYoQfH&1U4g_>PzCT0iH4qb6T`KzmlaJ^OYKt%G zpC)%dAwg$GL7=0}sqv9l5w~l)T?%M08hN3NnMAAPBu%Pd63=8KFM!xWirMK^ZeCC) zIp-p`O#I*dAkZxVwuUweijgoK*+Mqn8Zh;wC;bO#a>- zmJxYVD`9i>M)j(7>R?u&luK2CDV5FgyNm*p87LU6Wcfl=8p9a`P*ec=ed(^`V1Wf_ zXISZAn2qnG%Y-V>&cmu<(V4X8I24Yj5oh}gL0cMDC?&cHs%2-SU5mI1WnB!DWFd!_ zNag#p18vPEMr1iE!q|TjsN6eU!N%8N?glY*P7CP}XMhCEG<{ezFG@}1U{EZ-9Mn(X zknD5W6FM!5n8`pDO{%DXJYPs;6|{KVTM5z?s)1h(yygL4Nc3ZAm?%vuP+-dbrP-Md}5WO{Zyy@j;P*&|| zG7z~6^l!32(8YHNql%Y%24bWVUiVJ1@oczdTDS)&qSU`}33y}84o>MFzn*V&PcN9n z(!C=Kks6bWT?+Lusg958ZNk#Wxn2Rxr`B?-S3%}Rzh+Oe2wX~fh-RD^R?SLhIH=7A z+x_l+37GxnuPt>d-k=&1G!NWQTBMIe(gg$P-hv6X(^~O+VyCBtgfr5Y$nGdX@YFK~xTIc4?%%r-BG2v%C?cJ500!W}{AQAgv~@rj+0vGu$; z5rgLFj+-8S@8hV5W!6R6b4#;4%Cq5)+DB8Np8{XJ+bBq`#33U7$cq}=d|!ohY}Wcl zuBN*oc{|&0R}>ezaD#@SWTkb}u0#k{{NkLgin4vFdy!i?E|`(Fb^C6At3@5Z)mtz1 zBQ7a$8H<4kKi}JJ;<34WM+#$h!BgZ`mIWVR4Gz*HW5xf4y@}(zkC64UfiEj+|0nmJ z5Fk5EcYSEYkxt}%p^xpq_w$Zro2NM3y#M@584eBZO>e>wQ@bm55qoi;RyV4RljVjP zX@}!wKBArrQ?Zt_<`1g+yQ3l=-k-{Ij#n3x%*a$Ee&5Z`dq-Dw#)3v=*89uUEOsyd z1oTqlDLhX0=L!=7T~Pz*hT>hrujbCcpC~W~b3yZ5qm##5-g51TfMK zPhP2yx0LY9uj!V;dZwSM!(x%UU$#7i%kKuLuPkJ_^QpJ(DtoG^ofZ=z>_P6tI;En1 znxpb&u4AY@dZQY>M#3^a8_pp40_?SZOxlYK^zGbZD--^EHG)EjRnFaNb>v;qqCIbP z`_n7+%x&jne#Ico7AqH(^tlfZZ$rzSc>CFBzYE!~waNxv*2iv~`AvMu_Q;GW@3v?A z+G=Ekr{Yxk*RzogeT-1^3Aw6PMh{1HH)7+Hk{fB=-Za89LCzd0P$FgCG#O8jI<< z<2IUnl-Z`?W{}=<9-iF-)esC&ixDC47S!P5O!iPXr6cB}W$0381}zoq?FrKy;hIN6 zq2(Rx(fth~t&V?MjhGuUqt5cTDv|JWfcC~lXlDAfO|5!{FzZBgM3F*`*6G#;mwc#X zjLPpZ&ewj0Ek1d`XQbuB*$086HgBWoLA;tBF0NusBYrd|cn2>T^SL@U!zD7RbdM*| zbmd!-`yQR99_J8pX1q4YH4R&NE*hoDTK`MC`5EhBCQ2##+jc+YcJ%$s&4+Ts{Mu4) zIe(_4O-asqFi(ueQc3L<2h5XO$Yd&+7sL`Kz?wfd4tP%BdTr^E{FHB#oVw0We9*nP Pd$4Lp{7c0X$ezCdifpcy literal 0 HcmV?d00001 diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/deletebrand/controller/DeleteBrandRunController.java b/backend-java/src/main/java/com/nanri/aiimage/modules/deletebrand/controller/DeleteBrandRunController.java index 49d3d4c..157ef90 100644 --- a/backend-java/src/main/java/com/nanri/aiimage/modules/deletebrand/controller/DeleteBrandRunController.java +++ b/backend-java/src/main/java/com/nanri/aiimage/modules/deletebrand/controller/DeleteBrandRunController.java @@ -7,6 +7,7 @@ import com.nanri.aiimage.modules.deletebrand.model.dto.DeleteBrandTaskBatchReque import com.nanri.aiimage.modules.deletebrand.model.vo.DeleteBrandHistoryVo; import com.nanri.aiimage.modules.deletebrand.model.vo.DeleteBrandRunVo; import com.nanri.aiimage.modules.deletebrand.model.vo.DeleteBrandTaskBatchVo; +import com.nanri.aiimage.modules.deletebrand.model.vo.DeleteBrandTaskDeletionStatusVo; import com.nanri.aiimage.modules.deletebrand.service.DeleteBrandRunService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; @@ -83,6 +84,15 @@ public class DeleteBrandRunController { return ApiResponse.success(deleteBrandRunService.getTaskDetails(request.getTaskIds())); } + @GetMapping("/tasks/{taskId}/deletion-status") + @Operation(summary = "获取删除品牌任务删除状态", description = "供 Python/插件按 taskId 查询该删除品牌任务是否已被前端删空。") + public ApiResponse getTaskDeletionStatus( + @PathVariable Long taskId, + @Parameter(name = "user_id", description = "当前登录用户 ID", required = true, in = ParameterIn.QUERY) + @RequestParam("user_id") Long userId) { + return ApiResponse.success(deleteBrandRunService.getTaskDeletionStatus(taskId, userId)); + } + @PostMapping("/tasks/{taskId}/result") @Operation(summary = "提交删除品牌处理结果", description = "插件/前端分片回传处理结果;后端累计分片并推进进度,全部完成后再执行最终组装与落库。") public ApiResponse submitResult( diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/deletebrand/model/vo/DeleteBrandTaskDeletionStatusVo.java b/backend-java/src/main/java/com/nanri/aiimage/modules/deletebrand/model/vo/DeleteBrandTaskDeletionStatusVo.java new file mode 100644 index 0000000..27f389d --- /dev/null +++ b/backend-java/src/main/java/com/nanri/aiimage/modules/deletebrand/model/vo/DeleteBrandTaskDeletionStatusVo.java @@ -0,0 +1,17 @@ +package com.nanri.aiimage.modules.deletebrand.model.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Data +@Schema(description = "删除品牌任务删除状态响应") +public class DeleteBrandTaskDeletionStatusVo { + @Schema(description = "任务ID") + private Long taskId; + + @Schema(description = "该任务的结果记录是否已被全部删除") + private boolean deleted; + + @Schema(description = "该任务当前剩余的结果记录数量") + private long remainingResultCount; +} diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/deletebrand/service/DeleteBrandRunService.java b/backend-java/src/main/java/com/nanri/aiimage/modules/deletebrand/service/DeleteBrandRunService.java index 684a806..a562275 100644 --- a/backend-java/src/main/java/com/nanri/aiimage/modules/deletebrand/service/DeleteBrandRunService.java +++ b/backend-java/src/main/java/com/nanri/aiimage/modules/deletebrand/service/DeleteBrandRunService.java @@ -23,6 +23,7 @@ import com.nanri.aiimage.modules.deletebrand.model.vo.DeleteBrandLineProgressVo; import com.nanri.aiimage.modules.deletebrand.model.vo.DeleteBrandPreviewRowVo; import com.nanri.aiimage.modules.deletebrand.model.vo.DeleteBrandResultItemVo; import com.nanri.aiimage.modules.deletebrand.model.vo.DeleteBrandRunVo; +import com.nanri.aiimage.modules.deletebrand.model.vo.DeleteBrandTaskDeletionStatusVo; import com.nanri.aiimage.modules.deletebrand.model.vo.DeleteBrandTaskDetailVo; import com.nanri.aiimage.modules.deletebrand.model.vo.DeleteBrandTaskItemVo; import com.nanri.aiimage.modules.file.service.LocalFileStorageService; @@ -31,6 +32,8 @@ import com.nanri.aiimage.modules.task.mapper.FileResultMapper; import com.nanri.aiimage.modules.task.mapper.FileTaskMapper; import com.nanri.aiimage.modules.task.model.entity.FileResultEntity; import com.nanri.aiimage.modules.task.model.entity.FileTaskEntity; +import com.nanri.aiimage.modules.ziniao.model.vo.ZiniaoShopMatchResultVo; +import com.nanri.aiimage.modules.ziniao.service.ZiniaoShopSwitchService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.poi.ss.usermodel.Cell; @@ -68,7 +71,7 @@ public class DeleteBrandRunService { private final FileResultMapper fileResultMapper; private final LocalFileStorageService localFileStorageService; private final DeleteBrandTaskCacheService deleteBrandTaskCacheService; - private final com.nanri.aiimage.modules.ziniao.service.ZiniaoAuthService ziniaoAuthService; + private final ZiniaoShopSwitchService ziniaoShopSwitchService; private final OssStorageService ossStorageService; private final ObjectMapper objectMapper; private final DeleteBrandProgressProperties deleteBrandProgressProperties; @@ -96,7 +99,7 @@ public class DeleteBrandRunService { int parsedSuccessCount = 0; int parsedFailedCount = 0; Map parsedPayloadByFileIdentity = new LinkedHashMap<>(); - Map storeMatchByShopName = new LinkedHashMap<>(); + Map storeMatchByShopName = new LinkedHashMap<>(); for (DeleteBrandSourceFileDto sourceFile : request.getFiles()) { DeleteBrandResultItemVo item = new DeleteBrandResultItemVo(); @@ -117,25 +120,22 @@ public class DeleteBrandRunService { item.setPreviewRows(parsed.previewRows()); try { - String normalizedShopName = normalizeShopName(item.getShopName()); - com.nanri.aiimage.modules.ziniao.service.ZiniaoAuthService.StoreMatchResult matchResult = normalizedShopName.isBlank() - ? new com.nanri.aiimage.modules.ziniao.service.ZiniaoAuthService.StoreMatchResult(false, null, null, null, null, null) + String normalizedShopName = ziniaoShopSwitchService.normalizeShopName(item.getShopName()); + ZiniaoShopMatchResultVo matchResult = normalizedShopName.isBlank() + ? ziniaoShopSwitchService.emptyMatchResult() : storeMatchByShopName.computeIfAbsent(normalizedShopName, - ignored -> ziniaoAuthService.matchStoreByNameAcrossStaff(item.getShopName(), null)); - item.setMatched(matchResult.matched()); - if (matchResult.matched()) { - item.setShopId(matchResult.shopId()); - item.setPlatform(matchResult.platform()); - item.setOpenStoreUrl(matchResult.openStoreUrl()); + ignored -> ziniaoShopSwitchService.matchStoreByNameAcrossStaff(item.getShopName(), null)); + item.setMatched(matchResult.isMatched()); + if (matchResult.isMatched()) { + item.setShopId(matchResult.getShopId()); + item.setPlatform(matchResult.getPlatform()); + item.setOpenStoreUrl(matchResult.getOpenStoreUrl()); } else { throw new BusinessException("未匹配到紫鸟店铺,跳过处理"); } } catch (BusinessException ex) { String message = ex.getMessage(); - if (message != null && (message.contains("code=40004") - || message.contains("userId存在无效的参数值") - || message.contains("无权限") - || message.contains("没有权限")) && !message.contains("白名单")) { + if (ziniaoShopSwitchService.isSkippableMatchError(ex)) { log.warn("[ziniao-match] suppressed upstream permission error for {}, msg: {}", item.getShopName(), message); item.setMatched(false); } else { @@ -301,6 +301,24 @@ public class DeleteBrandRunService { return vo; } + public DeleteBrandTaskDeletionStatusVo getTaskDeletionStatus(Long taskId, Long userId) { + FileTaskEntity task = fileTaskMapper.selectById(taskId); + if (task == null || !MODULE_TYPE.equals(task.getModuleType()) || !userId.equals(task.getUserId())) { + throw new BusinessException("任务不存在"); + } + + Long remainingResultCount = fileResultMapper.selectCount(new LambdaQueryWrapper() + .eq(FileResultEntity::getTaskId, taskId) + .eq(FileResultEntity::getModuleType, MODULE_TYPE) + .eq(FileResultEntity::getUserId, userId)); + + DeleteBrandTaskDeletionStatusVo vo = new DeleteBrandTaskDeletionStatusVo(); + vo.setTaskId(taskId); + vo.setRemainingResultCount(remainingResultCount == null ? 0L : remainingResultCount); + vo.setDeleted(vo.getRemainingResultCount() == 0); + return vo; + } + public void deleteHistory(Long resultId, Long userId) { FileResultEntity entity = fileResultMapper.selectById(resultId); if (entity == null || !MODULE_TYPE.equals(entity.getModuleType()) || !userId.equals(entity.getUserId())) { @@ -400,13 +418,6 @@ public class DeleteBrandRunService { return value.replace(" ", "").trim().toUpperCase(Locale.ROOT); } - private String normalizeShopName(String value) { - if (value == null) { - return ""; - } - return value.replace("\u3000", " ").trim(); - } - private List resolveCountryPairs(Row titleRow, Row headerRow, DataFormatter formatter) { List pairs = new ArrayList<>(); int lastCellNum = Math.max(titleRow.getLastCellNum(), headerRow.getLastCellNum()); @@ -876,7 +887,7 @@ public class DeleteBrandRunService { throw new BusinessException("存在未完成的结果分片: " + parsedFile.getSourceFilename()); } MergedDeleteBrandFile mergedFile = mergeChunks(parsedFile, chunks); - File outputFile = buildResultWorkbook(task.getId(), parsedFile, mergedFile); + File outputFile = buildResultWorkbookPreserveLayout(task.getId(), parsedFile, mergedFile); deleteBrandTaskCacheService.saveProgress(task.getId(), Map.of( "phase", DeleteBrandTaskCacheService.PHASE_UPLOADING, @@ -1032,6 +1043,52 @@ public class DeleteBrandRunService { } } + private File buildResultWorkbookPreserveLayout(Long taskId, + DeleteBrandParsedFileCacheDto parsedFile, + MergedDeleteBrandFile mergedFile) { + File outputDir = FileUtil.mkdir(FileUtil.file(System.getProperty("java.io.tmpdir"), "delete-brand-result", String.valueOf(taskId))); + File outputFile = buildNamedOutputFile(outputDir, blankToDefault(parsedFile.getSourceFilename(), "delete-brand-result.xlsx")); + + try (Workbook workbook = new XSSFWorkbook(); FileOutputStream outputStream = new FileOutputStream(outputFile)) { + Sheet sheet = workbook.createSheet("删除品牌结果"); + Row countryRow = sheet.createRow(0); + Row headerRow = sheet.createRow(1); + + for (int countryIndex = 0; countryIndex < mergedFile.countries().size(); countryIndex++) { + DeleteBrandProcessedCountryDto country = mergedFile.countries().get(countryIndex); + int asinColumnIndex = countryIndex * 2; + int statusColumnIndex = asinColumnIndex + 1; + + createTextCell(countryRow, asinColumnIndex, country.getCountry()); + createTextCell(headerRow, asinColumnIndex, "删除ASIN"); + createTextCell(headerRow, statusColumnIndex, "状态"); + + List items = country.getItems(); + if (items == null) { + continue; + } + + for (int itemIndex = 0; itemIndex < items.size(); itemIndex++) { + DeleteBrandCountryResultItemDto item = items.get(itemIndex); + Row row = sheet.getRow(itemIndex + 2); + if (row == null) { + row = sheet.createRow(itemIndex + 2); + } + createTextCell(row, asinColumnIndex, item.getAsin()); + createTextCell(row, statusColumnIndex, item.getStatus()); + } + } + + for (int i = 0; i < mergedFile.countries().size() * 2; i++) { + sheet.autoSizeColumn(i); + } + workbook.write(outputStream); + return outputFile; + } catch (Exception ex) { + throw new BusinessException("生成删除品牌结果文件失败: " + parsedFile.getSourceFilename()); + } + } + private void createTextCell(Row row, int index, String value) { Cell cell = row.createCell(index); cell.setCellValue(value == null ? "" : value); diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/ziniao/model/vo/ZiniaoShopMatchResultVo.java b/backend-java/src/main/java/com/nanri/aiimage/modules/ziniao/model/vo/ZiniaoShopMatchResultVo.java new file mode 100644 index 0000000..b88ca41 --- /dev/null +++ b/backend-java/src/main/java/com/nanri/aiimage/modules/ziniao/model/vo/ZiniaoShopMatchResultVo.java @@ -0,0 +1,30 @@ +package com.nanri.aiimage.modules.ziniao.model.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Schema(description = "紫鸟店铺匹配结果") +public class ZiniaoShopMatchResultVo { + @Schema(description = "是否匹配成功") + private boolean matched; + + @Schema(description = "店铺ID") + private String shopId; + + @Schema(description = "店铺名称") + private String shopName; + + @Schema(description = "平台") + private String platform; + + @Schema(description = "匹配到的员工 userId") + private Long matchedUserId; + + @Schema(description = "打开店铺链接") + private String openStoreUrl; +} diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/ziniao/service/ZiniaoShopSwitchService.java b/backend-java/src/main/java/com/nanri/aiimage/modules/ziniao/service/ZiniaoShopSwitchService.java new file mode 100644 index 0000000..3f9b6d6 --- /dev/null +++ b/backend-java/src/main/java/com/nanri/aiimage/modules/ziniao/service/ZiniaoShopSwitchService.java @@ -0,0 +1,53 @@ +package com.nanri.aiimage.modules.ziniao.service; + +import com.nanri.aiimage.common.exception.BusinessException; +import com.nanri.aiimage.modules.ziniao.model.vo.ZiniaoShopMatchResultVo; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class ZiniaoShopSwitchService { + + private final ZiniaoAuthService ziniaoAuthService; + + public ZiniaoShopMatchResultVo matchStoreByNameAcrossStaff(String targetShopName, Long preferUserId) { + String normalizedShopName = normalizeShopName(targetShopName); + if (normalizedShopName.isBlank()) { + return emptyMatchResult(); + } + + ZiniaoAuthService.StoreMatchResult result = ziniaoAuthService.matchStoreByNameAcrossStaff(normalizedShopName, preferUserId); + return new ZiniaoShopMatchResultVo( + result.matched(), + result.shopId(), + result.shopName(), + result.platform(), + result.matchedUserId(), + result.openStoreUrl() + ); + } + + public ZiniaoShopMatchResultVo emptyMatchResult() { + return new ZiniaoShopMatchResultVo(false, null, null, null, null, null); + } + + public String normalizeShopName(String value) { + if (value == null) { + return ""; + } + return value.replace("\u3000", " ").trim(); + } + + public boolean isSkippableMatchError(BusinessException ex) { + String message = ex == null ? null : ex.getMessage(); + if (message == null || message.isBlank()) { + return false; + } + return (message.contains("code=40004") + || message.contains("userId\u5b58\u5728\u65e0\u6548\u7684\u53c2\u6570\u503c") + || message.contains("\u65e0\u6743\u9650") + || message.contains("\u6ca1\u6709\u6743\u9650")) + && !message.contains("\u767d\u540d\u5355"); + } +} diff --git a/frontend-vue/src/pages/brand/components/BrandDeleteBrandTab.vue b/frontend-vue/src/pages/brand/components/BrandDeleteBrandTab.vue index 2db5a03..182e4f7 100644 --- a/frontend-vue/src/pages/brand/components/BrandDeleteBrandTab.vue +++ b/frontend-vue/src/pages/brand/components/BrandDeleteBrandTab.vue @@ -249,6 +249,9 @@ const queuePayloadText = ref('') const taskDetails = ref>({}) const pollTimer = ref(null) const pollingInFlight = ref(false) +const chainStarted = ref(false) +const activeItemKey = ref('') +const autoAdvancing = ref(false) const displayPaths = computed(() => selectedPaths.value.slice(0, 10)) const currentSectionItems = computed(() => { @@ -517,6 +520,7 @@ function scheduleNextPoll(immediate = false) { let stateChanged = false try { await refreshTaskDetails(taskIds) + await maybeAutoAdvance() for (const taskId of taskIds) { if (isTaskTerminal(taskId)) { removeSessionTaskFromStorage(taskId) @@ -766,34 +770,38 @@ function isPythonQueueBusy() { return false } -async function triggerItemRun(item: DeleteBrandResultItem) { +function getItemKey(item: DeleteBrandResultItem) { + if (item.resultId != null) return `result:${item.resultId}` + return `task:${item.taskId || 0}:${item.sourceFilename || ''}` +} + +function findNextAutoRunnableItem() { + return currentSectionItems.value.find((item) => { + if (!item.openStoreUrl) return false + if (getItemKey(item) === activeItemKey.value) return false + return getQueueStatus(item) === '未入队' + }) +} + +async function runItem(item: DeleteBrandResultItem, options?: { auto?: boolean }) { if (isPythonQueueBusy()) { - ElMessage.warning('当前存在正在处理的任务,请等待其完成后再推送下一条!') - return + if (!options?.auto) { + ElMessage.warning('当前存在正在处理的任务,请等待其完成后再推送下一条!') + } + return false } const api = getPywebviewApi() - if (item.openStoreUrl) { - 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 - if (!taskId) return + if (!taskId) return false const sessionTask = sessionTasks.value.find(t => t.taskId === taskId) - if (!sessionTask) return + if (!sessionTask) return false if (!api?.enqueue_json) { ElMessage.error('当前环境未启用 pywebview enqueue_json(浏览器环境不会推送)') - return + return false } const payload = { @@ -811,18 +819,60 @@ async function triggerItemRun(item: DeleteBrandResultItem) { 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 } + activeItemKey.value = getItemKey(item) saveSessionTask(taskId, sessionTask.items, qpr, queuePayloadText.value) - ensurePolling(true) } else { qpr = `推送到 Python 队列失败:${pushResult?.error || '未知错误'}` } queuePushResult.value = qpr + return Boolean(pushResult?.success) +} + +async function maybeAutoAdvance() { + if (!chainStarted.value || autoAdvancing.value) return + if (isPythonQueueBusy()) return + + const currentKey = activeItemKey.value + if (currentKey) { + const currentItem = currentSectionItems.value.find(item => getItemKey(item) === currentKey) + if (currentItem) { + const status = getQueueStatus(currentItem) + if (status === '已入队' || status === '处理中') { + return + } + } + } + + const nextItem = findNextAutoRunnableItem() + if (!nextItem) { + activeItemKey.value = '' + chainStarted.value = false + return + } + + autoAdvancing.value = true + try { + const started = await runItem(nextItem, { auto: true }) + if (!started) { + chainStarted.value = false + } + } finally { + autoAdvancing.value = false + } +} + +async function triggerItemRun(item: DeleteBrandResultItem) { + chainStarted.value = true + const started = await runItem(item) + if (!started) { + chainStarted.value = false + activeItemKey.value = '' + } } function openShop(url: string) { diff --git a/frontend-vue/src/shared/bridges/pywebview.ts b/frontend-vue/src/shared/bridges/pywebview.ts index fe4dc0f..2a08292 100644 --- a/frontend-vue/src/shared/bridges/pywebview.ts +++ b/frontend-vue/src/shared/bridges/pywebview.ts @@ -21,7 +21,6 @@ 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 {