first commit
This commit is contained in:
124
resources/admin/layout/components/Menu/index.vue
Normal file
124
resources/admin/layout/components/Menu/index.vue
Normal file
@@ -0,0 +1,124 @@
|
||||
<script lang="ts">
|
||||
import { h, defineComponent, VNode } from 'vue'
|
||||
import { usePermissionsStore } from '/admin/stores/modules/user/permissions'
|
||||
import MenuItem from './item.vue'
|
||||
import menus from './menus.vue'
|
||||
import { useUserStore } from '/admin/stores/modules/user'
|
||||
import { Menu } from '/admin/types/Menu'
|
||||
|
||||
/**
|
||||
* 递归渲染 Menu 节点
|
||||
*/
|
||||
function getVNodes(menus: Menu[] | undefined, _subMenuClass: string | undefined): VNode[] {
|
||||
const vnodes: VNode[] = []
|
||||
menus?.forEach(menu => {
|
||||
if (!menu.meta?.hidden) {
|
||||
let vnode: VNode
|
||||
const len = menu.children?.length
|
||||
if (len) {
|
||||
vnode = h(
|
||||
MenuItem,
|
||||
{
|
||||
subMenuClass: _subMenuClass,
|
||||
menu,
|
||||
},
|
||||
{
|
||||
default: () => getVNodes(menu.children, 'children-menu'),
|
||||
},
|
||||
)
|
||||
} else {
|
||||
vnode = h(MenuItem, {
|
||||
subMenuClass: _subMenuClass,
|
||||
menu,
|
||||
})
|
||||
}
|
||||
vnodes.push(vnode)
|
||||
}
|
||||
})
|
||||
|
||||
return vnodes
|
||||
}
|
||||
|
||||
/**
|
||||
* filter menus
|
||||
*
|
||||
* @param menus
|
||||
*/
|
||||
function filterMenus(menus: Menu[] | undefined): Menu[] {
|
||||
const newMenus: Menu[] = []
|
||||
|
||||
menus?.forEach(m => {
|
||||
if (m.meta?.hidden) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (isHasOnlyChild(m) && m.children?.length) {
|
||||
newMenus.push(
|
||||
Object.assign({
|
||||
path: m.children[0].path,
|
||||
meta: m.children[0].meta,
|
||||
name: m.name,
|
||||
}),
|
||||
)
|
||||
} else {
|
||||
newMenus.push(m)
|
||||
}
|
||||
})
|
||||
|
||||
return newMenus
|
||||
}
|
||||
|
||||
/**
|
||||
* is has only child
|
||||
*
|
||||
* @param menu
|
||||
*/
|
||||
function isHasOnlyChild(menu: Menu): boolean {
|
||||
if (menu.children === undefined) {
|
||||
return true
|
||||
}
|
||||
|
||||
if (menu.children.length > 1 || !menu.children.length) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (menu.children[0].children?.length) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
subMenuClass: {
|
||||
type: String,
|
||||
require: true,
|
||||
},
|
||||
menuClass: {
|
||||
type: String,
|
||||
require: true,
|
||||
},
|
||||
},
|
||||
setup(props, ctx) {
|
||||
const permissionsStore = usePermissionsStore()
|
||||
const userStore = useUserStore()
|
||||
|
||||
// 后端的 permissions 返回 undefined,则认为该后端无权限系统
|
||||
const permissions = userStore.getPermissions === undefined ? [] : userStore.getPermissions
|
||||
const vnodes = getVNodes(filterMenus(permissionsStore.getMenusFrom(permissions)), props.subMenuClass)
|
||||
|
||||
return () => {
|
||||
return h(
|
||||
menus,
|
||||
{
|
||||
class: 'border-none side-menu ' + props.menuClass,
|
||||
},
|
||||
{
|
||||
default: () => vnodes,
|
||||
},
|
||||
)
|
||||
}
|
||||
},
|
||||
})
|
||||
</script>
|
58
resources/admin/layout/components/Menu/item.vue
Normal file
58
resources/admin/layout/components/Menu/item.vue
Normal file
@@ -0,0 +1,58 @@
|
||||
<template>
|
||||
<el-sub-menu :index="menu?.path" :class="subMenuClass" v-if="menu?.children?.length">
|
||||
<template #title>
|
||||
<el-icon>
|
||||
<Icon :name="menu?.meta?.icon" v-if="menu?.meta?.icon" class="text-sm"/>
|
||||
</el-icon>
|
||||
<span>{{ menu?.meta?.title }}</span>
|
||||
</template>
|
||||
<slot/>
|
||||
</el-sub-menu>
|
||||
|
||||
<el-menu-item v-else class="ct-menu-item" :index="menu?.path" @click="isMiniScreen() && store.changeExpaned()">
|
||||
<el-icon>
|
||||
<Icon :name="menu?.meta?.icon" v-if="menu?.meta?.icon" class="text-sm"/>
|
||||
</el-icon>
|
||||
<span>{{ menu?.meta?.title }}</span>
|
||||
</el-menu-item>
|
||||
</template>
|
||||
|
||||
<script lang="ts" name="MenuItem" setup>
|
||||
import { Menu } from '/admin/types/Menu'
|
||||
import { PropType } from 'vue'
|
||||
import { useAppStore } from '/admin/stores/modules/app'
|
||||
import { isMiniScreen } from '/admin/support/Helper'
|
||||
|
||||
const store = useAppStore()
|
||||
|
||||
defineProps({
|
||||
subMenuClass: {
|
||||
type: String,
|
||||
require: true,
|
||||
default: '',
|
||||
},
|
||||
|
||||
menu: {
|
||||
type: Object as PropType<Menu>,
|
||||
require: true,
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
:deep(.el-menu) {
|
||||
background-color: var(--sider-sub-menu-bg-color);
|
||||
}
|
||||
|
||||
.ct-menu-item:hover {
|
||||
background-color: var(--sider-sub-menu-hover-bg-color) !important;
|
||||
}
|
||||
|
||||
:deep(.children-menu .el-sub-menu__title) {
|
||||
background-color: var(--sider-sub-menu-bg-color) !important;
|
||||
}
|
||||
|
||||
:deep(.el-menu-item-group__title) {
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
9
resources/admin/layout/components/Menu/mask.vue
Normal file
9
resources/admin/layout/components/Menu/mask.vue
Normal file
@@ -0,0 +1,9 @@
|
||||
<template>
|
||||
<div class="block sm:hidden z-40 w-screen h-full absolute mask-bg left-0 top-0" />
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.mask-bg {
|
||||
background-color: #00000080;
|
||||
}
|
||||
</style>
|
66
resources/admin/layout/components/Menu/menus.vue
Normal file
66
resources/admin/layout/components/Menu/menus.vue
Normal file
@@ -0,0 +1,66 @@
|
||||
<template>
|
||||
<el-menu
|
||||
:default-active="appStore.getActiveMenu"
|
||||
background-color="var(--sider-menu-bg-color)"
|
||||
active-text-color="var(--sider-ment-active-text-color)"
|
||||
text-color="var(--sider-menu-text-color)"
|
||||
:collapse="!appStore.isExpand"
|
||||
:collapse-transition="false"
|
||||
:router="true"
|
||||
@select="selectMenu"
|
||||
:unique-opened="true"
|
||||
>
|
||||
<slot/>
|
||||
</el-menu>
|
||||
</template>
|
||||
<script lang="ts" setup name="menus">
|
||||
import { useAppStore } from '/admin/stores/modules/app'
|
||||
|
||||
const appStore = useAppStore()
|
||||
|
||||
const selectMenu = (index: string) => {
|
||||
if (index.startsWith('http') || index.startsWith('https')) {
|
||||
window.open(index)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.el-menu {
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
:deep(.el-menu--inline) {
|
||||
@apply pt-1 pb-2;
|
||||
}
|
||||
|
||||
:deep(.ct-menu-item) {
|
||||
@apply mt-1;
|
||||
}
|
||||
|
||||
:deep(.is-active) {
|
||||
background-color: var(--side-active-menu-bg-color) !important;
|
||||
}
|
||||
|
||||
:deep(.el-sub-menu__title) {
|
||||
padding-left: calc(calc(var(--el-menu-base-level-padding) + var(--el-menu-level) * var(--el-menu-level-padding)));
|
||||
|
||||
color: var(--sider-menu-text-color);
|
||||
}
|
||||
|
||||
:deep(.el-sub-menu) {
|
||||
color: var(--sider-sub-menu-bg-color);
|
||||
}
|
||||
|
||||
:deep(.el-sub-menu__title:hover) {
|
||||
background-color: var(--sider-menu-bg-color);
|
||||
}
|
||||
|
||||
:deep(.el-menu--popup .el-menu-item:hover) {
|
||||
background-color: var(--sider-menu-bg-color) !important;
|
||||
}
|
||||
|
||||
:deep(.el-menu-item:hover) {
|
||||
background-color: var(--sider-sub-menu-hover-bg-color) !important;
|
||||
}
|
||||
</style>
|
Reference in New Issue
Block a user