2025-05-02 23:56:59 +08:00
|
|
|
|
import { defineStore } from 'pinia'
|
|
|
|
|
|
import { ref, computed } from 'vue'
|
|
|
|
|
|
|
|
|
|
|
|
export const useUserStore = defineStore('user', () => {
|
|
|
|
|
|
// 状态
|
|
|
|
|
|
const token = ref(localStorage.getItem('user_token') || '')
|
|
|
|
|
|
const userId = ref(parseInt(localStorage.getItem('user_id') || '0') || null)
|
|
|
|
|
|
const username = ref(localStorage.getItem('username') || '')
|
2025-09-21 13:33:09 +08:00
|
|
|
|
const userIdLogin = ref(localStorage.getItem('user_id_login') || '') // 用于登录的user_id
|
|
|
|
|
|
const phoneNumber = ref(localStorage.getItem('phone_number') || '')
|
|
|
|
|
|
const avatar = ref(localStorage.getItem('avatar') || '')
|
2025-05-02 23:56:59 +08:00
|
|
|
|
const userRole = ref(localStorage.getItem('user_role') || '')
|
|
|
|
|
|
|
|
|
|
|
|
// 计算属性
|
|
|
|
|
|
const isLoggedIn = computed(() => !!token.value)
|
|
|
|
|
|
const isAdmin = computed(() => userRole.value === 'admin' || userRole.value === 'superadmin')
|
|
|
|
|
|
const isSuperAdmin = computed(() => userRole.value === 'superadmin')
|
|
|
|
|
|
|
|
|
|
|
|
// 动作
|
|
|
|
|
|
async function login(credentials) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const formData = new FormData()
|
2025-09-21 13:33:09 +08:00
|
|
|
|
// 支持user_id或phone_number登录
|
|
|
|
|
|
formData.append('username', credentials.loginId) // 使用loginId作为通用登录标识
|
2025-05-02 23:56:59 +08:00
|
|
|
|
formData.append('password', credentials.password)
|
|
|
|
|
|
|
|
|
|
|
|
const response = await fetch('/api/auth/token', {
|
|
|
|
|
|
method: 'POST',
|
|
|
|
|
|
body: formData
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
if (!response.ok) {
|
|
|
|
|
|
const error = await response.json()
|
2025-09-21 13:33:09 +08:00
|
|
|
|
|
2025-09-17 03:59:51 +08:00
|
|
|
|
// 如果是423锁定状态码,抛出包含状态码的错误
|
|
|
|
|
|
if (response.status === 423) {
|
|
|
|
|
|
const lockError = new Error(error.detail || '账户被锁定')
|
|
|
|
|
|
lockError.status = 423
|
|
|
|
|
|
lockError.headers = response.headers
|
|
|
|
|
|
throw lockError
|
|
|
|
|
|
}
|
2025-09-21 13:33:09 +08:00
|
|
|
|
|
2025-05-02 23:56:59 +08:00
|
|
|
|
throw new Error(error.detail || '登录失败')
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const data = await response.json()
|
|
|
|
|
|
|
|
|
|
|
|
// 更新状态
|
|
|
|
|
|
token.value = data.access_token
|
|
|
|
|
|
userId.value = data.user_id
|
|
|
|
|
|
username.value = data.username
|
2025-09-21 13:33:09 +08:00
|
|
|
|
userIdLogin.value = data.user_id_login
|
|
|
|
|
|
phoneNumber.value = data.phone_number || ''
|
|
|
|
|
|
avatar.value = data.avatar || ''
|
2025-05-02 23:56:59 +08:00
|
|
|
|
userRole.value = data.role
|
|
|
|
|
|
|
|
|
|
|
|
// 保存到本地存储
|
|
|
|
|
|
localStorage.setItem('user_token', data.access_token)
|
|
|
|
|
|
localStorage.setItem('user_id', data.user_id)
|
|
|
|
|
|
localStorage.setItem('username', data.username)
|
2025-09-21 13:33:09 +08:00
|
|
|
|
localStorage.setItem('user_id_login', data.user_id_login)
|
|
|
|
|
|
localStorage.setItem('phone_number', data.phone_number || '')
|
|
|
|
|
|
localStorage.setItem('avatar', data.avatar || '')
|
2025-05-02 23:56:59 +08:00
|
|
|
|
localStorage.setItem('user_role', data.role)
|
|
|
|
|
|
|
|
|
|
|
|
return true
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('登录错误:', error)
|
|
|
|
|
|
throw error
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function logout() {
|
|
|
|
|
|
// 清除状态
|
|
|
|
|
|
token.value = ''
|
|
|
|
|
|
userId.value = null
|
|
|
|
|
|
username.value = ''
|
2025-09-21 13:33:09 +08:00
|
|
|
|
userIdLogin.value = ''
|
|
|
|
|
|
phoneNumber.value = ''
|
|
|
|
|
|
avatar.value = ''
|
2025-05-02 23:56:59 +08:00
|
|
|
|
userRole.value = ''
|
|
|
|
|
|
|
|
|
|
|
|
// 清除本地存储
|
|
|
|
|
|
localStorage.removeItem('user_token')
|
|
|
|
|
|
localStorage.removeItem('user_id')
|
|
|
|
|
|
localStorage.removeItem('username')
|
2025-09-21 13:33:09 +08:00
|
|
|
|
localStorage.removeItem('user_id_login')
|
|
|
|
|
|
localStorage.removeItem('phone_number')
|
|
|
|
|
|
localStorage.removeItem('avatar')
|
2025-05-02 23:56:59 +08:00
|
|
|
|
localStorage.removeItem('user_role')
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function initialize(admin) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const response = await fetch('/api/auth/initialize', {
|
|
|
|
|
|
method: 'POST',
|
|
|
|
|
|
headers: {
|
|
|
|
|
|
'Content-Type': 'application/json'
|
|
|
|
|
|
},
|
|
|
|
|
|
body: JSON.stringify(admin)
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
if (!response.ok) {
|
|
|
|
|
|
const error = await response.json()
|
|
|
|
|
|
throw new Error(error.detail || '初始化管理员失败')
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const data = await response.json()
|
|
|
|
|
|
|
|
|
|
|
|
// 更新状态
|
|
|
|
|
|
token.value = data.access_token
|
|
|
|
|
|
userId.value = data.user_id
|
|
|
|
|
|
username.value = data.username
|
2025-09-21 13:33:09 +08:00
|
|
|
|
userIdLogin.value = data.user_id_login
|
|
|
|
|
|
phoneNumber.value = data.phone_number || ''
|
|
|
|
|
|
avatar.value = data.avatar || ''
|
2025-05-02 23:56:59 +08:00
|
|
|
|
userRole.value = data.role
|
|
|
|
|
|
|
|
|
|
|
|
// 保存到本地存储
|
|
|
|
|
|
localStorage.setItem('user_token', data.access_token)
|
|
|
|
|
|
localStorage.setItem('user_id', data.user_id)
|
|
|
|
|
|
localStorage.setItem('username', data.username)
|
2025-09-21 13:33:09 +08:00
|
|
|
|
localStorage.setItem('user_id_login', data.user_id_login)
|
|
|
|
|
|
localStorage.setItem('phone_number', data.phone_number || '')
|
|
|
|
|
|
localStorage.setItem('avatar', data.avatar || '')
|
2025-05-02 23:56:59 +08:00
|
|
|
|
localStorage.setItem('user_role', data.role)
|
|
|
|
|
|
|
|
|
|
|
|
return true
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('初始化管理员错误:', error)
|
|
|
|
|
|
throw error
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function checkFirstRun() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const response = await fetch('/api/auth/check-first-run')
|
|
|
|
|
|
const data = await response.json()
|
|
|
|
|
|
return data.first_run
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('检查首次运行状态错误:', error)
|
|
|
|
|
|
return false
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 用于API请求的授权头
|
|
|
|
|
|
function getAuthHeaders() {
|
|
|
|
|
|
return {
|
|
|
|
|
|
'Authorization': `Bearer ${token.value}`
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 用户管理功能
|
|
|
|
|
|
async function getUsers() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const response = await fetch('/api/auth/users', {
|
|
|
|
|
|
headers: {
|
|
|
|
|
|
...getAuthHeaders()
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
if (!response.ok) {
|
|
|
|
|
|
throw new Error('获取用户列表失败')
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return await response.json()
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('获取用户列表错误:', error)
|
|
|
|
|
|
throw error
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function createUser(userData) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const response = await fetch('/api/auth/users', {
|
|
|
|
|
|
method: 'POST',
|
|
|
|
|
|
headers: {
|
|
|
|
|
|
'Content-Type': 'application/json',
|
|
|
|
|
|
...getAuthHeaders()
|
|
|
|
|
|
},
|
|
|
|
|
|
body: JSON.stringify(userData)
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
if (!response.ok) {
|
|
|
|
|
|
const error = await response.json()
|
|
|
|
|
|
throw new Error(error.detail || '创建用户失败')
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return await response.json()
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('创建用户错误:', error)
|
|
|
|
|
|
throw error
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function updateUser(userId, userData) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const response = await fetch(`/api/auth/users/${userId}`, {
|
|
|
|
|
|
method: 'PUT',
|
|
|
|
|
|
headers: {
|
|
|
|
|
|
'Content-Type': 'application/json',
|
|
|
|
|
|
...getAuthHeaders()
|
|
|
|
|
|
},
|
|
|
|
|
|
body: JSON.stringify(userData)
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
if (!response.ok) {
|
|
|
|
|
|
const error = await response.json()
|
|
|
|
|
|
throw new Error(error.detail || '更新用户失败')
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return await response.json()
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('更新用户错误:', error)
|
|
|
|
|
|
throw error
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function deleteUser(userId) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const response = await fetch(`/api/auth/users/${userId}`, {
|
|
|
|
|
|
method: 'DELETE',
|
|
|
|
|
|
headers: {
|
|
|
|
|
|
...getAuthHeaders()
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
if (!response.ok) {
|
|
|
|
|
|
const error = await response.json()
|
|
|
|
|
|
throw new Error(error.detail || '删除用户失败')
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return await response.json()
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('删除用户错误:', error)
|
|
|
|
|
|
throw error
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-21 13:33:09 +08:00
|
|
|
|
// 验证用户名并生成user_id
|
|
|
|
|
|
async function validateUsernameAndGenerateUserId(username) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const response = await fetch('/api/auth/validate-username', {
|
|
|
|
|
|
method: 'POST',
|
|
|
|
|
|
headers: {
|
|
|
|
|
|
'Content-Type': 'application/json',
|
|
|
|
|
|
...getAuthHeaders()
|
|
|
|
|
|
},
|
|
|
|
|
|
body: JSON.stringify({ username })
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
if (!response.ok) {
|
|
|
|
|
|
const error = await response.json()
|
|
|
|
|
|
throw new Error(error.detail || '用户名验证失败')
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return await response.json()
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('用户名验证错误:', error)
|
|
|
|
|
|
throw error
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 上传头像
|
|
|
|
|
|
async function uploadAvatar(file) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const formData = new FormData()
|
|
|
|
|
|
formData.append('file', file)
|
|
|
|
|
|
|
|
|
|
|
|
const response = await fetch('/api/auth/upload-avatar', {
|
|
|
|
|
|
method: 'POST',
|
|
|
|
|
|
headers: {
|
|
|
|
|
|
...getAuthHeaders()
|
|
|
|
|
|
},
|
|
|
|
|
|
body: formData
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
if (!response.ok) {
|
|
|
|
|
|
const error = await response.json()
|
|
|
|
|
|
throw new Error(error.detail || '头像上传失败')
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const data = await response.json()
|
|
|
|
|
|
|
|
|
|
|
|
// 更新本地头像状态
|
|
|
|
|
|
avatar.value = data.avatar_url
|
|
|
|
|
|
localStorage.setItem('avatar', data.avatar_url)
|
|
|
|
|
|
|
|
|
|
|
|
return data
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('头像上传错误:', error)
|
|
|
|
|
|
throw error
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取当前用户信息
|
|
|
|
|
|
async function getCurrentUser() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const response = await fetch('/api/auth/me', {
|
|
|
|
|
|
headers: {
|
|
|
|
|
|
...getAuthHeaders()
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
if (!response.ok) {
|
|
|
|
|
|
throw new Error('获取用户信息失败')
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const userData = await response.json()
|
|
|
|
|
|
|
|
|
|
|
|
// 更新本地状态
|
|
|
|
|
|
username.value = userData.username
|
|
|
|
|
|
userIdLogin.value = userData.user_id
|
|
|
|
|
|
phoneNumber.value = userData.phone_number || ''
|
|
|
|
|
|
avatar.value = userData.avatar || ''
|
|
|
|
|
|
userRole.value = userData.role
|
|
|
|
|
|
|
|
|
|
|
|
// 更新本地存储
|
|
|
|
|
|
localStorage.setItem('username', userData.username)
|
|
|
|
|
|
localStorage.setItem('user_id_login', userData.user_id)
|
|
|
|
|
|
localStorage.setItem('phone_number', userData.phone_number || '')
|
|
|
|
|
|
localStorage.setItem('avatar', userData.avatar || '')
|
|
|
|
|
|
localStorage.setItem('user_role', userData.role)
|
|
|
|
|
|
|
|
|
|
|
|
return userData
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('获取用户信息错误:', error)
|
|
|
|
|
|
throw error
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 更新个人资料
|
|
|
|
|
|
async function updateProfile(profileData) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const response = await fetch('/api/auth/profile', {
|
|
|
|
|
|
method: 'PUT',
|
|
|
|
|
|
headers: {
|
|
|
|
|
|
'Content-Type': 'application/json',
|
|
|
|
|
|
...getAuthHeaders()
|
|
|
|
|
|
},
|
|
|
|
|
|
body: JSON.stringify(profileData)
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
if (!response.ok) {
|
|
|
|
|
|
const error = await response.json()
|
|
|
|
|
|
throw new Error(error.detail || '更新个人资料失败')
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const userData = await response.json()
|
|
|
|
|
|
|
|
|
|
|
|
// 更新本地状态
|
2025-10-06 21:24:02 +08:00
|
|
|
|
if (typeof userData.username === 'string') {
|
|
|
|
|
|
username.value = userData.username
|
|
|
|
|
|
localStorage.setItem('username', userData.username)
|
|
|
|
|
|
}
|
|
|
|
|
|
if (typeof userData.phone_number !== 'undefined') {
|
|
|
|
|
|
phoneNumber.value = userData.phone_number || ''
|
|
|
|
|
|
localStorage.setItem('phone_number', userData.phone_number || '')
|
|
|
|
|
|
}
|
2025-09-21 13:33:09 +08:00
|
|
|
|
|
|
|
|
|
|
return userData
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('更新个人资料错误:', error)
|
|
|
|
|
|
throw error
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-05-02 23:56:59 +08:00
|
|
|
|
return {
|
|
|
|
|
|
// 状态
|
|
|
|
|
|
token,
|
|
|
|
|
|
userId,
|
|
|
|
|
|
username,
|
2025-09-21 13:33:09 +08:00
|
|
|
|
userIdLogin,
|
|
|
|
|
|
phoneNumber,
|
|
|
|
|
|
avatar,
|
2025-05-02 23:56:59 +08:00
|
|
|
|
userRole,
|
|
|
|
|
|
|
|
|
|
|
|
// 计算属性
|
|
|
|
|
|
isLoggedIn,
|
|
|
|
|
|
isAdmin,
|
|
|
|
|
|
isSuperAdmin,
|
|
|
|
|
|
|
|
|
|
|
|
// 方法
|
|
|
|
|
|
login,
|
|
|
|
|
|
logout,
|
|
|
|
|
|
initialize,
|
|
|
|
|
|
checkFirstRun,
|
|
|
|
|
|
getAuthHeaders,
|
|
|
|
|
|
getUsers,
|
|
|
|
|
|
createUser,
|
|
|
|
|
|
updateUser,
|
2025-09-21 13:33:09 +08:00
|
|
|
|
deleteUser,
|
|
|
|
|
|
validateUsernameAndGenerateUserId,
|
|
|
|
|
|
uploadAvatar,
|
|
|
|
|
|
getCurrentUser,
|
|
|
|
|
|
updateProfile
|
2025-05-02 23:56:59 +08:00
|
|
|
|
}
|
2025-08-24 22:32:37 +08:00
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
// 检查当前用户是否有管理员权限
|
|
|
|
|
|
export const checkAdminPermission = () => {
|
|
|
|
|
|
const userStore = useUserStore()
|
|
|
|
|
|
if (!userStore.isAdmin) {
|
|
|
|
|
|
throw new Error('需要管理员权限')
|
|
|
|
|
|
}
|
|
|
|
|
|
return true
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查当前用户是否有超级管理员权限
|
|
|
|
|
|
export const checkSuperAdminPermission = () => {
|
|
|
|
|
|
const userStore = useUserStore()
|
|
|
|
|
|
if (!userStore.isSuperAdmin) {
|
|
|
|
|
|
throw new Error('需要超级管理员权限')
|
|
|
|
|
|
}
|
|
|
|
|
|
return true
|
|
|
|
|
|
}
|