yuwenjun před 4 roky
rodič
revize
eb7a5f67f3
43 změnil soubory, kde provedl 4978 přidání a 2908 odebrání
  1. 5 5
      .env.development
  2. 3 3
      .prettierrc
  3. 1 1
      README.md
  4. 86 0
      mock/auth.js
  5. 63 60
      mock/index.js
  6. 11 0
      mock/product.js
  7. 98 98
      mock/role/index.js
  8. 530 530
      mock/role/routes.js
  9. 39 0
      mock/supplier.js
  10. 100 84
      mock/user.js
  11. 9 0
      src/api/auth.js
  12. 19 0
      src/api/supplier.js
  13. 12 0
      src/api/upload.js
  14. 32 24
      src/api/user.js
  15. 1539 0
      src/common/country.js
  16. 158 152
      src/layout/components/Navbar.vue
  17. 55 53
      src/main.js
  18. 16 0
      src/mixin/base.js
  19. 75 74
      src/permission.js
  20. 197 180
      src/router/index.js
  21. 35 35
      src/settings.js
  22. 4 3
      src/store/getters.js
  23. 8 2
      src/store/modules/permission.js
  24. 95 131
      src/store/modules/user.js
  25. 13 0
      src/utils/auth.js
  26. 72 85
      src/utils/request.js
  27. 17 0
      src/utils/storage.js
  28. 9 9
      src/utils/validate.js
  29. 1 1
      src/views/error-page/404.vue
  30. 287 309
      src/views/login/index.vue
  31. 49 0
      src/views/normal/password.vue
  32. 86 0
      src/views/password/index.vue
  33. 184 161
      src/views/supplier/auth/index.vue
  34. 36 0
      src/views/supplier/components/upload.vue
  35. 43 21
      src/views/supplier/components/uploadFile.vue
  36. 70 38
      src/views/supplier/components/uploadImage.vue
  37. 86 86
      src/views/supplier/product/add.vue
  38. 86 86
      src/views/supplier/product/edit.vue
  39. 163 162
      src/views/supplier/product/index.vue
  40. 192 88
      src/views/supplier/user/add.vue
  41. 88 88
      src/views/supplier/user/edit.vue
  42. 182 215
      src/views/supplier/user/index.vue
  43. 124 124
      vue.config.js

+ 5 - 5
.env.development

@@ -1,5 +1,5 @@
-# just a flag
-ENV = 'development'
-
-# base api
-VUE_APP_BASE_API = '/dev-api'
+# just a flag
+ENV = 'development'
+
+# base api
+VUE_APP_BASE_API = 'http://192.168.2.68:8012'

+ 3 - 3
.prettierrc

