侧边栏和导航栏增加了主题切换功能

修复了导航栏自适应时节点未更新的问题

修复了SettingDrawer由于layout设置变化而重新渲染的问题

导航菜单在没有icon时不会再渲染占位元素

ContextMenu表现形式修改:
- 使用v-show代替整个dom的移除
- 定位由absolute改为fixed

代码优化
master
toesbieya 6 years ago
parent a444d520d3
commit a042a94346
  1. 1
      vue/src/asset/icon/svg/develop.svg
  2. 16
      vue/src/asset/style/element-ui.scss
  3. 39
      vue/src/asset/style/variables.scss
  4. 8
      vue/src/component/menu/ContextMenu/index.vue
  5. 30
      vue/src/component/menu/NavMenu/index.vue
  6. 55
      vue/src/component/menu/NavMenu/item.vue
  7. 156
      vue/src/component/menu/NavMenu/style.scss
  8. 61
      vue/src/component/menu/NavMenu/theme-dark.scss
  9. 39
      vue/src/component/menu/NavMenu/theme-light.scss
  10. 17
      vue/src/layout/component/Aside/TwoPart/index.vue
  11. 53
      vue/src/layout/component/Aside/component/OnePartRoot.vue
  12. 57
      vue/src/layout/component/Aside/component/TwoPartRoot.vue
  13. 44
      vue/src/layout/component/Aside/component/TwoPartSub.vue
  14. 52
      vue/src/layout/component/Aside/index.vue
  15. 89
      vue/src/layout/component/Aside/style.scss
  16. 18
      vue/src/layout/component/Aside/theme-dark.scss
  17. 6
      vue/src/layout/component/Aside/theme-light.scss
  18. 12
      vue/src/layout/component/Logo.vue
  19. 1
      vue/src/layout/component/Navbar/component/Bell.vue
  20. 80
      vue/src/layout/component/Navbar/component/HeadMenu.vue
  21. 241
      vue/src/layout/component/Navbar/index.vue
  22. 49
      vue/src/layout/component/Navbar/style.scss
  23. 11
      vue/src/layout/component/Navbar/theme-dark.scss
  24. 11
      vue/src/layout/component/Navbar/theme-light.scss
  25. 46
      vue/src/layout/component/Page/component/TagsView/index.vue
  26. 23
      vue/src/layout/component/Page/component/TagsView/style.scss
  27. 3
      vue/src/layout/component/Page/index.vue
  28. 99
      vue/src/layout/component/SettingDrawer.vue
  29. 30
      vue/src/layout/index.vue
  30. 4
      vue/src/layout/store/app.js
  31. 3
      vue/src/layout/store/aside.js
  32. 13
      vue/src/layout/store/navbar.js
  33. 2
      vue/src/router/module/devRoot/index.js
  34. 6
      vue/src/util/browser.js
  35. 2
      vue/src/view/app/common/DetailPage/style.scss
  36. 2
      vue/src/view/example/cool/fluidPage.vue
  37. 1
      vue/src/view/index/component/PanelGroup/PanelGroupItem.vue
  38. 10
      vue/src/view/index/component/PanelGroup/index.vue
  39. 8
      vue/src/view/system/category/component/List.vue
  40. 5
      vue/src/view/system/department/indexPage.vue

@ -0,0 +1 @@
<svg class="icon" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="200" height="200"><defs><style/></defs><path d="M964.432 861.95c0 53.44-43.325 96.763-96.764 96.763H153.817c-53.44 0-96.764-43.323-96.764-96.763v-699.9c0-53.438 43.324-96.763 96.764-96.763h713.851c53.44 0 96.764 43.324 96.764 96.764v699.9zm-55.839-699.9c0-22.602-18.322-40.924-40.925-40.924H153.817c-22.603 0-40.925 18.322-40.925 40.925v699.9c0 22.602 18.322 40.923 40.925 40.923h713.851c22.603 0 40.925-18.321 40.925-40.924v-699.9z"/><path d="M931.196 274.682H79.656c-7.717 0-13.96-6.244-13.96-13.96 0-7.717 6.244-13.96 13.96-13.96h851.54c7.701 0 13.959 6.243 13.959 13.96 0 7.716-6.257 13.96-13.96 13.96zM177.373 204.884h-13.96c-7.715 0-13.96-6.244-13.96-13.96 0-7.715 6.245-13.96 13.96-13.96h13.96c7.703 0 13.96 6.245 13.96 13.96 0 7.716-6.257 13.96-13.96 13.96zM275.09 204.884h-13.96c-7.715 0-13.96-6.244-13.96-13.96 0-7.715 6.245-13.96 13.96-13.96h13.96c7.703 0 13.96 6.245 13.96 13.96 0 7.716-6.257 13.96-13.96 13.96zM372.808 204.884h-13.96c-7.716 0-13.96-6.244-13.96-13.96 0-7.715 6.244-13.96 13.96-13.96h13.96c7.703 0 13.96 6.245 13.96 13.96 0 7.716-6.257 13.96-13.96 13.96zM361.18 769.985c-4.062 0-8.097-1.77-10.85-5.153l-127.97-157.92a13.992 13.992 0 010-17.586l78.837-97.302c4.854-5.971 13.646-6.885 19.631-2.058 6 4.853 6.912 13.64 2.06 19.637l-71.708 88.503 120.838 149.139c4.854 5.997 3.926 14.777-2.059 19.63a13.882 13.882 0 01-8.778 3.11zM348.407 469.886a13.906 13.906 0 01-8.78-3.122c-5.985-4.854-6.912-13.646-2.06-19.631l12.761-15.733c4.867-5.985 13.646-6.898 19.631-2.045 5.985 4.853 6.912 13.646 2.06 19.63l-12.761 15.733a13.948 13.948 0 01-10.851 5.168zM663.631 769.985c-3.095 0-6.203-1.007-8.779-3.108-5.998-4.853-6.911-13.633-2.059-19.631l120.838-149.139-120.838-149.12c-4.852-5.998-3.94-14.791 2.059-19.645 5.958-4.839 14.764-3.926 19.631 2.06l127.955 157.925a13.96 13.96 0 010 17.587L674.482 764.83a13.998 13.998 0 01-10.851 5.154zM454.236 695.825a13.891 13.891 0 01-8.779-3.108c-5.985-4.855-6.911-13.633-2.059-19.631l127.969-157.92c4.852-5.97 13.646-6.898 19.63-2.071 5.985 4.852 6.912 13.659 2.06 19.658L465.087 690.67a13.98 13.98 0 01-10.851 5.155z"/></svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

