Browse Source

feat: merge multi-tab

Sendya 6 years ago
parent
commit
03de3b72ba

+ 56 - 0
src/components/MultiTab/MultiTab.vue

@@ -0,0 +1,56 @@
+<template>
+  <div style="margin: -23px -24px 24px -24px">
+    <a-tabs
+      hideAdd
+      v-model="activeKey"
+      type="editable-card"
+      :tabBarStyle="{ background: '#FFF', margin: 0, paddingLeft: '16px', paddingTop: '1px' }"
+      @edit="onEdit"
+    >
+      <a-tab-pane v-for="page in pages" :style="{ height: 0 }" :tab="page.meta.title" :key="page.fullPath" :closable="pages.length > 1">
+      </a-tab-pane>
+    </a-tabs>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'MultiTab',
+  data () {
+    return {
+      fullPathList: [],
+      pages: [],
+      activeKey: '',
+      newTabIndex: 0
+    }
+  },
+  created () {
+    this.pages.push(this.$route)
+    this.fullPathList.push(this.$route.fullPath)
+  },
+  methods: {
+    onEdit (targetKey, action) {
+      this[action](targetKey)
+    },
+    remove (targetKey) {
+      if (this.pages.length === 1) {
+        return
+      }
+      this.pages = this.pages.filter(page => page.fullPath !== targetKey)
+      this.fullPathList = this.fullPathList.filter(path => path !== targetKey)
+    },
+  },
+  watch: {
+    '$route': function (newVal) {
+      this.activeKey = newVal.fullPath
+      if (this.fullPathList.indexOf(newVal.fullPath) < 0) {
+        this.fullPathList.push(newVal.fullPath)
+        this.pages.push(newVal)
+      }
+    },
+    activeKey: function (newPathKey) {
+      this.$router.push({ path: newPathKey })
+    }
+  }
+}
+</script>

+ 2 - 0
src/components/MultiTab/index.js

@@ -0,0 +1,2 @@
+import MultiTab from './MultiTab'
+export default MultiTab

+ 51 - 48
src/components/layouts/BasicLayout.vue

@@ -1,49 +1,52 @@
-<template>
-  <global-layout>
-    <transition name="page-transition">
-      <route-view />
-    </transition>
-  </global-layout>
-</template>
-
-<script>
-import RouteView from '@/components/layouts/RouteView'
-import GlobalLayout from '@/components/page/GlobalLayout'
-
-export default {
-  name: 'BasicLayout',
-  components: {
-    RouteView,
-    GlobalLayout
-  },
-  data () {
-    return {}
-  }
-}
-</script>
-
-<style lang="less">
-
-  /*
- * The following styles are auto-applied to elements with
- * transition="page-transition" when their visibility is toggled
- * by Vue.js.
- *
- * You can easily play with the page transition by editing
- * these styles.
- */
-
-  .page-transition-enter {
-    opacity: 0;
-  }
-
-  .page-transition-leave-active {
-    opacity: 0;
-  }
-
-  .page-transition-enter .page-transition-container,
-  .page-transition-leave-active .page-transition-container {
-    -webkit-transform: scale(1.1);
-    transform: scale(1.1);
-  }
+<template>
+  <global-layout>
+    <multi-tab v-if="$store.getters.multiTab"></multi-tab>
+    <transition name="page-transition">
+      <route-view />
+    </transition>
+  </global-layout>
+</template>
+
+<script>
+import RouteView from '@/components/layouts/RouteView'
+import MultiTab from '@/components/MultiTab'
+import GlobalLayout from '@/components/page/GlobalLayout'
+
+export default {
+  name: 'BasicLayout',
+  components: {
+    RouteView,
+    MultiTab,
+    GlobalLayout
+  },
+  data () {
+    return {}
+  }
+}
+</script>
+
+<style lang="less">
+
+  /*
+ * The following styles are auto-applied to elements with
+ * transition="page-transition" when their visibility is toggled
+ * by Vue.js.
+ *
+ * You can easily play with the page transition by editing
+ * these styles.
+ */
+
+  .page-transition-enter {
+    opacity: 0;
+  }
+
+  .page-transition-leave-active {
+    opacity: 0;
+  }
+
+  .page-transition-enter .page-transition-container,
+  .page-transition-leave-active .page-transition-container {
+    -webkit-transform: scale(1.1);
+    transform: scale(1.1);
+  }
 </style>

+ 620 - 620
src/components/page/GlobalLayout.vue

