Initial commit

This commit is contained in:
fawney19
2025-12-10 20:52:44 +08:00
commit f784106826
485 changed files with 110993 additions and 0 deletions

107
frontend/src/stores/auth.ts Normal file
View File

@@ -0,0 +1,107 @@
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
import { authApi, type User } from '@/api/auth'
import apiClient from '@/api/client'
import { log } from '@/utils/logger'
export const useAuthStore = defineStore('auth', () => {
// 初始化时从 localStorage 恢复 token
const storedToken = apiClient.getToken()
const user = ref<User | null>(null)
const token = ref<string | null>(storedToken)
const loading = ref(false)
const error = ref<string | null>(null)
const isAuthenticated = computed(() => {
// 使用 store 中的 token 状态判断认证状态
// 如果需要同步 localStorage应该在 checkAuth 或专门的 syncToken 方法中处理
return !!token.value
})
/**
* 同步 localStorage 中的 token 到 store
* 用于处理多标签页或外部 token 变更的情况
*/
function syncToken() {
const currentToken = apiClient.getToken()
if (token.value !== currentToken) {
token.value = currentToken
}
}
const isAdmin = computed(() => user.value?.role === 'admin')
async function login(email: string, password: string) {
loading.value = true
error.value = null
try {
const response = await authApi.login({ email, password })
token.value = response.access_token
// 获取用户信息
const userInfo = await authApi.getCurrentUser()
user.value = userInfo
return true
} catch (err: any) {
// 不要暴露后端的详细错误信息
if (err.response?.status === 401) {
error.value = '邮箱或密码错误'
} else if (err.response?.status === 422) {
error.value = '请输入有效的邮箱地址'
} else if (err.response?.status === 500) {
error.value = '服务器错误,请稍后重试'
} else {
error.value = '登录失败,请检查网络连接'
}
return false
} finally {
loading.value = false
}
}
async function logout() {
user.value = null
token.value = null
authApi.logout()
}
async function fetchCurrentUser() {
try {
const userInfo = await authApi.getCurrentUser()
user.value = userInfo
return userInfo
} catch (err: any) {
log.error('Failed to fetch user info', err)
// 根据用户要求,不管什么错误都不清除状态
// 保持登录状态,除非用户手动退出
log.info('Keeping session despite error, as per user requirement')
return null
}
}
async function checkAuth() {
const storedToken = apiClient.getToken()
if (storedToken) {
token.value = storedToken
// 即使获取用户信息失败,也保留 token
// 只有 401 错误才表示 token 真正失效
await fetchCurrentUser()
}
}
return {
user,
token,
loading,
error,
isAuthenticated,
isAdmin,
login,
logout,
fetchCurrentUser,
checkAuth,
syncToken
}
})

View File

@@ -0,0 +1,133 @@
import { defineStore } from 'pinia'
import { ref } from 'vue'
import { usersApi, type User, type CreateUserRequest, type UpdateUserRequest, type ApiKey } from '@/api/users'
export const useUsersStore = defineStore('users', () => {
const users = ref<User[]>([])
const loading = ref(false)
const error = ref<string | null>(null)
async function fetchUsers() {
loading.value = true
error.value = null
try {
users.value = await usersApi.getAllUsers()
} catch (err: any) {
error.value = err.response?.data?.detail || '获取用户列表失败'
} finally {
loading.value = false
}
}
async function createUser(userData: CreateUserRequest) {
loading.value = true
error.value = null
try {
const newUser = await usersApi.createUser(userData)
users.value.push(newUser)
return newUser
} catch (err: any) {
error.value = err.response?.data?.detail || '创建用户失败'
throw err
} finally {
loading.value = false
}
}
async function updateUser(userId: string, updates: UpdateUserRequest) {
loading.value = true
error.value = null
try {
const updatedUser = await usersApi.updateUser(userId, updates)
const index = users.value.findIndex(u => u.id === userId)
if (index !== -1) {
// 保留原有的创建时间等字段,只更新返回的字段
users.value[index] = {
...users.value[index],
...updatedUser
}
}
return updatedUser
} catch (err: any) {
error.value = err.response?.data?.detail || '更新用户失败'
throw err
} finally {
loading.value = false
}
}
async function deleteUser(userId: string) {
loading.value = true
error.value = null
try {
await usersApi.deleteUser(userId)
users.value = users.value.filter(u => u.id !== userId)
} catch (err: any) {
error.value = err.response?.data?.detail || '删除用户失败'
throw err
} finally {
loading.value = false
}
}
async function getUserApiKeys(userId: string): Promise<ApiKey[]> {
try {
return await usersApi.getUserApiKeys(userId)
} catch (err: any) {
error.value = err.response?.data?.detail || '获取 API Keys 失败'
throw err
}
}
async function createApiKey(userId: string, name?: string): Promise<ApiKey> {
try {
return await usersApi.createApiKey(userId, name)
} catch (err: any) {
error.value = err.response?.data?.detail || '创建 API Key 失败'
throw err
}
}
async function deleteApiKey(userId: string, keyId: string) {
try {
await usersApi.deleteApiKey(userId, keyId)
} catch (err: any) {
error.value = err.response?.data?.detail || '删除 API Key 失败'
throw err
}
}
async function resetUserQuota(userId: string) {
loading.value = true
error.value = null
try {
await usersApi.resetUserQuota(userId)
// 刷新用户列表以获取最新数据
await fetchUsers()
} catch (err: any) {
error.value = err.response?.data?.detail || '重置配额失败'
throw err
} finally {
loading.value = false
}
}
return {
users,
loading,
error,
fetchUsers,
createUser,
updateUser,
deleteUser,
getUserApiKeys,
createApiKey,
deleteApiKey,
resetUserQuota
}
})