feat(auth): implement login API integration with backend

This commit is contained in:
Quicy
2026-01-29 14:59:21 +08:00
parent 8c7d578657
commit bc63e85d13
28 changed files with 834 additions and 86 deletions
+23 -4
View File
@@ -49,7 +49,18 @@
</SidebarGroupContent>
</SidebarGroup>
</SidebarContent>
<SidebarHeader>
<SidebarMenu>
<SidebarMenuItem class="flex justify-center">
<Button
class="flex-[0.7] mb-10"
@click="exit"
>
退出登录
</Button>
</SidebarMenuItem>
</SidebarMenu>
</SidebarHeader>
<SidebarRail />
</Sidebar>
</aside>
@@ -67,13 +78,15 @@ import {
SidebarMenuItem,
SidebarRail,
CollapsibleTrigger,
Collapsible
Collapsible,
Button
} from '@memoh/ui'
import { reactive } from 'vue'
import SvgIcon from '@jamescoyle/vue-icon'
import { mdiRobot, mdiChatOutline, mdiCogBox, mdiListBox, mdiHome, mdiBookArrowDown } from '@mdi/js'
import { useRouter } from 'vue-router'
import {useUserStore} from '@/store/User.ts'
const router = useRouter()
@@ -102,5 +115,11 @@ const sidebarInfo = reactive([{
title: '平台',
name: 'platform',
icon: mdiBookArrowDown
}])
}])
const {exitLogin}=useUserStore()
const exit = () => {
exitLogin()
router.replace({name:'Login'})
}
</script>
+5 -1
View File
@@ -1,12 +1,16 @@
import { createApp } from 'vue'
// @ts-ignore
import './style.css'
import App from './App.vue'
import router from './router'
import { createPinia } from 'pinia'
import i18n from './i18n'
import { PiniaColada } from '@pinia/colada'
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
createApp(App)
.use(createPinia())
.use(createPinia().use(piniaPluginPersistedstate))
.use(PiniaColada)
.use(router)
.use(i18n)
.mount('#app')
+124 -58
View File
@@ -1,8 +1,9 @@
<template>
<section
class="w-screen h-screen flex *:m-auto bg-linear-to-t from-[#BFA4A0] to-[#7784AC] "
>
<section class="w-full max-w-sm flex flex-col gap-10 ">
<section class="w-screen h-screen flex *:m-auto bg-linear-to-t from-[#BFA4A0] to-[#7784AC] ">
<section
v-if="!loading"
class="w-full max-w-sm flex flex-col gap-10 "
>
<section>
<img
src="../../../public/logo.png"
@@ -14,32 +15,51 @@
Memoh
</h3>
</section>
<Card class="py-14">
<CardContent>
<form>
<div class="grid w-full items-center gap-4 [&_input]:py-5">
<div class="flex flex-col space-y-1.5 gap-2">
<Label
for="email"
class=""
>Email</Label>
<Input
id="email"
type="email"
placeholder="m@example.com"
/>
</div>
<div class="flex flex-col space-y-1.5 gap-2">
<div class="flex items-center ">
<Label for="password">Password</Label>
</div>
<Input
id="password"
type="password"
/>
</div>
</div>
<form @submit="login">
<Card class="py-14">
<CardContent class="flex flex-col [&_input]:py-5">
<FormField
v-slot="{ componentField }"
name="username"
>
<FormItem>
<FormLabel class="mb-2">
Username
</FormLabel>
<FormControl>
<Input
type="text"
placeholder="请输入用户名"
v-bind="componentField"
autocomplete="username"
/>
</FormControl>
<blockquote class="h-5">
<FormMessage />
</blockquote>
</FormItem>
</FormField>
<FormField
v-slot="{ componentField }"
name="password"
>
<FormItem>
<FormLabel class="mb-2">
Password
</FormLabel>
<FormControl>
<Input
type="password"
placeholder="请输入密码"
autocomplete="password"
v-bind="componentField"
/>
</FormControl>
<blockquote class="h-5">
<FormMessage />
</blockquote>
</FormItem>
</FormField>
<div class="flex">
<a
href="#"
@@ -48,23 +68,35 @@
Forgot your password?
</a>
</div>
</form>
</CardContent>
<CardFooter class="flex flex-col gap-4">
<Button
class="w-full"
@click="login"
>
登录
</Button>
<Button
variant="outline"
class="w-full"
>
注册
</Button>
</CardFooter>
</Card>
</CardContent>
<CardFooter class="flex flex-col gap-4">
<Button
class="w-full"
type="submit"
@click="login"
>
登录
</Button>
<Button
variant="outline"
class="w-full"
>
注册
</Button>
</CardFooter>
</Card>
</form>
</section>
<section
v-else
class="fixed inset-0 flex"
>
<img
src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBmaWxsPSJjdXJyZW50Q29sb3IiIGQ9Ik0xMiwxQTExLDExLDAsMSwwLDIzLDEyLDExLDExLDAsMCwwLDEyLDFabTAsMTlhOCw4LDAsMSwxLDgtOEE4LDgsMCwwLDEsMTIsMjBaIiBvcGFjaXR5PSIwLjI1Ii8+PHBhdGggZmlsbD0iY3VycmVudENvbG9yIiBkPSJNMTAuMTQsMS4xNmExMSwxMSwwLDAsMC05LDguOTJBMS41OSwxLjU5LDAsMCwwLDIuNDYsMTIsMS41MiwxLjUyLDAsMCwwLDQuMTEsMTAuN2E4LDgsMCwwLDEsNi42Ni02LjYxQTEuNDIsMS40MiwwLDAsMCwxMiwyLjY5aDBBMS41NywxLjU3LDAsMCwwLDEwLjE0LDEuMTZaIj48YW5pbWF0ZVRyYW5zZm9ybSBhdHRyaWJ1dGVOYW1lPSJ0cmFuc2Zvcm0iIGR1cj0iMC43NXMiIHJlcGVhdENvdW50PSJpbmRlZmluaXRlIiB0eXBlPSJyb3RhdGUiIHZhbHVlcz0iMCAxMiAxMjszNjAgMTIgMTIiLz48L3BhdGg+PC9zdmc+"
alt=""
width="80"
class="m-auto"
>
</section>
</section>
</template>
@@ -75,23 +107,57 @@ import {
CardContent,
CardFooter,
Input,
Label,
Button
Button,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '@memoh/ui'
import { useRouter } from 'vue-router'
import { toTypedSchema } from '@vee-validate/zod'
import { useForm } from 'vee-validate'
import * as z from 'zod'
import request from '@/utils/request'
import { useUserStore } from '@/store/user'
import { ref } from 'vue'
const router = useRouter()
const formSchema = toTypedSchema(z.object({
username: z.string().min(1),
password: z.string().min(1),
}))
const form = useForm({
validationSchema: formSchema,
})
const login = async () => {
// 先模拟一下数据
localStorage.setItem('token','afewfewf')
await router.push('/main')
console.log('登录')
}
const { login: LoginHandle } = useUserStore()
const loading=ref(false)
const login = form.handleSubmit(async (values) => {
try {
loading.value=true
const loginState = await request({
url: '/auth/login',
method: 'post',
data: { ...values }
},false)
const data = loginState?.data?.data
if (data?.token && data?.user) {
LoginHandle(data.user, data.token)
}
router.replace({
name:'Main'
})
} catch (error) {
return error
} finally {
loading.value=false
}
})
</script>
+1 -1
View File
@@ -75,7 +75,7 @@ router.beforeEach((to) => {
if (to.fullPath !== '/login') {
return token ? true : { name: 'Login' }
} else {
return token ? { name: 'Main' } : true
return token ? { path:'Main' } : true
}
})
+40
View File
@@ -0,0 +1,40 @@
import { defineStore } from 'pinia'
import { reactive } from 'vue'
type user={
'id': string,
'username': string,
'role': string,
'displayName': string
}
export const useUserStore = defineStore('user', () => {
const userInfo = reactive<user>({
'id': '',
'username': '',
'role': '',
'displayName': ''
})
const login = (userData: user,token:string) => {
localStorage.setItem('token',token)
for (const key of Object.keys(userData) as (keyof user)[]) {
userInfo[key] = userData[key]
}
}
const exitLogin = () => {
localStorage.removeItem('token')
for (const key of Object.keys(userInfo) as (keyof user)[]) {
userInfo[key]=''
}
}
return {
userInfo,
login,
exitLogin
}
}, {
persist:true
})
View File
+29
View File
@@ -0,0 +1,29 @@
import axios, { type AxiosRequestConfig } from 'axios'
import { useRouter } from 'vue-router'
const router=useRouter()
export default (function () {
const axiosInstance = axios.create({
baseURL:'http://localhost:7002/'
})
axiosInstance.interceptors.request.use((config) => {
return config
}, (error) => Promise.reject(error))
axiosInstance.interceptors.response.use((response) => {
return response
}, (error) => {
if (error?.status === 401) {
router.replace({
name:'Login'
})
}
return Promise.reject(error)
})
return (params: AxiosRequestConfig,isToken=true) => {
return axiosInstance(params)
}
}())