管理员为用户添加临时权限
This commit is contained in:
parent
ddfc8e98c6
commit
ebf7a5125b
@ -116,3 +116,84 @@ export const adminAddTempPrivilege = async (uuid, privilege, exp_time) => {
|
|||||||
throw error;
|
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 { justLoggedIn } from '../utils/authSessionState';
|
||||||
import EditorsMaps from '@/views/index/EditorsMaps.vue'
|
import EditorsMaps from '@/views/index/EditorsMaps.vue'
|
||||||
import mitt from 'mitt'
|
import mitt from 'mitt'
|
||||||
import { hasPrivilege } from '@/utils/privilege'
|
import { hasPrivilege, hasPrivilegeWithTemp } from '@/utils/privilege'
|
||||||
|
|
||||||
const routes = [
|
const routes = [
|
||||||
{
|
{
|
||||||
@ -22,7 +22,7 @@ const routes = [
|
|||||||
path: 'demands',
|
path: 'demands',
|
||||||
name: 'DemandList',
|
name: 'DemandList',
|
||||||
component: () => import('@/views/index/DemandList.vue'),
|
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',
|
path: 'maps',
|
||||||
@ -88,13 +88,13 @@ const routes = [
|
|||||||
path: 'PIC2TGA',
|
path: 'PIC2TGA',
|
||||||
name: 'PIC2TGA',
|
name: 'PIC2TGA',
|
||||||
component: () => import('@/views/index/PIC2TGA.vue'),
|
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',
|
path: 'terrainGenerate',
|
||||||
name: 'TerrainGenerate',
|
name: 'TerrainGenerate',
|
||||||
component: () => import('@/views/index/TerrainGenerate.vue'),
|
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) {
|
if (requiredPrivilege) {
|
||||||
const user = getStoredUser();
|
const user = getStoredUser();
|
||||||
if (!user || !hasPrivilege(user.privilege, requiredPrivilege)) {
|
if (!user || !hasPrivilegeWithTemp(user, requiredPrivilege)) {
|
||||||
// 通过事件总线通知弹窗
|
// 通过事件总线通知弹窗
|
||||||
eventBus.emit('no-privilege')
|
eventBus.emit('no-privilege')
|
||||||
return next(false)
|
return next(false)
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import axiosInstance from '../api/axiosConfig';
|
import axiosInstance from '../api/axiosConfig';
|
||||||
import router from '../router'; // 引入 Vue Router 实例
|
import router from '../router'; // 引入 Vue Router 实例
|
||||||
import { justLoggedIn } from './authSessionState'; // Import the flag
|
import { justLoggedIn } from './authSessionState'; // Import the flag
|
||||||
|
import { getTempPrivilege } from '../api/login'; // 导入获取临时权限的函数
|
||||||
|
|
||||||
const USER_INFO_URL = '/user'; // 获取用户信息的API端点
|
const USER_INFO_URL = '/user'; // 获取用户信息的API端点
|
||||||
|
|
||||||
@ -42,8 +43,21 @@ export const getUserInfo = async () => {
|
|||||||
throw new Error('No valid token found');
|
throw new Error('No valid token found');
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
|
// 首先获取用户基本信息
|
||||||
const response = await axiosInstance.get(USER_INFO_URL);
|
const response = await axiosInstance.get(USER_INFO_URL);
|
||||||
const user = response.data;
|
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
|
||||||
sessionStorage.setItem('currentUser', JSON.stringify(user));
|
sessionStorage.setItem('currentUser', JSON.stringify(user));
|
||||||
return user;
|
return user;
|
||||||
|
@ -8,4 +8,34 @@ export function hasPrivilege(privilege, required) {
|
|||||||
return required.some(r => privArr.includes(r));
|
return required.some(r => privArr.includes(r));
|
||||||
}
|
}
|
||||||
return privArr.includes(required);
|
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 { computed, ref, onMounted, onUnmounted, defineAsyncComponent } from 'vue'
|
||||||
import { getUserInfo } from '@/utils/jwt'
|
import { getUserInfo } from '@/utils/jwt'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { hasPrivilege } from '@/utils/privilege'
|
import { hasPrivilege, hasPrivilegeWithTemp, mergePrivileges } from '@/utils/privilege'
|
||||||
import { eventBus } from '@/router/index.js'
|
import { eventBus } from '@/router/index.js'
|
||||||
import { requestTempPrivilege } from '@/api/login.js'
|
import { requestTempPrivilege } from '@/api/login.js'
|
||||||
const PrivilegeRequestDialog = defineAsyncComponent(() => import('@/components/PrivilegeRequestDialog.vue'))
|
const PrivilegeRequestDialog = defineAsyncComponent(() => import('@/components/PrivilegeRequestDialog.vue'))
|
||||||
|
const SuccessDialog = defineAsyncComponent(() => import('@/components/SuccessDialog.vue'))
|
||||||
|
|
||||||
const isLoggedIn = computed(() => {
|
const isLoggedIn = computed(() => {
|
||||||
return !!localStorage.getItem('access_token') && !!currentUserData.value
|
return !!localStorage.getItem('access_token') && !!currentUserData.value
|
||||||
@ -28,13 +29,16 @@ const userAvatarUrl = computed(() => {
|
|||||||
return null
|
return null
|
||||||
})
|
})
|
||||||
|
|
||||||
// 获取用户最高权限
|
// 获取用户最高权限(包括临时权限)
|
||||||
const userHighestPrivilege = computed(() => {
|
const userHighestPrivilege = computed(() => {
|
||||||
if (!currentUserData.value || !currentUserData.value.privilege) {
|
if (!currentUserData.value) {
|
||||||
return null
|
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']
|
const privilegeLevels = ['lv-admin', 'lv-mod', 'lv-competitor', 'lv-map', 'lv-user']
|
||||||
@ -61,7 +65,14 @@ const privilegeDisplayName = computed(() => {
|
|||||||
'lv-competitor': '竞技'
|
'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
|
showDropdown.value = !showDropdown.value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let privilegeCheckTimer = null
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
if (localStorage.getItem('access_token')) {
|
if (localStorage.getItem('access_token')) {
|
||||||
getUserInfo().then(userInfo => {
|
getUserInfo().then(userInfo => {
|
||||||
@ -114,20 +127,49 @@ onMounted(() => {
|
|||||||
|
|
||||||
// 添加点击外部关闭下拉菜单的监听器
|
// 添加点击外部关闭下拉菜单的监听器
|
||||||
document.addEventListener('click', handleClickOutside)
|
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(() => {
|
onUnmounted(() => {
|
||||||
// 移除事件监听器
|
// 移除事件监听器
|
||||||
document.removeEventListener('click', handleClickOutside)
|
document.removeEventListener('click', handleClickOutside)
|
||||||
|
|
||||||
|
// 清除定时器
|
||||||
|
if (privilegeCheckTimer) {
|
||||||
|
clearInterval(privilegeCheckTimer)
|
||||||
|
privilegeCheckTimer = null
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// 判断每个一级菜单是否有可见项
|
// 判断每个一级菜单是否有可见项
|
||||||
const showTerrainList = true // 地形图列表始终可见
|
const showTerrainList = true // 地形图列表始终可见
|
||||||
const showTerrainGenerate = computed(() => isLoggedIn.value && currentUserData.value)
|
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 showPic2Tga = computed(() => isLoggedIn.value && currentUserData.value)
|
||||||
const showDemands = 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 showTerrainMenu = computed(() => showTerrainList || showTerrainGenerate.value)
|
||||||
const showOnlineToolsMenu = computed(() => showWeaponMatch.value || showPic2Tga.value)
|
const showOnlineToolsMenu = computed(() => showWeaponMatch.value || showPic2Tga.value)
|
||||||
@ -170,9 +212,9 @@ async function handlePrivilegeApply() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function handleNavClick(route, privilegeList) {
|
function handleNavClick(route, privilegeList) {
|
||||||
if (!isLoggedIn.value || !currentUserData.value || !hasPrivilege(currentUserData.value.privilege, privilegeList)) {
|
if (!isLoggedIn.value || !currentUserData.value || !hasPrivilegeWithTemp(currentUserData.value, privilegeList)) {
|
||||||
// 取权限数组中优先级最高的非lv-admin权限(如有)
|
// 取权限数组中优先级最高的非lv-admin权限(如有),排除lv-user
|
||||||
const order = ['lv-mod', 'lv-competitor', 'lv-map', 'lv-user']
|
const order = ['lv-mod', 'lv-competitor', 'lv-map']
|
||||||
let privilegeKey = ''
|
let privilegeKey = ''
|
||||||
for (const key of order) {
|
for (const key of order) {
|
||||||
if (privilegeList && privilegeList.includes(key)) {
|
if (privilegeList && privilegeList.includes(key)) {
|
||||||
@ -215,7 +257,7 @@ function handleNavClick(route, privilegeList) {
|
|||||||
<span class="nav-link">地形与纹理</span>
|
<span class="nav-link">地形与纹理</span>
|
||||||
<div class="dropdown-content">
|
<div class="dropdown-content">
|
||||||
<router-link to="/terrain" class="nav-link">地形图列表</router-link>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
<!-- 仅登录后显示的菜单项 -->
|
<!-- 仅登录后显示的菜单项 -->
|
||||||
@ -225,7 +267,7 @@ function handleNavClick(route, privilegeList) {
|
|||||||
<span class="nav-link">在线工具</span>
|
<span class="nav-link">在线工具</span>
|
||||||
<div class="dropdown-content">
|
<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="/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>
|
||||||
</div>
|
</div>
|
||||||
<!-- 赛事信息 一级菜单 -->
|
<!-- 赛事信息 一级菜单 -->
|
||||||
@ -239,7 +281,7 @@ function handleNavClick(route, privilegeList) {
|
|||||||
<div class="nav-dropdown">
|
<div class="nav-dropdown">
|
||||||
<span class="nav-link">公共信息区</span>
|
<span class="nav-link">公共信息区</span>
|
||||||
<div class="dropdown-content">
|
<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>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -292,6 +334,11 @@ function handleNavClick(route, privilegeList) {
|
|||||||
@close="privilegeDialogVisible = false"
|
@close="privilegeDialogVisible = false"
|
||||||
@apply="handlePrivilegeApply"
|
@apply="handlePrivilegeApply"
|
||||||
/>
|
/>
|
||||||
|
<SuccessDialog
|
||||||
|
:visible="successDialog.visible"
|
||||||
|
:message="successDialog.message"
|
||||||
|
@close="successDialog.visible = false"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user