Browse Source

fix more bug

Sendya 6 years ago
parent
commit
aff8fd7acd
42 changed files with 1479 additions and 1515 deletions
  1. 1 1
      src/App.vue
  2. 9 0
      src/api/login.js
  3. 0 22
      src/components/layout/LayoutBaseView.vue
  4. 0 47
      src/components/layout/LayoutFooter.vue
  5. 0 22
      src/components/layout/LayoutView.vue
  6. 0 16
      src/components/layout/RouteView.vue
  7. 60 0
      src/components/layouts/BasicLayout.vue
  8. 16 0
      src/components/layouts/BlankLayout.vue
  9. 79 79
      src/components/layouts/PageView.vue
  10. 17 0
      src/components/layouts/RouteView.vue
  11. 4 4
      src/components/layouts/UserLayout.vue
  12. 7 0
      src/components/layouts/index.js
  13. 8 6
      src/components/menu/SideMenu.vue
  14. 2 2
      src/components/menu/index.js
  15. 51 0
      src/components/page/GlobalFooter.vue
  16. 114 105
      src/components/page/GlobalHeader.vue
  17. 339 345
      src/components/page/GlobalLayout.vue
  18. 0 0
      src/components/page/HeaderNotice.vue
  19. 222 222
      src/components/page/PageHeader.vue
  20. 24 23
      src/components/page/PageLayout.vue
  21. 26 3
      src/components/table/README.md
  22. 0 16
      src/components/table/index.js
  23. 2 0
      src/components/tools/Breadcrumb.vue
  24. 315 0
      src/config/router.config.js
  25. 1 1
      src/main.js
  26. 1 1
      src/permission.js
  27. 54 37
      src/router/README.md
  28. 2 310
      src/router/index.js
  29. 5 4
      src/store/modules/permission.js
  30. 2 2
      src/views/account/center/Index.vue
  31. 1 1
      src/views/account/settings/Binding.vue
  32. 2 2
      src/views/account/settings/Index.vue
  33. 0 206
      src/views/account/settings/IndexOld.vue
  34. 1 1
      src/views/account/settings/Notification.vue
  35. 1 1
      src/views/dashboard/Workplace.vue
  36. 13 0
      src/views/exception/ExceptionPage.vue
  37. 1 1
      src/views/profile/advanced/Advanced.vue
  38. 8 3
      src/views/profile/basic/Index.vue
  39. 5 1
      src/views/result/Result.vue
  40. 2 2
      src/views/user/Login.vue
  41. 34 29
      src/views/user/Register.vue
  42. 50 0
      src/views/user/RegisterResult.vue

+ 1 - 1
src/App.vue

@@ -30,7 +30,7 @@
         }
         else {
           that.$store.commit('TOGGLE_DEVICE', 'desktop')
-          that.$store.commit('TOGGLE_SIDEBAR', true)
+          that.$store.commit('SET_SIDEBAR_TYPE', true)
         }
 
       })

+ 9 - 0
src/api/login.js

