Browse Source

refactor: update package, clear code

Sendya 4 years ago
parent
commit
54e9e63423
75 changed files with 1428 additions and 3286 deletions
  1. 2 0
      package.json
  2. 0 2
      src/App.vue
  3. 0 102
      src/components/CountDown/CountDown.vue
  4. 0 3
      src/components/CountDown/index.js
  5. 0 34
      src/components/CountDown/index.md
  6. 0 153
      src/components/DescriptionList/DescriptionList.vue
  7. 0 2
      src/components/DescriptionList/index.js
  8. 0 130
      src/components/Exception/ExceptionPage.vue
  9. 0 2
      src/components/Exception/index.js
  10. 0 19
      src/components/Exception/type.js
  11. 18 1
      src/components/FooterToolbar/FooterToolBar.vue
  12. 0 59
      src/components/GlobalFooter/GlobalFooter.vue
  13. 0 2
      src/components/GlobalFooter/index.js
  14. 22 0
      src/components/GlobalFooter/index.vue
  15. 77 0
      src/components/GlobalHeader/AvatarDropdown.vue
  16. 0 125
      src/components/GlobalHeader/GlobalHeader.vue
  17. 47 0
      src/components/GlobalHeader/RightContent.vue
  18. 0 2
      src/components/GlobalHeader/index.js
  19. 0 61
      src/components/Menu/SideMenu.vue
  20. 0 2
      src/components/Menu/index.js
  21. 0 177
      src/components/Menu/menu.js
  22. 0 156
      src/components/Menu/menu.render.js
  23. 0 202
      src/components/PageHeader/PageHeader.vue
  24. 0 2
      src/components/PageHeader/index.js
  25. 50 0
      src/components/SelectLang/index.jsx
  26. 23 0
      src/components/SelectLang/index.less
  27. 0 515
      src/components/global.less
  28. 0 8
      src/components/index.js
  29. 0 45
      src/components/tools/Breadcrumb.vue
  30. 0 5
      src/components/tools/DetailList.vue
  31. 0 67
      src/components/tools/HeadInfo.vue
  32. 0 46
      src/components/tools/LangSelect.vue
  33. 0 31
      src/components/tools/Logo.vue
  34. 0 82
      src/components/tools/UserMenu.vue
  35. 0 0
      src/components/tools/index.js
  36. 10 4
      src/config/defaultSettings.js
  37. 15 28
      src/config/router.config.js
  38. 18 22
      src/core/bootstrap.js
  39. 6 0
      src/core/lazy_use.js
  40. 94 0
      src/global.less
  41. 31 0
      src/layouts/BasicLayout.less
  42. 72 130
      src/layouts/BasicLayout.vue
  43. 4 173
      src/layouts/PageView.vue
  44. 58 0
      src/locales/index.js
  45. 17 0
      src/locales/lang/en-US.js
  46. 4 4
      src/main.js
  47. 32 0
      src/store/app-mixin.js
  48. 11 0
      src/store/device-mixin.js
  49. 3 3
      src/store/getters.js
  50. 16 0
      src/store/i18n-mixin.js
  51. 64 86
      src/store/modules/app.js
  52. 13 10
      src/store/mutation-types.js
  53. 0 33
      src/utils/device.js
  54. 0 76
      src/utils/mixin.js
  55. 0 215
      src/views/Home.vue
  56. 1 1
      src/views/dashboard/Analysis.vue
  57. 107 0
      src/views/dashboard/Workplace.less
  58. 55 28
      src/views/dashboard/Workplace.vue
  59. 8 10
      src/views/exception/403.vue
  60. 8 10
      src/views/exception/404.vue
  61. 9 11
      src/views/exception/500.vue
  62. 0 138
      src/views/form/BasicForm.vue
  63. 5 6
      src/views/form/advancedForm/AdvancedForm.vue
  64. 133 0
      src/views/form/basicForm/index.vue
  65. 2 2
      src/views/form/stepForm/Step3.vue
  66. 18 14
      src/views/form/stepForm/StepForm.vue
  67. 19 18
      src/views/list/CardList.vue
  68. 2 2
      src/views/list/StandardList.vue
  69. 95 93
      src/views/list/TableList.vue
  70. 41 59
      src/views/list/search/SearchLayout.vue
  71. 84 70
      src/views/profile/advanced/Advanced.vue
  72. 2 2
      src/views/profile/basic/index.vue
  73. 1 1
      src/views/result/Error.vue
  74. 1 1
      src/views/result/Success.vue
  75. 130 1
      yarn.lock

+ 2 - 0
package.json

@@ -12,6 +12,7 @@
     "postinstall": "opencollective-postinstall"
   },
   "dependencies": {
+    "@ant-design-vue/pro-layout": "^0.2.3",
     "@antv/data-set": "^0.10.2",
     "ant-design-vue": "1.5.3",
     "axios": "^0.19.0",
@@ -29,6 +30,7 @@
     "vue": "^2.6.10",
     "vue-clipboard2": "^0.2.1",
     "vue-cropper": "0.4.9",
+    "vue-i18n": "^8.17.4",
     "vue-quill-editor": "^3.0.6",
     "vue-router": "^3.1.2",
     "vue-svg-component-runtime": "^1.0.1",

+ 0 - 2
src/App.vue

@@ -8,10 +8,8 @@
 
 <script>
 import zhCN from 'ant-design-vue/lib/locale-provider/zh_CN'
-import { AppDeviceEnquire } from '@/utils/mixin'
 
 export default {
-  mixins: [AppDeviceEnquire],
   data () {
     return {
       locale: zhCN

+ 0 - 102
src/components/CountDown/CountDown.vue

@@ -1,102 +0,0 @@
-<template>
-  <span>
-    {{ lastTime | format }}
-  </span>
-</template>
-
-<script>
-
-function fixedZero (val) {
-  return val * 1 < 10 ? `0${val}` : val
-}
-
-export default {
-  name: 'CountDown',
-  props: {
-    format: {
-      type: Function,
-      default: undefined
-    },
-    target: {
-      type: [Date, Number],
-      required: true
-    },
-    onEnd: {
-      type: Function,
-      default: () => ({})
-    }
-  },
-  data () {
-    return {
-      dateTime: '0',
-      originTargetTime: 0,
-      lastTime: 0,
-      timer: 0,
-      interval: 1000
-    }
-  },
-  filters: {
-    format (time) {
-      const hours = 60 * 60 * 1000
-      const minutes = 60 * 1000
-
-      const h = Math.floor(time / hours)
-      const m = Math.floor((time - h * hours) / minutes)
-      const s = Math.floor((time - h * hours - m * minutes) / 1000)
-      return `${fixedZero(h)}:${fixedZero(m)}:${fixedZero(s)}`
-    }
-  },
-  created () {
-    this.initTime()
-    this.tick()
-  },
-  methods: {
-    initTime () {
-      let lastTime = 0
-      let targetTime = 0
-      this.originTargetTime = this.target
-      try {
-        if (Object.prototype.toString.call(this.target) === '[object Date]') {
-          targetTime = this.target
-        } else {
-          targetTime = new Date(this.target).getTime()
-        }
-      } catch (e) {
-        throw new Error('invalid target prop')
-      }
-
-      lastTime = targetTime - new Date().getTime()
-
-      this.lastTime = lastTime < 0 ? 0 : lastTime
-    },
-    tick () {
-      const { onEnd } = this
-
-      this.timer = setTimeout(() => {
-        if (this.lastTime < this.interval) {
-          clearTimeout(this.timer)
-          this.lastTime = 0
-          if (typeof onEnd === 'function') {
-            onEnd()
-          }
-        } else {
-          this.lastTime -= this.interval
-          this.tick()
-        }
-      }, this.interval)
-    }
-  },
-  beforeUpdate () {
-    if (this.originTargetTime !== this.target) {
-      this.initTime()
-    }
-  },
-  beforeDestroy () {
-    clearTimeout(this.timer)
-  }
-}
-</script>
-
-<style scoped>
-
-</style>

+ 0 - 3
src/components/CountDown/index.js

@@ -1,3 +0,0 @@
-import CountDown from './CountDown'
-
-export default CountDown

+ 0 - 34
src/components/CountDown/index.md

@@ -1,34 +0,0 @@
-# CountDown 倒计时
-
-倒计时组件。
-
-
-
-引用方式:
-
-```javascript
-import CountDown from '@/components/CountDown/CountDown'
-
-export default {
-    components: {
-        CountDown
-    }
-}
-```
-
-
-
-## 代码演示  [demo](https://pro.loacg.com/test/home)
-
-```html
-<count-down :target="new Date().getTime() + 3000000" :on-end="onEndHandle" />
-```
-
-
-
-## API
-
-| 参数      | 说明                                      | 类型         | 默认值 |
-|----------|------------------------------------------|-------------|-------|
-| target | 目标时间 | Date | - |
-| onEnd |  倒计时结束回调 | funtion | -|

+ 0 - 153
src/components/DescriptionList/DescriptionList.vue

@@ -1,153 +0,0 @@
-<template>
-  <div :class="['description-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="less" scoped>
-
-  .description-list {
-
-    .title {
-      color: rgba(0,0,0,.85);
-      font-size: 14px;
-      font-weight: 500;
-      margin-bottom: 16px;
-    }
-
-    /deep/ .term {
-      color: rgba(0,0,0,.85);
-      display: table-cell;
-      line-height: 20px;
-      margin-right: 8px;
-      padding-bottom: 16px;
-      white-space: nowrap;
-
-      &:not(:empty):after {
-        content: ":";
-        margin: 0 8px 0 2px;
-        position: relative;
-        top: -.5px;
-      }
-    }
-
-    /deep/ .content {
-      color: rgba(0,0,0,.65);
-      display: table-cell;
-      min-height: 22px;
-      line-height: 22px;
-      padding-bottom: 16px;
-      width: 100%;
-      &:empty {
-        content: ' ';
-        height: 38px;
-        padding-bottom: 16px;
-      }
-    }
-
-    &.small {
-
-      .title {
-        font-size: 14px;
-        color: rgba(0, 0, 0, .65);
-        font-weight: normal;
-        margin-bottom: 12px;
-      }
-      /deep/ .term, .content {
-        padding-bottom: 8px;
-      }
-    }
-
-    &.large {
-      /deep/ .term, .content {
-        padding-bottom: 16px;
-      }
-
-      .title {
-        font-size: 16px;
-      }
-    }
-
-    &.vertical {
-      .term {
-        padding-bottom: 8px;
-      }
-      /deep/ .term, .content {
-        display: block;
-      }
-    }
-  }
-</style>

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

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

+ 0 - 130
src/components/Exception/ExceptionPage.vue

@@ -1,130 +0,0 @@
-<template>
-  <div class="exception">
-    <div class="imgBlock">
-      <div class="imgEle" :style="{backgroundImage: `url(${config[type].img})`}">
-      </div>
-    </div>
-    <div class="content">
-      <h1>{{ config[type].title }}</h1>
-      <div class="desc">{{ config[type].desc }}</div>
-      <div class="actions">
-        <a-button type="primary" @click="handleToHome">返回首页</a-button>
-      </div>
-    </div>
-  </div>
-</template>
-
-<script>
-import types from './type'
-
-export default {
-  name: 'Exception',
-  props: {
-    type: {
-      type: String,
-      default: '404'
-    }
-  },
-  data () {
-    return {
-      config: types
-    }
-  },
-  methods: {
-    handleToHome () {
-      this.$router.push({ name: 'dashboard' })
-    }
-  }
-}
-</script>
-<style lang="less">
-@import "~ant-design-vue/lib/style/index";
-
-.exception {
-  display: flex;
-  align-items: center;
-  height: 80%;
-  min-height: 500px;
-
-  .imgBlock {
-    flex: 0 0 62.5%;
-    width: 62.5%;
-    padding-right: 152px;
-    zoom: 1;
-    &::before,
-    &::after {
-      content: ' ';
-      display: table;
-    }
-    &::after {
-      clear: both;
-      height: 0;
-      font-size: 0;
-      visibility: hidden;
-    }
-  }
-
-  .imgEle {
-    float: right;
-    width: 100%;
-    max-width: 430px;
-    height: 360px;
-    background-repeat: no-repeat;
-    background-position: 50% 50%;
-    background-size: contain;
-  }
-
-  .content {
-    flex: auto;
-
-    h1 {
-      margin-bottom: 24px;
-      color: #434e59;
-      font-weight: 600;
-      font-size: 72px;
-      line-height: 72px;
-    }
-
-    .desc {
-      margin-bottom: 16px;
-      color: @text-color-secondary;
-      font-size: 20px;
-      line-height: 28px;
-    }
-
-    .actions {
-      button:not(:last-child) {
-        margin-right: 8px;
-      }
-    }
-  }
-}
-
-@media screen and (max-width: @screen-xl) {
-  .exception {
-    .imgBlock {
-      padding-right: 88px;
-    }
-  }
-}
-
-@media screen and (max-width: @screen-sm) {
-  .exception {
-    display: block;
-    text-align: center;
-    .imgBlock {
-      margin: 0 auto 24px;
-      padding-right: 0;
-    }
-  }
-}
-
-@media screen and (max-width: @screen-xs) {
-  .exception {
-    .imgBlock {
-      margin-bottom: -24px;
-      overflow: hidden;
-    }
-  }
-}
-</style>

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

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

+ 0 - 19
src/components/Exception/type.js

@@ -1,19 +0,0 @@
-const types = {
-  403: {
-    img: 'https://gw.alipayobjects.com/zos/rmsportal/wZcnGqRDyhPOEYFcZDnb.svg',
-    title: '403',
-    desc: '抱歉,你无权访问该页面'
-  },
-  404: {
-    img: 'https://gw.alipayobjects.com/zos/rmsportal/KpnpchXsobRgLElEozzI.svg',
-    title: '404',
-    desc: '抱歉,你访问的页面不存在或仍在开发中'
-  },
-  500: {
-    img: 'https://gw.alipayobjects.com/zos/rmsportal/RVRUAYdCGeYNBWoKiIwB.svg',
-    title: '500',
-    desc: '抱歉,服务器出错了'
-  }
-}
-
-export default types

+ 18 - 1
src/components/FooterToolbar/FooterToolBar.vue

@@ -1,5 +1,5 @@
 <template>
-  <div :class="prefixCls">
+  <div :class="prefixCls" :style="{ width: barWidth, transition: '0.3s all' }">
     <div style="float: left">
       <slot name="extra">{{ extra }}</slot>
     </div>
@@ -17,10 +17,27 @@ export default {
       type: String,
       default: 'ant-pro-footer-toolbar'
     },
+    collapsed: {
+      type: Boolean,
+      default: false
+    },
+    isMobile: {
+      type: Boolean,
+      default: false
+    },
+    siderWidth: {
+      type: Number,
+      default: undefined
+    },
     extra: {
       type: [String, Object],
       default: ''
     }
+  },
+  computed: {
+    barWidth () {
+      return this.isMobile ? undefined : `calc(100% - ${this.collapsed ? 80 : this.siderWidth || 256}px)`
+    }
   }
 }
 </script>

+ 0 - 59
src/components/GlobalFooter/GlobalFooter.vue

@@ -1,59 +0,0 @@
-<template>
-  <div class="footer">
-    <div class="links">
-      <a
-        href="https://pro.loacg.com/"
-        target="_blank"
-      >Pro 首页</a>
-      <a
-        href="https://github.com/sendya/ant-design-pro-vue"
-        target="_blank"
-      >
-        <a-icon type="github" />
-      </a>
-      <a href="https://ant.design/">Ant Design</a>
-      <a href="https://vue.ant.design/">Vue Antd</a>
-    </div>
-    <div class="copyright">
-      Copyright
-      <a-icon type="copyright" /> 2018 <span>vueComponent</span>
-    </div>
-  </div>
-</template>
-
-<script>
-export default {
-  name: 'GlobalFooter',
-  data () {
-    return {}
-  }
-}
-</script>
-
-<style lang="less" scoped>
-.footer {
-  padding: 0 16px;
-  margin: 48px 0 24px;
-  text-align: center;
-
-  .links {
-    margin-bottom: 8px;
-
-    a {
-      color: rgba(0, 0, 0, 0.45);
-
-      &:hover {
-        color: rgba(0, 0, 0, 0.65);
-      }
-
-      &:not(:last-child) {
-        margin-right: 40px;
-      }
-    }
-  }
-  .copyright {
-    color: rgba(0, 0, 0, 0.45);
-    font-size: 14px;
-  }
-}
-</style>

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

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

+ 22 - 0
src/components/GlobalFooter/index.vue

@@ -0,0 +1,22 @@
+<template>
+  <global-footer class="footer custom-render">
+    <template v-slot:links>
+      <a href="https://www.github.com/vueComponent/ant-design-vue-pro" target="_blank">Github</a>
+      <a href="https://www.github.com/sendya/" target="_blank">@Sendya</a>
+    </template>
+    <template v-slot:copyright>
+      <a href="https://github.com/vueComponent" target="_blank">vueComponent</a>
+    </template>
+  </global-footer>
+</template>
+
+<script>
+import { GlobalFooter } from '@ant-design-vue/pro-layout'
+
+export default {
+  name: 'ProGlobalFooter',
+  components: {
+    GlobalFooter
+  }
+}
+</script>

+ 77 - 0
src/components/GlobalHeader/AvatarDropdown.vue

@@ -0,0 +1,77 @@
+<template>
+  <a-dropdown v-if="currentUser && currentUser.name" placement="bottomRight">
+    <span class="ant-pro-account-avatar">
+      <a-avatar size="small" src="https://gw.alipayobjects.com/zos/antfincdn/XAosXuNZyF/BiazfanxmamNRoxxVxka.png" class="antd-pro-global-header-index-avatar" />
+      <span>{{ currentUser.name }}</span>
+    </span>
+    <template v-slot:overlay>
+      <a-menu class="ant-pro-drop-down menu" :selected-keys="[]">
+        <a-menu-item v-if="menu" key="center" @click="handleToCenter">
+          <a-icon type="user" />
+          个人中心
+        </a-menu-item>
+        <a-menu-item v-if="menu" key="settings" @click="handleToSettings">
+          <a-icon type="setting" />
+          个人设置
+        </a-menu-item>
+        <a-menu-divider v-if="menu" />
+        <a-menu-item key="logout" @click="handleLogout">
+          <a-icon type="logout" />
+          退出登录
+        </a-menu-item>
+      </a-menu>
+    </template>
+  </a-dropdown>
+  <span v-else>
+    <a-spin size="small" :style="{ marginLeft: 8, marginRight: 8 }" />
+  </span>
+</template>
+
+<script>
+import { Modal } from 'ant-design-vue'
+
+export default {
+  name: 'AvatarDropdown',
+  props: {
+    currentUser: {
+      type: Object,
+      default: () => null
+    },
+    menu: {
+      type: Boolean,
+      default: true
+    }
+  },
+  methods: {
+    handleToCenter () {
+      this.$router.push({ path: '/account/center' })
+    },
+    handleToSettings () {
+      this.$router.push({ path: '/account/settings' })
+    },
+    handleLogout (e) {
+      Modal.confirm({
+        title: this.$t('layouts.usermenu.dialog.title'),
+        content: this.$t('layouts.usermenu.dialog.content'),
+        onOk: () => {
+          return new Promise((resolve, reject) => {
+            setTimeout(Math.random() > 0.5 ? resolve : reject, 1500)
+          }).catch(() => console.log('Oops errors!'))
+        },
+        onCancel () {}
+      })
+    }
+  }
+}
+</script>
+
+<style lang="less" scoped>
+.menu {
+  /deep/ .action {
+    margin-right: 8px;
+  }
+  /deep/ .ant-dropdown-menu-item {
+    min-width: 160px;
+  }
+}
+</style>

+ 0 - 125
src/components/GlobalHeader/GlobalHeader.vue

@@ -1,125 +0,0 @@
-<template>
-  <transition name="showHeader">
-    <div v-if="visible" class="header-animat">
-      <a-layout-header
-        v-if="visible"
-        :class="[fixedHeader && 'ant-header-fixedHeader', sidebarOpened ? 'ant-header-side-opened' : 'ant-header-side-closed', ]"
-        :style="{ padding: '0' }">
-        <div v-if="mode === 'sidemenu'" class="header">
-          <a-icon v-if="device==='mobile'" class="trigger" :type="collapsed ? 'menu-fold' : 'menu-unfold'" @click="toggle"/>
-          <a-icon v-else class="trigger" :type="collapsed ? 'menu-unfold' : 'menu-fold'" @click="toggle"/>
-          <user-menu></user-menu>
-        </div>
-        <div v-else :class="['top-nav-header-index', theme]">
-          <div class="header-index-wide">
-            <div class="header-index-left">
-              <logo class="top-nav-header" :show-title="device !== 'mobile'"/>
-              <s-menu v-if="device !== 'mobile'" mode="horizontal" :menu="menus" :theme="theme" />
-              <a-icon v-else class="trigger" :type="collapsed ? 'menu-fold' : 'menu-unfold'" @click="toggle" />
-            </div>
-            <user-menu class="header-index-right"></user-menu>
-          </div>
-        </div>
-      </a-layout-header>
-    </div>
-  </transition>
-</template>
-
-<script>
-import UserMenu from '../tools/UserMenu'
-import SMenu from '../Menu/'
-import Logo from '../tools/Logo'
-import { mixin } from '@/utils/mixin'
-
-export default {
-  name: 'GlobalHeader',
-  components: {
-    UserMenu,
-    SMenu,
-    Logo
-  },
-  mixins: [mixin],
-  props: {
-    mode: {
-      type: String,
-      // sidemenu, topmenu
-      default: 'sidemenu'
-    },
-    menus: {
-      type: Array,
-      required: true
-    },
-    theme: {
-      type: String,
-      required: false,
-      default: 'dark'
-    },
-    collapsed: {
-      type: Boolean,
-      required: false,
-      default: false
-    },
-    device: {
-      type: String,
-      required: false,
-      default: 'desktop'
-    }
-  },
-  data () {
-    return {
-      visible: true,
-      oldScrollTop: 0
-    }
-  },
-  mounted () {
-    document.addEventListener('scroll', this.handleScroll, { passive: true })
-  },
-  methods: {
-    handleScroll () {
-      if (!this.autoHideHeader) {
-        return
-      }
-
-      const scrollTop = document.body.scrollTop + document.documentElement.scrollTop
-      if (!this.ticking) {
-        this.ticking = true
-        requestAnimationFrame(() => {
-          if (this.oldScrollTop > scrollTop) {
-            this.visible = true
-          } else if (scrollTop > 300 && this.visible) {
-            this.visible = false
-          } else if (scrollTop < 300 && !this.visible) {
-            this.visible = true
-          }
-          this.oldScrollTop = scrollTop
-          this.ticking = false
-        })
-      }
-    },
-    toggle () {
-      this.$emit('toggle')
-    }
-  },
-  beforeDestroy () {
-    document.body.removeEventListener('scroll', this.handleScroll, true)
-  }
-}
-</script>
-
-<style lang="less">
-@import '../index.less';
-
-.header-animat{
-  position: relative;
-  z-index: @ant-global-header-zindex;
-}
-.showHeader-enter-active {
-  transition: all 0.25s ease;
-}
-.showHeader-leave-active {
-  transition: all 0.5s ease;
-}
-.showHeader-enter, .showHeader-leave-to {
-  opacity: 0;
-}
-</style>

+ 47 - 0
src/components/GlobalHeader/RightContent.vue

@@ -0,0 +1,47 @@
+<template>
+  <div :class="wrpCls">
+    <avatar-dropdown :menu="showMenu" :current-user="currentUser" class="ant-pro-global-header-index-action" />
+    <select-lang class="ant-pro-global-header-index-action" />
+  </div>
+</template>
+
+<script>
+import AvatarDropdown from './AvatarDropdown'
+import SelectLang from '@/components/SelectLang'
+
+export default {
+  name: 'RightContent',
+  components: {
+    AvatarDropdown,
+    SelectLang
+  },
+  props: {
+    theme: {
+      type: String,
+      default: 'light'
+    }
+  },
+  data () {
+    return {
+      showMenu: true,
+      currentUser: {}
+    }
+  },
+  computed: {
+    wrpCls () {
+      return { 'ant-pro-global-header-index-right': true, [`ant-pro-global-header-index-${this.theme}`]: true }
+    }
+  },
+  mounted () {
+    setTimeout(() => {
+      this.currentUser = {
+        name: 'Serati Ma'
+      }
+    }, 1500)
+  }
+}
+</script>
+
+<style scoped>
+
+</style>

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

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

+ 0 - 61
src/components/Menu/SideMenu.vue

@@ -1,61 +0,0 @@
-<template>
-  <a-layout-sider
-    :class="['sider', isDesktop() ? null : 'shadow', theme, fixSiderbar ? 'ant-fixed-sidemenu' : null ]"
-    width="256px"
-    :collapsible="collapsible"
-    v-model="collapsed"
-    :trigger="null">
-    <logo />
-    <s-menu
-      :collapsed="collapsed"
-      :menu="menus"
-      :theme="theme"
-      :mode="mode"
-      @select="onSelect"
-      style="padding: 16px 0px;"></s-menu>
-  </a-layout-sider>
-
-</template>
-
-<script>
-import Logo from '@/components/tools/Logo'
-import SMenu from './index'
-import { mixin, mixinDevice } from '@/utils/mixin'
-
-export default {
-  name: 'SideMenu',
-  components: { Logo, SMenu },
-  mixins: [mixin, mixinDevice],
-  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
-    }
-  },
-  methods: {
-    onSelect (obj) {
-      this.$emit('menuSelect', obj)
-    }
-  }
-}
-</script>

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

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

