From 6af90017d50dce10b43202fc0b680ec9b6f09eba Mon Sep 17 00:00:00 2001 From: jeremygan2021 Date: Mon, 2 Feb 2026 14:07:47 +0800 Subject: [PATCH] =?UTF-8?q?=E5=BF=BD=E8=A7=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 271 ++++++++++++++++-- .../shop/__pycache__/admin.cpython-312.pyc | Bin 7688 -> 8089 bytes .../shop/__pycache__/admin.cpython-313.pyc | Bin 7832 -> 8271 bytes .../shop/__pycache__/models.cpython-312.pyc | Bin 10038 -> 11688 bytes .../shop/__pycache__/models.cpython-313.pyc | Bin 9952 -> 11594 bytes .../__pycache__/serializers.cpython-312.pyc | Bin 5682 -> 6647 bytes .../__pycache__/serializers.cpython-313.pyc | Bin 5970 -> 6995 bytes backend/shop/admin.py | 8 +- .../shop/migrations/0007_productfeature.py | 32 +++ backend/shop/models.py | 21 ++ backend/shop/serializers.py | 20 +- frontend/src/pages/ProductDetail.jsx | 82 ++++-- 12 files changed, 388 insertions(+), 46 deletions(-) create mode 100644 backend/shop/migrations/0007_productfeature.py diff --git a/.gitignore b/.gitignore index c4fc064..4c1ac06 100644 --- a/.gitignore +++ b/.gitignore @@ -1,19 +1,252 @@ -default: - - .DS_Store - - .gitignore - - .git - - .idea - - .vscode - - .cache - - .dart_tool - - build - - coverage - - example/build - - example/.dart_tool - - example/.pub - - example/.flutter-plugins - - example/.flutter-plugins-dependencies - - example/ios/Flutter/flutter_export_environment.sh - - example/ios/Runner.xcworkspace - - "*.mtl" - - "*.obj" +# Django +*.log +*.pot +*.pyc +__pycache__/ +local_settings.py +db.sqlite3 +db.sqlite3-journal +media/ + +# Django 迁移文件 +*/migrations/__pycache__/ +*/migrations/*.pyc + +# Django 静态文件 +staticfiles/ +static/ + +# Python +*.py[cod] +*$py.class +*.so +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# Python 虚拟环境 +venv/ +env/ +ENV/ +env.bak/ +venv.bak/ + +# Node.js +node_modules/ +npm-debug.log* +yarn-debug.log* +yarn-error.log* +package-lock.json +yarn.lock + +# 前端构建文件 +dist/ +build/ +*.map + +# IDE +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# macOS +.DS_Store +.AppleDouble +.LSOverride + +# Windows +Thumbs.db +ehthumbs.db +*.stackdump + +# 大文件和媒体文件 +*.mp4 +*.mp3 +*.avi +*.mov +*.wmv +*.flv +*.mkv +*.wav +*.flac +*.aac +*.wma +*.m4a +*.m4v +*.3gp +*.3g2 +*.asf +*.rm +*.rmvb +*.vob +*.mpg +*.mpeg +*.m2v +*.m4v +*.svi +*.3gpp +*.3gpp2 + +# 图片文件(保留必要的,忽略大图片) +*.psd +*.ai +*.eps +*.raw +*.cr2 +*.nef +*.orf +*.sr2 +*.tiff +*.tif +*.bmp +*.ico +# 保留 PNG、JPG、JPEG、SVG 用于网站显示 +# *.png +# *.jpg +# *.jpeg +# *.svg + +# 3D模型文件(大文件) +*.obj +*.mtl +*.fbx +*.dae +*.3ds +*.max +*.ma +*.mb +*.blend +*.c4d +*.lwo +*.lws +*.skp +*.x3d +*.x3db +*.x3dv +*.wrl +*.wrz +*.ply +*.stl +*.stp +*.step +*.igs +*.iges + +# 压缩文件 +*.zip +*.rar +*.7z +*.tar +*.gz +*.bz2 +*.xz +*.tar.gz +*.tar.bz2 +*.tar.xz +*.tgz +*.tbz2 +*.txz + +# 文档文件(大文件) +*.pdf +*.doc +*.docx +*.xls +*.xlsx +*.ppt +*.pptx +*.odt +*.ods +*.odp + +# 数据库文件 +*.sql +*.sqlite +*.sqlite3 +*.db +*.mdb +*.accdb + +# 备份文件 +*.bak +*.backup +*.old +*.orig +*.tmp +*.temp +*.swp +*.swo + +# 日志文件 +*.log +*.log.* +logs/ + +# 缓存文件 +.cache/ +*.cache +*.tmp + +# 配置文件(敏感信息) +.env +.env.local +.env.development.local +.env.test.local +.env.production.local + +# Docker +.dockerignore + +# Git +.git/ + +# 其他大文件 +*.iso +*.dmg +*.img +*.vmdk +*.vdi +*.vhd +*.vhdx +*.qcow +*.qcow2 +*.ova +*.ovf + +# 前端特定忽略 +frontend/dist/ +frontend/build/ +frontend/node_modules/ + +# 后端特定忽略 +backend/db.sqlite3 +backend/__pycache__/ +backend/*.pyc +backend/media/ +backend/static/ +backend/venv/ +backend/env/ + +# 项目特定的大文件路径 +frontend/public/3d*/ +frontend/public/*.obj +frontend/public/*.mtl +frontend/dist/3d*/ +frontend/dist/*.obj +frontend/dist/*.mtl \ No newline at end of file diff --git a/backend/shop/__pycache__/admin.cpython-312.pyc b/backend/shop/__pycache__/admin.cpython-312.pyc index 5f826e1b5411446fd76b2f442af34e1b408929a0..47f0e0570ec19aa78aba6e3d71be536bc8da0aa2 100644 GIT binary patch delta 2314 zcmZ`)YiLwQ6uxsed-sv-BkyFhiHX@5Z){?!jiQzKpqh$#n6@e|>SoQoiEH;^=kB7B z!p5Q~rL7I8nka%=DbymS)KXi6g#Ifnf}xaz+lm(Qs6SfLYC%d1J?CyVAt5g8w`b1z zzL_~^=FFbiaABkK2ZzHh;HRmvH}q3cpRW6)f{|>DpxCs{r8{@>n3Cv>Z&QQu1cw8rqe(WC+>;_S`UO?+Gez)= zs>z?BFuz$5{Zfxf&d~GbN!9Jqa44#B2}QD`o8zH)Sk*HXRqN!TBk@oys@p=Hv1lL~ zjHtTZFhh}Gx2jtWJHf-cImQ*$#9L+fC8Ns29XOS4j>HrdL+Y_O59(4^NDV8{VBTBn z#waIn;#bQ?I4O)}d3v{wWjPQWyH0H5WoZt4XBv2k&RQ1vQgT)zB(e}DF5j8FbmQ@< z_owdOeDnM zhpDSK#)rO|IQ!1zxzA-YUkU$69*HL8tH5K4CMhjr(pK5FAPK&lVlqJu0TooYgp9M) z_zH3@$gq}t1XNCKKf+1jQEq#0^H@%8?~bv&6}>HEuFCsPUvJYRTj5w%-hCLcG^Q#+ zGO7e%U(|CtGu__F@#?8cAQ-wuoe$RU`amJK)|(k8jl7=5v}!BgO6G#P zG?9;>8HVkzq-JXZTjn!X2Vz#OWSm>`t%j^-CK*>{51e)^WgYODV+$*T-yQc@5Qd!V ze0y;Y-4r``ShpVuhLnI746B-ELT`C_IX9#&P~@s$-LT%(hSGOkKDG-6T}_oONWv=8 z47>|L6Ae{L)Q;m)&yb{wzwgeI9{7^P8Cy z8VlBB&L+ZRkSwT4)x8Y{3#wT!j1~+h>C6)@gvk#+o%m*W{LXt*Ltjo^IWc$rj0Q_Z zCvJZ-aq2cUDQ%6nk(8Qr{OgNTL)WKid(C9g>_*g>+5dsK$rg5!~s9cLh`6l$>xl@1-5EzaOeva((Q0}S9>`1%$McC=d zZ#14tGw(;YZhb2ejN*ge!wr@(%_a;O<(ato@#LA?sW%MQIFEjQ0j_yuHURfLh1)Np zqgA#pTClly=`6_>YvPY6KyVFeOO9t6O-w_%9KI-dGtWWcB!Vu6V%$li)9`|~KIwQP zcrXzT@|nLaH&Luy?G9;imFxBv`q$a4L_*QjlU_?odMk_;H)?FGy5JSitsC(FDiKy2 z`E?}I(P&+W(%49zeV7<6tR5+>ej?!DiAfMkr$w};eb8QdD}z%0ADaOd2><{9 delta 1971 zcmZ`(Yitx%6rQ`=oo;u#JNqbXw`|wlKDO+d8j4ULsT6^>pncegS{h}ubVgj9?v^_( z*rI7s2n2bRQ=%j`f{BSl0w$n=hEaly(VuOiMLwX&SrPD|A0$3Tf0kXQn) zqH6)V7UsCN(4ZI(K**Y4BD7eWVcfbCw%OiaL5+x8xCdX{i!e}(F!k}J>5tCd-*InJv`mlpU!6fnk!^-;`H>M^PKvPRS=WM2;vAv zy)&oJPG0_c>gc=EC%%Z=cr_&KWg-dl#qgXxvz9Uqb~_J{`2<0PAWBe~2l8t#A?y|I z`8!8iZ~AISp1)c0lw-S`hXW z-(^*ha@C4P-`8IlbG0(jD4_$U-4(1GilntH0DkAYAu$y4E8FkBFHMb-slx3LAV&nF_mo)yO;T`yj3$hZp>p zh_-IUpE1A2W=Q!LvmJ2IpTNwo{4Z9fD2Z`HU@O}5s@s5$p%}dC3&--+?IaEj$%t@M z^52l6*QDsF%Ii|>7ki9HE@Vq?SwyOsEAT>~mAPO%Q15*B?R*>D2_(!`w}88}h7Cb| z=|n>WGX`kHrjL9!_07cOr9(59znnR>XYQ~WGhs%jE`B<-?;_3@mB(A4E*MxreT;kg zqX&!#)WM`V`MiTdO)wlxY@}SX0nKEYs$_)`bXPLjqoy>iM^V*e5?i$i zy&6MUWB$T*L?ze+e+I9Z14TBa__xIGkU!V6`!IHhvU6x_!q$Dua_c$RFw(3g%^Z?vv>Q7tK2c_c%fl zqhJZeQ?!%W%dzv5mp{x=7(b5w_PG6jI~byLY9rrIKy7(+*bb48U>pvGhMbZ7GF}ee z@;9YjxYjM{EO$|07><=M8Mbp}TUzT^xbA4n%8L4|+?&oA_pEt&8MlEk#72jW9p;a? zZg1$#$^)vhnx8^l`X^c&;;fZ1c3X(t7UGC^g{~Vy*Db+yTUdxVD+*-|BewnUayV?g p$0Rr$UKNsQCpypbj=7E5V2UfvSK5Id1wgP?+Unq#6_P?$wz;FNn diff --git a/backend/shop/__pycache__/admin.cpython-313.pyc b/backend/shop/__pycache__/admin.cpython-313.pyc index d1bc2ad4485b405b5877aff361761b8917b0ff0f..3935e94de991974664c40f28a21bcdae13454278 100644 GIT binary patch delta 2472 zcmZ`)X-rgC6n=M>$IQSiFvyM$g5skE+`!se#8RMTt2*LB(CNrLVQ`qy`(`4xHDHal ziM7_85{Wjh)kaH3)w|fMy;1z2f+=1V?T4#-myC!XkJke`u zW*hU!b@nghp6Yw8b10Ih6}CAP#(9DO4pt=cR8%ZI@n0@*l%P5p$U{$P6_s z*Xqm=ye{m`aG4r+2DwD!l4fyP8ut%!Zsd|@aoHMIWypm)hG%ol*X*YZxz<$lNt@-f zpjDL8;cr6$i-UAyl~Z-B=aE3HC0Zr>qcILUjQhISWVsU_Z1f1t0%u-<8@!Agn@!y8 zHE_{u1e2S5OWG zpwX-px+ot3B~}sNNO)JU zbzPt%7#_=mtKymsD=Anh0v54?+O(hlohf4I-uaP(*Qbim6eUwKt=7@=SBLt496tWx zNY8hYnXiO(aTy{7nV%65b!!k_L9-F1UWh26D-$uD)+TLzuEzd7={t0Bcwsn69ioU8Y?u0nO6fhXZd zwY6~EzjDCmk4~EO5x;`uROuwttg5*s5{mE`ZO-A-cSp{BBpFl_?LnS`CV17l!c|V< z*9g`Stc3$ssp+uZ|>@1l0uku;GEN zv}i5$%L!=e8hGtg^O^lrPd0)&SRMbS9=HDDp?GF#LZf8jE8u8Cp{T9>rW9vO#i%)=Cj=6gGY%&c z7opRg#FJ7dGTijMr*W1ONjAfQq$O-0Tuv%L&w(Uwu0l?XYXPFzo;A9yXiyhJXX1RT zFA!Arg#7zIfUp~fdgG{jz#!6Cc_-{lsbdbfo>Ce=J!<|wjHeXn6SD<8srjrM-b%gK zMF%+ML>T$>%J8jwL$^N}?f-uC+`-9pB$m#j&JN$aG<^6b9#$F$-!erG{d8)yzi+hv z?2~Kfap>EKOhb^|b00U_cB+%Zku<01BZo#fomSAG`D(RqCgXITnPaHheZH2EUr}1* zP{`-Qd;wG{453_K!j0rf&;;)EpY<)Zk*+A1NN-^mVM|6%Nsu&c2x`3I56Q|NnJajP zs?dUKO$5I}pczd3616YGw;79eXr5%|Un6Lf^W@rv;bT3|URGxQ6_ToDZ_FRY_hUbw z&ZkAxyoRq`82R|7?u9uoQ^-D8nkjWrW_{K_zdqF8J1JpW!pV!wHuucVTx4e`K{W;4Z#SfcZ&DG{j8;3_D%ksv5=qZ zPp5u)TDaUARH8ChZT0lzX?MQ zaU)~wk>GhGNQjSxHu0g*HXyXYuI%FUrzRuY%z8^6W)=-(7CjZHH*OOQanPGxlI3FJ gXR(K=`2(r>69RT7jDjJ1+<>j|JQ&OV#zZCm4@6@_VgLXD delta 2132 zcmaJ?ZERCz6u!3~cU`;owrfYbt=+nHl-(gHgUx|M`Ir#g2OmW;@uQTrj6&Cr_iiK* z6Q&Ur&!;$sYZs3UlK~{sIj59K*T*!-I^0D`pdhYanPSg4Lp9;O`Jz7W2*CIcLA< zFxyLnh~uPK#zVeb$Ze|=^+|j}r4W&k0JD`7|w3F)$Z?7`3Fg03kc{{q)%j zcaQha{4lCX`;v)NTvd$oB<6-L={XB6e?({-flp2K@T*h@4@}G|Qzilj1oK{%LVO6( z(qgd*2J-^&v)QZIA_Woqa>FXaDkIB_IGCkVR-Bsi`N+1FwHq_(eaX)C@vdb0Zuv<| z<4X!}=OLq4pGwBEO<=&nco_=GD6%o_OnXHDS%MkdgW`-}^9{(o$ z;hWQ^zEaFgfqqMgM2XoG@TDcajg*|td}y_?Vj7dl_#&#v3g@644fREYw}o5o?R_ni zh0FSyCp}C1T5sihCks4R7uCaXzylNc)p`%Y)B}*Lv4$0>8Zl_*o@7rdp;;A$s37 z(D<>wHIa{C6I?E6(sMWQ+!gj2%T5d^CRPnj$1;ibm^EMA=vXI8yyv^%gj^}^h70lr zF$DMIU&I)UJ6Bh=BT+ME_8!oz-LYgms>V_YRW+hF7z{ElZ2_OF0_kg9TT%L+t4iDn zW3J7i79?S}DFfSwpc=SJ7i~jZtAc>bpC64U)$Ua6$bNXX(2p6$T*c5+xV@?wDR{`{ z^qOUFOij?P%+C*Z3v0y`_}z<9xWWCgl0gwOJ+3r9A|gsF7(CVDAz13ELFFz_By@m8 zBI5}TqWws@ThXC~;h(~?aIW0L#G#wrCrrxj8*;@px#C>tx*Wc03A4~eXVEo-L`z^9 znDMrVc39=Bx6LhvG3fQx*mwimD0w9&FSQ(E(T+?!aTI>@O<-O9#lB;6XO!Qs=?^~7 zem9Z5^jY@!CA_Ot7b86n-v0K?%=pO6_|TovVO$FJn4b~F&HneeYBFArcqW}X64l=! zwu_{+ff`hmgxh!~-kDYk%*{G~&NX{9+L4N>YDXfKibiqvmyxN`%ezkB@hzkz*a@dg ze$-Fr7QC;oi-Yh-X+_;0iboMN8$V>-38rS!tPwqU3Mjya2)M(<&XPR{wf@Dg?a8^> zFj~A5cSg@=`%nG%wKB6$FsNA$^~TcpC>&v2#nVzouF8&{pMLj}uEIG7(a+Ao*M3DD zf}4JCJEi9hZBwiZceI>XL4s2R3qA#BC`vF0ZGodU{{GQW3BdKhLHR8-HA6DP?8JK? zc9cDJ%)%0#Nwp`zG;1p!lN5i!`5%G)Xf_bFiGX*T&)}os+~cZQ8oM&_-c;gQHiiM( zA9X$AtWgxjDIqu|D2O+O_8UU`?}B|wScEt$2_=nv=6AqXUS_<7e{oyO>-@4Pebjry dyX2a8$$bF__l<%fFl#_-wi3>je_^6I{{^}V=x_i4 diff --git a/backend/shop/__pycache__/models.cpython-312.pyc b/backend/shop/__pycache__/models.cpython-312.pyc index fb02aa96f220334f3713a06417c4761dfae25484..6b7e3dfe4b195dd1070f769c90a7f6b647fa035e 100644 GIT binary patch delta 1927 zcmZ8iZA?>F81A|4t^NMEl+Oaal#h8GLYCl$3{gY@WimG!G5AqCdcl%H&1vg`5U3Ld zXb|w|)G4kwqc{+2nkas5`{PXXN1Kq)^yflBeryrvVq!GeIk&h?Z*!l#=Xu`qp7*@x zJ-0V&$9HKStJMk-e%6-x9JA>oniR12#s_PFf)Z7U?D`&&of&yxqP6W}E+M!N^vDz| zh&O)zYC>axlk!qt(xO3Qngp7%izg*$T}C!~W6+?NW}|9SJ^|w14J!1+oT{en3j9;r zRg>xo8l9)Ilp2B2PU3j??3eOKnk$6^ zcJ9Hvzb`i2J-Bcs;QbJ@a}SQiE`L+P*4dhPPkRLqJ&qEQz(_#IM~$sR%z=1)+=Xv9R_IN{LOjR#vh2#a>7#S^{2WZxV9Cq;tE1PM z8AXs(JYJ6DCt`Po=0}e#_6-VA=1&D;*G@mZc{COn!nio1^;B>QWjJ zo0?36PEm^ljVTT0QE{D!qiSD?+0`tr=!)g^U?yH8N_!k0CkiW*HzvJ%tOZu9+XJmu zQW&=xgHIG;l1}H3uQdRU|V%Br`?~#^JRCGMIw}xEez(mxRJ>D!Q1KRtc*JTS%aqKrw-}7@~4V zBknb!w@61v6~frjYHQ|UDIsJ6;e}545pw04(JHm3`ym#3L`xJ!%brO^TDHGwR$~fl zOnznH;H|D3T_MZfP;1AGrqfpyQKk1+&Z^SGs`PPcI&(%D?HdMo8f)>=mR2dIo;*g=9 zuh?(%Y`3*nxZ0W=%>{1TL4F5%!rWUgOdux+vk4IQsM>06blTkRM&9YPS~&pSn01pc z;08$A6x!axCiGjvbzlpAZP18mSdM8loV-SAA$lizoLrkixcKcA?y=Bn8;M^@%3*{~J^x|dcc?IWW)w3KE9 z-y*fKM7oy{RQ6T5sJVOWXoCtr-F56E0BIi*a|eAy+uiKgUc0}GJT?eX9#f?1I{D~HRdqV z9|`k4>c}~wxP;BiWH^dsxrI2d;@pqr@2)sTWCCZ=x!mJYVsScDm{(US^qGywLZO7z z1n+#p3749jb5vH^ivNb3d>OokhXB9sUUWN;(=i}pc*p78BfDRSu=!#Y`aRF`4^cu& A-~a#s delta 785 zcmYk4T}V@57{`0ucG`#Aw3%*-bMtFo4v8|C*>pvkUqf{2g~(}aM;skDd5?>VNGq{W z)9j(32!bGLU6gU4i;U