@ -94,9 +94,10 @@
} }
.el-menu { .el-menu {
border: none;
height: 100%; height: 100%;
width: 100%; width: 100%;
border: none !important;
overflow: hidden;
&::-webkit-scrollbar { &::-webkit-scrollbar {
display: none; display: none;
@ -125,6 +126,11 @@
color: inherit; color: inherit;
} }
//原先垂直时右侧展开箭头偏上
.el-submenu__icon-arrow {
margin-top: -4px;
}
//弹出菜单 //弹出菜单
&.el-menu--popup { &.el-menu--popup {
max-height: 88vh; max-height: 88vh;
@ -138,6 +144,14 @@
padding: 0 20px; padding: 0 20px;
} }
} }
&--horizontal > .el-menu-item:not(.is-disabled):hover,
&--horizontal > .el-menu-item:not(.is-disabled):focus,
&--horizontal > .el-submenu .el-submenu__title:hover,
&--horizontal .el-menu .el-menu-item,
&--horizontal .el-menu .el-submenu__title {
background-color: inherit;
}
} }
.el-message { .el-message {

@ -1,4 +1,4 @@
//设定移动端的最大宽度 //设定移动端的最大宽度只能是number类型
$max-mobile-width: 500px; $max-mobile-width: 500px;
//覆盖element颜色 //覆盖element颜色
@ -47,29 +47,33 @@ $--font-path: '~element-ui/lib/theme-chalk/fonts';
} }
} }
//菜单中icon的大小
$menu-icon-size: 16px;
//菜单中icon和文字的距离 //菜单中icon和文字的距离
$menu-icon-text-gap: 10px; $menu-icon-text-gap: 10px;
//暗色菜单的背景色
//侧边菜单icon的大小 $menu-dark-background: #304156;
$aside-icon-size: 16px; //暗色子级菜单的背景色
//侧边栏菜单的背景色 $sub-menu-dark-background: #1f2d3d;
$aside-menu-background: #304156; //暗色主菜单的背景色用于双层侧边栏的主菜单和顶部导航栏
//侧边栏子菜单的背景色 $root-menu-dark-background: #001529;
$aside-sub-menu-background: #1f2d3d; //亮色菜单的文字颜色
//侧边栏菜单文字的颜色 $menu-text-light-color: #303133;
$aside-menu-item-color: hsla(0, 0%, 99.6%, .65); //亮色菜单的文字hover颜色
//侧边栏菜单文字的hover颜色 $menu-text-hover-light-color: $--color-primary;
$aside-menu-item-hover-color: #fefefe; //暗色菜单的文字颜色
$menu-text-dark-color: rgba(255, 255, 255, .65);
//顶部菜单文字的颜色 //暗色菜单的文字hover颜色
$head-menu-item-color: #303133; $menu-text-dark-hover-color: rgba(255, 255, 255, 1);
//顶部菜单文字的hover颜色
$head-menu-item-hover-color: $--color-primary;
//侧边栏宽度 //侧边栏宽度
$aside-width: 208px; $aside-width: 208px;
//侧边栏折叠时的宽度
$aside-collapse-width: $menu-icon-size + 20px * 2;
//头部导航栏高度 //头部导航栏高度
$nav-height: 48px; $nav-height: 48px;
//头部多页签栏高度 //头部多页签栏高度
$tags-view-height: 32px; $tags-view-height: 32px;
@ -86,6 +90,7 @@ $dialog-top: 100px;
// the :export directive is the magic sauce for webpack // the :export directive is the magic sauce for webpack
// https://www.bluematador.com/blog/how-to-share-variables-between-js-and-sass // https://www.bluematador.com/blog/how-to-share-variables-between-js-and-sass
:export { :export {
maxMobileWidth: $max-mobile-width;
primary: $--color-primary; primary: $--color-primary;
info: $--color-info; info: $--color-info;
success: $--color-success; success: $--color-success;

@ -70,12 +70,12 @@ export default {
}, },
render() { render() {
if (!this.value || !Array.isArray(this.items) || this.items.length === 0) return if (!Array.isArray(this.items)) return
const style = {left: this.realLeft, top: this.realTop} const style = {left: this.realLeft, top: this.realTop}
return ( return (
<ul class="context-menu" style={style}> <ul v-show={this.value} class="context-menu" style={style}>
{this.items.map(i => ( {this.items.map(i => (
i && <item on-click={i.click}>{i.content}</item> i && <item on-click={i.click}>{i.content}</item>
))} ))}
@ -90,9 +90,9 @@ export default {
.context-menu { .context-menu {
margin: 0; margin: 0;
background: $aside-menu-background; background: $menu-dark-background;
z-index: 10; z-index: 10;
position: absolute; position: fixed;
list-style-type: none; list-style-type: none;
padding: 5px 0; padding: 5px 0;
border-radius: 4px; border-radius: 4px;

@ -2,45 +2,63 @@
import Item from './item' import Item from './item'
export default { export default {
name: "NavMenu",
functional: true, functional: true,
props: { props: {
//
menus: Array, menus: Array,
//el-menu
mode: {type: String, default: 'vertical'}, mode: {type: String, default: 'vertical'},
//light dark
theme: {type: String, default: 'light'},
//
collapse: Boolean, collapse: Boolean,
//
showParentOnCollapse: Boolean, showParentOnCollapse: Boolean,
// <0
showIconMaxDepth: {type: Number, default: 1}, showIconMaxDepth: {type: Number, default: 1},
//menus使
switchTransition: Boolean, switchTransition: Boolean,
switchTransitionName: {type: String, default: 'sidebar'}
//menus
switchTransitionName: String
}, },
render(h, context) { render(h, context) {
const { const {
data, children, data, children,
props: { props: {
mode, menus, collapse, showParentOnCollapse, mode, menus, theme, collapse, showParentOnCollapse,
showIconMaxDepth, switchTransition, switchTransitionName showIconMaxDepth, switchTransition, switchTransitionName
} }
} = context } = context
const themeClassName = `el-menu--${theme}`
let items = children || menus.map(m => ( let items = children || menus.map(m => (
<Item <Item
menu={m} menu={m}
popper-class={themeClassName}
show-parent={showParentOnCollapse} show-parent={showParentOnCollapse}
collapse={collapse} collapse={collapse}
show-icon-max-depth={showIconMaxDepth} show-icon-max-depth={showIconMaxDepth}
/> />
)) ))
if (switchTransition) {
if (switchTransition && switchTransitionName) {
items = <transition-group name={switchTransitionName}>{items}</transition-group> items = <transition-group name={switchTransitionName}>{items}</transition-group>
} }
return ( return (
<el-menu <el-menu
ref="menu" ref="menu"
class={`el-menu--${mode}`} class={`el-menu--${mode} ${themeClassName}`}
mode={mode} mode={mode}
collapse={collapse} collapse={collapse}
collapse-transition={false} collapse-transition={false}

@ -2,29 +2,18 @@
const MenuItemContent = { const MenuItemContent = {
functional: true, functional: true,
props: {icon: String, title: String, useTitleAsIcon: Boolean}, props: {icon: String, title: String},
render(h, context) { render(h, context) {
const {icon, title, useTitleAsIcon} = context.props const {icon, title} = context.props
const iconComponent = return [<v-icon icon={icon}/>, <span slot="title" class="menu-item-content">{title}</span>]
useTitleAsIcon
? <em class="icon" style="font-style: normal">{title[0]}</em>
: icon
? <v-icon icon={icon}/>
: <span class="icon" style="display: none"/>
const vnodes = [iconComponent]
title && vnodes.push(<span slot="title" class="menu-item-content">{title}</span>)
return vnodes
} }
} }
//showIconMaxDepthdepth //showIconMaxDepthdepth
function getIcon({icon, showIconMaxDepth, depth}) { function getIcon({icon, showIconMaxDepth, depth}) {
if (!showIconMaxDepth || showIconMaxDepth < 0) { if (showIconMaxDepth == null || showIconMaxDepth < 0) {
return icon return icon
} }
return showIconMaxDepth < depth ? undefined : icon return showIconMaxDepth < depth ? undefined : icon
@ -41,20 +30,18 @@ function getOnlyChild(menu) {
return null return null
} }
function renderSingleMenu(h, {index, icon, title, collapse, depth}) { function renderSingleMenu(h, {index, icon, title}) {
const useTitleAsIcon = depth === 1 && collapse && !!!icon && !!title
return ( return (
<el-menu-item key={index} index={index}> <el-menu-item key={index} index={index}>
<MenuItemContent icon={icon} title={title} useTitleAsIcon={useTitleAsIcon}/> <MenuItemContent icon={icon} title={title}/>
</el-menu-item> </el-menu-item>
) )
} }
function renderSubMenu(h, {index, icon, title, children, collapse, depth}) { function renderSubMenu(h, {index, icon, title, popperClass, children}) {
const useTitleAsIcon = depth === 1 && collapse && !!!icon && !!title
return ( return (
<el-submenu key={index} index={index} popper-append-to-body> <el-submenu key={index} index={index} popper-class={popperClass}>
<MenuItemContent slot="title" icon={icon} title={title} useTitleAsIcon={useTitleAsIcon}/> <MenuItemContent slot="title" icon={icon} title={title}/>
{children} {children}
</el-submenu> </el-submenu>
) )
@ -69,7 +56,9 @@ function renderChildrenWithParentMenu(h, {icon, title, children}) {
] ]
} }
function renderMenu(h, {menu, showParent, collapse, showIconMaxDepth, depth = 1}) { function renderMenu(h, props) {
const {menu, popperClass, showParent, collapse, showIconMaxDepth, depth = 1} = props
const onlyOneChild = getOnlyChild(menu) const onlyOneChild = getOnlyChild(menu)
const showSingle = onlyOneChild && !onlyOneChild.children const showSingle = onlyOneChild && !onlyOneChild.children
@ -79,21 +68,13 @@ function renderMenu(h, {menu, showParent, collapse, showIconMaxDepth, depth = 1}
return renderSingleMenu(h, { return renderSingleMenu(h, {
index: fullPath, index: fullPath,
icon: getIcon({icon, showIconMaxDepth, depth}), icon: getIcon({icon, showIconMaxDepth, depth}),
title, title
collapse,
depth
}) })
} }
const {icon, title} = menu.meta const {icon, title} = menu.meta
let children = menu.children.map(child => renderMenu(h, { let children = menu.children.map(child => renderMenu(h, {...props, menu: child, depth: depth + 1}))
menu: child,
showParent,
collapse,
showIconMaxDepth,
depth: depth + 1
}))
//<el-scrollbar>mouseleavemouseleave //<el-scrollbar>mouseleavemouseleave
if (collapse) { if (collapse) {
@ -107,9 +88,8 @@ function renderMenu(h, {menu, showParent, collapse, showIconMaxDepth, depth = 1}
index: menu.fullPath, index: menu.fullPath,
icon: getIcon({icon, showIconMaxDepth, depth}), icon: getIcon({icon, showIconMaxDepth, depth}),
title, title,
children, popperClass,
collapse, children
depth
}) })
} }
@ -120,6 +100,9 @@ export default {
// //
menu: Object, menu: Object,
//
popperClass: String,
// //
showParent: Boolean, showParent: Boolean,

@ -1,15 +1,27 @@
@import "~@/asset/style/variables.scss"; @import "~@/asset/style/variables.scss";
//设置菜单图标的大小和与文字的间隔
.el-menu {
.icon {
font-size: $menu-icon-size;
}
.icon + .menu-item-content {
margin-left: $menu-icon-text-gap;
}
}
//原始样式使用 '>' 可能导致侧边栏折叠时还会显示右侧箭头图标 //原始样式使用 '>' 可能导致侧边栏折叠时还会显示右侧箭头图标
//此样式只对垂直状态生效 //此样式只对垂直状态生效
.el-menu--collapse .el-submenu .el-submenu__title .el-submenu__icon-arrow { .el-menu--collapse .el-submenu .el-submenu__title .el-submenu__icon-arrow {
display: none; display: none;
} }
//hover时菜单左侧图标放大 //hover时菜单左侧图标放大弹出菜单的父级不放大
.el-menu-item, .el-menu-item:not(.popover-menu__title),
.el-submenu__title { .el-submenu__title {
> .icon, > .el-tooltip > .icon { > .icon,
> .el-tooltip > .icon {
transition: transform .1s; transition: transform .1s;
} }
@ -19,111 +31,71 @@
} }
} }
/*-------侧边栏垂直菜单开始---------*/ //侧边栏垂直菜单
.el-menu--vertical.el-menu { .el-menu--vertical {
overflow-y: auto; &.el-menu {
} overflow-y: auto;
.el-menu--vertical .el-menu,
.el-menu--vertical.el-menu {
background-color: $aside-menu-background;
.icon {
font-size: $aside-icon-size;
}
.icon + .menu-item-content {
margin-left: $menu-icon-text-gap;
} }
//菜单的文字以及hover颜色 &.el-menu,
.el-menu-item, .el-menu {
.el-submenu__title { //菜单的文字以及hover颜色
color: $aside-menu-item-color; .el-menu-item,
.el-submenu__title {
&:hover { //菜单文字的最大宽度
color: $aside-menu-item-hover-color; > .menu-item-content {
//侧边栏宽度菜单左右padding左侧图标宽度距离左右的距离右侧图标宽度
max-width: $aside-width - 20px * 2 - $menu-icon-size - $menu-icon-text-gap * 2 - 12px;
}
} }
//菜单文字的最大宽度 //弹出菜单的父级
> .menu-item-content { &.el-menu--popup .popover-menu__title {
//侧边栏宽度菜单左右padding左侧图标宽度距离左右的距离右侧图标宽度 cursor: auto;
max-width: $aside-width - 20px * 2 - $aside-icon-size - $menu-icon-text-gap * 2 - 12px; border-bottom-width: 1px;
border-bottom-style: solid;
} }
} }
//子级菜单的背景颜色
&.el-menu--inline,
.el-submenu .el-submenu > .el-submenu__title {
background-color: $aside-sub-menu-background;
}
//子级菜单内阴影
&.el-menu--inline {
box-shadow: inset 0 2px 8px rgba(0, 0, 0, .45)
}
//设置了显示弹出菜单的父级
&.el-menu--popup .popover-menu__title {
cursor: auto;
color: $aside-menu-item-hover-color;
border-bottom: 1px solid $aside-sub-menu-background;
}
} }
/*-------侧边栏垂直菜单结束---------*/ //顶部水平菜单
.el-menu--horizontal {
/*-------顶部水平菜单开始-----------*/ //顶部菜单的顶级节点
.el-menu--horizontal .el-menu, &.el-menu {
.el-menu--horizontal.el-menu { //不显示折叠展开按钮
font-size: 14px; > .el-submenu > .el-submenu__title > .el-submenu__icon-arrow {
display: none;
}
.icon { //顶级节点的高度以及hover颜色
font-size: 14px; > .el-menu-item,
} > .el-submenu > .el-submenu__title {
height: $nav-height;
line-height: $nav-height;
//图标和文字的间隔 &:hover {
.icon + .menu-item-content { border-bottom: 2px solid $--color-primary;
margin-left: $menu-icon-text-gap; }
}
} }
//菜单的文字以及hover颜色 &.el-menu,
.el-menu-item, .el-menu {
.el-submenu__title { //菜单的文字以及hover颜色
color: $head-menu-item-color; .el-menu-item,
.el-submenu__title {
&:hover { > span {
color: $head-menu-item-hover-color; max-width: 100px;
}
} }
> span { //submenu的右侧箭头会稍微偏上不懂为啥
max-width: 100px; .el-submenu__icon-arrow {
margin-top: -4px;
} }
} }
//submenu的右侧箭头会稍微偏上不懂为啥
.el-submenu__icon-arrow {
margin-top: -4px;
}
} }
//顶部菜单的顶级节点
.el-menu--horizontal.el-menu {
//不显示折叠展开按钮
> .el-submenu > .el-submenu__title > .el-submenu__icon-arrow {
display: none;
}
//顶级节点的高度以及hover颜色
> .el-menu-item,
> .el-submenu > .el-submenu__title {
height: $nav-height;
line-height: $nav-height;
&:hover {
border-bottom: 2px solid $head-menu-item-hover-color;
}
}
}
/*-------顶部水平菜单结束---------*/ @import "./theme-light";
@import "./theme-dark";

@ -0,0 +1,61 @@
.el-menu--dark {
//侧边栏垂直菜单
&.el-menu--vertical {
&.el-menu,
.el-menu {
background-color: $menu-dark-background;
//菜单的文字以及hover颜色
.el-menu-item,
.el-submenu__title {
color: $menu-text-dark-color;
&:hover {
color: $menu-text-dark-hover-color;
}
}
//子级菜单的背景颜色
&.el-menu--inline,
.el-submenu .el-submenu > .el-submenu__title {
background-color: $sub-menu-dark-background;
}
//子级菜单内阴影
&.el-menu--inline {
box-shadow: inset 0 2px 8px rgba(0, 0, 0, .45)
}
//设置了显示弹出菜单的父级
&.el-menu--popup .popover-menu__title {
color: $menu-text-dark-hover-color;
border-bottom-color: #101117;
}
}
}
//顶部水平菜单
&.el-menu--horizontal {
&.el-menu,
.el-menu {
background-color: $root-menu-dark-background;
//菜单的文字以及hover颜色
.el-menu-item,
.el-submenu__title {
color: $menu-text-dark-color;
&:hover {
color: $menu-text-dark-hover-color;
}
}
}
&.el-menu > .el-menu-item,
&.el-menu > .el-submenu > .el-submenu__title,
&.el-menu > .el-menu-item:hover,
&.el-menu > .el-submenu > .el-submenu__title:hover {
border-bottom: none;
}
}
}

@ -0,0 +1,39 @@
.el-menu--light {
//侧边栏垂直菜单
&.el-menu--vertical {
&.el-menu,
.el-menu {
//菜单的文字以及hover颜色
.el-menu-item,
.el-submenu__title {
color: $menu-text-light-color;
&:hover {
color: $menu-text-hover-light-color;
}
}
//弹出菜单的父级
&.el-menu--popup .popover-menu__title {
color: $menu-text-hover-light-color;
border-bottom-color: #dcdfe6;
}
}
}
//顶部水平菜单
&.el-menu--horizontal {
&.el-menu,
.el-menu {
//菜单的文字以及hover颜色
.el-menu-item,
.el-submenu__title {
color: $menu-text-light-color;
&:hover {
color: $menu-text-hover-light-color;
}
}
}
}
}

@ -1,17 +0,0 @@
<script type="text/jsx">
import RootSidebar from './root'
import SubSidebar from './sub'
export default {
functional: true,
render() {
return (
<aside class="aside">
<RootSidebar/>
<SubSidebar/>
</aside>
)
}
}
</script>

@ -32,6 +32,12 @@ export default {
renderInDrawer() { renderInDrawer() {
return this.isMobile || asideGetters.autoHide return this.isMobile || asideGetters.autoHide
}, },
//el-drawer
drawerClass() {
return this.isMobile || pageGetters.position !== 'top-bottom'
? undefined
: 'drawer-behind-header'
},
//truefalse //truefalse
show() { show() {
@ -74,6 +80,15 @@ export default {
//logo //logo
showLogo() { showLogo() {
return pageGetters.showLogo && (this.isMobile || pageGetters.position === 'left-right') return pageGetters.showLogo && (this.isMobile || pageGetters.position === 'left-right')
},
//使
switchTransition() {
return !this.isMobile && appGetters.navMode !== 'aside'
},
className() {
return {'sidebar': true, 'collapse': this.collapse}
} }
}, },
@ -165,22 +180,24 @@ export default {
if (this.menus.length <= 0) return if (this.menus.length <= 0) return
const aside = ( const aside = (
<aside <div
class={{'aside': true, 'sidebar': true, 'collapse': this.collapse}} class={this.className}
on-mouseleave={() => this.mouseOutside = true} on-mouseleave={() => this.mouseOutside = true}
on-mouseenter={() => this.mouseOutside = false} on-mouseenter={() => this.mouseOutside = false}
> >
{this.showLogo && <logo show-title={!this.collapse}/>} {this.showLogo && <logo show-title={!this.collapse}/>}
<nav-menu <nav-menu
menus={this.menus} menus={this.menus}
theme={asideGetters.theme}
collapse={this.collapse} collapse={this.collapse}
default-active={this.activeMenu} default-active={this.activeMenu}
unique-opened={asideGetters.uniqueOpen} unique-opened={asideGetters.uniqueOpen}
show-parent={asideGetters.showParentOnCollapse} show-parent-on-collapse={asideGetters.showParentOnCollapse}
switch-transition switch-transition={this.switchTransition}
switch-transition-name="sidebar"
on={{'select': this.onSelect, 'hook:mounted': this.watchOpenedMenus}} on={{'select': this.onSelect, 'hook:mounted': this.watchOpenedMenus}}
/> />
</aside> </div>
) )
if (this.renderInDrawer) { if (this.renderInDrawer) {
@ -188,6 +205,7 @@ export default {
<el-drawer <el-drawer
visible={this.show} visible={this.show}
with-header={false} with-header={false}
custom-class={this.drawerClass}
modal={this.isMobile} //使 modal={this.isMobile} //使
direction="ltr" direction="ltr"
size="auto" size="auto"
@ -202,28 +220,3 @@ export default {
} }
} }
</script> </script>
<style lang="scss">
@import "~@/asset/style/variables.scss";
.sidebar {
display: flex;
flex-direction: column;
width: $aside-width;
background-color: $aside-menu-background;
font-size: 0;
height: 100vh;
z-index: 10;
box-shadow: 2px 0 6px rgba(0, 21, 41, 0.35);
transition: all 0.2s ease-in-out;
//
&.collapse {
width: $aside-icon-size + 20px * 2;
.menu-item-content {
display: none;
}
}
}
</style>

@ -1,6 +1,7 @@
<script type="text/jsx"> <script type="text/jsx">
import menuMixin from "@/layout/mixin/menu" import menuMixin from "@/layout/mixin/menu"
import {getters as appGetters, mutations as appMutations} from "@/layout/store/app" import {getters as appGetters, mutations as appMutations} from "@/layout/store/app"
import {getters as asideGetters} from "@/layout/store/aside"
import {getters as pageGetters} from "@/layout/store/page" import {getters as pageGetters} from "@/layout/store/page"
import Logo from '@/layout/component/Logo' import Logo from '@/layout/component/Logo'
import {scrollIntoViewVertically} from "@/util/browser" import {scrollIntoViewVertically} from "@/util/browser"
@ -18,17 +19,12 @@ const Item = {
const {title, icon, active} = context.props const {title, icon, active} = context.props
const {click} = context.listeners const {click} = context.listeners
const iconComponent =
icon
? <v-icon icon={icon}/>
: <em class="icon" style="font-style: normal">{title[0]}</em>
return ( return (
<li <li
class={{'el-menu-item': true, 'is-active': active}} class={{'el-menu-item': true, 'is-active': active}}
on-click={click} on-click={click}
> >
{iconComponent} <v-icon icon={icon}/>
<span class="menu-item-content">{title}</span> <span class="menu-item-content">{title}</span>
</li> </li>
) )
@ -57,6 +53,14 @@ export default {
//logo //logo
showLogo() { showLogo() {
return pageGetters.showLogo && pageGetters.position === 'left-right' return pageGetters.showLogo && pageGetters.position === 'left-right'
},
containerClass() {
return {'root-sidebar': true, 'collapse': this.mouseOutside}
},
menuClass() {
return `el-menu el-menu--vertical el-menu--${asideGetters.theme}`
} }
}, },
@ -107,12 +111,12 @@ export default {
return ( return (
<div class="root-sidebar-container"> <div class="root-sidebar-container">
<div <div
class={{'root-sidebar': true, 'collapse': this.mouseOutside}} class={this.containerClass}
on-mouseleave={() => this.mouseOutside = true} on-mouseleave={() => this.mouseOutside = true}
on-mouseenter={() => this.mouseOutside = false} on-mouseenter={() => this.mouseOutside = false}
> >
{this.showLogo && <logo show-title={!this.mouseOutside}/>} {this.showLogo && <logo show-title={!this.mouseOutside}/>}
<ul class="el-menu el-menu--vertical"> <ul class={this.menuClass}>
{this.menus.map(({fullPath, meta: {title, icon}}) => ( {this.menus.map(({fullPath, meta: {title, icon}}) => (
<item <item
title={title} title={title}
@ -128,40 +132,3 @@ export default {
} }
} }
</script> </script>
<style lang="scss">
@import "~@/asset/style/variables.scss";
$background-color: #1d2935;
.root-sidebar-container {
width: $aside-icon-size + 20px * 2;
position: relative;
}
.root-sidebar {
display: flex;
flex-direction: column;
position: absolute;
height: 100vh;
width: $aside-width;
background-color: $background-color;
overflow: hidden;
z-index: 11;
border-right: 1px solid #101117;
transition: all 0.2s ease-in-out;
> .el-menu--vertical.el-menu {
background-color: $background-color;
}
//
&.collapse {
width: $aside-icon-size + 20px * 2;
.menu-item-content {
display: none;
}
}
}
</style>

@ -27,6 +27,10 @@ export default {
collapse() { collapse() {
return asideGetters.collapse && !asideGetters.isMobile return asideGetters.collapse && !asideGetters.isMobile
}, },
className() {
return {'sub-sidebar': true, 'collapse': this.collapse}
}
}, },
watch: { watch: {
@ -77,17 +81,19 @@ export default {
if (this.menus.length <= 0) return if (this.menus.length <= 0) return
return ( return (
<div class={{'sub-sidebar': true, 'collapse': this.collapse}}> <div class={this.className}>
<div class="sub-sidebar-title"> <div class="sub-sidebar-title">
{this.rootMenu && this.rootMenu.meta.title} {this.rootMenu && this.rootMenu.meta.title}
</div> </div>
<nav-menu <nav-menu
menus={this.menus} menus={this.menus}
theme={asideGetters.theme}
collapse={this.collapse} collapse={this.collapse}
default-active={this.activeMenu} default-active={this.activeMenu}
unique-opened={asideGetters.uniqueOpen} unique-opened={asideGetters.uniqueOpen}
show-parent={asideGetters.showParentOnCollapse} show-parent-on-collapse={asideGetters.showParentOnCollapse}
switch-transition switch-transition
switch-transition-name="sidebar"
on-select={this.onSelect} on-select={this.onSelect}
/> />
</div> </div>
@ -95,37 +101,3 @@ export default {
} }
} }
</script> </script>
<style lang="scss">
@import "~@/asset/style/variables.scss";
.sub-sidebar {
display: flex;
flex-direction: column;
width: $aside-width;
background-color: $aside-menu-background;
font-size: 0;
height: 100vh;
z-index: 10;
box-shadow: 2px 0 6px rgba(0, 21, 41, 0.35);
transition: all 0.2s ease-in-out;
//
&.collapse {
width: $aside-icon-size + 20px * 2;
.sub-sidebar-title,
.menu-item-content {
display: none;
}
}
&-title {
height: $nav-height;
line-height: $nav-height;
padding-left: 20px;
color: #ffffff;
font-size: 16px;
}
}
</style>

@ -1,48 +1,34 @@
<script type="text/jsx"> <script type="text/jsx">
import OnePart from './OnePart' import OnePart from './component/OnePartRoot'
import TwoPart from './TwoPart' import TwoPartRoot from './component/TwoPartRoot'
import TwoPartSub from './component/TwoPartSub'
import {getters as appGetters} from "@/layout/store/app" import {getters as appGetters} from "@/layout/store/app"
import {getters as asideGetters} from "@/layout/store/aside"
export default { export default {
functional: true, functional: true,
render() { render() {
let children
//使 //使
if (appGetters.isMobile) { if (appGetters.isMobile) {
return <OnePart/> children = <OnePart/>
} }
else {
switch (appGetters.navMode) { switch (appGetters.navMode) {
case 'mix': case 'mix':
case 'aside': case 'aside':
return <OnePart/> children = <OnePart/>
case 'aside-two-part': break
return <TwoPart/> case 'aside-two-part':
children = [<TwoPartRoot/>, <TwoPartSub/>]
}
} }
return <aside class={`aside ${asideGetters.theme}`}>{children}</aside>
} }
} }
</script> </script>
<style lang="scss"> <style lang="scss" src="./style.scss"></style>
.aside {
display: flex;
position: relative;
}
/* 侧边栏切换动画开始 */
.sidebar-enter-active {
transition: all 0.2s;
}
.sidebar-enter,
.sidebar-leave-active {
opacity: 0;
transform: translateY(30px) skewY(10deg)
}
.sidebar-leave-active {
position: absolute;
}
/* 侧边栏切换动画结束 */
</style>

@ -0,0 +1,89 @@
@import "~@/asset/style/variables.scss";
.aside {
display: flex;
z-index: 11;
//移动端下侧边栏抽屉不能设置z-index否则会被modal遮住
@include mobile {
z-index: auto;
}
//单层和双层侧边栏
.sidebar,
.root-sidebar,
.sub-sidebar {
display: flex;
flex-direction: column;
height: 100%;
width: $aside-width;
box-shadow: 2px 0 6px rgba(0, 21, 41, 0.35);
transition: all 0.2s;
//折叠状态
&.collapse {
width: $aside-collapse-width;
//隐藏菜单文字
.menu-item-content,
.sub-sidebar-title {
display: none;
}
}
}
}
//页面为上下结构时侧边栏的抽屉模式需要扣除导航栏的高度
.el-drawer.drawer-behind-header {
top: $nav-height;
}
/* 双侧侧边栏开始 */
.root-sidebar-container {
position: relative;
width: $aside-collapse-width;
height: 100%;
}
.root-sidebar {
position: absolute;
overflow: hidden;
z-index: 1;
box-shadow: none !important;
border-right-width: 1px;
border-right-style: solid;
}
.sub-sidebar-title {
height: $nav-height;
line-height: $nav-height;
padding-left: 20px;
font-size: 16px;
}
/* 双侧侧边栏结束 */
/* 侧边栏切换动画开始 */
.sidebar-enter-active {
transition: all 0.2s;
}
.sidebar-enter,
.sidebar-leave-active {
opacity: 0;
transform: translateY(30px) skewY(10deg)
}
.sidebar-leave-active {
position: absolute;
}
/* 侧边栏切换动画结束 */
@import "theme-light";
@import "theme-dark";

@ -0,0 +1,18 @@
.dark {
&.aside {
background-color: $menu-dark-background;
}
.root-sidebar,
.root-sidebar > .el-menu--vertical.el-menu {
background-color: $root-menu-dark-background;
}
.root-sidebar {
border-right-color: #101117;
}
.sub-sidebar-title {
color: #ffffff;
}
}

@ -0,0 +1,6 @@
.light {
.root-sidebar {
background-color: #ffffff;
border-right-color: #dcdfe6;
}
}

@ -31,7 +31,6 @@ export default {
$logoSize: 30px; $logoSize: 30px;
.logo-container { .logo-container {
position: relative;
width: 100%; width: 100%;
height: $nav-height; height: $nav-height;
line-height: $nav-height; line-height: $nav-height;
@ -54,17 +53,20 @@ $logoSize: 30px;
> h1 { > h1 {
display: inline-block; display: inline-block;
margin: 0; margin: 0;
color: #ffffff; color: $--color-primary;
font-weight: 600; font-weight: 600;
line-height: $nav-height; line-height: $nav-height;
font-size: 14px; font-size: 18px;
font-family: Avenir, Helvetica Neue, Arial, Helvetica, sans-serif;
vertical-align: middle; vertical-align: middle;
} }
} }
&.no-title { &.no-title {
padding-left: ($aside-icon-size + 20px * 2 - $logoSize) / 2; padding-left: ($aside-collapse-width - $logoSize) / 2;
} }
} }
.dark .logo-container .logo-link > h1 {
color: #ffffff;
}
</style> </style>

@ -36,5 +36,6 @@ export default {
.bell-badge .el-badge__content { .bell-badge .el-badge__content {
top: 15px !important; top: 15px !important;
right: 10px !important; right: 10px !important;
border: none;
} }
</style> </style>

@ -1,7 +1,6 @@
<script type="text/jsx"> <script type="text/jsx">
/** /**
* 顶部菜单参考了ant design的响应式设计 * 顶部菜单参考了ant design的响应式设计
* TODO 菜单隐藏后再显示出来存在BUG怀疑是组件复用的问题
*/ */
import menuMixin from "@/layout/mixin/menu" import menuMixin from "@/layout/mixin/menu"
import {getters as appGetters, mutations as appMutations} from "@/layout/store/app" import {getters as appGetters, mutations as appMutations} from "@/layout/store/app"
@ -18,14 +17,20 @@ export default {
props: { props: {
// //
alwaysShow: Boolean alwaysShow: Boolean,
//
theme: String
}, },
data() { data() {
return { return {
// //
//undefined-1 //undefined-1
lastVisibleIndex: undefined lastVisibleIndex: undefined,
//el-submenukey
seed: 0
} }
}, },
@ -42,6 +47,30 @@ export default {
case 'mix': case 'mix':
return appGetters.menus.map(root => ({...root, children: undefined})) return appGetters.menus.map(root => ({...root, children: undefined}))
} }
},
//
realMenus() {
const {lastVisibleIndex, menus} = this
//
if (lastVisibleIndex === undefined) {
return menus
}
const fullPath = `_${this.seed}`
//
if (lastVisibleIndex === -1) {
return [{fullPath, meta: {icon: 'el-icon-menu'}, children: menus}]
}
const visible = menus.slice(0, lastVisibleIndex + 1)
const hidden = menus.slice(lastVisibleIndex + 1)
visible.push({fullPath, meta: {title: '...'}, children: hidden})
return visible
} }
}, },
@ -65,6 +94,11 @@ export default {
handler(mode) { handler(mode) {
this.setActiveMenu(mode) this.setActiveMenu(mode)
} }
},
//
lastVisibleIndex() {
this.seed++
} }
}, },
@ -146,22 +180,6 @@ export default {
this.$once('hook:beforeDestroy', () => { this.$once('hook:beforeDestroy', () => {
this.resizeObserver && this.resizeObserver.disconnect() this.resizeObserver && this.resizeObserver.disconnect()
}) })
},
renderVisibleMenus(visibleMenus) {
return visibleMenus.map(m => <nav-menu-item menu={m} show-icon-max-depth={1}/>)
},
renderHiddenMenus(hiddenMenus, hideAll) {
if (hiddenMenus.length <= 0) return
const content = hideAll
? <v-icon slot="title" icon={'el-icon-menu'}/>
: <span slot="title" class="menu-item-content">{'...'}</span>
const children = hiddenMenus.map(m => <nav-menu-item menu={m} show-icon-max-depth={0}/>)
children.unshift(content)
return <el-submenu index={'_'}>{children}</el-submenu>
} }
}, },
@ -171,34 +189,18 @@ export default {
}, },
render() { render() {
const {lastVisibleIndex, menus} = this if (this.menus.length <= 0 || this.menus.length === 1 && !this.alwaysShow) {
if (!Array.isArray(menus) || menus.length <= 0 || menus.length === 1 && !this.alwaysShow) {
return return
} }
let visibleMenus = menus, hiddenMenus = []
if (lastVisibleIndex !== undefined) {
//
if (lastVisibleIndex === -1) {
visibleMenus = []
hiddenMenus = menus
}
//
else if (lastVisibleIndex !== menus.length - 1) {
visibleMenus = menus.slice(0, lastVisibleIndex + 1)
hiddenMenus = menus.slice(lastVisibleIndex + 1)
}
}
return ( return (
<nav-menu <nav-menu
menus={this.realMenus}
mode="horizontal" mode="horizontal"
theme={this.theme}
default-active={this.activeMenu} default-active={this.activeMenu}
on-select={this.onSelect} on-select={this.onSelect}
> />
{this.renderVisibleMenus(visibleMenus)}
{this.renderHiddenMenus(hiddenMenus, hiddenMenus.length === menus.length)}
</nav-menu>
) )
} }
} }

@ -5,9 +5,9 @@ import Bell from "./component/Bell"
import Hamburger from './component/Hamburger' import Hamburger from './component/Hamburger'
import HeadMenu from "./component/HeadMenu" import HeadMenu from "./component/HeadMenu"
import Logo from "@/layout/component/Logo" import Logo from "@/layout/component/Logo"
import SettingDrawer from '@/layout/component/SettingDrawer' import {getters as appGetters, mutations as appMutations} from "@/layout/store/app"
import {getters as appGetters} from "@/layout/store/app"
import {getters as asideGetters, mutations as asideMutations} from "@/layout/store/aside" import {getters as asideGetters, mutations as asideMutations} from "@/layout/store/aside"
import {getters as navbarGetters} from "@/layout/store/navbar"
import {getters as pageGetters} from "@/layout/store/page" import {getters as pageGetters} from "@/layout/store/page"
import {getSidebarMenus} from "@/layout/util" import {getSidebarMenus} from "@/layout/util"
import {elConfirm} from "@/util/message" import {elConfirm} from "@/util/message"
@ -18,11 +18,51 @@ export default {
mixins: [GuideMixin.navbar], mixins: [GuideMixin.navbar],
components: {Bell, Hamburger, HeadMenu, Logo, SettingDrawer}, components: {Bell, Hamburger, HeadMenu, Logo},
data: () => ({settingDrawer: false}), computed: {
...mapState('user', ['avatar', 'name', 'prepareLogout']),
computed: mapState('user', ['avatar', 'name', 'prepareLogout']), //logo
//
//logo
//
renderLogo() {
return !appGetters.isMobile
&& pageGetters.showLogo
&& (appGetters.navMode === 'head' || pageGetters.position === 'top-bottom')
},
//
//
//
//
//
renderHamburger() {
const hasSidebarMenus = getSidebarMenus().length > 0,
isMobile = appGetters.isMobile,
correctMode =
['aside', 'mix'].includes(appGetters.navMode) && !asideGetters.autoHide
|| appGetters.navMode === 'aside-two-part'
return hasSidebarMenus && (isMobile || correctMode)
},
//
isHamburgerActive() {
return appGetters.isMobile ? asideGetters.show : !asideGetters.collapse
},
//
//
//
renderHeadMenu() {
return !appGetters.isMobile && ['head', 'mix'].includes(appGetters.navMode)
},
className() {
return `navbar ${navbarGetters.theme}`
}
},
methods: { methods: {
command(command) { command(command) {
@ -40,161 +80,74 @@ export default {
} }
}, },
renderLogo() { clickRefreshBtn() {
const render = refreshPage()
!appGetters.isMobile
&& pageGetters.showLogo
&& pageGetters.position === 'top-bottom'
if (render) return <logo show-title/>
}, },
renderHamburger() {
//
//
//
//
//
const hasSidebarMenus = getSidebarMenus().length > 0,
isMobile = appGetters.isMobile,
correctMode =
['aside', 'mix'].includes(appGetters.navMode) && !asideGetters.autoHide
|| appGetters.navMode === 'aside-two-part',
renderHamburger = hasSidebarMenus && (isMobile || correctMode)
if (renderHamburger) { openSettingDrawer() {
const active = isMobile ? asideGetters.show : !asideGetters.collapse appMutations.showSettingDrawer(true)
return <hamburger class="navbar-item" active={active} on-click={() => asideMutations.switch()}/>
}
},
renderHeadMenuMenu() {
if (['head', 'mix'].includes(appGetters.navMode) && !appGetters.isMobile) {
return <head-menu always-show/>
}
},
renderActionButtons() {
return [
<bell class="navbar-item"/>,
<div class="navbar-item" title="刷新" on-click={() => refreshPage()}>
<i class="el-icon-refresh-right navbar-icon"/>
</div>,
<div class="setting-btn navbar-item"
title="个性设置"
on-click={() => this.settingDrawer = true}
>
<i class="el-icon-s-operation navbar-icon"/>
</div>
]
},
renderUserButton() {
return (
<el-dropdown
class="navbar-item"
trigger="click"
on-command={this.command}
>
<div class="avatar-wrapper">
<el-avatar size={30} src={this.avatar} icon="el-icon-user-solid"/>
<span class="hidden-xs">{this.name}</span>
</div>
<el-dropdown-menu class="user-dropdown" slot="dropdown">
<el-dropdown-item icon="el-icon-user" command="user">
个人中心
</el-dropdown-item>
<el-dropdown-item class="hidden-xs" icon="el-icon-guide" command="guide">
新手指引
</el-dropdown-item>
<el-dropdown-item divided icon="el-icon-switch-button" command="logout">
退出登录
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
)
} }
}, },
render() { render() {
return ( return (
<nav class="navbar"> <nav class={this.className}>
{this.renderLogo()} {this.renderLogo && <logo show-title/>}
<div style="flex: 1"> <div style="flex: 1">
{this.renderHamburger()} {
{this.renderHeadMenuMenu()} this.renderHamburger &&
<hamburger
class="navbar-item"
active={this.isHamburgerActive}
on-click={asideMutations.switch}
/>
}
{this.renderHeadMenu && <head-menu always-show theme={navbarGetters.theme}/>}
</div> </div>
<div> <div>
{this.renderActionButtons()} <bell class="navbar-item"/>
{this.renderUserButton()}
</div> <div
class="navbar-item"
title="刷新"
on-click={this.clickRefreshBtn}
>
<i class="el-icon-refresh-right navbar-icon"/>
</div>
<setting-drawer v-model={this.settingDrawer}/> <div
class="setting-btn navbar-item"
title="个性设置"
on-click={this.openSettingDrawer}
>
<i class="el-icon-s-operation navbar-icon"/>
</div>
<el-dropdown class="navbar-item" on-command={this.command}>
<div class="avatar-wrapper">
<el-avatar size={30} src={this.avatar} icon="el-icon-user-solid"/>
<span class="hidden-xs">{this.name}</span>
</div>
<el-dropdown-menu class="user-dropdown" slot="dropdown">
<el-dropdown-item icon="el-icon-user" command="user">
个人中心
</el-dropdown-item>
<el-dropdown-item class="hidden-xs" icon="el-icon-guide" command="guide">
新手指引
</el-dropdown-item>
<el-dropdown-item divided icon="el-icon-switch-button" command="logout">
退出登录
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</div>
</nav> </nav>
) )
} }
} }
</script> </script>
<style lang="scss"> <style lang="scss" src="./style.scss"></style>
@import "~@/asset/style/variables.scss";
.navbar {
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: nowrap;
height: $nav-height;
padding-left: 12px;
transition: height .3s ease-in-out;
position: relative;
z-index: 9;
background-color: #ffffff;
box-shadow: 0 1px 4px rgba(0, 21, 41, .08);
> div {
display: flex;
flex-wrap: nowrap;
height: 100%;
}
.navbar-icon {
font-size: 18px;
}
.navbar-item {
display: flex;
align-items: center;
padding: 0 8px;
color: #5a5e66;
transition: background .3s;
cursor: pointer;
&:hover {
background: rgba(0, 0, 0, .025)
}
}
.logo-container {
width: auto;
min-width: 192px;
padding-left: 0;
> .logo-link > h1 {
font-weight: 400;
color: #303133;
font-size: 16px;
}
}
.avatar-wrapper {
display: flex;
align-items: center;
span {
margin-right: 5px;
font-size: 18px;
}
}
}
</style>

@ -0,0 +1,49 @@
@import "~@/asset/style/variables.scss";
.navbar {
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: nowrap;
height: $nav-height;
padding-left: 12px;
z-index: 10;
box-shadow: 0 1px 4px rgba(0, 21, 41, .08);
transition: all .2s;
> div {
display: flex;
flex-wrap: nowrap;
height: 100%;
}
.navbar-icon {
font-size: 18px;
}
.navbar-item {
display: flex;
align-items: center;
padding: 0 8px;
cursor: pointer;
}
.logo-container {
width: auto;
min-width: 192px;
padding-left: 8px;
}
.avatar-wrapper {
display: flex;
align-items: center;
span {
margin-right: 5px;
font-size: 18px;
}
}
}
@import "theme-light";
@import "theme-dark";

@ -0,0 +1,11 @@
.navbar.dark {
background-color: $root-menu-dark-background;
.navbar-item {
color: rgba(255, 255, 255, .85);
&:hover {
background: $--color-primary;
}
}
}

@ -0,0 +1,11 @@
.navbar.light {
background-color: #ffffff;
.navbar-item {
color: $menu-text-light-color;
&:hover {
background: rgba(0, 0, 0, .025);
}
}
}

@ -26,7 +26,22 @@ export default {
computed: { computed: {
visitedViews: () => tagsViewGetters.visitedViews, visitedViews: () => tagsViewGetters.visitedViews,
menus: () => appGetters.menus
menus: () => appGetters.menus,
contextMenuItems() {
return [
{content: '刷新', click: this.refreshSelectedTag},
this.isAffix(this.selectedTag)
? undefined
: {
content: '关闭',
click: () => this.closeSelectedTag(this.selectedTag)
},
{content: '关闭其他', click: this.closeOthersTags},
{content: '关闭全部', click: this.closeAllTags}
]
}
}, },
watch: { watch: {
@ -131,10 +146,9 @@ export default {
e.preventDefault() e.preventDefault()
const contextMenuWidth = 105 // const contextMenuWidth = 105 //
const offsetLeft = this.$el.getBoundingClientRect().left // container margin left
const offsetWidth = this.$el.offsetWidth // container width const offsetWidth = this.$el.offsetWidth // container width
const maxLeft = offsetWidth - contextMenuWidth // left boundary const maxLeft = offsetWidth - contextMenuWidth // left boundary
const left = e.clientX - offsetLeft + 15 // 15: margin right const left = e.clientX + 15 // 15: margin right
this.contextmenu.left = left > maxLeft ? maxLeft : left this.contextmenu.left = left > maxLeft ? maxLeft : left
this.contextmenu.top = e.clientY this.contextmenu.top = e.clientY
@ -159,27 +173,11 @@ export default {
v-on:contextmenu_native={e => this.openContextMenu(e, tag)} v-on:contextmenu_native={e => this.openContextMenu(e, tag)}
v-on:dblclick_native={e => closeSelectedTag(e, tag)} v-on:dblclick_native={e => closeSelectedTag(e, tag)}
> >
{title} <span>{title}</span>
{!affix && <i class="el-icon-close" on-click={e => closeSelectedTag(e, tag)}/>} {!affix && <i class="el-icon-close" on-click={e => closeSelectedTag(e, tag)}/>}
</router-link> </router-link>
) )
}) })
},
renderContextMenu() {
const menu = this.contextmenu
const items = [
{content: '刷新', click: this.refreshSelectedTag},
this.isAffix(this.selectedTag)
? undefined
: {
content: '关闭',
click: () => this.closeSelectedTag(this.selectedTag)
},
{content: '关闭其他', click: this.closeOthersTags},
{content: '关闭全部', click: this.closeAllTags}
]
return <context-menu v-model={menu.show} items={items} left={menu.left} top={menu.top}/>
} }
}, },
@ -193,7 +191,13 @@ export default {
<scroll-panel ref="scrollPanel" class="tags-view-wrapper"> <scroll-panel ref="scrollPanel" class="tags-view-wrapper">
{this.renderTags()} {this.renderTags()}
</scroll-panel> </scroll-panel>
{this.renderContextMenu()}
<context-menu
v-model={this.contextmenu.show}
items={this.contextMenuItems}
left={this.contextmenu.left}
top={this.contextmenu.top}
/>
</div> </div>
) )
} }

