From 260f8378dab28ee5a764c9b6c36cf59a79c07fd3 Mon Sep 17 00:00:00 2001 From: jinchao <383321154@qq.com> Date: Wed, 18 Jun 2025 11:52:05 +0800 Subject: [PATCH] =?UTF-8?q?V0.27.0.250618=5Falpha:=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E7=94=A8=E6=88=B7=E4=B8=AA=E4=BA=BA=E4=BF=A1=E6=81=AF=E9=A1=B5?= =?UTF-8?q?=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Login/login.cpp | 154 +++++- Release/database/UserInfo.db | Bin 16384 -> 32768 bytes Release/database/XNSim.db | Bin 1224704 -> 1224704 bytes XNSimHtml/assets/icons/png/cancel.png | Bin 0 -> 3991 bytes XNSimHtml/assets/icons/png/mail.png | Bin 0 -> 3938 bytes XNSimHtml/components/profile-center.js | 727 ++++++++++++++++++++++++- XNSimHtml/components/user-info.js | 31 +- XNSimHtml/routes/auth.js | 28 +- XNSimHtml/utils/xnCoreService.js | 22 +- 9 files changed, 918 insertions(+), 44 deletions(-) create mode 100644 XNSimHtml/assets/icons/png/cancel.png create mode 100644 XNSimHtml/assets/icons/png/mail.png diff --git a/Login/login.cpp b/Login/login.cpp index 2d18852..0a7e92a 100644 --- a/Login/login.cpp +++ b/Login/login.cpp @@ -15,6 +15,8 @@ #include #include #include +#include +#include using json = nlohmann::json; namespace fs = std::filesystem; @@ -52,35 +54,49 @@ std::string encryptPassword(const std::string &password, const std::string &salt // Base64编码函数 std::string base64_encode(const unsigned char *data, size_t length) { - const char *base64_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; - std::string ret; - ret.reserve(((length + 2) / 3) * 4); + BIO *bio, *b64; + BUF_MEM *bufferPtr; - for (size_t i = 0; i < length; i += 3) { - unsigned char octet_a = i < length ? data[i] : 0; - unsigned char octet_b = i + 1 < length ? data[i + 1] : 0; - unsigned char octet_c = i + 2 < length ? data[i + 2] : 0; + b64 = BIO_new(BIO_f_base64()); + bio = BIO_new(BIO_s_mem()); + bio = BIO_push(b64, bio); - unsigned char triple = (octet_a << 16) + (octet_b << 8) + octet_c; + BIO_set_flags(bio, BIO_FLAGS_BASE64_NO_NL); // 不添加换行符 + BIO_write(bio, data, length); + BIO_flush(bio); + BIO_get_mem_ptr(bio, &bufferPtr); - ret.push_back(base64_chars[(triple >> 18) & 0x3F]); - ret.push_back(base64_chars[(triple >> 12) & 0x3F]); - ret.push_back(base64_chars[(triple >> 6) & 0x3F]); - ret.push_back(base64_chars[triple & 0x3F]); + std::string result(bufferPtr->data, bufferPtr->length); + BIO_free_all(bio); + + return result; +} + +// Base64解码函数 +std::vector base64_decode(const std::string &encoded_string) +{ + BIO *bio, *b64; + std::vector result; + + b64 = BIO_new(BIO_f_base64()); + bio = BIO_new_mem_buf(encoded_string.c_str(), encoded_string.length()); + bio = BIO_push(b64, bio); + + BIO_set_flags(bio, BIO_FLAGS_BASE64_NO_NL); // 不添加换行符 + + // 计算解码后的长度 + size_t decoded_length = (encoded_string.length() * 3) / 4; + result.resize(decoded_length); + + int actual_length = BIO_read(bio, result.data(), decoded_length); + if (actual_length > 0) { + result.resize(actual_length); + } else { + result.clear(); } - // 添加填充 - switch (length % 3) { - case 1: - ret[ret.size() - 2] = '='; - ret[ret.size() - 1] = '='; - break; - case 2: - ret[ret.size() - 1] = '='; - break; - } - - return ret; + BIO_free_all(bio); + return result; } // 利用用户名和固定密钥派生32字节AES密钥 @@ -910,4 +926,94 @@ extern "C" LOGIN_EXPORT int deleteUser(int user_id) } catch (const std::exception &) { return -1; } +} + +// 更新用户头像 +extern "C" LOGIN_EXPORT int updateUserIcon(int user_id, const char *icon_base64_buffer, + int icon_base64_length) +{ + try { + if (!icon_base64_buffer || icon_base64_length <= 0) { + return -1; // 无效的输入参数 + } + + std::string icon_base64(icon_base64_buffer, icon_base64_length); + if (icon_base64.empty()) { + return -1; // 空的Base64数据 + } + + const char *xnCorePath = std::getenv("XNCore"); + if (!xnCorePath) { + return -2; // 环境变量未设置 + } + + fs::path dbPath = fs::path(xnCorePath) / "database" / "UserInfo.db"; + + { + sqlite3 *db; + if (sqlite3_open(dbPath.string().c_str(), &db) != SQLITE_OK) { + return -3; // 数据库连接失败 + } + + // 首先检查用户是否存在 + sqlite3_stmt *stmt; + const char *checkQueryStr = "SELECT id FROM users WHERE id = ?"; + + if (sqlite3_prepare_v2(db, checkQueryStr, -1, &stmt, nullptr) == SQLITE_OK) { + sqlite3_bind_int(stmt, 1, user_id); + + if (sqlite3_step(stmt) != SQLITE_ROW) { + sqlite3_finalize(stmt); + sqlite3_close(db); + return -4; // 用户不存在 + } + sqlite3_finalize(stmt); + } + + // 将Base64字符串转换为二进制数据 + std::vector icon_data; + try { + icon_data = base64_decode(icon_base64); + } catch (...) { + sqlite3_close(db); + return -5; // Base64解码失败 + } + + if (icon_data.empty()) { + sqlite3_close(db); + return -6; // 解码后数据为空 + } + + // 更新用户头像 + stmt = nullptr; + const char *queryStr = "UPDATE users SET icon = ? WHERE id = ?"; + + if (sqlite3_prepare_v2(db, queryStr, -1, &stmt, nullptr) == SQLITE_OK) { + sqlite3_bind_blob(stmt, 1, icon_data.data(), icon_data.size(), SQLITE_TRANSIENT); + sqlite3_bind_int(stmt, 2, user_id); + + if (sqlite3_step(stmt) == SQLITE_DONE) { + sqlite3_finalize(stmt); + sqlite3_close(db); + return 0; // 更新成功 + } else { + // 获取具体的错误信息 + int error_code = sqlite3_errcode(db); + const char *error_msg = sqlite3_errmsg(db); + std::cout << "UPDATE icon failed with error code: " << error_code + << ", message: " << (error_msg ? error_msg : "unknown") << std::endl; + sqlite3_finalize(stmt); + sqlite3_close(db); + return -7; // UPDATE语句执行失败 + } + } else { + sqlite3_close(db); + return -8; // UPDATE语句准备失败 + } + } + + return -9; // 其他错误 + } catch (const std::exception &) { + return -10; // 异常错误 + } } \ No newline at end of file diff --git a/Release/database/UserInfo.db b/Release/database/UserInfo.db index 512d0d6ff355b25a25672bf3cd4da1619605d8c0..2b0b402b232b4d6dbc6176d9dd5262696f2b2d63 100644 GIT binary patch delta 10277 zcmZ8{cQhPt*!HYmW0&ZC)zw>cAxMZ81Ywm#38JqSs|``ILPU=)KT#s0_YwrrOGxzI ziRe*6@J-%-zVp6&&g`5y^Xzl>nYo|qzOL)OLqcF7A$W1jgbV-xki>H+!x$=_yra57 z2wlRcE}(CKi0YyWA+|~tuTOzT2+QT*AfxjA0S(}$;PkvZ5E$U<<*#h-@6E6vXGno@Ap+;aVMprQ&kQRm_Zhn z%yrozDPS@VS3t^HaL@{r|7=yGOraU^1WZN3O^-zpN*fnfhuPWLYj2dVWUsGnyyJ*p z$!Ny(^1tnOCk(L!o;>CxxXzPvLn5#Kn3b4Zc%{D}H#=eD2W&R0JR6#^MMl#GBo)Mn zgH~5|z@rSlJnsKF*uBIg;L{EjD8we#2mL>^5ZQ#!b3%X!|N3{y+HsX82}Tq#w~d`= zbvAojiVcLt5k1^#6midqb?!Jpl|+0sFV|KazA!`Mk;}bS(g*BPA6rb!aT0WCNK`&4 zB@aD3irLJNyG0&C!kL#eL&kX<6)wx|C3z+qQFc=`kQ@PK8C)uu?398;xTHfRPO;d{ zNyf^DzXCY*gPprg^kt4zCh*izJ`xDaxbOKGB+@GL;Y`<$Po)IVa0yOzt9d3$dI}&= z%{l0AYH0z>?N7x3G9Q}vk^rb3TC$Lq*eYFYT2*fB9Is8Cx&_1=9;*JSfZN!ZD^4}2 z=3rPHUOLsBUkn+86<)Wt$wD+y4-l%EGLhfKWQEp$nq9dE`!c+(87XHd$*vfU!ubmC z>i+ti6#$I`Il|BCKRd_BnUHY0@#W5F=Z|u+_~CG%_|m#CJF)Ee_OUSVL%T)l zc@=zFO%dBB3D>a7bXWspS(FxEPKyo*U_`09j5Y?@%}Hu5U%R+ zw|w#O4)e1>r_Wqsd+W!F4RT=jaf9a?oAz6gAfDPdal2WUszKh@?8;Xr$a9s1Zu!`% zqv`V&t=v=7{dGK!((PSBKv{QL42fRETA3WGjEjx=%xwct+$=6&i=^EcU!hm`Voty( zB2eHo8t0(ijVF*GP50$Tl%pn^~MoDzpj7U}@y zP2=Y$iaO!>xh~Izly9wv# z?Uot;f-{W>x7|n#Ya7G-RZ)Ko?kn!~WBe4dx_#y(fkkcc<5?JjzH4=m)qEUwNDkn< zUU_y76@CDT`v}gH&84LGc>1JL+^F_ zyk+<$J;rVBRHKtw!q$bEc2GivEhe6E6Ti&_?JUxsa`9i%Pp;gBPLQO@42M%#oxejw zWKRt}H8$3pQS0V*#dp*f_7%&D534OFvZ#QZTF)dnQu9#0@u8Zli|c*WocqvTaHmt9 zB%1zwPu-m8ZD^IM@D=Nfr`D!n1LsG(CpkAHSPbPI2jlRl!Cg(Sb(E#=i z$pJ6<+sl%|!tS8oBN{nXi@mH{nQ~szEG#U)!{HBwVge8#V!`E3j>4!8jQiJwHM5Kl z4Ki5vxg>oD2WstgtuQOjvCeH*BLM}4>^e>SSp3se#PUPv4QaIIQo!A(W04O$fOcppEhoL1a0tXdu&PP|21qzxBeT zB_S-!X2@(a4bmn^rdrs;&Xo_U|3%}TBby*rLgdbSa{kBvS$Pa`tNsm0NOttgViF>t} zm(;Tqpxx(VtxQb{ZueBtUE$%mTdtSog!ITK=Z=Lc0x*s*9KI)Q*#qozl^l2LY&AuR z+#9=C;9M@4TYW!PYfs9N33H^W47!b+4y^JX{Hu*>NQ=|8J95U=yG_VCrv8@(;0&FV zkp8{)FJ3YKUY~!3ec#eDe*QE=t_R0VrC(%p zV?Su&C#ctpVKFW-5g!hIW@pX|UbS zGyv*tKl4koaOvB7=XN>vL$4CO>FnvRzS!d(s$`Ic3}h+PRr?*-6yFX3_V1SX%q~B} zrp^>dLm;Q3e(h|#`pK-SOW3shom)pD@w5&AhO-Li|AjzaxIm9DS6gXVVLYiNW7A^d zQW{{fwLW?bKPaL5-M~3Wtx}&dMn{(|ijKL3G5OYUK*!n1M%NNZfVdFa^ioUb3Guk5 z%8-z{`ys$9WHPdOAqE$)VLxWhknrzL?23*nS1!HcaE6oCg06Sz_H8Oax?%k-2Z%gn z-)f9!4CyQ~)l+vrR2=^KLVQoK&1dcyws@CW;mCqixsNIhhBN%>fvo&ELIAvUEGc{C zsVZ}6h4K+YVC6eFQ=QI74_X*b7C?bl;f;h=DX+dOJb?J66A>g7gZ{b1t*66*}DnPcIvU|!M%DQB&z@(7?}-C+b)GaAHzKLQyc>I zKlQyP{!Ef+lfvsN%n!A#@8Cr1n{ccCxw5w@Yl4NzptySJ2e3&Q7}z)I`i7EE=AI# zx<~K>$@_tCAI)0;;d~ley>of>^S@%a6O;3U>bDy;U~SJ6Ci|Za;Od=K85^DsQ>yqy zCK<9*NUr-Q3@`u|A8KNK0y9?|q?@&h$}rKy%-KZMt;8WMpWK9LrrkQT1w+C){xBfG ze1qO_<-gc%Yhzev&o(J8FKJeApMbN|gtYUMjc~5dd!Me*4YIlY`bD)PEJnPh=FU?Q@o0(mt~x}e{AQT zEBz(eNUg&Eo{mMHCv0Hv>s^i-=C6Qjt3(_@K|$`O5S^-M*^`u=p7Juem3 zzU~iW0}aGI`15_K6u8Zj%X{a3F*2#ED`2y`?=ru#-U=OnscT^i;LD)U&M%Y01yarv z(pux_rZL$4ogoSpi-{~s5O5&p^nKtYg}~e&&9jkD&%u>GXW2+yrWTu$dTNslu_|wp z8yN+ny^4vhyU1FB{6E`{Auf$u1@5~$J4zWV?+q*K;u%VPv)_{KHz(V8c*vC6Jv3w8hSr@KDdEyMejU`lZ{IIz99ms965RS9;| z8t%V7QPIFt*Xm_o`YS#kd%(SbdypmXbJZR}{&?^6ZLjr#Er{q73>nj_a+CSGyd2fF zyWzQu{cM#}XV;rh*uCFidGsL`Pt&w0oScaJ3hpiz)|{NKEG5+$UG(ft3&cb2eztOW z=k5KB1KF5sBrsSEniRkB9(1xKkMyPZhcN50D;9%h)u;<={>@GKoCPM_Szghv@+k^F zgt)0I`H!^QT`8q(>Id^g06o2KsEZ~2n-(sBTB>#j&_=UD zYzM}Gec8=e6^A;oV9nTew~p<%Y}s3AfHGw14YL^WL$D1^4CR*3>Ya<42@jM;EoCC( z>x$;thX3VJXiZ8{jt@8q9F{PtnhHcOi)Ud#-9V@(ObwTW5hOG@*;-MfBF;Yb5jtR(_yWhr76@yYqGQ6NhguR!EXsBNtd-PaoWveh} z7gAWz02MW=7{-4J4A!#uSTNipVSo^!WFU~NS7uT?!59BuJpe+^PZYEg$$${D4 zD6QoA(ADzJNFd9i#N-b{HA4jP^loS^zTL%69sB8WwCGg~j=IL$-h%)vA-g^5`{pJU z!&>Op_reFMvY6#BsyMY5@V8rln9?L~j+*)-URU=2k_=n+*r|YSg2T74whriNSE5UN zAMokioq?2SvnEOWhh1k_Y)zFBw2rfuYXmUaI`W+BlyhmJn=mRVf$L`dT>A{OX^%0x zXxYTJ%!E@-_joLEIOr&-(ym^poyhPgge&Uw7gjL2ud!thly<})d>2PV9UOxrX886R z8E@!$)u)3$--Orb3_l1UsxC^XrsS|DAqAi7yeh`PV(YV`yXjMIDe>I9F_cnUcb%;D zlzHK_At!3Eo)8$CKrc9SB!$-s15^Km;_>g_VzM?e1*kSK-VVLv*YUNlZL#iNCWa1) zUfbhFwZMSK5DNUQaFm*+gYZQ}Hny~U5@Y8g5HU6cC zI_5yQx|Z(nYq|M*XOEvVliI0Q7Tka|3>bIx5<825ArBu{*;J3gcI7jjD_F*`hKDr?hmQWf%oeI)NU3 z-7$mXqy2}-T)8vL0))Vx(<8yqdik~2!#;EFFYCrIIu|g+XAx~C9JsbMXl@1abSD{?-`ZF4`^8phH9lBOt`707sMwX+wAIad z4^Jy)iEO2S6RCyMYe7BHuB)r7-e*5Zv?<{EKPuEa3pvNbaJM(GY;XUd?lCL9OG>}5 zE=~C94|3NW*E2FvMm6E%0Du5}r`v?z;OXVDQ_XbW5>=d`8#~>}%<%6oUzF0BWeDug zB&lKH+F`Mfw=kiKd!8ZCl70hdDXZDtoPJmhW{ilFhs%7KhoZV%gi)L1*&9J}lxyUSzHD)Z z^NyA8V2i3H$GyCxa&6*gB1=uQr8M@ErChoFO2bVZ;rF$A5NKWuC#dk@Kg=QroQp(6)-ys}8oL^VGtaJo(WQSFKzs6P8OrRbe|88I?e_u=KFt_=j1t!ZbKn%B%-^ zx!nR5!_eEc;k>3OL-?CR6@A~A9pOyllnhKvkUs$UYZU%glIm4)=|+eu44g4@+ut~N zj;P2I&WwJ_{vE%{e$d~%$Y?p3n^I#|3EB_a3b9qXknPxp$uyR`-Lj|rSRnaI-fg!2 zB|GH3^jyTmlSRw{NYR@?3*bVbN%4V$ic<#(hqV+B$Ztl8obEkpHfmBdL+Qi2r>zhp zW7!*KJSLa|8@O#9F@p8x(uuj;b4L%4eff+41d2z->a`YPox8+xl*G`&H4LGm6FbEu zyLLxC#`2`lbjFu$G46y`&_vN80pA+_wuTQm@05E-|L@LwYyQR^@T>U3M{ul~A@i>D zzB14@h2D0T8O>2U340K6Q1IV=Jg_t>M#HPKXz}8Cs;mox?$1A8QXp77EC)J@I`rtV z)?T#j2G|XjCqFN)#7iW|<%F!9SIZIVFHjLaFii@d7hM z|Id1^j!U%qXvQ9>2bHu?L1K*QmDkF57TpPFZ73bY^5tSv(uKBnc7#RI)sEj1$~fxw zL$({QK~msx)stuUwbYyLuGtfSVscO7@QG*SkncThOZyAti4on8f1p9s5Oeqi=^-j& zP?ieu=Y0i(>+g`H1n1FUf!7+T1iU5!$)Cd%-AlVBPpqlawi|a#0gnK!K8q+Ed)+^W zb+$9SiIkD58#52R3U(=beiB=38+XXC%05_z+Z0RexQWWwy#aR*mHoAHP@W;~9$jx1 z`)p>fr;cQXt~22?k1YRLBOJMP>h|3zrojA@XM#EJ8TaS2jeH?iOPrS! zD8y99^fXyg^}SxPiJ(Jjd8ZOL`*QnQ_Qs`Sp0#1$bHas63%VbhH~TP|K_ACHj>v#? zIqR^tJES*+tnPJ2zKzp`*wkOUZ{9~4DUyC{qqC42tn}CvOyt$|@5iT7Y-OEpXgD58A#BM^h9ihBg# zdnE|6G@W(x$>PN3*K-QGBv=tjh|C}_;{R-7LW%3$?|lZXKwqJG4o(%n=&vJfgIz0- z_MJ&v%?&@Szwc~UTn#18gJ z;I~1_HyYy^PWB`g-)d3TO1ZB%?!gEZd-9i;$1C&&fp=grO*!+NqF_khPOUgfNGE$0))29E(h34f= zDPOj7N8ayR`_Ag#A2+ZpjI*Bn`9n{q`5O_fk1hXBe@uJ*j5H^8s`R}MNA8T$@XY6U zSMW99`5A5ll4~fw!7-bDqt8S6si2NqXPtQn4TO*q_)-+^OSt)3_O8^Mq1uzafq63` zs-2NFATumnc~@3(%JC+j={+~CEPj$}St-rCnpIMctFC?x7Pkj5nMU$5H6EIFx8su} zOh{+#hq-ef1Y}Q$iPa%g&hVTBzca4E!Hq?4JLZSyFGN_W!Xs$d^fabh^9YR9PR7u6 zIc@EdAj=qgD+_B`jq(R=h`xE$v zS~EKyNqDotX&nl|Gq3bfJ^`6=hb9ub;|2*SE5g}x)Jd=BIap|}Yxy6PUVXm*2sCjqoOnz(HjGvq0Ge1N|Y{fUlvPeI=eR)C;s*+lEyC$_O3 z2f_SO=?@qEE}8tkz9lAAmfe(tmiQczX--^(w9jgJ1&h=i(YFQ}3v%yuzAEXnjz4Ok zxQR?B1ByM@!ye{tz(R@}mD_8YT^>! zsYTL;!(6)LaApQmFb)EOyrjSM&1<#%B`z&e1LCt*jK-=JV-A#%CvS%gpQJ*(K8dXD zqy4>e5R!DLpo)8szW+dwo{N%PM_zEI%Ps9^4Uvk?2@FMcE7iYAZBu8R5xD)SCYC~< zg*;Jb+P%~Dj3)oOM|gZlz0KfQmTI&0S60M{u9ea3@bh6%wJ{I>X#ZeX+}iEpnl^3A3I+&af0xzA#yY7Z!_gF|&<+x?GRPftj5(Q`9PIY5?sz zU(A>|4}za%$Ir!3nXXJocV`_K-2BbcOe@qanawL&v!E_aX5FLWu@B9sx0=7el7KeK zptF;!9iSY6_;V*>s4xN!Qb^p8+j^Cli0<-#?Tvw?@4#tLEW$-@If?yMf*HWwrzUs) z872FIYPlPZi7ndtOW$Hu#!mfXV@+E%@jwj;HqIU?t_<~R6=}Q>>fz72V!COu(1b3z zm#i)^f}z!g!EGNs%_z4Jao?4_A==Z^@}giNnN)tZ)RVXHQa;wJwAVxgvMQcNh)AbK zI6*lsS5gX!h9MYKn) zCl^4sQwj5Y$9>c(`JIN(OWt|Z;F1B*`EACGSMpP*>}aQR*s?RdU-qd#+Fd_7T5XNX zOD5qAA}cLWJ=x!(3W#Gd9P&F#OF|8|9QFz3(15D@!vxyabwnDy zvr>Eq{4Z!}n#7p#+FLuj#%^kueqB0vgH8$4T25srVs)vDo$>l6#Wkw_1&@Q*s*iLK zsszkK+^-(bz9TaESt=<&g&27owTaSuo~4MD=)8aNx~QMLW$!dCk36IA0T(-W@*B>F z5#)`lke;=InowjLz&r!$A(t_^3Emx0EfCACTHK{ZA2-2J6cI=%yL%SM{=cQqF+g`aqgHlZ@Lec}jZ<*Ydp-MM5kwltz3Q z5E$n$89t*8o^DJnRDHBa;qweHnhTUqaV#bV{#trRg08Uvc`rIh#sQML;h?1mdLmbX z@M;T_Ykl=*9wPTIm2^sC0Vlt7gBFw{9&pioz|bPuY&vp0GET{WOc~5_=h4iZaf5-S z&=9}NluKS37jAQl<4wqnMj@K|d)EOr?>Ojsi-Tkjk!V7Q*?FY3qo+R0-QCzq1hC}R zwDhfmQ(8q~QMlF!mu@Ox@$bDsHy6URZ*TA*+Bp84lb1@sF(Ci|>Z^9FO9WuNL9db$ zw&x}E(bAI^mnc~u2}wSDpiV<<&K^@YbJX3a(!(DzkbcWK(Xc4#j7VZj zo%Zjb(&EJxWQDvwU2tWb-M5E0Du|Tl0D-6dodP01>+Bwn2DJuJs$Xm;KRYLbyZ!pLoB-RyQD$ub>o<<$K z$i{9=CNpby1FEF_xL%Lpn8^B$b%wPFv9fUK;)}8!wli@g+qaWKiU5EgFcNp%^KHn-BSxGw}%RgctFbn0!^nSb=am1$9OKKpZLE+ zsi?*cP&s|1OP-lgAg;)-D^AdGiSX>1q~_q%6roDkrRx802P^i>R{gIn8^x9pGZID} zoM>7XxoZ@5luN%Lhs~xh7S*r<1I6fk8DQbGq#600QG@K{3XKPh@wzfiBdBLHE);Qa z?%Dq~s>KJ$HD~6VdoE~^o)aHzs;BTx0JDK@Ig6ij~5xOv-wCSc<07^jweUTn1?skgbCYnKe%T(n8z5Xyv9q298 wCMzbR`aAPK?P>b|t3Azz@+KQ@BlMd1%i(qgI%}`>pC@qtuAX|S>XR4$4@CtmGXMYp delta 113 zcmZo@U}|V!oFL6A#K6G7GEu>vQD|er0)8G|o(~LcT(cPXZMbGl=5lb{IKhL96)1Lx zVX|iY_08{N>KR%1(io&BI*Lv9i&;FmAZ{lM(1dFf8!rhoIx_Myh&S>!@@n(*Yil!1 R*7KOkXfXMlhuk6s1po>uAe8_B diff --git a/Release/database/XNSim.db b/Release/database/XNSim.db index 7bfd0594abd54d56295553f9b3658e16bc982d85..5fae359876abb9c603932b6490faa8ec473969c9 100644 GIT binary patch delta 278 zcmZp8;MMTJYl1Z6nTayajAt4XS`!$zCNM2|&+Ni0H=X?hQ!W#)-1d?WOe=V}xtJvx z4fKr6^$c9vHUBaJF*6Xe05K~NvjH(X5OZwT{L6V;h*^-?emc7Vmm(9h{boi1QReza zMi&lAMR8frdU1#;Mg~Tvx&~&th8BrA1sREkhE}EqRwfqDrfqn-Xx{TFE1q?1f3joM zlU=)>>|gk-asA7MTVKvygemg0fBlo)OP}mt_iW0}r%SdxZSH?Mspo0ywx{cwpU&U) rd|?AbJ8NP}Zf0KlVPP&H<_2OOAm#;PJ|N}?VgVo)+4`GVjHeqDS`!$zCNM2|&+N>rIi39jQ||V%4@@g~CI;|Mm;1}a z-fr-h35c12m<5PgftU@5LE;?S4gPXo5n>i#K0KXWfJ<>RqrhzD_QS$lK+FxqJV49~ U#C$-^55xjMEV%u!u#oK>0PJHjumAu6 diff --git a/XNSimHtml/assets/icons/png/cancel.png b/XNSimHtml/assets/icons/png/cancel.png new file mode 100644 index 0000000000000000000000000000000000000000..9862b73471b5286d05c234363fccd07a792e124d GIT binary patch literal 3991 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 JCst<({{?c`I9&h$ literal 0 HcmV?d00001 diff --git a/XNSimHtml/assets/icons/png/mail.png b/XNSimHtml/assets/icons/png/mail.png new file mode 100644 index 0000000000000000000000000000000000000000..ca3fe65537b357c4ce72498e6be3b58516d54c59 GIT binary patch literal 3938 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 literal 0 HcmV?d00001 diff --git a/XNSimHtml/components/profile-center.js b/XNSimHtml/components/profile-center.js index 1e00315..56e57f5 100644 --- a/XNSimHtml/components/profile-center.js +++ b/XNSimHtml/components/profile-center.js @@ -2,10 +2,33 @@ class ProfileCenter extends HTMLElement { constructor() { super(); this.attachShadow({ mode: 'open' }); + this.userInfo = null; + this.isEditing = false; } connectedCallback() { this.render(); + this.loadUserInfo(); + this.addEventListeners(); + } + + async loadUserInfo() { + try { + const response = await fetch('/api/check-auth', { + credentials: 'include' + }); + const result = await response.json(); + + if (result.success) { + this.userInfo = result.user; + this.updateDisplay(); + } else { + this.showError('获取用户信息失败'); + } + } catch (error) { + console.error('获取用户信息失败:', error); + this.showError('获取用户信息失败'); + } } render() { @@ -20,37 +43,713 @@ class ProfileCenter extends HTMLElement { } .profile-container { - background-color: white; - border-radius: 8px; - box-shadow: 0 2px 8px rgba(0,0,0,0.1); - padding: 16px; - height: 100%; - box-sizing: border-box; + max-width: 800px; + margin: 0 auto; + background: white; + border-radius: 12px; + box-shadow: 0 4px 20px rgba(0,0,0,0.1); + overflow: hidden; } .profile-header { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: white; + padding: 30px 24px; + text-align: center; + position: relative; + } + + .avatar-section { + margin-bottom: 15px; + position: relative; + display: inline-block; + } + + .avatar { + width: 100px; + height: 100px; + border-radius: 50%; + border: 3px solid rgba(255,255,255,0.3); + object-fit: cover; + margin: 0 auto 12px; + display: block; + background: rgba(255,255,255,0.1); + } + + .avatar-edit-btn { + position: absolute; + bottom: 15px; + right: 0; + width: 32px; + height: 32px; + border-radius: 50%; + background: rgba(255,255,255,0.9); + border: 2px solid #667eea; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + transition: all 0.3s ease; + box-shadow: 0 2px 8px rgba(0,0,0,0.2); + } + + .avatar-edit-btn:hover { + background: white; + transform: scale(1.1); + box-shadow: 0 4px 12px rgba(0,0,0,0.3); + } + + .avatar-edit-btn img { + width: 16px; + height: 16px; + opacity: 0.7; + } + + .avatar-edit-btn:hover img { + opacity: 1; + } + + .file-input { + display: none; + } + + .upload-loading { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + background: rgba(0,0,0,0.7); + color: white; + padding: 8px 12px; + border-radius: 6px; + font-size: 12px; + z-index: 10; + } + + .username { + font-size: 24px; + font-weight: 600; + margin: 0 0 6px 0; + } + + .user-role { + font-size: 14px; + opacity: 0.9; + margin: 0; + } + + .profile-content { + padding: 24px; + } + + .section-title { + font-size: 18px; + font-weight: 600; + color: #333; + margin: 0 0 16px 0; + display: flex; + align-items: center; + gap: 8px; + } + + .section-title img { + width: 18px; + height: 18px; + } + + .info-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); + gap: 16px; + margin-bottom: 24px; + } + + .info-card { + background: #f8f9fa; + border-radius: 10px; + padding: 16px; + border: 1px solid #e9ecef; + } + + .info-item { display: flex; justify-content: space-between; align-items: center; - margin-bottom: 20px; - padding-bottom: 10px; - border-bottom: 1px solid #e0e0e0; + padding: 10px 0; + border-bottom: 1px solid #e9ecef; } - .profile-title { - font-size: 18px; - font-weight: bold; + .info-item:last-child { + border-bottom: none; + } + + .info-label { + font-weight: 500; + color: #6c757d; + min-width: 90px; + } + + .info-value { color: #333; + text-align: right; + flex: 1; + margin-left: 16px; + } + + .info-value.empty { + color: #adb5bd; + font-style: italic; + } + + .edit-input { + width: 100%; + padding: 8px 12px; + border: 1px solid #ced4da; + border-radius: 6px; + font-size: 14px; + background: white; + } + + .edit-input:focus { + outline: none; + border-color: #667eea; + box-shadow: 0 0 0 2px rgba(102, 126, 234, 0.1); + } + + .action-buttons { + display: flex; + gap: 12px; + justify-content: center; + margin-top: 24px; + } + + .btn { + padding: 10px 20px; + border: none; + border-radius: 6px; + font-size: 14px; + font-weight: 500; + cursor: pointer; + transition: all 0.3s ease; + display: flex; + align-items: center; + gap: 6px; + } + + .btn-primary { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: white; + } + + .btn-primary:hover { + transform: translateY(-2px); + box-shadow: 0 5px 15px rgba(102, 126, 234, 0.3); + } + + .btn-secondary { + background: #6c757d; + color: white; + } + + .btn-secondary:hover { + background: #5a6268; + transform: translateY(-2px); + } + + .btn img { + width: 16px; + height: 16px; + } + + .permission-badge { + display: inline-block; + padding: 4px 12px; + border-radius: 20px; + font-size: 12px; + font-weight: 500; + text-transform: uppercase; + } + + .permission-admin { + background: #dc3545; + color: white; + } + + .permission-leader { + background: #fd7e14; + color: white; + } + + .permission-developer { + background: #17a2b8; + color: white; + } + + .permission-user { + background: #28a745; + color: white; + } + + .permission-guest { + background: #6c757d; + color: white; + } + + .error-message { + background: #f8d7da; + color: #721c24; + padding: 15px; + border-radius: 8px; + margin: 20px 0; + text-align: center; + } + + .success-message { + background: #d4edda; + color: #155724; + padding: 15px; + border-radius: 8px; + margin: 20px 0; + text-align: center; + } + + .loading { + text-align: center; + padding: 40px; + color: #6c757d; + } + + @media (max-width: 768px) { + :host { + padding: 10px; + } + + .profile-container { + margin: 0; + } + + .info-grid { + grid-template-columns: 1fr; + } + + .action-buttons { + flex-direction: column; + } }
-
个人中心
+
+ 用户头像 +
+ 编辑头像 +
+ + +

