From 83bc90ecf2193ee3127140177203b84ea967ea3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=93=AD=E5=9D=A4?= <10372463+ming-kun@user.noreply.gitee.com> Date: Thu, 30 Apr 2026 10:52:52 +0800 Subject: [PATCH 1/3] modified: amazon/__pycache__/approve.cpython-39.pyc modified: amazon/__pycache__/asin_status.cpython-39.pyc modified: amazon/__pycache__/del_brand.cpython-39.pyc modified: amazon/__pycache__/match_action.cpython-39.pyc modified: amazon/__pycache__/price_match.cpython-39.pyc modified: amazon/approve.py modified: amazon/asin_status.py modified: amazon/del_brand.py modified: amazon/match_action.py modified: amazon/price_match.py modified: main.py --- app/amazon/__pycache__/approve.cpython-39.pyc | Bin 32692 -> 32800 bytes .../__pycache__/asin_status.cpython-39.pyc | Bin 18497 -> 18497 bytes .../__pycache__/del_brand.cpython-39.pyc | Bin 18509 -> 18616 bytes .../__pycache__/match_action.cpython-39.pyc | Bin 19276 -> 19336 bytes .../__pycache__/price_match.cpython-39.pyc | Bin 34595 -> 35376 bytes app/amazon/approve.py | 217 ++++++++---- app/amazon/asin_status.py | 2 - app/amazon/del_brand.py | 9 +- app/amazon/match_action.py | 232 ++++++++----- app/amazon/price_match.py | 308 +++++++++--------- app/main.py | 2 +- 11 files changed, 473 insertions(+), 297 deletions(-) diff --git a/app/amazon/__pycache__/approve.cpython-39.pyc b/app/amazon/__pycache__/approve.cpython-39.pyc index d86cdd4d3588961fa0d2ae31faf533a61d37655c..84f4d46d52e464065f3b2b2fbfcb42e6db0aa3c7 100644 GIT binary patch delta 2195 zcma)8Z){Ul6uDX>ML zvI!`Eta)nGtw~G#6Fz|GkSKw~FZ#jw$rwn6pETVv6~RRKq``A;yD<_qzUKbkx##aa z_nmXL^LJ=rNbz*J-8zA1N$YN9ZVHb|wDpXZ{W;xVO>FANk6Q@c?&rKdbQabxy zY4q~+yRU?otqo5XKby{<3Wt~dG;}OHb!K$hHuP%ggUj4&KHa-Y;Tz0>-WH8FfeW+P zyIA8CsjqvVH^+F>y3MC4Uj>X?HViR(xpekIID_RHp3IF+oj+ANaxt8#!+q(k!zJ_3 zjZ4Q%mxf9o9V@**GL<{r*4Ad$^7W30kFNwJe%kCvKbCDo@*#v)guMs}fQXx;D?R}* z;fnQSl4;%-O?K^%^KX#y*8BHV;=|@%Qz0Dvaib`jG9-x}2UD2sR?Np~8_$`GU?s4anJ1g~B zP^wc^ae+mM>Pk@YfklY6SX|U)@2`FQzyaQKFuu7f)!kN!YQ?Y-%{-f~3&pG$x$;!O zdv+WQD(1$0JF`qT98fP-LE)>bLMy7P)(LP!m2a^SiOc=NIPMHO$3V8Jxp1ab}P_k zK{RFaq{EuLoESnn09oMQT+FelExUt*1k0TTZE7zP7;1D!>vqNP8opV7qU^6=fl=&$ zet{4|x|%O6;9vo?pX?ghntP?BUU)&$Abca8CZ`KPt>*zi6&me+?YV0f$h90tfIFH928%@aZ|FZx>7pe`Ut1 zY;oEt{FNCiKaHHR0mDD&PwFj95;$aYw6}fEm9~^W1AeQ>lJSY241Ixnp@1?zfZ2gi zZrj!jN+Qck7tPX{cgjPdHPaw@I8H71`J)keWnz1YsROq{=* z3l~urbYy_4-Ag*+Loe9T1gzV5t4LtT(3vT zhGHFQBMFEZBmM)Zqht2PeSap{2jj_L6wn0Vc^RJc8I}D!e%Q*?QSUU~iwgMAoY_#o zle;Oh72>1yGl(?M2BNJ-u<}zc$Uv#Sj4|Tc<4OS=itr6`-DI3%yS|Jt%6M!q@d!U` z`>4WKeJItVpbW&%Nf?Lpqe zI*yDX(dcy?(!>xzmD0n+0n;powZ5d90TDIeW{_#!34m@8T1VwIh_tB4f|s@n$*2dK zegI82!W1LCp#p2GJG+cq(L=YQ{$&-#YZ|*^DYTmS)>2|OuLX&hD&-KCW_}cz!74ou z-7*^{y9KBXkC9U4Fo6qEs*<*I*bMeT0R?&D58#3=pv$@IF7z`ZMxqMY0@^v48>)>h z$i^>||4%JB_Y;H*Jz_DZR%ithho(Y;#PH@MUMlnvwJlHsQk03&;4}t~O9jP<3yd2D zQ8txiQJvF5#b|!4h3@ET_Ov_E;J|J!xxKMhm615)$u}f3Y)0IXD3+0T0@^Z35^tHO z&x=qURE7XgRe22!RVUsOmZxbLEL&&BauJOgx zSi;K2U{!hV-T8AD7R%o*mM&C2yL{*B=g-Bno4Mhe(w(o*&liddS1&9UPu=CD04ugj=!bK|=&vKFE;v5(AAUdM#!)(I3p$(2_ zQyC*SkUBD^_j^WCL*oNudfNCMd~O8NZ;xaKhX>Nx!9hKpHg59WJzVev@C`GJaf7?3 zj~vyFZlN>uAwy3O4+xv_K6k#t#rj90g|8MsJg;nr@6;TukWZ5XID&mxlRfY#Sdju) z!EM+l`LKjNU~4gBk1uLeFbtjOG(`+9IANUm#wcp5}$ z2C0Fr5$uv1ao_9=-9Pz?^qazVQ5&yf*J%sB&;F+S r*h^*?eBrZZLTR*+pM7dh&f$`u3M&3kTUM>b85I_Gh(_|yBl_ewVW)I8DER0s0W$b>jGA3*mcd=w-EZZFD Nx|xZwYBH;b4FK_s5a|E_ delta 58 zcmX>&f$`u3M&3kTUM>b8Xi<8TX|a*l%94?Nvxntn7Dk)RGIqaM8Iv}PyI3+ZR%{M* N-OR*TGnv)H1_0;=5V8OO diff --git a/app/amazon/__pycache__/del_brand.cpython-39.pyc b/app/amazon/__pycache__/del_brand.cpython-39.pyc index 213f2a36db0734a81cc08397f531e4b5a6e1da8c..6eeeac4b54e9a412c486263c358a29487bad5b8a 100644 GIT binary patch delta 1451 zcmZvbT}&KR6vyYx%zgkn`@zTZwS4aaV{0l%p%s*`vRG{-wQ6*|;tOd=*J=k~il3<+0-trewpro+W)%jk?;9>d!HlTDN)SOF(2E?VkYn78bi%atd8AE- zY5sH5AxB!cr+G>9UJbUyMi7&T3kZh5OqS*zJp`p(*Q>--2laa{ClUTkHQ#6{=3hOg zSw~v((6C z0E^r@Is~I!Y433^WBhe~vV8y^SZnP+%B~IJiLynWJpKrbS~rgu0ucV`6EDIw4xKx7 z`w5sej<|tn6398?6O$Q}t@8Vwk!tLat)Sb*=5$K-q+k-c9oyji>-+#zX4d{t=-e& z3wOIumgC%*-SthOa&nrnRdikF1wGF|oApwU2VC326J=XG-n$I%@IQM;or@Sl`Psfl zJN{eYz{Eeon0FEOEU>KMA~ZowO4tp4uP+F<`Ci|%;^Kt+&jW1nwG$UxM@5(}vn9-H zN1ey)9qg_i9;aP=zi-|(kbqbD?m$n?|IO%QtaJx)*B;L;AzS1F<8cSvL3WFGM5E44 z4Eum*qeozeThYT>)BcsIL4Sfjh#ph%w(^F-(+M0i!(zU3St5ekLEX$wwZ zb?GJyMA{vrW^N2NP?uwLDs5;XD6!gR9an7G`H{_#3_6ybql|S?jx=Vxd`kTQcc{X zhU~khXtktP^H3M@6hL&Sq50YZXkO~wtfQVS(0s(J)ss4{K`4G&FO)`M?cb6_uy?$P zHW1$$XaU+x>gfR*C_q6LZvq3-X}{JYB3o&T$O+PB+Cmy=u%Licgc(19m!z=&0q*`p z7|Pb?lUd5*soPMlu=S6z^kgzOsc(V;;L^9{9T|N3aq}w*g!Egss|t+hJ@zjF#`Hhb z&t;g?xvRqrSNPpp(xixzvUvK%ld%hAr3EO!qP!rBmcWyq4L8G}u)^xZPuVn`W?4Sq zZLYeDDJB7_QlIsHYl3@vhyQ@7DJ>Ls3gJccA_fp|AkOeiU=XJHH-VE-saG_I6!VPG zqk5$E0>B)<6KphRgm9LZgI$o*J#C#bNHJ>>b2BVnHBm zH}=im5gTa0GL z9$y}x?j{Y37~*^g7Y3VC>CxoWF8{0heCv69l}EgbxPS;E))7|_*KuIpOTt_Rai%t5 z*uW$^Ubi{eCH?zj_W_pqC(q7`y*i`M`EY{FSpUbuD$5zh)=*mIGtuMFu74M`gLP9V zF?NIh+`9@N^4I##T9z<|@E`jQpZITu$7a8UF&`m}sbE>nN@$8^DS0%bupoG=VYt4dDsTO9*bE%#ki~dL989Nxo_x@+O~I-noytMFAg2y z>q9S$;;4-}%xLCDcN*b97}vR0SY>M>otw(Cb8H)RJTT3YnR_#nOR&8ap}d3WMHu%- h$xP(fZFIj7$Li&$hl36~h_B+dIPy|n%JSlH=pT`SO?ChP diff --git a/app/amazon/__pycache__/match_action.cpython-39.pyc b/app/amazon/__pycache__/match_action.cpython-39.pyc index 39ebc21a10a2fa5abc6602bc077b64564ac018fd..726e545d4dc38092ac22bd18c71f0b66339409f2 100644 GIT binary patch delta 2372 zcma)7Yiv|S6rQ_F{z1?n0yDi(YRDrJHQ9-n{RjCi2KB6{38%?uzR;-0>ncbDN z&SgQWKoqmds32|4E{V@5K8i*`e8d-OO~4nrMgesTjlcYXe~iI%?zRPfXzb1X_RN`Y z&Y3xPzSGes>GoaJjQV^!fxpn`A6>OOUo+E{^niSnL{XpgsDPj zS|tI?DKtRWuq#I?0k6ATqAch-}; zAY&R5wP2zQ?W?^YmH&UHqFG%iIchlt#Nbcb2}`jS!{CLJ)2tU$+Wr7~&K;MPS!oyS z$`C3m-$^YGINUaInqAC_q)&M8w5)PiD^`U<**ApaS5bw=v>@pwJY=82ja%9f5>DX~ZsEB{ zg&S6rS^#&auzlOu&b@{0uZ}&st*~!TVeruSQxApbT@@bBy)iz#Hyob#>*hzor(PZ$ zcWvHLc;*l{<=VzY3cp6~YMia{nGixAYMiBUiqlz-*44vA)x3KL%_>A1xR3jHHS>}oO`9hfEJ+irZMYbJWiV)S6pyG!WAv)V(P{Z9{J}LA0=TbV75vP2qxmNDJ)K^jliKmplP?7sD zZBN5Ba0&{aQWaWGy;HTkmSOoUQ+T-$2E4w+DDwy}+(%s)fL`GPG=v+_Cv-q_*lU$Q z995LC68ma7gzJ9755pC4R1xOa2w>?I#6emJ3*1KFE}B~@quoj-O}U9nS(zQMeAsRT z!VUn+(*G^Qd26N>!@))>ih=?4xpU+?a@XlQ70YjxPDSf2M$0;|)8-V2-;3v0S(R|W z@@ml3blktKw~##(5`LJe5v3q)6|eqOu#bd)Vso`EWWt(p{z)f%;q!g{6%f?}F;tpz+zi*!VJZ!@~3GQCF-X z7CLg#$e)+@(l!%5e98|nrO~32>Qg1)j4UV|kcF3b7j;^nLfJ$X6y=3-1+`9!8tn1h z+x7iQBrG>AS$usLQ2B!htpGRi_W)BNfbiJsxv`-g<3pQIKf9%{FE_U1?NfUO3Qs&- zkX!isK;R!BYy(&(pK6+?@dv<^pERweh72^<&p3?Jwrtm+bpYW?gk1oUGWlF{{Z;%U zu=DP&_&q%x>thL9vixHteS+{Q0?IM(i*eq=k&}q7jd4^Ux8#Y|V8cN$E8%}$0-FQT z4Tnhss4~!+u|@?;IctErE~W-3t(>YOKezm@(9cFbzxr-UkIH$i4_Ez&(T)P-9Vxyp z@9K^At?P+0{+%3cZJ@7?{N1|5;XHz*uVl;hqgQ{8<~Io60xXND{CON4L%?suzXwR+ zCo2p-{o6a)QwQbP(?d;R{sT_t}m8tt5q;}{bcd){2MdJuj>n2%sb{u!;~2q)yt z%WLRKxqf-XKecWC3r_tCyPd0HnZJ|AmS-x<`0ePv1EC$^ON0}0(~5=kuzYF7;`-U( za61&}Qugzg)8qF9OtW2YBWIyYoi%Q7FRf0KYUx_Jk-jU_D`(O#WNzhg`k~yiY7gy{ Iq16rl07fl%x&QzG delta 2334 zcmaJ>ZERCj7(S=>w(Gjvt{-FF#u(iPW5Y4W7!sf$@-a7vf5Zq(VcB->00$epZ6#f9 zslwbeGE+Gs2G*oqFn)x{$Iu`QqA~t*>O@5xpnvQJN}|7wQQ!BpWm}lG>2uFH?{nVw zc|UqzK2As9q_!5T)k5I&Ov$}HNA+{Iu$!Leuel2yC92HKgOp`5ORurX$V&NT_xz4B z=3+U$v?-Gkam<^#{eih<`~8CytY#qbk(OY`|3k1Wp40ev?3t%sj)jgeO^Zwj-_P?q1M zTMp9=Btmyw2q_WT8!J;vXF5_w|>0Buje^&4>NKwbk%66rLp%jb-bgK9r?{ z6YZ!7I0tJHH6$~kBx2|;?936CTu~)=R7%V5Y!cxOK}beqx|UJq+-ncn1V2yk^HXBN zBr3B4%o~lmE#`=m_vpj()dM>>ecM49KJq;3XM4Zc>E6jLY~H7{xmWecy_sR)vu z@jOV^cIp|rluuZZk%Zx)EGH82LeaO7o?Pgm6ALP|&QEpDpUUW7Dx;QAuQ%ETOFBK2X9`>!#f)PkMtt>%wTN zHnBNzvVrWl^QbQhCY1;wDQ~S? zz>m~D$=5I2Xw=rhdy<#0S|%UD#qGM%Z~$MSiI@8)28Qm&KfD_qN}M`(=fc}7J%KVb zoQ>W&b8I3qIC)_xapdE>FAYrafnTl+CXV$b_=}#3$S@;s?F5Ri@X)ImcPENp==Wvw6_q75WENr5w;>+0;tU4s<)zj zTxshHbo#Ymb7#vwb=(Y?!YuTl0b>d7@+cnj8D{4YmB&h9$I&7d95l1z}?5>l+WyPx$4=V)`k+-S{*8 PoL|{=f;#xN%{Bi5FDzx` diff --git a/app/amazon/__pycache__/price_match.cpython-39.pyc b/app/amazon/__pycache__/price_match.cpython-39.pyc index 6dd70c021beb9073fec6abd7ccea8117e8805470..818c7a4af48ad83c3edce2a6dae9af7c37387ac1 100644 GIT binary patch delta 10116 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ӝs zg!PM}2wrhxz15Fz`fw}wZT&$gMiuH$xCIt4f|EbRc$x~`Yy*ymKE|IKiK*~E3W2Ur zAH2u~32quW62@2)h_ODuOI-I&332sBU@*9M1hxDw24xF{N|Gu^joL(5*BR{%VcbuI z;Eq}pAj#UpKX_&<=q0bHLz|I`94FjzZalm>X6 zd&^bCnkOONltz17!DNOlTdDA}ek+=+mHOS&mTbfK^yO#~bWMJjFja@R#q=9i+%}c* zcno8;OxwyeZL3{iDkqw1s+*&)8B7I%9>#GBL$#uvd?xt+kih{(m({4alk}AczX~r0 z2BW$hfo~{cIMgrTem%hdaBneb<{9@jbcnP>B!pNoF)WirmclT}74HUE17eK`jw3)H zV|cu`5c?efKfV*_R05rSQRYC8mCe6@U+Sg}&;@I%q>h0@;>v^Uyw bJPELAP8vc*;D~K>n#BG6C)Uajq1XQasR=q; delta 9237 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 diff --git a/app/amazon/approve.py b/app/amazon/approve.py index db65147..b18d407 100644 --- a/app/amazon/approve.py +++ b/app/amazon/approve.py @@ -1081,7 +1081,69 @@ class ApproveTask: if shop_name in runing_shop: del runing_shop[shop_name] self.log(f"店铺 {shop_name} 已从执行列表中移除") - + + def action_init(self,driver,country_name,shop_name,risk_listing_filter): + """切换到指定国际 -> 页面 -》 筛选好""" + max_retries = 5 + switch_success = False + switch_success_pg = False + search_success = False + sku_ls = [] + for retry in range(max_retries): + try: + self.log(f"尝试切换到国家 {country_name} (第 {retry + 1}/{max_retries} 次)") + if retry > 2: + # 刷新不行就重新打开店铺 + 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("重试前刷新页面...") + try: + driver.tab.refresh() + time.sleep(3) + except Exception as e: + self.log(f"刷新页面失败: {str(e)}", "WARNING") + + switch_success = driver.SwitchingCountries(country_name) + if switch_success: + self.log(f"成功切换到国家 {country_name}") + + # 切换到库存管理页面 + driver.SwitchPage() + self.log(f"已切换到库存管理页面") + time.sleep(3) + switch_success_pg = True + + sku_ls = driver.search(filter_type=risk_listing_filter) + search_success = True + self.log(f"国家 {country_name} 搜索出 {len(sku_ls)} 商品,开始处理...") + break + else: + self.log(f"切换到国家 {country_name} 失败", "WARNING") + + except Exception as e: + import traceback + self.log(f"切换国家 {country_name} 异常: {str(e)}", "ERROR") + self.log(traceback.format_exc(), "ERROR") + + # 如果还有重试机会,等待后继续 + if retry < max_retries - 1: + time.sleep(2) + + # 如果切换失败,直接返回 + # if not switch_success or not switch_success_pg or not search_success: + # error_message = f"切换到国家({switch_success})/库存页面({switch_success_pg})/搜索筛选到指定选项({search_success}) {country_name} 失败,已重试 {max_retries} 次,跳过该国家" + # self.log(error_message, "ERROR") + # if task_id in runing_task: + # runing_task[task_id]["processed_countries"] += 1 + return switch_success,switch_success_pg,search_success,sku_ls + def process_country(self, driver: AmzoneApprove, country_code: str, task_id: int, shop_name: str, risk_listing_filter: str): """处理单个国家的审批任务 @@ -1108,87 +1170,98 @@ class ApproveTask: max_retries = 3 switch_success = False - 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("重试前刷新页面...") - try: - driver.tab.refresh() - time.sleep(3) - except Exception as e: - self.log(f"刷新页面失败: {str(e)}", "WARNING") - - switch_success = driver.SwitchingCountries(country_name) - if switch_success: - self.log(f"成功切换到国家 {country_name}") - break - else: - self.log(f"切换到国家 {country_name} 失败", "WARNING") - - except Exception as e: - import traceback - self.log(f"切换国家 {country_name} 异常: {str(e)}", "ERROR") - self.log(traceback.format_exc(), "ERROR") - - # 如果还有重试机会,等待后继续 - if retry < max_retries - 1: - time.sleep(2) - + # 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("重试前刷新页面...") + # try: + # driver.tab.refresh() + # time.sleep(3) + # except Exception as e: + # self.log(f"刷新页面失败: {str(e)}", "WARNING") + # + # switch_success = driver.SwitchingCountries(country_name) + # if switch_success: + # self.log(f"成功切换到国家 {country_name}") + # break + # else: + # self.log(f"切换到国家 {country_name} 失败", "WARNING") + # + # except Exception as e: + # import traceback + # self.log(f"切换国家 {country_name} 异常: {str(e)}", "ERROR") + # self.log(traceback.format_exc(), "ERROR") + # + # # 如果还有重试机会,等待后继续 + # if retry < max_retries - 1: + # time.sleep(2) + # + # # 如果切换失败,直接返回 + # if not switch_success: + # error_message = f"切换到国家 {country_name} 失败,已重试 {max_retries} 次,跳过该国家" + # self.log(error_message, "ERROR") + # if task_id in runing_task: + # runing_task[task_id]["processed_countries"] += 1 + # return + # + # # 切换到库存管理页面 + # try: + # driver.SwitchPage() + # self.log(f"已切换到库存管理页面") + # 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 + # return + # + # # 搜索需要审批的商品,最多重试3次 + # sku_ls = [] + # for retry in range(max_retries): + # try: + # self.log(f"尝试搜索需要审批的商品 (第 {retry + 1}/{max_retries} 次)") + # sku_ls = driver.search(filter_type=risk_listing_filter) + # break + # except Exception as e: + # self.log(f"搜索商品异常: {str(e)}", "ERROR") + # if retry < max_retries - 1: + # try: + # driver.tab.refresh() + # time.sleep(3) + # except Exception as refresh_error: + # self.log(f"刷新页面失败: {str(refresh_error)}", "WARNING") + # + max_retries = 5 + switch_success, switch_success_pg, search_success,sku_ls = self.action_init(driver, country_name, shop_name, + risk_listing_filter) # 如果切换失败,直接返回 - if not switch_success: - error_message = f"切换到国家 {country_name} 失败,已重试 {max_retries} 次,跳过该国家" + if not switch_success or not switch_success_pg or not search_success: + error_message = f"切换到国家({switch_success})/库存页面({switch_success_pg})/搜索筛选到指定选项({search_success}) {country_name} 失败,已重试 {max_retries} 次,跳过该国家" self.log(error_message, "ERROR") if task_id in runing_task: runing_task[task_id]["processed_countries"] += 1 return - - # 切换到库存管理页面 - try: - driver.SwitchPage() - self.log(f"已切换到库存管理页面") - 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 - return - - # 搜索需要审批的商品,最多重试3次 - sku_ls = [] - for retry in range(max_retries): - try: - self.log(f"尝试搜索需要审批的商品 (第 {retry + 1}/{max_retries} 次)") - sku_ls = driver.search(filter_type=risk_listing_filter) - break - except Exception as e: - self.log(f"搜索商品异常: {str(e)}", "ERROR") - if retry < max_retries - 1: - try: - driver.tab.refresh() - time.sleep(3) - except Exception as refresh_error: - self.log(f"刷新页面失败: {str(refresh_error)}", "WARNING") - - # 如果没有需要审批的商品,直接返回 + + # # 如果没有需要审批的商品,直接返回 if len(sku_ls) == 0: self.log(f"国家 {country_name} 没有需要审批的商品") if task_id in runing_task: runing_task[task_id]["processed_countries"] += 1 return - + self.log(f"国家 {country_name} 有 {len(sku_ls)} 个需要审批的商品,开始处理...") # 处理所有需要审批的商品(通过yield获取结果) diff --git a/app/amazon/asin_status.py b/app/amazon/asin_status.py index 9160f21..8a5ad46 100644 --- a/app/amazon/asin_status.py +++ b/app/amazon/asin_status.py @@ -537,8 +537,6 @@ class StatusTask: self.log(f"切换页面失败重试退出", "ERROR") return - - # 处理所有需要审批的商品(通过yield获取结果) # 指定 asin diff --git a/app/amazon/del_brand.py b/app/amazon/del_brand.py index af7c8ee..884b093 100644 --- a/app/amazon/del_brand.py +++ b/app/amazon/del_brand.py @@ -8,11 +8,12 @@ import json import os from typing import Literal -from DrissionPage import Chromium +from DrissionPage import Chromium,ChromiumOptions from DrissionPage.common import By from DrissionPage._pages.chromium_tab import ChromiumTab + def kill_process(version: Literal["v5", "v6"]): """杀紫鸟客户端进程(独立函数版本)""" driver = ZiniaoDriver({}) @@ -205,7 +206,11 @@ class ZiniaoDriver: Returns: Chromium: DrissionPage浏览器实例 """ - browser = Chromium(port) + co = ChromiumOptions() + # 设置不加载图片、静音 + co.set_local_port(port) + co.no_imgs(True).mute(True) + browser = Chromium(co) return browser def start_client(self): diff --git a/app/amazon/match_action.py b/app/amazon/match_action.py index 7745f20..07f2516 100644 --- a/app/amazon/match_action.py +++ b/app/amazon/match_action.py @@ -468,6 +468,70 @@ class MatchTak: del runing_shop[shop_name] self.log(f"店铺 {shop_name} 已从执行列表中移除") + def action_init(self,driver,country_name,shop_name,risk_listing_filter): + """切换到指定国际 -> 页面 -》 筛选好""" + max_retries = 5 + switch_success = False + switch_success_pg = False + search_success = False + sku_ls = [] + for retry in range(max_retries): + try: + self.log(f"尝试切换到国家 {country_name} (第 {retry + 1}/{max_retries} 次)") + if retry > 2: + # 刷新不行就重新打开店铺 + 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("重试前刷新页面...") + try: + driver.tab.refresh() + time.sleep(3) + except Exception as e: + self.log(f"刷新页面失败: {str(e)}", "WARNING") + + switch_success = driver.SwitchingCountries(country_name) + if switch_success: + self.log(f"成功切换到国家 {country_name}") + + # 切换到库存管理页面 + driver.SwitchPage() + self.log(f"已切换到库存管理页面") + time.sleep(3) + switch_success_pg = True + + sku_ls = driver.search(filter_type=risk_listing_filter) + search_success = True + self.log(f"国家 {country_name} 搜索出 {len(sku_ls)} 商品,开始处理...") + break + else: + self.log(f"切换到国家 {country_name} 失败", "WARNING") + + except Exception as e: + import traceback + self.log(f"切换国家 {country_name} 异常: {str(e)}", "ERROR") + self.log(traceback.format_exc(), "ERROR") + + # 如果还有重试机会,等待后继续 + if retry < max_retries - 1: + time.sleep(2) + + # 如果切换失败,直接返回 + # if not switch_success or not switch_success_pg or not search_success: + # error_message = f"切换到国家({switch_success})/库存页面({switch_success_pg})/搜索筛选到指定选项({search_success}) {country_name} 失败,已重试 {max_retries} 次,跳过该国家" + # self.log(error_message, "ERROR") + # if task_id in runing_task: + # runing_task[task_id]["processed_countries"] += 1 + return switch_success,switch_success_pg,search_success,sku_ls + + + def process_country(self, driver: AmzoneMatchAction, country_code: str, task_id: int, shop_name: str, risk_listing_filter: str,limit:str=None): """处理单个国家的审批任务 @@ -491,90 +555,108 @@ class MatchTak: runing_task[task_id]["current_country"] = country_name # 切换国家,最多重试3次 - max_retries = 3 - switch_success = False - - 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("重试前刷新页面...") - try: - driver.tab.refresh() - time.sleep(3) - except Exception as e: - self.log(f"刷新页面失败: {str(e)}", "WARNING") - - switch_success = driver.SwitchingCountries(country_name) - if switch_success: - self.log(f"成功切换到国家 {country_name}") - break - else: - self.log(f"切换到国家 {country_name} 失败", "WARNING") - - except Exception as e: - import traceback - self.log(f"切换国家 {country_name} 异常: {str(e)}", "ERROR") - self.log(traceback.format_exc(), "ERROR") - - # 如果还有重试机会,等待后继续 - if retry < max_retries - 1: - time.sleep(2) - + # max_retries = 3 + # switch_success = False + # + # 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("重试前刷新页面...") + # try: + # driver.tab.refresh() + # time.sleep(3) + # except Exception as e: + # self.log(f"刷新页面失败: {str(e)}", "WARNING") + # + # switch_success = driver.SwitchingCountries(country_name) + # if switch_success: + # self.log(f"成功切换到国家 {country_name}") + # break + # else: + # self.log(f"切换到国家 {country_name} 失败", "WARNING") + # + # except Exception as e: + # import traceback + # self.log(f"切换国家 {country_name} 异常: {str(e)}", "ERROR") + # self.log(traceback.format_exc(), "ERROR") + # + # # 如果还有重试机会,等待后继续 + # if retry < max_retries - 1: + # time.sleep(2) + # + # # 如果切换失败,直接返回 + # if not switch_success: + # error_message = f"切换到国家 {country_name} 失败,已重试 {max_retries} 次,跳过该国家" + # self.log(error_message, "ERROR") + # if task_id in runing_task: + # runing_task[task_id]["processed_countries"] += 1 + # return + # + # # 切换到库存管理页面 + # try: + # driver.SwitchPage() + # self.log(f"已切换到库存管理页面") + # 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 + # return + # + # # 搜索需要审批的商品,最多重试3次 + # sku_ls = [] + # for retry in range(max_retries): + # try: + # self.log(f"尝试搜索匹配操作商品 (第 {retry + 1}/{max_retries} 次)") + # sku_ls = driver.search(filter_type=risk_listing_filter) + # break + # except Exception as e: + # self.log(f"搜索商品异常: {str(e)}", "ERROR") + # if retry < max_retries - 1: + # try: + # driver.tab.refresh() + # time.sleep(3) + # except Exception as refresh_error: + # self.log(f"刷新页面失败: {str(refresh_error)}", "WARNING") + # + # # 如果没有需要审批的商品,直接返回 + # if len(sku_ls) == 0: + # self.log(f"国家 {country_name} 没有搜索出的商品") + # if task_id in runing_task: + # runing_task[task_id]["processed_countries"] += 1 + # return + # + max_retries = 5 + switch_success, switch_success_pg, search_success, sku_ls = self.action_init(driver, country_name, shop_name, + risk_listing_filter) # 如果切换失败,直接返回 - if not switch_success: - error_message = f"切换到国家 {country_name} 失败,已重试 {max_retries} 次,跳过该国家" + if not switch_success or not switch_success_pg or not search_success: + error_message = f"切换到国家({switch_success})/库存页面({switch_success_pg})/搜索筛选到指定选项({search_success}) {country_name} 失败,已重试 {max_retries} 次,跳过该国家" self.log(error_message, "ERROR") if task_id in runing_task: runing_task[task_id]["processed_countries"] += 1 return - - # 切换到库存管理页面 - try: - driver.SwitchPage() - self.log(f"已切换到库存管理页面") - 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 - return - - # 搜索需要审批的商品,最多重试3次 - sku_ls = [] - for retry in range(max_retries): - try: - self.log(f"尝试搜索匹配操作商品 (第 {retry + 1}/{max_retries} 次)") - sku_ls = driver.search(filter_type=risk_listing_filter) - break - except Exception as e: - self.log(f"搜索商品异常: {str(e)}", "ERROR") - if retry < max_retries - 1: - try: - driver.tab.refresh() - time.sleep(3) - except Exception as refresh_error: - self.log(f"刷新页面失败: {str(refresh_error)}", "WARNING") - - # 如果没有需要审批的商品,直接返回 + + # # 如果没有需要审批的商品,直接返回 if len(sku_ls) == 0: - self.log(f"国家 {country_name} 没有搜索出的商品") + self.log(f"国家 {country_name} 没有需要审批的商品") if task_id in runing_task: runing_task[task_id]["processed_countries"] += 1 return - + self.log(f"国家 {country_name} 搜索出 {len(sku_ls)} 商品,开始处理...") # 处理所有需要审批的商品(通过yield获取结果) diff --git a/app/amazon/price_match.py b/app/amazon/price_match.py index 4ce5d2b..fc57491 100644 --- a/app/amazon/price_match.py +++ b/app/amazon/price_match.py @@ -16,28 +16,32 @@ from amazon.tool import show_notification,get_shop_info,remove_special_character from config import runing_task, runing_shop,base_dir,DELETE_BRAND_API_BASE - class RepricingLogic: @staticmethod def situation_1_general_decrease(my_price, rank1_price, is_first_time=False): """ 情况1:常规递减跟价 (最左侧独立长图块) - 当不是自己购物车时,按总价当作第一名递减。 """ if is_first_time: return rank1_price - 0.3 - diff = my_price - rank1_price + # 这里的 diff 算出的是一个正数,代表“我比第一名低多少” + diff = rank1_price - my_price - # 精简了原图中长串的阶梯逻辑 + # 如果当前价格高于或等于第一名,默认按照第一名减0.3处理 + if diff <= 0: + return rank1_price - 0.3 + + # 逻辑更清晰的写法:第一名价格 - (差价 + 对应阶梯值) + # 例如差价0.7,则相当于 第一名价格 - (0.7 + 0.5) = 第一名价格 - 1.2 if diff <= 0.3: - return rank1_price - 0.5 + return rank1_price - (diff + 0.5) elif diff <= 0.8: - return rank1_price - 0.7 + return rank1_price - (diff + 0.7) elif diff <= 10.5: - # 0.8 到 10.5 之间,原图逻辑全部是“减1” - return rank1_price - 1.0 + # 0.8 到 10.5 之间全部是在减1 + return rank1_price - (diff + 1.0) else: return None # 相差10.5以上,跳过 @@ -45,55 +49,56 @@ class RepricingLogic: def situation_2_own_buybox(my_price, rank2_price): """ 情况2:已经是自己购物车 - 逻辑:判断与第二名的差价,如果拉开足够差距,则稍微降一点点(减0.5)保持优势,防止卖太便宜。 """ diff = rank2_price - my_price - # 1. 绝对差价2欧以上 if diff >= 2.0: return rank2_price - 0.3 - # 2. 产品售价区间判定 (是否要提高价格) if (20 <= my_price < 30 and diff >= 4) or \ (30 <= my_price < 60 and diff >= 8) or \ (60 <= my_price <= 150 and diff >= 15): - return rank2_price - 0.3 # 满足区间大差价,提价至第二名之下 + return rank2_price - 0.3 - return my_price # 不满足条件则保持原价 + return my_price @staticmethod def situation_3_not_own_buybox(my_price, rank1_price): """ - 情况3:不是自己购物车 (常规情况) + 情况3:不是自己购物车 (除Amazon US外) """ - diff = my_price - rank1_price + diff = rank1_price - my_price - if diff <= 0.3: - return rank1_price - 0.5 - elif 0.5 <= diff <= 0.8: - return rank1_price - 0.7 - elif 0.8 < diff <= 2.5: - return rank1_price - 1.0 - elif diff > 2.5: # 2.5-3.5以上跳过 - return None + if diff <= 0: + return rank1_price - 0.3 + + # 逻辑更清晰的写法:第一名价格 - (差价 + 对应阶梯值) + if diff <= 0.3: + return rank1_price - (diff + 0.5) + elif diff <= 0.8: + return rank1_price - (diff + 0.7) + elif diff <= 2.5: + return rank1_price - (diff + 1.0) + else: + return None # 2.5-3.5以上跳过 - return rank1_price - 0.3 # 默认按第一名减0.3 - @staticmethod def situation_4_encounter_amz_us_1st(my_price, amz_us_price): """ - 情况4:第一名是 Amazon US 卖家 + 情况4:遇到第一名是 Amazon US 卖家 """ - diff = my_price - amz_us_price + diff = amz_us_price - my_price - if diff <= 3: - return amz_us_price - 2 - elif 4 <= diff <= 6: + if diff <= 0: return amz_us_price - 3 - elif 8 <= diff <= 12: - return None # 跳过不跟价 - return amz_us_price - 3 # 默认直接按照 Amazon US 减去3 + # 逻辑更清晰的写法:第一名价格 - (差价 + 对应阶梯值) + if diff <= 3: + return amz_us_price - (diff + 2) + elif diff <= 6: + return amz_us_price - (diff + 3) + else: + return None # 超过6直接跳过 @staticmethod def situation_5_im_1st_amz_us_2nd(my_price, amz_us_price): @@ -103,7 +108,7 @@ class RepricingLogic: diff = amz_us_price - my_price if diff <= 7: - return None # 相差5以内跳过 (保持原价) + return None # 相差7以内跳过 (保持原价) else: return amz_us_price - 5 @@ -133,16 +138,6 @@ def calculate_target_price( price_col = recommended_price + recommended_shipping print(f"【紫鸟后台价格】{is_my_buybox} 推荐价格: {recommended_price},推荐运费: {recommended_shipping},总和: {price_col}") return RepricingLogic.situation_1_general_decrease(my_price,price_col,is_my_buybox) - # backend_base_price = recommended_price - # backend_shipping = recommended_shipping - # total_price = backend_base_price + backend_shipping - # - # if is_my_buybox: - # # 🌟【逻辑更新】:如果是自己的购物车,直接跳过,不做任何价格调整 - # return None - # else: - # # 不是自己的购物车:直接将紫鸟的总和作为"第一名"价格,强制抛入阶梯跟价逻辑 - # return calculate_standard_competitor_pricing(my_price, total_price) # # 第一名是自己, 第二名 Amazon US 卖家 的情况 shop_name_1 = front_end_data.get("top_sellers")[0].get("shop_name") @@ -152,13 +147,11 @@ def calculate_target_price( return RepricingLogic.situation_5_im_1st_amz_us_2nd(my_price,price_2) # --- Step 3: 常规核心分流逻辑 (如果没有紫鸟后台数据) --- - # 分支 A:第一名是 Amazon US 卖家 (特殊强敌优先) price_1 = float(front_end_data.get("top_sellers")[0].get("price")) # 实际第一名价格 if "Amazon" in shop_name_1: return RepricingLogic.situation_4_encounter_amz_us_1st(my_price,price_1) - # 分支 B:不是 Amazon US,且目前是自己的购物车 elif is_my_buybox: price_2 = float(front_end_data.get("top_sellers")[1].get("price")) # 实际第二名价格 @@ -650,6 +643,13 @@ class AmzonePriceMatch(AmamzonBase): print(f"【{self.mark_name}】获取到 {len(sku_ls)}") if len(sku_ls) == 0: + if mode == "asin": + print(f"asin 模式查询不到,{asin}") + yield (asin, { + "statu": "查询不到" + }) + break + retry_num += 1 print(f"没有获取到 ASIN 商品,重试{retry_num}/{max_retry_num}") self.tab.refresh() @@ -680,19 +680,16 @@ class AmzonePriceMatch(AmamzonBase): bottom_price = f"{sum(split_currency_values(bottom_price_text))}" except Exception as e: print("最低价提取失败",e) + current_price_ele = sku_ele.eles('xpath:.//b[text()="价格"]/../..//kat-input',timeout=5) 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): + # if mode == "status" and miniprice_info.get(asin): + if miniprice_info.get(asin): backend_price = float(miniprice_info.get(asin)) if float(current_price) <= backend_price: - # yield (asin, { - # "statu": "删除(当前价格低于最低价)", - # "deleteSkipAsin": True, - # "removeAsin": asin, - # }) yield (asin, { "statu": "跳过(当前价格低于或等于最低价)", "currentPrice": current_price, @@ -764,6 +761,9 @@ class AmzonePriceMatch(AmamzonBase): print(asin,"-->>",f"【逻辑计算后的价格】",adjust_prices) if adjust_prices is not None and adjust_prices != float(current_price): #修改价格 + print("准备填入的原始值:",adjust_prices) + adjust_prices = round(adjust_prices, 2) + print("保留两位小数后的::",adjust_prices) self.tab.actions.click(current_price_ele[0]) time.sleep(1) current_price_ele[0].input(f"{adjust_prices}", clear=True) @@ -784,12 +784,12 @@ class AmzonePriceMatch(AmamzonBase): "statu" : "改价成功", "currentPrice" : current_price, "recommendedPrice" : f"{recommendedPrice}", - "minimumPrice" : bottom_price, #最低价格 + "minimumPrice" : f"{miniprice_info.get(asin)}" if miniprice_info.get(asin) else "",#bottom_price, #最低价格 "firstPlace": price_1, "secondPlace": price_2, "cartShopName" : cartShopName, "shippingFee" : f"{shipping_fee}", - "priceChangeStatus" : f"{adjust_prices}", + "priceChangePrice" : f"{adjust_prices}", "priceChangeStatus" : "改价成功", }) else: @@ -803,12 +803,14 @@ class AmzonePriceMatch(AmamzonBase): "statu": "跳过,无需改价", "currentPrice": current_price, "recommendedPrice": f"{recommendedPrice}", - "minimumPrice": bottom_price, # 最低价格 + # "minimumPrice": bottom_price, # 最低价格 + "minimumPrice": f"{miniprice_info.get(asin)}" if miniprice_info.get(asin) else "", "firstPlace": price_1, "secondPlace": price_2, "cartShopName": cartShopName, "shippingFee": f"{shipping_fee}", "priceChangeStatus": "跳过,无需改价", + "priceChangePrice": f"{adjust_prices}".replace("None",""), }) already_asin.add(asin) @@ -1029,6 +1031,68 @@ class PriceTask: return driver return driver + def action_init(self,driver,country_name,shop_name,risk_listing_filter): + """切换到指定国际 -> 页面 -》 筛选好""" + max_retries = 5 + switch_success = False + switch_success_pg = False + search_success = False + sku_ls = [] + for retry in range(max_retries): + try: + self.log(f"尝试切换到国家 {country_name} (第 {retry + 1}/{max_retries} 次)") + if retry > 2: + # 刷新不行就重新打开店铺 + 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("重试前刷新页面...") + try: + driver.tab.refresh() + time.sleep(3) + except Exception as e: + self.log(f"刷新页面失败: {str(e)}", "WARNING") + + switch_success = driver.SwitchingCountries(country_name) + if switch_success: + self.log(f"成功切换到国家 {country_name}") + + # 切换到库存管理页面 + driver.SwitchPage() + self.log(f"已切换到库存管理页面") + time.sleep(3) + switch_success_pg = True + + sku_ls = driver.search(filter_type=risk_listing_filter) + search_success = True + self.log(f"国家 {country_name} 搜索出 {len(sku_ls)} 商品,开始处理...") + break + else: + self.log(f"切换到国家 {country_name} 失败", "WARNING") + + except Exception as e: + import traceback + self.log(f"切换国家 {country_name} 异常: {str(e)}", "ERROR") + self.log(traceback.format_exc(), "ERROR") + + # 如果还有重试机会,等待后继续 + if retry < max_retries - 1: + time.sleep(2) + + # 如果切换失败,直接返回 + # if not switch_success or not switch_success_pg or not search_success: + # error_message = f"切换到国家({switch_success})/库存页面({switch_success_pg})/搜索筛选到指定选项({search_success}) {country_name} 失败,已重试 {max_retries} 次,跳过该国家" + # self.log(error_message, "ERROR") + # if task_id in runing_task: + # runing_task[task_id]["processed_countries"] += 1 + return switch_success,switch_success_pg,search_success + 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): @@ -1149,94 +1213,42 @@ class PriceTask: runing_task[task_id]["current_country"] = country_name # 切换国家,最多重试3次 - max_retries = 3 - switch_success = False - - 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("重试前刷新页面...") - try: - driver.tab.refresh() - time.sleep(3) - except Exception as e: - self.log(f"刷新页面失败: {str(e)}", "WARNING") - - switch_success = driver.SwitchingCountries(country_name) - if switch_success: - self.log(f"成功切换到国家 {country_name}") - break - else: - self.log(f"切换到国家 {country_name} 失败", "WARNING") - - except Exception as e: - import traceback - self.log(f"切换国家 {country_name} 异常: {str(e)}", "ERROR") - self.log(traceback.format_exc(), "ERROR") - - # 如果还有重试机会,等待后继续 - if retry < max_retries - 1: - time.sleep(2) + max_retries = 5 + switch_success,switch_success_pg,search_success = self.action_init( driver, country_name, shop_name, risk_listing_filter) # 如果切换失败,直接返回 - if not switch_success: - error_message = f"切换到国家 {country_name} 失败,已重试 {max_retries} 次,跳过该国家" + if not switch_success or not switch_success_pg or not search_success: + error_message = f"切换到国家({switch_success})/库存页面({switch_success_pg})/搜索筛选到指定选项({search_success}) {country_name} 失败,已重试 {max_retries} 次,跳过该国家" self.log(error_message, "ERROR") if task_id in runing_task: runing_task[task_id]["processed_countries"] += 1 return - # 切换到库存管理页面 - for retry in range(max_retries): - try: - driver.SwitchPage() - self.log(f"已切换到库存管理页面") - except Exception as e: - import traceback - self.log(f"切换页面失败: {str(e)}", "ERROR") - self.log(traceback.format_exc(), "ERROR") - - if retry >= max_retries-1: - if task_id in runing_task: - runing_task[task_id]["processed_countries"] += 1 - self.log(f"切换页面失败重试退出", "ERROR") - return - # 搜索需要审批的商品,最多重试3次 - sku_ls = [] - for retry in range(max_retries): - try: - self.log(f"尝试搜索匹配操作商品 (第 {retry + 1}/{max_retries} 次)") - sku_ls = driver.search(filter_type=risk_listing_filter) - break - except Exception as e: - self.log(f"搜索商品异常: {str(e)}", "ERROR") - if retry < max_retries - 1: - try: - driver.tab.refresh() - time.sleep(3) - except Exception as refresh_error: - self.log(f"刷新页面失败: {str(refresh_error)}", "WARNING") + # sku_ls = [] + # for retry in range(max_retries): + # try: + # self.log(f"尝试搜索匹配操作商品 (第 {retry + 1}/{max_retries} 次)") + # sku_ls = driver.search(filter_type=risk_listing_filter) + # break + # except Exception as e: + # self.log(f"搜索商品异常: {str(e)}", "ERROR") + # if retry < max_retries - 1: + # try: + # driver.tab.refresh() + # driver.tab.wait.doc_loaded() + # time.sleep(3) + # except Exception as refresh_error: + # self.log(f"刷新页面失败: {str(refresh_error)}", "WARNING") + # + # # 如果没有需要审批的商品,直接返回 + # if len(sku_ls) == 0: + # self.log(f"国家 {country_name} 没有搜索出的商品") + # if task_id in runing_task: + # runing_task[task_id]["processed_countries"] += 1 + # return - # 如果没有需要审批的商品,直接返回 - if len(sku_ls) == 0: - self.log(f"国家 {country_name} 没有搜索出的商品") - if task_id in runing_task: - runing_task[task_id]["processed_countries"] += 1 - return - - self.log(f"国家 {country_name} 搜索出 {len(sku_ls)} 商品,开始处理...") + self.log(f"国家 {country_name} ,开始处理...") # 处理所有需要审批的商品(通过yield获取结果) @@ -1350,13 +1362,14 @@ class PriceTask: "firstPlace": status.get("firstPlace") if status.get("firstPlace") else "", "secondPlace": status.get("secondPlace") if status.get("secondPlace") else "", "cartShopName": status.get("cartShopName") if status.get("cartShopName") else "", - "priceChangeStatus": "UPDATED", + # "priceChangeStatus": "UPDATED", "deleteSkipAsin": status.get("deleteSkipAsin",False), "removeAsin": status.get("removeAsin") if status.get("removeAsin") else "", "modifyCount": "1", "shippingFee" : status.get("shippingFee") if status.get("shippingFee") else "", "status": status.get("statu"), "priceChangeStatus" : status.get("priceChangeStatus") if status.get("priceChangeStatus") else "", + "priceChangePrice" : status.get("priceChangePrice") if status.get("priceChangePrice") else "", } ] }, @@ -1405,20 +1418,20 @@ class PriceTask: if __name__ == '__main__': # 使用示例 # user_info = { - # "company": "rongchuang123", + # "company": "尾号5578的公司115", # "username": "自动化_Robot", # "password": "#20zsg25" # } - # shop_name = "郭亚芳" - # country = "德国" + # shop_name = "刘建煌" + # country = "法国" # kill_process('v6') # driver = AmzonePriceMatch(user_info) # browser = driver.open_shop(shop_name) # sw_suc = driver.SwitchingCountries(country) # driver.SwitchPage() # risk_listing_filter = "Active" - # _shopMallName = "yafang123" - # ap_asin ="B0BTHBJDKG" + # _shopMallName = "Jianhuang 888" + # ap_asin ="B0F25Y52PH" # skip_asin = [] # for _ in range(3): # try: @@ -1436,14 +1449,19 @@ if __name__ == '__main__': # print(f"ASIN {asin} 的处理结果: {status}") # print("已完成操作") - front_end_data = {'top_sellers': [{'rank': 1, 'price': '50.24', 'stock': '26', 'shop_name': 'yafang123'}, {'rank': 2, 'price': '44.35', 'stock': '100', 'shop_name': 'QinPimy'}], 'cart_seller': 'Amazon US', 'timestamp': '2026-04-27 16:17:42'} + front_end_data = {'top_sellers': [{'rank': 1, 'price': '52.51', 'stock': '100', 'shop_name': 'Amazon US'}, {'rank': 2, 'price': '62.58', 'stock': '30', 'shop_name': 'Amazon US'}], 'cart_seller': 'Jianhuang 888', 'timestamp': '2026-04-30 09:52:49'} - current_Price = 44.35 - current_shop_name = "yafang123" - recommended_price = 44.35 + current_Price = 52.51 + current_shop_name = "Jianhuang 888" + recommended_price = 52.51 recommended_shipping =0.0 res = calculate_target_price( front_end_data, current_Price, current_shop_name, recommended_price, recommended_shipping ) - print(res) \ No newline at end of file + print(res) + + + # task_data = {'type': 'price-track-run', 'ts': 1777388166302, 'data': {'task_id': 4892, 'result_id': 6685, 'shop_name': '魏振峰', 'shop_id': '27543917795757', 'platform': '亚马逊', 'company_name': 'rongchuang123', 'shop_mall_name': 'WEIZHENFENG168', 'matched': True, 'match_status': 'MATCHED', 'match_message': None, 'success': False, 'error': None, 'output_filename': None, 'download_url': None, 'task_status': 'RUNNING', 'loop_run_id': 168, 'round_index': 1, 'country_codes': ['DE', 'UK', 'FR', 'IT', 'ES'], 'mode': 'asin', 'skip_asins': {}, 'skip_asins_by_country': {}, 'skip_asin_details_by_country': {}, 'minimum_price_by_country_and_asin': {'DE': {'B0CFY5M3XJ': '38', 'B0DSJGJBWV': '9'}, 'UK': {'B0G2LPSGP5': '30', 'B0G1LY2K8L': '10'}}, 'skip_asin_delete_policy': {'enabled': False, 'mode': 'NONE', 'deleteWhenPriceBelowMinimum': False, 'compareField': 'price', 'thresholdField': 'minimumPrice', 'target': 'skip_asin', 'source': 'frontend-price-track'}, 'delete_skip_asin_when_price_below_minimum': False, 'deleteSkipAsinWhenPriceBelowMinimum': False, 'asin_rows_by_country': {'DE': [{'shopMallName': '', 'asin': 'B0CFY5M3XJ', 'price': '', 'recommendedPrice': '', 'minimumPrice': '38', 'firstPlace': '', 'secondPlace': '', 'cartShopName': '', 'priceChangeStatus': '', 'modifyCount': '', 'status': ''}, {'shopMallName': '', 'asin': 'B0DSJGJBWV', 'price': '', 'recommendedPrice': '', 'minimumPrice': '9', 'firstPlace': '', 'secondPlace': '', 'cartShopName': '', 'priceChangeStatus': '', 'modifyCount': '', 'status': ''}], 'UK': [{'shopMallName': '', 'asin': 'B0G2LPSGP5', 'price': '', 'recommendedPrice': '', 'minimumPrice': '30', 'firstPlace': '', 'secondPlace': '', 'cartShopName': '', 'priceChangeStatus': '', 'modifyCount': '', 'status': ''}, {'shopMallName': '', 'asin': 'B0G1LY2K8L', 'price': '', 'recommendedPrice': '', 'minimumPrice': '10', 'firstPlace': '', 'secondPlace': '', 'cartShopName': '', 'priceChangeStatus': '', 'modifyCount': '', 'status': ''}]}, 'taskId': 4892, 'resultId': 6685, 'shopName': '魏振峰', 'shopId': '27543917795757', 'platformName': '亚马逊', 'companyName': 'rongchuang123', 'shopMallName': 'WEIZHENFENG168', 'matchStatus': 'MATCHED', 'matchMessage': None, 'outputFilename': None, 'downloadUrl': None, 'taskStatus': 'RUNNING', 'loopRunId': 168, 'roundIndex': 1}} + # price_cls = PriceTask(user_info) + # price_cls.process_task(task_data) \ No newline at end of file diff --git a/app/main.py b/app/main.py index cd6403f..148cb5f 100644 --- a/app/main.py +++ b/app/main.py @@ -438,7 +438,7 @@ def main(): 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.get_detail_del,api.save_file_from_url_new) webview.start( - debug=False, + debug=True, storage_path=cache_path, private_mode=False ) From 172917ac419987b359fda6049c68c0a6ca54f6a6 Mon Sep 17 00:00:00 2001 From: super <2903208875@qq.com> Date: Thu, 30 Apr 2026 11:36:58 +0800 Subject: [PATCH 2/3] =?UTF-8?q?=E4=BC=98=E5=8C=96=E5=90=8E=E5=8F=B0?= =?UTF-8?q?=E4=B8=9A=E5=8A=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend-java/pom.xml | 11 + .../client/AppearancePatentCozeClient.java | 2 +- .../AppearancePatentController.java | 5 +- .../service/AppearancePatentTaskService.java | 12 +- .../brand/service/BrandTaskService.java | 4 +- .../convert/service/ConvertRunService.java | 4 +- .../dedupe/service/DedupeRunService.java | 28 +- .../file/service/LocalFileStorageService.java | 4 +- .../service/PatrolDeleteTaskService.java | 2 - .../dto/PriceTrackSubmitResultRequest.java | 5 + .../PriceTrackExcelAssemblyService.java | 25 +- .../service/PriceTrackTaskService.java | 21 +- .../service/ProductRiskTaskService.java | 19 +- .../service/QueryAsinTaskService.java | 2 - .../shopkey/mapper/ShopManageGroupMapper.java | 61 + .../shopkey/service/QueryAsinService.java | 58 +- .../service/ShopManageGroupService.java | 90 +- .../shopkey/service/ShopManageService.java | 44 +- .../shopkey/service/SkipPriceAsinService.java | 18 +- .../service/ShopMatchTaskService.java | 19 +- .../split/service/SplitRunService.java | 4 +- .../task/service/TaskFileJobService.java | 5 - .../task/service/TaskResultItemService.java | 2 - .../service/TaskResultPayloadService.java | 1 - .../ziniao/service/ZiniaoAuthService.java | 2 +- .../service/ZiniaoShopIndexService.java | 2 +- .../src/main/resources/application.yml | 6 +- .../db/V36__shopkey_list_query_indexes.sql | 61 + .../db/V37__permission_menu_query_indexes.sql | 31 + .../db/V38__task_result_hot_path_indexes.sql | 31 + backend/__pycache__/config.cpython-312.pyc | Bin 1517 -> 1512 bytes .../__pycache__/admin_api.cpython-312.pyc | Bin 108369 -> 106583 bytes backend/blueprints/admin_api.py | 139 +- backend/config.py | 4 +- .../utils/__pycache__/auth.cpython-312.pyc | Bin 5017 -> 5408 bytes backend/utils/__pycache__/db.cpython-312.pyc | Bin 8671 -> 8861 bytes backend/utils/auth.py | 30 +- backend/utils/db.py | 18 +- backend/web_source/_tmp_prefix.js | 2000 ----------------- backend/web_source/admin.html | 479 +++- .../components/BrandAppearancePatentTab.vue | 22 +- .../brand/components/BrandDeleteBrandTab.vue | 22 +- .../brand/components/BrandPatrolDeleteTab.vue | 33 +- .../brand/components/BrandPriceTrackTab.vue | 56 +- .../brand/components/BrandProductRiskTab.vue | 41 +- .../brand/components/BrandQueryAsinTab.vue | 33 +- .../brand/components/BrandShopMatchTab.vue | 33 +- .../src/shared/utils/categorized-timers.ts | 113 + 48 files changed, 1211 insertions(+), 2391 deletions(-) create mode 100644 backend-java/src/main/resources/db/V36__shopkey_list_query_indexes.sql create mode 100644 backend-java/src/main/resources/db/V37__permission_menu_query_indexes.sql create mode 100644 backend-java/src/main/resources/db/V38__task_result_hot_path_indexes.sql delete mode 100644 backend/web_source/_tmp_prefix.js create mode 100644 frontend-vue/src/shared/utils/categorized-timers.ts diff --git a/backend-java/pom.xml b/backend-java/pom.xml index 2fab57a..0c4fa85 100644 --- a/backend-java/pom.xml +++ b/backend-java/pom.xml @@ -25,8 +25,19 @@ 5.8.36 2.3.5 8.5.17 + 3.28.0-GA + + + + org.javassist + javassist + ${javassist.version} + + + + org.springframework.boot diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/appearancepatent/client/AppearancePatentCozeClient.java b/backend-java/src/main/java/com/nanri/aiimage/modules/appearancepatent/client/AppearancePatentCozeClient.java index 12cd99d..bbc92db 100644 --- a/backend-java/src/main/java/com/nanri/aiimage/modules/appearancepatent/client/AppearancePatentCozeClient.java +++ b/backend-java/src/main/java/com/nanri/aiimage/modules/appearancepatent/client/AppearancePatentCozeClient.java @@ -649,7 +649,7 @@ public class AppearancePatentCozeClient { } private String normalize(String value) { - return value == null ? "" : value.replace("\ufeff", "").replace("\u3000", " ").trim(); + return value == null ? "" : value.replace(String.valueOf((char) 0xFEFF), "").replace((char) 0x3000, ' ').trim(); } private String rowKey(AppearancePatentResultRowDto row) { diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/appearancepatent/controller/AppearancePatentController.java b/backend-java/src/main/java/com/nanri/aiimage/modules/appearancepatent/controller/AppearancePatentController.java index 8a7157a..0eddd72 100644 --- a/backend-java/src/main/java/com/nanri/aiimage/modules/appearancepatent/controller/AppearancePatentController.java +++ b/backend-java/src/main/java/com/nanri/aiimage/modules/appearancepatent/controller/AppearancePatentController.java @@ -83,7 +83,10 @@ public class AppearancePatentController { public ApiResponse result( @Parameter(description = "外观专利检测任务 ID。任务必须处于 RUNNING 状态。", required = true, example = "3938") @PathVariable Long taskId, - @Valid @RequestBody AppearancePatentSubmitResultRequest request) { + @Valid @RequestBody AppearancePatentSubmitResultRequest request, + jakarta.servlet.http.HttpServletResponse response) { + response.setCharacterEncoding(StandardCharsets.UTF_8.name()); + response.setContentType("application/json;charset=UTF-8"); service.submitResult(taskId, request); return ApiResponse.success(null); } diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/appearancepatent/service/AppearancePatentTaskService.java b/backend-java/src/main/java/com/nanri/aiimage/modules/appearancepatent/service/AppearancePatentTaskService.java index 9d751c2..2b6f85b 100644 --- a/backend-java/src/main/java/com/nanri/aiimage/modules/appearancepatent/service/AppearancePatentTaskService.java +++ b/backend-java/src/main/java/com/nanri/aiimage/modules/appearancepatent/service/AppearancePatentTaskService.java @@ -121,7 +121,7 @@ public class AppearancePatentTaskService { .filter(Objects::nonNull) .toList(); if (sourceFiles.isEmpty()) { - throw new BusinessException("璇峰厛涓婁紶 Excel 鏂囦欢"); + throw new BusinessException("请先上传 Excel 文件"); } List allRows = new ArrayList<>(); List mergedHeaders = new ArrayList<>(); @@ -461,10 +461,10 @@ public class AppearancePatentTaskService { private SubmitContext persistSubmittedChunk(Long taskId, AppearancePatentSubmitResultRequest request) { FileTaskEntity task = fileTaskMapper.selectById(taskId); if (task == null || !MODULE_TYPE.equals(task.getModuleType())) { - throw new BusinessException("浠诲姟涓嶅瓨鍦?"); + throw new BusinessException("任务不存在"); } if (!STATUS_RUNNING.equals(task.getStatus())) { - throw new BusinessException("浠诲姟涓嶆槸杩愯涓姸鎬?"); + throw new BusinessException("任务不是运行中状态"); } int chunkIndex = request.getChunkIndex() == null ? 0 : request.getChunkIndex(); @@ -482,7 +482,7 @@ public class AppearancePatentTaskService { .last("limit 1")); if (existing == null) { List rawRows = flattenSubmittedRows(request); - String payloadJson = writeJson(rawRows, "缁撴灉搴忓垪鍖栧け璐?"); + String payloadJson = writeJson(rawRows, "结果序列化失败"); TaskChunkEntity chunk = new TaskChunkEntity(); chunk.setTaskId(taskId); @@ -514,7 +514,7 @@ public class AppearancePatentTaskService { private void completeSubmittedChunk(SubmitContext context) { FileTaskEntity task = fileTaskMapper.selectById(context.task().getId()); if (task == null || !MODULE_TYPE.equals(task.getModuleType())) { - throw new BusinessException("浠诲姟涓嶅瓨鍦?"); + throw new BusinessException("任务不存在"); } if (!STATUS_RUNNING.equals(task.getStatus())) { log.info("[appearance-patent] skip completion because task already finalized taskId={} status={}", @@ -1379,7 +1379,7 @@ public class AppearancePatentTaskService { } private String normalize(String val) { - return val == null ? "" : val.replace("\ufeff", "").replace("\u3000", " ").trim().replaceAll("\\s+", " "); + return val == null ? "" : val.replace(String.valueOf((char) 0xFEFF), "").replace((char) 0x3000, ' ').trim().replaceAll("\\s+", " "); } private String buildParsedPayloadJson(String aiPrompt, List sourceFiles, List headers, List groups, List allRows) { diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/brand/service/BrandTaskService.java b/backend-java/src/main/java/com/nanri/aiimage/modules/brand/service/BrandTaskService.java index 6361298..5354ceb 100644 --- a/backend-java/src/main/java/com/nanri/aiimage/modules/brand/service/BrandTaskService.java +++ b/backend-java/src/main/java/com/nanri/aiimage/modules/brand/service/BrandTaskService.java @@ -881,8 +881,8 @@ public class BrandTaskService { if (value == null) { return ""; } - return value.replace("\ufeff", "") - .replace("\u3000", " ") + return value.replace(String.valueOf((char) 0xFEFF), "") + .replace((char) 0x3000, ' ') .replace("\r\n", " ") .replace("\r", " ") .replace("\n", " ") diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/convert/service/ConvertRunService.java b/backend-java/src/main/java/com/nanri/aiimage/modules/convert/service/ConvertRunService.java index a8161ed..6d2bf4a 100644 --- a/backend-java/src/main/java/com/nanri/aiimage/modules/convert/service/ConvertRunService.java +++ b/backend-java/src/main/java/com/nanri/aiimage/modules/convert/service/ConvertRunService.java @@ -475,8 +475,8 @@ public class ConvertRunService { if (value == null) { return ""; } - return value.replace("\ufeff", "") - .replace("\u3000", " ") + return value.replace(String.valueOf((char) 0xFEFF), "") + .replace((char) 0x3000, ' ') .replace("\r\n", " ") .replace("\r", " ") .replace("\n", " ") diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/dedupe/service/DedupeRunService.java b/backend-java/src/main/java/com/nanri/aiimage/modules/dedupe/service/DedupeRunService.java index 804130e..360a1c4 100644 --- a/backend-java/src/main/java/com/nanri/aiimage/modules/dedupe/service/DedupeRunService.java +++ b/backend-java/src/main/java/com/nanri/aiimage/modules/dedupe/service/DedupeRunService.java @@ -47,8 +47,8 @@ import java.util.zip.ZipOutputStream; @Slf4j public class DedupeRunService { - private static final String HEADER_SPLIT_MARKER = "idASIN\u56fd\u5bb6\u72b6\u6001\u4ef7\u683c\u53d8\u4f53\u6570\u91cf"; - private static final String HEADER_STOP_COLUMN = "\u7f29\u7565\u56fe\u5730\u57408"; + private static final String HEADER_SPLIT_MARKER = "idASIN国家状态价格变体数量"; + private static final String HEADER_STOP_COLUMN = "缩略图地址8"; private final FileTaskMapper fileTaskMapper; private final FileResultMapper fileResultMapper; @@ -59,7 +59,7 @@ public class DedupeRunService { public DedupeRunVo run(DedupeRunRequest request) { long runStartNs = System.nanoTime(); if (!request.isKeepIntegerIds() && !request.isKeepUnderscoreIds() && !request.isKeepIntegerMainIdsWhenNoSubIds()) { - throw new BusinessException("\u8bf7\u81f3\u5c11\u9009\u62e9\u4e00\u79cd ID \u4fdd\u7559\u89c4\u5219"); + throw new BusinessException("请至少选择一种 ID 保留规则"); } FileTaskEntity task = new FileTaskEntity(); @@ -90,7 +90,7 @@ public class DedupeRunService { File inputFile = findLocalSourceFile(sourceFile.getFileKey()); long findFileNs = elapsedNs(fileStartNs); if (inputFile == null || !inputFile.exists()) { - throw new BusinessException("\u4e0a\u4f20\u6587\u4ef6\u4e0d\u5b58\u5728\uff0c\u8bf7\u91cd\u65b0\u4e0a\u4f20"); + throw new BusinessException("上传文件不存在,请重新上传"); } String inputName = sourceFile.getOriginalFilename() == null ? inputFile.getName() : sourceFile.getOriginalFilename(); @@ -240,7 +240,7 @@ public class DedupeRunService { public void deleteHistory(Long resultId, Long userId) { FileResultEntity entity = fileResultMapper.selectById(resultId); if (entity == null || !"DEDUPE".equals(entity.getModuleType()) || !userId.equals(entity.getUserId())) { - throw new BusinessException("\u8bb0\u5f55\u4e0d\u5b58\u5728"); + throw new BusinessException("记录不存在"); } fileResultMapper.deleteById(resultId); } @@ -248,11 +248,11 @@ public class DedupeRunService { public Resource getResultFile(Long resultId, Long userId) { FileResultEntity entity = fileResultMapper.selectById(resultId); if (entity == null || entity.getResultFileUrl() == null || !"DEDUPE".equals(entity.getModuleType()) || !userId.equals(entity.getUserId())) { - throw new BusinessException("\u7ed3\u679c\u6587\u4ef6\u4e0d\u5b58\u5728"); + throw new BusinessException("结果文件不存在"); } File file = new File(entity.getResultFileUrl()); if (!file.exists()) { - throw new BusinessException("\u7ed3\u679c\u6587\u4ef6\u4e0d\u5b58\u5728"); + throw new BusinessException("结果文件不存在"); } return new FileSystemResource(file); } @@ -342,18 +342,18 @@ public class DedupeRunService { Sheet sheet = workbook.getSheetAt(0); Row headerRow = sheet.getRow(0); if (headerRow == null) { - throw new BusinessException("Excel \u8868\u5934\u4e3a\u7a7a"); + throw new BusinessException("Excel 表头为空"); } Map headerMap = buildHeaderMap(headerRow, formatter); if (headerMap.isEmpty()) { - throw new BusinessException("Excel \u8868\u5934\u4e3a\u7a7a"); + throw new BusinessException("Excel 表头为空"); } List missing = selectedColumns.stream() .filter(column -> !headerMap.containsKey(column)) .toList(); if (!missing.isEmpty()) { - throw new BusinessException("\u7f3a\u5c11\u5217\uff1a" + String.join("\u3001", missing)); + throw new BusinessException("缺少列:" + String.join("、", missing)); } List selectedIndexes = new ArrayList<>(selectedColumns.size()); @@ -478,7 +478,7 @@ public class DedupeRunService { zos.closeEntry(); } } catch (Exception ex) { - throw new BusinessException("\u53bb\u91cd\u7ed3\u679c\u6253\u5305\u5931\u8d25\uff1a" + ex.getMessage()); + throw new BusinessException("去重结果打包失败:" + ex.getMessage()); } return zipFile; } @@ -602,7 +602,7 @@ public class DedupeRunService { boolean previousWhitespace = false; for (int i = start; i < end; i++) { char ch = value.charAt(i); - if (ch == '\uFEFF') { + if (ch == 0xFEFF) { if (builder == null) { builder = new StringBuilder(value.length()); builder.append(value, start, i); @@ -629,11 +629,11 @@ public class DedupeRunService { } private boolean isTrimmedWhitespace(char ch) { - return ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n' || ch == '\u3000' || Character.isWhitespace(ch); + return ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n' || ch == 0x3000 || Character.isWhitespace(ch); } private boolean isNormalizedWhitespace(char ch) { - return ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n' || ch == '\u3000' || Character.isWhitespace(ch); + return ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n' || ch == 0x3000 || Character.isWhitespace(ch); } private record DedupeArchiveEntry(String relativePath, String inputName, File outputFile) { diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/file/service/LocalFileStorageService.java b/backend-java/src/main/java/com/nanri/aiimage/modules/file/service/LocalFileStorageService.java index 1263a42..aec1237 100644 --- a/backend-java/src/main/java/com/nanri/aiimage/modules/file/service/LocalFileStorageService.java +++ b/backend-java/src/main/java/com/nanri/aiimage/modules/file/service/LocalFileStorageService.java @@ -109,8 +109,8 @@ public class LocalFileStorageService { if (value == null) { return ""; } - return value.replace("\ufeff", "") - .replace("\u3000", " ") + return value.replace(String.valueOf((char) 0xFEFF), "") + .replace((char) 0x3000, ' ') .replace("\r\n", " ") .replace("\r", " ") .replace("\n", " ") diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/patroldelete/service/PatrolDeleteTaskService.java b/backend-java/src/main/java/com/nanri/aiimage/modules/patroldelete/service/PatrolDeleteTaskService.java index 21298b0..8f6d54d 100644 --- a/backend-java/src/main/java/com/nanri/aiimage/modules/patroldelete/service/PatrolDeleteTaskService.java +++ b/backend-java/src/main/java/com/nanri/aiimage/modules/patroldelete/service/PatrolDeleteTaskService.java @@ -281,7 +281,6 @@ public class PatrolDeleteTaskService { return vo; } - @Transactional public void submitResult(Long taskId, PatrolDeleteSubmitResultRequest request) { if (taskId == null || taskId <= 0) { throw new BusinessException("taskId 不合法"); @@ -334,7 +333,6 @@ public class PatrolDeleteTaskService { tryFinalizeTask(taskId, false); } - @Transactional public boolean tryFinalizeTask(Long taskId, boolean fromCompensation) { if (taskId == null || taskId <= 0) { return false; diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/pricetrack/model/dto/PriceTrackSubmitResultRequest.java b/backend-java/src/main/java/com/nanri/aiimage/modules/pricetrack/model/dto/PriceTrackSubmitResultRequest.java index e0aeff8..f3810a5 100644 --- a/backend-java/src/main/java/com/nanri/aiimage/modules/pricetrack/model/dto/PriceTrackSubmitResultRequest.java +++ b/backend-java/src/main/java/com/nanri/aiimage/modules/pricetrack/model/dto/PriceTrackSubmitResultRequest.java @@ -1,5 +1,6 @@ package com.nanri.aiimage.modules.pricetrack.model.dto; +import com.fasterxml.jackson.annotation.JsonAlias; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; @@ -88,6 +89,10 @@ public class PriceTrackSubmitResultRequest { @Schema(description = "购物车店铺名", example = "店铺A") private String cartShopName; + @JsonAlias({"changedPrice", "changePrice", "modifiedPrice", "newPrice", "targetPrice", "updatedPrice", "改价价格"}) + @Schema(description = "改价价格", example = "18.99") + private String priceChangePrice; + @Schema(description = "改价情况", example = "UPDATED") private String priceChangeStatus; diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/pricetrack/service/PriceTrackExcelAssemblyService.java b/backend-java/src/main/java/com/nanri/aiimage/modules/pricetrack/service/PriceTrackExcelAssemblyService.java index 6d313a1..c042541 100644 --- a/backend-java/src/main/java/com/nanri/aiimage/modules/pricetrack/service/PriceTrackExcelAssemblyService.java +++ b/backend-java/src/main/java/com/nanri/aiimage/modules/pricetrack/service/PriceTrackExcelAssemblyService.java @@ -29,6 +29,7 @@ public class PriceTrackExcelAssemblyService { "第一名", "第二名", "购物车店铺名", + "改价价格", "改价情况", "修改次数", "状态" @@ -40,13 +41,14 @@ public class PriceTrackExcelAssemblyService { workbook.setCompressTempFiles(true); try (FileOutputStream fos = new FileOutputStream(outputXlsx)) { for (ProductRiskCountryCode code : ProductRiskCountryCode.values()) { - String sheetName = code.name(); + String countryCode = code.name(); + String sheetName = sheetNameOf(code); Sheet sheet = workbook.createSheet(sheetName); Row headerRow = sheet.createRow(0); for (int i = 0; i < RESULT_HEADER.length; i++) { headerRow.createCell(i).setCellValue(RESULT_HEADER[i]); } - List rows = safe.getOrDefault(sheetName, List.of()); + List rows = safe.getOrDefault(countryCode, List.of()); int rowIdx = 1; for (PriceTrackSubmitResultRequest.AsinResult item : rows) { if (item == null) { @@ -62,9 +64,10 @@ public class PriceTrackExcelAssemblyService { row.createCell(6).setCellValue(valueOf(item.getFirstPlace())); row.createCell(7).setCellValue(valueOf(item.getSecondPlace())); row.createCell(8).setCellValue(valueOf(item.getCartShopName())); - row.createCell(9).setCellValue(valueOf(item.getPriceChangeStatus())); - row.createCell(10).setCellValue(valueOf(item.getModifyCount())); - row.createCell(11).setCellValue(valueOf(item.getStatus())); + row.createCell(9).setCellValue(valueOf(item.getPriceChangePrice())); + row.createCell(10).setCellValue(valueOf(item.getPriceChangeStatus())); + row.createCell(11).setCellValue(valueOf(item.getModifyCount())); + row.createCell(12).setCellValue(valueOf(item.getStatus())); } applyDefaultColumnWidths(sheet); } @@ -113,8 +116,18 @@ public class PriceTrackExcelAssemblyService { return value == null ? "" : value; } + private static String sheetNameOf(ProductRiskCountryCode code) { + return switch (code) { + case DE -> "德国"; + case FR -> "法国"; + case ES -> "西班牙"; + case IT -> "意大利"; + case UK -> "英国"; + }; + } + private void applyDefaultColumnWidths(Sheet sheet) { - int[] widths = {22, 18, 12, 12, 12, 12, 20, 20, 22, 18, 12, 12}; + int[] widths = {22, 18, 12, 12, 12, 12, 20, 20, 22, 14, 18, 12, 12}; for (int i = 0; i < widths.length; i++) { sheet.setColumnWidth(i, widths[i] * 256); } diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/pricetrack/service/PriceTrackTaskService.java b/backend-java/src/main/java/com/nanri/aiimage/modules/pricetrack/service/PriceTrackTaskService.java index e9ece5c..34043cb 100644 --- a/backend-java/src/main/java/com/nanri/aiimage/modules/pricetrack/service/PriceTrackTaskService.java +++ b/backend-java/src/main/java/com/nanri/aiimage/modules/pricetrack/service/PriceTrackTaskService.java @@ -127,6 +127,21 @@ public class PriceTrackTaskService { return tasks; } + private List selectTaskStatusesByIdsInBatches(List taskIds) { + List tasks = new ArrayList<>(); + if (taskIds == null || taskIds.isEmpty()) { + return tasks; + } + int batchSize = Math.max(1, taskPressureProperties.getDbSelectBatchSize()); + for (int start = 0; start < taskIds.size(); start += batchSize) { + int end = Math.min(start + batchSize, taskIds.size()); + tasks.addAll(fileTaskMapper.selectList(new LambdaQueryWrapper() + .select(FileTaskEntity::getId, FileTaskEntity::getStatus) + .in(FileTaskEntity::getId, taskIds.subList(start, end)))); + } + return tasks; + } + public PriceTrackDashboardVo dashboard(Long userId) { if (userId == null || userId <= 0) throw new BusinessException("user_id 不合法"); PriceTrackDashboardVo vo = new PriceTrackDashboardVo(); @@ -166,7 +181,7 @@ public class PriceTrackTaskService { .distinct() .toList(); if (!taskIds.isEmpty()) { - List tasks = selectTasksByIdsInBatches(taskIds); + List tasks = selectTaskStatusesByIdsInBatches(taskIds); for (FileTaskEntity t : tasks) { if (t != null) statusByTaskId.put(t.getId(), t.getStatus()); } @@ -383,7 +398,6 @@ public class PriceTrackTaskService { return vo; } - @Transactional public void submitResult(Long taskId, PriceTrackSubmitResultRequest request) { if (taskId == null || taskId <= 0) throw new BusinessException("taskId 不合法"); if (request == null || request.getShops() == null || request.getShops().isEmpty()) { @@ -458,7 +472,6 @@ public class PriceTrackTaskService { updateTaskStatusFromLatestRows(task, latest); } - @Transactional public boolean tryFinalizeTask(Long taskId, boolean fromCompensation) { if (taskId == null || taskId <= 0) return false; FileTaskEntity task = loadTaskForExecution(taskId); @@ -1139,6 +1152,7 @@ public class PriceTrackTaskService { merged.setFirstPlace(firstNonBlank(incoming.getFirstPlace(), merged.getFirstPlace())); merged.setSecondPlace(firstNonBlank(incoming.getSecondPlace(), merged.getSecondPlace())); merged.setCartShopName(firstNonBlank(incoming.getCartShopName(), merged.getCartShopName())); + merged.setPriceChangePrice(firstNonBlank(incoming.getPriceChangePrice(), merged.getPriceChangePrice())); merged.setPriceChangeStatus(firstNonBlank(incoming.getPriceChangeStatus(), merged.getPriceChangeStatus())); merged.setModifyCount(firstNonBlank(incoming.getModifyCount(), merged.getModifyCount())); merged.setStatus(firstNonBlank(incoming.getStatus(), merged.getStatus())); @@ -1163,6 +1177,7 @@ public class PriceTrackTaskService { out.setFirstPlace(row.getFirstPlace()); out.setSecondPlace(row.getSecondPlace()); out.setCartShopName(row.getCartShopName()); + out.setPriceChangePrice(row.getPriceChangePrice()); out.setPriceChangeStatus(row.getPriceChangeStatus()); out.setModifyCount(row.getModifyCount()); out.setStatus(row.getStatus()); diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/productrisk/service/ProductRiskTaskService.java b/backend-java/src/main/java/com/nanri/aiimage/modules/productrisk/service/ProductRiskTaskService.java index 1744916..d0e55c8 100644 --- a/backend-java/src/main/java/com/nanri/aiimage/modules/productrisk/service/ProductRiskTaskService.java +++ b/backend-java/src/main/java/com/nanri/aiimage/modules/productrisk/service/ProductRiskTaskService.java @@ -124,6 +124,21 @@ public class ProductRiskTaskService { return tasks; } + private List selectTaskStatusesByIdsInBatches(List taskIds) { + List tasks = new ArrayList<>(); + if (taskIds == null || taskIds.isEmpty()) { + return tasks; + } + int batchSize = Math.max(1, taskPressureProperties.getDbSelectBatchSize()); + for (int start = 0; start < taskIds.size(); start += batchSize) { + int end = Math.min(start + batchSize, taskIds.size()); + tasks.addAll(fileTaskMapper.selectList(new LambdaQueryWrapper() + .select(FileTaskEntity::getId, FileTaskEntity::getStatus) + .in(FileTaskEntity::getId, taskIds.subList(start, end)))); + } + return tasks; + } + public ProductRiskDashboardVo dashboard(Long userId) { if (userId == null || userId <= 0) { throw new BusinessException("user_id 不合法"); @@ -166,7 +181,7 @@ public class ProductRiskTaskService { .distinct() .toList(); if (!taskIds.isEmpty()) { - List tasks = selectTasksByIdsInBatches(taskIds); + List tasks = selectTaskStatusesByIdsInBatches(taskIds); for (FileTaskEntity t : tasks) { if (t != null) { statusByTaskId.put(t.getId(), t.getStatus()); @@ -465,7 +480,6 @@ public class ProductRiskTaskService { return vo; } - @Transactional public void submitResult(Long taskId, ProductRiskSubmitResultRequest request) { if (taskId == null || taskId <= 0) { throw new BusinessException("taskId 不合法"); @@ -630,7 +644,6 @@ public class ProductRiskTaskService { cleanupTaskCacheIfTerminal(taskId, task.getStatus()); } - @Transactional public boolean tryFinalizeTask(Long taskId, boolean fromCompensation) { if (taskId == null || taskId <= 0) { return false; diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/queryasin/service/QueryAsinTaskService.java b/backend-java/src/main/java/com/nanri/aiimage/modules/queryasin/service/QueryAsinTaskService.java index b266054..35c74a4 100644 --- a/backend-java/src/main/java/com/nanri/aiimage/modules/queryasin/service/QueryAsinTaskService.java +++ b/backend-java/src/main/java/com/nanri/aiimage/modules/queryasin/service/QueryAsinTaskService.java @@ -287,7 +287,6 @@ public class QueryAsinTaskService { return vo; } - @Transactional public void submitResult(Long taskId, QueryAsinSubmitResultRequest request) { if (taskId == null || taskId <= 0) { throw new BusinessException("taskId 不合法"); @@ -340,7 +339,6 @@ public class QueryAsinTaskService { tryFinalizeTask(taskId, false); } - @Transactional public boolean tryFinalizeTask(Long taskId, boolean fromCompensation) { if (taskId == null || taskId <= 0) { return false; diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/shopkey/mapper/ShopManageGroupMapper.java b/backend-java/src/main/java/com/nanri/aiimage/modules/shopkey/mapper/ShopManageGroupMapper.java index fb44b6c..39858ea 100644 --- a/backend-java/src/main/java/com/nanri/aiimage/modules/shopkey/mapper/ShopManageGroupMapper.java +++ b/backend-java/src/main/java/com/nanri/aiimage/modules/shopkey/mapper/ShopManageGroupMapper.java @@ -3,7 +3,68 @@ package com.nanri.aiimage.modules.shopkey.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.nanri.aiimage.modules.shopkey.model.entity.ShopManageGroupEntity; import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; + +import java.util.List; @Mapper public interface ShopManageGroupMapper extends BaseMapper { + + @Select(""" + SELECT DISTINCT accessible_group_id + FROM ( + SELECT g.id AS accessible_group_id + FROM biz_shop_manage_group g + WHERE g.created_by_id = #{operatorId} + OR g.user_id = #{operatorId} + UNION + SELECT gm.group_id AS accessible_group_id + FROM biz_shop_manage_group_member gm + WHERE gm.user_id = #{operatorId} + UNION + SELECT g2.id AS accessible_group_id + FROM biz_shop_manage_group_member gm + INNER JOIN biz_shop_manage_group g1 ON g1.id = gm.group_id + INNER JOIN biz_shop_manage_group g2 + ON g2.created_by_id = COALESCE(g1.created_by_id, g1.user_id) + OR g2.user_id = COALESCE(g1.created_by_id, g1.user_id) + WHERE gm.user_id = #{operatorId} + AND COALESCE(g1.created_by_id, g1.user_id) IS NOT NULL + UNION + SELECT member_groups.id AS accessible_group_id + FROM biz_shop_manage_group leader_group + INNER JOIN biz_shop_manage_group_member direct_member ON direct_member.group_id = leader_group.id + INNER JOIN biz_shop_manage_group member_groups + ON member_groups.created_by_id = direct_member.user_id + OR member_groups.user_id = direct_member.user_id + WHERE leader_group.created_by_id = #{operatorId} + OR leader_group.user_id = #{operatorId} + ) accessible_groups + WHERE accessible_group_id IS NOT NULL + ORDER BY accessible_group_id ASC + """) + List selectAccessibleGroupIds(@Param("operatorId") Long operatorId); + + @Select(""" + SELECT DISTINCT visible_user_id + FROM ( + SELECT #{operatorId} AS visible_user_id + UNION + SELECT direct_member.user_id AS visible_user_id + FROM biz_shop_manage_group leader_group + INNER JOIN biz_shop_manage_group_member direct_member ON direct_member.group_id = leader_group.id + WHERE leader_group.created_by_id = #{operatorId} + OR leader_group.user_id = #{operatorId} + UNION + SELECT COALESCE(direct_group.created_by_id, direct_group.user_id) AS visible_user_id + FROM biz_shop_manage_group_member membership + INNER JOIN biz_shop_manage_group direct_group ON direct_group.id = membership.group_id + WHERE membership.user_id = #{operatorId} + AND COALESCE(direct_group.created_by_id, direct_group.user_id) IS NOT NULL + ) visible_users + WHERE visible_user_id IS NOT NULL + ORDER BY visible_user_id ASC + """) + List selectAccessibleCreatorUserIds(@Param("operatorId") Long operatorId); } diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/shopkey/service/QueryAsinService.java b/backend-java/src/main/java/com/nanri/aiimage/modules/shopkey/service/QueryAsinService.java index 2e89945..8517d93 100644 --- a/backend-java/src/main/java/com/nanri/aiimage/modules/shopkey/service/QueryAsinService.java +++ b/backend-java/src/main/java/com/nanri/aiimage/modules/shopkey/service/QueryAsinService.java @@ -40,7 +40,7 @@ public class QueryAsinService { private static final List SUPPORTED_COUNTRIES = List.of("DE", "UK", "FR", "IT", "ES"); private static final String KEY_SEPARATOR = Character.toString((char) 1); - private static final String UTF8_BOM = Character.toString(0xFEFF); + private static final String UTF8_BOM = String.valueOf((char) 0xFEFF); private final QueryAsinMapper queryAsinMapper; private final ShopManageGroupService shopManageGroupService; @@ -79,7 +79,7 @@ public class QueryAsinService { List rows = queryAsinMapper .selectList(query.last("LIMIT " + ((safePage - 1) * safePageSize) + ", " + safePageSize)); - Map groupNameById = buildGroupNameMap(rows.stream() + Map groupNameById = shopManageGroupService.buildGroupNameMap(rows.stream() .map(QueryAsinEntity::getGroupId) .filter(id -> id != null && id > 0) .distinct() @@ -316,22 +316,6 @@ public class QueryAsinService { return vo; } - private Map buildGroupNameMap(List groupIds) { - if (groupIds == null || groupIds.isEmpty()) { - return Map.of(); - } - LinkedHashMap map = new LinkedHashMap<>(); - for (Long groupId : groupIds) { - try { - ShopManageGroupEntity group = shopManageGroupService.getById(groupId); - map.put(groupId, group.getGroupName()); - } catch (Exception ignored) { - map.put(groupId, ""); - } - } - return map; - } - private Set normalizeCountries(List countries) { LinkedHashSet normalized = new LinkedHashSet<>(); if (countries != null) { @@ -384,11 +368,11 @@ public class QueryAsinService { return upper; } return switch (normalizeHeaderKey(normalized)) { - case "\u5fb7\u56fd", "\u5fb7", "de", "germany", "german", "deutschland" -> "DE"; - case "\u82f1\u56fd", "\u82f1", "uk", "gb", "greatbritain", "britain", "unitedkingdom" -> "UK"; - case "\u6cd5\u56fd", "\u6cd5", "fr", "france", "french" -> "FR"; - case "\u610f\u5927\u5229", "\u610f", "it", "italy", "italian" -> "IT"; - case "\u897f\u73ed\u7259", "\u897f", "es", "spain", "spanish", "espana" -> "ES"; + case "德国", "德", "de", "germany", "german", "deutschland" -> "DE"; + case "英国", "英", "uk", "gb", "greatbritain", "britain", "unitedkingdom" -> "UK"; + case "法国", "法", "fr", "france", "french" -> "FR"; + case "意大利", "意", "it", "italy", "italian" -> "IT"; + case "西班牙", "西", "es", "spain", "spanish", "espana" -> "ES"; default -> ""; }; } @@ -860,11 +844,11 @@ public class QueryAsinService { if (index != null) { return index; } - index = firstHeader("groupname", "\u5206\u7ec4id", "\u5206\u7ec4"); + index = firstHeader("groupname", "分组id", "分组"); if (index != null) { return index; } - return findHeaderByPredicate(key -> key.contains("\u5206\u7ec4") || key.contains("group")); + return findHeaderByPredicate(key -> key.contains("分组") || key.contains("group")); } private Integer resolveShopHeaderIndex() { @@ -873,13 +857,13 @@ public class QueryAsinService { return index; } index = firstHeader( - "\u5e97\u94fa\u540d", "\u5e97\u94fa\u540d\u79f0", "\u5e97\u94fa\u8d26\u53f7", "\u8d26\u53f7", + "店铺名", "店铺名称", "店铺账号", "账号", "account", "selleraccount", "storeaccount", "storename"); if (index != null) { return index; } return findHeaderByPredicate(key -> - (key.contains("\u5e97\u94fa") && (key.contains("\u540d") || key.contains("\u8d26\u53f7"))) + (key.contains("店铺") && (key.contains("名") || key.contains("账号"))) || (key.contains("shop") && (key.contains("name") || key.contains("account"))) || key.contains("selleraccount") || key.contains("storeaccount")); @@ -891,32 +875,32 @@ public class QueryAsinService { return index; } return switch (country) { - case "DE" -> firstHeader("\u5fb7\u56fd", "\u5fb7", "\u5fb7\u56fdasin"); - case "UK" -> firstHeader("\u82f1\u56fd", "\u82f1", "\u82f1\u56fdasin"); - case "FR" -> firstHeader("\u6cd5\u56fd", "\u6cd5", "\u6cd5\u56fdasin"); - case "IT" -> firstHeader("\u610f\u5927\u5229", "\u610f", "\u610f\u5927\u5229asin"); - case "ES" -> firstHeader("\u897f\u73ed\u7259", "\u897f", "\u897f\u73ed\u7259asin"); + case "DE" -> firstHeader("德国", "德", "德国asin"); + case "UK" -> firstHeader("英国", "英", "英国asin"); + case "FR" -> firstHeader("法国", "法", "法国asin"); + case "IT" -> firstHeader("意大利", "意", "意大利asin"); + case "ES" -> firstHeader("西班牙", "西", "西班牙asin"); default -> null; }; } private Integer findSingleCountryIndex() { Integer index = firstHeader( - "\u4e2d\u6587\u56fd\u5bb6\u540d", "\u56fd\u5bb6", "\u56fd\u5bb6\u540d", "\u56fd\u5bb6\u7ad9\u70b9", - "\u7ad9\u70b9", "country", "countryname", "marketplace", "site"); + "中文国家名", "国家", "国家名", "国家站点", + "站点", "country", "countryname", "marketplace", "site"); if (index != null) { return index; } return findHeaderByPredicate(key -> - key.contains("\u56fd\u5bb6") - || key.contains("\u7ad9\u70b9") + key.contains("国家") + || key.contains("站点") || key.contains("country") || key.contains("marketplace") || key.equals("site")); } private Integer findSingleAsinIndex() { - return firstHeader("asin", "\u67e5\u8be2asin", "\u4ea7\u54c1asin", "\u94fe\u63a5asin"); + return firstHeader("asin", "查询asin", "产品asin", "链接asin"); } private boolean hasWideCountryColumns() { diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/shopkey/service/ShopManageGroupService.java b/backend-java/src/main/java/com/nanri/aiimage/modules/shopkey/service/ShopManageGroupService.java index 204beea..73a8471 100644 --- a/backend-java/src/main/java/com/nanri/aiimage/modules/shopkey/service/ShopManageGroupService.java +++ b/backend-java/src/main/java/com/nanri/aiimage/modules/shopkey/service/ShopManageGroupService.java @@ -48,6 +48,7 @@ public class ShopManageGroupService { public List list(Long createdById) { return toVoList(groupMapper.selectList(new LambdaQueryWrapper() .eq(createdById != null && createdById > 0, ShopManageGroupEntity::getCreatedById, createdById) + .or(createdById != null && createdById > 0, wrapper -> wrapper.eq(ShopManageGroupEntity::getUserId, createdById)) .orderByAsc(ShopManageGroupEntity::getId))); } @@ -74,40 +75,44 @@ public class ShopManageGroupService { .collect(Collectors.toCollection(LinkedHashSet::new)); } Long normalizedOperatorId = normalizePositiveId(operatorId, "操作人不能为空"); - LinkedHashSet groupIds = groupMapper.selectList(new LambdaQueryWrapper() - .select(ShopManageGroupEntity::getId) - .eq(ShopManageGroupEntity::getCreatedById, normalizedOperatorId)) + return groupMapper.selectAccessibleGroupIds(normalizedOperatorId) .stream() - .map(ShopManageGroupEntity::getId) .filter(id -> id != null && id > 0) .collect(Collectors.toCollection(LinkedHashSet::new)); - List memberGroupIds = groupMemberMapper.selectList(new LambdaQueryWrapper() - .select(ShopManageGroupMemberEntity::getGroupId) - .eq(ShopManageGroupMemberEntity::getUserId, normalizedOperatorId)) - .stream() - .map(ShopManageGroupMemberEntity::getGroupId) - .filter(id -> id != null && id > 0) - .toList(); - groupIds.addAll(memberGroupIds); - if (!memberGroupIds.isEmpty()) { - LinkedHashSet leaderIds = groupMapper.selectList(new LambdaQueryWrapper() - .select(ShopManageGroupEntity::getCreatedById) - .in(ShopManageGroupEntity::getId, memberGroupIds)) - .stream() - .map(ShopManageGroupEntity::getCreatedById) - .filter(id -> id != null && id > 0) - .collect(Collectors.toCollection(LinkedHashSet::new)); - if (!leaderIds.isEmpty()) { - groupIds.addAll(groupMapper.selectList(new LambdaQueryWrapper() - .select(ShopManageGroupEntity::getId) - .in(ShopManageGroupEntity::getCreatedById, leaderIds)) - .stream() - .map(ShopManageGroupEntity::getId) - .filter(id -> id != null && id > 0) - .toList()); - } + } + + public Set listAccessibleCreatorUserIds(Long operatorId, boolean superAdmin) { + if (superAdmin) { + return Set.of(); } - return groupIds; + Long normalizedOperatorId = normalizePositiveId(operatorId, "操作人不能为空"); + return groupMapper.selectAccessibleCreatorUserIds(normalizedOperatorId) + .stream() + .filter(id -> id != null && id > 0) + .collect(Collectors.toCollection(LinkedHashSet::new)); + } + + public Map buildGroupNameMap(List groupIds) { + if (groupIds == null || groupIds.isEmpty()) { + return Map.of(); + } + List normalizedGroupIds = groupIds.stream() + .filter(id -> id != null && id > 0) + .distinct() + .toList(); + if (normalizedGroupIds.isEmpty()) { + return Map.of(); + } + return groupMapper.selectList(new LambdaQueryWrapper() + .select(ShopManageGroupEntity::getId, ShopManageGroupEntity::getGroupName) + .in(ShopManageGroupEntity::getId, normalizedGroupIds)) + .stream() + .filter(group -> group.getId() != null) + .collect(Collectors.toMap( + ShopManageGroupEntity::getId, + group -> normalizeBlank(group.getGroupName()), + (left, right) -> left, + LinkedHashMap::new)); } @Transactional @@ -207,8 +212,9 @@ public class ShopManageGroupService { return; } Long normalizedOperatorId = normalizePositiveId(operatorId, "操作人不能为空"); - boolean createdBySelf = entity.getCreatedById() != null && entity.getCreatedById().equals(normalizedOperatorId); - if (!createdBySelf) { + boolean leaderBySelf = entity.getCreatedById() != null && entity.getCreatedById().equals(normalizedOperatorId); + boolean legacyLeaderBySelf = entity.getUserId() != null && entity.getUserId().equals(normalizedOperatorId); + if (!leaderBySelf && !legacyLeaderBySelf) { throw new BusinessException("只有组长才能维护分组"); } } @@ -332,8 +338,9 @@ public class ShopManageGroupService { LinkedHashSet userIds = new LinkedHashSet<>(); for (ShopManageGroupEntity group : groups) { - if (group.getCreatedById() != null && group.getCreatedById() > 0) { - userIds.add(group.getCreatedById()); + Long leaderUserId = resolveGroupLeaderUserId(group); + if (leaderUserId != null && leaderUserId > 0) { + userIds.add(leaderUserId); } } for (List members : membersByGroupId.values()) { @@ -355,8 +362,9 @@ public class ShopManageGroupService { ShopManageGroupItemVo vo = new ShopManageGroupItemVo(); vo.setId(entity.getId()); vo.setGroupName(entity.getGroupName()); - vo.setLeaderUserId(entity.getCreatedById()); - AdminUserEntity leaderUser = entity.getCreatedById() == null ? null : userById.get(entity.getCreatedById()); + Long leaderUserId = resolveGroupLeaderUserId(entity); + vo.setLeaderUserId(leaderUserId); + AdminUserEntity leaderUser = leaderUserId == null ? null : userById.get(leaderUserId); vo.setLeaderUsername(leaderUser == null ? "" : normalizeBlank(leaderUser.getUsername())); List memberEntities = membersByGroupId.getOrDefault(entity.getId(), Collections.emptyList()); @@ -380,4 +388,14 @@ public class ShopManageGroupService { } return result; } + + private Long resolveGroupLeaderUserId(ShopManageGroupEntity entity) { + if (entity == null) { + return null; + } + if (entity.getCreatedById() != null && entity.getCreatedById() > 0) { + return entity.getCreatedById(); + } + return entity.getUserId(); + } } diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/shopkey/service/ShopManageService.java b/backend-java/src/main/java/com/nanri/aiimage/modules/shopkey/service/ShopManageService.java index c994cde..5ab2b6f 100644 --- a/backend-java/src/main/java/com/nanri/aiimage/modules/shopkey/service/ShopManageService.java +++ b/backend-java/src/main/java/com/nanri/aiimage/modules/shopkey/service/ShopManageService.java @@ -15,7 +15,6 @@ import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; @@ -33,6 +32,7 @@ public class ShopManageService { long safePageSize = Math.min(Math.max(pageSize, 1), 100); String safeShopName = shopName == null ? "" : shopName.trim(); Set accessibleGroupIds = superAdmin ? Set.of() : shopManageGroupService.listAccessibleGroupIds(operatorId, false); + Set accessibleCreatorUserIds = superAdmin ? Set.of() : shopManageGroupService.listAccessibleCreatorUserIds(operatorId, false); LambdaQueryWrapper query = new LambdaQueryWrapper() .eq(groupId != null && groupId > 0, ShopManageEntity::getGroupId, groupId) @@ -40,20 +40,30 @@ public class ShopManageService { .orderByDesc(ShopManageEntity::getId); if (!superAdmin) { - Long normalizedOperatorId = normalizePositiveId(operatorId, "操作人不能为空"); - query.and(wrapper -> { - wrapper.eq(ShopManageEntity::getCreatedById, normalizedOperatorId); - if (!accessibleGroupIds.isEmpty()) { - wrapper.or().in(ShopManageEntity::getGroupId, accessibleGroupIds); - } - }); + normalizePositiveId(operatorId, "操作人不能为空"); + if (accessibleGroupIds.isEmpty() && accessibleCreatorUserIds.isEmpty()) { + query.eq(ShopManageEntity::getId, -1L); + } else { + query.and(wrapper -> { + boolean hasCreatorFilter = !accessibleCreatorUserIds.isEmpty(); + if (hasCreatorFilter) { + wrapper.in(ShopManageEntity::getCreatedById, accessibleCreatorUserIds); + } + if (!accessibleGroupIds.isEmpty()) { + if (hasCreatorFilter) { + wrapper.or(); + } + wrapper.in(ShopManageEntity::getGroupId, accessibleGroupIds); + } + }); + } } Long total = shopManageMapper.selectCount(query); List rows = shopManageMapper .selectList(query.last("LIMIT " + ((safePage - 1) * safePageSize) + ", " + safePageSize)); - Map groupNameById = buildGroupNameMap(rows.stream() + Map groupNameById = shopManageGroupService.buildGroupNameMap(rows.stream() .map(ShopManageEntity::getGroupId) .filter(id -> id != null && id > 0) .distinct() @@ -190,22 +200,6 @@ public class ShopManageService { } } - private Map buildGroupNameMap(List groupIds) { - if (groupIds == null || groupIds.isEmpty()) { - return Map.of(); - } - LinkedHashMap map = new LinkedHashMap<>(); - for (Long groupId : groupIds) { - try { - ShopManageGroupEntity group = shopManageGroupService.getById(groupId); - map.put(groupId, group.getGroupName()); - } catch (Exception ignored) { - map.put(groupId, ""); - } - } - return map; - } - private ShopManageItemVo toItemVo(ShopManageEntity entity, String groupName) { ShopManageItemVo vo = new ShopManageItemVo(); vo.setId(entity.getId()); diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/shopkey/service/SkipPriceAsinService.java b/backend-java/src/main/java/com/nanri/aiimage/modules/shopkey/service/SkipPriceAsinService.java index db38556..5499804 100644 --- a/backend-java/src/main/java/com/nanri/aiimage/modules/shopkey/service/SkipPriceAsinService.java +++ b/backend-java/src/main/java/com/nanri/aiimage/modules/shopkey/service/SkipPriceAsinService.java @@ -62,7 +62,7 @@ public class SkipPriceAsinService { List rows = skipPriceAsinMapper .selectList(query.last("LIMIT " + ((safePage - 1) * safePageSize) + ", " + safePageSize)); - Map groupNameById = buildGroupNameMap(rows.stream() + Map groupNameById = shopManageGroupService.buildGroupNameMap(rows.stream() .map(SkipPriceAsinEntity::getGroupId) .filter(id -> id != null && id > 0) .distinct() @@ -174,22 +174,6 @@ public class SkipPriceAsinService { return vo; } - private Map buildGroupNameMap(List groupIds) { - if (groupIds == null || groupIds.isEmpty()) { - return Map.of(); - } - LinkedHashMap map = new LinkedHashMap<>(); - for (Long groupId : groupIds) { - try { - ShopManageGroupEntity group = shopManageGroupService.getById(groupId); - map.put(groupId, group.getGroupName()); - } catch (Exception ignored) { - map.put(groupId, ""); - } - } - return map; - } - private Set normalizeCountries(List countries) { LinkedHashSet normalized = new LinkedHashSet<>(); if (countries != null) { diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/shopmatch/service/ShopMatchTaskService.java b/backend-java/src/main/java/com/nanri/aiimage/modules/shopmatch/service/ShopMatchTaskService.java index 4f18f2d..17035e6 100644 --- a/backend-java/src/main/java/com/nanri/aiimage/modules/shopmatch/service/ShopMatchTaskService.java +++ b/backend-java/src/main/java/com/nanri/aiimage/modules/shopmatch/service/ShopMatchTaskService.java @@ -132,6 +132,21 @@ public class ShopMatchTaskService { return tasks; } + private List selectTaskStatusesByIdsInBatches(List taskIds) { + List tasks = new ArrayList<>(); + if (taskIds == null || taskIds.isEmpty()) { + return tasks; + } + int batchSize = Math.max(1, taskPressureProperties.getDbSelectBatchSize()); + for (int start = 0; start < taskIds.size(); start += batchSize) { + int end = Math.min(start + batchSize, taskIds.size()); + tasks.addAll(fileTaskMapper.selectList(new LambdaQueryWrapper() + .select(FileTaskEntity::getId, FileTaskEntity::getStatus) + .in(FileTaskEntity::getId, taskIds.subList(start, end)))); + } + return tasks; + } + public ProductRiskDashboardVo dashboard(Long userId) { if (userId == null || userId <= 0) { throw new BusinessException("user_id 不合法"); @@ -174,7 +189,7 @@ public class ShopMatchTaskService { .distinct() .toList(); if (!taskIds.isEmpty()) { - for (FileTaskEntity task : selectTasksByIdsInBatches(taskIds)) { + for (FileTaskEntity task : selectTaskStatusesByIdsInBatches(taskIds)) { if (task != null) { statusByTaskId.put(task.getId(), task.getStatus()); } @@ -465,7 +480,6 @@ public class ShopMatchTaskService { } - @Transactional public void submitResult(Long taskId, ShopMatchSubmitResultRequest request) { if (taskId == null || taskId <= 0) { throw new BusinessException("taskId 不合法"); @@ -540,7 +554,6 @@ public class ShopMatchTaskService { updateTaskStatusFromLatestRows(task, latest); } - @Transactional public boolean tryFinalizeTask(Long taskId, boolean fromCompensation) { if (taskId == null || taskId <= 0) { return false; diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/split/service/SplitRunService.java b/backend-java/src/main/java/com/nanri/aiimage/modules/split/service/SplitRunService.java index 474e36e..91630f6 100644 --- a/backend-java/src/main/java/com/nanri/aiimage/modules/split/service/SplitRunService.java +++ b/backend-java/src/main/java/com/nanri/aiimage/modules/split/service/SplitRunService.java @@ -456,8 +456,8 @@ public class SplitRunService { if (value == null) { return ""; } - return value.replace("\ufeff", "") - .replace("\u3000", " ") + return value.replace(String.valueOf((char) 0xFEFF), "") + .replace((char) 0x3000, ' ') .replace("\r\n", " ") .replace("\r", " ") .replace("\n", " ") diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/task/service/TaskFileJobService.java b/backend-java/src/main/java/com/nanri/aiimage/modules/task/service/TaskFileJobService.java index 8166c2a..8365bf5 100644 --- a/backend-java/src/main/java/com/nanri/aiimage/modules/task/service/TaskFileJobService.java +++ b/backend-java/src/main/java/com/nanri/aiimage/modules/task/service/TaskFileJobService.java @@ -23,7 +23,6 @@ public class TaskFileJobService { private final TaskFileJobMapper taskFileJobMapper; private final ApplicationEventPublisher applicationEventPublisher; - @Transactional public TaskFileJobEntity enqueueAssembleResult(Long taskId, String moduleType, Long resultId, String scopeKey) { if (taskId == null || taskId <= 0 || moduleType == null || moduleType.isBlank() || resultId == null || resultId <= 0) { return null; @@ -146,7 +145,6 @@ public class TaskFileJobService { return reset; } - @Transactional public boolean markRunning(Long jobId) { if (jobId == null || jobId <= 0) { return false; @@ -158,7 +156,6 @@ public class TaskFileJobService { .set(TaskFileJobEntity::getUpdatedAt, LocalDateTime.now())) > 0; } - @Transactional public void touchRunning(Long jobId) { if (jobId == null || jobId <= 0) { return; @@ -169,7 +166,6 @@ public class TaskFileJobService { .set(TaskFileJobEntity::getUpdatedAt, LocalDateTime.now())); } - @Transactional public void markSuccess(TaskFileJobEntity job, String resultFileUrl) { taskFileJobMapper.update(null, new LambdaUpdateWrapper() .eq(TaskFileJobEntity::getId, job.getId()) @@ -180,7 +176,6 @@ public class TaskFileJobService { .set(TaskFileJobEntity::getFinishedAt, LocalDateTime.now())); } - @Transactional public void markFailed(TaskFileJobEntity job, String message) { int retryCount = job.getRetryCount() == null ? 0 : job.getRetryCount() + 1; taskFileJobMapper.update(null, new LambdaUpdateWrapper() diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/task/service/TaskResultItemService.java b/backend-java/src/main/java/com/nanri/aiimage/modules/task/service/TaskResultItemService.java index d955967..66b9de9 100644 --- a/backend-java/src/main/java/com/nanri/aiimage/modules/task/service/TaskResultItemService.java +++ b/backend-java/src/main/java/com/nanri/aiimage/modules/task/service/TaskResultItemService.java @@ -25,7 +25,6 @@ public class TaskResultItemService { private final ObjectMapper objectMapper; private final TransientPayloadStorageService transientPayloadStorageService; - @Transactional public void replaceResultSnapshot(Long taskId, String moduleType, Long resultId, String scopeKey, Object snapshot) { if (taskId == null || taskId <= 0 || isBlank(moduleType) || resultId == null || resultId <= 0 || snapshot == null) { return; @@ -35,7 +34,6 @@ public class TaskResultItemService { upsert(taskId, moduleType, resultId, normalizedScopeKey, itemKey, null, null, null, snapshot); } - @Transactional public void replaceTaskSnapshots(Long taskId, String moduleType, List snapshots, SnapshotKeyResolver resolver) { if (taskId == null || taskId <= 0 || isBlank(moduleType) || snapshots == null) { return; diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/task/service/TaskResultPayloadService.java b/backend-java/src/main/java/com/nanri/aiimage/modules/task/service/TaskResultPayloadService.java index e6fdf36..d6e094a 100644 --- a/backend-java/src/main/java/com/nanri/aiimage/modules/task/service/TaskResultPayloadService.java +++ b/backend-java/src/main/java/com/nanri/aiimage/modules/task/service/TaskResultPayloadService.java @@ -25,7 +25,6 @@ public class TaskResultPayloadService { private final ObjectMapper objectMapper; private final TransientPayloadStorageService transientPayloadStorageService; - @Transactional public void saveLatest(Long taskId, String moduleType, String scopeKey, Object payload) { if (taskId == null || taskId <= 0 || isBlank(moduleType) || isBlank(scopeKey) || payload == null) { return; diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/ziniao/service/ZiniaoAuthService.java b/backend-java/src/main/java/com/nanri/aiimage/modules/ziniao/service/ZiniaoAuthService.java index bc206da..f8c2a15 100644 --- a/backend-java/src/main/java/com/nanri/aiimage/modules/ziniao/service/ZiniaoAuthService.java +++ b/backend-java/src/main/java/com/nanri/aiimage/modules/ziniao/service/ZiniaoAuthService.java @@ -212,7 +212,7 @@ public boolean isIpWhitelistError(BusinessException ex) { if (value == null) { return ""; } - return value.replace("\u3000", " ") + return value.replace((char) 0x3000, ' ') .trim(); } diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/ziniao/service/ZiniaoShopIndexService.java b/backend-java/src/main/java/com/nanri/aiimage/modules/ziniao/service/ZiniaoShopIndexService.java index a446d9a..44959e5 100644 --- a/backend-java/src/main/java/com/nanri/aiimage/modules/ziniao/service/ZiniaoShopIndexService.java +++ b/backend-java/src/main/java/com/nanri/aiimage/modules/ziniao/service/ZiniaoShopIndexService.java @@ -357,7 +357,7 @@ public class ZiniaoShopIndexService { if (value == null) { return ""; } - return value.replace("\u3000", " ").trim(); + return value.replace((char) 0x3000, ' ').trim(); } private ZiniaoShopMatchResultVo pendingResult(String message) { diff --git a/backend-java/src/main/resources/application.yml b/backend-java/src/main/resources/application.yml index 8184634..f1f6719 100644 --- a/backend-java/src/main/resources/application.yml +++ b/backend-java/src/main/resources/application.yml @@ -19,6 +19,10 @@ spring: maximum-pool-size: ${AIIMAGE_DB_POOL_MAX_SIZE:30} minimum-idle: ${AIIMAGE_DB_POOL_MIN_IDLE:5} connection-timeout: ${AIIMAGE_DB_POOL_CONNECTION_TIMEOUT_MS:30000} + validation-timeout: ${AIIMAGE_DB_POOL_VALIDATION_TIMEOUT_MS:5000} + idle-timeout: ${AIIMAGE_DB_POOL_IDLE_TIMEOUT_MS:600000} + max-lifetime: ${AIIMAGE_DB_POOL_MAX_LIFETIME_MS:1500000} + keepalive-time: ${AIIMAGE_DB_POOL_KEEPALIVE_TIME_MS:120000} leak-detection-threshold: ${AIIMAGE_DB_POOL_LEAK_DETECT_MS:0} data: redis: @@ -54,7 +58,7 @@ mybatis-plus: mapper-locations: classpath*:mapper/**/*.xml configuration: map-underscore-to-camel-case: true - log-impl: org.apache.ibatis.logging.stdout.StdOutImpl + log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl springdoc: api-docs: diff --git a/backend-java/src/main/resources/db/V36__shopkey_list_query_indexes.sql b/backend-java/src/main/resources/db/V36__shopkey_list_query_indexes.sql new file mode 100644 index 0000000..747c3ea --- /dev/null +++ b/backend-java/src/main/resources/db/V36__shopkey_list_query_indexes.sql @@ -0,0 +1,61 @@ +SET @db_name = DATABASE(); + +SET @idx_exists := ( + SELECT COUNT(*) + FROM information_schema.STATISTICS + WHERE TABLE_SCHEMA = @db_name + AND TABLE_NAME = 'biz_shop_manage' + AND INDEX_NAME = 'idx_group_id_id' +); +SET @sql := IF(@idx_exists = 0, + 'ALTER TABLE biz_shop_manage ADD INDEX idx_group_id_id (group_id, id)', + 'SELECT 1' +); +PREPARE stmt FROM @sql; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +SET @idx_exists := ( + SELECT COUNT(*) + FROM information_schema.STATISTICS + WHERE TABLE_SCHEMA = @db_name + AND TABLE_NAME = 'biz_shop_manage' + AND INDEX_NAME = 'idx_created_by_id_id' +); +SET @sql := IF(@idx_exists = 0, + 'ALTER TABLE biz_shop_manage ADD INDEX idx_created_by_id_id (created_by_id, id)', + 'SELECT 1' +); +PREPARE stmt FROM @sql; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +SET @idx_exists := ( + SELECT COUNT(*) + FROM information_schema.STATISTICS + WHERE TABLE_SCHEMA = @db_name + AND TABLE_NAME = 'biz_skip_price_asin' + AND INDEX_NAME = 'idx_group_id_id' +); +SET @sql := IF(@idx_exists = 0, + 'ALTER TABLE biz_skip_price_asin ADD INDEX idx_group_id_id (group_id, id)', + 'SELECT 1' +); +PREPARE stmt FROM @sql; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +SET @idx_exists := ( + SELECT COUNT(*) + FROM information_schema.STATISTICS + WHERE TABLE_SCHEMA = @db_name + AND TABLE_NAME = 'biz_query_asin' + AND INDEX_NAME = 'idx_group_id_id' +); +SET @sql := IF(@idx_exists = 0, + 'ALTER TABLE biz_query_asin ADD INDEX idx_group_id_id (group_id, id)', + 'SELECT 1' +); +PREPARE stmt FROM @sql; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; diff --git a/backend-java/src/main/resources/db/V37__permission_menu_query_indexes.sql b/backend-java/src/main/resources/db/V37__permission_menu_query_indexes.sql new file mode 100644 index 0000000..daa2db1 --- /dev/null +++ b/backend-java/src/main/resources/db/V37__permission_menu_query_indexes.sql @@ -0,0 +1,31 @@ +SET @db_name = DATABASE(); + +SET @idx_exists := ( + SELECT COUNT(*) + FROM information_schema.STATISTICS + WHERE TABLE_SCHEMA = @db_name + AND TABLE_NAME = 'columns' + AND INDEX_NAME = 'idx_menu_type_sort_id' +); +SET @sql := IF(@idx_exists = 0, + 'ALTER TABLE columns ADD INDEX idx_menu_type_sort_id (menu_type, sort_order, id)', + 'SELECT 1' +); +PREPARE stmt FROM @sql; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +SET @idx_exists := ( + SELECT COUNT(*) + FROM information_schema.STATISTICS + WHERE TABLE_SCHEMA = @db_name + AND TABLE_NAME = 'user_column_permission' + AND INDEX_NAME = 'idx_user_column' +); +SET @sql := IF(@idx_exists = 0, + 'ALTER TABLE user_column_permission ADD INDEX idx_user_column (user_id, column_id)', + 'SELECT 1' +); +PREPARE stmt FROM @sql; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; diff --git a/backend-java/src/main/resources/db/V38__task_result_hot_path_indexes.sql b/backend-java/src/main/resources/db/V38__task_result_hot_path_indexes.sql new file mode 100644 index 0000000..01ac687 --- /dev/null +++ b/backend-java/src/main/resources/db/V38__task_result_hot_path_indexes.sql @@ -0,0 +1,31 @@ +SET @db_name = DATABASE(); + +SET @idx_exists := ( + SELECT COUNT(*) + FROM information_schema.STATISTICS + WHERE TABLE_SCHEMA = @db_name + AND TABLE_NAME = 'biz_file_result' + AND INDEX_NAME = 'idx_file_result_task_module_id' +); +SET @sql := IF(@idx_exists = 0, + 'ALTER TABLE biz_file_result ADD INDEX idx_file_result_task_module_id (task_id, module_type, id)', + 'SELECT 1' +); +PREPARE stmt FROM @sql; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +SET @idx_exists := ( + SELECT COUNT(*) + FROM information_schema.STATISTICS + WHERE TABLE_SCHEMA = @db_name + AND TABLE_NAME = 'biz_task_file_job' + AND INDEX_NAME = 'idx_file_job_status_retry_updated' +); +SET @sql := IF(@idx_exists = 0, + 'ALTER TABLE biz_task_file_job ADD INDEX idx_file_job_status_retry_updated (status, retry_count, updated_at)', + 'SELECT 1' +); +PREPARE stmt FROM @sql; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; diff --git a/backend/__pycache__/config.cpython-312.pyc b/backend/__pycache__/config.cpython-312.pyc index 0afb1db6f5722ef818574e29f7577f8be796375a..f3270ad1edcfed546e447e43970cd67e45fc48c3 100644 GIT binary patch delta 87 zcmaFM{eqk8G%qg~0}y2OZ{+&HBq)|qQc_^0uWx8%u4e#*hMOgr1DP~#NJ!1+n#nbv pZzkUbb-N1=5tk(*uS+Cclt{Rcm~vSn^#X(F2Q~&}y~z_Hq)$ diff --git a/backend/blueprints/__pycache__/admin_api.cpython-312.pyc b/backend/blueprints/__pycache__/admin_api.cpython-312.pyc index e207bee874a8e42f49fe6b60e20cd249f6deb65a..603f21e6e0519f1b2335f2844c4c15d694cbcc7d 100644 GIT binary patch delta 21896 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 delta 23142 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 total or not rows: - break - group_page += 1 - all_items.sort(key=lambda item: item.get('id') or 0, reverse=True) - total = len(all_items) - offset = (page - 1) * page_size - return jsonify({ - 'success': True, - 'items': [_format_shop_manage_item(item) for item in all_items[offset:offset + page_size]], - 'total': total, - 'page': page, - 'page_size': page_size, - }) - params = {'page': page, 'pageSize': page_size} if current_row and current_row.get('id'): params['operatorId'] = current_row.get('id') diff --git a/backend/config.py b/backend/config.py index 4e042f5..ccb1747 100644 --- a/backend/config.py +++ b/backend/config.py @@ -23,9 +23,9 @@ bucket_path = "nanri-image/" file_url_pre = f"https://{bucket}.oss-cn-hangzhou.aliyuncs.com/" import os -# backend_java_base_url = os.environ.get('BACKEND_JAVA_BASE_URL', 'http://127.0.0.1:18080').rstrip('/') +backend_java_base_url = os.environ.get('BACKEND_JAVA_BASE_URL', 'http://127.0.0.1:18080').rstrip('/') # backend_java_base_url = os.environ.get('BACKEND_JAVA_BASE_URL', 'http://8.136.19.173:18080').rstrip('/') -backend_java_base_url = os.environ.get('BACKEND_JAVA_BASE_URL', 'http://47.111.163.154:18080').rstrip('/') +# backend_java_base_url = os.environ.get('BACKEND_JAVA_BASE_URL', 'http://47.111.163.154:18080').rstrip('/') os.environ['OSS_ACCESS_KEY_ID'] = accessKeyId os.environ['OSS_ACCESS_KEY_SECRET'] = accessKeySecret os.environ['SECRET_KEY'] = "ddffc7c1d02121d9554d7b080b2511b6" diff --git a/backend/utils/__pycache__/auth.cpython-312.pyc b/backend/utils/__pycache__/auth.cpython-312.pyc index f8172f67ddca9266ff28dfa131c5f0f34e6fba1a..4638aec3e92df46a922f5dbc0093c4b975808a42 100644 GIT binary patch delta 2062 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 f>O#Tx@Cg)YkMU@34WTJe7aXzycm diff --git a/backend/utils/__pycache__/db.cpython-312.pyc b/backend/utils/__pycache__/db.cpython-312.pyc index 7b0e8ace4856a93b43747b263bab1ed37729d453..036fe34ceb48a13a75a58cdd4eb84809e28288b5 100644 GIT binary patch delta 925 zcmccbJl9qIG%qg~0}v$N`IvcFnt|alhyw#GP{!xHiRuRR?2HU4j47-+thpRfoM1K^ zkj)jv4Q8_g**sCaj0~xaS#mG~5OkIzf(>J(@T9V@W`gjd*a+#XhiPMANa0Q8glps= zq!CFUUn)0T9~TMw_)~dTyTV+`lOoW<5G9x*2qcA4gn*=QC6h8kDxV5N5hyG;qS#Y7 zqc~H5A(O%##T&`c(^<|?!5qm@&ZsFoxrouZp79n}Ze?*{PJBjwafv3JS6ZA}1my8S z#Rn0W2yhGz4)+gofeE-chB!Jo2D@%{W71}{(RH(mDJaU% zN=+__NiIq(&q*!PEyyWN&&-QSN=(j9%}a?XEy>I&j!8+UHYa=$F-(ZF{DtLP^-4o0?5V#18P zpLOyW8D)Nah-PHq5dLJrz{03?Lqhoii|7prIS?rWBqeXiXkKEG{J;s6{8cOnbnPSw SCl!W6D!fi6%!iB_fg}KdGwJ96 delta 669 zcmbR1df!?7G%qg~0}ym5|ulo?XFR2Yhi85vR;v&3M=!0E{c`9zgdSX0xVOK$N&L;@UxgTwuUTy8PIL|hz0gdCk5gI#ZN=O$)WWG3dN$0ufP zE@9GUR1!`~OwLZtOVKYa$;>I%Pf5}%sJz9No>~&0l2jxQ3`oJvx0!8N7-cp~a5RcA z8f~5>e(1w8Sk_BUS{$A zB*4MQ_DM{bk@vGs9wVd7j}L*23>?Cr7+8QxZU_obmzpRwU2dY>0-hDwmj$gFyimo` zE(@9h#YDxYM@@{H9yc-Wf{Mw8q|2g~4Sr~XxtB#%p@KSh82#c MP72J2' + (item.name || item.route_path) + ''; - }).join(''); - } - document.querySelectorAll('#adminTabs .tab').forEach(function (tab) { - tab.onclick = function () { - activateAdminTab(tab.dataset.tab); - }; - }); - var fallbackTab = activeAdminTabName && knownItems.some(function (item) { return item.route_path === activeAdminTabName; }) - ? activeAdminTabName - : knownItems[0].route_path; - activateAdminTab(fallbackTab); - } - function loadAdminMenus(preferredTab) { - if (preferredTab) activeAdminTabName = preferredTab; - fetch('/api/admin/current-user/menus') - .then(function (r) { return r.json(); }) - .then(function (res) { - if (!res.success) { - if (adminTabsEl) adminTabsEl.innerHTML = '
鑿滃崟鍔犺浇澶辫触
'; - hideAllAdminPanels(); - return; - } - renderAdminTabs(res.items || []); - }) - .catch(function () { - if (adminTabsEl) adminTabsEl.innerHTML = '
鑿滃崟鍔犺浇澶辫触
'; - hideAllAdminPanels(); - }); - } - hideAllAdminPanels(); - - // ========== 鐢ㄦ埛绠$悊 ========== - var userPage = 1, userPageSize = 15; - var currentUserId = null; - var currentUserRole = 'admin'; - var currentUserUsername = ''; - var adminsList = []; - function roleLabel(role) { - if (role === 'super_admin') return '瓒呯骇绠$悊鍛?; - if (role === 'admin') return '绠$悊鍛?; - return '鏅€氳处鍙?; - } - function buildUserListQuery(page) { - var q = 'page=' + (page || 1) + '&page_size=' + userPageSize; - var kw = (document.getElementById('searchUsername').value || '').trim(); - if (kw) q += '&username=' + encodeURIComponent(kw); - var cby = document.getElementById('filterCreatedBy').value; - if (cby) q += '&created_by_id=' + encodeURIComponent(cby); - return q; - } - function loadUsers(page) { - userPage = page || 1; - fetch('/api/admin/users?' + buildUserListQuery(userPage)) - .then(function (r) { return r.json(); }) - .then(function (res) { - var tbody = document.getElementById('userListBody'); - if (!res.success) { - tbody.innerHTML = '鍔犺浇澶辫触: ' + (res.error || '') + ''; - return; - } - currentUserId = res.current_user_id || null; - currentUserRole = res.current_user_role || 'admin'; - currentUserUsername = (res.current_user_username || '').trim(); - adminsList = res.admins || []; - var items = res.items || []; - if (items.length === 0) { - tbody.innerHTML = '鏆傛棤鐢ㄦ埛'; - } else { - tbody.innerHTML = items.map(function (u) { - return '' + u.id + '' + (u.username || '') + '' + - roleLabel(u.role || 'normal') + '' + (u.creator_username || '-') + '' + (u.created_at || '') + '' + - ' ' + - '' + - ''; - }).join(''); - } - renderPagination('userPagination', res.total, res.page, res.page_size, loadUsers); - bindUserActions(); - updateCreateFormByRole(); - updateUserFilterByRole(); - }) - .catch(function () { - document.getElementById('userListBody').innerHTML = '璇锋眰澶辫触'; - }); - } - function updateUserFilterByRole() { - var grp = document.getElementById('filterCreatedByGroup'); - var sel = document.getElementById('filterCreatedBy'); - if (currentUserRole === 'super_admin') { - grp.style.display = 'block'; - var cur = sel.value; - sel.innerHTML = ''; - adminsList.forEach(function (a) { - var opt = document.createElement('option'); - opt.value = a.id; - opt.textContent = a.username; - sel.appendChild(opt); - }); - sel.value = cur || ''; - } else { - grp.style.display = 'none'; - } - } - - function updateDedupeTotalDataAccess() { - var tab = document.querySelector('.tab[data-tab="dedupe-total-data"]'); - var panel = document.getElementById('panel-dedupe-total-data'); - var canUse = currentUserRole === 'super_admin' || (currentUserRole === 'admin' && currentUserUsername === ''); - if (tab) tab.style.display = canUse ? '' : 'none'; - if (panel) panel.style.display = canUse ? '' : 'none'; - if (!canUse && tab && tab.classList.contains('active')) { - tab.classList.remove('active'); - if (panel) panel.classList.remove('active'); - var usersTab = document.querySelector('.tab[data-tab="users"]'); - var usersPanel = document.getElementById('panel-users'); - if (usersTab) usersTab.classList.add('active'); - if (usersPanel) usersPanel.classList.add('active'); - } - } - function updateShopManageAccess() { - var tab = document.querySelector('.tab[data-tab="shop-manage"]'); - var panel = document.getElementById('panel-shop-manage'); - if (!tab || !panel) return; - var canUse = currentUserRole === 'super_admin' || - !!currentUserAdminPermissionKeys['admin_shop_manage'] || - !!currentUserAdminPermissionRoutes['shop-manage']; - tab.style.display = canUse ? '' : 'none'; - panel.style.display = canUse ? '' : 'none'; - if (!canUse && tab.classList.contains('active')) { - tab.classList.remove('active'); - panel.classList.remove('active'); - var usersTab = document.querySelector('.tab[data-tab="users"]'); - var usersPanel = document.getElementById('panel-users'); - if (usersTab) usersTab.classList.add('active'); - if (usersPanel) usersPanel.classList.add('active'); - } - } - function loadCurrentUserAdminPermissions() { - currentUserAdminPermissionKeys = {}; - currentUserAdminPermissionRoutes = {}; - if (!currentUserId || currentUserRole === 'super_admin') { - updateShopManageAccess(); - return; - } - fetch('/api/admin/user/' + currentUserId + '/column-permissions?menu_type=admin') - .then(function (r) { return r.json(); }) - .then(function (res) { - if (!res.success) { - updateShopManageAccess(); - return; - } - (res.items || []).forEach(function (item) { - var columnKey = (item.column_key || '').trim(); - var routePath = (item.route_path || '').trim(); - if (columnKey) currentUserAdminPermissionKeys[columnKey] = true; - if (routePath) currentUserAdminPermissionRoutes[routePath] = true; - }); - updateShopManageAccess(); - }) - .catch(function () { - updateShopManageAccess(); - }); - } - function applyTabAccess(tabName, canUse) { - var tab = document.querySelector('.tab[data-tab="' + tabName + '"]'); - var panel = document.getElementById('panel-' + tabName); - if (tab) tab.style.display = canUse ? '' : 'none'; - if (panel) panel.style.display = canUse ? '' : 'none'; - if (!canUse && tab && tab.classList.contains('active')) { - tab.classList.remove('active'); - if (panel) panel.classList.remove('active'); - var usersTab = document.querySelector('.tab[data-tab="users"]'); - var usersPanel = document.getElementById('panel-users'); - if (usersTab) usersTab.classList.add('active'); - if (usersPanel) usersPanel.classList.add('active'); - } - } - function hasAdminTabAccess(tabName) { - var config = ADMIN_TAB_ACCESS_CONFIG[tabName]; - if (!config) return true; - if (currentUserRole === 'super_admin') return true; - if (config.superAdminOnly) return false; - return !!currentUserAdminPermissionKeys[config.columnKey] || - !!currentUserAdminPermissionRoutes[config.routePath]; - } - function refreshAdminTabAccess() { - Object.keys(ADMIN_TAB_ACCESS_CONFIG).forEach(function (tabName) { - applyTabAccess(tabName, hasAdminTabAccess(tabName)); - }); - } - function loadCurrentUserAdminPermissions() { - currentUserAdminPermissionKeys = {}; - currentUserAdminPermissionRoutes = {}; - if (!currentUserId || currentUserRole === 'super_admin') { - refreshAdminTabAccess(); - return; - } - fetch('/api/admin/user/' + currentUserId + '/column-permissions?menu_type=admin') - .then(function (r) { return r.json(); }) - .then(function (res) { - if (!res.success) { - refreshAdminTabAccess(); - return; - } - (res.items || []).forEach(function (item) { - var columnKey = (item.column_key || '').trim(); - var routePath = (item.route_path || '').trim(); - if (columnKey) currentUserAdminPermissionKeys[columnKey] = true; - if (routePath) currentUserAdminPermissionRoutes[routePath] = true; - }); - refreshAdminTabAccess(); - }) - .catch(function () { - refreshAdminTabAccess(); - }); - } - var allColumnsList = []; - function loadColumnsForPermission() { - fetch('/api/admin/columns?menu_type=admin') - .then(function (r) { return r.json(); }) - .then(function (res) { - if (!res.success) return; - allColumnsList = res.items || []; - renderColumnPermissionWrap('createColumnPermissionWrap'); - renderColumnPermissionWrap('editColumnPermissionWrap'); - var selCreate = document.getElementById('createColumnPermissionWrap'); - var selEdit = document.getElementById('editColumnPermissionWrap'); - if (selCreate && !selCreate._colCardsBound) { - selCreate._colCardsBound = true; - selCreate.onchange = function () { renderColumnCards('createColumnPermissionWrap'); }; - } - if (selEdit && !selEdit._colCardsBound) { - selEdit._colCardsBound = true; - selEdit.onchange = function () { renderColumnCards('editColumnPermissionWrap'); }; - } - }) - .catch(function () { }); - } - var columnCardsContainerMap = { createColumnPermissionWrap: 'createColumnCards', editColumnPermissionWrap: 'editColumnCards' }; - function renderColumnCards(selectId) { - var sel = document.getElementById(selectId); - var cardsId = columnCardsContainerMap[selectId]; - var cardsEl = cardsId ? document.getElementById(cardsId) : null; - if (!sel || sel.tagName !== 'SELECT' || !cardsEl) return; - cardsEl.innerHTML = ''; - for (var i = 0; i < sel.options.length; i++) { - var opt = sel.options[i]; - if (opt.disabled || !opt.selected) continue; - var card = document.createElement('span'); - card.className = 'column-permission-card'; - card.textContent = opt.textContent; - var btn = document.createElement('button'); - btn.type = 'button'; - btn.className = 'col-card-remove'; - btn.setAttribute('aria-label', '绉婚櫎'); - btn.textContent = '脳'; - (function (option, seldId) { - btn.onclick = function () { - option.selected = false; - renderColumnCards(seldId); - }; - })(opt, selectId); - card.appendChild(btn); - cardsEl.appendChild(card); - } - } - function renderColumnPermissionWrap(wrapId) { - var sel = document.getElementById(wrapId); - if (!sel || sel.tagName !== 'SELECT') return; - sel.innerHTML = ''; - allColumnsList.forEach(function (c) { - var opt = document.createElement('option'); - opt.value = c.id; - opt.textContent = c.name + ' (' + c.column_key + ')'; - sel.appendChild(opt); - }); - if (allColumnsList.length === 0) { - var opt = document.createElement('option'); - opt.disabled = true; - opt.textContent = '鏆傛棤鑿滃崟锛岃鍏堝湪鈥滄爮鐩潈闄愰厤缃€濅腑鏂板鑿滃崟'; - sel.appendChild(opt); - } - renderColumnCards(wrapId); - } - function getSelectedColumnIds(wrapId) { - var sel = document.getElementById(wrapId); - if (!sel || sel.tagName !== 'SELECT') return []; - var ids = []; - for (var i = 0; i < sel.options.length; i++) { - if (sel.options[i].selected) { - var v = parseInt(sel.options[i].value, 10); - if (!isNaN(v)) ids.push(v); - } - } - return ids; - } - function setColumnPermissionCheckboxes(wrapId, columnIds) { - var sel = document.getElementById(wrapId); - if (!sel || sel.tagName !== 'SELECT') return; - var set = {}; - (columnIds || []).forEach(function (id) { set[id] = true; }); - for (var i = 0; i < sel.options.length; i++) { - var opt = sel.options[i]; - if (opt.disabled) continue; - opt.selected = set[parseInt(opt.value, 10)] || false; - } - renderColumnCards(wrapId); - } - function updateCreateFormByRole() { - var roleSel = document.getElementById('createRole'); - var optAdmin = document.getElementById('optAdmin'); - var formCreatedBy = document.getElementById('formGroupCreatedBy'); - var selCreatedBy = document.getElementById('createCreatedBy'); - if (currentUserRole === 'super_admin') { - if (optAdmin) optAdmin.style.display = ''; - formCreatedBy.style.display = (roleSel.value === 'normal') ? 'block' : 'none'; - selCreatedBy.innerHTML = ''; - (res.items || []).forEach(function (u) { - var opt = document.createElement('option'); - opt.value = u.id; - opt.textContent = u.username + ' (' + roleLabel(u.role || 'normal') + ')'; - sel.appendChild(opt); - }); - sel.value = cur || ''; - }); - } - var shopManageGroupUsers = []; - function refreshShopManageGroupUserSelect(selectedUserId) { - var sel = document.getElementById('shopManageGroupUserSelect'); - if (!sel) return; - var opts = ['']; - shopManageGroupUsers.forEach(function (u) { - opts.push(''); - }); - sel.innerHTML = opts.join(''); - if (selectedUserId != null && selectedUserId !== '') { - sel.value = String(selectedUserId); - } - syncShopManageGroupNameWithUser(); - } - function syncShopManageGroupNameWithUser() { - var sel = document.getElementById('shopManageGroupUserSelect'); - var input = document.getElementById('shopManageGroupInput'); - if (!sel || !input) return; - var selectedUser = null; - shopManageGroupUsers.some(function (u) { - if (String(u.id) === String(sel.value || '')) { - selectedUser = u; - return true; - } - return false; - }); - input.value = selectedUser ? (selectedUser.username || '') : ''; - } - function loadUserOptions() { - return fetch('/api/admin/users?page=1&page_size=999') - .then(function (r) { return r.json(); }) - .then(function (res) { - var items = res.items || []; - shopManageGroupUsers = items.map(function (u) { - return { id: u.id, username: u.username || '', role: u.role || 'normal' }; - }); - refreshShopManageGroupUserSelect(document.getElementById('shopManageGroupUserSelect') ? document.getElementById('shopManageGroupUserSelect').value : ''); - var sel = document.getElementById('filterUser'); - var cur = sel.value; - sel.innerHTML = ''; - items.forEach(function (u) { - var opt = document.createElement('option'); - opt.value = u.id; - opt.textContent = u.username + ' (' + roleLabel(u.role || 'normal') + ')'; - sel.appendChild(opt); - }); - sel.value = cur || ''; - }); - } - document.getElementById('btnFilterHistory').onclick = function () { loadHistory(1); }; - - // ========== 鏁版嵁鍘婚噸鎬绘暟鎹?========== - var dedupeTotalDataPage = 1, dedupeTotalDataPageSize = 15; - function buildDedupeTotalDataQuery(page) { - var q = 'page=' + (page || 1) + '&page_size=' + dedupeTotalDataPageSize; - var keyword = (document.getElementById('searchDedupeTotalData').value || '').trim(); - if (keyword) q += '&keyword=' + encodeURIComponent(keyword); - return q; - } - function loadDedupeTotalData(page) { - dedupeTotalDataPage = page || 1; - fetch('/api/admin/dedupe-total-data?' + buildDedupeTotalDataQuery(dedupeTotalDataPage)) - .then(function (r) { return r.json(); }) - .then(function (res) { - var tbody = document.getElementById('dedupeTotalDataListBody'); - if (!res.success) { - tbody.innerHTML = '鍔犺浇澶辫触: ' + (res.error || '') + ''; - return; - } - var items = res.items || []; - if (items.length === 0) { - tbody.innerHTML = '鏆傛棤鎬绘暟鎹?/td>'; - } else { - tbody.innerHTML = items.map(function (item) { - return '' + item.id + '' + (item.data_value || '') + '' + (item.created_at || '') + '' + - ' ' + - '' + - ''; - }).join(''); - } - renderPagination('dedupeTotalDataPagination', res.total, res.page, res.page_size, loadDedupeTotalData); - bindDedupeTotalDataActions(); - }) - .catch(function () { - document.getElementById('dedupeTotalDataListBody').innerHTML = '璇锋眰澶辫触'; - }); - } - function bindDedupeTotalDataActions() { - document.querySelectorAll('[data-dedupe-total-edit]').forEach(function (btn) { - btn.onclick = function () { - document.getElementById('editDedupeTotalDataId').value = btn.dataset.dedupeTotalEdit || ''; - document.getElementById('editDedupeTotalDataValue').value = (btn.dataset.value || '').replace(/"/g, '"'); - document.getElementById('msgEditDedupeTotalData').textContent = ''; - document.getElementById('msgEditDedupeTotalData').className = 'msg'; - document.getElementById('editDedupeTotalDataModal').classList.add('show'); - }; - }); - document.querySelectorAll('[data-dedupe-total-delete]').forEach(function (btn) { - btn.onclick = function () { - var value = (btn.dataset.value || '').replace(/"/g, '"'); - if (!confirm('纭畾鍒犻櫎鎬绘暟鎹€? + value + '鈥濆悧锛?)) return; - fetch('/api/admin/dedupe-total-data/' + btn.dataset.dedupeTotalDelete, { method: 'DELETE' }) - .then(function (r) { return r.json(); }) - .then(function (res) { - if (res.success) { loadDedupeTotalData(dedupeTotalDataPage); } - else { alert(res.error || '鍒犻櫎澶辫触'); } - }); - }; - }); - } - document.getElementById('btnSearchDedupeTotalData').onclick = function () { loadDedupeTotalData(1); }; - var dedupeImportPollTimer = null; - var dedupeDeleteImportPollTimer = null; - function stopDedupeImportProgress() { - if (dedupeImportPollTimer) { - clearInterval(dedupeImportPollTimer); - dedupeImportPollTimer = null; - } - } - function stopDedupeDeleteImportProgress() { - if (dedupeDeleteImportPollTimer) { - clearInterval(dedupeDeleteImportPollTimer); - dedupeDeleteImportPollTimer = null; - } - } - function setDedupeImportProgress(percent, text) { - var wrap = document.getElementById('dedupeTotalDataProgressWrap'); - var fill = document.getElementById('dedupeTotalDataProgressFill'); - var textEl = document.getElementById('dedupeTotalDataProgressText'); - wrap.style.display = 'block'; - fill.style.width = Math.max(0, Math.min(100, percent || 0)) + '%'; - textEl.textContent = text || ''; - } - function setDedupeDeleteImportProgress(percent, text) { - var wrap = document.getElementById('dedupeTotalDataDeleteProgressWrap'); - var fill = document.getElementById('dedupeTotalDataDeleteProgressFill'); - var textEl = document.getElementById('dedupeTotalDataDeleteProgressText'); - wrap.style.display = 'block'; - fill.style.width = Math.max(0, Math.min(100, percent || 0)) + '%'; - textEl.textContent = text || ''; - } - function pollDedupeImport(importId) { - stopDedupeImportProgress(); - function tick() { - fetch('/api/admin/dedupe-total-data/import/' + encodeURIComponent(importId)) - .then(function (r) { return r.json(); }) - .then(function (res) { - if (!res.success) { - stopDedupeImportProgress(); - document.getElementById('msgDedupeTotalData').textContent = res.error || '鏌ヨ瀵煎叆杩涘害澶辫触'; - document.getElementById('msgDedupeTotalData').className = 'msg err'; - return; - } - var progress = res.progress || {}; - var totalRows = progress.total_rows || 0; - var processedRows = progress.processed_rows || 0; - var percent = totalRows > 0 ? Math.round(processedRows * 100 / totalRows) : 0; - setDedupeImportProgress(percent, '澶勭悊涓細宸插鐞?' + processedRows + ' / ' + totalRows + '锛屾柊澧?' + (progress.inserted_count || 0) + '锛岃烦杩?' + (progress.skipped_count || 0)); - if (progress.status === 'success') { - stopDedupeImportProgress(); - document.getElementById('msgDedupeTotalData').textContent = '瀵煎叆鎴愬姛锛氭€昏鏁?' + (progress.total_rows || 0) + '锛孉SIN 鏁伴噺 ' + (progress.asin_count || 0) + '锛屾柊澧?' + (progress.inserted_count || 0) + '锛岃烦杩?' + (progress.skipped_count || 0); - document.getElementById('msgDedupeTotalData').className = 'msg ok'; - loadDedupeTotalData(1); - } else if (progress.status === 'failed') { - stopDedupeImportProgress(); - document.getElementById('msgDedupeTotalData').textContent = progress.error_message || '瀵煎叆澶辫触'; - document.getElementById('msgDedupeTotalData').className = 'msg err'; - } - }) - .catch(function () { - stopDedupeImportProgress(); - document.getElementById('msgDedupeTotalData').textContent = '鏌ヨ瀵煎叆杩涘害澶辫触'; - document.getElementById('msgDedupeTotalData').className = 'msg err'; - }); - } - tick(); - dedupeImportPollTimer = setInterval(tick, 1000); - } - function pollDedupeDeleteImport(importId) { - stopDedupeDeleteImportProgress(); - function tick() { - fetch('/api/admin/dedupe-total-data/delete-import/' + encodeURIComponent(importId)) - .then(function (r) { return r.json(); }) - .then(function (res) { - if (!res.success) { - stopDedupeDeleteImportProgress(); - document.getElementById('msgDeleteDedupeTotalData').textContent = res.error || '鏌ヨ鍒犻櫎杩涘害澶辫触'; - document.getElementById('msgDeleteDedupeTotalData').className = 'msg err'; - return; - } - var progress = res.progress || {}; - var totalRows = progress.total_rows || 0; - var processedRows = progress.processed_rows || 0; - var percent = totalRows > 0 ? Math.round(processedRows * 100 / totalRows) : 0; - setDedupeDeleteImportProgress(percent, '澶勭悊涓細宸插鐞?' + processedRows + ' / ' + totalRows + '锛屽垹闄?' + (progress.deleted_count || 0) + '锛岃烦杩?' + (progress.skipped_count || 0)); - if (progress.status === 'success') { - stopDedupeDeleteImportProgress(); - document.getElementById('msgDeleteDedupeTotalData').textContent = '鍒犻櫎鎴愬姛锛氭€昏鏁?' + (progress.total_rows || 0) + '锛孉SIN 鏁伴噺 ' + (progress.asin_count || 0) + '锛屽垹闄?' + (progress.deleted_count || 0) + '锛岃烦杩?' + (progress.skipped_count || 0); - document.getElementById('msgDeleteDedupeTotalData').className = 'msg ok'; - loadDedupeTotalData(1); - } else if (progress.status === 'failed') { - stopDedupeDeleteImportProgress(); - document.getElementById('msgDeleteDedupeTotalData').textContent = progress.error_message || '鍒犻櫎澶辫触'; - document.getElementById('msgDeleteDedupeTotalData').className = 'msg err'; - } - }) - .catch(function () { - stopDedupeDeleteImportProgress(); - document.getElementById('msgDeleteDedupeTotalData').textContent = '鏌ヨ鍒犻櫎杩涘害澶辫触'; - document.getElementById('msgDeleteDedupeTotalData').className = 'msg err'; - }); - } - tick(); - dedupeDeleteImportPollTimer = setInterval(tick, 1000); - } - - document.getElementById('btnAddDedupeTotalData').onclick = function () { - var fileInput = document.getElementById('dedupeTotalDataFile'); - var msgEl = document.getElementById('msgDedupeTotalData'); - msgEl.textContent = ''; - msgEl.className = 'msg'; - stopDedupeImportProgress(); - document.getElementById('dedupeTotalDataProgressWrap').style.display = 'none'; - if (!fileInput.files || fileInput.files.length === 0) { - msgEl.textContent = '璇烽€夋嫨 Excel 鏂囦欢'; - msgEl.classList.add('err'); - return; - } - var file = fileInput.files[0]; - var lowerName = (file.name || '').toLowerCase(); - if (!(lowerName.endsWith('.xlsx') || lowerName.endsWith('.xls'))) { - msgEl.textContent = '浠呮敮鎸?.xlsx 鎴?.xls 鏂囦欢'; - msgEl.classList.add('err'); - return; - } - var formData = new FormData(); - formData.append('file', file); - var xhr = new XMLHttpRequest(); - xhr.open('POST', '/api/admin/dedupe-total-data/import', true); - xhr.upload.onprogress = function (event) { - if (event.lengthComputable) { - var percent = Math.round(event.loaded * 100 / event.total); - setDedupeImportProgress(percent, '涓婁紶涓細' + percent + '%'); - } - }; - xhr.onreadystatechange = function () { - if (xhr.readyState !== 4) return; - if (xhr.status < 200 || xhr.status >= 300) { - msgEl.textContent = '璇锋眰澶辫触'; - msgEl.className = 'msg err'; - return; - } - var res; - try { res = JSON.parse(xhr.responseText || '{}'); } catch (e) { res = { success: false, error: '杩斿洖鏍煎紡閿欒' }; } - if (!res.success) { - msgEl.textContent = res.error || '瀵煎叆澶辫触'; - msgEl.className = 'msg err'; - return; - } - setDedupeImportProgress(100, '涓婁紶瀹屾垚锛屽悗绔鐞嗕腑...'); - pollDedupeImport(res.import_id); - fileInput.value = ''; - }; - xhr.onerror = function () { - msgEl.textContent = '璇锋眰澶辫触'; - msgEl.className = 'msg err'; - }; - xhr.send(formData); - }; - document.getElementById('btnDeleteImportDedupeTotalData').onclick = function () { - var fileInput = document.getElementById('dedupeTotalDataDeleteFile'); - var msgEl = document.getElementById('msgDeleteDedupeTotalData'); - msgEl.textContent = ''; - msgEl.className = 'msg'; - stopDedupeDeleteImportProgress(); - document.getElementById('dedupeTotalDataDeleteProgressWrap').style.display = 'none'; - if (!fileInput.files || fileInput.files.length === 0) { - msgEl.textContent = '璇烽€夋嫨 Excel 鏂囦欢'; - msgEl.classList.add('err'); - return; - } - var file = fileInput.files[0]; - var lowerName = (file.name || '').toLowerCase(); - if (!(lowerName.endsWith('.xlsx') || lowerName.endsWith('.xls'))) { - msgEl.textContent = '浠呮敮鎸?.xlsx 鎴?.xls 鏂囦欢'; - msgEl.classList.add('err'); - return; - } - if (!confirm('纭畾鎸?Excel 涓殑 ASIN 鎵归噺鍒犻櫎鍖归厤鐨勬€绘暟鎹悧锛?)) { - return; - } - var formData = new FormData(); - formData.append('file', file); - var xhr = new XMLHttpRequest(); - xhr.open('POST', '/api/admin/dedupe-total-data/delete-import', true); - xhr.upload.onprogress = function (event) { - if (event.lengthComputable) { - var percent = Math.round(event.loaded * 100 / event.total); - setDedupeDeleteImportProgress(percent, '涓婁紶涓細' + percent + '%'); - } - }; - xhr.onreadystatechange = function () { - if (xhr.readyState !== 4) return; - if (xhr.status < 200 || xhr.status >= 300) { - msgEl.textContent = '璇锋眰澶辫触'; - msgEl.className = 'msg err'; - return; - } - var res; - try { res = JSON.parse(xhr.responseText || '{}'); } catch (e) { res = { success: false, error: '杩斿洖鏍煎紡閿欒' }; } - if (!res.success) { - msgEl.textContent = res.error || '鍒犻櫎澶辫触'; - msgEl.className = 'msg err'; - return; - } - setDedupeDeleteImportProgress(100, '涓婁紶瀹屾垚锛屽悗绔鐞嗕腑...'); - pollDedupeDeleteImport(res.import_id); - fileInput.value = ''; - }; - xhr.onerror = function () { - msgEl.textContent = '璇锋眰澶辫触'; - msgEl.className = 'msg err'; - }; - xhr.send(formData); - }; - document.getElementById('btnSaveDedupeTotalData').onclick = function () { - var itemId = document.getElementById('editDedupeTotalDataId').value; - var value = (document.getElementById('editDedupeTotalDataValue').value || '').trim(); - var msgEl = document.getElementById('msgEditDedupeTotalData'); - msgEl.textContent = ''; - msgEl.className = 'msg'; - if (!value) { - msgEl.textContent = '璇峰~鍐欐€绘暟鎹€?; - msgEl.classList.add('err'); - return; - } - fetch('/api/admin/dedupe-total-data/' + itemId, { - method: 'PUT', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ data_value: value }) - }) - .then(function (r) { return r.json(); }) - .then(function (res) { - if (res.success) { - document.getElementById('editDedupeTotalDataModal').classList.remove('show'); - loadDedupeTotalData(dedupeTotalDataPage); - } else { - msgEl.textContent = res.error || '淇濆瓨澶辫触'; - msgEl.classList.add('err'); - } - }); - }; - document.getElementById('btnCloseEditDedupeTotalData').onclick = function () { - document.getElementById('editDedupeTotalDataModal').classList.remove('show'); - }; - - // ========== 搴楅摵瀵嗛挜绠$悊 ========== - var shopKeyPage = 1, shopKeyPageSize = 15; - function buildShopKeyQuery(page) { - return 'page=' + (page || 1) + '&page_size=' + shopKeyPageSize; - } - function loadShopKeys(page) { - shopKeyPage = page || 1; - fetch('/api/admin/shop-keys?' + buildShopKeyQuery(shopKeyPage)) - .then(function (r) { return r.json(); }) - .then(function (res) { - var tbody = document.getElementById('shopKeyListBody'); - if (!res.success) { - tbody.innerHTML = '鍔犺浇澶辫触: ' + (res.error || '') + ''; - return; - } - var items = res.items || []; - if (items.length === 0) { - tbody.innerHTML = '鏆傛棤搴楅摵瀵嗛挜'; - } else { - tbody.innerHTML = items.map(function (item, index) { - var rowNo = (shopKeyPage - 1) * shopKeyPageSize + index + 1; - return '' + rowNo + '' + (item.ziniao_account_name || '') + '' + (item.ziniao_token || '') + '' + (item.created_at || '') + '' + (item.updated_at || '') + '' + - ' ' + - '' + - ''; - }).join(''); - } - renderPagination('shopKeyPagination', res.total, res.page, res.page_size, loadShopKeys); - bindShopKeyActions(); - }) - .catch(function () { - document.getElementById('shopKeyListBody').innerHTML = '璇锋眰澶辫触'; - }); - } - function bindShopKeyActions() { - document.querySelectorAll('[data-shop-key-edit]').forEach(function (btn) { - btn.onclick = function () { - var item = {}; - try { item = JSON.parse((btn.dataset.shopKey || '').replace(/"/g, '"')); } catch (e) { item = {}; } - document.getElementById('editShopKeyId').value = item.id || ''; - document.getElementById('editShopKeyZiniaoAccountName').value = item.ziniao_account_name || ''; - document.getElementById('editShopKeyZiniaoToken').value = item.ziniao_token || ''; - document.getElementById('msgEditShopKey').textContent = ''; - document.getElementById('msgEditShopKey').className = 'msg'; - document.getElementById('editShopKeyModal').classList.add('show'); - }; - }); - document.querySelectorAll('[data-shop-key-delete]').forEach(function (btn) { - btn.onclick = function () { - var name = (btn.dataset.ziniaoAccountName || '').replace(/"/g, '"'); - if (!confirm('纭畾鍒犻櫎搴楅摵瀵嗛挜鈥? + name + '鈥濆悧锛?)) return; - fetch('/api/admin/shop-key/' + btn.dataset.shopKeyDelete, { method: 'DELETE' }) - .then(function (r) { return r.json(); }) - .then(function (res) { - if (res.success) { loadShopKeys(shopKeyPage); } - else { alert(res.error || '鍒犻櫎澶辫触'); } - }); - }; - }); - } - document.getElementById('btnCreateShopKey').onclick = function () { - var ziniaoAccountName = (document.getElementById('shopKeyZiniaoAccountName').value || '').trim(); - var ziniaoToken = (document.getElementById('shopKeyZiniaoToken').value || '').trim(); - var msgEl = document.getElementById('msgShopKey'); - msgEl.textContent = ''; - msgEl.className = 'msg'; - if (!ziniaoAccountName || !ziniaoToken) { - msgEl.textContent = '璇峰畬鏁村~鍐欑传楦熻处鍙峰悕绉般€佺传楦熶护鐗?; - msgEl.classList.add('err'); - return; - } - fetch('/api/admin/shop-key', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - ziniao_account_name: ziniaoAccountName, - ziniao_token: ziniaoToken - }) - }) - .then(function (r) { return r.json(); }) - .then(function (res) { - if (res.success) { - document.getElementById('shopKeyZiniaoAccountName').value = ''; - document.getElementById('shopKeyZiniaoToken').value = ''; - msgEl.textContent = res.msg || '鍒涘缓鎴愬姛'; - msgEl.className = 'msg ok'; - loadShopKeys(1); - } else { - msgEl.textContent = res.error || '鍒涘缓澶辫触'; - msgEl.className = 'msg err'; - } - }) - .catch(function () { - msgEl.textContent = '璇锋眰澶辫触'; - msgEl.className = 'msg err'; - }); - }; - document.getElementById('btnSaveShopKey').onclick = function () { - var itemId = document.getElementById('editShopKeyId').value; - var ziniaoAccountName = (document.getElementById('editShopKeyZiniaoAccountName').value || '').trim(); - var ziniaoToken = (document.getElementById('editShopKeyZiniaoToken').value || '').trim(); - var msgEl = document.getElementById('msgEditShopKey'); - msgEl.textContent = ''; - msgEl.className = 'msg'; - if (!ziniaoAccountName || !ziniaoToken) { - msgEl.textContent = '璇峰畬鏁村~鍐欑传楦熻处鍙峰悕绉般€佺传楦熶护鐗?; - msgEl.classList.add('err'); - return; - } - fetch('/api/admin/shop-key/' + itemId, { - method: 'PUT', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - ziniao_account_name: ziniaoAccountName, - ziniao_token: ziniaoToken - }) - }) - .then(function (r) { return r.json(); }) - .then(function (res) { - if (res.success) { - document.getElementById('editShopKeyModal').classList.remove('show'); - loadShopKeys(shopKeyPage); - } else { - msgEl.textContent = res.error || '淇濆瓨澶辫触'; - msgEl.classList.add('err'); - } - }) - .catch(function () { - msgEl.textContent = '璇锋眰澶辫触'; - msgEl.classList.add('err'); - }); - }; - document.getElementById('btnCloseEditShopKey').onclick = function () { - document.getElementById('editShopKeyModal').classList.remove('show'); - }; - - // ========== 搴楅摵绠$悊 ========== - var shopManagePage = 1, shopManagePageSize = 15; - var shopManageGroups = []; - - function buildShopManageQuery(page) { - var query = 'page=' + (page || 1) + '&page_size=' + shopManagePageSize; - var groupId = (document.getElementById('shopManageFilterGroupId').value || '').trim(); - var shopName = (document.getElementById('shopManageFilterShopName').value || '').trim(); - if (groupId) query += '&group_id=' + encodeURIComponent(groupId); - if (shopName) query += '&shop_name=' + encodeURIComponent(shopName); - return query; - } - - function refreshShopGroupSelects(selectedCreateId, selectedEditId) { - var createSel = document.getElementById('shopManageGroupSelect'); - var editSel = document.getElementById('editShopManageGroupSelect'); - var filterSel = document.getElementById('shopManageFilterGroupId'); - var skipCreateSel = document.getElementById('skipPriceAsinGroupSelect'); - var skipFilterSel = document.getElementById('skipPriceAsinFilterGroupId'); - var chooseSkipShopSel = document.getElementById('chooseSkipPriceAsinShopGroupId'); - var selectedFilterId = filterSel ? filterSel.value : ''; - var selectedSkipCreateId = skipCreateSel ? skipCreateSel.value : ''; - var selectedSkipFilterId = skipFilterSel ? skipFilterSel.value : ''; - var selectedChooseSkipShopId = chooseSkipShopSel ? chooseSkipShopSel.value : ''; - var createOpts = ['']; - var filterOpts = ['']; - shopManageGroups.forEach(function (g) { - var option = ''; - createOpts.push(option); - filterOpts.push(option); - }); - createSel.innerHTML = createOpts.join(''); - editSel.innerHTML = createOpts.join(''); - if (skipCreateSel) skipCreateSel.innerHTML = createOpts.join(''); - if (filterSel) filterSel.innerHTML = filterOpts.join(''); - if (skipFilterSel) skipFilterSel.innerHTML = filterOpts.join(''); - if (chooseSkipShopSel) chooseSkipShopSel.innerHTML = filterOpts.join(''); - if (selectedCreateId != null) createSel.value = String(selectedCreateId); - if (selectedEditId != null) editSel.value = String(selectedEditId); - if (filterSel && selectedFilterId) filterSel.value = selectedFilterId; - if (skipCreateSel && selectedSkipCreateId) skipCreateSel.value = selectedSkipCreateId; - if (skipFilterSel && selectedSkipFilterId) skipFilterSel.value = selectedSkipFilterId; - if (chooseSkipShopSel && selectedChooseSkipShopId) chooseSkipShopSel.value = selectedChooseSkipShopId; - } - - function loadShopManageGroups(selectedCreateId, selectedEditId) { - return fetch('/api/admin/shop-manage-groups') - .then(function (r) { return r.json(); }) - .then(function (res) { - if (!res.success) throw new Error(res.error || '鍔犺浇鍒嗙粍澶辫触'); - shopManageGroups = res.items || []; - refreshShopGroupSelects(selectedCreateId, selectedEditId); - return shopManageGroups; - }) - .catch(function () { - shopManageGroups = []; - refreshShopGroupSelects(); - return []; - }); - } - - function openShopManageGroupModal() { - document.getElementById('shopManageGroupEditId').value = ''; - document.getElementById('shopManageGroupInput').value = ''; - document.getElementById('msgShopManageGroup').textContent = ''; - document.getElementById('msgShopManageGroup').className = 'msg'; - loadShopManageGroups().then(function () { - renderShopManageGroupRows(); - document.getElementById('shopManageGroupModal').classList.add('show'); - }); - } - - function renderShopManageGroupRows() { - var tbody = document.getElementById('shopManageGroupListBody'); - if (!shopManageGroups.length) { - tbody.innerHTML = '鏆傛棤鍒嗙粍'; - return; - } - tbody.innerHTML = shopManageGroups.map(function (item, index) { - return '' + (index + 1) + '' + (item.group_name || '') + '' + (item.created_at || '') + '' + (item.updated_at || '') + '' + - ' ' + - '' + - ''; - }).join(''); - - document.querySelectorAll('[data-shop-group-edit]').forEach(function (btn) { - btn.onclick = function () { - document.getElementById('shopManageGroupEditId').value = btn.dataset.shopGroupEdit; - document.getElementById('shopManageGroupInput').value = (btn.dataset.shopGroupName || '').replace(/"/g, '"'); - }; - }); - - document.querySelectorAll('[data-shop-group-delete]').forEach(function (btn) { - btn.onclick = function () { - var gid = btn.dataset.shopGroupDelete; - var gname = (btn.dataset.shopGroupName || '').replace(/"/g, '"'); - if (!confirm('纭畾鍒犻櫎鍒嗙粍鈥? + gname + '鈥濆悧锛?)) return; - fetch('/api/admin/shop-manage-group/' + gid, { method: 'DELETE' }) - .then(function (r) { return r.json(); }) - .then(function (res) { - if (!res.success) { - alert(res.error || '鍒犻櫎澶辫触'); - return; - } - loadShopManageGroups().then(function () { - renderShopManageGroupRows(); - loadShopManage(shopManagePage); - loadSkipPriceAsin(skipPriceAsinPage); - }); - }); - }; - }); - } - - function loadShopManage(page) { - shopManagePage = page || 1; - fetch('/api/admin/shop-manages?' + buildShopManageQuery(shopManagePage)) - .then(function (r) { return r.json(); }) - .then(function (res) { - var tbody = document.getElementById('shopManageListBody'); - if (!res.success) { - tbody.innerHTML = '鍔犺浇澶辫触: ' + (res.error || '') + ''; - return; - } - var items = res.items || []; - if (items.length === 0) { - tbody.innerHTML = '鏆傛棤搴楅摵'; - } else { - tbody.innerHTML = items.map(function (item, index) { - var rowNo = (shopManagePage - 1) * shopManagePageSize + index + 1; - return '' + rowNo + '' + (item.group_name || '') + '' + (item.shop_name || '') + '' + (item.mall_name || '') + '' + (item.account || '') + '' + (item.password || '') + '' + (item.created_at || '') + '' + (item.updated_at || '') + '' + - ' ' + - '' + - ''; - }).join(''); - } - renderPagination('shopManagePagination', res.total, res.page, res.page_size, loadShopManage); - bindShopManageActions(); - }) - .catch(function () { - document.getElementById('shopManageListBody').innerHTML = '璇锋眰澶辫触'; - }); - } - - function bindShopManageActions() { - document.querySelectorAll('[data-shop-manage-edit]').forEach(function (btn) { - btn.onclick = function () { - var item = {}; - try { item = JSON.parse((btn.dataset.shopManage || '').replace(/"/g, '"')); } catch (e) { item = {}; } - document.getElementById('editShopManageId').value = item.id || ''; - document.getElementById('editShopManageShopName').value = item.shop_name || ''; - document.getElementById('editShopManageMallName').value = item.mall_name || ''; - document.getElementById('editShopManageAccount').value = item.account || ''; - document.getElementById('editShopManagePassword').value = item.password || ''; - document.getElementById('msgEditShopManage').textContent = ''; - document.getElementById('msgEditShopManage').className = 'msg'; - loadShopManageGroups(null, item.group_id).then(function () { - document.getElementById('editShopManageModal').classList.add('show'); - }); - }; - }); - document.querySelectorAll('[data-shop-manage-delete]').forEach(function (btn) { - btn.onclick = function () { - var name = (btn.dataset.shopManageName || '').replace(/"/g, '"'); - if (!confirm('纭畾鍒犻櫎搴楅摵鈥? + name + '鈥濆悧锛?)) return; - fetch('/api/admin/shop-manage/' + btn.dataset.shopManageDelete, { method: 'DELETE' }) - .then(function (r) { return r.json(); }) - .then(function (res) { - if (res.success) { loadShopManage(shopManagePage); } - else { alert(res.error || '鍒犻櫎澶辫触'); } - }); - }; - }); - } - - document.getElementById('btnManageShopGroups').onclick = openShopManageGroupModal; - document.getElementById('btnManageShopGroupsFromEdit').onclick = openShopManageGroupModal; - document.getElementById('btnSearchShopManage').onclick = function () { - loadShopManage(1); - }; - document.getElementById('shopManageFilterShopName').addEventListener('keydown', function (e) { - if (e.key === 'Enter') { - e.preventDefault(); - loadShopManage(1); - } - }); - document.getElementById('btnCloseShopManageGroupModal').onclick = function () { - document.getElementById('shopManageGroupModal').classList.remove('show'); - }; - document.getElementById('btnCancelShopManageGroupEdit').onclick = function () { - document.getElementById('shopManageGroupEditId').value = ''; - document.getElementById('shopManageGroupInput').value = ''; - }; - document.getElementById('btnSaveShopManageGroup').onclick = function () { - var editId = (document.getElementById('shopManageGroupEditId').value || '').trim(); - var groupName = (document.getElementById('shopManageGroupInput').value || '').trim(); - var msgEl = document.getElementById('msgShopManageGroup'); - msgEl.textContent = ''; - msgEl.className = 'msg'; - if (!groupName) { - msgEl.textContent = '璇疯緭鍏ュ垎缁勫悕绉?; - msgEl.className = 'msg err'; - return; - } - var method = editId ? 'PUT' : 'POST'; - var url = editId ? ('/api/admin/shop-manage-group/' + editId) : '/api/admin/shop-manage-group'; - fetch(url, { - method: method, - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ group_name: groupName }) - }) - .then(function (r) { return r.json(); }) - .then(function (res) { - if (!res.success) { - msgEl.textContent = res.error || '淇濆瓨澶辫触'; - msgEl.className = 'msg err'; - return; - } - document.getElementById('shopManageGroupEditId').value = ''; - document.getElementById('shopManageGroupInput').value = ''; - msgEl.textContent = res.msg || '淇濆瓨鎴愬姛'; - msgEl.className = 'msg ok'; - loadShopManageGroups().then(function () { - renderShopManageGroupRows(); - loadShopManage(shopManagePage); - loadSkipPriceAsin(skipPriceAsinPage); - }); - }) - .catch(function () { - msgEl.textContent = '璇锋眰澶辫触'; - msgEl.className = 'msg err'; - }); - }; - - function refreshShopGroupSelects(selectedCreateId, selectedEditId) { - var createSel = document.getElementById('shopManageGroupSelect'); - var editSel = document.getElementById('editShopManageGroupSelect'); - var filterSel = document.getElementById('shopManageFilterGroupId'); - var skipCreateSel = document.getElementById('skipPriceAsinGroupSelect'); - var skipFilterSel = document.getElementById('skipPriceAsinFilterGroupId'); - var chooseSkipShopSel = document.getElementById('chooseSkipPriceAsinShopGroupId'); - var selectedFilterId = filterSel ? filterSel.value : ''; - var selectedSkipCreateId = skipCreateSel ? skipCreateSel.value : ''; - var selectedSkipFilterId = skipFilterSel ? skipFilterSel.value : ''; - var selectedChooseSkipShopId = chooseSkipShopSel ? chooseSkipShopSel.value : ''; - var createOpts = ['']; - var filterOpts = ['']; - shopManageGroups.forEach(function (g) { - var option = ''; - createOpts.push(option); - filterOpts.push(option); - }); - createSel.innerHTML = createOpts.join(''); - editSel.innerHTML = createOpts.join(''); - if (skipCreateSel) skipCreateSel.innerHTML = createOpts.join(''); - if (filterSel) filterSel.innerHTML = filterOpts.join(''); - if (skipFilterSel) skipFilterSel.innerHTML = filterOpts.join(''); - if (chooseSkipShopSel) chooseSkipShopSel.innerHTML = filterOpts.join(''); - if (selectedCreateId != null) createSel.value = String(selectedCreateId); - if (selectedEditId != null) editSel.value = String(selectedEditId); - if (filterSel && selectedFilterId) filterSel.value = selectedFilterId; - if (skipCreateSel && selectedSkipCreateId) skipCreateSel.value = selectedSkipCreateId; - if (skipFilterSel && selectedSkipFilterId) skipFilterSel.value = selectedSkipFilterId; - if (chooseSkipShopSel && selectedChooseSkipShopId) chooseSkipShopSel.value = selectedChooseSkipShopId; - } - - function loadShopManageGroups(selectedCreateId, selectedEditId) { - return fetch('/api/admin/shop-manage-groups') - .then(function (r) { return r.json(); }) - .then(function (res) { - if (!res.success) throw new Error(res.error || '鍔犺浇鍒嗙粍澶辫触'); - shopManageGroups = res.items || []; - refreshShopGroupSelects(selectedCreateId, selectedEditId); - return shopManageGroups; - }) - .catch(function () { - shopManageGroups = []; - refreshShopGroupSelects(); - return []; - }); - } - - function resetShopManageGroupForm() { - document.getElementById('shopManageGroupEditId').value = ''; - document.getElementById('shopManageGroupInput').value = ''; - document.getElementById('msgShopManageGroup').textContent = ''; - document.getElementById('msgShopManageGroup').className = 'msg'; - refreshShopManageGroupUserSelect(''); - } - - function openShopManageGroupModal() { - resetShopManageGroupForm(); - loadUserOptions() - .then(function () { return loadShopManageGroups(); }) - .then(function () { - renderShopManageGroupRows(); - document.getElementById('shopManageGroupModal').classList.add('show'); - }); - } - - function renderShopManageGroupRows() { - var tbody = document.getElementById('shopManageGroupListBody'); - if (!shopManageGroups.length) { - tbody.innerHTML = '鏆傛棤鍒嗙粍'; - return; - } - tbody.innerHTML = shopManageGroups.map(function (item, index) { - return '' + (index + 1) + '' + (item.group_name || '') + '' + (item.username || '') + '' + (item.created_at || '') + '' + (item.updated_at || '') + '' + - ' ' + - '' + - ''; - }).join(''); - - document.querySelectorAll('[data-shop-group-edit]').forEach(function (btn) { - btn.onclick = function () { - document.getElementById('shopManageGroupEditId').value = btn.dataset.shopGroupEdit; - refreshShopManageGroupUserSelect(btn.dataset.shopGroupUserId || ''); - }; - }); - - document.querySelectorAll('[data-shop-group-delete]').forEach(function (btn) { - btn.onclick = function () { - var gid = btn.dataset.shopGroupDelete; - var gname = (btn.dataset.shopGroupName || '').replace(/"/g, '"'); - if (!confirm('纭畾鍒犻櫎鍒嗙粍鈥? + gname + '鈥濆悧锛?)) return; - fetch('/api/admin/shop-manage-group/' + gid, { method: 'DELETE' }) - .then(function (r) { return r.json(); }) - .then(function (res) { - if (!res.success) { - alert(res.error || '鍒犻櫎澶辫触'); - return; - } - loadShopManageGroups().then(function () { - renderShopManageGroupRows(); - loadShopManage(shopManagePage); - loadSkipPriceAsin(skipPriceAsinPage); - }); - }); - }; - }); - } - - document.getElementById('shopManageGroupUserSelect').onchange = syncShopManageGroupNameWithUser; - document.getElementById('btnManageShopGroups').onclick = openShopManageGroupModal; - document.getElementById('btnManageShopGroupsFromEdit').onclick = openShopManageGroupModal; - document.getElementById('btnManageSkipPriceAsinGroups').onclick = openShopManageGroupModal; - document.getElementById('btnCloseShopManageGroupModal').onclick = function () { - document.getElementById('shopManageGroupModal').classList.remove('show'); - }; - document.getElementById('btnCancelShopManageGroupEdit').onclick = resetShopManageGroupForm; - document.getElementById('btnSaveShopManageGroup').onclick = function () { - var editId = (document.getElementById('shopManageGroupEditId').value || '').trim(); - var groupName = (document.getElementById('shopManageGroupInput').value || '').trim(); - var userId = (document.getElementById('shopManageGroupUserSelect').value || '').trim(); - var msgEl = document.getElementById('msgShopManageGroup'); - msgEl.textContent = ''; - msgEl.className = 'msg'; - if (!userId || !groupName) { - msgEl.textContent = '璇烽€夋嫨鍏宠仈鐢ㄦ埛'; - msgEl.className = 'msg err'; - return; - } - var method = editId ? 'PUT' : 'POST'; - var url = editId ? ('/api/admin/shop-manage-group/' + editId) : '/api/admin/shop-manage-group'; - fetch(url, { - method: method, - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ group_name: groupName, user_id: Number(userId) }) - }) - .then(function (r) { return r.json(); }) - .then(function (res) { - if (!res.success) { - msgEl.textContent = res.error || '淇濆瓨澶辫触'; - msgEl.className = 'msg err'; - return; - } - resetShopManageGroupForm(); - msgEl.textContent = res.msg || '淇濆瓨鎴愬姛'; - msgEl.className = 'msg ok'; - loadShopManageGroups().then(function () { - renderShopManageGroupRows(); - loadShopManage(shopManagePage); - loadSkipPriceAsin(skipPriceAsinPage); - }); - }) - .catch(function () { - msgEl.textContent = '璇锋眰澶辫触'; - msgEl.className = 'msg err'; - }); - }; - - document.getElementById('btnCreateShopManage').onclick = function () { - var groupId = (document.getElementById('shopManageGroupSelect').value || '').trim(); - var shopName = (document.getElementById('shopManageShopName').value || '').trim(); - var mallName = (document.getElementById('shopManageMallName').value || '').trim(); - var account = (document.getElementById('shopManageAccount').value || '').trim(); - var password = (document.getElementById('shopManagePassword').value || '').trim(); - var msgEl = document.getElementById('msgShopManage'); - msgEl.textContent = ''; - msgEl.className = 'msg'; - if (!groupId || !shopName || !mallName || !account || !password) { - msgEl.textContent = '璇峰畬鏁村~鍐欏垎缁勩€佸簵閾哄悕銆佸簵閾哄晢鍩庡悕銆佽处鍙枫€佸瘑鐮?; - msgEl.classList.add('err'); - return; - } - fetch('/api/admin/shop-manage', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ group_id: Number(groupId), shop_name: shopName, mall_name: mallName, account: account, password: password }) - }) - .then(function (r) { return r.json(); }) - .then(function (res) { - if (res.success) { - document.getElementById('shopManageGroupSelect').value = ''; - document.getElementById('shopManageShopName').value = ''; - document.getElementById('shopManageMallName').value = ''; - document.getElementById('shopManageAccount').value = ''; - document.getElementById('shopManagePassword').value = ''; - msgEl.textContent = res.msg || '鍒涘缓鎴愬姛'; - msgEl.className = 'msg ok'; - loadShopManage(1); - } else { - msgEl.textContent = res.error || '鍒涘缓澶辫触'; - msgEl.className = 'msg err'; - } - }) - .catch(function () { - msgEl.textContent = '璇锋眰澶辫触'; - msgEl.className = 'msg err'; - }); - }; - - document.getElementById('btnSaveShopManage').onclick = function () { - var itemId = document.getElementById('editShopManageId').value; - var groupId = (document.getElementById('editShopManageGroupSelect').value || '').trim(); - var shopName = (document.getElementById('editShopManageShopName').value || '').trim(); - var mallName = (document.getElementById('editShopManageMallName').value || '').trim(); - var account = (document.getElementById('editShopManageAccount').value || '').trim(); - var password = (document.getElementById('editShopManagePassword').value || '').trim(); - var msgEl = document.getElementById('msgEditShopManage'); - msgEl.textContent = ''; - msgEl.className = 'msg'; - if (!groupId || !shopName || !mallName || !account || !password) { - msgEl.textContent = '璇峰畬鏁村~鍐欏垎缁勩€佸簵閾哄悕銆佸簵閾哄晢鍩庡悕銆佽处鍙枫€佸瘑鐮?; - msgEl.classList.add('err'); - return; - } - fetch('/api/admin/shop-manage/' + itemId, { - method: 'PUT', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ group_id: Number(groupId), shop_name: shopName, mall_name: mallName, account: account, password: password }) - }) - .then(function (r) { return r.json(); }) - .then(function (res) { - if (res.success) { - document.getElementById('editShopManageModal').classList.remove('show'); - loadShopManage(shopManagePage); - } else { - msgEl.textContent = res.error || '淇濆瓨澶辫触'; - msgEl.classList.add('err'); - } - }) - .catch(function () { - msgEl.textContent = '璇锋眰澶辫触'; - msgEl.classList.add('err'); - }); - }; - document.getElementById('btnCloseEditShopManage').onclick = function () { - document.getElementById('editShopManageModal').classList.remove('show'); - }; - - // ========== 鐗堟湰绠$悊 ========== - // ========== 璺宠繃璺熶环 ASIN ========== - var skipPriceAsinPage = 1, skipPriceAsinPageSize = 15; - var chooseSkipPriceAsinShopPage = 1, chooseSkipPriceAsinShopPageSize = 10; - var skipPriceCountryColumns = [ - { code: 'DE', field: 'asin_de', label: '寰峰浗' }, - { code: 'UK', field: 'asin_uk', label: '鑻卞浗' }, - { code: 'FR', field: 'asin_fr', label: '娉曞浗' }, - { code: 'IT', field: 'asin_it', label: '鎰忓ぇ鍒? }, - { code: 'ES', field: 'asin_es', label: '瑗跨彮鐗? } - ]; - function buildChooseSkipPriceAsinShopQuery(page) { - var query = 'page=' + (page || 1) + '&page_size=' + chooseSkipPriceAsinShopPageSize; - var groupId = (document.getElementById('chooseSkipPriceAsinShopGroupId').value || '').trim(); - var shopName = (document.getElementById('chooseSkipPriceAsinShopKeyword').value || '').trim(); - if (groupId) query += '&group_id=' + encodeURIComponent(groupId); - if (shopName) query += '&shop_name=' + encodeURIComponent(shopName); - return query; - } - function loadChooseSkipPriceAsinShops(page) { - chooseSkipPriceAsinShopPage = page || 1; - fetch('/api/admin/shop-manages?' + buildChooseSkipPriceAsinShopQuery(chooseSkipPriceAsinShopPage)) - .then(function (r) { return r.json(); }) - .then(function (res) { - var tbody = document.getElementById('chooseSkipPriceAsinShopListBody'); - if (!res.success) { - tbody.innerHTML = '鍔犺浇澶辫触: ' + (res.error || '') + ''; - return; - } - var items = res.items || []; - if (!items.length) { - tbody.innerHTML = '鏆傛棤搴楅摵'; - } else { - tbody.innerHTML = items.map(function (item, index) { - var rowNo = (chooseSkipPriceAsinShopPage - 1) * chooseSkipPriceAsinShopPageSize + index + 1; - return '' + rowNo + '' + (item.group_name || '') + '' + (item.shop_name || '') + '' + (item.mall_name || '') + '' + (item.account || '') + '' + - '' + - ''; - }).join(''); - } - renderPagination('chooseSkipPriceAsinShopPagination', res.total, res.page, res.page_size, loadChooseSkipPriceAsinShops); - bindChooseSkipPriceAsinShopActions(); - }) - .catch(function () { - document.getElementById('chooseSkipPriceAsinShopListBody').innerHTML = '璇锋眰澶辫触'; - }); - } - function bindChooseSkipPriceAsinShopActions() { - document.querySelectorAll('[data-choose-skip-price-asin-shop]').forEach(function (btn) { - btn.onclick = function () { - document.getElementById('skipPriceAsinShopName').value = (btn.dataset.shopName || '').replace(/"/g, '"'); - if (!document.getElementById('skipPriceAsinGroupSelect').value && btn.dataset.groupId) { - document.getElementById('skipPriceAsinGroupSelect').value = btn.dataset.groupId; - } - document.getElementById('chooseSkipPriceAsinShopModal').classList.remove('show'); - }; - }); - } - function openChooseSkipPriceAsinShopModal() { - var currentGroupId = (document.getElementById('skipPriceAsinGroupSelect').value || '').trim(); - if (currentGroupId) { - document.getElementById('chooseSkipPriceAsinShopGroupId').value = currentGroupId; - } - document.getElementById('chooseSkipPriceAsinShopKeyword').value = (document.getElementById('skipPriceAsinShopName').value || '').trim(); - document.getElementById('chooseSkipPriceAsinShopModal').classList.add('show'); - loadChooseSkipPriceAsinShops(1); - } - function setupSkipPriceAsinShopPicker() { - var input = document.getElementById('skipPriceAsinShopName'); - var button = document.getElementById('btnChooseSkipPriceAsinShop'); - if (!input || !button) return; - var inputParent = input.parentNode; - if (inputParent && inputParent.style && inputParent.style.display === 'flex') return; - var legacyWrap = button.parentNode; - var wrapper = document.createElement('div'); - wrapper.style.display = 'flex'; - wrapper.style.gap = '8px'; - wrapper.style.alignItems = 'center'; - input.parentNode.insertBefore(wrapper, input); - wrapper.appendChild(input); - wrapper.appendChild(button); - button.style.whiteSpace = 'nowrap'; - button.style.marginTop = '0'; - if (legacyWrap && legacyWrap !== wrapper) { - legacyWrap.style.display = 'none'; - } - } - function getSelectedSkipPriceCountries() { - return Array.from(document.getElementById('skipPriceAsinCountries').selectedOptions).map(function (option) { - return option.value; - }); - } - function renderSkipPriceAsinInputs() { - var container = document.getElementById('skipPriceAsinInputs'); - var selectedCountries = getSelectedSkipPriceCountries(); - var existingValues = {}; - container.querySelectorAll('[data-skip-price-country-input]').forEach(function (input) { - existingValues[input.getAttribute('data-skip-price-country-input')] = input.value; - }); - if (!selectedCountries.length) { - container.innerHTML = '
璇烽€夋嫨鍥藉鍚庤緭鍏?ASIN
'; - return; - } - container.innerHTML = selectedCountries.map(function (countryCode) { - var country = skipPriceCountryColumns.find(function (item) { return item.code === countryCode; }); - var label = country ? country.label : countryCode; - var value = existingValues[countryCode] || ''; - return '
' + - '' + label + '' + - '' + - '
'; - }).join(''); - } - function collectSkipPriceAsinMappings(countries) { - var mappings = {}; - for (var i = 0; i < countries.length; i++) { - var countryCode = countries[i]; - var input = document.querySelector('[data-skip-price-country-input="' + countryCode + '"]'); - var asin = input ? (input.value || '').trim().toUpperCase() : ''; - if (!asin) { - var country = skipPriceCountryColumns.find(function (item) { return item.code === countryCode; }); - var label = country ? country.label : countryCode; - throw new Error(label + ' ASIN 涓嶈兘涓虹┖'); - } - mappings[countryCode] = asin; - } - return mappings; - } - function buildSkipPriceAsinQuery(page) { - var query = 'page=' + (page || 1) + '&page_size=' + skipPriceAsinPageSize; - var groupId = (document.getElementById('skipPriceAsinFilterGroupId').value || '').trim(); - var shopName = (document.getElementById('skipPriceAsinFilterShopName').value || '').trim(); - var asin = (document.getElementById('skipPriceAsinFilterAsin').value || '').trim(); - if (groupId) query += '&group_id=' + encodeURIComponent(groupId); - if (shopName) query += '&shop_name=' + encodeURIComponent(shopName); - if (asin) query += '&asin=' + encodeURIComponent(asin); - return query; - } - function renderSkipPriceAsinCell(item, country) { - var value = item[country.field] || ''; - if (!value) return '-'; - return '
' + - '' + value + '' + - '' + - '' + - '
'; - } - function bindSkipPriceAsinActions() { - document.querySelectorAll('[data-skip-price-asin-edit]').forEach(function (btn) { - btn.onclick = function () { - var shopName = (btn.dataset.shopName || '').replace(/"/g, '"'); - var countryCode = btn.dataset.country || ''; - var currentAsin = (btn.dataset.asin || '').replace(/"/g, '"'); - var nextAsin = prompt('璇疯緭鍏ュ簵閾衡€? + shopName + '鈥濆湪 ' + countryCode + ' 鐨?ASIN', currentAsin); - if (nextAsin === null) return; - nextAsin = (nextAsin || '').trim(); - if (!nextAsin) { - alert('ASIN 涓嶈兘涓虹┖'); - return; - } - fetch('/api/admin/skip-price-asin/' + btn.dataset.skipPriceAsinEdit + '/country/' + countryCode, { - method: 'PUT', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ asin: nextAsin }) - }) - .then(function (r) { return r.json(); }) - .then(function (res) { - if (res.success) loadSkipPriceAsin(skipPriceAsinPage); - else alert(res.error || '淇濆瓨澶辫触'); - }); - }; - }); - document.querySelectorAll('[data-skip-price-asin-delete]').forEach(function (btn) { - btn.onclick = function () { - var shopName = (btn.dataset.shopName || '').replace(/"/g, '"'); - var countryCode = btn.dataset.country || ''; - if (!confirm('纭畾鍒犻櫎搴楅摵鈥? + shopName + '鈥濆湪 ' + countryCode + ' 鐨?ASIN 鍚楋紵')) return; - fetch('/api/admin/skip-price-asin/' + btn.dataset.skipPriceAsinDelete + '/country/' + countryCode, { - method: 'DELETE' - }) - .then(function (r) { return r.json(); }) - .then(function (res) { - if (res.success) loadSkipPriceAsin(skipPriceAsinPage); - else alert(res.error || '鍒犻櫎澶辫触'); - }); - }; - }); - } - function loadSkipPriceAsin(page) { - skipPriceAsinPage = page || 1; - fetch('/api/admin/skip-price-asins?' + buildSkipPriceAsinQuery(skipPriceAsinPage)) - .then(function (r) { return r.json(); }) - .then(function (res) { - var tbody = document.getElementById('skipPriceAsinListBody'); - if (!res.success) { - tbody.innerHTML = '鍔犺浇澶辫触: ' + (res.error || '') + ''; - return; - } - var items = res.items || []; - if (items.length === 0) { - tbody.innerHTML = '鏆傛棤鏁版嵁'; - } else { - tbody.innerHTML = items.map(function (item, index) { - var rowNo = (skipPriceAsinPage - 1) * skipPriceAsinPageSize + index + 1; - return '' + rowNo + '' + (item.group_name || '') + '' + (item.shop_name || '') + '' + - skipPriceCountryColumns.map(function (country) { - return '' + renderSkipPriceAsinCell(item, country) + ''; - }).join('') + - ''; - }).join(''); - } - renderPagination('skipPriceAsinPagination', res.total, res.page, res.page_size, loadSkipPriceAsin); - bindSkipPriceAsinActions(); - }) - .catch(function () { - document.getElementById('skipPriceAsinListBody').innerHTML = '璇锋眰澶辫触'; - }); - } - document.getElementById('btnManageSkipPriceAsinGroups').onclick = openShopManageGroupModal; - document.getElementById('btnChooseSkipPriceAsinShop').onclick = openChooseSkipPriceAsinShopModal; - document.getElementById('btnSearchChooseSkipPriceAsinShop').onclick = function () { - loadChooseSkipPriceAsinShops(1); - }; - document.getElementById('chooseSkipPriceAsinShopGroupId').onchange = function () { - loadChooseSkipPriceAsinShops(1); - }; - document.getElementById('chooseSkipPriceAsinShopKeyword').addEventListener('keydown', function (e) { - if (e.key === 'Enter') { - e.preventDefault(); - loadChooseSkipPriceAsinShops(1); - } - }); - document.getElementById('btnCloseChooseSkipPriceAsinShopModal').onclick = function () { - document.getElementById('chooseSkipPriceAsinShopModal').classList.remove('show'); - }; - document.getElementById('skipPriceAsinCountries').addEventListener('change', renderSkipPriceAsinInputs); - document.getElementById('btnSearchSkipPriceAsin').onclick = function () { - loadSkipPriceAsin(1); - }; - document.getElementById('skipPriceAsinFilterShopName').addEventListener('keydown', function (e) { - if (e.key === 'Enter') { - e.preventDefault(); - loadSkipPriceAsin(1); - } - }); - document.getElementById('skipPriceAsinFilterAsin').addEventListener('keydown', function (e) { - if (e.key === 'Enter') { - e.preventDefault(); - loadSkipPriceAsin(1); - } - }); - document.getElementById('btnCreateSkipPriceAsin').onclick = function () { - var groupId = (document.getElementById('skipPriceAsinGroupSelect').value || '').trim(); - var shopName = (document.getElementById('skipPriceAsinShopName').value || '').trim(); - var countries = getSelectedSkipPriceCountries(); - var msgEl = document.getElementById('msgSkipPriceAsin'); - msgEl.textContent = ''; - msgEl.className = 'msg'; - if (!groupId || !shopName || !countries.length) { - msgEl.textContent = '璇峰畬鏁村~鍐欏垎缁勩€佸簵閾哄悕銆佸浗瀹跺拰 ASIN'; - msgEl.className = 'msg err'; - return; - } - var asinMappings = {}; - try { - asinMappings = collectSkipPriceAsinMappings(countries); - } catch (err) { - msgEl.textContent = err.message || '璇疯緭鍏?ASIN'; - msgEl.className = 'msg err'; - return; - } - var fallbackAsin = ''; - Object.keys(asinMappings).some(function (countryCode) { - fallbackAsin = asinMappings[countryCode] || ''; - return !!fallbackAsin; - }); - fetch('/api/admin/skip-price-asin', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - group_id: Number(groupId), - shop_name: shopName, - countries: countries, - asin: fallbackAsin, - asin_mappings: asinMappings - }) - }) - .then(function (r) { return r.json(); }) - .then(function (res) { - if (!res.success) { - msgEl.textContent = res.error || '淇濆瓨澶辫触'; - msgEl.className = 'msg err'; - return; - } - document.getElementById('skipPriceAsinGroupSelect').value = ''; - document.getElementById('skipPriceAsinShopName').value = ''; - Array.from(document.getElementById('skipPriceAsinCountries').options).forEach(function (option) { - option.selected = false; - }); - renderSkipPriceAsinInputs(); - msgEl.textContent = res.msg || '淇濆瓨鎴愬姛'; - msgEl.className = 'msg ok'; - loadSkipPriceAsin(1); - }) - .catch(function () { - msgEl.textContent = '璇锋眰澶辫触'; - msgEl.className = 'msg err'; - }); - }; - setupSkipPriceAsinShopPicker(); - renderSkipPriceAsinInputs(); - function loadVersions() { - fetch('/api/admin/versions') - .then(function (r) { return r.json(); }) - .then(function (res) { - var tbody = document.getElementById('versionListBody'); - if (!res.success) { - tbody.innerHTML = '鍔犺浇澶辫触: ' + (res.error || '') + ''; - return; - } - var items = res.items || []; - if (items.length === 0) { - tbody.innerHTML = '鏆傛棤鐗堟湰璁板綍'; - } else { - tbody.innerHTML = items.map(function (v) { - var url = (v.file_url || '').replace(/"/g, '"'); - return '' + (v.version || '') + '' + url + '' + (v.created_at || '') + '涓嬭浇'; - }).join(''); - } - }) - .catch(function () { - document.getElementById('versionListBody').innerHTML = '璇锋眰澶辫触'; - }); - } - document.getElementById('btnUploadVersion').onclick = function () { - var version = (document.getElementById('versionNumber').value || '').trim(); - var fileInput = document.getElementById('versionZip'); - var msgEl = document.getElementById('msgVersion'); - msgEl.textContent = ''; - msgEl.className = 'msg'; - if (!version) { - msgEl.textContent = '璇峰~鍐欑増鏈彿'; - msgEl.classList.add('err'); - return; - } - if (!fileInput.files || fileInput.files.length === 0) { - msgEl.textContent = '璇烽€夋嫨 zip 鍘嬬缉鍖?; - msgEl.classList.add('err'); - return; - } - var file = fileInput.files[0]; - if (!(file.name || '').toLowerCase().endsWith('.zip')) { - msgEl.textContent = '浠呮敮鎸?.zip 鏍煎紡'; - msgEl.classList.add('err'); - return; - } - var formData = new FormData(); - formData.append('version', version); - formData.append('file', file); - msgEl.textContent = '涓婁紶涓?..'; - msgEl.classList.remove('err', 'ok'); - fetch('/api/admin/version', { - method: 'POST', - body: formData - }) - .then(function (r) { return r.json(); }) - .then(function (res) { - if (res.success) { - msgEl.textContent = '鍙戝竷鎴愬姛銆傜増鏈細' + res.version + '锛岄摼鎺ワ細' + (res.file_url || ''); - msgEl.classList.add('ok'); - document.getElementById('versionNumber').value = ''; - fileInput.value = ''; - loadVersions(); - } else { - msgEl.textContent = res.error || '涓婁紶澶辫触'; - msgEl.classList.add('err'); - } - }) - .catch(function () { - msgEl.textContent = '璇锋眰澶辫触'; - msgEl.classList.add('err'); - }); - }; - - // ========== 鏍忕洰鏉冮檺閰嶇疆 ========== - function loadColumns() { - fetch('/api/admin/columns') - .then(function (r) { return r.json(); }) - .then(function (res) { - var tbody = document.getElementById('columnListBody'); - if (!res.success) { - tbody.innerHTML = '鍔犺浇澶辫触: ' + (res.error || '') + ''; - return; - } - allColumnsList = res.items || []; - if (allColumnsList.length === 0) { - tbody.innerHTML = '鏆傛棤鑿滃崟锛岃鍦ㄤ笂鏂规柊澧?/td>'; - } else { - tbody.innerHTML = allColumnsList.map(function (c) { - return '' + c.id + '' + (c.name || '') + '' + (c.column_key || '') + '' + (c.created_at || '') + '' + - ' ' + - ''; - }).join(''); - } - bindColumnActions(); - }) - .catch(function () { - document.getElementById('columnListBody').innerHTML = '璇锋眰澶辫触'; - }); - } - function bindColumnActions() { - document.querySelectorAll('[data-column-edit]').forEach(function (btn) { - btn.onclick = function () { - document.getElementById('editColumnId').value = btn.dataset.columnEdit || ''; - document.getElementById('editColumnName').value = (btn.dataset.name || '').replace(/"/g, '"'); - document.getElementById('editColumnKey').value = (btn.dataset.key || '').replace(/"/g, '"'); - document.getElementById('msgEditColumn').textContent = ''; - document.getElementById('editColumnModal').classList.add('show'); - }; - }); - document.querySelectorAll('[data-column-delete]').forEach(function (btn) { - btn.onclick = function () { - if (!confirm('纭畾鍒犻櫎鑿滃崟鈥? + (btn.dataset.name || '').replace(/"/g, '"') + '鈥濆悧锛?)) return; - fetch('/api/admin/column/' + btn.dataset.columnDelete, { method: 'DELETE' }) - .then(function (r) { return r.json(); }) - .then(function (res) { - if (res.success) { loadColumns(); loadColumnsForPermission(); loadAdminMenus(getActiveAdminTabName()); } - else { alert(res.error || '鍒犻櫎澶辫触'); } - }); - }; - }); - } - document.getElementById('btnAddColumn').onclick = function () { - var name = (document.getElementById('columnName').value || '').trim(); - var key = (document.getElementById('columnKey').value || '').trim(); - var msgEl = document.getElementById('msgColumn'); - msgEl.textContent = ''; - msgEl.className = 'msg'; - if (!name) { msgEl.textContent = '璇峰~鍐欒彍鍗曞悕绉?; msgEl.classList.add('err'); return; } - if (!key) { - msgEl.textContent = '璇峰~鍐欐爮鐩爣璇?; msgEl.classList.add('err'); return; - } - fetch('/api/admin/column', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ name: name, column_key: key }) - }) - .then(function (r) { return r.json(); }) - .then(function (res) { - if (res.success) { - msgEl.textContent = res.msg || '鏂板鎴愬姛'; - msgEl.classList.add('ok'); - document.getElementById('columnName').value = ''; - document.getElementById('columnKey').value = ''; - loadColumns(); - loadColumnsForPermission(); - loadAdminMenus(getActiveAdminTabName()); - } else { diff --git a/backend/web_source/admin.html b/backend/web_source/admin.html index ccfb20b..04c7c99 100644 --- a/backend/web_source/admin.html +++ b/backend/web_source/admin.html @@ -336,6 +336,178 @@ font-size: 16px; } + #shopManageGroupModal .shop-group-modal { + width: min(1180px, calc(100vw - 48px)); + max-width: none; + max-height: calc(100vh - 48px); + padding: 0; + overflow: hidden; + display: flex; + flex-direction: column; + } + + .shop-group-modal-header { + padding: 20px 24px 14px; + border-bottom: 1px solid #edf0f5; + flex: 0 0 auto; + } + + .shop-group-modal-header h3 { + margin: 0; + } + + .shop-group-editor { + padding: 18px 24px 14px; + display: grid; + grid-template-columns: minmax(180px, 240px) minmax(240px, 320px) minmax(360px, 1fr); + gap: 16px; + align-items: start; + flex: 0 0 auto; + } + + .shop-group-editor .form-group { + margin-bottom: 0; + } + + .shop-group-member-select { + min-height: 128px; + max-height: 180px; + } + + .shop-group-help { + color: #777; + font-size: 12px; + line-height: 1.5; + margin-top: 6px; + } + + .shop-group-action-bar { + padding: 0 24px 12px; + display: flex; + gap: 8px; + justify-content: flex-end; + align-items: center; + flex: 0 0 auto; + } + + #msgShopManageGroup { + margin: 0; + padding: 0 24px 10px; + min-height: 18px; + flex: 0 0 auto; + } + + .shop-group-table-wrap { + margin: 0 24px; + border: 1px solid #edf0f5; + border-radius: 8px; + overflow: auto; + min-height: 220px; + flex: 1 1 auto; + } + + .shop-group-table { + min-width: 1040px; + table-layout: fixed; + } + + .shop-group-table th { + position: sticky; + top: 0; + z-index: 1; + } + + .shop-group-table th, + .shop-group-table td { + vertical-align: top; + } + + .shop-group-table .col-index { + width: 58px; + } + + .shop-group-table .col-name { + width: 130px; + } + + .shop-group-table .col-leader { + width: 110px; + } + + .shop-group-table .col-count { + width: 86px; + } + + .shop-group-table .col-members { + width: auto; + } + + .shop-group-table .col-time { + width: 120px; + } + + .shop-group-table .col-action { + width: 128px; + } + + .shop-group-time-cell, + .shop-group-action-cell { + white-space: nowrap; + } + + .shop-group-name-cell, + .shop-group-leader-cell { + word-break: break-word; + } + + .shop-group-members { + display: flex; + flex-wrap: wrap; + gap: 6px; + max-height: 96px; + overflow-y: auto; + padding-right: 4px; + } + + .shop-group-member-chip { + display: inline-flex; + align-items: center; + max-width: 150px; + padding: 3px 8px; + border-radius: 999px; + background: #eef1ff; + color: #4f63d8; + font-size: 12px; + line-height: 1.4; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + + .shop-group-modal-footer { + padding: 14px 24px 18px; + display: flex; + gap: 8px; + justify-content: flex-end; + border-top: 1px solid #edf0f5; + flex: 0 0 auto; + } + + @media (max-width: 900px) { + #shopManageGroupModal .shop-group-modal { + width: calc(100vw - 24px); + max-height: calc(100vh - 24px); + } + + .shop-group-editor { + grid-template-columns: 1fr; + } + + .shop-group-table-wrap { + margin: 0 16px; + } + } + .column-permission-cards { display: flex; flex-wrap: wrap; @@ -347,6 +519,97 @@ overflow: hidden; } + .multi-select-native { + position: absolute; + opacity: 0; + pointer-events: none; + width: 1px !important; + height: 1px !important; + min-height: 0 !important; + padding: 0 !important; + border: 0 !important; + } + + .multi-select-dropdown { + position: relative; + width: 100%; + } + + .multi-select-trigger { + width: 100%; + min-height: 42px; + padding: 10px 34px 10px 12px; + border: 1px solid #ddd; + border-radius: 6px; + background: #fff; + color: #333; + font-size: 14px; + text-align: left; + cursor: pointer; + position: relative; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + .multi-select-trigger::after { + content: ""; + position: absolute; + right: 12px; + top: 50%; + width: 7px; + height: 7px; + border-right: 1.5px solid #666; + border-bottom: 1.5px solid #666; + transform: translateY(-65%) rotate(45deg); + } + + .multi-select-dropdown.open .multi-select-trigger { + border-color: #667eea; + box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.12); + } + + .multi-select-panel { + display: none; + position: absolute; + left: 0; + right: 0; + top: calc(100% + 6px); + z-index: 30; + background: #fff; + border: 1px solid #ddd; + border-radius: 8px; + box-shadow: 0 12px 28px rgba(0, 0, 0, 0.14); + padding: 6px; + max-height: 220px; + overflow-y: auto; + } + + .multi-select-dropdown.open .multi-select-panel { + display: block; + } + + .multi-select-option { + display: flex; + align-items: center; + gap: 8px; + min-height: 34px; + padding: 7px 8px; + border-radius: 6px; + cursor: pointer; + font-size: 14px; + color: #333; + } + + .multi-select-option:hover { + background: #f4f6ff; + } + + .multi-select-option input { + width: auto; + margin: 0; + } + .column-permission-card { display: inline-flex; align-items: center; @@ -762,7 +1025,7 @@ - @@ -840,7 +1103,7 @@
- @@ -1063,48 +1326,62 @@