From efddcd0d0c2942615114e811fa4f835fda728cea Mon Sep 17 00:00:00 2001 From: From-wh Date: Thu, 23 Feb 2023 19:51:59 +0800 Subject: [PATCH] =?UTF-8?q?improve:=20=E8=8F=9C=E5=8D=95=E5=8F=8A=E6=A0=B7?= =?UTF-8?q?=E5=BC=8F=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/components/header-search/index.vue | 4 + .../components/side-menu/collapsed-menu.vue | 4 +- .../main/components/side-menu/side-menu.vue | 182 ++++---------- template/admin/src/components/main/main.vue | 20 +- template/admin/src/libs/system/index.js | 224 ++++++++++++++++++ template/admin/src/main.js | 42 +++- .../src/pages/product/productAdd/index.vue | 1 - .../src/pages/setting/setSystem/index.vue | 3 + template/admin/src/store/index.js | 3 + template/admin/src/store/module/menu.js | 165 +++++++++++++ 10 files changed, 498 insertions(+), 150 deletions(-) create mode 100644 template/admin/src/libs/system/index.js create mode 100644 template/admin/src/store/module/menu.js diff --git a/template/admin/src/components/main/components/header-search/index.vue b/template/admin/src/components/main/components/header-search/index.vue index edc85b3de..2e43d2f20 100644 --- a/template/admin/src/components/main/components/header-search/index.vue +++ b/template/admin/src/components/main/components/header-search/index.vue @@ -22,6 +22,7 @@ diff --git a/template/admin/src/components/main/main.vue b/template/admin/src/components/main/main.vue index 4379d4ef3..eb7d2d691 100644 --- a/template/admin/src/components/main/main.vue +++ b/template/admin/src/components/main/main.vue @@ -24,7 +24,7 @@ --> - + - + - + + - @@ -118,9 +118,14 @@ export default { headMenuNoShow: false, }; }, + watch: { + sider(val) { + console.log(val); + }, + }, computed: { - ...mapState('menus', ['childMenuList']), ...mapGetters(['errorCount']), + ...mapState('menu', ['sider']), ...mapState('media', ['isMobile']), tagNavList() { return this.$store.state.app.tagNavList; @@ -313,4 +318,7 @@ export default { width: 800px; } } +.main-warper{ + min-height: calc(~'100vh - 166px'); +} diff --git a/template/admin/src/libs/system/index.js b/template/admin/src/libs/system/index.js new file mode 100644 index 000000000..5d5c74983 --- /dev/null +++ b/template/admin/src/libs/system/index.js @@ -0,0 +1,224 @@ +/** + * 系统内置方法集,正常情况下您不应该修改或移除此文件 + * */ + +import { cloneDeep } from 'lodash'; + +/** + * @description 根据当前路由,找打顶部菜单名称 + * @param {String} currentPath 当前路径 + * @param {Array} menuList 所有路径 + * */ +function getHeaderName(to, menuList) { + const allMenus = []; + menuList.forEach((menu) => { + const headerName = menu.path || ''; + const menus = transferMenu(menu, headerName); + allMenus.push({ + path: menu.path, + header: headerName, + }); + menus.forEach((item) => allMenus.push(item)); + }); + const currentMenu = allMenus.find((item) => { + if (item.path === to.path) { + return true; + } else { + return to.path === getPath(to, item.path); + } + }); + return currentMenu ? currentMenu.header : null; +} + +function getPath(to, path) { + let params = []; + let query = []; + Object.keys(to.params).forEach((item) => { + params.push(to.params[item]); + }); + Object.keys(to.query).forEach((item) => { + query.push(item + '=' + to.query[item]); + }); + return path + (params.length ? '/' + params.join('/') : '') + (query.length ? '?' + query.join('&') : ''); +} + +function transferMenu(menu, headerName) { + if (menu.children && menu.children.length) { + return menu.children.reduce((all, item) => { + all.push({ + path: item.path, + header: headerName, + }); + const foundChildren = transferMenu(item, headerName); + return all.concat(foundChildren); + }, []); + } else { + return [menu]; + } +} + +export { getHeaderName }; + +/** + * @description 根据当前路由,找打顶部菜单名称 + * @param {String} currentPath 当前路径 + * @param {Array} menuList 所有路径 + * */ +function getHeaderSider(menuList) { + return menuList.filter((item) => item.is_header === 1); +} + +export { getHeaderSider }; +/** + * @description 根据当前路由,找以及菜单名称 + * @param {String} currentPath 当前路径 + * @param {Array} menuList 所有路径 + * */ +function getOneHeaderName(menuList, path) { + return menuList.filter((item) => item.path === path); +} + +export { getOneHeaderName }; + +/** + * @description 根据当前顶栏菜单 name,找到对应的二级菜单 + * @param {Array} menuList 所有的二级菜单 + * @param {String} headerName 当前顶栏菜单的 name + * */ +function getMenuSider(menuList, headerName = '') { + if (headerName) { + return menuList.filter((item) => item.path === headerName); + } else { + return menuList; + } +} + +export { getMenuSider }; + +/** + * @description 根据当前路由,找到其所有父菜单 path,作为展开侧边栏 open-names 依据 + * @param {String} currentPath 当前路径 + * @param {Array} menuList 所有路径 + * */ +// function getSiderSubmenu (currentPath, menuList) { +// const allMenus = []; +// menuList.forEach(menu => { +// const menus = transferSubMenu(menu, []); +// allMenus.push({ +// path: menu.path, +// openNames: [] +// }); +// menus.forEach(item => allMenus.push(item)); +// }); +// const currentMenu = allMenus.find(item => item.path === currentPath); +// return currentMenu ? currentMenu.openNames : []; +// } + +function getSiderSubmenu(to, menuList) { + const allMenus = []; + menuList.forEach((menu) => { + const menus = transferSubMenu(menu, []); + allMenus.push({ + path: menu.path, + openNames: [], + }); + menus.forEach((item) => allMenus.push(item)); + }); + const currentMenu = allMenus.find((item) => { + if (item.openNames.length) { + return item.path === to.path || to.path === getPath(to, item.path); + } + }); + return currentMenu ? currentMenu.openNames : []; +} + +function transferSubMenu(menu, openNames) { + if (menu.children && menu.children.length) { + const itemOpenNames = openNames.concat([menu.path]); + return menu.children.reduce((all, item) => { + all.push({ + path: item.path, + openNames: itemOpenNames, + }); + const foundChildren = transferSubMenu(item, itemOpenNames); + return all.concat(foundChildren); + }, []); + } else { + return [menu].map((item) => { + return { + path: item.path, + openNames: openNames, + }; + }); + } +} + +export { getSiderSubmenu }; + +/** + * @description 递归获取所有子菜单 + * */ +function getAllSiderMenu(menuList) { + let allMenus = []; + + menuList.forEach((menu) => { + if (menu.children && menu.children.length) { + const menus = getMenuChildren(menu); + menus.forEach((item) => allMenus.push(item)); + } else { + allMenus.push(menu); + } + }); + + return allMenus; +} + +function getMenuChildren(menu) { + if (menu.children && menu.children.length) { + return menu.children.reduce((all, item) => { + const foundChildren = getMenuChildren(item); + return all.concat(foundChildren); + }, []); + } else { + return [menu]; + } +} + +export { getAllSiderMenu }; + +/** + * @description 将菜单转为平级 + * */ +function flattenSiderMenu(menuList, newList) { + menuList.forEach((menu) => { + let newMenu = {}; + for (let i in menu) { + if (i !== 'children') newMenu[i] = cloneDeep(menu[i]); + } + newList.push(newMenu); + menu.children && flattenSiderMenu(menu.children, newList); + }); + return newList; +} + +export { flattenSiderMenu }; + +/** + * @description 判断列表1中是否包含了列表2中的某一项 + * 因为用户权限 access 为数组,includes 方法无法直接得出结论 + * */ +function includeArray(list1, list2) { + let status = false; + if (list1 === true) { + return true; + } else { + if (typeof list2 !== 'object') { + return false; + } + list2.forEach((item) => { + if (list1.includes(item)) status = true; + }); + return status; + } +} +export { includeArray }; diff --git a/template/admin/src/main.js b/template/admin/src/main.js index bc3eb7fca..c6da0e682 100644 --- a/template/admin/src/main.js +++ b/template/admin/src/main.js @@ -55,7 +55,7 @@ import Viewer from 'v-viewer'; import VueDND from 'awe-dnd'; import formCreate from '@form-create/iview'; import modalForm from '@/utils/modalForm'; -import exportExcel from '@/utils/newToExcel.js' +import exportExcel from '@/utils/newToExcel.js'; import videoCloud from '@/utils/videoCloud'; import { modalSure } from '@/utils/public'; import { authLapse } from '@/utils/authLapse'; @@ -67,6 +67,8 @@ import timeOptions from '@/libs/timeOptions'; import scroll from '@/libs/loading'; import * as tools from '@/libs/tools'; import VueTreeList from 'vue-tree-list'; +import { getHeaderName, getHeaderSider, getMenuSider, getSiderSubmenu } from '@/libs/system'; + // 复制到粘贴板插件 import VueClipboard from 'vue-clipboard2'; @@ -88,9 +90,9 @@ const routerPush = Router.prototype.push; Router.prototype.push = function push(location) { return routerPush.call(this, location).catch((error) => error); }; -import settings from '@/setting' +import settings from '@/setting'; -Vue.prototype.$routeProStr = settings.routePre +Vue.prototype.$routeProStr = settings.routePre; // 实际打包时应该不引入mock /* eslint-disable */ @@ -193,11 +195,45 @@ new Vue({ watch: { // 监听路由 控制侧边栏显示 标记当前顶栏菜单(如需要) $route(to, from) { + const path = to.path; + let menus = this.$store.state.menus.menusName; + console.log(menus, 'menus'); + const menuSider = menus; + + this.$store.commit('menu/setActivePath', path); + const openNames = getSiderSubmenu(to, menuSider); + this.$store.commit('menu/setOpenNames', openNames); + // 设置顶栏菜单 后台添加一个接口,设置顶部菜单 + const headerSider = getHeaderSider(menuSider); + console.log(headerSider, 'headerSider'); + this.$store.commit('menu/setHeader', headerSider); + // 指定当前侧边栏隶属顶部菜单名称。如果你没有使用顶部菜单,则设置为默认的(一般为 home)名称即可 + const headerName = getHeaderName(to, menuSider); + this.$store.commit('menu/setHeaderName', headerName); + // 获取侧边栏菜单 + const filterMenuSider = getMenuSider(menuSider, headerName); + console.log(filterMenuSider, 'filterMenuSider'); + // 指定当前显示的侧边菜单 + this.$store.commit('menu/setOpenMenuName', filterMenuSider[0].title); + this.$store.commit('menu/setSider', filterMenuSider[0]?.children || []); if (to.meta.kefu) { document.getElementsByTagName('body')[0].className = 'kf_mobile'; } else { document.getElementsByTagName('body')[0].className = ''; } + // var storage = window.localStorage; + // let menus = JSON.parse(storage.getItem('menuList')); + // this.getMenus().then(menus => { + // 处理手动清除db 跳转403问题 + if (!menus.length) { + if (path !== '/admin/login') { + this.$router.replace('/admin/login'); + } + return; + } + // 在 404 时,是没有 headerName 的 + + // }); }, }, }); diff --git a/template/admin/src/pages/product/productAdd/index.vue b/template/admin/src/pages/product/productAdd/index.vue index fc77833ed..d6fdfb2bd 100644 --- a/template/admin/src/pages/product/productAdd/index.vue +++ b/template/admin/src/pages/product/productAdd/index.vue @@ -3251,7 +3251,6 @@ export default { font-size: 12px; font-weight: 400; color: #999999; - margin-top: 14px; } .videbox { diff --git a/template/admin/src/pages/setting/setSystem/index.vue b/template/admin/src/pages/setting/setSystem/index.vue index 0649838d5..4a0056582 100644 --- a/template/admin/src/pages/setting/setSystem/index.vue +++ b/template/admin/src/pages/setting/setSystem/index.vue @@ -225,4 +225,7 @@ export default { min-height: 600px; margin-top 0px !important ; } +.article-manager /deep/ .ivu-form-item{ + margin-bottom: 20px !important; +} diff --git a/template/admin/src/store/index.js b/template/admin/src/store/index.js index edbc0c338..b2319ef7d 100644 --- a/template/admin/src/store/index.js +++ b/template/admin/src/store/index.js @@ -15,6 +15,7 @@ import VuexPersistence from 'vuex-persist'; import user from './module/user'; import app from './module/app'; import menus from './module/menus'; +import menu from './module/menu'; import userInfo from './module/userInfo'; import userLevel from './module/userLevel'; import order from './module/order'; @@ -51,6 +52,7 @@ export default new Vuex.Store({ user: state.user, //这个就是存入localStorage的值 app: state.app, menus: state.menus, + menu: state.menu, userInfo: state.userInfo, userLevel: state.userLevel, order: state.order, @@ -67,6 +69,7 @@ export default new Vuex.Store({ user, app, menus, + menu, userInfo, userLevel, order, diff --git a/template/admin/src/store/module/menu.js b/template/admin/src/store/module/menu.js new file mode 100644 index 000000000..1dc6fef4b --- /dev/null +++ b/template/admin/src/store/module/menu.js @@ -0,0 +1,165 @@ +// +---------------------------------------------------------------------- +// | CRMEB [ CRMEB赋能开发者,助力企业发展 ] +// +---------------------------------------------------------------------- +// | Copyright (c) 2016~2021 https://www.crmeb.com All rights reserved. +// +---------------------------------------------------------------------- +// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权 +// +---------------------------------------------------------------------- +// | Author: CRMEB Team +// +---------------------------------------------------------------------- +/** + * 菜单 + * */ +import { cloneDeep } from 'lodash'; +import { includeArray } from '@/libs/system'; + +// 根据 menu 配置的权限,过滤菜单 +function filterMenu(menuList, access, lastList) { + menuList.forEach((menu) => { + let menuAccess = menu.auth; + + if (!menuAccess || includeArray(menuAccess, access)) { + let newMenu = {}; + for (let i in menu) { + if (i !== 'children') newMenu[i] = cloneDeep(menu[i]); + } + if (menu.children && menu.children.length) newMenu.children = []; + + lastList.push(newMenu); + menu.children && filterMenu(menu.children, access, newMenu.children); + } + }); + return lastList; +} +// 递归处理顶部菜单问题 +function getChilden(data) { + if (data.children) { + return getChilden(data.children[0]); + } + return data.path; +} + +export default { + namespaced: true, + state: { + // 顶部菜单 + header: [], + // 一级菜单名称 + oneMenuName: '', + // 侧栏菜单 + sider: [], + // 当前顶栏菜单的 name + headerName: '', + // 当前所在菜单的 path + activePath: '', + // 展开的子菜单 name 集合 + openNames: [], + }, + getters: { + /** + * @description 根据 user 里登录用户权限,对侧边菜单进行鉴权过滤 + * */ + filterSider(state, getters, rootState) { + const userInfo = rootState.user.info; + // @权限 + const access = userInfo.access; + if (access && access.length) { + return filterMenu(state.sider, access, []); + } else { + return filterMenu(state.sider, [], []); + } + }, + // 处理顶部路由递归 + + /** + * @description 根据 user 里登录用户权限,对顶栏菜单进行鉴权过滤 + * */ + filterHeader(state, getters, rootState) { + // 调用递归函数 + state.header.forEach((item) => { + item.path = getChilden(item); + }); + + // @权限 + const userInfo = rootState.admin.user.info; + const access = userInfo.access; + if (access && access.length) { + return state.header.filter((item) => { + let state = true; + if (item.auth && !includeArray(item.auth, access)) state = false; + return state; + }); + } else { + return state.header.filter((item) => { + let state = true; + if (item.auth && item.auth.length) state = false; + return state; + }); + } + }, + /** + * @description 当前 header 的全部信息 + * */ + currentHeader(state) { + return state.header.find((item) => item.name === state.headerName); + }, + /** + * @description 在当前 header 下,是否隐藏 sider(及折叠按钮) + * */ + hideSider(state, getters) { + let visible = false; + if (getters.currentHeader && 'hideSider' in getters.currentHeader) visible = getters.currentHeader.hideSider; + return visible; + }, + }, + mutations: { + /** + * @description 设置侧边栏菜单 + * @param {Object} state vuex state + * @param {Array} menu menu + */ + setSider(state, menu) { + state.sider = menu; + }, + /** + * @description 设置侧边栏菜单 + * @param {Object} state vuex state + * @param {Array} menu menu + */ + setOpenMenuName(state, menu) { + state.oneMenuName = menu; + }, + /** + * @description 设置顶栏菜单 + * @param {Object} state vuex state + * @param {Array} menu menu + */ + setHeader(state, menu) { + state.header = menu; + }, + /** + * @description 设置当前顶栏菜单 name + * @param {Object} state vuex state + * @param {Array} name headerName + */ + setHeaderName(state, name) { + state.headerName = name; + }, + /** + * @description 设置当前所在菜单的 path,用于侧栏菜单高亮当前项 + * @param {Object} state vuex state + * @param {Array} path fullPath + */ + setActivePath(state, path) { + state.activePath = path; + }, + /** + * @description 设置当前所在菜单的全部展开父菜单的 names 集合 + * @param {Object} state vuex state + * @param {Array} names openNames + */ + setOpenNames(state, names) { + state.openNames = names; + }, + }, +};