Browse Source

feat: 🎸 layout布局重构中

chenkl 4 years ago
parent
commit
bd24b92acb

+ 33 - 11
src/components/Logo/index.vue

@@ -1,13 +1,13 @@
 <template>
-  <router-link class="app-logo" to="/">
+  <router-link class="app-logo" to="/" :class="{'app-logo--Top': layout !== 'Classic'}">
     <img :src="require('@/assets/img/logo.png')">
     <div v-if="show" class="sidebar-title">{{ title }}</div>
   </router-link>
 </template>
 
 <script lang="ts">
-import { defineComponent, ref, watch, PropType } from 'vue'
-import config from '_p/index/config'
+import { defineComponent, ref, watch, PropType, computed } from 'vue'
+import { appStore } from '_p/index/store/modules/app'
 
 export default defineComponent({
   name: 'Logo',
@@ -19,21 +19,28 @@ export default defineComponent({
   },
   setup(props) {
     const show = ref<boolean>(true)
+    const title = computed(() => appStore.title)
+    const layout = computed(() => appStore.layout)
     watch(
       () => props.collapsed,
       (collapsed: boolean) => {
-        if (!collapsed) {
-          setTimeout(() => {
-            show.value = !collapsed
-          }, 400)
+        if (layout.value !== 'Classic') {
+          show.value = true
         } else {
-          show.value = !collapsed
+          if (!collapsed) {
+            setTimeout(() => {
+              show.value = !collapsed
+            }, 400)
+          } else {
+            show.value = !collapsed
+          }
         }
       }
     )
     return {
       show,
-      title: config.title
+      title,
+      layout
     }
   }
 })
@@ -44,7 +51,7 @@ export default defineComponent({
   display: flex;
   align-items: center;
   cursor: pointer;
-  height: @topSilderHeight;
+  height: @topSiderHeight;
   width: 100%;
   background-color: @menuBg;
   img {
@@ -59,7 +66,22 @@ export default defineComponent({
     margin-left: 12px;
   }
   .sidebar-title {
-    color: #fff;
+    color: @menuActiveText;
+  }
+}
+.app-logo--Top {
+  width: auto;
+  background-color: @topMenuBg;
+  transition: background 0.2s;
+  padding: 0 5px;
+  &:hover {
+    background: #f6f6f6;
+  }
+  img {
+    margin-left: 0;
+  }
+  .sidebar-title {
+    color: @topMenuText;
   }
 }
 </style>

+ 0 - 111
src/components/ScrollPane/index.vue

@@ -1,111 +0,0 @@
-<template>
-  <el-scrollbar
-    ref="scrollContainer"
-    class="scroll-container"
-    @wheel="handleScroll"
-  >
-    <slot />
-  </el-scrollbar>
-</template>
-
-<script lang="ts">
-import { defineComponent, ref, unref, nextTick } from 'vue'
-import { useScrollTo } from '@/hooks/useScrollTo'
-const tagAndTagSpacing = 4 // tagAndTagSpacing
-
-export default defineComponent({
-  name: 'ScrollPane',
-  setup() {
-    const scrollContainer = ref<HTMLElement | null>(null)
-
-    function handleScroll(e: any): void {
-      const eventDelta: number = e.wheelDelta || -e.deltaY * 40
-      const $scrollWrapper: any = (unref(scrollContainer) as any).$.wrap
-      $scrollWrapper.scrollLeft = $scrollWrapper.scrollLeft + eventDelta / 4
-    }
-
-    function moveToTarget(currentTag: any) {
-      const $container: any = (unref(scrollContainer) as any).$el
-      const $containerWidth: number = $container.offsetWidth
-      const $scrollWrapper: any = (unref(scrollContainer) as any).$.wrap
-      const tagList = (unref(scrollContainer) as any).$parent.$parent.tagRefs
-
-      let firstTag: any = null
-      let lastTag: any = null
-
-      // find first tag and last tag
-      if (tagList.length > 0) {
-        firstTag = tagList[0]
-        lastTag = tagList[tagList.length - 1]
-      }
-
-      if (firstTag === currentTag) {
-        $scrollWrapper.scrollLeft = 0
-      } else if (lastTag === currentTag) {
-        $scrollWrapper.scrollLeft = $scrollWrapper.scrollWidth - $containerWidth
-      } else {
-        // find preTag and nextTag
-        const currentIndex: number = tagList.findIndex((item: any) => item === currentTag)
-        const prevTag = tagList[currentIndex - 1]
-        const nextTag = tagList[currentIndex + 1]
-        // the tag's offsetLeft after of nextTag
-        const afterNextTagOffsetLeft = nextTag.$el.offsetLeft + nextTag.$el.offsetWidth + tagAndTagSpacing
-
-        // the tag's offsetLeft before of prevTag
-        const beforePrevTagOffsetLeft = prevTag.$el.offsetLeft - tagAndTagSpacing
-
-        if (afterNextTagOffsetLeft > $scrollWrapper.scrollLeft + $containerWidth) {
-          nextTick(() => {
-            const { start } = useScrollTo({
-              el: $scrollWrapper,
-              position: 'scrollLeft',
-              to: afterNextTagOffsetLeft - $containerWidth,
-              duration: 500
-            })
-            start()
-          })
-        } else if (beforePrevTagOffsetLeft < $scrollWrapper.scrollLeft) {
-          nextTick(() => {
-            const { start } = useScrollTo({
-              el: $scrollWrapper,
-              position: 'scrollLeft',
-              to: beforePrevTagOffsetLeft,
-              duration: 500
-            })
-            start()
-          })
-        }
-      }
-    }
-
-    function moveTo(to: number) {
-      const $scrollWrapper: any = (unref(scrollContainer) as any).$.wrap
-      nextTick(() => {
-        const { start } = useScrollTo({
-          el: $scrollWrapper,
-          position: 'scrollLeft',
-          to: $scrollWrapper.scrollLeft + to,
-          duration: 500
-        })
-        start()
-      })
-    }
-
-    return {
-      handleScroll,
-      scrollContainer,
-      moveToTarget,
-      moveTo
-    }
-  }
-})
-</script>
-
-<style lang="less">
-.scroll-container {
-  white-space: nowrap;
-  position: relative;
-  overflow: hidden;
-  width: 100%;
-}
-</style>

+ 4 - 3
src/components/Setting/index.vue

@@ -38,15 +38,15 @@
     </div>
 
     <div class="setting__title">界面显示</div>
-    <div class="setting__item">
+    <div v-if="layout !== 'Top'" class="setting__item">
       <span>顶部操作栏</span>
       <el-switch v-model="navbar" @change="setNavbar" />
     </div>
-    <div class="setting__item">
+    <div v-if="layout !== 'Top'" class="setting__item">
       <span>侧边栏缩收</span>
       <el-switch v-model="hamburger" @change="setHamburger" />
     </div>
-    <div class="setting__item">
+    <div v-if="layout !== 'Top'" class="setting__item">
       <span>面包屑</span>
       <el-switch v-model="breadcrumb" @change="setBreadcrumb" />
     </div>
@@ -79,6 +79,7 @@ export default defineComponent({
     function setLayout(mode: 'Classic' | 'LeftTop' | 'Top' | 'Test') {
       if (mode === layout.value) return
       appStore.SetLayout(mode)
+      appStore.SetCollapsed(false)
     }
 
     // const fixedNavbar = ref<boolean>(appStore.fixedNavbar)

+ 14 - 4
src/components/Sider/SiderItem.vue

@@ -1,5 +1,5 @@
 <template>
-  <div v-if="!item.meta?.hidden">
+  <template v-if="!item.meta?.hidden">
     <template v-if="hasOneShowingChild(item.children, item) && (!onlyOneChild.children || onlyOneChild.noShowingChildren) && !item.meta?.alwaysShow">
       <el-menu-item :index="resolvePath(onlyOneChild.path)" :class="{'submenu-title-noDropdown': !isNest}">
         <item v-if="onlyOneChild.meta" :icon="onlyOneChild.meta.icon || (item.meta && item.meta.icon)" />
@@ -9,7 +9,13 @@
       </el-menu-item>
     </template>
 
-    <el-submenu v-else popper-class="nest-popper-menu" :index="resolvePath(item.path)">
+    <el-submenu
+      v-else
+      :popper-class="layout !== 'Top'
+        ? 'nest-popper-menu'
+        : 'top-popper-menu'"
+      :index="resolvePath(item.path)"
+    >
       <template #title>
         <item v-if="item.meta" :icon="item.meta && item.meta.icon" :title="item.meta.title" />
       </template>
@@ -18,11 +24,11 @@
         :key="child.path"
         :is-nest="true"
         :item="child"
+        :layout="layout"
         :base-path="resolvePath(child.path)"
-        class="nest-menu"
       />
     </el-submenu>
-  </div>
+  </template>
 </template>
 
 <script lang="ts">
@@ -47,6 +53,10 @@ export default defineComponent({
     basePath: {
       type: String as PropType<string>,
       default: ''
+    },
+    layout: {
+      type: String as PropType<string>,
+      default: 'Classic'
     }
   },
   setup(props) {

+ 48 - 14
src/components/Sider/index.vue

@@ -1,17 +1,21 @@
 <template>
-  <div :class="{'has-logo': showLogo}" class="sidebar-container">
+  <div
+    :class="{'has-logo': showLogo && layout === 'Classic', 'sidebar-container--Top': layout === 'Top'}"
+    class="sidebar-container"
+  >
     <el-scrollbar>
       <el-menu
         :default-active="activeMenu"
         :collapse="collapsed"
         :unique-opened="false"
-        mode="vertical"
+        :mode="mode"
         @select="selectMenu"
       >
         <sider-item
           v-for="route in routers"
           :key="route.path"
           :item="route"
+          :layout="layout"
           :base-path="route.path"
         />
       </el-menu>
@@ -20,17 +24,28 @@
 </template>
 
 <script lang="ts">
-import { defineComponent, computed } from 'vue'
+import { defineComponent, computed, PropType } from 'vue'
 import { useRouter } from 'vue-router'
 import { permissionStore } from '_p/index/store/modules/permission'
 import { appStore } from '_p/index/store/modules/app'
-import type { RouteRecordRaw, RouteLocationNormalizedLoaded } from 'vue-router'
+import type { RouteRecordRaw } from 'vue-router'
 import SiderItem from './SiderItem.vue'
 import variables from '@/styles/variables.less'
 import { isExternal } from '@/utils/validate'
 
 export default defineComponent({
+  name: 'Sider',
   components: { SiderItem },
+  props: {
+    layout: {
+      type: String as PropType<string>,
+      default: 'Classic'
+    },
+    mode: {
+      type: String as PropType<'horizontal' | 'vertical'>,
+      default: 'vertical'
+    }
+  },
   setup() {
     const { currentRoute, push } = useRouter()
     const routers = computed((): RouteRecordRaw[] => {
@@ -73,19 +88,38 @@ export default defineComponent({
   @{deep}(.svg-icon) {
     margin-right: 16px;
   }
+  @{deep}(.el-scrollbar) {
+    width: 100%;
+    height: 100%;
+    .el-scrollbar__wrap {
+      overflow: scroll;
+      overflow-x: hidden;
+      .el-menu {
+        width: 100%;
+        border: none;
+      }
+    }
+  }
 }
 .has-logo {
-  height: calc(~"100% - @{topSilderHeight}");
+  height: calc(~"100% - @{topSiderHeight}");
 }
-@{deep}(.el-scrollbar) {
-  width: 100%;
-  height: 100%;
-  .el-scrollbar__wrap {
-    overflow: scroll;
-    overflow-x: hidden;
-    .el-menu {
-      width: 100%;
-      border: none;
+
+.sidebar-container--Top {
+  @{deep}(.el-scrollbar) {
+    width: 100%;
+    height: 100%;
+    .el-scrollbar__wrap {
+      overflow: scroll;
+      overflow-x: hidden;
+      .el-scrollbar__view {
+        height: @topSiderHeight;
+      }
+      .el-menu {
+        width: auto;
+        height: 100%;
+        border: none;
+      }
     }
   }
 }

+ 3 - 5
src/components/TagsView/index.vue

@@ -158,7 +158,7 @@ export default defineComponent({
       })
     }
 
-    async function closeAllTags(view: RouteLocationNormalizedLoaded) {
+    async function closeAllTags() {
       const views: any = await tagsViewStore.delAllViews()
       // console.log(affixTags.value.some(tag => tag.path === view.path))
       // if (affixTags.value.some(tag => tag.path === view.path)) {
@@ -300,9 +300,9 @@ export default defineComponent({
         margin-right: 23px;
       }
       &.active {
-        background-color: #304156;
+        background-color: @tagActiveBg;
         color: #fff;
-        border-color: #304156;
+        border-color: @tagActiveBg;
         &::before {
           content: '';
           background: #fff;
@@ -322,9 +322,7 @@ export default defineComponent({
         line-height: 16px;
         transition: all .3s cubic-bezier(.645, .045, .355, 1);
         transform-origin: 100% 50%;
-        margin-left: 5px;
         &:before {
-          transform: scale(.6);
           display: inline-block;
         }
         &:hover {

+ 116 - 5
src/pages/index/layout/modules/Test.vue

@@ -1,12 +1,39 @@
 <template>
   <div :class="classObj" class="app__wrap">
-    <div class="sidebar__wrap" :class="{'sidebar__wrap--collapsed': collapsed}">
+    <!-- Classic -->
+    <div
+      v-if="layout === 'Classic' || layout === 'LeftTop'"
+      class="sidebar__wrap"
+      :class="{'sidebar__wrap--collapsed': collapsed}"
+    >
       <logo
-        v-if="showLogo"
+        v-if="showLogo && layout === 'Classic'"
         :collapsed="collapsed"
       />
-      <sider />
+      <sider :layout="layout" mode="vertical" />
+    </div>
+    <!-- Classic -->
+
+    <!-- Top -->
+    <div v-if="layout !== 'Classic'" class="sidebar__wrap--Top">
+      <div>
+        <logo
+          v-if="showLogo"
+          :collapsed="collapsed"
+        />
+      </div>
+      <div v-if="layout === 'Top'" class="sidebar__item--Top">
+        <sider :layout="layout" mode="horizontal" />
+      </div>
+      <div>
+        <div v-if="showScreenfull || showUserInfo" class="navbar__wrap--right">
+          <screenfull v-if="showScreenfull" class="hover-container screenfull-container" />
+          <user-info v-if="showUserInfo" class="hover-container user-container" />
+        </div>
+      </div>
     </div>
+    <!-- Top -->
+
     <div
       class="main__wrap"
       :class="{
@@ -29,7 +56,7 @@
           }"
         >
           <div
-            v-if="showNavbar"
+            v-if="showNavbar && layout !== 'Top'"
             class="navbar__wrap"
           >
             <hamburger
@@ -102,6 +129,7 @@ export default defineComponent({
   },
   setup() {
     const drawer = ref<boolean>(false)
+    const layout = computed(() => appStore.layout)
     const collapsed = computed(() => appStore.collapsed)
     const showLogo = computed(() => appStore.showLogo)
     const showTags = computed(() => appStore.showTags)
@@ -116,7 +144,7 @@ export default defineComponent({
 
     const classObj = computed(() => {
       const obj = {}
-      obj[`app__wrap--${appStore.layout}`] = true
+      obj[`app__wrap--${layout.value}`] = true
       return obj
     })
 
@@ -131,6 +159,7 @@ export default defineComponent({
     return {
       drawer,
       classObj,
+      layout,
       collapsed,
       showLogo,
       showTags,
@@ -199,6 +228,7 @@ export default defineComponent({
           height: 100%;
           line-height: @navbarHeight + 5px;
           padding: 0 5px;
+          text-align: center;
           &:hover {
             background: #f6f6f6;
           }
@@ -260,6 +290,87 @@ export default defineComponent({
   }
 }
 
+// 顶部模式
+.app__wrap--Top,
+.app__wrap--LeftTop {
+  .sidebar__wrap--Top {
+    height: @topSiderHeight;
+    display: flex;
+    flex-direction: row;
+    justify-content: space-between;
+    padding: 0 20px;
+    background-color: @topMenuBg;
+    position: relative;
+    &:after {
+      content: "";
+      width: 100%;
+      height: 1px;
+      border-top: 1px solid #d8dce5;
+      position: absolute;
+      bottom: 0;
+      left: 0;
+    }
+    .sidebar__item--Top(2) {
+      flex: 1;
+      margin: 0 50px;
+    }
+    .navbar__wrap--right {
+      display: flex;
+      align-items: center;
+      height: @topSiderHeight;
+      @{deep}(.hover-container) {
+        transition: background 0.2s;
+        height: 100%;
+        line-height: @topSiderHeight;
+        padding: 0 5px;
+        text-align: center;
+        &:hover {
+          background: #f6f6f6;
+        }
+      }
+    }
+  }
+  .header__wrap--fixed {
+    position: fixed !important;
+    width: 100% !important;
+    top: @topSiderHeight !important;
+    left: 0 !important;
+    z-index: 200;
+  }
+  .main__wrap {
+    width: 100%;
+    left: 0;
+    height: calc(~"100% - @{topSiderHeight}");
+    top: @topSiderHeight;
+  }
+  .main__wrap--fixed--all,
+  .main__wrap--fixed--tags {
+    margin-top: @navbarHeight !important;
+    height: calc(~"100% - @{navbarHeight}") !important;
+  }
+}
+
+.app__wrap--LeftTop {
+  .sidebar__wrap {
+    top: @topSiderHeight;
+    left: 0;
+    height: calc(~"100% - @{topSiderHeight}");
+  }
+  .header__wrap {
+    
+  }
+  .main__wrap {
+    width: calc(~"100% - @{menuWidth}");
+    left: @menuWidth;
+    height: calc(~"100% - @{topSiderHeight}");
+    top: @topSiderHeight;
+  }
+  .main__wrap--collapsed {
+    width: calc(~"100% - @{menuMinWidth}");
+    left: @menuMinWidth;
+  }
+}
+
 // 项目配置
 .setting__wrap {
   position: fixed;

+ 22 - 12
src/pages/index/store/modules/app.ts

@@ -2,18 +2,19 @@ import store from '../index'
 import { VuexModule, getModule, Module, Mutation, Action } from 'vuex-module-decorators'
 
 export interface AppState {
-  collapsed: boolean
-  showTags: boolean
-  showLogo: boolean
-  showNavbar: boolean
-  fixedHeader: boolean
-  // fixedTags: boolean
-  // fixedNavbar: boolean
-  layout: string
-  showBreadcrumb: boolean
-  showHamburger: boolean
-  showScreenfull: boolean
-  showUserInfo: boolean
+  collapsed: Boolean
+  showTags: Boolean
+  showLogo: Boolean
+  showNavbar: Boolean
+  fixedHeader: Boolean
+  // fixedTags: Boolean
+  // fixedNavbar: Boolean
+  layout: String
+  showBreadcrumb: Boolean
+  showHamburger: Boolean
+  showScreenfull: Boolean
+  showUserInfo: Boolean
+  title: String
 }
 
 @Module({ dynamic: true, namespaced: true, store, name: 'app' })
@@ -30,6 +31,7 @@ class App extends VuexModule implements AppState {
   public showHamburger = true // 是否显示侧边栏缩收按钮
   public showScreenfull = true // 是否全屏按钮
   public showUserInfo = true // 是否显示用户头像
+  public title = 'vue-antdv-admin' // 标题
 
   @Mutation
   private SET_COLLAPSED(collapsed: boolean): void {
@@ -79,6 +81,10 @@ class App extends VuexModule implements AppState {
   private SET_USERINFO(showUserInfo: boolean): void {
     this.showUserInfo = showUserInfo
   }
+  @Mutation
+  private SET_TITLE(title: string): void {
+    this.title = title
+  }
 
   @Action
   public SetCollapsed(collapsed: boolean): void {
@@ -128,6 +134,10 @@ class App extends VuexModule implements AppState {
   public SetUserInfo(showUserInfo: boolean): void {
     this.SET_USERINFO(showUserInfo)
   }
+  @Action
+  public SetTitle(title: string): void {
+    this.SET_TITLE(title)
+  }
 }
 
 export const appStore = getModule<App>(App)

+ 111 - 7
src/styles/sider.less

@@ -1,4 +1,5 @@
-.app__wrap--Classic {
+.app__wrap--Classic,
+.app__wrap--LeftTop {
   .horizontal-collapse-transition {
     transition: 0s width ease-in-out, 0s padding-left ease-in-out, 0s padding-right ease-in-out !important;
   }
@@ -14,6 +15,26 @@
           color: @menuText !important;
         }
       }
+      .is-opened {
+        .el-menu-item,
+        .el-submenu {
+          background-color: @subMenuBg !important;
+        }
+        .is-active {
+          color: @menuActiveText !important;
+          background-color: @subMenuHover !important;
+          &>.el-submenu__title {
+            color: @menuActiveText !important;
+          }
+        }
+        .is-opened,
+        .el-menu {
+          background-color: @subMenuBg !important;
+          .el-submenu__title {
+            background-color: @subMenuBg !important;
+          }
+        }
+      }
       
       .is-active {
         color: @menuActiveText !important;
@@ -22,11 +43,6 @@
           color: @menuActiveText !important;
         }
       }
-      .is-opened {
-        .el-menu-item {
-          background-color: @subMenuBg !important;
-        }
-      }
       
       .nest-menu {
         background-color: @subMenuBg !important;
@@ -47,7 +63,7 @@
       }
       .el-menu-item:hover {
         color: @subMenuActiveText !important;
-        background-color: @subMenuHover !important;
+        // background-color: @subMenuHover !important;
       }
     }
     .el-menu--collapse {
@@ -106,3 +122,91 @@
     }
   }
 }
+
+.app__wrap--Top {
+  .sidebar-container {
+    background: @topMenuBg;
+    .el-menu {
+      background-color: @topMenuBg !important;
+      .el-menu-item,
+      .el-submenu__title {
+        color: @topMenuText !important;
+        background-color: @topMenuBg !important;
+        i {
+          color: @topMenuText !important;
+        }
+      }
+      
+      .is-active {
+        color: @topMenuActiveText !important;
+        &>.el-submenu__title {
+          color: @topMenuActiveText !important;
+        }
+      }
+      .is-opened {
+        .el-menu-item {
+          background-color: @subMenuBg !important;
+        }
+      }
+      
+      .nest-menu {
+        background-color: @subMenuBg !important;
+        .el-submenu>.el-submenu__title {
+          background-color: @subMenuBg !important;
+        }
+        .is-active {
+          background-color: @subMenuHover !important;
+        }
+      }
+      // menu hover
+      .submenu-title-noDropdown,
+      .el-submenu__title {
+        &:hover {
+          color: @topSMenuActiveText !important;
+        }
+      }
+      .el-menu-item:hover {
+        color: @topMenuActiveText !important;
+      }
+    }
+  }
+}
+
+.top-popper-menu {
+  background: @topMenuBg;
+  .el-menu {
+    background-color: @topMenuBg !important;
+      
+    .el-menu-item,
+    .el-submenu__title {
+      color: @topMenuText !important;
+      background-color: @topMenuBg !important;
+    }
+      
+    .is-active {
+      color: @topMenuActiveText !important;
+      // background-color: @subMenuHover !important;
+      &>.el-submenu__title {
+        color: @topMenuActiveText !important;
+      }
+    }
+      
+    .nest-menu {
+      .is-active {
+        // background-color: @subMenuHover !important;
+      }
+    }
+    // menu hover
+    .submenu-title-noDropdown,
+    .el-submenu__title {
+      &:hover {
+        background-color: @topMenuBg !important;
+        color: @topSMenuHover !important;
+      }
+    }
+    .el-menu-item:hover {
+      color: @topSMenuHover !important;
+      // background-color: @subMenuHover !important;
+    }
+  }
+}

+ 13 - 2
src/styles/variables.less

@@ -12,14 +12,25 @@
 @menuWidth: 200px;
 @menuMinWidth: 64px;
 
-// topSilder
-@topSilderHeight: 50px;
+// topSider
+@topSiderHeight: 60px; // 最低60px,element的最小高度。
+
+@topMenuText: #303133;
+@topMenuActiveText: #2d8cf0;
+@topMenuActiveBg: #fff;
+
+@topMenuBg: #fff;
+
+@topSubMenuBg: #1f2d3d;
+@topSMenuHover: #2d8cf0;
+@topSMenuActiveText: #2d8cf0;
 
 // navbar
 @navbarHeight: 40px;
 
 // tagsView
 @tagsViewHeight: 40px;
+@tagActiveBg: #304156;
 
 // content
 @contentBg: #fff;