[Feat] Independent Admin Management Page

This commit is contained in:
Harry-zklcdc 2025-07-06 16:10:03 +08:00
parent e6f81e19dc
commit 2a18684162
6 changed files with 248 additions and 32 deletions

View File

@ -0,0 +1,87 @@
<script setup>
import { useLayout } from '@/layout/composables/layout';
import { computed, onMounted, ref, watch } from 'vue';
import AppAdminSidebar from './AppAdminSidebar.vue';
import AppFooter from './AppFooter.vue';
import AppTopbar from './AppTopbar.vue';
import api from '@/api';
import { useProfileStore } from '@/stores/profile';
import { useToast } from 'primevue/usetoast';
import { useRouter } from 'vue-router';
const router = useRouter()
const profileStore = useProfileStore()
const toast = useToast()
const { layoutConfig, layoutState, isSidebarActive, resetMenu } = useLayout();
const outsideClickListener = ref(null);
watch(isSidebarActive, (newVal) => {
if (newVal) {
bindOutsideClickListener();
} else {
unbindOutsideClickListener();
}
});
const containerClass = computed(() => {
return {
'layout-overlay': layoutConfig.menuMode === 'overlay',
'layout-static': layoutConfig.menuMode === 'static',
'layout-static-inactive': layoutState.staticMenuDesktopInactive && layoutConfig.menuMode === 'static',
'layout-overlay-active': layoutState.overlayMenuActive,
'layout-mobile-active': layoutState.staticMenuMobileActive
};
});
function bindOutsideClickListener() {
if (!outsideClickListener.value) {
outsideClickListener.value = (event) => {
if (isOutsideClicked(event)) {
resetMenu();
}
};
document.addEventListener('click', outsideClickListener.value);
}
}
function unbindOutsideClickListener() {
if (outsideClickListener.value) {
document.removeEventListener('click', outsideClickListener);
outsideClickListener.value = null;
}
}
function isOutsideClicked(event) {
const sidebarEl = document.querySelector('.layout-sidebar');
const topbarEl = document.querySelector('.layout-menu-button');
return !(sidebarEl.isSameNode(event.target) || sidebarEl.contains(event.target) || topbarEl.isSameNode(event.target) || topbarEl.contains(event.target));
}
onMounted(() => {
api.GetUserProfile().then(res => {
profileStore.setUserProfile(res.data.data.result)
}).catch(_ => {
toast.add({ severity: 'error', summary: '登录过期,请重新登录', life: 3000 })
profileStore.clearUserProfile()
router.push('/login')
})
})
</script>
<template>
<div class="layout-wrapper" :class="containerClass">
<app-topbar></app-topbar>
<app-admin-sidebar></app-admin-sidebar>
<div class="layout-main-container">
<div class="layout-main">
<router-view></router-view>
</div>
<app-footer></app-footer>
</div>
<div class="layout-mask animate-fadein"></div>
</div>
</template>

View File

@ -0,0 +1,88 @@
<script setup>
import { useProfileStore } from '@/stores/profile';
import { onMounted, ref } from 'vue';
import AppMenuItem from './AppMenuItem.vue';
const profileStore = useProfileStore();
const model = ref([]);
const defaultModel = ref({
label: '用户中心',
items: [
{
label: '实例管理',
icon: 'pi pi-fw pi-desktop text-lime-500',
to: '/'
},
]
})
const adminModel = ref({
label: '系统设置',
items: [
{
label: '实例管理',
icon: 'pi pi-fw pi-desktop text-lime-500',
to: '/admin/instances'
},
{
label: '用户管理',
icon: 'pi pi-fw pi-users text-indigo-500',
to: '/admin/users'
},
]
})
const superAdminModel = ref({
label: '系统设置',
items: [
{
label: '节点管理',
icon: 'pi pi-fw pi-server text-yellow-400',
to: '/admin/servers'
},
{
label: '实例管理',
icon: 'pi pi-fw pi-desktop text-lime-500',
to: '/admin/instances'
},
{
label: '用户管理',
icon: 'pi pi-fw pi-users text-indigo-500',
to: '/admin/users'
},
{
label: '镜像管理',
icon: 'pi pi-fw pi-images text-teal-500',
to: '/admin/images'
},
]
})
onMounted(() => {
const superAdmin = profileStore.isSuperAdmin
const admin = profileStore.isAdmin
if (superAdmin) {
model.value.push(superAdminModel.value)
} else if (admin) {
model.value.push(adminModel.value)
}
model.value.push(defaultModel.value);
});
</script>
<template>
<ul class="layout-menu">
<template v-for="(item, i) in model" :key="item">
<app-menu-item v-if="!item.separator" :item="item" :index="i"></app-menu-item>
<li v-if="item.separator" class="menu-separator"></li>
</template>
</ul>
</template>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,11 @@
<script setup>
import AppAdminMenu from './AppAdminMenu.vue';
</script>
<template>
<div class="layout-sidebar">
<app-admin-menu></app-admin-menu>
</div>
</template>
<style lang="scss" scoped></style>