@@ -1,621 +1,621 @@
-<template>
-  <a-layout class="layout" :class="[device]">
-
-    <template v-if="isSideMenu()">
-      <a-drawer
-        v-if="isMobile()"
-        :wrapClassName="'drawer-sider ' + navTheme"
-        :closable="false"
-        :visible="collapsed"
-        placement="left"
-        @close="() => this.collapsed = false"
-      >
-        <side-menu
-          :menus="menus"
-          :theme="navTheme"
-          :collapsed="false"
-          :collapsible="true"
-          mode="inline"
-          @menuSelect="menuSelect"></side-menu>
-      </a-drawer>
-
-      <side-menu
-        v-else
-        mode="inline"
-        :menus="menus"
-        :theme="navTheme"
-        :collapsed="collapsed"
-        :collapsible="true"></side-menu>
-    </template>
-    <!-- 下次优化这些代码 -->
-    <template v-else>
-      <a-drawer
-        v-if="isMobile()"
-        :wrapClassName="'drawer-sider ' + navTheme"
-        placement="left"
-        @close="() => this.collapsed = false"
-        :closable="false"
-        :visible="collapsed"
-      >
-        <side-menu
-          :menus="menus"
-          :theme="navTheme"
-          :collapsed="false"
-          :collapsible="true"
-          mode="inline"
-          @menuSelect="menuSelect"></side-menu>
-      </a-drawer>
-    </template>
-
-    <a-layout :class="[layoutMode, `content-width-${contentWidth}`]" :style="{ paddingLeft: contentPaddingLeft, minHeight: '100vh' }">
-      <!-- layout header -->
-      <global-header
-        :mode="layoutMode"
-        :menus="menus"
-        :theme="navTheme"
-        :collapsed="collapsed"
-        :device="device"
-        @toggle="toggle"
-      />
-
-      <!-- layout content -->
-      <a-layout-content :style="{ margin: '24px 24px 0', height: '100%', paddingTop: fixedHeader ? '64px' : '0' }">
-        <slot></slot>
-      </a-layout-content>
-
-      <!-- layout footer -->
-      <a-layout-footer style="padding: 0">
-        <global-footer />
-      </a-layout-footer>
-      <setting-drawer></setting-drawer>
-    </a-layout>
-  </a-layout>
-</template>
-
-<script>
-import SideMenu from '@/components/menu/SideMenu'
-import GlobalHeader from '@/components/page/GlobalHeader'
-import GlobalFooter from '@/components/page/GlobalFooter'
-import SettingDrawer from '@/components/setting/SettingDrawer'
-import { triggerWindowResizeEvent } from '@/utils/util'
-import { mapState, mapActions } from 'vuex'
-import { mixin, mixinDevice } from '@/utils/mixin.js'
-
-export default {
-  name: 'GlobalLayout',
-  components: {
-    SideMenu,
-    GlobalHeader,
-    GlobalFooter,
-    SettingDrawer
-  },
-  mixins: [mixin, mixinDevice],
-  data () {
-    return {
-      collapsed: false,
-      menus: []
-    }
-  },
-  computed: {
-    ...mapState({
-      // 主路由
-      mainMenu: state => state.permission.addRouters
-    }),
-    contentPaddingLeft () {
-      if (!this.fixSidebar || this.isMobile()) {
-        return '0'
-      }
-      if (this.sidebarOpened) {
-        return '256px'
-      }
-      return '80px'
-    }
-  },
-  watch: {
-    sidebarOpened (val) {
-      console.log('sidebarOpened', val)
-      this.collapsed = !val
-    }
-  },
-  created () {
-    this.menus = this.mainMenu.find((item) => item.path === '/').children
-    this.collapsed = !this.sidebarOpened
-  },
-  methods: {
-    ...mapActions(['setSidebar']),
-    toggle () {
-      this.collapsed = !this.collapsed
-      this.setSidebar(!this.collapsed)
-      triggerWindowResizeEvent()
-    },
-    paddingCalc () {
-      let left = ''
-      if (this.sidebarOpened) {
-        left = this.isDesktop() ? '256px' : '80px'
-      } else {
-        left = this.isMobile() && '0' || (this.fixSidebar && '80px' || '0')
-      }
-      console.log('left', left)
-      return left
-    },
-    menuSelect () {
-      if (!this.isDesktop()) {
-        this.collapsed = false
-      }
-    }
-  }
-}
-</script>
-
-<style lang="less">
-  body {
-    // 打开滚动条固定显示
-    overflow-y: scroll;
-
-    &.colorWeak {
-      filter: invert(80%);
-    }
-  }
-
-  .layout.ant-layout {
-    overflow-x: hidden;
-
-    &.mobile,&.tablet {
-
-      .ant-layout-content {
-
-        .content {
-          margin: 24px 0 0;
-        }
-      }
-
-      /**
-       * ant-table-wrapper
-       * 覆盖的表格手机模式样式,如果想修改在手机上表格最低宽度,可以在这里改动
-       */
-      .ant-table-wrapper {
-        .ant-table-content {
-          overflow-y: auto;
-        }
-        .ant-table-body {
-          min-width: 800px;
-        }
-      }
-      .topmenu {
-        /* 必须为 topmenu  才能启用流式布局 */
-        &.content-width-Fluid {
-          .header-index-wide {
-            margin-left: 0;
-          }
-        }
-      }
-    }
-
-    &.mobile {
-      .sidemenu {
-        .ant-header-fixedHeader {
-
-          &.ant-header-side-opened, &.ant-header-side-closed  {
-            width: 100%
-          }
-        }
-      }
-    }
-
-    &.ant-layout-has-sider {
-      flex-direction: row;
-    }
-
-    .trigger {
-      font-size: 20px;
-      line-height: 64px;
-      padding: 0 24px;
-      cursor: pointer;
-      transition: color .3s;
-      &:hover {
-        background: rgba(0, 0, 0, 0.025);
-      }
-    }
-
-    .topmenu {
-      .ant-header-fixedHeader {
-        position: fixed;
-        top: 0;
-        right: 0;
-        z-index: 9;
-        width: 100%;
-        transition: width .2s;
-
-        &.ant-header-side-opened {
-          width: 100%;
-        }
-
-        &.ant-header-side-closed {
-          width: 100%;
-        }
-      }
-      /* 必须为 topmenu  才能启用流式布局 */
-      &.content-width-Fluid {
-        .header-index-wide {
-          max-width: unset;
-          margin-left: 24px;
-        }
-
-        .page-header-index-wide {
-          max-width: unset;
-        }
-      }
-
-    }
-
-    .sidemenu {
-      .ant-header-fixedHeader {
-        position: fixed;
-        top: 0;
-        right: 0;
-        z-index: 9;
-        width: 100%;
-        transition: width .2s;
-
-        &.ant-header-side-opened {
-          width: calc(100% - 256px)
-        }
-
-        &.ant-header-side-closed {
-          width: calc(100% - 80px)
-        }
-      }
-    }
-
-    .header {
-      height: 64px;
-      padding: 0 12px 0 0;
-      background: #fff;
-      box-shadow: 0 1px 4px rgba(0, 21, 41, .08);
-      position: relative;
-    }
-
-    .header, .top-nav-header-index {
-
-      .user-wrapper {
-        float: right;
-        height: 100%;
-
-        .action {
-          cursor: pointer;
-          padding: 0 12px;
-          display: inline-block;
-          transition: all .3s;
-          height: 100%;
-
-          &:hover {
-            background: rgba(0, 0, 0, 0.025);
-          }
-
-          .avatar {
-            margin: 20px 8px 20px 0;
-            color: #1890ff;
-            background: hsla(0, 0%, 100%, .85);
-            vertical-align: middle;
-          }
-
-          .icon {
-            font-size: 16px;
-            padding: 4px;
-          }
-        }
-      }
-
-      &.dark {
-        .user-wrapper {
-
-          .action {
-            color: rgba(255, 255, 255, 0.85);
-
-            &:hover {
-              background: rgba(255, 255, 255, 0.16);
-            }
-          }
-        }
-      }
-    }
-
-    &.mobile,&.tablet {
-      .top-nav-header-index {
-
-        .header-index-wide {
-
-          .header-index-left {
-
-            .trigger {
-              color: rgba(255, 255, 255, 0.85);
-              padding: 0 12px;
-            }
-
-            .logo.top-nav-header {
-              text-align: center;
-              width: 56px;
-              line-height: 58px;
-            }
-          }
-        }
-
-        &.light {
-
-          .header-index-wide {
-
-            .header-index-left {
-              .trigger {
-                color: rgba(0, 0, 0, 0.65);
-              }
-            }
-          }
-          //
-        }
-      }
-    }
-
-    &.tablet {
-      // overflow: hidden; text-overflow:ellipsis; white-space: nowrap;
-      .top-nav-header-index {
-
-        .header-index-wide {
-
-          .header-index-left {
-            .logo > a {
-              overflow: hidden;
-              text-overflow:ellipsis;
-              white-space: nowrap;
-            }
-          }
-        }
-      }
-
-    }
-
-    .top-nav-header-index {
-      box-shadow: 0 1px 4px rgba(0,21,41,.08);
-      position: relative;
-      transition: background .3s,width .2s;
-
-      .header-index-wide {
-        max-width: 1200px;
-        margin: auto;
-        padding-left: 0;
-        display: flex;
-        height: 64px;
-
-        .ant-menu.ant-menu-horizontal {
-          border: none;
-          height: 64px;
-          line-height: 64px;
-        }
-
-        .header-index-left {
-          flex: 1 1;
-          display: flex;
-
-          .logo.top-nav-header {
-            width: 165px;
-            height: 64px;
-            position: relative;
-            line-height: 64px;
-            transition: all .3s;
-            overflow: hidden;
-
-            img {
-              display: inline-block;
-              vertical-align: middle;
-              height: 32px;
-            }
-
-            h1 {
-              color: #fff;
-              display: inline-block;
-              vertical-align: top;
-              font-size: 16px;
-              margin: 0 0 0 12px;
-              font-weight: 400;
-            }
-          }
-        }
-
-        .header-index-right {
-          float: right;
-          height: 64px;
-          overflow: hidden;
-        }
-      }
-
-      &.light {
-        background-color: #fff;
-
-        .header-index-wide {
-          .header-index-left {
-            .logo {
-              h1 {
-                color: #002140;
-              }
-            }
-          }
-        }
-      }
-    }
-
-    // 内容区
-    .layout-content {
-      margin: 24px 24px 0px;
-      height: 100%;
-      height: 64px;
-      padding: 0 12px 0 0;
-    }
-
-  }
-
-  .topmenu {
-    .page-header-index-wide {
-      max-width: 1200px;
-      margin: 0 auto;
-    }
-  }
-
-  // drawer-sider 自定义
-  .ant-drawer.drawer-sider {
-    .sider {
-      box-shadow: none;
-    }
-
-    &.dark {
-      .ant-drawer-content {
-        background-color: rgb(0, 21, 41);
-      }
-    }
-    &.light {
-      box-shadow: none;
-      .ant-drawer-content {
-        background-color: #fff;
-      }
-    }
-
-    .ant-drawer-body {
-      padding: 0
-    }
-  }
-
-  // 菜单样式
-  .sider {
-    box-shadow: 2px 0 6px rgba(0, 21, 41, .35);
-    position: relative;
-    z-index: 10;
-
-    .ant-layout-sider-children:hover {
-      overflow-y: auto;
-    }
-
-    &.ant-fixed-sidemenu {
-      position: fixed;
-      height: 100%;
-    }
-
-    .logo {
-      height: 64px;
-      position: relative;
-      line-height: 64px;
-      padding-left: 24px;
-      -webkit-transition: all .3s;
-      transition: all .3s;
-      background: #002140;
-      overflow: hidden;
-
-      img, h1 {
-        display: inline-block;
-        vertical-align: middle;
-      }
-
-      img {
-        height: 32px;
-      }
-
-      h1 {
-        color: #fff;
-        font-size: 20px;
-        margin: 0 0 0 12px;
-        font-family: "Chinese Quote", -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Helvetica Neue", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
-        font-weight: 600;
-      }
-    }
-
-    &.light {
-      background-color: #fff;
-      box-shadow: 2px 0px 8px 0px rgba(29, 35, 41, 0.05);
-
-      .logo {
-        background: #fff;
-        box-shadow: 1px 1px 0px 0px #e8e8e8;
-
-        h1 {
-          color: unset;
-        }
-      }
-
-      .ant-menu-light {
-        border-right-color: transparent;
-      }
-    }
-
-  }
-
-  // 外置的样式控制
-  .user-dropdown-menu {
-    span {
-      user-select: none;
-    }
-  }
-  .user-dropdown-menu-wrapper.ant-dropdown-menu {
-    padding: 4px 0;
-
-    .ant-dropdown-menu-item {
-      width: 160px;
-    }
-
-    .ant-dropdown-menu-item > .anticon:first-child,
-    .ant-dropdown-menu-item > a > .anticon:first-child,
-    .ant-dropdown-menu-submenu-title > .anticon:first-child
-    .ant-dropdown-menu-submenu-title > a > .anticon:first-child {
-      min-width: 12px;
-      margin-right: 8px;
-    }
-
-  }
-
-  // 数据列表 样式
-  .table-alert {
-    margin-bottom: 16px;
-  }
-
-  .table-page-search-wrapper {
-
-    .ant-form-inline {
-
-      .ant-form-item {
-        display: flex;
-        margin-bottom: 24px;
-        margin-right: 0;
-
-        .ant-form-item-control-wrapper {
-          flex: 1 1;
-          display: inline-block;
-          vertical-align: middle;
-        }
-
-        >.ant-form-item-label {
-          line-height: 32px;
-          padding-right: 8px;
-          width: auto;
-        }
-        .ant-form-item-control {
-          height: 32px;
-          line-height: 32px;
-        }
-      }
-    }
-
-    .table-page-search-submitButtons {
-      display: block;
-      margin-bottom: 24px;
-      white-space: nowrap;
-    }
-
-  }
-
-  .content {
-
-    .table-operator {
-      margin-bottom: 18px;
-
-      button {
-        margin-right: 8px;
-      }
-    }
-  }
+<template>
+  <a-layout class="layout" :class="[device]">
+
+    <template v-if="isSideMenu()">
+      <a-drawer
+        v-if="isMobile()"
+        :wrapClassName="'drawer-sider ' + navTheme"
+        :closable="false"
+        :visible="collapsed"
+        placement="left"
+        @close="() => this.collapsed = false"
+      >
+        <side-menu
+          :menus="menus"
+          :theme="navTheme"
+          :collapsed="false"
+          :collapsible="true"
+          mode="inline"
+          @menuSelect="menuSelect"></side-menu>
+      </a-drawer>
+
+      <side-menu
+        v-else
+        mode="inline"
+        :menus="menus"
+        :theme="navTheme"
+        :collapsed="collapsed"
+        :collapsible="true"></side-menu>
+    </template>
+    <!-- 下次优化这些代码 -->
+    <template v-else>
+      <a-drawer
+        v-if="isMobile()"
+        :wrapClassName="'drawer-sider ' + navTheme"
+        placement="left"
+        @close="() => this.collapsed = false"
+        :closable="false"
+        :visible="collapsed"
+      >
+        <side-menu
+          :menus="menus"
+          :theme="navTheme"
+          :collapsed="false"
+          :collapsible="true"
+          mode="inline"
+          @menuSelect="menuSelect"></side-menu>
+      </a-drawer>
+    </template>
+
+    <a-layout :class="[layoutMode, `content-width-${contentWidth}`]" :style="{ paddingLeft: contentPaddingLeft, minHeight: '100vh' }">
+      <!-- layout header -->
+      <global-header
+        :mode="layoutMode"
+        :menus="menus"
+        :theme="navTheme"
+        :collapsed="collapsed"
+        :device="device"
+        @toggle="toggle"
+      />
+
+      <!-- layout content -->
+      <a-layout-content :style="{ margin: $store.getters.multiTab ? '24px 24px 0' : '24px 24px 0', height: '100%', paddingTop: fixedHeader ? '64px' : '0' }">
+        <slot></slot>
+      </a-layout-content>
+
+      <!-- layout footer -->
+      <a-layout-footer style="padding: 0">
+        <global-footer />
+      </a-layout-footer>
+      <setting-drawer></setting-drawer>
+    </a-layout>
+  </a-layout>
+</template>
+
+<script>
+import SideMenu from '@/components/menu/SideMenu'
+import GlobalHeader from '@/components/page/GlobalHeader'
+import GlobalFooter from '@/components/page/GlobalFooter'
+import SettingDrawer from '@/components/setting/SettingDrawer'
+import { triggerWindowResizeEvent } from '@/utils/util'
+import { mapState, mapActions } from 'vuex'
+import { mixin, mixinDevice } from '@/utils/mixin.js'
+
+export default {
+  name: 'GlobalLayout',
+  components: {
+    SideMenu,
+    GlobalHeader,
+    GlobalFooter,
+    SettingDrawer
+  },
+  mixins: [mixin, mixinDevice],
+  data () {
+    return {
+      collapsed: false,
+      menus: []
+    }
+  },
+  computed: {
+    ...mapState({
+      // 主路由
+      mainMenu: state => state.permission.addRouters
+    }),
+    contentPaddingLeft () {
+      if (!this.fixSidebar || this.isMobile()) {
+        return '0'
+      }
+      if (this.sidebarOpened) {
+        return '256px'
+      }
+      return '80px'
+    }
+  },
+  watch: {
+    sidebarOpened (val) {
+      console.log('sidebarOpened', val)
+      this.collapsed = !val
+    }
+  },
+  created () {
+    this.menus = this.mainMenu.find((item) => item.path === '/').children
+    this.collapsed = !this.sidebarOpened
+  },
+  methods: {
+    ...mapActions(['setSidebar']),
+    toggle () {
+      this.collapsed = !this.collapsed
+      this.setSidebar(!this.collapsed)
+      triggerWindowResizeEvent()
+    },
+    paddingCalc () {
+      let left = ''
+      if (this.sidebarOpened) {
+        left = this.isDesktop() ? '256px' : '80px'
+      } else {
+        left = this.isMobile() && '0' || (this.fixSidebar && '80px' || '0')
+      }
+      console.log('left', left)
+      return left
+    },
+    menuSelect () {
+      if (!this.isDesktop()) {
+        this.collapsed = false
+      }
+    }
+  }
+}
+</script>
+
+<style lang="less">
+  body {
+    // 打开滚动条固定显示
+    overflow-y: scroll;
+
+    &.colorWeak {
+      filter: invert(80%);
+    }
+  }
+
+  .layout.ant-layout {
+    overflow-x: hidden;
+
+    &.mobile,&.tablet {
+
+      .ant-layout-content {
+
+        .content {
+          margin: 24px 0 0;
+        }
+      }
+
+      /**
+       * ant-table-wrapper
+       * 覆盖的表格手机模式样式,如果想修改在手机上表格最低宽度,可以在这里改动
+       */
+      .ant-table-wrapper {
+        .ant-table-content {
+          overflow-y: auto;
+        }
+        .ant-table-body {
+          min-width: 800px;
+        }
+      }
+      .topmenu {
+        /* 必须为 topmenu  才能启用流式布局 */
+        &.content-width-Fluid {
+          .header-index-wide {
+            margin-left: 0;
+          }
+        }
+      }
+    }
+
+    &.mobile {
+      .sidemenu {
+        .ant-header-fixedHeader {
+
+          &.ant-header-side-opened, &.ant-header-side-closed  {
+            width: 100%
+          }
+        }
+      }
+    }
+
+    &.ant-layout-has-sider {
+      flex-direction: row;
+    }
+
+    .trigger {
+      font-size: 20px;
+      line-height: 64px;
+      padding: 0 24px;
+      cursor: pointer;
+      transition: color .3s;
+      &:hover {
+        background: rgba(0, 0, 0, 0.025);
+      }
+    }
+
+    .topmenu {
+      .ant-header-fixedHeader {
+        position: fixed;
+        top: 0;
+        right: 0;
+        z-index: 9;
+        width: 100%;
+        transition: width .2s;
+
+        &.ant-header-side-opened {
+          width: 100%;
+        }
+
+        &.ant-header-side-closed {
+          width: 100%;
+        }
+      }
+      /* 必须为 topmenu  才能启用流式布局 */
+      &.content-width-Fluid {
+        .header-index-wide {
+          max-width: unset;
+          margin-left: 24px;
+        }
+
+        .page-header-index-wide {
+          max-width: unset;
+        }
+      }
+
+    }
+
+    .sidemenu {
+      .ant-header-fixedHeader {
+        position: fixed;
+        top: 0;
+        right: 0;
+        z-index: 9;
+        width: 100%;
+        transition: width .2s;
+
+        &.ant-header-side-opened {
+          width: calc(100% - 256px)
+        }
+
+        &.ant-header-side-closed {
+          width: calc(100% - 80px)
+        }
+      }
+    }
+
+    .header {
+      height: 64px;
+      padding: 0 12px 0 0;
+      background: #fff;
+      box-shadow: 0 1px 4px rgba(0, 21, 41, .08);
+      position: relative;
+    }
+
+    .header, .top-nav-header-index {
+
+      .user-wrapper {
+        float: right;
+        height: 100%;
+
+        .action {
+          cursor: pointer;
+          padding: 0 12px;
+          display: inline-block;
+          transition: all .3s;
+          height: 100%;
+
+          &:hover {
+            background: rgba(0, 0, 0, 0.025);
+          }
+
+          .avatar {
+            margin: 20px 8px 20px 0;
+            color: #1890ff;
+            background: hsla(0, 0%, 100%, .85);
+            vertical-align: middle;
+          }
+
+          .icon {
+            font-size: 16px;
+            padding: 4px;
+          }
+        }
+      }
+
+      &.dark {
+        .user-wrapper {
+
+          .action {
+            color: rgba(255, 255, 255, 0.85);
+
+            &:hover {
+              background: rgba(255, 255, 255, 0.16);
+            }
+          }
+        }
+      }
+    }
+
+    &.mobile,&.tablet {
+      .top-nav-header-index {
+
+        .header-index-wide {
+
+          .header-index-left {
+
+            .trigger {
+              color: rgba(255, 255, 255, 0.85);
+              padding: 0 12px;
+            }
+
+            .logo.top-nav-header {
+              text-align: center;
+              width: 56px;
+              line-height: 58px;
+            }
+          }
+        }
+
+        &.light {
+
+          .header-index-wide {
+
+            .header-index-left {
+              .trigger {
+                color: rgba(0, 0, 0, 0.65);
+              }
+            }
+          }
+          //
+        }
+      }
+    }
+
+    &.tablet {
+      // overflow: hidden; text-overflow:ellipsis; white-space: nowrap;
+      .top-nav-header-index {
+
+        .header-index-wide {
+
+          .header-index-left {
+            .logo > a {
+              overflow: hidden;
+              text-overflow:ellipsis;
+              white-space: nowrap;
+            }
+          }
+        }
+      }
+
+    }
+
+    .top-nav-header-index {
+      box-shadow: 0 1px 4px rgba(0,21,41,.08);
+      position: relative;
+      transition: background .3s,width .2s;
+
+      .header-index-wide {
+        max-width: 1200px;
+        margin: auto;
+        padding-left: 0;
+        display: flex;
+        height: 64px;
+
+        .ant-menu.ant-menu-horizontal {
+          border: none;
+          height: 64px;
+          line-height: 64px;
+        }
+
+        .header-index-left {
+          flex: 1 1;
+          display: flex;
+
+          .logo.top-nav-header {
+            width: 165px;
+            height: 64px;
+            position: relative;
+            line-height: 64px;
+            transition: all .3s;
+            overflow: hidden;
+
+            img {
+              display: inline-block;
+              vertical-align: middle;
+              height: 32px;
+            }
+
+            h1 {
+              color: #fff;
+              display: inline-block;
+              vertical-align: top;
+              font-size: 16px;
+              margin: 0 0 0 12px;
+              font-weight: 400;
+            }
+          }
+        }
+
+        .header-index-right {
+          float: right;
+          height: 64px;
+          overflow: hidden;
+        }
+      }
+
+      &.light {
+        background-color: #fff;
+
+        .header-index-wide {
+          .header-index-left {
+            .logo {
+              h1 {
+                color: #002140;
+              }
+            }
+          }
+        }
+      }
+    }
+
+    // 内容区
+    .layout-content {
+      margin: 24px 24px 0px;
+      height: 100%;
+      height: 64px;
+      padding: 0 12px 0 0;
+    }
+
+  }
+
+  .topmenu {
+    .page-header-index-wide {
+      max-width: 1200px;
+      margin: 0 auto;
+    }
+  }
+
+  // drawer-sider 自定义
+  .ant-drawer.drawer-sider {
+    .sider {
+      box-shadow: none;
+    }
+
+    &.dark {
+      .ant-drawer-content {
+        background-color: rgb(0, 21, 41);
+      }
+    }
+    &.light {
+      box-shadow: none;
+      .ant-drawer-content {
+        background-color: #fff;
+      }
+    }
+
+    .ant-drawer-body {
+      padding: 0
+    }
+  }
+
+  // 菜单样式
+  .sider {
+    box-shadow: 2px 0 6px rgba(0, 21, 41, .35);
+    position: relative;
+    z-index: 10;
+
+    .ant-layout-sider-children:hover {
+      overflow-y: auto;
+    }
+
+    &.ant-fixed-sidemenu {
+      position: fixed;
+      height: 100%;
+    }
+
+    .logo {
+      height: 64px;
+      position: relative;
+      line-height: 64px;
+      padding-left: 24px;
+      -webkit-transition: all .3s;
+      transition: all .3s;
+      background: #002140;
+      overflow: hidden;
+
+      img, h1 {
+        display: inline-block;
+        vertical-align: middle;
+      }
+
+      img {
+        height: 32px;
+      }
+
+      h1 {
+        color: #fff;
+        font-size: 20px;
+        margin: 0 0 0 12px;
+        font-family: "Chinese Quote", -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Helvetica Neue", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
+        font-weight: 600;
+      }
+    }
+
+    &.light {
+      background-color: #fff;
+      box-shadow: 2px 0px 8px 0px rgba(29, 35, 41, 0.05);
+
+      .logo {
+        background: #fff;
+        box-shadow: 1px 1px 0px 0px #e8e8e8;
+
+        h1 {
+          color: unset;
+        }
+      }
+
+      .ant-menu-light {
+        border-right-color: transparent;
+      }
+    }
+
+  }
+
+  // 外置的样式控制
+  .user-dropdown-menu {
+    span {
+      user-select: none;
+    }
+  }
+  .user-dropdown-menu-wrapper.ant-dropdown-menu {
+    padding: 4px 0;
+
+    .ant-dropdown-menu-item {
+      width: 160px;
+    }
+
+    .ant-dropdown-menu-item > .anticon:first-child,
+    .ant-dropdown-menu-item > a > .anticon:first-child,
+    .ant-dropdown-menu-submenu-title > .anticon:first-child
+    .ant-dropdown-menu-submenu-title > a > .anticon:first-child {
+      min-width: 12px;
+      margin-right: 8px;
+    }
+
+  }
+
+  // 数据列表 样式
+  .table-alert {
+    margin-bottom: 16px;
+  }
+
+  .table-page-search-wrapper {
+
+    .ant-form-inline {
+
+      .ant-form-item {
+        display: flex;
+        margin-bottom: 24px;
+        margin-right: 0;
+
+        .ant-form-item-control-wrapper {
+          flex: 1 1;
+          display: inline-block;
+          vertical-align: middle;
+        }
+
+        >.ant-form-item-label {
+          line-height: 32px;
+          padding-right: 8px;
+          width: auto;
+        }
+        .ant-form-item-control {
+          height: 32px;
+          line-height: 32px;
+        }
+      }
+    }
+
+    .table-page-search-submitButtons {
+      display: block;
+      margin-bottom: 24px;
+      white-space: nowrap;
+    }
+
+  }
+
+  .content {
+
+    .table-operator {
+      margin-bottom: 18px;
+
+      button {
+        margin-right: 8px;
+      }
+    }
+  }
 </style>