@@ -1,3 +1,4 @@
+import api from './index'
 import { axios } from '@/utils/request'
 
 /**
@@ -19,6 +20,14 @@ export function login(parameter) {
   })
 }
 
+export function getSmsCaptcha(parameter) {
+  return axios({
+    url: api.SendSms,
+    method: 'post',
+    data: parameter
+  })
+}
+
 export function getInfo() {
   return axios({
     url: '/user/info',

+ 0 - 22
src/components/layout/LayoutBaseView.vue

@@ -1,22 +0,0 @@
-<template>
-  <layout-main>
-    <page-view />
-  </layout-main>
-</template>
-
-<script>
-  import LayoutMain from './LayoutMain'
-  import PageView from './PageView'
-
-  export default {
-    name: "GlobalView",
-    components: {
-      LayoutMain,
-      PageView
-    }
-  }
-</script>
-
-<style scoped>
-
-</style>

+ 0 - 47
src/components/layout/LayoutFooter.vue

@@ -1,47 +0,0 @@
-<template>
-  <div class="footer">
-    <div class="links">
-      <a href="#">Pro 首页</a>
-      <a href="https://github.com/ant-design/ant-design-pro"><a-icon type="github"/></a>
-      <a href="https://ant.design/">Ant Design</a>
-      <a href="https://vuecomponent.github.io/ant-design-vue/docs/vue/introduce-cn/">Vue Antd</a>
-    </div>
-    <div class="copyright">
-      Copyright <a-icon type="copyright" /> 2018 <span>白鹭学园技术组出品</span>
-    </div>
-  </div>
-</template>
-
-<script>
-  export default {
-    name: "LayoutFooter"
-  }
-</script>
-
-<style lang="scss" scoped>
-    .footer {
-        padding: 0 16px;
-        margin: 48px 0 24px;
-        text-align: center;
-
-        .links {
-            margin-bottom: 8px;
-
-            a {
-                color: rgba(0,0,0,.45);
-
-                &:hover {
-                    color: rgba(0,0,0,.65);
-                }
-
-                &:not(:last-child) {
-                    margin-right: 40px;
-                }
-            }
-        }
-        .copyright {
-            color: rgba(0,0,0,.45);
-            font-size: 14px;
-        }
-    }
-</style>

+ 0 - 22
src/components/layout/LayoutView.vue

@@ -1,22 +0,0 @@
-<template>
-  <layout-main>
-    <route-view/>
-  </layout-main>
-</template>
-
-<script>
-  import LayoutMain from './LayoutMain'
-  import RouteView from './RouteView'
-
-  export default {
-    name: "GlobalView",
-    components: {
-      LayoutMain,
-      RouteView
-    }
-  }
-</script>
-
-<style scoped>
-
-</style>

+ 0 - 16
src/components/layout/RouteView.vue

@@ -1,16 +0,0 @@
-<template>
-  <keep-alive v-if="$route.meta.keepAlive">
-    <router-view></router-view>
-  </keep-alive>
-  <router-view v-else></router-view>
-</template>
-
-<script>
-  export default {
-    name: "RouteView"
-  }
-</script>
-
-<style scoped>
-
-</style>

+ 60 - 0
src/components/layouts/BasicLayout.vue

@@ -0,0 +1,60 @@
+<template>
+  <global-layout>
+    <transition name="page-transition">
+      <keep-alive v-if="keepAlive">
+        <router-view />
+      </keep-alive>
+      <router-view v-else />
+    </transition>
+  </global-layout>
+</template>
+
+<script>
+  import GlobalLayout from '@/components/page/GlobalLayout'
+
+  export default {
+    name: "BasicLayout",
+    components: {
+      GlobalLayout
+    },
+    data () {
+      return {
+
+      }
+    },
+    computed: {
+      keepAlive () {
+        return this.$route.meta.keepAlive
+      }
+    },
+    methods: {
+
+    },
+  }
+</script>
+
+<style lang="scss">
+
+  /*
+ * 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>

+ 16 - 0
src/components/layouts/BlankLayout.vue

@@ -0,0 +1,16 @@
+<template>
+  <div>
+    <router-view />
+  </div>
+</template>
+
+<script>
+
+  export default {
+    name: "BlankLayout",
+  }
+</script>
+
+<style scoped>
+
+</style>

+ 79 - 79
src/components/layout/PageView.vue → src/components/layouts/PageView.vue

@@ -1,80 +1,80 @@
-<template>
-  <page-layout :desc="description" :title="getTitle" :link-list="linkList">
-    <div slot="extra" class="extra-img">
-      <img :src="extraImage"/>
-    </div>
-    <!-- keep-alive  -->
-    <route-view ref="content"></route-view>
-  </page-layout>
-</template>
-
-<script>
-  import PageLayout from './PageLayout'
-  import RouteView from './RouteView'
-
-  export default {
-    name: "PageContent",
-    components: {
-      RouteView,
-      PageLayout
-    },
-    data () {
-      return {
-        title: '',
-        description: '',
-        linkList: [],
-        extraImage: ''
-      }
-    },
-    mounted () {
-      this.getPageHeaderInfo()
-    },
-    updated () {
-      this.getPageHeaderInfo()
-    },
-    computed: {
-
-      getTitle () {
-        return this.$route.meta.title
-      }
-
-    },
-    methods: {
-      getPageHeaderInfo () {
-        // eslint-disable-next-line
-        this.title = this.$route.meta.title
-        // 因为套用了一层 route-view 所以要取 ref 对象下的子节点的第一个对象
-        const content = this.$refs.content && this.$refs.content.$children[0]
-        if (content) {
-          this.description = content.description
-          this.linkList = content.linkList
-          this.extraImage = content.extraImage
-        }
-      }
-    }
-  }
-</script>
-
-<style lang="scss" scoped>
-  .extra-img {
-    margin-top: -60px;
-    text-align: center;
-    width: 195px;
-
-    img {
-      width: 100%;
-    }
-  }
-
-  .mobile {
-    .extra-img{
-      margin-top: 0;
-      text-align: center;
-      width: 96px;
-
-      img{
-        width: 100%;
-      }
-    }
-  }
+<template>
+  <page-layout :desc="description" :title="getTitle" :link-list="linkList">
+    <div slot="extra" class="extra-img">
+      <img :src="extraImage"/>
+    </div>
+    <!-- keep-alive  -->
+    <route-view ref="content"></route-view>
+  </page-layout>
+</template>
+
+<script>
+  import PageLayout from '../page/PageLayout'
+  import RouteView from './RouteView'
+
+  export default {
+    name: "PageContent",
+    components: {
+      RouteView,
+      PageLayout
+    },
+    data () {
+      return {
+        title: '',
+        description: '',
+        linkList: [],
+        extraImage: ''
+      }
+    },
+    mounted () {
+      this.getPageHeaderInfo()
+    },
+    updated () {
+      this.getPageHeaderInfo()
+    },
+    computed: {
+
+      getTitle () {
+        return this.$route.meta.title
+      }
+
+    },
+    methods: {
+      getPageHeaderInfo () {
+        // eslint-disable-next-line
+        this.title = this.$route.meta.title
+        // 因为套用了一层 route-view 所以要取 ref 对象下的子节点的第一个对象
+        const content = this.$refs.content && this.$refs.content.$children[0]
+        if (content) {
+          this.description = content.description
+          this.linkList = content.linkList
+          this.extraImage = content.extraImage
+        }
+      }
+    }
+  }
+</script>
+
+<style lang="scss" scoped>
+  .extra-img {
+    margin-top: -60px;
+    text-align: center;
+    width: 195px;
+
+    img {
+      width: 100%;
+    }
+  }
+
+  .mobile {
+    .extra-img{
+      margin-top: 0;
+      text-align: center;
+      width: 96px;
+
+      img{
+        width: 100%;
+      }
+    }
+  }
 </style>

+ 17 - 0
src/components/layouts/RouteView.vue

@@ -0,0 +1,17 @@
+<template>
+  <keep-alive v-if="keepAlive">
+    <router-view />
+  </keep-alive>
+  <router-view v-else />
+</template>
+
+<script>
+  export default {
+    name: "RouteView",
+    computed: {
+      keepAlive () {
+        return this.$route.meta.keepAlive
+      }
+    },
+  }
+</script>

+ 4 - 4
src/components/layout/UserLayout.vue → src/components/layouts/UserLayout.vue

@@ -12,9 +12,9 @@
           Ant Design 是西湖区最具影响力的 Web 设计规范
         </div>
       </div>
-      <div class="main">
-        <route-view></route-view>
-      </div>
+
+      <route-view></route-view>
+
       <div class="footer">
         <div class="links">
           <a href="_self">帮助</a>
@@ -30,7 +30,7 @@
 </template>
 
 <script>
-  import RouteView from "@/components/layout/RouteView"
+  import RouteView from "@/components/layouts/RouteView"
 
   export default {
     name: "UserLayout",

+ 7 - 0
src/components/layouts/index.js

@@ -0,0 +1,7 @@
+import UserLayout from '@/components/layouts/UserLayout'
+import BlankLayout from '@/components/layouts/BlankLayout'
+import BasicLayout from '@/components/layouts/BasicLayout'
+import RouteView from '@/components/layouts/RouteView'
+import PageView from '@/components/layouts/PageView'
+
+export { UserLayout, BasicLayout, BlankLayout, RouteView, PageView }

+ 8 - 6
src/components/menu/SiderMenu.vue → src/components/menu/SideMenu.vue

@@ -1,6 +1,6 @@
 <template>
-  <a-layout-sider 
-    :class="['sider', isMobile ? null : 'shadow', theme ]" 
+  <a-layout-sider
+    :class="['sider', device === 'desktop' ? null : 'shadow', theme ]"
     width="256px" 
     :collapsible="collapsible"
     v-model="collapsed" 
@@ -19,14 +19,16 @@
       :mode="mode"
       style="padding: 16px 0px;"></s-menu>
   </a-layout-sider>
+
 </template>
 
 <script>
   import ALayoutSider from "ant-design-vue/es/layout/Sider"
   import SMenu from './index'
+  import { mapState } from 'vuex'
 
   export default {
-    name: "SiderMenu",
+    name: "SideMenu",
     components: { ALayoutSider, SMenu },
     props: {
       mode: {
@@ -58,9 +60,9 @@
 
     },
     computed: {
-      isMobile () {
-        return this.$store.state.app.device !== 'desktop'
-      }
+      ...mapState({
+        device: state => state.app.device,
+      })
     },
     methods: {
       onSelect (obj) {

+ 2 - 2
src/components/menu/index.js

@@ -122,9 +122,9 @@ export default {
     },
     updateMenu () {
       let routes = this.$route.matched.concat()
-      if (routes.length >= 3 && this.$route.meta.hidden) {
+      if (routes.length >= 4 && this.$route.meta.hidden) {
         routes.pop()
-        this.selectedKeys = [ routes[1].path ]
+        this.selectedKeys = [ routes[2].path ]
       } else {
         this.selectedKeys = [ routes.pop().path ]
       }

+ 51 - 0
src/components/page/GlobalFooter.vue

@@ -0,0 +1,51 @@
+<template>
+  <div class="footer">
+    <div class="links">
+      <a href="https://pro.ant.design/" target="_blank">Pro 首页</a>
+      <a href="https://github.com/ant-design/ant-design-pro" target="_blank">
+        <a-icon type="github"/>
+      </a>
+      <a href="https://ant.design/">Ant Design</a>
+      <a href="https://vuecomponent.github.io/ant-design-vue/docs/vue/introduce-cn/">Vue Antd</a>
+    </div>
+    <div class="copyright">
+      Copyright
+      <a-icon type="copyright"/>
+      2018 <span>白鹭学园技术组出品</span>
+    </div>
+  </div>
+</template>
+
+<script>
+  export default {
+    name: "LayoutFooter"
+  }
+</script>
+
+<style lang="scss" scoped>
+  .footer {
+    padding: 0 16px;
+    margin: 48px 0 24px;
+    text-align: center;
+
+    .links {
+      margin-bottom: 8px;
+
+      a {
+        color: rgba(0, 0, 0, .45);
+
+        &:hover {
+          color: rgba(0, 0, 0, .65);
+        }
+
+        &:not(:last-child) {
+          margin-right: 40px;
+        }
+      }
+    }
+    .copyright {
+      color: rgba(0, 0, 0, .45);
+      font-size: 14px;
+    }
+  }
+</style>

+ 114 - 105
src/components/layout/LayoutHeader.vue → src/components/page/GlobalHeader.vue

@@ -1,106 +1,115 @@
-<template>
-  <a-layout-header style="padding: 0px;">
-    <div class="header">
-      <a-icon class="trigger" v-if="device==='mobile'" :type="collapsed ? 'menu-fold' : 'menu-unfold'" @click.native="toggle"></a-icon>
-      <a-icon class="trigger" v-else :type="collapsed ? 'menu-unfold' : 'menu-fold'" @click.native="toggle"/>
-
-      <div class="user-wrapper">
-        <span class="action">
-          <a-icon type="question-circle-o"></a-icon>
-        </span>
-        <header-notice class="action"/>
-        <a-dropdown>
-          <span class="action ant-dropdown-link user-dropdown-menu">
-            <a-avatar class="avatar" size="small" :src="avatar()"/>
-            <span>{{ nickname() }}</span>
-          </span>
-          <a-menu slot="overlay" class="user-dropdown-menu-wrapper">
-            <a-menu-item key="0">
-              <a-icon type="user"/>
-              <span>个人中心</span>
-            </a-menu-item>
-            <a-menu-item key="1">
-              <router-link :to="{ name: 'settings' }">
-                <a-icon type="setting"/>
-                <span>账户设置</span>
-              </router-link>
-            </a-menu-item>
-            <a-menu-item key="2" disabled>
-              <a-icon type="setting"/>
-              <span>测试</span>
-            </a-menu-item>
-            <a-menu-divider/>
-            <a-menu-item key="3">
-              <a href="javascript:;" @click="handleLogout">
-                <a-icon type="logout"/>
-                <span>退出登录</span>
-              </a>
-            </a-menu-item>
-          </a-menu>
-        </a-dropdown>
-      </div>
-    </div>
-  </a-layout-header>
-</template>
-
-<script>
-  import HeaderNotice from './HeaderNotice'
-  import { mapActions, mapGetters } from 'vuex'
-
-  export default {
-    name: "LayoutHeader",
-    components: {
-      HeaderNotice
-    },
-    props: {
-      collapsed: {
-        type: Boolean,
-        required: false,
-        default: false
-      },
-      device: {
-        type: String,
-        required: false,
-        default: 'desktop'
-      }
-    },
-    data() {
-      return {
-
-      }
-    },
-    created() {
-
-    },
-    methods: {
-      ...mapActions(["Logout"]),
-      ...mapGetters(["nickname", "avatar"]),
-      handleLogout() {
-        const that = this
-
-        this.$confirm({
-          title: '提示',
-          content: '真的要注销登录吗 ?',
-          onOk() {
-            return that.Logout({}).then(() => {
-              window.location.reload()
-            }).catch(err => {
-              that.$message.error({
-                title: '错误',
-                description: err.message
-              })
-            })
-          },
-          onCancel() {},
-        });
-      },
-      toggle() {
-        this.$emit('toggle')
-      }
-    }
-  }
-</script>
-
-<style scoped>
-
+<template>
+  <a-layout-header style="padding: 0px;">
+    <div class="header">
+      <a-icon
+        v-if="device==='mobile'"
+        class="trigger"
+        :type="collapsed ? 'menu-fold' : 'menu-unfold'"
+        @click.native="toggle"></a-icon>
+      <a-icon
+        v-else
+        class="trigger"
+        :type="collapsed ? 'menu-unfold' : 'menu-fold'"
+        @click.native="toggle"/>
+
+      <div class="user-wrapper">
+        <span class="action">
+          <a-icon type="question-circle-o"></a-icon>
+        </span>
+        <header-notice class="action"/>
+        <a-dropdown>
+          <span class="action ant-dropdown-link user-dropdown-menu">
+            <a-avatar class="avatar" size="small" :src="avatar()"/>
+            <span>{{ nickname() }}</span>
+          </span>
+          <a-menu slot="overlay" class="user-dropdown-menu-wrapper">
+            <a-menu-item key="0">
+              <router-link :to="{ name: 'center' }">
+                <a-icon type="user"/>
+                <span>个人中心</span>
+              </router-link>
+            </a-menu-item>
+            <a-menu-item key="1">
+              <router-link :to="{ name: 'settings' }">
+                <a-icon type="setting"/>
+                <span>账户设置</span>
+              </router-link>
+            </a-menu-item>
+            <a-menu-item key="2" disabled>
+              <a-icon type="setting"/>
+              <span>测试</span>
+            </a-menu-item>
+            <a-menu-divider/>
+            <a-menu-item key="3">
+              <a href="javascript:;" @click="handleLogout">
+                <a-icon type="logout"/>
+                <span>退出登录</span>
+              </a>
+            </a-menu-item>
+          </a-menu>
+        </a-dropdown>
+      </div>
+    </div>
+  </a-layout-header>
+</template>
+
+<script>
+  import HeaderNotice from './HeaderNotice'
+  import {mapActions, mapGetters} from 'vuex'
+
+  export default {
+    name: "LayoutHeader",
+    components: {
+      HeaderNotice
+    },
+    props: {
+      collapsed: {
+        type: Boolean,
+        required: false,
+        default: false
+      },
+      device: {
+        type: String,
+        required: false,
+        default: 'desktop'
+      }
+    },
+    data() {
+      return {}
+    },
+    created() {
+
+    },
+    methods: {
+      ...mapActions(["Logout"]),
+      ...mapGetters(["nickname", "avatar"]),
+      handleLogout() {
+        const that = this
+
+        this.$confirm({
+          title: '提示',
+          content: '真的要注销登录吗 ?',
+          onOk() {
+            return that.Logout({}).then(() => {
+              window.location.reload()
+            }).catch(err => {
+              that.$message.error({
+                title: '错误',
+                description: err.message
+              })
+            })
+          },
+          onCancel() {
+          },
+        });
+      },
+      toggle() {
+        this.$emit('toggle')
+      }
+    }
+  }
+</script>
+
+<style scoped>
+
 </style>

+ 339 - 345
src/components/layout/LayoutMain.vue → src/components/page/GlobalLayout.vue

@@ -1,346 +1,340 @@
-<template>
-  <a-layout class="layout" :class="device">
-
-    <a-drawer 
-      v-if="device === 'mobile'"
-      :wrapClassName="'drawer-sider ' + theme"
-      placement="left"
-      @close="() => this.collapsed = false"
-      :closable="false"
-      :visible="collapsed"
-    >
-      <sider-menu
-        mode="inline"
-        :menus="menus"
-        @menuSelect="menuSelect"
-        :theme="theme"
-        :collapsed="false"
-        :collapsible="true"></sider-menu>
-    </a-drawer>
-    <sider-menu
-      v-else
-      :menus="menus"
-      :theme="theme"
-      :mode="menuMode"
-      :collapsed="collapsed"
-      :collapsible="true"></sider-menu>
-
-    <a-layout>
-      <!-- layout header -->
-      <layout-header :collapsed="collapsed" :device="device" @toggle="toggle"/>
-      <!-- layout content -->
-      <a-layout-content :style="{ margin: '24px 24px 0', height: '100%' }">
-        <!-- content -->
-        <slot/>
-      </a-layout-content>
-
-      <a-layout-footer style="padding: 0px">
-        <layout-footer/>
-      </a-layout-footer>
-
-    </a-layout>
-
-    <setting-drawer></setting-drawer>
-  </a-layout>
-</template>
-
-<script>
-  import SiderMenu from '@/components/menu/SiderMenu'
-  import LayoutHeader from './LayoutHeader'
-  import LayoutFooter from './LayoutFooter'
-  import SettingDrawer from '@/components/setting/SettingDrawer'
-  import { triggerResize } from '@/utils/util'
-  import { mapState, mapActions } from 'vuex'
-
-  export default {
-    name: "LayoutView",
-    components: {
-      SiderMenu,
-      LayoutHeader,
-      LayoutFooter,
-      SettingDrawer
-    },
-    data() {
-      return {
-        // light, dark
-        menuTheme: 'light',
-        // inline, horizontal
-        menuMode: 'inline',
-        collapsed: false,
-        menus: []
-      }
-    },
-    created() {
-      this.menus = this.mainMenu
-    },
-    computed: {
-      ...mapState({
-        mainMenu: state => state.permission.addRouters,
-        sidebarOpened: state => state.app.sidebar.opened,
-        theme: state => state.app.theme,
-        device: state => state.app.device,
-      })
-    },
-    mounted() {
-      this.collapsed = this.sidebarOpened
-    },
-    methods: {
-      ...mapActions(['setSidebar']),
-      toggle() {
-        this.collapsed = !this.collapsed
-        triggerResize()
-        this.setSidebar(this.collapsed)
-      },
-      menuSelect() {
-        if (this.device !== 'desktop') {
-          this.collapsed = false
-        }
-      }
-    }
-  }
-</script>
-
-<style lang="scss">
-
-  body {
-    // 打开滚动条固定显示
-    overflow-y: scroll;
-
-    &.userLayout {
-      overflow-y: auto;
-    }
-
-    &.colorWeak {
-      filter: invert(80%);
-    }
-  }
-
-  .layout {
-    min-height: 100vh;
-    overflow-x: hidden;
-
-    &.mobile {
-
-      .ant-layout-content {
-
-        .content {
-          margin: 24px 0 0;
-        }
-      }
-
-      .ant-table-wrapper {
-        .ant-table-body {
-          overflow-y: auto;
-        }
-      }
-    }
-
-    &.ant-layout-has-sider {
-      flex-direction: row;
-    }
-
-    .trigger {
-      font-size: 20px;
-      line-height: 64px;
-      padding: 0 24px;
-      cursor: pointer;
-      transition: color .3s;
-      &:hover {
-        color: #1890ff;
-        background: #e6f7ff;
-      }
-    }
-
-    .header {
-      height: 64px;
-      padding: 0 12px 0 0;
-      background: #fff;
-      box-shadow: 0 1px 4px rgba(0, 21, 41, .08);
-      position: relative;
-
-      .user-wrapper {
-        float: right;
-        height: 100%;
-
-        .action {
-          cursor: pointer;
-          padding: 0 12px;
-          display: inline-block;
-          transition: all .3s;
-          height: 100%;
-
-          &:hover {
-            background: #e6f7ff;
-          }
-
-          .avatar {
-            margin: 20px 8px 20px 0;
-            color: #1890ff;
-            background: hsla(0, 0%, 100%, .85);
-            vertical-align: middle;
-          }
-
-          .icon {
-            font-size: 16px;
-            padding: 4px;
-          }
-        }
-      }
-    }
-
-    // 内容区
-    .layout-content {
-      margin: 24px 24px 0px;
-      height: 100%;
-    }
-
-  }
-
-  // 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;
-
-    .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-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">
+
+    <a-drawer
+      v-if="device === 'mobile'"
+      :wrapClassName="'drawer-sider ' + theme"
+      placement="left"
+      @close="() => this.collapsed = false"
+      :closable="false"
+      :visible="collapsed"
+    >
+      <side-menu
+        mode="inline"
+        :menus="menus"
+        @menuSelect="menuSelect"
+        :theme="theme"
+        :collapsed="false"
+        :collapsible="true"></side-menu>
+    </a-drawer>
+
+    <side-menu
+      v-else
+      :menus="menus"
+      :theme="theme"
+      :mode="menuMode"
+      :collapsed="collapsed"
+      :collapsible="true"></side-menu>
+
+    <a-layout>
+      <!-- layout header -->
+      <global-header :collapsed="collapsed" :device="device" @toggle="toggle"/>
+
+      <!-- layout content -->
+      <a-layout-content :style="{ margin: '24px 24px 0', height: '100%' }">
+        <slot></slot>
+      </a-layout-content>
+
+      <!-- layout footer -->
+      <a-layout-footer style="padding: 0px">
+        <global-footer />
+      </a-layout-footer>
+    </a-layout>
+
+    <setting-drawer></setting-drawer>
+  </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 { triggerResize } from '@/utils/util'
+  import { mapState, mapActions } from 'vuex'
+  export default {
+    name: "BasicLayout",
+    components: {
+      SideMenu,
+      GlobalHeader,
+      GlobalFooter,
+      SettingDrawer
+    },
+    data () {
+      return {
+        // light, dark
+        menuTheme: 'light',
+        // inline, horizontal
+        menuMode: 'inline',
+        collapsed: false,
+        menus: []
+      }
+    },
+    computed: {
+      ...mapState({
+        mainMenu: state => state.permission.addRouters,
+        sidebarOpened: state => state.app.sidebar.opened,
+        theme: state => state.app.theme,
+        device: state => state.app.device,
+      })
+    },
+    created() {
+      this.menus = this.mainMenu.find((item) => item.path === '/').children
+    },
+    methods: {
+      ...mapActions(['setSidebar']),
+      toggle() {
+        this.collapsed = !this.collapsed
+        triggerResize()
+        this.setSidebar(this.collapsed)
+      },
+      menuSelect() {
+        if (this.device !== 'desktop') {
+          this.collapsed = false
+        }
+      }
+    },
+  }
+</script>
+
+<style lang="scss">
+  body {
+    // 打开滚动条固定显示
+    overflow-y: auto;
+
+    &.userLayout {
+      overflow-y: auto;
+    }
+
+    &.colorWeak {
+      filter: invert(80%);
+    }
+  }
+
+  .layout {
+    min-height: 100vh;
+    overflow-x: hidden;
+
+    &.mobile {
+
+      .ant-layout-content {
+
+        .content {
+          margin: 24px 0 0;
+        }
+      }
+
+      .ant-table-wrapper {
+        .ant-table-body {
+          overflow-y: auto;
+        }
+      }
+    }
+
+    &.ant-layout-has-sider {
+      flex-direction: row;
+    }
+
+    .trigger {
+      font-size: 20px;
+      line-height: 64px;
+      padding: 0 24px;
+      cursor: pointer;
+      transition: color .3s;
+      &:hover {
+        color: #1890ff;
+        background: #e6f7ff;
+      }
+    }
+
+    .header {
+      height: 64px;
+      padding: 0 12px 0 0;
+      background: #fff;
+      box-shadow: 0 1px 4px rgba(0, 21, 41, .08);
+      position: relative;
+
+      .user-wrapper {
+        float: right;
+        height: 100%;
+
+        .action {
+          cursor: pointer;
+          padding: 0 12px;
+          display: inline-block;
+          transition: all .3s;
+          height: 100%;
+
+          &:hover {
+            background: #e6f7ff;
+          }
+
+          .avatar {
+            margin: 20px 8px 20px 0;
+            color: #1890ff;
+            background: hsla(0, 0%, 100%, .85);
+            vertical-align: middle;
+          }
+
+          .icon {
+            font-size: 16px;
+            padding: 4px;
+          }
+        }
+      }
+    }
+
+    // 内容区
+    .layout-content {
+      margin: 24px 24px 0px;
+      height: 100%;
+    }
+
+  }
+
+  // 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;
+
+    .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-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>

+ 0 - 0
src/components/layout/HeaderNotice.vue → src/components/page/HeaderNotice.vue


+ 222 - 222
src/components/layout/PageHeader.vue → src/components/page/PageHeader.vue

@@ -1,223 +1,223 @@
-<template>
-  <div class="page-header">
-
-    <a-breadcrumb class="breadcrumb">
-      <a-breadcrumb-item v-for="(item, index) in breadList" :key="index">
-        <router-link v-if="item.name != name" :to="{ path: item.path }">
-          {{ item.meta.title }}
-        </router-link>
-        <span v-else>{{ item.meta.title }}</span>
-      </a-breadcrumb-item>
-    </a-breadcrumb>
-
-    <div class="detail">
-      <div class="main" v-if="!$route.meta.hiddenPageHeader">
-        <div class="row">
-          <img v-if="logo" :src="logo" class="logo"/>
-          <h1 v-if="title" class="title">{{ title }}</h1>
-          <div class="action">
-            <slot name="action"></slot>
-          </div>
-        </div>
-        <div class="row">
-          <div v-if="avatar" class="avatar">
-            <a-avatar :src="avatar"/>
-          </div>
-          <div v-if="this.$slots.content" class="headerContent">
-            <slot name="content"></slot>
-          </div>
-          <div v-if="this.$slots.extra" class="extra">
-            <slot name="extra"></slot>
-          </div>
-        </div>
-      </div>
-
-    </div>
-  </div>
-</template>
-
-<script>
-  import Breadcrumb from '@/components/tools/Breadcrumb'
-
-  export default {
-    name: "PageHeader",
-    components: {
-      "s-breadcrumb": Breadcrumb
-    },
-    props: {
-      title: {
-        type: String,
-        default: '',
-        required: false
-      },
-      breadcrumb: {
-        type: Array,
-        default: null,
-        required: false
-      },
-      logo: {
-        type: String,
-        default: '',
-        required: false
-      },
-      avatar: {
-        type: String,
-        default: '',
-        required: false
-      }
-    },
-    data() {
-      return {
-        name: '',
-        breadList: [],
-      }
-    },
-    created() {
-      this.getBreadcrumb()
-    },
-    methods: {
-      getBreadcrumb() {
-
-        this.breadList = []
-        this.breadList.push({name: 'index', path: '/dashboard/', meta: {title: '首页'}})
-
-        this.name = this.$route.name
-        this.$route.matched.forEach((item) => {
-          // item.meta.name === 'dashboard' ? item.path = '/dashboard' : this.$route.path === item.path
-          this.breadList.push(item)
-        })
-      }
-    },
-    watch: {
-      $route() {
-        this.getBreadcrumb()
-      }
-    }
-  }
-</script>
-
-<style lang="scss" scoped>
-  .page-header {
-    background: #fff;
-    padding: 16px 32px 0;
-    border-bottom: 1px solid #e8e8e8;
-
-    .breadcrumb {
-      margin-bottom: 16px;
-    }
-
-    .detail {
-      display: flex;
-      /*margin-bottom: 16px;*/
-
-      .avatar {
-        flex: 0 1 72px;
-        margin: 0 24px 8px 0;
-
-        & > span {
-          border-radius: 72px;
-          display: block;
-          width: 72px;
-          height: 72px;
-        }
-      }
-
-      .main {
-        width: 100%;
-        flex: 0 1 auto;
-
-        .row {
-          display: flex;
-          width: 100%;
-
-          .avatar {
-            margin-bottom: 16px;
-          }
-        }
-
-        .title {
-          font-size: 20px;
-          font-weight: 500;
-
-          font-size: 20px;
-          line-height: 28px;
-          font-weight: 500;
-          color: rgba(0,0,0,.85);
-          margin-bottom: 16px;
-          flex: auto;
-
-        }
-        .logo {
-          width: 28px;
-          height: 28px;
-          border-radius: 4px;
-          margin-right: 16px;
-        }
-        .content, .headerContent {
-          flex: auto;
-          color: rgba(0,0,0,.45);
-          line-height: 22px;
-
-          .link {
-            margin-top: 16px;
-            line-height: 24px;
-
-            a {
-              font-size: 14px;
-              margin-right: 32px;
-            }
-          }
-        }
-        .extra {
-          flex: 0 1 auto;
-          margin-left: 88px;
-          min-width: 242px;
-          text-align: right;
-        }
-        .action {
-          margin-left: 56px;
-          min-width: 266px;
-          flex: 0 1 auto;
-          text-align: right;
-          &:empty {
-            display: none;
-          }
-        }
-      }
-    }
-  }
-
-  .mobile .page-header {
-
-    .main {
-
-      .row {
-        flex-wrap: wrap;
-
-        .avatar {
-          flex: 0 1 25%;
-          margin: 0 2% 8px 0;
-        }
-        .content, .headerContent {
-          flex: 0 1 70%;
-
-          .link {
-            margin-top: 16px;
-            line-height: 24px;
-
-            a {
-              font-size: 14px;
-              margin-right: 10px;
-            }
-          }
-        }
-        .extra {
-          flex: 1 1 auto;
-          margin-left: 0;
-          min-width: 0;
-          text-align: right;
-        }
-      }
-    }
-  }
+<template>
+  <div class="page-header">
+
+    <a-breadcrumb class="breadcrumb">
+      <a-breadcrumb-item v-for="(item, index) in breadList" :key="index">
+        <router-link v-if="item.name != name" :to="{ path: item.path }">
+          {{ item.meta.title }}
+        </router-link>
+        <span v-else>{{ item.meta.title }}</span>
+      </a-breadcrumb-item>
+    </a-breadcrumb>
+
+    <div class="detail">
+      <div class="main" v-if="!$route.meta.hiddenHeaderContent">
+        <div class="row">
+          <img v-if="logo" :src="logo" class="logo"/>
+          <h1 v-if="title" class="title">{{ title }}</h1>
+          <div class="action">
+            <slot name="action"></slot>
+          </div>
+        </div>
+        <div class="row">
+          <div v-if="avatar" class="avatar">
+            <a-avatar :src="avatar"/>
+          </div>
+          <div v-if="this.$slots.content" class="headerContent">
+            <slot name="content"></slot>
+          </div>
+          <div v-if="this.$slots.extra" class="extra">
+            <slot name="extra"></slot>
+          </div>
+        </div>
+      </div>
+
+    </div>
+  </div>
+</template>
+
+<script>
+  import Breadcrumb from '@/components/tools/Breadcrumb'
+
+  export default {
+    name: "PageHeader",
+    components: {
+      "s-breadcrumb": Breadcrumb
+    },
+    props: {
+      title: {
+        type: String,
+        default: '',
+        required: false
+      },
+      breadcrumb: {
+        type: Array,
+        default: null,
+        required: false
+      },
+      logo: {
+        type: String,
+        default: '',
+        required: false
+      },
+      avatar: {
+        type: String,
+        default: '',
+        required: false
+      }
+    },
+    data() {
+      return {
+        name: '',
+        breadList: [],
+      }
+    },
+    created() {
+      this.getBreadcrumb()
+    },
+    methods: {
+      getBreadcrumb() {
+
+        this.breadList = []
+        // this.breadList.push({name: 'index', path: '/dashboard/', meta: {title: '首页'}})
+
+        this.name = this.$route.name
+        this.$route.matched.forEach((item) => {
+          // item.name !== 'index' && this.breadList.push(item)
+          this.breadList.push(item)
+        })
+      }
+    },
+    watch: {
+      $route() {
+        this.getBreadcrumb()
+      }
+    }
+  }
+</script>
+
+<style lang="scss" scoped>
+  .page-header {
+    background: #fff;
+    padding: 16px 32px 0;
+    border-bottom: 1px solid #e8e8e8;
+
+    .breadcrumb {
+      margin-bottom: 16px;
+    }
+
+    .detail {
+      display: flex;
+      /*margin-bottom: 16px;*/
+
+      .avatar {
+        flex: 0 1 72px;
+        margin: 0 24px 8px 0;
+
+        & > span {
+          border-radius: 72px;
+          display: block;
+          width: 72px;
+          height: 72px;
+        }
+      }
+
+      .main {
+        width: 100%;
+        flex: 0 1 auto;
+
+        .row {
+          display: flex;
+          width: 100%;
+
+          .avatar {
+            margin-bottom: 16px;
+          }
+        }
+
+        .title {
+          font-size: 20px;
+          font-weight: 500;
+
+          font-size: 20px;
+          line-height: 28px;
+          font-weight: 500;
+          color: rgba(0,0,0,.85);
+          margin-bottom: 16px;
+          flex: auto;
+
+        }
+        .logo {
+          width: 28px;
+          height: 28px;
+          border-radius: 4px;
+          margin-right: 16px;
+        }
+        .content, .headerContent {
+          flex: auto;
+          color: rgba(0,0,0,.45);
+          line-height: 22px;
+
+          .link {
+            margin-top: 16px;
+            line-height: 24px;
+
+            a {
+              font-size: 14px;
+              margin-right: 32px;
+            }
+          }
+        }
+        .extra {
+          flex: 0 1 auto;
+          margin-left: 88px;
+          min-width: 242px;
+          text-align: right;
+        }
+        .action {
+          margin-left: 56px;
+          min-width: 266px;
+          flex: 0 1 auto;
+          text-align: right;
+          &:empty {
+            display: none;
+          }
+        }
+      }
+    }
+  }
+
+  .mobile .page-header {
+
+    .main {
+
+      .row {
+        flex-wrap: wrap;
+
+        .avatar {
+          flex: 0 1 25%;
+          margin: 0 2% 8px 0;
+        }
+        .content, .headerContent {
+          flex: 0 1 70%;
+
+          .link {
+            margin-top: 16px;
+            line-height: 24px;
+
+            a {
+              font-size: 14px;
+              margin-right: 10px;
+            }
+          }
+        }
+        .extra {
+          flex: 1 1 auto;
+          margin-left: 0;
+          min-width: 0;
+          text-align: right;
+        }
+      }
+    }
+  }
 </style>

