refactor: 优化权限管理与登录流程,修复细节问题

1.  调整仪表盘图标从DeviceOutlined为MonitorOutlined
2.  新增403无权访问页面与路由配置
3.  完善权限存储与路由守卫逻辑
4.  修复用户信息获取与token持久化问题
5.  补充普通管理员权限初始化与补丁脚本
6.  优化后端用户服务与认证控制器逻辑
7.  调整登录表单交互与请求流程
This commit is contained in:
andyesfly committed 2026-05-28 16:25:26 +08:00
1 parent 3d7eb12103
commit 2534723842
9 files changed
+259 -50

No files matched your search

+20 -3
View File
@@ -9,6 +9,12 @@ const routes: RouteRecordRaw[] = [
component: () => import("@/views/login/index.vue"),
meta: { requiresAuth: false, title: "登录" }
},
{
path: "/403",
name: "Forbidden",
component: () => import("@/views/403.vue"),
meta: { requiresAuth: false, title: "无权访问" }
},
{
path: "/",
component: () => import("@/layouts/index.vue"),
@@ -112,9 +118,20 @@ router.beforeEach(async (to, _from, next) => {
if (!permissionStore.userInfo) {
try {
await permissionStore.fetchUserInfo();
const success = await permissionStore.fetchUserInfo();
if (!success) {
next({
path: "/login",
query: { redirect: to.fullPath }
});
return;
}
} catch (error) {
permissionStore.logout();
console.error('获取用户信息失败:', error);
next({
path: "/login",
query: { redirect: to.fullPath }
});
return;
}
}
@@ -129,4 +146,4 @@ router.beforeEach(async (to, _from, next) => {
next();
});
export default router;
export default router;
+21 -2
View File
@@ -14,7 +14,7 @@ interface UserInfo {
export const usePermissionStore = defineStore('permission', () => {
const userInfo = ref<UserInfo | null>(null);
const token = ref<string>('');
const token = ref<string>(localStorage.getItem('access_token') || '');
const permissions = ref<string[]>([]);
const isLoggedIn = computed(() => !!token.value);
@@ -64,12 +64,28 @@ export const usePermissionStore = defineStore('permission', () => {
if (res.code === 200) {
userInfo.value = res.data;
permissions.value = res.data.permissions || [];
return true;
}
return false;
} catch (error) {
console.error('获取用户信息失败:', error);
return false;
}
}
function setToken(newToken: string) {
token.value = newToken;
localStorage.setItem('access_token', newToken);
}
function setUserInfo(info: UserInfo | null) {
userInfo.value = info;
}
function setPermissions(perms: string[]) {
permissions.value = perms;
}
return {
userInfo,
token,
@@ -80,5 +96,8 @@ export const usePermissionStore = defineStore('permission', () => {
login,
logout,
fetchUserInfo,
setToken,
setUserInfo,
setPermissions,
};
});
});
+81
View File
@@ -0,0 +1,81 @@
<template>
<div class="forbidden-container">
<div class="forbidden-content">
<div class="forbidden-icon">
<LockOutlined />
</div>
<h1 class="forbidden-title">403</h1>
<p class="forbidden-desc">抱歉,您没有访问此页面的权限</p>
<p class="forbidden-hint">如需访问,请联系管理员分配相应权限</p>
<a-button type="primary" @click="goBack">
<ArrowLeftOutlined />
返回登录
</a-button>
</div>
</div>
</template>
<script setup lang="ts">
import { useRouter } from 'vue-router'
import { LockOutlined, ArrowLeftOutlined } from '@ant-design/icons-vue'
import { Button } from 'ant-design-vue'
const router = useRouter()
function goBack() {
router.push('/login')
}
</script>
<style scoped lang="less">
.forbidden-container {
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
background: #f5f5f5;
}
.forbidden-content {
text-align: center;
padding: 60px 40px;
background: #fff;
border-radius: 12px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
}
.forbidden-icon {
width: 120px;
height: 120px;
margin: 0 auto 24px;
background: #fff2f0;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
:deep(.anticon) {
font-size: 64px;
color: #ff4d4f;
}
}
.forbidden-title {
font-size: 72px;
font-weight: 700;
color: #1f1f1f;
margin: 0 0 16px 0;
}
.forbidden-desc {
font-size: 16px;
color: #666;
margin: 0 0 8px 0;
}
.forbidden-hint {
font-size: 14px;
color: #999;
margin: 0 0 32px 0;
}
</style>
+3 -3
View File
@@ -121,7 +121,7 @@ import {
message,
} from 'ant-design-vue';
import {
DeviceOutlined,
MonitorOutlined,
CheckCircleOutlined,
AlertOutlined,
TeamOutlined,
@@ -149,7 +149,7 @@ const statCards = [
{
key: 'deviceTotal',
title: '设备总数',
icon: markRaw(DeviceOutlined),
icon: markRaw(MonitorOutlined),
color: '#1890ff',
iconBg: '#e6f7ff',
},
@@ -603,4 +603,4 @@ function getActivityColor(module: string): string {
}
}
}
</style>
</style>
+31 -17
View File
@@ -20,7 +20,7 @@
<a-form ref="formRef" :model="formState" :rules="rules" size="large" @finish="handleLogin">
<a-form-item name="username">
<a-input ref="usernameInputRef" v-model:value="formState.username" placeholder="请输入用户名" allow-clear
:disabled="loading" @pressEnter="focusPassword">
:disabled="loading" @pressEnter="handlePasswordEnter">
<template #prefix>
<UserOutlined class="input-icon" />
</template>
@@ -29,7 +29,7 @@
<a-form-item name="password">
<a-input-password ref="passwordInputRef" v-model:value="formState.password" placeholder="请输入密码" allow-clear
:disabled="loading" @pressEnter="submitForm">
:disabled="loading" @pressEnter="handleLogin">
<template #prefix>
<LockOutlined class="input-icon" />
</template>
@@ -68,6 +68,7 @@
import { reactive, ref, onMounted, nextTick } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { message } from 'ant-design-vue'
import axios from 'axios'
import {
UserOutlined,
LockOutlined,
@@ -112,33 +113,46 @@ onMounted(() => {
})
})
function focusPassword() {
function handlePasswordEnter() {
passwordInputRef.value?.focus()
}
function submitForm() {
formRef.value?.submit()
}
async function handleLogin() {
try {
loading.value = true
await permissionStore.login(formState.username, formState.password)
const response = await axios.post('/api/auth/login', {
username: formState.username,
password: formState.password
})
if (formState.rememberMe) {
localStorage.setItem('remembered_username', formState.username)
} else {
localStorage.removeItem('remembered_username')
}
const res = response.data
message.success('登录成功!')
if (res.code === 200) {
const { accessToken, refreshToken, userInfo } = res.data
localStorage.setItem('access_token', accessToken)
localStorage.setItem('refresh_token', refreshToken)
permissionStore.setToken(accessToken)
permissionStore.setUserInfo(userInfo)
permissionStore.setPermissions(userInfo.permissions || [])
if (formState.rememberMe) {
localStorage.setItem('remembered_username', formState.username)
} else {
localStorage.removeItem('remembered_username')
}
message.success('登录成功!')
setTimeout(() => {
const redirect = (route.query.redirect as string) || '/'
router.push(redirect)
}, 500)
} else {
throw new Error(res.message || '登录失败')
}
} catch (error: any) {
console.error('登录失败:', error)
message.error(error.message || '登录失败,请检查用户名和密码')
formState.password = ''
nextTick(() => {
@@ -344,4 +358,4 @@ async function handleLogin() {
color: rgba(255, 255, 255, 0.5);
}
}
</style>
</style>