mirror of
https://github.com/continew-org/continew-admin-ui.git
synced 2026-01-18 03:00:14 +08:00
first commit
This commit is contained in:
15
src/layout/components/Menu/MenuIcon.vue
Normal file
15
src/layout/components/Menu/MenuIcon.vue
Normal file
@@ -0,0 +1,15 @@
|
||||
<template>
|
||||
<GiSvgIcon v-if="props.svgIcon" :name="props.svgIcon" :size="18"></GiSvgIcon>
|
||||
<component v-else-if="props.icon" :is="props.icon" style="height: 18px; width: 18px"></component>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
interface Props {
|
||||
svgIcon?: string
|
||||
icon?: string
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
70
src/layout/components/Menu/MenuItem.vue
Normal file
70
src/layout/components/Menu/MenuItem.vue
Normal file
@@ -0,0 +1,70 @@
|
||||
<template>
|
||||
<template v-if="!item.meta?.hidden">
|
||||
<a-menu-item
|
||||
v-if="
|
||||
isOneShowingChild &&
|
||||
(!onlyOneChild?.children || onlyOneChild?.meta?.noShowingChildren) &&
|
||||
!item?.meta?.alwaysShow
|
||||
"
|
||||
v-bind="attrs"
|
||||
:key="onlyOneChild?.path"
|
||||
>
|
||||
<template #icon>
|
||||
<MenuIcon
|
||||
:svg-icon="onlyOneChild?.meta?.svgIcon || item?.meta?.svgIcon"
|
||||
:icon="onlyOneChild?.meta?.icon || item?.meta?.icon"
|
||||
></MenuIcon>
|
||||
</template>
|
||||
<span>{{ onlyOneChild?.meta?.title }}</span>
|
||||
</a-menu-item>
|
||||
|
||||
<a-sub-menu v-else v-bind="attrs" :key="item.path" :title="item?.meta?.title">
|
||||
<template #icon>
|
||||
<MenuIcon :svg-icon="item?.meta?.svgIcon" :icon="item?.meta?.icon"></MenuIcon>
|
||||
</template>
|
||||
<MenuItem v-for="child in item.children" :key="child.path" :item="child"></MenuItem>
|
||||
</a-sub-menu>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { RouteRecordRaw } from 'vue-router'
|
||||
import MenuIcon from './MenuIcon.vue'
|
||||
|
||||
defineOptions({ name: 'MenuItem' })
|
||||
const attrs = useAttrs()
|
||||
|
||||
interface Props {
|
||||
item: RouteRecordRaw
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {})
|
||||
|
||||
// 如果hidden: false那么代表这个路由项显示在左侧菜单栏中
|
||||
// 如果props.item的子项chidren只有一个hidden: false的子元素, 那么onlyOneChild就表示这个子元素
|
||||
const onlyOneChild = ref<RouteRecordRaw | null>(null)
|
||||
const isOneShowingChild = ref(false)
|
||||
|
||||
const handleFunction = () => {
|
||||
const chidrens = props.item?.children?.length ? props.item.children : []
|
||||
// 判断是否只有一个显示的子项
|
||||
const showingChildrens = chidrens.filter((i) => i.meta?.hidden === false)
|
||||
if (showingChildrens.length) {
|
||||
// 保存子项最后一个hidden: false的元素
|
||||
onlyOneChild.value = showingChildrens[showingChildrens.length - 1]
|
||||
}
|
||||
|
||||
// 当只有一个要显示子路由时, 默认显示该子路由器
|
||||
if (showingChildrens.length === 1) {
|
||||
isOneShowingChild.value = true
|
||||
}
|
||||
|
||||
// 如果没有要显示的子路由, 则显示父路由
|
||||
if (showingChildrens.length === 0) {
|
||||
onlyOneChild.value = { ...props.item, meta: { ...props.item.meta, noShowingChildren: true } } as any
|
||||
isOneShowingChild.value = true
|
||||
}
|
||||
}
|
||||
|
||||
handleFunction()
|
||||
</script>
|
||||
87
src/layout/components/Menu/index.vue
Normal file
87
src/layout/components/Menu/index.vue
Normal file
@@ -0,0 +1,87 @@
|
||||
<template>
|
||||
<a-menu
|
||||
:mode="mode"
|
||||
:selected-keys="activeMenu"
|
||||
:auto-open-selected="autoOpenSelected"
|
||||
:accordion="appStore.menuAccordion"
|
||||
:breakpoint="appStore.layout === 'mix' ? 'xl' : undefined"
|
||||
:trigger-props="{ animationName: 'slide-dynamic-origin' }"
|
||||
:collapsed="!isDesktop ? false : appStore.menuCollapse"
|
||||
@menu-item-click="onMenuItemClick"
|
||||
@collapse="onCollapse"
|
||||
:style="menuStyle"
|
||||
>
|
||||
<MenuItem v-for="(route, index) in sidebarRoutes" :key="route.path + index" :item="route"></MenuItem>
|
||||
</a-menu>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useAppStore, useRouteStore } from '@/stores'
|
||||
import MenuItem from './MenuItem.vue'
|
||||
import { isExternal } from '@/utils/validate'
|
||||
import type { RouteRecordRaw } from 'vue-router'
|
||||
import type { CSSProperties } from 'vue'
|
||||
import { useDevice } from '@/hooks'
|
||||
|
||||
defineOptions({ name: 'Menu' })
|
||||
const emit = defineEmits<{
|
||||
(e: 'menuItemClickAfter'): void
|
||||
}>()
|
||||
|
||||
interface Props {
|
||||
menus?: RouteRecordRaw[]
|
||||
menuStyle?: CSSProperties
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {})
|
||||
|
||||
const { isDesktop } = useDevice()
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const appStore = useAppStore()
|
||||
const routeStore = useRouteStore()
|
||||
const sidebarRoutes = computed(() => (props.menus ? props.menus : routeStore.routes))
|
||||
// console.log('sidebarRoutes', sidebarRoutes.value)
|
||||
|
||||
// 菜单垂直模式/水平模式
|
||||
const mode = computed(() => {
|
||||
if (!['left', 'mix'].includes(appStore.layout)) {
|
||||
return 'horizontal'
|
||||
} else {
|
||||
return 'vertical'
|
||||
}
|
||||
})
|
||||
|
||||
// 是否默认展开选中的菜单
|
||||
const autoOpenSelected = computed(() => {
|
||||
return ['left', 'mix'].includes(appStore.layout);
|
||||
})
|
||||
|
||||
// 当前页面激活菜单路径,先从路由里面找
|
||||
const activeMenu = computed(() => {
|
||||
const { meta, path } = route
|
||||
if (meta?.activeMenu) {
|
||||
return [meta.activeMenu]
|
||||
}
|
||||
return [path]
|
||||
})
|
||||
|
||||
// 菜单项点击事件
|
||||
const onMenuItemClick = (key: string) => {
|
||||
if (isExternal(key)) {
|
||||
window.open(key)
|
||||
return
|
||||
}
|
||||
router.push({ path: key })
|
||||
emit('menuItemClickAfter')
|
||||
}
|
||||
|
||||
// 折叠状态改变时触发
|
||||
const onCollapse = (collapsed: boolean) => {
|
||||
if (appStore.layout === 'mix') {
|
||||
appStore.menuCollapse = collapsed
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
Reference in New Issue
Block a user