From 4ae36bc727aa0016853bef5fd19534903094cee3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B5=B5=E5=BF=A0=E6=9E=97?= <170083662@qq.com> Date: Fri, 5 Sep 2025 12:14:46 +0800 Subject: [PATCH] =?UTF-8?q?feat(user):=20=E5=AE=9E=E7=8E=B0=E7=99=BB?= =?UTF-8?q?=E5=BD=95=E7=8A=B6=E6=80=81=E5=AE=9E=E6=97=B6=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 使用 useUser Hook集中管理用户状态 - 登录成功后实时更新 Header 和 UserCard 组件 - 移除页面刷新操作,提升用户体验- 添加登录成功提示 --- LOGIN_STATUS_UPDATE_TEST.md | 90 +++++++++++++++ src/pages/index/Header.tsx | 146 +++++++++++++------------ src/pages/user/components/UserCard.tsx | 94 ++++++++-------- 3 files changed, 218 insertions(+), 112 deletions(-) create mode 100644 LOGIN_STATUS_UPDATE_TEST.md diff --git a/LOGIN_STATUS_UPDATE_TEST.md b/LOGIN_STATUS_UPDATE_TEST.md new file mode 100644 index 0000000..9eba6dd --- /dev/null +++ b/LOGIN_STATUS_UPDATE_TEST.md @@ -0,0 +1,90 @@ +# 登录状态实时更新测试指南 + +## 问题描述 +之前的问题是:用户登录成功后,Header组件的登录状态没有实时更新,需要刷新页面才能看到登录状态的变化。 + +## 解决方案 +修改了以下组件,使其使用 `useUser` Hook 来管理用户状态: + +1. **Header组件** (`src/pages/index/Header.tsx`) +2. **UserCard组件** (`src/pages/user/components/UserCard.tsx`) + +### 主要修改内容 + +#### 1. Header组件修改 +- 移除了本地状态 `IsLogin` 和 `userInfo` +- 使用 `useUser` Hook 的 `user`, `isLoggedIn`, `loginUser`, `fetchUserInfo` +- 登录成功后调用 `loginUser(token, userData)` 而不是直接设置本地状态 +- 移除了 `Taro.reLaunch` 重新启动小程序的逻辑 + +#### 2. UserCard组件修改 +- 类似的修改,使用 `useUser` Hook 管理状态 +- 登录成功后调用 `loginUser(token, userData)` + +## 测试步骤 + +### 1. 基本登录状态更新测试 +1. 打开小程序,确保处于未登录状态 +2. 在首页Header区域,应该显示"未登录"状态 +3. 点击登录按钮,完成手机号授权登录 +4. **关键测试点**:登录成功后,Header应该立即显示用户信息,无需刷新页面 + +### 2. 跨页面状态同步测试 +1. 在首页完成登录 +2. 切换到用户中心页面 +3. 检查UserCard组件是否正确显示登录状态 +4. 返回首页,检查Header组件状态是否保持一致 + +### 3. 退出登录测试 +1. 在用户中心点击"退出登录" +2. 返回首页,检查Header是否立即显示未登录状态 + +### 4. 页面刷新测试 +1. 登录后刷新页面(或重新进入小程序) +2. 检查登录状态是否正确从本地存储恢复 + +## 预期结果 + +### 修改前的问题 +- 登录成功后需要 `Taro.reLaunch` 重新启动小程序 +- 状态更新不实时,用户体验差 + +### 修改后的预期效果 +- 登录成功后立即更新UI状态 +- 无需重新启动小程序 +- 所有使用 `useUser` Hook 的组件都能实时同步状态 +- 更好的用户体验 + +## 技术实现原理 + +### useUser Hook 的优势 +1. **集中状态管理**:所有用户相关状态都在一个地方管理 +2. **自动同步**:所有使用该Hook的组件都会自动同步状态变化 +3. **持久化存储**:自动处理本地存储的读写 +4. **错误处理**:统一的错误处理逻辑 + +### 状态更新流程 +``` +用户登录 → loginUser(token, userData) → 更新Hook状态 → 所有组件自动重新渲染 +``` + +## 注意事项 + +1. **确保所有相关组件都使用useUser Hook**:避免混用本地状态和全局状态 +2. **测试边界情况**:网络错误、token过期等情况 +3. **性能考虑**:useUser Hook 已经优化了不必要的重新渲染 + +## 如果测试失败 + +如果登录后状态仍然没有实时更新,请检查: + +1. 组件是否正确导入和使用了 `useUser` Hook +2. 登录成功后是否调用了 `loginUser` 方法 +3. 是否还有其他地方使用了本地状态而不是Hook状态 +4. 控制台是否有错误信息 + +## 进一步优化建议 + +1. 可以考虑添加加载状态指示器 +2. 可以添加登录状态变化的动画效果 +3. 可以考虑使用 React Context 进一步优化性能 diff --git a/src/pages/index/Header.tsx b/src/pages/index/Header.tsx index 4c23733..dc89d4a 100644 --- a/src/pages/index/Header.tsx +++ b/src/pages/index/Header.tsx @@ -3,31 +3,33 @@ import Taro from '@tarojs/taro'; import {Button, Space} from '@nutui/nutui-react-taro' import {TriangleDown} from '@nutui/icons-react-taro' import {Avatar, NavBar} from '@nutui/nutui-react-taro' -import {getUserInfo, getWxOpenId} from "@/api/layout"; +import {getWxOpenId} from "@/api/layout"; import {TenantId} from "@/config/app"; import {getOrganization} from "@/api/system/organization"; import {myUserVerify} from "@/api/system/userVerify"; import {useShopInfo} from '@/hooks/useShopInfo'; +import {useUser} from '@/hooks/useUser'; import {handleInviteRelation} from "@/utils/invite"; import {View, Text} from '@tarojs/components' import MySearch from "./MySearch"; import './Header.scss'; import navTo from "@/utils/common"; -import {User} from "@/api/system/user/model"; const Header = (props: any) => { // 使用新的useShopInfo Hook const { - getWebsiteName, getWebsiteLogo } = useShopInfo(); - const [IsLogin, setIsLogin] = useState(true) + // 使用useUser Hook管理用户状态 + const { + user, + isLoggedIn, + loginUser, + fetchUserInfo + } = useUser(); + const [statusBarHeight, setStatusBarHeight] = useState() - const [userInfo, setUserInfo] = useState({ - avatar: '', - mobile: '未登录' - }) const reload = async () => { Taro.getSystemInfo({ @@ -35,56 +37,58 @@ const Header = (props: any) => { setStatusBarHeight(res.statusBarHeight) }, }) - // 注意:商店信息现在通过useShopInfo自动管理,不需要手动获取 - // 获取用户信息 - const data = await getUserInfo(); - if (data) { - setIsLogin(true); - console.log('用户信息>>>', data.phone) - // 保存userId - Taro.setStorageSync('UserId', data.userId) - // 获取openId - if (!data.openid) { - Taro.login({ - success: (res) => { - getWxOpenId({code: res.code}).then(() => { + + // 如果已登录,获取最新用户信息 + if (isLoggedIn) { + try { + const data = await fetchUserInfo(); + if (data) { + console.log('用户信息>>>', data.phone) + // 获取openId + if (!data.openid) { + Taro.login({ + success: (res) => { + getWxOpenId({code: res.code}).then(() => { + }) + } }) } - }) - } - // 是否已认证 - if (data.certification) { - Taro.setStorageSync('Certification', '1') - } - // 机构ID - Taro.setStorageSync('OrganizationId', data.organizationId) - // 父级机构ID - if (Number(data.organizationId) > 0) { - getOrganization(Number(data.organizationId)).then(res => { - Taro.setStorageSync('OrganizationParentId', res.parentId) - }) - } - // 管理员 - const isKdy = data.roles?.findIndex(item => item.roleCode == 'admin') - if (isKdy != -1) { - Taro.setStorageSync('RoleName', '管理') - Taro.setStorageSync('RoleCode', 'admin') - return false; - } - // 注册用户 - const isUser = data.roles?.findIndex(item => item.roleCode == 'user') - if (isUser != -1) { - Taro.setStorageSync('RoleName', '注册用户') - Taro.setStorageSync('RoleCode', 'user') - return false; - } - // 认证信息 - myUserVerify({status: 1}).then(data => { - if (data?.realName) { - Taro.setStorageSync('RealName', data.realName) + // 是否已认证 + if (data.certification) { + Taro.setStorageSync('Certification', '1') + } + // 机构ID + Taro.setStorageSync('OrganizationId', data.organizationId) + // 父级机构ID + if (Number(data.organizationId) > 0) { + getOrganization(Number(data.organizationId)).then(res => { + Taro.setStorageSync('OrganizationParentId', res.parentId) + }) + } + // 管理员 + const isKdy = data.roles?.findIndex(item => item.roleCode == 'admin') + if (isKdy != -1) { + Taro.setStorageSync('RoleName', '管理') + Taro.setStorageSync('RoleCode', 'admin') + return false; + } + // 注册用户 + const isUser = data.roles?.findIndex(item => item.roleCode == 'user') + if (isUser != -1) { + Taro.setStorageSync('RoleName', '注册用户') + Taro.setStorageSync('RoleCode', 'user') + return false; + } + // 认证信息 + myUserVerify({status: 1}).then(data => { + if (data?.realName) { + Taro.setStorageSync('RealName', data.realName) + } + }) } - }) - setUserInfo(data) + } catch (error) { + console.error('获取用户信息失败:', error) + } } } @@ -120,14 +124,16 @@ const Header = (props: any) => { return false; } // 登录成功 - Taro.setStorageSync('access_token', res.data.data.access_token) - Taro.setStorageSync('UserId', res.data.data.user.userId) - setIsLogin(true) + const token = res.data.data.access_token; + const userData = res.data.data.user; + + // 使用useUser Hook的loginUser方法更新状态 + loginUser(token, userData); // 处理邀请关系 - if (res.data.data.user?.userId) { + if (userData?.userId) { try { - const inviteSuccess = await handleInviteRelation(res.data.data.user.userId) + const inviteSuccess = await handleInviteRelation(userData.userId) if (inviteSuccess) { Taro.showToast({ title: '邀请关系建立成功', @@ -140,10 +146,16 @@ const Header = (props: any) => { } } - // 重新加载小程序 - Taro.reLaunch({ - url: '/pages/index/index' + // 显示登录成功提示 + Taro.showToast({ + title: '登录成功', + icon: 'success', + duration: 1500 }) + + // 不需要重新启动小程序,状态已经通过useUser更新 + // 可以选择性地刷新当前页面数据 + reload(); } }) } else { @@ -170,13 +182,13 @@ const Header = (props: any) => { onBackClick={() => { }} left={ - IsLogin ? ( + isLoggedIn ? ( navTo(`/user/profile/profile`,true)}> - {userInfo?.mobile} + {user?.nickname || '已登录'} ) : ( @@ -187,7 +199,7 @@ const Header = (props: any) => { size="22" src={getWebsiteLogo()} /> - {getWebsiteName()} + 未登录 diff --git a/src/pages/user/components/UserCard.tsx b/src/pages/user/components/UserCard.tsx index 4ca426d..e9be33b 100644 --- a/src/pages/user/components/UserCard.tsx +++ b/src/pages/user/components/UserCard.tsx @@ -14,12 +14,15 @@ import {useUserData} from "@/hooks/useUserData"; function UserCard() { const { - isAdmin + user, + isLoggedIn, + loginUser, + fetchUserInfo, + isAdmin, + getDisplayName, + getRoleName } = useUser(); const { data, refresh } = useUserData() - const {getDisplayName, getRoleName} = useUser(); - const [IsLogin, setIsLogin] = useState(false) - const [userInfo, setUserInfo] = useState() const [couponCount, setCouponCount] = useState(0) const [pointsCount, setPointsCount] = useState(0) const [giftCount, setGiftCount] = useState(0) @@ -77,41 +80,31 @@ function UserCard() { // }) } - const reload = () => { - Taro.getUserInfo({ - success: (res) => { - const avatar = res.userInfo.avatarUrl; - setUserInfo({ - avatar, - nickname: res.userInfo.nickName, - sexName: res.userInfo.gender == 1 ? '男' : '女' - }) - getUserInfo().then((data) => { - if (data) { - setUserInfo(data) - setIsLogin(true); - Taro.setStorageSync('UserId', data.userId) - - // 加载用户统计数据 - if (data.userId) { - loadUserStats(data.userId) - } - - // 获取openId - if (!data.openid) { - Taro.login({ - success: (res) => { - getWxOpenId({code: res.code}).then(() => { - }) - } - }) - } + const reload = async () => { + // 如果已登录,获取最新用户信息 + if (isLoggedIn) { + try { + const data = await fetchUserInfo(); + if (data) { + // 加载用户统计数据 + if (data.userId) { + loadUserStats(data.userId) } - }).catch(() => { - console.log('未登录') - }); + + // 获取openId + if (!data.openid) { + Taro.login({ + success: (res) => { + getWxOpenId({code: res.code}).then(() => { + }) + } + }) + } + } + } catch (error) { + console.error('获取用户信息失败:', error) } - }); + } }; const showAuthModal = () => { @@ -180,10 +173,21 @@ function UserCard() { return false; } // 登录成功 - Taro.setStorageSync('access_token', res.data.data.access_token) - Taro.setStorageSync('UserId', res.data.data.user.userId) - setUserInfo(res.data.data.user) - setIsLogin(true) + const token = res.data.data.access_token; + const userData = res.data.data.user; + + // 使用useUser Hook的loginUser方法更新状态 + loginUser(token, userData); + + // 显示登录成功提示 + Taro.showToast({ + title: '登录成功', + icon: 'success', + duration: 1500 + }) + + // 刷新页面数据 + reload(); } }) } else { @@ -209,17 +213,17 @@ function UserCard() { { - IsLogin ? ( - + isLoggedIn ? ( + ) : ( ) } {getDisplayName()} - {IsLogin ? ( + {isLoggedIn ? (