+ 0 - 177
src/components/Menu/menu.js

@@ -1,177 +0,0 @@
-import Menu from 'ant-design-vue/es/menu'
-import Icon from 'ant-design-vue/es/icon'
-
-export default {
-  name: 'SMenu',
-  props: {
-    menu: {
-      type: Array,
-      required: true
-    },
-    theme: {
-      type: String,
-      required: false,
-      default: 'dark'
-    },
-    mode: {
-      type: String,
-      required: false,
-      default: 'inline'
-    },
-    collapsed: {
-      type: Boolean,
-      required: false,
-      default: false
-    }
-  },
-  data () {
-    return {
-      openKeys: [],
-      selectedKeys: [],
-      cachedOpenKeys: []
-    }
-  },
-  computed: {
-    rootSubmenuKeys: vm => {
-      const keys = []
-      vm.menu.forEach(item => keys.push(item.path))
-      return keys
-    }
-  },
-  mounted () {
-    this.updateMenu()
-  },
-  watch: {
-    collapsed (val) {
-      if (val) {
-        this.cachedOpenKeys = this.openKeys.concat()
-        this.openKeys = []
-      } else {
-        this.openKeys = this.cachedOpenKeys
-      }
-    },
-    $route: function () {
-      this.updateMenu()
-    }
-  },
-  methods: {
-    // select menu item
-    onOpenChange (openKeys) {
-      // 在水平模式下时执行,并且不再执行后续
-      if (this.mode === 'horizontal') {
-        this.openKeys = openKeys
-        return
-      }
-      // 非水平模式时
-      const latestOpenKey = openKeys.find(key => !this.openKeys.includes(key))
-      if (!this.rootSubmenuKeys.includes(latestOpenKey)) {
-        this.openKeys = openKeys
-      } else {
-        this.openKeys = latestOpenKey ? [latestOpenKey] : []
-      }
-    },
-    onSelect ({ item, key, selectedKeys }) {
-      this.selectedKeys = selectedKeys
-      this.$emit('select', { item, key, selectedKeys })
-    },
-    updateMenu () {
-      const routes = this.$route.matched.concat()
-      const { hidden } = this.$route.meta
-      if (routes.length >= 3 && hidden) {
-        routes.pop()
-        this.selectedKeys = [routes[routes.length - 1].path]
-      } else {
-        this.selectedKeys = [routes.pop().path]
-      }
-      const openKeys = []
-      if (this.mode === 'inline') {
-        routes.forEach(item => {
-          openKeys.push(item.path)
-        })
-      }
-
-      this.collapsed ? (this.cachedOpenKeys = openKeys) : (this.openKeys = openKeys)
-    },
-
-    // render
-    renderItem (menu) {
-      if (!menu.hidden) {
-        return menu.children && !menu.hideChildrenInMenu ? this.renderSubMenu(menu) : this.renderMenuItem(menu)
-      }
-      return null
-    },
-    renderMenuItem (menu) {
-      const target = menu.meta.target || null
-      const CustomTag = target && 'a' || 'router-link'
-      const props = { to: { name: menu.name } }
-      const attrs = { href: menu.path, target: menu.meta.target }
-
-      if (menu.children && menu.hideChildrenInMenu) {
-        // 把有子菜单的 并且 父菜单是要隐藏子菜单的
-        // 都给子菜单增加一个 hidden 属性
-        // 用来给刷新页面时, selectedKeys 做控制用
-        menu.children.forEach(item => {
-          item.meta = Object.assign(item.meta, { hidden: true })
-        })
-      }
-
-      return (
-        <Menu.Item {...{ key: menu.path }}>
-          <CustomTag {...{ props, attrs }}>
-            {this.renderIcon(menu.meta.icon)}
-            <span>{menu.meta.title}</span>
-          </CustomTag>
-        </Menu.Item>
-      )
-    },
-    renderSubMenu (menu) {
-      const itemArr = []
-      if (!menu.hideChildrenInMenu) {
-        menu.children.forEach(item => itemArr.push(this.renderItem(item)))
-      }
-      return (
-        <Menu.SubMenu {...{ key: menu.path }}>
-          <span slot="title">
-            {this.renderIcon(menu.meta.icon)}
-            <span>{menu.meta.title}</span>
-          </span>
-          {itemArr}
-        </Menu.SubMenu>
-      )
-    },
-    renderIcon (icon) {
-      if (icon === 'none' || icon === undefined) {
-        return null
-      }
-      const props = {}
-      typeof (icon) === 'object' ? props.component = icon : props.type = icon
-      return (
-        <Icon {... { props } }/>
-      )
-    }
-  },
-
-  render () {
-    const dynamicProps = {
-      props: {
-        mode: this.mode,
-        theme: this.theme,
-        openKeys: this.openKeys,
-        selectedKeys: this.selectedKeys
-      },
-      on: {
-        openChange: this.onOpenChange,
-        select: this.onSelect
-      }
-    }
-
-    const menuTree = this.menu.map(item => {
-      if (item.hidden) {
-        return null
-      }
-      return this.renderItem(item)
-    })
-
-    return (<Menu {...dynamicProps}>{menuTree}</Menu>)
-  }
-}

+ 0 - 156
src/components/Menu/menu.render.js

@@ -1,156 +0,0 @@
-import Menu from 'ant-design-vue/es/menu'
-import Icon from 'ant-design-vue/es/icon'
-
-const { Item, SubMenu } = Menu
-
-export default {
-  name: 'SMenu',
-  props: {
-    menu: {
-      type: Array,
-      required: true
-    },
-    theme: {
-      type: String,
-      required: false,
-      default: 'dark'
-    },
-    mode: {
-      type: String,
-      required: false,
-      default: 'inline'
-    },
-    collapsed: {
-      type: Boolean,
-      required: false,
-      default: false
-    }
-  },
-  data () {
-    return {
-      openKeys: [],
-      selectedKeys: [],
-      cachedOpenKeys: []
-    }
-  },
-  computed: {
-    rootSubmenuKeys: vm => {
-      const keys = []
-      vm.menu.forEach(item => keys.push(item.path))
-      return keys
-    }
-  },
-  created () {
-    this.updateMenu()
-  },
-  watch: {
-    collapsed (val) {
-      if (val) {
-        this.cachedOpenKeys = this.openKeys.concat()
-        this.openKeys = []
-      } else {
-        this.openKeys = this.cachedOpenKeys
-      }
-    },
-    $route: function () {
-      this.updateMenu()
-    }
-  },
-  methods: {
-    renderIcon: function (h, icon) {
-      if (icon === 'none' || icon === undefined) {
-        return null
-      }
-      const props = {}
-      typeof (icon) === 'object' ? props.component = icon : props.type = icon
-      return h(Icon, { props: { ...props } })
-    },
-    renderMenuItem: function (h, menu, pIndex, index) {
-      const target = menu.meta.target || null
-      return h(Item, { key: menu.path ? menu.path : 'item_' + pIndex + '_' + index }, [
-        h('router-link', { attrs: { to: { name: menu.name }, target: target } }, [
-          this.renderIcon(h, menu.meta.icon),
-          h('span', [menu.meta.title])
-        ])
-      ])
-    },
-    renderSubMenu: function (h, menu, pIndex, index) {
-      const this2_ = this
-      const subItem = [h('span', { slot: 'title' }, [this.renderIcon(h, menu.meta.icon), h('span', [menu.meta.title])])]
-      const itemArr = []
-      const pIndex_ = pIndex + '_' + index
-      console.log('menu', menu)
-      if (!menu.hideChildrenInMenu) {
-        menu.children.forEach(function (item, i) {
-          itemArr.push(this2_.renderItem(h, item, pIndex_, i))
-        })
-      }
-      return h(SubMenu, { key: menu.path ? menu.path : 'submenu_' + pIndex + '_' + index }, subItem.concat(itemArr))
-    },
-    renderItem: function (h, menu, pIndex, index) {
-      if (!menu.hidden) {
-        return menu.children && !menu.hideChildrenInMenu
-          ? this.renderSubMenu(h, menu, pIndex, index)
-          : this.renderMenuItem(h, menu, pIndex, index)
-      }
-    },
-    renderMenu: function (h, menuTree) {
-      const this2_ = this
-      const menuArr = []
-      menuTree.forEach(function (menu, i) {
-        if (!menu.hidden) {
-          menuArr.push(this2_.renderItem(h, menu, '0', i))
-        }
-      })
-      return menuArr
-    },
-    onOpenChange (openKeys) {
-      const latestOpenKey = openKeys.find(key => !this.openKeys.includes(key))
-      if (!this.rootSubmenuKeys.includes(latestOpenKey)) {
-        this.openKeys = openKeys
-      } else {
-        this.openKeys = latestOpenKey ? [latestOpenKey] : []
-      }
-    },
-    updateMenu () {
-      const routes = this.$route.matched.concat()
-
-      if (routes.length >= 4 && this.$route.meta.hidden) {
-        routes.pop()
-        this.selectedKeys = [routes[2].path]
-      } else {
-        this.selectedKeys = [routes.pop().path]
-      }
-
-      const openKeys = []
-      if (this.mode === 'inline') {
-        routes.forEach(item => {
-          openKeys.push(item.path)
-        })
-      }
-
-      this.collapsed ? (this.cachedOpenKeys = openKeys) : (this.openKeys = openKeys)
-    }
-  },
-  render (h) {
-    return h(
-      Menu,
-      {
-        props: {
-          theme: this.$props.theme,
-          mode: this.$props.mode,
-          openKeys: this.openKeys,
-          selectedKeys: this.selectedKeys
-        },
-        on: {
-          openChange: this.onOpenChange,
-          select: obj => {
-            this.selectedKeys = obj.selectedKeys
-            this.$emit('select', obj)
-          }
-        }
-      },
-      this.renderMenu(h, this.menu)
-    )
-  }
-}

+ 0 - 202
src/components/PageHeader/PageHeader.vue

@@ -1,202 +0,0 @@
-<template>
-  <div class="page-header">
-    <div class="page-header-index-wide">
-      <s-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>
-            <slot name="pageMenu"></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, Boolean],
-      default: true,
-      required: false
-    },
-    logo: {
-      type: String,
-      default: '',
-      required: false
-    },
-    avatar: {
-      type: String,
-      default: '',
-      required: false
-    }
-  },
-  data () {
-    return {}
-  }
-}
-</script>
-
-<style lang="less" 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, 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, 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;
-      }
-
-      .action {
-        margin-left: unset;
-        min-width: 266px;
-        flex: 0 1 auto;
-        text-align: left;
-        margin-bottom: 12px;
-
-        &:empty {
-          display: none;
-        }
-      }
-    }
-  }
-}
-</style>

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

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

+ 50 - 0
src/components/SelectLang/index.jsx

@@ -0,0 +1,50 @@
+import { Icon, Menu, Dropdown } from 'ant-design-vue'
+import { i18nRender } from '@/locales'
+import './index.less'
+import i18nMixin from '@/store/i18n-mixin'
+
+const locales = ['zh-CN', 'zh-TW', 'en-US', 'pt-BR']
+const languageLabels = {
+  'zh-CN': '简体中文',
+  'zh-TW': '繁体中文',
+  'en-US': 'English',
+  'pt-BR': 'Português'
+}
+// eslint-disable-next-line
+const languageIcons = {
+  'zh-CN': '🇨🇳',
+  'zh-TW': '🇭🇰',
+  'en-US': '🇺🇸',
+  'pt-BR': '🇧🇷'
+}
+
+const SelectLang = {
+  name: 'SelectLang',
+  mixins: [i18nMixin],
+  render () {
+    const changeLang = ({ key }) => {
+      this.setLang(key)
+    }
+    const langMenu = (
+      <Menu class={['menu', 'drop-down']} selectedKeys={[this.currentLang]} onClick={changeLang}>
+        {locales.map(locale => (
+          <Menu.Item key={locale}>
+            <span role="img" aria-label={languageLabels[locale]}>
+              {languageIcons[locale]}
+            </span>{' '}
+            {languageLabels[locale]}
+          </Menu.Item>
+        ))}
+      </Menu>
+    )
+    return (
+      <Dropdown overlay={langMenu} placement="bottomRight">
+        <span class={'.ant-pro-drop-down-lang'}>
+          <Icon type="global" title={i18nRender('navBar.lang')} />
+        </span>
+      </Dropdown>
+    )
+  }
+}
+
+export default SelectLang

+ 23 - 0
src/components/SelectLang/index.less

@@ -0,0 +1,23 @@
+@import "~ant-design-vue/es/style/themes/default";
+
+@header-menu-prefix-cls: ~'@{ant-prefix}-pro-header-menu';
+
+.ant-pro-drop-down-lang {
+  line-height: @layout-header-height;
+  vertical-align: top;
+  cursor: pointer;
+  > i {
+    font-size: 16px !important;
+    transform: none !important;
+    svg {
+      position: relative;
+      top: -1px;
+    }
+  }
+
+  &.select-lang {
+    .ant-dropdown-menu-item {
+      min-width: 120px;
+    }
+  }
+}

+ 0 - 515
src/components/global.less

