项目初始化
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>