管理员为用户添加临时权限,但是,如加,添加临时之后权限没变化
This commit is contained in:
parent
3fa236ca11
commit
ddfc8e98c6
@ -81,3 +81,38 @@ export const adminChangeUserPrivilege = async (uuid, privilege) => {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户申请临时权限
|
||||
* 需要登录
|
||||
* @param {string} privilege - 申请的权限
|
||||
* @returns {Promise<void>} 无返回值,成功即为修改成功
|
||||
*/
|
||||
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<void>} 无返回值,成功即为修改成功
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
151
src/components/PrivilegeRequestDialog.vue
Normal file
151
src/components/PrivilegeRequestDialog.vue
Normal file
@ -0,0 +1,151 @@
|
||||
<template>
|
||||
<div v-if="visible" class="privilege-dialog-overlay" @click.self="handleClose">
|
||||
<div class="privilege-dialog">
|
||||
<div class="privilege-dialog-header">
|
||||
<h3>权限申请</h3>
|
||||
</div>
|
||||
<div class="privilege-dialog-content">
|
||||
<div class="privilege-type privilege-type-highlight">
|
||||
申请权限:<span class="privilege-name" :class="privilegeColorClass[privilegeName]">{{ privilegeName }}</span>
|
||||
</div>
|
||||
<p>如需访问该功能,请点击下方按钮提交申请。</p>
|
||||
</div>
|
||||
<div class="privilege-dialog-footer">
|
||||
<button class="cancel-button" @click="handleClose">取消</button>
|
||||
<button class="apply-button" @click="handleApply">提交申请</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
visible: { type: Boolean, default: false },
|
||||
privilegeName: { type: String, default: '' }
|
||||
})
|
||||
const emit = defineEmits(['close', 'apply'])
|
||||
|
||||
function handleClose() {
|
||||
emit('close')
|
||||
}
|
||||
function handleApply() {
|
||||
emit('apply')
|
||||
}
|
||||
|
||||
// 动态class
|
||||
const privilegeColorClass = {
|
||||
'管理员': 'privilege-admin',
|
||||
'模组': 'privilege-mod',
|
||||
'竞技': 'privilege-competitor',
|
||||
'地图': 'privilege-map',
|
||||
'用户': 'privilege-user'
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.privilege-dialog-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 1100;
|
||||
}
|
||||
.privilege-dialog {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
width: 90%;
|
||||
max-width: 420px;
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.15);
|
||||
animation: dialog-fade-in 0.3s ease;
|
||||
}
|
||||
.privilege-dialog-header {
|
||||
padding: 16px 20px 0 20px;
|
||||
text-align: center;
|
||||
}
|
||||
.privilege-dialog-header h3 {
|
||||
margin: 0;
|
||||
color: #416bdf;
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.privilege-dialog-content {
|
||||
padding: 18px 20px 0 20px;
|
||||
color: #606266;
|
||||
font-size: 15px;
|
||||
line-height: 1.5;
|
||||
text-align: left;
|
||||
}
|
||||
.privilege-type {
|
||||
margin-bottom: 10px;
|
||||
font-size: 15px;
|
||||
color: #333;
|
||||
}
|
||||
.privilege-name {
|
||||
color: #416bdf;
|
||||
font-weight: 600;
|
||||
font-size: 16px;
|
||||
margin-left: 4px;
|
||||
}
|
||||
.privilege-dialog-footer {
|
||||
padding: 18px 20px 20px 20px;
|
||||
text-align: right;
|
||||
}
|
||||
.cancel-button, .apply-button {
|
||||
background: #f5f5f5;
|
||||
color: #333;
|
||||
border: none;
|
||||
padding: 7px 20px;
|
||||
border-radius: 4px;
|
||||
font-size: 15px;
|
||||
cursor: pointer;
|
||||
margin-left: 8px;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
.apply-button {
|
||||
background: #416bdf;
|
||||
color: #fff;
|
||||
}
|
||||
.cancel-button:hover {
|
||||
background: #e0e0e0;
|
||||
}
|
||||
.apply-button:hover {
|
||||
background: #274bb5;
|
||||
}
|
||||
@keyframes dialog-fade-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(-20px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
.privilege-type-highlight {
|
||||
background: #eaf3ff;
|
||||
border-radius: 6px;
|
||||
padding: 6px 12px;
|
||||
margin-bottom: 14px;
|
||||
display: inline-block;
|
||||
}
|
||||
.privilege-name.privilege-admin {
|
||||
color: #ff7675;
|
||||
}
|
||||
.privilege-name.privilege-mod {
|
||||
color: #6c5ce7;
|
||||
}
|
||||
.privilege-name.privilege-competitor {
|
||||
color: #00b894;
|
||||
}
|
||||
.privilege-name.privilege-map {
|
||||
color: #0984e3;
|
||||
}
|
||||
.privilege-name.privilege-user {
|
||||
color: #636e72;
|
||||
}
|
||||
</style>
|
142
src/components/backend/TempPrivilegeReview.vue
Normal file
142
src/components/backend/TempPrivilegeReview.vue
Normal file
@ -0,0 +1,142 @@
|
||||
<template>
|
||||
<div class="temp-privilege-form">
|
||||
<h2>添加临时权限</h2>
|
||||
<form @submit.prevent="handleSubmit">
|
||||
<div class="form-group">
|
||||
<label for="uuid">用户UUID:</label>
|
||||
<input id="uuid" v-model="uuid" type="text" placeholder="请输入用户UUID" required />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="privilege">权限:</label>
|
||||
<select id="privilege" v-model="privilege" required>
|
||||
<option value="" disabled>请选择权限</option>
|
||||
<option v-for="(label, key) in privilegeDisplayName" :key="key" :value="key">{{ label }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="exp_time">过期时间:</label>
|
||||
<select id="exp_time" v-model="exp_timeOption">
|
||||
<option value="">30分钟(默认)</option>
|
||||
<option value="60">1小时</option>
|
||||
<option value="180">3小时</option>
|
||||
<option value="1440">1天</option>
|
||||
<option value="other">其他</option>
|
||||
</select>
|
||||
<input v-if="exp_timeOption === 'other'" v-model="exp_timeCustom" type="number" min="1" placeholder="请输入分钟数" class="custom-exp-input" />
|
||||
</div>
|
||||
<button class="submit-btn" type="submit" :disabled="loading">提交</button>
|
||||
</form>
|
||||
<div v-if="msg" :class="{'success-msg': msgType==='success', 'error-msg': msgType==='error'}">{{ msg }}</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { adminAddTempPrivilege } from '@/api/login.js'
|
||||
const uuid = ref('')
|
||||
const privilege = ref('lv-map')
|
||||
const exp_timeOption = ref('')
|
||||
const exp_timeCustom = ref('')
|
||||
const loading = ref(false)
|
||||
const msg = ref('')
|
||||
const msgType = ref('')
|
||||
const privilegeDisplayName = {
|
||||
'lv-mod': '模组',
|
||||
'lv-map': '地图',
|
||||
'lv-competitor': '竞技'
|
||||
}
|
||||
|
||||
function getExpTime() {
|
||||
if (exp_timeOption.value === 'other') {
|
||||
return exp_timeCustom.value ? Number(exp_timeCustom.value) : ''
|
||||
}
|
||||
return exp_timeOption.value ? Number(exp_timeOption.value) : ''
|
||||
}
|
||||
|
||||
async function handleSubmit() {
|
||||
msg.value = ''
|
||||
msgType.value = ''
|
||||
loading.value = true
|
||||
try {
|
||||
await adminAddTempPrivilege(uuid.value, privilege.value, getExpTime())
|
||||
msg.value = '添加成功!'
|
||||
msgType.value = 'success'
|
||||
uuid.value = ''
|
||||
privilege.value = ''
|
||||
exp_timeOption.value = ''
|
||||
exp_timeCustom.value = ''
|
||||
} catch (e) {
|
||||
msg.value = '添加失败,请检查输入或重试。'
|
||||
msgType.value = 'error'
|
||||
}
|
||||
loading.value = false
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.temp-privilege-form {
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
padding: 32px 24px 24px 24px;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.06);
|
||||
max-width: 420px;
|
||||
margin: 40px auto 0 auto;
|
||||
}
|
||||
.temp-privilege-form h2 {
|
||||
margin-bottom: 24px;
|
||||
color: #2563eb;
|
||||
text-align: center;
|
||||
}
|
||||
.form-group {
|
||||
margin-bottom: 18px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.form-group label {
|
||||
margin-bottom: 6px;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
}
|
||||
.form-group input,
|
||||
.form-group select {
|
||||
padding: 8px 10px;
|
||||
border: 1px solid #d1d5db;
|
||||
border-radius: 4px;
|
||||
font-size: 15px;
|
||||
outline: none;
|
||||
transition: border 0.2s;
|
||||
}
|
||||
.form-group input:focus,
|
||||
.form-group select:focus {
|
||||
border-color: #2563eb;
|
||||
}
|
||||
.custom-exp-input {
|
||||
margin-top: 8px;
|
||||
}
|
||||
.submit-btn {
|
||||
width: 100%;
|
||||
background: #2563eb;
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
padding: 10px 0;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
.submit-btn:disabled {
|
||||
background: #a5b4fc;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
.success-msg {
|
||||
color: #22c55e;
|
||||
text-align: center;
|
||||
margin-top: 18px;
|
||||
}
|
||||
.error-msg {
|
||||
color: #ef4444;
|
||||
text-align: center;
|
||||
margin-top: 18px;
|
||||
}
|
||||
</style>
|
@ -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
|
@ -19,7 +19,7 @@
|
||||
</div>
|
||||
<ul v-show="dropdownOpen" style="list-style:none;padding-left:10px;">
|
||||
<li :class="{active: currentAdminView === 'permission-review'}">
|
||||
<a @click="selectAdminView('permission-review')">权限审核</a>
|
||||
<a @click="selectAdminView('permission-review')">临时权限申请</a>
|
||||
</li>
|
||||
<li :class="{active: currentAdminView === 'user-management'}">
|
||||
<a @click="selectAdminView('user-management')">用户管理</a>
|
||||
@ -70,6 +70,7 @@
|
||||
<div class="admin-main-content">
|
||||
<AdminEditUserPrivilege v-if="currentAdminView === 'admin-edit-user-privilege'" />
|
||||
<AffairManagement v-if="currentAdminView === 'affair-management'" />
|
||||
<TempPrivilegeReview v-if="currentAdminView === 'permission-review'" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -81,6 +82,7 @@ import { useRouter } from 'vue-router'
|
||||
import { getUserInfo } from '@/utils/jwt'
|
||||
import AdminEditUserPrivilege from '@/components/backend/AdminEditUserPrivilege.vue'
|
||||
import AffairManagement from '@/components/backend/AffairManagement.vue'
|
||||
import TempPrivilegeReview from '@/components/backend/TempPrivilegeReview.vue'
|
||||
|
||||
const router = useRouter()
|
||||
const hasToken = ref(false)
|
||||
|
@ -1,8 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, onMounted, onUnmounted } from 'vue'
|
||||
import { computed, ref, onMounted, onUnmounted, defineAsyncComponent } from 'vue'
|
||||
import { getUserInfo } from '@/utils/jwt'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { hasPrivilege } from '@/utils/privilege'
|
||||
import { eventBus } from '@/router/index.js'
|
||||
import { requestTempPrivilege } from '@/api/login.js'
|
||||
const PrivilegeRequestDialog = defineAsyncComponent(() => import('@/components/PrivilegeRequestDialog.vue'))
|
||||
|
||||
const isLoggedIn = computed(() => {
|
||||
return !!localStorage.getItem('access_token') && !!currentUserData.value
|
||||
@ -130,6 +133,63 @@ const showTerrainMenu = computed(() => showTerrainList || showTerrainGenerate.va
|
||||
const showOnlineToolsMenu = computed(() => showWeaponMatch.value || showPic2Tga.value)
|
||||
const showPublicMenu = computed(() => showDemands.value)
|
||||
const showCompetitionMenu = computed(() => showCompetition.value)
|
||||
|
||||
const errorDialogVisible = ref(false)
|
||||
const errorDialogMessage = ref('')
|
||||
const privilegeDialogVisible = ref(false)
|
||||
const privilegeDialogName = ref('')
|
||||
const privilegeDialogKey = ref('')
|
||||
const successDialog = ref({ visible: false, message: '' })
|
||||
|
||||
const privilegeDisplayNames = {
|
||||
'lv-admin': '管理员',
|
||||
'lv-mod': '模组',
|
||||
'lv-map': '地图',
|
||||
'lv-user': '用户',
|
||||
'lv-competitor': '竞技'
|
||||
}
|
||||
|
||||
function showNoPrivilegeDialog(name = '', key = '') {
|
||||
privilegeDialogName.value = name
|
||||
privilegeDialogKey.value = key
|
||||
privilegeDialogVisible.value = true
|
||||
}
|
||||
|
||||
async function handlePrivilegeApply() {
|
||||
privilegeDialogVisible.value = false
|
||||
if (!isLoggedIn.value) {
|
||||
successDialog.value = { visible: true, message: '请先登录后再申请权限!' }
|
||||
return
|
||||
}
|
||||
try {
|
||||
await requestTempPrivilege(privilegeDialogKey.value)
|
||||
successDialog.value = { visible: true, message: '权限申请已提交,请等待审核。' }
|
||||
} catch (e) {
|
||||
successDialog.value = { visible: true, message: '权限申请失败,请重试或联系管理员。' }
|
||||
}
|
||||
}
|
||||
|
||||
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']
|
||||
let privilegeKey = ''
|
||||
for (const key of order) {
|
||||
if (privilegeList && privilegeList.includes(key)) {
|
||||
privilegeKey = key
|
||||
break
|
||||
}
|
||||
}
|
||||
// 如果没有其他权限,才显示lv-admin
|
||||
if (!privilegeKey && privilegeList && privilegeList.includes('lv-admin')) {
|
||||
privilegeKey = 'lv-admin'
|
||||
}
|
||||
const privilegeName = privilegeDisplayNames[privilegeKey] || privilegeKey
|
||||
showNoPrivilegeDialog(privilegeName, privilegeKey)
|
||||
return
|
||||
}
|
||||
router.push(route)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@ -151,35 +211,38 @@ const showCompetitionMenu = computed(() => showCompetition.value)
|
||||
</div>
|
||||
</div>
|
||||
<!-- 地形 一级菜单 -->
|
||||
<div v-if="showTerrainMenu" class="nav-dropdown">
|
||||
<div class="nav-dropdown">
|
||||
<span class="nav-link">地形与纹理</span>
|
||||
<div class="dropdown-content">
|
||||
<router-link to="/terrain" class="nav-link">地形图列表</router-link>
|
||||
<router-link v-if="showTerrainGenerate" to="/terrainGenerate" 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>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 在线工具 一级菜单 -->
|
||||
<div v-if="showOnlineToolsMenu" class="nav-dropdown">
|
||||
<span class="nav-link">在线工具</span>
|
||||
<div class="dropdown-content">
|
||||
<router-link v-if="showWeaponMatch" to="/weapon-match" class="nav-link">Weapon 匹配</router-link>
|
||||
<router-link v-if="showPic2Tga" to="/PIC2TGA" class="nav-link">在线转tga工具</router-link>
|
||||
<!-- 仅登录后显示的菜单项 -->
|
||||
<template v-if="isLoggedIn">
|
||||
<!-- 在线工具 一级菜单 -->
|
||||
<div class="nav-dropdown">
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 赛事信息 一级菜单 -->
|
||||
<div v-if="showCompetitionMenu" class="nav-dropdown">
|
||||
<span class="nav-link">赛事信息</span>
|
||||
<div class="dropdown-content">
|
||||
<router-link v-if="showCompetition" to="/competition" class="nav-link">赛程信息</router-link>
|
||||
<!-- 赛事信息 一级菜单 -->
|
||||
<div class="nav-dropdown">
|
||||
<span class="nav-link">赛事信息</span>
|
||||
<div class="dropdown-content">
|
||||
<router-link to="/competition" class="nav-link" @click.prevent="handleNavClick('/competition', ['lv-admin','lv-competitor'])">赛程信息</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 公共信息区 一级菜单 -->
|
||||
<div v-if="showPublicMenu" class="nav-dropdown">
|
||||
<span class="nav-link">公共信息区</span>
|
||||
<div class="dropdown-content">
|
||||
<router-link v-if="showDemands" to="/demands" class="nav-link">办事大厅</router-link>
|
||||
<!-- 公共信息区 一级菜单 -->
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class="nav-right" :class="{ active: showMobileMenu }">
|
||||
<router-link v-if="!isLoggedIn" to="/backend/login" class="nav-link login-btn">
|
||||
@ -223,6 +286,12 @@ const showCompetitionMenu = computed(() => showCompetition.value)
|
||||
</p>
|
||||
</div>
|
||||
</footer>
|
||||
<PrivilegeRequestDialog
|
||||
:visible="privilegeDialogVisible"
|
||||
:privilegeName="privilegeDialogName"
|
||||
@close="privilegeDialogVisible = false"
|
||||
@apply="handlePrivilegeApply"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user