Unicode 引用
@@ -192,12 +198,12 @@@font-face {
font-family: 'iconfont';
- src: url('iconfont.eot?t=1765173841330'); /* IE9 */
- src: url('iconfont.eot?t=1765173841330#iefix') format('embedded-opentype'), /* IE6-IE8 */
- url('iconfont.woff2?t=1765173841330') format('woff2'),
- url('iconfont.woff?t=1765173841330') format('woff'),
- url('iconfont.ttf?t=1765173841330') format('truetype'),
- url('iconfont.svg?t=1765173841330#iconfont') format('svg');
+ src: url('iconfont.eot?t=1766052523135'); /* IE9 */
+ src: url('iconfont.eot?t=1766052523135#iefix') format('embedded-opentype'), /* IE6-IE8 */
+ url('iconfont.woff2?t=1766052523135') format('woff2'),
+ url('iconfont.woff?t=1766052523135') format('woff'),
+ url('iconfont.ttf?t=1766052523135') format('truetype'),
+ url('iconfont.svg?t=1766052523135#iconfont') format('svg');
}
第二步:定义使用 iconfont 的样式
@@ -223,6 +229,33 @@-
+
-
+
+ + Logo ++.icon-Logo ++
+
+
-
+
+ + 12 ++.icon-wodejiemianqianwang ++
+
+
-
+
+ + 群聊 ++.icon-qunliao ++
+
-
@@ -289,7 +322,7 @@- Frame 195 + 16-右.icon-a-Frame195@@ -325,7 +358,7 @@- Frame 194 + 16-左.icon-a-Frame194@@ -385,24 +418,6 @@
-
-
-
- - 折叠 --.icon-zhedie --
-
-
-
-
- - 展开 --.icon-zhankai --
-
font-class 引用
@@ -430,6 +445,30 @@-
+
-
+
+ Logo+#icon-Logo+
+
+
-
+
+ 12+#icon-wodejiemianqianwang+
+
+
-
+
+ 群聊+#icon-qunliao+
+
- @@ -522,7 +561,7 @@ -
-
-
- 折叠-#icon-zhedie-
-
-
-
-
- 展开-#icon-zhankai-
-
Symbol 引用
diff --git a/public/font-v2/iconfont.css b/public/font-v2/iconfont.css index 8302683..bc47704 100644 --- a/public/font-v2/iconfont.css +++ b/public/font-v2/iconfont.css @@ -1,11 +1,11 @@ @font-face { font-family: "iconfont"; /* Project id 5076160 */ - src: url('iconfont.eot?t=1765173841330'); /* IE9 */ - src: url('iconfont.eot?t=1765173841330#iefix') format('embedded-opentype'), /* IE6-IE8 */ - url('iconfont.woff2?t=1765173841330') format('woff2'), - url('iconfont.woff?t=1765173841330') format('woff'), - url('iconfont.ttf?t=1765173841330') format('truetype'), - url('iconfont.svg?t=1765173841330#iconfont') format('svg'); + src: url('iconfont.eot?t=1766052523135'); /* IE9 */ + src: url('iconfont.eot?t=1766052523135#iefix') format('embedded-opentype'), /* IE6-IE8 */ + url('iconfont.woff2?t=1766052523135') format('woff2'), + url('iconfont.woff?t=1766052523135') format('woff'), + url('iconfont.ttf?t=1766052523135') format('truetype'), + url('iconfont.svg?t=1766052523135#iconfont') format('svg'); } .iconfont { @@ -16,6 +16,18 @@ -moz-osx-font-smoothing: grayscale; } +.icon-Logo:before { + content: "\e624"; +} + +.icon-wodejiemianqianwang:before { + content: "\e622"; +} + +.icon-qunliao:before { + content: "\e621"; +} + .icon-guaduandianhua:before { content: "\e620"; } @@ -88,11 +100,3 @@ content: "\e60d"; } -.icon-zhedie:before { - content: "\e60e"; -} - -.icon-zhankai:before { - content: "\e60f"; -} - diff --git a/public/font-v2/iconfont.eot b/public/font-v2/iconfont.eot index 0dae13f70115ae86b1a05fc16ef20374cbedf6e6..7603066f525a4e504689c312158c6700788177a7 100644 GIT binary patch delta 2857 zcma)8Yit`?6~1?#o`;_^o{8f}#$(5J8pm$@7^jJAC$00?G>;~2nnGK$X*W$$nxxD2 zDS=!-LRA$Bio5I<&z2}N zV*9bDl`dTRcyUn?1fMOaxMqeI&h?JAd+Z~u82(1a$p$#pF#ErxheP0BmXEpe2_u(y z{7qpOHYS}B(I(06P-b}7nNtF7P_fWJc1-DOJUrlj$vR+3jv0Qq*d7+K&{0XSq EhRdNU3k>Z_>=B22GawMq{)z@Y^zSaM{DHh##TnhP+IWjrzoo+Kw;yAl2`Ct zRZ$wR$foHNL_1D2eyz~7$J4l> e*WUP$Inu1j&RSoq_jsw1+YuQ{0XEQpS zEl~A}>A-3T`c^KM{Q$>vlB%ncFXDq>p0FE-_fGbtx$I0&vW;`uXG$YGPmIaz+|(tz zi|Y=B(>;@WkL(-WHJ$f#p0rmchR=*B3NuyRJu)w;68t )`uw^Lh3HHs}@VO|%0I!&oK+d*Q%vlE#~Do@_|1hYwWA?vb97 zAtz{r?1x58&R?u_&lD2dIc+%BKbW4)4Yu(IBeYZTN0p7G`OT^I<70i!(fPSgjVyoP z@9$}kgaX}Kh 4)nx->X1F2>}2fFv3;LEcx0)4I8;76wf_2Y z {yUhV7pRwOQ=(D(vY?F9y^7zjJbsC^)X{(Opk(+(7C#~*4UQH4 zhV(J&Wq-^MzdyBZKb9DG$O+sTa?|pW+Fylz4E>3wc6QJI(xrx*!VQiH<46CG@XdST z9eV64b%*?;A*CQ-4!&4r946nbY#6?GR7wm=INSJ{6!c0D9#1&EUMF?yfkfRB#m2qH zJ<%H!-&RBF+hXwMFB~3xQ*d9RuvYt`vMq{r#7t}~Zz$(Bo>@Avy0o^sxc)5Q+~WE& U|LmFdldFpxwI3+ogFhPo09_jS8~^|S delta 1492 zcma)6O>7%Q6n- W#q z5lVc(h69I+OJ(Q@(FPC)DxnBpI0QjCpbBx}LXkj}1B{TUs)7_%0+jG}*J=X-346Xb zZ{EK5y>DhczFBBxgq8vDP48u)$ltvlx_Y^CsX2EKfV>6(XU^8EtKaVY@-YCdAR4vh zjpzQjF<%DYe+H1(mlmt%4&K^$9dmy`6qhg{e$QP&e;YBnR9{=4M+f=tpqXF3c(yvU zWxa{@&hv?Sb$yj2U;&>;ac!+s>x)|Ro#`zA?F=@otzLX#?bknFeFZ?jiAfp|0Is#y zf(0J)kp_dV^U#=X1fy3&*YBQIr*}a1a(`Or|H?`8=Djr@xR-J12ih%mm`pfHfp337 z>IB+{=@DYJ*QwQhm(I1fsK0%O_CDL_wp;_w^(JucK^+ZLGlvvBevyh Wa3 zL5E&2K!Xsdpg<5rY%XD~3;__phdg{|g$t1;=DHtzH7w`>i-9Q~kOr9Q0SSR=9*`PX z$OAS3=F}jt9WbW`fsKJVH3*ad%=Ca_fQ3CYQ5Ud?2UG|Q-;M)kpRCUVss`5Y0rdkL z@PIo3Hs}HO1Z>Cy?he?n2j?`{hzIBRndPB-vKfGB*d~KymAp;vQJX$P-{OEf$-T&Z z$4~GL{u=+K?||=l-*)Fm`6be%x9CwQ!7QG0ewdhsLK5**+O_r!iR2@tf+(0qxR9SA zhwM}$u3!?bN)aKR#fXzLL+ljBjWEsHPh|QZiA9IFU?vhz59UVF3U8Z4lC`irTQ1B` z&X&hx(pc@pSh=og1KO0C=nIBuC4D@bJeo0d-N+n&bd(H4HzZN+8O}zEQ@K<2;b|?S zCm+ksE|-7Ttz^WsM)Bx7mhu7N=|?o$eowR7zp1?^=(Qe2>Dm3%v83|QSA~$`qeOBe z_o+__2{{^73 )<%Q>3RwC#ag@rW#f^ zMYA(*>ZIvjP *L0|KB>sQk`ISW zE=YlZ^z{R?`@Z9nQ;zNW5mDgdT!4#nf^aaPH-kYv@J2un;JAWrK*x*W-dOmDsA71C z7{Vk;*`@S8QTv7L_vw~Tc4a;JK+WHDbTu7t1zNly9uou<72qgJ_!mu`sqiIA%=YKe bG5mWn`aTQ^jitqN=NHAsQg!7*^*q2|Y?lC9 diff --git a/public/font-v2/iconfont.js b/public/font-v2/iconfont.js index 0c7a81c..06f633d 100644 --- a/public/font-v2/iconfont.js +++ b/public/font-v2/iconfont.js @@ -1 +1 @@ -window._iconfont_svg_string_5076160=' ',(e=>{var a=(t=(t=document.getElementsByTagName("script"))[t.length-1]).getAttribute("data-injectcss"),t=t.getAttribute("data-disable-injectsvg");if(!t){var o,i,l,n,h,d=function(a,t){t.parentNode.insertBefore(a,t)};if(a&&!e.__iconfont__svg__cssinject__){e.__iconfont__svg__cssinject__=!0;try{document.write("")}catch(a){console&&console.log(a)}}o=function(){var a,t=document.createElement("div");t.innerHTML=e._iconfont_svg_string_5076160,(t=t.getElementsByTagName("svg")[0])&&(t.setAttribute("aria-hidden","true"),t.style.position="absolute",t.style.width=0,t.style.height=0,t.style.overflow="hidden",t=t,(a=document.body).firstChild?d(t,a.firstChild):a.appendChild(t))},document.addEventListener?~["complete","loaded","interactive"].indexOf(document.readyState)?setTimeout(o,0):(i=function(){document.removeEventListener("DOMContentLoaded",i,!1),o()},document.addEventListener("DOMContentLoaded",i,!1)):document.attachEvent&&(l=o,n=e.document,h=!1,c(),n.onreadystatechange=function(){"complete"==n.readyState&&(n.onreadystatechange=null,s())})}function s(){h||(h=!0,l())}function c(){try{n.documentElement.doScroll("left")}catch(a){return void setTimeout(c,50)}s()}})(window); \ No newline at end of file +window._iconfont_svg_string_5076160=' ',(l=>{var a=(t=(t=document.getElementsByTagName("script"))[t.length-1]).getAttribute("data-injectcss"),t=t.getAttribute("data-disable-injectsvg");if(!t){var o,e,c,i,n,h=function(a,t){t.parentNode.insertBefore(a,t)};if(a&&!l.__iconfont__svg__cssinject__){l.__iconfont__svg__cssinject__=!0;try{document.write("")}catch(a){console&&console.log(a)}}o=function(){var a,t=document.createElement("div");t.innerHTML=l._iconfont_svg_string_5076160,(t=t.getElementsByTagName("svg")[0])&&(t.setAttribute("aria-hidden","true"),t.style.position="absolute",t.style.width=0,t.style.height=0,t.style.overflow="hidden",t=t,(a=document.body).firstChild?h(t,a.firstChild):a.appendChild(t))},document.addEventListener?~["complete","loaded","interactive"].indexOf(document.readyState)?setTimeout(o,0):(e=function(){document.removeEventListener("DOMContentLoaded",e,!1),o()},document.addEventListener("DOMContentLoaded",e,!1)):document.attachEvent&&(c=o,i=l.document,n=!1,s(),i.onreadystatechange=function(){"complete"==i.readyState&&(i.onreadystatechange=null,d())})}function d(){n||(n=!0,c())}function s(){try{i.documentElement.doScroll("left")}catch(a){return void setTimeout(s,50)}d()}})(window); \ No newline at end of file diff --git a/public/font-v2/iconfont.json b/public/font-v2/iconfont.json index 0dfeea8..dd87d30 100644 --- a/public/font-v2/iconfont.json +++ b/public/font-v2/iconfont.json @@ -5,6 +5,27 @@ "css_prefix_text": "icon-", "description": "", "glyphs": [ + { + "icon_id": "46381406", + "name": "Logo", + "font_class": "Logo", + "unicode": "e624", + "unicode_decimal": 58916 + }, + { + "icon_id": "46339903", + "name": "12", + "font_class": "wodejiemianqianwang", + "unicode": "e622", + "unicode_decimal": 58914 + }, + { + "icon_id": "46337600", + "name": "群聊", + "font_class": "qunliao", + "unicode": "e621", + "unicode_decimal": 58913 + }, { "icon_id": "46281959", "name": "挂断电话", @@ -56,7 +77,7 @@ }, { "icon_id": "46252114", - "name": "Frame 195", + "name": "16-右", "font_class": "a-Frame195", "unicode": "e616", "unicode_decimal": 58902 @@ -84,7 +105,7 @@ }, { "icon_id": "46252113", - "name": "Frame 194", + "name": "16-左", "font_class": "a-Frame194", "unicode": "e61a", "unicode_decimal": 58906 @@ -130,20 +151,6 @@ "font_class": "sousuo", "unicode": "e60d", "unicode_decimal": 58893 - }, - { - "icon_id": "46211226", - "name": "折叠", - "font_class": "zhedie", - "unicode": "e60e", - "unicode_decimal": 58894 - }, - { - "icon_id": "46211225", - "name": "展开", - "font_class": "zhankai", - "unicode": "e60f", - "unicode_decimal": 58895 } ] } diff --git a/public/font-v2/iconfont.svg b/public/font-v2/iconfont.svg index 78b6051..974c1e9 100644 --- a/public/font-v2/iconfont.svg +++ b/public/font-v2/iconfont.svg @@ -14,11 +14,17 @@ /> + + + + + + - + @@ -26,11 +32,11 @@ - + - + @@ -44,16 +50,12 @@ - + - - - - diff --git a/public/font-v2/iconfont.ttf b/public/font-v2/iconfont.ttf index 3b9fc9211fce69a16f3c09a22349462531c2d5ac..8065346c16236c3daee04e26a886d624674aae1c 100644 GIT binary patch delta 2864 zcma)8YiwI*8UDU=^|{zS_PNDzd^>h*r*Z7Ym*X^X?WA=sOLJ+`rYW=~bGtNYX_Id2 zZ4$@@Bveh6D5S7WplMpg1f9fybYed$iiY?_K~zj=KTx+n9HtI|Km$QV9p2;9UHCD? zk)Q9p-}k=X^Ipz*zvH)4FXgWq&Bu-)gs&ik9zS-vapvaaGkXvs>VW50PCaw{?l*5w zB1DS_Ntaib8&8-&pPT^OcLC-qEJ&}3&w+dkkXSu^_F@AB;QJE{2T!dZYg97k^9WJD zhk5pN v` zc7r`a;$3^?zb>u40;~7#ybxaUR{o9zs%`Lx6JP1I1?RqXR=i7o38pSYwADZlcMX4s zK;8`lpT;LPuHqU_Z2SgCv3ujMc=j6vwJ}V{%`1<5eie~zDFb&NwqHRM`fQuA&1tzU z!%A#zAq0}-NNrCTe7aBuWEIIsL4M>z5- z0^=sszkR7Ty#t^_mgE44k>zs$E|BGS0M3wQ>j2;uS+))U0U*oX53rN5GzSnAvUCR! z9 |xO9prxUEy|>xrEbwYJwu IfH{=6Ka9+cj&M06~wj;5L?7Vb_&EgagkX2kv+F?a7 E4awWuMczvzQN5+Z%-k8Ko zL$ao^T?aTe(t4c^y1cAfDa8bTB}``oecdtK7W09jUsqkWVff)+c1OwHDJtPkM9EOs zV-lZ{W4Z0q^&N7LqC^>a_I@-~k$c^upGg{}&T4hFxzDWWO3@>-4|-WT%DdT)T?5gy z%HrN7{*aUzjAjQ~5>@kXEKV+QM0LKfFk)yfhUL_#OA6~ja;UfU#~qca0%yh|yQf$p zrnvJ7sWWl(3-xMhP^pgyynknBLyq((1`>=w(OjPvqocJB{^HVHb0!DZ%-qt na86>9yH z#h;PAK`(LNn@v#O8YR$s{8TvmI}g>x_`_Y%f#IUW^sMeTm=w>m*xfzQ9LbO#s&pW% zEPlGt$2NnLzV8o+yiX`!&j&(p3Eiz<&($JAzfZ`~>FviJmwRya^Oc6oaRFCImgYv6 zF7%Ig`rTtRANy|JOLs9!)ENAh9t-olC?1na&2+Swe)tNv3rDBDao#0}zHokY*SRws z86}e8p~8eb(0cEH??v-~DR}1C(Mo5G$6TbIrfb>C*pB_<`s5U+r~U3O64wslXT8O2 zphI!HHJ8iXdZ{Pljd%Dui1POA?EFvXXS3T48IwHrF!8k9A$3-o!%Oz+bvMiSTqIZg zZO$X~*JyIvg09G n!@ z4Y??{Ziu`qU>G7!+qU*0yR}8WN@6&}bu@wwp%wHrP%9p6WO2LG=}*Y1b~i(>h6xyS zje&D74u+{X11*`XLF;|k)X>h*{}IfVb*)gW;zC}Bvjw_dHau7f!`v(;3!jkjf}rS% z5QqmLm`Bo$!+WRua!g^aFVn$D?z!sN&XW@&y)bjxEit{}Sgvn+?~#4uyJk!N?o;mi z)abb}S*B)ok1Yy{AT$-h9|*i4D5BVw2;hAr`ahEX+>@%0&2 (%*~C}Q$4*2<9vPZE_Fg>qJu{VNnNX-HxjJ_qM|8cAQAZR{^)B%{)^a$ zx;H euP`zXvYSNyS^>%G-BN tIXAs?ZFejbGU}CqqeFeEUwTBpzc8IVbZp<(4jx(V91Yjbb+^(mEV1 zR#WlP&ctSv{Q;)PCF13cSGefL-(2z}*)Io!^1~PHkvRIX91@~Ip2iRCl@>$u!Km;# z;^km62)_@U+b;yl7xsee4f{y($i^(Uk0PIHa~Jk}8ZR;0CT=k{F@Es>5N{^<+vLPG z; g= z-%-NKJA7y}-XqhIE<0+QxnVwglD@HMPF7E?-!GV)^8W< 7%Q6n<}Zy&GrOyX&pjcJt@I*shb*!Oqq}V7F=1iYg#fiKqt@skKsLSB={U zB9vGN2aq@*4wVc&AxP)}2_zJe3x^=uo;Yxz7m5H;F07D1s7OSW06Dzbwc3C{!rt%A zH*e>C@0(qZH;P|nuO!>2?gHTJ0B~-xSzZ2S_oq()*bJdnUs`+Sj~nw90P#lvWwlYO zo ZIJ|~HM!?~^ z1hNE<<$=t#!3ooZKt92Vcp%5%&|Bi*`*sd`pk077;DOtA20hTuz!~yDdjx0L1ML=^ z5fA+7I57|WH8^pOA~oRef`7`egG0EC@8Vr%v!~hH0tlyt=Y?;@39%(^h@blo`JVOd zY?qZ6F~)AQ<1h`gR9+lGE2Lp6naR4*K8lgf*XmF^na3l1Qap-wCaGa4%<}dVxq*ig z(P1HwizKr{g;-V-Z43R15msg^Q}dIvmGOjstUfnZX&T0$VWkcRf@~&xGM_%43z=pp zck+=@9E`5{Wu<>4A1OUrIAcFtHgabAvHa{(<##ilj#%+gdc@nSfe%q+AG2tutH(QY z`hhvN(XVO!-RnH!%mc1VLCwd|?`G~XpB9t~EUJYxy4Jm+E-}B+N4(GQGpW#dPk%~e zH(l+i$Nx(g3$AeYp4-s>e}uPRFg_Jdq* |>aqSei;?Gd4@~S@NAb zvZc$LT}9D#1$Q@Pc2?#|-I6!;u)Zl<+pmO7Y`X`N-C*PN IHzm0QOq<;{X5v diff --git a/public/font-v2/iconfont.woff b/public/font-v2/iconfont.woff index 1961c5dd67768c4e4cd088bbbfc9a7250ce2da0e..ad56f9c8572ee0d3c7831cdd8905726144b0a191 100644 GIT binary patch delta 3862 zcmV+x59#oX8O$LRcTYw}00961000oo01E&B000>vkrY0ENnK-YVQ>Hd0f+zq0LK6T z0+_T)c}r((d1e3r0#E<|3%39O5Qx#0ze;FjVPpUR4hR4M051Rl05&$mN=RsEWnlmS z4mbb+0384T03;Bv43}taba(&&4p;yH01*HH09auF003-nV_^UQ4r~Ab04@Lk04_W% zHkfT;cyItW01li00018V001BXF9E1-VQpmq01mtW00BS%00L(aqdc&1Z*z1201x~C z007Vc00AUsu@MWC2mwI?$z+pD0VjW9ZusB8z`%Tmp$90;h@yxI0FLnu26&w9l3Q}Z zFbqYHAy6KHhDU&w7FveSuBZ#*tLxM@=#?+aK$+vnvMtT%3K#)H@go{2e-zSgik2E5 zGOO{1U+pLQ1`vz5#Pxo!UT3dbsW&oP&xS2)R&>fRr)9yCSEf22^P 6kbW5$0;96Q-{l5LxhI=e}ByUhl+lwAti7W$D2D1-t6RYj;0 z6d}|KwW9l8RtO;|ukQ)Hx2oQIdeSRM)za1bi}LP;ISiP3-&)P6&8YJt& zs|6Z;kk7h2!6q4|tHFOv5^T^$mQ4&0@#$_<5w+JQd>Iu$0oy{gJhf^FfiG;us}<;* zg(@HfmJuG*fX8{B d!n&@lqqzg^Gq{uOnck6?#)|E3}+cs(unSZy$b7@iG2TnH9MUzLR9r1!X zHk~dvdTOX05qN);-w|+Yy}B{)T4BNqW;(1oc7mNL^?o_kT&@aUX6V#1k28i+$*F_6 zOP}tv3e!esUJ_%ogCEp~CUcD(kwTm>Ze_T1dq8}0r@J}<&uVvPb+MS3S>wTkC?8@) zYoff?ZNqtmWUO9M)dU+&xKk0&tQrz;2_Z>f$&cq!TqJ*C=EuT{K&)`8_j?|l*Q1Fj zekWSag`(|{#N87MIptfCP?E%)WV$g|Q_0Acvo0x!B9D~e#{3u?3Dq|pW9$9MOy8~j zV?!1-$$z;LPyI|9?)|je9+D ENn(Gb-RbA;j3bJ&{D4|7rfbE6SA>%&y%NoeVM*4U%EHOV9}(Cz&O0;J(|V)# z|C`#2-liu-y8J?OFe4&is8i(H<>unl*(Lk#Wx+1Slq8GV7tk}&S~)&oD2f#hE4^=z zj775p+5q-Xl-Jh(b$zXT!qpKgq6o $Zc5@;kO}069bi+1u zR+19)`y*QTxiC)*JNjV3ws=uzMfd9X< $6Z%O zMJj*F5YTtXUS|;_F*Y;LTwpFUkAh|+g4{9+GMu}EE(B=|N$Ox2Y>NPlWr11963E4T z8&SD_HbWj$6ySZ^s@7VlTCo9AAlr2}f(!@Vy;{C{2aPr)!#1RNHV(ENQTzAbyD~mO zs@?Ii0ir71*5d3#r)6$q^~;J%N={~CeC2=M?K4X!*Xpt1k13tyg~u0lJ+yjiaZ55J z_{HM!Z%T$N2NY&*ld;>>Z$=88#qMAt(p;aI_a )UF;P3>YBhCmD8goR7KV5YEEE QOSs41vj4#5VBcw|R16{%f-4jUEeeQy1OwFP3uOej z=>=1Sx9L3GneUY`Pk`lnmVf7*+n;|!XI1qLbzJpR%PUDPCphubXV19VcYojrqoc9X zmElm*Fp@umm`0kZAS}hxnxtYyN_t;+QlVgyHOHGnqRJ}bSQq^Yl)a& EQW2|fIO`=bf;BPS95+LXsUm+a1^(mU z5U$|YnJH!gystPAeGvpDs}PL!ehB(io>rr3r3G=C&$={5X)UvZD5gSsg;p@iF)X~Y zwbvaPi {r787CM>~&~5GDoIm+&qFtLOX&C zt>#^iYk0GtjR*T$9BssovIXtkrY^^|gZ;h?-qhmE?fZ1`OH7ujGK>$JW)ST*AZrNd zw_r ER+JkXWZxnlb#Re9LrYRwg^!9;}fw+NS z8u+@>+q=EyMzPUAhlC3$nnrrpfslbX$~72vz&47RqcqyAz6o#=+#R&tqKTL3&(O=7 zK On7ETOLf22Eh@cfCbsXN$}?rrwksrh{2I6gA? zI#5}r0bZDy`mok720`qa07zPpHZ)fVWVC13k7BuBDuRD-_wYvV7MfFp-Yr2 $mc^yMI70jI=(Q9Ro$- zC`bA*hu?piy7btksi{lT+iNRjcvjZ_<(SwWU2CQxo+)qy!#>)l1--W!lyqF0rVAgt zG`&ZAgMX&&!); {EY(iILXyE1UP MnuAS=A?&9L^S9TY7&BWkSXV1OXnV*%EscVTsviB-ov48EJ3tO*Dx6nd$ z=dNNPy12U(^nNd}Uj+LqG&8^`Ko>BAr _;If#KP__3H zDP>AOKUj{MW)%PM=Mw&*DE8jyy&;+@@oj&@G2Rwa$M`>qzaQWcGfY2N3y6NY+8R~t zBl_*hvu?y0jrO>xQ;J4QP87Wt9d#nt=l~>49Q>HF(0f$usID9yu%Bm3%osC6qm_oJ zOHa|DA^JCCmQL_qg|lp{WO*-a*(xEzo}6X=2!OA-f&h4&V_;-p zU;yHKXJ*R9^V@u7;AUX}fv1_x+6el83C~65Mj)4ife9oE06E$U=m2<}V_;-pU~c%| zz!1aB00KN085kK*ffxW-DFa~u003inoMT~NVBiH}7Aj)^6C40y0000000002NCI#J z7z0!T)C4vJiUhm`3I$FDbOpEu8U|_xkOuY#MhBD!ya(tAL;wJIoMT{QU| ;p?pem47k=KFja1)Xf+YeD!3TKq>j@0o@ fXNAWFIcI{C#bgj Hd0f+zq0JH!A z0(#r^>%C`ed1e3r0z3c!2dMx63wZm%R6S^AVPpUR3FrU-051Rl05&bxd6Z~qWnlmS z3K#$Y02}}S03-+A1D0rQba(&&3N!!!01p5F08~@}003-nV_^UQ3Pb<^04x9i04yD_ zAe?PscyItW019vb0018V001BXBLG}(VQpmq01A)*00BS%00L(aqdc&1Z*z1201MCn z006oG009v3{Dy*)2mwI?VPKO>0VjW8ZusB8z`%Tmp$90;h@yxI0BBeYnRuM-lGzOc zF$_ds*yX-qxz7S9go5~@V7CF|9nl4ro+Dd!5_t@Cfl53?4dqQC?WJsK^A)2uU-N8h z>deRe^O2vXSFBD}tZ7)VWX_B!eFhBGJ7z*p?^LSlWN+#5w@JG4i<+FJ(Hwt?nbAav zxoK+_hCG*sJXeOST^sU_#*lY!jC3KkMhX!-Bdv(NNyXz}q#toK`Rh0tX-b?;Zc$uJ zZc|)M?nT^;wAa=c$`^q0F>rXCjaFN18)+4u{~ymkp3ICrW6vc{?1?>&?}^j+GUMDF zr*+b8n@ip1(snnkR2H?%Wm$i;Md6}d%uPrL9+0+utF*1e1|?bvgccRVLqG`eurH`I zJQki1JirrLu~i(-KX$SKZN-j8$7jxe{{KIh@0?=>G4Qbe#+R8C!=Oq!nXpL=X#rF~ zb~=-;<5@J!SvZJ1NfA}?@8U-1sTt2UvwV3);I+~V`2s4Va(Q`JV1<8+6)xAzqI*U> zZY%?Lrkuar93^C|D$Ly|8 n8SavKW2yF-brQ!?k3t0GD@?kj*KB>AwGc9 zV>|B4-vko`~=!m7^kZog(H5EV+*1o|M^L8Cq48_mxq_jV&%kxnaSIys&!NPJZ|^ zOUUO#h1)4#RMVn=)WUx(Qgs<&F%nlr1 )g->r ps z9g+re&A`RVVs7A6qCdvUIn&NW$A>ZjUqwfPs2Jkuneyt`>6t=8$lp4jpZUC^3@Umu zBCEKOnJ=d1a#|>)<>n`c(O~~q1YYd3il#F)zFw(KE4fhWZ1MExW Gpt*1Pr} z?z3ORe`TWJ0U&>;h^Q47D(hexSOyMJf2v?054>pvXfiP !K#Yd$6NM&{`-WX@(|6Q2Us>6;Tg%x;l7WjW9>KWNrMANit=Iap9yXI9Vf5 zF$B&x;7Vr(Rh*ImrA54p>&U`{hpH-K$Nh6HyC;XkvPV!G=s>uZ?X tVUmKHjsJt<}&W;e4{HlFlwL5-`0w0|Ou7enw 3B!j|GLfP6+@D36bOl`wIU&S^!-s#2l(72+L4N!=jNhf-uyDx8g3xa# zq{oj#PS2;`{C}UgBHUm?N24hq7VRX@^9hZ6PIW%;K7>!?R5$Nw{By=)>fIU!nByV> zoCh%PI?%F$v`ldr07jcBC|J0TU9We|LNW7%C+OMCtF180ii#?;>cZyB>l4BiS?3h_ zM 8V}&$fPn{7ldrT!4(^@Wm^iUq zY8@}HoS92!diY=yco(=NQv)ybL^Y*$2Z4uO-A>cm01VSwAyCndS?yuDJ0?81J9w?L zj~cSj*%xG47%r8DQJzvk{u02oaK7*)5DI? )r&?6EairMUA1*ayyQ>EJ zv RVFFU?bUL_B1;6XTBOFGb2sejcXV=+pz|5&KXa1*aWnkP@T$mmu z(=ZJ$GqihQq;|X6QKQK8YNXrd@RNy~cWzEh+??E8St`P_wDQX%W;4Gs9S1zq#^HaF z9kdgQL_!_tF(AN&NxJdQ&B^xI=+ebsTwEF*J7(I6>E(l6wB~7won47`oWDep5h-kH zVz>X^VfVl#ujg
nzRN;eGZyU|)$=1{ek11&rY7l%s>aLMOe2 zVvtE%EqbpDdl|sY%NuKzsyLEgX?@Bs%G?$qTbwNVm%ch1yz;&K-&_ ei`W#t#pqry z8Uo8~YEZXpBmf6E;n(@6PkBAaKj?^JFesvbJ>c;To(h9H{~%}tAMpB7?;poM^?DJL z0%+J7@D5O<(nDQ1AHc-g0V{tW>~KM=5DXTqAbJ+eTY+s#fCa(9ujmqbM%8*V3QwCxP^0jCE?%jCr!HBj`v S$u z6^P;0S6U0rOzxbVl-dFzifQ=U654GY?RZF6*DAdoA8lQu=l)QV+J1k-R0t8;Qj%eO zp7FoN1uq^+W{leIz&|@Jh)BAc>WxJjXZJ*>PMzRUL*!fzUJ-p(kR)OMm~?h1Me8Sc zlP5l#l~|i4WK;^ta!9%>g(P_I$@E8NKHetB8^ts}Zsvqdspr^)f5M5LRm;c5{FfOg z`M8hpO+r{|7JiON{{eq>1JV8f004NLV_;-pU;yIzf~()f^V@u7;AUX}fu}K27ZLRT z5*7yLMj)4ife9oE05CoZR{(gNV_;-pU~c%|z!1a200KagkpUHm0su!F0)BX$V_{%m zV4*Sw01m_eRR9100000y0K5Sl0dN7L0q_De0$Ku|0{{b119mS1+ypEHdIX>Z%mpX~ zas|A2oMT{QU| gfrhzz_cLNY5kLs-fPB)PF6|Koc#rp<&QL7ZG~sV}Kz> z7-NDdW|(7vr5fg*GEXiOa@LcnZ;Ry4<{>Umg(HghH@fm_z13;RPtvI@xid-fiIUx> zTeCQcO3N-kysUjW^p%z0*Ls?>%lH-_`AQd#FXDJyqc1RJSP;6+DU{s{ufs-{iY^OZ U`a)NhGhtKLDfI-t{6xb50NgzNO#lD@ diff --git a/public/font-v2/iconfont.woff2 b/public/font-v2/iconfont.woff2 index 3b3d4407149079baf331a4b0177a9ae12b8c4536..26cab29a8cbef273155848e56378ac0ca630ab17 100644 GIT binary patch literal 3688 zcmV-u4wvzFPew8T0RR9101jvX3jhEB02v?v01g`f0RR9100000000000000000000 z0000SR0d!Gg%}E<2%9hgHUcCASPL!y1Rw>3X9t2L8(0-HqjqAGqRIY?1Ww>;SXY*? zwaZ9p+0d8`7iHnfLq&b6Z h^v**X=^;%^?>=ASm&U}6Cx2Q8r 8~mm4C#IS
2imon{4JIT|6(K`XbN2#X01Z2crrgJXOZF)R={xDJq zgQVhrE#KK(x@ YFYY*T2w2lj%OlN=}kdb44%Qq zXa*;c5{dUf-Ow8*jwCTK&)vjyN};GqrPnYMfo2=DN!_m2T7ZIcj%X0TOGlm!oKxeR z-{%0bcNq#nd}s{RRC_E=ryQ0+x|Wu3hyM8ZYEx_?DKj#v9Vb+7;0d$*Kj;7YwZ wvUFC0Rn;UrS6sQthGQ{9Tp_f?7uC%1||J$iNP z(y3XCR&CmKXj=RSuckq%kR?IX06DG|;&l%C_>l7u4(5aO01H5RfrYS9EP_phJZy1L z0BHt_AT2-%q!m~Un?5W7X$O{qbO6ghnt U*Nv}; zXZ5Jlnv_1fI%HPV#9gZm!Z{^X5^^fLt(Km^42oGK@g_8lz<{JJqdO4J{1M!{b_que zdfRlngT%TUY{LN%)x9Z6E%e&8N9?}Y7?Sr!GkziJmJ(Cwa|3)PxuLd-(p6|`7P?D3 z;}$+*p}!e4Co+~NRq~O9K)~WgvCM?zY}qn{Slqnu2Y4U;fa1?A?NaJ`m2|lwX}nHV zKFF#z35{SlRC5t#itQJTpbEA*AJGkb4OBvjDF|E0T_oaHfcG j`4udt8C01TRa_62ZeVz& z=FE45Vze&>CY!r57poF_`pUJL64f*%-I+gg<>xGEIp`V92k50M{@lED_>hkd8_5-H zC-q{Mc&ekc!j%EVkHeQf-D4(2?OA!-jB43HQ)pV!&qRgnL Z2c zDAJ;>cqM71Rw2nEA3AAY(+JC97pQ`OO)s~0E{?pRp?~)3@v4g#f_lUpriP%F`(4S| zIBXGahwZ0=AQ~Nwv@=mW)^szR5WFyGR=xAZbj7x$Oxm~Wsg+Jh&U4)h%THnHlnZxW z)Z_fC;rFBxpsf<$DRqOP0h>lUs-Qwam4f79T;OyTm*lt*lCRVt4orKbce;a z&Kak_M-SfMSNDwjuOZ;U8j|GeGKRc;s5qu-CRc3nLKG#E-c9pGX{l_+c9eyAWHnX4 z=d-!kKTJGO)U~-DW%ae_BZNaxy47yR1*!624#_f|kOx%n>Z+L!UniVZGnM2Iq~~R% zpJgE(NcGl}&hcu(@lt$Fz~IcJYm3_PR=3nBC05t2DXMnTvfZT6HeA0>Q=TK~$IAq* ze ODl8@i}xLN70MKv#9&Cbt>nw~}9qej(@ zFofW`*2Bc`yL2oy!jSl5ykjlOI1LR+i{|xfbpbYn=Isr%C>Ct37 za{rGZ(x-S?8F%QSXINeb{VoDeloNG+%w_wzK4-G&K~YlhF7#_Jq-d+t6~3{@m(y?G zT=Tk#r&NqsLUK}1#O}b9{=H$D-(Fij0gB4ev5E`A>u6UK{Xml;vkvZ3O7JTy0q7NF zIl1U~8(ZQ~t%o5?tJ*8 St=S_hwiC9eG#PlYjNo(TV-;I7YUL32P~Os*P?|*UO2>h|(aCmyk7gl{^dA zX+=s$RH(+0n#+{B5d~3IkIv_cC(9 ^>_P<68U6 0v WxHv%3)NSB*-GA{IElYr;!TO5fsvCZe>JD*p54pL*e&7J zvHMNOEgx4KMVNf~6Btg{(zP#hZq>hf_?n>_&_3C{Nd`xCt@MCF(L(*rW>XR=A}K6^ z*bbveAY1S4A-%-P!+wsd;$gLv{-D#-z0agO*fS7td)wC(DAhIH*7a?w5hUon_J9{Z zS@r6%HcD;vgr)UOU{fUtKcSw$tMKFXI%uG|c_0LvVAvWsq^lptZ}{GLTpEg0cOgrQ zODh6>wis`_kG9vgfp!aSd6g`z?g~eaNrCt#?CxT;b}P{0-4?GmWkFOXT4)^j^zqHq zQR)- 4N#oUgkbf4X+5ekT6s z!Ube`Aj`;wu|Ki{Ho$ZR+l);niOs||9&M}`-LiS{4f4>$=4AG){3R`4Y7I0RP!J?s z`FqVGSfEndNUm&aBV^p%o&O}#@%NKc_y!SU&|vU06n;s7V?ZIby}6KKY 6j(VV_zA$m`opl46_s^zFk1X6ARvE&^tQlj2DN+W8aKA3S24S8E?@= zki!8*VI!kf!$Q&K0fen(va~Gy!_J)D^AYwVfng17rXz$zoi1rna~H&A;)PsmUdy@j zpT$S#*NBbhitjI95CK2O`b)a;=F0W!E6wVvJYP&O^G=Je-vLNhaQ+#_HD*VTnf zfR}OcnHi`vuP+CcOv{8-2)FhsQ^8~H?JGdOCZ *WKi$7gEhdHl?suTk#yAAfSG5Uzy_uMNkm0eF+MpU^*E{`vRvFDoCf{IdPU zp8Ppze4-vVQk9g+U# 1h0z+!uXN= z3Gn?^qlyYsC@@vket5Ayv1!vTE1LAEtGA0pJ+P^M3-upcU(f60Z_ 7_`k zZ(56{ZJ}Ot?Gw7)3u!yB3Hf1q+}W7Uqw03vmKlF9TJmZsRckMld~3}8$??K_|2D>o z>d|T(+yeM}kg6>HOk~*F-1lpJdnu&9_#FL{6c3?gRe#JVEK6uMim3zSNViXl672aR ztqP5LoHa> ud7(Mur@buu{|$ z8TPA~z-2f3t2LgWCz3=b!!aK(8ub#qkx+o3-i*#OG!!C)BcW9uSCl;%jfO=Wj+fO5 z+Z74`um}%_=xaka0;e^*`niMzdG8$c{(L4LJ|l)WL=-L5i5On-W&H`b*cufqjS4DV zu^{HK7$2VK((xiZWMo+tK%m|+$)RmU2tk-#wu*=A?6D2TC<_ze7 3X9t218v+p#Rji>T`vTjBJ&83Sfr1M# zL?J4eW Okwj>8NO+rb(D-Wv 2Rzz$GmA{1kZWU5-BJ2fss z)u;+zX=yQB&Qf#dVntpIX-pi2duwldz@JRbyHQZNbb zf}3E=UTJ`w0T!sE{4wCP@$2O)y%GRWSQ^k*TE4@u2weRj`K?+&cnQ$2IZ<989IXHg z&_H|3(qw;f_D%$rXg3T$D%*$oiZn3!Q0?`C50S>({p}!^Fv1BYgdk!f99%qn0zzyo zGzNuS{_$);7l$wvHTi%KkqAL9;6ogOJiv#9CqoK}0XdKWav_m}JV^3GJ|yKr0T2gJ z2*d>x0r3FEKzu+6kN{8$Bm|TJu>s{kEI JgHVSSjt*kuMH*cv= zp-$I;5E8Fgy!ONx=E9=xV=#@4QmGD_R2CEzmx{$<`QjC-qI{wX!V`i6<5G=GEKW(p z^e$^jhe&K1228l!B9V!U>G2#6nL737&9_)KDxxp={2Z^DCZrfc!HrH2tCC(~lMlPy zTy{3M_9*AcQMlMVshxEzG5d)K`jE{GcXl{zakRO^0uS9Y+S!RGvktG5m+!}!zQ;Ls zBgL{{!aX7~Ipl5UMW)N-sYmft ZJ1xcn8ws!6;`Z z=`anq5%!Jb^@pahtHiYj5J;d$S{~~_C69V^19fzaX**<-o#eP8lGEunVF%o=d(9-B zP!MHpC)y5)0!@x)$_|WjzRA|X^Cg`|0r?DqC_6FS>HUX+ny^C>%E{xAP8r3-<8eCV z=~~N>m1%DaUsPV>PIx255OnBvGiib5J1sHG5W|E^0xu9l(>7VdQYWRt?1U1fPoH)4 z^uk8mL1xwGn{6~L6`Sb6&?Gd&48e^{yJVI@i)mn%9pyMTEsQc+tj&< ~ z0oge;(y2g=Zwf9MS0T1(XyJS}&gG(Ka}OBn(j|Oi*yP~x&{F8Rc;+C-EdyFhHRbW} zmKJZp-U9^$O`B!7nS_lN3&Q}qYj9%{52lkh$Yi61l42-rl8G8uA(6+eOdU$rWbUhy z`q0%XxY^RMtnjsDPo$}ENnmJQV?K!%OBVt?vo;*wOj@MX3=v*Mn|TXiDH>~>)3FO3 zeXWH7Owh8LW{t&V!?UpAt&1|REE*QiQKM2Ih(hVFSz5fSHLM{6DNSaq>QuE>SzQ=A zXIK30T14xsnx#-6D$w}J+x3o)s^6+ LCUDMGC`?NjgKsRRTFWe=GrtI(_*+F_@U{mA#M1k0DtHp6ukZjoLQ8e zr)f-2#k9JjoV=u<1D`gR$EiwEi*uJQ%ME%R-0zJqiQV%k&IcSRiIESK49E@g{@7~W z?99yBD>=~ fMaLqfe;+ZxhfmXf$)$6Xp`6LRJJVp)c)Yz146k`c{PeDjge?+5h0=jG@2&u z7w|uJEEwx9#4yw}cXbB6u;| ZvK4TI!<)4&e}UGo0wgb zT9uqql~Q|w5JGI?p7`SJQL~A}nkd(lYA=dGr*dRDryxu`X?xp$aD*O+JrL@Ea_++T zk-)#ZXNDW)jgi~7MK;Qf%5!;0R-rW)6U`J?=xjDe*hPgatg TtBqMuc8XtJUgEX>@sdYsPFP4>nr!wK!Z7O&LGsHnXblh^>hu0 z+OR>|^c_8;NjhH8)Ytbs7%v l(T8(R`J>jgH%xT&uU;#6YmF6uptysN5 zi6kT-N<-Nvqp_}?JNIL_cQLlf+|v~c3JU$L$oyh)u{huAw 5(E){~t$jI>b3XHz@a80Q8%(?EO0V6H=6|^8w|T;-bFI z4E`-(o#ce(RRRSj>hBS}hMWTGT)Z8^XJwE9nxD&eBsUM>D8ao%!B^o>=Bsh3H8<9+ zz7_$!zBVqyzCLXj8V2OC3IXhmFt?H2@D)<*zKWRpLPuHO5eC*CUyDM17dEWl`}(XE z$=w1PiN#wWOeCS$ffbGwPe{gYze(pBLs-Q>g=kR*!`{GO_eD&hh;!mvk5W>gf@|RO z^lDVs4%*;K5~*iWYFGWfiaMt!T+?|Agh>l5L9q*Mg<~~w^8S9`q;riy()?eAs4%j7 zFw`@UF#j@GYQUE0aji!ZQ3`|zq_zQ{7XneAs~u>gD@zj3>>iV&vR&!3iIl9-t3STk z0P6t1L%)8(L(yWy5?dT`#S>ow2_=$P5=lRN{Pg+DSGPK& Mbb&7J>MJT3hgkwbhw;e1Wmic%V>zTP {d2g)5t4$s?v82#d u)L>2B+A`hyW6yXNYp3<%Sy@qGa{07 { + if (response?.sm) { + router.push('/home'); + } + }, [response?.sm]); + return ( diff --git a/src/app/(main)/chat/[id]/Drawer/Profile.tsx b/src/app/(main)/chat/[id]/Drawer/Profile.tsx index 7585b2f..38e7475 100644 --- a/src/app/(main)/chat/[id]/Drawer/Profile.tsx +++ b/src/app/(main)/chat/[id]/Drawer/Profile.tsx @@ -171,7 +171,7 @@ export default function Profile({ onActiveTab }: ProfileProps) { )} onClick={item.onClick} > - {item.label}+{item.label}{item.value && (@@ -193,7 +193,7 @@ export default function Profile({ onActiveTab }: ProfileProps) { return (); } diff --git a/src/layout/Topbar.tsx b/src/layout/Topbar.tsx index 9906beb..e5c45c8 100644 --- a/src/layout/Topbar.tsx +++ b/src/layout/Topbar.tsx @@ -22,7 +22,7 @@ function Topbar() { const response = useMedia(); const searchParamsString = searchParams.toString(); const redirectURL = `${pathname}${searchParamsString ? `?${searchParamsString}` : ''}`; - const loginHref = `/login?redirect=${encodeURIComponent(redirectURL)}`; + // const loginHref = `/login?redirect=${encodeURIComponent(redirectURL)}`; useEffect(() => { function handleScroll(event: Event) { @@ -42,7 +42,7 @@ function Topbar() { useEffect(() => { if (!user) { - router.prefetch(loginHref); + router.prefetch('/login'); } else { router.prefetch('/profile'); if (user.cpUserInfo) { @@ -55,11 +55,9 @@ function Topbar() { if (!response) return null; if (response.sm || items.some((item) => item.path === pathname)) { return ( ---+{/* Tags */} diff --git a/src/app/(main)/chat/[id]/Drawer/index.tsx b/src/app/(main)/chat/[id]/Drawer/index.tsx index e5e21bd..5a7888f 100644 --- a/src/app/(main)/chat/[id]/Drawer/index.tsx +++ b/src/app/(main)/chat/[id]/Drawer/index.tsx @@ -57,7 +57,7 @@ export default function SettingDialog({ open, onOpenChange }: SettingProps) { return ( - + {activeTab === 'profile' ? ( diff --git a/src/app/(main)/chat/[id]/page.tsx b/src/app/(main)/chat/[id]/page.tsx index 624ca39..670063b 100644 --- a/src/app/(main)/chat/[id]/page.tsx +++ b/src/app/(main)/chat/[id]/page.tsx @@ -29,11 +29,11 @@ export default function ChatPage() { setSettingOpen(!settingOpen)} - className="absolute top-1 right-1" + className="absolute top-2 right-2" variant="ghost" size="small" > - + diff --git a/src/app/(main)/crushcoin/components/CheckInGrid.tsx b/src/app/(main)/crushcoin/components/CheckInGrid.tsx index 4489007..8e14b92 100644 --- a/src/app/(main)/crushcoin/components/CheckInGrid.tsx +++ b/src/app/(main)/crushcoin/components/CheckInGrid.tsx @@ -1,7 +1,7 @@ 'use client'; -// import { useGetSevenDaysSignList, useSignIn } from '@/hooks/useHome' -// import { SignInListOutput } from '@/services/home/types' +import { useGetSevenDaysSignList, useSignIn } from '@/hooks/useHome'; +import { SignInListOutput } from '@/services/home/types'; import { useQueryClient } from '@tanstack/react-query'; import { homeKeys } from '@/lib/query-keys'; import { CheckInCard } from './CheckInCard'; @@ -10,8 +10,8 @@ import { toast } from 'sonner'; export function CheckInGrid() { const queryClient = useQueryClient(); - // const { data: signListData, isLoading } = useGetSevenDaysSignList() - // const signInMutation = useSignIn() + const { data: signListData, isLoading } = useGetSevenDaysSignList(); + const signInMutation = useSignIn(); const hasSignRef = useRef(false); useEffect(() => { diff --git a/src/app/(main)/home/components/Character/index.tsx b/src/app/(main)/home/components/Character/index.tsx index 32cc9bd..847b2ae 100644 --- a/src/app/(main)/home/components/Character/index.tsx +++ b/src/app/(main)/home/components/Character/index.tsx @@ -28,7 +28,7 @@ const Character = () => { return Math.floor(width / cardWidth); }} renderItem={(character) => } - getItemKey={(character) => character.id} + getItemKey={(character, index) => character.id + index} hasNextPage={!noMoreData} isLoading={isFirstLoading || isLoadingMore} fetchNextPage={onLoadMore} diff --git a/src/app/(main)/home/components/Header.tsx b/src/app/(main)/home/components/Header.tsx index c08e42e..6e28607 100644 --- a/src/app/(main)/home/components/Header.tsx +++ b/src/app/(main)/home/components/Header.tsx @@ -10,58 +10,53 @@ const Header = React.memo(() => { const response = useMedia(); return ( - - -- ) -} + ); +}; -export default VipPage +export default VipPage; diff --git a/src/app/api/auth/discord/callback/route.ts b/src/app/api/auth/discord/callback/route.ts index fae3af6..a8e25b0 100644 --- a/src/app/api/auth/discord/callback/route.ts +++ b/src/app/api/auth/discord/callback/route.ts @@ -1,8 +1,8 @@ import { NextRequest, NextResponse } from 'next/server'; export async function GET(request: NextRequest) { - console.log('request', request); - const url = 'http://localhost:3000'; + // console.log('request', request); + const url = request.nextUrl.origin; try { const { searchParams } = new URL(request.url); const code = searchParams.get('code'); diff --git a/src/css/iconfont-v2.css b/src/css/iconfont-v2.css index 8f0d8a4..f28c517 100644 --- a/src/css/iconfont-v2.css +++ b/src/css/iconfont-v2.css @@ -1,9 +1,9 @@ @font-face { font-family: 'iconfont-v2'; /* Project id 5076160 - spicyxx.ai */ src: - url('/font-v2/iconfont.woff2?t=1765173841330') format('woff2'), - url('/font-v2/iconfont.woff?t=1765173841330') format('woff'), - url('/font-v2/iconfont.ttf?t=1765173841330') format('truetype'); + url('/font-v2/iconfont.woff2?t=1766052523135') format('woff2'), + url('/font-v2/iconfont.woff?t=1766052523135') format('woff'), + url('/font-v2/iconfont.ttf?t=1766052523135') format('truetype'); } .iconfont-v2 { @@ -14,6 +14,21 @@ -moz-osx-font-smoothing: grayscale; } +/* Logo */ +.iconv2-Logo:before { + content: '\e624'; +} + +/* 我的界面前往 */ +.iconv2-wodejiemianqianwang:before { + content: '\e622'; +} + +/* 群聊 */ +.iconv2-qunliao:before { + content: '\e621'; +} + /* 挂断电话 */ .iconv2-guaduandianhua:before { content: '\e620'; diff --git a/src/hooks/useGlobalPrefetchRoutes.ts b/src/hooks/useGlobalPrefetchRoutes.ts new file mode 100644 index 0000000..e2defdd --- /dev/null +++ b/src/hooks/useGlobalPrefetchRoutes.ts @@ -0,0 +1,109 @@ +'use client' + +import { useEffect, useMemo } from 'react' +import { useRouter } from 'next/navigation' +import { useCurrentUser } from '@/hooks/auth' +import { useToken } from '@/hooks/auth' + +const PUBLIC_PREFETCH_TARGETS = ['/'] + +const AUTH_PREFETCH_TARGETS = [ + '/contact', + '/profile', + '/profile/edit', + '/profile/account', + '/vip', + '/wallet', + '/wallet/charge', + '/wallet/charge/result', + '/wallet/transactions', + '/leaderboard', + '/crushcoin', + '/generate/image', + '/generate/image-2-image', + '/generate/image-edit', + '/generate/image-2-background', + '/explore', + '/creator', + '/create/type', + '/create/dialogue', + '/create/character', + '/create/image', +] + +// 受保护路由前缀列表(需要登录才能访问的路由) +const PROTECTED_ROUTE_PREFIXES = [ + '/profile', + '/create', + '/settings', + '/login/fields', + '/chat', + '/contact', + '/vip', + '/wallet', + '/crushcoin', + '/leaderboard', + '/generate', + '/explore', + '/creator', +] + +// 检查路由是否是受保护路由 +const isProtectedRoute = (href: string): boolean => { + return PROTECTED_ROUTE_PREFIXES.some((prefix) => href.startsWith(prefix)) +} + +const DEFAULT_PREFETCH_TARGETS = [...PUBLIC_PREFETCH_TARGETS, ...AUTH_PREFETCH_TARGETS] + +type NullableRoute = string | null | undefined + +const prefetchedRouteCache = new Set-- ) -} + ); +}; -export default SubscribeVipDrawer +export default SubscribeVipDrawer; diff --git a/src/app/(main)/vip/vip-page.tsx b/src/app/(main)/vip/vip-page.tsx index 1242431..f151d3e 100644 --- a/src/app/(main)/vip/vip-page.tsx +++ b/src/app/(main)/vip/vip-page.tsx @@ -1,43 +1,41 @@ -'use client' -import { Button, IconButton } from '@/components/ui/button' -import { useRouter, useSearchParams } from 'next/navigation' -import { useGetMemberDetail } from '@/hooks/useWallet' -import SubscribeText from './components/SubscribeText' -import { useSetAtom } from 'jotai' -import { isVipDrawerOpenAtom } from '@/atoms/im' -import { VipType } from '@/services/wallet' -import { useEffect, useState } from 'react' +'use client'; +import { Button, IconButton } from '@/components/ui/button'; +import { useRouter, useSearchParams } from 'next/navigation'; +import { useGetMemberDetail } from '@/hooks/useWallet'; +import SubscribeText from './components/SubscribeText'; +import { VipType } from '@/services/wallet'; +import { useEffect, useState } from 'react'; const VipPage = () => { - const router = useRouter() - const searchParams = useSearchParams() - const back = searchParams.get('back') - const setIsVipDrawerOpen = useSetAtom(isVipDrawerOpenAtom) - const [enableQuery, setEnableQuery] = useState(!back) + const router = useRouter(); + const searchParams = useSearchParams(); + const back = searchParams.get('back'); + const setIsVipDrawerOpen = (P: any) => null; + const [enableQuery, setEnableQuery] = useState(!back); useEffect(() => { if (back) { // 如果有 back 参数,等待 2 秒后再启用查询 const timer = setTimeout(() => { - setEnableQuery(true) - }, 2000) - return () => clearTimeout(timer) + setEnableQuery(true); + }, 2000); + return () => clearTimeout(timer); } - }, [back]) + }, [back]); - const { data: memberDetail, isLoading } = useGetMemberDetail({ enabled: enableQuery }) - const { memberPrivList, userMemberInfo } = memberDetail || {} + const { data: memberDetail, isLoading } = useGetMemberDetail({ enabled: enableQuery }); + const { memberPrivList, userMemberInfo } = memberDetail || {}; const hasSubscribe = - userMemberInfo && userMemberInfo.expTime && new Date(userMemberInfo.expTime) > new Date() + userMemberInfo && userMemberInfo.expTime && new Date(userMemberInfo.expTime) > new Date(); const handleBack = () => { if (back) { - window.history.go(-3) - return + window.history.go(-3); + return; } - router.back() - } + router.back(); + }; return (-
-- ) + ); } // 数据转换函数 const convertProductListToPricingPlans = (productList: SubProductListOutput[]) => { - if (!productList) return [] + if (!productList) return []; return productList.map((product, index) => { // 根据订阅时长获取标题 const getTitleByPeriod = (period?: Period) => { switch (period) { case Period.SubMonth: - return '1 Month' + return '1 Month'; case Period.SubSeason: - return '3 Months' + return '3 Months'; case Period.SubYear: - return '12 Months' + return '12 Months'; default: - return 'Unknown' + return 'Unknown'; } - } + }; // 计算月单价(如果不是月订阅) const getSubtitle = (period?: Period, payAmount?: number) => { - if (!payAmount) return '' + if (!payAmount) return ''; - const monthlyPrice = payAmount / 100 // 转换为元 + const monthlyPrice = payAmount / 100; // 转换为元 switch (period) { case Period.SubSeason: - return `$${(monthlyPrice / 3).toFixed(2)}/Month` + return `$${(monthlyPrice / 3).toFixed(2)}/Month`; case Period.SubYear: - return `$${(monthlyPrice / 12).toFixed(2)}/Month` + return `$${(monthlyPrice / 12).toFixed(2)}/Month`; default: - return '' + return ''; } - } + }; return { id: index + 1, // 使用索引+1作为ID @@ -70,69 +69,73 @@ const convertProductListToPricingPlans = (productList: SubProductListOutput[]) = discount: product.discount || '', isSelected: false, // 初始都不选中 productId: product.productId, // 保留原始产品ID用于后续操作 - } - }) -} + }; + }); +}; const SubscribeVipDrawer = () => { - const [selectedPlan, setSelectedPlan] = useState- Check-in{' '} --- - - Daily Free crush coinsh - -+ // +- ++ // ); }); diff --git a/src/app/(main)/home/components/Story/index.tsx b/src/app/(main)/home/components/Story/index.tsx index 91286bc..b4cb654 100644 --- a/src/app/(main)/home/components/Story/index.tsx +++ b/src/app/(main)/home/components/Story/index.tsx @@ -1,7 +1,7 @@ 'use client'; const Story = () => { - return+- + {response?.lg && ( ++
+- {response?.lg && ( -+ Check-in{' '} +++ + + Daily Free crush coinsh + +- )} + )} + Story; + return开发中ing; }; export default Story; diff --git a/src/app/(main)/leaderboard/components/LargeRankCard.tsx b/src/app/(main)/leaderboard/components/LargeRankCard.tsx deleted file mode 100644 index e7118da..0000000 --- a/src/app/(main)/leaderboard/components/LargeRankCard.tsx +++ /dev/null @@ -1,75 +0,0 @@ -'use client' - -import { formatNumberToKMB } from '@/lib/utils' -import Image from 'next/image' -import { AiChatRankOutput, AiHeartbeatRankOutput, AiGiftRankOutput } from '@/services/home/types' -import { RankType } from '@/types/global' -import Link from 'next/link' - -interface LargeRankCardProps { - item: AiChatRankOutput | AiHeartbeatRankOutput | AiGiftRankOutput - rankType: RankType -} - -export default function LargeRankCard({ item, rankType }: LargeRankCardProps) { - // 根据排行榜类型获取对应的数值显示 - const getDisplayValue = () => { - switch (rankType) { - case RankType.CHAT: - return formatNumberToKMB((item as AiChatRankOutput).chatNum || 0) - case RankType.CRUSH: - return `${(item as AiHeartbeatRankOutput).heartbeatValTotal || 0}℃` - case RankType.GIFTS: - return formatNumberToKMB(Math.floor((item as AiGiftRankOutput).giftCoinNum || 0) / 100) - default: - return '0' - } - } - - // 根据排行榜类型获取对应的图标 - const getIcon = () => { - switch (rankType) { - case RankType.CHAT: - return - case RankType.CRUSH: - return- case RankType.GIFTS: - return - default: - return - } - } - - const imageUrl = item.homeImageUrl || '' - - if (!item) { - return - } - - return ( - - - --- -- - ) -} diff --git a/src/app/(main)/leaderboard/components/RankingList.tsx b/src/app/(main)/leaderboard/components/RankingList.tsx deleted file mode 100644 index 8c0c004..0000000 --- a/src/app/(main)/leaderboard/components/RankingList.tsx +++ /dev/null @@ -1,132 +0,0 @@ -'use client' - -import Image from 'next/image' -import { useMemo } from 'react' -import { AiChatRankOutput, AiHeartbeatRankOutput, AiGiftRankOutput } from '@/services/home/types' -import { RankType } from '@/types/global' -import { formatNumberToKMB } from '@/lib/utils' -import Link from 'next/link' -import { usePrefetchRoutes } from '@/hooks/useGlobalPrefetchRoutes' - -interface RankingListProps { - rankData: (AiChatRankOutput | AiHeartbeatRankOutput | AiGiftRankOutput)[] - rankType: RankType - startFromRank?: number // 从第几名开始显示,默认为4 -} - -const RankingList: React.FC- {getIcon()} --{getDisplayValue()}-1st-= ({ rankData, rankType, startFromRank = 4 }) => { - const filteredData = useMemo( - () => rankData.filter((item) => item.rankNo && item.rankNo >= startFromRank), - [rankData, startFromRank] - ) - const chatRoutes = useMemo( - () => filteredData.map((item) => (item?.aiId ? `/chat/${item.aiId}` : null)), - [filteredData] - ) - usePrefetchRoutes(chatRoutes, { limit: 20 }) - - // 根据排行榜类型获取格式化后的显示值 - const getDisplayValue = (item: AiChatRankOutput | AiHeartbeatRankOutput | AiGiftRankOutput) => { - switch (rankType) { - case RankType.CHAT: - return formatNumberToKMB((item as AiChatRankOutput).chatNum || 0) - case RankType.CRUSH: - return `${(item as AiHeartbeatRankOutput).heartbeatValTotal || 0}℃` - case RankType.GIFTS: - return formatNumberToKMB(Math.floor(((item as AiGiftRankOutput).giftCoinNum || 0) / 100)) - default: - return '0' - } - } - - // 根据排行榜类型获取对应的图标组件 - const getRankIcon = () => { - switch (rankType) { - case RankType.CHAT: - return - case RankType.CRUSH: - return - case RankType.GIFTS: - return - default: - return - } - } - - const getLikedCount = (item: AiChatRankOutput | AiHeartbeatRankOutput | AiGiftRankOutput) => { - if ('likedNum' in item && typeof item.likedNum === 'number') { - return item.likedNum - } - return 0 - } - - if (filteredData.length === 0) { - return null - } - - return ( - - {filteredData.map((item) => { - const displayValue = getDisplayValue(item) - - return ( - -- ) -} - -export default RankingList diff --git a/src/app/(main)/leaderboard/components/SmallRankCard.tsx b/src/app/(main)/leaderboard/components/SmallRankCard.tsx deleted file mode 100644 index 63ee3ce..0000000 --- a/src/app/(main)/leaderboard/components/SmallRankCard.tsx +++ /dev/null @@ -1,83 +0,0 @@ -'use client' - -import { formatNumberToKMB } from '@/lib/utils' -import Image from 'next/image' -import { AiChatRankOutput, AiHeartbeatRankOutput, AiGiftRankOutput } from '@/services/home/types' -import { RankType } from '@/types/global' -import Link from 'next/link' - -interface SmallRankCardProps { - item: AiChatRankOutput | AiHeartbeatRankOutput | AiGiftRankOutput - rankType: RankType - rank?: number // 用于显示排名,如果不传则使用item.rankNo -} - -export default function SmallRankCard({ item, rankType, rank }: SmallRankCardProps) { - // 根据排行榜类型获取对应的数值显示 - const getDisplayValue = () => { - switch (rankType) { - case RankType.CHAT: - return formatNumberToKMB((item as AiChatRankOutput).chatNum || 0) - case RankType.CRUSH: - return `${(item as AiHeartbeatRankOutput).heartbeatValTotal || 0}℃` - case RankType.GIFTS: - return formatNumberToKMB(Math.floor(((item as AiGiftRankOutput).giftCoinNum || 0) / 100)) - default: - return '0' - } - } - - // 根据排行榜类型获取对应的图标 - const getIcon = () => { - switch (rankType) { - case RankType.CHAT: - return - case RankType.CRUSH: - return- {/* 排名编号 */} -- - ) - })} -- {String(item.rankNo).padStart(2, '0')} -- - {/* 头像 */} --- - {/* 用户信息区域 */} ---- - {/* 用户名 */} -- - {/* 排行榜数值 */} --- - {/* 喜欢数 */} -{item.nickname}---- --- {formatNumberToKMB(getLikedCount(item) || 0)} ----- {/* 图标 */} --{getRankIcon()}- - {/* 数值 */} -{displayValue}-- case RankType.GIFTS: - return - default: - return - } - } - - if (!item) { - return - } - - const imageUrl = item.homeImageUrl || '' - const rankNo = rank || item.rankNo || 1 - - return ( - - -- - ) -} diff --git a/src/app/(main)/leaderboard/components/TopHeader.tsx b/src/app/(main)/leaderboard/components/TopHeader.tsx deleted file mode 100644 index 663d45b..0000000 --- a/src/app/(main)/leaderboard/components/TopHeader.tsx +++ /dev/null @@ -1,68 +0,0 @@ -'use client' - -import { useMemo } from 'react' -import LargeRankCard from './LargeRankCard' -import SmallRankCard from './SmallRankCard' -import { AiChatRankOutput, AiHeartbeatRankOutput, AiGiftRankOutput } from '@/services/home/types' -import { RankType } from '@/types/global' -import { usePrefetchRoutes } from '@/hooks/useGlobalPrefetchRoutes' - -interface TopHeaderProps { - rankData: AiChatRankOutput[] | AiHeartbeatRankOutput[] | AiGiftRankOutput[] - rankType: RankType - isLoading?: boolean -} - -export default function TopHeader({ rankData, rankType, isLoading }: TopHeaderProps) { - const topThree = useMemo(() => (rankData || []).slice(0, 3), [rankData]) - const chatRoutes = useMemo( - () => topThree.map((item) => (item?.aiId ? `/chat/${item.aiId}` : null)), - [topThree] - ) - usePrefetchRoutes(chatRoutes, { limit: 3 }) - - if (isLoading) { - return ( -- ----- --- {getIcon()} --{getDisplayValue()}-- {rankNo === 2 ? '2nd' : '3rd'} ---- ) - } - - if (!rankData || rankData.length === 0) { - return ( ---- -- -- ---- ) - } - - const firstPlace = topThree[0] - const secondPlace = topThree[1] - const thirdPlace = topThree[2] - - return ( ---No leaderboard data yet--- ) -} diff --git a/src/app/(main)/leaderboard/leaderboard-page.tsx b/src/app/(main)/leaderboard/leaderboard-page.tsx deleted file mode 100644 index b89119a..0000000 --- a/src/app/(main)/leaderboard/leaderboard-page.tsx +++ /dev/null @@ -1,166 +0,0 @@ -'use client' - -import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs' -import { cn } from '@/lib/utils' -import { useState, useEffect } from 'react' -import { useSearchParams } from 'next/navigation' -import TopHeader from './components/TopHeader' -import RankingList from './components/RankingList' -import { useGetChatRank, useGetHeartbeatRank, useGetGiftRank } from '@/hooks/useHome' -import { RankType } from '@/types/global' -import Image from 'next/image' -import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip' -import { IconButton } from '@/components/ui/button' - -const LeaderboardPage = () => { - const searchParams = useSearchParams() - const typeFromUrl = searchParams.get('type') - - // 验证并设置初始 tab,确保 typeFromUrl 是有效的 RankType - const getInitialTab = (): RankType => { - if (typeFromUrl && Object.values(RankType).includes(typeFromUrl as RankType)) { - return typeFromUrl as RankType - } - return RankType.CHAT - } - - const [selectedTab, setSelectedTab] = useState- {/* 第二名 */} --- - {/* 第一名 */} - - - {/* 第三名 */} - - (getInitialTab()) - - // 当 URL 参数变化时,更新选中的 tab - useEffect(() => { - const newTab = getInitialTab() - setSelectedTab(newTab) - }, [typeFromUrl]) - - // 调用三个排行榜接口 - const { data: chatRankData, isLoading: chatLoading, error: chatError } = useGetChatRank() - const { - data: heartbeatRankData, - isLoading: heartbeatLoading, - error: heartbeatError, - } = useGetHeartbeatRank() - const { data: giftRankData, isLoading: giftLoading, error: giftError } = useGetGiftRank() - - const tabs = [ - { value: RankType.CHAT, label: 'Chat' }, - { value: RankType.CRUSH, label: 'Crush' }, - { value: RankType.GIFTS, label: 'Gifts' }, - ] - - // 根据选中的tab获取对应的数据 - const getCurrentRankData = () => { - switch (selectedTab) { - case RankType.CHAT: - return { data: chatRankData, isLoading: chatLoading, error: chatError } - case RankType.CRUSH: - return { data: heartbeatRankData, isLoading: heartbeatLoading, error: heartbeatError } - case RankType.GIFTS: - return { data: giftRankData, isLoading: giftLoading, error: giftError } - default: - return { data: chatRankData, isLoading: chatLoading, error: chatError } - } - } - - const currentRankData = getCurrentRankData() - - const backgroundColorMap = { - [RankType.CHAT]: 'linear-gradient(122.5deg, #F264A4 20.45%, #C241E6 100%)', - [RankType.CRUSH]: 'linear-gradient(122.5deg, #D664F2 20.45%, #416DE6 100%)', - [RankType.GIFTS]: 'linear-gradient(122.5deg, #FFC336 20.45%, #FF972F 100%)', - } - // return (background: linear-gradient(122.5deg, #F264A4 20.45%, #C241E6 100%); - - return ( - -- ) -} - -export default LeaderboardPage diff --git a/src/app/(main)/leaderboard/page.tsx b/src/app/(main)/leaderboard/page.tsx deleted file mode 100644 index f4f8ed2..0000000 --- a/src/app/(main)/leaderboard/page.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import Leaderboard from './leaderboard-page' - -const LeaderboardPage = () => { - return ( -- --- --Leaderboard
-- -- -- - -The hot chat list is ranked by the number of AI chat sessions.
-- The heart list is ranked by the sum of the heart values generated by all the - interlocutors of the AI character. -
-- The gift list is ranked by the sum of the gift value received by the AI character. -
--- - {/* 排行榜内容 */} - { - <> -setSelectedTab(value as RankType)} - > - -- {tabs.map((tab) => ( - -- {tab.label} - {/* 活跃状态指示器 */} - - - ))} -- - {/* 后续排名列表 */} - {!currentRankData.isLoading && ( - - )} - > - } - -- ) -} - -export default LeaderboardPage diff --git a/src/app/(main)/user/[userId]/components/AboutSection.tsx b/src/app/(main)/user/[userId]/components/AboutSection.tsx deleted file mode 100644 index e07acaf..0000000 --- a/src/app/(main)/user/[userId]/components/AboutSection.tsx +++ /dev/null @@ -1,14 +0,0 @@ -'use client' - -interface AboutSectionProps { - introduction: string -} - -export function AboutSection({ introduction }: AboutSectionProps) { - return ( -- -- ) -} diff --git a/src/app/(main)/user/[userId]/components/AlbumImageViewerAction.tsx b/src/app/(main)/user/[userId]/components/AlbumImageViewerAction.tsx deleted file mode 100644 index ba54439..0000000 --- a/src/app/(main)/user/[userId]/components/AlbumImageViewerAction.tsx +++ /dev/null @@ -1,266 +0,0 @@ -'use client' -import { IAlbumItem, LikedStatus, LockStatus } from '@/services/user' -import { useAIUser } from '../context/aiUser' -import { Button, IconButton } from '@/components/ui/button' -import { - useLikeAlbumImage, - useSetAlbumImageUnlockMethod, - useSetDefaultAlbumImage, -} from '@/hooks/aiUser' -import { cn, delay } from '@/lib/utils' -import { Checkbox } from '@/components/ui/checkbox' -import { - AlertDialog, - AlertDialogAction, - AlertDialogCancel, - AlertDialogContent, - AlertDialogDescription, - AlertDialogFooter, - AlertDialogHeader, - AlertDialogTitle, -} from '@/components/ui/alert-dialog' -import { useState, useRef } from 'react' -import AlbumPriceSetting, { AlbumPriceFormData } from '@/components/features/album-price-setting' -import Image from 'next/image' -import AlbumDeleteAlert from '@/components/features/album-delete-alert' -import { formatFromCents } from '@/utils/number' -import { useUpdateWalletBalance } from '@/hooks/useWallet' -import { isChargeDrawerOpenAtom } from '@/atoms/im' -import { useSetAtom } from 'jotai' -import Decimal from 'decimal.js' - -const AlbumImageViewerAction = ({ - datas, - originalDatas, - currentIndex, - onDeleted, - onUnlock, - unlockingAlbumIdsRef, -}: { - currentIndex: number - datas: IAlbumItem[] - originalDatas: IAlbumItem[] - onUnlock?: (imageId: number) => PromiseIntroduction
-{introduction}
-- onDeleted?: (nextIndex: number | null) => void - unlockingAlbumIdsRef: React.RefObject > -}) => { - const [lockLoading, setLockLoading] = useState(false) - const [isDefaultDialogOpen, setIsDefaultDialogOpen] = useState(false) - const { isOwner, userId } = useAIUser() - const currentData = datas[currentIndex] - const { albumId: originalAlbumId } = currentData - const current = originalDatas.find((item) => item.albumId === originalAlbumId) - const walletUpdate = useUpdateWalletBalance() - const setIsChargeDrawerOpen = useSetAtom(isChargeDrawerOpenAtom) - - if (!current) return null - const { lockStatus, likedStatus, isDefault, unlockPrice, albumId } = current - const isLiked = likedStatus === LikedStatus.Liked - - const likeMutation = useLikeAlbumImage() - const setDefaultMutation = useSetDefaultAlbumImage() - const setAlbumImageUnlockMethodMutation = useSetAlbumImageUnlockMethod() - - const handleLike = (albumId: number, isLiked: boolean) => { - if (!userId) return - likeMutation.mutate({ - albumId, - likedStatus: isLiked ? LikedStatus.Canceled : LikedStatus.Liked, - aiId: userId, - }) - } - - const handleSetDefault = (albumId: number) => { - if (!userId) return - // 如果是付费图片,则需要弹窗确认 - if (lockStatus) { - setIsDefaultDialogOpen(true) - return - } - handleConfirmSetDefault(albumId) - } - - const handleUnlock = async () => { - if (!userId || lockLoading || unlockingAlbumIdsRef.current?.has(albumId)) return - - if (!walletUpdate.checkSufficient((unlockPrice || 0) / 100)) { - setIsChargeDrawerOpen(true) - return - } - - unlockingAlbumIdsRef.current?.add(albumId) - setLockLoading(true) - - try { - await onUnlock?.(albumId) - } finally { - setLockLoading(false) - unlockingAlbumIdsRef.current?.delete(albumId) - } - } - - const handleConfirmSetDefault = (albumId: number) => { - if (!userId) return - setDefaultMutation.mutate( - { - aiId: userId, - albumId, - }, - { - onSuccess: () => { - setIsDefaultDialogOpen(false) - }, - onError: () => { - setIsDefaultDialogOpen(false) - }, - } - ) - } - - const handleSetAlbumImageUnlockMethod = async (data: AlbumPriceFormData) => { - if (!userId) return - await setAlbumImageUnlockMethodMutation.mutateAsync({ - aiId: userId, - albumId: datas[currentIndex].albumId, - unlockPrice: data.price ? new Decimal(data.price).mul(100).toNumber() : 0, - }) - } - - const renderPayAction = () => { - if (lockStatus === LockStatus.Lock) { - return ( - -- ) - } - - if (isOwner && lockStatus === LockStatus.Unlock) { - return ( -How to Unlock:---- - {formatFromCents(unlockPrice)} ---- ) - } - returnHow to Unlock:---- - {formatFromCents(unlockPrice)} --How to Unlock: Free- } - - const renderDefaultAction = () => { - return ( - <> - -handleSetDefault(albumId)} - > --- Default-- - {!isDefault && } - {!isDefault && ( -- -- -Default image -- After setting this as the default image, its unlock method can only be "Free." - -- -Cancel - -- {renderPayAction()} -- )} - -- { - const nextLength = datas.length - 1 - if (nextLength <= 0) { - onDeleted?.(null) - return - } - const isLast = currentIndex >= nextLength - const nextIndex = isLast ? nextLength - 1 : currentIndex - onDeleted?.(nextIndex) - }} - > - - > - ) - } - - if (!isOwner) { - if (!lockStatus || lockStatus === LockStatus.Unlock) { - // 没有上锁的图片,显示点赞按钮 - return ( - <> - - - > - ) - } - - return ( - <> - - - > - ) - } - - return <>{renderDefaultAction()}> -} - -export default AlbumImageViewerAction diff --git a/src/app/(main)/user/[userId]/components/AlbumItem.tsx b/src/app/(main)/user/[userId]/components/AlbumItem.tsx deleted file mode 100644 index a492e50..0000000 --- a/src/app/(main)/user/[userId]/components/AlbumItem.tsx +++ /dev/null @@ -1,153 +0,0 @@ -import { Tag } from '@/components/ui/tag' -import { cn, formatNumberToKMB } from '@/lib/utils' -import { IAlbumItem, LikedStatus, LockStatus } from '@/services/user' -import Image from 'next/image' -import { useState } from 'react' -import { useAIUser } from '../context/aiUser' -import { IconButton } from '@/components/ui/button' -import AlbumItemAction from './AlbumItemAction' -import { formatFromCents } from '@/utils/number' - -interface AlbumItemProps { - item: IAlbumItem - onLike: (albumId: number, isLiked: boolean) => void - onImageClick: () => void -} - -const AlbumItem = ({ item, onLike, onImageClick }: AlbumItemProps) => { - const [imageLoading, setImageLoading] = useState(true) - const { isOwner } = useAIUser() - - const handleLike = () => { - onLike(item.albumId, item.likedStatus === LikedStatus.Liked) - } - - const renderTag = () => { - if (item.isDefault) { - return null - } - if (isOwner) { - if (item.lockStatus) { - return ( -- - - - ) - } - } - if (item.lockStatus === LockStatus.Unlock) { - return ( -- - - ) - } - - return null - } - - const renderOverlay = () => { - // 如果是自己的相册,则不显示解锁按钮 - if (isOwner) { - return null - } - - if (item.lockStatus === LockStatus.Lock) { - return ( -{ - // e.stopPropagation(); - // handleUnlock(); - }} - > - -- ) - } - return null - } - - const renderDefaultTag = () => { - if (item.isDefault) { - return ( ------- - {formatFromCents(item.unlockPrice || 0)} - -- Unlock-- Default - - ) - } - return null - } - - return ( --- ) -} - -export default AlbumItem diff --git a/src/app/(main)/user/[userId]/components/AlbumItemAction.tsx b/src/app/(main)/user/[userId]/components/AlbumItemAction.tsx deleted file mode 100644 index 08e1a4f..0000000 --- a/src/app/(main)/user/[userId]/components/AlbumItemAction.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuTrigger, -} from '@/components/ui/dropdown-menu' -import { IconButton } from '@/components/ui/button' -import { useAIUser } from '../context/aiUser' -import { IAlbumItem } from '@/services/user/types' -import AlbumDeleteAlert from '@/components/features/album-delete-alert' - -const AlbumItemAction = ({ data }: { data: IAlbumItem }) => { - const { isOwner, userId } = useAIUser() - - if (!isOwner) { - return null - } - - return ( -- {/* 背景图片 */} ---- {renderDefaultTag()} - - {/* 标签 */} - {renderTag()} - - {/* 付费内容遮罩 */} - {renderOverlay()} - - {/* 底部操作区 */} -setImageLoading(false)} - sizes="(max-width: 768px) 50vw, 176px" - /> - {imageLoading && ( - - )} - e.stopPropagation()} - > - {/* 点赞按钮 */} - {(item.lockStatus !== LockStatus.Lock || isOwner) && ( ---- )} - -- {item.likedStatus === LikedStatus.Liked ? ( - - ) : ( - - )} - - - {formatNumberToKMB(item.likedCount ?? 0)} - -- - - ) -} - -export default AlbumItemAction diff --git a/src/app/(main)/user/[userId]/components/AlbumList.tsx b/src/app/(main)/user/[userId]/components/AlbumList.tsx deleted file mode 100644 index 1f166f1..0000000 --- a/src/app/(main)/user/[userId]/components/AlbumList.tsx +++ /dev/null @@ -1,233 +0,0 @@ -'use client' - -import { useParams, useRouter, usePathname } from 'next/navigation' -import { - useGetAIUserAlbumInfinite, - useLikeAlbumImage, - useUnlockAlbumImage, - useUnlockImage, -} from '@/hooks/aiUser' -import { IAlbumItem, LikedStatus, LockStatus } from '@/services/user/types' -import Empty from '@/components/ui/empty' -import { toast } from 'sonner' -import AlbumItem from './AlbumItem' -import { InfiniteScrollList } from '@/components/ui/infinite-scroll-list' -import { ImageViewer, ImageViewerPaginationContent } from '@/components/ui/image-viewer' -import { useImageViewer } from '@/hooks/useImageViewer' -import { useMemo, useRef, useState } from 'react' -import { useAIUser } from '../context/aiUser' -import AlbumImageViewerAction from './AlbumImageViewerAction' -import Image from 'next/image' -import { formatFromCents } from '@/utils/number' -import { useToken } from '@/hooks/auth' - -// 专门的相册骨架屏组件 -const AlbumSkeleton = () => ( -- -- - -- -- -e.preventDefault()}> - - Delete - ---) - -const AlbumList = () => { - const { userId } = useParams() - const router = useRouter() - const pathname = usePathname() - const { isLogin } = useToken() - const pageSize = 20 - const unlockingAlbumIdsRef = useRef- -->(new Set()) - - const { data, isLoading, fetchNextPage, hasNextPage, isFetchingNextPage, isError, refetch } = - useGetAIUserAlbumInfinite(Number(userId), pageSize) - const likeMutation = useLikeAlbumImage() - const unlockMutation = useUnlockImage() - const { isOwner } = useAIUser() - const [tempList, setTempList] = useState ([]) - - // 图片查看器 - const { - isOpen: isViewerOpen, - currentIndex: viewerIndex, - openViewer, - closeViewer, - handleIndexChange, - } = useImageViewer() - - // 展平所有页面的数据 - const albumItems = useMemo(() => { - if (!data?.pages) return [] - return data.pages.flatMap((page) => page.datas || []) - }, [data?.pages]) - - const handleLike = (albumId: number, isLiked: boolean) => { - likeMutation.mutate({ - albumId, - likedStatus: isLiked ? LikedStatus.Canceled : LikedStatus.Liked, - aiId: Number(userId), - }) - } - - const handleUnlock = async (imageId: number) => { - await unlockMutation.mutateAsync( - { aiId: Number(userId), albumId: imageId }, - { - onSuccess: () => { - toast.success('Unlocked successfully!') - }, - onError: (error) => {}, - } - ) - } - - const handleImageClick = (item: IAlbumItem, index: number) => { - // 检查是否登录,如果未登录则跳转到登录页面 - if (!isLogin) { - const loginUrl = `/login?redirect=${encodeURIComponent(pathname)}` - router.push(loginUrl) - return - } - - // 获取所有图片URL - const imageUrls = albumItems.map((albumItem) => albumItem.imgUrl || albumItem.img3) - setTempList(albumItems) - // 打开图片查看器 - openViewer(imageUrls, index) - } - - const viewerImages: IAlbumItem[] = useMemo(() => { - return tempList.map((item) => { - const { albumId } = item - const data = albumItems.find((item) => item.albumId === albumId) || {} - return data as IAlbumItem - }) - }, [tempList, albumItems]) - - return ( - <> - - items={albumItems} - renderItem={(item, index) => ( - handleImageClick(item, index)} - /> - )} - getItemKey={(item) => item.albumId} - hasNextPage={!!hasNextPage} - isLoading={isLoading || isFetchingNextPage} - fetchNextPage={fetchNextPage} - columns={{ - default: 2, - sm: 3, - md: 4, - lg: 4, - xl: 5, - }} - gap={4} - LoadingSkeleton={AlbumSkeleton} - EmptyComponent={() => ( - -- )} - hasError={isError} - onRetry={refetch} - threshold={300} - enabled={true} - /> - - {/* 图片查看器 */} -- albumItem.imgUrl || albumItem.img3)} - currentIndex={viewerIndex} - open={isViewerOpen} - onClose={closeViewer} - onIndexChange={handleIndexChange} - showChooseButton={false} - ActionComponent={() => { - return ( - { - if (nextIndex === null) { - // 删除后没有图片了 - closeViewer() - return - } - // 调整到新的索引,避免越界 - handleIndexChange(nextIndex) - }} - /> - ) - }} - OverlayComponent={() => { - const findItem = albumItems.find((item, index) => index === viewerIndex) - const { unlockPrice, lockStatus } = findItem || {} - if (isOwner || !lockStatus || lockStatus === LockStatus.Unlock) { - return null - } - return ( - - -- ) - }} - PaginationComponent={() => { - const currentIndex = viewerIndex + 1 - const findItem = tempList.find((item, index) => index === viewerIndex) - const { albumId } = findItem || {} - const { lockStatus } = albumItems.find((item) => item.albumId === albumId) || {} - - if (isOwner) { - if (lockStatus) { - return ( ---- {`${formatFromCents(unlockPrice || 0)} to unlock`}-- - {`${currentIndex}/${albumItems.length}`} - - ) - } - return ( -- {`${currentIndex}/${albumItems.length}`} - - ) - } - if (lockStatus === LockStatus.Lock) { - return ( -- - {`${currentIndex}/${albumItems.length}`} - - ) - } - if (lockStatus === LockStatus.Unlock) { - return ( -- - {`${currentIndex}/${albumItems.length}`} - - ) - } - return ( -- {`${currentIndex}/${albumItems.length}`} - - ) - }} - /> - > - ) -} - -export default AlbumList diff --git a/src/app/(main)/user/[userId]/components/GiftGrid.tsx b/src/app/(main)/user/[userId]/components/GiftGrid.tsx deleted file mode 100644 index f832f43..0000000 --- a/src/app/(main)/user/[userId]/components/GiftGrid.tsx +++ /dev/null @@ -1,53 +0,0 @@ -'use client' -import Empty from '@/components/ui/empty' -import { useGetAIUserGifts } from '@/hooks/aiUser' -import { formatNumberToKMB } from '@/lib/utils' -import Image from 'next/image' - -export function GiftGrid({ userId }: { userId: string }) { - const { data } = useGetAIUserGifts({ aiId: Number(userId), page: { pn: 1, ps: 100 } }) - const { datas } = data || {} - - if (datas && !datas.length) { - return ( --- ) - } - - return ( -- Gifts -
- ---- -- ) -} diff --git a/src/app/(main)/user/[userId]/components/TabNavigation.tsx b/src/app/(main)/user/[userId]/components/TabNavigation.tsx deleted file mode 100644 index 1bcec2a..0000000 --- a/src/app/(main)/user/[userId]/components/TabNavigation.tsx +++ /dev/null @@ -1,65 +0,0 @@ -'use client' - -import { useState } from 'react' -import { cn } from '@/lib/utils' -import { UserTab } from '../types' - -interface TabNavigationProps { - activeTab?: UserTab - onTabChange?: (tab: UserTab) => void -} - -export function TabNavigation({ activeTab = UserTab.About, onTabChange }: TabNavigationProps) { - const [currentTab, setCurrentTab] = useState(activeTab) - - const handleTabClick = (tab: UserTab) => { - setCurrentTab(tab) - onTabChange?.(tab) - } - - return ( -- Gifts -
- -- {datas?.map((gift) => ( ---- ))} ------ - {gift.name} X{formatNumberToKMB(gift.getNum ?? 0)} --- {/* About 选项卡 */} -- ) -} diff --git a/src/app/(main)/user/[userId]/components/UserActionDropdown.tsx b/src/app/(main)/user/[userId]/components/UserActionDropdown.tsx deleted file mode 100644 index ef29714..0000000 --- a/src/app/(main)/user/[userId]/components/UserActionDropdown.tsx +++ /dev/null @@ -1,100 +0,0 @@ -import { - AlertDialog, - AlertDialogAction, - AlertDialogCancel, - AlertDialogContent, - AlertDialogDescription, - AlertDialogFooter, - AlertDialogHeader, - AlertDialogTitle, -} from '@/components/ui/alert-dialog' -import { IconButton } from '@/components/ui/button' -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuTrigger, -} from '@/components/ui/dropdown-menu' -import { useDeleteCharacter } from '@/hooks/aiUser' -import { useState } from 'react' -import { useAIUser } from '../context/aiUser' -import { useParams, useRouter } from 'next/navigation' -import { useNimChat, useNimConversation, useNimMsgContext } from '@/context/NimChat/useNimChat' - -const UserActionDropdown = () => { - const router = useRouter() - const [isDeleteCharacterDialogOpen, setIsDeleteCharacterDialogOpen] = useState(false) - const { userId, isOwner } = useAIUser() - const { removeConversationById } = useNimConversation() - const { clearHistoryMessage } = useNimMsgContext() - const { nim } = useNimChat() - - const { mutate: deleteCharacter, isPending: isDeleteCharacterLoading } = useDeleteCharacter({ - aiId: Number(userId), - }) - - const handleDeleteCharacter = () => { - setIsDeleteCharacterDialogOpen(true) - } - - const handleDeleteCharacterConfirm = async () => { - const conversationId = await nim.V2NIMConversationIdUtil.p2pConversationId( - `${userId}@r@t` as string - ) - await removeConversationById(conversationId) - await clearHistoryMessage(conversationId) - await deleteCharacter({ aiId: Number(userId) }) - router.replace('/profile') - } - - if (!isOwner) return null - - return ( - <> -- -- - {/* Album 选项卡 */} -- ---- -------- -- -- - -- - Delete Character - -- - > - ) -} - -export default UserActionDropdown diff --git a/src/app/(main)/user/[userId]/components/UserBackground.tsx b/src/app/(main)/user/[userId]/components/UserBackground.tsx deleted file mode 100644 index c9feea2..0000000 --- a/src/app/(main)/user/[userId]/components/UserBackground.tsx +++ /dev/null @@ -1,136 +0,0 @@ -import { useGetAIUserBaseInfo } from '@/hooks/aiUser' -import { cn, loadImageAsync } from '@/lib/utils' -import { useParams } from 'next/navigation' -import { useEffect, useRef, useState } from 'react' - -const getDominantColor = (data: Uint8ClampedArray) => { - let red = 0, - green = 0, - blue = 0 - const length = data.length - for (let i = 0; i < length; i += 4) { - red += data[i] - green += data[i + 1] - blue += data[i + 2] - } - // 计算像素点数 - const pixelCount = length / 4 - red = Math.round(red / pixelCount) - green = Math.round(green / pixelCount) - blue = Math.round(blue / pixelCount) - return `${red}, ${green}, ${blue}` -} - -const getImageRightDominantColor = (ctx: any, image: any) => { - const imageData = ctx.getImageData(image.width - 1, 0, 1, image.height) - return getDominantColor(imageData.data) -} -const getImageLeftDominantColor = (ctx: any, image: any) => { - const imageData = ctx.getImageData(0, 0, 1, image.height) - return getDominantColor(imageData.data) -} - -const UserBackground = () => { - const { userId } = useParams() - const { data } = useGetAIUserBaseInfo({ aiId: Number(userId) }) - const [colors, setColors] = useState({ - leftColor: '#37363b', - rightColor: '#313133', - }) - const [isLoading, setIsLoading] = useState(true) - const canvasRef = useRef- -- -Delete Character -- Once deleted, the character cannot be restored. To ensure a good user experience, users - who have previously chatted with or made purchases for this character will still be able - to interact with them. - -- -Cancel -- Delete - -(null) - - // 获取图片两边的颜色 - const getImageColors = async (url: string) => { - const image: any = await loadImageAsync(url) - const canvas: any = canvasRef.current - canvas.width = image.width - canvas.height = image.height - const ctx = canvas.getContext('2d') - ctx.drawImage(image, 0, 0) - const rightColor = getImageRightDominantColor(ctx, image) - const leftColor = getImageLeftDominantColor(ctx, image) - return { - leftColor, - rightColor, - } - } - - useEffect(() => { - const init = async () => { - if (data?.homeImageUrl) { - setIsLoading(true) - try { - const { leftColor, rightColor } = await getImageColors(data.homeImageUrl) - setColors({ - leftColor, - rightColor, - }) - } catch (error) { - console.error('获取图片颜色失败:', error) - } finally { - setIsLoading(false) - } - } else { - setIsLoading(false) - } - } - init() - }, [data?.homeImageUrl]) - - if (!data) { - return null - } - - return ( - <> - {/* 背景头部区域 */} - - {/* 加载状态 - 显示默认背景 */} - {isLoading && ( - <> - - - > - )} - - {/* 加载完成后的背景 */} - {!isLoading && ( - <> - - - - {/* 背景图片 */} - - - {/* 渐变遮罩 */} -- > - ) -} - -export default UserBackground diff --git a/src/app/(main)/user/[userId]/components/UserCard.tsx b/src/app/(main)/user/[userId]/components/UserCard.tsx deleted file mode 100644 index da8b973..0000000 --- a/src/app/(main)/user/[userId]/components/UserCard.tsx +++ /dev/null @@ -1,223 +0,0 @@ -'use client' - -import { Button, IconButton } from '@/components/ui/button' -import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar' -import { useGetAIUserBaseInfo, useGetAIUserStat } from '@/hooks/aiUser' -import { useParams, useRouter } from 'next/navigation' -import { formatNumberToKMB, getAge } from '@/lib/utils' -import { Tag } from '@/components/ui/tag' -import Image from 'next/image' -import { useAIUser } from '../context/aiUser' -import { useEditFormStorage, useEditAI, useEditAIAvatar } from '@/hooks/create' -import { useNimChat, useNimConversation } from '@/context/NimChat/useNimChat' -import { useCurrentUser, useToken } from '@/hooks/auth' -import { AvatarCropModal } from '@/components/ui/avatar-crop-modal' -import { useState, useRef } from 'react' -import { BizTypeEnum } from '@/services/common' -import { AIPermission, CreateOrEditAiRequest } from '@/services/create' -import UserShare from './UserShare' -import UserLikeButton from './UserLikeButton' -import Decimal from 'decimal.js' - -const genderMap = { - 0: '/icons/male.svg', - 1: '/icons/female.svg', - 2: '/icons/gender-neutral.svg', -} - -export function UserCard() { - const { userId } = useParams() - const { data } = useGetAIUserBaseInfo({ aiId: Number(userId) }) - const { isOwner } = useAIUser() - const router = useRouter() - const { insertConversationActive } = useNimConversation() - - const { birthday, characterName, headImg, nickname, sex, tagName, homeImageUrl } = data || {} - const { data: statData } = useGetAIUserStat({ aiId: Number(userId) }) - const { chatNum, coinNum, conversationNum, likedNum } = statData || {} - const { clearFormData } = useEditFormStorage() - const { isNimLoggedIn } = useNimChat() - const { getLoginStatus } = useToken() - - // AI用户编辑hook - const { mutateAsync: editAIAvatar, isPending: isEditingAIAvatar } = useEditAIAvatar() - - // 头像裁剪相关状态 - const [showCropModal, setShowCropModal] = useState(false) - const fileInputRef = useRef- {/* - */} -- > - )} - - {/* 底部渐变遮罩 */} - - -(null) - - const statList = [ - { - label: 'Likes', - value: formatNumberToKMB(likedNum || 0), - }, - { - label: 'Chats', - value: formatNumberToKMB(chatNum || 0), - }, - { - label: 'Users', - value: formatNumberToKMB(conversationNum || 0), - }, - isOwner && { - label: 'CrushCoin', - value: formatNumberToKMB(new Decimal(coinNum || 0).div(100).toNumber()), - }, - ].filter(Boolean) as { label: string; value: string | number }[] - - const handleEdit = () => { - clearFormData() - router.push(`/edit/${userId}/type`) - } - - const handleChat = () => { - if (!getLoginStatus()) { - router.push(`/chat/${userId}`) - return - } - - if (!isNimLoggedIn) { - return - } - // insertConversationActive({ - // receiverId: `${userId}@r@t` as string, - // }) - router.push(`/chat/${userId}`) - } - - // 处理头像点击 - const handleAvatarClick = () => { - if (!isOwner) return - setShowCropModal(true) - } - - // 处理裁剪完成 - const handleCropComplete = async (croppedImageUrl: string) => { - try { - // 更新AI用户头像 - 只更新头像,其他信息保持原样 - await editAIAvatar({ - aiId: Number(userId), - userHead: croppedImageUrl, - }) - } catch (error) { - // 可以添加错误提示 - } finally { - setShowCropModal(false) - // 清空文件输入 - if (fileInputRef.current) { - fileInputRef.current.value = '' - } - } - } - - // 处理裁剪取消 - const handleCropCancel = () => { - setShowCropModal(false) - // 清空文件输入 - if (fileInputRef.current) { - fileInputRef.current.value = '' - } - } - - const renderChatButton = () => { - if (isOwner) { - return ( - - ) - } - - return ( - - ) - } - - if (!data) { - return null - } - - return ( - - {/* 背景卡片 */} - - - {/* 内容 */} -- ) -} diff --git a/src/app/(main)/user/[userId]/components/UserLikeButton.tsx b/src/app/(main)/user/[userId]/components/UserLikeButton.tsx deleted file mode 100644 index a7a4b14..0000000 --- a/src/app/(main)/user/[userId]/components/UserLikeButton.tsx +++ /dev/null @@ -1,59 +0,0 @@ -'use client' -import { IconButton } from '@/components/ui/button' -import { useGetAIUserBaseInfo } from '@/hooks/aiUser' -import { useDoAiUserLiked } from '@/hooks/useCommon' -import { aiUserKeys, imKeys } from '@/lib/query-keys' -import { useQueryClient } from '@tanstack/react-query' -import { useParams, useRouter } from 'next/navigation' -import { useToken } from '@/hooks/auth' - -const UserLikeButton = () => { - const { userId } = useParams() - const router = useRouter() - const { isLogin } = useToken() - const { data } = useGetAIUserBaseInfo({ aiId: Number(userId) }) - const { mutateAsync: doAiUserLiked } = useDoAiUserLiked() - const { liked } = data || {} - const queryClient = useQueryClient() - - const handleLike = () => { - // 检查是否登录,如果未登录则跳转到登录页面 - if (!isLogin) { - const currentPath = `/@${userId}` - router.push(`/login?redirect=${encodeURIComponent(currentPath)}`) - return - } - - doAiUserLiked({ aiId: Number(userId), likedStatus: liked ? 'CANCELED' : 'LIKED' }) - queryClient.setQueryData(aiUserKeys.baseInfo({ aiId: Number(userId) }), (oldData: any) => { - return { - ...oldData, - liked: !liked, - } - }) - queryClient.setQueryData(imKeys.imUserInfo(Number(userId)), (oldData: any) => { - return { - ...oldData, - liked: !liked, - } - }) - queryClient.setQueryData(aiUserKeys.stat({ aiId: Number(userId) }), (oldData: any) => { - return { - ...oldData, - likedNum: !liked ? oldData.likedNum + 1 : oldData.likedNum - 1, - } - }) - } - - return ( -- {/* 头像 */} -- - {/* 头像裁剪弹窗 */} - {homeImageUrl && ( --- - {/* 用户信息 */} -- -- - {nickname?.slice(0, 1)} - -- {/* 用户名 */} -- - {/* 统计数据 */} -{nickname}
- - {/* 标签 */} -- {/* 年龄和性别标签 */} --- -- {getAge(birthday ?? 0)}-{characterName} -{tagName} -- {statList.map((item, index) => ( -- - {/* 操作按钮 */} --- ))} -{item.value}-{item.label}-- {/* 分享按钮 */} --- - {/* 聊天 */} - {isOwner && ( - - - - )} - -- - {renderChatButton()} - setShowCropModal(false)} - image={homeImageUrl} - onConfirm={handleCropComplete} - onCancel={handleCropCancel} - bizType={BizTypeEnum.Album} - /> - )} - - {!liked ? ( - - ) : ( - - )} - - ) -} - -export default UserLikeButton diff --git a/src/app/(main)/user/[userId]/components/UserProfileTabs.tsx b/src/app/(main)/user/[userId]/components/UserProfileTabs.tsx deleted file mode 100644 index 25c2780..0000000 --- a/src/app/(main)/user/[userId]/components/UserProfileTabs.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import { TabsList, TabsTrigger } from '@/components/ui/tabs' -import { UserTab } from '../types' -import { useAIUser } from '../context/aiUser' -import { Button } from '@/components/ui/button' -import { useRouter } from 'next/navigation' - -const UserProfileTabs = ({ currentTab }: { currentTab: UserTab }) => { - const { isOwner, userId } = useAIUser() - const router = useRouter() - - const handleCreatePhoto = () => { - router.push(`/generate/image-2-image?id=${userId}`) - } - - return ( -- {/* Tab 列表 */} -- ) -} - -export default UserProfileTabs diff --git a/src/app/(main)/user/[userId]/components/UserShare.tsx b/src/app/(main)/user/[userId]/components/UserShare.tsx deleted file mode 100644 index 44fa025..0000000 --- a/src/app/(main)/user/[userId]/components/UserShare.tsx +++ /dev/null @@ -1,52 +0,0 @@ -'use client' - -import { IconButton } from '@/components/ui/button' -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuTrigger, -} from '@/components/ui/dropdown-menu' -import useShare from '@/hooks/useShare' -import { useParams } from 'next/navigation' - -const UserShare = () => { - const { userId } = useParams() - - const { shareFacebook, shareTwitter } = useShare() - - const handleShareFacebook = () => { - shareFacebook({ - text: 'Come to Crushlevel for chat, Crush, and AI - chat.', - shareUrl: `${process.env.NEXT_PUBLIC_APP_URL}/@${userId}`, - }) - } - const handleShareTwitter = () => { - shareTwitter({ - text: 'Come to Crushlevel for chat, Crush, and AI - chat.', - shareUrl: `${process.env.NEXT_PUBLIC_APP_URL}/@${userId}`, - }) - } - - return ( -- - - {isOwner && currentTab === UserTab.Album && ( - - )} -- - About - - - - -- - Album - - - -- - ) -} - -export default UserShare diff --git a/src/app/(main)/user/[userId]/context/aiUser/index.tsx b/src/app/(main)/user/[userId]/context/aiUser/index.tsx deleted file mode 100644 index da3387c..0000000 --- a/src/app/(main)/user/[userId]/context/aiUser/index.tsx +++ /dev/null @@ -1,44 +0,0 @@ -'use client' - -import { AiUserBaseOutput } from '@/services/user' -import { createContext } from 'react' -import { useGetAIUserBaseInfo } from '@/hooks/aiUser' -import { useParams } from 'next/navigation' -import { useCurrentUser } from '@/hooks/auth' -import Empty from '@/components/ui/empty' - -export * from './useAIUser' - -const AIUserContext = createContext<{ - user: AiUserBaseOutput | undefined - isOwner: boolean - userId: number | undefined -}>({ - user: undefined, - isOwner: false, - userId: undefined, -}) - -export const AIUserProvider = ({ children }: { children: React.ReactNode }) => { - const { userId } = useParams() - - const { data, error } = useGetAIUserBaseInfo({ aiId: Number(userId) }) - const { data: currentUser } = useCurrentUser() - const isOwner = currentUser?.userId === data?.userId - - if (error) { - return ( -- -- - -- -- - Share to Facebook - -- - Share to X - --- ) - } - - return ( -- - {children} - - ) -} - -export default AIUserContext diff --git a/src/app/(main)/user/[userId]/context/aiUser/useAIUser.ts b/src/app/(main)/user/[userId]/context/aiUser/useAIUser.ts deleted file mode 100644 index ad982c5..0000000 --- a/src/app/(main)/user/[userId]/context/aiUser/useAIUser.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { useContext } from 'react' -import AIUserContext from '.' - -export const useAIUser = () => { - const context = useContext(AIUserContext) - if (!context) { - throw new Error('useAIUser must be used within a AIUserProvider') - } - return context -} diff --git a/src/app/(main)/user/[userId]/not-found.tsx b/src/app/(main)/user/[userId]/not-found.tsx deleted file mode 100644 index 2ea4904..0000000 --- a/src/app/(main)/user/[userId]/not-found.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import Empty from '@/components/ui/empty' - -export default async function NotFound() { - return ( --- ) -} diff --git a/src/app/(main)/user/[userId]/page.tsx b/src/app/(main)/user/[userId]/page.tsx deleted file mode 100644 index 33a3e79..0000000 --- a/src/app/(main)/user/[userId]/page.tsx +++ /dev/null @@ -1,92 +0,0 @@ -import UserPage from './user-page' -import { HydrationBoundary } from '@tanstack/react-query' -import { dehydrate } from '@tanstack/react-query' -import { QueryClient } from '@tanstack/react-query' -import { aiUserKeys } from '@/lib/query-keys' -import { userService } from '@/services/user' -import { ApiError } from '@/types/api' -import { notFound, redirect } from 'next/navigation' -import { headers } from 'next/headers' -import { isMobileDevice } from '@/utils/device' - -export const generateMetadata = async ({ params }: { params: Promise<{ userId: string }> }) => { - const { userId } = await params - try { - const resp = await userService.getSEOUserBaseInfo({ aiId: Number(userId) }) - const { nickname, homeImageUrl } = resp || {} - - return { - title: `${nickname} - Crushlevel AI`, - description: `${nickname}`, - openGraph: { - title: `Crushlevel AI`, - description: `Grow your love story with CrushLevel AI—From ‘Hi’ to ‘I Do', sparked by every chat`, - images: { - url: homeImageUrl, - width: 720, - height: 1280, - alt: `${nickname} - Crushlevel AI`, - }, - siteName: 'Crushlevel AI', - type: 'website', - url: `https://www.crushlevel.com/@${userId}`, - }, - twitter: { - title: `Crushlevel AI`, - description: `Grow your love story with CrushLevel AI—From ‘Hi’ to ‘I Do', sparked by every chat`, - images: { - url: homeImageUrl, - width: 720, - height: 1280, - alt: `${nickname} - Crushlevel AI`, - }, - siteName: 'Crushlevel AI', - type: 'website', - url: `https://www.crushlevel.com/@${userId}`, - }, - } - } catch (error) { - return { - title: `Crushlevel AI`, - description: `Grow your love story with CrushLevel AI—From ‘Hi’ to ‘I Do', sparked by every chat`, - openGraph: { - title: `Crushlevel AI`, - description: `Grow your love story with CrushLevel AI—From ‘Hi’ to ‘I Do', sparked by every chat`, - }, - } - } -} - -const Page = async ({ params }: { params: Promise<{ userId: string }> }) => { - const { userId } = await params - - // 检测移动端设备并重定向到分享页面 - const headersList = await headers() - const userAgent = headersList.get('user-agent') || '' - - if (isMobileDevice(userAgent)) { - redirect(`/share/${userId}`) - } - - const queryClient = new QueryClient() - - // try { - // // 使用 fetchQuery 替代 prefetchQuery,因为 fetchQuery 会抛出错误 - // await queryClient.fetchQuery({ - // queryKey: aiUserKeys.tempBaseInfo({ aiId: Number(userId) }), - // queryFn: () => userService.getAIUserBaseInfo({ aiId: Number(userId) }), - // }); - // } catch (error) { - // if (error instanceof ApiError && error.errorCode === "10010012") { - // notFound(); - // } - // } - - return ( -- - - ) -} - -export default Page diff --git a/src/app/(main)/user/[userId]/types.ts b/src/app/(main)/user/[userId]/types.ts deleted file mode 100644 index 7e1f1e7..0000000 --- a/src/app/(main)/user/[userId]/types.ts +++ /dev/null @@ -1,4 +0,0 @@ -export enum UserTab { - About = 'about', - Album = 'album', -} diff --git a/src/app/(main)/user/[userId]/user-page.tsx b/src/app/(main)/user/[userId]/user-page.tsx deleted file mode 100644 index 262a6fb..0000000 --- a/src/app/(main)/user/[userId]/user-page.tsx +++ /dev/null @@ -1,80 +0,0 @@ -'use client' - -import { useParams, useSearchParams, useRouter } from 'next/navigation' -import { UserCard } from './components/UserCard' -import { AboutSection } from './components/AboutSection' -import { GiftGrid } from './components/GiftGrid' -import UserBackground from './components/UserBackground' -import AlbumList from './components/AlbumList' -import { UserTab } from './types' -import { useGetAIUserBaseInfo } from '@/hooks/aiUser' -import { Tabs, TabsContent } from '@/components/ui/tabs' -import { AIUserProvider } from './context/aiUser' -import UserActionDropdown from './components/UserActionDropdown' -import UserProfileTabs from './components/UserProfileTabs' -import { useCallback } from 'react' - -const UserPage = () => { - const { userId } = useParams() - const searchParams = useSearchParams() - const router = useRouter() - const { data } = useGetAIUserBaseInfo({ aiId: Number(userId) }) - const { introduction } = data || {} - - // 从 URL 参数中获取当前 tab,如果没有或无效则默认为 About - const tabParam = searchParams.get('tab') - const currentTab = ( - Object.values(UserTab).includes(tabParam as UserTab) ? tabParam : UserTab.About - ) as UserTab - - // 处理 tab 切换 - const handleTabChange = useCallback( - (newTab: string) => { - const params = new URLSearchParams(searchParams.toString()) - params.set('tab', newTab) - router.push(`/@${userId}?${params.toString()}`, { scroll: false }) - }, - [searchParams, router, userId] - ) - - return ( -- - {data && ( - - ) -} - -export default UserPage diff --git a/src/app/(main)/vip/components/SubscribeVipDrawer/index.tsx b/src/app/(main)/vip/components/SubscribeVipDrawer/index.tsx index dbf3c8b..8e625fd 100644 --- a/src/app/(main)/vip/components/SubscribeVipDrawer/index.tsx +++ b/src/app/(main)/vip/components/SubscribeVipDrawer/index.tsx @@ -1,17 +1,16 @@ -'use client' +'use client'; -import { Button, IconButton } from '@/components/ui/button' -import { Drawer, DrawerContent, DrawerHeader, DrawerTitle } from '@/components/ui/drawer' -import { useState, useMemo } from 'react' -import SubscribeProducts from './SubscribeProducs' -import SubscribeProductsSkeleton from './SubscribeProductsSkeleton' -import CarouselBanner from './CarouselBanner' -import { useGetSubProductCheckoutLink, useGetSubProductList } from '@/hooks/useWallet' -import { SubProductListOutput, Period, VipType } from '@/services/wallet/types' -import { isVipDrawerOpenAtom } from '@/atoms/im' -import { useAtom } from 'jotai' -import QueryString from 'qs' -import { usePathname, useRouter, useSearchParams } from 'next/navigation' +import { Button, IconButton } from '@/components/ui/button'; +import { Drawer, DrawerContent, DrawerHeader, DrawerTitle } from '@/components/ui/drawer'; +import { useState, useMemo } from 'react'; +import SubscribeProducts from './SubscribeProducs'; +import SubscribeProductsSkeleton from './SubscribeProductsSkeleton'; +import CarouselBanner from './CarouselBanner'; +import { useGetSubProductCheckoutLink, useGetSubProductList } from '@/hooks/useWallet'; +import { SubProductListOutput, Period, VipType } from '@/services/wallet/types'; +import { useAtom } from 'jotai'; +import QueryString from 'qs'; +import { usePathname, useRouter, useSearchParams } from 'next/navigation'; function SubscribeBackground() { return ( @@ -24,43 +23,43 @@ function SubscribeBackground() { backgroundRepeat: 'no-repeat', }} >-- )} -- - {/* 主要内容 */} - --- - {/* 左侧用户卡片 */} - -- - {/* 右侧内容区域 */} -- --- -- - {/* Tab 内容 */} - - - ---- - - -- (0) // 默认不选择任何计划 - const [isVipDrawerOpen, setIsVipDrawerOpen] = useAtom(isVipDrawerOpenAtom) + const [selectedPlan, setSelectedPlan] = useState (0); // 默认不选择任何计划 + // const [isVipDrawerOpen, setIsVipDrawerOpen] = useAtom(isVipDrawerOpenAtom) + const [isVipDrawerOpen, setIsVipDrawerOpen] = useState({ + open: false, + vipType: undefined, + }); - const { data: productList, isLoading } = useGetSubProductList() - const { mutateAsync, isPending } = useGetSubProductCheckoutLink() - const pathname = usePathname() - const searchParams = useSearchParams() + const { data: productList, isLoading } = useGetSubProductList(); + const { mutateAsync, isPending } = useGetSubProductCheckoutLink(); + const pathname = usePathname(); + const searchParams = useSearchParams(); // 转换产品数据为组件需要的格式 const pricingPlans = useMemo(() => { - const plans = convertProductListToPricingPlans(productList || []) + const plans = convertProductListToPricingPlans(productList || []); // 设置选中状态 return plans.map((plan) => ({ ...plan, isSelected: plan.id === selectedPlan, - })) - }, [productList, selectedPlan]) + })); + }, [productList, selectedPlan]); // 设置默认选中第二个计划(3个月) useMemo(() => { if (pricingPlans.length > 0 && selectedPlan === 0) { - const seasonPlan = pricingPlans.find((plan) => plan.title === '3 Month') + const seasonPlan = pricingPlans.find((plan) => plan.title === '3 Month'); if (seasonPlan) { - setSelectedPlan(seasonPlan.id) + setSelectedPlan(seasonPlan.id); } else { - setSelectedPlan(pricingPlans[0].id) // 如果没有3个月计划,选择第一个 + setSelectedPlan(pricingPlans[0].id); // 如果没有3个月计划,选择第一个 } } - }, [pricingPlans, selectedPlan]) + }, [pricingPlans, selectedPlan]); const handleClose = () => { - setIsVipDrawerOpen({ open: false, vipType: undefined }) - } + setIsVipDrawerOpen({ open: false, vipType: undefined }); + }; const handleSubscribe = async () => { // 获取选中的计划详情 - const selectedPlanData = pricingPlans.find((plan) => plan.id === selectedPlan) + const selectedPlanData = pricingPlans.find((plan) => plan.id === selectedPlan); if (selectedPlanData && selectedPlanData.productId) { // 这里可以调用订阅API,使用selectedPlanData.productId const query = { ...QueryString.parse(searchParams.toString()), back: 1, - } + }; - const baseURL = `${process.env.NEXT_PUBLIC_APP_URL}${pathname}?${QueryString.stringify(query)}` + const baseURL = `${process.env.NEXT_PUBLIC_APP_URL}${pathname}?${QueryString.stringify(query)}`; const response = await mutateAsync({ subProductId: selectedPlanData.productId, returnUrl: baseURL, cancelUrl: baseURL, - }) - const { payUrl } = response || {} + }); + const { payUrl } = response || {}; if (payUrl) { - window.location.href = payUrl + window.location.href = payUrl; } } - } + }; return ( { @@ -126,7 +124,7 @@ const VipPage = () => { )}() + +export function usePrefetchRoutes( + routes?: NullableRoute[], + options?: { + limit?: number + } +) { + const router = useRouter() + const { isLogin } = useToken() + const normalizedRoutes = useMemo(() => { + if (!routes || routes.length === 0) return [] + return routes.filter(Boolean) as string[] + }, [routes]) + const limit = options?.limit ?? Infinity + + useEffect(() => { + if (!normalizedRoutes.length) return + let count = 0 + for (const href of normalizedRoutes) { + if (!href || prefetchedRouteCache.has(href)) continue + // 如果未登录且是受保护路由,跳过 prefetch,避免缓存重定向响应 + if (!isLogin && isProtectedRoute(href)) continue + prefetchedRouteCache.add(href) + router.prefetch(href) + count += 1 + if (count >= limit) break + } + }, [limit, normalizedRoutes, router, isLogin]) +} + +export function useGlobalPrefetchRoutes(extraRoutes?: NullableRoute[]) { + const { data: user } = useCurrentUser() + const isAuthenticated = !!user + + const protectedTargets = useMemo(() => { + const routes = new Set(AUTH_PREFETCH_TARGETS) + extraRoutes?.forEach((href) => { + if (href && href !== '/') { + routes.add(href) + } + }) + return Array.from(routes) + }, [extraRoutes]) + + usePrefetchRoutes(PUBLIC_PREFETCH_TARGETS) + usePrefetchRoutes(isAuthenticated ? protectedTargets : []) +} + +export const GLOBAL_PREFETCH_ROUTES = DEFAULT_PREFETCH_TARGETS diff --git a/src/hooks/useHome.ts b/src/hooks/useHome.ts new file mode 100644 index 0000000..e8e44e9 --- /dev/null +++ b/src/hooks/useHome.ts @@ -0,0 +1,105 @@ +import { useInfiniteQuery, useMutation, useQuery } from '@tanstack/react-query'; +import { homeKeys } from '@/lib/query-keys'; +import { GetMeetListRequest, homeService } from '@/services/home'; + +type PageParam = number | { page: number; exList: number[] }; + +export function useGetMeetList(params: Omit , enabled: boolean = true) { + return useInfiniteQuery({ + queryKey: homeKeys.getMeetList(params), + queryFn: ({ pageParam }: { pageParam: PageParam }) => { + // pageParam 可能是数字(第一页)或对象(后续页面包含 exList) + const page = typeof pageParam === 'number' ? pageParam : pageParam.page; + const exList = + typeof pageParam === 'object' && pageParam !== null ? pageParam.exList : undefined; + + return homeService.getMeetList({ + ...params, + pn: page, + ...(exList && { exList }), + }); + }, + initialPageParam: 1 as PageParam, + getNextPageParam: (lastPage, allPages) => { + // 如果最后一页的数据数量少于每页大小,说明没有更多数据了 + if (lastPage.length < params.ps) { + return undefined; + } + + // 收集所有已获取的 aiId 作为下一页的排除列表 + const exList: number[] = []; + + // 首先添加初始传入的 exList(如果有的话) + if (params.exList && Array.isArray(params.exList)) { + exList.push(...params.exList); + } + + // 然后添加所有已获取页面的 aiId + for (const page of allPages) { + for (const item of page) { + if (item.aiId) { + exList.push(item.aiId); + } + } + } + + return { page: allPages.length + 1, exList } as PageParam; + }, + enabled, // 控制是否启用查询 + }); +} + +export function useGetChatRank() { + return useQuery({ + queryKey: homeKeys.getChatRank(), + queryFn: homeService.getChatRank, + }); +} + +export function useGetHeartbeatRank() { + return useQuery({ + queryKey: homeKeys.getHeartbeatRank(), + queryFn: homeService.getHeartbeatRank, + }); +} + +export function useGetGiftRank() { + return useQuery({ + queryKey: homeKeys.getGiftRank(), + queryFn: homeService.getGiftRank, + }); +} + +export function useGetSevenDaysSignList() { + return useQuery({ + queryKey: homeKeys.getSevenDaysSignList(), + queryFn: homeService.getSevenDaysSignList, + }); +} + +export function useSignIn() { + return useMutation({ + mutationFn: homeService.signIn, + }); +} + +export function useGetExplore() { + return useQuery({ + queryKey: homeKeys.getExplore(), + queryFn: homeService.getExplore, + }); +} + +export function useGetHomeAiCarouselList() { + return useQuery({ + queryKey: homeKeys.getHomeAiCarouselList(), + queryFn: homeService.getHomeAiCarouselList, + }); +} + +export function useGetHomeAggregateRecommend() { + return useQuery({ + queryKey: homeKeys.getHomeAggregateRecommend(), + queryFn: homeService.getHomeAggregateRecommend, + }); +} diff --git a/src/layout/BasicLayout.tsx b/src/layout/BasicLayout.tsx index 42ef571..949e444 100644 --- a/src/layout/BasicLayout.tsx +++ b/src/layout/BasicLayout.tsx @@ -4,10 +4,10 @@ import { usePathname } from 'next/navigation'; import { useEffect, useRef } from 'react'; import Sidebar from './Sidebar'; import Topbar from './Topbar'; -import ChargeDrawer from '../components/features/charge-drawer'; -import SubscribeVipDrawer from '@/app/(main)/vip/components/SubscribeVipDrawer'; +// import ChargeDrawer from '../components/features/charge-drawer'; +// import SubscribeVipDrawer from '@/app/(main)/vip/components/SubscribeVipDrawer'; import { cn } from '@/lib/utils'; -import CreateReachedLimitDialog from '../components/features/create-reached-limit-dialog'; +// import CreateReachedLimitDialog from '../components/features/create-reached-limit-dialog'; import { useMedia } from '@/hooks/tools'; import BottomBar from './BottomBar'; import { useStreamChatStore } from '@/app/(main)/chat/[id]/stream-chat'; @@ -60,9 +60,9 @@ export default function ConditionalLayout({ children }: ConditionalLayoutProps) {response && !response.sm && } - - + {/* */} + {/* */} + {/* */} - -+ + + ); } return ( @@ -75,7 +73,7 @@ function Topbar() { const rightDomRender = () => { if (!user) return ( - + ); diff --git a/src/layout/components/Notice.tsx b/src/layout/components/Notice.tsx index 319f609..a9ed8f9 100644 --- a/src/layout/components/Notice.tsx +++ b/src/layout/components/Notice.tsx @@ -17,6 +17,7 @@ const Notice = () => { // 监听路径变化,刷新通知统计 useEffect(() => { + return; if (user) { // 当路径变化时,无效化并重新获取通知统计数据 queryClient.invalidateQueries({ @@ -26,6 +27,7 @@ const Notice = () => { }, [pathname]); useEffect(() => { + return; if (isDrawerOpen && user) { queryClient.invalidateQueries({ queryKey: userKeys.noticeStat(), diff --git a/src/proxy.ts b/src/proxy.ts index 41cc5d6..62ab494 100644 --- a/src/proxy.ts +++ b/src/proxy.ts @@ -3,10 +3,9 @@ import type { NextRequest } from 'next/server'; // 需要认证的路由 const protectedRoutes = [ - // '/profile', - // '/profile/account', - // '/profile/edit', - '/create', + '/profile', + '/profile/account', + '/profile/edit', '/settings', '/login/fields', '/chat', diff --git a/src/services/home/home.service.ts b/src/services/home/home.service.ts new file mode 100644 index 0000000..0e83727 --- /dev/null +++ b/src/services/home/home.service.ts @@ -0,0 +1,59 @@ +import { frogHttp } from '@/lib/http/instances' +import { + AiCarouselListOutput, + AiChatRankOutput, + AiGiftRankOutput, + AiHeartbeatRankOutput, + ExploreInfoOutput, + GetMeetListRequest, + GetMeetListResponse, + HomeRecommendV2Output, + SignInRoundOutput, +} from './types' + +export const homeService = { + // 发现 + getExplore: (): Promise- - => { + return frogHttp.post('/web/explore/info') + }, + + // 首页分类列表 + getMeetList: (data: GetMeetListRequest): Promise => { + return frogHttp.post('/web/home/classification-list', data) + }, + + // 热聊榜 + getChatRank: (): Promise => { + return frogHttp.post('/web/rank/chat') + }, + + // 心动榜 + getHeartbeatRank: (): Promise => { + return frogHttp.post('/web/rank/heartbeat') + }, + + // 送礼榜 + getGiftRank: (): Promise => { + return frogHttp.post('/web/rank/gift') + }, + + // 七天签到列表 + getSevenDaysSignList: (): Promise => { + return frogHttp.post('/web/si/list') + }, + + // 签到 + signIn: (): Promise => { + return frogHttp.post('/web/si/asi') + }, + + // 首页AI轮播列表 + getHomeAiCarouselList: (): Promise => { + return frogHttp.post('/web/home/ai-carousel-list') + }, + + // 首页聚合推荐 + getHomeAggregateRecommend: (): Promise => { + return frogHttp.post('/web/home/agg-recommend') + }, +} diff --git a/src/services/home/index.ts b/src/services/home/index.ts new file mode 100644 index 0000000..5d0027b --- /dev/null +++ b/src/services/home/index.ts @@ -0,0 +1,2 @@ +export * from './types' +export * from './home.service' diff --git a/src/services/home/types.ts b/src/services/home/types.ts new file mode 100644 index 0000000..71be8ee --- /dev/null +++ b/src/services/home/types.ts @@ -0,0 +1,819 @@ +import { Gender } from 'nim-web-sdk-ng/dist/v2/NIM_BROWSER_SDK/UserServiceInterface' + +/** + * ClassificationListInput + */ +export interface GetMeetListRequest { + /** + * 情感性格code + */ + characterCodeList: string[] + /** + * 需要排除的aiId列表 + */ + exList?: number[] + /** + * 页码 + */ + pn?: number + /** + * 每页大小 + */ + ps: number + /** + * 角色code列表 + */ + roleCodeList?: string[] + sex?: Gender + age?: Age + /** 多选:性别列表 */ + sexList?: Gender[] + /** 多选:年龄列表 */ + ageList?: Age[] + /** + * 标签code列表 + */ + tagCodeList?: string[] +} + +/** + * GetMeetListResponse + */ +export interface GetMeetListResponse { + /** + * AI的id + */ + aiId?: number + /** + * 出生日期 + */ + birthday?: string + /** + * 性格名称 + */ + characterName?: string + /** + * 头像 + */ + headImg?: string + /** + * 主页头图 + */ + homeImageUrl?: string + /** + * 简介 + */ + introduction?: string + /** + * 被喜欢数 + */ + likedNum?: number + /** + * 昵称 + */ + nickname?: string + /** + * 角色名称 + */ + roleName?: string + /** + * 0,男;1,女;2,自定义 + */ + sex?: number + /** + * 标签名称 + */ + tagName?: string + /** + * ai所属用户id + */ + userId?: number +} + +/** + * AiChatRankOutput + */ +export interface AiChatRankOutput { + /** + * AI的id + */ + aiId?: number + /** + * 出生日期 + */ + birthday?: string + /** + * 性格名称 + */ + characterName?: string + /** + * 聊天次数 + */ + chatNum?: number + /** + * 头像 + */ + headImg?: string + /** + * 主页头图 + */ + homeImageUrl?: string + /** + * 简介 + */ + introduction?: string + /** + * 昵称 + */ + nickname?: string + /** + * 排名编号 + */ + rankNo?: number + /** + * 角色名称 + */ + roleName?: string + /** + * 0,男;1,女;2,自定义 + */ + sex?: number + /** + * 标签名称 + */ + tagName?: string +} + +/** + * AiHeartbeatRankOutput + */ +export interface AiHeartbeatRankOutput { + /** + * AI的id + */ + aiId?: number + /** + * 出生日期 + */ + birthday?: string + /** + * 性格名称 + */ + characterName?: string + /** + * 头像 + */ + headImg?: string + /** + * 心动总分值 + */ + heartbeatValTotal?: number + /** + * 主页头图 + */ + homeImageUrl?: string + /** + * 简介 + */ + introduction?: string + /** + * 昵称 + */ + nickname?: string + /** + * 排名编号 + */ + rankNo?: number + /** + * 角色名称 + */ + roleName?: string + /** + * 0,男;1,女;2,自定义 + */ + sex?: number + /** + * 标签名称 + */ + tagName?: string +} + +/** + * AiGiftRankOutput + */ +export interface AiGiftRankOutput { + /** + * AI的id + */ + aiId?: number + /** + * 出生日期 + */ + birthday?: string + /** + * 性格名称 + */ + characterName?: string + /** + * 礼物 + */ + giftCoinNum?: number + /** + * 头像 + */ + headImg?: string + /** + * 主页头图 + */ + homeImageUrl?: string + /** + * 简介 + */ + introduction?: string + /** + * 昵称 + */ + nickname?: string + /** + * 排名编号 + */ + rankNo?: number + /** + * 角色名称 + */ + roleName?: string + /** + * 0,男;1,女;2,自定义 + */ + sex?: number + /** + * 标签名称 + */ + tagName?: string +} + +/** + * SignInRoundOutput + */ +export interface SignInRoundOutput { + /** + * 最大连续签到天数 + */ + continuousDays?: number + /** + * 签到周期基础数据 + */ + list?: SignInListOutput[] +} + +/** + * SignInListOutput + */ +export interface SignInListOutput { + /** + * 得到coin的数量 + */ + coinNum?: number + /** + * PST 天(yyyy-MM-dd) + */ + dayStr?: string + /** + * 是否已签到 + */ + signIn?: boolean +} + +/** + * ExploreInfoOutput + */ +export interface ExploreInfoOutput { + /** + * AI总心动值榜单top3 + */ + aiChatRankTop3List?: AiChatRankOutput[] + /** + * AI总心动值榜单top3 + */ + aiGiftRankTop3List?: AiGiftRankOutput[] + /** + * AI总心动值榜单top3 + */ + aiHeartbeatRankTop3List?: AiHeartbeatRankOutput[] + /** + * 广告列表 + */ + outputList?: AdvertiseOutput[] +} + +/** + * com.sonic.frog.domain.output.AiChatRankOutput + * + * AiChatRankOutput + */ +export interface AiChatRankOutput { + /** + * AI的id + */ + aiId?: number + /** + * 出生日期 + */ + birthday?: string + /** + * 性格名称 + */ + characterName?: string + /** + * 聊天次数 + */ + chatNum?: number + /** + * 头像 + */ + headImg?: string + /** + * 主页头图 + */ + homeImageUrl?: string + /** + * 简介 + */ + introduction?: string + /** + * 昵称 + */ + nickname?: string + /** + * 排名编号 + */ + rankNo?: number + /** + * 角色名称 + */ + roleName?: string + /** + * 0,男;1,女;2,自定义 + */ + sex?: number + /** + * 标签名称 + */ + tagName?: string +} + +/** + * com.sonic.frog.domain.output.AiGiftRankOutput + * + * AiGiftRankOutput + */ +export interface AiGiftRankOutput { + /** + * AI的id + */ + aiId?: number + /** + * 出生日期 + */ + birthday?: string + /** + * 性格名称 + */ + characterName?: string + /** + * 礼物 + */ + giftCoinNum?: number + /** + * 头像 + */ + headImg?: string + /** + * 主页头图 + */ + homeImageUrl?: string + /** + * 简介 + */ + introduction?: string + /** + * 昵称 + */ + nickname?: string + /** + * 排名编号 + */ + rankNo?: number + /** + * 角色名称 + */ + roleName?: string + /** + * 0,男;1,女;2,自定义 + */ + sex?: number + /** + * 标签名称 + */ + tagName?: string +} + +/** + * com.sonic.frog.domain.output.AiHeartbeatRankOutput + * + * AiHeartbeatRankOutput + */ +export interface AiHeartbeatRankOutput { + /** + * AI的id + */ + aiId?: number + /** + * 出生日期 + */ + birthday?: string + /** + * 性格名称 + */ + characterName?: string + /** + * 头像 + */ + headImg?: string + /** + * 心动总分值 + */ + heartbeatValTotal?: number + /** + * 主页头图 + */ + homeImageUrl?: string + /** + * 简介 + */ + introduction?: string + /** + * 昵称 + */ + nickname?: string + /** + * 排名编号 + */ + rankNo?: number + /** + * 角色名称 + */ + roleName?: string + /** + * 0,男;1,女;2,自定义 + */ + sex?: number + /** + * 标签名称 + */ + tagName?: string +} + +/** + * AdvertiseOutput + */ +export interface AdvertiseOutput { + /** + * 使用端点(WEB/ANDROID/IOS) + * endpoint + */ + endpoint?: string + /** + * 扩展字段 + * ext + */ + ext?: string + /** + * 广告配图 + * icon + */ + icon?: string + /** + * 是否弹窗(1.是,0.否) + * is_global + */ + isGlobal?: number + /** + * 跳转连接 + * jump_link + */ + jumpLink?: string + /** + * 广告名称 + * name + */ + name?: string + /** + * 展示结束时间 + * show_end_time + */ + showEndTime?: string + /** + * 展示开始时间 + * show_start_time + */ + showStartTime?: string + /** + * 排序 + * sort + */ + sort?: number +} + +/** + * 年龄:单选 AGE_1(18-24)、AGE_2(25-34)、AGE_3(35-44)、AGE_4(45-54)、AGE_5(>54) + */ +export enum Age { + Age1 = 'AGE_1', + Age2 = 'AGE_2', + Age3 = 'AGE_3', + Age4 = 'AGE_4', + Age5 = 'AGE_5', +} + +/** + * ai轮播列表输出 + * + * AiCarouselListOutput + */ +export interface AiCarouselListOutput { + /** + * AI的id + */ + aiId?: number + /** + * 出生日期 + */ + birthday?: string + /** + * 性格名称 + */ + characterName?: string + /** + * 头像 + */ + headImg?: string + /** + * 主页头图 + */ + homeImageUrl?: string + /** + * 简介 + */ + introduction?: string + /** + * 是否点赞过 + */ + liked?: boolean + /** + * 点赞数 + */ + likedCount?: number + /** + * 昵称 + */ + nickname?: string + /** + * 角色名称 + */ + roleName?: string + /** + * 0,男;1,女;2,自定义 + */ + sex?: number + /** + * 标签名称 + */ + tagName?: string + /** + * ai所属用户id + */ + userId?: number +} + +/** + * HomeRecommendV2Output + */ +export interface HomeRecommendV2Output { + mostChat?: AiChatRankOutput[] + mustCrush?: AiHeartbeatRankOutput[] + mustGifted?: AiGiftRankOutput[] + starAChat?: StartChatOutput[] +} + +/** + * com.sonic.frog.domain.output.AiChatRankOutput + * + * AiChatRankOutput + */ +export interface AiChatRankOutput { + /** + * AI的id + */ + aiId?: number + /** + * 出生日期 + */ + birthday?: string + /** + * 性格名称 + */ + characterName?: string + /** + * 聊天次数 + */ + chatNum?: number + /** + * 头像 + */ + headImg?: string + /** + * 主页头图 + */ + homeImageUrl?: string + /** + * 简介 + */ + introduction?: string + /** + * 被喜欢数 + */ + likedNum?: number + /** + * 昵称 + */ + nickname?: string + /** + * 排名编号 + */ + rankNo?: number + /** + * 角色名称 + */ + roleName?: string + /** + * 0,男;1,女;2,自定义 + */ + sex?: number + /** + * 标签名称 + */ + tagName?: string +} + +/** + * com.sonic.frog.domain.output.AiHeartbeatRankOutput + * + * AiHeartbeatRankOutput + */ +export interface AiHeartbeatRankOutput { + /** + * AI的id + */ + aiId?: number + /** + * 出生日期 + */ + birthday?: string + /** + * 性格名称 + */ + characterName?: string + /** + * 头像 + */ + headImg?: string + /** + * 心动总分值 + */ + heartbeatValTotal?: number + /** + * 主页头图 + */ + homeImageUrl?: string + /** + * 简介 + */ + introduction?: string + /** + * 被喜欢数 + */ + likedNum?: number + /** + * 昵称 + */ + nickname?: string + /** + * 排名编号 + */ + rankNo?: number + /** + * 角色名称 + */ + roleName?: string + /** + * 0,男;1,女;2,自定义 + */ + sex?: number + /** + * 标签名称 + */ + tagName?: string +} + +/** + * com.sonic.frog.domain.output.AiGiftRankOutput + * + * AiGiftRankOutput + */ +export interface AiGiftRankOutput { + /** + * AI的id + */ + aiId?: number + /** + * 出生日期 + */ + birthday?: string + /** + * 性格名称 + */ + characterName?: string + /** + * 礼物 + */ + giftCoinNum?: number + /** + * 头像 + */ + headImg?: string + /** + * 主页头图 + */ + homeImageUrl?: string + /** + * 简介 + */ + introduction?: string + /** + * 被喜欢数 + */ + likedNum?: number + /** + * 昵称 + */ + nickname?: string + /** + * 排名编号 + */ + rankNo?: number + /** + * 角色名称 + */ + roleName?: string + /** + * 0,男;1,女;2,自定义 + */ + sex?: number + /** + * 标签名称 + */ + tagName?: string +} + +/** + * com.sonic.frog.domain.output.StartChatOutput + * + * StartChatOutput + */ +export interface StartChatOutput { + /** + * AI的id + */ + aiId?: number + /** + * 开场白语音地址 + */ + dialoguePrologueSound?: string + /** + * 头像 + */ + headImg?: string + /** + * 被喜欢数 + */ + likedNum?: number + /** + * 昵称 + */ + nickname?: string + /** + * 主动聊天内容 + */ + supportingContentList?: string[] +}