Browse Source

add: drawer menu

Sendya 6 năm trước cách đây
mục cha
commit
9e01b91461

+ 93 - 86
src/components/layout/LayoutHeader.vue

@@ -1,87 +1,94 @@
-<template>
-  <a-layout-header style="padding: 0px;">
-    <div class="header">
-      <a-icon 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">
-              <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
-      },
-    },
-    data() {
-      return {
-
-      }
-    },
-    created() {
-
-    },
-    methods: {
-      ...mapActions(["Logout"]),
-      ...mapGetters(["nickname", "avatar"]),
-      handleLogout() {
-        this.Logout({}).then(() => {
-          window.location.reload()
-        }).catch(err => {
-          this.$message.error(err.message)
-        })
-      },
-      toggle() {
-        this.$emit('toggle')
-      }
-    }
-  }
-</script>
-
-<style scoped>
-
+<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() {
+        this.Logout({}).then(() => {
+          window.location.reload()
+        }).catch(err => {
+          this.$message.error(err.message)
+        })
+      },
+      toggle() {
+        this.$emit('toggle')
+      }
+    }
+  }
+</script>
+
+<style scoped>
+
 </style>

+ 261 - 236
src/components/layout/LayoutMain.vue

@@ -1,237 +1,262 @@
-<template>
-  <a-layout class="layout">
-
-    <sider-menu
-      :menus="menus"
-      :theme="theme"
-      v-if="menuMode === 'inline'"
-      :mode="menuMode"
-      :collapsed="!siderOpen || collapsed"
-      :collapsible="true"></sider-menu>
-
-    <a-layout>
-      <!-- layout header -->
-      <layout-header :collapsed="collapsed" @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>
-  </a-layout>
-</template>
-
-<script>
-  import SiderMenu from '@/components/menu/SiderMenu'
-  import LayoutHeader from './LayoutHeader'
-  import LayoutFooter from './LayoutFooter'
-  import { asyncRouterMap } from '@/router/index'
-  import { mapState } from 'vuex'
-
-  export default {
-    name: "LayoutView",
-    components: {
-      SiderMenu,
-      LayoutHeader,
-      LayoutFooter
-    },
-    data () {
-      return {
-        // light, dark
-        menuTheme: 'light',
-        // inline, horizontal
-        menuMode: 'inline',
-        collapsed: false,
-        menus: []
-      }
-    },
-    created () {
-      this.menus = asyncRouterMap
-    },
-    computed: {
-      ...mapState({
-        siderOpen: state => state.app.sidebar.opened,
-        theme: state => state.app.theme
-      })
-    },
-    methods: {
-      toggle () {
-        this.collapsed = !this.collapsed;
-      },
-    }
-  }
-</script>
-
-<style lang="scss">
-
-  body {
-    // 打开滚动条固定显示
-    overflow-y: scroll;
-  }
-
-  .layout {
-    min-height: 100vh;
-    overflow-x: hidden;
-
-    &.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;
-      }
-    }
-
-    .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: "Myriad Pro", "Helvetica Neue", Arial, Helvetica, sans-serif;
-          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;
-        }
-      }
-
-    }
-
-    .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%;
-    }
-
-  }
-
-  // 外置的样式控制
-  .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;
-  }
-  .content {
-
-    .search {
-      margin-bottom: 54px;
-    }
-
-    .fold {
-      width: calc(100% - 216px);
-      display: inline-block
-    }
-
-    .operator {
-      margin-bottom: 18px;
-    }
-
-    @media screen and (max-width: 900px) {
-      .fold {
-        width: 100%;
-      }
-    }
-  }
+<template>
+  <a-layout class="layout">
+
+    <a-drawer v-if="device === 'mobile'"
+              wrapClassName="drawer-sider"
+              placement="left"
+              @close="() => this.collapsed = false"
+              :closable="false"
+              :visible="collapsed"
+      >
+      <sider-menu
+        mode="inline"
+        :menus="menus"
+        :theme="theme"
+        :collapsed="false"
+        :collapsible="true"></sider-menu>
+    </a-drawer>
+    <sider-menu
+      v-else
+      :menus="menus"
+      :theme="theme"
+      :mode="menuMode"
+      :collapsed="!siderOpen || 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>
+  </a-layout>
+</template>
+
+<script>
+  import SiderMenu from '@/components/menu/SiderMenu'
+  import LayoutHeader from './LayoutHeader'
+  import LayoutFooter from './LayoutFooter'
+  import { asyncRouterMap } from '@/router/index'
+  import { mapState } from 'vuex'
+
+  export default {
+    name: "LayoutView",
+    components: {
+      SiderMenu,
+      LayoutHeader,
+      LayoutFooter
+    },
+    data () {
+      return {
+        // light, dark
+        menuTheme: 'light',
+        // inline, horizontal
+        menuMode: 'inline',
+        collapsed: false,
+        menus: []
+      }
+    },
+    created () {
+      this.menus = asyncRouterMap
+
+      console.log( this.collapsed )
+    },
+    computed: {
+      ...mapState({
+        siderOpen: state => state.app.sidebar.opened,
+        theme: state => state.app.theme,
+        device: state => state.app.device,
+      })
+    },
+    methods: {
+      toggle () {
+        this.collapsed = !this.collapsed;
+      },
+    }
+  }
+</script>
+
+<style lang="scss">
+
+  body {
+    // 打开滚动条固定显示
+    overflow-y: scroll;
+  }
+
+  .layout {
+    min-height: 100vh;
+    overflow-x: hidden;
+
+    &.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 {
+    .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: "Myriad Pro", "Helvetica Neue", Arial, Helvetica, sans-serif;
+        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;
+  }
+  .content {
+
+    .search {
+      margin-bottom: 54px;
+    }
+
+    .fold {
+      width: calc(100% - 216px);
+      display: inline-block
+    }
+
+    .operator {
+      margin-bottom: 18px;
+    }
+
+    @media screen and (max-width: 900px) {
+      .fold {
+        width: 100%;
+      }
+    }
+  }
 </style>

+ 71 - 0
src/components/menu/MiniSiderMenu.vue

@@ -0,0 +1,71 @@
+<template>
+  <a-layout-sider 
+    :class="['sider', isMobile ? null : 'shadow', theme ]" 
+    width="256px" 
+    :collapsible="collapsible"
+    v-model="collapsed" 
+    :trigger="null">
+    <div class="logo">
+      <router-link :to="{name:'dashboard'}">
+        <img src="~@/assets/logo.svg" alt="logo">
+        <h1>Ant Design Pro</h1>
+      </router-link>
+    </div>
+    <s-menu
+      :collapsed="collapsed"
+      :menu="menus"
+      :theme="theme"
+      @select="onSelect"
+      :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'
+
+  export default {
+    name: "SiderMenu",
+    components: { ALayoutSider, SMenu },
+    props: {
+      mode: {
+        type: String,
+        required: false,
+        default: 'inline'
+      },
+      theme: {
+        type: String,
+        required: false,
+        default: 'dark'
+      },
+      collapsible: {
+        type: Boolean,
+        required: false,
+        default: false
+      },
+      collapsed: {
+        type: Boolean,
+        required: false,
+        default: false
+      },
+      menus: {
+        type: Array,
+        required: true
+      }
+    },
+    created () {
+
+    },
+    computed: {
+      isMobile () {
+        return this.$store.state.app.device !== 'desktop'
+      }
+    },
+    methods: {
+      onSelect (obj) {
+        this.$emit('menuSelect', obj)
+      }
+    }
+  }
+</script>

+ 241 - 241
src/components/table/index.js

@@ -1,241 +1,241 @@
-import T from "ant-design-vue/es/table/Table";
-export default {
-  data() {
-    return {
-      needTotalList: [],
-
-      selectedRows: [],
-      selectedRowKeys: [],
-
-      localLoading: false,
-      localDataSource: [],
-      localPagination: Object.assign({}, T.props.pagination)
-    };
-  },
-  props: Object.assign({}, T.props, {
-    data: {
-      type: Function,
-      required: true
-    },
-    pageNum: {
-      type: Number,
-      default: 1
-    },
-    pageSize: {
-      type: Number,
-      default: 10
-    },
-    showSizeChanger: {
-      type: Boolean,
-      default: true
-    },
-    showAlertInfo: {
-      type: Boolean,
-      default: true
-    },
-  }),
-  watch: {
-    'localPagination.current' (val) {
-      this.$router.push({
-        name: this.$route.name,
-        params: Object.assign({}, this.$route.params, {
-          pageNo: val
-        }),
-      });
-    },
-    pageNum(val) {
-      this.localPagination = Object.assign({}, this.localPagination, {
-        current: val
-      });
-    },
-    pageSize(val) {
-      this.localPagination = Object.assign({}, this.localPagination, {
-        pageSize: val
-      });
-    },
-    showSizeChanger(val) {
-      this.localPagination = Object.assign({}, this.localPagination, {
-        showSizeChanger: val
-      });
-    },
-    /*
-    'selectedRows': function (selectedRows) {
-      this.needTotalList = this.needTotalList.map(item => {
-        return {
-          ...item,
-          total: selectedRows.reduce( (sum, val) => {
-            return sum + val[item.dataIndex]
-          }, 0)
-        }
-      })
-    }*/
-  },
-  created() {
-    this.localPagination = Object.assign({}, this.localPagination, {
-      current: this.pageNum,
-      pageSize: this.pageSize,
-      showSizeChanger: this.showSizeChanger
-    });
-
-    this.needTotalList = this.initTotalList(this.columns)
-
-    this.loadData();
-  },
-  methods: {
-    refresh() {
-      this.loadData();
-    },
-    loadData(pagination, filters, sorter) {
-      this.localLoading = true
-      const result = this.data(
-        Object.assign({
-            pageNo:
-              (pagination && pagination.current) ||
-              this.localPagination.current,
-            pageSize:
-              (pagination && pagination.pageSize) ||
-              this.localPagination.pageSize
-          },
-          (sorter && sorter.field && {
-            sortField: sorter.field
-          }) || {},
-          (sorter && sorter.order && {
-            sortOrder: sorter.order
-          }) || {}, {
-            ...filters
-          }
-        )
-      );
-
-      if (result instanceof Promise) {
-        result.then(r => {
-          this.localPagination = Object.assign({}, this.localPagination, {
-            current: r.pageNo,  // 返回结果中的当前分页数
-            total: r.totalCount, // 返回结果中的总记录数
-            showSizeChanger: this.showSizeChanger,
-            pageSize: (pagination && pagination.pageSize) ||
-              this.localPagination.pageSize
-          });
-          this.localDataSource = r.data; // 返回结果中的数组数据
-          this.localLoading = false
-        }).catch(() => {
-          this.localLoading = false
-        });
-      }
-    },
-    initTotalList (columns) {
-      const totalList = []
-      columns.forEach(column => {
-        if (column.needTotal) {
-          totalList.push({ ...column, total: 0 })
-        }
-      })
-      return totalList
-    },
-    updateSelect (selectedRowKeys, selectedRows) {
-      this.selectedRowKeys = selectedRowKeys
-      this.selectedRows = selectedRows
-      let list = this.needTotalList
-      this.needTotalList = list.map(item => {
-        return {
-          ...item,
-          total: selectedRows.reduce((sum, val) => {
-            return sum + val[item.dataIndex]
-          }, 0)
-        }
-      })
-      // this.$emit('change', selectedRowKeys, selectedRows)
-    },
-    updateEdit() {
-      this.selectedRows = []
-    },
-    onClearSelected () {
-      this.selectedRowKeys = []
-      this.updateSelect([], [])
-    },
-    renderMsg(h) {
-      const _vm = this
-      let d = []
-      // 构建 已选择
-      d.push(
-        h('span', { style: { marginRight: '12px' } }, ['已选择 ', h('a', { style: { fontWeight: 600 }}, this.selectedRows.length)])
-      );
-
-      // 构建 列统计
-      this.needTotalList.map(item => {
-        d.push( h('span',
-          { style: { marginRight: '12px' } },
-          [
-            `${ item.title }总计 `,
-            h('a', { style: { fontWeight: 600 }}, `${ item.customRender ? item.customRender(item.total) : item.total }`)
-          ] )
-        )
-      });
-
-      // 构建 清空选择
-      d.push( h('a', {
-        style: { marginLeft: '24px' },
-        on: {
-          click: _vm.onClearSelected
-        }
-      }, '清空') )
-
-      return d
-    },
-    renderAlert(h) {
-
-      return h('span', {
-        slot: 'message'
-      }, this.renderMsg(h))
-    },
-  },
-  render(h) {
-    const _vm = this
-
-    let props = {},
-      localKeys = Object.keys(this.$data);
-
-    Object.keys(T.props).forEach(k => {
-      let localKey = `local${k.substring(0,1).toUpperCase()}${k.substring(1)}`;
-      if (localKeys.includes(localKey)) {
-        return props[k] = _vm[localKey];
-      }
-      return props[k] = _vm[k];
-    })
-
-    // 显示信息提示
-    if (this.showAlertInfo) {
-
-      props.rowSelection = { selectedRowKeys: this.selectedRowKeys, onChange: this.updateSelect };
-
-      return h('div', {}, [
-        h("a-alert", {
-          style: {
-            marginBottom: '16px'
-          },
-          props: {
-            type: 'info',
-            showIcon: true
-          }
-        }, [ _vm.renderAlert(h) ]),
-        h("a-table", {
-          tag: "component",
-          attrs: props,
-          on: {
-            change: _vm.loadData
-          },
-          scopedSlots: this.$scopedSlots
-        })
-      ]);
-    }
-
-    return h("a-table", {
-      tag: "component",
-      attrs: props,
-      on: {
-        change: _vm.loadData
-      },
-      scopedSlots: this.$scopedSlots
-    });
-  }
-};
+import T from "ant-design-vue/es/table/Table";
+export default {
+  data() {
+    return {
+      needTotalList: [],
+
+      selectedRows: [],
+      selectedRowKeys: [],
+
+      localLoading: false,
+      localDataSource: [],
+      localPagination: Object.assign({}, T.props.pagination)
+    };
+  },
+  props: Object.assign({}, T.props, {
+    data: {
+      type: Function,
+      required: true
+    },
+    pageNum: {
+      type: Number,
+      default: 1
+    },
+    pageSize: {
+      type: Number,
+      default: 10
+    },
+    showSizeChanger: {
+      type: Boolean,
+      default: true
+    },
+    showAlertInfo: {
+      type: Boolean,
+      default: false
+    },
+  }),
+  watch: {
+    'localPagination.current' (val) {
+      this.$router.push({
+        name: this.$route.name,
+        params: Object.assign({}, this.$route.params, {
+          pageNo: val
+        }),
+      });
+    },
+    pageNum(val) {
+      this.localPagination = Object.assign({}, this.localPagination, {
+        current: val
+      });
+    },
+    pageSize(val) {
+      this.localPagination = Object.assign({}, this.localPagination, {
+        pageSize: val
+      });
+    },
+    showSizeChanger(val) {
+      this.localPagination = Object.assign({}, this.localPagination, {
+        showSizeChanger: val
+      });
+    },
+    /*
+    'selectedRows': function (selectedRows) {
+      this.needTotalList = this.needTotalList.map(item => {
+        return {
+          ...item,
+          total: selectedRows.reduce( (sum, val) => {
+            return sum + val[item.dataIndex]
+          }, 0)
+        }
+      })
+    }*/
+  },
+  created() {
+    this.localPagination = Object.assign({}, this.localPagination, {
+      current: this.pageNum,
+      pageSize: this.pageSize,
+      showSizeChanger: this.showSizeChanger
+    });
+
+    this.needTotalList = this.initTotalList(this.columns)
+
+    this.loadData();
+  },
+  methods: {
+    refresh() {
+      this.loadData();
+    },
+    loadData(pagination, filters, sorter) {
+      this.localLoading = true
+      const result = this.data(
+        Object.assign({
+            pageNo:
+              (pagination && pagination.current) ||
+              this.localPagination.current,
+            pageSize:
+              (pagination && pagination.pageSize) ||
+              this.localPagination.pageSize
+          },
+          (sorter && sorter.field && {
+            sortField: sorter.field
+          }) || {},
+          (sorter && sorter.order && {
+            sortOrder: sorter.order
+          }) || {}, {
+            ...filters
+          }
+        )
+      );
+
+      if (result instanceof Promise) {
+        result.then(r => {
+          this.localPagination = Object.assign({}, this.localPagination, {
+            current: r.pageNo,  // 返回结果中的当前分页数
+            total: r.totalCount, // 返回结果中的总记录数
+            showSizeChanger: this.showSizeChanger,
+            pageSize: (pagination && pagination.pageSize) ||
+              this.localPagination.pageSize
+          });
+          this.localDataSource = r.data; // 返回结果中的数组数据
+          this.localLoading = false
+        }).catch(() => {
+          this.localLoading = false
+        });
+      }
+    },
+    initTotalList (columns) {
+      const totalList = []
+      columns.forEach(column => {
+        if (column.needTotal) {
+          totalList.push({ ...column, total: 0 })
+        }
+      })
+      return totalList
+    },
+    updateSelect (selectedRowKeys, selectedRows) {
+      this.selectedRowKeys = selectedRowKeys
+      this.selectedRows = selectedRows
+      let list = this.needTotalList
+      this.needTotalList = list.map(item => {
+        return {
+          ...item,
+          total: selectedRows.reduce((sum, val) => {
+            return sum + val[item.dataIndex]
+          }, 0)
+        }
+      })
+      // this.$emit('change', selectedRowKeys, selectedRows)
+    },
+    updateEdit() {
+      this.selectedRows = []
+    },
+    onClearSelected () {
+      this.selectedRowKeys = []
+      this.updateSelect([], [])
+    },
+    renderMsg(h) {
+      const _vm = this
+      let d = []
+      // 构建 已选择
+      d.push(
+        h('span', { style: { marginRight: '12px' } }, ['已选择 ', h('a', { style: { fontWeight: 600 }}, this.selectedRows.length)])
+      );
+
+      // 构建 列统计
+      this.needTotalList.map(item => {
+        d.push( h('span',
+          { style: { marginRight: '12px' } },
+          [
+            `${ item.title }总计 `,
+            h('a', { style: { fontWeight: 600 }}, `${ item.customRender ? item.customRender(item.total) : item.total }`)
+          ] )
+        )
+      });
+
+      // 构建 清空选择
+      d.push( h('a', {
+        style: { marginLeft: '24px' },
+        on: {
+          click: _vm.onClearSelected
+        }
+      }, '清空') )
+
+      return d
+    },
+    renderAlert(h) {
+
+      return h('span', {
+        slot: 'message'
+      }, this.renderMsg(h))
+    },
+  },
+  render(h) {
+    const _vm = this
+
+    let props = {},
+      localKeys = Object.keys(this.$data);
+
+    Object.keys(T.props).forEach(k => {
+      let localKey = `local${k.substring(0,1).toUpperCase()}${k.substring(1)}`;
+      if (localKeys.includes(localKey)) {
+        return props[k] = _vm[localKey];
+      }
+      return props[k] = _vm[k];
+    })
+
+    // 显示信息提示
+    if (this.showAlertInfo) {
+
+      props.rowSelection = { selectedRowKeys: this.selectedRowKeys, onChange: this.updateSelect };
+
+      return h('div', {}, [
+        h("a-alert", {
+          style: {
+            marginBottom: '16px'
+          },
+          props: {
+            type: 'info',
+            showIcon: true
+          }
+        }, [ _vm.renderAlert(h) ]),
+        h("a-table", {
+          tag: "component",
+          attrs: props,
+          on: {
+            change: _vm.loadData
+          },
+          scopedSlots: this.$scopedSlots
+        })
+      ]);
+    }
+
+    return h("a-table", {
+      tag: "component",
+      attrs: props,
+      on: {
+        change: _vm.loadData
+      },
+      scopedSlots: this.$scopedSlots
+    });
+  }
+};

+ 146 - 31
src/components/tools/DetailList.vue

@@ -1,32 +1,147 @@
-<template>
-  <div :class="['detail-list', size === 'small' ? 'small' : 'large', layout === 'vertical' ? 'vertical': 'horizontal']">
-    <div v-if="title" class="title">{{ title }}</div>
-    <a-row>
-      <slot></slot>
-    </a-row>
-  </div>
-</template>
-
-<script>
-  export default {
-    name: "DetailList",
-    props: {
-      term: {
-        type: String,
-        default: null
-      }
-    },
-    inject: {
-      col: {
-        type: Number
-      }
-    },
-    methods: {
-
-    }
-  }
-</script>
-
-<style scoped>
-
+<template>
+  <div :class="['detail-list', size, layout === 'vertical' ? 'vertical': 'horizontal']">
+    <div v-if="title" class="title">{{ title }}</div>
+    <a-row>
+      <slot></slot>
+    </a-row>
+  </div>
+</template>
+
+<script>
+  import { Col } from 'ant-design-vue/es/grid/'
+
+  const Item = {
+    name: 'DetailListItem',
+    props: {
+      term: {
+        type: String,
+        default: '',
+        required: false
+      },
+    },
+    inject: {
+      col: {
+        type: Number
+      }
+    },
+    render () {
+      return (
+        <Col {...{props: responsive[this.col]}}>
+          <div class="term">{this.$props.term}</div>
+          <div class="content">{this.$slots.default}</div>
+        </Col>
+      )
+    }
+  }
+
+  const responsive = {
+    1: { xs: 24 },
+    2: { xs: 24, sm: 12 },
+    3: { xs: 24, sm: 12, md: 8 },
+    4: { xs: 24, sm: 12, md: 6 }
+  }
+
+  export default {
+    name: "DetailList",
+    Item: Item,
+    components: {
+      Col
+    },
+    props: {
+      title: {
+        type: String,
+        default: '',
+        required: false
+      },
+      col: {
+        type: Number,
+        required: false,
+        default: 3
+      },
+      size: {
+        type: String,
+        required: false,
+        default: 'large'
+      },
+      layout: {
+        type: String,
+        required: false,
+        default: 'horizontal'
+      }
+    },
+    provide () {
+      return {
+        col: this.col > 4 ? 4 : this.col
+      }
+    }
+  }
+</script>
+
+<style lang="scss">
+
+  .detail-list {
+
+    .title {
+      color: rgba(0,0,0,.85);
+      font-size: 14px;
+      font-weight: 500;
+      margin-bottom: 16px;
+    }
+
+    .term {
+      color: rgba(0,0,0,.85);
+      display: table-cell;
+      line-height: 20px;
+      margin-right: 8px;
+      padding-bottom: 16px;
+      white-space: nowrap;
+
+      &:after {
+        content: ":";
+        margin: 0 8px 0 2px;
+        position: relative;
+        top: -.5px;
+      }
+    }
+
+    .content {
+      color: rgba(0,0,0,.65);
+      display: table-cell;
+      line-height: 22px;
+      padding-bottom: 16px;
+      width: 100%;
+    }
+
+    &.small {
+
+      .title {
+        font-size: 14px;
+        color: rgba(0, 0, 0, .65);
+        font-weight: normal;
+        margin-bottom: 12px;
+      }
+      .term, .content {
+        padding-bottom: 8px;
+      }
+    }
+
+    &.large {
+      .term, .content {
+        padding-bottom: 16px;
+      }
+
+      .title {
+        font-size: 16px;
+      }
+    }
+
+    &.vertical {
+      .term {
+        padding-bottom: 8px;
+      }
+      .term, .content {
+        display: block;
+      }
+    }
+  }
 </style>

+ 301 - 301
src/router/index.js

@@ -1,302 +1,302 @@
-import Vue from 'vue'
-import Router from 'vue-router'
-import Layout from '../components/layout/LayoutView'
-import LayoutBase from '../components/layout/LayoutBaseView'
-
-Vue.use(Router)
-/**
- * 路由配置说明:
- * 建议:sider menu 请不要超过三级菜单,若超过三级菜单,则应该设计为顶部主菜单 配合左侧次级菜单
- *
- * hidden: true                   if `hidden:true` will not show in the sidebar(default is false)
- * alwaysShow: true               if set true, will always show the root menu, whatever its child routes length
- *                                if not set alwaysShow, only more than one route under the children
- *                                it will becomes nested mode, otherwise not show the root menu
- * redirect: noredirect           if `redirect:noredirect` will no redirct in the breadcrumb
- * name:'router-name'             the name is used by <keep-alive> (must set!!!)
- * meta : {
-    title: 'title'               the name show in submenu and breadcrumb (recommend set)
-    icon: 'svg-name'             the icon show in the sidebar,
-    keepAlive: true              keep alive component
-    hiddenPageHeader: true       if `hiddenPageHeader: true` will not show page-header(details)
-  }
- **/
-export const constantRouterMap = [
-  {
-    path: '/login',
-    component: () => import('../views/Login')
-  },
-  {
-    path: '/404',
-    component: () => import(/* webpackChunkName: "fail" */ '../views/exception/404')
-  },
-  {
-    path: '/',
-    component: Layout,
-    redirect: '/login',
-    name: '首页',
-    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' },
-    children: [
-      {
-        path: '/dashboard/analysis',
-        name: 'Analysis',
-        component: () => import('../views/dashboard/Analysis'),
-        meta: { title: '分析页', hideHeader: true }
-      },
-      {
-        path: '/dashboard/monitor',
-        name: 'Monitor',
-        hidden: true,
-        component: () => import('../views/dashboard/Monitor'),
-        meta: { title: '监控页', hideHeader: true }
-      },
-      {
-        path: '/dashboard/workplace',
-        name: 'Workplace',
-        component: () => import('../views/dashboard/Workplace'),
-        meta: { title: '工作台' }
-      }
-    ]
-  },
-  {
-    path: '/form',
-    component: LayoutBase,
-    name: 'form',
-    redirect: '/form/base-form',
-    meta: { title: '表单页', icon: 'form' },
-    children: [
-      {
-        path: '/form/base-form',
-        name: 'BaseForm',
-        component: () => import('../views/form/BasicForm'),
-        meta: { title: '基础表单' }
-      },
-      {
-        path: '/form/step-form',
-        name: 'StepForm',
-        component: () => import('../views/form/stepForm/StepForm'),
-        meta: { title: '分步表单' }
-      },
-      {
-        path: '/form/advanced-form',
-        name: 'AdvanceForm',
-        component: () => import('../views/form/advancedForm/AdvancedForm'),
-        meta: { title: '高级表单' }
-      }
-    ]
-  },
-  {
-    path: '/list',
-    component: LayoutBase,
-    name: 'list',
-    redirect: '/list/query-list',
-    meta: { title: '列表页', icon: 'table' },
-    children: [
-      {
-        path: '/list/query-list',
-        name: 'QueryList',
-        component: () => import('../views/list/TableList'),
-        meta: { title: '查询表格' }
-      },
-      {
-        path: '/list/edit-table',
-        name: 'EditList',
-        component: () => import('@/views/list/TableInnerEditList'),
-        meta: { title: '内联编辑表格' }
-      },
-      {
-        path: '/list/basic-list',
-        name: 'BasicList',
-        component: () => import('../views/list/StandardList'),
-        meta: { title: '标准列表' }
-      },
-      {
-        path: '/list/card',
-        name: 'CardList',
-        component: () => import('../views/list/CardList'),
-        meta: { title: '卡片列表' }
-      },
-      {
-        path: '/list/search',
-        name: 'SearchList',
-        component: () => import('@/views/list/search/SearchLayout'),
-        redirect: '/list/search/article',
-        meta: { title: '搜索列表' },
-        children: [
-          {
-            path: '/list/search/article',
-            name: 'SearchArticles',
-            component: () => import('../views/list/TableList'),
-            meta: { title: '搜索列表(文章)' }
-          },
-          {
-              path: '/list/search/project',
-              name: 'SearchProjects',
-              component: () => import('../views/list/TableList'),
-              meta: { title: '搜索列表(项目)' }
-          },
-          {
-              path: '/list/search/application',
-              name: 'SearchApplications',
-              component: () => import('../views/list/TableList'),
-              meta: { title: '搜索列表(应用)' }
-          },
-        ]
-      },
-    ]
-  },
-  {
-    path: '/profile',
-    component: LayoutBase,
-    name: 'profile',
-    redirect: '/profile/basic',
-    meta: { title: '详情页', icon: 'profile' },
-    children: [
-      {
-        path: '/profile/basic',
-        name: 'ProfileBasic',
-        component: () => import('../views/profile/basic/Index'),
-        meta: { title: '基础详情页' }
-      },
-      {
-        path: '/profile/advanced',
-        name: 'ProfileAdvanced',
-        component: () => import('../views/list/TableList'),
-        meta: { title: '高级详情页' }
-      }
-    ]
-  },
-  {
-    path: '/result',
-    component: LayoutBase,
-    name: 'result',
-    redirect: '/result/success',
-    meta: { title: '结果页', icon: 'check-circle-o' },
-    children: [
-      {
-        path: '/result/success',
-        name: 'ResultSuccess',
-        component: () => import(/* webpackChunkName: "result" */ '../views/result/Success'),
-        meta: { title: '成功', hiddenPageHeader: true }
-      },
-      {
-        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 }
-      }
-    ]
-  },
-  {
-    path: '/exception',
-    component: Layout,
-    name: 'exception',
-    redirect: '/exception/403',
-    meta: { title: '异常页', icon: 'warning' },
-    children: [
-      {
-        path: '/exception/403',
-        name: 'Exception403',
-        component: () => import(/* webpackChunkName: "fail" */ '../views/exception/403'),
-        meta: { title: '403' }
-      },
-      {
-        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' }
-      },
-      {
-        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' }
-      }
-    ]
-  },
-  {
-    path: '/account',
-    component: Layout,
-    name: 'account',
-    meta: { title: '个人页', icon: 'user', keepAlive: true },
-    children: [
-      {
-        path: '/account/center',
-        name: 'center',
-        component: () => import('@/views/account/center/Index'),
-        meta: { title: '个人中心', keepAlive: true }
-      },
-      {
-        path: '/account/settings',
-        name: 'settings',
-        component: () => import('@/views/account/settings/Index'),
-        meta: { title: '个人设置', hideHeader: true, keepAlive: true },
-        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 }
-          },
-          {
-            path: '/account/settings/security',
-            name: 'SecuritySettings',
-            component: () => import('@/views/account/settings/Security'),
-            meta: { title: '安全设置', hidden: true, keepAlive: true }
-          },
-          {
-            path: '/account/settings/custom',
-            name: 'CustomSettings',
-            component: () => import('@/views/account/settings/Custom'),
-            meta: { title: '个性化设置', hidden: true, keepAlive: true }
-          },
-          {
-            path: '/account/settings/binding',
-            name: 'BindingSettings',
-            component: () => import('@/views/account/settings/Binding'),
-            meta: { title: '账户绑定', hidden: true, keepAlive: true }
-          },
-          {
-            path: '/account/settings/notification',
-            name: 'NotificationSettings',
-            component: () => import('@/views/account/settings/Notification'),
-            meta: { title: '新消息通知', hidden: true, keepAlive: true }
-          },
-        ]
-      },
-
-    ]
-  },
-
-  {
-    path: '*', redirect: '/404', hidden: true
-  }
+import Vue from 'vue'
+import Router from 'vue-router'
+import Layout from '../components/layout/LayoutView'
+import LayoutBase from '../components/layout/LayoutBaseView'
+
+Vue.use(Router)
+/**
+ * 路由配置说明:
+ * 建议:sider menu 请不要超过三级菜单,若超过三级菜单,则应该设计为顶部主菜单 配合左侧次级菜单
+ *
+ * hidden: true                   if `hidden:true` will not show in the sidebar(default is false)
+ * alwaysShow: true               if set true, will always show the root menu, whatever its child routes length
+ *                                if not set alwaysShow, only more than one route under the children
+ *                                it will becomes nested mode, otherwise not show the root menu
+ * redirect: noredirect           if `redirect:noredirect` will no redirct in the breadcrumb
+ * name:'router-name'             the name is used by <keep-alive> (must set!!!)
+ * meta : {
+    title: 'title'               the name show in submenu and breadcrumb (recommend set)
+    icon: 'svg-name'             the icon show in the sidebar,
+    keepAlive: true              keep alive component
+    hiddenPageHeader: true       if `hiddenPageHeader: true` will not show page-header(details)
+  }
+ **/
+export const constantRouterMap = [
+  {
+    path: '/login',
+    component: () => import('../views/Login')
+  },
+  {
+    path: '/404',
+    component: () => import(/* webpackChunkName: "fail" */ '../views/exception/404')
+  },
+  {
+    path: '/',
+    component: Layout,
+    redirect: '/login',
+    name: '首页',
+    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' },
+    children: [
+      {
+        path: '/dashboard/analysis',
+        name: 'Analysis',
+        component: () => import('../views/dashboard/Analysis'),
+        meta: { title: '分析页', hideHeader: true }
+      },
+      {
+        path: '/dashboard/monitor',
+        name: 'Monitor',
+        hidden: true,
+        component: () => import('../views/dashboard/Monitor'),
+        meta: { title: '监控页', hideHeader: true }
+      },
+      {
+        path: '/dashboard/workplace',
+        name: 'Workplace',
+        component: () => import('../views/dashboard/Workplace'),
+        meta: { title: '工作台' }
+      }
+    ]
+  },
+  {
+    path: '/form',
+    component: LayoutBase,
+    name: 'form',
+    redirect: '/form/base-form',
+    meta: { title: '表单页', icon: 'form' },
+    children: [
+      {
+        path: '/form/base-form',
+        name: 'BaseForm',
+        component: () => import('../views/form/BasicForm'),
+        meta: { title: '基础表单' }
+      },
+      {
+        path: '/form/step-form',
+        name: 'StepForm',
+        component: () => import('../views/form/stepForm/StepForm'),
+        meta: { title: '分步表单' }
+      },
+      {
+        path: '/form/advanced-form',
+        name: 'AdvanceForm',
+        component: () => import('../views/form/advancedForm/AdvancedForm'),
+        meta: { title: '高级表单' }
+      }
+    ]
+  },
+  {
+    path: '/list',
+    component: LayoutBase,
+    name: 'list',
+    redirect: '/list/query-list',
+    meta: { title: '列表页', icon: 'table' },
+    children: [
+      {
+        path: '/list/query-list',
+        name: 'QueryList',
+        component: () => import('../views/list/TableList'),
+        meta: { title: '查询表格' }
+      },
+      {
+        path: '/list/edit-table',
+        name: 'EditList',
+        component: () => import('@/views/list/TableInnerEditList'),
+        meta: { title: '内联编辑表格' }
+      },
+      {
+        path: '/list/basic-list',
+        name: 'BasicList',
+        component: () => import('../views/list/StandardList'),
+        meta: { title: '标准列表' }
+      },
+      {
+        path: '/list/card',
+        name: 'CardList',
+        component: () => import('../views/list/CardList'),
+        meta: { title: '卡片列表' }
+      },
+      {
+        path: '/list/search',
+        name: 'SearchList',
+        component: () => import('@/views/list/search/SearchLayout'),
+        redirect: '/list/search/article',
+        meta: { title: '搜索列表' },
+        children: [
+          {
+            path: '/list/search/article',
+            name: 'SearchArticles',
+            component: () => import('../views/list/TableList'),
+            meta: { title: '搜索列表(文章)' }
+          },
+          {
+              path: '/list/search/project',
+              name: 'SearchProjects',
+              component: () => import('../views/list/TableList'),
+              meta: { title: '搜索列表(项目)' }
+          },
+          {
+              path: '/list/search/application',
+              name: 'SearchApplications',
+              component: () => import('../views/list/TableList'),
+              meta: { title: '搜索列表(应用)' }
+          },
+        ]
+      },
+    ]
+  },
+  {
+    path: '/profile',
+    component: Layout,
+    name: 'profile',
+    redirect: '/profile/basic',
+    meta: { title: '详情页', icon: 'profile' },
+    children: [
+      {
+        path: '/profile/basic',
+        name: 'ProfileBasic',
+        component: () => import('@/views/profile/basic/Index'),
+        meta: { title: '基础详情页' }
+      },
+      {
+        path: '/profile/advanced',
+        name: 'ProfileAdvanced',
+        component: () => import('@/views/profile/advanced/Advanced'),
+        meta: { title: '高级详情页' }
+      }
+    ]
+  },
+  {
+    path: '/result',
+    component: LayoutBase,
+    name: 'result',
+    redirect: '/result/success',
+    meta: { title: '结果页', icon: 'check-circle-o' },
+    children: [
+      {
+        path: '/result/success',
+        name: 'ResultSuccess',
+        component: () => import(/* webpackChunkName: "result" */ '../views/result/Success'),
+        meta: { title: '成功', hiddenPageHeader: true }
+      },
+      {
+        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 }
+      }
+    ]
+  },
+  {
+    path: '/exception',
+    component: Layout,
+    name: 'exception',
+    redirect: '/exception/403',
+    meta: { title: '异常页', icon: 'warning' },
+    children: [
+      {
+        path: '/exception/403',
+        name: 'Exception403',
+        component: () => import(/* webpackChunkName: "fail" */ '../views/exception/403'),
+        meta: { title: '403' }
+      },
+      {
+        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' }
+      },
+      {
+        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' }
+      }
+    ]
+  },
+  {
+    path: '/account',
+    component: Layout,
+    name: 'account',
+    meta: { title: '个人页', icon: 'user', keepAlive: true },
+    children: [
+      {
+        path: '/account/center',
+        name: 'center',
+        component: () => import('@/views/account/center/Index'),
+        meta: { title: '个人中心', keepAlive: true }
+      },
+      {
+        path: '/account/settings',
+        name: 'settings',
+        component: () => import('@/views/account/settings/Index'),
+        meta: { title: '个人设置', hideHeader: true, keepAlive: true },
+        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 }
+          },
+          {
+            path: '/account/settings/security',
+            name: 'SecuritySettings',
+            component: () => import('@/views/account/settings/Security'),
+            meta: { title: '安全设置', hidden: true, keepAlive: true }
+          },
+          {
+            path: '/account/settings/custom',
+            name: 'CustomSettings',
+            component: () => import('@/views/account/settings/Custom'),
+            meta: { title: '个性化设置', hidden: true, keepAlive: true }
+          },
+          {
+            path: '/account/settings/binding',
+            name: 'BindingSettings',
+            component: () => import('@/views/account/settings/Binding'),
+            meta: { title: '账户绑定', hidden: true, keepAlive: true }
+          },
+          {
+            path: '/account/settings/notification',
+            name: 'NotificationSettings',
+            component: () => import('@/views/account/settings/Notification'),
+            meta: { title: '新消息通知', hidden: true, keepAlive: true }
+          },
+        ]
+      },
+
+    ]
+  },
+
+  {
+    path: '*', redirect: '/404', hidden: true
+  }
 ]

