实现登录

This commit is contained in:
2025-09-30 22:35:36 +08:00
parent effc2c06e0
commit a2a9cc1450
6 changed files with 198 additions and 23 deletions

View File

@@ -1749,7 +1749,6 @@
"properties": {
"password": {
"type": "string",
"minLength": 6,
"example": "password123"
},
"username": {

View File

@@ -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>

View 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>

View File

@@ -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
};
}
};

View File

@@ -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');

View File

@@ -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);