修复侧边栏自动隐藏的问题

侧边栏增加了滚动至激活菜单的功能
layout中对是否为移动端的判断由device改为isMobile
使用了window全局对象的方法全部以window.开头
部分命名修改
部分样式修改
master
toesbieya 6 years ago
parent 87757f0b9e
commit c718730d1e
  1. 4
      vue/element-ui-personal/component/Message/main.vue
  2. 24
      vue/src/asset/style/element-ui.scss
  3. 35
      vue/src/asset/style/index.scss
  4. 2
      vue/src/asset/style/variables.scss
  5. 12
      vue/src/component/Icon/index.vue
  6. 17
      vue/src/component/form/Anchor/index.vue
  7. 48
      vue/src/component/menu/NavMenu/item.vue
  8. 2
      vue/src/globalMethod/Guide/main.vue
  9. 20
      vue/src/globalMethod/Verify/main.vue
  10. 162
      vue/src/layout/component/Aside/index.vue
  11. 23
      vue/src/layout/component/Aside/style.scss
  12. 2
      vue/src/layout/component/Header/component/Navbar/component/HeadMenu.vue
  13. 5
      vue/src/layout/component/Header/component/Navbar/component/SettingDrawer.vue
  14. 4
      vue/src/layout/component/Header/component/Navbar/index.vue
  15. 69
      vue/src/layout/component/Header/component/TagsView/ScrollPanel.vue
  16. 71
      vue/src/layout/component/Header/component/TagsView/index.vue
  17. 10
      vue/src/layout/component/Header/component/TagsView/style.scss
  18. 4
      vue/src/layout/component/Page/component/PageFooter.vue
  19. 7
      vue/src/layout/component/Page/component/PageHeader.vue
  20. 3
      vue/src/layout/component/Page/index.vue
  21. 5
      vue/src/layout/index.vue
  22. 21
      vue/src/layout/mixin/menu.js
  23. 4
      vue/src/layout/store/app.js
  24. 18
      vue/src/layout/store/aside.js
  25. 2
      vue/src/layout/util.js
  26. 2
      vue/src/plugin/canvasAnimation/firework/index.js
  27. 2
      vue/src/plugin/canvasAnimation/godrays/original.js
  28. 2
      vue/src/plugin/canvasAnimation/particleNetwork/index.js
  29. 6
      vue/src/plugin/live2d/index.js
  30. 0
      vue/src/router/guardian/avoidReuse.js
  31. 0
      vue/src/router/guardian/iframe.js
  32. 0
      vue/src/router/guardian/index.js
  33. 0
      vue/src/router/guardian/nprogress.js
  34. 0
      vue/src/router/guardian/security.js
  35. 0
      vue/src/router/guardian/setInfo.js
  36. 4
      vue/src/router/index.js
  37. 95
      vue/src/util/browser.js
  38. 6
      vue/src/util/index.js
  39. 6
      vue/src/view/app/login/LoginForm.vue
  40. 4
      vue/src/view/app/login/RegisterForm.vue
  41. 2
      vue/src/view/message/manage/EditDialog.vue
  42. 2
      vue/src/view/message/user/indexPage.vue
  43. 8
      vue/src/view/system/department/component/OrgTreeView.vue

@ -94,9 +94,7 @@ export default {
this.pause = false
this.startAt = Date.now()
if (this.remainTime === 0) this.remainTime = this.duration
this.timer = setTimeout(() => {
!this.closed && this.close()
}, this.remainTime)
this.timer = window.setTimeout(() => !this.closed && this.close(), this.remainTime)
}
}
},

@ -99,17 +99,19 @@
width: 100%;
&::-webkit-scrollbar {
width: 0;
}
//文字超出截断
.el-menu-item > span,
.el-submenu__title > span {
display: inline-block;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
word-break: break-all;
display: none;
}
.el-menu-item,
.el-submenu__title {
//文字超出截断
> span {
display: inline-block;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
word-break: break-all;
}
}
//子级菜单激活时父级同样高亮

