refactor(user): 重构用户卡片组件并优化登录状态更新逻辑- 重构了 UserCard 组件,统一了前端和后台的实现

- 移除了冗余的数据获取逻辑,统一使用 useUserData Hook
- 优化了登录状态更新流程,使用 loginUser 方法统一处理登录
- 添加了登录成功后的提示和页面数据刷新
This commit is contained in:
2025-09-05 12:22:43 +08:00
parent 4ae36bc727
commit 7c715d9a85
3 changed files with 70 additions and 134 deletions

View File

@@ -8,6 +8,14 @@
1. **Header组件** (`src/pages/index/Header.tsx`) 1. **Header组件** (`src/pages/index/Header.tsx`)
2. **UserCard组件** (`src/pages/user/components/UserCard.tsx`) 2. **UserCard组件** (`src/pages/user/components/UserCard.tsx`)
3. **Admin UserCard组件** (`src/admin/components/UserCard.tsx`)
## 额外优化:清理冗余代码
发现 UserCard 组件中存在冗余的数据获取逻辑:
- `useUserData` Hook 已经动态获取了优惠券、礼品卡、积分、余额等数据
- 删除了重复的本地状态:`couponCount`, `pointsCount`, `giftCount`
- 删除了重复的数据获取方法:`loadUserStats`
- 统一使用 `useUserData` 提供的数据
### 主要修改内容 ### 主要修改内容

View File