@@ -1,515 +0,0 @@
-@import './index.less';
-
-body {
-
-
-}
-
-#app {
-  height: 100%;
-
-  &.colorWeak {
-    filter: invert(80%);
-  }
-  &.userLayout {
-    overflow: auto;
-  }
-}
-
-.layout.ant-layout {
-  height: auto;
-  overflow-x: hidden;
-
-  &.mobile,
-  &.tablet {
-    .ant-layout-content {
-      .content {
-        margin: 24px 0 0;
-      }
-    }
-
-    /**
-     * ant-table-wrapper
-     * 覆盖的表格手机模式样式,如果想修改在手机上表格最低宽度,可以在这里改动
-     */
-    .ant-table-wrapper {
-      .ant-table-content {
-        overflow-y: auto;
-      }
-      .ant-table-body {
-        min-width: 800px;
-      }
-    }
-    .topmenu {
-      /* 必须为 topmenu  才能启用流式布局 */
-      &.content-width-Fluid {
-        .header-index-wide {
-          margin-left: 0;
-        }
-      }
-    }
-  }
-
-  &.mobile {
-    .sidemenu {
-      .ant-header-fixedHeader {
-        &.ant-header-side-opened,
-        &.ant-header-side-closed {
-          width: 100%;
-        }
-      }
-    }
-  }
-
-  &.ant-layout-has-sider {
-    flex-direction: row;
-  }
-
-  .trigger {
-    font-size: 20px;
-    line-height: 64px;
-    padding: 0 24px;
-    cursor: pointer;
-    transition: color 0.3s;
-    &:hover {
-      background: rgba(0, 0, 0, 0.025);
-    }
-  }
-
-  .topmenu {
-    .ant-header-fixedHeader {
-      position: fixed;
-      top: 0;
-      right: 0;
-      z-index: 9;
-      width: 100%;
-      transition: width 0.2s;
-
-      &.ant-header-side-opened {
-        width: 100%;
-      }
-
-      &.ant-header-side-closed {
-        width: 100%;
-      }
-    }
-    /* 必须为 topmenu  才能启用流式布局 */
-    &.content-width-Fluid {
-      .header-index-wide {
-        max-width: unset;
-        .header-index-left {
-          flex: 1 1 1000px;
-          .logo{
-            margin-left: 25px;
-          }
-          .ant-menu.ant-menu-horizontal{
-            max-width: calc(100vw - 190px - 238px - 25px);
-            flex: 1 1 calc(100vw - 190px - 238px - 25px);
-          }
-        }
-        .header-index-right{
-          margin-right:25px;
-        }
-      }
-
-      .page-header-index-wide {
-        max-width: unset;
-      }
-    }
-  }
-
-  .sidemenu {
-    .ant-header-fixedHeader {
-      position: fixed;
-      top: 0;
-      right: 0;
-      z-index: 9;
-      width: 100%;
-      transition: width 0.2s;
-
-      &.ant-header-side-opened {
-        width: calc(100% - 256px);
-      }
-
-      &.ant-header-side-closed {
-        width: calc(100% - 80px);
-      }
-    }
-  }
-
-  .header {
-    height: 64px;
-    padding: 0 12px 0 0;
-    background: #fff;
-    box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
-    position: relative;
-  }
-
-  .header,
-  .top-nav-header-index {
-    .user-wrapper {
-      float: right;
-      height: 100%;
-
-      .action {
-        cursor: pointer;
-        padding: 0 12px;
-        display: inline-block;
-        transition: all 0.3s;
-        height: 100%;
-        color: rgba(0, 0, 0, 0.65);
-
-        &:hover {
-          background: rgba(0, 0, 0, 0.025);
-        }
-
-        .avatar {
-          margin: 20px 8px 20px 0;
-          color: #1890ff;
-          background: hsla(0, 0%, 100%, 0.85);
-          vertical-align: middle;
-        }
-
-        .icon {
-          font-size: 16px;
-          padding: 4px;
-        }
-      }
-    }
-
-    &.dark {
-      .user-wrapper {
-        .action {
-          color: rgba(255, 255, 255, 0.85);
-          a {
-            color: rgba(255, 255, 255, 0.85);
-          }
-
-          &:hover {
-            background: rgba(255, 255, 255, 0.16);
-          }
-        }
-      }
-    }
-  }
-
-  &.mobile,
-  &.tablet {
-    .top-nav-header-index {
-      .header-index-wide {
-        .header-index-left {
-          .trigger {
-            color: rgba(255, 255, 255, 0.85);
-            padding: 0 12px;
-          }
-
-          .logo.top-nav-header {
-            flex: 0 0 56px;
-            text-align: center;
-            line-height: 58px;
-            h1 {
-              display: none;
-            }
-          }
-        }
-      }
-
-      &.light {
-        .header-index-wide {
-          .header-index-left {
-            .trigger {
-              color: rgba(0, 0, 0, 0.65);
-            }
-          }
-        }
-        //
-      }
-    }
-  }
-
-  &.tablet {
-    // overflow: hidden; text-overflow:ellipsis; white-space: nowrap;
-    .top-nav-header-index {
-      .header-index-wide {
-        .header-index-left {
-          .logo > a {
-            overflow: hidden;
-            text-overflow: ellipsis;
-            white-space: nowrap;
-          }
-        }
-        .ant-menu.ant-menu-horizontal {
-          flex: 1 1 auto;
-          white-space: normal;
-        }
-      }
-    }
-  }
-
-  .top-nav-header-index {
-    box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
-    position: relative;
-    transition: background 0.3s, width 0.2s;
-
-    .header-index-wide {
-      max-width: 1200px;
-      margin: auto;
-      padding-left: 0;
-      display: flex;
-      height: 64px;
-
-      .ant-menu.ant-menu-horizontal {
-        max-width: 835px;
-        flex: 0 1 835px;
-        border: none;
-        height: 64px;
-        line-height: 64px;
-      }
-
-      .header-index-left {
-        flex: 0 1 1000px;
-        display: flex;
-
-        .logo.top-nav-header {
-          flex: 0 0 165px;
-          width: 165px;
-          height: 64px;
-          position: relative;
-          line-height: 64px;
-          transition: all 0.3s;
-          overflow: hidden;
-
-          img,
-          svg {
-            display: inline-block;
-            vertical-align: middle;
-            height: 32px;
-            width: 32px;
-          }
-
-          h1 {
-            color: #fff;
-            display: inline-block;
-            vertical-align: top;
-            font-size: 16px;
-            margin: 0 0 0 12px;
-            font-weight: 400;
-          }
-        }
-      }
-
-      .header-index-right {
-        flex: 0 0 238px;
-        align-self: flex-end;
-        height: 64px;
-        overflow: hidden;
-
-        .content-box {
-          float: right;
-          .action {
-            max-width: 140px;
-            overflow: hidden;
-            text-overflow:ellipsis;
-            white-space:nowrap;
-          }
-        }
-      }
-    }
-
-    &.light {
-      background-color: #fff;
-
-      .header-index-wide {
-        .header-index-left {
-          .logo {
-            h1 {
-              color: #002140;
-            }
-          }
-        }
-      }
-    }
-  }
-
-  // 内容区
-  .layout-content {
-    margin: 24px 24px 0px;
-    height: 100%;
-    height: 64px;
-    padding: 0 12px 0 0;
-  }
-
-  // footer
-  .ant-layout-footer {
-    padding: 0;
-  }
-}
-
-.topmenu {
-  .page-header-index-wide {
-    max-width: 1200px;
-    margin: 0 auto;
-  }
-}
-
-// drawer-sider 自定义
-.ant-drawer.drawer-sider {
-  .sider {
-    box-shadow: none;
-  }
-
-  &.dark {
-    .ant-drawer-content {
-      background-color: rgb(0, 21, 41);
-    }
-  }
-  &.light {
-    box-shadow: none;
-    .ant-drawer-content {
-      background-color: #fff;
-    }
-  }
-
-  .ant-drawer-body {
-    padding: 0;
-  }
-}
-
-// 菜单样式
-.sider {
-  box-shadow: 2px 0 6px rgba(0, 21, 41, 0.35);
-  position: relative;
-  z-index: @ant-global-sider-zindex;
-  min-height: 100vh;
-
-  .ant-layout-sider-children {
-    overflow-y: hidden;
-
-    &:hover {
-      overflow-y: auto;
-    }
-  }
-
-  &.ant-fixed-sidemenu {
-    position: fixed;
-    height: 100%;
-  }
-
-  .logo {
-    position: relative;
-    height: 64px;
-    padding-left: 24px;
-    overflow: hidden;
-    line-height: 64px;
-    background: #002140;
-    transition: all .3s;
-
-    img,
-    svg,
-    h1 {
-      display: inline-block;
-      vertical-align: middle;
-    }
-
-    img,
-    svg {
-      height: 32px;
-      width: 32px;
-    }
-
-    h1 {
-      color: #fff;
-      font-size: 20px;
-      margin: 0 0 0 12px;
-      font-family: Avenir, Helvetica Neue, Arial, Helvetica, sans-serif;
-      font-weight: 600;
-      vertical-align: middle;
-    }
-  }
-
-  &.light {
-    background-color: #fff;
-    box-shadow: 2px 0px 8px 0px rgba(29, 35, 41, 0.05);
-
-    .logo {
-      background: #fff;
-      box-shadow: 1px 1px 0px 0px #e8e8e8;
-
-      h1 {
-        color: unset;
-      }
-    }
-
-    .ant-menu-light {
-      border-right-color: transparent;
-    }
-  }
-}
-
-// 外置的样式控制
-.user-dropdown-menu {
-  span {
-    user-select: none;
-  }
-}
-.user-dropdown-menu-wrapper.ant-dropdown-menu {
-  padding: 4px 0;
-
-  .ant-dropdown-menu-item {
-    width: 160px;
-  }
-
-  .ant-dropdown-menu-item > .anticon:first-child,
-  .ant-dropdown-menu-item > a > .anticon:first-child,
-  .ant-dropdown-menu-submenu-title > .anticon:first-child .ant-dropdown-menu-submenu-title > a > .anticon:first-child {
-    min-width: 12px;
-    margin-right: 8px;
-  }
-}
-
-// 数据列表 样式
-.table-alert {
-  margin-bottom: 16px;
-}
-
-.table-page-search-wrapper {
-  .ant-form-inline {
-    .ant-form-item {
-      display: flex;
-      margin-bottom: 24px;
-      margin-right: 0;
-
-      .ant-form-item-control-wrapper {
-        flex: 1 1;
-        display: inline-block;
-        vertical-align: middle;
-      }
-
-      > .ant-form-item-label {
-        line-height: 32px;
-        padding-right: 8px;
-        width: auto;
-      }
-      .ant-form-item-control {
-        height: 32px;
-        line-height: 32px;
-      }
-    }
-  }
-
-  .table-page-search-submitButtons {
-    display: block;
-    margin-bottom: 24px;
-    white-space: nowrap;
-  }
-}
-
-.content {
-  .table-operator {
-    margin-bottom: 18px;
-
-    button {
-      margin-right: 8px;
-    }
-  }
-}

+ 0 - 8
src/components/index.js

@@ -13,18 +13,15 @@ import TagCloud from '@/components/Charts/TagCloud'
 
 // pro components
 import AvatarList from '@/components/AvatarList'
-import CountDown from '@/components/CountDown'
 import Ellipsis from '@/components/Ellipsis'
 import FooterToolbar from '@/components/FooterToolbar'
 import NumberInfo from '@/components/NumberInfo'
-import DescriptionList from '@/components/DescriptionList'
 import Tree from '@/components/Tree/Tree'
 import Trend from '@/components/Trend'
 import STable from '@/components/Table'
 import MultiTab from '@/components/MultiTab'
 import IconSelector from '@/components/IconSelector'
 import TagSelect from '@/components/TagSelect'
-import ExceptionPage from '@/components/Exception'
 import StandardFormRow from '@/components/StandardFormRow'
 import ArticleListContent from '@/components/ArticleListContent'
 