+ 24 - 23
src/components/layout/PageLayout.vue → src/components/page/PageLayout.vue

@@ -1,7 +1,7 @@
 <template>
-  <div :style="!$route.meta.hideHeader ? 'margin: -24px -24px 0px;' : null">
+  <div :style="!$route.meta.pageHeader ? 'margin: -24px -24px 0px;' : null">
     <!-- pageHeader , route meta hideHeader:true on hide -->
-    <page-header v-if="!$route.meta.hideHeader" :title="title" :logo="logo" :avatar="avatar">
+    <page-header v-if="!$route.meta.pageHeader" :title="title" :logo="logo" :avatar="avatar">
       <slot slot="action" name="action"></slot>
       <slot slot="content" name="headerContent"></slot>
       <div slot="content" v-if="!this.$slots.headerContent && desc">
@@ -9,7 +9,8 @@
         <div class="link">
           <template v-for="(link, index) in linkList">
             <a :key="index" :href="link.href">
-              <a-icon :type="link.icon" /><span>{{ link.title }}</span>
+              <a-icon :type="link.icon"/>
+              <span>{{ link.title }}</span>
             </a>
           </template>
         </div>
@@ -61,33 +62,33 @@
 </script>
 
 <style lang="scss" scoped>
-    .content {
-        margin: 24px 24px 0;
+  .content {
+    margin: 24px 24px 0;
 
-      .link {
-        margin-top: 16px;
+    .link {
+      margin-top: 16px;
 
-        &:not(:empty) {
-          margin-bottom: 16px;
+      &:not(:empty) {
+        margin-bottom: 16px;
+      }
+      a {
+        margin-right: 32px;
+        height: 24px;
+        line-height: 24px;
+        display: inline-block;
+
+        i {
+          font-size: 24px;
+          margin-right: 8px;
+          vertical-align: middle;
         }
-        a {
-          margin-right: 32px;
+        span {
           height: 24px;
           line-height: 24px;
           display: inline-block;
-
-          i {
-            font-size: 24px;
-            margin-right: 8px;
-            vertical-align: middle;
-          }
-          span {
-            height: 24px;
-            line-height: 24px;
-            display: inline-block;
-            vertical-align: middle;
-          }
+          vertical-align: middle;
         }
       }
     }
+  }
 </style>

+ 26 - 3
src/components/table/README.md

@@ -18,6 +18,7 @@ Table 重封装组件说明
 ```vue
 <template>
     <s-table
+      ref="table"
       size="default"
       :columns="columns"
       :data="loadData"
@@ -70,7 +71,7 @@ Table 重封装组件说明
                     return res.result
                   })
                 },
-            }
+            },
     }
 </script>
 
@@ -86,6 +87,7 @@ Table 重封装组件说明
 ```vue
 <template>
     <s-table
+      ref="table"
       size="default"
       :columns="columns"
       :data="loadData"
@@ -158,13 +160,34 @@ Table 重封装组件说明
                     return res.result
                   })
                 },
-            }
+        	},
+        },
+		methods: {
+			edit (row) {
+				// axios 发送数据到后端 修改数据成功后
+				// 调用 refresh() 重新加载列表数据
+				// 这里 setTimeout 模拟发起请求的网络延迟..
+				setTimeout (() => {
+                    this.$refs.table.refresh()
+				}, 1500)
+
+			}
+		}
     }
 </script>
 ```
 
 
 
+内置方法
+----
+
+通过 `this.$refs.table` 调用
+
+`this.$refs.table.refresh()` 刷新列表 (用户新增/修改数据后,重载列表数据)
+
+> 注意:要调用 `refresh()` 需要给表格组件设定 `ref` 值
+
 
 
 注意事项
@@ -261,4 +284,4 @@ result.then(r => {
 更新时间
 ----
 
-该文档最后更新于: 2018-10-20 PM 3:36
+该文档最后更新于: 2018-10-31 PM 08:15

+ 0 - 16
src/components/table/index.js

@@ -84,22 +84,6 @@ export default {
     },
     loadData(pagination, filters, sorter) {
 
-      /* region
-       * 由于 Pagination 修改分页下拉选项触发了 showSizeChange 和 change 事件 ,
-       * 而 a-table 中将 showSizeChange 转为 change 事件 , 导致 change 事件重复触发了 2 次 ,
-       * 此处临时处理 , 待作者修复后移除  */
-      let _paramStr = JSON.stringify({
-        pagination,
-        filters,
-        sorter
-      })
-      if (this._lastChangeParamStr === _paramStr && (Date.now() - this._lastChangeTimestamp) < 10) {
-        return
-      }
-      this._lastChangeParamStr = _paramStr;
-      this._lastChangeTimestamp = Date.now();
-      /* regionend */
-
       this.localLoading = true
       var result = this.data(
         Object.assign({

+ 2 - 0
src/components/tools/Breadcrumb.vue

@@ -23,6 +23,8 @@ export default {
   methods: {
     getBreadcrumb() {
 
+      console.log('this.$route.matched', this.$route.matched)
+
       this.breadList = []
       this.breadList.push({ name: 'index', path: '/dashboard/', meta: { title: '首页' } })
 

+ 315 - 0
src/config/router.config.js

@@ -0,0 +1,315 @@
+import { UserLayout, BasicLayout, RouteView, PageView } from '@/components/layouts'
+
+export const asyncRouterMap = [
+
+  {
+    path: '/',
+    name: 'index',
+    component: BasicLayout,
+    meta: { title: '首页' },
+    redirect: '/dashboard/analysis',
+    children: [
+      // dashboard
+      {
+        path: '/dashboard',
+        name: 'dashboard',
+        redirect: '/dashboard/analysis',
+        component: RouteView,
+        meta: { title: '仪表盘', icon: 'dashboard', permission: [ 'dashboard' ] },
+        children: [
+          {
+            path: '/dashboard/analysis',
+            name: 'Analysis',
+            component: () => import('@/views/dashboard/Analysis'),
+            meta: { title: '分析页', permission: [ 'dashboard' ] }
+          },
+          {
+            path: '/dashboard/monitor',
+            name: 'Monitor',
+            hidden: true,
+            component: () => import('@/views/dashboard/Monitor'),
+            meta: { title: '监控页', permission: [ 'dashboard' ] }
+          },
+          {
+            path: '/dashboard/workplace',
+            name: 'Workplace',
+            component: () => import('@/views/dashboard/Workplace'),
+            meta: { title: '工作台', permission: [ 'dashboard' ] }
+          }
+        ]
+      },
+
+      // forms
+      {
+        path: '/form',
+        redirect: '/form/basic-form',
+        component: PageView,
+        meta: { title: '表单页', icon: 'form', permission: [ 'form' ] },
+        children: [
+          {
+            path: '/form/base-form',
+            name: 'BaseForm',
+            component: () => import('@/views/form/BasicForm'),
+            meta: { title: '基础表单', permission: [ 'form' ] }
+          },
+          {
+            path: '/form/step-form',
+            name: 'StepForm',
+            component: () => import('@/views/form/stepForm/StepForm'),
+            meta: { title: '分步表单', permission: [ 'form' ] }
+          },
+          {
+            path: '/form/advanced-form',
+            name: 'AdvanceForm',
+            component: () => import('@/views/form/advancedForm/AdvancedForm'),
+            meta: { title: '高级表单', permission: [ 'form' ] }
+          }
+        ]
+      },
+
+      // list
+      {
+        path: '/list',
+        name: 'list',
+        component: PageView,
+        redirect: '/list/query-list',
+        meta: { title: '列表页', icon: 'table', permission: [ 'table' ] },
+        children: [
+          {
+            path: '/list/query-list',
+            name: 'QueryList',
+            component: () => import('@/views/list/TableList'),
+            meta: { title: '查询表格', permission: [ 'table' ] }
+          },
+          {
+            path: '/list/edit-table',
+            name: 'EditList',
+            component: () => import('@/views/list/TableInnerEditList'),
+            meta: { title: '内联编辑表格', permission: [ 'table' ] }
+          },
+          {
+            path: '/list/role-list',
+            name: 'RoleList',
+            component: () => import('@/views/list/RoleList'),
+            meta: { title: '角色列表', permission: [ 'table' ] }
+          },
+          {
+            path: '/list/permission-list',
+            name: 'PermissionList',
+            component: () => import('@/views/list/PermissionList'),
+            meta: { title: '权限列表', permission: [ 'table' ] }
+          },
+          {
+            path: '/list/basic-list',
+            name: 'BasicList',
+            component: () => import('@/views/list/StandardList'),
+            meta: { title: '标准列表', permission: [ 'table' ] }
+          },
+          {
+            path: '/list/card',
+            name: 'CardList',
+            component: () => import('@/views/list/CardList'),
+            meta: { title: '卡片列表', permission: [ 'table' ] }
+          },
+          {
+            path: '/list/search',
+            name: 'SearchList',
+            component: () => import('@/views/list/search/SearchLayout'),
+            redirect: '/list/search/article',
+            meta: { title: '搜索列表', permission: [ 'table' ] },
+            children: [
+              {
+                path: '/list/search/article',
+                name: 'SearchArticles',
+                component: () => import('../views/list/TableList'),
+                meta: { title: '搜索列表(文章)', permission: [ 'table' ] }
+              },
+              {
+                path: '/list/search/project',
+                name: 'SearchProjects',
+                component: () => import('../views/list/TableList'),
+                meta: { title: '搜索列表(项目)', permission: [ 'table' ] }
+              },
+              {
+                path: '/list/search/application',
+                name: 'SearchApplications',
+                component: () => import('../views/list/TableList'),
+                meta: { title: '搜索列表(应用)', permission: [ 'table' ] }
+              },
+            ]
+          },
+        ]
+      },
+
+      // profile
+      {
+        path: '/profile',
+        name: 'profile',
+        component: RouteView,
+        redirect: '/profile/basic',
+        meta: { title: '详情页', icon: 'profile', permission: [ 'profile' ] },
+        children: [
+          {
+            path: '/profile/basic',
+            name: 'ProfileBasic',
+            component: () => import('@/views/profile/basic/Index'),
+            meta: { title: '基础详情页', permission: [ 'profile' ] }
+          },
+          {
+            path: '/profile/advanced',
+            name: 'ProfileAdvanced',
+            component: () => import('@/views/profile/advanced/Advanced'),
+            meta: { title: '高级详情页', permission: [ 'profile' ] }
+          }
+        ]
+      },
+
+      // result
+      {
+        path: '/result',
+        name: 'result',
+        component: PageView,
+        redirect: '/result/success',
+        meta: { title: '结果页', icon: 'check-circle-o', permission: [ 'result' ] },
+        children: [
+          {
+            path: '/result/success',
+            name: 'ResultSuccess',
+            component: () => import(/* webpackChunkName: "result" */ '@/views/result/Success'),
+            meta: { title: '成功', hiddenHeaderContent: true, permission: [ 'result' ] }
+          },
+          {
+            path: '/result/fail',
+            name: 'ResultFail',
+            component: () => import(/* webpackChunkName: "result" */ '@/views/result/Error'),
+            meta: { title: '失败', hiddenHeaderContent: true, permission: [ 'result' ] }
+          }
+        ]
+      },
+
+      // Exception
+      {
+        path: '/exception',
+        name: 'exception',
+        component: RouteView,
+        redirect: '/exception/403',
+        meta: { title: '异常页', icon: 'warning', permission: [ 'exception' ] },
+        children: [
+          {
+            path: '/exception/403',
+            name: 'Exception403',
+            component: () => import(/* webpackChunkName: "fail" */ '@/views/exception/403'),
+            meta: { title: '403', permission: [ 'exception' ] }
+          },
+          {
+            path: '/exception/404',
+            name: 'Exception404',
+            component: () => import(/* webpackChunkName: "fail" */ '@/views/exception/404'),
+            meta: { title: '404', permission: [ 'exception' ] }
+          },
+          {
+            path: '/exception/500',
+            name: 'Exception500',
+            component: () => import(/* webpackChunkName: "fail" */ '@/views/exception/500'),
+            meta: { title: '500', permission: [ 'exception' ] }
+          }
+        ]
+      },
+
+      // account
+      {
+        path: '/account',
+        component: RouteView,
+        name: 'account',
+        meta: { title: '个人页', icon: 'user', keepAlive: true, permission: [ 'user' ] },
+        children: [
+          {
+            path: '/account/center',
+            name: 'center',
+            component: () => import('@/views/account/center/Index'),
+            meta: { title: '个人中心', keepAlive: true, permission: [ 'user' ] }
+          },
+          {
+            path: '/account/settings',
+            name: 'settings',
+            component: () => import('@/views/account/settings/Index'),
+            meta: { title: '个人设置', hideHeader: true, keepAlive: true, permission: [ 'user' ]  },
+            redirect: '/account/settings/base',
+            alwaysShow: true,
+            children: [
+              {
+                path: '/account/settings/base',
+                name: 'BaseSettings',
+                component: () => import('@/views/account/settings/BaseSetting'),
+                meta: { title: '基本设置', hidden: true, keepAlive: true, permission: [ 'user' ]  }
+              },
+              {
+                path: '/account/settings/security',
+                name: 'SecuritySettings',
+                component: () => import('@/views/account/settings/Security'),
+                meta: { title: '安全设置', hidden: true, keepAlive: true, permission: [ 'user' ]  }
+              },
+              {
+                path: '/account/settings/custom',
+                name: 'CustomSettings',
+                component: () => import('@/views/account/settings/Custom'),
+                meta: { title: '个性化设置', hidden: true, keepAlive: true, permission: [ 'user' ]  }
+              },
+              {
+                path: '/account/settings/binding',
+                name: 'BindingSettings',
+                component: () => import('@/views/account/settings/Binding'),
+                meta: { title: '账户绑定', hidden: true, keepAlive: true, permission: [ 'user' ]  }
+              },
+              {
+                path: '/account/settings/notification',
+                name: 'NotificationSettings',
+                component: () => import('@/views/account/settings/Notification'),
+                meta: { title: '新消息通知', hidden: true, keepAlive: true, permission: [ 'user' ]  }
+              },
+            ]
+          },
+        ]
+      }
+    ]
+  },
+  {
+    path: '*', redirect: '/404', hidden: true
+  }
+]
+
+/**
+ * 基础路由
+ * @type { *[] }
+ */
+export const constantRouterMap = [
+  {
+    path: '/user',
+    component: UserLayout,
+    redirect: '/user/login',
+    hidden: true,
+    children: [
+      {
+        path: 'login',
+        name: 'login',
+        component: () => import(/* webpackChunkName: "user" */ '@/views/user/Login')
+      },
+      {
+        path: 'register',
+        name: 'register',
+        component: () => import(/* webpackChunkName: "user" */ '@/views/user/Register')
+      },
+      {
+        path: 'register-result',
+        name: 'registerResult',
+        component: () => import(/* webpackChunkName: "user" */ '@/views/user/RegisterResult')
+      }
+    ]
+  },
+
+  {
+    path: '/404',
+    component: () => import(/* webpackChunkName: "fail" */ '@/views/exception/404')
+  },
+
+]

+ 1 - 1
src/main.js

@@ -1,7 +1,7 @@
 import Vue from 'vue'
 import App from './App.vue'
 import Storage from 'vue-ls'
-import router from './router/'
+import router from './router'
 import store from './store/'
 
 import { VueAxios } from "@/utils/request"

+ 1 - 1
src/permission.js

@@ -8,7 +8,7 @@ import { ACCESS_TOKEN } from "@/store/mutation-types"
 
 NProgress.configure({ showSpinner: false })// NProgress Configuration
 
-const whiteList = ['/user/login', '/user/register']// no redirect whitelist
+const whiteList = ['/user/login', '/user/register', '/user/register-result']// no redirect whitelist
 
 router.beforeEach((to, from, next) => {
   NProgress.start() // start progress bar

+ 54 - 37
src/router/README.md

@@ -3,6 +3,13 @@
 
 
 
+配置文件路径
+----
+
+`@/config/router.config.js`
+
+
+
 格式和说明
 ----
 
@@ -11,16 +18,16 @@
  * 路由配置说明:
  * 建议:sider menu 请不要超过三级菜单,若超过三级菜单,则应该设计为顶部主菜单 配合左侧次级菜单
  *
- **/ 
+ **/
  {
-  hidden: true,
   redirect: noredirect,
   name: 'router-name',
+  hidden: true,
   meta: {
     title: 'title',
-    icon: 'svg-name',
+    icon: 'a-icon',
     keepAlive: true,
-    hiddenPageHeader: true, 
+    hiddenHeaderContent: true,
   }
 }
 ```
