From 3541ae775589242f4e5cad629498f299af1683b3 Mon Sep 17 00:00:00 2001 From: qq1244 <1244154570@qq.com> Date: Sat, 21 Jun 2025 11:21:19 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=9E=E7=8E=B0=E5=AE=8C=E6=95=B4=E7=9A=84?= =?UTF-8?q?=E7=99=BB=E5=BD=95=E7=B3=BB=E7=BB=9F=E5=92=8C=E9=80=80=E5=87=BA?= =?UTF-8?q?=E7=99=BB=E5=BD=95=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增登录、注册、修改密码页面,支持三种用户类型 - 实现路由守卫和权限控制系统 - 集成Axios拦截器处理认证和错误 - 添加统一错误页面处理 - 完善Header组件退出登录功能,清除本地数据并跳转登录页 - 使用Element Plus构建现代化UI界面 - 支持响应式设计和移动端适配 --- Frontend_API_Documentation.md | 645 ++++++++++++++++++ src/api/auth.ts | 109 +++ src/components/Header.vue | 21 +- src/main.ts | 16 +- src/router/auth.ts | 49 ++ src/router/guards.ts | 219 ++++++ src/router/index.ts | 60 ++ src/stores/auth.ts | 147 ++++ src/types/auth.ts | 84 +++ src/utils/storage.ts | 127 ++++ src/views/ErrorPage.vue | 451 ++++++++++++ src/views/login/ChangePasswordPage.vue | 421 ++++++++++++ src/views/login/LoginPage.vue | 388 +++++++++++ src/views/login/RegisterPage.vue | 442 ++++++++++++ .../login/components/UserTypeSelector.vue | 199 ++++++ 登录系统测试指南.md | 292 ++++++++ 登录系统设计方案.md | 251 +++++++ 17 files changed, 3915 insertions(+), 6 deletions(-) create mode 100644 Frontend_API_Documentation.md create mode 100644 src/api/auth.ts create mode 100644 src/router/auth.ts create mode 100644 src/router/guards.ts create mode 100644 src/stores/auth.ts create mode 100644 src/types/auth.ts create mode 100644 src/utils/storage.ts create mode 100644 src/views/ErrorPage.vue create mode 100644 src/views/login/ChangePasswordPage.vue create mode 100644 src/views/login/LoginPage.vue create mode 100644 src/views/login/RegisterPage.vue create mode 100644 src/views/login/components/UserTypeSelector.vue create mode 100644 登录系统测试指南.md create mode 100644 登录系统设计方案.md diff --git a/Frontend_API_Documentation.md b/Frontend_API_Documentation.md new file mode 100644 index 0000000..e2fb89f --- /dev/null +++ b/Frontend_API_Documentation.md @@ -0,0 +1,645 @@ +# 餐厅管理系统 - 前端API接口文档 + +## 📋 接口概述 + +本系统使用JWT Token进行身份认证。除了登录注册接口外,所有API都需要在请求头中携带有效的JWT Token。 + +**服务器地址**: `http://localhost:8080` + +## 🔐 认证机制 + +### JWT Token使用方式 +- **获取Token**: 通过登录接口获取 +- **使用Token**: 在请求头中添加 `Authorization: Bearer {token}` +- **Token有效期**: 24小时 +- **无需Token**: 仅注册和登录接口 + +## 📱 认证相关接口 + +### 1. 用户注册 + +**接口地址**: `POST /api/auth/register` +**是否需要Token**: ❌ 否 + +**请求参数**: +```json +{ + "accountName": "用户名(3-20字符,字母数字下划线)", + "accountPassword": "密码(8-20字符,至少包含字母和数字)", + "confirmPassword": "确认密码(需与密码相同)", + "accountPhone": "手机号(11位数字,可选)", + "accountType": "账户类型(admin/manager/customer)", + "accountGender": "性别(male/female,可选)" +} +``` + +**请求示例**: +```javascript +const registerData = { + accountName: "zhangsan", + accountPassword: "password123", + confirmPassword: "password123", + accountPhone: "13812345678", + accountType: "customer", + accountGender: "male" +}; + +const response = await fetch('http://localhost:8080/api/auth/register', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(registerData) +}); + +const result = await response.json(); +``` + +**响应示例**: +```json +{ + "code": 200, + "msg": "成功", + "data": { + "token": null, + "accountInfo": { + "accountId": 1, + "accountName": "zhangsan", + "accountType": "customer", + "accountPhone": "13812345678", + "accountGender": "male", + "accountImg": null + } + } +} +``` + +### 2. 用户登录 + +**接口地址**: `POST /api/auth/login` +**是否需要Token**: ❌ 否 + +**请求参数**: +```json +{ + "accountName": "用户名", + "accountPassword": "密码" +} +``` + +**请求示例**: +```javascript +const loginData = { + accountName: "zhangsan", + accountPassword: "password123" +}; + +const response = await fetch('http://localhost:8080/api/auth/login', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(loginData) +}); + +const result = await response.json(); + +// 保存Token到本地存储 +if (result.code === 200) { + const jwtToken = result.data.token; + localStorage.setItem('token', jwtToken); + localStorage.setItem('userInfo', JSON.stringify(result.data.accountInfo)); +} +``` + +**响应示例**: +```json +{ + "code": 200, + "msg": "成功", + "data": { + "token": "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ6aGFuZ3NhbiIsImlhdCI6MTcwNDczNzgxLCJleHAiOjE3NTA1NjAxODF9...", + "accountInfo": { + "accountId": 1, + "accountName": "zhangsan", + "accountType": "customer", + "accountPhone": "13812345678", + "accountGender": "male", + "accountImg": null + } + } +} +``` + +### 3. 修改密码 + +**接口地址**: `PUT /api/auth/change-password` +**是否需要Token**: ✅ 是 + +**请求参数**: +```json +{ + "oldPassword": "当前密码", + "newPassword": "新密码(8-20字符,至少包含字母和数字)", + "confirmPassword": "确认新密码" +} +``` + +**请求示例**: +```javascript +// 获取存储的Token +const storedToken = localStorage.getItem('token'); + +const passwordData = { + oldPassword: "password123", + newPassword: "newpassword456", + confirmPassword: "newpassword456" +}; + +// 构建请求头,确保完整Token传递 +const headers = { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${storedToken}` +}; + +const response = await fetch('http://localhost:8080/api/auth/change-password', { + method: 'PUT', + headers: headers, + body: JSON.stringify(passwordData) +}); + +const result = await response.json(); +``` + +**响应示例**: +```json +{ + "code": 200, + "msg": "成功", + "data": "密码修改成功" +} +``` + +### 4. 获取当前用户信息 + +**接口地址**: `GET /api/auth/current-user` +**是否需要Token**: ✅ 是 + +**请求示例**: +```javascript +// 获取存储的Token +const authToken = localStorage.getItem('token'); + +// 构建请求头 +const requestHeaders = { + 'Authorization': `Bearer ${authToken}` +}; + +const response = await fetch('http://localhost:8080/api/auth/current-user', { + method: 'GET', + headers: requestHeaders +}); + +const result = await response.json(); +``` + +**响应示例**: +```json +{ + "code": 200, + "msg": "成功", + "data": "当前登录用户: zhangsan" +} +``` + +## 🔧 前端Token管理 + +### Token存储和使用 + +```javascript +class AuthManager { + // 保存Token - 确保完整保存 + static saveToken(tokenString, userInfo) { + if (tokenString && tokenString.trim()) { + localStorage.setItem('token', tokenString); + localStorage.setItem('userInfo', JSON.stringify(userInfo)); + } + } + + // 获取完整Token + static getToken() { + const tokenValue = localStorage.getItem('token'); + return tokenValue ? tokenValue.trim() : null; + } + + // 获取用户信息 + static getUserInfo() { + const userInfo = localStorage.getItem('userInfo'); + return userInfo ? JSON.parse(userInfo) : null; + } + + // 清除Token(登出) + static logout() { + localStorage.removeItem('token'); + localStorage.removeItem('userInfo'); + } + + // 检查是否已登录 + static isLoggedIn() { + const token = this.getToken(); + return token && token.length > 0; + } + + // 创建认证头 - 确保完整Token传递 + static getAuthHeaders() { + const fullToken = this.getToken(); + return fullToken ? { 'Authorization': `Bearer ${fullToken}` } : {}; + } +} +``` + +### 统一请求封装 + +```javascript +class ApiClient { + static baseURL = 'http://localhost:8080'; + + // 通用请求方法 - 使用变量方式确保Token完整传递 + static async request(url, options = {}) { + // 获取完整Token + const currentToken = AuthManager.getToken(); + + // 构建基础headers + const baseHeaders = { + 'Content-Type': 'application/json' + }; + + // 如果有Token,添加认证header + const authHeaders = currentToken ? { 'Authorization': `Bearer ${currentToken}` } : {}; + + // 合并headers + const finalHeaders = { + ...baseHeaders, + ...authHeaders, + ...(options.headers || {}) + }; + + const config = { + ...options, + headers: finalHeaders + }; + + try { + const response = await fetch(`${this.baseURL}${url}`, config); + const result = await response.json(); + + // 处理认证失败 + if (response.status === 403 || response.status === 401) { + AuthManager.logout(); + // 跳转到登录页面 + window.location.href = '/login'; + return null; + } + + return result; + } catch (error) { + console.error('API请求失败:', error); + throw error; + } + } + + // GET请求 + static async get(url) { + return this.request(url, { method: 'GET' }); + } + + // POST请求 + static async post(url, data) { + return this.request(url, { + method: 'POST', + body: JSON.stringify(data) + }); + } + + // PUT请求 + static async put(url, data) { + return this.request(url, { + method: 'PUT', + body: JSON.stringify(data) + }); + } + + // DELETE请求 + static async delete(url) { + return this.request(url, { method: 'DELETE' }); + } +} +``` + +### 认证相关操作 + +```javascript +class AuthAPI { + // 用户注册 + static async register(userData) { + return ApiClient.post('/api/auth/register', userData); + } + + // 用户登录 - 确保完整Token保存 + static async login(credentials) { + const result = await ApiClient.post('/api/auth/login', credentials); + + if (result && result.code === 200) { + // 获取完整Token并保存 + const completeToken = result.data.token; + AuthManager.saveToken(completeToken, result.data.accountInfo); + } + + return result; + } + + // 修改密码 - 使用变量方式传递Token + static async changePassword(passwordData) { + // 获取完整Token + const userToken = AuthManager.getToken(); + + // 构建请求头 + const requestHeaders = { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${userToken}` + }; + + // 直接使用fetch确保Token完整传递 + const response = await fetch(`${ApiClient.baseURL}/api/auth/change-password`, { + method: 'PUT', + headers: requestHeaders, + body: JSON.stringify(passwordData) + }); + + return await response.json(); + } + + // 获取当前用户 - 使用变量方式传递Token + static async getCurrentUser() { + const accessToken = AuthManager.getToken(); + const headers = { 'Authorization': `Bearer ${accessToken}` }; + + const response = await fetch(`${ApiClient.baseURL}/api/auth/current-user`, { + method: 'GET', + headers: headers + }); + + return await response.json(); + } + + // 登出 + static logout() { + AuthManager.logout(); + window.location.href = '/login'; + } +} +``` + +## 🎯 使用示例 + +### 登录页面 + +```javascript +// 登录表单提交 +async function handleLogin(event) { + event.preventDefault(); + + const formData = new FormData(event.target); + const loginCredentials = { + accountName: formData.get('username'), + accountPassword: formData.get('password') + }; + + try { + const loginResult = await AuthAPI.login(loginCredentials); + + if (loginResult.code === 200) { + alert('登录成功!'); + // 验证Token是否正确保存 + const savedToken = AuthManager.getToken(); + console.log('Token已保存:', savedToken ? '是' : '否'); + + window.location.href = '/dashboard'; + } else { + alert(`登录失败:${loginResult.msg}`); + } + } catch (error) { + alert('登录请求失败,请检查网络连接'); + } +} +``` + +### 注册页面 + +```javascript +// 注册表单提交 +async function handleRegister(event) { + event.preventDefault(); + + const formData = new FormData(event.target); + const registrationData = { + accountName: formData.get('username'), + accountPassword: formData.get('password'), + confirmPassword: formData.get('confirmPassword'), + accountPhone: formData.get('phone'), + accountType: formData.get('userType'), + accountGender: formData.get('gender') + }; + + try { + const registerResult = await AuthAPI.register(registrationData); + + if (registerResult.code === 200) { + alert('注册成功!请登录'); + window.location.href = '/login'; + } else { + alert(`注册失败:${registerResult.msg}`); + } + } catch (error) { + alert('注册请求失败,请检查网络连接'); + } +} +``` + +### 修改密码页面 + +```javascript +// 修改密码表单提交 +async function handleChangePassword(event) { + event.preventDefault(); + + const formData = new FormData(event.target); + const changePasswordInfo = { + oldPassword: formData.get('oldPassword'), + newPassword: formData.get('newPassword'), + confirmPassword: formData.get('confirmPassword') + }; + + try { + // 使用专门的方法确保Token完整传递 + const changeResult = await AuthAPI.changePassword(changePasswordInfo); + + if (changeResult.code === 200) { + alert('密码修改成功!'); + } else { + alert(`密码修改失败:${changeResult.msg}`); + } + } catch (error) { + alert('请求失败,请检查网络连接'); + } +} +``` + +### 业务API调用示例 + +```javascript +// 获取数据列表 - 使用变量方式确保Token传递 +async function fetchDataWithAuth(apiEndpoint) { + try { + // 获取完整Token + const authenticationToken = AuthManager.getToken(); + + if (!authenticationToken) { + alert('请先登录'); + window.location.href = '/login'; + return; + } + + // 构建请求头 + const apiHeaders = { + 'Authorization': `Bearer ${authenticationToken}` + }; + + const response = await fetch(`http://localhost:8080${apiEndpoint}`, { + method: 'GET', + headers: apiHeaders + }); + + const result = await response.json(); + + if (result && result.code === 200) { + return result.data; + } else { + alert(`请求失败:${result.msg}`); + return null; + } + } catch (error) { + alert('请求失败,请检查网络连接'); + return null; + } +} + +// 发送数据 - 使用变量方式确保Token传递 +async function postDataWithAuth(apiEndpoint, postData) { + try { + // 获取完整Token + const userAuthToken = AuthManager.getToken(); + + if (!userAuthToken) { + alert('请先登录'); + window.location.href = '/login'; + return; + } + + // 构建请求头 + const requestHeaders = { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${userAuthToken}` + }; + + const response = await fetch(`http://localhost:8080${apiEndpoint}`, { + method: 'POST', + headers: requestHeaders, + body: JSON.stringify(postData) + }); + + const result = await response.json(); + + if (result && result.code === 200) { + return result.data; + } else { + alert(`操作失败:${result.msg}`); + return null; + } + } catch (error) { + alert('请求失败,请检查网络连接'); + return null; + } +} +``` + +## 🚨 错误处理 + +### 常见错误码 + +| 错误码 | 说明 | 处理方式 | +|--------|------|----------| +| 200 | 成功 | 正常处理数据 | +| 401 | 未认证 | 跳转到登录页面 | +| 403 | 已禁止/Token无效 | 清除Token,跳转到登录页面 | +| 500 | 服务器错误 | 显示错误信息,建议重试 | + +### Token验证和错误处理 + +```javascript +// 页面加载时验证Token有效性 +async function validateTokenOnPageLoad() { + const currentToken = AuthManager.getToken(); + + if (!currentToken) { + // 没有Token,跳转到登录页 + window.location.href = '/login'; + return; + } + + try { + // 测试Token是否有效 + const testHeaders = { 'Authorization': `Bearer ${currentToken}` }; + + const testResponse = await fetch('http://localhost:8080/api/auth/test', { + method: 'GET', + headers: testHeaders + }); + + if (testResponse.status === 403 || testResponse.status === 401) { + // Token无效,清除并跳转 + AuthManager.logout(); + window.location.href = '/login'; + } + } catch (error) { + console.error('Token验证失败:', error); + } +} + +// 页面加载时执行Token验证 +window.addEventListener('DOMContentLoaded', validateTokenOnPageLoad); +``` + +## 📝 重要注意事项 + +1. **Token完整性**: + - 始终使用变量方式传递Token,避免字符串截断 + - 在存储和读取Token时检查完整性 + +2. **Token安全**: + - Token存储在localStorage中,注意防止XSS攻击 + - 生产环境建议考虑更安全的存储方式 + +3. **请求头构建**: + - 使用变量方式构建Authorization头 + - 确保Token前缀"Bearer "正确添加 + +4. **错误处理**: + - 统一处理401/403错误,自动跳转登录页 + - 为用户提供友好的错误提示 + +5. **调试建议**: + - 在控制台检查Token是否完整 + - 验证请求头是否正确设置 + +这份文档强调了使用变量方式来确保Token完整传递,避免了直接字符串拼接可能导致的截断问题! \ No newline at end of file diff --git a/src/api/auth.ts b/src/api/auth.ts new file mode 100644 index 0000000..8b5221f --- /dev/null +++ b/src/api/auth.ts @@ -0,0 +1,109 @@ +import axios from 'axios' +import type { + LoginRequest, + RegisterRequest, + ChangePasswordRequest, + ApiResponse, + LoginResponseData, + UserInfo, +} from '@/types/auth' +import { StorageManager } from '@/utils/storage' + +// 创建axios实例 +const api = axios.create({ + baseURL: 'http://localhost:8080', + timeout: 10000, + headers: { + 'Content-Type': 'application/json', + }, +}) + +// 请求拦截器 - 自动添加Token +api.interceptors.request.use( + (config) => { + const token = StorageManager.getToken() + if (token) { + config.headers.Authorization = `Bearer ${token}` + } + return config + }, + (error) => { + return Promise.reject(error) + }, +) + +// 响应拦截器 - 处理认证错误 +api.interceptors.response.use( + (response) => { + return response + }, + (error) => { + if (error.response?.status === 401) { + // Token无效,清除存储并跳转错误页面 + StorageManager.clearAuthData() + window.location.href = '/error?type=auth&message=登录已过期,请重新登录' + } else if (error.response?.status === 403) { + // 权限不足 + window.location.href = '/error?type=403&message=您没有权限访问此资源' + } else if (error.response?.status === 500) { + // 服务器错误 + window.location.href = '/error?type=500&message=服务器内部错误,请稍后重试' + } else if (!error.response) { + // 网络错误 + window.location.href = '/error?type=network&message=网络连接失败,请检查网络设置' + } + return Promise.reject(error) + }, +) + +/** + * 认证相关API + */ +export class AuthAPI { + /** + * 用户登录 + */ + static async login(credentials: LoginRequest): Promise> { + const response = await api.post('/api/auth/login', credentials) + + // 登录成功后保存Token和用户信息 + if (response.data.code === 200) { + const { token, accountInfo } = response.data.data + StorageManager.saveAuthInfo(token, accountInfo) + } + + return response.data + } + + /** + * 用户注册 + */ + static async register(userData: RegisterRequest): Promise> { + const response = await api.post('/api/auth/register', userData) + return response.data + } + + /** + * 修改密码 + */ + static async changePassword(passwordData: ChangePasswordRequest): Promise> { + const response = await api.put('/api/auth/change-password', passwordData) + return response.data + } + + /** + * 获取当前用户信息 + */ + static async getCurrentUser(): Promise> { + const response = await api.get('/api/auth/current-user') + return response.data + } + + /** + * 登出 + */ + static logout(): void { + StorageManager.clearAuthData() + window.location.href = '/login' + } +} diff --git a/src/components/Header.vue b/src/components/Header.vue index 8fa1bcb..500cbef 100644 --- a/src/components/Header.vue +++ b/src/components/Header.vue @@ -20,7 +20,7 @@ alt="用户头像" > - + {{ user.name }} @@ -28,9 +28,7 @@ 账号管理 资质认证 - 退出账号 - + 退出账号 @@ -45,10 +43,25 @@ + + diff --git a/src/views/login/ChangePasswordPage.vue b/src/views/login/ChangePasswordPage.vue new file mode 100644 index 0000000..c9e96a9 --- /dev/null +++ b/src/views/login/ChangePasswordPage.vue @@ -0,0 +1,421 @@ + + + + + diff --git a/src/views/login/LoginPage.vue b/src/views/login/LoginPage.vue new file mode 100644 index 0000000..b9ff317 --- /dev/null +++ b/src/views/login/LoginPage.vue @@ -0,0 +1,388 @@ + + + + + diff --git a/src/views/login/RegisterPage.vue b/src/views/login/RegisterPage.vue new file mode 100644 index 0000000..c52c688 --- /dev/null +++ b/src/views/login/RegisterPage.vue @@ -0,0 +1,442 @@ + + + + + diff --git a/src/views/login/components/UserTypeSelector.vue b/src/views/login/components/UserTypeSelector.vue new file mode 100644 index 0000000..8a81999 --- /dev/null +++ b/src/views/login/components/UserTypeSelector.vue @@ -0,0 +1,199 @@ + + + + + diff --git a/登录系统测试指南.md b/登录系统测试指南.md new file mode 100644 index 0000000..bf92453 --- /dev/null +++ b/登录系统测试指南.md @@ -0,0 +1,292 @@ +# 🧪 餐厅管理系统登录模块测试指南 + +## 📋 系统概述 + +已成功创建完整的登录系统,包含以下核心功能: + +- 用户登录(支持三种用户类型) +- 用户注册 +- 密码修改 +- 路由守卫和权限控制 +- 统一错误处理 +- Axios请求拦截器 + +## 🎯 已实现的功能模块 + +### 1. 认证页面 + +- ✅ **登录页面** (`/login`) - 支持客户/商户/管理员三种类型登录 +- ✅ **注册页面** (`/register`) - 完整的用户注册流程 +- ✅ **修改密码页面** (`/change-password`) - 安全的密码修改功能 +- ✅ **错误页面** (`/error`) - 统一的错误处理页面 + +### 2. 路由系统 + +- ✅ **路由守卫** - 自动检查登录状态和权限 +- ✅ **权限控制** - 根据用户类型限制访问 +- ✅ **自动重定向** - 登录后跳转到对应模块 + +### 3. 状态管理 + +- ✅ **Pinia Store** - 统一的认证状态管理 +- ✅ **本地存储** - Token和用户信息持久化 +- ✅ **状态同步** - 页面刷新后状态恢复 + +### 4. 网络请求 + +- ✅ **Axios拦截器** - 自动添加认证头 +- ✅ **错误处理** - 统一处理HTTP错误 +- ✅ **Token管理** - 自动Token验证和清理 + +## 🔍 测试步骤 + +### 基础功能测试 + +#### 1. 访问登录页面 + +``` +访问地址: http://localhost:5173 +预期结果: 自动重定向到 /login 页面 +``` + +#### 2. 测试用户类型选择 + +- 点击不同的用户类型标签(客户/商户/管理员) +- 验证UI界面切换正常 + +#### 3. 测试表单验证 + +- **用户名验证**: + + - 输入少于3个字符 → 显示错误提示 + - 输入特殊字符 → 显示错误提示 + - 输入正确格式 → 验证通过 + +- **密码验证**: + - 输入少于8个字符 → 显示错误提示 + - 输入纯数字或纯字母 → 显示错误提示 + - 输入包含字母和数字 → 验证通过 + +#### 4. 测试注册功能 + +``` +访问地址: http://localhost:5173/register +测试数据: +- 用户名: testuser123 +- 密码: password123 +- 确认密码: password123 +- 手机号: 13812345678 +- 用户类型: customer +- 性别: male +``` + +#### 5. 测试登录功能 + +使用API文档中的示例数据进行登录测试: + +``` +用户名: zhangsan +密码: password123 +用户类型: customer +``` + +### 路由权限测试 + +#### 1. 未登录状态测试 + +- 直接访问 `/admin` → 应跳转到错误页面 +- 直接访问 `/merchant` → 应跳转到错误页面 +- 直接访问 `/user` → 应跳转到错误页面 + +#### 2. 权限控制测试 + +- 客户身份登录后访问 `/admin` → 应显示权限不足错误 +- 商户身份登录后访问 `/admin` → 应显示权限不足错误 +- 管理员身份登录后 → 可以访问所有模块 + +#### 3. 自动重定向测试 + +- 客户登录成功 → 自动跳转到 `/user` +- 商户登录成功 → 自动跳转到 `/merchant` +- 管理员登录成功 → 自动跳转到 `/admin` + +### 错误处理测试 + +#### 1. 网络错误测试 + +- 关闭后端服务器 +- 尝试登录 → 应跳转到网络错误页面 + +#### 2. 认证错误测试 + +- 使用错误的用户名/密码 → 显示登录失败消息 +- Token过期 → 自动跳转到认证错误页面 + +#### 3. 404错误测试 + +- 访问不存在的路径 → 应跳转到404错误页面 + +## 🔧 API接口测试 + +### 1. 登录接口测试 + +```bash +curl -X POST http://localhost:8080/api/auth/login \ + -H "Content-Type: application/json" \ + -d '{ + "accountName": "zhangsan", + "accountPassword": "password123" + }' +``` + +### 2. 注册接口测试 + +```bash +curl -X POST http://localhost:8080/api/auth/register \ + -H "Content-Type: application/json" \ + -d '{ + "accountName": "testuser123", + "accountPassword": "password123", + "confirmPassword": "password123", + "accountPhone": "13812345678", + "accountType": "customer", + "accountGender": "male" + }' +``` + +### 3. 获取当前用户接口测试 + +```bash +curl -X GET http://localhost:8080/api/auth/current-user \ + -H "Authorization: Bearer YOUR_TOKEN_HERE" +``` + +## 🐛 常见问题排查 + +### 1. 页面无法访问 + +- 检查开发服务器是否启动:`npm run dev` +- 检查端口是否被占用 +- 检查控制台是否有JavaScript错误 + +### 2. 登录失败 + +- 确认后端服务器运行在 `http://localhost:8080` +- 检查API接口是否可访问 +- 查看浏览器开发者工具的Network标签 + +### 3. 权限问题 + +- 检查用户类型是否正确 +- 确认Token是否正确保存在localStorage +- 验证路由守卫配置 + +### 4. 样式问题 + +- 确认Element Plus正确安装和导入 +- 检查CSS样式是否正确加载 +- 验证图标组件是否正确注册 + +## 📱 响应式设计测试 + +### 移动端测试 + +- 使用Chrome开发者工具切换到移动设备视图 +- 测试不同屏幕尺寸下的页面布局 +- 验证触摸操作的可用性 + +### 平板端测试 + +- 测试中等尺寸屏幕的适配 +- 验证表单元素的布局 + +## 🔒 安全性测试 + +### 1. XSS防护测试 + +- 在表单中输入脚本代码 +- 验证是否被正确转义 + +### 2. Token安全测试 + +- 检查Token是否正确存储 +- 验证Token过期处理 +- 测试退出登录后Token清理 + +### 3. 路由安全测试 + +- 尝试直接访问受保护的路由 +- 验证权限检查是否生效 + +## 📊 性能测试 + +### 1. 页面加载速度 + +- 测试首屏加载时间 +- 验证资源懒加载是否正常 + +### 2. 网络请求优化 + +- 检查是否有重复请求 +- 验证请求拦截器性能 + +## 🎨 用户体验测试 + +### 1. 交互反馈 + +- 点击按钮时的加载状态 +- 表单验证的即时反馈 +- 错误消息的友好提示 + +### 2. 视觉设计 + +- 颜色搭配和对比度 +- 字体大小和可读性 +- 图标和按钮的直观性 + +## 📝 测试清单 + +- [ ] 登录页面正常显示 +- [ ] 用户类型选择器工作正常 +- [ ] 表单验证规则生效 +- [ ] 注册功能完整可用 +- [ ] 登录成功后正确跳转 +- [ ] 路由守卫正确拦截 +- [ ] 权限控制按预期工作 +- [ ] 错误页面正确显示 +- [ ] Token自动添加到请求头 +- [ ] 网络错误正确处理 +- [ ] 移动端适配良好 +- [ ] 密码修改功能正常 +- [ ] 退出登录清理状态 + +## 🚀 部署前检查 + +1. **代码质量** + + - 运行 `npm run type-check` 检查TypeScript类型 + - 运行 `npm run format` 格式化代码 + +2. **构建测试** + + - 运行 `npm run build` 构建生产版本 + - 检查构建产物是否正常 + +3. **环境配置** + - 确认API地址配置正确 + - 检查生产环境的安全设置 + +--- + +## 🎯 总结 + +登录系统已完整实现,包含了现代Web应用所需的所有核心功能: + +- ✅ 完整的用户认证流程 +- ✅ 细粒度的权限控制 +- ✅ 优雅的错误处理 +- ✅ 现代化的UI设计 +- ✅ 良好的用户体验 +- ✅ 安全的Token管理 + +系统已准备好投入使用!🎉 diff --git a/登录系统设计方案.md b/登录系统设计方案.md new file mode 100644 index 0000000..953c5b0 --- /dev/null +++ b/登录系统设计方案.md @@ -0,0 +1,251 @@ +# 🏗️ 餐厅管理系统登录模块设计方案 + +## 📊 系统架构图 + +```mermaid +graph TB + A[登录页面] --> B{用户类型选择} + B --> C[管理员 admin] + B --> D[商户 manager] + B --> E[客户 customer] + + A --> F[注册页面] + A --> G[忘记密码页面] + + C --> H[管理员后台 /admin] + D --> I[商户后台 /merchant] + E --> J[用户前台 /user] + + K[路由守卫] --> L{Token验证} + L --> M[有效] --> N[允许访问] + L --> O[无效] --> P[跳转登录] + + Q[Axios拦截器] --> R[请求拦截器] + Q --> S[响应拦截器] + R --> T[自动添加Token] + S --> U[处理401/403错误] + U --> P +``` + +## 🎯 功能模块划分 + +### 1. 认证模块 (Authentication) + +- **用户登录**: 支持三种用户类型选择 +- **用户注册**: 完整的注册流程 +- **密码管理**: 修改密码功能 +- **Token管理**: JWT令牌的存储和管理 + +### 2. 路由守卫模块 (Route Guards) + +- **全局前置守卫**: 检查路由访问权限 +- **角色权限控制**: 根据用户类型限制访问 +- **登录状态验证**: Token有效性检查 + +### 3. HTTP拦截器模块 (HTTP Interceptors) + +- **请求拦截器**: 自动添加Authorization头 +- **响应拦截器**: 统一处理认证错误 +- **错误处理**: 401/403自动跳转登录 + +### 4. 状态管理模块 (State Management) + +- **用户状态**: 当前登录用户信息 +- **认证状态**: 登录状态管理 +- **权限状态**: 用户权限信息 + +## 📁 文件结构设计 + +``` +src/ +├── views/login/ # 登录相关页面目录 +│ ├── LoginPage.vue # 主登录页面 +│ ├── RegisterPage.vue # 注册页面 +│ ├── ChangePasswordPage.vue # 修改密码页面 +│ └── components/ # 登录组件 +│ ├── LoginForm.vue # 登录表单组件 +│ ├── RegisterForm.vue # 注册表单组件 +│ ├── UserTypeSelector.vue # 用户类型选择器 +│ └── PasswordStrength.vue # 密码强度指示器 +├── stores/ # Pinia状态管理 +│ ├── auth.ts # 认证状态管理 +│ └── user.ts # 用户信息状态(已存在,需扩展) +├── utils/ # 工具函数 +│ ├── auth.ts # 认证相关工具函数 +│ ├── http.ts # HTTP请求封装 +│ ├── storage.ts # 本地存储工具 +│ └── validation.ts # 表单验证工具 +├── api/ # API接口 +│ └── auth.ts # 认证相关API +├── router/ # 路由配置(需扩展) +│ ├── index.ts # 主路由文件(需添加守卫) +│ ├── guards.ts # 路由守卫 +│ └── auth.ts # 认证路由 +└── types/ # TypeScript类型定义 + ├── auth.ts # 认证相关类型 + └── user.ts # 用户相关类型 +``` + +## 🔒 权限控制设计 + +### 路由权限映射 + +```mermaid +graph LR + A[用户类型] --> B{权限判断} + B --> C[admin] --> D[可访问 /admin/*] + B --> E[manager] --> F[可访问 /merchant/*] + B --> G[customer] --> H[可访问 /user/*] + + I[未登录用户] --> J[只能访问 /login, /register] +``` + +### 路由守卫逻辑 + +1. **检查登录状态**: 验证Token是否存在且有效 +2. **验证用户权限**: 检查用户类型是否匹配路由权限 +3. **重定向逻辑**: 未授权用户跳转到登录页面 +4. **登录后跳转**: 根据用户类型跳转到对应模块 + +## 🛠️ 技术实现方案 + +### 1. 认证状态管理 (Pinia Store) + +```typescript +interface AuthState { + isAuthenticated: boolean + token: string | null + userInfo: UserInfo | null + userType: 'admin' | 'manager' | 'customer' | null +} +``` + +### 2. 路由守卫实现 + +- 使用 `router.beforeEach()` 实现全局前置守卫 +- 检查路由meta信息中的权限要求 +- 实现角色基础的访问控制 (RBAC) + +### 3. Axios拦截器配置 + +- 请求拦截器自动添加 `Authorization: Bearer {token}` +- 响应拦截器处理401/403错误,自动清除Token并跳转 +- 统一错误处理和用户提示 + +### 4. 表单验证规则 + +- 用户名:3-20字符,字母数字下划线 +- 密码:8-20字符,至少包含字母和数字 +- 手机号:11位数字格式验证 +- 实时验证和错误提示 + +## 🎨 UI/UX设计要点 + +### 1. 登录页面布局 + +- 响应式设计,支持移动端和桌面端 +- 用户类型选择器(Tab切换或下拉选择) +- 记住登录状态选项 +- 第三方登录预留接口 + +### 2. 用户体验优化 + +- 加载状态指示器 +- 友好的错误提示信息 +- 密码强度实时检测 +- 自动焦点管理 + +### 3. 安全性考虑 + +- 密码输入遮盖 +- 防止暴力破解(前端限制) +- Token自动刷新机制 +- 安全退出清理 + +## 📋 实施步骤 + +### 阶段一:基础架构搭建 + +1. 创建登录页面目录结构 +2. 设置认证相关的TypeScript类型 +3. 实现基础的Pinia认证状态管理 +4. 配置Axios基础设置 + +### 阶段二:核心功能实现 + +1. 实现登录表单和API调用 +2. 创建用户类型选择器 +3. 实现注册和密码修改功能 +4. 配置本地存储管理 + +### 阶段三:安全保障 + +1. 实现路由守卫逻辑 +2. 配置Axios请求/响应拦截器 +3. 添加Token验证和刷新机制 +4. 实现权限控制 + +### 阶段四:用户体验优化 + +1. 添加表单验证和错误处理 +2. 实现加载状态和用户反馈 +3. 优化页面布局和响应式设计 +4. 添加安全退出和会话管理 + +## 🔄 数据流设计 + +```mermaid +sequenceDiagram + participant U as 用户 + participant L as 登录页面 + participant A as API + participant S as Pinia Store + participant R as Router + + U->>L: 输入登录信息 + L->>A: 发送登录请求 + A-->>L: 返回Token和用户信息 + L->>S: 保存认证状态 + S->>R: 触发路由跳转 + R-->>U: 跳转到用户对应模块 + + Note over U,R: 后续请求自动携带Token + Note over S: 定期验证Token有效性 +``` + +## 📚 API接口规范 + +基于项目API文档,主要使用以下接口: + +### 认证接口 + +- `POST /api/auth/login` - 用户登录 +- `POST /api/auth/register` - 用户注册 +- `PUT /api/auth/change-password` - 修改密码 +- `GET /api/auth/current-user` - 获取当前用户信息 + +### 认证机制 + +- JWT Token 认证 +- Token有效期:24小时 +- 请求头格式:`Authorization: Bearer {token}` + +## 🔧 开发环境配置 + +### 技术栈 + +- Vue 3 + TypeScript +- Vue Router 4 +- Pinia (状态管理) +- Element Plus (UI组件库) +- Axios (HTTP客户端) +- Vite (构建工具) + +### 开发规范 + +- 使用 TypeScript 强类型约束 +- 采用 Composition API +- 遵循 Vue 3 最佳实践 +- 统一代码格式化 (Prettier) + +这个设计方案提供了完整的登录系统架构,包含了认证、授权、安全防护等所有必要功能。方案采用模块化设计,便于维护和扩展。