From 49dbced0cb6d6cf89bd3a2e2c86a09c0756de1ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E7=BF=94?= Date: Tue, 7 Jul 2026 06:54:47 +0800 Subject: [PATCH] =?UTF-8?q?feat(assets):=20=E6=9B=B4=E6=96=B0=20Logo=20?= =?UTF-8?q?=E4=B8=8E=E5=9B=BE=E7=89=87=E8=B5=84=E6=BA=90=EF=BC=8C=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E6=95=B0=E6=8D=AE=E5=B1=82=E6=9C=8D=E5=8A=A1=E6=A8=A1?= =?UTF-8?q?=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 更新 logo.svg/logo-light.svg/logo-white.svg 品牌标识 - 新增 logo-calligraphy.svg 书法体 Logo 变体 - 新增新闻、二维码、微信业务等图片资源 - 删除旧版 JPEG 测试图片 - 新增 CMS 数据层服务模块 (admin-api, auth, cms, crypto, db) - 新增 cases/cross-references/methodology/team 常量数据 - 新增 site-config 站点配置 --- public/images/news/founding.webp | Bin 0 -> 83498 bytes public/images/news/solution.webp | Bin 0 -> 17928 bytes public/images/qrcode.webp | Bin 0 -> 27574 bytes ...B_1_201_a.jpeg => wechat-business-qr.jpeg} | Bin public/images/wechat-business-qr.webp | Bin 0 -> 28674 bytes public/logo-calligraphy.svg | 74 ++ public/logo-light.svg | 22 +- public/logo-v4-backup.svg | 74 ++ public/logo-white.svg | 4 +- public/logo.svg | 20 +- src/lib/admin-api.ts | 178 +++ src/lib/auth.ts | 84 ++ src/lib/cms/ContentZoneRenderer.tsx | 108 ++ src/lib/cms/client.ts | 238 ++++ src/lib/cms/component-registry.ts | 67 ++ src/lib/cms/content-types.ts | 1020 +++++++++++++++++ src/lib/cms/data-server.ts | 174 +++ src/lib/cms/index.ts | 82 ++ src/lib/cms/init.ts | 36 + src/lib/cms/mock-data.ts | 272 +++++ src/lib/cms/mock-home.ts | 239 ++++ src/lib/cms/registry.ts | 184 +++ src/lib/cms/renderers.tsx | 264 +++++ src/lib/cms/storage.ts | 239 ++++ src/lib/cms/types.ts | 179 +++ src/lib/constants/cases.ts | 284 +++++ src/lib/constants/cross-references.ts | 80 ++ src/lib/constants/methodology.ts | 93 ++ src/lib/constants/team.ts | 65 ++ src/lib/crypto.ts | 69 ++ src/lib/db.ts | 11 + src/lib/site-config.tsx | 82 ++ 32 files changed, 4219 insertions(+), 23 deletions(-) create mode 100644 public/images/news/founding.webp create mode 100644 public/images/news/solution.webp create mode 100644 public/images/qrcode.webp rename public/images/{149A1D2F-D9FD-49C7-B139-142C50C5FE8B_1_201_a.jpeg => wechat-business-qr.jpeg} (100%) create mode 100644 public/images/wechat-business-qr.webp create mode 100644 public/logo-calligraphy.svg create mode 100644 public/logo-v4-backup.svg create mode 100644 src/lib/admin-api.ts create mode 100644 src/lib/auth.ts create mode 100644 src/lib/cms/ContentZoneRenderer.tsx create mode 100644 src/lib/cms/client.ts create mode 100644 src/lib/cms/component-registry.ts create mode 100644 src/lib/cms/content-types.ts create mode 100644 src/lib/cms/data-server.ts create mode 100644 src/lib/cms/index.ts create mode 100644 src/lib/cms/init.ts create mode 100644 src/lib/cms/mock-data.ts create mode 100644 src/lib/cms/mock-home.ts create mode 100644 src/lib/cms/registry.ts create mode 100644 src/lib/cms/renderers.tsx create mode 100644 src/lib/cms/storage.ts create mode 100644 src/lib/cms/types.ts create mode 100644 src/lib/constants/cases.ts create mode 100644 src/lib/constants/cross-references.ts create mode 100644 src/lib/constants/methodology.ts create mode 100644 src/lib/constants/team.ts create mode 100644 src/lib/crypto.ts create mode 100644 src/lib/db.ts create mode 100644 src/lib/site-config.tsx diff --git a/public/images/news/founding.webp b/public/images/news/founding.webp new file mode 100644 index 0000000000000000000000000000000000000000..0cbb18cadad16851f8cdf58607fdcd494ad0e992 GIT binary patch literal 83498 zcmV(tK ziDyd>$bMmYC2mAAlymRz&${_H{~sRn{68NK)#Zip!O!!_N1o4gi0d5w6aU@SJk|eR zp+EogQ|r9*{>T51E?>X@2|3C6Q0RvB&;H&Bd;cGtZ`GB(r`V4M>3}{+NyR(5tT4^+Z)JnOaLX zbl-Io-Ra1WW{a$8BVG9Bbpo(k=et$@SY6&t9d*3SAH8jwR@~OP6O|Nxre^$5EBpWh zaVfUbG(81tkNtUX3t1*y<6!#Qu?inL26W?OA^=vQ-JJc#&Xa6QIv|qUqe`hrB$7ud zj#f=`Le&yT!P7UK*$4lOLUDUi+CX&N{)3%-$?9^OmyDA?PbvAcMI{>O1^5`a@>dzO zMOlUV&1Oj`QF`MoU;3pQPeW#{9_-r9?{RNwWze2L>c65u>0lc*tlsf=7HGzcSC(i9 z0lIL2a`ES;0;3 zFm$1)LErXXEiZs{qP*4%7I=NI17}lW&JXpb{=b?Q2WhE_c1knsK3>lxuxESu$c&p>d}>_SvnQQB~j**=xO&}a#_Pa{5%)}3TxAt zA#@$k`3LjFRf00JUVP6fP23!fGj(9DAczWz7UNq__Dl53wtZ* zowD+1yTh4`?q2P#BEqKq!g<$Xx~+!F6G z9!FI`KVXV3PbG~ZGi(&wYUG*6ZYSX(5&Rza3)xxV1I4C5Q?UrQjZbp0ECT#jg3}pq zsOHcMv%c8a=@IDO^4A||%H$+6x~weF-({0rT_50$4wQ|{eub5JLAHbJ)~8%PSbSrix;(-%p=v-GC(cE{-n2+u#cXGWEZZmO$k(u>MgX@FIG4DpF-Y_C0b*5y_oKo4x0XYAUNR*=_7WD z)z(W&?xja$K}y?SDdXI?|JncUF@x}U+N^T@dk?l|4)j%U7--1XxTY;WnFGpd*{&d%@Q~` zL>YAMwF;AOS*)*NaSOd5R_13n#YaD8Ii-|h=KwXq(^=7mj+Ox=Kdltf?8Z7VH+;fd zC0z91*7i>S&0vVV2ayJnv%zyg^mAY*Hlsln3da{FhuDBQqHXWL?8kC3B@}q1Qk{yt zLKSr_o;FPoIL0Tnux>=A3~hqKHA0fvKM>8(m*P!$VWp6!hk16jQUr7&3&dxaA64g4 z_hNUAQ^iFQC+)kw>yBH{aPi+qsTB}gDRk7sG5%j#^9z})n|k;`-Rx92nRauQa?43? zgn!POn*v^Qcv8j$=LN+Ea`uFy*H<8aFO7|8-H?jmX9JQ7_vZxEHlFIT`{YFT0d7-# zGxI}ZtH?R0ZzB8uvyjgijG&2JjChdqcJ#P1&eeMvd5Th`VM1%-xn>iF+(*9E>(f1} zF4lY)by_%~yPG904uRyU}tK37zhaDyy=$IafW=qLjyxGY5{z=7 zrS;BVX_A^!5+FTP4~nE<5NM}Wa&nGG8w`9QD)qD7`p~kapSJGMm^~qv+wTusC9gPd zI?BqGn4#apx6=?+b;~%Y$njOXQ5U84Xl|9~+VeWLCT(8v_dwf?Gk^=Rh3J<#D*(3c z9AXrM%ovtsSQ(RLuuxY4FY;BSAqqv>%1djs3)~>6;FLG%>yQ@c9?cbf)jXw(qntN` zX5?bHb*QcY0kwf+lY6X70Sh+=h*j1pIA4gi3EX{!X8 zfll8*y>FHHoxS$ay}{I~(uUi`@;dDwnOYl}=s8RDA#G!TW?RVm`nq@Ozzte$=6H?!5zPRusNZjxJa!xxC^W})`sWatD^J|0JiV5828*C+T zYfbGlT8sbSY_7ArBh^s{qR8FdV6c69Xt0?>1>6{kc)FqE9qvp%mz_to=h`0_04)YF zYgP&!p{7E;#?s{lW(v}8R?Tl$Osp*!{C1B9?4ll!z9$k5I@eck?C$N4%J-)0e}$*( z1+e8VF9mr%_eO7^TkF|mQ=SlJ(4G_e(>L1xZ>S~K$h;%}*e zV+@5p^gftTisdDrlva5lgg&2?@g1 zbDK`kXR|v#L*o=(vHz8m4&j{4iGr1n0(S&6EXL0uVrV)(i?ijVvlnVIVN_cW(lm|} zFREX%|3r>+D%(5kLmqLVG6MA5p`NQO>{y?Glq~eR7jRM)*pa(p?z6Q>->`0y5AUx2 z`G+^CZK_m1gN@!KK+CI@!?WjCG@GBKr~f;R`Fw6;ww#6mCR zGH??ybd!s|Lkf6p4NMrss9$rxyEfzh^Vm`1|HI98c0LViJoor;X*`f<3y+eV8&oz( zQyU#iY`cAsqL#F*;2fXuH^$>-CJ{hGnAUCp1AZGvJ+hHp>)NzWZ~Q{Wk*x0M*9tUM zRaoD$X^lkxA;;gBV1La$)UuHss$?;V@}E93G z6T9J-Na18i=z2Itc-$Uurr<1T^V-0FzzJ7c6MKJQv`G8+L(I+h>Z0p0h~iE#^runz zcUaTv=m~woabw9!WYBIVzOyGyxtXD%cVp5t)~{9C%Pbpc0;M-eYVghUa$4K84r1yC zfW`5SxwS^h*Dc*hUpPfS;@jyjK;o@Y&1eydf}PhP+}07~fDC7!6|7u!Mjfw&@z|*_ zEveuEnf!jCKOs6EI-7rA2I}&w4A63E@t4Ey{KW4~RB#EaC$&yH?#$+xV_bZ4;lg&> zd!9YWY&V?f={0Ui(v3$M8dS4_2rfR^?(~&&Fkk$^r=WMnS4+tha2CIkan~mh-?4pqcnAC zI+CM}{v@hERKm9_&Hk7Ap#@e0H>;y5tAwynp_;)Ntw@$PiMu6q`;y=nLnQKdfE^bN$9t`3WS9nvss`nV#D;jCD^!-}hZv;psKAA{Kuy%nWD;U-@xlf#D; z+*?C1=h}@_yQ2J8GY1sCc(kO(UY8Hs@xk`cXU`{7J7q$G}Yd_Er|{Xn#QD5kfRpCew~r2Vt%G78;P|{XLankG)H;_ zZ4bGsq62AjP`7t0#9sti_R5`Qa(r(1!1^^bB& z76Zq|vqQi_o__bl)rnhbQtTq4Q6#awZCwjTq)?L}*&J!yq3H`$qRor@eZc`hSUq6a z__;e4GPer>`|g+hg~GiQbi-JXOz*5rogq$8JQyB>1W-Z%=ZV61a>F?}d^L(tU5W@C z)a#2az~WXSY9JAZ3hmySz{LJEUeX+f;1jOdK}8Hm*z{_-{wn}F3jaR))~xETPMbf|;Wl|h>1GYAtQ&{meux>ve; zC!=etw}pP$n*wUgW0SuIKiEHs!sCA^Gg~F@m@QKFoGru-fcIi!5aHjRjY9Ejr4>_( z!=ZC%B~0}0VzFSz46yRDjQB2i_x65XMQo3r_fAtt*FY^N$}exyOazZ;FvvrCu3nc(G`@8ik+~?w0!rkp^PHZJ}^}aC~KcN>hD_x+rb1fY)Rk z1-Zx?W$5xOGv35~^|5p^6hl%O8ez~F?!(t@lSGKDGU{kFJFG!-G{BdFx(Ld^XOfsD zm;HKNDfNxZOTZ=QQ~wr;T9Wc%lsZ| z%oZPm_QWwnD))yEK-pfy(S+o)2{fDjyOY!B#t0)dqSaJwTB0pq8=6`%UZih*_eS-% zXRlAouW{Z^bw_a5v^T*%Ucv<=0g4aHAza6fnLBk;hZ$du|BfyR(+r_kdQUFh8=eU# zRunR*&Dysc3SB#u<@aFH*zQ9jEg%^0L-<8OB^}avw_*p;PJQ%fZlzUsuz$WieP6(y zAa5>NoSUK?unc}uyOZ1B?d@B-p{tSr+vXL#hGnU|gX~ZZ16-AYJEPnN_d39?#Y&!! zUR|$h$V^g-w^X~g`VL6_a0;tdR~R>|fXgIPQhj+3nB((~$o0)tr9BO>(SAXAf|F;} z6TQ_cFpH=Zp7Jl{bPzvM1~F!#vmz2zMg-`U{3SZra$X7@S#Jr(hL93`?p=RskwBq2 zAN%Q_GS!c>TmtWDQbr&d8MTKqc{B(XZz8b8Jv$mi$Eic*eEeK^mz!#d7=gt3f}pgD*|wAT=x8f}aY za+4JCMEz|2a4r5ENEr*uhKsAm9^4X|S2H8t!(>z*QW)T_75q z-mJZ|d~mI|cNyY-blu*-la8`nw7J~I^_F~@oF`E^e`i7?#;pzYE-nHQe+=LBQK5Fg zR>U4Kdda5}yR>dhPX4%d|4Nwf2FPpHwXci}7eg0V&)%+_geT^?&47!oM`-2<#Gf?x z#iWvUbTzBKA8aCam(L_U5LXm$(s0M~yL}z3d#F`*OO&LoFy#T&lG3Q%*ikR_d67Ip64Ip?^yRWyWUuB5mTRE@%S`ld)A`Up=hpY{bs_xuIOTyM`P9 zD+UmwzgvT)HUj6H{Zwm!HAZSzyFn!}9x(1|>g6r^sxdPnHtu8!M|a$HLYfdEbyq@M zQ<&KwRwGvQ!{*P}f7kKI0&>o(>DB!@u}D=?idBhz-wNKdGpkg**_=I(U~t^je3Oz} zcSzH5tY;Xm$ojdpVaiWTA&%#Da4k%hmQ+4ogizn3dToIq{>AzMNPEgpURV4Bf~am6 zYb^%w?*@=Nr%uC6-8UAy-|Wc`=snvsfbexcf@<*~M|o{qisio8v!pX)lZ&%;!v(`& znD3~$-v=I!7B~O;P#gQ3HtYTX`6B7YMi{bbv64;4$`3~mU@ExnSAEvV)RKO_$I*_3 z!b%J;(GfOTNVgS6~op4wA+Hly443f^mm-QC33xQg3L+27>!|{wR zQ}Yvsy)gL&J~P?ztdLhErI*aS=~(Ii*dMi2OW!fA!Vk8OeasBgGhpycVN*hmq_W`*UsCHl~PB&qBlnae&+#iAZ>rc;>o~YIG zgjqF9dF#AJYp%0DdvL&KIr**}iEAn%$p7@K}NY=3bU&^|@4 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

BOpL=N`vM1I^t~N{wa8QJ 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 literal 0 HcmV?d00001 diff --git a/public/images/149A1D2F-D9FD-49C7-B139-142C50C5FE8B_1_201_a.jpeg b/public/images/wechat-business-qr.jpeg similarity index 100% rename from public/images/149A1D2F-D9FD-49C7-B139-142C50C5FE8B_1_201_a.jpeg rename to public/images/wechat-business-qr.jpeg diff --git a/public/images/wechat-business-qr.webp b/public/images/wechat-business-qr.webp new file mode 100644 index 0000000000000000000000000000000000000000..f63bb0bcffb7708a8c19a7f5f62bc02146bf9035 GIT binary patch literal 28674 zcmV(%K;pkrNk&HcZvX&SMM6+kP&gp&ZvX)B;Q^fiDklOg0zPdnlt-i@GNB>VzS2+) ziD_>8z1Oa_?K?TPnf(Ee+F&1NY{f6(pZ z)t+D6`R6+%!uckUn9Ke+y$ek%KO`!DXl^*up-Y5hC$NBgJkx8U>id9^@4i~lMAHT|3Xzxprh zeJfwyG2xr|@WPOR73b4zE z_9yUEI(@O`JKvEX0ZMaHUQ^p2+w{d5Wa#v<%&y&x4pF^_7u3_u5gKuaqsX|gh%Q7d zMK3LZ%A~8_&AdXoug0|D?ahCupbUB8?L7m{`BsKaRz^PC*jC(<LN{y4 zout33fzW|Cj6#k_vK(jbaOQYfIjfic+fh|jB%Y(o?LPbCdORXsD-BVW4|uqoV5&ZG zwI9{0F99PzNfzd}Pulg+;(fPOEaq~}TyPh>B58^V zKQ*?`NqQ=HG6&Zz>FS%obXVen!2*e$D*9t0SSq7?&?#CRNx^#>q%*B!uj0^6BBVB{?mmA)+Rn zP_i_CQT+q*B7J=Wfw?XY`T@Cx7V!ESON@XL8YGF^a^)UF?N*4Y9U-89tbD){9gtvs zH{6Tk1#IkT{}kns9fp$ASGWH{98jD*7{e~0HykPP`?5U1j5b;z7qb2< z;MA!Y?W3fCPL;bMr(rpkDBFK-b8M3VBuMEDiL}!pWfg|&sikf7^s73LX2!ueGsH)0 zFlA<-3==VIPI;Bs;YhSz?8EKNBqidL8HCf}{9GO&5ueo$Rlua3EmM0GcrSH+kK_0A z{e<-T^=yt+?T9GAZ%2L>1JZ|qGPTM>q+!h-S~ZpK0HPc$VFwh`qy()3O4;Uw8c>xs zsvV^00r@2Od8KjXCT-{XCp*le*#X8o`T40EWS9n+J$011MRAHuD*1GrVQn*I3&@YQ z@KXVE2`_TqVb+4tL<{jX*?lon#88G$Yyr)}cr34k-%c+Z<$~e z&yu}juA<~5Ex&f&<4$UCvwAVIDs=4-TCxJ~f1Al!kxPD8@e8>6R;#UK_J4P4ggNK1 z_1x?C%6|6cMHUCa6g`m^l6Kz7fL&NO#z+V8G_Q9|5){ZB?Dk*0$tznSfO=HvUElKw z2oeN3_nqIf&ZFfr@n#*cFO=eMMmrXL?_@Ow|4G5-V1FO$lOt*BLb_~lF}bV7mO?vk z4P=^5c<`{t51908877R1-Hx4cu%+1_Qh+|j@tQPa9x??s+sK?BmVv5QDIH61Y{zvp zo70hr|KY2sQFOnf-s@062XnmO!T6%t30HdA)k2Vjo;z@Et2nKtDm35IY@ymGW&|xQ zdLmTAP^XEwl=$Dz1pnS_`%~L7B?>h$*Yu2{f%WbFdRyW_ih=(S z?ICq8lt$MUiahWfpwfO|cp<4LMJHhh*oW2?V~<1Q92>?*_~DTS;+-{eoXDuW>0Qs5 z;CO&gytotK_MM_FgOu@gdXoWo%9jtc`#uG-t+o?@#<{R$wjJ1j&E@O16-gD*{onAK zo>>dt7Dyb4$)_20iEb1?k?kcRtSCb?z_rit$_@=^jj(7=*FX!USv6T+d5I*D_e()iRe+hzG`U%1cRrZpt7j%noXg11P^~0zeCrK2ihk5NRc|4(|6q#?no5ez_L<-VX&g3Kp zD?E1Z$aPzs%j3P(d545EJY?Gbk66=8S%-XQdZN#wV`Vx42Ud|(R^(R0FCjE@i>HZ_ zLnbzsR&JA7uSZ;BwQQ@Kw7loa2R0{%I9uPyRBtIaCm>T~_%PNkiFJ8%NEHInX-rii z_jbPFql~5s0|BV~I~E4xZ@G!LS5wOOl9GGK(qlLy)-JxZ)3D>HHi*bw9pP{b@=V3r zYoZJ=UmqO$!IpCI*J5X&YB%X8hBOLzt4^GcTRx7q9b)JQc-WhE)Oxy1_4}CWZ$pi=F#N z6M4)|&$ooe*)mQ2Crt2K2VG)qc3Qc_(&PRISu(1fTYXI)Q5 z&W(l{EA)8&Y&}UzwIQ_WqP3YmqEsZ#8p*T-se-rX*2 z%o?`KR8az7wAfJ~#W3=`jF$d#Wk(Lr(0f`NWm7FfKzjlMN^?!|B_;{r1mOftk90X6 z&dT|d@XdbOA1*t|9_o$h@(M#$igST4Q2DY+NtYDYl z5-;`DJJV&a@vA@p0WeKE@x@{xoHlkSY2{xhu^22%>|t8){7GhoA0;La9WJ@s{`uOg z@@3NKLGKYy>H{8za{s;{_v`f&sv~b#>6!ZQ##+L6O1B(SqI5Op9r2vLRs2EYsTsio zI-oRT;BDZPr{1t7_w-N1V)~EWZx~AlzmIJfGB-f#H{tOWxDab8GZpe?A&Uj)yp60dl(Cc=Cq3vAZ?yy zK{E0#-KI==tkkfH8|Sh@ne(YJD3>sLS{p<`MPr{|>6q143$$?xcO(D>=3|`oKw){m zg|xPpRL2(|wP)k;=|II<|^`7gxE115kn3S?5xA zk*Gohsyza~>0hflMWDU#)t!z(ToT zqOlX^`PfMhPbHVGa~#s*iVIV;3Y;xBe%qsR!bh-3TA!hHoNgq$;>uTKeeD7b|53Y5 ziD{e5QJ6Ec!s}t36iThALR;4Uvd`K1a#T8`zeQ~dtJa=eHZq(~eEEqQIO7T)mq3Qg z4P*do=7yOlb>zPY7wWKkWq!p7%-(#aMRN$~YsaxnnsH{96tXd{B!!gUJVJwL`k04;{6L2MR?W`}#JaU}pI zY;V*ehJ#d+w10VoHSp)fUGt#sqr!IXAnxiX-p#tD-%hb=V53fqD+^Z86f@?3uV+rk z>s5!g(Ib^LgJ~&>8=3XO<9YuX>l;DLV)uJWzsWg543hRBxnyX`fYr*jt{j%pV zS})Yp%FM#Y zps*VD=~?Xj3Z=sUD)sf2b>UMLaUXXR_9Z_+84l8 z0gkq^mfMo z>_xTkljK2#$IeMfNAtpbdqy*|G<=XOI1!XoMlGzDy~Olq!)N?c!mt1U`L5CP8XXJ{ zUAVn`uN^XXw4(AiDPY;nKy!(0v25ir_tc%6YV6|60s23~O{x#hAx2SD%nH^?1M}+4 z!deA6eE40a%D@%|`c&LzSc)uWs3aoB1aZlAD>ZUo_G;}R1}izu(-ed(K%J28!w)@) zNyoPT%oF+Afj~X}^h==*)@mBf3G3&d(u3%zkn1E!k_)0v?E``GaLs}twFEvE@`~iL z8Pcdq5I9<&d-IcHc}r(hd6e`w1vh+B3|A##R7f|SeIl#YT1d!|GkJcAs~U?Z*I2lw=)SgH4k<7xl^4!g%N zqtnj1gYR@~=n@v5Fq%;~a1$h4WYiMsD(c`I2fhZM_S&8hiN+Owu(2rG*F$(gaqt_q zW*#{Z-9>AZSmyU@MXffnz~eyw_5VuV2mS72(s(6_B}!r+--fG(cJz7Mk)GbE%!SS| zU81J96{4$I88@5rBNQlUQ%@xuf`S%s!m^QdF@mWNa%Qi?M7I@cMr}$z@s>kDzx1Jz z9*t_%f)J!KhY{PXi9F&@5fo;OB0C--!<&W^x;%n2)m+L%Q@Y=}?&2WI|MX)wvX9Rw zlG8YJ{r~x_u+LC%02x!L>F~m!==^aez?3)?bZeX)*Q@q(2gvcUqBc3snSU3>Pwa9i z4rZQn^qpjj9^?i5=&sYY{!W}Y@HF|Z19%E`?e)gUX_djE2b3<4bSdHOyHD7n66OOY z$_@S3Nw&g-8EmC!CjZYxqs#e*t2T`okkm&-l3#;hXBaY6Wx*~wIkqKa-0xG9t?X95Q=-H&!twI)EteaI8^&6kp+RR? z{B3v)s!eWwOPHCtZ8o0@i&cL}zIaqKr6ATd>gW>flW7{GB-15Mg1M z|GhR+)|O*oFoMSMeQbK1hf~&EJQJeAGf{yY^B)@-(6cLh?ng3WKAq5ZM zae8MX$2?bCpXp>!q#ihmpP?23=Ow()KFWLAp7$V6kisOH4ISkH=8N@mxa}=l_jH?a zY-%r0<#47^s8jgl!>~o$i%Dw5d7pJMGq%+emiKw7E zJ~Qf1(-scALI5#ZmBiw#%Cc{(XqRs_usD3Drn zSlOZVI#Hn|(F8Jk`zUc2`S#+(lQxGZL{V=rjJzHsq`4d=&;p(nPeEPHdH9lcilS+) zzbVAzsV~W?tz9ePM!m!6_lUZ4dyVp9m1H^Ju&SQ35Q8oEG^oJr-x~WFln(JpKTTI? zZ305VEA8RwEx%D)MnkIth@d+I>Q!O~J!We_3Qzz0a1-FLMS__)_?p&6QILe9NXGoMlHR41$Pe^JwC-EA<@ zwhqezb{Xn=<_U?vt&SNXj7R&)I=S0cBY99+iJt=ByC-J%D()xC!W ztI(J|SCEP+&jUm$6^>%{r1?L|P%T_;{aa37ZM`@u+^O1%E!IxUbRX2Z2Ss5$qvZWj z=CyLG%`0oG#t>Cj4@hpNZdRCF+3#zA`-z#ZXWghyW=sG(umxbrqWy3N?3e;{xy~38 zV1Hx+_64|f=|II`K;BxmXCE$j5tru&(I@hy1(epG%R|#P3nNydM&HBVFDW+TCZ{oo zS&8^bsW@ewS8r03cX)Z`nl3`zM*e{69KYQ2UWlW?5(Z5YKnXS2#&Y}1Q6pZ|QHfrf zm9l@JMvb#U^$rB`DQ;C!@g}H1$$zV4zz-$+>c!PqFbp zE?<(qer!Ztc>EW^U9v$q$;#3u7wx}y7VRPALl==7+sTfW;!)z4ud~HgLTx@=g7kyl zzZr6t0?l%Q%*pHv7LyZ@>NP@*Oov6JjV<4 zff+X4c%mneg!pAqk)U5=NWr>`Xq0FC9JED2OK~1ImqL31`iatS%oAQ9NC0z>3J#(! zmTdJtB#*xOttZ;1N5HHSE)KI|TCv4@M7&KwShPaE_Kk7G#i}QH!nh1Ddgj??eLJ#p?|f`!;fYnWF=D$w<7{&b$9pGGVQ*Z# zZVAGq9`9*?$#mh6#H2pvCg>>)Ej9>l@QQ0qHq)>_pvRzi6f@%4y^#JliU?B1jArh) z_R<#p{pr*JU~#YpYL0M=i;S^Jl!N3R%5x9-)DBjvhW7!lw!JQ1Bym-$sk3Rx2^+L) zI>zW?v|Snqge9}64bVXn3EX8q#h%$JsHrb+KOX)X=q3f(oTunJ$wS(yv9}KWC!(~9 zYZBl+ctDj6k@LpfBqOwU)kvxFBGAL3Ig7l#Hx=|aC+fAhDzzrxOm=JiQ(*An{2eDC=O$uj0k zzdjYn#4b3ZbWXsvv%at}eZSS>m(Vz$dI?J-v_j+`0VT~MUIaH84Xs<2&g*sg5usdT#& z60Afb0^9jnk44vvmpm$!zA7QR#y{-7z4N?9tNcZR=xfd*hQFLQwckb43`S57EyDvO z5=FKaxtvEsGalUcn&ih4TXdKVlI#v`60r~e3m$R;Y!bs4JMbx6J!G!DknH)JL*X(A z2GaqUy*G?3<7$)NeP-Rq;!K*9w=-Z0_vgYEHS=-G#C|@v??3q$o`ZG)&%nQ*6pe5o6k&YAu*Sq)9(0`7Y@#}2cc0UyHvclY6hS4z)3oJ)_ zA_KG;I)jxt11v$LDOzd~8}pZwcH3WKC_wKJPng~$%%NGA&WL?sT~=BH?kB!;EFxJ1 zfI3R4Amk4%CQn^HlY$Ks-K2@!thxwv##sbE9^`g#(M9ubIMEDMocObk|G0jT#A5&M zv6A7p>vUgIq+R;M!_J$Qi{nsS5AfPuMzc5EHu0G38{xe*;Y{GWlSL4&3#pkK(%VYx z$xPcc`av4y>Y)t*IG2SvY$taN43e8S)NCLajBu#a|VE(9# zHL{+0=Q$WE2DcO~9Sw9c^nKPpQpDgi0he$yJxsciu?pY(mNQCV$wlx)^}hkgq!n{V zfC$gTs<{2_(Y#rxoqgo@CP&|G+yqdr1Q5P(X;w+rh-+;MG%R|c_x;7i6YSgHIEOY! z$b?RxT{R?r>Jk9%JV$oE@L1-f9{H5%m^#(}aQ_h{kHuqB*^h^mx~W~`- zNy$A|#{xjhkZbFvEIL?-TIN@F}*|2NQxkr+=Uh*4LA7A|*!vty9 zKY-Wk51BJ*1tn3B-sL=BZ9|zD$6p)=Kx#so)~N;;KS~kR{y}O#@bq{CE?FGvif!0h z_3}Jv+8;D+o)=GCG@l5hspieIwMw^-q{g#Q5&FEYHIyy<WeGqwB*eLWY8tTYTSz`y zPsq(B4)S3SY>hGIwUiuvMYtstf&1aeIVu6=O)<+A`qgxv2JvMxbq1)Mw!gfgSPGitm>Ihw*FuMv>*-V2d9?Lk5-6f_EK}=is0A`rCXGa1MPRXXA*+ky< z5m9Cm$X{fbVs{n2$sKGI$h51$ATXev*7rg5dfnIfd*<=9A$lW+q7EG!?1QsiKbw4#ex_!{JYQL| zPK{NTQL((~&OUA>@&Ddv>O(Ko;fj@=B48-(A0;TiV_Hwt{bAUF0M(W+|2#m8=1E*e z6!J}&eK1x3!08h#ci!s?<8-bZ*r`!BN^YHclX4`IR;ecOO)x`#c$UF;z*00SS!NfD zaJzAJkvW}aTQmQd$C9kOuO-cc`MhW%J^R|ERu45(lR1TsM*;`*U|1K`*U4W$HQ*Nz z+D;-6jd=P2fR)d2D@_baxKR=5tboC-_!r9h2(JDB zPd=oS!9AH^IU2#9g@;@Ug3votF<{a+4_mJ&Sz6LTPYZLFE&B+6CMQfzu%q z^Bf>~A+r!8la-4aIWc|K&h$*>#6^w)-?QlVLxB^$4CT<2)Pv!?-;iDc{}!y)W2}L* zDpFa_A9L&!TRfge%FX?u@r)_%MrI-+!+qA&8ql0$&&}noHF=}ki_3M>gx>gcz94RQ z9V5bcu_rRvm7!Bk9`ZQ!PGY3e_Mh7qpG7LTtqyJ!|CWRS{2X1r!9JJ%8%C_+Vp=rb z7xDCWAo^xk5_vy|SAsgAnT76Q&wYjK5;Vr1w*MG`Tg;;e?&@4B3Eo z(W|gcCImq`rr+8{WOrhoVn!!Y5GW)uE+>hqYIHY#;y3%PZ_8}^@T3Z*OZd&ow@y>hyrSZK3j3u*ZT zkJ$OlPo{BaTRX~dU+vE+=fILqAFYIugfumV^<-yiHdE~e+4J@g`Gqunm_RKfhYQv< z>Dvovg6$I^c8l)mb1PU&a`-U=W7svj^hgoT#u!6vC?~x%6i~5=qTfl-Hry=^R-baLgKwrV^%5mbjf%am({+4Ucm4C zl#S}v?tKd|@NAk%E`=Us#M$7%fo>tY<31Q1wk(wkuPRPdu9o6K6sEqI5n?V%`sR{p zv`3YUl2*Nc*%VTjA+{(PIAlZZ}-nM5QCgD z1L;CPFTS+IJx%p`YV&yo)Vo3^X!RbK=c3*u`EO=NL%ztVhzqv(QpJ`cye7kyDhzKX z9_E$TN070N4o|X7zM27GWCml#E;o!&90-^?u06(6fxGRXLaxh6pt-x3`pHs+ZQ?M$ zs7UY+ar01;OxUF;)qO^!G<*o<04sLlBENUgJ8_~NOosC_3@qwpqr10K(RZSN(7~aG z1NOE^9Q-MCf;z;($gx#Z^6re3#(I)E&LY;rG8A}|GFCO$vY1W(0J2m}`N@Z*(9!`h zMWR1>qzR7fxGgeRGPO7WtMjNj9rUko_^E? z-w*TG_IIHHb}&=PUgg08rs$?xGu+(3@xT&jT;n2AKX;{TSnO+3IL_z>O_C(q;xkS_ zfVR(Pn>1W7se=?g4C!6VF-~WQ@d6O{VD6Iww2p|e|I1-haS3Q1?Qm2$d<~@PqkGLOH91LC@ai?#12O4v;)ZDIlB5NoK?5Pu8=pC_Y@HXJz1$uL6U~|FrY?!2 zdi!*+Z2+pJQx3-TLIlZKb+Gr?masd_ z6z2aKW$mTlBDB)BdINaS`0NHo;RGZ0!UX)E`+8{a&PNzN;S>C~xBgi(MX?WJI@D*7 z8Uu=Wb=p6fArgIJvEaRQ=FFCczX?8EegI685z^HsMpr*sHL7<#Ee%+8P7 z{;_`K<0FK~e6}~UrKMiPV@FFe`n0QItLVf9e_qBfD*`jyQaZhC03l#qrJYv869>Mg zO*OnEnnwJYcjzEKa*r zd97Ou*Bl(B31TM|o+rTmX@Qr9sZU0ruFkyprk>V-qi7=y9grgIoK zf8=G_yDK(b8No|JGfsg4io-?``Sj-q1ptY)@R4udDQGwXM8FS&t9NC}=4>a(m;<3^ z_T6m*QJ@xB>qgJVXad6bv)#oG9?ZRX*t65$rKW&0(L@BH+AnAPe#3PIS7Mqr7O0Lz ze}1wn4d$wiGvtXAdW3bBU&GZeUEn@2HX-!u7^Pw8ZRxf9Is`ObvoqQin|!$j_FheS zxgZ%$VDiF)@l^a&$`%75S2+wi>(3n6i)5+a(yjv!xHc+@xSDu2(smL@cg6k^ z-vkW;n>huzTJTd0r4HhOJi-0&DF#Ga_yB)@7g#%2J{a&0)SA7adf30kkDL65Z32EL zW|GfrwsTIJwNrLqE$fZ-BABU~vy2Tx&Wv>S5G@3L$L5;|#UB4RM1-YSODrv`2}*$C zy~g3Ka@?A9X7CV{zorZY@GGMTw4Fm4lb_CS;M&4)VPIKvvjSQc=#tb|0bup))Xz$F z91K?gVAREHP-A&Yn)4e70mc?H21`>Ue(wOUZx@A^%cT2@csB<2Zn&4HTNhlm=dx}r zNs)=T;)={M&{g9_O*m3Df{B9P=+Hu~yZ=WSe@1iL+q-q8z<5a<;>E?YjI$Ko!MPs( zqHB^z&80bexS)QW^mf)?b+e?pVsW*djsQ(f5~k6tc=60bo{!Lv7Sf@TEQLJ9Ax?}0 zzhzhq=~PFfFx{&ZydRiiMzCbf+$KaF!tM~21PJCaXCa8k-oaonc5JC!1qJeC<6h~; zYq`kJubNTyDQ%^-aQ+{fq9ev^Qv7<$`jkBa#M27ws7x{46~Y8lw_F@r{)9ch&Px4$#cU6MV@ET&rU<7~A}Cj98BX+j)G3NHeakFHV-g2o|~WXtTqI0ilU%cSp{lizHR z0`lUJ*VJ#{X{%S<%dzI&!kgwe)7KS%oIrM$Txm})+>yuYpgr!VAk6myV8e#!w07vC z0pp(Sc*7o6Dv}M6B%Y)sNe|~CC3u^P#7avoxauK5gT5LH>|~O6W9WuXcO+V(QbKgn8&*tW3*kXFu;-X8X3D|tRwEfegM~D<-|qC z){c0f`E4m2m3%UR!ebWA4qeaQY1kQH%jy_*v1YvS2KET089Ss;`rBhnGJGhQ)62uo zxSqM#LPYgxSPSg1a|x9hs~0-W1t5KvdH3v1MXg!lV%c{0*uyI-bv$}ZA9knSR6q{w zy00r7mqc_uc`;m#wK=HW>4tnY^cg%+Re=3bRgqtfx1pyzklZKNoR+KO@h=3n3$LwbuaQ$2QyH}!7fFM8qAnin56=i^ycul zFlZpobQV+^Pon^6bom~z?4WMp1K2@0E-htVmt)s6e+TD69zlrZDZ2qj^aS`U?76bv zoP5DoAgrC%<%e+19jEp|IH=y(1y6)zSEJhJ@|5s`pGuu+`BZhC+YFNFBvQaPmdh11 zz{bYSCy{^G7}He}5K=J0SAdStKVdMMkLVr`giJiyc)}efdHLBE zD@muIdg;S0h^UDMrVI(JXa^aBG|nvhRDez?rLZIXCAl!KKKe7KrPcIoc(xFiDCxji z{)-vsR`?nn2c9Hef_(K0=^Ay}}%|iEEB!u~ha4Kf}#+v;6Pw+!yP@*x62zNNUspL(I+* zE&-Ni%MIPGmQm4BID~EKwo0*+AYOZaPgka4oson47rs-TaTy&SGx=dU;3A~!6U?=+ znZi!u^X-(r!-G7&Krz2^ElBfKD~>&>vMZ`wM0%{ZgN27m*H)t-wAuaeEC8pak&Gh( zw<0MKYRcIO%UY&SRVq7@nJh;Y13h4;LKr(}d` z;T>5*0ODwwk~{c{D-G5ls4*FI?O9QDZYrW3P%kL)Y0nN(fQ!GAzuW0k+_l#|Y)Bs1 zmUx+HJka9+ZPnGc;kb(A0I$69zDo)KV42k}6gRwXVW1NoA#hC;AnUFRAnoO=KZ(BH zwqzi`Ur(gje+b^tsy`tvZ`OBWsU5*8dREk37gG;)F6QF+>HGXNzs{AfHTn8Tuopl$ zFI1|L?S&#D&>`9u`WU-1!d_9#c7qd&cG5?~-fmLwtE90#)V#ODc%2Ik)%iZQ_H$ju z?x&EX4wqH}$!^drzA6CaT15%qhW1=7Y`41Zv`#khQ|5oa?W^&YZM+C9r7l~ZCl=Kh zpixwBH%Bz{%Ahsyc$Y-Rd*qpaLi-(~1>M@|_0G1n4*RFb1Au=#Z-P3(j{$(c*UQ!T z`X&vQ^UcHCEeFHx`3A_$aIs%&cz?GL3^!4Hoa3>I-=Tlqdu+W&^_u$$w6<9TL#eI@ zFhVF0A9{_Y{b%apvJ3hVq?~?G;I>i}xH{CU2>FdC0_T|uHWmKQnTzJGh($NRP&mhx zYcln!NK30;2p{pyGRgfhwa`XYZlk$gIR}Wn zF1;2u3rvd*mugaaubiY$#gqm^v1yX=s;pkqz+S(nIaLN4I-fT4xF zGf4hrZhrggO*LmT<$-wUVIMp!4&C}lbS3m!^!w1(2F8TX?oj#BdKi#K@Vc09Y!ysqfNn*~^~lkf8H^lA`1RF3qxiNkch! zJ-S9L>B#`!P#h?lEaV8O{csfwlnR2Zadpy*;gw7^;`4N^e8o|c zj00Y+yTz_Fq}Qi}zhI?oz4aMDUl2Wqq@~OaU#Icb7rRr?9)>~O6i-1nqc^V*rwT=k zoGKkIW+;62j`_5d;^sVb-r01^+lL;qRUg^g+AJ}EUZM)){=r>ve{rpc17AK<&1h#4 z$%8dW@Y7vze$F*)L@e}=-wYU!L*CZHL+o2*E5jH2xodYs%ylcmU4v`$PlKw?v7X2< zHIb97mOaI_#A-DH{_xL&QITu&6ha_3hV`)rQ}ad#z+iLdpK+v3Yk@#%KDt5uG_v0P z2dJT*>-ZcG)0p?g=FenxJTERY3!F=!UnG6TdP<2xN7J=lZ|n^3JVs^Hioi>A9FNoZZ8cf62PH;G$C5S zBr`MReFH^Q9mD}AE{DU(PEg-;k#SZ3fk%ycba<=ABVN^FQ=c=#o|-S@9^^f|!(cVV z^XB;@*yP2Rz*|ii{}c9?Z;M)U0aAIKlCFmhKnb2(hibxWMR!DNKb*#myW4)yIL%L< z%=^xyAro>$4U+u~jWmgQB*TE*#(llcj9Cg<`Z+r(i)AI_Eyp-Kq`R(-)-o;!*^uWz zT3iw#9j1AM(bv3t0PKV8SmY|Yxt84-tZQ{kw1cNq>CD}rp0$rQmd|#K}r;#*r+>nzRSJ^Nf+r7gHd-U6JZioZRe$r;SZRRwG3ESb`-IzJ}4SKpha9)%vG z+J;oM`4?r?j5;Ia^-8Otq*?RL&Md~6eAr*}oi|P~{@TIJjL=IlM8N;o!td%1#P1}IC`Z7P1X1jtDCwYP0q?Fp>6jiPY85lyPB3?<9!^b2B`4c47(3N(BwLX`Olc z0H^$($O`Ht*__`k%(7A2WRAh4>slpv46Jd^9_Zlfl}BY?eydqif;CymBPUn?Z% zNyTSg&y*vDX?^rBbe)JV)Cq~=DeD-@4$qLQm`@ioV2Kc@Fmhs4sQ9d<1O=9muwZw` zJ#pOaRnTg}`H-X|X>@%a zao@kTg)A6GR6o{=rTgjVXh^^v8O}2x{%W>B z5BrG$UvCj4@89STm4EExV{(i_Ob<|rWnaEvL!nSD9{Vvh!)u!lccTJIm|F?+1r^@A zq|T(G9N*a|YsQIv7w0Pn7&VDlOD*+8p3(J}{Iuj6tS4v+`T5c33~Scbax*W~tdALv zy4RR)=`gmbj(!~K{VaKeco=JSUyNrwk|5gx23(wyjSTH?+ zpIw%OM)uTQKWoe%-hQY~(mo^tr)xe?&wPIDJ3dJ`&>tZ(3Zl-v@WG+z5`P@i97+E4 zmcff#)*hze>n;G6eqyijj@;lW1Ox_}-J{^%p0@;@*;|JOo}ngdnB=`dwbML1G6YTp zP+PP53WIrRz2Lj?%|q19B;+3f9K%X%>5F(x*##Jy@xdx$~%D5h3c-I~&K8GQs}ZY{LRUjAz2WtK*xWJ$;~yjPYg##_Lr57vrKh^0=bS$p92(G^W6*c#O&^&VC=k1=XLkA5r(mtA%Z*yOn;1?d?I_1clO}d>Fa#_qItaX*C&q*t2|I|S38l_|>@p66tH(f<&NrgTlQoKNU+v*1J)|HBuysGSQ z2|NEdcQdn`&~mT&@`lD7iE`4;?UlWnhP}$}&w-}{)S^%g?_L5j&6TbvnO61NMYWwY zP|z75L0g;lL9Y>d)CC#y4Pc5Fn+M=*qLwVe!Wi&XHH>CISqL<-B}~F|Yt0yaMM34w z!p9p7qTz?MGk#AOc%8JaxGuX>#m+$_1JG8{p#&v9q zK!B%bh8Kn=!r46N)F|R&B(Rvq2g^A%zc+9ugn`v&x1onn``pv`Vfd)64RXJs`s}W? zv+}|xUnC^yS3d~(Pw)8zou1t_aZD;xUgMNkv$4_;K+ZM;&!(sxO!d&yF17)g z4f7U|*47bsdN+bU%g5EVw;S2A`De*8G}!N*L8#?7HRKCt^Vx9Ea3UaeNPEhM=r};$ zc(@02SJZdHMW2HPH%7|&QQoMJqVrz5Gf%#4AHtiy1T|LldX?#lHgy-~!3`fWtD;F+N{V^-tQ@cTJ6^UVh+q4gUZ7(NYTOQJg~>sCAJ~ z_L*?=bJi890UU2*YQ{x;dzyPQTc;zvRWW;(zV*g|FkQ@bg=)WO^TP|Y& z;2NhhU$*mAk!7k~h#fbAe}&>n>R)+YYf`*qjY9xjF48K!pI|KNz;wIA;2>SClaSLv zmbS0%-K^yeqF#MhpOs>$xhX)f>DOXWN^;OQ5}nT?%ba#BQPkP4mzKQJ{HVm_n1sF0 zo8C_DmfQ^GnvpM3;+wf`$;?fTt2M!+-+n~C0^*S~r4H4cIDbmfVZ!haPPRC0tSOk~ z0KW;{=FjcC0a>69hF*egYZH~I|A{&-{K#1}c28;cXmZn<6JSDe#PppVZ4@r4K@U|h z_UhaQP8~@}4?*%p)Ey|=5!{6EoJtOvCS%>D=r!VVJ{bxed2WW4JEXv}j|$hAR9(}s z4i_sYll&`wEZfi?sL?VMc{sb#KxN+6y%&WkbN&P(*o0on@q$_pgSSe8z;PpP?elGD z)IzlXc-xvR(QAT3oSb1zJ1|*Ztv?>$4jHsi(1^AGG=0+QX;@Ekxfj*@d!KN-u{6WR zFL~y6_{qF#JdD&;t3Fd5g{)m5i0C?^L1bFl?mW)x7lhzpR2eC)&Clnn0>%T8F5e~# zG8N{3ZdA0zRSX`A>9aY#a!EK_7!4s5eL2YkO$VqVjsMQNOJuC00p%|m(A~x(Y>-Qj zQ+~{(xnjKWDop78q-8XS#}dkq=$8Q1Fxxa8><32KlnR*rGA!LC32fyzhiOVl@%s*e;AQN#Nip6mduj}ggV4V1LDWmtF7p+Cf4;XX4nI7j zKZYvMtVmp+rsXD@T0MnQv;ye{AYpSVA2-SJe67&(+f&9yE-XP8HO^p=toE?U+P0=Q zr=xZ=PGLG*b`^gX<#OX5xpL~(d)D#cGUD_Qmls*884ny1DR6pL+JOO6dC)lXTR@QL zPf_JLX76y05UwF-jjNCrVg*{x3CtH^8`;LWBmRm2)Vcb;P$OdQ&2MCKX@Qv(wHTz9 z_7G1^@xiw4hI5m9vmkDYveT=UAFV+4h9|T;8BybR!V}IjO5+$$Wd`Fd9$S* zXT*EG&uH#g2G6`L4UPLy`SNT1?E5g}kc5VH z-!NBwU(l}D$kzqArH~`EZD{xUZ1-KxRjzal6|4J(6@+K3;g+0Ds~Bo@mvrYeK-g%`vpK{qnwblv`=4VoW0?rHzeUnUHHdI*7o+Aaf0aMk%Rc*) zhqIe=|7nQK4<|kSB66*X-uoXXJFW)^v!|;(<}Z>rlE3zam>cs8br+Bs^{*};zk@(I z2TfMSQ~oxtJnc}QG3~(sMP5Jkbpw+bYP+Iy z0RQ-i;!8Zq91ce9Q%;g;wLe6lJLBSf^(gnWVH6x%Ta<2jGJWzE?6Kh4^lbi%mb8Yz zfCFm*hh^!YGX3jSD+z5vKJZ(Utm%B4GO^N;f3`Sgo8S5VKiTA*|*_QKPs|SO-FD8VlmJeaNfqwjgXTN!m_3e{tPPq=sTp_5`jrP1H zh!R{0pluEA3$Fobb_AWF+wiZ?%lQSeZGC{x(d#YyC+SXkJT6JAMc{uRF zLUyBzw39HXqBar<;Hu1eE%zg!EIIiGHrM5aFgfK)$%6o^igcM1mof?Bn~rk_G(g|m z^tgutwSu)V`&e(JII8p?_j_={Mtxg?^>w1g>0@pd1KT$7zLN$ZL)rrq|6d57;p7~*m zMc_&P#Hx}%leJs6CeDvimmR3&qv)w#dyMAxXXB^OGilpVjgxmm^|60<`n{@Ro5mYK z+_|VD0C2C|zqi*j{F|=lD6)9$XefJ^IB>bx!xkXR07ZSQ-I{oY!FQO|csZ)!)yc8? zIMHG28A?8>fr&}`Pa7C;0tjihJDD44Hsj1O5XEm_H|E<@i_}?_JcdsYlAzz)2Oo@h zu+~JYWf^_-;rf+s=>`b)Xl@EAUj%2;{c@wjc5?aXazAA9YtVhGuP~xzOEicTZj^=@eF$ZB(%01TX;-9x5WcO&%?F3F ziR0$N9Y1#vZ!PswTKdfL=NM;dK*yqZK@%E!6dl4u6#Vq;JvwQ*5-$D3PsYCMScW#+ zQ&>vr3nxh!!Y*XAjX`kVe2u9wAS4@d0CX%kkRFh7HC73&2~IvPLD$1<|KEhSGLYO3 zeWB(*EO9XwCjA=fk@5~B7$gV2K4y++XPFG`Yh_+29!YzHS+^LdPuc=)ympgy2;Ip2 zq`gle-(%f`svCZYby?WM5CTX)TlRmv!jY>^C7^rHou%s_GO`Z`CyG1!`IMpA;>8nT zsaWKlY%kQD8L@B3;mJttV1}R;C$JjjGu``ut~3VQV+9NAt571A-A1`@=mrGHH z5O7HbWi;&FwBLJ1y%b$+f666TcujuYRm0!Q2M0U*fQDUHuWqzeQ5SC)FB!8E32eYQ zuqLsWK|?Jd4Fa^!h*RJFK#^0W4E)SlgpdF9%hPsfl@YHOYrasEUD&$i zP7~;H4#J0eRQ_4L%!4fyt&}`<)0Y)|L3ibl-oOL*fCfn2z%?vC&6w1!=05HEPByA9 zHlTP9Pmzl)0^|W15o7+>#AsqqRaJ1rW@(nX(6D=`PG41jCS25z&2A30tK>Ztj$>=f z0^>~oq(H8Yz$iI_cEQDo+A6)zQd)_rVA@GO?B%SW24DFBEoRlBvtpe z!(K%8w%W{+O7ZE?92v#@2k^;?)KW2fhm+ux+*1=5EvwqZxE8zw7nf`kI?ya3n&3ir3RQO`h zEB9Z4J~(xYH2ZLpvL$Q%I8)MgB`y*_4{j2${Si4huV(<|3in@Pq2?$NOSVf9TV(Jg zPqR$i2*P@_tOU|!h`R7|smbj3*_0ihA`773sQIQWEYeec*%>KB^D*DQsSz zjiyxu6arqLMaveP%}iNOlH+m@;hwxh-VgtiyW`tGPIT)BCOTJZmn43!N59mnYXH=I zP=$(_)1xh54##!2AbnHji$c-xU?WU(Q=A4FKojEt@4k93FUkNanoNZ`n~5-l0A@am z480FfU$v*yg5teU3Ps50vhY1tF37P{V8{QIVBfM8gEk$rglO&w?~sr~+3CqU7h>cQ z?ny?A!56H(5yxMhpt1DPgrGS22-xH|9udZkYzs;au~q-MJ=AQ5wpxz_C3Y{szDN`Q z7R?E@aQy8aS{OV(E*i9mMNY&Gwtn>mAzGrl2}aSiqNxm;da(fPd2@1y)R8TSw>zRs zd`Vl8J?lu-eAVW7vou|&x9EY*Gw%e-W=sVyH&M$K;i9mHh;-|>_s>lZeG{fI{V{e2 zy#6j(w1VTGBvwqoiX5K2$u}oy_mug37SmJle9$aZy_x0wa`#SsyGF^H?dEfH?h^$Y zecGeRJi-Hk1gV(0!hUTO&^#WC`3k5;C%RL$5+`AiL)cDY{m=Bb61#Qh2CLpt;}<OQL;BP$r-2rw+S*B`F6qam;W~>JtNqR$iaM6g z)zC-0ck+#adOB2b$9Z%!oGf&;`W-D5Tie#ZXRgbouPS!rz<{i_}kTu1VtELS-X#TJe0vz3nbKa*nAOjD&lA{xrhTtCg){ z=>$Zd@bovl+0qBly7vR;E8$$o18n|`Km=rs*@@+5#q@4)RshPC-T9$mVaVV?U|L>V z>z#I|IJz>J_wCEGwY=sMzT6e4{r5gGGiNwB3nm)IcG`=MI=oCz*a?9oURrTY0C>P0tCuAWNgJvS!5gSZDx%3N>jv+xFURIdL7VRmqcar{ve< z!s!x0RJKm$7oJKRlyH|1QhuNGOlSd^Man9i^80F@$)NT9r0XR&i)WZ>`|~ z+sfG=Q}kaEH@@|f$*~bsTC~Ymj+q%Kfad`C&39z65XD}Hm(4Ge*`T6BWI^W*D(3*|Cfe~W&sl;#<1!6ulw1NOt zFvXRhi!5&&A4u=LP!NuD`HT9QEI@VNA|Y|A5-*4l(Lke1Kf{OshZxnGp86>Te}n(U zQ?=F3;+L=!J>g6ovhWqqus6NHxb=833j18`;|U@F5nQ(cpe$<~WGl%8IWH#&YE(q4 z!*8FQi+lpzX`c$*@eD3@mwh9r1ETZd;^;LT@N$9VQd~sQ2J!m_XodLwzpDgDN~K6M zO5^T%nXxg(DKtjiB&s(qH>t?7$Pd3mJZrikBC7c+pOq#cE31$jw`G`Ig2M5+-rCaP zSbK-EFXDx|X&cVZ^$SOSI`xiM`DJG}+#|s`d=91CJ$uW}O-B>TpSP{{!M7s45FwP@ z5e5kxQ~t*1E!TZP(43U0Bitz^;_dN~ek>y*BkaK|^aD(&z~=RNlp%h7%P9K?}`LN@RMPpe1@qAhc(VN`7tm!r>r9F0~ve& z{dqUp@9DW{!SZmC$Y{gY=oi^1$Zi9#5&Wn@E&aVq3e@}H1QD3eSV4B#M!6G-(E5s5w z>*4#>aveizZ(9b|n9fE0lk4y298^2wkoaTH@ic|qiK$~zUH2uPY4ti`Xq;{5g;$== z{OO)dzg=6NOeku~nJo4foC~Q{gKl_m8MVn{L&p2%xHGy|2(RhESk<>RhNFb*x0_Hn zh~&?Tcl~U~QoOs9ZhBP3LECT5jPnzyX8n{ z6F8qArQQ}xLAVS@Bg@LxI^eNvP9U9uTS2lGH>thXb|H!_#@Eh2*;m(N+Pl4Y5Ix^M zq5c&xE@8d|Jx@(8%s z79zYyjeRCbhndw`0>!DOP!tLDx!v;*Rfzkj0OpeMenH86S|qj@Mu$O!LJu>_yMm0BWd3OF`h>~f!IE+ z!t?L~bXMAir%p^u%Ucn7E_3kRN{HnK8A{OFF}?4fp*7oU3OAv5>p?=+49)MOgkYH) zO~-0EDEcZ_(r|2Sh_5&Hod_JyO9-@|oz1=pwogh6$p!DqM?xN8?dUos_Mu7z9l&6d)mKu(xLh14dP zp!(=?tPT}WW9*b?cB;Lq&wJ7d=Q}q6j_G}LAK*Yma5Kl4GZ7u)!VUN_!w(*ZD$$k; z;zDJI5h*T;Xz$3qv=W(aCIfJ-Tn4wgkgUaDPNnSKC{~B(>;_8u4r)MB8#4C$ge7OH zcjh_+Hs^&xLPCbP0kiN-C;B; z^Vf#kU2X&L(VY!bCSeww`Nr{5lkM8)EI=giG(ORLbV1rhTYBSqq_@^zVn5A@=-iDl zvDA-=EAz|_d&Qz-6cJ6Rzxi`;H~hLLnn;LVNkS7)+)5%jmjCfWa8U)Piqr!-=b3VD zzf>>kY{=FtrHK2(+1$%_J1U+xrkRU4#26RG`-Trr*rlnCAeNB3KFprSi8g*0K2bJ* zT|xe{3YxFZ`?ykMn9V2Q%EdQy3B>ztkN2506zI_kTU8z?6_$ z!)z*`hna75&)77Z=R`@4iVa(6;kW0^@5b?zo=W}eJLeT<#!-dPhF7M`0f(+t&NJ`Z z3stwxE~Jv7D8aX!i19a6q|)F*y%YOW^7Xs4Lh@XHoqUXg6agf)!9=I4=jB38ao}7Q zQa}Mo8^m!mIE+d0r9<#xap)W~A;6VYG?2!inCm3L$&|&5->FkxVFcBapM2YUaB?I6 z`(OC*R@UrvgntTDDfFPu-2Lp{=(swa<$@rE|I$(quI`P51wVHa!%EuV_*R+~X9bT; zrO-Qx{IUOxn1t4Og1{Ge8|ZkmJQ);!#1ehN#bv-@)oH{oZ{c9_rI#CK1@nf^x>y>& zHO(r|kaYfae}E(&%&Ge=i20aXqc#e-IyZk2t#B%rkqEXDcB4=5w6SQ4GGbUPZfh#k zXr3{C>7OGT~I$odl zqePBn0d_@{Z(95!Ri!HUqg-5^NiC%F*gV+wMseZ?(MrIp~^D6E!a2GfMXSO%*`mYTvpk9sn;AkZ8$#CQ?M&HGu>- zs8{2S6DCjdGJ7@rwYU!a;NsuF)6iRmD*Gq}+tc@O8Kq+_u_3}yCdvxV6l8=gBCBf^=R`6&e9-cu*$ zWg}olh`}8qvD%)LEd2U+;KFsT&`Bd>*n)H(?^p3n+nW}y@L3JJK##aj9Qcn@7k33X zFoe3-B&)46B5USm`b3oifIEkZkrW^rl%zg4sIXd2&2ZQDzd}hO4k%_5Oh&kfzT<)h z?;F{*7NyzvNq4V1$UUQ31VHZ5FX05g2;4Lzk6Tt`v-jrLdjtKdNnnJ5`L)og^t(;g z9v3F-7abTfhlG$6G`5_e!0^QzS00cM*6(*}J?n05=0ipc7!gDM79r3FX5NJgdxGP? ziU(0iMK=FRey_V>ejctfZdYvMHFW9>(6R>H8Vq!NHJl-iOp3miyv+WEDTRvPhUsD zfPz#Itd`72C1kGufS3&bBf6U%s=2MOu1K8}%>l2Lmy5zFOi+#KJ4m<}Own{3v=h1D z&qneQocWh3QzE6QqCjia=Ida7kB*?orDY`)FAhBgbZvMqRVa4Qqc?+WF}q@9F9+fG zSpi-TSM`8JZ-7O7(HOpb0G4}4>B@qTEpx&YNV=PN^>RlF8~_GjC*9?cZWRGuXE^7@ ztDoFWt{)3ju1_*#a6yFAgi)hGs@^6 zgsjREB29+21bya9^g{{2fpN(iir6jNYWxN$fsn}%F$v}0!I8t-`-IbmR~&L*6lw|Co{XK6ocH=|hb<0${F0u2zfXw^VOw3V24C8#C3mfUiN zK|rfB^#>uPL%w4Y^ItD7yOZ>C!0K!|D|~lI$uuos%-;Nx3ukw+)748xf-?%Gr$iRXH^(YS72*Fj4noh2)?Y zdLOrWCZc>F`S68`W9c&613jp6UXOgdMrus~_{06;&6{RCQyYa>x0z0A1U@yI~GF)^xmEe z@kcSf_<+^OSW3hH+8;r=sB6^OY`1*o^sY$4i?#VGnX2z-w{qjUmf{9O>NndUxf?nD z53&zkk{UOcRC6mogG98>)}KD~;v@D>&>BB;d2x-tE3Mf})K^is3eqllK}@Ex5C@lC>w! zNhh>o$;!R>CAejY2qn*i;Mi4ECIz1{RU&E`2>bv4GtKCmn#V7LE~3b#N^D(K4UiWD z6MrKkj)+k{zz`oV<)vvmt*27 zPcr?Kjz|aix@7+Wc6M0%IL+rPP_+L>$TX{#K4Pjw)G`t6P?B(WJct_T6A8!P@8P>9 z9EN=4-0*{7j_g+itY;q5L#VM*6#d#DQ*9C_Uy57=A;VHzlPda}=3-_LB6Z97KK~nTbd*&Iv ziIf5Uh0&f~ulcfr_L)KmVl$HK~ZDlf}m-^yHt|00vyqBx&rEZt=J^gZEzXM zsMrRv{znk0R*@lc=@uFW%T1Z+uK9t=ru~~@uwitV3j%NOTtET7PoS8RvI_p}!5lWy zOTvPmUT+)OtU~mY?uMZR+uXfB3o(>V?kb&*`tHhx@a?Gw;wm zTe-cq&k^XVK8}kG`VK=e?pah`P}HfyMj7;5o6jVvcsT9MX4;{eCh!GTjyd96a`uyf zb7u`IT5GOZbkfiHx1TrcX2aV|*=LPc#%usfX_Z_kr8SJ^cD%|CgnS*|nBvx~*3$#d zxgG z7=z}|^P|`|-w@_?@H)S%R3LgD@fIP+`lduwUM*x%j#aMegKYQ0pG8! z0N{ah$B!EYS|RjnB;!8%G()?D6gt#riq7fopeRXmvQnN*ASTdgZTE6j$Jsa7eVrnG zbOlRsN&m(&aQg|Y1HAt#;+3QA=)(u>Va`j**M?$^x z!AFM#*&$=D1BLqPH7R5K?hC>{U}Uk#2Q?VFpst;Q?hrDkLn0P1Xsuc~!k89JmuG+A z1Ivngj(4bDFf*PoTMT25Vjz71RXcN_`x#bw*Q=zizaM`6a zES#FFih?vfR8}Y2A9wxMVp2)aq7EWm@+Sr(i9g9fn%NV{%Ia-nDBAZPKKLOwITWbE z?ryV-)<}IWI@qa}!U?2!yInU!GuhW|OL0^9q1QSGbz7iuxiKuGujLC)c3^a_20Mxq ze)rJa(J9d!U{3xV_cnPh8nW)iOxi_6VX6)ekc-jR2V6EE2u?+FlwYnnJ4D>m$^q27 z)J|t)nJ(PJKgX%(xil^gfz3!jswtt~GIC?Ntj^g!x5us{fHRi(3))^f$n)j6FQiZQ zC(cli99Y6KiB4a?rLHj)=)|*dNW3SdQG8-A={oaXcRj5Y<~$RX36_9k_&u?@^~0$y zWsL_ebjD?iq+M9v4iv%3FXluwPo|mdJ)bZs~GD0juTzmZ;T-dNq$ zln9-*K-Bq33vaEai`VkSgPNzrT=*h3SN7jlZ{bDylVZ~WzRD%_*O;||vdj2MO*&5Z@8TR} zP~`P59Z(xkNwi^t2#`4$P6|B%ALw`4WfL;zZN31gfi2ckJ}CZ2(Jvlo38*TrVtX#6 zgSh3tpx}nDWLL(RKzjSY=wL{UG2qvduys~Vq?4Q%pQ^O80&r3>2Z1PHwMR!txWPZv zg0w{(I&oC*AS*7r^naJjFzyiLQ#%!9Q85dj2Hwk4D zQj!yUey$Xdth0_$eSn7n3Kk*Z7$;Q)Lx{rr`+i|U#k3}7#s8J+k zVH>468{Wle_FC3WVx+?epM~>ZZUo5Aui0Jz!!ZBf|87jvN|VJ==g|WJP_|#t*ww^i zkN4@jTUL#c#4M{ezC4bJL+ZFhe!^%o<+l~QUnI}?D#U2(PI~S!pb8A1ug#*>M4+k~ z&GcqSc~p&lzYTwOz;M91hK1aPJ!w1irn2da|Gkp=I-K-!ol|aemM@oZvuHEsrmPRstP?1eTg+ zhWzrgS|)jGQdr!$jkUzVapog$CIQgs{qw^rvp3h%^I)8L$o!Xx{ntM|eY_hLu)}UT zG;KAFK~)aDB1s*e~mNieh*x>fNuS}4KEM) zKMXhIqRP$VU zEca@dDe~s7u;6Lh@;3xFsNMiD35~cbM#(5ZbA2L3?ov1Qf6#TnOFKjsQ00yQ3}@Ql zf~2273>hh;7@0pd;Oc>%E4&T848v+$XJ~A93fAFoBzypxXzuirK=gqH(K&q(>)_jM zjJp^4&~ny)Xll{L(_NZ4-|F4OD0-$UbQqa)alF-J=othiI-)*qV4G-`S~&d;%aS-@ zk44AG*31&^j5%`wXt`3`i`Z=|PYPlb^dAGfQo=z{QUrIMzCw|qndGb4)2lap!kte6emn#1{rclkFe7Y*Iq72SErAe^S7O zPP>(4-qpFrREfmY8(`z(rQGg$U|EPk=qII%D~`I$Ms`fOSmX^%TQq*q5eK1XhlK#DGb@rUV!X#?xpYLl(o z<|q1+d0ikWy~R#C-n8B|q5v6bwS$%a>SZx&LRFji*0~;#R_wDL*^U1;6W_Q( z0GAkssFL!=7uY}Dm{|+SsVQP2Nr3NP4O9?S=A2=I`FncXmG#bViWyR?7{AC8Jcg$r zH}5W1edBtN>#v50{E98C8%RErP5&%0==U0tM!$$UQsE!@b~36@Yk?0#Np(bM#P~mC97z{S>>tv^ck>N=U`aKXqR0}`O3xG2VRdh&S1aoAB(NZhb z3zEk^j@xN*YB_Rg1zf0>TI)7rBRoOl4t&x_D7oylL?=us+Q2^z2$K3g{%5T4$*^k71=hd63hFOA%LEor^By1I2`~0 literal 0 HcmV?d00001 diff --git a/public/logo-calligraphy.svg b/public/logo-calligraphy.svg new file mode 100644 index 0000000..591aa65 --- /dev/null +++ b/public/logo-calligraphy.svg @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + NOVALON + + \ No newline at end of file diff --git a/public/logo-light.svg b/public/logo-light.svg index f2f64dc..79da069 100644 --- a/public/logo-light.svg +++ b/public/logo-light.svg @@ -1,4 +1,4 @@ - + @@ -32,7 +32,7 @@ C7,15 10,11 14,10 Z" fill="none" stroke="#fff" stroke-width="1.5" opacity="0.5"/> - + @@ -41,7 +41,7 @@ - + @@ -52,23 +52,23 @@ - + - + - + - + - + - + - - NOVALON + + 睿新致远 \ No newline at end of file diff --git a/public/logo-v4-backup.svg b/public/logo-v4-backup.svg new file mode 100644 index 0000000..79da069 --- /dev/null +++ b/public/logo-v4-backup.svg @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 睿新致远 + + \ No newline at end of file diff --git a/public/logo-white.svg b/public/logo-white.svg index 4a96760..f64d6a0 100644 --- a/public/logo-white.svg +++ b/public/logo-white.svg @@ -68,7 +68,7 @@ - - NOVALON + + 睿新致远 \ No newline at end of file diff --git a/public/logo.svg b/public/logo.svg index 1c5ea9b..eb58576 100644 --- a/public/logo.svg +++ b/public/logo.svg @@ -34,19 +34,19 @@ - + - + - + - + @@ -56,19 +56,19 @@ - + - + - + - + - - NOVALON + + NOVALON \ No newline at end of file diff --git a/src/lib/admin-api.ts b/src/lib/admin-api.ts new file mode 100644 index 0000000..d11c228 --- /dev/null +++ b/src/lib/admin-api.ts @@ -0,0 +1,178 @@ +'use client'; + +import { encrypt, decrypt } from '@/lib/crypto'; + +class AdminApiClient { + private getToken(): string | null { + if (typeof window === 'undefined') return null; + return localStorage.getItem('novalon_admin_token'); + } + + private async request( + path: string, + options: RequestInit & { encrypt?: boolean } = {}, + ): Promise { + const token = this.getToken(); + const shouldEncrypt = options.encrypt !== false && token; + + const headers: Record = { + ...(options.headers as Record), + }; + + if (token) { + headers['Authorization'] = `Bearer ${token}`; + } + + let body = options.body; + + if (shouldEncrypt && body && token) { + try { + const parsed = JSON.parse(body as string); + const encrypted = encrypt(parsed, token); + headers['Content-Type'] = 'application/json'; + headers['X-Encrypted'] = '1'; + body = JSON.stringify({ data: encrypted }); + } catch { + // 如果不是 JSON 或加密失败,使用原始 body + if (!headers['Content-Type']) { + headers['Content-Type'] = 'application/json'; + } + } + } else if (body && !headers['Content-Type']) { + headers['Content-Type'] = 'application/json'; + } + + const res = await fetch(path, { + ...options, + headers, + body, + }); + + if (res.status === 401) { + localStorage.removeItem('novalon_admin_token'); + localStorage.removeItem('novalon_admin_user'); + if (typeof window !== 'undefined') { + window.location.href = '/admin/login'; + } + throw new Error('未授权'); + } + + if (!res.ok) { + const data = await res.json().catch(() => ({})); + throw new Error(data.error || data.message || `请求失败 (${res.status})`); + } + + const data = await res.json(); + + // 解密响应 + if (shouldEncrypt && token && data.encrypted) { + try { + return decrypt(data.encrypted, token); + } catch { + return data as T; + } + } + + return data as T; + } + + async login(username: string, password: string) { + const res = await fetch('/api/auth/login', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ username, password }), + }); + if (!res.ok) { + const data = await res.json(); + throw new Error(data.error || '登录失败'); + } + return res.json(); + } + + async getModels() { + return this.request('/api/admin/models'); + } + + async getItems(params: Record) { + const query = new URLSearchParams(); + Object.entries(params).forEach(([k, v]) => query.set(k, String(v))); + return this.request<{ items: unknown[]; total: number; page: number; pageSize: number; totalPages: number }>( + `/api/admin/items?${query.toString()}`, + ); + } + + async createItem(data: Record) { + return this.request('/api/admin/items', { + method: 'POST', + body: JSON.stringify(data), + }); + } + + async updateItem(id: string, data: Record) { + return this.request(`/api/admin/items?id=${id}`, { + method: 'PUT', + body: JSON.stringify(data), + }); + } + + async deleteItem(id: string) { + return this.request(`/api/admin/items?id=${id}`, { + method: 'DELETE', + }); + } + + async getZones(pageCode?: string) { + const query = pageCode ? `?pageCode=${pageCode}` : ''; + return this.request(`/api/admin/zones${query}`); + } + + async saveZone(data: Record) { + return this.request('/api/admin/zones', { + method: 'POST', + body: JSON.stringify(data), + }); + } + + async getMedia(params?: Record) { + const query = new URLSearchParams(); + if (params) { + Object.entries(params).forEach(([k, v]) => query.set(k, String(v))); + } + return this.request<{ items: unknown[]; total: number }>(`/api/admin/media?${query.toString()}`); + } + + async uploadMedia(file: File) { + const formData = new FormData(); + formData.append('file', file); + const token = this.getToken(); + const res = await fetch('/api/admin/media', { + method: 'POST', + headers: token ? { Authorization: `Bearer ${token}` } : {}, + body: formData, + }); + if (!res.ok) { + const data = await res.json(); + throw new Error(data.error || '上传失败'); + } + return res.json(); + } + + async deleteMedia(id: string) { + return this.request(`/api/admin/media?id=${id}`, { method: 'DELETE' }); + } + + async getStats() { + const [models, zones] = await Promise.all([ + this.getModels(), + this.getZones(), + ]); + const itemsRes = await this.getItems({ page: 1, pageSize: 1 }); + return { + models: Array.isArray(models) ? models.length : 0, + items: (itemsRes).total || 0, + zones: Array.isArray(zones) ? zones.length : 0, + }; + } +} + +export const adminApi = new AdminApiClient(); \ No newline at end of file diff --git a/src/lib/auth.ts b/src/lib/auth.ts new file mode 100644 index 0000000..38b29a5 --- /dev/null +++ b/src/lib/auth.ts @@ -0,0 +1,84 @@ +import jwt from 'jsonwebtoken'; +import bcrypt from 'bcryptjs'; +import { type NextRequest } from 'next/server'; + +const JWT_SECRET = process.env.JWT_SECRET || 'novalon-cms-secret-key-2024'; +const JWT_REFRESH_SECRET = process.env.JWT_REFRESH_SECRET || 'novalon-cms-refresh-secret-2024'; +const TOKEN_EXPIRY = '24h'; +const REFRESH_TOKEN_EXPIRY = '7d'; + +export interface JwtPayload { + userId: string; + username: string; + role: string; +} + +// ============ 密码工具 ============ + +export async function hashPassword(password: string): Promise { + return bcrypt.hash(password, 10); +} + +export async function verifyPassword(password: string, hash: string): Promise { + return bcrypt.compare(password, hash); +} + +// ============ Token 工具 ============ + +export function generateTokens(payload: JwtPayload): { accessToken: string; refreshToken: string } { + const accessToken = jwt.sign(payload, JWT_SECRET, { expiresIn: TOKEN_EXPIRY }); + const refreshToken = jwt.sign(payload, JWT_REFRESH_SECRET, { expiresIn: REFRESH_TOKEN_EXPIRY }); + return { accessToken, refreshToken }; +} + +export function verifyAccessToken(token: string): JwtPayload { + return jwt.verify(token, JWT_SECRET) as JwtPayload; +} + +export function verifyRefreshToken(token: string): JwtPayload { + return jwt.verify(token, JWT_REFRESH_SECRET) as JwtPayload; +} + +// ============ 请求认证 ============ + +export function getTokenFromRequest(request: NextRequest): string | null { + const authHeader = request.headers.get('authorization'); + if (authHeader?.startsWith('Bearer ')) { + return authHeader.slice(7); + } + return null; +} + +export function authenticateRequest(request: NextRequest): JwtPayload | null { + const token = getTokenFromRequest(request); + if (!token) return null; + try { + return verifyAccessToken(token); + } catch { + return null; + } +} + +// ============ Cookie 工具 ============ + +export function setTokenCookie(response: Response, accessToken: string, refreshToken: string): void { + response.headers.set( + 'Set-Cookie', + `novalon_token=${accessToken}; Path=/; HttpOnly; SameSite=Lax; Max-Age=86400` + ); + response.headers.append( + 'Set-Cookie', + `novalon_refresh=${refreshToken}; Path=/; HttpOnly; SameSite=Lax; Max-Age=604800` + ); +} + +export function clearTokenCookie(response: Response): void { + response.headers.set( + 'Set-Cookie', + `novalon_token=; Path=/; HttpOnly; SameSite=Lax; Max-Age=0` + ); + response.headers.append( + 'Set-Cookie', + `novalon_refresh=; Path=/; HttpOnly; SameSite=Lax; Max-Age=0` + ); +} \ No newline at end of file diff --git a/src/lib/cms/ContentZoneRenderer.tsx b/src/lib/cms/ContentZoneRenderer.tsx new file mode 100644 index 0000000..4846314 --- /dev/null +++ b/src/lib/cms/ContentZoneRenderer.tsx @@ -0,0 +1,108 @@ +'use client'; + +import { getItemRenderer } from './component-registry'; +import type { ContentZone } from './types'; + +interface ContentZoneRendererProps { + zone: ContentZone; + className?: string; + itemClassName?: string; +} + +export function ContentZoneRenderer({ zone, className = '', itemClassName = '' }: ContentZoneRendererProps) { + const items = zone.items + .slice() + .sort((a, b) => (a.sortOrder ?? 0) - (b.sortOrder ?? 0)) + .filter((zi) => zi.item); + + const settings = zone.settings || {}; + const layout = settings.layout || 'grid'; + const columns = settings.columns || 3; + const gap = settings.gap || 'medium'; + + const gapClasses: Record = { + small: 'gap-3', + medium: 'gap-6', + large: 'gap-8', + }; + + const gridColsClass = { + 1: 'grid-cols-1', + 2: 'grid-cols-1 md:grid-cols-2', + 3: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3', + 4: 'grid-cols-1 sm:grid-cols-2 lg:grid-cols-4', + 5: 'grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-5', + 6: 'grid-cols-2 md:grid-cols-3 lg:grid-cols-6', + }[columns as number] || 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3'; + + if (layout === 'list') { + return ( +

+ ); + } + + if (layout === 'carousel') { + return ( +
+
+ {items.map((zoneItem, i) => { + const item = zoneItem.item!; + const Renderer = getItemRenderer(item.modelCode); + if (!Renderer) return null; + return ( +
+ +
+ ); + })} +
+
+ ); + } + + return ( +
+ {items.map((zoneItem, i) => { + const item = zoneItem.item!; + const Renderer = getItemRenderer(item.modelCode); + if (!Renderer) { + return ( +
+

未知内容类型: {item.modelCode}

+
+ ); + } + return ( + + ); + })} +
+ ); +} diff --git a/src/lib/cms/client.ts b/src/lib/cms/client.ts new file mode 100644 index 0000000..d96ad84 --- /dev/null +++ b/src/lib/cms/client.ts @@ -0,0 +1,238 @@ +import type { + ContentModel, + ContentItem, + ContentQueryParams, + PaginatedResult, + ContentZone, + ThemeConfig, +} from './types'; +import { + getMockContentModel, + getMockItems, + getMockItemBySlug, + getMockItemById, + contentModels, + allMockItems, +} from './mock-data'; + +export interface CmsClientConfig { + baseUrl: string; + apiKey?: string; + timeout?: number; + mockMode?: boolean; +} + +export class CmsClient { + private config: Required; + + constructor(config: CmsClientConfig) { + this.config = { + baseUrl: config.baseUrl, + apiKey: config.apiKey ?? '', + timeout: config.timeout ?? 10000, + mockMode: config.mockMode ?? false, + }; + } + + private async request(path: string, options?: RequestInit): Promise { + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), this.config.timeout); + + try { + const headers: Record = { + 'Content-Type': 'application/json', + ...(options?.headers as Record), + }; + + if (this.config.apiKey) { + headers['Authorization'] = `Bearer ${this.config.apiKey}`; + } + + const response = await fetch(`${this.config.baseUrl}${path}`, { + ...options, + headers, + signal: controller.signal, + }); + + if (!response.ok) { + throw new Error(`CMS API Error: ${response.status} ${response.statusText}`); + } + + return (await response.json()) as T; + } finally { + clearTimeout(timeoutId); + } + } + + async getModels(): Promise { + if (this.config.mockMode) { + return Object.values(contentModels); + } + return this.request('/api/models'); + } + + async getModel(code: string): Promise { + if (this.config.mockMode) { + const model = getMockContentModel(code); + if (!model) throw new Error(`Content model not found: ${code}`); + return model; + } + return this.request(`/api/models/${code}`); + } + + async getItems(params: ContentQueryParams): Promise> { + if (this.config.mockMode) { + // 使用统一 mock 数据源(覆盖所有已注册模型) + let items = params.modelCode ? [...getMockItems(params.modelCode)] : []; + + if (params.status) { + items = items.filter((item) => item.status === params.status); + } + + if (params.search) { + const searchLower = params.search.toLowerCase(); + items = items.filter( + (item) => + item.title.toLowerCase().includes(searchLower) || + (item.slug?.toLowerCase().includes(searchLower) ?? false), + ); + } + + const page = params.page ?? 1; + const pageSize = params.pageSize ?? 10; + const total = items.length; + const totalPages = Math.ceil(total / pageSize); + const start = (page - 1) * pageSize; + const paginatedItems = items.slice(start, start + pageSize); + + return { + items: paginatedItems, + total, + page, + pageSize, + totalPages, + }; + } + + const queryParams = new URLSearchParams(); + queryParams.set('modelCode', params.modelCode); + if (params.status) queryParams.set('status', params.status); + if (params.page) queryParams.set('page', String(params.page)); + if (params.pageSize) queryParams.set('pageSize', String(params.pageSize)); + if (params.sortBy) queryParams.set('sortBy', params.sortBy); + if (params.sortOrder) queryParams.set('sortOrder', params.sortOrder); + if (params.search) queryParams.set('search', params.search); + if (params.filters) { + Object.entries(params.filters).forEach(([key, value]) => { + queryParams.set(`filters[${key}]`, String(value)); + }); + } + + return this.request>( + `/api/items?${queryParams.toString()}`, + ); + } + + async getItem(id: string): Promise { + if (this.config.mockMode) { + // 跨所有模型查找 + for (const modelCode of Object.keys(allMockItems)) { + const item = getMockItemById(modelCode, id); + if (item) return item; + } + throw new Error(`Content item not found: ${id}`); + } + return this.request(`/api/items/${id}`); + } + + async getItemBySlug(modelCode: string, slug: string): Promise { + if (this.config.mockMode) { + const item = getMockItemBySlug(modelCode, slug); + if (!item) throw new Error(`Content item not found: ${modelCode}/${slug}`); + return item; + } + return this.request(`/api/items/slug/${modelCode}/${slug}`); + } + + async getZone(code: string): Promise { + if (this.config.mockMode) { + return { + id: `zone-${code}`, + code, + name: code, + allowedModels: ['case-study'], + items: getMockItems('case-study').slice(0, 3).map((item, index) => ({ + itemId: item.id, + item, + sortOrder: index, + })), + }; + } + return this.request(`/api/zones/${code}`); + } + + async getZonesByPage(pageCode: string): Promise { + if (this.config.mockMode) { + return [ + { + id: `zone-${pageCode}-featured`, + code: `${pageCode}-featured`, + name: `${pageCode} 精选区域`, + pageCode, + allowedModels: ['case-study'], + items: getMockItems('case-study') + .filter((item) => (item.data as { featured?: boolean }).featured) + .map((item, index) => ({ + itemId: item.id, + item, + sortOrder: index, + })), + }, + ]; + } + return this.request(`/api/zones/page/${pageCode}`); + } + + async getThemeConfig(): Promise { + if (this.config.mockMode) { + return { + id: 'theme-default', + siteName: 'Novalon', + siteDescription: '企业数字化转型合作伙伴', + colors: { + primary: '#2563eb', + secondary: '#64748b', + accent: '#0891b2', + background: '#ffffff', + text: '#0f172a', + }, + seo: { + defaultTitle: 'Novalon - 企业数字化转型合作伙伴', + defaultDescription: '专注企业数字化转型,提供从战略咨询到技术落地的全链路服务', + defaultKeywords: '数字化转型,ERP,数据中台,全渠道零售', + }, + social: { + wechat: 'novalon', + linkedin: 'novalon', + github: 'novalon', + }, + settings: {}, + }; + } + return this.request('/api/theme/config'); + } + + getTypedItem(item: ContentItem): T { + return { + id: item.id, + slug: item.slug, + title: item.title, + ...item.data, + } as unknown as T; + } +} + +export const cmsClient = new CmsClient({ + baseUrl: '', + mockMode: true, +}); diff --git a/src/lib/cms/component-registry.ts b/src/lib/cms/component-registry.ts new file mode 100644 index 0000000..3b48063 --- /dev/null +++ b/src/lib/cms/component-registry.ts @@ -0,0 +1,67 @@ +'use client'; + +import type { ComponentType } from 'react'; +import type { ContentItem, ContentZone } from './types'; + +export interface ContentItemComponentProps { + item: ContentItem; + index?: number; + variant?: string; + className?: string; +} + +export interface ContentZoneComponentProps { + zone: ContentZone; + className?: string; +} + +export type ContentItemRenderer = ComponentType; +export type ContentZoneRenderer = ComponentType; + +interface ComponentRegistry { + itemRenderers: Map; + zoneRenderers: Map; +} + +const registry: ComponentRegistry = { + itemRenderers: new Map(), + zoneRenderers: new Map(), +}; + +export function registerItemRenderer( + contentType: string, + renderer: ContentItemRenderer, +): void { + registry.itemRenderers.set(contentType, renderer); +} + +export function registerZoneRenderer( + zoneType: string, + renderer: ContentZoneRenderer, +): void { + registry.zoneRenderers.set(zoneType, renderer); +} + +export function getItemRenderer(contentType: string): ContentItemRenderer | undefined { + return registry.itemRenderers.get(contentType); +} + +export function getZoneRenderer(zoneType: string): ContentZoneRenderer | undefined { + return registry.zoneRenderers.get(zoneType); +} + +export function hasItemRenderer(contentType: string): boolean { + return registry.itemRenderers.has(contentType); +} + +export function hasZoneRenderer(zoneType: string): boolean { + return registry.zoneRenderers.has(zoneType); +} + +export function getAllRegisteredItemTypes(): string[] { + return Array.from(registry.itemRenderers.keys()); +} + +export function getAllRegisteredZoneTypes(): string[] { + return Array.from(registry.zoneRenderers.keys()); +} diff --git a/src/lib/cms/content-types.ts b/src/lib/cms/content-types.ts new file mode 100644 index 0000000..b25cea3 --- /dev/null +++ b/src/lib/cms/content-types.ts @@ -0,0 +1,1020 @@ +import { registerContentType, type ContentTypeConfig } from './registry'; +import type { FieldDefinition } from './types'; + +const newsFields: FieldDefinition[] = [ + { + name: 'excerpt', + type: 'textarea', + label: '摘要', + required: true, + description: '新闻摘要,用于列表页展示', + }, + { + name: 'date', + type: 'date', + label: '发布日期', + required: true, + }, + { + name: 'category', + type: 'text', + label: '分类', + required: true, + options: [ + { label: '公司动态', value: 'company' }, + { label: '行业资讯', value: 'industry' }, + { label: '技术分享', value: 'tech' }, + ], + }, + { + name: 'image', + type: 'image', + label: '封面图', + required: true, + }, + { + name: 'content', + type: 'richtext', + label: '正文内容', + required: true, + }, + { + name: 'featured', + type: 'boolean', + label: '精选推荐', + defaultValue: false, + description: '是否在首页精选区展示', + }, +]; + +const serviceFields: FieldDefinition[] = [ + { + name: 'description', + type: 'textarea', + label: '简短描述', + required: true, + }, + { + name: 'icon', + type: 'text', + label: '图标标识', + required: true, + description: '图标名称,对应图标库', + }, + { + name: 'overview', + type: 'textarea', + label: '服务概述', + required: true, + }, + { + name: 'features', + type: 'array', + label: '核心能力', + fields: [{ name: 'name', type: 'text', label: '能力名称', required: true }], + }, + { + name: 'benefits', + type: 'array', + label: '服务价值', + fields: [{ name: 'name', type: 'text', label: '价值点', required: true }], + }, + { + name: 'process', + type: 'array', + label: '服务流程', + fields: [{ name: 'step', type: 'text', label: '步骤名称', required: true }], + }, + { + name: 'heroThemeId', + type: 'text', + label: 'Hero 主题 ID', + description: '关联的 Hero 区域主题配置', + }, +]; + +const productFields: FieldDefinition[] = [ + { + name: 'description', + type: 'textarea', + label: '产品简介', + required: true, + }, + { + name: 'image', + type: 'image', + label: '产品图片', + required: true, + }, + { + name: 'category', + type: 'text', + label: '分类名称', + required: true, + }, + { + name: 'categoryId', + type: 'text', + label: '分类 ID', + required: true, + options: [ + { label: '企业套装', value: 'enterprise' }, + { label: '专业产品', value: 'specialized' }, + ], + }, + { + name: 'status', + type: 'text', + label: '产品状态', + required: true, + options: [ + { label: '研发中', value: '研发中' }, + { label: '内测中', value: '内测中' }, + { label: '已发布', value: '已发布' }, + ], + }, + { + name: 'overview', + type: 'textarea', + label: '产品概述', + required: true, + }, + { + name: 'features', + type: 'array', + label: '核心功能', + fields: [{ name: 'name', type: 'text', label: '功能名称', required: true }], + }, + { + name: 'benefits', + type: 'array', + label: '产品价值', + fields: [{ name: 'name', type: 'text', label: '价值点', required: true }], + }, + { + name: 'tags', + type: 'array', + label: '标签', + fields: [{ name: 'name', type: 'text', label: '标签名', required: true }], + }, + { + name: 'featured', + type: 'boolean', + label: '精选产品', + defaultValue: false, + }, +]; + +const solutionFields: FieldDefinition[] = [ + { + name: 'description', + type: 'textarea', + label: '方案简介', + required: true, + }, + { + name: 'icon', + type: 'text', + label: '图标标识', + required: true, + }, + { + name: 'industry', + type: 'text', + label: '适用行业', + }, + { + name: 'overview', + type: 'textarea', + label: '方案概述', + required: true, + }, + { + name: 'painPoints', + type: 'array', + label: '行业痛点', + fields: [{ name: 'text', type: 'text', label: '痛点描述', required: true }], + }, + { + name: 'valueProps', + type: 'array', + label: '方案价值', + fields: [{ name: 'text', type: 'text', label: '价值描述', required: true }], + }, + { + name: 'featured', + type: 'boolean', + label: '精选方案', + defaultValue: false, + }, +]; + +const heroBannerFields: FieldDefinition[] = [ + { + name: 'heading', + type: 'text', + label: '主标题', + required: true, + ui: { width: 'full' }, + }, + { + name: 'subheading', + type: 'textarea', + label: '副标题', + required: true, + ui: { width: 'full' }, + }, + { + name: 'description', + type: 'textarea', + label: '描述文本', + }, + { + name: 'primaryCtaText', + type: 'text', + label: '主按钮文案', + defaultValue: '了解更多', + }, + { + name: 'primaryCtaLink', + type: 'text', + label: '主按钮链接', + defaultValue: '/solutions', + }, + { + name: 'secondaryCtaText', + type: 'text', + label: '副按钮文案', + defaultValue: '联系我们', + }, + { + name: 'secondaryCtaLink', + type: 'text', + label: '副按钮链接', + defaultValue: '/contact', + }, + { + name: 'theme', + type: 'text', + label: '主题风格', + defaultValue: 'brand', + options: [ + { label: '品牌绿', value: 'brand' }, + { label: '蓝', value: 'blue' }, + { label: '青', value: 'teal' }, + { label: '琥珀', value: 'amber' }, + { label: '紫', value: 'purple' }, + ], + }, + { + name: 'sortOrder', + type: 'number', + label: '排序', + defaultValue: 0, + }, +]; + +const statItemFields: FieldDefinition[] = [ + { + name: 'icon', + type: 'text', + label: '图标', + required: true, + }, + { + name: 'label', + type: 'text', + label: '指标名称', + required: true, + }, + { + name: 'value', + type: 'number', + label: '数值', + required: true, + }, + { + name: 'suffix', + type: 'text', + label: '后缀', + defaultValue: '', + }, + { + name: 'prefix', + type: 'text', + label: '前缀', + defaultValue: '', + }, + { + name: 'decimals', + type: 'number', + label: '小数位数', + defaultValue: 0, + }, + { + name: 'description', + type: 'textarea', + label: '描述说明', + }, + { + name: 'trendValue', + type: 'text', + label: '趋势值', + }, + { + name: 'trendDirection', + type: 'text', + label: '趋势方向', + defaultValue: 'up', + options: [ + { label: '上升', value: 'up' }, + { label: '下降', value: 'down' }, + { label: '持平', value: 'neutral' }, + ], + }, + { + name: 'accentColor', + type: 'text', + label: '强调色', + defaultValue: 'brand', + options: [ + { label: '品牌绿', value: 'brand' }, + { label: '蓝', value: 'blue' }, + { label: '青', value: 'teal' }, + { label: '琥珀', value: 'amber' }, + { label: '紫', value: 'purple' }, + ], + }, + { + name: 'sortOrder', + type: 'number', + label: '排序', + defaultValue: 0, + }, +]; + +const aboutPageFields: FieldDefinition[] = [ + { + name: 'heroSubtitle', + type: 'text', + label: 'Hero 副标题', + }, + { + name: 'heroTitle', + type: 'text', + label: 'Hero 主标题', + required: true, + }, + { + name: 'heroDescription', + type: 'textarea', + label: 'Hero 描述', + }, + { + name: 'heroPrimaryCtaLabel', + type: 'text', + label: '主按钮文案', + defaultValue: '了解更多', + }, + { + name: 'heroPrimaryCtaHref', + type: 'text', + label: '主按钮链接', + defaultValue: '/contact', + }, + { + name: 'heroSecondaryCtaLabel', + type: 'text', + label: '副按钮文案', + defaultValue: '我们的团队', + }, + { + name: 'heroSecondaryCtaHref', + type: 'text', + label: '副按钮链接', + defaultValue: '/team', + }, + { + name: 'whoWeAreTitle', + type: 'text', + label: '关于我们标题', + required: true, + }, + { + name: 'whoWeAreDescription', + type: 'textarea', + label: '关于我们描述', + required: true, + }, + { + name: 'promiseText', + type: 'textarea', + label: '承诺宣言', + }, + { + name: 'coreValues', + type: 'array', + label: '核心价值观', + fields: [ + { name: 'number', type: 'text', label: '序号', required: true }, + { name: 'title', type: 'text', label: '标题', required: true }, + { name: 'description', type: 'textarea', label: '描述', required: true }, + ], + }, + { + name: 'milestones', + type: 'array', + label: '发展历程', + fields: [ + { name: 'year', type: 'text', label: '年份', required: true }, + { name: 'title', type: 'text', label: '标题', required: true }, + { name: 'description', type: 'textarea', label: '描述' }, + { name: 'highlight', type: 'text', label: '亮点' }, + ], + }, + { + name: 'keyMetrics', + type: 'array', + label: '关键数据', + fields: [ + { name: 'value', type: 'text', label: '数值', required: true }, + { name: 'label', type: 'text', label: '标签', required: true }, + { name: 'sub', type: 'text', label: '副标题' }, + ], + }, + { + name: 'certifications', + type: 'array', + label: '资质认证', + fields: [ + { name: 'name', type: 'text', label: '名称', required: true }, + { name: 'description', type: 'textarea', label: '描述' }, + ], + }, + { + name: 'partners', + type: 'array', + label: '合作伙伴', + fields: [ + { name: 'name', type: 'text', label: '名称', required: true }, + ], + }, + { + name: 'ctaTitle', + type: 'text', + label: 'CTA 标题', + required: true, + }, + { + name: 'ctaDescription', + type: 'textarea', + label: 'CTA 描述', + }, + { + name: 'ctaPrimaryLabel', + type: 'text', + label: '主按钮文案', + defaultValue: '联系我们', + }, + { + name: 'ctaPrimaryHref', + type: 'text', + label: '主按钮链接', + defaultValue: '/contact', + }, + { + name: 'ctaSecondaryLabel', + type: 'text', + label: '副按钮文案', + defaultValue: '了解更多', + }, + { + name: 'ctaSecondaryHref', + type: 'text', + label: '副按钮链接', + defaultValue: '/solutions', + }, +]; + +const teamPageFields: FieldDefinition[] = [ + { + name: 'heroSubtitle', + type: 'text', + label: 'Hero 副标题', + }, + { + name: 'heroTitle', + type: 'text', + label: 'Hero 主标题', + required: true, + }, + { + name: 'heroDescription', + type: 'textarea', + label: 'Hero 描述', + }, + { + name: 'heroPrimaryCtaLabel', + type: 'text', + label: '主按钮文案', + defaultValue: '加入我们', + }, + { + name: 'heroPrimaryCtaHref', + type: 'text', + label: '主按钮链接', + defaultValue: '/contact', + }, + { + name: 'heroSecondaryCtaLabel', + type: 'text', + label: '副按钮文案', + defaultValue: '了解更多', + }, + { + name: 'heroSecondaryCtaHref', + type: 'text', + label: '副按钮链接', + defaultValue: '/about', + }, + { + name: 'strengths', + type: 'array', + label: '团队优势', + fields: [ + { name: 'number', type: 'text', label: '序号', required: true }, + { name: 'title', type: 'text', label: '标题', required: true }, + { name: 'description', type: 'textarea', label: '描述', required: true }, + { name: 'iconName', type: 'text', label: '图标名称' }, + ], + }, + { + name: 'culture', + type: 'array', + label: '团队文化', + fields: [ + { name: 'number', type: 'text', label: '序号', required: true }, + { name: 'title', type: 'text', label: '标题', required: true }, + { name: 'description', type: 'textarea', label: '描述', required: true }, + { name: 'iconName', type: 'text', label: '图标名称' }, + ], + }, + { + name: 'stats', + type: 'array', + label: '数据统计', + fields: [ + { name: 'value', type: 'number', label: '数值', required: true }, + { name: 'suffix', type: 'text', label: '后缀' }, + { name: 'label', type: 'text', label: '标签', required: true }, + ], + }, + { + name: 'aboutTitle', + type: 'text', + label: '关于标题', + }, + { + name: 'aboutParagraphs', + type: 'array', + label: '关于段落', + fields: [ + { name: 'text', type: 'textarea', label: '段落内容', required: true }, + ], + }, + { + name: 'ctaTitle', + type: 'text', + label: 'CTA 标题', + required: true, + }, + { + name: 'ctaDescription', + type: 'textarea', + label: 'CTA 描述', + }, + { + name: 'ctaPrimaryLabel', + type: 'text', + label: '主按钮文案', + defaultValue: '联系我们', + }, + { + name: 'ctaPrimaryHref', + type: 'text', + label: '主按钮链接', + defaultValue: '/contact', + }, + { + name: 'ctaSecondaryLabel', + type: 'text', + label: '副按钮文案', + defaultValue: '关于我们', + }, + { + name: 'ctaSecondaryHref', + type: 'text', + label: '副按钮链接', + defaultValue: '/about', + }, +]; + +const contactPageFields: FieldDefinition[] = [ + { + name: 'heroSubtitle', + type: 'text', + label: 'Hero 副标题', + }, + { + name: 'heroTitle', + type: 'text', + label: 'Hero 主标题', + required: true, + }, + { + name: 'heroDescription', + type: 'textarea', + label: 'Hero 描述', + }, + { + name: 'contactTitle', + type: 'text', + label: '联系方式标题', + required: true, + }, + { + name: 'emailLabel', + type: 'text', + label: '邮箱标签', + defaultValue: '邮箱', + }, + { + name: 'addressLabel', + type: 'text', + label: '地址标签', + defaultValue: '地址', + }, + { + name: 'email', + type: 'text', + label: '邮箱地址', + required: true, + }, + { + name: 'address', + type: 'text', + label: '办公地址', + required: true, + }, + { + name: 'workHoursTitle', + type: 'text', + label: '工作时间标题', + }, + { + name: 'dayLabel', + type: 'text', + label: '工作日标签', + defaultValue: '周一至周五', + }, + { + name: 'hours', + type: 'text', + label: '工作时间', + defaultValue: '09:00 - 18:00', + }, + { + name: 'commitmentTitle', + type: 'text', + label: '承诺标题', + }, + { + name: 'commitmentItems', + type: 'array', + label: '承诺列表', + fields: [ + { name: 'item', type: 'text', label: '承诺内容', required: true }, + ], + }, + { + name: 'formTitle', + type: 'text', + label: '表单标题', + }, + { + name: 'successTitle', + type: 'text', + label: '提交成功标题', + defaultValue: '感谢您的留言', + }, + { + name: 'successMessage', + type: 'textarea', + label: '提交成功消息', + }, + { + name: 'submitLabel', + type: 'text', + label: '提交按钮文案', + defaultValue: '发送消息', + }, + { + name: 'submittingLabel', + type: 'text', + label: '提交中文案', + defaultValue: '发送中...', + }, + { + name: 'ctaSubtitle', + type: 'text', + label: 'CTA 区域副标题', + }, + { + name: 'ctaTitle', + type: 'text', + label: 'CTA 标题', + }, + { + name: 'ctaDescription', + type: 'textarea', + label: 'CTA 描述', + }, + { + name: 'ctaPrimaryLabel', + type: 'text', + label: '主按钮文案', + defaultValue: '了解更多', + }, + { + name: 'ctaPrimaryHref', + type: 'text', + label: '主按钮链接', + defaultValue: '/solutions', + }, + { + name: 'ctaSecondaryLabel', + type: 'text', + label: '副按钮文案', + defaultValue: '关于我们', + }, + { + name: 'ctaSecondaryHref', + type: 'text', + label: '副按钮链接', + defaultValue: '/about', + }, +]; + +const legalPageFields: FieldDefinition[] = [ + { + name: 'pageType', + type: 'text', + label: '页面类型', + required: true, + options: [ + { label: '隐私政策', value: 'privacy' }, + { label: '服务条款', value: 'terms' }, + ], + }, + { + name: 'heroTitle', + type: 'text', + label: '页面标题', + required: true, + }, + { + name: 'heroDescription', + type: 'textarea', + label: '页面描述', + }, + { + name: 'lastUpdated', + type: 'text', + label: '最后更新日期', + required: true, + }, + { + name: 'sections', + type: 'array', + label: '内容章节', + fields: [ + { name: 'title', type: 'text', label: '章节标题', required: true }, + { name: 'order', type: 'number', label: '排序', required: true }, + { name: 'content', type: 'json', label: '章节内容', required: true }, + ], + }, +]; + +const newsConfig: ContentTypeConfig = { + model: { + id: 'model-news', + code: 'news', + name: '新闻资讯', + description: '公司动态、行业资讯、技术博客等内容', + fields: newsFields, + isPageType: true, + urlPattern: '/news/{slug}', + hasVersions: true, + hasDraft: true, + icon: 'newspaper', + createdAt: '2024-01-01T00:00:00Z', + updatedAt: '2024-01-01T00:00:00Z', + }, + listPage: { + route: '/news', + }, + detailPage: { + routePattern: '/news/{slug}', + }, +}; + +const serviceConfig: ContentTypeConfig = { + model: { + id: 'model-service', + code: 'service', + name: '服务', + description: '咨询服务、实施服务、运维服务等', + fields: serviceFields, + isPageType: true, + urlPattern: '/services/{slug}', + hasVersions: true, + hasDraft: true, + icon: 'briefcase', + createdAt: '2024-01-01T00:00:00Z', + updatedAt: '2024-01-01T00:00:00Z', + }, + listPage: { + route: '/services', + }, + detailPage: { + routePattern: '/services/{slug}', + }, +}; + +const productConfig: ContentTypeConfig = { + model: { + id: 'model-product', + code: 'product', + name: '产品', + description: '企业级产品、专业产品等', + fields: productFields, + isPageType: true, + urlPattern: '/products/{slug}', + hasVersions: true, + hasDraft: true, + icon: 'box', + createdAt: '2024-01-01T00:00:00Z', + updatedAt: '2024-01-01T00:00:00Z', + }, + listPage: { + route: '/products', + }, + detailPage: { + routePattern: '/products/{slug}', + }, +}; + +const solutionConfig: ContentTypeConfig = { + model: { + id: 'model-solution', + code: 'solution', + name: '解决方案', + description: '行业解决方案、场景解决方案', + fields: solutionFields, + isPageType: true, + urlPattern: '/solutions/{slug}', + hasVersions: true, + hasDraft: true, + icon: 'layers', + createdAt: '2024-01-01T00:00:00Z', + updatedAt: '2024-01-01T00:00:00Z', + }, + listPage: { + route: '/solutions', + }, + detailPage: { + routePattern: '/solutions/{slug}', + }, +}; + +const heroBannerConfig: ContentTypeConfig = { + model: { + id: 'model-hero-banner', + code: 'hero-banner', + name: 'Hero Banner', + description: '首页/落地页顶部大 Banner', + fields: heroBannerFields, + isPageType: false, + hasVersions: false, + hasDraft: true, + icon: 'image', + createdAt: '2024-01-01T00:00:00Z', + updatedAt: '2024-01-01T00:00:00Z', + }, +}; + +const statItemConfig: ContentTypeConfig = { + model: { + id: 'model-stat-item', + code: 'stat-item', + name: '数据指标', + description: '用于数据展示区的指标卡片', + fields: statItemFields, + isPageType: false, + hasVersions: false, + hasDraft: false, + icon: 'bar-chart', + createdAt: '2024-01-01T00:00:00Z', + updatedAt: '2024-01-01T00:00:00Z', + }, +}; + +const aboutPageConfig: ContentTypeConfig = { + model: { + id: 'model-about-page', + code: 'about-page', + name: '关于我们', + description: '关于我们页面内容配置', + fields: aboutPageFields, + isPageType: false, + hasVersions: false, + hasDraft: true, + icon: 'info', + createdAt: '2024-01-01T00:00:00Z', + updatedAt: '2024-01-01T00:00:00Z', + }, +}; + +const teamPageConfig: ContentTypeConfig = { + model: { + id: 'model-team-page', + code: 'team-page', + name: '团队介绍', + description: '团队介绍页面内容配置', + fields: teamPageFields, + isPageType: false, + hasVersions: false, + hasDraft: true, + icon: 'users', + createdAt: '2024-01-01T00:00:00Z', + updatedAt: '2024-01-01T00:00:00Z', + }, +}; + +const contactPageConfig: ContentTypeConfig = { + model: { + id: 'model-contact-page', + code: 'contact-page', + name: '联系我们', + description: '联系我们页面内容配置', + fields: contactPageFields, + isPageType: false, + hasVersions: false, + hasDraft: true, + icon: 'phone', + createdAt: '2024-01-01T00:00:00Z', + updatedAt: '2024-01-01T00:00:00Z', + }, +}; + +const legalPageConfig: ContentTypeConfig = { + model: { + id: 'model-legal-page', + code: 'legal-page', + name: '法律页面', + description: '隐私政策/服务条款页面内容配置', + fields: legalPageFields, + isPageType: false, + hasVersions: false, + hasDraft: true, + icon: 'file-text', + createdAt: '2024-01-01T00:00:00Z', + updatedAt: '2024-01-01T00:00:00Z', + }, +}; + +export function registerAllContentTypes(): void { + registerContentType(newsConfig); + registerContentType(serviceConfig); + registerContentType(productConfig); + registerContentType(solutionConfig); + registerContentType(heroBannerConfig); + registerContentType(statItemConfig); + registerContentType(aboutPageConfig); + registerContentType(teamPageConfig); + registerContentType(contactPageConfig); + registerContentType(legalPageConfig); +} + +export const CONTENT_TYPE_CONFIGS = { + news: newsConfig, + service: serviceConfig, + product: productConfig, + solution: solutionConfig, + 'hero-banner': heroBannerConfig, + 'stat-item': statItemConfig, + 'about-page': aboutPageConfig, + 'team-page': teamPageConfig, + 'contact-page': contactPageConfig, + 'legal-page': legalPageConfig, +}; diff --git a/src/lib/cms/data-server.ts b/src/lib/cms/data-server.ts new file mode 100644 index 0000000..ee58738 --- /dev/null +++ b/src/lib/cms/data-server.ts @@ -0,0 +1,174 @@ +/** + * CMS 服务端数据访问层 + * 从 Prisma 数据库直接读取已发布内容,供页面组件使用 + */ +import { prisma } from '@/lib/db'; +import { cache } from 'react'; +import type { ContentItem, ContentStatus } from './types'; + +// ============ 类型转换 ============ + +function toContentItem(item: Record): ContentItem { + return { + id: item.id as string, + modelId: item.modelId as string, + modelCode: item.modelCode as string, + title: item.title as string, + slug: (item.slug as string | null) ?? undefined, + status: item.status as ContentStatus, + data: JSON.parse(item.data as string), + version: item.version as number, + sortOrder: (item.sortOrder as number | null) ?? undefined, + publishedAt: (item.publishedAt as Date | null)?.toISOString(), + createdBy: item.createdBy as string, + updatedBy: item.updatedBy as string, + createdAt: (item.createdAt as Date).toISOString(), + updatedAt: (item.updatedAt as Date).toISOString(), + }; +} + +// ============ 内容条目查询 ============ + +/** + * 获取指定模型的已发布内容列表 + */ +export const getPublishedItems = cache(async (modelCode: string): Promise => { + const items = await prisma.contentItem.findMany({ + where: { modelCode, status: 'published' }, + orderBy: { sortOrder: 'asc' }, + }); + return items.map((item) => toContentItem(item as unknown as Record)); +}); + +/** + * 根据 slug 获取单条已发布内容 + */ +export const getPublishedItemBySlug = cache(async (modelCode: string, slug: string): Promise => { + const item = await prisma.contentItem.findFirst({ + where: { modelCode, slug, status: 'published' }, + }); + if (!item) return null; + return toContentItem(item as unknown as Record); +}); + +/** + * 根据 ID 获取单条内容(不限状态) + */ +export const getItemById = cache(async (id: string): Promise => { + const item = await prisma.contentItem.findUnique({ where: { id } }); + if (!item) return null; + return toContentItem(item as unknown as Record); +}); + +// ============ 内容区域查询 ============ + +/** + * 获取指定页面的内容区域配置 + */ +export const getPageZones = cache(async (pageCode: string) => { + const zones = await prisma.contentZone.findMany({ + where: { pageCode }, + }); + return zones.map((z) => ({ + ...z, + allowedModels: JSON.parse(z.allowedModels), + items: JSON.parse(z.items), + settings: JSON.parse(z.settings), + })); +}); + +/** + * 获取单个内容区域 + */ +export const getZone = cache(async (code: string) => { + const zone = await prisma.contentZone.findUnique({ where: { code } }); + if (!zone) return null; + return { + ...zone, + allowedModels: JSON.parse(zone.allowedModels), + items: JSON.parse(zone.items), + settings: JSON.parse(zone.settings), + }; +}); + +// ============ 内容模型查询 ============ + +/** + * 获取所有内容模型 + */ +export const getContentModels = cache(async () => { + const models = await prisma.contentModel.findMany({ + orderBy: { sortOrder: 'asc' }, + }); + return models.map((m) => ({ + ...m, + fields: JSON.parse(m.fields), + })); +}); + +// ============ 便捷函数:按业务类型获取 ============ + +export async function getCases() { + return getPublishedItems('case-study'); +} + +export async function getNews() { + return getPublishedItems('news'); +} + +export async function getServices() { + return getPublishedItems('service'); +} + +export async function getProducts() { + return getPublishedItems('product'); +} + +export async function getSolutions() { + return getPublishedItems('solution'); +} + +export async function getStats() { + return getPublishedItems('stat-item'); +} + +export async function getHeroBanners() { + return getPublishedItems('hero-banner'); +} + +export async function getCaseBySlug(slug: string) { + return getPublishedItemBySlug('case-study', slug); +} + +export async function getNewsBySlug(slug: string) { + return getPublishedItemBySlug('news', slug); +} + +export async function getServiceBySlug(slug: string) { + return getPublishedItemBySlug('service', slug); +} + +export async function getProductBySlug(slug: string) { + return getPublishedItemBySlug('product', slug); +} + +export async function getSolutionBySlug(slug: string) { + return getPublishedItemBySlug('solution', slug); +} + +export async function getHomePageZones() { + return getPageZones('home'); +} + +// ============ 静态生成辅助 ============ + +/** + * 获取所有已发布内容的 slug 列表(用于 generateStaticParams) + */ +export async function getAllPublishedSlugs(modelCode: string) { + const items = await prisma.contentItem.findMany({ + where: { modelCode, status: 'published' }, + select: { slug: true }, + }); + return items.filter((i) => i.slug).map((i) => ({ slug: i.slug })); +} \ No newline at end of file diff --git a/src/lib/cms/index.ts b/src/lib/cms/index.ts new file mode 100644 index 0000000..f8e540e --- /dev/null +++ b/src/lib/cms/index.ts @@ -0,0 +1,82 @@ +export { + type FieldType, + type FieldDefinition, + type ContentModel, + type ContentStatus, + type ContentItem, + type ContentZoneItem, + type ContentZone, + type ContentZoneSettings, + type ThemeConfig, + type MediaAsset, + type ContentQueryParams, + type PaginatedResult, + type AuditLog, +} from './types'; + +export { cmsClient } from './client'; +export type { CmsClient, CmsClientConfig } from './client'; + +export { getMockContentModel, getMockCaseStudies, getMockCaseStudyBySlug } from './mock-data'; +export type { CaseStudyData } from './mock-data'; + +export { + registerContentType, + getContentTypeConfig, + getAllContentTypeConfigs, + getContentModel, + getAllContentModels, +} from './registry'; +export type { ContentTypeConfig } from './registry'; + +export { registerAllContentTypes, CONTENT_TYPE_CONFIGS } from './content-types'; + +export { + registerItemRenderer, + registerZoneRenderer, + getItemRenderer, + getZoneRenderer, + hasItemRenderer, + hasZoneRenderer, + getAllRegisteredItemTypes, + getAllRegisteredZoneTypes, +} from './component-registry'; +export type { + ContentItemComponentProps, + ContentZoneComponentProps, + ContentItemRenderer, + ContentZoneRenderer as ContentZoneRendererType, +} from './component-registry'; + +export { + NewsCardRenderer, + StatCardRenderer, + ServiceCardRenderer, + ProductCardRenderer, + SolutionCardRenderer, + HeroBannerRenderer, +} from './renderers'; + +export { ContentZoneRenderer } from './ContentZoneRenderer'; + +export { + loadCmsData, + saveCmsData, + getContentZone, + saveContentZone, + getAllContentZones, + getContentItems, + saveContentItem, + deleteContentItem, + resetCmsData, + exportCmsData, + importCmsData, + getContentZoneKeys, + getAvailableItemsForZone, + addItemToZone, + removeItemFromZone, + reorderZoneItems, + updateZoneSettings, +} from './storage'; + +export { initCms, isCmsInitialized } from './init'; diff --git a/src/lib/cms/init.ts b/src/lib/cms/init.ts new file mode 100644 index 0000000..f5731bb --- /dev/null +++ b/src/lib/cms/init.ts @@ -0,0 +1,36 @@ +'use client'; + +import { + registerItemRenderer, +} from './component-registry'; +import { + NewsCardRenderer, + StatCardRenderer, + ServiceCardRenderer, + ProductCardRenderer, + SolutionCardRenderer, + HeroBannerRenderer, +} from './renderers'; +import { registerAllContentTypes } from './content-types'; +import type { ContentItem } from './types'; + +let initialized = false; + +export function initCms(): void { + if (initialized) return; + initialized = true; + + registerAllContentTypes(); + + registerItemRenderer('news', NewsCardRenderer); + registerItemRenderer('case-study', NewsCardRenderer as unknown as React.ComponentType<{ item: ContentItem; index?: number; variant?: string; className?: string }>); + registerItemRenderer('stat-item', StatCardRenderer); + registerItemRenderer('service', ServiceCardRenderer); + registerItemRenderer('product', ProductCardRenderer); + registerItemRenderer('solution', SolutionCardRenderer); + registerItemRenderer('hero-banner', HeroBannerRenderer); +} + +export function isCmsInitialized(): boolean { + return initialized; +} diff --git a/src/lib/cms/mock-data.ts b/src/lib/cms/mock-data.ts new file mode 100644 index 0000000..93528ae --- /dev/null +++ b/src/lib/cms/mock-data.ts @@ -0,0 +1,272 @@ +import type { ContentModel, ContentItem } from './types'; +import { + CASE_STUDIES, + type CaseStudy, + type CaseMetric, + type CaseTimelinePhase, + type CaseService, + type CaseTestimonial, +} from '../constants/cases'; +import { NEWS } from '../constants/news'; +import { SERVICES } from '../constants/services'; +import { PRODUCTS } from '../constants/products'; +import { SOLUTIONS } from '../constants/solutions'; + +// ============ 类型定义 ============ + +export interface CaseStudyData { + client: string; + industry: string; + companySize: string; + subtitle: string; + challenge: string; + solution: string; + result: string; + metrics: CaseMetric[]; + timeline: CaseTimelinePhase[]; + services: CaseService[]; + testimonial?: CaseTestimonial; + color: 'brand' | 'blue' | 'teal' | 'amber' | 'purple'; + featured?: boolean; +} + +// ============ 内容模型定义 ============ + +const caseStudyModel: ContentModel = { + id: 'model-case-study', + code: 'case-study', + name: '案例研究', + description: '客户成功案例与实践分享', + isPageType: true, + urlPattern: '/cases/{slug}', + hasVersions: true, + hasDraft: true, + icon: 'briefcase', + createdAt: '2024-01-01T00:00:00Z', + updatedAt: '2024-01-01T00:00:00Z', + fields: [ + { name: 'client', type: 'text', label: '客户名称', required: true, description: '客户公司或机构名称' }, + { name: 'industry', type: 'text', label: '所属行业', required: true, description: '客户所属行业领域' }, + { name: 'companySize', type: 'text', label: '公司规模', required: true, description: '客户公司人员规模' }, + { name: 'subtitle', type: 'text', label: '副标题', required: true, description: '案例副标题,简要概括项目价值' }, + { name: 'challenge', type: 'textarea', label: '挑战', required: true, description: '客户面临的业务挑战与痛点' }, + { name: 'solution', type: 'textarea', label: '解决方案', required: true, description: '我们提供的解决方案与实施路径' }, + { name: 'result', type: 'textarea', label: '实施成果', required: true, description: '项目交付后的业务成果与价值' }, + { + name: 'metrics', + type: 'array', + label: '关键指标', + required: false, + description: '项目关键量化指标', + fields: [ + { name: 'value', type: 'text', label: '数值', required: true }, + { name: 'label', type: 'text', label: '标签', required: true }, + { name: 'highlight', type: 'boolean', label: '高亮', required: false }, + ], + }, + { + name: 'timeline', + type: 'array', + label: '项目时间线', + required: false, + description: '项目各阶段时间与描述', + fields: [ + { name: 'phase', type: 'text', label: '阶段', required: true }, + { name: 'duration', type: 'text', label: '周期', required: true }, + { name: 'description', type: 'textarea', label: '描述', required: true }, + ], + }, + { + name: 'services', + type: 'array', + label: '相关服务', + required: false, + description: '案例涉及的服务项目', + fields: [ + { name: 'id', type: 'text', label: '服务ID', required: true }, + { name: 'title', type: 'text', label: '服务名称', required: true }, + { name: 'description', type: 'textarea', label: '服务描述', required: true }, + ], + }, + { + name: 'testimonial', + type: 'object', + label: '客户评价', + required: false, + description: '客户证言与评价', + fields: [ + { name: 'quote', type: 'textarea', label: '评价内容', required: true }, + { name: 'author', type: 'text', label: '评价人', required: true }, + { name: 'role', type: 'text', label: '职位', required: true }, + { name: 'company', type: 'text', label: '公司', required: true }, + ], + }, + { + name: 'color', + type: 'text', + label: '主题色', + required: true, + description: '案例展示主题色', + options: [ + { label: '品牌色', value: 'brand' }, + { label: '蓝色', value: 'blue' }, + { label: '青色', value: 'teal' }, + { label: '琥珀色', value: 'amber' }, + { label: '紫色', value: 'purple' }, + ], + defaultValue: 'brand', + }, + { + name: 'featured', + type: 'boolean', + label: '精选案例', + required: false, + description: '是否在首页/推荐位展示', + defaultValue: false, + }, + ], +}; + +// ============ 通用转换函数 ============ + +/** + * 将业务数据记录转换为 CMS ContentItem + * @param source 原始数据记录 + * @param modelCode 内容模型代码 + * @param modelId 内容模型 ID + * @param index 索引(用于排序) + * @param options 配置选项(slug 字段名、title 字段名) + */ +function toContentItem>( + source: T, + modelCode: string, + modelId: string, + index: number, + options: { slugField?: string; titleField?: string } = {}, +): ContentItem { + const slugField = options.slugField || 'id'; + const titleField = options.titleField || 'title'; + return { + id: String(source.id || `item-${modelCode}-${index}`), + modelId, + modelCode, + title: String(source[titleField] || `未命名 ${index + 1}`), + slug: String(source[slugField] || source.id || `item-${index}`), + status: 'published', + version: 1, + sortOrder: index, + data: source, + createdAt: '2024-01-01T00:00:00Z', + updatedAt: '2024-06-01T00:00:00Z', + publishedAt: '2024-06-01T00:00:00Z', + createdBy: 'system', + updatedBy: 'system', + }; +} + +// ============ Mock 数据生成 ============ + +function caseStudyToContentItem(cs: CaseStudy): ContentItem { + const { id, slug, title, ...data } = cs; + return { + id, + modelId: 'model-case-study', + modelCode: 'case-study', + title, + slug, + status: 'published', + data: data as Record, + version: 1, + publishedAt: '2024-06-01T00:00:00Z', + createdBy: 'system', + updatedBy: 'system', + createdAt: '2024-01-01T00:00:00Z', + updatedAt: '2024-06-01T00:00:00Z', + }; +} + +const caseStudyItems: ContentItem[] = CASE_STUDIES.map(caseStudyToContentItem); +const newsItems: ContentItem[] = NEWS.map((n, i) => + toContentItem(n as unknown as Record, 'news', 'model-news', i), +); +const serviceItems: ContentItem[] = SERVICES.map((s, i) => + toContentItem(s as unknown as Record, 'service', 'model-service', i), +); +const productItems: ContentItem[] = PRODUCTS.map((p, i) => + toContentItem(p as unknown as Record, 'product', 'model-product', i), +); +const solutionItems: ContentItem[] = SOLUTIONS.map((s, i) => + toContentItem(s as unknown as Record, 'solution', 'model-solution', i), +); + +// ============ 模型注册表 ============ + +const contentModels: Record = { + 'case-study': caseStudyModel, +}; + +// ============ 所有 Mock 数据集合 ============ + +/** + * 所有模型的 Mock 数据集合 + * 用于 cmsClient mock 模式和 storage 默认数据初始化 + */ +export const allMockItems: Record = { + 'case-study': caseStudyItems, + news: newsItems, + service: serviceItems, + product: productItems, + solution: solutionItems, +}; + +// ============ 同步访问函数(server-safe) ============ + +/** + * 获取指定模型的所有 Mock 数据 + * 同步函数,server-safe,兼容静态导出(generateStaticParams) + */ +export function getMockItems(modelCode: string): ContentItem[] { + return allMockItems[modelCode] ?? []; +} + +/** + * 根据 slug 获取单条 Mock 数据 + * 同步函数,server-safe + */ +export function getMockItemBySlug(modelCode: string, slug: string): ContentItem | undefined { + return getMockItems(modelCode).find((item) => item.slug === slug); +} + +/** + * 根据 ID 获取单条 Mock 数据 + * 同步函数,server-safe + */ +export function getMockItemById(modelCode: string, id: string): ContentItem | undefined { + return getMockItems(modelCode).find((item) => item.id === id); +} + +/** + * 获取所有已注册的内容模型 + */ +export function getMockContentModel(code: string): ContentModel | undefined { + return contentModels[code]; +} + +/** + * 获取所有内容模型列表 + */ +export function getAllMockContentModels(): ContentModel[] { + return Object.values(contentModels); +} + +// ============ 兼容旧接口(保留导出,避免破坏性变更) ============ + +export function getMockCaseStudies(): ContentItem[] { + return caseStudyItems; +} + +export function getMockCaseStudyBySlug(slug: string): ContentItem | undefined { + return caseStudyItems.find((item) => item.slug === slug); +} + +export { contentModels, caseStudyItems }; diff --git a/src/lib/cms/mock-home.ts b/src/lib/cms/mock-home.ts new file mode 100644 index 0000000..5b92db4 --- /dev/null +++ b/src/lib/cms/mock-home.ts @@ -0,0 +1,239 @@ +import type { ContentZone, ContentItem } from './types'; +import { STATS } from '@/lib/constants/stats'; +import { SERVICES } from '@/lib/constants/services'; +import { getFeaturedCases } from '@/lib/constants/cases'; +import { NEWS } from '@/lib/constants/news'; +import { SOLUTIONS } from '@/lib/constants/solutions'; + +function createBaseItem(id: string, modelCode: string, title: string, slug: string, index: number): Omit { + return { + id, + modelId: modelCode, + modelCode, + title, + slug, + status: 'published', + version: 1, + sortOrder: index, + createdAt: '2024-01-01T00:00:00Z', + updatedAt: '2024-01-01T00:00:00Z', + publishedAt: '2024-01-01T00:00:00Z', + createdBy: 'system', + updatedBy: 'system', + }; +} + +function statToContentItem(stat: Record, index: number): ContentItem { + return { + ...createBaseItem(`stat-${index}`, 'stat-item', String(stat.label || ''), `stat-${index}`, index), + data: { + icon: stat.icon, + label: stat.label, + value: stat.value, + suffix: stat.suffix, + prefix: stat.prefix || '', + decimals: stat.decimals || 0, + description: stat.description || '', + trendValue: stat.trend || '', + trendDirection: typeof stat.trend === 'string' && stat.trend.startsWith('+') ? 'up' : 'down', + accentColor: stat.color || 'brand', + }, + }; +} + +function serviceToContentItem(service: { id: string; title: string; description: string; icon?: string; overview?: string; features?: unknown[]; benefits?: unknown[] }, index: number): ContentItem { + return { + ...createBaseItem(service.id, 'service', service.title, service.id, index), + data: { + description: service.description, + icon: service.icon, + overview: service.overview, + features: service.features || [], + benefits: service.benefits || [], + }, + }; +} + +function caseStudyToContentItem(cs: { id: string; title: string; slug: string; client?: string; industry?: string; subtitle?: string; challenge?: string; result?: string; metrics?: unknown[]; color?: string; featured?: boolean }, index: number): ContentItem { + return { + ...createBaseItem(cs.id, 'case-study', cs.title, cs.slug, index), + data: { + client: cs.client, + industry: cs.industry, + subtitle: cs.subtitle, + challenge: cs.challenge, + result: cs.result, + metrics: cs.metrics || [], + color: cs.color, + featured: cs.featured, + }, + }; +} + +function newsToContentItem(news: { id: string; title: string; excerpt?: string; date?: string; category?: string; image?: string; content?: string }, index: number): ContentItem { + return { + ...createBaseItem(news.id, 'news', news.title, news.id, index), + data: { + excerpt: news.excerpt, + date: news.date, + category: news.category, + image: news.image, + content: news.content, + }, + }; +} + +function solutionToContentItem(solution: { id: string; title: string; description: string; icon?: string; industry?: string; overview?: string }, index: number): ContentItem { + return { + ...createBaseItem(solution.id, 'solution', solution.title, solution.id, index), + data: { + description: solution.description, + icon: solution.icon, + industry: solution.industry || '', + overview: solution.overview || '', + }, + }; +} + +export function getMockHomeHeroBanner(): ContentItem { + return { + ...createBaseItem('hero-home-main', 'hero-banner', '企业数字化转型的可靠伙伴', 'hero-home-main', 0), + data: { + heading: '企业数字化转型的可靠伙伴', + subheading: '深耕企业数字化 10+ 年', + description: '从战略规划到落地实施,为成长型企业提供端到端的数字化解决方案。以务实的态度、专业的能力、长期的陪伴,帮助企业实现可持续增长。', + primaryCtaText: '了解解决方案', + primaryCtaLink: '/solutions', + secondaryCtaText: '联系我们', + secondaryCtaLink: '/contact', + theme: 'brand', + }, + }; +} + +export function getMockHomeStatsZone(): ContentZone { + const statList = Array.isArray(STATS) ? STATS : (STATS as { stats?: unknown[] }).stats || []; + const items = statList.slice(0, 4).map((s, i) => statToContentItem(s as Record, i)); + return { + id: 'zone-home-stats', + code: 'home-stats', + name: '首页数据指标区', + zoneKey: 'home-stats', + pageCode: 'home', + allowedModels: ['stat-item'], + items: items.map((item, i) => ({ + item, + itemId: item.id, + sortOrder: i, + variant: 'default', + })), + settings: { + layout: 'grid', + columns: 4, + gap: 'large', + }, + }; +} + +export function getMockHomeServicesZone(): ContentZone { + const items = SERVICES.slice(0, 4).map((s, i) => serviceToContentItem(s, i)); + return { + id: 'zone-home-services', + code: 'home-services', + name: '首页服务区', + zoneKey: 'home-services', + pageCode: 'home', + allowedModels: ['service'], + items: items.map((item, i) => ({ + item, + itemId: item.id, + sortOrder: i, + variant: 'default', + })), + settings: { + layout: 'grid', + columns: 4, + gap: 'large', + }, + }; +} + +export function getMockHomeCasesZone(): ContentZone { + const items = getFeaturedCases().slice(0, 3).map((cs, i) => caseStudyToContentItem(cs, i)); + return { + id: 'zone-home-cases', + code: 'home-cases', + name: '首页案例区', + zoneKey: 'home-cases', + pageCode: 'home', + allowedModels: ['case-study'], + items: items.map((item, i) => ({ + item, + itemId: item.id, + sortOrder: i, + variant: 'default', + })), + settings: { + layout: 'grid', + columns: 3, + gap: 'large', + }, + }; +} + +export function getMockHomeNewsZone(): ContentZone { + const items = NEWS.slice(0, 3).map((n, i) => newsToContentItem(n, i)); + return { + id: 'zone-home-news', + code: 'home-news', + name: '首页新闻区', + zoneKey: 'home-news', + pageCode: 'home', + allowedModels: ['news'], + items: items.map((item, i) => ({ + item, + itemId: item.id, + sortOrder: i, + variant: 'default', + })), + settings: { + layout: 'grid', + columns: 3, + gap: 'large', + }, + }; +} + +export function getMockHomeSolutionsZone(): ContentZone { + const items = SOLUTIONS.slice(0, 4).map((s, i) => solutionToContentItem(s, i)); + return { + id: 'zone-home-solutions', + code: 'home-solutions', + name: '首页方案区', + zoneKey: 'home-solutions', + pageCode: 'home', + allowedModels: ['solution'], + items: items.map((item, i) => ({ + item, + itemId: item.id, + sortOrder: i, + variant: 'default', + })), + settings: { + layout: 'grid', + columns: 4, + gap: 'large', + }, + }; +} + +export function getMockContentZone(zoneKey: string, _page = 'home'): ContentZone | null { + const zones: Record = { + 'home-stats': getMockHomeStatsZone(), + 'home-services': getMockHomeServicesZone(), + 'home-cases': getMockHomeCasesZone(), + 'home-news': getMockHomeNewsZone(), + 'home-solutions': getMockHomeSolutionsZone(), + }; + return zones[zoneKey] || null; +} diff --git a/src/lib/cms/registry.ts b/src/lib/cms/registry.ts new file mode 100644 index 0000000..4a4aef0 --- /dev/null +++ b/src/lib/cms/registry.ts @@ -0,0 +1,184 @@ +import type { ContentModel, FieldDefinition, ContentItem } from './types'; +import { getMockCaseStudies, getMockCaseStudyBySlug } from './mock-data'; + +export interface ContentTypeConfig { + model: ContentModel; + mockData?: { + list: () => Promise<{ items: ContentItem[]; total: number }>; + getBySlug?: (slug: string) => Promise; + getById?: (id: string) => Promise; + }; + listPage?: { + route: string; + }; + detailPage?: { + routePattern: string; + }; +} + +const contentTypeRegistry = new Map(); + +export function registerContentType( + config: ContentTypeConfig +): void { + contentTypeRegistry.set(config.model.code, config); +} + +export function getContentTypeConfig(code: string): ContentTypeConfig | undefined { + return contentTypeRegistry.get(code); +} + +export function getAllContentTypeConfigs(): ContentTypeConfig[] { + return Array.from(contentTypeRegistry.values()); +} + +export function getContentModel(code: string): ContentModel | undefined { + return contentTypeRegistry.get(code)?.model; +} + +export function getAllContentModels(): ContentModel[] { + return getAllContentTypeConfigs().map((c) => c.model); +} + +const caseStudyFields: FieldDefinition[] = [ + { + name: 'client', + type: 'text', + label: '客户名称', + required: true, + description: '客户公司或机构名称', + }, + { + name: 'industry', + type: 'text', + label: '所属行业', + required: true, + description: '客户所属行业领域', + }, + { + name: 'companySize', + type: 'text', + label: '公司规模', + required: true, + }, + { + name: 'subtitle', + type: 'text', + label: '副标题', + required: true, + description: '案例副标题,简要概括项目价值', + }, + { + name: 'challenge', + type: 'textarea', + label: '挑战', + required: true, + description: '客户面临的业务挑战与痛点', + }, + { + name: 'solution', + type: 'textarea', + label: '方案', + required: true, + description: '我们提供的解决方案', + }, + { + name: 'result', + type: 'textarea', + label: '成果', + required: true, + description: '项目成果与价值', + }, + { + name: 'metrics', + type: 'array', + label: '核心指标', + description: '可量化的项目成果指标', + fields: [ + { name: 'value', type: 'text', label: '数值', required: true }, + { name: 'label', type: 'text', label: '指标名称', required: true }, + { name: 'highlight', type: 'boolean', label: '突出显示', defaultValue: false }, + ], + }, + { + name: 'timeline', + type: 'array', + label: '项目时间线', + fields: [ + { name: 'phase', type: 'text', label: '阶段名称', required: true }, + { name: 'duration', type: 'text', label: '持续时间', required: true }, + { name: 'description', type: 'textarea', label: '阶段描述' }, + { name: 'deliverables', type: 'array', label: '交付物', fields: [{ name: 'name', type: 'text', label: '名称' }] }, + ], + }, + { + name: 'services', + type: 'array', + label: '相关服务', + fields: [ + { name: 'name', type: 'text', label: '服务名称', required: true }, + { name: 'description', type: 'textarea', label: '服务描述' }, + ], + }, + { + name: 'testimonial', + type: 'object', + label: '客户证言', + fields: [ + { name: 'quote', type: 'textarea', label: '证言内容' }, + { name: 'author', type: 'text', label: '发言人' }, + { name: 'role', type: 'text', label: '职位' }, + { name: 'company', type: 'text', label: '公司' }, + ], + }, + { + name: 'color', + type: 'text', + label: '主题色', + defaultValue: 'brand', + options: [ + { label: '品牌绿', value: 'brand' }, + { label: '蓝', value: 'blue' }, + { label: '青', value: 'teal' }, + { label: '琥珀', value: 'amber' }, + { label: '紫', value: 'purple' }, + ], + }, + { + name: 'featured', + type: 'boolean', + label: '精选案例', + defaultValue: false, + description: '是否在首页精选区展示', + }, +]; + +registerContentType({ + model: { + id: 'model-case-study', + code: 'case-study', + name: '案例研究', + description: '客户成功案例与数字化转型实践', + fields: caseStudyFields, + isPageType: true, + urlPattern: '/cases/{slug}', + hasVersions: true, + hasDraft: true, + icon: 'briefcase', + createdAt: '2024-01-01T00:00:00Z', + updatedAt: '2024-01-01T00:00:00Z', + }, + mockData: { + list: async () => { + const items = getMockCaseStudies(); + return { items, total: items.length }; + }, + getBySlug: async (slug: string) => getMockCaseStudyBySlug(slug) ?? null, + }, + listPage: { + route: '/cases', + }, + detailPage: { + routePattern: '/cases/{slug}', + }, +}); diff --git a/src/lib/cms/renderers.tsx b/src/lib/cms/renderers.tsx new file mode 100644 index 0000000..24a06ed --- /dev/null +++ b/src/lib/cms/renderers.tsx @@ -0,0 +1,264 @@ +'use client'; + +import type { ContentItemComponentProps } from './component-registry'; + +function getField(item: Record, key: string, fallback = ''): string { + const value = item[key]; + if (value === null || value === undefined) return fallback; + return String(value); +} + +export function NewsCardRenderer({ item, variant = 'default', className = '' }: ContentItemComponentProps) { + const data = item.data as Record; + const title = getField(data, 'title') || item.title; + const excerpt = getField(data, 'excerpt'); + const date = getField(data, 'date'); + const category = getField(data, 'category'); + const image = getField(data, 'image'); + const link = `/news/${item.slug}`; + + if (variant === 'featured') { + return ( +
+
+ {image && ( + {title} + )} +
+
+
+ {category} + {date} +
+

{title}

+

{excerpt}

+
+
+ ); + } + + return ( + +
+ {image && ( +
+ {title} +
+ )} +
+
+ {category} + {date} +
+

{title}

+
+
+
+ ); +} + +export function StatCardRenderer({ item, className = '' }: ContentItemComponentProps) { + const data = item.data as Record; + const label = getField(data, 'label') || item.title; + const value = data.value ?? 0; + const prefix = getField(data, 'prefix'); + const suffix = getField(data, 'suffix'); + const decimals = (data.decimals as number) ?? 0; + const description = getField(data, 'description'); + const trendValue = getField(data, 'trendValue'); + const trendDirection = getField(data, 'trendDirection', 'up'); + const accentColor = getField(data, 'accentColor', 'brand'); + const icon = getField(data, 'icon'); + + const formattedValue = typeof value === 'number' + ? `${prefix}${value.toLocaleString('zh-CN', { minimumFractionDigits: decimals, maximumFractionDigits: decimals })}${suffix}` + : `${prefix}${value}${suffix}`; + + const accentClasses: Record = { + brand: 'from-brand/20 to-brand/0 text-brand', + blue: 'from-blue-500/20 to-blue-500/0 text-blue-400', + teal: 'from-teal-500/20 to-teal-500/0 text-teal-400', + amber: 'from-amber-500/20 to-amber-500/0 text-amber-400', + purple: 'from-purple-500/20 to-purple-500/0 text-purple-400', + }; + + const trendClasses: Record = { + up: 'text-emerald-400', + down: 'text-red-400', + neutral: 'text-white/50', + }; + + return ( +
+
+
+ {icon &&
{icon}
} +
{formattedValue}
+
{label}
+ {description &&
{description}
} + {trendValue && ( +
+ {trendDirection === 'up' && '↑'} + {trendDirection === 'down' && '↓'} + {trendDirection === 'neutral' && '→'} + {trendValue} +
+ )} +
+
+ ); +} + +export function ServiceCardRenderer({ item, className = '' }: ContentItemComponentProps) { + const data = item.data as Record; + const title = getField(data, 'title') || item.title; + const description = getField(data, 'description'); + const icon = getField(data, 'icon'); + const link = `/services/${item.slug}`; + + return ( + +
+
+ {icon && ( +
+ {icon} +
+ )} +

{title}

+

{description}

+
+ 了解详情 + +
+
+
+ ); +} + +export function ProductCardRenderer({ item, className = '' }: ContentItemComponentProps) { + const data = item.data as Record; + const title = getField(data, 'title') || item.title; + const description = getField(data, 'description'); + const image = getField(data, 'image'); + const category = getField(data, 'category'); + const status = getField(data, 'status'); + const link = `/products/${item.slug}`; + + const statusColors: Record = { + '研发中': 'bg-amber-500/20 text-amber-400', + '内测中': 'bg-blue-500/20 text-blue-400', + '已发布': 'bg-emerald-500/20 text-emerald-400', + }; + + return ( + + {image && ( +
+ {title} + {status && ( + + {status} + + )} +
+ )} +
+ {category &&
{category}
} +

{title}

+

{description}

+
+
+ ); +} + +export function SolutionCardRenderer({ item, className = '' }: ContentItemComponentProps) { + const data = item.data as Record; + const title = getField(data, 'title') || item.title; + const description = getField(data, 'description'); + const icon = getField(data, 'icon'); + const industry = getField(data, 'industry'); + const link = `/solutions/${item.slug}`; + + return ( + +
+ {icon && ( +
+ {icon} +
+ )} + {industry &&
{industry}
} +

{title}

+

{description}

+
+ 查看方案 + +
+
+
+ ); +} + +export function HeroBannerRenderer({ item, className = '' }: ContentItemComponentProps) { + const data = item.data as Record; + const heading = getField(data, 'heading') || item.title; + const subheading = getField(data, 'subheading'); + const description = getField(data, 'description'); + const primaryCtaText = getField(data, 'primaryCtaText', '了解更多'); + const primaryCtaLink = getField(data, 'primaryCtaLink', '/solutions'); + const secondaryCtaText = getField(data, 'secondaryCtaText', '联系我们'); + const secondaryCtaLink = getField(data, 'secondaryCtaLink', '/contact'); + const theme = getField(data, 'theme', 'brand'); + + const themeGradients: Record = { + brand: 'from-brand/30 via-brand/10 to-transparent', + blue: 'from-blue-500/30 via-blue-500/10 to-transparent', + teal: 'from-teal-500/30 via-teal-500/10 to-transparent', + amber: 'from-amber-500/30 via-amber-500/10 to-transparent', + purple: 'from-purple-500/30 via-purple-500/10 to-transparent', + }; + + return ( +
+
+
+
+
+
+
+
+ {subheading && ( +
+ + {subheading} +
+ )} +

+ {heading} +

+ {description && ( +

+ {description} +

+ )} + +
+
+
+ ); +} diff --git a/src/lib/cms/storage.ts b/src/lib/cms/storage.ts new file mode 100644 index 0000000..0b75ac1 --- /dev/null +++ b/src/lib/cms/storage.ts @@ -0,0 +1,239 @@ +'use client'; + +import type { ContentZone, ContentItem, ContentZoneSettings } from './types'; +import { + getMockHomeStatsZone, + getMockHomeServicesZone, + getMockHomeCasesZone, + getMockHomeNewsZone, + getMockHomeSolutionsZone, + getMockHomeHeroBanner, +} from './mock-home'; +import { NEWS } from '@/lib/constants/news'; +import { SERVICES } from '@/lib/constants/services'; +import { PRODUCTS } from '@/lib/constants/products'; +import { SOLUTIONS } from '@/lib/constants/solutions'; +import { getFeaturedCases } from '@/lib/constants/cases'; +import { STATS } from '@/lib/constants/stats'; + +const STORAGE_KEY = 'novalon-cms-data'; +const VERSION = '1.0.0'; + +interface CmsStorageData { + version: string; + zones: Record; + items: Record; + updatedAt: string; +} + +function getDefaultZones(): Record { + return { + 'home-stats': getMockHomeStatsZone(), + 'home-services': getMockHomeServicesZone(), + 'home-solutions': getMockHomeSolutionsZone(), + 'home-cases': getMockHomeCasesZone(), + 'home-news': getMockHomeNewsZone(), + }; +} + +function toContentItem(source: Record, modelCode: string, index: number): ContentItem { + return { + id: String(source.id || `item-${index}`), + modelId: modelCode, + modelCode, + title: String(source.title || source.label || source.heading || `未命名 ${index + 1}`), + slug: String(source.slug || source.id || `item-${index}`), + status: 'published', + version: 1, + sortOrder: index, + data: source, + createdAt: '2024-01-01T00:00:00Z', + updatedAt: '2024-01-01T00:00:00Z', + publishedAt: '2024-01-01T00:00:00Z', + createdBy: 'system', + updatedBy: 'system', + }; +} + +function getDefaultItems(): Record { + const statList = Array.isArray(STATS) ? STATS : (STATS as { stats?: unknown[] }).stats || []; + return { + 'case-study': getFeaturedCases().map((cs, i) => toContentItem(cs as unknown as Record, 'case-study', i)), + news: NEWS.map((n, i) => toContentItem(n as unknown as Record, 'news', i)), + service: SERVICES.map((s, i) => toContentItem(s as unknown as Record, 'service', i)), + product: PRODUCTS.map((p, i) => toContentItem(p as unknown as Record, 'product', i)), + solution: SOLUTIONS.map((s, i) => toContentItem(s as unknown as Record, 'solution', i)), + 'hero-banner': [toContentItem(getMockHomeHeroBanner().data, 'hero-banner', 0)], + 'stat-item': statList.map((s, i) => toContentItem({ ...(s as Record), accentColor: (s as Record).color }, 'stat-item', i)), + }; +} + +function getDefaultData(): CmsStorageData { + return { + version: VERSION, + zones: getDefaultZones(), + items: getDefaultItems(), + updatedAt: new Date().toISOString(), + }; +} + +function isBrowser(): boolean { + return typeof window !== 'undefined'; +} + +export function loadCmsData(): CmsStorageData { + if (!isBrowser()) { + return getDefaultData(); + } + try { + const raw = localStorage.getItem(STORAGE_KEY); + if (!raw) { + const defaultData = getDefaultData(); + saveCmsData(defaultData); + return defaultData; + } + const parsed = JSON.parse(raw) as CmsStorageData; + if (parsed.version !== VERSION) { + const defaultData = getDefaultData(); + saveCmsData(defaultData); + return defaultData; + } + return parsed; + } catch { + return getDefaultData(); + } +} + +export function saveCmsData(data: CmsStorageData): void { + if (!isBrowser()) return; + try { + localStorage.setItem( + STORAGE_KEY, + JSON.stringify({ ...data, updatedAt: new Date().toISOString() }) + ); + window.dispatchEvent(new CustomEvent('cms-updated')); + } catch { + // silently fail + } +} + +export function getContentZone(zoneKey: string): ContentZone | null { + const data = loadCmsData(); + return data.zones[zoneKey] || null; +} + +export function saveContentZone(zoneKey: string, zone: ContentZone): void { + const data = loadCmsData(); + data.zones[zoneKey] = zone; + saveCmsData(data); +} + +export function getAllContentZones(): Record { + return loadCmsData().zones; +} + +export function getContentItems(modelCode: string): ContentItem[] { + const data = loadCmsData(); + return data.items[modelCode] || []; +} + +export function saveContentItem(modelCode: string, item: ContentItem): void { + const data = loadCmsData(); + const items = data.items[modelCode] || []; + const idx = items.findIndex((i) => i.id === item.id); + if (idx >= 0) { + items[idx] = item; + } else { + items.push(item); + } + data.items[modelCode] = items; + saveCmsData(data); +} + +export function deleteContentItem(modelCode: string, itemId: string): void { + const data = loadCmsData(); + const items = data.items[modelCode] || []; + data.items[modelCode] = items.filter((i) => i.id !== itemId); + saveCmsData(data); +} + +export function resetCmsData(): CmsStorageData { + const defaultData = getDefaultData(); + saveCmsData(defaultData); + return defaultData; +} + +export function exportCmsData(): string { + const data = loadCmsData(); + return JSON.stringify(data, null, 2); +} + +export function importCmsData(json: string): boolean { + try { + const data = JSON.parse(json) as CmsStorageData; + if (!data.version || !data.zones || !data.items) return false; + saveCmsData(data); + return true; + } catch { + return false; + } +} + +export function getContentZoneKeys(): string[] { + const data = loadCmsData(); + return Object.keys(data.zones); +} + +export function getAvailableItemsForZone(zoneKey: string): ContentItem[] { + const zone = getContentZone(zoneKey); + if (!zone || zone.items.length === 0) return []; + const firstItem = zone.items.find((zi) => zi.item); + if (!firstItem?.item) return []; + const modelCode = firstItem.item.modelCode; + const allItems = getContentItems(modelCode); + const existingIds = new Set( + zone.items + .filter((zi) => zi.item) + .map((zi) => zi.item!.id) + ); + return allItems.filter((item) => !existingIds.has(item.id)); +} + +export function addItemToZone(zoneKey: string, item: ContentItem): void { + const zone = getContentZone(zoneKey); + if (!zone) return; + zone.items.push({ + item, + itemId: item.id, + sortOrder: zone.items.length, + variant: 'default', + }); + saveContentZone(zoneKey, zone); +} + +export function removeItemFromZone(zoneKey: string, itemId: string): void { + const zone = getContentZone(zoneKey); + if (!zone) return; + zone.items = zone.items.filter((zi) => zi.item?.id !== itemId); + zone.items.forEach((zi, i) => { zi.sortOrder = i; }); + saveContentZone(zoneKey, zone); +} + +export function reorderZoneItems(zoneKey: string, fromIndex: number, toIndex: number): void { + const zone = getContentZone(zoneKey); + if (!zone) return; + const items = [...zone.items].sort((a, b) => (a.sortOrder ?? 0) - (b.sortOrder ?? 0)); + const removed = items.splice(fromIndex, 1)[0]; + if (!removed) return; + items.splice(toIndex, 0, removed); + items.forEach((item, i) => { item.sortOrder = i; }); + zone.items = items; + saveContentZone(zoneKey, zone); +} + +export function updateZoneSettings(zoneKey: string, settings: Partial): void { + const zone = getContentZone(zoneKey); + if (!zone) return; + zone.settings = { ...(zone.settings || {}), ...settings }; + saveContentZone(zoneKey, zone); +} diff --git a/src/lib/cms/types.ts b/src/lib/cms/types.ts new file mode 100644 index 0000000..c98ac48 --- /dev/null +++ b/src/lib/cms/types.ts @@ -0,0 +1,179 @@ +export type FieldType = + | 'text' + | 'textarea' + | 'richtext' + | 'number' + | 'boolean' + | 'date' + | 'datetime' + | 'image' + | 'media' + | 'reference' + | 'references' + | 'json' + | 'array' + | 'object' + | 'select' + | 'dropdown'; + +export interface FieldDefinition { + name: string; + type: FieldType; + label: string; + required?: boolean; + description?: string; + placeholder?: string; + defaultValue?: unknown; + validation?: { + min?: number; + max?: number; + pattern?: string; + custom?: string; + }; + options?: Array<{ label: string; value: string | number | boolean }>; + referenceModel?: string; + ui?: { + component?: string; + width?: 'full' | 'half' | 'third'; + helpText?: string; + }; + fields?: FieldDefinition[]; +} + +export interface ContentModel { + id: string; + code: string; + name: string; + description?: string; + fields: FieldDefinition[]; + isPageType: boolean; + urlPattern?: string; + hasVersions: boolean; + hasDraft: boolean; + icon?: string; + createdAt: string; + updatedAt: string; +} + +export type ContentStatus = 'draft' | 'review' | 'published' | 'archived'; + +export interface ContentItem { + id: string; + modelId: string; + modelCode: string; + title: string; + slug?: string; + status: ContentStatus; + data: Record; + version: number; + sortOrder?: number; + publishedAt?: string; + createdBy: string; + updatedBy: string; + createdAt: string; + updatedAt: string; +} + +export interface ContentZoneItem { + itemId?: string; + item?: ContentItem; + sortOrder: number; + variant?: string; + config?: Record; +} + +export interface ContentZoneSettings { + layout?: 'grid' | 'list' | 'carousel'; + columns?: number; + gap?: 'small' | 'medium' | 'large'; + showTitle?: boolean; + showDescription?: boolean; + maxItems?: number; +} + +export interface ContentZone { + id: string; + code: string; + name: string; + description?: string; + pageCode?: string; + zoneKey?: string; + allowedModels: string[]; + items: ContentZoneItem[]; + settings?: ContentZoneSettings; +} + +export interface ThemeConfig { + id: string; + siteName: string; + siteDescription: string; + logo?: string; + favicon?: string; + colors: { + primary: string; + secondary: string; + accent: string; + background: string; + text: string; + }; + seo: { + defaultTitle: string; + defaultDescription: string; + defaultKeywords: string; + ogImage?: string; + }; + social: { + wechat?: string; + weibo?: string; + linkedin?: string; + github?: string; + }; + settings: Record; +} + +export interface MediaAsset { + id: string; + name: string; + path: string; + url: string; + mimeType: string; + size: number; + width?: number; + height?: number; + alt?: string; + storageType: 'local' | 'oss' | 's3'; + createdBy: string; + createdAt: string; +} + +export interface ContentQueryParams { + modelCode: string; + status?: ContentStatus; + page?: number; + pageSize?: number; + sortBy?: string; + sortOrder?: 'asc' | 'desc'; + filters?: Record; + search?: string; +} + +export interface PaginatedResult { + items: T[]; + total: number; + page: number; + pageSize: number; + totalPages: number; +} + +export interface AuditLog { + id: string; + module: 'model' | 'item' | 'zone' | 'theme' | 'media'; + targetId: string; + action: 'create' | 'update' | 'delete' | 'publish' | 'unpublish'; + operator: string; + beforeData?: Record; + afterData?: Record; + ip?: string; + userAgent?: string; + createdAt: string; +} diff --git a/src/lib/constants/cases.ts b/src/lib/constants/cases.ts new file mode 100644 index 0000000..0ad1356 --- /dev/null +++ b/src/lib/constants/cases.ts @@ -0,0 +1,284 @@ +export interface CaseMetric { + value: string; + label: string; + highlight?: boolean; +} + +export interface CaseTimelinePhase { + phase: string; + duration: string; + description: string; +} + +export interface CaseService { + id: string; + title: string; + description: string; +} + +export interface CaseTestimonial { + quote: string; + author: string; + role: string; + company: string; +} + +export interface CaseStudy { + id: string; + slug: string; + client: string; + industry: string; + companySize: string; + title: string; + subtitle: string; + challenge: string; + solution: string; + result: string; + metrics: CaseMetric[]; + timeline: CaseTimelinePhase[]; + services: CaseService[]; + testimonial?: CaseTestimonial; + color: 'brand' | 'blue' | 'teal' | 'amber' | 'purple'; + featured?: boolean; +} + +export const CASE_STUDIES: CaseStudy[] = [ + { + id: 'case-001', + slug: 'manufacturing-erp-upgrade', + client: '某上市制造企业', + industry: '制造业', + companySize: '1000人以上', + title: '大型制造企业 ERP 升级与数字化转型', + subtitle: '从传统 ERP 到智能运营平台的跨越', + challenge: + '该企业原有 ERP 系统已运行 8 年,无法支撑新业务模式,数据孤岛严重,决策效率低下。生产、财务、供应链各系统独立运作,数据口径不统一,管理层难以获取实时运营洞察。', + solution: + '采用分步迁移策略,以数据中台为核心,重构业务流程,实现从生产到财务的全链路数字化。引入敏捷开发方法论,分 6 个阶段推进,确保业务连续性与系统稳定性。', + result: + '项目一次性上线成功,业务中断时间控制在 48 小时内,关键指标全面超越预期。建立了数据驱动的运营体系,决策效率提升 3 倍以上。', + metrics: [ + { value: '40%', label: '生产效率提升', highlight: true }, + { value: '25%', label: '库存成本下降' }, + { value: '99.5%', label: '数据准确率' }, + { value: '3x', label: '决策效率提升' }, + ], + timeline: [ + { phase: '诊断评估', duration: '4周', description: '全面调研现有系统与业务流程,识别核心痛点与机会点' }, + { phase: '方案设计', duration: '6周', description: '制定技术架构与业务流程重构方案,明确实施路径' }, + { phase: '系统建设', duration: '4个月', description: '分模块开发与配置,同步开展数据清洗与迁移准备' }, + { phase: '上线切换', duration: '2个月', description: '用户培训、试点运行、全量切换,确保平稳过渡' }, + ], + services: [ + { id: 'digital-consulting', title: '数字化转型咨询', description: '战略规划与路线图设计,帮助企业明确数字化方向' }, + { id: 'erp-implementation', title: 'ERP 实施与升级', description: '全流程 ERP 实施服务,确保系统落地见效' }, + { id: 'data-platform', title: '数据中台建设', description: '构建统一数据底座,打通数据孤岛,释放数据价值' }, + ], + testimonial: { + quote: + '睿新团队不仅完成了系统升级,更帮助我们建立了数据驱动的运营体系,这是超出预期的价值。整个项目过程专业、高效,是真正值得信赖的合作伙伴。', + author: '王总', + role: 'CTO', + company: '某上市制造企业', + }, + color: 'brand', + featured: true, + }, + { + id: 'case-002', + slug: 'retail-omnichannel', + client: '某区域零售龙头', + industry: '贸易零售', + companySize: '500-1000人', + title: '连锁零售全渠道数字化升级', + subtitle: '线上线下一体化,重塑零售新体验', + challenge: + '线上线下渠道割裂,会员体系不统一,库存数据不同步,导致客户体验差、运营成本高。促销活动无法跨渠道联动,会员复购率低,库存周转慢。', + solution: + '构建全渠道中台,打通会员、库存、订单、营销体系,实现线上线下一体化运营。建立统一的会员中心与库存中心,支持 200+ 门店实时同步。', + result: + '6 个月内完成 200+ 门店的系统切换,营收实现快速增长,会员复购率显著提升。全渠道库存准确率达到 99% 以上,缺货率大幅下降。', + metrics: [ + { value: '25%', label: '营收增长', highlight: true }, + { value: '50%', label: '库存周转优化' }, + { value: '3x', label: '会员复购率' }, + { value: '200+', label: '门店覆盖' }, + ], + timeline: [ + { phase: '业务诊断', duration: '3周', description: '全渠道现状评估,识别核心瓶颈与优化方向' }, + { phase: '中台建设', duration: '3个月', description: '全渠道中台核心模块开发,打通会员、库存、订单' }, + { phase: '门店接入', duration: '2个月', description: '200+ 门店系统切换,人员培训与运营支持' }, + { phase: '运营优化', duration: '持续', description: '数据驱动的持续运营优化,营销活动精细化' }, + ], + services: [ + { id: 'omnichannel', title: '全渠道零售解决方案', description: '构建线上线下一体化的零售运营体系' }, + { id: 'crm', title: 'CRM 与会员体系', description: '打造以客户为中心的会员运营体系' }, + { id: 'digital-consulting', title: '数字化转型咨询', description: '零售行业数字化转型战略与路径规划' }, + ], + testimonial: { + quote: + '从咨询到落地,睿新团队展现出了超出预期的专业能力和责任心。他们不仅懂技术,更懂零售业务,是值得信赖的长期合作伙伴。', + author: '李总', + role: 'CEO', + company: '某区域零售龙头', + }, + color: 'blue', + featured: true, + }, + { + id: 'case-003', + slug: 'healthcare-data-platform', + client: '某三甲医院', + industry: '医疗健康', + companySize: '1000人以上', + title: '医院数据中台与智慧运营平台建设', + subtitle: '以数据驱动医疗质量与运营效率双提升', + challenge: + '医院各业务系统(HIS、EMR、LIS、PACS等)独立建设,数据分散,难以形成统一视图。运营决策依赖人工统计报表,时效性差、准确性低,无法支撑精细化管理需求。', + solution: + '构建医院数据中台,打通 20+ 业务系统数据,建立统一的数据标准与治理体系。基于数据中台建设智慧运营平台,实现运营指标实时监控、智能预警与辅助决策。', + result: + '运营数据从 T+1 变为实时,决策响应速度提升 10 倍以上。医疗质量指标持续改善,运营效率显著提升,成为区域智慧医院标杆。', + metrics: [ + { value: '10x', label: '决策效率提升', highlight: true }, + { value: '20+', label: '系统打通' }, + { value: '99.9%', label: '数据准确率' }, + { value: '30%', label: '运营成本下降' }, + ], + timeline: [ + { phase: '现状调研', duration: '4周', description: '全面梳理 20+ 业务系统,建立数据资产目录' }, + { phase: '中台建设', duration: '5个月', description: '数据中台与数据治理体系建设,构建统一数据底座' }, + { phase: '应用开发', duration: '3个月', description: '智慧运营平台开发,涵盖运营、质控、后勤等模块' }, + { phase: '推广运营', duration: '持续', description: '全院推广使用,持续优化迭代' }, + ], + services: [ + { id: 'data-platform', title: '数据中台建设', description: '构建统一数据底座,释放医疗数据价值' }, + { id: 'bi-analytics', title: 'BI 与数据分析', description: '智慧运营与数据可视化平台建设' }, + { id: 'digital-consulting', title: '数字化转型咨询', description: '医疗行业数字化转型规划与实施指导' }, + ], + color: 'teal', + featured: true, + }, + { + id: 'case-004', + slug: 'education-crm-system', + client: '某教育科技集团', + industry: '教育培训', + companySize: '500-1000人', + title: '教育集团 CRM 与营销自动化系统建设', + subtitle: '从线索到续费的全链路数字化运营', + challenge: + '招生线索管理分散,各校区独立运营,无法实现统一的线索分配与跟进。营销活动效果难以追踪,客户生命周期管理缺失,导致线索转化率低、续费率不稳定。', + solution: + '建设集团统一的 CRM 系统,覆盖线索管理、客户跟进、营销自动化、续费管理全链路。打通线上线下营销渠道,建立数据驱动的营销运营体系。', + result: + '线索转化率提升 45%,平均跟进周期缩短 60%,营销 ROI 提升 2 倍以上。实现了集团层面的统一客户管理与数据洞察。', + metrics: [ + { value: '45%', label: '线索转化率提升', highlight: true }, + { value: '60%', label: '跟进周期缩短' }, + { value: '2x', label: '营销 ROI 提升' }, + { value: '35%', label: '续费率提升' }, + ], + timeline: [ + { phase: '业务梳理', duration: '3周', description: '全链路业务流程梳理,明确需求与优化方向' }, + { phase: '系统建设', duration: '4个月', description: 'CRM 核心系统开发,集成营销自动化能力' }, + { phase: '推广使用', duration: '2个月', description: '各校区推广培训,建立运营规范与考核体系' }, + { phase: '持续优化', duration: '持续', description: '数据驱动的持续运营优化,提升转化效果' }, + ], + services: [ + { id: 'crm', title: 'CRM 与会员体系', description: '全渠道客户关系管理与营销自动化' }, + { id: 'digital-marketing', title: '数字营销服务', description: '数据驱动的营销体系建设与优化' }, + { id: 'digital-consulting', title: '数字化转型咨询', description: '教育行业数字化转型规划与实施' }, + ], + color: 'amber', + }, + { + id: 'case-005', + slug: 'finance-erp-upgrade', + client: '某金融科技公司', + industry: '金融服务', + companySize: '200-500人', + title: '金融科技公司财务共享中心建设', + subtitle: '打造高效、合规、智能的财务运营体系', + challenge: + '随着业务快速发展,传统财务模式难以支撑多业务线、多主体的复杂核算需求。财务流程不规范、数据不及时,合规风险高,财务团队疲于应付事务性工作。', + solution: + '建设财务共享中心,统一核算标准与流程,引入财务 RPA 自动化处理,建立业财一体化的数据平台。实现从核算型财务向价值型财务的转型。', + result: + '月结时间从 10 天缩短到 3 天,财务处理效率提升 70%。合规风险显著降低,财务团队得以更多投入业务分析与决策支持工作。', + metrics: [ + { value: '70%', label: '财务效率提升', highlight: true }, + { value: '3天', label: '月结周期缩短' }, + { value: '50%', label: '人力成本节约' }, + { value: '0', label: '重大合规事故' }, + ], + timeline: [ + { phase: '现状评估', duration: '3周', description: '财务流程全面诊断,识别优化点与风险点' }, + { phase: '方案设计', duration: '4周', description: '财务共享中心方案设计,明确组织、流程、系统规划' }, + { phase: '系统建设', duration: '4个月', description: '财务共享系统与 RPA 机器人开发部署' }, + { phase: '上线运营', duration: '3个月', description: '共享中心正式运营,持续优化流程与系统' }, + ], + services: [ + { id: 'erp-implementation', title: 'ERP 实施与升级', description: '财务模块深度实施与业财一体化' }, + { id: 'digital-consulting', title: '数字化转型咨询', description: '财务数字化转型与共享中心建设咨询' }, + { id: 'rpa', title: 'RPA 智能自动化', description: '财务场景 RPA 机器人开发与运营' }, + ], + color: 'purple', + }, + { + id: 'case-006', + slug: 'logistics-supply-chain', + client: '某物流供应链企业', + industry: '物流运输', + companySize: '500-1000人', + title: '物流企业供应链协同平台建设', + subtitle: '端到端供应链可视化与智能调度', + challenge: + '物流环节信息不透明,运输状态无法实时追踪,客户体验差。调度依赖人工经验,车辆空驶率高,成本难以下降。上下游协同效率低,异常处理响应慢。', + solution: + '建设供应链协同平台,实现订单、运输、仓储全链路可视化。引入智能调度算法,优化运力资源配置。建立上下游协同机制,提升整体供应链效率。', + result: + '运输全程可视化率达到 95% 以上,车辆空驶率降低 20%,客户满意度提升 30%。调度效率大幅提升,异常响应时间从 2 小时缩短到 15 分钟。', + metrics: [ + { value: '95%+', label: '可视化覆盖率', highlight: true }, + { value: '20%', label: '空驶率降低' }, + { value: '30%', label: '客户满意度提升' }, + { value: '15min', label: '异常响应时间' }, + ], + timeline: [ + { phase: '业务诊断', duration: '3周', description: '供应链全链路调研,识别核心痛点与优化机会' }, + { phase: '平台建设', duration: '5个月', description: '供应链协同平台开发,集成 TMS、WMS 等系统' }, + { phase: '智能升级', duration: '2个月', description: '智能调度算法上线,持续优化调优' }, + { phase: '全面推广', duration: '持续', description: '全业务线推广使用,上下游协同扩展' }, + ], + services: [ + { id: 'supply-chain', title: '供应链数字化', description: '端到端供应链数字化与智能协同' }, + { id: 'data-platform', title: '数据中台建设', description: '物流数据中台与可视化平台建设' }, + { id: 'digital-consulting', title: '数字化转型咨询', description: '物流行业数字化转型战略规划' }, + ], + color: 'brand', + }, +]; + +export const CASE_INDUSTRIES = [ + { id: 'all', label: '全部行业' }, + { id: '制造业', label: '制造业' }, + { id: '贸易零售', label: '贸易零售' }, + { id: '医疗健康', label: '医疗健康' }, + { id: '教育培训', label: '教育培训' }, + { id: '金融服务', label: '金融服务' }, + { id: '物流运输', label: '物流运输' }, +]; + +export function getCaseBySlug(slug: string): CaseStudy | undefined { + return CASE_STUDIES.find((c) => c.slug === slug); +} + +export function getFeaturedCases(): CaseStudy[] { + return CASE_STUDIES.filter((c) => c.featured); +} + +export function getCasesByIndustry(industry: string): CaseStudy[] { + if (industry === 'all') return CASE_STUDIES; + return CASE_STUDIES.filter((c) => c.industry === industry); +} diff --git a/src/lib/constants/cross-references.ts b/src/lib/constants/cross-references.ts new file mode 100644 index 0000000..2288c2b --- /dev/null +++ b/src/lib/constants/cross-references.ts @@ -0,0 +1,80 @@ +import { PRODUCTS } from './products'; +import { SOLUTIONS } from './solutions'; +import { SERVICES } from './services'; + +export interface CrossReference { + id: string; + title: string; + type: 'product' | 'solution' | 'service'; + href: string; + reason: string; +} + +export function getProductCrossRefs(productId: string): CrossReference[] { + const product = PRODUCTS.find(p => p.id === productId); + if (!product) return []; + + const relatedSolutions = SOLUTIONS.filter(s => s.relatedProducts.includes(productId)); + const otherProducts = PRODUCTS.filter(p => + p.id !== productId && + SOLUTIONS.some(s => s.relatedProducts.includes(productId) && s.relatedProducts.includes(p.id)) + ); + + return [ + ...relatedSolutions.map(s => ({ + id: s.id, + title: s.title, + type: 'solution' as const, + href: `/solutions/${s.id}`, + reason: `${product.title} 是「${s.title}」的核心组件`, + })), + ...otherProducts.slice(0, 3).map(p => ({ + id: p.id, + title: p.title, + type: 'product' as const, + href: `/products/${p.id}`, + reason: `常与 ${product.title} 一起使用`, + })), + ]; +} + +export function getSolutionCrossRefs(solutionId: string): CrossReference[] { + const solution = SOLUTIONS.find(s => s.id === solutionId); + if (!solution) return []; + + const relatedProducts = PRODUCTS.filter(p => solution.relatedProducts.includes(p.id)); + + return relatedProducts.map(p => ({ + id: p.id, + title: p.title, + type: 'product' as const, + href: `/products/${p.id}`, + reason: `「${solution.title}」推荐使用 ${p.title}`, + })); +} + +export function getServiceCrossRefs(serviceId: string): CrossReference[] { + const service = SERVICES.find(s => s.id === serviceId); + if (!service) return []; + + const mapping: Record = { + software: { solutionIds: ['manufacturing', 'retail'], label: '软件开发服务支撑各行业方案落地' }, + data: { solutionIds: ['manufacturing', 'retail', 'healthcare'], label: '数据分析为方案提供决策支持' }, + consulting: { solutionIds: ['manufacturing', 'education'], label: '咨询规划方案实施路径' }, + solutions: { solutionIds: ['manufacturing', 'retail', 'education', 'healthcare'], label: '行业方案实施全流程覆盖' }, + }; + + const config = mapping[serviceId]; + if (!config) return []; + + return SOLUTIONS + .filter(s => config.solutionIds.includes(s.id)) + .slice(0, 3) + .map(s => ({ + id: s.id, + title: s.title, + type: 'solution' as const, + href: `/solutions/${s.id}`, + reason: config.label, + })); +} diff --git a/src/lib/constants/methodology.ts b/src/lib/constants/methodology.ts new file mode 100644 index 0000000..5aee19d --- /dev/null +++ b/src/lib/constants/methodology.ts @@ -0,0 +1,93 @@ +export interface MethodologyPhase { + id: string; + number: number; + title: string; + subtitle: string; + description: string; + activities: string[]; + deliverables: string[]; +} + +/** + * 数字化转型方法论 — 四阶段模型 + * 基于"评估→规划→实施→优化"的行业通用框架 + */ +export const METHODOLOGY: MethodologyPhase[] = [ + { + id: 'assess', + number: 1, + title: '诊断评估', + subtitle: '了解现状,找准痛点', + description: '深入调研企业业务现状、技术基础和组织能力,识别数字化转型中的关键痛点和机会点。', + activities: [ + '业务流程调研', + 'IT基础设施评估', + '数据资产盘点', + '组织能力诊断', + '行业对标分析', + ], + deliverables: [ + '数字化转型成熟度评估报告', + '关键痛点清单', + '机会优先级矩阵', + ], + }, + { + id: 'plan', + number: 2, + title: '战略规划', + subtitle: '明确方向,制定路线', + description: '基于评估结果,制定匹配企业发展战略的数字化转型路线图,明确技术选型和实施优先级。', + activities: [ + 'IT战略对齐', + '技术选型评估', + '实施路线图制定', + '投资回报分析', + '风险预案制定', + ], + deliverables: [ + '数字化转型战略规划书', + '技术架构蓝图', + '分阶段实施计划', + ], + }, + { + id: 'implement', + number: 3, + title: '落地实施', + subtitle: '敏捷交付,快速见效', + description: '采用敏捷方法论,分阶段推进实施,确保每个迭代都能交付可衡量的业务价值。', + activities: [ + '方案详细设计', + '敏捷开发迭代', + '系统集成测试', + '用户培训赋能', + '数据迁移验证', + ], + deliverables: [ + '系统功能模块', + '集成测试报告', + '用户操作手册', + '培训记录', + ], + }, + { + id: 'optimize', + number: 4, + title: '持续优化', + subtitle: '数据驱动,迭代提升', + description: '上线后持续跟踪系统运行效果,基于数据反馈进行优化迭代,确保数字化转型的长期成功。', + activities: [ + '运行效果监控', + '用户反馈收集', + '性能调优迭代', + '功能持续升级', + '季度复盘总结', + ], + deliverables: [ + '运行效果分析报告', + '优化建议清单', + '季度复盘报告', + ], + }, +]; diff --git a/src/lib/constants/team.ts b/src/lib/constants/team.ts new file mode 100644 index 0000000..dc8cc47 --- /dev/null +++ b/src/lib/constants/team.ts @@ -0,0 +1,65 @@ +export interface TeamMember { + id: string; + name: string; + title: string; + avatar?: string; + specialties: string[]; + bio: string; + yearsOfExperience?: number; + certifications?: string[]; + isCore?: boolean; +} + +export const TEAM_MEMBERS: TeamMember[] = [ + { + id: 'member-1', + name: '创始人', + title: '创始人兼CEO', + avatar: undefined, + specialties: ['企业战略', '数字化转型', '组织管理'], + bio: '核心团队长期从事技术咨询、企业数字化等领域,服务覆盖金融、制造、零售、政务、农业等多个行业。以"客户业务是否真正改善"为衡量标准,追求可量化的业务价值。', + yearsOfExperience: 15, + certifications: [], + isCore: true, + }, + { + id: 'member-2', + name: '技术负责人', + title: '联合创始人兼CTO', + avatar: undefined, + specialties: ['系统架构', '云原生', '微服务'], + bio: '开发团队成员来自多个大型传统IT企业,具备扎实的工程能力和规范化的交付经验。掌握从前端到后端、从云原生到数据智能的全栈技术能力。', + yearsOfExperience: 12, + certifications: [], + isCore: true, + }, + { + id: 'member-3', + name: '技术总监', + title: '技术总监', + avatar: undefined, + specialties: ['全栈开发', '数据工程', 'DevOps'], + bio: '团队成员既懂技术又懂业务,能够深入理解客户的真实场景和痛点。以务实态度交付每一个项目,确保技术方案真正落地见效。', + yearsOfExperience: 10, + certifications: [], + isCore: true, + }, + { + id: 'member-4', + name: '咨询总监', + title: '咨询总监', + avatar: undefined, + specialties: ['业务咨询', '流程优化', '项目管理'], + bio: '不追逐风口,只做真正为客户创造价值的事。交付只是开始,长期陪跑才是我们的承诺。用扎实的工程能力和行业经验赢得信任。', + yearsOfExperience: 10, + certifications: [], + isCore: true, + }, +]; + +export const TEAM_STATS = [ + { value: '12+', label: '年核心团队经验' }, + { value: '5+', label: '覆盖行业' }, + { value: '6', label: '自研产品' }, + { value: '10+', label: '团队成员' }, +]; diff --git a/src/lib/crypto.ts b/src/lib/crypto.ts new file mode 100644 index 0000000..3d05360 --- /dev/null +++ b/src/lib/crypto.ts @@ -0,0 +1,69 @@ +import crypto from 'crypto'; + +const ALGORITHM = 'aes-256-gcm'; +const IV_LENGTH = 16; +const AUTH_TAG_LENGTH = 16; +const SALT = 'novalon-cms-crypto-salt'; + +/** + * 从 JWT token 派生加密密钥 + */ +function deriveKey(token: string): Buffer { + return crypto.pbkdf2Sync(token, SALT, 10000, 32, 'sha256'); +} + +/** + * 加密数据 + */ +export function encrypt(data: unknown, token: string): string { + const key = deriveKey(token); + const iv = crypto.randomBytes(IV_LENGTH); + const cipher = crypto.createCipheriv(ALGORITHM, key, iv); + + const json = JSON.stringify(data); + const encrypted = Buffer.concat([ + cipher.update(json, 'utf8'), + cipher.final(), + ]); + const authTag = cipher.getAuthTag(); + + // 格式: iv + authTag + encrypted (全部 base64) + const combined = Buffer.concat([iv, authTag, encrypted]); + return combined.toString('base64'); +} + +/** + * 解密数据 + */ +export function decrypt(encryptedData: string, token: string): T { + const key = deriveKey(token); + const combined = Buffer.from(encryptedData, 'base64'); + + const iv = combined.subarray(0, IV_LENGTH); + const authTag = combined.subarray(IV_LENGTH, IV_LENGTH + AUTH_TAG_LENGTH); + const encrypted = combined.subarray(IV_LENGTH + AUTH_TAG_LENGTH); + + const decipher = crypto.createDecipheriv(ALGORITHM, key, iv); + decipher.setAuthTag(authTag); + + const decrypted = Buffer.concat([ + decipher.update(encrypted), + decipher.final(), + ]); + + return JSON.parse(decrypted.toString('utf8')) as T; +} + +/** + * 生成随机 Token(用于前端存储的加密密钥) + */ +export function generateCryptoToken(): string { + return crypto.randomBytes(32).toString('hex'); +} + +/** + * 对敏感字段进行哈希(用于日志脱敏) + */ +export function hashSensitive(data: string): string { + return crypto.createHash('sha256').update(data + SALT).digest('hex').slice(0, 16); +} \ No newline at end of file diff --git a/src/lib/db.ts b/src/lib/db.ts new file mode 100644 index 0000000..f479a84 --- /dev/null +++ b/src/lib/db.ts @@ -0,0 +1,11 @@ +import { PrismaClient } from '@/generated/prisma/client'; + +const globalForPrisma = globalThis as unknown as { + prisma: PrismaClient | undefined; +}; + +export const prisma = globalForPrisma.prisma ?? new PrismaClient(); + +if (process.env.NODE_ENV !== 'production') { + globalForPrisma.prisma = prisma; +} \ No newline at end of file diff --git a/src/lib/site-config.tsx b/src/lib/site-config.tsx new file mode 100644 index 0000000..8fd38be --- /dev/null +++ b/src/lib/site-config.tsx @@ -0,0 +1,82 @@ +'use client'; + +import { createContext, useContext, type ReactNode } from 'react'; + +export interface NavigationItem { + id: string; + label: string; + href: string; + hasDropdown?: boolean; + dropdownKey?: string; +} + +export interface MegaDropdownItem { + id: string; + title: string; + description: string; + href: string; + badge?: string; +} + +export interface MegaDropdownGroup { + id: string; + title: string; + description?: string; + items: MegaDropdownItem[]; + highlight?: boolean; +} + +export interface SiteConfig { + /** Company info (matches COMPANY_INFO field names) */ + name: string; + shortName: string; + displayName: string; + slogan: string; + description: string; + founded: string; + location: string; + email: string; + address: string; + icp: string; + police: string; + + /** Navigation */ + mainNav: NavigationItem[]; + megaDropdown: Record; +} + +const DEFAULT_CONFIG: SiteConfig = { + name: '', + shortName: '', + displayName: '', + slogan: '', + description: '', + founded: '', + location: '', + email: '', + address: '', + icp: '', + police: '', + mainNav: [], + megaDropdown: {}, +}; + +const SiteConfigContext = createContext(DEFAULT_CONFIG); + +export function useSiteConfig(): SiteConfig { + return useContext(SiteConfigContext); +} + +export function SiteConfigProvider({ + config, + children, +}: { + config: SiteConfig; + children: ReactNode; +}) { + return ( + + {children} + + ); +}