UNPKG

@jianghujs/jianghu

Version:

Progressive Enterprise Framework

524 lines (502 loc) 18.8 kB
<!-- jhMenu.html >>>>>>>>>>>>> --> <script type="text/html" id="jh-menu"> <div> <!-- 手机端左边抽屉菜单 >>>>>>>>>>>>> --> <v-navigation-drawer v-model="isMobileMenuDrawerShown" app clipped v-if="isMobile" style="z-index: 99999" class="jh-page-nav-bar" > <!-- 页面标题 --> <v-toolbar-title ref="toolbarTitle" class="px-4 jh-toolbar-title"> <span class="text-h7 font-weight-bold">{{ appTitle }}</span> </v-toolbar-title> <v-divider class="jh-divider"></v-divider> <!-- 菜单 --> <v-list flat class="mx-2"> <v-list-item-group :value="currentMenuTabIndex"> <template v-for="(item, index) in inMenuList"> <template v-if="item.children && item.children.length > 0"> <v-list-group :value="item.active" class="mx-2"> <template v-slot:activator> <v-list-item class="pl-0" :ripple="false"> <v-list-item-content> <v-list-item-title> {{ item.title }} </v-list-item-title> </v-list-item-content> </v-list-item> </template> <v-list-item @click="openUrl(menu.path, menu.query)" v-for="menu in item.children" color="rgba(0, 0, 0, 0.87)" :class="{'second-active': currentSecondMenuPageId === menu.pageId && currentMenuTabIndex === index}"> <v-list-item-content> <v-list-item-title class="pl-6 pl-sm-4">{{ menu.title }}</v-list-item-title> </v-list-item-content> </v-list-item> </v-list-group> </template> <v-list-item v-else :key="item.path" @click="openUrl(item.path, item.query)" class="mx-2" :class="{'second-active': currentMenuTabIndex === index}" > <v-list-item-content class="pl-0"> <v-list-item-title> {{ item.title }} </v-list-item-title> </v-list-item-content> </v-list-item> </template> </v-list-item-group> </v-list> <!-- 抽屉关闭按钮 --> <v-btn elevation="0" color="primary" fab absolute top left small tile class="jh-menu-drawer-close-float-btn" @click="isMobileMenuDrawerShown = !isMobileMenuDrawerShown"> <v-icon>mdi-close</v-icon> </v-btn> </v-navigation-drawer> <!-- <<<<<<<<<<<<< 手机端左边抽屉菜单 --> <!-- 页面头部 >>>>>>>>>>>>> --> <v-app-bar app clipped-left height="52" class="jh-page-header px-8" style="z-index: 50;" flat > <!-- 手机端左侧菜单开启按钮 --> <v-app-bar-nav-icon color="primary" @click.stop="isMobileMenuDrawerShown = !isMobileMenuDrawerShown" v-if="isMobile"></v-app-bar-nav-icon> <!-- 页面标题 --> <v-toolbar-title ref="toolbarTitle" class="mr-5 pl-0"> <span class="text-h7 font-weight-bold">{{ appTitle }}</span> </v-toolbar-title> <!-- pc端菜单 >>>>>>>>>>>>> --> <v-tabs v-if="!isMobile" show-arrows slider-size="0" color="success" :style="{maxWidth: tabsMaxWidth}" > <template v-for="(item, index) in inMenuList"> <!-- 一级菜单 >>>>>>>>>>>> --> <template v-if="item.pageId && !item.children"> <v-tab class="px-2 mx-1 jh-header-tab" :class="{'jh-header-tab-active': currentMenuTabIndex === index}" :key="item.path" @click="openUrl(item.path, item.query)" > {{ item.title }} </v-tab> </template> <!-- <<<<<<<<<<<<<一级菜单 --> <!-- 二级菜单>>>>>>>>>>>> --> <template v-else> <v-menu offset-y v-model="inMenuShownStatusList[index]"> <template v-slot:activator="{on, attrs}"> <v-tab v-bind="attrs" v-on="on" class="px-2 mx-1 jh-header-tab" :class="{'parent-tab-active': currentMenuTabIndex === index, 'jh-header-tab-active': currentMenuTabIndex === index}" :key="item.path" > {{ item.title }} <v-icon disabled v-if="inMenuShownStatusList[index]" size="12">mdi-chevron-up</v-icon> <v-icon disabled v-else size="12">mdi-chevron-down</v-icon> </v-tab> </template> <v-list nav dense> <v-list-item @click="openUrl(child.path, child.query)" v-for="child in item.children" :class="{'second-active': currentSecondMenuPageId === child.pageId && currentMenuTabIndex === index}" > <v-list-item-content> <v-list-item-title style="color: #41434f">{{ child.title }}</v-list-item-title> </v-list-item-content> </v-list-item> </v-list> </v-menu> </template> <!-- <<<<<<<<<<<<<二级菜单 --> <v-divider style="max-height: 35px;min-height: 35px;align-self: center;" v-if="item.path && item.path.includes('operationManual')" vertical ></v-divider> </template> </v-tabs> <!-- <<<<<<<<<<<<<pc端菜单 --> <div style="white-space: nowrap" class="d-flex align-center"> <!-- PC端右上角菜单 --> <div class="d-flex d-lg-flex align-center"> <div class="d-flex align-center mr-2"> <div class="d-flex align-center ml-4 jh-right-menu" role="button" v-for="rightMenu of inRightMenuList" :key="rightMenu.path" @click="openUrl(rightMenu.path)"> <div v-if="rightMenu.icon" v-html='rightMenu.icon'></div> <div :class="{'ml-1': rightMenu.icon}">{{rightMenu.title}}</div> </div> </div> <v-divider vertical class="mx-3 jh-divider" v-if="inRightMenuList.length > 0"></v-divider> </div> <v-menu offset-y v-model="isAvatarMenuShown"> <template v-slot:activator="{on, attrs}"> <!-- pc端右上角用户信息 --> <div v-if="!isMobile" text v-bind="attrs" v-on="on" class="jh-avatar-menu-btn px-1"> <v-icon :size="32" color="grey lighten-2">mdi-account-circle</v-icon> <div class="ml-1"> <p class="jh-font-size-12 black--text mb-0">{{ userInfo.user.username }}</p> </div> </div> <!-- 移动端右上角用户信息 --> <div v-else text v-bind="attrs" v-on="on" class="jh-avatar-menu-btn px-1"> <v-icon :size="32" color="grey lighten-2">mdi-account-circle</v-icon> </div> </template> <v-list nav dense> <!-- 右上角下拉菜单 --> <v-list-item> <v-list-item-content> <p class="jh-font-size-12 black--text mb-0">{{ userInfo.user.username }}</p> <p class="jh-font-size-10 grey--text mb-0">{{ userInfo.user.userId }}</p> </v-list-item-content> </v-list-item> <template> <div style="max-width: 220px;"> <v-chip x-small class="jh-font-size-10 mr-1 mb-1" v-for="(userGroupRole, i) of userInfo.userGroupRoleList" :key="i">{{ userGroupRole.groupName||userGroupRole.groupId }} | {{ userGroupRole.roleName||userGroupRole.roleId }}</v-chip> </div> <v-divider class="my-1 jh-divider"></v-divider> </template> <v-list-item v-for="avatarMenu of inAvatarMenuList" :key="avatarMenu.path" @click="openUrl(avatarMenu.path)"> <v-list-item-icon class="mr-1 mt-1"> <v-icon size="16" color="grey darken-3">mdi-account-cog-outline</v-icon> </v-list-item-icon> <v-list-item-content> <v-list-item-title color="grey darken-3">{{ avatarMenu.title }}</v-list-item-title> </v-list-item-content> </v-list-item> <v-list-item @click="logout"> <v-list-item-icon class="mr-1 mt-1"> <v-icon size="16" color="grey darken-3">mdi-logout</v-icon> </v-list-item-icon> <v-list-item-content> <v-list-item-title color="grey darken-3">登出</v-list-item-title> </v-list-item-content> </v-list-item> </v-list> </v-menu> </div> </v-app-bar> <!-- <<<<<<<<<<<<< 页面头部 --> </div> </script> <script> Vue.component('jh-menu', { template: "#jh-menu", vueComponent: 'jh-menu', vuetify: new Vuetify(), data() { return { isMobile: window.innerWidth < 500, tabsMaxWidth: 'calc(100vw - 353px)', appDirectoryLink: '<$ ctx.app.config.appDirectoryLink $>', appType: '<$ ctx.app.config.appType $>', appTitle: '<$ ctx.app.config.appTitle $>', userInfo: window.userInfo, // 弹出菜单数据 isMobileMenuDrawerShown: false, isAvatarMenuShown: false, // 用户菜单 menuSort: null, menuGroup: null, inMenuList: [], inRightMenuList: [], inAvatarMenuList: [], inMenuShownStatusList: [], currentMenuTabIndex: -1, // 当前二级菜单pageID currentSecondMenuPageId: null, }; }, watch: { isMobileMenuDrawerShown(value) { this.setCurrentMenuItem(); }, inMenuShownStatusList: { deep: true, handler(value, oValue) { if (this.currentMenuTabIndex > -1 && !value[this.currentMenuTabIndex]) { this.setCurrentMenuItem(); } } } }, created() { this.computedPageId(); this.groupMenuList(); this.computedMenuList(); this.computedMultiAppMenuList(); this.computedRightMenuList(); this.computedAvatarMenuList(); this.setCurrentMenuItem(); this.getTabsMaxWidth(); }, methods: { // 获取当前页面PageId computedPageId() { let urlPathList = window.location.pathname.split('/'); if (window.location.pathname.includes('/page/')) { urlPathList = window.location.pathname.split('/page/'); } this.currentPageId = urlPathList && urlPathList[urlPathList.length - 1]; }, // 动态计算菜单栏目的最大宽度,按照实际的标题宽度计算 getTabsMaxWidth() { this.$nextTick(() => { if (this.$refs.toolbarTitle) { this.tabsMaxWidth = 'calc(100vw - ' + (this.$refs.toolbarTitle.offsetWidth + 195) + 'px)'; } }) }, // 跳转链接 openUrl(url, queryParams) { if (queryParams) { const queryStrings = Object.keys(queryParams) .map(k => encodeURIComponent(k) + '=' + encodeURIComponent(queryParams[k])) .join('&'); window.location.href = url + '?' + queryStrings; } else { window.location.href = url; } }, // 定位当前页面在属于哪个菜单 setCurrentMenuItem() { // 一级菜单定位 const index = _.findIndex(this.inMenuList, { path: location.pathname }); if (index > -1) { // 设置标题、菜单选中 this.currentMenuTabIndex = index; const currentPageTitle = this.inMenuList[index].title; document.title = this.appTitle + (currentPageTitle ? " - " + currentPageTitle : ""); return; } // 子菜单定位 this.inMenuList.some((item, index) => { const findSecondMenuSuccess = item.children && item.children.some(child => { if (child.pageId === this.currentPageId) { this.currentMenuTabIndex = index; this.currentSecondMenuPageId = child.pageId; item.active = true; const currentPageTitle = `[${item.title}]${child.title}`; document.title = this.appTitle + (currentPageTitle ? " - " + currentPageTitle : ""); return true; } return false; }) return findSecondMenuSuccess; }) }, // 菜单数据分组、排序处理 groupMenuList() { // 菜单元数据处理 const menuListResource = _ .chain(this.userInfo.allowPageList) .filter(page => _.includes(['showInMenu', 'link'], page.pageType) || (_.includes(['dynamicInMenu', 'avatarInMenu'], page.pageType) && this.currentPageId === page.pageId)) .value(); // 分组菜单数据 this.menuGroup = _.groupBy(menuListResource, function (item) { if (item.pageName.startsWith("[")) { const pageName = item.pageName.substring(1); const pageNameArr = pageName.split("]"); return `${pageNameArr[0]}`; } return `${item.pageName}`; }); // 二级菜单排序 for (let key in this.menuGroup) { this.menuGroup[key] = _.orderBy(this.menuGroup[key], ['sort'], ['asc']) } // 一级菜单排序 this.menuSort = Object.keys(this.menuGroup).sort((a, b) => { return this.menuGroup[a][0].sort - this.menuGroup[b][0].sort }) }, // 菜单构建 computedMenuList() { this.inMenuList = this.menuSort.map(key => { const page = this.menuGroup[key]; // 没有子菜单,直接构建菜单 if (page.length === 1) { const menu = page[0]; return { path: menu.pageType === 'link' ? menu.pageFile : `/${window.appInfo.appId}/page/${menu.pageId}`, pageId: menu.pageId, title: menu.pageName, children: null }; } // 二级的菜单创建 const children = page.map(child => { let title = ''; if (child.pageName.startsWith("[")) { const pageName = child.pageName.substring(1); title = pageName.split("]")[1]; } else if (child.pageName.includes("|")) { title = child.pageName.split('|')[1]; } else { title = child.pageName; } return { path: child.pageType === 'link' ? child.pageFile : `/${window.appInfo.appId}/page/${child.pageId}`, pageId: child.pageId, title, children: null }; }); return { path: null, pageId: null, title: key, children } }) }, // 多应用项目,添加回到目录 computedMultiAppMenuList() { if (window.appInfo.appId !== 'directory') { const appType = '<$ ctx.app.config.appType $>'; if (appType === 'multiApp' && this.appDirectoryLink) { this.inMenuList.unshift({ path: this.appDirectoryLink, pageId: 'directory', title: '回到目录', children: null }); } } }, // 右侧菜单构建 computedRightMenuList() { this.inRightMenuList = _ .chain(this.userInfo.allowPageList) .filter(['pageType', 'showInRightMenu']) .map((page) => { return { path: _.includes(page.pageId, 'http') ? page.pageId : `/${window.appInfo.appId}/page/${page.pageId}`, title: page.pageName, sort: parseInt(page.sort), icon: page.pageIcon }; }) .orderBy(['sort'], ['asc']) .value(); }, // 头像菜单构建 computedAvatarMenuList() { this.inAvatarMenuList = _ .chain(this.userInfo.allowPageList) .filter(['pageType', 'showInAvatarMenu']) .map((page) => { return { path: `/${window.appInfo.appId}/page/${page.pageId}`, title: page.pageName, sort: parseInt(page.sort) }; }) .orderBy(['sort'], ['asc']) .value(); }, // 登出 async logout() { try { await window.jianghuAxios({ data: { appData: { pageId: 'allPage', actionId: 'logout' } } }) vtoast.success('注销成功'); this.routeToLoginPage(); } catch (error) { vtoast.fail(error.errorReason); this.routeToLoginPage(); } }, // 导航到登陆页 routeToLoginPage() { localStorage.removeItem(`${window.appInfo.authTokenKey}_authToken`); setTimeout(() => { location.href = '<$ ctx.app.config.loginPage $>'; }, 700); } }, }); </script> <style> /* 侧边栏菜单 */ .jh-page-nav-bar .v-list-item, .jh-page-nav-bar .v-list-group__header { border-bottom: 1px solid rgba(0, 0, 0, 0.03); } .jh-page-nav-bar .v-list-group__header .v-list-item { border-bottom: none; } @media (max-width: 600px) { body .jh-page-nav-bar .v-list-group .v-list-group__header { padding: 0 !important; } body .jh-page-nav-bar .v-list-item { border-top: none; } } /* <<<<< 侧边栏菜单 */ .second-active .v-list-item__title { color: var(--cPrimaryColor) !important; } .jh-avatar-menu-btn { display: flex; align-items: center; cursor: pointer; transition: all .3s; border-radius: 5px; } .jh-avatar-menu-btn:hover { opacity: 0.8; } .second-active { caret-color: var(--cPrimaryColor) !important; background-color: rgb(from var(--cPrimaryColor) r g b / 10%); } .jh-toolbar-title { height: 60px; line-height: 60px; } .jh-menu-drawer-close-float-btn { top: 120px !important; right: -40px; position: fixed; left: auto !important; } /* 左上角菜单 */ .jh-header-tab.v-tab--active{ color: var(--gray-900) !important; } .jh-header-tab.jh-header-tab-active{ color: var(--cPrimaryColor) !important; } /* 右上角菜单 */ .jh-right-menu svg{ width: 28px; height: 28px; vertical-align: middle; } </style> <!-- <<<<<<<<<<<<< jhMenu.html -->