View File

@ -67,15 +67,10 @@ const adminModel = ref({
label: '系统设置', label: '系统设置',
items: [ items: [
{ {
label: '实例管理', label: '管理后台',
icon: 'pi pi-fw pi-desktop text-lime-500', icon: 'pi pi-fw pi-sliders-h text-yellow-500',
to: '/admin/instances' to: '/admin/instances'
}, },
{
label: '用户管理',
icon: 'pi pi-fw pi-users text-indigo-500',
to: '/admin/users'
},
] ]
}) })
@ -83,25 +78,10 @@ const superAdminModel = ref({
label: '系统设置', label: '系统设置',
items: [ items: [
{ {
label: '节点管理', label: '管理后台',
icon: 'pi pi-fw pi-server text-yellow-400', icon: 'pi pi-fw pi-sliders-h text-yellow-400',
to: '/admin/servers' to: '/admin/servers'
}, },
{
label: '实例管理',
icon: 'pi pi-fw pi-desktop text-lime-500',
to: '/admin/instances'
},
{
label: '镜像管理',
icon: 'pi pi-fw pi-images text-teal-500',
to: '/admin/images'
},
{
label: '用户管理',
icon: 'pi pi-fw pi-users text-indigo-500',
to: '/admin/users'
},
] ]
}) })

View File

@ -39,7 +39,7 @@
</div> </div>
</div> </div>
<Menu ref="profileMenu" :model="instanceMenuItems" :popup="true" @blur="profileMenuActive = false" /> <Menu ref="profileMenu" :model="profileMenuItems" :popup="true" @blur="profileMenuActive = false" />
</template> </template>
@ -61,7 +61,7 @@ const username = ref('');
const profileMenu = ref(null); const profileMenu = ref(null);
const profileMenuActive = ref(false); const profileMenuActive = ref(false);
const instanceMenuItems = [ const profileMenuItems = ref([
{ {
label: '个人信息', label: '个人信息',
icon: 'pi pi-user !text-cyan-500', icon: 'pi pi-user !text-cyan-500',
@ -78,6 +78,12 @@ const instanceMenuItems = [
router.push('/settings'); router.push('/settings');
} }
}, },
{
separator: true
},
]);
const profileExitItems = ref([
{ {
label: '退出登录', label: '退出登录',
icon: 'pi pi-sign-out', icon: 'pi pi-sign-out',
@ -86,7 +92,35 @@ const instanceMenuItems = [
logout(); logout();
} }
} }
]; ]);
const profileAdminItems = ref([
{
label: '管理后台',
icon: 'pi pi-sliders-h !text-yellow-400',
command: () => {
profileMenu.value.hide();
router.push('/admin/instances');
}
},
{
separator: true
},
])
const profileSuperadminItems = ref([
{
label: '管理后台',
icon: 'pi pi-sliders-h !text-yellow-400',
command: () => {
profileMenu.value.hide();
router.push('/admin/servers');
}
},
{
separator: true
},
])
const showMenu = (event) => { const showMenu = (event) => {
profileMenu.value.show(event); profileMenu.value.show(event);
@ -105,6 +139,14 @@ const logout = () => {
onMounted(() => { onMounted(() => {
username.value = profileStore.username; username.value = profileStore.username;
if (profileStore.isSuperAdmin) {
profileMenuItems.value.push(...profileSuperadminItems.value);
} else if (profileStore.isAdmin) {
profileMenuItems.value.push(...profileAdminItems.value);
}
profileMenuItems.value.push(...profileExitItems.value);
}); });
</script> </script>

View File

@ -17,6 +17,7 @@ import Instances from '@/views/admin/Instances.vue';
import Servers from '@/views/admin/Servers.vue'; import Servers from '@/views/admin/Servers.vue';
import Users from '@/views/admin/Users.vue'; import Users from '@/views/admin/Users.vue';
import AppAdminLayout from '@/layout/AppAdminLayout.vue';
import NotFound from '@/views/NotFound.vue'; import NotFound from '@/views/NotFound.vue';
const router = createRouter({ const router = createRouter({
@ -70,24 +71,31 @@ const router = createRouter({
path: 'settings', path: 'settings',
name: 'settings', name: 'settings',
component: Settings component: Settings
}, }
]
},
{
path: '/admin/',
name: 'admin-dashboard',
component: AppAdminLayout,
children: [
{ {
path: 'admin/images', path: 'images',
name: 'admin-images', name: 'admin-images',
component: Images component: Images
}, },
{ {
path: 'admin/instances', path: 'instances',
name: 'admin-instances', name: 'admin-instances',
component: Instances component: Instances
}, },
{ {
path: 'admin/servers', path: 'servers',
name: 'admin-servers', name: 'admin-servers',
component: Servers component: Servers
}, },
{ {
path: 'admin/users', path: 'users',
name: 'admin-users', name: 'admin-users',
component: Users component: Users
} }