diff --git a/src/api/login.js b/src/api/login.js index 09483c0..1cc6f05 100644 --- a/src/api/login.js +++ b/src/api/login.js @@ -81,3 +81,38 @@ export const adminChangeUserPrivilege = async (uuid, privilege) => { } } +/** + * 用户申请临时权限 + * 需要登录 + * @param {string} privilege - 申请的权限 + * @returns {Promise} 无返回值,成功即为修改成功 + */ +export const requestTempPrivilege = async (privilege) => { + try { + console.log('申请的权限【requestTempPrivilege】privilege:', privilege); + + await axiosInstance.post('/user/temp_privilege_request', { privilege }); + } catch (error) { + throw error; + } +} + +/** + * 管理员为用户添加临时权限 + * 需要admin权限 + * @param {string} uuid - 用户uuid + * @param {string} privilege - 权限 + * @param {number} exp_time - 过期时间(分钟) + * @returns {Promise} 无返回值,成功即为修改成功 + */ +export const adminAddTempPrivilege = async (uuid, privilege, exp_time) => { + try { + const payload = { uuid, privilege }; + if (exp_time !== undefined && exp_time !== null && exp_time !== '') { + payload.exp_time = exp_time; + } + await axiosInstance.post('/admin/add_temp_privilege', payload); + } catch (error) { + throw error; + } +} diff --git a/src/components/PrivilegeRequestDialog.vue b/src/components/PrivilegeRequestDialog.vue new file mode 100644 index 0000000..0ccb816 --- /dev/null +++ b/src/components/PrivilegeRequestDialog.vue @@ -0,0 +1,151 @@ + + + + + \ No newline at end of file diff --git a/src/components/backend/TempPrivilegeReview.vue b/src/components/backend/TempPrivilegeReview.vue new file mode 100644 index 0000000..62b2c8c --- /dev/null +++ b/src/components/backend/TempPrivilegeReview.vue @@ -0,0 +1,142 @@ + + + + + \ No newline at end of file diff --git a/src/router/index.js b/src/router/index.js index bb0ba29..d3fa8a3 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -2,6 +2,8 @@ import { createRouter, createWebHistory } from 'vue-router' import { hasValidToken, getUserInfo, logoutUser, getStoredUser } from '../utils/jwt'; import { justLoggedIn } from '../utils/authSessionState'; import EditorsMaps from '@/views/index/EditorsMaps.vue' +import mitt from 'mitt' +import { hasPrivilege } from '@/utils/privilege' const routes = [ { @@ -20,7 +22,7 @@ const routes = [ path: 'demands', name: 'DemandList', component: () => import('@/views/index/DemandList.vue'), - meta: { requiresAuth: true } + meta: { requiresAuth: true, requiredPrivilege: ['lv-admin','lv-mod','lv-map','lv-user','lv-competitor'] } }, { path: 'maps', @@ -46,13 +48,13 @@ const routes = [ path: 'weapon-match', name: 'WeaponMatch', component: () => import('@/views/index/WeaponMatch.vue'), - meta: { requiresAuth: true } + meta: { requiresAuth: true, requiredPrivilege: ['lv-admin','lv-mod'] } }, { path: 'competition', name: 'Competition', component: () => import('@/views/index/Competition.vue'), - meta: { requiresAuth: true } + meta: { requiresAuth: true, requiredPrivilege: ['lv-admin','lv-competitor'] } }, { path: 'competition/add', @@ -85,12 +87,14 @@ const routes = [ { path: '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'] } }, { path: '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'] } } ] }, @@ -124,17 +128,20 @@ const router = createRouter({ linkExactActiveClass: 'router-link-exact-active' }) +export const eventBus = mitt() + // 路由守卫 router.beforeEach(async (to, from, next) => { const requiresAuth = to.matched.some(record => record.meta.requiresAuth); + const requiredPrivilege = to.matched.find(record => record.meta.requiredPrivilege)?.meta.requiredPrivilege; - if (!requiresAuth) { - return next(); // 如果页面不需要认证,直接放行 + if (!requiresAuth && !requiredPrivilege) { + return next(); // 如果页面不需要认证和权限,直接放行 } // 方案1: 检查SessionStorage中是否已有用户信息 (刷新后最先检查这里) if (getStoredUser()) { - return next(); + // 继续检查权限 } // 方案2: 如果刚登录过,直接放行 @@ -143,14 +150,13 @@ router.beforeEach(async (to, from, next) => { // 此时token肯定有效,但可能还没来得及请求用户信息 // 为确保用户信息被存储,可以调用一次,但不阻塞导航 getUserInfo().catch(err => console.error("Error fetching user info after login:", err)); - return next(); } // 方案3: 检查localStorage中是否有token (刷新后SessionStorage没有用户信息时) if (hasValidToken()) { try { await getUserInfo(); // 尝试获取用户信息,该函数会把用户信息存入SessionStorage - return next(); // 获取成功,放行 + // 继续检查权限 } catch (error) { // 获取失败 (token无效, 网络问题等) logoutUser(); // 清除所有凭证 @@ -161,11 +167,17 @@ router.beforeEach(async (to, from, next) => { } } - // 方案4: 如果以上条件都不满足,跳转登录页 - return next({ - path: '/backend/login', - query: { redirect: to.fullPath } - }); + // 权限校验 + if (requiredPrivilege) { + const user = getStoredUser(); + if (!user || !hasPrivilege(user.privilege, requiredPrivilege)) { + // 通过事件总线通知弹窗 + eventBus.emit('no-privilege') + return next(false) + } + } + + return next(); }); export default router \ No newline at end of file diff --git a/src/views/backend/Dashboard.vue b/src/views/backend/Dashboard.vue index bb4083a..f78c9f9 100644 --- a/src/views/backend/Dashboard.vue +++ b/src/views/backend/Dashboard.vue @@ -19,7 +19,7 @@