设备管理
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

415 lines
11 KiB

<template>
<div class="root">
<div class="menu-box">
<div class="logo">
<img :src="logoIcon" alt="" class="logo-icon" />
<span class="logo-label">设备管理平台</span>
</div>
<el-scrollbar>
<el-menu :default-active="activeMenu" class="menu" @select="menuClick">
<template v-for="(menu, idx) in menuData" :key="idx">
<el-menu-item
:index="menu.path"
class="menu-item"
v-if="!menu?.children?.length"
>
<Icon :size="16" v-if="menu.meta.icon" :icon="menu.meta.icon" />
<span class="menu-label">{{ menu.meta.title }}</span>
</el-menu-item>
<el-sub-menu :index="menu.path" v-else>
<template #title>
<Icon :size="16" v-if="menu.meta.icon" :icon="menu.meta.icon" />
<span class="menu-label">{{ menu.meta.title }}</span>
</template>
<template v-for="(child, index) in menu.children" :key="index">
<el-menu-item
class="menu-item"
:index="`${menu.path}/${child.path}`"
v-if="!child?.children?.length"
>
<Icon :size="16" v-if="child.meta.icon" :icon="child.meta.icon" />
{{ child.meta.title }}
</el-menu-item>
<el-sub-menu :index="`${menu.path}/${child.path}`" v-else>
<template #title>
<Icon :size="16" v-if="child.meta.icon" :icon="child.meta.icon" />
{{ child.meta.title }}
</template>
<template
v-for="(subChild, subIndex) in child.children"
:key="subIndex"
>
<el-menu-item
class="menu-item"
:index="`${menu.path}/${child.path}/${subChild.path}`"
>
<Icon
:size="16"
v-if="subChild.meta.icon"
:icon="subChild.meta.icon"
/>
{{ subChild.meta.title }}
</el-menu-item>
</template>
</el-sub-menu>
</template>
</el-sub-menu>
</template>
</el-menu>
</el-scrollbar>
</div>
<div class="root-right">
<el-header class="header">
<div class="time">
{{ currentTime }}
</div>
<div class="right">
<el-dropdown
style="width: 100%; height: 100%"
@command="command => handleCommand(command)"
>
<div class="user-avatar">
<img :src="Avatar" height="32" />
<div class="user-name" :title="userName">{{ userName }}</div>
</div>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item command="personal">个人中心</el-dropdown-item>
<el-dropdown-item command="theme">{{ themeText }}</el-dropdown-item>
<el-dropdown-item command="logout">退出登录</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</el-header>
<main class="layout-main">
<el-config-provider :locale="zhCn">
<RouterView />
</el-config-provider>
<!-- </div> -->
</main>
</div>
</div>
</template>
<script setup lang="ts">
import Icon from '@/components/dashboard/Icon/src/Icon.vue'
import zhCn from 'element-plus/dist/locale/zh-cn.mjs'
import { cloneDeep, debounce } from 'lodash'
import { RouterView, useRouter, useRoute } from 'vue-router'
import logoIcon from '@/assets/image/dashboard/common/icon_logo_pg.png'
import { usePermissionStore } from '@/stores/permission'
import dayjs from 'dayjs'
import { useUserStore } from '@/stores/user'
import { isResError } from '@/hooks/useMessage'
import Avatar from './avatar.png'
import { useI18n } from 'vue-i18n'
import { useTheme } from '@/utils/useTheme'
import { THEME_KEY, INIT_OPTIONS_KEY } from 'vue-echarts'
import { registerTheme } from 'echarts/core'
const { theme, toggle } = useTheme()
import { customDark, customLight } from '@/utils/dark'
registerTheme('dark', customDark)
registerTheme('light', customLight)
provide(THEME_KEY, theme)
provide(INIT_OPTIONS_KEY, {
devicePixelRatio: 2,
})
const route = useRoute()
const breadcrumbItems = computed(() => {
console.log(route.matched)
const matched = route.matched.filter(m => m.meta.title)
return matched.map(m => ({
name: m.meta.title,
path: m.path,
}))
})
function transformMenu(menu: any) {
const newMenu = []
function handleChildren(item: any) {
if (item.children && item.children.length === 1) {
const child = item.children[0]
item.path = `${item.path}${child.path ? '/' : ''}${child.path}`
item.name = child.name
item.meta = { ...item.meta, ...child.meta }
item.children = child.children || []
}
return item
}
for (const item of menu) {
const transformedItem = handleChildren(item)
if (!transformedItem.children?.length) {
delete transformedItem.children
}
newMenu.push(transformedItem)
}
return newMenu
}
const themeText = computed(() => (theme.value === 'dark' ? '亮色主题' : '暗色主题'))
const userStore = useUserStore()
const { t } = useI18n() // 国际化
const avatar = computed(() => userStore.user.avatar ?? '')
const userName = computed(() => userStore.user.nickname ?? 'Admin')
const { push, currentRoute } = useRouter()
const permissionStore = usePermissionStore()
const menuData = computed(() =>
transformMenu(filterHiddenRoutes(cloneDeep(permissionStore.getRouters)))
) as ComputedRef<any>
function filterHiddenRoutes(routes: any[]) {
return routes
.filter((route: any) => !route.meta?.hidden)
.map((route: any) => {
if (route.children) {
route.children = filterHiddenRoutes(route.children)
}
return route
})
}
// 当前时间
const currentTime = ref('')
const updateTime = () => {
currentTime.value = dayjs().format('YYYY-MM-DD HH:mm')
}
updateTime()
setInterval(updateTime, 5000)
//菜单相关
const activeIndex = ref('')
const isCollapse = ref(false)
const menuSwitch = () => {
const el = document.querySelector('.menu-box') as HTMLElement
if (el) {
isCollapse.value = !isCollapse.value
if (isCollapse.value) {
el.classList.add('collapsed')
} else {
el.classList.remove('collapsed')
}
}
}
const menuClick = (index: string) => {
activeIndex.value = index
push(activeIndex.value)
}
const activeMenu = computed(() => {
const { path } = unref(currentRoute)
return path
})
function handleCommand(command: string) {
if (command === 'logout') {
loginOut()
}
if (command === 'personal') {
push('/user/profile')
}
if (command === 'theme') {
toggle()
}
}
async function loginOut() {
try {
await ElMessageBox.confirm(t('common.loginOutMessage'), t('common.reminder'), {
confirmButtonText: t('common.ok'),
cancelButtonText: t('common.cancel'),
type: 'warning',
})
await userStore.loginout()
push('/login')
} catch {}
}
</script>
<style scoped lang="scss">
$header-height: 48px;
.root {
width: 100%;
height: 100%;
display: flex;
.menu-box {
max-width: 216px;
height: 100%;
position: relative;
transition: width 0.3s ease;
overflow: hidden;
background-color: var(--layout-menu-bg);
@include ResponsiveW(216);
.logo {
height: 48px;
width: 100%;
display: flex;
align-items: center;
box-sizing: border-box;
font-family: FZZYJW--GB1-0;
background: var(--layout-menu-bg);
justify-content: center;
column-gap: 6px;
font-size: 16px;
color: #fff;
font-weight: 400;
.logo-icon {
width: 22px;
height: 22px;
}
.logo-label {
font-size: 16px;
line-height: 22px;
font-weight: 400;
}
.icon {
width: 20px;
height: 20px;
}
}
:deep(.el-menu) {
background-color: var(--layout-menu-bg);
border-right: none;
}
.menu {
height: calc(100% - 48px);
width: 100%;
overflow: auto;
background-color: var(--layout-menu-bg);
:deep(.el-menu-item:hover),
:deep(.el-sub-menu__title:hover) {
background: #272b35;
}
:deep(.el-menu-item),
:deep(.el-sub-menu__title) {
font-family: Alibaba-PuHuiTi-R;
font-size: 14px;
color: #fff;
line-height: 22px;
font-weight: 400;
height: 40px;
border-left: 3px solid transparent;
}
:deep(.el-menu-item.is-active) {
background-color: #2c342c;
border-left: 3px solid #619925;
}
.mr-5px {
margin-right: 5px;
font-size: 26px;
}
}
}
.menu-box.collapsed {
width: 0;
}
.root-right {
flex: 1;
overflow: hidden;
}
.el-menu--collapse {
overflow: hidden;
width: 0;
}
.header {
display: flex;
align-items: center;
justify-content: start;
background: var(--layout-header-bg);
box-shadow: 0px 2px 4px 0px rgba(0, 0, 0, 0.12);
height: $header-height;
.btn {
--el-button-bg-color: transparent;
--el-button-border-color: transparent;
--el-button-hover-bg-color: transparent;
--el-button-hover-border-color: transparent;
padding: 0;
}
.switch {
width: 24px;
height: 24px;
padding: 3px;
}
.time {
margin-left: 19px;
font-family: Alibaba-PuHuiTi-R;
font-size: 14px;
color: var(--text-color);
font-weight: 400;
}
.right {
margin-left: auto;
display: flex;
align-items: center;
.user-avatar {
display: flex;
outline: none;
align-items: center;
cursor: pointer;
.user-name {
font-size: 14px;
color: var(--text-color);
font-weight: 400;
margin-left: 4px;
}
}
.icon {
width: 24px;
height: 24px;
padding: 3px;
// margin-left: 24px;
}
}
}
.layout-main {
height: calc(100% - $header-height - 28px);
width: calc(100% - 28px);
min-width: 800px;
padding: 14px;
background: var(--bg);
display: flex;
flex-direction: column;
.layout-breadcrumb {
height: 20px;
margin-bottom: 14px;
}
.layout-view {
flex: 1;
background: var(--bg);
border-radius: 4px;
}
// overflow: hidden;
}
}
</style>