优化用户信息展示和权限显示逻辑

This commit is contained in:
jxxghp
2025-06-11 12:56:10 +08:00
parent 052d6edd13
commit 3c051b8698
8 changed files with 251 additions and 166 deletions
+5 -2
View File
@@ -123,8 +123,10 @@ onMounted(() => {
'transition-transform duration-300 hover:-translate-y-1',
!props.user.is_active ? 'opacity-85 bg-surface-lighten-1' : '',
]"
class="flex flex-column"
@click="userEditDialog = true"
>
<div class="flex-grow">
<!-- 用户头像和基本信息 -->
<VCardItem :class="[user.is_superuser ? 'admin-header' : '']">
<template v-slot:prepend>
@@ -242,10 +244,10 @@ onMounted(() => {
{{ t('dialog.userAddEdit.permissions.manage') }}
</VChip>
</div>
</div>
<!-- 独立的邮箱显示 -->
<VDivider class="mx-4" />
<div>
<VCardText class="d-flex align-center py-2 px-4 text-medium-emphasis">
<VIcon icon="mdi-email-outline" size="small" color="primary" class="mr-2 opacity-70" />
<span class="text-body-2 truncate">{{ user.email || t('user.noEmail') }}</span>
@@ -290,6 +292,7 @@ onMounted(() => {
</div>
</div>
</VCardText>
</div>
</VCard>
<!-- 用户编辑弹窗 -->
+53 -5
View File
@@ -19,10 +19,23 @@ const route = useRoute()
const userStore = useUserStore()
// 获取用户权限信息
const userPermissions = computed(() => ({
const userPermissions = computed(() => {
// 确保用户已认证且信息已加载
if (!userStore || userStore.userID === -1) {
return {
is_superuser: false,
discovery: false,
search: false,
subscribe: false,
manage: false,
}
}
return {
is_superuser: userStore.superUser,
...userStore.permissions,
}))
}
})
// 获取导航菜单
const navMenus = computed(() => {
@@ -41,7 +54,42 @@ const currentMenu = ref<string>(getMenuPathFromRoute(route.path))
// 过滤出底部菜单项
const footerMenus = computed(() => {
return navMenus.value.filter((menu: NavMenu) => menu.footer === true)
// 获取所有有权限的菜单
const allAuthorizedMenus = navMenus.value
// 优先获取有 footer: true 属性的菜单
const footerMenusWithProperty = allAuthorizedMenus.filter((menu: NavMenu) => menu.footer === true)
// 设置期望的底部菜单数量(不包括"更多"按钮)
// 一般来说,底部导航栏显示 3-4 个主要功能比较合适
const expectedFooterMenuCount = 3
// 如果有 footer 属性的菜单已经足够,优先显示它们
if (footerMenusWithProperty.length >= expectedFooterMenuCount) {
return footerMenusWithProperty.slice(0, expectedFooterMenuCount)
}
// 如果不够,从没有 footer 属性或 footer 为 false 的菜单中补充
// 优先选择一些常用的功能菜单
const nonFooterMenus = allAuthorizedMenus.filter(
(menu: NavMenu) =>
menu.footer !== true &&
// 排除已经在 footerMenusWithProperty 中的菜单
!footerMenusWithProperty.some(footerMenu => footerMenu.to === menu.to),
)
// 计算还需要多少个菜单
const needCount = expectedFooterMenuCount - footerMenusWithProperty.length
// 合并菜单:优先显示有 footer 属性的,然后按菜单定义顺序添加其他菜单
let finalMenus = [...footerMenusWithProperty, ...nonFooterMenus.slice(0, needCount)]
// 确保至少有一个菜单显示,如果都没有权限,则显示第一个有权限的菜单
if (finalMenus.length === 0 && allAuthorizedMenus.length > 0) {
finalMenus = [allAuthorizedMenus[0]]
}
return finalMenus
})
// 监听路由变化来更新currentMenu
@@ -209,8 +257,8 @@ const showDynamicButton = computed(() => {
.footer-card-content {
position: relative;
padding-block: 6px;
padding-inline: 8px;
padding-block: 4px;
padding-inline: 6px;
}
.footer-btn-group {
+1
View File
@@ -63,6 +63,7 @@ function logout() {
// 清除登录状态信息
authStore.logout()
userStore.reset()
// 重定向到登录页面或其他适当的页面
router.push('/login')
}
+1
View File
@@ -137,6 +137,7 @@ export default {
networkError: 'Login failed, please check your network connection!',
authFailure: 'Login failed, please check your username, password or two-factor authentication!',
permissionDenied: 'Login failed, you do not have permission to access!',
noPermission: 'Login failed, you have no functional permissions, please contact the administrator!',
serverError: 'Login failed, server error!',
loginFailed: 'Login Failed',
checkCredentials: 'Please check your username, password or two-factor authentication code!',
+1
View File
@@ -137,6 +137,7 @@ export default {
networkError: '登录失败,请检查网络连接!',
authFailure: '登录失败,请检查用户名、密码或双重验证是否正确!',
permissionDenied: '登录失败,您没有权限访问!',
noPermission: '登录失败,您没有任何功能权限,请联系管理员!',
serverError: '登录失败,服务器错误!',
loginFailed: '登录失败',
checkCredentials: '请检查用户名、密码或双重验证码是否正确!',
+1
View File
@@ -139,6 +139,7 @@ export default {
authFailure: '登錄失敗,請檢查用戶名、密碼或雙重驗證是否正確!',
permissionDenied: '登錄失敗,您沒有權限訪問!',
serverError: '登錄失敗,服務器錯誤!',
noPermission: '登錄失敗,您沒有任何功能權限,請聯繫管理員!',
loginFailed: '登錄失敗',
checkCredentials: '請檢查用戶名、密碼或雙重驗證碼是否正確!',
},
+36 -9
View File
@@ -11,6 +11,8 @@ import { urlBase64ToUint8Array } from '@/@core/utils/navigator'
import { SUPPORTED_LOCALES, SupportedLocale } from '@/types/i18n'
import { getCurrentLocale, setI18nLanguage } from '@/plugins/i18n'
import { useTheme } from 'vuetify'
import { getNavMenus } from '@/router/i18n-menu'
import { filterMenusByPermission } from '@/utils/permission'
//
const { t } = useI18n()
@@ -19,6 +21,9 @@ const authStore = useAuthStore()
// Store
const userStore = useUserStore()
//
const navMenus = getNavMenus()
//
const form = ref({
username: '',
@@ -111,9 +116,15 @@ async function subscribeForPushNotifications() {
}
//
async function afterLogin(superuser: boolean) {
//
router.push(authStore.originalPath ?? '/')
async function afterLogin(superuser: boolean, userPayload: userState, filteredMenus: any[]) {
//
if (authStore.originalPath && authStore.originalPath !== '/') {
router.push(authStore.originalPath)
} else {
//
router.push(filteredMenus[0].to)
}
//
if (superuser) await subscribeForPushNotifications()
// loading
@@ -147,11 +158,6 @@ function login() {
},
})
.then((response: any) => {
const authPayLoad: authState = {
token: response.access_token,
remember: form.value.remember,
}
const userPayload: userState = {
superUser: response.super_user,
userID: response.user_id,
@@ -161,11 +167,32 @@ function login() {
permissions: response.permissions,
}
//
const userPermissions = {
is_superuser: userPayload.superUser,
...userPayload.permissions,
}
const filteredMenus = filterMenusByPermission(navMenus, userPermissions)
//
if (filteredMenus.length === 0) {
//
errorMessage.value = t('login.noPermission')
loading.value = false
return
}
//
const authPayLoad: authState = {
token: response.access_token,
remember: form.value.remember,
}
authStore.login(authPayLoad)
userStore.loginUser(userPayload)
//
afterLogin(userPayload.superUser)
afterLogin(userPayload.superUser, userPayload, filteredMenus)
})
.catch((error: any) => {
//
+4 -1
View File
@@ -224,15 +224,18 @@ function abortAllControllers() {
}
// 路由导航守卫
router.beforeEach((to: any, from: any, next: any) => {
router.beforeEach(async (to: any, from: any, next: any) => {
// 认证 Store
const authStore = useAuthStore()
// 总是记录非login路由
if (to.fullPath != '/login') authStore.originalPath = to.fullPath
const isAuthenticated = authStore.token !== null
if (to.meta.requiresAuth && !isAuthenticated) {
// 用户未登录,重定向到登录页
next('/login')
} else {
// 清理所有中止控制器
abortAllControllers()
next()
}