diff --git a/mock/index.ts b/mock/index.ts index 81d4952a..b391e4a1 100644 --- a/mock/index.ts +++ b/mock/index.ts @@ -1,5 +1,7 @@ import prose from './modules/prose.mock' +import user from './modules/user.mock' export default { ...prose, + ...user, } diff --git a/mock/modules/user.mock.ts b/mock/modules/user.mock.ts new file mode 100644 index 00000000..98ded722 --- /dev/null +++ b/mock/modules/user.mock.ts @@ -0,0 +1,45 @@ +import { defineMock } from 'vite-plugin-mock-dev-server' +import proses from '../data' + +export default defineMock([ + { + url: '/api/auth/login', + delay: 500, + body: () => { + return { + code: 0, + data: { + token: 'admin', + }, + msg: 'success', + } + }, + }, + { + url: '/api/user/me', + delay: 100, + body: () => { + const { prose } = proses.value[Math.floor(Math.random() * 8)] + return { + code: 0, + data: { + uid: 1, + name: 'admin', + avatar: 'https://iconfont.alicdn.com/p/user/eZQFvSX6g8f1/f0d9fd95-a5f0-474d-98b0-d51e8450f2cf.png', + prose, + }, + msg: 'success', + } + }, + }, + { + url: '/api/user/logout', + delay: 500, + body: () => { + return { + code: 0, + msg: 'success', + } + }, + }, +]) diff --git a/src/api/user.ts b/src/api/user.ts new file mode 100644 index 00000000..85cf3cba --- /dev/null +++ b/src/api/user.ts @@ -0,0 +1,29 @@ +import request from '@/utils/request' + +export interface LoginData { + username: string + password: string +} + +export interface LoginRes { + token: string +} + +export interface UserState { + uid?: number + name?: string + avatar?: string + prose?: string +} + +export function login(data: LoginData): Promise { + return request.post('/auth/login', data) +} + +export function logout() { + return request.post('/user/logout') +} + +export function getUserInfo() { + return request('/user/me') +} diff --git a/src/assets/images/default-avatar.svg b/src/assets/images/default-avatar.svg new file mode 100644 index 00000000..947b9b85 --- /dev/null +++ b/src/assets/images/default-avatar.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/components.d.ts b/src/components.d.ts index 766c0542..b4918d8d 100644 --- a/src/components.d.ts +++ b/src/components.d.ts @@ -19,7 +19,9 @@ declare module 'vue' { VanConfigProvider: typeof import('vant/es')['ConfigProvider'] VanEmpty: typeof import('vant/es')['Empty'] VanField: typeof import('vant/es')['Field'] + VanForm: typeof import('vant/es')['Form'] VanIcon: typeof import('vant/es')['Icon'] + VanImage: typeof import('vant/es')['Image'] VanNavBar: typeof import('vant/es')['NavBar'] VanPicker: typeof import('vant/es')['Picker'] VanPopup: typeof import('vant/es')['Popup'] diff --git a/src/locales/en-US.json b/src/locales/en-US.json index 2d50d267..2bdad9c4 100644 --- a/src/locales/en-US.json +++ b/src/locales/en-US.json @@ -7,7 +7,8 @@ "echartsDemo": "📊 Echarts Demo", "persistPiniaState": "🍍 Persistent Pinia state", "unocssExample": "🎨 Unocss example", - "keepAlive": "🧡 KeepAlive Demo" + "keepAlive": "🧡 KeepAlive Demo", + "login": "🔒 Login" }, "mock": { "fromAsyncData": "Data from asynchronous requests", @@ -32,5 +33,13 @@ }, "keepAlive": { "label": "The current component will be cached" + }, + "login": { + "login": "Sign In", + "logout": "Sign Out", + "username": "Username", + "password": "Password", + "pleaseEnterUsername": "Please enter username", + "pleaseEnterPassword": "Please enter password" } } diff --git a/src/locales/zh-CN.json b/src/locales/zh-CN.json index a9d33c68..cfa4792f 100644 --- a/src/locales/zh-CN.json +++ b/src/locales/zh-CN.json @@ -7,7 +7,8 @@ "persistPiniaState": "🍍 持久化 Pinia 状态", "404Demo": "🙅 404页 演示", "unocssExample": "🎨 Unocss 示例", - "keepAlive": "🧡 KeepAlive 演示" + "keepAlive": "🧡 KeepAlive 演示", + "login": "🔒 用户登陆" }, "mock": { "fromAsyncData": "来自异步请求的数据", @@ -32,5 +33,13 @@ }, "keepAlive": { "label": "当前组件将会被缓存" + }, + "login": { + "login": "登陆", + "logout": "退出", + "username": "请输入用户名", + "password": "请输入密码", + "pleaseEnterUsername": "请输入用户名", + "pleaseEnterPassword": "请输入密码" } } diff --git a/src/pages/login/index.vue b/src/pages/login/index.vue new file mode 100644 index 00000000..90f4c6a5 --- /dev/null +++ b/src/pages/login/index.vue @@ -0,0 +1,70 @@ + + + diff --git a/src/pages/profile/index.vue b/src/pages/profile/index.vue index 91f0a7c9..aaa9b1e4 100644 --- a/src/pages/profile/index.vue +++ b/src/pages/profile/index.vue @@ -1,4 +1,8 @@ diff --git a/src/router/index.ts b/src/router/index.ts index 04890cc9..c1d900d8 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -6,6 +6,8 @@ import 'nprogress/nprogress.css' import type { EnhancedRouteLocation } from './types' import useRouteTransitionNameStore from '@/stores/modules/routeTransitionName' import useRouteCacheStore from '@/stores/modules/routeCache' +import { useUserStore } from '@/stores' +import { isLogin } from '@/utils/auth' NProgress.configure({ showSpinner: true, parent: '#app' }) @@ -14,11 +16,12 @@ const router = createRouter({ extendRoutes: routes => routes, }) -router.beforeEach((to: EnhancedRouteLocation, from, next) => { +router.beforeEach(async (to: EnhancedRouteLocation, from, next) => { NProgress.start() const routeCacheStore = useRouteCacheStore() const routeTransitionNameStore = useRouteTransitionNameStore() + const userStore = useUserStore() // Route cache routeCacheStore.addRoute(to) @@ -32,7 +35,19 @@ router.beforeEach((to: EnhancedRouteLocation, from, next) => { else routeTransitionNameStore.setName('') - next() + if (isLogin()) { + if (!userStore.userInfo?.uid) { + await userStore.info() + next() + } + + else { + next() + } + } + else { + next() + } }) router.afterEach(() => { diff --git a/src/stores/index.ts b/src/stores/index.ts index e952ed84..4473ca4f 100644 --- a/src/stores/index.ts +++ b/src/stores/index.ts @@ -1,7 +1,11 @@ import { createPinia } from 'pinia' import piniaPluginPersistedstate from 'pinia-plugin-persistedstate' +import useAppStore from './modules/app' +import useUserStore from './modules/user' + const pinia = createPinia() pinia.use(piniaPluginPersistedstate) +export { useAppStore, useUserStore } export default pinia diff --git a/src/stores/modules/user.ts b/src/stores/modules/user.ts new file mode 100644 index 00000000..9d9ad869 --- /dev/null +++ b/src/stores/modules/user.ts @@ -0,0 +1,62 @@ +import { defineStore } from 'pinia' +import type { LoginData, UserState } from '@/api/user' +import { getUserInfo, login as userLogin, logout as userLogout } from '@/api/user' +import { clearToken, setToken } from '@/utils/auth' + +const InitUserInfo = { + uid: 0, + nickname: '', + avatar: '', +} + +export const useUserStore = defineStore('user', () => { + const userInfo = ref({ ...InitUserInfo }) + + // Set user's information + const setInfo = (partial: Partial) => { + userInfo.value = { ...partial } + } + + const login = async (loginForm: LoginData) => { + try { + const { data } = await userLogin(loginForm) + setToken(data.token) + } + catch (error) { + clearToken() + throw error + } + } + + const info = async () => { + try { + const { data } = await getUserInfo() + setInfo(data) + } + catch (error) { + clearToken() + throw error + } + } + + const logout = async () => { + try { + await userLogout() + } + finally { + clearToken() + setInfo({ ...InitUserInfo }) + } + } + + return { + userInfo, + info, + login, + logout, + } +}, { + persist: true, +}) + +export default useUserStore diff --git a/src/typed-router.d.ts b/src/typed-router.d.ts index ac828f85..402b7bc3 100644 --- a/src/typed-router.d.ts +++ b/src/typed-router.d.ts @@ -23,6 +23,7 @@ declare module 'vue-router/auto-routes' { 'charts': RouteRecordInfo<'charts', '/charts', Record, Record>, 'counter': RouteRecordInfo<'counter', '/counter', Record, Record>, 'KeepAlive': RouteRecordInfo<'KeepAlive', '/keepalive', Record, Record>, + 'login': RouteRecordInfo<'login', '/login', Record, Record>, 'mock': RouteRecordInfo<'mock', '/mock', Record, Record>, 'profile': RouteRecordInfo<'profile', '/profile', Record, Record>, 'unocss': RouteRecordInfo<'unocss', '/unocss', Record, Record>, diff --git a/src/utils/auth.ts b/src/utils/auth.ts new file mode 100644 index 00000000..2e97ff51 --- /dev/null +++ b/src/utils/auth.ts @@ -0,0 +1,19 @@ +import { STORAGE_TOKEN_KEY } from '@/stores/mutation-type' + +function isLogin() { + return !!localStorage.getItem(STORAGE_TOKEN_KEY) +} + +function getToken() { + return localStorage.getItem(STORAGE_TOKEN_KEY) +} + +function setToken(token: string) { + localStorage.setItem(STORAGE_TOKEN_KEY, token) +} + +function clearToken() { + localStorage.removeItem(STORAGE_TOKEN_KEY) +} + +export { isLogin, getToken, setToken, clearToken }