+ 324 - 0
src/views/profile/advanced/Advanced.vue

@@ -0,0 +1,324 @@
+<template>
+  <page-layout title="单号:234231029431" logo="https://gw.alipayobjects.com/zos/rmsportal/nxkuOJlFJuAUhzlMTCEe.png">
+
+    <detail-list slot="headerContent" size="small" :col="2" style="margin-left: 44px;">
+      <detail-list-item term="创建人">曲丽丽</detail-list-item>
+      <detail-list-item term="订购产品">XX服务</detail-list-item>
+      <detail-list-item term="创建时间">2018-08-07</detail-list-item>
+      <detail-list-item term="关联单据"><a>12421</a></detail-list-item>
+      <detail-list-item term="生效日期">2018-08-07 ~ 2018-12-11</detail-list-item>
+      <detail-list-item term="备注">请于两个工作日内确认</detail-list-item>
+    </detail-list>
+    <a-row slot="extra">
+      <a-col :xs="24" :sm="12">
+        <div class="text">状态</div>
+        <div class="heading">待审批</div>
+      </a-col>
+      <a-col :xs="24" :sm="12">
+        <div class="text">订单金额</div>
+        <div class="heading">¥ 568.08</div>
+      </a-col>
+    </a-row>
+    <!-- actions -->
+    <template slot="action">
+      <a-button-group style="margin-right: 4px;">
+        <a-button>操作</a-button>
+        <a-button>操作</a-button>
+        <a-button><a-icon type="ellipsis"/></a-button>
+      </a-button-group>
+      <a-button type="primary" >主操作</a-button>
+    </template>
+
+    <a-card :bordered="false" title="流程进度">
+      <a-steps :current="1" progressDot>
+        <a-step title="创建项目">
+        </a-step>
+        <a-step title="部门初审">
+        </a-step>
+        <a-step title="财务复核">
+        </a-step>
+        <a-step title="完成">
+        </a-step>
+      </a-steps>
+    </a-card>
+
+    <a-card style="margin-top: 24px" :bordered="false" title="用户信息">
+      <detail-list>
+        <detail-list-item term="用户姓名">付晓晓</detail-list-item>
+        <detail-list-item term="会员卡号">32943898021309809423</detail-list-item>
+        <detail-list-item term="身份证">3321944288191034921</detail-list-item>
+        <detail-list-item term="联系方式">18112345678</detail-list-item>
+        <detail-list-item term="联系地址">浙江省杭州市西湖区黄姑山路工专路交叉路口</detail-list-item>
+      </detail-list>
+      <detail-list title="信息组">
+        <detail-list-item term="某某数据">725</detail-list-item>
+        <detail-list-item term="该数据更新时间">2018-08-08</detail-list-item>
+        <detail-list-item >&nbsp;</detail-list-item>
+        <detail-list-item term="某某数据">725</detail-list-item>
+        <detail-list-item term="该数据更新时间">2018-08-08</detail-list-item>
+        <detail-list-item >&nbsp;</detail-list-item>
+      </detail-list>
+      <a-card type="inner" title="多层信息组">
+        <detail-list title="组名称" size="small">
+          <detail-list-item term="负责人">林东东</detail-list-item>
+          <detail-list-item term="角色码">1234567</detail-list-item>
+          <detail-list-item term="所属部门">XX公司-YY部</detail-list-item>
+          <detail-list-item term="过期时间">2018-08-08</detail-list-item>
+          <detail-list-item term="描述">这段描述很长很长很长很长很长很长很长很长很长很长很长很长很长很长...</detail-list-item>
+        </detail-list>
+        <a-divider style="margin: 16px 0" />
+        <detail-list title="组名称" size="small" :col="1">
+          <detail-list-item term="学名">	Citrullus lanatus (Thunb.) Matsum. et Nakai一年生蔓生藤本;茎、枝粗壮,具明显的棱。卷须较粗..</detail-list-item>
+        </detail-list>
+        <a-divider style="margin: 16px 0" />
+        <detail-list title="组名称" size="small" :col="2">
+          <detail-list-item term="负责人">付小小</detail-list-item>
+          <detail-list-item term="角色码">1234567</detail-list-item>
+        </detail-list>
+      </a-card>
+
+    </a-card>
+
+    <a-card style="margin-top: 24px" :bordered="false" title="用户近半年来电记录">
+      <div class="no-data"><a-icon type="frown-o"/>暂无数据</div>
+    </a-card>
+
+    <!-- 操作 -->
+    <a-card
+      style="margin-top: 24px"
+      :bordered="false"
+      :tabList="tabList"
+      :activeTabKey="activeTabKey"
+      @tabChange="(key) => {this.activeTabKey = key}"
+    >
+      <a-table
+        v-if="activeTabKey === '1'"
+        :columns="operationColumns"
+        :dataSource="operation1"
+        :pagination="false"
+      >
+        <template
+          slot="status"
+          slot-scope="status">
+          <a-badge :status="status | statusTypeFilter" :text="status | statusFilter"/>
+        </template>
+      </a-table>
+      <a-table
+        v-if="activeTabKey === '2'"
+        :columns="operationColumns"
+        :dataSource="operation2"
+        :pagination="false"
+      >
+        <template
+          slot="status"
+          slot-scope="status">
+          <a-badge :status="status | statusTypeFilter" :text="status | statusFilter"/>
+        </template>
+      </a-table>
+      <a-table
+        v-if="activeTabKey === '3'"
+        :columns="operationColumns"
+        :dataSource="operation3"
+        :pagination="false"
+      >
+        <template
+          slot="status"
+          slot-scope="status">
+          <a-badge :status="status | statusTypeFilter" :text="status | statusFilter"/>
+        </template>
+      </a-table>
+    </a-card>
+
+  </page-layout>
+</template>
+
+<script>
+  import PageLayout from '@/components/layout/PageLayout'
+  import DetailList from '@/components/tools/DetailList'
+
+  const DetailListItem = DetailList.Item
+
+  export default {
+    name: "Advanced",
+    components: {
+      PageLayout,
+      DetailList,
+      DetailListItem
+    },
+    data () {
+      return {
+        tabList: [
+          {
+            key: '1',
+            tab: '操作日志一'
+          },
+          {
+            key: '2',
+            tab: '操作日志二'
+          },
+          {
+            key: '3',
+            tab: '操作日志三'
+          }
+        ],
+        activeTabKey: '1',
+
+        operationColumns: [
+          {
+            title: '操作类型',
+            dataIndex: 'type',
+            key: 'type'
+          },
+          {
+            title: '操作人',
+            dataIndex: 'name',
+            key: 'name'
+          },
+          {
+            title: '执行结果',
+            dataIndex: 'status',
+            key: 'status',
+            scopedSlots: { customRender: 'status' },
+          },
+          {
+            title: '操作时间',
+            dataIndex: 'updatedAt',
+            key: 'updatedAt'
+          },
+          {
+            title: '备注',
+            dataIndex: 'remark',
+            key: 'remark'
+          }
+        ],
+        operation1: [
+          {
+            key: 'op1',
+            type: '订购关系生效',
+            name: '曲丽丽',
+            status: 'agree',
+            updatedAt: '2017-10-03  19:23:12',
+            remark: '-'
+          },
+          {
+            key: 'op2',
+            type: '财务复审',
+            name: '付小小',
+            status: 'reject',
+            updatedAt: '2017-10-03  19:23:12',
+            remark: '不通过原因'
+          },
+          {
+            key: 'op3',
+            type: '部门初审',
+            name: '周毛毛',
+            status: 'agree',
+            updatedAt: '2017-10-03  19:23:12',
+            remark: '-'
+          },
+          {
+            key: 'op4',
+            type: '提交订单',
+            name: '林东东',
+            status: 'agree',
+            updatedAt: '2017-10-03  19:23:12',
+            remark: '很棒'
+          },
+          {
+            key: 'op5',
+            type: '创建订单',
+            name: '汗牙牙',
+            status: 'agree',
+            updatedAt: '2017-10-03  19:23:12',
+            remark: '-'
+          }
+        ],
+        operation2: [
+          {
+            key: 'op2',
+            type: '财务复审',
+            name: '付小小',
+            status: 'reject',
+            updatedAt: '2017-10-03  19:23:12',
+            remark: '不通过原因'
+          },
+          {
+            key: 'op3',
+            type: '部门初审',
+            name: '周毛毛',
+            status: 'agree',
+            updatedAt: '2017-10-03  19:23:12',
+            remark: '-'
+          },
+          {
+            key: 'op4',
+            type: '提交订单',
+            name: '林东东',
+            status: 'agree',
+            updatedAt: '2017-10-03  19:23:12',
+            remark: '很棒'
+          }
+        ],
+        operation3: [
+          {
+            key: 'op2',
+            type: '财务复审',
+            name: '付小小',
+            status: 'reject',
+            updatedAt: '2017-10-03  19:23:12',
+            remark: '不通过原因'
+          },
+          {
+            key: 'op3',
+            type: '部门初审',
+            name: '周毛毛',
+            status: 'agree',
+            updatedAt: '2017-10-03  19:23:12',
+            remark: '-'
+          }
+        ],
+      }
+    },
+    filters: {
+      statusFilter(status) {
+        const statusMap = {
+          'agree': '成功',
+          'reject': '驳回'
+        }
+        return statusMap[status]
+      },
+      statusTypeFilter(type) {
+        const statusTypeMap = {
+          'agree': 'success',
+          'reject': 'error'
+        }
+        return statusTypeMap[type]
+      }
+    },
+  }
+</script>
+
+<style lang="scss" scoped>
+  .text {
+    color: rgba(0, 0, 0, .45);
+  }
+
+  .heading {
+    color: rgba(0, 0, 0, .85);
+    font-size: 20px;
+  }
+
+  .no-data {
+    color: rgba(0, 0, 0, .25);
+    text-align: center;
+    line-height: 64px;
+    font-size: 16px;
+
+    i {
+      font-size: 24px;
+      margin-right: 16px;
+      position: relative;
+      top: 3px;
+    }
+  }
+</style>

