import { useState, useEffect } from 'react'; import Taro from '@tarojs/taro'; import { User } from '@/api/system/user/model'; import { getUserInfo, updateUserInfo, loginByOpenId, getWxOpenId } from '@/api/layout'; import { TenantId } from '@/config/app'; import { handleInviteRelation } from '@/utils/invite'; // 用户Hook export const useUser = () => { const [user, setUser] = useState(null); const [isLoggedIn, setIsLoggedIn] = useState(false); const [loading, setLoading] = useState(true); // 自动登录(通过OpenID) const autoLoginByOpenId = async () => { try { const res = await new Promise((resolve, reject) => { Taro.login({ success: (loginRes) => { loginByOpenId({ code: loginRes.code, tenantId: TenantId }).then(async (data) => { if (data) { // 保存登录信息 saveUserToStorage(data.access_token, data.user); setUser(data.user); setIsLoggedIn(true); // 自动登录成功后,补齐 openid(JSAPI 微信支付必需) // 防止后续支付时报"下单账号与支付账号不一致" if (!data.user?.openid) { try { const freshCode = await new Promise((resolve, reject) => { Taro.login({ success: (r) => resolve(r.code as string), fail: () => resolve(undefined), }); }); if (freshCode) { await getWxOpenId({ code: freshCode }); } } catch (_e) { console.warn('自动登录后绑定 openid 失败'); } } // 处理邀请关系 if (data.user?.userId) { try { const inviteSuccess = await handleInviteRelation(data.user.userId); if (inviteSuccess) { console.log('自动登录时邀请关系建立成功'); } } catch (error) { console.error('自动登录时处理邀请关系失败:', error); } } resolve(data.user); } else { reject(new Error('自动登录失败')); } }).catch(_ => { // 登录失败(通常是新用户尚未注册/未绑定手机号等)。 // 这里不做任何“自动跳转”,避免用户点击「我的」时被强制带到分销/申请页,体验割裂。 // 需要登录的页面请使用 utils/auth 的 ensureLoggedIn / goToRegister 做显式跳转。 reject(new Error('autoLoginByOpenId failed')); }); }, fail: reject }); }); return res; } catch (error) { const msg = error instanceof Error ? error.message : String(error); // 新用户首次进入、未绑定手机号等场景属于“预期失败”,避免刷屏报错。 if (msg !== 'autoLoginByOpenId failed') { console.error('自动登录失败:', error); } return null; } }; // 从本地存储加载用户数据 const loadUserFromStorage = async () => { try { const token = Taro.getStorageSync('access_token'); const userData = Taro.getStorageSync('User'); const userId = Taro.getStorageSync('UserId'); const tenantId = Taro.getStorageSync('TenantId'); if (token && userData) { const userInfo = typeof userData === 'string' ? JSON.parse(userData) : userData; setUser(userInfo); setIsLoggedIn(true); } else if (token && userId) { // 如果有token和userId但没有完整用户信息,标记为已登录但需要获取用户信息 setIsLoggedIn(true); setUser({ userId, tenantId } as User); } else { // 没有本地登录信息,尝试自动登录 console.log('没有本地登录信息,尝试自动登录...'); const autoLoginResult = await autoLoginByOpenId(); if (!autoLoginResult) { setUser(null); setIsLoggedIn(false); } } } catch (error) { console.error('加载用户数据失败:', error); setUser(null); setIsLoggedIn(false); } finally { setLoading(false); } }; // 保存用户数据到本地存储 const saveUserToStorage = (token: string, userInfo: User) => { try { Taro.setStorageSync('access_token', token); Taro.setStorageSync('User', userInfo); // 确保关键字段不为空时才保存,避免覆盖现有数据 if (userInfo.userId) { Taro.setStorageSync('UserId', userInfo.userId); } if (userInfo.tenantId) { Taro.setStorageSync('TenantId', userInfo.tenantId); } if (userInfo.phone) { Taro.setStorageSync('Phone', userInfo.phone); } // 保存头像和昵称信息 if (userInfo.avatar) { Taro.setStorageSync('Avatar', userInfo.avatar); } if (userInfo.nickname) { Taro.setStorageSync('Nickname', userInfo.nickname); } } catch (error) { console.error('保存用户数据失败:', error); } }; // 登录用户 const loginUser = (token: string, userInfo: User) => { setUser(userInfo); setIsLoggedIn(true); saveUserToStorage(token, userInfo); }; // 退出登录 const logoutUser = () => { setUser(null); setIsLoggedIn(false); // 清除本地存储 try { Taro.removeStorageSync('access_token'); Taro.removeStorageSync('User'); Taro.removeStorageSync('UserId'); Taro.removeStorageSync('TenantId'); Taro.removeStorageSync('Phone'); Taro.removeStorageSync('userInfo'); } catch (error) { console.error('清除用户数据失败:', error); } }; // 从服务器获取最新用户信息 const fetchUserInfo = async () => { if (!isLoggedIn) { return null; } try { setLoading(true); const userInfo = await getUserInfo(); setUser(userInfo); // 更新本地存储 const token = Taro.getStorageSync('access_token'); if (token) { saveUserToStorage(token, userInfo); } return userInfo; } catch (error) { console.error('获取用户信息失败:', error); // 如果获取失败,可能是token过期,清除登录状态 const errorMessage = error instanceof Error ? error.message : String(error); if (errorMessage?.includes('401') || errorMessage?.includes('未授权')) { logoutUser(); } return null; } finally { setLoading(false); } }; // 更新用户信息 const updateUser = async (userData: Partial) => { if (!user) { throw new Error('用户未登录'); } try { // 先获取最新的用户信息,确保我们有完整的数据 const latestUserInfo = await getUserInfo(); // 合并最新的用户信息和要更新的数据 const updatedUser = { ...latestUserInfo, ...userData }; // 调用API更新用户信息 await updateUserInfo(updatedUser); // 更新本地状态 setUser(updatedUser); // 更新本地存储 const token = Taro.getStorageSync('access_token'); if (token) { saveUserToStorage(token, updatedUser); } Taro.showToast({ title: '更新成功', icon: 'success', duration: 1500 }); return updatedUser; } catch (error) { console.error('更新用户信息失败:', error); Taro.showToast({ title: '更新失败', icon: 'error', duration: 1500 }); throw error; } }; // 检查是否有特定权限 const hasPermission = (permission: string) => { if (!user || !user.authorities) { return false; } return user.authorities.some(auth => auth.authority === permission); }; // 检查是否有特定角色 const hasRole = (roleCode: string) => { if (!user || !user.roles) { return false; } return user.roles.some(role => role.roleCode === roleCode); }; // 获取用户头像URL const getAvatarUrl = () => { return user?.avatar || user?.avatarUrl || ''; }; const getUserId = () => { return user?.userId; }; // 获取用户显示名称 const getDisplayName = () => { return user?.nickname || user?.realName || user?.username || '未登录'; }; // 获取用户显示的角色(同步版本) const getRoleName = () => { if(hasRole('superAdmin')){ return '超级管理员'; } if(hasRole('admin')){ return '管理员'; } if(hasRole('staff')){ return '员工'; } if(hasRole('vip')){ return 'VIP会员'; } return '注册用户'; } // 检查用户是否已实名认证 const isCertified = () => { return user?.certification === true; }; // 检查用户是否是管理员 const isAdmin = () => { // Some backends use `1/0` (or `1/2`) instead of boolean. const v: any = (user as any)?.isAdmin; return v === true || v === 1 || v === '1'; }; const isSuperAdmin = () => { const v: any = (user as any)?.isSuperAdmin; return v === true || v === 1 || v === '1'; }; // 获取用户余额 const getBalance = () => { return user?.balance || 0; }; // 获取用户积分 const getPoints = () => { return user?.points || 0; }; // 初始化时加载用户数据 useEffect(() => { loadUserFromStorage().catch(error => { console.error('初始化用户数据失败:', error); setLoading(false); }); }, []); return { // 状态 user, isLoggedIn, loading, // 方法 loginUser, logoutUser, fetchUserInfo, updateUser, loadUserFromStorage, autoLoginByOpenId, // 工具方法 hasPermission, hasRole, getAvatarUrl, getDisplayName, getRoleName, isCertified, isAdmin, getBalance, getPoints, getUserId, isSuperAdmin }; };