87rNb6XJ6RTn|s^*$@1bMfPO{{R2`z7OwtpG>}t+umEP z<~{uT5LwKq&P|&W4!!WTf&RQMP_C}Pk`dqfP3omGdkOe(eP6-Q$HPJFbo4+kUUfup z*>1xf#~DnQRjcue&!8^=v*5wXO~VLJcHmCsedxjIswhO&M^#>^NN_m-T1h6|6ELwc z-mxLnoNk!F-_Bl`!kEhiF`RV0?kbvLN;D9rI5ev$rsSleq$F7u#Q?C=TvdRho7Ho& zpo>FpJD?PGw0{hc+cTA&yYkorT+Bx0?go+-=&T> z_)745X}R^Mz^$*(7Qbw-vSHk4ia?1{OT1w96nWSijR8Gz@c9g7Rgui3s$55?# znR-GN-}rvOvbyCLLAXxpFzyfbS*}s}@BT8uh_FKaDn1T|EbCO>AT%QEZ9!9L#C(^x znR diff --git a/backend/shop/__pycache__/models.cpython-313.pyc b/backend/shop/__pycache__/models.cpython-313.pyc index da80bf389b50dcc3d528f6ab3c11e50d8fa65132..6d22e886340afed237cdad8f13ef9f743e31b114 100644 GIT binary patch delta 1996 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 Hug?Df9)i>^ delta 1451 zcmaizPfQed6vyW`J2TAwncW3u|FNrVmj&I5Gzdil3mQ#8N?og{iPmH@Wkx$Xvh8nx z1}=Eupy>tj(u8B{!NkNKDpwC0lU|x8C51pHF&=v2rY$Wu#H3d-xYk3kdA<$FbY?lC}^|iYvmx!4IQE5QQPZl;#9ew#bbr{3JIn{HjL$ z+^jHjU9CC5%_=kdYt2D!4lr|Y$LyD;Lj?^wNE4A^n0&ls$R`Or6I+Q28{$+}W5)@x zuZx%y)}x6#@_K#KU4_P&fhOP|Wh?DTuH{_we3dV5c#>m}Tdt2ev)L?-L!H`3qHs$6 z6LcxsBiUn>anCnNod+V_hY|KOxFVO+IQ#LU*R6@NX}e!J#&j9wGdXBh;@#3Z%cvSYgwUZ^&?r?bR*2ThbqtK@b6*7{}+v_BM4sjUY+bmF9M5qG4Gu4N)%=F zr6Oed7yH)I#pQJI_w>MTQgTZY<#gp{;?EyFBV#+~V< zRAUP{U1DB(7I6;o8Dfy(LcvGR<6;zXfuU4TcehVLU-eR9PS2-wT<=UFFKQUK!c)Ef z?Z_-iv3n#wg32=p32_lcv;>dims{~Edf_Rly4oT-?WGip(ropZ*b)fb07;?1fCc>g*)wRo#yPCC{AwV4jHxtADT z`UoME2iz929qsy72n*v*G=G#ce{LQs^=W@LBZ-FiNJ--2B C^CY|g diff --git a/backend/shop/__pycache__/serializers.cpython-313.pyc b/backend/shop/__pycache__/serializers.cpython-313.pyc index 445d1eeaa26fd42b640ef486f0a7d01171471c2b..124ad0cd039aba4c36e9ad1682f5ca35a9f6919a 100644 GIT binary patch delta 2463 zcma)8U2GIp6rMY~vpYMxv(t8eTDE^nZ5I}#f|MeqY>);NETymosx}#h-JuLjck#{^ z1*5yX5Qq<0Z$vRs;}b$+qUi(bqw&QjDVS`Xgvf(v;0Z#B5??%Lwtv_P%qHL5`<-*| zIrp4%&+N7K>z%>-vMh1%>`Rr5o61jy+ z$)P+zd1olULy187DpW(zVI*MuGfJ{U1%Q&LDIqu zWvFqiVB~YQ&&cF+YEB!`?LgMBj+xpC)sTR*g*?sblzqW(Boe#9uOLw-y7agLd*Oao z0N^w?S=Dq#u&Z~y=b=qN&^WszUt$l1svVNEvoWF#>$Yo@nwNOnZRzGA+m%0f#KIHC zE?la;W24sN;I5v()>Sn-}`YJr41KCSTYycv=rfKUCW5xXkR|};C(?->+vI5_ z33kxaL7JczE|V6v#ha*ExPYDXZYQhQkKR<#shmoJltn|Z63wA+^jQE~KIGgn-BL}% zDmclr!!rexg^D*!)hWD6O1p5Olh+;`EeN0tWIIX_BJ3-1pU?3Kr;xEI-edNTFB0eg zVif(9Ufjc0^S$hjFIIGRUkl`8ljuW*r$}Ni=;4mDNnG-nB!V7c|Hv00#*O~g#bTWH zZzn1CslRJ}j2B)4bnZVf4$Z_flRvK0`QhA++6#gR+;$WoV4N&0t+^Y*_63?GC#tBB z8LN)C4nlN4Q&zHmpY;5Y2%XvmKlcWn`kZDfK(qC%n=V)?rM)qZ`qLr}4EvIj9buXr z;cstc9(ieaAPK!?M9ZD9eN@-7YCdP4VBh#Qumf^K8Y4=dm>&VPo+3Z5fnG2BS&n#S z+uh{`ydQKk!h$Q&!@dr#E%u{qE5f`|m!G24=}Wi>TL7$X=-G5a=K*W^YzqAO~1=_4z#} zOz8+h9wCR20T_U`a11AJAW($LJ(d#;=qc)=xC7pWRt*j2niukj#Pn!w7tT^HmMK1F>NoC_Cy&7WbxyHe*i?;GxylE6fM)3rF`Oh&i9^k ze9qPVYw&hC`CL&F9DAO8blO=|=9AgT9d3S8zgFO~+y-up&vRqKIN77kUo_NsV2UwQ z+lna>m_%lZhoso!Rrmg|}(+>TU< z2+3$aNx`di?%Iz!!S5!#J0HZXXRKu`1R&(|jGX|v&YQ?4~@ zq8!vjk))xLsE{1|nz~NBKx7dZI_v?~Or;g@a75l03$jaa zQ^>(xd5ZMFu=4F(2{jrbtV{eyUFeG=GKfw@t7Jh@L(Gd2S0|Hub27|T$F8nC1a76S zu+H5|7F%^LrjC;L;VX6f+?viwEi2r@LQ)(s9c1u3R{B zOyKoFxx;{~azcI~3W6Rc9Dx%ZH^?yb=oQJ2Q`>5o%}I9_#`PKdz=4&RbK_Rm<9JR4n&n%G40o*>0 z;1LZJ(Ly zJmh*Gb0c@Tk!2o4qu8es{qkq`y0$KLZC&QrV!7B$$`?=E%M2`K23~M%5d?k-LPI*Y diff --git a/backend/shop/admin.py b/backend/shop/admin.py index 8dadd4e..50e70b7 100644 --- a/backend/shop/admin.py +++ b/backend/shop/admin.py @@ -1,7 +1,7 @@ from django.contrib import admin from django.utils.html import format_html from django.db.models import Sum -from .models import ESP32Config, Order, Salesperson, WeChatPayConfig, Service, ARService +from .models import ESP32Config, Order, Salesperson, WeChatPayConfig, Service, ARService, ProductFeature import qrcode from io import BytesIO import base64 @@ -11,6 +11,11 @@ admin.site.site_header = "量迹AI硬件销售管理后台" admin.site.site_title = "量迹AI后台" admin.site.index_title = "欢迎使用量迹AI管理系统" +class ProductFeatureInline(admin.TabularInline): + model = ProductFeature + extra = 1 + fields = ('title', 'description', 'icon_name', 'icon_image', 'icon_url', 'order') + @admin.register(WeChatPayConfig) class WeChatPayConfigAdmin(admin.ModelAdmin): list_display = ('app_id', 'mch_id', 'is_active', 'notify_url') @@ -33,6 +38,7 @@ class ESP32ConfigAdmin(admin.ModelAdmin): list_display = ('name', 'chip_type', 'price', 'has_camera', 'has_microphone') list_filter = ('chip_type', 'has_camera') search_fields = ('name', 'description') + inlines = [ProductFeatureInline] fieldsets = ( ('基本信息', { 'fields': ('name', 'price', 'description') diff --git a/backend/shop/migrations/0007_productfeature.py b/backend/shop/migrations/0007_productfeature.py new file mode 100644 index 0000000..a8e0c21 --- /dev/null +++ b/backend/shop/migrations/0007_productfeature.py @@ -0,0 +1,32 @@ +# Generated by Django 6.0.1 on 2026-02-02 06:04 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('shop', '0006_arservice_esp32config_detail_image_and_more'), + ] + + operations = [ + migrations.CreateModel( + name='ProductFeature', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('title', models.CharField(max_length=50, verbose_name='特性标题')), + ('description', models.TextField(verbose_name='特性描述')), + ('icon_name', models.CharField(blank=True, help_text='例如: SafetyCertificate, Eye, Thunderbolt', max_length=50, null=True, verbose_name='Antd图标名称')), + ('icon_image', models.ImageField(blank=True, null=True, upload_to='products/features/', verbose_name='特性图标 (上传)')), + ('icon_url', models.URLField(blank=True, null=True, verbose_name='特性图标 (URL)')), + ('order', models.IntegerField(default=0, help_text='数字越小越靠前', verbose_name='排序权重')), + ('product', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='features', to='shop.esp32config', verbose_name='所属产品')), + ], + options={ + 'verbose_name': '产品特性', + 'verbose_name_plural': '产品特性', + 'ordering': ['order'], + }, + ), + ] diff --git a/backend/shop/models.py b/backend/shop/models.py index 9a9ee37..017467f 100644 --- a/backend/shop/models.py +++ b/backend/shop/models.py @@ -28,6 +28,27 @@ class ESP32Config(models.Model): verbose_name_plural = "硬件配置 (小智参数)" +class ProductFeature(models.Model): + """ + 产品特性模型 (关联到具体硬件配置) + """ + product = models.ForeignKey(ESP32Config, on_delete=models.CASCADE, related_name='features', verbose_name="所属产品") + title = models.CharField(max_length=50, verbose_name="特性标题") + description = models.TextField(verbose_name="特性描述") + icon_name = models.CharField(max_length=50, blank=True, null=True, verbose_name="Antd图标名称", help_text="例如: SafetyCertificate, Eye, Thunderbolt") + icon_image = models.ImageField(upload_to='products/features/', blank=True, null=True, verbose_name="特性图标 (上传)") + icon_url = models.URLField(blank=True, null=True, verbose_name="特性图标 (URL)") + order = models.IntegerField(default=0, verbose_name="排序权重", help_text="数字越小越靠前") + + def __str__(self): + return f"{self.product.name} - {self.title}" + + class Meta: + verbose_name = "产品特性" + verbose_name_plural = "产品特性" + ordering = ['order'] + + class Salesperson(models.Model): """ 销售人员模型 diff --git a/backend/shop/serializers.py b/backend/shop/serializers.py index 5436361..ce5ae6d 100644 --- a/backend/shop/serializers.py +++ b/backend/shop/serializers.py @@ -1,5 +1,22 @@ from rest_framework import serializers -from .models import ESP32Config, Order, Salesperson, Service, ARService +from .models import ESP32Config, Order, Salesperson, Service, ARService, ProductFeature + +class ProductFeatureSerializer(serializers.ModelSerializer): + """ + 产品特性序列化器 + """ + display_icon = serializers.SerializerMethodField() + + class Meta: + model = ProductFeature + fields = ['title', 'description', 'icon_name', 'display_icon', 'order'] + + def get_display_icon(self, obj): + if obj.icon_url: + return obj.icon_url + if obj.icon_image: + return obj.icon_image.url + return None class ServiceSerializer(serializers.ModelSerializer): """ @@ -54,6 +71,7 @@ class ESP32ConfigSerializer(serializers.ModelSerializer): ESP32配置序列化器 """ display_detail_image = serializers.SerializerMethodField() + features = ProductFeatureSerializer(many=True, read_only=True) class Meta: model = ESP32Config diff --git a/frontend/src/pages/ProductDetail.jsx b/frontend/src/pages/ProductDetail.jsx index c9f9721..078928c 100644 --- a/frontend/src/pages/ProductDetail.jsx +++ b/frontend/src/pages/ProductDetail.jsx @@ -1,7 +1,7 @@ import React, { useEffect, useState } from 'react'; import { useParams, useNavigate, useSearchParams } from 'react-router-dom'; import { Button, Row, Col, Tag, Statistic, Modal, Form, Input, InputNumber, message, Spin, Descriptions } from 'antd'; -import { ShoppingCartOutlined, SafetyCertificateOutlined, ThunderboltOutlined, EyeOutlined } from '@ant-design/icons'; +import { ShoppingCartOutlined, SafetyCertificateOutlined, ThunderboltOutlined, EyeOutlined, StarOutlined } from '@ant-design/icons'; import { getConfigs, createOrder } from '../api'; import ModelViewer from '../components/ModelViewer'; import './ProductDetail.css'; @@ -78,6 +78,25 @@ const ProductDetail = () => { const modelPaths = getModelPaths(product); + const renderIcon = (feature) => { + if (feature.display_icon) { + return {feature.title}; + } + + const iconProps = { style: { fontSize: 60, color: '#00b96b', marginBottom: 20 } }; + + switch(feature.icon_name) { + case 'SafetyCertificate': + return ; + case 'Eye': + return ; + case 'Thunderbolt': + return ; + default: + return ; + } + }; + if (loading) return

; if (!product) return null; @@ -114,7 +133,7 @@ const ProductDetail = () => {
- +