From ebf7a5125b7e39b0385ed331f6a32b0343566c9e Mon Sep 17 00:00:00 2001 From: Kunagisa <1549184870@qq.com> Date: Sat, 5 Jul 2025 00:24:11 +0800 Subject: [PATCH] =?UTF-8?q?=E7=AE=A1=E7=90=86=E5=91=98=E4=B8=BA=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E6=B7=BB=E5=8A=A0=E4=B8=B4=E6=97=B6=E6=9D=83=E9=99=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/login.js | 81 ++++++++++++++++++++++++++++++++++++++++++ src/router/index.js | 10 +++--- src/utils/jwt.js | 14 ++++++++ src/utils/privilege.js | 30 ++++++++++++++++ src/views/index.vue | 73 ++++++++++++++++++++++++++++++------- 5 files changed, 190 insertions(+), 18 deletions(-) diff --git a/src/api/login.js b/src/api/login.js index 1cc6f05..7dfd10b 100644 --- a/src/api/login.js +++ b/src/api/login.js @@ -116,3 +116,84 @@ export const adminAddTempPrivilege = async (uuid, privilege, exp_time) => { throw error; } } + +/** + * 获取用户信息 + * 路由: /user/get + * 方法: GET + * 参数: uuid | name | qq_code (优先级: uuid > name > qq_code) + * @param {Object} params - 查询参数 { uuid, name, qq_code } + * @returns {Promise} 用户信息对象 + */ +export const getUserByInfo = async (params = {}) => { + try { + const response = await axiosInstance.get('/user/get', { params }); + return response.data; + } catch (error) { + throw error; + } +} + +/** + * 修改用户名 + * 路由: /user/changename + * 方法: PUT + * 需要登录 + * @param {string} name - 新用户名 + * @returns {Promise} 无返回值,成功即为修改成功 + */ +export const changeUserName = async (name) => { + try { + await axiosInstance.put('/user/changename', { name }); + } catch (error) { + throw error; + } +} + +/** + * 用户请求修改密码(发送重置请求) + * 路由: /user/resetpassword + * 方法: POST + * 需要登录 + * @returns {Promise} 无返回值,成功即为请求成功 + */ +export const requestResetPassword = async () => { + try { + await axiosInstance.post('/user/resetpassword'); + } catch (error) { + throw error; + } +} + +/** + * 用户修改密码 + * 路由: /user/resetpassword + * 方法: PUT + * 需要登录 + * @param {string} token - 请求token + * @param {string} password - 新密码 + * @returns {Promise} 无返回值,成功即为修改成功 + */ +export const resetPassword = async (token, password) => { + try { + await axiosInstance.put('/user/resetpassword', { token, password }); + } catch (error) { + throw error; + } +} + +/** + * 获取用户临时权限信息 + * 路由: /user/temp_privilege + * 方法: GET + * 需要登录 + * @returns {Promise} 返回一个包含临时权限信息的Promise对象 + */ +export const getTempPrivilege = async () => { + try { + const response = await axiosInstance.get('/user/temp_privilege'); + return response.data; + } catch (error) { + throw error; + } +} diff --git a/src/router/index.js b/src/router/index.js index d3fa8a3..288214b 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -3,7 +3,7 @@ import { hasValidToken, getUserInfo, logoutUser, getStoredUser } from '../utils/ import { justLoggedIn } from '../utils/authSessionState'; import EditorsMaps from '@/views/index/EditorsMaps.vue' import mitt from 'mitt' -import { hasPrivilege } from '@/utils/privilege' +import { hasPrivilege, hasPrivilegeWithTemp } from '@/utils/privilege' const routes = [ { @@ -22,7 +22,7 @@ const routes = [ path: 'demands', name: 'DemandList', component: () => import('@/views/index/DemandList.vue'), - meta: { requiresAuth: true, requiredPrivilege: ['lv-admin','lv-mod','lv-map','lv-user','lv-competitor'] } + meta: { requiresAuth: true, requiredPrivilege: ['lv-admin','lv-mod','lv-map','lv-competitor'] } }, { path: 'maps', @@ -88,13 +88,13 @@ const routes = [ path: 'PIC2TGA', name: 'PIC2TGA', component: () => import('@/views/index/PIC2TGA.vue'), - meta: { requiredPrivilege: ['lv-admin','lv-mod','lv-map','lv-user','lv-competitor'] } + meta: { requiredPrivilege: ['lv-admin','lv-mod','lv-map','lv-competitor'] } }, { path: 'terrainGenerate', name: 'TerrainGenerate', component: () => import('@/views/index/TerrainGenerate.vue'), - meta: { requiredPrivilege: ['lv-admin','lv-mod','lv-map','lv-user','lv-competitor'] } + meta: { requiredPrivilege: ['lv-admin','lv-mod','lv-map','lv-competitor'] } } ] }, @@ -170,7 +170,7 @@ router.beforeEach(async (to, from, next) => { // 权限校验 if (requiredPrivilege) { const user = getStoredUser(); - if (!user || !hasPrivilege(user.privilege, requiredPrivilege)) { + if (!user || !hasPrivilegeWithTemp(user, requiredPrivilege)) { // 通过事件总线通知弹窗 eventBus.emit('no-privilege') return next(false) diff --git a/src/utils/jwt.js b/src/utils/jwt.js index 4abb70e..5b5ae28 100644 --- a/src/utils/jwt.js +++ b/src/utils/jwt.js @@ -1,6 +1,7 @@ import axiosInstance from '../api/axiosConfig'; import router from '../router'; // 引入 Vue Router 实例 import { justLoggedIn } from './authSessionState'; // Import the flag +import { getTempPrivilege } from '../api/login'; // 导入获取临时权限的函数 const USER_INFO_URL = '/user'; // 获取用户信息的API端点 @@ -42,8 +43,21 @@ export const getUserInfo = async () => { throw new Error('No valid token found'); } try { + // 首先获取用户基本信息 const response = await axiosInstance.get(USER_INFO_URL); const user = response.data; + + try { + // 尝试获取临时权限信息,如果失败不影响用户基本信息的返回 + const tempPrivilegeResponse = await getTempPrivilege(); + if (tempPrivilegeResponse && tempPrivilegeResponse.privilege) { + user.temp_privilege = tempPrivilegeResponse.privilege; + } + } catch (error) { + // 如果获取临时权限失败,只记录错误但不影响主流程 + console.warn('Failed to fetch temp privilege:', error); + } + // 将获取到的用户信息存入 sessionStorage sessionStorage.setItem('currentUser', JSON.stringify(user)); return user; diff --git a/src/utils/privilege.js b/src/utils/privilege.js index e48ff4c..7c69d2f 100644 --- a/src/utils/privilege.js +++ b/src/utils/privilege.js @@ -8,4 +8,34 @@ export function hasPrivilege(privilege, required) { return required.some(r => privArr.includes(r)); } return privArr.includes(required); +} + +// 合并永久权限和临时权限 +export function mergePrivileges(permanentPrivilege, tempPrivilege) { + if (!permanentPrivilege && !tempPrivilege) return ''; + + let mergedPrivileges = []; + + // 添加永久权限 + if (permanentPrivilege) { + mergedPrivileges.push(...permanentPrivilege.split(';')); + } + + // 添加临时权限(如果存在) + if (tempPrivilege) { + mergedPrivileges.push(...tempPrivilege.split(';')); + } + + // 去重并返回 + return [...new Set(mergedPrivileges)].join(';'); +} + +// 检查用户是否有指定权限(包括临时权限) +export function hasPrivilegeWithTemp(userData, required) { + if (!userData) return false; + + // 合并永久权限和临时权限 + const mergedPrivileges = mergePrivileges(userData.privilege, userData.temp_privilege); + + return hasPrivilege(mergedPrivileges, required); } \ No newline at end of file diff --git a/src/views/index.vue b/src/views/index.vue index d2f872b..3a35a76 100644 --- a/src/views/index.vue +++ b/src/views/index.vue @@ -2,10 +2,11 @@ import { computed, ref, onMounted, onUnmounted, defineAsyncComponent } from 'vue' import { getUserInfo } from '@/utils/jwt' import { useRouter } from 'vue-router' -import { hasPrivilege } from '@/utils/privilege' +import { hasPrivilege, hasPrivilegeWithTemp, mergePrivileges } from '@/utils/privilege' import { eventBus } from '@/router/index.js' import { requestTempPrivilege } from '@/api/login.js' const PrivilegeRequestDialog = defineAsyncComponent(() => import('@/components/PrivilegeRequestDialog.vue')) +const SuccessDialog = defineAsyncComponent(() => import('@/components/SuccessDialog.vue')) const isLoggedIn = computed(() => { return !!localStorage.getItem('access_token') && !!currentUserData.value @@ -28,13 +29,16 @@ const userAvatarUrl = computed(() => { return null }) -// 获取用户最高权限 +// 获取用户最高权限(包括临时权限) const userHighestPrivilege = computed(() => { - if (!currentUserData.value || !currentUserData.value.privilege) { + if (!currentUserData.value) { return null } - const privileges = currentUserData.value.privilege.split(';') + const mergedPrivileges = mergePrivileges(currentUserData.value.privilege, currentUserData.value.temp_privilege) + if (!mergedPrivileges) return null + + const privileges = mergedPrivileges.split(';') // 权限等级排序(从高到低) const privilegeLevels = ['lv-admin', 'lv-mod', 'lv-competitor', 'lv-map', 'lv-user'] @@ -61,7 +65,14 @@ const privilegeDisplayName = computed(() => { 'lv-competitor': '竞技' } - return displayNames[privilege] || privilege + // 检查是否是临时权限(lv-user不添加临时前缀) + const isTempPrivilege = privilege !== 'lv-user' && + currentUserData.value && + currentUserData.value.temp_privilege && + currentUserData.value.temp_privilege.includes(privilege) + + const baseName = displayNames[privilege] || privilege + return isTempPrivilege ? `临时:${baseName}` : baseName }) // 权限标签样式 @@ -101,6 +112,8 @@ const toggleDropdown = () => { showDropdown.value = !showDropdown.value } +let privilegeCheckTimer = null + onMounted(() => { if (localStorage.getItem('access_token')) { getUserInfo().then(userInfo => { @@ -114,20 +127,49 @@ onMounted(() => { // 添加点击外部关闭下拉菜单的监听器 document.addEventListener('click', handleClickOutside) + + // 启动权限检查定时器(每30秒检查一次) + privilegeCheckTimer = setInterval(async () => { + if (localStorage.getItem('access_token') && currentUserData.value) { + try { + const userInfo = await getUserInfo() + if (userInfo) { + // 检查临时权限是否消失 + const hadTempPrivilege = currentUserData.value.temp_privilege && currentUserData.value.temp_privilege.trim() !== '' + const hasTempPrivilege = userInfo.temp_privilege && userInfo.temp_privilege.trim() !== '' + + // 如果之前有临时权限,现在没有了,则重定向到maps页面 + if (hadTempPrivilege && !hasTempPrivilege) { + router.push('/maps') + } + + currentUserData.value = userInfo + } + } catch (error) { + console.log('权限检查失败:', error) + } + } + }, 30 * 1000) // 30秒 }) onUnmounted(() => { // 移除事件监听器 document.removeEventListener('click', handleClickOutside) + + // 清除定时器 + if (privilegeCheckTimer) { + clearInterval(privilegeCheckTimer) + privilegeCheckTimer = null + } }) // 判断每个一级菜单是否有可见项 const showTerrainList = true // 地形图列表始终可见 const showTerrainGenerate = computed(() => isLoggedIn.value && currentUserData.value) -const showWeaponMatch = computed(() => isLoggedIn.value && currentUserData.value && hasPrivilege(currentUserData.value.privilege, ['lv-admin', 'lv-mod'])) +const showWeaponMatch = computed(() => isLoggedIn.value && currentUserData.value && hasPrivilegeWithTemp(currentUserData.value, ['lv-admin', 'lv-mod'])) const showPic2Tga = computed(() => isLoggedIn.value && currentUserData.value) const showDemands = computed(() => isLoggedIn.value && currentUserData.value) -const showCompetition = computed(() => isLoggedIn.value && currentUserData.value && hasPrivilege(currentUserData.value.privilege, ['lv-admin', 'lv-competitor'])) +const showCompetition = computed(() => isLoggedIn.value && currentUserData.value && hasPrivilegeWithTemp(currentUserData.value, ['lv-admin', 'lv-competitor'])) const showTerrainMenu = computed(() => showTerrainList || showTerrainGenerate.value) const showOnlineToolsMenu = computed(() => showWeaponMatch.value || showPic2Tga.value) @@ -170,9 +212,9 @@ async function handlePrivilegeApply() { } function handleNavClick(route, privilegeList) { - if (!isLoggedIn.value || !currentUserData.value || !hasPrivilege(currentUserData.value.privilege, privilegeList)) { - // 取权限数组中优先级最高的非lv-admin权限(如有) - const order = ['lv-mod', 'lv-competitor', 'lv-map', 'lv-user'] + if (!isLoggedIn.value || !currentUserData.value || !hasPrivilegeWithTemp(currentUserData.value, privilegeList)) { + // 取权限数组中优先级最高的非lv-admin权限(如有),排除lv-user + const order = ['lv-mod', 'lv-competitor', 'lv-map'] let privilegeKey = '' for (const key of order) { if (privilegeList && privilegeList.includes(key)) { @@ -215,7 +257,7 @@ function handleNavClick(route, privilegeList) { 地形与纹理 @@ -225,7 +267,7 @@ function handleNavClick(route, privilegeList) { 在线工具 @@ -239,7 +281,7 @@ function handleNavClick(route, privilegeList) { @@ -292,6 +334,11 @@ function handleNavClick(route, privilegeList) { @close="privilegeDialogVisible = false" @apply="handlePrivilegeApply" /> +