项目初始化
diff --git a/frontend/.editorconfig b/frontend/.editorconfig
new file mode 100644
index 0000000..3454886
--- /dev/null
+++ b/frontend/.editorconfig
@@ -0,0 +1,14 @@
+# https://editorconfig.org
+root = true
+
+[*]
+charset = utf-8
+indent_style = space
+indent_size = 2
+end_of_line = lf
+insert_final_newline = true
+trim_trailing_whitespace = true
+
+[*.md]
+insert_final_newline = false
+trim_trailing_whitespace = false
diff --git a/frontend/.env.development b/frontend/.env.development
new file mode 100644
index 0000000..de583d0
--- /dev/null
+++ b/frontend/.env.development
@@ -0,0 +1,5 @@
+# just a flag
+ENV = 'development'
+
+# base api
+VUE_APP_BASE_API = '/dev-api'
diff --git a/frontend/.env.production b/frontend/.env.production
new file mode 100644
index 0000000..80c8103
--- /dev/null
+++ b/frontend/.env.production
@@ -0,0 +1,6 @@
+# just a flag
+ENV = 'production'
+
+# base api
+VUE_APP_BASE_API = '/prod-api'
+
diff --git a/frontend/.env.staging b/frontend/.env.staging
new file mode 100644
index 0000000..a8793a0
--- /dev/null
+++ b/frontend/.env.staging
@@ -0,0 +1,8 @@
+NODE_ENV = production
+
+# just a flag
+ENV = 'staging'
+
+# base api
+VUE_APP_BASE_API = '/stage-api'
+
diff --git a/frontend/.eslintignore b/frontend/.eslintignore
new file mode 100644
index 0000000..e6529fc
--- /dev/null
+++ b/frontend/.eslintignore
@@ -0,0 +1,4 @@
+build/*.js
+src/assets
+public
+dist
diff --git a/frontend/.eslintrc.js b/frontend/.eslintrc.js
new file mode 100644
index 0000000..c977505
--- /dev/null
+++ b/frontend/.eslintrc.js
@@ -0,0 +1,198 @@
+module.exports = {
+  root: true,
+  parserOptions: {
+    parser: 'babel-eslint',
+    sourceType: 'module'
+  },
+  env: {
+    browser: true,
+    node: true,
+    es6: true,
+  },
+  extends: ['plugin:vue/recommended', 'eslint:recommended'],
+
+  // add your custom rules here
+  //it is base on https://github.com/vuejs/eslint-config-vue
+  rules: {
+    "vue/max-attributes-per-line": [2, {
+      "singleline": 10,
+      "multiline": {
+        "max": 1,
+        "allowFirstLine": false
+      }
+    }],
+    "vue/singleline-html-element-content-newline": "off",
+    "vue/multiline-html-element-content-newline":"off",
+    "vue/name-property-casing": ["error", "PascalCase"],
+    "vue/no-v-html": "off",
+    'accessor-pairs': 2,
+    'arrow-spacing': [2, {
+      'before': true,
+      'after': true
+    }],
+    'block-spacing': [2, 'always'],
+    'brace-style': [2, '1tbs', {
+      'allowSingleLine': true
+    }],
+    'camelcase': [0, {
+      'properties': 'always'
+    }],
+    'comma-dangle': [2, 'never'],
+    'comma-spacing': [2, {
+      'before': false,
+      'after': true
+    }],
+    'comma-style': [2, 'last'],
+    'constructor-super': 2,
+    'curly': [2, 'multi-line'],
+    'dot-location': [2, 'property'],
+    'eol-last': 2,
+    'eqeqeq': ["error", "always", {"null": "ignore"}],
+    'generator-star-spacing': [2, {
+      'before': true,
+      'after': true
+    }],
+    'handle-callback-err': [2, '^(err|error)$'],
+    'indent': [2, 2, {
+      'SwitchCase': 1
+    }],
+    'jsx-quotes': [2, 'prefer-single'],
+    'key-spacing': [2, {
+      'beforeColon': false,
+      'afterColon': true
+    }],
+    'keyword-spacing': [2, {
+      'before': true,
+      'after': true
+    }],
+    'new-cap': [2, {
+      'newIsCap': true,
+      'capIsNew': false
+    }],
+    'new-parens': 2,
+    'no-array-constructor': 2,
+    'no-caller': 2,
+    'no-console': 'off',
+    'no-class-assign': 2,
+    'no-cond-assign': 2,
+    'no-const-assign': 2,
+    'no-control-regex': 0,
+    'no-delete-var': 2,
+    'no-dupe-args': 2,
+    'no-dupe-class-members': 2,
+    'no-dupe-keys': 2,
+    'no-duplicate-case': 2,
+    'no-empty-character-class': 2,
+    'no-empty-pattern': 2,
+    'no-eval': 2,
+    'no-ex-assign': 2,
+    'no-extend-native': 2,
+    'no-extra-bind': 2,
+    'no-extra-boolean-cast': 2,
+    'no-extra-parens': [2, 'functions'],
+    'no-fallthrough': 2,
+    'no-floating-decimal': 2,
+    'no-func-assign': 2,
+    'no-implied-eval': 2,
+    'no-inner-declarations': [2, 'functions'],
+    'no-invalid-regexp': 2,
+    'no-irregular-whitespace': 2,
+    'no-iterator': 2,
+    'no-label-var': 2,
+    'no-labels': [2, {
+      'allowLoop': false,
+      'allowSwitch': false
+    }],
+    'no-lone-blocks': 2,
+    'no-mixed-spaces-and-tabs': 2,
+    'no-multi-spaces': 2,
+    'no-multi-str': 2,
+    'no-multiple-empty-lines': [2, {
+      'max': 1
+    }],
+    'no-native-reassign': 2,
+    'no-negated-in-lhs': 2,
+    'no-new-object': 2,
+    'no-new-require': 2,
+    'no-new-symbol': 2,
+    'no-new-wrappers': 2,
+    'no-obj-calls': 2,
+    'no-octal': 2,
+    'no-octal-escape': 2,
+    'no-path-concat': 2,
+    'no-proto': 2,
+    'no-redeclare': 2,
+    'no-regex-spaces': 2,
+    'no-return-assign': [2, 'except-parens'],
+    'no-self-assign': 2,
+    'no-self-compare': 2,
+    'no-sequences': 2,
+    'no-shadow-restricted-names': 2,
+    'no-spaced-func': 2,
+    'no-sparse-arrays': 2,
+    'no-this-before-super': 2,
+    'no-throw-literal': 2,
+    'no-trailing-spaces': 2,
+    'no-undef': 2,
+    'no-undef-init': 2,
+    'no-unexpected-multiline': 2,
+    'no-unmodified-loop-condition': 2,
+    'no-unneeded-ternary': [2, {
+      'defaultAssignment': false
+    }],
+    'no-unreachable': 2,
+    'no-unsafe-finally': 2,
+    'no-unused-vars': [2, {
+      'vars': 'all',
+      'args': 'none'
+    }],
+    'no-useless-call': 2,
+    'no-useless-computed-key': 2,
+    'no-useless-constructor': 2,
+    'no-useless-escape': 0,
+    'no-whitespace-before-property': 2,
+    'no-with': 2,
+    'one-var': [2, {
+      'initialized': 'never'
+    }],
+    'operator-linebreak': [2, 'after', {
+      'overrides': {
+        '?': 'before',
+        ':': 'before'
+      }
+    }],
+    'padded-blocks': [2, 'never'],
+    'quotes': [2, 'single', {
+      'avoidEscape': true,
+      'allowTemplateLiterals': true
+    }],
+    'semi': [2, 'never'],
+    'semi-spacing': [2, {
+      'before': false,
+      'after': true
+    }],
+    'space-before-blocks': [2, 'always'],
+    'space-before-function-paren': [2, 'never'],
+    'space-in-parens': [2, 'never'],
+    'space-infix-ops': 2,
+    'space-unary-ops': [2, {
+      'words': true,
+      'nonwords': false
+    }],
+    'spaced-comment': [2, 'always', {
+      'markers': ['global', 'globals', 'eslint', 'eslint-disable', '*package', '!', ',']
+    }],
+    'template-curly-spacing': [2, 'never'],
+    'use-isnan': 2,
+    'valid-typeof': 2,
+    'wrap-iife': [2, 'any'],
+    'yield-star-spacing': [2, 'both'],
+    'yoda': [2, 'never'],
+    'prefer-const': 2,
+    'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0,
+    'object-curly-spacing': [2, 'always', {
+      objectsInObjects: false
+    }],
+    'array-bracket-spacing': [2, 'never']
+  }
+}
diff --git a/frontend/.gitignore b/frontend/.gitignore
new file mode 100644
index 0000000..78a752d
--- /dev/null
+++ b/frontend/.gitignore
@@ -0,0 +1,23 @@
+.DS_Store
+node_modules/
+dist/
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+**/*.log
+
+tests/**/coverage/
+tests/e2e/reports
+selenium-debug.log
+
+# Editor directories and files
+.idea
+.vscode
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.local
+
+package-lock.json
+yarn.lock
diff --git a/frontend/.travis.yml b/frontend/.travis.yml
new file mode 100644
index 0000000..f4be7a0
--- /dev/null
+++ b/frontend/.travis.yml
@@ -0,0 +1,5 @@
+language: node_js
+node_js: 10
+script: npm run test
+notifications:
+  email: false
diff --git a/frontend/LICENSE b/frontend/LICENSE
new file mode 100644
index 0000000..6151575
--- /dev/null
+++ b/frontend/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2017-present PanJiaChen
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/frontend/README.md b/frontend/README.md
new file mode 100644
index 0000000..5067b8d
--- /dev/null
+++ b/frontend/README.md
@@ -0,0 +1,222 @@
+<p align="center">
+  <img width="320" src="https://wpimg.wallstcn.com/ecc53a42-d79b-42e2-8852-5126b810a4c8.svg">
+</p>
+
+<p align="center">
+  <a href="https://github.com/vuejs/vue">
+    <img src="https://img.shields.io/badge/vue-2.6.10-brightgreen.svg" alt="vue">
+  </a>
+  <a href="https://github.com/ElemeFE/element">
+    <img src="https://img.shields.io/badge/element--ui-2.7.0-brightgreen.svg" alt="element-ui">
+  </a>
+  <a href="https://travis-ci.org/PanJiaChen/vue-element-admin" rel="nofollow">
+    <img src="https://travis-ci.org/PanJiaChen/vue-element-admin.svg?branch=master" alt="Build Status">
+  </a>
+  <a href="https://github.com/PanJiaChen/vue-element-admin/blob/master/LICENSE">
+    <img src="https://img.shields.io/github/license/mashape/apistatus.svg" alt="license">
+  </a>
+  <a href="https://github.com/PanJiaChen/vue-element-admin/releases">
+    <img src="https://img.shields.io/github/release/PanJiaChen/vue-element-admin.svg" alt="GitHub release">
+  </a>
+  <a href="https://gitter.im/vue-element-admin/discuss">
+    <img src="https://badges.gitter.im/Join%20Chat.svg" alt="gitter">
+  </a>
+  <a href="https://panjiachen.github.io/vue-element-admin-site/donate">
+    <img src="https://img.shields.io/badge/%24-donate-ff69b4.svg" alt="donate">
+  </a>
+</p>
+
+English | [简体中文](./README.zh-CN.md) | [日本語](./README.ja.md) | [Spanish](./README.es.md)
+
+## Introduction
+
+[vue-element-admin](https://panjiachen.github.io/vue-element-admin) is a production-ready front-end solution for admin interfaces. It is based on [vue](https://github.com/vuejs/vue) and uses the UI Toolkit [element-ui](https://github.com/ElemeFE/element).
+
+[vue-element-admin](https://panjiachen.github.io/vue-element-admin) is based on the newest development stack of vue and it has a built-in i18n solution, typical templates for enterprise applications, and lots of awesome features. It helps you build large and complex Single-Page Applications. I believe whatever your needs are, this project will help you.
+
+- [Preview](https://panjiachen.github.io/vue-element-admin)
+
+- [Documentation](https://panjiachen.github.io/vue-element-admin-site/)
+
+- [Gitter](https://gitter.im/vue-element-admin/discuss)
+
+- [Donate](https://panjiachen.github.io/vue-element-admin-site/donate/)
+
+- [Wiki](https://github.com/PanJiaChen/vue-element-admin/wiki)
+
+- [Gitee](https://panjiachen.gitee.io/vue-element-admin/) 国内用户可访问该地址在线预览
+
+- Base template recommends using: [vue-admin-template](https://github.com/PanJiaChen/vue-admin-template)
+- Desktop: [electron-vue-admin](https://github.com/PanJiaChen/electron-vue-admin)
+- Typescript: [vue-typescript-admin-template](https://github.com/Armour/vue-typescript-admin-template) (Credits: [@Armour](https://github.com/Armour))
+- [awesome-project](https://github.com/PanJiaChen/vue-element-admin/issues/2312)
+
+**After the `v4.1.0+` version, the default master branch will not support i18n. Please use [i18n Branch](https://github.com/PanJiaChen/vue-element-admin/tree/i18n), it will keep up with the master update**
+
+**The current version is `v4.0+` build on `vue-cli`. If you find a problem, please put [issue](https://github.com/PanJiaChen/vue-element-admin/issues/new). If you want to use the old version , you can switch branch to [tag/3.11.0](https://github.com/PanJiaChen/vue-element-admin/tree/tag/3.11.0), it does not rely on `vue-cli`**
+
+**This project does not support low version browsers (e.g. IE). Please add polyfill by yourself.**
+
+## Preparation
+
+You need to install [node](https://nodejs.org/) and [git](https://git-scm.com/) locally. The project is based on [ES2015+](https://es6.ruanyifeng.com/), [vue](https://cn.vuejs.org/index.html), [vuex](https://vuex.vuejs.org/zh-cn/), [vue-router](https://router.vuejs.org/zh-cn/), [vue-cli](https://github.com/vuejs/vue-cli) , [axios](https://github.com/axios/axios) and [element-ui](https://github.com/ElemeFE/element), all request data is simulated using [Mock.js](https://github.com/nuysoft/Mock).
+Understanding and learning this knowledge in advance will greatly help the use of this project.
+
+[![Edit on CodeSandbox](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/github/PanJiaChen/vue-element-admin/tree/CodeSandbox)
+
+<p align="center">
+  <img width="900" src="https://wpimg.wallstcn.com/a5894c1b-f6af-456e-82df-1151da0839bf.png">
+</p>
+
+## Sponsors
+
+Become a sponsor and get your logo on our README on GitHub with a link to your site. [[Become a sponsor]](https://www.patreon.com/panjiachen)
+
+<a href="https://flatlogic.com/admin-dashboards?from=vue-element-admin"><img width="150px" src="https://wpimg.wallstcn.com/9c0b719b-5551-4c1e-b776-63994632d94a.png" /></a><p>Admin Dashboard Templates made with Vue, React and Angular.</p>
+
+## Features
+
+```
+- Login / Logout
+
+- Permission Authentication
+  - Page permission
+  - Directive permission
+  - Permission configuration page
+  - Two-step login
+
+- Multi-environment build
+  - Develop (dev)
+  - sit
+  - Stage Test (stage)
+  - Production (prod)
+
+- Global Features
+  - I18n
+  - Multiple dynamic themes
+  - Dynamic sidebar (supports multi-level routing)
+  - Dynamic breadcrumb
+  - Tags-view (Tab page Support right-click operation)
+  - Svg Sprite
+  - Mock data
+  - Screenfull
+  - Responsive Sidebar
+
+- Editor
+  - Rich Text Editor
+  - Markdown Editor
+  - JSON Editor
+
+- Excel
+  - Export Excel
+  - Upload Excel
+  - Visualization Excel
+  - Export zip
+
+- Table
+  - Dynamic Table
+  - Drag And Drop Table
+  - Inline Edit Table
+
+- Error Page
+  - 401
+  - 404
+
+- Components
+  - Avatar Upload
+  - Back To Top
+  - Drag Dialog
+  - Drag Select
+  - Drag Kanban
+  - Drag List
+  - SplitPane
+  - Dropzone
+  - Sticky
+  - CountTo
+
+- Advanced Example
+- Error Log
+- Dashboard
+- Guide Page
+- ECharts
+- Clipboard
+- Markdown to html
+```
+
+## Getting started
+
+```bash
+# clone the project
+git clone https://github.com/PanJiaChen/vue-element-admin.git
+
+# enter the project directory
+cd vue-element-admin
+
+# install dependency
+npm install
+
+# develop
+npm run dev
+```
+
+This will automatically open http://localhost:9527
+
+## Build
+
+```bash
+# build for test environment
+npm run build:stage
+
+# build for production environment
+npm run build:prod
+```
+
+## Advanced
+
+```bash
+# preview the release environment effect
+npm run preview
+
+# preview the release environment effect + static resource analysis
+npm run preview -- --report
+
+# code format check
+npm run lint
+
+# code format check and auto fix
+npm run lint -- --fix
+```
+
+Refer to [Documentation](https://panjiachen.github.io/vue-element-admin-site/guide/essentials/deploy.html) for more information
+
+## Changelog
+
+Detailed changes for each release are documented in the [release notes](https://github.com/PanJiaChen/vue-element-admin/releases).
+
+## Online Demo
+
+[Preview](https://panjiachen.github.io/vue-element-admin)
+
+## Donate
+
+If you find this project useful, you can buy author a glass of juice :tropical_drink:
+
+![donate](https://wpimg.wallstcn.com/bd273f0d-83a0-4ef2-92e1-9ac8ed3746b9.png)
+
+[Paypal Me](https://www.paypal.me/panfree23)
+
+[Buy me a coffee](https://www.buymeacoffee.com/Pan)
+
+## Browsers support
+
+Modern browsers and Internet Explorer 10+.
+
+| [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt="IE / Edge" width="24px" height="24px" />](https://godban.github.io/browsers-support-badges/)</br>IE / Edge | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png" alt="Firefox" width="24px" height="24px" />](https://godban.github.io/browsers-support-badges/)</br>Firefox | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png" alt="Chrome" width="24px" height="24px" />](https://godban.github.io/browsers-support-badges/)</br>Chrome | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png" alt="Safari" width="24px" height="24px" />](https://godban.github.io/browsers-support-badges/)</br>Safari |
+| --------- | --------- | --------- | --------- |
+| IE10, IE11, Edge | last 2 versions | last 2 versions | last 2 versions |
+
+## License
+
+[MIT](https://github.com/PanJiaChen/vue-element-admin/blob/master/LICENSE)
+
+Copyright (c) 2017-present PanJiaChen
diff --git a/frontend/babel.config.js b/frontend/babel.config.js
new file mode 100644
index 0000000..fb82b27
--- /dev/null
+++ b/frontend/babel.config.js
@@ -0,0 +1,14 @@
+module.exports = {
+  presets: [
+    // https://github.com/vuejs/vue-cli/tree/master/packages/@vue/babel-preset-app
+    '@vue/cli-plugin-babel/preset'
+  ],
+  'env': {
+    'development': {
+      // babel-plugin-dynamic-import-node plugin only does one thing by converting all import() to require().
+      // This plugin can significantly increase the speed of hot updates, when you have a large number of pages.
+      // https://panjiachen.github.io/vue-element-admin-site/guide/advanced/lazy-loading.html
+      'plugins': ['dynamic-import-node']
+    }
+  }
+}
diff --git a/frontend/jest.config.js b/frontend/jest.config.js
new file mode 100644
index 0000000..143cdc8
--- /dev/null
+++ b/frontend/jest.config.js
@@ -0,0 +1,24 @@
+module.exports = {
+  moduleFileExtensions: ['js', 'jsx', 'json', 'vue'],
+  transform: {
+    '^.+\\.vue$': 'vue-jest',
+    '.+\\.(css|styl|less|sass|scss|svg|png|jpg|ttf|woff|woff2)$':
+      'jest-transform-stub',
+    '^.+\\.jsx?$': 'babel-jest'
+  },
+  moduleNameMapper: {
+    '^@/(.*)$': '<rootDir>/src/$1'
+  },
+  snapshotSerializers: ['jest-serializer-vue'],
+  testMatch: [
+    '**/tests/unit/**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)'
+  ],
+  collectCoverageFrom: ['src/utils/**/*.{js,vue}', '!src/utils/auth.js', '!src/utils/request.js', 'src/components/**/*.{js,vue}'],
+  coverageDirectory: '<rootDir>/tests/unit/coverage',
+  // 'collectCoverage': true,
+  'coverageReporters': [
+    'lcov',
+    'text-summary'
+  ],
+  testURL: 'http://localhost/'
+}
diff --git a/frontend/jsconfig.json b/frontend/jsconfig.json
new file mode 100644
index 0000000..958df04
--- /dev/null
+++ b/frontend/jsconfig.json
@@ -0,0 +1,9 @@
+{ 
+  "compilerOptions": {
+    "baseUrl": "./",
+    "paths": {
+        "@/*": ["src/*"]
+    }
+  },
+  "exclude": ["node_modules", "dist"]
+}
\ No newline at end of file
diff --git a/frontend/mock/article.js b/frontend/mock/article.js
new file mode 100644
index 0000000..23d8ba5
--- /dev/null
+++ b/frontend/mock/article.js
@@ -0,0 +1,116 @@
+const Mock = require('mockjs')
+
+const List = []
+const count = 100
+
+const baseContent = '<p>I am testing data, I am testing data.</p><p><img src="https://wpimg.wallstcn.com/4c69009c-0fd4-4153-b112-6cb53d1cf943"></p>'
+const image_uri = 'https://wpimg.wallstcn.com/e4558086-631c-425c-9430-56ffb46e70b3'
+
+for (let i = 0; i < count; i++) {
+  List.push(Mock.mock({
+    id: '@increment',
+    timestamp: +Mock.Random.date('T'),
+    author: '@first',
+    reviewer: '@first',
+    title: '@title(5, 10)',
+    content_short: 'mock data',
+    content: baseContent,
+    forecast: '@float(0, 100, 2, 2)',
+    importance: '@integer(1, 3)',
+    'type|1': ['CN', 'US', 'JP', 'EU'],
+    'status|1': ['published', 'draft'],
+    display_time: '@datetime',
+    comment_disabled: true,
+    pageviews: '@integer(300, 5000)',
+    image_uri,
+    platforms: ['a-platform']
+  }))
+}
+
+module.exports = [
+  {
+    url: '/vue-element-admin/article/list',
+    type: 'get',
+    response: config => {
+      const { importance, type, title, page = 1, limit = 20, sort } = config.query
+
+      let mockList = List.filter(item => {
+        if (importance && item.importance !== +importance) return false
+        if (type && item.type !== type) return false
+        if (title && item.title.indexOf(title) < 0) return false
+        return true
+      })
+
+      if (sort === '-id') {
+        mockList = mockList.reverse()
+      }
+
+      const pageList = mockList.filter((item, index) => index < limit * page && index >= limit * (page - 1))
+
+      return {
+        code: 20000,
+        data: {
+          total: mockList.length,
+          items: pageList
+        }
+      }
+    }
+  },
+
+  {
+    url: '/vue-element-admin/article/detail',
+    type: 'get',
+    response: config => {
+      const { id } = config.query
+      for (const article of List) {
+        if (article.id === +id) {
+          return {
+            code: 20000,
+            data: article
+          }
+        }
+      }
+    }
+  },
+
+  {
+    url: '/vue-element-admin/article/pv',
+    type: 'get',
+    response: _ => {
+      return {
+        code: 20000,
+        data: {
+          pvData: [
+            { key: 'PC', pv: 1024 },
+            { key: 'mobile', pv: 1024 },
+            { key: 'ios', pv: 1024 },
+            { key: 'android', pv: 1024 }
+          ]
+        }
+      }
+    }
+  },
+
+  {
+    url: '/vue-element-admin/article/create',
+    type: 'post',
+    response: _ => {
+      return {
+        code: 20000,
+        data: 'success'
+      }
+    }
+  },
+
+  {
+    url: '/vue-element-admin/article/update',
+    type: 'post',
+    response: _ => {
+      return {
+        code: 20000,
+        data: 'success'
+      }
+    }
+  }
+]
+
diff --git a/frontend/mock/index.js b/frontend/mock/index.js
new file mode 100644
index 0000000..2eed65d
--- /dev/null
+++ b/frontend/mock/index.js
@@ -0,0 +1,60 @@
+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
+}
diff --git a/frontend/mock/mock-server.js b/frontend/mock/mock-server.js
new file mode 100644
index 0000000..8941ec0
--- /dev/null
+++ b/frontend/mock/mock-server.js
@@ -0,0 +1,81 @@
+const chokidar = require('chokidar')
+const bodyParser = require('body-parser')
+const chalk = require('chalk')
+const path = require('path')
+const Mock = require('mockjs')
+
+const mockDir = path.join(process.cwd(), 'mock')
+
+function registerRoutes(app) {
+  let mockLastIndex
+  const { mocks } = require('./index.js')
+  const mocksForServer = mocks.map(route => {
+    return responseFake(route.url, route.type, route.response)
+  })
+  for (const mock of mocksForServer) {
+    app[mock.type](mock.url, mock.response)
+    mockLastIndex = app._router.stack.length
+  }
+  const mockRoutesLength = Object.keys(mocksForServer).length
+  return {
+    mockRoutesLength: mockRoutesLength,
+    mockStartIndex: mockLastIndex - mockRoutesLength
+  }
+}
+
+function unregisterRoutes() {
+  Object.keys(require.cache).forEach(i => {
+    if (i.includes(mockDir)) {
+      delete require.cache[require.resolve(i)]
+    }
+  })
+}
+
+// for mock server
+const responseFake = (url, type, respond) => {
+  return {
+    url: new RegExp(`${process.env.VUE_APP_BASE_API}${url}`),
+    type: type || 'get',
+    response(req, res) {
+      console.log('request invoke:' + req.path)
+      res.json(Mock.mock(respond instanceof Function ? respond(req, res) : respond))
+    }
+  }
+}
+
+module.exports = app => {
+  // parse app.body
+  // https://expressjs.com/en/4x/api.html#req.body
+  app.use(bodyParser.json())
+  app.use(bodyParser.urlencoded({
+    extended: true
+  }))
+
+  const mockRoutes = registerRoutes(app)
+  var mockRoutesLength = mockRoutes.mockRoutesLength
+  var mockStartIndex = mockRoutes.mockStartIndex
+
+  // watch files, hot reload mock server
+  chokidar.watch(mockDir, {
+    ignored: /mock-server/,
+    ignoreInitial: true
+  }).on('all', (event, path) => {
+    if (event === 'change' || event === 'add') {
+      try {
+        // remove mock routes stack
+        app._router.stack.splice(mockStartIndex, mockRoutesLength)
+
+        // clear routes cache
+        unregisterRoutes()
+
+        const mockRoutes = registerRoutes(app)
+        mockRoutesLength = mockRoutes.mockRoutesLength
+        mockStartIndex = mockRoutes.mockStartIndex
+
+        console.log(chalk.magentaBright(`\n > Mock Server hot reload success! changed  ${path}`))
+      } catch (error) {
+        console.log(chalk.redBright(error))
+      }
+    }
+  })
+}
diff --git a/frontend/mock/remote-search.js b/frontend/mock/remote-search.js
new file mode 100644
index 0000000..8fc4926
--- /dev/null
+++ b/frontend/mock/remote-search.js
@@ -0,0 +1,51 @@
+const Mock = require('mockjs')
+
+const NameList = []
+const count = 100
+
+for (let i = 0; i < count; i++) {
+  NameList.push(Mock.mock({
+    name: '@first'
+  }))
+}
+NameList.push({ name: 'mock-Pan' })
+
+module.exports = [
+  // username search
+  {
+    url: '/vue-element-admin/search/user',
+    type: 'get',
+    response: config => {
+      const { name } = config.query
+      const mockNameList = NameList.filter(item => {
+        const lowerCaseName = item.name.toLowerCase()
+        return !(name && lowerCaseName.indexOf(name.toLowerCase()) < 0)
+      })
+      return {
+        code: 20000,
+        data: { items: mockNameList }
+      }
+    }
+  },
+
+  // transaction list
+  {
+    url: '/vue-element-admin/transaction/list',
+    type: 'get',
+    response: _ => {
+      return {
+        code: 20000,
+        data: {
+          total: 20,
+          'items|20': [{
+            order_no: '@guid()',
+            timestamp: +Mock.Random.date('T'),
+            username: '@name()',
+            price: '@float(1000, 15000, 0, 2)',
+            'status|1': ['success', 'pending']
+          }]
+        }
+      }
+    }
+  }
+]
diff --git a/frontend/mock/role/index.js b/frontend/mock/role/index.js
new file mode 100644
index 0000000..4643f00
--- /dev/null
+++ b/frontend/mock/role/index.js
@@ -0,0 +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'
+      }
+    }
+  }
+]
diff --git a/frontend/mock/role/routes.js b/frontend/mock/role/routes.js
new file mode 100644
index 0000000..d33f162
--- /dev/null
+++ b/frontend/mock/role/routes.js
@@ -0,0 +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
+}
diff --git a/frontend/mock/user.js b/frontend/mock/user.js
new file mode 100644
index 0000000..d82e079
--- /dev/null
+++ b/frontend/mock/user.js
@@ -0,0 +1,84 @@
+
+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'
+      }
+    }
+  }
+]
diff --git a/frontend/mock/utils.js b/frontend/mock/utils.js
new file mode 100644
index 0000000..f909a29
--- /dev/null
+++ b/frontend/mock/utils.js
@@ -0,0 +1,48 @@
+/**
+ * @param {string} url
+ * @returns {Object}
+ */
+function param2Obj(url) {
+  const search = decodeURIComponent(url.split('?')[1]).replace(/\+/g, ' ')
+  if (!search) {
+    return {}
+  }
+  const obj = {}
+  const searchArr = search.split('&')
+  searchArr.forEach(v => {
+    const index = v.indexOf('=')
+    if (index !== -1) {
+      const name = v.substring(0, index)
+      const val = v.substring(index + 1, v.length)
+      obj[name] = val
+    }
+  })
+  return obj
+}
+
+/**
+ * This is just a simple version of deep copy
+ * Has a lot of edge cases bug
+ * If you want to use a perfect deep copy, use lodash's _.cloneDeep
+ * @param {Object} source
+ * @returns {Object}
+ */
+function deepClone(source) {
+  if (!source && typeof source !== 'object') {
+    throw new Error('error arguments', 'deepClone')
+  }
+  const targetObj = source.constructor === Array ? [] : {}
+  Object.keys(source).forEach(keys => {
+    if (source[keys] && typeof source[keys] === 'object') {
+      targetObj[keys] = deepClone(source[keys])
+    } else {
+      targetObj[keys] = source[keys]
+    }
+  })
+  return targetObj
+}
+
+module.exports = {
+  param2Obj,
+  deepClone
+}
diff --git a/frontend/package.json b/frontend/package.json
new file mode 100644
index 0000000..02f68e2
--- /dev/null
+++ b/frontend/package.json
@@ -0,0 +1,111 @@
+{
+  "name": "vue-element-admin",
+  "version": "4.4.0",
+  "description": "A magical vue admin. An out-of-box UI solution for enterprise applications. Newest development stack of vue. Lots of awesome features",
+  "author": "Pan <panfree23@gmail.com>",
+  "scripts": {
+    "dev": "vue-cli-service serve",
+    "lint": "eslint --ext .js,.vue src",
+    "build:prod": "vue-cli-service build",
+    "build:stage": "vue-cli-service build --mode staging",
+    "preview": "node build/index.js --preview",
+    "new": "plop",
+    "svgo": "svgo -f src/icons/svg --config=src/icons/svgo.yml",
+    "test:unit": "jest --clearCache && vue-cli-service test:unit",
+    "test:ci": "npm run lint && npm run test:unit"
+  },
+  "dependencies": {
+    "axios": "0.18.1",
+    "clipboard": "2.0.4",
+    "codemirror": "5.45.0",
+    "core-js": "3.6.5",
+    "driver.js": "0.9.5",
+    "dropzone": "5.5.1",
+    "echarts": "4.2.1",
+    "element-ui": "2.13.2",
+    "file-saver": "2.0.1",
+    "fuse.js": "3.4.4",
+    "js-cookie": "2.2.0",
+    "jsonlint": "1.6.3",
+    "jszip": "3.2.1",
+    "normalize.css": "7.0.0",
+    "nprogress": "0.2.0",
+    "path-to-regexp": "2.4.0",
+    "screenfull": "4.2.0",
+    "script-loader": "0.7.2",
+    "sortablejs": "1.8.4",
+    "tui-editor": "1.3.3",
+    "vue": "2.6.10",
+    "vue-count-to": "1.0.13",
+    "vue-router": "3.0.2",
+    "vue-splitpane": "1.0.4",
+    "vuedraggable": "2.20.0",
+    "vuex": "3.1.0",
+    "xlsx": "0.14.1"
+  },
+  "devDependencies": {
+    "@vue/cli-plugin-babel": "4.4.4",
+    "@vue/cli-plugin-eslint": "4.4.4",
+    "@vue/cli-plugin-unit-jest": "4.4.4",
+    "@vue/cli-service": "4.4.4",
+    "@vue/test-utils": "1.0.0-beta.29",
+    "autoprefixer": "9.5.1",
+    "babel-eslint": "10.1.0",
+    "babel-jest": "23.6.0",
+    "babel-plugin-dynamic-import-node": "2.3.3",
+    "chalk": "2.4.2",
+    "chokidar": "2.1.5",
+    "connect": "3.6.6",
+    "eslint": "6.7.2",
+    "eslint-plugin-vue": "6.2.2",
+    "html-webpack-plugin": "3.2.0",
+    "husky": "1.3.1",
+    "lint-staged": "8.1.5",
+    "mockjs": "1.0.1-beta3",
+    "plop": "2.3.0",
+    "runjs": "4.3.2",
+    "sass": "1.26.2",
+    "sass-loader": "8.0.2",
+    "script-ext-html-webpack-plugin": "2.1.3",
+    "serve-static": "1.13.2",
+    "svg-sprite-loader": "4.1.3",
+    "svgo": "1.2.0",
+    "vue-template-compiler": "2.6.10"
+  },
+  "browserslist": [
+    "> 1%",
+    "last 2 versions"
+  ],
+  "bugs": {
+    "url": "https://github.com/PanJiaChen/vue-element-admin/issues"
+  },
+  "engines": {
+    "node": ">=8.9",
+    "npm": ">= 3.0.0"
+  },
+  "keywords": [
+    "vue",
+    "admin",
+    "dashboard",
+    "element-ui",
+    "boilerplate",
+    "admin-template",
+    "management-system"
+  ],
+  "license": "MIT",
+  "lint-staged": {
+    "src/**/*.{js,vue}": [
+      "eslint --fix",
+      "git add"
+    ]
+  },
+  "husky": {
+    "hooks": {
+      "pre-commit": "lint-staged"
+    }
+  },
+  "repository": {
+    "type": "git",
+    "url": "git+https://github.com/PanJiaChen/vue-element-admin.git"
+  }
+}
diff --git a/frontend/plop-templates/component/index.hbs b/frontend/plop-templates/component/index.hbs
new file mode 100644
index 0000000..7661055
--- /dev/null
+++ b/frontend/plop-templates/component/index.hbs
@@ -0,0 +1,26 @@
+{{#if template}}
+<template>
+  <div />
+</template>
+{{/if}}
+
+{{#if script}}
+<script>
+export default {
+  name: '{{ properCase name }}',
+  props: {},
+  data() {
+    return {}
+  },
+  created() {},
+  mounted() {},
+  methods: {}
+}
+</script>
+{{/if}}
+
+{{#if style}}
+<style lang="scss" scoped>
+
+</style>
+{{/if}}
diff --git a/frontend/plop-templates/component/prompt.js b/frontend/plop-templates/component/prompt.js
new file mode 100644
index 0000000..3723e8e
--- /dev/null
+++ b/frontend/plop-templates/component/prompt.js
@@ -0,0 +1,55 @@
+const { notEmpty } = require('../utils.js')
+
+module.exports = {
+  description: 'generate vue component',
+  prompts: [{
+    type: 'input',
+    name: 'name',
+    message: 'component name please',
+    validate: notEmpty('name')
+  },
+  {
+    type: 'checkbox',
+    name: 'blocks',
+    message: 'Blocks:',
+    choices: [{
+      name: '<template>',
+      value: 'template',
+      checked: true
+    },
+    {
+      name: '<script>',
+      value: 'script',
+      checked: true
+    },
+    {
+      name: 'style',
+      value: 'style',
+      checked: true
+    }
+    ],
+    validate(value) {
+      if (value.indexOf('script') === -1 && value.indexOf('template') === -1) {
+        return 'Components require at least a <script> or <template> tag.'
+      }
+      return true
+    }
+  }
+  ],
+  actions: data => {
+    const name = '{{properCase name}}'
+    const actions = [{
+      type: 'add',
+      path: `src/components/${name}/index.vue`,
+      templateFile: 'plop-templates/component/index.hbs',
+      data: {
+        name: name,
+        template: data.blocks.includes('template'),
+        script: data.blocks.includes('script'),
+        style: data.blocks.includes('style')
+      }
+    }]
+
+    return actions
+  }
+}
diff --git a/frontend/plop-templates/store/index.hbs b/frontend/plop-templates/store/index.hbs
new file mode 100644
index 0000000..4f8e2dc
--- /dev/null
+++ b/frontend/plop-templates/store/index.hbs
@@ -0,0 +1,16 @@
+{{#if state}}
+const state = {}
+{{/if}}
+
+{{#if mutations}}
+const mutations = {}
+{{/if}}
+
+{{#if actions}}
+const actions = {}
+{{/if}}
+
+export default {
+  namespaced: true,
+  {{options}}
+}
diff --git a/frontend/plop-templates/store/prompt.js b/frontend/plop-templates/store/prompt.js
new file mode 100644
index 0000000..bcbc11d
--- /dev/null
+++ b/frontend/plop-templates/store/prompt.js
@@ -0,0 +1,62 @@
+const { notEmpty } = require('../utils.js')
+
+module.exports = {
+  description: 'generate store',
+  prompts: [{
+    type: 'input',
+    name: 'name',
+    message: 'store name please',
+    validate: notEmpty('name')
+  },
+  {
+    type: 'checkbox',
+    name: 'blocks',
+    message: 'Blocks:',
+    choices: [{
+      name: 'state',
+      value: 'state',
+      checked: true
+    },
+    {
+      name: 'mutations',
+      value: 'mutations',
+      checked: true
+    },
+    {
+      name: 'actions',
+      value: 'actions',
+      checked: true
+    }
+    ],
+    validate(value) {
+      if (!value.includes('state') || !value.includes('mutations')) {
+        return 'store require at least state and mutations'
+      }
+      return true
+    }
+  }
+  ],
+  actions(data) {
+    const name = '{{name}}'
+    const { blocks } = data
+    const options = ['state', 'mutations']
+    const joinFlag = `,
+  `
+    if (blocks.length === 3) {
+      options.push('actions')
+    }
+
+    const actions = [{
+      type: 'add',
+      path: `src/store/modules/${name}.js`,
+      templateFile: 'plop-templates/store/index.hbs',
+      data: {
+        options: options.join(joinFlag),
+        state: blocks.includes('state'),
+        mutations: blocks.includes('mutations'),
+        actions: blocks.includes('actions')
+      }
+    }]
+    return actions
+  }
+}
diff --git a/frontend/plop-templates/utils.js b/frontend/plop-templates/utils.js
new file mode 100644
index 0000000..0498753
--- /dev/null
+++ b/frontend/plop-templates/utils.js
@@ -0,0 +1,2 @@
+exports.notEmpty = name => v =>
+  !v || v.trim() === '' ? `${name} is required` : true
diff --git a/frontend/plop-templates/view/index.hbs b/frontend/plop-templates/view/index.hbs
new file mode 100644
index 0000000..7661055
--- /dev/null
+++ b/frontend/plop-templates/view/index.hbs
@@ -0,0 +1,26 @@
+{{#if template}}
+<template>
+  <div />
+</template>
+{{/if}}
+
+{{#if script}}
+<script>
+export default {
+  name: '{{ properCase name }}',
+  props: {},
+  data() {
+    return {}
+  },
+  created() {},
+  mounted() {},
+  methods: {}
+}
+</script>
+{{/if}}
+
+{{#if style}}
+<style lang="scss" scoped>
+
+</style>
+{{/if}}
diff --git a/frontend/plop-templates/view/prompt.js b/frontend/plop-templates/view/prompt.js
new file mode 100644
index 0000000..1d490ee
--- /dev/null
+++ b/frontend/plop-templates/view/prompt.js
@@ -0,0 +1,55 @@
+const { notEmpty } = require('../utils.js')
+
+module.exports = {
+  description: 'generate a view',
+  prompts: [{
+    type: 'input',
+    name: 'name',
+    message: 'view name please',
+    validate: notEmpty('name')
+  },
+  {
+    type: 'checkbox',
+    name: 'blocks',
+    message: 'Blocks:',
+    choices: [{
+      name: '<template>',
+      value: 'template',
+      checked: true
+    },
+    {
+      name: '<script>',
+      value: 'script',
+      checked: true
+    },
+    {
+      name: 'style',
+      value: 'style',
+      checked: true
+    }
+    ],
+    validate(value) {
+      if (value.indexOf('script') === -1 && value.indexOf('template') === -1) {
+        return 'View require at least a <script> or <template> tag.'
+      }
+      return true
+    }
+  }
+  ],
+  actions: data => {
+    const name = '{{name}}'
+    const actions = [{
+      type: 'add',
+      path: `src/views/${name}/index.vue`,
+      templateFile: 'plop-templates/view/index.hbs',
+      data: {
+        name: name,
+        template: data.blocks.includes('template'),
+        script: data.blocks.includes('script'),
+        style: data.blocks.includes('style')
+      }
+    }]
+
+    return actions
+  }
+}
diff --git a/frontend/plopfile.js b/frontend/plopfile.js
new file mode 100644
index 0000000..57387bf
--- /dev/null
+++ b/frontend/plopfile.js
@@ -0,0 +1,9 @@
+const viewGenerator = require('./plop-templates/view/prompt')
+const componentGenerator = require('./plop-templates/component/prompt')
+const storeGenerator = require('./plop-templates/store/prompt.js')
+
+module.exports = function(plop) {
+  plop.setGenerator('view', viewGenerator)
+  plop.setGenerator('component', componentGenerator)
+  plop.setGenerator('store', storeGenerator)
+}
diff --git a/frontend/postcss.config.js b/frontend/postcss.config.js
new file mode 100644
index 0000000..961986e
--- /dev/null
+++ b/frontend/postcss.config.js
@@ -0,0 +1,5 @@
+module.exports = {
+  plugins: {
+    autoprefixer: {}
+  }
+}
diff --git a/frontend/public/favicon.ico b/frontend/public/favicon.ico
new file mode 100644
index 0000000..34b63ac
--- /dev/null
+++ b/frontend/public/favicon.ico
Binary files differ
diff --git a/frontend/public/index.html b/frontend/public/index.html
new file mode 100644
index 0000000..e918500
--- /dev/null
+++ b/frontend/public/index.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+    <meta name="renderer" content="webkit">
+    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
+    <link rel="icon" href="<%= BASE_URL %>favicon.ico">
+    <title><%= webpackConfig.name %></title>
+  </head>
+  <body>
+    <div id="app"></div>
+    <!-- built files will be auto injected -->
+  </body>
+</html>
diff --git a/frontend/src/App.vue b/frontend/src/App.vue
new file mode 100644
index 0000000..ec9032c
--- /dev/null
+++ b/frontend/src/App.vue
@@ -0,0 +1,11 @@
+<template>
+  <div id="app">
+    <router-view />
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'App'
+}
+</script>
diff --git a/frontend/src/api/article.js b/frontend/src/api/article.js
new file mode 100644
index 0000000..407bda1
--- /dev/null
+++ b/frontend/src/api/article.js
@@ -0,0 +1,41 @@
+import request from '@/utils/request'
+
+export function fetchList(query) {
+  return request({
+    url: '/vue-element-admin/article/list',
+    method: 'get',
+    params: query
+  })
+}
+
+export function fetchArticle(id) {
+  return request({
+    url: '/vue-element-admin/article/detail',
+    method: 'get',
+    params: { id }
+  })
+}
+
+export function fetchPv(pv) {
+  return request({
+    url: '/vue-element-admin/article/pv',
+    method: 'get',
+    params: { pv }
+  })
+}
+
+export function createArticle(data) {
+  return request({
+    url: '/vue-element-admin/article/create',
+    method: 'post',
+    data
+  })
+}
+
+export function updateArticle(data) {
+  return request({
+    url: '/vue-element-admin/article/update',
+    method: 'post',
+    data
+  })
+}
diff --git a/frontend/src/api/qiniu.js b/frontend/src/api/qiniu.js
new file mode 100644
index 0000000..a037584
--- /dev/null
+++ b/frontend/src/api/qiniu.js
@@ -0,0 +1,8 @@
+import request from '@/utils/request'
+
+export function getToken() {
+  return request({
+    url: '/qiniu/upload/token', // 假地址 自行替换
+    method: 'get'
+  })
+}
diff --git a/frontend/src/api/remote-search.js b/frontend/src/api/remote-search.js
new file mode 100644
index 0000000..02e42b4
--- /dev/null
+++ b/frontend/src/api/remote-search.js
@@ -0,0 +1,17 @@
+import request from '@/utils/request'
+
+export function searchUser(name) {
+  return request({
+    url: '/vue-element-admin/search/user',
+    method: 'get',
+    params: { name }
+  })
+}
+
+export function transactionList(query) {
+  return request({
+    url: '/vue-element-admin/transaction/list',
+    method: 'get',
+    params: query
+  })
+}
diff --git a/frontend/src/api/role.js b/frontend/src/api/role.js
new file mode 100644
index 0000000..959bbd2
--- /dev/null
+++ b/frontend/src/api/role.js
@@ -0,0 +1,38 @@
+import request from '@/utils/request'
+
+export function getRoutes() {
+  return request({
+    url: '/vue-element-admin/routes',
+    method: 'get'
+  })
+}
+
+export function getRoles() {
+  return request({
+    url: '/vue-element-admin/roles',
+    method: 'get'
+  })
+}
+
+export function addRole(data) {
+  return request({
+    url: '/vue-element-admin/role',
+    method: 'post',
+    data
+  })
+}
+
+export function updateRole(id, data) {
+  return request({
+    url: `/vue-element-admin/role/${id}`,
+    method: 'put',
+    data
+  })
+}
+
+export function deleteRole(id) {
+  return request({
+    url: `/vue-element-admin/role/${id}`,
+    method: 'delete'
+  })
+}
diff --git a/frontend/src/api/user.js b/frontend/src/api/user.js
new file mode 100644
index 0000000..b8b8741
--- /dev/null
+++ b/frontend/src/api/user.js
@@ -0,0 +1,24 @@
+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'
+  })
+}
diff --git a/frontend/src/assets/401_images/401.gif b/frontend/src/assets/401_images/401.gif
new file mode 100644
index 0000000..cd6e0d9
--- /dev/null
+++ b/frontend/src/assets/401_images/401.gif
Binary files differ
diff --git a/frontend/src/assets/404_images/404.png b/frontend/src/assets/404_images/404.png
new file mode 100644
index 0000000..3d8e230
--- /dev/null
+++ b/frontend/src/assets/404_images/404.png
Binary files differ
diff --git a/frontend/src/assets/404_images/404_cloud.png b/frontend/src/assets/404_images/404_cloud.png
new file mode 100644
index 0000000..c6281d0
--- /dev/null
+++ b/frontend/src/assets/404_images/404_cloud.png
Binary files differ
diff --git a/frontend/src/assets/custom-theme/fonts/element-icons.ttf b/frontend/src/assets/custom-theme/fonts/element-icons.ttf
new file mode 100644
index 0000000..570a3e1
--- /dev/null
+++ b/frontend/src/assets/custom-theme/fonts/element-icons.ttf
Binary files differ
diff --git a/frontend/src/assets/custom-theme/fonts/element-icons.woff b/frontend/src/assets/custom-theme/fonts/element-icons.woff
new file mode 100644
index 0000000..c2bcc00
--- /dev/null
+++ b/frontend/src/assets/custom-theme/fonts/element-icons.woff
Binary files differ
diff --git a/frontend/src/assets/custom-theme/index.css b/frontend/src/assets/custom-theme/index.css
new file mode 100644
index 0000000..e8b4e08
--- /dev/null
+++ b/frontend/src/assets/custom-theme/index.css
@@ -0,0 +1 @@
+@charset "UTF-8";.custom-theme .fade-in-linear-enter-active,.custom-theme .fade-in-linear-leave-active{-webkit-transition:opacity .2s linear;transition:opacity .2s linear}.custom-theme .fade-in-linear-enter,.custom-theme .fade-in-linear-leave,.custom-theme .fade-in-linear-leave-active{opacity:0}.custom-theme .el-fade-in-linear-enter-active,.custom-theme .el-fade-in-linear-leave-active{-webkit-transition:opacity .2s linear;transition:opacity .2s linear}.custom-theme .el-fade-in-linear-enter,.custom-theme .el-fade-in-linear-leave,.custom-theme .el-fade-in-linear-leave-active{opacity:0}.custom-theme .el-fade-in-enter-active,.custom-theme .el-fade-in-leave-active{-webkit-transition:all .3s cubic-bezier(.55,0,.1,1);transition:all .3s cubic-bezier(.55,0,.1,1)}.custom-theme .el-fade-in-enter,.custom-theme .el-fade-in-leave-active{opacity:0}.custom-theme .el-zoom-in-center-enter-active,.custom-theme .el-zoom-in-center-leave-active{-webkit-transition:all .3s cubic-bezier(.55,0,.1,1);transition:all .3s cubic-bezier(.55,0,.1,1)}.custom-theme .el-zoom-in-center-enter,.custom-theme .el-zoom-in-center-leave-active{opacity:0;-webkit-transform:scaleX(0);transform:scaleX(0)}.custom-theme .el-zoom-in-top-enter-active,.custom-theme .el-zoom-in-top-leave-active{opacity:1;-webkit-transform:scaleY(1);transform:scaleY(1);-webkit-transition:opacity .3s cubic-bezier(.23,1,.32,1) .1s,-webkit-transform .3s cubic-bezier(.23,1,.32,1) .1s;transition:opacity .3s cubic-bezier(.23,1,.32,1) .1s,-webkit-transform .3s cubic-bezier(.23,1,.32,1) .1s;transition:transform .3s cubic-bezier(.23,1,.32,1) .1s,opacity .3s cubic-bezier(.23,1,.32,1) .1s;transition:transform .3s cubic-bezier(.23,1,.32,1) .1s,opacity .3s cubic-bezier(.23,1,.32,1) .1s,-webkit-transform .3s cubic-bezier(.23,1,.32,1) .1s;-webkit-transform-origin:center top;transform-origin:center top}.custom-theme .el-zoom-in-top-enter,.custom-theme .el-zoom-in-top-leave-active{opacity:0;-webkit-transform:scaleY(0);transform:scaleY(0)}.custom-theme .el-zoom-in-bottom-enter-active,.custom-theme .el-zoom-in-bottom-leave-active{opacity:1;-webkit-transform:scaleY(1);transform:scaleY(1);-webkit-transition:opacity .3s cubic-bezier(.23,1,.32,1) .1s,-webkit-transform .3s cubic-bezier(.23,1,.32,1) .1s;transition:opacity .3s cubic-bezier(.23,1,.32,1) .1s,-webkit-transform .3s cubic-bezier(.23,1,.32,1) .1s;transition:transform .3s cubic-bezier(.23,1,.32,1) .1s,opacity .3s cubic-bezier(.23,1,.32,1) .1s;transition:transform .3s cubic-bezier(.23,1,.32,1) .1s,opacity .3s cubic-bezier(.23,1,.32,1) .1s,-webkit-transform .3s cubic-bezier(.23,1,.32,1) .1s;-webkit-transform-origin:center bottom;transform-origin:center bottom}.custom-theme .el-zoom-in-bottom-enter,.custom-theme .el-zoom-in-bottom-leave-active{opacity:0;-webkit-transform:scaleY(0);transform:scaleY(0)}.custom-theme .el-zoom-in-left-enter-active,.custom-theme .el-zoom-in-left-leave-active{opacity:1;-webkit-transform:scale(1,1);transform:scale(1,1);-webkit-transition:opacity .3s cubic-bezier(.23,1,.32,1) .1s,-webkit-transform .3s cubic-bezier(.23,1,.32,1) .1s;transition:opacity .3s cubic-bezier(.23,1,.32,1) .1s,-webkit-transform .3s cubic-bezier(.23,1,.32,1) .1s;transition:transform .3s cubic-bezier(.23,1,.32,1) .1s,opacity .3s cubic-bezier(.23,1,.32,1) .1s;transition:transform .3s cubic-bezier(.23,1,.32,1) .1s,opacity .3s cubic-bezier(.23,1,.32,1) .1s,-webkit-transform .3s cubic-bezier(.23,1,.32,1) .1s;-webkit-transform-origin:top left;transform-origin:top left}.custom-theme .el-zoom-in-left-enter,.custom-theme .el-zoom-in-left-leave-active{opacity:0;-webkit-transform:scale(.45,.45);transform:scale(.45,.45)}.custom-theme .collapse-transition{-webkit-transition:.3s height ease-in-out,.3s padding-top ease-in-out,.3s padding-bottom ease-in-out;transition:.3s height ease-in-out,.3s padding-top ease-in-out,.3s padding-bottom ease-in-out}.custom-theme .horizontal-collapse-transition{-webkit-transition:.3s width ease-in-out,.3s padding-left ease-in-out,.3s padding-right ease-in-out;transition:.3s width ease-in-out,.3s padding-left ease-in-out,.3s padding-right ease-in-out}.custom-theme .el-list-enter-active,.custom-theme .el-list-leave-active{-webkit-transition:all 1s;transition:all 1s}.custom-theme .el-list-enter,.custom-theme .el-list-leave-active{opacity:0;-webkit-transform:translateY(-30px);transform:translateY(-30px)}.custom-theme .el-opacity-transition{-webkit-transition:opacity .3s cubic-bezier(.55,0,.1,1);transition:opacity .3s cubic-bezier(.55,0,.1,1)}@font-face{font-family:element-icons;src:url(fonts/element-icons.woff?t=1508751886602) format("woff"),url(fonts/element-icons.ttf?t=1508751886602) format("truetype");font-weight:400;font-style:normal}.custom-theme [class*=" el-icon-"],.custom-theme [class^=el-icon-]{font-family:element-icons!important;speak:none;font-style:normal;font-weight:400;font-variant:normal;text-transform:none;line-height:1;vertical-align:baseline;display:inline-block;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.custom-theme .el-icon-upload:before{content:"\e60d"}.custom-theme .el-icon-error:before{content:"\e62c"}.custom-theme .el-icon-success:before{content:"\e62d"}.custom-theme .el-icon-warning:before{content:"\e62e"}.custom-theme .el-icon-sort-down:before{content:"\e630"}.custom-theme .el-icon-sort-up:before{content:"\e631"}.custom-theme .el-icon-arrow-left:before{content:"\e600"}.custom-theme .el-icon-circle-plus:before{content:"\e601"}.custom-theme .el-icon-circle-plus-outline:before{content:"\e602"}.custom-theme .el-icon-arrow-down:before{content:"\e603"}.custom-theme .el-icon-arrow-right:before{content:"\e604"}.custom-theme .el-icon-arrow-up:before{content:"\e605"}.custom-theme .el-icon-back:before{content:"\e606"}.custom-theme .el-icon-circle-close:before{content:"\e607"}.custom-theme .el-icon-date:before{content:"\e608"}.custom-theme .el-icon-circle-close-outline:before{content:"\e609"}.custom-theme .el-icon-caret-left:before{content:"\e60a"}.custom-theme .el-icon-caret-bottom:before{content:"\e60b"}.custom-theme .el-icon-caret-top:before{content:"\e60c"}.custom-theme .el-icon-caret-right:before{content:"\e60e"}.custom-theme .el-icon-close:before{content:"\e60f"}.custom-theme .el-icon-d-arrow-left:before{content:"\e610"}.custom-theme .el-icon-check:before{content:"\e611"}.custom-theme .el-icon-delete:before{content:"\e612"}.custom-theme .el-icon-d-arrow-right:before{content:"\e613"}.custom-theme .el-icon-document:before{content:"\e614"}.custom-theme .el-icon-d-caret:before{content:"\e615"}.custom-theme .el-icon-edit-outline:before{content:"\e616"}.custom-theme .el-icon-download:before{content:"\e617"}.custom-theme .el-icon-goods:before{content:"\e618"}.custom-theme .el-icon-search:before{content:"\e619"}.custom-theme .el-icon-info:before{content:"\e61a"}.custom-theme .el-icon-message:before{content:"\e61b"}.custom-theme .el-icon-edit:before{content:"\e61c"}.custom-theme .el-icon-location:before{content:"\e61d"}.custom-theme .el-icon-loading:before{content:"\e61e"}.custom-theme .el-icon-location-outline:before{content:"\e61f"}.custom-theme .el-icon-menu:before{content:"\e620"}.custom-theme .el-icon-minus:before{content:"\e621"}.custom-theme .el-icon-bell:before{content:"\e622"}.custom-theme .el-icon-mobile-phone:before{content:"\e624"}.custom-theme .el-icon-news:before{content:"\e625"}.custom-theme .el-icon-more:before{content:"\e646"}.custom-theme .el-icon-more-outline:before{content:"\e626"}.custom-theme .el-icon-phone:before{content:"\e627"}.custom-theme .el-icon-phone-outline:before{content:"\e628"}.custom-theme .el-icon-picture:before{content:"\e629"}.custom-theme .el-icon-picture-outline:before{content:"\e62a"}.custom-theme .el-icon-plus:before{content:"\e62b"}.custom-theme .el-icon-printer:before{content:"\e62f"}.custom-theme .el-icon-rank:before{content:"\e632"}.custom-theme .el-icon-refresh:before{content:"\e633"}.custom-theme .el-icon-question:before{content:"\e634"}.custom-theme .el-icon-remove:before{content:"\e635"}.custom-theme .el-icon-share:before{content:"\e636"}.custom-theme .el-icon-star-on:before{content:"\e637"}.custom-theme .el-icon-setting:before{content:"\e638"}.custom-theme .el-icon-circle-check:before{content:"\e639"}.custom-theme .el-icon-service:before{content:"\e63a"}.custom-theme .el-icon-sold-out:before{content:"\e63b"}.custom-theme .el-icon-remove-outline:before{content:"\e63c"}.custom-theme .el-icon-star-off:before{content:"\e63d"}.custom-theme .el-icon-circle-check-outline:before{content:"\e63e"}.custom-theme .el-icon-tickets:before{content:"\e63f"}.custom-theme .el-icon-sort:before{content:"\e640"}.custom-theme .el-icon-zoom-in:before{content:"\e641"}.custom-theme .el-icon-time:before{content:"\e642"}.custom-theme .el-icon-view:before{content:"\e643"}.custom-theme .el-icon-upload2:before{content:"\e644"}.custom-theme .el-icon-zoom-out:before{content:"\e645"}.custom-theme .el-icon-loading{-webkit-animation:rotating 2s linear infinite;animation:rotating 2s linear infinite}.custom-theme .el-icon--right{margin-left:5px}.custom-theme .el-icon--left{margin-right:5px}@-webkit-keyframes rotating{0%{-webkit-transform:rotateZ(0);transform:rotateZ(0)}100%{-webkit-transform:rotateZ(360deg);transform:rotateZ(360deg)}}@keyframes rotating{0%{-webkit-transform:rotateZ(0);transform:rotateZ(0)}100%{-webkit-transform:rotateZ(360deg);transform:rotateZ(360deg)}}.custom-theme .el-popper .popper__arrow,.custom-theme .el-popper .popper__arrow::after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.custom-theme .el-popper .popper__arrow{border-width:6px;-webkit-filter:drop-shadow(0 2px 12px rgba(0, 0, 0, .03));filter:drop-shadow(0 2px 12px rgba(0, 0, 0, .03))}.custom-theme .el-popper .popper__arrow::after{content:" ";border-width:6px}.custom-theme .el-popper[x-placement^=top]{margin-bottom:12px}.custom-theme .el-popper[x-placement^=top] .popper__arrow{bottom:-6px;left:50%;margin-right:3px;border-top-color:#e6ebf5;border-bottom-width:0}.custom-theme .el-popper[x-placement^=top] .popper__arrow::after{bottom:1px;margin-left:-6px;border-top-color:#fff;border-bottom-width:0}.custom-theme .el-popper[x-placement^=bottom]{margin-top:12px}.custom-theme .el-popper[x-placement^=bottom] .popper__arrow{top:-6px;left:50%;margin-right:3px;border-top-width:0;border-bottom-color:#e6ebf5}.custom-theme .el-popper[x-placement^=bottom] .popper__arrow::after{top:1px;margin-left:-6px;border-top-width:0;border-bottom-color:#fff}.custom-theme .el-popper[x-placement^=right]{margin-left:12px}.custom-theme .el-popper[x-placement^=right] .popper__arrow{top:50%;left:-6px;margin-bottom:3px;border-right-color:#e6ebf5;border-left-width:0}.custom-theme .el-popper[x-placement^=right] .popper__arrow::after{bottom:-6px;left:1px;border-right-color:#fff;border-left-width:0}.custom-theme .el-popper[x-placement^=left]{margin-right:12px}.custom-theme .el-popper[x-placement^=left] .popper__arrow{top:50%;right:-6px;margin-bottom:3px;border-right-width:0;border-left-color:#e6ebf5}.custom-theme .el-popper[x-placement^=left] .popper__arrow::after{right:1px;bottom:-6px;margin-left:-6px;border-right-width:0;border-left-color:#fff}.custom-theme .el-select-dropdown{position:absolute;z-index:1001;border:solid 1px #dfe4ed;border-radius:4px;background-color:#fff;-webkit-box-shadow:0 2px 12px 0 rgba(0,0,0,.1);box-shadow:0 2px 12px 0 rgba(0,0,0,.1);-webkit-box-sizing:border-box;box-sizing:border-box;margin:5px 0}.custom-theme .el-select-dropdown.is-multiple .el-select-dropdown__item.selected{color:#262729;background-color:#fff}.custom-theme .el-select-dropdown.is-multiple .el-select-dropdown__item.selected.hover{background-color:#f5f7fa}.custom-theme .el-select-dropdown.is-multiple .el-select-dropdown__item.selected::after{position:absolute;right:20px;font-family:element-icons;content:"\E611";font-size:12px;font-weight:700;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.custom-theme .el-select-dropdown .el-scrollbar.is-empty .el-select-dropdown__list{padding:0}.custom-theme .el-select-dropdown .popper__arrow{-webkit-transform:translateX(-400%);transform:translateX(-400%)}.custom-theme .el-select-dropdown.is-arrow-fixed .popper__arrow{-webkit-transform:translateX(-200%);transform:translateX(-200%)}.custom-theme .el-select-dropdown__empty{padding:10px 0;margin:0;text-align:center;color:#999;font-size:14px}.custom-theme .el-select-dropdown__wrap{max-height:274px}.custom-theme .el-select-dropdown__list{list-style:none;padding:6px 0;margin:0;-webkit-box-sizing:border-box;box-sizing:border-box}.custom-theme .el-input{position:relative;font-size:14px;display:inline-block;width:100%}.custom-theme .el-input::-webkit-scrollbar{z-index:11;width:6px}.custom-theme .el-input::-webkit-scrollbar:horizontal{height:6px}.custom-theme .el-input::-webkit-scrollbar-thumb{border-radius:5px;width:6px;background:#b4bccc}.custom-theme .el-input::-webkit-scrollbar-corner{background:#fff}.custom-theme .el-input::-webkit-scrollbar-track{background:#fff}.custom-theme .el-input::-webkit-scrollbar-track-piece{background:#fff;width:6px}.custom-theme .el-input__inner{-webkit-appearance:none;background-color:#fff;background-image:none;border-radius:4px;border:1px solid #d8dce5;-webkit-box-sizing:border-box;box-sizing:border-box;color:#5a5e66;display:inline-block;font-size:inherit;height:40px;line-height:1;outline:0;padding:0 15px;-webkit-transition:border-color .2s cubic-bezier(.645,.045,.355,1);transition:border-color .2s cubic-bezier(.645,.045,.355,1);width:100%}.custom-theme .el-input__inner::-webkit-input-placeholder{color:#b4bccc}.custom-theme .el-input__inner:-ms-input-placeholder{color:#b4bccc}.custom-theme .el-input__inner::-ms-input-placeholder{color:#b4bccc}.custom-theme .el-input__inner::placeholder{color:#b4bccc}.custom-theme .el-input__inner:hover{border-color:#b4bccc}.custom-theme .el-input__inner:focus{outline:0;border-color:#262729}.custom-theme .el-input__suffix{position:absolute;height:100%;right:5px;top:0;text-align:center;color:#b4bccc;-webkit-transition:all .3s;transition:all .3s;pointer-events:none}.custom-theme .el-input__suffix-inner{pointer-events:all}.custom-theme .el-input__prefix{position:absolute;height:100%;left:5px;top:0;text-align:center;color:#b4bccc;-webkit-transition:all .3s;transition:all .3s}.custom-theme .el-input__icon{height:100%;width:25px;text-align:center;-webkit-transition:all .3s;transition:all .3s;line-height:40px}.custom-theme .el-input__icon:after{content:'';height:100%;width:0;display:inline-block;vertical-align:middle}.custom-theme .el-input__validateIcon{pointer-events:none}.custom-theme .el-input.is-active .el-input__inner{outline:0;border-color:#262729}.custom-theme .el-input.is-disabled .el-input__inner{background-color:#f5f7fa;border-color:#dfe4ed;color:#b4bccc;cursor:not-allowed}.custom-theme .el-input.is-disabled .el-input__inner::-webkit-input-placeholder{color:#b4bccc}.custom-theme .el-input.is-disabled .el-input__inner:-ms-input-placeholder{color:#b4bccc}.custom-theme .el-input.is-disabled .el-input__inner::-ms-input-placeholder{color:#b4bccc}.custom-theme .el-input.is-disabled .el-input__inner::placeholder{color:#b4bccc}.custom-theme .el-input.is-disabled .el-input__icon{cursor:not-allowed}.custom-theme .el-input--suffix .el-input__inner{padding-right:30px}.custom-theme .el-input--prefix .el-input__inner{padding-left:30px}.custom-theme .el-input--medium{font-size:14px}.custom-theme .el-input--medium .el-input__inner{height:36px}.custom-theme .el-input--medium .el-input__icon{line-height:36px}.custom-theme .el-input--small{font-size:13px}.custom-theme .el-input--small .el-input__inner{height:32px}.custom-theme .el-input--small .el-input__icon{line-height:32px}.custom-theme .el-input--mini{font-size:12px}.custom-theme .el-input--mini .el-input__inner{height:28px}.custom-theme .el-input--mini .el-input__icon{line-height:28px}.custom-theme .el-input-group{line-height:normal;display:inline-table;width:100%;border-collapse:separate}.custom-theme .el-input-group>.el-input__inner{vertical-align:middle;display:table-cell}.custom-theme .el-input-group__append,.custom-theme .el-input-group__prepend{background-color:#f5f7fa;color:#0a76a4;vertical-align:middle;display:table-cell;position:relative;border:1px solid #d8dce5;border-radius:4px;padding:0 20px;width:1px;white-space:nowrap}.custom-theme .el-input-group__append:focus,.custom-theme .el-input-group__prepend:focus{outline:0}.custom-theme .el-input-group__append .el-button,.custom-theme .el-input-group__append .el-select,.custom-theme .el-input-group__prepend .el-button,.custom-theme .el-input-group__prepend .el-select{display:inline-block;margin:-20px}.custom-theme .el-input-group__append button.el-button,.custom-theme .el-input-group__append div.el-select .el-input__inner,.custom-theme .el-input-group__append div.el-select:hover .el-input__inner,.custom-theme .el-input-group__prepend button.el-button,.custom-theme .el-input-group__prepend div.el-select .el-input__inner,.custom-theme .el-input-group__prepend div.el-select:hover .el-input__inner{border-color:transparent;background-color:transparent;color:inherit;border-top:0;border-bottom:0}.custom-theme .el-input-group__append .el-button,.custom-theme .el-input-group__append .el-input,.custom-theme .el-input-group__prepend .el-button,.custom-theme .el-input-group__prepend .el-input{font-size:inherit}.custom-theme .el-input-group__prepend{border-right:0;border-top-right-radius:0;border-bottom-right-radius:0}.custom-theme .el-input-group__append{border-left:0;border-top-left-radius:0;border-bottom-left-radius:0}.custom-theme .el-input-group--prepend .el-input__inner{border-top-left-radius:0;border-bottom-left-radius:0}.custom-theme .el-input-group--append .el-input__inner{border-top-right-radius:0;border-bottom-right-radius:0}.custom-theme .el-textarea{display:inline-block;width:100%;vertical-align:bottom}.custom-theme .el-textarea__inner{display:block;resize:vertical;padding:5px 15px;line-height:1.5;-webkit-box-sizing:border-box;box-sizing:border-box;width:100%;font-size:14px;color:#5a5e66;background-color:#fff;background-image:none;border:1px solid #d8dce5;border-radius:4px;-webkit-transition:border-color .2s cubic-bezier(.645,.045,.355,1);transition:border-color .2s cubic-bezier(.645,.045,.355,1)}.custom-theme .el-textarea__inner::-webkit-input-placeholder{color:#b4bccc}.custom-theme .el-textarea__inner:-ms-input-placeholder{color:#b4bccc}.custom-theme .el-textarea__inner::-ms-input-placeholder{color:#b4bccc}.custom-theme .el-textarea__inner::placeholder{color:#b4bccc}.custom-theme .el-textarea__inner:hover{border-color:#b4bccc}.custom-theme .el-textarea__inner:focus{outline:0;border-color:#262729}.custom-theme .el-textarea.is-disabled .el-textarea__inner{background-color:#f5f7fa;border-color:#dfe4ed;color:#b4bccc;cursor:not-allowed}.custom-theme .el-textarea.is-disabled .el-textarea__inner::-webkit-input-placeholder{color:#b4bccc}.custom-theme .el-textarea.is-disabled .el-textarea__inner:-ms-input-placeholder{color:#b4bccc}.custom-theme .el-textarea.is-disabled .el-textarea__inner::-ms-input-placeholder{color:#b4bccc}.custom-theme .el-textarea.is-disabled .el-textarea__inner::placeholder{color:#b4bccc}.custom-theme .el-tag{background-color:rgba(38,39,41,.1);display:inline-block;padding:0 10px;height:32px;line-height:30px;font-size:12px;color:#262729;border-radius:4px;-webkit-box-sizing:border-box;box-sizing:border-box;border:1px solid rgba(38,39,41,.2);white-space:nowrap}.custom-theme .el-tag .el-icon-close{border-radius:50%;text-align:center;position:relative;cursor:pointer;font-size:12px;height:18px;width:18px;line-height:18px;vertical-align:middle;top:-1px;right:-5px;color:#262729}.custom-theme .el-tag .el-icon-close::before{display:block}.custom-theme .el-tag .el-icon-close:hover{background-color:#262729;color:#fff}.custom-theme .el-tag--info{background-color:rgba(10,118,164,.1);border-color:rgba(10,118,164,.2);color:#0a76a4}.custom-theme .el-tag--info.is-hit{border-color:#0a76a4}.custom-theme .el-tag--info .el-tag__close{color:#0a76a4}.custom-theme .el-tag--info .el-tag__close:hover{background-color:#0a76a4;color:#fff}.custom-theme .el-tag--success{background-color:rgba(64,145,103,.1);border-color:rgba(64,145,103,.2);color:#409167}.custom-theme .el-tag--success.is-hit{border-color:#409167}.custom-theme .el-tag--success .el-tag__close{color:#409167}.custom-theme .el-tag--success .el-tag__close:hover{background-color:#409167;color:#fff}.custom-theme .el-tag--warning{background-color:rgba(157,164,8,.1);border-color:rgba(157,164,8,.2);color:#9da408}.custom-theme .el-tag--warning.is-hit{border-color:#9da408}.custom-theme .el-tag--warning .el-tag__close{color:#9da408}.custom-theme .el-tag--warning .el-tag__close:hover{background-color:#9da408;color:#fff}.custom-theme .el-tag--danger{background-color:rgba(179,69,14,.1);border-color:rgba(179,69,14,.2);color:#b3450e}.custom-theme .el-tag--danger.is-hit{border-color:#b3450e}.custom-theme .el-tag--danger .el-tag__close{color:#b3450e}.custom-theme .el-tag--danger .el-tag__close:hover{background-color:#b3450e;color:#fff}.custom-theme .el-tag--medium{height:28px;line-height:26px}.custom-theme .el-tag--medium .el-icon-close{-webkit-transform:scale(.8);transform:scale(.8)}.custom-theme .el-tag--small{height:24px;padding:0 8px;line-height:22px}.custom-theme .el-tag--small .el-icon-close{-webkit-transform:scale(.8);transform:scale(.8)}.custom-theme .el-tag--mini{height:20px;padding:0 5px;line-height:19px}.custom-theme .el-tag--mini .el-icon-close{margin-left:-3px;-webkit-transform:scale(.7);transform:scale(.7)}.custom-theme .el-select-dropdown__item{font-size:14px;padding:0 20px;position:relative;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;color:#5a5e66;height:34px;line-height:34px;-webkit-box-sizing:border-box;box-sizing:border-box;cursor:pointer}.custom-theme .el-select-dropdown__item.is-disabled{color:#b4bccc;cursor:not-allowed}.custom-theme .el-select-dropdown__item.is-disabled:hover{background-color:#fff}.custom-theme .el-select-dropdown__item.hover,.custom-theme .el-select-dropdown__item:hover{background-color:#f5f7fa}.custom-theme .el-select-dropdown__item.selected{color:#262729;font-weight:700}.custom-theme .el-select-dropdown__item span{line-height:34px!important}.custom-theme .el-select-group{margin:0;padding:0}.custom-theme .el-select-group__wrap{position:relative;list-style:none;margin:0;padding:0}.custom-theme .el-select-group__wrap:not(:last-of-type){padding-bottom:24px}.custom-theme .el-select-group__wrap:not(:last-of-type)::after{content:'';position:absolute;display:block;left:20px;right:20px;bottom:12px;height:1px;background:#dfe4ed}.custom-theme .el-select-group__title{padding-left:20px;font-size:12px;color:#0a76a4;line-height:30px}.custom-theme .el-select-group .el-select-dropdown__item{padding-left:20px}.custom-theme .el-scrollbar{overflow:hidden;position:relative}.custom-theme .el-scrollbar:active>.el-scrollbar__bar,.custom-theme .el-scrollbar:focus>.el-scrollbar__bar,.custom-theme .el-scrollbar:hover>.el-scrollbar__bar{opacity:1;-webkit-transition:opacity 340ms ease-out;transition:opacity 340ms ease-out}.custom-theme .el-scrollbar__wrap{overflow:scroll;height:100%}.custom-theme .el-scrollbar__wrap--hidden-default::-webkit-scrollbar{width:0;height:0}.custom-theme .el-scrollbar__thumb{position:relative;display:block;width:0;height:0;cursor:pointer;border-radius:inherit;background-color:rgba(135,141,153,.3);-webkit-transition:.3s background-color;transition:.3s background-color}.custom-theme .el-scrollbar__thumb:hover{background-color:rgba(135,141,153,.5)}.custom-theme .el-scrollbar__bar{position:absolute;right:2px;bottom:2px;z-index:1;border-radius:4px;opacity:0;-webkit-transition:opacity 120ms ease-out;transition:opacity 120ms ease-out}.custom-theme .el-scrollbar__bar.is-vertical{width:6px;top:2px}.custom-theme .el-scrollbar__bar.is-vertical>div{width:100%}.custom-theme .el-scrollbar__bar.is-horizontal{height:6px;left:2px}.custom-theme .el-scrollbar__bar.is-horizontal>div{height:100%}.custom-theme .el-select{display:inline-block;position:relative}.custom-theme .el-select:hover .el-input__inner{border-color:#b4bccc}.custom-theme .el-select .el-input__inner{cursor:pointer;padding-right:35px}.custom-theme .el-select .el-input__inner:focus{border-color:#262729}.custom-theme .el-select .el-input .el-select__caret{color:#b4bccc;font-size:14px;-webkit-transition:-webkit-transform .3s;transition:-webkit-transform .3s;transition:transform .3s;transition:transform .3s,-webkit-transform .3s;-webkit-transform:rotateZ(180deg);transform:rotateZ(180deg);line-height:16px;cursor:pointer}.custom-theme .el-select .el-input .el-select__caret.is-reverse{-webkit-transform:rotateZ(0);transform:rotateZ(0)}.custom-theme .el-select .el-input .el-select__caret.is-show-close{font-size:14px;text-align:center;-webkit-transform:rotateZ(180deg);transform:rotateZ(180deg);border-radius:100%;color:#b4bccc;-webkit-transition:color .2s cubic-bezier(.645,.045,.355,1);transition:color .2s cubic-bezier(.645,.045,.355,1)}.custom-theme .el-select .el-input .el-select__caret.is-show-close:hover{color:#878d99}.custom-theme .el-select .el-input.is-disabled .el-input__inner{cursor:not-allowed}.custom-theme .el-select .el-input.is-disabled .el-input__inner:hover{border-color:#dfe4ed}.custom-theme .el-select>.el-input{display:block}.custom-theme .el-select__input{border:none;outline:0;padding:0;margin-left:15px;color:#666;font-size:14px;vertical-align:baseline;-webkit-appearance:none;-moz-appearance:none;appearance:none;height:28px;background-color:transparent}.custom-theme .el-select__input.is-mini{height:14px}.custom-theme .el-select__close{cursor:pointer;position:absolute;top:8px;z-index:1000;right:25px;color:#b4bccc;line-height:18px;font-size:14px}.custom-theme .el-select__close:hover{color:#878d99}.custom-theme .el-select__tags{position:absolute;line-height:normal;white-space:normal;z-index:1;top:50%;-webkit-transform:translateY(-50%);transform:translateY(-50%)}.custom-theme .el-select .el-tag__close{margin-top:-2px}.custom-theme .el-select .el-tag{-webkit-box-sizing:border-box;box-sizing:border-box;border-color:transparent;margin:3px 0 3px 6px;background-color:#f0f2f5}.custom-theme .el-select .el-tag__close.el-icon-close{background-color:#b4bccc;right:-7px;color:#fff}.custom-theme .el-select .el-tag__close.el-icon-close:hover{background-color:#878d99}.custom-theme .el-select .el-tag__close.el-icon-close::before{display:block;-webkit-transform:translate(0,.5px);transform:translate(0,.5px)}.custom-theme .el-select__tag{display:inline-block;height:24px;line-height:24px;font-size:14px;border-radius:4px;color:#fff;background-color:#262729}.custom-theme .el-select__tag .el-icon-close{font-size:14px}.custom-theme .el-pagination{white-space:nowrap;padding:2px 5px;color:#2d2f33;font-weight:700}.custom-theme .el-pagination::after,.custom-theme .el-pagination::before{display:table;content:""}.custom-theme .el-pagination::after{clear:both}.custom-theme .el-pagination button,.custom-theme .el-pagination span:not([class*=suffix]){display:inline-block;font-size:13px;min-width:35.5px;height:28px;line-height:28px;vertical-align:top;-webkit-box-sizing:border-box;box-sizing:border-box}.custom-theme .el-pagination .el-input__inner{text-align:center}.custom-theme .el-pagination .el-input__suffix{right:0;-webkit-transform:scale(.8);transform:scale(.8)}.custom-theme .el-pagination .el-select .el-input{width:100px;margin:0 5px}.custom-theme .el-pagination .el-select .el-input .el-input__inner{padding-right:25px;border-radius:3px;height:28px}.custom-theme .el-pagination button{border:none;padding:0 6px;background:0 0}.custom-theme .el-pagination button:focus{outline:0}.custom-theme .el-pagination button:hover{color:#262729}.custom-theme .el-pagination button.disabled{color:#b4bccc;background-color:#fff;cursor:not-allowed}.custom-theme .el-pagination .btn-next,.custom-theme .el-pagination .btn-prev{background:center center no-repeat;background-size:16px;background-color:#fff;cursor:pointer;margin:0;color:#2d2f33}.custom-theme .el-pagination .btn-next .el-icon,.custom-theme .el-pagination .btn-prev .el-icon{display:block;font-size:12px}.custom-theme .el-pagination .btn-prev{padding-right:12px}.custom-theme .el-pagination .btn-next{padding-left:12px}.custom-theme .el-pagination--small .btn-next,.custom-theme .el-pagination--small .btn-prev,.custom-theme .el-pagination--small .el-pager li,.custom-theme .el-pagination--small .el-pager li:last-child{border-color:transparent;font-size:12px;line-height:22px;height:22px;min-width:22px}.custom-theme .el-pagination--small .arrow.disabled{visibility:hidden}.custom-theme .el-pagination__sizes{margin:0 10px 0 0;font-weight:400;color:#5a5e66}.custom-theme .el-pagination__sizes .el-input .el-input__inner{font-size:13px;padding-left:8px}.custom-theme .el-pagination__sizes .el-input .el-input__inner:hover{border-color:#262729}.custom-theme .el-pagination__total{margin-right:10px;font-weight:400;color:#5a5e66}.custom-theme .el-pagination__jump{margin-left:24px;font-weight:400;color:#5a5e66}.custom-theme .el-pagination__jump .el-input__inner{padding:0 3px}.custom-theme .el-pagination__rightwrapper{float:right}.custom-theme .el-pagination__editor{line-height:18px;padding:0 2px;height:28px;text-align:center;margin:0 2px;-webkit-box-sizing:border-box;box-sizing:border-box;border-radius:3px;-moz-appearance:textfield}.custom-theme .el-pagination__editor.el-input{width:50px}.custom-theme .el-pagination__editor.el-input .el-input__inner{height:28px}.custom-theme .el-pagination__editor .el-input__inner::-webkit-inner-spin-button,.custom-theme .el-pagination__editor .el-input__inner::-webkit-outer-spin-button{-webkit-appearance:none;margin:0}.custom-theme .el-pager{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;list-style:none;display:inline-block;vertical-align:top;font-size:0;padding:0;margin:0}.custom-theme .el-pager .el-icon-more::before{vertical-align:-4px}.custom-theme .el-pager li{padding:0 4px;background:#fff;vertical-align:top;display:inline-block;font-size:13px;min-width:35.5px;height:28px;line-height:28px;cursor:pointer;-webkit-box-sizing:border-box;box-sizing:border-box;text-align:center;margin:0}.custom-theme .el-pager li.btn-quicknext,.custom-theme .el-pager li.btn-quickprev{line-height:28px;color:#2d2f33}.custom-theme .el-pager li.btn-quickprev:hover{cursor:pointer}.custom-theme .el-pager li.btn-quicknext:hover{cursor:pointer}.custom-theme .el-pager li.active+li{border-left:0}.custom-theme .el-pager li:hover{color:#262729}.custom-theme .el-pager li.active{color:#262729;cursor:default}.custom-theme .v-modal-enter{-webkit-animation:v-modal-in .2s ease;animation:v-modal-in .2s ease}.custom-theme .v-modal-leave{-webkit-animation:v-modal-out .2s ease forwards;animation:v-modal-out .2s ease forwards}@-webkit-keyframes v-modal-in{0%{opacity:0}}@keyframes v-modal-in{0%{opacity:0}}@-webkit-keyframes v-modal-out{100%{opacity:0}}@keyframes v-modal-out{100%{opacity:0}}.custom-theme .v-modal{position:fixed;left:0;top:0;width:100%;height:100%;opacity:.5;background:#000}.custom-theme .el-dialog{position:relative;margin:0 auto 50px;background:#fff;border-radius:2px;-webkit-box-shadow:0 1px 3px rgba(0,0,0,.3);box-shadow:0 1px 3px rgba(0,0,0,.3);-webkit-box-sizing:border-box;box-sizing:border-box;width:50%}.custom-theme .el-dialog.is-fullscreen{width:100%;margin-top:0;margin-bottom:0;height:100%;overflow:auto}.custom-theme .el-dialog__wrapper{position:fixed;top:0;right:0;bottom:0;left:0;overflow:auto;margin:0}.custom-theme .el-dialog__header{padding:15px;padding-bottom:10px}.custom-theme .el-dialog__headerbtn{position:absolute;top:15px;right:15px;padding:0;background:0 0;border:none;outline:0;cursor:pointer;font-size:16px}.custom-theme .el-dialog__headerbtn .el-dialog__close{color:#0a76a4}.custom-theme .el-dialog__headerbtn:focus .el-dialog__close,.custom-theme .el-dialog__headerbtn:hover .el-dialog__close{color:#262729}.custom-theme .el-dialog__title{line-height:24px;font-size:18px;color:#2d2f33}.custom-theme .el-dialog__body{padding:30px 20px;color:#5a5e66;line-height:24px;font-size:14px}.custom-theme .el-dialog__footer{padding:15px;padding-top:10px;text-align:right;-webkit-box-sizing:border-box;box-sizing:border-box}.custom-theme .el-dialog--center{text-align:center}.custom-theme .el-dialog--center .el-dialog__header{padding-top:30px}.custom-theme .el-dialog--center .el-dialog__body{text-align:initial;padding:25px 27px 30px}.custom-theme .el-dialog--center .el-dialog__footer{text-align:inherit;padding-bottom:30px}.custom-theme .dialog-fade-enter-active{-webkit-animation:dialog-fade-in .3s;animation:dialog-fade-in .3s}.custom-theme .dialog-fade-leave-active{-webkit-animation:dialog-fade-out .3s;animation:dialog-fade-out .3s}@-webkit-keyframes dialog-fade-in{0%{-webkit-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0);opacity:0}100%{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0);opacity:1}}@keyframes dialog-fade-in{0%{-webkit-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0);opacity:0}100%{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0);opacity:1}}@-webkit-keyframes dialog-fade-out{0%{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0);opacity:1}100%{-webkit-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0);opacity:0}}@keyframes dialog-fade-out{0%{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0);opacity:1}100%{-webkit-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0);opacity:0}}.custom-theme .el-input{position:relative;font-size:14px;display:inline-block;width:100%}.custom-theme .el-input::-webkit-scrollbar{z-index:11;width:6px}.custom-theme .el-input::-webkit-scrollbar:horizontal{height:6px}.custom-theme .el-input::-webkit-scrollbar-thumb{border-radius:5px;width:6px;background:#b4bccc}.custom-theme .el-input::-webkit-scrollbar-corner{background:#fff}.custom-theme .el-input::-webkit-scrollbar-track{background:#fff}.custom-theme .el-input::-webkit-scrollbar-track-piece{background:#fff;width:6px}.custom-theme .el-input__inner{-webkit-appearance:none;background-color:#fff;background-image:none;border-radius:4px;border:1px solid #d8dce5;-webkit-box-sizing:border-box;box-sizing:border-box;color:#5a5e66;display:inline-block;font-size:inherit;height:40px;line-height:1;outline:0;padding:0 15px;-webkit-transition:border-color .2s cubic-bezier(.645,.045,.355,1);transition:border-color .2s cubic-bezier(.645,.045,.355,1);width:100%}.custom-theme .el-input__inner::-webkit-input-placeholder{color:#b4bccc}.custom-theme .el-input__inner:-ms-input-placeholder{color:#b4bccc}.custom-theme .el-input__inner::-ms-input-placeholder{color:#b4bccc}.custom-theme .el-input__inner::placeholder{color:#b4bccc}.custom-theme .el-input__inner:hover{border-color:#b4bccc}.custom-theme .el-input__inner:focus{outline:0;border-color:#262729}.custom-theme .el-input__suffix{position:absolute;height:100%;right:5px;top:0;text-align:center;color:#b4bccc;-webkit-transition:all .3s;transition:all .3s;pointer-events:none}.custom-theme .el-input__suffix-inner{pointer-events:all}.custom-theme .el-input__prefix{position:absolute;height:100%;left:5px;top:0;text-align:center;color:#b4bccc;-webkit-transition:all .3s;transition:all .3s}.custom-theme .el-input__icon{height:100%;width:25px;text-align:center;-webkit-transition:all .3s;transition:all .3s;line-height:40px}.custom-theme .el-input__icon:after{content:'';height:100%;width:0;display:inline-block;vertical-align:middle}.custom-theme .el-input__validateIcon{pointer-events:none}.custom-theme .el-input.is-active .el-input__inner{outline:0;border-color:#262729}.custom-theme .el-input.is-disabled .el-input__inner{background-color:#f5f7fa;border-color:#dfe4ed;color:#b4bccc;cursor:not-allowed}.custom-theme .el-input.is-disabled .el-input__inner::-webkit-input-placeholder{color:#b4bccc}.custom-theme .el-input.is-disabled .el-input__inner:-ms-input-placeholder{color:#b4bccc}.custom-theme .el-input.is-disabled .el-input__inner::-ms-input-placeholder{color:#b4bccc}.custom-theme .el-input.is-disabled .el-input__inner::placeholder{color:#b4bccc}.custom-theme .el-input.is-disabled .el-input__icon{cursor:not-allowed}.custom-theme .el-input--suffix .el-input__inner{padding-right:30px}.custom-theme .el-input--prefix .el-input__inner{padding-left:30px}.custom-theme .el-input--medium{font-size:14px}.custom-theme .el-input--medium .el-input__inner{height:36px}.custom-theme .el-input--medium .el-input__icon{line-height:36px}.custom-theme .el-input--small{font-size:13px}.custom-theme .el-input--small .el-input__inner{height:32px}.custom-theme .el-input--small .el-input__icon{line-height:32px}.custom-theme .el-input--mini{font-size:12px}.custom-theme .el-input--mini .el-input__inner{height:28px}.custom-theme .el-input--mini .el-input__icon{line-height:28px}.custom-theme .el-input-group{line-height:normal;display:inline-table;width:100%;border-collapse:separate}.custom-theme .el-input-group>.el-input__inner{vertical-align:middle;display:table-cell}.custom-theme .el-input-group__append,.custom-theme .el-input-group__prepend{background-color:#f5f7fa;color:#0a76a4;vertical-align:middle;display:table-cell;position:relative;border:1px solid #d8dce5;border-radius:4px;padding:0 20px;width:1px;white-space:nowrap}.custom-theme .el-input-group__append:focus,.custom-theme .el-input-group__prepend:focus{outline:0}.custom-theme .el-input-group__append .el-button,.custom-theme .el-input-group__append .el-select,.custom-theme .el-input-group__prepend .el-button,.custom-theme .el-input-group__prepend .el-select{display:inline-block;margin:-20px}.custom-theme .el-input-group__append button.el-button,.custom-theme .el-input-group__append div.el-select .el-input__inner,.custom-theme .el-input-group__append div.el-select:hover .el-input__inner,.custom-theme .el-input-group__prepend button.el-button,.custom-theme .el-input-group__prepend div.el-select .el-input__inner,.custom-theme .el-input-group__prepend div.el-select:hover .el-input__inner{border-color:transparent;background-color:transparent;color:inherit;border-top:0;border-bottom:0}.custom-theme .el-input-group__append .el-button,.custom-theme .el-input-group__append .el-input,.custom-theme .el-input-group__prepend .el-button,.custom-theme .el-input-group__prepend .el-input{font-size:inherit}.custom-theme .el-input-group__prepend{border-right:0;border-top-right-radius:0;border-bottom-right-radius:0}.custom-theme .el-input-group__append{border-left:0;border-top-left-radius:0;border-bottom-left-radius:0}.custom-theme .el-input-group--prepend .el-input__inner{border-top-left-radius:0;border-bottom-left-radius:0}.custom-theme .el-input-group--append .el-input__inner{border-top-right-radius:0;border-bottom-right-radius:0}.custom-theme .el-textarea{display:inline-block;width:100%;vertical-align:bottom}.custom-theme .el-textarea__inner{display:block;resize:vertical;padding:5px 15px;line-height:1.5;-webkit-box-sizing:border-box;box-sizing:border-box;width:100%;font-size:14px;color:#5a5e66;background-color:#fff;background-image:none;border:1px solid #d8dce5;border-radius:4px;-webkit-transition:border-color .2s cubic-bezier(.645,.045,.355,1);transition:border-color .2s cubic-bezier(.645,.045,.355,1)}.custom-theme .el-textarea__inner::-webkit-input-placeholder{color:#b4bccc}.custom-theme .el-textarea__inner:-ms-input-placeholder{color:#b4bccc}.custom-theme .el-textarea__inner::-ms-input-placeholder{color:#b4bccc}.custom-theme .el-textarea__inner::placeholder{color:#b4bccc}.custom-theme .el-textarea__inner:hover{border-color:#b4bccc}.custom-theme .el-textarea__inner:focus{outline:0;border-color:#262729}.custom-theme .el-textarea.is-disabled .el-textarea__inner{background-color:#f5f7fa;border-color:#dfe4ed;color:#b4bccc;cursor:not-allowed}.custom-theme .el-textarea.is-disabled .el-textarea__inner::-webkit-input-placeholder{color:#b4bccc}.custom-theme .el-textarea.is-disabled .el-textarea__inner:-ms-input-placeholder{color:#b4bccc}.custom-theme .el-textarea.is-disabled .el-textarea__inner::-ms-input-placeholder{color:#b4bccc}.custom-theme .el-textarea.is-disabled .el-textarea__inner::placeholder{color:#b4bccc}.custom-theme .el-scrollbar{overflow:hidden;position:relative}.custom-theme .el-scrollbar:active>.el-scrollbar__bar,.custom-theme .el-scrollbar:focus>.el-scrollbar__bar,.custom-theme .el-scrollbar:hover>.el-scrollbar__bar{opacity:1;-webkit-transition:opacity 340ms ease-out;transition:opacity 340ms ease-out}.custom-theme .el-scrollbar__wrap{overflow:scroll;height:100%}.custom-theme .el-scrollbar__wrap--hidden-default::-webkit-scrollbar{width:0;height:0}.custom-theme .el-scrollbar__thumb{position:relative;display:block;width:0;height:0;cursor:pointer;border-radius:inherit;background-color:rgba(135,141,153,.3);-webkit-transition:.3s background-color;transition:.3s background-color}.custom-theme .el-scrollbar__thumb:hover{background-color:rgba(135,141,153,.5)}.custom-theme .el-scrollbar__bar{position:absolute;right:2px;bottom:2px;z-index:1;border-radius:4px;opacity:0;-webkit-transition:opacity 120ms ease-out;transition:opacity 120ms ease-out}.custom-theme .el-scrollbar__bar.is-vertical{width:6px;top:2px}.custom-theme .el-scrollbar__bar.is-vertical>div{width:100%}.custom-theme .el-scrollbar__bar.is-horizontal{height:6px;left:2px}.custom-theme .el-scrollbar__bar.is-horizontal>div{height:100%}.custom-theme .el-popper .popper__arrow,.custom-theme .el-popper .popper__arrow::after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.custom-theme .el-popper .popper__arrow{border-width:6px;-webkit-filter:drop-shadow(0 2px 12px rgba(0, 0, 0, .03));filter:drop-shadow(0 2px 12px rgba(0, 0, 0, .03))}.custom-theme .el-popper .popper__arrow::after{content:" ";border-width:6px}.custom-theme .el-popper[x-placement^=top]{margin-bottom:12px}.custom-theme .el-popper[x-placement^=top] .popper__arrow{bottom:-6px;left:50%;margin-right:3px;border-top-color:#e6ebf5;border-bottom-width:0}.custom-theme .el-popper[x-placement^=top] .popper__arrow::after{bottom:1px;margin-left:-6px;border-top-color:#fff;border-bottom-width:0}.custom-theme .el-popper[x-placement^=bottom]{margin-top:12px}.custom-theme .el-popper[x-placement^=bottom] .popper__arrow{top:-6px;left:50%;margin-right:3px;border-top-width:0;border-bottom-color:#e6ebf5}.custom-theme .el-popper[x-placement^=bottom] .popper__arrow::after{top:1px;margin-left:-6px;border-top-width:0;border-bottom-color:#fff}.custom-theme .el-popper[x-placement^=right]{margin-left:12px}.custom-theme .el-popper[x-placement^=right] .popper__arrow{top:50%;left:-6px;margin-bottom:3px;border-right-color:#e6ebf5;border-left-width:0}.custom-theme .el-popper[x-placement^=right] .popper__arrow::after{bottom:-6px;left:1px;border-right-color:#fff;border-left-width:0}.custom-theme .el-popper[x-placement^=left]{margin-right:12px}.custom-theme .el-popper[x-placement^=left] .popper__arrow{top:50%;right:-6px;margin-bottom:3px;border-right-width:0;border-left-color:#e6ebf5}.custom-theme .el-popper[x-placement^=left] .popper__arrow::after{right:1px;bottom:-6px;margin-left:-6px;border-right-width:0;border-left-color:#fff}.custom-theme .el-autocomplete{position:relative;display:inline-block}.custom-theme .el-autocomplete-suggestion{margin:5px 0;-webkit-box-shadow:0 2px 12px 0 rgba(0,0,0,.1);box-shadow:0 2px 12px 0 rgba(0,0,0,.1);border-radius:4px}.custom-theme .el-autocomplete-suggestion.el-popper .popper__arrow{left:24px!important}.custom-theme .el-autocomplete-suggestion__wrap{max-height:280px;padding:10px 0;-webkit-box-sizing:border-box;box-sizing:border-box;overflow:auto;background-color:#fff;border:1px solid #dfe4ed;border-radius:4px}.custom-theme .el-autocomplete-suggestion__list{margin:0;padding:0}.custom-theme .el-autocomplete-suggestion li{padding:0 20px;margin:0;line-height:34px;cursor:pointer;color:#5a5e66;font-size:14px;list-style:none;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.custom-theme .el-autocomplete-suggestion li:hover{background-color:#f5f7fa}.custom-theme .el-autocomplete-suggestion li.highlighted{background-color:#f5f7fa}.custom-theme .el-autocomplete-suggestion li.divider{margin-top:6px;border-top:1px solid #000}.custom-theme .el-autocomplete-suggestion li.divider:last-child{margin-bottom:-6px}.custom-theme .el-autocomplete-suggestion.is-loading li{text-align:center;height:100px;line-height:100px;font-size:20px;color:#999}.custom-theme .el-autocomplete-suggestion.is-loading li::after{display:inline-block;content:"";height:100%;vertical-align:middle}.custom-theme .el-autocomplete-suggestion.is-loading li:hover{background-color:#fff}.custom-theme .el-autocomplete-suggestion.is-loading .el-icon-loading{vertical-align:middle}.custom-theme .el-button{display:inline-block;line-height:1;white-space:nowrap;cursor:pointer;background:#fff;border:1px solid #d8dce5;border-color:#d8dce5;color:#5a5e66;-webkit-appearance:none;text-align:center;-webkit-box-sizing:border-box;box-sizing:border-box;outline:0;margin:0;-webkit-transition:.1s;transition:.1s;font-weight:500;-moz-user-select:none;-webkit-user-select:none;-ms-user-select:none;padding:12px 20px;font-size:14px;border-radius:4px}.custom-theme .el-button+.el-button{margin-left:10px}.custom-theme .el-button.is-round{padding:12px 20px}.custom-theme .el-button:focus,.custom-theme .el-button:hover{color:#262729;border-color:#bebebf;background-color:#e9e9ea}.custom-theme .el-button:active{color:#222325;border-color:#222325;outline:0}.custom-theme .el-button::-moz-focus-inner{border:0}.custom-theme .el-button [class*=el-icon-]+span{margin-left:5px}.custom-theme .el-button.is-plain:focus,.custom-theme .el-button.is-plain:hover{background:#fff;border-color:#262729;color:#262729}.custom-theme .el-button.is-plain:active{background:#fff;border-color:#222325;color:#222325;outline:0}.custom-theme .el-button.is-active{color:#222325;border-color:#222325}.custom-theme .el-button.is-disabled,.custom-theme .el-button.is-disabled:focus,.custom-theme .el-button.is-disabled:hover{color:#b4bccc;cursor:not-allowed;background-image:none;background-color:#fff;border-color:#e6ebf5}.custom-theme .el-button.is-disabled.el-button--text{background-color:transparent}.custom-theme .el-button.is-disabled.is-plain,.custom-theme .el-button.is-disabled.is-plain:focus,.custom-theme .el-button.is-disabled.is-plain:hover{background-color:#fff;border-color:#e6ebf5;color:#b4bccc}.custom-theme .el-button.is-loading{position:relative;pointer-events:none}.custom-theme .el-button.is-loading:before{pointer-events:none;content:'';position:absolute;left:-1px;top:-1px;right:-1px;bottom:-1px;border-radius:inherit;background-color:rgba(255,255,255,.35)}.custom-theme .el-button.is-round{border-radius:20px;padding:12px 23px}.custom-theme .el-button--primary{color:#fff;background-color:#262729;border-color:#262729}.custom-theme .el-button--primary:focus,.custom-theme .el-button--primary:hover{background:#515254;border-color:#515254;color:#fff}.custom-theme .el-button--primary:active{background:#222325;border-color:#222325;color:#fff;outline:0}.custom-theme .el-button--primary.is-active{background:#222325;border-color:#222325;color:#fff}.custom-theme .el-button--primary.is-disabled,.custom-theme .el-button--primary.is-disabled:active,.custom-theme .el-button--primary.is-disabled:focus,.custom-theme .el-button--primary.is-disabled:hover{color:#fff;background-color:#939394;border-color:#939394}.custom-theme .el-button--primary.is-plain{color:#262729;background:#e9e9ea;border-color:#a8a9a9}.custom-theme .el-button--primary.is-plain:focus,.custom-theme .el-button--primary.is-plain:hover{background:#262729;border-color:#262729;color:#fff}.custom-theme .el-button--primary.is-plain:active{background:#222325;border-color:#222325;color:#fff;outline:0}.custom-theme .el-button--primary.is-plain.is-disabled,.custom-theme .el-button--primary.is-plain.is-disabled:active,.custom-theme .el-button--primary.is-plain.is-disabled:focus,.custom-theme .el-button--primary.is-plain.is-disabled:hover{color:#7d7d7f;background-color:#e9e9ea;border-color:#d4d4d4}.custom-theme .el-button--success{color:#fff;background-color:#409167;border-color:#409167}.custom-theme .el-button--success:focus,.custom-theme .el-button--success:hover{background:#66a785;border-color:#66a785;color:#fff}.custom-theme .el-button--success:active{background:#3a835d;border-color:#3a835d;color:#fff;outline:0}.custom-theme .el-button--success.is-active{background:#3a835d;border-color:#3a835d;color:#fff}.custom-theme .el-button--success.is-disabled,.custom-theme .el-button--success.is-disabled:active,.custom-theme .el-button--success.is-disabled:focus,.custom-theme .el-button--success.is-disabled:hover{color:#fff;background-color:#a0c8b3;border-color:#a0c8b3}.custom-theme .el-button--success.is-plain{color:#409167;background:#ecf4f0;border-color:#b3d3c2}.custom-theme .el-button--success.is-plain:focus,.custom-theme .el-button--success.is-plain:hover{background:#409167;border-color:#409167;color:#fff}.custom-theme .el-button--success.is-plain:active{background:#3a835d;border-color:#3a835d;color:#fff;outline:0}.custom-theme .el-button--success.is-plain.is-disabled,.custom-theme .el-button--success.is-plain.is-disabled:active,.custom-theme .el-button--success.is-plain.is-disabled:focus,.custom-theme .el-button--success.is-plain.is-disabled:hover{color:#8cbda4;background-color:#ecf4f0;border-color:#d9e9e1}.custom-theme .el-button--warning{color:#fff;background-color:#9da408;border-color:#9da408}.custom-theme .el-button--warning:focus,.custom-theme .el-button--warning:hover{background:#b1b639;border-color:#b1b639;color:#fff}.custom-theme .el-button--warning:active{background:#8d9407;border-color:#8d9407;color:#fff;outline:0}.custom-theme .el-button--warning.is-active{background:#8d9407;border-color:#8d9407;color:#fff}.custom-theme .el-button--warning.is-disabled,.custom-theme .el-button--warning.is-disabled:active,.custom-theme .el-button--warning.is-disabled:focus,.custom-theme .el-button--warning.is-disabled:hover{color:#fff;background-color:#ced284;border-color:#ced284}.custom-theme .el-button--warning.is-plain{color:#9da408;background:#f5f6e6;border-color:#d8db9c}.custom-theme .el-button--warning.is-plain:focus,.custom-theme .el-button--warning.is-plain:hover{background:#9da408;border-color:#9da408;color:#fff}.custom-theme .el-button--warning.is-plain:active{background:#8d9407;border-color:#8d9407;color:#fff;outline:0}.custom-theme .el-button--warning.is-plain.is-disabled,.custom-theme .el-button--warning.is-plain.is-disabled:active,.custom-theme .el-button--warning.is-plain.is-disabled:focus,.custom-theme .el-button--warning.is-plain.is-disabled:hover{color:#c4c86b;background-color:#f5f6e6;border-color:#ebedce}.custom-theme .el-button--danger{color:#fff;background-color:#b3450e;border-color:#b3450e}.custom-theme .el-button--danger:focus,.custom-theme .el-button--danger:hover{background:#c26a3e;border-color:#c26a3e;color:#fff}.custom-theme .el-button--danger:active{background:#a13e0d;border-color:#a13e0d;color:#fff;outline:0}.custom-theme .el-button--danger.is-active{background:#a13e0d;border-color:#a13e0d;color:#fff}.custom-theme .el-button--danger.is-disabled,.custom-theme .el-button--danger.is-disabled:active,.custom-theme .el-button--danger.is-disabled:focus,.custom-theme .el-button--danger.is-disabled:hover{color:#fff;background-color:#d9a287;border-color:#d9a287}.custom-theme .el-button--danger.is-plain{color:#b3450e;background:#f7ece7;border-color:#e1b59f}.custom-theme .el-button--danger.is-plain:focus,.custom-theme .el-button--danger.is-plain:hover{background:#b3450e;border-color:#b3450e;color:#fff}.custom-theme .el-button--danger.is-plain:active{background:#a13e0d;border-color:#a13e0d;color:#fff;outline:0}.custom-theme .el-button--danger.is-plain.is-disabled,.custom-theme .el-button--danger.is-plain.is-disabled:active,.custom-theme .el-button--danger.is-plain.is-disabled:focus,.custom-theme .el-button--danger.is-plain.is-disabled:hover{color:#d18f6e;background-color:#f7ece7;border-color:#f0dacf}.custom-theme .el-button--info{color:#fff;background-color:#0a76a4;border-color:#0a76a4}.custom-theme .el-button--info:focus,.custom-theme .el-button--info:hover{background:#3b91b6;border-color:#3b91b6;color:#fff}.custom-theme .el-button--info:active{background:#096a94;border-color:#096a94;color:#fff;outline:0}.custom-theme .el-button--info.is-active{background:#096a94;border-color:#096a94;color:#fff}.custom-theme .el-button--info.is-disabled,.custom-theme .el-button--info.is-disabled:active,.custom-theme .el-button--info.is-disabled:focus,.custom-theme .el-button--info.is-disabled:hover{color:#fff;background-color:#85bbd2;border-color:#85bbd2}.custom-theme .el-button--info.is-plain{color:#0a76a4;background:#e7f1f6;border-color:#9dc8db}.custom-theme .el-button--info.is-plain:focus,.custom-theme .el-button--info.is-plain:hover{background:#0a76a4;border-color:#0a76a4;color:#fff}.custom-theme .el-button--info.is-plain:active{background:#096a94;border-color:#096a94;color:#fff;outline:0}.custom-theme .el-button--info.is-plain.is-disabled,.custom-theme .el-button--info.is-plain.is-disabled:active,.custom-theme .el-button--info.is-plain.is-disabled:focus,.custom-theme .el-button--info.is-plain.is-disabled:hover{color:#6cadc8;background-color:#e7f1f6;border-color:#cee4ed}.custom-theme .el-button--medium{padding:10px 20px;font-size:14px;border-radius:4px}.custom-theme .el-button--medium.is-round{padding:10px 20px}.custom-theme .el-button--small{padding:9px 15px;font-size:12px;border-radius:3px}.custom-theme .el-button--small.is-round{padding:9px 15px}.custom-theme .el-button--mini{padding:7px 15px;font-size:12px;border-radius:3px}.custom-theme .el-button--mini.is-round{padding:7px 15px}.custom-theme .el-button--text{border:none;color:#262729;background:0 0;padding-left:0;padding-right:0}.custom-theme .el-button--text:focus,.custom-theme .el-button--text:hover{color:#515254;border-color:transparent;background-color:transparent}.custom-theme .el-button--text:active{color:#222325;border-color:transparent;background-color:transparent}.custom-theme .el-button-group{display:inline-block;vertical-align:middle}.custom-theme .el-button-group::after,.custom-theme .el-button-group::before{display:table;content:""}.custom-theme .el-button-group::after{clear:both}.custom-theme .el-button-group .el-button{float:left;position:relative}.custom-theme .el-button-group .el-button+.el-button{margin-left:0}.custom-theme .el-button-group .el-button:first-child{border-top-right-radius:0;border-bottom-right-radius:0}.custom-theme .el-button-group .el-button:last-child{border-top-left-radius:0;border-bottom-left-radius:0}.custom-theme .el-button-group .el-button:not(:first-child):not(:last-child){border-radius:0}.custom-theme .el-button-group .el-button:not(:last-child){margin-right:-1px}.custom-theme .el-button-group .el-button:active,.custom-theme .el-button-group .el-button:focus,.custom-theme .el-button-group .el-button:hover{z-index:1}.custom-theme .el-button-group .el-button.is-active{z-index:1}.custom-theme .el-button-group .el-button--primary:first-child{border-right-color:rgba(255,255,255,.5)}.custom-theme .el-button-group .el-button--primary:last-child{border-left-color:rgba(255,255,255,.5)}.custom-theme .el-button-group .el-button--primary:not(:first-child):not(:last-child){border-left-color:rgba(255,255,255,.5);border-right-color:rgba(255,255,255,.5)}.custom-theme .el-button-group .el-button--success:first-child{border-right-color:rgba(255,255,255,.5)}.custom-theme .el-button-group .el-button--success:last-child{border-left-color:rgba(255,255,255,.5)}.custom-theme .el-button-group .el-button--success:not(:first-child):not(:last-child){border-left-color:rgba(255,255,255,.5);border-right-color:rgba(255,255,255,.5)}.custom-theme .el-button-group .el-button--warning:first-child{border-right-color:rgba(255,255,255,.5)}.custom-theme .el-button-group .el-button--warning:last-child{border-left-color:rgba(255,255,255,.5)}.custom-theme .el-button-group .el-button--warning:not(:first-child):not(:last-child){border-left-color:rgba(255,255,255,.5);border-right-color:rgba(255,255,255,.5)}.custom-theme .el-button-group .el-button--danger:first-child{border-right-color:rgba(255,255,255,.5)}.custom-theme .el-button-group .el-button--danger:last-child{border-left-color:rgba(255,255,255,.5)}.custom-theme .el-button-group .el-button--danger:not(:first-child):not(:last-child){border-left-color:rgba(255,255,255,.5);border-right-color:rgba(255,255,255,.5)}.custom-theme .el-button-group .el-button--info:first-child{border-right-color:rgba(255,255,255,.5)}.custom-theme .el-button-group .el-button--info:last-child{border-left-color:rgba(255,255,255,.5)}.custom-theme .el-button-group .el-button--info:not(:first-child):not(:last-child){border-left-color:rgba(255,255,255,.5);border-right-color:rgba(255,255,255,.5)}.custom-theme .el-popper .popper__arrow,.custom-theme .el-popper .popper__arrow::after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.custom-theme .el-popper .popper__arrow{border-width:6px;-webkit-filter:drop-shadow(0 2px 12px rgba(0, 0, 0, .03));filter:drop-shadow(0 2px 12px rgba(0, 0, 0, .03))}.custom-theme .el-popper .popper__arrow::after{content:" ";border-width:6px}.custom-theme .el-popper[x-placement^=top]{margin-bottom:12px}.custom-theme .el-popper[x-placement^=top] .popper__arrow{bottom:-6px;left:50%;margin-right:3px;border-top-color:#e6ebf5;border-bottom-width:0}.custom-theme .el-popper[x-placement^=top] .popper__arrow::after{bottom:1px;margin-left:-6px;border-top-color:#fff;border-bottom-width:0}.custom-theme .el-popper[x-placement^=bottom]{margin-top:12px}.custom-theme .el-popper[x-placement^=bottom] .popper__arrow{top:-6px;left:50%;margin-right:3px;border-top-width:0;border-bottom-color:#e6ebf5}.custom-theme .el-popper[x-placement^=bottom] .popper__arrow::after{top:1px;margin-left:-6px;border-top-width:0;border-bottom-color:#fff}.custom-theme .el-popper[x-placement^=right]{margin-left:12px}.custom-theme .el-popper[x-placement^=right] .popper__arrow{top:50%;left:-6px;margin-bottom:3px;border-right-color:#e6ebf5;border-left-width:0}.custom-theme .el-popper[x-placement^=right] .popper__arrow::after{bottom:-6px;left:1px;border-right-color:#fff;border-left-width:0}.custom-theme .el-popper[x-placement^=left]{margin-right:12px}.custom-theme .el-popper[x-placement^=left] .popper__arrow{top:50%;right:-6px;margin-bottom:3px;border-right-width:0;border-left-color:#e6ebf5}.custom-theme .el-popper[x-placement^=left] .popper__arrow::after{right:1px;bottom:-6px;margin-left:-6px;border-right-width:0;border-left-color:#fff}.custom-theme .el-dropdown{display:inline-block;position:relative;color:#5a5e66;font-size:14px}.custom-theme .el-dropdown .el-button-group{display:block}.custom-theme .el-dropdown .el-button-group .el-button{float:none}.custom-theme .el-dropdown .el-dropdown__caret-button{padding-left:5px;padding-right:5px;position:relative;border-left:none}.custom-theme .el-dropdown .el-dropdown__caret-button::before{content:'';position:absolute;display:block;width:1px;top:5px;bottom:5px;left:0;background:rgba(255,255,255,.5)}.custom-theme .el-dropdown .el-dropdown__caret-button:hover::before{top:0;bottom:0}.custom-theme .el-dropdown .el-dropdown__caret-button .el-dropdown__icon{padding-left:0}.custom-theme .el-dropdown__icon{font-size:12px;margin:0 3px}.custom-theme .el-dropdown-menu{position:absolute;top:0;left:0;z-index:10;padding:10px 0;margin:5px 0;background-color:#fff;border:1px solid #e6ebf5;border-radius:4px;-webkit-box-shadow:0 2px 12px 0 rgba(0,0,0,.1);box-shadow:0 2px 12px 0 rgba(0,0,0,.1)}.custom-theme .el-dropdown-menu__item{list-style:none;line-height:36px;padding:0 20px;margin:0;font-size:14px;color:#5a5e66;cursor:pointer}.custom-theme .el-dropdown-menu__item:not(.is-disabled):hover{background-color:#e9e9ea;color:#515254}.custom-theme .el-dropdown-menu__item--divided{position:relative;margin-top:6px;border-top:1px solid #e6ebf5}.custom-theme .el-dropdown-menu__item--divided:before{content:'';height:6px;display:block;margin:0 -20px;background-color:#fff}.custom-theme .el-dropdown-menu__item.is-disabled{cursor:default;color:#bbb;pointer-events:none}.custom-theme .el-dropdown-menu--medium{padding:6px 0}.custom-theme .el-dropdown-menu--medium .el-dropdown-menu__item{line-height:30px;padding:0 17px;font-size:14px}.custom-theme .el-dropdown-menu--medium .el-dropdown-menu__item.el-dropdown-menu__item--divided{margin-top:6px}.custom-theme .el-dropdown-menu--medium .el-dropdown-menu__item.el-dropdown-menu__item--divided:before{height:6px;margin:0 -17px}.custom-theme .el-dropdown-menu--small{padding:6px 0}.custom-theme .el-dropdown-menu--small .el-dropdown-menu__item{line-height:27px;padding:0 15px;font-size:13px}.custom-theme .el-dropdown-menu--small .el-dropdown-menu__item.el-dropdown-menu__item--divided{margin-top:4px}.custom-theme .el-dropdown-menu--small .el-dropdown-menu__item.el-dropdown-menu__item--divided:before{height:4px;margin:0 -15px}.custom-theme .el-dropdown-menu--mini{padding:3px 0}.custom-theme .el-dropdown-menu--mini .el-dropdown-menu__item{line-height:24px;padding:0 10px;font-size:12px}.custom-theme .el-dropdown-menu--mini .el-dropdown-menu__item.el-dropdown-menu__item--divided{margin-top:3px}.custom-theme .el-dropdown-menu--mini .el-dropdown-menu__item.el-dropdown-menu__item--divided:before{height:3px;margin:0 -10px}.custom-theme .el-menu{border-right:solid 1px #e6e6e6;list-style:none;position:relative;margin:0;padding-left:0;background-color:#fff}.custom-theme .el-menu::after,.custom-theme .el-menu::before{display:table;content:""}.custom-theme .el-menu::after{clear:both}.custom-theme .el-menu li{list-style:none}.custom-theme .el-menu--horizontal{border-right:none;border-bottom:solid 1px #e6e6e6}.custom-theme .el-menu--horizontal .el-menu-item{float:left;height:60px;line-height:60px;margin:0;cursor:pointer;position:relative;-webkit-box-sizing:border-box;box-sizing:border-box;border-bottom:2px solid transparent;color:#878d99}.custom-theme .el-menu--horizontal .el-menu-item a,.custom-theme .el-menu--horizontal .el-menu-item a:hover{color:inherit}.custom-theme .el-menu--horizontal .el-menu-item:focus,.custom-theme .el-menu--horizontal .el-menu-item:hover{background-color:#fff}.custom-theme .el-menu--horizontal .el-submenu{float:left;position:relative}.custom-theme .el-menu--horizontal .el-submenu:focus{outline:0}.custom-theme .el-menu--horizontal .el-submenu:focus>.el-submenu__title{color:#2d2f33}.custom-theme .el-menu--horizontal .el-submenu>.el-menu{position:absolute;top:65px;left:0;border:none;padding:5px 0;background-color:#fff;z-index:100;min-width:100%;-webkit-box-shadow:0 2px 12px 0 rgba(0,0,0,.1);box-shadow:0 2px 12px 0 rgba(0,0,0,.1);border-radius:2px}.custom-theme .el-menu--horizontal .el-submenu .el-submenu__title{height:60px;line-height:60px;border-bottom:2px solid transparent;color:#878d99}.custom-theme .el-menu--horizontal .el-submenu .el-submenu__title:hover{background-color:#fff}.custom-theme .el-menu--horizontal .el-submenu .el-menu-item{background-color:#fff;float:none;height:36px;line-height:36px;padding:0 10px}.custom-theme .el-menu--horizontal .el-submenu .el-submenu__icon-arrow{position:static;vertical-align:middle;margin-left:8px;margin-top:-3px}.custom-theme .el-menu--horizontal .el-menu-item:focus,.custom-theme .el-menu--horizontal .el-menu-item:hover,.custom-theme .el-menu--horizontal .el-submenu__title:hover{outline:0;color:#2d2f33}.custom-theme .el-menu--horizontal>.el-menu-item.is-active,.custom-theme .el-menu--horizontal>.el-submenu.is-active .el-submenu__title{border-bottom:2px solid #262729;color:#2d2f33}.custom-theme .el-menu--collapse{width:64px}.custom-theme .el-menu--collapse>.el-menu-item [class^=el-icon-],.custom-theme .el-menu--collapse>.el-submenu>.el-submenu__title [class^=el-icon-]{margin:0;vertical-align:middle;width:24px;text-align:center}.custom-theme .el-menu--collapse>.el-menu-item .el-submenu__icon-arrow,.custom-theme .el-menu--collapse>.el-submenu>.el-submenu__title .el-submenu__icon-arrow{display:none}.custom-theme .el-menu--collapse>.el-menu-item span,.custom-theme .el-menu--collapse>.el-submenu>.el-submenu__title span{height:0;width:0;overflow:hidden;visibility:hidden;display:inline-block}.custom-theme .el-menu--collapse>.el-menu-item.is-active i{color:inherit}.custom-theme .el-menu--collapse .el-menu .el-submenu{min-width:200px}.custom-theme .el-menu--collapse .el-submenu{position:relative}.custom-theme .el-menu--collapse .el-submenu .el-menu{position:absolute;margin-left:5px;top:0;left:100%;z-index:10;border:1px solid #dfe4ed;border-radius:2px;-webkit-box-shadow:0 2px 12px 0 rgba(0,0,0,.1);box-shadow:0 2px 12px 0 rgba(0,0,0,.1)}.custom-theme .el-menu--collapse .el-submenu.is-opened>.el-submenu__title .el-submenu__icon-arrow{-webkit-transform:none;transform:none}.custom-theme .el-menu-item{height:56px;line-height:56px;font-size:14px;color:#2d2f33;padding:0 20px;cursor:pointer;position:relative;-webkit-transition:border-color .3s,background-color .3s,color .3s;transition:border-color .3s,background-color .3s,color .3s;-webkit-box-sizing:border-box;box-sizing:border-box;white-space:nowrap}.custom-theme .el-menu-item [class^=el-icon-]{margin-right:5px;width:24px;text-align:center;font-size:18px}.custom-theme .el-menu-item *{vertical-align:middle}.custom-theme .el-menu-item:first-child{margin-left:0}.custom-theme .el-menu-item:last-child{margin-right:0}.custom-theme .el-menu-item:focus,.custom-theme .el-menu-item:hover{outline:0;background-color:#e9e9ea}.custom-theme .el-menu-item i{color:#878d99}.custom-theme .el-menu-item.is-active{color:#262729}.custom-theme .el-menu-item.is-active i{color:inherit}.custom-theme .el-submenu__title{position:relative;height:56px;line-height:56px;font-size:14px;color:#2d2f33;padding:0 20px;cursor:pointer;position:relative;-webkit-transition:border-color .3s,background-color .3s,color .3s;transition:border-color .3s,background-color .3s,color .3s;-webkit-box-sizing:border-box;box-sizing:border-box;white-space:nowrap}.custom-theme .el-submenu__title *{vertical-align:middle}.custom-theme .el-submenu__title i{color:#878d99}.custom-theme .el-submenu__title:hover{background-color:#e9e9ea}.custom-theme .el-submenu .el-menu{border:none}.custom-theme .el-submenu .el-menu-item{height:50px;line-height:50px;padding:0 45px;min-width:200px}.custom-theme .el-submenu__icon-arrow{position:absolute;top:50%;right:20px;margin-top:-7px;-webkit-transition:-webkit-transform .3s;transition:-webkit-transform .3s;transition:transform .3s;transition:transform .3s,-webkit-transform .3s;font-size:12px}.custom-theme .el-submenu.is-active .el-submenu__title{border-bottom-color:#262729}.custom-theme .el-submenu.is-opened>.el-submenu__title .el-submenu__icon-arrow{-webkit-transform:rotateZ(180deg);transform:rotateZ(180deg)}.custom-theme .el-submenu [class^=el-icon-]{vertical-align:middle;margin-right:5px;width:24px;text-align:center;font-size:18px}.custom-theme .el-menu-item-group>ul{padding:0}.custom-theme .el-menu-item-group__title{padding:7px 0 7px 20px;line-height:normal;font-size:12px;color:#878d99}.custom-theme .horizontal-collapse-transition .el-submenu__title .el-submenu__icon-arrow{-webkit-transition:.2s;transition:.2s;opacity:0}.custom-theme .el-input{position:relative;font-size:14px;display:inline-block;width:100%}.custom-theme .el-input::-webkit-scrollbar{z-index:11;width:6px}.custom-theme .el-input::-webkit-scrollbar:horizontal{height:6px}.custom-theme .el-input::-webkit-scrollbar-thumb{border-radius:5px;width:6px;background:#b4bccc}.custom-theme .el-input::-webkit-scrollbar-corner{background:#fff}.custom-theme .el-input::-webkit-scrollbar-track{background:#fff}.custom-theme .el-input::-webkit-scrollbar-track-piece{background:#fff;width:6px}.custom-theme .el-input__inner{-webkit-appearance:none;background-color:#fff;background-image:none;border-radius:4px;border:1px solid #d8dce5;-webkit-box-sizing:border-box;box-sizing:border-box;color:#5a5e66;display:inline-block;font-size:inherit;height:40px;line-height:1;outline:0;padding:0 15px;-webkit-transition:border-color .2s cubic-bezier(.645,.045,.355,1);transition:border-color .2s cubic-bezier(.645,.045,.355,1);width:100%}.custom-theme .el-input__inner::-webkit-input-placeholder{color:#b4bccc}.custom-theme .el-input__inner:-ms-input-placeholder{color:#b4bccc}.custom-theme .el-input__inner::-ms-input-placeholder{color:#b4bccc}.custom-theme .el-input__inner::placeholder{color:#b4bccc}.custom-theme .el-input__inner:hover{border-color:#b4bccc}.custom-theme .el-input__inner:focus{outline:0;border-color:#262729}.custom-theme .el-input__suffix{position:absolute;height:100%;right:5px;top:0;text-align:center;color:#b4bccc;-webkit-transition:all .3s;transition:all .3s;pointer-events:none}.custom-theme .el-input__suffix-inner{pointer-events:all}.custom-theme .el-input__prefix{position:absolute;height:100%;left:5px;top:0;text-align:center;color:#b4bccc;-webkit-transition:all .3s;transition:all .3s}.custom-theme .el-input__icon{height:100%;width:25px;text-align:center;-webkit-transition:all .3s;transition:all .3s;line-height:40px}.custom-theme .el-input__icon:after{content:'';height:100%;width:0;display:inline-block;vertical-align:middle}.custom-theme .el-input__validateIcon{pointer-events:none}.custom-theme .el-input.is-active .el-input__inner{outline:0;border-color:#262729}.custom-theme .el-input.is-disabled .el-input__inner{background-color:#f5f7fa;border-color:#dfe4ed;color:#b4bccc;cursor:not-allowed}.custom-theme .el-input.is-disabled .el-input__inner::-webkit-input-placeholder{color:#b4bccc}.custom-theme .el-input.is-disabled .el-input__inner:-ms-input-placeholder{color:#b4bccc}.custom-theme .el-input.is-disabled .el-input__inner::-ms-input-placeholder{color:#b4bccc}.custom-theme .el-input.is-disabled .el-input__inner::placeholder{color:#b4bccc}.custom-theme .el-input.is-disabled .el-input__icon{cursor:not-allowed}.custom-theme .el-input--suffix .el-input__inner{padding-right:30px}.custom-theme .el-input--prefix .el-input__inner{padding-left:30px}.custom-theme .el-input--medium{font-size:14px}.custom-theme .el-input--medium .el-input__inner{height:36px}.custom-theme .el-input--medium .el-input__icon{line-height:36px}.custom-theme .el-input--small{font-size:13px}.custom-theme .el-input--small .el-input__inner{height:32px}.custom-theme .el-input--small .el-input__icon{line-height:32px}.custom-theme .el-input--mini{font-size:12px}.custom-theme .el-input--mini .el-input__inner{height:28px}.custom-theme .el-input--mini .el-input__icon{line-height:28px}.custom-theme .el-input-group{line-height:normal;display:inline-table;width:100%;border-collapse:separate}.custom-theme .el-input-group>.el-input__inner{vertical-align:middle;display:table-cell}.custom-theme .el-input-group__append,.custom-theme .el-input-group__prepend{background-color:#f5f7fa;color:#0a76a4;vertical-align:middle;display:table-cell;position:relative;border:1px solid #d8dce5;border-radius:4px;padding:0 20px;width:1px;white-space:nowrap}.custom-theme .el-input-group__append:focus,.custom-theme .el-input-group__prepend:focus{outline:0}.custom-theme .el-input-group__append .el-button,.custom-theme .el-input-group__append .el-select,.custom-theme .el-input-group__prepend .el-button,.custom-theme .el-input-group__prepend .el-select{display:inline-block;margin:-20px}.custom-theme .el-input-group__append button.el-button,.custom-theme .el-input-group__append div.el-select .el-input__inner,.custom-theme .el-input-group__append div.el-select:hover .el-input__inner,.custom-theme .el-input-group__prepend button.el-button,.custom-theme .el-input-group__prepend div.el-select .el-input__inner,.custom-theme .el-input-group__prepend div.el-select:hover .el-input__inner{border-color:transparent;background-color:transparent;color:inherit;border-top:0;border-bottom:0}.custom-theme .el-input-group__append .el-button,.custom-theme .el-input-group__append .el-input,.custom-theme .el-input-group__prepend .el-button,.custom-theme .el-input-group__prepend .el-input{font-size:inherit}.custom-theme .el-input-group__prepend{border-right:0;border-top-right-radius:0;border-bottom-right-radius:0}.custom-theme .el-input-group__append{border-left:0;border-top-left-radius:0;border-bottom-left-radius:0}.custom-theme .el-input-group--prepend .el-input__inner{border-top-left-radius:0;border-bottom-left-radius:0}.custom-theme .el-input-group--append .el-input__inner{border-top-right-radius:0;border-bottom-right-radius:0}.custom-theme .el-textarea{display:inline-block;width:100%;vertical-align:bottom}.custom-theme .el-textarea__inner{display:block;resize:vertical;padding:5px 15px;line-height:1.5;-webkit-box-sizing:border-box;box-sizing:border-box;width:100%;font-size:14px;color:#5a5e66;background-color:#fff;background-image:none;border:1px solid #d8dce5;border-radius:4px;-webkit-transition:border-color .2s cubic-bezier(.645,.045,.355,1);transition:border-color .2s cubic-bezier(.645,.045,.355,1)}.custom-theme .el-textarea__inner::-webkit-input-placeholder{color:#b4bccc}.custom-theme .el-textarea__inner:-ms-input-placeholder{color:#b4bccc}.custom-theme .el-textarea__inner::-ms-input-placeholder{color:#b4bccc}.custom-theme .el-textarea__inner::placeholder{color:#b4bccc}.custom-theme .el-textarea__inner:hover{border-color:#b4bccc}.custom-theme .el-textarea__inner:focus{outline:0;border-color:#262729}.custom-theme .el-textarea.is-disabled .el-textarea__inner{background-color:#f5f7fa;border-color:#dfe4ed;color:#b4bccc;cursor:not-allowed}.custom-theme .el-textarea.is-disabled .el-textarea__inner::-webkit-input-placeholder{color:#b4bccc}.custom-theme .el-textarea.is-disabled .el-textarea__inner:-ms-input-placeholder{color:#b4bccc}.custom-theme .el-textarea.is-disabled .el-textarea__inner::-ms-input-placeholder{color:#b4bccc}.custom-theme .el-textarea.is-disabled .el-textarea__inner::placeholder{color:#b4bccc}.custom-theme .el-input{position:relative;font-size:14px;display:inline-block;width:100%}.custom-theme .el-input::-webkit-scrollbar{z-index:11;width:6px}.custom-theme .el-input::-webkit-scrollbar:horizontal{height:6px}.custom-theme .el-input::-webkit-scrollbar-thumb{border-radius:5px;width:6px;background:#b4bccc}.custom-theme .el-input::-webkit-scrollbar-corner{background:#fff}.custom-theme .el-input::-webkit-scrollbar-track{background:#fff}.custom-theme .el-input::-webkit-scrollbar-track-piece{background:#fff;width:6px}.custom-theme .el-input__inner{-webkit-appearance:none;background-color:#fff;background-image:none;border-radius:4px;border:1px solid #d8dce5;-webkit-box-sizing:border-box;box-sizing:border-box;color:#5a5e66;display:inline-block;font-size:inherit;height:40px;line-height:1;outline:0;padding:0 15px;-webkit-transition:border-color .2s cubic-bezier(.645,.045,.355,1);transition:border-color .2s cubic-bezier(.645,.045,.355,1);width:100%}.custom-theme .el-input__inner::-webkit-input-placeholder{color:#b4bccc}.custom-theme .el-input__inner:-ms-input-placeholder{color:#b4bccc}.custom-theme .el-input__inner::-ms-input-placeholder{color:#b4bccc}.custom-theme .el-input__inner::placeholder{color:#b4bccc}.custom-theme .el-input__inner:hover{border-color:#b4bccc}.custom-theme .el-input__inner:focus{outline:0;border-color:#262729}.custom-theme .el-input__suffix{position:absolute;height:100%;right:5px;top:0;text-align:center;color:#b4bccc;-webkit-transition:all .3s;transition:all .3s;pointer-events:none}.custom-theme .el-input__suffix-inner{pointer-events:all}.custom-theme .el-input__prefix{position:absolute;height:100%;left:5px;top:0;text-align:center;color:#b4bccc;-webkit-transition:all .3s;transition:all .3s}.custom-theme .el-input__icon{height:100%;width:25px;text-align:center;-webkit-transition:all .3s;transition:all .3s;line-height:40px}.custom-theme .el-input__icon:after{content:'';height:100%;width:0;display:inline-block;vertical-align:middle}.custom-theme .el-input__validateIcon{pointer-events:none}.custom-theme .el-input.is-active .el-input__inner{outline:0;border-color:#262729}.custom-theme .el-input.is-disabled .el-input__inner{background-color:#f5f7fa;border-color:#dfe4ed;color:#b4bccc;cursor:not-allowed}.custom-theme .el-input.is-disabled .el-input__inner::-webkit-input-placeholder{color:#b4bccc}.custom-theme .el-input.is-disabled .el-input__inner:-ms-input-placeholder{color:#b4bccc}.custom-theme .el-input.is-disabled .el-input__inner::-ms-input-placeholder{color:#b4bccc}.custom-theme .el-input.is-disabled .el-input__inner::placeholder{color:#b4bccc}.custom-theme .el-input.is-disabled .el-input__icon{cursor:not-allowed}.custom-theme .el-input--suffix .el-input__inner{padding-right:30px}.custom-theme .el-input--prefix .el-input__inner{padding-left:30px}.custom-theme .el-input--medium{font-size:14px}.custom-theme .el-input--medium .el-input__inner{height:36px}.custom-theme .el-input--medium .el-input__icon{line-height:36px}.custom-theme .el-input--small{font-size:13px}.custom-theme .el-input--small .el-input__inner{height:32px}.custom-theme .el-input--small .el-input__icon{line-height:32px}.custom-theme .el-input--mini{font-size:12px}.custom-theme .el-input--mini .el-input__inner{height:28px}.custom-theme .el-input--mini .el-input__icon{line-height:28px}.custom-theme .el-input-group{line-height:normal;display:inline-table;width:100%;border-collapse:separate}.custom-theme .el-input-group>.el-input__inner{vertical-align:middle;display:table-cell}.custom-theme .el-input-group__append,.custom-theme .el-input-group__prepend{background-color:#f5f7fa;color:#0a76a4;vertical-align:middle;display:table-cell;position:relative;border:1px solid #d8dce5;border-radius:4px;padding:0 20px;width:1px;white-space:nowrap}.custom-theme .el-input-group__append:focus,.custom-theme .el-input-group__prepend:focus{outline:0}.custom-theme .el-input-group__append .el-button,.custom-theme .el-input-group__append .el-select,.custom-theme .el-input-group__prepend .el-button,.custom-theme .el-input-group__prepend .el-select{display:inline-block;margin:-20px}.custom-theme .el-input-group__append button.el-button,.custom-theme .el-input-group__append div.el-select .el-input__inner,.custom-theme .el-input-group__append div.el-select:hover .el-input__inner,.custom-theme .el-input-group__prepend button.el-button,.custom-theme .el-input-group__prepend div.el-select .el-input__inner,.custom-theme .el-input-group__prepend div.el-select:hover .el-input__inner{border-color:transparent;background-color:transparent;color:inherit;border-top:0;border-bottom:0}.custom-theme .el-input-group__append .el-button,.custom-theme .el-input-group__append .el-input,.custom-theme .el-input-group__prepend .el-button,.custom-theme .el-input-group__prepend .el-input{font-size:inherit}.custom-theme .el-input-group__prepend{border-right:0;border-top-right-radius:0;border-bottom-right-radius:0}.custom-theme .el-input-group__append{border-left:0;border-top-left-radius:0;border-bottom-left-radius:0}.custom-theme .el-input-group--prepend .el-input__inner{border-top-left-radius:0;border-bottom-left-radius:0}.custom-theme .el-input-group--append .el-input__inner{border-top-right-radius:0;border-bottom-right-radius:0}.custom-theme .el-textarea{display:inline-block;width:100%;vertical-align:bottom}.custom-theme .el-textarea__inner{display:block;resize:vertical;padding:5px 15px;line-height:1.5;-webkit-box-sizing:border-box;box-sizing:border-box;width:100%;font-size:14px;color:#5a5e66;background-color:#fff;background-image:none;border:1px solid #d8dce5;border-radius:4px;-webkit-transition:border-color .2s cubic-bezier(.645,.045,.355,1);transition:border-color .2s cubic-bezier(.645,.045,.355,1)}.custom-theme .el-textarea__inner::-webkit-input-placeholder{color:#b4bccc}.custom-theme .el-textarea__inner:-ms-input-placeholder{color:#b4bccc}.custom-theme .el-textarea__inner::-ms-input-placeholder{color:#b4bccc}.custom-theme .el-textarea__inner::placeholder{color:#b4bccc}.custom-theme .el-textarea__inner:hover{border-color:#b4bccc}.custom-theme .el-textarea__inner:focus{outline:0;border-color:#262729}.custom-theme .el-textarea.is-disabled .el-textarea__inner{background-color:#f5f7fa;border-color:#dfe4ed;color:#b4bccc;cursor:not-allowed}.custom-theme .el-textarea.is-disabled .el-textarea__inner::-webkit-input-placeholder{color:#b4bccc}.custom-theme .el-textarea.is-disabled .el-textarea__inner:-ms-input-placeholder{color:#b4bccc}.custom-theme .el-textarea.is-disabled .el-textarea__inner::-ms-input-placeholder{color:#b4bccc}.custom-theme .el-textarea.is-disabled .el-textarea__inner::placeholder{color:#b4bccc}.custom-theme .el-input-number{position:relative;display:inline-block;width:180px;line-height:38px}.custom-theme .el-input-number .el-input{display:block}.custom-theme .el-input-number .el-input__inner{-webkit-appearance:none;padding-left:50px;padding-right:50px;text-align:center}.custom-theme .el-input-number__decrease,.custom-theme .el-input-number__increase{position:absolute;z-index:1;top:1px;width:40px;height:auto;text-align:center;background:#f5f7fa;color:#5a5e66;cursor:pointer;font-size:13px}.custom-theme .el-input-number__decrease:hover,.custom-theme .el-input-number__increase:hover{color:#262729}.custom-theme .el-input-number__decrease:hover:not(.is-disabled)~.el-input .el-input__inner:not(.is-disabled),.custom-theme .el-input-number__increase:hover:not(.is-disabled)~.el-input .el-input__inner:not(.is-disabled){border-color:#262729}.custom-theme .el-input-number__decrease.is-disabled,.custom-theme .el-input-number__increase.is-disabled{color:#b4bccc;cursor:not-allowed}.custom-theme .el-input-number__increase{right:1px;border-radius:0 4px 4px 0;border-left:1px solid #d8dce5}.custom-theme .el-input-number__decrease{left:1px;border-radius:4px 0 0 4px;border-right:1px solid #d8dce5}.custom-theme .el-input-number.is-disabled .el-input-number__decrease,.custom-theme .el-input-number.is-disabled .el-input-number__increase{border-color:#dfe4ed;color:#dfe4ed}.custom-theme .el-input-number.is-disabled .el-input-number__decrease:hover,.custom-theme .el-input-number.is-disabled .el-input-number__increase:hover{color:#dfe4ed;cursor:not-allowed}.custom-theme .el-input-number--medium{width:200px;line-height:34px}.custom-theme .el-input-number--medium .el-input-number__decrease,.custom-theme .el-input-number--medium .el-input-number__increase{width:36px;font-size:14px}.custom-theme .el-input-number--medium .el-input__inner{padding-left:43px;padding-right:43px}.custom-theme .el-input-number--small{width:130px;line-height:30px}.custom-theme .el-input-number--small .el-input-number__decrease,.custom-theme .el-input-number--small .el-input-number__increase{width:32px;font-size:13px}.custom-theme .el-input-number--small .el-input-number__decrease [class*=el-icon],.custom-theme .el-input-number--small .el-input-number__increase [class*=el-icon]{-webkit-transform:scale(.9);transform:scale(.9)}.custom-theme .el-input-number--small .el-input__inner{padding-left:39px;padding-right:39px}.custom-theme .el-input-number--mini{width:130px;line-height:26px}.custom-theme .el-input-number--mini .el-input-number__decrease,.custom-theme .el-input-number--mini .el-input-number__increase{width:28px;font-size:12px}.custom-theme .el-input-number--mini .el-input-number__decrease [class*=el-icon],.custom-theme .el-input-number--mini .el-input-number__increase [class*=el-icon]{-webkit-transform:scale(.8);transform:scale(.8)}.custom-theme .el-input-number--mini .el-input__inner{padding-left:35px;padding-right:35px}.custom-theme .el-input-number.is-without-controls .el-input__inner{padding-left:15px;padding-right:15px}.custom-theme .el-input-number.is-controls-right .el-input__inner{padding-left:15px;padding-right:50px}.custom-theme .el-input-number.is-controls-right .el-input-number__decrease,.custom-theme .el-input-number.is-controls-right .el-input-number__increase{height:auto;line-height:19px}.custom-theme .el-input-number.is-controls-right .el-input-number__decrease [class*=el-icon],.custom-theme .el-input-number.is-controls-right .el-input-number__increase [class*=el-icon]{-webkit-transform:scale(.8);transform:scale(.8)}.custom-theme .el-input-number.is-controls-right .el-input-number__increase{border-radius:0 4px 0 0;border-bottom:1px solid #d8dce5}.custom-theme .el-input-number.is-controls-right .el-input-number__decrease{right:1px;bottom:1px;top:auto;left:auto;border-right:none;border-left:1px solid #d8dce5;border-radius:0 0 4px 0}.custom-theme .el-input-number.is-controls-right[class*=medium] [class*=decrease],.custom-theme .el-input-number.is-controls-right[class*=medium] [class*=increase]{line-height:17px}.custom-theme .el-input-number.is-controls-right[class*=small] [class*=decrease],.custom-theme .el-input-number.is-controls-right[class*=small] [class*=increase]{line-height:15px}.custom-theme .el-input-number.is-controls-right[class*=mini] [class*=decrease],.custom-theme .el-input-number.is-controls-right[class*=mini] [class*=increase]{line-height:13px}.custom-theme .el-radio{color:#5a5e66;font-weight:500;line-height:1;position:relative;cursor:pointer;display:inline-block;white-space:nowrap;outline:0;font-size:14px;-moz-user-select:none;-webkit-user-select:none;-ms-user-select:none}.custom-theme .el-radio.is-bordered{padding:10px 20px 10px 10px;border-radius:4px;border:1px solid #d8dce5}.custom-theme .el-radio.is-bordered.is-checked{border-color:#262729}.custom-theme .el-radio.is-bordered.is-disabled{cursor:not-allowed;border-color:#e6ebf5}.custom-theme .el-radio.is-bordered+.el-radio.is-bordered{margin-left:10px}.custom-theme .el-radio--medium.is-bordered{padding:8px 20px 8px 10px;border-radius:4px}.custom-theme .el-radio--medium.is-bordered .el-radio__label{font-size:14px}.custom-theme .el-radio--medium.is-bordered .el-radio__inner{height:14px;width:14px}.custom-theme .el-radio--small.is-bordered{padding:6px 15px 6px 10px;border-radius:3px}.custom-theme .el-radio--small.is-bordered .el-radio__label{font-size:12px}.custom-theme .el-radio--small.is-bordered .el-radio__inner{height:12px;width:12px}.custom-theme .el-radio--mini.is-bordered{padding:4px 15px 4px 10px;border-radius:3px}.custom-theme .el-radio--mini.is-bordered .el-radio__label{font-size:12px}.custom-theme .el-radio--mini.is-bordered .el-radio__inner{height:12px;width:12px}.custom-theme .el-radio+.el-radio{margin-left:30px}.custom-theme .el-radio__input{white-space:nowrap;cursor:pointer;outline:0;display:inline-block;line-height:1;position:relative;vertical-align:middle}.custom-theme .el-radio__input.is-disabled .el-radio__inner{background-color:#f5f7fa;border-color:#dfe4ed;cursor:not-allowed}.custom-theme .el-radio__input.is-disabled .el-radio__inner::after{cursor:not-allowed;background-color:#f5f7fa}.custom-theme .el-radio__input.is-disabled .el-radio__inner+.el-radio__label{cursor:not-allowed}.custom-theme .el-radio__input.is-disabled.is-checked .el-radio__inner{background-color:#f5f7fa;border-color:#dfe4ed}.custom-theme .el-radio__input.is-disabled.is-checked .el-radio__inner::after{background-color:#b4bccc}.custom-theme .el-radio__input.is-disabled+span.el-radio__label{color:#b4bccc;cursor:not-allowed}.custom-theme .el-radio__input.is-checked .el-radio__inner{border-color:#262729;background:#262729}.custom-theme .el-radio__input.is-checked .el-radio__inner::after{-webkit-transform:translate(-50%,-50%) scale(1);transform:translate(-50%,-50%) scale(1)}.custom-theme .el-radio__input.is-checked+.el-radio__label{color:#262729}.custom-theme .el-radio__input.is-focus .el-radio__inner{border-color:#262729}.custom-theme .el-radio__inner{border:1px solid #d8dce5;border-radius:100%;width:14px;height:14px;background-color:#fff;position:relative;cursor:pointer;display:inline-block;-webkit-box-sizing:border-box;box-sizing:border-box}.custom-theme .el-radio__inner:hover{border-color:#262729}.custom-theme .el-radio__inner::after{width:4px;height:4px;border-radius:100%;background-color:#fff;content:"";position:absolute;left:50%;top:50%;-webkit-transform:translate(-50%,-50%) scale(0);transform:translate(-50%,-50%) scale(0);-webkit-transition:-webkit-transform .15s cubic-bezier(.71,-.46,.88,.6);transition:-webkit-transform .15s cubic-bezier(.71,-.46,.88,.6);transition:transform .15s cubic-bezier(.71,-.46,.88,.6);transition:transform .15s cubic-bezier(.71,-.46,.88,.6),-webkit-transform .15s cubic-bezier(.71,-.46,.88,.6)}.custom-theme .el-radio__original{opacity:0;outline:0;position:absolute;z-index:-1;top:0;left:0;right:0;bottom:0;margin:0}.custom-theme .el-radio:focus:not(.is-focus):not(:active) .el-radio__inner{-webkit-box-shadow:0 0 2px 2px #262729;box-shadow:0 0 2px 2px #262729}.custom-theme .el-radio__label{font-size:14px;padding-left:10px}.custom-theme .el-radio-group{display:inline-block;line-height:1;vertical-align:middle;font-size:0}.custom-theme .el-radio-button{position:relative;display:inline-block;outline:0}.custom-theme .el-radio-button__inner{display:inline-block;line-height:1;white-space:nowrap;vertical-align:middle;background:#fff;border:1px solid #d8dce5;font-weight:500;border-left:0;color:#5a5e66;-webkit-appearance:none;text-align:center;-webkit-box-sizing:border-box;box-sizing:border-box;outline:0;margin:0;position:relative;cursor:pointer;-webkit-transition:all .3s cubic-bezier(.645,.045,.355,1);transition:all .3s cubic-bezier(.645,.045,.355,1);padding:12px 20px;font-size:14px;border-radius:0}.custom-theme .el-radio-button__inner.is-round{padding:12px 20px}.custom-theme .el-radio-button__inner:hover{color:#262729}.custom-theme .el-radio-button__inner [class*=el-icon-]{line-height:.9}.custom-theme .el-radio-button__inner [class*=el-icon-]+span{margin-left:5px}.custom-theme .el-radio-button__orig-radio{opacity:0;outline:0;position:absolute;z-index:-1;left:-999px}.custom-theme .el-radio-button__orig-radio:checked+.el-radio-button__inner{color:#fff;background-color:#262729;border-color:#262729;-webkit-box-shadow:-1px 0 0 0 #262729;box-shadow:-1px 0 0 0 #262729}.custom-theme .el-radio-button__orig-radio:disabled+.el-radio-button__inner{color:#b4bccc;cursor:not-allowed;background-image:none;background-color:#fff;border-color:#e6ebf5;-webkit-box-shadow:none;box-shadow:none}.custom-theme .el-radio-button__orig-radio:disabled:checked+.el-radio-button__inner{background-color:#edf2fc}.custom-theme .el-radio-button:first-child .el-radio-button__inner{border-left:1px solid #d8dce5;border-radius:4px 0 0 4px;-webkit-box-shadow:none!important;box-shadow:none!important}.custom-theme .el-radio-button:last-child .el-radio-button__inner{border-radius:0 4px 4px 0}.custom-theme .el-radio-button:first-child:last-child .el-radio-button__inner{border-radius:4px}.custom-theme .el-radio-button--medium .el-radio-button__inner{padding:10px 20px;font-size:14px;border-radius:0}.custom-theme .el-radio-button--medium .el-radio-button__inner.is-round{padding:10px 20px}.custom-theme .el-radio-button--small .el-radio-button__inner{padding:9px 15px;font-size:12px;border-radius:0}.custom-theme .el-radio-button--small .el-radio-button__inner.is-round{padding:9px 15px}.custom-theme .el-radio-button--mini .el-radio-button__inner{padding:7px 15px;font-size:12px;border-radius:0}.custom-theme .el-radio-button--mini .el-radio-button__inner.is-round{padding:7px 15px}.custom-theme .el-radio-button:focus:not(.is-focus):not(:active){-webkit-box-shadow:0 0 2px 2px #262729;box-shadow:0 0 2px 2px #262729}.custom-theme .el-checkbox{color:#5a5e66;font-weight:500;font-size:14px;position:relative;cursor:pointer;display:inline-block;white-space:nowrap;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.custom-theme .el-checkbox.is-bordered{padding:9px 20px 9px 10px;border-radius:4px;border:1px solid #d8dce5}.custom-theme .el-checkbox.is-bordered.is-checked{border-color:#262729}.custom-theme .el-checkbox.is-bordered.is-disabled{border-color:#e6ebf5;cursor:not-allowed}.custom-theme .el-checkbox.is-bordered+.el-checkbox.is-bordered{margin-left:10px}.custom-theme .el-checkbox.is-bordered.el-checkbox--medium{padding:7px 20px 7px 10px;border-radius:4px}.custom-theme .el-checkbox.is-bordered.el-checkbox--medium .el-checkbox__label{line-height:17px;font-size:14px}.custom-theme .el-checkbox.is-bordered.el-checkbox--medium .el-checkbox__inner{height:14px;width:14px}.custom-theme .el-checkbox.is-bordered.el-checkbox--small{padding:3px 15px 7px 10px;border-radius:3px}.custom-theme .el-checkbox.is-bordered.el-checkbox--small .el-checkbox__label{line-height:15px;font-size:12px}.custom-theme .el-checkbox.is-bordered.el-checkbox--small .el-checkbox__inner{height:12px;width:12px}.custom-theme .el-checkbox.is-bordered.el-checkbox--small .el-checkbox__inner::after{height:6px;width:2px}.custom-theme .el-checkbox.is-bordered.el-checkbox--mini{padding:1px 15px 5px 10px;border-radius:3px}.custom-theme .el-checkbox.is-bordered.el-checkbox--mini .el-checkbox__label{line-height:12px;font-size:12px}.custom-theme .el-checkbox.is-bordered.el-checkbox--mini .el-checkbox__inner{height:12px;width:12px}.custom-theme .el-checkbox.is-bordered.el-checkbox--mini .el-checkbox__inner::after{height:6px;width:2px}.custom-theme .el-checkbox__input{white-space:nowrap;cursor:pointer;outline:0;display:inline-block;line-height:1;position:relative;vertical-align:middle}.custom-theme .el-checkbox__input.is-disabled .el-checkbox__inner{background-color:#edf2fc;border-color:#d8dce5;cursor:not-allowed}.custom-theme .el-checkbox__input.is-disabled .el-checkbox__inner::after{cursor:not-allowed;border-color:#b4bccc}.custom-theme .el-checkbox__input.is-disabled .el-checkbox__inner+.el-checkbox__label{cursor:not-allowed}.custom-theme .el-checkbox__input.is-disabled.is-checked .el-checkbox__inner{background-color:#edf2fc;border-color:#d8dce5}.custom-theme .el-checkbox__input.is-disabled.is-checked .el-checkbox__inner::after{border-color:#b4bccc}.custom-theme .el-checkbox__input.is-disabled.is-indeterminate .el-checkbox__inner{background-color:#edf2fc;border-color:#d8dce5}.custom-theme .el-checkbox__input.is-disabled.is-indeterminate .el-checkbox__inner::before{background-color:#b4bccc;border-color:#b4bccc}.custom-theme .el-checkbox__input.is-disabled+span.el-checkbox__label{color:#b4bccc;cursor:not-allowed}.custom-theme .el-checkbox__input.is-checked .el-checkbox__inner{background-color:#262729;border-color:#262729}.custom-theme .el-checkbox__input.is-checked .el-checkbox__inner::after{-webkit-transform:rotate(45deg) scaleY(1);transform:rotate(45deg) scaleY(1)}.custom-theme .el-checkbox__input.is-checked+.el-checkbox__label{color:#262729}.custom-theme .el-checkbox__input.is-focus .el-checkbox__inner{border-color:#262729}.custom-theme .el-checkbox__input.is-indeterminate .el-checkbox__inner{background-color:#262729;border-color:#262729}.custom-theme .el-checkbox__input.is-indeterminate .el-checkbox__inner::before{content:'';position:absolute;display:block;background-color:#fff;height:2px;-webkit-transform:scale(.5);transform:scale(.5);left:0;right:0;top:5px}.custom-theme .el-checkbox__input.is-indeterminate .el-checkbox__inner::after{display:none}.custom-theme .el-checkbox__inner{display:inline-block;position:relative;border:1px solid #d8dce5;border-radius:2px;-webkit-box-sizing:border-box;box-sizing:border-box;width:14px;height:14px;background-color:#fff;z-index:1;-webkit-transition:border-color .25s cubic-bezier(.71,-.46,.29,1.46),background-color .25s cubic-bezier(.71,-.46,.29,1.46);transition:border-color .25s cubic-bezier(.71,-.46,.29,1.46),background-color .25s cubic-bezier(.71,-.46,.29,1.46)}.custom-theme .el-checkbox__inner:hover{border-color:#262729}.custom-theme .el-checkbox__inner::after{-webkit-box-sizing:content-box;box-sizing:content-box;content:"";border:1px solid #fff;border-left:0;border-top:0;height:7px;left:4px;position:absolute;top:1px;-webkit-transform:rotate(45deg) scaleY(0);transform:rotate(45deg) scaleY(0);width:3px;-webkit-transition:-webkit-transform .15s cubic-bezier(.71,-.46,.88,.6) 50ms;transition:-webkit-transform .15s cubic-bezier(.71,-.46,.88,.6) 50ms;transition:transform .15s cubic-bezier(.71,-.46,.88,.6) 50ms;transition:transform .15s cubic-bezier(.71,-.46,.88,.6) 50ms,-webkit-transform .15s cubic-bezier(.71,-.46,.88,.6) 50ms;-webkit-transform-origin:center;transform-origin:center}.custom-theme .el-checkbox__original{opacity:0;outline:0;position:absolute;margin:0;width:0;height:0;left:-999px}.custom-theme .el-checkbox__label{display:inline-block;padding-left:10px;line-height:19px;font-size:14px}.custom-theme .el-checkbox+.el-checkbox{margin-left:30px}.custom-theme .el-checkbox-button{position:relative;display:inline-block}.custom-theme .el-checkbox-button__inner{display:inline-block;line-height:1;font-weight:500;white-space:nowrap;vertical-align:middle;cursor:pointer;background:#fff;border:1px solid #d8dce5;border-left:0;color:#5a5e66;-webkit-appearance:none;text-align:center;-webkit-box-sizing:border-box;box-sizing:border-box;outline:0;margin:0;position:relative;-webkit-transition:all .3s cubic-bezier(.645,.045,.355,1);transition:all .3s cubic-bezier(.645,.045,.355,1);-moz-user-select:none;-webkit-user-select:none;-ms-user-select:none;padding:12px 20px;font-size:14px;border-radius:0}.custom-theme .el-checkbox-button__inner.is-round{padding:12px 20px}.custom-theme .el-checkbox-button__inner:hover{color:#262729}.custom-theme .el-checkbox-button__inner [class*=el-icon-]{line-height:.9}.custom-theme .el-checkbox-button__inner [class*=el-icon-]+span{margin-left:5px}.custom-theme .el-checkbox-button__original{opacity:0;outline:0;position:absolute;margin:0;left:-999px}.custom-theme .el-checkbox-button.is-checked .el-checkbox-button__inner{color:#fff;background-color:#262729;border-color:#262729;-webkit-box-shadow:-1px 0 0 0 #7d7d7f;box-shadow:-1px 0 0 0 #7d7d7f}.custom-theme .el-checkbox-button.is-disabled .el-checkbox-button__inner{color:#b4bccc;cursor:not-allowed;background-image:none;background-color:#fff;border-color:#e6ebf5;-webkit-box-shadow:none;box-shadow:none}.custom-theme .el-checkbox-button:first-child .el-checkbox-button__inner{border-left:1px solid #d8dce5;border-radius:4px 0 0 4px;-webkit-box-shadow:none!important;box-shadow:none!important}.custom-theme .el-checkbox-button.is-focus .el-checkbox-button__inner{border-color:#262729}.custom-theme .el-checkbox-button:last-child .el-checkbox-button__inner{border-radius:0 4px 4px 0}.custom-theme .el-checkbox-button--medium .el-checkbox-button__inner{padding:10px 20px;font-size:14px;border-radius:0}.custom-theme .el-checkbox-button--medium .el-checkbox-button__inner.is-round{padding:10px 20px}.custom-theme .el-checkbox-button--small .el-checkbox-button__inner{padding:9px 15px;font-size:12px;border-radius:0}.custom-theme .el-checkbox-button--small .el-checkbox-button__inner.is-round{padding:9px 15px}.custom-theme .el-checkbox-button--mini .el-checkbox-button__inner{padding:7px 15px;font-size:12px;border-radius:0}.custom-theme .el-checkbox-button--mini .el-checkbox-button__inner.is-round{padding:7px 15px}.custom-theme .el-checkbox-group{font-size:0}.custom-theme .el-switch{display:inline-block;position:relative;font-size:14px;line-height:20px;height:20px;vertical-align:middle}.custom-theme .el-switch.is-disabled .el-switch__core,.custom-theme .el-switch.is-disabled .el-switch__label{cursor:not-allowed}.custom-theme .el-switch__label{-webkit-transition:.2s;transition:.2s;height:20px;display:inline-block;font-size:14px;font-weight:500;cursor:pointer;vertical-align:middle;color:#2d2f33}.custom-theme .el-switch__label.is-active{color:#262729}.custom-theme .el-switch__label--left{margin-right:10px}.custom-theme .el-switch__label--right{margin-left:10px}.custom-theme .el-switch__label *{line-height:1;font-size:14px;display:inline-block}.custom-theme .el-switch__input{position:absolute;width:0;height:0;opacity:0;margin:0}.custom-theme .el-switch__input:focus~.el-switch__core{outline:1px solid #262729}.custom-theme .el-switch__core{margin:0;display:inline-block;position:relative;width:40px;height:20px;border:1px solid #d8dce5;outline:0;border-radius:10px;-webkit-box-sizing:border-box;box-sizing:border-box;background:#d8dce5;cursor:pointer;-webkit-transition:border-color .3s,background-color .3s;transition:border-color .3s,background-color .3s;vertical-align:middle}.custom-theme .el-switch__core .el-switch__button{position:absolute;top:1px;left:1px;border-radius:100%;-webkit-transition:-webkit-transform .3s;transition:-webkit-transform .3s;transition:transform .3s;transition:transform .3s,-webkit-transform .3s;width:16px;height:16px;background-color:#fff}.custom-theme .el-switch.is-checked .el-switch__core{border-color:#262729;background-color:#262729}.custom-theme .el-switch.is-disabled{opacity:.6}.custom-theme .el-switch--wide .el-switch__label.el-switch__label--left span{left:10px}.custom-theme .el-switch--wide .el-switch__label.el-switch__label--right span{right:10px}.custom-theme .el-switch .label-fade-enter,.custom-theme .el-switch .label-fade-leave-active{opacity:0}.custom-theme .el-popper .popper__arrow,.custom-theme .el-popper .popper__arrow::after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.custom-theme .el-popper .popper__arrow{border-width:6px;-webkit-filter:drop-shadow(0 2px 12px rgba(0, 0, 0, .03));filter:drop-shadow(0 2px 12px rgba(0, 0, 0, .03))}.custom-theme .el-popper .popper__arrow::after{content:" ";border-width:6px}.custom-theme .el-popper[x-placement^=top]{margin-bottom:12px}.custom-theme .el-popper[x-placement^=top] .popper__arrow{bottom:-6px;left:50%;margin-right:3px;border-top-color:#e6ebf5;border-bottom-width:0}.custom-theme .el-popper[x-placement^=top] .popper__arrow::after{bottom:1px;margin-left:-6px;border-top-color:#fff;border-bottom-width:0}.custom-theme .el-popper[x-placement^=bottom]{margin-top:12px}.custom-theme .el-popper[x-placement^=bottom] .popper__arrow{top:-6px;left:50%;margin-right:3px;border-top-width:0;border-bottom-color:#e6ebf5}.custom-theme .el-popper[x-placement^=bottom] .popper__arrow::after{top:1px;margin-left:-6px;border-top-width:0;border-bottom-color:#fff}.custom-theme .el-popper[x-placement^=right]{margin-left:12px}.custom-theme .el-popper[x-placement^=right] .popper__arrow{top:50%;left:-6px;margin-bottom:3px;border-right-color:#e6ebf5;border-left-width:0}.custom-theme .el-popper[x-placement^=right] .popper__arrow::after{bottom:-6px;left:1px;border-right-color:#fff;border-left-width:0}.custom-theme .el-popper[x-placement^=left]{margin-right:12px}.custom-theme .el-popper[x-placement^=left] .popper__arrow{top:50%;right:-6px;margin-bottom:3px;border-right-width:0;border-left-color:#e6ebf5}.custom-theme .el-popper[x-placement^=left] .popper__arrow::after{right:1px;bottom:-6px;margin-left:-6px;border-right-width:0;border-left-color:#fff}.custom-theme .el-select-dropdown{position:absolute;z-index:1001;border:solid 1px #dfe4ed;border-radius:4px;background-color:#fff;-webkit-box-shadow:0 2px 12px 0 rgba(0,0,0,.1);box-shadow:0 2px 12px 0 rgba(0,0,0,.1);-webkit-box-sizing:border-box;box-sizing:border-box;margin:5px 0}.custom-theme .el-select-dropdown.is-multiple .el-select-dropdown__item.selected{color:#262729;background-color:#fff}.custom-theme .el-select-dropdown.is-multiple .el-select-dropdown__item.selected.hover{background-color:#f5f7fa}.custom-theme .el-select-dropdown.is-multiple .el-select-dropdown__item.selected::after{position:absolute;right:20px;font-family:element-icons;content:"\E611";font-size:12px;font-weight:700;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.custom-theme .el-select-dropdown .el-scrollbar.is-empty .el-select-dropdown__list{padding:0}.custom-theme .el-select-dropdown .popper__arrow{-webkit-transform:translateX(-400%);transform:translateX(-400%)}.custom-theme .el-select-dropdown.is-arrow-fixed .popper__arrow{-webkit-transform:translateX(-200%);transform:translateX(-200%)}.custom-theme .el-select-dropdown__empty{padding:10px 0;margin:0;text-align:center;color:#999;font-size:14px}.custom-theme .el-select-dropdown__wrap{max-height:274px}.custom-theme .el-select-dropdown__list{list-style:none;padding:6px 0;margin:0;-webkit-box-sizing:border-box;box-sizing:border-box}.custom-theme .el-input{position:relative;font-size:14px;display:inline-block;width:100%}.custom-theme .el-input::-webkit-scrollbar{z-index:11;width:6px}.custom-theme .el-input::-webkit-scrollbar:horizontal{height:6px}.custom-theme .el-input::-webkit-scrollbar-thumb{border-radius:5px;width:6px;background:#b4bccc}.custom-theme .el-input::-webkit-scrollbar-corner{background:#fff}.custom-theme .el-input::-webkit-scrollbar-track{background:#fff}.custom-theme .el-input::-webkit-scrollbar-track-piece{background:#fff;width:6px}.custom-theme .el-input__inner{-webkit-appearance:none;background-color:#fff;background-image:none;border-radius:4px;border:1px solid #d8dce5;-webkit-box-sizing:border-box;box-sizing:border-box;color:#5a5e66;display:inline-block;font-size:inherit;height:40px;line-height:1;outline:0;padding:0 15px;-webkit-transition:border-color .2s cubic-bezier(.645,.045,.355,1);transition:border-color .2s cubic-bezier(.645,.045,.355,1);width:100%}.custom-theme .el-input__inner::-webkit-input-placeholder{color:#b4bccc}.custom-theme .el-input__inner:-ms-input-placeholder{color:#b4bccc}.custom-theme .el-input__inner::-ms-input-placeholder{color:#b4bccc}.custom-theme .el-input__inner::placeholder{color:#b4bccc}.custom-theme .el-input__inner:hover{border-color:#b4bccc}.custom-theme .el-input__inner:focus{outline:0;border-color:#262729}.custom-theme .el-input__suffix{position:absolute;height:100%;right:5px;top:0;text-align:center;color:#b4bccc;-webkit-transition:all .3s;transition:all .3s;pointer-events:none}.custom-theme .el-input__suffix-inner{pointer-events:all}.custom-theme .el-input__prefix{position:absolute;height:100%;left:5px;top:0;text-align:center;color:#b4bccc;-webkit-transition:all .3s;transition:all .3s}.custom-theme .el-input__icon{height:100%;width:25px;text-align:center;-webkit-transition:all .3s;transition:all .3s;line-height:40px}.custom-theme .el-input__icon:after{content:'';height:100%;width:0;display:inline-block;vertical-align:middle}.custom-theme .el-input__validateIcon{pointer-events:none}.custom-theme .el-input.is-active .el-input__inner{outline:0;border-color:#262729}.custom-theme .el-input.is-disabled .el-input__inner{background-color:#f5f7fa;border-color:#dfe4ed;color:#b4bccc;cursor:not-allowed}.custom-theme .el-input.is-disabled .el-input__inner::-webkit-input-placeholder{color:#b4bccc}.custom-theme .el-input.is-disabled .el-input__inner:-ms-input-placeholder{color:#b4bccc}.custom-theme .el-input.is-disabled .el-input__inner::-ms-input-placeholder{color:#b4bccc}.custom-theme .el-input.is-disabled .el-input__inner::placeholder{color:#b4bccc}.custom-theme .el-input.is-disabled .el-input__icon{cursor:not-allowed}.custom-theme .el-input--suffix .el-input__inner{padding-right:30px}.custom-theme .el-input--prefix .el-input__inner{padding-left:30px}.custom-theme .el-input--medium{font-size:14px}.custom-theme .el-input--medium .el-input__inner{height:36px}.custom-theme .el-input--medium .el-input__icon{line-height:36px}.custom-theme .el-input--small{font-size:13px}.custom-theme .el-input--small .el-input__inner{height:32px}.custom-theme .el-input--small .el-input__icon{line-height:32px}.custom-theme .el-input--mini{font-size:12px}.custom-theme .el-input--mini .el-input__inner{height:28px}.custom-theme .el-input--mini .el-input__icon{line-height:28px}.custom-theme .el-input-group{line-height:normal;display:inline-table;width:100%;border-collapse:separate}.custom-theme .el-input-group>.el-input__inner{vertical-align:middle;display:table-cell}.custom-theme .el-input-group__append,.custom-theme .el-input-group__prepend{background-color:#f5f7fa;color:#0a76a4;vertical-align:middle;display:table-cell;position:relative;border:1px solid #d8dce5;border-radius:4px;padding:0 20px;width:1px;white-space:nowrap}.custom-theme .el-input-group__append:focus,.custom-theme .el-input-group__prepend:focus{outline:0}.custom-theme .el-input-group__append .el-button,.custom-theme .el-input-group__append .el-select,.custom-theme .el-input-group__prepend .el-button,.custom-theme .el-input-group__prepend .el-select{display:inline-block;margin:-20px}.custom-theme .el-input-group__append button.el-button,.custom-theme .el-input-group__append div.el-select .el-input__inner,.custom-theme .el-input-group__append div.el-select:hover .el-input__inner,.custom-theme .el-input-group__prepend button.el-button,.custom-theme .el-input-group__prepend div.el-select .el-input__inner,.custom-theme .el-input-group__prepend div.el-select:hover .el-input__inner{border-color:transparent;background-color:transparent;color:inherit;border-top:0;border-bottom:0}.custom-theme .el-input-group__append .el-button,.custom-theme .el-input-group__append .el-input,.custom-theme .el-input-group__prepend .el-button,.custom-theme .el-input-group__prepend .el-input{font-size:inherit}.custom-theme .el-input-group__prepend{border-right:0;border-top-right-radius:0;border-bottom-right-radius:0}.custom-theme .el-input-group__append{border-left:0;border-top-left-radius:0;border-bottom-left-radius:0}.custom-theme .el-input-group--prepend .el-input__inner{border-top-left-radius:0;border-bottom-left-radius:0}.custom-theme .el-input-group--append .el-input__inner{border-top-right-radius:0;border-bottom-right-radius:0}.custom-theme .el-textarea{display:inline-block;width:100%;vertical-align:bottom}.custom-theme .el-textarea__inner{display:block;resize:vertical;padding:5px 15px;line-height:1.5;-webkit-box-sizing:border-box;box-sizing:border-box;width:100%;font-size:14px;color:#5a5e66;background-color:#fff;background-image:none;border:1px solid #d8dce5;border-radius:4px;-webkit-transition:border-color .2s cubic-bezier(.645,.045,.355,1);transition:border-color .2s cubic-bezier(.645,.045,.355,1)}.custom-theme .el-textarea__inner::-webkit-input-placeholder{color:#b4bccc}.custom-theme .el-textarea__inner:-ms-input-placeholder{color:#b4bccc}.custom-theme .el-textarea__inner::-ms-input-placeholder{color:#b4bccc}.custom-theme .el-textarea__inner::placeholder{color:#b4bccc}.custom-theme .el-textarea__inner:hover{border-color:#b4bccc}.custom-theme .el-textarea__inner:focus{outline:0;border-color:#262729}.custom-theme .el-textarea.is-disabled .el-textarea__inner{background-color:#f5f7fa;border-color:#dfe4ed;color:#b4bccc;cursor:not-allowed}.custom-theme .el-textarea.is-disabled .el-textarea__inner::-webkit-input-placeholder{color:#b4bccc}.custom-theme .el-textarea.is-disabled .el-textarea__inner:-ms-input-placeholder{color:#b4bccc}.custom-theme .el-textarea.is-disabled .el-textarea__inner::-ms-input-placeholder{color:#b4bccc}.custom-theme .el-textarea.is-disabled .el-textarea__inner::placeholder{color:#b4bccc}.custom-theme .el-tag{background-color:rgba(38,39,41,.1);display:inline-block;padding:0 10px;height:32px;line-height:30px;font-size:12px;color:#262729;border-radius:4px;-webkit-box-sizing:border-box;box-sizing:border-box;border:1px solid rgba(38,39,41,.2);white-space:nowrap}.custom-theme .el-tag .el-icon-close{border-radius:50%;text-align:center;position:relative;cursor:pointer;font-size:12px;height:18px;width:18px;line-height:18px;vertical-align:middle;top:-1px;right:-5px;color:#262729}.custom-theme .el-tag .el-icon-close::before{display:block}.custom-theme .el-tag .el-icon-close:hover{background-color:#262729;color:#fff}.custom-theme .el-tag--info{background-color:rgba(10,118,164,.1);border-color:rgba(10,118,164,.2);color:#0a76a4}.custom-theme .el-tag--info.is-hit{border-color:#0a76a4}.custom-theme .el-tag--info .el-tag__close{color:#0a76a4}.custom-theme .el-tag--info .el-tag__close:hover{background-color:#0a76a4;color:#fff}.custom-theme .el-tag--success{background-color:rgba(64,145,103,.1);border-color:rgba(64,145,103,.2);color:#409167}.custom-theme .el-tag--success.is-hit{border-color:#409167}.custom-theme .el-tag--success .el-tag__close{color:#409167}.custom-theme .el-tag--success .el-tag__close:hover{background-color:#409167;color:#fff}.custom-theme .el-tag--warning{background-color:rgba(157,164,8,.1);border-color:rgba(157,164,8,.2);color:#9da408}.custom-theme .el-tag--warning.is-hit{border-color:#9da408}.custom-theme .el-tag--warning .el-tag__close{color:#9da408}.custom-theme .el-tag--warning .el-tag__close:hover{background-color:#9da408;color:#fff}.custom-theme .el-tag--danger{background-color:rgba(179,69,14,.1);border-color:rgba(179,69,14,.2);color:#b3450e}.custom-theme .el-tag--danger.is-hit{border-color:#b3450e}.custom-theme .el-tag--danger .el-tag__close{color:#b3450e}.custom-theme .el-tag--danger .el-tag__close:hover{background-color:#b3450e;color:#fff}.custom-theme .el-tag--medium{height:28px;line-height:26px}.custom-theme .el-tag--medium .el-icon-close{-webkit-transform:scale(.8);transform:scale(.8)}.custom-theme .el-tag--small{height:24px;padding:0 8px;line-height:22px}.custom-theme .el-tag--small .el-icon-close{-webkit-transform:scale(.8);transform:scale(.8)}.custom-theme .el-tag--mini{height:20px;padding:0 5px;line-height:19px}.custom-theme .el-tag--mini .el-icon-close{margin-left:-3px;-webkit-transform:scale(.7);transform:scale(.7)}.custom-theme .el-select-dropdown__item{font-size:14px;padding:0 20px;position:relative;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;color:#5a5e66;height:34px;line-height:34px;-webkit-box-sizing:border-box;box-sizing:border-box;cursor:pointer}.custom-theme .el-select-dropdown__item.is-disabled{color:#b4bccc;cursor:not-allowed}.custom-theme .el-select-dropdown__item.is-disabled:hover{background-color:#fff}.custom-theme .el-select-dropdown__item.hover,.custom-theme .el-select-dropdown__item:hover{background-color:#f5f7fa}.custom-theme .el-select-dropdown__item.selected{color:#262729;font-weight:700}.custom-theme .el-select-dropdown__item span{line-height:34px!important}.custom-theme .el-select-group{margin:0;padding:0}.custom-theme .el-select-group__wrap{position:relative;list-style:none;margin:0;padding:0}.custom-theme .el-select-group__wrap:not(:last-of-type){padding-bottom:24px}.custom-theme .el-select-group__wrap:not(:last-of-type)::after{content:'';position:absolute;display:block;left:20px;right:20px;bottom:12px;height:1px;background:#dfe4ed}.custom-theme .el-select-group__title{padding-left:20px;font-size:12px;color:#0a76a4;line-height:30px}.custom-theme .el-select-group .el-select-dropdown__item{padding-left:20px}.custom-theme .el-scrollbar{overflow:hidden;position:relative}.custom-theme .el-scrollbar:active>.el-scrollbar__bar,.custom-theme .el-scrollbar:focus>.el-scrollbar__bar,.custom-theme .el-scrollbar:hover>.el-scrollbar__bar{opacity:1;-webkit-transition:opacity 340ms ease-out;transition:opacity 340ms ease-out}.custom-theme .el-scrollbar__wrap{overflow:scroll;height:100%}.custom-theme .el-scrollbar__wrap--hidden-default::-webkit-scrollbar{width:0;height:0}.custom-theme .el-scrollbar__thumb{position:relative;display:block;width:0;height:0;cursor:pointer;border-radius:inherit;background-color:rgba(135,141,153,.3);-webkit-transition:.3s background-color;transition:.3s background-color}.custom-theme .el-scrollbar__thumb:hover{background-color:rgba(135,141,153,.5)}.custom-theme .el-scrollbar__bar{position:absolute;right:2px;bottom:2px;z-index:1;border-radius:4px;opacity:0;-webkit-transition:opacity 120ms ease-out;transition:opacity 120ms ease-out}.custom-theme .el-scrollbar__bar.is-vertical{width:6px;top:2px}.custom-theme .el-scrollbar__bar.is-vertical>div{width:100%}.custom-theme .el-scrollbar__bar.is-horizontal{height:6px;left:2px}.custom-theme .el-scrollbar__bar.is-horizontal>div{height:100%}.custom-theme .el-select{display:inline-block;position:relative}.custom-theme .el-select:hover .el-input__inner{border-color:#b4bccc}.custom-theme .el-select .el-input__inner{cursor:pointer;padding-right:35px}.custom-theme .el-select .el-input__inner:focus{border-color:#262729}.custom-theme .el-select .el-input .el-select__caret{color:#b4bccc;font-size:14px;-webkit-transition:-webkit-transform .3s;transition:-webkit-transform .3s;transition:transform .3s;transition:transform .3s,-webkit-transform .3s;-webkit-transform:rotateZ(180deg);transform:rotateZ(180deg);line-height:16px;cursor:pointer}.custom-theme .el-select .el-input .el-select__caret.is-reverse{-webkit-transform:rotateZ(0);transform:rotateZ(0)}.custom-theme .el-select .el-input .el-select__caret.is-show-close{font-size:14px;text-align:center;-webkit-transform:rotateZ(180deg);transform:rotateZ(180deg);border-radius:100%;color:#b4bccc;-webkit-transition:color .2s cubic-bezier(.645,.045,.355,1);transition:color .2s cubic-bezier(.645,.045,.355,1)}.custom-theme .el-select .el-input .el-select__caret.is-show-close:hover{color:#878d99}.custom-theme .el-select .el-input.is-disabled .el-input__inner{cursor:not-allowed}.custom-theme .el-select .el-input.is-disabled .el-input__inner:hover{border-color:#dfe4ed}.custom-theme .el-select>.el-input{display:block}.custom-theme .el-select__input{border:none;outline:0;padding:0;margin-left:15px;color:#666;font-size:14px;vertical-align:baseline;-webkit-appearance:none;-moz-appearance:none;appearance:none;height:28px;background-color:transparent}.custom-theme .el-select__input.is-mini{height:14px}.custom-theme .el-select__close{cursor:pointer;position:absolute;top:8px;z-index:1000;right:25px;color:#b4bccc;line-height:18px;font-size:14px}.custom-theme .el-select__close:hover{color:#878d99}.custom-theme .el-select__tags{position:absolute;line-height:normal;white-space:normal;z-index:1;top:50%;-webkit-transform:translateY(-50%);transform:translateY(-50%)}.custom-theme .el-select .el-tag__close{margin-top:-2px}.custom-theme .el-select .el-tag{-webkit-box-sizing:border-box;box-sizing:border-box;border-color:transparent;margin:3px 0 3px 6px;background-color:#f0f2f5}.custom-theme .el-select .el-tag__close.el-icon-close{background-color:#b4bccc;right:-7px;color:#fff}.custom-theme .el-select .el-tag__close.el-icon-close:hover{background-color:#878d99}.custom-theme .el-select .el-tag__close.el-icon-close::before{display:block;-webkit-transform:translate(0,.5px);transform:translate(0,.5px)}.custom-theme .el-select__tag{display:inline-block;height:24px;line-height:24px;font-size:14px;border-radius:4px;color:#fff;background-color:#262729}.custom-theme .el-select__tag .el-icon-close{font-size:14px}.custom-theme .el-button{display:inline-block;line-height:1;white-space:nowrap;cursor:pointer;background:#fff;border:1px solid #d8dce5;border-color:#d8dce5;color:#5a5e66;-webkit-appearance:none;text-align:center;-webkit-box-sizing:border-box;box-sizing:border-box;outline:0;margin:0;-webkit-transition:.1s;transition:.1s;font-weight:500;-moz-user-select:none;-webkit-user-select:none;-ms-user-select:none;padding:12px 20px;font-size:14px;border-radius:4px}.custom-theme .el-button+.el-button{margin-left:10px}.custom-theme .el-button.is-round{padding:12px 20px}.custom-theme .el-button:focus,.custom-theme .el-button:hover{color:#262729;border-color:#bebebf;background-color:#e9e9ea}.custom-theme .el-button:active{color:#222325;border-color:#222325;outline:0}.custom-theme .el-button::-moz-focus-inner{border:0}.custom-theme .el-button [class*=el-icon-]+span{margin-left:5px}.custom-theme .el-button.is-plain:focus,.custom-theme .el-button.is-plain:hover{background:#fff;border-color:#262729;color:#262729}.custom-theme .el-button.is-plain:active{background:#fff;border-color:#222325;color:#222325;outline:0}.custom-theme .el-button.is-active{color:#222325;border-color:#222325}.custom-theme .el-button.is-disabled,.custom-theme .el-button.is-disabled:focus,.custom-theme .el-button.is-disabled:hover{color:#b4bccc;cursor:not-allowed;background-image:none;background-color:#fff;border-color:#e6ebf5}.custom-theme .el-button.is-disabled.el-button--text{background-color:transparent}.custom-theme .el-button.is-disabled.is-plain,.custom-theme .el-button.is-disabled.is-plain:focus,.custom-theme .el-button.is-disabled.is-plain:hover{background-color:#fff;border-color:#e6ebf5;color:#b4bccc}.custom-theme .el-button.is-loading{position:relative;pointer-events:none}.custom-theme .el-button.is-loading:before{pointer-events:none;content:'';position:absolute;left:-1px;top:-1px;right:-1px;bottom:-1px;border-radius:inherit;background-color:rgba(255,255,255,.35)}.custom-theme .el-button.is-round{border-radius:20px;padding:12px 23px}.custom-theme .el-button--primary{color:#fff;background-color:#262729;border-color:#262729}.custom-theme .el-button--primary:focus,.custom-theme .el-button--primary:hover{background:#515254;border-color:#515254;color:#fff}.custom-theme .el-button--primary:active{background:#222325;border-color:#222325;color:#fff;outline:0}.custom-theme .el-button--primary.is-active{background:#222325;border-color:#222325;color:#fff}.custom-theme .el-button--primary.is-disabled,.custom-theme .el-button--primary.is-disabled:active,.custom-theme .el-button--primary.is-disabled:focus,.custom-theme .el-button--primary.is-disabled:hover{color:#fff;background-color:#939394;border-color:#939394}.custom-theme .el-button--primary.is-plain{color:#262729;background:#e9e9ea;border-color:#a8a9a9}.custom-theme .el-button--primary.is-plain:focus,.custom-theme .el-button--primary.is-plain:hover{background:#262729;border-color:#262729;color:#fff}.custom-theme .el-button--primary.is-plain:active{background:#222325;border-color:#222325;color:#fff;outline:0}.custom-theme .el-button--primary.is-plain.is-disabled,.custom-theme .el-button--primary.is-plain.is-disabled:active,.custom-theme .el-button--primary.is-plain.is-disabled:focus,.custom-theme .el-button--primary.is-plain.is-disabled:hover{color:#7d7d7f;background-color:#e9e9ea;border-color:#d4d4d4}.custom-theme .el-button--success{color:#fff;background-color:#409167;border-color:#409167}.custom-theme .el-button--success:focus,.custom-theme .el-button--success:hover{background:#66a785;border-color:#66a785;color:#fff}.custom-theme .el-button--success:active{background:#3a835d;border-color:#3a835d;color:#fff;outline:0}.custom-theme .el-button--success.is-active{background:#3a835d;border-color:#3a835d;color:#fff}.custom-theme .el-button--success.is-disabled,.custom-theme .el-button--success.is-disabled:active,.custom-theme .el-button--success.is-disabled:focus,.custom-theme .el-button--success.is-disabled:hover{color:#fff;background-color:#a0c8b3;border-color:#a0c8b3}.custom-theme .el-button--success.is-plain{color:#409167;background:#ecf4f0;border-color:#b3d3c2}.custom-theme .el-button--success.is-plain:focus,.custom-theme .el-button--success.is-plain:hover{background:#409167;border-color:#409167;color:#fff}.custom-theme .el-button--success.is-plain:active{background:#3a835d;border-color:#3a835d;color:#fff;outline:0}.custom-theme .el-button--success.is-plain.is-disabled,.custom-theme .el-button--success.is-plain.is-disabled:active,.custom-theme .el-button--success.is-plain.is-disabled:focus,.custom-theme .el-button--success.is-plain.is-disabled:hover{color:#8cbda4;background-color:#ecf4f0;border-color:#d9e9e1}.custom-theme .el-button--warning{color:#fff;background-color:#9da408;border-color:#9da408}.custom-theme .el-button--warning:focus,.custom-theme .el-button--warning:hover{background:#b1b639;border-color:#b1b639;color:#fff}.custom-theme .el-button--warning:active{background:#8d9407;border-color:#8d9407;color:#fff;outline:0}.custom-theme .el-button--warning.is-active{background:#8d9407;border-color:#8d9407;color:#fff}.custom-theme .el-button--warning.is-disabled,.custom-theme .el-button--warning.is-disabled:active,.custom-theme .el-button--warning.is-disabled:focus,.custom-theme .el-button--warning.is-disabled:hover{color:#fff;background-color:#ced284;border-color:#ced284}.custom-theme .el-button--warning.is-plain{color:#9da408;background:#f5f6e6;border-color:#d8db9c}.custom-theme .el-button--warning.is-plain:focus,.custom-theme .el-button--warning.is-plain:hover{background:#9da408;border-color:#9da408;color:#fff}.custom-theme .el-button--warning.is-plain:active{background:#8d9407;border-color:#8d9407;color:#fff;outline:0}.custom-theme .el-button--warning.is-plain.is-disabled,.custom-theme .el-button--warning.is-plain.is-disabled:active,.custom-theme .el-button--warning.is-plain.is-disabled:focus,.custom-theme .el-button--warning.is-plain.is-disabled:hover{color:#c4c86b;background-color:#f5f6e6;border-color:#ebedce}.custom-theme .el-button--danger{color:#fff;background-color:#b3450e;border-color:#b3450e}.custom-theme .el-button--danger:focus,.custom-theme .el-button--danger:hover{background:#c26a3e;border-color:#c26a3e;color:#fff}.custom-theme .el-button--danger:active{background:#a13e0d;border-color:#a13e0d;color:#fff;outline:0}.custom-theme .el-button--danger.is-active{background:#a13e0d;border-color:#a13e0d;color:#fff}.custom-theme .el-button--danger.is-disabled,.custom-theme .el-button--danger.is-disabled:active,.custom-theme .el-button--danger.is-disabled:focus,.custom-theme .el-button--danger.is-disabled:hover{color:#fff;background-color:#d9a287;border-color:#d9a287}.custom-theme .el-button--danger.is-plain{color:#b3450e;background:#f7ece7;border-color:#e1b59f}.custom-theme .el-button--danger.is-plain:focus,.custom-theme .el-button--danger.is-plain:hover{background:#b3450e;border-color:#b3450e;color:#fff}.custom-theme .el-button--danger.is-plain:active{background:#a13e0d;border-color:#a13e0d;color:#fff;outline:0}.custom-theme .el-button--danger.is-plain.is-disabled,.custom-theme .el-button--danger.is-plain.is-disabled:active,.custom-theme .el-button--danger.is-plain.is-disabled:focus,.custom-theme .el-button--danger.is-plain.is-disabled:hover{color:#d18f6e;background-color:#f7ece7;border-color:#f0dacf}.custom-theme .el-button--info{color:#fff;background-color:#0a76a4;border-color:#0a76a4}.custom-theme .el-button--info:focus,.custom-theme .el-button--info:hover{background:#3b91b6;border-color:#3b91b6;color:#fff}.custom-theme .el-button--info:active{background:#096a94;border-color:#096a94;color:#fff;outline:0}.custom-theme .el-button--info.is-active{background:#096a94;border-color:#096a94;color:#fff}.custom-theme .el-button--info.is-disabled,.custom-theme .el-button--info.is-disabled:active,.custom-theme .el-button--info.is-disabled:focus,.custom-theme .el-button--info.is-disabled:hover{color:#fff;background-color:#85bbd2;border-color:#85bbd2}.custom-theme .el-button--info.is-plain{color:#0a76a4;background:#e7f1f6;border-color:#9dc8db}.custom-theme .el-button--info.is-plain:focus,.custom-theme .el-button--info.is-plain:hover{background:#0a76a4;border-color:#0a76a4;color:#fff}.custom-theme .el-button--info.is-plain:active{background:#096a94;border-color:#096a94;color:#fff;outline:0}.custom-theme .el-button--info.is-plain.is-disabled,.custom-theme .el-button--info.is-plain.is-disabled:active,.custom-theme .el-button--info.is-plain.is-disabled:focus,.custom-theme .el-button--info.is-plain.is-disabled:hover{color:#6cadc8;background-color:#e7f1f6;border-color:#cee4ed}.custom-theme .el-button--medium{padding:10px 20px;font-size:14px;border-radius:4px}.custom-theme .el-button--medium.is-round{padding:10px 20px}.custom-theme .el-button--small{padding:9px 15px;font-size:12px;border-radius:3px}.custom-theme .el-button--small.is-round{padding:9px 15px}.custom-theme .el-button--mini{padding:7px 15px;font-size:12px;border-radius:3px}.custom-theme .el-button--mini.is-round{padding:7px 15px}.custom-theme .el-button--text{border:none;color:#262729;background:0 0;padding-left:0;padding-right:0}.custom-theme .el-button--text:focus,.custom-theme .el-button--text:hover{color:#515254;border-color:transparent;background-color:transparent}.custom-theme .el-button--text:active{color:#222325;border-color:transparent;background-color:transparent}.custom-theme .el-button-group{display:inline-block;vertical-align:middle}.custom-theme .el-button-group::after,.custom-theme .el-button-group::before{display:table;content:""}.custom-theme .el-button-group::after{clear:both}.custom-theme .el-button-group .el-button{float:left;position:relative}.custom-theme .el-button-group .el-button+.el-button{margin-left:0}.custom-theme .el-button-group .el-button:first-child{border-top-right-radius:0;border-bottom-right-radius:0}.custom-theme .el-button-group .el-button:last-child{border-top-left-radius:0;border-bottom-left-radius:0}.custom-theme .el-button-group .el-button:not(:first-child):not(:last-child){border-radius:0}.custom-theme .el-button-group .el-button:not(:last-child){margin-right:-1px}.custom-theme .el-button-group .el-button:active,.custom-theme .el-button-group .el-button:focus,.custom-theme .el-button-group .el-button:hover{z-index:1}.custom-theme .el-button-group .el-button.is-active{z-index:1}.custom-theme .el-button-group .el-button--primary:first-child{border-right-color:rgba(255,255,255,.5)}.custom-theme .el-button-group .el-button--primary:last-child{border-left-color:rgba(255,255,255,.5)}.custom-theme .el-button-group .el-button--primary:not(:first-child):not(:last-child){border-left-color:rgba(255,255,255,.5);border-right-color:rgba(255,255,255,.5)}.custom-theme .el-button-group .el-button--success:first-child{border-right-color:rgba(255,255,255,.5)}.custom-theme .el-button-group .el-button--success:last-child{border-left-color:rgba(255,255,255,.5)}.custom-theme .el-button-group .el-button--success:not(:first-child):not(:last-child){border-left-color:rgba(255,255,255,.5);border-right-color:rgba(255,255,255,.5)}.custom-theme .el-button-group .el-button--warning:first-child{border-right-color:rgba(255,255,255,.5)}.custom-theme .el-button-group .el-button--warning:last-child{border-left-color:rgba(255,255,255,.5)}.custom-theme .el-button-group .el-button--warning:not(:first-child):not(:last-child){border-left-color:rgba(255,255,255,.5);border-right-color:rgba(255,255,255,.5)}.custom-theme .el-button-group .el-button--danger:first-child{border-right-color:rgba(255,255,255,.5)}.custom-theme .el-button-group .el-button--danger:last-child{border-left-color:rgba(255,255,255,.5)}.custom-theme .el-button-group .el-button--danger:not(:first-child):not(:last-child){border-left-color:rgba(255,255,255,.5);border-right-color:rgba(255,255,255,.5)}.custom-theme .el-button-group .el-button--info:first-child{border-right-color:rgba(255,255,255,.5)}.custom-theme .el-button-group .el-button--info:last-child{border-left-color:rgba(255,255,255,.5)}.custom-theme .el-button-group .el-button--info:not(:first-child):not(:last-child){border-left-color:rgba(255,255,255,.5);border-right-color:rgba(255,255,255,.5)}.custom-theme .el-checkbox{color:#5a5e66;font-weight:500;font-size:14px;position:relative;cursor:pointer;display:inline-block;white-space:nowrap;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.custom-theme .el-checkbox.is-bordered{padding:9px 20px 9px 10px;border-radius:4px;border:1px solid #d8dce5}.custom-theme .el-checkbox.is-bordered.is-checked{border-color:#262729}.custom-theme .el-checkbox.is-bordered.is-disabled{border-color:#e6ebf5;cursor:not-allowed}.custom-theme .el-checkbox.is-bordered+.el-checkbox.is-bordered{margin-left:10px}.custom-theme .el-checkbox.is-bordered.el-checkbox--medium{padding:7px 20px 7px 10px;border-radius:4px}.custom-theme .el-checkbox.is-bordered.el-checkbox--medium .el-checkbox__label{line-height:17px;font-size:14px}.custom-theme .el-checkbox.is-bordered.el-checkbox--medium .el-checkbox__inner{height:14px;width:14px}.custom-theme .el-checkbox.is-bordered.el-checkbox--small{padding:3px 15px 7px 10px;border-radius:3px}.custom-theme .el-checkbox.is-bordered.el-checkbox--small .el-checkbox__label{line-height:15px;font-size:12px}.custom-theme .el-checkbox.is-bordered.el-checkbox--small .el-checkbox__inner{height:12px;width:12px}.custom-theme .el-checkbox.is-bordered.el-checkbox--small .el-checkbox__inner::after{height:6px;width:2px}.custom-theme .el-checkbox.is-bordered.el-checkbox--mini{padding:1px 15px 5px 10px;border-radius:3px}.custom-theme .el-checkbox.is-bordered.el-checkbox--mini .el-checkbox__label{line-height:12px;font-size:12px}.custom-theme .el-checkbox.is-bordered.el-checkbox--mini .el-checkbox__inner{height:12px;width:12px}.custom-theme .el-checkbox.is-bordered.el-checkbox--mini .el-checkbox__inner::after{height:6px;width:2px}.custom-theme .el-checkbox__input{white-space:nowrap;cursor:pointer;outline:0;display:inline-block;line-height:1;position:relative;vertical-align:middle}.custom-theme .el-checkbox__input.is-disabled .el-checkbox__inner{background-color:#edf2fc;border-color:#d8dce5;cursor:not-allowed}.custom-theme .el-checkbox__input.is-disabled .el-checkbox__inner::after{cursor:not-allowed;border-color:#b4bccc}.custom-theme .el-checkbox__input.is-disabled .el-checkbox__inner+.el-checkbox__label{cursor:not-allowed}.custom-theme .el-checkbox__input.is-disabled.is-checked .el-checkbox__inner{background-color:#edf2fc;border-color:#d8dce5}.custom-theme .el-checkbox__input.is-disabled.is-checked .el-checkbox__inner::after{border-color:#b4bccc}.custom-theme .el-checkbox__input.is-disabled.is-indeterminate .el-checkbox__inner{background-color:#edf2fc;border-color:#d8dce5}.custom-theme .el-checkbox__input.is-disabled.is-indeterminate .el-checkbox__inner::before{background-color:#b4bccc;border-color:#b4bccc}.custom-theme .el-checkbox__input.is-disabled+span.el-checkbox__label{color:#b4bccc;cursor:not-allowed}.custom-theme .el-checkbox__input.is-checked .el-checkbox__inner{background-color:#262729;border-color:#262729}.custom-theme .el-checkbox__input.is-checked .el-checkbox__inner::after{-webkit-transform:rotate(45deg) scaleY(1);transform:rotate(45deg) scaleY(1)}.custom-theme .el-checkbox__input.is-checked+.el-checkbox__label{color:#262729}.custom-theme .el-checkbox__input.is-focus .el-checkbox__inner{border-color:#262729}.custom-theme .el-checkbox__input.is-indeterminate .el-checkbox__inner{background-color:#262729;border-color:#262729}.custom-theme .el-checkbox__input.is-indeterminate .el-checkbox__inner::before{content:'';position:absolute;display:block;background-color:#fff;height:2px;-webkit-transform:scale(.5);transform:scale(.5);left:0;right:0;top:5px}.custom-theme .el-checkbox__input.is-indeterminate .el-checkbox__inner::after{display:none}.custom-theme .el-checkbox__inner{display:inline-block;position:relative;border:1px solid #d8dce5;border-radius:2px;-webkit-box-sizing:border-box;box-sizing:border-box;width:14px;height:14px;background-color:#fff;z-index:1;-webkit-transition:border-color .25s cubic-bezier(.71,-.46,.29,1.46),background-color .25s cubic-bezier(.71,-.46,.29,1.46);transition:border-color .25s cubic-bezier(.71,-.46,.29,1.46),background-color .25s cubic-bezier(.71,-.46,.29,1.46)}.custom-theme .el-checkbox__inner:hover{border-color:#262729}.custom-theme .el-checkbox__inner::after{-webkit-box-sizing:content-box;box-sizing:content-box;content:"";border:1px solid #fff;border-left:0;border-top:0;height:7px;left:4px;position:absolute;top:1px;-webkit-transform:rotate(45deg) scaleY(0);transform:rotate(45deg) scaleY(0);width:3px;-webkit-transition:-webkit-transform .15s cubic-bezier(.71,-.46,.88,.6) 50ms;transition:-webkit-transform .15s cubic-bezier(.71,-.46,.88,.6) 50ms;transition:transform .15s cubic-bezier(.71,-.46,.88,.6) 50ms;transition:transform .15s cubic-bezier(.71,-.46,.88,.6) 50ms,-webkit-transform .15s cubic-bezier(.71,-.46,.88,.6) 50ms;-webkit-transform-origin:center;transform-origin:center}.custom-theme .el-checkbox__original{opacity:0;outline:0;position:absolute;margin:0;width:0;height:0;left:-999px}.custom-theme .el-checkbox__label{display:inline-block;padding-left:10px;line-height:19px;font-size:14px}.custom-theme .el-checkbox+.el-checkbox{margin-left:30px}.custom-theme .el-checkbox-button{position:relative;display:inline-block}.custom-theme .el-checkbox-button__inner{display:inline-block;line-height:1;font-weight:500;white-space:nowrap;vertical-align:middle;cursor:pointer;background:#fff;border:1px solid #d8dce5;border-left:0;color:#5a5e66;-webkit-appearance:none;text-align:center;-webkit-box-sizing:border-box;box-sizing:border-box;outline:0;margin:0;position:relative;-webkit-transition:all .3s cubic-bezier(.645,.045,.355,1);transition:all .3s cubic-bezier(.645,.045,.355,1);-moz-user-select:none;-webkit-user-select:none;-ms-user-select:none;padding:12px 20px;font-size:14px;border-radius:0}.custom-theme .el-checkbox-button__inner.is-round{padding:12px 20px}.custom-theme .el-checkbox-button__inner:hover{color:#262729}.custom-theme .el-checkbox-button__inner [class*=el-icon-]{line-height:.9}.custom-theme .el-checkbox-button__inner [class*=el-icon-]+span{margin-left:5px}.custom-theme .el-checkbox-button__original{opacity:0;outline:0;position:absolute;margin:0;left:-999px}.custom-theme .el-checkbox-button.is-checked .el-checkbox-button__inner{color:#fff;background-color:#262729;border-color:#262729;-webkit-box-shadow:-1px 0 0 0 #7d7d7f;box-shadow:-1px 0 0 0 #7d7d7f}.custom-theme .el-checkbox-button.is-disabled .el-checkbox-button__inner{color:#b4bccc;cursor:not-allowed;background-image:none;background-color:#fff;border-color:#e6ebf5;-webkit-box-shadow:none;box-shadow:none}.custom-theme .el-checkbox-button:first-child .el-checkbox-button__inner{border-left:1px solid #d8dce5;border-radius:4px 0 0 4px;-webkit-box-shadow:none!important;box-shadow:none!important}.custom-theme .el-checkbox-button.is-focus .el-checkbox-button__inner{border-color:#262729}.custom-theme .el-checkbox-button:last-child .el-checkbox-button__inner{border-radius:0 4px 4px 0}.custom-theme .el-checkbox-button--medium .el-checkbox-button__inner{padding:10px 20px;font-size:14px;border-radius:0}.custom-theme .el-checkbox-button--medium .el-checkbox-button__inner.is-round{padding:10px 20px}.custom-theme .el-checkbox-button--small .el-checkbox-button__inner{padding:9px 15px;font-size:12px;border-radius:0}.custom-theme .el-checkbox-button--small .el-checkbox-button__inner.is-round{padding:9px 15px}.custom-theme .el-checkbox-button--mini .el-checkbox-button__inner{padding:7px 15px;font-size:12px;border-radius:0}.custom-theme .el-checkbox-button--mini .el-checkbox-button__inner.is-round{padding:7px 15px}.custom-theme .el-checkbox-group{font-size:0}.custom-theme .el-tag{background-color:rgba(38,39,41,.1);display:inline-block;padding:0 10px;height:32px;line-height:30px;font-size:12px;color:#262729;border-radius:4px;-webkit-box-sizing:border-box;box-sizing:border-box;border:1px solid rgba(38,39,41,.2);white-space:nowrap}.custom-theme .el-tag .el-icon-close{border-radius:50%;text-align:center;position:relative;cursor:pointer;font-size:12px;height:18px;width:18px;line-height:18px;vertical-align:middle;top:-1px;right:-5px;color:#262729}.custom-theme .el-tag .el-icon-close::before{display:block}.custom-theme .el-tag .el-icon-close:hover{background-color:#262729;color:#fff}.custom-theme .el-tag--info{background-color:rgba(10,118,164,.1);border-color:rgba(10,118,164,.2);color:#0a76a4}.custom-theme .el-tag--info.is-hit{border-color:#0a76a4}.custom-theme .el-tag--info .el-tag__close{color:#0a76a4}.custom-theme .el-tag--info .el-tag__close:hover{background-color:#0a76a4;color:#fff}.custom-theme .el-tag--success{background-color:rgba(64,145,103,.1);border-color:rgba(64,145,103,.2);color:#409167}.custom-theme .el-tag--success.is-hit{border-color:#409167}.custom-theme .el-tag--success .el-tag__close{color:#409167}.custom-theme .el-tag--success .el-tag__close:hover{background-color:#409167;color:#fff}.custom-theme .el-tag--warning{background-color:rgba(157,164,8,.1);border-color:rgba(157,164,8,.2);color:#9da408}.custom-theme .el-tag--warning.is-hit{border-color:#9da408}.custom-theme .el-tag--warning .el-tag__close{color:#9da408}.custom-theme .el-tag--warning .el-tag__close:hover{background-color:#9da408;color:#fff}.custom-theme .el-tag--danger{background-color:rgba(179,69,14,.1);border-color:rgba(179,69,14,.2);color:#b3450e}.custom-theme .el-tag--danger.is-hit{border-color:#b3450e}.custom-theme .el-tag--danger .el-tag__close{color:#b3450e}.custom-theme .el-tag--danger .el-tag__close:hover{background-color:#b3450e;color:#fff}.custom-theme .el-tag--medium{height:28px;line-height:26px}.custom-theme .el-tag--medium .el-icon-close{-webkit-transform:scale(.8);transform:scale(.8)}.custom-theme .el-tag--small{height:24px;padding:0 8px;line-height:22px}.custom-theme .el-tag--small .el-icon-close{-webkit-transform:scale(.8);transform:scale(.8)}.custom-theme .el-tag--mini{height:20px;padding:0 5px;line-height:19px}.custom-theme .el-tag--mini .el-icon-close{margin-left:-3px;-webkit-transform:scale(.7);transform:scale(.7)}.custom-theme .el-table{position:relative;overflow:hidden;-webkit-box-sizing:border-box;box-sizing:border-box;-webkit-box-flex:1;-ms-flex:1;flex:1;width:100%;max-width:100%;background-color:#fff;font-size:14px;color:#5a5e66}.custom-theme .el-table__empty-block{position:relative;min-height:60px;text-align:center;width:100%;height:100%}.custom-theme .el-table__empty-text{position:absolute;left:50%;top:50%;-webkit-transform:translate(-50%,-50%);transform:translate(-50%,-50%);color:color(#262729 s(16%) l(44%))}.custom-theme .el-table__expand-column .cell{padding:0;text-align:center}.custom-theme .el-table__expand-icon{position:relative;cursor:pointer;color:#666;font-size:12px;-webkit-transition:-webkit-transform .2s ease-in-out;transition:-webkit-transform .2s ease-in-out;transition:transform .2s ease-in-out;transition:transform .2s ease-in-out,-webkit-transform .2s ease-in-out;height:20px}.custom-theme .el-table__expand-icon--expanded{-webkit-transform:rotate(90deg);transform:rotate(90deg)}.custom-theme .el-table__expand-icon>.el-icon{position:absolute;left:50%;top:50%;margin-left:-5px;margin-top:-5px}.custom-theme .el-table__expanded-cell{background-color:#fff}.custom-theme .el-table__expanded-cell[class*=cell]{padding:20px 50px}.custom-theme .el-table__expanded-cell:hover{background-color:#f5f7fa!important}.custom-theme .el-table--fit{border-right:0;border-bottom:0}.custom-theme .el-table--fit td.gutter,.custom-theme .el-table--fit th.gutter{border-right-width:1px}.custom-theme .el-table thead{color:#878d99;font-weight:500}.custom-theme .el-table thead.is-group th{background:#f5f7fa}.custom-theme .el-table td,.custom-theme .el-table th{padding:12px 0;min-width:0;-webkit-box-sizing:border-box;box-sizing:border-box;text-overflow:ellipsis;vertical-align:middle;position:relative}.custom-theme .el-table td.is-center,.custom-theme .el-table th.is-center{text-align:center}.custom-theme .el-table td.is-left,.custom-theme .el-table th.is-left{text-align:left}.custom-theme .el-table td.is-right,.custom-theme .el-table th.is-right{text-align:right}.custom-theme .el-table td.gutter,.custom-theme .el-table th.gutter{width:15px;border-right-width:0;border-bottom-width:0;padding:0}.custom-theme .el-table td.is-hidden>*,.custom-theme .el-table th.is-hidden>*{visibility:hidden}.custom-theme .el-table--medium td,.custom-theme .el-table--medium th{padding:10px 0}.custom-theme .el-table--small{font-size:12px}.custom-theme .el-table--small td,.custom-theme .el-table--small th{padding:8px 0}.custom-theme .el-table--mini{font-size:12px}.custom-theme .el-table--mini td,.custom-theme .el-table--mini th{padding:6px 0}.custom-theme .el-table tr{background-color:#fff}.custom-theme .el-table tr input[type=checkbox]{margin:0}.custom-theme .el-table td,.custom-theme .el-table th.is-leaf{border-bottom:1px solid #e6ebf5}.custom-theme .el-table th.is-sortable{cursor:pointer}.custom-theme .el-table th{white-space:nowrap;overflow:hidden;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;text-align:left}.custom-theme .el-table th div{display:inline-block;padding-left:10px;padding-right:10px;line-height:40px;-webkit-box-sizing:border-box;box-sizing:border-box;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.custom-theme .el-table th>.cell{position:relative;word-wrap:normal;text-overflow:ellipsis;display:inline-block;vertical-align:middle;width:100%;-webkit-box-sizing:border-box;box-sizing:border-box}.custom-theme .el-table th>.cell.highlight{color:#262729}.custom-theme .el-table th.required>div::before{display:inline-block;content:"";width:8px;height:8px;border-radius:50%;background:#ff4d51;margin-right:5px;vertical-align:middle}.custom-theme .el-table td div{-webkit-box-sizing:border-box;box-sizing:border-box}.custom-theme .el-table td.gutter{width:0}.custom-theme .el-table .cell{-webkit-box-sizing:border-box;box-sizing:border-box;overflow:hidden;text-overflow:ellipsis;white-space:normal;word-break:break-all;line-height:23px;padding-left:10px;padding-right:10px}.custom-theme .el-table .cell.el-tooltip{white-space:nowrap;min-width:50px}.custom-theme .el-table td:first-child .cell,.custom-theme .el-table th:first-child .cell{padding-left:0}.custom-theme .el-table--border,.custom-theme .el-table--group{border:1px solid #e6ebf5}.custom-theme .el-table--border::after,.custom-theme .el-table--group::after,.custom-theme .el-table::before{content:'';position:absolute;background-color:#e6ebf5;z-index:1}.custom-theme .el-table--border::after,.custom-theme .el-table--group::after{top:0;right:0;width:1px;height:100%}.custom-theme .el-table::before{left:0;bottom:0;width:100%;height:1px}.custom-theme .el-table--border{border-right:none;border-bottom:none}.custom-theme .el-table--border td,.custom-theme .el-table--border th{border-right:1px solid #e6ebf5}.custom-theme .el-table--border td:first-child .cell,.custom-theme .el-table--border th:first-child .cell{padding-left:10px}.custom-theme .el-table--border .has-gutter td:nth-last-of-type(2),.custom-theme .el-table--border .has-gutter th:nth-last-of-type(2){border-right:none}.custom-theme .el-table--border th.gutter:last-of-type{border-bottom:1px solid #e6ebf5;border-bottom-width:1px}.custom-theme .el-table--border th{border-bottom:1px solid #e6ebf5}.custom-theme .el-table--hidden{visibility:hidden}.custom-theme .el-table__fixed,.custom-theme .el-table__fixed-right{position:absolute;top:0;left:0;overflow-x:hidden;-webkit-box-shadow:0 0 10px rgba(0,0,0,.12);box-shadow:0 0 10px rgba(0,0,0,.12)}.custom-theme .el-table__fixed-right::before,.custom-theme .el-table__fixed::before{content:'';position:absolute;left:0;bottom:0;width:100%;height:1px;background-color:#e6ebf5;z-index:4}.custom-theme .el-table__fixed-right-patch{position:absolute;top:-1px;right:0;background-color:#fff;border-bottom:1px solid #e6ebf5}.custom-theme .el-table__fixed-right{top:0;left:auto;right:0}.custom-theme .el-table__fixed-right .el-table__fixed-body-wrapper,.custom-theme .el-table__fixed-right .el-table__fixed-footer-wrapper,.custom-theme .el-table__fixed-right .el-table__fixed-header-wrapper{left:auto;right:0}.custom-theme .el-table__fixed-header-wrapper{position:absolute;left:0;top:0;z-index:3}.custom-theme .el-table__fixed-footer-wrapper{position:absolute;left:0;bottom:0;z-index:3}.custom-theme .el-table__fixed-footer-wrapper tbody td{border-top:1px solid #e6ebf5;background-color:#f5f7fa;color:#5a5e66}.custom-theme .el-table__fixed-body-wrapper{position:absolute;left:0;top:37px;overflow:hidden;z-index:3}.custom-theme .el-table__body-wrapper,.custom-theme .el-table__footer-wrapper,.custom-theme .el-table__header-wrapper{width:100%}.custom-theme .el-table__footer-wrapper{margin-top:-1px}.custom-theme .el-table__footer-wrapper td{border-top:1px solid #e6ebf5}.custom-theme .el-table__body,.custom-theme .el-table__footer,.custom-theme .el-table__header{table-layout:fixed}.custom-theme .el-table__footer-wrapper,.custom-theme .el-table__header-wrapper{overflow:hidden}.custom-theme .el-table__footer-wrapper tbody td,.custom-theme .el-table__header-wrapper tbody td{background-color:#f5f7fa;color:#5a5e66}.custom-theme .el-table__body-wrapper{overflow:auto;position:relative}.custom-theme .el-table__body-wrapper.is-scroll-none~.el-table__fixed,.custom-theme .el-table__body-wrapper.is-scroll-none~.el-table__fixed-right{-webkit-box-shadow:none;box-shadow:none}.custom-theme .el-table__body-wrapper.is-scroll-left~.el-table__fixed{-webkit-box-shadow:none;box-shadow:none}.custom-theme .el-table__body-wrapper.is-scroll-right~.el-table__fixed-right{-webkit-box-shadow:none;box-shadow:none}.custom-theme .el-table__body-wrapper .el-table--border.is-scroll-right~.el-table__fixed-right{border-left:1px solid #e6ebf5}.custom-theme .el-table__body-wrapper .el-table--border.is-scroll-left~.el-table__fixed{border-right:1px solid #e6ebf5}.custom-theme .el-table .caret-wrapper{position:relative;display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;height:13px;width:24px;cursor:pointer;overflow:initial}.custom-theme .el-table .sort-caret{color:#0a76a4;width:14px;overflow:hidden;font-size:13px}.custom-theme .el-table .ascending .sort-caret.ascending{color:#262729}.custom-theme .el-table .descending .sort-caret.descending{color:#262729}.custom-theme .el-table .hidden-columns{visibility:hidden;position:absolute;z-index:-1}.custom-theme .el-table--striped .el-table__body tr.el-table__row--striped td{background:#fafafa}.custom-theme .el-table--striped .el-table__body tr.el-table__row--striped.current-row td{background-color:#e9e9ea}.custom-theme .el-table__body tr.hover-row.current-row>td,.custom-theme .el-table__body tr.hover-row.el-table__row--striped.current-row>td,.custom-theme .el-table__body tr.hover-row.el-table__row--striped>td,.custom-theme .el-table__body tr.hover-row>td{background-color:#e9e9ea}.custom-theme .el-table__body tr.current-row>td{background-color:#e9e9ea}.custom-theme .el-table__column-resize-proxy{position:absolute;left:200px;top:0;bottom:0;width:0;border-left:1px solid #e6ebf5;z-index:10}.custom-theme .el-table__column-filter-trigger{display:inline-block;line-height:34px;cursor:pointer}.custom-theme .el-table__column-filter-trigger i{color:#0a76a4;font-size:12px;-webkit-transform:scale(.75);transform:scale(.75)}.custom-theme .el-table--enable-row-transition .el-table__body td{-webkit-transition:background-color .25s ease;transition:background-color .25s ease}.custom-theme .el-table--enable-row-hover .el-table__body tr:hover>td{background-color:#f5f7fa}.custom-theme .el-table--fluid-height .el-table__fixed,.custom-theme .el-table--fluid-height .el-table__fixed-right{bottom:0;overflow:hidden}.custom-theme .el-checkbox{color:#5a5e66;font-weight:500;font-size:14px;position:relative;cursor:pointer;display:inline-block;white-space:nowrap;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.custom-theme .el-checkbox.is-bordered{padding:9px 20px 9px 10px;border-radius:4px;border:1px solid #d8dce5}.custom-theme .el-checkbox.is-bordered.is-checked{border-color:#262729}.custom-theme .el-checkbox.is-bordered.is-disabled{border-color:#e6ebf5;cursor:not-allowed}.custom-theme .el-checkbox.is-bordered+.el-checkbox.is-bordered{margin-left:10px}.custom-theme .el-checkbox.is-bordered.el-checkbox--medium{padding:7px 20px 7px 10px;border-radius:4px}.custom-theme .el-checkbox.is-bordered.el-checkbox--medium .el-checkbox__label{line-height:17px;font-size:14px}.custom-theme .el-checkbox.is-bordered.el-checkbox--medium .el-checkbox__inner{height:14px;width:14px}.custom-theme .el-checkbox.is-bordered.el-checkbox--small{padding:3px 15px 7px 10px;border-radius:3px}.custom-theme .el-checkbox.is-bordered.el-checkbox--small .el-checkbox__label{line-height:15px;font-size:12px}.custom-theme .el-checkbox.is-bordered.el-checkbox--small .el-checkbox__inner{height:12px;width:12px}.custom-theme .el-checkbox.is-bordered.el-checkbox--small .el-checkbox__inner::after{height:6px;width:2px}.custom-theme .el-checkbox.is-bordered.el-checkbox--mini{padding:1px 15px 5px 10px;border-radius:3px}.custom-theme .el-checkbox.is-bordered.el-checkbox--mini .el-checkbox__label{line-height:12px;font-size:12px}.custom-theme .el-checkbox.is-bordered.el-checkbox--mini .el-checkbox__inner{height:12px;width:12px}.custom-theme .el-checkbox.is-bordered.el-checkbox--mini .el-checkbox__inner::after{height:6px;width:2px}.custom-theme .el-checkbox__input{white-space:nowrap;cursor:pointer;outline:0;display:inline-block;line-height:1;position:relative;vertical-align:middle}.custom-theme .el-checkbox__input.is-disabled .el-checkbox__inner{background-color:#edf2fc;border-color:#d8dce5;cursor:not-allowed}.custom-theme .el-checkbox__input.is-disabled .el-checkbox__inner::after{cursor:not-allowed;border-color:#b4bccc}.custom-theme .el-checkbox__input.is-disabled .el-checkbox__inner+.el-checkbox__label{cursor:not-allowed}.custom-theme .el-checkbox__input.is-disabled.is-checked .el-checkbox__inner{background-color:#edf2fc;border-color:#d8dce5}.custom-theme .el-checkbox__input.is-disabled.is-checked .el-checkbox__inner::after{border-color:#b4bccc}.custom-theme .el-checkbox__input.is-disabled.is-indeterminate .el-checkbox__inner{background-color:#edf2fc;border-color:#d8dce5}.custom-theme .el-checkbox__input.is-disabled.is-indeterminate .el-checkbox__inner::before{background-color:#b4bccc;border-color:#b4bccc}.custom-theme .el-checkbox__input.is-disabled+span.el-checkbox__label{color:#b4bccc;cursor:not-allowed}.custom-theme .el-checkbox__input.is-checked .el-checkbox__inner{background-color:#262729;border-color:#262729}.custom-theme .el-checkbox__input.is-checked .el-checkbox__inner::after{-webkit-transform:rotate(45deg) scaleY(1);transform:rotate(45deg) scaleY(1)}.custom-theme .el-checkbox__input.is-checked+.el-checkbox__label{color:#262729}.custom-theme .el-checkbox__input.is-focus .el-checkbox__inner{border-color:#262729}.custom-theme .el-checkbox__input.is-indeterminate .el-checkbox__inner{background-color:#262729;border-color:#262729}.custom-theme .el-checkbox__input.is-indeterminate .el-checkbox__inner::before{content:'';position:absolute;display:block;background-color:#fff;height:2px;-webkit-transform:scale(.5);transform:scale(.5);left:0;right:0;top:5px}.custom-theme .el-checkbox__input.is-indeterminate .el-checkbox__inner::after{display:none}.custom-theme .el-checkbox__inner{display:inline-block;position:relative;border:1px solid #d8dce5;border-radius:2px;-webkit-box-sizing:border-box;box-sizing:border-box;width:14px;height:14px;background-color:#fff;z-index:1;-webkit-transition:border-color .25s cubic-bezier(.71,-.46,.29,1.46),background-color .25s cubic-bezier(.71,-.46,.29,1.46);transition:border-color .25s cubic-bezier(.71,-.46,.29,1.46),background-color .25s cubic-bezier(.71,-.46,.29,1.46)}.custom-theme .el-checkbox__inner:hover{border-color:#262729}.custom-theme .el-checkbox__inner::after{-webkit-box-sizing:content-box;box-sizing:content-box;content:"";border:1px solid #fff;border-left:0;border-top:0;height:7px;left:4px;position:absolute;top:1px;-webkit-transform:rotate(45deg) scaleY(0);transform:rotate(45deg) scaleY(0);width:3px;-webkit-transition:-webkit-transform .15s cubic-bezier(.71,-.46,.88,.6) 50ms;transition:-webkit-transform .15s cubic-bezier(.71,-.46,.88,.6) 50ms;transition:transform .15s cubic-bezier(.71,-.46,.88,.6) 50ms;transition:transform .15s cubic-bezier(.71,-.46,.88,.6) 50ms,-webkit-transform .15s cubic-bezier(.71,-.46,.88,.6) 50ms;-webkit-transform-origin:center;transform-origin:center}.custom-theme .el-checkbox__original{opacity:0;outline:0;position:absolute;margin:0;width:0;height:0;left:-999px}.custom-theme .el-checkbox__label{display:inline-block;padding-left:10px;line-height:19px;font-size:14px}.custom-theme .el-checkbox+.el-checkbox{margin-left:30px}.custom-theme .el-checkbox-button{position:relative;display:inline-block}.custom-theme .el-checkbox-button__inner{display:inline-block;line-height:1;font-weight:500;white-space:nowrap;vertical-align:middle;cursor:pointer;background:#fff;border:1px solid #d8dce5;border-left:0;color:#5a5e66;-webkit-appearance:none;text-align:center;-webkit-box-sizing:border-box;box-sizing:border-box;outline:0;margin:0;position:relative;-webkit-transition:all .3s cubic-bezier(.645,.045,.355,1);transition:all .3s cubic-bezier(.645,.045,.355,1);-moz-user-select:none;-webkit-user-select:none;-ms-user-select:none;padding:12px 20px;font-size:14px;border-radius:0}.custom-theme .el-checkbox-button__inner.is-round{padding:12px 20px}.custom-theme .el-checkbox-button__inner:hover{color:#262729}.custom-theme .el-checkbox-button__inner [class*=el-icon-]{line-height:.9}.custom-theme .el-checkbox-button__inner [class*=el-icon-]+span{margin-left:5px}.custom-theme .el-checkbox-button__original{opacity:0;outline:0;position:absolute;margin:0;left:-999px}.custom-theme .el-checkbox-button.is-checked .el-checkbox-button__inner{color:#fff;background-color:#262729;border-color:#262729;-webkit-box-shadow:-1px 0 0 0 #7d7d7f;box-shadow:-1px 0 0 0 #7d7d7f}.custom-theme .el-checkbox-button.is-disabled .el-checkbox-button__inner{color:#b4bccc;cursor:not-allowed;background-image:none;background-color:#fff;border-color:#e6ebf5;-webkit-box-shadow:none;box-shadow:none}.custom-theme .el-checkbox-button:first-child .el-checkbox-button__inner{border-left:1px solid #d8dce5;border-radius:4px 0 0 4px;-webkit-box-shadow:none!important;box-shadow:none!important}.custom-theme .el-checkbox-button.is-focus .el-checkbox-button__inner{border-color:#262729}.custom-theme .el-checkbox-button:last-child .el-checkbox-button__inner{border-radius:0 4px 4px 0}.custom-theme .el-checkbox-button--medium .el-checkbox-button__inner{padding:10px 20px;font-size:14px;border-radius:0}.custom-theme .el-checkbox-button--medium .el-checkbox-button__inner.is-round{padding:10px 20px}.custom-theme .el-checkbox-button--small .el-checkbox-button__inner{padding:9px 15px;font-size:12px;border-radius:0}.custom-theme .el-checkbox-button--small .el-checkbox-button__inner.is-round{padding:9px 15px}.custom-theme .el-checkbox-button--mini .el-checkbox-button__inner{padding:7px 15px;font-size:12px;border-radius:0}.custom-theme .el-checkbox-button--mini .el-checkbox-button__inner.is-round{padding:7px 15px}.custom-theme .el-checkbox-group{font-size:0}.custom-theme .el-tag{background-color:rgba(38,39,41,.1);display:inline-block;padding:0 10px;height:32px;line-height:30px;font-size:12px;color:#262729;border-radius:4px;-webkit-box-sizing:border-box;box-sizing:border-box;border:1px solid rgba(38,39,41,.2);white-space:nowrap}.custom-theme .el-tag .el-icon-close{border-radius:50%;text-align:center;position:relative;cursor:pointer;font-size:12px;height:18px;width:18px;line-height:18px;vertical-align:middle;top:-1px;right:-5px;color:#262729}.custom-theme .el-tag .el-icon-close::before{display:block}.custom-theme .el-tag .el-icon-close:hover{background-color:#262729;color:#fff}.custom-theme .el-tag--info{background-color:rgba(10,118,164,.1);border-color:rgba(10,118,164,.2);color:#0a76a4}.custom-theme .el-tag--info.is-hit{border-color:#0a76a4}.custom-theme .el-tag--info .el-tag__close{color:#0a76a4}.custom-theme .el-tag--info .el-tag__close:hover{background-color:#0a76a4;color:#fff}.custom-theme .el-tag--success{background-color:rgba(64,145,103,.1);border-color:rgba(64,145,103,.2);color:#409167}.custom-theme .el-tag--success.is-hit{border-color:#409167}.custom-theme .el-tag--success .el-tag__close{color:#409167}.custom-theme .el-tag--success .el-tag__close:hover{background-color:#409167;color:#fff}.custom-theme .el-tag--warning{background-color:rgba(157,164,8,.1);border-color:rgba(157,164,8,.2);color:#9da408}.custom-theme .el-tag--warning.is-hit{border-color:#9da408}.custom-theme .el-tag--warning .el-tag__close{color:#9da408}.custom-theme .el-tag--warning .el-tag__close:hover{background-color:#9da408;color:#fff}.custom-theme .el-tag--danger{background-color:rgba(179,69,14,.1);border-color:rgba(179,69,14,.2);color:#b3450e}.custom-theme .el-tag--danger.is-hit{border-color:#b3450e}.custom-theme .el-tag--danger .el-tag__close{color:#b3450e}.custom-theme .el-tag--danger .el-tag__close:hover{background-color:#b3450e;color:#fff}.custom-theme .el-tag--medium{height:28px;line-height:26px}.custom-theme .el-tag--medium .el-icon-close{-webkit-transform:scale(.8);transform:scale(.8)}.custom-theme .el-tag--small{height:24px;padding:0 8px;line-height:22px}.custom-theme .el-tag--small .el-icon-close{-webkit-transform:scale(.8);transform:scale(.8)}.custom-theme .el-tag--mini{height:20px;padding:0 5px;line-height:19px}.custom-theme .el-tag--mini .el-icon-close{margin-left:-3px;-webkit-transform:scale(.7);transform:scale(.7)}.custom-theme .el-table-column--selection .cell{padding-left:14px;padding-right:14px}.custom-theme .el-table-filter{border:solid 1px #e6ebf5;border-radius:2px;background-color:#fff;-webkit-box-shadow:0 2px 12px 0 rgba(0,0,0,.1);box-shadow:0 2px 12px 0 rgba(0,0,0,.1);-webkit-box-sizing:border-box;box-sizing:border-box;margin:2px 0}.custom-theme .el-table-filter__list{padding:5px 0;margin:0;list-style:none;min-width:100px}.custom-theme .el-table-filter__list-item{line-height:36px;padding:0 10px;cursor:pointer;font-size:14px}.custom-theme .el-table-filter__list-item:hover{background-color:#e9e9ea;color:#515254}.custom-theme .el-table-filter__list-item.is-active{background-color:#262729;color:#fff}.custom-theme .el-table-filter__content{min-width:100px}.custom-theme .el-table-filter__bottom{border-top:1px solid #e6ebf5;padding:8px}.custom-theme .el-table-filter__bottom button{background:0 0;border:none;color:#5a5e66;cursor:pointer;font-size:13px;padding:0 3px}.custom-theme .el-table-filter__bottom button:hover{color:#262729}.custom-theme .el-table-filter__bottom button:focus{outline:0}.custom-theme .el-table-filter__bottom button.is-disabled{color:#b4bccc;cursor:not-allowed}.custom-theme .el-table-filter__checkbox-group{padding:10px}.custom-theme .el-table-filter__checkbox-group label.el-checkbox{display:block;margin-bottom:8px;margin-left:5px}.custom-theme .el-table-filter__checkbox-group .el-checkbox:last-child{margin-bottom:0}.custom-theme .el-date-table{font-size:12px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.custom-theme .el-date-table.is-week-mode .el-date-table__row:hover div{background-color:#edf2fc}.custom-theme .el-date-table.is-week-mode .el-date-table__row:hover td.available:hover{color:#5a5e66}.custom-theme .el-date-table.is-week-mode .el-date-table__row:hover td:first-child div{margin-left:5px;border-top-left-radius:15px;border-bottom-left-radius:15px}.custom-theme .el-date-table.is-week-mode .el-date-table__row:hover td:last-child div{margin-right:5px;border-top-right-radius:15px;border-bottom-right-radius:15px}.custom-theme .el-date-table.is-week-mode .el-date-table__row.current div{background-color:#edf2fc}.custom-theme .el-date-table td{width:32px;height:30px;padding:4px 0;-webkit-box-sizing:border-box;box-sizing:border-box;text-align:center;cursor:pointer;position:relative}.custom-theme .el-date-table td div{height:30px;padding:3px 0;-webkit-box-sizing:border-box;box-sizing:border-box}.custom-theme .el-date-table td span{width:24px;height:24px;display:block;margin:0 auto;line-height:24px;position:absolute;left:50%;-webkit-transform:translateX(-50%);transform:translateX(-50%);border-radius:50%}.custom-theme .el-date-table td.next-month,.custom-theme .el-date-table td.prev-month{color:#b4bccc}.custom-theme .el-date-table td.today{position:relative}.custom-theme .el-date-table td.today span{color:#262729}.custom-theme .el-date-table td.today.end-date span,.custom-theme .el-date-table td.today.start-date span{color:#fff}.custom-theme .el-date-table td.available:hover{color:#262729}.custom-theme .el-date-table td.in-range div{background-color:#edf2fc}.custom-theme .el-date-table td.in-range div:hover{background-color:#edf2fc}.custom-theme .el-date-table td.current:not(.disabled) span{color:#fff;background-color:#262729}.custom-theme .el-date-table td.end-date div,.custom-theme .el-date-table td.start-date div{color:#fff}.custom-theme .el-date-table td.end-date span,.custom-theme .el-date-table td.start-date span{background-color:#262729}.custom-theme .el-date-table td.start-date div{margin-left:5px;border-top-left-radius:15px;border-bottom-left-radius:15px}.custom-theme .el-date-table td.end-date div{margin-right:5px;border-top-right-radius:15px;border-bottom-right-radius:15px}.custom-theme .el-date-table td.disabled div{background-color:#f5f7fa;opacity:1;cursor:not-allowed;color:#b4bccc}.custom-theme .el-date-table td.week{font-size:80%;color:#5a5e66}.custom-theme .el-date-table th{padding:5px;color:#5a5e66;font-weight:400;border-bottom:solid 1px #e6ebf5}.custom-theme .el-month-table{font-size:12px;margin:-1px;border-collapse:collapse}.custom-theme .el-month-table td{text-align:center;padding:20px 3px;cursor:pointer}.custom-theme .el-month-table td.disabled .cell{background-color:#f5f7fa;cursor:not-allowed;color:#b4bccc}.custom-theme .el-month-table td.disabled .cell:hover{color:#b4bccc}.custom-theme .el-month-table td .cell{width:48px;height:32px;display:block;line-height:32px;color:#5a5e66;margin:0 auto}.custom-theme .el-month-table td .cell:hover{color:#262729}.custom-theme .el-month-table td.current:not(.disabled) .cell{color:#262729}.custom-theme .el-year-table{font-size:12px;margin:-1px;border-collapse:collapse}.custom-theme .el-year-table .el-icon{color:#2d2f33}.custom-theme .el-year-table td{text-align:center;padding:20px 3px;cursor:pointer}.custom-theme .el-year-table td.disabled .cell{background-color:#f5f7fa;cursor:not-allowed;color:#b4bccc}.custom-theme .el-year-table td.disabled .cell:hover{color:#b4bccc}.custom-theme .el-year-table td .cell{width:48px;height:32px;display:block;line-height:32px;color:#5a5e66;margin:0 auto}.custom-theme .el-year-table td .cell:hover{color:#262729}.custom-theme .el-year-table td.current:not(.disabled) .cell{color:#262729}.custom-theme .el-time-spinner.has-seconds .el-time-spinner__wrapper{width:33.3%}.custom-theme .el-time-spinner.has-seconds .el-time-spinner__wrapper:nth-child(2){margin-left:1%}.custom-theme .el-time-spinner__wrapper{max-height:190px;overflow:auto;display:inline-block;width:50%;vertical-align:top;position:relative}.custom-theme .el-time-spinner__wrapper .el-scrollbar__wrap:not(.el-scrollbar__wrap--hidden-default){padding-bottom:15px}.custom-theme .el-time-spinner__wrapper.is-arrow{-webkit-box-sizing:border-box;box-sizing:border-box;text-align:center;overflow:hidden}.custom-theme .el-time-spinner__wrapper.is-arrow .el-time-spinner__list{-webkit-transform:translateY(-32px);transform:translateY(-32px)}.custom-theme .el-time-spinner__wrapper.is-arrow .el-time-spinner__item:hover:not(.disabled):not(.active){background:#fff;cursor:default}.custom-theme .el-time-spinner__arrow{font-size:12px;color:#878d99;position:absolute;left:0;width:100%;z-index:1;text-align:center;height:30px;line-height:30px;cursor:pointer}.custom-theme .el-time-spinner__arrow:hover{color:#262729}.custom-theme .el-time-spinner__arrow.el-icon-arrow-up{top:10px}.custom-theme .el-time-spinner__arrow.el-icon-arrow-down{bottom:10px}.custom-theme .el-time-spinner__input.el-input{width:70%}.custom-theme .el-time-spinner__input.el-input .el-input__inner{padding:0;text-align:center}.custom-theme .el-time-spinner__list{padding:0;margin:0;list-style:none;text-align:center}.custom-theme .el-time-spinner__list::after,.custom-theme .el-time-spinner__list::before{content:'';display:block;width:100%;height:80px}.custom-theme .el-time-spinner__item{height:32px;line-height:32px;font-size:12px;color:#5a5e66}.custom-theme .el-time-spinner__item:hover:not(.disabled):not(.active){background:#f5f7fa;cursor:pointer}.custom-theme .el-time-spinner__item.active:not(.disabled){color:#2d2f33;font-weight:700}.custom-theme .el-time-spinner__item.disabled{color:#b4bccc;cursor:not-allowed}.custom-theme .fade-in-linear-enter-active,.custom-theme .fade-in-linear-leave-active{-webkit-transition:opacity .2s linear;transition:opacity .2s linear}.custom-theme .fade-in-linear-enter,.custom-theme .fade-in-linear-leave,.custom-theme .fade-in-linear-leave-active{opacity:0}.custom-theme .el-fade-in-linear-enter-active,.custom-theme .el-fade-in-linear-leave-active{-webkit-transition:opacity .2s linear;transition:opacity .2s linear}.custom-theme .el-fade-in-linear-enter,.custom-theme .el-fade-in-linear-leave,.custom-theme .el-fade-in-linear-leave-active{opacity:0}.custom-theme .el-fade-in-enter-active,.custom-theme .el-fade-in-leave-active{-webkit-transition:all .3s cubic-bezier(.55,0,.1,1);transition:all .3s cubic-bezier(.55,0,.1,1)}.custom-theme .el-fade-in-enter,.custom-theme .el-fade-in-leave-active{opacity:0}.custom-theme .el-zoom-in-center-enter-active,.custom-theme .el-zoom-in-center-leave-active{-webkit-transition:all .3s cubic-bezier(.55,0,.1,1);transition:all .3s cubic-bezier(.55,0,.1,1)}.custom-theme .el-zoom-in-center-enter,.custom-theme .el-zoom-in-center-leave-active{opacity:0;-webkit-transform:scaleX(0);transform:scaleX(0)}.custom-theme .el-zoom-in-top-enter-active,.custom-theme .el-zoom-in-top-leave-active{opacity:1;-webkit-transform:scaleY(1);transform:scaleY(1);-webkit-transition:opacity .3s cubic-bezier(.23,1,.32,1) .1s,-webkit-transform .3s cubic-bezier(.23,1,.32,1) .1s;transition:opacity .3s cubic-bezier(.23,1,.32,1) .1s,-webkit-transform .3s cubic-bezier(.23,1,.32,1) .1s;transition:transform .3s cubic-bezier(.23,1,.32,1) .1s,opacity .3s cubic-bezier(.23,1,.32,1) .1s;transition:transform .3s cubic-bezier(.23,1,.32,1) .1s,opacity .3s cubic-bezier(.23,1,.32,1) .1s,-webkit-transform .3s cubic-bezier(.23,1,.32,1) .1s;-webkit-transform-origin:center top;transform-origin:center top}.custom-theme .el-zoom-in-top-enter,.custom-theme .el-zoom-in-top-leave-active{opacity:0;-webkit-transform:scaleY(0);transform:scaleY(0)}.custom-theme .el-zoom-in-bottom-enter-active,.custom-theme .el-zoom-in-bottom-leave-active{opacity:1;-webkit-transform:scaleY(1);transform:scaleY(1);-webkit-transition:opacity .3s cubic-bezier(.23,1,.32,1) .1s,-webkit-transform .3s cubic-bezier(.23,1,.32,1) .1s;transition:opacity .3s cubic-bezier(.23,1,.32,1) .1s,-webkit-transform .3s cubic-bezier(.23,1,.32,1) .1s;transition:transform .3s cubic-bezier(.23,1,.32,1) .1s,opacity .3s cubic-bezier(.23,1,.32,1) .1s;transition:transform .3s cubic-bezier(.23,1,.32,1) .1s,opacity .3s cubic-bezier(.23,1,.32,1) .1s,-webkit-transform .3s cubic-bezier(.23,1,.32,1) .1s;-webkit-transform-origin:center bottom;transform-origin:center bottom}.custom-theme .el-zoom-in-bottom-enter,.custom-theme .el-zoom-in-bottom-leave-active{opacity:0;-webkit-transform:scaleY(0);transform:scaleY(0)}.custom-theme .el-zoom-in-left-enter-active,.custom-theme .el-zoom-in-left-leave-active{opacity:1;-webkit-transform:scale(1,1);transform:scale(1,1);-webkit-transition:opacity .3s cubic-bezier(.23,1,.32,1) .1s,-webkit-transform .3s cubic-bezier(.23,1,.32,1) .1s;transition:opacity .3s cubic-bezier(.23,1,.32,1) .1s,-webkit-transform .3s cubic-bezier(.23,1,.32,1) .1s;transition:transform .3s cubic-bezier(.23,1,.32,1) .1s,opacity .3s cubic-bezier(.23,1,.32,1) .1s;transition:transform .3s cubic-bezier(.23,1,.32,1) .1s,opacity .3s cubic-bezier(.23,1,.32,1) .1s,-webkit-transform .3s cubic-bezier(.23,1,.32,1) .1s;-webkit-transform-origin:top left;transform-origin:top left}.custom-theme .el-zoom-in-left-enter,.custom-theme .el-zoom-in-left-leave-active{opacity:0;-webkit-transform:scale(.45,.45);transform:scale(.45,.45)}.custom-theme .collapse-transition{-webkit-transition:.3s height ease-in-out,.3s padding-top ease-in-out,.3s padding-bottom ease-in-out;transition:.3s height ease-in-out,.3s padding-top ease-in-out,.3s padding-bottom ease-in-out}.custom-theme .horizontal-collapse-transition{-webkit-transition:.3s width ease-in-out,.3s padding-left ease-in-out,.3s padding-right ease-in-out;transition:.3s width ease-in-out,.3s padding-left ease-in-out,.3s padding-right ease-in-out}.custom-theme .el-list-enter-active,.custom-theme .el-list-leave-active{-webkit-transition:all 1s;transition:all 1s}.custom-theme .el-list-enter,.custom-theme .el-list-leave-active{opacity:0;-webkit-transform:translateY(-30px);transform:translateY(-30px)}.custom-theme .el-opacity-transition{-webkit-transition:opacity .3s cubic-bezier(.55,0,.1,1);transition:opacity .3s cubic-bezier(.55,0,.1,1)}.custom-theme .el-date-editor{position:relative;display:inline-block;text-align:left}.custom-theme .el-date-editor.el-input,.custom-theme .el-date-editor.el-input__inner{width:220px}.custom-theme .el-date-editor--daterange.el-input,.custom-theme .el-date-editor--daterange.el-input__inner,.custom-theme .el-date-editor--timerange.el-input,.custom-theme .el-date-editor--timerange.el-input__inner{width:350px}.custom-theme .el-date-editor--datetimerange.el-input,.custom-theme .el-date-editor--datetimerange.el-input__inner{width:400px}.custom-theme .el-date-editor .el-range__icon{font-size:14px;margin-left:-5px;color:#b4bccc;float:left;line-height:32px}.custom-theme .el-date-editor .el-range-input{-webkit-appearance:none;-moz-appearance:none;appearance:none;border:none;outline:0;display:inline-block;height:100%;margin:0;padding:0;width:39%;text-align:center;font-size:14px;color:#5a5e66}.custom-theme .el-date-editor .el-range-input::-webkit-input-placeholder{color:#b4bccc}.custom-theme .el-date-editor .el-range-input:-ms-input-placeholder{color:#b4bccc}.custom-theme .el-date-editor .el-range-input::-ms-input-placeholder{color:#b4bccc}.custom-theme .el-date-editor .el-range-input::placeholder{color:#b4bccc}.custom-theme .el-date-editor .el-range-separator{display:inline-block;height:100%;padding:0 5px;margin:0;text-align:center;line-height:32px;font-size:14px;width:5%;color:#2d2f33}.custom-theme .el-date-editor .el-range__close-icon{font-size:14px;color:#b4bccc;width:25px;display:inline-block;float:right;line-height:32px}.custom-theme .el-range-editor.el-input__inner{padding:3px 10px}.custom-theme .el-range-editor.is-active{border-color:#262729}.custom-theme .el-range-editor.is-active:hover{border-color:#262729}.custom-theme .el-range-editor--medium.el-input__inner{height:36px}.custom-theme .el-range-editor--medium .el-range-separator{line-height:28px;font-size:14px}.custom-theme .el-range-editor--medium .el-range-input{font-size:14px}.custom-theme .el-range-editor--medium .el-range__close-icon,.custom-theme .el-range-editor--medium .el-range__icon{line-height:28px}.custom-theme .el-range-editor--small.el-input__inner{height:32px}.custom-theme .el-range-editor--small .el-range-separator{line-height:24px;font-size:13px}.custom-theme .el-range-editor--small .el-range-input{font-size:13px}.custom-theme .el-range-editor--small .el-range__close-icon,.custom-theme .el-range-editor--small .el-range__icon{line-height:24px}.custom-theme .el-range-editor--mini.el-input__inner{height:28px}.custom-theme .el-range-editor--mini .el-range-separator{line-height:20px;font-size:12px}.custom-theme .el-range-editor--mini .el-range-input{font-size:12px}.custom-theme .el-range-editor--mini .el-range__close-icon,.custom-theme .el-range-editor--mini .el-range__icon{line-height:20px}.custom-theme .el-range-editor.is-disabled{background-color:#f5f7fa;border-color:#dfe4ed;color:#b4bccc;cursor:not-allowed}.custom-theme .el-range-editor.is-disabled:focus,.custom-theme .el-range-editor.is-disabled:hover{border-color:#dfe4ed}.custom-theme .el-range-editor.is-disabled input{background-color:#f5f7fa;color:#b4bccc;cursor:not-allowed}.custom-theme .el-range-editor.is-disabled input::-webkit-input-placeholder{color:#b4bccc}.custom-theme .el-range-editor.is-disabled input:-ms-input-placeholder{color:#b4bccc}.custom-theme .el-range-editor.is-disabled input::-ms-input-placeholder{color:#b4bccc}.custom-theme .el-range-editor.is-disabled input::placeholder{color:#b4bccc}.custom-theme .el-range-editor.is-disabled .el-range-separator{color:#b4bccc}.custom-theme .el-picker-panel{color:#5a5e66;border:1px solid #dfe4ed;-webkit-box-shadow:0 2px 12px 0 rgba(0,0,0,.1);box-shadow:0 2px 12px 0 rgba(0,0,0,.1);background:#fff;border-radius:4px;line-height:30px;margin:5px 0}.custom-theme .el-picker-panel__body-wrapper::after,.custom-theme .el-picker-panel__body::after{content:"";display:table;clear:both}.custom-theme .el-picker-panel__content{position:relative;margin:15px}.custom-theme .el-picker-panel__footer{border-top:1px solid #e4e4e4;padding:4px;text-align:right;background-color:#fff;position:relative;font-size:0}.custom-theme .el-picker-panel__shortcut{display:block;width:100%;border:0;background-color:transparent;line-height:28px;font-size:14px;color:#5a5e66;padding-left:12px;text-align:left;outline:0;cursor:pointer}.custom-theme .el-picker-panel__shortcut:hover{color:#262729}.custom-theme .el-picker-panel__shortcut.active{background-color:#e6f1fe;color:#262729}.custom-theme .el-picker-panel__btn{border:1px solid #dcdcdc;color:#333;line-height:24px;border-radius:2px;padding:0 20px;cursor:pointer;background-color:transparent;outline:0;font-size:12px}.custom-theme .el-picker-panel__btn[disabled]{color:#ccc;cursor:not-allowed}.custom-theme .el-picker-panel__icon-btn{font-size:12px;color:#2d2f33;border:0;background:0 0;cursor:pointer;outline:0;margin-top:8px}.custom-theme .el-picker-panel__icon-btn:hover{color:#262729}.custom-theme .el-picker-panel__icon-btn.is-disabled{color:#bbb}.custom-theme .el-picker-panel__icon-btn.is-disabled:hover{cursor:not-allowed}.custom-theme .el-picker-panel__link-btn{vertical-align:middle}.custom-theme .el-picker-panel .popper__arrow{-webkit-transform:translateX(-400%);transform:translateX(-400%)}.custom-theme .el-picker-panel [slot=sidebar],.custom-theme .el-picker-panel__sidebar{position:absolute;top:0;bottom:0;width:110px;border-right:1px solid #e4e4e4;-webkit-box-sizing:border-box;box-sizing:border-box;padding-top:6px;background-color:#fff;overflow:auto}.custom-theme .el-picker-panel [slot=sidebar]+.el-picker-panel__body,.custom-theme .el-picker-panel__sidebar+.el-picker-panel__body{margin-left:110px}.custom-theme .el-date-picker{width:322px}.custom-theme .el-date-picker.has-sidebar.has-time{width:434px}.custom-theme .el-date-picker.has-sidebar{width:438px}.custom-theme .el-date-picker.has-time .el-picker-panel__body-wrapper{position:relative}.custom-theme .el-date-picker .el-picker-panel__content{width:292px}.custom-theme .el-date-picker table{table-layout:fixed;width:100%}.custom-theme .el-date-picker__editor-wrap{position:relative;display:table-cell;padding:0 5px}.custom-theme .el-date-picker__time-header{position:relative;border-bottom:1px solid #e4e4e4;font-size:12px;padding:8px 5px 5px 5px;display:table;width:100%;-webkit-box-sizing:border-box;box-sizing:border-box}.custom-theme .el-date-picker__header{margin:12px;text-align:center}.custom-theme .el-date-picker__header--bordered{margin-bottom:0;padding-bottom:12px;border-bottom:solid 1px #e6ebf5}.custom-theme .el-date-picker__header--bordered+.el-picker-panel__content{margin-top:0}.custom-theme .el-date-picker__header-label{font-size:16px;font-weight:500;padding:0 5px;line-height:22px;text-align:center;cursor:pointer;color:#5a5e66}.custom-theme .el-date-picker__header-label:hover{color:#262729}.custom-theme .el-date-picker__header-label.active{color:#262729}.custom-theme .el-date-picker__prev-btn{float:left}.custom-theme .el-date-picker__next-btn{float:right}.custom-theme .el-date-picker__time-wrap{padding:10px;text-align:center}.custom-theme .el-date-picker__time-label{float:left;cursor:pointer;line-height:30px;margin-left:10px}.custom-theme .el-date-range-picker{width:646px}.custom-theme .el-date-range-picker.has-sidebar{width:756px}.custom-theme .el-date-range-picker table{table-layout:fixed;width:100%}.custom-theme .el-date-range-picker .el-picker-panel__body{min-width:513px}.custom-theme .el-date-range-picker .el-picker-panel__content{margin:0}.custom-theme .el-date-range-picker__header{position:relative;text-align:center;height:28px}.custom-theme .el-date-range-picker__header [class*=arrow-left]{float:left}.custom-theme .el-date-range-picker__header [class*=arrow-right]{float:right}.custom-theme .el-date-range-picker__header div{font-size:16px;font-weight:500;margin-right:50px}.custom-theme .el-date-range-picker__content{float:left;width:50%;-webkit-box-sizing:border-box;box-sizing:border-box;margin:0;padding:16px}.custom-theme .el-date-range-picker__content.is-left{border-right:1px solid #e4e4e4}.custom-theme .el-date-range-picker__content.is-right .el-date-range-picker__header div{margin-left:50px;margin-right:50px}.custom-theme .el-date-range-picker__editors-wrap{-webkit-box-sizing:border-box;box-sizing:border-box;display:table-cell}.custom-theme .el-date-range-picker__editors-wrap.is-right{text-align:right}.custom-theme .el-date-range-picker__time-header{position:relative;border-bottom:1px solid #e4e4e4;font-size:12px;padding:8px 5px 5px 5px;display:table;width:100%;-webkit-box-sizing:border-box;box-sizing:border-box}.custom-theme .el-date-range-picker__time-header>.el-icon-arrow-right{font-size:20px;vertical-align:middle;display:table-cell;color:#2d2f33}.custom-theme .el-date-range-picker__time-picker-wrap{position:relative;display:table-cell;padding:0 5px}.custom-theme .el-date-range-picker__time-picker-wrap .el-picker-panel{position:absolute;top:13px;right:0;z-index:1;background:#fff}.custom-theme .el-time-range-picker{width:354px;overflow:visible}.custom-theme .el-time-range-picker__content{position:relative;text-align:center;padding:10px}.custom-theme .el-time-range-picker__cell{-webkit-box-sizing:border-box;box-sizing:border-box;margin:0;padding:4px 7px 7px;width:50%;display:inline-block}.custom-theme .el-time-range-picker__header{margin-bottom:5px;text-align:center;font-size:14px}.custom-theme .el-time-range-picker__body{border-radius:2px;border:1px solid #dfe4ed}.custom-theme .el-time-panel{margin:5px 0;border:solid 1px #dfe4ed;background-color:#fff;-webkit-box-shadow:0 2px 12px 0 rgba(0,0,0,.1);box-shadow:0 2px 12px 0 rgba(0,0,0,.1);border-radius:2px;position:absolute;width:180px;left:0;z-index:1000;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.custom-theme .el-time-panel__content{font-size:0;position:relative;overflow:hidden}.custom-theme .el-time-panel__content::after,.custom-theme .el-time-panel__content::before{content:"";top:50%;position:absolute;margin-top:-15px;height:32px;z-index:-1;left:0;right:0;-webkit-box-sizing:border-box;box-sizing:border-box;padding-top:6px;text-align:left;border-top:1px solid #dfe4ed;border-bottom:1px solid #dfe4ed}.custom-theme .el-time-panel__content::after{left:50%;margin-left:12%;margin-right:12%}.custom-theme .el-time-panel__content::before{padding-left:50%;margin-right:12%;margin-left:12%}.custom-theme .el-time-panel__content.has-seconds::after{left:calc(100% / 3 * 2)}.custom-theme .el-time-panel__content.has-seconds::before{padding-left:calc(100% / 3)}.custom-theme .el-time-panel__footer{border-top:1px solid #e4e4e4;padding:4px;height:36px;line-height:25px;text-align:right;-webkit-box-sizing:border-box;box-sizing:border-box}.custom-theme .el-time-panel__btn{border:none;line-height:28px;padding:0 5px;margin:0 5px;cursor:pointer;background-color:transparent;outline:0;font-size:12px;color:#2d2f33}.custom-theme .el-time-panel__btn.confirm{font-weight:800;color:#262729}.custom-theme .el-time-panel .popper__arrow{-webkit-transform:translateX(-400%);transform:translateX(-400%)}.custom-theme .el-input{position:relative;font-size:14px;display:inline-block;width:100%}.custom-theme .el-input::-webkit-scrollbar{z-index:11;width:6px}.custom-theme .el-input::-webkit-scrollbar:horizontal{height:6px}.custom-theme .el-input::-webkit-scrollbar-thumb{border-radius:5px;width:6px;background:#b4bccc}.custom-theme .el-input::-webkit-scrollbar-corner{background:#fff}.custom-theme .el-input::-webkit-scrollbar-track{background:#fff}.custom-theme .el-input::-webkit-scrollbar-track-piece{background:#fff;width:6px}.custom-theme .el-input__inner{-webkit-appearance:none;background-color:#fff;background-image:none;border-radius:4px;border:1px solid #d8dce5;-webkit-box-sizing:border-box;box-sizing:border-box;color:#5a5e66;display:inline-block;font-size:inherit;height:40px;line-height:1;outline:0;padding:0 15px;-webkit-transition:border-color .2s cubic-bezier(.645,.045,.355,1);transition:border-color .2s cubic-bezier(.645,.045,.355,1);width:100%}.custom-theme .el-input__inner::-webkit-input-placeholder{color:#b4bccc}.custom-theme .el-input__inner:-ms-input-placeholder{color:#b4bccc}.custom-theme .el-input__inner::-ms-input-placeholder{color:#b4bccc}.custom-theme .el-input__inner::placeholder{color:#b4bccc}.custom-theme .el-input__inner:hover{border-color:#b4bccc}.custom-theme .el-input__inner:focus{outline:0;border-color:#262729}.custom-theme .el-input__suffix{position:absolute;height:100%;right:5px;top:0;text-align:center;color:#b4bccc;-webkit-transition:all .3s;transition:all .3s;pointer-events:none}.custom-theme .el-input__suffix-inner{pointer-events:all}.custom-theme .el-input__prefix{position:absolute;height:100%;left:5px;top:0;text-align:center;color:#b4bccc;-webkit-transition:all .3s;transition:all .3s}.custom-theme .el-input__icon{height:100%;width:25px;text-align:center;-webkit-transition:all .3s;transition:all .3s;line-height:40px}.custom-theme .el-input__icon:after{content:'';height:100%;width:0;display:inline-block;vertical-align:middle}.custom-theme .el-input__validateIcon{pointer-events:none}.custom-theme .el-input.is-active .el-input__inner{outline:0;border-color:#262729}.custom-theme .el-input.is-disabled .el-input__inner{background-color:#f5f7fa;border-color:#dfe4ed;color:#b4bccc;cursor:not-allowed}.custom-theme .el-input.is-disabled .el-input__inner::-webkit-input-placeholder{color:#b4bccc}.custom-theme .el-input.is-disabled .el-input__inner:-ms-input-placeholder{color:#b4bccc}.custom-theme .el-input.is-disabled .el-input__inner::-ms-input-placeholder{color:#b4bccc}.custom-theme .el-input.is-disabled .el-input__inner::placeholder{color:#b4bccc}.custom-theme .el-input.is-disabled .el-input__icon{cursor:not-allowed}.custom-theme .el-input--suffix .el-input__inner{padding-right:30px}.custom-theme .el-input--prefix .el-input__inner{padding-left:30px}.custom-theme .el-input--medium{font-size:14px}.custom-theme .el-input--medium .el-input__inner{height:36px}.custom-theme .el-input--medium .el-input__icon{line-height:36px}.custom-theme .el-input--small{font-size:13px}.custom-theme .el-input--small .el-input__inner{height:32px}.custom-theme .el-input--small .el-input__icon{line-height:32px}.custom-theme .el-input--mini{font-size:12px}.custom-theme .el-input--mini .el-input__inner{height:28px}.custom-theme .el-input--mini .el-input__icon{line-height:28px}.custom-theme .el-input-group{line-height:normal;display:inline-table;width:100%;border-collapse:separate}.custom-theme .el-input-group>.el-input__inner{vertical-align:middle;display:table-cell}.custom-theme .el-input-group__append,.custom-theme .el-input-group__prepend{background-color:#f5f7fa;color:#0a76a4;vertical-align:middle;display:table-cell;position:relative;border:1px solid #d8dce5;border-radius:4px;padding:0 20px;width:1px;white-space:nowrap}.custom-theme .el-input-group__append:focus,.custom-theme .el-input-group__prepend:focus{outline:0}.custom-theme .el-input-group__append .el-button,.custom-theme .el-input-group__append .el-select,.custom-theme .el-input-group__prepend .el-button,.custom-theme .el-input-group__prepend .el-select{display:inline-block;margin:-20px}.custom-theme .el-input-group__append button.el-button,.custom-theme .el-input-group__append div.el-select .el-input__inner,.custom-theme .el-input-group__append div.el-select:hover .el-input__inner,.custom-theme .el-input-group__prepend button.el-button,.custom-theme .el-input-group__prepend div.el-select .el-input__inner,.custom-theme .el-input-group__prepend div.el-select:hover .el-input__inner{border-color:transparent;background-color:transparent;color:inherit;border-top:0;border-bottom:0}.custom-theme .el-input-group__append .el-button,.custom-theme .el-input-group__append .el-input,.custom-theme .el-input-group__prepend .el-button,.custom-theme .el-input-group__prepend .el-input{font-size:inherit}.custom-theme .el-input-group__prepend{border-right:0;border-top-right-radius:0;border-bottom-right-radius:0}.custom-theme .el-input-group__append{border-left:0;border-top-left-radius:0;border-bottom-left-radius:0}.custom-theme .el-input-group--prepend .el-input__inner{border-top-left-radius:0;border-bottom-left-radius:0}.custom-theme .el-input-group--append .el-input__inner{border-top-right-radius:0;border-bottom-right-radius:0}.custom-theme .el-textarea{display:inline-block;width:100%;vertical-align:bottom}.custom-theme .el-textarea__inner{display:block;resize:vertical;padding:5px 15px;line-height:1.5;-webkit-box-sizing:border-box;box-sizing:border-box;width:100%;font-size:14px;color:#5a5e66;background-color:#fff;background-image:none;border:1px solid #d8dce5;border-radius:4px;-webkit-transition:border-color .2s cubic-bezier(.645,.045,.355,1);transition:border-color .2s cubic-bezier(.645,.045,.355,1)}.custom-theme .el-textarea__inner::-webkit-input-placeholder{color:#b4bccc}.custom-theme .el-textarea__inner:-ms-input-placeholder{color:#b4bccc}.custom-theme .el-textarea__inner::-ms-input-placeholder{color:#b4bccc}.custom-theme .el-textarea__inner::placeholder{color:#b4bccc}.custom-theme .el-textarea__inner:hover{border-color:#b4bccc}.custom-theme .el-textarea__inner:focus{outline:0;border-color:#262729}.custom-theme .el-textarea.is-disabled .el-textarea__inner{background-color:#f5f7fa;border-color:#dfe4ed;color:#b4bccc;cursor:not-allowed}.custom-theme .el-textarea.is-disabled .el-textarea__inner::-webkit-input-placeholder{color:#b4bccc}.custom-theme .el-textarea.is-disabled .el-textarea__inner:-ms-input-placeholder{color:#b4bccc}.custom-theme .el-textarea.is-disabled .el-textarea__inner::-ms-input-placeholder{color:#b4bccc}.custom-theme .el-textarea.is-disabled .el-textarea__inner::placeholder{color:#b4bccc}.custom-theme .el-scrollbar{overflow:hidden;position:relative}.custom-theme .el-scrollbar:active>.el-scrollbar__bar,.custom-theme .el-scrollbar:focus>.el-scrollbar__bar,.custom-theme .el-scrollbar:hover>.el-scrollbar__bar{opacity:1;-webkit-transition:opacity 340ms ease-out;transition:opacity 340ms ease-out}.custom-theme .el-scrollbar__wrap{overflow:scroll;height:100%}.custom-theme .el-scrollbar__wrap--hidden-default::-webkit-scrollbar{width:0;height:0}.custom-theme .el-scrollbar__thumb{position:relative;display:block;width:0;height:0;cursor:pointer;border-radius:inherit;background-color:rgba(135,141,153,.3);-webkit-transition:.3s background-color;transition:.3s background-color}.custom-theme .el-scrollbar__thumb:hover{background-color:rgba(135,141,153,.5)}.custom-theme .el-scrollbar__bar{position:absolute;right:2px;bottom:2px;z-index:1;border-radius:4px;opacity:0;-webkit-transition:opacity 120ms ease-out;transition:opacity 120ms ease-out}.custom-theme .el-scrollbar__bar.is-vertical{width:6px;top:2px}.custom-theme .el-scrollbar__bar.is-vertical>div{width:100%}.custom-theme .el-scrollbar__bar.is-horizontal{height:6px;left:2px}.custom-theme .el-scrollbar__bar.is-horizontal>div{height:100%}.custom-theme .el-popper .popper__arrow,.custom-theme .el-popper .popper__arrow::after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.custom-theme .el-popper .popper__arrow{border-width:6px;-webkit-filter:drop-shadow(0 2px 12px rgba(0, 0, 0, .03));filter:drop-shadow(0 2px 12px rgba(0, 0, 0, .03))}.custom-theme .el-popper .popper__arrow::after{content:" ";border-width:6px}.custom-theme .el-popper[x-placement^=top]{margin-bottom:12px}.custom-theme .el-popper[x-placement^=top] .popper__arrow{bottom:-6px;left:50%;margin-right:3px;border-top-color:#e6ebf5;border-bottom-width:0}.custom-theme .el-popper[x-placement^=top] .popper__arrow::after{bottom:1px;margin-left:-6px;border-top-color:#fff;border-bottom-width:0}.custom-theme .el-popper[x-placement^=bottom]{margin-top:12px}.custom-theme .el-popper[x-placement^=bottom] .popper__arrow{top:-6px;left:50%;margin-right:3px;border-top-width:0;border-bottom-color:#e6ebf5}.custom-theme .el-popper[x-placement^=bottom] .popper__arrow::after{top:1px;margin-left:-6px;border-top-width:0;border-bottom-color:#fff}.custom-theme .el-popper[x-placement^=right]{margin-left:12px}.custom-theme .el-popper[x-placement^=right] .popper__arrow{top:50%;left:-6px;margin-bottom:3px;border-right-color:#e6ebf5;border-left-width:0}.custom-theme .el-popper[x-placement^=right] .popper__arrow::after{bottom:-6px;left:1px;border-right-color:#fff;border-left-width:0}.custom-theme .el-popper[x-placement^=left]{margin-right:12px}.custom-theme .el-popper[x-placement^=left] .popper__arrow{top:50%;right:-6px;margin-bottom:3px;border-right-width:0;border-left-color:#e6ebf5}.custom-theme .el-popper[x-placement^=left] .popper__arrow::after{right:1px;bottom:-6px;margin-left:-6px;border-right-width:0;border-left-color:#fff}.custom-theme .fade-in-linear-enter-active,.custom-theme .fade-in-linear-leave-active{-webkit-transition:opacity .2s linear;transition:opacity .2s linear}.custom-theme .fade-in-linear-enter,.custom-theme .fade-in-linear-leave,.custom-theme .fade-in-linear-leave-active{opacity:0}.custom-theme .el-fade-in-linear-enter-active,.custom-theme .el-fade-in-linear-leave-active{-webkit-transition:opacity .2s linear;transition:opacity .2s linear}.custom-theme .el-fade-in-linear-enter,.custom-theme .el-fade-in-linear-leave,.custom-theme .el-fade-in-linear-leave-active{opacity:0}.custom-theme .el-fade-in-enter-active,.custom-theme .el-fade-in-leave-active{-webkit-transition:all .3s cubic-bezier(.55,0,.1,1);transition:all .3s cubic-bezier(.55,0,.1,1)}.custom-theme .el-fade-in-enter,.custom-theme .el-fade-in-leave-active{opacity:0}.custom-theme .el-zoom-in-center-enter-active,.custom-theme .el-zoom-in-center-leave-active{-webkit-transition:all .3s cubic-bezier(.55,0,.1,1);transition:all .3s cubic-bezier(.55,0,.1,1)}.custom-theme .el-zoom-in-center-enter,.custom-theme .el-zoom-in-center-leave-active{opacity:0;-webkit-transform:scaleX(0);transform:scaleX(0)}.custom-theme .el-zoom-in-top-enter-active,.custom-theme .el-zoom-in-top-leave-active{opacity:1;-webkit-transform:scaleY(1);transform:scaleY(1);-webkit-transition:opacity .3s cubic-bezier(.23,1,.32,1) .1s,-webkit-transform .3s cubic-bezier(.23,1,.32,1) .1s;transition:opacity .3s cubic-bezier(.23,1,.32,1) .1s,-webkit-transform .3s cubic-bezier(.23,1,.32,1) .1s;transition:transform .3s cubic-bezier(.23,1,.32,1) .1s,opacity .3s cubic-bezier(.23,1,.32,1) .1s;transition:transform .3s cubic-bezier(.23,1,.32,1) .1s,opacity .3s cubic-bezier(.23,1,.32,1) .1s,-webkit-transform .3s cubic-bezier(.23,1,.32,1) .1s;-webkit-transform-origin:center top;transform-origin:center top}.custom-theme .el-zoom-in-top-enter,.custom-theme .el-zoom-in-top-leave-active{opacity:0;-webkit-transform:scaleY(0);transform:scaleY(0)}.custom-theme .el-zoom-in-bottom-enter-active,.custom-theme .el-zoom-in-bottom-leave-active{opacity:1;-webkit-transform:scaleY(1);transform:scaleY(1);-webkit-transition:opacity .3s cubic-bezier(.23,1,.32,1) .1s,-webkit-transform .3s cubic-bezier(.23,1,.32,1) .1s;transition:opacity .3s cubic-bezier(.23,1,.32,1) .1s,-webkit-transform .3s cubic-bezier(.23,1,.32,1) .1s;transition:transform .3s cubic-bezier(.23,1,.32,1) .1s,opacity .3s cubic-bezier(.23,1,.32,1) .1s;transition:transform .3s cubic-bezier(.23,1,.32,1) .1s,opacity .3s cubic-bezier(.23,1,.32,1) .1s,-webkit-transform .3s cubic-bezier(.23,1,.32,1) .1s;-webkit-transform-origin:center bottom;transform-origin:center bottom}.custom-theme .el-zoom-in-bottom-enter,.custom-theme .el-zoom-in-bottom-leave-active{opacity:0;-webkit-transform:scaleY(0);transform:scaleY(0)}.custom-theme .el-zoom-in-left-enter-active,.custom-theme .el-zoom-in-left-leave-active{opacity:1;-webkit-transform:scale(1,1);transform:scale(1,1);-webkit-transition:opacity .3s cubic-bezier(.23,1,.32,1) .1s,-webkit-transform .3s cubic-bezier(.23,1,.32,1) .1s;transition:opacity .3s cubic-bezier(.23,1,.32,1) .1s,-webkit-transform .3s cubic-bezier(.23,1,.32,1) .1s;transition:transform .3s cubic-bezier(.23,1,.32,1) .1s,opacity .3s cubic-bezier(.23,1,.32,1) .1s;transition:transform .3s cubic-bezier(.23,1,.32,1) .1s,opacity .3s cubic-bezier(.23,1,.32,1) .1s,-webkit-transform .3s cubic-bezier(.23,1,.32,1) .1s;-webkit-transform-origin:top left;transform-origin:top left}.custom-theme .el-zoom-in-left-enter,.custom-theme .el-zoom-in-left-leave-active{opacity:0;-webkit-transform:scale(.45,.45);transform:scale(.45,.45)}.custom-theme .collapse-transition{-webkit-transition:.3s height ease-in-out,.3s padding-top ease-in-out,.3s padding-bottom ease-in-out;transition:.3s height ease-in-out,.3s padding-top ease-in-out,.3s padding-bottom ease-in-out}.custom-theme .horizontal-collapse-transition{-webkit-transition:.3s width ease-in-out,.3s padding-left ease-in-out,.3s padding-right ease-in-out;transition:.3s width ease-in-out,.3s padding-left ease-in-out,.3s padding-right ease-in-out}.custom-theme .el-list-enter-active,.custom-theme .el-list-leave-active{-webkit-transition:all 1s;transition:all 1s}.custom-theme .el-list-enter,.custom-theme .el-list-leave-active{opacity:0;-webkit-transform:translateY(-30px);transform:translateY(-30px)}.custom-theme .el-opacity-transition{-webkit-transition:opacity .3s cubic-bezier(.55,0,.1,1);transition:opacity .3s cubic-bezier(.55,0,.1,1)}.custom-theme .el-date-editor{position:relative;display:inline-block;text-align:left}.custom-theme .el-date-editor.el-input,.custom-theme .el-date-editor.el-input__inner{width:220px}.custom-theme .el-date-editor--daterange.el-input,.custom-theme .el-date-editor--daterange.el-input__inner,.custom-theme .el-date-editor--timerange.el-input,.custom-theme .el-date-editor--timerange.el-input__inner{width:350px}.custom-theme .el-date-editor--datetimerange.el-input,.custom-theme .el-date-editor--datetimerange.el-input__inner{width:400px}.custom-theme .el-date-editor .el-range__icon{font-size:14px;margin-left:-5px;color:#b4bccc;float:left;line-height:32px}.custom-theme .el-date-editor .el-range-input{-webkit-appearance:none;-moz-appearance:none;appearance:none;border:none;outline:0;display:inline-block;height:100%;margin:0;padding:0;width:39%;text-align:center;font-size:14px;color:#5a5e66}.custom-theme .el-date-editor .el-range-input::-webkit-input-placeholder{color:#b4bccc}.custom-theme .el-date-editor .el-range-input:-ms-input-placeholder{color:#b4bccc}.custom-theme .el-date-editor .el-range-input::-ms-input-placeholder{color:#b4bccc}.custom-theme .el-date-editor .el-range-input::placeholder{color:#b4bccc}.custom-theme .el-date-editor .el-range-separator{display:inline-block;height:100%;padding:0 5px;margin:0;text-align:center;line-height:32px;font-size:14px;width:5%;color:#2d2f33}.custom-theme .el-date-editor .el-range__close-icon{font-size:14px;color:#b4bccc;width:25px;display:inline-block;float:right;line-height:32px}.custom-theme .el-range-editor.el-input__inner{padding:3px 10px}.custom-theme .el-range-editor.is-active{border-color:#262729}.custom-theme .el-range-editor.is-active:hover{border-color:#262729}.custom-theme .el-range-editor--medium.el-input__inner{height:36px}.custom-theme .el-range-editor--medium .el-range-separator{line-height:28px;font-size:14px}.custom-theme .el-range-editor--medium .el-range-input{font-size:14px}.custom-theme .el-range-editor--medium .el-range__close-icon,.custom-theme .el-range-editor--medium .el-range__icon{line-height:28px}.custom-theme .el-range-editor--small.el-input__inner{height:32px}.custom-theme .el-range-editor--small .el-range-separator{line-height:24px;font-size:13px}.custom-theme .el-range-editor--small .el-range-input{font-size:13px}.custom-theme .el-range-editor--small .el-range__close-icon,.custom-theme .el-range-editor--small .el-range__icon{line-height:24px}.custom-theme .el-range-editor--mini.el-input__inner{height:28px}.custom-theme .el-range-editor--mini .el-range-separator{line-height:20px;font-size:12px}.custom-theme .el-range-editor--mini .el-range-input{font-size:12px}.custom-theme .el-range-editor--mini .el-range__close-icon,.custom-theme .el-range-editor--mini .el-range__icon{line-height:20px}.custom-theme .el-range-editor.is-disabled{background-color:#f5f7fa;border-color:#dfe4ed;color:#b4bccc;cursor:not-allowed}.custom-theme .el-range-editor.is-disabled:focus,.custom-theme .el-range-editor.is-disabled:hover{border-color:#dfe4ed}.custom-theme .el-range-editor.is-disabled input{background-color:#f5f7fa;color:#b4bccc;cursor:not-allowed}.custom-theme .el-range-editor.is-disabled input::-webkit-input-placeholder{color:#b4bccc}.custom-theme .el-range-editor.is-disabled input:-ms-input-placeholder{color:#b4bccc}.custom-theme .el-range-editor.is-disabled input::-ms-input-placeholder{color:#b4bccc}.custom-theme .el-range-editor.is-disabled input::placeholder{color:#b4bccc}.custom-theme .el-range-editor.is-disabled .el-range-separator{color:#b4bccc}.custom-theme .el-picker-panel{color:#5a5e66;border:1px solid #dfe4ed;-webkit-box-shadow:0 2px 12px 0 rgba(0,0,0,.1);box-shadow:0 2px 12px 0 rgba(0,0,0,.1);background:#fff;border-radius:4px;line-height:30px;margin:5px 0}.custom-theme .el-picker-panel__body-wrapper::after,.custom-theme .el-picker-panel__body::after{content:"";display:table;clear:both}.custom-theme .el-picker-panel__content{position:relative;margin:15px}.custom-theme .el-picker-panel__footer{border-top:1px solid #e4e4e4;padding:4px;text-align:right;background-color:#fff;position:relative;font-size:0}.custom-theme .el-picker-panel__shortcut{display:block;width:100%;border:0;background-color:transparent;line-height:28px;font-size:14px;color:#5a5e66;padding-left:12px;text-align:left;outline:0;cursor:pointer}.custom-theme .el-picker-panel__shortcut:hover{color:#262729}.custom-theme .el-picker-panel__shortcut.active{background-color:#e6f1fe;color:#262729}.custom-theme .el-picker-panel__btn{border:1px solid #dcdcdc;color:#333;line-height:24px;border-radius:2px;padding:0 20px;cursor:pointer;background-color:transparent;outline:0;font-size:12px}.custom-theme .el-picker-panel__btn[disabled]{color:#ccc;cursor:not-allowed}.custom-theme .el-picker-panel__icon-btn{font-size:12px;color:#2d2f33;border:0;background:0 0;cursor:pointer;outline:0;margin-top:8px}.custom-theme .el-picker-panel__icon-btn:hover{color:#262729}.custom-theme .el-picker-panel__icon-btn.is-disabled{color:#bbb}.custom-theme .el-picker-panel__icon-btn.is-disabled:hover{cursor:not-allowed}.custom-theme .el-picker-panel__link-btn{vertical-align:middle}.custom-theme .el-picker-panel .popper__arrow{-webkit-transform:translateX(-400%);transform:translateX(-400%)}.custom-theme .el-picker-panel [slot=sidebar],.custom-theme .el-picker-panel__sidebar{position:absolute;top:0;bottom:0;width:110px;border-right:1px solid #e4e4e4;-webkit-box-sizing:border-box;box-sizing:border-box;padding-top:6px;background-color:#fff;overflow:auto}.custom-theme .el-picker-panel [slot=sidebar]+.el-picker-panel__body,.custom-theme .el-picker-panel__sidebar+.el-picker-panel__body{margin-left:110px}.custom-theme .el-date-picker{width:322px}.custom-theme .el-date-picker.has-sidebar.has-time{width:434px}.custom-theme .el-date-picker.has-sidebar{width:438px}.custom-theme .el-date-picker.has-time .el-picker-panel__body-wrapper{position:relative}.custom-theme .el-date-picker .el-picker-panel__content{width:292px}.custom-theme .el-date-picker table{table-layout:fixed;width:100%}.custom-theme .el-date-picker__editor-wrap{position:relative;display:table-cell;padding:0 5px}.custom-theme .el-date-picker__time-header{position:relative;border-bottom:1px solid #e4e4e4;font-size:12px;padding:8px 5px 5px 5px;display:table;width:100%;-webkit-box-sizing:border-box;box-sizing:border-box}.custom-theme .el-date-picker__header{margin:12px;text-align:center}.custom-theme .el-date-picker__header--bordered{margin-bottom:0;padding-bottom:12px;border-bottom:solid 1px #e6ebf5}.custom-theme .el-date-picker__header--bordered+.el-picker-panel__content{margin-top:0}.custom-theme .el-date-picker__header-label{font-size:16px;font-weight:500;padding:0 5px;line-height:22px;text-align:center;cursor:pointer;color:#5a5e66}.custom-theme .el-date-picker__header-label:hover{color:#262729}.custom-theme .el-date-picker__header-label.active{color:#262729}.custom-theme .el-date-picker__prev-btn{float:left}.custom-theme .el-date-picker__next-btn{float:right}.custom-theme .el-date-picker__time-wrap{padding:10px;text-align:center}.custom-theme .el-date-picker__time-label{float:left;cursor:pointer;line-height:30px;margin-left:10px}.custom-theme .el-scrollbar{overflow:hidden;position:relative}.custom-theme .el-scrollbar:active>.el-scrollbar__bar,.custom-theme .el-scrollbar:focus>.el-scrollbar__bar,.custom-theme .el-scrollbar:hover>.el-scrollbar__bar{opacity:1;-webkit-transition:opacity 340ms ease-out;transition:opacity 340ms ease-out}.custom-theme .el-scrollbar__wrap{overflow:scroll;height:100%}.custom-theme .el-scrollbar__wrap--hidden-default::-webkit-scrollbar{width:0;height:0}.custom-theme .el-scrollbar__thumb{position:relative;display:block;width:0;height:0;cursor:pointer;border-radius:inherit;background-color:rgba(135,141,153,.3);-webkit-transition:.3s background-color;transition:.3s background-color}.custom-theme .el-scrollbar__thumb:hover{background-color:rgba(135,141,153,.5)}.custom-theme .el-scrollbar__bar{position:absolute;right:2px;bottom:2px;z-index:1;border-radius:4px;opacity:0;-webkit-transition:opacity 120ms ease-out;transition:opacity 120ms ease-out}.custom-theme .el-scrollbar__bar.is-vertical{width:6px;top:2px}.custom-theme .el-scrollbar__bar.is-vertical>div{width:100%}.custom-theme .el-scrollbar__bar.is-horizontal{height:6px;left:2px}.custom-theme .el-scrollbar__bar.is-horizontal>div{height:100%}.custom-theme .el-popper .popper__arrow,.custom-theme .el-popper .popper__arrow::after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.custom-theme .el-popper .popper__arrow{border-width:6px;-webkit-filter:drop-shadow(0 2px 12px rgba(0, 0, 0, .03));filter:drop-shadow(0 2px 12px rgba(0, 0, 0, .03))}.custom-theme .el-popper .popper__arrow::after{content:" ";border-width:6px}.custom-theme .el-popper[x-placement^=top]{margin-bottom:12px}.custom-theme .el-popper[x-placement^=top] .popper__arrow{bottom:-6px;left:50%;margin-right:3px;border-top-color:#e6ebf5;border-bottom-width:0}.custom-theme .el-popper[x-placement^=top] .popper__arrow::after{bottom:1px;margin-left:-6px;border-top-color:#fff;border-bottom-width:0}.custom-theme .el-popper[x-placement^=bottom]{margin-top:12px}.custom-theme .el-popper[x-placement^=bottom] .popper__arrow{top:-6px;left:50%;margin-right:3px;border-top-width:0;border-bottom-color:#e6ebf5}.custom-theme .el-popper[x-placement^=bottom] .popper__arrow::after{top:1px;margin-left:-6px;border-top-width:0;border-bottom-color:#fff}.custom-theme .el-popper[x-placement^=right]{margin-left:12px}.custom-theme .el-popper[x-placement^=right] .popper__arrow{top:50%;left:-6px;margin-bottom:3px;border-right-color:#e6ebf5;border-left-width:0}.custom-theme .el-popper[x-placement^=right] .popper__arrow::after{bottom:-6px;left:1px;border-right-color:#fff;border-left-width:0}.custom-theme .el-popper[x-placement^=left]{margin-right:12px}.custom-theme .el-popper[x-placement^=left] .popper__arrow{top:50%;right:-6px;margin-bottom:3px;border-right-width:0;border-left-color:#e6ebf5}.custom-theme .el-popper[x-placement^=left] .popper__arrow::after{right:1px;bottom:-6px;margin-left:-6px;border-right-width:0;border-left-color:#fff}.custom-theme .time-select{margin:5px 0;min-width:0}.custom-theme .time-select .el-picker-panel__content{max-height:200px;margin:0}.custom-theme .time-select-item{padding:8px 10px;font-size:14px;line-height:20px}.custom-theme .time-select-item.selected:not(.disabled){color:#262729;font-weight:700}.custom-theme .time-select-item.disabled{color:#dfe4ed;cursor:not-allowed}.custom-theme .time-select-item:hover{background-color:#f5f7fa;font-weight:700;cursor:pointer}.custom-theme .fade-in-linear-enter-active,.custom-theme .fade-in-linear-leave-active{-webkit-transition:opacity .2s linear;transition:opacity .2s linear}.custom-theme .fade-in-linear-enter,.custom-theme .fade-in-linear-leave,.custom-theme .fade-in-linear-leave-active{opacity:0}.custom-theme .el-fade-in-linear-enter-active,.custom-theme .el-fade-in-linear-leave-active{-webkit-transition:opacity .2s linear;transition:opacity .2s linear}.custom-theme .el-fade-in-linear-enter,.custom-theme .el-fade-in-linear-leave,.custom-theme .el-fade-in-linear-leave-active{opacity:0}.custom-theme .el-fade-in-enter-active,.custom-theme .el-fade-in-leave-active{-webkit-transition:all .3s cubic-bezier(.55,0,.1,1);transition:all .3s cubic-bezier(.55,0,.1,1)}.custom-theme .el-fade-in-enter,.custom-theme .el-fade-in-leave-active{opacity:0}.custom-theme .el-zoom-in-center-enter-active,.custom-theme .el-zoom-in-center-leave-active{-webkit-transition:all .3s cubic-bezier(.55,0,.1,1);transition:all .3s cubic-bezier(.55,0,.1,1)}.custom-theme .el-zoom-in-center-enter,.custom-theme .el-zoom-in-center-leave-active{opacity:0;-webkit-transform:scaleX(0);transform:scaleX(0)}.custom-theme .el-zoom-in-top-enter-active,.custom-theme .el-zoom-in-top-leave-active{opacity:1;-webkit-transform:scaleY(1);transform:scaleY(1);-webkit-transition:opacity .3s cubic-bezier(.23,1,.32,1) .1s,-webkit-transform .3s cubic-bezier(.23,1,.32,1) .1s;transition:opacity .3s cubic-bezier(.23,1,.32,1) .1s,-webkit-transform .3s cubic-bezier(.23,1,.32,1) .1s;transition:transform .3s cubic-bezier(.23,1,.32,1) .1s,opacity .3s cubic-bezier(.23,1,.32,1) .1s;transition:transform .3s cubic-bezier(.23,1,.32,1) .1s,opacity .3s cubic-bezier(.23,1,.32,1) .1s,-webkit-transform .3s cubic-bezier(.23,1,.32,1) .1s;-webkit-transform-origin:center top;transform-origin:center top}.custom-theme .el-zoom-in-top-enter,.custom-theme .el-zoom-in-top-leave-active{opacity:0;-webkit-transform:scaleY(0);transform:scaleY(0)}.custom-theme .el-zoom-in-bottom-enter-active,.custom-theme .el-zoom-in-bottom-leave-active{opacity:1;-webkit-transform:scaleY(1);transform:scaleY(1);-webkit-transition:opacity .3s cubic-bezier(.23,1,.32,1) .1s,-webkit-transform .3s cubic-bezier(.23,1,.32,1) .1s;transition:opacity .3s cubic-bezier(.23,1,.32,1) .1s,-webkit-transform .3s cubic-bezier(.23,1,.32,1) .1s;transition:transform .3s cubic-bezier(.23,1,.32,1) .1s,opacity .3s cubic-bezier(.23,1,.32,1) .1s;transition:transform .3s cubic-bezier(.23,1,.32,1) .1s,opacity .3s cubic-bezier(.23,1,.32,1) .1s,-webkit-transform .3s cubic-bezier(.23,1,.32,1) .1s;-webkit-transform-origin:center bottom;transform-origin:center bottom}.custom-theme .el-zoom-in-bottom-enter,.custom-theme .el-zoom-in-bottom-leave-active{opacity:0;-webkit-transform:scaleY(0);transform:scaleY(0)}.custom-theme .el-zoom-in-left-enter-active,.custom-theme .el-zoom-in-left-leave-active{opacity:1;-webkit-transform:scale(1,1);transform:scale(1,1);-webkit-transition:opacity .3s cubic-bezier(.23,1,.32,1) .1s,-webkit-transform .3s cubic-bezier(.23,1,.32,1) .1s;transition:opacity .3s cubic-bezier(.23,1,.32,1) .1s,-webkit-transform .3s cubic-bezier(.23,1,.32,1) .1s;transition:transform .3s cubic-bezier(.23,1,.32,1) .1s,opacity .3s cubic-bezier(.23,1,.32,1) .1s;transition:transform .3s cubic-bezier(.23,1,.32,1) .1s,opacity .3s cubic-bezier(.23,1,.32,1) .1s,-webkit-transform .3s cubic-bezier(.23,1,.32,1) .1s;-webkit-transform-origin:top left;transform-origin:top left}.custom-theme .el-zoom-in-left-enter,.custom-theme .el-zoom-in-left-leave-active{opacity:0;-webkit-transform:scale(.45,.45);transform:scale(.45,.45)}.custom-theme .collapse-transition{-webkit-transition:.3s height ease-in-out,.3s padding-top ease-in-out,.3s padding-bottom ease-in-out;transition:.3s height ease-in-out,.3s padding-top ease-in-out,.3s padding-bottom ease-in-out}.custom-theme .horizontal-collapse-transition{-webkit-transition:.3s width ease-in-out,.3s padding-left ease-in-out,.3s padding-right ease-in-out;transition:.3s width ease-in-out,.3s padding-left ease-in-out,.3s padding-right ease-in-out}.custom-theme .el-list-enter-active,.custom-theme .el-list-leave-active{-webkit-transition:all 1s;transition:all 1s}.custom-theme .el-list-enter,.custom-theme .el-list-leave-active{opacity:0;-webkit-transform:translateY(-30px);transform:translateY(-30px)}.custom-theme .el-opacity-transition{-webkit-transition:opacity .3s cubic-bezier(.55,0,.1,1);transition:opacity .3s cubic-bezier(.55,0,.1,1)}.custom-theme .el-date-editor{position:relative;display:inline-block;text-align:left}.custom-theme .el-date-editor.el-input,.custom-theme .el-date-editor.el-input__inner{width:220px}.custom-theme .el-date-editor--daterange.el-input,.custom-theme .el-date-editor--daterange.el-input__inner,.custom-theme .el-date-editor--timerange.el-input,.custom-theme .el-date-editor--timerange.el-input__inner{width:350px}.custom-theme .el-date-editor--datetimerange.el-input,.custom-theme .el-date-editor--datetimerange.el-input__inner{width:400px}.custom-theme .el-date-editor .el-range__icon{font-size:14px;margin-left:-5px;color:#b4bccc;float:left;line-height:32px}.custom-theme .el-date-editor .el-range-input{-webkit-appearance:none;-moz-appearance:none;appearance:none;border:none;outline:0;display:inline-block;height:100%;margin:0;padding:0;width:39%;text-align:center;font-size:14px;color:#5a5e66}.custom-theme .el-date-editor .el-range-input::-webkit-input-placeholder{color:#b4bccc}.custom-theme .el-date-editor .el-range-input:-ms-input-placeholder{color:#b4bccc}.custom-theme .el-date-editor .el-range-input::-ms-input-placeholder{color:#b4bccc}.custom-theme .el-date-editor .el-range-input::placeholder{color:#b4bccc}.custom-theme .el-date-editor .el-range-separator{display:inline-block;height:100%;padding:0 5px;margin:0;text-align:center;line-height:32px;font-size:14px;width:5%;color:#2d2f33}.custom-theme .el-date-editor .el-range__close-icon{font-size:14px;color:#b4bccc;width:25px;display:inline-block;float:right;line-height:32px}.custom-theme .el-range-editor.el-input__inner{padding:3px 10px}.custom-theme .el-range-editor.is-active{border-color:#262729}.custom-theme .el-range-editor.is-active:hover{border-color:#262729}.custom-theme .el-range-editor--medium.el-input__inner{height:36px}.custom-theme .el-range-editor--medium .el-range-separator{line-height:28px;font-size:14px}.custom-theme .el-range-editor--medium .el-range-input{font-size:14px}.custom-theme .el-range-editor--medium .el-range__close-icon,.custom-theme .el-range-editor--medium .el-range__icon{line-height:28px}.custom-theme .el-range-editor--small.el-input__inner{height:32px}.custom-theme .el-range-editor--small .el-range-separator{line-height:24px;font-size:13px}.custom-theme .el-range-editor--small .el-range-input{font-size:13px}.custom-theme .el-range-editor--small .el-range__close-icon,.custom-theme .el-range-editor--small .el-range__icon{line-height:24px}.custom-theme .el-range-editor--mini.el-input__inner{height:28px}.custom-theme .el-range-editor--mini .el-range-separator{line-height:20px;font-size:12px}.custom-theme .el-range-editor--mini .el-range-input{font-size:12px}.custom-theme .el-range-editor--mini .el-range__close-icon,.custom-theme .el-range-editor--mini .el-range__icon{line-height:20px}.custom-theme .el-range-editor.is-disabled{background-color:#f5f7fa;border-color:#dfe4ed;color:#b4bccc;cursor:not-allowed}.custom-theme .el-range-editor.is-disabled:focus,.custom-theme .el-range-editor.is-disabled:hover{border-color:#dfe4ed}.custom-theme .el-range-editor.is-disabled input{background-color:#f5f7fa;color:#b4bccc;cursor:not-allowed}.custom-theme .el-range-editor.is-disabled input::-webkit-input-placeholder{color:#b4bccc}.custom-theme .el-range-editor.is-disabled input:-ms-input-placeholder{color:#b4bccc}.custom-theme .el-range-editor.is-disabled input::-ms-input-placeholder{color:#b4bccc}.custom-theme .el-range-editor.is-disabled input::placeholder{color:#b4bccc}.custom-theme .el-range-editor.is-disabled .el-range-separator{color:#b4bccc}.custom-theme .el-time-spinner.has-seconds .el-time-spinner__wrapper{width:33.3%}.custom-theme .el-time-spinner.has-seconds .el-time-spinner__wrapper:nth-child(2){margin-left:1%}.custom-theme .el-time-spinner__wrapper{max-height:190px;overflow:auto;display:inline-block;width:50%;vertical-align:top;position:relative}.custom-theme .el-time-spinner__wrapper .el-scrollbar__wrap:not(.el-scrollbar__wrap--hidden-default){padding-bottom:15px}.custom-theme .el-time-spinner__wrapper.is-arrow{-webkit-box-sizing:border-box;box-sizing:border-box;text-align:center;overflow:hidden}.custom-theme .el-time-spinner__wrapper.is-arrow .el-time-spinner__list{-webkit-transform:translateY(-32px);transform:translateY(-32px)}.custom-theme .el-time-spinner__wrapper.is-arrow .el-time-spinner__item:hover:not(.disabled):not(.active){background:#fff;cursor:default}.custom-theme .el-time-spinner__arrow{font-size:12px;color:#878d99;position:absolute;left:0;width:100%;z-index:1;text-align:center;height:30px;line-height:30px;cursor:pointer}.custom-theme .el-time-spinner__arrow:hover{color:#262729}.custom-theme .el-time-spinner__arrow.el-icon-arrow-up{top:10px}.custom-theme .el-time-spinner__arrow.el-icon-arrow-down{bottom:10px}.custom-theme .el-time-spinner__input.el-input{width:70%}.custom-theme .el-time-spinner__input.el-input .el-input__inner{padding:0;text-align:center}.custom-theme .el-time-spinner__list{padding:0;margin:0;list-style:none;text-align:center}.custom-theme .el-time-spinner__list::after,.custom-theme .el-time-spinner__list::before{content:'';display:block;width:100%;height:80px}.custom-theme .el-time-spinner__item{height:32px;line-height:32px;font-size:12px;color:#5a5e66}.custom-theme .el-time-spinner__item:hover:not(.disabled):not(.active){background:#f5f7fa;cursor:pointer}.custom-theme .el-time-spinner__item.active:not(.disabled){color:#2d2f33;font-weight:700}.custom-theme .el-time-spinner__item.disabled{color:#b4bccc;cursor:not-allowed}.custom-theme .el-time-panel{margin:5px 0;border:solid 1px #dfe4ed;background-color:#fff;-webkit-box-shadow:0 2px 12px 0 rgba(0,0,0,.1);box-shadow:0 2px 12px 0 rgba(0,0,0,.1);border-radius:2px;position:absolute;width:180px;left:0;z-index:1000;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.custom-theme .el-time-panel__content{font-size:0;position:relative;overflow:hidden}.custom-theme .el-time-panel__content::after,.custom-theme .el-time-panel__content::before{content:"";top:50%;position:absolute;margin-top:-15px;height:32px;z-index:-1;left:0;right:0;-webkit-box-sizing:border-box;box-sizing:border-box;padding-top:6px;text-align:left;border-top:1px solid #dfe4ed;border-bottom:1px solid #dfe4ed}.custom-theme .el-time-panel__content::after{left:50%;margin-left:12%;margin-right:12%}.custom-theme .el-time-panel__content::before{padding-left:50%;margin-right:12%;margin-left:12%}.custom-theme .el-time-panel__content.has-seconds::after{left:calc(100% / 3 * 2)}.custom-theme .el-time-panel__content.has-seconds::before{padding-left:calc(100% / 3)}.custom-theme .el-time-panel__footer{border-top:1px solid #e4e4e4;padding:4px;height:36px;line-height:25px;text-align:right;-webkit-box-sizing:border-box;box-sizing:border-box}.custom-theme .el-time-panel__btn{border:none;line-height:28px;padding:0 5px;margin:0 5px;cursor:pointer;background-color:transparent;outline:0;font-size:12px;color:#2d2f33}.custom-theme .el-time-panel__btn.confirm{font-weight:800;color:#262729}.custom-theme .el-time-panel .popper__arrow{-webkit-transform:translateX(-400%);transform:translateX(-400%)}.custom-theme .el-input{position:relative;font-size:14px;display:inline-block;width:100%}.custom-theme .el-input::-webkit-scrollbar{z-index:11;width:6px}.custom-theme .el-input::-webkit-scrollbar:horizontal{height:6px}.custom-theme .el-input::-webkit-scrollbar-thumb{border-radius:5px;width:6px;background:#b4bccc}.custom-theme .el-input::-webkit-scrollbar-corner{background:#fff}.custom-theme .el-input::-webkit-scrollbar-track{background:#fff}.custom-theme .el-input::-webkit-scrollbar-track-piece{background:#fff;width:6px}.custom-theme .el-input__inner{-webkit-appearance:none;background-color:#fff;background-image:none;border-radius:4px;border:1px solid #d8dce5;-webkit-box-sizing:border-box;box-sizing:border-box;color:#5a5e66;display:inline-block;font-size:inherit;height:40px;line-height:1;outline:0;padding:0 15px;-webkit-transition:border-color .2s cubic-bezier(.645,.045,.355,1);transition:border-color .2s cubic-bezier(.645,.045,.355,1);width:100%}.custom-theme .el-input__inner::-webkit-input-placeholder{color:#b4bccc}.custom-theme .el-input__inner:-ms-input-placeholder{color:#b4bccc}.custom-theme .el-input__inner::-ms-input-placeholder{color:#b4bccc}.custom-theme .el-input__inner::placeholder{color:#b4bccc}.custom-theme .el-input__inner:hover{border-color:#b4bccc}.custom-theme .el-input__inner:focus{outline:0;border-color:#262729}.custom-theme .el-input__suffix{position:absolute;height:100%;right:5px;top:0;text-align:center;color:#b4bccc;-webkit-transition:all .3s;transition:all .3s;pointer-events:none}.custom-theme .el-input__suffix-inner{pointer-events:all}.custom-theme .el-input__prefix{position:absolute;height:100%;left:5px;top:0;text-align:center;color:#b4bccc;-webkit-transition:all .3s;transition:all .3s}.custom-theme .el-input__icon{height:100%;width:25px;text-align:center;-webkit-transition:all .3s;transition:all .3s;line-height:40px}.custom-theme .el-input__icon:after{content:'';height:100%;width:0;display:inline-block;vertical-align:middle}.custom-theme .el-input__validateIcon{pointer-events:none}.custom-theme .el-input.is-active .el-input__inner{outline:0;border-color:#262729}.custom-theme .el-input.is-disabled .el-input__inner{background-color:#f5f7fa;border-color:#dfe4ed;color:#b4bccc;cursor:not-allowed}.custom-theme .el-input.is-disabled .el-input__inner::-webkit-input-placeholder{color:#b4bccc}.custom-theme .el-input.is-disabled .el-input__inner:-ms-input-placeholder{color:#b4bccc}.custom-theme .el-input.is-disabled .el-input__inner::-ms-input-placeholder{color:#b4bccc}.custom-theme .el-input.is-disabled .el-input__inner::placeholder{color:#b4bccc}.custom-theme .el-input.is-disabled .el-input__icon{cursor:not-allowed}.custom-theme .el-input--suffix .el-input__inner{padding-right:30px}.custom-theme .el-input--prefix .el-input__inner{padding-left:30px}.custom-theme .el-input--medium{font-size:14px}.custom-theme .el-input--medium .el-input__inner{height:36px}.custom-theme .el-input--medium .el-input__icon{line-height:36px}.custom-theme .el-input--small{font-size:13px}.custom-theme .el-input--small .el-input__inner{height:32px}.custom-theme .el-input--small .el-input__icon{line-height:32px}.custom-theme .el-input--mini{font-size:12px}.custom-theme .el-input--mini .el-input__inner{height:28px}.custom-theme .el-input--mini .el-input__icon{line-height:28px}.custom-theme .el-input-group{line-height:normal;display:inline-table;width:100%;border-collapse:separate}.custom-theme .el-input-group>.el-input__inner{vertical-align:middle;display:table-cell}.custom-theme .el-input-group__append,.custom-theme .el-input-group__prepend{background-color:#f5f7fa;color:#0a76a4;vertical-align:middle;display:table-cell;position:relative;border:1px solid #d8dce5;border-radius:4px;padding:0 20px;width:1px;white-space:nowrap}.custom-theme .el-input-group__append:focus,.custom-theme .el-input-group__prepend:focus{outline:0}.custom-theme .el-input-group__append .el-button,.custom-theme .el-input-group__append .el-select,.custom-theme .el-input-group__prepend .el-button,.custom-theme .el-input-group__prepend .el-select{display:inline-block;margin:-20px}.custom-theme .el-input-group__append button.el-button,.custom-theme .el-input-group__append div.el-select .el-input__inner,.custom-theme .el-input-group__append div.el-select:hover .el-input__inner,.custom-theme .el-input-group__prepend button.el-button,.custom-theme .el-input-group__prepend div.el-select .el-input__inner,.custom-theme .el-input-group__prepend div.el-select:hover .el-input__inner{border-color:transparent;background-color:transparent;color:inherit;border-top:0;border-bottom:0}.custom-theme .el-input-group__append .el-button,.custom-theme .el-input-group__append .el-input,.custom-theme .el-input-group__prepend .el-button,.custom-theme .el-input-group__prepend .el-input{font-size:inherit}.custom-theme .el-input-group__prepend{border-right:0;border-top-right-radius:0;border-bottom-right-radius:0}.custom-theme .el-input-group__append{border-left:0;border-top-left-radius:0;border-bottom-left-radius:0}.custom-theme .el-input-group--prepend .el-input__inner{border-top-left-radius:0;border-bottom-left-radius:0}.custom-theme .el-input-group--append .el-input__inner{border-top-right-radius:0;border-bottom-right-radius:0}.custom-theme .el-textarea{display:inline-block;width:100%;vertical-align:bottom}.custom-theme .el-textarea__inner{display:block;resize:vertical;padding:5px 15px;line-height:1.5;-webkit-box-sizing:border-box;box-sizing:border-box;width:100%;font-size:14px;color:#5a5e66;background-color:#fff;background-image:none;border:1px solid #d8dce5;border-radius:4px;-webkit-transition:border-color .2s cubic-bezier(.645,.045,.355,1);transition:border-color .2s cubic-bezier(.645,.045,.355,1)}.custom-theme .el-textarea__inner::-webkit-input-placeholder{color:#b4bccc}.custom-theme .el-textarea__inner:-ms-input-placeholder{color:#b4bccc}.custom-theme .el-textarea__inner::-ms-input-placeholder{color:#b4bccc}.custom-theme .el-textarea__inner::placeholder{color:#b4bccc}.custom-theme .el-textarea__inner:hover{border-color:#b4bccc}.custom-theme .el-textarea__inner:focus{outline:0;border-color:#262729}.custom-theme .el-textarea.is-disabled .el-textarea__inner{background-color:#f5f7fa;border-color:#dfe4ed;color:#b4bccc;cursor:not-allowed}.custom-theme .el-textarea.is-disabled .el-textarea__inner::-webkit-input-placeholder{color:#b4bccc}.custom-theme .el-textarea.is-disabled .el-textarea__inner:-ms-input-placeholder{color:#b4bccc}.custom-theme .el-textarea.is-disabled .el-textarea__inner::-ms-input-placeholder{color:#b4bccc}.custom-theme .el-textarea.is-disabled .el-textarea__inner::placeholder{color:#b4bccc}.custom-theme .el-scrollbar{overflow:hidden;position:relative}.custom-theme .el-scrollbar:active>.el-scrollbar__bar,.custom-theme .el-scrollbar:focus>.el-scrollbar__bar,.custom-theme .el-scrollbar:hover>.el-scrollbar__bar{opacity:1;-webkit-transition:opacity 340ms ease-out;transition:opacity 340ms ease-out}.custom-theme .el-scrollbar__wrap{overflow:scroll;height:100%}.custom-theme .el-scrollbar__wrap--hidden-default::-webkit-scrollbar{width:0;height:0}.custom-theme .el-scrollbar__thumb{position:relative;display:block;width:0;height:0;cursor:pointer;border-radius:inherit;background-color:rgba(135,141,153,.3);-webkit-transition:.3s background-color;transition:.3s background-color}.custom-theme .el-scrollbar__thumb:hover{background-color:rgba(135,141,153,.5)}.custom-theme .el-scrollbar__bar{position:absolute;right:2px;bottom:2px;z-index:1;border-radius:4px;opacity:0;-webkit-transition:opacity 120ms ease-out;transition:opacity 120ms ease-out}.custom-theme .el-scrollbar__bar.is-vertical{width:6px;top:2px}.custom-theme .el-scrollbar__bar.is-vertical>div{width:100%}.custom-theme .el-scrollbar__bar.is-horizontal{height:6px;left:2px}.custom-theme .el-scrollbar__bar.is-horizontal>div{height:100%}.custom-theme .el-popper .popper__arrow,.custom-theme .el-popper .popper__arrow::after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.custom-theme .el-popper .popper__arrow{border-width:6px;-webkit-filter:drop-shadow(0 2px 12px rgba(0, 0, 0, .03));filter:drop-shadow(0 2px 12px rgba(0, 0, 0, .03))}.custom-theme .el-popper .popper__arrow::after{content:" ";border-width:6px}.custom-theme .el-popper[x-placement^=top]{margin-bottom:12px}.custom-theme .el-popper[x-placement^=top] .popper__arrow{bottom:-6px;left:50%;margin-right:3px;border-top-color:#e6ebf5;border-bottom-width:0}.custom-theme .el-popper[x-placement^=top] .popper__arrow::after{bottom:1px;margin-left:-6px;border-top-color:#fff;border-bottom-width:0}.custom-theme .el-popper[x-placement^=bottom]{margin-top:12px}.custom-theme .el-popper[x-placement^=bottom] .popper__arrow{top:-6px;left:50%;margin-right:3px;border-top-width:0;border-bottom-color:#e6ebf5}.custom-theme .el-popper[x-placement^=bottom] .popper__arrow::after{top:1px;margin-left:-6px;border-top-width:0;border-bottom-color:#fff}.custom-theme .el-popper[x-placement^=right]{margin-left:12px}.custom-theme .el-popper[x-placement^=right] .popper__arrow{top:50%;left:-6px;margin-bottom:3px;border-right-color:#e6ebf5;border-left-width:0}.custom-theme .el-popper[x-placement^=right] .popper__arrow::after{bottom:-6px;left:1px;border-right-color:#fff;border-left-width:0}.custom-theme .el-popper[x-placement^=left]{margin-right:12px}.custom-theme .el-popper[x-placement^=left] .popper__arrow{top:50%;right:-6px;margin-bottom:3px;border-right-width:0;border-left-color:#e6ebf5}.custom-theme .el-popper[x-placement^=left] .popper__arrow::after{right:1px;bottom:-6px;margin-left:-6px;border-right-width:0;border-left-color:#fff}.custom-theme .el-popper .popper__arrow,.custom-theme .el-popper .popper__arrow::after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.custom-theme .el-popper .popper__arrow{border-width:6px;-webkit-filter:drop-shadow(0 2px 12px rgba(0, 0, 0, .03));filter:drop-shadow(0 2px 12px rgba(0, 0, 0, .03))}.custom-theme .el-popper .popper__arrow::after{content:" ";border-width:6px}.custom-theme .el-popper[x-placement^=top]{margin-bottom:12px}.custom-theme .el-popper[x-placement^=top] .popper__arrow{bottom:-6px;left:50%;margin-right:3px;border-top-color:#e6ebf5;border-bottom-width:0}.custom-theme .el-popper[x-placement^=top] .popper__arrow::after{bottom:1px;margin-left:-6px;border-top-color:#fff;border-bottom-width:0}.custom-theme .el-popper[x-placement^=bottom]{margin-top:12px}.custom-theme .el-popper[x-placement^=bottom] .popper__arrow{top:-6px;left:50%;margin-right:3px;border-top-width:0;border-bottom-color:#e6ebf5}.custom-theme .el-popper[x-placement^=bottom] .popper__arrow::after{top:1px;margin-left:-6px;border-top-width:0;border-bottom-color:#fff}.custom-theme .el-popper[x-placement^=right]{margin-left:12px}.custom-theme .el-popper[x-placement^=right] .popper__arrow{top:50%;left:-6px;margin-bottom:3px;border-right-color:#e6ebf5;border-left-width:0}.custom-theme .el-popper[x-placement^=right] .popper__arrow::after{bottom:-6px;left:1px;border-right-color:#fff;border-left-width:0}.custom-theme .el-popper[x-placement^=left]{margin-right:12px}.custom-theme .el-popper[x-placement^=left] .popper__arrow{top:50%;right:-6px;margin-bottom:3px;border-right-width:0;border-left-color:#e6ebf5}.custom-theme .el-popper[x-placement^=left] .popper__arrow::after{right:1px;bottom:-6px;margin-left:-6px;border-right-width:0;border-left-color:#fff}.custom-theme .el-popover{position:absolute;background:#fff;min-width:150px;border-radius:4px;border:1px solid #e6ebf5;padding:12px;z-index:2000;color:#5a5e66;line-height:1.4;text-align:justify;word-break:break-all;font-size:14px;-webkit-box-shadow:0 2px 12px 0 rgba(0,0,0,.1);box-shadow:0 2px 12px 0 rgba(0,0,0,.1)}.custom-theme .el-popover--plain{padding:18px 20px}.custom-theme .el-popover__title{color:#2d2f33;font-size:16px;line-height:1;margin-bottom:12px}.custom-theme .el-tooltip__popper{position:absolute;border-radius:4px;padding:10px;z-index:2000;font-size:12px;line-height:1.2}.custom-theme .el-tooltip__popper .popper__arrow,.custom-theme .el-tooltip__popper .popper__arrow::after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.custom-theme .el-tooltip__popper .popper__arrow{border-width:6px}.custom-theme .el-tooltip__popper .popper__arrow::after{content:" ";border-width:5px}.custom-theme .el-tooltip__popper[x-placement^=top]{margin-bottom:12px}.custom-theme .el-tooltip__popper[x-placement^=top] .popper__arrow{bottom:-6px;border-top-color:#2d2f33;border-bottom-width:0}.custom-theme .el-tooltip__popper[x-placement^=top] .popper__arrow::after{bottom:1px;margin-left:-5px;border-top-color:#2d2f33;border-bottom-width:0}.custom-theme .el-tooltip__popper[x-placement^=bottom]{margin-top:12px}.custom-theme .el-tooltip__popper[x-placement^=bottom] .popper__arrow{top:-6px;border-top-width:0;border-bottom-color:#2d2f33}.custom-theme .el-tooltip__popper[x-placement^=bottom] .popper__arrow::after{top:1px;margin-left:-5px;border-top-width:0;border-bottom-color:#2d2f33}.custom-theme .el-tooltip__popper[x-placement^=right]{margin-left:12px}.custom-theme .el-tooltip__popper[x-placement^=right] .popper__arrow{left:-6px;border-right-color:#2d2f33;border-left-width:0}.custom-theme .el-tooltip__popper[x-placement^=right] .popper__arrow::after{bottom:-5px;left:1px;border-right-color:#2d2f33;border-left-width:0}.custom-theme .el-tooltip__popper[x-placement^=left]{margin-right:12px}.custom-theme .el-tooltip__popper[x-placement^=left] .popper__arrow{right:-6px;border-right-width:0;border-left-color:#2d2f33}.custom-theme .el-tooltip__popper[x-placement^=left] .popper__arrow::after{right:1px;bottom:-5px;margin-left:-5px;border-right-width:0;border-left-color:#2d2f33}.custom-theme .el-tooltip__popper.is-dark{background:#2d2f33;color:#fff}.custom-theme .el-tooltip__popper.is-light{background:#fff;border:1px solid #2d2f33}.custom-theme .el-tooltip__popper.is-light[x-placement^=top] .popper__arrow{border-top-color:#2d2f33}.custom-theme .el-tooltip__popper.is-light[x-placement^=top] .popper__arrow::after{border-top-color:#fff}.custom-theme .el-tooltip__popper.is-light[x-placement^=bottom] .popper__arrow{border-bottom-color:#2d2f33}.custom-theme .el-tooltip__popper.is-light[x-placement^=bottom] .popper__arrow::after{border-bottom-color:#fff}.custom-theme .el-tooltip__popper.is-light[x-placement^=left] .popper__arrow{border-left-color:#2d2f33}.custom-theme .el-tooltip__popper.is-light[x-placement^=left] .popper__arrow::after{border-left-color:#fff}.custom-theme .el-tooltip__popper.is-light[x-placement^=right] .popper__arrow{border-right-color:#2d2f33}.custom-theme .el-tooltip__popper.is-light[x-placement^=right] .popper__arrow::after{border-right-color:#fff}.custom-theme .v-modal-enter{-webkit-animation:v-modal-in .2s ease;animation:v-modal-in .2s ease}.custom-theme .v-modal-leave{-webkit-animation:v-modal-out .2s ease forwards;animation:v-modal-out .2s ease forwards}@keyframes v-modal-in{0%{opacity:0}}@keyframes v-modal-out{100%{opacity:0}}.custom-theme .v-modal{position:fixed;left:0;top:0;width:100%;height:100%;opacity:.5;background:#000}.custom-theme .el-button{display:inline-block;line-height:1;white-space:nowrap;cursor:pointer;background:#fff;border:1px solid #d8dce5;border-color:#d8dce5;color:#5a5e66;-webkit-appearance:none;text-align:center;-webkit-box-sizing:border-box;box-sizing:border-box;outline:0;margin:0;-webkit-transition:.1s;transition:.1s;font-weight:500;-moz-user-select:none;-webkit-user-select:none;-ms-user-select:none;padding:12px 20px;font-size:14px;border-radius:4px}.custom-theme .el-button+.el-button{margin-left:10px}.custom-theme .el-button.is-round{padding:12px 20px}.custom-theme .el-button:focus,.custom-theme .el-button:hover{color:#262729;border-color:#bebebf;background-color:#e9e9ea}.custom-theme .el-button:active{color:#222325;border-color:#222325;outline:0}.custom-theme .el-button::-moz-focus-inner{border:0}.custom-theme .el-button [class*=el-icon-]+span{margin-left:5px}.custom-theme .el-button.is-plain:focus,.custom-theme .el-button.is-plain:hover{background:#fff;border-color:#262729;color:#262729}.custom-theme .el-button.is-plain:active{background:#fff;border-color:#222325;color:#222325;outline:0}.custom-theme .el-button.is-active{color:#222325;border-color:#222325}.custom-theme .el-button.is-disabled,.custom-theme .el-button.is-disabled:focus,.custom-theme .el-button.is-disabled:hover{color:#b4bccc;cursor:not-allowed;background-image:none;background-color:#fff;border-color:#e6ebf5}.custom-theme .el-button.is-disabled.el-button--text{background-color:transparent}.custom-theme .el-button.is-disabled.is-plain,.custom-theme .el-button.is-disabled.is-plain:focus,.custom-theme .el-button.is-disabled.is-plain:hover{background-color:#fff;border-color:#e6ebf5;color:#b4bccc}.custom-theme .el-button.is-loading{position:relative;pointer-events:none}.custom-theme .el-button.is-loading:before{pointer-events:none;content:'';position:absolute;left:-1px;top:-1px;right:-1px;bottom:-1px;border-radius:inherit;background-color:rgba(255,255,255,.35)}.custom-theme .el-button.is-round{border-radius:20px;padding:12px 23px}.custom-theme .el-button--primary{color:#fff;background-color:#262729;border-color:#262729}.custom-theme .el-button--primary:focus,.custom-theme .el-button--primary:hover{background:#515254;border-color:#515254;color:#fff}.custom-theme .el-button--primary:active{background:#222325;border-color:#222325;color:#fff;outline:0}.custom-theme .el-button--primary.is-active{background:#222325;border-color:#222325;color:#fff}.custom-theme .el-button--primary.is-disabled,.custom-theme .el-button--primary.is-disabled:active,.custom-theme .el-button--primary.is-disabled:focus,.custom-theme .el-button--primary.is-disabled:hover{color:#fff;background-color:#939394;border-color:#939394}.custom-theme .el-button--primary.is-plain{color:#262729;background:#e9e9ea;border-color:#a8a9a9}.custom-theme .el-button--primary.is-plain:focus,.custom-theme .el-button--primary.is-plain:hover{background:#262729;border-color:#262729;color:#fff}.custom-theme .el-button--primary.is-plain:active{background:#222325;border-color:#222325;color:#fff;outline:0}.custom-theme .el-button--primary.is-plain.is-disabled,.custom-theme .el-button--primary.is-plain.is-disabled:active,.custom-theme .el-button--primary.is-plain.is-disabled:focus,.custom-theme .el-button--primary.is-plain.is-disabled:hover{color:#7d7d7f;background-color:#e9e9ea;border-color:#d4d4d4}.custom-theme .el-button--success{color:#fff;background-color:#409167;border-color:#409167}.custom-theme .el-button--success:focus,.custom-theme .el-button--success:hover{background:#66a785;border-color:#66a785;color:#fff}.custom-theme .el-button--success:active{background:#3a835d;border-color:#3a835d;color:#fff;outline:0}.custom-theme .el-button--success.is-active{background:#3a835d;border-color:#3a835d;color:#fff}.custom-theme .el-button--success.is-disabled,.custom-theme .el-button--success.is-disabled:active,.custom-theme .el-button--success.is-disabled:focus,.custom-theme .el-button--success.is-disabled:hover{color:#fff;background-color:#a0c8b3;border-color:#a0c8b3}.custom-theme .el-button--success.is-plain{color:#409167;background:#ecf4f0;border-color:#b3d3c2}.custom-theme .el-button--success.is-plain:focus,.custom-theme .el-button--success.is-plain:hover{background:#409167;border-color:#409167;color:#fff}.custom-theme .el-button--success.is-plain:active{background:#3a835d;border-color:#3a835d;color:#fff;outline:0}.custom-theme .el-button--success.is-plain.is-disabled,.custom-theme .el-button--success.is-plain.is-disabled:active,.custom-theme .el-button--success.is-plain.is-disabled:focus,.custom-theme .el-button--success.is-plain.is-disabled:hover{color:#8cbda4;background-color:#ecf4f0;border-color:#d9e9e1}.custom-theme .el-button--warning{color:#fff;background-color:#9da408;border-color:#9da408}.custom-theme .el-button--warning:focus,.custom-theme .el-button--warning:hover{background:#b1b639;border-color:#b1b639;color:#fff}.custom-theme .el-button--warning:active{background:#8d9407;border-color:#8d9407;color:#fff;outline:0}.custom-theme .el-button--warning.is-active{background:#8d9407;border-color:#8d9407;color:#fff}.custom-theme .el-button--warning.is-disabled,.custom-theme .el-button--warning.is-disabled:active,.custom-theme .el-button--warning.is-disabled:focus,.custom-theme .el-button--warning.is-disabled:hover{color:#fff;background-color:#ced284;border-color:#ced284}.custom-theme .el-button--warning.is-plain{color:#9da408;background:#f5f6e6;border-color:#d8db9c}.custom-theme .el-button--warning.is-plain:focus,.custom-theme .el-button--warning.is-plain:hover{background:#9da408;border-color:#9da408;color:#fff}.custom-theme .el-button--warning.is-plain:active{background:#8d9407;border-color:#8d9407;color:#fff;outline:0}.custom-theme .el-button--warning.is-plain.is-disabled,.custom-theme .el-button--warning.is-plain.is-disabled:active,.custom-theme .el-button--warning.is-plain.is-disabled:focus,.custom-theme .el-button--warning.is-plain.is-disabled:hover{color:#c4c86b;background-color:#f5f6e6;border-color:#ebedce}.custom-theme .el-button--danger{color:#fff;background-color:#b3450e;border-color:#b3450e}.custom-theme .el-button--danger:focus,.custom-theme .el-button--danger:hover{background:#c26a3e;border-color:#c26a3e;color:#fff}.custom-theme .el-button--danger:active{background:#a13e0d;border-color:#a13e0d;color:#fff;outline:0}.custom-theme .el-button--danger.is-active{background:#a13e0d;border-color:#a13e0d;color:#fff}.custom-theme .el-button--danger.is-disabled,.custom-theme .el-button--danger.is-disabled:active,.custom-theme .el-button--danger.is-disabled:focus,.custom-theme .el-button--danger.is-disabled:hover{color:#fff;background-color:#d9a287;border-color:#d9a287}.custom-theme .el-button--danger.is-plain{color:#b3450e;background:#f7ece7;border-color:#e1b59f}.custom-theme .el-button--danger.is-plain:focus,.custom-theme .el-button--danger.is-plain:hover{background:#b3450e;border-color:#b3450e;color:#fff}.custom-theme .el-button--danger.is-plain:active{background:#a13e0d;border-color:#a13e0d;color:#fff;outline:0}.custom-theme .el-button--danger.is-plain.is-disabled,.custom-theme .el-button--danger.is-plain.is-disabled:active,.custom-theme .el-button--danger.is-plain.is-disabled:focus,.custom-theme .el-button--danger.is-plain.is-disabled:hover{color:#d18f6e;background-color:#f7ece7;border-color:#f0dacf}.custom-theme .el-button--info{color:#fff;background-color:#0a76a4;border-color:#0a76a4}.custom-theme .el-button--info:focus,.custom-theme .el-button--info:hover{background:#3b91b6;border-color:#3b91b6;color:#fff}.custom-theme .el-button--info:active{background:#096a94;border-color:#096a94;color:#fff;outline:0}.custom-theme .el-button--info.is-active{background:#096a94;border-color:#096a94;color:#fff}.custom-theme .el-button--info.is-disabled,.custom-theme .el-button--info.is-disabled:active,.custom-theme .el-button--info.is-disabled:focus,.custom-theme .el-button--info.is-disabled:hover{color:#fff;background-color:#85bbd2;border-color:#85bbd2}.custom-theme .el-button--info.is-plain{color:#0a76a4;background:#e7f1f6;border-color:#9dc8db}.custom-theme .el-button--info.is-plain:focus,.custom-theme .el-button--info.is-plain:hover{background:#0a76a4;border-color:#0a76a4;color:#fff}.custom-theme .el-button--info.is-plain:active{background:#096a94;border-color:#096a94;color:#fff;outline:0}.custom-theme .el-button--info.is-plain.is-disabled,.custom-theme .el-button--info.is-plain.is-disabled:active,.custom-theme .el-button--info.is-plain.is-disabled:focus,.custom-theme .el-button--info.is-plain.is-disabled:hover{color:#6cadc8;background-color:#e7f1f6;border-color:#cee4ed}.custom-theme .el-button--medium{padding:10px 20px;font-size:14px;border-radius:4px}.custom-theme .el-button--medium.is-round{padding:10px 20px}.custom-theme .el-button--small{padding:9px 15px;font-size:12px;border-radius:3px}.custom-theme .el-button--small.is-round{padding:9px 15px}.custom-theme .el-button--mini{padding:7px 15px;font-size:12px;border-radius:3px}.custom-theme .el-button--mini.is-round{padding:7px 15px}.custom-theme .el-button--text{border:none;color:#262729;background:0 0;padding-left:0;padding-right:0}.custom-theme .el-button--text:focus,.custom-theme .el-button--text:hover{color:#515254;border-color:transparent;background-color:transparent}.custom-theme .el-button--text:active{color:#222325;border-color:transparent;background-color:transparent}.custom-theme .el-button-group{display:inline-block;vertical-align:middle}.custom-theme .el-button-group::after,.custom-theme .el-button-group::before{display:table;content:""}.custom-theme .el-button-group::after{clear:both}.custom-theme .el-button-group .el-button{float:left;position:relative}.custom-theme .el-button-group .el-button+.el-button{margin-left:0}.custom-theme .el-button-group .el-button:first-child{border-top-right-radius:0;border-bottom-right-radius:0}.custom-theme .el-button-group .el-button:last-child{border-top-left-radius:0;border-bottom-left-radius:0}.custom-theme .el-button-group .el-button:not(:first-child):not(:last-child){border-radius:0}.custom-theme .el-button-group .el-button:not(:last-child){margin-right:-1px}.custom-theme .el-button-group .el-button:active,.custom-theme .el-button-group .el-button:focus,.custom-theme .el-button-group .el-button:hover{z-index:1}.custom-theme .el-button-group .el-button.is-active{z-index:1}.custom-theme .el-button-group .el-button--primary:first-child{border-right-color:rgba(255,255,255,.5)}.custom-theme .el-button-group .el-button--primary:last-child{border-left-color:rgba(255,255,255,.5)}.custom-theme .el-button-group .el-button--primary:not(:first-child):not(:last-child){border-left-color:rgba(255,255,255,.5);border-right-color:rgba(255,255,255,.5)}.custom-theme .el-button-group .el-button--success:first-child{border-right-color:rgba(255,255,255,.5)}.custom-theme .el-button-group .el-button--success:last-child{border-left-color:rgba(255,255,255,.5)}.custom-theme .el-button-group .el-button--success:not(:first-child):not(:last-child){border-left-color:rgba(255,255,255,.5);border-right-color:rgba(255,255,255,.5)}.custom-theme .el-button-group .el-button--warning:first-child{border-right-color:rgba(255,255,255,.5)}.custom-theme .el-button-group .el-button--warning:last-child{border-left-color:rgba(255,255,255,.5)}.custom-theme .el-button-group .el-button--warning:not(:first-child):not(:last-child){border-left-color:rgba(255,255,255,.5);border-right-color:rgba(255,255,255,.5)}.custom-theme .el-button-group .el-button--danger:first-child{border-right-color:rgba(255,255,255,.5)}.custom-theme .el-button-group .el-button--danger:last-child{border-left-color:rgba(255,255,255,.5)}.custom-theme .el-button-group .el-button--danger:not(:first-child):not(:last-child){border-left-color:rgba(255,255,255,.5);border-right-color:rgba(255,255,255,.5)}.custom-theme .el-button-group .el-button--info:first-child{border-right-color:rgba(255,255,255,.5)}.custom-theme .el-button-group .el-button--info:last-child{border-left-color:rgba(255,255,255,.5)}.custom-theme .el-button-group .el-button--info:not(:first-child):not(:last-child){border-left-color:rgba(255,255,255,.5);border-right-color:rgba(255,255,255,.5)}.custom-theme .el-input{position:relative;font-size:14px;display:inline-block;width:100%}.custom-theme .el-input::-webkit-scrollbar{z-index:11;width:6px}.custom-theme .el-input::-webkit-scrollbar:horizontal{height:6px}.custom-theme .el-input::-webkit-scrollbar-thumb{border-radius:5px;width:6px;background:#b4bccc}.custom-theme .el-input::-webkit-scrollbar-corner{background:#fff}.custom-theme .el-input::-webkit-scrollbar-track{background:#fff}.custom-theme .el-input::-webkit-scrollbar-track-piece{background:#fff;width:6px}.custom-theme .el-input__inner{-webkit-appearance:none;background-color:#fff;background-image:none;border-radius:4px;border:1px solid #d8dce5;-webkit-box-sizing:border-box;box-sizing:border-box;color:#5a5e66;display:inline-block;font-size:inherit;height:40px;line-height:1;outline:0;padding:0 15px;-webkit-transition:border-color .2s cubic-bezier(.645,.045,.355,1);transition:border-color .2s cubic-bezier(.645,.045,.355,1);width:100%}.custom-theme .el-input__inner::-webkit-input-placeholder{color:#b4bccc}.custom-theme .el-input__inner:-ms-input-placeholder{color:#b4bccc}.custom-theme .el-input__inner::-ms-input-placeholder{color:#b4bccc}.custom-theme .el-input__inner::placeholder{color:#b4bccc}.custom-theme .el-input__inner:hover{border-color:#b4bccc}.custom-theme .el-input__inner:focus{outline:0;border-color:#262729}.custom-theme .el-input__suffix{position:absolute;height:100%;right:5px;top:0;text-align:center;color:#b4bccc;-webkit-transition:all .3s;transition:all .3s;pointer-events:none}.custom-theme .el-input__suffix-inner{pointer-events:all}.custom-theme .el-input__prefix{position:absolute;height:100%;left:5px;top:0;text-align:center;color:#b4bccc;-webkit-transition:all .3s;transition:all .3s}.custom-theme .el-input__icon{height:100%;width:25px;text-align:center;-webkit-transition:all .3s;transition:all .3s;line-height:40px}.custom-theme .el-input__icon:after{content:'';height:100%;width:0;display:inline-block;vertical-align:middle}.custom-theme .el-input__validateIcon{pointer-events:none}.custom-theme .el-input.is-active .el-input__inner{outline:0;border-color:#262729}.custom-theme .el-input.is-disabled .el-input__inner{background-color:#f5f7fa;border-color:#dfe4ed;color:#b4bccc;cursor:not-allowed}.custom-theme .el-input.is-disabled .el-input__inner::-webkit-input-placeholder{color:#b4bccc}.custom-theme .el-input.is-disabled .el-input__inner:-ms-input-placeholder{color:#b4bccc}.custom-theme .el-input.is-disabled .el-input__inner::-ms-input-placeholder{color:#b4bccc}.custom-theme .el-input.is-disabled .el-input__inner::placeholder{color:#b4bccc}.custom-theme .el-input.is-disabled .el-input__icon{cursor:not-allowed}.custom-theme .el-input--suffix .el-input__inner{padding-right:30px}.custom-theme .el-input--prefix .el-input__inner{padding-left:30px}.custom-theme .el-input--medium{font-size:14px}.custom-theme .el-input--medium .el-input__inner{height:36px}.custom-theme .el-input--medium .el-input__icon{line-height:36px}.custom-theme .el-input--small{font-size:13px}.custom-theme .el-input--small .el-input__inner{height:32px}.custom-theme .el-input--small .el-input__icon{line-height:32px}.custom-theme .el-input--mini{font-size:12px}.custom-theme .el-input--mini .el-input__inner{height:28px}.custom-theme .el-input--mini .el-input__icon{line-height:28px}.custom-theme .el-input-group{line-height:normal;display:inline-table;width:100%;border-collapse:separate}.custom-theme .el-input-group>.el-input__inner{vertical-align:middle;display:table-cell}.custom-theme .el-input-group__append,.custom-theme .el-input-group__prepend{background-color:#f5f7fa;color:#0a76a4;vertical-align:middle;display:table-cell;position:relative;border:1px solid #d8dce5;border-radius:4px;padding:0 20px;width:1px;white-space:nowrap}.custom-theme .el-input-group__append:focus,.custom-theme .el-input-group__prepend:focus{outline:0}.custom-theme .el-input-group__append .el-button,.custom-theme .el-input-group__append .el-select,.custom-theme .el-input-group__prepend .el-button,.custom-theme .el-input-group__prepend .el-select{display:inline-block;margin:-20px}.custom-theme .el-input-group__append button.el-button,.custom-theme .el-input-group__append div.el-select .el-input__inner,.custom-theme .el-input-group__append div.el-select:hover .el-input__inner,.custom-theme .el-input-group__prepend button.el-button,.custom-theme .el-input-group__prepend div.el-select .el-input__inner,.custom-theme .el-input-group__prepend div.el-select:hover .el-input__inner{border-color:transparent;background-color:transparent;color:inherit;border-top:0;border-bottom:0}.custom-theme .el-input-group__append .el-button,.custom-theme .el-input-group__append .el-input,.custom-theme .el-input-group__prepend .el-button,.custom-theme .el-input-group__prepend .el-input{font-size:inherit}.custom-theme .el-input-group__prepend{border-right:0;border-top-right-radius:0;border-bottom-right-radius:0}.custom-theme .el-input-group__append{border-left:0;border-top-left-radius:0;border-bottom-left-radius:0}.custom-theme .el-input-group--prepend .el-input__inner{border-top-left-radius:0;border-bottom-left-radius:0}.custom-theme .el-input-group--append .el-input__inner{border-top-right-radius:0;border-bottom-right-radius:0}.custom-theme .el-textarea{display:inline-block;width:100%;vertical-align:bottom}.custom-theme .el-textarea__inner{display:block;resize:vertical;padding:5px 15px;line-height:1.5;-webkit-box-sizing:border-box;box-sizing:border-box;width:100%;font-size:14px;color:#5a5e66;background-color:#fff;background-image:none;border:1px solid #d8dce5;border-radius:4px;-webkit-transition:border-color .2s cubic-bezier(.645,.045,.355,1);transition:border-color .2s cubic-bezier(.645,.045,.355,1)}.custom-theme .el-textarea__inner::-webkit-input-placeholder{color:#b4bccc}.custom-theme .el-textarea__inner:-ms-input-placeholder{color:#b4bccc}.custom-theme .el-textarea__inner::-ms-input-placeholder{color:#b4bccc}.custom-theme .el-textarea__inner::placeholder{color:#b4bccc}.custom-theme .el-textarea__inner:hover{border-color:#b4bccc}.custom-theme .el-textarea__inner:focus{outline:0;border-color:#262729}.custom-theme .el-textarea.is-disabled .el-textarea__inner{background-color:#f5f7fa;border-color:#dfe4ed;color:#b4bccc;cursor:not-allowed}.custom-theme .el-textarea.is-disabled .el-textarea__inner::-webkit-input-placeholder{color:#b4bccc}.custom-theme .el-textarea.is-disabled .el-textarea__inner:-ms-input-placeholder{color:#b4bccc}.custom-theme .el-textarea.is-disabled .el-textarea__inner::-ms-input-placeholder{color:#b4bccc}.custom-theme .el-textarea.is-disabled .el-textarea__inner::placeholder{color:#b4bccc}.custom-theme .el-message-box{display:inline-block;width:420px;padding-bottom:10px;vertical-align:middle;background-color:#fff;border-radius:4px;border:1px solid #e6ebf5;font-size:18px;-webkit-box-shadow:0 2px 12px 0 rgba(0,0,0,.1);box-shadow:0 2px 12px 0 rgba(0,0,0,.1);text-align:left;overflow:hidden;-webkit-backface-visibility:hidden;backface-visibility:hidden}.custom-theme .el-message-box__wrapper{position:fixed;top:0;bottom:0;left:0;right:0;text-align:center}.custom-theme .el-message-box__wrapper::after{content:"";display:inline-block;height:100%;width:0;vertical-align:middle}.custom-theme .el-message-box__header{position:relative;padding:15px;padding-bottom:10px}.custom-theme .el-message-box__title{padding-left:0;margin-bottom:0;font-size:18px;line-height:1;color:#2d2f33}.custom-theme .el-message-box__headerbtn{position:absolute;top:15px;right:15px;padding:0;border:none;outline:0;background:0 0;font-size:16px;cursor:pointer}.custom-theme .el-message-box__headerbtn .el-message-box__close{color:#0a76a4}.custom-theme .el-message-box__headerbtn:focus .el-message-box__close,.custom-theme .el-message-box__headerbtn:hover .el-message-box__close{color:#262729}.custom-theme .el-message-box__content{position:relative;padding:10px 15px;color:#5a5e66;font-size:14px}.custom-theme .el-message-box__input{padding-top:15px}.custom-theme .el-message-box__input input.invalid{border-color:#b3450e}.custom-theme .el-message-box__input input.invalid:focus{border-color:#b3450e}.custom-theme .el-message-box__status{position:absolute;top:50%;-webkit-transform:translateY(-50%);transform:translateY(-50%);font-size:24px!important}.custom-theme .el-message-box__status::before{padding-left:1px}.custom-theme .el-message-box__status+.el-message-box__message{padding-left:36px;padding-right:12px}.custom-theme .el-message-box__status.el-icon-success{color:#409167}.custom-theme .el-message-box__status.el-icon-info{color:#0a76a4}.custom-theme .el-message-box__status.el-icon-warning{color:#9da408}.custom-theme .el-message-box__status.el-icon-error{color:#b3450e}.custom-theme .el-message-box__message{margin:0}.custom-theme .el-message-box__message p{margin:0;line-height:24px}.custom-theme .el-message-box__errormsg{color:#b3450e;font-size:12px;min-height:18px;margin-top:2px}.custom-theme .el-message-box__btns{padding:5px 15px 0;text-align:right}.custom-theme .el-message-box__btns button:nth-child(2){margin-left:10px}.custom-theme .el-message-box__btns-reverse{-webkit-box-orient:horizontal;-webkit-box-direction:reverse;-ms-flex-direction:row-reverse;flex-direction:row-reverse}.custom-theme .el-message-box--center{padding-bottom:30px}.custom-theme .el-message-box--center .el-message-box__header{padding-top:30px}.custom-theme .el-message-box--center .el-message-box__title{position:relative;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}.custom-theme .el-message-box--center .el-message-box__status{position:relative;top:auto;padding-right:5px;text-align:center;-webkit-transform:translateY(-1px);transform:translateY(-1px)}.custom-theme .el-message-box--center .el-message-box__message{margin-left:0}.custom-theme .el-message-box--center .el-message-box__btns,.custom-theme .el-message-box--center .el-message-box__content{text-align:center}.custom-theme .el-message-box--center .el-message-box__content{padding-left:27px;padding-right:27px}.custom-theme .msgbox-fade-enter-active{-webkit-animation:msgbox-fade-in .3s;animation:msgbox-fade-in .3s}.custom-theme .msgbox-fade-leave-active{-webkit-animation:msgbox-fade-out .3s;animation:msgbox-fade-out .3s}@-webkit-keyframes msgbox-fade-in{0%{-webkit-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0);opacity:0}100%{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0);opacity:1}}@keyframes msgbox-fade-in{0%{-webkit-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0);opacity:0}100%{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0);opacity:1}}@-webkit-keyframes msgbox-fade-out{0%{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0);opacity:1}100%{-webkit-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0);opacity:0}}@keyframes msgbox-fade-out{0%{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0);opacity:1}100%{-webkit-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0);opacity:0}}.custom-theme .el-breadcrumb{font-size:14px;line-height:1}.custom-theme .el-breadcrumb::after,.custom-theme .el-breadcrumb::before{display:table;content:""}.custom-theme .el-breadcrumb::after{clear:both}.custom-theme .el-breadcrumb__separator{margin:0 9px;font-weight:700;color:#b4bccc}.custom-theme .el-breadcrumb__separator[class*=icon]{margin:0 6px;font-weight:400}.custom-theme .el-breadcrumb__item{float:left}.custom-theme .el-breadcrumb__inner,.custom-theme .el-breadcrumb__inner a{font-weight:700;-webkit-transition:color .2s cubic-bezier(.645,.045,.355,1);transition:color .2s cubic-bezier(.645,.045,.355,1);color:#2d2f33}.custom-theme .el-breadcrumb__inner a:hover,.custom-theme .el-breadcrumb__inner:hover{color:#262729;cursor:pointer}.custom-theme .el-breadcrumb__item:last-child .el-breadcrumb__inner,.custom-theme .el-breadcrumb__item:last-child .el-breadcrumb__inner a,.custom-theme .el-breadcrumb__item:last-child .el-breadcrumb__inner a:hover,.custom-theme .el-breadcrumb__item:last-child .el-breadcrumb__inner:hover{font-weight:400;color:#5a5e66;cursor:text}.custom-theme .el-breadcrumb__item:last-child .el-breadcrumb__separator{display:none}.custom-theme .el-form--label-left .el-form-item__label{text-align:left}.custom-theme .el-form--label-top .el-form-item__label{float:none;display:inline-block;text-align:left;padding:0 0 10px 0}.custom-theme .el-form--inline .el-form-item{display:inline-block;margin-right:10px;vertical-align:top}.custom-theme .el-form--inline .el-form-item__label{float:none;display:inline-block}.custom-theme .el-form--inline .el-form-item__content{display:inline-block;vertical-align:top}.custom-theme .el-form--inline.el-form--label-top .el-form-item__content{display:block}.custom-theme .el-form-item{margin-bottom:22px}.custom-theme .el-form-item::after,.custom-theme .el-form-item::before{display:table;content:""}.custom-theme .el-form-item::after{clear:both}.custom-theme .el-form-item .el-form-item{margin-bottom:0}.custom-theme .el-form-item .el-input__validateIcon{display:none}.custom-theme .el-form-item--medium .el-form-item__label{line-height:36px}.custom-theme .el-form-item--medium .el-form-item__content{line-height:36px}.custom-theme .el-form-item--small .el-form-item__label{line-height:32px}.custom-theme .el-form-item--small .el-form-item__content{line-height:32px}.custom-theme .el-form-item--small.el-form-item{margin-bottom:18px}.custom-theme .el-form-item--small .el-form-item__error{padding-top:2px}.custom-theme .el-form-item--mini .el-form-item__label{line-height:28px}.custom-theme .el-form-item--mini .el-form-item__content{line-height:28px}.custom-theme .el-form-item--mini.el-form-item{margin-bottom:18px}.custom-theme .el-form-item--mini .el-form-item__error{padding-top:1px}.custom-theme .el-form-item__label{text-align:right;vertical-align:middle;float:left;font-size:14px;color:#5a5e66;line-height:40px;padding:0 12px 0 0;-webkit-box-sizing:border-box;box-sizing:border-box}.custom-theme .el-form-item__content{line-height:40px;position:relative;font-size:14px}.custom-theme .el-form-item__content::after,.custom-theme .el-form-item__content::before{display:table;content:""}.custom-theme .el-form-item__content::after{clear:both}.custom-theme .el-form-item__error{color:#b3450e;font-size:12px;line-height:1;padding-top:4px;position:absolute;top:100%;left:0}.custom-theme .el-form-item__error--inline{position:relative;top:auto;left:auto;display:inline-block;margin-left:10px}.custom-theme .el-form-item.is-required .el-form-item__label:before{content:'*';color:#b3450e;margin-right:4px}.custom-theme .el-form-item.is-error .el-input__inner,.custom-theme .el-form-item.is-error .el-input__inner:focus,.custom-theme .el-form-item.is-error .el-textarea__inner,.custom-theme .el-form-item.is-error .el-textarea__inner:focus{border-color:#b3450e}.custom-theme .el-form-item.is-error .el-input-group__append .el-input__inner,.custom-theme .el-form-item.is-error .el-input-group__prepend .el-input__inner{border-color:transparent}.custom-theme .el-form-item.is-error .el-input__validateIcon{color:#b3450e}.custom-theme .el-form-item.is-success .el-input__inner,.custom-theme .el-form-item.is-success .el-input__inner:focus,.custom-theme .el-form-item.is-success .el-textarea__inner,.custom-theme .el-form-item.is-success .el-textarea__inner:focus{border-color:#409167}.custom-theme .el-form-item.is-success .el-input-group__append .el-input__inner,.custom-theme .el-form-item.is-success .el-input-group__prepend .el-input__inner{border-color:transparent}.custom-theme .el-form-item.is-success .el-input__validateIcon{color:#409167}.custom-theme .el-form-item--feedback .el-input__validateIcon{display:inline-block}.custom-theme .el-tabs__header{padding:0;position:relative;margin:0 0 15px}.custom-theme .el-tabs__active-bar{position:absolute;bottom:0;left:0;height:2px;background-color:#262729;z-index:1;-webkit-transition:-webkit-transform .3s cubic-bezier(.645,.045,.355,1);transition:-webkit-transform .3s cubic-bezier(.645,.045,.355,1);transition:transform .3s cubic-bezier(.645,.045,.355,1);transition:transform .3s cubic-bezier(.645,.045,.355,1),-webkit-transform .3s cubic-bezier(.645,.045,.355,1);list-style:none}.custom-theme .el-tabs__new-tab{float:right;border:1px solid #d3dce6;height:18px;width:18px;line-height:18px;margin:12px 0 9px 10px;border-radius:3px;text-align:center;font-size:12px;color:#d3dce6;cursor:pointer;-webkit-transition:all .15s;transition:all .15s}.custom-theme .el-tabs__new-tab .el-icon-plus{-webkit-transform:scale(.8,.8);transform:scale(.8,.8)}.custom-theme .el-tabs__new-tab:hover{color:#262729}.custom-theme .el-tabs__nav-wrap{overflow:hidden;margin-bottom:-1px;position:relative}.custom-theme .el-tabs__nav-wrap::after{content:"";position:absolute;left:0;bottom:0;width:100%;height:2px;background-color:#dfe4ed;z-index:1}.custom-theme .el-tabs__nav-wrap.is-scrollable{padding:0 20px;-webkit-box-sizing:border-box;box-sizing:border-box}.custom-theme .el-tabs__nav-scroll{overflow:hidden}.custom-theme .el-tabs__nav-next,.custom-theme .el-tabs__nav-prev{position:absolute;cursor:pointer;line-height:44px;font-size:12px;color:#878d99}.custom-theme .el-tabs__nav-next{right:0}.custom-theme .el-tabs__nav-prev{left:0}.custom-theme .el-tabs__nav{white-space:nowrap;position:relative;-webkit-transition:-webkit-transform .3s;transition:-webkit-transform .3s;transition:transform .3s;transition:transform .3s,-webkit-transform .3s;float:left;z-index:2}.custom-theme .el-tabs__item{padding:0 20px;height:40px;-webkit-box-sizing:border-box;box-sizing:border-box;line-height:40px;display:inline-block;list-style:none;font-size:14px;font-weight:500;color:#2d2f33;position:relative}.custom-theme .el-tabs__item:focus,.custom-theme .el-tabs__item:focus:active{outline:0}.custom-theme .el-tabs__item .el-icon-close{border-radius:50%;text-align:center;-webkit-transition:all .3s cubic-bezier(.645,.045,.355,1);transition:all .3s cubic-bezier(.645,.045,.355,1);margin-left:5px}.custom-theme .el-tabs__item .el-icon-close:before{-webkit-transform:scale(.9);transform:scale(.9);display:inline-block}.custom-theme .el-tabs__item .el-icon-close:hover{background-color:#b4bccc;color:#fff}.custom-theme .el-tabs__item.is-active{color:#262729}.custom-theme .el-tabs__item:hover{color:#262729;cursor:pointer}.custom-theme .el-tabs__item.is-disabled{color:#b4bccc;cursor:default}.custom-theme .el-tabs__content{overflow:hidden;position:relative}.custom-theme .el-tabs--card>.el-tabs__header{border-bottom:1px solid #dfe4ed}.custom-theme .el-tabs--card>.el-tabs__header .el-tabs__nav-wrap::after{content:none}.custom-theme .el-tabs--card>.el-tabs__header .el-tabs__nav{border:1px solid #dfe4ed;border-bottom:none;border-radius:4px 4px 0 0}.custom-theme .el-tabs--card>.el-tabs__header .el-tabs__active-bar{display:none}.custom-theme .el-tabs--card>.el-tabs__header .el-tabs__item .el-icon-close{position:relative;font-size:12px;width:0;height:14px;vertical-align:middle;line-height:15px;overflow:hidden;top:-1px;right:-2px;-webkit-transform-origin:100% 50%;transform-origin:100% 50%}.custom-theme .el-tabs--card>.el-tabs__header .el-tabs__item{border-bottom:1px solid transparent;border-left:1px solid #dfe4ed;-webkit-transition:color .3s cubic-bezier(.645,.045,.355,1),padding .3s cubic-bezier(.645,.045,.355,1);transition:color .3s cubic-bezier(.645,.045,.355,1),padding .3s cubic-bezier(.645,.045,.355,1)}.custom-theme .el-tabs--card>.el-tabs__header .el-tabs__item:first-child{border-left:none}.custom-theme .el-tabs--card>.el-tabs__header .el-tabs__item.is-closable:hover{padding-left:13px;padding-right:13px}.custom-theme .el-tabs--card>.el-tabs__header .el-tabs__item.is-closable:hover .el-icon-close{width:14px}.custom-theme .el-tabs--card>.el-tabs__header .el-tabs__item.is-active{border-bottom-color:#fff}.custom-theme .el-tabs--card>.el-tabs__header .el-tabs__item.is-active.is-closable{padding-left:20px;padding-right:20px}.custom-theme .el-tabs--card>.el-tabs__header .el-tabs__item.is-active.is-closable .el-icon-close{width:14px}.custom-theme .el-tabs--border-card{background:#fff;border:1px solid #d8dce5;-webkit-box-shadow:0 2px 4px 0 rgba(0,0,0,.12),0 0 6px 0 rgba(0,0,0,.04);box-shadow:0 2px 4px 0 rgba(0,0,0,.12),0 0 6px 0 rgba(0,0,0,.04)}.custom-theme .el-tabs--border-card>.el-tabs__content{padding:15px}.custom-theme .el-tabs--border-card>.el-tabs__header{background-color:#f5f7fa;border-bottom:1px solid #dfe4ed;margin:0}.custom-theme .el-tabs--border-card>.el-tabs__header .el-tabs__nav-wrap::after{content:none}.custom-theme .el-tabs--border-card>.el-tabs__header .el-tabs__item{-webkit-transition:all .3s cubic-bezier(.645,.045,.355,1);transition:all .3s cubic-bezier(.645,.045,.355,1);border:1px solid transparent;margin:-1px -1px 0;color:#878d99}.custom-theme .el-tabs--border-card>.el-tabs__header .el-tabs__item.is-active{color:#262729;background-color:#fff;border-right-color:#d8dce5;border-left-color:#d8dce5}.custom-theme .el-tabs--border-card>.el-tabs__header .el-tabs__item:hover{color:#262729}.custom-theme .el-tabs--bottom:not(.el-tabs--border-card):not(.el-tabs--card) .el-tabs__item:nth-child(2),.custom-theme .el-tabs--top:not(.el-tabs--border-card):not(.el-tabs--card) .el-tabs__item:nth-child(2){padding-left:0}.custom-theme .el-tabs--bottom .el-tabs__header{margin-bottom:0;margin-top:10px}.custom-theme .el-tabs--bottom.el-tabs--border-card .el-tabs__header{border-bottom:0;border-top:1px solid #d8dce5}.custom-theme .el-tabs--bottom.el-tabs--border-card .el-tabs__nav-wrap{margin-top:-1px;margin-bottom:0}.custom-theme .el-tabs--bottom.el-tabs--border-card .el-tabs__item{border:1px solid transparent;margin:0 -1px -1px -1px}.custom-theme .el-tabs--left,.custom-theme .el-tabs--right{overflow:hidden}.custom-theme .el-tabs--left .el-tabs__header,.custom-theme .el-tabs--left .el-tabs__nav-scroll,.custom-theme .el-tabs--left .el-tabs__nav-wrap,.custom-theme .el-tabs--right .el-tabs__header,.custom-theme .el-tabs--right .el-tabs__nav-scroll,.custom-theme .el-tabs--right .el-tabs__nav-wrap{height:100%}.custom-theme .el-tabs--left .el-tabs__active-bar,.custom-theme .el-tabs--right .el-tabs__active-bar{top:0;bottom:auto;width:2px;height:auto}.custom-theme .el-tabs--left .el-tabs__nav-wrap,.custom-theme .el-tabs--right .el-tabs__nav-wrap{margin-bottom:0}.custom-theme .el-tabs--left .el-tabs__nav-wrap.is-scrollable,.custom-theme .el-tabs--right .el-tabs__nav-wrap.is-scrollable{padding:30px 0}.custom-theme .el-tabs--left .el-tabs__nav-wrap::after,.custom-theme .el-tabs--right .el-tabs__nav-wrap::after{height:100%;width:2px;bottom:auto;top:0}.custom-theme .el-tabs--left .el-tabs__nav,.custom-theme .el-tabs--right .el-tabs__nav{float:none}.custom-theme .el-tabs--left .el-tabs__item,.custom-theme .el-tabs--right .el-tabs__item{display:block}.custom-theme .el-tabs--left .el-tabs__nav-next,.custom-theme .el-tabs--left .el-tabs__nav-prev,.custom-theme .el-tabs--right .el-tabs__nav-next,.custom-theme .el-tabs--right .el-tabs__nav-prev{height:30px;line-height:30px;width:100%;text-align:center;cursor:pointer}.custom-theme .el-tabs--left .el-tabs__nav-next i,.custom-theme .el-tabs--left .el-tabs__nav-prev i,.custom-theme .el-tabs--right .el-tabs__nav-next i,.custom-theme .el-tabs--right .el-tabs__nav-prev i{-webkit-transform:rotateZ(90deg);transform:rotateZ(90deg)}.custom-theme .el-tabs--left .el-tabs__nav-prev,.custom-theme .el-tabs--right .el-tabs__nav-prev{left:auto;top:0}.custom-theme .el-tabs--left .el-tabs__nav-next,.custom-theme .el-tabs--right .el-tabs__nav-next{right:auto;bottom:0}.custom-theme .el-tabs--left .el-tabs__header{float:left;margin-bottom:0;margin-right:10px}.custom-theme .el-tabs--left .el-tabs__nav-wrap{margin-right:-1px}.custom-theme .el-tabs--left .el-tabs__nav-wrap::after{left:auto;right:0}.custom-theme .el-tabs--left .el-tabs__active-bar{right:0;left:auto}.custom-theme .el-tabs--left .el-tabs__item{text-align:right}.custom-theme .el-tabs--left.el-tabs--card .el-tabs__active-bar{display:none}.custom-theme .el-tabs--left.el-tabs--card .el-tabs__item{border-left:none;border-right:1px solid #dfe4ed;border-bottom:none;border-top:1px solid #dfe4ed}.custom-theme .el-tabs--left.el-tabs--card .el-tabs__item:first-child{border-right:1px solid #dfe4ed;border-top:none}.custom-theme .el-tabs--left.el-tabs--card .el-tabs__item.is-active{border:1px solid #dfe4ed;border-right-color:#fff;border-left:none;border-bottom:none}.custom-theme .el-tabs--left.el-tabs--card .el-tabs__item.is-active:first-child{border-top:none}.custom-theme .el-tabs--left.el-tabs--card .el-tabs__item.is-active:last-child{border-bottom:none}.custom-theme .el-tabs--left.el-tabs--card .el-tabs__nav{border-radius:4px 0 0 4px;border-bottom:1px solid #dfe4ed;border-right:none}.custom-theme .el-tabs--left.el-tabs--card .el-tabs__new-tab{float:none}.custom-theme .el-tabs--left.el-tabs--border-card .el-tabs__header{border-right:1px solid #dfe4ed}.custom-theme .el-tabs--left.el-tabs--border-card .el-tabs__item{border:1px solid transparent;margin:-1px 0 -1px -1px}.custom-theme .el-tabs--left.el-tabs--border-card .el-tabs__item.is-active{border-color:transparent;border-top-color:#d1dbe5;border-bottom-color:#d1dbe5}.custom-theme .el-tabs--right .el-tabs__header{float:right;margin-bottom:0;margin-left:10px}.custom-theme .el-tabs--right .el-tabs__nav-wrap{margin-left:-1px}.custom-theme .el-tabs--right .el-tabs__nav-wrap::after{left:0;right:auto}.custom-theme .el-tabs--right .el-tabs__active-bar{left:0}.custom-theme .el-tabs--right.el-tabs--card .el-tabs__active-bar{display:none}.custom-theme .el-tabs--right.el-tabs--card .el-tabs__item{border-bottom:none;border-top:1px solid #dfe4ed}.custom-theme .el-tabs--right.el-tabs--card .el-tabs__item:first-child{border-left:1px solid #dfe4ed;border-top:none}.custom-theme .el-tabs--right.el-tabs--card .el-tabs__item.is-active{border:1px solid #dfe4ed;border-left-color:#fff;border-right:none;border-bottom:none}.custom-theme .el-tabs--right.el-tabs--card .el-tabs__item.is-active:first-child{border-top:none}.custom-theme .el-tabs--right.el-tabs--card .el-tabs__item.is-active:last-child{border-bottom:none}.custom-theme .el-tabs--right.el-tabs--card .el-tabs__nav{border-radius:0 4px 4px 0;border-bottom:1px solid #dfe4ed;border-left:none}.custom-theme .el-tabs--right.el-tabs--border-card .el-tabs__header{border-left:1px solid #dfe4ed}.custom-theme .el-tabs--right.el-tabs--border-card .el-tabs__item{border:1px solid transparent;margin:-1px -1px -1px 0}.custom-theme .el-tabs--right.el-tabs--border-card .el-tabs__item.is-active{border-color:transparent;border-top-color:#d1dbe5;border-bottom-color:#d1dbe5}.custom-theme .slideInLeft-transition,.custom-theme .slideInRight-transition{display:inline-block}.custom-theme .slideInRight-enter{-webkit-animation:slideInRight-enter .3s;animation:slideInRight-enter .3s}.custom-theme .slideInRight-leave{position:absolute;left:0;right:0;-webkit-animation:slideInRight-leave .3s;animation:slideInRight-leave .3s}.custom-theme .slideInLeft-enter{-webkit-animation:slideInLeft-enter .3s;animation:slideInLeft-enter .3s}.custom-theme .slideInLeft-leave{position:absolute;left:0;right:0;-webkit-animation:slideInLeft-leave .3s;animation:slideInLeft-leave .3s}@-webkit-keyframes slideInRight-enter{0%{opacity:0;-webkit-transform-origin:0 0;transform-origin:0 0;-webkit-transform:translateX(100%);transform:translateX(100%)}to{opacity:1;-webkit-transform-origin:0 0;transform-origin:0 0;-webkit-transform:translateX(0);transform:translateX(0)}}@keyframes slideInRight-enter{0%{opacity:0;-webkit-transform-origin:0 0;transform-origin:0 0;-webkit-transform:translateX(100%);transform:translateX(100%)}to{opacity:1;-webkit-transform-origin:0 0;transform-origin:0 0;-webkit-transform:translateX(0);transform:translateX(0)}}@-webkit-keyframes slideInRight-leave{0%{-webkit-transform-origin:0 0;transform-origin:0 0;-webkit-transform:translateX(0);transform:translateX(0);opacity:1}100%{-webkit-transform-origin:0 0;transform-origin:0 0;-webkit-transform:translateX(100%);transform:translateX(100%);opacity:0}}@keyframes slideInRight-leave{0%{-webkit-transform-origin:0 0;transform-origin:0 0;-webkit-transform:translateX(0);transform:translateX(0);opacity:1}100%{-webkit-transform-origin:0 0;transform-origin:0 0;-webkit-transform:translateX(100%);transform:translateX(100%);opacity:0}}@-webkit-keyframes slideInLeft-enter{0%{opacity:0;-webkit-transform-origin:0 0;transform-origin:0 0;-webkit-transform:translateX(-100%);transform:translateX(-100%)}to{opacity:1;-webkit-transform-origin:0 0;transform-origin:0 0;-webkit-transform:translateX(0);transform:translateX(0)}}@keyframes slideInLeft-enter{0%{opacity:0;-webkit-transform-origin:0 0;transform-origin:0 0;-webkit-transform:translateX(-100%);transform:translateX(-100%)}to{opacity:1;-webkit-transform-origin:0 0;transform-origin:0 0;-webkit-transform:translateX(0);transform:translateX(0)}}@-webkit-keyframes slideInLeft-leave{0%{-webkit-transform-origin:0 0;transform-origin:0 0;-webkit-transform:translateX(0);transform:translateX(0);opacity:1}100%{-webkit-transform-origin:0 0;transform-origin:0 0;-webkit-transform:translateX(-100%);transform:translateX(-100%);opacity:0}}@keyframes slideInLeft-leave{0%{-webkit-transform-origin:0 0;transform-origin:0 0;-webkit-transform:translateX(0);transform:translateX(0);opacity:1}100%{-webkit-transform-origin:0 0;transform-origin:0 0;-webkit-transform:translateX(-100%);transform:translateX(-100%);opacity:0}}.custom-theme .el-tag{background-color:rgba(38,39,41,.1);display:inline-block;padding:0 10px;height:32px;line-height:30px;font-size:12px;color:#262729;border-radius:4px;-webkit-box-sizing:border-box;box-sizing:border-box;border:1px solid rgba(38,39,41,.2);white-space:nowrap}.custom-theme .el-tag .el-icon-close{border-radius:50%;text-align:center;position:relative;cursor:pointer;font-size:12px;height:18px;width:18px;line-height:18px;vertical-align:middle;top:-1px;right:-5px;color:#262729}.custom-theme .el-tag .el-icon-close::before{display:block}.custom-theme .el-tag .el-icon-close:hover{background-color:#262729;color:#fff}.custom-theme .el-tag--info{background-color:rgba(10,118,164,.1);border-color:rgba(10,118,164,.2);color:#0a76a4}.custom-theme .el-tag--info.is-hit{border-color:#0a76a4}.custom-theme .el-tag--info .el-tag__close{color:#0a76a4}.custom-theme .el-tag--info .el-tag__close:hover{background-color:#0a76a4;color:#fff}.custom-theme .el-tag--success{background-color:rgba(64,145,103,.1);border-color:rgba(64,145,103,.2);color:#409167}.custom-theme .el-tag--success.is-hit{border-color:#409167}.custom-theme .el-tag--success .el-tag__close{color:#409167}.custom-theme .el-tag--success .el-tag__close:hover{background-color:#409167;color:#fff}.custom-theme .el-tag--warning{background-color:rgba(157,164,8,.1);border-color:rgba(157,164,8,.2);color:#9da408}.custom-theme .el-tag--warning.is-hit{border-color:#9da408}.custom-theme .el-tag--warning .el-tag__close{color:#9da408}.custom-theme .el-tag--warning .el-tag__close:hover{background-color:#9da408;color:#fff}.custom-theme .el-tag--danger{background-color:rgba(179,69,14,.1);border-color:rgba(179,69,14,.2);color:#b3450e}.custom-theme .el-tag--danger.is-hit{border-color:#b3450e}.custom-theme .el-tag--danger .el-tag__close{color:#b3450e}.custom-theme .el-tag--danger .el-tag__close:hover{background-color:#b3450e;color:#fff}.custom-theme .el-tag--medium{height:28px;line-height:26px}.custom-theme .el-tag--medium .el-icon-close{-webkit-transform:scale(.8);transform:scale(.8)}.custom-theme .el-tag--small{height:24px;padding:0 8px;line-height:22px}.custom-theme .el-tag--small .el-icon-close{-webkit-transform:scale(.8);transform:scale(.8)}.custom-theme .el-tag--mini{height:20px;padding:0 5px;line-height:19px}.custom-theme .el-tag--mini .el-icon-close{margin-left:-3px;-webkit-transform:scale(.7);transform:scale(.7)}.custom-theme .el-tree{cursor:default;background:#fff;color:#5a5e66}.custom-theme .el-tree__empty-block{position:relative;min-height:60px;text-align:center;width:100%;height:100%}.custom-theme .el-tree__empty-text{position:absolute;left:50%;top:50%;-webkit-transform:translate(-50%,-50%);transform:translate(-50%,-50%);color:#623615}.custom-theme .el-tree-node{white-space:nowrap}.custom-theme .el-tree-node__content{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;height:26px;cursor:pointer}.custom-theme .el-tree-node__content>.el-tree-node__expand-icon{padding:6px}.custom-theme .el-tree-node__content>.el-checkbox{margin-right:8px}.custom-theme .el-tree-node__content:hover{background-color:#f5f7fa}.custom-theme .el-tree-node__expand-icon{cursor:pointer;color:#b4bccc;font-size:12px;-webkit-transform:rotate(0);transform:rotate(0);-webkit-transition:-webkit-transform .3s ease-in-out;transition:-webkit-transform .3s ease-in-out;transition:transform .3s ease-in-out;transition:transform .3s ease-in-out,-webkit-transform .3s ease-in-out}.custom-theme .el-tree-node__expand-icon.expanded{-webkit-transform:rotate(90deg);transform:rotate(90deg)}.custom-theme .el-tree-node__expand-icon.is-leaf{color:transparent;cursor:default}.custom-theme .el-tree-node__label{font-size:14px}.custom-theme .el-tree-node__loading-icon{margin-right:8px;font-size:14px;color:#b4bccc}.custom-theme .el-tree-node>.el-tree-node__children{overflow:hidden;background-color:transparent}.custom-theme .el-tree-node.is-expanded>.el-tree-node__children{display:block}.custom-theme .el-tree--highlight-current .el-tree-node.is-current>.el-tree-node__content{background-color:#eee}.custom-theme .el-alert{width:100%;padding:8px 16px;margin:0;-webkit-box-sizing:border-box;box-sizing:border-box;border-radius:4px;position:relative;background-color:#fff;overflow:hidden;opacity:1;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-transition:opacity .2s;transition:opacity .2s}.custom-theme .el-alert.is-center{-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}.custom-theme .el-alert--success{background-color:#ecf4f0;color:#409167}.custom-theme .el-alert--success .el-alert__description{color:#409167}.custom-theme .el-alert--info{background-color:#e7f1f6;color:#0a76a4}.custom-theme .el-alert--info .el-alert__description{color:#0a76a4}.custom-theme .el-alert--warning{background-color:#f5f6e6;color:#9da408}.custom-theme .el-alert--warning .el-alert__description{color:#9da408}.custom-theme .el-alert--error{background-color:#f7ece7;color:#b3450e}.custom-theme .el-alert--error .el-alert__description{color:#b3450e}.custom-theme .el-alert__content{display:table-cell;padding:0 8px}.custom-theme .el-alert__icon{font-size:16px;width:16px}.custom-theme .el-alert__icon.is-big{font-size:28px;width:28px}.custom-theme .el-alert__title{font-size:13px;line-height:18px}.custom-theme .el-alert__title.is-bold{font-weight:700}.custom-theme .el-alert .el-alert__description{font-size:12px;margin:5px 0 0 0}.custom-theme .el-alert__closebtn{font-size:12px;color:#b4bccc;opacity:1;position:absolute;top:12px;right:15px;cursor:pointer}.custom-theme .el-alert__closebtn.is-customed{font-style:normal;font-size:13px;top:9px}.custom-theme .el-alert-fade-enter,.custom-theme .el-alert-fade-leave-active{opacity:0}.custom-theme .el-notification{display:-webkit-box;display:-ms-flexbox;display:flex;width:330px;padding:14px 26px 14px 13px;border-radius:8px;-webkit-box-sizing:border-box;box-sizing:border-box;border:1px solid #e6ebf5;position:fixed;background-color:#fff;-webkit-box-shadow:0 2px 12px 0 rgba(0,0,0,.1);box-shadow:0 2px 12px 0 rgba(0,0,0,.1);-webkit-transition:opacity .3s,left .3s,right .3s,top .4s,bottom .3s,-webkit-transform .3s;transition:opacity .3s,left .3s,right .3s,top .4s,bottom .3s,-webkit-transform .3s;transition:opacity .3s,transform .3s,left .3s,right .3s,top .4s,bottom .3s;transition:opacity .3s,transform .3s,left .3s,right .3s,top .4s,bottom .3s,-webkit-transform .3s;overflow:hidden}.custom-theme .el-notification.right{right:16px}.custom-theme .el-notification.left{left:16px}.custom-theme .el-notification__group{margin-left:13px}.custom-theme .el-notification__title{font-weight:700;font-size:16px;color:#2d2f33;margin:0}.custom-theme .el-notification__content{font-size:14px;line-height:21px;margin:6px 0 0 0;color:#5a5e66;text-align:justify}.custom-theme .el-notification__content p{margin:0}.custom-theme .el-notification__icon{height:24px;width:24px;font-size:24px;-webkit-transform:translateY(4px);transform:translateY(4px)}.custom-theme .el-notification__closeBtn{position:absolute;top:15px;right:15px;cursor:pointer;color:#878d99;font-size:16px}.custom-theme .el-notification__closeBtn:hover{color:#5a5e66}.custom-theme .el-notification .el-icon-success{color:#409167}.custom-theme .el-notification .el-icon-error{color:#b3450e}.custom-theme .el-notification .el-icon-info{color:#0a76a4}.custom-theme .el-notification .el-icon-warning{color:#9da408}.custom-theme .el-notification-fade-enter.right{right:0;-webkit-transform:translateX(100%);transform:translateX(100%)}.custom-theme .el-notification-fade-enter.left{left:0;-webkit-transform:translateX(-100%);transform:translateX(-100%)}.custom-theme .el-notification-fade-leave-active{opacity:0}.custom-theme .el-input{position:relative;font-size:14px;display:inline-block;width:100%}.custom-theme .el-input::-webkit-scrollbar{z-index:11;width:6px}.custom-theme .el-input::-webkit-scrollbar:horizontal{height:6px}.custom-theme .el-input::-webkit-scrollbar-thumb{border-radius:5px;width:6px;background:#b4bccc}.custom-theme .el-input::-webkit-scrollbar-corner{background:#fff}.custom-theme .el-input::-webkit-scrollbar-track{background:#fff}.custom-theme .el-input::-webkit-scrollbar-track-piece{background:#fff;width:6px}.custom-theme .el-input__inner{-webkit-appearance:none;background-color:#fff;background-image:none;border-radius:4px;border:1px solid #d8dce5;-webkit-box-sizing:border-box;box-sizing:border-box;color:#5a5e66;display:inline-block;font-size:inherit;height:40px;line-height:1;outline:0;padding:0 15px;-webkit-transition:border-color .2s cubic-bezier(.645,.045,.355,1);transition:border-color .2s cubic-bezier(.645,.045,.355,1);width:100%}.custom-theme .el-input__inner::-webkit-input-placeholder{color:#b4bccc}.custom-theme .el-input__inner:-ms-input-placeholder{color:#b4bccc}.custom-theme .el-input__inner::-ms-input-placeholder{color:#b4bccc}.custom-theme .el-input__inner::placeholder{color:#b4bccc}.custom-theme .el-input__inner:hover{border-color:#b4bccc}.custom-theme .el-input__inner:focus{outline:0;border-color:#262729}.custom-theme .el-input__suffix{position:absolute;height:100%;right:5px;top:0;text-align:center;color:#b4bccc;-webkit-transition:all .3s;transition:all .3s;pointer-events:none}.custom-theme .el-input__suffix-inner{pointer-events:all}.custom-theme .el-input__prefix{position:absolute;height:100%;left:5px;top:0;text-align:center;color:#b4bccc;-webkit-transition:all .3s;transition:all .3s}.custom-theme .el-input__icon{height:100%;width:25px;text-align:center;-webkit-transition:all .3s;transition:all .3s;line-height:40px}.custom-theme .el-input__icon:after{content:'';height:100%;width:0;display:inline-block;vertical-align:middle}.custom-theme .el-input__validateIcon{pointer-events:none}.custom-theme .el-input.is-active .el-input__inner{outline:0;border-color:#262729}.custom-theme .el-input.is-disabled .el-input__inner{background-color:#f5f7fa;border-color:#dfe4ed;color:#b4bccc;cursor:not-allowed}.custom-theme .el-input.is-disabled .el-input__inner::-webkit-input-placeholder{color:#b4bccc}.custom-theme .el-input.is-disabled .el-input__inner:-ms-input-placeholder{color:#b4bccc}.custom-theme .el-input.is-disabled .el-input__inner::-ms-input-placeholder{color:#b4bccc}.custom-theme .el-input.is-disabled .el-input__inner::placeholder{color:#b4bccc}.custom-theme .el-input.is-disabled .el-input__icon{cursor:not-allowed}.custom-theme .el-input--suffix .el-input__inner{padding-right:30px}.custom-theme .el-input--prefix .el-input__inner{padding-left:30px}.custom-theme .el-input--medium{font-size:14px}.custom-theme .el-input--medium .el-input__inner{height:36px}.custom-theme .el-input--medium .el-input__icon{line-height:36px}.custom-theme .el-input--small{font-size:13px}.custom-theme .el-input--small .el-input__inner{height:32px}.custom-theme .el-input--small .el-input__icon{line-height:32px}.custom-theme .el-input--mini{font-size:12px}.custom-theme .el-input--mini .el-input__inner{height:28px}.custom-theme .el-input--mini .el-input__icon{line-height:28px}.custom-theme .el-input-group{line-height:normal;display:inline-table;width:100%;border-collapse:separate}.custom-theme .el-input-group>.el-input__inner{vertical-align:middle;display:table-cell}.custom-theme .el-input-group__append,.custom-theme .el-input-group__prepend{background-color:#f5f7fa;color:#0a76a4;vertical-align:middle;display:table-cell;position:relative;border:1px solid #d8dce5;border-radius:4px;padding:0 20px;width:1px;white-space:nowrap}.custom-theme .el-input-group__append:focus,.custom-theme .el-input-group__prepend:focus{outline:0}.custom-theme .el-input-group__append .el-button,.custom-theme .el-input-group__append .el-select,.custom-theme .el-input-group__prepend .el-button,.custom-theme .el-input-group__prepend .el-select{display:inline-block;margin:-20px}.custom-theme .el-input-group__append button.el-button,.custom-theme .el-input-group__append div.el-select .el-input__inner,.custom-theme .el-input-group__append div.el-select:hover .el-input__inner,.custom-theme .el-input-group__prepend button.el-button,.custom-theme .el-input-group__prepend div.el-select .el-input__inner,.custom-theme .el-input-group__prepend div.el-select:hover .el-input__inner{border-color:transparent;background-color:transparent;color:inherit;border-top:0;border-bottom:0}.custom-theme .el-input-group__append .el-button,.custom-theme .el-input-group__append .el-input,.custom-theme .el-input-group__prepend .el-button,.custom-theme .el-input-group__prepend .el-input{font-size:inherit}.custom-theme .el-input-group__prepend{border-right:0;border-top-right-radius:0;border-bottom-right-radius:0}.custom-theme .el-input-group__append{border-left:0;border-top-left-radius:0;border-bottom-left-radius:0}.custom-theme .el-input-group--prepend .el-input__inner{border-top-left-radius:0;border-bottom-left-radius:0}.custom-theme .el-input-group--append .el-input__inner{border-top-right-radius:0;border-bottom-right-radius:0}.custom-theme .el-textarea{display:inline-block;width:100%;vertical-align:bottom}.custom-theme .el-textarea__inner{display:block;resize:vertical;padding:5px 15px;line-height:1.5;-webkit-box-sizing:border-box;box-sizing:border-box;width:100%;font-size:14px;color:#5a5e66;background-color:#fff;background-image:none;border:1px solid #d8dce5;border-radius:4px;-webkit-transition:border-color .2s cubic-bezier(.645,.045,.355,1);transition:border-color .2s cubic-bezier(.645,.045,.355,1)}.custom-theme .el-textarea__inner::-webkit-input-placeholder{color:#b4bccc}.custom-theme .el-textarea__inner:-ms-input-placeholder{color:#b4bccc}.custom-theme .el-textarea__inner::-ms-input-placeholder{color:#b4bccc}.custom-theme .el-textarea__inner::placeholder{color:#b4bccc}.custom-theme .el-textarea__inner:hover{border-color:#b4bccc}.custom-theme .el-textarea__inner:focus{outline:0;border-color:#262729}.custom-theme .el-textarea.is-disabled .el-textarea__inner{background-color:#f5f7fa;border-color:#dfe4ed;color:#b4bccc;cursor:not-allowed}.custom-theme .el-textarea.is-disabled .el-textarea__inner::-webkit-input-placeholder{color:#b4bccc}.custom-theme .el-textarea.is-disabled .el-textarea__inner:-ms-input-placeholder{color:#b4bccc}.custom-theme .el-textarea.is-disabled .el-textarea__inner::-ms-input-placeholder{color:#b4bccc}.custom-theme .el-textarea.is-disabled .el-textarea__inner::placeholder{color:#b4bccc}.custom-theme .el-input-number{position:relative;display:inline-block;width:180px;line-height:38px}.custom-theme .el-input-number .el-input{display:block}.custom-theme .el-input-number .el-input__inner{-webkit-appearance:none;padding-left:50px;padding-right:50px;text-align:center}.custom-theme .el-input-number__decrease,.custom-theme .el-input-number__increase{position:absolute;z-index:1;top:1px;width:40px;height:auto;text-align:center;background:#f5f7fa;color:#5a5e66;cursor:pointer;font-size:13px}.custom-theme .el-input-number__decrease:hover,.custom-theme .el-input-number__increase:hover{color:#262729}.custom-theme .el-input-number__decrease:hover:not(.is-disabled)~.el-input .el-input__inner:not(.is-disabled),.custom-theme .el-input-number__increase:hover:not(.is-disabled)~.el-input .el-input__inner:not(.is-disabled){border-color:#262729}.custom-theme .el-input-number__decrease.is-disabled,.custom-theme .el-input-number__increase.is-disabled{color:#b4bccc;cursor:not-allowed}.custom-theme .el-input-number__increase{right:1px;border-radius:0 4px 4px 0;border-left:1px solid #d8dce5}.custom-theme .el-input-number__decrease{left:1px;border-radius:4px 0 0 4px;border-right:1px solid #d8dce5}.custom-theme .el-input-number.is-disabled .el-input-number__decrease,.custom-theme .el-input-number.is-disabled .el-input-number__increase{border-color:#dfe4ed;color:#dfe4ed}.custom-theme .el-input-number.is-disabled .el-input-number__decrease:hover,.custom-theme .el-input-number.is-disabled .el-input-number__increase:hover{color:#dfe4ed;cursor:not-allowed}.custom-theme .el-input-number--medium{width:200px;line-height:34px}.custom-theme .el-input-number--medium .el-input-number__decrease,.custom-theme .el-input-number--medium .el-input-number__increase{width:36px;font-size:14px}.custom-theme .el-input-number--medium .el-input__inner{padding-left:43px;padding-right:43px}.custom-theme .el-input-number--small{width:130px;line-height:30px}.custom-theme .el-input-number--small .el-input-number__decrease,.custom-theme .el-input-number--small .el-input-number__increase{width:32px;font-size:13px}.custom-theme .el-input-number--small .el-input-number__decrease [class*=el-icon],.custom-theme .el-input-number--small .el-input-number__increase [class*=el-icon]{-webkit-transform:scale(.9);transform:scale(.9)}.custom-theme .el-input-number--small .el-input__inner{padding-left:39px;padding-right:39px}.custom-theme .el-input-number--mini{width:130px;line-height:26px}.custom-theme .el-input-number--mini .el-input-number__decrease,.custom-theme .el-input-number--mini .el-input-number__increase{width:28px;font-size:12px}.custom-theme .el-input-number--mini .el-input-number__decrease [class*=el-icon],.custom-theme .el-input-number--mini .el-input-number__increase [class*=el-icon]{-webkit-transform:scale(.8);transform:scale(.8)}.custom-theme .el-input-number--mini .el-input__inner{padding-left:35px;padding-right:35px}.custom-theme .el-input-number.is-without-controls .el-input__inner{padding-left:15px;padding-right:15px}.custom-theme .el-input-number.is-controls-right .el-input__inner{padding-left:15px;padding-right:50px}.custom-theme .el-input-number.is-controls-right .el-input-number__decrease,.custom-theme .el-input-number.is-controls-right .el-input-number__increase{height:auto;line-height:19px}.custom-theme .el-input-number.is-controls-right .el-input-number__decrease [class*=el-icon],.custom-theme .el-input-number.is-controls-right .el-input-number__increase [class*=el-icon]{-webkit-transform:scale(.8);transform:scale(.8)}.custom-theme .el-input-number.is-controls-right .el-input-number__increase{border-radius:0 4px 0 0;border-bottom:1px solid #d8dce5}.custom-theme .el-input-number.is-controls-right .el-input-number__decrease{right:1px;bottom:1px;top:auto;left:auto;border-right:none;border-left:1px solid #d8dce5;border-radius:0 0 4px 0}.custom-theme .el-input-number.is-controls-right[class*=medium] [class*=decrease],.custom-theme .el-input-number.is-controls-right[class*=medium] [class*=increase]{line-height:17px}.custom-theme .el-input-number.is-controls-right[class*=small] [class*=decrease],.custom-theme .el-input-number.is-controls-right[class*=small] [class*=increase]{line-height:15px}.custom-theme .el-input-number.is-controls-right[class*=mini] [class*=decrease],.custom-theme .el-input-number.is-controls-right[class*=mini] [class*=increase]{line-height:13px}.custom-theme .el-tooltip__popper{position:absolute;border-radius:4px;padding:10px;z-index:2000;font-size:12px;line-height:1.2}.custom-theme .el-tooltip__popper .popper__arrow,.custom-theme .el-tooltip__popper .popper__arrow::after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.custom-theme .el-tooltip__popper .popper__arrow{border-width:6px}.custom-theme .el-tooltip__popper .popper__arrow::after{content:" ";border-width:5px}.custom-theme .el-tooltip__popper[x-placement^=top]{margin-bottom:12px}.custom-theme .el-tooltip__popper[x-placement^=top] .popper__arrow{bottom:-6px;border-top-color:#2d2f33;border-bottom-width:0}.custom-theme .el-tooltip__popper[x-placement^=top] .popper__arrow::after{bottom:1px;margin-left:-5px;border-top-color:#2d2f33;border-bottom-width:0}.custom-theme .el-tooltip__popper[x-placement^=bottom]{margin-top:12px}.custom-theme .el-tooltip__popper[x-placement^=bottom] .popper__arrow{top:-6px;border-top-width:0;border-bottom-color:#2d2f33}.custom-theme .el-tooltip__popper[x-placement^=bottom] .popper__arrow::after{top:1px;margin-left:-5px;border-top-width:0;border-bottom-color:#2d2f33}.custom-theme .el-tooltip__popper[x-placement^=right]{margin-left:12px}.custom-theme .el-tooltip__popper[x-placement^=right] .popper__arrow{left:-6px;border-right-color:#2d2f33;border-left-width:0}.custom-theme .el-tooltip__popper[x-placement^=right] .popper__arrow::after{bottom:-5px;left:1px;border-right-color:#2d2f33;border-left-width:0}.custom-theme .el-tooltip__popper[x-placement^=left]{margin-right:12px}.custom-theme .el-tooltip__popper[x-placement^=left] .popper__arrow{right:-6px;border-right-width:0;border-left-color:#2d2f33}.custom-theme .el-tooltip__popper[x-placement^=left] .popper__arrow::after{right:1px;bottom:-5px;margin-left:-5px;border-right-width:0;border-left-color:#2d2f33}.custom-theme .el-tooltip__popper.is-dark{background:#2d2f33;color:#fff}.custom-theme .el-tooltip__popper.is-light{background:#fff;border:1px solid #2d2f33}.custom-theme .el-tooltip__popper.is-light[x-placement^=top] .popper__arrow{border-top-color:#2d2f33}.custom-theme .el-tooltip__popper.is-light[x-placement^=top] .popper__arrow::after{border-top-color:#fff}.custom-theme .el-tooltip__popper.is-light[x-placement^=bottom] .popper__arrow{border-bottom-color:#2d2f33}.custom-theme .el-tooltip__popper.is-light[x-placement^=bottom] .popper__arrow::after{border-bottom-color:#fff}.custom-theme .el-tooltip__popper.is-light[x-placement^=left] .popper__arrow{border-left-color:#2d2f33}.custom-theme .el-tooltip__popper.is-light[x-placement^=left] .popper__arrow::after{border-left-color:#fff}.custom-theme .el-tooltip__popper.is-light[x-placement^=right] .popper__arrow{border-right-color:#2d2f33}.custom-theme .el-tooltip__popper.is-light[x-placement^=right] .popper__arrow::after{border-right-color:#fff}.custom-theme .el-slider::after,.custom-theme .el-slider::before{display:table;content:""}.custom-theme .el-slider::after{clear:both}.custom-theme .el-slider__runway{width:100%;height:6px;margin:16px 0;background-color:#dfe4ed;border-radius:3px;position:relative;cursor:pointer;vertical-align:middle}.custom-theme .el-slider__runway.show-input{margin-right:160px;width:auto}.custom-theme .el-slider__runway.disabled{cursor:default}.custom-theme .el-slider__runway.disabled .el-slider__bar{background-color:#b4bccc}.custom-theme .el-slider__runway.disabled .el-slider__button{border-color:#b4bccc}.custom-theme .el-slider__runway.disabled .el-slider__button-wrapper.hover,.custom-theme .el-slider__runway.disabled .el-slider__button-wrapper:hover{cursor:not-allowed}.custom-theme .el-slider__runway.disabled .el-slider__button-wrapper.dragging{cursor:not-allowed}.custom-theme .el-slider__runway.disabled .el-slider__button.dragging,.custom-theme .el-slider__runway.disabled .el-slider__button.hover,.custom-theme .el-slider__runway.disabled .el-slider__button:hover{-webkit-transform:scale(1);transform:scale(1)}.custom-theme .el-slider__runway.disabled .el-slider__button.hover,.custom-theme .el-slider__runway.disabled .el-slider__button:hover{cursor:not-allowed}.custom-theme .el-slider__runway.disabled .el-slider__button.dragging{cursor:not-allowed}.custom-theme .el-slider__input{float:right;margin-top:3px}.custom-theme .el-slider__bar{height:6px;background-color:#262729;border-top-left-radius:3px;border-bottom-left-radius:3px;position:absolute}.custom-theme .el-slider__button-wrapper{height:36px;width:36px;position:absolute;z-index:1001;top:-15px;-webkit-transform:translateX(-50%);transform:translateX(-50%);background-color:transparent;text-align:center;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.custom-theme .el-slider__button-wrapper::after{display:inline-block;content:"";height:100%;vertical-align:middle}.custom-theme .el-slider__button-wrapper .el-tooltip{vertical-align:middle;display:inline-block}.custom-theme .el-slider__button-wrapper.hover,.custom-theme .el-slider__button-wrapper:hover{cursor:-webkit-grab;cursor:grab}.custom-theme .el-slider__button-wrapper.dragging{cursor:-webkit-grabbing;cursor:grabbing}.custom-theme .el-slider__button{width:16px;height:16px;border:solid 2px #262729;background-color:#fff;border-radius:50%;-webkit-transition:.2s;transition:.2s;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.custom-theme .el-slider__button.dragging,.custom-theme .el-slider__button.hover,.custom-theme .el-slider__button:hover{-webkit-transform:scale(1.2);transform:scale(1.2)}.custom-theme .el-slider__button.hover,.custom-theme .el-slider__button:hover{cursor:-webkit-grab;cursor:grab}.custom-theme .el-slider__button.dragging{cursor:-webkit-grabbing;cursor:grabbing}.custom-theme .el-slider__stop{position:absolute;height:6px;width:6px;border-radius:100%;background-color:#fff;-webkit-transform:translateX(-50%);transform:translateX(-50%)}.custom-theme .el-slider.is-vertical{position:relative}.custom-theme .el-slider.is-vertical .el-slider__runway{width:4px;height:100%;margin:0 16px}.custom-theme .el-slider.is-vertical .el-slider__bar{width:4px;height:auto;border-radius:0 0 3px 3px}.custom-theme .el-slider.is-vertical .el-slider__button-wrapper{top:auto;left:-15px;-webkit-transform:translateY(50%);transform:translateY(50%)}.custom-theme .el-slider.is-vertical .el-slider__stop{-webkit-transform:translateY(50%);transform:translateY(50%)}.custom-theme .el-slider.is-vertical.el-slider--with-input{padding-bottom:58px}.custom-theme .el-slider.is-vertical.el-slider--with-input .el-slider__input{overflow:visible;float:none;position:absolute;bottom:22px;width:36px;margin-top:15px}.custom-theme .el-slider.is-vertical.el-slider--with-input .el-slider__input .el-input__inner{text-align:center;padding-left:5px;padding-right:5px}.custom-theme .el-slider.is-vertical.el-slider--with-input .el-slider__input .el-input-number__decrease,.custom-theme .el-slider.is-vertical.el-slider--with-input .el-slider__input .el-input-number__increase{top:32px;margin-top:-1px;border:1px solid #d8dce5;line-height:20px;-webkit-box-sizing:border-box;box-sizing:border-box;-webkit-transition:border-color .2s cubic-bezier(.645,.045,.355,1);transition:border-color .2s cubic-bezier(.645,.045,.355,1)}.custom-theme .el-slider.is-vertical.el-slider--with-input .el-slider__input .el-input-number__decrease{width:18px;right:18px;border-bottom-left-radius:4px}.custom-theme .el-slider.is-vertical.el-slider--with-input .el-slider__input .el-input-number__increase{width:19px;border-bottom-right-radius:4px}.custom-theme .el-slider.is-vertical.el-slider--with-input .el-slider__input .el-input-number__increase~.el-input .el-input__inner{border-bottom-left-radius:0;border-bottom-right-radius:0}.custom-theme .el-slider.is-vertical.el-slider--with-input .el-slider__input:hover .el-input-number__decrease,.custom-theme .el-slider.is-vertical.el-slider--with-input .el-slider__input:hover .el-input-number__increase{border-color:#b4bccc}.custom-theme .el-slider.is-vertical.el-slider--with-input .el-slider__input:active .el-input-number__decrease,.custom-theme .el-slider.is-vertical.el-slider--with-input .el-slider__input:active .el-input-number__increase{border-color:#262729}.custom-theme .el-loading-parent--relative{position:relative!important}.custom-theme .el-loading-parent--hidden{overflow:hidden!important}.custom-theme .el-loading-mask{position:absolute;z-index:10000;background-color:rgba(255,255,255,.9);margin:0;top:0;right:0;bottom:0;left:0;-webkit-transition:opacity .3s;transition:opacity .3s}.custom-theme .el-loading-mask.is-fullscreen{position:fixed}.custom-theme .el-loading-mask.is-fullscreen .el-loading-spinner{margin-top:-25px}.custom-theme .el-loading-mask.is-fullscreen .el-loading-spinner .circular{height:50px;width:50px}.custom-theme .el-loading-spinner{top:50%;margin-top:-21px;width:100%;text-align:center;position:absolute}.custom-theme .el-loading-spinner .el-loading-text{color:#262729;margin:3px 0;font-size:14px}.custom-theme .el-loading-spinner .circular{height:42px;width:42px;-webkit-animation:loading-rotate 2s linear infinite;animation:loading-rotate 2s linear infinite}.custom-theme .el-loading-spinner .path{-webkit-animation:loading-dash 1.5s ease-in-out infinite;animation:loading-dash 1.5s ease-in-out infinite;stroke-dasharray:90,150;stroke-dashoffset:0;stroke-width:2;stroke:#262729;stroke-linecap:round}.custom-theme .el-loading-spinner i{color:#262729}.custom-theme .el-loading-fade-enter,.custom-theme .el-loading-fade-leave-active{opacity:0}@-webkit-keyframes loading-rotate{100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes loading-rotate{100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@-webkit-keyframes loading-dash{0%{stroke-dasharray:1,200;stroke-dashoffset:0}50%{stroke-dasharray:90,150;stroke-dashoffset:-40px}100%{stroke-dasharray:90,150;stroke-dashoffset:-120px}}@keyframes loading-dash{0%{stroke-dasharray:1,200;stroke-dashoffset:0}50%{stroke-dasharray:90,150;stroke-dashoffset:-40px}100%{stroke-dasharray:90,150;stroke-dashoffset:-120px}}.custom-theme .el-row{position:relative;-webkit-box-sizing:border-box;box-sizing:border-box}.custom-theme .el-row::after,.custom-theme .el-row::before{display:table;content:""}.custom-theme .el-row::after{clear:both}.custom-theme .el-row--flex{display:-webkit-box;display:-ms-flexbox;display:flex}.custom-theme .el-row--flex:after,.custom-theme .el-row--flex:before{display:none}.custom-theme .el-row--flex.is-justify-center{-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}.custom-theme .el-row--flex.is-justify-end{-webkit-box-pack:end;-ms-flex-pack:end;justify-content:flex-end}.custom-theme .el-row--flex.is-justify-space-between{-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.custom-theme .el-row--flex.is-justify-space-around{-ms-flex-pack:distribute;justify-content:space-around}.custom-theme .el-row--flex.is-align-middle{-webkit-box-align:center;-ms-flex-align:center;align-items:center}.custom-theme .el-row--flex.is-align-bottom{-webkit-box-align:end;-ms-flex-align:end;align-items:flex-end}.custom-theme [class*=el-col-]{float:left;-webkit-box-sizing:border-box;box-sizing:border-box}.custom-theme .el-col-0{display:none}.custom-theme .el-col-1{width:4.16667%}.custom-theme .el-col-offset-1{margin-left:4.16667%}.custom-theme .el-col-pull-1{position:relative;right:4.16667%}.custom-theme .el-col-push-1{position:relative;left:4.16667%}.custom-theme .el-col-2{width:8.33333%}.custom-theme .el-col-offset-2{margin-left:8.33333%}.custom-theme .el-col-pull-2{position:relative;right:8.33333%}.custom-theme .el-col-push-2{position:relative;left:8.33333%}.custom-theme .el-col-3{width:12.5%}.custom-theme .el-col-offset-3{margin-left:12.5%}.custom-theme .el-col-pull-3{position:relative;right:12.5%}.custom-theme .el-col-push-3{position:relative;left:12.5%}.custom-theme .el-col-4{width:16.66667%}.custom-theme .el-col-offset-4{margin-left:16.66667%}.custom-theme .el-col-pull-4{position:relative;right:16.66667%}.custom-theme .el-col-push-4{position:relative;left:16.66667%}.custom-theme .el-col-5{width:20.83333%}.custom-theme .el-col-offset-5{margin-left:20.83333%}.custom-theme .el-col-pull-5{position:relative;right:20.83333%}.custom-theme .el-col-push-5{position:relative;left:20.83333%}.custom-theme .el-col-6{width:25%}.custom-theme .el-col-offset-6{margin-left:25%}.custom-theme .el-col-pull-6{position:relative;right:25%}.custom-theme .el-col-push-6{position:relative;left:25%}.custom-theme .el-col-7{width:29.16667%}.custom-theme .el-col-offset-7{margin-left:29.16667%}.custom-theme .el-col-pull-7{position:relative;right:29.16667%}.custom-theme .el-col-push-7{position:relative;left:29.16667%}.custom-theme .el-col-8{width:33.33333%}.custom-theme .el-col-offset-8{margin-left:33.33333%}.custom-theme .el-col-pull-8{position:relative;right:33.33333%}.custom-theme .el-col-push-8{position:relative;left:33.33333%}.custom-theme .el-col-9{width:37.5%}.custom-theme .el-col-offset-9{margin-left:37.5%}.custom-theme .el-col-pull-9{position:relative;right:37.5%}.custom-theme .el-col-push-9{position:relative;left:37.5%}.custom-theme .el-col-10{width:41.66667%}.custom-theme .el-col-offset-10{margin-left:41.66667%}.custom-theme .el-col-pull-10{position:relative;right:41.66667%}.custom-theme .el-col-push-10{position:relative;left:41.66667%}.custom-theme .el-col-11{width:45.83333%}.custom-theme .el-col-offset-11{margin-left:45.83333%}.custom-theme .el-col-pull-11{position:relative;right:45.83333%}.custom-theme .el-col-push-11{position:relative;left:45.83333%}.custom-theme .el-col-12{width:50%}.custom-theme .el-col-offset-12{margin-left:50%}.custom-theme .el-col-pull-12{position:relative;right:50%}.custom-theme .el-col-push-12{position:relative;left:50%}.custom-theme .el-col-13{width:54.16667%}.custom-theme .el-col-offset-13{margin-left:54.16667%}.custom-theme .el-col-pull-13{position:relative;right:54.16667%}.custom-theme .el-col-push-13{position:relative;left:54.16667%}.custom-theme .el-col-14{width:58.33333%}.custom-theme .el-col-offset-14{margin-left:58.33333%}.custom-theme .el-col-pull-14{position:relative;right:58.33333%}.custom-theme .el-col-push-14{position:relative;left:58.33333%}.custom-theme .el-col-15{width:62.5%}.custom-theme .el-col-offset-15{margin-left:62.5%}.custom-theme .el-col-pull-15{position:relative;right:62.5%}.custom-theme .el-col-push-15{position:relative;left:62.5%}.custom-theme .el-col-16{width:66.66667%}.custom-theme .el-col-offset-16{margin-left:66.66667%}.custom-theme .el-col-pull-16{position:relative;right:66.66667%}.custom-theme .el-col-push-16{position:relative;left:66.66667%}.custom-theme .el-col-17{width:70.83333%}.custom-theme .el-col-offset-17{margin-left:70.83333%}.custom-theme .el-col-pull-17{position:relative;right:70.83333%}.custom-theme .el-col-push-17{position:relative;left:70.83333%}.custom-theme .el-col-18{width:75%}.custom-theme .el-col-offset-18{margin-left:75%}.custom-theme .el-col-pull-18{position:relative;right:75%}.custom-theme .el-col-push-18{position:relative;left:75%}.custom-theme .el-col-19{width:79.16667%}.custom-theme .el-col-offset-19{margin-left:79.16667%}.custom-theme .el-col-pull-19{position:relative;right:79.16667%}.custom-theme .el-col-push-19{position:relative;left:79.16667%}.custom-theme .el-col-20{width:83.33333%}.custom-theme .el-col-offset-20{margin-left:83.33333%}.custom-theme .el-col-pull-20{position:relative;right:83.33333%}.custom-theme .el-col-push-20{position:relative;left:83.33333%}.custom-theme .el-col-21{width:87.5%}.custom-theme .el-col-offset-21{margin-left:87.5%}.custom-theme .el-col-pull-21{position:relative;right:87.5%}.custom-theme .el-col-push-21{position:relative;left:87.5%}.custom-theme .el-col-22{width:91.66667%}.custom-theme .el-col-offset-22{margin-left:91.66667%}.custom-theme .el-col-pull-22{position:relative;right:91.66667%}.custom-theme .el-col-push-22{position:relative;left:91.66667%}.custom-theme .el-col-23{width:95.83333%}.custom-theme .el-col-offset-23{margin-left:95.83333%}.custom-theme .el-col-pull-23{position:relative;right:95.83333%}.custom-theme .el-col-push-23{position:relative;left:95.83333%}.custom-theme .el-col-24{width:100%}.custom-theme .el-col-offset-24{margin-left:100%}.custom-theme .el-col-pull-24{position:relative;right:100%}.custom-theme .el-col-push-24{position:relative;left:100%}@media only screen and (max-width:768px){.custom-theme .el-col-xs-0{display:none}.custom-theme .el-col-xs-1{width:4.16667%}.custom-theme .el-col-xs-offset-1{margin-left:4.16667%}.custom-theme .el-col-xs-pull-1{position:relative;right:4.16667%}.custom-theme .el-col-xs-push-1{position:relative;left:4.16667%}.custom-theme .el-col-xs-2{width:8.33333%}.custom-theme .el-col-xs-offset-2{margin-left:8.33333%}.custom-theme .el-col-xs-pull-2{position:relative;right:8.33333%}.custom-theme .el-col-xs-push-2{position:relative;left:8.33333%}.custom-theme .el-col-xs-3{width:12.5%}.custom-theme .el-col-xs-offset-3{margin-left:12.5%}.custom-theme .el-col-xs-pull-3{position:relative;right:12.5%}.custom-theme .el-col-xs-push-3{position:relative;left:12.5%}.custom-theme .el-col-xs-4{width:16.66667%}.custom-theme .el-col-xs-offset-4{margin-left:16.66667%}.custom-theme .el-col-xs-pull-4{position:relative;right:16.66667%}.custom-theme .el-col-xs-push-4{position:relative;left:16.66667%}.custom-theme .el-col-xs-5{width:20.83333%}.custom-theme .el-col-xs-offset-5{margin-left:20.83333%}.custom-theme .el-col-xs-pull-5{position:relative;right:20.83333%}.custom-theme .el-col-xs-push-5{position:relative;left:20.83333%}.custom-theme .el-col-xs-6{width:25%}.custom-theme .el-col-xs-offset-6{margin-left:25%}.custom-theme .el-col-xs-pull-6{position:relative;right:25%}.custom-theme .el-col-xs-push-6{position:relative;left:25%}.custom-theme .el-col-xs-7{width:29.16667%}.custom-theme .el-col-xs-offset-7{margin-left:29.16667%}.custom-theme .el-col-xs-pull-7{position:relative;right:29.16667%}.custom-theme .el-col-xs-push-7{position:relative;left:29.16667%}.custom-theme .el-col-xs-8{width:33.33333%}.custom-theme .el-col-xs-offset-8{margin-left:33.33333%}.custom-theme .el-col-xs-pull-8{position:relative;right:33.33333%}.custom-theme .el-col-xs-push-8{position:relative;left:33.33333%}.custom-theme .el-col-xs-9{width:37.5%}.custom-theme .el-col-xs-offset-9{margin-left:37.5%}.custom-theme .el-col-xs-pull-9{position:relative;right:37.5%}.custom-theme .el-col-xs-push-9{position:relative;left:37.5%}.custom-theme .el-col-xs-10{width:41.66667%}.custom-theme .el-col-xs-offset-10{margin-left:41.66667%}.custom-theme .el-col-xs-pull-10{position:relative;right:41.66667%}.custom-theme .el-col-xs-push-10{position:relative;left:41.66667%}.custom-theme .el-col-xs-11{width:45.83333%}.custom-theme .el-col-xs-offset-11{margin-left:45.83333%}.custom-theme .el-col-xs-pull-11{position:relative;right:45.83333%}.custom-theme .el-col-xs-push-11{position:relative;left:45.83333%}.custom-theme .el-col-xs-12{width:50%}.custom-theme .el-col-xs-offset-12{margin-left:50%}.custom-theme .el-col-xs-pull-12{position:relative;right:50%}.custom-theme .el-col-xs-push-12{position:relative;left:50%}.custom-theme .el-col-xs-13{width:54.16667%}.custom-theme .el-col-xs-offset-13{margin-left:54.16667%}.custom-theme .el-col-xs-pull-13{position:relative;right:54.16667%}.custom-theme .el-col-xs-push-13{position:relative;left:54.16667%}.custom-theme .el-col-xs-14{width:58.33333%}.custom-theme .el-col-xs-offset-14{margin-left:58.33333%}.custom-theme .el-col-xs-pull-14{position:relative;right:58.33333%}.custom-theme .el-col-xs-push-14{position:relative;left:58.33333%}.custom-theme .el-col-xs-15{width:62.5%}.custom-theme .el-col-xs-offset-15{margin-left:62.5%}.custom-theme .el-col-xs-pull-15{position:relative;right:62.5%}.custom-theme .el-col-xs-push-15{position:relative;left:62.5%}.custom-theme .el-col-xs-16{width:66.66667%}.custom-theme .el-col-xs-offset-16{margin-left:66.66667%}.custom-theme .el-col-xs-pull-16{position:relative;right:66.66667%}.custom-theme .el-col-xs-push-16{position:relative;left:66.66667%}.custom-theme .el-col-xs-17{width:70.83333%}.custom-theme .el-col-xs-offset-17{margin-left:70.83333%}.custom-theme .el-col-xs-pull-17{position:relative;right:70.83333%}.custom-theme .el-col-xs-push-17{position:relative;left:70.83333%}.custom-theme .el-col-xs-18{width:75%}.custom-theme .el-col-xs-offset-18{margin-left:75%}.custom-theme .el-col-xs-pull-18{position:relative;right:75%}.custom-theme .el-col-xs-push-18{position:relative;left:75%}.custom-theme .el-col-xs-19{width:79.16667%}.custom-theme .el-col-xs-offset-19{margin-left:79.16667%}.custom-theme .el-col-xs-pull-19{position:relative;right:79.16667%}.custom-theme .el-col-xs-push-19{position:relative;left:79.16667%}.custom-theme .el-col-xs-20{width:83.33333%}.custom-theme .el-col-xs-offset-20{margin-left:83.33333%}.custom-theme .el-col-xs-pull-20{position:relative;right:83.33333%}.custom-theme .el-col-xs-push-20{position:relative;left:83.33333%}.custom-theme .el-col-xs-21{width:87.5%}.custom-theme .el-col-xs-offset-21{margin-left:87.5%}.custom-theme .el-col-xs-pull-21{position:relative;right:87.5%}.custom-theme .el-col-xs-push-21{position:relative;left:87.5%}.custom-theme .el-col-xs-22{width:91.66667%}.custom-theme .el-col-xs-offset-22{margin-left:91.66667%}.custom-theme .el-col-xs-pull-22{position:relative;right:91.66667%}.custom-theme .el-col-xs-push-22{position:relative;left:91.66667%}.custom-theme .el-col-xs-23{width:95.83333%}.custom-theme .el-col-xs-offset-23{margin-left:95.83333%}.custom-theme .el-col-xs-pull-23{position:relative;right:95.83333%}.custom-theme .el-col-xs-push-23{position:relative;left:95.83333%}.custom-theme .el-col-xs-24{width:100%}.custom-theme .el-col-xs-offset-24{margin-left:100%}.custom-theme .el-col-xs-pull-24{position:relative;right:100%}.custom-theme .el-col-xs-push-24{position:relative;left:100%}}@media only screen and (min-width:768px){.custom-theme .el-col-sm-0{display:none}.custom-theme .el-col-sm-1{width:4.16667%}.custom-theme .el-col-sm-offset-1{margin-left:4.16667%}.custom-theme .el-col-sm-pull-1{position:relative;right:4.16667%}.custom-theme .el-col-sm-push-1{position:relative;left:4.16667%}.custom-theme .el-col-sm-2{width:8.33333%}.custom-theme .el-col-sm-offset-2{margin-left:8.33333%}.custom-theme .el-col-sm-pull-2{position:relative;right:8.33333%}.custom-theme .el-col-sm-push-2{position:relative;left:8.33333%}.custom-theme .el-col-sm-3{width:12.5%}.custom-theme .el-col-sm-offset-3{margin-left:12.5%}.custom-theme .el-col-sm-pull-3{position:relative;right:12.5%}.custom-theme .el-col-sm-push-3{position:relative;left:12.5%}.custom-theme .el-col-sm-4{width:16.66667%}.custom-theme .el-col-sm-offset-4{margin-left:16.66667%}.custom-theme .el-col-sm-pull-4{position:relative;right:16.66667%}.custom-theme .el-col-sm-push-4{position:relative;left:16.66667%}.custom-theme .el-col-sm-5{width:20.83333%}.custom-theme .el-col-sm-offset-5{margin-left:20.83333%}.custom-theme .el-col-sm-pull-5{position:relative;right:20.83333%}.custom-theme .el-col-sm-push-5{position:relative;left:20.83333%}.custom-theme .el-col-sm-6{width:25%}.custom-theme .el-col-sm-offset-6{margin-left:25%}.custom-theme .el-col-sm-pull-6{position:relative;right:25%}.custom-theme .el-col-sm-push-6{position:relative;left:25%}.custom-theme .el-col-sm-7{width:29.16667%}.custom-theme .el-col-sm-offset-7{margin-left:29.16667%}.custom-theme .el-col-sm-pull-7{position:relative;right:29.16667%}.custom-theme .el-col-sm-push-7{position:relative;left:29.16667%}.custom-theme .el-col-sm-8{width:33.33333%}.custom-theme .el-col-sm-offset-8{margin-left:33.33333%}.custom-theme .el-col-sm-pull-8{position:relative;right:33.33333%}.custom-theme .el-col-sm-push-8{position:relative;left:33.33333%}.custom-theme .el-col-sm-9{width:37.5%}.custom-theme .el-col-sm-offset-9{margin-left:37.5%}.custom-theme .el-col-sm-pull-9{position:relative;right:37.5%}.custom-theme .el-col-sm-push-9{position:relative;left:37.5%}.custom-theme .el-col-sm-10{width:41.66667%}.custom-theme .el-col-sm-offset-10{margin-left:41.66667%}.custom-theme .el-col-sm-pull-10{position:relative;right:41.66667%}.custom-theme .el-col-sm-push-10{position:relative;left:41.66667%}.custom-theme .el-col-sm-11{width:45.83333%}.custom-theme .el-col-sm-offset-11{margin-left:45.83333%}.custom-theme .el-col-sm-pull-11{position:relative;right:45.83333%}.custom-theme .el-col-sm-push-11{position:relative;left:45.83333%}.custom-theme .el-col-sm-12{width:50%}.custom-theme .el-col-sm-offset-12{margin-left:50%}.custom-theme .el-col-sm-pull-12{position:relative;right:50%}.custom-theme .el-col-sm-push-12{position:relative;left:50%}.custom-theme .el-col-sm-13{width:54.16667%}.custom-theme .el-col-sm-offset-13{margin-left:54.16667%}.custom-theme .el-col-sm-pull-13{position:relative;right:54.16667%}.custom-theme .el-col-sm-push-13{position:relative;left:54.16667%}.custom-theme .el-col-sm-14{width:58.33333%}.custom-theme .el-col-sm-offset-14{margin-left:58.33333%}.custom-theme .el-col-sm-pull-14{position:relative;right:58.33333%}.custom-theme .el-col-sm-push-14{position:relative;left:58.33333%}.custom-theme .el-col-sm-15{width:62.5%}.custom-theme .el-col-sm-offset-15{margin-left:62.5%}.custom-theme .el-col-sm-pull-15{position:relative;right:62.5%}.custom-theme .el-col-sm-push-15{position:relative;left:62.5%}.custom-theme .el-col-sm-16{width:66.66667%}.custom-theme .el-col-sm-offset-16{margin-left:66.66667%}.custom-theme .el-col-sm-pull-16{position:relative;right:66.66667%}.custom-theme .el-col-sm-push-16{position:relative;left:66.66667%}.custom-theme .el-col-sm-17{width:70.83333%}.custom-theme .el-col-sm-offset-17{margin-left:70.83333%}.custom-theme .el-col-sm-pull-17{position:relative;right:70.83333%}.custom-theme .el-col-sm-push-17{position:relative;left:70.83333%}.custom-theme .el-col-sm-18{width:75%}.custom-theme .el-col-sm-offset-18{margin-left:75%}.custom-theme .el-col-sm-pull-18{position:relative;right:75%}.custom-theme .el-col-sm-push-18{position:relative;left:75%}.custom-theme .el-col-sm-19{width:79.16667%}.custom-theme .el-col-sm-offset-19{margin-left:79.16667%}.custom-theme .el-col-sm-pull-19{position:relative;right:79.16667%}.custom-theme .el-col-sm-push-19{position:relative;left:79.16667%}.custom-theme .el-col-sm-20{width:83.33333%}.custom-theme .el-col-sm-offset-20{margin-left:83.33333%}.custom-theme .el-col-sm-pull-20{position:relative;right:83.33333%}.custom-theme .el-col-sm-push-20{position:relative;left:83.33333%}.custom-theme .el-col-sm-21{width:87.5%}.custom-theme .el-col-sm-offset-21{margin-left:87.5%}.custom-theme .el-col-sm-pull-21{position:relative;right:87.5%}.custom-theme .el-col-sm-push-21{position:relative;left:87.5%}.custom-theme .el-col-sm-22{width:91.66667%}.custom-theme .el-col-sm-offset-22{margin-left:91.66667%}.custom-theme .el-col-sm-pull-22{position:relative;right:91.66667%}.custom-theme .el-col-sm-push-22{position:relative;left:91.66667%}.custom-theme .el-col-sm-23{width:95.83333%}.custom-theme .el-col-sm-offset-23{margin-left:95.83333%}.custom-theme .el-col-sm-pull-23{position:relative;right:95.83333%}.custom-theme .el-col-sm-push-23{position:relative;left:95.83333%}.custom-theme .el-col-sm-24{width:100%}.custom-theme .el-col-sm-offset-24{margin-left:100%}.custom-theme .el-col-sm-pull-24{position:relative;right:100%}.custom-theme .el-col-sm-push-24{position:relative;left:100%}}@media only screen and (min-width:992px){.custom-theme .el-col-md-0{display:none}.custom-theme .el-col-md-1{width:4.16667%}.custom-theme .el-col-md-offset-1{margin-left:4.16667%}.custom-theme .el-col-md-pull-1{position:relative;right:4.16667%}.custom-theme .el-col-md-push-1{position:relative;left:4.16667%}.custom-theme .el-col-md-2{width:8.33333%}.custom-theme .el-col-md-offset-2{margin-left:8.33333%}.custom-theme .el-col-md-pull-2{position:relative;right:8.33333%}.custom-theme .el-col-md-push-2{position:relative;left:8.33333%}.custom-theme .el-col-md-3{width:12.5%}.custom-theme .el-col-md-offset-3{margin-left:12.5%}.custom-theme .el-col-md-pull-3{position:relative;right:12.5%}.custom-theme .el-col-md-push-3{position:relative;left:12.5%}.custom-theme .el-col-md-4{width:16.66667%}.custom-theme .el-col-md-offset-4{margin-left:16.66667%}.custom-theme .el-col-md-pull-4{position:relative;right:16.66667%}.custom-theme .el-col-md-push-4{position:relative;left:16.66667%}.custom-theme .el-col-md-5{width:20.83333%}.custom-theme .el-col-md-offset-5{margin-left:20.83333%}.custom-theme .el-col-md-pull-5{position:relative;right:20.83333%}.custom-theme .el-col-md-push-5{position:relative;left:20.83333%}.custom-theme .el-col-md-6{width:25%}.custom-theme .el-col-md-offset-6{margin-left:25%}.custom-theme .el-col-md-pull-6{position:relative;right:25%}.custom-theme .el-col-md-push-6{position:relative;left:25%}.custom-theme .el-col-md-7{width:29.16667%}.custom-theme .el-col-md-offset-7{margin-left:29.16667%}.custom-theme .el-col-md-pull-7{position:relative;right:29.16667%}.custom-theme .el-col-md-push-7{position:relative;left:29.16667%}.custom-theme .el-col-md-8{width:33.33333%}.custom-theme .el-col-md-offset-8{margin-left:33.33333%}.custom-theme .el-col-md-pull-8{position:relative;right:33.33333%}.custom-theme .el-col-md-push-8{position:relative;left:33.33333%}.custom-theme .el-col-md-9{width:37.5%}.custom-theme .el-col-md-offset-9{margin-left:37.5%}.custom-theme .el-col-md-pull-9{position:relative;right:37.5%}.custom-theme .el-col-md-push-9{position:relative;left:37.5%}.custom-theme .el-col-md-10{width:41.66667%}.custom-theme .el-col-md-offset-10{margin-left:41.66667%}.custom-theme .el-col-md-pull-10{position:relative;right:41.66667%}.custom-theme .el-col-md-push-10{position:relative;left:41.66667%}.custom-theme .el-col-md-11{width:45.83333%}.custom-theme .el-col-md-offset-11{margin-left:45.83333%}.custom-theme .el-col-md-pull-11{position:relative;right:45.83333%}.custom-theme .el-col-md-push-11{position:relative;left:45.83333%}.custom-theme .el-col-md-12{width:50%}.custom-theme .el-col-md-offset-12{margin-left:50%}.custom-theme .el-col-md-pull-12{position:relative;right:50%}.custom-theme .el-col-md-push-12{position:relative;left:50%}.custom-theme .el-col-md-13{width:54.16667%}.custom-theme .el-col-md-offset-13{margin-left:54.16667%}.custom-theme .el-col-md-pull-13{position:relative;right:54.16667%}.custom-theme .el-col-md-push-13{position:relative;left:54.16667%}.custom-theme .el-col-md-14{width:58.33333%}.custom-theme .el-col-md-offset-14{margin-left:58.33333%}.custom-theme .el-col-md-pull-14{position:relative;right:58.33333%}.custom-theme .el-col-md-push-14{position:relative;left:58.33333%}.custom-theme .el-col-md-15{width:62.5%}.custom-theme .el-col-md-offset-15{margin-left:62.5%}.custom-theme .el-col-md-pull-15{position:relative;right:62.5%}.custom-theme .el-col-md-push-15{position:relative;left:62.5%}.custom-theme .el-col-md-16{width:66.66667%}.custom-theme .el-col-md-offset-16{margin-left:66.66667%}.custom-theme .el-col-md-pull-16{position:relative;right:66.66667%}.custom-theme .el-col-md-push-16{position:relative;left:66.66667%}.custom-theme .el-col-md-17{width:70.83333%}.custom-theme .el-col-md-offset-17{margin-left:70.83333%}.custom-theme .el-col-md-pull-17{position:relative;right:70.83333%}.custom-theme .el-col-md-push-17{position:relative;left:70.83333%}.custom-theme .el-col-md-18{width:75%}.custom-theme .el-col-md-offset-18{margin-left:75%}.custom-theme .el-col-md-pull-18{position:relative;right:75%}.custom-theme .el-col-md-push-18{position:relative;left:75%}.custom-theme .el-col-md-19{width:79.16667%}.custom-theme .el-col-md-offset-19{margin-left:79.16667%}.custom-theme .el-col-md-pull-19{position:relative;right:79.16667%}.custom-theme .el-col-md-push-19{position:relative;left:79.16667%}.custom-theme .el-col-md-20{width:83.33333%}.custom-theme .el-col-md-offset-20{margin-left:83.33333%}.custom-theme .el-col-md-pull-20{position:relative;right:83.33333%}.custom-theme .el-col-md-push-20{position:relative;left:83.33333%}.custom-theme .el-col-md-21{width:87.5%}.custom-theme .el-col-md-offset-21{margin-left:87.5%}.custom-theme .el-col-md-pull-21{position:relative;right:87.5%}.custom-theme .el-col-md-push-21{position:relative;left:87.5%}.custom-theme .el-col-md-22{width:91.66667%}.custom-theme .el-col-md-offset-22{margin-left:91.66667%}.custom-theme .el-col-md-pull-22{position:relative;right:91.66667%}.custom-theme .el-col-md-push-22{position:relative;left:91.66667%}.custom-theme .el-col-md-23{width:95.83333%}.custom-theme .el-col-md-offset-23{margin-left:95.83333%}.custom-theme .el-col-md-pull-23{position:relative;right:95.83333%}.custom-theme .el-col-md-push-23{position:relative;left:95.83333%}.custom-theme .el-col-md-24{width:100%}.custom-theme .el-col-md-offset-24{margin-left:100%}.custom-theme .el-col-md-pull-24{position:relative;right:100%}.custom-theme .el-col-md-push-24{position:relative;left:100%}}@media only screen and (min-width:1200px){.custom-theme .el-col-lg-0{display:none}.custom-theme .el-col-lg-1{width:4.16667%}.custom-theme .el-col-lg-offset-1{margin-left:4.16667%}.custom-theme .el-col-lg-pull-1{position:relative;right:4.16667%}.custom-theme .el-col-lg-push-1{position:relative;left:4.16667%}.custom-theme .el-col-lg-2{width:8.33333%}.custom-theme .el-col-lg-offset-2{margin-left:8.33333%}.custom-theme .el-col-lg-pull-2{position:relative;right:8.33333%}.custom-theme .el-col-lg-push-2{position:relative;left:8.33333%}.custom-theme .el-col-lg-3{width:12.5%}.custom-theme .el-col-lg-offset-3{margin-left:12.5%}.custom-theme .el-col-lg-pull-3{position:relative;right:12.5%}.custom-theme .el-col-lg-push-3{position:relative;left:12.5%}.custom-theme .el-col-lg-4{width:16.66667%}.custom-theme .el-col-lg-offset-4{margin-left:16.66667%}.custom-theme .el-col-lg-pull-4{position:relative;right:16.66667%}.custom-theme .el-col-lg-push-4{position:relative;left:16.66667%}.custom-theme .el-col-lg-5{width:20.83333%}.custom-theme .el-col-lg-offset-5{margin-left:20.83333%}.custom-theme .el-col-lg-pull-5{position:relative;right:20.83333%}.custom-theme .el-col-lg-push-5{position:relative;left:20.83333%}.custom-theme .el-col-lg-6{width:25%}.custom-theme .el-col-lg-offset-6{margin-left:25%}.custom-theme .el-col-lg-pull-6{position:relative;right:25%}.custom-theme .el-col-lg-push-6{position:relative;left:25%}.custom-theme .el-col-lg-7{width:29.16667%}.custom-theme .el-col-lg-offset-7{margin-left:29.16667%}.custom-theme .el-col-lg-pull-7{position:relative;right:29.16667%}.custom-theme .el-col-lg-push-7{position:relative;left:29.16667%}.custom-theme .el-col-lg-8{width:33.33333%}.custom-theme .el-col-lg-offset-8{margin-left:33.33333%}.custom-theme .el-col-lg-pull-8{position:relative;right:33.33333%}.custom-theme .el-col-lg-push-8{position:relative;left:33.33333%}.custom-theme .el-col-lg-9{width:37.5%}.custom-theme .el-col-lg-offset-9{margin-left:37.5%}.custom-theme .el-col-lg-pull-9{position:relative;right:37.5%}.custom-theme .el-col-lg-push-9{position:relative;left:37.5%}.custom-theme .el-col-lg-10{width:41.66667%}.custom-theme .el-col-lg-offset-10{margin-left:41.66667%}.custom-theme .el-col-lg-pull-10{position:relative;right:41.66667%}.custom-theme .el-col-lg-push-10{position:relative;left:41.66667%}.custom-theme .el-col-lg-11{width:45.83333%}.custom-theme .el-col-lg-offset-11{margin-left:45.83333%}.custom-theme .el-col-lg-pull-11{position:relative;right:45.83333%}.custom-theme .el-col-lg-push-11{position:relative;left:45.83333%}.custom-theme .el-col-lg-12{width:50%}.custom-theme .el-col-lg-offset-12{margin-left:50%}.custom-theme .el-col-lg-pull-12{position:relative;right:50%}.custom-theme .el-col-lg-push-12{position:relative;left:50%}.custom-theme .el-col-lg-13{width:54.16667%}.custom-theme .el-col-lg-offset-13{margin-left:54.16667%}.custom-theme .el-col-lg-pull-13{position:relative;right:54.16667%}.custom-theme .el-col-lg-push-13{position:relative;left:54.16667%}.custom-theme .el-col-lg-14{width:58.33333%}.custom-theme .el-col-lg-offset-14{margin-left:58.33333%}.custom-theme .el-col-lg-pull-14{position:relative;right:58.33333%}.custom-theme .el-col-lg-push-14{position:relative;left:58.33333%}.custom-theme .el-col-lg-15{width:62.5%}.custom-theme .el-col-lg-offset-15{margin-left:62.5%}.custom-theme .el-col-lg-pull-15{position:relative;right:62.5%}.custom-theme .el-col-lg-push-15{position:relative;left:62.5%}.custom-theme .el-col-lg-16{width:66.66667%}.custom-theme .el-col-lg-offset-16{margin-left:66.66667%}.custom-theme .el-col-lg-pull-16{position:relative;right:66.66667%}.custom-theme .el-col-lg-push-16{position:relative;left:66.66667%}.custom-theme .el-col-lg-17{width:70.83333%}.custom-theme .el-col-lg-offset-17{margin-left:70.83333%}.custom-theme .el-col-lg-pull-17{position:relative;right:70.83333%}.custom-theme .el-col-lg-push-17{position:relative;left:70.83333%}.custom-theme .el-col-lg-18{width:75%}.custom-theme .el-col-lg-offset-18{margin-left:75%}.custom-theme .el-col-lg-pull-18{position:relative;right:75%}.custom-theme .el-col-lg-push-18{position:relative;left:75%}.custom-theme .el-col-lg-19{width:79.16667%}.custom-theme .el-col-lg-offset-19{margin-left:79.16667%}.custom-theme .el-col-lg-pull-19{position:relative;right:79.16667%}.custom-theme .el-col-lg-push-19{position:relative;left:79.16667%}.custom-theme .el-col-lg-20{width:83.33333%}.custom-theme .el-col-lg-offset-20{margin-left:83.33333%}.custom-theme .el-col-lg-pull-20{position:relative;right:83.33333%}.custom-theme .el-col-lg-push-20{position:relative;left:83.33333%}.custom-theme .el-col-lg-21{width:87.5%}.custom-theme .el-col-lg-offset-21{margin-left:87.5%}.custom-theme .el-col-lg-pull-21{position:relative;right:87.5%}.custom-theme .el-col-lg-push-21{position:relative;left:87.5%}.custom-theme .el-col-lg-22{width:91.66667%}.custom-theme .el-col-lg-offset-22{margin-left:91.66667%}.custom-theme .el-col-lg-pull-22{position:relative;right:91.66667%}.custom-theme .el-col-lg-push-22{position:relative;left:91.66667%}.custom-theme .el-col-lg-23{width:95.83333%}.custom-theme .el-col-lg-offset-23{margin-left:95.83333%}.custom-theme .el-col-lg-pull-23{position:relative;right:95.83333%}.custom-theme .el-col-lg-push-23{position:relative;left:95.83333%}.custom-theme .el-col-lg-24{width:100%}.custom-theme .el-col-lg-offset-24{margin-left:100%}.custom-theme .el-col-lg-pull-24{position:relative;right:100%}.custom-theme .el-col-lg-push-24{position:relative;left:100%}}@media only screen and (min-width:1920px){.custom-theme .el-col-xl-0{display:none}.custom-theme .el-col-xl-1{width:4.16667%}.custom-theme .el-col-xl-offset-1{margin-left:4.16667%}.custom-theme .el-col-xl-pull-1{position:relative;right:4.16667%}.custom-theme .el-col-xl-push-1{position:relative;left:4.16667%}.custom-theme .el-col-xl-2{width:8.33333%}.custom-theme .el-col-xl-offset-2{margin-left:8.33333%}.custom-theme .el-col-xl-pull-2{position:relative;right:8.33333%}.custom-theme .el-col-xl-push-2{position:relative;left:8.33333%}.custom-theme .el-col-xl-3{width:12.5%}.custom-theme .el-col-xl-offset-3{margin-left:12.5%}.custom-theme .el-col-xl-pull-3{position:relative;right:12.5%}.custom-theme .el-col-xl-push-3{position:relative;left:12.5%}.custom-theme .el-col-xl-4{width:16.66667%}.custom-theme .el-col-xl-offset-4{margin-left:16.66667%}.custom-theme .el-col-xl-pull-4{position:relative;right:16.66667%}.custom-theme .el-col-xl-push-4{position:relative;left:16.66667%}.custom-theme .el-col-xl-5{width:20.83333%}.custom-theme .el-col-xl-offset-5{margin-left:20.83333%}.custom-theme .el-col-xl-pull-5{position:relative;right:20.83333%}.custom-theme .el-col-xl-push-5{position:relative;left:20.83333%}.custom-theme .el-col-xl-6{width:25%}.custom-theme .el-col-xl-offset-6{margin-left:25%}.custom-theme .el-col-xl-pull-6{position:relative;right:25%}.custom-theme .el-col-xl-push-6{position:relative;left:25%}.custom-theme .el-col-xl-7{width:29.16667%}.custom-theme .el-col-xl-offset-7{margin-left:29.16667%}.custom-theme .el-col-xl-pull-7{position:relative;right:29.16667%}.custom-theme .el-col-xl-push-7{position:relative;left:29.16667%}.custom-theme .el-col-xl-8{width:33.33333%}.custom-theme .el-col-xl-offset-8{margin-left:33.33333%}.custom-theme .el-col-xl-pull-8{position:relative;right:33.33333%}.custom-theme .el-col-xl-push-8{position:relative;left:33.33333%}.custom-theme .el-col-xl-9{width:37.5%}.custom-theme .el-col-xl-offset-9{margin-left:37.5%}.custom-theme .el-col-xl-pull-9{position:relative;right:37.5%}.custom-theme .el-col-xl-push-9{position:relative;left:37.5%}.custom-theme .el-col-xl-10{width:41.66667%}.custom-theme .el-col-xl-offset-10{margin-left:41.66667%}.custom-theme .el-col-xl-pull-10{position:relative;right:41.66667%}.custom-theme .el-col-xl-push-10{position:relative;left:41.66667%}.custom-theme .el-col-xl-11{width:45.83333%}.custom-theme .el-col-xl-offset-11{margin-left:45.83333%}.custom-theme .el-col-xl-pull-11{position:relative;right:45.83333%}.custom-theme .el-col-xl-push-11{position:relative;left:45.83333%}.custom-theme .el-col-xl-12{width:50%}.custom-theme .el-col-xl-offset-12{margin-left:50%}.custom-theme .el-col-xl-pull-12{position:relative;right:50%}.custom-theme .el-col-xl-push-12{position:relative;left:50%}.custom-theme .el-col-xl-13{width:54.16667%}.custom-theme .el-col-xl-offset-13{margin-left:54.16667%}.custom-theme .el-col-xl-pull-13{position:relative;right:54.16667%}.custom-theme .el-col-xl-push-13{position:relative;left:54.16667%}.custom-theme .el-col-xl-14{width:58.33333%}.custom-theme .el-col-xl-offset-14{margin-left:58.33333%}.custom-theme .el-col-xl-pull-14{position:relative;right:58.33333%}.custom-theme .el-col-xl-push-14{position:relative;left:58.33333%}.custom-theme .el-col-xl-15{width:62.5%}.custom-theme .el-col-xl-offset-15{margin-left:62.5%}.custom-theme .el-col-xl-pull-15{position:relative;right:62.5%}.custom-theme .el-col-xl-push-15{position:relative;left:62.5%}.custom-theme .el-col-xl-16{width:66.66667%}.custom-theme .el-col-xl-offset-16{margin-left:66.66667%}.custom-theme .el-col-xl-pull-16{position:relative;right:66.66667%}.custom-theme .el-col-xl-push-16{position:relative;left:66.66667%}.custom-theme .el-col-xl-17{width:70.83333%}.custom-theme .el-col-xl-offset-17{margin-left:70.83333%}.custom-theme .el-col-xl-pull-17{position:relative;right:70.83333%}.custom-theme .el-col-xl-push-17{position:relative;left:70.83333%}.custom-theme .el-col-xl-18{width:75%}.custom-theme .el-col-xl-offset-18{margin-left:75%}.custom-theme .el-col-xl-pull-18{position:relative;right:75%}.custom-theme .el-col-xl-push-18{position:relative;left:75%}.custom-theme .el-col-xl-19{width:79.16667%}.custom-theme .el-col-xl-offset-19{margin-left:79.16667%}.custom-theme .el-col-xl-pull-19{position:relative;right:79.16667%}.custom-theme .el-col-xl-push-19{position:relative;left:79.16667%}.custom-theme .el-col-xl-20{width:83.33333%}.custom-theme .el-col-xl-offset-20{margin-left:83.33333%}.custom-theme .el-col-xl-pull-20{position:relative;right:83.33333%}.custom-theme .el-col-xl-push-20{position:relative;left:83.33333%}.custom-theme .el-col-xl-21{width:87.5%}.custom-theme .el-col-xl-offset-21{margin-left:87.5%}.custom-theme .el-col-xl-pull-21{position:relative;right:87.5%}.custom-theme .el-col-xl-push-21{position:relative;left:87.5%}.custom-theme .el-col-xl-22{width:91.66667%}.custom-theme .el-col-xl-offset-22{margin-left:91.66667%}.custom-theme .el-col-xl-pull-22{position:relative;right:91.66667%}.custom-theme .el-col-xl-push-22{position:relative;left:91.66667%}.custom-theme .el-col-xl-23{width:95.83333%}.custom-theme .el-col-xl-offset-23{margin-left:95.83333%}.custom-theme .el-col-xl-pull-23{position:relative;right:95.83333%}.custom-theme .el-col-xl-push-23{position:relative;left:95.83333%}.custom-theme .el-col-xl-24{width:100%}.custom-theme .el-col-xl-offset-24{margin-left:100%}.custom-theme .el-col-xl-pull-24{position:relative;right:100%}.custom-theme .el-col-xl-push-24{position:relative;left:100%}}.custom-theme .el-progress{position:relative;line-height:1}.custom-theme .el-progress__text{font-size:14px;color:#5a5e66;display:inline-block;vertical-align:middle;margin-left:10px;line-height:1}.custom-theme .el-progress__text i{vertical-align:middle;display:block}.custom-theme .el-progress--circle{display:inline-block}.custom-theme .el-progress--circle .el-progress__text{position:absolute;top:50%;left:0;width:100%;text-align:center;margin:0;-webkit-transform:translate(0,-50%);transform:translate(0,-50%)}.custom-theme .el-progress--circle .el-progress__text i{vertical-align:middle;display:inline-block}.custom-theme .el-progress--without-text .el-progress__text{display:none}.custom-theme .el-progress--without-text .el-progress-bar{padding-right:0;margin-right:0;display:block}.custom-theme .el-progress--text-inside .el-progress-bar{padding-right:0;margin-right:0}.custom-theme .el-progress.is-success .el-progress-bar__inner{background-color:#409167}.custom-theme .el-progress.is-success .el-progress__text{color:#409167}.custom-theme .el-progress.is-exception .el-progress-bar__inner{background-color:#b3450e}.custom-theme .el-progress.is-exception .el-progress__text{color:#b3450e}.custom-theme .el-progress-bar{padding-right:50px;display:inline-block;vertical-align:middle;width:100%;margin-right:-55px;-webkit-box-sizing:border-box;box-sizing:border-box}.custom-theme .el-progress-bar__outer{height:6px;border-radius:100px;background-color:#e6ebf5;overflow:hidden;position:relative;vertical-align:middle}.custom-theme .el-progress-bar__inner{position:absolute;left:0;top:0;height:100%;background-color:#262729;text-align:right;border-radius:100px;line-height:1;white-space:nowrap}.custom-theme .el-progress-bar__inner::after{display:inline-block;content:"";height:100%;vertical-align:middle}.custom-theme .el-progress-bar__innerText{display:inline-block;vertical-align:middle;color:#fff;font-size:12px;margin:0 5px}@-webkit-keyframes progress{0%{background-position:0 0}100%{background-position:32px 0}}@keyframes progress{0%{background-position:0 0}100%{background-position:32px 0}}.custom-theme .el-upload{display:inline-block;text-align:center;cursor:pointer}.custom-theme .el-upload__input{display:none}.custom-theme .el-upload__tip{font-size:12px;color:#5a5e66;margin-top:7px}.custom-theme .el-upload iframe{position:absolute;z-index:-1;top:0;left:0;opacity:0}.custom-theme .el-upload--picture-card{background-color:#fbfdff;border:1px dashed #c0ccda;border-radius:6px;-webkit-box-sizing:border-box;box-sizing:border-box;width:148px;height:148px;cursor:pointer;line-height:146px;vertical-align:top}.custom-theme .el-upload--picture-card i{font-size:28px;color:#8c939d}.custom-theme .el-upload--picture-card:hover{border-color:#262729;color:#262729}.custom-theme .el-upload-dragger{background-color:#fff;border:1px dashed #d9d9d9;border-radius:6px;-webkit-box-sizing:border-box;box-sizing:border-box;width:360px;height:180px;text-align:center;cursor:pointer;position:relative;overflow:hidden}.custom-theme .el-upload-dragger .el-icon-upload{font-size:67px;color:#b4bccc;margin:40px 0 16px;line-height:50px}.custom-theme .el-upload-dragger+.el-upload__tip{text-align:center}.custom-theme .el-upload-dragger~.el-upload__files{border-top:1px solid #d8dce5;margin-top:7px;padding-top:5px}.custom-theme .el-upload-dragger .el-upload__text{color:#5a5e66;font-size:14px;text-align:center}.custom-theme .el-upload-dragger .el-upload__text em{color:#262729;font-style:normal}.custom-theme .el-upload-dragger:hover{border-color:#262729}.custom-theme .el-upload-dragger.is-dragover{background-color:rgba(32,159,255,.06);border:2px dashed #262729}.custom-theme .el-upload-list{margin:0;padding:0;list-style:none}.custom-theme .el-upload-list__item{-webkit-transition:all .5s cubic-bezier(.55,0,.1,1);transition:all .5s cubic-bezier(.55,0,.1,1);font-size:14px;color:#5a5e66;line-height:1.8;margin-top:5px;position:relative;-webkit-box-sizing:border-box;box-sizing:border-box;border-radius:4px;width:100%}.custom-theme .el-upload-list__item .el-progress{position:absolute;top:20px;width:100%}.custom-theme .el-upload-list__item .el-progress__text{position:absolute;right:0;top:-13px}.custom-theme .el-upload-list__item .el-progress-bar{margin-right:0;padding-right:0}.custom-theme .el-upload-list__item:first-child{margin-top:10px}.custom-theme .el-upload-list__item .el-icon-upload-success{color:#409167}.custom-theme .el-upload-list__item .el-icon-close{display:none;position:absolute;top:5px;right:5px;cursor:pointer;opacity:.75;color:#5a5e66}.custom-theme .el-upload-list__item .el-icon-close:hover{opacity:1}.custom-theme .el-upload-list__item .el-icon-close-tip{display:none;position:absolute;top:5px;right:0;cursor:pointer;opacity:1;color:#262729;-webkit-transform:translate(15%,0);transform:translate(15%,0)}.custom-theme .el-upload-list__item:hover{background-color:#f5f7fa}.custom-theme .el-upload-list__item:hover .el-icon-close{display:inline-block}.custom-theme .el-upload-list__item:hover .el-progress__text{display:none}.custom-theme .el-upload-list__item.is-success .el-upload-list__item-status-label{display:block}.custom-theme .el-upload-list__item.is-success .el-upload-list__item-name:focus,.custom-theme .el-upload-list__item.is-success .el-upload-list__item-name:hover{color:#262729;cursor:pointer}.custom-theme .el-upload-list__item.is-success:focus .el-icon-close-tip{display:inline-block}.custom-theme .el-upload-list__item.is-success:active,.custom-theme .el-upload-list__item.is-success:focus:not(.focusing){outline-width:0}.custom-theme .el-upload-list__item.is-success:active .el-icon-close-tip,.custom-theme .el-upload-list__item.is-success:focus:not(.focusing) .el-icon-close-tip{display:none}.custom-theme .el-upload-list__item.is-success:focus .el-upload-list__item-status-label,.custom-theme .el-upload-list__item.is-success:hover .el-upload-list__item-status-label{display:none}.custom-theme .el-upload-list.is-disabled .el-upload-list__item:hover .el-upload-list__item-status-label{display:block}.custom-theme .el-upload-list__item-name{color:#5a5e66;display:block;margin-right:40px;overflow:hidden;padding-left:4px;text-overflow:ellipsis;-webkit-transition:color .3s;transition:color .3s;white-space:nowrap}.custom-theme .el-upload-list__item-name [class^=el-icon]{height:100%;margin-right:7px;color:#878d99;line-height:inherit}.custom-theme .el-upload-list__item-status-label{position:absolute;right:5px;top:0;line-height:inherit;display:none}.custom-theme .el-upload-list__item-delete{position:absolute;right:10px;top:0;font-size:12px;color:#5a5e66;display:none}.custom-theme .el-upload-list__item-delete:hover{color:#262729}.custom-theme .el-upload-list--picture-card{margin:0;display:inline;vertical-align:top}.custom-theme .el-upload-list--picture-card .el-upload-list__item{overflow:hidden;background-color:#fff;border:1px solid #c0ccda;border-radius:6px;-webkit-box-sizing:border-box;box-sizing:border-box;width:148px;height:148px;margin:0 8px 8px 0;display:inline-block}.custom-theme .el-upload-list--picture-card .el-upload-list__item .el-icon-check,.custom-theme .el-upload-list--picture-card .el-upload-list__item .el-icon-circle-check{color:#fff}.custom-theme .el-upload-list--picture-card .el-upload-list__item .el-icon-close{display:none}.custom-theme .el-upload-list--picture-card .el-upload-list__item:hover .el-upload-list__item-status-label{display:none}.custom-theme .el-upload-list--picture-card .el-upload-list__item:hover .el-progress__text{display:block}.custom-theme .el-upload-list--picture-card .el-upload-list__item-name{display:none}.custom-theme .el-upload-list--picture-card .el-upload-list__item-thumbnail{width:100%;height:100%}.custom-theme .el-upload-list--picture-card .el-upload-list__item-status-label{position:absolute;right:-15px;top:-6px;width:40px;height:24px;background:#13ce66;text-align:center;-webkit-transform:rotate(45deg);transform:rotate(45deg);-webkit-box-shadow:0 0 1pc 1px rgba(0,0,0,.2);box-shadow:0 0 1pc 1px rgba(0,0,0,.2)}.custom-theme .el-upload-list--picture-card .el-upload-list__item-status-label i{font-size:12px;margin-top:11px;-webkit-transform:rotate(-45deg);transform:rotate(-45deg)}.custom-theme .el-upload-list--picture-card .el-upload-list__item-actions{position:absolute;width:100%;height:100%;left:0;top:0;cursor:default;text-align:center;color:#fff;opacity:0;font-size:20px;background-color:rgba(0,0,0,.5);-webkit-transition:opacity .3s;transition:opacity .3s}.custom-theme .el-upload-list--picture-card .el-upload-list__item-actions::after{display:inline-block;content:"";height:100%;vertical-align:middle}.custom-theme .el-upload-list--picture-card .el-upload-list__item-actions span{display:none;cursor:pointer}.custom-theme .el-upload-list--picture-card .el-upload-list__item-actions span+span{margin-left:15px}.custom-theme .el-upload-list--picture-card .el-upload-list__item-actions .el-upload-list__item-delete{position:static;font-size:inherit;color:inherit}.custom-theme .el-upload-list--picture-card .el-upload-list__item-actions:hover{opacity:1}.custom-theme .el-upload-list--picture-card .el-upload-list__item-actions:hover span{display:inline-block}.custom-theme .el-upload-list--picture-card .el-progress{top:50%;left:50%;-webkit-transform:translate(-50%,-50%);transform:translate(-50%,-50%);bottom:auto;width:126px}.custom-theme .el-upload-list--picture-card .el-progress .el-progress__text{top:50%}.custom-theme .el-upload-list--picture .el-upload-list__item{overflow:hidden;background-color:#fff;border:1px solid #c0ccda;border-radius:6px;-webkit-box-sizing:border-box;box-sizing:border-box;margin-top:10px;padding:10px 10px 10px 90px;height:92px}.custom-theme .el-upload-list--picture .el-upload-list__item .el-icon-check,.custom-theme .el-upload-list--picture .el-upload-list__item .el-icon-circle-check{color:#fff}.custom-theme .el-upload-list--picture .el-upload-list__item:hover .el-upload-list__item-status-label{background:0 0;-webkit-box-shadow:none;box-shadow:none;top:-2px;right:-12px}.custom-theme .el-upload-list--picture .el-upload-list__item:hover .el-progress__text{display:block}.custom-theme .el-upload-list--picture .el-upload-list__item.is-success .el-upload-list__item-name{line-height:70px;margin-top:0}.custom-theme .el-upload-list--picture .el-upload-list__item.is-success .el-upload-list__item-name i{display:none}.custom-theme .el-upload-list--picture .el-upload-list__item-thumbnail{vertical-align:middle;display:inline-block;width:70px;height:70px;float:left;position:relative;z-index:1;margin-left:-80px}.custom-theme .el-upload-list--picture .el-upload-list__item-name{display:block;margin-top:20px}.custom-theme .el-upload-list--picture .el-upload-list__item-name i{font-size:70px;line-height:1;position:absolute;left:9px;top:10px}.custom-theme .el-upload-list--picture .el-upload-list__item-status-label{position:absolute;right:-17px;top:-7px;width:46px;height:26px;background:#13ce66;text-align:center;-webkit-transform:rotate(45deg);transform:rotate(45deg);-webkit-box-shadow:0 1px 1px #ccc;box-shadow:0 1px 1px #ccc}.custom-theme .el-upload-list--picture .el-upload-list__item-status-label i{font-size:12px;margin-top:12px;-webkit-transform:rotate(-45deg);transform:rotate(-45deg)}.custom-theme .el-upload-list--picture .el-progress{position:relative;top:-7px}.custom-theme .el-upload-cover{position:absolute;left:0;top:0;width:100%;height:100%;overflow:hidden;z-index:10;cursor:default}.custom-theme .el-upload-cover::after{display:inline-block;content:"";height:100%;vertical-align:middle}.custom-theme .el-upload-cover img{display:block;width:100%;height:100%}.custom-theme .el-upload-cover__label{position:absolute;right:-15px;top:-6px;width:40px;height:24px;background:#13ce66;text-align:center;-webkit-transform:rotate(45deg);transform:rotate(45deg);-webkit-box-shadow:0 0 1pc 1px rgba(0,0,0,.2);box-shadow:0 0 1pc 1px rgba(0,0,0,.2)}.custom-theme .el-upload-cover__label i{font-size:12px;margin-top:11px;-webkit-transform:rotate(-45deg);transform:rotate(-45deg);color:#fff}.custom-theme .el-upload-cover__progress{display:inline-block;vertical-align:middle;position:static;width:243px}.custom-theme .el-upload-cover__progress+.el-upload__inner{opacity:0}.custom-theme .el-upload-cover__content{position:absolute;top:0;left:0;width:100%;height:100%}.custom-theme .el-upload-cover__interact{position:absolute;bottom:0;left:0;width:100%;height:100%;background-color:rgba(0,0,0,.72);text-align:center}.custom-theme .el-upload-cover__interact .btn{display:inline-block;color:#fff;font-size:14px;cursor:pointer;vertical-align:middle;-webkit-transition:opacity .3s cubic-bezier(.23,1,.32,1) .1s,-webkit-transform .3s cubic-bezier(.23,1,.32,1) .1s;transition:opacity .3s cubic-bezier(.23,1,.32,1) .1s,-webkit-transform .3s cubic-bezier(.23,1,.32,1) .1s;transition:transform .3s cubic-bezier(.23,1,.32,1) .1s,opacity .3s cubic-bezier(.23,1,.32,1) .1s;transition:transform .3s cubic-bezier(.23,1,.32,1) .1s,opacity .3s cubic-bezier(.23,1,.32,1) .1s,-webkit-transform .3s cubic-bezier(.23,1,.32,1) .1s;margin-top:60px}.custom-theme .el-upload-cover__interact .btn i{margin-top:0}.custom-theme .el-upload-cover__interact .btn span{opacity:0;-webkit-transition:opacity .15s linear;transition:opacity .15s linear}.custom-theme .el-upload-cover__interact .btn:not(:first-child){margin-left:35px}.custom-theme .el-upload-cover__interact .btn:hover{-webkit-transform:translateY(-13px);transform:translateY(-13px)}.custom-theme .el-upload-cover__interact .btn:hover span{opacity:1}.custom-theme .el-upload-cover__interact .btn i{color:#fff;display:block;font-size:24px;line-height:inherit;margin:0 auto 5px}.custom-theme .el-upload-cover__title{position:absolute;bottom:0;left:0;background-color:#fff;height:36px;width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-weight:400;text-align:left;padding:0 10px;margin:0;line-height:36px;font-size:14px;color:#2d2f33}.custom-theme .el-upload-cover+.el-upload__inner{opacity:0;position:relative;z-index:1}.custom-theme .el-progress{position:relative;line-height:1}.custom-theme .el-progress__text{font-size:14px;color:#5a5e66;display:inline-block;vertical-align:middle;margin-left:10px;line-height:1}.custom-theme .el-progress__text i{vertical-align:middle;display:block}.custom-theme .el-progress--circle{display:inline-block}.custom-theme .el-progress--circle .el-progress__text{position:absolute;top:50%;left:0;width:100%;text-align:center;margin:0;-webkit-transform:translate(0,-50%);transform:translate(0,-50%)}.custom-theme .el-progress--circle .el-progress__text i{vertical-align:middle;display:inline-block}.custom-theme .el-progress--without-text .el-progress__text{display:none}.custom-theme .el-progress--without-text .el-progress-bar{padding-right:0;margin-right:0;display:block}.custom-theme .el-progress--text-inside .el-progress-bar{padding-right:0;margin-right:0}.custom-theme .el-progress.is-success .el-progress-bar__inner{background-color:#409167}.custom-theme .el-progress.is-success .el-progress__text{color:#409167}.custom-theme .el-progress.is-exception .el-progress-bar__inner{background-color:#b3450e}.custom-theme .el-progress.is-exception .el-progress__text{color:#b3450e}.custom-theme .el-progress-bar{padding-right:50px;display:inline-block;vertical-align:middle;width:100%;margin-right:-55px;-webkit-box-sizing:border-box;box-sizing:border-box}.custom-theme .el-progress-bar__outer{height:6px;border-radius:100px;background-color:#e6ebf5;overflow:hidden;position:relative;vertical-align:middle}.custom-theme .el-progress-bar__inner{position:absolute;left:0;top:0;height:100%;background-color:#262729;text-align:right;border-radius:100px;line-height:1;white-space:nowrap}.custom-theme .el-progress-bar__inner::after{display:inline-block;content:"";height:100%;vertical-align:middle}.custom-theme .el-progress-bar__innerText{display:inline-block;vertical-align:middle;color:#fff;font-size:12px;margin:0 5px}@keyframes progress{0%{background-position:0 0}100%{background-position:32px 0}}.custom-theme .el-time-spinner{width:100%;white-space:nowrap}.custom-theme .el-spinner{display:inline-block;vertical-align:middle}.custom-theme .el-spinner-inner{-webkit-animation:rotate 2s linear infinite;animation:rotate 2s linear infinite;width:50px;height:50px}.custom-theme .el-spinner-inner .path{stroke:#ececec;stroke-linecap:round;-webkit-animation:dash 1.5s ease-in-out infinite;animation:dash 1.5s ease-in-out infinite}@-webkit-keyframes rotate{100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes rotate{100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@-webkit-keyframes dash{0%{stroke-dasharray:1,150;stroke-dashoffset:0}50%{stroke-dasharray:90,150;stroke-dashoffset:-35}100%{stroke-dasharray:90,150;stroke-dashoffset:-124}}@keyframes dash{0%{stroke-dasharray:1,150;stroke-dashoffset:0}50%{stroke-dasharray:90,150;stroke-dashoffset:-35}100%{stroke-dasharray:90,150;stroke-dashoffset:-124}}.custom-theme .el-message{min-width:380px;-webkit-box-sizing:border-box;box-sizing:border-box;border-radius:4px;border-width:1px;border-style:solid;border-color:#e6ebf5;position:fixed;left:50%;top:20px;-webkit-transform:translateX(-50%);transform:translateX(-50%);background-color:#edf2fc;-webkit-transition:opacity .3s,-webkit-transform .4s;transition:opacity .3s,-webkit-transform .4s;transition:opacity .3s,transform .4s;transition:opacity .3s,transform .4s,-webkit-transform .4s;overflow:hidden;padding:15px 15px 15px 20px;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.custom-theme .el-message.is-center{-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}.custom-theme .el-message p{margin:0}.custom-theme .el-message--info .el-message__content{color:#0a76a4}.custom-theme .el-message--success{background-color:#ecf4f0;border-color:#d9e9e1}.custom-theme .el-message--success .el-message__content{color:#409167}.custom-theme .el-message--warning{background-color:#f5f6e6;border-color:#ebedce}.custom-theme .el-message--warning .el-message__content{color:#9da408}.custom-theme .el-message--error{background-color:#f7ece7;border-color:#f0dacf}.custom-theme .el-message--error .el-message__content{color:#b3450e}.custom-theme .el-message__icon{margin-right:10px}.custom-theme .el-message__content{padding:0;font-size:14px;line-height:1}.custom-theme .el-message__content:focus{outline-width:0}.custom-theme .el-message__closeBtn{position:absolute;top:50%;right:15px;-webkit-transform:translateY(-50%);transform:translateY(-50%);cursor:pointer;color:#b4bccc;font-size:16px}.custom-theme .el-message__closeBtn:focus{outline-width:0}.custom-theme .el-message__closeBtn:hover{color:#878d99}.custom-theme .el-message .el-icon-success{color:#409167}.custom-theme .el-message .el-icon-error{color:#b3450e}.custom-theme .el-message .el-icon-info{color:#0a76a4}.custom-theme .el-message .el-icon-warning{color:#9da408}.custom-theme .el-message-fade-enter,.custom-theme .el-message-fade-leave-active{opacity:0;-webkit-transform:translate(-50%,-100%);transform:translate(-50%,-100%)}.custom-theme .el-badge{position:relative;vertical-align:middle;display:inline-block}.custom-theme .el-badge__content{background-color:#b3450e;border-radius:10px;color:#fff;display:inline-block;font-size:12px;height:18px;line-height:18px;padding:0 6px;text-align:center;white-space:nowrap;border:1px solid #fff}.custom-theme .el-badge__content.is-fixed{position:absolute;top:0;right:10px;-webkit-transform:translateY(-50%) translateX(100%);transform:translateY(-50%) translateX(100%)}.custom-theme .el-badge__content.is-fixed.is-dot{right:5px}.custom-theme .el-badge__content.is-dot{height:8px;width:8px;padding:0;right:0;border-radius:50%}.custom-theme .el-card{border-radius:4px;border:1px solid #e6ebf5;background-color:#fff;overflow:hidden;-webkit-box-shadow:0 2px 12px 0 rgba(0,0,0,.1);box-shadow:0 2px 12px 0 rgba(0,0,0,.1);color:#2d2f33}.custom-theme .el-card__header{padding:18px 20px;border-bottom:1px solid #e6ebf5;-webkit-box-sizing:border-box;box-sizing:border-box}.custom-theme .el-card__body{padding:20px}.custom-theme .el-rate{height:20px;line-height:1}.custom-theme .el-rate:active,.custom-theme .el-rate:focus{outline-width:0}.custom-theme .el-rate__item{display:inline-block;position:relative;font-size:0;vertical-align:middle}.custom-theme .el-rate__icon{position:relative;display:inline-block;font-size:18px;margin-right:6px;color:#b4bccc;-webkit-transition:.3s;transition:.3s}.custom-theme .el-rate__icon.hover{-webkit-transform:scale(1.15);transform:scale(1.15)}.custom-theme .el-rate__icon .path2{position:absolute;left:0;top:0}.custom-theme .el-rate__decimal{position:absolute;top:0;left:0;display:inline-block;overflow:hidden}.custom-theme .el-rate__text{font-size:14px;vertical-align:middle}.custom-theme .el-steps{display:-webkit-box;display:-ms-flexbox;display:flex}.custom-theme .el-steps--simple{padding:13px 8%;border-radius:4px;background:#f5f7fa}.custom-theme .el-steps--horizontal{white-space:nowrap}.custom-theme .el-steps--vertical{height:100%;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-flow:column;flex-flow:column}.custom-theme .el-step{position:relative;-ms-flex-negative:1;flex-shrink:1}.custom-theme .el-step:last-of-type .el-step__line{display:none}.custom-theme .el-step:last-of-type.is-flex{-ms-flex-preferred-size:auto!important;flex-basis:auto!important;-ms-flex-negative:0;flex-shrink:0;-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0}.custom-theme .el-step:last-of-type .el-step__description,.custom-theme .el-step:last-of-type .el-step__main{padding-right:0}.custom-theme .el-step__head{position:relative;width:100%}.custom-theme .el-step__head.is-process{color:#2d2f33;border-color:#2d2f33}.custom-theme .el-step__head.is-wait{color:#b4bccc;border-color:#b4bccc}.custom-theme .el-step__head.is-success{color:#409167;border-color:#409167}.custom-theme .el-step__head.is-error{color:#b3450e;border-color:#b3450e}.custom-theme .el-step__head.is-finish{color:#262729;border-color:#262729}.custom-theme .el-step__icon{position:relative;z-index:1;display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;width:24px;height:24px;font-size:14px;-webkit-box-sizing:border-box;box-sizing:border-box;background:#fff;-webkit-transition:.15s ease-out;transition:.15s ease-out}.custom-theme .el-step__icon.is-text{border-radius:50%;border:2px solid;border-color:inherit}.custom-theme .el-step__icon.is-icon{width:40px}.custom-theme .el-step__icon-inner{display:inline-block;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;text-align:center;font-weight:700;line-height:1;color:inherit}.custom-theme .el-step__icon-inner[class*=el-icon]:not(.is-status){font-size:25px;font-weight:400}.custom-theme .el-step__icon-inner.is-status{-webkit-transform:translateY(1px);transform:translateY(1px)}.custom-theme .el-step__line{position:absolute;border-color:inherit;background-color:#b4bccc}.custom-theme .el-step__line-inner{display:block;border-width:1px;border-style:solid;border-color:inherit;-webkit-transition:.15s ease-out;transition:.15s ease-out;-webkit-box-sizing:border-box;box-sizing:border-box;width:0;height:0}.custom-theme .el-step__main{white-space:normal;text-align:left}.custom-theme .el-step__title{font-size:16px;line-height:38px}.custom-theme .el-step__title.is-process{font-weight:700;color:#2d2f33}.custom-theme .el-step__title.is-wait{color:#b4bccc}.custom-theme .el-step__title.is-success{color:#409167}.custom-theme .el-step__title.is-error{color:#b3450e}.custom-theme .el-step__title.is-finish{color:#262729}.custom-theme .el-step__description{padding-right:10%;margin-top:-5px;font-size:12px;line-height:20px;font-weight:400}.custom-theme .el-step__description.is-process{color:#2d2f33}.custom-theme .el-step__description.is-wait{color:#b4bccc}.custom-theme .el-step__description.is-success{color:#409167}.custom-theme .el-step__description.is-error{color:#b3450e}.custom-theme .el-step__description.is-finish{color:#262729}.custom-theme .el-step.is-horizontal{display:inline-block}.custom-theme .el-step.is-horizontal .el-step__line{height:2px;top:11px;left:0;right:0}.custom-theme .el-step.is-vertical{display:-webkit-box;display:-ms-flexbox;display:flex}.custom-theme .el-step.is-vertical .el-step__head{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;width:24px}.custom-theme .el-step.is-vertical .el-step__main{padding-left:10px;-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1}.custom-theme .el-step.is-vertical .el-step__title{line-height:24px;padding-bottom:8px}.custom-theme .el-step.is-vertical .el-step__line{width:2px;top:0;bottom:0;left:11px}.custom-theme .el-step.is-vertical .el-step__icon.is-icon{width:24px}.custom-theme .el-step.is-center .el-step__head{text-align:center}.custom-theme .el-step.is-center .el-step__main{text-align:center}.custom-theme .el-step.is-center .el-step__description{padding-left:20%;padding-right:20%}.custom-theme .el-step.is-center .el-step__line{left:50%;right:-50%}.custom-theme .el-step.is-simple{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.custom-theme .el-step.is-simple .el-step__head{width:auto;font-size:0;padding-right:10px}.custom-theme .el-step.is-simple .el-step__icon{background:0 0;width:16px;height:16px;font-size:12px}.custom-theme .el-step.is-simple .el-step__icon-inner[class*=el-icon]:not(.is-status){font-size:18px}.custom-theme .el-step.is-simple .el-step__icon-inner.is-status{-webkit-transform:scale(.8) translateY(1px);transform:scale(.8) translateY(1px)}.custom-theme .el-step.is-simple .el-step__main{position:relative;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:stretch;-ms-flex-align:stretch;align-items:stretch;-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1}.custom-theme .el-step.is-simple .el-step__title{font-size:16px;line-height:20px}.custom-theme .el-step.is-simple:not(:last-of-type) .el-step__title{max-width:50%;word-break:break-all}.custom-theme .el-step.is-simple .el-step__arrow{-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}.custom-theme .el-step.is-simple .el-step__arrow::after,.custom-theme .el-step.is-simple .el-step__arrow::before{content:'';display:inline-block;position:absolute;height:15px;width:1px;background:#b4bccc}.custom-theme .el-step.is-simple .el-step__arrow::before{-webkit-transform:rotate(-45deg) translateY(-4px);transform:rotate(-45deg) translateY(-4px);-webkit-transform-origin:0 0;transform-origin:0 0}.custom-theme .el-step.is-simple .el-step__arrow::after{-webkit-transform:rotate(45deg) translateY(4px);transform:rotate(45deg) translateY(4px);-webkit-transform-origin:100% 100%;transform-origin:100% 100%}.custom-theme .el-step.is-simple:last-of-type .el-step__arrow{display:none}.custom-theme .el-carousel{overflow-x:hidden;position:relative}.custom-theme .el-carousel__container{position:relative;height:300px}.custom-theme .el-carousel__arrow{border:none;outline:0;padding:0;margin:0;height:36px;width:36px;cursor:pointer;-webkit-transition:.3s;transition:.3s;border-radius:50%;background-color:rgba(31,45,61,.11);color:#fff;position:absolute;top:50%;z-index:10;-webkit-transform:translateY(-50%);transform:translateY(-50%);text-align:center;font-size:12px}.custom-theme .el-carousel__arrow--left{left:16px}.custom-theme .el-carousel__arrow--right{right:16px}.custom-theme .el-carousel__arrow:hover{background-color:rgba(31,45,61,.23)}.custom-theme .el-carousel__arrow i{cursor:pointer}.custom-theme .el-carousel__indicators{position:absolute;list-style:none;bottom:0;left:50%;-webkit-transform:translateX(-50%);transform:translateX(-50%);margin:0;padding:0;z-index:2}.custom-theme .el-carousel__indicators--outside{bottom:26px;text-align:center;position:static;-webkit-transform:none;transform:none}.custom-theme .el-carousel__indicators--outside .el-carousel__indicator:hover button{opacity:.64}.custom-theme .el-carousel__indicators--outside button{background-color:#b4bccc;opacity:.24}.custom-theme .el-carousel__indicators--labels{left:0;right:0;-webkit-transform:none;transform:none;text-align:center}.custom-theme .el-carousel__indicators--labels .el-carousel__button{height:auto;width:auto;padding:2px 18px;font-size:12px}.custom-theme .el-carousel__indicators--labels .el-carousel__indicator{padding:6px 4px}.custom-theme .el-carousel__indicator{display:inline-block;background-color:transparent;padding:12px 4px;cursor:pointer}.custom-theme .el-carousel__indicator:hover button{opacity:.72}.custom-theme .el-carousel__indicator.is-active button{opacity:1}.custom-theme .el-carousel__button{display:block;opacity:.48;width:30px;height:2px;background-color:#fff;border:none;outline:0;padding:0;margin:0;cursor:pointer;-webkit-transition:.3s;transition:.3s}.custom-theme .carousel-arrow-left-enter,.custom-theme .carousel-arrow-left-leave-active{-webkit-transform:translateY(-50%) translateX(-10px);transform:translateY(-50%) translateX(-10px);opacity:0}.custom-theme .carousel-arrow-right-enter,.custom-theme .carousel-arrow-right-leave-active{-webkit-transform:translateY(-50%) translateX(10px);transform:translateY(-50%) translateX(10px);opacity:0}.custom-theme .el-scrollbar{overflow:hidden;position:relative}.custom-theme .el-scrollbar:active>.el-scrollbar__bar,.custom-theme .el-scrollbar:focus>.el-scrollbar__bar,.custom-theme .el-scrollbar:hover>.el-scrollbar__bar{opacity:1;-webkit-transition:opacity 340ms ease-out;transition:opacity 340ms ease-out}.custom-theme .el-scrollbar__wrap{overflow:scroll;height:100%}.custom-theme .el-scrollbar__wrap--hidden-default::-webkit-scrollbar{width:0;height:0}.custom-theme .el-scrollbar__thumb{position:relative;display:block;width:0;height:0;cursor:pointer;border-radius:inherit;background-color:rgba(135,141,153,.3);-webkit-transition:.3s background-color;transition:.3s background-color}.custom-theme .el-scrollbar__thumb:hover{background-color:rgba(135,141,153,.5)}.custom-theme .el-scrollbar__bar{position:absolute;right:2px;bottom:2px;z-index:1;border-radius:4px;opacity:0;-webkit-transition:opacity 120ms ease-out;transition:opacity 120ms ease-out}.custom-theme .el-scrollbar__bar.is-vertical{width:6px;top:2px}.custom-theme .el-scrollbar__bar.is-vertical>div{width:100%}.custom-theme .el-scrollbar__bar.is-horizontal{height:6px;left:2px}.custom-theme .el-scrollbar__bar.is-horizontal>div{height:100%}.custom-theme .el-carousel__item{position:absolute;top:0;left:0;width:100%;height:100%;display:inline-block;overflow:hidden;z-index:0}.custom-theme .el-carousel__item.is-active{z-index:2}.custom-theme .el-carousel__item.is-animating{-webkit-transition:-webkit-transform .4s ease-in-out;transition:-webkit-transform .4s ease-in-out;transition:transform .4s ease-in-out;transition:transform .4s ease-in-out,-webkit-transform .4s ease-in-out}.custom-theme .el-carousel__item--card{width:50%;-webkit-transition:-webkit-transform .4s ease-in-out;transition:-webkit-transform .4s ease-in-out;transition:transform .4s ease-in-out;transition:transform .4s ease-in-out,-webkit-transform .4s ease-in-out}.custom-theme .el-carousel__item--card.is-in-stage{cursor:pointer;z-index:1}.custom-theme .el-carousel__item--card.is-in-stage.is-hover .el-carousel__mask,.custom-theme .el-carousel__item--card.is-in-stage:hover .el-carousel__mask{opacity:.12}.custom-theme .el-carousel__item--card.is-active{z-index:2}.custom-theme .el-carousel__mask{position:absolute;width:100%;height:100%;top:0;left:0;background-color:#fff;opacity:.24;-webkit-transition:.2s;transition:.2s}.custom-theme .el-collapse{border-top:1px solid #e6ebf5;border-bottom:1px solid #e6ebf5}.custom-theme .el-collapse-item__header{height:48px;line-height:48px;background-color:#fff;color:#2d2f33;cursor:pointer;border-bottom:1px solid #e6ebf5;font-size:13px;font-weight:500;-webkit-transition:border-bottom-color .3s;transition:border-bottom-color .3s}.custom-theme .el-collapse-item__header:active,.custom-theme .el-collapse-item__header:focus:not(.focusing){outline-width:0}.custom-theme .el-collapse-item__arrow{margin-right:8px;-webkit-transition:-webkit-transform .3s;transition:-webkit-transform .3s;transition:transform .3s;transition:transform .3s,-webkit-transform .3s;float:right;line-height:48px;font-weight:300}.custom-theme .el-collapse-item__wrap{will-change:height;background-color:#fff;overflow:hidden;-webkit-box-sizing:border-box;box-sizing:border-box;border-bottom:1px solid #e6ebf5}.custom-theme .el-collapse-item__content{padding-bottom:25px;font-size:13px;color:#2d2f33;line-height:1.769230769230769}.custom-theme .el-collapse-item.is-active .el-collapse-item__header{border-bottom-color:transparent}.custom-theme .el-collapse-item.is-active .el-collapse-item__header .el-collapse-item__arrow{-webkit-transform:rotate(90deg);transform:rotate(90deg)}.custom-theme .el-collapse-item:last-child{margin-bottom:-1px}.custom-theme .el-input{position:relative;font-size:14px;display:inline-block;width:100%}.custom-theme .el-input::-webkit-scrollbar{z-index:11;width:6px}.custom-theme .el-input::-webkit-scrollbar:horizontal{height:6px}.custom-theme .el-input::-webkit-scrollbar-thumb{border-radius:5px;width:6px;background:#b4bccc}.custom-theme .el-input::-webkit-scrollbar-corner{background:#fff}.custom-theme .el-input::-webkit-scrollbar-track{background:#fff}.custom-theme .el-input::-webkit-scrollbar-track-piece{background:#fff;width:6px}.custom-theme .el-input__inner{-webkit-appearance:none;background-color:#fff;background-image:none;border-radius:4px;border:1px solid #d8dce5;-webkit-box-sizing:border-box;box-sizing:border-box;color:#5a5e66;display:inline-block;font-size:inherit;height:40px;line-height:1;outline:0;padding:0 15px;-webkit-transition:border-color .2s cubic-bezier(.645,.045,.355,1);transition:border-color .2s cubic-bezier(.645,.045,.355,1);width:100%}.custom-theme .el-input__inner::-webkit-input-placeholder{color:#b4bccc}.custom-theme .el-input__inner:-ms-input-placeholder{color:#b4bccc}.custom-theme .el-input__inner::-ms-input-placeholder{color:#b4bccc}.custom-theme .el-input__inner::placeholder{color:#b4bccc}.custom-theme .el-input__inner:hover{border-color:#b4bccc}.custom-theme .el-input__inner:focus{outline:0;border-color:#262729}.custom-theme .el-input__suffix{position:absolute;height:100%;right:5px;top:0;text-align:center;color:#b4bccc;-webkit-transition:all .3s;transition:all .3s;pointer-events:none}.custom-theme .el-input__suffix-inner{pointer-events:all}.custom-theme .el-input__prefix{position:absolute;height:100%;left:5px;top:0;text-align:center;color:#b4bccc;-webkit-transition:all .3s;transition:all .3s}.custom-theme .el-input__icon{height:100%;width:25px;text-align:center;-webkit-transition:all .3s;transition:all .3s;line-height:40px}.custom-theme .el-input__icon:after{content:'';height:100%;width:0;display:inline-block;vertical-align:middle}.custom-theme .el-input__validateIcon{pointer-events:none}.custom-theme .el-input.is-active .el-input__inner{outline:0;border-color:#262729}.custom-theme .el-input.is-disabled .el-input__inner{background-color:#f5f7fa;border-color:#dfe4ed;color:#b4bccc;cursor:not-allowed}.custom-theme .el-input.is-disabled .el-input__inner::-webkit-input-placeholder{color:#b4bccc}.custom-theme .el-input.is-disabled .el-input__inner:-ms-input-placeholder{color:#b4bccc}.custom-theme .el-input.is-disabled .el-input__inner::-ms-input-placeholder{color:#b4bccc}.custom-theme .el-input.is-disabled .el-input__inner::placeholder{color:#b4bccc}.custom-theme .el-input.is-disabled .el-input__icon{cursor:not-allowed}.custom-theme .el-input--suffix .el-input__inner{padding-right:30px}.custom-theme .el-input--prefix .el-input__inner{padding-left:30px}.custom-theme .el-input--medium{font-size:14px}.custom-theme .el-input--medium .el-input__inner{height:36px}.custom-theme .el-input--medium .el-input__icon{line-height:36px}.custom-theme .el-input--small{font-size:13px}.custom-theme .el-input--small .el-input__inner{height:32px}.custom-theme .el-input--small .el-input__icon{line-height:32px}.custom-theme .el-input--mini{font-size:12px}.custom-theme .el-input--mini .el-input__inner{height:28px}.custom-theme .el-input--mini .el-input__icon{line-height:28px}.custom-theme .el-input-group{line-height:normal;display:inline-table;width:100%;border-collapse:separate}.custom-theme .el-input-group>.el-input__inner{vertical-align:middle;display:table-cell}.custom-theme .el-input-group__append,.custom-theme .el-input-group__prepend{background-color:#f5f7fa;color:#0a76a4;vertical-align:middle;display:table-cell;position:relative;border:1px solid #d8dce5;border-radius:4px;padding:0 20px;width:1px;white-space:nowrap}.custom-theme .el-input-group__append:focus,.custom-theme .el-input-group__prepend:focus{outline:0}.custom-theme .el-input-group__append .el-button,.custom-theme .el-input-group__append .el-select,.custom-theme .el-input-group__prepend .el-button,.custom-theme .el-input-group__prepend .el-select{display:inline-block;margin:-20px}.custom-theme .el-input-group__append button.el-button,.custom-theme .el-input-group__append div.el-select .el-input__inner,.custom-theme .el-input-group__append div.el-select:hover .el-input__inner,.custom-theme .el-input-group__prepend button.el-button,.custom-theme .el-input-group__prepend div.el-select .el-input__inner,.custom-theme .el-input-group__prepend div.el-select:hover .el-input__inner{border-color:transparent;background-color:transparent;color:inherit;border-top:0;border-bottom:0}.custom-theme .el-input-group__append .el-button,.custom-theme .el-input-group__append .el-input,.custom-theme .el-input-group__prepend .el-button,.custom-theme .el-input-group__prepend .el-input{font-size:inherit}.custom-theme .el-input-group__prepend{border-right:0;border-top-right-radius:0;border-bottom-right-radius:0}.custom-theme .el-input-group__append{border-left:0;border-top-left-radius:0;border-bottom-left-radius:0}.custom-theme .el-input-group--prepend .el-input__inner{border-top-left-radius:0;border-bottom-left-radius:0}.custom-theme .el-input-group--append .el-input__inner{border-top-right-radius:0;border-bottom-right-radius:0}.custom-theme .el-textarea{display:inline-block;width:100%;vertical-align:bottom}.custom-theme .el-textarea__inner{display:block;resize:vertical;padding:5px 15px;line-height:1.5;-webkit-box-sizing:border-box;box-sizing:border-box;width:100%;font-size:14px;color:#5a5e66;background-color:#fff;background-image:none;border:1px solid #d8dce5;border-radius:4px;-webkit-transition:border-color .2s cubic-bezier(.645,.045,.355,1);transition:border-color .2s cubic-bezier(.645,.045,.355,1)}.custom-theme .el-textarea__inner::-webkit-input-placeholder{color:#b4bccc}.custom-theme .el-textarea__inner:-ms-input-placeholder{color:#b4bccc}.custom-theme .el-textarea__inner::-ms-input-placeholder{color:#b4bccc}.custom-theme .el-textarea__inner::placeholder{color:#b4bccc}.custom-theme .el-textarea__inner:hover{border-color:#b4bccc}.custom-theme .el-textarea__inner:focus{outline:0;border-color:#262729}.custom-theme .el-textarea.is-disabled .el-textarea__inner{background-color:#f5f7fa;border-color:#dfe4ed;color:#b4bccc;cursor:not-allowed}.custom-theme .el-textarea.is-disabled .el-textarea__inner::-webkit-input-placeholder{color:#b4bccc}.custom-theme .el-textarea.is-disabled .el-textarea__inner:-ms-input-placeholder{color:#b4bccc}.custom-theme .el-textarea.is-disabled .el-textarea__inner::-ms-input-placeholder{color:#b4bccc}.custom-theme .el-textarea.is-disabled .el-textarea__inner::placeholder{color:#b4bccc}.custom-theme .el-popper .popper__arrow,.custom-theme .el-popper .popper__arrow::after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.custom-theme .el-popper .popper__arrow{border-width:6px;-webkit-filter:drop-shadow(0 2px 12px rgba(0, 0, 0, .03));filter:drop-shadow(0 2px 12px rgba(0, 0, 0, .03))}.custom-theme .el-popper .popper__arrow::after{content:" ";border-width:6px}.custom-theme .el-popper[x-placement^=top]{margin-bottom:12px}.custom-theme .el-popper[x-placement^=top] .popper__arrow{bottom:-6px;left:50%;margin-right:3px;border-top-color:#e6ebf5;border-bottom-width:0}.custom-theme .el-popper[x-placement^=top] .popper__arrow::after{bottom:1px;margin-left:-6px;border-top-color:#fff;border-bottom-width:0}.custom-theme .el-popper[x-placement^=bottom]{margin-top:12px}.custom-theme .el-popper[x-placement^=bottom] .popper__arrow{top:-6px;left:50%;margin-right:3px;border-top-width:0;border-bottom-color:#e6ebf5}.custom-theme .el-popper[x-placement^=bottom] .popper__arrow::after{top:1px;margin-left:-6px;border-top-width:0;border-bottom-color:#fff}.custom-theme .el-popper[x-placement^=right]{margin-left:12px}.custom-theme .el-popper[x-placement^=right] .popper__arrow{top:50%;left:-6px;margin-bottom:3px;border-right-color:#e6ebf5;border-left-width:0}.custom-theme .el-popper[x-placement^=right] .popper__arrow::after{bottom:-6px;left:1px;border-right-color:#fff;border-left-width:0}.custom-theme .el-popper[x-placement^=left]{margin-right:12px}.custom-theme .el-popper[x-placement^=left] .popper__arrow{top:50%;right:-6px;margin-bottom:3px;border-right-width:0;border-left-color:#e6ebf5}.custom-theme .el-popper[x-placement^=left] .popper__arrow::after{right:1px;bottom:-6px;margin-left:-6px;border-right-width:0;border-left-color:#fff}.custom-theme .el-cascader{display:inline-block;position:relative;font-size:14px;line-height:40px}.custom-theme .el-cascader .el-input,.custom-theme .el-cascader .el-input__inner{cursor:pointer}.custom-theme .el-cascader .el-input__icon{-webkit-transition:none;transition:none}.custom-theme .el-cascader .el-icon-arrow-down{-webkit-transition:-webkit-transform .3s;transition:-webkit-transform .3s;transition:transform .3s;transition:transform .3s,-webkit-transform .3s;font-size:14px}.custom-theme .el-cascader .el-icon-arrow-down.is-reverse{-webkit-transform:rotateZ(180deg);transform:rotateZ(180deg)}.custom-theme .el-cascader .el-icon-circle-close{z-index:2;-webkit-transition:color .2s cubic-bezier(.645,.045,.355,1);transition:color .2s cubic-bezier(.645,.045,.355,1)}.custom-theme .el-cascader .el-icon-circle-close:hover{color:#878d99}.custom-theme .el-cascader__clearIcon{z-index:2;position:relative}.custom-theme .el-cascader__label{position:absolute;left:0;top:0;height:100%;padding:0 25px 0 15px;color:#5a5e66;width:100%;white-space:nowrap;text-overflow:ellipsis;overflow:hidden;-webkit-box-sizing:border-box;box-sizing:border-box;cursor:pointer;text-align:left;font-size:inherit}.custom-theme .el-cascader__label span{color:#000}.custom-theme .el-cascader--medium{font-size:14px;line-height:36px}.custom-theme .el-cascader--small{font-size:13px;line-height:32px}.custom-theme .el-cascader--mini{font-size:12px;line-height:28px}.custom-theme .el-cascader.is-disabled .el-cascader__label{z-index:2;color:#b4bccc}.custom-theme .el-cascader-menus{white-space:nowrap;background:#fff;position:absolute;margin:5px 0;z-index:2;border:solid 1px #dfe4ed;border-radius:2px;-webkit-box-shadow:0 2px 12px 0 rgba(0,0,0,.1);box-shadow:0 2px 12px 0 rgba(0,0,0,.1)}.custom-theme .el-cascader-menus .popper__arrow{-webkit-transform:translateX(-400%);transform:translateX(-400%)}.custom-theme .el-cascader-menu{display:inline-block;vertical-align:top;height:204px;overflow:auto;border-right:solid 1px #dfe4ed;background-color:#fff;-webkit-box-sizing:border-box;box-sizing:border-box;margin:0;padding:6px 0;min-width:160px}.custom-theme .el-cascader-menu:last-child{border-right:0}.custom-theme .el-cascader-menu__item{font-size:14px;padding:8px 20px;position:relative;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;color:#5a5e66;height:34px;line-height:1.5;-webkit-box-sizing:border-box;box-sizing:border-box;cursor:pointer}.custom-theme .el-cascader-menu__item--extensible:after{font-family:element-icons;content:"\e604";font-size:14px;color:#bfcbd9;position:absolute;right:15px}.custom-theme .el-cascader-menu__item.is-disabled{color:#b4bccc;background-color:#fff;cursor:not-allowed}.custom-theme .el-cascader-menu__item.is-disabled:hover{background-color:#fff}.custom-theme .el-cascader-menu__item.is-active{color:#262729}.custom-theme .el-cascader-menu__item:hover{background-color:#f5f7fa}.custom-theme .el-cascader-menu__item.selected{color:#fff;background-color:#f5f7fa}.custom-theme .el-cascader-menu__item__keyword{font-weight:700}.custom-theme .el-cascader-menu--flexible{height:auto;max-height:180px;overflow:auto}.custom-theme .el-cascader-menu--flexible .el-cascader-menu__item{overflow:visible}.custom-theme .el-color-hue-slider{position:relative;-webkit-box-sizing:border-box;box-sizing:border-box;width:280px;height:12px;background-color:red;padding:0 2px}.custom-theme .el-color-hue-slider__bar{position:relative;background:-webkit-gradient(linear,left top,right top,from(red),color-stop(17%,#ff0),color-stop(33%,#0f0),color-stop(50%,#0ff),color-stop(67%,#00f),color-stop(83%,#f0f),to(red));background:linear-gradient(to right,red 0,#ff0 17%,#0f0 33%,#0ff 50%,#00f 67%,#f0f 83%,red 100%);height:100%}.custom-theme .el-color-hue-slider__thumb{position:absolute;cursor:pointer;-webkit-box-sizing:border-box;box-sizing:border-box;left:0;top:0;width:4px;height:100%;border-radius:1px;background:#fff;border:1px solid #f0f0f0;-webkit-box-shadow:0 0 2px rgba(0,0,0,.6);box-shadow:0 0 2px rgba(0,0,0,.6);z-index:1}.custom-theme .el-color-hue-slider.is-vertical{width:12px;height:180px;padding:2px 0}.custom-theme .el-color-hue-slider.is-vertical .el-color-hue-slider__bar{background:-webkit-gradient(linear,left top,left bottom,from(red),color-stop(17%,#ff0),color-stop(33%,#0f0),color-stop(50%,#0ff),color-stop(67%,#00f),color-stop(83%,#f0f),to(red));background:linear-gradient(to bottom,red 0,#ff0 17%,#0f0 33%,#0ff 50%,#00f 67%,#f0f 83%,red 100%)}.custom-theme .el-color-hue-slider.is-vertical .el-color-hue-slider__thumb{left:0;top:0;width:100%;height:4px}.custom-theme .el-color-svpanel{position:relative;width:280px;height:180px}.custom-theme .el-color-svpanel__black,.custom-theme .el-color-svpanel__white{position:absolute;top:0;left:0;right:0;bottom:0}.custom-theme .el-color-svpanel__white{background:-webkit-gradient(linear,left top,right top,from(#fff),to(rgba(255,255,255,0)));background:linear-gradient(to right,#fff,rgba(255,255,255,0))}.custom-theme .el-color-svpanel__black{background:-webkit-gradient(linear,left bottom,left top,from(#000),to(transparent));background:linear-gradient(to top,#000,transparent)}.custom-theme .el-color-svpanel__cursor{position:absolute}.custom-theme .el-color-svpanel__cursor>div{cursor:head;width:4px;height:4px;-webkit-box-shadow:0 0 0 1.5px #fff,inset 0 0 1px 1px rgba(0,0,0,.3),0 0 1px 2px rgba(0,0,0,.4);box-shadow:0 0 0 1.5px #fff,inset 0 0 1px 1px rgba(0,0,0,.3),0 0 1px 2px rgba(0,0,0,.4);border-radius:50%;-webkit-transform:translate(-2px,-2px);transform:translate(-2px,-2px)}.custom-theme .el-color-alpha-slider{position:relative;-webkit-box-sizing:border-box;box-sizing:border-box;width:280px;height:12px;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAIAAADZF8uwAAAAGUlEQVQYV2M4gwH+YwCGIasIUwhT25BVBADtzYNYrHvv4gAAAABJRU5ErkJggg==)}.custom-theme .el-color-alpha-slider__bar{position:relative;background:-webkit-gradient(linear,left top,right top,from(rgba(255,255,255,0)),to(white));background:linear-gradient(to right,rgba(255,255,255,0) 0,#fff 100%);height:100%}.custom-theme .el-color-alpha-slider__thumb{position:absolute;cursor:pointer;-webkit-box-sizing:border-box;box-sizing:border-box;left:0;top:0;width:4px;height:100%;border-radius:1px;background:#fff;border:1px solid #f0f0f0;-webkit-box-shadow:0 0 2px rgba(0,0,0,.6);box-shadow:0 0 2px rgba(0,0,0,.6);z-index:1}.custom-theme .el-color-alpha-slider.is-vertical{width:20px;height:180px}.custom-theme .el-color-alpha-slider.is-vertical .el-color-alpha-slider__bar{background:-webkit-gradient(linear,left top,left bottom,from(rgba(255,255,255,0)),to(white));background:linear-gradient(to bottom,rgba(255,255,255,0) 0,#fff 100%)}.custom-theme .el-color-alpha-slider.is-vertical .el-color-alpha-slider__thumb{left:0;top:0;width:100%;height:4px}.custom-theme .el-color-dropdown{width:300px}.custom-theme .el-color-dropdown__main-wrapper{margin-bottom:6px}.custom-theme .el-color-dropdown__main-wrapper::after{content:"";display:table;clear:both}.custom-theme .el-color-dropdown__btns{margin-top:6px;text-align:right}.custom-theme .el-color-dropdown__value{float:left;line-height:26px;font-size:12px;color:#000;width:160px}.custom-theme .el-color-dropdown__btn{border:1px solid #dcdcdc;color:#333;line-height:24px;border-radius:2px;padding:0 20px;cursor:pointer;background-color:transparent;outline:0;font-size:12px}.custom-theme .el-color-dropdown__btn[disabled]{color:#ccc;cursor:not-allowed}.custom-theme .el-color-dropdown__btn:hover{color:#262729;border-color:#262729}.custom-theme .el-color-dropdown__link-btn{cursor:pointer;color:#262729;text-decoration:none;padding:15px;font-size:12px}.custom-theme .el-color-dropdown__link-btn:hover{color:tint(#262729,20%)}.custom-theme .el-color-picker{display:inline-block;position:relative;line-height:normal;height:40px}.custom-theme .el-color-picker.is-disabled .el-color-picker__trigger{cursor:not-allowed}.custom-theme .el-color-picker--medium{height:36px}.custom-theme .el-color-picker--medium .el-color-picker__trigger{height:36px;width:36px}.custom-theme .el-color-picker--medium .el-color-picker__mask{height:34px;width:34px}.custom-theme .el-color-picker--small{height:32px}.custom-theme .el-color-picker--small .el-color-picker__trigger{height:32px;width:32px}.custom-theme .el-color-picker--small .el-color-picker__mask{height:30px;width:30px}.custom-theme .el-color-picker--small .el-color-picker__empty,.custom-theme .el-color-picker--small .el-color-picker__icon{-webkit-transform:translate3d(-50%,-50%,0) scale(.8);transform:translate3d(-50%,-50%,0) scale(.8)}.custom-theme .el-color-picker--mini{height:28px}.custom-theme .el-color-picker--mini .el-color-picker__trigger{height:28px;width:28px}.custom-theme .el-color-picker--mini .el-color-picker__mask{height:26px;width:26px}.custom-theme .el-color-picker--mini .el-color-picker__empty,.custom-theme .el-color-picker--mini .el-color-picker__icon{-webkit-transform:translate3d(-50%,-50%,0) scale(.8);transform:translate3d(-50%,-50%,0) scale(.8)}.custom-theme .el-color-picker__mask{height:38px;width:38px;border-radius:4px;position:absolute;top:1px;left:1px;z-index:1;cursor:not-allowed;background-color:rgba(255,255,255,.7)}.custom-theme .el-color-picker__trigger{display:inline-block;-webkit-box-sizing:border-box;box-sizing:border-box;height:40px;width:40px;padding:4px;border:1px solid #e6e6e6;border-radius:4px;font-size:0;position:relative;cursor:pointer}.custom-theme .el-color-picker__color{position:relative;display:block;-webkit-box-sizing:border-box;box-sizing:border-box;border:1px solid #999;border-radius:2px;width:100%;height:100%;text-align:center}.custom-theme .el-color-picker__color.is-alpha{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAIAAADZF8uwAAAAGUlEQVQYV2M4gwH+YwCGIasIUwhT25BVBADtzYNYrHvv4gAAAABJRU5ErkJggg==)}.custom-theme .el-color-picker__color-inner{position:absolute;left:0;top:0;right:0;bottom:0}.custom-theme .el-color-picker__empty{font-size:12px;color:#999;position:absolute;top:50%;left:50%;-webkit-transform:translate3d(-50%,-50%,0);transform:translate3d(-50%,-50%,0)}.custom-theme .el-color-picker__icon{display:inline-block;position:absolute;width:100%;top:50%;left:50%;-webkit-transform:translate3d(-50%,-50%,0);transform:translate3d(-50%,-50%,0);color:#fff;text-align:center;font-size:12px}.custom-theme .el-color-picker__panel{position:absolute;z-index:10;padding:6px;background-color:#fff;border:1px solid #e6ebf5;border-radius:4px;-webkit-box-shadow:0 2px 12px 0 rgba(0,0,0,.1);box-shadow:0 2px 12px 0 rgba(0,0,0,.1)}.custom-theme .el-input{position:relative;font-size:14px;display:inline-block;width:100%}.custom-theme .el-input::-webkit-scrollbar{z-index:11;width:6px}.custom-theme .el-input::-webkit-scrollbar:horizontal{height:6px}.custom-theme .el-input::-webkit-scrollbar-thumb{border-radius:5px;width:6px;background:#b4bccc}.custom-theme .el-input::-webkit-scrollbar-corner{background:#fff}.custom-theme .el-input::-webkit-scrollbar-track{background:#fff}.custom-theme .el-input::-webkit-scrollbar-track-piece{background:#fff;width:6px}.custom-theme .el-input__inner{-webkit-appearance:none;background-color:#fff;background-image:none;border-radius:4px;border:1px solid #d8dce5;-webkit-box-sizing:border-box;box-sizing:border-box;color:#5a5e66;display:inline-block;font-size:inherit;height:40px;line-height:1;outline:0;padding:0 15px;-webkit-transition:border-color .2s cubic-bezier(.645,.045,.355,1);transition:border-color .2s cubic-bezier(.645,.045,.355,1);width:100%}.custom-theme .el-input__inner::-webkit-input-placeholder{color:#b4bccc}.custom-theme .el-input__inner:-ms-input-placeholder{color:#b4bccc}.custom-theme .el-input__inner::-ms-input-placeholder{color:#b4bccc}.custom-theme .el-input__inner::placeholder{color:#b4bccc}.custom-theme .el-input__inner:hover{border-color:#b4bccc}.custom-theme .el-input__inner:focus{outline:0;border-color:#262729}.custom-theme .el-input__suffix{position:absolute;height:100%;right:5px;top:0;text-align:center;color:#b4bccc;-webkit-transition:all .3s;transition:all .3s;pointer-events:none}.custom-theme .el-input__suffix-inner{pointer-events:all}.custom-theme .el-input__prefix{position:absolute;height:100%;left:5px;top:0;text-align:center;color:#b4bccc;-webkit-transition:all .3s;transition:all .3s}.custom-theme .el-input__icon{height:100%;width:25px;text-align:center;-webkit-transition:all .3s;transition:all .3s;line-height:40px}.custom-theme .el-input__icon:after{content:'';height:100%;width:0;display:inline-block;vertical-align:middle}.custom-theme .el-input__validateIcon{pointer-events:none}.custom-theme .el-input.is-active .el-input__inner{outline:0;border-color:#262729}.custom-theme .el-input.is-disabled .el-input__inner{background-color:#f5f7fa;border-color:#dfe4ed;color:#b4bccc;cursor:not-allowed}.custom-theme .el-input.is-disabled .el-input__inner::-webkit-input-placeholder{color:#b4bccc}.custom-theme .el-input.is-disabled .el-input__inner:-ms-input-placeholder{color:#b4bccc}.custom-theme .el-input.is-disabled .el-input__inner::-ms-input-placeholder{color:#b4bccc}.custom-theme .el-input.is-disabled .el-input__inner::placeholder{color:#b4bccc}.custom-theme .el-input.is-disabled .el-input__icon{cursor:not-allowed}.custom-theme .el-input--suffix .el-input__inner{padding-right:30px}.custom-theme .el-input--prefix .el-input__inner{padding-left:30px}.custom-theme .el-input--medium{font-size:14px}.custom-theme .el-input--medium .el-input__inner{height:36px}.custom-theme .el-input--medium .el-input__icon{line-height:36px}.custom-theme .el-input--small{font-size:13px}.custom-theme .el-input--small .el-input__inner{height:32px}.custom-theme .el-input--small .el-input__icon{line-height:32px}.custom-theme .el-input--mini{font-size:12px}.custom-theme .el-input--mini .el-input__inner{height:28px}.custom-theme .el-input--mini .el-input__icon{line-height:28px}.custom-theme .el-input-group{line-height:normal;display:inline-table;width:100%;border-collapse:separate}.custom-theme .el-input-group>.el-input__inner{vertical-align:middle;display:table-cell}.custom-theme .el-input-group__append,.custom-theme .el-input-group__prepend{background-color:#f5f7fa;color:#0a76a4;vertical-align:middle;display:table-cell;position:relative;border:1px solid #d8dce5;border-radius:4px;padding:0 20px;width:1px;white-space:nowrap}.custom-theme .el-input-group__append:focus,.custom-theme .el-input-group__prepend:focus{outline:0}.custom-theme .el-input-group__append .el-button,.custom-theme .el-input-group__append .el-select,.custom-theme .el-input-group__prepend .el-button,.custom-theme .el-input-group__prepend .el-select{display:inline-block;margin:-20px}.custom-theme .el-input-group__append button.el-button,.custom-theme .el-input-group__append div.el-select .el-input__inner,.custom-theme .el-input-group__append div.el-select:hover .el-input__inner,.custom-theme .el-input-group__prepend button.el-button,.custom-theme .el-input-group__prepend div.el-select .el-input__inner,.custom-theme .el-input-group__prepend div.el-select:hover .el-input__inner{border-color:transparent;background-color:transparent;color:inherit;border-top:0;border-bottom:0}.custom-theme .el-input-group__append .el-button,.custom-theme .el-input-group__append .el-input,.custom-theme .el-input-group__prepend .el-button,.custom-theme .el-input-group__prepend .el-input{font-size:inherit}.custom-theme .el-input-group__prepend{border-right:0;border-top-right-radius:0;border-bottom-right-radius:0}.custom-theme .el-input-group__append{border-left:0;border-top-left-radius:0;border-bottom-left-radius:0}.custom-theme .el-input-group--prepend .el-input__inner{border-top-left-radius:0;border-bottom-left-radius:0}.custom-theme .el-input-group--append .el-input__inner{border-top-right-radius:0;border-bottom-right-radius:0}.custom-theme .el-textarea{display:inline-block;width:100%;vertical-align:bottom}.custom-theme .el-textarea__inner{display:block;resize:vertical;padding:5px 15px;line-height:1.5;-webkit-box-sizing:border-box;box-sizing:border-box;width:100%;font-size:14px;color:#5a5e66;background-color:#fff;background-image:none;border:1px solid #d8dce5;border-radius:4px;-webkit-transition:border-color .2s cubic-bezier(.645,.045,.355,1);transition:border-color .2s cubic-bezier(.645,.045,.355,1)}.custom-theme .el-textarea__inner::-webkit-input-placeholder{color:#b4bccc}.custom-theme .el-textarea__inner:-ms-input-placeholder{color:#b4bccc}.custom-theme .el-textarea__inner::-ms-input-placeholder{color:#b4bccc}.custom-theme .el-textarea__inner::placeholder{color:#b4bccc}.custom-theme .el-textarea__inner:hover{border-color:#b4bccc}.custom-theme .el-textarea__inner:focus{outline:0;border-color:#262729}.custom-theme .el-textarea.is-disabled .el-textarea__inner{background-color:#f5f7fa;border-color:#dfe4ed;color:#b4bccc;cursor:not-allowed}.custom-theme .el-textarea.is-disabled .el-textarea__inner::-webkit-input-placeholder{color:#b4bccc}.custom-theme .el-textarea.is-disabled .el-textarea__inner:-ms-input-placeholder{color:#b4bccc}.custom-theme .el-textarea.is-disabled .el-textarea__inner::-ms-input-placeholder{color:#b4bccc}.custom-theme .el-textarea.is-disabled .el-textarea__inner::placeholder{color:#b4bccc}.custom-theme .el-button{display:inline-block;line-height:1;white-space:nowrap;cursor:pointer;background:#fff;border:1px solid #d8dce5;border-color:#d8dce5;color:#5a5e66;-webkit-appearance:none;text-align:center;-webkit-box-sizing:border-box;box-sizing:border-box;outline:0;margin:0;-webkit-transition:.1s;transition:.1s;font-weight:500;-moz-user-select:none;-webkit-user-select:none;-ms-user-select:none;padding:12px 20px;font-size:14px;border-radius:4px}.custom-theme .el-button+.el-button{margin-left:10px}.custom-theme .el-button.is-round{padding:12px 20px}.custom-theme .el-button:focus,.custom-theme .el-button:hover{color:#262729;border-color:#bebebf;background-color:#e9e9ea}.custom-theme .el-button:active{color:#222325;border-color:#222325;outline:0}.custom-theme .el-button::-moz-focus-inner{border:0}.custom-theme .el-button [class*=el-icon-]+span{margin-left:5px}.custom-theme .el-button.is-plain:focus,.custom-theme .el-button.is-plain:hover{background:#fff;border-color:#262729;color:#262729}.custom-theme .el-button.is-plain:active{background:#fff;border-color:#222325;color:#222325;outline:0}.custom-theme .el-button.is-active{color:#222325;border-color:#222325}.custom-theme .el-button.is-disabled,.custom-theme .el-button.is-disabled:focus,.custom-theme .el-button.is-disabled:hover{color:#b4bccc;cursor:not-allowed;background-image:none;background-color:#fff;border-color:#e6ebf5}.custom-theme .el-button.is-disabled.el-button--text{background-color:transparent}.custom-theme .el-button.is-disabled.is-plain,.custom-theme .el-button.is-disabled.is-plain:focus,.custom-theme .el-button.is-disabled.is-plain:hover{background-color:#fff;border-color:#e6ebf5;color:#b4bccc}.custom-theme .el-button.is-loading{position:relative;pointer-events:none}.custom-theme .el-button.is-loading:before{pointer-events:none;content:'';position:absolute;left:-1px;top:-1px;right:-1px;bottom:-1px;border-radius:inherit;background-color:rgba(255,255,255,.35)}.custom-theme .el-button.is-round{border-radius:20px;padding:12px 23px}.custom-theme .el-button--primary{color:#fff;background-color:#262729;border-color:#262729}.custom-theme .el-button--primary:focus,.custom-theme .el-button--primary:hover{background:#515254;border-color:#515254;color:#fff}.custom-theme .el-button--primary:active{background:#222325;border-color:#222325;color:#fff;outline:0}.custom-theme .el-button--primary.is-active{background:#222325;border-color:#222325;color:#fff}.custom-theme .el-button--primary.is-disabled,.custom-theme .el-button--primary.is-disabled:active,.custom-theme .el-button--primary.is-disabled:focus,.custom-theme .el-button--primary.is-disabled:hover{color:#fff;background-color:#939394;border-color:#939394}.custom-theme .el-button--primary.is-plain{color:#262729;background:#e9e9ea;border-color:#a8a9a9}.custom-theme .el-button--primary.is-plain:focus,.custom-theme .el-button--primary.is-plain:hover{background:#262729;border-color:#262729;color:#fff}.custom-theme .el-button--primary.is-plain:active{background:#222325;border-color:#222325;color:#fff;outline:0}.custom-theme .el-button--primary.is-plain.is-disabled,.custom-theme .el-button--primary.is-plain.is-disabled:active,.custom-theme .el-button--primary.is-plain.is-disabled:focus,.custom-theme .el-button--primary.is-plain.is-disabled:hover{color:#7d7d7f;background-color:#e9e9ea;border-color:#d4d4d4}.custom-theme .el-button--success{color:#fff;background-color:#409167;border-color:#409167}.custom-theme .el-button--success:focus,.custom-theme .el-button--success:hover{background:#66a785;border-color:#66a785;color:#fff}.custom-theme .el-button--success:active{background:#3a835d;border-color:#3a835d;color:#fff;outline:0}.custom-theme .el-button--success.is-active{background:#3a835d;border-color:#3a835d;color:#fff}.custom-theme .el-button--success.is-disabled,.custom-theme .el-button--success.is-disabled:active,.custom-theme .el-button--success.is-disabled:focus,.custom-theme .el-button--success.is-disabled:hover{color:#fff;background-color:#a0c8b3;border-color:#a0c8b3}.custom-theme .el-button--success.is-plain{color:#409167;background:#ecf4f0;border-color:#b3d3c2}.custom-theme .el-button--success.is-plain:focus,.custom-theme .el-button--success.is-plain:hover{background:#409167;border-color:#409167;color:#fff}.custom-theme .el-button--success.is-plain:active{background:#3a835d;border-color:#3a835d;color:#fff;outline:0}.custom-theme .el-button--success.is-plain.is-disabled,.custom-theme .el-button--success.is-plain.is-disabled:active,.custom-theme .el-button--success.is-plain.is-disabled:focus,.custom-theme .el-button--success.is-plain.is-disabled:hover{color:#8cbda4;background-color:#ecf4f0;border-color:#d9e9e1}.custom-theme .el-button--warning{color:#fff;background-color:#9da408;border-color:#9da408}.custom-theme .el-button--warning:focus,.custom-theme .el-button--warning:hover{background:#b1b639;border-color:#b1b639;color:#fff}.custom-theme .el-button--warning:active{background:#8d9407;border-color:#8d9407;color:#fff;outline:0}.custom-theme .el-button--warning.is-active{background:#8d9407;border-color:#8d9407;color:#fff}.custom-theme .el-button--warning.is-disabled,.custom-theme .el-button--warning.is-disabled:active,.custom-theme .el-button--warning.is-disabled:focus,.custom-theme .el-button--warning.is-disabled:hover{color:#fff;background-color:#ced284;border-color:#ced284}.custom-theme .el-button--warning.is-plain{color:#9da408;background:#f5f6e6;border-color:#d8db9c}.custom-theme .el-button--warning.is-plain:focus,.custom-theme .el-button--warning.is-plain:hover{background:#9da408;border-color:#9da408;color:#fff}.custom-theme .el-button--warning.is-plain:active{background:#8d9407;border-color:#8d9407;color:#fff;outline:0}.custom-theme .el-button--warning.is-plain.is-disabled,.custom-theme .el-button--warning.is-plain.is-disabled:active,.custom-theme .el-button--warning.is-plain.is-disabled:focus,.custom-theme .el-button--warning.is-plain.is-disabled:hover{color:#c4c86b;background-color:#f5f6e6;border-color:#ebedce}.custom-theme .el-button--danger{color:#fff;background-color:#b3450e;border-color:#b3450e}.custom-theme .el-button--danger:focus,.custom-theme .el-button--danger:hover{background:#c26a3e;border-color:#c26a3e;color:#fff}.custom-theme .el-button--danger:active{background:#a13e0d;border-color:#a13e0d;color:#fff;outline:0}.custom-theme .el-button--danger.is-active{background:#a13e0d;border-color:#a13e0d;color:#fff}.custom-theme .el-button--danger.is-disabled,.custom-theme .el-button--danger.is-disabled:active,.custom-theme .el-button--danger.is-disabled:focus,.custom-theme .el-button--danger.is-disabled:hover{color:#fff;background-color:#d9a287;border-color:#d9a287}.custom-theme .el-button--danger.is-plain{color:#b3450e;background:#f7ece7;border-color:#e1b59f}.custom-theme .el-button--danger.is-plain:focus,.custom-theme .el-button--danger.is-plain:hover{background:#b3450e;border-color:#b3450e;color:#fff}.custom-theme .el-button--danger.is-plain:active{background:#a13e0d;border-color:#a13e0d;color:#fff;outline:0}.custom-theme .el-button--danger.is-plain.is-disabled,.custom-theme .el-button--danger.is-plain.is-disabled:active,.custom-theme .el-button--danger.is-plain.is-disabled:focus,.custom-theme .el-button--danger.is-plain.is-disabled:hover{color:#d18f6e;background-color:#f7ece7;border-color:#f0dacf}.custom-theme .el-button--info{color:#fff;background-color:#0a76a4;border-color:#0a76a4}.custom-theme .el-button--info:focus,.custom-theme .el-button--info:hover{background:#3b91b6;border-color:#3b91b6;color:#fff}.custom-theme .el-button--info:active{background:#096a94;border-color:#096a94;color:#fff;outline:0}.custom-theme .el-button--info.is-active{background:#096a94;border-color:#096a94;color:#fff}.custom-theme .el-button--info.is-disabled,.custom-theme .el-button--info.is-disabled:active,.custom-theme .el-button--info.is-disabled:focus,.custom-theme .el-button--info.is-disabled:hover{color:#fff;background-color:#85bbd2;border-color:#85bbd2}.custom-theme .el-button--info.is-plain{color:#0a76a4;background:#e7f1f6;border-color:#9dc8db}.custom-theme .el-button--info.is-plain:focus,.custom-theme .el-button--info.is-plain:hover{background:#0a76a4;border-color:#0a76a4;color:#fff}.custom-theme .el-button--info.is-plain:active{background:#096a94;border-color:#096a94;color:#fff;outline:0}.custom-theme .el-button--info.is-plain.is-disabled,.custom-theme .el-button--info.is-plain.is-disabled:active,.custom-theme .el-button--info.is-plain.is-disabled:focus,.custom-theme .el-button--info.is-plain.is-disabled:hover{color:#6cadc8;background-color:#e7f1f6;border-color:#cee4ed}.custom-theme .el-button--medium{padding:10px 20px;font-size:14px;border-radius:4px}.custom-theme .el-button--medium.is-round{padding:10px 20px}.custom-theme .el-button--small{padding:9px 15px;font-size:12px;border-radius:3px}.custom-theme .el-button--small.is-round{padding:9px 15px}.custom-theme .el-button--mini{padding:7px 15px;font-size:12px;border-radius:3px}.custom-theme .el-button--mini.is-round{padding:7px 15px}.custom-theme .el-button--text{border:none;color:#262729;background:0 0;padding-left:0;padding-right:0}.custom-theme .el-button--text:focus,.custom-theme .el-button--text:hover{color:#515254;border-color:transparent;background-color:transparent}.custom-theme .el-button--text:active{color:#222325;border-color:transparent;background-color:transparent}.custom-theme .el-button-group{display:inline-block;vertical-align:middle}.custom-theme .el-button-group::after,.custom-theme .el-button-group::before{display:table;content:""}.custom-theme .el-button-group::after{clear:both}.custom-theme .el-button-group .el-button{float:left;position:relative}.custom-theme .el-button-group .el-button+.el-button{margin-left:0}.custom-theme .el-button-group .el-button:first-child{border-top-right-radius:0;border-bottom-right-radius:0}.custom-theme .el-button-group .el-button:last-child{border-top-left-radius:0;border-bottom-left-radius:0}.custom-theme .el-button-group .el-button:not(:first-child):not(:last-child){border-radius:0}.custom-theme .el-button-group .el-button:not(:last-child){margin-right:-1px}.custom-theme .el-button-group .el-button:active,.custom-theme .el-button-group .el-button:focus,.custom-theme .el-button-group .el-button:hover{z-index:1}.custom-theme .el-button-group .el-button.is-active{z-index:1}.custom-theme .el-button-group .el-button--primary:first-child{border-right-color:rgba(255,255,255,.5)}.custom-theme .el-button-group .el-button--primary:last-child{border-left-color:rgba(255,255,255,.5)}.custom-theme .el-button-group .el-button--primary:not(:first-child):not(:last-child){border-left-color:rgba(255,255,255,.5);border-right-color:rgba(255,255,255,.5)}.custom-theme .el-button-group .el-button--success:first-child{border-right-color:rgba(255,255,255,.5)}.custom-theme .el-button-group .el-button--success:last-child{border-left-color:rgba(255,255,255,.5)}.custom-theme .el-button-group .el-button--success:not(:first-child):not(:last-child){border-left-color:rgba(255,255,255,.5);border-right-color:rgba(255,255,255,.5)}.custom-theme .el-button-group .el-button--warning:first-child{border-right-color:rgba(255,255,255,.5)}.custom-theme .el-button-group .el-button--warning:last-child{border-left-color:rgba(255,255,255,.5)}.custom-theme .el-button-group .el-button--warning:not(:first-child):not(:last-child){border-left-color:rgba(255,255,255,.5);border-right-color:rgba(255,255,255,.5)}.custom-theme .el-button-group .el-button--danger:first-child{border-right-color:rgba(255,255,255,.5)}.custom-theme .el-button-group .el-button--danger:last-child{border-left-color:rgba(255,255,255,.5)}.custom-theme .el-button-group .el-button--danger:not(:first-child):not(:last-child){border-left-color:rgba(255,255,255,.5);border-right-color:rgba(255,255,255,.5)}.custom-theme .el-button-group .el-button--info:first-child{border-right-color:rgba(255,255,255,.5)}.custom-theme .el-button-group .el-button--info:last-child{border-left-color:rgba(255,255,255,.5)}.custom-theme .el-button-group .el-button--info:not(:first-child):not(:last-child){border-left-color:rgba(255,255,255,.5);border-right-color:rgba(255,255,255,.5)}.custom-theme .el-checkbox{color:#5a5e66;font-weight:500;font-size:14px;position:relative;cursor:pointer;display:inline-block;white-space:nowrap;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.custom-theme .el-checkbox.is-bordered{padding:9px 20px 9px 10px;border-radius:4px;border:1px solid #d8dce5}.custom-theme .el-checkbox.is-bordered.is-checked{border-color:#262729}.custom-theme .el-checkbox.is-bordered.is-disabled{border-color:#e6ebf5;cursor:not-allowed}.custom-theme .el-checkbox.is-bordered+.el-checkbox.is-bordered{margin-left:10px}.custom-theme .el-checkbox.is-bordered.el-checkbox--medium{padding:7px 20px 7px 10px;border-radius:4px}.custom-theme .el-checkbox.is-bordered.el-checkbox--medium .el-checkbox__label{line-height:17px;font-size:14px}.custom-theme .el-checkbox.is-bordered.el-checkbox--medium .el-checkbox__inner{height:14px;width:14px}.custom-theme .el-checkbox.is-bordered.el-checkbox--small{padding:3px 15px 7px 10px;border-radius:3px}.custom-theme .el-checkbox.is-bordered.el-checkbox--small .el-checkbox__label{line-height:15px;font-size:12px}.custom-theme .el-checkbox.is-bordered.el-checkbox--small .el-checkbox__inner{height:12px;width:12px}.custom-theme .el-checkbox.is-bordered.el-checkbox--small .el-checkbox__inner::after{height:6px;width:2px}.custom-theme .el-checkbox.is-bordered.el-checkbox--mini{padding:1px 15px 5px 10px;border-radius:3px}.custom-theme .el-checkbox.is-bordered.el-checkbox--mini .el-checkbox__label{line-height:12px;font-size:12px}.custom-theme .el-checkbox.is-bordered.el-checkbox--mini .el-checkbox__inner{height:12px;width:12px}.custom-theme .el-checkbox.is-bordered.el-checkbox--mini .el-checkbox__inner::after{height:6px;width:2px}.custom-theme .el-checkbox__input{white-space:nowrap;cursor:pointer;outline:0;display:inline-block;line-height:1;position:relative;vertical-align:middle}.custom-theme .el-checkbox__input.is-disabled .el-checkbox__inner{background-color:#edf2fc;border-color:#d8dce5;cursor:not-allowed}.custom-theme .el-checkbox__input.is-disabled .el-checkbox__inner::after{cursor:not-allowed;border-color:#b4bccc}.custom-theme .el-checkbox__input.is-disabled .el-checkbox__inner+.el-checkbox__label{cursor:not-allowed}.custom-theme .el-checkbox__input.is-disabled.is-checked .el-checkbox__inner{background-color:#edf2fc;border-color:#d8dce5}.custom-theme .el-checkbox__input.is-disabled.is-checked .el-checkbox__inner::after{border-color:#b4bccc}.custom-theme .el-checkbox__input.is-disabled.is-indeterminate .el-checkbox__inner{background-color:#edf2fc;border-color:#d8dce5}.custom-theme .el-checkbox__input.is-disabled.is-indeterminate .el-checkbox__inner::before{background-color:#b4bccc;border-color:#b4bccc}.custom-theme .el-checkbox__input.is-disabled+span.el-checkbox__label{color:#b4bccc;cursor:not-allowed}.custom-theme .el-checkbox__input.is-checked .el-checkbox__inner{background-color:#262729;border-color:#262729}.custom-theme .el-checkbox__input.is-checked .el-checkbox__inner::after{-webkit-transform:rotate(45deg) scaleY(1);transform:rotate(45deg) scaleY(1)}.custom-theme .el-checkbox__input.is-checked+.el-checkbox__label{color:#262729}.custom-theme .el-checkbox__input.is-focus .el-checkbox__inner{border-color:#262729}.custom-theme .el-checkbox__input.is-indeterminate .el-checkbox__inner{background-color:#262729;border-color:#262729}.custom-theme .el-checkbox__input.is-indeterminate .el-checkbox__inner::before{content:'';position:absolute;display:block;background-color:#fff;height:2px;-webkit-transform:scale(.5);transform:scale(.5);left:0;right:0;top:5px}.custom-theme .el-checkbox__input.is-indeterminate .el-checkbox__inner::after{display:none}.custom-theme .el-checkbox__inner{display:inline-block;position:relative;border:1px solid #d8dce5;border-radius:2px;-webkit-box-sizing:border-box;box-sizing:border-box;width:14px;height:14px;background-color:#fff;z-index:1;-webkit-transition:border-color .25s cubic-bezier(.71,-.46,.29,1.46),background-color .25s cubic-bezier(.71,-.46,.29,1.46);transition:border-color .25s cubic-bezier(.71,-.46,.29,1.46),background-color .25s cubic-bezier(.71,-.46,.29,1.46)}.custom-theme .el-checkbox__inner:hover{border-color:#262729}.custom-theme .el-checkbox__inner::after{-webkit-box-sizing:content-box;box-sizing:content-box;content:"";border:1px solid #fff;border-left:0;border-top:0;height:7px;left:4px;position:absolute;top:1px;-webkit-transform:rotate(45deg) scaleY(0);transform:rotate(45deg) scaleY(0);width:3px;-webkit-transition:-webkit-transform .15s cubic-bezier(.71,-.46,.88,.6) 50ms;transition:-webkit-transform .15s cubic-bezier(.71,-.46,.88,.6) 50ms;transition:transform .15s cubic-bezier(.71,-.46,.88,.6) 50ms;transition:transform .15s cubic-bezier(.71,-.46,.88,.6) 50ms,-webkit-transform .15s cubic-bezier(.71,-.46,.88,.6) 50ms;-webkit-transform-origin:center;transform-origin:center}.custom-theme .el-checkbox__original{opacity:0;outline:0;position:absolute;margin:0;width:0;height:0;left:-999px}.custom-theme .el-checkbox__label{display:inline-block;padding-left:10px;line-height:19px;font-size:14px}.custom-theme .el-checkbox+.el-checkbox{margin-left:30px}.custom-theme .el-checkbox-button{position:relative;display:inline-block}.custom-theme .el-checkbox-button__inner{display:inline-block;line-height:1;font-weight:500;white-space:nowrap;vertical-align:middle;cursor:pointer;background:#fff;border:1px solid #d8dce5;border-left:0;color:#5a5e66;-webkit-appearance:none;text-align:center;-webkit-box-sizing:border-box;box-sizing:border-box;outline:0;margin:0;position:relative;-webkit-transition:all .3s cubic-bezier(.645,.045,.355,1);transition:all .3s cubic-bezier(.645,.045,.355,1);-moz-user-select:none;-webkit-user-select:none;-ms-user-select:none;padding:12px 20px;font-size:14px;border-radius:0}.custom-theme .el-checkbox-button__inner.is-round{padding:12px 20px}.custom-theme .el-checkbox-button__inner:hover{color:#262729}.custom-theme .el-checkbox-button__inner [class*=el-icon-]{line-height:.9}.custom-theme .el-checkbox-button__inner [class*=el-icon-]+span{margin-left:5px}.custom-theme .el-checkbox-button__original{opacity:0;outline:0;position:absolute;margin:0;left:-999px}.custom-theme .el-checkbox-button.is-checked .el-checkbox-button__inner{color:#fff;background-color:#262729;border-color:#262729;-webkit-box-shadow:-1px 0 0 0 #7d7d7f;box-shadow:-1px 0 0 0 #7d7d7f}.custom-theme .el-checkbox-button.is-disabled .el-checkbox-button__inner{color:#b4bccc;cursor:not-allowed;background-image:none;background-color:#fff;border-color:#e6ebf5;-webkit-box-shadow:none;box-shadow:none}.custom-theme .el-checkbox-button:first-child .el-checkbox-button__inner{border-left:1px solid #d8dce5;border-radius:4px 0 0 4px;-webkit-box-shadow:none!important;box-shadow:none!important}.custom-theme .el-checkbox-button.is-focus .el-checkbox-button__inner{border-color:#262729}.custom-theme .el-checkbox-button:last-child .el-checkbox-button__inner{border-radius:0 4px 4px 0}.custom-theme .el-checkbox-button--medium .el-checkbox-button__inner{padding:10px 20px;font-size:14px;border-radius:0}.custom-theme .el-checkbox-button--medium .el-checkbox-button__inner.is-round{padding:10px 20px}.custom-theme .el-checkbox-button--small .el-checkbox-button__inner{padding:9px 15px;font-size:12px;border-radius:0}.custom-theme .el-checkbox-button--small .el-checkbox-button__inner.is-round{padding:9px 15px}.custom-theme .el-checkbox-button--mini .el-checkbox-button__inner{padding:7px 15px;font-size:12px;border-radius:0}.custom-theme .el-checkbox-button--mini .el-checkbox-button__inner.is-round{padding:7px 15px}.custom-theme .el-checkbox-group{font-size:0}.custom-theme .el-transfer{font-size:14px}.custom-theme .el-transfer__buttons{display:inline-block;vertical-align:middle;padding:0 30px}.custom-theme .el-transfer__button{display:block;margin:0 auto;padding:10px;border-radius:50%;color:#fff;background-color:#262729;font-size:0}.custom-theme .el-transfer__button.is-with-texts{border-radius:4px}.custom-theme .el-transfer__button.is-disabled{border:1px solid #d8dce5;background-color:#f5f7fa;color:#b4bccc}.custom-theme .el-transfer__button.is-disabled:hover{border:1px solid #d8dce5;background-color:#f5f7fa;color:#b4bccc}.custom-theme .el-transfer__button:first-child{margin-bottom:10px}.custom-theme .el-transfer__button:nth-child(2){margin:0}.custom-theme .el-transfer__button i,.custom-theme .el-transfer__button span{font-size:14px}.custom-theme .el-transfer__button [class*=el-icon-]+span{margin-left:0}.custom-theme .el-transfer-panel{border:1px solid #e6ebf5;border-radius:4px;overflow:hidden;background:#fff;display:inline-block;vertical-align:middle;width:200px;-webkit-box-sizing:border-box;box-sizing:border-box;position:relative}.custom-theme .el-transfer-panel__body{height:246px}.custom-theme .el-transfer-panel__body.is-with-footer{padding-bottom:40px}.custom-theme .el-transfer-panel__list{margin:0;padding:6px 0;list-style:none;height:246px;overflow:auto;-webkit-box-sizing:border-box;box-sizing:border-box}.custom-theme .el-transfer-panel__list.is-filterable{height:194px;padding-top:0}.custom-theme .el-transfer-panel__item{height:30px;line-height:30px;padding-left:15px;display:block}.custom-theme .el-transfer-panel__item+.el-transfer-panel__item{margin-left:0}.custom-theme .el-transfer-panel__item.el-checkbox{color:#5a5e66}.custom-theme .el-transfer-panel__item:hover{color:#262729}.custom-theme .el-transfer-panel__item.el-checkbox .el-checkbox__label{width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;display:block;-webkit-box-sizing:border-box;box-sizing:border-box;padding-left:24px;line-height:30px}.custom-theme .el-transfer-panel__item .el-checkbox__input{position:absolute;top:8px}.custom-theme .el-transfer-panel__filter{text-align:center;margin:15px;-webkit-box-sizing:border-box;box-sizing:border-box;display:block;width:auto}.custom-theme .el-transfer-panel__filter .el-input__inner{height:32px;width:100%;font-size:12px;display:inline-block;-webkit-box-sizing:border-box;box-sizing:border-box;border-radius:16px;padding-right:10px;padding-left:30px}.custom-theme .el-transfer-panel__filter .el-input__icon{margin-left:5px}.custom-theme .el-transfer-panel__filter .el-icon-circle-close{cursor:pointer}.custom-theme .el-transfer-panel .el-transfer-panel__header{height:40px;line-height:40px;background:#f5f7fa;margin:0;padding-left:15px;border-bottom:1px solid #e6ebf5;-webkit-box-sizing:border-box;box-sizing:border-box;color:#000}.custom-theme .el-transfer-panel .el-transfer-panel__header .el-checkbox{display:block;line-height:40px}.custom-theme .el-transfer-panel .el-transfer-panel__header .el-checkbox .el-checkbox__label{font-size:16px;color:#2d2f33;font-weight:400}.custom-theme .el-transfer-panel .el-transfer-panel__header .el-checkbox .el-checkbox__label span{position:absolute;right:15px;color:#878d99;font-size:12px;font-weight:400}.custom-theme .el-transfer-panel .el-transfer-panel__footer{height:40px;background:#fff;margin:0;padding:0;border-top:1px solid #e6ebf5;position:absolute;bottom:0;left:0;width:100%;z-index:1}.custom-theme .el-transfer-panel .el-transfer-panel__footer::after{display:inline-block;content:"";height:100%;vertical-align:middle}.custom-theme .el-transfer-panel .el-transfer-panel__footer .el-checkbox{padding-left:20px;color:#5a5e66}.custom-theme .el-transfer-panel .el-transfer-panel__empty{margin:0;height:30px;line-height:30px;padding:6px 15px 0;color:#878d99}.custom-theme .el-transfer-panel .el-checkbox__label{padding-left:8px}.custom-theme .el-transfer-panel .el-checkbox__inner{height:14px;width:14px;border-radius:3px}.custom-theme .el-transfer-panel .el-checkbox__inner::after{height:6px;width:3px;left:4px}.custom-theme .el-container{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row;-webkit-box-flex:1;-ms-flex:1;flex:1;-webkit-box-sizing:border-box;box-sizing:border-box}.custom-theme .el-container.is-vertical{-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.custom-theme .el-header{padding:0 20px;-webkit-box-sizing:border-box;box-sizing:border-box}.custom-theme .el-aside{overflow:auto;-webkit-box-sizing:border-box;box-sizing:border-box}.custom-theme .el-main{-webkit-box-flex:1;-ms-flex:1;flex:1;overflow:auto;-webkit-box-sizing:border-box;box-sizing:border-box;padding:20px}.custom-theme .el-footer{padding:0 20px;-webkit-box-sizing:border-box;box-sizing:border-box}
\ No newline at end of file
diff --git a/frontend/src/components/BackToTop/index.vue b/frontend/src/components/BackToTop/index.vue
new file mode 100644
index 0000000..36522f4
--- /dev/null
+++ b/frontend/src/components/BackToTop/index.vue
@@ -0,0 +1,111 @@
+<template>
+  <transition :name="transitionName">
+    <div v-show="visible" :style="customStyle" class="back-to-ceiling" @click="backToTop">
+      <svg width="16" height="16" viewBox="0 0 17 17" xmlns="http://www.w3.org/2000/svg" class="Icon Icon--backToTopArrow" aria-hidden="true" style="height:16px;width:16px"><path d="M12.036 15.59a1 1 0 0 1-.997.995H5.032a.996.996 0 0 1-.997-.996V8.584H1.03c-1.1 0-1.36-.633-.578-1.416L7.33.29a1.003 1.003 0 0 1 1.412 0l6.878 6.88c.782.78.523 1.415-.58 1.415h-3.004v7.004z" /></svg>
+    </div>
+  </transition>
+</template>
+
+<script>
+export default {
+  name: 'BackToTop',
+  props: {
+    visibilityHeight: {
+      type: Number,
+      default: 400
+    },
+    backPosition: {
+      type: Number,
+      default: 0
+    },
+    customStyle: {
+      type: Object,
+      default: function() {
+        return {
+          right: '50px',
+          bottom: '50px',
+          width: '40px',
+          height: '40px',
+          'border-radius': '4px',
+          'line-height': '45px',
+          background: '#e7eaf1'
+        }
+      }
+    },
+    transitionName: {
+      type: String,
+      default: 'fade'
+    }
+  },
+  data() {
+    return {
+      visible: false,
+      interval: null,
+      isMoving: false
+    }
+  },
+  mounted() {
+    window.addEventListener('scroll', this.handleScroll)
+  },
+  beforeDestroy() {
+    window.removeEventListener('scroll', this.handleScroll)
+    if (this.interval) {
+      clearInterval(this.interval)
+    }
+  },
+  methods: {
+    handleScroll() {
+      this.visible = window.pageYOffset > this.visibilityHeight
+    },
+    backToTop() {
+      if (this.isMoving) return
+      const start = window.pageYOffset
+      let i = 0
+      this.isMoving = true
+      this.interval = setInterval(() => {
+        const next = Math.floor(this.easeInOutQuad(10 * i, start, -start, 500))
+        if (next <= this.backPosition) {
+          window.scrollTo(0, this.backPosition)
+          clearInterval(this.interval)
+          this.isMoving = false
+        } else {
+          window.scrollTo(0, next)
+        }
+        i++
+      }, 16.7)
+    },
+    easeInOutQuad(t, b, c, d) {
+      if ((t /= d / 2) < 1) return c / 2 * t * t + b
+      return -c / 2 * (--t * (t - 2) - 1) + b
+    }
+  }
+}
+</script>
+
+<style scoped>
+.back-to-ceiling {
+  position: fixed;
+  display: inline-block;
+  text-align: center;
+  cursor: pointer;
+}
+
+.back-to-ceiling:hover {
+  background: #d5dbe7;
+}
+
+.fade-enter-active,
+.fade-leave-active {
+  transition: opacity .5s;
+}
+
+.fade-enter,
+.fade-leave-to {
+  opacity: 0
+}
+
+.back-to-ceiling .Icon {
+  fill: #9aaabf;
+  background: none;
+}
+</style>
diff --git a/frontend/src/components/Breadcrumb/index.vue b/frontend/src/components/Breadcrumb/index.vue
new file mode 100644
index 0000000..e224ff7
--- /dev/null
+++ b/frontend/src/components/Breadcrumb/index.vue
@@ -0,0 +1,82 @@
+<template>
+  <el-breadcrumb class="app-breadcrumb" separator="/">
+    <transition-group name="breadcrumb">
+      <el-breadcrumb-item v-for="(item,index) in levelList" :key="item.path">
+        <span v-if="item.redirect==='noRedirect'||index==levelList.length-1" class="no-redirect">{{ item.meta.title }}</span>
+        <a v-else @click.prevent="handleLink(item)">{{ item.meta.title }}</a>
+      </el-breadcrumb-item>
+    </transition-group>
+  </el-breadcrumb>
+</template>
+
+<script>
+import pathToRegexp from 'path-to-regexp'
+
+export default {
+  data() {
+    return {
+      levelList: null
+    }
+  },
+  watch: {
+    $route(route) {
+      // if you go to the redirect page, do not update the breadcrumbs
+      if (route.path.startsWith('/redirect/')) {
+        return
+      }
+      this.getBreadcrumb()
+    }
+  },
+  created() {
+    this.getBreadcrumb()
+  },
+  methods: {
+    getBreadcrumb() {
+      // only show routes with meta.title
+      let matched = this.$route.matched.filter(item => item.meta && item.meta.title)
+      const first = matched[0]
+
+      if (!this.isDashboard(first)) {
+        matched = [{ path: '/dashboard', meta: { title: 'Dashboard' }}].concat(matched)
+      }
+
+      this.levelList = matched.filter(item => item.meta && item.meta.title && item.meta.breadcrumb !== false)
+    },
+    isDashboard(route) {
+      const name = route && route.name
+      if (!name) {
+        return false
+      }
+      return name.trim().toLocaleLowerCase() === 'Dashboard'.toLocaleLowerCase()
+    },
+    pathCompile(path) {
+      // To solve this problem https://github.com/PanJiaChen/vue-element-admin/issues/561
+      const { params } = this.$route
+      var toPath = pathToRegexp.compile(path)
+      return toPath(params)
+    },
+    handleLink(item) {
+      const { redirect, path } = item
+      if (redirect) {
+        this.$router.push(redirect)
+        return
+      }
+      this.$router.push(this.pathCompile(path))
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.app-breadcrumb.el-breadcrumb {
+  display: inline-block;
+  font-size: 14px;
+  line-height: 50px;
+  margin-left: 8px;
+
+  .no-redirect {
+    color: #97a8be;
+    cursor: text;
+  }
+}
+</style>
diff --git a/frontend/src/components/Charts/Keyboard.vue b/frontend/src/components/Charts/Keyboard.vue
new file mode 100644
index 0000000..0b258f3
--- /dev/null
+++ b/frontend/src/components/Charts/Keyboard.vue
@@ -0,0 +1,155 @@
+<template>
+  <div :id="id" :class="className" :style="{height:height,width:width}" />
+</template>
+
+<script>
+import echarts from 'echarts'
+import resize from './mixins/resize'
+
+export default {
+  mixins: [resize],
+  props: {
+    className: {
+      type: String,
+      default: 'chart'
+    },
+    id: {
+      type: String,
+      default: 'chart'
+    },
+    width: {
+      type: String,
+      default: '200px'
+    },
+    height: {
+      type: String,
+      default: '200px'
+    }
+  },
+  data() {
+    return {
+      chart: null
+    }
+  },
+  mounted() {
+    this.initChart()
+  },
+  beforeDestroy() {
+    if (!this.chart) {
+      return
+    }
+    this.chart.dispose()
+    this.chart = null
+  },
+  methods: {
+    initChart() {
+      this.chart = echarts.init(document.getElementById(this.id))
+
+      const xAxisData = []
+      const data = []
+      const data2 = []
+      for (let i = 0; i < 50; i++) {
+        xAxisData.push(i)
+        data.push((Math.sin(i / 5) * (i / 5 - 10) + i / 6) * 5)
+        data2.push((Math.sin(i / 5) * (i / 5 + 10) + i / 6) * 3)
+      }
+      this.chart.setOption({
+        backgroundColor: '#08263a',
+        grid: {
+          left: '5%',
+          right: '5%'
+        },
+        xAxis: [{
+          show: false,
+          data: xAxisData
+        }, {
+          show: false,
+          data: xAxisData
+        }],
+        visualMap: {
+          show: false,
+          min: 0,
+          max: 50,
+          dimension: 0,
+          inRange: {
+            color: ['#4a657a', '#308e92', '#b1cfa5', '#f5d69f', '#f5898b', '#ef5055']
+          }
+        },
+        yAxis: {
+          axisLine: {
+            show: false
+          },
+          axisLabel: {
+            textStyle: {
+              color: '#4a657a'
+            }
+          },
+          splitLine: {
+            show: true,
+            lineStyle: {
+              color: '#08263f'
+            }
+          },
+          axisTick: {
+            show: false
+          }
+        },
+        series: [{
+          name: 'back',
+          type: 'bar',
+          data: data2,
+          z: 1,
+          itemStyle: {
+            normal: {
+              opacity: 0.4,
+              barBorderRadius: 5,
+              shadowBlur: 3,
+              shadowColor: '#111'
+            }
+          }
+        }, {
+          name: 'Simulate Shadow',
+          type: 'line',
+          data,
+          z: 2,
+          showSymbol: false,
+          animationDelay: 0,
+          animationEasing: 'linear',
+          animationDuration: 1200,
+          lineStyle: {
+            normal: {
+              color: 'transparent'
+            }
+          },
+          areaStyle: {
+            normal: {
+              color: '#08263a',
+              shadowBlur: 50,
+              shadowColor: '#000'
+            }
+          }
+        }, {
+          name: 'front',
+          type: 'bar',
+          data,
+          xAxisIndex: 1,
+          z: 3,
+          itemStyle: {
+            normal: {
+              barBorderRadius: 5
+            }
+          }
+        }],
+        animationEasing: 'elasticOut',
+        animationEasingUpdate: 'elasticOut',
+        animationDelay(idx) {
+          return idx * 20
+        },
+        animationDelayUpdate(idx) {
+          return idx * 20
+        }
+      })
+    }
+  }
+}
+</script>
diff --git a/frontend/src/components/Charts/LineMarker.vue b/frontend/src/components/Charts/LineMarker.vue
new file mode 100644
index 0000000..3dd7436
--- /dev/null
+++ b/frontend/src/components/Charts/LineMarker.vue
@@ -0,0 +1,227 @@
+<template>
+  <div :id="id" :class="className" :style="{height:height,width:width}" />
+</template>
+
+<script>
+import echarts from 'echarts'
+import resize from './mixins/resize'
+
+export default {
+  mixins: [resize],
+  props: {
+    className: {
+      type: String,
+      default: 'chart'
+    },
+    id: {
+      type: String,
+      default: 'chart'
+    },
+    width: {
+      type: String,
+      default: '200px'
+    },
+    height: {
+      type: String,
+      default: '200px'
+    }
+  },
+  data() {
+    return {
+      chart: null
+    }
+  },
+  mounted() {
+    this.initChart()
+  },
+  beforeDestroy() {
+    if (!this.chart) {
+      return
+    }
+    this.chart.dispose()
+    this.chart = null
+  },
+  methods: {
+    initChart() {
+      this.chart = echarts.init(document.getElementById(this.id))
+
+      this.chart.setOption({
+        backgroundColor: '#394056',
+        title: {
+          top: 20,
+          text: 'Requests',
+          textStyle: {
+            fontWeight: 'normal',
+            fontSize: 16,
+            color: '#F1F1F3'
+          },
+          left: '1%'
+        },
+        tooltip: {
+          trigger: 'axis',
+          axisPointer: {
+            lineStyle: {
+              color: '#57617B'
+            }
+          }
+        },
+        legend: {
+          top: 20,
+          icon: 'rect',
+          itemWidth: 14,
+          itemHeight: 5,
+          itemGap: 13,
+          data: ['CMCC', 'CTCC', 'CUCC'],
+          right: '4%',
+          textStyle: {
+            fontSize: 12,
+            color: '#F1F1F3'
+          }
+        },
+        grid: {
+          top: 100,
+          left: '2%',
+          right: '2%',
+          bottom: '2%',
+          containLabel: true
+        },
+        xAxis: [{
+          type: 'category',
+          boundaryGap: false,
+          axisLine: {
+            lineStyle: {
+              color: '#57617B'
+            }
+          },
+          data: ['13:00', '13:05', '13:10', '13:15', '13:20', '13:25', '13:30', '13:35', '13:40', '13:45', '13:50', '13:55']
+        }],
+        yAxis: [{
+          type: 'value',
+          name: '(%)',
+          axisTick: {
+            show: false
+          },
+          axisLine: {
+            lineStyle: {
+              color: '#57617B'
+            }
+          },
+          axisLabel: {
+            margin: 10,
+            textStyle: {
+              fontSize: 14
+            }
+          },
+          splitLine: {
+            lineStyle: {
+              color: '#57617B'
+            }
+          }
+        }],
+        series: [{
+          name: 'CMCC',
+          type: 'line',
+          smooth: true,
+          symbol: 'circle',
+          symbolSize: 5,
+          showSymbol: false,
+          lineStyle: {
+            normal: {
+              width: 1
+            }
+          },
+          areaStyle: {
+            normal: {
+              color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
+                offset: 0,
+                color: 'rgba(137, 189, 27, 0.3)'
+              }, {
+                offset: 0.8,
+                color: 'rgba(137, 189, 27, 0)'
+              }], false),
+              shadowColor: 'rgba(0, 0, 0, 0.1)',
+              shadowBlur: 10
+            }
+          },
+          itemStyle: {
+            normal: {
+              color: 'rgb(137,189,27)',
+              borderColor: 'rgba(137,189,2,0.27)',
+              borderWidth: 12
+
+            }
+          },
+          data: [220, 182, 191, 134, 150, 120, 110, 125, 145, 122, 165, 122]
+        }, {
+          name: 'CTCC',
+          type: 'line',
+          smooth: true,
+          symbol: 'circle',
+          symbolSize: 5,
+          showSymbol: false,
+          lineStyle: {
+            normal: {
+              width: 1
+            }
+          },
+          areaStyle: {
+            normal: {
+              color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
+                offset: 0,
+                color: 'rgba(0, 136, 212, 0.3)'
+              }, {
+                offset: 0.8,
+                color: 'rgba(0, 136, 212, 0)'
+              }], false),
+              shadowColor: 'rgba(0, 0, 0, 0.1)',
+              shadowBlur: 10
+            }
+          },
+          itemStyle: {
+            normal: {
+              color: 'rgb(0,136,212)',
+              borderColor: 'rgba(0,136,212,0.2)',
+              borderWidth: 12
+
+            }
+          },
+          data: [120, 110, 125, 145, 122, 165, 122, 220, 182, 191, 134, 150]
+        }, {
+          name: 'CUCC',
+          type: 'line',
+          smooth: true,
+          symbol: 'circle',
+          symbolSize: 5,
+          showSymbol: false,
+          lineStyle: {
+            normal: {
+              width: 1
+            }
+          },
+          areaStyle: {
+            normal: {
+              color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
+                offset: 0,
+                color: 'rgba(219, 50, 51, 0.3)'
+              }, {
+                offset: 0.8,
+                color: 'rgba(219, 50, 51, 0)'
+              }], false),
+              shadowColor: 'rgba(0, 0, 0, 0.1)',
+              shadowBlur: 10
+            }
+          },
+          itemStyle: {
+            normal: {
+              color: 'rgb(219,50,51)',
+              borderColor: 'rgba(219,50,51,0.2)',
+              borderWidth: 12
+            }
+          },
+          data: [220, 182, 125, 145, 122, 191, 134, 150, 120, 110, 165, 122]
+        }]
+      })
+    }
+  }
+}
+</script>
diff --git a/frontend/src/components/Charts/MixChart.vue b/frontend/src/components/Charts/MixChart.vue
new file mode 100644
index 0000000..c416542
--- /dev/null
+++ b/frontend/src/components/Charts/MixChart.vue
@@ -0,0 +1,271 @@
+<template>
+  <div :id="id" :class="className" :style="{height:height,width:width}" />
+</template>
+
+<script>
+import echarts from 'echarts'
+import resize from './mixins/resize'
+
+export default {
+  mixins: [resize],
+  props: {
+    className: {
+      type: String,
+      default: 'chart'
+    },
+    id: {
+      type: String,
+      default: 'chart'
+    },
+    width: {
+      type: String,
+      default: '200px'
+    },
+    height: {
+      type: String,
+      default: '200px'
+    }
+  },
+  data() {
+    return {
+      chart: null
+    }
+  },
+  mounted() {
+    this.initChart()
+  },
+  beforeDestroy() {
+    if (!this.chart) {
+      return
+    }
+    this.chart.dispose()
+    this.chart = null
+  },
+  methods: {
+    initChart() {
+      this.chart = echarts.init(document.getElementById(this.id))
+      const xData = (function() {
+        const data = []
+        for (let i = 1; i < 13; i++) {
+          data.push(i + 'month')
+        }
+        return data
+      }())
+      this.chart.setOption({
+        backgroundColor: '#344b58',
+        title: {
+          text: 'statistics',
+          x: '20',
+          top: '20',
+          textStyle: {
+            color: '#fff',
+            fontSize: '22'
+          },
+          subtextStyle: {
+            color: '#90979c',
+            fontSize: '16'
+          }
+        },
+        tooltip: {
+          trigger: 'axis',
+          axisPointer: {
+            textStyle: {
+              color: '#fff'
+            }
+          }
+        },
+        grid: {
+          left: '5%',
+          right: '5%',
+          borderWidth: 0,
+          top: 150,
+          bottom: 95,
+          textStyle: {
+            color: '#fff'
+          }
+        },
+        legend: {
+          x: '5%',
+          top: '10%',
+          textStyle: {
+            color: '#90979c'
+          },
+          data: ['female', 'male', 'average']
+        },
+        calculable: true,
+        xAxis: [{
+          type: 'category',
+          axisLine: {
+            lineStyle: {
+              color: '#90979c'
+            }
+          },
+          splitLine: {
+            show: false
+          },
+          axisTick: {
+            show: false
+          },
+          splitArea: {
+            show: false
+          },
+          axisLabel: {
+            interval: 0
+
+          },
+          data: xData
+        }],
+        yAxis: [{
+          type: 'value',
+          splitLine: {
+            show: false
+          },
+          axisLine: {
+            lineStyle: {
+              color: '#90979c'
+            }
+          },
+          axisTick: {
+            show: false
+          },
+          axisLabel: {
+            interval: 0
+          },
+          splitArea: {
+            show: false
+          }
+        }],
+        dataZoom: [{
+          show: true,
+          height: 30,
+          xAxisIndex: [
+            0
+          ],
+          bottom: 30,
+          start: 10,
+          end: 80,
+          handleIcon: 'path://M306.1,413c0,2.2-1.8,4-4,4h-59.8c-2.2,0-4-1.8-4-4V200.8c0-2.2,1.8-4,4-4h59.8c2.2,0,4,1.8,4,4V413z',
+          handleSize: '110%',
+          handleStyle: {
+            color: '#d3dee5'
+
+          },
+          textStyle: {
+            color: '#fff' },
+          borderColor: '#90979c'
+
+        }, {
+          type: 'inside',
+          show: true,
+          height: 15,
+          start: 1,
+          end: 35
+        }],
+        series: [{
+          name: 'female',
+          type: 'bar',
+          stack: 'total',
+          barMaxWidth: 35,
+          barGap: '10%',
+          itemStyle: {
+            normal: {
+              color: 'rgba(255,144,128,1)',
+              label: {
+                show: true,
+                textStyle: {
+                  color: '#fff'
+                },
+                position: 'insideTop',
+                formatter(p) {
+                  return p.value > 0 ? p.value : ''
+                }
+              }
+            }
+          },
+          data: [
+            709,
+            1917,
+            2455,
+            2610,
+            1719,
+            1433,
+            1544,
+            3285,
+            5208,
+            3372,
+            2484,
+            4078
+          ]
+        },
+
+        {
+          name: 'male',
+          type: 'bar',
+          stack: 'total',
+          itemStyle: {
+            normal: {
+              color: 'rgba(0,191,183,1)',
+              barBorderRadius: 0,
+              label: {
+                show: true,
+                position: 'top',
+                formatter(p) {
+                  return p.value > 0 ? p.value : ''
+                }
+              }
+            }
+          },
+          data: [
+            327,
+            1776,
+            507,
+            1200,
+            800,
+            482,
+            204,
+            1390,
+            1001,
+            951,
+            381,
+            220
+          ]
+        }, {
+          name: 'average',
+          type: 'line',
+          stack: 'total',
+          symbolSize: 10,
+          symbol: 'circle',
+          itemStyle: {
+            normal: {
+              color: 'rgba(252,230,48,1)',
+              barBorderRadius: 0,
+              label: {
+                show: true,
+                position: 'top',
+                formatter(p) {
+                  return p.value > 0 ? p.value : ''
+                }
+              }
+            }
+          },
+          data: [
+            1036,
+            3693,
+            2962,
+            3810,
+            2519,
+            1915,
+            1748,
+            4675,
+            6209,
+            4323,
+            2865,
+            4298
+          ]
+        }
+        ]
+      })
+    }
+  }
+}
+</script>
diff --git a/frontend/src/components/Charts/mixins/resize.js b/frontend/src/components/Charts/mixins/resize.js
new file mode 100644
index 0000000..b1e76e9
--- /dev/null
+++ b/frontend/src/components/Charts/mixins/resize.js
@@ -0,0 +1,56 @@
+import { debounce } from '@/utils'
+
+export default {
+  data() {
+    return {
+      $_sidebarElm: null,
+      $_resizeHandler: null
+    }
+  },
+  mounted() {
+    this.initListener()
+  },
+  activated() {
+    if (!this.$_resizeHandler) {
+      // avoid duplication init
+      this.initListener()
+    }
+
+    // when keep-alive chart activated, auto resize
+    this.resize()
+  },
+  beforeDestroy() {
+    this.destroyListener()
+  },
+  deactivated() {
+    this.destroyListener()
+  },
+  methods: {
+    // use $_ for mixins properties
+    // https://vuejs.org/v2/style-guide/index.html#Private-property-names-essential
+    $_sidebarResizeHandler(e) {
+      if (e.propertyName === 'width') {
+        this.$_resizeHandler()
+      }
+    },
+    initListener() {
+      this.$_resizeHandler = debounce(() => {
+        this.resize()
+      }, 100)
+      window.addEventListener('resize', this.$_resizeHandler)
+
+      this.$_sidebarElm = document.getElementsByClassName('sidebar-container')[0]
+      this.$_sidebarElm && this.$_sidebarElm.addEventListener('transitionend', this.$_sidebarResizeHandler)
+    },
+    destroyListener() {
+      window.removeEventListener('resize', this.$_resizeHandler)
+      this.$_resizeHandler = null
+
+      this.$_sidebarElm && this.$_sidebarElm.removeEventListener('transitionend', this.$_sidebarResizeHandler)
+    },
+    resize() {
+      const { chart } = this
+      chart && chart.resize()
+    }
+  }
+}
diff --git a/frontend/src/components/DndList/index.vue b/frontend/src/components/DndList/index.vue
new file mode 100644
index 0000000..23ca006
--- /dev/null
+++ b/frontend/src/components/DndList/index.vue
@@ -0,0 +1,166 @@
+<template>
+  <div class="dndList">
+    <div :style="{width:width1}" class="dndList-list">
+      <h3>{{ list1Title }}</h3>
+      <draggable :set-data="setData" :list="list1" group="article" class="dragArea">
+        <div v-for="element in list1" :key="element.id" class="list-complete-item">
+          <div class="list-complete-item-handle">
+            {{ element.id }}[{{ element.author }}] {{ element.title }}
+          </div>
+          <div style="position:absolute;right:0px;">
+            <span style="float: right ;margin-top: -20px;margin-right:5px;" @click="deleteEle(element)">
+              <i style="color:#ff4949" class="el-icon-delete" />
+            </span>
+          </div>
+        </div>
+      </draggable>
+    </div>
+    <div :style="{width:width2}" class="dndList-list">
+      <h3>{{ list2Title }}</h3>
+      <draggable :list="list2" group="article" class="dragArea">
+        <div v-for="element in list2" :key="element.id" class="list-complete-item">
+          <div class="list-complete-item-handle2" @click="pushEle(element)">
+            {{ element.id }} [{{ element.author }}] {{ element.title }}
+          </div>
+        </div>
+      </draggable>
+    </div>
+  </div>
+</template>
+
+<script>
+import draggable from 'vuedraggable'
+
+export default {
+  name: 'DndList',
+  components: { draggable },
+  props: {
+    list1: {
+      type: Array,
+      default() {
+        return []
+      }
+    },
+    list2: {
+      type: Array,
+      default() {
+        return []
+      }
+    },
+    list1Title: {
+      type: String,
+      default: 'list1'
+    },
+    list2Title: {
+      type: String,
+      default: 'list2'
+    },
+    width1: {
+      type: String,
+      default: '48%'
+    },
+    width2: {
+      type: String,
+      default: '48%'
+    }
+  },
+  methods: {
+    isNotInList1(v) {
+      return this.list1.every(k => v.id !== k.id)
+    },
+    isNotInList2(v) {
+      return this.list2.every(k => v.id !== k.id)
+    },
+    deleteEle(ele) {
+      for (const item of this.list1) {
+        if (item.id === ele.id) {
+          const index = this.list1.indexOf(item)
+          this.list1.splice(index, 1)
+          break
+        }
+      }
+      if (this.isNotInList2(ele)) {
+        this.list2.unshift(ele)
+      }
+    },
+    pushEle(ele) {
+      for (const item of this.list2) {
+        if (item.id === ele.id) {
+          const index = this.list2.indexOf(item)
+          this.list2.splice(index, 1)
+          break
+        }
+      }
+      if (this.isNotInList1(ele)) {
+        this.list1.push(ele)
+      }
+    },
+    setData(dataTransfer) {
+      // to avoid Firefox bug
+      // Detail see : https://github.com/RubaXa/Sortable/issues/1012
+      dataTransfer.setData('Text', '')
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.dndList {
+  background: #fff;
+  padding-bottom: 40px;
+  &:after {
+    content: "";
+    display: table;
+    clear: both;
+  }
+  .dndList-list {
+    float: left;
+    padding-bottom: 30px;
+    &:first-of-type {
+      margin-right: 2%;
+    }
+    .dragArea {
+      margin-top: 15px;
+      min-height: 50px;
+      padding-bottom: 30px;
+    }
+  }
+}
+
+.list-complete-item {
+  cursor: pointer;
+  position: relative;
+  font-size: 14px;
+  padding: 5px 12px;
+  margin-top: 4px;
+  border: 1px solid #bfcbd9;
+  transition: all 1s;
+}
+
+.list-complete-item-handle {
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+  margin-right: 50px;
+}
+
+.list-complete-item-handle2 {
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+  margin-right: 20px;
+}
+
+.list-complete-item.sortable-chosen {
+  background: #4AB7BD;
+}
+
+.list-complete-item.sortable-ghost {
+  background: #30B08F;
+}
+
+.list-complete-enter,
+.list-complete-leave-active {
+  opacity: 0;
+}
+</style>
diff --git a/frontend/src/components/DragSelect/index.vue b/frontend/src/components/DragSelect/index.vue
new file mode 100644
index 0000000..47f4247
--- /dev/null
+++ b/frontend/src/components/DragSelect/index.vue
@@ -0,0 +1,65 @@
+<template>
+  <el-select ref="dragSelect" v-model="selectVal" v-bind="$attrs" class="drag-select" multiple v-on="$listeners">
+    <slot />
+  </el-select>
+</template>
+
+<script>
+import Sortable from 'sortablejs'
+
+export default {
+  name: 'DragSelect',
+  props: {
+    value: {
+      type: Array,
+      required: true
+    }
+  },
+  computed: {
+    selectVal: {
+      get() {
+        return [...this.value]
+      },
+      set(val) {
+        this.$emit('input', [...val])
+      }
+    }
+  },
+  mounted() {
+    this.setSort()
+  },
+  methods: {
+    setSort() {
+      const el = this.$refs.dragSelect.$el.querySelectorAll('.el-select__tags > span')[0]
+      this.sortable = Sortable.create(el, {
+        ghostClass: 'sortable-ghost', // Class name for the drop placeholder,
+        setData: function(dataTransfer) {
+          dataTransfer.setData('Text', '')
+          // to avoid Firefox bug
+          // Detail see : https://github.com/RubaXa/Sortable/issues/1012
+        },
+        onEnd: evt => {
+          const targetRow = this.value.splice(evt.oldIndex, 1)[0]
+          this.value.splice(evt.newIndex, 0, targetRow)
+        }
+      })
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.drag-select {
+  ::v-deep {
+    .sortable-ghost {
+      opacity: .8;
+      color: #fff !important;
+      background: #42b983 !important;
+    }
+
+    .el-tag {
+      cursor: pointer;
+    }
+  }
+}
+</style>
diff --git a/frontend/src/components/Dropzone/index.vue b/frontend/src/components/Dropzone/index.vue
new file mode 100644
index 0000000..bad9eb9
--- /dev/null
+++ b/frontend/src/components/Dropzone/index.vue
@@ -0,0 +1,297 @@
+<template>
+  <div :id="id" :ref="id" :action="url" class="dropzone">
+    <input type="file" name="file">
+  </div>
+</template>
+
+<script>
+import Dropzone from 'dropzone'
+import 'dropzone/dist/dropzone.css'
+// import { getToken } from 'api/qiniu';
+
+Dropzone.autoDiscover = false
+
+export default {
+  props: {
+    id: {
+      type: String,
+      required: true
+    },
+    url: {
+      type: String,
+      required: true
+    },
+    clickable: {
+      type: Boolean,
+      default: true
+    },
+    defaultMsg: {
+      type: String,
+      default: '上传图片'
+    },
+    acceptedFiles: {
+      type: String,
+      default: ''
+    },
+    thumbnailHeight: {
+      type: Number,
+      default: 200
+    },
+    thumbnailWidth: {
+      type: Number,
+      default: 200
+    },
+    showRemoveLink: {
+      type: Boolean,
+      default: true
+    },
+    maxFilesize: {
+      type: Number,
+      default: 2
+    },
+    maxFiles: {
+      type: Number,
+      default: 3
+    },
+    autoProcessQueue: {
+      type: Boolean,
+      default: true
+    },
+    useCustomDropzoneOptions: {
+      type: Boolean,
+      default: false
+    },
+    defaultImg: {
+      default: '',
+      type: [String, Array]
+    },
+    couldPaste: {
+      type: Boolean,
+      default: false
+    }
+  },
+  data() {
+    return {
+      dropzone: '',
+      initOnce: true
+    }
+  },
+  watch: {
+    defaultImg(val) {
+      if (val.length === 0) {
+        this.initOnce = false
+        return
+      }
+      if (!this.initOnce) return
+      this.initImages(val)
+      this.initOnce = false
+    }
+  },
+  mounted() {
+    const element = document.getElementById(this.id)
+    const vm = this
+    this.dropzone = new Dropzone(element, {
+      clickable: this.clickable,
+      thumbnailWidth: this.thumbnailWidth,
+      thumbnailHeight: this.thumbnailHeight,
+      maxFiles: this.maxFiles,
+      maxFilesize: this.maxFilesize,
+      dictRemoveFile: 'Remove',
+      addRemoveLinks: this.showRemoveLink,
+      acceptedFiles: this.acceptedFiles,
+      autoProcessQueue: this.autoProcessQueue,
+      dictDefaultMessage: '<i style="margin-top: 3em;display: inline-block" class="material-icons">' + this.defaultMsg + '</i><br>Drop files here to upload',
+      dictMaxFilesExceeded: '只能一个图',
+      previewTemplate: '<div class="dz-preview dz-file-preview">  <div class="dz-image" style="width:' + this.thumbnailWidth + 'px;height:' + this.thumbnailHeight + 'px" ><img style="width:' + this.thumbnailWidth + 'px;height:' + this.thumbnailHeight + 'px" data-dz-thumbnail /></div>  <div class="dz-details"><div class="dz-size"><span data-dz-size></span></div> <div class="dz-progress"><span class="dz-upload" data-dz-uploadprogress></span></div>  <div class="dz-error-message"><span data-dz-errormessage></span></div>  <div class="dz-success-mark"> <i class="material-icons">done</i> </div>  <div class="dz-error-mark"><i class="material-icons">error</i></div></div>',
+      init() {
+        const val = vm.defaultImg
+        if (!val) return
+        if (Array.isArray(val)) {
+          if (val.length === 0) return
+          val.map((v, i) => {
+            const mockFile = { name: 'name' + i, size: 12345, url: v }
+            this.options.addedfile.call(this, mockFile)
+            this.options.thumbnail.call(this, mockFile, v)
+            mockFile.previewElement.classList.add('dz-success')
+            mockFile.previewElement.classList.add('dz-complete')
+            vm.initOnce = false
+            return true
+          })
+        } else {
+          const mockFile = { name: 'name', size: 12345, url: val }
+          this.options.addedfile.call(this, mockFile)
+          this.options.thumbnail.call(this, mockFile, val)
+          mockFile.previewElement.classList.add('dz-success')
+          mockFile.previewElement.classList.add('dz-complete')
+          vm.initOnce = false
+        }
+      },
+      accept: (file, done) => {
+        /* 七牛*/
+        // const token = this.$store.getters.token;
+        // getToken(token).then(response => {
+        //   file.token = response.data.qiniu_token;
+        //   file.key = response.data.qiniu_key;
+        //   file.url = response.data.qiniu_url;
+        //   done();
+        // })
+        done()
+      },
+      sending: (file, xhr, formData) => {
+        // formData.append('token', file.token);
+        // formData.append('key', file.key);
+        vm.initOnce = false
+      }
+    })
+
+    if (this.couldPaste) {
+      document.addEventListener('paste', this.pasteImg)
+    }
+
+    this.dropzone.on('success', file => {
+      vm.$emit('dropzone-success', file, vm.dropzone.element)
+    })
+    this.dropzone.on('addedfile', file => {
+      vm.$emit('dropzone-fileAdded', file)
+    })
+    this.dropzone.on('removedfile', file => {
+      vm.$emit('dropzone-removedFile', file)
+    })
+    this.dropzone.on('error', (file, error, xhr) => {
+      vm.$emit('dropzone-error', file, error, xhr)
+    })
+    this.dropzone.on('successmultiple', (file, error, xhr) => {
+      vm.$emit('dropzone-successmultiple', file, error, xhr)
+    })
+  },
+  destroyed() {
+    document.removeEventListener('paste', this.pasteImg)
+    this.dropzone.destroy()
+  },
+  methods: {
+    removeAllFiles() {
+      this.dropzone.removeAllFiles(true)
+    },
+    processQueue() {
+      this.dropzone.processQueue()
+    },
+    pasteImg(event) {
+      const items = (event.clipboardData || event.originalEvent.clipboardData).items
+      if (items[0].kind === 'file') {
+        this.dropzone.addFile(items[0].getAsFile())
+      }
+    },
+    initImages(val) {
+      if (!val) return
+      if (Array.isArray(val)) {
+        val.map((v, i) => {
+          const mockFile = { name: 'name' + i, size: 12345, url: v }
+          this.dropzone.options.addedfile.call(this.dropzone, mockFile)
+          this.dropzone.options.thumbnail.call(this.dropzone, mockFile, v)
+          mockFile.previewElement.classList.add('dz-success')
+          mockFile.previewElement.classList.add('dz-complete')
+          return true
+        })
+      } else {
+        const mockFile = { name: 'name', size: 12345, url: val }
+        this.dropzone.options.addedfile.call(this.dropzone, mockFile)
+        this.dropzone.options.thumbnail.call(this.dropzone, mockFile, val)
+        mockFile.previewElement.classList.add('dz-success')
+        mockFile.previewElement.classList.add('dz-complete')
+      }
+    }
+
+  }
+}
+</script>
+
+<style scoped>
+    .dropzone {
+        border: 2px solid #E5E5E5;
+        font-family: 'Roboto', sans-serif;
+        color: #777;
+        transition: background-color .2s linear;
+        padding: 5px;
+    }
+
+    .dropzone:hover {
+        background-color: #F6F6F6;
+    }
+
+    i {
+        color: #CCC;
+    }
+
+    .dropzone .dz-image img {
+        width: 100%;
+        height: 100%;
+    }
+
+    .dropzone input[name='file'] {
+        display: none;
+    }
+
+    .dropzone .dz-preview .dz-image {
+        border-radius: 0px;
+    }
+
+    .dropzone .dz-preview:hover .dz-image img {
+        transform: none;
+        filter: none;
+        width: 100%;
+        height: 100%;
+    }
+
+    .dropzone .dz-preview .dz-details {
+        bottom: 0px;
+        top: 0px;
+        color: white;
+        background-color: rgba(33, 150, 243, 0.8);
+        transition: opacity .2s linear;
+        text-align: left;
+    }
+
+    .dropzone .dz-preview .dz-details .dz-filename span, .dropzone .dz-preview .dz-details .dz-size span {
+        background-color: transparent;
+    }
+
+    .dropzone .dz-preview .dz-details .dz-filename:not(:hover) span {
+        border: none;
+    }
+
+    .dropzone .dz-preview .dz-details .dz-filename:hover span {
+        background-color: transparent;
+        border: none;
+    }
+
+    .dropzone .dz-preview .dz-remove {
+        position: absolute;
+        z-index: 30;
+        color: white;
+        margin-left: 15px;
+        padding: 10px;
+        top: inherit;
+        bottom: 15px;
+        border: 2px white solid;
+        text-decoration: none;
+        text-transform: uppercase;
+        font-size: 0.8rem;
+        font-weight: 800;
+        letter-spacing: 1.1px;
+        opacity: 0;
+    }
+
+    .dropzone .dz-preview:hover .dz-remove {
+        opacity: 1;
+    }
+
+    .dropzone .dz-preview .dz-success-mark, .dropzone .dz-preview .dz-error-mark {
+        margin-left: -40px;
+        margin-top: -50px;
+    }
+
+    .dropzone .dz-preview .dz-success-mark i, .dropzone .dz-preview .dz-error-mark i {
+        color: white;
+        font-size: 5rem;
+    }
+</style>
diff --git a/frontend/src/components/ErrorLog/index.vue b/frontend/src/components/ErrorLog/index.vue
new file mode 100644
index 0000000..6119c03
--- /dev/null
+++ b/frontend/src/components/ErrorLog/index.vue
@@ -0,0 +1,78 @@
+<template>
+  <div v-if="errorLogs.length>0">
+    <el-badge :is-dot="true" style="line-height: 25px;margin-top: -5px;" @click.native="dialogTableVisible=true">
+      <el-button style="padding: 8px 10px;" size="small" type="danger">
+        <svg-icon icon-class="bug" />
+      </el-button>
+    </el-badge>
+
+    <el-dialog :visible.sync="dialogTableVisible" width="80%" append-to-body>
+      <div slot="title">
+        <span style="padding-right: 10px;">Error Log</span>
+        <el-button size="mini" type="primary" icon="el-icon-delete" @click="clearAll">Clear All</el-button>
+      </div>
+      <el-table :data="errorLogs" border>
+        <el-table-column label="Message">
+          <template slot-scope="{row}">
+            <div>
+              <span class="message-title">Msg:</span>
+              <el-tag type="danger">
+                {{ row.err.message }}
+              </el-tag>
+            </div>
+            <br>
+            <div>
+              <span class="message-title" style="padding-right: 10px;">Info: </span>
+              <el-tag type="warning">
+                {{ row.vm.$vnode.tag }} error in {{ row.info }}
+              </el-tag>
+            </div>
+            <br>
+            <div>
+              <span class="message-title" style="padding-right: 16px;">Url: </span>
+              <el-tag type="success">
+                {{ row.url }}
+              </el-tag>
+            </div>
+          </template>
+        </el-table-column>
+        <el-table-column label="Stack">
+          <template slot-scope="scope">
+            {{ scope.row.err.stack }}
+          </template>
+        </el-table-column>
+      </el-table>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'ErrorLog',
+  data() {
+    return {
+      dialogTableVisible: false
+    }
+  },
+  computed: {
+    errorLogs() {
+      return this.$store.getters.errorLogs
+    }
+  },
+  methods: {
+    clearAll() {
+      this.dialogTableVisible = false
+      this.$store.dispatch('errorLog/clearErrorLog')
+    }
+  }
+}
+</script>
+
+<style scoped>
+.message-title {
+  font-size: 16px;
+  color: #333;
+  font-weight: bold;
+  padding-right: 8px;
+}
+</style>
diff --git a/frontend/src/components/GithubCorner/index.vue b/frontend/src/components/GithubCorner/index.vue
new file mode 100644
index 0000000..970faaf
--- /dev/null
+++ b/frontend/src/components/GithubCorner/index.vue
@@ -0,0 +1,54 @@
+<template>
+  <a href="https://github.com/PanJiaChen/vue-element-admin" target="_blank" class="github-corner" aria-label="View source on Github">
+    <svg
+      width="80"
+      height="80"
+      viewBox="0 0 250 250"
+      style="fill:#40c9c6; color:#fff;"
+      aria-hidden="true"
+    >
+      <path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z" />
+      <path
+        d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2"
+        fill="currentColor"
+        style="transform-origin: 130px 106px;"
+        class="octo-arm"
+      />
+      <path
+        d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z"
+        fill="currentColor"
+        class="octo-body"
+      />
+    </svg>
+  </a>
+</template>
+
+<style scoped>
+.github-corner:hover .octo-arm {
+  animation: octocat-wave 560ms ease-in-out
+}
+
+@keyframes octocat-wave {
+  0%,
+  100% {
+    transform: rotate(0)
+  }
+  20%,
+  60% {
+    transform: rotate(-25deg)
+  }
+  40%,
+  80% {
+    transform: rotate(10deg)
+  }
+}
+
+@media (max-width:500px) {
+  .github-corner:hover .octo-arm {
+    animation: none
+  }
+  .github-corner .octo-arm {
+    animation: octocat-wave 560ms ease-in-out
+  }
+}
+</style>
diff --git a/frontend/src/components/Hamburger/index.vue b/frontend/src/components/Hamburger/index.vue
new file mode 100644
index 0000000..368b002
--- /dev/null
+++ b/frontend/src/components/Hamburger/index.vue
@@ -0,0 +1,44 @@
+<template>
+  <div style="padding: 0 15px;" @click="toggleClick">
+    <svg
+      :class="{'is-active':isActive}"
+      class="hamburger"
+      viewBox="0 0 1024 1024"
+      xmlns="http://www.w3.org/2000/svg"
+      width="64"
+      height="64"
+    >
+      <path d="M408 442h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm-8 204c0 4.4 3.6 8 8 8h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56zm504-486H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 632H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM142.4 642.1L298.7 519a8.84 8.84 0 0 0 0-13.9L142.4 381.9c-5.8-4.6-14.4-.5-14.4 6.9v246.3a8.9 8.9 0 0 0 14.4 7z" />
+    </svg>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'Hamburger',
+  props: {
+    isActive: {
+      type: Boolean,
+      default: false
+    }
+  },
+  methods: {
+    toggleClick() {
+      this.$emit('toggleClick')
+    }
+  }
+}
+</script>
+
+<style scoped>
+.hamburger {
+  display: inline-block;
+  vertical-align: middle;
+  width: 20px;
+  height: 20px;
+}
+
+.hamburger.is-active {
+  transform: rotate(180deg);
+}
+</style>
diff --git a/frontend/src/components/HeaderSearch/index.vue b/frontend/src/components/HeaderSearch/index.vue
new file mode 100644
index 0000000..6026ebb
--- /dev/null
+++ b/frontend/src/components/HeaderSearch/index.vue
@@ -0,0 +1,180 @@
+<template>
+  <div :class="{'show':show}" class="header-search">
+    <svg-icon class-name="search-icon" icon-class="search" @click.stop="click" />
+    <el-select
+      ref="headerSearchSelect"
+      v-model="search"
+      :remote-method="querySearch"
+      filterable
+      default-first-option
+      remote
+      placeholder="Search"
+      class="header-search-select"
+      @change="change"
+    >
+      <el-option v-for="item in options" :key="item.path" :value="item" :label="item.title.join(' > ')" />
+    </el-select>
+  </div>
+</template>
+
+<script>
+// fuse is a lightweight fuzzy-search module
+// make search results more in line with expectations
+import Fuse from 'fuse.js'
+import path from 'path'
+
+export default {
+  name: 'HeaderSearch',
+  data() {
+    return {
+      search: '',
+      options: [],
+      searchPool: [],
+      show: false,
+      fuse: undefined
+    }
+  },
+  computed: {
+    routes() {
+      return this.$store.getters.permission_routes
+    }
+  },
+  watch: {
+    routes() {
+      this.searchPool = this.generateRoutes(this.routes)
+    },
+    searchPool(list) {
+      this.initFuse(list)
+    },
+    show(value) {
+      if (value) {
+        document.body.addEventListener('click', this.close)
+      } else {
+        document.body.removeEventListener('click', this.close)
+      }
+    }
+  },
+  mounted() {
+    this.searchPool = this.generateRoutes(this.routes)
+  },
+  methods: {
+    click() {
+      this.show = !this.show
+      if (this.show) {
+        this.$refs.headerSearchSelect && this.$refs.headerSearchSelect.focus()
+      }
+    },
+    close() {
+      this.$refs.headerSearchSelect && this.$refs.headerSearchSelect.blur()
+      this.options = []
+      this.show = false
+    },
+    change(val) {
+      this.$router.push(val.path)
+      this.search = ''
+      this.options = []
+      this.$nextTick(() => {
+        this.show = false
+      })
+    },
+    initFuse(list) {
+      this.fuse = new Fuse(list, {
+        shouldSort: true,
+        threshold: 0.4,
+        location: 0,
+        distance: 100,
+        maxPatternLength: 32,
+        minMatchCharLength: 1,
+        keys: [{
+          name: 'title',
+          weight: 0.7
+        }, {
+          name: 'path',
+          weight: 0.3
+        }]
+      })
+    },
+    // Filter out the routes that can be displayed in the sidebar
+    // And generate the internationalized title
+    generateRoutes(routes, basePath = '/', prefixTitle = []) {
+      let res = []
+
+      for (const router of routes) {
+        // skip hidden router
+        if (router.hidden) { continue }
+
+        const data = {
+          path: path.resolve(basePath, router.path),
+          title: [...prefixTitle]
+        }
+
+        if (router.meta && router.meta.title) {
+          data.title = [...data.title, router.meta.title]
+
+          if (router.redirect !== 'noRedirect') {
+            // only push the routes with title
+            // special case: need to exclude parent router without redirect
+            res.push(data)
+          }
+        }
+
+        // recursive child routes
+        if (router.children) {
+          const tempRoutes = this.generateRoutes(router.children, data.path, data.title)
+          if (tempRoutes.length >= 1) {
+            res = [...res, ...tempRoutes]
+          }
+        }
+      }
+      return res
+    },
+    querySearch(query) {
+      if (query !== '') {
+        this.options = this.fuse.search(query)
+      } else {
+        this.options = []
+      }
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.header-search {
+  font-size: 0 !important;
+
+  .search-icon {
+    cursor: pointer;
+    font-size: 18px;
+    vertical-align: middle;
+  }
+
+  .header-search-select {
+    font-size: 18px;
+    transition: width 0.2s;
+    width: 0;
+    overflow: hidden;
+    background: transparent;
+    border-radius: 0;
+    display: inline-block;
+    vertical-align: middle;
+
+    ::v-deep .el-input__inner {
+      border-radius: 0;
+      border: 0;
+      padding-left: 0;
+      padding-right: 0;
+      box-shadow: none !important;
+      border-bottom: 1px solid #d9d9d9;
+      vertical-align: middle;
+    }
+  }
+
+  &.show {
+    .header-search-select {
+      width: 210px;
+      margin-left: 10px;
+    }
+  }
+}
+</style>
diff --git a/frontend/src/components/ImageCropper/index.vue b/frontend/src/components/ImageCropper/index.vue
new file mode 100644
index 0000000..65a4262
--- /dev/null
+++ b/frontend/src/components/ImageCropper/index.vue
@@ -0,0 +1,1779 @@
+<template>
+  <div v-show="value" class="vue-image-crop-upload">
+    <div class="vicp-wrap">
+      <div class="vicp-close" @click="off">
+        <i class="vicp-icon4" />
+      </div>
+
+      <div v-show="step == 1" class="vicp-step1">
+        <div
+          class="vicp-drop-area"
+          @dragleave="preventDefault"
+          @dragover="preventDefault"
+          @dragenter="preventDefault"
+          @click="handleClick"
+          @drop="handleChange"
+        >
+          <i v-show="loading != 1" class="vicp-icon1">
+            <i class="vicp-icon1-arrow" />
+            <i class="vicp-icon1-body" />
+            <i class="vicp-icon1-bottom" />
+          </i>
+          <span v-show="loading !== 1" class="vicp-hint">{{ lang.hint }}</span>
+          <span v-show="!isSupported" class="vicp-no-supported-hint">{{ lang.noSupported }}</span>
+          <input v-show="false" v-if="step == 1" ref="fileinput" type="file" @change="handleChange">
+        </div>
+        <div v-show="hasError" class="vicp-error">
+          <i class="vicp-icon2" />
+          {{ errorMsg }}
+        </div>
+        <div class="vicp-operate">
+          <a @click="off" @mousedown="ripple">{{ lang.btn.off }}</a>
+        </div>
+      </div>
+
+      <div v-if="step == 2" class="vicp-step2">
+        <div class="vicp-crop">
+          <div v-show="true" class="vicp-crop-left">
+            <div class="vicp-img-container">
+              <img
+                ref="img"
+                :src="sourceImgUrl"
+                :style="sourceImgStyle"
+                class="vicp-img"
+                draggable="false"
+                @drag="preventDefault"
+                @dragstart="preventDefault"
+                @dragend="preventDefault"
+                @dragleave="preventDefault"
+                @dragover="preventDefault"
+                @dragenter="preventDefault"
+                @drop="preventDefault"
+                @touchstart="imgStartMove"
+                @touchmove="imgMove"
+                @touchend="createImg"
+                @touchcancel="createImg"
+                @mousedown="imgStartMove"
+                @mousemove="imgMove"
+                @mouseup="createImg"
+                @mouseout="createImg"
+              >
+              <div :style="sourceImgShadeStyle" class="vicp-img-shade vicp-img-shade-1" />
+              <div :style="sourceImgShadeStyle" class="vicp-img-shade vicp-img-shade-2" />
+            </div>
+
+            <div class="vicp-range">
+              <input
+                :value="scale.range"
+                type="range"
+                step="1"
+                min="0"
+                max="100"
+                @input="zoomChange"
+              >
+              <i
+                class="vicp-icon5"
+                @mousedown="startZoomSub"
+                @mouseout="endZoomSub"
+                @mouseup="endZoomSub"
+              />
+              <i
+                class="vicp-icon6"
+                @mousedown="startZoomAdd"
+                @mouseout="endZoomAdd"
+                @mouseup="endZoomAdd"
+              />
+            </div>
+
+            <div v-if="!noRotate" class="vicp-rotate">
+              <i @mousedown="startRotateLeft" @mouseout="endRotate" @mouseup="endRotate">↺</i>
+              <i @mousedown="startRotateRight" @mouseout="endRotate" @mouseup="endRotate">↻</i>
+            </div>
+          </div>
+          <div v-show="true" class="vicp-crop-right">
+            <div class="vicp-preview">
+              <div v-if="!noSquare" class="vicp-preview-item">
+                <img :src="createImgUrl" :style="previewStyle">
+                <span>{{ lang.preview }}</span>
+              </div>
+              <div v-if="!noCircle" class="vicp-preview-item vicp-preview-item-circle">
+                <img :src="createImgUrl" :style="previewStyle">
+                <span>{{ lang.preview }}</span>
+              </div>
+            </div>
+          </div>
+        </div>
+        <div class="vicp-operate">
+          <a @click="setStep(1)" @mousedown="ripple">{{ lang.btn.back }}</a>
+          <a class="vicp-operate-btn" @click="prepareUpload" @mousedown="ripple">{{ lang.btn.save }}</a>
+        </div>
+      </div>
+
+      <div v-if="step == 3" class="vicp-step3">
+        <div class="vicp-upload">
+          <span v-show="loading === 1" class="vicp-loading">{{ lang.loading }}</span>
+          <div class="vicp-progress-wrap">
+            <span v-show="loading === 1" :style="progressStyle" class="vicp-progress" />
+          </div>
+          <div v-show="hasError" class="vicp-error">
+            <i class="vicp-icon2" />
+            {{ errorMsg }}
+          </div>
+          <div v-show="loading === 2" class="vicp-success">
+            <i class="vicp-icon3" />
+            {{ lang.success }}
+          </div>
+        </div>
+        <div class="vicp-operate">
+          <a @click="setStep(2)" @mousedown="ripple">{{ lang.btn.back }}</a>
+          <a @click="off" @mousedown="ripple">{{ lang.btn.close }}</a>
+        </div>
+      </div>
+      <canvas v-show="false" ref="canvas" :width="width" :height="height" />
+    </div>
+  </div>
+</template>
+
+<script>
+'use strict'
+import request from '@/utils/request'
+import language from './utils/language.js'
+import mimes from './utils/mimes.js'
+import data2blob from './utils/data2blob.js'
+import effectRipple from './utils/effectRipple.js'
+export default {
+  props: {
+    // 域,上传文件name,触发事件会带上(如果一个页面多个图片上传控件,可以做区分
+    field: {
+      type: String,
+      default: 'avatar'
+    },
+    // 原名key,类似于id,触发事件会带上(如果一个页面多个图片上传控件,可以做区分
+    ki: {
+      type: Number,
+      default: 0
+    },
+    // 显示该控件与否
+    value: {
+      type: Boolean,
+      default: true
+    },
+    // 上传地址
+    url: {
+      type: String,
+      default: ''
+    },
+    // 其他要上传文件附带的数据,对象格式
+    params: {
+      type: Object,
+      default: null
+    },
+    // Add custom headers
+    headers: {
+      type: Object,
+      default: null
+    },
+    // 剪裁图片的宽
+    width: {
+      type: Number,
+      default: 200
+    },
+    // 剪裁图片的高
+    height: {
+      type: Number,
+      default: 200
+    },
+    // 不显示旋转功能
+    noRotate: {
+      type: Boolean,
+      default: true
+    },
+    // 不预览圆形图片
+    noCircle: {
+      type: Boolean,
+      default: false
+    },
+    // 不预览方形图片
+    noSquare: {
+      type: Boolean,
+      default: false
+    },
+    // 单文件大小限制
+    maxSize: {
+      type: Number,
+      default: 10240
+    },
+    // 语言类型
+    langType: {
+      type: String,
+      default: 'zh'
+    },
+    // 语言包
+    langExt: {
+      type: Object,
+      default: null
+    },
+    // 图片上传格式
+    imgFormat: {
+      type: String,
+      default: 'png'
+    },
+    // 是否支持跨域
+    withCredentials: {
+      type: Boolean,
+      default: false
+    }
+  },
+  data() {
+    const { imgFormat, langType, langExt, width, height } = this
+    let isSupported = true
+    const allowImgFormat = ['jpg', 'png']
+    const tempImgFormat =
+      allowImgFormat.indexOf(imgFormat) === -1 ? 'jpg' : imgFormat
+    const lang = language[langType] ? language[langType] : language['en']
+    const mime = mimes[tempImgFormat]
+    // 规范图片格式
+    this.imgFormat = tempImgFormat
+    if (langExt) {
+      Object.assign(lang, langExt)
+    }
+    if (typeof FormData !== 'function') {
+      isSupported = false
+    }
+    return {
+      // 图片的mime
+      mime,
+      // 语言包
+      lang,
+      // 浏览器是否支持该控件
+      isSupported,
+      // 浏览器是否支持触屏事件
+      // eslint-disable-next-line no-prototype-builtins
+      isSupportTouch: document.hasOwnProperty('ontouchstart'),
+      // 步骤
+      step: 1, // 1选择文件 2剪裁 3上传
+      // 上传状态及进度
+      loading: 0, // 0未开始 1正在 2成功 3错误
+      progress: 0,
+      // 是否有错误及错误信息
+      hasError: false,
+      errorMsg: '',
+      // 需求图宽高比
+      ratio: width / height,
+      // 原图地址、生成图片地址
+      sourceImg: null,
+      sourceImgUrl: '',
+      createImgUrl: '',
+      // 原图片拖动事件初始值
+      sourceImgMouseDown: {
+        on: false,
+        mX: 0, // 鼠标按下的坐标
+        mY: 0,
+        x: 0, // scale原图坐标
+        y: 0
+      },
+      // 生成图片预览的容器大小
+      previewContainer: {
+        width: 100,
+        height: 100
+      },
+      // 原图容器宽高
+      sourceImgContainer: {
+        // sic
+        width: 240,
+        height: 184 // 如果生成图比例与此一致会出现bug,先改成特殊的格式吧,哈哈哈
+      },
+      // 原图展示属性
+      scale: {
+        zoomAddOn: false, // 按钮缩放事件开启
+        zoomSubOn: false, // 按钮缩放事件开启
+        range: 1, // 最大100
+        rotateLeft: false, // 按钮向左旋转事件开启
+        rotateRight: false, // 按钮向右旋转事件开启
+        degree: 0, // 旋转度数
+        x: 0,
+        y: 0,
+        width: 0,
+        height: 0,
+        maxWidth: 0,
+        maxHeight: 0,
+        minWidth: 0, // 最宽
+        minHeight: 0,
+        naturalWidth: 0, // 原宽
+        naturalHeight: 0
+      }
+    }
+  },
+  computed: {
+    // 进度条样式
+    progressStyle() {
+      const { progress } = this
+      return {
+        width: progress + '%'
+      }
+    },
+    // 原图样式
+    sourceImgStyle() {
+      const { scale, sourceImgMasking } = this
+      const top = scale.y + sourceImgMasking.y + 'px'
+      const left = scale.x + sourceImgMasking.x + 'px'
+      return {
+        top,
+        left,
+        width: scale.width + 'px',
+        height: scale.height + 'px',
+        transform: 'rotate(' + scale.degree + 'deg)', // 旋转时 左侧原始图旋转样式
+        '-ms-transform': 'rotate(' + scale.degree + 'deg)', // 兼容IE9
+        '-moz-transform': 'rotate(' + scale.degree + 'deg)', // 兼容FireFox
+        '-webkit-transform': 'rotate(' + scale.degree + 'deg)', // 兼容Safari 和 chrome
+        '-o-transform': 'rotate(' + scale.degree + 'deg)' // 兼容 Opera
+      }
+    },
+    // 原图蒙版属性
+    sourceImgMasking() {
+      const { width, height, ratio, sourceImgContainer } = this
+      const sic = sourceImgContainer
+      const sicRatio = sic.width / sic.height // 原图容器宽高比
+      let x = 0
+      let y = 0
+      let w = sic.width
+      let h = sic.height
+      let scale = 1
+      if (ratio < sicRatio) {
+        scale = sic.height / height
+        w = sic.height * ratio
+        x = (sic.width - w) / 2
+      }
+      if (ratio > sicRatio) {
+        scale = sic.width / width
+        h = sic.width / ratio
+        y = (sic.height - h) / 2
+      }
+      return {
+        scale, // 蒙版相对需求宽高的缩放
+        x,
+        y,
+        width: w,
+        height: h
+      }
+    },
+    // 原图遮罩样式
+    sourceImgShadeStyle() {
+      const { sourceImgMasking, sourceImgContainer } = this
+      const sic = sourceImgContainer
+      const sim = sourceImgMasking
+      const w =
+        sim.width === sic.width ? sim.width : (sic.width - sim.width) / 2
+      const h =
+        sim.height === sic.height ? sim.height : (sic.height - sim.height) / 2
+      return {
+        width: w + 'px',
+        height: h + 'px'
+      }
+    },
+    previewStyle() {
+      const { ratio, previewContainer } = this
+      const pc = previewContainer
+      let w = pc.width
+      let h = pc.height
+      const pcRatio = w / h
+      if (ratio < pcRatio) {
+        w = pc.height * ratio
+      }
+      if (ratio > pcRatio) {
+        h = pc.width / ratio
+      }
+      return {
+        width: w + 'px',
+        height: h + 'px'
+      }
+    }
+  },
+  watch: {
+    value(newValue) {
+      if (newValue && this.loading !== 1) {
+        this.reset()
+      }
+    }
+  },
+  created() {
+    // 绑定按键esc隐藏此插件事件
+    document.addEventListener('keyup', this.closeHandler)
+  },
+  destroyed() {
+    document.removeEventListener('keyup', this.closeHandler)
+  },
+  methods: {
+    // 点击波纹效果
+    ripple(e) {
+      effectRipple(e)
+    },
+    // 关闭控件
+    off() {
+      setTimeout(() => {
+        this.$emit('input', false)
+        this.$emit('close')
+        if (this.step === 3 && this.loading === 2) {
+          this.setStep(1)
+        }
+      }, 200)
+    },
+    // 设置步骤
+    setStep(no) {
+      // 延时是为了显示动画效果呢,哈哈哈
+      setTimeout(() => {
+        this.step = no
+      }, 200)
+    },
+    /* 图片选择区域函数绑定
+     ---------------------------------------------------------------*/
+    preventDefault(e) {
+      e.preventDefault()
+      return false
+    },
+    handleClick(e) {
+      if (this.loading !== 1) {
+        if (e.target !== this.$refs.fileinput) {
+          e.preventDefault()
+          if (document.activeElement !== this.$refs) {
+            this.$refs.fileinput.click()
+          }
+        }
+      }
+    },
+    handleChange(e) {
+      e.preventDefault()
+      if (this.loading !== 1) {
+        const files = e.target.files || e.dataTransfer.files
+        this.reset()
+        if (this.checkFile(files[0])) {
+          this.setSourceImg(files[0])
+        }
+      }
+    },
+    /* ---------------------------------------------------------------*/
+    // 检测选择的文件是否合适
+    checkFile(file) {
+      const { lang, maxSize } = this
+      // 仅限图片
+      if (file.type.indexOf('image') === -1) {
+        this.hasError = true
+        this.errorMsg = lang.error.onlyImg
+        return false
+      }
+      // 超出大小
+      if (file.size / 1024 > maxSize) {
+        this.hasError = true
+        this.errorMsg = lang.error.outOfSize + maxSize + 'kb'
+        return false
+      }
+      return true
+    },
+    // 重置控件
+    reset() {
+      this.loading = 0
+      this.hasError = false
+      this.errorMsg = ''
+      this.progress = 0
+    },
+    // 设置图片源
+    setSourceImg(file) {
+      const fr = new FileReader()
+      fr.onload = e => {
+        this.sourceImgUrl = fr.result
+        this.startCrop()
+      }
+      fr.readAsDataURL(file)
+    },
+    // 剪裁前准备工作
+    startCrop() {
+      const {
+        width,
+        height,
+        ratio,
+        scale,
+        sourceImgUrl,
+        sourceImgMasking,
+        lang
+      } = this
+      const sim = sourceImgMasking
+      const img = new Image()
+      img.src = sourceImgUrl
+      img.onload = () => {
+        const nWidth = img.naturalWidth
+        const nHeight = img.naturalHeight
+        const nRatio = nWidth / nHeight
+        let w = sim.width
+        let h = sim.height
+        let x = 0
+        let y = 0
+        // 图片像素不达标
+        if (nWidth < width || nHeight < height) {
+          this.hasError = true
+          this.errorMsg = lang.error.lowestPx + width + '*' + height
+          return false
+        }
+        if (ratio > nRatio) {
+          h = w / nRatio
+          y = (sim.height - h) / 2
+        }
+        if (ratio < nRatio) {
+          w = h * nRatio
+          x = (sim.width - w) / 2
+        }
+        scale.range = 0
+        scale.x = x
+        scale.y = y
+        scale.width = w
+        scale.height = h
+        scale.degree = 0
+        scale.minWidth = w
+        scale.minHeight = h
+        scale.maxWidth = nWidth * sim.scale
+        scale.maxHeight = nHeight * sim.scale
+        scale.naturalWidth = nWidth
+        scale.naturalHeight = nHeight
+        this.sourceImg = img
+        this.createImg()
+        this.setStep(2)
+      }
+    },
+    // 鼠标按下图片准备移动
+    imgStartMove(e) {
+      e.preventDefault()
+      // 支持触摸事件,则鼠标事件无效
+      if (this.isSupportTouch && !e.targetTouches) {
+        return false
+      }
+      const et = e.targetTouches ? e.targetTouches[0] : e
+      const { sourceImgMouseDown, scale } = this
+      const simd = sourceImgMouseDown
+      simd.mX = et.screenX
+      simd.mY = et.screenY
+      simd.x = scale.x
+      simd.y = scale.y
+      simd.on = true
+    },
+    // 鼠标按下状态下移动,图片移动
+    imgMove(e) {
+      e.preventDefault()
+      // 支持触摸事件,则鼠标事件无效
+      if (this.isSupportTouch && !e.targetTouches) {
+        return false
+      }
+      const et = e.targetTouches ? e.targetTouches[0] : e
+      const {
+        sourceImgMouseDown: { on, mX, mY, x, y },
+        scale,
+        sourceImgMasking
+      } = this
+      const sim = sourceImgMasking
+      const nX = et.screenX
+      const nY = et.screenY
+      const dX = nX - mX
+      const dY = nY - mY
+      let rX = x + dX
+      let rY = y + dY
+      if (!on) return
+      if (rX > 0) {
+        rX = 0
+      }
+      if (rY > 0) {
+        rY = 0
+      }
+      if (rX < sim.width - scale.width) {
+        rX = sim.width - scale.width
+      }
+      if (rY < sim.height - scale.height) {
+        rY = sim.height - scale.height
+      }
+      scale.x = rX
+      scale.y = rY
+    },
+    // 按钮按下开始向右旋转
+    startRotateRight(e) {
+      const { scale } = this
+      scale.rotateRight = true
+      const rotate = () => {
+        if (scale.rotateRight) {
+          const degree = ++scale.degree
+          this.createImg(degree)
+          setTimeout(function() {
+            rotate()
+          }, 60)
+        }
+      }
+      rotate()
+    },
+    // 按钮按下开始向左旋转
+    startRotateLeft(e) {
+      const { scale } = this
+      scale.rotateLeft = true
+      const rotate = () => {
+        if (scale.rotateLeft) {
+          const degree = --scale.degree
+          this.createImg(degree)
+          setTimeout(function() {
+            rotate()
+          }, 60)
+        }
+      }
+      rotate()
+    },
+    // 停止旋转
+    endRotate() {
+      const { scale } = this
+      scale.rotateLeft = false
+      scale.rotateRight = false
+    },
+    // 按钮按下开始放大
+    startZoomAdd(e) {
+      const { scale } = this
+      scale.zoomAddOn = true
+      const zoom = () => {
+        if (scale.zoomAddOn) {
+          const range = scale.range >= 100 ? 100 : ++scale.range
+          this.zoomImg(range)
+          setTimeout(function() {
+            zoom()
+          }, 60)
+        }
+      }
+      zoom()
+    },
+    // 按钮松开或移开取消放大
+    endZoomAdd(e) {
+      this.scale.zoomAddOn = false
+    },
+    // 按钮按下开始缩小
+    startZoomSub(e) {
+      const { scale } = this
+      scale.zoomSubOn = true
+      const zoom = () => {
+        if (scale.zoomSubOn) {
+          const range = scale.range <= 0 ? 0 : --scale.range
+          this.zoomImg(range)
+          setTimeout(function() {
+            zoom()
+          }, 60)
+        }
+      }
+      zoom()
+    },
+    // 按钮松开或移开取消缩小
+    endZoomSub(e) {
+      const { scale } = this
+      scale.zoomSubOn = false
+    },
+    zoomChange(e) {
+      this.zoomImg(e.target.value)
+    },
+    // 缩放原图
+    zoomImg(newRange) {
+      const { sourceImgMasking, scale } = this
+      const {
+        maxWidth,
+        maxHeight,
+        minWidth,
+        minHeight,
+        width,
+        height,
+        x,
+        y
+      } = scale
+      const sim = sourceImgMasking
+      // 蒙版宽高
+      const sWidth = sim.width
+      const sHeight = sim.height
+      // 新宽高
+      const nWidth = minWidth + ((maxWidth - minWidth) * newRange) / 100
+      const nHeight = minHeight + ((maxHeight - minHeight) * newRange) / 100
+      // 新坐标(根据蒙版中心点缩放)
+      let nX = sWidth / 2 - (nWidth / width) * (sWidth / 2 - x)
+      let nY = sHeight / 2 - (nHeight / height) * (sHeight / 2 - y)
+      // 判断新坐标是否超过蒙版限制
+      if (nX > 0) {
+        nX = 0
+      }
+      if (nY > 0) {
+        nY = 0
+      }
+      if (nX < sWidth - nWidth) {
+        nX = sWidth - nWidth
+      }
+      if (nY < sHeight - nHeight) {
+        nY = sHeight - nHeight
+      }
+      // 赋值处理
+      scale.x = nX
+      scale.y = nY
+      scale.width = nWidth
+      scale.height = nHeight
+      scale.range = newRange
+      setTimeout(() => {
+        if (scale.range === newRange) {
+          this.createImg()
+        }
+      }, 300)
+    },
+    // 生成需求图片
+    createImg(e) {
+      const {
+        mime,
+        sourceImg,
+        scale: { x, y, width, height, degree },
+        sourceImgMasking: { scale }
+      } = this
+      const canvas = this.$refs.canvas
+      const ctx = canvas.getContext('2d')
+      if (e) {
+        // 取消鼠标按下移动状态
+        this.sourceImgMouseDown.on = false
+      }
+      canvas.width = this.width
+      canvas.height = this.height
+      ctx.clearRect(0, 0, this.width, this.height)
+      // 将透明区域设置为白色底边
+      ctx.fillStyle = '#fff'
+      ctx.fillRect(0, 0, this.width, this.height)
+      ctx.translate(this.width * 0.5, this.height * 0.5)
+      ctx.rotate((Math.PI * degree) / 180)
+      ctx.translate(-this.width * 0.5, -this.height * 0.5)
+      ctx.drawImage(
+        sourceImg,
+        x / scale,
+        y / scale,
+        width / scale,
+        height / scale
+      )
+      this.createImgUrl = canvas.toDataURL(mime)
+    },
+    prepareUpload() {
+      const { url, createImgUrl, field, ki } = this
+      this.$emit('crop-success', createImgUrl, field, ki)
+      if (typeof url === 'string' && url) {
+        this.upload()
+      } else {
+        this.off()
+      }
+    },
+    // 上传图片
+    upload() {
+      const {
+        lang,
+        imgFormat,
+        mime,
+        url,
+        params,
+        field,
+        ki,
+        createImgUrl
+      } = this
+      const fmData = new FormData()
+      fmData.append(
+        field,
+        data2blob(createImgUrl, mime),
+        field + '.' + imgFormat
+      )
+      // 添加其他参数
+      if (typeof params === 'object' && params) {
+        Object.keys(params).forEach(k => {
+          fmData.append(k, params[k])
+        })
+      }
+      // 监听进度回调
+      // const uploadProgress = (event) => {
+      //   if (event.lengthComputable) {
+      //     this.progress = 100 * Math.round(event.loaded) / event.total
+      //   }
+      // }
+      // 上传文件
+      this.reset()
+      this.loading = 1
+      this.setStep(3)
+      request({
+        url,
+        method: 'post',
+        data: fmData
+      })
+        .then(resData => {
+          this.loading = 2
+          this.$emit('crop-upload-success', resData.data)
+        })
+        .catch(err => {
+          if (this.value) {
+            this.loading = 3
+            this.hasError = true
+            this.errorMsg = lang.fail
+            this.$emit('crop-upload-fail', err, field, ki)
+          }
+        })
+    },
+    closeHandler(e) {
+      if (this.value && (e.key === 'Escape' || e.keyCode === 27)) {
+        this.off()
+      }
+    }
+  }
+}
+</script>
+
+<style lang="scss">
+@charset "UTF-8";
+@-webkit-keyframes vicp_progress {
+  0% {
+    background-position-y: 0;
+  }
+  100% {
+    background-position-y: 40px;
+  }
+}
+@keyframes vicp_progress {
+  0% {
+    background-position-y: 0;
+  }
+  100% {
+    background-position-y: 40px;
+  }
+}
+@-webkit-keyframes vicp {
+  0% {
+    opacity: 0;
+    -webkit-transform: scale(0) translatey(-60px);
+    transform: scale(0) translatey(-60px);
+  }
+  100% {
+    opacity: 1;
+    -webkit-transform: scale(1) translatey(0);
+    transform: scale(1) translatey(0);
+  }
+}
+@keyframes vicp {
+  0% {
+    opacity: 0;
+    -webkit-transform: scale(0) translatey(-60px);
+    transform: scale(0) translatey(-60px);
+  }
+  100% {
+    opacity: 1;
+    -webkit-transform: scale(1) translatey(0);
+    transform: scale(1) translatey(0);
+  }
+}
+.vue-image-crop-upload {
+  position: fixed;
+  display: block;
+  -webkit-box-sizing: border-box;
+  box-sizing: border-box;
+  z-index: 10000;
+  top: 0;
+  bottom: 0;
+  left: 0;
+  right: 0;
+  width: 100%;
+  height: 100%;
+  background-color: rgba(0, 0, 0, 0.65);
+  -webkit-tap-highlight-color: transparent;
+  -moz-tap-highlight-color: transparent;
+}
+.vue-image-crop-upload .vicp-wrap {
+  -webkit-box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.23);
+  box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.23);
+  position: fixed;
+  display: block;
+  -webkit-box-sizing: border-box;
+  box-sizing: border-box;
+  z-index: 10000;
+  top: 0;
+  bottom: 0;
+  left: 0;
+  right: 0;
+  margin: auto;
+  width: 600px;
+  height: 330px;
+  padding: 25px;
+  background-color: #fff;
+  border-radius: 2px;
+  -webkit-animation: vicp 0.12s ease-in;
+  animation: vicp 0.12s ease-in;
+}
+.vue-image-crop-upload .vicp-wrap .vicp-close {
+  position: absolute;
+  right: -30px;
+  top: -30px;
+}
+.vue-image-crop-upload .vicp-wrap .vicp-close .vicp-icon4 {
+  position: relative;
+  display: block;
+  width: 30px;
+  height: 30px;
+  cursor: pointer;
+  -webkit-transition: -webkit-transform 0.18s;
+  transition: -webkit-transform 0.18s;
+  transition: transform 0.18s;
+  transition: transform 0.18s, -webkit-transform 0.18s;
+  -webkit-transform: rotate(0);
+  -ms-transform: rotate(0);
+  transform: rotate(0);
+}
+.vue-image-crop-upload .vicp-wrap .vicp-close .vicp-icon4::after,
+.vue-image-crop-upload .vicp-wrap .vicp-close .vicp-icon4::before {
+  -webkit-box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.23);
+  box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.23);
+  content: "";
+  position: absolute;
+  top: 12px;
+  left: 4px;
+  width: 20px;
+  height: 3px;
+  -webkit-transform: rotate(45deg);
+  -ms-transform: rotate(45deg);
+  transform: rotate(45deg);
+  background-color: #fff;
+}
+.vue-image-crop-upload .vicp-wrap .vicp-close .vicp-icon4::after {
+  -webkit-transform: rotate(-45deg);
+  -ms-transform: rotate(-45deg);
+  transform: rotate(-45deg);
+}
+.vue-image-crop-upload .vicp-wrap .vicp-close .vicp-icon4:hover {
+  -webkit-transform: rotate(90deg);
+  -ms-transform: rotate(90deg);
+  transform: rotate(90deg);
+}
+.vue-image-crop-upload .vicp-wrap .vicp-step1 .vicp-drop-area {
+  position: relative;
+  -webkit-box-sizing: border-box;
+  box-sizing: border-box;
+  padding: 35px;
+  height: 170px;
+  background-color: rgba(0, 0, 0, 0.03);
+  text-align: center;
+  border: 1px dashed rgba(0, 0, 0, 0.08);
+  overflow: hidden;
+}
+.vue-image-crop-upload .vicp-wrap .vicp-step1 .vicp-drop-area .vicp-icon1 {
+  display: block;
+  margin: 0 auto 6px;
+  width: 42px;
+  height: 42px;
+  overflow: hidden;
+}
+.vue-image-crop-upload
+  .vicp-wrap
+  .vicp-step1
+  .vicp-drop-area
+  .vicp-icon1
+  .vicp-icon1-arrow {
+  display: block;
+  margin: 0 auto;
+  width: 0;
+  height: 0;
+  border-bottom: 14.7px solid rgba(0, 0, 0, 0.3);
+  border-left: 14.7px solid transparent;
+  border-right: 14.7px solid transparent;
+}
+.vue-image-crop-upload
+  .vicp-wrap
+  .vicp-step1
+  .vicp-drop-area
+  .vicp-icon1
+  .vicp-icon1-body {
+  display: block;
+  width: 12.6px;
+  height: 14.7px;
+  margin: 0 auto;
+  background-color: rgba(0, 0, 0, 0.3);
+}
+.vue-image-crop-upload
+  .vicp-wrap
+  .vicp-step1
+  .vicp-drop-area
+  .vicp-icon1
+  .vicp-icon1-bottom {
+  -webkit-box-sizing: border-box;
+  box-sizing: border-box;
+  display: block;
+  height: 12.6px;
+  border: 6px solid rgba(0, 0, 0, 0.3);
+  border-top: none;
+}
+.vue-image-crop-upload .vicp-wrap .vicp-step1 .vicp-drop-area .vicp-hint {
+  display: block;
+  padding: 15px;
+  font-size: 14px;
+  color: #666;
+  line-height: 30px;
+}
+.vue-image-crop-upload
+  .vicp-wrap
+  .vicp-step1
+  .vicp-drop-area
+  .vicp-no-supported-hint {
+  display: block;
+  position: absolute;
+  top: 0;
+  left: 0;
+  padding: 30px;
+  width: 100%;
+  height: 60px;
+  line-height: 30px;
+  background-color: #eee;
+  text-align: center;
+  color: #666;
+  font-size: 14px;
+}
+.vue-image-crop-upload .vicp-wrap .vicp-step1 .vicp-drop-area:hover {
+  cursor: pointer;
+  border-color: rgba(0, 0, 0, 0.1);
+  background-color: rgba(0, 0, 0, 0.05);
+}
+.vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop {
+  overflow: hidden;
+}
+.vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left {
+  float: left;
+}
+.vue-image-crop-upload
+  .vicp-wrap
+  .vicp-step2
+  .vicp-crop
+  .vicp-crop-left
+  .vicp-img-container {
+  position: relative;
+  display: block;
+  width: 240px;
+  height: 180px;
+  background-color: #e5e5e0;
+  overflow: hidden;
+}
+.vue-image-crop-upload
+  .vicp-wrap
+  .vicp-step2
+  .vicp-crop
+  .vicp-crop-left
+  .vicp-img-container
+  .vicp-img {
+  position: absolute;
+  display: block;
+  cursor: move;
+  -webkit-user-select: none;
+  -moz-user-select: none;
+  -ms-user-select: none;
+  user-select: none;
+}
+.vue-image-crop-upload
+  .vicp-wrap
+  .vicp-step2
+  .vicp-crop
+  .vicp-crop-left
+  .vicp-img-container
+  .vicp-img-shade {
+  -webkit-box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.18);
+  box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.18);
+  position: absolute;
+  background-color: rgba(241, 242, 243, 0.8);
+}
+.vue-image-crop-upload
+  .vicp-wrap
+  .vicp-step2
+  .vicp-crop
+  .vicp-crop-left
+  .vicp-img-container
+  .vicp-img-shade.vicp-img-shade-1 {
+  top: 0;
+  left: 0;
+}
+.vue-image-crop-upload
+  .vicp-wrap
+  .vicp-step2
+  .vicp-crop
+  .vicp-crop-left
+  .vicp-img-container
+  .vicp-img-shade.vicp-img-shade-2 {
+  bottom: 0;
+  right: 0;
+}
+.vue-image-crop-upload
+  .vicp-wrap
+  .vicp-step2
+  .vicp-crop
+  .vicp-crop-left
+  .vicp-rotate {
+  position: relative;
+  width: 240px;
+  height: 18px;
+}
+.vue-image-crop-upload
+  .vicp-wrap
+  .vicp-step2
+  .vicp-crop
+  .vicp-crop-left
+  .vicp-rotate
+  i {
+  display: block;
+  width: 18px;
+  height: 18px;
+  border-radius: 100%;
+  line-height: 18px;
+  text-align: center;
+  font-size: 12px;
+  font-weight: bold;
+  background-color: rgba(0, 0, 0, 0.08);
+  color: #fff;
+  overflow: hidden;
+}
+.vue-image-crop-upload
+  .vicp-wrap
+  .vicp-step2
+  .vicp-crop
+  .vicp-crop-left
+  .vicp-rotate
+  i:hover {
+  -webkit-box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.12);
+  box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.12);
+  cursor: pointer;
+  background-color: rgba(0, 0, 0, 0.14);
+}
+.vue-image-crop-upload
+  .vicp-wrap
+  .vicp-step2
+  .vicp-crop
+  .vicp-crop-left
+  .vicp-rotate
+  i:first-child {
+  float: left;
+}
+.vue-image-crop-upload
+  .vicp-wrap
+  .vicp-step2
+  .vicp-crop
+  .vicp-crop-left
+  .vicp-rotate
+  i:last-child {
+  float: right;
+}
+.vue-image-crop-upload
+  .vicp-wrap
+  .vicp-step2
+  .vicp-crop
+  .vicp-crop-left
+  .vicp-range {
+  position: relative;
+  margin: 30px 0 10px 0;
+  width: 240px;
+  height: 18px;
+}
+.vue-image-crop-upload
+  .vicp-wrap
+  .vicp-step2
+  .vicp-crop
+  .vicp-crop-left
+  .vicp-range
+  .vicp-icon5,
+.vue-image-crop-upload
+  .vicp-wrap
+  .vicp-step2
+  .vicp-crop
+  .vicp-crop-left
+  .vicp-range
+  .vicp-icon6 {
+  position: absolute;
+  top: 0;
+  width: 18px;
+  height: 18px;
+  border-radius: 100%;
+  background-color: rgba(0, 0, 0, 0.08);
+}
+.vue-image-crop-upload
+  .vicp-wrap
+  .vicp-step2
+  .vicp-crop
+  .vicp-crop-left
+  .vicp-range
+  .vicp-icon5:hover,
+.vue-image-crop-upload
+  .vicp-wrap
+  .vicp-step2
+  .vicp-crop
+  .vicp-crop-left
+  .vicp-range
+  .vicp-icon6:hover {
+  -webkit-box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.12);
+  box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.12);
+  cursor: pointer;
+  background-color: rgba(0, 0, 0, 0.14);
+}
+.vue-image-crop-upload
+  .vicp-wrap
+  .vicp-step2
+  .vicp-crop
+  .vicp-crop-left
+  .vicp-range
+  .vicp-icon5 {
+  left: 0;
+}
+.vue-image-crop-upload
+  .vicp-wrap
+  .vicp-step2
+  .vicp-crop
+  .vicp-crop-left
+  .vicp-range
+  .vicp-icon5::before {
+  position: absolute;
+  content: "";
+  display: block;
+  left: 3px;
+  top: 8px;
+  width: 12px;
+  height: 2px;
+  background-color: #fff;
+}
+.vue-image-crop-upload
+  .vicp-wrap
+  .vicp-step2
+  .vicp-crop
+  .vicp-crop-left
+  .vicp-range
+  .vicp-icon6 {
+  right: 0;
+}
+.vue-image-crop-upload
+  .vicp-wrap
+  .vicp-step2
+  .vicp-crop
+  .vicp-crop-left
+  .vicp-range
+  .vicp-icon6::before {
+  position: absolute;
+  content: "";
+  display: block;
+  left: 3px;
+  top: 8px;
+  width: 12px;
+  height: 2px;
+  background-color: #fff;
+}
+.vue-image-crop-upload
+  .vicp-wrap
+  .vicp-step2
+  .vicp-crop
+  .vicp-crop-left
+  .vicp-range
+  .vicp-icon6::after {
+  position: absolute;
+  content: "";
+  display: block;
+  top: 3px;
+  left: 8px;
+  width: 2px;
+  height: 12px;
+  background-color: #fff;
+}
+.vue-image-crop-upload
+  .vicp-wrap
+  .vicp-step2
+  .vicp-crop
+  .vicp-crop-left
+  .vicp-range
+  input[type="range"] {
+  display: block;
+  padding-top: 5px;
+  margin: 0 auto;
+  width: 180px;
+  height: 8px;
+  vertical-align: top;
+  background: transparent;
+  -webkit-appearance: none;
+  -moz-appearance: none;
+  appearance: none;
+  cursor: pointer;
+  /* 滑块
+               ---------------------------------------------------------------*/
+  /* 轨道
+               ---------------------------------------------------------------*/
+}
+.vue-image-crop-upload
+  .vicp-wrap
+  .vicp-step2
+  .vicp-crop
+  .vicp-crop-left
+  .vicp-range
+  input[type="range"]:focus {
+  outline: none;
+}
+.vue-image-crop-upload
+  .vicp-wrap
+  .vicp-step2
+  .vicp-crop
+  .vicp-crop-left
+  .vicp-range
+  input[type="range"]::-webkit-slider-thumb {
+  -webkit-box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.18);
+  box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.18);
+  -webkit-appearance: none;
+  appearance: none;
+  margin-top: -3px;
+  width: 12px;
+  height: 12px;
+  background-color: #61c091;
+  border-radius: 100%;
+  border: none;
+  -webkit-transition: 0.2s;
+  transition: 0.2s;
+}
+.vue-image-crop-upload
+  .vicp-wrap
+  .vicp-step2
+  .vicp-crop
+  .vicp-crop-left
+  .vicp-range
+  input[type="range"]::-moz-range-thumb {
+  box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.18);
+  -moz-appearance: none;
+  appearance: none;
+  width: 12px;
+  height: 12px;
+  background-color: #61c091;
+  border-radius: 100%;
+  border: none;
+  -webkit-transition: 0.2s;
+  transition: 0.2s;
+}
+.vue-image-crop-upload
+  .vicp-wrap
+  .vicp-step2
+  .vicp-crop
+  .vicp-crop-left
+  .vicp-range
+  input[type="range"]::-ms-thumb {
+  box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.18);
+  appearance: none;
+  width: 12px;
+  height: 12px;
+  background-color: #61c091;
+  border: none;
+  border-radius: 100%;
+  -webkit-transition: 0.2s;
+  transition: 0.2s;
+}
+.vue-image-crop-upload
+  .vicp-wrap
+  .vicp-step2
+  .vicp-crop
+  .vicp-crop-left
+  .vicp-range
+  input[type="range"]:active::-moz-range-thumb {
+  box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.23);
+  width: 14px;
+  height: 14px;
+}
+.vue-image-crop-upload
+  .vicp-wrap
+  .vicp-step2
+  .vicp-crop
+  .vicp-crop-left
+  .vicp-range
+  input[type="range"]:active::-ms-thumb {
+  box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.23);
+  width: 14px;
+  height: 14px;
+}
+.vue-image-crop-upload
+  .vicp-wrap
+  .vicp-step2
+  .vicp-crop
+  .vicp-crop-left
+  .vicp-range
+  input[type="range"]:active::-webkit-slider-thumb {
+  -webkit-box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.23);
+  box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.23);
+  margin-top: -4px;
+  width: 14px;
+  height: 14px;
+}
+.vue-image-crop-upload
+  .vicp-wrap
+  .vicp-step2
+  .vicp-crop
+  .vicp-crop-left
+  .vicp-range
+  input[type="range"]::-webkit-slider-runnable-track {
+  -webkit-box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.12);
+  box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.12);
+  width: 100%;
+  height: 6px;
+  cursor: pointer;
+  border-radius: 2px;
+  border: none;
+  background-color: rgba(68, 170, 119, 0.3);
+}
+.vue-image-crop-upload
+  .vicp-wrap
+  .vicp-step2
+  .vicp-crop
+  .vicp-crop-left
+  .vicp-range
+  input[type="range"]::-moz-range-track {
+  box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.12);
+  width: 100%;
+  height: 6px;
+  cursor: pointer;
+  border-radius: 2px;
+  border: none;
+  background-color: rgba(68, 170, 119, 0.3);
+}
+.vue-image-crop-upload
+  .vicp-wrap
+  .vicp-step2
+  .vicp-crop
+  .vicp-crop-left
+  .vicp-range
+  input[type="range"]::-ms-track {
+  box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.12);
+  width: 100%;
+  cursor: pointer;
+  background: transparent;
+  border-color: transparent;
+  color: transparent;
+  height: 6px;
+  border-radius: 2px;
+  border: none;
+}
+.vue-image-crop-upload
+  .vicp-wrap
+  .vicp-step2
+  .vicp-crop
+  .vicp-crop-left
+  .vicp-range
+  input[type="range"]::-ms-fill-lower {
+  background-color: rgba(68, 170, 119, 0.3);
+}
+.vue-image-crop-upload
+  .vicp-wrap
+  .vicp-step2
+  .vicp-crop
+  .vicp-crop-left
+  .vicp-range
+  input[type="range"]::-ms-fill-upper {
+  background-color: rgba(68, 170, 119, 0.15);
+}
+.vue-image-crop-upload
+  .vicp-wrap
+  .vicp-step2
+  .vicp-crop
+  .vicp-crop-left
+  .vicp-range
+  input[type="range"]:focus::-webkit-slider-runnable-track {
+  background-color: rgba(68, 170, 119, 0.5);
+}
+.vue-image-crop-upload
+  .vicp-wrap
+  .vicp-step2
+  .vicp-crop
+  .vicp-crop-left
+  .vicp-range
+  input[type="range"]:focus::-moz-range-track {
+  background-color: rgba(68, 170, 119, 0.5);
+}
+.vue-image-crop-upload
+  .vicp-wrap
+  .vicp-step2
+  .vicp-crop
+  .vicp-crop-left
+  .vicp-range
+  input[type="range"]:focus::-ms-fill-lower {
+  background-color: rgba(68, 170, 119, 0.45);
+}
+.vue-image-crop-upload
+  .vicp-wrap
+  .vicp-step2
+  .vicp-crop
+  .vicp-crop-left
+  .vicp-range
+  input[type="range"]:focus::-ms-fill-upper {
+  background-color: rgba(68, 170, 119, 0.25);
+}
+.vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-right {
+  float: right;
+}
+.vue-image-crop-upload
+  .vicp-wrap
+  .vicp-step2
+  .vicp-crop
+  .vicp-crop-right
+  .vicp-preview {
+  height: 150px;
+  overflow: hidden;
+}
+.vue-image-crop-upload
+  .vicp-wrap
+  .vicp-step2
+  .vicp-crop
+  .vicp-crop-right
+  .vicp-preview
+  .vicp-preview-item {
+  position: relative;
+  padding: 5px;
+  width: 100px;
+  height: 100px;
+  float: left;
+  margin-right: 16px;
+}
+.vue-image-crop-upload
+  .vicp-wrap
+  .vicp-step2
+  .vicp-crop
+  .vicp-crop-right
+  .vicp-preview
+  .vicp-preview-item
+  span {
+  position: absolute;
+  bottom: -30px;
+  width: 100%;
+  font-size: 14px;
+  color: #bbb;
+  display: block;
+  text-align: center;
+}
+.vue-image-crop-upload
+  .vicp-wrap
+  .vicp-step2
+  .vicp-crop
+  .vicp-crop-right
+  .vicp-preview
+  .vicp-preview-item
+  img {
+  position: absolute;
+  display: block;
+  top: 0;
+  bottom: 0;
+  left: 0;
+  right: 0;
+  margin: auto;
+  padding: 3px;
+  background-color: #fff;
+  border: 1px solid rgba(0, 0, 0, 0.15);
+  overflow: hidden;
+  -webkit-user-select: none;
+  -moz-user-select: none;
+  -ms-user-select: none;
+  user-select: none;
+}
+.vue-image-crop-upload
+  .vicp-wrap
+  .vicp-step2
+  .vicp-crop
+  .vicp-crop-right
+  .vicp-preview
+  .vicp-preview-item.vicp-preview-item-circle {
+  margin-right: 0;
+}
+.vue-image-crop-upload
+  .vicp-wrap
+  .vicp-step2
+  .vicp-crop
+  .vicp-crop-right
+  .vicp-preview
+  .vicp-preview-item.vicp-preview-item-circle
+  img {
+  border-radius: 100%;
+}
+.vue-image-crop-upload .vicp-wrap .vicp-step3 .vicp-upload {
+  position: relative;
+  -webkit-box-sizing: border-box;
+  box-sizing: border-box;
+  padding: 35px;
+  height: 170px;
+  background-color: rgba(0, 0, 0, 0.03);
+  text-align: center;
+  border: 1px dashed #ddd;
+}
+.vue-image-crop-upload .vicp-wrap .vicp-step3 .vicp-upload .vicp-loading {
+  display: block;
+  padding: 15px;
+  font-size: 16px;
+  color: #999;
+  line-height: 30px;
+}
+.vue-image-crop-upload .vicp-wrap .vicp-step3 .vicp-upload .vicp-progress-wrap {
+  margin-top: 12px;
+  background-color: rgba(0, 0, 0, 0.08);
+  border-radius: 3px;
+}
+.vue-image-crop-upload
+  .vicp-wrap
+  .vicp-step3
+  .vicp-upload
+  .vicp-progress-wrap
+  .vicp-progress {
+  position: relative;
+  display: block;
+  height: 5px;
+  border-radius: 3px;
+  background-color: #4a7;
+  -webkit-box-shadow: 0 2px 6px 0 rgba(68, 170, 119, 0.3);
+  box-shadow: 0 2px 6px 0 rgba(68, 170, 119, 0.3);
+  -webkit-transition: width 0.15s linear;
+  transition: width 0.15s linear;
+  background-image: -webkit-linear-gradient(
+    135deg,
+    rgba(255, 255, 255, 0.2) 25%,
+    transparent 25%,
+    transparent 50%,
+    rgba(255, 255, 255, 0.2) 50%,
+    rgba(255, 255, 255, 0.2) 75%,
+    transparent 75%,
+    transparent
+  );
+  background-image: linear-gradient(
+    -45deg,
+    rgba(255, 255, 255, 0.2) 25%,
+    transparent 25%,
+    transparent 50%,
+    rgba(255, 255, 255, 0.2) 50%,
+    rgba(255, 255, 255, 0.2) 75%,
+    transparent 75%,
+    transparent
+  );
+  background-size: 40px 40px;
+  -webkit-animation: vicp_progress 0.5s linear infinite;
+  animation: vicp_progress 0.5s linear infinite;
+}
+.vue-image-crop-upload
+  .vicp-wrap
+  .vicp-step3
+  .vicp-upload
+  .vicp-progress-wrap
+  .vicp-progress::after {
+  content: "";
+  position: absolute;
+  display: block;
+  top: -3px;
+  right: -3px;
+  width: 9px;
+  height: 9px;
+  border: 1px solid rgba(245, 246, 247, 0.7);
+  -webkit-box-shadow: 0 1px 4px 0 rgba(68, 170, 119, 0.7);
+  box-shadow: 0 1px 4px 0 rgba(68, 170, 119, 0.7);
+  border-radius: 100%;
+  background-color: #4a7;
+}
+.vue-image-crop-upload .vicp-wrap .vicp-step3 .vicp-upload .vicp-error,
+.vue-image-crop-upload .vicp-wrap .vicp-step3 .vicp-upload .vicp-success {
+  height: 100px;
+  line-height: 100px;
+}
+.vue-image-crop-upload .vicp-wrap .vicp-operate {
+  position: absolute;
+  right: 20px;
+  bottom: 20px;
+}
+.vue-image-crop-upload .vicp-wrap .vicp-operate a {
+  position: relative;
+  float: left;
+  display: block;
+  margin-left: 10px;
+  width: 100px;
+  height: 36px;
+  line-height: 36px;
+  text-align: center;
+  cursor: pointer;
+  font-size: 14px;
+  color: #4a7;
+  border-radius: 2px;
+  overflow: hidden;
+  -webkit-user-select: none;
+  -moz-user-select: none;
+  -ms-user-select: none;
+  user-select: none;
+}
+.vue-image-crop-upload .vicp-wrap .vicp-operate a:hover {
+  background-color: rgba(0, 0, 0, 0.03);
+}
+.vue-image-crop-upload .vicp-wrap .vicp-error,
+.vue-image-crop-upload .vicp-wrap .vicp-success {
+  display: block;
+  font-size: 14px;
+  line-height: 24px;
+  height: 24px;
+  color: #d10;
+  text-align: center;
+  vertical-align: top;
+}
+.vue-image-crop-upload .vicp-wrap .vicp-success {
+  color: #4a7;
+}
+.vue-image-crop-upload .vicp-wrap .vicp-icon3 {
+  position: relative;
+  display: inline-block;
+  width: 20px;
+  height: 20px;
+  top: 4px;
+}
+.vue-image-crop-upload .vicp-wrap .vicp-icon3::after {
+  position: absolute;
+  top: 3px;
+  left: 6px;
+  width: 6px;
+  height: 10px;
+  border-width: 0 2px 2px 0;
+  border-color: #4a7;
+  border-style: solid;
+  -webkit-transform: rotate(45deg);
+  -ms-transform: rotate(45deg);
+  transform: rotate(45deg);
+  content: "";
+}
+.vue-image-crop-upload .vicp-wrap .vicp-icon2 {
+  position: relative;
+  display: inline-block;
+  width: 20px;
+  height: 20px;
+  top: 4px;
+}
+.vue-image-crop-upload .vicp-wrap .vicp-icon2::after,
+.vue-image-crop-upload .vicp-wrap .vicp-icon2::before {
+  content: "";
+  position: absolute;
+  top: 9px;
+  left: 4px;
+  width: 13px;
+  height: 2px;
+  background-color: #d10;
+  -webkit-transform: rotate(45deg);
+  -ms-transform: rotate(45deg);
+  transform: rotate(45deg);
+}
+.vue-image-crop-upload .vicp-wrap .vicp-icon2::after {
+  -webkit-transform: rotate(-45deg);
+  -ms-transform: rotate(-45deg);
+  transform: rotate(-45deg);
+}
+.e-ripple {
+  position: absolute;
+  border-radius: 100%;
+  background-color: rgba(0, 0, 0, 0.15);
+  background-clip: padding-box;
+  pointer-events: none;
+  -webkit-user-select: none;
+  -moz-user-select: none;
+  -ms-user-select: none;
+  user-select: none;
+  -webkit-transform: scale(0);
+  -ms-transform: scale(0);
+  transform: scale(0);
+  opacity: 1;
+}
+.e-ripple.z-active {
+  opacity: 0;
+  -webkit-transform: scale(2);
+  -ms-transform: scale(2);
+  transform: scale(2);
+  -webkit-transition: opacity 1.2s ease-out, -webkit-transform 0.6s ease-out;
+  transition: opacity 1.2s ease-out, -webkit-transform 0.6s ease-out;
+  transition: opacity 1.2s ease-out, transform 0.6s ease-out;
+  transition: opacity 1.2s ease-out, transform 0.6s ease-out,
+    -webkit-transform 0.6s ease-out;
+}
+</style>
diff --git a/frontend/src/components/ImageCropper/utils/data2blob.js b/frontend/src/components/ImageCropper/utils/data2blob.js
new file mode 100644
index 0000000..9c47f8a
--- /dev/null
+++ b/frontend/src/components/ImageCropper/utils/data2blob.js
@@ -0,0 +1,19 @@
+/**
+ * database64文件格式转换为2进制
+ *
+ * @param  {[String]} data dataURL 的格式为 “data:image/png;base64,****”,逗号之前都是一些说明性的文字,我们只需要逗号之后的就行了
+ * @param  {[String]} mime [description]
+ * @return {[blob]}      [description]
+ */
+export default function(data, mime) {
+  data = data.split(',')[1]
+  data = window.atob(data)
+  var ia = new Uint8Array(data.length)
+  for (var i = 0; i < data.length; i++) {
+    ia[i] = data.charCodeAt(i)
+  }
+  // canvas.toDataURL 返回的默认格式就是 image/png
+  return new Blob([ia], {
+    type: mime
+  })
+}
diff --git a/frontend/src/components/ImageCropper/utils/effectRipple.js b/frontend/src/components/ImageCropper/utils/effectRipple.js
new file mode 100644
index 0000000..46a0164
--- /dev/null
+++ b/frontend/src/components/ImageCropper/utils/effectRipple.js
@@ -0,0 +1,39 @@
+/**
+ * 点击波纹效果
+ *
+ * @param  {[event]} e        [description]
+ * @param  {[Object]} arg_opts [description]
+ * @return {[bollean]}          [description]
+ */
+export default function(e, arg_opts) {
+  var opts = Object.assign({
+    ele: e.target, // 波纹作用元素
+    type: 'hit', // hit点击位置扩散center中心点扩展
+    bgc: 'rgba(0, 0, 0, 0.15)' // 波纹颜色
+  }, arg_opts)
+  var target = opts.ele
+  if (target) {
+    var rect = target.getBoundingClientRect()
+    var ripple = target.querySelector('.e-ripple')
+    if (!ripple) {
+      ripple = document.createElement('span')
+      ripple.className = 'e-ripple'
+      ripple.style.height = ripple.style.width = Math.max(rect.width, rect.height) + 'px'
+      target.appendChild(ripple)
+    } else {
+      ripple.className = 'e-ripple'
+    }
+    switch (opts.type) {
+      case 'center':
+        ripple.style.top = (rect.height / 2 - ripple.offsetHeight / 2) + 'px'
+        ripple.style.left = (rect.width / 2 - ripple.offsetWidth / 2) + 'px'
+        break
+      default:
+        ripple.style.top = (e.pageY - rect.top - ripple.offsetHeight / 2 - document.body.scrollTop) + 'px'
+        ripple.style.left = (e.pageX - rect.left - ripple.offsetWidth / 2 - document.body.scrollLeft) + 'px'
+    }
+    ripple.style.backgroundColor = opts.bgc
+    ripple.className = 'e-ripple z-active'
+    return false
+  }
+}
diff --git a/frontend/src/components/ImageCropper/utils/language.js b/frontend/src/components/ImageCropper/utils/language.js
new file mode 100644
index 0000000..727872d
--- /dev/null
+++ b/frontend/src/components/ImageCropper/utils/language.js
@@ -0,0 +1,232 @@
+export default {
+  zh: {
+    hint: '点击,或拖动图片至此处',
+    loading: '正在上传……',
+    noSupported: '浏览器不支持该功能,请使用IE10以上或其他现在浏览器!',
+    success: '上传成功',
+    fail: '图片上传失败',
+    preview: '头像预览',
+    btn: {
+      off: '取消',
+      close: '关闭',
+      back: '上一步',
+      save: '保存'
+    },
+    error: {
+      onlyImg: '仅限图片格式',
+      outOfSize: '单文件大小不能超过 ',
+      lowestPx: '图片最低像素为(宽*高):'
+    }
+  },
+  'zh-tw': {
+    hint: '點擊,或拖動圖片至此處',
+    loading: '正在上傳……',
+    noSupported: '瀏覽器不支持該功能,請使用IE10以上或其他現代瀏覽器!',
+    success: '上傳成功',
+    fail: '圖片上傳失敗',
+    preview: '頭像預覽',
+    btn: {
+      off: '取消',
+      close: '關閉',
+      back: '上一步',
+      save: '保存'
+    },
+    error: {
+      onlyImg: '僅限圖片格式',
+      outOfSize: '單文件大小不能超過 ',
+      lowestPx: '圖片最低像素為(寬*高):'
+    }
+  },
+  en: {
+    hint: 'Click or drag the file here to upload',
+    loading: 'Uploading…',
+    noSupported: 'Browser is not supported, please use IE10+ or other browsers',
+    success: 'Upload success',
+    fail: 'Upload failed',
+    preview: 'Preview',
+    btn: {
+      off: 'Cancel',
+      close: 'Close',
+      back: 'Back',
+      save: 'Save'
+    },
+    error: {
+      onlyImg: 'Image only',
+      outOfSize: 'Image exceeds size limit: ',
+      lowestPx: 'Image\'s size is too low. Expected at least: '
+    }
+  },
+  ro: {
+    hint: 'Atinge sau trage fișierul aici',
+    loading: 'Se încarcă',
+    noSupported: 'Browser-ul tău nu suportă acest feature. Te rugăm încearcă cu alt browser.',
+    success: 'S-a încărcat cu succes',
+    fail: 'A apărut o problemă la încărcare',
+    preview: 'Previzualizează',
+
+    btn: {
+      off: 'Anulează',
+      close: 'Închide',
+      back: 'Înapoi',
+      save: 'Salvează'
+    },
+
+    error: {
+      onlyImg: 'Doar imagini',
+      outOfSize: 'Imaginea depășește limita de: ',
+      loewstPx: 'Imaginea este prea mică; Minim: '
+    }
+  },
+  ru: {
+    hint: 'Нажмите, или перетащите файл в это окно',
+    loading: 'Загружаю……',
+    noSupported: 'Ваш браузер не поддерживается, пожалуйста, используйте IE10 + или другие браузеры',
+    success: 'Загрузка выполнена успешно',
+    fail: 'Ошибка загрузки',
+    preview: 'Предпросмотр',
+    btn: {
+      off: 'Отменить',
+      close: 'Закрыть',
+      back: 'Назад',
+      save: 'Сохранить'
+    },
+    error: {
+      onlyImg: 'Только изображения',
+      outOfSize: 'Изображение превышает предельный размер: ',
+      lowestPx: 'Минимальный размер изображения: '
+    }
+  },
+  'pt-br': {
+    hint: 'Clique ou arraste o arquivo aqui para carregar',
+    loading: 'Carregando…',
+    noSupported: 'Browser não suportado, use o IE10+ ou outro browser',
+    success: 'Sucesso ao carregar imagem',
+    fail: 'Falha ao carregar imagem',
+    preview: 'Pré-visualizar',
+    btn: {
+      off: 'Cancelar',
+      close: 'Fechar',
+      back: 'Voltar',
+      save: 'Salvar'
+    },
+    error: {
+      onlyImg: 'Apenas imagens',
+      outOfSize: 'A imagem excede o limite de tamanho: ',
+      lowestPx: 'O tamanho da imagem é muito pequeno. Tamanho mínimo: '
+    }
+  },
+  fr: {
+    hint: 'Cliquez ou glissez le fichier ici.',
+    loading: 'Téléchargement…',
+    noSupported: 'Votre navigateur n\'est pas supporté. Utilisez IE10 + ou un autre navigateur s\'il vous plaît.',
+    success: 'Téléchargement réussit',
+    fail: 'Téléchargement echoué',
+    preview: 'Aperçu',
+    btn: {
+      off: 'Annuler',
+      close: 'Fermer',
+      back: 'Retour',
+      save: 'Enregistrer'
+    },
+    error: {
+      onlyImg: 'Image uniquement',
+      outOfSize: 'L\'image sélectionnée dépasse la taille maximum: ',
+      lowestPx: 'L\'image sélectionnée est trop petite. Dimensions attendues: '
+    }
+  },
+  nl: {
+    hint: 'Klik hier of sleep een afbeelding in dit vlak',
+    loading: 'Uploaden…',
+    noSupported: 'Je browser wordt helaas niet ondersteund. Gebruik IE10+ of een andere browser.',
+    success: 'Upload succesvol',
+    fail: 'Upload mislukt',
+    preview: 'Voorbeeld',
+    btn: {
+      off: 'Annuleren',
+      close: 'Sluiten',
+      back: 'Terug',
+      save: 'Opslaan'
+    },
+    error: {
+      onlyImg: 'Alleen afbeeldingen',
+      outOfSize: 'De afbeelding is groter dan: ',
+      lowestPx: 'De afbeelding is te klein! Minimale afmetingen: '
+    }
+  },
+  tr: {
+    hint: 'Tıkla veya yüklemek istediğini buraya sürükle',
+    loading: 'Yükleniyor…',
+    noSupported: 'Tarayıcı desteklenmiyor, lütfen IE10+ veya farklı tarayıcı kullanın',
+    success: 'Yükleme başarılı',
+    fail: 'Yüklemede hata oluştu',
+    preview: 'Önizle',
+    btn: {
+      off: 'İptal',
+      close: 'Kapat',
+      back: 'Geri',
+      save: 'Kaydet'
+    },
+    error: {
+      onlyImg: 'Sadece resim',
+      outOfSize: 'Resim yükleme limitini aşıyor: ',
+      lowestPx: 'Resmin boyutu çok küçük. En az olması gereken: '
+    }
+  },
+  'es-MX': {
+    hint: 'Selecciona o arrastra una imagen',
+    loading: 'Subiendo...',
+    noSupported: 'Tu navegador no es soportado, porfavor usa IE10+ u otros navegadores mas recientes',
+    success: 'Subido exitosamente',
+    fail: 'Sucedió un error',
+    preview: 'Vista previa',
+    btn: {
+      off: 'Cancelar',
+      close: 'Cerrar',
+      back: 'Atras',
+      save: 'Guardar'
+    },
+    error: {
+      onlyImg: 'Unicamente imagenes',
+      outOfSize: 'La imagen excede el tamaño maximo:',
+      lowestPx: 'La imagen es demasiado pequeño. Se espera por lo menos:'
+    }
+  },
+  de: {
+    hint: 'Klick hier oder zieh eine Datei hier rein zum Hochladen',
+    loading: 'Hochladen…',
+    noSupported: 'Browser wird nicht unterstützt, bitte verwende IE10+ oder andere Browser',
+    success: 'Upload erfolgreich',
+    fail: 'Upload fehlgeschlagen',
+    preview: 'Vorschau',
+    btn: {
+      off: 'Abbrechen',
+      close: 'Schließen',
+      back: 'Zurück',
+      save: 'Speichern'
+    },
+    error: {
+      onlyImg: 'Nur Bilder',
+      outOfSize: 'Das Bild ist zu groß: ',
+      lowestPx: 'Das Bild ist zu klein. Mindestens: '
+    }
+  },
+  ja: {
+    hint: 'クリック・ドラッグしてファイルをアップロード',
+    loading: 'アップロード中...',
+    noSupported: 'このブラウザは対応されていません。IE10+かその他の主要ブラウザをお使いください。',
+    success: 'アップロード成功',
+    fail: 'アップロード失敗',
+    preview: 'プレビュー',
+    btn: {
+      off: 'キャンセル',
+      close: '閉じる',
+      back: '戻る',
+      save: '保存'
+    },
+    error: {
+      onlyImg: '画像のみ',
+      outOfSize: '画像サイズが上限を超えています。上限: ',
+      lowestPx: '画像が小さすぎます。最小サイズ: '
+    }
+  }
+}
diff --git a/frontend/src/components/ImageCropper/utils/mimes.js b/frontend/src/components/ImageCropper/utils/mimes.js
new file mode 100644
index 0000000..e20c085
--- /dev/null
+++ b/frontend/src/components/ImageCropper/utils/mimes.js
@@ -0,0 +1,7 @@
+export default {
+  'jpg': 'image/jpeg',
+  'png': 'image/png',
+  'gif': 'image/gif',
+  'svg': 'image/svg+xml',
+  'psd': 'image/photoshop'
+}
diff --git a/frontend/src/components/JsonEditor/index.vue b/frontend/src/components/JsonEditor/index.vue
new file mode 100644
index 0000000..c05b090
--- /dev/null
+++ b/frontend/src/components/JsonEditor/index.vue
@@ -0,0 +1,77 @@
+<template>
+  <div class="json-editor">
+    <textarea ref="textarea" />
+  </div>
+</template>
+
+<script>
+import CodeMirror from 'codemirror'
+import 'codemirror/addon/lint/lint.css'
+import 'codemirror/lib/codemirror.css'
+import 'codemirror/theme/rubyblue.css'
+require('script-loader!jsonlint')
+import 'codemirror/mode/javascript/javascript'
+import 'codemirror/addon/lint/lint'
+import 'codemirror/addon/lint/json-lint'
+
+export default {
+  name: 'JsonEditor',
+  /* eslint-disable vue/require-prop-types */
+  props: ['value'],
+  data() {
+    return {
+      jsonEditor: false
+    }
+  },
+  watch: {
+    value(value) {
+      const editorValue = this.jsonEditor.getValue()
+      if (value !== editorValue) {
+        this.jsonEditor.setValue(JSON.stringify(this.value, null, 2))
+      }
+    }
+  },
+  mounted() {
+    this.jsonEditor = CodeMirror.fromTextArea(this.$refs.textarea, {
+      lineNumbers: true,
+      mode: 'application/json',
+      gutters: ['CodeMirror-lint-markers'],
+      theme: 'rubyblue',
+      lint: true
+    })
+
+    this.jsonEditor.setValue(JSON.stringify(this.value, null, 2))
+    this.jsonEditor.on('change', cm => {
+      this.$emit('changed', cm.getValue())
+      this.$emit('input', cm.getValue())
+    })
+  },
+  methods: {
+    getValue() {
+      return this.jsonEditor.getValue()
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.json-editor {
+  height: 100%;
+  position: relative;
+
+  ::v-deep {
+    .CodeMirror {
+      height: auto;
+      min-height: 300px;
+    }
+
+    .CodeMirror-scroll {
+      min-height: 300px;
+    }
+
+    .cm-s-rubyblue span.cm-string {
+      color: #F08047;
+    }
+  }
+}
+</style>
diff --git a/frontend/src/components/Kanban/index.vue b/frontend/src/components/Kanban/index.vue
new file mode 100644
index 0000000..82f7dd7
--- /dev/null
+++ b/frontend/src/components/Kanban/index.vue
@@ -0,0 +1,99 @@
+<template>
+  <div class="board-column">
+    <div class="board-column-header">
+      {{ headerText }}
+    </div>
+    <draggable
+      :list="list"
+      v-bind="$attrs"
+      class="board-column-content"
+      :set-data="setData"
+    >
+      <div v-for="element in list" :key="element.id" class="board-item">
+        {{ element.name }} {{ element.id }}
+      </div>
+    </draggable>
+  </div>
+</template>
+
+<script>
+import draggable from 'vuedraggable'
+
+export default {
+  name: 'DragKanbanDemo',
+  components: {
+    draggable
+  },
+  props: {
+    headerText: {
+      type: String,
+      default: 'Header'
+    },
+    options: {
+      type: Object,
+      default() {
+        return {}
+      }
+    },
+    list: {
+      type: Array,
+      default() {
+        return []
+      }
+    }
+  },
+  methods: {
+    setData(dataTransfer) {
+      // to avoid Firefox bug
+      // Detail see : https://github.com/RubaXa/Sortable/issues/1012
+      dataTransfer.setData('Text', '')
+    }
+  }
+}
+</script>
+<style lang="scss" scoped>
+.board-column {
+  min-width: 300px;
+  min-height: 100px;
+  height: auto;
+  overflow: hidden;
+  background: #f0f0f0;
+  border-radius: 3px;
+
+  .board-column-header {
+    height: 50px;
+    line-height: 50px;
+    overflow: hidden;
+    padding: 0 20px;
+    text-align: center;
+    background: #333;
+    color: #fff;
+    border-radius: 3px 3px 0 0;
+  }
+
+  .board-column-content {
+    height: auto;
+    overflow: hidden;
+    border: 10px solid transparent;
+    min-height: 60px;
+    display: flex;
+    justify-content: flex-start;
+    flex-direction: column;
+    align-items: center;
+
+    .board-item {
+      cursor: pointer;
+      width: 100%;
+      height: 64px;
+      margin: 5px 0;
+      background-color: #fff;
+      text-align: left;
+      line-height: 54px;
+      padding: 5px 10px;
+      box-sizing: border-box;
+      box-shadow: 0px 1px 3px 0 rgba(0, 0, 0, 0.2);
+    }
+  }
+}
+</style>
+
diff --git a/frontend/src/components/MDinput/index.vue b/frontend/src/components/MDinput/index.vue
new file mode 100644
index 0000000..c59ea34
--- /dev/null
+++ b/frontend/src/components/MDinput/index.vue
@@ -0,0 +1,360 @@
+<template>
+  <div :class="computedClasses" class="material-input__component">
+    <div :class="{iconClass:icon}">
+      <i v-if="icon" :class="['el-icon-' + icon]" class="el-input__icon material-input__icon" />
+      <input
+        v-if="type === 'email'"
+        v-model="currentValue"
+        :name="name"
+        :placeholder="fillPlaceHolder"
+        :readonly="readonly"
+        :disabled="disabled"
+        :autocomplete="autoComplete"
+        :required="required"
+        type="email"
+        class="material-input"
+        @focus="handleMdFocus"
+        @blur="handleMdBlur"
+        @input="handleModelInput"
+      >
+      <input
+        v-if="type === 'url'"
+        v-model="currentValue"
+        :name="name"
+        :placeholder="fillPlaceHolder"
+        :readonly="readonly"
+        :disabled="disabled"
+        :autocomplete="autoComplete"
+        :required="required"
+        type="url"
+        class="material-input"
+        @focus="handleMdFocus"
+        @blur="handleMdBlur"
+        @input="handleModelInput"
+      >
+      <input
+        v-if="type === 'number'"
+        v-model="currentValue"
+        :name="name"
+        :placeholder="fillPlaceHolder"
+        :step="step"
+        :readonly="readonly"
+        :disabled="disabled"
+        :autocomplete="autoComplete"
+        :max="max"
+        :min="min"
+        :minlength="minlength"
+        :maxlength="maxlength"
+        :required="required"
+        type="number"
+        class="material-input"
+        @focus="handleMdFocus"
+        @blur="handleMdBlur"
+        @input="handleModelInput"
+      >
+      <input
+        v-if="type === 'password'"
+        v-model="currentValue"
+        :name="name"
+        :placeholder="fillPlaceHolder"
+        :readonly="readonly"
+        :disabled="disabled"
+        :autocomplete="autoComplete"
+        :max="max"
+        :min="min"
+        :required="required"
+        type="password"
+        class="material-input"
+        @focus="handleMdFocus"
+        @blur="handleMdBlur"
+        @input="handleModelInput"
+      >
+      <input
+        v-if="type === 'tel'"
+        v-model="currentValue"
+        :name="name"
+        :placeholder="fillPlaceHolder"
+        :readonly="readonly"
+        :disabled="disabled"
+        :autocomplete="autoComplete"
+        :required="required"
+        type="tel"
+        class="material-input"
+        @focus="handleMdFocus"
+        @blur="handleMdBlur"
+        @input="handleModelInput"
+      >
+      <input
+        v-if="type === 'text'"
+        v-model="currentValue"
+        :name="name"
+        :placeholder="fillPlaceHolder"
+        :readonly="readonly"
+        :disabled="disabled"
+        :autocomplete="autoComplete"
+        :minlength="minlength"
+        :maxlength="maxlength"
+        :required="required"
+        type="text"
+        class="material-input"
+        @focus="handleMdFocus"
+        @blur="handleMdBlur"
+        @input="handleModelInput"
+      >
+      <span class="material-input-bar" />
+      <label class="material-label">
+        <slot />
+      </label>
+    </div>
+  </div>
+</template>
+
+<script>
+// source:https://github.com/wemake-services/vue-material-input/blob/master/src/components/MaterialInput.vue
+
+export default {
+  name: 'MdInput',
+  props: {
+    /* eslint-disable */
+    icon: String,
+    name: String,
+    type: {
+      type: String,
+      default: 'text'
+    },
+    value: [String, Number],
+    placeholder: String,
+    readonly: Boolean,
+    disabled: Boolean,
+    min: String,
+    max: String,
+    step: String,
+    minlength: Number,
+    maxlength: Number,
+    required: {
+      type: Boolean,
+      default: true
+    },
+    autoComplete: {
+      type: String,
+      default: 'off'
+    },
+    validateEvent: {
+      type: Boolean,
+      default: true
+    }
+  },
+  data() {
+    return {
+      currentValue: this.value,
+      focus: false,
+      fillPlaceHolder: null
+    }
+  },
+  computed: {
+    computedClasses() {
+      return {
+        'material--active': this.focus,
+        'material--disabled': this.disabled,
+        'material--raised': Boolean(this.focus || this.currentValue) // has value
+      }
+    }
+  },
+  watch: {
+    value(newValue) {
+      this.currentValue = newValue
+    }
+  },
+  methods: {
+    handleModelInput(event) {
+      const value = event.target.value
+      this.$emit('input', value)
+      if (this.$parent.$options.componentName === 'ElFormItem') {
+        if (this.validateEvent) {
+          this.$parent.$emit('el.form.change', [value])
+        }
+      }
+      this.$emit('change', value)
+    },
+    handleMdFocus(event) {
+      this.focus = true
+      this.$emit('focus', event)
+      if (this.placeholder && this.placeholder !== '') {
+        this.fillPlaceHolder = this.placeholder
+      }
+    },
+    handleMdBlur(event) {
+      this.focus = false
+      this.$emit('blur', event)
+      this.fillPlaceHolder = null
+      if (this.$parent.$options.componentName === 'ElFormItem') {
+        if (this.validateEvent) {
+          this.$parent.$emit('el.form.blur', [this.currentValue])
+        }
+      }
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+  // Fonts:
+  $font-size-base: 16px;
+  $font-size-small: 18px;
+  $font-size-smallest: 12px;
+  $font-weight-normal: normal;
+  $font-weight-bold: bold;
+  $apixel: 1px;
+  // Utils
+  $spacer: 12px;
+  $transition: 0.2s ease all;
+  $index: 0px;
+  $index-has-icon: 30px;
+  // Theme:
+  $color-white: white;
+  $color-grey: #9E9E9E;
+  $color-grey-light: #E0E0E0;
+  $color-blue: #2196F3;
+  $color-red: #F44336;
+  $color-black: black;
+  // Base clases:
+  %base-bar-pseudo {
+    content: '';
+    height: 1px;
+    width: 0;
+    bottom: 0;
+    position: absolute;
+    transition: $transition;
+  }
+
+  // Mixins:
+  @mixin slided-top() {
+    top: - ($font-size-base + $spacer);
+    left: 0;
+    font-size: $font-size-base;
+    font-weight: $font-weight-bold;
+  }
+
+  // Component:
+  .material-input__component {
+    margin-top: 36px;
+    position: relative;
+    * {
+      box-sizing: border-box;
+    }
+    .iconClass {
+      .material-input__icon {
+        position: absolute;
+        left: 0;
+        line-height: $font-size-base;
+        color: $color-blue;
+        top: $spacer;
+        width: $index-has-icon;
+        height: $font-size-base;
+        font-size: $font-size-base;
+        font-weight: $font-weight-normal;
+        pointer-events: none;
+      }
+      .material-label {
+        left: $index-has-icon;
+      }
+      .material-input {
+        text-indent: $index-has-icon;
+      }
+    }
+    .material-input {
+      font-size: $font-size-base;
+      padding: $spacer $spacer $spacer - $apixel * 10 $spacer / 2;
+      display: block;
+      width: 100%;
+      border: none;
+      line-height: 1;
+      border-radius: 0;
+      &:focus {
+        outline: none;
+        border: none;
+        border-bottom: 1px solid transparent; // fixes the height issue
+      }
+    }
+    .material-label {
+      font-weight: $font-weight-normal;
+      position: absolute;
+      pointer-events: none;
+      left: $index;
+      top: 0;
+      transition: $transition;
+      font-size: $font-size-small;
+    }
+    .material-input-bar {
+      position: relative;
+      display: block;
+      width: 100%;
+      &:before {
+        @extend %base-bar-pseudo;
+        left: 50%;
+      }
+      &:after {
+        @extend %base-bar-pseudo;
+        right: 50%;
+      }
+    }
+    // Disabled state:
+    &.material--disabled {
+      .material-input {
+        border-bottom-style: dashed;
+      }
+    }
+    // Raised state:
+    &.material--raised {
+      .material-label {
+        @include slided-top();
+      }
+    }
+    // Active state:
+    &.material--active {
+      .material-input-bar {
+        &:before,
+        &:after {
+          width: 50%;
+        }
+      }
+    }
+  }
+
+  .material-input__component {
+    background: $color-white;
+    .material-input {
+      background: none;
+      color: $color-black;
+      text-indent: $index;
+      border-bottom: 1px solid $color-grey-light;
+    }
+    .material-label {
+      color: $color-grey;
+    }
+    .material-input-bar {
+      &:before,
+      &:after {
+        background: $color-blue;
+      }
+    }
+    // Active state:
+    &.material--active {
+      .material-label {
+        color: $color-blue;
+      }
+    }
+    // Errors:
+    &.material--has-errors {
+      &.material--active .material-label {
+        color: $color-red;
+      }
+      .material-input-bar {
+        &:before,
+        &:after {
+          background: transparent;
+        }
+      }
+    }
+  }
+</style>
diff --git a/frontend/src/components/MarkdownEditor/default-options.js b/frontend/src/components/MarkdownEditor/default-options.js
new file mode 100644
index 0000000..303aa13
--- /dev/null
+++ b/frontend/src/components/MarkdownEditor/default-options.js
@@ -0,0 +1,31 @@
+// doc: https://nhnent.github.io/tui.editor/api/latest/ToastUIEditor.html#ToastUIEditor
+export default {
+  minHeight: '200px',
+  previewStyle: 'vertical',
+  useCommandShortcut: true,
+  useDefaultHTMLSanitizer: true,
+  usageStatistics: false,
+  hideModeSwitch: false,
+  toolbarItems: [
+    'heading',
+    'bold',
+    'italic',
+    'strike',
+    'divider',
+    'hr',
+    'quote',
+    'divider',
+    'ul',
+    'ol',
+    'task',
+    'indent',
+    'outdent',
+    'divider',
+    'table',
+    'image',
+    'link',
+    'divider',
+    'code',
+    'codeblock'
+  ]
+}
diff --git a/frontend/src/components/MarkdownEditor/index.vue b/frontend/src/components/MarkdownEditor/index.vue
new file mode 100644
index 0000000..1a8a01e
--- /dev/null
+++ b/frontend/src/components/MarkdownEditor/index.vue
@@ -0,0 +1,118 @@
+<template>
+  <div :id="id" />
+</template>
+
+<script>
+// deps for editor
+import 'codemirror/lib/codemirror.css' // codemirror
+import 'tui-editor/dist/tui-editor.css' // editor ui
+import 'tui-editor/dist/tui-editor-contents.css' // editor content
+
+import Editor from 'tui-editor'
+import defaultOptions from './default-options'
+
+export default {
+  name: 'MarkdownEditor',
+  props: {
+    value: {
+      type: String,
+      default: ''
+    },
+    id: {
+      type: String,
+      required: false,
+      default() {
+        return 'markdown-editor-' + +new Date() + ((Math.random() * 1000).toFixed(0) + '')
+      }
+    },
+    options: {
+      type: Object,
+      default() {
+        return defaultOptions
+      }
+    },
+    mode: {
+      type: String,
+      default: 'markdown'
+    },
+    height: {
+      type: String,
+      required: false,
+      default: '300px'
+    },
+    language: {
+      type: String,
+      required: false,
+      default: 'en_US' // https://github.com/nhnent/tui.editor/tree/master/src/js/langs
+    }
+  },
+  data() {
+    return {
+      editor: null
+    }
+  },
+  computed: {
+    editorOptions() {
+      const options = Object.assign({}, defaultOptions, this.options)
+      options.initialEditType = this.mode
+      options.height = this.height
+      options.language = this.language
+      return options
+    }
+  },
+  watch: {
+    value(newValue, preValue) {
+      if (newValue !== preValue && newValue !== this.editor.getValue()) {
+        this.editor.setValue(newValue)
+      }
+    },
+    language(val) {
+      this.destroyEditor()
+      this.initEditor()
+    },
+    height(newValue) {
+      this.editor.height(newValue)
+    },
+    mode(newValue) {
+      this.editor.changeMode(newValue)
+    }
+  },
+  mounted() {
+    this.initEditor()
+  },
+  destroyed() {
+    this.destroyEditor()
+  },
+  methods: {
+    initEditor() {
+      this.editor = new Editor({
+        el: document.getElementById(this.id),
+        ...this.editorOptions
+      })
+      if (this.value) {
+        this.editor.setValue(this.value)
+      }
+      this.editor.on('change', () => {
+        this.$emit('input', this.editor.getValue())
+      })
+    },
+    destroyEditor() {
+      if (!this.editor) return
+      this.editor.off('change')
+      this.editor.remove()
+    },
+    setValue(value) {
+      this.editor.setValue(value)
+    },
+    getValue() {
+      return this.editor.getValue()
+    },
+    setHtml(value) {
+      this.editor.setHtml(value)
+    },
+    getHtml() {
+      return this.editor.getHtml()
+    }
+  }
+}
+</script>
diff --git a/frontend/src/components/Pagination/index.vue b/frontend/src/components/Pagination/index.vue
new file mode 100644
index 0000000..c815e13
--- /dev/null
+++ b/frontend/src/components/Pagination/index.vue
@@ -0,0 +1,101 @@
+<template>
+  <div :class="{'hidden':hidden}" class="pagination-container">
+    <el-pagination
+      :background="background"
+      :current-page.sync="currentPage"
+      :page-size.sync="pageSize"
+      :layout="layout"
+      :page-sizes="pageSizes"
+      :total="total"
+      v-bind="$attrs"
+      @size-change="handleSizeChange"
+      @current-change="handleCurrentChange"
+    />
+  </div>
+</template>
+
+<script>
+import { scrollTo } from '@/utils/scroll-to'
+
+export default {
+  name: 'Pagination',
+  props: {
+    total: {
+      required: true,
+      type: Number
+    },
+    page: {
+      type: Number,
+      default: 1
+    },
+    limit: {
+      type: Number,
+      default: 20
+    },
+    pageSizes: {
+      type: Array,
+      default() {
+        return [10, 20, 30, 50]
+      }
+    },
+    layout: {
+      type: String,
+      default: 'total, sizes, prev, pager, next, jumper'
+    },
+    background: {
+      type: Boolean,
+      default: true
+    },
+    autoScroll: {
+      type: Boolean,
+      default: true
+    },
+    hidden: {
+      type: Boolean,
+      default: false
+    }
+  },
+  computed: {
+    currentPage: {
+      get() {
+        return this.page
+      },
+      set(val) {
+        this.$emit('update:page', val)
+      }
+    },
+    pageSize: {
+      get() {
+        return this.limit
+      },
+      set(val) {
+        this.$emit('update:limit', val)
+      }
+    }
+  },
+  methods: {
+    handleSizeChange(val) {
+      this.$emit('pagination', { page: this.currentPage, limit: val })
+      if (this.autoScroll) {
+        scrollTo(0, 800)
+      }
+    },
+    handleCurrentChange(val) {
+      this.$emit('pagination', { page: val, limit: this.pageSize })
+      if (this.autoScroll) {
+        scrollTo(0, 800)
+      }
+    }
+  }
+}
+</script>
+
+<style scoped>
+.pagination-container {
+  background: #fff;
+  padding: 32px 16px;
+}
+.pagination-container.hidden {
+  display: none;
+}
+</style>
diff --git a/frontend/src/components/PanThumb/index.vue b/frontend/src/components/PanThumb/index.vue
new file mode 100644
index 0000000..1bcf417
--- /dev/null
+++ b/frontend/src/components/PanThumb/index.vue
@@ -0,0 +1,142 @@
+<template>
+  <div :style="{zIndex:zIndex,height:height,width:width}" class="pan-item">
+    <div class="pan-info">
+      <div class="pan-info-roles-container">
+        <slot />
+      </div>
+    </div>
+    <!-- eslint-disable-next-line -->
+    <div :style="{backgroundImage: `url(${image})`}" class="pan-thumb"></div>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'PanThumb',
+  props: {
+    image: {
+      type: String,
+      required: true
+    },
+    zIndex: {
+      type: Number,
+      default: 1
+    },
+    width: {
+      type: String,
+      default: '150px'
+    },
+    height: {
+      type: String,
+      default: '150px'
+    }
+  }
+}
+</script>
+
+<style scoped>
+.pan-item {
+  width: 200px;
+  height: 200px;
+  border-radius: 50%;
+  display: inline-block;
+  position: relative;
+  cursor: default;
+  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
+}
+
+.pan-info-roles-container {
+  padding: 20px;
+  text-align: center;
+}
+
+.pan-thumb {
+  width: 100%;
+  height: 100%;
+  background-position: center center;
+  background-size: cover;
+  border-radius: 50%;
+  overflow: hidden;
+  position: absolute;
+  transform-origin: 95% 40%;
+  transition: all 0.3s ease-in-out;
+}
+
+/* .pan-thumb:after {
+  content: '';
+  width: 8px;
+  height: 8px;
+  position: absolute;
+  border-radius: 50%;
+  top: 40%;
+  left: 95%;
+  margin: -4px 0 0 -4px;
+  background: radial-gradient(ellipse at center, rgba(14, 14, 14, 1) 0%, rgba(125, 126, 125, 1) 100%);
+  box-shadow: 0 0 1px rgba(255, 255, 255, 0.9);
+} */
+
+.pan-info {
+  position: absolute;
+  width: inherit;
+  height: inherit;
+  border-radius: 50%;
+  overflow: hidden;
+  box-shadow: inset 0 0 0 5px rgba(0, 0, 0, 0.05);
+}
+
+.pan-info h3 {
+  color: #fff;
+  text-transform: uppercase;
+  position: relative;
+  letter-spacing: 2px;
+  font-size: 18px;
+  margin: 0 60px;
+  padding: 22px 0 0 0;
+  height: 85px;
+  font-family: 'Open Sans', Arial, sans-serif;
+  text-shadow: 0 0 1px #fff, 0 1px 2px rgba(0, 0, 0, 0.3);
+}
+
+.pan-info p {
+  color: #fff;
+  padding: 10px 5px;
+  font-style: italic;
+  margin: 0 30px;
+  font-size: 12px;
+  border-top: 1px solid rgba(255, 255, 255, 0.5);
+}
+
+.pan-info p a {
+  display: block;
+  color: #333;
+  width: 80px;
+  height: 80px;
+  background: rgba(255, 255, 255, 0.3);
+  border-radius: 50%;
+  color: #fff;
+  font-style: normal;
+  font-weight: 700;
+  text-transform: uppercase;
+  font-size: 9px;
+  letter-spacing: 1px;
+  padding-top: 24px;
+  margin: 7px auto 0;
+  font-family: 'Open Sans', Arial, sans-serif;
+  opacity: 0;
+  transition: transform 0.3s ease-in-out 0.2s, opacity 0.3s ease-in-out 0.2s, background 0.2s linear 0s;
+  transform: translateX(60px) rotate(90deg);
+}
+
+.pan-info p a:hover {
+  background: rgba(255, 255, 255, 0.5);
+}
+
+.pan-item:hover .pan-thumb {
+  transform: rotate(-110deg);
+}
+
+.pan-item:hover .pan-info p a {
+  opacity: 1;
+  transform: translateX(0px) rotate(0deg);
+}
+</style>
diff --git a/frontend/src/components/RightPanel/index.vue b/frontend/src/components/RightPanel/index.vue
new file mode 100644
index 0000000..55e8c1e
--- /dev/null
+++ b/frontend/src/components/RightPanel/index.vue
@@ -0,0 +1,145 @@
+<template>
+  <div ref="rightPanel" :class="{show:show}" class="rightPanel-container">
+    <div class="rightPanel-background" />
+    <div class="rightPanel">
+      <div class="handle-button" :style="{'top':buttonTop+'px','background-color':theme}" @click="show=!show">
+        <i :class="show?'el-icon-close':'el-icon-setting'" />
+      </div>
+      <div class="rightPanel-items">
+        <slot />
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import { addClass, removeClass } from '@/utils'
+
+export default {
+  name: 'RightPanel',
+  props: {
+    clickNotClose: {
+      default: false,
+      type: Boolean
+    },
+    buttonTop: {
+      default: 250,
+      type: Number
+    }
+  },
+  data() {
+    return {
+      show: false
+    }
+  },
+  computed: {
+    theme() {
+      return this.$store.state.settings.theme
+    }
+  },
+  watch: {
+    show(value) {
+      if (value && !this.clickNotClose) {
+        this.addEventClick()
+      }
+      if (value) {
+        addClass(document.body, 'showRightPanel')
+      } else {
+        removeClass(document.body, 'showRightPanel')
+      }
+    }
+  },
+  mounted() {
+    this.insertToBody()
+  },
+  beforeDestroy() {
+    const elx = this.$refs.rightPanel
+    elx.remove()
+  },
+  methods: {
+    addEventClick() {
+      window.addEventListener('click', this.closeSidebar)
+    },
+    closeSidebar(evt) {
+      const parent = evt.target.closest('.rightPanel')
+      if (!parent) {
+        this.show = false
+        window.removeEventListener('click', this.closeSidebar)
+      }
+    },
+    insertToBody() {
+      const elx = this.$refs.rightPanel
+      const body = document.querySelector('body')
+      body.insertBefore(elx, body.firstChild)
+    }
+  }
+}
+</script>
+
+<style>
+.showRightPanel {
+  overflow: hidden;
+  position: relative;
+  width: calc(100% - 15px);
+}
+</style>
+
+<style lang="scss" scoped>
+.rightPanel-background {
+  position: fixed;
+  top: 0;
+  left: 0;
+  opacity: 0;
+  transition: opacity .3s cubic-bezier(.7, .3, .1, 1);
+  background: rgba(0, 0, 0, .2);
+  z-index: -1;
+}
+
+.rightPanel {
+  width: 100%;
+  max-width: 260px;
+  height: 100vh;
+  position: fixed;
+  top: 0;
+  right: 0;
+  box-shadow: 0px 0px 15px 0px rgba(0, 0, 0, .05);
+  transition: all .25s cubic-bezier(.7, .3, .1, 1);
+  transform: translate(100%);
+  background: #fff;
+  z-index: 40000;
+}
+
+.show {
+  transition: all .3s cubic-bezier(.7, .3, .1, 1);
+
+  .rightPanel-background {
+    z-index: 20000;
+    opacity: 1;
+    width: 100%;
+    height: 100%;
+  }
+
+  .rightPanel {
+    transform: translate(0);
+  }
+}
+
+.handle-button {
+  width: 48px;
+  height: 48px;
+  position: absolute;
+  left: -48px;
+  text-align: center;
+  font-size: 24px;
+  border-radius: 6px 0 0 6px !important;
+  z-index: 0;
+  pointer-events: auto;
+  cursor: pointer;
+  color: #fff;
+  line-height: 48px;
+  i {
+    font-size: 24px;
+    line-height: 48px;
+  }
+}
+</style>
diff --git a/frontend/src/components/Screenfull/index.vue b/frontend/src/components/Screenfull/index.vue
new file mode 100644
index 0000000..260c90d
--- /dev/null
+++ b/frontend/src/components/Screenfull/index.vue
@@ -0,0 +1,60 @@
+<template>
+  <div>
+    <svg-icon :icon-class="isFullscreen?'exit-fullscreen':'fullscreen'" @click="click" />
+  </div>
+</template>
+
+<script>
+import screenfull from 'screenfull'
+
+export default {
+  name: 'Screenfull',
+  data() {
+    return {
+      isFullscreen: false
+    }
+  },
+  mounted() {
+    this.init()
+  },
+  beforeDestroy() {
+    this.destroy()
+  },
+  methods: {
+    click() {
+      if (!screenfull.enabled) {
+        this.$message({
+          message: 'you browser can not work',
+          type: 'warning'
+        })
+        return false
+      }
+      screenfull.toggle()
+    },
+    change() {
+      this.isFullscreen = screenfull.isFullscreen
+    },
+    init() {
+      if (screenfull.enabled) {
+        screenfull.on('change', this.change)
+      }
+    },
+    destroy() {
+      if (screenfull.enabled) {
+        screenfull.off('change', this.change)
+      }
+    }
+  }
+}
+</script>
+
+<style scoped>
+.screenfull-svg {
+  display: inline-block;
+  cursor: pointer;
+  fill: #5a5e66;;
+  width: 20px;
+  height: 20px;
+  vertical-align: 10px;
+}
+</style>
diff --git a/frontend/src/components/Share/DropdownMenu.vue b/frontend/src/components/Share/DropdownMenu.vue
new file mode 100644
index 0000000..d194a51
--- /dev/null
+++ b/frontend/src/components/Share/DropdownMenu.vue
@@ -0,0 +1,103 @@
+<template>
+  <div :class="{active:isActive}" class="share-dropdown-menu">
+    <div class="share-dropdown-menu-wrapper">
+      <span class="share-dropdown-menu-title" @click.self="clickTitle">{{ title }}</span>
+      <div v-for="(item,index) of items" :key="index" class="share-dropdown-menu-item">
+        <a v-if="item.href" :href="item.href" target="_blank">{{ item.title }}</a>
+        <span v-else>{{ item.title }}</span>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+export default {
+  props: {
+    items: {
+      type: Array,
+      default: function() {
+        return []
+      }
+    },
+    title: {
+      type: String,
+      default: 'vue'
+    }
+  },
+  data() {
+    return {
+      isActive: false
+    }
+  },
+  methods: {
+    clickTitle() {
+      this.isActive = !this.isActive
+    }
+  }
+}
+</script>
+
+<style lang="scss" >
+$n: 9; //和items.length 相同
+$t: .1s;
+.share-dropdown-menu {
+  width: 250px;
+  position: relative;
+  z-index: 1;
+  height: auto!important;
+  &-title {
+    width: 100%;
+    display: block;
+    cursor: pointer;
+    background: black;
+    color: white;
+    height: 60px;
+    line-height: 60px;
+    font-size: 20px;
+    text-align: center;
+    z-index: 2;
+    transform: translate3d(0,0,0);
+  }
+  &-wrapper {
+    position: relative;
+  }
+  &-item {
+    text-align: center;
+    position: absolute;
+    width: 100%;
+    background: #e0e0e0;
+    color: #000;
+    line-height: 60px;
+    height: 60px;
+    cursor: pointer;
+    font-size: 18px;
+    overflow: hidden;
+    opacity: 1;
+    transition: transform 0.28s ease;
+    &:hover {
+      background: black;
+      color: white;
+    }
+    @for $i from 1 through $n {
+      &:nth-of-type(#{$i}) {
+        z-index: -1;
+        transition-delay: $i*$t;
+        transform: translate3d(0, -60px, 0);
+      }
+    }
+  }
+  &.active {
+    .share-dropdown-menu-wrapper {
+      z-index: 1;
+    }
+    .share-dropdown-menu-item {
+      @for $i from 1 through $n {
+        &:nth-of-type(#{$i}) {
+          transition-delay: ($n - $i)*$t;
+          transform: translate3d(0, ($i - 1)*60px, 0);
+        }
+      }
+    }
+  }
+}
+</style>
diff --git a/frontend/src/components/SizeSelect/index.vue b/frontend/src/components/SizeSelect/index.vue
new file mode 100644
index 0000000..e88065b
--- /dev/null
+++ b/frontend/src/components/SizeSelect/index.vue
@@ -0,0 +1,57 @@
+<template>
+  <el-dropdown trigger="click" @command="handleSetSize">
+    <div>
+      <svg-icon class-name="size-icon" icon-class="size" />
+    </div>
+    <el-dropdown-menu slot="dropdown">
+      <el-dropdown-item v-for="item of sizeOptions" :key="item.value" :disabled="size===item.value" :command="item.value">
+        {{
+          item.label }}
+      </el-dropdown-item>
+    </el-dropdown-menu>
+  </el-dropdown>
+</template>
+
+<script>
+export default {
+  data() {
+    return {
+      sizeOptions: [
+        { label: 'Default', value: 'default' },
+        { label: 'Medium', value: 'medium' },
+        { label: 'Small', value: 'small' },
+        { label: 'Mini', value: 'mini' }
+      ]
+    }
+  },
+  computed: {
+    size() {
+      return this.$store.getters.size
+    }
+  },
+  methods: {
+    handleSetSize(size) {
+      this.$ELEMENT.size = size
+      this.$store.dispatch('app/setSize', size)
+      this.refreshView()
+      this.$message({
+        message: 'Switch Size Success',
+        type: 'success'
+      })
+    },
+    refreshView() {
+      // In order to make the cached page re-rendered
+      this.$store.dispatch('tagsView/delAllCachedViews', this.$route)
+
+      const { fullPath } = this.$route
+
+      this.$nextTick(() => {
+        this.$router.replace({
+          path: '/redirect' + fullPath
+        })
+      })
+    }
+  }
+
+}
+</script>
diff --git a/frontend/src/components/Sticky/index.vue b/frontend/src/components/Sticky/index.vue
new file mode 100644
index 0000000..97ce0e9
--- /dev/null
+++ b/frontend/src/components/Sticky/index.vue
@@ -0,0 +1,91 @@
+<template>
+  <div :style="{height:height+'px',zIndex:zIndex}">
+    <div
+      :class="className"
+      :style="{top:(isSticky ? stickyTop +'px' : ''),zIndex:zIndex,position:position,width:width,height:height+'px'}"
+    >
+      <slot>
+        <div>sticky</div>
+      </slot>
+    </div>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'Sticky',
+  props: {
+    stickyTop: {
+      type: Number,
+      default: 0
+    },
+    zIndex: {
+      type: Number,
+      default: 1
+    },
+    className: {
+      type: String,
+      default: ''
+    }
+  },
+  data() {
+    return {
+      active: false,
+      position: '',
+      width: undefined,
+      height: undefined,
+      isSticky: false
+    }
+  },
+  mounted() {
+    this.height = this.$el.getBoundingClientRect().height
+    window.addEventListener('scroll', this.handleScroll)
+    window.addEventListener('resize', this.handleResize)
+  },
+  activated() {
+    this.handleScroll()
+  },
+  destroyed() {
+    window.removeEventListener('scroll', this.handleScroll)
+    window.removeEventListener('resize', this.handleResize)
+  },
+  methods: {
+    sticky() {
+      if (this.active) {
+        return
+      }
+      this.position = 'fixed'
+      this.active = true
+      this.width = this.width + 'px'
+      this.isSticky = true
+    },
+    handleReset() {
+      if (!this.active) {
+        return
+      }
+      this.reset()
+    },
+    reset() {
+      this.position = ''
+      this.width = 'auto'
+      this.active = false
+      this.isSticky = false
+    },
+    handleScroll() {
+      const width = this.$el.getBoundingClientRect().width
+      this.width = width || 'auto'
+      const offsetTop = this.$el.getBoundingClientRect().top
+      if (offsetTop < this.stickyTop) {
+        this.sticky()
+        return
+      }
+      this.handleReset()
+    },
+    handleResize() {
+      if (this.isSticky) {
+        this.width = this.$el.getBoundingClientRect().width + 'px'
+      }
+    }
+  }
+}
+</script>
diff --git a/frontend/src/components/SvgIcon/index.vue b/frontend/src/components/SvgIcon/index.vue
new file mode 100644
index 0000000..b07ded2
--- /dev/null
+++ b/frontend/src/components/SvgIcon/index.vue
@@ -0,0 +1,62 @@
+<template>
+  <div v-if="isExternal" :style="styleExternalIcon" class="svg-external-icon svg-icon" v-on="$listeners" />
+  <svg v-else :class="svgClass" aria-hidden="true" v-on="$listeners">
+    <use :xlink:href="iconName" />
+  </svg>
+</template>
+
+<script>
+// doc: https://panjiachen.github.io/vue-element-admin-site/feature/component/svg-icon.html#usage
+import { isExternal } from '@/utils/validate'
+
+export default {
+  name: 'SvgIcon',
+  props: {
+    iconClass: {
+      type: String,
+      required: true
+    },
+    className: {
+      type: String,
+      default: ''
+    }
+  },
+  computed: {
+    isExternal() {
+      return isExternal(this.iconClass)
+    },
+    iconName() {
+      return `#icon-${this.iconClass}`
+    },
+    svgClass() {
+      if (this.className) {
+        return 'svg-icon ' + this.className
+      } else {
+        return 'svg-icon'
+      }
+    },
+    styleExternalIcon() {
+      return {
+        mask: `url(${this.iconClass}) no-repeat 50% 50%`,
+        '-webkit-mask': `url(${this.iconClass}) no-repeat 50% 50%`
+      }
+    }
+  }
+}
+</script>
+
+<style scoped>
+.svg-icon {
+  width: 1em;
+  height: 1em;
+  vertical-align: -0.15em;
+  fill: currentColor;
+  overflow: hidden;
+}
+
+.svg-external-icon {
+  background-color: currentColor;
+  mask-size: cover!important;
+  display: inline-block;
+}
+</style>
diff --git a/frontend/src/components/TextHoverEffect/Mallki.vue b/frontend/src/components/TextHoverEffect/Mallki.vue
new file mode 100644
index 0000000..5d6d16c
--- /dev/null
+++ b/frontend/src/components/TextHoverEffect/Mallki.vue
@@ -0,0 +1,113 @@
+<template>
+  <a :class="className" class="link--mallki" href="#">
+    {{ text }}
+    <span :data-letters="text" />
+    <span :data-letters="text" />
+  </a>
+</template>
+
+<script>
+export default {
+  props: {
+    className: {
+      type: String,
+      default: ''
+    },
+    text: {
+      type: String,
+      default: 'vue-element-admin'
+    }
+  }
+}
+</script>
+
+<style>
+/* Mallki */
+
+.link--mallki {
+  font-weight: 800;
+  color: #4dd9d5;
+  font-family: 'Dosis', sans-serif;
+  -webkit-transition: color 0.5s 0.25s;
+  transition: color 0.5s 0.25s;
+  overflow: hidden;
+  position: relative;
+  display: inline-block;
+  line-height: 1;
+  outline: none;
+  text-decoration: none;
+}
+
+.link--mallki:hover {
+  -webkit-transition: none;
+  transition: none;
+  color: transparent;
+}
+
+.link--mallki::before {
+  content: '';
+  width: 100%;
+  height: 6px;
+  margin: -3px 0 0 0;
+  background: #3888fa;
+  position: absolute;
+  left: 0;
+  top: 50%;
+  -webkit-transform: translate3d(-100%, 0, 0);
+  transform: translate3d(-100%, 0, 0);
+  -webkit-transition: -webkit-transform 0.4s;
+  transition: transform 0.4s;
+  -webkit-transition-timing-function: cubic-bezier(0.7, 0, 0.3, 1);
+  transition-timing-function: cubic-bezier(0.7, 0, 0.3, 1);
+}
+
+.link--mallki:hover::before {
+  -webkit-transform: translate3d(100%, 0, 0);
+  transform: translate3d(100%, 0, 0);
+}
+
+.link--mallki span {
+  position: absolute;
+  height: 50%;
+  width: 100%;
+  left: 0;
+  top: 0;
+  overflow: hidden;
+}
+
+.link--mallki span::before {
+  content: attr(data-letters);
+  color: red;
+  position: absolute;
+  left: 0;
+  width: 100%;
+  color: #3888fa;
+  -webkit-transition: -webkit-transform 0.5s;
+  transition: transform 0.5s;
+}
+
+.link--mallki span:nth-child(2) {
+  top: 50%;
+}
+
+.link--mallki span:first-child::before {
+  top: 0;
+  -webkit-transform: translate3d(0, 100%, 0);
+  transform: translate3d(0, 100%, 0);
+}
+
+.link--mallki span:nth-child(2)::before {
+  bottom: 0;
+  -webkit-transform: translate3d(0, -100%, 0);
+  transform: translate3d(0, -100%, 0);
+}
+
+.link--mallki:hover span::before {
+  -webkit-transition-delay: 0.3s;
+  transition-delay: 0.3s;
+  -webkit-transform: translate3d(0, 0, 0);
+  transform: translate3d(0, 0, 0);
+  -webkit-transition-timing-function: cubic-bezier(0.2, 1, 0.3, 1);
+  transition-timing-function: cubic-bezier(0.2, 1, 0.3, 1);
+}
+</style>
diff --git a/frontend/src/components/ThemePicker/index.vue b/frontend/src/components/ThemePicker/index.vue
new file mode 100644
index 0000000..3879c5a
--- /dev/null
+++ b/frontend/src/components/ThemePicker/index.vue
@@ -0,0 +1,175 @@
+<template>
+  <el-color-picker
+    v-model="theme"
+    :predefine="['#409EFF', '#1890ff', '#304156','#212121','#11a983', '#13c2c2', '#6959CD', '#f5222d', ]"
+    class="theme-picker"
+    popper-class="theme-picker-dropdown"
+  />
+</template>
+
+<script>
+const version = require('element-ui/package.json').version // element-ui version from node_modules
+const ORIGINAL_THEME = '#409EFF' // default color
+
+export default {
+  data() {
+    return {
+      chalk: '', // content of theme-chalk css
+      theme: ''
+    }
+  },
+  computed: {
+    defaultTheme() {
+      return this.$store.state.settings.theme
+    }
+  },
+  watch: {
+    defaultTheme: {
+      handler: function(val, oldVal) {
+        this.theme = val
+      },
+      immediate: true
+    },
+    async theme(val) {
+      const oldVal = this.chalk ? this.theme : ORIGINAL_THEME
+      if (typeof val !== 'string') return
+      const themeCluster = this.getThemeCluster(val.replace('#', ''))
+      const originalCluster = this.getThemeCluster(oldVal.replace('#', ''))
+      console.log(themeCluster, originalCluster)
+
+      const $message = this.$message({
+        message: '  Compiling the theme',
+        customClass: 'theme-message',
+        type: 'success',
+        duration: 0,
+        iconClass: 'el-icon-loading'
+      })
+
+      const getHandler = (variable, id) => {
+        return () => {
+          const originalCluster = this.getThemeCluster(ORIGINAL_THEME.replace('#', ''))
+          const newStyle = this.updateStyle(this[variable], originalCluster, themeCluster)
+
+          let styleTag = document.getElementById(id)
+          if (!styleTag) {
+            styleTag = document.createElement('style')
+            styleTag.setAttribute('id', id)
+            document.head.appendChild(styleTag)
+          }
+          styleTag.innerText = newStyle
+        }
+      }
+
+      if (!this.chalk) {
+        const url = `https://unpkg.com/element-ui@${version}/lib/theme-chalk/index.css`
+        await this.getCSSString(url, 'chalk')
+      }
+
+      const chalkHandler = getHandler('chalk', 'chalk-style')
+
+      chalkHandler()
+
+      const styles = [].slice.call(document.querySelectorAll('style'))
+        .filter(style => {
+          const text = style.innerText
+          return new RegExp(oldVal, 'i').test(text) && !/Chalk Variables/.test(text)
+        })
+      styles.forEach(style => {
+        const { innerText } = style
+        if (typeof innerText !== 'string') return
+        style.innerText = this.updateStyle(innerText, originalCluster, themeCluster)
+      })
+
+      this.$emit('change', val)
+
+      $message.close()
+    }
+  },
+
+  methods: {
+    updateStyle(style, oldCluster, newCluster) {
+      let newStyle = style
+      oldCluster.forEach((color, index) => {
+        newStyle = newStyle.replace(new RegExp(color, 'ig'), newCluster[index])
+      })
+      return newStyle
+    },
+
+    getCSSString(url, variable) {
+      return new Promise(resolve => {
+        const xhr = new XMLHttpRequest()
+        xhr.onreadystatechange = () => {
+          if (xhr.readyState === 4 && xhr.status === 200) {
+            this[variable] = xhr.responseText.replace(/@font-face{[^}]+}/, '')
+            resolve()
+          }
+        }
+        xhr.open('GET', url)
+        xhr.send()
+      })
+    },
+
+    getThemeCluster(theme) {
+      const tintColor = (color, tint) => {
+        let red = parseInt(color.slice(0, 2), 16)
+        let green = parseInt(color.slice(2, 4), 16)
+        let blue = parseInt(color.slice(4, 6), 16)
+
+        if (tint === 0) { // when primary color is in its rgb space
+          return [red, green, blue].join(',')
+        } else {
+          red += Math.round(tint * (255 - red))
+          green += Math.round(tint * (255 - green))
+          blue += Math.round(tint * (255 - blue))
+
+          red = red.toString(16)
+          green = green.toString(16)
+          blue = blue.toString(16)
+
+          return `#${red}${green}${blue}`
+        }
+      }
+
+      const shadeColor = (color, shade) => {
+        let red = parseInt(color.slice(0, 2), 16)
+        let green = parseInt(color.slice(2, 4), 16)
+        let blue = parseInt(color.slice(4, 6), 16)
+
+        red = Math.round((1 - shade) * red)
+        green = Math.round((1 - shade) * green)
+        blue = Math.round((1 - shade) * blue)
+
+        red = red.toString(16)
+        green = green.toString(16)
+        blue = blue.toString(16)
+
+        return `#${red}${green}${blue}`
+      }
+
+      const clusters = [theme]
+      for (let i = 0; i <= 9; i++) {
+        clusters.push(tintColor(theme, Number((i / 10).toFixed(2))))
+      }
+      clusters.push(shadeColor(theme, 0.1))
+      return clusters
+    }
+  }
+}
+</script>
+
+<style>
+.theme-message,
+.theme-picker-dropdown {
+  z-index: 99999 !important;
+}
+
+.theme-picker .el-color-picker__trigger {
+  height: 26px !important;
+  width: 26px !important;
+  padding: 2px;
+}
+
+.theme-picker-dropdown .el-color-dropdown__link-btn {
+  display: none;
+}
+</style>
diff --git a/frontend/src/components/Tinymce/components/EditorImage.vue b/frontend/src/components/Tinymce/components/EditorImage.vue
new file mode 100644
index 0000000..07d48e6
--- /dev/null
+++ b/frontend/src/components/Tinymce/components/EditorImage.vue
@@ -0,0 +1,111 @@
+<template>
+  <div class="upload-container">
+    <el-button :style="{background:color,borderColor:color}" icon="el-icon-upload" size="mini" type="primary" @click=" dialogVisible=true">
+      upload
+    </el-button>
+    <el-dialog :visible.sync="dialogVisible">
+      <el-upload
+        :multiple="true"
+        :file-list="fileList"
+        :show-file-list="true"
+        :on-remove="handleRemove"
+        :on-success="handleSuccess"
+        :before-upload="beforeUpload"
+        class="editor-slide-upload"
+        action="https://httpbin.org/post"
+        list-type="picture-card"
+      >
+        <el-button size="small" type="primary">
+          Click upload
+        </el-button>
+      </el-upload>
+      <el-button @click="dialogVisible = false">
+        Cancel
+      </el-button>
+      <el-button type="primary" @click="handleSubmit">
+        Confirm
+      </el-button>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+// import { getToken } from 'api/qiniu'
+
+export default {
+  name: 'EditorSlideUpload',
+  props: {
+    color: {
+      type: String,
+      default: '#1890ff'
+    }
+  },
+  data() {
+    return {
+      dialogVisible: false,
+      listObj: {},
+      fileList: []
+    }
+  },
+  methods: {
+    checkAllSuccess() {
+      return Object.keys(this.listObj).every(item => this.listObj[item].hasSuccess)
+    },
+    handleSubmit() {
+      const arr = Object.keys(this.listObj).map(v => this.listObj[v])
+      if (!this.checkAllSuccess()) {
+        this.$message('Please wait for all images to be uploaded successfully. If there is a network problem, please refresh the page and upload again!')
+        return
+      }
+      this.$emit('successCBK', arr)
+      this.listObj = {}
+      this.fileList = []
+      this.dialogVisible = false
+    },
+    handleSuccess(response, file) {
+      const uid = file.uid
+      const objKeyArr = Object.keys(this.listObj)
+      for (let i = 0, len = objKeyArr.length; i < len; i++) {
+        if (this.listObj[objKeyArr[i]].uid === uid) {
+          this.listObj[objKeyArr[i]].url = response.files.file
+          this.listObj[objKeyArr[i]].hasSuccess = true
+          return
+        }
+      }
+    },
+    handleRemove(file) {
+      const uid = file.uid
+      const objKeyArr = Object.keys(this.listObj)
+      for (let i = 0, len = objKeyArr.length; i < len; i++) {
+        if (this.listObj[objKeyArr[i]].uid === uid) {
+          delete this.listObj[objKeyArr[i]]
+          return
+        }
+      }
+    },
+    beforeUpload(file) {
+      const _self = this
+      const _URL = window.URL || window.webkitURL
+      const fileName = file.uid
+      this.listObj[fileName] = {}
+      return new Promise((resolve, reject) => {
+        const img = new Image()
+        img.src = _URL.createObjectURL(file)
+        img.onload = function() {
+          _self.listObj[fileName] = { hasSuccess: false, uid: file.uid, width: this.width, height: this.height }
+        }
+        resolve(true)
+      })
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.editor-slide-upload {
+  margin-bottom: 20px;
+  ::v-deep .el-upload--picture-card {
+    width: 100%;
+  }
+}
+</style>
diff --git a/frontend/src/components/Tinymce/dynamicLoadScript.js b/frontend/src/components/Tinymce/dynamicLoadScript.js
new file mode 100644
index 0000000..185f58d
--- /dev/null
+++ b/frontend/src/components/Tinymce/dynamicLoadScript.js
@@ -0,0 +1,59 @@
+let callbacks = []
+
+function loadedTinymce() {
+  // to fixed https://github.com/PanJiaChen/vue-element-admin/issues/2144
+  // check is successfully downloaded script
+  return window.tinymce
+}
+
+const dynamicLoadScript = (src, callback) => {
+  const existingScript = document.getElementById(src)
+  const cb = callback || function() {}
+
+  if (!existingScript) {
+    const script = document.createElement('script')
+    script.src = src // src url for the third-party library being loaded.
+    script.id = src
+    document.body.appendChild(script)
+    callbacks.push(cb)
+    const onEnd = 'onload' in script ? stdOnEnd : ieOnEnd
+    onEnd(script)
+  }
+
+  if (existingScript && cb) {
+    if (loadedTinymce()) {
+      cb(null, existingScript)
+    } else {
+      callbacks.push(cb)
+    }
+  }
+
+  function stdOnEnd(script) {
+    script.onload = function() {
+      // this.onload = null here is necessary
+      // because even IE9 works not like others
+      this.onerror = this.onload = null
+      for (const cb of callbacks) {
+        cb(null, script)
+      }
+      callbacks = null
+    }
+    script.onerror = function() {
+      this.onerror = this.onload = null
+      cb(new Error('Failed to load ' + src), script)
+    }
+  }
+
+  function ieOnEnd(script) {
+    script.onreadystatechange = function() {
+      if (this.readyState !== 'complete' && this.readyState !== 'loaded') return
+      this.onreadystatechange = null
+      for (const cb of callbacks) {
+        cb(null, script) // there is no way to catch loading errors in IE8
+      }
+      callbacks = null
+    }
+  }
+}
+
+export default dynamicLoadScript
diff --git a/frontend/src/components/Tinymce/index.vue b/frontend/src/components/Tinymce/index.vue
new file mode 100644
index 0000000..cb6b91c
--- /dev/null
+++ b/frontend/src/components/Tinymce/index.vue
@@ -0,0 +1,247 @@
+<template>
+  <div :class="{fullscreen:fullscreen}" class="tinymce-container" :style="{width:containerWidth}">
+    <textarea :id="tinymceId" class="tinymce-textarea" />
+    <div class="editor-custom-btn-container">
+      <editorImage color="#1890ff" class="editor-upload-btn" @successCBK="imageSuccessCBK" />
+    </div>
+  </div>
+</template>
+
+<script>
+/**
+ * docs:
+ * https://panjiachen.github.io/vue-element-admin-site/feature/component/rich-editor.html#tinymce
+ */
+import editorImage from './components/EditorImage'
+import plugins from './plugins'
+import toolbar from './toolbar'
+import load from './dynamicLoadScript'
+
+// why use this cdn, detail see https://github.com/PanJiaChen/tinymce-all-in-one
+const tinymceCDN = 'https://cdn.jsdelivr.net/npm/tinymce-all-in-one@4.9.3/tinymce.min.js'
+
+export default {
+  name: 'Tinymce',
+  components: { editorImage },
+  props: {
+    id: {
+      type: String,
+      default: function() {
+        return 'vue-tinymce-' + +new Date() + ((Math.random() * 1000).toFixed(0) + '')
+      }
+    },
+    value: {
+      type: String,
+      default: ''
+    },
+    toolbar: {
+      type: Array,
+      required: false,
+      default() {
+        return []
+      }
+    },
+    menubar: {
+      type: String,
+      default: 'file edit insert view format table'
+    },
+    height: {
+      type: [Number, String],
+      required: false,
+      default: 360
+    },
+    width: {
+      type: [Number, String],
+      required: false,
+      default: 'auto'
+    }
+  },
+  data() {
+    return {
+      hasChange: false,
+      hasInit: false,
+      tinymceId: this.id,
+      fullscreen: false,
+      languageTypeList: {
+        'en': 'en',
+        'zh': 'zh_CN',
+        'es': 'es_MX',
+        'ja': 'ja'
+      }
+    }
+  },
+  computed: {
+    containerWidth() {
+      const width = this.width
+      if (/^[\d]+(\.[\d]+)?$/.test(width)) { // matches `100`, `'100'`
+        return `${width}px`
+      }
+      return width
+    }
+  },
+  watch: {
+    value(val) {
+      if (!this.hasChange && this.hasInit) {
+        this.$nextTick(() =>
+          window.tinymce.get(this.tinymceId).setContent(val || ''))
+      }
+    }
+  },
+  mounted() {
+    this.init()
+  },
+  activated() {
+    if (window.tinymce) {
+      this.initTinymce()
+    }
+  },
+  deactivated() {
+    this.destroyTinymce()
+  },
+  destroyed() {
+    this.destroyTinymce()
+  },
+  methods: {
+    init() {
+      // dynamic load tinymce from cdn
+      load(tinymceCDN, (err) => {
+        if (err) {
+          this.$message.error(err.message)
+          return
+        }
+        this.initTinymce()
+      })
+    },
+    initTinymce() {
+      const _this = this
+      window.tinymce.init({
+        selector: `#${this.tinymceId}`,
+        language: this.languageTypeList['en'],
+        height: this.height,
+        body_class: 'panel-body ',
+        object_resizing: false,
+        toolbar: this.toolbar.length > 0 ? this.toolbar : toolbar,
+        menubar: this.menubar,
+        plugins: plugins,
+        end_container_on_empty_block: true,
+        powerpaste_word_import: 'clean',
+        code_dialog_height: 450,
+        code_dialog_width: 1000,
+        advlist_bullet_styles: 'square',
+        advlist_number_styles: 'default',
+        imagetools_cors_hosts: ['www.tinymce.com', 'codepen.io'],
+        default_link_target: '_blank',
+        link_title: false,
+        nonbreaking_force_tab: true, // inserting nonbreaking space &nbsp; need Nonbreaking Space Plugin
+        init_instance_callback: editor => {
+          if (_this.value) {
+            editor.setContent(_this.value)
+          }
+          _this.hasInit = true
+          editor.on('NodeChange Change KeyUp SetContent', () => {
+            this.hasChange = true
+            this.$emit('input', editor.getContent())
+          })
+        },
+        setup(editor) {
+          editor.on('FullscreenStateChanged', (e) => {
+            _this.fullscreen = e.state
+          })
+        },
+        // it will try to keep these URLs intact
+        // https://www.tiny.cloud/docs-3x/reference/configuration/Configuration3x@convert_urls/
+        // https://stackoverflow.com/questions/5196205/disable-tinymce-absolute-to-relative-url-conversions
+        convert_urls: false
+        // 整合七牛上传
+        // images_dataimg_filter(img) {
+        //   setTimeout(() => {
+        //     const $image = $(img);
+        //     $image.removeAttr('width');
+        //     $image.removeAttr('height');
+        //     if ($image[0].height && $image[0].width) {
+        //       $image.attr('data-wscntype', 'image');
+        //       $image.attr('data-wscnh', $image[0].height);
+        //       $image.attr('data-wscnw', $image[0].width);
+        //       $image.addClass('wscnph');
+        //     }
+        //   }, 0);
+        //   return img
+        // },
+        // images_upload_handler(blobInfo, success, failure, progress) {
+        //   progress(0);
+        //   const token = _this.$store.getters.token;
+        //   getToken(token).then(response => {
+        //     const url = response.data.qiniu_url;
+        //     const formData = new FormData();
+        //     formData.append('token', response.data.qiniu_token);
+        //     formData.append('key', response.data.qiniu_key);
+        //     formData.append('file', blobInfo.blob(), url);
+        //     upload(formData).then(() => {
+        //       success(url);
+        //       progress(100);
+        //     })
+        //   }).catch(err => {
+        //     failure('出现未知问题,刷新页面,或者联系程序员')
+        //     console.log(err);
+        //   });
+        // },
+      })
+    },
+    destroyTinymce() {
+      const tinymce = window.tinymce.get(this.tinymceId)
+      if (this.fullscreen) {
+        tinymce.execCommand('mceFullScreen')
+      }
+
+      if (tinymce) {
+        tinymce.destroy()
+      }
+    },
+    setContent(value) {
+      window.tinymce.get(this.tinymceId).setContent(value)
+    },
+    getContent() {
+      window.tinymce.get(this.tinymceId).getContent()
+    },
+    imageSuccessCBK(arr) {
+      arr.forEach(v => window.tinymce.get(this.tinymceId).insertContent(`<img class="wscnph" src="${v.url}" >`))
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.tinymce-container {
+  position: relative;
+  line-height: normal;
+}
+
+.tinymce-container {
+  ::v-deep {
+    .mce-fullscreen {
+      z-index: 10000;
+    }
+  }
+}
+
+.tinymce-textarea {
+  visibility: hidden;
+  z-index: -1;
+}
+
+.editor-custom-btn-container {
+  position: absolute;
+  right: 4px;
+  top: 4px;
+  /*z-index: 2005;*/
+}
+
+.fullscreen .editor-custom-btn-container {
+  z-index: 10000;
+  position: fixed;
+}
+
+.editor-upload-btn {
+  display: inline-block;
+}
+</style>
diff --git a/frontend/src/components/Tinymce/plugins.js b/frontend/src/components/Tinymce/plugins.js
new file mode 100644
index 0000000..058d2ae
--- /dev/null
+++ b/frontend/src/components/Tinymce/plugins.js
@@ -0,0 +1,7 @@
+// Any plugins you want to use has to be imported
+// Detail plugins list see https://www.tinymce.com/docs/plugins/
+// Custom builds see https://www.tinymce.com/download/custom-builds/
+
+const plugins = ['advlist anchor autolink autosave code codesample colorpicker colorpicker contextmenu directionality emoticons fullscreen hr image imagetools insertdatetime link lists media nonbreaking noneditable pagebreak paste preview print save searchreplace spellchecker tabfocus table template textcolor textpattern visualblocks visualchars wordcount']
+
+export default plugins
diff --git a/frontend/src/components/Tinymce/toolbar.js b/frontend/src/components/Tinymce/toolbar.js
new file mode 100644
index 0000000..4f8a545
--- /dev/null
+++ b/frontend/src/components/Tinymce/toolbar.js
@@ -0,0 +1,6 @@
+// Here is a list of the toolbar
+// Detail list see https://www.tinymce.com/docs/advanced/editor-control-identifiers/#toolbarcontrols
+
+const toolbar = ['searchreplace bold italic underline strikethrough alignleft aligncenter alignright outdent indent  blockquote undo redo removeformat subscript superscript code codesample', 'hr bullist numlist link image charmap preview anchor pagebreak insertdatetime media table emoticons forecolor backcolor fullscreen']
+
+export default toolbar
diff --git a/frontend/src/components/Upload/SingleImage.vue b/frontend/src/components/Upload/SingleImage.vue
new file mode 100644
index 0000000..d16bbf3
--- /dev/null
+++ b/frontend/src/components/Upload/SingleImage.vue
@@ -0,0 +1,134 @@
+<template>
+  <div class="upload-container">
+    <el-upload
+      :data="dataObj"
+      :multiple="false"
+      :show-file-list="false"
+      :on-success="handleImageSuccess"
+      class="image-uploader"
+      drag
+      action="https://httpbin.org/post"
+    >
+      <i class="el-icon-upload" />
+      <div class="el-upload__text">
+        将文件拖到此处,或<em>点击上传</em>
+      </div>
+    </el-upload>
+    <div class="image-preview">
+      <div v-show="imageUrl.length>1" class="image-preview-wrapper">
+        <img :src="imageUrl+'?imageView2/1/w/200/h/200'">
+        <div class="image-preview-action">
+          <i class="el-icon-delete" @click="rmImage" />
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import { getToken } from '@/api/qiniu'
+
+export default {
+  name: 'SingleImageUpload',
+  props: {
+    value: {
+      type: String,
+      default: ''
+    }
+  },
+  data() {
+    return {
+      tempUrl: '',
+      dataObj: { token: '', key: '' }
+    }
+  },
+  computed: {
+    imageUrl() {
+      return this.value
+    }
+  },
+  methods: {
+    rmImage() {
+      this.emitInput('')
+    },
+    emitInput(val) {
+      this.$emit('input', val)
+    },
+    handleImageSuccess() {
+      this.emitInput(this.tempUrl)
+    },
+    beforeUpload() {
+      const _self = this
+      return new Promise((resolve, reject) => {
+        getToken().then(response => {
+          const key = response.data.qiniu_key
+          const token = response.data.qiniu_token
+          _self._data.dataObj.token = token
+          _self._data.dataObj.key = key
+          this.tempUrl = response.data.qiniu_url
+          resolve(true)
+        }).catch(err => {
+          console.log(err)
+          reject(false)
+        })
+      })
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+    @import "~@/styles/mixin.scss";
+    .upload-container {
+        width: 100%;
+        position: relative;
+        @include clearfix;
+        .image-uploader {
+            width: 60%;
+            float: left;
+        }
+        .image-preview {
+            width: 200px;
+            height: 200px;
+            position: relative;
+            border: 1px dashed #d9d9d9;
+            float: left;
+            margin-left: 50px;
+            .image-preview-wrapper {
+                position: relative;
+                width: 100%;
+                height: 100%;
+                img {
+                    width: 100%;
+                    height: 100%;
+                }
+            }
+            .image-preview-action {
+                position: absolute;
+                width: 100%;
+                height: 100%;
+                left: 0;
+                top: 0;
+                cursor: default;
+                text-align: center;
+                color: #fff;
+                opacity: 0;
+                font-size: 20px;
+                background-color: rgba(0, 0, 0, .5);
+                transition: opacity .3s;
+                cursor: pointer;
+                text-align: center;
+                line-height: 200px;
+                .el-icon-delete {
+                    font-size: 36px;
+                }
+            }
+            &:hover {
+                .image-preview-action {
+                    opacity: 1;
+                }
+            }
+        }
+    }
+
+</style>
diff --git a/frontend/src/components/Upload/SingleImage2.vue b/frontend/src/components/Upload/SingleImage2.vue
new file mode 100644
index 0000000..07637a9
--- /dev/null
+++ b/frontend/src/components/Upload/SingleImage2.vue
@@ -0,0 +1,130 @@
+<template>
+  <div class="singleImageUpload2 upload-container">
+    <el-upload
+      :data="dataObj"
+      :multiple="false"
+      :show-file-list="false"
+      :on-success="handleImageSuccess"
+      class="image-uploader"
+      drag
+      action="https://httpbin.org/post"
+    >
+      <i class="el-icon-upload" />
+      <div class="el-upload__text">
+        Drag或<em>点击上传</em>
+      </div>
+    </el-upload>
+    <div v-show="imageUrl.length>0" class="image-preview">
+      <div v-show="imageUrl.length>1" class="image-preview-wrapper">
+        <img :src="imageUrl">
+        <div class="image-preview-action">
+          <i class="el-icon-delete" @click="rmImage" />
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import { getToken } from '@/api/qiniu'
+
+export default {
+  name: 'SingleImageUpload2',
+  props: {
+    value: {
+      type: String,
+      default: ''
+    }
+  },
+  data() {
+    return {
+      tempUrl: '',
+      dataObj: { token: '', key: '' }
+    }
+  },
+  computed: {
+    imageUrl() {
+      return this.value
+    }
+  },
+  methods: {
+    rmImage() {
+      this.emitInput('')
+    },
+    emitInput(val) {
+      this.$emit('input', val)
+    },
+    handleImageSuccess() {
+      this.emitInput(this.tempUrl)
+    },
+    beforeUpload() {
+      const _self = this
+      return new Promise((resolve, reject) => {
+        getToken().then(response => {
+          const key = response.data.qiniu_key
+          const token = response.data.qiniu_token
+          _self._data.dataObj.token = token
+          _self._data.dataObj.key = key
+          this.tempUrl = response.data.qiniu_url
+          resolve(true)
+        }).catch(() => {
+          reject(false)
+        })
+      })
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.upload-container {
+  width: 100%;
+  height: 100%;
+  position: relative;
+  .image-uploader {
+    height: 100%;
+  }
+  .image-preview {
+    width: 100%;
+    height: 100%;
+    position: absolute;
+    left: 0px;
+    top: 0px;
+    border: 1px dashed #d9d9d9;
+    .image-preview-wrapper {
+      position: relative;
+      width: 100%;
+      height: 100%;
+      img {
+        width: 100%;
+        height: 100%;
+      }
+    }
+    .image-preview-action {
+      position: absolute;
+      width: 100%;
+      height: 100%;
+      left: 0;
+      top: 0;
+      cursor: default;
+      text-align: center;
+      color: #fff;
+      opacity: 0;
+      font-size: 20px;
+      background-color: rgba(0, 0, 0, .5);
+      transition: opacity .3s;
+      cursor: pointer;
+      text-align: center;
+      line-height: 200px;
+      .el-icon-delete {
+        font-size: 36px;
+      }
+    }
+    &:hover {
+      .image-preview-action {
+        opacity: 1;
+      }
+    }
+  }
+}
+</style>
diff --git a/frontend/src/components/Upload/SingleImage3.vue b/frontend/src/components/Upload/SingleImage3.vue
new file mode 100644
index 0000000..6300da4
--- /dev/null
+++ b/frontend/src/components/Upload/SingleImage3.vue
@@ -0,0 +1,157 @@
+<template>
+  <div class="upload-container">
+    <el-upload
+      :data="dataObj"
+      :multiple="false"
+      :show-file-list="false"
+      :on-success="handleImageSuccess"
+      class="image-uploader"
+      drag
+      action="https://httpbin.org/post"
+    >
+      <i class="el-icon-upload" />
+      <div class="el-upload__text">
+        将文件拖到此处,或<em>点击上传</em>
+      </div>
+    </el-upload>
+    <div class="image-preview image-app-preview">
+      <div v-show="imageUrl.length>1" class="image-preview-wrapper">
+        <img :src="imageUrl">
+        <div class="image-preview-action">
+          <i class="el-icon-delete" @click="rmImage" />
+        </div>
+      </div>
+    </div>
+    <div class="image-preview">
+      <div v-show="imageUrl.length>1" class="image-preview-wrapper">
+        <img :src="imageUrl">
+        <div class="image-preview-action">
+          <i class="el-icon-delete" @click="rmImage" />
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import { getToken } from '@/api/qiniu'
+
+export default {
+  name: 'SingleImageUpload3',
+  props: {
+    value: {
+      type: String,
+      default: ''
+    }
+  },
+  data() {
+    return {
+      tempUrl: '',
+      dataObj: { token: '', key: '' }
+    }
+  },
+  computed: {
+    imageUrl() {
+      return this.value
+    }
+  },
+  methods: {
+    rmImage() {
+      this.emitInput('')
+    },
+    emitInput(val) {
+      this.$emit('input', val)
+    },
+    handleImageSuccess(file) {
+      this.emitInput(file.files.file)
+    },
+    beforeUpload() {
+      const _self = this
+      return new Promise((resolve, reject) => {
+        getToken().then(response => {
+          const key = response.data.qiniu_key
+          const token = response.data.qiniu_token
+          _self._data.dataObj.token = token
+          _self._data.dataObj.key = key
+          this.tempUrl = response.data.qiniu_url
+          resolve(true)
+        }).catch(err => {
+          console.log(err)
+          reject(false)
+        })
+      })
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+@import "~@/styles/mixin.scss";
+.upload-container {
+  width: 100%;
+  position: relative;
+  @include clearfix;
+  .image-uploader {
+    width: 35%;
+    float: left;
+  }
+  .image-preview {
+    width: 200px;
+    height: 200px;
+    position: relative;
+    border: 1px dashed #d9d9d9;
+    float: left;
+    margin-left: 50px;
+    .image-preview-wrapper {
+      position: relative;
+      width: 100%;
+      height: 100%;
+      img {
+        width: 100%;
+        height: 100%;
+      }
+    }
+    .image-preview-action {
+      position: absolute;
+      width: 100%;
+      height: 100%;
+      left: 0;
+      top: 0;
+      cursor: default;
+      text-align: center;
+      color: #fff;
+      opacity: 0;
+      font-size: 20px;
+      background-color: rgba(0, 0, 0, .5);
+      transition: opacity .3s;
+      cursor: pointer;
+      text-align: center;
+      line-height: 200px;
+      .el-icon-delete {
+        font-size: 36px;
+      }
+    }
+    &:hover {
+      .image-preview-action {
+        opacity: 1;
+      }
+    }
+  }
+  .image-app-preview {
+    width: 320px;
+    height: 180px;
+    position: relative;
+    border: 1px dashed #d9d9d9;
+    float: left;
+    margin-left: 50px;
+    .app-fake-conver {
+      height: 44px;
+      position: absolute;
+      width: 100%; // background: rgba(0, 0, 0, .1);
+      text-align: center;
+      line-height: 64px;
+      color: #fff;
+    }
+  }
+}
+</style>
diff --git a/frontend/src/components/UploadExcel/index.vue b/frontend/src/components/UploadExcel/index.vue
new file mode 100644
index 0000000..9e8ba8b
--- /dev/null
+++ b/frontend/src/components/UploadExcel/index.vue
@@ -0,0 +1,138 @@
+<template>
+  <div>
+    <input ref="excel-upload-input" class="excel-upload-input" type="file" accept=".xlsx, .xls" @change="handleClick">
+    <div class="drop" @drop="handleDrop" @dragover="handleDragover" @dragenter="handleDragover">
+      Drop excel file here or
+      <el-button :loading="loading" style="margin-left:16px;" size="mini" type="primary" @click="handleUpload">
+        Browse
+      </el-button>
+    </div>
+  </div>
+</template>
+
+<script>
+import XLSX from 'xlsx'
+
+export default {
+  props: {
+    beforeUpload: Function, // eslint-disable-line
+    onSuccess: Function// eslint-disable-line
+  },
+  data() {
+    return {
+      loading: false,
+      excelData: {
+        header: null,
+        results: null
+      }
+    }
+  },
+  methods: {
+    generateData({ header, results }) {
+      this.excelData.header = header
+      this.excelData.results = results
+      this.onSuccess && this.onSuccess(this.excelData)
+    },
+    handleDrop(e) {
+      e.stopPropagation()
+      e.preventDefault()
+      if (this.loading) return
+      const files = e.dataTransfer.files
+      if (files.length !== 1) {
+        this.$message.error('Only support uploading one file!')
+        return
+      }
+      const rawFile = files[0] // only use files[0]
+
+      if (!this.isExcel(rawFile)) {
+        this.$message.error('Only supports upload .xlsx, .xls, .csv suffix files')
+        return false
+      }
+      this.upload(rawFile)
+      e.stopPropagation()
+      e.preventDefault()
+    },
+    handleDragover(e) {
+      e.stopPropagation()
+      e.preventDefault()
+      e.dataTransfer.dropEffect = 'copy'
+    },
+    handleUpload() {
+      this.$refs['excel-upload-input'].click()
+    },
+    handleClick(e) {
+      const files = e.target.files
+      const rawFile = files[0] // only use files[0]
+      if (!rawFile) return
+      this.upload(rawFile)
+    },
+    upload(rawFile) {
+      this.$refs['excel-upload-input'].value = null // fix can't select the same excel
+
+      if (!this.beforeUpload) {
+        this.readerData(rawFile)
+        return
+      }
+      const before = this.beforeUpload(rawFile)
+      if (before) {
+        this.readerData(rawFile)
+      }
+    },
+    readerData(rawFile) {
+      this.loading = true
+      return new Promise((resolve, reject) => {
+        const reader = new FileReader()
+        reader.onload = e => {
+          const data = e.target.result
+          const workbook = XLSX.read(data, { type: 'array' })
+          const firstSheetName = workbook.SheetNames[0]
+          const worksheet = workbook.Sheets[firstSheetName]
+          const header = this.getHeaderRow(worksheet)
+          const results = XLSX.utils.sheet_to_json(worksheet)
+          this.generateData({ header, results })
+          this.loading = false
+          resolve()
+        }
+        reader.readAsArrayBuffer(rawFile)
+      })
+    },
+    getHeaderRow(sheet) {
+      const headers = []
+      const range = XLSX.utils.decode_range(sheet['!ref'])
+      let C
+      const R = range.s.r
+      /* start in the first row */
+      for (C = range.s.c; C <= range.e.c; ++C) { /* walk every column in the range */
+        const cell = sheet[XLSX.utils.encode_cell({ c: C, r: R })]
+        /* find the cell in the first row */
+        let hdr = 'UNKNOWN ' + C // <-- replace with your desired default
+        if (cell && cell.t) hdr = XLSX.utils.format_cell(cell)
+        headers.push(hdr)
+      }
+      return headers
+    },
+    isExcel(file) {
+      return /\.(xlsx|xls|csv)$/.test(file.name)
+    }
+  }
+}
+</script>
+
+<style scoped>
+.excel-upload-input{
+  display: none;
+  z-index: -9999;
+}
+.drop{
+  border: 2px dashed #bbb;
+  width: 600px;
+  height: 160px;
+  line-height: 160px;
+  margin: 0 auto;
+  font-size: 24px;
+  border-radius: 5px;
+  text-align: center;
+  color: #bbb;
+  position: relative;
+}
+</style>
diff --git a/frontend/src/directive/clipboard/clipboard.js b/frontend/src/directive/clipboard/clipboard.js
new file mode 100644
index 0000000..514aad2
--- /dev/null
+++ b/frontend/src/directive/clipboard/clipboard.js
@@ -0,0 +1,49 @@
+// Inspired by https://github.com/Inndy/vue-clipboard2
+const Clipboard = require('clipboard')
+if (!Clipboard) {
+  throw new Error('you should npm install `clipboard` --save at first ')
+}
+
+export default {
+  bind(el, binding) {
+    if (binding.arg === 'success') {
+      el._v_clipboard_success = binding.value
+    } else if (binding.arg === 'error') {
+      el._v_clipboard_error = binding.value
+    } else {
+      const clipboard = new Clipboard(el, {
+        text() { return binding.value },
+        action() { return binding.arg === 'cut' ? 'cut' : 'copy' }
+      })
+      clipboard.on('success', e => {
+        const callback = el._v_clipboard_success
+        callback && callback(e) // eslint-disable-line
+      })
+      clipboard.on('error', e => {
+        const callback = el._v_clipboard_error
+        callback && callback(e) // eslint-disable-line
+      })
+      el._v_clipboard = clipboard
+    }
+  },
+  update(el, binding) {
+    if (binding.arg === 'success') {
+      el._v_clipboard_success = binding.value
+    } else if (binding.arg === 'error') {
+      el._v_clipboard_error = binding.value
+    } else {
+      el._v_clipboard.text = function() { return binding.value }
+      el._v_clipboard.action = function() { return binding.arg === 'cut' ? 'cut' : 'copy' }
+    }
+  },
+  unbind(el, binding) {
+    if (binding.arg === 'success') {
+      delete el._v_clipboard_success
+    } else if (binding.arg === 'error') {
+      delete el._v_clipboard_error
+    } else {
+      el._v_clipboard.destroy()
+      delete el._v_clipboard
+    }
+  }
+}
diff --git a/frontend/src/directive/clipboard/index.js b/frontend/src/directive/clipboard/index.js
new file mode 100644
index 0000000..02c9816
--- /dev/null
+++ b/frontend/src/directive/clipboard/index.js
@@ -0,0 +1,13 @@
+import Clipboard from './clipboard'
+
+const install = function(Vue) {
+  Vue.directive('Clipboard', Clipboard)
+}
+
+if (window.Vue) {
+  window.clipboard = Clipboard
+  Vue.use(install); // eslint-disable-line
+}
+
+Clipboard.install = install
+export default Clipboard
diff --git a/frontend/src/directive/el-drag-dialog/drag.js b/frontend/src/directive/el-drag-dialog/drag.js
new file mode 100644
index 0000000..299e985
--- /dev/null
+++ b/frontend/src/directive/el-drag-dialog/drag.js
@@ -0,0 +1,77 @@
+export default {
+  bind(el, binding, vnode) {
+    const dialogHeaderEl = el.querySelector('.el-dialog__header')
+    const dragDom = el.querySelector('.el-dialog')
+    dialogHeaderEl.style.cssText += ';cursor:move;'
+    dragDom.style.cssText += ';top:0px;'
+
+    // 获取原有属性 ie dom元素.currentStyle 火狐谷歌 window.getComputedStyle(dom元素, null);
+    const getStyle = (function() {
+      if (window.document.currentStyle) {
+        return (dom, attr) => dom.currentStyle[attr]
+      } else {
+        return (dom, attr) => getComputedStyle(dom, false)[attr]
+      }
+    })()
+
+    dialogHeaderEl.onmousedown = (e) => {
+      // 鼠标按下,计算当前元素距离可视区的距离
+      const disX = e.clientX - dialogHeaderEl.offsetLeft
+      const disY = e.clientY - dialogHeaderEl.offsetTop
+
+      const dragDomWidth = dragDom.offsetWidth
+      const dragDomHeight = dragDom.offsetHeight
+
+      const screenWidth = document.body.clientWidth
+      const screenHeight = document.body.clientHeight
+
+      const minDragDomLeft = dragDom.offsetLeft
+      const maxDragDomLeft = screenWidth - dragDom.offsetLeft - dragDomWidth
+
+      const minDragDomTop = dragDom.offsetTop
+      const maxDragDomTop = screenHeight - dragDom.offsetTop - dragDomHeight
+
+      // 获取到的值带px 正则匹配替换
+      let styL = getStyle(dragDom, 'left')
+      let styT = getStyle(dragDom, 'top')
+
+      if (styL.includes('%')) {
+        styL = +document.body.clientWidth * (+styL.replace(/\%/g, '') / 100)
+        styT = +document.body.clientHeight * (+styT.replace(/\%/g, '') / 100)
+      } else {
+        styL = +styL.replace(/\px/g, '')
+        styT = +styT.replace(/\px/g, '')
+      }
+
+      document.onmousemove = function(e) {
+        // 通过事件委托,计算移动的距离
+        let left = e.clientX - disX
+        let top = e.clientY - disY
+
+        // 边界处理
+        if (-(left) > minDragDomLeft) {
+          left = -minDragDomLeft
+        } else if (left > maxDragDomLeft) {
+          left = maxDragDomLeft
+        }
+
+        if (-(top) > minDragDomTop) {
+          top = -minDragDomTop
+        } else if (top > maxDragDomTop) {
+          top = maxDragDomTop
+        }
+
+        // 移动当前元素
+        dragDom.style.cssText += `;left:${left + styL}px;top:${top + styT}px;`
+
+        // emit onDrag event
+        vnode.child.$emit('dragDialog')
+      }
+
+      document.onmouseup = function(e) {
+        document.onmousemove = null
+        document.onmouseup = null
+      }
+    }
+  }
+}
diff --git a/frontend/src/directive/el-drag-dialog/index.js b/frontend/src/directive/el-drag-dialog/index.js
new file mode 100644
index 0000000..29facbf
--- /dev/null
+++ b/frontend/src/directive/el-drag-dialog/index.js
@@ -0,0 +1,13 @@
+import drag from './drag'
+
+const install = function(Vue) {
+  Vue.directive('el-drag-dialog', drag)
+}
+
+if (window.Vue) {
+  window['el-drag-dialog'] = drag
+  Vue.use(install); // eslint-disable-line
+}
+
+drag.install = install
+export default drag
diff --git a/frontend/src/directive/el-table/adaptive.js b/frontend/src/directive/el-table/adaptive.js
new file mode 100644
index 0000000..d229e9f
--- /dev/null
+++ b/frontend/src/directive/el-table/adaptive.js
@@ -0,0 +1,41 @@
+import { addResizeListener, removeResizeListener } from 'element-ui/src/utils/resize-event'
+
+/**
+ * How to use
+ * <el-table height="100px" v-el-height-adaptive-table="{bottomOffset: 30}">...</el-table>
+ * el-table height is must be set
+ * bottomOffset: 30(default)   // The height of the table from the bottom of the page.
+ */
+
+const doResize = (el, binding, vnode) => {
+  const { componentInstance: $table } = vnode
+
+  const { value } = binding
+
+  if (!$table.height) {
+    throw new Error(`el-$table must set the height. Such as height='100px'`)
+  }
+  const bottomOffset = (value && value.bottomOffset) || 30
+
+  if (!$table) return
+
+  const height = window.innerHeight - el.getBoundingClientRect().top - bottomOffset
+  $table.layout.setHeight(height)
+  $table.doLayout()
+}
+
+export default {
+  bind(el, binding, vnode) {
+    el.resizeListener = () => {
+      doResize(el, binding, vnode)
+    }
+    // parameter 1 is must be "Element" type
+    addResizeListener(window.document.body, el.resizeListener)
+  },
+  inserted(el, binding, vnode) {
+    doResize(el, binding, vnode)
+  },
+  unbind(el) {
+    removeResizeListener(window.document.body, el.resizeListener)
+  }
+}
diff --git a/frontend/src/directive/el-table/index.js b/frontend/src/directive/el-table/index.js
new file mode 100644
index 0000000..d3d4515
--- /dev/null
+++ b/frontend/src/directive/el-table/index.js
@@ -0,0 +1,13 @@
+import adaptive from './adaptive'
+
+const install = function(Vue) {
+  Vue.directive('el-height-adaptive-table', adaptive)
+}
+
+if (window.Vue) {
+  window['el-height-adaptive-table'] = adaptive
+  Vue.use(install); // eslint-disable-line
+}
+
+adaptive.install = install
+export default adaptive
diff --git a/frontend/src/directive/permission/index.js b/frontend/src/directive/permission/index.js
new file mode 100644
index 0000000..e5dadd3
--- /dev/null
+++ b/frontend/src/directive/permission/index.js
@@ -0,0 +1,13 @@
+import permission from './permission'
+
+const install = function(Vue) {
+  Vue.directive('permission', permission)
+}
+
+if (window.Vue) {
+  window['permission'] = permission
+  Vue.use(install); // eslint-disable-line
+}
+
+permission.install = install
+export default permission
diff --git a/frontend/src/directive/permission/permission.js b/frontend/src/directive/permission/permission.js
new file mode 100644
index 0000000..49d1f88
--- /dev/null
+++ b/frontend/src/directive/permission/permission.js
@@ -0,0 +1,31 @@
+import store from '@/store'
+
+function checkPermission(el, binding) {
+  const { value } = binding
+  const roles = store.getters && store.getters.roles
+
+  if (value && value instanceof Array) {
+    if (value.length > 0) {
+      const permissionRoles = value
+
+      const hasPermission = roles.some(role => {
+        return permissionRoles.includes(role)
+      })
+
+      if (!hasPermission) {
+        el.parentNode && el.parentNode.removeChild(el)
+      }
+    }
+  } else {
+    throw new Error(`need roles! Like v-permission="['admin','editor']"`)
+  }
+}
+
+export default {
+  inserted(el, binding) {
+    checkPermission(el, binding)
+  },
+  update(el, binding) {
+    checkPermission(el, binding)
+  }
+}
diff --git a/frontend/src/directive/sticky.js b/frontend/src/directive/sticky.js
new file mode 100644
index 0000000..bc23466
--- /dev/null
+++ b/frontend/src/directive/sticky.js
@@ -0,0 +1,91 @@
+const vueSticky = {}
+let listenAction
+vueSticky.install = Vue => {
+  Vue.directive('sticky', {
+    inserted(el, binding) {
+      const params = binding.value || {}
+      const stickyTop = params.stickyTop || 0
+      const zIndex = params.zIndex || 1000
+      const elStyle = el.style
+
+      elStyle.position = '-webkit-sticky'
+      elStyle.position = 'sticky'
+      // if the browser support css sticky(Currently Safari, Firefox and Chrome Canary)
+      // if (~elStyle.position.indexOf('sticky')) {
+      //     elStyle.top = `${stickyTop}px`;
+      //     elStyle.zIndex = zIndex;
+      //     return
+      // }
+      const elHeight = el.getBoundingClientRect().height
+      const elWidth = el.getBoundingClientRect().width
+      elStyle.cssText = `top: ${stickyTop}px; z-index: ${zIndex}`
+
+      const parentElm = el.parentNode || document.documentElement
+      const placeholder = document.createElement('div')
+      placeholder.style.display = 'none'
+      placeholder.style.width = `${elWidth}px`
+      placeholder.style.height = `${elHeight}px`
+      parentElm.insertBefore(placeholder, el)
+
+      let active = false
+
+      const getScroll = (target, top) => {
+        const prop = top ? 'pageYOffset' : 'pageXOffset'
+        const method = top ? 'scrollTop' : 'scrollLeft'
+        let ret = target[prop]
+        if (typeof ret !== 'number') {
+          ret = window.document.documentElement[method]
+        }
+        return ret
+      }
+
+      const sticky = () => {
+        if (active) {
+          return
+        }
+        if (!elStyle.height) {
+          elStyle.height = `${el.offsetHeight}px`
+        }
+
+        elStyle.position = 'fixed'
+        elStyle.width = `${elWidth}px`
+        placeholder.style.display = 'inline-block'
+        active = true
+      }
+
+      const reset = () => {
+        if (!active) {
+          return
+        }
+
+        elStyle.position = ''
+        placeholder.style.display = 'none'
+        active = false
+      }
+
+      const check = () => {
+        const scrollTop = getScroll(window, true)
+        const offsetTop = el.getBoundingClientRect().top
+        if (offsetTop < stickyTop) {
+          sticky()
+        } else {
+          if (scrollTop < elHeight + stickyTop) {
+            reset()
+          }
+        }
+      }
+      listenAction = () => {
+        check()
+      }
+
+      window.addEventListener('scroll', listenAction)
+    },
+
+    unbind() {
+      window.removeEventListener('scroll', listenAction)
+    }
+  })
+}
+
+export default vueSticky
+
diff --git a/frontend/src/directive/waves/index.js b/frontend/src/directive/waves/index.js
new file mode 100644
index 0000000..65f9b30
--- /dev/null
+++ b/frontend/src/directive/waves/index.js
@@ -0,0 +1,13 @@
+import waves from './waves'
+
+const install = function(Vue) {
+  Vue.directive('waves', waves)
+}
+
+if (window.Vue) {
+  window.waves = waves
+  Vue.use(install); // eslint-disable-line
+}
+
+waves.install = install
+export default waves
diff --git a/frontend/src/directive/waves/waves.css b/frontend/src/directive/waves/waves.css
new file mode 100644
index 0000000..af7a7ef
--- /dev/null
+++ b/frontend/src/directive/waves/waves.css
@@ -0,0 +1,26 @@
+.waves-ripple {
+    position: absolute;
+    border-radius: 100%;
+    background-color: rgba(0, 0, 0, 0.15);
+    background-clip: padding-box;
+    pointer-events: none;
+    -webkit-user-select: none;
+    -moz-user-select: none;
+    -ms-user-select: none;
+    user-select: none;
+    -webkit-transform: scale(0);
+    -ms-transform: scale(0);
+    transform: scale(0);
+    opacity: 1;
+}
+
+.waves-ripple.z-active {
+    opacity: 0;
+    -webkit-transform: scale(2);
+    -ms-transform: scale(2);
+    transform: scale(2);
+    -webkit-transition: opacity 1.2s ease-out, -webkit-transform 0.6s ease-out;
+    transition: opacity 1.2s ease-out, -webkit-transform 0.6s ease-out;
+    transition: opacity 1.2s ease-out, transform 0.6s ease-out;
+    transition: opacity 1.2s ease-out, transform 0.6s ease-out, -webkit-transform 0.6s ease-out;
+}
\ No newline at end of file
diff --git a/frontend/src/directive/waves/waves.js b/frontend/src/directive/waves/waves.js
new file mode 100644
index 0000000..ec2ff43
--- /dev/null
+++ b/frontend/src/directive/waves/waves.js
@@ -0,0 +1,72 @@
+import './waves.css'
+
+const context = '@@wavesContext'
+
+function handleClick(el, binding) {
+  function handle(e) {
+    const customOpts = Object.assign({}, binding.value)
+    const opts = Object.assign({
+      ele: el, // 波纹作用元素
+      type: 'hit', // hit 点击位置扩散 center中心点扩展
+      color: 'rgba(0, 0, 0, 0.15)' // 波纹颜色
+    },
+    customOpts
+    )
+    const target = opts.ele
+    if (target) {
+      target.style.position = 'relative'
+      target.style.overflow = 'hidden'
+      const rect = target.getBoundingClientRect()
+      let ripple = target.querySelector('.waves-ripple')
+      if (!ripple) {
+        ripple = document.createElement('span')
+        ripple.className = 'waves-ripple'
+        ripple.style.height = ripple.style.width = Math.max(rect.width, rect.height) + 'px'
+        target.appendChild(ripple)
+      } else {
+        ripple.className = 'waves-ripple'
+      }
+      switch (opts.type) {
+        case 'center':
+          ripple.style.top = rect.height / 2 - ripple.offsetHeight / 2 + 'px'
+          ripple.style.left = rect.width / 2 - ripple.offsetWidth / 2 + 'px'
+          break
+        default:
+          ripple.style.top =
+            (e.pageY - rect.top - ripple.offsetHeight / 2 - document.documentElement.scrollTop ||
+              document.body.scrollTop) + 'px'
+          ripple.style.left =
+            (e.pageX - rect.left - ripple.offsetWidth / 2 - document.documentElement.scrollLeft ||
+              document.body.scrollLeft) + 'px'
+      }
+      ripple.style.backgroundColor = opts.color
+      ripple.className = 'waves-ripple z-active'
+      return false
+    }
+  }
+
+  if (!el[context]) {
+    el[context] = {
+      removeHandle: handle
+    }
+  } else {
+    el[context].removeHandle = handle
+  }
+
+  return handle
+}
+
+export default {
+  bind(el, binding) {
+    el.addEventListener('click', handleClick(el, binding), false)
+  },
+  update(el, binding) {
+    el.removeEventListener('click', el[context].removeHandle, false)
+    el.addEventListener('click', handleClick(el, binding), false)
+  },
+  unbind(el) {
+    el.removeEventListener('click', el[context].removeHandle, false)
+    el[context] = null
+    delete el[context]
+  }
+}
diff --git a/frontend/src/filters/index.js b/frontend/src/filters/index.js
new file mode 100644
index 0000000..9822233
--- /dev/null
+++ b/frontend/src/filters/index.js
@@ -0,0 +1,68 @@
+// import parseTime, formatTime and set to filter
+export { parseTime, formatTime } from '@/utils'
+
+/**
+ * Show plural label if time is plural number
+ * @param {number} time
+ * @param {string} label
+ * @return {string}
+ */
+function pluralize(time, label) {
+  if (time === 1) {
+    return time + label
+  }
+  return time + label + 's'
+}
+
+/**
+ * @param {number} time
+ */
+export function timeAgo(time) {
+  const between = Date.now() / 1000 - Number(time)
+  if (between < 3600) {
+    return pluralize(~~(between / 60), ' minute')
+  } else if (between < 86400) {
+    return pluralize(~~(between / 3600), ' hour')
+  } else {
+    return pluralize(~~(between / 86400), ' day')
+  }
+}
+
+/**
+ * Number formatting
+ * like 10000 => 10k
+ * @param {number} num
+ * @param {number} digits
+ */
+export function numberFormatter(num, digits) {
+  const si = [
+    { value: 1E18, symbol: 'E' },
+    { value: 1E15, symbol: 'P' },
+    { value: 1E12, symbol: 'T' },
+    { value: 1E9, symbol: 'G' },
+    { value: 1E6, symbol: 'M' },
+    { value: 1E3, symbol: 'k' }
+  ]
+  for (let i = 0; i < si.length; i++) {
+    if (num >= si[i].value) {
+      return (num / si[i].value).toFixed(digits).replace(/\.0+$|(\.[0-9]*[1-9])0+$/, '$1') + si[i].symbol
+    }
+  }
+  return num.toString()
+}
+
+/**
+ * 10000 => "10,000"
+ * @param {number} num
+ */
+export function toThousandFilter(num) {
+  return (+num || 0).toString().replace(/^-?\d+/g, m => m.replace(/(?=(?!\b)(\d{3})+$)/g, ','))
+}
+
+/**
+ * Upper case first char
+ * @param {String} string
+ */
+export function uppercaseFirst(string) {
+  return string.charAt(0).toUpperCase() + string.slice(1)
+}
diff --git a/frontend/src/icons/index.js b/frontend/src/icons/index.js
new file mode 100644
index 0000000..2c6b309
--- /dev/null
+++ b/frontend/src/icons/index.js
@@ -0,0 +1,9 @@
+import Vue from 'vue'
+import SvgIcon from '@/components/SvgIcon'// svg component
+
+// register globally
+Vue.component('svg-icon', SvgIcon)
+
+const req = require.context('./svg', false, /\.svg$/)
+const requireAll = requireContext => requireContext.keys().map(requireContext)
+requireAll(req)
diff --git a/frontend/src/icons/svg/404.svg b/frontend/src/icons/svg/404.svg
new file mode 100644
index 0000000..6df5019
--- /dev/null
+++ b/frontend/src/icons/svg/404.svg
@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M121.718 73.272v9.953c3.957-7.584 6.199-16.05 6.199-24.995C127.917 26.079 99.273 0 63.958 0 28.644 0 0 26.079 0 58.23c0 .403.028.806.028 1.21l22.97-25.953h13.34l-19.76 27.187h6.42V53.77l13.728-19.477v49.361H22.998V73.272H2.158c5.951 20.284 23.608 36.208 45.998 41.399-1.44 3.3-5.618 11.263-12.565 12.674-8.607 1.764 23.358.428 46.163-13.178 17.519-4.611 31.938-15.849 39.77-30.513h-13.506V73.272H85.02V59.464l22.998-25.977h13.008l-19.429 27.187h6.421v-7.433l13.727-19.402v39.433h-.027zm-78.24 2.822a10.516 10.516 0 0 1-.996-4.535V44.548c0-1.613.332-3.124.996-4.535a11.66 11.66 0 0 1 2.713-3.68c1.134-1.032 2.49-1.864 4.04-2.468 1.55-.605 3.21-.908 4.982-.908h11.292c1.77 0 3.431.303 4.981.908 1.522.604 2.85 1.41 3.986 2.418l-12.26 16.303v-2.898a1.96 1.96 0 0 0-.665-1.512c-.443-.403-.996-.604-1.66-.604-.665 0-1.218.201-1.661.604a1.96 1.96 0 0 0-.664 1.512v9.071L44.364 77.606a10.556 10.556 0 0 1-.886-1.512zm35.73-4.535c0 1.613-.332 3.124-.997 4.535a11.66 11.66 0 0 1-2.712 3.68c-1.134 1.032-2.49 1.864-4.04 2.469-1.55.604-3.21.907-4.982.907H55.185c-1.77 0-3.431-.303-4.981-.907-1.55-.605-2.906-1.437-4.041-2.47a12.49 12.49 0 0 1-1.384-1.512l13.727-18.217v6.375c0 .605.222 1.109.665 1.512.442.403.996.604 1.66.604.664 0 1.218-.201 1.66-.604a1.96 1.96 0 0 0 .665-1.512V53.87L75.97 36.838c.913.932 1.66 1.99 2.214 3.175.664 1.41.996 2.922.996 4.535v27.011h.028z"/></svg>
\ No newline at end of file
diff --git a/frontend/src/icons/svg/bug.svg b/frontend/src/icons/svg/bug.svg
new file mode 100644
index 0000000..05a150d
--- /dev/null
+++ b/frontend/src/icons/svg/bug.svg
@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M127.88 73.143c0 1.412-.506 2.635-1.518 3.669-1.011 1.033-2.209 1.55-3.592 1.55h-17.887c0 9.296-1.783 17.178-5.35 23.645l16.609 17.044c1.011 1.034 1.517 2.257 1.517 3.67 0 1.412-.506 2.635-1.517 3.668-.958 1.033-2.155 1.55-3.593 1.55-1.438 0-2.635-.517-3.593-1.55l-15.811-16.063a15.49 15.49 0 0 1-1.196 1.06c-.532.434-1.65 1.208-3.353 2.322a50.104 50.104 0 0 1-5.192 2.974c-1.758.87-3.94 1.658-6.546 2.364-2.607.706-5.189 1.06-7.748 1.06V47.044H58.89v73.062c-2.716 0-5.417-.367-8.106-1.102-2.688-.734-5.003-1.631-6.945-2.692a66.769 66.769 0 0 1-5.268-3.179c-1.571-1.057-2.73-1.94-3.476-2.65L33.9 109.34l-14.611 16.877c-1.066 1.14-2.344 1.711-3.833 1.711-1.277 0-2.422-.434-3.434-1.304-1.012-.978-1.557-2.187-1.635-3.627-.079-1.44.333-2.705 1.236-3.794l16.129-18.51c-3.087-6.197-4.63-13.644-4.63-22.342H5.235c-1.383 0-2.58-.517-3.592-1.55S.125 74.545.125 73.132c0-1.412.506-2.635 1.518-3.668 1.012-1.034 2.21-1.55 3.592-1.55h17.887V43.939L9.308 29.833c-1.012-1.033-1.517-2.256-1.517-3.669 0-1.412.505-2.635 1.517-3.668 1.012-1.034 2.21-1.55 3.593-1.55s2.58.516 3.593 1.55l13.813 14.106h67.396l13.814-14.106c1.012-1.034 2.21-1.55 3.592-1.55 1.384 0 2.581.516 3.593 1.55 1.012 1.033 1.518 2.256 1.518 3.668 0 1.413-.506 2.636-1.518 3.67l-13.814 14.105v23.975h17.887c1.383 0 2.58.516 3.593 1.55 1.011 1.033 1.517 2.256 1.517 3.668l-.005.01zM89.552 26.175H38.448c0-7.23 2.489-13.386 7.466-18.469C50.892 2.623 56.92.082 64 .082c7.08 0 13.108 2.541 18.086 7.624 4.977 5.083 7.466 11.24 7.466 18.469z"/></svg>
\ No newline at end of file
diff --git a/frontend/src/icons/svg/chart.svg b/frontend/src/icons/svg/chart.svg
new file mode 100644
index 0000000..27728fb
--- /dev/null
+++ b/frontend/src/icons/svg/chart.svg
@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M0 54.857h36.571V128H0V54.857zM91.429 27.43H128V128H91.429V27.429zM45.714 0h36.572v128H45.714V0z"/></svg>
\ No newline at end of file
diff --git a/frontend/src/icons/svg/clipboard.svg b/frontend/src/icons/svg/clipboard.svg
new file mode 100644
index 0000000..90923ff
--- /dev/null
+++ b/frontend/src/icons/svg/clipboard.svg
@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M54.857 118.857h64V73.143H89.143c-1.902 0-3.52-.668-4.855-2.002-1.335-1.335-2.002-2.954-2.002-4.855V36.57H54.857v82.286zM73.143 16v-4.571a2.2 2.2 0 0 0-.677-1.61 2.198 2.198 0 0 0-1.609-.676H20.571c-.621 0-1.158.225-1.609.676a2.198 2.198 0 0 0-.676 1.61V16a2.2 2.2 0 0 0 .676 1.61c.451.45.988.676 1.61.676h50.285c.622 0 1.158-.226 1.61-.677.45-.45.676-.987.676-1.609zm18.286 48h21.357L91.43 42.642V64zM128 73.143v48c0 1.902-.667 3.52-2.002 4.855-1.335 1.335-2.953 2.002-4.855 2.002H52.57c-1.901 0-3.52-.667-4.854-2.002-1.335-1.335-2.003-2.953-2.003-4.855v-11.429H6.857c-1.902 0-3.52-.667-4.855-2.002C.667 106.377 0 104.759 0 102.857v-96c0-1.902.667-3.52 2.002-4.855C3.337.667 4.955 0 6.857 0h77.714c1.902 0 3.52.667 4.855 2.002 1.335 1.335 2.003 2.953 2.003 4.855V30.29c1 .622 1.856 1.29 2.569 2.003l29.147 29.147c1.335 1.335 2.478 3.145 3.429 5.43.95 2.287 1.426 4.383 1.426 6.291v-.018z"/></svg>
\ No newline at end of file
diff --git a/frontend/src/icons/svg/component.svg b/frontend/src/icons/svg/component.svg
new file mode 100644
index 0000000..207ada3
--- /dev/null
+++ b/frontend/src/icons/svg/component.svg
@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M0 0h54.857v54.857H0V0zm0 73.143h54.857V128H0V73.143zm73.143 0H128V128H73.143V73.143zm27.428-18.286C115.72 54.857 128 42.577 128 27.43 128 12.28 115.72 0 100.571 0 85.423 0 73.143 12.28 73.143 27.429c0 15.148 12.28 27.428 27.428 27.428z"/></svg>
\ No newline at end of file
diff --git a/frontend/src/icons/svg/dashboard.svg b/frontend/src/icons/svg/dashboard.svg
new file mode 100644
index 0000000..5317d37
--- /dev/null
+++ b/frontend/src/icons/svg/dashboard.svg
@@ -0,0 +1 @@
+<svg width="128" height="100" xmlns="http://www.w3.org/2000/svg"><path d="M27.429 63.638c0-2.508-.893-4.65-2.679-6.424-1.786-1.775-3.94-2.662-6.464-2.662-2.524 0-4.679.887-6.465 2.662-1.785 1.774-2.678 3.916-2.678 6.424 0 2.508.893 4.65 2.678 6.424 1.786 1.775 3.94 2.662 6.465 2.662 2.524 0 4.678-.887 6.464-2.662 1.786-1.775 2.679-3.916 2.679-6.424zm13.714-31.801c0-2.508-.893-4.65-2.679-6.424-1.785-1.775-3.94-2.662-6.464-2.662-2.524 0-4.679.887-6.464 2.662-1.786 1.774-2.679 3.916-2.679 6.424 0 2.508.893 4.65 2.679 6.424 1.785 1.774 3.94 2.662 6.464 2.662 2.524 0 4.679-.888 6.464-2.662 1.786-1.775 2.679-3.916 2.679-6.424zM71.714 65.98l7.215-27.116c.285-1.23.107-2.378-.536-3.443-.643-1.064-1.56-1.762-2.75-2.094-1.19-.33-2.333-.177-3.429.462-1.095.639-1.81 1.573-2.143 2.804l-7.214 27.116c-2.857.237-5.405 1.266-7.643 3.088-2.238 1.822-3.738 4.152-4.5 6.992-.952 3.644-.476 7.098 1.429 10.364 1.905 3.265 4.69 5.37 8.357 6.317 3.667.947 7.143.474 10.429-1.42 3.285-1.892 5.404-4.66 6.357-8.305.762-2.84.619-5.607-.429-8.305-1.047-2.697-2.762-4.85-5.143-6.46zm47.143-2.342c0-2.508-.893-4.65-2.678-6.424-1.786-1.775-3.94-2.662-6.465-2.662-2.524 0-4.678.887-6.464 2.662-1.786 1.774-2.679 3.916-2.679 6.424 0 2.508.893 4.65 2.679 6.424 1.786 1.775 3.94 2.662 6.464 2.662 2.524 0 4.679-.887 6.465-2.662 1.785-1.775 2.678-3.916 2.678-6.424zm-45.714-45.43c0-2.509-.893-4.65-2.679-6.425C68.68 10.01 66.524 9.122 64 9.122c-2.524 0-4.679.887-6.464 2.661-1.786 1.775-2.679 3.916-2.679 6.425 0 2.508.893 4.65 2.679 6.424 1.785 1.774 3.94 2.662 6.464 2.662 2.524 0 4.679-.888 6.464-2.662 1.786-1.775 2.679-3.916 2.679-6.424zm32 13.629c0-2.508-.893-4.65-2.679-6.424-1.785-1.775-3.94-2.662-6.464-2.662-2.524 0-4.679.887-6.464 2.662-1.786 1.774-2.679 3.916-2.679 6.424 0 2.508.893 4.65 2.679 6.424 1.785 1.774 3.94 2.662 6.464 2.662 2.524 0 4.679-.888 6.464-2.662 1.786-1.775 2.679-3.916 2.679-6.424zM128 63.638c0 12.351-3.357 23.78-10.071 34.286-.905 1.372-2.19 2.058-3.858 2.058H13.93c-1.667 0-2.953-.686-3.858-2.058C3.357 87.465 0 76.037 0 63.638c0-8.613 1.69-16.847 5.071-24.703C8.452 31.08 13 24.312 18.714 18.634c5.715-5.68 12.524-10.199 20.429-13.559C47.048 1.715 55.333.035 64 .035c8.667 0 16.952 1.68 24.857 5.04 7.905 3.36 14.714 7.88 20.429 13.559 5.714 5.678 10.262 12.446 13.643 20.301 3.38 7.856 5.071 16.09 5.071 24.703z"/></svg>
\ No newline at end of file
diff --git a/frontend/src/icons/svg/documentation.svg b/frontend/src/icons/svg/documentation.svg
new file mode 100644
index 0000000..7043122
--- /dev/null
+++ b/frontend/src/icons/svg/documentation.svg
@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M71.984 44.815H115.9L71.984 9.642v35.173zM16.094.05h63.875l47.906 38.37v76.74c0 3.392-1.682 6.645-4.677 9.044-2.995 2.399-7.056 3.746-11.292 3.746H16.094c-4.236 0-8.297-1.347-11.292-3.746-2.995-2.399-4.677-5.652-4.677-9.044V12.84C.125 5.742 7.23.05 16.094.05zm71.86 102.32V89.58h-71.86v12.79h71.86zm23.952-25.58V64H16.094v12.79h95.812z"/></svg>
\ No newline at end of file
diff --git a/frontend/src/icons/svg/drag.svg b/frontend/src/icons/svg/drag.svg
new file mode 100644
index 0000000..4185d3c
--- /dev/null
+++ b/frontend/src/icons/svg/drag.svg
@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M73.137 29.08h-9.209 29.7L63.886.093 34.373 29.08h20.49v27.035H27.238v17.948h27.625v27.133h18.274V74.063h27.41V56.115h-27.41V29.08zm-9.245 98.827l27.518-26.711H36.59l27.302 26.71zM.042 64.982l27.196 27.029V38.167L.042 64.982zm100.505-26.815V92.01l27.41-27.029-27.41-26.815z"/></svg>
\ No newline at end of file
diff --git a/frontend/src/icons/svg/edit.svg b/frontend/src/icons/svg/edit.svg
new file mode 100644
index 0000000..d26101f
--- /dev/null
+++ b/frontend/src/icons/svg/edit.svg
@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M106.133 67.2a4.797 4.797 0 0 0-4.8 4.8c0 .187.014.36.027.533h-.027V118.4H9.6V26.667h50.133c2.654 0 4.8-2.147 4.8-4.8 0-2.654-2.146-4.8-4.8-4.8H9.6a9.594 9.594 0 0 0-9.6 9.6V118.4c0 5.307 4.293 9.6 9.6 9.6h91.733c5.307 0 9.6-4.293 9.6-9.6V72.533h-.026c.013-.173.026-.346.026-.533 0-2.653-2.146-4.8-4.8-4.8z"/><path d="M125.16 13.373L114.587 2.8c-3.747-3.747-9.854-3.72-13.6.027l-52.96 52.96a4.264 4.264 0 0 0-.907 1.36L33.813 88.533c-.746 1.76-.226 3.534.907 4.68 1.133 1.147 2.92 1.667 4.693.92l31.4-13.293c.507-.213.96-.52 1.36-.907l52.96-52.96c3.747-3.746 3.774-9.853.027-13.6zM66.107 72.4l-18.32 7.76 7.76-18.32L92.72 24.667l10.56 10.56L66.107 72.4zm52.226-52.227l-8.266 8.267-10.56-10.56 8.266-8.267.027-.026 10.56 10.56-.027.026z"/></svg>
\ No newline at end of file
diff --git a/frontend/src/icons/svg/education.svg b/frontend/src/icons/svg/education.svg
new file mode 100644
index 0000000..7bfb01d
--- /dev/null
+++ b/frontend/src/icons/svg/education.svg
@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M88.883 119.565c-7.284 0-19.434 2.495-21.333 8.25v.127c-4.232.13-5.222 0-7.108 0-1.895-5.76-14.045-8.256-21.333-8.256H0V0h42.523c9.179 0 17.109 5.47 21.47 13.551C68.352 5.475 76.295 0 85.478 0H128v119.57l-39.113-.005h-.004zM60.442 24.763c0-9.651-8.978-16.507-17.777-16.507H7.108V111.43H39.11c7.054-.14 18.177.082 21.333 6.12v-4.628c-.134-5.722-.004-13.522 0-13.832V27.413l.004-2.655-.004.005zm60.442-16.517h-35.55c-8.802 0-17.78 6.856-17.78 16.493v74.259c.004.32.138 8.115 0 13.813v4.627c3.155-6.022 14.279-6.26 21.333-6.114h32V8.25l-.003-.005z"/></svg>
\ No newline at end of file
diff --git a/frontend/src/icons/svg/email.svg b/frontend/src/icons/svg/email.svg
new file mode 100644
index 0000000..74d25e2
--- /dev/null
+++ b/frontend/src/icons/svg/email.svg
@@ -0,0 +1 @@
+<svg width="128" height="96" xmlns="http://www.w3.org/2000/svg"><path d="M64.125 56.975L120.188.912A12.476 12.476 0 0 0 115.5 0h-103c-1.588 0-3.113.3-4.513.838l56.138 56.137z"/><path d="M64.125 68.287l-62.3-62.3A12.42 12.42 0 0 0 0 12.5v71C0 90.4 5.6 96 12.5 96h103c6.9 0 12.5-5.6 12.5-12.5v-71a12.47 12.47 0 0 0-1.737-6.35L64.125 68.287z"/></svg>
\ No newline at end of file
diff --git a/frontend/src/icons/svg/example.svg b/frontend/src/icons/svg/example.svg
new file mode 100644
index 0000000..46f42b5
--- /dev/null
+++ b/frontend/src/icons/svg/example.svg
@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M96.258 57.462h31.421C124.794 27.323 100.426 2.956 70.287.07v31.422a32.856 32.856 0 0 1 25.971 25.97zm-38.796-25.97V.07C27.323 2.956 2.956 27.323.07 57.462h31.422a32.856 32.856 0 0 1 25.97-25.97zm12.825 64.766v31.421c30.46-2.885 54.507-27.253 57.713-57.712H96.579c-2.886 13.466-13.146 23.726-26.292 26.291zM31.492 70.287H.07c2.886 30.46 27.253 54.507 57.713 57.713V96.579c-13.466-2.886-23.726-13.146-26.291-26.292z"/></svg>
\ No newline at end of file
diff --git a/frontend/src/icons/svg/excel.svg b/frontend/src/icons/svg/excel.svg
new file mode 100644
index 0000000..74d97b8
--- /dev/null
+++ b/frontend/src/icons/svg/excel.svg
@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M78.208 16.576v8.384h38.72v5.376h-38.72v8.704h38.72v5.376h-38.72v8.576h38.72v5.376h-38.72v8.576h38.72v5.376h-38.72v8.576h38.72v5.376h-38.72v8.512h38.72v5.376h-38.72v11.136H128v-94.72H78.208zM0 114.368L72.128 128V0L0 13.632v100.736z"/><path d="M28.672 82.56h-11.2l14.784-23.488-14.08-22.592h11.52l8.192 14.976 8.448-14.976h11.136l-14.08 22.208L58.368 82.56H46.656l-8.768-15.68z"/></svg>
\ No newline at end of file
diff --git a/frontend/src/icons/svg/exit-fullscreen.svg b/frontend/src/icons/svg/exit-fullscreen.svg
new file mode 100644
index 0000000..485c128
--- /dev/null
+++ b/frontend/src/icons/svg/exit-fullscreen.svg
@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M49.217 41.329l-.136-35.24c-.06-2.715-2.302-4.345-5.022-4.405h-3.65c-2.712-.06-4.866 2.303-4.806 5.016l.152 19.164-24.151-23.79a6.698 6.698 0 0 0-9.499 0 6.76 6.76 0 0 0 0 9.526l23.93 23.713-18.345.074c-2.712-.069-5.228 1.813-5.64 5.02v3.462c.069 2.721 2.31 4.97 5.022 5.03l35.028-.207c.052.005.087.025.133.025l2.457.054a4.626 4.626 0 0 0 3.436-1.38c.88-.874 1.205-2.096 1.169-3.462l-.262-2.465c0-.048.182-.081.182-.136h.002zm52.523 51.212l18.32-.073c2.713.06 5.224-1.609 5.64-4.815v-3.462c-.068-2.722-2.317-4.97-5.021-5.04l-34.58.21c-.053 0-.086-.021-.138-.021l-2.451-.06a4.64 4.64 0 0 0-3.445 1.381c-.885.868-1.201 2.094-1.174 3.46l.27 2.46c.005.06-.177.095-.177.141l.141 34.697c.069 2.713 2.31 4.338 5.022 4.397l3.45.006c2.705.062 4.867-2.31 4.8-5.026l-.153-18.752 24.151 23.946a6.69 6.69 0 0 0 9.494 0 6.747 6.747 0 0 0 0-9.523L101.74 92.54v.001zM48.125 80.662a4.636 4.636 0 0 0-3.437-1.382l-2.457.06c-.05 0-.082.022-.137.022l-35.025-.21c-2.712.07-4.957 2.318-5.022 5.04v3.462c.409 3.206 2.925 4.874 5.633 4.814l18.554.06-24.132 23.928c-2.62 2.626-2.62 6.89 0 9.524a6.694 6.694 0 0 0 9.496 0l24.155-23.79-.155 18.866c-.06 2.722 2.094 5.093 4.801 5.025h3.65c2.72-.069 4.962-1.685 5.022-4.406l.141-34.956c0-.05-.182-.082-.182-.136l.262-2.46c.03-1.366-.286-2.592-1.166-3.46h-.001zM80.08 47.397a4.62 4.62 0 0 0 3.443 1.374l2.45-.054c.055 0 .088-.02.143-.028l35.08.21c2.712-.062 4.953-2.312 5.021-5.033l.009-3.463c-.417-3.211-2.937-5.084-5.64-5.025l-18.615-.073 23.917-23.715c2.63-2.623 2.63-6.879.008-9.513a6.691 6.691 0 0 0-9.494 0L92.251 26.016l.155-19.312c.065-2.713-2.097-5.085-4.802-5.025h-3.45c-2.713.069-4.954 1.693-5.022 4.406l-.139 35.247c0 .054.18.088.18.136l-.267 2.465c-.028 1.366.288 2.588 1.174 3.463v.001z"/></svg>
\ No newline at end of file
diff --git a/frontend/src/icons/svg/eye-open.svg b/frontend/src/icons/svg/eye-open.svg
new file mode 100644
index 0000000..88dcc98
--- /dev/null
+++ b/frontend/src/icons/svg/eye-open.svg
@@ -0,0 +1 @@
+<svg class="icon" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="128" height="128"><defs><style/></defs><path d="M512 128q69.675 0 135.51 21.163t115.498 54.997 93.483 74.837 73.685 82.006 51.67 74.837 32.17 54.827L1024 512q-2.347 4.992-6.315 13.483T998.87 560.17t-31.658 51.669-44.331 59.99-56.832 64.34-69.504 60.16-82.347 51.5-94.848 34.687T512 896q-69.675 0-135.51-21.163t-115.498-54.826-93.483-74.326-73.685-81.493-51.67-74.496-32.17-54.997L0 513.707q2.347-4.992 6.315-13.483t18.816-34.816 31.658-51.84 44.331-60.33 56.832-64.683 69.504-60.331 82.347-51.84 94.848-34.816T512 128.085zm0 85.333q-46.677 0-91.648 12.331t-81.152 31.83-70.656 47.146-59.648 54.485-48.853 57.686-37.675 52.821-26.325 43.99q12.33 21.674 26.325 43.52t37.675 52.351 48.853 57.003 59.648 53.845T339.2 767.02t81.152 31.488T512 810.667t91.648-12.331 81.152-31.659 70.656-46.848 59.648-54.186 48.853-57.344 37.675-52.651T927.957 512q-12.33-21.675-26.325-43.648t-37.675-52.65-48.853-57.345-59.648-54.186-70.656-46.848-81.152-31.659T512 213.334zm0 128q70.656 0 120.661 50.006T682.667 512 632.66 632.661 512 682.667 391.339 632.66 341.333 512t50.006-120.661T512 341.333zm0 85.334q-35.328 0-60.33 25.002T426.666 512t25.002 60.33T512 597.334t60.33-25.002T597.334 512t-25.002-60.33T512 426.666z"/></svg>
\ No newline at end of file
diff --git a/frontend/src/icons/svg/eye.svg b/frontend/src/icons/svg/eye.svg
new file mode 100644
index 0000000..16ed2d8
--- /dev/null
+++ b/frontend/src/icons/svg/eye.svg
@@ -0,0 +1 @@
+<svg width="128" height="64" xmlns="http://www.w3.org/2000/svg"><path d="M127.072 7.994c1.37-2.208.914-5.152-.914-6.87-2.056-1.717-4.797-1.226-6.396.982-.229.245-25.586 32.382-55.74 32.382-29.24 0-55.74-32.382-55.968-32.627-1.6-1.963-4.57-2.208-6.397-.49C-.17 3.086-.399 6.275 1.2 8.238c.457.736 5.94 7.36 14.62 14.72L4.17 35.96c-1.828 1.963-1.6 5.152.228 6.87.457.98 1.6 1.471 2.742 1.471s2.284-.49 3.198-1.472l12.564-13.983c5.94 4.416 13.021 8.587 20.788 11.53l-4.797 17.418c-.685 2.699.686 5.397 3.198 6.133h1.37c2.057 0 3.884-1.472 4.341-3.68L52.6 42.83c3.655.736 7.538 1.227 11.422 1.227 3.883 0 7.767-.49 11.422-1.227l4.797 17.173c.457 2.208 2.513 3.68 4.34 3.68.457 0 .914 0 1.143-.246 2.513-.736 3.883-3.434 3.198-6.133l-4.797-17.172c7.767-2.944 14.848-7.114 20.788-11.53l12.336 13.738c.913.981 2.056 1.472 3.198 1.472s2.284-.49 3.198-1.472c1.828-1.963 1.828-4.906.228-6.87l-11.65-13.001c9.366-7.36 14.849-14.474 14.849-14.474z"/></svg>
\ No newline at end of file
diff --git a/frontend/src/icons/svg/form.svg b/frontend/src/icons/svg/form.svg
new file mode 100644
index 0000000..dcbaa18
--- /dev/null
+++ b/frontend/src/icons/svg/form.svg
@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M84.068 23.784c-1.02 0-1.877-.32-2.572-.96a8.588 8.588 0 0 1-1.738-2.237 11.524 11.524 0 0 1-1.042-2.621c-.232-.895-.348-1.641-.348-2.238V0h.278c.834 0 1.622.085 2.363.256.742.17 1.645.575 2.711 1.214 1.066.64 2.363 1.535 3.892 2.686 1.53 1.15 3.453 2.664 5.77 4.54 2.502 2.045 4.494 3.771 5.977 5.178 1.483 1.406 2.618 2.6 3.406 3.58.787.98 1.274 1.812 1.46 2.494.185.682.277 1.278.277 1.79v2.046H84.068zM127.3 84.01c.278.682.464 1.535.556 2.558.093 1.023-.37 2.003-1.39 2.94-.463.427-.88.832-1.25 1.215-.372.384-.696.704-.974.96a6.69 6.69 0 0 1-.973.767l-11.816-10.741a44.331 44.331 0 0 0 1.877-1.535 31.028 31.028 0 0 1 1.737-1.406c1.112-.938 2.317-1.343 3.615-1.215 1.297.128 2.363.405 3.197.83.927.427 1.923 1.173 2.989 2.239 1.065 1.065 1.876 2.195 2.432 3.388zM78.23 95.902c2.038 0 3.752-.511 5.143-1.534l-26.969 25.83H18.037c-1.761 0-3.684-.47-5.77-1.407a24.549 24.549 0 0 1-5.838-3.709 21.373 21.373 0 0 1-4.518-5.306c-1.204-2.003-1.807-4.07-1.807-6.202V16.495c0-1.79.44-3.665 1.32-5.626A18.41 18.41 0 0 1 5.04 5.562a21.798 21.798 0 0 1 5.213-3.964C12.198.533 14.237 0 16.37 0h53.24v15.984c0 1.62.278 3.367.834 5.242a16.704 16.704 0 0 0 2.572 5.179c1.159 1.577 2.665 2.898 4.518 3.964 1.853 1.066 4.078 1.598 6.673 1.598h20.295v42.325L85.458 92.45c1.02-1.364 1.529-2.856 1.529-4.476 0-2.216-.857-4.113-2.572-5.69-1.714-1.577-3.776-2.366-6.186-2.366H26.1c-2.409 0-4.448.789-6.116 2.366-1.668 1.577-2.502 3.474-2.502 5.69 0 2.217.834 4.092 2.502 5.626 1.668 1.535 3.707 2.302 6.117 2.302h52.13zM26.1 47.951c-2.41 0-4.449.789-6.117 2.366-1.668 1.577-2.502 3.473-2.502 5.69 0 2.216.834 4.092 2.502 5.626 1.668 1.534 3.707 2.302 6.117 2.302h52.13c2.409 0 4.47-.768 6.185-2.302 1.715-1.534 2.572-3.41 2.572-5.626 0-2.217-.857-4.113-2.572-5.69-1.714-1.577-3.776-2.366-6.186-2.366H26.1zm52.407 64.063l1.807-1.663 3.476-3.196a479.75 479.75 0 0 0 4.587-4.284 500.757 500.757 0 0 1 5.004-4.667c3.985-3.666 8.48-7.758 13.485-12.276l11.677 10.741-13.485 12.404-5.004 4.603-4.587 4.22a179.46 179.46 0 0 0-3.267 3.068c-.88.853-1.367 1.322-1.46 1.407-.463.341-.973.703-1.529 1.087-.556.383-1.112.703-1.668.959-.556.256-1.413.575-2.572.959a83.5 83.5 0 0 1-3.545 1.087 72.2 72.2 0 0 1-3.475.895c-1.112.256-1.946.426-2.502.511-1.112.17-1.854.043-2.224-.383-.371-.426-.464-1.151-.278-2.174.092-.511.278-1.279.556-2.302.278-1.023.602-2.067.973-3.132l1.042-3.005c.325-.938.58-1.577.765-1.918a10.157 10.157 0 0 1 2.224-2.941z"/></svg>
\ No newline at end of file
diff --git a/frontend/src/icons/svg/fullscreen.svg b/frontend/src/icons/svg/fullscreen.svg
new file mode 100644
index 0000000..0e86b6f
--- /dev/null
+++ b/frontend/src/icons/svg/fullscreen.svg
@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M38.47 52L52 38.462l-23.648-23.67L43.209 0H.035L0 43.137l14.757-14.865L38.47 52zm74.773 47.726L89.526 76 76 89.536l23.648 23.672L84.795 128h43.174L128 84.863l-14.757 14.863zM89.538 52l23.668-23.648L128 43.207V.038L84.866 0 99.73 14.76 76 38.472 89.538 52zM38.46 76L14.792 99.651 0 84.794v43.173l43.137.033-14.865-14.757L52 89.53 38.46 76z"/></svg>
\ No newline at end of file
diff --git a/frontend/src/icons/svg/guide.svg b/frontend/src/icons/svg/guide.svg
new file mode 100644
index 0000000..b271001
--- /dev/null
+++ b/frontend/src/icons/svg/guide.svg
@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M1.482 70.131l36.204 16.18 69.932-65.485-61.38 70.594 46.435 18.735c1.119.425 2.397-.17 2.797-1.363v-.085L127.998.047 1.322 65.874c-1.12.597-1.519 1.959-1.04 3.151.32.511.72.937 1.2 1.107zm44.676 57.821L64.22 107.26l-18.062-7.834v28.527z"/></svg>
\ No newline at end of file
diff --git a/frontend/src/icons/svg/icon.svg b/frontend/src/icons/svg/icon.svg
new file mode 100644
index 0000000..82be8ee
--- /dev/null
+++ b/frontend/src/icons/svg/icon.svg
@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M115.147.062a13 13 0 0 1 4.94.945c1.55.63 2.907 1.526 4.069 2.688a13.148 13.148 0 0 1 2.761 4.069c.678 1.55 1.017 3.245 1.017 5.086v102.3c0 3.681-1.187 6.733-3.56 9.155-2.373 2.422-5.352 3.633-8.937 3.633H12.992c-3.875 0-7-1.26-9.373-3.779-2.373-2.518-3.56-5.667-3.56-9.445V12.704c0-3.39 1.163-6.345 3.488-8.863C5.872 1.32 8.972.062 12.847.062h102.3zM81.434 109.047c1.744 0 3.003-.412 3.778-1.235.775-.824 1.163-1.914 1.163-3.27 0-1.26-.388-2.325-1.163-3.197-.775-.872-2.034-1.307-3.778-1.307H72.57c.097-.194.145-.485.145-.872V27.09h9.01c1.743 0 2.954-.436 3.633-1.308.678-.872 1.017-1.938 1.017-3.197 0-1.26-.34-2.325-1.017-3.197-.679-.872-1.89-1.308-3.633-1.308H46.268c-1.743 0-2.954.436-3.632 1.308-.678.872-1.018 1.938-1.018 3.197 0 1.26.34 2.325 1.018 3.197.678.872 1.889 1.308 3.632 1.308h8.138v72.075c0 .193.024.339.073.436.048.096.072.242.072.436H46.56c-1.744 0-3.003.435-3.778 1.307-.775.872-1.163 1.938-1.163 3.197 0 1.356.388 2.446 1.163 3.27.775.823 2.034 1.235 3.778 1.235h34.875z"/></svg>
\ No newline at end of file
diff --git a/frontend/src/icons/svg/international.svg b/frontend/src/icons/svg/international.svg
new file mode 100644
index 0000000..e9b56ee
--- /dev/null
+++ b/frontend/src/icons/svg/international.svg
@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M83.287 103.01c-1.57-3.84-6.778-10.414-15.447-19.548-2.327-2.444-2.182-4.306-1.338-9.862v-.64c.553-3.81 1.513-6.05 14.313-8.087 6.516-1.018 8.203 1.57 10.589 5.178l.785 1.193a12.625 12.625 0 0 0 6.43 5.207c1.134.524 2.53 1.164 4.421 2.24 4.596 2.53 4.596 5.41 4.596 11.753v.727a26.91 26.91 0 0 1-5.178 17.454 59.055 59.055 0 0 1-19.025 11.026c3.49-6.546.814-14.313 0-16.553l-.146-.087zM64 5.12a58.502 58.502 0 0 1 25.484 5.818 54.313 54.313 0 0 0-12.859 10.327c-.93 1.28-1.716 2.473-2.472 3.579-2.444 3.694-3.637 5.352-5.818 5.614a25.105 25.105 0 0 1-4.219 0c-4.276-.29-10.094-.64-11.956 4.422-1.193 3.23-1.396 11.956 2.444 16.495.66 1.077.778 2.4.32 3.578a7.01 7.01 0 0 1-2.066 3.229 18.938 18.938 0 0 1-2.909-2.91 18.91 18.91 0 0 0-8.32-6.603c-1.25-.349-2.647-.64-3.985-.93-3.782-.786-8.03-1.688-9.019-3.812a14.895 14.895 0 0 1-.727-5.818 21.935 21.935 0 0 0-1.396-9.25 8.873 8.873 0 0 0-5.557-4.946A58.705 58.705 0 0 1 64 5.12zM0 64c0 35.346 28.654 64 64 64 35.346 0 64-28.654 64-64 0-35.346-28.654-64-64-64C28.654 0 0 28.654 0 64z"/></svg>
\ No newline at end of file
diff --git a/frontend/src/icons/svg/language.svg b/frontend/src/icons/svg/language.svg
new file mode 100644
index 0000000..0082b57
--- /dev/null
+++ b/frontend/src/icons/svg/language.svg
@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M84.742 36.8c2.398 7.2 5.595 12.8 11.19 18.4 4.795-4.8 7.992-11.2 10.39-18.4h-21.58zm-52.748 40h20.78l-10.39-28-10.39 28z"/><path d="M111.916 0H16.009C7.218 0 .025 7.2.025 16v96c0 8.8 7.193 16 15.984 16h95.907c8.791 0 15.984-7.2 15.984-16V16c0-8.8-6.394-16-15.984-16zM72.754 103.2c-1.598 1.6-3.197 1.6-4.795 1.6-.8 0-2.398 0-3.197-.8-.8-.8-1.599 0-1.599-.8s-.799-1.6-1.598-3.2c-.8-1.6-.8-2.4-1.599-4l-3.196-8.8H28.797L25.6 96c-1.598 3.2-2.398 5.6-3.197 7.2-.8 1.6-2.398 1.6-4.795 1.6-1.599 0-3.197-.8-4.796-1.6-1.598-1.6-2.397-2.4-2.397-4 0-.8 0-1.6.799-3.2.8-1.6.8-2.4 1.598-4l17.583-44.8c.8-1.6.8-3.2 1.599-4.8.799-1.6 1.598-3.2 2.397-4 .8-.8 1.599-2.4 3.197-3.2 1.599-.8 3.197-.8 4.796-.8 1.598 0 3.196 0 4.795.8 1.598.8 2.398 1.6 3.197 3.2.799.8 1.598 2.4 2.397 4 .8 1.6 1.599 3.2 2.398 5.6l17.583 44c1.598 3.2 2.398 5.6 2.398 7.2-.8.8-1.599 2.4-2.398 4zM116.711 72c-8.791-3.2-15.185-7.2-20.78-12-5.594 5.6-12.787 9.6-21.579 12l-2.397-4c8.791-2.4 15.984-5.6 21.579-11.2C87.939 51.2 83.144 44 81.545 36h-7.992v-3.2h21.58c-1.6-2.4-3.198-5.6-4.796-8l2.397-.8c1.599 2.4 3.997 5.6 5.595 8.8h19.98v4h-7.992c-2.397 8-6.393 15.2-11.189 20 5.595 4.8 11.988 8.8 20.78 11.2l-3.197 4z"/></svg>
\ No newline at end of file
diff --git a/frontend/src/icons/svg/link.svg b/frontend/src/icons/svg/link.svg
new file mode 100644
index 0000000..48197ba
--- /dev/null
+++ b/frontend/src/icons/svg/link.svg
@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M115.625 127.937H.063V12.375h57.781v12.374H12.438v90.813h90.813V70.156h12.374z"/><path d="M116.426 2.821l8.753 8.753-56.734 56.734-8.753-8.745z"/><path d="M127.893 37.982h-12.375V12.375H88.706V0h39.187z"/></svg>
\ No newline at end of file
diff --git a/frontend/src/icons/svg/list.svg b/frontend/src/icons/svg/list.svg
new file mode 100644
index 0000000..20259ed
--- /dev/null
+++ b/frontend/src/icons/svg/list.svg
@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M1.585 12.087c0 6.616 3.974 11.98 8.877 11.98 4.902 0 8.877-5.364 8.877-11.98 0-6.616-3.975-11.98-8.877-11.98-4.903 0-8.877 5.364-8.877 11.98zM125.86.107H35.613c-1.268 0-2.114 1.426-2.114 2.852v18.255c0 1.712 1.057 2.853 2.114 2.853h90.247c1.268 0 2.114-1.426 2.114-2.853V2.96c0-1.711-1.057-2.852-2.114-2.852zM.106 62.86c0 6.615 3.974 11.979 8.876 11.979 4.903 0 8.877-5.364 8.877-11.98 0-6.616-3.974-11.98-8.877-11.98-4.902 0-8.876 5.364-8.876 11.98zM124.17 50.88H33.921c-1.268 0-2.114 1.425-2.114 2.851v18.256c0 1.711 1.057 2.852 2.114 2.852h90.247c1.268 0 2.114-1.426 2.114-2.852V53.73c0-1.426-.846-2.852-2.114-2.852zM.106 115.913c0 6.616 3.974 11.98 8.876 11.98 4.903 0 8.877-5.364 8.877-11.98 0-6.616-3.974-11.98-8.877-11.98-4.902 0-8.876 5.364-8.876 11.98zm124.064-11.98H33.921c-1.268 0-2.114 1.426-2.114 2.853v18.255c0 1.711 1.057 2.852 2.114 2.852h90.247c1.268 0 2.114-1.426 2.114-2.852v-18.255c0-1.427-.846-2.853-2.114-2.853z"/></svg>
\ No newline at end of file
diff --git a/frontend/src/icons/svg/lock.svg b/frontend/src/icons/svg/lock.svg
new file mode 100644
index 0000000..74fee54
--- /dev/null
+++ b/frontend/src/icons/svg/lock.svg
@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M119.88 49.674h-7.987V39.52C111.893 17.738 90.45.08 63.996.08 37.543.08 16.1 17.738 16.1 39.52v10.154H8.113c-4.408 0-7.987 2.94-7.987 6.577v65.13c0 3.637 3.57 6.577 7.987 6.577H119.88c4.407 0 7.987-2.94 7.987-6.577v-65.13c-.008-3.636-3.58-6.577-7.987-6.577zm-23.953 0H32.065V39.52c0-14.524 14.301-26.295 31.931-26.295 17.63 0 31.932 11.777 31.932 26.295v10.153z"/></svg>
\ No newline at end of file
diff --git a/frontend/src/icons/svg/message.svg b/frontend/src/icons/svg/message.svg
new file mode 100644
index 0000000..14ca817
--- /dev/null
+++ b/frontend/src/icons/svg/message.svg
@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M0 20.967v59.59c0 11.59 8.537 20.966 19.075 20.966h28.613l1 26.477L76.8 101.523h32.125c10.538 0 19.075-9.377 19.075-20.966v-59.59C128 9.377 119.463 0 108.925 0h-89.85C8.538 0 0 9.377 0 20.967zm82.325 33.1c0-5.524 4.013-9.935 9.037-9.935 5.026 0 9.038 4.41 9.038 9.934 0 5.524-4.025 9.934-9.038 9.934-5.024 0-9.037-4.41-9.037-9.934zm-27.613 0c0-5.524 4.013-9.935 9.038-9.935s9.037 4.41 9.037 9.934c0 5.524-4.025 9.934-9.037 9.934-5.025 0-9.038-4.41-9.038-9.934zm-27.1 0c0-5.524 4.013-9.935 9.038-9.935s9.038 4.41 9.038 9.934c0 5.524-4.026 9.934-9.05 9.934-5.013 0-9.025-4.41-9.025-9.934z"/></svg>
\ No newline at end of file
diff --git a/frontend/src/icons/svg/money.svg b/frontend/src/icons/svg/money.svg
new file mode 100644
index 0000000..c1580de
--- /dev/null
+++ b/frontend/src/icons/svg/money.svg
@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M54.122 127.892v-28.68H7.513V87.274h46.609v-12.4H7.513v-12.86h38.003L.099 0h22.6l32.556 45.07c3.617 5.144 6.44 9.611 8.487 13.385 1.788-3.05 4.89-7.779 9.301-14.186L103.93 0h24.01L82.385 62.013h38.34v12.862h-46.41v12.4h46.41v11.937h-46.41v28.68H54.123z"/></svg>
\ No newline at end of file
diff --git a/frontend/src/icons/svg/nested.svg b/frontend/src/icons/svg/nested.svg
new file mode 100644
index 0000000..06713a8
--- /dev/null
+++ b/frontend/src/icons/svg/nested.svg
@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M.002 9.2c0 5.044 3.58 9.133 7.998 9.133 4.417 0 7.997-4.089 7.997-9.133 0-5.043-3.58-9.132-7.997-9.132S.002 4.157.002 9.2zM31.997.066h95.981V18.33H31.997V.066zm0 45.669c0 5.044 3.58 9.132 7.998 9.132 4.417 0 7.997-4.088 7.997-9.132 0-3.263-1.524-6.278-3.998-7.91-2.475-1.63-5.524-1.63-7.998 0-2.475 1.632-4 4.647-4 7.91zM63.992 36.6h63.986v18.265H63.992V36.6zm-31.995 82.2c0 5.043 3.58 9.132 7.998 9.132 4.417 0 7.997-4.089 7.997-9.132 0-5.044-3.58-9.133-7.997-9.133s-7.998 4.089-7.998 9.133zm31.995-9.131h63.986v18.265H63.992V109.67zm0-27.404c0 5.044 3.58 9.133 7.998 9.133 4.417 0 7.997-4.089 7.997-9.133 0-3.263-1.524-6.277-3.998-7.909-2.475-1.631-5.524-1.631-7.998 0-2.475 1.632-4 4.646-4 7.91zm31.995-9.13h31.991V91.4H95.987V73.135z"/></svg>
\ No newline at end of file
diff --git a/frontend/src/icons/svg/password.svg b/frontend/src/icons/svg/password.svg
new file mode 100644
index 0000000..e291d85
--- /dev/null
+++ b/frontend/src/icons/svg/password.svg
@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M108.8 44.322H89.6v-5.36c0-9.04-3.308-24.163-25.6-24.163-23.145 0-25.6 16.881-25.6 24.162v5.361H19.2v-5.36C19.2 15.281 36.798 0 64 0c27.202 0 44.8 15.281 44.8 38.961v5.361zm-32 39.356c0-5.44-5.763-9.832-12.8-9.832-7.037 0-12.8 4.392-12.8 9.832 0 3.682 2.567 6.808 6.407 8.477v11.205c0 2.718 2.875 4.962 6.4 4.962 3.524 0 6.4-2.244 6.4-4.962V92.155c3.833-1.669 6.393-4.795 6.393-8.477zM128 64v49.201c0 8.158-8.645 14.799-19.2 14.799H19.2C8.651 128 0 121.359 0 113.201V64c0-8.153 8.645-14.799 19.2-14.799h89.6c10.555 0 19.2 6.646 19.2 14.799z"/></svg>
\ No newline at end of file
diff --git a/frontend/src/icons/svg/pdf.svg b/frontend/src/icons/svg/pdf.svg
new file mode 100644
index 0000000..957aa0c
--- /dev/null
+++ b/frontend/src/icons/svg/pdf.svg
@@ -0,0 +1 @@
+<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="128" height="128"><path d="M869.073 277.307H657.111V65.344l211.962 211.963zm-238.232 26.27V65.344l-476.498-.054v416.957h714.73v-178.67H630.841zm-335.836 360.57c-5.07-3.064-10.944-5.133-17.61-6.201-6.67-1.064-13.603-1.6-20.81-1.6h-48.821v85.641h48.822c7.206 0 14.14-.532 20.81-1.6 6.665-1.065 12.54-3.133 17.609-6.202 5.064-3.063 9.134-7.406 12.208-13.007 3.065-5.602 4.6-12.937 4.6-22.011 0-9.07-1.535-16.408-4.6-22.01-3.074-5.603-7.144-9.94-12.208-13.01zM35.82 541.805v416.904h952.358V541.805H35.821zm331.421 191.179c-3.6 11.071-9.343 20.879-17.209 29.413-7.874 8.542-18.078 15.408-30.617 20.61-12.544 5.206-27.747 7.807-45.621 7.807h-66.036v102.45h-62.831V607.517h128.867c17.874 0 33.077 2.6 45.62 7.802 12.541 5.207 22.745 12.076 30.618 20.615 7.866 8.538 13.604 18.277 17.21 29.212 3.6 10.943 5.401 22.278 5.401 34.018 0 11.477-1.8 22.752-5.402 33.819zM644.9 806.417c-5.343 17.61-13.408 32.818-24.212 45.627-10.807 12.803-24.283 22.879-40.423 30.213-16.146 7.343-35.155 11.007-57.03 11.007h-123.26V607.518h123.26c18.41 0 35.552 2.941 51.428 8.808 15.873 5.869 29.618 14.671 41.22 26.412 11.608 11.744 20.674 26.411 27.217 44.02 6.535 17.61 9.803 38.288 9.803 62.035 0 20.81-2.67 40.02-8.003 57.624zm245.362-146.07h-138.07v66.03h119.66v48.829h-119.66v118.058h-62.83V607.518h200.9v52.829h-.001zm-318.2 25.611c-6.402-8.266-14.877-14.604-25.412-19.01-10.544-4.402-23.551-6.602-39.019-6.602h-44.825v180.088h56.029c9.07 0 17.872-1.463 26.415-4.401 8.535-2.932 16.14-7.802 22.812-14.609 6.665-6.8 12.007-15.667 16.007-26.61 4.003-10.94 6.003-24.275 6.003-40.021 0-14.408-1.4-27.416-4.202-39.019-2.8-11.607-7.406-21.542-13.808-29.816zm0 0"/></svg>
\ No newline at end of file
diff --git a/frontend/src/icons/svg/people.svg b/frontend/src/icons/svg/people.svg
new file mode 100644
index 0000000..2bd54ae
--- /dev/null
+++ b/frontend/src/icons/svg/people.svg
@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M104.185 95.254c8.161 7.574 13.145 17.441 13.145 28.28 0 1.508-.098 2.998-.285 4.466h-10.784c.238-1.465.403-2.948.403-4.465 0-8.983-4.36-17.115-11.419-23.216C86 104.66 75.355 107.162 64 107.162c-11.344 0-21.98-2.495-31.22-6.83-7.064 6.099-11.444 14.218-11.444 23.203 0 1.517.165 3 .403 4.465H10.955a35.444 35.444 0 0 1-.285-4.465c0-10.838 4.974-20.713 13.127-28.291C9.294 85.42.003 70.417.003 53.58.003 23.99 28.656.001 64 .001s63.997 23.988 63.997 53.58c0 16.842-9.299 31.85-23.812 41.673zM64 36.867c-29.454 0-53.33-10.077-53.33 15.342 0 25.418 23.876 46.023 53.33 46.023 29.454 0 53.33-20.605 53.33-46.023 0-25.419-23.876-15.342-53.33-15.342zm24.888 25.644c-3.927 0-7.111-2.665-7.111-5.953 0-3.288 3.184-5.954 7.11-5.954 3.928 0 7.111 2.666 7.111 5.954s-3.183 5.953-7.11 5.953zm-3.556 16.372c0 4.11-9.55 7.442-21.332 7.442-11.781 0-21.332-3.332-21.332-7.442 0-1.06.656-2.064 1.8-2.976 3.295 2.626 10.79 4.465 19.532 4.465 8.743 0 16.237-1.84 19.531-4.465 1.145.912 1.801 1.916 1.801 2.976zm-46.22-16.372c-3.927 0-7.11-2.665-7.11-5.953 0-3.288 3.183-5.954 7.11-5.954 3.927 0 7.111 2.666 7.111 5.954s-3.184 5.953-7.11 5.953z"/></svg>
\ No newline at end of file
diff --git a/frontend/src/icons/svg/peoples.svg b/frontend/src/icons/svg/peoples.svg
new file mode 100644
index 0000000..aab852e
--- /dev/null
+++ b/frontend/src/icons/svg/peoples.svg
@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M95.648 118.762c0 5.035-3.563 9.121-7.979 9.121H7.98c-4.416 0-7.979-4.086-7.979-9.121C0 100.519 15.408 83.47 31.152 76.75c-9.099-6.43-15.216-17.863-15.216-30.987v-9.128c0-20.16 14.293-36.518 31.893-36.518s31.894 16.358 31.894 36.518v9.122c0 13.137-6.123 24.556-15.216 30.993 15.738 6.726 31.141 23.769 31.141 42.012z"/><path d="M106.032 118.252h15.867c3.376 0 6.101-3.125 6.101-6.972 0-13.957-11.787-26.984-23.819-32.123 6.955-4.919 11.638-13.66 11.638-23.704v-6.985c0-15.416-10.928-27.926-24.39-27.926-1.674 0-3.306.193-4.89.561 1.936 4.713 3.018 9.974 3.018 15.526v9.121c0 13.137-3.056 23.111-11.066 30.993 14.842 4.41 27.312 23.42 27.541 41.509z"/></svg>
\ No newline at end of file
diff --git a/frontend/src/icons/svg/qq.svg b/frontend/src/icons/svg/qq.svg
new file mode 100644
index 0000000..ee13d4e
--- /dev/null
+++ b/frontend/src/icons/svg/qq.svg
@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M18.448 57.545l-.244-.744-.198-.968-.132-.53v-2.181l.236-.859.24-.908.317-.953.428-1.06.561-1.103.794-1.104v-.773l.077-.724.123-.984.34-1.106.313-1.194.25-.548.289-.511.371-.569.405-.423v-2.73l.234-1.407.236-1.633.42-1.955.577-2.035.43-1.118.426-1.217.468-1.135.559-1.216.57-1.332.655-1.247.737-1.331.929-1.33.43-.762.457-.624.995-1.406 1.025-1.403 1.163-1.444 1.246-1.405 1.352-1.384 1.41-1.423 1.708-1.536 1.083-.934 1.322-1.008 1.34-.89 1.448-.855 1.392-.76 1.57-.63 1.667-.775 1.657-.532 1.653-.552 1.787-.548 1.785-.417 1.876-.347L59.128.68l1.879-.245 1.876-.252 2.002-.106h5.912l1.97.243 1.981.231 2.019.207 1.874.441 1.979.413 1.857.475 2.035.53 1.862.646 1.782.738 1.904.78 1.736.853 1.689.95 1.655 1.044 1.425.971.662.548.693.401 1.323 1.1 1.115 1.064 1.112 1.1 1.083 1.214.894 1.178 1.064 1.217.74 1.306.752 1.162.798 1.352.661 1.175 1.113 2.489.546 1.286.428 1.192.428 1.294.384 1.217.267 1.047.347 1.231.607 2.198.388 1.924.253 1.861.217 1.497.342 2.28.077.362.274.41.737 1.18.473.8.42.832.534.892.472 1.07.307 1.093.334 1.2.252 1.232.115.605.106.746v.648l-.106.643v.8l-.192.774-.35 1.5-.403.76-.299.852v.213l.142.264.4.623 1.746 2.53 1.377 1.9.66 1.267.889 1.389.774 1.52.893 1.627.894 1.828 1.006 2.069.567 1.268.518 1.239.447 1.307.44 1.175.336 1.235.342 1.16.432 2.261.343 2.31.235 2.05v2.891l-.158 1.025-.226 1.768-.308 1.59-.48 1.44-.18.588-.336.707-.28.493-.375.607-.33.383-.42.494-.375.4-.401.34-.48.207-.432.207-.355.114h-.543l-.346-.114-.66-.32-.302-.212-.317-.223-.347-.304-.35-.342-.579-.63-.684-.89-.539-.917-.538-.734-.526-.855-.741-1.517-.833-1.579-.098-.055h-.138l-.338.247-.196.415-.326.516-.567 1.533-.856 2.182-1.096 2.626-.824 1.308-.864 1.366-1.027 1.536-1.09 1.503-.557.68-.676.743-1.555 1.497.136.135.21.214.777.446 3.235 1.524 1.41.779 1.347.756 1.332.953 1.187.982.574.443.432.511.445.593.367.643.198.533.242.64.105.554.115.647-.115.433v.44l-.105.454-.242.415-.092.325-.22.394-.587.784-.543.627-.42.47-.35.348-.893.638-1.01.556-1.077.532-1.155.511-1.287.495-.693.207-.608.167-1.496.342-1.545.325-1.552.323-1.689.27-1.74.072-1.785.21h-5.539l-1.998-.114-1.86-.168-2.005-.27-1.99-.209-2.095-.286-2.03-.495-1.981-.374-1.968-.552-2.019-.707-1.98-.585-1.044-.342-.927-.323-.586-.223-.582-.12h-1.647l-1.904-.131-.962-.096-1.24-.135-.795.705-1.085.665-1.471.701-1.628.875-.99.475-1.033.376-2.281.914-1.24.305-1.3.343-1.803.344-1.13.086-1.193.1-1.246.135-1.45.053h-5.926l-3.346-.053-3.25-.321-1.644-.23-1.589-.23-1.546-.227-1.547-.305-1.442-.456-1.434-.325-1.294-.51-1.223-.474-1.142-.533-.99-.583-.984-.71-.336-.343-.44-.415-.334-.362-.3-.417-.278-.415-.215-.42-.311-.89-.109-.46-.138-.51v-.473l.138-.533v-.53l.109-.53v-1.069l.052-.564.259-.647.215-.646.39-.779.286-.3.236-.348.615-.738.49-.38.464-.266.428-.338.676-.21.543-.324.676-.341.77-.227.775-.231.897-.192.85-.11 1.008-.13 1.093-.081.284-.092h.063l.137-.115v-.13l-.2-.266-.58-.27-1.45-1.231-.975-.761-1.127-.967-1.136-1.082-1.181-1.382-1.36-1.558-.508-.843-.672-.87-.58-1.007-.522-1.1-.704-1.047-.459-1.194-.547-1.192-.546-1.33-.397-1.273-.378-1.575-.112-.057h-.115l-.059-.113h-.14l-.23.113-.114.057-.158.264-.057.321-.119.286-.206.477-.664 1.157-.345.701-.546.612-.58.736-.641.816-.677.724-.795.701-.734.658-.814.524-.89.546-.855.325-1.008.247-.99.095h-.233l-.228-.095-.18-.384-.29-.188-.38-.912-.237-.493-.255-.707-.21-.734-.113-.724-.313-1.648-.12-.972v-3.185l.12-2.379.196-1.214.23-1.252.21-1.347.374-1.254.42-1.443.431-1.407.578-1.448.545-1.38.754-1.4.699-1.52.855-1.425 1.006-1.538 1.023-1.382 1.069-1.538.891-1.071 1.142-1.227 1.202-1.237.56-.59.678-.662.985-.836 1.012-.853 1.647-1.446 1.242-.889z"/></svg>
\ No newline at end of file
diff --git a/frontend/src/icons/svg/search.svg b/frontend/src/icons/svg/search.svg
new file mode 100644
index 0000000..84233dd
--- /dev/null
+++ b/frontend/src/icons/svg/search.svg
@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M124.884 109.812L94.256 79.166c-.357-.357-.757-.629-1.129-.914a50.366 50.366 0 0 0 8.186-27.59C101.327 22.689 78.656 0 50.67 0 22.685 0 0 22.688 0 50.663c0 27.989 22.685 50.663 50.656 50.663 10.186 0 19.643-3.03 27.6-8.201.286.385.557.771.9 1.114l30.628 30.632a10.633 10.633 0 0 0 7.543 3.129c2.728 0 5.457-1.043 7.543-3.115 4.171-4.157 4.171-10.915.014-15.073M50.671 85.338C31.557 85.338 16 69.78 16 50.663c0-19.102 15.557-34.661 34.67-34.661 19.115 0 34.657 15.559 34.657 34.675 0 19.102-15.557 34.661-34.656 34.661"/></svg>
\ No newline at end of file
diff --git a/frontend/src/icons/svg/shopping.svg b/frontend/src/icons/svg/shopping.svg
new file mode 100644
index 0000000..87513e7
--- /dev/null
+++ b/frontend/src/icons/svg/shopping.svg
@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M42.913 101.36c1.642 0 3.198.332 4.667.996a12.28 12.28 0 0 1 3.89 2.772c1.123 1.184 1.987 2.582 2.592 4.193.605 1.612.908 3.318.908 5.118 0 1.8-.303 3.507-.908 5.118-.605 1.611-1.469 3.01-2.593 4.194a13.3 13.3 0 0 1-3.889 2.843 10.582 10.582 0 0 1-4.667 1.066c-1.729 0-3.306-.355-4.732-1.066a13.604 13.604 0 0 1-3.825-2.843c-1.123-1.185-1.988-2.583-2.593-4.194a14.437 14.437 0 0 1-.907-5.118c0-1.8.302-3.506.907-5.118.605-1.61 1.47-3.009 2.593-4.193a12.515 12.515 0 0 1 3.825-2.772c1.426-.664 3.003-.996 4.732-.996zm53.932.285c1.643 0 3.22.331 4.733.995a11.386 11.386 0 0 1 3.889 2.772c1.08 1.185 1.945 2.583 2.593 4.194.648 1.61.972 3.317.972 5.118 0 1.8-.324 3.506-.972 5.117-.648 1.611-1.513 3.01-2.593 4.194a12.253 12.253 0 0 1-3.89 2.843 11 11 0 0 1-4.732 1.066 10.58 10.58 0 0 1-4.667-1.066 12.478 12.478 0 0 1-3.824-2.843c-1.08-1.185-1.945-2.583-2.593-4.194a13.581 13.581 0 0 1-.973-5.117c0-1.801.325-3.507.973-5.118.648-1.611 1.512-3.01 2.593-4.194a11.559 11.559 0 0 1 3.824-2.772 11.212 11.212 0 0 1 4.667-.995zm21.781-80.747c2.42 0 4.3.355 5.64 1.066 1.34.71 2.29 1.587 2.852 2.63a6.427 6.427 0 0 1 .778 3.34c-.044 1.185-.195 2.204-.454 3.057-.26.853-.8 2.606-1.62 5.26a589.268 589.268 0 0 1-2.788 8.743 1236.373 1236.373 0 0 0-3.047 9.453c-.994 3.128-1.75 5.592-2.269 7.393-1.123 3.79-2.55 6.42-4.278 7.89-1.728 1.469-3.846 2.203-6.352 2.203H39.023l1.945 12.795h65.342c4.148 0 6.223 1.943 6.223 5.828 0 1.896-.41 3.53-1.232 4.905-.821 1.374-2.442 2.061-4.862 2.061H38.505c-1.729 0-3.176-.426-4.343-1.28-1.167-.852-2.14-1.966-2.917-3.34a21.277 21.277 0 0 1-1.88-4.478 44.128 44.128 0 0 1-1.102-4.55c-.087-.568-.324-1.942-.713-4.122-.39-2.18-.865-4.904-1.426-8.174l-1.88-10.947c-.692-4.027-1.383-8.079-2.075-12.154-1.642-9.572-3.5-20.234-5.574-31.986H6.87c-1.296 0-2.377-.356-3.24-1.067a9.024 9.024 0 0 1-2.14-2.558 10.416 10.416 0 0 1-1.167-3.2C.108 8.53 0 7.488 0 6.54c0-1.896.583-3.46 1.75-4.69C2.917.615 4.494 0 6.482 0h13.095c1.728 0 3.111.284 4.148.853 1.037.569 1.858 1.28 2.463 2.132a8.548 8.548 0 0 1 1.297 2.701c.26.948.475 1.754.648 2.417.173.758.346 1.825.519 3.199.173 1.374.345 2.772.518 4.193.26 1.706.519 3.507.778 5.403h88.678z"/></svg>
\ No newline at end of file
diff --git a/frontend/src/icons/svg/size.svg b/frontend/src/icons/svg/size.svg
new file mode 100644
index 0000000..ddb25b8
--- /dev/null
+++ b/frontend/src/icons/svg/size.svg
@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M0 54.857h54.796v18.286H36.531V128H18.265V73.143H0V54.857zm127.857-36.571H91.935V128H72.456V18.286H36.534V0h91.326l-.003 18.286z"/></svg>
\ No newline at end of file
diff --git a/frontend/src/icons/svg/skill.svg b/frontend/src/icons/svg/skill.svg
new file mode 100644
index 0000000..a3b7312
--- /dev/null
+++ b/frontend/src/icons/svg/skill.svg
@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M31.652 93.206h33.401c1.44 2.418 3.077 4.663 4.93 6.692h-38.33v-6.692zm0-10.586h28.914a44.8 44.8 0 0 1-1.264-6.688h-27.65v6.688zm0-17.27H59.39c.288-2.286.714-4.532 1.34-6.687H31.65v6.687h.003zm53.913 44.84v5.85c0 2.798-2.095 5.075-4.667 5.075h-70.07c-2.576 0-4.663-2.277-4.663-5.075V31.26l23.22-20.96v22.25H17.16v6.688h18.39V6.688h45.348c2.576 0 4.667 2.277 4.667 5.066v20.009c1.987-.675 4.053-1.128 6.17-1.445v-18.56C91.738 5.28 86.874 0 80.902 0H31.15L0 28.118v87.917c0 6.48 4.859 11.759 10.832 11.759h70.07c5.974 0 10.837-5.27 10.837-11.759v-4.41c-2.117-.312-4.183-.765-6.17-1.435h-.004zM23.279 58.667h-7.96v6.688h7.96v-6.688zm-7.956 41.23h7.96v-6.691h-7.96v6.692zm7.956-23.96h-7.96v6.687h7.96v-6.688zm89.718-15.042l-4.896-4.07-12.447 17.613-11.19-9.305-3.762 5.311 16.091 13.38 16.204-22.929zM128 70.978c0-18.632-13.97-33.782-31.147-33.782-17.168 0-31.135 15.155-31.135 33.782 0 18.628 13.97 33.783 31.135 33.783 17.172 0 31.143-15.15 31.143-33.783H128zm-6.17 0c0 14.933-11.203 27.1-24.981 27.1-13.77 0-24.987-12.158-24.987-27.1 0-14.941 11.195-27.099 24.987-27.099 13.778 0 24.982 12.158 24.982 27.1z"/></svg>
\ No newline at end of file
diff --git a/frontend/src/icons/svg/star.svg b/frontend/src/icons/svg/star.svg
new file mode 100644
index 0000000..6cf86e6
--- /dev/null
+++ b/frontend/src/icons/svg/star.svg
@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M70.66 4.328l14.01 29.693c1.088 2.29 3.177 3.882 5.603 4.25l31.347 4.76c6.087.926 8.528 8.756 4.117 13.247L103.05 79.395c-1.75 1.78-2.544 4.352-2.132 6.867l5.352 32.641c1.043 6.337-5.33 11.182-10.778 8.19l-28.039-15.409a7.13 7.13 0 0 0-6.91 0l-28.039 15.41c-5.448 2.99-11.821-1.854-10.777-8.19l5.352-32.642c.415-2.515-.387-5.088-2.136-6.867L2.264 56.278C-2.146 51.787.286 43.957 6.38 43.031l31.343-4.76c2.419-.368 4.51-1.96 5.595-4.25L57.334 4.328c2.728-5.77 10.605-5.77 13.325 0z"/></svg>
\ No newline at end of file
diff --git a/frontend/src/icons/svg/tab.svg b/frontend/src/icons/svg/tab.svg
new file mode 100644
index 0000000..b4b48e4
--- /dev/null
+++ b/frontend/src/icons/svg/tab.svg
@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M78.921.052H49.08c-1.865 0-3.198 1.599-3.198 3.464v6.661c0 1.865 1.6 3.464 3.198 3.464h29.84c1.865 0 3.198-1.599 3.198-3.464V3.516C82.385 1.65 80.786.052 78.92.052zm45.563 0H94.642c-1.865 0-3.464 1.599-3.464 3.464v6.661c0 1.865 1.599 3.464 3.464 3.464h29.842c1.865-.266 3.464-1.599 3.464-3.464V3.516c0-1.865-1.599-3.464-3.464-3.464zm0 22.382H40.02c-1.866 0-3.464-1.599-3.464-3.464V3.516c0-1.865-1.599-3.464-3.464-3.464H3.516C1.65.052.052 1.651.052 3.516V124.75c0 1.598 1.599 3.197 3.464 3.197h120.968c1.865 0 3.464-1.599 3.464-3.464V25.898c0-1.865-1.599-3.464-3.464-3.464z"/></svg>
\ No newline at end of file
diff --git a/frontend/src/icons/svg/table.svg b/frontend/src/icons/svg/table.svg
new file mode 100644
index 0000000..0e3dc9d
--- /dev/null
+++ b/frontend/src/icons/svg/table.svg
@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M.006.064h127.988v31.104H.006V.064zm0 38.016h38.396v41.472H.006V38.08zm0 48.384h38.396v41.472H.006V86.464zM44.802 38.08h38.396v41.472H44.802V38.08zm0 48.384h38.396v41.472H44.802V86.464zM89.598 38.08h38.396v41.472H89.598zm0 48.384h38.396v41.472H89.598z"/><path d="M.006.064h127.988v31.104H.006V.064zm0 38.016h38.396v41.472H.006V38.08zm0 48.384h38.396v41.472H.006V86.464zM44.802 38.08h38.396v41.472H44.802V38.08zm0 48.384h38.396v41.472H44.802V86.464zM89.598 38.08h38.396v41.472H89.598zm0 48.384h38.396v41.472H89.598z"/></svg>
\ No newline at end of file
diff --git a/frontend/src/icons/svg/theme.svg b/frontend/src/icons/svg/theme.svg
new file mode 100644
index 0000000..5982a2f
--- /dev/null
+++ b/frontend/src/icons/svg/theme.svg
@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M125.5 36.984L95.336 2.83C93.735 1.018 91.565 0 89.3 0c-2.263 0-4.433 1.018-6.033 2.83l-3.786 4.286c-1.6 1.812-3.77 2.83-6.032 2.831H54.553c-2.263 0-4.434-1.018-6.033-2.83L44.734 2.83C43.134 1.018 40.964 0 38.701 0c-2.263 0-4.434 1.018-6.034 2.83L2.5 36.984C.9 38.796 0 41.254 0 43.815c0 2.562.899 5.02 2.5 6.831L14.565 64.31c2.178 2.468 5.367 3.403 8.33 2.444 1.35-.435 2.709.592 2.709 2.18v49.407c0 5.313 3.84 9.66 8.532 9.66h59.726c4.693 0 8.532-4.347 8.532-9.66V68.934c0-1.59 1.36-2.616 2.71-2.181 2.962.96 6.15.024 8.329-2.444L125.5 50.646c1.6-1.811 2.499-4.269 2.499-6.83 0-2.563-.899-5.02-2.5-6.832z"/></svg>
\ No newline at end of file
diff --git a/frontend/src/icons/svg/tree-table.svg b/frontend/src/icons/svg/tree-table.svg
new file mode 100644
index 0000000..8aafdb8
--- /dev/null
+++ b/frontend/src/icons/svg/tree-table.svg
@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M44.8 0h79.543C126.78 0 128 1.422 128 4.267v23.466c0 2.845-1.219 4.267-3.657 4.267H44.8c-2.438 0-3.657-1.422-3.657-4.267V4.267C41.143 1.422 42.362 0 44.8 0zm22.857 48h56.686c2.438 0 3.657 1.422 3.657 4.267v23.466c0 2.845-1.219 4.267-3.657 4.267H67.657C65.22 80 64 78.578 64 75.733V52.267C64 49.422 65.219 48 67.657 48zm0 48h56.686c2.438 0 3.657 1.422 3.657 4.267v23.466c0 2.845-1.219 4.267-3.657 4.267H67.657C65.22 128 64 126.578 64 123.733v-23.466C64 97.422 65.219 96 67.657 96zM50.286 68.267c2.02 0 3.657-1.91 3.657-4.267 0-2.356-1.638-4.267-3.657-4.267H17.37V32h6.4c2.02 0 3.658-1.91 3.658-4.267V4.267C27.429 1.91 25.79 0 23.77 0H3.657C1.637 0 0 1.91 0 4.267v23.466C0 30.09 1.637 32 3.657 32h6.4v80c0 2.356 1.638 4.267 3.657 4.267h36.572c2.02 0 3.657-1.91 3.657-4.267 0-2.356-1.638-4.267-3.657-4.267H17.37V68.267h32.915z"/></svg>
\ No newline at end of file
diff --git a/frontend/src/icons/svg/tree.svg b/frontend/src/icons/svg/tree.svg
new file mode 100644
index 0000000..dd4b7dd
--- /dev/null
+++ b/frontend/src/icons/svg/tree.svg
@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M126.713 90.023c.858.985 1.287 2.134 1.287 3.447v29.553c0 1.423-.429 2.6-1.287 3.53-.858.93-1.907 1.395-3.146 1.395H97.824c-1.145 0-2.146-.465-3.004-1.395-.858-.93-1.287-2.107-1.287-3.53V93.47c0-.875.19-1.696.572-2.462.382-.766.906-1.368 1.573-1.806a3.84 3.84 0 0 1 2.146-.657h9.725V69.007a3.84 3.84 0 0 0-.43-1.806 3.569 3.569 0 0 0-1.143-1.313 2.714 2.714 0 0 0-1.573-.492h-36.47v23.149h9.725c1.144 0 2.145.492 3.004 1.478.858.985 1.287 2.134 1.287 3.447v29.553c0 .876-.191 1.696-.573 2.463-.38.766-.905 1.368-1.573 1.806a3.84 3.84 0 0 1-2.145.656H51.915a3.84 3.84 0 0 1-2.145-.656c-.668-.438-1.216-1.04-1.645-1.806a4.96 4.96 0 0 1-.644-2.463V93.47c0-1.313.43-2.462 1.288-3.447.858-.986 1.907-1.478 3.146-1.478h9.582v-23.15h-37.9c-.953 0-1.74.356-2.359 1.068-.62.711-.93 1.56-.93 2.544v19.538h9.726c1.239 0 2.264.492 3.074 1.478.81.985 1.216 2.134 1.216 3.447v29.553c0 1.423-.405 2.6-1.216 3.53-.81.93-1.835 1.395-3.074 1.395H4.29c-.476 0-.93-.082-1.358-.246a4.1 4.1 0 0 1-1.144-.657 4.658 4.658 0 0 1-.93-1.067 5.186 5.186 0 0 1-.643-1.395 5.566 5.566 0 0 1-.215-1.56V93.47c0-.437.048-.875.143-1.313a3.95 3.95 0 0 1 .429-1.15c.19-.328.429-.656.715-.984.286-.329.572-.602.858-.821.286-.22.62-.383 1.001-.493.382-.11.763-.164 1.144-.164h9.726V61.619c0-.985.31-1.833.93-2.544.619-.712 1.358-1.068 2.216-1.068h44.335V39.62h-9.582c-1.24 0-2.288-.492-3.146-1.477a5.09 5.09 0 0 1-1.287-3.448V5.14c0-1.423.429-2.627 1.287-3.612.858-.985 1.907-1.477 3.146-1.477h25.743c.763 0 1.478.246 2.145.739a5.17 5.17 0 0 1 1.573 1.888c.382.766.573 1.587.573 2.462v29.553c0 1.313-.43 2.463-1.287 3.448-.859.985-1.86 1.477-3.004 1.477h-9.725v18.389h42.762c.954 0 1.74.355 2.36 1.067.62.711.93 1.56.93 2.545v26.925h9.582c1.239 0 2.288.492 3.146 1.478z"/></svg>
\ No newline at end of file
diff --git a/frontend/src/icons/svg/user.svg b/frontend/src/icons/svg/user.svg
new file mode 100644
index 0000000..0ba0716
--- /dev/null
+++ b/frontend/src/icons/svg/user.svg
@@ -0,0 +1 @@
+<svg width="130" height="130" xmlns="http://www.w3.org/2000/svg"><path d="M63.444 64.996c20.633 0 37.359-14.308 37.359-31.953 0-17.649-16.726-31.952-37.359-31.952-20.631 0-37.36 14.303-37.358 31.952 0 17.645 16.727 31.953 37.359 31.953zM80.57 75.65H49.434c-26.652 0-48.26 18.477-48.26 41.27v2.664c0 9.316 21.608 9.325 48.26 9.325H80.57c26.649 0 48.256-.344 48.256-9.325v-2.663c0-22.794-21.605-41.271-48.256-41.271z" stroke="#979797"/></svg>
\ No newline at end of file
diff --git a/frontend/src/icons/svg/wechat.svg b/frontend/src/icons/svg/wechat.svg
new file mode 100644
index 0000000..c586e55
--- /dev/null
+++ b/frontend/src/icons/svg/wechat.svg
@@ -0,0 +1 @@
+<svg width="128" height="110" xmlns="http://www.w3.org/2000/svg"><path d="M86.635 33.334c1.467 0 2.917.113 4.358.283C87.078 14.392 67.58.111 45.321.111 20.44.111.055 17.987.055 40.687c0 13.104 6.781 23.863 18.115 32.209l-4.527 14.352 15.82-8.364c5.666 1.182 10.207 2.395 15.858 2.395 1.42 0 2.829-.073 4.227-.189-.886-3.19-1.398-6.53-1.398-9.996 0-20.845 16.98-37.76 38.485-37.76zm-24.34-12.936c3.407 0 5.665 2.363 5.665 5.954 0 3.576-2.258 5.97-5.666 5.97-3.392 0-6.795-2.395-6.795-5.97 0-3.591 3.403-5.954 6.795-5.954zM30.616 32.323c-3.393 0-6.818-2.395-6.818-5.971 0-3.591 3.425-5.954 6.818-5.954 3.392 0 5.65 2.363 5.65 5.954 0 3.576-2.258 5.97-5.65 5.97z"/><path d="M127.945 70.52c0-19.075-18.108-34.623-38.448-34.623-21.537 0-38.5 15.548-38.5 34.623 0 19.108 16.963 34.622 38.5 34.622 4.508 0 9.058-1.2 13.584-2.395l12.414 7.167-3.404-11.923c9.087-7.184 15.854-16.712 15.854-27.471zm-50.928-5.97c-2.254 0-4.53-2.362-4.53-4.773 0-2.378 2.276-4.771 4.53-4.771 3.422 0 5.665 2.393 5.665 4.771 0 2.41-2.243 4.773-5.665 4.773zm24.897 0c-2.24 0-4.498-2.362-4.498-4.773 0-2.378 2.258-4.771 4.498-4.771 3.392 0 5.665 2.393 5.665 4.771 0 2.41-2.273 4.773-5.665 4.773z"/></svg>
\ No newline at end of file
diff --git a/frontend/src/icons/svg/zip.svg b/frontend/src/icons/svg/zip.svg
new file mode 100644
index 0000000..f806fc4
--- /dev/null
+++ b/frontend/src/icons/svg/zip.svg
@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M78.527 116.793c.178.008.348.024.527.024h40.233c4.711-.005 8.53-3.677 8.534-8.21V18.895c-.004-4.532-3.823-8.204-8.534-8.209H79.054c-.179 0-.353.016-.527.024V0L0 10.082v107.406l78.527 10.342v-11.037zm0-101.362c.174-.024.348-.052.527-.052h40.233c2.018 0 3.659 1.578 3.659 3.52v89.713c-.003 1.942-1.64 3.517-3.659 3.519H79.054c-.179 0-.353-.028-.527-.052V15.431zM30.262 75.757l-18.721-.46V72.37l11.3-16.673v-.148l-10.266.164v-4.51l17.504-.44v3.264L18.696 70.76v.144l11.566.176v4.678zm9.419.231l-5.823-.144V50.671l5.823-.144v25.461zm22.255-11.632c-2.168 1.922-5.353 2.76-9.02 2.736-.702.004-1.402-.04-2.097-.131v9.303l-5.997-.148V50.743c1.852-.352 4.473-.647 8.218-.743 3.838-.096 6.608.539 8.48 1.913 1.807 1.306 3.032 3.5 3.032 6.112s-.926 4.833-2.612 6.331h-.004zM53.36 54.45c-.856-.01-1.71.083-2.541.275v7.682c.523.116 1.167.152 2.06.152 3.301-.004 5.36-1.614 5.36-4.314 0-2.425-1.772-3.843-4.875-3.791l-.004-.004zm39.847-37.066h9.564v3.795h-9.564v-3.795zm-9.568 5.68h9.564v3.8h-9.564v-3.8zm9.568 6.216h9.564v3.799h-9.564V29.28zm0 12h9.564v3.794h-9.564V41.28zm-9.568-6.096h9.564v3.795h-9.564v-3.795zm9.472 47.064c2.512 0 4.921-.96 6.697-2.67 1.776-1.708 2.773-4.026 2.772-6.442l-1.748-15.263c0-5.033-2.492-9.112-7.725-9.112-5.232 0-7.72 4.079-7.72 9.112l-1.752 15.263c-.001 2.417.996 4.735 2.773 6.444 1.777 1.71 4.187 2.669 6.7 2.668h.003zm-3.135-16.75h6.27v12.743h-6.27V65.5z"/></svg>
\ No newline at end of file
diff --git a/frontend/src/icons/svgo.yml b/frontend/src/icons/svgo.yml
new file mode 100644
index 0000000..d11906a
--- /dev/null
+++ b/frontend/src/icons/svgo.yml
@@ -0,0 +1,22 @@
+# replace default config
+
+# multipass: true
+# full: true
+
+plugins:
+
+  # - name
+  #
+  # or:
+  # - name: false
+  # - name: true
+  #
+  # or:
+  # - name:
+  #     param1: 1
+  #     param2: 2
+
+- removeAttrs:
+    attrs:
+      - 'fill'
+      - 'fill-rule'
diff --git a/frontend/src/layout/components/AppMain.vue b/frontend/src/layout/components/AppMain.vue
new file mode 100644
index 0000000..a897638
--- /dev/null
+++ b/frontend/src/layout/components/AppMain.vue
@@ -0,0 +1,57 @@
+<template>
+  <section class="app-main">
+    <transition name="fade-transform" mode="out-in">
+      <keep-alive :include="cachedViews">
+        <router-view :key="key" />
+      </keep-alive>
+    </transition>
+  </section>
+</template>
+
+<script>
+export default {
+  name: 'AppMain',
+  computed: {
+    cachedViews() {
+      return this.$store.state.tagsView.cachedViews
+    },
+    key() {
+      return this.$route.path
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.app-main {
+  /* 50= navbar  50  */
+  min-height: calc(100vh - 50px);
+  width: 100%;
+  position: relative;
+  overflow: hidden;
+}
+
+.fixed-header+.app-main {
+  padding-top: 50px;
+}
+
+.hasTagsView {
+  .app-main {
+    /* 84 = navbar + tags-view = 50 + 34 */
+    min-height: calc(100vh - 84px);
+  }
+
+  .fixed-header+.app-main {
+    padding-top: 84px;
+  }
+}
+</style>
+
+<style lang="scss">
+// fix css style bug in open el-dialog
+.el-popup-parent--hidden {
+  .fixed-header {
+    padding-right: 15px;
+  }
+}
+</style>
diff --git a/frontend/src/layout/components/Navbar.vue b/frontend/src/layout/components/Navbar.vue
new file mode 100644
index 0000000..37bc1e6
--- /dev/null
+++ b/frontend/src/layout/components/Navbar.vue
@@ -0,0 +1,167 @@
+<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" />
+
+        <el-tooltip content="Global Size" effect="dark" placement="bottom">
+          <size-select id="size-select" class="right-menu-item hover-effect" />
+        </el-tooltip>
+
+      </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>Profile</el-dropdown-item>
+          </router-link>
+          <router-link to="/">
+            <el-dropdown-item>Dashboard</el-dropdown-item>
+          </router-link>
+          <a target="_blank" href="https://github.com/PanJiaChen/vue-element-admin/">
+            <el-dropdown-item>Github</el-dropdown-item>
+          </a>
+          <a target="_blank" href="https://panjiachen.github.io/vue-element-admin-site/#/">
+            <el-dropdown-item>Docs</el-dropdown-item>
+          </a>
+          <el-dropdown-item divided @click.native="logout">
+            <span style="display:block;">Log Out</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 SizeSelect from '@/components/SizeSelect'
+import Search from '@/components/HeaderSearch'
+
+export default {
+  components: {
+    Breadcrumb,
+    Hamburger,
+    ErrorLog,
+    Screenfull,
+    SizeSelect,
+    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>
diff --git a/frontend/src/layout/components/Settings/index.vue b/frontend/src/layout/components/Settings/index.vue
new file mode 100644
index 0000000..32ef018
--- /dev/null
+++ b/frontend/src/layout/components/Settings/index.vue
@@ -0,0 +1,108 @@
+<template>
+  <div class="drawer-container">
+    <div>
+      <h3 class="drawer-title">Page style setting</h3>
+
+      <div class="drawer-item">
+        <span>Theme Color</span>
+        <theme-picker style="float: right;height: 26px;margin: -3px 8px 0 0;" @change="themeChange" />
+      </div>
+
+      <div class="drawer-item">
+        <span>Open Tags-View</span>
+        <el-switch v-model="tagsView" class="drawer-switch" />
+      </div>
+
+      <div class="drawer-item">
+        <span>Fixed Header</span>
+        <el-switch v-model="fixedHeader" class="drawer-switch" />
+      </div>
+
+      <div class="drawer-item">
+        <span>Sidebar Logo</span>
+        <el-switch v-model="sidebarLogo" class="drawer-switch" />
+      </div>
+
+    </div>
+  </div>
+</template>
+
+<script>
+import ThemePicker from '@/components/ThemePicker'
+
+export default {
+  components: { ThemePicker },
+  data() {
+    return {}
+  },
+  computed: {
+    fixedHeader: {
+      get() {
+        return this.$store.state.settings.fixedHeader
+      },
+      set(val) {
+        this.$store.dispatch('settings/changeSetting', {
+          key: 'fixedHeader',
+          value: val
+        })
+      }
+    },
+    tagsView: {
+      get() {
+        return this.$store.state.settings.tagsView
+      },
+      set(val) {
+        this.$store.dispatch('settings/changeSetting', {
+          key: 'tagsView',
+          value: val
+        })
+      }
+    },
+    sidebarLogo: {
+      get() {
+        return this.$store.state.settings.sidebarLogo
+      },
+      set(val) {
+        this.$store.dispatch('settings/changeSetting', {
+          key: 'sidebarLogo',
+          value: val
+        })
+      }
+    }
+  },
+  methods: {
+    themeChange(val) {
+      this.$store.dispatch('settings/changeSetting', {
+        key: 'theme',
+        value: val
+      })
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.drawer-container {
+  padding: 24px;
+  font-size: 14px;
+  line-height: 1.5;
+  word-wrap: break-word;
+
+  .drawer-title {
+    margin-bottom: 12px;
+    color: rgba(0, 0, 0, .85);
+    font-size: 14px;
+    line-height: 22px;
+  }
+
+  .drawer-item {
+    color: rgba(0, 0, 0, .65);
+    font-size: 14px;
+    padding: 12px 0;
+  }
+
+  .drawer-switch {
+    float: right
+  }
+}
+</style>
diff --git a/frontend/src/layout/components/Sidebar/FixiOSBug.js b/frontend/src/layout/components/Sidebar/FixiOSBug.js
new file mode 100644
index 0000000..bc14856
--- /dev/null
+++ b/frontend/src/layout/components/Sidebar/FixiOSBug.js
@@ -0,0 +1,26 @@
+export default {
+  computed: {
+    device() {
+      return this.$store.state.app.device
+    }
+  },
+  mounted() {
+    // In order to fix the click on menu on the ios device will trigger the mouseleave bug
+    // https://github.com/PanJiaChen/vue-element-admin/issues/1135
+    this.fixBugIniOS()
+  },
+  methods: {
+    fixBugIniOS() {
+      const $subMenu = this.$refs.subMenu
+      if ($subMenu) {
+        const handleMouseleave = $subMenu.handleMouseleave
+        $subMenu.handleMouseleave = (e) => {
+          if (this.device === 'mobile') {
+            return
+          }
+          handleMouseleave(e)
+        }
+      }
+    }
+  }
+}
diff --git a/frontend/src/layout/components/Sidebar/Item.vue b/frontend/src/layout/components/Sidebar/Item.vue
new file mode 100644
index 0000000..aa1f5da
--- /dev/null
+++ b/frontend/src/layout/components/Sidebar/Item.vue
@@ -0,0 +1,41 @@
+<script>
+export default {
+  name: 'MenuItem',
+  functional: true,
+  props: {
+    icon: {
+      type: String,
+      default: ''
+    },
+    title: {
+      type: String,
+      default: ''
+    }
+  },
+  render(h, context) {
+    const { icon, title } = context.props
+    const vnodes = []
+
+    if (icon) {
+      if (icon.includes('el-icon')) {
+        vnodes.push(<i class={[icon, 'sub-el-icon']} />)
+      } else {
+        vnodes.push(<svg-icon icon-class={icon}/>)
+      }
+    }
+
+    if (title) {
+      vnodes.push(<span slot='title'>{(title)}</span>)
+    }
+    return vnodes
+  }
+}
+</script>
+
+<style scoped>
+.sub-el-icon {
+  color: currentColor;
+  width: 1em;
+  height: 1em;
+}
+</style>
diff --git a/frontend/src/layout/components/Sidebar/Link.vue b/frontend/src/layout/components/Sidebar/Link.vue
new file mode 100644
index 0000000..530b3d5
--- /dev/null
+++ b/frontend/src/layout/components/Sidebar/Link.vue
@@ -0,0 +1,43 @@
+<template>
+  <component :is="type" v-bind="linkProps(to)">
+    <slot />
+  </component>
+</template>
+
+<script>
+import { isExternal } from '@/utils/validate'
+
+export default {
+  props: {
+    to: {
+      type: String,
+      required: true
+    }
+  },
+  computed: {
+    isExternal() {
+      return isExternal(this.to)
+    },
+    type() {
+      if (this.isExternal) {
+        return 'a'
+      }
+      return 'router-link'
+    }
+  },
+  methods: {
+    linkProps(to) {
+      if (this.isExternal) {
+        return {
+          href: to,
+          target: '_blank',
+          rel: 'noopener'
+        }
+      }
+      return {
+        to: to
+      }
+    }
+  }
+}
+</script>
diff --git a/frontend/src/layout/components/Sidebar/Logo.vue b/frontend/src/layout/components/Sidebar/Logo.vue
new file mode 100644
index 0000000..ac0c8d8
--- /dev/null
+++ b/frontend/src/layout/components/Sidebar/Logo.vue
@@ -0,0 +1,82 @@
+<template>
+  <div class="sidebar-logo-container" :class="{'collapse':collapse}">
+    <transition name="sidebarLogoFade">
+      <router-link v-if="collapse" key="collapse" class="sidebar-logo-link" to="/">
+        <img v-if="logo" :src="logo" class="sidebar-logo">
+        <h1 v-else class="sidebar-title">{{ title }} </h1>
+      </router-link>
+      <router-link v-else key="expand" class="sidebar-logo-link" to="/">
+        <img v-if="logo" :src="logo" class="sidebar-logo">
+        <h1 class="sidebar-title">{{ title }} </h1>
+      </router-link>
+    </transition>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'SidebarLogo',
+  props: {
+    collapse: {
+      type: Boolean,
+      required: true
+    }
+  },
+  data() {
+    return {
+      title: 'Vue Element Admin',
+      logo: 'https://wpimg.wallstcn.com/69a1c46c-eb1c-4b46-8bd4-e9e686ef5251.png'
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.sidebarLogoFade-enter-active {
+  transition: opacity 1.5s;
+}
+
+.sidebarLogoFade-enter,
+.sidebarLogoFade-leave-to {
+  opacity: 0;
+}
+
+.sidebar-logo-container {
+  position: relative;
+  width: 100%;
+  height: 50px;
+  line-height: 50px;
+  background: #2b2f3a;
+  text-align: center;
+  overflow: hidden;
+
+  & .sidebar-logo-link {
+    height: 100%;
+    width: 100%;
+
+    & .sidebar-logo {
+      width: 32px;
+      height: 32px;
+      vertical-align: middle;
+      margin-right: 12px;
+    }
+
+    & .sidebar-title {
+      display: inline-block;
+      margin: 0;
+      color: #fff;
+      font-weight: 600;
+      line-height: 50px;
+      font-size: 14px;
+      font-family: Avenir, Helvetica Neue, Arial, Helvetica, sans-serif;
+      vertical-align: middle;
+    }
+  }
+
+  &.collapse {
+    .sidebar-logo {
+      margin-right: 0px;
+    }
+  }
+}
+</style>
diff --git a/frontend/src/layout/components/Sidebar/SidebarItem.vue b/frontend/src/layout/components/Sidebar/SidebarItem.vue
new file mode 100644
index 0000000..a418c3d
--- /dev/null
+++ b/frontend/src/layout/components/Sidebar/SidebarItem.vue
@@ -0,0 +1,95 @@
+<template>
+  <div v-if="!item.hidden">
+    <template v-if="hasOneShowingChild(item.children,item) && (!onlyOneChild.children||onlyOneChild.noShowingChildren)&&!item.alwaysShow">
+      <app-link v-if="onlyOneChild.meta" :to="resolvePath(onlyOneChild.path)">
+        <el-menu-item :index="resolvePath(onlyOneChild.path)" :class="{'submenu-title-noDropdown':!isNest}">
+          <item :icon="onlyOneChild.meta.icon||(item.meta&&item.meta.icon)" :title="onlyOneChild.meta.title" />
+        </el-menu-item>
+      </app-link>
+    </template>
+
+    <el-submenu v-else ref="subMenu" :index="resolvePath(item.path)" popper-append-to-body>
+      <template slot="title">
+        <item v-if="item.meta" :icon="item.meta && item.meta.icon" :title="item.meta.title" />
+      </template>
+      <sidebar-item
+        v-for="child in item.children"
+        :key="child.path"
+        :is-nest="true"
+        :item="child"
+        :base-path="resolvePath(child.path)"
+        class="nest-menu"
+      />
+    </el-submenu>
+  </div>
+</template>
+
+<script>
+import path from 'path'
+import { isExternal } from '@/utils/validate'
+import Item from './Item'
+import AppLink from './Link'
+import FixiOSBug from './FixiOSBug'
+
+export default {
+  name: 'SidebarItem',
+  components: { Item, AppLink },
+  mixins: [FixiOSBug],
+  props: {
+    // route object
+    item: {
+      type: Object,
+      required: true
+    },
+    isNest: {
+      type: Boolean,
+      default: false
+    },
+    basePath: {
+      type: String,
+      default: ''
+    }
+  },
+  data() {
+    // To fix https://github.com/PanJiaChen/vue-admin-template/issues/237
+    // TODO: refactor with render function
+    this.onlyOneChild = null
+    return {}
+  },
+  methods: {
+    hasOneShowingChild(children = [], parent) {
+      const showingChildren = children.filter(item => {
+        if (item.hidden) {
+          return false
+        } else {
+          // Temp set(will be used if only has one showing child)
+          this.onlyOneChild = item
+          return true
+        }
+      })
+
+      // When there is only one child router, the child router is displayed by default
+      if (showingChildren.length === 1) {
+        return true
+      }
+
+      // Show parent if there are no child router to display
+      if (showingChildren.length === 0) {
+        this.onlyOneChild = { ... parent, path: '', noShowingChildren: true }
+        return true
+      }
+
+      return false
+    },
+    resolvePath(routePath) {
+      if (isExternal(routePath)) {
+        return routePath
+      }
+      if (isExternal(this.basePath)) {
+        return this.basePath
+      }
+      return path.resolve(this.basePath, routePath)
+    }
+  }
+}
+</script>
diff --git a/frontend/src/layout/components/Sidebar/index.vue b/frontend/src/layout/components/Sidebar/index.vue
new file mode 100644
index 0000000..fb014a2
--- /dev/null
+++ b/frontend/src/layout/components/Sidebar/index.vue
@@ -0,0 +1,54 @@
+<template>
+  <div :class="{'has-logo':showLogo}">
+    <logo v-if="showLogo" :collapse="isCollapse" />
+    <el-scrollbar wrap-class="scrollbar-wrapper">
+      <el-menu
+        :default-active="activeMenu"
+        :collapse="isCollapse"
+        :background-color="variables.menuBg"
+        :text-color="variables.menuText"
+        :unique-opened="false"
+        :active-text-color="variables.menuActiveText"
+        :collapse-transition="false"
+        mode="vertical"
+      >
+        <sidebar-item v-for="route in permission_routes" :key="route.path" :item="route" :base-path="route.path" />
+      </el-menu>
+    </el-scrollbar>
+  </div>
+</template>
+
+<script>
+import { mapGetters } from 'vuex'
+import Logo from './Logo'
+import SidebarItem from './SidebarItem'
+import variables from '@/styles/variables.scss'
+
+export default {
+  components: { SidebarItem, Logo },
+  computed: {
+    ...mapGetters([
+      'permission_routes',
+      'sidebar'
+    ]),
+    activeMenu() {
+      const route = this.$route
+      const { meta, path } = route
+      // if set path, the sidebar will highlight the path you set
+      if (meta.activeMenu) {
+        return meta.activeMenu
+      }
+      return path
+    },
+    showLogo() {
+      return this.$store.state.settings.sidebarLogo
+    },
+    variables() {
+      return variables
+    },
+    isCollapse() {
+      return !this.sidebar.opened
+    }
+  }
+}
+</script>
diff --git a/frontend/src/layout/components/TagsView/ScrollPane.vue b/frontend/src/layout/components/TagsView/ScrollPane.vue
new file mode 100644
index 0000000..bb753a1
--- /dev/null
+++ b/frontend/src/layout/components/TagsView/ScrollPane.vue
@@ -0,0 +1,94 @@
+<template>
+  <el-scrollbar ref="scrollContainer" :vertical="false" class="scroll-container" @wheel.native.prevent="handleScroll">
+    <slot />
+  </el-scrollbar>
+</template>
+
+<script>
+const tagAndTagSpacing = 4 // tagAndTagSpacing
+
+export default {
+  name: 'ScrollPane',
+  data() {
+    return {
+      left: 0
+    }
+  },
+  computed: {
+    scrollWrapper() {
+      return this.$refs.scrollContainer.$refs.wrap
+    }
+  },
+  mounted() {
+    this.scrollWrapper.addEventListener('scroll', this.emitScroll, true)
+  },
+  beforeDestroy() {
+    this.scrollWrapper.removeEventListener('scroll', this.emitScroll)
+  },
+  methods: {
+    handleScroll(e) {
+      const eventDelta = e.wheelDelta || -e.deltaY * 40
+      const $scrollWrapper = this.scrollWrapper
+      $scrollWrapper.scrollLeft = $scrollWrapper.scrollLeft + eventDelta / 4
+    },
+    emitScroll() {
+      this.$emit('scroll')
+    },
+    moveToTarget(currentTag) {
+      const $container = this.$refs.scrollContainer.$el
+      const $containerWidth = $container.offsetWidth
+      const $scrollWrapper = this.scrollWrapper
+      const tagList = this.$parent.$refs.tag
+
+      let firstTag = null
+      let lastTag = null
+
+      // find first tag and last tag
+      if (tagList.length > 0) {
+        firstTag = tagList[0]
+        lastTag = tagList[tagList.length - 1]
+      }
+
+      if (firstTag === currentTag) {
+        $scrollWrapper.scrollLeft = 0
+      } else if (lastTag === currentTag) {
+        $scrollWrapper.scrollLeft = $scrollWrapper.scrollWidth - $containerWidth
+      } else {
+        // find preTag and nextTag
+        const currentIndex = tagList.findIndex(item => item === currentTag)
+        const prevTag = tagList[currentIndex - 1]
+        const nextTag = tagList[currentIndex + 1]
+
+        // the tag's offsetLeft after of nextTag
+        const afterNextTagOffsetLeft = nextTag.$el.offsetLeft + nextTag.$el.offsetWidth + tagAndTagSpacing
+
+        // the tag's offsetLeft before of prevTag
+        const beforePrevTagOffsetLeft = prevTag.$el.offsetLeft - tagAndTagSpacing
+
+        if (afterNextTagOffsetLeft > $scrollWrapper.scrollLeft + $containerWidth) {
+          $scrollWrapper.scrollLeft = afterNextTagOffsetLeft - $containerWidth
+        } else if (beforePrevTagOffsetLeft < $scrollWrapper.scrollLeft) {
+          $scrollWrapper.scrollLeft = beforePrevTagOffsetLeft
+        }
+      }
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.scroll-container {
+  white-space: nowrap;
+  position: relative;
+  overflow: hidden;
+  width: 100%;
+  ::v-deep {
+    .el-scrollbar__bar {
+      bottom: 0px;
+    }
+    .el-scrollbar__wrap {
+      height: 49px;
+    }
+  }
+}
+</style>
diff --git a/frontend/src/layout/components/TagsView/index.vue b/frontend/src/layout/components/TagsView/index.vue
new file mode 100644
index 0000000..d2a56e7
--- /dev/null
+++ b/frontend/src/layout/components/TagsView/index.vue
@@ -0,0 +1,292 @@
+<template>
+  <div id="tags-view-container" class="tags-view-container">
+    <scroll-pane ref="scrollPane" class="tags-view-wrapper" @scroll="handleScroll">
+      <router-link
+        v-for="tag in visitedViews"
+        ref="tag"
+        :key="tag.path"
+        :class="isActive(tag)?'active':''"
+        :to="{ path: tag.path, query: tag.query, fullPath: tag.fullPath }"
+        tag="span"
+        class="tags-view-item"
+        @click.middle.native="!isAffix(tag)?closeSelectedTag(tag):''"
+        @contextmenu.prevent.native="openMenu(tag,$event)"
+      >
+        {{ tag.title }}
+        <span v-if="!isAffix(tag)" class="el-icon-close" @click.prevent.stop="closeSelectedTag(tag)" />
+      </router-link>
+    </scroll-pane>
+    <ul v-show="visible" :style="{left:left+'px',top:top+'px'}" class="contextmenu">
+      <li @click="refreshSelectedTag(selectedTag)">Refresh</li>
+      <li v-if="!isAffix(selectedTag)" @click="closeSelectedTag(selectedTag)">Close</li>
+      <li @click="closeOthersTags">Close Others</li>
+      <li @click="closeAllTags(selectedTag)">Close All</li>
+    </ul>
+  </div>
+</template>
+
+<script>
+import ScrollPane from './ScrollPane'
+import path from 'path'
+
+export default {
+  components: { ScrollPane },
+  data() {
+    return {
+      visible: false,
+      top: 0,
+      left: 0,
+      selectedTag: {},
+      affixTags: []
+    }
+  },
+  computed: {
+    visitedViews() {
+      return this.$store.state.tagsView.visitedViews
+    },
+    routes() {
+      return this.$store.state.permission.routes
+    }
+  },
+  watch: {
+    $route() {
+      this.addTags()
+      this.moveToCurrentTag()
+    },
+    visible(value) {
+      if (value) {
+        document.body.addEventListener('click', this.closeMenu)
+      } else {
+        document.body.removeEventListener('click', this.closeMenu)
+      }
+    }
+  },
+  mounted() {
+    this.initTags()
+    this.addTags()
+  },
+  methods: {
+    isActive(route) {
+      return route.path === this.$route.path
+    },
+    isAffix(tag) {
+      return tag.meta && tag.meta.affix
+    },
+    filterAffixTags(routes, basePath = '/') {
+      let tags = []
+      routes.forEach(route => {
+        if (route.meta && route.meta.affix) {
+          const tagPath = path.resolve(basePath, route.path)
+          tags.push({
+            fullPath: tagPath,
+            path: tagPath,
+            name: route.name,
+            meta: { ...route.meta }
+          })
+        }
+        if (route.children) {
+          const tempTags = this.filterAffixTags(route.children, route.path)
+          if (tempTags.length >= 1) {
+            tags = [...tags, ...tempTags]
+          }
+        }
+      })
+      return tags
+    },
+    initTags() {
+      const affixTags = this.affixTags = this.filterAffixTags(this.routes)
+      for (const tag of affixTags) {
+        // Must have tag name
+        if (tag.name) {
+          this.$store.dispatch('tagsView/addVisitedView', tag)
+        }
+      }
+    },
+    addTags() {
+      const { name } = this.$route
+      if (name) {
+        this.$store.dispatch('tagsView/addView', this.$route)
+      }
+      return false
+    },
+    moveToCurrentTag() {
+      const tags = this.$refs.tag
+      this.$nextTick(() => {
+        for (const tag of tags) {
+          if (tag.to.path === this.$route.path) {
+            this.$refs.scrollPane.moveToTarget(tag)
+            // when query is different then update
+            if (tag.to.fullPath !== this.$route.fullPath) {
+              this.$store.dispatch('tagsView/updateVisitedView', this.$route)
+            }
+            break
+          }
+        }
+      })
+    },
+    refreshSelectedTag(view) {
+      this.$store.dispatch('tagsView/delCachedView', view).then(() => {
+        const { fullPath } = view
+        this.$nextTick(() => {
+          this.$router.replace({
+            path: '/redirect' + fullPath
+          })
+        })
+      })
+    },
+    closeSelectedTag(view) {
+      this.$store.dispatch('tagsView/delView', view).then(({ visitedViews }) => {
+        if (this.isActive(view)) {
+          this.toLastView(visitedViews, view)
+        }
+      })
+    },
+    closeOthersTags() {
+      this.$router.push(this.selectedTag)
+      this.$store.dispatch('tagsView/delOthersViews', this.selectedTag).then(() => {
+        this.moveToCurrentTag()
+      })
+    },
+    closeAllTags(view) {
+      this.$store.dispatch('tagsView/delAllViews').then(({ visitedViews }) => {
+        if (this.affixTags.some(tag => tag.path === view.path)) {
+          return
+        }
+        this.toLastView(visitedViews, view)
+      })
+    },
+    toLastView(visitedViews, view) {
+      const latestView = visitedViews.slice(-1)[0]
+      if (latestView) {
+        this.$router.push(latestView.fullPath)
+      } else {
+        // now the default is to redirect to the home page if there is no tags-view,
+        // you can adjust it according to your needs.
+        if (view.name === 'Dashboard') {
+          // to reload home page
+          this.$router.replace({ path: '/redirect' + view.fullPath })
+        } else {
+          this.$router.push('/')
+        }
+      }
+    },
+    openMenu(tag, e) {
+      const menuMinWidth = 105
+      const offsetLeft = this.$el.getBoundingClientRect().left // container margin left
+      const offsetWidth = this.$el.offsetWidth // container width
+      const maxLeft = offsetWidth - menuMinWidth // left boundary
+      const left = e.clientX - offsetLeft + 15 // 15: margin right
+
+      if (left > maxLeft) {
+        this.left = maxLeft
+      } else {
+        this.left = left
+      }
+
+      this.top = e.clientY
+      this.visible = true
+      this.selectedTag = tag
+    },
+    closeMenu() {
+      this.visible = false
+    },
+    handleScroll() {
+      this.closeMenu()
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.tags-view-container {
+  height: 34px;
+  width: 100%;
+  background: #fff;
+  border-bottom: 1px solid #d8dce5;
+  box-shadow: 0 1px 3px 0 rgba(0, 0, 0, .12), 0 0 3px 0 rgba(0, 0, 0, .04);
+  .tags-view-wrapper {
+    .tags-view-item {
+      display: inline-block;
+      position: relative;
+      cursor: pointer;
+      height: 26px;
+      line-height: 26px;
+      border: 1px solid #d8dce5;
+      color: #495060;
+      background: #fff;
+      padding: 0 8px;
+      font-size: 12px;
+      margin-left: 5px;
+      margin-top: 4px;
+      &:first-of-type {
+        margin-left: 15px;
+      }
+      &:last-of-type {
+        margin-right: 15px;
+      }
+      &.active {
+        background-color: #42b983;
+        color: #fff;
+        border-color: #42b983;
+        &::before {
+          content: '';
+          background: #fff;
+          display: inline-block;
+          width: 8px;
+          height: 8px;
+          border-radius: 50%;
+          position: relative;
+          margin-right: 2px;
+        }
+      }
+    }
+  }
+  .contextmenu {
+    margin: 0;
+    background: #fff;
+    z-index: 3000;
+    position: absolute;
+    list-style-type: none;
+    padding: 5px 0;
+    border-radius: 4px;
+    font-size: 12px;
+    font-weight: 400;
+    color: #333;
+    box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, .3);
+    li {
+      margin: 0;
+      padding: 7px 16px;
+      cursor: pointer;
+      &:hover {
+        background: #eee;
+      }
+    }
+  }
+}
+</style>
+
+<style lang="scss">
+//reset element css of el-icon-close
+.tags-view-wrapper {
+  .tags-view-item {
+    .el-icon-close {
+      width: 16px;
+      height: 16px;
+      vertical-align: 2px;
+      border-radius: 50%;
+      text-align: center;
+      transition: all .3s cubic-bezier(.645, .045, .355, 1);
+      transform-origin: 100% 50%;
+      &:before {
+        transform: scale(.6);
+        display: inline-block;
+        vertical-align: -3px;
+      }
+      &:hover {
+        background-color: #b4bccc;
+        color: #fff;
+      }
+    }
+  }
+}
+</style>
diff --git a/frontend/src/layout/components/index.js b/frontend/src/layout/components/index.js
new file mode 100644
index 0000000..104bd3a
--- /dev/null
+++ b/frontend/src/layout/components/index.js
@@ -0,0 +1,5 @@
+export { default as AppMain } from './AppMain'
+export { default as Navbar } from './Navbar'
+export { default as Settings } from './Settings'
+export { default as Sidebar } from './Sidebar/index.vue'
+export { default as TagsView } from './TagsView/index.vue'
diff --git a/frontend/src/layout/index.vue b/frontend/src/layout/index.vue
new file mode 100644
index 0000000..965bcd1
--- /dev/null
+++ b/frontend/src/layout/index.vue
@@ -0,0 +1,102 @@
+<template>
+  <div :class="classObj" class="app-wrapper">
+    <div v-if="device==='mobile'&&sidebar.opened" class="drawer-bg" @click="handleClickOutside" />
+    <sidebar class="sidebar-container" />
+    <div :class="{hasTagsView:needTagsView}" class="main-container">
+      <div :class="{'fixed-header':fixedHeader}">
+        <navbar />
+        <tags-view v-if="needTagsView" />
+      </div>
+      <app-main />
+      <right-panel v-if="showSettings">
+        <settings />
+      </right-panel>
+    </div>
+  </div>
+</template>
+
+<script>
+import RightPanel from '@/components/RightPanel'
+import { AppMain, Navbar, Settings, Sidebar, TagsView } from './components'
+import ResizeMixin from './mixin/ResizeHandler'
+import { mapState } from 'vuex'
+
+export default {
+  name: 'Layout',
+  components: {
+    AppMain,
+    Navbar,
+    RightPanel,
+    Settings,
+    Sidebar,
+    TagsView
+  },
+  mixins: [ResizeMixin],
+  computed: {
+    ...mapState({
+      sidebar: state => state.app.sidebar,
+      device: state => state.app.device,
+      showSettings: state => state.settings.showSettings,
+      needTagsView: state => state.settings.tagsView,
+      fixedHeader: state => state.settings.fixedHeader
+    }),
+    classObj() {
+      return {
+        hideSidebar: !this.sidebar.opened,
+        openSidebar: this.sidebar.opened,
+        withoutAnimation: this.sidebar.withoutAnimation,
+        mobile: this.device === 'mobile'
+      }
+    }
+  },
+  methods: {
+    handleClickOutside() {
+      this.$store.dispatch('app/closeSideBar', { withoutAnimation: false })
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+  @import "~@/styles/mixin.scss";
+  @import "~@/styles/variables.scss";
+
+  .app-wrapper {
+    @include clearfix;
+    position: relative;
+    height: 100%;
+    width: 100%;
+
+    &.mobile.openSidebar {
+      position: fixed;
+      top: 0;
+    }
+  }
+
+  .drawer-bg {
+    background: #000;
+    opacity: 0.3;
+    width: 100%;
+    top: 0;
+    height: 100%;
+    position: absolute;
+    z-index: 999;
+  }
+
+  .fixed-header {
+    position: fixed;
+    top: 0;
+    right: 0;
+    z-index: 9;
+    width: calc(100% - #{$sideBarWidth});
+    transition: width 0.28s;
+  }
+
+  .hideSidebar .fixed-header {
+    width: calc(100% - 54px)
+  }
+
+  .mobile .fixed-header {
+    width: 100%;
+  }
+</style>
diff --git a/frontend/src/layout/mixin/ResizeHandler.js b/frontend/src/layout/mixin/ResizeHandler.js
new file mode 100644
index 0000000..e8d0df8
--- /dev/null
+++ b/frontend/src/layout/mixin/ResizeHandler.js
@@ -0,0 +1,45 @@
+import store from '@/store'
+
+const { body } = document
+const WIDTH = 992 // refer to Bootstrap's responsive design
+
+export default {
+  watch: {
+    $route(route) {
+      if (this.device === 'mobile' && this.sidebar.opened) {
+        store.dispatch('app/closeSideBar', { withoutAnimation: false })
+      }
+    }
+  },
+  beforeMount() {
+    window.addEventListener('resize', this.$_resizeHandler)
+  },
+  beforeDestroy() {
+    window.removeEventListener('resize', this.$_resizeHandler)
+  },
+  mounted() {
+    const isMobile = this.$_isMobile()
+    if (isMobile) {
+      store.dispatch('app/toggleDevice', 'mobile')
+      store.dispatch('app/closeSideBar', { withoutAnimation: true })
+    }
+  },
+  methods: {
+    // use $_ for mixins properties
+    // https://vuejs.org/v2/style-guide/index.html#Private-property-names-essential
+    $_isMobile() {
+      const rect = body.getBoundingClientRect()
+      return rect.width - 1 < WIDTH
+    },
+    $_resizeHandler() {
+      if (!document.hidden) {
+        const isMobile = this.$_isMobile()
+        store.dispatch('app/toggleDevice', isMobile ? 'mobile' : 'desktop')
+
+        if (isMobile) {
+          store.dispatch('app/closeSideBar', { withoutAnimation: true })
+        }
+      }
+    }
+  }
+}
diff --git a/frontend/src/main.js b/frontend/src/main.js
new file mode 100644
index 0000000..b5fa135
--- /dev/null
+++ b/frontend/src/main.js
@@ -0,0 +1,53 @@
+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)
+})
diff --git a/frontend/src/permission.js b/frontend/src/permission.js
new file mode 100644
index 0000000..ff5eaad
--- /dev/null
+++ b/frontend/src/permission.js
@@ -0,0 +1,74 @@
+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()
+})
diff --git a/frontend/src/router/index.js b/frontend/src/router/index.js
new file mode 100644
index 0000000..2be959d
--- /dev/null
+++ b/frontend/src/router/index.js
@@ -0,0 +1,404 @@
+import Vue from 'vue'
+import Router from 'vue-router'
+
+Vue.use(Router)
+
+/* Layout */
+import Layout from '@/layout'
+
+/* Router Modules */
+import componentsRouter from './modules/components'
+import chartsRouter from './modules/charts'
+import tableRouter from './modules/table'
+import nestedRouter from './modules/nested'
+
+/**
+ * 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: '/dashboard',
+    children: [
+      {
+        path: 'dashboard',
+        component: () => import('@/views/dashboard/index'),
+        name: 'Dashboard',
+        meta: { title: 'Dashboard', icon: 'dashboard', affix: true }
+      }
+    ]
+  },
+  {
+    path: '/documentation',
+    component: Layout,
+    children: [
+      {
+        path: 'index',
+        component: () => import('@/views/documentation/index'),
+        name: 'Documentation',
+        meta: { title: 'Documentation', icon: 'documentation', affix: true }
+      }
+    ]
+  },
+  {
+    path: '/guide',
+    component: Layout,
+    redirect: '/guide/index',
+    children: [
+      {
+        path: 'index',
+        component: () => import('@/views/guide/index'),
+        name: 'Guide',
+        meta: { title: 'Guide', icon: 'guide', noCache: true }
+      }
+    ]
+  },
+  {
+    path: '/profile',
+    component: Layout,
+    redirect: '/profile/index',
+    hidden: true,
+    children: [
+      {
+        path: 'index',
+        component: () => import('@/views/profile/index'),
+        name: 'Profile',
+        meta: { title: 'Profile', icon: 'user', noCache: true }
+      }
+    ]
+  }
+]
+
+/**
+ * asyncRoutes
+ * the routes that need to be dynamically loaded based on user roles
+ */
+export const asyncRoutes = [
+  {
+    path: '/permission',
+    component: Layout,
+    redirect: '/permission/page',
+    alwaysShow: true, // will always show the root menu
+    name: 'Permission',
+    meta: {
+      title: 'Permission',
+      icon: 'lock',
+      roles: ['admin', 'editor'] // you can set roles in root nav
+    },
+    children: [
+      {
+        path: 'page',
+        component: () => import('@/views/permission/page'),
+        name: 'PagePermission',
+        meta: {
+          title: 'Page Permission',
+          roles: ['admin'] // or you can only set roles in sub nav
+        }
+      },
+      {
+        path: 'directive',
+        component: () => import('@/views/permission/directive'),
+        name: 'DirectivePermission',
+        meta: {
+          title: 'Directive Permission'
+          // if do not set roles, means: this page does not require permission
+        }
+      },
+      {
+        path: 'role',
+        component: () => import('@/views/permission/role'),
+        name: 'RolePermission',
+        meta: {
+          title: 'Role Permission',
+          roles: ['admin']
+        }
+      }
+    ]
+  },
+
+  {
+    path: '/icon',
+    component: Layout,
+    children: [
+      {
+        path: 'index',
+        component: () => import('@/views/icons/index'),
+        name: 'Icons',
+        meta: { title: 'Icons', icon: 'icon', noCache: true }
+      }
+    ]
+  },
+
+  /** when your routing map is too long, you can split it into small modules **/
+  componentsRouter,
+  chartsRouter,
+  nestedRouter,
+  tableRouter,
+
+  {
+    path: '/example',
+    component: Layout,
+    redirect: '/example/list',
+    name: 'Example',
+    meta: {
+      title: 'Example',
+      icon: 'el-icon-s-help'
+    },
+    children: [
+      {
+        path: 'create',
+        component: () => import('@/views/example/create'),
+        name: 'CreateArticle',
+        meta: { title: 'Create Article', icon: 'edit' }
+      },
+      {
+        path: 'edit/:id(\\d+)',
+        component: () => import('@/views/example/edit'),
+        name: 'EditArticle',
+        meta: { title: 'Edit Article', noCache: true, activeMenu: '/example/list' },
+        hidden: true
+      },
+      {
+        path: 'list',
+        component: () => import('@/views/example/list'),
+        name: 'ArticleList',
+        meta: { title: 'Article List', icon: 'list' }
+      }
+    ]
+  },
+
+  {
+    path: '/tab',
+    component: Layout,
+    children: [
+      {
+        path: 'index',
+        component: () => import('@/views/tab/index'),
+        name: 'Tab',
+        meta: { title: 'Tab', icon: 'tab' }
+      }
+    ]
+  },
+
+  {
+    path: '/error',
+    component: Layout,
+    redirect: 'noRedirect',
+    name: 'ErrorPages',
+    meta: {
+      title: 'Error Pages',
+      icon: '404'
+    },
+    children: [
+      {
+        path: '401',
+        component: () => import('@/views/error-page/401'),
+        name: 'Page401',
+        meta: { title: '401', noCache: true }
+      },
+      {
+        path: '404',
+        component: () => import('@/views/error-page/404'),
+        name: 'Page404',
+        meta: { title: '404', noCache: true }
+      }
+    ]
+  },
+
+  {
+    path: '/error-log',
+    component: Layout,
+    children: [
+      {
+        path: 'log',
+        component: () => import('@/views/error-log/index'),
+        name: 'ErrorLog',
+        meta: { title: 'Error Log', icon: 'bug' }
+      }
+    ]
+  },
+
+  {
+    path: '/excel',
+    component: Layout,
+    redirect: '/excel/export-excel',
+    name: 'Excel',
+    meta: {
+      title: 'Excel',
+      icon: 'excel'
+    },
+    children: [
+      {
+        path: 'export-excel',
+        component: () => import('@/views/excel/export-excel'),
+        name: 'ExportExcel',
+        meta: { title: 'Export Excel' }
+      },
+      {
+        path: 'export-selected-excel',
+        component: () => import('@/views/excel/select-excel'),
+        name: 'SelectExcel',
+        meta: { title: 'Export Selected' }
+      },
+      {
+        path: 'export-merge-header',
+        component: () => import('@/views/excel/merge-header'),
+        name: 'MergeHeader',
+        meta: { title: 'Merge Header' }
+      },
+      {
+        path: 'upload-excel',
+        component: () => import('@/views/excel/upload-excel'),
+        name: 'UploadExcel',
+        meta: { title: 'Upload Excel' }
+      }
+    ]
+  },
+
+  {
+    path: '/zip',
+    component: Layout,
+    redirect: '/zip/download',
+    alwaysShow: true,
+    name: 'Zip',
+    meta: { title: 'Zip', icon: 'zip' },
+    children: [
+      {
+        path: 'download',
+        component: () => import('@/views/zip/index'),
+        name: 'ExportZip',
+        meta: { title: 'Export Zip' }
+      }
+    ]
+  },
+
+  {
+    path: '/pdf',
+    component: Layout,
+    redirect: '/pdf/index',
+    children: [
+      {
+        path: 'index',
+        component: () => import('@/views/pdf/index'),
+        name: 'PDF',
+        meta: { title: 'PDF', icon: 'pdf' }
+      }
+    ]
+  },
+  {
+    path: '/pdf/download',
+    component: () => import('@/views/pdf/download'),
+    hidden: true
+  },
+
+  {
+    path: '/theme',
+    component: Layout,
+    children: [
+      {
+        path: 'index',
+        component: () => import('@/views/theme/index'),
+        name: 'Theme',
+        meta: { title: 'Theme', icon: 'theme' }
+      }
+    ]
+  },
+
+  {
+    path: '/clipboard',
+    component: Layout,
+    children: [
+      {
+        path: 'index',
+        component: () => import('@/views/clipboard/index'),
+        name: 'ClipboardDemo',
+        meta: { title: 'Clipboard', icon: 'clipboard' }
+      }
+    ]
+  },
+
+  {
+    path: 'external-link',
+    component: Layout,
+    children: [
+      {
+        path: 'https://github.com/PanJiaChen/vue-element-admin',
+        meta: { title: 'External Link', icon: 'link' }
+      }
+    ]
+  },
+
+  // 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
diff --git a/frontend/src/router/modules/charts.js b/frontend/src/router/modules/charts.js
new file mode 100644
index 0000000..29684de
--- /dev/null
+++ b/frontend/src/router/modules/charts.js
@@ -0,0 +1,36 @@
+/** When your routing table is too long, you can split it into small modules**/
+
+import Layout from '@/layout'
+
+const chartsRouter = {
+  path: '/charts',
+  component: Layout,
+  redirect: 'noRedirect',
+  name: 'Charts',
+  meta: {
+    title: 'Charts',
+    icon: 'chart'
+  },
+  children: [
+    {
+      path: 'keyboard',
+      component: () => import('@/views/charts/keyboard'),
+      name: 'KeyboardChart',
+      meta: { title: 'Keyboard Chart', noCache: true }
+    },
+    {
+      path: 'line',
+      component: () => import('@/views/charts/line'),
+      name: 'LineChart',
+      meta: { title: 'Line Chart', noCache: true }
+    },
+    {
+      path: 'mix-chart',
+      component: () => import('@/views/charts/mix-chart'),
+      name: 'MixChart',
+      meta: { title: 'Mix Chart', noCache: true }
+    }
+  ]
+}
+
+export default chartsRouter
diff --git a/frontend/src/router/modules/components.js b/frontend/src/router/modules/components.js
new file mode 100644
index 0000000..0da96f9
--- /dev/null
+++ b/frontend/src/router/modules/components.js
@@ -0,0 +1,102 @@
+/** When your routing table is too long, you can split it into small modules **/
+
+import Layout from '@/layout'
+
+const componentsRouter = {
+  path: '/components',
+  component: Layout,
+  redirect: 'noRedirect',
+  name: 'ComponentDemo',
+  meta: {
+    title: 'Components',
+    icon: 'component'
+  },
+  children: [
+    {
+      path: 'tinymce',
+      component: () => import('@/views/components-demo/tinymce'),
+      name: 'TinymceDemo',
+      meta: { title: 'Tinymce' }
+    },
+    {
+      path: 'markdown',
+      component: () => import('@/views/components-demo/markdown'),
+      name: 'MarkdownDemo',
+      meta: { title: 'Markdown' }
+    },
+    {
+      path: 'json-editor',
+      component: () => import('@/views/components-demo/json-editor'),
+      name: 'JsonEditorDemo',
+      meta: { title: 'JSON Editor' }
+    },
+    {
+      path: 'split-pane',
+      component: () => import('@/views/components-demo/split-pane'),
+      name: 'SplitpaneDemo',
+      meta: { title: 'SplitPane' }
+    },
+    {
+      path: 'avatar-upload',
+      component: () => import('@/views/components-demo/avatar-upload'),
+      name: 'AvatarUploadDemo',
+      meta: { title: 'Upload' }
+    },
+    {
+      path: 'dropzone',
+      component: () => import('@/views/components-demo/dropzone'),
+      name: 'DropzoneDemo',
+      meta: { title: 'Dropzone' }
+    },
+    {
+      path: 'sticky',
+      component: () => import('@/views/components-demo/sticky'),
+      name: 'StickyDemo',
+      meta: { title: 'Sticky' }
+    },
+    {
+      path: 'count-to',
+      component: () => import('@/views/components-demo/count-to'),
+      name: 'CountToDemo',
+      meta: { title: 'Count To' }
+    },
+    {
+      path: 'mixin',
+      component: () => import('@/views/components-demo/mixin'),
+      name: 'ComponentMixinDemo',
+      meta: { title: 'Component Mixin' }
+    },
+    {
+      path: 'back-to-top',
+      component: () => import('@/views/components-demo/back-to-top'),
+      name: 'BackToTopDemo',
+      meta: { title: 'Back To Top' }
+    },
+    {
+      path: 'drag-dialog',
+      component: () => import('@/views/components-demo/drag-dialog'),
+      name: 'DragDialogDemo',
+      meta: { title: 'Drag Dialog' }
+    },
+    {
+      path: 'drag-select',
+      component: () => import('@/views/components-demo/drag-select'),
+      name: 'DragSelectDemo',
+      meta: { title: 'Drag Select' }
+    },
+    {
+      path: 'dnd-list',
+      component: () => import('@/views/components-demo/dnd-list'),
+      name: 'DndListDemo',
+      meta: { title: 'Dnd List' }
+    },
+    {
+      path: 'drag-kanban',
+      component: () => import('@/views/components-demo/drag-kanban'),
+      name: 'DragKanbanDemo',
+      meta: { title: 'Drag Kanban' }
+    }
+  ]
+}
+
+export default componentsRouter
diff --git a/frontend/src/router/modules/nested.js b/frontend/src/router/modules/nested.js
new file mode 100644
index 0000000..48033ed
--- /dev/null
+++ b/frontend/src/router/modules/nested.js
@@ -0,0 +1,66 @@
+/** When your routing table is too long, you can split it into small modules **/
+
+import Layout from '@/layout'
+
+const nestedRouter = {
+  path: '/nested',
+  component: Layout,
+  redirect: '/nested/menu1/menu1-1',
+  name: 'Nested',
+  meta: {
+    title: 'Nested Routes',
+    icon: 'nested'
+  },
+  children: [
+    {
+      path: 'menu1',
+      component: () => import('@/views/nested/menu1/index'), // Parent router-view
+      name: 'Menu1',
+      meta: { title: 'Menu 1' },
+      redirect: '/nested/menu1/menu1-1',
+      children: [
+        {
+          path: 'menu1-1',
+          component: () => import('@/views/nested/menu1/menu1-1'),
+          name: 'Menu1-1',
+          meta: { title: 'Menu 1-1' }
+        },
+        {
+          path: 'menu1-2',
+          component: () => import('@/views/nested/menu1/menu1-2'),
+          name: 'Menu1-2',
+          redirect: '/nested/menu1/menu1-2/menu1-2-1',
+          meta: { title: 'Menu 1-2' },
+          children: [
+            {
+              path: 'menu1-2-1',
+              component: () => import('@/views/nested/menu1/menu1-2/menu1-2-1'),
+              name: 'Menu1-2-1',
+              meta: { title: 'Menu 1-2-1' }
+            },
+            {
+              path: 'menu1-2-2',
+              component: () => import('@/views/nested/menu1/menu1-2/menu1-2-2'),
+              name: 'Menu1-2-2',
+              meta: { title: 'Menu 1-2-2' }
+            }
+          ]
+        },
+        {
+          path: 'menu1-3',
+          component: () => import('@/views/nested/menu1/menu1-3'),
+          name: 'Menu1-3',
+          meta: { title: 'Menu 1-3' }
+        }
+      ]
+    },
+    {
+      path: 'menu2',
+      name: 'Menu2',
+      component: () => import('@/views/nested/menu2/index'),
+      meta: { title: 'Menu 2' }
+    }
+  ]
+}
+
+export default nestedRouter
diff --git a/frontend/src/router/modules/table.js b/frontend/src/router/modules/table.js
new file mode 100644
index 0000000..ec28c52
--- /dev/null
+++ b/frontend/src/router/modules/table.js
@@ -0,0 +1,41 @@
+/** When your routing table is too long, you can split it into small modules **/
+
+import Layout from '@/layout'
+
+const tableRouter = {
+  path: '/table',
+  component: Layout,
+  redirect: '/table/complex-table',
+  name: 'Table',
+  meta: {
+    title: 'Table',
+    icon: 'table'
+  },
+  children: [
+    {
+      path: 'dynamic-table',
+      component: () => import('@/views/table/dynamic-table/index'),
+      name: 'DynamicTable',
+      meta: { title: 'Dynamic Table' }
+    },
+    {
+      path: 'drag-table',
+      component: () => import('@/views/table/drag-table'),
+      name: 'DragTable',
+      meta: { title: 'Drag Table' }
+    },
+    {
+      path: 'inline-edit-table',
+      component: () => import('@/views/table/inline-edit-table'),
+      name: 'InlineEditTable',
+      meta: { title: 'Inline Edit' }
+    },
+    {
+      path: 'complex-table',
+      component: () => import('@/views/table/complex-table'),
+      name: 'ComplexTable',
+      meta: { title: 'Complex Table' }
+    }
+  ]
+}
+export default tableRouter
diff --git a/frontend/src/settings.js b/frontend/src/settings.js
new file mode 100644
index 0000000..1ebc7f2
--- /dev/null
+++ b/frontend/src/settings.js
@@ -0,0 +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: false,
+
+  /**
+   * @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'
+}
diff --git a/frontend/src/store/getters.js b/frontend/src/store/getters.js
new file mode 100644
index 0000000..8fcf5a5
--- /dev/null
+++ b/frontend/src/store/getters.js
@@ -0,0 +1,15 @@
+const getters = {
+  sidebar: state => state.app.sidebar,
+  size: state => state.app.size,
+  device: state => state.app.device,
+  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,
+  permission_routes: state => state.permission.routes,
+  errorLogs: state => state.errorLog.logs
+}
+export default getters
diff --git a/frontend/src/store/index.js b/frontend/src/store/index.js
new file mode 100644
index 0000000..0fd8395
--- /dev/null
+++ b/frontend/src/store/index.js
@@ -0,0 +1,25 @@
+import Vue from 'vue'
+import Vuex from 'vuex'
+import getters from './getters'
+
+Vue.use(Vuex)
+
+// https://webpack.js.org/guides/dependency-management/#requirecontext
+const modulesFiles = require.context('./modules', true, /\.js$/)
+
+// you do not need `import app from './modules/app'`
+// it will auto require all vuex module from modules file
+const modules = modulesFiles.keys().reduce((modules, modulePath) => {
+  // set './app.js' => 'app'
+  const moduleName = modulePath.replace(/^\.\/(.*)\.\w+$/, '$1')
+  const value = modulesFiles(modulePath)
+  modules[moduleName] = value.default
+  return modules
+}, {})
+
+const store = new Vuex.Store({
+  modules,
+  getters
+})
+
+export default store
diff --git a/frontend/src/store/modules/app.js b/frontend/src/store/modules/app.js
new file mode 100644
index 0000000..45d89bb
--- /dev/null
+++ b/frontend/src/store/modules/app.js
@@ -0,0 +1,56 @@
+import Cookies from 'js-cookie'
+
+const state = {
+  sidebar: {
+    opened: Cookies.get('sidebarStatus') ? !!+Cookies.get('sidebarStatus') : true,
+    withoutAnimation: false
+  },
+  device: 'desktop',
+  size: Cookies.get('size') || 'medium'
+}
+
+const mutations = {
+  TOGGLE_SIDEBAR: state => {
+    state.sidebar.opened = !state.sidebar.opened
+    state.sidebar.withoutAnimation = false
+    if (state.sidebar.opened) {
+      Cookies.set('sidebarStatus', 1)
+    } else {
+      Cookies.set('sidebarStatus', 0)
+    }
+  },
+  CLOSE_SIDEBAR: (state, withoutAnimation) => {
+    Cookies.set('sidebarStatus', 0)
+    state.sidebar.opened = false
+    state.sidebar.withoutAnimation = withoutAnimation
+  },
+  TOGGLE_DEVICE: (state, device) => {
+    state.device = device
+  },
+  SET_SIZE: (state, size) => {
+    state.size = size
+    Cookies.set('size', size)
+  }
+}
+
+const actions = {
+  toggleSideBar({ commit }) {
+    commit('TOGGLE_SIDEBAR')
+  },
+  closeSideBar({ commit }, { withoutAnimation }) {
+    commit('CLOSE_SIDEBAR', withoutAnimation)
+  },
+  toggleDevice({ commit }, device) {
+    commit('TOGGLE_DEVICE', device)
+  },
+  setSize({ commit }, size) {
+    commit('SET_SIZE', size)
+  }
+}
+
+export default {
+  namespaced: true,
+  state,
+  mutations,
+  actions
+}
diff --git a/frontend/src/store/modules/errorLog.js b/frontend/src/store/modules/errorLog.js
new file mode 100644
index 0000000..6b01f95
--- /dev/null
+++ b/frontend/src/store/modules/errorLog.js
@@ -0,0 +1,28 @@
+const state = {
+  logs: []
+}
+
+const mutations = {
+  ADD_ERROR_LOG: (state, log) => {
+    state.logs.push(log)
+  },
+  CLEAR_ERROR_LOG: (state) => {
+    state.logs.splice(0)
+  }
+}
+
+const actions = {
+  addErrorLog({ commit }, log) {
+    commit('ADD_ERROR_LOG', log)
+  },
+  clearErrorLog({ commit }) {
+    commit('CLEAR_ERROR_LOG')
+  }
+}
+
+export default {
+  namespaced: true,
+  state,
+  mutations,
+  actions
+}
diff --git a/frontend/src/store/modules/permission.js b/frontend/src/store/modules/permission.js
new file mode 100644
index 0000000..aeb5ee5
--- /dev/null
+++ b/frontend/src/store/modules/permission.js
@@ -0,0 +1,69 @@
+import { asyncRoutes, constantRoutes } from '@/router'
+
+/**
+ * Use meta.role to determine if the current user has permission
+ * @param roles
+ * @param route
+ */
+function hasPermission(roles, route) {
+  if (route.meta && route.meta.roles) {
+    return roles.some(role => route.meta.roles.includes(role))
+  } else {
+    return true
+  }
+}
+
+/**
+ * Filter asynchronous routing tables by recursion
+ * @param routes asyncRoutes
+ * @param roles
+ */
+export function filterAsyncRoutes(routes, roles) {
+  const res = []
+
+  routes.forEach(route => {
+    const tmp = { ...route }
+    if (hasPermission(roles, tmp)) {
+      if (tmp.children) {
+        tmp.children = filterAsyncRoutes(tmp.children, roles)
+      }
+      res.push(tmp)
+    }
+  })
+
+  return res
+}
+
+const state = {
+  routes: [],
+  addRoutes: []
+}
+
+const mutations = {
+  SET_ROUTES: (state, routes) => {
+    state.addRoutes = routes
+    state.routes = constantRoutes.concat(routes)
+  }
+}
+
+const actions = {
+  generateRoutes({ commit }, roles) {
+    return new Promise(resolve => {
+      let accessedRoutes
+      if (roles.includes('admin')) {
+        accessedRoutes = asyncRoutes || []
+      } else {
+        accessedRoutes = filterAsyncRoutes(asyncRoutes, roles)
+      }
+      commit('SET_ROUTES', accessedRoutes)
+      resolve(accessedRoutes)
+    })
+  }
+}
+
+export default {
+  namespaced: true,
+  state,
+  mutations,
+  actions
+}
diff --git a/frontend/src/store/modules/settings.js b/frontend/src/store/modules/settings.js
new file mode 100644
index 0000000..110533f
--- /dev/null
+++ b/frontend/src/store/modules/settings.js
@@ -0,0 +1,35 @@
+import variables from '@/styles/element-variables.scss'
+import defaultSettings from '@/settings'
+
+const { showSettings, tagsView, fixedHeader, sidebarLogo } = defaultSettings
+
+const state = {
+  theme: variables.theme,
+  showSettings: showSettings,
+  tagsView: tagsView,
+  fixedHeader: fixedHeader,
+  sidebarLogo: sidebarLogo
+}
+
+const mutations = {
+  CHANGE_SETTING: (state, { key, value }) => {
+    // eslint-disable-next-line no-prototype-builtins
+    if (state.hasOwnProperty(key)) {
+      state[key] = value
+    }
+  }
+}
+
+const actions = {
+  changeSetting({ commit }, data) {
+    commit('CHANGE_SETTING', data)
+  }
+}
+
+export default {
+  namespaced: true,
+  state,
+  mutations,
+  actions
+}
+
diff --git a/frontend/src/store/modules/tagsView.js b/frontend/src/store/modules/tagsView.js
new file mode 100644
index 0000000..57e7242
--- /dev/null
+++ b/frontend/src/store/modules/tagsView.js
@@ -0,0 +1,160 @@
+const state = {
+  visitedViews: [],
+  cachedViews: []
+}
+
+const mutations = {
+  ADD_VISITED_VIEW: (state, view) => {
+    if (state.visitedViews.some(v => v.path === view.path)) return
+    state.visitedViews.push(
+      Object.assign({}, view, {
+        title: view.meta.title || 'no-name'
+      })
+    )
+  },
+  ADD_CACHED_VIEW: (state, view) => {
+    if (state.cachedViews.includes(view.name)) return
+    if (!view.meta.noCache) {
+      state.cachedViews.push(view.name)
+    }
+  },
+
+  DEL_VISITED_VIEW: (state, view) => {
+    for (const [i, v] of state.visitedViews.entries()) {
+      if (v.path === view.path) {
+        state.visitedViews.splice(i, 1)
+        break
+      }
+    }
+  },
+  DEL_CACHED_VIEW: (state, view) => {
+    const index = state.cachedViews.indexOf(view.name)
+    index > -1 && state.cachedViews.splice(index, 1)
+  },
+
+  DEL_OTHERS_VISITED_VIEWS: (state, view) => {
+    state.visitedViews = state.visitedViews.filter(v => {
+      return v.meta.affix || v.path === view.path
+    })
+  },
+  DEL_OTHERS_CACHED_VIEWS: (state, view) => {
+    const index = state.cachedViews.indexOf(view.name)
+    if (index > -1) {
+      state.cachedViews = state.cachedViews.slice(index, index + 1)
+    } else {
+      // if index = -1, there is no cached tags
+      state.cachedViews = []
+    }
+  },
+
+  DEL_ALL_VISITED_VIEWS: state => {
+    // keep affix tags
+    const affixTags = state.visitedViews.filter(tag => tag.meta.affix)
+    state.visitedViews = affixTags
+  },
+  DEL_ALL_CACHED_VIEWS: state => {
+    state.cachedViews = []
+  },
+
+  UPDATE_VISITED_VIEW: (state, view) => {
+    for (let v of state.visitedViews) {
+      if (v.path === view.path) {
+        v = Object.assign(v, view)
+        break
+      }
+    }
+  }
+}
+
+const actions = {
+  addView({ dispatch }, view) {
+    dispatch('addVisitedView', view)
+    dispatch('addCachedView', view)
+  },
+  addVisitedView({ commit }, view) {
+    commit('ADD_VISITED_VIEW', view)
+  },
+  addCachedView({ commit }, view) {
+    commit('ADD_CACHED_VIEW', view)
+  },
+
+  delView({ dispatch, state }, view) {
+    return new Promise(resolve => {
+      dispatch('delVisitedView', view)
+      dispatch('delCachedView', view)
+      resolve({
+        visitedViews: [...state.visitedViews],
+        cachedViews: [...state.cachedViews]
+      })
+    })
+  },
+  delVisitedView({ commit, state }, view) {
+    return new Promise(resolve => {
+      commit('DEL_VISITED_VIEW', view)
+      resolve([...state.visitedViews])
+    })
+  },
+  delCachedView({ commit, state }, view) {
+    return new Promise(resolve => {
+      commit('DEL_CACHED_VIEW', view)
+      resolve([...state.cachedViews])
+    })
+  },
+
+  delOthersViews({ dispatch, state }, view) {
+    return new Promise(resolve => {
+      dispatch('delOthersVisitedViews', view)
+      dispatch('delOthersCachedViews', view)
+      resolve({
+        visitedViews: [...state.visitedViews],
+        cachedViews: [...state.cachedViews]
+      })
+    })
+  },
+  delOthersVisitedViews({ commit, state }, view) {
+    return new Promise(resolve => {
+      commit('DEL_OTHERS_VISITED_VIEWS', view)
+      resolve([...state.visitedViews])
+    })
+  },
+  delOthersCachedViews({ commit, state }, view) {
+    return new Promise(resolve => {
+      commit('DEL_OTHERS_CACHED_VIEWS', view)
+      resolve([...state.cachedViews])
+    })
+  },
+
+  delAllViews({ dispatch, state }, view) {
+    return new Promise(resolve => {
+      dispatch('delAllVisitedViews', view)
+      dispatch('delAllCachedViews', view)
+      resolve({
+        visitedViews: [...state.visitedViews],
+        cachedViews: [...state.cachedViews]
+      })
+    })
+  },
+  delAllVisitedViews({ commit, state }) {
+    return new Promise(resolve => {
+      commit('DEL_ALL_VISITED_VIEWS')
+      resolve([...state.visitedViews])
+    })
+  },
+  delAllCachedViews({ commit, state }) {
+    return new Promise(resolve => {
+      commit('DEL_ALL_CACHED_VIEWS')
+      resolve([...state.cachedViews])
+    })
+  },
+
+  updateVisitedView({ commit }, view) {
+    commit('UPDATE_VISITED_VIEW', view)
+  }
+}
+
+export default {
+  namespaced: true,
+  state,
+  mutations,
+  actions
+}
diff --git a/frontend/src/store/modules/user.js b/frontend/src/store/modules/user.js
new file mode 100644
index 0000000..7800941
--- /dev/null
+++ b/frontend/src/store/modules/user.js
@@ -0,0 +1,131 @@
+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
+}
diff --git a/frontend/src/styles/btn.scss b/frontend/src/styles/btn.scss
new file mode 100644
index 0000000..e6ba1a8
--- /dev/null
+++ b/frontend/src/styles/btn.scss
@@ -0,0 +1,99 @@
+@import './variables.scss';
+
+@mixin colorBtn($color) {
+  background: $color;
+
+  &:hover {
+    color: $color;
+
+    &:before,
+    &:after {
+      background: $color;
+    }
+  }
+}
+
+.blue-btn {
+  @include colorBtn($blue)
+}
+
+.light-blue-btn {
+  @include colorBtn($light-blue)
+}
+
+.red-btn {
+  @include colorBtn($red)
+}
+
+.pink-btn {
+  @include colorBtn($pink)
+}
+
+.green-btn {
+  @include colorBtn($green)
+}
+
+.tiffany-btn {
+  @include colorBtn($tiffany)
+}
+
+.yellow-btn {
+  @include colorBtn($yellow)
+}
+
+.pan-btn {
+  font-size: 14px;
+  color: #fff;
+  padding: 14px 36px;
+  border-radius: 8px;
+  border: none;
+  outline: none;
+  transition: 600ms ease all;
+  position: relative;
+  display: inline-block;
+
+  &:hover {
+    background: #fff;
+
+    &:before,
+    &:after {
+      width: 100%;
+      transition: 600ms ease all;
+    }
+  }
+
+  &:before,
+  &:after {
+    content: '';
+    position: absolute;
+    top: 0;
+    right: 0;
+    height: 2px;
+    width: 0;
+    transition: 400ms ease all;
+  }
+
+  &::after {
+    right: inherit;
+    top: inherit;
+    left: 0;
+    bottom: 0;
+  }
+}
+
+.custom-button {
+  display: inline-block;
+  line-height: 1;
+  white-space: nowrap;
+  cursor: pointer;
+  background: #fff;
+  color: #fff;
+  -webkit-appearance: none;
+  text-align: center;
+  box-sizing: border-box;
+  outline: 0;
+  margin: 0;
+  padding: 10px 15px;
+  font-size: 14px;
+  border-radius: 4px;
+}
diff --git a/frontend/src/styles/element-ui.scss b/frontend/src/styles/element-ui.scss
new file mode 100644
index 0000000..49474de
--- /dev/null
+++ b/frontend/src/styles/element-ui.scss
@@ -0,0 +1,84 @@
+// cover some element-ui styles
+
+.el-breadcrumb__inner,
+.el-breadcrumb__inner a {
+  font-weight: 400 !important;
+}
+
+.el-upload {
+  input[type="file"] {
+    display: none !important;
+  }
+}
+
+.el-upload__input {
+  display: none;
+}
+
+.cell {
+  .el-tag {
+    margin-right: 0px;
+  }
+}
+
+.small-padding {
+  .cell {
+    padding-left: 5px;
+    padding-right: 5px;
+  }
+}
+
+.fixed-width {
+  .el-button--mini {
+    padding: 7px 10px;
+    min-width: 60px;
+  }
+}
+
+.status-col {
+  .cell {
+    padding: 0 10px;
+    text-align: center;
+
+    .el-tag {
+      margin-right: 0px;
+    }
+  }
+}
+
+// to fixed https://github.com/ElemeFE/element/issues/2461
+.el-dialog {
+  transform: none;
+  left: 0;
+  position: relative;
+  margin: 0 auto;
+}
+
+// refine element ui upload
+.upload-container {
+  .el-upload {
+    width: 100%;
+
+    .el-upload-dragger {
+      width: 100%;
+      height: 200px;
+    }
+  }
+}
+
+// dropdown
+.el-dropdown-menu {
+  a {
+    display: block
+  }
+}
+
+// fix date-picker ui bug in filter-item
+.el-range-editor.el-input__inner {
+  display: inline-flex !important;
+}
+
+// to fix el-date-picker css style
+.el-range-separator {
+  box-sizing: content-box;
+}
diff --git a/frontend/src/styles/element-variables.scss b/frontend/src/styles/element-variables.scss
new file mode 100644
index 0000000..5bdc4da
--- /dev/null
+++ b/frontend/src/styles/element-variables.scss
@@ -0,0 +1,31 @@
+/**
+* I think element-ui's default theme color is too light for long-term use.
+* So I modified the default color and you can modify it to your liking.
+**/
+
+/* theme color */
+$--color-primary: #1890ff;
+$--color-success: #13ce66;
+$--color-warning: #ffba00;
+$--color-danger: #ff4949;
+// $--color-info: #1E1E1E;
+
+$--button-font-weight: 400;
+
+// $--color-text-regular: #1f2d3d;
+
+$--border-color-light: #dfe4ed;
+$--border-color-lighter: #e6ebf5;
+
+$--table-border: 1px solid #dfe6ec;
+
+/* icon font path, required */
+$--font-path: "~element-ui/lib/theme-chalk/fonts";
+
+@import "~element-ui/packages/theme-chalk/src/index";
+
+// the :export directive is the magic sauce for webpack
+// https://www.bluematador.com/blog/how-to-share-variables-between-js-and-sass
+:export {
+  theme: $--color-primary;
+}
diff --git a/frontend/src/styles/index.scss b/frontend/src/styles/index.scss
new file mode 100644
index 0000000..96095ef
--- /dev/null
+++ b/frontend/src/styles/index.scss
@@ -0,0 +1,191 @@
+@import './variables.scss';
+@import './mixin.scss';
+@import './transition.scss';
+@import './element-ui.scss';
+@import './sidebar.scss';
+@import './btn.scss';
+
+body {
+  height: 100%;
+  -moz-osx-font-smoothing: grayscale;
+  -webkit-font-smoothing: antialiased;
+  text-rendering: optimizeLegibility;
+  font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Arial, sans-serif;
+}
+
+label {
+  font-weight: 700;
+}
+
+html {
+  height: 100%;
+  box-sizing: border-box;
+}
+
+#app {
+  height: 100%;
+}
+
+*,
+*:before,
+*:after {
+  box-sizing: inherit;
+}
+
+.no-padding {
+  padding: 0px !important;
+}
+
+.padding-content {
+  padding: 4px 0;
+}
+
+a:focus,
+a:active {
+  outline: none;
+}
+
+a,
+a:focus,
+a:hover {
+  cursor: pointer;
+  color: inherit;
+  text-decoration: none;
+}
+
+div:focus {
+  outline: none;
+}
+
+.fr {
+  float: right;
+}
+
+.fl {
+  float: left;
+}
+
+.pr-5 {
+  padding-right: 5px;
+}
+
+.pl-5 {
+  padding-left: 5px;
+}
+
+.block {
+  display: block;
+}
+
+.pointer {
+  cursor: pointer;
+}
+
+.inlineBlock {
+  display: block;
+}
+
+.clearfix {
+  &:after {
+    visibility: hidden;
+    display: block;
+    font-size: 0;
+    content: " ";
+    clear: both;
+    height: 0;
+  }
+}
+
+aside {
+  background: #eef1f6;
+  padding: 8px 24px;
+  margin-bottom: 20px;
+  border-radius: 2px;
+  display: block;
+  line-height: 32px;
+  font-size: 16px;
+  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
+  color: #2c3e50;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+
+  a {
+    color: #337ab7;
+    cursor: pointer;
+
+    &:hover {
+      color: rgb(32, 160, 255);
+    }
+  }
+}
+
+//main-container全局样式
+.app-container {
+  padding: 20px;
+}
+
+.components-container {
+  margin: 30px 50px;
+  position: relative;
+}
+
+.pagination-container {
+  margin-top: 30px;
+}
+
+.text-center {
+  text-align: center
+}
+
+.sub-navbar {
+  height: 50px;
+  line-height: 50px;
+  position: relative;
+  width: 100%;
+  text-align: right;
+  padding-right: 20px;
+  transition: 600ms ease position;
+  background: linear-gradient(90deg, rgba(32, 182, 249, 1) 0%, rgba(32, 182, 249, 1) 0%, rgba(33, 120, 241, 1) 100%, rgba(33, 120, 241, 1) 100%);
+
+  .subtitle {
+    font-size: 20px;
+    color: #fff;
+  }
+
+  &.draft {
+    background: #d0d0d0;
+  }
+
+  &.deleted {
+    background: #d0d0d0;
+  }
+}
+
+.link-type,
+.link-type:focus {
+  color: #337ab7;
+  cursor: pointer;
+
+  &:hover {
+    color: rgb(32, 160, 255);
+  }
+}
+
+.filter-container {
+  padding-bottom: 10px;
+
+  .filter-item {
+    display: inline-block;
+    vertical-align: middle;
+    margin-bottom: 10px;
+  }
+}
+
+//refine vue-multiselect plugin
+.multiselect {
+  line-height: 16px;
+}
+
+.multiselect--active {
+  z-index: 1000 !important;
+}
diff --git a/frontend/src/styles/mixin.scss b/frontend/src/styles/mixin.scss
new file mode 100644
index 0000000..06fa061
--- /dev/null
+++ b/frontend/src/styles/mixin.scss
@@ -0,0 +1,66 @@
+@mixin clearfix {
+  &:after {
+    content: "";
+    display: table;
+    clear: both;
+  }
+}
+
+@mixin scrollBar {
+  &::-webkit-scrollbar-track-piece {
+    background: #d3dce6;
+  }
+
+  &::-webkit-scrollbar {
+    width: 6px;
+  }
+
+  &::-webkit-scrollbar-thumb {
+    background: #99a9bf;
+    border-radius: 20px;
+  }
+}
+
+@mixin relative {
+  position: relative;
+  width: 100%;
+  height: 100%;
+}
+
+@mixin pct($pct) {
+  width: #{$pct};
+  position: relative;
+  margin: 0 auto;
+}
+
+@mixin triangle($width, $height, $color, $direction) {
+  $width: $width/2;
+  $color-border-style: $height solid $color;
+  $transparent-border-style: $width solid transparent;
+  height: 0;
+  width: 0;
+
+  @if $direction==up {
+    border-bottom: $color-border-style;
+    border-left: $transparent-border-style;
+    border-right: $transparent-border-style;
+  }
+
+  @else if $direction==right {
+    border-left: $color-border-style;
+    border-top: $transparent-border-style;
+    border-bottom: $transparent-border-style;
+  }
+
+  @else if $direction==down {
+    border-top: $color-border-style;
+    border-left: $transparent-border-style;
+    border-right: $transparent-border-style;
+  }
+
+  @else if $direction==left {
+    border-right: $color-border-style;
+    border-top: $transparent-border-style;
+    border-bottom: $transparent-border-style;
+  }
+}
diff --git a/frontend/src/styles/sidebar.scss b/frontend/src/styles/sidebar.scss
new file mode 100644
index 0000000..94760cc
--- /dev/null
+++ b/frontend/src/styles/sidebar.scss
@@ -0,0 +1,226 @@
+#app {
+
+  .main-container {
+    min-height: 100%;
+    transition: margin-left .28s;
+    margin-left: $sideBarWidth;
+    position: relative;
+  }
+
+  .sidebar-container {
+    transition: width 0.28s;
+    width: $sideBarWidth !important;
+    background-color: $menuBg;
+    height: 100%;
+    position: fixed;
+    font-size: 0px;
+    top: 0;
+    bottom: 0;
+    left: 0;
+    z-index: 1001;
+    overflow: hidden;
+
+    // reset element-ui css
+    .horizontal-collapse-transition {
+      transition: 0s width ease-in-out, 0s padding-left ease-in-out, 0s padding-right ease-in-out;
+    }
+
+    .scrollbar-wrapper {
+      overflow-x: hidden !important;
+    }
+
+    .el-scrollbar__bar.is-vertical {
+      right: 0px;
+    }
+
+    .el-scrollbar {
+      height: 100%;
+    }
+
+    &.has-logo {
+      .el-scrollbar {
+        height: calc(100% - 50px);
+      }
+    }
+
+    .is-horizontal {
+      display: none;
+    }
+
+    a {
+      display: inline-block;
+      width: 100%;
+      overflow: hidden;
+    }
+
+    .svg-icon {
+      margin-right: 16px;
+    }
+
+    .sub-el-icon {
+      margin-right: 12px;
+      margin-left: -2px;
+    }
+
+    .el-menu {
+      border: none;
+      height: 100%;
+      width: 100% !important;
+    }
+
+    // menu hover
+    .submenu-title-noDropdown,
+    .el-submenu__title {
+      &:hover {
+        background-color: $menuHover !important;
+      }
+    }
+
+    .is-active>.el-submenu__title {
+      color: $subMenuActiveText !important;
+    }
+
+    & .nest-menu .el-submenu>.el-submenu__title,
+    & .el-submenu .el-menu-item {
+      min-width: $sideBarWidth !important;
+      background-color: $subMenuBg !important;
+
+      &:hover {
+        background-color: $subMenuHover !important;
+      }
+    }
+  }
+
+  .hideSidebar {
+    .sidebar-container {
+      width: 54px !important;
+    }
+
+    .main-container {
+      margin-left: 54px;
+    }
+
+    .submenu-title-noDropdown {
+      padding: 0 !important;
+      position: relative;
+
+      .el-tooltip {
+        padding: 0 !important;
+
+        .svg-icon {
+          margin-left: 20px;
+        }
+
+        .sub-el-icon {
+          margin-left: 19px;
+        }
+      }
+    }
+
+    .el-submenu {
+      overflow: hidden;
+
+      &>.el-submenu__title {
+        padding: 0 !important;
+
+        .svg-icon {
+          margin-left: 20px;
+        }
+
+        .sub-el-icon {
+          margin-left: 19px;
+        }
+
+        .el-submenu__icon-arrow {
+          display: none;
+        }
+      }
+    }
+
+    .el-menu--collapse {
+      .el-submenu {
+        &>.el-submenu__title {
+          &>span {
+            height: 0;
+            width: 0;
+            overflow: hidden;
+            visibility: hidden;
+            display: inline-block;
+          }
+        }
+      }
+    }
+  }
+
+  .el-menu--collapse .el-menu .el-submenu {
+    min-width: $sideBarWidth !important;
+  }
+
+  // mobile responsive
+  .mobile {
+    .main-container {
+      margin-left: 0px;
+    }
+
+    .sidebar-container {
+      transition: transform .28s;
+      width: $sideBarWidth !important;
+    }
+
+    &.hideSidebar {
+      .sidebar-container {
+        pointer-events: none;
+        transition-duration: 0.3s;
+        transform: translate3d(-$sideBarWidth, 0, 0);
+      }
+    }
+  }
+
+  .withoutAnimation {
+
+    .main-container,
+    .sidebar-container {
+      transition: none;
+    }
+  }
+}
+
+// when menu collapsed
+.el-menu--vertical {
+  &>.el-menu {
+    .svg-icon {
+      margin-right: 16px;
+    }
+    .sub-el-icon {
+      margin-right: 12px;
+      margin-left: -2px;
+    }
+  }
+
+  .nest-menu .el-submenu>.el-submenu__title,
+  .el-menu-item {
+    &:hover {
+      // you can use $subMenuHover
+      background-color: $menuHover !important;
+    }
+  }
+
+  // the scroll bar appears when the subMenu is too long
+  >.el-menu--popup {
+    max-height: 100vh;
+    overflow-y: auto;
+
+    &::-webkit-scrollbar-track-piece {
+      background: #d3dce6;
+    }
+
+    &::-webkit-scrollbar {
+      width: 6px;
+    }
+
+    &::-webkit-scrollbar-thumb {
+      background: #99a9bf;
+      border-radius: 20px;
+    }
+  }
+}
diff --git a/frontend/src/styles/transition.scss b/frontend/src/styles/transition.scss
new file mode 100644
index 0000000..4cb27cc
--- /dev/null
+++ b/frontend/src/styles/transition.scss
@@ -0,0 +1,48 @@
+// global transition css
+
+/* fade */
+.fade-enter-active,
+.fade-leave-active {
+  transition: opacity 0.28s;
+}
+
+.fade-enter,
+.fade-leave-active {
+  opacity: 0;
+}
+
+/* fade-transform */
+.fade-transform-leave-active,
+.fade-transform-enter-active {
+  transition: all .5s;
+}
+
+.fade-transform-enter {
+  opacity: 0;
+  transform: translateX(-30px);
+}
+
+.fade-transform-leave-to {
+  opacity: 0;
+  transform: translateX(30px);
+}
+
+/* breadcrumb transition */
+.breadcrumb-enter-active,
+.breadcrumb-leave-active {
+  transition: all .5s;
+}
+
+.breadcrumb-enter,
+.breadcrumb-leave-active {
+  opacity: 0;
+  transform: translateX(20px);
+}
+
+.breadcrumb-move {
+  transition: all .5s;
+}
+
+.breadcrumb-leave-active {
+  position: absolute;
+}
diff --git a/frontend/src/styles/variables.scss b/frontend/src/styles/variables.scss
new file mode 100644
index 0000000..a19c27c
--- /dev/null
+++ b/frontend/src/styles/variables.scss
@@ -0,0 +1,35 @@
+// base color
+$blue:#324157;
+$light-blue:#3A71A8;
+$red:#C03639;
+$pink: #E65D6E;
+$green: #30B08F;
+$tiffany: #4AB7BD;
+$yellow:#FEC171;
+$panGreen: #30B08F;
+
+// sidebar
+$menuText:#bfcbd9;
+$menuActiveText:#409EFF;
+$subMenuActiveText:#f4f4f5; // https://github.com/ElemeFE/element/issues/12951
+
+$menuBg:#304156;
+$menuHover:#263445;
+
+$subMenuBg:#1f2d3d;
+$subMenuHover:#001528;
+
+$sideBarWidth: 210px;
+
+// the :export directive is the magic sauce for webpack
+// https://www.bluematador.com/blog/how-to-share-variables-between-js-and-sass
+:export {
+  menuText: $menuText;
+  menuActiveText: $menuActiveText;
+  subMenuActiveText: $subMenuActiveText;
+  menuBg: $menuBg;
+  menuHover: $menuHover;
+  subMenuBg: $subMenuBg;
+  subMenuHover: $subMenuHover;
+  sideBarWidth: $sideBarWidth;
+}
diff --git a/frontend/src/utils/auth.js b/frontend/src/utils/auth.js
new file mode 100644
index 0000000..08a43d6
--- /dev/null
+++ b/frontend/src/utils/auth.js
@@ -0,0 +1,15 @@
+import Cookies from 'js-cookie'
+
+const TokenKey = 'Admin-Token'
+
+export function getToken() {
+  return Cookies.get(TokenKey)
+}
+
+export function setToken(token) {
+  return Cookies.set(TokenKey, token)
+}
+
+export function removeToken() {
+  return Cookies.remove(TokenKey)
+}
diff --git a/frontend/src/utils/clipboard.js b/frontend/src/utils/clipboard.js
new file mode 100644
index 0000000..cf5b07a
--- /dev/null
+++ b/frontend/src/utils/clipboard.js
@@ -0,0 +1,32 @@
+import Vue from 'vue'
+import Clipboard from 'clipboard'
+
+function clipboardSuccess() {
+  Vue.prototype.$message({
+    message: 'Copy successfully',
+    type: 'success',
+    duration: 1500
+  })
+}
+
+function clipboardError() {
+  Vue.prototype.$message({
+    message: 'Copy failed',
+    type: 'error'
+  })
+}
+
+export default function handleClipboard(text, event) {
+  const clipboard = new Clipboard(event.target, {
+    text: () => text
+  })
+  clipboard.on('success', () => {
+    clipboardSuccess()
+    clipboard.destroy()
+  })
+  clipboard.on('error', () => {
+    clipboardError()
+    clipboard.destroy()
+  })
+  clipboard.onClick(event)
+}
diff --git a/frontend/src/utils/error-log.js b/frontend/src/utils/error-log.js
new file mode 100644
index 0000000..a7f5b55
--- /dev/null
+++ b/frontend/src/utils/error-log.js
@@ -0,0 +1,35 @@
+import Vue from 'vue'
+import store from '@/store'
+import { isString, isArray } from '@/utils/validate'
+import settings from '@/settings'
+
+// you can set in settings.js
+// errorLog:'production' | ['production', 'development']
+const { errorLog: needErrorLog } = settings
+
+function checkNeed() {
+  const env = process.env.NODE_ENV
+  if (isString(needErrorLog)) {
+    return env === needErrorLog
+  }
+  if (isArray(needErrorLog)) {
+    return needErrorLog.includes(env)
+  }
+  return false
+}
+
+if (checkNeed()) {
+  Vue.config.errorHandler = function(err, vm, info, a) {
+  // Don't ask me why I use Vue.nextTick, it just a hack.
+  // detail see https://forum.vuejs.org/t/dispatch-in-vue-config-errorhandler-has-some-problem/23500
+    Vue.nextTick(() => {
+      store.dispatch('errorLog/addErrorLog', {
+        err,
+        vm,
+        info,
+        url: window.location.href
+      })
+      console.error(err, info)
+    })
+  }
+}
diff --git a/frontend/src/utils/get-page-title.js b/frontend/src/utils/get-page-title.js
new file mode 100644
index 0000000..cab7fd9
--- /dev/null
+++ b/frontend/src/utils/get-page-title.js
@@ -0,0 +1,10 @@
+import defaultSettings from '@/settings'
+
+const title = defaultSettings.title || 'Vue Element Admin'
+
+export default function getPageTitle(pageTitle) {
+  if (pageTitle) {
+    return `${pageTitle} - ${title}`
+  }
+  return `${title}`
+}
diff --git a/frontend/src/utils/index.js b/frontend/src/utils/index.js
new file mode 100644
index 0000000..3225d3c
--- /dev/null
+++ b/frontend/src/utils/index.js
@@ -0,0 +1,357 @@
+/**
+ * Created by PanJiaChen on 16/11/18.
+ */
+
+/**
+ * Parse the time to string
+ * @param {(Object|string|number)} time
+ * @param {string} cFormat
+ * @returns {string | null}
+ */
+export function parseTime(time, cFormat) {
+  if (arguments.length === 0 || !time) {
+    return null
+  }
+  const format = cFormat || '{y}-{m}-{d} {h}:{i}:{s}'
+  let date
+  if (typeof time === 'object') {
+    date = time
+  } else {
+    if ((typeof time === 'string')) {
+      if ((/^[0-9]+$/.test(time))) {
+        // support "1548221490638"
+        time = parseInt(time)
+      } else {
+        // support safari
+        // https://stackoverflow.com/questions/4310953/invalid-date-in-safari
+        time = time.replace(new RegExp(/-/gm), '/')
+      }
+    }
+
+    if ((typeof time === 'number') && (time.toString().length === 10)) {
+      time = time * 1000
+    }
+    date = new Date(time)
+  }
+  const formatObj = {
+    y: date.getFullYear(),
+    m: date.getMonth() + 1,
+    d: date.getDate(),
+    h: date.getHours(),
+    i: date.getMinutes(),
+    s: date.getSeconds(),
+    a: date.getDay()
+  }
+  const time_str = format.replace(/{([ymdhisa])+}/g, (result, key) => {
+    const value = formatObj[key]
+    // Note: getDay() returns 0 on Sunday
+    if (key === 'a') { return ['日', '一', '二', '三', '四', '五', '六'][value ] }
+    return value.toString().padStart(2, '0')
+  })
+  return time_str
+}
+
+/**
+ * @param {number} time
+ * @param {string} option
+ * @returns {string}
+ */
+export function formatTime(time, option) {
+  if (('' + time).length === 10) {
+    time = parseInt(time) * 1000
+  } else {
+    time = +time
+  }
+  const d = new Date(time)
+  const now = Date.now()
+
+  const diff = (now - d) / 1000
+
+  if (diff < 30) {
+    return '刚刚'
+  } else if (diff < 3600) {
+    // less 1 hour
+    return Math.ceil(diff / 60) + '分钟前'
+  } else if (diff < 3600 * 24) {
+    return Math.ceil(diff / 3600) + '小时前'
+  } else if (diff < 3600 * 24 * 2) {
+    return '1天前'
+  }
+  if (option) {
+    return parseTime(time, option)
+  } else {
+    return (
+      d.getMonth() +
+      1 +
+      '月' +
+      d.getDate() +
+      '日' +
+      d.getHours() +
+      '时' +
+      d.getMinutes() +
+      '分'
+    )
+  }
+}
+
+/**
+ * @param {string} url
+ * @returns {Object}
+ */
+export function getQueryObject(url) {
+  url = url == null ? window.location.href : url
+  const search = url.substring(url.lastIndexOf('?') + 1)
+  const obj = {}
+  const reg = /([^?&=]+)=([^?&=]*)/g
+  search.replace(reg, (rs, $1, $2) => {
+    const name = decodeURIComponent($1)
+    let val = decodeURIComponent($2)
+    val = String(val)
+    obj[name] = val
+    return rs
+  })
+  return obj
+}
+
+/**
+ * @param {string} input value
+ * @returns {number} output value
+ */
+export function byteLength(str) {
+  // returns the byte length of an utf8 string
+  let s = str.length
+  for (var i = str.length - 1; i >= 0; i--) {
+    const code = str.charCodeAt(i)
+    if (code > 0x7f && code <= 0x7ff) s++
+    else if (code > 0x7ff && code <= 0xffff) s += 2
+    if (code >= 0xDC00 && code <= 0xDFFF) i--
+  }
+  return s
+}
+
+/**
+ * @param {Array} actual
+ * @returns {Array}
+ */
+export function cleanArray(actual) {
+  const newArray = []
+  for (let i = 0; i < actual.length; i++) {
+    if (actual[i]) {
+      newArray.push(actual[i])
+    }
+  }
+  return newArray
+}
+
+/**
+ * @param {Object} json
+ * @returns {Array}
+ */
+export function param(json) {
+  if (!json) return ''
+  return cleanArray(
+    Object.keys(json).map(key => {
+      if (json[key] === undefined) return ''
+      return encodeURIComponent(key) + '=' + encodeURIComponent(json[key])
+    })
+  ).join('&')
+}
+
+/**
+ * @param {string} url
+ * @returns {Object}
+ */
+export function param2Obj(url) {
+  const search = decodeURIComponent(url.split('?')[1]).replace(/\+/g, ' ')
+  if (!search) {
+    return {}
+  }
+  const obj = {}
+  const searchArr = search.split('&')
+  searchArr.forEach(v => {
+    const index = v.indexOf('=')
+    if (index !== -1) {
+      const name = v.substring(0, index)
+      const val = v.substring(index + 1, v.length)
+      obj[name] = val
+    }
+  })
+  return obj
+}
+
+/**
+ * @param {string} val
+ * @returns {string}
+ */
+export function html2Text(val) {
+  const div = document.createElement('div')
+  div.innerHTML = val
+  return div.textContent || div.innerText
+}
+
+/**
+ * Merges two objects, giving the last one precedence
+ * @param {Object} target
+ * @param {(Object|Array)} source
+ * @returns {Object}
+ */
+export function objectMerge(target, source) {
+  if (typeof target !== 'object') {
+    target = {}
+  }
+  if (Array.isArray(source)) {
+    return source.slice()
+  }
+  Object.keys(source).forEach(property => {
+    const sourceProperty = source[property]
+    if (typeof sourceProperty === 'object') {
+      target[property] = objectMerge(target[property], sourceProperty)
+    } else {
+      target[property] = sourceProperty
+    }
+  })
+  return target
+}
+
+/**
+ * @param {HTMLElement} element
+ * @param {string} className
+ */
+export function toggleClass(element, className) {
+  if (!element || !className) {
+    return
+  }
+  let classString = element.className
+  const nameIndex = classString.indexOf(className)
+  if (nameIndex === -1) {
+    classString += '' + className
+  } else {
+    classString =
+      classString.substr(0, nameIndex) +
+      classString.substr(nameIndex + className.length)
+  }
+  element.className = classString
+}
+
+/**
+ * @param {string} type
+ * @returns {Date}
+ */
+export function getTime(type) {
+  if (type === 'start') {
+    return new Date().getTime() - 3600 * 1000 * 24 * 90
+  } else {
+    return new Date(new Date().toDateString())
+  }
+}
+
+/**
+ * @param {Function} func
+ * @param {number} wait
+ * @param {boolean} immediate
+ * @return {*}
+ */
+export function debounce(func, wait, immediate) {
+  let timeout, args, context, timestamp, result
+
+  const later = function() {
+    // 据上一次触发时间间隔
+    const last = +new Date() - timestamp
+
+    // 上次被包装函数被调用时间间隔 last 小于设定时间间隔 wait
+    if (last < wait && last > 0) {
+      timeout = setTimeout(later, wait - last)
+    } else {
+      timeout = null
+      // 如果设定为immediate===true,因为开始边界已经调用过了此处无需调用
+      if (!immediate) {
+        result = func.apply(context, args)
+        if (!timeout) context = args = null
+      }
+    }
+  }
+
+  return function(...args) {
+    context = this
+    timestamp = +new Date()
+    const callNow = immediate && !timeout
+    // 如果延时不存在,重新设定延时
+    if (!timeout) timeout = setTimeout(later, wait)
+    if (callNow) {
+      result = func.apply(context, args)
+      context = args = null
+    }
+
+    return result
+  }
+}
+
+/**
+ * This is just a simple version of deep copy
+ * Has a lot of edge cases bug
+ * If you want to use a perfect deep copy, use lodash's _.cloneDeep
+ * @param {Object} source
+ * @returns {Object}
+ */
+export function deepClone(source) {
+  if (!source && typeof source !== 'object') {
+    throw new Error('error arguments', 'deepClone')
+  }
+  const targetObj = source.constructor === Array ? [] : {}
+  Object.keys(source).forEach(keys => {
+    if (source[keys] && typeof source[keys] === 'object') {
+      targetObj[keys] = deepClone(source[keys])
+    } else {
+      targetObj[keys] = source[keys]
+    }
+  })
+  return targetObj
+}
+
+/**
+ * @param {Array} arr
+ * @returns {Array}
+ */
+export function uniqueArr(arr) {
+  return Array.from(new Set(arr))
+}
+
+/**
+ * @returns {string}
+ */
+export function createUniqueString() {
+  const timestamp = +new Date() + ''
+  const randomNum = parseInt((1 + Math.random()) * 65536) + ''
+  return (+(randomNum + timestamp)).toString(32)
+}
+
+/**
+ * Check if an element has a class
+ * @param {HTMLElement} elm
+ * @param {string} cls
+ * @returns {boolean}
+ */
+export function hasClass(ele, cls) {
+  return !!ele.className.match(new RegExp('(\\s|^)' + cls + '(\\s|$)'))
+}
+
+/**
+ * Add class to element
+ * @param {HTMLElement} elm
+ * @param {string} cls
+ */
+export function addClass(ele, cls) {
+  if (!hasClass(ele, cls)) ele.className += ' ' + cls
+}
+
+/**
+ * Remove class from element
+ * @param {HTMLElement} elm
+ * @param {string} cls
+ */
+export function removeClass(ele, cls) {
+  if (hasClass(ele, cls)) {
+    const reg = new RegExp('(\\s|^)' + cls + '(\\s|$)')
+    ele.className = ele.className.replace(reg, ' ')
+  }
+}
diff --git a/frontend/src/utils/open-window.js b/frontend/src/utils/open-window.js
new file mode 100644
index 0000000..1a655d7
--- /dev/null
+++ b/frontend/src/utils/open-window.js
@@ -0,0 +1,25 @@
+/**
+ *Created by PanJiaChen on 16/11/29.
+ * @param {Sting} url
+ * @param {Sting} title
+ * @param {Number} w
+ * @param {Number} h
+ */
+export default function openWindow(url, title, w, h) {
+  // Fixes dual-screen position                            Most browsers       Firefox
+  const dualScreenLeft = window.screenLeft !== undefined ? window.screenLeft : screen.left
+  const dualScreenTop = window.screenTop !== undefined ? window.screenTop : screen.top
+
+  const width = window.innerWidth ? window.innerWidth : document.documentElement.clientWidth ? document.documentElement.clientWidth : screen.width
+  const height = window.innerHeight ? window.innerHeight : document.documentElement.clientHeight ? document.documentElement.clientHeight : screen.height
+
+  const left = ((width / 2) - (w / 2)) + dualScreenLeft
+  const top = ((height / 2) - (h / 2)) + dualScreenTop
+  const newWindow = window.open(url, title, 'toolbar=no, location=no, directories=no, status=no, menubar=no, scrollbars=no, resizable=yes, copyhistory=no, width=' + w + ', height=' + h + ', top=' + top + ', left=' + left)
+
+  // Puts focus on the newWindow
+  if (window.focus) {
+    newWindow.focus()
+  }
+}
+
diff --git a/frontend/src/utils/permission.js b/frontend/src/utils/permission.js
new file mode 100644
index 0000000..8e2bbad
--- /dev/null
+++ b/frontend/src/utils/permission.js
@@ -0,0 +1,21 @@
+import store from '@/store'
+
+/**
+ * @param {Array} value
+ * @returns {Boolean}
+ * @example see @/views/permission/directive.vue
+ */
+export default function checkPermission(value) {
+  if (value && value instanceof Array && value.length > 0) {
+    const roles = store.getters && store.getters.roles
+    const permissionRoles = value
+
+    const hasPermission = roles.some(role => {
+      return permissionRoles.includes(role)
+    })
+    return hasPermission
+  } else {
+    console.error(`need roles! Like v-permission="['admin','editor']"`)
+    return false
+  }
+}
diff --git a/frontend/src/utils/request.js b/frontend/src/utils/request.js
new file mode 100644
index 0000000..2fb95ac
--- /dev/null
+++ b/frontend/src/utils/request.js
@@ -0,0 +1,85 @@
+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
diff --git a/frontend/src/utils/scroll-to.js b/frontend/src/utils/scroll-to.js
new file mode 100644
index 0000000..c5d8e04
--- /dev/null
+++ b/frontend/src/utils/scroll-to.js
@@ -0,0 +1,58 @@
+Math.easeInOutQuad = function(t, b, c, d) {
+  t /= d / 2
+  if (t < 1) {
+    return c / 2 * t * t + b
+  }
+  t--
+  return -c / 2 * (t * (t - 2) - 1) + b
+}
+
+// requestAnimationFrame for Smart Animating http://goo.gl/sx5sts
+var requestAnimFrame = (function() {
+  return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || function(callback) { window.setTimeout(callback, 1000 / 60) }
+})()
+
+/**
+ * Because it's so fucking difficult to detect the scrolling element, just move them all
+ * @param {number} amount
+ */
+function move(amount) {
+  document.documentElement.scrollTop = amount
+  document.body.parentNode.scrollTop = amount
+  document.body.scrollTop = amount
+}
+
+function position() {
+  return document.documentElement.scrollTop || document.body.parentNode.scrollTop || document.body.scrollTop
+}
+
+/**
+ * @param {number} to
+ * @param {number} duration
+ * @param {Function} callback
+ */
+export function scrollTo(to, duration, callback) {
+  const start = position()
+  const change = to - start
+  const increment = 20
+  let currentTime = 0
+  duration = (typeof (duration) === 'undefined') ? 500 : duration
+  var animateScroll = function() {
+    // increment the time
+    currentTime += increment
+    // find the value with the quadratic in-out easing function
+    var val = Math.easeInOutQuad(currentTime, start, change, duration)
+    // move the document.body
+    move(val)
+    // do the animation unless its over
+    if (currentTime < duration) {
+      requestAnimFrame(animateScroll)
+    } else {
+      if (callback && typeof (callback) === 'function') {
+        // the animation is done so lets callback
+        callback()
+      }
+    }
+  }
+  animateScroll()
+}
diff --git a/frontend/src/utils/validate.js b/frontend/src/utils/validate.js
new file mode 100644
index 0000000..6b3ac41
--- /dev/null
+++ b/frontend/src/utils/validate.js
@@ -0,0 +1,87 @@
+/**
+ * Created by PanJiaChen on 16/11/18.
+ */
+
+/**
+ * @param {string} path
+ * @returns {Boolean}
+ */
+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}
+ */
+export function validURL(url) {
+  const reg = /^(https?|ftp):\/\/([a-zA-Z0-9.-]+(:[a-zA-Z0-9.&%$-]+)*@)*((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}|([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+\.(com|edu|gov|int|mil|net|org|biz|arpa|info|name|pro|aero|coop|museum|[a-zA-Z]{2}))(:[0-9]+)*(\/($|[a-zA-Z0-9.,?'\\+&%$#=~_-]+))*$/
+  return reg.test(url)
+}
+
+/**
+ * @param {string} str
+ * @returns {Boolean}
+ */
+export function validLowerCase(str) {
+  const reg = /^[a-z]+$/
+  return reg.test(str)
+}
+
+/**
+ * @param {string} str
+ * @returns {Boolean}
+ */
+export function validUpperCase(str) {
+  const reg = /^[A-Z]+$/
+  return reg.test(str)
+}
+
+/**
+ * @param {string} str
+ * @returns {Boolean}
+ */
+export function validAlphabets(str) {
+  const reg = /^[A-Za-z]+$/
+  return reg.test(str)
+}
+
+/**
+ * @param {string} email
+ * @returns {Boolean}
+ */
+export function validEmail(email) {
+  const reg = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
+  return reg.test(email)
+}
+
+/**
+ * @param {string} str
+ * @returns {Boolean}
+ */
+export function isString(str) {
+  if (typeof str === 'string' || str instanceof String) {
+    return true
+  }
+  return false
+}
+
+/**
+ * @param {Array} arg
+ * @returns {Boolean}
+ */
+export function isArray(arg) {
+  if (typeof Array.isArray === 'undefined') {
+    return Object.prototype.toString.call(arg) === '[object Array]'
+  }
+  return Array.isArray(arg)
+}
diff --git a/frontend/src/vendor/Export2Excel.js b/frontend/src/vendor/Export2Excel.js
new file mode 100644
index 0000000..d8a2af3
--- /dev/null
+++ b/frontend/src/vendor/Export2Excel.js
@@ -0,0 +1,220 @@
+/* eslint-disable */
+import { saveAs } from 'file-saver'
+import XLSX from 'xlsx'
+
+function generateArray(table) {
+  var out = [];
+  var rows = table.querySelectorAll('tr');
+  var ranges = [];
+  for (var R = 0; R < rows.length; ++R) {
+    var outRow = [];
+    var row = rows[R];
+    var columns = row.querySelectorAll('td');
+    for (var C = 0; C < columns.length; ++C) {
+      var cell = columns[C];
+      var colspan = cell.getAttribute('colspan');
+      var rowspan = cell.getAttribute('rowspan');
+      var cellValue = cell.innerText;
+      if (cellValue !== "" && cellValue == +cellValue) cellValue = +cellValue;
+
+      //Skip ranges
+      ranges.forEach(function (range) {
+        if (R >= range.s.r && R <= range.e.r && outRow.length >= range.s.c && outRow.length <= range.e.c) {
+          for (var i = 0; i <= range.e.c - range.s.c; ++i) outRow.push(null);
+        }
+      });
+
+      //Handle Row Span
+      if (rowspan || colspan) {
+        rowspan = rowspan || 1;
+        colspan = colspan || 1;
+        ranges.push({
+          s: {
+            r: R,
+            c: outRow.length
+          },
+          e: {
+            r: R + rowspan - 1,
+            c: outRow.length + colspan - 1
+          }
+        });
+      };
+
+      //Handle Value
+      outRow.push(cellValue !== "" ? cellValue : null);
+
+      //Handle Colspan
+      if (colspan)
+        for (var k = 0; k < colspan - 1; ++k) outRow.push(null);
+    }
+    out.push(outRow);
+  }
+  return [out, ranges];
+};
+
+function datenum(v, date1904) {
+  if (date1904) v += 1462;
+  var epoch = Date.parse(v);
+  return (epoch - new Date(Date.UTC(1899, 11, 30))) / (24 * 60 * 60 * 1000);
+}
+
+function sheet_from_array_of_arrays(data, opts) {
+  var ws = {};
+  var range = {
+    s: {
+      c: 10000000,
+      r: 10000000
+    },
+    e: {
+      c: 0,
+      r: 0
+    }
+  };
+  for (var R = 0; R != data.length; ++R) {
+    for (var C = 0; C != data[R].length; ++C) {
+      if (range.s.r > R) range.s.r = R;
+      if (range.s.c > C) range.s.c = C;
+      if (range.e.r < R) range.e.r = R;
+      if (range.e.c < C) range.e.c = C;
+      var cell = {
+        v: data[R][C]
+      };
+      if (cell.v == null) continue;
+      var cell_ref = XLSX.utils.encode_cell({
+        c: C,
+        r: R
+      });
+
+      if (typeof cell.v === 'number') cell.t = 'n';
+      else if (typeof cell.v === 'boolean') cell.t = 'b';
+      else if (cell.v instanceof Date) {
+        cell.t = 'n';
+        cell.z = XLSX.SSF._table[14];
+        cell.v = datenum(cell.v);
+      } else cell.t = 's';
+
+      ws[cell_ref] = cell;
+    }
+  }
+  if (range.s.c < 10000000) ws['!ref'] = XLSX.utils.encode_range(range);
+  return ws;
+}
+
+function Workbook() {
+  if (!(this instanceof Workbook)) return new Workbook();
+  this.SheetNames = [];
+  this.Sheets = {};
+}
+
+function s2ab(s) {
+  var buf = new ArrayBuffer(s.length);
+  var view = new Uint8Array(buf);
+  for (var i = 0; i != s.length; ++i) view[i] = s.charCodeAt(i) & 0xFF;
+  return buf;
+}
+
+export function export_table_to_excel(id) {
+  var theTable = document.getElementById(id);
+  var oo = generateArray(theTable);
+  var ranges = oo[1];
+
+  /* original data */
+  var data = oo[0];
+  var ws_name = "SheetJS";
+
+  var wb = new Workbook(),
+    ws = sheet_from_array_of_arrays(data);
+
+  /* add ranges to worksheet */
+  // ws['!cols'] = ['apple', 'banan'];
+  ws['!merges'] = ranges;
+
+  /* add worksheet to workbook */
+  wb.SheetNames.push(ws_name);
+  wb.Sheets[ws_name] = ws;
+
+  var wbout = XLSX.write(wb, {
+    bookType: 'xlsx',
+    bookSST: false,
+    type: 'binary'
+  });
+
+  saveAs(new Blob([s2ab(wbout)], {
+    type: "application/octet-stream"
+  }), "test.xlsx")
+}
+
+export function export_json_to_excel({
+  multiHeader = [],
+  header,
+  data,
+  filename,
+  merges = [],
+  autoWidth = true,
+  bookType = 'xlsx'
+} = {}) {
+  /* original data */
+  filename = filename || 'excel-list'
+  data = [...data]
+  data.unshift(header);
+
+  for (let i = multiHeader.length - 1; i > -1; i--) {
+    data.unshift(multiHeader[i])
+  }
+
+  var ws_name = "SheetJS";
+  var wb = new Workbook(),
+    ws = sheet_from_array_of_arrays(data);
+
+  if (merges.length > 0) {
+    if (!ws['!merges']) ws['!merges'] = [];
+    merges.forEach(item => {
+      ws['!merges'].push(XLSX.utils.decode_range(item))
+    })
+  }
+
+  if (autoWidth) {
+    /*设置worksheet每列的最大宽度*/
+    const colWidth = data.map(row => row.map(val => {
+      /*先判断是否为null/undefined*/
+      if (val == null) {
+        return {
+          'wch': 10
+        };
+      }
+      /*再判断是否为中文*/
+      else if (val.toString().charCodeAt(0) > 255) {
+        return {
+          'wch': val.toString().length * 2
+        };
+      } else {
+        return {
+          'wch': val.toString().length
+        };
+      }
+    }))
+    /*以第一行为初始值*/
+    let result = colWidth[0];
+    for (let i = 1; i < colWidth.length; i++) {
+      for (let j = 0; j < colWidth[i].length; j++) {
+        if (result[j]['wch'] < colWidth[i][j]['wch']) {
+          result[j]['wch'] = colWidth[i][j]['wch'];
+        }
+      }
+    }
+    ws['!cols'] = result;
+  }
+
+  /* add worksheet to workbook */
+  wb.SheetNames.push(ws_name);
+  wb.Sheets[ws_name] = ws;
+
+  var wbout = XLSX.write(wb, {
+    bookType: bookType,
+    bookSST: false,
+    type: 'binary'
+  });
+  saveAs(new Blob([s2ab(wbout)], {
+    type: "application/octet-stream"
+  }), `${filename}.${bookType}`);
+}
diff --git a/frontend/src/vendor/Export2Zip.js b/frontend/src/vendor/Export2Zip.js
new file mode 100644
index 0000000..db70707
--- /dev/null
+++ b/frontend/src/vendor/Export2Zip.js
@@ -0,0 +1,24 @@
+/* eslint-disable */
+import { saveAs } from 'file-saver'
+import JSZip from 'jszip'
+
+export function export_txt_to_zip(th, jsonData, txtName, zipName) {
+  const zip = new JSZip()
+  const txt_name = txtName || 'file'
+  const zip_name = zipName || 'file'
+  const data = jsonData
+  let txtData = `${th}\r\n`
+  data.forEach((row) => {
+    let tempStr = ''
+    tempStr = row.toString()
+    txtData += `${tempStr}\r\n`
+  })
+  zip.file(`${txt_name}.txt`, txtData)
+  zip.generateAsync({
+    type: "blob"
+  }).then((blob) => {
+    saveAs(blob, `${zip_name}.zip`)
+  }, (err) => {
+    alert('导出失败')
+  })
+}
diff --git a/frontend/src/views/charts/keyboard.vue b/frontend/src/views/charts/keyboard.vue
new file mode 100644
index 0000000..917f8ee
--- /dev/null
+++ b/frontend/src/views/charts/keyboard.vue
@@ -0,0 +1,23 @@
+<template>
+  <div class="chart-container">
+    <chart height="100%" width="100%" />
+  </div>
+</template>
+
+<script>
+import Chart from '@/components/Charts/Keyboard'
+
+export default {
+  name: 'KeyboardChart',
+  components: { Chart }
+}
+</script>
+
+<style scoped>
+.chart-container{
+  position: relative;
+  width: 100%;
+  height: calc(100vh - 84px);
+}
+</style>
+
diff --git a/frontend/src/views/charts/line.vue b/frontend/src/views/charts/line.vue
new file mode 100644
index 0000000..fea1497
--- /dev/null
+++ b/frontend/src/views/charts/line.vue
@@ -0,0 +1,23 @@
+<template>
+  <div class="chart-container">
+    <chart height="100%" width="100%" />
+  </div>
+</template>
+
+<script>
+import Chart from '@/components/Charts/LineMarker'
+
+export default {
+  name: 'LineChart',
+  components: { Chart }
+}
+</script>
+
+<style scoped>
+.chart-container{
+  position: relative;
+  width: 100%;
+  height: calc(100vh - 84px);
+}
+</style>
+
diff --git a/frontend/src/views/charts/mix-chart.vue b/frontend/src/views/charts/mix-chart.vue
new file mode 100644
index 0000000..c57db75
--- /dev/null
+++ b/frontend/src/views/charts/mix-chart.vue
@@ -0,0 +1,23 @@
+<template>
+  <div class="chart-container">
+    <chart height="100%" width="100%" />
+  </div>
+</template>
+
+<script>
+import Chart from '@/components/Charts/MixChart'
+
+export default {
+  name: 'MixChart',
+  components: { Chart }
+}
+</script>
+
+<style scoped>
+.chart-container{
+  position: relative;
+  width: 100%;
+  height: calc(100vh - 84px);
+}
+</style>
+
diff --git a/frontend/src/views/clipboard/index.vue b/frontend/src/views/clipboard/index.vue
new file mode 100644
index 0000000..4a6bdd1
--- /dev/null
+++ b/frontend/src/views/clipboard/index.vue
@@ -0,0 +1,49 @@
+<template>
+  <div class="app-container">
+    <el-tabs v-model="activeName">
+      <el-tab-pane label="use clipboard  directly" name="directly">
+        <el-input v-model="inputData" placeholder="Please input" style="width:400px;max-width:100%;" />
+        <el-button type="primary" icon="el-icon-document" @click="handleCopy(inputData,$event)">
+          copy
+        </el-button>
+      </el-tab-pane>
+      <el-tab-pane label="use clipboard by v-directive" name="v-directive">
+        <el-input v-model="inputData" placeholder="Please input" style="width:400px;max-width:100%;" />
+        <el-button v-clipboard:copy="inputData" v-clipboard:success="clipboardSuccess" type="primary" icon="el-icon-document">
+          copy
+        </el-button>
+      </el-tab-pane>
+    </el-tabs>
+  </div>
+</template>
+
+<script>
+import clip from '@/utils/clipboard' // use clipboard directly
+import clipboard from '@/directive/clipboard/index.js' // use clipboard by v-directive
+
+export default {
+  name: 'ClipboardDemo',
+  directives: {
+    clipboard
+  },
+  data() {
+    return {
+      activeName: 'directly',
+      inputData: 'https://github.com/PanJiaChen/vue-element-admin'
+    }
+  },
+  methods: {
+    handleCopy(text, event) {
+      clip(text, event)
+    },
+    clipboardSuccess() {
+      this.$message({
+        message: 'Copy successfully',
+        type: 'success',
+        duration: 1500
+      })
+    }
+  }
+}
+</script>
+
diff --git a/frontend/src/views/components-demo/avatar-upload.vue b/frontend/src/views/components-demo/avatar-upload.vue
new file mode 100644
index 0000000..5cb20a5
--- /dev/null
+++ b/frontend/src/views/components-demo/avatar-upload.vue
@@ -0,0 +1,61 @@
+<template>
+  <div class="components-container">
+    <aside>This is based on
+      <a class="link-type" href="//github.com/dai-siki/vue-image-crop-upload"> vue-image-crop-upload</a>.
+      Since I was using only the vue@1 version, and it is not compatible with mockjs at the moment, I modified it myself, and if you are going to use it, it is better to use official version.
+    </aside>
+
+    <pan-thumb :image="image" />
+
+    <el-button type="primary" icon="el-icon-upload" style="position: absolute;bottom: 15px;margin-left: 40px;" @click="imagecropperShow=true">
+      Change Avatar
+    </el-button>
+
+    <image-cropper
+      v-show="imagecropperShow"
+      :key="imagecropperKey"
+      :width="300"
+      :height="300"
+      url="https://httpbin.org/post"
+      lang-type="en"
+      @close="close"
+      @crop-upload-success="cropSuccess"
+    />
+  </div>
+</template>
+
+<script>
+import ImageCropper from '@/components/ImageCropper'
+import PanThumb from '@/components/PanThumb'
+
+export default {
+  name: 'AvatarUploadDemo',
+  components: { ImageCropper, PanThumb },
+  data() {
+    return {
+      imagecropperShow: false,
+      imagecropperKey: 0,
+      image: 'https://wpimg.wallstcn.com/577965b9-bb9e-4e02-9f0c-095b41417191'
+    }
+  },
+  methods: {
+    cropSuccess(resData) {
+      this.imagecropperShow = false
+      this.imagecropperKey = this.imagecropperKey + 1
+      this.image = resData.files.avatar
+    },
+    close() {
+      this.imagecropperShow = false
+    }
+  }
+}
+</script>
+
+<style scoped>
+  .avatar{
+    width: 200px;
+    height: 200px;
+    border-radius: 50%;
+  }
+</style>
+
diff --git a/frontend/src/views/components-demo/back-to-top.vue b/frontend/src/views/components-demo/back-to-top.vue
new file mode 100644
index 0000000..df5370b
--- /dev/null
+++ b/frontend/src/views/components-demo/back-to-top.vue
@@ -0,0 +1,154 @@
+<template>
+  <div class="components-container">
+    <aside>
+      When the page is scrolled to the specified position, the Back to Top button appears in the lower right corner
+    </aside>
+    <aside>
+      You can customize the style of the button, show / hide, height of appearance, height of the return. If you need a text prompt, you can use element-ui el-tooltip elements externally
+    </aside>
+    <div class="placeholder-container">
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+    </div>
+    <!-- you can add element-ui's tooltip -->
+    <el-tooltip placement="top" content="tooltip">
+      <back-to-top :custom-style="myBackToTopStyle" :visibility-height="300" :back-position="50" transition-name="fade" />
+    </el-tooltip>
+  </div>
+</template>
+
+<script>
+import BackToTop from '@/components/BackToTop'
+
+export default {
+  name: 'BackToTopDemo',
+  components: { BackToTop },
+  data() {
+    return {
+      // customizable button style, show/hide critical point, return position
+      myBackToTopStyle: {
+        right: '50px',
+        bottom: '50px',
+        width: '40px',
+        height: '40px',
+        'border-radius': '4px',
+        'line-height': '45px', // 请保持与高度一致以垂直居中 Please keep consistent with height to center vertically
+        background: '#e7eaf1'// 按钮的背景颜色 The background color of the button
+      }
+    }
+  }
+}
+</script>
+
+<style scoped>
+.placeholder-container div {
+  margin: 10px;
+}
+</style>
diff --git a/frontend/src/views/components-demo/count-to.vue b/frontend/src/views/components-demo/count-to.vue
new file mode 100644
index 0000000..70681df
--- /dev/null
+++ b/frontend/src/views/components-demo/count-to.vue
@@ -0,0 +1,218 @@
+<template>
+  <div class="components-container">
+    <aside>
+      <a href="https://github.com/PanJiaChen/vue-countTo" target="_blank">countTo-component</a>
+    </aside>
+    <count-to
+      ref="example"
+      :start-val="_startVal"
+      :end-val="_endVal"
+      :duration="_duration"
+      :decimals="_decimals"
+      :separator="_separator"
+      :prefix="_prefix"
+      :suffix="_suffix"
+      :autoplay="false"
+      class="example"
+    />
+    <div style="margin-left: 25%;margin-top: 40px;">
+      <label class="label" for="startValInput">startVal:
+        <input v-model.number="setStartVal" type="number" name="startValInput">
+      </label>
+      <label class="label" for="endValInput">endVal:
+        <input v-model.number="setEndVal" type="number" name="endVaInput">
+      </label>
+      <label class="label" for="durationInput">duration:
+        <input v-model.number="setDuration" type="number" name="durationInput">
+      </label>
+      <div class="startBtn example-btn" @click="start">
+        Start
+      </div>
+      <div class="pause-resume-btn example-btn" @click="pauseResume">
+        pause/resume
+      </div>
+      <br>
+      <label class="label" for="decimalsInput">decimals:
+        <input v-model.number="setDecimals" type="number" name="decimalsInput">
+      </label>
+      <label class="label" for="separatorInput">separator:
+        <input v-model="setSeparator" name="separatorInput">
+      </label>
+      <label class="label" for="prefixInput">prefix:
+        <input v-model="setPrefix" name="prefixInput">
+      </label>
+      <label class="label" for="suffixInput">suffix:
+        <input v-model="setSuffix" name="suffixInput">
+      </label>
+    </div>
+    <aside>&lt;count-to :start-val=&#x27;{{ _startVal }}&#x27; :end-val=&#x27;{{ _endVal }}&#x27; :duration=&#x27;{{ _duration }}&#x27;
+      :decimals=&#x27;{{ _decimals }}&#x27; :separator=&#x27;{{ _separator }}&#x27; :prefix=&#x27;{{ _prefix }}&#x27; :suffix=&#x27;{{ _suffix }}&#x27;
+      :autoplay=false&gt;</aside>
+  </div>
+</template>
+
+<script>
+import countTo from 'vue-count-to'
+
+export default {
+  name: 'CountToDemo',
+  components: { countTo },
+  data() {
+    return {
+      setStartVal: 0,
+      setEndVal: 2017,
+      setDuration: 4000,
+      setDecimals: 0,
+      setSeparator: ',',
+      setSuffix: ' rmb',
+      setPrefix: '¥ '
+    }
+  },
+  computed: {
+    _startVal() {
+      if (this.setStartVal) {
+        return this.setStartVal
+      } else {
+        return 0
+      }
+    },
+    _endVal() {
+      if (this.setEndVal) {
+        return this.setEndVal
+      } else {
+        return 0
+      }
+    },
+    _duration() {
+      if (this.setDuration) {
+        return this.setDuration
+      } else {
+        return 100
+      }
+    },
+    _decimals() {
+      if (this.setDecimals) {
+        if (this.setDecimals < 0 || this.setDecimals > 20) {
+          alert('digits argument must be between 0 and 20')
+          return 0
+        }
+        return this.setDecimals
+      } else {
+        return 0
+      }
+    },
+    _separator() {
+      return this.setSeparator
+    },
+    _suffix() {
+      return this.setSuffix
+    },
+    _prefix() {
+      return this.setPrefix
+    }
+  },
+  methods: {
+    start() {
+      this.$refs.example.start()
+    },
+    pauseResume() {
+      this.$refs.example.pauseResume()
+    }
+  }
+}
+</script>
+
+<style scoped>
+.example-btn {
+  display: inline-block;
+  margin-bottom: 0;
+  font-weight: 500;
+  text-align: center;
+  -ms-touch-action: manipulation;
+  touch-action: manipulation;
+  cursor: pointer;
+  background-image: none;
+  border: 1px solid transparent;
+  white-space: nowrap;
+  line-height: 1.5;
+  padding: 4px 15px;
+  font-size: 12px;
+  border-radius: 4px;
+  -webkit-user-select: none;
+  -moz-user-select: none;
+  -ms-user-select: none;
+  user-select: none;
+  -webkit-transition: all .3s cubic-bezier(.645, .045, .355, 1);
+  transition: all .3s cubic-bezier(.645, .045, .355, 1);
+  position: relative;
+  color: rgba(0, 0, 0, .65);
+  background-color: #fff;
+  border-color: #d9d9d9;
+}
+
+.example-btn:hover {
+  color: #4AB7BD;
+  background-color: #fff;
+  border-color: #4AB7BD;
+}
+.example {
+  font-size: 50px;
+  color: #F6416C;
+  display: block;
+  margin: 10px 0;
+  text-align: center;
+  font-size: 80px;
+  font-weight: 500;
+}
+
+.label {
+  color: #2f4f4f;
+  font-size: 16px;
+  display: inline-block;
+  margin: 15px 30px 15px 0;
+}
+
+input {
+  position: relative;
+  display: inline-block;
+  padding: 4px 7px;
+  width: 70px;
+  height: 28px;
+  cursor: text;
+  font-size: 12px;
+  line-height: 1.5;
+  color: rgba(0, 0, 0, .65);
+  background-color: #fff;
+  background-image: none;
+  border: 1px solid #d9d9d9;
+  border-radius: 4px;
+  -webkit-transition: all .3s;
+  transition: all .3s;
+}
+
+.startBtn {
+  margin-left: 20px;
+  font-size: 20px;
+  color: #30B08F;
+  background-color: #fff;
+}
+
+.startBtn:hover {
+  background-color: #30B08F;
+  color: #fff;
+  border-color: #30B08F;
+}
+
+.pause-resume-btn {
+  font-size: 20px;
+  color: #E65D6E;
+  background-color: #fff;
+}
+
+.pause-resume-btn:hover {
+  background-color: #E65D6E;
+  color: #fff;
+  border-color: #E65D6E;
+}
+</style>
+
diff --git a/frontend/src/views/components-demo/dnd-list.vue b/frontend/src/views/components-demo/dnd-list.vue
new file mode 100644
index 0000000..e299fa6
--- /dev/null
+++ b/frontend/src/views/components-demo/dnd-list.vue
@@ -0,0 +1,39 @@
+<template>
+  <div class="components-container">
+    <aside>drag-list base on
+      <a href="https://github.com/SortableJS/Vue.Draggable" target="_blank">Vue.Draggable</a>
+    </aside>
+    <div class="editor-container">
+      <dnd-list :list1="list1" :list2="list2" list1-title="List" list2-title="Article pool" />
+    </div>
+  </div>
+</template>
+
+<script>
+import DndList from '@/components/DndList'
+import { fetchList } from '@/api/article'
+
+export default {
+  name: 'DndListDemo',
+  components: { DndList },
+  data() {
+    return {
+      list1: [],
+      list2: []
+    }
+  },
+  created() {
+    this.getData()
+  },
+  methods: {
+    getData() {
+      this.listLoading = true
+      fetchList().then(response => {
+        this.list1 = response.data.items.splice(0, 5)
+        this.list2 = response.data.items
+      })
+    }
+  }
+}
+</script>
+
diff --git a/frontend/src/views/components-demo/drag-dialog.vue b/frontend/src/views/components-demo/drag-dialog.vue
new file mode 100644
index 0000000..c815b28
--- /dev/null
+++ b/frontend/src/views/components-demo/drag-dialog.vue
@@ -0,0 +1,61 @@
+<template>
+  <div class="components-container">
+    <el-button type="primary" @click="dialogTableVisible = true">
+      open a Drag Dialog
+    </el-button>
+    <el-dialog v-el-drag-dialog :visible.sync="dialogTableVisible" title="Shipping address" @dragDialog="handleDrag">
+      <el-select ref="select" v-model="value" placeholder="请选择">
+        <el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value" />
+      </el-select>
+      <el-table :data="gridData">
+        <el-table-column property="date" label="Date" width="150" />
+        <el-table-column property="name" label="Name" width="200" />
+        <el-table-column property="address" label="Address" />
+      </el-table>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import elDragDialog from '@/directive/el-drag-dialog' // base on element-ui
+
+export default {
+  name: 'DragDialogDemo',
+  directives: { elDragDialog },
+  data() {
+    return {
+      dialogTableVisible: false,
+      options: [
+        { value: '选项1', label: '黄金糕' },
+        { value: '选项2', label: '双皮奶' },
+        { value: '选项3', label: '蚵仔煎' },
+        { value: '选项4', label: '龙须面' }
+      ],
+      value: '',
+      gridData: [{
+        date: '2016-05-02',
+        name: 'John Smith',
+        address: 'No.1518,  Jinshajiang Road, Putuo District'
+      }, {
+        date: '2016-05-04',
+        name: 'John Smith',
+        address: 'No.1518,  Jinshajiang Road, Putuo District'
+      }, {
+        date: '2016-05-01',
+        name: 'John Smith',
+        address: 'No.1518,  Jinshajiang Road, Putuo District'
+      }, {
+        date: '2016-05-03',
+        name: 'John Smith',
+        address: 'No.1518,  Jinshajiang Road, Putuo District'
+      }]
+    }
+  },
+  methods: {
+    // v-el-drag-dialog onDrag callback function
+    handleDrag() {
+      this.$refs.select.blur()
+    }
+  }
+}
+</script>
diff --git a/frontend/src/views/components-demo/drag-kanban.vue b/frontend/src/views/components-demo/drag-kanban.vue
new file mode 100644
index 0000000..943be45
--- /dev/null
+++ b/frontend/src/views/components-demo/drag-kanban.vue
@@ -0,0 +1,66 @@
+<template>
+  <div class="components-container board">
+    <Kanban :key="1" :list="list1" :group="group" class="kanban todo" header-text="Todo" />
+    <Kanban :key="2" :list="list2" :group="group" class="kanban working" header-text="Working" />
+    <Kanban :key="3" :list="list3" :group="group" class="kanban done" header-text="Done" />
+  </div>
+</template>
+<script>
+import Kanban from '@/components/Kanban'
+
+export default {
+  name: 'DragKanbanDemo',
+  components: {
+    Kanban
+  },
+  data() {
+    return {
+      group: 'mission',
+      list1: [
+        { name: 'Mission', id: 1 },
+        { name: 'Mission', id: 2 },
+        { name: 'Mission', id: 3 },
+        { name: 'Mission', id: 4 }
+      ],
+      list2: [
+        { name: 'Mission', id: 5 },
+        { name: 'Mission', id: 6 },
+        { name: 'Mission', id: 7 }
+      ],
+      list3: [
+        { name: 'Mission', id: 8 },
+        { name: 'Mission', id: 9 },
+        { name: 'Mission', id: 10 }
+      ]
+    }
+  }
+}
+</script>
+<style lang="scss">
+.board {
+  width: 1000px;
+  margin-left: 20px;
+  display: flex;
+  justify-content: space-around;
+  flex-direction: row;
+  align-items: flex-start;
+}
+.kanban {
+  &.todo {
+    .board-column-header {
+      background: #4A9FF9;
+    }
+  }
+  &.working {
+    .board-column-header {
+      background: #f9944a;
+    }
+  }
+  &.done {
+    .board-column-header {
+      background: #2ac06d;
+    }
+  }
+}
+</style>
+
diff --git a/frontend/src/views/components-demo/drag-select.vue b/frontend/src/views/components-demo/drag-select.vue
new file mode 100644
index 0000000..905ecb9
--- /dev/null
+++ b/frontend/src/views/components-demo/drag-select.vue
@@ -0,0 +1,43 @@
+<template>
+  <div class="components-container">
+    <el-drag-select v-model="value" style="width:500px;" multiple placeholder="请选择">
+      <el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value" />
+    </el-drag-select>
+
+    <div style="margin-top:30px;">
+      <el-tag v-for="item of value" :key="item" style="margin-right:15px;">
+        {{ item }}
+      </el-tag>
+    </div>
+  </div>
+</template>
+
+<script>
+import ElDragSelect from '@/components/DragSelect' // base on element-ui
+
+export default {
+  name: 'DragSelectDemo',
+  components: { ElDragSelect },
+  data() {
+    return {
+      value: ['Apple', 'Banana', 'Orange'],
+      options: [{
+        value: 'Apple',
+        label: 'Apple'
+      }, {
+        value: 'Banana',
+        label: 'Banana'
+      }, {
+        value: 'Orange',
+        label: 'Orange'
+      }, {
+        value: 'Pear',
+        label: 'Pear'
+      }, {
+        value: 'Strawberry',
+        label: 'Strawberry'
+      }]
+    }
+  }
+}
+</script>
diff --git a/frontend/src/views/components-demo/dropzone.vue b/frontend/src/views/components-demo/dropzone.vue
new file mode 100644
index 0000000..a8c1040
--- /dev/null
+++ b/frontend/src/views/components-demo/dropzone.vue
@@ -0,0 +1,31 @@
+<template>
+  <div class="components-container">
+    <aside>
+      Based on <a class="link-type" href="https://github.com/rowanwins/vue-dropzone"> dropzone </a>.
+      Because my business has special needs, and has to upload images to qiniu, so instead of a third party, I chose encapsulate it by myself. It is very simple, you can see the detail code in @/components/Dropzone.
+    </aside>
+    <div class="editor-container">
+      <dropzone id="myVueDropzone" url="https://httpbin.org/post" @dropzone-removedFile="dropzoneR" @dropzone-success="dropzoneS" />
+    </div>
+  </div>
+</template>
+
+<script>
+import Dropzone from '@/components/Dropzone'
+
+export default {
+  name: 'DropzoneDemo',
+  components: { Dropzone },
+  methods: {
+    dropzoneS(file) {
+      console.log(file)
+      this.$message({ message: 'Upload success', type: 'success' })
+    },
+    dropzoneR(file) {
+      console.log(file)
+      this.$message({ message: 'Delete success', type: 'success' })
+    }
+  }
+}
+</script>
+
diff --git a/frontend/src/views/components-demo/json-editor.vue b/frontend/src/views/components-demo/json-editor.vue
new file mode 100644
index 0000000..85bf383
--- /dev/null
+++ b/frontend/src/views/components-demo/json-editor.vue
@@ -0,0 +1,36 @@
+<template>
+  <div class="components-container">
+    <aside>Json-Editor is base on <a href="https://github.com/codemirror/CodeMirror" target="_blank">CodeMirrorr</a>. Lint
+      base on <a
+        href="https://github.com/codemirror/CodeMirror/blob/master/addon/lint/json-lint.js"
+        target="_blank"
+      >json-lint</a>.</aside>
+    <div class="editor-container">
+      <json-editor ref="jsonEditor" v-model="value" />
+    </div>
+  </div>
+</template>
+
+<script>
+import JsonEditor from '@/components/JsonEditor'
+
+const jsonData = '[{"items":[{"market_type":"forexdata","symbol":"XAUUSD"},{"market_type":"forexdata","symbol":"UKOIL"},{"market_type":"forexdata","symbol":"CORN"}],"name":""},{"items":[{"market_type":"forexdata","symbol":"XAUUSD"},{"market_type":"forexdata","symbol":"XAGUSD"},{"market_type":"forexdata","symbol":"AUTD"},{"market_type":"forexdata","symbol":"AGTD"}],"name":"贵金属"},{"items":[{"market_type":"forexdata","symbol":"CORN"},{"market_type":"forexdata","symbol":"WHEAT"},{"market_type":"forexdata","symbol":"SOYBEAN"},{"market_type":"forexdata","symbol":"SUGAR"}],"name":"农产品"},{"items":[{"market_type":"forexdata","symbol":"UKOIL"},{"market_type":"forexdata","symbol":"USOIL"},{"market_type":"forexdata","symbol":"NGAS"}],"name":"能源化工"}]'
+
+export default {
+  name: 'JsonEditorDemo',
+  components: { JsonEditor },
+  data() {
+    return {
+      value: JSON.parse(jsonData)
+    }
+  }
+}
+</script>
+
+<style scoped>
+.editor-container{
+  position: relative;
+  height: 100%;
+}
+</style>
+
diff --git a/frontend/src/views/components-demo/markdown.vue b/frontend/src/views/components-demo/markdown.vue
new file mode 100644
index 0000000..25cf3e3
--- /dev/null
+++ b/frontend/src/views/components-demo/markdown.vue
@@ -0,0 +1,101 @@
+<template>
+  <div class="components-container">
+    <aside>Markdown is based on
+      <a href="https://github.com/nhnent/tui.editor" target="_blank">tui.editor</a> ,simply wrapped with Vue.
+      <a
+        target="_blank"
+        href="https://panjiachen.github.io/vue-element-admin-site/feature/component/markdown-editor.html"
+      >
+        Documentation </a>
+    </aside>
+
+    <div class="editor-container">
+      <el-tag class="tag-title">
+        Basic:
+      </el-tag>
+      <markdown-editor v-model="content1" height="300px" />
+    </div>
+
+    <div class="editor-container">
+      <el-tag class="tag-title">
+        Markdown Mode:
+      </el-tag>
+      <markdown-editor ref="markdownEditor" v-model="content2" :options="{hideModeSwitch:true,previewStyle:'tab'}" height="200px" />
+    </div>
+
+    <div class="editor-container">
+      <el-tag class="tag-title">
+        Customize Toolbar:
+      </el-tag>
+      <markdown-editor v-model="content3" :options="{ toolbarItems: ['heading','bold','italic']}" />
+    </div>
+
+    <div class="editor-container">
+      <el-tag class="tag-title">
+        I18n:
+      </el-tag>
+      <el-alert
+        :closable="false"
+        title="You can change the language of the admin system to see the effect"
+        type="success"
+      />
+      <markdown-editor ref="markdownEditor" v-model="content4" :language="language" height="300px" />
+    </div>
+
+    <el-button style="margin-top:80px;" type="primary" icon="el-icon-document" @click="getHtml">
+      Get HTML
+    </el-button>
+    <div v-html="html" />
+  </div>
+</template>
+
+<script>
+import MarkdownEditor from '@/components/MarkdownEditor'
+
+const content = `
+**This is test**
+
+* vue
+* element
+* webpack
+
+`
+export default {
+  name: 'MarkdownDemo',
+  components: { MarkdownEditor },
+  data() {
+    return {
+      content1: content,
+      content2: content,
+      content3: content,
+      content4: content,
+      html: '',
+      languageTypeList: {
+        'en': 'en_US',
+        'zh': 'zh_CN',
+        'es': 'es_ES'
+      }
+    }
+  },
+  computed: {
+    language() {
+      return this.languageTypeList['en']
+    }
+  },
+  methods: {
+    getHtml() {
+      this.html = this.$refs.markdownEditor.getHtml()
+      console.log(this.html)
+    }
+  }
+}
+</script>
+
+<style scoped>
+.editor-container{
+  margin-bottom: 30px;
+}
+.tag-title{
+  margin-bottom: 5px;
+}
+</style>
diff --git a/frontend/src/views/components-demo/mixin.vue b/frontend/src/views/components-demo/mixin.vue
new file mode 100644
index 0000000..425cf4f
--- /dev/null
+++ b/frontend/src/views/components-demo/mixin.vue
@@ -0,0 +1,169 @@
+<template>
+  <div class="mixin-components-container">
+    <el-row>
+      <el-card class="box-card">
+        <div slot="header" class="clearfix">
+          <span>Buttons</span>
+        </div>
+        <div style="margin-bottom:50px;">
+          <el-col :span="4" class="text-center">
+            <router-link class="pan-btn blue-btn" to="/documentation/index">
+              Documentation
+            </router-link>
+          </el-col>
+          <el-col :span="4" class="text-center">
+            <router-link class="pan-btn light-blue-btn" to="/icon/index">
+              Icons
+            </router-link>
+          </el-col>
+          <el-col :span="4" class="text-center">
+            <router-link class="pan-btn pink-btn" to="/excel/export-excel">
+              Excel
+            </router-link>
+          </el-col>
+          <el-col :span="4" class="text-center">
+            <router-link class="pan-btn green-btn" to="/table/complex-table">
+              Table
+            </router-link>
+          </el-col>
+          <el-col :span="4" class="text-center">
+            <router-link class="pan-btn tiffany-btn" to="/example/create">
+              Form
+            </router-link>
+          </el-col>
+          <el-col :span="4" class="text-center">
+            <router-link class="pan-btn yellow-btn" to="/theme/index">
+              Theme
+            </router-link>
+          </el-col>
+        </div>
+      </el-card>
+    </el-row>
+
+    <el-row :gutter="20" style="margin-top:50px;">
+      <el-col :span="6">
+        <el-card class="box-card">
+          <div slot="header" class="clearfix">
+            <span>Material Design 的input</span>
+          </div>
+          <div style="height:100px;">
+            <el-form :model="demo" :rules="demoRules">
+              <el-form-item prop="title">
+                <md-input v-model="demo.title" icon="el-icon-search" name="title" placeholder="输入标题">
+                  标题
+                </md-input>
+              </el-form-item>
+            </el-form>
+          </div>
+        </el-card>
+      </el-col>
+
+      <el-col :span="6">
+        <el-card class="box-card">
+          <div slot="header" class="clearfix">
+            <span>图片hover效果</span>
+          </div>
+          <div class="component-item">
+            <pan-thumb width="100px" height="100px" image="https://wpimg.wallstcn.com/577965b9-bb9e-4e02-9f0c-095b41417191">
+              vue-element-admin
+            </pan-thumb>
+          </div>
+        </el-card>
+      </el-col>
+
+      <el-col :span="6">
+        <el-card class="box-card">
+          <div slot="header" class="clearfix">
+            <span>水波纹 waves v-directive</span>
+          </div>
+          <div class="component-item">
+            <el-button v-waves type="primary">
+              水波纹效果
+            </el-button>
+          </div>
+        </el-card>
+      </el-col>
+
+      <el-col :span="6">
+        <el-card class="box-card">
+          <div slot="header" class="clearfix">
+            <span>hover text</span>
+          </div>
+          <div class="component-item">
+            <mallki class-name="mallki-text" text="vue-element-admin" />
+          </div>
+        </el-card>
+      </el-col>
+    </el-row>
+
+    <el-row :gutter="20" style="margin-top:50px;">
+      <el-col :span="8">
+        <el-card class="box-card">
+          <div slot="header" class="clearfix">
+            <span>Share</span>
+          </div>
+          <div class="component-item" style="height:420px;">
+            <dropdown-menu :items="articleList" style="margin:0 auto;" title="系列文章" />
+          </div>
+        </el-card>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script>
+import PanThumb from '@/components/PanThumb'
+import MdInput from '@/components/MDinput'
+import Mallki from '@/components/TextHoverEffect/Mallki'
+import DropdownMenu from '@/components/Share/DropdownMenu'
+import waves from '@/directive/waves/index.js' // 水波纹指令
+
+export default {
+  name: 'ComponentMixinDemo',
+  components: {
+    PanThumb,
+    MdInput,
+    Mallki,
+    DropdownMenu
+  },
+  directives: {
+    waves
+  },
+  data() {
+    const validate = (rule, value, callback) => {
+      if (value.length !== 6) {
+        callback(new Error('请输入六个字符'))
+      } else {
+        callback()
+      }
+    }
+    return {
+      demo: {
+        title: ''
+      },
+      demoRules: {
+        title: [{ required: true, trigger: 'change', validator: validate }]
+      },
+      articleList: [
+        { title: '基础篇', href: 'https://juejin.im/post/59097cd7a22b9d0065fb61d2' },
+        { title: '登录权限篇', href: 'https://juejin.im/post/591aa14f570c35006961acac' },
+        { title: '实战篇', href: 'https://juejin.im/post/593121aa0ce4630057f70d35' },
+        { title: 'vue-admin-template 篇', href: 'https://juejin.im/post/595b4d776fb9a06bbe7dba56' },
+        { title: 'v4.0 篇', href: 'https://juejin.im/post/5c92ff94f265da6128275a85' },
+        { title: '优雅的使用 icon', href: 'https://juejin.im/post/59bb864b5188257e7a427c09' }
+      ]
+    }
+  }
+}
+</script>
+
+<style scoped>
+.mixin-components-container {
+  background-color: #f0f2f5;
+  padding: 30px;
+  min-height: calc(100vh - 84px);
+}
+.component-item{
+  min-height: 100px;
+}
+</style>
diff --git a/frontend/src/views/components-demo/split-pane.vue b/frontend/src/views/components-demo/split-pane.vue
new file mode 100644
index 0000000..7dba353
--- /dev/null
+++ b/frontend/src/views/components-demo/split-pane.vue
@@ -0,0 +1,67 @@
+<template>
+  <div class="components-container">
+    <aside><strong>SplitPane</strong> If you've used
+      <a href="https://codepen.io/" target="_blank"> codepen</a>,
+      <a href="https://jsfiddle.net/" target="_blank"> jsfiddle </a>will not be unfamiliar.
+      <a href="https://github.com/PanJiaChen/vue-split-pane" target="_blank"> Github repository</a>
+    </aside>
+    <split-pane split="vertical" @resize="resize">
+      <template slot="paneL">
+        <div class="left-container" />
+      </template>
+      <template slot="paneR">
+        <split-pane split="horizontal">
+          <template slot="paneL">
+            <div class="top-container" />
+          </template>
+          <template slot="paneR">
+            <div class="bottom-container" />
+          </template>
+        </split-pane>
+      </template>
+    </split-pane>
+  </div>
+</template>
+
+<script>
+import splitPane from 'vue-splitpane'
+
+export default {
+  name: 'SplitpaneDemo',
+  components: { splitPane },
+  methods: {
+    resize() {
+      console.log('resize')
+    }
+  }
+}
+</script>
+
+<style  scoped>
+  .components-container {
+    position: relative;
+    height: 100vh;
+  }
+
+  .left-container {
+    background-color: #F38181;
+    height: 100%;
+  }
+
+  .right-container {
+    background-color: #FCE38A;
+    height: 200px;
+  }
+
+  .top-container {
+    background-color: #FCE38A;
+    width: 100%;
+    height: 100%;
+  }
+
+  .bottom-container {
+    width: 100%;
+    background-color: #95E1D3;
+    height: 100%;
+  }
+</style>
diff --git a/frontend/src/views/components-demo/sticky.vue b/frontend/src/views/components-demo/sticky.vue
new file mode 100644
index 0000000..f01d088
--- /dev/null
+++ b/frontend/src/views/components-demo/sticky.vue
@@ -0,0 +1,135 @@
+<template>
+  <div>
+    <sticky :z-index="10" class-name="sub-navbar">
+      <el-dropdown trigger="click">
+        <el-button plain>
+          Platform<i class="el-icon-caret-bottom el-icon--right" />
+        </el-button>
+        <el-dropdown-menu slot="dropdown" class="no-border">
+          <el-checkbox-group v-model="platforms" style="padding: 5px 15px;">
+            <el-checkbox v-for="item in platformsOptions" :key="item.key" :label="item.key">
+              {{ item.name }}
+            </el-checkbox>
+          </el-checkbox-group>
+        </el-dropdown-menu>
+      </el-dropdown>
+
+      <el-dropdown trigger="click">
+        <el-button plain>
+          Link<i class="el-icon-caret-bottom el-icon--right" />
+        </el-button>
+        <el-dropdown-menu slot="dropdown" class="no-padding no-border" style="width:300px">
+          <el-input v-model="url" placeholder="Please enter the content">
+            <template slot="prepend">
+              Url
+            </template>
+          </el-input>
+        </el-dropdown-menu>
+      </el-dropdown>
+
+      <div class="time-container">
+        <el-date-picker v-model="time" type="datetime" format="yyyy-MM-dd HH:mm:ss" placeholder="Release time" />
+      </div>
+
+      <el-button style="margin-left: 10px;" type="success">
+        publish
+      </el-button>
+    </sticky>
+
+    <div class="components-container">
+      <aside>
+        Sticky header, When the page is scrolled to the preset position will be sticky on the top.
+      </aside>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <sticky :sticky-top="200">
+        <el-button type="primary"> placeholder</el-button>
+      </sticky>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+      <div>placeholder</div>
+    </div>
+  </div>
+</template>
+
+<script>
+import Sticky from '@/components/Sticky'
+
+export default {
+  name: 'StickyDemo',
+  components: { Sticky },
+  data() {
+    return {
+      time: '',
+      url: '',
+      platforms: ['a-platform'],
+      platformsOptions: [
+        { key: 'a-platform', name: 'platformA' },
+        { key: 'b-platform', name: 'platformB' },
+        { key: 'c-platform', name: 'platformC' }
+      ],
+      pickerOptions: {
+        disabledDate(time) {
+          return time.getTime() > Date.now()
+        }
+      }
+    }
+  }
+}
+</script>
+
+<style scoped>
+.components-container div {
+  margin: 10px;
+}
+
+.time-container {
+  display: inline-block;
+}
+</style>
diff --git a/frontend/src/views/components-demo/tinymce.vue b/frontend/src/views/components-demo/tinymce.vue
new file mode 100644
index 0000000..f03389a
--- /dev/null
+++ b/frontend/src/views/components-demo/tinymce.vue
@@ -0,0 +1,36 @@
+<template>
+  <div class="components-container">
+    <aside>
+      Rich text is a core feature of the management backend, but at the same time it is a place with lots of pits. In the process of selecting rich texts, I also took a lot of detours. The common rich texts on the market have been basically used, and I finally chose Tinymce. See the more detailed rich text comparison and introduction.
+      <a target="_blank" class="link-type" href="https://panjiachen.github.io/vue-element-admin-site/feature/component/rich-editor.html">Documentation</a>
+    </aside>
+    <div>
+      <tinymce v-model="content" :height="300" />
+    </div>
+    <div class="editor-content" v-html="content" />
+  </div>
+</template>
+
+<script>
+import Tinymce from '@/components/Tinymce'
+
+export default {
+  name: 'TinymceDemo',
+  components: { Tinymce },
+  data() {
+    return {
+      content:
+      `<h1 style="text-align: center;">Welcome to the TinyMCE demo!</h1><p style="text-align: center; font-size: 15px;"><img title="TinyMCE Logo" src="//www.tinymce.com/images/glyph-tinymce@2x.png" alt="TinyMCE Logo" width="110" height="97" /><ul>
+        <li>Our <a href="//www.tinymce.com/docs/">documentation</a> is a great resource for learning how to configure TinyMCE.</li><li>Have a specific question? Visit the <a href="https://community.tinymce.com/forum/">Community Forum</a>.</li><li>We also offer enterprise grade support as part of <a href="https://tinymce.com/pricing">TinyMCE premium subscriptions</a>.</li>
+      </ul>`
+    }
+  }
+}
+</script>
+
+<style scoped>
+.editor-content{
+  margin-top: 20px;
+}
+</style>
+
diff --git a/frontend/src/views/dashboard/admin/components/BarChart.vue b/frontend/src/views/dashboard/admin/components/BarChart.vue
new file mode 100644
index 0000000..be0af34
--- /dev/null
+++ b/frontend/src/views/dashboard/admin/components/BarChart.vue
@@ -0,0 +1,102 @@
+<template>
+  <div :class="className" :style="{height:height,width:width}" />
+</template>
+
+<script>
+import echarts from 'echarts'
+require('echarts/theme/macarons') // echarts theme
+import resize from './mixins/resize'
+
+const animationDuration = 6000
+
+export default {
+  mixins: [resize],
+  props: {
+    className: {
+      type: String,
+      default: 'chart'
+    },
+    width: {
+      type: String,
+      default: '100%'
+    },
+    height: {
+      type: String,
+      default: '300px'
+    }
+  },
+  data() {
+    return {
+      chart: null
+    }
+  },
+  mounted() {
+    this.$nextTick(() => {
+      this.initChart()
+    })
+  },
+  beforeDestroy() {
+    if (!this.chart) {
+      return
+    }
+    this.chart.dispose()
+    this.chart = null
+  },
+  methods: {
+    initChart() {
+      this.chart = echarts.init(this.$el, 'macarons')
+
+      this.chart.setOption({
+        tooltip: {
+          trigger: 'axis',
+          axisPointer: { // 坐标轴指示器,坐标轴触发有效
+            type: 'shadow' // 默认为直线,可选为:'line' | 'shadow'
+          }
+        },
+        grid: {
+          top: 10,
+          left: '2%',
+          right: '2%',
+          bottom: '3%',
+          containLabel: true
+        },
+        xAxis: [{
+          type: 'category',
+          data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
+          axisTick: {
+            alignWithLabel: true
+          }
+        }],
+        yAxis: [{
+          type: 'value',
+          axisTick: {
+            show: false
+          }
+        }],
+        series: [{
+          name: 'pageA',
+          type: 'bar',
+          stack: 'vistors',
+          barWidth: '60%',
+          data: [79, 52, 200, 334, 390, 330, 220],
+          animationDuration
+        }, {
+          name: 'pageB',
+          type: 'bar',
+          stack: 'vistors',
+          barWidth: '60%',
+          data: [80, 52, 200, 334, 390, 330, 220],
+          animationDuration
+        }, {
+          name: 'pageC',
+          type: 'bar',
+          stack: 'vistors',
+          barWidth: '60%',
+          data: [30, 52, 200, 334, 390, 330, 220],
+          animationDuration
+        }]
+      })
+    }
+  }
+}
+</script>
diff --git a/frontend/src/views/dashboard/admin/components/BoxCard.vue b/frontend/src/views/dashboard/admin/components/BoxCard.vue
new file mode 100644
index 0000000..1ec1d37
--- /dev/null
+++ b/frontend/src/views/dashboard/admin/components/BoxCard.vue
@@ -0,0 +1,118 @@
+<template>
+  <el-card class="box-card-component" style="margin-left:8px;">
+    <div slot="header" class="box-card-header">
+      <img src="https://wpimg.wallstcn.com/e7d23d71-cf19-4b90-a1cc-f56af8c0903d.png">
+    </div>
+    <div style="position:relative;">
+      <pan-thumb :image="avatar" class="panThumb" />
+      <mallki class-name="mallki-text" text="vue-element-admin" />
+      <div style="padding-top:35px;" class="progress-item">
+        <span>Vue</span>
+        <el-progress :percentage="70" />
+      </div>
+      <div class="progress-item">
+        <span>JavaScript</span>
+        <el-progress :percentage="18" />
+      </div>
+      <div class="progress-item">
+        <span>CSS</span>
+        <el-progress :percentage="12" />
+      </div>
+      <div class="progress-item">
+        <span>ESLint</span>
+        <el-progress :percentage="100" status="success" />
+      </div>
+    </div>
+  </el-card>
+</template>
+
+<script>
+import { mapGetters } from 'vuex'
+import PanThumb from '@/components/PanThumb'
+import Mallki from '@/components/TextHoverEffect/Mallki'
+
+export default {
+  components: { PanThumb, Mallki },
+
+  filters: {
+    statusFilter(status) {
+      const statusMap = {
+        success: 'success',
+        pending: 'danger'
+      }
+      return statusMap[status]
+    }
+  },
+  data() {
+    return {
+      statisticsData: {
+        article_count: 1024,
+        pageviews_count: 1024
+      }
+    }
+  },
+  computed: {
+    ...mapGetters([
+      'name',
+      'avatar',
+      'roles'
+    ])
+  }
+}
+</script>
+
+<style lang="scss" >
+.box-card-component{
+  .el-card__header {
+    padding: 0px!important;
+  }
+}
+</style>
+<style lang="scss" scoped>
+.box-card-component {
+  .box-card-header {
+    position: relative;
+    height: 220px;
+    img {
+      width: 100%;
+      height: 100%;
+      transition: all 0.2s linear;
+      &:hover {
+        transform: scale(1.1, 1.1);
+        filter: contrast(130%);
+      }
+    }
+  }
+  .mallki-text {
+    position: absolute;
+    top: 0px;
+    right: 0px;
+    font-size: 20px;
+    font-weight: bold;
+  }
+  .panThumb {
+    z-index: 100;
+    height: 70px!important;
+    width: 70px!important;
+    position: absolute!important;
+    top: -45px;
+    left: 0px;
+    border: 5px solid #ffffff;
+    background-color: #fff;
+    margin: auto;
+    box-shadow: none!important;
+    ::v-deep .pan-info {
+      box-shadow: none!important;
+    }
+  }
+  .progress-item {
+    margin-bottom: 10px;
+    font-size: 14px;
+  }
+  @media only screen and (max-width: 1510px){
+    .mallki-text{
+      display: none;
+    }
+  }
+}
+</style>
diff --git a/frontend/src/views/dashboard/admin/components/LineChart.vue b/frontend/src/views/dashboard/admin/components/LineChart.vue
new file mode 100644
index 0000000..e654168
--- /dev/null
+++ b/frontend/src/views/dashboard/admin/components/LineChart.vue
@@ -0,0 +1,135 @@
+<template>
+  <div :class="className" :style="{height:height,width:width}" />
+</template>
+
+<script>
+import echarts from 'echarts'
+require('echarts/theme/macarons') // echarts theme
+import resize from './mixins/resize'
+
+export default {
+  mixins: [resize],
+  props: {
+    className: {
+      type: String,
+      default: 'chart'
+    },
+    width: {
+      type: String,
+      default: '100%'
+    },
+    height: {
+      type: String,
+      default: '350px'
+    },
+    autoResize: {
+      type: Boolean,
+      default: true
+    },
+    chartData: {
+      type: Object,
+      required: true
+    }
+  },
+  data() {
+    return {
+      chart: null
+    }
+  },
+  watch: {
+    chartData: {
+      deep: true,
+      handler(val) {
+        this.setOptions(val)
+      }
+    }
+  },
+  mounted() {
+    this.$nextTick(() => {
+      this.initChart()
+    })
+  },
+  beforeDestroy() {
+    if (!this.chart) {
+      return
+    }
+    this.chart.dispose()
+    this.chart = null
+  },
+  methods: {
+    initChart() {
+      this.chart = echarts.init(this.$el, 'macarons')
+      this.setOptions(this.chartData)
+    },
+    setOptions({ expectedData, actualData } = {}) {
+      this.chart.setOption({
+        xAxis: {
+          data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
+          boundaryGap: false,
+          axisTick: {
+            show: false
+          }
+        },
+        grid: {
+          left: 10,
+          right: 10,
+          bottom: 20,
+          top: 30,
+          containLabel: true
+        },
+        tooltip: {
+          trigger: 'axis',
+          axisPointer: {
+            type: 'cross'
+          },
+          padding: [5, 10]
+        },
+        yAxis: {
+          axisTick: {
+            show: false
+          }
+        },
+        legend: {
+          data: ['expected', 'actual']
+        },
+        series: [{
+          name: 'expected', itemStyle: {
+            normal: {
+              color: '#FF005A',
+              lineStyle: {
+                color: '#FF005A',
+                width: 2
+              }
+            }
+          },
+          smooth: true,
+          type: 'line',
+          data: expectedData,
+          animationDuration: 2800,
+          animationEasing: 'cubicInOut'
+        },
+        {
+          name: 'actual',
+          smooth: true,
+          type: 'line',
+          itemStyle: {
+            normal: {
+              color: '#3888fa',
+              lineStyle: {
+                color: '#3888fa',
+                width: 2
+              },
+              areaStyle: {
+                color: '#f3f8ff'
+              }
+            }
+          },
+          data: actualData,
+          animationDuration: 2800,
+          animationEasing: 'quadraticOut'
+        }]
+      })
+    }
+  }
+}
+</script>
diff --git a/frontend/src/views/dashboard/admin/components/PanelGroup.vue b/frontend/src/views/dashboard/admin/components/PanelGroup.vue
new file mode 100644
index 0000000..589236e
--- /dev/null
+++ b/frontend/src/views/dashboard/admin/components/PanelGroup.vue
@@ -0,0 +1,181 @@
+<template>
+  <el-row :gutter="40" class="panel-group">
+    <el-col :xs="12" :sm="12" :lg="6" class="card-panel-col">
+      <div class="card-panel" @click="handleSetLineChartData('newVisitis')">
+        <div class="card-panel-icon-wrapper icon-people">
+          <svg-icon icon-class="peoples" class-name="card-panel-icon" />
+        </div>
+        <div class="card-panel-description">
+          <div class="card-panel-text">
+            New Visits
+          </div>
+          <count-to :start-val="0" :end-val="102400" :duration="2600" class="card-panel-num" />
+        </div>
+      </div>
+    </el-col>
+    <el-col :xs="12" :sm="12" :lg="6" class="card-panel-col">
+      <div class="card-panel" @click="handleSetLineChartData('messages')">
+        <div class="card-panel-icon-wrapper icon-message">
+          <svg-icon icon-class="message" class-name="card-panel-icon" />
+        </div>
+        <div class="card-panel-description">
+          <div class="card-panel-text">
+            Messages
+          </div>
+          <count-to :start-val="0" :end-val="81212" :duration="3000" class="card-panel-num" />
+        </div>
+      </div>
+    </el-col>
+    <el-col :xs="12" :sm="12" :lg="6" class="card-panel-col">
+      <div class="card-panel" @click="handleSetLineChartData('purchases')">
+        <div class="card-panel-icon-wrapper icon-money">
+          <svg-icon icon-class="money" class-name="card-panel-icon" />
+        </div>
+        <div class="card-panel-description">
+          <div class="card-panel-text">
+            Purchases
+          </div>
+          <count-to :start-val="0" :end-val="9280" :duration="3200" class="card-panel-num" />
+        </div>
+      </div>
+    </el-col>
+    <el-col :xs="12" :sm="12" :lg="6" class="card-panel-col">
+      <div class="card-panel" @click="handleSetLineChartData('shoppings')">
+        <div class="card-panel-icon-wrapper icon-shopping">
+          <svg-icon icon-class="shopping" class-name="card-panel-icon" />
+        </div>
+        <div class="card-panel-description">
+          <div class="card-panel-text">
+            Shoppings
+          </div>
+          <count-to :start-val="0" :end-val="13600" :duration="3600" class="card-panel-num" />
+        </div>
+      </div>
+    </el-col>
+  </el-row>
+</template>
+
+<script>
+import CountTo from 'vue-count-to'
+
+export default {
+  components: {
+    CountTo
+  },
+  methods: {
+    handleSetLineChartData(type) {
+      this.$emit('handleSetLineChartData', type)
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.panel-group {
+  margin-top: 18px;
+
+  .card-panel-col {
+    margin-bottom: 32px;
+  }
+
+  .card-panel {
+    height: 108px;
+    cursor: pointer;
+    font-size: 12px;
+    position: relative;
+    overflow: hidden;
+    color: #666;
+    background: #fff;
+    box-shadow: 4px 4px 40px rgba(0, 0, 0, .05);
+    border-color: rgba(0, 0, 0, .05);
+
+    &:hover {
+      .card-panel-icon-wrapper {
+        color: #fff;
+      }
+
+      .icon-people {
+        background: #40c9c6;
+      }
+
+      .icon-message {
+        background: #36a3f7;
+      }
+
+      .icon-money {
+        background: #f4516c;
+      }
+
+      .icon-shopping {
+        background: #34bfa3
+      }
+    }
+
+    .icon-people {
+      color: #40c9c6;
+    }
+
+    .icon-message {
+      color: #36a3f7;
+    }
+
+    .icon-money {
+      color: #f4516c;
+    }
+
+    .icon-shopping {
+      color: #34bfa3
+    }
+
+    .card-panel-icon-wrapper {
+      float: left;
+      margin: 14px 0 0 14px;
+      padding: 16px;
+      transition: all 0.38s ease-out;
+      border-radius: 6px;
+    }
+
+    .card-panel-icon {
+      float: left;
+      font-size: 48px;
+    }
+
+    .card-panel-description {
+      float: right;
+      font-weight: bold;
+      margin: 26px;
+      margin-left: 0px;
+
+      .card-panel-text {
+        line-height: 18px;
+        color: rgba(0, 0, 0, 0.45);
+        font-size: 16px;
+        margin-bottom: 12px;
+      }
+
+      .card-panel-num {
+        font-size: 20px;
+      }
+    }
+  }
+}
+
+@media (max-width:550px) {
+  .card-panel-description {
+    display: none;
+  }
+
+  .card-panel-icon-wrapper {
+    float: none !important;
+    width: 100%;
+    height: 100%;
+    margin: 0 !important;
+
+    .svg-icon {
+      display: block;
+      margin: 14px auto !important;
+      float: none !important;
+    }
+  }
+}
+</style>
diff --git a/frontend/src/views/dashboard/admin/components/PieChart.vue b/frontend/src/views/dashboard/admin/components/PieChart.vue
new file mode 100644
index 0000000..4d2ef32
--- /dev/null
+++ b/frontend/src/views/dashboard/admin/components/PieChart.vue
@@ -0,0 +1,79 @@
+<template>
+  <div :class="className" :style="{height:height,width:width}" />
+</template>
+
+<script>
+import echarts from 'echarts'
+require('echarts/theme/macarons') // echarts theme
+import resize from './mixins/resize'
+
+export default {
+  mixins: [resize],
+  props: {
+    className: {
+      type: String,
+      default: 'chart'
+    },
+    width: {
+      type: String,
+      default: '100%'
+    },
+    height: {
+      type: String,
+      default: '300px'
+    }
+  },
+  data() {
+    return {
+      chart: null
+    }
+  },
+  mounted() {
+    this.$nextTick(() => {
+      this.initChart()
+    })
+  },
+  beforeDestroy() {
+    if (!this.chart) {
+      return
+    }
+    this.chart.dispose()
+    this.chart = null
+  },
+  methods: {
+    initChart() {
+      this.chart = echarts.init(this.$el, 'macarons')
+
+      this.chart.setOption({
+        tooltip: {
+          trigger: 'item',
+          formatter: '{a} <br/>{b} : {c} ({d}%)'
+        },
+        legend: {
+          left: 'center',
+          bottom: '10',
+          data: ['Industries', 'Technology', 'Forex', 'Gold', 'Forecasts']
+        },
+        series: [
+          {
+            name: 'WEEKLY WRITE ARTICLES',
+            type: 'pie',
+            roseType: 'radius',
+            radius: [15, 95],
+            center: ['50%', '38%'],
+            data: [
+              { value: 320, name: 'Industries' },
+              { value: 240, name: 'Technology' },
+              { value: 149, name: 'Forex' },
+              { value: 100, name: 'Gold' },
+              { value: 59, name: 'Forecasts' }
+            ],
+            animationEasing: 'cubicInOut',
+            animationDuration: 2600
+          }
+        ]
+      })
+    }
+  }
+}
+</script>
diff --git a/frontend/src/views/dashboard/admin/components/RaddarChart.vue b/frontend/src/views/dashboard/admin/components/RaddarChart.vue
new file mode 100644
index 0000000..52c8f9f
--- /dev/null
+++ b/frontend/src/views/dashboard/admin/components/RaddarChart.vue
@@ -0,0 +1,116 @@
+<template>
+  <div :class="className" :style="{height:height,width:width}" />
+</template>
+
+<script>
+import echarts from 'echarts'
+require('echarts/theme/macarons') // echarts theme
+import resize from './mixins/resize'
+
+const animationDuration = 3000
+
+export default {
+  mixins: [resize],
+  props: {
+    className: {
+      type: String,
+      default: 'chart'
+    },
+    width: {
+      type: String,
+      default: '100%'
+    },
+    height: {
+      type: String,
+      default: '300px'
+    }
+  },
+  data() {
+    return {
+      chart: null
+    }
+  },
+  mounted() {
+    this.$nextTick(() => {
+      this.initChart()
+    })
+  },
+  beforeDestroy() {
+    if (!this.chart) {
+      return
+    }
+    this.chart.dispose()
+    this.chart = null
+  },
+  methods: {
+    initChart() {
+      this.chart = echarts.init(this.$el, 'macarons')
+
+      this.chart.setOption({
+        tooltip: {
+          trigger: 'axis',
+          axisPointer: { // 坐标轴指示器,坐标轴触发有效
+            type: 'shadow' // 默认为直线,可选为:'line' | 'shadow'
+          }
+        },
+        radar: {
+          radius: '66%',
+          center: ['50%', '42%'],
+          splitNumber: 8,
+          splitArea: {
+            areaStyle: {
+              color: 'rgba(127,95,132,.3)',
+              opacity: 1,
+              shadowBlur: 45,
+              shadowColor: 'rgba(0,0,0,.5)',
+              shadowOffsetX: 0,
+              shadowOffsetY: 15
+            }
+          },
+          indicator: [
+            { name: 'Sales', max: 10000 },
+            { name: 'Administration', max: 20000 },
+            { name: 'Information Technology', max: 20000 },
+            { name: 'Customer Support', max: 20000 },
+            { name: 'Development', max: 20000 },
+            { name: 'Marketing', max: 20000 }
+          ]
+        },
+        legend: {
+          left: 'center',
+          bottom: '10',
+          data: ['Allocated Budget', 'Expected Spending', 'Actual Spending']
+        },
+        series: [{
+          type: 'radar',
+          symbolSize: 0,
+          areaStyle: {
+            normal: {
+              shadowBlur: 13,
+              shadowColor: 'rgba(0,0,0,.2)',
+              shadowOffsetX: 0,
+              shadowOffsetY: 10,
+              opacity: 1
+            }
+          },
+          data: [
+            {
+              value: [5000, 7000, 12000, 11000, 15000, 14000],
+              name: 'Allocated Budget'
+            },
+            {
+              value: [4000, 9000, 15000, 15000, 13000, 11000],
+              name: 'Expected Spending'
+            },
+            {
+              value: [5500, 11000, 12000, 15000, 12000, 12000],
+              name: 'Actual Spending'
+            }
+          ],
+          animationDuration: animationDuration
+        }]
+      })
+    }
+  }
+}
+</script>
diff --git a/frontend/src/views/dashboard/admin/components/TodoList/Todo.vue b/frontend/src/views/dashboard/admin/components/TodoList/Todo.vue
new file mode 100644
index 0000000..c4b3cae
--- /dev/null
+++ b/frontend/src/views/dashboard/admin/components/TodoList/Todo.vue
@@ -0,0 +1,81 @@
+<template>
+  <li :class="{ completed: todo.done, editing: editing }" class="todo">
+    <div class="view">
+      <input
+        :checked="todo.done"
+        class="toggle"
+        type="checkbox"
+        @change="toggleTodo( todo)"
+      >
+      <label @dblclick="editing = true" v-text="todo.text" />
+      <button class="destroy" @click="deleteTodo( todo )" />
+    </div>
+    <input
+      v-show="editing"
+      v-focus="editing"
+      :value="todo.text"
+      class="edit"
+      @keyup.enter="doneEdit"
+      @keyup.esc="cancelEdit"
+      @blur="doneEdit"
+    >
+  </li>
+</template>
+
+<script>
+export default {
+  name: 'Todo',
+  directives: {
+    focus(el, { value }, { context }) {
+      if (value) {
+        context.$nextTick(() => {
+          el.focus()
+        })
+      }
+    }
+  },
+  props: {
+    todo: {
+      type: Object,
+      default: function() {
+        return {}
+      }
+    }
+  },
+  data() {
+    return {
+      editing: false
+    }
+  },
+  methods: {
+    deleteTodo(todo) {
+      this.$emit('deleteTodo', todo)
+    },
+    editTodo({ todo, value }) {
+      this.$emit('editTodo', { todo, value })
+    },
+    toggleTodo(todo) {
+      this.$emit('toggleTodo', todo)
+    },
+    doneEdit(e) {
+      const value = e.target.value.trim()
+      const { todo } = this
+      if (!value) {
+        this.deleteTodo({
+          todo
+        })
+      } else if (this.editing) {
+        this.editTodo({
+          todo,
+          value
+        })
+        this.editing = false
+      }
+    },
+    cancelEdit(e) {
+      e.target.value = this.todo.text
+      this.editing = false
+    }
+  }
+}
+</script>
diff --git a/frontend/src/views/dashboard/admin/components/TodoList/index.scss b/frontend/src/views/dashboard/admin/components/TodoList/index.scss
new file mode 100644
index 0000000..74ce0d5
--- /dev/null
+++ b/frontend/src/views/dashboard/admin/components/TodoList/index.scss
@@ -0,0 +1,320 @@
+.todoapp {
+  font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif;
+  line-height: 1.4em;
+  color: #4d4d4d;
+  min-width: 230px;
+  max-width: 550px;
+  margin: 0 auto ;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+  font-weight: 300;
+  background: #fff;
+  z-index: 1;
+  position: relative;
+  button {
+    margin: 0;
+    padding: 0;
+    border: 0;
+    background: none;
+    font-size: 100%;
+    vertical-align: baseline;
+    font-family: inherit;
+    font-weight: inherit;
+    color: inherit;
+    -webkit-appearance: none;
+    appearance: none;
+    -webkit-font-smoothing: antialiased;
+    -moz-osx-font-smoothing: grayscale;
+  }
+  :focus {
+    outline: 0;
+  }
+  .hidden {
+    display: none;
+  }
+  .todoapp {
+    background: #fff;
+    margin: 130px 0 40px 0;
+    position: relative;
+    box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), 0 25px 50px 0 rgba(0, 0, 0, 0.1);
+  }
+  .todoapp input::-webkit-input-placeholder {
+    font-style: italic;
+    font-weight: 300;
+    color: #e6e6e6;
+  }
+  .todoapp input::-moz-placeholder {
+    font-style: italic;
+    font-weight: 300;
+    color: #e6e6e6;
+  }
+  .todoapp input::input-placeholder {
+    font-style: italic;
+    font-weight: 300;
+    color: #e6e6e6;
+  }
+  .todoapp h1 {
+    position: absolute;
+    top: -155px;
+    width: 100%;
+    font-size: 100px;
+    font-weight: 100;
+    text-align: center;
+    color: rgba(175, 47, 47, 0.15);
+    -webkit-text-rendering: optimizeLegibility;
+    -moz-text-rendering: optimizeLegibility;
+    text-rendering: optimizeLegibility;
+  }
+  .new-todo,
+  .edit {
+    position: relative;
+    margin: 0;
+    width: 100%;
+    font-size: 18px;
+    font-family: inherit;
+    font-weight: inherit;
+    line-height: 1.4em;
+    border: 0;
+    color: inherit;
+    padding: 6px;
+    border: 1px solid #999;
+    box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2);
+    box-sizing: border-box;
+    -webkit-font-smoothing: antialiased;
+    -moz-osx-font-smoothing: grayscale;
+  }
+  .new-todo {
+    padding: 10px 16px 16px 60px;
+    border: none;
+    background: rgba(0, 0, 0, 0.003);
+    box-shadow: inset 0 -2px 1px rgba(0, 0, 0, 0.03);
+  }
+  .main {
+    position: relative;
+    z-index: 2;
+    border-top: 1px solid #e6e6e6;
+  }
+  .toggle-all {
+    text-align: center;
+    border: none;
+    /* Mobile Safari */
+    opacity: 0;
+    position: absolute;
+  }
+  .toggle-all+label {
+    width: 60px;
+    height: 34px;
+    font-size: 0;
+    position: absolute;
+    top: -52px;
+    left: -13px;
+    -webkit-transform: rotate(90deg);
+    transform: rotate(90deg);
+  }
+  .toggle-all+label:before {
+    content: '❯';
+    font-size: 22px;
+    color: #e6e6e6;
+    padding: 10px 27px 10px 27px;
+  }
+  .toggle-all:checked+label:before {
+    color: #737373;
+  }
+  .todo-list {
+    margin: 0;
+    padding: 0;
+    list-style: none;
+  }
+  .todo-list li {
+    position: relative;
+    font-size: 24px;
+    border-bottom: 1px solid #ededed;
+  }
+  .todo-list li:last-child {
+    border-bottom: none;
+  }
+  .todo-list li.editing {
+    border-bottom: none;
+    padding: 0;
+  }
+  .todo-list li.editing .edit {
+    display: block;
+    width: 506px;
+    padding: 12px 16px;
+    margin: 0 0 0 43px;
+  }
+  .todo-list li.editing .view {
+    display: none;
+  }
+  .todo-list li .toggle {
+    text-align: center;
+    width: 40px;
+    /* auto, since non-WebKit browsers doesn't support input styling */
+    height: auto;
+    position: absolute;
+    top: 0;
+    bottom: 0;
+    margin: auto 0;
+    border: none;
+    /* Mobile Safari */
+    -webkit-appearance: none;
+    appearance: none;
+  }
+  .todo-list li .toggle {
+    opacity: 0;
+  }
+  .todo-list li .toggle+label {
+    /*
+    Firefox requires `#` to be escaped - https://bugzilla.mozilla.org/show_bug.cgi?id=922433
+    IE and Edge requires *everything* to be escaped to render, so we do that instead of just the `#` - https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/7157459/
+  */
+    background-image: url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2240%22%20height%3D%2240%22%20viewBox%3D%22-10%20-18%20100%20135%22%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2250%22%20fill%3D%22none%22%20stroke%3D%22%23ededed%22%20stroke-width%3D%223%22/%3E%3C/svg%3E');
+    background-repeat: no-repeat;
+    background-position: center left;
+    background-size: 36px;
+  }
+  .todo-list li .toggle:checked+label {
+    background-size: 36px;
+    background-image: url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2240%22%20height%3D%2240%22%20viewBox%3D%22-10%20-18%20100%20135%22%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2250%22%20fill%3D%22none%22%20stroke%3D%22%23bddad5%22%20stroke-width%3D%223%22/%3E%3Cpath%20fill%3D%22%235dc2af%22%20d%3D%22M72%2025L42%2071%2027%2056l-4%204%2020%2020%2034-52z%22/%3E%3C/svg%3E');
+  }
+  .todo-list li label {
+    word-break: break-all;
+    padding: 15px 15px 15px 50px;
+    display: block;
+    line-height: 1.0;
+        font-size: 14px;
+    transition: color 0.4s;
+  }
+  .todo-list li.completed label {
+    color: #d9d9d9;
+    text-decoration: line-through;
+  }
+  .todo-list li .destroy {
+    display: none;
+    position: absolute;
+    top: 0;
+    right: 10px;
+    bottom: 0;
+    width: 40px;
+    height: 40px;
+    margin: auto 0;
+    font-size: 30px;
+    color: #cc9a9a;
+    transition: color 0.2s ease-out;
+    cursor: pointer;
+  }
+  .todo-list li .destroy:hover {
+    color: #af5b5e;
+  }
+  .todo-list li .destroy:after {
+    content: '×';
+  }
+  .todo-list li:hover .destroy {
+    display: block;
+  }
+  .todo-list li .edit {
+    display: none;
+  }
+  .todo-list li.editing:last-child {
+    margin-bottom: -1px;
+  }
+  .footer {
+    color: #777;
+    position: relative;
+    padding: 10px 15px;
+    height: 40px;
+    text-align: center;
+    border-top: 1px solid #e6e6e6;
+  }
+  .footer:before {
+    content: '';
+    position: absolute;
+    right: 0;
+    bottom: 0;
+    left: 0;
+    height: 40px;
+    overflow: hidden;
+    box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2), 0 8px 0 -3px #f6f6f6, 0 9px 1px -3px rgba(0, 0, 0, 0.2), 0 16px 0 -6px #f6f6f6, 0 17px 2px -6px rgba(0, 0, 0, 0.2);
+  }
+  .todo-count {
+    float: left;
+    text-align: left;
+  }
+  .todo-count strong {
+    font-weight: 300;
+  }
+  .filters {
+    margin: 0;
+    padding: 0;
+    position: relative;
+    z-index: 1;
+    list-style: none;
+  }
+  .filters li {
+    display: inline;
+  }
+  .filters li a {
+    color: inherit;
+    font-size: 12px;
+    padding: 3px 7px;
+    text-decoration: none;
+    border: 1px solid transparent;
+    border-radius: 3px;
+  }
+  .filters li a:hover {
+    border-color: rgba(175, 47, 47, 0.1);
+  }
+  .filters li a.selected {
+    border-color: rgba(175, 47, 47, 0.2);
+  }
+  .clear-completed,
+  html .clear-completed:active {
+    float: right;
+    position: relative;
+    line-height: 20px;
+    text-decoration: none;
+    cursor: pointer;
+  }
+  .clear-completed:hover {
+    text-decoration: underline;
+  }
+  .info {
+    margin: 65px auto 0;
+    color: #bfbfbf;
+    font-size: 10px;
+    text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
+    text-align: center;
+  }
+  .info p {
+    line-height: 1;
+  }
+  .info a {
+    color: inherit;
+    text-decoration: none;
+    font-weight: 400;
+  }
+  .info a:hover {
+    text-decoration: underline;
+  }
+  /*
+  Hack to remove background from Mobile Safari.
+  Can't use it globally since it destroys checkboxes in Firefox
+*/
+  @media screen and (-webkit-min-device-pixel-ratio:0) {
+    .toggle-all,
+    .todo-list li .toggle {
+      background: none;
+    }
+    .todo-list li .toggle {
+      height: 40px;
+    }
+  }
+  @media (max-width: 430px) {
+    .footer {
+      height: 50px;
+    }
+    .filters {
+      bottom: 10px;
+    }
+  }
+}
diff --git a/frontend/src/views/dashboard/admin/components/TodoList/index.vue b/frontend/src/views/dashboard/admin/components/TodoList/index.vue
new file mode 100644
index 0000000..8000d41
--- /dev/null
+++ b/frontend/src/views/dashboard/admin/components/TodoList/index.vue
@@ -0,0 +1,127 @@
+<template>
+  <section class="todoapp">
+    <!-- header -->
+    <header class="header">
+      <input class="new-todo" autocomplete="off" placeholder="Todo List" @keyup.enter="addTodo">
+    </header>
+    <!-- main section -->
+    <section v-show="todos.length" class="main">
+      <input id="toggle-all" :checked="allChecked" class="toggle-all" type="checkbox" @change="toggleAll({ done: !allChecked })">
+      <label for="toggle-all" />
+      <ul class="todo-list">
+        <todo
+          v-for="(todo, index) in filteredTodos"
+          :key="index"
+          :todo="todo"
+          @toggleTodo="toggleTodo"
+          @editTodo="editTodo"
+          @deleteTodo="deleteTodo"
+        />
+      </ul>
+    </section>
+    <!-- footer -->
+    <footer v-show="todos.length" class="footer">
+      <span class="todo-count">
+        <strong>{{ remaining }}</strong>
+        {{ remaining | pluralize('item') }} left
+      </span>
+      <ul class="filters">
+        <li v-for="(val, key) in filters" :key="key">
+          <a :class="{ selected: visibility === key }" @click.prevent="visibility = key">{{ key | capitalize }}</a>
+        </li>
+      </ul>
+      <!-- <button class="clear-completed" v-show="todos.length > remaining" @click="clearCompleted">
+        Clear completed
+      </button> -->
+    </footer>
+  </section>
+</template>
+
+<script>
+import Todo from './Todo.vue'
+
+const STORAGE_KEY = 'todos'
+const filters = {
+  all: todos => todos,
+  active: todos => todos.filter(todo => !todo.done),
+  completed: todos => todos.filter(todo => todo.done)
+}
+const defalutList = [
+  { text: 'star this repository', done: false },
+  { text: 'fork this repository', done: false },
+  { text: 'follow author', done: false },
+  { text: 'vue-element-admin', done: true },
+  { text: 'vue', done: true },
+  { text: 'element-ui', done: true },
+  { text: 'axios', done: true },
+  { text: 'webpack', done: true }
+]
+export default {
+  components: { Todo },
+  filters: {
+    pluralize: (n, w) => n === 1 ? w : w + 's',
+    capitalize: s => s.charAt(0).toUpperCase() + s.slice(1)
+  },
+  data() {
+    return {
+      visibility: 'all',
+      filters,
+      // todos: JSON.parse(window.localStorage.getItem(STORAGE_KEY)) || defalutList
+      todos: defalutList
+    }
+  },
+  computed: {
+    allChecked() {
+      return this.todos.every(todo => todo.done)
+    },
+    filteredTodos() {
+      return filters[this.visibility](this.todos)
+    },
+    remaining() {
+      return this.todos.filter(todo => !todo.done).length
+    }
+  },
+  methods: {
+    setLocalStorage() {
+      window.localStorage.setItem(STORAGE_KEY, JSON.stringify(this.todos))
+    },
+    addTodo(e) {
+      const text = e.target.value
+      if (text.trim()) {
+        this.todos.push({
+          text,
+          done: false
+        })
+        this.setLocalStorage()
+      }
+      e.target.value = ''
+    },
+    toggleTodo(val) {
+      val.done = !val.done
+      this.setLocalStorage()
+    },
+    deleteTodo(todo) {
+      this.todos.splice(this.todos.indexOf(todo), 1)
+      this.setLocalStorage()
+    },
+    editTodo({ todo, value }) {
+      todo.text = value
+      this.setLocalStorage()
+    },
+    clearCompleted() {
+      this.todos = this.todos.filter(todo => !todo.done)
+      this.setLocalStorage()
+    },
+    toggleAll({ done }) {
+      this.todos.forEach(todo => {
+        todo.done = done
+        this.setLocalStorage()
+      })
+    }
+  }
+}
+</script>
+
+<style lang="scss">
+  @import './index.scss';
+</style>
diff --git a/frontend/src/views/dashboard/admin/components/TransactionTable.vue b/frontend/src/views/dashboard/admin/components/TransactionTable.vue
new file mode 100644
index 0000000..d07b0ed
--- /dev/null
+++ b/frontend/src/views/dashboard/admin/components/TransactionTable.vue
@@ -0,0 +1,55 @@
+<template>
+  <el-table :data="list" style="width: 100%;padding-top: 15px;">
+    <el-table-column label="Order_No" min-width="200">
+      <template slot-scope="scope">
+        {{ scope.row.order_no | orderNoFilter }}
+      </template>
+    </el-table-column>
+    <el-table-column label="Price" width="195" align="center">
+      <template slot-scope="scope">
+        ¥{{ scope.row.price | toThousandFilter }}
+      </template>
+    </el-table-column>
+    <el-table-column label="Status" width="100" align="center">
+      <template slot-scope="{row}">
+        <el-tag :type="row.status | statusFilter">
+          {{ row.status }}
+        </el-tag>
+      </template>
+    </el-table-column>
+  </el-table>
+</template>
+
+<script>
+import { transactionList } from '@/api/remote-search'
+
+export default {
+  filters: {
+    statusFilter(status) {
+      const statusMap = {
+        success: 'success',
+        pending: 'danger'
+      }
+      return statusMap[status]
+    },
+    orderNoFilter(str) {
+      return str.substring(0, 30)
+    }
+  },
+  data() {
+    return {
+      list: null
+    }
+  },
+  created() {
+    this.fetchData()
+  },
+  methods: {
+    fetchData() {
+      transactionList().then(response => {
+        this.list = response.data.items.slice(0, 8)
+      })
+    }
+  }
+}
+</script>
diff --git a/frontend/src/views/dashboard/admin/components/mixins/resize.js b/frontend/src/views/dashboard/admin/components/mixins/resize.js
new file mode 100644
index 0000000..234953b
--- /dev/null
+++ b/frontend/src/views/dashboard/admin/components/mixins/resize.js
@@ -0,0 +1,55 @@
+import { debounce } from '@/utils'
+
+export default {
+  data() {
+    return {
+      $_sidebarElm: null,
+      $_resizeHandler: null
+    }
+  },
+  mounted() {
+    this.$_resizeHandler = debounce(() => {
+      if (this.chart) {
+        this.chart.resize()
+      }
+    }, 100)
+    this.$_initResizeEvent()
+    this.$_initSidebarResizeEvent()
+  },
+  beforeDestroy() {
+    this.$_destroyResizeEvent()
+    this.$_destroySidebarResizeEvent()
+  },
+  // to fixed bug when cached by keep-alive
+  // https://github.com/PanJiaChen/vue-element-admin/issues/2116
+  activated() {
+    this.$_initResizeEvent()
+    this.$_initSidebarResizeEvent()
+  },
+  deactivated() {
+    this.$_destroyResizeEvent()
+    this.$_destroySidebarResizeEvent()
+  },
+  methods: {
+    // use $_ for mixins properties
+    // https://vuejs.org/v2/style-guide/index.html#Private-property-names-essential
+    $_initResizeEvent() {
+      window.addEventListener('resize', this.$_resizeHandler)
+    },
+    $_destroyResizeEvent() {
+      window.removeEventListener('resize', this.$_resizeHandler)
+    },
+    $_sidebarResizeHandler(e) {
+      if (e.propertyName === 'width') {
+        this.$_resizeHandler()
+      }
+    },
+    $_initSidebarResizeEvent() {
+      this.$_sidebarElm = document.getElementsByClassName('sidebar-container')[0]
+      this.$_sidebarElm && this.$_sidebarElm.addEventListener('transitionend', this.$_sidebarResizeHandler)
+    },
+    $_destroySidebarResizeEvent() {
+      this.$_sidebarElm && this.$_sidebarElm.removeEventListener('transitionend', this.$_sidebarResizeHandler)
+    }
+  }
+}
diff --git a/frontend/src/views/dashboard/admin/index.vue b/frontend/src/views/dashboard/admin/index.vue
new file mode 100644
index 0000000..8cb557b
--- /dev/null
+++ b/frontend/src/views/dashboard/admin/index.vue
@@ -0,0 +1,124 @@
+<template>
+  <div class="dashboard-editor-container">
+    <github-corner class="github-corner" />
+
+    <panel-group @handleSetLineChartData="handleSetLineChartData" />
+
+    <el-row style="background:#fff;padding:16px 16px 0;margin-bottom:32px;">
+      <line-chart :chart-data="lineChartData" />
+    </el-row>
+
+    <el-row :gutter="32">
+      <el-col :xs="24" :sm="24" :lg="8">
+        <div class="chart-wrapper">
+          <raddar-chart />
+        </div>
+      </el-col>
+      <el-col :xs="24" :sm="24" :lg="8">
+        <div class="chart-wrapper">
+          <pie-chart />
+        </div>
+      </el-col>
+      <el-col :xs="24" :sm="24" :lg="8">
+        <div class="chart-wrapper">
+          <bar-chart />
+        </div>
+      </el-col>
+    </el-row>
+
+    <el-row :gutter="8">
+      <el-col :xs="{span: 24}" :sm="{span: 24}" :md="{span: 24}" :lg="{span: 12}" :xl="{span: 12}" style="padding-right:8px;margin-bottom:30px;">
+        <transaction-table />
+      </el-col>
+      <el-col :xs="{span: 24}" :sm="{span: 12}" :md="{span: 12}" :lg="{span: 6}" :xl="{span: 6}" style="margin-bottom:30px;">
+        <todo-list />
+      </el-col>
+      <el-col :xs="{span: 24}" :sm="{span: 12}" :md="{span: 12}" :lg="{span: 6}" :xl="{span: 6}" style="margin-bottom:30px;">
+        <box-card />
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script>
+import GithubCorner from '@/components/GithubCorner'
+import PanelGroup from './components/PanelGroup'
+import LineChart from './components/LineChart'
+import RaddarChart from './components/RaddarChart'
+import PieChart from './components/PieChart'
+import BarChart from './components/BarChart'
+import TransactionTable from './components/TransactionTable'
+import TodoList from './components/TodoList'
+import BoxCard from './components/BoxCard'
+
+const lineChartData = {
+  newVisitis: {
+    expectedData: [100, 120, 161, 134, 105, 160, 165],
+    actualData: [120, 82, 91, 154, 162, 140, 145]
+  },
+  messages: {
+    expectedData: [200, 192, 120, 144, 160, 130, 140],
+    actualData: [180, 160, 151, 106, 145, 150, 130]
+  },
+  purchases: {
+    expectedData: [80, 100, 121, 104, 105, 90, 100],
+    actualData: [120, 90, 100, 138, 142, 130, 130]
+  },
+  shoppings: {
+    expectedData: [130, 140, 141, 142, 145, 150, 160],
+    actualData: [120, 82, 91, 154, 162, 140, 130]
+  }
+}
+
+export default {
+  name: 'DashboardAdmin',
+  components: {
+    GithubCorner,
+    PanelGroup,
+    LineChart,
+    RaddarChart,
+    PieChart,
+    BarChart,
+    TransactionTable,
+    TodoList,
+    BoxCard
+  },
+  data() {
+    return {
+      lineChartData: lineChartData.newVisitis
+    }
+  },
+  methods: {
+    handleSetLineChartData(type) {
+      this.lineChartData = lineChartData[type]
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.dashboard-editor-container {
+  padding: 32px;
+  background-color: rgb(240, 242, 245);
+  position: relative;
+
+  .github-corner {
+    position: absolute;
+    top: 0px;
+    border: 0;
+    right: 0;
+  }
+
+  .chart-wrapper {
+    background: #fff;
+    padding: 16px 16px 0;
+    margin-bottom: 32px;
+  }
+}
+
+@media (max-width:1024px) {
+  .chart-wrapper {
+    padding: 8px;
+  }
+}
+</style>
diff --git a/frontend/src/views/dashboard/editor/index.vue b/frontend/src/views/dashboard/editor/index.vue
new file mode 100644
index 0000000..9723bcc
--- /dev/null
+++ b/frontend/src/views/dashboard/editor/index.vue
@@ -0,0 +1,74 @@
+<template>
+  <div class="dashboard-editor-container">
+    <div class=" clearfix">
+      <pan-thumb :image="avatar" style="float: left">
+        Your roles:
+        <span v-for="item in roles" :key="item" class="pan-info-roles">{{ item }}</span>
+      </pan-thumb>
+      <github-corner style="position: absolute; top: 0px; border: 0; right: 0;" />
+      <div class="info-container">
+        <span class="display_name">{{ name }}</span>
+        <span style="font-size:20px;padding-top:20px;display:inline-block;">Editor's Dashboard</span>
+      </div>
+    </div>
+    <div>
+      <img :src="emptyGif" class="emptyGif">
+    </div>
+  </div>
+</template>
+
+<script>
+import { mapGetters } from 'vuex'
+import PanThumb from '@/components/PanThumb'
+import GithubCorner from '@/components/GithubCorner'
+
+export default {
+  name: 'DashboardEditor',
+  components: { PanThumb, GithubCorner },
+  data() {
+    return {
+      emptyGif: 'https://wpimg.wallstcn.com/0e03b7da-db9e-4819-ba10-9016ddfdaed3'
+    }
+  },
+  computed: {
+    ...mapGetters([
+      'name',
+      'avatar',
+      'roles'
+    ])
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+  .emptyGif {
+    display: block;
+    width: 45%;
+    margin: 0 auto;
+  }
+
+  .dashboard-editor-container {
+    background-color: #e3e3e3;
+    min-height: 100vh;
+    padding: 50px 60px 0px;
+    .pan-info-roles {
+      font-size: 12px;
+      font-weight: 700;
+      color: #333;
+      display: block;
+    }
+    .info-container {
+      position: relative;
+      margin-left: 190px;
+      height: 150px;
+      line-height: 200px;
+      .display_name {
+        font-size: 48px;
+        line-height: 48px;
+        color: #212121;
+        position: absolute;
+        top: 25px;
+      }
+    }
+  }
+</style>
diff --git a/frontend/src/views/dashboard/index.vue b/frontend/src/views/dashboard/index.vue
new file mode 100644
index 0000000..1720ea8
--- /dev/null
+++ b/frontend/src/views/dashboard/index.vue
@@ -0,0 +1,31 @@
+<template>
+  <div class="dashboard-container">
+    <component :is="currentRole" />
+  </div>
+</template>
+
+<script>
+import { mapGetters } from 'vuex'
+import adminDashboard from './admin'
+import editorDashboard from './editor'
+
+export default {
+  name: 'Dashboard',
+  components: { adminDashboard, editorDashboard },
+  data() {
+    return {
+      currentRole: 'adminDashboard'
+    }
+  },
+  computed: {
+    ...mapGetters([
+      'roles'
+    ])
+  },
+  created() {
+    if (!this.roles.includes('admin')) {
+      this.currentRole = 'editorDashboard'
+    }
+  }
+}
+</script>
diff --git a/frontend/src/views/documentation/index.vue b/frontend/src/views/documentation/index.vue
new file mode 100644
index 0000000..d3f7746
--- /dev/null
+++ b/frontend/src/views/documentation/index.vue
@@ -0,0 +1,56 @@
+<template>
+  <div class="app-container documentation-container">
+    <a class="document-btn" target="_blank" href="https://panjiachen.github.io/vue-element-admin-site/">Documentation</a>
+    <a class="document-btn" target="_blank" href="https://github.com/PanJiaChen/vue-element-admin/">Github Repository</a>
+    <a class="document-btn" target="_blank" href="https://panjiachen.gitee.io/vue-element-admin-site/zh/">国内文档</a>
+    <dropdown-menu class="document-btn" :items="articleList" title="系列文章" />
+    <a class="document-btn" target="_blank" href="https://panjiachen.github.io/vue-element-admin-site/zh/job/">内推招聘</a>
+  </div>
+</template>
+
+<script>
+import DropdownMenu from '@/components/Share/DropdownMenu'
+
+export default {
+  name: 'Documentation',
+  components: { DropdownMenu },
+  data() {
+    return {
+      articleList: [
+        { title: '基础篇', href: 'https://juejin.im/post/59097cd7a22b9d0065fb61d2' },
+        { title: '登录权限篇', href: 'https://juejin.im/post/591aa14f570c35006961acac' },
+        { title: '实战篇', href: 'https://juejin.im/post/593121aa0ce4630057f70d35' },
+        { title: 'vue-admin-template 篇', href: 'https://juejin.im/post/595b4d776fb9a06bbe7dba56' },
+        { title: 'v4.0 篇', href: 'https://juejin.im/post/5c92ff94f265da6128275a85' },
+        { title: '自行封装 component', href: 'https://segmentfault.com/a/1190000009090836' },
+        { title: '优雅的使用 icon', href: 'https://juejin.im/post/59bb864b5188257e7a427c09' },
+        { title: 'webpack4(上)', href: 'https://juejin.im/post/59bb864b5188257e7a427c09' },
+        { title: 'webpack4(下)', href: 'https://juejin.im/post/5b5d6d6f6fb9a04fea58aabc' }
+      ]
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.documentation-container {
+  margin: 50px;
+  display: flex;
+  flex-wrap: wrap;
+  justify-content: space-evenly;
+
+  .document-btn {
+    flex-shrink: 0;
+    display: block;
+    cursor: pointer;
+    background: black;
+    color: white;
+    height: 60px;
+    width: 200px;
+    margin-bottom: 16px;
+    line-height: 60px;
+    font-size: 20px;
+    text-align: center;
+  }
+}
+</style>
diff --git a/frontend/src/views/error-log/components/ErrorTestA.vue b/frontend/src/views/error-log/components/ErrorTestA.vue
new file mode 100644
index 0000000..52654e0
--- /dev/null
+++ b/frontend/src/views/error-log/components/ErrorTestA.vue
@@ -0,0 +1,13 @@
+<template>
+  <div>
+    <!--error code-->
+    {{ a.a }}
+    <!--error code-->
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'ErrorTestA'
+}
+</script>
diff --git a/frontend/src/views/error-log/components/ErrorTestB.vue b/frontend/src/views/error-log/components/ErrorTestB.vue
new file mode 100644
index 0000000..d796bee
--- /dev/null
+++ b/frontend/src/views/error-log/components/ErrorTestB.vue
@@ -0,0 +1,11 @@
+<template>
+  <div />
+</template>
+
+<script>
+export default {
+  created() {
+    this.b = b // eslint-disable-line
+  }
+}
+</script>
diff --git a/frontend/src/views/error-log/index.vue b/frontend/src/views/error-log/index.vue
new file mode 100644
index 0000000..e999c85
--- /dev/null
+++ b/frontend/src/views/error-log/index.vue
@@ -0,0 +1,32 @@
+<template>
+  <div class="errPage-container">
+    <ErrorA />
+    <ErrorB />
+    <h3>Please click the bug icon in the upper right corner</h3>
+    <aside>
+      Now the management system are basically the form of the spa, it enhances the user experience, but it also increases the possibility of page problems, a small negligence may lead to the entire page deadlock. Fortunately Vue provides a way to catch handling exceptions, where you can handle errors or report exceptions.
+      <a target="_blank" class="link-type" href="https://panjiachen.github.io/vue-element-admin-site/guide/advanced/error.html">
+        Document introduction
+      </a>
+    </aside>
+    <a href="#">
+      <img src="https://wpimg.wallstcn.com/360e4842-4db5-42d0-b078-f9a84a825546.gif">
+    </a>
+  </div>
+</template>
+
+<script>
+import ErrorA from './components/ErrorTestA'
+import ErrorB from './components/ErrorTestB'
+
+export default {
+  name: 'ErrorLog',
+  components: { ErrorA, ErrorB }
+}
+</script>
+
+<style scoped>
+  .errPage-container {
+    padding: 30px;
+  }
+</style>
diff --git a/frontend/src/views/error-page/401.vue b/frontend/src/views/error-page/401.vue
new file mode 100644
index 0000000..a52ed23
--- /dev/null
+++ b/frontend/src/views/error-page/401.vue
@@ -0,0 +1,99 @@
+<template>
+  <div class="errPage-container">
+    <el-button icon="el-icon-arrow-left" class="pan-back-btn" @click="back">
+      返回
+    </el-button>
+    <el-row>
+      <el-col :span="12">
+        <h1 class="text-jumbo text-ginormous">
+          Oops!
+        </h1>
+        gif来源<a href="https://zh.airbnb.com/" target="_blank">airbnb</a> 页面
+        <h2>你没有权限去该页面</h2>
+        <h6>如有不满请联系你领导</h6>
+        <ul class="list-unstyled">
+          <li>或者你可以去:</li>
+          <li class="link-type">
+            <router-link to="/dashboard">
+              回首页
+            </router-link>
+          </li>
+          <li class="link-type">
+            <a href="https://www.taobao.com/">随便看看</a>
+          </li>
+          <li><a href="#" @click.prevent="dialogVisible=true">点我看图</a></li>
+        </ul>
+      </el-col>
+      <el-col :span="12">
+        <img :src="errGif" width="313" height="428" alt="Girl has dropped her ice cream.">
+      </el-col>
+    </el-row>
+    <el-dialog :visible.sync="dialogVisible" title="随便看">
+      <img :src="ewizardClap" class="pan-img">
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import errGif from '@/assets/401_images/401.gif'
+
+export default {
+  name: 'Page401',
+  data() {
+    return {
+      errGif: errGif + '?' + +new Date(),
+      ewizardClap: 'https://wpimg.wallstcn.com/007ef517-bafd-4066-aae4-6883632d9646',
+      dialogVisible: false
+    }
+  },
+  methods: {
+    back() {
+      if (this.$route.query.noGoBack) {
+        this.$router.push({ path: '/dashboard' })
+      } else {
+        this.$router.go(-1)
+      }
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+  .errPage-container {
+    width: 800px;
+    max-width: 100%;
+    margin: 100px auto;
+    .pan-back-btn {
+      background: #008489;
+      color: #fff;
+      border: none!important;
+    }
+    .pan-gif {
+      margin: 0 auto;
+      display: block;
+    }
+    .pan-img {
+      display: block;
+      margin: 0 auto;
+      width: 100%;
+    }
+    .text-jumbo {
+      font-size: 60px;
+      font-weight: 700;
+      color: #484848;
+    }
+    .list-unstyled {
+      font-size: 14px;
+      li {
+        padding-bottom: 5px;
+      }
+      a {
+        color: #008489;
+        text-decoration: none;
+        &:hover {
+          text-decoration: underline;
+        }
+      }
+    }
+  }
+</style>
diff --git a/frontend/src/views/error-page/404.vue b/frontend/src/views/error-page/404.vue
new file mode 100644
index 0000000..1791f55
--- /dev/null
+++ b/frontend/src/views/error-page/404.vue
@@ -0,0 +1,228 @@
+<template>
+  <div class="wscn-http404-container">
+    <div class="wscn-http404">
+      <div class="pic-404">
+        <img class="pic-404__parent" src="@/assets/404_images/404.png" alt="404">
+        <img class="pic-404__child left" src="@/assets/404_images/404_cloud.png" alt="404">
+        <img class="pic-404__child mid" src="@/assets/404_images/404_cloud.png" alt="404">
+        <img class="pic-404__child right" src="@/assets/404_images/404_cloud.png" alt="404">
+      </div>
+      <div class="bullshit">
+        <div class="bullshit__oops">OOPS!</div>
+        <div class="bullshit__info">All rights reserved
+          <a style="color:#20a0ff" href="https://wallstreetcn.com" target="_blank">wallstreetcn</a>
+        </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>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+
+export default {
+  name: 'Page404',
+  computed: {
+    message() {
+      return 'The webmaster said that you can not enter this page...'
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.wscn-http404-container{
+  transform: translate(-50%,-50%);
+  position: absolute;
+  top: 40%;
+  left: 50%;
+}
+.wscn-http404 {
+  position: relative;
+  width: 1200px;
+  padding: 0 50px;
+  overflow: hidden;
+  .pic-404 {
+    position: relative;
+    float: left;
+    width: 600px;
+    overflow: hidden;
+    &__parent {
+      width: 100%;
+    }
+    &__child {
+      position: absolute;
+      &.left {
+        width: 80px;
+        top: 17px;
+        left: 220px;
+        opacity: 0;
+        animation-name: cloudLeft;
+        animation-duration: 2s;
+        animation-timing-function: linear;
+        animation-fill-mode: forwards;
+        animation-delay: 1s;
+      }
+      &.mid {
+        width: 46px;
+        top: 10px;
+        left: 420px;
+        opacity: 0;
+        animation-name: cloudMid;
+        animation-duration: 2s;
+        animation-timing-function: linear;
+        animation-fill-mode: forwards;
+        animation-delay: 1.2s;
+      }
+      &.right {
+        width: 62px;
+        top: 100px;
+        left: 500px;
+        opacity: 0;
+        animation-name: cloudRight;
+        animation-duration: 2s;
+        animation-timing-function: linear;
+        animation-fill-mode: forwards;
+        animation-delay: 1s;
+      }
+      @keyframes cloudLeft {
+        0% {
+          top: 17px;
+          left: 220px;
+          opacity: 0;
+        }
+        20% {
+          top: 33px;
+          left: 188px;
+          opacity: 1;
+        }
+        80% {
+          top: 81px;
+          left: 92px;
+          opacity: 1;
+        }
+        100% {
+          top: 97px;
+          left: 60px;
+          opacity: 0;
+        }
+      }
+      @keyframes cloudMid {
+        0% {
+          top: 10px;
+          left: 420px;
+          opacity: 0;
+        }
+        20% {
+          top: 40px;
+          left: 360px;
+          opacity: 1;
+        }
+        70% {
+          top: 130px;
+          left: 180px;
+          opacity: 1;
+        }
+        100% {
+          top: 160px;
+          left: 120px;
+          opacity: 0;
+        }
+      }
+      @keyframes cloudRight {
+        0% {
+          top: 100px;
+          left: 500px;
+          opacity: 0;
+        }
+        20% {
+          top: 120px;
+          left: 460px;
+          opacity: 1;
+        }
+        80% {
+          top: 180px;
+          left: 340px;
+          opacity: 1;
+        }
+        100% {
+          top: 200px;
+          left: 300px;
+          opacity: 0;
+        }
+      }
+    }
+  }
+  .bullshit {
+    position: relative;
+    float: left;
+    width: 300px;
+    padding: 30px 0;
+    overflow: hidden;
+    &__oops {
+      font-size: 32px;
+      font-weight: bold;
+      line-height: 40px;
+      color: #1482f0;
+      opacity: 0;
+      margin-bottom: 20px;
+      animation-name: slideUp;
+      animation-duration: 0.5s;
+      animation-fill-mode: forwards;
+    }
+    &__headline {
+      font-size: 20px;
+      line-height: 24px;
+      color: #222;
+      font-weight: bold;
+      opacity: 0;
+      margin-bottom: 10px;
+      animation-name: slideUp;
+      animation-duration: 0.5s;
+      animation-delay: 0.1s;
+      animation-fill-mode: forwards;
+    }
+    &__info {
+      font-size: 13px;
+      line-height: 21px;
+      color: grey;
+      opacity: 0;
+      margin-bottom: 30px;
+      animation-name: slideUp;
+      animation-duration: 0.5s;
+      animation-delay: 0.2s;
+      animation-fill-mode: forwards;
+    }
+    &__return-home {
+      display: block;
+      float: left;
+      width: 110px;
+      height: 36px;
+      background: #1482f0;
+      border-radius: 100px;
+      text-align: center;
+      color: #ffffff;
+      opacity: 0;
+      font-size: 14px;
+      line-height: 36px;
+      cursor: pointer;
+      animation-name: slideUp;
+      animation-duration: 0.5s;
+      animation-delay: 0.3s;
+      animation-fill-mode: forwards;
+    }
+    @keyframes slideUp {
+      0% {
+        transform: translateY(60px);
+        opacity: 0;
+      }
+      100% {
+        transform: translateY(0);
+        opacity: 1;
+      }
+    }
+  }
+}
+</style>
diff --git a/frontend/src/views/example/components/ArticleDetail.vue b/frontend/src/views/example/components/ArticleDetail.vue
new file mode 100644
index 0000000..157497b
--- /dev/null
+++ b/frontend/src/views/example/components/ArticleDetail.vue
@@ -0,0 +1,289 @@
+<template>
+  <div class="createPost-container">
+    <el-form ref="postForm" :model="postForm" :rules="rules" class="form-container">
+
+      <sticky :z-index="10" :class-name="'sub-navbar '+postForm.status">
+        <CommentDropdown v-model="postForm.comment_disabled" />
+        <PlatformDropdown v-model="postForm.platforms" />
+        <SourceUrlDropdown v-model="postForm.source_uri" />
+        <el-button v-loading="loading" style="margin-left: 10px;" type="success" @click="submitForm">
+          Publish
+        </el-button>
+        <el-button v-loading="loading" type="warning" @click="draftForm">
+          Draft
+        </el-button>
+      </sticky>
+
+      <div class="createPost-main-container">
+        <el-row>
+          <Warning />
+
+          <el-col :span="24">
+            <el-form-item style="margin-bottom: 40px;" prop="title">
+              <MDinput v-model="postForm.title" :maxlength="100" name="name" required>
+                Title
+              </MDinput>
+            </el-form-item>
+
+            <div class="postInfo-container">
+              <el-row>
+                <el-col :span="8">
+                  <el-form-item label-width="60px" label="Author:" class="postInfo-container-item">
+                    <el-select v-model="postForm.author" :remote-method="getRemoteUserList" filterable default-first-option remote placeholder="Search user">
+                      <el-option v-for="(item,index) in userListOptions" :key="item+index" :label="item" :value="item" />
+                    </el-select>
+                  </el-form-item>
+                </el-col>
+
+                <el-col :span="10">
+                  <el-form-item label-width="120px" label="Publish Time:" class="postInfo-container-item">
+                    <el-date-picker v-model="displayTime" type="datetime" format="yyyy-MM-dd HH:mm:ss" placeholder="Select date and time" />
+                  </el-form-item>
+                </el-col>
+
+                <el-col :span="6">
+                  <el-form-item label-width="90px" label="Importance:" class="postInfo-container-item">
+                    <el-rate
+                      v-model="postForm.importance"
+                      :max="3"
+                      :colors="['#99A9BF', '#F7BA2A', '#FF9900']"
+                      :low-threshold="1"
+                      :high-threshold="3"
+                      style="display:inline-block"
+                    />
+                  </el-form-item>
+                </el-col>
+              </el-row>
+            </div>
+          </el-col>
+        </el-row>
+
+        <el-form-item style="margin-bottom: 40px;" label-width="70px" label="Summary:">
+          <el-input v-model="postForm.content_short" :rows="1" type="textarea" class="article-textarea" autosize placeholder="Please enter the content" />
+          <span v-show="contentShortLength" class="word-counter">{{ contentShortLength }}words</span>
+        </el-form-item>
+
+        <el-form-item prop="content" style="margin-bottom: 30px;">
+          <Tinymce ref="editor" v-model="postForm.content" :height="400" />
+        </el-form-item>
+
+        <el-form-item prop="image_uri" style="margin-bottom: 30px;">
+          <Upload v-model="postForm.image_uri" />
+        </el-form-item>
+      </div>
+    </el-form>
+  </div>
+</template>
+
+<script>
+import Tinymce from '@/components/Tinymce'
+import Upload from '@/components/Upload/SingleImage3'
+import MDinput from '@/components/MDinput'
+import Sticky from '@/components/Sticky' // 粘性header组件
+import { validURL } from '@/utils/validate'
+import { fetchArticle } from '@/api/article'
+import { searchUser } from '@/api/remote-search'
+import Warning from './Warning'
+import { CommentDropdown, PlatformDropdown, SourceUrlDropdown } from './Dropdown'
+
+const defaultForm = {
+  status: 'draft',
+  title: '', // 文章题目
+  content: '', // 文章内容
+  content_short: '', // 文章摘要
+  source_uri: '', // 文章外链
+  image_uri: '', // 文章图片
+  display_time: undefined, // 前台展示时间
+  id: undefined,
+  platforms: ['a-platform'],
+  comment_disabled: false,
+  importance: 0
+}
+
+export default {
+  name: 'ArticleDetail',
+  components: { Tinymce, MDinput, Upload, Sticky, Warning, CommentDropdown, PlatformDropdown, SourceUrlDropdown },
+  props: {
+    isEdit: {
+      type: Boolean,
+      default: false
+    }
+  },
+  data() {
+    const validateRequire = (rule, value, callback) => {
+      if (value === '') {
+        this.$message({
+          message: rule.field + '为必传项',
+          type: 'error'
+        })
+        callback(new Error(rule.field + '为必传项'))
+      } else {
+        callback()
+      }
+    }
+    const validateSourceUri = (rule, value, callback) => {
+      if (value) {
+        if (validURL(value)) {
+          callback()
+        } else {
+          this.$message({
+            message: '外链url填写不正确',
+            type: 'error'
+          })
+          callback(new Error('外链url填写不正确'))
+        }
+      } else {
+        callback()
+      }
+    }
+    return {
+      postForm: Object.assign({}, defaultForm),
+      loading: false,
+      userListOptions: [],
+      rules: {
+        image_uri: [{ validator: validateRequire }],
+        title: [{ validator: validateRequire }],
+        content: [{ validator: validateRequire }],
+        source_uri: [{ validator: validateSourceUri, trigger: 'blur' }]
+      },
+      tempRoute: {}
+    }
+  },
+  computed: {
+    contentShortLength() {
+      return this.postForm.content_short.length
+    },
+    displayTime: {
+      // set and get is useful when the data
+      // returned by the back end api is different from the front end
+      // back end return => "2013-06-25 06:59:25"
+      // front end need timestamp => 1372114765000
+      get() {
+        return (+new Date(this.postForm.display_time))
+      },
+      set(val) {
+        this.postForm.display_time = new Date(val)
+      }
+    }
+  },
+  created() {
+    if (this.isEdit) {
+      const id = this.$route.params && this.$route.params.id
+      this.fetchData(id)
+    }
+
+    // Why need to make a copy of this.$route here?
+    // Because if you enter this page and quickly switch tag, may be in the execution of the setTagsViewTitle function, this.$route is no longer pointing to the current page
+    // https://github.com/PanJiaChen/vue-element-admin/issues/1221
+    this.tempRoute = Object.assign({}, this.$route)
+  },
+  methods: {
+    fetchData(id) {
+      fetchArticle(id).then(response => {
+        this.postForm = response.data
+
+        // just for test
+        this.postForm.title += `   Article Id:${this.postForm.id}`
+        this.postForm.content_short += `   Article Id:${this.postForm.id}`
+
+        // set tagsview title
+        this.setTagsViewTitle()
+
+        // set page title
+        this.setPageTitle()
+      }).catch(err => {
+        console.log(err)
+      })
+    },
+    setTagsViewTitle() {
+      const title = 'Edit Article'
+      const route = Object.assign({}, this.tempRoute, { title: `${title}-${this.postForm.id}` })
+      this.$store.dispatch('tagsView/updateVisitedView', route)
+    },
+    setPageTitle() {
+      const title = 'Edit Article'
+      document.title = `${title} - ${this.postForm.id}`
+    },
+    submitForm() {
+      console.log(this.postForm)
+      this.$refs.postForm.validate(valid => {
+        if (valid) {
+          this.loading = true
+          this.$notify({
+            title: '成功',
+            message: '发布文章成功',
+            type: 'success',
+            duration: 2000
+          })
+          this.postForm.status = 'published'
+          this.loading = false
+        } else {
+          console.log('error submit!!')
+          return false
+        }
+      })
+    },
+    draftForm() {
+      if (this.postForm.content.length === 0 || this.postForm.title.length === 0) {
+        this.$message({
+          message: '请填写必要的标题和内容',
+          type: 'warning'
+        })
+        return
+      }
+      this.$message({
+        message: '保存成功',
+        type: 'success',
+        showClose: true,
+        duration: 1000
+      })
+      this.postForm.status = 'draft'
+    },
+    getRemoteUserList(query) {
+      searchUser(query).then(response => {
+        if (!response.data.items) return
+        this.userListOptions = response.data.items.map(v => v.name)
+      })
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+@import "~@/styles/mixin.scss";
+
+.createPost-container {
+  position: relative;
+
+  .createPost-main-container {
+    padding: 40px 45px 20px 50px;
+
+    .postInfo-container {
+      position: relative;
+      @include clearfix;
+      margin-bottom: 10px;
+
+      .postInfo-container-item {
+        float: left;
+      }
+    }
+  }
+
+  .word-counter {
+    width: 40px;
+    position: absolute;
+    right: 10px;
+    top: 0px;
+  }
+}
+
+.article-textarea ::v-deep {
+  textarea {
+    padding-right: 40px;
+    resize: none;
+    border: none;
+    border-radius: 0px;
+    border-bottom: 1px solid #bfcbd9;
+  }
+}
+</style>
diff --git a/frontend/src/views/example/components/Dropdown/Comment.vue b/frontend/src/views/example/components/Dropdown/Comment.vue
new file mode 100644
index 0000000..d34b2b9
--- /dev/null
+++ b/frontend/src/views/example/components/Dropdown/Comment.vue
@@ -0,0 +1,41 @@
+<template>
+  <el-dropdown :show-timeout="100" trigger="click">
+    <el-button plain>
+      {{ !comment_disabled?'Comment: opened':'Comment: closed' }}
+      <i class="el-icon-caret-bottom el-icon--right" />
+    </el-button>
+    <el-dropdown-menu slot="dropdown" class="no-padding">
+      <el-dropdown-item>
+        <el-radio-group v-model="comment_disabled" style="padding: 10px;">
+          <el-radio :label="true">
+            Close comment
+          </el-radio>
+          <el-radio :label="false">
+            Open comment
+          </el-radio>
+        </el-radio-group>
+      </el-dropdown-item>
+    </el-dropdown-menu>
+  </el-dropdown>
+</template>
+
+<script>
+export default {
+  props: {
+    value: {
+      type: Boolean,
+      default: false
+    }
+  },
+  computed: {
+    comment_disabled: {
+      get() {
+        return this.value
+      },
+      set(val) {
+        this.$emit('input', val)
+      }
+    }
+  }
+}
+</script>
diff --git a/frontend/src/views/example/components/Dropdown/Platform.vue b/frontend/src/views/example/components/Dropdown/Platform.vue
new file mode 100644
index 0000000..0a52726
--- /dev/null
+++ b/frontend/src/views/example/components/Dropdown/Platform.vue
@@ -0,0 +1,46 @@
+<template>
+  <el-dropdown :hide-on-click="false" :show-timeout="100" trigger="click">
+    <el-button plain>
+      Platfroms({{ platforms.length }})
+      <i class="el-icon-caret-bottom el-icon--right" />
+    </el-button>
+    <el-dropdown-menu slot="dropdown" class="no-border">
+      <el-checkbox-group v-model="platforms" style="padding: 5px 15px;">
+        <el-checkbox v-for="item in platformsOptions" :key="item.key" :label="item.key">
+          {{ item.name }}
+        </el-checkbox>
+      </el-checkbox-group>
+    </el-dropdown-menu>
+  </el-dropdown>
+</template>
+
+<script>
+export default {
+  props: {
+    value: {
+      required: true,
+      default: () => [],
+      type: Array
+    }
+  },
+  data() {
+    return {
+      platformsOptions: [
+        { key: 'a-platform', name: 'a-platform' },
+        { key: 'b-platform', name: 'b-platform' },
+        { key: 'c-platform', name: 'c-platform' }
+      ]
+    }
+  },
+  computed: {
+    platforms: {
+      get() {
+        return this.value
+      },
+      set(val) {
+        this.$emit('input', val)
+      }
+    }
+  }
+}
+</script>
diff --git a/frontend/src/views/example/components/Dropdown/SourceUrl.vue b/frontend/src/views/example/components/Dropdown/SourceUrl.vue
new file mode 100644
index 0000000..8f47485
--- /dev/null
+++ b/frontend/src/views/example/components/Dropdown/SourceUrl.vue
@@ -0,0 +1,38 @@
+<template>
+  <el-dropdown :show-timeout="100" trigger="click">
+    <el-button plain>
+      Link
+      <i class="el-icon-caret-bottom el-icon--right" />
+    </el-button>
+    <el-dropdown-menu slot="dropdown" class="no-padding no-border" style="width:400px">
+      <el-form-item label-width="0px" style="margin-bottom: 0px" prop="source_uri">
+        <el-input v-model="source_uri" placeholder="Please enter the content">
+          <template slot="prepend">
+            URL
+          </template>
+        </el-input>
+      </el-form-item>
+    </el-dropdown-menu>
+  </el-dropdown>
+</template>
+
+<script>
+export default {
+  props: {
+    value: {
+      type: String,
+      default: ''
+    }
+  },
+  computed: {
+    source_uri: {
+      get() {
+        return this.value
+      },
+      set(val) {
+        this.$emit('input', val)
+      }
+    }
+  }
+}
+</script>
diff --git a/frontend/src/views/example/components/Dropdown/index.js b/frontend/src/views/example/components/Dropdown/index.js
new file mode 100644
index 0000000..bc0c171
--- /dev/null
+++ b/frontend/src/views/example/components/Dropdown/index.js
@@ -0,0 +1,3 @@
+export { default as CommentDropdown } from './Comment'
+export { default as PlatformDropdown } from './Platform'
+export { default as SourceUrlDropdown } from './SourceUrl'
diff --git a/frontend/src/views/example/components/Warning.vue b/frontend/src/views/example/components/Warning.vue
new file mode 100644
index 0000000..8d2a7e5
--- /dev/null
+++ b/frontend/src/views/example/components/Warning.vue
@@ -0,0 +1,13 @@
+<template>
+  <aside>
+    Creating and editing pages cannot be cached by keep-alive because keep-alive include does not currently support
+    caching based on routes, so it is currently cached based on component name. If you want to achieve a similar caching
+    effect, you can use a browser caching scheme such as localStorage. Or do not use keep-alive include to cache all
+    pages directly. See details
+    <a
+      href="https://panjiachen.github.io/vue-element-admin-site/guide/essentials/tags-view.html"
+      target="_blank"
+    >Document</a>
+  </aside>
+</template>
+
diff --git a/frontend/src/views/example/create.vue b/frontend/src/views/example/create.vue
new file mode 100644
index 0000000..f28ce28
--- /dev/null
+++ b/frontend/src/views/example/create.vue
@@ -0,0 +1,13 @@
+<template>
+  <article-detail :is-edit="false" />
+</template>
+
+<script>
+import ArticleDetail from './components/ArticleDetail'
+
+export default {
+  name: 'CreateArticle',
+  components: { ArticleDetail }
+}
+</script>
+
diff --git a/frontend/src/views/example/edit.vue b/frontend/src/views/example/edit.vue
new file mode 100644
index 0000000..87b6126
--- /dev/null
+++ b/frontend/src/views/example/edit.vue
@@ -0,0 +1,13 @@
+<template>
+  <article-detail :is-edit="true" />
+</template>
+
+<script>
+import ArticleDetail from './components/ArticleDetail'
+
+export default {
+  name: 'EditForm',
+  components: { ArticleDetail }
+}
+</script>
+
diff --git a/frontend/src/views/example/list.vue b/frontend/src/views/example/list.vue
new file mode 100644
index 0000000..7cdc4ac
--- /dev/null
+++ b/frontend/src/views/example/list.vue
@@ -0,0 +1,112 @@
+<template>
+  <div class="app-container">
+    <el-table v-loading="listLoading" :data="list" border fit highlight-current-row style="width: 100%">
+      <el-table-column align="center" label="ID" width="80">
+        <template slot-scope="scope">
+          <span>{{ scope.row.id }}</span>
+        </template>
+      </el-table-column>
+
+      <el-table-column width="180px" align="center" label="Date">
+        <template slot-scope="scope">
+          <span>{{ scope.row.timestamp | parseTime('{y}-{m}-{d} {h}:{i}') }}</span>
+        </template>
+      </el-table-column>
+
+      <el-table-column width="120px" align="center" label="Author">
+        <template slot-scope="scope">
+          <span>{{ scope.row.author }}</span>
+        </template>
+      </el-table-column>
+
+      <el-table-column width="100px" label="Importance">
+        <template slot-scope="scope">
+          <svg-icon v-for="n in +scope.row.importance" :key="n" icon-class="star" class="meta-item__icon" />
+        </template>
+      </el-table-column>
+
+      <el-table-column class-name="status-col" label="Status" width="110">
+        <template slot-scope="{row}">
+          <el-tag :type="row.status | statusFilter">
+            {{ row.status }}
+          </el-tag>
+        </template>
+      </el-table-column>
+
+      <el-table-column min-width="300px" label="Title">
+        <template slot-scope="{row}">
+          <router-link :to="'/example/edit/'+row.id" class="link-type">
+            <span>{{ row.title }}</span>
+          </router-link>
+        </template>
+      </el-table-column>
+
+      <el-table-column align="center" label="Actions" width="120">
+        <template slot-scope="scope">
+          <router-link :to="'/example/edit/'+scope.row.id">
+            <el-button type="primary" size="small" icon="el-icon-edit">
+              Edit
+            </el-button>
+          </router-link>
+        </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: 'ArticleList',
+  components: { Pagination },
+  filters: {
+    statusFilter(status) {
+      const statusMap = {
+        published: 'success',
+        draft: 'info',
+        deleted: 'danger'
+      }
+      return statusMap[status]
+    }
+  },
+  data() {
+    return {
+      list: null,
+      total: 0,
+      listLoading: true,
+      listQuery: {
+        page: 1,
+        limit: 20
+      }
+    }
+  },
+  created() {
+    this.getList()
+  },
+  methods: {
+    getList() {
+      this.listLoading = true
+      fetchList(this.listQuery).then(response => {
+        this.list = response.data.items
+        this.total = response.data.total
+        this.listLoading = false
+      })
+    }
+  }
+}
+</script>
+
+<style scoped>
+.edit-input {
+  padding-right: 100px;
+}
+.cancel-btn {
+  position: absolute;
+  right: 15px;
+  top: 10px;
+}
+</style>
diff --git a/frontend/src/views/excel/components/AutoWidthOption.vue b/frontend/src/views/excel/components/AutoWidthOption.vue
new file mode 100644
index 0000000..9050e65
--- /dev/null
+++ b/frontend/src/views/excel/components/AutoWidthOption.vue
@@ -0,0 +1,34 @@
+<template>
+  <div style="display:inline-block;">
+    <label class="radio-label">Cell Auto-Width: </label>
+    <el-radio-group v-model="autoWidth">
+      <el-radio :label="true" border>
+        True
+      </el-radio>
+      <el-radio :label="false" border>
+        False
+      </el-radio>
+    </el-radio-group>
+  </div>
+</template>
+
+<script>
+export default {
+  props: {
+    value: {
+      type: Boolean,
+      default: true
+    }
+  },
+  computed: {
+    autoWidth: {
+      get() {
+        return this.value
+      },
+      set(val) {
+        this.$emit('input', val)
+      }
+    }
+  }
+}
+</script>
diff --git a/frontend/src/views/excel/components/BookTypeOption.vue b/frontend/src/views/excel/components/BookTypeOption.vue
new file mode 100644
index 0000000..af9fed3
--- /dev/null
+++ b/frontend/src/views/excel/components/BookTypeOption.vue
@@ -0,0 +1,39 @@
+<template>
+  <div style="display:inline-block;">
+    <label class="radio-label">Book Type: </label>
+    <el-select v-model="bookType" style="width:120px;">
+      <el-option
+        v-for="item in options"
+        :key="item"
+        :label="item"
+        :value="item"
+      />
+    </el-select>
+  </div>
+</template>
+
+<script>
+export default {
+  props: {
+    value: {
+      type: String,
+      default: 'xlsx'
+    }
+  },
+  data() {
+    return {
+      options: ['xlsx', 'csv', 'txt']
+    }
+  },
+  computed: {
+    bookType: {
+      get() {
+        return this.value
+      },
+      set(val) {
+        this.$emit('input', val)
+      }
+    }
+  }
+}
+</script>
diff --git a/frontend/src/views/excel/components/FilenameOption.vue b/frontend/src/views/excel/components/FilenameOption.vue
new file mode 100644
index 0000000..7509223
--- /dev/null
+++ b/frontend/src/views/excel/components/FilenameOption.vue
@@ -0,0 +1,27 @@
+<template>
+  <div style="display:inline-block;">
+    <label class="radio-label" style="padding-left:0;">Filename: </label>
+    <el-input v-model="filename" placeholder="Please enter the file name (default excel-list)" style="width:345px;" prefix-icon="el-icon-document" />
+  </div>
+</template>
+
+<script>
+export default {
+  props: {
+    value: {
+      type: String,
+      default: ''
+    }
+  },
+  computed: {
+    filename: {
+      get() {
+        return this.value
+      },
+      set(val) {
+        this.$emit('input', val)
+      }
+    }
+  }
+}
+</script>
diff --git a/frontend/src/views/excel/export-excel.vue b/frontend/src/views/excel/export-excel.vue
new file mode 100644
index 0000000..b4d7500
--- /dev/null
+++ b/frontend/src/views/excel/export-excel.vue
@@ -0,0 +1,116 @@
+<template>
+  <div class="app-container">
+
+    <div>
+      <FilenameOption v-model="filename" />
+      <AutoWidthOption v-model="autoWidth" />
+      <BookTypeOption v-model="bookType" />
+      <el-button :loading="downloadLoading" style="margin:0 0 20px 20px;" type="primary" icon="el-icon-document" @click="handleDownload">
+        Export Excel
+      </el-button>
+      <a href="https://panjiachen.github.io/vue-element-admin-site/feature/component/excel.html" target="_blank" style="margin-left:15px;">
+        <el-tag type="info">Documentation</el-tag>
+      </a>
+    </div>
+
+    <el-table v-loading="listLoading" :data="list" element-loading-text="Loading..." border fit highlight-current-row>
+      <el-table-column align="center" label="Id" width="95">
+        <template slot-scope="scope">
+          {{ scope.$index }}
+        </template>
+      </el-table-column>
+      <el-table-column label="Title">
+        <template slot-scope="scope">
+          {{ scope.row.title }}
+        </template>
+      </el-table-column>
+      <el-table-column label="Author" width="110" align="center">
+        <template slot-scope="scope">
+          <el-tag>{{ scope.row.author }}</el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column label="Readings" width="115" align="center">
+        <template slot-scope="scope">
+          {{ scope.row.pageviews }}
+        </template>
+      </el-table-column>
+      <el-table-column align="center" label="Date" width="220">
+        <template slot-scope="scope">
+          <i class="el-icon-time" />
+          <span>{{ scope.row.timestamp | parseTime('{y}-{m}-{d} {h}:{i}') }}</span>
+        </template>
+      </el-table-column>
+    </el-table>
+  </div>
+</template>
+
+<script>
+import { fetchList } from '@/api/article'
+import { parseTime } from '@/utils'
+// options components
+import FilenameOption from './components/FilenameOption'
+import AutoWidthOption from './components/AutoWidthOption'
+import BookTypeOption from './components/BookTypeOption'
+
+export default {
+  name: 'ExportExcel',
+  components: { FilenameOption, AutoWidthOption, BookTypeOption },
+  data() {
+    return {
+      list: null,
+      listLoading: true,
+      downloadLoading: false,
+      filename: '',
+      autoWidth: true,
+      bookType: 'xlsx'
+    }
+  },
+  created() {
+    this.fetchData()
+  },
+  methods: {
+    fetchData() {
+      this.listLoading = true
+      fetchList().then(response => {
+        this.list = response.data.items
+        this.listLoading = false
+      })
+    },
+    handleDownload() {
+      this.downloadLoading = true
+      import('@/vendor/Export2Excel').then(excel => {
+        const tHeader = ['Id', 'Title', 'Author', 'Readings', 'Date']
+        const filterVal = ['id', 'title', 'author', 'pageviews', 'display_time']
+        const list = this.list
+        const data = this.formatJson(filterVal, list)
+        excel.export_json_to_excel({
+          header: tHeader,
+          data,
+          filename: this.filename,
+          autoWidth: this.autoWidth,
+          bookType: this.bookType
+        })
+        this.downloadLoading = false
+      })
+    },
+    formatJson(filterVal, jsonData) {
+      return jsonData.map(v => filterVal.map(j => {
+        if (j === 'timestamp') {
+          return parseTime(v[j])
+        } else {
+          return v[j]
+        }
+      }))
+    }
+  }
+}
+</script>
+
+<style>
+.radio-label {
+  font-size: 14px;
+  color: #606266;
+  line-height: 40px;
+  padding: 0 12px 0 30px;
+}
+</style>
diff --git a/frontend/src/views/excel/merge-header.vue b/frontend/src/views/excel/merge-header.vue
new file mode 100644
index 0000000..4be0227
--- /dev/null
+++ b/frontend/src/views/excel/merge-header.vue
@@ -0,0 +1,101 @@
+<template>
+  <div class="app-container">
+
+    <el-button :loading="downloadLoading" style="margin-bottom:20px" type="primary" icon="el-icon-document" @click="handleDownload">Export</el-button>
+
+    <el-table
+      ref="multipleTable"
+      v-loading="listLoading"
+      :data="list"
+      element-loading-text="Loading"
+      border
+      fit
+      highlight-current-row
+    >
+      <el-table-column align="center" label="Id" width="95">
+        <template slot-scope="scope">
+          {{ scope.$index }}
+        </template>
+      </el-table-column>
+      <el-table-column label="Main Information" align="center">
+        <el-table-column label="Title">
+          <template slot-scope="scope">
+            {{ scope.row.title }}
+          </template>
+        </el-table-column>
+        <el-table-column label="Author" width="110" align="center">
+          <template slot-scope="scope">
+            <el-tag>{{ scope.row.author }}</el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column label="Readings" width="115" align="center">
+          <template slot-scope="scope">
+            {{ scope.row.pageviews }}
+          </template>
+        </el-table-column>
+      </el-table-column>
+      <el-table-column align="center" label="Date" width="220">
+        <template slot-scope="scope">
+          <i class="el-icon-time" />
+          <span>{{ scope.row.timestamp | parseTime('{y}-{m}-{d} {h}:{i}') }}</span>
+        </template>
+      </el-table-column>
+    </el-table>
+
+  </div>
+</template>
+
+<script>
+import { fetchList } from '@/api/article'
+import { parseTime } from '@/utils'
+
+export default {
+  name: 'MergeHeader',
+  data() {
+    return {
+      list: null,
+      listLoading: true,
+      downloadLoading: false
+    }
+  },
+  created() {
+    this.fetchData()
+  },
+  methods: {
+    fetchData() {
+      this.listLoading = true
+      fetchList(this.listQuery).then(response => {
+        this.list = response.data.items
+        this.listLoading = false
+      })
+    },
+    handleDownload() {
+      this.downloadLoading = true
+      import('@/vendor/Export2Excel').then(excel => {
+        const multiHeader = [['Id', 'Main Information', '', '', 'Date']]
+        const header = ['', 'Title', 'Author', 'Readings', '']
+        const filterVal = ['id', 'title', 'author', 'pageviews', 'display_time']
+        const list = this.list
+        const data = this.formatJson(filterVal, list)
+        const merges = ['A1:A2', 'B1:D1', 'E1:E2']
+        excel.export_json_to_excel({
+          multiHeader,
+          header,
+          merges,
+          data
+        })
+        this.downloadLoading = false
+      })
+    },
+    formatJson(filterVal, jsonData) {
+      return jsonData.map(v => filterVal.map(j => {
+        if (j === 'timestamp') {
+          return parseTime(v[j])
+        } else {
+          return v[j]
+        }
+      }))
+    }
+  }
+}
+</script>
diff --git a/frontend/src/views/excel/select-excel.vue b/frontend/src/views/excel/select-excel.vue
new file mode 100644
index 0000000..05b85fb
--- /dev/null
+++ b/frontend/src/views/excel/select-excel.vue
@@ -0,0 +1,107 @@
+<template>
+  <div class="app-container">
+    <el-input v-model="filename" placeholder="Please enter the file name (default excel-list)" style="width:350px;" prefix-icon="el-icon-document" />
+    <el-button :loading="downloadLoading" style="margin-bottom:20px" type="primary" icon="el-icon-document" @click="handleDownload">
+      Export Selected Items
+    </el-button>
+    <a href="https://panjiachen.github.io/vue-element-admin-site/feature/component/excel.html" target="_blank" style="margin-left:15px;">
+      <el-tag type="info">Documentation</el-tag>
+    </a>
+    <el-table
+      ref="multipleTable"
+      v-loading="listLoading"
+      :data="list"
+      element-loading-text="拼命加载中"
+      border
+      fit
+      highlight-current-row
+      @selection-change="handleSelectionChange"
+    >
+      <el-table-column type="selection" align="center" />
+      <el-table-column align="center" label="Id" width="95">
+        <template slot-scope="scope">
+          {{ scope.$index }}
+        </template>
+      </el-table-column>
+      <el-table-column label="Title">
+        <template slot-scope="scope">
+          {{ scope.row.title }}
+        </template>
+      </el-table-column>
+      <el-table-column label="Author" width="110" align="center">
+        <template slot-scope="scope">
+          <el-tag>{{ scope.row.author }}</el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column label="Readings" width="115" align="center">
+        <template slot-scope="scope">
+          {{ scope.row.pageviews }}
+        </template>
+      </el-table-column>
+      <el-table-column align="center" label="PDate" width="220">
+        <template slot-scope="scope">
+          <i class="el-icon-time" />
+          <span>{{ scope.row.display_time }}</span>
+        </template>
+      </el-table-column>
+    </el-table>
+  </div>
+</template>
+
+<script>
+import { fetchList } from '@/api/article'
+
+export default {
+  name: 'SelectExcel',
+  data() {
+    return {
+      list: null,
+      listLoading: true,
+      multipleSelection: [],
+      downloadLoading: false,
+      filename: ''
+    }
+  },
+  created() {
+    this.fetchData()
+  },
+  methods: {
+    fetchData() {
+      this.listLoading = true
+      fetchList(this.listQuery).then(response => {
+        this.list = response.data.items
+        this.listLoading = false
+      })
+    },
+    handleSelectionChange(val) {
+      this.multipleSelection = val
+    },
+    handleDownload() {
+      if (this.multipleSelection.length) {
+        this.downloadLoading = true
+        import('@/vendor/Export2Excel').then(excel => {
+          const tHeader = ['Id', 'Title', 'Author', 'Readings', 'Date']
+          const filterVal = ['id', 'title', 'author', 'pageviews', 'display_time']
+          const list = this.multipleSelection
+          const data = this.formatJson(filterVal, list)
+          excel.export_json_to_excel({
+            header: tHeader,
+            data,
+            filename: this.filename
+          })
+          this.$refs.multipleTable.clearSelection()
+          this.downloadLoading = false
+        })
+      } else {
+        this.$message({
+          message: 'Please select at least one item',
+          type: 'warning'
+        })
+      }
+    },
+    formatJson(filterVal, jsonData) {
+      return jsonData.map(v => filterVal.map(j => v[j]))
+    }
+  }
+}
+</script>
diff --git a/frontend/src/views/excel/upload-excel.vue b/frontend/src/views/excel/upload-excel.vue
new file mode 100644
index 0000000..1772b7f
--- /dev/null
+++ b/frontend/src/views/excel/upload-excel.vue
@@ -0,0 +1,42 @@
+<template>
+  <div class="app-container">
+    <upload-excel-component :on-success="handleSuccess" :before-upload="beforeUpload" />
+    <el-table :data="tableData" border highlight-current-row style="width: 100%;margin-top:20px;">
+      <el-table-column v-for="item of tableHeader" :key="item" :prop="item" :label="item" />
+    </el-table>
+  </div>
+</template>
+
+<script>
+import UploadExcelComponent from '@/components/UploadExcel/index.vue'
+
+export default {
+  name: 'UploadExcel',
+  components: { UploadExcelComponent },
+  data() {
+    return {
+      tableData: [],
+      tableHeader: []
+    }
+  },
+  methods: {
+    beforeUpload(file) {
+      const isLt1M = file.size / 1024 / 1024 < 1
+
+      if (isLt1M) {
+        return true
+      }
+
+      this.$message({
+        message: 'Please do not upload files larger than 1m in size.',
+        type: 'warning'
+      })
+      return false
+    },
+    handleSuccess({ results, header }) {
+      this.tableData = results
+      this.tableHeader = header
+    }
+  }
+}
+</script>
diff --git a/frontend/src/views/guide/index.vue b/frontend/src/views/guide/index.vue
new file mode 100644
index 0000000..4897c8f
--- /dev/null
+++ b/frontend/src/views/guide/index.vue
@@ -0,0 +1,36 @@
+<template>
+  <div class="app-container">
+    <aside>
+      The guide page is useful for some people who entered the project for the first time. You can briefly introduce the
+      features of the project. Demo is based on
+      <a href="https://github.com/kamranahmedse/driver.js" target="_blank">driver.js.</a>
+    </aside>
+    <el-button icon="el-icon-question" type="primary" @click.prevent.stop="guide">
+      Show Guide
+    </el-button>
+  </div>
+</template>
+
+<script>
+import Driver from 'driver.js' // import driver.js
+import 'driver.js/dist/driver.min.css' // import driver.js css
+import steps from './steps'
+
+export default {
+  name: 'Guide',
+  data() {
+    return {
+      driver: null
+    }
+  },
+  mounted() {
+    this.driver = new Driver()
+  },
+  methods: {
+    guide() {
+      this.driver.defineSteps(steps)
+      this.driver.start()
+    }
+  }
+}
+</script>
diff --git a/frontend/src/views/guide/steps.js b/frontend/src/views/guide/steps.js
new file mode 100644
index 0000000..283c672
--- /dev/null
+++ b/frontend/src/views/guide/steps.js
@@ -0,0 +1,53 @@
+const steps = [
+  {
+    element: '#hamburger-container',
+    popover: {
+      title: 'Hamburger',
+      description: 'Open && Close sidebar',
+      position: 'bottom'
+    }
+  },
+  {
+    element: '#breadcrumb-container',
+    popover: {
+      title: 'Breadcrumb',
+      description: 'Indicate the current page location',
+      position: 'bottom'
+    }
+  },
+  {
+    element: '#header-search',
+    popover: {
+      title: 'Page Search',
+      description: 'Page search, quick navigation',
+      position: 'left'
+    }
+  },
+  {
+    element: '#screenfull',
+    popover: {
+      title: 'Screenfull',
+      description: 'Set the page into fullscreen',
+      position: 'left'
+    }
+  },
+  {
+    element: '#size-select',
+    popover: {
+      title: 'Switch Size',
+      description: 'Switch the system size',
+      position: 'left'
+    }
+  },
+  {
+    element: '#tags-view-container',
+    popover: {
+      title: 'Tags view',
+      description: 'The history of the page you visited',
+      position: 'bottom'
+    },
+    padding: 0
+  }
+]
+
+export default steps
diff --git a/frontend/src/views/icons/element-icons.js b/frontend/src/views/icons/element-icons.js
new file mode 100644
index 0000000..9ea4d63
--- /dev/null
+++ b/frontend/src/views/icons/element-icons.js
@@ -0,0 +1,3 @@
+const elementIcons = ['platform-eleme', 'eleme', 'delete-solid', 'delete', 's-tools', 'setting', 'user-solid', 'user', 'phone', 'phone-outline', 'more', 'more-outline', 'star-on', 'star-off', 's-goods', 'goods', 'warning', 'warning-outline', 'question', 'info', 'remove', 'circle-plus', 'success', 'error', 'zoom-in', 'zoom-out', 'remove-outline', 'circle-plus-outline', 'circle-check', 'circle-close', 's-help', 'help', 'minus', 'plus', 'check', 'close', 'picture', 'picture-outline', 'picture-outline-round', 'upload', 'upload2', 'download', 'camera-solid', 'camera', 'video-camera-solid', 'video-camera', 'message-solid', 'bell', 's-cooperation', 's-order', 's-platform', 's-fold', 's-unfold', 's-operation', 's-promotion', 's-home', 's-release', 's-ticket', 's-management', 's-open', 's-shop', 's-marketing', 's-flag', 's-comment', 's-finance', 's-claim', 's-custom', 's-opportunity', 's-data', 's-check', 's-grid', 'menu', 'share', 'd-caret', 'caret-left', 'caret-right', 'caret-bottom', 'caret-top', 'bottom-left', 'bottom-right', 'back', 'right', 'bottom', 'top', 'top-left', 'top-right', 'arrow-left', 'arrow-right', 'arrow-down', 'arrow-up', 'd-arrow-left', 'd-arrow-right', 'video-pause', 'video-play', 'refresh', 'refresh-right', 'refresh-left', 'finished', 'sort', 'sort-up', 'sort-down', 'rank', 'loading', 'view', 'c-scale-to-original', 'date', 'edit', 'edit-outline', 'folder', 'folder-opened', 'folder-add', 'folder-remove', 'folder-delete', 'folder-checked', 'tickets', 'document-remove', 'document-delete', 'document-copy', 'document-checked', 'document', 'document-add', 'printer', 'paperclip', 'takeaway-box', 'search', 'monitor', 'attract', 'mobile', 'scissors', 'umbrella', 'headset', 'brush', 'mouse', 'coordinate', 'magic-stick', 'reading', 'data-line', 'data-board', 'pie-chart', 'data-analysis', 'collection-tag', 'film', 'suitcase', 'suitcase-1', 'receiving', 'collection', 'files', 'notebook-1', 'notebook-2', 'toilet-paper', 'office-building', 'school', 'table-lamp', 'house', 'no-smoking', 'smoking', 'shopping-cart-full', 'shopping-cart-1', 'shopping-cart-2', 'shopping-bag-1', 'shopping-bag-2', 'sold-out', 'sell', 'present', 'box', 'bank-card', 'money', 'coin', 'wallet', 'discount', 'price-tag', 'news', 'guide', 'male', 'female', 'thumb', 'cpu', 'link', 'connection', 'open', 'turn-off', 'set-up', 'chat-round', 'chat-line-round', 'chat-square', 'chat-dot-round', 'chat-dot-square', 'chat-line-square', 'message', 'postcard', 'position', 'turn-off-microphone', 'microphone', 'close-notification', 'bangzhu', 'time', 'odometer', 'crop', 'aim', 'switch-button', 'full-screen', 'copy-document', 'mic', 'stopwatch', 'medal-1', 'medal', 'trophy', 'trophy-1', 'first-aid-kit', 'discover', 'place', 'location', 'location-outline', 'location-information', 'add-location', 'delete-location', 'map-location', 'alarm-clock', 'timer', 'watch-1', 'watch', 'lock', 'unlock', 'key', 'service', 'mobile-phone', 'bicycle', 'truck', 'ship', 'basketball', 'football', 'soccer', 'baseball', 'wind-power', 'light-rain', 'lightning', 'heavy-rain', 'sunrise', 'sunrise-1', 'sunset', 'sunny', 'cloudy', 'partly-cloudy', 'cloudy-and-sunny', 'moon', 'moon-night', 'dish', 'dish-1', 'food', 'chicken', 'fork-spoon', 'knife-fork', 'burger', 'tableware', 'sugar', 'dessert', 'ice-cream', 'hot-water', 'water-cup', 'coffee-cup', 'cold-drink', 'goblet', 'goblet-full', 'goblet-square', 'goblet-square-full', 'refrigerator', 'grape', 'watermelon', 'cherry', 'apple', 'pear', 'orange', 'coffee', 'ice-tea', 'ice-drink', 'milk-tea', 'potato-strips', 'lollipop', 'ice-cream-square', 'ice-cream-round']
+
+export default elementIcons
diff --git a/frontend/src/views/icons/index.vue b/frontend/src/views/icons/index.vue
new file mode 100644
index 0000000..4c483db
--- /dev/null
+++ b/frontend/src/views/icons/index.vue
@@ -0,0 +1,101 @@
+<template>
+  <div class="icons-container">
+    <aside>
+      <a href="https://panjiachen.github.io/vue-element-admin-site/guide/advanced/icon.html" target="_blank">Add and use
+      </a>
+    </aside>
+    <el-tabs type="border-card">
+      <el-tab-pane label="Icons">
+        <div class="grid">
+          <div v-for="item of svgIcons" :key="item" @click="handleClipboard(generateIconCode(item),$event)">
+            <el-tooltip placement="top">
+              <div slot="content">
+                {{ generateIconCode(item) }}
+              </div>
+              <div class="icon-item">
+                <svg-icon :icon-class="item" class-name="disabled" />
+                <span>{{ item }}</span>
+              </div>
+            </el-tooltip>
+          </div>
+        </div>
+      </el-tab-pane>
+      <el-tab-pane label="Element-UI Icons">
+        <div class="grid">
+          <div v-for="item of elementIcons" :key="item" @click="handleClipboard(generateElementIconCode(item),$event)">
+            <el-tooltip placement="top">
+              <div slot="content">
+                {{ generateElementIconCode(item) }}
+              </div>
+              <div class="icon-item">
+                <i :class="'el-icon-' + item" />
+                <span>{{ item }}</span>
+              </div>
+            </el-tooltip>
+          </div>
+        </div>
+      </el-tab-pane>
+    </el-tabs>
+  </div>
+</template>
+
+<script>
+import clipboard from '@/utils/clipboard'
+import svgIcons from './svg-icons'
+import elementIcons from './element-icons'
+
+export default {
+  name: 'Icons',
+  data() {
+    return {
+      svgIcons,
+      elementIcons
+    }
+  },
+  methods: {
+    generateIconCode(symbol) {
+      return `<svg-icon icon-class="${symbol}" />`
+    },
+    generateElementIconCode(symbol) {
+      return `<i class="el-icon-${symbol}" />`
+    },
+    handleClipboard(text, event) {
+      clipboard(text, event)
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.icons-container {
+  margin: 10px 20px 0;
+  overflow: hidden;
+
+  .grid {
+    position: relative;
+    display: grid;
+    grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
+  }
+
+  .icon-item {
+    margin: 20px;
+    height: 85px;
+    text-align: center;
+    width: 100px;
+    float: left;
+    font-size: 30px;
+    color: #24292e;
+    cursor: pointer;
+  }
+
+  span {
+    display: block;
+    font-size: 16px;
+    margin-top: 10px;
+  }
+
+  .disabled {
+    pointer-events: none;
+  }
+}
+</style>
diff --git a/frontend/src/views/icons/svg-icons.js b/frontend/src/views/icons/svg-icons.js
new file mode 100644
index 0000000..1e3c66d
--- /dev/null
+++ b/frontend/src/views/icons/svg-icons.js
@@ -0,0 +1,10 @@
+const req = require.context('../../icons/svg', false, /\.svg$/)
+const requireAll = requireContext => requireContext.keys()
+
+const re = /\.\/(.*)\.svg/
+
+const svgIcons = requireAll(req).map(i => {
+  return i.match(re)[1]
+})
+
+export default svgIcons
diff --git a/frontend/src/views/login/auth-redirect.vue b/frontend/src/views/login/auth-redirect.vue
new file mode 100644
index 0000000..7df8934
--- /dev/null
+++ b/frontend/src/views/login/auth-redirect.vue
@@ -0,0 +1,15 @@
+<script>
+export default {
+  name: 'AuthRedirect',
+  created() {
+    const hash = window.location.search.slice(1)
+    if (window.localStorage) {
+      window.localStorage.setItem('x-admin-oauth-code', hash)
+      window.close()
+    }
+  },
+  render: function(h) {
+    return h() // avoid warning message
+  }
+}
+</script>
diff --git a/frontend/src/views/login/components/SocialSignin.vue b/frontend/src/views/login/components/SocialSignin.vue
new file mode 100644
index 0000000..e9bf4f2
--- /dev/null
+++ b/frontend/src/views/login/components/SocialSignin.vue
@@ -0,0 +1,72 @@
+<template>
+  <div class="social-signup-container">
+    <div class="sign-btn" @click="wechatHandleClick('wechat')">
+      <span class="wx-svg-container"><svg-icon icon-class="wechat" class="icon" /></span>
+      WeChat
+    </div>
+    <div class="sign-btn" @click="tencentHandleClick('tencent')">
+      <span class="qq-svg-container"><svg-icon icon-class="qq" class="icon" /></span>
+      QQ
+    </div>
+  </div>
+</template>
+
+<script>
+// import openWindow from '@/utils/open-window'
+
+export default {
+  name: 'SocialSignin',
+  methods: {
+    wechatHandleClick(thirdpart) {
+      alert('ok')
+      // this.$store.commit('SET_AUTH_TYPE', thirdpart)
+      // const appid = 'xxxxx'
+      // const redirect_uri = encodeURIComponent('xxx/redirect?redirect=' + window.location.origin + '/auth-redirect')
+      // const url = 'https://open.weixin.qq.com/connect/qrconnect?appid=' + appid + '&redirect_uri=' + redirect_uri + '&response_type=code&scope=snsapi_login#wechat_redirect'
+      // openWindow(url, thirdpart, 540, 540)
+    },
+    tencentHandleClick(thirdpart) {
+      alert('ok')
+      // this.$store.commit('SET_AUTH_TYPE', thirdpart)
+      // const client_id = 'xxxxx'
+      // const redirect_uri = encodeURIComponent('xxx/redirect?redirect=' + window.location.origin + '/auth-redirect')
+      // const url = 'https://graph.qq.com/oauth2.0/authorize?response_type=code&client_id=' + client_id + '&redirect_uri=' + redirect_uri
+      // openWindow(url, thirdpart, 540, 540)
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+  .social-signup-container {
+    margin: 20px 0;
+    .sign-btn {
+      display: inline-block;
+      cursor: pointer;
+    }
+    .icon {
+      color: #fff;
+      font-size: 24px;
+      margin-top: 8px;
+    }
+    .wx-svg-container,
+    .qq-svg-container {
+      display: inline-block;
+      width: 40px;
+      height: 40px;
+      line-height: 40px;
+      text-align: center;
+      padding-top: 1px;
+      border-radius: 4px;
+      margin-bottom: 20px;
+      margin-right: 5px;
+    }
+    .wx-svg-container {
+      background-color: #24da70;
+    }
+    .qq-svg-container {
+      background-color: #6BA2D6;
+      margin-left: 50px;
+    }
+  }
+</style>
diff --git a/frontend/src/views/login/index.vue b/frontend/src/views/login/index.vue
new file mode 100644
index 0000000..2590640
--- /dev/null
+++ b/frontend/src/views/login/index.vue
@@ -0,0 +1,324 @@
+<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">Login</el-button>
+
+      <div style="position:relative">
+        <div class="tips">
+          <span>Username : admin</span>
+          <span>Password : any</span>
+        </div>
+        <div class="tips">
+          <span style="margin-right:18px;">Username : editor</span>
+          <span>Password : any</span>
+        </div>
+
+        <el-button class="thirdparty-button" type="primary" @click="showDialog=true">
+          Or connect with
+        </el-button>
+      </div>
+    </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('Please enter the correct user name'))
+      } else {
+        callback()
+      }
+    }
+    const validatePassword = (rule, value, callback) => {
+      if (value.length < 6) {
+        callback(new Error('The password can not be less than 6 digits'))
+      } 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>
diff --git a/frontend/src/views/nested/menu1/index.vue b/frontend/src/views/nested/menu1/index.vue
new file mode 100644
index 0000000..30cb670
--- /dev/null
+++ b/frontend/src/views/nested/menu1/index.vue
@@ -0,0 +1,7 @@
+<template>
+  <div style="padding:30px;">
+    <el-alert :closable="false" title="menu 1">
+      <router-view />
+    </el-alert>
+  </div>
+</template>
diff --git a/frontend/src/views/nested/menu1/menu1-1/index.vue b/frontend/src/views/nested/menu1/menu1-1/index.vue
new file mode 100644
index 0000000..27e173a
--- /dev/null
+++ b/frontend/src/views/nested/menu1/menu1-1/index.vue
@@ -0,0 +1,7 @@
+<template>
+  <div style="padding:30px;">
+    <el-alert :closable="false" title="menu 1-1" type="success">
+      <router-view />
+    </el-alert>
+  </div>
+</template>
diff --git a/frontend/src/views/nested/menu1/menu1-2/index.vue b/frontend/src/views/nested/menu1/menu1-2/index.vue
new file mode 100644
index 0000000..0c86276
--- /dev/null
+++ b/frontend/src/views/nested/menu1/menu1-2/index.vue
@@ -0,0 +1,7 @@
+<template>
+  <div style="padding:30px;">
+    <el-alert :closable="false" title="menu 1-2" type="success">
+      <router-view />
+    </el-alert>
+  </div>
+</template>
diff --git a/frontend/src/views/nested/menu1/menu1-2/menu1-2-1/index.vue b/frontend/src/views/nested/menu1/menu1-2/menu1-2-1/index.vue
new file mode 100644
index 0000000..f87d88f
--- /dev/null
+++ b/frontend/src/views/nested/menu1/menu1-2/menu1-2-1/index.vue
@@ -0,0 +1,5 @@
+<template functional>
+  <div style="padding:30px;">
+    <el-alert :closable="false" title="menu 1-2-1" type="warning" />
+  </div>
+</template>
diff --git a/frontend/src/views/nested/menu1/menu1-2/menu1-2-2/index.vue b/frontend/src/views/nested/menu1/menu1-2/menu1-2-2/index.vue
new file mode 100644
index 0000000..d88789f
--- /dev/null
+++ b/frontend/src/views/nested/menu1/menu1-2/menu1-2-2/index.vue
@@ -0,0 +1,5 @@
+<template functional>
+  <div style="padding:30px;">
+    <el-alert :closable="false" title="menu 1-2-2" type="warning" />
+  </div>
+</template>
diff --git a/frontend/src/views/nested/menu1/menu1-3/index.vue b/frontend/src/views/nested/menu1/menu1-3/index.vue
new file mode 100644
index 0000000..f7cd073
--- /dev/null
+++ b/frontend/src/views/nested/menu1/menu1-3/index.vue
@@ -0,0 +1,5 @@
+<template functional>
+  <div style="padding:30px;">
+    <el-alert :closable="false" title="menu 1-3" type="success" />
+  </div>
+</template>
diff --git a/frontend/src/views/nested/menu2/index.vue b/frontend/src/views/nested/menu2/index.vue
new file mode 100644
index 0000000..19dd48f
--- /dev/null
+++ b/frontend/src/views/nested/menu2/index.vue
@@ -0,0 +1,5 @@
+<template>
+  <div style="padding:30px;">
+    <el-alert :closable="false" title="menu 2" />
+  </div>
+</template>
diff --git a/frontend/src/views/pdf/content.js b/frontend/src/views/pdf/content.js
new file mode 100644
index 0000000..e62b1a2
--- /dev/null
+++ b/frontend/src/views/pdf/content.js
@@ -0,0 +1,58 @@
+const title = 'Plans for the Next Iteration of Vue.js'
+
+const content = `<p>Last week at<a href="https://vuejs.london/summary" rel="nofollow">Vue.js London</a>I gave a brief sneak peek of what’s coming in the next major version of Vue. This post provides an in-depth overview of the plan.</p>
+<p><img class=" wscnph" src="https://wpimg.wallstcn.com/b8a1d7be-0b73-41b8-be8c-7c01c93cab66.png" data-wscntype="image" data-wscnh="742" data-wscnw="692" /></p>
+<h3>Why a new majorversion?</h3>
+<p>Vue 2.0 was released<a href="https://medium.com/the-vue-point/vue-2-0-is-here-ef1f26acf4b8" rel="nofollow">exactly two years ago</a>(how time flies!). During this period, the core has remained backwards compatible with five minor releases. We’ve accumulated a number of ideas that would bring improvements, but they were held off because they would result in breaking changes. At the same time, the JavaScript ecosystem and the language itself has been evolving rapidly. There are greatly improved tools that could enhance our workflow, and many new language features that could unlock simpler, more complete, and more efficient solutions to the problems Vue is trying to solve. What’s more exciting is that we are seeing ES2015 support becoming a baseline for all major evergreen browsers. Vue 3.0 aims to leverage these new language features to make Vue core smaller, faster, and more powerful.</p>
+<p>Vue 3.0 is currently in prototyping phase, and we have already implemented a runtime close to feature-parity with 2.x.<strong>Many of the items listed below are either already implemented, or confirmed to be feasible. Ones that are not yet implemented or still in exploration phase are marked with a *.</strong></p>
+<h3>The Details</h3>
+<h4>High-Level APIChanges</h4>
+<blockquote>TL;DR: Everything except render function API and scoped-slots syntax will either remain the same or can be made 2.x compatible via a compatibility build.</blockquote>
+<p>Since it’s a new major, there is going to be some breaking changes. However, we take backwards compatibility seriously, so we want to start communicating these changes as soon as possible. Here’s the currently planned public API changes:</p>
+<ul><li>Template syntax will remain 99% the same. There may be small tweaks in scoped slots syntax, but other than that we have no plans to change anything else for templates.</li><li>3.0 will support class-based components natively, with the aim to provide an API that is pleasant to use in native ES2015 without requiring any transpilation or stage-x features. Most current options will have a reasonable mapping in the class-based API. Stage-x features such as class fields and decorators can still be used optionally to enhance the authoring experience. In addition, the API is designed with TypeScript type inference in mind. The 3.x codebase will itself be written in TypeScript, and providing improved TypeScript support. (That said, usage of TypeScript in an application is still entirely optional.)</li><li>The 2.x object-based component format will still be supported by internally transforming the object to a corresponding class.</li><li>Mixins will still be supported.*</li><li>Top level APIs will likely receive an overhaul to avoid globally mutating the Vue runtime when installing plugins. Instead, plugins will be applied and scoped to a component tree. This will make it easier to test components that rely on specific plugins, and also make it possible to mount multiple Vue applications on the same page with different plugins, but using the same Vue runtime.*</li><li>Functional components can finally be plain functions —however, async components will now need to be explicitly created via a helper function.</li><li>The part that will receive the most changes is the Virtual DOM format used in render functions. We are currently collecting feedback from major library authors and will be sharing more details as we are more confident of the changes, but as long as you don’t heavily rely on hand-written (non-JSX) render functions in your app, upgrading should be a reasonably straightforward process.</li></ul>
+<h4>Source Code Architecture</h4>
+<blockquote>TL;DR: better decoupled internal modules, TypeScript, and a codebase that is easier to contribute to.</blockquote>
+<p>We are re-writing 3.0 from the ground up for a cleaner and more maintainable architecture, in particular trying to make it easier to contribute to. We are breaking some internal functionalities into individual packages in order to isolate the scope of complexity. For example, the observer module will become its own package, with its own public API and tests. Note this does not affect framework-level API— you will not have to manually import individual bits from multiple packages in order to use Vue. Instead, the final Vue package is assembled using these internal packages.</p>
+<p>The codebase is also now written in TypeScript. Although this will make proficiency in TypeScript a pre-requisite for contributing to the new codebase, we believe the type information and IDE support will actually make it easier for a new contributor to make meaningful contributions.</p>
+<p>Decoupling the observer and scheduler into separate packages also allows us to easily experiment with alternative implementations of these parts. For example, we can implement an IE11 compatible observer implementation with the same API, or an alternative scheduler that leverages<code>requestIdleCallback</code>to yield to the browser during long updates.*</p>
+<p><img class=" wscnph" src="https://wpimg.wallstcn.com/4d0b5fb2-d7f9-48fd-8f1b-03362b534dd9.png" data-wscntype="image" data-wscnh="716" data-wscnw="460" /></p>
+<h4>Observation Mechanism</h4>
+<blockquote>TL;DR: more complete, precise, efficient and debuggable reactivity tracking &amp; API for creating observables.</blockquote>
+<p>3.0 will ship with a Proxy-based observer implementation that provides reactivity tracking with full language coverage. This eliminates a number of limitations of Vue 2’s current implementation based on<code>Object.defineProperty</code>:</p>
+<p>The new observer also features the following:</p>
+<p>Easily understand why a component is re-rendering</p>
+<p><img class=" wscnph" src="https://wpimg.wallstcn.com/a0c9d811-1ef9-4628-8976-f7c1aaa66da0.png" data-wscntype="image" data-wscnh="540" data-wscnw="789" /></p>
+<h4>Other Runtime Improvements</h4>
+<blockquote>TL;DR: smaller, faster, tree-shakable features, fragments &amp; portals, custom renderer API.</blockquote>
+<h4>Compiler Improvements*</h4>
+<blockquote>TL;DR: tree-shaking friendly output, more AOT optimizations, parser with better error info and source map support.</blockquote>
+<h4>IE11 Support*</h4>
+<blockquote>TL;DR: it will be supported, but in a separate build with the same reactivity limitations of Vue 2.x.</blockquote>
+<p>The new codebase currently targets evergreen browsers only and assumes baseline native ES2015 support. But alas, we know a lot of our users still need to support IE11 for the foreseeable future. Most of the ES2015 features used can be transpiled / polyfilled for IE11, with the exception for Proxies. Our plan is to implement an alternative observer with the same API, but using the good old ES5<code>Object.defineProperty</code>API. A separate build of Vue 3.x will be distributed using this observer implementation. However, this build will be subject to the same change detection caveats of Vue 2.x and thus not fully compatible with the “modern” build of 3.x. We are aware that this imposes some inconvenience for library authors as they will need to be aware of compatibility for two different builds, but we will make sure to provide clear guidelines on this when we reach that stage.</p>
+<h3>How Do We GetThere</h3>
+<p>First of all, although we are announcing it today, we do not have a definitive timeline yet. What we do know at the moment is the steps we will be taking to get there:</p>
+<h4>1. Internal Feedback for the Runtime Prototype</h4>
+<p>This is the phase we are in right now. Currently, we already have a working runtime prototype that includes the new observer, Virtual DOM and component implementation. We have invited a group of authors of influential community projects to provide feedback for the internal changes, and would like to make sure they are comfortable with the changes before moving forward. We want to ensure that important libraries in the ecosystem will be ready at the same time when we release 3.0, so that users relying on those projects can upgrade easily.</p>
+<h4>2. Public Feedback viaRFCs</h4>
+<p>Once we gain a certain level of confidence in the new design, for each breaking change we will be opening a dedicated RFC issue which includes:</p>
+<p>We will anticipate public feedback from the wider community to help us consolidate these ideas.</p>
+<h4>3. Introduce Compatible Features in 2.x &amp;2.x-next</h4>
+<p>We are not forgetting about 2.x! In fact, we plan to use 2.x to progressively accustom users to the new changes. We will be gradually introducing confirmed API changes into 2.x via opt-in adaptors, and 2.x-next will allow users to try out the new Proxy-based observer.</p>
+<p>The last minor release in 2.x will become LTS and continue to receive bug and security fixes for 18 months when 3.0 is released.</p>
+<h4>4. AlphaPhase</h4>
+<p>Next, we will finish up the compiler and server-side rendering parts of 3.0 and start making alpha releases. These will mostly be for stability testing purposes in small greenfield apps.</p>
+<h4>5. BetaPhase</h4>
+<p>During beta phase, our main goal is updating support libraries and tools like Vue Router, Vuex, Vue CLI, Vue DevTools and make sure they work smoothly with the new core. We will also be working with major library authors from the community to help them get ready for 3.0.</p>
+<h4>6. RCPhase</h4>
+<p>Once we consider the API and codebase stable, we will enter RC phase with API freeze. During this phase we will also work on a “compat build”: a build of 3.0 that includes compatibility layers for 2.x API. This build will also ship with a flag you can turn on to emit deprecation warnings for 2.x API usage in your app. The compat build can be used as a guide to upgrade your app to 3.0.</p>
+<h4>7. IE11build</h4>
+<p>The last task before the final release will be the IE11 compatibility build as mentioned above.</p>
+<h4>8. FinalRelease</h4>
+<p>In all honesty, we don’t know when this will happen yet, but likely in 2019. Again, we care more about shipping something that is solid and stable rather than hitting specific dates. There is a lot of work to be done, but we are excited for what’s coming next!</p>`
+
+const data = {
+  title,
+  content
+}
+
+export default data
diff --git a/frontend/src/views/pdf/download.vue b/frontend/src/views/pdf/download.vue
new file mode 100644
index 0000000..a348c69
--- /dev/null
+++ b/frontend/src/views/pdf/download.vue
@@ -0,0 +1,201 @@
+<template>
+  <div v-loading.fullscreen.lock="fullscreenLoading" class="main-article" element-loading-text="Efforts to generate PDF">
+    <div class="article__heading">
+      <div class="article__heading__title">
+        {{ article.title }}
+      </div>
+    </div>
+    <div style="color: #ccc;">
+      This article is from Evan You on <a target="_blank" href="https://medium.com/the-vue-point/plans-for-the-next-iteration-of-vue-js-777ffea6fabf">medium</a>
+    </div>
+    <div ref="content" class="node-article-content" v-html="article.content" />
+  </div>
+</template>
+
+<script>
+
+export default {
+  data() {
+    return {
+      article: '',
+      fullscreenLoading: true
+    }
+  },
+  mounted() {
+    this.fetchData()
+  },
+  methods: {
+    fetchData() {
+      import('./content.js').then(data => {
+        const { title } = data.default
+        document.title = title
+        this.article = data.default
+        setTimeout(() => {
+          this.fullscreenLoading = false
+          this.$nextTick(() => {
+            window.print()
+          })
+        }, 3000)
+      })
+    }
+  }
+}
+</script>
+
+<style lang="scss">
+@mixin clearfix {
+  &:before {
+    display: table;
+    content: '';
+    clear: both;
+  }
+
+  &:after {
+    display: table;
+    content: '';
+    clear: both;
+  }
+}
+
+.main-article {
+  padding: 20px;
+  margin: 0 auto;
+  display: block;
+  width: 740px;
+  background: #fff;
+}
+
+.article__heading {
+  position: relative;
+  padding: 0 0 20px;
+  overflow: hidden;
+}
+
+.article__heading__title {
+  display: block;
+  display: -webkit-box;
+  -webkit-box-orient: vertical;
+  -webkit-line-clamp: 2;
+  line-clamp: 2;
+  word-wrap: break-word;
+  overflow-wrap: break-word;
+  font-size: 32px;
+  line-height: 48px;
+  font-weight: 600;
+  color: #333;
+  overflow: hidden;
+}
+
+.node-article-content {
+  margin: 20px 0 0;
+  @include clearfix;
+  font-size: 16px;
+  color: #333;
+  letter-spacing: 0.5px;
+  line-height: 28px;
+  margin-bottom: 30px;
+  font-family: medium-content-serif-font, Georgia, Cambria, "Times New Roman", Times, serif;
+
+  &> :last-child {
+    margin-bottom: 0;
+  }
+
+  b,
+  strong {
+    font-weight: inherit;
+    font-weight: bolder;
+  }
+
+  img {
+    max-width: 100%;
+    display: block;
+    margin: 0 auto;
+  }
+
+  p {
+    font-weight: 400;
+    font-style: normal;
+    font-size: 21px;
+    line-height: 1.58;
+    letter-spacing: -.003em;
+
+  }
+
+  ul {
+    margin-bottom: 30px;
+  }
+
+  li {
+    --x-height-multiplier: 0.375;
+    --baseline-multiplier: 0.17;
+
+    letter-spacing: .01rem;
+    font-weight: 400;
+    font-style: normal;
+    font-size: 21px;
+    line-height: 1.58;
+    letter-spacing: -.003em;
+    margin-left: 30px;
+    margin-bottom: 14px;
+  }
+
+  a {
+    text-decoration: none;
+    background-repeat: repeat-x;
+    background-image: linear-gradient(to right, rgba(0, 0, 0, .84) 100%, rgba(0, 0, 0, 0) 0);
+    background-size: 1px 1px;
+    background-position: 0 calc(1em + 1px);
+    padding: 0 6px;
+  }
+
+  code {
+    background: rgba(0, 0, 0, .05);
+    padding: 3px 4px;
+    margin: 0 2px;
+    font-size: 16px;
+    display: inline-block;
+  }
+
+  img {
+    border: 0;
+  }
+
+  /* 解决 IE6-7 图片缩放锯齿问题 */
+  img {
+    -ms-interpolation-mode: bicubic;
+  }
+
+  blockquote {
+    --x-height-multiplier: 0.375;
+    --baseline-multiplier: 0.17;
+    font-family: medium-content-serif-font, Georgia, Cambria, "Times New Roman", Times, serif;
+    letter-spacing: .01rem;
+    font-weight: 400;
+    font-style: italic;
+    font-size: 21px;
+    line-height: 1.58;
+    letter-spacing: -.003em;
+    border-left: 3px solid rgba(0, 0, 0, .84);
+    padding-left: 20px;
+    margin-left: -23px;
+    padding-bottom: 2px;
+  }
+
+  a {
+    text-decoration: none;
+  }
+
+  h2,
+  h3,
+  h4 {
+    font-size: 34px;
+    line-height: 1.15;
+    letter-spacing: -.015em;
+    margin: 53px 0 0;
+  }
+
+  h4 {
+    font-size: 26px;
+  }
+}
+</style>
diff --git a/frontend/src/views/pdf/index.vue b/frontend/src/views/pdf/index.vue
new file mode 100644
index 0000000..86278b3
--- /dev/null
+++ b/frontend/src/views/pdf/index.vue
@@ -0,0 +1,13 @@
+<template>
+  <div class="app-container">
+    <aside style="margin-top:15px;">
+      Here we use window.print() to implement the feature of downloading PDF.
+    </aside>
+    <router-link target="_blank" to="/pdf/download">
+      <el-button type="primary">
+        Click to download PDF
+      </el-button>
+    </router-link>
+  </div>
+</template>
+
diff --git a/frontend/src/views/permission/components/SwitchRoles.vue b/frontend/src/views/permission/components/SwitchRoles.vue
new file mode 100644
index 0000000..9fabbe1
--- /dev/null
+++ b/frontend/src/views/permission/components/SwitchRoles.vue
@@ -0,0 +1,32 @@
+<template>
+  <div>
+    <div style="margin-bottom:15px;">
+      Your roles: {{ roles }}
+    </div>
+    Switch roles:
+    <el-radio-group v-model="switchRoles">
+      <el-radio-button label="editor" />
+      <el-radio-button label="admin" />
+    </el-radio-group>
+  </div>
+</template>
+
+<script>
+export default {
+  computed: {
+    roles() {
+      return this.$store.getters.roles
+    },
+    switchRoles: {
+      get() {
+        return this.roles[0]
+      },
+      set(val) {
+        this.$store.dispatch('user/changeRoles', val).then(() => {
+          this.$emit('change')
+        })
+      }
+    }
+  }
+}
+</script>
diff --git a/frontend/src/views/permission/directive.vue b/frontend/src/views/permission/directive.vue
new file mode 100644
index 0000000..4a49792
--- /dev/null
+++ b/frontend/src/views/permission/directive.vue
@@ -0,0 +1,111 @@
+<template>
+  <div class="app-container">
+    <switch-roles @change="handleRolesChange" />
+    <div :key="key" style="margin-top:30px;">
+      <div>
+        <span v-permission="['admin']" class="permission-alert">
+          Only
+          <el-tag class="permission-tag" size="small">admin</el-tag> can see this
+        </span>
+        <el-tag v-permission="['admin']" class="permission-sourceCode" type="info">
+          v-permission="['admin']"
+        </el-tag>
+      </div>
+
+      <div>
+        <span v-permission="['editor']" class="permission-alert">
+          Only
+          <el-tag class="permission-tag" size="small">editor</el-tag> can see this
+        </span>
+        <el-tag v-permission="['editor']" class="permission-sourceCode" type="info">
+          v-permission="['editor']"
+        </el-tag>
+      </div>
+
+      <div>
+        <span v-permission="['admin','editor']" class="permission-alert">
+          Both
+          <el-tag class="permission-tag" size="small">admin</el-tag> and
+          <el-tag class="permission-tag" size="small">editor</el-tag> can see this
+        </span>
+        <el-tag v-permission="['admin','editor']" class="permission-sourceCode" type="info">
+          v-permission="['admin','editor']"
+        </el-tag>
+      </div>
+    </div>
+
+    <div :key="'checkPermission'+key" style="margin-top:60px;">
+      <aside>
+        In some cases, using v-permission will have no effect. For example: Element-UI's Tab component or el-table-column and other scenes that dynamically render dom. You can only do this with v-if.
+        <br> e.g.
+      </aside>
+
+      <el-tabs type="border-card" style="width:550px;">
+        <el-tab-pane v-if="checkPermission(['admin'])" label="Admin">
+          Admin can see this
+          <el-tag class="permission-sourceCode" type="info">
+            v-if="checkPermission(['admin'])"
+          </el-tag>
+        </el-tab-pane>
+
+        <el-tab-pane v-if="checkPermission(['editor'])" label="Editor">
+          Editor can see this
+          <el-tag class="permission-sourceCode" type="info">
+            v-if="checkPermission(['editor'])"
+          </el-tag>
+        </el-tab-pane>
+
+        <el-tab-pane v-if="checkPermission(['admin','editor'])" label="Admin-OR-Editor">
+          Both admin or editor can see this
+          <el-tag class="permission-sourceCode" type="info">
+            v-if="checkPermission(['admin','editor'])"
+          </el-tag>
+        </el-tab-pane>
+      </el-tabs>
+    </div>
+  </div>
+</template>
+
+<script>
+import permission from '@/directive/permission/index.js' // 权限判断指令
+import checkPermission from '@/utils/permission' // 权限判断函数
+import SwitchRoles from './components/SwitchRoles'
+
+export default {
+  name: 'DirectivePermission',
+  components: { SwitchRoles },
+  directives: { permission },
+  data() {
+    return {
+      key: 1 // 为了能每次切换权限的时候重新初始化指令
+    }
+  },
+  methods: {
+    checkPermission,
+    handleRolesChange() {
+      this.key++
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.app-container {
+  ::v-deep .permission-alert {
+    width: 320px;
+    margin-top: 15px;
+    background-color: #f0f9eb;
+    color: #67c23a;
+    padding: 8px 16px;
+    border-radius: 4px;
+    display: inline-block;
+  }
+  ::v-deep .permission-sourceCode {
+    margin-left: 15px;
+  }
+  ::v-deep .permission-tag {
+    background-color: #ecf5ff;
+  }
+}
+</style>
+
diff --git a/frontend/src/views/permission/page.vue b/frontend/src/views/permission/page.vue
new file mode 100644
index 0000000..5291a78
--- /dev/null
+++ b/frontend/src/views/permission/page.vue
@@ -0,0 +1,19 @@
+<template>
+  <div class="app-container">
+    <switch-roles @change="handleRolesChange" />
+  </div>
+</template>
+
+<script>
+import SwitchRoles from './components/SwitchRoles'
+
+export default {
+  name: 'PagePermission',
+  components: { SwitchRoles },
+  methods: {
+    handleRolesChange() {
+      this.$router.push({ path: '/permission/index?' + +new Date() })
+    }
+  }
+}
+</script>
diff --git a/frontend/src/views/permission/role.vue b/frontend/src/views/permission/role.vue
new file mode 100644
index 0000000..cd7ecd4
--- /dev/null
+++ b/frontend/src/views/permission/role.vue
@@ -0,0 +1,270 @@
+<template>
+  <div class="app-container">
+    <el-button type="primary" @click="handleAddRole">New Role</el-button>
+
+    <el-table :data="rolesList" style="width: 100%;margin-top:30px;" border>
+      <el-table-column align="center" label="Role Key" width="220">
+        <template slot-scope="scope">
+          {{ scope.row.key }}
+        </template>
+      </el-table-column>
+      <el-table-column align="center" label="Role Name" width="220">
+        <template slot-scope="scope">
+          {{ scope.row.name }}
+        </template>
+      </el-table-column>
+      <el-table-column align="header-center" label="Description">
+        <template slot-scope="scope">
+          {{ scope.row.description }}
+        </template>
+      </el-table-column>
+      <el-table-column align="center" label="Operations">
+        <template slot-scope="scope">
+          <el-button type="primary" size="small" @click="handleEdit(scope)">Edit</el-button>
+          <el-button type="danger" size="small" @click="handleDelete(scope)">Delete</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <el-dialog :visible.sync="dialogVisible" :title="dialogType==='edit'?'Edit Role':'New Role'">
+      <el-form :model="role" label-width="80px" label-position="left">
+        <el-form-item label="Name">
+          <el-input v-model="role.name" placeholder="Role Name" />
+        </el-form-item>
+        <el-form-item label="Desc">
+          <el-input
+            v-model="role.description"
+            :autosize="{ minRows: 2, maxRows: 4}"
+            type="textarea"
+            placeholder="Role Description"
+          />
+        </el-form-item>
+        <el-form-item label="Menus">
+          <el-tree
+            ref="tree"
+            :check-strictly="checkStrictly"
+            :data="routesData"
+            :props="defaultProps"
+            show-checkbox
+            node-key="path"
+            class="permission-tree"
+          />
+        </el-form-item>
+      </el-form>
+      <div style="text-align:right;">
+        <el-button type="danger" @click="dialogVisible=false">Cancel</el-button>
+        <el-button type="primary" @click="confirmRole">Confirm</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import path from 'path'
+import { deepClone } from '@/utils'
+import { getRoutes, getRoles, addRole, deleteRole, updateRole } from '@/api/role'
+
+const defaultRole = {
+  key: '',
+  name: '',
+  description: '',
+  routes: []
+}
+
+export default {
+  data() {
+    return {
+      role: Object.assign({}, defaultRole),
+      routes: [],
+      rolesList: [],
+      dialogVisible: false,
+      dialogType: 'new',
+      checkStrictly: false,
+      defaultProps: {
+        children: 'children',
+        label: 'title'
+      }
+    }
+  },
+  computed: {
+    routesData() {
+      return this.routes
+    }
+  },
+  created() {
+    // Mock: get all routes and roles list from server
+    this.getRoutes()
+    this.getRoles()
+  },
+  methods: {
+    async getRoutes() {
+      const res = await getRoutes()
+      this.serviceRoutes = res.data
+      this.routes = this.generateRoutes(res.data)
+    },
+    async getRoles() {
+      const res = await getRoles()
+      this.rolesList = res.data
+    },
+
+    // Reshape the routes structure so that it looks the same as the sidebar
+    generateRoutes(routes, basePath = '/') {
+      const res = []
+
+      for (let route of routes) {
+        // skip some route
+        if (route.hidden) { continue }
+
+        const onlyOneShowingChild = this.onlyOneShowingChild(route.children, route)
+
+        if (route.children && onlyOneShowingChild && !route.alwaysShow) {
+          route = onlyOneShowingChild
+        }
+
+        const data = {
+          path: path.resolve(basePath, route.path),
+          title: route.meta && route.meta.title
+
+        }
+
+        // recursive child routes
+        if (route.children) {
+          data.children = this.generateRoutes(route.children, data.path)
+        }
+        res.push(data)
+      }
+      return res
+    },
+    generateArr(routes) {
+      let data = []
+      routes.forEach(route => {
+        data.push(route)
+        if (route.children) {
+          const temp = this.generateArr(route.children)
+          if (temp.length > 0) {
+            data = [...data, ...temp]
+          }
+        }
+      })
+      return data
+    },
+    handleAddRole() {
+      this.role = Object.assign({}, defaultRole)
+      if (this.$refs.tree) {
+        this.$refs.tree.setCheckedNodes([])
+      }
+      this.dialogType = 'new'
+      this.dialogVisible = true
+    },
+    handleEdit(scope) {
+      this.dialogType = 'edit'
+      this.dialogVisible = true
+      this.checkStrictly = true
+      this.role = deepClone(scope.row)
+      this.$nextTick(() => {
+        const routes = this.generateRoutes(this.role.routes)
+        this.$refs.tree.setCheckedNodes(this.generateArr(routes))
+        // set checked state of a node not affects its father and child nodes
+        this.checkStrictly = false
+      })
+    },
+    handleDelete({ $index, row }) {
+      this.$confirm('Confirm to remove the role?', 'Warning', {
+        confirmButtonText: 'Confirm',
+        cancelButtonText: 'Cancel',
+        type: 'warning'
+      })
+        .then(async() => {
+          await deleteRole(row.key)
+          this.rolesList.splice($index, 1)
+          this.$message({
+            type: 'success',
+            message: 'Delete succed!'
+          })
+        })
+        .catch(err => { console.error(err) })
+    },
+    generateTree(routes, basePath = '/', checkedKeys) {
+      const res = []
+
+      for (const route of routes) {
+        const routePath = path.resolve(basePath, route.path)
+
+        // recursive child routes
+        if (route.children) {
+          route.children = this.generateTree(route.children, routePath, checkedKeys)
+        }
+
+        if (checkedKeys.includes(routePath) || (route.children && route.children.length >= 1)) {
+          res.push(route)
+        }
+      }
+      return res
+    },
+    async confirmRole() {
+      const isEdit = this.dialogType === 'edit'
+
+      const checkedKeys = this.$refs.tree.getCheckedKeys()
+      this.role.routes = this.generateTree(deepClone(this.serviceRoutes), '/', checkedKeys)
+
+      if (isEdit) {
+        await updateRole(this.role.key, this.role)
+        for (let index = 0; index < this.rolesList.length; index++) {
+          if (this.rolesList[index].key === this.role.key) {
+            this.rolesList.splice(index, 1, Object.assign({}, this.role))
+            break
+          }
+        }
+      } else {
+        const { data } = await addRole(this.role)
+        this.role.key = data.key
+        this.rolesList.push(this.role)
+      }
+
+      const { description, key, name } = this.role
+      this.dialogVisible = false
+      this.$notify({
+        title: 'Success',
+        dangerouslyUseHTMLString: true,
+        message: `
+            <div>Role Key: ${key}</div>
+            <div>Role Name: ${name}</div>
+            <div>Description: ${description}</div>
+          `,
+        type: 'success'
+      })
+    },
+    // reference: src/view/layout/components/Sidebar/SidebarItem.vue
+    onlyOneShowingChild(children = [], parent) {
+      let onlyOneChild = null
+      const showingChildren = children.filter(item => !item.hidden)
+
+      // When there is only one child route, the child route is displayed by default
+      if (showingChildren.length === 1) {
+        onlyOneChild = showingChildren[0]
+        onlyOneChild.path = path.resolve(parent.path, onlyOneChild.path)
+        return onlyOneChild
+      }
+
+      // Show parent if there are no child route to display
+      if (showingChildren.length === 0) {
+        onlyOneChild = { ... parent, path: '', noShowingChildren: true }
+        return onlyOneChild
+      }
+
+      return false
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.app-container {
+  .roles-table {
+    margin-top: 30px;
+  }
+  .permission-tree {
+    margin-bottom: 30px;
+  }
+}
+</style>
diff --git a/frontend/src/views/profile/components/Account.vue b/frontend/src/views/profile/components/Account.vue
new file mode 100644
index 0000000..9f2e386
--- /dev/null
+++ b/frontend/src/views/profile/components/Account.vue
@@ -0,0 +1,38 @@
+<template>
+  <el-form>
+    <el-form-item label="Name">
+      <el-input v-model.trim="user.name" />
+    </el-form-item>
+    <el-form-item label="Email">
+      <el-input v-model.trim="user.email" />
+    </el-form-item>
+    <el-form-item>
+      <el-button type="primary" @click="submit">Update</el-button>
+    </el-form-item>
+  </el-form>
+</template>
+
+<script>
+export default {
+  props: {
+    user: {
+      type: Object,
+      default: () => {
+        return {
+          name: '',
+          email: ''
+        }
+      }
+    }
+  },
+  methods: {
+    submit() {
+      this.$message({
+        message: 'User information has been updated successfully',
+        type: 'success',
+        duration: 5 * 1000
+      })
+    }
+  }
+}
+</script>
diff --git a/frontend/src/views/profile/components/Activity.vue b/frontend/src/views/profile/components/Activity.vue
new file mode 100644
index 0000000..dd5db3a
--- /dev/null
+++ b/frontend/src/views/profile/components/Activity.vue
@@ -0,0 +1,185 @@
+<template>
+  <div class="user-activity">
+    <div class="post">
+      <div class="user-block">
+        <img class="img-circle" :src="'https://wpimg.wallstcn.com/57ed425a-c71e-4201-9428-68760c0537c4.jpg'+avatarPrefix">
+        <span class="username text-muted">Iron Man</span>
+        <span class="description">Shared publicly - 7:30 PM today</span>
+      </div>
+      <p>
+        Lorem ipsum represents a long-held tradition for designers,
+        typographers and the like. Some people hate it and argue for
+        its demise, but others ignore the hate as they create awesome
+        tools to help create filler text for everyone from bacon lovers
+        to Charlie Sheen fans.
+      </p>
+      <ul class="list-inline">
+        <li>
+          <span class="link-black text-sm">
+            <i class="el-icon-share" />
+            Share
+          </span>
+        </li>
+        <li>
+          <span class="link-black text-sm">
+            <svg-icon icon-class="like" />
+            Like
+          </span>
+        </li>
+      </ul>
+    </div>
+    <div class="post">
+      <div class="user-block">
+        <img class="img-circle" :src="'https://wpimg.wallstcn.com/9e2a5d0a-bd5b-457f-ac8e-86554616c87b.jpg'+avatarPrefix">
+        <span class="username text-muted">Captain American</span>
+        <span class="description">Sent you a message - yesterday</span>
+      </div>
+      <p>
+        Lorem ipsum represents a long-held tradition for designers,
+        typographers and the like. Some people hate it and argue for
+        its demise, but others ignore the hate as they create awesome
+        tools to help create filler text for everyone from bacon lovers
+        to Charlie Sheen fans.
+      </p>
+      <ul class="list-inline">
+        <li>
+          <span class="link-black text-sm">
+            <i class="el-icon-share" />
+            Share
+          </span>
+        </li>
+        <li>
+          <span class="link-black text-sm">
+            <svg-icon icon-class="like" />
+            Like
+          </span>
+        </li>
+      </ul>
+    </div>
+    <div class="post">
+      <div class="user-block">
+        <img class="img-circle" :src="'https://wpimg.wallstcn.com/fb57f689-e1ab-443c-af12-8d4066e202e2.jpg'+avatarPrefix">
+        <span class="username">Spider Man</span>
+        <span class="description">Posted 4 photos - 2 days ago</span>
+      </div>
+      <div class="user-images">
+        <el-carousel :interval="6000" type="card" height="220px">
+          <el-carousel-item v-for="item in carouselImages" :key="item">
+            <img :src="item+carouselPrefix" class="image">
+          </el-carousel-item>
+        </el-carousel>
+      </div>
+      <ul class="list-inline">
+        <li><span class="link-black text-sm"><i class="el-icon-share" /> Share</span></li>
+        <li>
+          <span class="link-black text-sm">
+            <svg-icon icon-class="like" /> Like</span>
+        </li>
+      </ul>
+    </div>
+  </div>
+</template>
+
+<script>
+const avatarPrefix = '?imageView2/1/w/80/h/80'
+const carouselPrefix = '?imageView2/2/h/440'
+
+export default {
+  data() {
+    return {
+      carouselImages: [
+        'https://wpimg.wallstcn.com/9679ffb0-9e0b-4451-9916-e21992218054.jpg',
+        'https://wpimg.wallstcn.com/bcce3734-0837-4b9f-9261-351ef384f75a.jpg',
+        'https://wpimg.wallstcn.com/d1d7b033-d75e-4cd6-ae39-fcd5f1c0a7c5.jpg',
+        'https://wpimg.wallstcn.com/50530061-851b-4ca5-9dc5-2fead928a939.jpg'
+      ],
+      avatarPrefix,
+      carouselPrefix
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.user-activity {
+  .user-block {
+
+    .username,
+    .description {
+      display: block;
+      margin-left: 50px;
+      padding: 2px 0;
+    }
+
+    .username{
+      font-size: 16px;
+      color: #000;
+    }
+
+    :after {
+      clear: both;
+    }
+
+    .img-circle {
+      border-radius: 50%;
+      width: 40px;
+      height: 40px;
+      float: left;
+    }
+
+    span {
+      font-weight: 500;
+      font-size: 12px;
+    }
+  }
+
+  .post {
+    font-size: 14px;
+    border-bottom: 1px solid #d2d6de;
+    margin-bottom: 15px;
+    padding-bottom: 15px;
+    color: #666;
+
+    .image {
+      width: 100%;
+      height: 100%;
+
+    }
+
+    .user-images {
+      padding-top: 20px;
+    }
+  }
+
+  .list-inline {
+    padding-left: 0;
+    margin-left: -5px;
+    list-style: none;
+
+    li {
+      display: inline-block;
+      padding-right: 5px;
+      padding-left: 5px;
+      font-size: 13px;
+    }
+
+    .link-black {
+
+      &:hover,
+      &:focus {
+        color: #999;
+      }
+    }
+  }
+
+}
+
+.box-center {
+  margin: 0 auto;
+  display: table;
+}
+
+.text-muted {
+  color: #777;
+}
+</style>
diff --git a/frontend/src/views/profile/components/Timeline.vue b/frontend/src/views/profile/components/Timeline.vue
new file mode 100644
index 0000000..ba90b3d
--- /dev/null
+++ b/frontend/src/views/profile/components/Timeline.vue
@@ -0,0 +1,43 @@
+<template>
+  <div class="block">
+    <el-timeline>
+      <el-timeline-item v-for="(item,index) of timeline" :key="index" :timestamp="item.timestamp" placement="top">
+        <el-card>
+          <h4>{{ item.title }}</h4>
+          <p>{{ item.content }}</p>
+        </el-card>
+      </el-timeline-item>
+    </el-timeline>
+  </div>
+</template>
+
+<script>
+export default {
+  data() {
+    return {
+      timeline: [
+        {
+          timestamp: '2019/4/20',
+          title: 'Update Github template',
+          content: 'PanJiaChen committed 2019/4/20 20:46'
+        },
+        {
+          timestamp: '2019/4/21',
+          title: 'Update Github template',
+          content: 'PanJiaChen committed 2019/4/21 20:46'
+        },
+        {
+          timestamp: '2019/4/22',
+          title: 'Build Template',
+          content: 'PanJiaChen committed 2019/4/22 20:46'
+        },
+        {
+          timestamp: '2019/4/23',
+          title: 'Release New Version',
+          content: 'PanJiaChen committed 2019/4/23 20:46'
+        }
+      ]
+    }
+  }
+}
+</script>
diff --git a/frontend/src/views/profile/components/UserCard.vue b/frontend/src/views/profile/components/UserCard.vue
new file mode 100644
index 0000000..2476f04
--- /dev/null
+++ b/frontend/src/views/profile/components/UserCard.vue
@@ -0,0 +1,134 @@
+<template>
+  <el-card style="margin-bottom:20px;">
+    <div slot="header" class="clearfix">
+      <span>About me</span>
+    </div>
+
+    <div class="user-profile">
+      <div class="box-center">
+        <pan-thumb :image="user.avatar" :height="'100px'" :width="'100px'" :hoverable="false">
+          <div>Hello</div>
+          {{ user.role }}
+        </pan-thumb>
+      </div>
+      <div class="box-center">
+        <div class="user-name text-center">{{ user.name }}</div>
+        <div class="user-role text-center text-muted">{{ user.role | uppercaseFirst }}</div>
+      </div>
+    </div>
+
+    <div class="user-bio">
+      <div class="user-education user-bio-section">
+        <div class="user-bio-section-header"><svg-icon icon-class="education" /><span>Education</span></div>
+        <div class="user-bio-section-body">
+          <div class="text-muted">
+            JS in Computer Science from the University of Technology
+          </div>
+        </div>
+      </div>
+
+      <div class="user-skills user-bio-section">
+        <div class="user-bio-section-header"><svg-icon icon-class="skill" /><span>Skills</span></div>
+        <div class="user-bio-section-body">
+          <div class="progress-item">
+            <span>Vue</span>
+            <el-progress :percentage="70" />
+          </div>
+          <div class="progress-item">
+            <span>JavaScript</span>
+            <el-progress :percentage="18" />
+          </div>
+          <div class="progress-item">
+            <span>Css</span>
+            <el-progress :percentage="12" />
+          </div>
+          <div class="progress-item">
+            <span>ESLint</span>
+            <el-progress :percentage="100" status="success" />
+          </div>
+        </div>
+      </div>
+    </div>
+  </el-card>
+</template>
+
+<script>
+import PanThumb from '@/components/PanThumb'
+
+export default {
+  components: { PanThumb },
+  props: {
+    user: {
+      type: Object,
+      default: () => {
+        return {
+          name: '',
+          email: '',
+          avatar: '',
+          role: ''
+        }
+      }
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.box-center {
+  margin: 0 auto;
+  display: table;
+}
+
+.text-muted {
+  color: #777;
+}
+
+.user-profile {
+  .user-name {
+    font-weight: bold;
+  }
+
+  .box-center {
+    padding-top: 10px;
+  }
+
+  .user-role {
+    padding-top: 10px;
+    font-weight: 400;
+    font-size: 14px;
+  }
+
+  .box-social {
+    padding-top: 30px;
+
+    .el-table {
+      border-top: 1px solid #dfe6ec;
+    }
+  }
+
+  .user-follow {
+    padding-top: 20px;
+  }
+}
+
+.user-bio {
+  margin-top: 20px;
+  color: #606266;
+
+  span {
+    padding-left: 4px;
+  }
+
+  .user-bio-section {
+    font-size: 14px;
+    padding: 15px 0;
+
+    .user-bio-section-header {
+      border-bottom: 1px solid #dfe6ec;
+      padding-bottom: 10px;
+      margin-bottom: 10px;
+      font-weight: bold;
+    }
+  }
+}
+</style>
diff --git a/frontend/src/views/profile/index.vue b/frontend/src/views/profile/index.vue
new file mode 100644
index 0000000..87e4f94
--- /dev/null
+++ b/frontend/src/views/profile/index.vue
@@ -0,0 +1,68 @@
+<template>
+  <div class="app-container">
+    <div v-if="user">
+      <el-row :gutter="20">
+
+        <el-col :span="6" :xs="24">
+          <user-card :user="user" />
+        </el-col>
+
+        <el-col :span="18" :xs="24">
+          <el-card>
+            <el-tabs v-model="activeTab">
+              <el-tab-pane label="Activity" name="activity">
+                <activity />
+              </el-tab-pane>
+              <el-tab-pane label="Timeline" name="timeline">
+                <timeline />
+              </el-tab-pane>
+              <el-tab-pane label="Account" name="account">
+                <account :user="user" />
+              </el-tab-pane>
+            </el-tabs>
+          </el-card>
+        </el-col>
+
+      </el-row>
+    </div>
+  </div>
+</template>
+
+<script>
+import { mapGetters } from 'vuex'
+import UserCard from './components/UserCard'
+import Activity from './components/Activity'
+import Timeline from './components/Timeline'
+import Account from './components/Account'
+
+export default {
+  name: 'Profile',
+  components: { UserCard, Activity, Timeline, Account },
+  data() {
+    return {
+      user: {},
+      activeTab: 'activity'
+    }
+  },
+  computed: {
+    ...mapGetters([
+      'name',
+      'avatar',
+      'roles'
+    ])
+  },
+  created() {
+    this.getUser()
+  },
+  methods: {
+    getUser() {
+      this.user = {
+        name: this.name,
+        role: this.roles.join(' | '),
+        email: 'admin@test.com',
+        avatar: this.avatar
+      }
+    }
+  }
+}
+</script>
diff --git a/frontend/src/views/qiniu/upload.vue b/frontend/src/views/qiniu/upload.vue
new file mode 100644
index 0000000..9dc9aed
--- /dev/null
+++ b/frontend/src/views/qiniu/upload.vue
@@ -0,0 +1,41 @@
+<template>
+  <el-upload :data="dataObj" :multiple="true" :before-upload="beforeUpload" action="https://upload.qbox.me" drag>
+    <i class="el-icon-upload" />
+    <div class="el-upload__text">
+      将文件拖到此处,或<em>点击上传</em>
+    </div>
+  </el-upload>
+</template>
+
+<script>
+import { getToken } from '@/api/qiniu'
+// 获取七牛token 后端通过Access Key,Secret Key,bucket等生成token
+// 七牛官方sdk https://developer.qiniu.com/sdk#official-sdk
+
+export default {
+  data() {
+    return {
+      dataObj: { token: '', key: '' },
+      image_uri: [],
+      fileList: []
+    }
+  },
+  methods: {
+    beforeUpload() {
+      const _self = this
+      return new Promise((resolve, reject) => {
+        getToken().then(response => {
+          const key = response.data.qiniu_key
+          const token = response.data.qiniu_token
+          _self._data.dataObj.token = token
+          _self._data.dataObj.key = key
+          resolve(true)
+        }).catch(err => {
+          console.log(err)
+          reject(false)
+        })
+      })
+    }
+  }
+}
+</script>
diff --git a/frontend/src/views/redirect/index.vue b/frontend/src/views/redirect/index.vue
new file mode 100644
index 0000000..db4c1d6
--- /dev/null
+++ b/frontend/src/views/redirect/index.vue
@@ -0,0 +1,12 @@
+<script>
+export default {
+  created() {
+    const { params, query } = this.$route
+    const { path } = params
+    this.$router.replace({ path: '/' + path, query })
+  },
+  render: function(h) {
+    return h() // avoid warning message
+  }
+}
+</script>
diff --git a/frontend/src/views/tab/components/TabPane.vue b/frontend/src/views/tab/components/TabPane.vue
new file mode 100644
index 0000000..3fb1439
--- /dev/null
+++ b/frontend/src/views/tab/components/TabPane.vue
@@ -0,0 +1,103 @@
+<template>
+  <el-table :data="list" border fit highlight-current-row style="width: 100%">
+    <el-table-column
+      v-loading="loading"
+      align="center"
+      label="ID"
+      width="65"
+      element-loading-text="请给我点时间!"
+    >
+      <template slot-scope="scope">
+        <span>{{ scope.row.id }}</span>
+      </template>
+    </el-table-column>
+
+    <el-table-column width="180px" align="center" label="Date">
+      <template slot-scope="scope">
+        <span>{{ scope.row.timestamp | parseTime('{y}-{m}-{d} {h}:{i}') }}</span>
+      </template>
+    </el-table-column>
+
+    <el-table-column min-width="300px" label="Title">
+      <template slot-scope="{row}">
+        <span>{{ row.title }}</span>
+        <el-tag>{{ row.type }}</el-tag>
+      </template>
+    </el-table-column>
+
+    <el-table-column width="110px" align="center" label="Author">
+      <template slot-scope="scope">
+        <span>{{ scope.row.author }}</span>
+      </template>
+    </el-table-column>
+
+    <el-table-column width="120px" label="Importance">
+      <template slot-scope="scope">
+        <svg-icon v-for="n in +scope.row.importance" :key="n" icon-class="star" />
+      </template>
+    </el-table-column>
+
+    <el-table-column align="center" label="Readings" width="95">
+      <template slot-scope="scope">
+        <span>{{ scope.row.pageviews }}</span>
+      </template>
+    </el-table-column>
+
+    <el-table-column class-name="status-col" label="Status" width="110">
+      <template slot-scope="{row}">
+        <el-tag :type="row.status | statusFilter">
+          {{ row.status }}
+        </el-tag>
+      </template>
+    </el-table-column>
+  </el-table>
+</template>
+
+<script>
+import { fetchList } from '@/api/article'
+
+export default {
+  filters: {
+    statusFilter(status) {
+      const statusMap = {
+        published: 'success',
+        draft: 'info',
+        deleted: 'danger'
+      }
+      return statusMap[status]
+    }
+  },
+  props: {
+    type: {
+      type: String,
+      default: 'CN'
+    }
+  },
+  data() {
+    return {
+      list: null,
+      listQuery: {
+        page: 1,
+        limit: 5,
+        type: this.type,
+        sort: '+id'
+      },
+      loading: false
+    }
+  },
+  created() {
+    this.getList()
+  },
+  methods: {
+    getList() {
+      this.loading = true
+      this.$emit('create') // for test
+      fetchList(this.listQuery).then(response => {
+        this.list = response.data.items
+        this.loading = false
+      })
+    }
+  }
+}
+</script>
+
diff --git a/frontend/src/views/tab/index.vue b/frontend/src/views/tab/index.vue
new file mode 100644
index 0000000..2a35fa5
--- /dev/null
+++ b/frontend/src/views/tab/index.vue
@@ -0,0 +1,57 @@
+<template>
+  <div class="tab-container">
+    <el-tag>mounted times :{{ createdTimes }}</el-tag>
+    <el-alert :closable="false" style="width:200px;display:inline-block;vertical-align: middle;margin-left:30px;" title="Tab with keep-alive" type="success" />
+    <el-tabs v-model="activeName" style="margin-top:15px;" type="border-card">
+      <el-tab-pane v-for="item in tabMapOptions" :key="item.key" :label="item.label" :name="item.key">
+        <keep-alive>
+          <tab-pane v-if="activeName==item.key" :type="item.key" @create="showCreatedTimes" />
+        </keep-alive>
+      </el-tab-pane>
+    </el-tabs>
+  </div>
+</template>
+
+<script>
+import TabPane from './components/TabPane'
+
+export default {
+  name: 'Tab',
+  components: { TabPane },
+  data() {
+    return {
+      tabMapOptions: [
+        { label: 'China', key: 'CN' },
+        { label: 'USA', key: 'US' },
+        { label: 'Japan', key: 'JP' },
+        { label: 'Eurozone', key: 'EU' }
+      ],
+      activeName: 'CN',
+      createdTimes: 0
+    }
+  },
+  watch: {
+    activeName(val) {
+      this.$router.push(`${this.$route.path}?tab=${val}`)
+    }
+  },
+  created() {
+    // init the default selected tab
+    const tab = this.$route.query.tab
+    if (tab) {
+      this.activeName = tab
+    }
+  },
+  methods: {
+    showCreatedTimes() {
+      this.createdTimes = this.createdTimes + 1
+    }
+  }
+}
+</script>
+
+<style scoped>
+  .tab-container {
+    margin: 30px;
+  }
+</style>
diff --git a/frontend/src/views/table/complex-table.vue b/frontend/src/views/table/complex-table.vue
new file mode 100644
index 0000000..295c5fc
--- /dev/null
+++ b/frontend/src/views/table/complex-table.vue
@@ -0,0 +1,379 @@
+<template>
+  <div class="app-container">
+    <div class="filter-container">
+      <el-input v-model="listQuery.title" placeholder="Title" style="width: 200px;" class="filter-item" @keyup.enter.native="handleFilter" />
+      <el-select v-model="listQuery.importance" placeholder="Imp" clearable style="width: 90px" class="filter-item">
+        <el-option v-for="item in importanceOptions" :key="item" :label="item" :value="item" />
+      </el-select>
+      <el-select v-model="listQuery.type" placeholder="Type" clearable class="filter-item" style="width: 130px">
+        <el-option v-for="item in calendarTypeOptions" :key="item.key" :label="item.display_name+'('+item.key+')'" :value="item.key" />
+      </el-select>
+      <el-select v-model="listQuery.sort" style="width: 140px" class="filter-item" @change="handleFilter">
+        <el-option v-for="item in sortOptions" :key="item.key" :label="item.label" :value="item.key" />
+      </el-select>
+      <el-button v-waves class="filter-item" type="primary" icon="el-icon-search" @click="handleFilter">
+        Search
+      </el-button>
+      <el-button class="filter-item" style="margin-left: 10px;" type="primary" icon="el-icon-edit" @click="handleCreate">
+        Add
+      </el-button>
+      <el-button v-waves :loading="downloadLoading" class="filter-item" type="primary" icon="el-icon-download" @click="handleDownload">
+        Export
+      </el-button>
+      <el-checkbox v-model="showReviewer" class="filter-item" style="margin-left:15px;" @change="tableKey=tableKey+1">
+        reviewer
+      </el-checkbox>
+    </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="ID" prop="id" sortable="custom" align="center" width="80" :class-name="getSortClass('id')">
+        <template slot-scope="{row}">
+          <span>{{ row.id }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="Date" width="150px" align="center">
+        <template slot-scope="{row}">
+          <span>{{ row.timestamp | parseTime('{y}-{m}-{d} {h}:{i}') }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="Title" min-width="150px">
+        <template slot-scope="{row}">
+          <span class="link-type" @click="handleUpdate(row)">{{ row.title }}</span>
+          <el-tag>{{ row.type | typeFilter }}</el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column label="Author" width="110px" align="center">
+        <template slot-scope="{row}">
+          <span>{{ row.author }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column v-if="showReviewer" label="Reviewer" width="110px" align="center">
+        <template slot-scope="{row}">
+          <span style="color:red;">{{ row.reviewer }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="Imp" width="80px">
+        <template slot-scope="{row}">
+          <svg-icon v-for="n in + row.importance" :key="n" icon-class="star" class="meta-item__icon" />
+        </template>
+      </el-table-column>
+      <el-table-column label="Readings" align="center" width="95">
+        <template slot-scope="{row}">
+          <span v-if="row.pageviews" class="link-type" @click="handleFetchPv(row.pageviews)">{{ row.pageviews }}</span>
+          <span v-else>0</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="Status" class-name="status-col" width="100">
+        <template slot-scope="{row}">
+          <el-tag :type="row.status | statusFilter">
+            {{ row.status }}
+          </el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column label="Actions" align="center" width="230" class-name="small-padding fixed-width">
+        <template slot-scope="{row,$index}">
+          <el-button type="primary" size="mini" @click="handleUpdate(row)">
+            Edit
+          </el-button>
+          <el-button v-if="row.status!='published'" size="mini" type="success" @click="handleModifyStatus(row,'published')">
+            Publish
+          </el-button>
+          <el-button v-if="row.status!='draft'" size="mini" @click="handleModifyStatus(row,'draft')">
+            Draft
+          </el-button>
+          <el-button v-if="row.status!='deleted'" size="mini" type="danger" @click="handleDelete(row,$index)">
+            Delete
+          </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" />
+
+    <el-dialog :title="textMap[dialogStatus]" :visible.sync="dialogFormVisible">
+      <el-form ref="dataForm" :rules="rules" :model="temp" label-position="left" label-width="70px" style="width: 400px; margin-left:50px;">
+        <el-form-item label="Type" prop="type">
+          <el-select v-model="temp.type" class="filter-item" placeholder="Please select">
+            <el-option v-for="item in calendarTypeOptions" :key="item.key" :label="item.display_name" :value="item.key" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="Date" prop="timestamp">
+          <el-date-picker v-model="temp.timestamp" type="datetime" placeholder="Please pick a date" />
+        </el-form-item>
+        <el-form-item label="Title" prop="title">
+          <el-input v-model="temp.title" />
+        </el-form-item>
+        <el-form-item label="Status">
+          <el-select v-model="temp.status" class="filter-item" placeholder="Please select">
+            <el-option v-for="item in statusOptions" :key="item" :label="item" :value="item" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="Imp">
+          <el-rate v-model="temp.importance" :colors="['#99A9BF', '#F7BA2A', '#FF9900']" :max="3" style="margin-top:8px;" />
+        </el-form-item>
+        <el-form-item label="Remark">
+          <el-input v-model="temp.remark" :autosize="{ minRows: 2, maxRows: 4}" type="textarea" placeholder="Please input" />
+        </el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button @click="dialogFormVisible = false">
+          Cancel
+        </el-button>
+        <el-button type="primary" @click="dialogStatus==='create'?createData():updateData()">
+          Confirm
+        </el-button>
+      </div>
+    </el-dialog>
+
+    <el-dialog :visible.sync="dialogPvVisible" title="Reading statistics">
+      <el-table :data="pvData" border fit highlight-current-row style="width: 100%">
+        <el-table-column prop="key" label="Channel" />
+        <el-table-column prop="pv" label="Pv" />
+      </el-table>
+      <span slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="dialogPvVisible = false">Confirm</el-button>
+      </span>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import { fetchList, fetchPv, createArticle, updateArticle } from '@/api/article'
+import waves from '@/directive/waves' // waves directive
+import { parseTime } from '@/utils'
+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' }
+]
+
+// arr to obj, such as { CN : "China", US : "USA" }
+const calendarTypeKeyValue = calendarTypeOptions.reduce((acc, cur) => {
+  acc[cur.key] = cur.display_name
+  return acc
+}, {})
+
+export default {
+  name: 'ComplexTable',
+  components: { Pagination },
+  directives: { waves },
+  filters: {
+    statusFilter(status) {
+      const statusMap = {
+        published: 'success',
+        draft: 'info',
+        deleted: 'danger'
+      }
+      return statusMap[status]
+    },
+    typeFilter(type) {
+      return calendarTypeKeyValue[type]
+    }
+  },
+  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,
+      sortOptions: [{ label: 'ID Ascending', key: '+id' }, { label: 'ID Descending', key: '-id' }],
+      statusOptions: ['published', 'draft', 'deleted'],
+      showReviewer: false,
+      temp: {
+        id: undefined,
+        importance: 1,
+        remark: '',
+        timestamp: new Date(),
+        title: '',
+        type: '',
+        status: 'published'
+      },
+      dialogFormVisible: false,
+      dialogStatus: '',
+      textMap: {
+        update: 'Edit',
+        create: 'Create'
+      },
+      dialogPvVisible: false,
+      pvData: [],
+      rules: {
+        type: [{ required: true, message: 'type is required', trigger: 'change' }],
+        timestamp: [{ type: 'date', required: true, message: 'timestamp is required', trigger: 'change' }],
+        title: [{ required: true, message: 'title is required', trigger: 'blur' }]
+      },
+      downloadLoading: false
+    }
+  },
+  created() {
+    this.getList()
+  },
+  methods: {
+    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()
+    },
+    handleModifyStatus(row, status) {
+      this.$message({
+        message: '操作Success',
+        type: 'success'
+      })
+      row.status = status
+    },
+    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()
+    },
+    resetTemp() {
+      this.temp = {
+        id: undefined,
+        importance: 1,
+        remark: '',
+        timestamp: new Date(),
+        title: '',
+        status: 'published',
+        type: ''
+      }
+    },
+    handleCreate() {
+      this.resetTemp()
+      this.dialogStatus = 'create'
+      this.dialogFormVisible = true
+      this.$nextTick(() => {
+        this.$refs['dataForm'].clearValidate()
+      })
+    },
+    createData() {
+      this.$refs['dataForm'].validate((valid) => {
+        if (valid) {
+          this.temp.id = parseInt(Math.random() * 100) + 1024 // mock a id
+          this.temp.author = 'vue-element-admin'
+          createArticle(this.temp).then(() => {
+            this.list.unshift(this.temp)
+            this.dialogFormVisible = false
+            this.$notify({
+              title: 'Success',
+              message: 'Created Successfully',
+              type: 'success',
+              duration: 2000
+            })
+          })
+        }
+      })
+    },
+    handleUpdate(row) {
+      this.temp = Object.assign({}, row) // copy obj
+      this.temp.timestamp = new Date(this.temp.timestamp)
+      this.dialogStatus = 'update'
+      this.dialogFormVisible = true
+      this.$nextTick(() => {
+        this.$refs['dataForm'].clearValidate()
+      })
+    },
+    updateData() {
+      this.$refs['dataForm'].validate((valid) => {
+        if (valid) {
+          const tempData = Object.assign({}, this.temp)
+          tempData.timestamp = +new Date(tempData.timestamp) // change Thu Nov 30 2017 16:41:05 GMT+0800 (CST) to 1512031311464
+          updateArticle(tempData).then(() => {
+            const index = this.list.findIndex(v => v.id === this.temp.id)
+            this.list.splice(index, 1, this.temp)
+            this.dialogFormVisible = false
+            this.$notify({
+              title: 'Success',
+              message: 'Update Successfully',
+              type: 'success',
+              duration: 2000
+            })
+          })
+        }
+      })
+    },
+    handleDelete(row, index) {
+      this.$notify({
+        title: 'Success',
+        message: 'Delete Successfully',
+        type: 'success',
+        duration: 2000
+      })
+      this.list.splice(index, 1)
+    },
+    handleFetchPv(pv) {
+      fetchPv(pv).then(response => {
+        this.pvData = response.data.pvData
+        this.dialogPvVisible = true
+      })
+    },
+    handleDownload() {
+      this.downloadLoading = true
+      import('@/vendor/Export2Excel').then(excel => {
+        const tHeader = ['timestamp', 'title', 'type', 'importance', 'status']
+        const filterVal = ['timestamp', 'title', 'type', 'importance', 'status']
+        const data = this.formatJson(filterVal)
+        excel.export_json_to_excel({
+          header: tHeader,
+          data,
+          filename: 'table-list'
+        })
+        this.downloadLoading = false
+      })
+    },
+    formatJson(filterVal) {
+      return this.list.map(v => filterVal.map(j => {
+        if (j === 'timestamp') {
+          return parseTime(v[j])
+        } else {
+          return v[j]
+        }
+      }))
+    },
+    getSortClass: function(key) {
+      const sort = this.listQuery.sort
+      return sort === `+${key}` ? 'ascending' : 'descending'
+    }
+  }
+}
+</script>
diff --git a/frontend/src/views/table/drag-table.vue b/frontend/src/views/table/drag-table.vue
new file mode 100644
index 0000000..3b5c890
--- /dev/null
+++ b/frontend/src/views/table/drag-table.vue
@@ -0,0 +1,153 @@
+<template>
+  <div class="app-container">
+    <!-- Note that row-key is necessary to get a correct row order. -->
+    <el-table ref="dragTable" v-loading="listLoading" :data="list" row-key="id" border fit highlight-current-row style="width: 100%">
+      <el-table-column align="center" label="ID" width="65">
+        <template slot-scope="{row}">
+          <span>{{ row.id }}</span>
+        </template>
+      </el-table-column>
+
+      <el-table-column width="180px" align="center" label="Date">
+        <template slot-scope="{row}">
+          <span>{{ row.timestamp | parseTime('{y}-{m}-{d} {h}:{i}') }}</span>
+        </template>
+      </el-table-column>
+
+      <el-table-column min-width="300px" label="Title">
+        <template slot-scope="{row}">
+          <span>{{ row.title }}</span>
+        </template>
+      </el-table-column>
+
+      <el-table-column width="110px" align="center" label="Author">
+        <template slot-scope="{row}">
+          <span>{{ row.author }}</span>
+        </template>
+      </el-table-column>
+
+      <el-table-column width="100px" label="Importance">
+        <template slot-scope="{row}">
+          <svg-icon v-for="n in + row.importance" :key="n" icon-class="star" class="icon-star" />
+        </template>
+      </el-table-column>
+
+      <el-table-column align="center" label="Readings" width="95">
+        <template slot-scope="{row}">
+          <span>{{ row.pageviews }}</span>
+        </template>
+      </el-table-column>
+
+      <el-table-column class-name="status-col" label="Status" width="110">
+        <template slot-scope="{row}">
+          <el-tag :type="row.status | statusFilter">
+            {{ row.status }}
+          </el-tag>
+        </template>
+      </el-table-column>
+
+      <el-table-column align="center" label="Drag" width="80">
+        <template slot-scope="{}">
+          <svg-icon class="drag-handler" icon-class="drag" />
+        </template>
+      </el-table-column>
+    </el-table>
+    <div class="show-d">
+      <el-tag>The default order :</el-tag> {{ oldList }}
+    </div>
+    <div class="show-d">
+      <el-tag>The after dragging order :</el-tag> {{ newList }}
+    </div>
+  </div>
+</template>
+
+<script>
+import { fetchList } from '@/api/article'
+import Sortable from 'sortablejs'
+
+export default {
+  name: 'DragTable',
+  filters: {
+    statusFilter(status) {
+      const statusMap = {
+        published: 'success',
+        draft: 'info',
+        deleted: 'danger'
+      }
+      return statusMap[status]
+    }
+  },
+  data() {
+    return {
+      list: null,
+      total: null,
+      listLoading: true,
+      listQuery: {
+        page: 1,
+        limit: 10
+      },
+      sortable: null,
+      oldList: [],
+      newList: []
+    }
+  },
+  created() {
+    this.getList()
+  },
+  methods: {
+    async getList() {
+      this.listLoading = true
+      const { data } = await fetchList(this.listQuery)
+      this.list = data.items
+      this.total = data.total
+      this.listLoading = false
+      this.oldList = this.list.map(v => v.id)
+      this.newList = this.oldList.slice()
+      this.$nextTick(() => {
+        this.setSort()
+      })
+    },
+    setSort() {
+      const el = this.$refs.dragTable.$el.querySelectorAll('.el-table__body-wrapper > table > tbody')[0]
+      this.sortable = Sortable.create(el, {
+        ghostClass: 'sortable-ghost', // Class name for the drop placeholder,
+        setData: function(dataTransfer) {
+          // to avoid Firefox bug
+          // Detail see : https://github.com/RubaXa/Sortable/issues/1012
+          dataTransfer.setData('Text', '')
+        },
+        onEnd: evt => {
+          const targetRow = this.list.splice(evt.oldIndex, 1)[0]
+          this.list.splice(evt.newIndex, 0, targetRow)
+
+          // for show the changes, you can delete in you code
+          const tempIndex = this.newList.splice(evt.oldIndex, 1)[0]
+          this.newList.splice(evt.newIndex, 0, tempIndex)
+        }
+      })
+    }
+  }
+}
+</script>
+
+<style>
+.sortable-ghost{
+  opacity: .8;
+  color: #fff!important;
+  background: #42b983!important;
+}
+</style>
+
+<style scoped>
+.icon-star{
+  margin-right:2px;
+}
+.drag-handler{
+  width: 20px;
+  height: 20px;
+  cursor: pointer;
+}
+.show-d{
+  margin-top: 15px;
+}
+</style>
diff --git a/frontend/src/views/table/dynamic-table/components/FixedThead.vue b/frontend/src/views/table/dynamic-table/components/FixedThead.vue
new file mode 100644
index 0000000..c3deb92
--- /dev/null
+++ b/frontend/src/views/table/dynamic-table/components/FixedThead.vue
@@ -0,0 +1,62 @@
+<template>
+  <div class="app-container">
+    <div class="filter-container">
+      <el-checkbox-group v-model="checkboxVal">
+        <el-checkbox label="apple">
+          apple
+        </el-checkbox>
+        <el-checkbox label="banana">
+          banana
+        </el-checkbox>
+        <el-checkbox label="orange">
+          orange
+        </el-checkbox>
+      </el-checkbox-group>
+    </div>
+
+    <el-table :key="key" :data="tableData" border fit highlight-current-row style="width: 100%">
+      <el-table-column prop="name" label="fruitName" width="180" />
+      <el-table-column v-for="fruit in formThead" :key="fruit" :label="fruit">
+        <template slot-scope="scope">
+          {{ scope.row[fruit] }}
+        </template>
+      </el-table-column>
+    </el-table>
+  </div>
+</template>
+
+<script>
+const defaultFormThead = ['apple', 'banana']
+
+export default {
+  data() {
+    return {
+      tableData: [
+        {
+          name: 'fruit-1',
+          apple: 'apple-10',
+          banana: 'banana-10',
+          orange: 'orange-10'
+        },
+        {
+          name: 'fruit-2',
+          apple: 'apple-20',
+          banana: 'banana-20',
+          orange: 'orange-20'
+        }
+      ],
+      key: 1, // table key
+      formTheadOptions: ['apple', 'banana', 'orange'],
+      checkboxVal: defaultFormThead, // checkboxVal
+      formThead: defaultFormThead // 默认表头 Default header
+    }
+  },
+  watch: {
+    checkboxVal(valArr) {
+      this.formThead = this.formTheadOptions.filter(i => valArr.indexOf(i) >= 0)
+      this.key = this.key + 1// 为了保证table 每次都会重渲 In order to ensure the table will be re-rendered each time
+    }
+  }
+}
+</script>
+
diff --git a/frontend/src/views/table/dynamic-table/components/UnfixedThead.vue b/frontend/src/views/table/dynamic-table/components/UnfixedThead.vue
new file mode 100644
index 0000000..831b070
--- /dev/null
+++ b/frontend/src/views/table/dynamic-table/components/UnfixedThead.vue
@@ -0,0 +1,50 @@
+<template>
+  <div class="app-container">
+    <div class="filter-container">
+      <el-checkbox-group v-model="formThead">
+        <el-checkbox label="apple">
+          apple
+        </el-checkbox>
+        <el-checkbox label="banana">
+          banana
+        </el-checkbox>
+        <el-checkbox label="orange">
+          orange
+        </el-checkbox>
+      </el-checkbox-group>
+    </div>
+
+    <el-table :data="tableData" border fit highlight-current-row style="width: 100%">
+      <el-table-column prop="name" label="fruitName" width="180" />
+      <el-table-column v-for="fruit in formThead" :key="fruit" :label="fruit">
+        <template slot-scope="scope">
+          {{ scope.row[fruit] }}
+        </template>
+      </el-table-column>
+    </el-table>
+  </div>
+</template>
+
+<script>
+export default {
+  data() {
+    return {
+      tableData: [
+        {
+          name: 'fruit-1',
+          apple: 'apple-10',
+          banana: 'banana-10',
+          orange: 'orange-10'
+        },
+        {
+          name: 'fruit-2',
+          apple: 'apple-20',
+          banana: 'banana-20',
+          orange: 'orange-20'
+        }
+      ],
+      formThead: ['apple', 'banana']
+    }
+  }
+}
+</script>
diff --git a/frontend/src/views/table/dynamic-table/index.vue b/frontend/src/views/table/dynamic-table/index.vue
new file mode 100644
index 0000000..5a4dd36
--- /dev/null
+++ b/frontend/src/views/table/dynamic-table/index.vue
@@ -0,0 +1,24 @@
+<template>
+  <div class="app-container">
+    <div style="margin:0 0 5px 20px">
+      Fixed header, sorted by header order,
+    </div>
+    <fixed-thead />
+
+    <div style="margin:30px 0 5px 20px">
+      Not fixed header, sorted by click order
+    </div>
+    <unfixed-thead />
+  </div>
+</template>
+
+<script>
+import FixedThead from './components/FixedThead'
+import UnfixedThead from './components/UnfixedThead'
+
+export default {
+  name: 'DynamicTable',
+  components: { FixedThead, UnfixedThead }
+}
+</script>
+
diff --git a/frontend/src/views/table/inline-edit-table.vue b/frontend/src/views/table/inline-edit-table.vue
new file mode 100644
index 0000000..31e0065
--- /dev/null
+++ b/frontend/src/views/table/inline-edit-table.vue
@@ -0,0 +1,149 @@
+<template>
+  <div class="app-container">
+    <el-table v-loading="listLoading" :data="list" border fit highlight-current-row style="width: 100%">
+      <el-table-column align="center" label="ID" width="80">
+        <template slot-scope="{row}">
+          <span>{{ row.id }}</span>
+        </template>
+      </el-table-column>
+
+      <el-table-column width="180px" align="center" label="Date">
+        <template slot-scope="{row}">
+          <span>{{ row.timestamp | parseTime('{y}-{m}-{d} {h}:{i}') }}</span>
+        </template>
+      </el-table-column>
+
+      <el-table-column width="120px" align="center" label="Author">
+        <template slot-scope="{row}">
+          <span>{{ row.author }}</span>
+        </template>
+      </el-table-column>
+
+      <el-table-column width="100px" label="Importance">
+        <template slot-scope="{row}">
+          <svg-icon v-for="n in + row.importance" :key="n" icon-class="star" class="meta-item__icon" />
+        </template>
+      </el-table-column>
+
+      <el-table-column class-name="status-col" label="Status" width="110">
+        <template slot-scope="{row}">
+          <el-tag :type="row.status | statusFilter">
+            {{ row.status }}
+          </el-tag>
+        </template>
+      </el-table-column>
+
+      <el-table-column min-width="300px" label="Title">
+        <template slot-scope="{row}">
+          <template v-if="row.edit">
+            <el-input v-model="row.title" class="edit-input" size="small" />
+            <el-button
+              class="cancel-btn"
+              size="small"
+              icon="el-icon-refresh"
+              type="warning"
+              @click="cancelEdit(row)"
+            >
+              cancel
+            </el-button>
+          </template>
+          <span v-else>{{ row.title }}</span>
+        </template>
+      </el-table-column>
+
+      <el-table-column align="center" label="Actions" width="120">
+        <template slot-scope="{row}">
+          <el-button
+            v-if="row.edit"
+            type="success"
+            size="small"
+            icon="el-icon-circle-check-outline"
+            @click="confirmEdit(row)"
+          >
+            Ok
+          </el-button>
+          <el-button
+            v-else
+            type="primary"
+            size="small"
+            icon="el-icon-edit"
+            @click="row.edit=!row.edit"
+          >
+            Edit
+          </el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+  </div>
+</template>
+
+<script>
+import { fetchList } from '@/api/article'
+
+export default {
+  name: 'InlineEditTable',
+  filters: {
+    statusFilter(status) {
+      const statusMap = {
+        published: 'success',
+        draft: 'info',
+        deleted: 'danger'
+      }
+      return statusMap[status]
+    }
+  },
+  data() {
+    return {
+      list: null,
+      listLoading: true,
+      listQuery: {
+        page: 1,
+        limit: 10
+      }
+    }
+  },
+  created() {
+    this.getList()
+  },
+  methods: {
+    async getList() {
+      this.listLoading = true
+      const { data } = await fetchList(this.listQuery)
+      const items = data.items
+      this.list = items.map(v => {
+        this.$set(v, 'edit', false) // https://vuejs.org/v2/guide/reactivity.html
+        v.originalTitle = v.title //  will be used when user click the cancel botton
+        return v
+      })
+      this.listLoading = false
+    },
+    cancelEdit(row) {
+      row.title = row.originalTitle
+      row.edit = false
+      this.$message({
+        message: 'The title has been restored to the original value',
+        type: 'warning'
+      })
+    },
+    confirmEdit(row) {
+      row.edit = false
+      row.originalTitle = row.title
+      this.$message({
+        message: 'The title has been edited',
+        type: 'success'
+      })
+    }
+  }
+}
+</script>
+
+<style scoped>
+.edit-input {
+  padding-right: 100px;
+}
+.cancel-btn {
+  position: absolute;
+  right: 15px;
+  top: 10px;
+}
+</style>
diff --git a/frontend/src/views/theme/index.vue b/frontend/src/views/theme/index.vue
new file mode 100644
index 0000000..0af7711
--- /dev/null
+++ b/frontend/src/views/theme/index.vue
@@ -0,0 +1,120 @@
+<template>
+  <div class="app-container">
+    <el-card class="box-card">
+      <div slot="header">
+        <a class="link-type link-title" target="_blank" href="https://panjiachen.github.io/vue-element-admin-site/guide/advanced/theme.html">
+          Theme documentation
+        </a>
+      </div>
+      <div class="box-item">
+        <span class="field-label">Change Theme : </span>
+        <el-switch v-model="theme" />
+        <aside style="margin-top:15px;">
+          Tips: It is different from the theme-pick on the navbar is two different skinning methods, each with different application scenarios. Refer to the documentation for details.
+        </aside>
+      </div>
+    </el-card>
+
+    <div class="block">
+      <el-button type="primary">
+        Primary
+      </el-button>
+      <el-button type="success">
+        Success
+      </el-button>
+      <el-button type="info">
+        Info
+      </el-button>
+      <el-button type="warning">
+        Warning
+      </el-button>
+      <el-button type="danger">
+        Danger
+      </el-button>
+    </div>
+
+    <div class="block">
+      <el-button type="primary" icon="el-icon-edit" />
+      <el-button type="primary" icon="el-icon-share" />
+      <el-button type="primary" icon="el-icon-delete" />
+      <el-button type="primary" icon="el-icon-search">
+        Search
+      </el-button>
+      <el-button type="primary">
+        Upload
+        <i class="el-icon-upload el-icon-right" />
+      </el-button>
+    </div>
+
+    <div class="block">
+      <el-tag v-for="tag in tags" :key="tag.type" :type="tag.type" class="tag-item">
+        {{ tag.name }}
+      </el-tag>
+    </div>
+
+    <div class="block">
+      <el-radio-group v-model="radio">
+        <el-radio :label="3">
+          Option A
+        </el-radio>
+        <el-radio :label="6">
+          Option B
+        </el-radio>
+        <el-radio :label="9">
+          Option C
+        </el-radio>
+      </el-radio-group>
+    </div>
+
+    <div class="block">
+      <el-slider v-model="slideValue" />
+    </div>
+  </div>
+</template>
+
+<script>
+import { toggleClass } from '@/utils'
+import '@/assets/custom-theme/index.css' // the theme changed version element-ui css
+
+export default {
+  name: 'Theme',
+  data() {
+    return {
+      theme: false,
+      tags: [
+        { name: 'Tag One', type: '' },
+        { name: 'Tag Two', type: 'info' },
+        { name: 'Tag Three', type: 'success' },
+        { name: 'Tag Four', type: 'warning' },
+        { name: 'Tag Five', type: 'danger' }
+      ],
+      slideValue: 50,
+      radio: 3
+    }
+  },
+  watch: {
+    theme() {
+      toggleClass(document.body, 'custom-theme')
+    }
+  }
+}
+</script>
+
+<style scoped>
+.field-label{
+  vertical-align: middle;
+}
+.box-card {
+  width: 400px;
+  max-width: 100%;
+  margin: 20px auto;
+}
+
+.block {
+  padding: 30px 24px;
+}
+
+.tag-item {
+  margin-right: 15px;
+}
+</style>
diff --git a/frontend/src/views/zip/index.vue b/frontend/src/views/zip/index.vue
new file mode 100644
index 0000000..412608f
--- /dev/null
+++ b/frontend/src/views/zip/index.vue
@@ -0,0 +1,77 @@
+<template>
+  <div class="app-container">
+    <el-input v-model="filename" placeholder="Please enter the file name (default file)" style="width:300px;" prefix-icon="el-icon-document" />
+    <el-button :loading="downloadLoading" style="margin-bottom:20px;" type="primary" icon="el-icon-document" @click="handleDownload">
+      Export Zip
+    </el-button>
+    <el-table v-loading="listLoading" :data="list" element-loading-text="拼命加载中" border fit highlight-current-row>
+      <el-table-column align="center" label="ID" width="95">
+        <template slot-scope="scope">
+          {{ scope.$index }}
+        </template>
+      </el-table-column>
+      <el-table-column label="Title">
+        <template slot-scope="scope">
+          {{ scope.row.title }}
+        </template>
+      </el-table-column>
+      <el-table-column label="Author" width="95" align="center">
+        <template slot-scope="scope">
+          <el-tag>{{ scope.row.author }}</el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column label="Readings" width="115" align="center">
+        <template slot-scope="scope">
+          {{ scope.row.pageviews }}
+        </template>
+      </el-table-column>
+      <el-table-column align="center" label="Date" width="220">
+        <template slot-scope="scope">
+          <i class="el-icon-time" />
+          <span>{{ scope.row.display_time }}</span>
+        </template>
+      </el-table-column>
+    </el-table>
+  </div>
+</template>
+
+<script>
+import { fetchList } from '@/api/article'
+
+export default {
+  name: 'ExportZip',
+  data() {
+    return {
+      list: null,
+      listLoading: true,
+      downloadLoading: false,
+      filename: ''
+    }
+  },
+  created() {
+    this.fetchData()
+  },
+  methods: {
+    async fetchData() {
+      this.listLoading = true
+      const { data } = await fetchList()
+      this.list = data.items
+      this.listLoading = false
+    },
+    handleDownload() {
+      this.downloadLoading = true
+      import('@/vendor/Export2Zip').then(zip => {
+        const tHeader = ['Id', 'Title', 'Author', 'Readings', 'Date']
+        const filterVal = ['id', 'title', 'author', 'pageviews', 'display_time']
+        const list = this.list
+        const data = this.formatJson(filterVal, list)
+        zip.export_txt_to_zip(tHeader, data, this.filename, this.filename)
+        this.downloadLoading = false
+      })
+    },
+    formatJson(filterVal, jsonData) {
+      return jsonData.map(v => filterVal.map(j => v[j]))
+    }
+  }
+}
+</script>
diff --git a/frontend/vue.config.js b/frontend/vue.config.js
new file mode 100644
index 0000000..33a6348
--- /dev/null
+++ b/frontend/vue.config.js
@@ -0,0 +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')
+        }
+      )
+  }
+}