+ 245 - 12
src/views/profile/basic/Index.vue

@@ -1,13 +1,246 @@
-<template>
-  <a-card :bordered="false">
-    basic
-  </a-card>
-</template>
-
-<script>
-  export default {}
-</script>
-
-<style scoped>
-
+<template>
+  <page-layout>
+    <a-card :bordered="false">
+      <detail-list title="退款申请">
+        <detail-list-item term="取货单号">1000000000</detail-list-item>
+        <detail-list-item term="状态">已取货</detail-list-item>
+        <detail-list-item term="销售单号">1234123421</detail-list-item>
+        <detail-list-item term="子订单">3214321432</detail-list-item>
+      </detail-list>
+      <a-divider style="margin-bottom: 32px"/>
+      <detail-list title="用户信息">
+        <detail-list-item term="用户姓名">付小小</detail-list-item>
+        <detail-list-item term="联系电话">18100000000</detail-list-item>
+        <detail-list-item term="常用快递">菜鸟仓储</detail-list-item>
+        <detail-list-item term="取货地址">浙江省杭州市西湖区万塘路18号</detail-list-item>
+        <detail-list-item term="备注">	无</detail-list-item>
+      </detail-list>
+      <a-divider style="margin-bottom: 32px"/>
+
+      <div class="title">退货商品</div>
+      <s-table
+        style="margin-bottom: 24px" :columns="goodsColumns" :data="loadGoodsData">
+
+      </s-table>
+
+      <div class="title">退货进度</div>
+      <s-table
+        style="margin-bottom: 24px" :columns="scheduleColumns" :data="loadScheduleData">
+
+        <template
+          slot="status"
+          slot-scope="status">
+          <a-badge :status="status" :text="status | statusFilter"/>
+        </template>
+
+      </s-table>
+    </a-card>
+  </page-layout>
+</template>
+
+<script>
+  import PageLayout from '@/components/layout/PageLayout'
+  import STable from '@/components/table/'
+  import DetailList from '@/components/tools/DetailList'
+  import ABadge from "ant-design-vue/es/badge/Badge";
+  const DetailListItem = DetailList.Item
+
+  export default {
+    components: {
+      PageLayout,
+      ABadge,
+      DetailList,
+      DetailListItem,
+      STable
+    },
+    data () {
+      return {
+        goodsColumns: [
+          {
+            title: '商品编号',
+            dataIndex: 'id',
+            key: 'id'
+          },
+          {
+            title: '商品名称',
+            dataIndex: 'name',
+            key: 'name'
+          },
+          {
+            title: '商品条码',
+            dataIndex: 'barcode',
+            key: 'barcode'
+          },
+          {
+            title: '单价',
+            dataIndex: 'price',
+            key: 'price',
+            align: 'right'
+          },
+          {
+            title: '数量(件)',
+            dataIndex: 'num',
+            key: 'num',
+            align: 'right'
+          },
+          {
+            title: '金额',
+            dataIndex: 'amount',
+            key: 'amount',
+            align: 'right'
+          }
+        ],
+        // 加载数据方法 必须为 Promise 对象
+        loadGoodsData: parameter => {
+          return new Promise((resolve => {
+            resolve({
+              data: [
+                {
+                  id: '1234561',
+                  name: '矿泉水 550ml',
+                  barcode: '12421432143214321',
+                  price: '2.00',
+                  num: '1',
+                  amount: '2.00'
+                },
+                {
+                  id: '1234562',
+                  name: '凉茶 300ml',
+                  barcode: '12421432143214322',
+                  price: '3.00',
+                  num: '2',
+                  amount: '6.00'
+                },
+                {
+                  id: '1234563',
+                  name: '好吃的薯片',
+                  barcode: '12421432143214323',
+                  price: '7.00',
+                  num: '4',
+                  amount: '28.00'
+                },
+                {
+                  id: '1234564',
+                  name: '特别好吃的蛋卷',
+                  barcode: '12421432143214324',
+                  price: '8.50',
+                  num: '3',
+                  amount: '25.50'
+                }
+              ],
+              pageSize: 10,
+              pageNo: 1,
+              totalPage: 1,
+              totalCount: 10
+            })
+          })).then(res => {
+            return res
+          })
+        },
+
+        scheduleColumns: [
+          {
+            title: '时间',
+            dataIndex: 'time',
+            key: 'time'
+          },
+          {
+            title: '当前进度',
+            dataIndex: 'rate',
+            key: 'rate'
+          },
+          {
+            title: '状态',
+            dataIndex: 'status',
+            key: 'status',
+            scopedSlots: { customRender: 'status' },
+          },
+          {
+            title: '操作员ID',
+            dataIndex: 'operator',
+            key: 'operator'
+          },
+          {
+            title: '耗时',
+            dataIndex: 'cost',
+            key: 'cost'
+          }
+        ],
+        loadScheduleData: parameter => {
+          return new Promise((resolve => {
+            resolve({
+              data: [
+                {
+                  key: '1',
+                  time: '2017-10-01 14:10',
+                  rate: '联系客户',
+                  status: 'processing',
+                  operator: '取货员 ID1234',
+                  cost: '5mins'
+                },
+                {
+                  key: '2',
+                  time: '2017-10-01 14:05',
+                  rate: '取货员出发',
+                  status: 'success',
+                  operator: '取货员 ID1234',
+                  cost: '1h'
+                },
+                {
+                  key: '3',
+                  time: '2017-10-01 13:05',
+                  rate: '取货员接单',
+                  status: 'success',
+                  operator: '取货员 ID1234',
+                  cost: '5mins'
+                },
+                {
+                  key: '4',
+                  time: '2017-10-01 13:00',
+                  rate: '申请审批通过',
+                  status: 'success',
+                  operator: '系统',
+                  cost: '1h'
+                },
+                {
+                  key: '5',
+                  time: '2017-10-01 12:00',
+                  rate: '发起退货申请',
+                  status: 'success',
+                  operator: '用户',
+                  cost: '5mins'
+                }
+              ],
+              pageSize: 10,
+              pageNo: 1,
+              totalPage: 1,
+              totalCount: 10
+            })
+          })).then(res => {
+            return res
+          })
+        },
+      }
+    },
+    filters: {
+      statusFilter(status) {
+        const statusMap = {
+          'processing': '进行中',
+          'success': '完成',
+          'failed': '失败'
+        }
+        return statusMap[status]
+      }
+    }
+
+  }
+</script>
+
+<style lang="scss" scoped>
+  .title {
+    color: rgba(0,0,0,.85);
+    font-size: 16px;
+    font-weight: 500;
+    margin-bottom: 16px;
+  }
 </style>