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>
|
30
resources/admin/layout/components/content.vue
Normal file
30
resources/admin/layout/components/content.vue
Normal file
@@ -0,0 +1,30 @@
|
||||
<template>
|
||||
<div :class="'w-full h-screen flex flex-col transition-spacing duration-300 ease-linear overflow-hidden ' + mlClass">
|
||||
<!-- Header -->
|
||||
<Header />
|
||||
<!-- Tag view -->
|
||||
<!--<div class=""></div>-->
|
||||
<!-- Container -->
|
||||
<div class="p-1 sm:p-4 max-w-full h-screen overflow-auto sm:overflow-x-hidden">
|
||||
<router-view />
|
||||
|
||||
<!--<div class="w-full text-center text-gray-400 h-10 leading-10 mt-2">CatchAdmin 管理系统 @copyright 2018 ~ {{ year }}</div>-->
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue'
|
||||
import { useAppStore } from '/admin/stores/modules/app'
|
||||
|
||||
const appStore = useAppStore()
|
||||
|
||||
const mlClass = computed(() => {
|
||||
return appStore.isExpand ? 'ml-0 sm:ml-56' : 'ml-0 sm:ml-16'
|
||||
})
|
||||
|
||||
const year = computed(() => {
|
||||
const date = new Date()
|
||||
|
||||
return date.getFullYear()
|
||||
})
|
||||
</script>
|
33
resources/admin/layout/components/header/index.vue
Normal file
33
resources/admin/layout/components/header/index.vue
Normal file
@@ -0,0 +1,33 @@
|
||||
<template>
|
||||
<div class="flex flex-row h-16 w-full drop-shadow border-l dark:border-l-0 border-gray-200" style="background-color: var(--header-bg-color)">
|
||||
<div class="flex flex-row justify-between w-full">
|
||||
<div class="flex flex-row min-w-[17rem]">
|
||||
<div class="h-full flex items-center w-8 ml-2 hover:cursor-pointer" @click="store.changeExpaned">
|
||||
<Icon name="list-bullet" class="w-6 h-8" />
|
||||
</div>
|
||||
<div class="w-96 flex items-center pl-3 sm:pl-0">
|
||||
<Breadcrumbs />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex w-52 sm:min-w-[18rem] flex-row item-center pl-1 sm:pl-0 justify-end sm:justify-between mr-4">
|
||||
<div class="w-3/5 hidden sm:flex">
|
||||
<!-- 搜索 -->
|
||||
<Search />
|
||||
<!-- 多语言 -->
|
||||
<Lang />
|
||||
<!-- 暗黑主题 -->
|
||||
<Theme />
|
||||
<Notification />
|
||||
</div>
|
||||
<Profile />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { useAppStore } from '/admin/stores/modules/app'
|
||||
import Notification from './notification.vue'
|
||||
|
||||
const store = useAppStore()
|
||||
</script>
|
38
resources/admin/layout/components/header/lang.vue
Normal file
38
resources/admin/layout/components/header/lang.vue
Normal file
@@ -0,0 +1,38 @@
|
||||
<template>
|
||||
<div class="w-10 h-10 grid place-items-center rounded-full mt-3 hover:cursor-pointer">
|
||||
<div class="flex hover:cursor-pointer pl-1 pr-1">
|
||||
<el-dropdown size="large" class="flex items-center justify-center hover:cursor-pointer w-full" @command="selectLanguage">
|
||||
<Icon name="language" />
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item v-for="lang in langs" :key="lang.value" :command="lang.value" :disabled="lang.value == defaultLang">
|
||||
{{ $t('system.' + lang.label) }}
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive, computed } from 'vue'
|
||||
import { useAppStore } from '/admin/stores/modules/app'
|
||||
|
||||
const langs = reactive([
|
||||
{ label: 'chinese', value: 'zh' },
|
||||
{ label: 'english', value: 'en' },
|
||||
])
|
||||
|
||||
const appStore = useAppStore()
|
||||
// select default languages
|
||||
const defaultLang = computed(() => {
|
||||
return appStore.getLocale
|
||||
})
|
||||
|
||||
// select language
|
||||
const selectLanguage = (value: 'zh' | 'en') => {
|
||||
appStore.changeLocale(value)
|
||||
location.reload()
|
||||
}
|
||||
</script>
|
22
resources/admin/layout/components/header/logo.vue
Normal file
22
resources/admin/layout/components/header/logo.vue
Normal file
@@ -0,0 +1,22 @@
|
||||
<template>
|
||||
<div class="h-16 flex items-center justify-center logo-bg">
|
||||
<img :src="logo" class="h-9 w-9" />
|
||||
<div class="text-md logo-text pl-3" v-if="store.isExpand">CatchAdmin</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useAppStore } from '/admin/stores/modules/app'
|
||||
import logo from '/admin/assets/logo.png'
|
||||
|
||||
const store = useAppStore()
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
.logo-bg {
|
||||
background-color: var(--header-logo-bg-color);
|
||||
}
|
||||
|
||||
.logo-text {
|
||||
color: var(--header-logo-text-color);
|
||||
}
|
||||
</style>
|
49
resources/admin/layout/components/header/notification.vue
Normal file
49
resources/admin/layout/components/header/notification.vue
Normal file
@@ -0,0 +1,49 @@
|
||||
<template>
|
||||
<!-- 通知 -->
|
||||
<div class="w-10 h-10 grid place-items-center rounded-full mt-3 hover:cursor-pointer" ref="messageRef" v-click-outside="onClickOutside">
|
||||
<el-badge :value="3">
|
||||
<Icon name="bell" />
|
||||
</el-badge>
|
||||
<el-popover ref="popoverRef" :virtual-ref="messageRef" trigger="hover" virtual-triggering :width="300">
|
||||
<el-tabs model-value="message">
|
||||
<el-tab-pane label="消息(8)" name="message">
|
||||
<div>
|
||||
<div class="flex flex-row w-full border-b border-b-slate-300 dark:border-b-slate-500 mt-2" v-for="(message, key) in messages" :key="key">
|
||||
<div>
|
||||
<el-avatar src="https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png" class="w-2 h-2" />
|
||||
</div>
|
||||
<div class="ml-2 h-10 mt-2">
|
||||
{{ message }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="通知(1)" name="notice">
|
||||
<div>
|
||||
<div class="flex flex-row w-full border-b border-b-slate-300 dark:border-b-slate-500 mt-2" v-for="(message, key) in messages" :key="key">
|
||||
<div>
|
||||
<el-avatar src="https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png" class="w-2 h-2" />
|
||||
</div>
|
||||
<div class="ml-2 h-10 mt-2">
|
||||
{{ message }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</el-popover>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, unref } from 'vue'
|
||||
import { ClickOutside as vClickOutside } from 'element-plus'
|
||||
const messageRef = ref()
|
||||
const popoverRef = ref()
|
||||
const onClickOutside = () => {
|
||||
unref(popoverRef).popperRef?.delayHide?.()
|
||||
}
|
||||
|
||||
const messages = ref()
|
||||
|
||||
messages.value = ['你收到 catchadmin 的好友申请', '你收到 catchadmin pro 的 license 授权', '你收到 catchadmin 通知']
|
||||
</script>
|
34
resources/admin/layout/components/header/profile.vue
Normal file
34
resources/admin/layout/components/header/profile.vue
Normal file
@@ -0,0 +1,34 @@
|
||||
<template>
|
||||
<div class="flex w-2/5 hover:cursor-pointer pl-1 pr-1">
|
||||
<el-dropdown size="large" placement="bottom-end" class="flex items-center justify-center hover:cursor-pointer w-full">
|
||||
<div class="flex lg:items-center">
|
||||
<img :src="userStore.getAvatar" class="w-7 h-7 rounded-full" />
|
||||
<div class="ml-2 hidden lg:block">{{ userStore.getNickname }}</div>
|
||||
</div>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item>Action 1</el-dropdown-item>
|
||||
<el-dropdown-item>Action 2</el-dropdown-item>
|
||||
<el-dropdown-item>Action 3</el-dropdown-item>
|
||||
<el-dropdown-item divided @click="logout">
|
||||
<Icon name="logout" class="mr-1" />
|
||||
退 出
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useUserStore } from '/admin/stores/modules/user'
|
||||
import Message from '/admin/support/message'
|
||||
|
||||
const userStore = useUserStore()
|
||||
|
||||
const logout = () => {
|
||||
Message.confirm('确定退出系统吗?', () => {
|
||||
userStore.logout()
|
||||
})
|
||||
}
|
||||
</script>
|
58
resources/admin/layout/components/header/search.vue
Normal file
58
resources/admin/layout/components/header/search.vue
Normal file
@@ -0,0 +1,58 @@
|
||||
<template>
|
||||
<div class="w-10 h-10 grid place-items-center rounded-full mt-3 hover:cursor-pointer">
|
||||
<div class="flex flex-row w-96">
|
||||
<Icon name="magnifying-glass" class="hidden sm:block" @click="serachMenuVisable = true" />
|
||||
|
||||
<Teleport to="body">
|
||||
<el-dialog v-model="serachMenuVisable" width="30%" draggable>
|
||||
<el-cascader :filterable="true" :options="options" @change="toWhere" placeholder="请输入菜单名称" clearable class="w-full" :show-all-levels="false" />
|
||||
</el-dialog>
|
||||
</Teleport>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { usePermissionsStore } from '/admin/stores/modules/user/permissions'
|
||||
import { Menu } from '/admin/types/Menu'
|
||||
import router from '/admin/router'
|
||||
import { ref, computed } from 'vue'
|
||||
|
||||
const serachMenuVisable = ref(false)
|
||||
|
||||
const permissionStore = usePermissionsStore()
|
||||
const options = computed(() => {
|
||||
return filterMenus(permissionStore.getMenus)
|
||||
})
|
||||
const toWhere = (value: string[]) => {
|
||||
if (value.length) {
|
||||
router.push({ path: value[value.length - 1] })
|
||||
}
|
||||
|
||||
serachMenuVisable.value = false
|
||||
}
|
||||
|
||||
/**
|
||||
* filter menus
|
||||
*
|
||||
* @param menus
|
||||
*/
|
||||
function filterMenus(menus: Menu[] | undefined): Object[] {
|
||||
const cascaderMenus: Object[] = []
|
||||
|
||||
menus?.forEach(menu => {
|
||||
if (menu.meta === undefined) {
|
||||
const child = menu.children?.pop()
|
||||
cascaderMenus.push(Object.assign({ label: child?.meta?.title, value: child?.path }))
|
||||
} else {
|
||||
const cascaderMenu = Object.assign({ label: menu.meta?.title, value: menu.path, children: [] })
|
||||
|
||||
cascaderMenu.children = filterMenus(menu.children)
|
||||
|
||||
cascaderMenus.push(cascaderMenu)
|
||||
}
|
||||
})
|
||||
|
||||
return cascaderMenus
|
||||
}
|
||||
</script>
|
22
resources/admin/layout/components/header/theme.vue
Normal file
22
resources/admin/layout/components/header/theme.vue
Normal file
@@ -0,0 +1,22 @@
|
||||
<template>
|
||||
<div class="w-10 h-10 grid place-items-center rounded-full mt-3 hover:cursor-pointer">
|
||||
<Icon name="moon" @click="changeTheme()" v-if="isDark" />
|
||||
<Icon name="sun" @click="changeTheme()" v-else />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useDark, useToggle } from '@vueuse/core'
|
||||
import { useAppStore } from '/admin/stores/modules/app'
|
||||
import { unref } from 'vue'
|
||||
|
||||
const appStore = useAppStore()
|
||||
const isDark = useDark()
|
||||
|
||||
const toggleDark = useToggle(isDark)
|
||||
|
||||
function changeTheme() {
|
||||
appStore.setDarkMode(!unref(isDark))
|
||||
toggleDark()
|
||||
}
|
||||
</script>
|
60
resources/admin/layout/components/sider.vue
Normal file
60
resources/admin/layout/components/sider.vue
Normal file
@@ -0,0 +1,60 @@
|
||||
<template>
|
||||
<div>
|
||||
<div :class="sideClass + ' drop-shadow-md overflow-y'">
|
||||
<!--logo -->
|
||||
<Logo />
|
||||
<!-- menu item -->
|
||||
<Menu :menu-class="menuClass" />
|
||||
</div>
|
||||
<Mask v-if="isMobile && appStore.isExpand" @click="appStore.changeExpaned()" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useAppStore } from '/admin/stores/modules/app'
|
||||
import { computed, onMounted, ref, watch } from 'vue'
|
||||
import { isMiniScreen } from '/admin/support/Helper'
|
||||
|
||||
const isMobile = ref(isMiniScreen())
|
||||
const layoutSide = ' h-screen z-[1000] sm:z-0 absolute top-0 left-0 sm:fixed transition-width duration-300 ease-linear sider-bg'
|
||||
const layoutSideOpenClass = 'w-56' + layoutSide
|
||||
const layoutSideHiddenClass = 'w-0 sm:w-16' + layoutSide
|
||||
|
||||
// 是否是小屏幕
|
||||
const sideClass = computed(() => {
|
||||
return appStore.isExpand ? layoutSideOpenClass : layoutSideHiddenClass
|
||||
})
|
||||
|
||||
// menu class
|
||||
const menuClass = ref<string>()
|
||||
// 判断展开状态
|
||||
const appStore = useAppStore()
|
||||
watch(appStore.$state, state => {
|
||||
// 如果切换到小屏幕,并且是菜单是收缩状态
|
||||
menuClass.value = isExpandWhenInMobile()
|
||||
})
|
||||
|
||||
// 监控屏幕大小
|
||||
onMounted(() => {
|
||||
window.onresize = () => {
|
||||
return (() => {
|
||||
// 如果切换到小屏幕,并且是菜单是收缩状态,则隐藏子菜单
|
||||
isMobile.value = isMiniScreen()
|
||||
menuClass.value = isExpandWhenInMobile()
|
||||
})()
|
||||
}
|
||||
|
||||
// 刷新或者 go back 的时候默认展开菜单
|
||||
appStore.isExpand = true
|
||||
})
|
||||
|
||||
function isExpandWhenInMobile(): string {
|
||||
return !appStore.isExpand && isMobile.value ? 'hidden' : ''
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.sider-bg {
|
||||
background-color: var(--sider-menu-bg-color);
|
||||
}
|
||||
</style>
|
8
resources/admin/layout/index.vue
Normal file
8
resources/admin/layout/index.vue
Normal file
@@ -0,0 +1,8 @@
|
||||
<template>
|
||||
<div class="w-full flex" ref="el">
|
||||
<!--- Sider -->
|
||||
<Sider />
|
||||
<!--content-->
|
||||
<Content />
|
||||
</div>
|
||||
</template>
|
Reference in New Issue
Block a user