+ 365 - 354
src/components/setting/SettingDrawer.vue

@@ -1,355 +1,366 @@
-<template>
-  <div class="setting-drawer" ref="settingDrawer">
-    <a-drawer
-      width="300"
-      placement="right"
-      :closable="false"
-      @close="onClose"
-      :visible="visible"
-      :getContainer="() => $refs.settingDrawer"
-      :style="{}"
-    >
-      <div class="setting-drawer-index-content">
-
-        <div :style="{ marginBottom: '24px' }">
-          <h3 class="setting-drawer-index-title">整体风格设置</h3>
-
-          <div class="setting-drawer-index-blockChecbox">
-            <a-tooltip>
-              <template slot="title">
-                暗色菜单风格
-              </template>
-              <div class="setting-drawer-index-item" @click="handleMenuTheme('dark')">
-                <img src="https://gw.alipayobjects.com/zos/rmsportal/LCkqqYNmvBEbokSDscrm.svg" alt="dark">
-                <div class="setting-drawer-index-selectIcon" v-if="navTheme === 'dark'">
-                  <a-icon type="check"/>
-                </div>
-              </div>
-            </a-tooltip>
-
-            <a-tooltip>
-              <template slot="title">
-                亮色菜单风格
-              </template>
-              <div class="setting-drawer-index-item" @click="handleMenuTheme('light')">
-                <img src="https://gw.alipayobjects.com/zos/rmsportal/jpRkZQMyYRryryPNtyIC.svg" alt="light">
-                <div class="setting-drawer-index-selectIcon" v-if="navTheme !== 'dark'">
-                  <a-icon type="check"/>
-                </div>
-              </div>
-            </a-tooltip>
-          </div>
-        </div>
-
-        <div :style="{ marginBottom: '24px' }">
-          <h3 class="setting-drawer-index-title">主题色</h3>
-
-          <div style="height: 20px">
-            <a-tooltip class="setting-drawer-theme-color-colorBlock" v-for="(item, index) in colorList" :key="index">
-              <template slot="title">
-                {{ item.key }}
-              </template>
-              <a-tag :color="item.color" @click="changeColor(item.color)">
-                <a-icon type="check" v-if="item.color === primaryColor"></a-icon>
-              </a-tag>
-            </a-tooltip>
-
-          </div>
-        </div>
-        <a-divider />
-
-        <div :style="{ marginBottom: '24px' }">
-          <h3 class="setting-drawer-index-title">导航模式</h3>
-
-          <div class="setting-drawer-index-blockChecbox">
-            <a-tooltip>
-              <template slot="title">
-                侧边栏导航
-              </template>
-              <div class="setting-drawer-index-item" @click="handleLayout('sidemenu')">
-                <img src="https://gw.alipayobjects.com/zos/rmsportal/JopDzEhOqwOjeNTXkoje.svg" alt="sidemenu">
-                <div class="setting-drawer-index-selectIcon" v-if="layoutMode === 'sidemenu'">
-                  <a-icon type="check"/>
-                </div>
-              </div>
-            </a-tooltip>
-
-            <a-tooltip>
-              <template slot="title">
-                顶部栏导航
-              </template>
-              <div class="setting-drawer-index-item" @click="handleLayout('topmenu')">
-                <img src="https://gw.alipayobjects.com/zos/rmsportal/KDNDBbriJhLwuqMoxcAr.svg" alt="topmenu">
-                <div class="setting-drawer-index-selectIcon" v-if="layoutMode !== 'sidemenu'">
-                  <a-icon type="check"/>
-                </div>
-              </div>
-            </a-tooltip>
-          </div>
-          <div :style="{ marginTop: '24px' }">
-            <a-list :split="false">
-              <a-list-item>
-                <a-tooltip slot="actions">
-                  <template slot="title">
-                    该设定仅 [顶部栏导航] 时有效
-                  </template>
-                  <a-select size="small" style="width: 80px;" :defaultValue="contentWidth" @change="handleContentWidthChange">
-                    <a-select-option value="Fixed">固定</a-select-option>
-                    <a-select-option value="Fluid" v-if="layoutMode !== 'sidemenu'">流式</a-select-option>
-                  </a-select>
-                </a-tooltip>
-                <a-list-item-meta>
-                  <div slot="title">内容区域宽度</div>
-                </a-list-item-meta>
-              </a-list-item>
-              <a-list-item>
-                <a-switch slot="actions" size="small" :defaultChecked="fixedHeader" @change="handleFixedHeader" />
-                <a-list-item-meta>
-                  <div slot="title">固定 Header</div>
-                </a-list-item-meta>
-              </a-list-item>
-              <a-list-item>
-                <a-switch slot="actions" size="small" :disabled="!fixedHeader" :defaultChecked="autoHideHeader" @change="handleFixedHeaderHidden" />
-                <a-list-item-meta>
-                  <div slot="title" :style="{ textDecoration: !fixedHeader ? 'line-through' : 'unset' }">下滑时隐藏 Header</div>
-                </a-list-item-meta>
-              </a-list-item>
-              <a-list-item >
-                <a-switch slot="actions" size="small" :disabled="(layoutMode === 'topmenu')" :defaultChecked="fixSiderbar" @change="handleFixSiderbar" />
-                <a-list-item-meta>
-                  <div slot="title" :style="{ textDecoration: layoutMode === 'topmenu' ? 'line-through' : 'unset' }">固定侧边菜单</div>
-                </a-list-item-meta>
-              </a-list-item>
-            </a-list>
-          </div>
-        </div>
-        <a-divider />
-
-        <div :style="{ marginBottom: '24px' }">
-          <h3 class="setting-drawer-index-title">其他设置</h3>
-          <div>
-            <a-list :split="false">
-              <a-list-item>
-                <a-switch slot="actions" size="small" :defaultChecked="colorWeak" @change="onColorWeak" />
-                <a-list-item-meta>
-                  <div slot="title">色弱模式</div>
-                </a-list-item-meta>
-              </a-list-item>
-            </a-list>
-          </div>
-        </div>
-        <a-divider />
-        <div :style="{ marginBottom: '24px' }">
-          <a-button
-            @click="doCopy"
-            icon="copy"
-            block
-          >拷贝设置</a-button>
-          <a-alert type="warning" :style="{ marginTop: '24px' }">
-            <span slot="message">
-              配置栏只在开发环境用于预览,生产环境不会展现,请手动修改配置文件
-              <a href="https://github.com/sendya/ant-design-pro-vue/blob/master/src/defaultSettings.js" target="_blank">src/defaultSettings.js</a>
-            </span>
-          </a-alert>
-        </div>
-      </div>
-      <div class="setting-drawer-index-handle" @click="toggle">
-        <a-icon type="setting" v-if="!visible"/>
-        <a-icon type="close" v-else/>
-      </div>
-    </a-drawer>
-  </div>
-</template>
-
-<script>
-import DetailList from '@/components/tools/DetailList'
-import SettingItem from '@/components/setting/SettingItem'
-import config from '@/config/defaultSettings'
-import { updateTheme, updateColorWeak, colorList } from '@/components/tools/setting'
-import { mixin, mixinDevice } from '@/utils/mixin'
-
-export default {
-  components: {
-    DetailList,
-    SettingItem
-  },
-  mixins: [mixin, mixinDevice],
-  data () {
-    return {
-      visible: true,
-      colorList,
-      baseConfig: Object.assign({}, config)
-    }
-  },
-  watch: {
-
-  },
-  mounted () {
-    const vm = this
-    setTimeout(() => {
-      vm.visible = false
-    }, 16)
-    // 当主题色不是默认色时,才进行主题编译
-    if (this.primaryColor !== config.primaryColor) {
-      updateTheme(this.primaryColor)
-    }
-    if (this.colorWeak !== config.colorWeak) {
-      updateColorWeak(this.colorWeak)
-    }
-  },
-  methods: {
-    showDrawer () {
-      this.visible = true
-    },
-    onClose () {
-      this.visible = false
-    },
-    toggle () {
-      this.visible = !this.visible
-    },
-    onColorWeak (checked) {
-      this.baseConfig.colorWeak = checked
-      this.$store.dispatch('ToggleWeak', checked)
-      updateColorWeak(checked)
-    },
-    handleMenuTheme (theme) {
-      this.baseConfig.navTheme = theme
-      this.$store.dispatch('ToggleTheme', theme)
-    },
-    doCopy () {
-      const text = `export default {
-  primaryColor: '${this.baseConfig.primaryColor}', // primary color of ant design
-  navTheme: '${this.baseConfig.navTheme}', // theme for nav menu
-  layout: '${this.baseConfig.layout}', // nav menu position: sidemenu or topmenu
-  contentWidth: '${this.baseConfig.contentWidth}', // layout of content: Fluid or Fixed, only works when layout is topmenu
-  fixedHeader: ${this.baseConfig.fixedHeader}, // sticky header
-  fixSiderbar: ${this.baseConfig.fixSiderbar}, // sticky siderbar
-  autoHideHeader: ${this.baseConfig.autoHideHeader}, //  auto hide header
-  colorWeak: ${this.baseConfig.colorWeak},
-  // vue-ls options
-  storageOptions: {
-    namespace: 'pro__',
-    name: 'ls',
-    storage: 'local',
-  }
-}`
-      this.$copyText(text).then(message => {
-        console.log('copy', message)
-        this.$message.success('复制完毕')
-      }).catch(err => {
-        console.log('copy.err', err)
-        this.$message.error('复制失败')
-      })
-    },
-    handleLayout (mode) {
-      this.baseConfig.layout = mode
-      this.$store.dispatch('ToggleLayoutMode', mode)
-      // 因为顶部菜单不能固定左侧菜单栏,所以强制关闭
-      //
-      this.handleFixSiderbar(false)
-    },
-    handleContentWidthChange (type) {
-      this.baseConfig.contentWidth = type
-      this.$store.dispatch('ToggleContentWidth', type)
-    },
-    changeColor (color) {
-      this.baseConfig.primaryColor = color
-      if (this.primaryColor !== color) {
-        this.$store.dispatch('ToggleColor', color)
-        updateTheme(color)
-      }
-    },
-    handleFixedHeader (fixed) {
-      this.baseConfig.fixedHeader = fixed
-      this.$store.dispatch('ToggleFixedHeader', fixed)
-    },
-    handleFixedHeaderHidden (autoHidden) {
-      this.baseConfig.autoHideHeader = autoHidden
-      this.$store.dispatch('ToggleFixedHeaderHidden', autoHidden)
-    },
-    handleFixSiderbar (fixed) {
-      if (this.layoutMode === 'topmenu') {
-        this.baseConfig.fixSiderbar = false
-        this.$store.dispatch('ToggleFixSiderbar', false)
-        return
-      }
-      this.baseConfig.fixSiderbar = fixed
-      this.$store.dispatch('ToggleFixSiderbar', fixed)
-    }
-  }
-}
-</script>
-
-<style lang="less" scoped>
-
-  .setting-drawer-index-content {
-
-    .setting-drawer-index-blockChecbox {
-      display: flex;
-
-      .setting-drawer-index-item {
-        margin-right: 16px;
-        position: relative;
-        border-radius: 4px;
-        cursor: pointer;
-
-        img {
-          width: 48px;
-        }
-
-        .setting-drawer-index-selectIcon {
-          position: absolute;
-          top: 0;
-          right: 0;
-          width: 100%;
-          padding-top: 15px;
-          padding-left: 24px;
-          height: 100%;
-          color: #1890ff;
-          font-size: 14px;
-          font-weight: 700;
-        }
-      }
-    }
-    .setting-drawer-theme-color-colorBlock {
-      width: 20px;
-      height: 20px;
-      border-radius: 2px;
-      float: left;
-      cursor: pointer;
-      margin-right: 8px;
-      padding-left: 0px;
-      padding-right: 0px;
-      text-align: center;
-      color: #fff;
-      font-weight: 700;
-
-      i {
-        font-size: 14px;
-      }
-    }
-  }
-
-  .setting-drawer-index-handle {
-    position: absolute;
-    top: 240px;
-    background: #1890ff;
-    width: 48px;
-    height: 48px;
-    right: 300px;
-    display: flex;
-    justify-content: center;
-    align-items: center;
-    cursor: pointer;
-    pointer-events: auto;
-    z-index: 1001;
-    text-align: center;
-    font-size: 16px;
-    border-radius: 4px 0 0 4px;
-
-    i {
-      color: rgb(255, 255, 255);
-      font-size: 20px;
-    }
-  }
+<template>
+  <div class="setting-drawer" ref="settingDrawer">
+    <a-drawer
+      width="300"
+      placement="right"
+      :closable="false"
+      @close="onClose"
+      :visible="visible"
+      :getContainer="() => $refs.settingDrawer"
+      :style="{}"
+    >
+      <div class="setting-drawer-index-content">
+
+        <div :style="{ marginBottom: '24px' }">
+          <h3 class="setting-drawer-index-title">整体风格设置</h3>
+
+          <div class="setting-drawer-index-blockChecbox">
+            <a-tooltip>
+              <template slot="title">
+                暗色菜单风格
+              </template>
+              <div class="setting-drawer-index-item" @click="handleMenuTheme('dark')">
+                <img src="https://gw.alipayobjects.com/zos/rmsportal/LCkqqYNmvBEbokSDscrm.svg" alt="dark">
+                <div class="setting-drawer-index-selectIcon" v-if="navTheme === 'dark'">
+                  <a-icon type="check"/>
+                </div>
+              </div>
+            </a-tooltip>
+
+            <a-tooltip>
+              <template slot="title">
+                亮色菜单风格
+              </template>
+              <div class="setting-drawer-index-item" @click="handleMenuTheme('light')">
+                <img src="https://gw.alipayobjects.com/zos/rmsportal/jpRkZQMyYRryryPNtyIC.svg" alt="light">
+                <div class="setting-drawer-index-selectIcon" v-if="navTheme !== 'dark'">
+                  <a-icon type="check"/>
+                </div>
+              </div>
+            </a-tooltip>
+          </div>
+        </div>
+
+        <div :style="{ marginBottom: '24px' }">
+          <h3 class="setting-drawer-index-title">主题色</h3>
+
+          <div style="height: 20px">
+            <a-tooltip class="setting-drawer-theme-color-colorBlock" v-for="(item, index) in colorList" :key="index">
+              <template slot="title">
+                {{ item.key }}
+              </template>
+              <a-tag :color="item.color" @click="changeColor(item.color)">
+                <a-icon type="check" v-if="item.color === primaryColor"></a-icon>
+              </a-tag>
+            </a-tooltip>
+
+          </div>
+        </div>
+        <a-divider />
+
+        <div :style="{ marginBottom: '24px' }">
+          <h3 class="setting-drawer-index-title">导航模式</h3>
+
+          <div class="setting-drawer-index-blockChecbox">
+            <a-tooltip>
+              <template slot="title">
+                侧边栏导航
+              </template>
+              <div class="setting-drawer-index-item" @click="handleLayout('sidemenu')">
+                <img src="https://gw.alipayobjects.com/zos/rmsportal/JopDzEhOqwOjeNTXkoje.svg" alt="sidemenu">
+                <div class="setting-drawer-index-selectIcon" v-if="layoutMode === 'sidemenu'">
+                  <a-icon type="check"/>
+                </div>
+              </div>
+            </a-tooltip>
+
+            <a-tooltip>
+              <template slot="title">
+                顶部栏导航
+              </template>
+              <div class="setting-drawer-index-item" @click="handleLayout('topmenu')">
+                <img src="https://gw.alipayobjects.com/zos/rmsportal/KDNDBbriJhLwuqMoxcAr.svg" alt="topmenu">
+                <div class="setting-drawer-index-selectIcon" v-if="layoutMode !== 'sidemenu'">
+                  <a-icon type="check"/>
+                </div>
+              </div>
+            </a-tooltip>
+          </div>
+          <div :style="{ marginTop: '24px' }">
+            <a-list :split="false">
+              <a-list-item>
+                <a-tooltip slot="actions">
+                  <template slot="title">
+                    该设定仅 [顶部栏导航] 时有效
+                  </template>
+                  <a-select size="small" style="width: 80px;" :defaultValue="contentWidth" @change="handleContentWidthChange">
+                    <a-select-option value="Fixed">固定</a-select-option>
+                    <a-select-option value="Fluid" v-if="layoutMode !== 'sidemenu'">流式</a-select-option>
+                  </a-select>
+                </a-tooltip>
+                <a-list-item-meta>
+                  <div slot="title">内容区域宽度</div>
+                </a-list-item-meta>
+              </a-list-item>
+              <a-list-item>
+                <a-switch slot="actions" size="small" :defaultChecked="fixedHeader" @change="handleFixedHeader" />
+                <a-list-item-meta>
+                  <div slot="title">固定 Header</div>
+                </a-list-item-meta>
+              </a-list-item>
+              <a-list-item>
+                <a-switch slot="actions" size="small" :disabled="!fixedHeader" :defaultChecked="autoHideHeader" @change="handleFixedHeaderHidden" />
+                <a-list-item-meta>
+                  <div slot="title" :style="{ textDecoration: !fixedHeader ? 'line-through' : 'unset' }">下滑时隐藏 Header</div>
+                </a-list-item-meta>
+              </a-list-item>
+              <a-list-item >
+                <a-switch slot="actions" size="small" :disabled="(layoutMode === 'topmenu')" :defaultChecked="fixSiderbar" @change="handleFixSiderbar" />
+                <a-list-item-meta>
+                  <div slot="title" :style="{ textDecoration: layoutMode === 'topmenu' ? 'line-through' : 'unset' }">固定侧边菜单</div>
+                </a-list-item-meta>
+              </a-list-item>
+            </a-list>
+          </div>
+        </div>
+        <a-divider />
+
+        <div :style="{ marginBottom: '24px' }">
+          <h3 class="setting-drawer-index-title">其他设置</h3>
+          <div>
+            <a-list :split="false">
+              <a-list-item>
+                <a-switch slot="actions" size="small" :defaultChecked="colorWeak" @change="onColorWeak" />
+                <a-list-item-meta>
+                  <div slot="title">色弱模式</div>
+                </a-list-item-meta>
+              </a-list-item>
+              <a-list-item>
+                <a-switch slot="actions" size="small" :defaultChecked="multiTab" @change="onMultiTab" />
+                <a-list-item-meta>
+                  <div slot="title">多页签模式</div>
+                </a-list-item-meta>
+              </a-list-item>
+            </a-list>
+          </div>
+        </div>
+        <a-divider />
+        <div :style="{ marginBottom: '24px' }">
+          <a-button
+            @click="doCopy"
+            icon="copy"
+            block
+          >拷贝设置</a-button>
+          <a-alert type="warning" :style="{ marginTop: '24px' }">
+            <span slot="message">
+              配置栏只在开发环境用于预览,生产环境不会展现,请手动修改配置文件
+              <a href="https://github.com/sendya/ant-design-pro-vue/blob/master/src/defaultSettings.js" target="_blank">src/defaultSettings.js</a>
+            </span>
+          </a-alert>
+        </div>
+      </div>
+      <div class="setting-drawer-index-handle" @click="toggle">
+        <a-icon type="setting" v-if="!visible"/>
+        <a-icon type="close" v-else/>
+      </div>
+    </a-drawer>
+  </div>
+</template>
+
+<script>
+import DetailList from '@/components/tools/DetailList'
+import SettingItem from '@/components/setting/SettingItem'
+import config from '@/config/defaultSettings'
+import { updateTheme, updateColorWeak, colorList } from '@/components/tools/setting'
+import { mixin, mixinDevice } from '@/utils/mixin'
+
+export default {
+  components: {
+    DetailList,
+    SettingItem
+  },
+  mixins: [mixin, mixinDevice],
+  data () {
+    return {
+      visible: true,
+      colorList,
+      baseConfig: Object.assign({}, config)
+    }
+  },
+  watch: {
+
+  },
+  mounted () {
+    const vm = this
+    setTimeout(() => {
+      vm.visible = false
+    }, 16)
+    // 当主题色不是默认色时,才进行主题编译
+    if (this.primaryColor !== config.primaryColor) {
+      updateTheme(this.primaryColor)
+    }
+    if (this.colorWeak !== config.colorWeak) {
+      updateColorWeak(this.colorWeak)
+    }
+  },
+  methods: {
+    showDrawer () {
+      this.visible = true
+    },
+    onClose () {
+      this.visible = false
+    },
+    toggle () {
+      this.visible = !this.visible
+    },
+    onColorWeak (checked) {
+      this.baseConfig.colorWeak = checked
+      this.$store.dispatch('ToggleWeak', checked)
+      updateColorWeak(checked)
+    },
+    onMultiTab (checked) {
+      this.baseConfig.multiTab = checked
+      this.$store.dispatch('ToggleMultiTab', checked)
+    },
+    handleMenuTheme (theme) {
+      this.baseConfig.navTheme = theme
+      this.$store.dispatch('ToggleTheme', theme)
+    },
+    doCopy () {
+      const text = `export default {
+  primaryColor: '${this.baseConfig.primaryColor}', // primary color of ant design
+  navTheme: '${this.baseConfig.navTheme}', // theme for nav menu
+  layout: '${this.baseConfig.layout}', // nav menu position: sidemenu or topmenu
+  contentWidth: '${this.baseConfig.contentWidth}', // layout of content: Fluid or Fixed, only works when layout is topmenu
+  fixedHeader: ${this.baseConfig.fixedHeader}, // sticky header
+  fixSiderbar: ${this.baseConfig.fixSiderbar}, // sticky siderbar
+  autoHideHeader: ${this.baseConfig.autoHideHeader}, //  auto hide header
+  colorWeak: ${this.baseConfig.colorWeak},
+  multiTab: ${this.baseConfig.multiTab},
+  // vue-ls options
+  storageOptions: {
+    namespace: 'pro__',
+    name: 'ls',
+    storage: 'local',
+  }
+}`
+      this.$copyText(text).then(message => {
+        console.log('copy', message)
+        this.$message.success('复制完毕')
+      }).catch(err => {
+        console.log('copy.err', err)
+        this.$message.error('复制失败')
+      })
+    },
+    handleLayout (mode) {
+      this.baseConfig.layout = mode
+      this.$store.dispatch('ToggleLayoutMode', mode)
+      // 因为顶部菜单不能固定左侧菜单栏,所以强制关闭
+      //
+      this.handleFixSiderbar(false)
+    },
+    handleContentWidthChange (type) {
+      this.baseConfig.contentWidth = type
+      this.$store.dispatch('ToggleContentWidth', type)
+    },
+    changeColor (color) {
+      this.baseConfig.primaryColor = color
+      if (this.primaryColor !== color) {
+        this.$store.dispatch('ToggleColor', color)
+        updateTheme(color)
+      }
+    },
+    handleFixedHeader (fixed) {
+      this.baseConfig.fixedHeader = fixed
+      this.$store.dispatch('ToggleFixedHeader', fixed)
+    },
+    handleFixedHeaderHidden (autoHidden) {
+      this.baseConfig.autoHideHeader = autoHidden
+      this.$store.dispatch('ToggleFixedHeaderHidden', autoHidden)
+    },
+    handleFixSiderbar (fixed) {
+      if (this.layoutMode === 'topmenu') {
+        this.baseConfig.fixSiderbar = false
+        this.$store.dispatch('ToggleFixSiderbar', false)
+        return
+      }
+      this.baseConfig.fixSiderbar = fixed
+      this.$store.dispatch('ToggleFixSiderbar', fixed)
+    }
+  }
+}
+</script>
+
+<style lang="less" scoped>
+
+  .setting-drawer-index-content {
+
+    .setting-drawer-index-blockChecbox {
+      display: flex;
+
+      .setting-drawer-index-item {
+        margin-right: 16px;
+        position: relative;
+        border-radius: 4px;
+        cursor: pointer;
+
+        img {
+          width: 48px;
+        }
+
+        .setting-drawer-index-selectIcon {
+          position: absolute;
+          top: 0;
+          right: 0;
+          width: 100%;
+          padding-top: 15px;
+          padding-left: 24px;
+          height: 100%;
+          color: #1890ff;
+          font-size: 14px;
+          font-weight: 700;
+        }
+      }
+    }
+    .setting-drawer-theme-color-colorBlock {
+      width: 20px;
+      height: 20px;
+      border-radius: 2px;
+      float: left;
+      cursor: pointer;
+      margin-right: 8px;
+      padding-left: 0px;
+      padding-right: 0px;
+      text-align: center;
+      color: #fff;
+      font-weight: 700;
+
+      i {
+        font-size: 14px;
+      }
+    }
+  }
+
+  .setting-drawer-index-handle {
+    position: absolute;
+    top: 240px;
+    background: #1890ff;
+    width: 48px;
+    height: 48px;
+    right: 300px;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    cursor: pointer;
+    pointer-events: auto;
+    z-index: 1001;
+    text-align: center;
+    font-size: 16px;
+    border-radius: 4px 0 0 4px;
+
+    i {
+      color: rgb(255, 255, 255);
+      font-size: 20px;
+    }
+  }
 </style>

