实现登录
This commit is contained in:
		
							
								
								
									
										19
									
								
								src/App.vue
									
									
									
									
									
								
							
							
						
						
									
										19
									
								
								src/App.vue
									
									
									
									
									
								
							| @@ -1,14 +1,31 @@ | ||||
| <template> | ||||
|   <MainLayout /> | ||||
|   <div id="app"> | ||||
|     <template v-if="isLoginPage"> | ||||
|       <router-view /> | ||||
|     </template> | ||||
|     <template v-else> | ||||
|       <MainLayout /> | ||||
|     </template> | ||||
|   </div> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| import { computed } from 'vue'; | ||||
| import { useRoute } from 'vue-router'; | ||||
| import MainLayout from './layouts/MainLayout.vue'; | ||||
|  | ||||
| export default { | ||||
|   name: 'App', | ||||
|   components: { | ||||
|     MainLayout | ||||
|   }, | ||||
|   setup() { | ||||
|     const route = useRoute(); | ||||
|     const isLoginPage = computed(() => route.path === '/login'); | ||||
|  | ||||
|     return { | ||||
|       isLoginPage | ||||
|     }; | ||||
|   } | ||||
| }; | ||||
| </script> | ||||
|   | ||||
							
								
								
									
										117
									
								
								src/components/LoginForm.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										117
									
								
								src/components/LoginForm.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,117 @@ | ||||
