From 1977a6757d4fb9bd6509cf740ffb8b20dbbed66e Mon Sep 17 00:00:00 2001 From: zyh Date: Thu, 10 Apr 2025 13:16:11 +0000 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E7=94=A8=E6=88=B7=E4=B8=AA?= =?UTF-8?q?=E4=BA=BA=E4=BF=A1=E6=81=AF=E9=A1=B5=E9=9D=A2=EF=BC=8C=E6=95=B4?= =?UTF-8?q?=E5=90=88=E7=94=A8=E6=88=B7=E4=BF=A1=E6=81=AF=E8=8E=B7=E5=8F=96?= =?UTF-8?q?=E5=92=8C=E6=9B=B4=E6=96=B0=E5=8A=9F=E8=83=BD=EF=BC=8C=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E8=A1=A8=E5=8D=95=E9=AA=8C=E8=AF=81=E5=92=8C=E5=AF=86?= =?UTF-8?q?=E7=A0=81=E4=BF=AE=E6=94=B9=EF=BC=8C=E6=8F=90=E5=8D=87=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E4=BD=93=E9=AA=8C=E5=92=8C=E4=BB=A3=E7=A0=81=E5=8F=AF?= =?UTF-8?q?=E7=BB=B4=E6=8A=A4=E6=80=A7=E3=80=82=E5=90=8C=E6=97=B6=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=E7=9B=B8=E5=85=B3=E7=B1=BB=E5=9E=8B=E5=AE=9A=E4=B9=89?= =?UTF-8?q?=E5=92=8CAPI=E8=B7=AF=E7=94=B1=EF=BC=8C=E7=A1=AE=E4=BF=9D?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E4=B8=80=E8=87=B4=E6=80=A7=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/mobile/deno.json | 1 + client/mobile/deno.lock | 13 ++++ client/mobile/pages_profile.tsx | 132 ++++++++++++++++++++++++++++++++ client/share/types.ts | 1 + server/routes_users.ts | 72 ++++++++++++++++- 5 files changed, 218 insertions(+), 1 deletion(-) create mode 100644 client/mobile/pages_profile.tsx diff --git a/client/mobile/deno.json b/client/mobile/deno.json index f285609..c1cc3f8 100644 --- a/client/mobile/deno.json +++ b/client/mobile/deno.json @@ -4,6 +4,7 @@ "react-dom": "https://esm.d8d.fun/react-dom@19.0.0", "react-dom/client": "https://esm.d8d.fun/react-dom@19.0.0/client", "react-router": "https://esm.d8d.fun/react-router@7.3.0?deps=react@19.0.0,react-dom@19.0.0", + "react-hook-form": "https://esm.d8d.fun/react-hook-form@7.55.0?deps=react@19.0.0,react-dom@19.0.0", "@heroicons/react/24/outline": "https://esm.d8d.fun/@heroicons/react@2.1.1/24/outline?deps=react@19.0.0,react-dom@19.0.0", "@heroicons/react/24/solid": "https://esm.d8d.fun/@heroicons/react@2.1.1/24/solid?deps=react@19.0.0,react-dom@19.0.0", "axios": "https://esm.d8d.fun/axios@1.6.7", diff --git a/client/mobile/deno.lock b/client/mobile/deno.lock index 0ffa5bb..abd0c09 100644 --- a/client/mobile/deno.lock +++ b/client/mobile/deno.lock @@ -26,7 +26,9 @@ "https://esm.d8d.fun/@types/proxy-from-env@~1.0.4/index.d.ts": "https://esm.d8d.fun/@types/proxy-from-env@1.0.4/index.d.ts", "https://esm.d8d.fun/@types/react-dom@~19.0.4/X-ZHJlYWN0QDE5LjAuMA/index.d.ts": "https://esm.d8d.fun/@types/react-dom@19.0.6/X-ZHJlYWN0QDE5LjAuMA/index.d.ts", "https://esm.d8d.fun/@types/react-dom@~19.0.4/client.d.ts": "https://esm.d8d.fun/@types/react-dom@19.0.6/client.d.ts", + "https://esm.d8d.fun/@types/react-dom@~19.0.4/index.d.ts": "https://esm.d8d.fun/@types/react-dom@19.0.6/index.d.ts", "https://esm.d8d.fun/@types/react-dom@~19.0.6/X-ZHJlYWN0QDE5LjAuMA/client.d.ts": "https://esm.d8d.fun/@types/react-dom@19.0.6/X-ZHJlYWN0QDE5LjAuMA/client.d.ts", + "https://esm.d8d.fun/@types/react@~19.0.11/index.d.ts": "https://esm.d8d.fun/@types/react@19.0.14/index.d.ts", "https://esm.d8d.fun/@types/react@~19.0.12/index.d.ts": "https://esm.d8d.fun/@types/react@19.0.14/index.d.ts", "https://esm.d8d.fun/@types/react@~19.0.12/jsx-runtime.d.ts": "https://esm.d8d.fun/@types/react@19.0.14/jsx-runtime.d.ts", "https://esm.d8d.fun/@types/react@~19.0.14/X-ZHJlYWN0LWRvbUAxOS4wLjA/index.d.ts": "https://esm.d8d.fun/@types/react@19.0.14/X-ZHJlYWN0LWRvbUAxOS4wLjA/index.d.ts", @@ -52,6 +54,7 @@ "https://esm.d8d.fun/nanoid@^5.1.2?target=denonext": "https://esm.d8d.fun/nanoid@5.1.5?target=denonext", "https://esm.d8d.fun/node-gyp-build@^4.3.0?target=denonext": "https://esm.d8d.fun/node-gyp-build@4.8.4?target=denonext", "https://esm.d8d.fun/proxy-from-env@^1.1.0?target=denonext": "https://esm.d8d.fun/proxy-from-env@1.1.0?target=denonext", + "https://esm.d8d.fun/react@%3E=18?target=denonext": "https://esm.d8d.fun/react@19.1.0?target=denonext", "https://esm.d8d.fun/scheduler@^0.25.0?target=denonext": "https://esm.d8d.fun/scheduler@0.25.0?target=denonext", "https://esm.d8d.fun/set-cookie-parser@^2.6.0?target=denonext": "https://esm.d8d.fun/set-cookie-parser@2.7.1?target=denonext", "https://esm.d8d.fun/socket.io-client@^4.7.2?target=denonext": "https://esm.d8d.fun/socket.io-client@4.8.1?target=denonext", @@ -78,7 +81,9 @@ "https://esm.d8d.fun/@heroicons/react@2.1.1/24/outline?deps=react@19.0.0": "d2c14cfd7f3090062c9f968f25d0ddbb277ca76055af1ac3fd22045276571a75", "https://esm.d8d.fun/@heroicons/react@2.1.1/24/outline?deps=react@19.0.0,react-dom@19.0.0": "5e99f4d40ce60c55b5cf421c3cf3f13df1707cf53152e447b2332570412cd77a", "https://esm.d8d.fun/@heroicons/react@2.1.1/24/solid?deps=react@19.0.0": "546051c9fdfdca5c7d51cd4cf588fe709da509274c5fcf203d616a5e87bdd595", + "https://esm.d8d.fun/@heroicons/react@2.1.1/24/solid?deps=react@19.0.0,react-dom@19.0.0": "e3940182b574da537337b1e90a1b7f380e17050457423e13d5ac8c7bc88a3cc0", "https://esm.d8d.fun/@heroicons/react@2.1.1/X-ZHJlYWN0LWRvbUAxOS4wLjAscmVhY3RAMTkuMC4w/denonext/24/outline.mjs": "640f934a0c987f682032049e5d4a455567db676de47bca0d44e76b72023661f7", + "https://esm.d8d.fun/@heroicons/react@2.1.1/X-ZHJlYWN0LWRvbUAxOS4wLjAscmVhY3RAMTkuMC4w/denonext/24/solid.mjs": "dcbd0c377d92857b6eb23c7dbb2ee6e650b12aa6ae1ef7fcc10dc1964df8ba47", "https://esm.d8d.fun/@heroicons/react@2.1.1/X-ZHJlYWN0QDE5LjAuMA/denonext/24/outline.mjs": "640f934a0c987f682032049e5d4a455567db676de47bca0d44e76b72023661f7", "https://esm.d8d.fun/@heroicons/react@2.1.1/X-ZHJlYWN0QDE5LjAuMA/denonext/24/solid.mjs": "dcbd0c377d92857b6eb23c7dbb2ee6e650b12aa6ae1ef7fcc10dc1964df8ba47", "https://esm.d8d.fun/@socket.io/component-emitter@3.1.2/denonext/component-emitter.mjs": "3c6c5f2d64d4933b577a7117df1d8855c51ff01ab3dea8f42af1adcb1a5989e7", @@ -173,6 +178,7 @@ "https://esm.d8d.fun/node-gyp-build@4.8.4?target=denonext": "261a6cedf1fdbf159798141ba1e2311ac1510682c5c8b55dacc8cf5fdee4aa06", "https://esm.d8d.fun/proxy-from-env@1.1.0/denonext/proxy-from-env.mjs": "f60f9c79fc3baa07c13c800798d645ae70d1b2059b8d593dcd4f8c5710b50333", "https://esm.d8d.fun/proxy-from-env@1.1.0?target=denonext": "bf02a050a1a6aa56ddba25dbea2c355da294630e5c5520fddea4b2f30a9292bc", + "https://esm.d8d.fun/react-dom@19.0.0": "d057f65e74eca8add1702ba9a5ecbcc8e60a73dd358b7852094bde0361725137", "https://esm.d8d.fun/react-dom@19.0.0/X-ZHJlYWN0QDE5LjAuMA/denonext/client.mjs": "af662fd134eea98f37fdcea6142accd0f8a7d2e13c1c3c9e98dc37a8c7aad46b", "https://esm.d8d.fun/react-dom@19.0.0/X-ZHJlYWN0QDE5LjAuMA/denonext/react-dom.mjs": "a2f7bc344e1d5b7ca47e68665291e206ae4db17ee84f234f3d3e2533b9119f63", "https://esm.d8d.fun/react-dom@19.0.0/client": "c972c16184c695fc5828dfa61d7f341edbc463d20d8108765c93a98027c24227", @@ -180,14 +186,21 @@ "https://esm.d8d.fun/react-dom@19.0.0/denonext/client.mjs": "af662fd134eea98f37fdcea6142accd0f8a7d2e13c1c3c9e98dc37a8c7aad46b", "https://esm.d8d.fun/react-dom@19.0.0/denonext/react-dom.mjs": "a2f7bc344e1d5b7ca47e68665291e206ae4db17ee84f234f3d3e2533b9119f63", "https://esm.d8d.fun/react-dom@19.0.0?deps=react@19.0.0,react-dom@19.0.0": "0e49978c3f0fb4a94db9c9318aebd7e1b35651678050871a91ebb080cc3e1f83", + "https://esm.d8d.fun/react-hook-form@7.55.0/X-ZHJlYWN0LWRvbUAxOS4wLjAscmVhY3RAMTkuMC4w/denonext/react-hook-form.mjs": "788ec1a54e10051f539ba435aa513802c823bad03e11e2534b1b17df99189a87", + "https://esm.d8d.fun/react-hook-form@7.55.0?deps=react@19.0.0,react-dom@19.0.0": "8ed376b3af6e11be43538b15e654692d5995232523a6dc16ce7f81263b1a3614", + "https://esm.d8d.fun/react-router@7.3.0": "ed310627e3a6bd90acbaefa1474263abd85e127041ccc5f665cf3d3a574c85c8", "https://esm.d8d.fun/react-router@7.3.0/X-ZHJlYWN0LWRvbUAxOS4wLjAscmVhY3RAMTkuMC4w/denonext/dist/development/chunk-K6CSEXPM.mjs": "441898046ad7c4fd9a6b53e13a398c9c74c4412c519e942f82b8a77f7af9f9d6", "https://esm.d8d.fun/react-router@7.3.0/X-ZHJlYWN0LWRvbUAxOS4wLjAscmVhY3RAMTkuMC4w/denonext/react-router.mjs": "b0b05fcfc3a03c5f679cd0bc69ca19aa10abaa977395df00e86b3fb114e5e346", + "https://esm.d8d.fun/react-router@7.3.0/denonext/dist/development/chunk-K6CSEXPM.mjs": "095a3225d9bbe00e5749781a8335be24f770a9e2634f88f75bb691de46a50a18", + "https://esm.d8d.fun/react-router@7.3.0/denonext/react-router.mjs": "b0b05fcfc3a03c5f679cd0bc69ca19aa10abaa977395df00e86b3fb114e5e346", "https://esm.d8d.fun/react-router@7.3.0?deps=react@19.0.0,react-dom@19.0.0": "ad747718e32a45020d67eb4ff98f9734cb06a10ceb393baac0a965043e96cdf0", "https://esm.d8d.fun/react@19.0.0": "ab1f4aa20ac56c237bbb204632bdb55f03a0ab005d21944eeb447e5e37879637", "https://esm.d8d.fun/react@19.0.0/X-ZHJlYWN0LWRvbUAxOS4wLjA/denonext/react.mjs": "87fdb28d39ca8983bdba3e7ec329305f95463cfc70c015b2620b4900fa15efdd", "https://esm.d8d.fun/react@19.0.0/denonext/jsx-runtime.mjs": "643b749fa9666fbf73619a99fd708722edb4acaa34c8cea7be783a3432367780", "https://esm.d8d.fun/react@19.0.0/denonext/react.mjs": "87fdb28d39ca8983bdba3e7ec329305f95463cfc70c015b2620b4900fa15efdd", "https://esm.d8d.fun/react@19.0.0?deps=react@19.0.0,react-dom@19.0.0": "05a4c12599a7d4b62ff7fc37228964902d26e2f7ba03a61d6335793b998972b7", + "https://esm.d8d.fun/react@19.1.0/denonext/react.mjs": "b43f435068776ab7a40daea8854ab1f8eca6252e86a9ac8b716bb9110ffeb76e", + "https://esm.d8d.fun/react@19.1.0?target=denonext": "8cb1e2ba1aeb012dc6807c8b3cf6ae90579b448317c9debc9d888dcabc246e66", "https://esm.d8d.fun/scheduler@0.25.0/denonext/scheduler.mjs": "50687edf9e0034b6db97303b1b16893b59c5833c21ea8cf913dc380b537f6aaf", "https://esm.d8d.fun/scheduler@0.25.0?target=denonext": "c12810f51123057a8a8e309cc8befaac0b5cd371cb4d61bf0372ab8046acc8e0", "https://esm.d8d.fun/set-cookie-parser@2.7.1/denonext/set-cookie-parser.mjs": "81f09c909c63221a2460bc7602746543af6fd05b54fd866a04e81bb754bc7f26", diff --git a/client/mobile/pages_profile.tsx b/client/mobile/pages_profile.tsx new file mode 100644 index 0000000..6fc9456 --- /dev/null +++ b/client/mobile/pages_profile.tsx @@ -0,0 +1,132 @@ +import React from 'react' +import { useNavigate } from 'react-router' +import { useForm } from 'react-hook-form' +import { UserAPI } from './api.ts' +import type { User } from '../share/types.ts' + +export default function ProfilePage() { + const navigate = useNavigate() + const { register, handleSubmit, formState: { errors }, setValue } = useForm & { password?: string }>() + const [loading, setLoading] = React.useState(false) + const [user, setUser] = React.useState(null) + + // 获取当前用户信息 + React.useEffect(() => { + const fetchUser = async () => { + try { + const res = await UserAPI.getUsers({ limit: 1 }) + if (res.data?.length > 0) { + const userData = res.data[0] + setUser(userData) + setValue('username', userData.username) + setValue('nickname', userData.nickname) + setValue('email', userData.email) + setValue('phone', userData.phone) + } + } catch (error) { + console.error('获取用户信息失败:', error) + } + } + fetchUser() + }, [setValue]) + + // 提交表单更新用户信息 + const onSubmit = async (data: User) => { + try { + setLoading(true) + if (!user?.id) return + + const updatedUser = await UserAPI.updateUser(user.id, { + nickname: data.nickname, + email: data.email, + phone: data.phone, + ...(data.password ? { password: data.password } : {}) + }) + + setUser(updatedUser.data) + alert('更新成功') + } catch (error) { + console.error('更新失败:', error) + alert('更新失败') + } finally { + setLoading(false) + } + } + + return ( +
+

个人信息

+ +
+
+ + +
+ +
+ + + {errors.nickname &&

{errors.nickname.message}

} +
+ +
+ + + {errors.email &&

{errors.email.message}

} +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+
+
+ ) +} \ No newline at end of file diff --git a/client/share/types.ts b/client/share/types.ts index 3a4553a..3d042ad 100644 --- a/client/share/types.ts +++ b/client/share/types.ts @@ -32,6 +32,7 @@ export interface User { phone?: string; role: string; avatar?: string; + password?: string; } export interface MenuItem { diff --git a/server/routes_users.ts b/server/routes_users.ts index f1cce12..1db6478 100644 --- a/server/routes_users.ts +++ b/server/routes_users.ts @@ -237,5 +237,75 @@ export function createUserRoutes(withAuth: WithAuth) { } }) + // 获取当前用户信息 + usersRoutes.get('/me', withAuth, async (c) => { + try { + const user = c.get('user')! + + const apiClient = c.get('apiClient') + const userData = await apiClient.database.table('users') + .where('id', user.id) + .select('id', 'username', 'nickname', 'email', 'phone', 'role', 'created_at') + .first() + + if (!user) { + return c.json({ error: '用户不存在' }, 404) + } + + return c.json({ + data: user, + message: '获取用户详情成功' + }) + } catch (error) { + console.error('获取当前用户信息失败:', error) + return c.json({ error: '获取当前用户信息失败' }, 500) + } + }) + + // 更新当前用户信息 + usersRoutes.put('/me', withAuth, async (c) => { + try { + const user = c.get('user')! + const apiClient = c.get('apiClient') + const body = await c.req.json() + + // 验证必填字段 + const { nickname, email, phone } = body + if (!nickname || !email) { + return c.json({ error: '缺少必要的用户信息' }, 400) + } + + // 更新用户信息 + const updateData: any = { + nickname, + email, + phone: phone || null, + updated_at: new Date() + } + + // 如果提供了新密码,则更新密码 + if (body.password) { + updateData.password = body.password + } + + await apiClient.database.table('users') + .where('id', user.id) + .update(updateData) + + const updatedUser = await apiClient.database.table('users') + .where('id', user.id) + .select('id', 'username', 'nickname', 'email', 'phone', 'role', 'created_at') + .first() + + return c.json({ + data: updatedUser, + message: '更新用户信息成功' + }) + } catch (error) { + console.error('更新当前用户信息失败:', error) + return c.json({ error: '更新当前用户信息失败' }, 500) + } + }) + return usersRoutes -} \ No newline at end of file +} \ No newline at end of file