From a6a19976e295744bf321bc7205109e20c3eea2a3 Mon Sep 17 00:00:00 2001 From: toesbieya <1647775459@126.com> Date: Fri, 30 Oct 2020 18:02:48 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8Dredirect=E8=BF=87=E5=BF=AB?= =?UTF-8?q?=E5=AF=BC=E8=87=B4=E8=B7=AF=E7=94=B1=E6=9C=AA=E8=B7=B3=E8=BD=AC?= =?UTF-8?q?=E7=9A=84=E9=97=AE=E9=A2=98=20=E4=BB=A3=E7=A0=81=E4=BC=98?= =?UTF-8?q?=E5=8C=96=20=E5=8F=8C=E5=B1=82=E4=BE=A7=E8=BE=B9=E6=A0=8F?= =?UTF-8?q?=E5=AF=BC=E8=88=AA=E5=87=86=E5=A4=87=E5=B7=A5=E4=BD=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- vue/src/asset/style/variables.scss | 2 +- vue/src/component/Charts/PieChart.vue | 3 +- vue/src/component/ExtraArea/index.vue | 2 +- vue/src/component/checkbox/ColorCheckbox.vue | 1 + vue/src/component/checkbox/Group.vue | 2 +- vue/src/component/checkbox/ImgCheckbox.vue | 2 + vue/src/component/menu/NavMenu/index.vue | 41 +++++++ .../layout/component/Aside/TwoPart/index.vue | 12 +++ .../layout/component/Aside/TwoPart/root.vue | 18 ++++ .../layout/component/Aside/TwoPart/sub.vue | 20 ++++ vue/src/layout/component/Aside/index.vue | 61 +---------- .../component/Navbar/component/HeadMenu.vue | 1 + .../Header/component/Navbar/index.vue | 10 +- .../Header/component/TagsView/ScrollPanel.vue | 45 ++++---- .../Navbar/component => }/SettingDrawer.vue | 5 + vue/src/layout/mixin/menu.js | 19 ++-- vue/src/layout/store/app.js | 2 +- vue/src/layout/util.js | 3 + vue/src/layout/布局设计.md | 9 ++ vue/src/mixin/guide/base.js | 9 +- vue/src/router/define.js | 3 +- vue/src/router/index.js | 22 +++- vue/src/util/browser.js | 38 ++++++- vue/src/util/element-ui/elMenu.js | 35 ++++++ vue/src/util/element-ui/elTree.js | 1 + vue/src/util/excel/common.js | 2 +- vue/src/util/index.js | 101 ++++++++++++------ vue/src/view/app/redirect.vue | 17 +-- 28 files changed, 332 insertions(+), 154 deletions(-) create mode 100644 vue/src/component/menu/NavMenu/index.vue create mode 100644 vue/src/layout/component/Aside/TwoPart/index.vue create mode 100644 vue/src/layout/component/Aside/TwoPart/root.vue create mode 100644 vue/src/layout/component/Aside/TwoPart/sub.vue rename vue/src/layout/component/{Header/component/Navbar/component => }/SettingDrawer.vue (97%) create mode 100644 vue/src/util/element-ui/elMenu.js diff --git a/vue/src/asset/style/variables.scss b/vue/src/asset/style/variables.scss index 5e3122d..7265234 100644 --- a/vue/src/asset/style/variables.scss +++ b/vue/src/asset/style/variables.scss @@ -2,7 +2,7 @@ $max-mobile-width: 500px; //覆盖element颜色 -$--color-primary: #1890ff; +$--color-primary: #1890ff; //这里如果有改动,需要同时修改vue.config.js中ThemeColorReplacer的配置 $--color-success: #21BA45; $--color-warning: #FF5722; $--color-danger: #F44336; diff --git a/vue/src/component/Charts/PieChart.vue b/vue/src/component/Charts/PieChart.vue index 184085a..f350485 100644 --- a/vue/src/component/Charts/PieChart.vue +++ b/vue/src/component/Charts/PieChart.vue @@ -17,7 +17,6 @@ export default { data: { deep: true, handler(val) { - if (!window.echarts) return this.init(val) } } @@ -25,7 +24,7 @@ export default { methods: { init(data) { - this.chart.setOption({ + this.chart && this.chart.setOption({ title: { text: this.title, left: 'center', diff --git a/vue/src/component/ExtraArea/index.vue b/vue/src/component/ExtraArea/index.vue index 3171f03..a76df7e 100644 --- a/vue/src/component/ExtraArea/index.vue +++ b/vue/src/component/ExtraArea/index.vue @@ -12,7 +12,7 @@ - diff --git a/vue/src/layout/component/Aside/TwoPart/index.vue b/vue/src/layout/component/Aside/TwoPart/index.vue new file mode 100644 index 0000000..21364fd --- /dev/null +++ b/vue/src/layout/component/Aside/TwoPart/index.vue @@ -0,0 +1,12 @@ + diff --git a/vue/src/layout/component/Aside/TwoPart/root.vue b/vue/src/layout/component/Aside/TwoPart/root.vue new file mode 100644 index 0000000..aa7553c --- /dev/null +++ b/vue/src/layout/component/Aside/TwoPart/root.vue @@ -0,0 +1,18 @@ + diff --git a/vue/src/layout/component/Aside/TwoPart/sub.vue b/vue/src/layout/component/Aside/TwoPart/sub.vue new file mode 100644 index 0000000..912014f --- /dev/null +++ b/vue/src/layout/component/Aside/TwoPart/sub.vue @@ -0,0 +1,20 @@ + + + + + diff --git a/vue/src/layout/component/Aside/index.vue b/vue/src/layout/component/Aside/index.vue index 07c3281..52810e4 100644 --- a/vue/src/layout/component/Aside/index.vue +++ b/vue/src/layout/component/Aside/index.vue @@ -2,10 +2,10 @@ import menuMixin from "@/layout/mixin/menu" import {getters as appGetters} from "@/layout/store/app" import {getters as asideGetters, mutations as asideMutations} from "@/layout/store/aside" -import Logo from './Logo' +import Logo from '@/layout/component/Aside/Logo' import NavMenuItem from '@/component/menu/NavMenu/item' import {getSidebarMenus, getActiveMenuByRoute} from "@/layout/util" -import {getTopDistance} from "@/util/browser" +import {moveToActiveMenuVertically} from "@/util/element-ui/elMenu" export default { name: 'Aside', @@ -23,7 +23,6 @@ export default { computed: { isMobile: () => appGetters.isMobile, - activeRootMenu: () => appGetters.activeRootMenu, //侧边栏菜单 menus: () => getSidebarMenus(), @@ -48,7 +47,7 @@ export default { if (!this.mouseOutside) return true //侧边栏处于折叠状态 且 存在弹出的子菜单,true - return this.collapse && this.openedMenus.length > 0 + return this.collapse && this.openedMenuNum > 0 }, //侧边栏的折叠状态,true折叠、false展开,仅在pc端可折叠 @@ -104,11 +103,6 @@ export default { } }, - //顶部菜单改变时重设高亮项(ele考虑不周,只要菜单被点击就会激活) - activeRootMenu(v) { - v && this.resetActiveMenu() - }, - //抽屉模式下侧边栏显示时滚动至激活菜单 show(v) { v && this.$nextTick(this.moveToCurrentMenu) @@ -153,52 +147,7 @@ export default { //滚动至当前激活的菜单 moveToCurrentMenu() { - const menu = this.$refs.menu, cur = this.activeMenu - if (!menu || !cur) return - - const curInstance = menu.items[cur] - if (!curInstance) return - - let el = curInstance.$el - - //当侧边栏折叠时,需要滚动至可视区域的元素是激活菜单的最顶层父节点 - if (this.collapse) { - let rootParent = curInstance - while (rootParent.$parent.$options.componentName !== 'ElMenu') { - rootParent = rootParent.$parent - } - el = rootParent.$el - } - - /* - * 这里考虑了菜单展开时的200ms动画时间 - * 为什么不分情况讨论?比如当subMenu已经是展开状态时,无需延时滚动 - * 但这种情况无法判断,因为这时menu.openedMenus已经包含了subMenu,无论subMenu之前是否展开 - * 所以统一延时300ms - * */ - window.setTimeout(() => this.scrollMenuIntoView(el, menu.$el), 300) - }, - - //将指定菜单滚动到可视区域内 - scrollMenuIntoView(el, container) { - const {scrollTop, scrollHeight, offsetHeight: menuHeight} = container - - //当菜单高度不足以滚动时跳过 - if (scrollHeight <= menuHeight) return - - const elHeight = el.offsetHeight, between = getTopDistance(el, container) - - //计算需要滚动的距离,undefined说明不需要滚动 - let distance - - if (between < 0) distance = between - else if (between + elHeight > menuHeight) { - distance = between + elHeight - menuHeight - } - - if (distance !== undefined) { - container.scrollTo({top: scrollTop + distance, behavior: 'smooth'}) - } + moveToActiveMenuVertically(this.$refs.menu) } }, @@ -229,7 +178,7 @@ export default { ))} diff --git a/vue/src/layout/component/Header/component/Navbar/component/HeadMenu.vue b/vue/src/layout/component/Header/component/Navbar/component/HeadMenu.vue index 5a610ad..827b953 100644 --- a/vue/src/layout/component/Header/component/Navbar/component/HeadMenu.vue +++ b/vue/src/layout/component/Header/component/Navbar/component/HeadMenu.vue @@ -81,6 +81,7 @@ export default { else this.activeMenu = getActiveMenuByRoute({path, meta}) }, onSelect(index) { + //混合导航模式下,点击相同菜单不刷新页面 if (this.navMode === 'mix' && index === appGetters.activeRootMenu) { return } diff --git a/vue/src/layout/component/Header/component/Navbar/index.vue b/vue/src/layout/component/Header/component/Navbar/index.vue index 5e575d9..6a9d01a 100644 --- a/vue/src/layout/component/Header/component/Navbar/index.vue +++ b/vue/src/layout/component/Header/component/Navbar/index.vue @@ -4,7 +4,7 @@ import GuideMixin from '@/mixin/guide' import Bell from "./component/Bell" import Hamburger from './component/Hamburger' import HeadMenu from "./component/HeadMenu" -import SettingDrawer from './component/SettingDrawer' +import SettingDrawer from '@/layout/component/SettingDrawer' import {getters as appGetters} from "@/layout/store/app" import {getters as asideGetters, mutations as asideMutations} from "@/layout/store/aside" import {getSidebarMenus} from "@/layout/util" @@ -42,13 +42,13 @@ export default { //渲染折叠按钮的条件 //①侧边栏有菜单 //②移动端 - //③桌面端且非顶部导航 + //③桌面端且是侧边栏导航或混合导航模式 //④桌面端且未设置侧边栏自动隐藏 const hasSidebarMenus = getSidebarMenus().length > 0, isMobile = appGetters.isMobile, - notHeadMode = appGetters.navMode !== 'head', + correctMode = ['aside','mix'].includes(appGetters.navMode), notAutoHidden = !asideGetters.autoHide, - renderHamburger = hasSidebarMenus && (isMobile || notHeadMode && notAutoHidden) + renderHamburger = hasSidebarMenus && (isMobile || correctMode && notAutoHidden) if (renderHamburger) { const active = isMobile ? asideGetters.show : !asideGetters.collapse @@ -56,7 +56,7 @@ export default { } }, renderHeadMenuMenu() { - if (appGetters.navMode !== 'aside' && !appGetters.isMobile) { + if (['head', 'mix'].includes(appGetters.navMode) && !appGetters.isMobile) { return } }, diff --git a/vue/src/layout/component/Header/component/TagsView/ScrollPanel.vue b/vue/src/layout/component/Header/component/TagsView/ScrollPanel.vue index 04eef53..7c14ce7 100644 --- a/vue/src/layout/component/Header/component/TagsView/ScrollPanel.vue +++ b/vue/src/layout/component/Header/component/TagsView/ScrollPanel.vue @@ -35,7 +35,9 @@ export default { }, moveToTarget(currentTag, tagInstances) { - const {offsetWidth: $containerWidth, scrollWidth, scrollLeft} = this.$el + const {offsetWidth: containerWidth, scrollWidth, scrollLeft} = this.$el + + if (containerWidth >= scrollWidth) return let firstTag = null let lastTag = null @@ -47,29 +49,28 @@ export default { } if (firstTag === currentTag) { - this.scrollLeft(0) + return this.scrollLeft(0) + } + if (lastTag === currentTag) { + return this.scrollLeft(scrollWidth - containerWidth) } - else if (lastTag === currentTag) { - this.scrollLeft(scrollWidth - $containerWidth) + + // find preTag and nextTag + const currentIndex = tagInstances.findIndex(item => item === currentTag) + const prevTag = tagInstances[currentIndex - 1] + const nextTag = tagInstances[currentIndex + 1] + + // the tag's offsetLeft after of nextTag + const afterNextTagOffsetLeft = nextTag.$el.offsetLeft + nextTag.$el.offsetWidth + this.between + + // the tag's offsetLeft before of prevTag + const beforePrevTagOffsetLeft = prevTag.$el.offsetLeft - this.between + + if (afterNextTagOffsetLeft > scrollLeft + containerWidth) { + this.scrollLeft(afterNextTagOffsetLeft - containerWidth) } - else { - // find preTag and nextTag - const currentIndex = tagInstances.findIndex(item => item === currentTag) - const prevTag = tagInstances[currentIndex - 1] - const nextTag = tagInstances[currentIndex + 1] - - // the tag's offsetLeft after of nextTag - const afterNextTagOffsetLeft = nextTag.$el.offsetLeft + nextTag.$el.offsetWidth + this.between - - // the tag's offsetLeft before of prevTag - const beforePrevTagOffsetLeft = prevTag.$el.offsetLeft - this.between - - if (afterNextTagOffsetLeft > scrollLeft + $containerWidth) { - this.scrollLeft(afterNextTagOffsetLeft - $containerWidth) - } - else if (beforePrevTagOffsetLeft < scrollLeft) { - this.scrollLeft(beforePrevTagOffsetLeft) - } + else if (beforePrevTagOffsetLeft < scrollLeft) { + this.scrollLeft(beforePrevTagOffsetLeft) } }, diff --git a/vue/src/layout/component/Header/component/Navbar/component/SettingDrawer.vue b/vue/src/layout/component/SettingDrawer.vue similarity index 97% rename from vue/src/layout/component/Header/component/Navbar/component/SettingDrawer.vue rename to vue/src/layout/component/SettingDrawer.vue index 649c7b5..3a91e18 100644 --- a/vue/src/layout/component/Header/component/Navbar/component/SettingDrawer.vue +++ b/vue/src/layout/component/SettingDrawer.vue @@ -112,6 +112,11 @@ export default { value: 'mix', img: 'https://gw.alipayobjects.com/zos/antfincdn/x8Ob%26B8cy8/LCkqqYNmvBEbokSDscrm.svg' }, + { + title: '双层侧边栏导航', + value: 'aside-two-part', + img: 'https://gw.alipayobjects.com/zos/antfincdn/XwFOFbLkSM/LCkqqYNmvBEbokSDscrm.svg' + } ], setting: { diff --git a/vue/src/layout/mixin/menu.js b/vue/src/layout/mixin/menu.js index 1e06479..45fd111 100644 --- a/vue/src/layout/mixin/menu.js +++ b/vue/src/layout/mixin/menu.js @@ -9,10 +9,11 @@ export default { data() { return { //当前激活的菜单的fullPath + //之所以手动维护是因为el-menu在点击后就会设置activeIndex activeMenu: '', - //el-menu的openedMenus - openedMenus: [] + //用于判断鼠标是否在弹出菜单内 + openedMenuNum: 0 } }, @@ -23,11 +24,7 @@ export default { if (isExternal(fullPath)) { window.open(fullPath) //重置高亮菜单 - const menu = this.$refs.menu - if (menu && menu.updateActiveIndex) { - menu.updateActiveIndex(this.activeMenu) - } - return + return this.resetActiveMenu() } //触发的菜单路径是当前路由时,根据参数判断是否进行刷新 @@ -39,11 +36,11 @@ export default { else this.$router.push(fullPath) }, + //由于侧边栏菜单数组更新后,el-menu不一定会更新(当数组中不存在单级菜单时) + //所以手动更新el-menu的当前高亮菜单 resetActiveMenu() { - //由于侧边栏菜单数组更新后,el-menu不一定会更新(当数组中不存在单级菜单时) - //所以手动更新el-menu的当前高亮菜单 const menu = this.$refs.menu - menu && menu.updateActiveIndex() + menu && menu.updateActiveIndex(this.activeMenu) }, //渲染el-menu时监听其展开菜单 @@ -58,7 +55,7 @@ export default { if (!menu) return this.watchOpenedMenusCallback = menu.$watch('openedMenus', v => { - this.openedMenus = [...v] + this.openedMenuNum = v.length }) } } diff --git a/vue/src/layout/store/app.js b/vue/src/layout/store/app.js index a3f3442..a07601f 100644 --- a/vue/src/layout/store/app.js +++ b/vue/src/layout/store/app.js @@ -15,7 +15,7 @@ const state = { //主题色 color: '#1890ff', - //导航模式 + //导航模式,'aside'、'aside-two-part'、'head'、'mix' navMode: 'mix' } diff --git a/vue/src/layout/util.js b/vue/src/layout/util.js index 60b383a..9114bf4 100644 --- a/vue/src/layout/util.js +++ b/vue/src/layout/util.js @@ -17,12 +17,15 @@ export function getSidebarMenus() { } switch (appGetters.navMode) { case 'aside': + case 'aside-two-part': return menus case 'head': return [] case 'mix': const root = menus.find(i => i.path === appGetters.activeRootMenu) return root ? root.children || [] : [] + default: + return [] } } diff --git a/vue/src/layout/布局设计.md b/vue/src/layout/布局设计.md index ac0c491..fa848e7 100644 --- a/vue/src/layout/布局设计.md +++ b/vue/src/layout/布局设计.md @@ -10,6 +10,8 @@ 3.混合导航:顶部菜单为完整菜单的所有根节点,侧边栏菜单为当前激活的顶部菜单的子级 +4.双层侧边栏导航:侧边栏分为最左侧的根节点栏、二级子菜单,顶部菜单不渲染 + ### 移动端 1.侧边栏导航:侧边栏菜单为完整菜单,顶部菜单不渲染,侧边栏为抽屉模式 @@ -18,6 +20,8 @@ 3.混合导航:设置无效,效果等同于侧边栏导航 +4.双层侧边栏导航:设置无效,效果等同于侧边栏导航 + ## 侧边栏 ### 桌面端 @@ -25,6 +29,11 @@ 未设置自动隐藏时,折叠状态依据用户设置 设置了自动隐藏时,折叠状态无效,侧边栏为抽屉模式 +当导航模式为*双层侧边栏导航*时,以下设置无效: +- 折叠 +- 折叠时显示父级 +- 自动隐藏 + ### 移动端 侧边栏只能为抽屉模式 diff --git a/vue/src/mixin/guide/base.js b/vue/src/mixin/guide/base.js index fc20c40..c9344cf 100644 --- a/vue/src/mixin/guide/base.js +++ b/vue/src/mixin/guide/base.js @@ -1,4 +1,11 @@ -import {delAllUrlParam} from "@/util" +//删除所有url参数 +function delAllUrlParam() { + let paramStartIndex = location.href.indexOf('?') + if (paramStartIndex > -1) { + const href = location.href.substring(0, paramStartIndex) + history.replaceState(null, null, [...href].join('')) + } +} //将引导步骤中函数的this绑定为组件实例 function transformGuideSteps(instance, steps) { diff --git a/vue/src/router/define.js b/vue/src/router/define.js index a55c602..b6ea3b8 100644 --- a/vue/src/router/define.js +++ b/vue/src/router/define.js @@ -18,8 +18,7 @@ export default [ component: Layout, children: [ { - path: ':path(.*)', - props: true, + path: '*', component: Redirect } ] diff --git a/vue/src/router/index.js b/vue/src/router/index.js index df04cd9..3d20106 100644 --- a/vue/src/router/index.js +++ b/vue/src/router/index.js @@ -33,8 +33,20 @@ registerGuardian(router) export default router -export function addDynamicRoutes(jsonTree) { - jsonTree = deepClone(jsonTree) - const endRoute = {path: '*', redirect: '/404'} - router.addRoutes([...generateRoutes(jsonTree), endRoute]) -} +/** + * 向外部暴露的动态路由添加方法,只能被调用一次 + * + * @param jsonTree {array} 路由配置项构成的树型数组 + */ +export const addDynamicRoutes = (() => { + let init = false + + return (jsonTree) => { + if (init) return + init = true + + jsonTree = deepClone(jsonTree) + const endRoute = {path: '*', redirect: '/404'} + router.addRoutes([...generateRoutes(jsonTree), endRoute]) + } +})() diff --git a/vue/src/util/browser.js b/vue/src/util/browser.js index 4571bf7..a6e3a40 100644 --- a/vue/src/util/browser.js +++ b/vue/src/util/browser.js @@ -19,7 +19,7 @@ export function isDom(obj) { } /** - * 获取元素的内宽(去除padding) + * 获取元素的内宽(扣除左右padding后) * * @param el {HTMLElement} * @return {number} @@ -101,7 +101,9 @@ export function loadExternalResource(url, type = 'js') { if (type === "css") { const links = Array.from(document.getElementsByTagName('link')) - if (links.some(link => link.getAttribute('href') === url)) return resolve() + if (links.some(link => link.getAttribute('href') === url)) { + return resolve() + } tag = document.createElement("link") tag.rel = "stylesheet" @@ -109,7 +111,9 @@ export function loadExternalResource(url, type = 'js') { } else if (type === "js") { const scripts = Array.from(document.getElementsByTagName('script')) - if (scripts.some(script => script.getAttribute('src') === url)) return resolve() + if (scripts.some(script => script.getAttribute('src') === url)) { + return resolve() + } tag = document.createElement("script") tag.src = url @@ -124,6 +128,34 @@ export function loadExternalResource(url, type = 'js') { }) } +/** + * 将dom按最小距离垂直地滚动至视窗内 + * 比如dom在视窗下,那么会滚动到视窗底部 + * + * @param child {HTMLElement} 需要滚动的dom + * @param parent {HTMLElement|Window} 包含child的容器 + */ +export function scrollIntoViewVertically(child, parent = window) { + const {scrollTop, scrollHeight, offsetHeight: containerHeight} = parent + + //当菜单高度不足以滚动时跳过 + if (scrollHeight <= containerHeight) return + + const elHeight = child.offsetHeight, between = getTopDistance(child, parent) + + //计算需要滚动的距离,undefined说明不需要滚动 + let distance + + if (between < 0) distance = between + else if (between + elHeight > containerHeight) { + distance = between + elHeight - containerHeight + } + + if (distance !== undefined) { + parent.scrollTo({top: scrollTop + distance, behavior: 'smooth'}) + } +} + /** * 平滑滚动至指定的位置 * diff --git a/vue/src/util/element-ui/elMenu.js b/vue/src/util/element-ui/elMenu.js new file mode 100644 index 0000000..29eaa6e --- /dev/null +++ b/vue/src/util/element-ui/elMenu.js @@ -0,0 +1,35 @@ +import {scrollIntoViewVertically} from "@/util/browser" + +/** + * 将当前激活的菜单移动到视窗中(仅限垂直菜单) + * + * @param menu el-menu实例 + */ +export function moveToActiveMenuVertically(menu) { + if (!menu) return + + const cur = menu.activeIndex + if (!cur) return + + const curInstance = menu.items[cur] + if (!curInstance) return + + let el = curInstance.$el + + //当侧边栏折叠时,需要滚动至可视区域的元素是激活菜单的最顶层父节点 + if (menu.collapse) { + let rootParent = curInstance + while (rootParent.$parent.$options.componentName !== 'ElMenu') { + rootParent = rootParent.$parent + } + el = rootParent.$el + } + + /* + * 这里考虑了菜单展开时的200ms动画时间 + * 为什么不分情况讨论?比如当subMenu已经是展开状态时,无需延时滚动 + * 但这种情况无法判断,因为这时menu.openedMenus已经包含了subMenu,无论subMenu之前是否展开 + * 所以统一延时300ms + * */ + window.setTimeout(() => scrollIntoViewVertically(el, menu.$el), 300) +} diff --git a/vue/src/util/element-ui/elTree.js b/vue/src/util/element-ui/elTree.js index cf5736f..d43f7dc 100644 --- a/vue/src/util/element-ui/elTree.js +++ b/vue/src/util/element-ui/elTree.js @@ -1,5 +1,6 @@ /** * el-tree展开折叠控制 + * * @param ref el-tree实例 * @param action 'expand' | 'collapse' * @param level 展开的节点最大深度,为0时匹配全部节点 diff --git a/vue/src/util/excel/common.js b/vue/src/util/excel/common.js index 133f752..da67a09 100644 --- a/vue/src/util/excel/common.js +++ b/vue/src/util/excel/common.js @@ -1,5 +1,5 @@ -import {isEmpty} from "@/util" import request from "@/api/request" +import {isEmpty} from "@/util" import {elError} from "@/util/message" import {download} from "@/util/file" import {flatTree} from "@/util/tree" diff --git a/vue/src/util/index.js b/vue/src/util/index.js index 7caf767..95e425f 100644 --- a/vue/src/util/index.js +++ b/vue/src/util/index.js @@ -1,41 +1,77 @@ +/** + * 判断是否为空值,undefined、null、'' 都视为空值 + * + * @param str 不定参数 + * @return {boolean} 若为空值,返回true,否则返回false + */ export function isEmpty(...str) { return str.some(i => i === undefined || i === null || i === '') } +/** + * 当传入空值时,返回默认值 + * + * @param v 传入值 + * @param defaultValue 当传入值为空值(使用{@link #isEmpty}判断)时,返回的值 + * @return {string|*} + */ export function emptyOrDefault(v, defaultValue = '') { return isEmpty(v) ? defaultValue : v } +/** + * 字符串替换全部 + * + * @param str 原字符串 + * @param substr 被替换的字符串 + * @param replacement 替换的字符串 + * @return {string} + */ +export function replaceAll(str, substr, replacement) { + if (str == null) return str + + return str.replace(new RegExp(substr, 'gm'), replacement) +} + +/** + * 根据传入值的类型,返回基础起始值 + * + * @param v + * @return {boolean|{}|string|*[]|number|null} + */ export function getInitialValue(v) { - if (v === undefined || v === null) v = null - else if (typeof v === 'string') v = '' - else if (typeof v === 'boolean') v = false - else if (typeof v === 'number') v = 0 - else if (typeof v === 'object') v = {} - else if (Array.isArray(v)) v = [] + if (v === undefined || v === null) return null + if (typeof v === 'string') return '' + if (typeof v === 'boolean') return false + if (typeof v === 'number') return 0 + if (typeof v === 'object') return {} + if (Array.isArray(v)) return [] } -//简单重置对象属性 +/** + * 简单重置对象属性,遇到对象时会递归重置 + * 重置方法使用{@link #getInitialValue} + * + * @param obj 需要重置的对象 + */ export function resetObj(obj) { if (isEmpty(obj)) return Object.keys(obj).forEach(key => { - if (Array.isArray(obj[key])) { - obj[key] = [] - } - else if (typeof obj[key] === 'number') { - obj[key] = 0 - } - else if (typeof obj[key] === 'boolean') { - obj[key] = false - } - else if (obj[key] !== null && typeof obj[key] === 'object') { + if (obj[key] !== null && typeof obj[key] === 'object') { resetObj(obj[key]) } - else obj[key] = null + else obj[key] = getInitialValue(obj[key]) }) } -//简单合并对象 +/** + * 将source合并到target中 + * 仅对target中存在的键进行合并 + * 仅当遇到对象值时进行递归 + * + * @param target + * @param source + */ export function mergeObj(target, source) { if (isEmpty(target, source)) return @@ -64,6 +100,13 @@ export function mergeObj(target, source) { } } +/** + * 日期格式化 + * + * @param fmt {string} 格式,y(+..y)-年、M(+M)-月、d(+d)-天、H(+H)-时、m(+m)-分、s(+s)-秒、S-毫秒 + * @param date {Date} 可选,被格式化的日期 + * @return {string} 格式化后的日期字符串 + */ export function timeFormat(fmt, date = new Date()) { if (isEmpty(fmt)) fmt = 'yyyy-MM-dd HH:mm:ss' @@ -73,7 +116,6 @@ export function timeFormat(fmt, date = new Date()) { "H+": date.getHours(), //小时 "m+": date.getMinutes(), //分 "s+": date.getSeconds(), //秒 - "q+": Math.floor((date.getMonth() + 3) / 3), //季度 "S": date.getMilliseconds() //毫秒 } @@ -85,7 +127,7 @@ export function timeFormat(fmt, date = new Date()) { for (const k in o) { if (new RegExp(`(${k})`).test(fmt)) { const firstMatch = RegExp.$1 - const replace = firstMatch.length === 1 ? o[k] : ("00" + o[k]).substring(("" + o[k]).length) + const replace = firstMatch.length === 1 ? o[k] + "" : ("00" + o[k]).substring(("" + o[k]).length) fmt = fmt.replace(firstMatch, [...replace].join('')) } } @@ -93,6 +135,14 @@ export function timeFormat(fmt, date = new Date()) { return fmt } +/** + * 防抖 + * + * @param func {function} 原函数 + * @param wait {number} 防抖间隔,单位毫秒 + * @param immediate {boolean} 是否立即执行一次 + * @return {function} 经过防抖包装后的函数 + */ export function debounce(func, wait = 100, immediate = false) { let timeout, args, context, timestamp, result @@ -183,15 +233,6 @@ export function waitUntilSuccess(success, callback, interval = 1000, maxTryTime }) } -//删除所有url参数 -export function delAllUrlParam() { - let paramStartIndex = location.href.indexOf('?') - if (paramStartIndex > -1) { - const href = location.href.substring(0, paramStartIndex) - history.replaceState(null, null, [...href].join('')) - } -} - //将传入对象的所有函数的this绑定为其自身 export function bindThis(obj, root = obj) { if (!obj || typeof obj !== 'object') return diff --git a/vue/src/view/app/redirect.vue b/vue/src/view/app/redirect.vue index 3240ad1..55c36e2 100644 --- a/vue/src/view/app/redirect.vue +++ b/vue/src/view/app/redirect.vue @@ -1,17 +1,10 @@