Skip to content

Commit 7012759

Browse files
authored
feat: Add login page and user auth (#90)
1 parent bbadf78 commit 7012759

File tree

14 files changed

+306
-5
lines changed

14 files changed

+306
-5
lines changed

mock/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import prose from './modules/prose.mock'
2+
import user from './modules/user.mock'
23

34
export default {
45
...prose,
6+
...user,
57
}

mock/modules/user.mock.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { defineMock } from 'vite-plugin-mock-dev-server'
2+
import proses from '../data'
3+
4+
export default defineMock([
5+
{
6+
url: '/api/auth/login',
7+
delay: 500,
8+
body: () => {
9+
return {
10+
code: 0,
11+
data: {
12+
token: 'admin',
13+
},
14+
msg: 'success',
15+
}
16+
},
17+
},
18+
{
19+
url: '/api/user/me',
20+
delay: 100,
21+
body: () => {
22+
const { prose } = proses.value[Math.floor(Math.random() * 8)]
23+
return {
24+
code: 0,
25+
data: {
26+
uid: 1,
27+
name: 'admin',
28+
avatar: 'https://iconfont.alicdn.com/p/user/eZQFvSX6g8f1/f0d9fd95-a5f0-474d-98b0-d51e8450f2cf.png',
29+
prose,
30+
},
31+
msg: 'success',
32+
}
33+
},
34+
},
35+
{
36+
url: '/api/user/logout',
37+
delay: 500,
38+
body: () => {
39+
return {
40+
code: 0,
41+
msg: 'success',
42+
}
43+
},
44+
},
45+
])

src/api/user.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import request from '@/utils/request'
2+
3+
export interface LoginData {
4+
username: string
5+
password: string
6+
}
7+
8+
export interface LoginRes {
9+
token: string
10+
}
11+
12+
export interface UserState {
13+
uid?: number
14+
name?: string
15+
avatar?: string
16+
prose?: string
17+
}
18+
19+
export function login(data: LoginData): Promise<any> {
20+
return request.post<LoginRes>('/auth/login', data)
21+
}
22+
23+
export function logout() {
24+
return request.post('/user/logout')
25+
}
26+
27+
export function getUserInfo() {
28+
return request<UserState>('/user/me')
29+
}
Lines changed: 1 addition & 0 deletions
Loading

src/components.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@ declare module 'vue' {
1919
VanConfigProvider: typeof import('vant/es')['ConfigProvider']
2020
VanEmpty: typeof import('vant/es')['Empty']
2121
VanField: typeof import('vant/es')['Field']
22+
VanForm: typeof import('vant/es')['Form']
2223
VanIcon: typeof import('vant/es')['Icon']
24+
VanImage: typeof import('vant/es')['Image']
2325
VanNavBar: typeof import('vant/es')['NavBar']
2426
VanPicker: typeof import('vant/es')['Picker']
2527
VanPopup: typeof import('vant/es')['Popup']

src/locales/en-US.json

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
"echartsDemo": "📊 Echarts Demo",
88
"persistPiniaState": "🍍 Persistent Pinia state",
99
"unocssExample": "🎨 Unocss example",
10-
"keepAlive": "🧡 KeepAlive Demo"
10+
"keepAlive": "🧡 KeepAlive Demo",
11+
"login": "🔒 Login"
1112
},
1213
"mock": {
1314
"fromAsyncData": "Data from asynchronous requests",
@@ -32,5 +33,13 @@
3233
},
3334
"keepAlive": {
3435
"label": "The current component will be cached"
36+
},
37+
"login": {
38+
"login": "Sign In",
39+
"logout": "Sign Out",
40+
"username": "Username",
41+
"password": "Password",
42+
"pleaseEnterUsername": "Please enter username",
43+
"pleaseEnterPassword": "Please enter password"
3544
}
3645
}

src/locales/zh-CN.json

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
"persistPiniaState": "🍍 持久化 Pinia 状态",
88
"404Demo": "🙅 404页 演示",
99
"unocssExample": "🎨 Unocss 示例",
10-
"keepAlive": "🧡 KeepAlive 演示"
10+
"keepAlive": "🧡 KeepAlive 演示",
11+
"login": "🔒 用户登陆"
1112
},
1213
"mock": {
1314
"fromAsyncData": "来自异步请求的数据",
@@ -32,5 +33,13 @@
3233
},
3334
"keepAlive": {
3435
"label": "当前组件将会被缓存"
36+
},
37+
"login": {
38+
"login": "登陆",
39+
"logout": "退出",
40+
"username": "请输入用户名",
41+
"password": "请输入密码",
42+
"pleaseEnterUsername": "请输入用户名",
43+
"pleaseEnterPassword": "请输入密码"
3544
}
3645
}

