实现登录
This commit is contained in:
@@ -1749,7 +1749,6 @@
|
||||
"properties": {
|
||||
"password": {
|
||||
"type": "string",
|
||||
"minLength": 6,
|
||||
"example": "password123"
|
||||
},
|
||||
"username": {
|
||||
|
||||
17
src/App.vue
17
src/App.vue
@@ -1,14 +1,31 @@
|
||||
<template>
|
||||
<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
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
27
src/main.js
27
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');
|
||||
@@ -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