Files
tyapi-frontend/src/stores/user.js
2026-02-27 14:49:21 +08:00

539 lines
15 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 用户状态管理 Store
*
* 后端响应格式说明:
* 所有API响应都被包装在标准格式中
* {
* "success": true,
* "data": { ... }, // 真正的数据在这里
* "message": "操作成功",
* "requestId": "",
* "timestamp": 1752469413
* }
*
* 使用示例:
*
* // 在组件中使用
* import { useUserStore } from '@/stores/user'
*
* const userStore = useUserStore()
*
* // 登录
* const loginData = {
* method: 'sms', // 或 'password'
* phone: '13800138000',
* code: '123456', // 短信登录时需要
* password: 'password123' // 密码登录时需要
* }
* const result = await userStore.login(loginData)
* if (result.success) {
* console.log('登录成功')
* // result.data 包含: { user, access_token, token_type, expires_in, login_method }
* }
*
* // 注册
* const registerData = {
* phone: '13800138000',
* password: 'password123',
* confirmPassword: 'password123',
* code: '123456'
* }
* const result = await userStore.register(registerData)
* if (result.success) {
* // result.data 包含注册成功的数据
* }
*
* // 发送验证码
* const result = await userStore.sendCode('13800138000', 'register')
* if (result.success) {
* // result.data 包含验证码发送结果
* }
*
* // 重置密码
* const resetData = {
* phone: '13800138000',
* newPassword: 'newpassword123',
* confirmNewPassword: 'newpassword123',
* code: '123456'
* }
* const result = await userStore.resetPassword(resetData)
* if (result.success) {
* // result.data 包含重置密码结果
* }
*
* // 获取用户信息
* const result = await userStore.fetchUserProfile()
* if (result.success) {
* // result.data 包含用户信息: { id, phone, created_at, updated_at }
* }
*
* // 修改密码
* const passwordData = {
* oldPassword: 'oldpassword123',
* newPassword: 'newpassword123',
* confirmNewPassword: 'newpassword123',
* code: '123456'
* }
* const result = await userStore.changePassword(passwordData)
* if (result.success) {
* // result.data 包含修改密码结果
* }
*
* // 登出
* userStore.logout()
*
* // 检查登录状态
* const isAuth = await userStore.checkAuth()
*
* // 响应式状态
* console.log(userStore.isLoggedIn) // 是否已登录
* console.log(userStore.userInfo) // 用户信息: { id, phone, created_at, updated_at }
* console.log(userStore.loading) // 加载状态
*/
import { userApi } from '@/api'
import router from '@/router'
import { authEventBus } from '@/utils/request'
import { generateSMSRequest } from '@/utils/smsSignature'
import { clearLocalVersions, saveLocalVersions, VERSION_CONFIG, versionChecker } from '@/utils/version'
import { ElMessage } from 'element-plus'
export const useUserStore = defineStore('user', () => {
// 状态
const user = ref(null)
const accessToken = ref(localStorage.getItem('access_token') || '')
const tokenType = ref(localStorage.getItem('token_type') || 'Bearer')
const isAuthenticated = ref(false)
const loading = ref(false)
// 新增:初始化标志
const initialized = ref(false)
// 版本管理
const tokenVersion = ref(localStorage.getItem('token_version') || VERSION_CONFIG.TOKEN_VERSION)
const appVersion = ref(localStorage.getItem('app_version') || VERSION_CONFIG.APP_VERSION)
// 计算属性
const isLoggedIn = computed(() => {
return isAuthenticated.value && !!accessToken.value
})
const userInfo = computed(() => user.value)
// 新增:用户类型相关计算属性
const isAdmin = computed(() => {
return user.value?.user_type === 'admin'
})
const userType = computed(() => {
return user.value?.user_type || 'user'
})
// 新增:用户认证状态计算属性
const isCertified = computed(() => {
return user.value?.is_certified || false
})
// 检查用户信息是否完整
const isUserInfoComplete = computed(() => {
return user.value &&
user.value.id &&
user.value.phone &&
user.value.user_type !== undefined
})
// 强制刷新用户信息
const refreshUserProfile = async () => {
if (!accessToken.value) {
return { success: false, error: '未登录' }
}
loading.value = true
try {
const result = await fetchUserProfile()
return result
} catch (error) {
return { success: false, error }
} finally {
loading.value = false
}
}
const hasRole = (role) => {
return userType.value === role
}
// 监听认证错误事件
const handleAuthError = (message) => {
console.log('用户store收到认证错误事件:', message)
logout()
}
// 处理版本退出登录事件
const handleVersionLogout = (event) => {
console.log('收到版本退出登录事件:', event.detail)
logout()
ElMessage.error('系统已更新,请重新登录')
router.push('/auth/login')
}
// 处理版本刷新事件
const handleVersionRefresh = (event) => {
console.log('收到版本刷新事件:', event.detail)
saveLocalVersions({ appVersion: VERSION_CONFIG.APP_VERSION })
ElMessage.success('应用已更新,正在刷新页面...')
setTimeout(() => {
window.location.reload()
}, 1500)
}
// 版本检查
const checkVersions = () => {
const currentTokenVersion = localStorage.getItem('token_version') || VERSION_CONFIG.TOKEN_VERSION
const currentAppVersion = localStorage.getItem('app_version') || VERSION_CONFIG.APP_VERSION
// 检查token版本
if (currentTokenVersion !== VERSION_CONFIG.TOKEN_VERSION) {
console.warn('Token版本不匹配需要重新登录', {
current: currentTokenVersion,
expected: VERSION_CONFIG.TOKEN_VERSION
})
// 清除所有认证相关数据
logout()
// 显示提示信息
ElMessage.error('系统已更新,请重新登录')
// 跳转到登录页面
router.push('/auth/login')
return false
}
// 检查应用版本
if (currentAppVersion !== VERSION_CONFIG.APP_VERSION) {
console.log('应用版本已更新,刷新页面', {
current: currentAppVersion,
expected: VERSION_CONFIG.APP_VERSION
})
// 更新本地存储的应用版本
saveLocalVersions({ appVersion: VERSION_CONFIG.APP_VERSION })
// 显示提示信息
ElMessage.success('应用已更新,正在刷新页面...')
// 延迟刷新页面
setTimeout(() => {
window.location.reload()
}, 1500)
return false
}
return true
}
// 登录
const login = async (loginData) => {
loading.value = true
try {
let response
if (loginData.method === 'sms') {
response = await userApi.loginWithSMS({
phone: loginData.phone,
code: loginData.code
})
} else {
response = await userApi.loginWithPassword({
phone: loginData.phone,
password: loginData.password
})
}
// 后端返回格式: { success: true, data: { user, access_token, token_type, ... }, message, ... }
// 真正的数据在 response.data 中
const responseData = response.data
// 保存token和用户信息
accessToken.value = responseData.access_token
tokenType.value = responseData.token_type
user.value = responseData.user
isAuthenticated.value = true
// 保存到localStorage
localStorage.setItem('access_token', responseData.access_token)
localStorage.setItem('token_type', responseData.token_type)
saveLocalVersions({ tokenVersion: VERSION_CONFIG.TOKEN_VERSION })
// 登录成功后,主动获取最新的用户信息以确保数据完整
try {
const profileResult = await fetchUserProfile()
if (profileResult.success) {
console.log('登录后用户信息更新成功')
// 如果获取到更完整的用户信息,更新用户数据
if (profileResult.data) {
user.value = { ...user.value, ...profileResult.data }
}
}
} catch (profileError) {
console.warn('登录后获取用户信息失败:', profileError)
// 即使获取用户信息失败,也不影响登录流程
}
return { success: true, data: responseData }
} catch (error) {
console.error('登录失败:', error)
return { success: false, error }
} finally {
loading.value = false
}
}
// 注册
const register = async (registerData) => {
loading.value = true
try {
const response = await userApi.register({
phone: registerData.phone,
password: registerData.password,
confirm_password: registerData.confirmPassword,
code: registerData.code
})
// 后端返回格式: { success: true, data: {...}, message, ... }
return { success: true, data: response.data }
} catch (error) {
return { success: false, error }
} finally {
loading.value = false
}
}
// 重置密码
const resetPassword = async (resetData) => {
loading.value = true
try {
const response = await userApi.resetPassword({
phone: resetData.phone,
new_password: resetData.newPassword,
confirm_new_password: resetData.confirmNewPassword,
code: resetData.code
})
// 后端返回格式: { success: true, data: {...}, message, ... }
return { success: true, data: response.data }
} catch (error) {
return { success: false, error }
} finally {
loading.value = false
}
}
// 发送验证码(使用自定义编码和签名)
const sendCode = async (phone, scene, captchaVerifyParam = null) => {
try {
// 1. 生成签名并编码请求数据
const encodedRequest = await generateSMSRequest(phone, scene)
// 2. 如果有滑块验证码参数,添加到请求数据中
if (captchaVerifyParam) {
encodedRequest.captchaVerifyParam = captchaVerifyParam
}
// 3. 发送编码后的请求
const response = await userApi.sendCode(encodedRequest)
// 后端返回格式: { success: true, data: {...}, message, ... }
return { success: true, data: response.data }
} catch (error) {
console.error('发送验证码失败:', error)
return { success: false, error }
}
}
// 获取用户信息
const fetchUserProfile = async () => {
loading.value = true
try {
const response = await userApi.getProfile()
// 后端返回格式: { success: true, data: {...}, message, ... }
const userData = response.data
user.value = userData
isAuthenticated.value = true
// 记录认证状态变化
if (userData.is_certified !== undefined) {
console.log('用户认证状态:', userData.is_certified ? '已认证' : '未认证')
}
return { success: true, data: userData }
} catch (error) {
return { success: false, error }
} finally {
loading.value = false
}
}
// 修改密码
const changePassword = async (passwordData) => {
loading.value = true
try {
const response = await userApi.changePassword({
old_password: passwordData.oldPassword,
new_password: passwordData.newPassword,
confirm_new_password: passwordData.confirmNewPassword,
code: passwordData.code
})
// 后端返回格式: { success: true, data: {...}, message, ... }
return { success: true, data: response.data }
} catch (error) {
return { success: false, error }
} finally {
loading.value = false
}
}
// 登出
const logout = () => {
// 清除状态
user.value = null
accessToken.value = ''
tokenType.value = 'Bearer'
isAuthenticated.value = false
loading.value = false
// 清除localStorage
localStorage.removeItem('access_token')
localStorage.removeItem('token_type')
clearLocalVersions()
}
// 检查登录状态
const checkAuth = async () => {
if (!accessToken.value) {
return false
}
try {
const result = await fetchUserProfile()
return result.success
} catch (error) {
logout()
return false
}
}
// 初始化标志,防止重复初始化
let isInitializing = false
// 初始化
const init = async () => {
// 如果已经初始化完成,直接返回
if (initialized.value) {
return
}
// 如果正在初始化,等待完成
if (isInitializing) {
// 等待初始化完成
while (isInitializing) {
await new Promise(resolve => setTimeout(resolve, 50))
}
return
}
isInitializing = true
try {
// 监听认证错误事件(只注册一次)
if (!authEventBus.listeners.includes(handleAuthError)) {
authEventBus.onAuthError(handleAuthError)
}
// 监听版本更新事件(只注册一次)
if (!window.hasVersionListeners) {
window.addEventListener('version:logout', handleVersionLogout)
window.addEventListener('version:refresh', handleVersionRefresh)
window.hasVersionListeners = true
}
// 进行版本检查
if (!checkVersions()) {
initialized.value = true
return
}
if (accessToken.value && !user.value) {
// 有token但无用户信息自动拉取
loading.value = true
try {
const result = await fetchUserProfile()
isAuthenticated.value = result.success
// 如果认证成功,启动版本检查器
if (result.success) {
versionChecker.startAutoCheck()
}
} catch {
isAuthenticated.value = false
logout()
} finally {
loading.value = false
initialized.value = true
}
} else {
// 如果已经认证,启动版本检查器
if (isAuthenticated.value) {
versionChecker.startAutoCheck()
}
initialized.value = true
}
} finally {
isInitializing = false
}
}
// 清理
const cleanup = () => {
// 停止版本检查器
versionChecker.stopAutoCheck()
// 移除事件监听器
window.removeEventListener('version:logout', handleVersionLogout)
window.removeEventListener('version:refresh', handleVersionRefresh)
}
return {
// 状态
user,
accessToken,
tokenType,
isAuthenticated,
loading,
initialized, // 新增
// 计算属性
isLoggedIn,
userInfo,
isAdmin,
userType,
isCertified, // 新增
isUserInfoComplete, // 新增
hasRole,
// 方法
login,
register,
resetPassword,
sendCode,
fetchUserProfile,
changePassword,
logout,
checkAuth,
init,
cleanup,
refreshUserProfile // 新增
}
})