mirror of
https://github.com/memohai/Memoh.git
synced 2026-04-27 07:16:19 +09:00
feat(auth): implement login API integration with backend
This commit is contained in:
@@ -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>
|
||||
@@ -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')
|
||||
|
||||
@@ -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>
|
||||
@@ -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
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -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
|
||||
})
|
||||
@@ -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)
|
||||
}
|
||||
}())
|
||||
Reference in New Issue
Block a user