|
@@ -1,12 +1,13 @@
|
|
|
<script setup lang="ts">
|
|
|
-import { onMounted, ref } from 'vue';
|
|
|
+import { computed, onMounted, ref } from 'vue';
|
|
|
import { useRoute, useRouter } from 'vue-router';
|
|
|
+import Simplebar from 'simplebar-vue';
|
|
|
|
|
|
-import { routes } from '@/router';
|
|
|
+import SvgIcon from '@/components/SvgIcon.vue';
|
|
|
+import { dataCenterRoutes, opsCenterRoutes } from '@/router';
|
|
|
+import { t } from '@/i18n';
|
|
|
import { translateNavigation } from '@/utils';
|
|
|
|
|
|
-import AsideItem from './AsideItem.vue';
|
|
|
-
|
|
|
import type { MenuInfo } from 'ant-design-vue/es/menu/src/interface';
|
|
|
|
|
|
const router = useRouter();
|
|
@@ -14,6 +15,72 @@ const route = useRoute();
|
|
|
const selectedKeys = ref<string[]>([route.path]);
|
|
|
const openKeys = ref<string[]>([]);
|
|
|
|
|
|
+const menuGroupList = computed(() => {
|
|
|
+ return [
|
|
|
+ {
|
|
|
+ category: t('common.dataCenter'),
|
|
|
+ routes: dataCenterRoutes,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ category: t('common.opsCenter'),
|
|
|
+ routes: opsCenterRoutes,
|
|
|
+ },
|
|
|
+ ];
|
|
|
+});
|
|
|
+
|
|
|
+type DeviceGroup = {
|
|
|
+ id: string;
|
|
|
+ name: string;
|
|
|
+ children?: DeviceGroup[];
|
|
|
+};
|
|
|
+
|
|
|
+const deviceGroupList: DeviceGroup[] = [
|
|
|
+ {
|
|
|
+ id: '1',
|
|
|
+ name: '空调总站房',
|
|
|
+ children: [
|
|
|
+ {
|
|
|
+ id: '1-1',
|
|
|
+ name: '一二期空调群控',
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: '1-2',
|
|
|
+ name: '三四期空调群控',
|
|
|
+ },
|
|
|
+ ],
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: '2',
|
|
|
+ name: '空调次站房',
|
|
|
+ children: [
|
|
|
+ {
|
|
|
+ id: '2-1',
|
|
|
+ name: '一二期空调群控',
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: '2-2',
|
|
|
+ name: '三四期空调群控',
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: '2-3',
|
|
|
+ name: '五期空调群控',
|
|
|
+ },
|
|
|
+ ],
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: '3',
|
|
|
+ name: '空调次站房',
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: '4',
|
|
|
+ name: '空调次站房',
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: '5',
|
|
|
+ name: '空调次站房',
|
|
|
+ },
|
|
|
+];
|
|
|
+
|
|
|
onMounted(() => {
|
|
|
const firstPath = route.path.split('/')[1];
|
|
|
|
|
@@ -27,53 +94,109 @@ const handleMenuClick = ({ key }: MenuInfo) => {
|
|
|
};
|
|
|
|
|
|
const collapsed = ref<boolean>(false);
|
|
|
+
|
|
|
+const toggleCollapsed = () => {
|
|
|
+ collapsed.value = !collapsed.value;
|
|
|
+};
|
|
|
</script>
|
|
|
|
|
|
<template>
|
|
|
- <ALayoutSider class="aside-container" v-model:collapsed="collapsed" collapsible :trigger="null" :width="246">
|
|
|
+ <ALayoutSider
|
|
|
+ class="aside-container"
|
|
|
+ v-model:collapsed="collapsed"
|
|
|
+ collapsible
|
|
|
+ :collapsed-width="64"
|
|
|
+ :trigger="null"
|
|
|
+ :width="246"
|
|
|
+ >
|
|
|
<div class="aside-header">
|
|
|
- <div class="aside-header-logo"></div>
|
|
|
- <span class="aside-header-title">{{ $t('common.unimatIoT') }}</span>
|
|
|
+ <img class="aside-header-logo" src="@/assets/img/logo.png" />
|
|
|
+ <span v-show="!collapsed" class="aside-header-title">{{ $t('common.unimatIoT') }}</span>
|
|
|
</div>
|
|
|
- <AMenu
|
|
|
- class="aside-menu"
|
|
|
- v-model:selected-keys="selectedKeys"
|
|
|
- v-model:open-keys="openKeys"
|
|
|
- mode="inline"
|
|
|
- @click="handleMenuClick"
|
|
|
- >
|
|
|
- <template v-for="{ path, meta, children } in routes" :key="path">
|
|
|
- <template v-if="meta && !meta.hideInMenu">
|
|
|
- <ASubMenu v-if="!meta.hideSubMenu && children" :key="path">
|
|
|
- <template #title>
|
|
|
- <AsideItem :title="translateNavigation(meta.title)" :icon="meta.icon" />
|
|
|
+ <Simplebar class="aside-scroll">
|
|
|
+ <AMenu
|
|
|
+ class="aside-menu"
|
|
|
+ v-model:selected-keys="selectedKeys"
|
|
|
+ v-model:open-keys="openKeys"
|
|
|
+ mode="inline"
|
|
|
+ @click="handleMenuClick"
|
|
|
+ >
|
|
|
+ <div class="aside-menu-category">{{ $t('common.aiCtrl') }}</div>
|
|
|
+ <template v-for="item in deviceGroupList" :key="item.id">
|
|
|
+ <ASubMenu v-if="item.children?.length" :key="item.id" :title="item.name">
|
|
|
+ <template #icon>
|
|
|
+ <SvgIcon name="air-conditioning" />
|
|
|
</template>
|
|
|
- <AMenuItem v-for="{ path: subPath, meta } in children" :key="`${path}/${subPath}`">
|
|
|
- <AsideItem :title="translateNavigation(meta?.title)" />
|
|
|
+ <AMenuItem v-for="subItem in item.children" :key="subItem.id" disabled>
|
|
|
+ {{ subItem.name }}
|
|
|
</AMenuItem>
|
|
|
</ASubMenu>
|
|
|
- <AMenuItem v-else :key="`${path}/index`">
|
|
|
- <AsideItem :title="translateNavigation(meta.title)" :icon="meta.icon" />
|
|
|
- </AMenuItem>
|
|
|
+ <template v-else>
|
|
|
+ <AMenuItem :key="item.id" :title="item.name" disabled>
|
|
|
+ <template #icon>
|
|
|
+ <SvgIcon name="air-conditioning" />
|
|
|
+ </template>
|
|
|
+ {{ item.name }}
|
|
|
+ </AMenuItem>
|
|
|
+ </template>
|
|
|
</template>
|
|
|
- </template>
|
|
|
- <div class="aside-menu-ai-ctrl">{{ $t('common.aiCtrl') }}</div>
|
|
|
- <AMenuItem key="ai-1" disabled>
|
|
|
- <AsideItem title="一级菜单" icon="setting" />
|
|
|
- </AMenuItem>
|
|
|
- <AMenuItem key="ai-2" disabled>
|
|
|
- <AsideItem title="一级菜单" icon="setting" />
|
|
|
- </AMenuItem>
|
|
|
- <AMenuItem key="ai-3" disabled>
|
|
|
- <AsideItem title="一级菜单" icon="setting" />
|
|
|
- </AMenuItem>
|
|
|
- </AMenu>
|
|
|
+ <template v-for="(item, index) in menuGroupList" :key="index">
|
|
|
+ <div class="aside-menu-category">{{ item.category }}</div>
|
|
|
+ <template v-for="{ path, meta, children } in item.routes" :key="path">
|
|
|
+ <template v-if="meta && !meta.hideInMenu">
|
|
|
+ <ASubMenu
|
|
|
+ v-if="!meta.hideSubMenu && children"
|
|
|
+ :key="path"
|
|
|
+ :title="translateNavigation(meta.title)"
|
|
|
+ popup-class-name="aside-menu-submenu-popup"
|
|
|
+ >
|
|
|
+ <template #icon>
|
|
|
+ <SvgIcon v-if="meta.icon" :name="meta.icon" />
|
|
|
+ </template>
|
|
|
+ <AMenuItem v-for="{ path: subPath, meta } in children" :key="`${path}/${subPath}`">
|
|
|
+ {{ translateNavigation(meta?.title) }}
|
|
|
+ </AMenuItem>
|
|
|
+ </ASubMenu>
|
|
|
+ <AMenuItem v-else :key="`${path}/index`" :title="translateNavigation(meta.title)">
|
|
|
+ <template #icon>
|
|
|
+ <SvgIcon v-if="meta.icon" :name="meta.icon" />
|
|
|
+ </template>
|
|
|
+ {{ translateNavigation(meta.title) }}
|
|
|
+ </AMenuItem>
|
|
|
+ </template>
|
|
|
+ </template>
|
|
|
+ </template>
|
|
|
+ </AMenu>
|
|
|
+ </Simplebar>
|
|
|
+ <div v-show="collapsed" class="aside-collapsed-icons">
|
|
|
+ <SvgIcon name="information" />
|
|
|
+ <SvgIcon name="setting" />
|
|
|
+ <SvgIcon name="unfold" @click="toggleCollapsed" />
|
|
|
+ </div>
|
|
|
<div class="aside-footer">
|
|
|
<div class="aside-footer-avatar"></div>
|
|
|
+ <div v-show="!collapsed">
|
|
|
+ <SvgIcon name="setting" />
|
|
|
+ <SvgIcon name="information" />
|
|
|
+ <SvgIcon name="fold" @click="toggleCollapsed" />
|
|
|
+ </div>
|
|
|
</div>
|
|
|
</ALayoutSider>
|
|
|
</template>
|
|
|
|
|
|
+<style lang="scss">
|
|
|
+.aside-menu-submenu-popup.ant-menu-submenu-popup {
|
|
|
+ .ant-menu-item:not(.ant-menu-item-selected):hover {
|
|
|
+ color: var(--antd-color-primary);
|
|
|
+ background-color: initial;
|
|
|
+ }
|
|
|
+
|
|
|
+ .ant-menu-item-selected {
|
|
|
+ background-color: initial;
|
|
|
+ }
|
|
|
+}
|
|
|
+</style>
|
|
|
+
|
|
|
<style lang="scss" scoped>
|
|
|
.aside-container {
|
|
|
--aside-border-radius: 18px;
|
|
@@ -92,31 +215,39 @@ const collapsed = ref<boolean>(false);
|
|
|
|
|
|
& > .ant-menu-item,
|
|
|
.ant-menu-submenu-title {
|
|
|
- padding-left: 6px !important;
|
|
|
+ padding-left: 12px !important;
|
|
|
}
|
|
|
|
|
|
.ant-menu-item,
|
|
|
.ant-menu-submenu-title {
|
|
|
width: calc(100%);
|
|
|
- height: 36px;
|
|
|
+ height: 40px;
|
|
|
padding-inline: 12px;
|
|
|
margin-block: 0;
|
|
|
margin-inline: 0;
|
|
|
overflow: hidden;
|
|
|
- line-height: 36px;
|
|
|
+ line-height: 40px;
|
|
|
text-overflow: ellipsis;
|
|
|
+
|
|
|
+ .ant-menu-item-icon {
|
|
|
+ font-size: 16px;
|
|
|
+
|
|
|
+ + span {
|
|
|
+ margin-inline-start: 8px;
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
& > .ant-menu-submenu,
|
|
|
& > .ant-menu-item {
|
|
|
& + .ant-menu-submenu,
|
|
|
& + .ant-menu-item {
|
|
|
- margin-top: 16px;
|
|
|
+ margin-top: 2px;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
.ant-menu-submenu-title + .ant-menu-sub > li:first-child {
|
|
|
- margin-top: 13px;
|
|
|
+ margin-top: 8px;
|
|
|
}
|
|
|
|
|
|
.ant-menu-submenu-arrow {
|
|
@@ -133,14 +264,14 @@ const collapsed = ref<boolean>(false);
|
|
|
}
|
|
|
|
|
|
.ant-menu-sub .ant-menu-item {
|
|
|
- padding-left: 42px !important;
|
|
|
+ padding-left: 34px !important;
|
|
|
|
|
|
&::before {
|
|
|
position: absolute;
|
|
|
- left: 15px;
|
|
|
+ left: 16px;
|
|
|
height: 100%;
|
|
|
content: '';
|
|
|
- border: 1px solid var(--antd-color-primary-bg-hover);
|
|
|
+ border: 1px solid var(--antd-color-primary-opacity-15);
|
|
|
}
|
|
|
|
|
|
&.ant-menu-item-selected::before {
|
|
@@ -153,8 +284,7 @@ const collapsed = ref<boolean>(false);
|
|
|
}
|
|
|
|
|
|
.ant-menu-submenu-selected > .ant-menu-submenu-title {
|
|
|
- color: var(--antd-color-text-secondary);
|
|
|
- background-color: var(--antd-color-primary-bg);
|
|
|
+ background-color: var(--antd-color-primary-opacity-15);
|
|
|
}
|
|
|
|
|
|
.ant-menu-item:not(.ant-menu-item-selected):hover,
|
|
@@ -164,12 +294,13 @@ const collapsed = ref<boolean>(false);
|
|
|
}
|
|
|
|
|
|
& > .ant-menu-item.ant-menu-item-selected {
|
|
|
- background-color: var(--antd-color-primary-bg);
|
|
|
+ background-color: var(--antd-color-primary-opacity-15);
|
|
|
}
|
|
|
|
|
|
.ant-menu-title-content {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
+ font-weight: 500;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
@@ -177,7 +308,7 @@ const collapsed = ref<boolean>(false);
|
|
|
.aside-header {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
- padding: 24px var(--aside-padding) 32px;
|
|
|
+ padding: 24px var(--aside-padding);
|
|
|
background-color: var(--antd-color-bg-base);
|
|
|
border-top-left-radius: var(--aside-border-radius);
|
|
|
border-top-right-radius: var(--aside-border-radius);
|
|
@@ -186,45 +317,67 @@ const collapsed = ref<boolean>(false);
|
|
|
.aside-header-logo {
|
|
|
width: 40px;
|
|
|
height: 40px;
|
|
|
- margin-right: 12px;
|
|
|
- background-color: var(--antd-color-primary);
|
|
|
- border-radius: 16px;
|
|
|
}
|
|
|
|
|
|
.aside-header-title {
|
|
|
+ margin-left: 12px;
|
|
|
+ overflow: hidden;
|
|
|
font-size: 16px;
|
|
|
font-style: normal;
|
|
|
font-weight: 600;
|
|
|
line-height: 24px;
|
|
|
color: var(--antd-color-text);
|
|
|
+ text-overflow: clip;
|
|
|
+ white-space: nowrap;
|
|
|
+}
|
|
|
+
|
|
|
+.aside-scroll {
|
|
|
+ height: calc(100% - 155px);
|
|
|
+ background-color: #fff;
|
|
|
}
|
|
|
|
|
|
.aside-menu {
|
|
|
padding: 0 var(--aside-padding);
|
|
|
- color: var(--antd-color-text-secondary);
|
|
|
+ color: var(--antd-color-text);
|
|
|
}
|
|
|
|
|
|
-.aside-menu-ai-ctrl {
|
|
|
- width: 48px;
|
|
|
- height: 22px;
|
|
|
- margin: 24px 0;
|
|
|
+.aside-menu-category {
|
|
|
+ width: 56px;
|
|
|
+ height: 24px;
|
|
|
+ margin-top: 24px;
|
|
|
+ margin-bottom: 8px;
|
|
|
font-size: 12px;
|
|
|
font-weight: 500;
|
|
|
- line-height: 22px;
|
|
|
+ line-height: 24px;
|
|
|
color: var(--antd-color-text-secondary);
|
|
|
text-align: center;
|
|
|
- background-color: var(--antd-color-primary-bg);
|
|
|
- border-radius: 8px;
|
|
|
+ background-color: #f5f7fa;
|
|
|
+ border-radius: 4px;
|
|
|
+
|
|
|
+ &:first-child {
|
|
|
+ margin-top: 0;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
.aside-footer {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: space-between;
|
|
|
height: 65px;
|
|
|
padding: var(--aside-padding);
|
|
|
- padding-right: 23px;
|
|
|
margin-top: 2px;
|
|
|
background-color: var(--antd-color-bg-base);
|
|
|
border-bottom-right-radius: var(--aside-border-radius);
|
|
|
border-bottom-left-radius: var(--aside-border-radius);
|
|
|
+
|
|
|
+ i {
|
|
|
+ font-size: 16px;
|
|
|
+ cursor: pointer;
|
|
|
+
|
|
|
+ + i {
|
|
|
+ margin-left: 12px;
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
.aside-footer-avatar {
|
|
@@ -245,4 +398,80 @@ const collapsed = ref<boolean>(false);
|
|
|
content: '贾';
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+.aside-container.ant-layout-sider-collapsed {
|
|
|
+ .aside-menu-category {
|
|
|
+ position: relative;
|
|
|
+ left: -8px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .aside-scroll {
|
|
|
+ height: calc(100% - 304px);
|
|
|
+ }
|
|
|
+
|
|
|
+ :deep(.aside-menu) {
|
|
|
+ .ant-menu-submenu,
|
|
|
+ .ant-menu-submenu-title {
|
|
|
+ border-radius: 4px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .ant-menu-item,
|
|
|
+ .ant-menu-submenu-title {
|
|
|
+ text-overflow: initial;
|
|
|
+ }
|
|
|
+
|
|
|
+ .ant-menu-submenu-title {
|
|
|
+ &::before {
|
|
|
+ position: absolute;
|
|
|
+ right: 6px;
|
|
|
+ bottom: 6px;
|
|
|
+ display: block;
|
|
|
+ width: 4px;
|
|
|
+ height: 4px;
|
|
|
+ content: '';
|
|
|
+ border: 1px solid var(--antd-color-text);
|
|
|
+ border-top-width: 0;
|
|
|
+ border-left-width: 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ &:hover::before {
|
|
|
+ border-color: var(--antd-color-primary);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .ant-menu-submenu-selected {
|
|
|
+ .ant-menu-submenu-title {
|
|
|
+ &::before {
|
|
|
+ border-color: var(--antd-color-primary);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.aside-collapsed-icons {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+ padding-top: 25px;
|
|
|
+ background-color: #fff;
|
|
|
+
|
|
|
+ i {
|
|
|
+ width: 40px;
|
|
|
+ height: 40px;
|
|
|
+ font-size: 16px;
|
|
|
+ line-height: 40px;
|
|
|
+ text-align: center;
|
|
|
+ cursor: pointer;
|
|
|
+ transition: color 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
|
|
|
+
|
|
|
+ + i {
|
|
|
+ margin-bottom: 2px;
|
|
|
+ }
|
|
|
+
|
|
|
+ &:hover {
|
|
|
+ color: var(--antd-color-primary);
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
</style>
|