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 @@