加载中...

+

加载中...

+
+
+
+ + + +
+ 基本信息 + 基本信息 +
+ +
+
+
+ 用户名 + - +
+
+ 真实姓名 + - +
+
+ 权限级别 + - +
+
+ +
+
+ 所属部门 + - +
+
+ 职位 + - +
+
+ 用户ID + - +
+
+
+ +
+ 联系方式 + 联系方式 +
+ +
+
+
+ 电子邮箱 + - +
+
+ 联系电话 + - +
+
+
+ +
+ + + +
-
个人中心组件内容(待实现)
`; } + + updateDisplay() { + if (!this.userInfo) return; + + // 更新头像 + const avatar = this.shadowRoot.getElementById('userAvatar'); + if (this.userInfo.icon && this.userInfo.icon.trim() !== '') { + const base64Data = this.userInfo.icon.trim(); + // 检查是否是有效的图片Base64数据 + if (base64Data.length > 100 && + /^[A-Za-z0-9+/]*={0,2}$/.test(base64Data)) { + + // 根据Base64数据开头判断图片格式 + let mimeType = 'image/jpeg'; // 默认 + if (base64Data.startsWith('iVBORw0KGgo')) { + mimeType = 'image/png'; + } else if (base64Data.startsWith('R0lGODlh')) { + mimeType = 'image/gif'; + } else if (base64Data.startsWith('UklGR')) { + mimeType = 'image/webp'; + } + + avatar.src = `data:${mimeType};base64,${base64Data}`; + } else { + avatar.src = 'assets/icons/png/user.png'; + } + } else { + avatar.src = 'assets/icons/png/user.png'; + } + + // 更新基本信息 + this.shadowRoot.getElementById('userName').textContent = this.userInfo.username || '未设置'; + this.shadowRoot.getElementById('userRole').textContent = this.getAccessLevelName(this.userInfo.access_level); + + // 更新详细信息 + this.updateInfoField('username', this.userInfo.username); + this.updateInfoField('fullName', this.userInfo.full_name); + this.updateInfoField('accessLevel', this.getAccessLevelName(this.userInfo.access_level), true); + this.updateInfoField('department', this.userInfo.department); + this.updateInfoField('position', this.userInfo.position); + this.updateInfoField('userId', this.userInfo.id); + this.updateInfoField('email', this.userInfo.email); + this.updateInfoField('phone', this.userInfo.phone); + } + + updateInfoField(fieldId, value, isBadge = false) { + const element = this.shadowRoot.getElementById(fieldId); + if (element) { + if (isBadge) { + element.innerHTML = `${value}`; + } else { + if (value && value !== '') { + element.textContent = value; + element.classList.remove('empty'); + } else { + element.textContent = '未设置'; + element.classList.add('empty'); + } + } + } + } + + getAccessLevelName(accessLevel) { + const level = parseInt(accessLevel); + switch(level) { + case 1: return '普通用户'; + case 2: return '开发者'; + case 3: return '组长'; + case 4: return '管理员'; + default: return '访客'; + } + } + + getPermissionClass(accessLevel) { + const level = parseInt(accessLevel); + switch(level) { + case 1: return 'user'; + case 2: return 'developer'; + case 3: return 'leader'; + case 4: return 'admin'; + default: return 'guest'; + } + } + + addEventListeners() { + const editBtn = this.shadowRoot.getElementById('editBtn'); + const saveBtn = this.shadowRoot.getElementById('saveBtn'); + const cancelBtn = this.shadowRoot.getElementById('cancelBtn'); + const avatarEditBtn = this.shadowRoot.getElementById('avatarEditBtn'); + const avatarFileInput = this.shadowRoot.getElementById('avatarFileInput'); + + editBtn.addEventListener('click', () => this.startEditing()); + saveBtn.addEventListener('click', () => this.saveChanges()); + cancelBtn.addEventListener('click', () => this.cancelEditing()); + + // 头像编辑按钮点击事件 + avatarEditBtn.addEventListener('click', () => { + avatarFileInput.click(); + }); + + // 文件选择事件 + avatarFileInput.addEventListener('change', (e) => { + const file = e.target.files[0]; + if (file) { + this.handleAvatarUpload(file); + } + }); + } + + startEditing() { + this.isEditing = true; + this.shadowRoot.getElementById('editBtn').style.display = 'none'; + this.shadowRoot.getElementById('saveBtn').style.display = 'flex'; + this.shadowRoot.getElementById('cancelBtn').style.display = 'flex'; + + // 将可编辑字段转换为输入框 + this.convertToInput('fullName', this.userInfo.full_name || ''); + this.convertToInput('department', this.userInfo.department || ''); + this.convertToInput('position', this.userInfo.position || ''); + this.convertToInput('email', this.userInfo.email || ''); + this.convertToInput('phone', this.userInfo.phone || ''); + } + + convertToInput(fieldId, value) { + const element = this.shadowRoot.getElementById(fieldId); + if (element) { + const input = document.createElement('input'); + input.type = 'text'; + input.className = 'edit-input'; + input.value = value; + input.id = `${fieldId}Input`; + element.innerHTML = ''; + element.appendChild(input); + } + } + + async saveChanges() { + const updatedInfo = { + full_name: this.shadowRoot.getElementById('fullNameInput')?.value || '', + department: this.shadowRoot.getElementById('departmentInput')?.value || '', + position: this.shadowRoot.getElementById('positionInput')?.value || '', + email: this.shadowRoot.getElementById('emailInput')?.value || '', + phone: this.shadowRoot.getElementById('phoneInput')?.value || '' + }; + + try { + const response = await fetch('/api/update-user-info', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + userId: this.userInfo.id, + userInfo: updatedInfo + }), + credentials: 'include' + }); + + const result = await response.json(); + + if (result.success) { + // 更新本地用户信息 + this.userInfo = { ...this.userInfo, ...updatedInfo }; + this.showSuccess('用户信息更新成功'); + this.cancelEditing(); + this.updateDisplay(); + } else { + this.showError('更新失败: ' + result.message); + } + } catch (error) { + console.error('更新用户信息失败:', error); + this.showError('更新用户信息失败'); + } + } + + cancelEditing() { + this.isEditing = false; + this.shadowRoot.getElementById('editBtn').style.display = 'flex'; + this.shadowRoot.getElementById('saveBtn').style.display = 'none'; + this.shadowRoot.getElementById('cancelBtn').style.display = 'none'; + + // 恢复显示模式 + this.updateDisplay(); + } + + showError(message) { + const errorElement = this.shadowRoot.getElementById('errorMessage'); + errorElement.textContent = message; + errorElement.style.display = 'block'; + setTimeout(() => { + errorElement.style.display = 'none'; + }, 5000); + } + + showSuccess(message) { + const successElement = this.shadowRoot.getElementById('successMessage'); + successElement.textContent = message; + successElement.style.display = 'block'; + setTimeout(() => { + successElement.style.display = 'none'; + }, 3000); + } + + async handleAvatarUpload(file) { + // 验证文件类型 + if (!file.type.startsWith('image/')) { + this.showError('请选择图片文件'); + return; + } + + // 验证文件大小(限制为500KB) + if (file.size > 500 * 1024) { + this.showError('图片大小不能超过500KB'); + return; + } + + try { + // 显示上传中状态 + const loadingElement = this.shadowRoot.getElementById('uploadLoading'); + loadingElement.style.display = 'block'; + + // 压缩图片并转换为Base64 + const base64Data = await this.compressAndConvertToBase64(file); + + // 提取纯Base64数据(去掉data URL前缀) + const pureBase64 = base64Data.replace(/^data:image\/[a-z]+;base64,/, ''); + + // 调用后端API更新头像 + const response = await fetch('/api/update-user-icon', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + userId: this.userInfo.id, + iconBase64: pureBase64 + }), + credentials: 'include' + }); + + const result = await response.json(); + + if (result.success) { + // 更新本地头像显示 + const avatar = this.shadowRoot.getElementById('userAvatar'); + avatar.src = base64Data; + + // 更新用户信息中的头像 + this.userInfo.icon = base64Data; + + this.showSuccess('头像更新成功'); + } else { + this.showError('头像更新失败: ' + (result.error || result.message)); + } + } catch (error) { + console.error('头像上传失败:', error); + this.showError('头像上传失败'); + } finally { + // 隐藏上传中状态 + const loadingElement = this.shadowRoot.getElementById('uploadLoading'); + loadingElement.style.display = 'none'; + + // 清空文件输入框 + this.shadowRoot.getElementById('avatarFileInput').value = ''; + } + } + + compressAndConvertToBase64(file) { + return new Promise((resolve, reject) => { + const canvas = document.createElement('canvas'); + const ctx = canvas.getContext('2d'); + const img = new Image(); + + img.onload = () => { + // 设置画布尺寸(限制最大尺寸为200x200) + const maxSize = 200; + let { width, height } = img; + + if (width > height) { + if (width > maxSize) { + height = (height * maxSize) / width; + width = maxSize; + } + } else { + if (height > maxSize) { + width = (width * maxSize) / height; + height = maxSize; + } + } + + canvas.width = width; + canvas.height = height; + + // 清空画布并设置透明背景 + ctx.clearRect(0, 0, width, height); + + // 绘制图片到画布 + ctx.drawImage(img, 0, 0, width, height); + + // 根据原图片格式选择合适的输出格式 + let mimeType = 'image/png'; // 默认使用PNG保持透明背景 + let quality = 0.9; + + // 如果原图是JPEG格式,则输出JPEG + if (file.type === 'image/jpeg' || file.type === 'image/jpg') { + mimeType = 'image/jpeg'; + quality = 0.8; + } + + // 转换为Base64 + const base64 = canvas.toDataURL(mimeType, quality); + resolve(base64); + }; + + img.onerror = () => { + reject(new Error('图片加载失败')); + }; + + // 从文件创建URL + const url = URL.createObjectURL(file); + img.src = url; + }); + } } customElements.define('profile-center', ProfileCenter); \ No newline at end of file diff --git a/XNSimHtml/components/user-info.js b/XNSimHtml/components/user-info.js index 99f8dff..8ef894d 100644 --- a/XNSimHtml/components/user-info.js +++ b/XNSimHtml/components/user-info.js @@ -326,14 +326,41 @@ class UserInfo extends HTMLElement { const userTooltip = this.shadowRoot.getElementById('userTooltip'); // 使用用户自定义图标 - if (userInfo.icon) { - userIcon.src = userInfo.icon; + if (userInfo.icon && userInfo.icon.trim() !== '') { + const base64Data = userInfo.icon.trim(); + + // 检查Base64数据是否有效且看起来像图片数据 + if (base64Data.length > 100 && + /^[A-Za-z0-9+/]*={0,2}$/.test(base64Data)) { + + // 根据Base64数据开头判断图片格式 + let mimeType = 'image/jpeg'; // 默认 + if (base64Data.startsWith('iVBORw0KGgo')) { + mimeType = 'image/png'; + } else if (base64Data.startsWith('R0lGODlh')) { + mimeType = 'image/gif'; + } else if (base64Data.startsWith('UklGR')) { + mimeType = 'image/webp'; + } + + userIcon.src = `data:${mimeType};base64,${base64Data}`; + } else { + console.warn('无效的图片Base64数据格式,使用默认头像'); + userIcon.src = 'assets/icons/png/user_b.png'; + } } else { // 如果没有自定义图标,使用默认用户图标 userIcon.src = 'assets/icons/png/user_b.png'; } userIcon.alt = userInfo.username; + // 添加图片加载错误处理 + userIcon.onerror = () => { + console.warn('用户头像加载失败,使用默认头像'); + console.warn('尝试加载的URL:', userIcon.src); + userIcon.src = 'assets/icons/png/user_b.png'; + }; + // 构建HTML内容 const tooltipContent = `
diff --git a/XNSimHtml/routes/auth.js b/XNSimHtml/routes/auth.js index 29f7961..057a0a4 100644 --- a/XNSimHtml/routes/auth.js +++ b/XNSimHtml/routes/auth.js @@ -1,6 +1,6 @@ const express = require('express'); const router = express.Router(); -const { loginLib, stringToBuffer } = require('../utils/xnCoreService'); +const { loginLib, stringToBuffer, updateUserIcon } = require('../utils/xnCoreService'); // 登录API路由 router.post('/login', (req, res) => { @@ -28,7 +28,7 @@ router.post('/login', (req, res) => { if (userId > 0) { try { - const userInfoBuffer = Buffer.alloc(1024); + const userInfoBuffer = Buffer.alloc(1024 * 1024); // 增加到1MB以容纳头像数据 const userInfoState = loginLib.getUserInfo(userId, userInfoBuffer, userInfoBuffer.length); if (userInfoState === 0) { @@ -392,7 +392,7 @@ router.get('/user-info/:userId', (req, res) => { throw new Error('动态库未正确加载'); } - const userInfoBuffer = Buffer.alloc(1024); + const userInfoBuffer = Buffer.alloc(1024 * 1024); // 增加到1MB以容纳头像数据 const result = loginLib.getUserInfo(parseInt(userId), userInfoBuffer, userInfoBuffer.length); if (result === 0) { @@ -416,4 +416,26 @@ router.get('/user-info/:userId', (req, res) => { } }); +// 更新用户头像路由 +router.post('/update-user-icon', (req, res) => { + const { userId, iconBase64 } = req.body; + + if (!userId || !iconBase64) { + return res.status(400).json({ success: false, message: '用户ID和头像数据不能为空' }); + } + + try { + const result = updateUserIcon(parseInt(userId), iconBase64); + + if (result === '头像更新成功') { + res.json({ success: true, message: '头像更新成功' }); + } else { + res.status(400).json({ success: false, message: '头像更新失败', error: result }); + } + } catch (error) { + console.error('更新用户头像过程出错:', error); + res.status(500).json({ success: false, message: '服务器内部错误', error: error.message }); + } +}); + module.exports = router; \ No newline at end of file diff --git a/XNSimHtml/utils/xnCoreService.js b/XNSimHtml/utils/xnCoreService.js index eb1bb5d..17fa21e 100644 --- a/XNSimHtml/utils/xnCoreService.js +++ b/XNSimHtml/utils/xnCoreService.js @@ -37,6 +37,7 @@ try { 'changePassword': ['int', ['int', StringType, 'int', StringType, 'int']], 'updateUserInfo': ['int', ['int', StringType, 'int']], 'updateUserAccessLevel': ['int', ['int', 'int']], + 'updateUserIcon': ['int', ['int', StringType, 'int']], 'getAllUsersSimpleInfo': ['int', [StringType, 'int']], 'resetPassword': ['int', ['int']], 'deleteUser': ['int', ['int']] @@ -602,6 +603,24 @@ function deleteUser(userId) { } } +// 更新用户头像 +function updateUserIcon(userId, iconBase64) { + if (!loginLib) { + return '登录库未加载'; + } + try { + const iconBuffer = stringToBuffer(iconBase64); + const result = loginLib.updateUserIcon(userId, iconBuffer.buffer, iconBuffer.length); + if (result === 0) { + return '头像更新成功'; + } else { + return `头像更新失败,错误码:${result}`; + } + } catch (error) { + return `头像更新失败: ${error.message}`; + } +} + module.exports = { loginLib, monitorLib, @@ -633,5 +652,6 @@ module.exports = { getCollectDataStatus, stopCollectData, resetPassword, - deleteUser + deleteUser, + updateUserIcon }; \ No newline at end of file