diff --git a/docs/ui-mockups/designs/login.png b/docs/ui-mockups/designs/login.png new file mode 100644 index 0000000..cc15e3c Binary files /dev/null and b/docs/ui-mockups/designs/login.png differ diff --git a/src/api/auth.ts b/src/api/auth.ts new file mode 100644 index 0000000..d15088f --- /dev/null +++ b/src/api/auth.ts @@ -0,0 +1,20 @@ +import http from '@/utils/http' +import type { ApiResponse, LoginParams } from '@/types' +import type { AxiosResponse } from 'axios' + +/** + * 用户登录 + */ +export const login = (params: LoginParams): Promise>> => { + // 创建FormData对象 + const formData = new FormData() + formData.append('username', params.username) + formData.append('password', params.password) + // 添加rememberMe参数,默认为true + formData.append('rememberMe', String(params.rememberMe ?? true)) + + return http.post('/login', formData) +} \ No newline at end of file diff --git a/src/store/auth.ts b/src/store/auth.ts new file mode 100644 index 0000000..760bcaf --- /dev/null +++ b/src/store/auth.ts @@ -0,0 +1,80 @@ +import { defineStore } from 'pinia' +import { login } from '@/api/auth' +import type { LoginParams } from '@/types' + +interface AuthState { + token: string | null + userInfo: any | null + isAuthenticated: boolean +} + +export const useAuthStore = defineStore('auth', { + state: (): AuthState => ({ + token: null, + userInfo: null, + isAuthenticated: localStorage.getItem('isAuthenticated') === 'true' + }), + + actions: { + /** + * 用户登录 + */ + async login(params: LoginParams) { + try { + // 调用登录接口 + const response = await login(params) + + // 检查响应状态 + if (response.status === 200 && response.data.code === 0) { + // 由于服务器使用session判断登录状态,我们只需要设置认证状态为true + this.isAuthenticated = true + + // 保存到localStorage,用于页面刷新后保持登录状态 + localStorage.setItem('isAuthenticated', 'true') + + // 如果选择了记住密码,则保存用户名和密码 + if (params.rememberMe) { + localStorage.setItem('savedUsername', params.username) + localStorage.setItem('savedPassword', params.password) + } else { + // 清除保存的用户名和密码 + localStorage.removeItem('savedUsername') + localStorage.removeItem('savedPassword') + } + + return response.data + } else { + throw new Error(response.data.msg || '登录失败') + } + } catch (error: any) { + console.error('登录接口调用失败:', error) + throw error + } + }, + + /** + * 用户登出 + */ + logout() { + this.token = null + this.userInfo = null + this.isAuthenticated = false + + // 清除localStorage中的认证状态 + localStorage.removeItem('isAuthenticated') + + // 注意:这里不清除保存的用户名和密码,以便下次自动填充 + }, + + /** + * 检查登录状态 + */ + checkAuthStatus() { + // 从localStorage检查认证状态 + const isAuthenticated = localStorage.getItem('isAuthenticated') + if (isAuthenticated === 'true') { + this.isAuthenticated = true + } + } + } +}) \ No newline at end of file diff --git a/src/store/index.ts b/src/store/index.ts new file mode 100644 index 0000000..e447e03 --- /dev/null +++ b/src/store/index.ts @@ -0,0 +1,17 @@ +import { createPinia } from 'pinia' +import { useAuthStore } from './auth' +import { useOrderStore } from './order' + +// 创建 pinia 实例 +export const store = createPinia() + +// 导出所有 store 的 hook +export { useAuthStore, useOrderStore } + +// 初始化 store +export const initStores = () => { + // 可以在这里添加初始化逻辑 + const authStore = useAuthStore(store) + // 检查本地存储的认证状态 + authStore.checkAuthStatus() +} \ No newline at end of file diff --git a/src/views/List/index.vue b/src/views/List/index.vue index 9abe162..35b5e5a 100644 --- a/src/views/List/index.vue +++ b/src/views/List/index.vue @@ -134,18 +134,39 @@ onMounted(() => { \ No newline at end of file diff --git a/src/views/Login/index.vue b/src/views/Login/index.vue new file mode 100644 index 0000000..c4e7347 --- /dev/null +++ b/src/views/Login/index.vue @@ -0,0 +1,243 @@ + + + + + \ No newline at end of file