管理员为用户添加临时权限
This commit is contained in:
parent
ddfc8e98c6
commit
ebf7a5125b
@ -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<Object>} 用户信息对象
|
||||
*/
|
||||
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<void>} 无返回值,成功即为修改成功
|
||||
*/
|
||||
export const changeUserName = async (name) => {
|
||||
try {
|
||||
await axiosInstance.put('/user/changename', { name });
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户请求修改密码(发送重置请求)
|
||||
* 路由: /user/resetpassword
|
||||
* 方法: POST
|
||||
* 需要登录
|
||||
* @returns {Promise<void>} 无返回值,成功即为请求成功
|
||||
*/
|
||||
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<void>} 无返回值,成功即为修改成功
|
||||
*/
|
||||
export const resetPassword = async (token, password) => {
|
||||
try {
|
||||
await axiosInstance.put('/user/resetpassword', { token, password });
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户临时权限信息
|
||||
* 路由: /user/temp_privilege
|
||||
* 方法: GET
|
||||
* 需要登录
|
||||
* @returns {Promise<Object>} 返回一个包含临时权限信息的Promise对象
|
||||
*/
|
||||
export const getTempPrivilege = async () => {
|
||||
try {
|
||||
const response = await axiosInstance.get('/user/temp_privilege');
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
@ -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) {
|
||||
<span class="nav-link">地形与纹理</span>
|
||||
<div class="dropdown-content">
|
||||
<router-link to="/terrain" class="nav-link">地形图列表</router-link>
|
||||
<router-link v-if="isLoggedIn" to="/terrainGenerate" class="nav-link" @click.prevent="handleNavClick('/terrainGenerate', ['lv-admin','lv-mod','lv-map','lv-user','lv-competitor'])">地形纹理合成工具</router-link>
|
||||
<router-link v-if="isLoggedIn" to="/terrainGenerate" class="nav-link" @click.prevent="handleNavClick('/terrainGenerate', ['lv-admin','lv-mod','lv-map','lv-competitor'])">地形纹理合成工具</router-link>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 仅登录后显示的菜单项 -->
|
||||
@ -225,7 +267,7 @@ function handleNavClick(route, privilegeList) {
|
||||
<span class="nav-link">在线工具</span>
|
||||
<div class="dropdown-content">
|
||||
<router-link to="/weapon-match" class="nav-link" @click.prevent="handleNavClick('/weapon-match', ['lv-admin','lv-mod'])">Weapon 匹配</router-link>
|
||||
<router-link to="/PIC2TGA" class="nav-link" @click.prevent="handleNavClick('/PIC2TGA', ['lv-admin','lv-mod','lv-map','lv-user','lv-competitor'])">在线转tga工具</router-link>
|
||||
<router-link to="/PIC2TGA" class="nav-link" @click.prevent="handleNavClick('/PIC2TGA', ['lv-admin','lv-mod','lv-map','lv-competitor'])">在线转tga工具</router-link>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 赛事信息 一级菜单 -->
|
||||
@ -239,7 +281,7 @@ function handleNavClick(route, privilegeList) {
|
||||
<div class="nav-dropdown">
|
||||
<span class="nav-link">公共信息区</span>
|
||||
<div class="dropdown-content">
|
||||
<router-link to="/demands" class="nav-link" @click.prevent="handleNavClick('/demands', ['lv-admin','lv-mod','lv-map','lv-user','lv-competitor'])">办事大厅</router-link>
|
||||
<router-link to="/demands" class="nav-link" @click.prevent="handleNavClick('/demands', ['lv-admin','lv-mod','lv-map','lv-competitor'])">办事大厅</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@ -292,6 +334,11 @@ function handleNavClick(route, privilegeList) {
|
||||
@close="privilegeDialogVisible = false"
|
||||
@apply="handlePrivilegeApply"
|
||||
/>
|
||||
<SuccessDialog
|
||||
:visible="successDialog.visible"
|
||||
:message="successDialog.message"
|
||||
@close="successDialog.visible = false"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user