@ -1,15 +1,12 @@
@import "~@/asset/style/variables.scss"; @import "~@/asset/style/variables.scss";
.tags-view-container { .tags-view-container {
z-index: 8;
height: $tags-view-height; height: $tags-view-height;
background-color: #f0f2f5;
box-shadow: inset 0 0 3px 2px hsla(0, 0%, 39.2%, .1); box-shadow: inset 0 0 3px 2px hsla(0, 0%, 39.2%, .1);
.tags-view-wrapper { .tags-view-wrapper {
height: 100%; height: 100%;
white-space: nowrap; white-space: nowrap;
position: relative;
overflow-x: auto; overflow-x: auto;
&::-webkit-scrollbar { &::-webkit-scrollbar {
@ -18,15 +15,16 @@
.tags-view-item { .tags-view-item {
$margin: 4px; $margin: 4px;
$border-width: 1px;
display: inline-block; display: inline-block;
position: relative;
cursor: pointer; cursor: pointer;
height: #{$tags-view-height - $margin * 2}; height: #{$tags-view-height - $margin * 2};
line-height: #{$tags-view-height - $margin * 2}; line-height: #{$tags-view-height - $margin * 2 - $border-width * 2};
color: #909399; color: #909399;
background-color: #ffffff; background-color: #ffffff;
border-radius: 3px; border-radius: 3px;
border: 1px solid #e8eaec; border: $border-width solid #dcdfe6;
padding: 0 12px; padding: 0 12px;
font-size: 12px; font-size: 12px;
margin: $margin; margin: $margin;
@ -42,12 +40,12 @@
&::before { &::before {
content: ''; content: '';
background: #e8eaec; position: relative;
background-color: #e8eaec;
display: inline-block; display: inline-block;
width: 12px; width: 12px;
height: 12px; height: 12px;
border-radius: 50%; border-radius: 50%;
position: relative;
top: 1px; top: 1px;
margin-right: 5px; margin-right: 5px;
} }
@ -61,18 +59,9 @@
} }
.el-icon-close { .el-icon-close {
vertical-align: 2px;
border-radius: 50%; border-radius: 50%;
font-size: 14px; font-size: 14px;
margin-left: 10px; margin-left: 10px;
&:before {
vertical-align: -3px;
}
&:hover {
color: #3A71A8;
}
} }
} }
} }

@ -67,13 +67,12 @@ export default {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
flex: 1; flex: 1;
position: relative;
overflow: hidden; overflow: hidden;
background-color: #f0f2f5; background-color: #f0f2f5;
> .scroll-container { > .scroll-container {
height: 100%;
display: flex; display: flex;
flex: 1;
flex-direction: column; flex-direction: column;
overflow-y: overlay; overflow-y: overlay;
overflow-x: inherit; overflow-x: inherit;

@ -54,6 +54,18 @@
</div> </div>
<el-divider>侧边栏设置</el-divider> <el-divider>侧边栏设置</el-divider>
<div class="drawer-item">
<span>主题风格</span>
<el-select
:value="asideGetters.theme"
size="mini"
style="width: 80px"
@input="changeAsideTheme"
>
<el-option value="light" label="亮色"/>
<el-option value="dark" label="暗色"/>
</el-select>
</div>
<div class="drawer-item"> <div class="drawer-item">
<span>手风琴效果</span> <span>手风琴效果</span>
<el-switch :value="asideGetters.uniqueOpen" @input="changeAsideUniqueOpen"/> <el-switch :value="asideGetters.uniqueOpen" @input="changeAsideUniqueOpen"/>
@ -71,6 +83,20 @@
<el-switch :value="asideGetters.autoHide" @input="changeAsideAutoHide"/> <el-switch :value="asideGetters.autoHide" @input="changeAsideAutoHide"/>
</div> </div>
<el-divider>导航栏设置</el-divider>
<div class="drawer-item">
<span>主题风格</span>
<el-select
:value="navbarGetters.theme"
size="mini"
style="width: 80px"
@input="changeNavbarTheme"
>
<el-option value="light" label="亮色"/>
<el-option value="dark" label="暗色"/>
</el-select>
</div>
<el-divider>多页签设置</el-divider> <el-divider>多页签设置</el-divider>
<div class="drawer-item"> <div class="drawer-item">
<span>启用</span> <span>启用</span>
@ -96,6 +122,7 @@ import ColorCheckbox from "@/component/checkbox/ColorCheckbox"
import ImgCheckbox from "@/component/checkbox/ImgCheckbox" import ImgCheckbox from "@/component/checkbox/ImgCheckbox"
import {getters as appGetters, mutations as appMutations} from "@/layout/store/app" import {getters as appGetters, mutations as appMutations} from "@/layout/store/app"
import {getters as asideGetters, mutations as asideMutations} from "@/layout/store/aside" import {getters as asideGetters, mutations as asideMutations} from "@/layout/store/aside"
import {getters as navbarGetters, mutations as navbarMutations} from "@/layout/store/navbar"
import {getters as pageGetters, mutations as pageMutations} from "@/layout/store/page" import {getters as pageGetters, mutations as pageMutations} from "@/layout/store/page"
import {getters as tagsViewGetters, mutations as tagsViewMutations} from "@/layout/store/tagsView" import {getters as tagsViewGetters, mutations as tagsViewMutations} from "@/layout/store/tagsView"
import {mergeObj} from "@/util" import {mergeObj} from "@/util"
@ -110,7 +137,7 @@ export default {
data() { data() {
return { return {
appGetters, asideGetters, pageGetters, tagsViewGetters, appGetters, asideGetters, navbarGetters, pageGetters, tagsViewGetters,
colors: ['#f5222d', '#fa541c', '#fadb14', '#3eaf7c', '#13c2c2', '#1890ff', '#722ed1', '#eb2f96'], colors: ['#f5222d', '#fa541c', '#fadb14', '#3eaf7c', '#13c2c2', '#1890ff', '#722ed1', '#eb2f96'],
navModes: [ navModes: [
@ -148,12 +175,15 @@ export default {
showBackToTop: true showBackToTop: true
}, },
aside: { aside: {
showLogo: true, theme: 'light',
uniqueOpen: true, uniqueOpen: true,
collapse: false, collapse: false,
showParentOnCollapse: false, showParentOnCollapse: false,
autoHide: false autoHide: false
}, },
navbar: {
theme: 'light'
},
tagsView: { tagsView: {
enabled: true, enabled: true,
shortcut: true, shortcut: true,
@ -164,51 +194,29 @@ export default {
}, },
methods: { methods: {
//layoutstore //layoutstore
updateLayoutStore() { syncLayoutStore() {
const {app, page, aside, tagsView} = this.setting const {app, page, aside, navbar, tagsView} = this.setting
appMutations.color(app.color)
appMutations.navMode(app.navMode)
pageMutations.showLogo(page.showLogo)
pageMutations.position(page.position)
pageMutations.showPageHeader(page.showPageHeader)
pageMutations.showBackToTop(page.showBackToTop)
asideMutations.uniqueOpen(aside.uniqueOpen)
asideMutations.collapse(aside.collapse)
asideMutations.showParentOnCollapse(aside.showParentOnCollapse)
asideMutations.autoHide(aside.autoHide)
tagsViewMutations.enabled(tagsView.enabled) Object.entries(app).forEach(([k, v]) => appMutations[k](v))
tagsViewMutations.shortcut(tagsView.shortcut) Object.entries(page).forEach(([k, v]) => pageMutations[k](v))
tagsViewMutations.persistent(tagsView.persistent) Object.entries(aside).forEach(([k, v]) => asideMutations[k](v))
Object.entries(navbar).forEach(([k, v]) => navbarMutations[k](v))
Object.entries(tagsView).forEach(([k, v]) => tagsViewMutations[k](v))
}, },
//layoutstore //layoutstore
syncLayoutStore() { syncSettingDrawer() {
const {app, page, aside, tagsView} = this.setting const {app, page, aside, navbar, tagsView} = this.setting
app.color = appGetters.color
app.navMode = appGetters.navMode
page.showLogo = pageGetters.showLogo
page.position = pageGetters.position
page.showPageHeader = pageGetters.showPageHeader
page.showBackToTop = pageGetters.showBackToTop
aside.showLogo = asideGetters.showLogo
aside.uniqueOpen = asideGetters.uniqueOpen
aside.collapse = asideGetters.collapse
aside.showParentOnCollapse = asideGetters.showParentOnCollapse
aside.autoHide = asideGetters.autoHide
tagsView.enabled = tagsViewGetters.enabled Object.keys(app).forEach(k => app[k] = appGetters[k])
tagsView.shortcut = tagsViewGetters.shortcut Object.keys(page).forEach(k => page[k] = pageGetters[k])
tagsView.persistent = tagsViewGetters.persistent Object.keys(aside).forEach(k => aside[k] = asideGetters[k])
Object.keys(navbar).forEach(k => navbar[k] = navbarGetters[k])
Object.keys(tagsView).forEach(k => tagsView[k] = tagsViewGetters[k])
}, },
// //
saveSetting() { saveSetting() {
this.syncLayoutStore() this.syncSettingDrawer()
setLocalPersonalSettings(this.setting) setLocalPersonalSettings(this.setting)
}, },
@ -233,6 +241,9 @@ export default {
pageMutations.showBackToTop(v) pageMutations.showBackToTop(v)
}, },
changeAsideTheme(v) {
asideMutations.theme(v)
},
changeAsideUniqueOpen(v) { changeAsideUniqueOpen(v) {
asideMutations.uniqueOpen(v) asideMutations.uniqueOpen(v)
}, },
@ -246,6 +257,10 @@ export default {
asideMutations.autoHide(v) asideMutations.autoHide(v)
}, },
changeNavbarTheme(v) {
navbarMutations.theme(v)
},
changeTagsViewEnabled(v) { changeTagsViewEnabled(v) {
tagsViewMutations.enabled(v) tagsViewMutations.enabled(v)
}, },
@ -275,7 +290,7 @@ export default {
// //
setLocalPersonalSettings(this.setting) setLocalPersonalSettings(this.setting)
this.updateLayoutStore() this.syncLayoutStore()
// //
this.changeThemeColor(this.setting.app.color) this.changeThemeColor(this.setting.app.color)
@ -293,7 +308,7 @@ export default {
.drawer-item { .drawer-item {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
color: rgba(0, 0, 0, .65); color: #606266;
font-size: 14px; font-size: 14px;
padding: 12px 0; padding: 12px 0;
} }

@ -3,7 +3,8 @@ import offlineMixin from './mixin/offline'
import VAside from './component/Aside' import VAside from './component/Aside'
import VNavbar from './component/Navbar' import VNavbar from './component/Navbar'
import VPage from './component/Page' import VPage from './component/Page'
import {getters as appGetters} from "@/layout/store/app" import SettingDrawer from './component/SettingDrawer'
import {getters as appGetters, mutations as appMutations} from "@/layout/store/app"
import {getters as pageGetters} from "@/layout/store/page" import {getters as pageGetters} from "@/layout/store/page"
import {getters as tagsViewGetters} from "@/layout/store/tagsView" import {getters as tagsViewGetters} from "@/layout/store/tagsView"
@ -12,12 +13,15 @@ export default {
mixins: [offlineMixin], mixins: [offlineMixin],
components: {VAside, VNavbar, VPage}, components: {VAside, VNavbar, VPage, SettingDrawer},
computed: { computed: {
isLeftRight() { isLeftRight() {
return pageGetters.position === 'left-right' return pageGetters.position === 'left-right'
}, },
renderAside() {
return appGetters.isMobile || ['aside', 'aside-two-part', 'mix'].includes(appGetters.navMode)
},
wrapperClass() { wrapperClass() {
return { return {
'app-wrapper': true, 'app-wrapper': true,
@ -32,9 +36,12 @@ export default {
this.isLeftRight && 'flex-column', this.isLeftRight && 'flex-column',
tagsViewGetters.enabled && 'has-tags-view' tagsViewGetters.enabled && 'has-tags-view'
] ]
}, }
renderAside() { },
return appGetters.isMobile || ['aside', 'aside-two-part', 'mix'].includes(appGetters.navMode)
methods: {
closeSettingDrawer() {
appMutations.showSettingDrawer(false)
} }
}, },
@ -49,6 +56,11 @@ export default {
{this.isLeftRight ? <v-navbar/> : aside} {this.isLeftRight ? <v-navbar/> : aside}
<v-page/> <v-page/>
</section> </section>
<setting-drawer
value={appGetters.showSettingDrawer}
on-input={this.closeSettingDrawer}
/>
</section> </section>
) )
} }
@ -63,7 +75,6 @@ export default {
.app-wrapper { .app-wrapper {
display: flex; display: flex;
position: relative;
height: 100%; height: 100%;
width: 100%; width: 100%;
@ -71,7 +82,12 @@ export default {
display: flex; display: flex;
flex: 1; flex: 1;
overflow: hidden; overflow: hidden;
position: relative; }
//z-index
//z-index
&.flex-column .aside {
z-index: 9;
} }
} }
</style> </style>

@ -7,6 +7,10 @@ const state = {
//区分pc和移动端 //区分pc和移动端
isMobile: isMobile(), isMobile: isMobile(),
//设置抽屉的显隐
//放这里的原因是navbar可能会重新渲染,从而导致抽屉的重新渲染
showSettingDrawer: false,
//当前激活的顶部菜单的fullPath //当前激活的顶部菜单的fullPath
activeRootMenu: '', activeRootMenu: '',

@ -10,6 +10,9 @@ const state = {
//抽屉模式时的显隐 //抽屉模式时的显隐
show: false, show: false,
//主题,light 或 dark
theme: 'light',
//手风琴效果 //手风琴效果
uniqueOpen: true, uniqueOpen: true,

@ -0,0 +1,13 @@
import Vue from 'vue'
import {createGetters, createMutations} from "@/util/observable"
const state = {
//主题,light 或 dark
theme: 'light',
}
const store = Vue.observable(state)
export const getters = createGetters(store)
export const mutations = createMutations(store)

@ -6,6 +6,6 @@ const files = require.context('./child', false, /\.js$/)
export default { export default {
path: '/dev', path: '/dev',
component: {template: `<div><div>开发专用页面</div><router-view/></div>`}, component: {template: `<div><div>开发专用页面</div><router-view/></div>`},
meta: {title: '开发专用页面', noAuth: true, noCache: true, alwaysShow: true}, meta: {title: '开发专用页面', icon: 'svg-develop', noAuth: true, noCache: true, alwaysShow: true},
children: context2array(files) children: context2array(files)
} }

@ -1,3 +1,7 @@
import cssVariables from '@/asset/style/variables.scss'
const maxMobileWidth = parseFloat(cssVariables.maxMobileWidth)
/** /**
* 根据body宽度判断是否为移动端是则返回true * 根据body宽度判断是否为移动端是则返回true
* *
@ -5,7 +9,7 @@
*/ */
export function isMobile() { export function isMobile() {
const rect = document.body.getBoundingClientRect() const rect = document.body.getBoundingClientRect()
return rect.width < 501 return rect.width <= maxMobileWidth
} }
/** /**

@ -9,7 +9,7 @@
margin: -#{$page-view-margin} -#{$page-view-margin} 0 -#{$page-view-margin}; margin: -#{$page-view-margin} -#{$page-view-margin} 0 -#{$page-view-margin};
padding: 16px 32px 0 32px; padding: 16px 32px 0 32px;
background: #fff; background: #fff;
border-bottom: 1px solid #e8eaec; border-bottom: 1px solid #dcdfe6;
> .el-page-header { > .el-page-header {
padding: 0 0 24px 0; padding: 0 0 24px 0;

@ -1,5 +1,5 @@
<template> <template>
<div style="position: absolute;top: 0;right: 0;left: 0;bottom: 0;"> <div style="margin: -24px">
<canvas :height="height" :width="width" id="canvas-fluid" style="display: block"/> <canvas :height="height" :width="width" id="canvas-fluid" style="display: block"/>
</div> </div>
</template> </template>

@ -42,7 +42,6 @@ export default {
color: #666; color: #666;
background: #fff; background: #fff;
border-radius: 4px; border-radius: 4px;
-webkit-box-shadow: 0 2px 12px 0 rgba(0, 0, 0, .1);
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, .1); box-shadow: 0 2px 12px 0 rgba(0, 0, 0, .1);
border-color: rgba(0, 0, 0, .05); border-color: rgba(0, 0, 0, .05);

@ -8,7 +8,7 @@
<script> <script>
import PanelGroupItem from './PanelGroupItem' import PanelGroupItem from './PanelGroupItem'
import variables from '@/asset/style/variables.scss' import cssVariables from '@/asset/style/variables.scss'
import {getFourBlock} from '@/api/statistic' import {getFourBlock} from '@/api/statistic'
import {isEmpty} from "@/util" import {isEmpty} from "@/util"
import {auth} from "@/util/auth" import {auth} from "@/util/auth"
@ -26,7 +26,7 @@ export default {
id: 'online', id: 'online',
path: '/system/user', path: '/system/user',
icon: 'svg-user', icon: 'svg-user',
color: variables.info, color: cssVariables.info,
value: 0, value: 0,
text: '在线用户' text: '在线用户'
}, },
@ -34,7 +34,7 @@ export default {
id: 'purchase', id: 'purchase',
path: '/purchase/order', path: '/purchase/order',
icon: 'svg-shopping', icon: 'svg-shopping',
color: variables.primary, color: cssVariables.primary,
value: 0, value: 0,
text: '今日采购额' text: '今日采购额'
}, },
@ -42,14 +42,14 @@ export default {
id: 'sell', id: 'sell',
path: '/sell/order', path: '/sell/order',
icon: 'svg-sell', icon: 'svg-sell',
color: variables.warning, color: cssVariables.warning,
value: 0, value: 0,
text: '今日销售额' text: '今日销售额'
}, },
{ {
id: 'profit', id: 'profit',
icon: 'svg-money', icon: 'svg-money',
color: variables.success, color: cssVariables.success,
value: 0, value: 0,
text: '今日毛利润' text: '今日毛利润'
}, },

@ -101,15 +101,17 @@ export default {
openContextMenu(e, obj, node) { openContextMenu(e, obj, node) {
e.preventDefault() e.preventDefault()
this.currentCategory = obj ? { this.currentCategory = obj ? {
...obj, ...obj,
level: node.level, level: node.level,
parent: node.parent ? node.parent.data : null parent: node.parent ? node.parent.data : null
} : null } : null
this.$refs.tree.$refs.tree.setCurrentKey(obj ? obj.id : null) this.$refs.tree.$refs.tree.setCurrentKey(obj ? obj.id : null)
const {left, top} = this.$el.getBoundingClientRect()
this.contextmenu.left = e.clientX - left + 15 this.contextmenu.left = e.clientX + 15
this.contextmenu.top = e.clientY - top this.contextmenu.top = e.clientY
this.contextmenu.show = true this.contextmenu.show = true
} }
}, },

@ -110,9 +110,8 @@ export default {
openMenu(e, obj) { openMenu(e, obj) {
e.preventDefault() e.preventDefault()
this.currentNode = obj this.currentNode = obj
const {left, top} = this.$el.getBoundingClientRect() this.contextmenu.left = e.clientX + 15
this.contextmenu.left = e.clientX - left + 15 this.contextmenu.top = e.clientY
this.contextmenu.top = e.clientY - top
this.contextmenu.show = true this.contextmenu.show = true
}, },

Loading…
Cancel
Save