@ -72,10 +72,6 @@ a:hover {
overflow: hidden !important;
}
.text-center {
text-align: center
}
.flex {
display: flex;
flex-wrap: wrap;
@ -83,20 +79,37 @@ a:hover {
align-content: space-between;
}
/*----------路由页面的最大高度开始----------*/
.max-view-height {
height: calc(100vh - #{$page-view-margin * 2});
}
.has-nav {
.max-view-height {
height: calc(100vh - #{$nav-height} - #{$page-view-margin * 2});
}
.has-nav .max-view-height {
height: calc(100vh - #{$page-view-margin * 2} - #{$nav-height});
}
&.has-tags-view .max-view-height {
height: calc(100vh - #{$nav-height} - #{$tags-view-height} - #{$page-view-margin * 2});
}
.has-tags-view .max-view-height {
height: calc(100vh - #{$page-view-margin * 2} - #{$tags-view-height});
}
.has-page-header .max-view-height {
height: calc(100vh - #{$page-view-margin * 2} - #{$page-header-height});
}
.has-nav.has-tags-view .max-view-height {
height: calc(100vh - #{$page-view-margin * 2} - #{$nav-height} - #{$tags-view-height});
}
.has-nav .has-page-header .max-view-height {
height: calc(100vh - #{$page-view-margin * 2} - #{$nav-height} - #{$page-header-height});
}
.has-nav.has-tags-view .has-page-header .max-view-height {
height: calc(100vh - #{$page-view-margin * 2} - #{$nav-height} - #{$tags-view-height} - #{$page-header-height});
}
/*----------路由页面的最大高度结束----------*/
.table-container {
margin-top: 10px;
}

@ -73,6 +73,8 @@ $nav-height: 50px;
//头部多页签栏高度
$tags-view-height: 40px;
//页头高度
$page-header-height: 51px;
//路由页面的margin
$page-view-margin: 24px;

@ -1,4 +1,5 @@
<script type="text/jsx">
const SVG_PREFIX = 'svg-'
export default {
name: 'VIcon',
@ -9,12 +10,12 @@ export default {
render(h, context) {
const {data, props: {icon}} = context
const svgPrefix = 'svg-'
if (icon.startsWith(svgPrefix)) {
//svg
if (icon.startsWith(SVG_PREFIX)) {
return (
<svg class="icon" aria-hidden="true" {...data}>
<use href={`#icon-${icon.replace(svgPrefix, '')}`}/>
<svg class="icon svg" aria-hidden="true" {...data}>
<use href={`#icon-${icon.replace(SVG_PREFIX, '')}`}/>
</svg>
)
}
@ -28,6 +29,9 @@ export default {
.icon {
width: 1em;
height: 1em;
}
.icon.svg {
fill: currentColor;
}
</style>

@ -64,16 +64,21 @@ export default {
click(ref) {
if (!ref || this.animating || !this.reference) return
this.cur = ref
const {offsetTop, getContainer} = this
const container = getContainer()
const scrollTop = getScroll(container, true)
const targetElement = this.getDomFromRef(ref)
if (!targetElement) return
const eleOffsetTop = getOffsetTop(targetElement, container)
const y = scrollTop + eleOffsetTop - offsetTop
this.animating = true
scrollTo(y, {callback: () => this.animating = false, getContainer})
const container = this.getContainer()
const scrollTop = getScroll(container, true)
const elOffsetTop = getOffsetTop(targetElement, container)
const y = scrollTop + elOffsetTop - this.offsetTop
//100ms
scrollTo(container, y, {callback: () => window.setTimeout(() => this.animating = false, 110)})
},
scroll() {

@ -4,26 +4,31 @@ const MenuItemContent = {
functional: true,
props: {icon: String, title: String},
props: {icon: String, title: String, useTitleAsIcon: Boolean},
render(h, context) {
const {icon, title} = context.props
const vnodes = []
const {icon, title, useTitleAsIcon} = context.props
const iconComponent =
icon ? <v-icon icon={icon}/> : <span class="icon" style="display: none"/>
useTitleAsIcon
? <em>{title[0]}</em>
: icon
? <v-icon icon={icon}/>
: <span class="icon" style="display: none"/>
vnodes.push(iconComponent)
const vnodes = [iconComponent]
if (title) vnodes.push(<span slot="title" class="menu-item-content">{title}</span>)
title && vnodes.push(<span slot="title" class="menu-item-content">{title}</span>)
return vnodes
}
}
//showIconMaxDepthdepth
function getIcon(icon, showIconMaxDepth, depth) {
if (!showIconMaxDepth || showIconMaxDepth < 0) return icon
function getIcon({icon, showIconMaxDepth, depth}) {
if (!showIconMaxDepth || showIconMaxDepth < 0) {
return icon
}
return showIconMaxDepth < depth ? undefined : icon
}
@ -38,18 +43,20 @@ function getOnlyChild(menu) {
return null
}
function renderSingleMenu(h, {index, icon, title}) {
function renderSingleMenu(h, {index, icon, title, collapse, depth}) {
const useTitleAsIcon = depth === 1 && collapse && !!!icon && !!title
return (
<el-menu-item key={index} index={index}>
<MenuItemContent icon={icon} title={title}/>
<MenuItemContent icon={icon} title={title} useTitleAsIcon={useTitleAsIcon}/>
</el-menu-item>
)
}
function renderSubMenu(h, {index, icon, title, children}) {
function renderSubMenu(h, {index, icon, title, children, collapse, depth}) {
const useTitleAsIcon = depth === 1 && collapse && !!!icon && !!title
return (
<el-submenu key={index} index={index} popper-append-to-body>
<MenuItemContent slot="title" icon={icon} title={title}/>
<MenuItemContent slot="title" icon={icon} title={title} useTitleAsIcon={useTitleAsIcon}/>
{children}
</el-submenu>
)
@ -71,7 +78,13 @@ function renderMenu(h, {menu, showParent, collapse, showIconMaxDepth, depth = 1}
if (showSingle) {
const {fullPath, meta: {icon, title}} = onlyOneChild
return renderSingleMenu(h, {index: fullPath, icon: getIcon(icon, showIconMaxDepth, depth), title})
return renderSingleMenu(h, {
index: fullPath,
icon: getIcon({icon, showIconMaxDepth, depth}),
title,
collapse,
depth
})
}
const {icon, title} = menu.meta
@ -92,7 +105,14 @@ function renderMenu(h, {menu, showParent, collapse, showIconMaxDepth, depth = 1}
}
}
return renderSubMenu(h, {index: menu.fullPath, icon: getIcon(icon, showIconMaxDepth, depth), title, children})
return renderSubMenu(h, {
index: menu.fullPath,
icon: getIcon({icon, showIconMaxDepth, depth}),
title,
children,
collapse,
depth
})
}
export default {

@ -208,7 +208,7 @@ export default {
this.$_setStageStyle(getCalculatedPosition(el))
setTimeout(() => {
window.setTimeout(() => {
this.$refs.popover.updatePopper()
this.moving = false
}, 300)

@ -285,14 +285,14 @@ export default {
if (this.moveStartAtX !== null) {
const success = maxLeft > distance && distance > minLeft
this.stat = success ? 'success' : 'fail'
setTimeout(() => {
window.setTimeout(() => {
this.stat = 'ready'
if (!success) return this.initCanvas()
this.resolve()
this.close()
}, success ? 500 : 1000)
}
setTimeout(() => {
window.setTimeout(() => {
this.sliderStyle.left = '0'
this.sliderStyle.transition = "left 0.5s"
}, 1000)
@ -302,17 +302,17 @@ export default {
addListener() {
if (this.image.loading) return
document.addEventListener("mousemove", this.moving)
document.addEventListener("mouseup", this.moveEnd)
document.addEventListener("touchmove", this.moving)
document.addEventListener("touchend", this.moveEnd)
window.addEventListener("mousemove", this.moving)
window.addEventListener("mouseup", this.moveEnd)
window.addEventListener("touchmove", this.moving)
window.addEventListener("touchend", this.moveEnd)
},
removeListener() {
document.removeEventListener("mousemove", this.moving)
document.removeEventListener("mouseup", this.moveEnd)
document.addEventListener("touchmove", this.moving)
document.addEventListener("touchend", this.moveEnd)
window.removeEventListener("mousemove", this.moving)
window.removeEventListener("mouseup", this.moveEnd)
window.addEventListener("touchmove", this.moving)
window.addEventListener("touchend", this.moveEnd)
},
handleAfterLeave() {

@ -5,6 +5,7 @@ import {getters as asideGetters, mutations as asideMutations} from "@/layout/sto
import Logo from './Logo'
import NavMenuItem from '@/component/menu/NavMenu/item'
import {getSidebarMenus, getActiveMenuByRoute} from "@/layout/util"
import {getOffsetTop} from "@/util/browser"
export default {
name: 'Aside',
@ -21,25 +22,57 @@ export default {
},
computed: {
device: () => appGetters.device,
isMobile: () => appGetters.isMobile,
activeRootMenu: () => appGetters.activeRootMenu,
//truefalsepc
//
menus: () => getSidebarMenus(),
//
renderInDrawer() {
return this.isMobile || asideGetters.autoHide
},
//truefalse
show() {
//store
if (asideGetters.show) return true
//false
if (this.isMobile) return false
//false
if (!asideGetters.autoHide) return false
//true
if (!this.mouseOutside) return true
// true
return this.collapse && this.openedMenus.length > 0
},
//truefalsepc
collapse() {
return asideGetters.collapse && this.device === 'pc'
return asideGetters.collapse && !this.isMobile
},
//
//pc
//
hide() {
return asideGetters.autoHide && this.mouseOutside && this.device === 'pc'
|| asideGetters.collapse && this.device === 'mobile'
//
shouldBindMouseMoveEvent() {
//false
if (this.isMobile) return false
//false
if (!asideGetters.autoHide) return false
//false
if (this.show) return false
//true
return this.mouseOutside
}
},
watch: {
//elMenuinitOpenedMenu()select
'$route.path': {
immediate: true,
handler(v) {
@ -55,33 +88,45 @@ export default {
//
if (!item) return menu.openedMenus = []
//elMenuinitOpenedMenu()select
this.onSelect(item.index, item.indexPath, item, false)
//
!this.renderInDrawer && this.$nextTick(this.moveToCurrentMenu)
}
},
//
device: {
isMobile: {
immediate: true,
handler(v) {
v === 'mobile' && asideMutations.close()
v && asideMutations.close()
}
},
//
//ele
activeRootMenu(v) {
v && this.resetActiveMenu()
},
//
hide(v) {
if (!asideGetters.autoHide) return
const method = `${v ? 'add' : 'remove'}EventListener`
document[method]('mousemove', this.moveEvent)
//
show(v) {
v && this.$nextTick(this.moveToCurrentMenu)
},
//
shouldBindMouseMoveEvent(v) {
//
window.removeEventListener('mousemove', this.moveEvent)
v && window.addEventListener('mousemove', this.moveEvent)
}
},
methods: {
//
moveEvent(e) {
//
if (e.clientX <= 1) this.mouseOutside = false
},
@ -95,27 +140,75 @@ export default {
}
jump && this.actionOnSelectMenu(index)
}
},
mounted() {
if (asideGetters.autoHide) {
document.addEventListener('mousemove', this.moveEvent)
//
if (this.renderInDrawer && this.show) {
asideMutations.close()
this.mouseOutside = true
}
},
//
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 = getOffsetTop(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'})
}
}
},
this.$once('hook:beforeDestroy', () => {
document.removeEventListener('mousemove', this.moveEvent)
})
beforeDestroy() {
window.removeEventListener('mousemove', this.moveEvent)
},
render() {
const menus = getSidebarMenus()
if (menus.length <= 0) return
if (this.menus.length <= 0) return
const aside = (
<aside
class={{'aside': true, 'collapse': this.collapse}}
on-mouseenter={() => this.mouseOutside = false}
on-mouseleave={() => this.mouseOutside = true}
>
{asideGetters.showLogo && <logo collapse={this.collapse}/>}
@ -126,9 +219,9 @@ export default {
collapse-transition={false}
default-active={this.activeMenu}
unique-opened={asideGetters.uniqueOpen}
on-select={this.onSelect}
on={{'select': this.onSelect, 'hook:mounted': this.watchOpenedMenus}}
>
{menus.map(m => (
{this.menus.map(m => (
<nav-menu-item
menu={m}
show-parent={asideGetters.showParentOnCollapse}
@ -140,13 +233,10 @@ export default {
</aside>
)
//
const renderInDrawer = this.device === 'mobile' || asideGetters.autoHide
if (renderInDrawer) {
if (this.renderInDrawer) {
return (
<el-drawer
visible={asideGetters.show}
visible={this.show}
with-header={false}
direction="ltr"
size="auto"

@ -18,7 +18,7 @@
width: $aside-icon-size + 20px * 2;
}
.sidebar-logo-container {
> .sidebar-logo-container {
position: relative;
width: 100%;
height: $nav-height;
@ -54,21 +54,16 @@
}
}
}
}
.el-scrollbar {
flex: 1;
&__wrap {
overflow-x: hidden;
}
}
/*-------侧边栏垂直菜单开始---------*/
.el-menu--vertical.el-menu {
overflow-y: auto;
}
//侧边栏垂直菜单
.el-menu--vertical .el-menu,
.el-menu--vertical.el-menu {
background-color: $aside-menu-background;
overflow-y: auto;
.icon {
font-size: $aside-icon-size;
@ -87,7 +82,8 @@
color: $aside-menu-item-hover-color;
}
> span {
//菜单文字的最大宽度
> .menu-item-content {
//侧边栏宽度菜单左右padding左侧图标宽度距离左右的距离右侧图标宽度
max-width: $aside-width - 20px * 2 - $aside-icon-size - $menu-icon-text-gap * 2 - 12px;
}
@ -111,7 +107,4 @@
border-bottom: 1px solid $aside-sub-menu-background;
}
}
/*-------侧边栏垂直菜单结束---------*/

@ -32,7 +32,7 @@ export default {
navMode: () => appGetters.navMode,
menus() {
if (appGetters.device === 'mobile') return []
if (appGetters.isMobile) return []
switch (this.navMode) {
case 'aside':
return []

@ -142,11 +142,10 @@ export default {
asideMutations.collapse(aside.collapse)
asideMutations.showParentOnCollapse(aside.showParentOnCollapse)
asideMutations.autoHide(aside.autoHide)
},
//layoutstore
syncLayoutStore() {
const {app, page, aside, other} = this.setting
const {app, page, aside} = this.setting
app.color = appGetters.color
app.navMode = appGetters.navMode
@ -211,7 +210,7 @@ export default {
}
},
mounted() {
created() {
mergeObj(this.setting, getLocalPersonalSettings())
//

@ -48,7 +48,7 @@ export default {
//
//
const hasSidebarMenus = getSidebarMenus().length > 0,
isMobile = appGetters.device === 'mobile',
isMobile = appGetters.isMobile,
notHeadMode = appGetters.navMode !== 'head',
notAutoHidden = !asideGetters.autoHide,
renderHamburger = hasSidebarMenus && (isMobile || notHeadMode && notAutoHidden)
@ -59,7 +59,7 @@ export default {
}
},
renderHeadMenuMenu() {
if (appGetters.navMode !== 'aside' && appGetters.device !== 'mobile') {
if (appGetters.navMode !== 'aside' && !appGetters.isMobile) {
return <head-menu always-show/>
}
},

@ -1,23 +1,23 @@
<template>
<el-scrollbar ref="scrollContainer" native noresize @wheel.native.prevent="handleScroll">
<div class="tags-view-wrapper" @wheel="handleScroll">
<slot/>
</el-scrollbar>
</div>
</template>
<script>
const tagAndTagSpacing = 4
import {scrollTo} from "@/util/browser"
export default {
name: 'ScrollPane',
name: 'ScrollPanel',
methods: {
getWrapper() {
return this.$refs.scrollContainer.$refs.wrap
},
props: {
between: {type: Number, default: 4}
},
methods: {
handleScroll(e) {
const eventDelta = e.wheelDelta || -(e.detail || 0) * 40
const {scrollLeft, scrollWidth, clientWidth} = this.getWrapper()
const {scrollLeft, scrollWidth, clientWidth} = this.$el
if (eventDelta > 0 && scrollLeft === 0
|| eventDelta < 0 && scrollWidth <= scrollLeft + clientWidth) {
return
@ -25,18 +25,25 @@ export default {
this.smoothScroll(-eventDelta * 2)
},
moveToTarget(currentTag) {
const $containerWidth = this.$refs.scrollContainer.$el.offsetWidth
const {scrollWidth, scrollLeft} = this.getWrapper()
const tagList = this.$parent.getTags()
smoothScroll(val) {
if (this.rAF) {
window.cancelAnimationFrame(this.rAF)
this.rAf = null
}
this.rAf = scrollTo(this.$el, this.$el.scrollLeft + val, {direction: 'left'})
},
moveToTarget(currentTag, tagInstances) {
const {offsetWidth: $containerWidth, scrollWidth, scrollLeft} = this.$el
let firstTag = null
let lastTag = null
// find first tag and last tag
if (tagList.length > 0) {
firstTag = tagList[0]
lastTag = tagList[tagList.length - 1]
if (tagInstances.length > 0) {
firstTag = tagInstances[0]
lastTag = tagInstances[tagInstances.length - 1]
}
if (firstTag === currentTag) {
@ -47,15 +54,15 @@ export default {
}
else {
// find preTag and nextTag
const currentIndex = tagList.findIndex(item => item === currentTag)
const prevTag = tagList[currentIndex - 1]
const nextTag = tagList[currentIndex + 1]
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 + tagAndTagSpacing
const afterNextTagOffsetLeft = nextTag.$el.offsetLeft + nextTag.$el.offsetWidth + this.between
// the tag's offsetLeft before of prevTag
const beforePrevTagOffsetLeft = prevTag.$el.offsetLeft - tagAndTagSpacing
const beforePrevTagOffsetLeft = prevTag.$el.offsetLeft - this.between
if (afterNextTagOffsetLeft > scrollLeft + $containerWidth) {
this.scrollLeft(afterNextTagOffsetLeft - $containerWidth)
@ -67,25 +74,7 @@ export default {
},
scrollLeft(val) {
this.getWrapper().scrollTo({
left: val,
behavior: 'smooth'
})
},
//300ms
smoothScroll(val) {
if (this.rAF) window.cancelAnimationFrame(this.rAF)
let cost = 300, times = cost / 16, gap = val / times + 1
const frameFunc = () => {
if (times > 0) {
this.getWrapper().scrollLeft += gap
this.rAF = window.requestAnimationFrame(frameFunc)
times--
}
else window.cancelAnimationFrame(this.rAF)
}
frameFunc()
this.$el.scrollTo({left: val, behavior: 'smooth'})
}
}
}

@ -5,12 +5,12 @@ import {route as routeConfig} from '@/config'
import {getters as appGetters} from "@/layout/store/app"
import {getters as tagsViewGetters, mutations as tagsViewMutations} from "@/layout/store/tagsView"
import ContextMenu from "@/component/menu/ContextMenu"
import ScrollPane from './ScrollPane'
import ScrollPanel from './ScrollPanel'
export default {
mixins: [shortcutMixin, decideRouterTransitionMixin],
components: {ContextMenu, ScrollPane},
components: {ContextMenu, ScrollPanel},
data() {
return {
@ -34,39 +34,43 @@ export default {
$route(to, from) {
this.decideRouteTransition && this.decideRouteTransition(to, from)
this.addTags(to)
this.moveToCurrentTag()
this.$nextTick(this.moveToCurrentTag)
}
},
methods: {
isActive(route) {
return route.path === this.$route.path
//
isActive({path}) {
const {path: routePath} = this.$route
return routePath === path || routePath === `/redirect${path}`
},
isAffix(tag) {
return tag.meta && tag.meta.affix
},
filterAffixTags(menus) {
const tags = []
menus.forEach(({name, fullPath, children, meta}) => {
if (meta && meta.title && meta.affix) {
tags.push({
//fullPath$route.fullPath
fullPath,
path: fullPath,
name,
meta: {...meta}
})
}
if (children) {
const tempTags = this.filterAffixTags(children)
tempTags.length && tags.push(...tempTags)
}
})
return tags
},
initTags() {
this.affixTags = this.filterAffixTags(this.menus)
//
function getAffixTags(menus) {
const tags = []
menus.forEach(({name, fullPath, children, meta}) => {
if (meta && meta.title && meta.affix) {
tags.push({
//fullPath$route.fullPath
fullPath,
path: fullPath,
name,
meta: {...meta}
})
}
if (children) {
const tempTags = getAffixTags(children)
tempTags.length && tags.push(...tempTags)
}
})
return tags
}
this.affixTags = getAffixTags(this.menus)
for (const tag of this.affixTags) {
tagsViewMutations.addVisitedView(tag)
}
@ -76,16 +80,13 @@ export default {
addTags(to = this.$route) {
to.meta.title && tagsViewMutations.addView(to)
},
getTags() {
return this.$refs.scrollPane.$children[0].$children
},
//tab
moveToCurrentTag() {
this.$nextTick(() => {
const tag = this.getTags().find(i => i.to.path === this.$route.path)
tag && this.$refs.scrollPane.moveToTarget(tag)
})
//componentInstance
const tagInstances = this.$refs.scrollPanel.$children
const tag = tagInstances.find(i => this.isActive(i.to))
tag && this.$refs.scrollPanel.moveToTarget(tag, tagInstances)
},
/*
@ -155,7 +156,7 @@ export default {
key={path}
class={{'tags-view-item': true, active}}
to={{path, query, fullPath}}
tag="div"
tag="a"
v-on:contextmenu_native={e => this.openMenu(tag, e)}
v-on:dblclick_native={e => closeSelectedTag(e, tag)}
>
@ -196,9 +197,9 @@ export default {
render(h) {
return (
<nav class="tags-view-container">
<scroll-pane ref="scrollPane" class="tags-view-wrapper">
<scroll-panel ref="scrollPanel" class="tags-view-wrapper">
{this.renderTags(h)}
</scroll-pane>
</scroll-panel>
{this.renderContextMenu(h)}
</nav>
)

@ -7,8 +7,14 @@
box-shadow: inset 0 0 3px 2px hsla(0, 0%, 39.2%, .1);
.tags-view-wrapper {
height: 100%;
white-space: nowrap;
position: relative;
overflow-x: auto;
&::-webkit-scrollbar {
display: none;
}
.tags-view-item {
display: inline-block;
@ -68,9 +74,5 @@
}
}
}
.el-scrollbar__wrap {
height: 57px;
}
}
}

@ -13,8 +13,10 @@ export default {
</script>
<style lang="scss">
@import "~@/asset/style/variables.scss";
.page-footer {
padding: 48px 24px 24px 24px;
padding: 48px $page-view-margin $page-view-margin $page-view-margin;
text-align: center;
.copyright {

@ -21,8 +21,8 @@ export default {
immediate: true,
handler(to) {
const {path, meta: {title}, matched} = to
this.title = title
if (!path.startsWith('/redirect')) {
this.title = title
this.data = matched.filter(item => item.meta.title)
}
}
@ -32,13 +32,16 @@ export default {
</script>
<style lang="scss">
@import "~@/asset/style/variables.scss";
.page-header {
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: nowrap;
height: $page-header-height;
font-size: 14px;
padding: 16px 24px 0 24px;
padding: 16px $page-view-margin 0 $page-view-margin;
> .el-breadcrumb .no-redirect {
color: #909399;

@ -14,10 +14,11 @@ export default {
methods: {
renderPage() {
const render = pageGetters.showPageHeader && this.$route.meta.pageHeader !== false
const className = {'scroll-container': true, 'has-page-header': render}
const {cachedViews, transitionName} = tagsViewGetters
return (
<div v-show={!pageGetters.showIframe} class="scroll-container">
<div v-show={!pageGetters.showIframe} class={className}>
{render && <page-header/>}
<page-view include={cachedViews} transition-name={transitionName}/>
<page-footer/>

@ -26,8 +26,7 @@ export default {
methods: {
$_resizeHandler() {
if (!document.hidden) {
const mobile = isMobile()
appMutations.device(mobile ? 'mobile' : 'pc')
appMutations.isMobile(isMobile())
}
}
},
@ -44,7 +43,7 @@ export default {
},
render() {
const renderAide = appGetters.device === 'mobile' || appGetters.navMode !== 'head'
const renderAide = appGetters.isMobile || appGetters.navMode !== 'head'
return (
<section class="app-wrapper">
{renderAide && <v-aside/>}

@ -8,7 +8,10 @@ export default {
data() {
return {
//当前激活的菜单的fullPath
activeMenu: ''
activeMenu: '',
//el-menu的openedMenus
openedMenus: []
}
},
@ -40,6 +43,22 @@ export default {
//所以手动更新el-menu的当前高亮菜单
const menu = this.$refs.menu
menu && menu.updateActiveIndex()
},
//渲染el-menu时监听其展开菜单
watchOpenedMenus() {
//尝试取消之前设置的监听
if (this.watchOpenedMenusCallback) {
this.watchOpenedMenusCallback()
this.watchOpenedMenusCallback = null
}
const menu = this.$refs.menu
if (!menu) return
this.watchOpenedMenusCallback = menu.$watch('openedMenus', v => {
this.openedMenus = [...v]
})
}
}
}

@ -4,8 +4,8 @@ import {isMobile} from "@/util/browser"
import {createGetters, createMutations} from "@/util/observable"
const state = {
//区分pc和移动端(mobile)
device: isMobile() ? 'mobile' : 'pc',
//区分pc和移动端
isMobile: isMobile(),
//当前激活的顶部菜单的fullPath
activeRootMenu: '',

@ -33,25 +33,15 @@ export const getters = createGetters(store)
export const mutations = bindThis({
...createMutations(store),
/*折叠和自动隐藏不能同时启用*/
collapse(v) {
if (v) store.autoHide = false
store.collapse = v
},
autoHide(v) {
if (v) store.collapse = false
store.autoHide = v
},
/*移动端是打开关闭抽屉,桌面端是展开折叠*/
/*移动端或设置了侧边栏自动隐藏时打开关闭抽屉,否则展开折叠*/
open() {
if (appGetters.device === 'mobile') {
if (appGetters.isMobile || store.autoHide) {
store.show = true
}
else store.collapse = false
},
close() {
if (appGetters.device === 'mobile') {
if (appGetters.isMobile || store.autoHide) {
store.show = false
}
else store.collapse = true
@ -65,7 +55,7 @@ export const mutations = bindThis({
return this.close()
default :
let open = true
if (appGetters.device === 'mobile') {
if (appGetters.isMobile) {
open = !store.show
}
else open = store.collapse

@ -12,7 +12,7 @@ export function getSidebarMenus() {
if (!Array.isArray(menus)) {
return []
}
if (appGetters.device === 'mobile') {
if (appGetters.isMobile) {
return menus
}
switch (appGetters.navMode) {

@ -81,7 +81,7 @@ export default class Firework {
start() {
this.resizeObserver = new ResizeObserver(() => this.resize())
this.resizeObserver.observe(this.canvas)
document.addEventListener('click', this.addRocket)
window.addEventListener('click', this.addRocket)
this.timer = window.setInterval(this.addRocket, 1000)
this.resize()
this.loop()

@ -10,7 +10,7 @@
// Readable (more or less) version of the code
// ...
document.addEventListener("DOMContentLoaded", go)
window.addEventListener("DOMContentLoaded", go)
function go() {
"use strict"

@ -150,7 +150,7 @@ export default class ParticleNetwork {
this.onTouchStart = function (e) {
e.preventDefault()
setTimeout(function () {
window.setTimeout(function () {
if (!this.touchIsMoving) {
for (let i = 0; i < this.spawnQuantity; i++) {
this.particles.push(new Particle(this, e.changedTouches[0].clientX, e.changedTouches[0].clientY))

@ -56,7 +56,7 @@ export default class Waifu {
`
)
setTimeout(() => document.getElementById("waifu").style.bottom = '0', 0)
window.setTimeout(() => document.getElementById("waifu").style.bottom = '0', 0)
}
addEventListener() {
@ -190,7 +190,7 @@ export default class Waifu {
this.showMessage(result.hitokoto, 6000, 9)
setTimeout(() => this.showMessage(text, 4000, 9), 6000)
window.setTimeout(() => this.showMessage(text, 4000, 9), 6000)
})
}
@ -286,6 +286,6 @@ export default class Waifu {
const dom = document.getElementById("waifu")
dom.style.bottom = "-500px"
setTimeout(() => dom.remove && dom.remove(), 3000)
window.setTimeout(() => dom.remove && dom.remove(), 3000)
}
}

@ -16,7 +16,7 @@ import Router from 'vue-router'
import {route as routeConfig} from '@/config'
import {stringifyRoutes, parseRoutes, generateRoutes} from './util'
import defaultRoutes from './define'
import registerGuard from './guard'
import registerGuardian from './guardian'
Vue.use(Router)
@ -27,7 +27,7 @@ const router = new Router({
routes: defaultRoutes
})
registerGuard(router)
registerGuardian(router)
export default router

@ -1,23 +1,25 @@
const {body} = document
const MAX_MOBILE_WIDTH = 500
//根据body宽度判断是否为移动端,是则返回true
export function isMobile() {
const rect = body.getBoundingClientRect()
return rect.width - 1 < MAX_MOBILE_WIDTH
const rect = document.body.getBoundingClientRect()
return rect.width < 501
}
export function isDom(obj) {
return obj && typeof obj === 'object' && obj.nodeType === 1 && typeof obj.nodeName === 'string'
}
//获取元素的内宽度
export function getElementInnerWidth(ele) {
if (!ele) return 0
const style = window.getComputedStyle(ele)
return parseFloat(style.width) - (parseFloat(style.paddingLeft) + parseFloat(style.paddingRight))
}
export function getElementHeight(ele, style) {
if (!ele) return 0
let node = ele.cloneNode(true)
const node = ele.cloneNode(true)
node.style.opacity = '0'
if (style) {
@ -36,6 +38,7 @@ export function getElementHeight(ele, style) {
return height
}
//获取元素距离其容器的顶部距离
export function getOffsetTop(element, container) {
if (!element) return 0
@ -57,11 +60,10 @@ export function getOffsetTop(element, container) {
}
/**
* 加载js或css
*
* @desc 加载js或css
* @param url
* @param type js|css
* @returns {Promise<String>} url
* @returns {Promise<String>} 加载成功返回url若资源此前已加载返回undefined
*/
export function loadExternalResource(url, type = 'js') {
return new Promise((resolve, reject) => {
@ -92,45 +94,52 @@ export function loadExternalResource(url, type = 'js') {
})
}
export function scrollTo(y, options = {}) {
const raf = window.requestAnimationFrame
const {getContainer = () => window, callback, duration = 450} = options
/**
* @desc 平滑滚动至指定的位置
* @param el 滚动容器或可用于querySelector的字符串或一个返回DOM的函数
* @param position 滚动的目的地
* @param options
*/
export function scrollTo(el, position, options) {
const {
callback, //滚动完成的回调
duration = 300, //滚动耗时
direction = 'top' //滚动方向,top滚动至距元素顶部distance的位置,left滚动至距元素左边distance的位置
} = options || {}
if (typeof el === 'string') el = document.querySelector(el)
else if (typeof el === 'function') el = el()
if (!isDom(el)) return
const elPosition = getScroll(el, direction === 'top')
const scrollFunc = ((el, direction) => {
if (el === window) {
return direction === 'top'
? y => el.scrollTo(window.pageXOffset, window.pageYOffset + y)
: x => el.scrollTo(window.pageXOffset + x, window.pageYOffset)
}
return direction === 'top'
? y => el.scrollTop += y
: x => el.scrollLeft += x
})(el, direction)
const container = getContainer()
const scrollTop = getScroll(container, true)
const startTime = Date.now()
let times = duration / 16, distance = (position - elPosition) / (times + 1)
const frameFunc = () => {
const timestamp = Date.now()
const time = timestamp - startTime
const nextScrollTop = easeInOutCubic(time > duration ? duration : time, scrollTop, y, duration)
if (container === window) {
container.scrollTo(window.pageXOffset, nextScrollTop)
}
else {
container.scrollTop = nextScrollTop
}
if (time < duration) {
raf(frameFunc)
}
else if (typeof callback === 'function') {
callback()
if (times > 0) {
scrollFunc(distance)
times--
return window.requestAnimationFrame(frameFunc)
}
typeof callback === 'function' && callback()
}
raf(frameFunc)
}
export function getScroll(target, top) {
const prop = top ? 'pageYOffset' : 'pageXOffset'
const method = top ? 'scrollTop' : 'scrollLeft'
return target === window ? target[prop] : target[method]
return window.requestAnimationFrame(frameFunc)
}
export function easeInOutCubic(t, b, c, d) {
const cc = c - b
t /= d / 2
if (t < 1) {
return (cc / 2) * t * t * t + b
}
return (cc / 2) * ((t -= 2) * t * t + 2) + b
export function getScroll(el, top) {
return el === window
? el[top ? 'pageYOffset' : 'pageXOffset']
: el[top ? 'scrollTop' : 'scrollLeft']
}

@ -96,7 +96,7 @@ export function debounce(func, wait = 100, immediate = false) {
// 上次被包装函数被调用时间间隔 last 小于设定时间间隔 wait
if (last < wait && last > 0) {
timeout = setTimeout(later, wait - last)
timeout = window.setTimeout(later, wait - last)
}
else {
timeout = null
@ -114,7 +114,7 @@ export function debounce(func, wait = 100, immediate = false) {
timestamp = new Date().getTime()
const callNow = immediate && !timeout
// 如果延时不存在,重新设定延时
if (!timeout) timeout = setTimeout(later, wait)
if (!timeout) timeout = window.setTimeout(later, wait)
if (callNow) {
result = func.apply(context, args)
context = args = null
@ -141,7 +141,7 @@ export function throttle(func, delay = 100) {
clearTimeout(timeoutID)
if (elapsed > delay) exec()
else timeoutID = setTimeout(exec, delay - elapsed)
else timeoutID = window.setTimeout(exec, delay - elapsed)
}
return wrapper

@ -107,7 +107,7 @@ export default {
elSuccess('登陆成功')
const redirect = this.$route.query.redirect || '/'
//0.2s
setTimeout(() => this.$router.replace(redirect), 200)
window.setTimeout(() => this.$router.replace(redirect), 200)
},
thirdPartyLogin(channel) {
@ -119,11 +119,11 @@ export default {
},
addCapsLockEvent() {
document.addEventListener('keyup', this.capsLockTip)
window.addEventListener('keyup', this.capsLockTip)
},
removeEvent() {
document.removeEventListener('keyup', this.addCapsLockEvent)
window.removeEventListener('keyup', this.addCapsLockEvent)
}
},

@ -136,11 +136,11 @@ export default {
},
addEvent() {
document.addEventListener('keyup', this.capsLockTip)
window.addEventListener('keyup', this.capsLockTip)
},
removeEvent() {
document.removeEventListener('keyup', this.addCapsLockEvent)
window.removeEventListener('keyup', this.addCapsLockEvent)
}
},

@ -152,7 +152,7 @@ export default {
this.closeDialog()
this.needSearch && this.$emit('search')
setTimeout(() => this.clearForm(), 200)
window.setTimeout(() => this.clearForm(), 200)
this.loading = false
},

@ -27,7 +27,7 @@ export default {
computed: {
tabPosition() {
return appGetters.device === 'pc' ? 'left' : 'top'
return appGetters.isMobile ? 'top' : 'left'
},
tabContentClass() {
return this.tabPosition === 'left' ? 'tab-main-right' : 'tab-main-top'

@ -62,8 +62,8 @@ export default {
this.initPageY = event.pageY
this.oldMarginLeft = this.orgTreeOffsetLeft
this.oldMarginTop = this.orgTreeOffsetTop
document.addEventListener('mousemove', this.mousemoveView)
document.addEventListener('mouseup', this.mouseupView)
window.addEventListener('mousemove', this.mousemoveView)
window.addEventListener('mouseup', this.mouseupView)
},
mousemoveView(event) {
if (!this.canMove) return
@ -73,8 +73,8 @@ export default {
},
mouseupView() {
this.canMove = false
document.removeEventListener('mousemove', this.mousemoveView)
document.removeEventListener('mouseup', this.mouseupView)
window.removeEventListener('mousemove', this.mousemoveView)
window.removeEventListener('mouseup', this.mouseupView)
}
}
}

Loading…
Cancel
Save