+ 1 - 0
src/config/defaultSettings.js

@@ -22,6 +22,7 @@ export default {
   fixSiderbar: false, // sticky siderbar
   autoHideHeader: false, //  auto hide header
   colorWeak: false,
+  multiTab: false,
   // vue-ls options
   storageOptions: {
     namespace: 'pro__', // key prefix

+ 31 - 28
src/core/bootstrap.js

@@ -1,29 +1,32 @@
-import Vue from 'vue'
-import store from '@/store/'
-import {
-  ACCESS_TOKEN,
-  DEFAULT_COLOR,
-  DEFAULT_THEME,
-  DEFAULT_LAYOUT_MODE,
-  DEFAULT_COLOR_WEAK,
-  SIDEBAR_TYPE,
-  DEFAULT_FIXED_HEADER,
-  DEFAULT_FIXED_HEADER_HIDDEN,
-  DEFAULT_FIXED_SIDEMENU,
-  DEFAULT_CONTENT_WIDTH_TYPE
-} from '@/store/mutation-types'
-import config from '@/config/defaultSettings'
-
-export default function Initializer () {
-  store.commit('SET_SIDEBAR_TYPE', Vue.ls.get(SIDEBAR_TYPE, true))
-  store.commit('TOGGLE_THEME', Vue.ls.get(DEFAULT_THEME, config.navTheme))
-  store.commit('TOGGLE_LAYOUT_MODE', Vue.ls.get(DEFAULT_LAYOUT_MODE, config.layout))
-  store.commit('TOGGLE_FIXED_HEADER', Vue.ls.get(DEFAULT_FIXED_HEADER, config.fixedHeader))
-  store.commit('TOGGLE_FIXED_SIDERBAR', Vue.ls.get(DEFAULT_FIXED_SIDEMENU, config.fixSiderbar))
-  store.commit('TOGGLE_CONTENT_WIDTH', Vue.ls.get(DEFAULT_CONTENT_WIDTH_TYPE, config.contentWidth))
-  store.commit('TOGGLE_FIXED_HEADER_HIDDEN', Vue.ls.get(DEFAULT_FIXED_HEADER_HIDDEN, config.autoHideHeader))
-  store.commit('TOGGLE_WEAK', Vue.ls.get(DEFAULT_COLOR_WEAK, config.colorWeak))
-  store.commit('TOGGLE_COLOR', Vue.ls.get(DEFAULT_COLOR, config.primaryColor))
-  store.commit('SET_TOKEN', Vue.ls.get(ACCESS_TOKEN))
-  // last step
+import Vue from 'vue'
+import store from '@/store/'
+import {
+  ACCESS_TOKEN,
+  DEFAULT_COLOR,
+  DEFAULT_THEME,
+  DEFAULT_LAYOUT_MODE,
+  DEFAULT_COLOR_WEAK,
+  SIDEBAR_TYPE,
+  DEFAULT_FIXED_HEADER,
+  DEFAULT_FIXED_HEADER_HIDDEN,
+  DEFAULT_FIXED_SIDEMENU,
+  DEFAULT_CONTENT_WIDTH_TYPE,
+  DEFAULT_MULTI_TAB
+} from '@/store/mutation-types'
+import config from '@/config/defaultSettings'
+
+export default function Initializer () {
+  store.commit('SET_SIDEBAR_TYPE', Vue.ls.get(SIDEBAR_TYPE, true))
+  store.commit('TOGGLE_THEME', Vue.ls.get(DEFAULT_THEME, config.navTheme))
+  store.commit('TOGGLE_LAYOUT_MODE', Vue.ls.get(DEFAULT_LAYOUT_MODE, config.layout))
+  store.commit('TOGGLE_FIXED_HEADER', Vue.ls.get(DEFAULT_FIXED_HEADER, config.fixedHeader))
+  store.commit('TOGGLE_FIXED_SIDERBAR', Vue.ls.get(DEFAULT_FIXED_SIDEMENU, config.fixSiderbar))
+  store.commit('TOGGLE_CONTENT_WIDTH', Vue.ls.get(DEFAULT_CONTENT_WIDTH_TYPE, config.contentWidth))
+  store.commit('TOGGLE_FIXED_HEADER_HIDDEN', Vue.ls.get(DEFAULT_FIXED_HEADER_HIDDEN, config.autoHideHeader))
+  store.commit('TOGGLE_WEAK', Vue.ls.get(DEFAULT_COLOR_WEAK, config.colorWeak))
+  store.commit('TOGGLE_COLOR', Vue.ls.get(DEFAULT_COLOR, config.primaryColor))
+  store.commit('TOGGLE_MULTI_TAB', Vue.ls.get(DEFAULT_MULTI_TAB, config.multiTab))
+  store.commit('SET_TOKEN', Vue.ls.get(ACCESS_TOKEN))
+
+  // last step
 }

+ 2 - 1
src/store/getters.js

@@ -8,7 +8,8 @@ const getters = {
   welcome: state => state.user.welcome,
   roles: state => state.user.roles,
   userInfo: state => state.user.info,
-  addRouters: state => state.permission.addRouters
+  addRouters: state => state.permission.addRouters,
+  multiTab: state => state.app.multiTab
 }
 
 export default getters

+ 11 - 2
src/store/modules/app.js

@@ -8,7 +8,8 @@ import {
   DEFAULT_FIXED_HEADER,
   DEFAULT_FIXED_SIDEMENU,
   DEFAULT_FIXED_HEADER_HIDDEN,
-  DEFAULT_CONTENT_WIDTH_TYPE
+  DEFAULT_CONTENT_WIDTH_TYPE,
+  DEFAULT_MULTI_TAB
 } from '@/store/mutation-types'
 
 const app = {
@@ -22,7 +23,8 @@ const app = {
     fixSiderbar: false,
     autoHideHeader: false,
     color: null,
-    weak: false
+    weak: false,
+    multiTab: true
   },
   mutations: {
     SET_SIDEBAR_TYPE: (state, type) => {
@@ -70,6 +72,10 @@ const app = {
     TOGGLE_WEAK: (state, flag) => {
       Vue.ls.set(DEFAULT_COLOR_WEAK, flag)
       state.weak = flag
+    },
+    TOGGLE_MULTI_TAB: (state, bool) => {
+      Vue.ls.set(DEFAULT_MULTI_TAB, bool)
+      state.multiTab = bool
     }
   },
   actions: {
@@ -108,6 +114,9 @@ const app = {
     },
     ToggleWeak ({ commit }, weakFlag) {
       commit('TOGGLE_WEAK', weakFlag)
+    },
+    ToggleMultiTab ({ commit }, bool) {
+      commit('TOGGLE_MULTI_TAB', bool)
     }
   }
 }

+ 1 - 0
src/store/mutation-types.js

@@ -8,6 +8,7 @@ export const DEFAULT_FIXED_HEADER = 'DEFAULT_FIXED_HEADER'
 export const DEFAULT_FIXED_SIDEMENU = 'DEFAULT_FIXED_SIDEMENU'
 export const DEFAULT_FIXED_HEADER_HIDDEN = 'DEFAULT_FIXED_HEADER_HIDDEN'
 export const DEFAULT_CONTENT_WIDTH_TYPE = 'DEFAULT_CONTENT_WIDTH_TYPE'
+export const DEFAULT_MULTI_TAB = 'DEFAULT_MULTI_TAB'
 
 export const CONTENT_WIDTH_TYPE = {
   Fluid: 'Fluid',

+ 2 - 1
src/utils/mixin.js

@@ -17,7 +17,8 @@ const mixin = {
       fixSidebar: state => state.app.fixSiderbar,
       contentWidth: state => state.app.contentWidth,
       autoHideHeader: state => state.app.autoHideHeader,
-      sidebarOpened: state => state.app.sidebar
+      sidebarOpened: state => state.app.sidebar,
+      multiTab: state => state.app.multiTab
     })
   },
   methods: {