@@ -1,21 +1,23 @@
import {Button} from '@nutui/nutui-react-taro' import {Button} from '@nutui/nutui-react-taro'
import {Avatar, Tag} from '@nutui/nutui-react-taro' import {Avatar, Tag} from '@nutui/nutui-react-taro'
import {getUserInfo, getWxOpenId} from '@/api/layout'; import {getWxOpenId} from '@/api/layout';
import Taro from '@tarojs/taro'; import Taro from '@tarojs/taro';
import {useEffect, useState} from "react"; import {useEffect} from "react";
import {User} from "@/api/system/user/model";
import navTo from "@/utils/common"; import navTo from "@/utils/common";
import {TenantId} from "@/config/app"; import {TenantId} from "@/config/app";
import {getMyAvailableCoupons} from "@/api/shop/shopUserCoupon";
import {useUser} from "@/hooks/useUser"; import {useUser} from "@/hooks/useUser";
import {useUserData} from "@/hooks/useUserData";
function UserCard() { function UserCard() {
const {getDisplayName, getRoleName} = useUser(); const {
const [IsLogin, setIsLogin] = useState<boolean>(false) user,
const [userInfo, setUserInfo] = useState<User>() isLoggedIn,
const [couponCount, setCouponCount] = useState(0) loginUser,
// const [pointsCount, setPointsCount] = useState(0) fetchUserInfo,
const [giftCount, setGiftCount] = useState(0) getDisplayName,
getRoleName
} = useUser();
const {data} = useUserData()
useEffect(() => { useEffect(() => {
// Taro.getSetting获取用户的当前设置。返回值中只会出现小程序已经向用户请求过的权限。 // Taro.getSetting获取用户的当前设置。返回值中只会出现小程序已经向用户请求过的权限。
@@ -34,67 +36,27 @@ function UserCard() {
}); });
}, []); }, []);
const loadUserStats = (userId: number) => {
// 加载优惠券数量
getMyAvailableCoupons()
.then((coupons: any) => {
setCouponCount(coupons?.length || 0)
})
.catch((error: any) => {
console.error('Coupon count error:', error)
})
// 加载积分数量 const reload = async () => {
console.log(userId) // 如果已登录,获取最新用户信息
// getUserPointsStats(userId) if (isLoggedIn) {
// .then((res: any) => { try {
// setPointsCount(res.currentPoints || 0) const data = await fetchUserInfo();
// }) if (data) {
// .catch((error: any) => { // 获取openId
// console.error('Points stats error:', error) if (!data.openid) {
// }) Taro.login({
// 加载礼品劵数量 success: (res) => {
setGiftCount(0) getWxOpenId({code: res.code}).then(() => {
// pageUserGiftLog({userId, page: 1, limit: 1}).then(res => { })
// setGiftCount(res.count || 0) }
// }) })
}
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(() => {
})
}
})
}
} }
}).catch(() => { }
console.log('未登录') } catch (error) {
}); console.error('获取用户信息失败:', error)
} }
}); }
}; };
const showAuthModal = () => { const showAuthModal = () => {
@@ -132,7 +94,7 @@ function UserCard() {
}; };
/* 获取用户手机号 */ /* 获取用户手机号 */
const handleGetPhoneNumber = ({detail}: {detail: {code?: string, encryptedData?: string, iv?: string}}) => { const handleGetPhoneNumber = ({detail}: { detail: { code?: string, encryptedData?: string, iv?: string } }) => {
const {code, encryptedData, iv} = detail const {code, encryptedData, iv} = detail
Taro.login({ Taro.login({
success: function () { success: function () {
@@ -163,10 +125,21 @@ function UserCard() {
return false; return false;
} }
// 登录成功 // 登录成功
Taro.setStorageSync('access_token', res.data.data.access_token) const token = res.data.data.access_token;
Taro.setStorageSync('UserId', res.data.data.user.userId) const userData = res.data.data.user;
setUserInfo(res.data.data.user)
setIsLogin(true) // 使用useUser Hook的loginUser方法更新状态
loginUser(token, userData);
// 显示登录成功提示
Taro.showToast({
title: '登录成功',
icon: 'success',
duration: 1500
})
// 刷新页面数据
reload();
} }
}) })
} else { } else {
@@ -192,17 +165,17 @@ function UserCard() {
<div className={'user-card-header flex w-full justify-between items-center pt-4'}> <div className={'user-card-header flex w-full justify-between items-center pt-4'}>
<div className={'flex items-center mx-4'}> <div className={'flex items-center mx-4'}>
{ {
IsLogin ? ( isLoggedIn ? (
<Avatar size="large" src={userInfo?.avatar} shape="round"/> <Avatar size="large" src={user?.avatar} shape="round"/>
) : ( ) : (
<Button className={'text-black'} open-type="getPhoneNumber" onGetPhoneNumber={handleGetPhoneNumber}> <Button className={'text-black'} open-type="getPhoneNumber" onGetPhoneNumber={handleGetPhoneNumber}>
<Avatar size="large" src={userInfo?.avatar} shape="round"/> <Avatar size="large" src={user?.avatar} shape="round"/>
</Button> </Button>
) )
} }
<div className={'user-info flex flex-col px-2'}> <div className={'user-info flex flex-col px-2'}>
<div className={'py-1 text-black font-bold'}>{getDisplayName()}</div> <div className={'py-1 text-black font-bold'}>{getDisplayName()}</div>
{IsLogin ? ( {isLoggedIn ? (
<div className={'grade text-xs py-1'}> <div className={'grade text-xs py-1'}>
<Tag type="success" round> <Tag type="success" round>
<div className={'p-1'}> <div className={'p-1'}>
@@ -222,17 +195,17 @@ function UserCard() {
<div className={'item flex justify-center flex-col items-center'} <div className={'item flex justify-center flex-col items-center'}
onClick={() => navTo('/user/wallet/wallet', true)}> onClick={() => navTo('/user/wallet/wallet', true)}>
<span className={'text-sm text-gray-500'}></span> <span className={'text-sm text-gray-500'}></span>
<span className={'text-xl'}>¥ {userInfo?.balance || '0.00'}</span> <span className={'text-xl'}>¥ {data?.balance || '0.00'}</span>
</div> </div>
<div className={'item flex justify-center flex-col items-center'} <div className={'item flex justify-center flex-col items-center'}
onClick={() => navTo('/user/coupon/index', true)}> onClick={() => navTo('/user/coupon/index', true)}>
<span className={'text-sm text-gray-500'}></span> <span className={'text-sm text-gray-500'}></span>
<span className={'text-xl'}>{couponCount}</span> <span className={'text-xl'}>{data?.coupons || 0}</span>
</div> </div>
<div className={'item flex justify-center flex-col items-center'} <div className={'item flex justify-center flex-col items-center'}
onClick={() => navTo('/user/gift/index', true)}> onClick={() => navTo('/user/gift/index', true)}>
<span className={'text-sm text-gray-500'}></span> <span className={'text-sm text-gray-500'}></span>
<span className={'text-xl'}>{giftCount}</span> <span className={'text-xl'}>{data?.giftCards || 0}</span>
</div> </div>
{/*<div className={'item flex justify-center flex-col items-center'}>*/} {/*<div className={'item flex justify-center flex-col items-center'}>*/}
{/* <span className={'text-sm text-gray-500'}>积分</span>*/} {/* <span className={'text-sm text-gray-500'}>积分</span>*/}

View File

@@ -2,13 +2,11 @@ import {Button} from '@nutui/nutui-react-taro'
import {Avatar, Tag} from '@nutui/nutui-react-taro' import {Avatar, Tag} from '@nutui/nutui-react-taro'
import {View, Text} from '@tarojs/components' import {View, Text} from '@tarojs/components'
import {Scan} from '@nutui/icons-react-taro'; import {Scan} from '@nutui/icons-react-taro';
import {getUserInfo, getWxOpenId} from '@/api/layout'; import {getWxOpenId} from '@/api/layout';
import Taro from '@tarojs/taro'; import Taro from '@tarojs/taro';
import {useEffect, useState} from "react"; import {useEffect} from "react";
import {User} from "@/api/system/user/model";
import navTo from "@/utils/common"; import navTo from "@/utils/common";
import {TenantId} from "@/config/app"; import {TenantId} from "@/config/app";
import {getMyAvailableCoupons} from "@/api/shop/shopUserCoupon";
import {useUser} from "@/hooks/useUser"; import {useUser} from "@/hooks/useUser";
import {useUserData} from "@/hooks/useUserData"; import {useUserData} from "@/hooks/useUserData";
@@ -22,19 +20,7 @@ function UserCard() {
getDisplayName, getDisplayName,
getRoleName getRoleName
} = useUser(); } = useUser();
const { data, refresh } = useUserData() const {data} = useUserData()
const [couponCount, setCouponCount] = useState(0)
const [pointsCount, setPointsCount] = useState(0)
const [giftCount, setGiftCount] = useState(0)
// 下拉刷新
const handleRefresh = async () => {
await refresh()
Taro.showToast({
title: '刷新成功',
icon: 'success'
})
}
useEffect(() => { useEffect(() => {
// Taro.getSetting获取用户的当前设置。返回值中只会出现小程序已经向用户请求过的权限。 // Taro.getSetting获取用户的当前设置。返回值中只会出现小程序已经向用户请求过的权限。
@@ -53,32 +39,6 @@ function UserCard() {
}); });
}, []); }, []);
const loadUserStats = (userId: number) => {
// 加载优惠券数量
getMyAvailableCoupons()
.then((coupons: any) => {
setCouponCount(coupons?.length || 0)
})
.catch((error: any) => {
console.error('Coupon count error:', error)
})
// 加载积分数量
console.log(userId)
setPointsCount(0)
// getUserPointsStats(userId)
// .then((res: any) => {
// setPointsCount(res.currentPoints || 0)
// })
// .catch((error: any) => {
// console.error('Points stats error:', error)
// })
// 加载礼品劵数量
setGiftCount(0)
// pageUserGiftLog({userId, page: 1, limit: 1}).then(res => {
// setGiftCount(res.count || 0)
// })
}
const reload = async () => { const reload = async () => {
// 如果已登录,获取最新用户信息 // 如果已登录,获取最新用户信息
@@ -86,11 +46,6 @@ function UserCard() {
try { try {
const data = await fetchUserInfo(); const data = await fetchUserInfo();
if (data) { if (data) {
// 加载用户统计数据
if (data.userId) {
loadUserStats(data.userId)
}
// 获取openId // 获取openId
if (!data.openid) { if (!data.openid) {
Taro.login({ Taro.login({
@@ -142,7 +97,7 @@ function UserCard() {
}; };
/* 获取用户手机号 */ /* 获取用户手机号 */
const handleGetPhoneNumber = ({detail}: {detail: {code?: string, encryptedData?: string, iv?: string}}) => { const handleGetPhoneNumber = ({detail}: { detail: { code?: string, encryptedData?: string, iv?: string } }) => {
const {code, encryptedData, iv} = detail const {code, encryptedData, iv} = detail
Taro.login({ Taro.login({
success: function () { success: function () {
@@ -234,15 +189,15 @@ function UserCard() {
) : ''} ) : ''}
</View> </View>
</View> </View>
{isAdmin() && <Scan onClick={() => navTo('/user/store/verification', true)} />} {isAdmin() && <Scan onClick={() => navTo('/user/store/verification', true)}/>}
<View className={'mr-4 text-sm px-3 py-1 text-black border-gray-400 border-solid border-2 rounded-3xl'} <View className={'mr-4 text-sm px-3 py-1 text-black border-gray-400 border-solid border-2 rounded-3xl'}
onClick={() => navTo('/user/profile/profile', true)}> onClick={() => navTo('/user/profile/profile', true)}>
{'个人资料'} {'个人资料'}
</View> </View>
</View> </View>
<View className={'flex justify-around mt-1'}> <View className={'flex justify-around mt-1'}>
<View className={'item flex justify-center flex-col items-center'} <View className={'item flex justify-center flex-col items-center'}
onClick={() => navTo('/user/wallet/wallet', true)}> onClick={() => navTo('/user/wallet/wallet', true)}>
<Text className={'text-sm text-gray-500'}></Text> <Text className={'text-sm text-gray-500'}></Text>
<Text className={'text-xl'}>{data?.balance || '0.00'}</Text> <Text className={'text-xl'}>{data?.balance || '0.00'}</Text>
</View> </View>
@@ -251,12 +206,12 @@ function UserCard() {
<Text className={'text-xl'}>{data?.points || 0}</Text> <Text className={'text-xl'}>{data?.points || 0}</Text>
</View> </View>
<View className={'item flex justify-center flex-col items-center'} <View className={'item flex justify-center flex-col items-center'}
onClick={() => navTo('/user/coupon/index', true)}> onClick={() => navTo('/user/coupon/index', true)}>
<Text className={'text-sm text-gray-500'}></Text> <Text className={'text-sm text-gray-500'}></Text>
<Text className={'text-xl'}>{data?.coupons || 0}</Text> <Text className={'text-xl'}>{data?.coupons || 0}</Text>
</View> </View>
<View className={'item flex justify-center flex-col items-center'} <View className={'item flex justify-center flex-col items-center'}
onClick={() => navTo('/user/gift/index', true)}> onClick={() => navTo('/user/gift/index', true)}>
<Text className={'text-sm text-gray-500'}></Text> <Text className={'text-sm text-gray-500'}></Text>
<Text className={'text-xl'}>{data?.giftCards || 0}</Text> <Text className={'text-xl'}>{data?.giftCards || 0}</Text>
</View> </View>