src/pages/login/index.vue

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
<script setup lang="ts">
2+
import { useRouter } from 'vue-router'
3+
import { useUserStore } from '@/stores'
4+
5+
import defaultAvatar from '@/assets/images/default-avatar.svg'
6+
7+
definePage({
8+
name: 'login',
9+
meta: {
10+
level: 2,
11+
i18n: 'home.login',
12+
},
13+
})
14+
15+
const { t } = useI18n()
16+
const router = useRouter()
17+
const userStore = useUserStore()
18+
19+
const loading = ref(false)
20+
const postData = reactive({
21+
username: '',
22+
password: '',
23+
})
24+
const rules = reactive({
25+
username: [
26+
{ required: true, message: t('login.pleaseEnterUsername') },
27+
],
28+
password: [
29+
{ required: true, message: t('login.pleaseEnterPassword') },
30+
],
31+
})
32+
33+
async function asyncLogin(values: any) {
34+
try {
35+
loading.value = true
36+
await userStore.login({ ...postData, ...values })
37+
const { redirect, ...othersQuery } = router.currentRoute.value.query
38+
router.push({
39+
name: (redirect as string) || 'home',
40+
query: {
41+
...othersQuery,
42+
},
43+
})
44+
}
45+
finally {
46+
loading.value = false
47+
}
48+
}
49+
</script>
50+
51+
<template>
52+
<Container :padding-x="0">
53+
<div class="m-x-a w-7xl text-center">
54+
<div class="mb-32 mt-64">
55+
<van-image :src="defaultAvatar" round class="h-64 w-64" />
56+
</div>
57+
<van-form :model="postData" :rules="rules" @submit="asyncLogin">
58+
<van-cell-group inset>
59+
<van-field v-model="postData.username" :rules="rules.username" name="username" :placeholder="t('login.username')" left-icon="contact" />
60+
<van-field v-model="postData.password" :rules="rules.password" name="password" :placeholder="t('login.password')" left-icon="lock" type="password" />
61+
</van-cell-group>
62+
<div class="m-16">
63+
<van-button :loading="loading" round block type="primary" native-type="submit">
64+
{{ t('login.logout') }}
65+
</van-button>
66+
</div>
67+
</van-form>
68+
</div>
69+
</Container>
70+
</template>

src/pages/profile/index.vue

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
11
<script setup lang="ts">
2+
import router from '@/router'
3+
import { useUserStore } from '@/stores'
4+
import defaultAvatar from '@/assets/images/default-avatar.svg'
5+
26
definePage({
37
name: 'profile',
48
meta: {
@@ -7,12 +11,41 @@ definePage({
711
})
812
913
const { t } = useI18n()
14+
const userStore = useUserStore()
15+
const userInfo = computed(() => userStore.userInfo)
16+
17+
function goLogin() {
18+
router.push({ name: 'login', query: { redirect: 'profile' } })
19+
}
20+
21+
function logout() {
22+
userStore.logout()
23+
router.push({ name: 'login', query: { redirect: 'profile' } })
24+
}
1025
</script>
1126

1227
<template>
1328
<Container>
1429
<div mx-auto mb-60 pt-15 text-center text-16 text-dark dark:text-white>
15-
{{ t('profile.placeholder') }}
30+
<template v-if="userInfo.uid">
31+
<div class="bg mb-32 mt-64">
32+
<van-image :src="userInfo.avatar || defaultAvatar" round class="h-64 w-64 border" />
33+
</div>
34+
<div class="mb-64">
35+
{{ userInfo.name }}
36+
</div>
37+
<div class="mb-32">
38+
{{ userInfo.prose }}
39+
</div>
40+
<van-button class="w-xl" type="primary" @click="logout()">
41+
{{ t('login.logout') }}
42+
</van-button>
43+
</template>
44+
<template v-else>
45+
<van-button class="w-xl" type="primary" @click="goLogin()">
46+
{{ t('login.login') }}
47+
</van-button>
48+
</template>
1649
</div>
1750
</Container>
1851
</template>

src/router/index.ts

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import 'nprogress/nprogress.css'
66
import type { EnhancedRouteLocation } from './types'
77
import useRouteTransitionNameStore from '@/stores/modules/routeTransitionName'
88
import useRouteCacheStore from '@/stores/modules/routeCache'
9+
import { useUserStore } from '@/stores'
10+
import { isLogin } from '@/utils/auth'
911

1012
NProgress.configure({ showSpinner: true, parent: '#app' })
1113

@@ -14,11 +16,12 @@ const router = createRouter({
1416
extendRoutes: routes => routes,
1517
})
1618

17-
router.beforeEach((to: EnhancedRouteLocation, from, next) => {
19+
router.beforeEach(async (to: EnhancedRouteLocation, from, next) => {
1820
NProgress.start()
1921

2022
const routeCacheStore = useRouteCacheStore()
2123
const routeTransitionNameStore = useRouteTransitionNameStore()
24+
const userStore = useUserStore()
2225

2326
// Route cache
2427
routeCacheStore.addRoute(to)
@@ -32,7 +35,19 @@ router.beforeEach((to: EnhancedRouteLocation, from, next) => {
3235
else
3336
routeTransitionNameStore.setName('')
3437

35-
next()
38+
if (isLogin()) {
39+
if (!userStore.userInfo?.uid) {
40+
await userStore.info()
41+
next()
42+
}
43+
44+
else {
45+
next()
46+
}
47+
}
48+
else {
49+
next()
50+
}
3651
})
3752

3853
router.afterEach(() => {

0 commit comments

Comments
 (0)