502 lines
13 KiB
JavaScript
502 lines
13 KiB
JavaScript
|
|
/**
|
|||
|
|
* 用户状态管理 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 { 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) => {
|
|||
|
|
try {
|
|||
|
|
const response = await userApi.sendCode({
|
|||
|
|
phone,
|
|||
|
|
scene
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
// 后端返回格式: { success: true, data: {...}, message, ... }
|
|||
|
|
return { success: true, data: response.data }
|
|||
|
|
} catch (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
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 初始化
|
|||
|
|
const init = async () => {
|
|||
|
|
// 监听认证错误事件
|
|||
|
|
authEventBus.onAuthError(handleAuthError)
|
|||
|
|
|
|||
|
|
// 监听版本更新事件
|
|||
|
|
window.addEventListener('version:logout', handleVersionLogout)
|
|||
|
|
window.addEventListener('version:refresh', handleVersionRefresh)
|
|||
|
|
|
|||
|
|
// 进行版本检查
|
|||
|
|
if (!checkVersions()) {
|
|||
|
|
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
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 清理
|
|||
|
|
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 // 新增
|
|||
|
|
}
|
|||
|
|
})
|