@@ -1,4 +1,4 @@
-{
-  "semi": false,
-  "singleQuote": true
+{
+  "semi": false,
+  "singleQuote": true
 }

+ 1 - 1
README.md

@@ -1 +1 @@
-正品联盟管理后台
+正品联盟管理后台

+ 86 - 0
mock/auth.js

@@ -0,0 +1,86 @@
+const Mock = require('mockjs')
+
+const count = 10
+const authList = []
+
+for (let i = 0; i < count; i++) {
+  authList.push(Mock.mock({
+    authParty: '@ctitle(4,8)', // 授权机构
+    authUserId: '@increment(1)', // 供应商用户id
+    createBy: '@cname', // 创建人
+    createTime: '@date', // 创建时间
+    id: '@increment(1)', // 授权id
+    status: '@integer(0,1)' // 上架状态
+  }))
+}
+
+module.exports = [
+  {
+    url: '/auth/list',
+    type: 'get',
+    response: config => {
+      return {
+        code: 0,
+        data: {
+          list: authList,
+          total: authList.length,
+          endRow: 0,
+          firstPage: 0,
+          hasNextPage: true,
+          hasPreviousPage: true,
+          isFirstPage: true,
+          isLastPage: true,
+          lastPage: 0,
+          navigateFirstPage: 0,
+          navigateLastPage: 0,
+          navigatePages: 0,
+          navigatepageNums: [],
+          nextPage: 0,
+          pageNum: 0,
+          pageSize: 0,
+          pages: 0,
+          prePage: 0,
+          size: 0,
+          startRow: 0
+        }
+      }
+    }
+  }
+]
+
+// {
+//   "code": 0,
+//   "data": {
+//     "endRow": 0,
+//     "firstPage": 0,
+//     "hasNextPage": true,
+//     "hasPreviousPage": true,
+//     "isFirstPage": true,
+//     "isLastPage": true,
+//     "lastPage": 0,
+//     "list": [
+//       {
+//         "authParty": "",
+//         "authUserId": 0,
+//         "createBy": "",
+//         "createTime": "",
+//         "id": 0,
+//         "status": 0
+//       }
+//     ],
+//     "navigateFirstPage": 0,
+//     "navigateLastPage": 0,
+//     "navigatePages": 0,
+//     "navigatepageNums": [],
+//     "nextPage": 0,
+//     "pageNum": 0,
+//     "pageSize": 0,
+//     "pages": 0,
+//     "prePage": 0,
+//     "size": 0,
+//     "startRow": 0,
+//     "total": 0
+//   },
+//   "msg": ""
+// }
+// }

+ 63 - 60
mock/index.js

@@ -1,60 +1,63 @@
-const Mock = require('mockjs')
-const { param2Obj } = require('./utils')
-
-const user = require('./user')
-const role = require('./role')
-const article = require('./article')
-const search = require('./remote-search')
-
-const mocks = [
-  ...user,
-  ...role,
-  ...article,
-  ...search
-]
-
-// for front mock
-// please use it cautiously, it will redefine XMLHttpRequest,
-// which will cause many of your third-party libraries to be invalidated(like progress event).
-function mockXHR() {
-  // mock patch
-  // https://github.com/nuysoft/Mock/issues/300
-  Mock.XHR.prototype.proxy_send = Mock.XHR.prototype.send
-  Mock.XHR.prototype.send = function() {
-    if (this.custom.xhr) {
-      this.custom.xhr.withCredentials = this.withCredentials || false
-
-      if (this.responseType) {
-        this.custom.xhr.responseType = this.responseType
-      }
-    }
-    this.proxy_send(...arguments)
-  }
-
-  function XHR2ExpressReqWrap(respond) {
-    return function(options) {
-      let result = null
-      if (respond instanceof Function) {
-        const { body, type, url } = options
-        // https://expressjs.com/en/4x/api.html#req
-        result = respond({
-          method: type,
-          body: JSON.parse(body),
-          query: param2Obj(url)
-        })
-      } else {
-        result = respond
-      }
-      return Mock.mock(result)
-    }
-  }
-
-  for (const i of mocks) {
-    Mock.mock(new RegExp(i.url), i.type || 'get', XHR2ExpressReqWrap(i.response))
-  }
-}
-
-module.exports = {
-  mocks,
-  mockXHR
-}
+const Mock = require('mockjs')
+const { param2Obj } = require('./utils')
+
+const user = require('./user')
+const role = require('./role')
+const supplier = require('./supplier')
+const auth = require('./auth')
+
+const mocks = [
+  ...user,
+  ...role,
+  ...supplier,
+  ...auth
+]
+
+// for front mock
+// please use it cautiously, it will redefine XMLHttpRequest,
+// which will cause many of your third-party libraries to be invalidated(like progress event).
+function mockXHR() {
+  Mock.setup({
+    timeout: 800
+  })
+  // mock patch
+  // https://github.com/nuysoft/Mock/issues/300
+  Mock.XHR.prototype.proxy_send = Mock.XHR.prototype.send
+  Mock.XHR.prototype.send = function() {
+    if (this.custom.xhr) {
+      this.custom.xhr.withCredentials = this.withCredentials || false
+
+      if (this.responseType) {
+        this.custom.xhr.responseType = this.responseType
+      }
+    }
+    this.proxy_send(...arguments)
+  }
+
+  function XHR2ExpressReqWrap(respond) {
+    return function(options) {
+      let result = null
+      if (respond instanceof Function) {
+        const { body, type, url } = options
+        // https://expressjs.com/en/4x/api.html#req
+        result = respond({
+          method: type,
+          body: JSON.parse(body),
+          query: param2Obj(url)
+        })
+      } else {
+        result = respond
+      }
+      return Mock.mock(result)
+    }
+  }
+
+  for (const i of mocks) {
+    Mock.mock(new RegExp(i.url), i.type || 'get', XHR2ExpressReqWrap(i.response))
+  }
+}
+
+module.exports = {
+  mocks,
+  mockXHR
+}

+ 11 - 0
mock/product.js

@@ -0,0 +1,11 @@
+// const Mock = require('mockjs')
+
+// const count = 20
+// const productList = []
+
+// for (let i = 0; i < count; i++) {
+//   productList.push(Mock.mock({
+
+//   }))
+
+// }

+ 98 - 98
mock/role/index.js

@@ -1,98 +1,98 @@
-const Mock = require('mockjs')
-const { deepClone } = require('../utils')
-const { asyncRoutes, constantRoutes } = require('./routes.js')
-
-const routes = deepClone([...constantRoutes, ...asyncRoutes])
-
-const roles = [
-  {
-    key: 'admin',
-    name: 'admin',
-    description: 'Super Administrator. Have access to view all pages.',
-    routes: routes
-  },
-  {
-    key: 'editor',
-    name: 'editor',
-    description: 'Normal Editor. Can see all pages except permission page',
-    routes: routes.filter(i => i.path !== '/permission')// just a mock
-  },
-  {
-    key: 'visitor',
-    name: 'visitor',
-    description: 'Just a visitor. Can only see the home page and the document page',
-    routes: [{
-      path: '',
-      redirect: 'dashboard',
-      children: [
-        {
-          path: 'dashboard',
-          name: 'Dashboard',
-          meta: { title: 'dashboard', icon: 'dashboard' }
-        }
-      ]
-    }]
-  }
-]
-
-module.exports = [
-  // mock get all routes form server
-  {
-    url: '/vue-element-admin/routes',
-    type: 'get',
-    response: _ => {
-      return {
-        code: 20000,
-        data: routes
-      }
-    }
-  },
-
-  // mock get all roles form server
-  {
-    url: '/vue-element-admin/roles',
-    type: 'get',
-    response: _ => {
-      return {
-        code: 20000,
-        data: roles
-      }
-    }
-  },
-
-  // add role
-  {
-    url: '/vue-element-admin/role',
-    type: 'post',
-    response: {
-      code: 20000,
-      data: {
-        key: Mock.mock('@integer(300, 5000)')
-      }
-    }
-  },
-
-  // update role
-  {
-    url: '/vue-element-admin/role/[A-Za-z0-9]',
-    type: 'put',
-    response: {
-      code: 20000,
-      data: {
-        status: 'success'
-      }
-    }
-  },
-
-  // delete role
-  {
-    url: '/vue-element-admin/role/[A-Za-z0-9]',
-    type: 'delete',
-    response: {
-      code: 20000,
-      data: {
-        status: 'success'
-      }
-    }
-  }
-]
+const Mock = require('mockjs')
+const { deepClone } = require('../utils')
+const { asyncRoutes, constantRoutes } = require('./routes.js')
+
+const routes = deepClone([...constantRoutes, ...asyncRoutes])
+
+const roles = [
+  {
+    key: 'admin',
+    name: 'admin',
+    description: 'Super Administrator. Have access to view all pages.',
+    routes: routes
+  },
+  {
+    key: 'editor',
+    name: 'editor',
+    description: 'Normal Editor. Can see all pages except permission page',
+    routes: routes.filter(i => i.path !== '/permission')// just a mock
+  },
+  {
+    key: 'visitor',
+    name: 'visitor',
+    description: 'Just a visitor. Can only see the home page and the document page',
+    routes: [{
+      path: '',
+      redirect: 'dashboard',
+      children: [
+        {
+          path: 'dashboard',
+          name: 'Dashboard',
+          meta: { title: 'dashboard', icon: 'dashboard' }
+        }
+      ]
+    }]
+  }
+]
+
+module.exports = [
+  // mock get all routes form server
+  {
+    url: '/vue-element-admin/routes',
+    type: 'get',
+    response: _ => {
+      return {
+        code: 20000,
+        data: routes
+      }
+    }
+  },
+
+  // mock get all roles form server
+  {
+    url: '/vue-element-admin/roles',
+    type: 'get',
+    response: _ => {
+      return {
+        code: 20000,
+        data: roles
+      }
+    }
+  },
+
+  // add role
+  {
+    url: '/vue-element-admin/role',
+    type: 'post',
+    response: {
+      code: 20000,
+      data: {
+        key: Mock.mock('@integer(300, 5000)')
+      }
+    }
+  },
+
+  // update role
+  {
+    url: '/vue-element-admin/role/[A-Za-z0-9]',
+    type: 'put',
+    response: {
+      code: 20000,
+      data: {
+        status: 'success'
+      }
+    }
+  },
+
+  // delete role
+  {
+    url: '/vue-element-admin/role/[A-Za-z0-9]',
+    type: 'delete',
+    response: {
+      code: 20000,
+      data: {
+        status: 'success'
+      }
+    }
+  }
+]

+ 530 - 530
mock/role/routes.js

@@ -1,530 +1,530 @@
-// Just a mock data
-
-const constantRoutes = [
-  {
-    path: '/redirect',
-    component: 'layout/Layout',
-    hidden: true,
-    children: [
-      {
-        path: '/redirect/:path*',
-        component: 'views/redirect/index'
-      }
-    ]
-  },
-  {
-    path: '/login',
-    component: 'views/login/index',
-    hidden: true
-  },
-  {
-    path: '/auth-redirect',
-    component: 'views/login/auth-redirect',
-    hidden: true
-  },
-  {
-    path: '/404',
-    component: 'views/error-page/404',
-    hidden: true
-  },
-  {
-    path: '/401',
-    component: 'views/error-page/401',
-    hidden: true
-  },
-  {
-    path: '',
-    component: 'layout/Layout',
-    redirect: 'dashboard',
-    children: [
-      {
-        path: 'dashboard',
-        component: 'views/dashboard/index',
-        name: 'Dashboard',
-        meta: { title: 'Dashboard', icon: 'dashboard', affix: true }
-      }
-    ]
-  },
-  {
-    path: '/documentation',
-    component: 'layout/Layout',
-    children: [
-      {
-        path: 'index',
-        component: 'views/documentation/index',
-        name: 'Documentation',
-        meta: { title: 'Documentation', icon: 'documentation', affix: true }
-      }
-    ]
-  },
-  {
-    path: '/guide',
-    component: 'layout/Layout',
-    redirect: '/guide/index',
-    children: [
-      {
-        path: 'index',
-        component: 'views/guide/index',
-        name: 'Guide',
-        meta: { title: 'Guide', icon: 'guide', noCache: true }
-      }
-    ]
-  }
-]
-
-const asyncRoutes = [
-  {
-    path: '/permission',
-    component: 'layout/Layout',
-    redirect: '/permission/index',
-    alwaysShow: true,
-    meta: {
-      title: 'Permission',
-      icon: 'lock',
-      roles: ['admin', 'editor']
-    },
-    children: [
-      {
-        path: 'page',
-        component: 'views/permission/page',
-        name: 'PagePermission',
-        meta: {
-          title: 'Page Permission',
-          roles: ['admin']
-        }
-      },
-      {
-        path: 'directive',
-        component: 'views/permission/directive',
-        name: 'DirectivePermission',
-        meta: {
-          title: 'Directive Permission'
-        }
-      },
-      {
-        path: 'role',
-        component: 'views/permission/role',
-        name: 'RolePermission',
-        meta: {
-          title: 'Role Permission',
-          roles: ['admin']
-        }
-      }
-    ]
-  },
-
-  {
-    path: '/icon',
-    component: 'layout/Layout',
-    children: [
-      {
-        path: 'index',
-        component: 'views/icons/index',
-        name: 'Icons',
-        meta: { title: 'Icons', icon: 'icon', noCache: true }
-      }
-    ]
-  },
-
-  {
-    path: '/components',
-    component: 'layout/Layout',
-    redirect: 'noRedirect',
-    name: 'ComponentDemo',
-    meta: {
-      title: 'Components',
-      icon: 'component'
-    },
-    children: [
-      {
-        path: 'tinymce',
-        component: 'views/components-demo/tinymce',
-        name: 'TinymceDemo',
-        meta: { title: 'Tinymce' }
-      },
-      {
-        path: 'markdown',
-        component: 'views/components-demo/markdown',
-        name: 'MarkdownDemo',
-        meta: { title: 'Markdown' }
-      },
-      {
-        path: 'json-editor',
-        component: 'views/components-demo/json-editor',
-        name: 'JsonEditorDemo',
-        meta: { title: 'Json Editor' }
-      },
-      {
-        path: 'split-pane',
-        component: 'views/components-demo/split-pane',
-        name: 'SplitpaneDemo',
-        meta: { title: 'SplitPane' }
-      },
-      {
-        path: 'avatar-upload',
-        component: 'views/components-demo/avatar-upload',
-        name: 'AvatarUploadDemo',
-        meta: { title: 'Avatar Upload' }
-      },
-      {
-        path: 'dropzone',
-        component: 'views/components-demo/dropzone',
-        name: 'DropzoneDemo',
-        meta: { title: 'Dropzone' }
-      },
-      {
-        path: 'sticky',
-        component: 'views/components-demo/sticky',
-        name: 'StickyDemo',
-        meta: { title: 'Sticky' }
-      },
-      {
-        path: 'count-to',
-        component: 'views/components-demo/count-to',
-        name: 'CountToDemo',
-        meta: { title: 'Count To' }
-      },
-      {
-        path: 'mixin',
-        component: 'views/components-demo/mixin',
-        name: 'ComponentMixinDemo',
-        meta: { title: 'componentMixin' }
-      },
-      {
-        path: 'back-to-top',
-        component: 'views/components-demo/back-to-top',
-        name: 'BackToTopDemo',
-        meta: { title: 'Back To Top' }
-      },
-      {
-        path: 'drag-dialog',
-        component: 'views/components-demo/drag-dialog',
-        name: 'DragDialogDemo',
-        meta: { title: 'Drag Dialog' }
-      },
-      {
-        path: 'drag-select',
-        component: 'views/components-demo/drag-select',
-        name: 'DragSelectDemo',
-        meta: { title: 'Drag Select' }
-      },
-      {
-        path: 'dnd-list',
-        component: 'views/components-demo/dnd-list',
-        name: 'DndListDemo',
-        meta: { title: 'Dnd List' }
-      },
-      {
-        path: 'drag-kanban',
-        component: 'views/components-demo/drag-kanban',
-        name: 'DragKanbanDemo',
-        meta: { title: 'Drag Kanban' }
-      }
-    ]
-  },
-  {
-    path: '/charts',
-    component: 'layout/Layout',
-    redirect: 'noRedirect',
-    name: 'Charts',
-    meta: {
-      title: 'Charts',
-      icon: 'chart'
-    },
-    children: [
-      {
-        path: 'keyboard',
-        component: 'views/charts/keyboard',
-        name: 'KeyboardChart',
-        meta: { title: 'Keyboard Chart', noCache: true }
-      },
-      {
-        path: 'line',
-        component: 'views/charts/line',
-        name: 'LineChart',
-        meta: { title: 'Line Chart', noCache: true }
-      },
-      {
-        path: 'mixchart',
-        component: 'views/charts/mixChart',
-        name: 'MixChart',
-        meta: { title: 'Mix Chart', noCache: true }
-      }
-    ]
-  },
-  {
-    path: '/nested',
-    component: 'layout/Layout',
-    redirect: '/nested/menu1/menu1-1',
-    name: 'Nested',
-    meta: {
-      title: 'Nested',
-      icon: 'nested'
-    },
-    children: [
-      {
-        path: 'menu1',
-        component: 'views/nested/menu1/index',
-        name: 'Menu1',
-        meta: { title: 'Menu1' },
-        redirect: '/nested/menu1/menu1-1',
-        children: [
-          {
-            path: 'menu1-1',
-            component: 'views/nested/menu1/menu1-1',
-            name: 'Menu1-1',
-            meta: { title: 'Menu1-1' }
-          },
-          {
-            path: 'menu1-2',
-            component: 'views/nested/menu1/menu1-2',
-            name: 'Menu1-2',
-            redirect: '/nested/menu1/menu1-2/menu1-2-1',
-            meta: { title: 'Menu1-2' },
-            children: [
-              {
-                path: 'menu1-2-1',
-                component: 'views/nested/menu1/menu1-2/menu1-2-1',
-                name: 'Menu1-2-1',
-                meta: { title: 'Menu1-2-1' }
-              },
-              {
-                path: 'menu1-2-2',
-                component: 'views/nested/menu1/menu1-2/menu1-2-2',
-                name: 'Menu1-2-2',
-                meta: { title: 'Menu1-2-2' }
-              }
-            ]
-          },
-          {
-            path: 'menu1-3',
-            component: 'views/nested/menu1/menu1-3',
-            name: 'Menu1-3',
-            meta: { title: 'Menu1-3' }
-          }
-        ]
-      },
-      {
-        path: 'menu2',
-        name: 'Menu2',
-        component: 'views/nested/menu2/index',
-        meta: { title: 'Menu2' }
-      }
-    ]
-  },
-
-  {
-    path: '/example',
-    component: 'layout/Layout',
-    redirect: '/example/list',
-    name: 'Example',
-    meta: {
-      title: 'Example',
-      icon: 'example'
-    },
-    children: [
-      {
-        path: 'create',
-        component: 'views/example/create',
-        name: 'CreateArticle',
-        meta: { title: 'Create Article', icon: 'edit' }
-      },
-      {
-        path: 'edit/:id(\\d+)',
-        component: 'views/example/edit',
-        name: 'EditArticle',
-        meta: { title: 'Edit Article', noCache: true },
-        hidden: true
-      },
-      {
-        path: 'list',
-        component: 'views/example/list',
-        name: 'ArticleList',
-        meta: { title: 'Article List', icon: 'list' }
-      }
-    ]
-  },
-
-  {
-    path: '/tab',
-    component: 'layout/Layout',
-    children: [
-      {
-        path: 'index',
-        component: 'views/tab/index',
-        name: 'Tab',
-        meta: { title: 'Tab', icon: 'tab' }
-      }
-    ]
-  },
-
-  {
-    path: '/error',
-    component: 'layout/Layout',
-    redirect: 'noRedirect',
-    name: 'ErrorPages',
-    meta: {
-      title: 'Error Pages',
-      icon: '404'
-    },
-    children: [
-      {
-        path: '401',
-        component: 'views/error-page/401',
-        name: 'Page401',
-        meta: { title: 'Page 401', noCache: true }
-      },
-      {
-        path: '404',
-        component: 'views/error-page/404',
-        name: 'Page404',
-        meta: { title: 'Page 404', noCache: true }
-      }
-    ]
-  },
-
-  {
-    path: '/error-log',
-    component: 'layout/Layout',
-    redirect: 'noRedirect',
-    children: [
-      {
-        path: 'log',
-        component: 'views/error-log/index',
-        name: 'ErrorLog',
-        meta: { title: 'Error Log', icon: 'bug' }
-      }
-    ]
-  },
-
-  {
-    path: '/excel',
-    component: 'layout/Layout',
-    redirect: '/excel/export-excel',
-    name: 'Excel',
-    meta: {
-      title: 'Excel',
-      icon: 'excel'
-    },
-    children: [
-      {
-        path: 'export-excel',
-        component: 'views/excel/export-excel',
-        name: 'ExportExcel',
-        meta: { title: 'Export Excel' }
-      },
-      {
-        path: 'export-selected-excel',
-        component: 'views/excel/select-excel',
-        name: 'SelectExcel',
-        meta: { title: 'Select Excel' }
-      },
-      {
-        path: 'export-merge-header',
-        component: 'views/excel/merge-header',
-        name: 'MergeHeader',
-        meta: { title: 'Merge Header' }
-      },
-      {
-        path: 'upload-excel',
-        component: 'views/excel/upload-excel',
-        name: 'UploadExcel',
-        meta: { title: 'Upload Excel' }
-      }
-    ]
-  },
-
-  {
-    path: '/zip',
-    component: 'layout/Layout',
-    redirect: '/zip/download',
-    alwaysShow: true,
-    meta: { title: 'Zip', icon: 'zip' },
-    children: [
-      {
-        path: 'download',
-        component: 'views/zip/index',
-        name: 'ExportZip',
-        meta: { title: 'Export Zip' }
-      }
-    ]
-  },
-
-  {
-    path: '/pdf',
-    component: 'layout/Layout',
-    redirect: '/pdf/index',
-    children: [
-      {
-        path: 'index',
-        component: 'views/pdf/index',
-        name: 'PDF',
-        meta: { title: 'PDF', icon: 'pdf' }
-      }
-    ]
-  },
-  {
-    path: '/pdf/download',
-    component: 'views/pdf/download',
-    hidden: true
-  },
-
-  {
-    path: '/theme',
-    component: 'layout/Layout',
-    redirect: 'noRedirect',
-    children: [
-      {
-        path: 'index',
-        component: 'views/theme/index',
-        name: 'Theme',
-        meta: { title: 'Theme', icon: 'theme' }
-      }
-    ]
-  },
-
-  {
-    path: '/clipboard',
-    component: 'layout/Layout',
-    redirect: 'noRedirect',
-    children: [
-      {
-        path: 'index',
-        component: 'views/clipboard/index',
-        name: 'ClipboardDemo',
-        meta: { title: 'Clipboard Demo', icon: 'clipboard' }
-      }
-    ]
-  },
-
-  {
-    path: '/i18n',
-    component: 'layout/Layout',
-    children: [
-      {
-        path: 'index',
-        component: 'views/i18n-demo/index',
-        name: 'I18n',
-        meta: { title: 'I18n', icon: 'international' }
-      }
-    ]
-  },
-
-  {
-    path: 'external-link',
-    component: 'layout/Layout',
-    children: [
-      {
-        path: 'https://github.com/PanJiaChen/vue-element-admin',
-        meta: { title: 'External Link', icon: 'link' }
-      }
-    ]
-  },
-
-  { path: '*', redirect: '/404', hidden: true }
-]
-
-module.exports = {
-  constantRoutes,
-  asyncRoutes
-}
+// Just a mock data
+
+const constantRoutes = [
+  {
+    path: '/redirect',
+    component: 'layout/Layout',
+    hidden: true,
+    children: [
+      {
+        path: '/redirect/:path*',
+        component: 'views/redirect/index'
+      }
+    ]
+  },
+  {
+    path: '/login',
+    component: 'views/login/index',
+    hidden: true
+  },
+  {
+    path: '/auth-redirect',
+    component: 'views/login/auth-redirect',
+    hidden: true
+  },
+  {
+    path: '/404',
+    component: 'views/error-page/404',
+    hidden: true
+  },
+  {
+    path: '/401',
+    component: 'views/error-page/401',
+    hidden: true
+  },
+  {
+    path: '',
+    component: 'layout/Layout',
+    redirect: 'dashboard',
+    children: [
+      {
+        path: 'dashboard',
+        component: 'views/dashboard/index',
+        name: 'Dashboard',
+        meta: { title: 'Dashboard', icon: 'dashboard', affix: true }
+      }
+    ]
+  },
+  {
+    path: '/documentation',
+    component: 'layout/Layout',
+    children: [
+      {
+        path: 'index',
+        component: 'views/documentation/index',
+        name: 'Documentation',
+        meta: { title: 'Documentation', icon: 'documentation', affix: true }
+      }
+    ]
+  },
+  {
+    path: '/guide',
+    component: 'layout/Layout',
+    redirect: '/guide/index',
+    children: [
+      {
+        path: 'index',
+        component: 'views/guide/index',
+        name: 'Guide',
+        meta: { title: 'Guide', icon: 'guide', noCache: true }
+      }
+    ]
+  }
+]
+
+const asyncRoutes = [
+  {
+    path: '/permission',
+    component: 'layout/Layout',
+    redirect: '/permission/index',
+    alwaysShow: true,
+    meta: {
+      title: 'Permission',
+      icon: 'lock',
+      roles: ['admin', 'editor']
+    },
+    children: [
+      {
+        path: 'page',
+        component: 'views/permission/page',
+        name: 'PagePermission',
+        meta: {
+          title: 'Page Permission',
+          roles: ['admin']
+        }
+      },
+      {
+        path: 'directive',
+        component: 'views/permission/directive',
+        name: 'DirectivePermission',
+        meta: {
+          title: 'Directive Permission'
+        }
+      },
+      {
+        path: 'role',
+        component: 'views/permission/role',
+        name: 'RolePermission',
+        meta: {
+          title: 'Role Permission',
+          roles: ['admin']
+        }
+      }
+    ]
+  },
+
+  {
+    path: '/icon',
+    component: 'layout/Layout',
+    children: [
+      {
+        path: 'index',
+        component: 'views/icons/index',
+        name: 'Icons',
+        meta: { title: 'Icons', icon: 'icon', noCache: true }
+      }
+    ]
+  },
+
+  {
+    path: '/components',
+    component: 'layout/Layout',
+    redirect: 'noRedirect',
+    name: 'ComponentDemo',
+    meta: {
+      title: 'Components',
+      icon: 'component'
+    },
+    children: [
+      {
+        path: 'tinymce',
+        component: 'views/components-demo/tinymce',
+        name: 'TinymceDemo',
+        meta: { title: 'Tinymce' }
+      },
+      {
+        path: 'markdown',
+        component: 'views/components-demo/markdown',
+        name: 'MarkdownDemo',
+        meta: { title: 'Markdown' }
+      },
+      {
+        path: 'json-editor',
+        component: 'views/components-demo/json-editor',
+        name: 'JsonEditorDemo',
+        meta: { title: 'Json Editor' }
+      },
+      {
+        path: 'split-pane',
+        component: 'views/components-demo/split-pane',
+        name: 'SplitpaneDemo',
+        meta: { title: 'SplitPane' }
+      },
+      {
+        path: 'avatar-upload',
+        component: 'views/components-demo/avatar-upload',
+        name: 'AvatarUploadDemo',
+        meta: { title: 'Avatar Upload' }
+      },
+      {
+        path: 'dropzone',
+        component: 'views/components-demo/dropzone',
+        name: 'DropzoneDemo',
+        meta: { title: 'Dropzone' }
+      },
+      {
+        path: 'sticky',
+        component: 'views/components-demo/sticky',
+        name: 'StickyDemo',
+        meta: { title: 'Sticky' }
+      },
+      {
+        path: 'count-to',
+        component: 'views/components-demo/count-to',
+        name: 'CountToDemo',
+        meta: { title: 'Count To' }
+      },
+      {
+        path: 'mixin',
+        component: 'views/components-demo/mixin',
+        name: 'ComponentMixinDemo',
+        meta: { title: 'componentMixin' }
+      },
+      {
+        path: 'back-to-top',
+        component: 'views/components-demo/back-to-top',
+        name: 'BackToTopDemo',
+        meta: { title: 'Back To Top' }
+      },
+      {
+        path: 'drag-dialog',
+        component: 'views/components-demo/drag-dialog',
+        name: 'DragDialogDemo',
+        meta: { title: 'Drag Dialog' }
+      },
+      {
+        path: 'drag-select',
+        component: 'views/components-demo/drag-select',
+        name: 'DragSelectDemo',
+        meta: { title: 'Drag Select' }
+      },
+      {
+        path: 'dnd-list',
+        component: 'views/components-demo/dnd-list',
+        name: 'DndListDemo',
+        meta: { title: 'Dnd List' }
+      },
+      {
+        path: 'drag-kanban',
+        component: 'views/components-demo/drag-kanban',
+        name: 'DragKanbanDemo',
+        meta: { title: 'Drag Kanban' }
+      }
+    ]
+  },
+  {
+    path: '/charts',
+    component: 'layout/Layout',
+    redirect: 'noRedirect',
+    name: 'Charts',
+    meta: {
+      title: 'Charts',
+      icon: 'chart'
+    },
+    children: [
+      {
+        path: 'keyboard',
+        component: 'views/charts/keyboard',
+        name: 'KeyboardChart',
+        meta: { title: 'Keyboard Chart', noCache: true }
+      },
+      {
+        path: 'line',
+        component: 'views/charts/line',
+        name: 'LineChart',
+        meta: { title: 'Line Chart', noCache: true }
+      },
+      {
+        path: 'mixchart',
+        component: 'views/charts/mixChart',
+        name: 'MixChart',
+        meta: { title: 'Mix Chart', noCache: true }
+      }
+    ]
+  },
+  {
+    path: '/nested',
+    component: 'layout/Layout',
+    redirect: '/nested/menu1/menu1-1',
+    name: 'Nested',
+    meta: {
+      title: 'Nested',
+      icon: 'nested'
+    },
+    children: [
+      {
+        path: 'menu1',
+        component: 'views/nested/menu1/index',
+        name: 'Menu1',
+        meta: { title: 'Menu1' },
+        redirect: '/nested/menu1/menu1-1',
+        children: [
+          {
+            path: 'menu1-1',
+            component: 'views/nested/menu1/menu1-1',
+            name: 'Menu1-1',
+            meta: { title: 'Menu1-1' }
+          },
+          {
+            path: 'menu1-2',
+            component: 'views/nested/menu1/menu1-2',
+            name: 'Menu1-2',
+            redirect: '/nested/menu1/menu1-2/menu1-2-1',
+            meta: { title: 'Menu1-2' },
+            children: [
+              {
+                path: 'menu1-2-1',
+                component: 'views/nested/menu1/menu1-2/menu1-2-1',
+                name: 'Menu1-2-1',
+                meta: { title: 'Menu1-2-1' }
+              },
+              {
+                path: 'menu1-2-2',
+                component: 'views/nested/menu1/menu1-2/menu1-2-2',
+                name: 'Menu1-2-2',
+                meta: { title: 'Menu1-2-2' }
+              }
+            ]
+          },
+          {
+            path: 'menu1-3',
+            component: 'views/nested/menu1/menu1-3',
+            name: 'Menu1-3',
+            meta: { title: 'Menu1-3' }
+          }
+        ]
+      },
+      {
+        path: 'menu2',
+        name: 'Menu2',
+        component: 'views/nested/menu2/index',
+        meta: { title: 'Menu2' }
+      }
+    ]
+  },
+
+  {
+    path: '/example',
+    component: 'layout/Layout',
+    redirect: '/example/list',
+    name: 'Example',
+    meta: {
+      title: 'Example',
+      icon: 'example'
+    },
+    children: [
+      {
+        path: 'create',
+        component: 'views/example/create',
+        name: 'CreateArticle',
+        meta: { title: 'Create Article', icon: 'edit' }
+      },
+      {
+        path: 'edit/:id(\\d+)',
+        component: 'views/example/edit',
+        name: 'EditArticle',
+        meta: { title: 'Edit Article', noCache: true },
+        hidden: true
+      },
+      {
+        path: 'list',
+        component: 'views/example/list',
+        name: 'ArticleList',
+        meta: { title: 'Article List', icon: 'list' }
+      }
+    ]
+  },
+
+  {
+    path: '/tab',
+    component: 'layout/Layout',
+    children: [
+      {
+        path: 'index',
+        component: 'views/tab/index',
+        name: 'Tab',
+        meta: { title: 'Tab', icon: 'tab' }
+      }
+    ]
+  },
+
+  {
+    path: '/error',
+    component: 'layout/Layout',
+    redirect: 'noRedirect',
+    name: 'ErrorPages',
+    meta: {
+      title: 'Error Pages',
+      icon: '404'
+    },
+    children: [
+      {
+        path: '401',
+        component: 'views/error-page/401',
+        name: 'Page401',
+        meta: { title: 'Page 401', noCache: true }
+      },
+      {
+        path: '404',
+        component: 'views/error-page/404',
+        name: 'Page404',
+        meta: { title: 'Page 404', noCache: true }
+      }
+    ]
+  },
+
+  {
+    path: '/error-log',
+    component: 'layout/Layout',
+    redirect: 'noRedirect',
+    children: [
+      {
+        path: 'log',
+        component: 'views/error-log/index',
+        name: 'ErrorLog',
+        meta: { title: 'Error Log', icon: 'bug' }
+      }
+    ]
+  },
+
+  {
+    path: '/excel',
+    component: 'layout/Layout',
+    redirect: '/excel/export-excel',
+    name: 'Excel',
+    meta: {
+      title: 'Excel',
+      icon: 'excel'
+    },
+    children: [
+      {
+        path: 'export-excel',
+        component: 'views/excel/export-excel',
+        name: 'ExportExcel',
+        meta: { title: 'Export Excel' }
+      },
+      {
+        path: 'export-selected-excel',
+        component: 'views/excel/select-excel',
+        name: 'SelectExcel',
+        meta: { title: 'Select Excel' }
+      },
+      {
+        path: 'export-merge-header',
+        component: 'views/excel/merge-header',
+        name: 'MergeHeader',
+        meta: { title: 'Merge Header' }
+      },
+      {
+        path: 'upload-excel',
+        component: 'views/excel/upload-excel',
+        name: 'UploadExcel',
+        meta: { title: 'Upload Excel' }
+      }
+    ]
+  },
+
+  {
+    path: '/zip',
+    component: 'layout/Layout',
+    redirect: '/zip/download',
+    alwaysShow: true,
+    meta: { title: 'Zip', icon: 'zip' },
+    children: [
+      {
+        path: 'download',
+        component: 'views/zip/index',
+        name: 'ExportZip',
+        meta: { title: 'Export Zip' }
+      }
+    ]
+  },
+
+  {
+    path: '/pdf',
+    component: 'layout/Layout',
+    redirect: '/pdf/index',
+    children: [
+      {
+        path: 'index',
+        component: 'views/pdf/index',
+        name: 'PDF',
+        meta: { title: 'PDF', icon: 'pdf' }
+      }
+    ]
+  },
+  {
+    path: '/pdf/download',
+    component: 'views/pdf/download',
+    hidden: true
+  },
+
+  {
+    path: '/theme',
+    component: 'layout/Layout',
+    redirect: 'noRedirect',
+    children: [
+      {
+        path: 'index',
+        component: 'views/theme/index',
+        name: 'Theme',
+        meta: { title: 'Theme', icon: 'theme' }
+      }
+    ]
+  },
+
+  {
+    path: '/clipboard',
+    component: 'layout/Layout',
+    redirect: 'noRedirect',
+    children: [
+      {
+        path: 'index',
+        component: 'views/clipboard/index',
+        name: 'ClipboardDemo',
+        meta: { title: 'Clipboard Demo', icon: 'clipboard' }
+      }
+    ]
+  },
+
+  {
+    path: '/i18n',
+    component: 'layout/Layout',
+    children: [
+      {
+        path: 'index',
+        component: 'views/i18n-demo/index',
+        name: 'I18n',
+        meta: { title: 'I18n', icon: 'international' }
+      }
+    ]
+  },
+
+  {
+    path: 'external-link',
+    component: 'layout/Layout',
+    children: [
+      {
+        path: 'https://github.com/PanJiaChen/vue-element-admin',
+        meta: { title: 'External Link', icon: 'link' }
+      }
+    ]
+  },
+
+  { path: '*', redirect: '/404', hidden: true }
+]
+
+module.exports = {
+  constantRoutes,
+  asyncRoutes
+}

+ 39 - 0
mock/supplier.js

@@ -0,0 +1,39 @@
+const Mock = require('mockjs')
+
+const supplierList = []
+const count = 20
+
+for (let i = 0; i < count; i++) {
+  supplierList.push(Mock.mock({
+    authUserId: '@increment(1)',
+    brandId: '@id', // 品牌id
+    name: '@cname',
+    linkMan: '@cname', // 联系人
+    mobile: '18888888888', // 手机号
+    pageNum: 0, // 页码
+    pageSize: 20, // 分页大小
+    shopName: '@cname', // 供应商名称
+    shopType: '@boolean', // 供应商类型
+    shopStatus: '@integer(0,1)',
+    brandName: '@ctitle',
+    createTime: '@date',
+    createBy: '@name'
+  }))
+}
+
+module.exports = [
+  {
+    url: '/shop/list',
+    type: 'get',
+    response: config => {
+      return {
+        code: 0,
+        data: {
+          list: supplierList,
+          total: supplierList.length
+        }
+      }
+    }
+  }
+]
+

+ 100 - 84
mock/user.js

@@ -1,84 +1,100 @@
-
-const tokens = {
-  admin: {
-    token: 'admin-token'
-  },
-  editor: {
-    token: 'editor-token'
-  }
-}
-
-const users = {
-  'admin-token': {
-    roles: ['admin'],
-    introduction: 'I am a super administrator',
-    avatar: 'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif',
-    name: 'Super Admin'
-  },
-  'editor-token': {
-    roles: ['editor'],
-    introduction: 'I am an editor',
-    avatar: 'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif',
-    name: 'Normal Editor'
-  }
-}
-
-module.exports = [
-  // user login
-  {
-    url: '/vue-element-admin/user/login',
-    type: 'post',
-    response: config => {
-      const { username } = config.body
-      const token = tokens[username]
-
-      // mock error
-      if (!token) {
-        return {
-          code: 60204,
-          message: 'Account and password are incorrect.'
-        }
-      }
-
-      return {
-        code: 20000,
-        data: token
-      }
-    }
-  },
-
-  // get user info
-  {
-    url: '/vue-element-admin/user/info\.*',
-    type: 'get',
-    response: config => {
-      const { token } = config.query
-      const info = users[token]
-
-      // mock error
-      if (!info) {
-        return {
-          code: 50008,
-          message: 'Login failed, unable to get user details.'
-        }
-      }
-
-      return {
-        code: 20000,
-        data: info
-      }
-    }
-  },
-
-  // user logout
-  {
-    url: '/vue-element-admin/user/logout',
-    type: 'post',
-    response: _ => {
-      return {
-        code: 20000,
-        data: 'success'
-      }
-    }
-  }
-]
+
+// const tokens = {
+//   admin: {
+//     token: 'admin-token'
+//   },
+//   editor: {
+//     token: 'editor-token'
+//   }
+// }
+
+const users = {
+  'admin-token': {
+    roles: ['admin'],
+    introduction: 'I am a super administrator',
+    avatar: 'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif',
+    name: 'Super Admin'
+  },
+  'editor-token': {
+    roles: ['editor'],
+    introduction: 'I am an editor',
+    avatar: 'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif',
+    name: 'Normal Editor'
+  }
+}
+
+module.exports = [
+  {
+    url: '/user/login/password',
+    type: 'post',
+    response: config => {
+      return {
+        code: 0,
+        data: {
+          shopStatus: 1,
+          authUserId: 1,
+          userIdentity: 1,
+          token: 'abc-token',
+          name: '用户'
+        }
+      }
+    }
+  },
+  // user login
+  // {
+  //   url: '/vue-element-admin/user/login',
+  //   type: 'post',
+  //   response: config => {
+  //     const { username } = config.body
+  //     const token = tokens[username]
+
+  //     // mock error
+  //     if (!token) {
+  //       return {
+  //         code: 60204,
+  //         message: 'Account and password are incorrect.'
+  //       }
+  //     }
+
+  //     return {
+  //       code: 20000,
+  //       data: token
+  //     }
+  //   }
+  // },
+
+  // get user info
+  {
+    url: '/vue-element-admin/user/info\.*',
+    type: 'get',
+    response: config => {
+      const { token } = config.query
+      const info = users[token]
+
+      // mock error
+      if (!info) {
+        return {
+          code: 50008,
+          message: 'Login failed, unable to get user details.'
+        }
+      }
+
+      return {
+        code: 20000,
+        data: info
+      }
+    }
+  },
+
+  // user logout
+  {
+    url: '/vue-element-admin/user/logout',
+    type: 'post',
+    response: _ => {
+      return {
+        code: 20000,
+        data: 'success'
+      }
+    }
+  }
+]

+ 9 - 0
src/api/auth.js

@@ -0,0 +1,9 @@
+import request from '@/utils/request'
+
+export function fecthAuthList(params) {
+  return request({
+    url: '/auth/list',
+    method: 'get',
+    params
+  })
+}

+ 19 - 0
src/api/supplier.js

@@ -0,0 +1,19 @@
+import request from '@/utils/request'
+
+// 供应商列表
+export function fetchSupplierList(params) {
+  return request({
+    url: '/shop/list',
+    method: 'get',
+    params
+  })
+}
+
+// 供应商状态
+export function supplierStatusChange(params) {
+  return request({
+    url: '/shop/update/status',
+    method: 'post',
+    params
+  })
+}

+ 12 - 0
src/api/upload.js

@@ -0,0 +1,12 @@
+import request from '@/utils/request'
+
+export function uploadImage(data) {
+  return request({
+    headers: {
+      'Content-type': 'multipart/form-data'
+    },
+    url: '/upload/image',
+    data: data,
+    method: 'post'
+  })
+}

+ 32 - 24
src/api/user.js

@@ -1,24 +1,32 @@
-import request from '@/utils/request'
-
-export function login(data) {
-  return request({
-    url: '/vue-element-admin/user/login',
-    method: 'post',
-    data
-  })
-}
-
-export function getInfo(token) {
-  return request({
-    url: '/vue-element-admin/user/info',
-    method: 'get',
-    params: { token }
-  })
-}
-
-export function logout() {
-  return request({
-    url: '/vue-element-admin/user/logout',
-    method: 'post'
-  })
-}
+import request from '@/utils/request'
+
+export function login(data) {
+  return request({
+    url: '/user/login/password',
+    method: 'post',
+    data
+  })
+}
+
+export function modifyPassword(data) {
+  return request({
+    url: '/user/update/password',
+    method: 'post',
+    data
+  })
+}
+
+// export function getInfo(token) {
+//   return request({
+//     url: '/vue-element-admin/user/info',
+//     method: 'get',
+//     params: { token }
+//   })
+// }
+
+// export function logout() {
+//   return request({
+//     url: '/vue-element-admin/user/logout',
+//     method: 'post'
+//   })
+// }

+ 1539 - 0
src/common/country.js

@@ -0,0 +1,1539 @@
+module.exports = [
+  {
+    'short': 'AD',
+    'name': '安道尔共和国',
+    'en': 'Andorra',
+    'tel': '376',
+    'pinyin': 'adeghg'
+  },
+  {
+    'short': 'AE',
+    'name': '阿拉伯联合酋长国',
+    'en': 'UnitedArabEmirates',
+    'tel': '971',
+    'pinyin': 'alblhqzg'
+  },
+  {
+    'short': 'AF',
+    'name': '阿富汗',
+    'en': 'Afghanistan',
+    'tel': '93',
+    'pinyin': 'afh'
+  },
+  {
+    'short': 'AG',
+    'name': '安提瓜和巴布达',
+    'en': 'AntiguaandBarbuda',
+    'tel': '1268',
+    'pinyin': 'atghbbd'
+  },
+  {
+    'short': 'AI',
+    'name': '安圭拉岛',
+    'en': 'Anguilla',
+    'tel': '1264',
+    'pinyin': 'agld'
+  },
+  {
+    'short': 'AL',
+    'name': '阿尔巴尼亚',
+    'en': 'Albania',
+    'tel': '355',
+    'pinyin': 'aebny'
+  },
+  {
+    'short': 'AM',
+    'name': '阿美尼亚',
+    'en': 'Armenia',
+    'tel': '374',
+    'pinyin': 'amny'
+  },
+  {
+    'short': '',
+    'name': '阿森松',
+    'en': 'Ascension',
+    'tel': '247',
+    'pinyin': 'als'
+
+  },
+  {
+    'short': 'AO',
+    'name': '安哥拉',
+    'en': 'Angola',
+    'tel': '244',
+    'pinyin': 'agl'
+
+  },
+  {
+    'short': 'AR',
+    'name': '阿根廷',
+    'en': 'Argentina',
+    'tel': '54',
+    'pinyin': 'agt'
+
+  },
+  {
+    'short': 'AT',
+    'name': '奥地利',
+    'en': 'Austria',
+    'tel': '43',
+    'pinyin': 'adl'
+
+  },
+  {
+    'short': 'AU',
+    'name': '澳大利亚',
+    'en': 'Australia',
+    'tel': '61',
+    'pinyin': 'adly'
+
+  },
+  {
+    'short': 'AZ',
+    'name': '阿塞拜疆',
+    'en': 'Azerbaijan',
+    'tel': '994',
+    'pinyin': 'asbj'
+
+  },
+  {
+    'short': 'BB',
+    'name': '巴巴多斯',
+    'en': 'Barbados',
+    'tel': '1246',
+    'pinyin': 'bbds'
+
+  },
+  {
+    'short': 'BD',
+    'name': '孟加拉国',
+    'en': 'Bangladesh',
+    'tel': '880',
+    'pinyin': 'mjlg'
+
+  },
+  {
+    'short': 'BE',
+    'name': '比利时',
+    'en': 'Belgium',
+    'tel': '32',
+    'pinyin': 'bls'
+
+  },
+  {
+    'short': 'BF',
+    'name': '布基纳法索',
+    'en': 'Burkina-faso',
+    'tel': '226',
+    'pinyin': 'bjnfs'
+
+  },
+  {
+    'short': 'BG',
+    'name': '保加利亚',
+    'en': 'Bulgaria',
+    'tel': '359',
+    'pinyin': 'bjly'
+
+  },
+  {
+    'short': 'BH',
+    'name': '巴林',
+    'en': 'Bahrain',
+    'tel': '973',
+    'pinyin': 'bl'
+
+  },
+  {
+    'short': 'BI',
+    'name': '布隆迪',
+    'en': 'Burundi',
+    'tel': '257',
+    'pinyin': 'bld'
+
+  },
+  {
+    'short': 'BJ',
+    'name': '贝宁',
+    'en': 'Benin',
+    'tel': '229',
+    'pinyin': 'bl'
+
+  },
+  {
+    'short': 'BL',
+    'name': '巴勒斯坦',
+    'en': 'Palestine',
+    'tel': '970',
+    'pinyin': 'blst'
+
+  },
+  {
+    'short': 'BM',
+    'name': '百慕大群岛',
+    'en': 'BermudaIs.',
+    'tel': '1441',
+    'pinyin': 'bmdqd'
+
+  },
+  {
+    'short': 'BN',
+    'name': '文莱',
+    'en': 'Brunei',
+    'tel': '673',
+    'pinyin': 'wl'
+
+  },
+  {
+    'short': 'BO',
+    'name': '玻利维亚',
+    'en': 'Bolivia',
+    'tel': '591',
+    'pinyin': 'blwy'
+
+  },
+  {
+    'short': 'BR',
+    'name': '巴西',
+    'en': 'Brazil',
+    'tel': '55',
+    'pinyin': 'bx'
+
+  },
+  {
+    'short': 'BS',
+    'name': '巴哈马',
+    'en': 'Bahamas',
+    'tel': '1242',
+    'pinyin': 'bhm'
+
+  },
+  {
+    'short': 'BW',
+    'name': '博茨瓦纳',
+    'en': 'Botswana',
+    'tel': '267',
+    'pinyin': 'bcwn'
+
+  },
+  {
+    'short': 'BY',
+    'name': '白俄罗斯',
+    'en': 'Belarus',
+    'tel': '375',
+    'pinyin': 'bels'
+
+  },
+  {
+    'short': 'BZ',
+    'name': '伯利兹',
+    'en': 'Belize',
+    'tel': '501',
+    'pinyin': 'blz'
+
+  },
+  {
+    'short': 'CA',
+    'name': '加拿大',
+    'en': 'Canada',
+    'tel': '1',
+    'pinyin': 'jnd'
+
+  },
+  {
+    'short': '',
+    'name': '开曼群岛',
+    'en': 'CaymanIs.',
+    'tel': '1345',
+    'pinyin': 'kmqd'
+
+  },
+  {
+    'short': 'CF',
+    'name': '中非共和国',
+    'en': 'CentralAfricanRepublic',
+    'tel': '236',
+    'pinyin': 'zfghg'
+
+  },
+  {
+    'short': 'CG',
+    'name': '刚果',
+    'en': 'Congo',
+    'tel': '242',
+    'pinyin': 'gg'
+
+  },
+  {
+    'short': 'CH',
+    'name': '瑞士',
+    'en': 'Switzerland',
+    'tel': '41',
+    'pinyin': 'rs'
+
+  },
+  {
+    'short': 'CK',
+    'name': '库克群岛',
+    'en': 'CookIs.',
+    'tel': '682',
+    'pinyin': 'kkqd'
+
+  },
+  {
+    'short': 'CL',
+    'name': '智利',
+    'en': 'Chile',
+    'tel': '56',
+    'pinyin': 'zl'
+
+  },
+  {
+    'short': 'CM',
+    'name': '喀麦隆',
+    'en': 'Cameroon',
+    'tel': '237',
+    'pinyin': 'kml'
+
+  },
+  {
+    'short': 'CN',
+    'name': '中国',
+    'en': 'China',
+    'tel': '86',
+    'pinyin': 'zg'
+
+  },
+  {
+    'short': 'CO',
+    'name': '哥伦比亚',
+    'en': 'Colombia',
+    'tel': '57',
+    'pinyin': 'glby'
+
+  },
+  {
+    'short': 'CR',
+    'name': '哥斯达黎加',
+    'en': 'CostaRica',
+    'tel': '506',
+    'pinyin': 'gsdlj'
+
+  },
+  {
+    'short': 'CS',
+    'name': '捷克',
+    'en': 'Czech',
+    'tel': '420',
+    'pinyin': 'jk'
+
+  },
+  {
+    'short': 'CU',
+    'name': '古巴',
+    'en': 'Cuba',
+    'tel': '53',
+    'pinyin': 'gb'
+
+  },
+  {
+    'short': 'CY',
+    'name': '塞浦路斯',
+    'en': 'Cyprus',
+    'tel': '357',
+    'pinyin': 'spls'
+
+  },
+  {
+    'short': 'CZ',
+    'name': '捷克',
+    'en': 'CzechRepublic',
+    'tel': '420',
+    'pinyin': 'jk'
+
+  },
+  {
+    'short': 'DE',
+    'name': '德国',
+    'en': 'Germany',
+    'tel': '49',
+    'pinyin': 'dg'
+
+  },
+  {
+    'short': 'DJ',
+    'name': '吉布提',
+    'en': 'Djibouti',
+    'tel': '253',
+    'pinyin': 'jbt'
+
+  },
+  {
+    'short': 'DK',
+    'name': '丹麦',
+    'en': 'Denmark',
+    'tel': '45',
+    'pinyin': 'dm'
+
+  },
+  {
+    'short': 'DO',
+    'name': '多米尼加共和国',
+    'en': 'DominicaRep.',
+    'tel': '1890',
+    'pinyin': 'dmnjghg'
+
+  },
+  {
+    'short': 'DZ',
+    'name': '阿尔及利亚',
+    'en': 'Algeria',
+    'tel': '213',
+    'pinyin': 'aejly'
+
+  },
+  {
+    'short': 'EC',
+    'name': '厄瓜多尔',
+    'en': 'Ecuador',
+    'tel': '593',
+    'pinyin': 'egde'
+
+  },
+  {
+    'short': 'EE',
+    'name': '爱沙尼亚',
+    'en': 'Estonia',
+    'tel': '372',
+    'pinyin': 'asny'
+
+  },
+  {
+    'short': 'EG',
+    'name': '埃及',
+    'en': 'Egypt',
+    'tel': '20',
+    'pinyin': 'ej'
+
+  },
+  {
+    'short': 'ES',
+    'name': '西班牙',
+    'en': 'Spain',
+    'tel': '34',
+    'pinyin': 'xby'
+
+  },
+  {
+    'short': 'ET',
+    'name': '埃塞俄比亚',
+    'en': 'Ethiopia',
+    'tel': '251',
+    'pinyin': 'aseby'
+
+  },
+  {
+    'short': 'FI',
+    'name': '芬兰',
+    'en': 'Finland',
+    'tel': '358',
+    'pinyin': 'fl'
+
+  },
+  {
+    'short': 'FJ',
+    'name': '斐济',
+    'en': 'Fiji',
+    'tel': '679',
+    'pinyin': 'fj'
+
+  },
+  {
+    'short': 'FR',
+    'name': '法国',
+    'en': 'France',
+    'tel': '33',
+    'pinyin': 'fg'
+
+  },
+  {
+    'short': 'GA',
+    'name': '加蓬',
+    'en': 'Gabon',
+    'tel': '241',
+    'pinyin': 'jp'
+
+  },
+  {
+    'short': 'GB',
+    'name': '英国',
+    'en': 'UnitedKiongdom',
+    'tel': '44',
+    'pinyin': 'yg'
+
+  },
+  {
+    'short': 'GD',
+    'name': '格林纳达',
+    'en': 'Grenada',
+    'tel': '1809',
+    'pinyin': 'glnd'
+
+  },
+  {
+    'short': 'GE',
+    'name': '格鲁吉亚',
+    'en': 'Georgia',
+    'tel': '995',
+    'pinyin': 'gljy'
+
+  },
+  {
+    'short': 'GF',
+    'name': '法属圭亚那',
+    'en': 'FrenchGuiana',
+    'tel': '594',
+    'pinyin': 'fsgyn'
+
+  },
+  {
+    'short': 'GH',
+    'name': '加纳',
+    'en': 'Ghana',
+    'tel': '233',
+    'pinyin': 'jn'
+
+  },
+  {
+    'short': 'GI',
+    'name': '直布罗陀',
+    'en': 'Gibraltar',
+    'tel': '350',
+    'pinyin': 'zblt'
+
+  },
+  {
+    'short': 'GM',
+    'name': '冈比亚',
+    'en': 'Gambia',
+    'tel': '220',
+    'pinyin': 'gby'
+
+  },
+  {
+    'short': 'GN',
+    'name': '几内亚',
+    'en': 'Guinea',
+    'tel': '224',
+    'pinyin': 'jny'
+
+  },
+  {
+    'short': 'GR',
+    'name': '希腊',
+    'en': 'Greece',
+    'tel': '30',
+    'pinyin': 'xl'
+
+  },
+  {
+    'short': 'GT',
+    'name': '危地马拉',
+    'en': 'Guatemala',
+    'tel': '502',
+    'pinyin': 'wdml'
+
+  },
+  {
+    'short': 'GU',
+    'name': '关岛',
+    'en': 'Guam',
+    'tel': '1671',
+    'pinyin': 'gd'
+
+  },
+  {
+    'short': 'GY',
+    'name': '圭亚那',
+    'en': 'Guyana',
+    'tel': '592',
+    'pinyin': 'gyn'
+
+  },
+  {
+    'short': 'HK',
+    'name': '香港(中国)',
+    'en': 'Hongkong',
+    'tel': '852',
+    'pinyin': 'xgzg'
+
+  },
+  {
+    'short': 'HN',
+    'name': '洪都拉斯',
+    'en': 'Honduras',
+    'tel': '504',
+    'pinyin': 'hdls'
+
+  },
+  {
+    'short': 'HT',
+    'name': '海地',
+    'en': 'Haiti',
+    'tel': '509',
+    'pinyin': 'hd'
+
+  },
+  {
+    'short': 'HU',
+    'name': '匈牙利',
+    'en': 'Hungary',
+    'tel': '36',
+    'pinyin': 'xyl'
+
+  },
+  {
+    'short': 'ID',
+    'name': '印度尼西亚',
+    'en': 'Indonesia',
+    'tel': '62',
+    'pinyin': 'ydnxy'
+
+  },
+  {
+    'short': 'IE',
+    'name': '爱尔兰',
+    'en': 'Ireland',
+    'tel': '353',
+    'pinyin': 'ael'
+
+  },
+  {
+    'short': 'IL',
+    'name': '以色列',
+    'en': 'Israel',
+    'tel': '972',
+    'pinyin': 'ysl'
+
+  },
+  {
+    'short': 'IN',
+    'name': '印度',
+    'en': 'India',
+    'tel': '91',
+    'pinyin': 'yd'
+
+  },
+  {
+    'short': 'IQ',
+    'name': '伊拉克',
+    'en': 'Iraq',
+    'tel': '964',
+    'pinyin': 'ylk'
+
+  },
+  {
+    'short': 'IR',
+    'name': '伊朗',
+    'en': 'Iran',
+    'tel': '98',
+    'pinyin': 'yl'
+
+  },
+  {
+    'short': 'IS',
+    'name': '冰岛',
+    'en': 'Iceland',
+    'tel': '354',
+    'pinyin': 'bd'
+
+  },
+  {
+    'short': 'IT',
+    'name': '意大利',
+    'en': 'Italy',
+    'tel': '39',
+    'pinyin': 'ydl'
+
+  },
+  {
+    'short': '',
+    'name': '科特迪瓦',
+    'en': 'IvoryCoast',
+    'tel': '225',
+    'pinyin': 'ktdw'
+
+  },
+  {
+    'short': 'JM',
+    'name': '牙买加',
+    'en': 'Jamaica',
+    'tel': '1876',
+    'pinyin': 'ymj'
+
+  },
+  {
+    'short': 'JO',
+    'name': '约旦',
+    'en': 'Jordan',
+    'tel': '962',
+    'pinyin': 'yd'
+
+  },
+  {
+    'short': 'JP',
+    'name': '日本',
+    'en': 'Japan',
+    'tel': '81',
+    'pinyin': 'rb'
+
+  },
+  {
+    'short': 'KE',
+    'name': '肯尼亚',
+    'en': 'Kenya',
+    'tel': '254',
+    'pinyin': 'kny'
+
+  },
+  {
+    'short': 'KG',
+    'name': '吉尔吉斯坦',
+    'en': 'Kyrgyzstan',
+    'tel': '331',
+    'pinyin': 'jejst'
+
+  },
+  {
+    'short': 'KH',
+    'name': '柬埔寨',
+    'en': 'Kampuchea(Cambodia)',
+    'tel': '855',
+    'pinyin': 'jpz'
+
+  },
+  {
+    'short': 'KP',
+    'name': '朝鲜',
+    'en': 'NorthKorea',
+    'tel': '850',
+    'pinyin': 'cx'
+
+  },
+  {
+    'short': 'KR',
+    'name': '韩国',
+    'en': 'Korea',
+    'tel': '82',
+    'pinyin': 'hg'
+
+  },
+  {
+    'short': 'KT',
+    'name': '科特迪瓦共和国',
+    'en': 'RepublicofIvoryCoast',
+    'tel': '225',
+    'pinyin': 'ktdwghg'
+
+  },
+  {
+    'short': 'KW',
+    'name': '科威特',
+    'en': 'Kuwait',
+    'tel': '965',
+    'pinyin': 'kwt'
+
+  },
+  {
+    'short': 'KZ',
+    'name': '哈萨克斯坦',
+    'en': 'Kazakstan',
+    'tel': '327',
+    'pinyin': 'hskst'
+
+  },
+  {
+    'short': 'LA',
+    'name': '老挝',
+    'en': 'Laos',
+    'tel': '856',
+    'pinyin': 'lw'
+
+  },
+  {
+    'short': 'LB',
+    'name': '黎巴嫩',
+    'en': 'Lebanon',
+    'tel': '961',
+    'pinyin': 'lbn'
+
+  },
+  {
+    'short': 'LC',
+    'name': '圣卢西亚',
+    'en': 'St.Lucia',
+    'tel': '1758',
+    'pinyin': 'slxy'
+
+  },
+  {
+    'short': 'LI',
+    'name': '列支敦士登',
+    'en': 'Liechtenstein',
+    'tel': '423',
+    'pinyin': 'lzdsd'
+
+  },
+  {
+    'short': 'LK',
+    'name': '斯里兰卡',
+    'en': 'SriLanka',
+    'tel': '94',
+    'pinyin': 'sllk'
+
+  },
+  {
+    'short': 'LR',
+    'name': '利比里亚',
+    'en': 'Liberia',
+    'tel': '231',
+    'pinyin': 'lbly'
+
+  },
+  {
+    'short': 'LS',
+    'name': '莱索托',
+    'en': 'Lesotho',
+    'tel': '266',
+    'pinyin': 'lst'
+
+  },
+  {
+    'short': 'LT',
+    'name': '立陶宛',
+    'en': 'Lithuania',
+    'tel': '370',
+    'pinyin': 'ltw'
+
+  },
+  {
+    'short': 'LU',
+    'name': '卢森堡',
+    'en': 'Luxembourg',
+    'tel': '352',
+    'pinyin': 'lsb'
+
+  },
+  {
+    'short': 'LV',
+    'name': '拉脱维亚',
+    'en': 'Latvia',
+    'tel': '371',
+    'pinyin': 'ltwy'
+
+  },
+  {
+    'short': 'LY',
+    'name': '利比亚',
+    'en': 'Libya',
+    'tel': '218',
+    'pinyin': 'lby'
+
+  },
+  {
+    'short': 'MA',
+    'name': '摩洛哥',
+    'en': 'Morocco',
+    'tel': '212',
+    'pinyin': 'mlg'
+
+  },
+  {
+    'short': 'MC',
+    'name': '摩纳哥',
+    'en': 'Monaco',
+    'tel': '377',
+    'pinyin': 'mng'
+
+  },
+  {
+    'short': 'MD',
+    'name': '摩尔多瓦',
+    'en': 'Moldova,Republicof',
+    'tel': '373',
+    'pinyin': 'medw'
+
+  },
+  {
+    'short': 'MG',
+    'name': '马达加斯加',
+    'en': 'Madagascar',
+    'tel': '261',
+    'pinyin': 'mdjsj'
+
+  },
+  {
+    'short': 'ML',
+    'name': '马里',
+    'en': 'Mali',
+    'tel': '223',
+    'pinyin': 'ml'
+
+  },
+  {
+    'short': 'MM',
+    'name': '缅甸',
+    'en': 'Burma',
+    'tel': '95',
+    'pinyin': 'md'
+
+  },
+  {
+    'short': 'MN',
+    'name': '蒙古',
+    'en': 'Mongolia',
+    'tel': '976',
+    'pinyin': 'mg'
+
+  },
+  {
+    'short': 'MO',
+    'name': '澳门(中国)',
+    'en': 'Macao',
+    'tel': '853',
+    'pinyin': 'am zg'
+
+  },
+  {
+    'short': 'MS',
+    'name': '蒙特塞拉特岛',
+    'en': 'MontserratIs',
+    'tel': '1664',
+    'pinyin': 'mtsstd'
+
+  },
+  {
+    'short': 'MT',
+    'name': '马耳他',
+    'en': 'Malta',
+    'tel': '356',
+    'pinyin': 'met'
+
+  },
+  {
+    'short': '',
+    'name': '马里亚那群岛',
+    'en': 'MarianaIs',
+    'tel': '1670',
+    'pinyin': 'mlynqd'
+
+  },
+  {
+    'short': '',
+    'name': '马提尼克',
+    'en': 'Martinique',
+    'tel': '596',
+    'pinyin': 'mtnk'
+
+  },
+  {
+    'short': 'MU',
+    'name': '毛里求斯',
+    'en': 'Mauritius',
+    'tel': '230',
+    'pinyin': 'mlqs'
+
+  },
+  {
+    'short': 'MV',
+    'name': '马尔代夫',
+    'en': 'Maldives',
+    'tel': '960',
+    'pinyin': 'medf'
+
+  },
+  {
+    'short': 'MW',
+    'name': '马拉维',
+    'en': 'Malawi',
+    'tel': '265',
+    'pinyin': 'mlw'
+
+  },
+  {
+    'short': 'MX',
+    'name': '墨西哥',
+    'en': 'Mexico',
+    'tel': '52',
+    'pinyin': 'mxg'
+
+  },
+  {
+    'short': 'MY',
+    'name': '马来西亚',
+    'en': 'Malaysia',
+    'tel': '60',
+    'pinyin': 'mlxy'
+
+  },
+  {
+    'short': 'MZ',
+    'name': '莫桑比克',
+    'en': 'Mozambique',
+    'tel': '258',
+    'pinyin': 'msbk'
+
+  },
+  {
+    'short': 'NA',
+    'name': '纳米比亚',
+    'en': 'Namibia',
+    'tel': '264',
+    'pinyin': 'nmby'
+
+  },
+  {
+    'short': 'NE',
+    'name': '尼日尔',
+    'en': 'Niger',
+    'tel': '977',
+    'pinyin': 'nre'
+
+  },
+  {
+    'short': 'NG',
+    'name': '尼日利亚',
+    'en': 'Nigeria',
+    'tel': '234',
+    'pinyin': 'nrly'
+
+  },
+  {
+    'short': 'NI',
+    'name': '尼加拉瓜',
+    'en': 'Nicaragua',
+    'tel': '505',
+    'pinyin': 'njlg'
+
+  },
+  {
+    'short': 'NL',
+    'name': '荷兰',
+    'en': 'Netherlands',
+    'tel': '31',
+    'pinyin': 'hl'
+
+  },
+  {
+    'short': 'NO',
+    'name': '挪威',
+    'en': 'Norway',
+    'tel': '47',
+    'pinyin': 'nw'
+
+  },
+  {
+    'short': 'NP',
+    'name': '尼泊尔',
+    'en': 'Nepal',
+    'tel': '977',
+    'pinyin': 'nbe'
+
+  },
+  {
+    'short': '',
+    'name': '荷属安的列斯',
+    'en': 'NetheriandsAntilles',
+    'tel': '599',
+    'pinyin': 'hsadls'
+
+  },
+  {
+    'short': 'NR',
+    'name': '瑙鲁',
+    'en': 'Nauru',
+    'tel': '674',
+    'pinyin': 'nl'
+
+  },
+  {
+    'short': 'NZ',
+    'name': '新西兰',
+    'en': 'NewZealand',
+    'tel': '64',
+    'pinyin': 'xxl'
+
+  },
+  {
+    'short': 'OM',
+    'name': '阿曼',
+    'en': 'Oman',
+    'tel': '968',
+    'pinyin': 'am'
+
+  },
+  {
+    'short': 'PA',
+    'name': '巴拿马',
+    'en': 'Panama',
+    'tel': '507',
+    'pinyin': 'bnm'
+
+  },
+  {
+    'short': 'PE',
+    'name': '秘鲁',
+    'en': 'Peru',
+    'tel': '51',
+    'pinyin': 'bl'
+
+  },
+  {
+    'short': 'PF',
+    'name': '法属玻利尼西亚',
+    'en': 'FrenchPolynesia',
+    'tel': '689',
+    'pinyin': 'fsblnxy'
+
+  },
+  {
+    'short': 'PG',
+    'name': '巴布亚新几内亚',
+    'en': 'PapuaNewCuinea',
+    'tel': '675',
+    'pinyin': 'bbyxjny'
+
+  },
+  {
+    'short': 'PH',
+    'name': '菲律宾',
+    'en': 'Philippines',
+    'tel': '63',
+    'pinyin': 'flb'
+
+  },
+  {
+    'short': 'PK',
+    'name': '巴基斯坦',
+    'en': 'Pakistan',
+    'tel': '92',
+    'pinyin': 'bjst'
+
+  },
+  {
+    'short': 'PL',
+    'name': '波兰',
+    'en': 'Poland',
+    'tel': '48',
+    'pinyin': 'bl'
+
+  },
+  {
+    'short': 'PR',
+    'name': '波多黎各',
+    'en': 'PuertoRico',
+    'tel': '1787',
+    'pinyin': 'bdlg'
+
+  },
+  {
+    'short': 'PT',
+    'name': '葡萄牙',
+    'en': 'Portugal',
+    'tel': '351',
+    'pinyin': 'pty'
+
+  },
+  {
+    'short': 'PY',
+    'name': '巴拉圭',
+    'en': 'Paraguay',
+    'tel': '595',
+    'pinyin': 'blg'
+
+  },
+  {
+    'short': 'QA',
+    'name': '卡塔尔',
+    'en': 'Qatar',
+    'tel': '974',
+    'pinyin': 'kte'
+
+  },
+  {
+    'short': '',
+    'name': '留尼旺',
+    'en': 'Reunion',
+    'tel': '262',
+    'pinyin': 'lnw'
+
+  },
+  {
+    'short': 'RO',
+    'name': '罗马尼亚',
+    'en': 'Romania',
+    'tel': '40',
+    'pinyin': 'lmny'
+
+  },
+  {
+    'short': 'RU',
+    'name': '俄罗斯',
+    'en': 'Russia',
+    'tel': '7',
+    'pinyin': 'els'
+
+  },
+  {
+    'short': 'SA',
+    'name': '沙特阿拉伯',
+    'en': 'SaudiArabia',
+    'tel': '966',
+    'pinyin': 'stalb'
+
+  },
+  {
+    'short': 'SB',
+    'name': '所罗门群岛',
+    'en': 'SolomonIs',
+    'tel': '677',
+    'pinyin': 'slmqd'
+
+  },
+  {
+    'short': 'SC',
+    'name': '塞舌尔',
+    'en': 'Seychelles',
+    'tel': '248',
+    'pinyin': 'sse'
+
+  },
+  {
+    'short': 'SD',
+    'name': '苏丹',
+    'en': 'Sudan',
+    'tel': '249',
+    'pinyin': 'sd'
+
+  },
+  {
+    'short': 'SE',
+    'name': '瑞典',
+    'en': 'Sweden',
+    'tel': '46',
+    'pinyin': 'rd'
+
+  },
+  {
+    'short': 'SG',
+    'name': '新加坡',
+    'en': 'Singapore',
+    'tel': '65',
+    'pinyin': 'xjp'
+
+  },
+  {
+    'short': 'SI',
+    'name': '斯洛文尼亚',
+    'en': 'Slovenia',
+    'tel': '386',
+    'pinyin': 'slwny'
+
+  },
+  {
+    'short': 'SK',
+    'name': '斯洛伐克',
+    'en': 'Slovakia',
+    'tel': '421',
+    'pinyin': 'slfk'
+
+  },
+  {
+    'short': 'SL',
+    'name': '塞拉利昂',
+    'en': 'SierraLeone',
+    'tel': '232',
+    'pinyin': 'slla'
+
+  },
+  {
+    'short': 'SM',
+    'name': '圣马力诺',
+    'en': 'SanMarino',
+    'tel': '378',
+    'pinyin': 'smln'
+
+  },
+  {
+    'short': '',
+    'name': '东萨摩亚(美)',
+    'en': 'SamoaEastern',
+    'tel': '684',
+    'pinyin': 'dsmym'
+
+  },
+  {
+    'short': '',
+    'name': '西萨摩亚',
+    'en': 'SanMarino',
+    'tel': '685',
+    'pinyin': 'xsmy'
+
+  },
+  {
+    'short': 'SN',
+    'name': '塞内加尔',
+    'en': 'Senegal',
+    'tel': '221',
+    'pinyin': 'snje'
+
+  },
+  {
+    'short': 'SO',
+    'name': '索马里',
+    'en': 'Somali',
+    'tel': '252',
+    'pinyin': 'sml'
+
+  },
+  {
+    'short': 'SR',
+    'name': '苏里南',
+    'en': 'Suriname',
+    'tel': '597',
+    'pinyin': 'sln'
+
+  },
+  {
+    'short': 'ST',
+    'name': '圣多美和普林西比',
+    'en': 'SaoTomeandPrincipe',
+    'tel': '239',
+    'pinyin': 'sdmhplxb'
+
+  },
+  {
+    'short': 'SV',
+    'name': '萨尔瓦多',
+    'en': 'EISalvador',
+    'tel': '503',
+    'pinyin': 'sewd'
+
+  },
+  {
+    'short': 'SY',
+    'name': '叙利亚',
+    'en': 'Syria',
+    'tel': '963',
+    'pinyin': 'xly'
+
+  },
+  {
+    'short': 'SZ',
+    'name': '斯威士兰',
+    'en': 'Swaziland',
+    'tel': '268',
+    'pinyin': 'swsl'
+
+  },
+  {
+    'short': 'TD',
+    'name': '乍得',
+    'en': 'Chad',
+    'tel': '235',
+    'pinyin': 'zd'
+
+  },
+  {
+    'short': 'TG',
+    'name': '多哥',
+    'en': 'Togo',
+    'tel': '228',
+    'pinyin': 'dg'
+
+  },
+  {
+    'short': 'TH',
+    'name': '泰国',
+    'en': 'Thailand',
+    'tel': '66',
+    'pinyin': 'tg'
+
+  },
+  {
+    'short': 'TJ',
+    'name': '塔吉克斯坦',
+    'en': 'Tajikstan',
+    'tel': '992',
+    'pinyin': 'tjkst'
+
+  },
+  {
+    'short': 'TM',
+    'name': '土库曼斯坦',
+    'en': 'Turkmenistan',
+    'tel': '993',
+    'pinyin': 'tkmst'
+
+  },
+  {
+    'short': 'TN',
+    'name': '突尼斯',
+    'en': 'Tunisia',
+    'tel': '216',
+    'pinyin': 'tns'
+
+  },
+  {
+    'short': 'TO',
+    'name': '汤加',
+    'en': 'Tonga',
+    'tel': '676',
+    'pinyin': 'tj'
+
+  },
+  {
+    'short': 'TR',
+    'name': '土耳其',
+    'en': 'Turkey',
+    'tel': '90',
+    'pinyin': 'teq'
+
+  },
+  {
+    'short': 'TT',
+    'name': '特立尼达和多巴哥',
+    'en': 'TrinidadandTobago',
+    'tel': '1809',
+    'pinyin': 'tlndhdbg'
+
+  },
+  {
+    'short': 'TW',
+    'name': '台湾(中国)',
+    'en': 'Taiwan',
+    'tel': '886',
+    'pinyin': 'twzg'
+
+  },
+  {
+    'short': 'TZ',
+    'name': '坦桑尼亚',
+    'en': 'Tanzania',
+    'tel': '255',
+    'pinyin': 'tsny'
+
+  },
+  {
+    'short': 'UA',
+    'name': '乌克兰',
+    'en': 'Ukraine',
+    'tel': '380',
+    'pinyin': 'wkl'
+
+  },
+  {
+    'short': 'UG',
+    'name': '乌干达',
+    'en': 'Uganda',
+    'tel': '256',
+    'pinyin': 'wgd'
+
+  },
+  {
+    'short': 'US',
+    'name': '美国',
+    'en': 'UnitedStatesofAmerica',
+    'tel': '1',
+    'pinyin': 'mg'
+
+  },
+  {
+    'short': 'UY',
+    'name': '乌拉圭',
+    'en': 'Uruguay',
+    'tel': '598',
+    'pinyin': 'wlg'
+
+  },
+  {
+    'short': 'UZ',
+    'name': '乌兹别克斯坦',
+    'en': 'Uzbekistan',
+    'tel': '233',
+    'pinyin': 'wzbkst'
+
+  },
+  {
+    'short': 'VC',
+    'name': '圣文森特岛',
+    'en': 'SaintVincent',
+    'tel': '1784',
+    'pinyin': 'swstd'
+
+  },
+  {
+    'short': 'VE',
+    'name': '委内瑞拉',
+    'en': 'Venezuela',
+    'tel': '58',
+    'pinyin': 'wnrl'
+
+  },
+  {
+    'short': 'VN',
+    'name': '越南',
+    'en': 'Vietnam',
+    'tel': '84',
+    'pinyin': 'yn'
+
+  },
+  {
+    'short': 'YE',
+    'name': '也门',
+    'en': 'Yemen',
+    'tel': '967',
+    'pinyin': 'ym'
+
+  },
+  {
+    'short': 'YU',
+    'name': '南斯拉夫',
+    'en': 'Yugoslavia',
+    'tel': '381',
+    'pinyin': 'nslf'
+
+  },
+  {
+    'short': 'ZA',
+    'name': '南非',
+    'en': 'SouthAfrica',
+    'tel': '27',
+    'pinyin': 'nf'
+
+  },
+  {
+    'short': 'ZM',
+    'name': '赞比亚',
+    'en': 'Zambia',
+    'tel': '260',
+    'pinyin': 'zby'
+
+  },
+  {
+    'short': 'ZR',
+    'name': '扎伊尔',
+    'en': 'Zaire',
+    'tel': '243',
+    'pinyin': 'zye'
+
+  },
+  {
+    'short': 'ZW',
+    'name': '津巴布韦',
+    'en': 'Zimbabwe',
+    'tel': '263',
+    'pinyin': 'jbbw'
+
+  }
+]

+ 158 - 152
src/layout/components/Navbar.vue

@@ -1,152 +1,158 @@
-<template>
-  <div class="navbar">
-    <hamburger id="hamburger-container" :is-active="sidebar.opened" class="hamburger-container" @toggleClick="toggleSideBar" />
-
-    <breadcrumb id="breadcrumb-container" class="breadcrumb-container" />
-
-    <div class="right-menu">
-      <template v-if="device!=='mobile'">
-        <search id="header-search" class="right-menu-item" />
-
-        <error-log class="errLog-container right-menu-item hover-effect" />
-
-        <screenfull id="screenfull" class="right-menu-item hover-effect" />
-
-      </template>
-
-      <el-dropdown class="avatar-container right-menu-item hover-effect" trigger="click">
-        <div class="avatar-wrapper">
-          <img :src="avatar+'?imageView2/1/w/80/h/80'" class="user-avatar">
-          <i class="el-icon-caret-bottom" />
-        </div>
-        <el-dropdown-menu slot="dropdown">
-          <router-link to="/profile/index">
-            <el-dropdown-item>修改密码</el-dropdown-item>
-          </router-link>
-          <el-dropdown-item divided @click.native="logout">
-            <span style="display:block;">退出</span>
-          </el-dropdown-item>
-        </el-dropdown-menu>
-      </el-dropdown>
-    </div>
-  </div>
-</template>
-
-<script>
-import { mapGetters } from 'vuex'
-import Breadcrumb from '@/components/Breadcrumb'
-import Hamburger from '@/components/Hamburger'
-import ErrorLog from '@/components/ErrorLog'
-import Screenfull from '@/components/Screenfull'
-import Search from '@/components/HeaderSearch'
-
-export default {
-  components: {
-    Breadcrumb,
-    Hamburger,
-    ErrorLog,
-    Screenfull,
-    Search
-  },
-  computed: {
-    ...mapGetters([
-      'sidebar',
-      'avatar',
-      'device'
-    ])
-  },
-  methods: {
-    toggleSideBar() {
-      this.$store.dispatch('app/toggleSideBar')
-    },
-    async logout() {
-      await this.$store.dispatch('user/logout')
-      this.$router.push(`/login?redirect=${this.$route.fullPath}`)
-    }
-  }
-}
-</script>
-
-<style lang="scss" scoped>
-.navbar {
-  height: 50px;
-  overflow: hidden;
-  position: relative;
-  background: #fff;
-  box-shadow: 0 1px 4px rgba(0,21,41,.08);
-
-  .hamburger-container {
-    line-height: 46px;
-    height: 100%;
-    float: left;
-    cursor: pointer;
-    transition: background .3s;
-    -webkit-tap-highlight-color:transparent;
-
-    &:hover {
-      background: rgba(0, 0, 0, .025)
-    }
-  }
-
-  .breadcrumb-container {
-    float: left;
-  }
-
-  .errLog-container {
-    display: inline-block;
-    vertical-align: top;
-  }
-
-  .right-menu {
-    float: right;
-    height: 100%;
-    line-height: 50px;
-
-    &:focus {
-      outline: none;
-    }
-
-    .right-menu-item {
-      display: inline-block;
-      padding: 0 8px;
-      height: 100%;
-      font-size: 18px;
-      color: #5a5e66;
-      vertical-align: text-bottom;
-
-      &.hover-effect {
-        cursor: pointer;
-        transition: background .3s;
-
-        &:hover {
-          background: rgba(0, 0, 0, .025)
-        }
-      }
-    }
-
-    .avatar-container {
-      margin-right: 30px;
-
-      .avatar-wrapper {
-        margin-top: 5px;
-        position: relative;
-
-        .user-avatar {
-          cursor: pointer;
-          width: 40px;
-          height: 40px;
-          border-radius: 10px;
-        }
-
-        .el-icon-caret-bottom {
-          cursor: pointer;
-          position: absolute;
-          right: -20px;
-          top: 25px;
-          font-size: 12px;
-        }
-      }
-    }
-  }
-}
-</style>
+<template>
+  <div class="navbar">
+    <hamburger id="hamburger-container" :is-active="sidebar.opened" class="hamburger-container" @toggleClick="toggleSideBar" />
+
+    <breadcrumb id="breadcrumb-container" class="breadcrumb-container" />
+
+    <div class="right-menu">
+      <template v-if="device!=='mobile'">
+        <search id="header-search" class="right-menu-item" />
+
+        <error-log class="errLog-container right-menu-item hover-effect" />
+
+        <screenfull id="screenfull" class="right-menu-item hover-effect" />
+
+      </template>
+
+      <el-dropdown class="avatar-container right-menu-item hover-effect" trigger="click">
+        <div class="avatar-wrapper">
+          <img src="https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif" class="user-avatar">
+          <span>{{ name }}</span>
+          <i class="el-icon-caret-bottom" />
+        </div>
+        <el-dropdown-menu slot="dropdown">
+          <router-link to="/password/edit">
+            <el-dropdown-item>修改密码</el-dropdown-item>
+          </router-link>
+          <el-dropdown-item divided @click.native="logout">
+            <span style="display:block;">退出</span>
+          </el-dropdown-item>
+        </el-dropdown-menu>
+      </el-dropdown>
+    </div>
+  </div>
+</template>
+
+<script>
+import { mapGetters } from 'vuex'
+import Breadcrumb from '@/components/Breadcrumb'
+import Hamburger from '@/components/Hamburger'
+import ErrorLog from '@/components/ErrorLog'
+import Screenfull from '@/components/Screenfull'
+import Search from '@/components/HeaderSearch'
+
+export default {
+  components: {
+    Breadcrumb,
+    Hamburger,
+    ErrorLog,
+    Screenfull,
+    Search
+  },
+  computed: {
+    ...mapGetters([
+      'sidebar',
+      'device',
+      'name'
+    ])
+  },
+  methods: {
+    toggleSideBar() {
+      this.$store.dispatch('app/toggleSideBar')
+    },
+    async logout() {
+      await this.$store.dispatch('user/logout')
+      await this.$store.commit('permission/SET_INITROUTER', false)
+      this.$router.replace('/login')
+      this.$message.success('已退出当前账号')
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.navbar {
+  height: 50px;
+  overflow: hidden;
+  position: relative;
+  background: #fff;
+  box-shadow: 0 1px 4px rgba(0,21,41,.08);
+
+  .hamburger-container {
+    line-height: 46px;
+    height: 100%;
+    float: left;
+    cursor: pointer;
+    transition: background .3s;
+    -webkit-tap-highlight-color:transparent;
+
+    &:hover {
+      background: rgba(0, 0, 0, .025)
+    }
+  }
+
+  .breadcrumb-container {
+    float: left;
+  }
+
+  .errLog-container {
+    display: inline-block;
+    vertical-align: top;
+  }
+
+  .right-menu {
+    float: right;
+    height: 100%;
+    line-height: 50px;
+
+    &:focus {
+      outline: none;
+    }
+
+    .right-menu-item {
+      display: inline-block;
+      padding: 0 8px;
+      height: 100%;
+      font-size: 18px;
+      color: #5a5e66;
+      vertical-align: text-bottom;
+
+      &.hover-effect {
+        cursor: pointer;
+        transition: background .3s;
+
+        &:hover {
+          background: rgba(0, 0, 0, .025)
+        }
+      }
+    }
+
+    .avatar-container {
+      margin-right: 30px;
+
+      .avatar-wrapper {
+        // margin-top: 5px;
+        position: relative;
+
+        .user-avatar {
+          cursor: pointer;
+          width: 40px;
+          height: 40px;
+          border-radius: 10px;
+          vertical-align: middle;
+        }
+        span{
+          margin-left: 10px;
+        }
+        .el-icon-caret-bottom {
+          cursor: pointer;
+          position: absolute;
+          right: -20px;
+          top: 25px;
+          font-size: 12px;
+        }
+      }
+    }
+  }
+}
+</style>

+ 55 - 53
src/main.js

@@ -1,53 +1,55 @@
-import Vue from 'vue'
-
-import Cookies from 'js-cookie'
-
-import 'normalize.css/normalize.css' // a modern alternative to CSS resets
-
-import Element from 'element-ui'
-import './styles/element-variables.scss'
-import enLang from 'element-ui/lib/locale/lang/en'// 如果使用中文语言包请默认支持,无需额外引入,请删除该依赖
-
-import '@/styles/index.scss' // global css
-
-import App from './App'
-import store from './store'
-import router from './router'
-
-import './icons' // icon
-import './permission' // permission control
-import './utils/error-log' // error log
-
-import * as filters from './filters' // global filters
-
-/**
- * If you don't want to use mock-server
- * you want to use MockJs for mock api
- * you can execute: mockXHR()
- *
- * Currently MockJs will be used in the production environment,
- * please remove it before going online ! ! !
- */
-if (process.env.NODE_ENV === 'production') {
-  const { mockXHR } = require('../mock')
-  mockXHR()
-}
-
-Vue.use(Element, {
-  size: Cookies.get('size') || 'medium', // set element-ui default size
-  locale: enLang // 如果使用中文,无需设置,请删除
-})
-
-// register global utility filters
-Object.keys(filters).forEach(key => {
-  Vue.filter(key, filters[key])
-})
-
-Vue.config.productionTip = false
-
-new Vue({
-  el: '#app',
-  router,
-  store,
-  render: h => h(App)
-})
+import Vue from 'vue'
+
+import Cookies from 'js-cookie'
+
+import 'normalize.css/normalize.css' // a modern alternative to CSS resets
+
+import Element from 'element-ui'
+import './styles/element-variables.scss'
+// import enLang from 'element-ui/lib/locale/lang/en'// 如果使用中文语言包请默认支持,无需额外引入,请删除该依赖
+
+import '@/styles/index.scss' // global css
+
+import App from './App'
+import store from './store'
+import router from './router'
+
+import './icons' // icon
+import './permission' // permission control
+import './utils/error-log' // error log
+
+import * as filters from './filters' // global filters
+
+import './mixin/base' // 公共方法
+
+/**
+ * If you don't want to use mock-server
+ * you want to use MockJs for mock api
+ * you can execute: mockXHR()
+ *
+ * Currently MockJs will be used in the production environment,
+ * please remove it before going online ! ! !
+ */
+// if (process.env.NODE_ENV === 'production') {
+const { mockXHR } = require('../mock')
+mockXHR()
+// }
+
+Vue.use(Element, {
+  size: Cookies.get('size') || 'medium' // set element-ui default size
+  // locale: enLang // 如果使用中文,无需设置,请删除
+})
+
+// register global utility filters
+Object.keys(filters).forEach(key => {
+  Vue.filter(key, filters[key])
+})
+
+Vue.config.productionTip = false
+
+new Vue({
+  el: '#app',
+  router,
+  store,
+  render: h => h(App)
+})

+ 16 - 0
src/mixin/base.js

@@ -0,0 +1,16 @@
+import Vue from 'vue'
+
+const option = {
+  methods: {
+    // 后退
+    $_back() {
+      this.$router.go(-1)
+    },
+    // 跳转
+    $_navigationTo(url) {
+      this.$router.push(url)
+    }
+  }
+}
+
+Vue.mixin(option)

+ 75 - 74
src/permission.js

@@ -1,74 +1,75 @@
-import router from './router'
-import store from './store'
-import { Message } from 'element-ui'
-import NProgress from 'nprogress' // progress bar
-import 'nprogress/nprogress.css' // progress bar style
-import { getToken } from '@/utils/auth' // get token from cookie
-import getPageTitle from '@/utils/get-page-title'
-
-NProgress.configure({ showSpinner: false }) // NProgress Configuration
-
-const whiteList = ['/login', '/auth-redirect'] // no redirect whitelist
-
-router.beforeEach(async(to, from, next) => {
-  // start progress bar
-  NProgress.start()
-
-  // set page title
-  document.title = getPageTitle(to.meta.title)
-
-  // determine whether the user has logged in
-  const hasToken = getToken()
-
-  if (hasToken) {
-    if (to.path === '/login') {
-      // if is logged in, redirect to the home page
-      next({ path: '/' })
-      NProgress.done() // hack: https://github.com/PanJiaChen/vue-element-admin/pull/2939
-    } else {
-      // determine whether the user has obtained his permission roles through getInfo
-      const hasRoles = store.getters.roles && store.getters.roles.length > 0
-      if (hasRoles) {
-        next()
-      } else {
-        try {
-          // get user info
-          // note: roles must be a object array! such as: ['admin'] or ,['developer','editor']
-          const { roles } = await store.dispatch('user/getInfo')
-
-          // generate accessible routes map based on roles
-          const accessRoutes = await store.dispatch('permission/generateRoutes', roles)
-
-          // dynamically add accessible routes
-          router.addRoutes(accessRoutes)
-
-          // hack method to ensure that addRoutes is complete
-          // set the replace: true, so the navigation will not leave a history record
-          next({ ...to, replace: true })
-        } catch (error) {
-          // remove token and go to login page to re-login
-          await store.dispatch('user/resetToken')
-          Message.error(error || 'Has Error')
-          next(`/login?redirect=${to.path}`)
-          NProgress.done()
-        }
-      }
-    }
-  } else {
-    /* has no token*/
-
-    if (whiteList.indexOf(to.path) !== -1) {
-      // in the free login whitelist, go directly
-      next()
-    } else {
-      // other pages that do not have permission to access are redirected to the login page.
-      next(`/login?redirect=${to.path}`)
-      NProgress.done()
-    }
-  }
-})
-
-router.afterEach(() => {
-  // finish progress bar
-  NProgress.done()
-})
+import router from './router'
+import store from './store'
+import { Message } from 'element-ui'
+import NProgress from 'nprogress' // progress bar
+import 'nprogress/nprogress.css' // progress bar style
+import { getToken } from '@/utils/auth' // get token from cookie
+import getPageTitle from '@/utils/get-page-title'
+
+NProgress.configure({ showSpinner: false }) // NProgress Configuration
+
+const whiteList = ['/login', '/auth-redirect'] // no redirect whitelist
+
+router.beforeEach(async(to, from, next) => {
+  // start progress bar
+  NProgress.start()
+
+  // set page title
+  document.title = getPageTitle(to.meta.title)
+
+  // determine whether the user has logged in
+  const hasToken = getToken()
+
+  if (hasToken) {
+    if (to.path === '/login') {
+      // if is logged in, redirect to the home page
+      next({ path: '/' })
+      NProgress.done() // hack: https://github.com/PanJiaChen/vue-element-admin/pull/2939
+    } else {
+      // determine whether the user has obtained his permission roles through getInfo
+      // const hasRoles = store.getters.roles && store.getters.roles.length > 0
+      const hasInitRouter = store.getters.initRouter
+      console.log(store.getters.roles)
+      if (hasInitRouter) {
+        next()
+      } else {
+        try {
+          // get user info
+          // note: roles must be a object array! such as: ['admin'] or ,['developer','editor']
+          store.commit('permission/SET_INITROUTER', true)
+          // generate accessible routes map based on roles
+          const accessRoutes = await store.dispatch('permission/generateRoutes', store.getters.roles)
+          console.log(accessRoutes)
+          // dynamically add accessible routes
+          router.addRoutes(accessRoutes)
+
+          // hack method to ensure that addRoutes is complete
+          // set the replace: true, so the navigation will not leave a history record
+          next({ ...to, replace: true })
+        } catch (error) {
+          // remove token and go to login page to re-login
+          await store.dispatch('user/resetToken')
+          Message.error(error || 'Has Error')
+          next(`/login?redirect=${to.path}`)
+          NProgress.done()
+        }
+      }
+    }
+  } else {
+    /* has no token*/
+
+    if (whiteList.indexOf(to.path) !== -1) {
+      // in the free login whitelist, go directly
+      next()
+    } else {
+      // other pages that do not have permission to access are redirected to the login page.
+      next(`/login?redirect=${to.path}`)
+      NProgress.done()
+    }
+  }
+})
+
+router.afterEach(() => {
+  // finish progress bar
+  NProgress.done()
+})

+ 197 - 180
src/router/index.js

@@ -1,180 +1,197 @@
-import Vue from 'vue'
-import Router from 'vue-router'
-
-Vue.use(Router)
-
-/* Layout */
-import Layout from '@/layout'
-
-/**
- * Note: sub-menu only appear when route children.length >= 1
- * Detail see: https://panjiachen.github.io/vue-element-admin-site/guide/essentials/router-and-nav.html
- *
- * hidden: true                   if set true, item will not show in the sidebar(default is false)
- * alwaysShow: true               if set true, will always show the root menu
- *                                if not set alwaysShow, when item has more than one children route,
- *                                it will becomes nested mode, otherwise not show the root menu
- * redirect: noRedirect           if set noRedirect will no redirect in the breadcrumb
- * name:'router-name'             the name is used by <keep-alive> (must set!!!)
- * meta : {
-    roles: ['admin','editor']    control the page roles (you can set multiple roles)
-    title: 'title'               the name show in sidebar and breadcrumb (recommend set)
-    icon: 'svg-name'/'el-icon-x' the icon show in the sidebar
-    noCache: true                if set true, the page will no be cached(default is false)
-    affix: true                  if set true, the tag will affix in the tags-view
-    breadcrumb: false            if set false, the item will hidden in breadcrumb(default is true)
-    activeMenu: '/example/list'  if set path, the sidebar will highlight the path you set
-  }
- */
-
-/**
- * constantRoutes
- * a base page that does not have permission requirements
- * all roles can be accessed
- */
-export const constantRoutes = [
-  {
-    path: '/redirect',
-    component: Layout,
-    hidden: true,
-    children: [
-      {
-        path: '/redirect/:path(.*)',
-        component: () => import('@/views/redirect/index')
-      }
-    ]
-  },
-  // 登录
-  {
-    path: '/login',
-    component: () => import('@/views/login/index'),
-    hidden: true
-  },
-  {
-    path: '/auth-redirect',
-    component: () => import('@/views/login/auth-redirect'),
-    hidden: true
-  },
-  {
-    path: '/404',
-    component: () => import('@/views/error-page/404'),
-    hidden: true
-  },
-  {
-    path: '/401',
-    component: () => import('@/views/error-page/401'),
-    hidden: true
-  },
-  // 首页显示的页面
-  {
-    path: '/',
-    component: Layout
-    // redirect: '/supplier/list'
-  },
-  // 供应商管理页面
-  {
-    path: '/supplier',
-    component: Layout,
-    alwaysShow: true,
-    name: 'Supplier',
-    redirect: '/supplier/list',
-    meta: { title: '供应商管理', icon: 'peoples' },
-    children: [
-      // admin
-      {
-        path: 'list',
-        component: () => import('@/views/supplier/user/index'),
-        name: 'SupplierList',
-        meta: { title: '供应商列表', affix: true }
-      },
-      {
-        hidden: true,
-        path: 'add',
-        component: () => import('@/views/supplier/user/add'),
-        name: 'SupplierAdd',
-        meta: { title: '添加供应商' }
-      },
-      {
-        hidden: true,
-        path: 'edit',
-        component: () => import('@/views/supplier/user/edit'),
-        name: 'SupplierAdd',
-        meta: { title: '修改供应商' }
-      }
-    ]
-  },
-  // 授权管理页面
-  {
-    path: '/auth',
-    component: Layout,
-    alwaysShow: true,
-    redirect: '/auth/list',
-    name: 'Auth',
-    meta: { title: '授权管理', icon: 'el-icon-s-promotion' },
-    children: [
-      {
-        path: 'list',
-        component: () => import('@/views/supplier/auth/index'),
-        name: 'AuthList',
-        meta: { title: '授权列表' }
-      }
-    ]
-  },
-  // 商品管理
-  {
-    path: '/product',
-    component: Layout,
-    alwaysShow: true,
-    redirect: '/product/list',
-    name: 'Auth',
-    meta: { title: '商品管理', icon: 'el-icon-s-shop' },
-    children: [
-      {
-        path: 'list',
-        component: () => import('@/views/supplier/product/index'),
-        name: 'Product',
-        meta: { title: '商品列表' }
-      },
-      {
-        hidden: true,
-        path: 'add',
-        component: () => import('@/views/supplier/product/add'),
-        name: 'AddProduct',
-        meta: { title: '添加商品' }
-      },
-      {
-        hidden: true,
-        path: 'edit',
-        component: () => import('@/views/supplier/product/edit'),
-        name: 'EditProduct',
-        meta: { title: '修改商品' }
-      }
-    ]
-  }
-]
-
-/**
- * asyncRoutes
- * the routes that need to be dynamically loaded based on user roles
- */
-export const asyncRoutes = [
-  // 404 page must be placed at the end !!!
-  { path: '*', redirect: '/404', hidden: true }
-]
-
-const createRouter = () =>
-  new Router({
-    // mode: 'history', // require service support
-    scrollBehavior: () => ({ y: 0 }),
-    routes: constantRoutes
-  })
-
-const router = createRouter()
-
-// Detail see: https://github.com/vuejs/vue-router/issues/1234#issuecomment-357941465
-export function resetRouter() {
-  const newRouter = createRouter()
-  router.matcher = newRouter.matcher // reset router
-}
-
-export default router
+import Vue from 'vue'
+import Router from 'vue-router'
+
+Vue.use(Router)
+
+/* Layout */
+import Layout from '@/layout'
+
+/**
+ * Note: sub-menu only appear when route children.length >= 1
+ * Detail see: https://panjiachen.github.io/vue-element-admin-site/guide/essentials/router-and-nav.html
+ *
+ * hidden: true                   if set true, item will not show in the sidebar(default is false)
+ * alwaysShow: true               if set true, will always show the root menu
+ *                                if not set alwaysShow, when item has more than one children route,
+ *                                it will becomes nested mode, otherwise not show the root menu
+ * redirect: noRedirect           if set noRedirect will no redirect in the breadcrumb
+ * name:'router-name'             the name is used by <keep-alive> (must set!!!)
+ * meta : {
+    roles: ['admin','editor']    control the page roles (you can set multiple roles)
+    title: 'title'               the name show in sidebar and breadcrumb (recommend set)
+    icon: 'svg-name'/'el-icon-x' the icon show in the sidebar
+    noCache: true                if set true, the page will no be cached(default is false)
+    affix: true                  if set true, the tag will affix in the tags-view
+    breadcrumb: false            if set false, the item will hidden in breadcrumb(default is true)
+    activeMenu: '/example/list'  if set path, the sidebar will highlight the path you set
+  }
+ */
+
+/**
+ * constantRoutes
+ * a base page that does not have permission requirements
+ * all roles can be accessed
+ */
+export const constantRoutes = [
+  {
+    path: '/redirect',
+    component: Layout,
+    hidden: true,
+    children: [
+      {
+        path: '/redirect/:path(.*)',
+        component: () => import('@/views/redirect/index')
+      }
+    ]
+  },
+  // 登录
+  {
+    path: '/login',
+    component: () => import('@/views/login/index'),
+    hidden: true
+  },
+  {
+    path: '/auth-redirect',
+    component: () => import('@/views/login/auth-redirect'),
+    hidden: true
+  },
+  {
+    path: '/404',
+    component: () => import('@/views/error-page/404'),
+    hidden: true
+  },
+  {
+    path: '/401',
+    component: () => import('@/views/error-page/401'),
+    hidden: true
+  },
+  // 首页显示的页面
+  {
+    path: '/',
+    component: Layout
+    // redirect: '/supplier/list'
+  },
+  // 修改密码
+  {
+    component: Layout,
+    path: '/password',
+    redirect: 'edit',
+    hidden: true,
+    meta: { title: '密码管理' },
+    children: [
+      {
+        path: 'edit',
+        component: () => import('@/views/password/index'),
+        meta: { title: '修改密码' }
+      }
+    ]
+  }
+]
+
+/**
+ * asyncRoutes
+ * the routes that need to be dynamically loaded based on user roles
+ */
+export const asyncRoutes = [
+
+  // 供应商管理页面
+  {
+    path: '/supplier',
+    component: Layout,
+    alwaysShow: true,
+    name: 'Supplier',
+    redirect: '/supplier/list',
+    meta: { title: '供应商管理', icon: 'el-icon-s-custom', roles: ['admin'], noCache: true },
+    children: [
+      // admin
+      {
+        path: 'list',
+        component: () => import('@/views/supplier/user/index'),
+        name: 'SupplierList',
+        meta: { title: '供应商列表', icon: 'el-icon-menu', affix: true, roles: ['admin'], noCache: true }
+      },
+      {
+        hidden: true,
+        path: 'add',
+        component: () => import('@/views/supplier/user/add'),
+        name: 'SupplierAdd',
+        meta: { title: '添加供应商', icon: 'el-icon-menu', roles: ['admin'], noCache: true }
+      },
+      {
+        hidden: true,
+        path: 'edit',
+        component: () => import('@/views/supplier/user/edit'),
+        name: 'SupplierAdd',
+        meta: { title: '修改供应商', icon: 'el-icon-menu', roles: ['admin'], noCache: true }
+      }
+    ]
+  },
+  // 授权管理页面
+  {
+    path: '/auth',
+    component: Layout,
+    alwaysShow: true,
+    redirect: '/auth/list',
+    name: 'Auth',
+    meta: { title: '授权管理', icon: 'el-icon-s-promotion', roles: ['normal'], noCache: true },
+    children: [
+      {
+        path: 'list',
+        component: () => import('@/views/supplier/auth/index'),
+        name: 'AuthList',
+        meta: { title: '授权列表', icon: 'el-icon-menu', roles: ['normal'], noCache: true }
+      }
+    ]
+  },
+  // 商品管理
+  {
+    path: '/product',
+    component: Layout,
+    alwaysShow: true,
+    redirect: '/product/list',
+    // hidden: true,
+    name: 'Auth',
+    meta: { title: '商品管理', icon: 'el-icon-s-shop', roles: ['admin', 'normal'], noCache: true },
+    children: [
+      {
+        path: 'list',
+        component: () => import('@/views/supplier/product/index'),
+        name: 'Product',
+        meta: { title: '商品列表', icon: 'el-icon-menu', roles: ['admin', 'normal'], noCache: true }
+      },
+      {
+        hidden: true,
+        path: 'add',
+        component: () => import('@/views/supplier/product/add'),
+        name: 'AddProduct',
+        meta: { title: '添加商品', icon: 'el-icon-menu', roles: ['admin', 'normal'], noCache: true }
+      },
+      {
+        hidden: true,
+        path: 'edit',
+        component: () => import('@/views/supplier/product/edit'),
+        name: 'EditProduct',
+        meta: { title: '修改商品', icon: 'el-icon-menu', roles: ['admin', 'normal'], noCache: true }
+      }
+    ]
+  },
+  // 404 page must be placed at the end !!!
+  { path: '*', redirect: '/404', hidden: true }
+]
+
+const createRouter = () =>
+  new Router({
+    // mode: 'history', // require service support
+    scrollBehavior: () => ({ y: 0 }),
+    routes: constantRoutes
+  })
+
+const router = createRouter()
+
+// Detail see: https://github.com/vuejs/vue-router/issues/1234#issuecomment-357941465
+export function resetRouter() {
+  const newRouter = createRouter()
+  router.matcher = newRouter.matcher // reset router
+}
+
+export default router

+ 35 - 35
src/settings.js

@@ -1,35 +1,35 @@
-module.exports = {
-  title: 'Vue Element Admin',
-
-  /**
-   * @type {boolean} true | false
-   * @description Whether show the settings right-panel
-   */
-  showSettings: true,
-
-  /**
-   * @type {boolean} true | false
-   * @description Whether need tagsView
-   */
-  tagsView: true,
-
-  /**
-   * @type {boolean} true | false
-   * @description Whether fix the header
-   */
-  fixedHeader: false,
-
-  /**
-   * @type {boolean} true | false
-   * @description Whether show the logo in sidebar
-   */
-  sidebarLogo: true,
-
-  /**
-   * @type {string | array} 'production' | ['production', 'development']
-   * @description Need show err logs component.
-   * The default is only used in the production env
-   * If you want to also use it in dev, you can pass ['production', 'development']
-   */
-  errorLog: 'production'
-}
+module.exports = {
+  title: 'Vue Element Admin',
+
+  /**
+   * @type {boolean} true | false
+   * @description Whether show the settings right-panel
+   */
+  showSettings: true,
+
+  /**
+   * @type {boolean} true | false
+   * @description Whether need tagsView
+   */
+  tagsView: true,
+
+  /**
+   * @type {boolean} true | false
+   * @description Whether fix the header
+   */
+  fixedHeader: false,
+
+  /**
+   * @type {boolean} true | false
+   * @description Whether show the logo in sidebar
+   */
+  sidebarLogo: true,
+
+  /**
+   * @type {string | array} 'production' | ['production', 'development']
+   * @description Need show err logs component.
+   * The default is only used in the production env
+   * If you want to also use it in dev, you can pass ['production', 'development']
+   */
+  errorLog: 'production'
+}

+ 4 - 3
src/store/getters.js

@@ -5,11 +5,12 @@ const getters = {
   visitedViews: state => state.tagsView.visitedViews,
   cachedViews: state => state.tagsView.cachedViews,
   token: state => state.user.token,
-  avatar: state => state.user.avatar,
   name: state => state.user.name,
-  introduction: state => state.user.introduction,
   roles: state => state.user.roles,
+  authUserId: state => state.user.authUserId,
+  userIdentity: state => state.user.userIdentity,
   permission_routes: state => state.permission.routes,
-  errorLogs: state => state.errorLog.logs
+  errorLogs: state => state.errorLog.logs,
+  initRouter: state => state.permission.initRouter
 }
 export default getters

+ 8 - 2
src/store/modules/permission.js

@@ -36,25 +36,31 @@ export function filterAsyncRoutes(routes, roles) {
 
 const state = {
   routes: [],
-  addRoutes: []
+  addRoutes: [],
+  initRouter: false
 }
 
 const mutations = {
   SET_ROUTES: (state, routes) => {
     state.addRoutes = routes
     state.routes = constantRoutes.concat(routes)
+  },
+  SET_INITROUTER: (state, flag) => {
+    state.initRouter = flag
   }
 }
 
 const actions = {
   generateRoutes({ commit }, roles) {
     return new Promise(resolve => {
-      let accessedRoutes
+      let accessedRoutes = []
+      // 开启后admin可以访问所有的路由
       if (roles.includes('admin')) {
         accessedRoutes = asyncRoutes || []
       } else {
         accessedRoutes = filterAsyncRoutes(asyncRoutes, roles)
       }
+      // accessedRoutes = filterAsyncRoutes(asyncRoutes, roles)
       commit('SET_ROUTES', accessedRoutes)
       resolve(accessedRoutes)
     })

+ 95 - 131
src/store/modules/user.js

@@ -1,131 +1,95 @@
-import { login, logout, getInfo } from '@/api/user'
-import { getToken, setToken, removeToken } from '@/utils/auth'
-import router, { resetRouter } from '@/router'
-
-const state = {
-  token: getToken(),
-  name: '',
-  avatar: '',
-  introduction: '',
-  roles: []
-}
-
-const mutations = {
-  SET_TOKEN: (state, token) => {
-    state.token = token
-  },
-  SET_INTRODUCTION: (state, introduction) => {
-    state.introduction = introduction
-  },
-  SET_NAME: (state, name) => {
-    state.name = name
-  },
-  SET_AVATAR: (state, avatar) => {
-    state.avatar = avatar
-  },
-  SET_ROLES: (state, roles) => {
-    state.roles = roles
-  }
-}
-
-const actions = {
-  // user login
-  login({ commit }, userInfo) {
-    const { username, password } = userInfo
-    return new Promise((resolve, reject) => {
-      login({ username: username.trim(), password: password }).then(response => {
-        const { data } = response
-        commit('SET_TOKEN', data.token)
-        setToken(data.token)
-        resolve()
-      }).catch(error => {
-        reject(error)
-      })
-    })
-  },
-
-  // get user info
-  getInfo({ commit, state }) {
-    return new Promise((resolve, reject) => {
-      getInfo(state.token).then(response => {
-        const { data } = response
-
-        if (!data) {
-          reject('Verification failed, please Login again.')
-        }
-
-        const { roles, name, avatar, introduction } = data
-
-        // roles must be a non-empty array
-        if (!roles || roles.length <= 0) {
-          reject('getInfo: roles must be a non-null array!')
-        }
-
-        commit('SET_ROLES', roles)
-        commit('SET_NAME', name)
-        commit('SET_AVATAR', avatar)
-        commit('SET_INTRODUCTION', introduction)
-        resolve(data)
-      }).catch(error => {
-        reject(error)
-      })
-    })
-  },
-
-  // user logout
-  logout({ commit, state, dispatch }) {
-    return new Promise((resolve, reject) => {
-      logout(state.token).then(() => {
-        commit('SET_TOKEN', '')
-        commit('SET_ROLES', [])
-        removeToken()
-        resetRouter()
-
-        // reset visited views and cached views
-        // to fixed https://github.com/PanJiaChen/vue-element-admin/issues/2485
-        dispatch('tagsView/delAllViews', null, { root: true })
-
-        resolve()
-      }).catch(error => {
-        reject(error)
-      })
-    })
-  },
-
-  // remove token
-  resetToken({ commit }) {
-    return new Promise(resolve => {
-      commit('SET_TOKEN', '')
-      commit('SET_ROLES', [])
-      removeToken()
-      resolve()
-    })
-  },
-
-  // dynamically modify permissions
-  async changeRoles({ commit, dispatch }, role) {
-    const token = role + '-token'
-
-    commit('SET_TOKEN', token)
-    setToken(token)
-
-    const { roles } = await dispatch('getInfo')
-
-    resetRouter()
-
-    // generate accessible routes map based on roles
-    const accessRoutes = await dispatch('permission/generateRoutes', roles, { root: true })
-    // dynamically add accessible routes
-    router.addRoutes(accessRoutes)
-
-    // reset visited views and cached views
-    dispatch('tagsView/delAllViews', null, { root: true })
-  }
-}
-
-export default {
-  namespaced: true,
-  state,
-  mutations,
-  actions
-}
+import { login } from '@/api/user'
+import { getToken, setToken, removeToken, setUser, getUser, removeUser } from '@/utils/auth'
+import { resetRouter } from '@/router'
+// console.log(getUser())
+const userInfo = getUser() && JSON.parse(getUser())
+
+function formatRoles(userIdentity) {
+  let roles = []
+  switch (userIdentity) {
+    case 1: roles = ['admin']
+      break
+    case 2:roles = ['normal']
+      break
+    default :roles = []
+      break
+  }
+  return roles
+}
+
+const state = {
+  token: getToken(),
+  name: userInfo?.name || '',
+  roles: formatRoles(userInfo?.userIdentity),
+  shopStatus: userInfo?.shopStatus || '',
+  authUserId: userInfo?.authUserId || '',
+  userIdentity: userInfo?.userIdentity || ''
+}
+
+const mutations = {
+  SET_TOKEN: (state, token) => {
+    state.token = token
+  },
+  SET_NAME: (state, name) => {
+    state.name = name
+  },
+  SET_ROLES: (state, roles) => {
+    state.roles = roles
+  },
+  SET_USERINFO: (state, userInfo) => {
+    state.shopStatus = userInfo.shopStatus
+    state.authUserId = userInfo.authUserId
+    state.userIdentity = userInfo.userIdentity
+  }
+}
+
+const actions = {
+  // 登录操作
+  login({ commit }, userInfo) {
+    const { username, password } = userInfo
+    return new Promise((resolve, reject) => {
+      login({ mobileOrName: username.trim(), password: password }).then(response => {
+        console.log(response)
+        const { shopStatus, token, authUserId, userIdentity, name } = response.data
+        const roles = formatRoles(userIdentity)
+        commit('SET_TOKEN', token) // 保存token
+        commit('SET_USERINFO', { shopStatus, authUserId, userIdentity }) // 保存用户信息
+        commit('SET_ROLES', roles) // 根据用户类型生成角色列表
+        commit('SET_NAME', name) // 保存用户名称
+        setToken(token) // cokies保存token
+        setUser(response.data) // cokies保存当前用户信息
+        resolve()
+      }).catch(error => {
+        reject(error)
+      })
+    })
+  },
+
+  // 退出登录
+  logout({ commit, state, dispatch }) {
+    commit('SET_TOKEN', '') // 重置token
+    commit('SET_ROLES', []) // 重置角色权限表
+    removeToken() // 从cokies中移除token
+    resetRouter() // 重置路由
+    removeUser() // 从cokies中移除用户信息
+    dispatch('tagsView/delAllViews', null, { root: true }) // 关闭所有打开的标签页
+  },
+
+  // 移除token
+  resetToken({ commit }) {
+    return new Promise(resolve => {
+      commit('SET_TOKEN', '')
+      commit('SET_ROLES', [])
+      removeToken()
+      resolve()
+    })
+  }
+
+}
+
+export default {
+  namespaced: true,
+  state,
+  mutations,
+  actions
+}

+ 13 - 0
src/utils/auth.js

@@ -13,3 +13,16 @@ export function setToken(token) {
 export function removeToken() {
   return Cookies.remove(TokenKey)
 }
+
+export function setUser(userInfo) {
+  return Cookies.set('userInfo', userInfo)
+}
+
+export function getUser() {
+  return Cookies.get('userInfo')
+}
+
+export function removeUser() {
+  return Cookies.remove('userInfo')
+}
+

+ 72 - 85
src/utils/request.js

@@ -1,85 +1,72 @@
-import axios from 'axios'
-import { MessageBox, Message } from 'element-ui'
-import store from '@/store'
-import { getToken } from '@/utils/auth'
-
-// create an axios instance
-const service = axios.create({
-  baseURL: process.env.VUE_APP_BASE_API, // url = base url + request url
-  // withCredentials: true, // send cookies when cross-domain requests
-  timeout: 5000 // request timeout
-})
-
-// request interceptor
-service.interceptors.request.use(
-  config => {
-    // do something before request is sent
-
-    if (store.getters.token) {
-      // let each request carry token
-      // ['X-Token'] is a custom headers key
-      // please modify it according to the actual situation
-      config.headers['X-Token'] = getToken()
-    }
-    return config
-  },
-  error => {
-    // do something with request error
-    console.log(error) // for debug
-    return Promise.reject(error)
-  }
-)
-
-// response interceptor
-service.interceptors.response.use(
-  /**
-   * If you want to get http information such as headers or status
-   * Please return  response => response
-  */
-
-  /**
-   * Determine the request status by custom code
-   * Here is just an example
-   * You can also judge the status by HTTP Status Code
-   */
-  response => {
-    const res = response.data
-
-    // if the custom code is not 20000, it is judged as an error.
-    if (res.code !== 20000) {
-      Message({
-        message: res.message || 'Error',
-        type: 'error',
-        duration: 5 * 1000
-      })
-
-      // 50008: Illegal token; 50012: Other clients logged in; 50014: Token expired;
-      if (res.code === 50008 || res.code === 50012 || res.code === 50014) {
-        // to re-login
-        MessageBox.confirm('You have been logged out, you can cancel to stay on this page, or log in again', 'Confirm logout', {
-          confirmButtonText: 'Re-Login',
-          cancelButtonText: 'Cancel',
-          type: 'warning'
-        }).then(() => {
-          store.dispatch('user/resetToken').then(() => {
-            location.reload()
-          })
-        })
-      }
-      return Promise.reject(new Error(res.message || 'Error'))
-    } else {
-      return res
-    }
-  },
-  error => {
-    console.log('err' + error) // for debug
-    Message({
-      message: error.message,
-      type: 'error',
-      duration: 5 * 1000
-    })
-    return Promise.reject(error)
-  }
-)
-
-export default service
+import axios from 'axios'
+import { Message } from 'element-ui'
+import store from '@/store'
+import { getToken } from '@/utils/auth'
+
+// create an axios instance
+const service = axios.create({
+  baseURL: process.env.VUE_APP_BASE_API, // url = base url + request url
+  // withCredentials: true, // send cookies when cross-domain requests
+  timeout: 5000 // request timeout
+})
+
+// request interceptor
+service.interceptors.request.use(
+  config => {
+    // do something before request is sent
+    // config.headers['Content-Type'] = 'application/json'
+    // config.data = JSON.stringify(config.data)
+    if (store.getters.token) {
+      // let each request carry token
+      // ['X-Token'] is a custom headers key
+      // please modify it according to the actual situation
+      config.headers['X-Token'] = getToken()
+    }
+    return config
+  },
+  error => {
+    // do something with request error
+    console.log(error) // for debug
+    return Promise.reject(error)
+  }
+)
+
+// response interceptor
+service.interceptors.response.use(
+  /**
+   * If you want to get http information such as headers or status
+   * Please return  response => response
+  */
+
+  /**
+   * Determine the request status by custom code
+   * Here is just an example
+   * You can also judge the status by HTTP Status Code
+   */
+  response => {
+    const res = response.data
+
+    // if the custom code is not 20000, it is judged as an error.
+    if (res.code !== 0) {
+      Message({
+        message: res.msg || 'Error',
+        type: 'error',
+        duration: 5 * 1000
+      })
+      return Promise.reject(new Error(res.message || 'Error'))
+    } else {
+      return res
+    }
+  },
+  error => {
+    console.log('错误:' + error) // for debug
+    Message({
+      message: error.message,
+      type: 'error',
+      duration: 5 * 1000
+    })
+    return Promise.reject(error)
+  }
+)
+
+export default service

+ 17 - 0
src/utils/storage.js

@@ -0,0 +1,17 @@
+'use strict'
+
+export function setStorage(key, value) {
+  window.localStorage.setItem(key, value)
+}
+
+export function getStorage(key) {
+  window.localStorage.getItem(key)
+}
+
+export function removeStorage(key) {
+  window.localStorage.removeItem(key)
+}
+
+export function clearStorage() {
+  window.localStorage.clear()
+}

+ 9 - 9
src/utils/validate.js

@@ -10,15 +10,6 @@ export function isExternal(path) {
   return /^(https?:|mailto:|tel:)/.test(path)
 }
 
-/**
- * @param {string} str
- * @returns {Boolean}
- */
-export function validUsername(str) {
-  const valid_map = ['admin', 'editor']
-  return valid_map.indexOf(str.trim()) >= 0
-}
-
 /**
  * @param {string} url
  * @returns {Boolean}
@@ -85,3 +76,12 @@ export function isArray(arg) {
   }
   return Array.isArray(arg)
 }
+
+/**
+ * @param {String} arg
+ * @returns {Boolean}
+ */
+export function isMobile(arg) {
+  const reg = /^(13[0-9]|14[5|7]|15[0|1|2|3|4|5|6|7|8|9]|18[0|1|2|3|5|6|7|8|9])\d{8}$/
+  return reg.test(arg)
+}

+ 1 - 1
src/views/error-page/404.vue

@@ -14,7 +14,7 @@
         </div>
         <div class="bullshit__headline">{{ message }}</div>
         <div class="bullshit__info">Please check that the URL you entered is correct, or click the button below to return to the homepage.</div>
-        <a href="" class="bullshit__return-home">Back to home</a>
+        <a href="javascript:void(0)" class="bullshit__return-home" @click="$_back()">Back to home</a>
       </div>
     </div>
   </div>

+ 287 - 309
src/views/login/index.vue

@@ -1,309 +1,287 @@
-<template>
-  <div class="login-container">
-    <el-form ref="loginForm" :model="loginForm" :rules="loginRules" class="login-form" autocomplete="on" label-position="left">
-
-      <div class="title-container">
-        <h3 class="title">Login Form</h3>
-      </div>
-
-      <el-form-item prop="username">
-        <span class="svg-container">
-          <svg-icon icon-class="user" />
-        </span>
-        <el-input
-          ref="username"
-          v-model="loginForm.username"
-          placeholder="Username"
-          name="username"
-          type="text"
-          tabindex="1"
-          autocomplete="on"
-        />
-      </el-form-item>
-
-      <el-tooltip v-model="capsTooltip" content="Caps lock is On" placement="right" manual>
-        <el-form-item prop="password">
-          <span class="svg-container">
-            <svg-icon icon-class="password" />
-          </span>
-          <el-input
-            :key="passwordType"
-            ref="password"
-            v-model="loginForm.password"
-            :type="passwordType"
-            placeholder="Password"
-            name="password"
-            tabindex="2"
-            autocomplete="on"
-            @keyup.native="checkCapslock"
-            @blur="capsTooltip = false"
-            @keyup.enter.native="handleLogin"
-          />
-          <span class="show-pwd" @click="showPwd">
-            <svg-icon :icon-class="passwordType === 'password' ? 'eye' : 'eye-open'" />
-          </span>
-        </el-form-item>
-      </el-tooltip>
-
-      <el-button :loading="loading" type="primary" style="width:100%;margin-bottom:30px;" @click.native.prevent="handleLogin">登录</el-button>
-    </el-form>
-
-    <el-dialog title="Or connect with" :visible.sync="showDialog">
-      Can not be simulated on local, so please combine you own business simulation! ! !
-      <br>
-      <br>
-      <br>
-      <social-sign />
-    </el-dialog>
-  </div>
-</template>
-
-<script>
-import { validUsername } from '@/utils/validate'
-import SocialSign from './components/SocialSignin'
-
-export default {
-  name: 'Login',
-  components: { SocialSign },
-  data() {
-    const validateUsername = (rule, value, callback) => {
-      if (!validUsername(value)) {
-        callback(new Error('请输入登录账号'))
-      } else {
-        callback()
-      }
-    }
-    const validatePassword = (rule, value, callback) => {
-      if (value.length < 6) {
-        callback(new Error('请输入登录密码'))
-      } else {
-        callback()
-      }
-    }
-    return {
-      loginForm: {
-        username: 'admin',
-        password: '111111'
-      },
-      loginRules: {
-        username: [{ required: true, trigger: 'blur', validator: validateUsername }],
-        password: [{ required: true, trigger: 'blur', validator: validatePassword }]
-      },
-      passwordType: 'password',
-      capsTooltip: false,
-      loading: false,
-      showDialog: false,
-      redirect: undefined,
-      otherQuery: {}
-    }
-  },
-  watch: {
-    $route: {
-      handler: function(route) {
-        const query = route.query
-        if (query) {
-          this.redirect = query.redirect
-          this.otherQuery = this.getOtherQuery(query)
-        }
-      },
-      immediate: true
-    }
-  },
-  created() {
-    // window.addEventListener('storage', this.afterQRScan)
-  },
-  mounted() {
-    if (this.loginForm.username === '') {
-      this.$refs.username.focus()
-    } else if (this.loginForm.password === '') {
-      this.$refs.password.focus()
-    }
-  },
-  destroyed() {
-    // window.removeEventListener('storage', this.afterQRScan)
-  },
-  methods: {
-    checkCapslock(e) {
-      const { key } = e
-      this.capsTooltip = key && key.length === 1 && (key >= 'A' && key <= 'Z')
-    },
-    showPwd() {
-      if (this.passwordType === 'password') {
-        this.passwordType = ''
-      } else {
-        this.passwordType = 'password'
-      }
-      this.$nextTick(() => {
-        this.$refs.password.focus()
-      })
-    },
-    handleLogin() {
-      this.$refs.loginForm.validate(valid => {
-        if (valid) {
-          this.loading = true
-          this.$store.dispatch('user/login', this.loginForm)
-            .then(() => {
-              this.$router.push({ path: this.redirect || '/', query: this.otherQuery })
-              this.loading = false
-            })
-            .catch(() => {
-              this.loading = false
-            })
-        } else {
-          console.log('error submit!!')
-          return false
-        }
-      })
-    },
-    getOtherQuery(query) {
-      return Object.keys(query).reduce((acc, cur) => {
-        if (cur !== 'redirect') {
-          acc[cur] = query[cur]
-        }
-        return acc
-      }, {})
-    }
-    // afterQRScan() {
-    //   if (e.key === 'x-admin-oauth-code') {
-    //     const code = getQueryObject(e.newValue)
-    //     const codeMap = {
-    //       wechat: 'code',
-    //       tencent: 'code'
-    //     }
-    //     const type = codeMap[this.auth_type]
-    //     const codeName = code[type]
-    //     if (codeName) {
-    //       this.$store.dispatch('LoginByThirdparty', codeName).then(() => {
-    //         this.$router.push({ path: this.redirect || '/' })
-    //       })
-    //     } else {
-    //       alert('第三方登录失败')
-    //     }
-    //   }
-    // }
-  }
-}
-</script>
-
-<style lang="scss">
-/* 修复input 背景不协调 和光标变色 */
-/* Detail see https://github.com/PanJiaChen/vue-element-admin/pull/927 */
-
-$bg:#283443;
-$light_gray:#fff;
-$cursor: #fff;
-
-@supports (-webkit-mask: none) and (not (cater-color: $cursor)) {
-  .login-container .el-input input {
-    color: $cursor;
-  }
-}
-
-/* reset element-ui css */
-.login-container {
-  .el-input {
-    display: inline-block;
-    height: 47px;
-    width: 85%;
-
-    input {
-      background: transparent;
-      border: 0px;
-      -webkit-appearance: none;
-      border-radius: 0px;
-      padding: 12px 5px 12px 15px;
-      color: $light_gray;
-      height: 47px;
-      caret-color: $cursor;
-
-      &:-webkit-autofill {
-        box-shadow: 0 0 0px 1000px $bg inset !important;
-        -webkit-text-fill-color: $cursor !important;
-      }
-    }
-  }
-
-  .el-form-item {
-    border: 1px solid rgba(255, 255, 255, 0.1);
-    background: rgba(0, 0, 0, 0.1);
-    border-radius: 5px;
-    color: #454545;
-  }
-}
-</style>
-
-<style lang="scss" scoped>
-$bg:#2d3a4b;
-$dark_gray:#889aa4;
-$light_gray:#eee;
-
-.login-container {
-  min-height: 100%;
-  width: 100%;
-  background-color: $bg;
-  overflow: hidden;
-
-  .login-form {
-    position: relative;
-    width: 520px;
-    max-width: 100%;
-    padding: 160px 35px 0;
-    margin: 0 auto;
-    overflow: hidden;
-  }
-
-  .tips {
-    font-size: 14px;
-    color: #fff;
-    margin-bottom: 10px;
-
-    span {
-      &:first-of-type {
-        margin-right: 16px;
-      }
-    }
-  }
-
-  .svg-container {
-    padding: 6px 5px 6px 15px;
-    color: $dark_gray;
-    vertical-align: middle;
-    width: 30px;
-    display: inline-block;
-  }
-
-  .title-container {
-    position: relative;
-
-    .title {
-      font-size: 26px;
-      color: $light_gray;
-      margin: 0px auto 40px auto;
-      text-align: center;
-      font-weight: bold;
-    }
-  }
-
-  .show-pwd {
-    position: absolute;
-    right: 10px;
-    top: 7px;
-    font-size: 16px;
-    color: $dark_gray;
-    cursor: pointer;
-    user-select: none;
-  }
-
-  .thirdparty-button {
-    position: absolute;
-    right: 0;
-    bottom: 6px;
-  }
-
-  @media only screen and (max-width: 470px) {
-    .thirdparty-button {
-      display: none;
-    }
-  }
-}
-</style>
+<template>
+  <div class="login-container">
+    <el-form ref="loginForm" :model="loginForm" :rules="loginRules" class="login-form" autocomplete="on" label-position="left">
+
+      <div class="title-container">
+        <h3 class="title">正品联盟管理系统</h3>
+      </div>
+
+      <el-form-item prop="username">
+        <span class="svg-container">
+          <svg-icon icon-class="user" />
+        </span>
+        <el-input
+          ref="username"
+          v-model="loginForm.username"
+          placeholder="请输入用户名"
+          name="username"
+          type="text"
+          tabindex="1"
+          autocomplete="on"
+        />
+      </el-form-item>
+
+      <el-tooltip v-model="capsTooltip" content="Caps lock is On" placement="right" manual>
+        <el-form-item prop="password">
+          <span class="svg-container">
+            <svg-icon icon-class="password" />
+          </span>
+          <el-input
+            :key="passwordType"
+            ref="password"
+            v-model="loginForm.password"
+            :type="passwordType"
+            placeholder="请输入密码"
+            name="password"
+            tabindex="2"
+            autocomplete="on"
+            @keyup.native="checkCapslock"
+            @blur="capsTooltip = false"
+            @keyup.enter.native="handleLogin"
+          />
+          <span class="show-pwd" @click="showPwd">
+            <svg-icon :icon-class="passwordType === 'password' ? 'eye' : 'eye-open'" />
+          </span>
+        </el-form-item>
+      </el-tooltip>
+
+      <el-button :loading="loading" type="primary" style="width:100%;margin-bottom:30px;" @click.native.prevent="handleLogin">登录</el-button>
+    </el-form>
+  </div>
+</template>
+
+<script>
+
+export default {
+  name: 'Login',
+
+  data() {
+    return {
+      loginForm: {
+        username: 'admin',
+        password: ''
+      },
+      loginRules: {
+        username: [{ required: true, trigger: 'blur', message: '请输入登录账号' }],
+        password: [{ required: true, trigger: 'blur', message: '请输入登录密码' }]
+      },
+      passwordType: 'password',
+      capsTooltip: false,
+      loading: false,
+      redirect: undefined,
+      otherQuery: {}
+    }
+  },
+  watch: {
+    $route: {
+      handler: function(route) {
+        const query = route.query
+        if (query) {
+          this.redirect = query.redirect
+          this.otherQuery = this.getOtherQuery(query)
+        }
+      },
+      immediate: true
+    }
+  },
+  created() {
+    // window.addEventListener('storage', this.afterQRScan)
+  },
+  mounted() {
+    if (this.loginForm.username === '') {
+      this.$refs.username.focus()
+    } else if (this.loginForm.password === '') {
+      this.$refs.password.focus()
+    }
+  },
+  destroyed() {
+    // window.removeEventListener('storage', this.afterQRScan)
+  },
+  methods: {
+    checkCapslock(e) {
+      const { key } = e
+      this.capsTooltip = key && key.length === 1 && (key >= 'A' && key <= 'Z')
+    },
+    showPwd() {
+      if (this.passwordType === 'password') {
+        this.passwordType = ''
+      } else {
+        this.passwordType = 'password'
+      }
+      this.$nextTick(() => {
+        this.$refs.password.focus()
+      })
+    },
+    handleLogin() {
+      this.$refs.loginForm.validate(valid => {
+        console.log(valid)
+        if (valid) {
+          this.loading = true
+          this.$store.dispatch('user/login', this.loginForm)
+            .then(() => {
+              // this.$router.push({ path: this.redirect || '/', query: this.otherQuery })
+              this.$router.push('/')
+              this.loading = false
+              this.$message.success('登录成功')
+            })
+            .catch(() => {
+              this.loading = false
+            })
+        } else {
+          console.log('字段校验未通过')
+          return false
+        }
+      })
+    },
+    getOtherQuery(query) {
+      return Object.keys(query).reduce((acc, cur) => {
+        if (cur !== 'redirect') {
+          acc[cur] = query[cur]
+        }
+        return acc
+      }, {})
+    }
+    // afterQRScan() {
+    //   if (e.key === 'x-admin-oauth-code') {
+    //     const code = getQueryObject(e.newValue)
+    //     const codeMap = {
+    //       wechat: 'code',
+    //       tencent: 'code'
+    //     }
+    //     const type = codeMap[this.auth_type]
+    //     const codeName = code[type]
+    //     if (codeName) {
+    //       this.$store.dispatch('LoginByThirdparty', codeName).then(() => {
+    //         this.$router.push({ path: this.redirect || '/' })
+    //       })
+    //     } else {
+    //       alert('第三方登录失败')
+    //     }
+    //   }
+    // }
+  }
+}
+</script>
+
+<style lang="scss">
+/* 修复input 背景不协调 和光标变色 */
+/* Detail see https://github.com/PanJiaChen/vue-element-admin/pull/927 */
+
+$bg:#283443;
+$light_gray:#fff;
+$cursor: #fff;
+
+@supports (-webkit-mask: none) and (not (cater-color: $cursor)) {
+  .login-container .el-input input {
+    color: $cursor;
+  }
+}
+
+/* reset element-ui css */
+.login-container {
+  .el-input {
+    display: inline-block;
+    height: 47px;
+    width: 85%;
+
+    input {
+      background: transparent;
+      border: 0px;
+      -webkit-appearance: none;
+      border-radius: 0px;
+      padding: 12px 5px 12px 15px;
+      color: $light_gray;
+      height: 47px;
+      caret-color: $cursor;
+
+      &:-webkit-autofill {
+        box-shadow: 0 0 0px 1000px $bg inset !important;
+        -webkit-text-fill-color: $cursor !important;
+      }
+    }
+  }
+
+  .el-form-item {
+    border: 1px solid rgba(255, 255, 255, 0.1);
+    background: rgba(0, 0, 0, 0.1);
+    border-radius: 5px;
+    color: #454545;
+  }
+}
+</style>
+
+<style lang="scss" scoped>
+$bg:#2d3a4b;
+$dark_gray:#889aa4;
+$light_gray:#eee;
+
+.login-container {
+  min-height: 100%;
+  width: 100%;
+  background-color: $bg;
+  overflow: hidden;
+
+  .login-form {
+    position: relative;
+    width: 520px;
+    max-width: 100%;
+    padding: 160px 35px 0;
+    margin: 0 auto;
+    overflow: hidden;
+  }
+
+  .tips {
+    font-size: 14px;
+    color: #fff;
+    margin-bottom: 10px;
+
+    span {
+      &:first-of-type {
+        margin-right: 16px;
+      }
+    }
+  }
+
+  .svg-container {
+    padding: 6px 5px 6px 15px;
+    color: $dark_gray;
+    vertical-align: middle;
+    width: 30px;
+    display: inline-block;
+  }
+
+  .title-container {
+    position: relative;
+
+    .title {
+      font-size:36px;
+      color: $light_gray;
+      margin: 0px auto 40px auto;
+      text-align: center;
+      font-weight: bold;
+    }
+  }
+
+  .show-pwd {
+    position: absolute;
+    right: 10px;
+    top: 7px;
+    font-size: 16px;
+    color: $dark_gray;
+    cursor: pointer;
+    user-select: none;
+  }
+
+  .thirdparty-button {
+    position: absolute;
+    right: 0;
+    bottom: 6px;
+  }
+
+  @media only screen and (max-width: 470px) {
+    .thirdparty-button {
+      display: none;
+    }
+  }
+}
+</style>

+ 49 - 0
src/views/normal/password.vue

@@ -0,0 +1,49 @@
+<template>
+  <div class="password">
+    <el-form ref="form" :model="formData" label-width="80px" class="form">
+      <el-form-item label="旧密码:">
+        <el-input v-model="formData.oldPwd" placeholder="请输入旧密码" />
+      </el-form-item>
+      <el-form-item label="新密码:">
+        <el-input v-model="formData.newPwd" placeholder="请输入新密码" />
+      </el-form-item>
+      <el-form-item label="确认密码:">
+        <el-input v-model="formData.confirmPwd" placeholder="请再次输入密码" />
+      </el-form-item>
+      <el-form-item class="submit">
+        <el-button type="primary">确认</el-button>
+        <el-button type="warning" @click="$_back">取消</el-button>
+      </el-form-item>
+    </el-form>
+  </div>
+</template>
+
+<script>
+export default {
+  data() {
+    return {
+      formData: {
+        oldPwd: '',
+        newPwd: '',
+        confirmPwd: ''
+      }
+    }
+  },
+  methods: {
+    // back() {
+    //   this.$router.go(-1)
+    // }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.form{
+  width: 600px;
+  margin: 80px auto;
+
+  .el-button{
+    width: 120px;
+  }
+}
+</style>

+ 86 - 0
src/views/password/index.vue

@@ -0,0 +1,86 @@
+<template>
+  <div>
+    <el-form ref="form" :model="formData" :rules="formRules" label-width="80px" class="form">
+      <el-form-item label="旧密码" prop="oldPassword">
+        <el-input v-model="formData.oldPassword" type="password" placeholder="请输入旧密码" />
+      </el-form-item>
+      <el-form-item label="新密码" prop="newPassword">
+        <el-input v-model="formData.newPassword" type="password" placeholder="请输入新密码" />
+      </el-form-item>
+      <el-form-item label="确认密码" prop="confirmPwd">
+        <el-input v-model="formData.confirmPwd" type="password" placeholder="请再次输入密码" />
+      </el-form-item>
+      <el-form-item>
+        <el-button type="primary" @click="submit()">确认</el-button>
+        <el-button type="warning" @click="$_back()">返回</el-button>
+      </el-form-item>
+    </el-form>
+  </div>
+</template>
+
+<script>
+import { modifyPassword } from '@/api/user'
+export default {
+  data() {
+    var validatePass = (rule, value, callback) => {
+      if (value === '') {
+        callback(new Error('请再次输入密码'))
+      } else if (value !== this.formData.newPassword) {
+        callback(new Error('两次输入密码不一致!'))
+      } else {
+        callback()
+      }
+    }
+    return {
+      formData: {
+        oldPassword: '',
+        newPassword: '',
+        confirmPwd: ''
+      },
+      formRules: {
+        oldPassword: [
+          { required: true, message: '请输入原来的密码', trigger: 'blur' }
+        ],
+        newPassword: [
+          { required: true, message: '请输入新密码', trigger: 'blur' }
+        ],
+        confirmPwd: [
+          { validator: validatePass, trigger: 'blur' }
+        ]
+      }
+    }
+  },
+  methods: {
+    submit() {
+      const that = this
+      this.$refs.form.validate(valide => {
+        if (!valide) return
+        const authUserId = this.$store.getters.authUserId
+        modifyPassword({ authUserId, ...that.formData }).then(res => {
+          if (res.code === 0) {
+            this.$refs.form.resetFields()
+            this.$message.success('密码修改成功,请重新登录')
+            this.logout()
+          }
+        }).catch(err => {
+          console.log(err)
+        })
+      })
+    },
+    logout() {
+      this.$store.dispatch('user/logout')
+      this.$router.replace('/login')
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+  .form{
+    width: 600px;
+    margin: 80px auto;
+    .el-button{
+      width: 120px;
+    }
+  }
+</style>

+ 184 - 161
src/views/supplier/auth/index.vue

@@ -1,161 +1,184 @@
-<template>
-  <div class="app-container">
-    <div class="filter-container">
-      <span>授权机构:</span>
-      <el-input v-model="listQuery.title" placeholder="授权机构" style="width: 200px;" class="filter-item" @keyup.enter.native="handleFilter" />
-      <el-button type="primary">查询</el-button>
-      <el-button type="primary">添加品牌授权</el-button>
-    </div>
-    <!-- 表格区域 -->
-    <el-table
-      :key="tableKey"
-      v-loading="listLoading"
-      :data="list"
-      border
-      fit
-      highlight-current-row
-      style="width: 100%;"
-      @sort-change="sortChange"
-    >
-      <el-table-column label="序号" prop="id" sortable="custom" align="center" width="80" :class-name="getSortClass('id')">
-        <template>
-          <span>1</span>
-        </template>
-      </el-table-column>
-      <el-table-column label="授权机构" align="center">
-        <template>
-          <span>授权机构</span>
-        </template>
-      </el-table-column>
-      <el-table-column label="授权状态" width="140px" align="center">
-        <template>
-          <i class="el-icon-circle-check status success" />
-        </template>
-      </el-table-column>
-      <el-table-column label="供应商类型" width="300px" align="center">
-        <template>
-          <span>类型</span>
-        </template>
-      </el-table-column>
-      <el-table-column label="创建时间" class-name="status-col" width="300px">
-        <template slot-scope="{row}">
-          <span>{{ row.timestamp | parseTime('{y}-{m}-{d} {h}:{i}') }}</span>
-        </template>
-      </el-table-column>
-      <el-table-column label="创建人" class-name="status-col" width="100px">
-        <template>
-          <span>创建人</span>
-        </template>
-      </el-table-column>
-      <el-table-column label="操作" align="center" width="140px" class-name="small-padding fixed-width">
-        <template>
-          <el-button type="primary" size="mini" @click="handleProList">
-            商品列表
-          </el-button>
-        </template>
-      </el-table-column>
-    </el-table>
-
-    <!-- 页码 -->
-    <pagination v-show="total>0" :total="total" :page.sync="listQuery.page" :limit.sync="listQuery.limit" @pagination="getList" />
-  </div>
-</template>
-
-<script>
-import { fetchList } from '@/api/article'
-import Pagination from '@/components/Pagination' // secondary package based on el-pagination
-
-export default {
-  name: 'ComplexTable',
-  components: { Pagination },
-  data() {
-    return {
-      tableKey: 0,
-      list: null,
-      total: 0,
-      listLoading: true,
-      listQuery: {
-        page: 1,
-        limit: 20,
-        importance: undefined,
-        title: undefined,
-        type: undefined,
-        sort: '+id'
-      },
-      temp: {
-        id: undefined,
-        importance: 1,
-        remark: '',
-        timestamp: new Date(),
-        title: '',
-        type: '',
-        status: 'published'
-      }
-    }
-  },
-  created() {
-    this.getList()
-  },
-  methods: {
-    // 修改
-    handleProList() {
-      this.$router.push('/product')
-    },
-    getList() {
-      this.listLoading = true
-      fetchList(this.listQuery).then(response => {
-        this.list = response.data.items
-        this.total = response.data.total
-
-        // Just to simulate the time of the request
-        setTimeout(() => {
-          this.listLoading = false
-        }, 1.5 * 1000)
-      })
-    },
-    handleFilter() {
-      this.listQuery.page = 1
-      this.getList()
-    },
-
-    sortChange(data) {
-      const { prop, order } = data
-      if (prop === 'id') {
-        this.sortByID(order)
-      }
-    },
-    sortByID(order) {
-      if (order === 'ascending') {
-        this.listQuery.sort = '+id'
-      } else {
-        this.listQuery.sort = '-id'
-      }
-      this.handleFilter()
-    },
-    getSortClass: function(key) {
-      const sort = this.listQuery.sort
-      return sort === `+${key}` ? 'ascending' : 'descending'
-    }
-  }
-}
-</script>
-
-<style lang="scss" scoped>
-.filter-container{
-  span{
-    display: inline-block;
-    margin-bottom: 10px;
-    vertical-align: middle;
-    font-size: 14px;
-  }
-  .el-button{
-    display: inline-block;
-    margin-bottom: 10px;
-    vertical-align: middle;
-  }
-  .el-input,.el-select{
-    margin-right: 10px;
-    margin-left: 10px;
-  }
-}
-</style>
+<template>
+  <div class="app-container">
+    <div class="filter-container">
+      <span>授权机构:</span>
+      <el-input v-model="listQuery.authParty" placeholder="授权机构" style="width: 280px;" class="filter-item" @keyup.enter.native="handleFilter" />
+      <el-button type="primary">查询</el-button>
+      <el-button type="primary" @click="showAddAuthDialog = true">添加品牌授权</el-button>
+    </div>
+    <!-- 表格区域 -->
+    <el-table
+      :key="tableKey"
+      v-loading="listLoading"
+      :data="list"
+      border
+      fit
+      highlight-current-row
+      style="width: 100%;"
+    >
+      <el-table-column label="序号" prop="id" sortable="custom" align="center" width="80">
+        <template>
+          <span>1</span>
+        </template>
+      </el-table-column>
+
+      <el-table-column label="授权机构" align="center" prop="authParty" />
+
+      <el-table-column label="授权状态" width="140px" align="center" prop="status">
+        <template v-slot="{row}">
+          <el-switch v-model="row.status" @change="handleChangeStatus(row)" />
+        </template>
+      </el-table-column>
+
+      <el-table-column label="创建时间" class-name="status-col" width="360px">
+        <template slot-scope="{row}">
+          <span>{{ row.createTime | parseTime('{y}-{m}-{d} {h}:{i}') }}</span>
+        </template>
+      </el-table-column>
+
+      <el-table-column label="创建人" class-name="status-col" width="160px" prop="createBy" />
+
+      <el-table-column label="操作" align="center" width="160px" class-name="small-padding fixed-width">
+        <template>
+          <el-button type="primary" size="mini" @click="$_navigationTo('/product')">
+            商品列表
+          </el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <!-- 页码 -->
+    <pagination v-show="total>0" :total="total" :page.sync="listQuery.pageNum" :limit.sync="listQuery.pageSize" @pagination="getList" />
+
+    <!-- 对话框区域 -->
+    <el-dialog
+      title="添加品牌授权"
+      :visible.sync="showAddAuthDialog"
+      width="30%"
+      @close="dialogClosed"
+    >
+      <el-form ref="addAuthForm" :rules="addAuthFormRules" :model="addAuthFormData" label-width="100px">
+        <el-form-item label="授权机构:" prop="name">
+          <el-input v-model="addAuthFormData.name" placeholder="请输入授权机构名称" />
+        </el-form-item>
+        <el-form-item label="上架状态:">
+          <el-select v-model="addAuthFormData.state">
+            <el-option label="上架" :value="1" />
+            <el-option label="下架" :value="0" />
+          </el-select>
+        </el-form-item>
+      </el-form>
+      <span slot="footer" class="dialog-footer">
+        <el-button @click="showAddAuthDialog = false">取 消</el-button>
+        <el-button type="primary" @click="showAddAuthDialog = false">确 定</el-button>
+      </span>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import { fecthAuthList } from '@/api/auth'
+import Pagination from '@/components/Pagination' // secondary package based on el-pagination
+
+export default {
+  name: 'ComplexTable',
+  components: { Pagination },
+  data() {
+    return {
+      tableKey: 0,
+      list: null,
+      total: 0,
+      listLoading: true,
+      // 查询参数
+      listQuery: {
+        authParty: '', // 授权机构
+        authUserId: '', // 供应商用户id
+        pageNum: 1, // 页码
+        pageSize: 10 // 分页
+      },
+      // 添加品牌授权
+      showAddAuthDialog: false, // 显示添加供应商对话框
+      addAuthFormData: {
+        name: '',
+        state: 1
+      },
+      addAuthFormRules: {
+        name: [
+          { required: true, message: '请输入授权机构名称', trigger: 'blur' }
+        ]
+      }
+    }
+  },
+  created() {
+    this.getList()
+  },
+  methods: {
+    // 获取授权列表
+    getList() {
+      this.listLoading = true
+      fecthAuthList(this.listQuery).then(response => {
+        if (response.code !== 0) {
+          return this.$message.error('授权列表信息获取失败')
+        }
+        console.log(response)
+        const { list, total } = response.data
+        this.formatList(list)
+        this.list = list
+        this.total = total
+      }).catch(err => {
+        console.log(err)
+        return this.$message.error('授权列表信息获取失败')
+      }).finally(() => {
+        this.listLoading = false
+      })
+    },
+    // 格式化列表数据
+    formatList(list = []) {
+      list.forEach(i => {
+        i.status = i.status === 1
+      })
+    },
+    // 供应商状态改变
+    handleChangeStatus(item) {
+      const params = {
+        shopStatus: item.shopStatus ? 1 : 0,
+        authUserId: item.authUserId
+      }
+      console.log(params)
+    },
+    // 过滤列表
+    handleFilter() {
+      this.listQuery.page = 1
+      this.getList()
+    },
+    // 添加供应商
+    handleAddAuth() {
+      console.log('添加供应商')
+    },
+    // 对话框关闭事件
+    dialogClosed() {
+      this.$refs.addAuthForm.resetFields()
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.filter-container{
+  span{
+    display: inline-block;
+    margin-bottom: 10px;
+    vertical-align: middle;
+    font-size: 14px;
+  }
+  .el-button{
+    display: inline-block;
+    margin-bottom: 10px;
+    vertical-align: middle;
+  }
+  .el-input,.el-select{
+    margin-right: 10px;
+    margin-left: 10px;
+  }
+}
+</style>

+ 36 - 0
src/views/supplier/components/upload.vue

@@ -0,0 +1,36 @@
+<template>
+  <div>
+    <el-upload
+      ref="uploadFile"
+      class="upload-demo"
+      drag
+      :action="action"
+      multiple
+      :limit="1"
+    >
+      <i class="el-icon-upload" />
+      <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
+      <!-- <div slot="tip" class="el-upload__tip">只能上传jpg/png文件,且不超过500kb</div> -->
+    </el-upload>
+  </div>
+</template>
+
+<script>
+export default {
+  props: {
+    limit: {
+      type: Number,
+      default: 1
+    }
+  },
+  data() {
+    return {
+
+    }
+  },
+  methods: {
+
+  }
+}
+</script>
+

+ 43 - 21
src/views/supplier/components/uploadFile.vue

@@ -1,21 +1,43 @@
-<template>
-  <div>
-    <el-upload
-      class="upload-demo"
-      drag
-      action="https://jsonplaceholder.typicode.com/posts/"
-      multiple
-      :limit="1"
-    >
-      <i class="el-icon-upload" />
-      <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
-      <!-- <div slot="tip" class="el-upload__tip">只能上传jpg/png文件,且不超过500kb</div> -->
-    </el-upload>
-  </div>
-</template>
-
-<script>
-export default {
-}
-</script>
-
+<template>
+  <div>
+    <el-upload
+      ref="uploadFile"
+      class="upload-demo"
+      drag
+      :action="action"
+      multiple
+      :limit="1"
+      :data="params"
+      :on-success="success"
+    >
+      <i class="el-icon-upload" />
+      <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
+      <!-- <div slot="tip" class="el-upload__tip">只能上传jpg/png文件,且不超过500kb</div> -->
+    </el-upload>
+  </div>
+</template>
+
+<script>
+
+export default {
+  data() {
+    return {
+      // 图片上传接口
+      action: process.env.VUE_APP_BASE_API + '/shop/upload/file',
+      // 请求参数
+      params: {
+        authUserId: this.$store.getters.authUserId
+      }
+    }
+  },
+  methods: {
+    success(response, file, fileList) {
+      this.$emit('success', response, file, fileList)
+    },
+    error(err, file, fileList) {
+      this.$emit('error', err, file, fileList)
+    }
+  }
+}
+</script>
+

+ 70 - 38
src/views/supplier/components/uploadImage.vue

@@ -1,38 +1,70 @@
-<template>
-  <div>
-    <el-upload
-      action="https://jsonplaceholder.typicode.com/posts/"
-      list-type="picture-card"
-      :on-preview="handlePictureCardPreview"
-      :on-remove="handleRemove"
-      :limit="1"
-      :auto-upload="false"
-    >
-      <i class="el-icon-plus" />
-    </el-upload>
-    <el-dialog :visible.sync="dialogVisible">
-      <img width="100%" :src="dialogImageUrl" alt="">
-    </el-dialog>
-  </div>
-</template>
-
-<script>
-export default {
-  data() {
-    return {
-      dialogImageUrl: '',
-      dialogVisible: false
-    }
-  },
-  methods: {
-    handleRemove(file, fileList) {
-      console.log(file, fileList)
-    },
-    handlePictureCardPreview(file) {
-      this.dialogImageUrl = file.url
-      this.dialogVisible = true
-    }
-  }
-}
-</script>
-
+<template>
+  <div>
+    <el-upload
+      ref="upload"
+      :action="action"
+      list-type="picture-card"
+      :on-preview="handlePictureCardPreview"
+      :on-remove="handleRemove"
+      :before-upload="beforeUpload"
+      :limit="1"
+      :accept="accept"
+      :class="{hidden:isChooesed}"
+      :auto-upload="true"
+    >
+      <i class="el-icon-plus" />
+    </el-upload>
+    <el-dialog :visible.sync="dialogVisible">
+      <img width="100%" :src="dialogImageUrl" alt="">
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+// import { uploadImage } from '@/api/upload'
+export default {
+  data() {
+    return {
+      isChooesed: false, // 是否上选择了图片
+      dialogImageUrl: '',
+      dialogVisible: false,
+      // 文件上传请求接口
+      action: process.env.VUE_APP_BASE_API + '/upload/image',
+      // action: 'https://d1fd53a1-a1f9-41a8-90e0-e9e54a0e00ba.bspapp.com/http/upload',
+      accept: '.jpg,.png,.gif'
+    }
+  },
+  methods: {
+    handleRemove(file, fileList) {
+      console.log(file, fileList)
+    },
+    handlePictureCardPreview(file) {
+      this.dialogImageUrl = file.url
+      this.dialogVisible = true
+    },
+    success(response, file, fileList) {
+      this.$emit('success', response, file, fileList)
+      this.isChooesed = true
+    },
+    error(err, file, fileList) {
+      this.$emit('error', err, file, fileList)
+    },
+    beforeUpload(file) {
+      const size = file.size
+      if (size > 1024) {
+        this.$message.warning('文件上传大小超出限制(≤1MB)')
+        return false
+      }
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.hidden {
+  .el-upload {
+    display: none !important;
+  }
+}
+</style>
+

+ 86 - 86
src/views/supplier/product/add.vue

@@ -1,86 +1,86 @@
-<template>
-  <div class="addProduct">
-    <el-form ref="addFormRef" label-width="120px" class="addForm">
-      <el-form-item label="商品名称:">
-        <el-input placeholder="请输入商品名称" />
-      </el-form-item>
-      <el-form-item label="商品SN码:">
-        <el-input placeholder="请输入商品SN码" />
-      </el-form-item>
-      <el-form-item label="商品图片:">
-        <upload-image />
-      </el-form-item>
-      <el-form-item label="授权牌:">
-        <upload-image />
-      </el-form-item>
-      <el-form-item label="相关参数:">
-        <div class="form-group">
-          <el-input class="param-title" placeholder="例如:品牌" />
-          <el-input class="param-info" placeholder="请输入参数信息" />
-        </div>
-        <div class="form-group">
-          <el-input class="param-title" placeholder="例如:尺寸" />
-          <el-input class="param-info" placeholder="请输入参数信息" />
-        </div>
-        <div class="form-group">
-          <el-input class="param-title" placeholder="例如:功率" />
-          <el-input class="param-info" placeholder="请输入参数信息" />
-        </div>
-        <div class="form-group">
-          <el-input class="param-title" placeholder="例如:型号" />
-          <el-input class="param-info" placeholder="请输入参数信息" />
-        </div>
-        <el-button type="primary" size="mini">添加参数</el-button>
-      </el-form-item>
-      <el-form-item label="上架状态:">
-        <el-select placeholder="产地" style="width:100%">
-          <el-option label="上架" />
-          <el-option label="下架" />
-        </el-select>
-      </el-form-item>
-    </el-form>
-    <div class="submit-btn">
-      <el-button type="primary">保存</el-button>
-      <el-button type="warning">返回</el-button>
-    </div>
-  </div>
-</template>
-
-<script>
-import UploadImage from '../components/uploadImage'
-export default {
-  components: { UploadImage },
-  data() {
-    return {
-      statement: 1
-    }
-  }
-}
-</script>
-
-<style lang="scss" scoped>
-.addProduct{
-  margin-bottom: 80px;
-  .form-group{
-    margin-bottom: 2%;
-    .param-title{
-      width: 30%;
-    }
-    .param-info{
-      width: 68%;
-      margin-left: 2%;
-    }
-  }
-}
-.addForm{
-  width: 500px;
-  margin: 0 auto;
-  margin-top: 80px;
-}
-.submit-btn{
-    text-align: center;
-    .el-button{
-      width:140px;
-    }
-  }
-</style>
+<template>
+  <div class="addProduct">
+    <el-form ref="addFormRef" label-width="120px" class="addForm">
+      <el-form-item label="商品名称:">
+        <el-input placeholder="请输入商品名称" />
+      </el-form-item>
+      <el-form-item label="商品SN码:">
+        <el-input placeholder="请输入商品SN码" />
+      </el-form-item>
+      <el-form-item label="商品图片:">
+        <upload-image />
+      </el-form-item>
+      <el-form-item label="授权牌:">
+        <upload-image />
+      </el-form-item>
+      <el-form-item label="相关参数:">
+        <div class="form-group">
+          <el-input class="param-title" placeholder="例如:品牌" />
+          <el-input class="param-info" placeholder="请输入参数信息" />
+        </div>
+        <div class="form-group">
+          <el-input class="param-title" placeholder="例如:尺寸" />
+          <el-input class="param-info" placeholder="请输入参数信息" />
+        </div>
+        <div class="form-group">
+          <el-input class="param-title" placeholder="例如:功率" />
+          <el-input class="param-info" placeholder="请输入参数信息" />
+        </div>
+        <div class="form-group">
+          <el-input class="param-title" placeholder="例如:型号" />
+          <el-input class="param-info" placeholder="请输入参数信息" />
+        </div>
+        <el-button type="primary" size="mini">添加参数</el-button>
+      </el-form-item>
+      <el-form-item label="上架状态:">
+        <el-select placeholder="产地" style="width:100%">
+          <el-option label="上架" />
+          <el-option label="下架" />
+        </el-select>
+      </el-form-item>
+    </el-form>
+    <div class="submit-btn">
+      <el-button type="primary">保存</el-button>
+      <el-button type="warning" @click="$_back()">返回</el-button>
+    </div>
+  </div>
+</template>
+
+<script>
+import UploadImage from '../components/uploadImage'
+export default {
+  components: { UploadImage },
+  data() {
+    return {
+      statement: 1
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.addProduct{
+  margin-bottom: 80px;
+  .form-group{
+    margin-bottom: 2%;
+    .param-title{
+      width: 30%;
+    }
+    .param-info{
+      width: 68%;
+      margin-left: 2%;
+    }
+  }
+}
+.addForm{
+  width: 500px;
+  margin: 0 auto;
+  margin-top: 80px;
+}
+.submit-btn{
+    text-align: center;
+    .el-button{
+      width:140px;
+    }
+  }
+</style>

+ 86 - 86
src/views/supplier/product/edit.vue

@@ -1,86 +1,86 @@
-<template>
-  <div class="addProduct">
-    <el-form ref="addFormRef" label-width="120px" class="addForm">
-      <el-form-item label="商品名称:">
-        <el-input placeholder="请输入商品名称" />
-      </el-form-item>
-      <el-form-item label="商品SN码:">
-        <el-input placeholder="请输入商品SN码" />
-      </el-form-item>
-      <el-form-item label="商品图片:">
-        <upload-image />
-      </el-form-item>
-      <el-form-item label="授权牌:">
-        <upload-image />
-      </el-form-item>
-      <el-form-item label="相关参数:">
-        <div class="form-group">
-          <el-input class="param-title" placeholder="例如:品牌" />
-          <el-input class="param-info" placeholder="请输入参数信息" />
-        </div>
-        <div class="form-group">
-          <el-input class="param-title" placeholder="例如:尺寸" />
-          <el-input class="param-info" placeholder="请输入参数信息" />
-        </div>
-        <div class="form-group">
-          <el-input class="param-title" placeholder="例如:功率" />
-          <el-input class="param-info" placeholder="请输入参数信息" />
-        </div>
-        <div class="form-group">
-          <el-input class="param-title" placeholder="例如:型号" />
-          <el-input class="param-info" placeholder="请输入参数信息" />
-        </div>
-        <el-button type="primary" size="mini">添加参数</el-button>
-      </el-form-item>
-      <el-form-item label="上架状态:">
-        <el-select placeholder="产地" style="width:100%">
-          <el-option label="上架" />
-          <el-option label="下架" />
-        </el-select>
-      </el-form-item>
-    </el-form>
-    <div class="submit-btn">
-      <el-button type="primary">保存</el-button>
-      <el-button type="warning">返回</el-button>
-    </div>
-  </div>
-</template>
-
-<script>
-import UploadImage from '../components/uploadImage'
-export default {
-  components: { UploadImage },
-  data() {
-    return {
-      statement: 1
-    }
-  }
-}
-</script>
-
-<style lang="scss" scoped>
-.addProduct{
-  margin-bottom: 80px;
-  .form-group{
-    margin-bottom: 2%;
-    .param-title{
-      width: 30%;
-    }
-    .param-info{
-      width: 68%;
-      margin-left: 2%;
-    }
-  }
-}
-.addForm{
-  width: 500px;
-  margin: 0 auto;
-  margin-top: 80px;
-}
-.submit-btn{
-    text-align: center;
-    .el-button{
-      width:140px;
-    }
-  }
-</style>
+<template>
+  <div class="addProduct">
+    <el-form ref="addFormRef" label-width="120px" class="addForm">
+      <el-form-item label="商品名称:">
+        <el-input placeholder="请输入商品名称" />
+      </el-form-item>
+      <el-form-item label="商品SN码:">
+        <el-input placeholder="请输入商品SN码" />
+      </el-form-item>
+      <el-form-item label="商品图片:">
+        <upload-image />
+      </el-form-item>
+      <el-form-item label="授权牌:">
+        <upload-image />
+      </el-form-item>
+      <el-form-item label="相关参数:">
+        <div class="form-group">
+          <el-input class="param-title" placeholder="例如:品牌" />
+          <el-input class="param-info" placeholder="请输入参数信息" />
+        </div>
+        <div class="form-group">
+          <el-input class="param-title" placeholder="例如:尺寸" />
+          <el-input class="param-info" placeholder="请输入参数信息" />
+        </div>
+        <div class="form-group">
+          <el-input class="param-title" placeholder="例如:功率" />
+          <el-input class="param-info" placeholder="请输入参数信息" />
+        </div>
+        <div class="form-group">
+          <el-input class="param-title" placeholder="例如:型号" />
+          <el-input class="param-info" placeholder="请输入参数信息" />
+        </div>
+        <el-button type="primary" size="mini">添加参数</el-button>
+      </el-form-item>
+      <el-form-item label="上架状态:">
+        <el-select placeholder="产地" style="width:100%">
+          <el-option label="上架" />
+          <el-option label="下架" />
+        </el-select>
+      </el-form-item>
+    </el-form>
+    <div class="submit-btn">
+      <el-button type="primary">保存</el-button>
+      <el-button type="warning" @click="$_back()">返回</el-button>
+    </div>
+  </div>
+</template>
+
+<script>
+import UploadImage from '../components/uploadImage'
+export default {
+  components: { UploadImage },
+  data() {
+    return {
+      statement: 1
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.addProduct{
+  margin-bottom: 80px;
+  .form-group{
+    margin-bottom: 2%;
+    .param-title{
+      width: 30%;
+    }
+    .param-info{
+      width: 68%;
+      margin-left: 2%;
+    }
+  }
+}
+.addForm{
+  width: 500px;
+  margin: 0 auto;
+  margin-top: 80px;
+}
+.submit-btn{
+    text-align: center;
+    .el-button{
+      width:140px;
+    }
+  }
+</style>

+ 163 - 162
src/views/supplier/product/index.vue

@@ -1,162 +1,163 @@
-<template>
-  <div class="app-container">
-    <div class="filter-container">
-      <span>授权机构:</span>
-      <el-input v-model="listQuery.title" placeholder="授权机构" style="width: 200px;" class="filter-item" @keyup.enter.native="handleFilter" />
-      <el-button type="primary">查询</el-button>
-      <el-button type="primary" @click="handleAddPro">添加商品</el-button>
-    </div>
-    <!-- 表格区域 -->
-    <el-table
-      :key="tableKey"
-      v-loading="listLoading"
-      :data="list"
-      border
-      fit
-      highlight-current-row
-      style="width: 100%;"
-      @sort-change="sortChange"
-    >
-      <el-table-column label="序号" prop="id" sortable="custom" align="center" width="80" :class-name="getSortClass('id')">
-        <template>
-          <span>1</span>
-        </template>
-      </el-table-column>
-      <el-table-column label="商品名称" align="center">
-        <template>
-          <span>商品名称</span>
-        </template>
-      </el-table-column>
-      <el-table-column label="商品SN码" align="center">
-        <template>
-          <span>类型</span>
-        </template>
-      </el-table-column>
-      <el-table-column label="上架状态" width="140px" align="center">
-        <template>
-          <i class="el-icon-circle-check status success" />
-        </template>
-      </el-table-column>
-      <el-table-column label="创建时间" class-name="status-col" width="300px" align="center">
-        <template slot-scope="{row}">
-          <span>{{ row.timestamp | parseTime('{y}-{m}-{d} {h}:{i}') }}</span>
-        </template>
-      </el-table-column>
-      <el-table-column label="创建人" width="180px" align="center">
-        <template>
-          <span>创建人</span>
-        </template>
-      </el-table-column>
-      <el-table-column label="操作" align="center" width="240px" class-name="small-padding fixed-width">
-        <template>
-          <el-button type="default" size="mini" @click="handleEditPro">
-            编辑
-          </el-button>
-          <el-button type="danger" size="mini">
-            删除
-          </el-button>
-          <el-button type="primary" size="mini">
-            二维码
-          </el-button>
-        </template>
-      </el-table-column>
-    </el-table>
-
-    <!-- 页码 -->
-    <pagination v-show="total>0" :total="total" :page.sync="listQuery.page" :limit.sync="listQuery.limit" @pagination="getList" />
-  </div>
-</template>
-
-<script>
-import { fetchList } from '@/api/article'
-import Pagination from '@/components/Pagination' // secondary package based on el-pagination
-
-export default {
-  name: 'ComplexTable',
-  components: { Pagination },
-  data() {
-    return {
-      tableKey: 0,
-      list: null,
-      total: 0,
-      listLoading: true,
-      listQuery: {
-        page: 1,
-        limit: 20,
-        importance: undefined,
-        title: undefined,
-        type: undefined,
-        sort: '+id'
-      }
-    }
-  },
-  created() {
-    this.getList()
-  },
-  methods: {
-    // 添加
-    handleAddPro() {
-      this.$router.push('add')
-    },
-    // 修改
-    handleEditPro() {
-      this.$router.push('edit')
-    },
-    getList() {
-      this.listLoading = true
-      fetchList(this.listQuery).then(response => {
-        this.list = response.data.items
-        this.total = response.data.total
-
-        // Just to simulate the time of the request
-        setTimeout(() => {
-          this.listLoading = false
-        }, 1.5 * 1000)
-      })
-    },
-    handleFilter() {
-      this.listQuery.page = 1
-      this.getList()
-    },
-
-    sortChange(data) {
-      const { prop, order } = data
-      if (prop === 'id') {
-        this.sortByID(order)
-      }
-    },
-    sortByID(order) {
-      if (order === 'ascending') {
-        this.listQuery.sort = '+id'
-      } else {
-        this.listQuery.sort = '-id'
-      }
-      this.handleFilter()
-    },
-    getSortClass: function(key) {
-      const sort = this.listQuery.sort
-      return sort === `+${key}` ? 'ascending' : 'descending'
-    }
-  }
-}
-</script>
-
-<style lang="scss" scoped>
-.filter-container{
-  span{
-    display: inline-block;
-    margin-bottom: 10px;
-    vertical-align: middle;
-    font-size: 14px;
-  }
-  .el-button{
-    display: inline-block;
-    margin-bottom: 10px;
-    vertical-align: middle;
-  }
-  .el-input,.el-select{
-    margin-right: 10px;
-    margin-left: 10px;
-  }
-}
-</style>
+<template>
+  <div class="app-container">
+    <div class="filter-container">
+      <span>授权机构:</span>
+      <el-input v-model="listQuery.title" placeholder="授权机构" style="width: 200px;" class="filter-item" @keyup.enter.native="handleFilter" />
+      <el-button type="primary">查询</el-button>
+      <el-button type="primary" @click="$_navigationTo('add')">添加商品</el-button>
+    </div>
+    <!-- 表格区域 -->
+    <el-table
+      :key="tableKey"
+      v-loading="listLoading"
+      :data="list"
+      border
+      fit
+      highlight-current-row
+      style="width: 100%;"
+      @sort-change="sortChange"
+    >
+      <el-table-column label="序号" prop="id" sortable="custom" align="center" width="80" :class-name="getSortClass('id')">
+        <template>
+          <span>1</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="商品名称" align="center">
+        <template>
+          <span>商品名称</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="商品SN码" align="center">
+        <template>
+          <span>类型</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="上架状态" width="140px" align="center">
+        <template>
+          <i class="el-icon-circle-check status success" />
+        </template>
+      </el-table-column>
+      <el-table-column label="创建时间" class-name="status-col" width="300px" align="center">
+        <template slot-scope="{row}">
+          <span>{{ row.timestamp | parseTime('{y}-{m}-{d} {h}:{i}') }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="创建人" width="180px" align="center">
+        <template>
+          <span>创建人</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="操作" align="center" width="240px" class-name="small-padding fixed-width">
+        <template>
+          <el-button type="default" size="mini" @click="$_navigationTo('edit')">
+            编辑
+          </el-button>
+          <el-button type="danger" size="mini">
+            删除
+          </el-button>
+          <el-button type="primary" size="mini">
+            二维码
+          </el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <!-- 页码 -->
+    <pagination v-show="total>0" :total="total" :page.sync="listQuery.page" :limit.sync="listQuery.limit" @pagination="getList" />
+  </div>
+</template>
+
+<script>
+import { fetchList } from '@/api/article'
+import Pagination from '@/components/Pagination' // secondary package based on el-pagination
+
+export default {
+  name: 'ComplexTable',
+  components: { Pagination },
+  data() {
+    return {
+      tableKey: 0,
+      list: null,
+      total: 0,
+      listLoading: true,
+      listQuery: {
+        page: 1,
+        limit: 20,
+        importance: undefined,
+        title: undefined,
+        type: undefined,
+        sort: '+id'
+      }
+    }
+  },
+  created() {
+    this.getList()
+  },
+  methods: {
+    // 添加
+    handleAddPro() {
+      this.$router.push('add')
+    },
+    // 修改
+    handleEditPro() {
+      this.$router.push('edit')
+    },
+    // 获取列表信息
+    getList() {
+      fetchList(this.listQuery).then(response => {
+        this.list = response.data.items
+        this.total = response.data.total
+
+        // Just to simulate the time of the request
+        setTimeout(() => {
+          this.listLoading = false
+        }, 1.5 * 1000)
+      })
+    },
+    // 过滤列表
+    handleFilter() {
+      this.listQuery.page = 1
+      this.getList()
+    },
+
+    sortChange(data) {
+      const { prop, order } = data
+      if (prop === 'id') {
+        this.sortByID(order)
+      }
+    },
+    sortByID(order) {
+      if (order === 'ascending') {
+        this.listQuery.sort = '+id'
+      } else {
+        this.listQuery.sort = '-id'
+      }
+      this.handleFilter()
+    },
+    getSortClass: function(key) {
+      const sort = this.listQuery.sort
+      return sort === `+${key}` ? 'ascending' : 'descending'
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.filter-container{
+  span{
+    display: inline-block;
+    margin-bottom: 10px;
+    vertical-align: middle;
+    font-size: 14px;
+  }
+  .el-button{
+    display: inline-block;
+    margin-bottom: 10px;
+    vertical-align: middle;
+  }
+  .el-input,.el-select{
+    margin-right: 10px;
+    margin-left: 10px;
+  }
+}
+</style>

+ 192 - 88
src/views/supplier/user/add.vue

@@ -1,88 +1,192 @@
-<template>
-  <div class="addSupplier">
-    <el-form ref="addFormRef" label-width="120px" class="addForm">
-      <el-form-item label="供应商类型:">
-        <el-select placeholder="供应商类型" style="width:100%">
-          <el-option label="供应商类型1" />
-          <el-option label="供应商类型2" />
-        </el-select>
-      </el-form-item>
-      <el-form-item label="供应商名称:">
-        <el-input placeholder="供应商名称" />
-      </el-form-item>
-      <el-form-item label="手机号:">
-        <el-input placeholder="手机号" />
-      </el-form-item>
-      <el-form-item label="联系人:">
-        <el-input placeholder="联系人" />
-      </el-form-item>
-      <el-form-item label="所属品牌:">
-        <el-select placeholder="所属品牌" style="width:100%">
-          <el-option label="品牌1" />
-          <el-option label="品牌2" />
-        </el-select>
-      </el-form-item>
-      <el-form-item label="产地:">
-        <el-select placeholder="产地" style="width:100%">
-          <el-option label="中国" />
-          <el-option label="法国" />
-        </el-select>
-      </el-form-item>
-      <el-form-item label="品牌logo:">
-        <upload-image />
-      </el-form-item>
-      <el-form-item label="官网认证链接:">
-        <el-input />
-      </el-form-item>
-      <el-form-item label="代理声明:">
-        <el-radio v-model="statement" :label="1">弹窗</el-radio>
-        <el-radio v-model="statement" :label="2">链接</el-radio>
-        <el-radio v-model="statement" :label="3">图片</el-radio>
-        <el-radio v-model="statement" :label="4">文件</el-radio>
-      </el-form-item>
-      <el-form-item label="弹窗内容:">
-        <el-input v-if="statement === 1" type="textarea" />
-        <el-input v-if="statement === 2" />
-        <upload-image v-if="statement === 3" />
-        <upload-file v-if="statement === 4" />
-      </el-form-item>
-      <el-form-item label="供应商状态:">
-        <el-input />
-      </el-form-item>
-    </el-form>
-    <div class="submit-btn">
-      <el-button type="primary">保存</el-button>
-      <el-button type="warning">返回</el-button>
-    </div>
-  </div>
-</template>
-
-<script>
-import UploadImage from '../components/uploadImage'
-import UploadFile from '../components/uploadFile'
-export default {
-  components: { UploadImage, UploadFile },
-  data() {
-    return {
-      statement: 1
-    }
-  }
-}
-</script>
-
-<style lang="scss" scoped>
-.addSupplier{
-  margin-bottom: 80px;
-}
-.addForm{
-  width: 500px;
-  margin: 0 auto;
-  margin-top: 80px;
-}
-.submit-btn{
-    text-align: center;
-    .el-button{
-      width:140px;
-    }
-  }
-</style>
+<template>
+  <div class="addSupplier">
+    <el-form ref="addFormRef" :model="addData" :rules="rules" label-width="120px" class="addForm">
+      <el-form-item label="供应商类型:" prop="shopType">
+        <el-select v-model="addData.shopType" placeholder="请选择供应商类型" style="width: 100%">
+          <el-option label="代理商" :value="1" />
+          <el-option label="品牌方" :value="2" />
+        </el-select>
+      </el-form-item>
+
+      <!-- 供应商名称 -->
+      <el-form-item v-if="addData.shopType===1" label="供应商名称:" prop="shopName">
+        <el-input v-model="addData.shopName" placeholder="请输入供应商名称" />
+      </el-form-item>
+
+      <el-form-item v-if="addData.shopType===2" label="供应商名称:" prop="shopName">
+        <el-select v-model="addData.shopName" placeholder="请选择供应商类型" style="width: 100%">
+          <el-option label="供应商名称1" value="1" />
+          <el-option label="供应商名称2" value="2" />
+        </el-select>
+      </el-form-item>
+      <!-- 供应商名称END -->
+
+      <el-form-item label="手机号:" prop="mobile">
+        <el-input v-model="addData.mobile" placeholder="请输入手机号" />
+      </el-form-item>
+      <el-form-item label="联系人:" prop="linkMan">
+        <el-input v-model="addData.linkMan" placeholder="请输入联系人" />
+      </el-form-item>
+      <el-form-item label="所属品牌:" prop="brandId">
+        <el-select v-model="addData.brandId" placeholder="请选择所属品牌" style="width: 100%">
+          <el-option
+            v-for="item in brand"
+            :key="item.id"
+            :label="item.label"
+            :value="item.id"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="产地:" prop="countryId">
+        <el-select v-model="addData.countryId" placeholder="产地" style="width: 100%">
+          <el-option label="中国" value="1" />
+          <el-option label="法国" value="2" />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="品牌logo:" class="no-input" prop="brandAuthLogo">
+        <upload-image @success="imageUploadSuccess" @error="imageUploadFaild" />
+        <el-input v-model="addData.brandAuthLogo" type="hidden" />
+      </el-form-item>
+      <el-form-item label="官网认证链接:">
+        <el-input v-model="addData.securityLink" placeholder="请输入官网认证链接" />
+      </el-form-item>
+      <template v-if="addData.shopType===1">
+        <el-form-item label="代理声明:">
+          <el-radio v-model="addData.statementType" :label="1">弹窗</el-radio>
+          <el-radio v-model="addData.statementType" :label="2">链接</el-radio>
+          <el-radio v-model="addData.statementType" :label="3">图片</el-radio>
+          <el-radio v-model="addData.statementType" :label="4">文件</el-radio>
+        </el-form-item>
+
+        <el-form-item v-if="addData.statementType === 1" label="弹窗:" prop="statementContent">
+          <el-input v-if="addData.statementType === 1" v-model="addData.statementContent" type="textarea" />
+        </el-form-item>
+        <el-form-item v-else-if="addData.statementType === 2" label="链接:" prop="statementLink">
+          <el-input v-if="addData.statementType === 2" v-model="addData.statementLink" />
+        </el-form-item>
+        <el-form-item v-else-if="addData.statementType === 3" label="图片:" class="no-input" prop="statementImage">
+          <upload-image v-if="addData.statementType === 3" />
+          <el-input v-model="addData.statementImage" type="statementFileId" />
+        </el-form-item>
+        <el-form-item v-else label="文件:" prop="statementImage">
+          <upload-file v-if="addData.statementType === 4" @success="fileUploadSuccess" @error="fileUploadFaild" />
+          <el-input v-model="addData.statementFileId" type="hidden" />
+        </el-form-item>
+
+      </template>
+      <el-form-item label="供应商状态:">
+        <el-select v-model="addData.shopStatus" placeholder="请选择供应商状态" style="width: 100%">
+          <el-option label="启用" :value="1" />
+          <el-option label="禁用" :value="0" />
+        </el-select>
+      </el-form-item>
+    </el-form>
+    <div class="submit-btn">
+      <el-button type="primary" @click="save">保存</el-button>
+      <el-button type="warning" @click="$_back()">返回</el-button>
+    </div>
+  </div>
+</template>
+
+<script>
+import UploadImage from '../components/uploadImage'
+import UploadFile from '../components/uploadFile'
+import { mapGetters } from 'vuex'
+import { isMobile } from '@/utils/validate'
+export default {
+  components: { UploadImage, UploadFile },
+  data() {
+    var valideMobile = (rule, value, callback) => {
+      if (!isMobile(value)) {
+        console.log(isMobile(value))
+        callback(new Error('手机号格式不正确'))
+      } else {
+        callback()
+      }
+    }
+    return {
+      addData: {
+        authUserId: '', // 供应商id
+        brandAuthLogo: 'brandAuthLogo.png', //	品牌授权logo
+        brandId: '', // 品牌id
+        countryId: '', // 产地国家id
+        createBy: '', // 创建人用户id
+        linkMan: '', // 联系人
+        mobile: '', // 手机号
+        securityLink: '', // 官网认证链接
+        shopName: '', // 供应商名称
+        shopStatus: 1, // 供应商状态:0停用 1启用
+        shopType: 2, //	供应商类型:1代理商,2品牌方
+        statementContent: '', // 声明弹窗内容
+        statementLink: '', // 声明链接
+        statementFileId: '', //	声明文件id
+        statementImage: 'statement.png', // 声明图片
+        statementType: 1 // 代理声明类型:1弹窗 2链接 3图片 4文件(.doc .ppt .pdf)
+      },
+      brand: [
+        { id: 1, label: '品牌1' },
+        { id: 2, label: '品牌2' }
+      ],
+      rules: {
+        shopType: [{ required: true, message: '供应商类型不能为空', trigger: 'change' }],
+        shopName: [{ required: true, message: '供应商名称不能为空', trigger: ['blur', 'change'] }],
+        mobile: [
+          { required: true, message: '手机号不能为空', trigger: 'blur' },
+          { validator: valideMobile, trigger: 'blur' }
+        ], // 手机号格式校验
+        linkMan: [{ required: true, message: '联系人不能为空', trigger: 'blur' }],
+        countryId: [{ required: true, message: '产地不能为空', trigger: 'change' }],
+        brandAuthLogo: [{ required: true }],
+        brandId: [{ required: true, message: '所属品牌不能为空', trigger: 'change' }],
+        statementContent: [{ required: true, message: '声明不能为空', trigger: 'blur' }],
+        statementLink: [{ required: true, message: '声明链接不能为空', trigger: 'blur' }],
+        statementImage: [{ required: true }],
+        statementFileId: [{ required: true }]
+      }
+    }
+  },
+  computed: {
+    ...mapGetters(['userIdentity', 'authUserId'])
+  },
+  methods: {
+    // 保存
+    save() {
+      this.$refs.addFormRef.validate(valide => {
+        console.log(valide)
+      })
+    },
+    // 文件上传成功
+    fileUploadSuccess(response, file, fileList) {},
+    fileUploadFaild(err, file, fileList) {
+      console.log(err)
+    },
+
+    // 图片上传成功
+    imageUploadSuccess(response, file, fileList) {},
+    imageUploadFaild(err, file, fileList) {
+      console.log(err)
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.addSupplier {
+  margin-bottom: 80px;
+}
+.addForm {
+  width: 500px;
+  margin: 0 auto;
+  margin-top: 80px;
+}
+.submit-btn {
+  text-align: center;
+  .el-button {
+    width: 140px;
+  }
+}
+.no-input{
+  height: 148px !important;
+  overflow: hidden;
+}
+</style>

+ 88 - 88
src/views/supplier/user/edit.vue

@@ -1,88 +1,88 @@
-<template>
-  <div class="addSupplier">
-    <el-form ref="addFormRef" label-width="120px" class="addForm">
-      <el-form-item label="供应商类型:">
-        <el-select placeholder="供应商类型" style="width:100%">
-          <el-option label="供应商类型1" />
-          <el-option label="供应商类型2" />
-        </el-select>
-      </el-form-item>
-      <el-form-item label="供应商名称:">
-        <el-input placeholder="供应商名称" />
-      </el-form-item>
-      <el-form-item label="手机号:">
-        <el-input placeholder="手机号" />
-      </el-form-item>
-      <el-form-item label="联系人:">
-        <el-input placeholder="联系人" />
-      </el-form-item>
-      <el-form-item label="所属品牌:">
-        <el-select placeholder="所属品牌" style="width:100%">
-          <el-option label="品牌1" />
-          <el-option label="品牌2" />
-        </el-select>
-      </el-form-item>
-      <el-form-item label="产地:">
-        <el-select placeholder="产地" style="width:100%">
-          <el-option label="中国" />
-          <el-option label="法国" />
-        </el-select>
-      </el-form-item>
-      <el-form-item label="品牌logo:">
-        <upload-image />
-      </el-form-item>
-      <el-form-item label="官网认证链接:">
-        <el-input />
-      </el-form-item>
-      <el-form-item label="代理声明:">
-        <el-radio v-model="statement" :label="1">弹窗</el-radio>
-        <el-radio v-model="statement" :label="2">链接</el-radio>
-        <el-radio v-model="statement" :label="3">图片</el-radio>
-        <el-radio v-model="statement" :label="4">文件</el-radio>
-      </el-form-item>
-      <el-form-item label="弹窗内容:">
-        <el-input v-if="statement === 1" type="textarea" />
-        <el-input v-if="statement === 2" />
-        <upload-image v-if="statement === 3" />
-        <upload-file v-if="statement === 4" />
-      </el-form-item>
-      <el-form-item label="供应商状态:">
-        <el-input />
-      </el-form-item>
-    </el-form>
-    <div class="submit-btn">
-      <el-button type="primary">保存</el-button>
-      <el-button type="warning">返回</el-button>
-    </div>
-  </div>
-</template>
-
-<script>
-import UploadImage from '../components/uploadImage'
-import UploadFile from '../components/uploadFile'
-export default {
-  components: { UploadImage, UploadFile },
-  data() {
-    return {
-      statement: 1
-    }
-  }
-}
-</script>
-
-<style lang="scss" scoped>
-.addSupplier{
-  margin-bottom: 80px;
-}
-.addForm{
-  width: 500px;
-  margin: 0 auto;
-  margin-top: 80px;
-}
-.submit-btn{
-    text-align: center;
-    .el-button{
-      width:140px;
-    }
-  }
-</style>
+<template>
+  <div class="addSupplier">
+    <el-form ref="addFormRef" label-width="120px" class="addForm">
+      <el-form-item label="供应商类型:">
+        <el-select placeholder="供应商类型" style="width:100%">
+          <el-option label="供应商类型1" />
+          <el-option label="供应商类型2" />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="供应商名称:">
+        <el-input placeholder="供应商名称" />
+      </el-form-item>
+      <el-form-item label="手机号:">
+        <el-input placeholder="手机号" />
+      </el-form-item>
+      <el-form-item label="联系人:">
+        <el-input placeholder="联系人" />
+      </el-form-item>
+      <el-form-item label="所属品牌:">
+        <el-select placeholder="所属品牌" style="width:100%">
+          <el-option label="品牌1" />
+          <el-option label="品牌2" />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="产地:">
+        <el-select placeholder="产地" style="width:100%">
+          <el-option label="中国" />
+          <el-option label="法国" />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="品牌logo:">
+        <upload-image />
+      </el-form-item>
+      <el-form-item label="官网认证链接:">
+        <el-input />
+      </el-form-item>
+      <el-form-item label="代理声明:">
+        <el-radio v-model="statement" :label="1">弹窗</el-radio>
+        <el-radio v-model="statement" :label="2">链接</el-radio>
+        <el-radio v-model="statement" :label="3">图片</el-radio>
+        <el-radio v-model="statement" :label="4">文件</el-radio>
+      </el-form-item>
+      <el-form-item label="弹窗内容:">
+        <el-input v-if="statement === 1" type="textarea" />
+        <el-input v-if="statement === 2" />
+        <upload-image v-if="statement === 3" />
+        <upload-file v-if="statement === 4" />
+      </el-form-item>
+      <el-form-item label="供应商状态:">
+        <el-input />
+      </el-form-item>
+    </el-form>
+    <div class="submit-btn">
+      <el-button type="primary">保存</el-button>
+      <el-button type="warning" @click="$_back()">返回</el-button>
+    </div>
+  </div>
+</template>
+
+<script>
+import UploadImage from '../components/uploadImage'
+import UploadFile from '../components/uploadFile'
+export default {
+  components: { UploadImage, UploadFile },
+  data() {
+    return {
+      statement: 1
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.addSupplier{
+  margin-bottom: 80px;
+}
+.addForm{
+  width: 500px;
+  margin: 0 auto;
+  margin-top: 80px;
+}
+.submit-btn{
+    text-align: center;
+    .el-button{
+      width:140px;
+    }
+  }
+</style>

+ 182 - 215
src/views/supplier/user/index.vue

@@ -1,215 +1,182 @@
-<template>
-  <div class="app-container">
-    <div class="filter-container">
-      <span>供应商名称:</span>
-      <el-input v-model="listQuery.title" placeholder="供应商名称" style="width: 200px;" class="filter-item" @keyup.enter.native="handleFilter" />
-      <span>供应商类型:</span>
-      <el-select v-model="listQuery.importance" placeholder="供应商类型" clearable style="width: 200px" class="filter-item">
-        <el-option v-for="item in importanceOptions" :key="item" :label="item" :value="item" />
-      </el-select>
-      <span>所属品牌:</span>
-      <el-select v-model="listQuery.type" placeholder="所属品牌" clearable class="filter-item" style="width: 200px">
-        <el-option v-for="item in calendarTypeOptions" :key="item.key" :label="item.display_name+'('+item.key+')'" :value="item.key" />
-      </el-select>
-      <span>手机号:</span>
-      <el-input v-model="listQuery.title" placeholder="手机号" style="width: 200px;" class="filter-item" @keyup.enter.native="handleFilter" />
-      <span>联系人:</span>
-      <el-input v-model="listQuery.title" placeholder="联系人" style="width: 200px;" class="filter-item" @keyup.enter.native="handleFilter" />
-      <el-button type="primary">查询</el-button>
-      <el-button type="primary" @click="addSupplier">添加供应商</el-button>
-    </div>
-    <!-- 表格区域 -->
-    <el-table
-      :key="tableKey"
-      v-loading="listLoading"
-      :data="list"
-      border
-      fit
-      highlight-current-row
-      style="width: 100%;"
-      @sort-change="sortChange"
-    >
-      <el-table-column label="序号" prop="id" sortable="custom" align="center" width="80" :class-name="getSortClass('id')">
-        <template>
-          <span>1</span>
-        </template>
-      </el-table-column>
-      <el-table-column label="供应商名称" align="center">
-        <template>
-          <span>名称</span>
-        </template>
-      </el-table-column>
-      <el-table-column label="供应商类型" width="150px" align="center">
-        <template>
-          <span>类型</span>
-        </template>
-      </el-table-column>
-      <el-table-column label="所属品牌" width="150px" align="center">
-        <template>
-          <span>品牌</span>
-        </template>
-      </el-table-column>
-      <el-table-column label="手机号" width="120px" align="center">
-        <template>
-          <span>18888888888</span>
-        </template>
-      </el-table-column>
-      <el-table-column label="联系人" width="100px" align="center">
-        <template>
-          <span>马云</span>
-        </template>
-      </el-table-column>
-      <el-table-column label="供应商状态" align="center" width="100px">
-        <template>
-          <i class="el-icon-circle-check status success" />
-        </template>
-      </el-table-column>
-      <el-table-column label="创建时间" class-name="status-col" width="200px">
-        <template slot-scope="{row}">
-          <span>{{ row.timestamp | parseTime('{y}-{m}-{d} {h}:{i}') }}</span>
-        </template>
-      </el-table-column>
-      <el-table-column label="创建人" class-name="status-col" width="100px">
-        <template>
-          <span>创建人</span>
-        </template>
-      </el-table-column>
-      <el-table-column label="操作" align="center" width="360" class-name="small-padding fixed-width">
-        <template>
-          <el-button type="info" size="mini" @click="editSupplier">
-            编辑
-          </el-button>
-          <el-button size="mini" type="primary">
-            代他操作
-          </el-button>
-          <el-button size="mini" type="success" @click="handleAuthList">
-            授权信息
-          </el-button>
-          <el-button size="mini" type="danger">
-            重置密码
-          </el-button>
-        </template>
-      </el-table-column>
-    </el-table>
-
-    <!-- 页码 -->
-    <pagination v-show="total>0" :total="total" :page.sync="listQuery.page" :limit.sync="listQuery.limit" @pagination="getList" />
-
-  </div>
-</template>
-
-<script>
-import { fetchList } from '@/api/article'
-import Pagination from '@/components/Pagination' // secondary package based on el-pagination
-
-const calendarTypeOptions = [
-  { key: 'CN', display_name: 'China' },
-  { key: 'US', display_name: 'USA' },
-  { key: 'JP', display_name: 'Japan' },
-  { key: 'EU', display_name: 'Eurozone' }
-]
-
-export default {
-  name: 'ComplexTable',
-  components: { Pagination },
-  data() {
-    return {
-      tableKey: 0,
-      list: null,
-      total: 0,
-      listLoading: true,
-      listQuery: {
-        page: 1,
-        limit: 20,
-        importance: undefined,
-        title: undefined,
-        type: undefined,
-        sort: '+id'
-      },
-      importanceOptions: [1, 2, 3],
-      calendarTypeOptions,
-      temp: {
-        id: undefined,
-        importance: 1,
-        remark: '',
-        timestamp: new Date(),
-        title: '',
-        type: '',
-        status: 'published'
-      }
-    }
-  },
-  created() {
-    this.getList()
-  },
-  methods: {
-    // 跳转授权列表
-    handleAuthList() {
-      this.$router.push('/auth/list')
-    },
-    // 修改
-    editSupplier() {
-      this.$router.push('edit')
-    },
-    // 添加
-    addSupplier() {
-      this.$router.push('add')
-    },
-    getList() {
-      this.listLoading = true
-      fetchList(this.listQuery).then(response => {
-        this.list = response.data.items
-        this.total = response.data.total
-
-        // Just to simulate the time of the request
-        setTimeout(() => {
-          this.listLoading = false
-        }, 1.5 * 1000)
-      })
-    },
-    handleFilter() {
-      this.listQuery.page = 1
-      this.getList()
-    },
-    sortChange(data) {
-      const { prop, order } = data
-      if (prop === 'id') {
-        this.sortByID(order)
-      }
-    },
-    sortByID(order) {
-      if (order === 'ascending') {
-        this.listQuery.sort = '+id'
-      } else {
-        this.listQuery.sort = '-id'
-      }
-      this.handleFilter()
-    },
-
-    getSortClass: function(key) {
-      const sort = this.listQuery.sort
-      return sort === `+${key}` ? 'ascending' : 'descending'
-    }
-  }
-}
-</script>
-
-<style lang="scss" scoped>
-.filter-container{
-  span{
-    display: inline-block;
-    margin-bottom: 10px;
-    vertical-align: middle;
-    font-size: 14px;
-  }
-  .el-button{
-    display: inline-block;
-    margin-bottom: 10px;
-    vertical-align: middle;
-  }
-  .el-input,.el-select{
-    margin-right: 10px;
-    margin-left: 10px;
-  }
-}
-</style>
+<template>
+  <div class="app-container">
+    <div class="filter-container">
+      <span>供应商名称:</span>
+      <el-input v-model="listQuery.shopName" placeholder="供应商名称" style="width: 200px;" class="filter-item" @keyup.enter.native="handleFilter" />
+      <span>供应商类型:</span>
+      <el-select v-model="listQuery.shopType" placeholder="供应商类型" clearable style="width: 200px" class="filter-item">
+        <el-option label="供应商" :value="1" />
+        <el-option label="品牌方" :value="2" />
+      </el-select>
+      <span>所属品牌:</span>
+      <el-select v-model="listQuery.brandId" placeholder="所属品牌" clearable class="filter-item" style="width: 200px">
+        <el-option label="品牌1" :value="1" />
+        <el-option label="品牌2" :value="2" />
+      </el-select>
+      <span>手机号:</span>
+      <el-input v-model="listQuery.mobile" placeholder="手机号" style="width: 200px;" class="filter-item" @keyup.enter.native="handleFilter" />
+      <span>联系人:</span>
+      <el-input v-model="listQuery.linkMan" placeholder="联系人" style="width: 200px;" class="filter-item" @keyup.enter.native="handleFilter" />
+      <el-button type="primary" @click="getList()">查询</el-button>
+      <el-button type="primary" @click="$_navigationTo('add')">添加供应商</el-button>
+    </div>
+    <!-- 表格区域 -->
+    <el-table
+      :key="tableKey"
+      v-loading="listLoading"
+      :data="list"
+      border
+      fit
+      highlight-current-row
+      style="width: 100%;"
+    >
+      <el-table-column label="序号" type="index" sortable="custom" align="center" width="80" />
+      <el-table-column label="供应商名称" align="center" prop="name" />
+      <el-table-column label="供应商类型" width="150px" align="center">
+        <template>
+          <span>类型</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="所属品牌" width="150px" align="center" prop="brandName" />
+      <el-table-column label="手机号" width="120px" align="center" prop="mobile" />
+      <el-table-column label="联系人" width="100px" align="center" prop="linkMan" />
+      <el-table-column label="供应商状态" align="center" width="100px">
+        <template v-slot="{row}">
+          <el-switch v-model="row.shopStatus" @change="handleChangeStatus(row)" />
+        </template>
+      </el-table-column>
+      <el-table-column label="创建时间" class-name="status-col" width="200px">
+        <template slot-scope="{row}">
+          <span>{{ row.createTime | parseTime('{y}-{m}-{d} {h}:{i}') }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="创建人" class-name="status-col" width="100px" prop="createBy" />
+      <el-table-column label="操作" align="center" width="360" class-name="small-padding fixed-width">
+        <template>
+          <el-button type="info" size="mini" @click="$_navigationTo('edit')">
+            编辑
+          </el-button>
+          <el-button size="mini" type="primary" @click="handleProxy">
+            代他操作
+          </el-button>
+          <el-button size="mini" type="success" @click="$_navigationTo('/auth/list')">
+            授权信息
+          </el-button>
+          <el-button size="mini" type="danger" @click="handleResetPwd">
+            重置密码
+          </el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <!-- 页码 -->
+    <pagination v-show="total>0" :total="total" :page.sync="listQuery.pageNum" :limit.sync="listQuery.pageSize" @pagination="getList" />
+
+  </div>
+</template>
+
+<script>
+import Pagination from '@/components/Pagination' // secondary package based on el-pagination
+import { fetchSupplierList } from '@/api/supplier'
+export default {
+  name: 'ComplexTable',
+  components: { Pagination },
+  data() {
+    return {
+      slider: 1,
+      listLoading: true,
+      tableKey: '',
+      total: 0, // 条数统计
+      listQuery: {
+        brandId: '', // 品牌id
+        linkMan: '', // 联系人
+        mobile: '', // 手机号
+        pageNum: 0, // 页码
+        pageSize: 20, // 分页大小
+        shopName: '', // 供应商名称
+        shopType: ''// 供应商类型
+      },
+      list: []
+    }
+  },
+  created() {
+    this.getList(this.listQuery)
+  },
+  methods: {
+    // 获取列表数据
+    getList(listQuery) {
+      this.listLoading = true
+      fetchSupplierList().then(res => {
+        if (res.code !== 0) {
+          return this.$message.error('获取数据失败~')
+        }
+        console.log(res)
+        const { total, list } = res.data
+        this.total = total
+        this.formatList(list)
+        this.list = list
+      }).catch(err => { console.log(err) }).finally(() => {
+        this.listLoading = false
+      })
+    },
+    // 格式化数组
+    formatList(list = []) {
+      list.forEach(i => {
+        i.shopStatus = i.shopStatus === 1
+      })
+    },
+    // 供应商状态改变
+    handleChangeStatus(item) {
+      const params = {
+        shopStatus: item.shopStatus ? 1 : 0,
+        authUserId: item.authUserId
+      }
+      console.log(params)
+    },
+    // 重置密码
+    async handleResetPwd() {
+      const text = await this.$confirm('此操作将重置该供应商的密码, 是否继续?', '提示', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).catch(() => {
+        this.$message.info('已取消操作')
+      })
+      if (text !== 'confirm') return
+      // 要执行的操作
+    },
+    // 代他操作
+    async handleProxy() {
+      const text = await this.$confirm('此操作将临时登录该供应商账号, 是否继续?', '提示', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).catch(() => {
+        this.$message.info('已取消操作')
+      })
+      if (text !== 'confirm') return
+      // 要执行的操作
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.filter-container{
+  span{
+    display: inline-block;
+    margin-bottom: 10px;
+    vertical-align: middle;
+    font-size: 14px;
+  }
+  .el-button{
+    display: inline-block;
+    margin-bottom: 10px;
+    vertical-align: middle;
+  }
+  .el-input,.el-select{
+    margin-right: 10px;
+    margin-left: 10px;
+  }
+}
+</style>

+ 124 - 124
vue.config.js

@@ -1,124 +1,124 @@
-'use strict'
-const path = require('path')
-const defaultSettings = require('./src/settings.js')
-
-function resolve(dir) {
-  return path.join(__dirname, dir)
-}
-
-const name = defaultSettings.title || 'vue Element Admin' // page title
-
-// If your port is set to 80,
-// use administrator privileges to execute the command line.
-// For example, Mac: sudo npm run
-// You can change the port by the following method:
-// port = 9527 npm run dev OR npm run dev --port = 9527
-const port = process.env.port || process.env.npm_config_port || 9527 // dev port
-
-// All configuration item explanations can be find in https://cli.vuejs.org/config/
-module.exports = {
-  /**
-   * You will need to set publicPath if you plan to deploy your site under a sub path,
-   * for example GitHub Pages. If you plan to deploy your site to https://foo.github.io/bar/,
-   * then publicPath should be set to "/bar/".
-   * In most cases please use '/' !!!
-   * Detail: https://cli.vuejs.org/config/#publicpath
-   */
-  publicPath: '/',
-  outputDir: 'dist',
-  assetsDir: 'static',
-  lintOnSave: process.env.NODE_ENV === 'development',
-  productionSourceMap: false,
-  devServer: {
-    port: port,
-    open: true,
-    overlay: {
-      warnings: false,
-      errors: true
-    },
-    before: require('./mock/mock-server.js')
-  },
-  configureWebpack: {
-    // provide the app's title in webpack's name field, so that
-    // it can be accessed in index.html to inject the correct title.
-    name: name,
-    resolve: {
-      alias: {
-        '@': resolve('src')
-      }
-    }
-  },
-  chainWebpack(config) {
-    // it can improve the speed of the first screen, it is recommended to turn on preload
-    // it can improve the speed of the first screen, it is recommended to turn on preload
-    config.plugin('preload').tap(() => [
-      {
-        rel: 'preload',
-        // to ignore runtime.js
-        // https://github.com/vuejs/vue-cli/blob/dev/packages/@vue/cli-service/lib/config/app.js#L171
-        fileBlacklist: [/\.map$/, /hot-update\.js$/, /runtime\..*\.js$/],
-        include: 'initial'
-      }
-    ])
-
-    // when there are many pages, it will cause too many meaningless requests
-    config.plugins.delete('prefetch')
-
-    // set svg-sprite-loader
-    config.module
-      .rule('svg')
-      .exclude.add(resolve('src/icons'))
-      .end()
-    config.module
-      .rule('icons')
-      .test(/\.svg$/)
-      .include.add(resolve('src/icons'))
-      .end()
-      .use('svg-sprite-loader')
-      .loader('svg-sprite-loader')
-      .options({
-        symbolId: 'icon-[name]'
-      })
-      .end()
-
-    config
-      .when(process.env.NODE_ENV !== 'development',
-        config => {
-          config
-            .plugin('ScriptExtHtmlWebpackPlugin')
-            .after('html')
-            .use('script-ext-html-webpack-plugin', [{
-            // `runtime` must same as runtimeChunk name. default is `runtime`
-              inline: /runtime\..*\.js$/
-            }])
-            .end()
-          config
-            .optimization.splitChunks({
-              chunks: 'all',
-              cacheGroups: {
-                libs: {
-                  name: 'chunk-libs',
-                  test: /[\\/]node_modules[\\/]/,
-                  priority: 10,
-                  chunks: 'initial' // only package third parties that are initially dependent
-                },
-                elementUI: {
-                  name: 'chunk-elementUI', // split elementUI into a single package
-                  priority: 20, // the weight needs to be larger than libs and app or it will be packaged into libs or app
-                  test: /[\\/]node_modules[\\/]_?element-ui(.*)/ // in order to adapt to cnpm
-                },
-                commons: {
-                  name: 'chunk-commons',
-                  test: resolve('src/components'), // can customize your rules
-                  minChunks: 3, //  minimum common number
-                  priority: 5,
-                  reuseExistingChunk: true
-                }
-              }
-            })
-          // https:// webpack.js.org/configuration/optimization/#optimizationruntimechunk
-          config.optimization.runtimeChunk('single')
-        }
-      )
-  }
-}
+'use strict'
+const path = require('path')
+const defaultSettings = require('./src/settings.js')
+
+function resolve(dir) {
+  return path.join(__dirname, dir)
+}
+
+const name = defaultSettings.title || 'vue Element Admin' // page title
+
+// If your port is set to 80,
+// use administrator privileges to execute the command line.
+// For example, Mac: sudo npm run
+// You can change the port by the following method:
+// port = 9527 npm run dev OR npm run dev --port = 9527
+const port = process.env.port || process.env.npm_config_port || 9527 // dev port
+
+// All configuration item explanations can be find in https://cli.vuejs.org/config/
+module.exports = {
+  /**
+   * You will need to set publicPath if you plan to deploy your site under a sub path,
+   * for example GitHub Pages. If you plan to deploy your site to https://foo.github.io/bar/,
+   * then publicPath should be set to "/bar/".
+   * In most cases please use '/' !!!
+   * Detail: https://cli.vuejs.org/config/#publicpath
+   */
+  publicPath: '/',
+  outputDir: 'dist',
+  assetsDir: 'static',
+  lintOnSave: process.env.NODE_ENV === 'development',
+  productionSourceMap: false,
+  devServer: {
+    port: port,
+    open: true,
+    overlay: {
+      warnings: false,
+      errors: true
+    },
+    before: require('./mock/mock-server.js')
+  },
+  configureWebpack: {
+    // provide the app's title in webpack's name field, so that
+    // it can be accessed in index.html to inject the correct title.
+    name: name,
+    resolve: {
+      alias: {
+        '@': resolve('src')
+      }
+    }
+  },
+  chainWebpack(config) {
+    // it can improve the speed of the first screen, it is recommended to turn on preload
+    // it can improve the speed of the first screen, it is recommended to turn on preload
+    config.plugin('preload').tap(() => [
+      {
+        rel: 'preload',
+        // to ignore runtime.js
+        // https://github.com/vuejs/vue-cli/blob/dev/packages/@vue/cli-service/lib/config/app.js#L171
+        fileBlacklist: [/\.map$/, /hot-update\.js$/, /runtime\..*\.js$/],
+        include: 'initial'
+      }
+    ])
+
+    // when there are many pages, it will cause too many meaningless requests
+    config.plugins.delete('prefetch')
+
+    // set svg-sprite-loader
+    config.module
+      .rule('svg')
+      .exclude.add(resolve('src/icons'))
+      .end()
+    config.module
+      .rule('icons')
+      .test(/\.svg$/)
+      .include.add(resolve('src/icons'))
+      .end()
+      .use('svg-sprite-loader')
+      .loader('svg-sprite-loader')
+      .options({
+        symbolId: 'icon-[name]'
+      })
+      .end()
+
+    config
+      .when(process.env.NODE_ENV !== 'development',
+        config => {
+          config
+            .plugin('ScriptExtHtmlWebpackPlugin')
+            .after('html')
+            .use('script-ext-html-webpack-plugin', [{
+            // `runtime` must same as runtimeChunk name. default is `runtime`
+              inline: /runtime\..*\.js$/
+            }])
+            .end()
+          config
+            .optimization.splitChunks({
+              chunks: 'all',
+              cacheGroups: {
+                libs: {
+                  name: 'chunk-libs',
+                  test: /[\\/]node_modules[\\/]/,
+                  priority: 10,
+                  chunks: 'initial' // only package third parties that are initially dependent
+                },
+                elementUI: {
+                  name: 'chunk-elementUI', // split elementUI into a single package
+                  priority: 20, // the weight needs to be larger than libs and app or it will be packaged into libs or app
+                  test: /[\\/]node_modules[\\/]_?element-ui(.*)/ // in order to adapt to cnpm
+                },
+                commons: {
+                  name: 'chunk-commons',
+                  test: resolve('src/components'), // can customize your rules
+                  minChunks: 3, //  minimum common number
+                  priority: 5,
+                  reuseExistingChunk: true
+                }
+              }
+            })
+          // https:// webpack.js.org/configuration/optimization/#optimizationruntimechunk
+          config.optimization.runtimeChunk('single')
+        }
+      )
+  }
+}