| <template> | ||||
|   <div class="login-container"> | ||||
|     <el-card class="login-card"> | ||||
|       <template #header> | ||||
|         <div class="card-header"> | ||||
|           <span>系统登录</span> | ||||
|         </div> | ||||
|       </template> | ||||
|       <el-form | ||||
|         ref="loginFormRef" | ||||
|         :model="loginForm" | ||||
|         :rules="loginRules" | ||||
|         label-width="80px" | ||||
|         class="login-form" | ||||
|         @keyup.enter="handleLogin" | ||||
|       > | ||||
|         <el-form-item label="用户名" prop="identifier"> | ||||
|           <el-input v-model="loginForm.identifier" placeholder="请输入用户名/邮箱/手机号"></el-input> | ||||
|         </el-form-item> | ||||
|         <el-form-item label="密码" prop="password"> | ||||
|           <el-input type="password" v-model="loginForm.password" placeholder="请输入密码" show-password></el-input> | ||||
|         </el-form-item> | ||||
|         <el-form-item> | ||||
|           <el-button type="primary" @click="handleLogin" :loading="loading" style="width: 100%;">登录</el-button> | ||||
|         </el-form-item> | ||||
|       </el-form> | ||||
|     </el-card> | ||||
|   </div> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| import { ref, reactive } from 'vue'; | ||||
| import { useRouter } from 'vue-router'; | ||||
| import { ElMessage } from 'element-plus'; | ||||
| import apiClient from '../api/index.js'; | ||||
|  | ||||
| export default { | ||||
|   name: 'LoginForm', | ||||
|   setup() { | ||||
|     const router = useRouter(); | ||||
|     const loginFormRef = ref(null); | ||||
|     const loading = ref(false); | ||||
|  | ||||
|     const loginForm = reactive({ | ||||
|       identifier: '', | ||||
|       password: '', | ||||
|     }); | ||||
|  | ||||
|     const loginRules = reactive({ | ||||
|       identifier: [ | ||||
|         { required: true, message: '请输入用户名/邮箱/手机号', trigger: 'blur' }, | ||||
|       ], | ||||
|       password: [ | ||||
|         { required: true, message: '请输入密码', trigger: 'blur' }, | ||||
|       ], | ||||
|     }); | ||||
|  | ||||
|     const handleLogin = async () => { | ||||
|       if (!loginFormRef.value) return; | ||||
|       loginFormRef.value.validate(async (valid) => { | ||||
|         if (valid) { | ||||
|           loading.value = true; | ||||
|           try { | ||||
|             const response = await apiClient.users.login(loginForm); | ||||
|             if (response.code === 2000 && response.data && response.data.token) { | ||||
|               localStorage.setItem('jwt_token', response.data.token); | ||||
|               localStorage.setItem('username', response.data.username); // 存储用户名 | ||||
|               ElMessage.success('登录成功!'); | ||||
|               router.push('/'); // 登录成功后跳转到首页 | ||||
|             } else { | ||||
|               ElMessage.error(response.message || '登录失败,请检查用户名或密码!'); | ||||
|             } | ||||
|           } catch (error) { | ||||
|             console.error('登录请求失败:', error); | ||||
|             ElMessage.error('登录请求失败,请稍后再试!'); | ||||
|           } finally { | ||||
|             loading.value = false; | ||||
|           } | ||||
|         } | ||||
|       }); | ||||
|     }; | ||||
|  | ||||
|     return { | ||||
|       loginFormRef, | ||||
|       loginForm, | ||||
|       loginRules, | ||||
|       loading, | ||||
|       handleLogin, | ||||
|     }; | ||||
|   }, | ||||
| }; | ||||
| </script> | ||||
|  | ||||
| <style scoped> | ||||
| .login-container { | ||||
|   display: flex; | ||||
|   justify-content: center; | ||||
|   align-items: center; | ||||
|   min-height: 100vh; | ||||
|   background-color: #f0f2f5; | ||||
| } | ||||
|  | ||||
| .login-card { | ||||
|   width: 400px; | ||||
|   max-width: 90%; | ||||
| } | ||||
|  | ||||
| .card-header { | ||||
|   text-align: center; | ||||
|   font-size: 1.2em; | ||||
|   font-weight: bold; | ||||
| } | ||||
|  | ||||
| .login-form { | ||||
|   padding: 20px; | ||||
| } | ||||
| </style> | ||||
| @@ -45,12 +45,12 @@ | ||||
|           <div class="user-info"> | ||||
|             <el-dropdown> | ||||
|               <span class="el-dropdown-link"> | ||||
|                 管理员 <el-icon><ArrowDown /></el-icon> | ||||
|                 {{ username }} <el-icon><ArrowDown /></el-icon> | ||||
|               </span> | ||||
|               <template #dropdown> | ||||
|                 <el-dropdown-menu> | ||||
|                   <el-dropdown-item>个人信息</el-dropdown-item> | ||||
|                   <el-dropdown-item>退出登录</el-dropdown-item> | ||||
|                   <el-dropdown-item @click="logout">退出登录</el-dropdown-item> | ||||
|                 </el-dropdown-menu> | ||||
|               </template> | ||||
|             </el-dropdown> | ||||
| @@ -72,8 +72,8 @@ | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| import { ref, computed } from 'vue'; | ||||
| import { useRoute } from 'vue-router'; | ||||
| import { ref, computed, onMounted, onUnmounted } from 'vue'; | ||||
| import { useRoute, useRouter } from 'vue-router'; | ||||
| import { House, Monitor, Calendar, ArrowDown, Menu, Fold, Expand } from '@element-plus/icons-vue'; | ||||
|  | ||||
| export default { | ||||
| @@ -89,19 +89,31 @@ export default { | ||||
|   }, | ||||
|   setup() { | ||||
|     const route = useRoute(); | ||||
|     const router = useRouter(); | ||||
|     const isCollapse = ref(false); | ||||
|     const username = ref(localStorage.getItem('username') || '管理员'); | ||||
|  | ||||
|     // 监听localStorage变化,实时更新用户名 | ||||
|     const handleStorageChange = () => { | ||||
|       username.value = localStorage.getItem('username') || '管理员'; | ||||
|     }; | ||||
|  | ||||
|     onMounted(() => { | ||||
|       window.addEventListener('storage', handleStorageChange); | ||||
|     }); | ||||
|  | ||||
|     onUnmounted(() => { | ||||
|       window.removeEventListener('storage', handleStorageChange); | ||||
|     }); | ||||
|      | ||||
|     // 切换侧边栏折叠状态 | ||||
|     const toggleCollapse = () => { | ||||
|       isCollapse.value = !isCollapse.value; | ||||
|     }; | ||||
|      | ||||
|     // 当前激活的菜单项 | ||||
|     const activeMenu = computed(() => { | ||||
|       return route.path; | ||||
|     }); | ||||
|  | ||||
|     // 当前页面标题 | ||||
|     const currentPageTitle = computed(() => { | ||||
|       const routeMap = { | ||||
|         '/': '系统首页', | ||||
| @@ -111,11 +123,20 @@ export default { | ||||
|       return routeMap[route.path] || '猪场管理系统'; | ||||
|     }); | ||||
|  | ||||
|     const logout = () => { | ||||
|       localStorage.removeItem('jwt_token'); | ||||
|       localStorage.removeItem('username'); // 清除用户名 | ||||
|       username.value = '管理员'; // 重置显示 | ||||
|       router.push('/login'); | ||||
|     }; | ||||
|  | ||||
|     return { | ||||
|       isCollapse, | ||||
|       activeMenu, | ||||
|       currentPageTitle, | ||||
|       toggleCollapse | ||||
|       toggleCollapse, | ||||
|       logout, | ||||
|       username | ||||
|     }; | ||||
|   } | ||||
| }; | ||||
|   | ||||
							
								
								
									
										29
									
								
								src/main.js
									
									
									
									
									
								
							
							
						
						
									
										29
									
								
								src/main.js
									
									
									
									
									
								
							| @@ -7,15 +7,17 @@ import App from './App.vue'; | ||||
| import Home from './components/Home.vue'; | ||||
| import DeviceList from './components/DeviceList.vue'; | ||||
| import PlanList from './components/PlanList.vue'; | ||||
| import LoginForm from './components/LoginForm.vue'; // 导入登录组件 | ||||
|  | ||||
| // 导入全局样式 | ||||
| import './assets/styles/main.css'; | ||||
|  | ||||
| // 配置路由 | ||||
| const routes = [ | ||||
|   { path: '/', component: Home }, | ||||
|   { path: '/devices', component: DeviceList }, | ||||
|   { path: '/plans', component: PlanList } | ||||
|   { path: '/', component: Home, meta: { requiresAuth: true } }, | ||||
|   { path: '/devices', component: DeviceList, meta: { requiresAuth: true } }, | ||||
|   { path: '/plans', component: PlanList, meta: { requiresAuth: true } }, | ||||
|   { path: '/login', component: LoginForm } | ||||
| ]; | ||||
|  | ||||
| const router = createRouter({ | ||||
| @@ -23,6 +25,21 @@ const router = createRouter({ | ||||
|   routes | ||||
| }); | ||||
|  | ||||
| // 全局路由守卫 | ||||
| router.beforeEach((to, from, next) => { | ||||
|   const loggedIn = localStorage.getItem('jwt_token'); | ||||
|  | ||||
|   if (to.matched.some(record => record.meta.requiresAuth) && !loggedIn) { | ||||
|     // 如果路由需要认证但用户未登录,则重定向到登录页 | ||||
|     next('/login'); | ||||
|   } else if (to.path === '/login' && loggedIn) { | ||||
|     // 如果用户已登录但试图访问登录页,则重定向到首页 | ||||
|     next('/'); | ||||
|   } else { | ||||
|     next(); // 正常放行 | ||||
|   } | ||||
| }); | ||||
|  | ||||
| // 创建Vue应用实例 | ||||
| const app = createApp(App); | ||||
|  | ||||
| @@ -32,9 +49,5 @@ app.use(ElementPlus); | ||||
| // 使用路由 | ||||
| app.use(router); | ||||
|  | ||||
| // 初始化服务(示例) | ||||
| // app.config.globalProperties.$api = ApiService; | ||||
| // app.config.globalProperties.$utils = Utils; | ||||
|  | ||||
| // 挂载应用 | ||||
| app.mount('#app'); | ||||
| app.mount('#app'); | ||||
|   | ||||
| @@ -14,11 +14,11 @@ const http = axios.create({ | ||||
| // 请求拦截器 | ||||
| http.interceptors.request.use( | ||||
|   config => { | ||||
|     // 可以在这里添加认证token等 | ||||
|     // const token = localStorage.getItem('access_token'); | ||||
|     // if (token) { | ||||
|     //   config.headers.Authorization = `Bearer ${token}`; | ||||
|     // } | ||||
|     // 在这里添加认证token | ||||
|     const token = localStorage.getItem('jwt_token'); | ||||
|     if (token) { | ||||
|       config.headers.Authorization = `Bearer ${token}`; | ||||
|     } | ||||
|     return config; | ||||
|   }, | ||||
|   error => { | ||||
| @@ -49,6 +49,14 @@ http.interceptors.response.use( | ||||
|     if (error.response) { | ||||
|       // 服务器返回错误状态码 | ||||
|       console.error('API Error:', error.response.status, error.response.data); | ||||
|       // 如果是401未授权,可以考虑重定向到登录页 | ||||
|       if (error.response.status === 401) { | ||||
|         // 清除token并重定向到登录页 | ||||
|         localStorage.removeItem('jwt_token'); | ||||
|         // 这里需要访问router,但http.js是纯工具文件,不应直接依赖Vue Router实例 | ||||
|         // 可以在main.js的全局错误处理或组件中处理401错误 | ||||
|         // 例如:window.location.href = '/login'; | ||||
|       } | ||||
|     } else if (error.request) { | ||||
|       // 请求发出但没有收到响应 | ||||
|       console.error('Network Error:', error.message); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user