@@ -38,15 +45,15 @@
 
 
 
-`{ Meta }` 对象
+`{ Meta }` 路由元信息对象
 
-| 参数             | 说明                                                         | 类型    | 默认值 |
-| ---------------- | ------------------------------------------------------------ | ------- | ------ |
-| title            | 路由标题, 用于显示面包屑, 页面标题 *推荐设置                 | string  | -      |
-| icon             | 路由在 menu 上显示的图标                                     | string  | -      |
-| keepAlive        | 缓存该路由                                                   | boolean | false  |
-| hiddenPageHeader | 隐藏 [PageHeader](https://github.com/sendya/ant-design-pro-vue/blob/master/src/components/layout/PageHeader.vue#L14) 组件, *特殊 用于隐藏页面带的 面包屑和页面标题栏 | boolean | false  |
-| permission       | 与项目提供的权限拦截匹配的权限,如果不匹配,则会被禁止访问该路由页面 | array   | []     |
+| 参数                | 说明                                                         | 类型    | 默认值 |
+| ------------------- | ------------------------------------------------------------ | ------- | ------ |
+| title               | 路由标题, 用于显示面包屑, 页面标题 *推荐设置                 | string  | -      |
+| icon                | 路由在 menu 上显示的图标                                     | string  | -      |
+| keepAlive           | 缓存该路由                                                   | boolean | false  |
+| hiddenHeaderContent | *特殊 隐藏 [PageHeader](https://github.com/sendya/ant-design-pro-vue/blob/master/src/components/layout/PageHeader.vue#L14) 组件中的页面带的 面包屑和页面标题栏 | boolean | false  |
+| permission          | 与项目提供的权限拦截匹配的权限,如果不匹配,则会被禁止访问该路由页面 | array   | []     |
 
 
 
@@ -56,37 +63,47 @@
 ```ecmascript 6
 const asyncRouterMap = [
   {
-    path: '/dashboard',
-    component: Layout,
-    name: 'dashboard',
-    redirect: '/dashboard/workplace',
-    meta: { title: '仪表盘', icon: 'dashboard', permission: [ 'dashboard' ] },
+    path: '/',
+    name: 'index',
+    component: BasicLayout,
+    meta: { title: '首页' },
+    redirect: '/dashboard/analysis',
     children: [
       {
-        path: '/dashboard/analysis',
-        name: 'Analysis',
-        component: () => import('@/views/dashboard/Analysis'),
-        meta: { title: '分析页', hideHeader: true, permission: [ 'dashboard' ] }
-      },
-      {
-        path: '/dashboard/monitor',
-        name: 'Monitor',
-        hidden: true,
-        component: () => import('@/views/dashboard/Monitor'),
-        meta: { title: '监控页', hideHeader: true, permission: [ 'dashboard' ] }
+        path: '/dashboard',
+        component: Layout,
+        name: 'dashboard',
+        redirect: '/dashboard/workplace',
+        meta: {title: '仪表盘', icon: 'dashboard', permission: ['dashboard']},
+        children: [
+          {
+            path: '/dashboard/analysis',
+            name: 'Analysis',
+            component: () => import('@/views/dashboard/Analysis'),
+            meta: {title: '分析页', hideHeader: true, permission: ['dashboard']}
+          },
+          {
+            path: '/dashboard/monitor',
+            name: 'Monitor',
+            hidden: true,
+            component: () => import('@/views/dashboard/Monitor'),
+            meta: {title: '监控页', hideHeader: true, permission: ['dashboard']}
+          },
+          {
+            path: '/dashboard/workplace',
+            name: 'Workplace',
+            component: () => import('@/views/dashboard/Workplace'),
+            meta: {title: '工作台', permission: ['dashboard']}
+          }
+        ]
       },
-      {
-        path: '/dashboard/workplace',
-        name: 'Workplace',
-        component: () => import('@/views/dashboard/Workplace'),
-        meta: { title: '工作台', permission: [ 'dashboard' ] }
-      }
+      ...
     ]
-  }
+  },
 ]
 ```
 
-> 请注意 `component: () => import('..') ` 方式引入路由的页面组件为 懒加载模式 
+> 1. 请注意 `component: () => import('..') ` 方式引入路由的页面组件为 懒加载模式。具体可以看 [Vue 官方文档](https://router.vuejs.org/zh/guide/advanced/lazy-loading.html)
+> 2. 增加新的路由应该增加在 '/' (index) 路由的 `children` 内
 >
-> 具体可以看 [Vue 官方文档](https://router.vuejs.org/zh/guide/advanced/lazy-loading.html)
 

+ 2 - 310
src/router/index.js

@@ -1,320 +1,12 @@
 import Vue from 'vue'
 import Router from 'vue-router'
-import Layout from '../components/layout/LayoutView'
-import LayoutBase from '../components/layout/LayoutBaseView'
-import LayoutUser from '@/components/layout/UserLayout'
+import { constantRouterMap } from '@/config/router.config'
 
 Vue.use(Router)
-/**
- * 路由配置说明:
- * 建议:sider menu 请不要超过三级菜单,若超过三级菜单,则应该设计为顶部主菜单 配合左侧次级菜单
- *
- **/
-
-
-
-export const constantRouterMap = [
-  {
-    path: '/user',
-    component: LayoutUser,
-    redirect: '/user/login',
-    hidden: true,
-    children: [
-      {
-        path: 'login',
-        name: 'login',
-        component: () => import(/* webpackChunkName: "user" */ '@/views/Login')
-      },
-      {
-        path: 'register',
-        name: 'register',
-        component: () => import(/* webpackChunkName: "user" */ '@/views/Register')
-      }
-    ]
-  },
-  {
-    path: '/404',
-    component: () => import(/* webpackChunkName: "fail" */ '@/views/exception/404')
-  },
-  {
-    path: '/',
-    component: Layout,
-    redirect: { name: 'login' },
-    name: 'home',
-    hidden: true
-  }
-]
 
 export default new Router({
   mode: 'history',
   base: process.env.BASE_URL,
   scrollBehavior: () => ({ y: 0 }),
   routes: constantRouterMap
-})
-
-export const asyncRouterMap = [
-  {
-    path: '/dashboard',
-    component: Layout,
-    name: 'dashboard',
-    redirect: '/dashboard/workplace',
-    meta: { title: '仪表盘', icon: 'dashboard', permission: [ 'dashboard' ] },
-    children: [
-      {
-        path: '/dashboard/analysis',
-        name: 'Analysis',
-        component: () => import('@/views/dashboard/Analysis'),
-        meta: { title: '分析页', hideHeader: true, permission: [ 'dashboard' ] }
-      },
-      {
-        path: '/dashboard/monitor',
-        name: 'Monitor',
-        hidden: true,
-        component: () => import('@/views/dashboard/Monitor'),
-        meta: { title: '监控页', hideHeader: true, permission: [ 'dashboard' ] }
-      },
-      {
-        path: '/dashboard/workplace',
-        name: 'Workplace',
-        component: () => import('@/views/dashboard/Workplace'),
-        meta: { title: '工作台', permission: [ 'dashboard' ] }
-      }
-    ]
-  },
-  {
-    path: '/form',
-    component: LayoutBase,
-    name: 'form',
-    redirect: '/form/base-form',
-    meta: { title: '表单页', icon: 'form', permission: [ 'form' ] },
-    children: [
-      {
-        path: '/form/base-form',
-        name: 'BaseForm',
-        component: () => import('@/views/form/BasicForm'),
-        meta: { title: '基础表单', permission: [ 'form' ] }
-      },
-      {
-        path: '/form/step-form',
-        name: 'StepForm',
-        component: () => import('@/views/form/stepForm/StepForm'),
-        meta: { title: '分步表单', permission: [ 'form' ] }
-      },
-      {
-        path: '/form/advanced-form',
-        name: 'AdvanceForm',
-        component: () => import('@/views/form/advancedForm/AdvancedForm'),
-        meta: { title: '高级表单', permission: [ 'form' ] }
-      }
-    ]
-  },
-  {
-    path: '/list',
-    component: LayoutBase,
-    name: 'list',
-    redirect: '/list/query-list',
-    meta: { title: '列表页', icon: 'table', permission: [ 'table' ] },
-    children: [
-      {
-        path: '/list/query-list',
-        name: 'QueryList',
-        component: () => import('@/views/list/TableList'),
-        meta: { title: '查询表格', permission: [ 'table' ] }
-      },
-      {
-        path: '/list/edit-table',
-        name: 'EditList',
-        component: () => import('@/views/list/TableInnerEditList'),
-        meta: { title: '内联编辑表格', permission: [ 'table' ] }
-      },
-      {
-        path: '/list/role-list',
-        name: 'RoleList',
-        component: () => import('@/views/list/RoleList'),
-        meta: { title: '角色列表', permission: [ 'table' ] }
-      },
-      {
-        path: '/list/permission-list',
-        name: 'PermissionList',
-        component: () => import('@/views/list/PermissionList'),
-        meta: { title: '权限列表', permission: [ 'table' ] }
-      },
-      {
-        path: '/list/basic-list',
-        name: 'BasicList',
-        component: () => import('@/views/list/StandardList'),
-        meta: { title: '标准列表', permission: [ 'table' ] }
-      },
-      {
-        path: '/list/card',
-        name: 'CardList',
-        component: () => import('@/views/list/CardList'),
-        meta: { title: '卡片列表', permission: [ 'table' ] }
-      },
-      {
-        path: '/list/search',
-        name: 'SearchList',
-        component: () => import('@/views/list/search/SearchLayout'),
-        redirect: '/list/search/article',
-        meta: { title: '搜索列表', permission: [ 'table' ] },
-        children: [
-          {
-            path: '/list/search/article',
-            name: 'SearchArticles',
-            component: () => import('../views/list/TableList'),
-            meta: { title: '搜索列表(文章)', permission: [ 'table' ] }
-          },
-          {
-              path: '/list/search/project',
-              name: 'SearchProjects',
-              component: () => import('../views/list/TableList'),
-              meta: { title: '搜索列表(项目)', permission: [ 'table' ] }
-          },
-          {
-              path: '/list/search/application',
-              name: 'SearchApplications',
-              component: () => import('../views/list/TableList'),
-              meta: { title: '搜索列表(应用)', permission: [ 'table' ] }
-          },
-        ]
-      },
-    ]
-  },
-  {
-    path: '/profile',
-    component: Layout,
-    name: 'profile',
-    redirect: '/profile/basic',
-    meta: { title: '详情页', icon: 'profile', permission: [ 'profile' ] },
-    children: [
-      {
-        path: '/profile/basic',
-        name: 'ProfileBasic',
-        component: () => import('@/views/profile/basic/Index'),
-        meta: { title: '基础详情页', permission: [ 'profile' ] }
-      },
-      {
-        path: '/profile/advanced',
-        name: 'ProfileAdvanced',
-        component: () => import('@/views/profile/advanced/Advanced'),
-        meta: { title: '高级详情页', permission: [ 'profile' ] }
-      }
-    ]
-  },
-  {
-    path: '/result',
-    component: LayoutBase,
-    name: 'result',
-    redirect: '/result/success',
-    meta: { title: '结果页', icon: 'check-circle-o', permission: [ 'result' ] },
-    children: [
-      {
-        path: '/result/success',
-        name: 'ResultSuccess',
-        component: () => import(/* webpackChunkName: "result" */ '../views/result/Success'),
-        meta: { title: '成功', hiddenPageHeader: true, permission: [ 'result' ] }
-      },
-      {
-        path: '/result/fail',
-        name: 'ResultFail',
-        // route level code-splitting
-        // this generates a separate chunk (about.[hash].js) for this route
-        // which is lazy-loaded when the route is visited.
-        component: () => import(/* webpackChunkName: "result" */ '../views/result/Error'),
-        meta: { title: '失败', hiddenPageHeader: true, permission: [ 'result' ] }
-      }
-    ]
-  },
-  {
-    path: '/exception',
-    component: Layout,
-    name: 'exception',
-    redirect: '/exception/403',
-    meta: { title: '异常页', icon: 'warning', permission: [ 'exception' ] },
-    children: [
-      {
-        path: '/exception/403',
-        name: 'Exception403',
-        component: () => import(/* webpackChunkName: "fail" */ '../views/exception/403'),
-        meta: { title: '403', permission: [ 'exception' ] }
-      },
-      {
-        path: '/exception/404',
-        name: 'Exception404',
-        // route level code-splitting
-        // this generates a separate chunk (about.[hash].js) for this route
-        // which is lazy-loaded when the route is visited.
-        component: () => import(/* webpackChunkName: "fail" */ '../views/exception/404'),
-        meta: { title: '404', permission: [ 'exception' ] }
-      },
-      {
-        path: '/exception/500',
-        name: 'Exception500',
-        // route level code-splitting
-        // this generates a separate chunk (about.[hash].js) for this route
-        // which is lazy-loaded when the route is visited.
-        component: () => import(/* webpackChunkName: "fail" */ '../views/exception/500'),
-        meta: { title: '500', permission: [ 'exception' ] }
-      }
-    ]
-  },
-  {
-    path: '/account',
-    component: Layout,
-    name: 'account',
-    meta: { title: '个人页', icon: 'user', keepAlive: true, permission: [ 'user' ] },
-    children: [
-      {
-        path: '/account/center',
-        name: 'center',
-        component: () => import('@/views/account/center/Index'),
-        meta: { title: '个人中心', keepAlive: true, permission: [ 'user' ] }
-      },
-      {
-        path: '/account/settings',
-        name: 'settings',
-        component: () => import('@/views/account/settings/Index'),
-        meta: { title: '个人设置', hideHeader: true, keepAlive: true, permission: [ 'user' ]  },
-        redirect: '/account/settings/base',
-        alwaysShow: true,
-        children: [
-          {
-            path: '/account/settings/base',
-            name: 'BaseSettings',
-            component: () => import('@/views/account/settings/BaseSetting'),
-            meta: { title: '基本设置', hidden: true, keepAlive: true, permission: [ 'user' ]  }
-          },
-          {
-            path: '/account/settings/security',
-            name: 'SecuritySettings',
-            component: () => import('@/views/account/settings/Security'),
-            meta: { title: '安全设置', hidden: true, keepAlive: true, permission: [ 'user' ]  }
-          },
-          {
-            path: '/account/settings/custom',
-            name: 'CustomSettings',
-            component: () => import('@/views/account/settings/Custom'),
-            meta: { title: '个性化设置', hidden: true, keepAlive: true, permission: [ 'user' ]  }
-          },
-          {
-            path: '/account/settings/binding',
-            name: 'BindingSettings',
-            component: () => import('@/views/account/settings/Binding'),
-            meta: { title: '账户绑定', hidden: true, keepAlive: true, permission: [ 'user' ]  }
-          },
-          {
-            path: '/account/settings/notification',
-            name: 'NotificationSettings',
-            component: () => import('@/views/account/settings/Notification'),
-            meta: { title: '新消息通知', hidden: true, keepAlive: true, permission: [ 'user' ]  }
-          },
-        ]
-      },
-
-    ]
-  },
-
-  {
-    path: '*', redirect: '/404', hidden: true
-  }
-]
+})

+ 5 - 4
src/store/modules/permission.js

@@ -1,4 +1,4 @@
-import { asyncRouterMap, constantRouterMap } from "@/router"
+import { asyncRouterMap, constantRouterMap } from "@/config/router.config"
 
 /**
  * 过滤账户是否拥有某一个权限,并将菜单从加载列表移除
@@ -16,8 +16,9 @@ function hasPermission(permission, route) {
         return true
       }
     }
+    return false
   }
-  return false
+  return true
 }
 
 /**
@@ -36,8 +37,8 @@ function hasRole(roles, route) {
   }
 }
 
-function filterAsyncRouter(asyncRouterMap, roles) {
-  const accessedRouters = asyncRouterMap.filter(route => {
+function filterAsyncRouter(routerMap, roles) {
+  const accessedRouters = routerMap.filter(route => {
     if (hasPermission(roles.permissionList, route)) {
       if (route.children && route.children.length) {
         route.children = filterAsyncRouter(route.children, roles)

+ 2 - 2
src/views/account/center/Index.vue

@@ -90,8 +90,8 @@
 </template>
 
 <script>
-  import PageLayout from '@/components/layout/PageLayout'
-  import RouteView from "@/components/layout/RouteView"
+  import PageLayout from '@/components/page/PageLayout'
+  import RouteView from "@/components/layouts/RouteView"
   import { AppPage, ArticlePage, ProjectPage } from './page'
 
 

+ 1 - 1
src/views/account/settings/Binding.vue

@@ -11,7 +11,7 @@
   export default {
     data () {
       return {
-
+        data: []
       }
     },
     methods: {

+ 2 - 2
src/views/account/settings/Index.vue

@@ -47,8 +47,8 @@
 </template>
 
 <script>
-  import PageLayout from '@/components/layout/PageLayout'
-  import RouteView from "@/components/layout/RouteView"
+  import PageLayout from '@/components/page/PageLayout'
+  import RouteView from "@/components/layouts/RouteView"
   import { mapState } from 'vuex'
 
   export default {

+ 0 - 206
src/views/account/settings/IndexOld.vue

@@ -1,206 +0,0 @@
-<template>
-  <page-layout :avatar="avatar">
-    <div slot="headerContent">
-      <div class="title">{{ timeFix }},{{ user.name }},{{ welcome }}</div>
-      <div>You are not alone.</div>
-    </div>
-    <div slot="extra">
-      <a-row>
-        <a-col :sm="8" :xs="24">
-          <head-info title="可用节点" content="16" :bordered="true"/>
-        </a-col>
-        <a-col :sm="8" :xs="24">
-          <head-info title="高级节点" content="7/16" :bordered="true"/>
-        </a-col>
-        <a-col :sm="8" :xs="24">
-          <head-info title="剩余流量" content="2,23Gb"/>
-        </a-col>
-      </a-row>
-    </div>
-
-    <a-card :style="{ padding: '0 15%' }">
-      <a-row :gutter="16">
-        <a-col :md="24" :lg="8" :style="{ minHeight: '180px' }">
-          <div class="ant-upload-preview" >
-            <a-icon type="cloud-upload-o" class="upload-icon"/>
-            <div class="mask">
-              <a-icon type="plus" />
-            </div>
-            <img :src="option.img"/>
-          </div>
-        </a-col>
-        <a-col :md="24" :lg="16">
-
-          <a-form layout="vertical">
-            <a-form-item
-              label="昵称"
-            >
-              <a-input placeholder="给自己起个名字" />
-            </a-form-item>
-            <a-form-item
-              label="Bio"
-            >
-              <a-textarea rows="4" placeholder="You are not alone."/>
-            </a-form-item>
-
-            <a-form-item
-              label="电子邮件"
-              :required="false"
-            >
-              <a-input placeholder="exp@admin.com"/>
-            </a-form-item>
-            <a-form-item
-              label="加密方式"
-              :required="false"
-            >
-              <a-select defaultValue="aes-256-cfb">
-                <a-select-option value="aes-256-cfb">aes-256-cfb</a-select-option>
-                <a-select-option value="aes-128-cfb">aes-128-cfb</a-select-option>
-                <a-select-option value="chacha20">chacha20</a-select-option>
-              </a-select>
-            </a-form-item>
-            <a-form-item
-              label="连接密码"
-              :required="false"
-            >
-              <a-input placeholder="h3gSbecd"/>
-            </a-form-item>
-            <a-form-item
-              label="登陆密码"
-              :required="false"
-            >
-              <a-input placeholder="密码"/>
-            </a-form-item>
-
-            <a-form-item>
-              <a-button type="primary">提交</a-button>
-              <a-button style="margin-left: 8px">保存</a-button>
-            </a-form-item>
-          </a-form>
-
-        </a-col>
-      </a-row>
-    </a-card>
-
-  </page-layout>
-</template>
-
-<script>
-  import {timeFix, welcome} from "../../../utils/util"
-  import LayoutMain from '@/components/layout/LayoutMain'
-  import PageLayout from '@/components/layout/PageLayout'
-
-  import HeadInfo from '@/components/tools/HeadInfo'
-  import ASelect from "ant-design-vue/es/select"
-  import AForm from "ant-design-vue/es/form/Form"
-  import VueCropper from "vue-cropper/example/src/vue-cropper/vue-cropper"
-
-  export default {
-    name: "Index",
-    components: {
-      VueCropper,
-      AForm,
-      ASelect,
-      LayoutMain,
-      PageLayout,
-      HeadInfo
-    },
-    data () {
-      return {
-        timeFix: timeFix(),
-        welcome: welcome(),
-        avatar: '',
-        user: {},
-
-        // cropper
-        preview: {},
-        option: {
-          img: '/avatar2.jpg',
-          info: true,
-          size: 1,
-          outputType: 'jpeg',
-          canScale: false,
-          autoCrop: true,
-          // 只有自动截图开启 宽度高度才生效
-          autoCropWidth: 180,
-          autoCropHeight: 180,
-          fixedBox: true,
-          // 开启宽度和高度比例
-          fixed: true,
-          fixedNumber: [1, 1]
-        }
-      }
-    },
-    computed: {
-      userInfo() {
-        return this.$store.getters.userInfo
-      }
-    },
-    created() {
-      this.user = this.userInfo
-      this.avatar = this.userInfo.avatar
-    },
-    methods: {
-
-      realTime (data) {
-        this.preview = data
-      }
-    }
-  }
-</script>
-
-<style lang="scss" scoped>
-  .avatar-upload-wrapper {
-    height: 200px;
-    width: 100%;
-  }
-
-  .ant-upload-preview {
-    position: relative;
-    margin: 0 auto;
-    width: 100%;
-    max-width: 180px;
-    border-radius: 50%;
-    box-shadow: 0 0 4px #ccc;
-
-    .upload-icon {
-      position: absolute;
-      top: 0;
-      right: 10px;
-      font-size: 1.4rem;
-      padding: 0.5rem;
-      background: rgba(222, 221, 221, 0.7);
-      border-radius: 50%;
-      border: 1px solid rgba(0, 0, 0, 0.2);
-    }
-    .mask {
-      opacity: 0;
-      position: absolute;
-      background: rgba(0,0,0,0.4);
-      cursor: pointer;
-      transition: opacity 0.4s;
-
-      &:hover {
-        opacity: 1;
-      }
-
-      i {
-        font-size: 2rem;
-        position: absolute;
-        top: 50%;
-        left: 50%;
-        margin-left: -1rem;
-        margin-top: -1rem;
-        color: #d6d6d6;
-      }
-    }
-
-    img, .mask {
-      width: 100%;
-      max-width: 180px;
-      height: 100%;
-      border-radius: 50%;
-      overflow: hidden;
-    }
-  }
-</style>

+ 1 - 1
src/views/account/settings/Notification.vue

@@ -11,7 +11,7 @@
   export default {
     data () {
       return {
-
+        data: []
       }
     },
     methods: {

+ 1 - 1
src/views/dashboard/Workplace.vue

@@ -113,7 +113,7 @@
   import { timeFix } from "@/utils/util"
   import {mapGetters} from "vuex"
 
-  import PageLayout from '@/components/layout/PageLayout'
+  import PageLayout from '@/components/page/PageLayout'
   import HeadInfo from '@/components/tools/HeadInfo'
   import Radar from '@/components/chart/Radar'
 

+ 13 - 0
src/views/exception/ExceptionPage.vue

@@ -72,4 +72,17 @@
     }
   }
 
+  .mobile {
+    .exception {
+      margin-top: 30px;
+      .img {
+        padding-right: unset;
+
+        img {
+          height: 40%;
+          max-width: 80%;
+        }
+      }
+    }
+  }
 </style>

+ 1 - 1
src/views/profile/advanced/Advanced.vue

@@ -134,7 +134,7 @@
 
 <script>
   import { mapState } from 'vuex'
-  import PageLayout from '@/components/layout/PageLayout'
+  import PageLayout from '@/components/page/PageLayout'
   import DetailList from '@/components/tools/DetailList'
 
   const DetailListItem = DetailList.Item

+ 8 - 3
src/views/profile/basic/Index.vue

@@ -1,5 +1,5 @@
 <template>
-  <page-layout>
+  <page-layout :title="title">
     <a-card :bordered="false">
       <detail-list title="退款申请">
         <detail-list-item term="取货单号">1000000000</detail-list-item>
@@ -43,7 +43,7 @@
 </template>
 
 <script>
-  import PageLayout from '@/components/layout/PageLayout'
+  import PageLayout from '@/components/page/PageLayout'
   import STable from '@/components/table/'
   import DetailList from '@/components/tools/DetailList'
   import ABadge from "ant-design-vue/es/badge/Badge"
@@ -235,7 +235,12 @@
         }
         return statusMap[status]
       }
-    }
+    },
+    computed: {
+      title () {
+        return this.$route.meta.title
+      }
+    },
 
   }
 </script>

+ 5 - 1
src/views/result/Result.vue

@@ -5,7 +5,7 @@
     </div>
     <div class="title" v-if="title">{{ title }}</div>
     <div class="description" v-if="description">{{ description }}</div>
-    <div class="content">
+    <div class="content" v-if="content">
       <slot></slot>
     </div>
     <div class="action">
@@ -30,6 +30,10 @@
       description: {
         type: String,
         default: ''
+      },
+      content: {
+        type: Boolean,
+        default: true
       }
     }
   }

+ 2 - 2
src/views/Login.vue → src/views/user/Login.vue

@@ -1,5 +1,5 @@
 <template>
-  <div>
+  <div class="main">
     <a-form class="user-layout-login" ref="formLogin" :autoFormCreate="(form)=>{this.form = form}" id="formLogin">
       <a-tabs
         :activeKey="customActiveKey"
@@ -90,7 +90,7 @@
 
 <script>
   import md5 from "md5"
-  import api from '@/api/'
+  import api from '@/api'
   import TwoStepCaptcha from '@/components/tools/TwoStepCaptcha'
   import { mapActions } from "vuex"
   import { timeFix } from "@/utils/util"

+ 34 - 29
src/views/Register.vue → src/views/user/Register.vue

@@ -1,5 +1,5 @@
 <template>
-  <div class="user-layout-register">
+  <div class="main user-layout-register">
     <h3><span>注册</span></h3>
     <a-form ref="formRegister" :autoFormCreate="(form)=>{this.form = form}" id="formRegister">
       <a-form-item
@@ -18,7 +18,7 @@
 
       <a-form-item
         fieldDecoratorId="password2"
-        :fieldDecoratorOptions="{rules: [{ required: true, message: '至少6位密码,区分大小写' }], validateTrigger: 'blur'}">
+        :fieldDecoratorOptions="{rules: [{ required: true, message: '至少6位密码,区分大小写' }, { validator: this.handlePasswordCheck }], validateTrigger: 'blur'}">
 
         <a-input size="large" type="password" placeholder="确认密码"></a-input>
       </a-form-item>
@@ -74,15 +74,13 @@
 </template>
 
 <script>
-  import api from '@/api/'
-  import UserLayout from '@/components/layout/UserLayout'
+  import { getSmsCaptcha } from '@/api/login'
 
   export default {
     name: "Register",
     components: {
-      UserLayout
     },
-    data () {
+    data() {
       return {
         form: null,
 
@@ -95,20 +93,28 @@
     },
     methods: {
 
-      handleSubmit () {
+      handlePasswordCheck (rule, value, callback) {
+        let password = this.form.getFieldValue('password')
+        if (value && password && value.trim() !== password.trim()) {
+          callback(new Error('两次密码不一致'))
+        }
+        callback()
+      },
+
+      handleSubmit() {
         this.form.validateFields((err, values) => {
           if (!err) {
-            console.log('form data', values)
+            this.$router.push({ name: 'registerResult', params: {...values} })
           }
         })
       },
 
-      getCaptcha (e) {
+      getCaptcha(e) {
         e.preventDefault()
         let that = this
 
-        this.form.validateFields([ 'mobile' ], { force: true },
-          (err) => {
+        this.form.validateFields(['mobile'], {force: true},
+          (err, values) => {
             if (!err) {
               this.state.smsSendBtn = true;
 
@@ -121,28 +127,27 @@
               }, 1000);
 
               const hide = this.$message.loading('验证码发送中..', 0);
-              this.$http.post(api.SendSms, { mobile: that.formRegister.mobile })
-                .then(res => {
-                  setTimeout(hide, 2500);
-                  this.$notification[ 'success' ]({
-                    message: '提示',
-                    description: '验证码获取成功,您的验证码为:' + res.result.captcha,
-                    duration: 8
-                  })
+
+              getSmsCaptcha({mobile: values.mobile}).then(res => {
+                setTimeout(hide, 2500);
+                this.$notification['success']({
+                  message: '提示',
+                  description: '验证码获取成功,您的验证码为:' + res.result.captcha,
+                  duration: 8
                 })
-                .catch(err => {
-                  setTimeout(hide, 1);
-                  clearInterval(interval);
-                  that.state.time = 60;
-                  that.state.smsSendBtn = false;
-                  this.requestFailed(err);
-                });
+              }).catch(err => {
+                setTimeout(hide, 1);
+                clearInterval(interval);
+                that.state.time = 60;
+                that.state.smsSendBtn = false;
+                this.requestFailed(err);
+              });
             }
           }
         );
       },
-      requestFailed (err) {
-        this.$notification[ 'error' ]({
+      requestFailed(err) {
+        this.$notification['error']({
           message: '错误',
           description: ((err.response || {}).data || {}).message || "请求出现错误,请稍后再试",
           duration: 4,
@@ -156,7 +161,7 @@
 <style lang="scss" scoped>
   .user-layout-register {
 
-    &> h3 {
+    & > h3 {
       font-size: 16px;
       margin-bottom: 20px;
     }

+ 50 - 0
src/views/user/RegisterResult.vue

@@ -0,0 +1,50 @@
+<template>
+  <result
+    :isSuccess="true"
+    :content="false"
+    :title="email"
+    :description="description">
+
+    <template slot="action">
+      <a-button size="large" type="primary">查看邮箱</a-button>
+      <a-button size="large" style="margin-left: 8px" @click="goHomeHandle">返回首页</a-button>
+    </template>
+
+  </result>
+</template>
+
+<script>
+  import Result from '@/views/result/Result'
+
+  export default {
+    name: "RegisterResult",
+    components: {
+      Result
+    },
+    data () {
+      return {
+        description: '激活邮件已发送到你的邮箱中,邮件有效期为24小时。请及时登录邮箱,点击邮件中的链接激活帐户。',
+        form: {}
+      }
+    },
+    computed: {
+      email () {
+        let v = this.form && this.form.email || 'xxx'
+        let title = `你的账户:${v} 注册成功`
+        return title
+      }
+    },
+    created () {
+      this.form = this.$route.params
+    },
+    methods: {
+      goHomeHandle () {
+        this.$router.push({ name: 'login' })
+      }
+    }
+  }
+</script>
+
+<style scoped>
+
+</style>