@@ -44,17 +41,12 @@ export {
   RankList,
   TransferBar,
   Trend,
-  CountDown,
   Ellipsis,
   FooterToolbar,
   NumberInfo,
-  DescriptionList,
-  // 兼容写法,请勿继续使用
-  DescriptionList as DetailList,
   Tree,
   STable,
   MultiTab,
-  ExceptionPage,
   IconSelector,
   TagSelect,
   StandardFormRow,

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

@@ -1,45 +0,0 @@
-<template>
-  <a-breadcrumb class="breadcrumb">
-    <a-breadcrumb-item v-for="(item, index) in breadList" :key="item.name">
-      <router-link
-        v-if="item.name != name && index != 1"
-        :to="{ path: item.path === '' ? '/' : item.path }"
-      >{{ item.meta.title }}</router-link>
-      <span v-else>{{ item.meta.title }}</span>
-    </a-breadcrumb-item>
-  </a-breadcrumb>
-</template>
-
-<script>
-export default {
-  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 scoped>
-</style>

+ 0 - 5
src/components/tools/DetailList.vue

@@ -1,5 +0,0 @@
-<script>
-/* WARNING: 兼容老引入,请勿继续使用 */
-import DescriptionList from '@/components/DescriptionList'
-export default DescriptionList
-</script>

+ 0 - 67
src/components/tools/HeadInfo.vue

@@ -1,67 +0,0 @@
-<template>
-  <div class="head-info" :class="center && 'center'">
-    <span>{{ title }}</span>
-    <p>{{ content }}</p>
-    <em v-if="bordered"/>
-  </div>
-</template>
-
-<script>
-export default {
-  name: 'HeadInfo',
-  props: {
-    title: {
-      type: String,
-      default: ''
-    },
-    content: {
-      type: String,
-      default: ''
-    },
-    bordered: {
-      type: Boolean,
-      default: false
-    },
-    center: {
-      type: Boolean,
-      default: true
-    }
-  }
-}
-</script>
-
-<style lang="less" scoped>
-  .head-info {
-    position: relative;
-    text-align: left;
-    padding: 0 32px 0 0;
-    min-width: 125px;
-
-    &.center {
-      text-align: center;
-      padding: 0 32px;
-    }
-
-    span {
-      color: rgba(0, 0, 0, .45);
-      display: inline-block;
-      font-size: 14px;
-      line-height: 22px;
-      margin-bottom: 4px;
-    }
-    p {
-      color: rgba(0, 0, 0, .85);
-      font-size: 24px;
-      line-height: 32px;
-      margin: 0;
-    }
-    em {
-      background-color: #e8e8e8;
-      position: absolute;
-      height: 56px;
-      width: 1px;
-      top: 0;
-      right: 0;
-    }
-  }
-</style>

+ 0 - 46
src/components/tools/LangSelect.vue

@@ -1,46 +0,0 @@
-<template>
-  <a-dropdown>
-    <span class="action global-lang">
-      <a-icon type="global" style="font-size: 16px"/>
-    </span>
-    <a-menu slot="overlay" style="width: 150px;" @click="SwitchLang">
-      <a-menu-item key="zh-CN">
-        <a rel="noopener noreferrer">
-          <span role="img" aria-label="简体中文">🇨🇳</span> 简体中文
-        </a>
-      </a-menu-item>
-      <a-menu-item key="zh-TW">
-        <a rel="noopener noreferrer">
-          <span role="img" aria-label="繁体中文">🇭🇰</span> 繁体中文
-        </a>
-      </a-menu-item>
-      <a-menu-item key="en-US">
-        <a rel="noopener noreferrer">
-          <span role="img" aria-label="English">🇬🇧</span> English
-        </a>
-      </a-menu-item>
-      <a-menu-item key="pt-BR">
-        <a rel="noopener noreferrer">
-          <span role="img" aria-label="Português">🇧🇷</span> Português
-        </a>
-      </a-menu-item>
-    </a-menu>
-  </a-dropdown>
-</template>
-
-<script>
-// import { mixin as langMixin } from '@/store/i18n-mixin'
-
-export default {
-  name: 'LangSelect',
-  // mixins: [langMixin],
-  data () {
-    return {}
-  },
-  methods: {
-    // SwitchLang (row) {
-    //   this.setLang(row.key)
-    // }
-  }
-}
-</script>

+ 0 - 31
src/components/tools/Logo.vue

@@ -1,31 +0,0 @@
-<template>
-  <div class="logo">
-    <router-link :to="{name:'dashboard'}">
-      <LogoSvg alt="logo" />
-      <h1 v-if="showTitle">{{ title }}</h1>
-    </router-link>
-  </div>
-</template>
-
-<script>
-import LogoSvg from '@/assets/logo.svg?inline'
-
-export default {
-  name: 'Logo',
-  components: {
-    LogoSvg
-  },
-  props: {
-    title: {
-      type: String,
-      default: 'Ant Design Pro',
-      required: false
-    },
-    showTitle: {
-      type: Boolean,
-      default: true,
-      required: false
-    }
-  }
-}
-</script>

+ 0 - 82
src/components/tools/UserMenu.vue

@@ -1,82 +0,0 @@
-<template>
-  <div class="user-wrapper">
-    <div class="content-box">
-      <a href="https://pro.loacg.com/docs/getting-started" target="_blank">
-        <span class="action">
-          <a-icon type="question-circle-o"></a-icon>
-        </span>
-      </a>
-      <notice-icon 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>
-</template>
-
-<script>
-import NoticeIcon from '@/components/NoticeIcon'
-import { mapActions, mapGetters } from 'vuex'
-
-export default {
-  name: 'UserMenu',
-  components: {
-    NoticeIcon
-  },
-  computed: {
-    ...mapGetters(['nickname', 'avatar'])
-
-  },
-  methods: {
-    ...mapActions(['Logout']),
-    handleLogout () {
-      this.$confirm({
-        title: '提示',
-        content: '真的要注销登录吗 ?',
-        onOk: () => {
-          return this.Logout({}).then(() => {
-            setTimeout(() => {
-              window.location.reload()
-            }, 16)
-          }).catch(err => {
-            this.$message.error({
-              title: '错误',
-              description: err.message
-            })
-          })
-        },
-        onCancel () {
-        }
-      })
-    }
-  }
-}
-</script>

+ 0 - 0
src/components/tools/index.js


+ 10 - 4
src/config/defaultSettings.js

@@ -14,14 +14,20 @@
  */
 
 export default {
-  primaryColor: '#52C41A', // primary color of ant design
   navTheme: 'dark', // theme for nav menu
-  layout: 'sidemenu', // nav menu position: sidemenu or topmenu
-  contentWidth: 'Fixed', // layout of content: Fluid or Fixed, only works when layout is topmenu
+  primaryColor: '#52C41A', // primary color of ant design
+  layout: 'sidemenu', // nav menu position: `sidemenu` or `topmenu`
+  contentWidth: 'Fixed', // layout of content: `Fluid` or `Fixed`, only works when layout is topmenu
   fixedHeader: false, // sticky header
-  fixSiderbar: false, // sticky siderbar
   autoHideHeader: false, //  auto hide header
+  fixSiderbar: false, // sticky siderbar
   colorWeak: false,
+  menu: {
+    locale: true
+  },
+  title: 'Ant Design Pro',
   multiTab: false,
+  pwa: false,
+  iconfontUrl: '',
   production: process.env.NODE_ENV === 'production' && process.env.VUE_APP_PREVIEW !== 'true'
 }

+ 15 - 28
src/config/router.config.js

@@ -1,6 +1,12 @@
 // eslint-disable-next-line
-import { UserLayout, BasicLayout, RouteView, BlankLayout, PageView } from '@/layouts'
+import { UserLayout, BasicLayout, BlankLayout } from '@/layouts'
 import { bxAnaalyse } from '@/core/icons'
+import PageView from '@/layouts/PageView'
+
+const RouteView = {
+  name: 'RouteView',
+  render: (h) => h('router-view')
+}
 
 export const asyncRouterMap = [
 
@@ -13,14 +19,14 @@ export const asyncRouterMap = [
     children: [
       // dashboard
       {
-        path: 'dashboard',
+        path: '/dashboard',
         name: 'dashboard',
         redirect: '/dashboard/workplace',
         component: RouteView,
         meta: { title: '仪表盘', keepAlive: true, icon: bxAnaalyse, permission: [ 'dashboard' ] },
         children: [
           {
-            path: 'analysis/:pageNo([1-9]\\d*)?',
+            path: '/dashboard/analysis/:pageNo([1-9]\\d*)?',
             name: 'Analysis',
             component: () => import('@/views/dashboard/Analysis'),
             meta: { title: '分析页', keepAlive: false, permission: [ 'dashboard' ] }
@@ -32,16 +38,10 @@ export const asyncRouterMap = [
             meta: { title: '监控页(外部)', target: '_blank' }
           },
           {
-            path: 'workplace',
+            path: '/dashboard/workplace',
             name: 'Workplace',
             component: () => import('@/views/dashboard/Workplace'),
             meta: { title: '工作台', keepAlive: true, permission: [ 'dashboard' ] }
-          },
-          {
-            path: 'test-work',
-            name: 'TestWork',
-            component: () => import('@/views/dashboard/TestWork'),
-            meta: { title: '测试功能', keepAlive: true, permission: [ 'dashboard' ] }
           }
         ]
       },
@@ -50,13 +50,13 @@ export const asyncRouterMap = [
       {
         path: '/form',
         redirect: '/form/base-form',
-        component: PageView,
+        component: RouteView,
         meta: { title: '表单页', icon: 'form', permission: [ 'form' ] },
         children: [
           {
             path: '/form/base-form',
             name: 'BaseForm',
-            component: () => import('@/views/form/BasicForm'),
+            component: () => import('@/views/form/basicForm'),
             meta: { title: '基础表单', keepAlive: true, permission: [ 'form' ] }
           },
           {
@@ -78,7 +78,7 @@ export const asyncRouterMap = [
       {
         path: '/list',
         name: 'list',
-        component: PageView,
+        component: RouteView,
         redirect: '/list/table-list',
         meta: { title: '列表页', icon: 'table', permission: [ 'table' ] },
         children: [
@@ -142,7 +142,7 @@ export const asyncRouterMap = [
           {
             path: '/profile/basic',
             name: 'ProfileBasic',
-            component: () => import('@/views/profile/basic/Index'),
+            component: () => import('@/views/profile/basic'),
             meta: { title: '基础详情页', permission: [ 'profile' ] }
           },
           {
@@ -158,7 +158,7 @@ export const asyncRouterMap = [
       {
         path: '/result',
         name: 'result',
-        component: PageView,
+        component: RouteView,
         redirect: '/result/success',
         meta: { title: '结果页', icon: 'check-circle-o', permission: [ 'result' ] },
         children: [
@@ -364,19 +364,6 @@ export const constantRouterMap = [
     ]
   },
 
-  {
-    path: '/test',
-    component: BlankLayout,
-    redirect: '/test/home',
-    children: [
-      {
-        path: 'home',
-        name: 'TestHome',
-        component: () => import('@/views/Home')
-      }
-    ]
-  },
-
   {
     path: '/404',
     component: () => import(/* webpackChunkName: "fail" */ '@/views/exception/404')

+ 18 - 22
src/core/bootstrap.js

@@ -1,34 +1,30 @@
-import store from '@/store/'
+import store from '@/store'
 import storage from 'store'
 import {
   ACCESS_TOKEN,
-  DEFAULT_COLOR,
-  DEFAULT_THEME,
-  DEFAULT_LAYOUT_MODE,
-  DEFAULT_COLOR_WEAK,
-  SIDEBAR_TYPE,
-  DEFAULT_FIXED_HEADER,
-  DEFAULT_FIXED_HEADER_HIDDEN,
-  DEFAULT_FIXED_SIDEMENU,
-  DEFAULT_CONTENT_WIDTH_TYPE,
-  DEFAULT_MULTI_TAB
+  APP_LANGUAGE,
+  TOGGLE_CONTENT_WIDTH,
+  TOGGLE_FIXED_HEADER,
+  TOGGLE_FIXED_SIDEBAR, TOGGLE_HIDE_HEADER,
+  TOGGLE_LAYOUT, TOGGLE_NAV_THEME, TOGGLE_WEAK,
+  TOGGLE_COLOR, TOGGLE_MULTI_TAB
 } from '@/store/mutation-types'
-import config from '@/config/defaultSettings'
+import defaultSettings from '@/config/defaultSettings'
 
 export default function Initializer () {
   console.log(`API_URL: ${process.env.VUE_APP_API_BASE_URL}`)
 
-  store.commit('SET_SIDEBAR_TYPE', storage.get(SIDEBAR_TYPE, true))
-  store.commit('TOGGLE_THEME', storage.get(DEFAULT_THEME, config.navTheme))
-  store.commit('TOGGLE_LAYOUT_MODE', storage.get(DEFAULT_LAYOUT_MODE, config.layout))
-  store.commit('TOGGLE_FIXED_HEADER', storage.get(DEFAULT_FIXED_HEADER, config.fixedHeader))
-  store.commit('TOGGLE_FIXED_SIDERBAR', storage.get(DEFAULT_FIXED_SIDEMENU, config.fixSiderbar))
-  store.commit('TOGGLE_CONTENT_WIDTH', storage.get(DEFAULT_CONTENT_WIDTH_TYPE, config.contentWidth))
-  store.commit('TOGGLE_FIXED_HEADER_HIDDEN', storage.get(DEFAULT_FIXED_HEADER_HIDDEN, config.autoHideHeader))
-  store.commit('TOGGLE_WEAK', storage.get(DEFAULT_COLOR_WEAK, config.colorWeak))
-  store.commit('TOGGLE_COLOR', storage.get(DEFAULT_COLOR, config.primaryColor))
-  store.commit('TOGGLE_MULTI_TAB', storage.get(DEFAULT_MULTI_TAB, config.multiTab))
+  store.commit(TOGGLE_LAYOUT, storage.get(TOGGLE_LAYOUT, defaultSettings.layout))
+  store.commit(TOGGLE_FIXED_HEADER, storage.get(TOGGLE_FIXED_HEADER, defaultSettings.fixedHeader))
+  store.commit(TOGGLE_FIXED_SIDEBAR, storage.get(TOGGLE_FIXED_SIDEBAR, defaultSettings.fixSiderbar))
+  store.commit(TOGGLE_CONTENT_WIDTH, storage.get(TOGGLE_CONTENT_WIDTH, defaultSettings.contentWidth))
+  store.commit(TOGGLE_HIDE_HEADER, storage.get(TOGGLE_HIDE_HEADER, defaultSettings.autoHideHeader))
+  store.commit(TOGGLE_NAV_THEME, storage.get(TOGGLE_NAV_THEME, defaultSettings.navTheme))
+  store.commit(TOGGLE_WEAK, storage.get(TOGGLE_WEAK, defaultSettings.colorWeak))
+  store.commit(TOGGLE_COLOR, storage.get(TOGGLE_COLOR, defaultSettings.primaryColor))
+  store.commit(TOGGLE_MULTI_TAB, storage.get(TOGGLE_MULTI_TAB, defaultSettings.multiTab))
   store.commit('SET_TOKEN', storage.get(ACCESS_TOKEN))
 
+  store.dispatch('setLang', storage.get(APP_LANGUAGE, 'en-US'))
   // last step
 }

+ 6 - 0
src/core/lazy_use.js

@@ -39,7 +39,10 @@ import {
   Progress,
   Skeleton,
   Popconfirm,
+  PageHeader,
   Result,
+  Statistic,
+  Descriptions,
   message,
   notification
 } from 'ant-design-vue'
@@ -90,7 +93,10 @@ Vue.use(Upload)
 Vue.use(Progress)
 Vue.use(Skeleton)
 Vue.use(Popconfirm)
+Vue.use(PageHeader)
 Vue.use(Result)
+Vue.use(Statistic)
+Vue.use(Descriptions)
 
 Vue.prototype.$confirm = Modal.confirm
 Vue.prototype.$message = message

+ 94 - 0
src/global.less

@@ -0,0 +1,94 @@
+@import '~ant-design-vue/es/style/themes/default.less';
+
+html,
+body,
+#app, #root {
+  height: 100%;
+}
+
+.colorWeak {
+  filter: invert(80%);
+}
+
+.ant-layout.layout-basic {
+  height: 100vh;
+  min-height: 100vh;
+}
+
+canvas {
+  display: block;
+}
+
+body {
+  text-rendering: optimizeLegibility;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+}
+
+ul,
+ol {
+  list-style: none;
+}
+
+// 数据列表 样式
+.table-alert {
+  margin-bottom: 16px;
+}
+// 数据列表 操作
+.table-operator {
+  margin-bottom: 18px;
+
+  button {
+    margin-right: 8px;
+  }
+}
+// 数据列表 搜索条件
+.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;
+  }
+}
+
+@media (max-width: @screen-xs) {
+  .ant-table {
+    width: 100%;
+    overflow-x: auto;
+    &-thead > tr,
+    &-tbody > tr {
+      > th,
+      > td {
+        white-space: pre;
+        > span {
+          display: block;
+        }
+      }
+    }
+  }
+}

+ 31 - 0
src/layouts/BasicLayout.less

@@ -0,0 +1,31 @@
+@import "~ant-design-vue/es/style/themes/default";
+
+.ant-pro-global-header-index-right {
+    &.ant-pro-global-header-index-dark {
+        .ant-pro-global-header-index-action {
+            color: hsla(0,0%,100%,.85);
+            &:hover {
+                background: #1890ff;
+            }
+        }
+    }
+    .ant-pro-account-avatar {
+        .antd-pro-global-header-index-avatar {
+            margin: ~'calc((@{layout-header-height} - 24px) / 2)' 0;
+            margin-right: 8px;
+            color: @primary-color;
+            vertical-align: top;
+            background: rgba(255, 255, 255, 0.85);
+        }
+    }
+
+    .menu {
+        .anticon {
+            margin-right: 8px;
+        }
+        .ant-dropdown-menu-item {
+            min-width: 100px;
+        }
+    }
+}
+

+ 72 - 130
src/layouts/BasicLayout.vue

@@ -1,118 +1,83 @@
 <template>
-  <a-layout :class="['layout', device]">
-    <!-- SideMenu -->
-    <a-drawer
-      v-if="isMobile()"
-      placement="left"
-      :wrapClassName="`drawer-sider ${navTheme}`"
-      :closable="false"
-      :visible="collapsed"
-      @close="drawerClose"
-    >
-      <side-menu
-        mode="inline"
-        :menus="menus"
-        :theme="navTheme"
-        :collapsed="false"
-        :collapsible="true"
-        @menuSelect="menuSelect"
-      ></side-menu>
-    </a-drawer>
-
-    <side-menu
-      v-else-if="isSideMenu()"
-      mode="inline"
-      :menus="menus"
-      :theme="navTheme"
-      :collapsed="collapsed"
-      :collapsible="true"
-    ></side-menu>
-
-    <a-layout :class="[layoutMode, `content-width-${contentWidth}`]" :style="{ paddingLeft: contentPaddingLeft, minHeight: '100vh' }">
-      <!-- layout header -->
-      <global-header
-        :mode="layoutMode"
-        :menus="menus"
-        :theme="navTheme"
-        :collapsed="collapsed"
-        :device="device"
-        @toggle="toggle"
-      />
-
-      <!-- layout content -->
-      <a-layout-content :style="{ height: '100%', margin: '24px 24px 0', paddingTop: fixedHeader ? '64px' : '0' }">
-        <multi-tab v-if="multiTab"></multi-tab>
-        <transition name="page-transition">
-          <route-view />
-        </transition>
-      </a-layout-content>
-
-      <!-- layout footer -->
-      <a-layout-footer>
-        <global-footer />
-      </a-layout-footer>
-
-      <!-- Setting Drawer (show in development mode) -->
-      <setting-drawer v-if="!production"></setting-drawer>
-    </a-layout>
-  </a-layout>
-
+  <pro-layout
+    title="Ant Design Pro"
+    :menus="menus"
+    :collapsed="collapsed"
+    :theme="theme"
+    :layout="layout"
+    :contentWidth="contentWidth"
+    :auto-hide-header="autoHideHeader"
+    :mediaQuery="query"
+    :isMobile="isMobile"
+    :handleMediaQuery="handleMediaQuery"
+    :handleCollapse="handleCollapse"
+    :logo="logoRender"
+    :i18nRender="i18nRender"
+  >
+    <template v-slot:rightContentRender>
+      <right-content />
+    </template>
+    <template v-slot:footerRender>
+      <global-footer />
+    </template>
+    <router-view />
+  </pro-layout>
 </template>
 
 <script>
-import { triggerWindowResizeEvent } from '@/utils/util'
-import { mapState, mapActions } from 'vuex'
-import { mixin, mixinDevice } from '@/utils/mixin'
-import config from '@/config/defaultSettings'
-
-import RouteView from './RouteView'
-import SideMenu from '@/components/Menu/SideMenu'
-import GlobalHeader from '@/components/GlobalHeader'
+import { i18nRender } from '@/locales'
+import { mapState } from 'vuex'
+import ProLayout from '@ant-design-vue/pro-layout'
+import RightContent from '@/components/GlobalHeader/RightContent'
 import GlobalFooter from '@/components/GlobalFooter'
-import SettingDrawer from '@/components/SettingDrawer'
-import { convertRoutes } from '@/utils/routeConvert'
+
+import LogoSvg from '../assets/logo.svg?inline'
+import { SIDEBAR_TYPE, TOGGLE_MOBILE_TYPE } from '@/store/mutation-types'
 
 export default {
   name: 'BasicLayout',
-  mixins: [mixin, mixinDevice],
   components: {
-    RouteView,
-    SideMenu,
-    GlobalHeader,
+    ProLayout,
+    RightContent,
     GlobalFooter,
-    SettingDrawer
+    LogoSvg
   },
   data () {
     return {
-      production: config.production,
+      // base
+      menus: [],
+      // 侧栏收起状态
       collapsed: false,
-      menus: []
+      // 自动隐藏头部栏
+      autoHideHeader: false,
+      // 媒体查询
+      query: {},
+      // 布局类型
+      layout: 'sidemenu', // 'sidemenu', 'topmenu'
+      // 定宽: true / 流式: false
+      contentWidth: true,
+      // 主题 'dark' | 'light'
+      theme: 'dark',
+      // 是否手机模式
+      isMobile: false
     }
   },
   computed: {
     ...mapState({
       // 动态主路由
       mainMenu: state => state.permission.addRouters
-    }),
-    contentPaddingLeft () {
-      if (!this.fixSidebar || this.isMobile()) {
-        return '0'
-      }
-      if (this.sidebarOpened) {
-        return '256px'
-      }
-      return '80px'
-    }
-  },
-  watch: {
-    sidebarOpened (val) {
-      this.collapsed = !val
-    }
+    })
   },
   created () {
-    const routes = convertRoutes(this.mainMenu.find(item => item.path === '/'))
+    const routes = this.mainMenu.find(item => item.path === '/')
     this.menus = (routes && routes.children) || []
-    this.collapsed = !this.sidebarOpened
+    // 处理侧栏收起状态
+    this.$watch('collapsed', () => {
+      this.$store.commit(SIDEBAR_TYPE, this.collapsed)
+    })
+    this.$watch('isMobile', () => {
+      this.$store.commit(TOGGLE_MOBILE_TYPE, this.isMobile)
+    })
   },
   mounted () {
     const userAgent = navigator.userAgent
@@ -126,51 +91,28 @@ export default {
     }
   },
   methods: {
-    ...mapActions(['setSidebar']),
-    toggle () {
-      this.collapsed = !this.collapsed
-      this.setSidebar(!this.collapsed)
-      triggerWindowResizeEvent()
-    },
-    paddingCalc () {
-      let left = ''
-      if (this.sidebarOpened) {
-        left = this.isDesktop() ? '256px' : '80px'
-      } else {
-        left = (this.isMobile() && '0') || ((this.fixSidebar && '80px') || '0')
+    i18nRender,
+    handleMediaQuery (val) {
+      this.query = val
+      if (this.isMobile && !val['screen-xs']) {
+        this.isMobile = false
+        return
+      }
+      if (!this.isMobile && val['screen-xs']) {
+        this.isMobile = true
+        this.collapsed = false
       }
-      return left
     },
-    menuSelect () {
+    handleCollapse (val) {
+      this.collapsed = val
     },
-    drawerClose () {
-      this.collapsed = false
+    logoRender () {
+      return <LogoSvg />
     }
   }
 }
 </script>
 
 <style lang="less">
-/*
- * The following styles are auto-applied to elements with
- * transition="page-transition" when their visibility is toggled
- * by Vue.js.
- *
- * You can easily play with the page transition by editing
- * these styles.
- */
-
-.page-transition-enter {
-  opacity: 0;
-}
-
-.page-transition-leave-active {
-  opacity: 0;
-}
-
-.page-transition-enter .page-transition-container,
-.page-transition-leave-active .page-transition-container {
-  -webkit-transform: scale(1.1);
-  transform: scale(1.1);
-}
+@import "./BasicLayout.less";
 </style>

+ 4 - 173
src/layouts/PageView.vue

@@ -1,181 +1,12 @@
 <template>
-  <div :style="!$route.meta.hiddenHeaderContent ? 'margin: -24px -24px 0px;' : null">
-    <!-- pageHeader , route meta :true on hide -->
-    <page-header v-if="!$route.meta.hiddenHeaderContent" :title="pageTitle" :logo="logo" :avatar="avatar">
-      <slot slot="action" name="action"></slot>
-      <slot slot="content" name="headerContent"></slot>
-      <div slot="content" v-if="!this.$slots.headerContent && description">
-        <p style="font-size: 14px;color: rgba(0,0,0,.65)">{{ description }}</p>
-        <div class="link">
-          <template v-for="(link, index) in linkList">
-            <a :key="index" @click="() => { link.callback && link.callback() }">
-              <a-icon :type="link.icon" />
-              <span>{{ link.title }}</span>
-            </a>
-          </template>
-        </div>
-      </div>
-      <slot slot="extra" name="extra">
-        <div class="extra-img">
-          <img v-if="typeof extraImage !== 'undefined'" :src="extraImage"/>
-        </div>
-      </slot>
-      <div slot="pageMenu">
-        <div class="page-menu-search" v-if="search">
-          <a-input-search
-            style="width: 80%; max-width: 522px;"
-            placeholder="请输入..."
-            size="large"
-            enterButton="搜索"
-          />
-        </div>
-        <div class="page-menu-tabs" v-if="tabs && tabs.items">
-          <!-- @change="callback" :activeKey="activeKey" -->
-          <a-tabs :tabBarStyle="{margin: 0}" :activeKey="tabs.active()" @change="tabs.callback">
-            <a-tab-pane v-for="item in tabs.items" :tab="item.title" :key="item.key"></a-tab-pane>
-          </a-tabs>
-        </div>
-      </div>
-    </page-header>
-    <div class="content">
-      <div class="page-header-index-wide">
-        <slot>
-          <!-- keep-alive  -->
-          <keep-alive v-if="multiTab">
-            <router-view ref="content" />
-          </keep-alive>
-          <router-view v-else ref="content" />
-        </slot>
-      </div>
-    </div>
-  </div>
+  <page-header-wrapper>
+    <router-view />
+  </page-header-wrapper>
 </template>
 
 <script>
-import { mapState } from 'vuex'
-import PageHeader from '@/components/PageHeader'
 
 export default {
-  name: 'PageView',
-  components: {
-    PageHeader
-  },
-  props: {
-    avatar: {
-      type: String,
-      default: null
-    },
-    title: {
-      type: [String, Boolean],
-      default: true
-    },
-    logo: {
-      type: String,
-      default: null
-    },
-    directTabs: {
-      type: Object,
-      default: null
-    }
-  },
-  data () {
-    return {
-      pageTitle: null,
-      description: null,
-      linkList: [],
-      extraImage: '',
-      search: false,
-      tabs: {}
-    }
-  },
-  computed: {
-    ...mapState({
-      multiTab: state => state.app.multiTab
-    })
-  },
-  mounted () {
-    this.tabs = this.directTabs
-    this.getPageMeta()
-  },
-  updated () {
-    this.getPageMeta()
-  },
-  methods: {
-    getPageMeta () {
-      // eslint-disable-next-line
-      this.pageTitle = (typeof(this.title) === 'string' || !this.title) ? this.title : this.$route.meta.title
-
-      const content = this.$refs.content
-      if (content) {
-        if (content.pageMeta) {
-          Object.assign(this, content.pageMeta)
-        } else {
-          this.description = content.description
-          this.linkList = content.linkList
-          this.extraImage = content.extraImage
-          this.search = content.search === true
-          this.tabs = content.tabs
-        }
-      }
-    }
-  }
+  name: 'PageView'
 }
 </script>
-
-<style lang="less" scoped>
-  .content {
-    margin: 24px 24px 0;
-    .link {
-      margin-top: 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;
-        }
-        span {
-          height: 24px;
-          line-height: 24px;
-          display: inline-block;
-          vertical-align: middle;
-        }
-      }
-    }
-  }
-  .page-menu-search {
-    text-align: center;
-    margin-bottom: 16px;
-  }
-  .page-menu-tabs {
-    margin-top: 48px;
-  }
-
-  .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>

+ 58 - 0
src/locales/index.js

@@ -0,0 +1,58 @@
+import Vue from 'vue'
+import VueI18n from 'vue-i18n'
+import storage from 'store'
+import moment from 'moment'
+
+// default lang
+import enUS from './lang/en-US'
+
+Vue.use(VueI18n)
+
+export const defaultLang = 'en-US'
+
+const messages = {
+  'en-US': {
+    ...enUS
+  }
+}
+
+const i18n = new VueI18n({
+  locale: defaultLang,
+  fallbackLocale: defaultLang,
+  messages
+})
+
+const loadedLanguages = [defaultLang]
+
+function setI18nLanguage (lang) {
+  i18n.locale = lang
+  // request.headers['Accept-Language'] = lang
+  document.querySelector('html').setAttribute('lang', lang)
+  return lang
+}
+
+export function loadLanguageAsync (lang = defaultLang) {
+  return new Promise(resolve => {
+    // 缓存语言设置
+    storage.set('lang', lang)
+    if (i18n.locale !== lang) {
+      if (!loadedLanguages.includes(lang)) {
+        return import(/* webpackChunkName: "lang-[request]" */ `./lang/${lang}`).then(msg => {
+          const locale = msg.default
+          i18n.setLocaleMessage(lang, locale)
+          loadedLanguages.push(lang)
+          moment.updateLocale(locale.momentName, locale.momentLocale)
+          return setI18nLanguage(lang)
+        })
+      }
+      return resolve(setI18nLanguage(lang))
+    }
+    return resolve(lang)
+  })
+}
+
+export function i18nRender (key) {
+  return i18n.t(`${key}`)
+}
+
+export default i18n

+ 17 - 0
src/locales/lang/en-US.js

@@ -0,0 +1,17 @@
+import antdEnUS from 'ant-design-vue/es/locale-provider/en_US'
+import momentEU from 'moment/locale/eu'
+
+const components = {
+  antLocale: antdEnUS,
+  momentName: 'eu',
+  momentLocale: momentEU
+}
+
+const locale = {
+  'message': '-'
+}
+
+export default {
+  ...components,
+  ...locale
+}

+ 4 - 4
src/main.js

@@ -7,6 +7,7 @@ import App from './App.vue'
 import router from './router'
 import store from './store/'
 import { VueAxios } from './utils/request'
+import { PageHeaderWrapper } from '@ant-design-vue/pro-layout'
 
 // mock
 // WARNING: `mockjs` NOT SUPPORT `IE` PLEASE DO NOT USE IN `production` ENV.
@@ -16,14 +17,13 @@ import bootstrap from './core/bootstrap'
 import './core/lazy_use'
 import './permission' // permission control
 import './utils/filter' // global filter
-import './components/global.less'
-import { Dialog } from '@/components'
+import './global.less'
 
 Vue.config.productionTip = false
 
-// mount axios Vue.$http and this.$http
+// mount axios to `Vue.$http` and `this.$http`
 Vue.use(VueAxios)
-Vue.use(Dialog)
+Vue.component('page-header-wrapper', PageHeaderWrapper)
 
 new Vue({
   router,

+ 32 - 0
src/store/app-mixin.js

@@ -0,0 +1,32 @@
+import { mapState } from 'vuex'
+
+const baseMixin = {
+  computed: {
+    ...mapState({
+      layout: state => state.app.layout,
+      navTheme: state => state.app.theme,
+      primaryColor: state => state.app.color,
+      colorWeak: state => state.app.weak,
+      fixedHeader: state => state.app.fixedHeader,
+      fixedSidebar: state => state.app.fixedSidebar,
+      contentWidth: state => state.app.contentWidth,
+      autoHideHeader: state => state.app.autoHideHeader,
+
+      isMobile: state => state.app.isMobile,
+      sideCollapsed: state => state.app.sideCollapsed,
+      multiTab: state => state.app.multiTab
+    }),
+    isTopMenu () {
+      return this.layout === 'topmenu'
+    }
+  },
+  methods: {
+    isSideMenu () {
+      return !this.isTopMenu
+    }
+  }
+}
+
+export {
+  baseMixin
+}

+ 11 - 0
src/store/device-mixin.js

@@ -0,0 +1,11 @@
+import { mapState } from 'vuex'
+
+const deviceMixin = {
+  computed: {
+    ...mapState({
+      isMobile: state => state.app.isMobile
+    })
+  }
+}
+
+export { deviceMixin }

+ 3 - 3
src/store/getters.js

@@ -1,5 +1,6 @@
 const getters = {
-  device: state => state.app.device,
+  isMobile: state => state.app.isMobile,
+  lang: state => state.i18n.lang,
   theme: state => state.app.theme,
   color: state => state.app.color,
   token: state => state.user.token,
@@ -9,8 +10,7 @@ const getters = {
   roles: state => state.user.roles,
   userInfo: state => state.user.info,
   addRouters: state => state.permission.addRouters,
-  multiTab: state => state.app.multiTab,
-  lang: state => state.i18n.lang
+  multiTab: state => state.app.multiTab
 }
 
 export default getters

+ 16 - 0
src/store/i18n-mixin.js

@@ -0,0 +1,16 @@
+import { mapState } from 'vuex'
+
+const i18nMixin = {
+  computed: {
+    ...mapState({
+      currentLang: state => state.app.lang
+    })
+  },
+  methods: {
+    setLang (lang) {
+      this.$store.dispatch('setLang', lang)
+    }
+  }
+}
+
+export default i18nMixin

+ 64 - 86
src/store/modules/app.js

@@ -1,120 +1,98 @@
 import storage from 'store'
 import {
   SIDEBAR_TYPE,
-  DEFAULT_THEME,
-  DEFAULT_LAYOUT_MODE,
-  DEFAULT_COLOR,
-  DEFAULT_COLOR_WEAK,
-  DEFAULT_FIXED_HEADER,
-  DEFAULT_FIXED_SIDEMENU,
-  DEFAULT_FIXED_HEADER_HIDDEN,
-  DEFAULT_CONTENT_WIDTH_TYPE,
-  DEFAULT_MULTI_TAB
+  TOGGLE_MOBILE_TYPE,
+  TOGGLE_NAV_THEME,
+  TOGGLE_LAYOUT,
+  TOGGLE_FIXED_HEADER,
+  TOGGLE_FIXED_SIDEBAR,
+  TOGGLE_CONTENT_WIDTH,
+  TOGGLE_HIDE_HEADER,
+  TOGGLE_COLOR,
+  TOGGLE_WEAK,
+  TOGGLE_MULTI_TAB,
+  // i18n
+  APP_LANGUAGE
 } from '@/store/mutation-types'
+import { loadLanguageAsync } from '@/locales'
 
 const app = {
   state: {
-    sidebar: true,
-    device: 'desktop',
-    theme: '',
+    sideCollapsed: false,
+    isMobile: false,
+    theme: 'dark',
     layout: '',
     contentWidth: '',
     fixedHeader: false,
-    fixSiderbar: false,
+    fixedSidebar: false,
     autoHideHeader: false,
-    color: null,
+    color: '',
     weak: false,
-    multiTab: true
+    multiTab: true,
+    lang: 'en-US',
+    _antLocale: {}
   },
   mutations: {
-    SET_SIDEBAR_TYPE: (state, type) => {
-      state.sidebar = type
+    [SIDEBAR_TYPE]: (state, type) => {
+      state.sideCollapsed = type
       storage.set(SIDEBAR_TYPE, type)
     },
-    CLOSE_SIDEBAR: (state) => {
-      storage.set(SIDEBAR_TYPE, true)
-      state.sidebar = false
+    [TOGGLE_MOBILE_TYPE]: (state, isMobile) => {
+      state.isMobile = isMobile
     },
-    TOGGLE_DEVICE: (state, device) => {
-      state.device = device
-    },
-    TOGGLE_THEME: (state, theme) => {
-      // setStore('_DEFAULT_THEME', theme)
-      storage.set(DEFAULT_THEME, theme)
+    [TOGGLE_NAV_THEME]: (state, theme) => {
       state.theme = theme
+      storage.set(TOGGLE_NAV_THEME, theme)
     },
-    TOGGLE_LAYOUT_MODE: (state, layout) => {
-      storage.set(DEFAULT_LAYOUT_MODE, layout)
-      state.layout = layout
-    },
-    TOGGLE_FIXED_HEADER: (state, fixed) => {
-      storage.set(DEFAULT_FIXED_HEADER, fixed)
-      state.fixedHeader = fixed
+    [TOGGLE_LAYOUT]: (state, mode) => {
+      state.layout = mode
+      storage.set(TOGGLE_LAYOUT, mode)
     },
-    TOGGLE_FIXED_SIDERBAR: (state, fixed) => {
-      storage.set(DEFAULT_FIXED_SIDEMENU, fixed)
-      state.fixSiderbar = fixed
+    [TOGGLE_FIXED_HEADER]: (state, mode) => {
+      state.fixedHeader = mode
+      storage.set(TOGGLE_FIXED_HEADER, mode)
     },
-    TOGGLE_FIXED_HEADER_HIDDEN: (state, show) => {
-      storage.set(DEFAULT_FIXED_HEADER_HIDDEN, show)
-      state.autoHideHeader = show
+    [TOGGLE_FIXED_SIDEBAR]: (state, mode) => {
+      state.fixedSidebar = mode
+      storage.set(TOGGLE_FIXED_SIDEBAR, mode)
     },
-    TOGGLE_CONTENT_WIDTH: (state, type) => {
-      storage.set(DEFAULT_CONTENT_WIDTH_TYPE, type)
+    [TOGGLE_CONTENT_WIDTH]: (state, type) => {
       state.contentWidth = type
+      storage.set(TOGGLE_CONTENT_WIDTH, type)
     },
-    TOGGLE_COLOR: (state, color) => {
-      storage.set(DEFAULT_COLOR, color)
+    [TOGGLE_HIDE_HEADER]: (state, type) => {
+      state.autoHideHeader = type
+      storage.set(TOGGLE_HIDE_HEADER, type)
+    },
+    [TOGGLE_COLOR]: (state, color) => {
       state.color = color
+      storage.set(TOGGLE_COLOR, color)
+    },
+    [TOGGLE_WEAK]: (state, mode) => {
+      state.weak = mode
+      storage.set(TOGGLE_WEAK, mode)
     },
-    TOGGLE_WEAK: (state, flag) => {
-      storage.set(DEFAULT_COLOR_WEAK, flag)
-      state.weak = flag
+    [APP_LANGUAGE]: (state, lang, antd = {}) => {
+      console.log('lang', lang)
+      state.lang = lang
+      state._antLocale = antd
+      storage.set(APP_LANGUAGE, lang)
     },
-    TOGGLE_MULTI_TAB: (state, bool) => {
-      storage.set(DEFAULT_MULTI_TAB, bool)
+    [TOGGLE_MULTI_TAB]: (state, bool) => {
+      storage.set(TOGGLE_MULTI_TAB, bool)
       state.multiTab = bool
     }
   },
   actions: {
-    setSidebar ({ commit }, type) {
-      commit('SET_SIDEBAR_TYPE', type)
-    },
-    CloseSidebar ({ commit }) {
-      commit('CLOSE_SIDEBAR')
-    },
-    ToggleDevice ({ commit }, device) {
-      commit('TOGGLE_DEVICE', device)
-    },
-    ToggleTheme ({ commit }, theme) {
-      commit('TOGGLE_THEME', theme)
-    },
-    ToggleLayoutMode ({ commit }, mode) {
-      commit('TOGGLE_LAYOUT_MODE', mode)
-    },
-    ToggleFixedHeader ({ commit }, fixedHeader) {
-      if (!fixedHeader) {
-        commit('TOGGLE_FIXED_HEADER_HIDDEN', false)
-      }
-      commit('TOGGLE_FIXED_HEADER', fixedHeader)
-    },
-    ToggleFixSiderbar ({ commit }, fixSiderbar) {
-      commit('TOGGLE_FIXED_SIDERBAR', fixSiderbar)
-    },
-    ToggleFixedHeaderHidden ({ commit }, show) {
-      commit('TOGGLE_FIXED_HEADER_HIDDEN', show)
-    },
-    ToggleContentWidth ({ commit }, type) {
-      commit('TOGGLE_CONTENT_WIDTH', type)
-    },
-    ToggleColor ({ commit }, color) {
-      commit('TOGGLE_COLOR', color)
-    },
-    ToggleWeak ({ commit }, weakFlag) {
-      commit('TOGGLE_WEAK', weakFlag)
-    },
-    ToggleMultiTab ({ commit }, bool) {
-      commit('TOGGLE_MULTI_TAB', bool)
+    setLang ({ commit }, lang) {
+      return new Promise((resolve, reject) => {
+        commit(APP_LANGUAGE, lang)
+        loadLanguageAsync(lang).then(() => {
+          resolve()
+        }).catch((e) => {
+          reject(e)
+        })
+      })
     }
   }
 }

+ 13 - 10
src/store/mutation-types.js

@@ -1,14 +1,17 @@
 export const ACCESS_TOKEN = 'Access-Token'
-export const SIDEBAR_TYPE = 'SIDEBAR_TYPE'
-export const DEFAULT_THEME = 'DEFAULT_THEME'
-export const DEFAULT_LAYOUT_MODE = 'DEFAULT_LAYOUT_MODE'
-export const DEFAULT_COLOR = 'DEFAULT_COLOR'
-export const DEFAULT_COLOR_WEAK = 'DEFAULT_COLOR_WEAK'
-export const DEFAULT_FIXED_HEADER = 'DEFAULT_FIXED_HEADER'
-export const DEFAULT_FIXED_SIDEMENU = 'DEFAULT_FIXED_SIDEMENU'
-export const DEFAULT_FIXED_HEADER_HIDDEN = 'DEFAULT_FIXED_HEADER_HIDDEN'
-export const DEFAULT_CONTENT_WIDTH_TYPE = 'DEFAULT_CONTENT_WIDTH_TYPE'
-export const DEFAULT_MULTI_TAB = 'DEFAULT_MULTI_TAB'
+
+export const SIDEBAR_TYPE = 'sidebar_type'
+export const TOGGLE_MOBILE_TYPE = 'is_mobile'
+export const TOGGLE_NAV_THEME = 'nav_theme'
+export const TOGGLE_LAYOUT = 'layout'
+export const TOGGLE_FIXED_HEADER = 'fixed_header'
+export const TOGGLE_FIXED_SIDEBAR = 'fixed_sidebar'
+export const TOGGLE_CONTENT_WIDTH = 'content_width'
+export const TOGGLE_HIDE_HEADER = 'auto_hide_header'
+export const TOGGLE_COLOR = 'color'
+export const TOGGLE_WEAK = 'weak'
+export const TOGGLE_MULTI_TAB = 'multi_tab'
+export const APP_LANGUAGE = 'app_language'
 
 export const CONTENT_WIDTH_TYPE = {
   Fluid: 'Fluid',

+ 0 - 33
src/utils/device.js

@@ -1,33 +0,0 @@
-import enquireJs from 'enquire.js'
-
-export const DEVICE_TYPE = {
-  DESKTOP: 'desktop',
-  TABLET: 'tablet',
-  MOBILE: 'mobile'
-}
-
-export const deviceEnquire = function (callback) {
-  const matchDesktop = {
-    match: () => {
-      callback && callback(DEVICE_TYPE.DESKTOP)
-    }
-  }
-
-  const matchLablet = {
-    match: () => {
-      callback && callback(DEVICE_TYPE.TABLET)
-    }
-  }
-
-  const matchMobile = {
-    match: () => {
-      callback && callback(DEVICE_TYPE.MOBILE)
-    }
-  }
-
-  // screen and (max-width: 1087.99px)
-  enquireJs
-    .register('screen and (max-width: 576px)', matchMobile)
-    .register('screen and (min-width: 576px) and (max-width: 1199px)', matchLablet)
-    .register('screen and (min-width: 1200px)', matchDesktop)
-}

+ 0 - 76
src/utils/mixin.js

@@ -1,76 +0,0 @@
-// import Vue from 'vue'
-import { deviceEnquire, DEVICE_TYPE } from '@/utils/device'
-import { mapState } from 'vuex'
-
-// const mixinsComputed = Vue.config.optionMergeStrategies.computed
-// const mixinsMethods = Vue.config.optionMergeStrategies.methods
-
-const mixin = {
-  computed: {
-    ...mapState({
-      layoutMode: state => state.app.layout,
-      navTheme: state => state.app.theme,
-      primaryColor: state => state.app.color,
-      colorWeak: state => state.app.weak,
-      fixedHeader: state => state.app.fixedHeader,
-      fixSiderbar: state => state.app.fixSiderbar,
-      fixSidebar: state => state.app.fixSiderbar,
-      contentWidth: state => state.app.contentWidth,
-      autoHideHeader: state => state.app.autoHideHeader,
-      sidebarOpened: state => state.app.sidebar,
-      multiTab: state => state.app.multiTab
-    })
-  },
-  methods: {
-    isTopMenu () {
-      return this.layoutMode === 'topmenu'
-    },
-    isSideMenu () {
-      return !this.isTopMenu()
-    }
-  }
-}
-
-const mixinDevice = {
-  computed: {
-    ...mapState({
-      device: state => state.app.device
-    })
-  },
-  methods: {
-    isMobile () {
-      return this.device === DEVICE_TYPE.MOBILE
-    },
-    isDesktop () {
-      return this.device === DEVICE_TYPE.DESKTOP
-    },
-    isTablet () {
-      return this.device === DEVICE_TYPE.TABLET
-    }
-  }
-}
-
-const AppDeviceEnquire = {
-  mounted () {
-    const { $store } = this
-    deviceEnquire(deviceType => {
-      switch (deviceType) {
-        case DEVICE_TYPE.DESKTOP:
-          $store.commit('TOGGLE_DEVICE', 'desktop')
-          $store.dispatch('setSidebar', true)
-          break
-        case DEVICE_TYPE.TABLET:
-          $store.commit('TOGGLE_DEVICE', 'tablet')
-          $store.dispatch('setSidebar', false)
-          break
-        case DEVICE_TYPE.MOBILE:
-        default:
-          $store.commit('TOGGLE_DEVICE', 'mobile')
-          $store.dispatch('setSidebar', true)
-          break
-      }
-    })
-  }
-}
-
-export { mixin, AppDeviceEnquire, mixinDevice }

+ 0 - 215
src/views/Home.vue

@@ -1,215 +0,0 @@
-<template>
-  <div class="home">
-    <div class="banner">
-      <img alt="Vue logo" style="width: 64px; height: 64px" src="../assets/logo.png">
-      <h3 style="margin-top: 1rem">Welcome to Your Vue.js App</h3>
-    </div>
-
-    <br/>
-
-    <h2># Trend 组件 </h2>
-
-    <a-divider> 正常 </a-divider>
-
-    <a-card>
-
-      <trend flag="up" style="margin-right: 16px;">
-        <span slot="term">工资</span>
-        5%
-      </trend>
-      <trend flag="up" style="margin-right: 16px;">
-        <span slot="term">工作量</span>
-        50%
-      </trend>
-      <trend flag="down">
-        <span slot="term">身体状态</span>
-        50%
-      </trend>
-
-    </a-card>
-
-    <a-divider> 颜色反转 </a-divider>
-
-    <a-card style="margin-bottom: 3rem">
-
-      <trend flag="up" :reverse-color="true" style="margin-right: 16px;">
-        <span slot="term">工资</span>
-        5%
-      </trend>
-      <trend flag="down" :reverse-color="true" style="margin-right: 16px;">
-        <span slot="term">工作量</span>
-        50%
-      </trend>
-
-    </a-card>
-
-    <h2># AvatarList 组件 </h2>
-
-    <a-divider> AvatarList </a-divider>
-    <a-card style="margin-bottom: 3rem">
-      <avatar-list :max-length="3">
-        <avatar-list-item tips="Jake" src="https://gw.alipayobjects.com/zos/rmsportal/zOsKZmFRdUtvpqCImOVY.png" />
-        <avatar-list-item tips="Andy" src="https://gw.alipayobjects.com/zos/rmsportal/sfjbOqnsXXJgNCjCzDBL.png" />
-        <avatar-list-item tips="Niko" src="https://gw.alipayobjects.com/zos/rmsportal/kZzEzemZyKLKFsojXItE.png" />
-        <avatar-list-item tips="Niko" src="https://gw.alipayobjects.com/zos/rmsportal/kZzEzemZyKLKFsojXItE.png" />
-        <avatar-list-item tips="Niko" src="https://gw.alipayobjects.com/zos/rmsportal/kZzEzemZyKLKFsojXItE.png" />
-        <avatar-list-item tips="Niko" src="https://gw.alipayobjects.com/zos/rmsportal/kZzEzemZyKLKFsojXItE.png" />
-        <avatar-list-item tips="Niko" src="https://gw.alipayobjects.com/zos/rmsportal/kZzEzemZyKLKFsojXItE.png" />
-
-      </avatar-list>
-
-      <a-divider type="vertical" style="margin: 0 16px" />
-
-      <avatar-list size="mini">
-        <avatar-list-item tips="Jake" src="https://gw.alipayobjects.com/zos/rmsportal/zOsKZmFRdUtvpqCImOVY.png" />
-        <avatar-list-item tips="Andy" src="https://gw.alipayobjects.com/zos/rmsportal/sfjbOqnsXXJgNCjCzDBL.png" />
-        <avatar-list-item tips="Niko" src="https://gw.alipayobjects.com/zos/rmsportal/kZzEzemZyKLKFsojXItE.png" />
-      </avatar-list>
-    </a-card>
-
-    <h2># CountDown 组件 </h2>
-
-    <a-divider> CountDown </a-divider>
-    <a-card style="margin-bottom: 3rem">
-      <count-down
-        style="font-size: 2rem"
-        :target="new Date().getTime() + 3000000"
-        :on-end="onEndHandle">
-      </count-down>
-
-      <a-divider type="vertical" style="margin: 0 16px" />
-
-      <count-down
-        style="font-size: 2rem"
-        :target="new Date().getTime() + 10000"
-        :on-end="onEndHandle2">
-      </count-down>
-    </a-card>
-
-    <h2># Ellipsis 组件 </h2>
-
-    <a-divider> Ellipsis </a-divider>
-    <a-card style="margin-bottom: 3rem">
-      <ellipsis :length="100" tooltip>
-        There were injuries alleged in three cases in 2015, and a
-        fourth incident in September, according to the safety recall report. After meeting with US regulators in October, the firm decided to issue a voluntary recall.
-      </ellipsis>
-    </a-card>
-
-    <h2># NumberInfo 组件 </h2>
-
-    <a-divider> NumberInfo </a-divider>
-    <a-card style="margin-bottom: 3rem">
-      <number-info
-        :sub-title="() => { return 'Visits this week' }"
-        :total="12321"
-        status="up"
-        :sub-total="17.1"></number-info>
-    </a-card>
-
-    <h2># TagSelect 组件 </h2>
-
-    <a-divider> TagSelect </a-divider>
-    <a-card style="margin-bottom: 3rem">
-      <tag-select>
-        <tag-select-option value="cat1">类目1</tag-select-option>
-        <tag-select-option value="cat2">类目2</tag-select-option>
-        <tag-select-option value="cat3">类目3</tag-select-option>
-        <tag-select-option value="cat4">类目4</tag-select-option>
-        <tag-select-option value="cat5">类目5</tag-select-option>
-        <tag-select-option value="cat6">类目6</tag-select-option>
-        <tag-select-option value="cat7">类目7</tag-select-option>
-      </tag-select>
-    </a-card>
-
-    <h2># DescriptionList 组件 </h2>
-
-    <a-divider> DescriptionList </a-divider>
-    <a-card style="margin-bottom: 3rem">
-      <description-list title="组名称" size="small">
-        <description-list-item term="负责人">林东东</description-list-item>
-        <description-list-item term="角色码">1234567</description-list-item>
-        <description-list-item term="所属部门">XX公司-YY部</description-list-item>
-        <description-list-item term="过期时间">2018-08-08</description-list-item>
-        <description-list-item term="描述">这段描述很长很长很长很长很长很长很长很长很长很长很长很长很长很长...</description-list-item>
-      </description-list>
-    </a-card>
-
-    <a-divider> TagCloud </a-divider>
-    <a-card style="margin-bottom: 3rem">
-      <tag-cloud :tag-list="tagCloudData"></tag-cloud>
-    </a-card>
-  </div>
-</template>
-
-<script>
-// @ is an alias to /src
-
-import Trend from '@/components/Trend'
-import AvatarList from '@/components/AvatarList'
-import CountDown from '@/components/CountDown/CountDown'
-import Ellipsis from '@/components/Ellipsis'
-import NumberInfo from '@/components/NumberInfo'
-import TagSelect from '@/components/TagSelect'
-import { DescriptionList, TagCloud } from '@/components/'
-
-const AvatarListItem = AvatarList.AvatarItem
-const TagSelectOption = TagSelect.Option
-
-const DescriptionListItem = DescriptionList.Item
-
-export default {
-  name: 'Home',
-  components: {
-    NumberInfo,
-    Ellipsis,
-    CountDown,
-    Trend,
-    AvatarList,
-    AvatarListItem,
-    TagSelect,
-    TagSelectOption,
-    TagCloud,
-    DescriptionList,
-    DescriptionListItem
-  },
-  data () {
-    return {
-      targetTime: new Date().getTime() + 3900000,
-      tagCloudData: []
-    }
-  },
-  created () {
-    this.getTagCloudData()
-  },
-  methods: {
-    onEndHandle () {
-      this.$message.success('CountDown callback!!!')
-    },
-    onEndHandle2 () {
-      this.$notification.open({
-        message: 'Notification Title',
-        description: 'This is the content of the notification. This is the content of the notification. This is the content of the notification.'
-      })
-    },
-    getTagCloudData () {
-      this.$http.get('/data/antv/tag-cloud').then(res => {
-        this.tagCloudData = res.result
-      })
-    }
-  }
-}
-</script>
-
-<style scoped>
-  .home {
-    width: 900px;
-    margin: 0 auto;
-    padding: 25px 0;
-  }
-  .home > .banner {
-    text-align: center;
-    padding: 25px 0;
-    margin: 25px 0;
-  }
-</style>

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

@@ -1,5 +1,5 @@
 <template>
-  <div class="page-header-index-wide">
+  <div>
     <a-row :gutter="24">
       <a-col :sm="24" :md="12" :xl="6" :style="{ marginBottom: '24px' }">
         <chart-card :loading="loading" title="总销售额" total="¥126,560">

+ 107 - 0
src/views/dashboard/Workplace.less

@@ -0,0 +1,107 @@
+@import '~ant-design-vue/es/style/themes/default.less';
+
+.text-overflow() {
+  overflow: hidden;
+  white-space: nowrap;
+  text-overflow: ellipsis;
+  word-break: break-all;
+}
+
+// mixins for clearfix
+// ------------------------
+.clearfix() {
+  zoom: 1;
+  &::before,
+  &::after {
+    display: table;
+    content: ' ';
+  }
+  &::after {
+    clear: both;
+    height: 0;
+    font-size: 0;
+    visibility: hidden;
+  }
+}
+
+
+.page-header-content {
+  display: flex;
+
+  .avatar {
+    flex: 0 1 72px;
+
+    & > span {
+      display: block;
+      width: 72px;
+      height: 72px;
+      border-radius: 72px;
+    }
+  }
+
+  .content {
+    position: relative;
+    top: 4px;
+    flex: 1 1 auto;
+    margin-left: 24px;
+    color: @text-color-secondary;
+    line-height: 22px;
+
+    .content-title {
+      margin-bottom: 12px;
+      color: @heading-color;
+      font-weight: 500;
+      font-size: 20px;
+      line-height: 28px;
+    }
+  }
+}
+
+.extra-content {
+  .clearfix();
+  float: right;
+  white-space: nowrap;
+
+  .stat-item {
+    position: relative;
+    display: inline-block;
+    padding: 0 32px;
+
+    > p:first-child {
+      margin-bottom: 4px;
+      color: @text-color-secondary;
+      font-size: @font-size-base;
+      line-height: 22px;
+    }
+
+    > p {
+      margin: 0;
+      color: @heading-color;
+      font-size: 30px;
+      line-height: 38px;
+
+      > span {
+        color: @text-color-secondary;
+        font-size: 20px;
+      }
+    }
+
+    &::after {
+      position: absolute;
+      top: 8px;
+      right: 0;
+      width: 1px;
+      height: 40px;
+      background-color: @border-color-split;
+      content: '';
+    }
+
+    &:last-child {
+      padding-right: 0;
+
+      &::after {
+        display: none;
+      }
+    }
+  }
+}

+ 55 - 28
src/views/dashboard/Workplace.vue

@@ -1,22 +1,31 @@
 <template>
-  <page-view :avatar="avatar" :title="false">
-    <div slot="headerContent">
-      <div class="title">{{ timeFix }},{{ user.name }}<span class="welcome-text">,{{ welcome }}</span></div>
-      <div>前端工程师 | 蚂蚁金服 - 某某某事业群 - VUE平台</div>
-    </div>
-    <div slot="extra">
-      <a-row class="more-info">
-        <a-col :span="8">
-          <head-info title="项目" content="56" :center="false" :bordered="false"/>
-        </a-col>
-        <a-col :span="8">
-          <head-info title="团队排名" content="8/24" :center="false" :bordered="false"/>
-        </a-col>
-        <a-col :span="8">
-          <head-info title="项目数" content="2,223" :center="false" />
-        </a-col>
-      </a-row>
-    </div>
+  <page-header-wrapper>
+    <template v-slot:content>
+      <div class="page-header-content">
+        <div class="avatar">
+          <a-avatar size="large" :src="currentUser.avatar"/>
+        </div>
+        <div class="content">
+          <div class="content-title">
+            {{ timeFix }},{{ user.name }}<span class="welcome-text">,{{ welcome }}</span>
+          </div>
+          <div>前端工程师 | 蚂蚁金服 - 某某某事业群 - VUE平台</div>
+        </div>
+      </div>
+    </template>
+    <template v-slot:extraContent>
+      <div class="extra-content">
+        <div class="stat-item">
+          <a-statistic title="项目数" :value="56" />
+        </div>
+        <div class="stat-item">
+          <a-statistic title="团队内排名" :value="8" suffix="/ 24" />
+        </div>
+        <div class="stat-item">
+          <a-statistic title="项目访问" :value="2223" />
+        </div>
+      </div>
+    </template>
 
     <div>
       <a-row :gutter="24">
@@ -54,7 +63,7 @@
             <a-list>
               <a-list-item :key="index" v-for="(item, index) in activities">
                 <a-list-item-meta>
-                  <a-avatar slot="avatar" :src="item.user.avatar" />
+                  <a-avatar slot="avatar" :src="item.user.avatar"/>
                   <div slot="title">
                     <span>{{ item.user.nickname }}</span>&nbsp;
                     在&nbsp;<a href="#">{{ item.project.name }}</a>&nbsp;
@@ -85,10 +94,15 @@
               <a-button size="small" type="primary" ghost icon="plus">添加</a-button>
             </div>
           </a-card>
-          <a-card title="XX 指数" style="margin-bottom: 24px" :loading="radarLoading" :bordered="false" :body-style="{ padding: 0 }">
+          <a-card
+            title="XX 指数"
+            style="margin-bottom: 24px"
+            :loading="radarLoading"
+            :bordered="false"
+            :body-style="{ padding: 0 }">
             <div style="min-height: 400px;">
               <!-- :scale="scale" :axis1Opts="axis1Opts" :axis2Opts="axis2Opts"  -->
-              <radar :data="radarData" />
+              <radar :data="radarData"/>
             </div>
           </a-card>
           <a-card :loading="loading" title="团队" :bordered="false">
@@ -96,7 +110,7 @@
               <a-row>
                 <a-col :span="12" v-for="(item, index) in teams" :key="index">
                   <a>
-                    <a-avatar size="small" :src="item.avatar" />
+                    <a-avatar size="small" :src="item.avatar"/>
                     <span class="member">{{ item.name }}</span>
                   </a>
                 </a-col>
@@ -106,15 +120,13 @@
         </a-col>
       </a-row>
     </div>
-  </page-view>
+  </page-header-wrapper>
 </template>
 
 <script>
 import { timeFix } from '@/utils/util'
 import { mapState } from 'vuex'
-
-import { PageView } from '@/layouts'
-import HeadInfo from '@/components/tools/HeadInfo'
+import { PageHeaderWrapper } from '@ant-design-vue/pro-layout'
 import { Radar } from '@/components'
 
 import { getRoleList, getServiceList } from '@/api/manage'
@@ -124,8 +136,7 @@ const DataSet = require('@antv/data-set')
 export default {
   name: 'Workplace',
   components: {
-    PageView,
-    HeadInfo,
+    PageHeaderWrapper,
     Radar
   },
   data () {
@@ -184,6 +195,12 @@ export default {
       nickname: (state) => state.user.nickname,
       welcome: (state) => state.user.welcome
     }),
+    currentUser () {
+      return {
+        name: 'Serati Ma',
+        avatar: 'https://gw.alipayobjects.com/zos/antfincdn/XAosXuNZyF/BiazfanxmamNRoxxVxka.png'
+      }
+    },
     userInfo () {
       return this.$store.getters.userInfo
     }
@@ -248,6 +265,8 @@ export default {
 </script>
 
 <style lang="less" scoped>
+  @import "./Workplace.less";
+
   .project-list {
 
     .card-title {
@@ -267,12 +286,14 @@ export default {
         }
       }
     }
+
     .card-description {
       color: rgba(0, 0, 0, 0.45);
       height: 44px;
       line-height: 22px;
       overflow: hidden;
     }
+
     .project-item {
       display: flex;
       margin-top: 8px;
@@ -280,6 +301,7 @@ export default {
       font-size: 12px;
       height: 20px;
       line-height: 20px;
+
       a {
         color: rgba(0, 0, 0, 0.45);
         display: inline-block;
@@ -289,12 +311,14 @@ export default {
           color: #1890ff;
         }
       }
+
       .datetime {
         color: rgba(0, 0, 0, 0.25);
         flex: 0 0 auto;
         float: right;
       }
     }
+
     .ant-card-meta-description {
       color: rgba(0, 0, 0, 0.45);
       height: 44px;
@@ -306,6 +330,7 @@ export default {
   .item-group {
     padding: 20px 0 8px 24px;
     font-size: 0;
+
     a {
       color: rgba(0, 0, 0, 0.65);
       display: inline-block;
@@ -321,6 +346,7 @@ export default {
       margin: 12px 0;
       line-height: 24px;
       height: 24px;
+
       .member {
         font-size: 14px;
         color: rgba(0, 0, 0, .65);
@@ -331,6 +357,7 @@ export default {
         transition: all 0.3s;
         display: inline-block;
       }
+
       &:hover {
         span {
           color: #1890ff;

+ 8 - 10
src/views/exception/403.vue

@@ -1,17 +1,15 @@
 <template>
-  <exception-page type="403" />
+  <a-result status="403" title="403" sub-title="Sorry, you don't have access to this page.">
+    <template #extra>
+      <a-button type="primary">
+        Back Home
+      </a-button>
+    </template>
+  </a-result>
 </template>
 
 <script>
-import { ExceptionPage } from '@/components'
-
 export default {
-  components: {
-    ExceptionPage
-  }
+  name: 'Exception403'
 }
 </script>
-
-<style scoped>
-
-</style>

+ 8 - 10
src/views/exception/404.vue

@@ -1,17 +1,15 @@
 <template>
-  <exception-page type="404" />
+  <a-result status="404" title="404" sub-title="Sorry, the page you visited does not exist.">
+    <template #extra>
+      <a-button type="primary">
+        Back Home
+      </a-button>
+    </template>
+  </a-result>
 </template>
 
 <script>
-import { ExceptionPage } from '@/components'
-
 export default {
-  components: {
-    ExceptionPage
-  }
+  name: 'Exception404'
 }
 </script>
-
-<style scoped>
-
-</style>

+ 9 - 11
src/views/exception/500.vue

@@ -1,17 +1,15 @@
 <template>
-  <exception-page type="500" />
+  <a-result status="500" title="500" sub-title="Sorry, the server is reporting an error.">
+    <template #extra>
+      <a-button type="primary">
+        Back Home
+      </a-button>
+    </template>
+  </a-result>
 </template>
 
 <script>
-import { ExceptionPage } from '@/components'
-
-export default {
-  components: {
-    ExceptionPage
+  export default {
+    name: 'Exception500'
   }
-}
 </script>
-
-<style scoped>
-
-</style>

+ 0 - 138
src/views/form/BasicForm.vue

@@ -1,138 +0,0 @@
-<template>
-  <a-card :body-style="{padding: '24px 32px'}" :bordered="false">
-    <a-form @submit="handleSubmit" :form="form">
-      <a-form-item
-        label="标题"
-        :labelCol="{lg: {span: 7}, sm: {span: 7}}"
-        :wrapperCol="{lg: {span: 10}, sm: {span: 17} }">
-        <a-input
-          v-decorator="[
-            'name',
-            {rules: [{ required: true, message: '请输入标题' }]}
-          ]"
-          name="name"
-          placeholder="给目标起个名字" />
-      </a-form-item>
-      <a-form-item
-        label="起止日期"
-        :labelCol="{lg: {span: 7}, sm: {span: 7}}"
-        :wrapperCol="{lg: {span: 10}, sm: {span: 17} }">
-        <a-range-picker
-          name="buildTime"
-          style="width: 100%"
-          v-decorator="[
-            'buildTime',
-            {rules: [{ required: true, message: '请选择起止日期' }]}
-          ]" />
-      </a-form-item>
-      <a-form-item
-        label="目标描述"
-        :labelCol="{lg: {span: 7}, sm: {span: 7}}"
-        :wrapperCol="{lg: {span: 10}, sm: {span: 17} }">
-        <a-textarea
-          rows="4"
-          placeholder="请输入你阶段性工作目标"
-          v-decorator="[
-            'description',
-            {rules: [{ required: true, message: '请输入目标描述' }]}
-          ]" />
-      </a-form-item>
-      <a-form-item
-        label="衡量标准"
-        :labelCol="{lg: {span: 7}, sm: {span: 7}}"
-        :wrapperCol="{lg: {span: 10}, sm: {span: 17} }">
-        <a-textarea
-          rows="4"
-          placeholder="请输入衡量标准"
-          v-decorator="[
-            'type',
-            {rules: [{ required: true, message: '请输入衡量标准' }]}
-          ]" />
-      </a-form-item>
-      <a-form-item
-        label="客户"
-        :labelCol="{lg: {span: 7}, sm: {span: 7}}"
-        :wrapperCol="{lg: {span: 10}, sm: {span: 17} }">
-        <a-input
-          placeholder="请描述你服务的客户,内部客户直接 @姓名/工号"
-          v-decorator="[
-            'customer',
-            {rules: [{ required: true, message: '请描述你服务的客户' }]}
-          ]" />
-      </a-form-item>
-      <a-form-item
-        label="邀评人"
-        :labelCol="{lg: {span: 7}, sm: {span: 7}}"
-        :wrapperCol="{lg: {span: 10}, sm: {span: 17} }"
-        :required="false"
-      >
-        <a-input placeholder="请直接 @姓名/工号,最多可邀请 5 人" />
-      </a-form-item>
-      <a-form-item
-        label="权重"
-        :labelCol="{lg: {span: 7}, sm: {span: 7}}"
-        :wrapperCol="{lg: {span: 10}, sm: {span: 17} }"
-        :required="false"
-      >
-        <a-input-number :min="0" :max="100" />
-        <span> %</span>
-      </a-form-item>
-      <a-form-item
-        label="目标公开"
-        :labelCol="{lg: {span: 7}, sm: {span: 7}}"
-        :wrapperCol="{lg: {span: 10}, sm: {span: 17} }"
-        :required="false"
-        help="客户、邀评人默认被分享"
-      >
-        <a-radio-group v-model="value">
-          <a-radio :value="1">公开</a-radio>
-          <a-radio :value="2">部分公开</a-radio>
-          <a-radio :value="3">不公开</a-radio>
-        </a-radio-group>
-        <a-form-item>
-          <a-select mode="multiple" v-if="value === 2">
-            <a-select-option value="4">同事一</a-select-option>
-            <a-select-option value="5">同事二</a-select-option>
-            <a-select-option value="6">同事三</a-select-option>
-          </a-select>
-        </a-form-item>
-      </a-form-item>
-      <a-form-item
-        :wrapperCol="{ span: 24 }"
-        style="text-align: center"
-      >
-        <a-button htmlType="submit" type="primary">提交</a-button>
-        <a-button style="margin-left: 8px">保存</a-button>
-      </a-form-item>
-    </a-form>
-  </a-card>
-</template>
-
-<script>
-export default {
-  name: 'BaseForm',
-  data () {
-    return {
-      description: '表单页用于向用户收集或验证信息,基础表单常见于数据项较少的表单场景。',
-      value: 1,
-
-      // form
-      form: this.$form.createForm(this)
-
-    }
-  },
-  methods: {
-
-    // handler
-    handleSubmit (e) {
-      e.preventDefault()
-      this.form.validateFields((err, values) => {
-        if (!err) {
-          // eslint-disable-next-line no-console
-          console.log('Received values of form: ', values)
-        }
-      })
-    }
-  }
-}
-</script>

+ 5 - 6
src/views/form/advancedForm/AdvancedForm.vue

@@ -1,5 +1,5 @@
 <template>
-  <div>
+  <page-header-wrapper content="高级表单常见于一次性输入和提交大批量数据的场景">
     <a-card class="card" title="仓库管理" :bordered="false">
       <repository-form ref="repository" :showSubmit="false" />
     </a-card>
@@ -54,7 +54,7 @@
     </a-card>
 
     <!-- fixed footer toolbar -->
-    <footer-tool-bar :style="{ width: isSideMenu() && isDesktop() ? `calc(100% - ${sidebarOpened ? 256 : 80}px)` : '100%'}">
+    <footer-tool-bar :is-mobile="isMobile" :collapsed="sideCollapsed">
       <span class="popover-wrapper">
         <a-popover title="表单校验信息" overlayClassName="antd-pro-pages-forms-style-errorPopover" trigger="click" :getPopupContainer="trigger => trigger.parentNode">
           <template slot="content">
@@ -71,14 +71,14 @@
       </span>
       <a-button type="primary" @click="validate" :loading="loading">提交</a-button>
     </footer-tool-bar>
-  </div>
+  </page-header-wrapper>
 </template>
 
 <script>
 import RepositoryForm from './RepositoryForm'
 import TaskForm from './TaskForm'
 import FooterToolBar from '@/components/FooterToolbar'
-import { mixin, mixinDevice } from '@/utils/mixin'
+import { baseMixin } from '@/store/app-mixin'
 
 const fieldLabels = {
   name: '仓库名',
@@ -97,7 +97,7 @@ const fieldLabels = {
 
 export default {
   name: 'AdvancedForm',
-  mixins: [mixin, mixinDevice],
+  mixins: [baseMixin],
   components: {
     FooterToolBar,
     RepositoryForm,
@@ -105,7 +105,6 @@ export default {
   },
   data () {
     return {
-      description: '高级表单常见于一次性输入和提交大批量数据的场景。',
       loading: false,
       memberLoading: false,
 

+ 133 - 0
src/views/form/basicForm/index.vue

@@ -0,0 +1,133 @@
+<template>
+  <page-header-wrapper content="表单页用于向用户收集或验证信息,基础表单常见于数据项较少的表单场景。">
+    <a-card :body-style="{padding: '24px 32px'}" :bordered="false">
+      <a-form @submit="handleSubmit" :form="form">
+        <a-form-item
+          label="标题"
+          :labelCol="{lg: {span: 7}, sm: {span: 7}}"
+          :wrapperCol="{lg: {span: 10}, sm: {span: 17} }">
+          <a-input
+            v-decorator="[
+              'name',
+              {rules: [{ required: true, message: '请输入标题' }]}
+            ]"
+            name="name"
+            placeholder="给目标起个名字" />
+        </a-form-item>
+        <a-form-item
+          label="起止日期"
+          :labelCol="{lg: {span: 7}, sm: {span: 7}}"
+          :wrapperCol="{lg: {span: 10}, sm: {span: 17} }">
+          <a-range-picker
+            name="buildTime"
+            style="width: 100%"
+            v-decorator="[
+              'buildTime',
+              {rules: [{ required: true, message: '请选择起止日期' }]}
+            ]" />
+        </a-form-item>
+        <a-form-item
+          label="目标描述"
+          :labelCol="{lg: {span: 7}, sm: {span: 7}}"
+          :wrapperCol="{lg: {span: 10}, sm: {span: 17} }">
+          <a-textarea
+            rows="4"
+            placeholder="请输入你阶段性工作目标"
+            v-decorator="[
+              'description',
+              {rules: [{ required: true, message: '请输入目标描述' }]}
+            ]" />
+        </a-form-item>
+        <a-form-item
+          label="衡量标准"
+          :labelCol="{lg: {span: 7}, sm: {span: 7}}"
+          :wrapperCol="{lg: {span: 10}, sm: {span: 17} }">
+          <a-textarea
+            rows="4"
+            placeholder="请输入衡量标准"
+            v-decorator="[
+              'type',
+              {rules: [{ required: true, message: '请输入衡量标准' }]}
+            ]" />
+        </a-form-item>
+        <a-form-item
+          label="客户"
+          :labelCol="{lg: {span: 7}, sm: {span: 7}}"
+          :wrapperCol="{lg: {span: 10}, sm: {span: 17} }">
+          <a-input
+            placeholder="请描述你服务的客户,内部客户直接 @姓名/工号"
+            v-decorator="[
+              'customer',
+              {rules: [{ required: true, message: '请描述你服务的客户' }]}
+            ]" />
+        </a-form-item>
+        <a-form-item
+          label="邀评人"
+          :labelCol="{lg: {span: 7}, sm: {span: 7}}"
+          :wrapperCol="{lg: {span: 10}, sm: {span: 17} }"
+          :required="false"
+        >
+          <a-input placeholder="请直接 @姓名/工号,最多可邀请 5 人" />
+        </a-form-item>
+        <a-form-item
+          label="权重"
+          :labelCol="{lg: {span: 7}, sm: {span: 7}}"
+          :wrapperCol="{lg: {span: 10}, sm: {span: 17} }"
+          :required="false"
+        >
+          <a-input-number :min="0" :max="100" />
+          <span> %</span>
+        </a-form-item>
+        <a-form-item
+          label="目标公开"
+          :labelCol="{lg: {span: 7}, sm: {span: 7}}"
+          :wrapperCol="{lg: {span: 10}, sm: {span: 17} }"
+          :required="false"
+          help="客户、邀评人默认被分享"
+        >
+          <a-radio-group v-decorator="['target', { initialValue: 1 }]">
+            <a-radio :value="1">公开</a-radio>
+            <a-radio :value="2">部分公开</a-radio>
+            <a-radio :value="3">不公开</a-radio>
+          </a-radio-group>
+          <a-form-item v-show="form.getFieldValue('target') === 2">
+            <a-select mode="multiple">
+              <a-select-option value="4">同事一</a-select-option>
+              <a-select-option value="5">同事二</a-select-option>
+              <a-select-option value="6">同事三</a-select-option>
+            </a-select>
+          </a-form-item>
+        </a-form-item>
+        <a-form-item
+          :wrapperCol="{ span: 24 }"
+          style="text-align: center"
+        >
+          <a-button htmlType="submit" type="primary">提交</a-button>
+          <a-button style="margin-left: 8px">保存</a-button>
+        </a-form-item>
+      </a-form>
+    </a-card>
+  </page-header-wrapper>
+</template>
+
+<script>
+export default {
+  name: 'BaseForm',
+  data () {
+    return {
+      form: this.$form.createForm(this)
+    }
+  },
+  methods: {
+    // handler
+    handleSubmit (e) {
+      e.preventDefault()
+      this.form.validateFields((err, values) => {
+        if (!err) {
+          console.log('Received values of form: ', values)
+        }
+      })
+    }
+  }
+}
+</script>

+ 2 - 2
src/views/form/stepForm/Step3.vue

@@ -1,7 +1,7 @@
 <template>
   <div>
-    <a-form style="margin: 40px auto 0;">
-      <a-result title="操作成功" :is-success="true" sub-title="预计两小时内到账" style="max-width: 560px;">
+    <a-form>
+      <a-result title="操作成功" :is-success="true" sub-title="预计两小时内到账" style="max-width: 560px; margin: 40px auto 0;">
         <div class="information">
           <a-row>
             <a-col :sm="8" :xs="24">付款账户:</a-col>

+ 18 - 14
src/views/form/stepForm/StepForm.vue

@@ -1,16 +1,22 @@
 <template>
-  <a-card :bordered="false">
-    <a-steps class="steps" :current="currentTab">
-      <a-step title="填写转账信息" />
-      <a-step title="确认转账信息" />
-      <a-step title="完成" />
-    </a-steps>
-    <div class="content">
-      <step1 v-if="currentTab === 0" @nextStep="nextStep"/>
-      <step2 v-if="currentTab === 1" @nextStep="nextStep" @prevStep="prevStep"/>
-      <step3 v-if="currentTab === 2" @prevStep="prevStep" @finish="finish"/>
-    </div>
-  </a-card>
+  <page-header-wrapper>
+    <!-- PageHeader 第二种使用方式 (v-slot) -->
+    <template v-slot:content>
+      将一个冗长或用户不熟悉的表单任务分成多个步骤,指导用户完成。
+    </template>
+    <a-card :bordered="false">
+      <a-steps class="steps" :current="currentTab">
+        <a-step title="填写转账信息" />
+        <a-step title="确认转账信息" />
+        <a-step title="完成" />
+      </a-steps>
+      <div class="content">
+        <step1 v-if="currentTab === 0" @nextStep="nextStep"/>
+        <step2 v-if="currentTab === 1" @nextStep="nextStep" @prevStep="prevStep"/>
+        <step3 v-if="currentTab === 2" @prevStep="prevStep" @finish="finish"/>
+      </div>
+    </a-card>
+  </page-header-wrapper>
 </template>
 
 <script>
@@ -27,9 +33,7 @@ export default {
   },
   data () {
     return {
-      description: '将一个冗长或用户不熟悉的表单任务分成多个步骤,指导用户完成。',
       currentTab: 0,
-
       // form
       form: null
     }

+ 19 - 18
src/views/list/CardList.vue

@@ -1,5 +1,15 @@
 <template>
-  <div class="card-list" ref="content">
+  <page-header-wrapper
+    :tab-list="tabList"
+    :tab-active-key="tabActiveKey"
+    :tab-change="(key) => {
+      this.tabActiveKey = key
+    }"
+    content="段落示意:蚂蚁金服务设计平台 ant.design,用最小的工作量,无缝接入蚂蚁金服生态, 提供跨越设计与开发的体验解决方案。"
+  >
+    <template v-slot:extraContent>
+      <div style="width: 155px; margin-top: -20px;"><img style="width: 100%" :src="extraImage" /></div>
+    </template>
     <a-list
       rowKey="id"
       :grid="{gutter: 24, lg: 3, md: 2, sm: 1, xs: 1}"
@@ -27,7 +37,7 @@
         </template>
       </a-list-item>
     </a-list>
-  </div>
+  </page-header-wrapper>
 </template>
 
 <script>
@@ -46,23 +56,14 @@ for (let i = 0; i < 11; i++) {
 export default {
   name: 'CardList',
   data () {
+    this.tabList = [
+      { key: 'tab1', tab: '快速开始' },
+      { key: 'tab2', tab: '产品简介' },
+      { key: 'tab3', tab: '产品文档' }
+    ]
     return {
-      description: '段落示意:蚂蚁金服务设计平台 ant.design,用最小的工作量,无缝接入蚂蚁金服生态, 提供跨越设计与开发的体验解决方案。',
-      linkList: [
-        {
-          icon: 'rocket',
-          href: '#',
-          title: '快速开始',
-          // 回调,可不写
-          callback: () => {
-            // this.$message.info('快速开始被单击')
-            this.testFun()
-            console.log('call[\'快速开始\'] action')
-          }
-        },
-        { icon: 'info-circle-o', href: '#', title: '产品简介' },
-        { icon: 'file-text', href: '#', title: '产品文档' }
-      ],
+      tabActiveKey: 'tab1',
+
       extraImage: 'https://gw.alipayobjects.com/zos/rmsportal/RzwpdLnhmvDJToTdfDPe.png',
       dataSource
     }

+ 2 - 2
src/views/list/StandardList.vue

@@ -1,5 +1,5 @@
 <template>
-  <div>
+  <page-header-wrapper>
     <a-card :bordered="false">
       <a-row>
         <a-col :sm="8" :xs="24">
@@ -66,7 +66,7 @@
         </a-list-item>
       </a-list>
     </a-card>
-  </div>
+  </page-header-wrapper>
 </template>
 
 <script>

+ 95 - 93
src/views/list/TableList.vue

@@ -1,112 +1,114 @@
 <template>
-  <a-card :bordered="false">
-    <div class="table-page-search-wrapper">
-      <a-form layout="inline">
-        <a-row :gutter="48">
-          <a-col :md="8" :sm="24">
-            <a-form-item label="规则编号">
-              <a-input v-model="queryParam.id" placeholder=""/>
-            </a-form-item>
-          </a-col>
-          <a-col :md="8" :sm="24">
-            <a-form-item label="使用状态">
-              <a-select v-model="queryParam.status" placeholder="请选择" default-value="0">
-                <a-select-option value="0">全部</a-select-option>
-                <a-select-option value="1">关闭</a-select-option>
-                <a-select-option value="2">运行中</a-select-option>
-              </a-select>
-            </a-form-item>
-          </a-col>
-          <template v-if="advanced">
+  <page-header-wrapper>
+    <a-card :bordered="false">
+      <div class="table-page-search-wrapper">
+        <a-form layout="inline">
+          <a-row :gutter="48">
             <a-col :md="8" :sm="24">
-              <a-form-item label="调用次数">
-                <a-input-number v-model="queryParam.callNo" style="width: 100%"/>
-              </a-form-item>
-            </a-col>
-            <a-col :md="8" :sm="24">
-              <a-form-item label="更新日期">
-                <a-date-picker v-model="queryParam.date" style="width: 100%" placeholder="请输入更新日期"/>
+              <a-form-item label="规则编号">
+                <a-input v-model="queryParam.id" placeholder=""/>
               </a-form-item>
             </a-col>
             <a-col :md="8" :sm="24">
               <a-form-item label="使用状态">
-                <a-select v-model="queryParam.useStatus" placeholder="请选择" default-value="0">
+                <a-select v-model="queryParam.status" placeholder="请选择" default-value="0">
                   <a-select-option value="0">全部</a-select-option>
                   <a-select-option value="1">关闭</a-select-option>
                   <a-select-option value="2">运行中</a-select-option>
                 </a-select>
               </a-form-item>
             </a-col>
-            <a-col :md="8" :sm="24">
-              <a-form-item label="使用状态">
-                <a-select placeholder="请选择" default-value="0">
-                  <a-select-option value="0">全部</a-select-option>
-                  <a-select-option value="1">关闭</a-select-option>
-                  <a-select-option value="2">运行中</a-select-option>
-                </a-select>
-              </a-form-item>
+            <template v-if="advanced">
+              <a-col :md="8" :sm="24">
+                <a-form-item label="调用次数">
+                  <a-input-number v-model="queryParam.callNo" style="width: 100%"/>
+                </a-form-item>
+              </a-col>
+              <a-col :md="8" :sm="24">
+                <a-form-item label="更新日期">
+                  <a-date-picker v-model="queryParam.date" style="width: 100%" placeholder="请输入更新日期"/>
+                </a-form-item>
+              </a-col>
+              <a-col :md="8" :sm="24">
+                <a-form-item label="使用状态">
+                  <a-select v-model="queryParam.useStatus" placeholder="请选择" default-value="0">
+                    <a-select-option value="0">全部</a-select-option>
+                    <a-select-option value="1">关闭</a-select-option>
+                    <a-select-option value="2">运行中</a-select-option>
+                  </a-select>
+                </a-form-item>
+              </a-col>
+              <a-col :md="8" :sm="24">
+                <a-form-item label="使用状态">
+                  <a-select placeholder="请选择" default-value="0">
+                    <a-select-option value="0">全部</a-select-option>
+                    <a-select-option value="1">关闭</a-select-option>
+                    <a-select-option value="2">运行中</a-select-option>
+                  </a-select>
+                </a-form-item>
+              </a-col>
+            </template>
+            <a-col :md="!advanced && 8 || 24" :sm="24">
+              <span class="table-page-search-submitButtons" :style="advanced && { float: 'right', overflow: 'hidden' } || {} ">
+                <a-button type="primary" @click="$refs.table.refresh(true)">查询</a-button>
+                <a-button style="margin-left: 8px" @click="() => queryParam = {}">重置</a-button>
+                <a @click="toggleAdvanced" style="margin-left: 8px">
+                  {{ advanced ? '收起' : '展开' }}
+                  <a-icon :type="advanced ? 'up' : 'down'"/>
+                </a>
+              </span>
             </a-col>
-          </template>
-          <a-col :md="!advanced && 8 || 24" :sm="24">
-            <span class="table-page-search-submitButtons" :style="advanced && { float: 'right', overflow: 'hidden' } || {} ">
-              <a-button type="primary" @click="$refs.table.refresh(true)">查询</a-button>
-              <a-button style="margin-left: 8px" @click="() => queryParam = {}">重置</a-button>
-              <a @click="toggleAdvanced" style="margin-left: 8px">
-                {{ advanced ? '收起' : '展开' }}
-                <a-icon :type="advanced ? 'up' : 'down'"/>
-              </a>
-            </span>
-          </a-col>
-        </a-row>
-      </a-form>
-    </div>
+          </a-row>
+        </a-form>
+      </div>
 
-    <div class="table-operator">
-      <a-button type="primary" icon="plus" @click="$refs.createModal.add()">新建</a-button>
-      <a-button type="dashed" @click="tableOption">{{ optionAlertShow && '关闭' || '开启' }} alert</a-button>
-      <a-dropdown v-action:edit v-if="selectedRowKeys.length > 0">
-        <a-menu slot="overlay">
-          <a-menu-item key="1"><a-icon type="delete" />删除</a-menu-item>
-          <!-- lock | unlock -->
-          <a-menu-item key="2"><a-icon type="lock" />锁定</a-menu-item>
-        </a-menu>
-        <a-button style="margin-left: 8px">
-          批量操作 <a-icon type="down" />
-        </a-button>
-      </a-dropdown>
-    </div>
+      <div class="table-operator">
+        <a-button type="primary" icon="plus" @click="$refs.createModal.add()">新建</a-button>
+        <a-button type="dashed" @click="tableOption">{{ optionAlertShow && '关闭' || '开启' }} alert</a-button>
+        <a-dropdown v-action:edit v-if="selectedRowKeys.length > 0">
+          <a-menu slot="overlay">
+            <a-menu-item key="1"><a-icon type="delete" />删除</a-menu-item>
+            <!-- lock | unlock -->
+            <a-menu-item key="2"><a-icon type="lock" />锁定</a-menu-item>
+          </a-menu>
+          <a-button style="margin-left: 8px">
+            批量操作 <a-icon type="down" />
+          </a-button>
+        </a-dropdown>
+      </div>
 
-    <s-table
-      ref="table"
-      size="default"
-      rowKey="key"
-      :columns="columns"
-      :data="loadData"
-      :alert="options.alert"
-      :rowSelection="options.rowSelection"
-      showPagination="auto"
-    >
-      <span slot="serial" slot-scope="text, record, index">
-        {{ index + 1 }}
-      </span>
-      <span slot="status" slot-scope="text">
-        <a-badge :status="text | statusTypeFilter" :text="text | statusFilter" />
-      </span>
-      <span slot="description" slot-scope="text">
-        <ellipsis :length="4" tooltip>{{ text }}</ellipsis>
-      </span>
+      <s-table
+        ref="table"
+        size="default"
+        rowKey="key"
+        :columns="columns"
+        :data="loadData"
+        :alert="options.alert"
+        :rowSelection="options.rowSelection"
+        showPagination="auto"
+      >
+        <span slot="serial" slot-scope="text, record, index">
+          {{ index + 1 }}
+        </span>
+        <span slot="status" slot-scope="text">
+          <a-badge :status="text | statusTypeFilter" :text="text | statusFilter" />
+        </span>
+        <span slot="description" slot-scope="text">
+          <ellipsis :length="4" tooltip>{{ text }}</ellipsis>
+        </span>
 
-      <span slot="action" slot-scope="text, record">
-        <template>
-          <a @click="handleEdit(record)">配置</a>
-          <a-divider type="vertical" />
-          <a @click="handleSub(record)">订阅报警</a>
-        </template>
-      </span>
-    </s-table>
-    <create-form ref="createModal" @ok="handleOk" />
-    <step-by-step-modal ref="modal" @ok="handleOk"/>
-  </a-card>
+        <span slot="action" slot-scope="text, record">
+          <template>
+            <a @click="handleEdit(record)">配置</a>
+            <a-divider type="vertical" />
+            <a @click="handleSub(record)">订阅报警</a>
+          </template>
+        </span>
+      </s-table>
+      <create-form ref="createModal" @ok="handleOk" />
+      <step-by-step-modal ref="modal" @ok="handleOk"/>
+    </a-card>
+  </page-header-wrapper>
 </template>
 
 <script>

+ 41 - 59
src/views/list/search/SearchLayout.vue

@@ -1,7 +1,20 @@
 <template>
-  <div class="search-content">
+  <page-header-wrapper
+    :tab-list="tabList"
+    :tab-active-key="tabActiveKey"
+    :tab-change="handleTabChange"
+  >
+    <template v-slot:content>
+      <div class="ant-pro-page-header-search">
+        <a-input-search size="large" style="width: 80%; max-width: 522px;">
+          <template v-slot:enterButton>
+            搜索
+          </template>
+        </a-input-search>
+      </div>
+    </template>
     <router-view />
-  </div>
+  </page-header-wrapper>
 </template>
 
 <script>
@@ -9,70 +22,39 @@ export default {
   name: 'SearchLayout',
   data () {
     return {
-      tabs: {
-        items: [
-          {
-            key: '1',
-            title: '文章'
-          },
-          {
-            key: '2',
-            title: '项目'
-          },
-          {
-            key: '3',
-            title: '应用'
-          }
-        ],
-        active: () => {
-          switch (this.$route.path) {
-            case '/list/search/article':
-              return '1'
-            case '/list/search/project':
-              return '2'
-            case '/list/search/application':
-              return '3'
-            default:
-              return '1'
-          }
-        },
-        callback: (key) => {
-          switch (key) {
-            case '1':
-              this.$router.push('/list/search/article')
-              break
-            case '2':
-              this.$router.push('/list/search/project')
-              break
-            case '3':
-              this.$router.push('/list/search/application')
-              break
-            default:
-              this.$router.push('/workplace')
-          }
-        }
-      },
+      tabList: [
+        { key: '1', tab: '文章' },
+        { key: '2', tab: '项目' },
+        { key: '3', tab: '应用' }
+      ],
+      tabActiveKey: '1',
       search: true
     }
-  },
-  computed: {
-
   },
   methods: {
+    handleTabChange (key) {
+      this.tabActiveKey = key
+      switch (key) {
+        case '1':
+          this.$router.push('/list/search/article')
+          break
+        case '2':
+          this.$router.push('/list/search/project')
+          break
+        case '3':
+          this.$router.push('/list/search/application')
+          break
+        default:
+          this.$router.push('/workplace')
+      }
+    }
   }
 }
 </script>
 
 <style lang="less" scoped>
-  .search-head{
-    background-color: #fff;
-    margin: -25px -24px -24px;
-    .search-input{
-      text-align: center;
-      margin-bottom: 16px;
-    }
-  }
-  .search-content{
-    margin-top: 48px;
-  }
+.ant-pro-page-header-search {
+  text-align: center;
+  margin-bottom: 16px;
+}
 </style>

+ 84 - 70
src/views/profile/advanced/Advanced.vue

@@ -1,27 +1,25 @@
-
 <template>
-  <page-view title="单号:234231029431" logo="https://gw.alipayobjects.com/zos/rmsportal/nxkuOJlFJuAUhzlMTCEe.png">
+  <page-header-wrapper
+    title="单号:234231029431"
+    :tab-list="tabList"
+    :tab-active-key="tabActiveKey"
+    @tabChange="handleTabChange"
+  >
+    <template v-slot:content>
+      <a-descriptions size="small" :column="isMobile ? 1 : 2">
+        <a-descriptions-item label="创建人">曲丽丽</a-descriptions-item>
+        <a-descriptions-item label="订购产品">XX 服务</a-descriptions-item>
+        <a-descriptions-item label="创建时间">2017-07-07</a-descriptions-item>
+        <a-descriptions-item label="关联单据">
+          <a href="">12421</a>
+        </a-descriptions-item>
+        <a-descriptions-item label="生效日期">2017-07-07 ~ 2017-08-08</a-descriptions-item>
+        <a-descriptions-item label="备注">请于两个工作日内确认</a-descriptions-item>
+      </a-descriptions>
+    </template>
 
-    <detail-list slot="headerContent" size="small" :col="2" class="detail-layout">
-      <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" class="status-list">
-      <a-col :xs="12" :sm="12">
-        <div class="text">状态</div>
-        <div class="heading">待审批</div>
-      </a-col>
-      <a-col :xs="12" :sm="12">
-        <div class="text">订单金额</div>
-        <div class="heading">¥ 568.08</div>
-      </a-col>
-    </a-row>
     <!-- actions -->
-    <template slot="action">
+    <template v-slot:extra>
       <a-button-group style="margin-right: 4px;">
         <a-button>操作</a-button>
         <a-button>操作</a-button>
@@ -30,8 +28,21 @@
       <a-button type="primary" >主操作</a-button>
     </template>
 
+    <template v-slot:extraContent>
+      <a-row class="status-list">
+        <a-col :xs="12" :sm="12">
+          <div class="text">状态</div>
+          <div class="heading">待审批</div>
+        </a-col>
+        <a-col :xs="12" :sm="12">
+          <div class="text">订单金额</div>
+          <div class="heading">¥ 568.08</div>
+        </a-col>
+      </a-row>
+    </template>
+
     <a-card :bordered="false" title="流程进度">
-      <a-steps :direction="isMobile() && 'vertical' || 'horizontal'" :current="1" progressDot>
+      <a-steps :direction="isMobile && 'vertical' || 'horizontal'" :current="1" progressDot>
         <a-step title="创建项目">
         </a-step>
         <a-step title="部门初审">
@@ -44,38 +55,38 @@
     </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 ></detail-list-item>
-        <detail-list-item term="某某数据">725</detail-list-item>
-        <detail-list-item term="该数据更新时间">2018-08-08</detail-list-item>
-        <detail-list-item ></detail-list-item>
-      </detail-list>
+      <a-descriptions>
+        <a-descriptions-item label="用户姓名">付晓晓</a-descriptions-item>
+        <a-descriptions-item label="会员卡号">32943898021309809423</a-descriptions-item>
+        <a-descriptions-item label="身份证">3321944288191034921</a-descriptions-item>
+        <a-descriptions-item label="联系方式">18112345678</a-descriptions-item>
+        <a-descriptions-item label="联系地址">浙江省杭州市西湖区黄姑山路工专路交叉路口</a-descriptions-item>
+      </a-descriptions>
+      <a-descriptions title="信息组">
+        <a-descriptions-item label="某某数据">725</a-descriptions-item>
+        <a-descriptions-item label="该数据更新时间">2018-08-08</a-descriptions-item>
+        <a-descriptions-item ></a-descriptions-item>
+        <a-descriptions-item label="某某数据">725</a-descriptions-item>
+        <a-descriptions-item label="该数据更新时间">2018-08-08</a-descriptions-item>
+        <a-descriptions-item ></a-descriptions-item>
+      </a-descriptions>
       <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-descriptions title="组名称" size="small">
+          <a-descriptions-item label="负责人">林东东</a-descriptions-item>
+          <a-descriptions-item label="角色码">1234567</a-descriptions-item>
+          <a-descriptions-item label="所属部门">XX公司-YY部</a-descriptions-item>
+          <a-descriptions-item label="过期时间">2018-08-08</a-descriptions-item>
+          <a-descriptions-item label="描述">这段描述很长很长很长很长很长很长很长很长很长很长很长很长很长很长...</a-descriptions-item>
+        </a-descriptions>
         <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-descriptions title="组名称" size="small" :col="1">
+          <a-descriptions-item label="学名">	Citrullus lanatus (Thunb.) Matsum. et Nakai一年生蔓生藤本;茎、枝粗壮,具明显的棱。卷须较粗..</a-descriptions-item>
+        </a-descriptions>
         <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-descriptions title="组名称" size="small" :col="2">
+          <a-descriptions-item label="负责人">付小小</a-descriptions-item>
+          <a-descriptions-item label="角色码">1234567</a-descriptions-item>
+        </a-descriptions>
       </a-card>
 
     </a-card>
@@ -88,12 +99,12 @@
     <a-card
       style="margin-top: 24px"
       :bordered="false"
-      :tabList="tabList"
-      :activeTabKey="activeTabKey"
-      @tabChange="(key) => {this.activeTabKey = key}"
+      :tabList="operationTabList"
+      :activeTabKey="operationActiveTabKey"
+      @tabChange="(key) => {this.operationActiveTabKey = key}"
     >
       <a-table
-        v-if="activeTabKey === '1'"
+        v-if="operationActiveTabKey === '1'"
         :columns="operationColumns"
         :dataSource="operation1"
         :pagination="false"
@@ -105,7 +116,7 @@
         </template>
       </a-table>
       <a-table
-        v-if="activeTabKey === '2'"
+        v-if="operationActiveTabKey === '2'"
         :columns="operationColumns"
         :dataSource="operation2"
         :pagination="false"
@@ -117,7 +128,7 @@
         </template>
       </a-table>
       <a-table
-        v-if="activeTabKey === '3'"
+        v-if="operationActiveTabKey === '3'"
         :columns="operationColumns"
         :dataSource="operation3"
         :pagination="false"
@@ -130,27 +141,24 @@
       </a-table>
     </a-card>
 
-  </page-view>
+  </page-header-wrapper>
 </template>
 
 <script>
-import { mixinDevice } from '@/utils/mixin'
-import { PageView } from '@/layouts'
-import DetailList from '@/components/tools/DetailList'
-
-const DetailListItem = DetailList.Item
+import { baseMixin } from '@/store/app-mixin'
 
 export default {
   name: 'Advanced',
-  components: {
-    PageView,
-    DetailList,
-    DetailListItem
-  },
-  mixins: [mixinDevice],
+  mixins: [baseMixin],
   data () {
     return {
       tabList: [
+        { key: 'detail', tab: '详情' },
+        { key: 'rule', tab: '规则' }
+      ],
+      tabActiveKey: 'detail',
+
+      operationTabList: [
         {
           key: '1',
           tab: '操作日志一'
@@ -164,7 +172,7 @@ export default {
           tab: '操作日志三'
         }
       ],
-      activeTabKey: '1',
+      operationActiveTabKey: '1',
 
       operationColumns: [
         {
@@ -297,6 +305,12 @@ export default {
       }
       return statusTypeMap[type]
     }
+  },
+  methods: {
+    handleTabChange (key) {
+      console.log('')
+      this.tabActiveKey = key
+    }
   }
 }
 </script>

+ 2 - 2
src/views/profile/basic/Index.vue → src/views/profile/basic/index.vue

@@ -1,5 +1,5 @@
 <template>
-  <page-view :title="title">
+  <page-header-wrapper>
     <a-card :bordered="false">
       <detail-list title="退款申请">
         <detail-list-item term="取货单号">1000000000</detail-list-item>
@@ -41,7 +41,7 @@
 
       </s-table>
     </a-card>
-  </page-view>
+  </page-header-wrapper>
 </template>
 
 <script>

+ 1 - 1
src/views/result/Error.vue

@@ -1,6 +1,6 @@
 <template>
   <a-card :bordered="false" style="margin: -24px -24px 0px;">
-    <a-result type="error" :title="title" :sub-title="description">
+    <a-result status="error" :title="title" :sub-title="description">
       <template #extra>
         <a-button type="primary" >返回修改</a-button>
       </template>

+ 1 - 1
src/views/result/Success.vue

@@ -1,6 +1,6 @@
 <template>
   <a-card :bordered="false" style="margin: -24px -24px 0px;">
-    <a-result type="success" :sub-title="description" :title="title">
+    <a-result status="success" :sub-title="description" :title="title">
       <template #extra>
         <a-button type="primary">返回列表</a-button>
         <a-button style="margin-left: 8px">查看项目</a-button>

+ 130 - 1
yarn.lock

@@ -2,6 +2,18 @@
 # yarn lockfile v1
 
 
+"@ant-design-vue/pro-layout@^0.2.3":
+  version "0.2.3"
+  resolved "https://registry.npm.taobao.org/@ant-design-vue/pro-layout/download/@ant-design-vue/pro-layout-0.2.3.tgz?cache=0&sync_timestamp=1588617532162&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40ant-design-vue%2Fpro-layout%2Fdownload%2F%40ant-design-vue%2Fpro-layout-0.2.3.tgz#ab5d359a111eff77fee4889fd008635b5eaf3dbe"
+  integrity sha1-q101mhEe/3f+5Iif0AhjW16vPb4=
+  dependencies:
+    ant-design-vue "^1.5.3"
+    classnames "^2.2.6"
+    insert-css "^2.0.0"
+    lodash "^4.17.15"
+    umi-request "^1.2.11"
+    vue-container-query "^0.1.0"
+
 "@ant-design/colors@^3.1.0":
   version "3.1.0"
   resolved "https://registry.npmjs.org/@ant-design/colors/-/colors-3.1.0.tgz#b7e2cc61a4e86d3d109494034acfb1222dacaa3c"
@@ -2005,6 +2017,41 @@ ant-design-vue@1.5.3:
     vue-ref "^2.0.0"
     warning "^4.0.0"
 
+ant-design-vue@^1.5.3:
+  version "1.5.4"
+  resolved "https://registry.npm.taobao.org/ant-design-vue/download/ant-design-vue-1.5.4.tgz?cache=0&sync_timestamp=1588234849668&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fant-design-vue%2Fdownload%2Fant-design-vue-1.5.4.tgz#428473de9f6b22df63cacdfe07d5096258f871a9"
+  integrity sha1-QoRz3p9rIt9jys3+B9UJYlj4cak=
+  dependencies:
+    "@ant-design/icons" "^2.1.1"
+    "@ant-design/icons-vue" "^2.0.0"
+    add-dom-event-listener "^1.0.2"
+    array-tree-filter "^2.1.0"
+    async-validator "^3.0.3"
+    babel-helper-vue-jsx-merge-props "^2.0.3"
+    babel-runtime "6.x"
+    classnames "^2.2.5"
+    component-classes "^1.2.6"
+    dom-align "^1.10.4"
+    dom-closest "^0.2.0"
+    dom-scroll-into-view "^2.0.0"
+    enquire.js "^2.1.6"
+    intersperse "^1.0.0"
+    is-mobile "^2.2.1"
+    is-negative-zero "^2.0.0"
+    ismobilejs "^1.0.0"
+    json2mq "^0.2.0"
+    lodash "^4.17.5"
+    moment "^2.21.0"
+    mutationobserver-shim "^0.3.2"
+    node-emoji "^1.10.0"
+    omit.js "^1.0.0"
+    raf "^3.4.0"
+    resize-observer-polyfill "^1.5.1"
+    shallow-equal "^1.0.0"
+    shallowequal "^1.0.2"
+    vue-ref "^2.0.0"
+    warning "^4.0.0"
+
 any-promise@^1.0.0:
   version "1.3.0"
   resolved "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f"
@@ -2442,6 +2489,11 @@ base@^0.11.1:
     mixin-deep "^1.2.0"
     pascalcase "^0.1.1"
 
+batch-processor@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.npm.taobao.org/batch-processor/download/batch-processor-1.0.0.tgz#75c95c32b748e0850d10c2b168f6bdbe9891ace8"
+  integrity sha1-dclcMrdI4IUNEMKxaPa9vpiRrOg=
+
 batch@0.6.1:
   version "0.6.1"
   resolved "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz#dc34314f4e679318093fc760272525f94bf25c16"
@@ -2996,7 +3048,7 @@ class-utils@^0.3.5:
     isobject "^3.0.0"
     static-extend "^0.1.1"
 
-classnames@^2.2.5:
+classnames@^2.2.5, classnames@^2.2.6:
   version "2.2.6"
   resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.6.tgz#43935bffdd291f326dad0a205309b38d00f650ce"
   integrity sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q==
@@ -3280,6 +3332,11 @@ constants-browserify@^1.0.0:
   resolved "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75"
   integrity sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=
 
+container-query-toolkit@^0.1.3:
+  version "0.1.3"
+  resolved "https://registry.npm.taobao.org/container-query-toolkit/download/container-query-toolkit-0.1.3.tgz#79ece9fe491d9187b7051247067f1faac6ce00b0"
+  integrity sha1-eezp/kkdkYe3BRJHBn8fqsbOALA=
+
 contains-path@^0.1.0:
   version "0.1.0"
   resolved "https://registry.yarnpkg.com/contains-path/-/contains-path-0.1.0.tgz#fe8cf184ff6670b6baef01a9d4861a5cbec4120a"
@@ -4279,6 +4336,13 @@ electron-to-chromium@^1.3.284:
   resolved "https://registry.npm.taobao.org/electron-to-chromium/download/electron-to-chromium-1.3.289.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Felectron-to-chromium%2Fdownload%2Felectron-to-chromium-1.3.289.tgz#1f85add5d7086ce95d9361348c26aa9de5779906"
   integrity sha1-H4Wt1dcIbOldk2E0jCaqneV3mQY=
 
+element-resize-detector@1.1.13:
+  version "1.1.13"
+  resolved "https://registry.npm.taobao.org/element-resize-detector/download/element-resize-detector-1.1.13.tgz?cache=0&sync_timestamp=1579090861211&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Felement-resize-detector%2Fdownload%2Felement-resize-detector-1.1.13.tgz#f61907e98a91b1ad215f92790bc15113df68444d"
+  integrity sha1-9hkH6YqRsa0hX5J5C8FRE99oRE0=
+  dependencies:
+    batch-processor "^1.0.0"
+
 elliptic@^6.0.0:
   version "6.4.1"
   resolved "https://registry.npmjs.org/elliptic/-/elliptic-6.4.1.tgz#c2d0b7776911b86722c632c3c06c60f2f819939a"
@@ -5824,6 +5888,11 @@ inquirer@^6.2.2:
     strip-ansi "^5.0.0"
     through "^2.3.6"
 
+insert-css@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.npm.taobao.org/insert-css/download/insert-css-2.0.0.tgz#eb5d1097b7542f4c79ea3060d3aee07d053880f4"
+  integrity sha1-610Ql7dUL0x56jBg067gfQU4gPQ=
+
 internal-ip@^4.3.0:
   version "4.3.0"
   resolved "https://registry.npmjs.org/internal-ip/-/internal-ip-4.3.0.tgz#845452baad9d2ca3b69c635a137acb9a0dad0907"
@@ -6177,6 +6246,14 @@ isobject@^3.0.0, isobject@^3.0.1:
   resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df"
   integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8=
 
+isomorphic-fetch@^2.2.1:
+  version "2.2.1"
+  resolved "https://registry.npm.taobao.org/isomorphic-fetch/download/isomorphic-fetch-2.2.1.tgz#611ae1acf14f5e81f729507472819fe9733558a9"
+  integrity sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk=
+  dependencies:
+    node-fetch "^1.0.1"
+    whatwg-fetch ">=0.10.0"
+
 isstream@~0.1.2:
   version "0.1.2"
   resolved "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
@@ -7591,6 +7668,14 @@ node-fetch@1.6.3:
     encoding "^0.1.11"
     is-stream "^1.0.1"
 
+node-fetch@^1.0.1:
+  version "1.7.3"
+  resolved "https://registry.npm.taobao.org/node-fetch/download/node-fetch-1.7.3.tgz?cache=0&sync_timestamp=1587548798776&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fnode-fetch%2Fdownload%2Fnode-fetch-1.7.3.tgz#980f6f72d85211a5347c6b2bc18c5b84c3eb47ef"
+  integrity sha1-mA9vcthSEaU0fGsrwYxbhMPrR+8=
+  dependencies:
+    encoding "^0.1.11"
+    is-stream "^1.0.1"
+
 node-forge@0.7.5:
   version "0.7.5"
   resolved "https://registry.npmjs.org/node-forge/-/node-forge-0.7.5.tgz#6c152c345ce11c52f465c2abd957e8639cd674df"
@@ -8941,6 +9026,11 @@ qs@6.7.0:
   resolved "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc"
   integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==
 
+qs@^6.9.1:
+  version "6.9.4"
+  resolved "https://registry.npm.taobao.org/qs/download/qs-6.9.4.tgz#9090b290d1f91728d3c22e54843ca44aea5ab687"
+  integrity sha1-kJCykNH5FyjTwi5UhDykSupatoc=
+
 qs@~6.5.2:
   version "6.5.2"
   resolved "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36"
@@ -9312,6 +9402,13 @@ reselect@^3.0.1:
   resolved "https://registry.npmjs.org/reselect/-/reselect-3.0.1.tgz#efdaa98ea7451324d092b2b2163a6a1d7a9a2147"
   integrity sha1-79qpjqdFEyTQkrKyFjpqHXqaIUc=
 
+resize-observer-lite@^0.2.3:
+  version "0.2.3"
+  resolved "https://registry.npm.taobao.org/resize-observer-lite/download/resize-observer-lite-0.2.3.tgz#b557f378e2ce9b9aab1dc71a91047bd7ed1d8915"
+  integrity sha1-tVfzeOLOm5qrHccakQR71+0diRU=
+  dependencies:
+    element-resize-detector "1.1.13"
+
 resize-observer-polyfill@^1.5.1:
   version "1.5.1"
   resolved "https://registry.yarnpkg.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz#0e9020dd3d21024458d4ebd27e23e40269810464"
@@ -10649,6 +10746,14 @@ uglify-to-browserify@~1.0.0:
   resolved "https://registry.yarnpkg.com/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz#6e0924d6bda6b5afe349e39a6d632850a0f882b7"
   integrity sha1-bgkk1r2mta/jSeOabWMoUKD4grc=
 
+umi-request@^1.2.11:
+  version "1.2.19"
+  resolved "https://registry.npm.taobao.org/umi-request/download/umi-request-1.2.19.tgz?cache=0&sync_timestamp=1582015205176&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fumi-request%2Fdownload%2Fumi-request-1.2.19.tgz#57ec16322506674f2d6392d553119829cedb1589"
+  integrity sha1-V+wWMiUGZ08tY5LVUxGYKc7bFYk=
+  dependencies:
+    isomorphic-fetch "^2.2.1"
+    qs "^6.9.1"
+
 unicode-canonical-property-names-ecmascript@^1.0.4:
   version "1.0.4"
   resolved "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz#2619800c4c825800efdd8343af7dd9933cbe2818"
@@ -10894,6 +10999,15 @@ vue-clipboard2@^0.2.1:
   dependencies:
     clipboard "^2.0.0"
 
+vue-container-query@^0.1.0:
+  version "0.1.0"
+  resolved "https://registry.npm.taobao.org/vue-container-query/download/vue-container-query-0.1.0.tgz#8210b49388f2350aa4afda92df5caca18ff220a1"
+  integrity sha1-ghC0k4jyNQqkr9qS31ysoY/yIKE=
+  dependencies:
+    container-query-toolkit "^0.1.3"
+    resize-observer-lite "^0.2.3"
+    vue "^2.5.17"
+
 vue-cropper@0.4.9:
   version "0.4.9"
   resolved "https://registry.npm.taobao.org/vue-cropper/download/vue-cropper-0.4.9.tgz#fe650f32516ecf29014bbd4a9079191c8dc5a5ae"
@@ -10916,6 +11030,11 @@ vue-hot-reload-api@^2.3.0:
   resolved "https://registry.npmjs.org/vue-hot-reload-api/-/vue-hot-reload-api-2.3.3.tgz#2756f46cb3258054c5f4723de8ae7e87302a1ccf"
   integrity sha512-KmvZVtmM26BQOMK1rwUZsrqxEGeKiYSZGA7SNWE6uExx8UX/cj9hq2MRV/wWC3Cq6AoeDGk57rL9YMFRel/q+g==
 
+vue-i18n@^8.17.4:
+  version "8.17.4"
+  resolved "https://registry.npm.taobao.org/vue-i18n/download/vue-i18n-8.17.4.tgz?cache=0&sync_timestamp=1588409193904&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fvue-i18n%2Fdownload%2Fvue-i18n-8.17.4.tgz#d314df7a3fa0780f86cff46a02752668f89b3935"
+  integrity sha1-0xTfej+geA+Gz/RqAnUmaPibOTU=
+
 vue-jest@^3.0.5:
   version "3.0.5"
   resolved "https://registry.npm.taobao.org/vue-jest/download/vue-jest-3.0.5.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fvue-jest%2Fdownload%2Fvue-jest-3.0.5.tgz#d6f124b542dcbff207bf9296c19413f4c40b70c9"
@@ -11002,6 +11121,11 @@ vue-template-es2015-compiler@^1.6.0, vue-template-es2015-compiler@^1.9.0:
   resolved "https://registry.yarnpkg.com/vue-template-es2015-compiler/-/vue-template-es2015-compiler-1.9.1.tgz#1ee3bc9a16ecbf5118be334bb15f9c46f82f5825"
   integrity sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw==
 
+vue@^2.5.17:
+  version "2.6.11"
+  resolved "https://registry.npm.taobao.org/vue/download/vue-2.6.11.tgz#76594d877d4b12234406e84e35275c6d514125c5"
+  integrity sha1-dllNh31LEiNEBuhONSdcbVFBJcU=
+
 vue@^2.5.3:
   version "2.6.7"
   resolved "https://registry.yarnpkg.com/vue/-/vue-2.6.7.tgz#254f188e7621d2d19ee28d0c0442c6d21b53ae2d"
@@ -11238,6 +11362,11 @@ whatwg-encoding@^1.0.1, whatwg-encoding@^1.0.3, whatwg-encoding@^1.0.5:
   dependencies:
     iconv-lite "0.4.24"
 
+whatwg-fetch@>=0.10.0:
+  version "3.0.0"
+  resolved "https://registry.npm.taobao.org/whatwg-fetch/download/whatwg-fetch-3.0.0.tgz#fc804e458cc460009b1a2b966bc8817d2578aefb"
+  integrity sha1-/IBORYzEYACbGiuWa8iBfSV4rvs=
+
 whatwg-mimetype@^2.1.0, whatwg-mimetype@^2.2.0, whatwg-mimetype@^2.3.0:
   version "2.3.0"
   resolved "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz#3d4b1e0312d2079879f826aff18dbeeca5960fbf"