feat: 前端认证和工具模块
- 新增 useAuth.ts composable - 新增 tools/useTools.ts - 更新 Login.vue, Signup.vue Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
114
web/src/composables/useAuth.ts
Normal file
114
web/src/composables/useAuth.ts
Normal file
@@ -0,0 +1,114 @@
|
||||
const API_BASE = 'http://localhost:8082'
|
||||
|
||||
export interface LoginRequest {
|
||||
username: string
|
||||
password: string
|
||||
}
|
||||
|
||||
export interface RegisterRequest {
|
||||
username: string
|
||||
password: string
|
||||
email?: string
|
||||
}
|
||||
|
||||
export interface LoginResponse {
|
||||
token: string
|
||||
user: any
|
||||
}
|
||||
|
||||
export const useAuth = () => {
|
||||
const token = localStorage.getItem('token')
|
||||
const user = localStorage.getItem('user')
|
||||
|
||||
const login = async (data: LoginRequest): Promise<LoginResponse> => {
|
||||
const response = await fetch(`${API_BASE}/auth/login`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(data),
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.json()
|
||||
throw new Error(error.message || 'Login failed')
|
||||
}
|
||||
|
||||
const result = await response.json()
|
||||
|
||||
// Save token and user info
|
||||
localStorage.setItem('token', result.token)
|
||||
localStorage.setItem('user', JSON.stringify(result.user))
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
const register = async (data: RegisterRequest): Promise<any> => {
|
||||
const response = await fetch(`${API_BASE}/auth/register`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(data),
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.json()
|
||||
throw new Error(error.message || 'Registration failed')
|
||||
}
|
||||
|
||||
return await response.json()
|
||||
}
|
||||
|
||||
const logout = () => {
|
||||
localStorage.removeItem('token')
|
||||
localStorage.removeItem('user')
|
||||
}
|
||||
|
||||
const getToken = () => {
|
||||
return localStorage.getItem('token')
|
||||
}
|
||||
|
||||
const getUser = () => {
|
||||
const userStr = localStorage.getItem('user')
|
||||
return userStr ? JSON.parse(userStr) : null
|
||||
}
|
||||
|
||||
const isLoggedIn = () => {
|
||||
return !!token
|
||||
}
|
||||
|
||||
const getCurrentUser = async () => {
|
||||
const token = localStorage.getItem('token')
|
||||
if (!token) {
|
||||
throw new Error('Not authenticated')
|
||||
}
|
||||
|
||||
const response = await fetch(`${API_BASE}/auth/me`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${token}`,
|
||||
},
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.json()
|
||||
throw new Error(error.message || 'Failed to get user')
|
||||
}
|
||||
|
||||
const user = await response.json()
|
||||
localStorage.setItem('user', JSON.stringify(user))
|
||||
return user
|
||||
}
|
||||
|
||||
return {
|
||||
login,
|
||||
register,
|
||||
logout,
|
||||
getToken,
|
||||
getUser,
|
||||
isLoggedIn,
|
||||
getCurrentUser,
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import { useAuth } from '@/composables/useAuth'
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const { login } = useAuth()
|
||||
|
||||
// 表单数据
|
||||
const username = ref('')
|
||||
@@ -11,23 +14,38 @@ const rememberMe = ref(false)
|
||||
const isLoading = ref(false)
|
||||
const showPassword = ref(false)
|
||||
const errorMsg = ref('')
|
||||
const successMsg = ref('')
|
||||
|
||||
// 模拟登录验证
|
||||
const handleLogin = () => {
|
||||
// 检查是否从注册页跳转过来
|
||||
onMounted(() => {
|
||||
if (route.query.registered === 'true') {
|
||||
successMsg.value = 'Account created successfully! Please sign in.'
|
||||
}
|
||||
})
|
||||
|
||||
// 登录处理
|
||||
const handleLogin = async () => {
|
||||
errorMsg.value = ''
|
||||
|
||||
if (username.value !== 'admin' || password.value !== 'admin') {
|
||||
errorMsg.value = 'Invalid username or password'
|
||||
if (!username.value || !password.value) {
|
||||
errorMsg.value = 'Please enter username and password'
|
||||
return
|
||||
}
|
||||
|
||||
isLoading.value = true
|
||||
// 模拟登录
|
||||
setTimeout(() => {
|
||||
isLoading.value = false
|
||||
|
||||
try {
|
||||
await login({
|
||||
username: username.value,
|
||||
password: password.value,
|
||||
})
|
||||
// 登录成功,跳转到 Dashboard
|
||||
router.push('/dashboard')
|
||||
}, 1500)
|
||||
router.push('/chat')
|
||||
} catch (error: any) {
|
||||
errorMsg.value = error.message || 'Login failed, please check your credentials'
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -52,6 +70,12 @@ const handleLogin = () => {
|
||||
|
||||
<!-- 登录表单 -->
|
||||
<div class="bg-dark-700 rounded-2xl p-8 shadow-xl border border-dark-500/50">
|
||||
<!-- 成功提示 -->
|
||||
<div v-if="successMsg" class="mb-4 p-3 bg-green-500/10 border border-green-500/30 rounded-lg text-green-400 text-sm flex items-center gap-2">
|
||||
<i class="fa-solid fa-check-circle"></i>
|
||||
{{ successMsg }}
|
||||
</div>
|
||||
|
||||
<!-- 错误提示 -->
|
||||
<div v-if="errorMsg" class="mb-4 p-3 bg-red-500/10 border border-red-500/30 rounded-lg text-red-400 text-sm flex items-center gap-2">
|
||||
<i class="fa-solid fa-circle-exclamation"></i>
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useAuth } from '@/composables/useAuth'
|
||||
|
||||
const router = useRouter()
|
||||
const { register } = useAuth()
|
||||
|
||||
// 表单数据
|
||||
const username = ref('')
|
||||
@@ -14,6 +16,7 @@ const isLoading = ref(false)
|
||||
const showPassword = ref(false)
|
||||
const showConfirmPassword = ref(false)
|
||||
const errorMsg = ref('')
|
||||
const successMsg = ref('')
|
||||
|
||||
// 表单验证
|
||||
const validateForm = () => {
|
||||
@@ -24,11 +27,6 @@ const validateForm = () => {
|
||||
return false
|
||||
}
|
||||
|
||||
if (!email.value || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email.value)) {
|
||||
errorMsg.value = 'Please enter a valid email address'
|
||||
return false
|
||||
}
|
||||
|
||||
if (!password.value || password.value.length < 6) {
|
||||
errorMsg.value = 'Password must be at least 6 characters'
|
||||
return false
|
||||
@@ -48,21 +46,34 @@ const validateForm = () => {
|
||||
}
|
||||
|
||||
// 注册处理
|
||||
const handleSignup = () => {
|
||||
const handleSignup = async () => {
|
||||
if (!validateForm()) return
|
||||
|
||||
isLoading.value = true
|
||||
// 模拟注册
|
||||
setTimeout(() => {
|
||||
errorMsg.value = ''
|
||||
successMsg.value = ''
|
||||
|
||||
try {
|
||||
await register({
|
||||
username: username.value,
|
||||
password: password.value,
|
||||
email: email.value || undefined,
|
||||
})
|
||||
// 注册成功
|
||||
successMsg.value = 'Account created successfully! Redirecting to login...'
|
||||
setTimeout(() => {
|
||||
router.push('/?registered=true')
|
||||
}, 1500)
|
||||
} catch (error: any) {
|
||||
errorMsg.value = error.message || 'Registration failed, please try again'
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
// 注册成功,跳转到登录页
|
||||
router.push('/')
|
||||
}, 1500)
|
||||
}
|
||||
}
|
||||
|
||||
// 跳转到登录
|
||||
const goToLogin = () => {
|
||||
router.push('/')
|
||||
router.push('/?registered=true')
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -87,6 +98,12 @@ const goToLogin = () => {
|
||||
|
||||
<!-- 注册表单 -->
|
||||
<div class="bg-dark-700 rounded-2xl p-8 shadow-xl border border-dark-500/50">
|
||||
<!-- 成功提示 -->
|
||||
<div v-if="successMsg" class="mb-4 p-3 bg-green-500/10 border border-green-500/30 rounded-lg text-green-400 text-sm flex items-center gap-2">
|
||||
<i class="fa-solid fa-check-circle"></i>
|
||||
{{ successMsg }}
|
||||
</div>
|
||||
|
||||
<!-- 错误提示 -->
|
||||
<div v-if="errorMsg" class="mb-4 p-3 bg-red-500/10 border border-red-500/30 rounded-lg text-red-400 text-sm flex items-center gap-2">
|
||||
<i class="fa-solid fa-circle-exclamation"></i>
|
||||
|
||||
146
web/src/views/tools/useTools.ts
Normal file
146
web/src/views/tools/useTools.ts
Normal file
@@ -0,0 +1,146 @@
|
||||
const API_BASE = 'http://localhost:8082'
|
||||
|
||||
// Tool 接口
|
||||
export interface Tool {
|
||||
id: string
|
||||
name: string
|
||||
description: string
|
||||
category: string
|
||||
provider: string
|
||||
security_level: string
|
||||
require_approval: boolean
|
||||
parameters: string
|
||||
status: string
|
||||
created_at?: string
|
||||
updated_at?: string
|
||||
}
|
||||
|
||||
export function useTools() {
|
||||
const tools = ref<Tool[]>([])
|
||||
const toolsLoading = ref(false)
|
||||
|
||||
// 获取工具列表
|
||||
const fetchTools = async (category?: string, status?: string) => {
|
||||
toolsLoading.value = true
|
||||
try {
|
||||
let url = `${API_BASE}/tool/list?`
|
||||
if (category) url += `category=${category}&`
|
||||
if (status) url += `status=${status}&`
|
||||
|
||||
const response = await fetch(url)
|
||||
const data = await response.json()
|
||||
|
||||
if (data.list) {
|
||||
tools.value = data.list
|
||||
}
|
||||
return data.list || []
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch tools:', error)
|
||||
return []
|
||||
} finally {
|
||||
toolsLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 同步工具到数据库
|
||||
const syncTools = async () => {
|
||||
try {
|
||||
const response = await fetch(`${API_BASE}/tool/sync`)
|
||||
const data = await response.json()
|
||||
await fetchTools() // 重新获取列表
|
||||
return data
|
||||
} catch (error) {
|
||||
console.error('Failed to sync tools:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
// 获取工具详情
|
||||
const fetchToolById = async (id: string) => {
|
||||
try {
|
||||
const response = await fetch(`${API_BASE}/tool/${id}`)
|
||||
const data = await response.json()
|
||||
return data.tool
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch tool:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
// 创建工具
|
||||
const createTool = async (tool: Partial<Tool>) => {
|
||||
try {
|
||||
const response = await fetch(`${API_BASE}/tool/add`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(tool),
|
||||
})
|
||||
const data = await response.json()
|
||||
await fetchTools() // 重新获取列表
|
||||
return data
|
||||
} catch (error) {
|
||||
console.error('Failed to create tool:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
// 更新工具
|
||||
const updateTool = async (id: string, tool: Partial<Tool>) => {
|
||||
try {
|
||||
const response = await fetch(`${API_BASE}/tool/${id}`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(tool),
|
||||
})
|
||||
const data = await response.json()
|
||||
await fetchTools() // 重新获取列表
|
||||
return data
|
||||
} catch (error) {
|
||||
console.error('Failed to update tool:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
// 删除工具
|
||||
const deleteTool = async (id: string) => {
|
||||
try {
|
||||
const response = await fetch(`${API_BASE}/tool/${id}`, {
|
||||
method: 'DELETE',
|
||||
})
|
||||
const data = await response.json()
|
||||
await fetchTools() // 重新获取列表
|
||||
return data
|
||||
} catch (error) {
|
||||
console.error('Failed to delete tool:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
// 按分类获取工具
|
||||
const getToolsByCategory = (category: string) => {
|
||||
return tools.value.filter(tool => tool.category === category)
|
||||
}
|
||||
|
||||
// 获取所有分类
|
||||
const categories = computed(() => {
|
||||
const cats = new Set(tools.value.map(tool => tool.category))
|
||||
return Array.from(cats)
|
||||
})
|
||||
|
||||
return {
|
||||
tools,
|
||||
toolsLoading,
|
||||
categories,
|
||||
fetchTools,
|
||||
syncTools,
|
||||
fetchToolById,
|
||||
createTool,
|
||||
updateTool,
|
||||
deleteTool,
|
||||
getToolsByCategory,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user