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:
2026-03-11 14:26:18 +08:00
parent fdd6b2c17d
commit 7791d198f1
4 changed files with 324 additions and 23 deletions

View 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,
}
}

View File

@@ -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>

View File

@@ -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>

View 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,
}
}