项目初始化
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.
+
+[](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:
+
+
+
+[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 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><count-to :start-val='{{ _startVal }}' :end-val='{{ _endVal }}' :duration='{{ _duration }}'
+ :decimals='{{ _decimals }}' :separator='{{ _separator }}' :prefix='{{ _prefix }}' :suffix='{{ _suffix }}'
+ :autoplay=false></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 & 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 & 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 &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')
+ }
+ )
+ }
+}