From bcd7ab8a441e4ad848d2c6d6a87374d33b433789 Mon Sep 17 00:00:00 2001
From: zhangzengfei <zhangzengfei@smartai.com>
Date: 星期二, 07 九月 2021 11:11:26 +0800
Subject: [PATCH] base functions

---
 prettier.config.js                             |   16 
 src/plugins/vabMarkdownEditor.js               |    5 
 vue.config.js                                  |  194 
 .gitattributes                                 |   12 
 src/store/modules/tabsBar.js                   |  112 
 public/static/css/loading.css                  |   99 
 src/assets/comparison/right.jpg                |    0 
 src/plugins/vabMagnifier.js                    |    3 
 src/store/modules/settings.js                  |   75 
 src/colorfulIcon/svg/alphabetical_sorting.svg  |    1 
 src/styles/variables.scss                      |   83 
 mock/controller/user.js                        |   95 
 src/config/net.config.js                       |   23 
 src/store/modules/user.js                      |   99 
 nginx.conf                                     |   45 
 src/store/index.js                             |   22 
 mock/controller/router.js                      |  292 +
 src/assets/zfb_100.jpg                         |    0 
 src/views/project/components/ProjectEdit.vue   |  119 
 src/utils/accessToken.js                       |   59 
 src/plugins/index.js                           |   17 
 src/styles/vab.scss                            |  295 +
 src/views/project/index.vue                    |  271 +
 src/utils/index.js                             |  266 +
 plopfile.js                                    |   16 
 mock/controller/personalCenter.js              |   42 
 src/components/VabCharge/index.vue             |  191 
 src/api/package.js                             |    9 
 src/layouts/components/VabLogo/index.vue       |   92 
 src/plugins/echarts.js                         |    4 
 public/favicon_backup.ico                      |    0 
 src/views/login/index.vue                      |  327 +
 .stylelintrc.js                                |    3 
 src/remixIcon/svg/vuejs-fill.svg               |    1 
 src/layouts/components/VabAvatar/index.vue     |   83 
 src/utils/clipboard.js                         |   31 
 src/remixIcon/svg/qq-fill.svg                  |    1 
 src/layouts/index.vue                          |  297 +
 src/plugins/support.js                         |   20 
 src/utils/static.js                            |   52 
 src/config/index.js                            |    7 
 src/utils/errorLog.js                          |   25 
 src/layouts/export.js                          |   25 
 src/styles/loading.scss                        |  346 +
 src/styles/normalize.scss                      |  353 +
 src/styles/themes/default.scss                 |    1 
 src/assets/error_images/cloud.png              |    0 
 mock/controller/ad.js                          |   40 
 public/index.html                              |   32 
 src/layouts/components/VabThemeBar/index.vue   |  255 +
 src/styles/spinner/gauge.css                   |  104 
 src/utils/handleRoutes.js                      |   63 
 src/views/404.vue                              |  296 +
 src/utils/request.js                           |  125 
 babel.config.js                                |    7 
 mock/controller/tree.js                        |   54 
 src/api/project.js                             |   51 
 src/plugins/vabQuill.js                        |    4 
 mock/controller/goodsList.js                   |   43 
 src/components/SelectTree/index.vue            |  201 
 src/layouts/components/VabBreadcrumb/index.vue |   63 
 .eslintignore                                  |    7 
 src/assets/zfb_699.jpg                         |    0 
 src/styles/element-variables.scss              | 1037 ++++
 mock/controller/menuManagement.js              |   51 
 src/assets/error_images/401.png                |    0 
 src/colorfulIcon/index.js                      |   13 
 src/plugins/element.js                         |    9 
 mock/controller/table.js                       |   88 
 src/assets/ewm.png                             |    0 
 src/layouts/components/VabNavBar/index.vue     |  130 
 .eslintrc.js                                   |   31 
 webstorm.config.js                             |    6 
 public/favicon.ico                             |    0 
 .editorconfig                                  |   14 
 LICENSE                                        |   21 
 src/store/modules/errorLog.js                  |   28 
 src/styles/spinner/inner-circles.css           |   51 
 .dockerignore                                  |    1 
 src/api/user.js                                |   34 
 mock/controller/userManagement.js              |   70 
 src/styles/spinner/dots.css                    |  110 
 src/styles/spinner/plus.css                    |  341 +
 mock/controller/colorfulIcon.js                |  324 +
 src/assets/zfb_799.jpg                         |    0 
 src/config/setting.config.js                   |   70 
 nginx.default.conf                             |   46 
 src/router/index.js                            |   86 
 src/assets/comparison/left.jpg                 |    0 
 src/main.js                                    |   24 
 mock/utils/index.js                            |   43 
 src/store/modules/routes.js                    |   42 
 mock/controller/icon.js                        |  985 ++++
 src/views/401.vue                              |  296 +
 src/utils/vab.js                               |  180 
 .gitignore                                     |   68 
 src/layouts/components/VabAppMain/index.vue    |   92 
 src/store/modules/table.js                     |   23 
 src/views/index/index.vue                      |   11 
 mock/controller/changeLog.js                   |  250 +
 src/plugins/vabIcon.js                         |    4 
 src/assets/error_images/404.png                |    0 
 src/components/VabUpload/index.vue             |  255 +
 src/config/theme.config.js                     |   14 
 src/assets/qr_logo/lqr_logo.png                |    0 
 mock/controller/notice.js                      |   33 
 mock/controller/remixIcon.js                   | 2294 ++++++++++
 src/assets/zfb_kf.jpg                          |    0 
 src/colorfulIcon/svg/vab.svg                   |   21 
 src/remixIcon/index.js                         |   13 
 src/utils/pageTitle.js                         |   14 
 src/plugins/vabPlayer.js                       |    3 
 src/config/permission.js                       |   84 
 mock/controller/roleManagement.js              |   53 
 src/utils/validate.js                          |  217 
 src/styles/transition.scss                     |   19 
 src/utils/permission.js                        |   20 
 src/components/VabSnow/index.vue               |   82 
 mock/index.js                                  |   98 
 .browserslistrc                                |    5 
 src/components/VabProfile/index.vue            |  313 +
 package.json                                   |  105 
 src/config/settings.js                         |    6 
 src/plugins/vabVerify.js                       |    4 
 src/assets/pro.png                             |    0 
 src/assets/login_images/background.jpg         |    0 
 src/App.vue                                    |   12 
 src/layouts/EmptyLayout.vue                    |    3 
 128 files changed, 13,678 insertions(+), 43 deletions(-)

diff --git a/.browserslistrc b/.browserslistrc
new file mode 100644
index 0000000..ab3713a
--- /dev/null
+++ b/.browserslistrc
@@ -0,0 +1,5 @@
+# 鏀寔娴忚鍣ㄩ厤缃�
+> 1%
+last 2 versions
+not dead
+
diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000..3c3629e
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1 @@
+node_modules
diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..7b06489
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,14 @@
+# 缂栬緫鍣ㄩ厤缃�
+
+root = true
+
+[*]
+charset = utf-8
+end_of_line = lf
+indent_size = 2
+indent_style = space
+insert_final_newline = true
+trim_trailing_whitespace = true
+
+[*.md]
+trim_trailing_whitespace = false
diff --git a/.eslintignore b/.eslintignore
new file mode 100644
index 0000000..f8aa487
--- /dev/null
+++ b/.eslintignore
@@ -0,0 +1,7 @@
+# .eslintignore
+
+src/assets
+src/icons
+public
+dist
+node_modules
diff --git a/.eslintrc.js b/.eslintrc.js
new file mode 100644
index 0000000..ab6565e
--- /dev/null
+++ b/.eslintrc.js
@@ -0,0 +1,31 @@
+/**
+ * @author chuzhixin 1204505056@qq.com 锛堜笉鎯充繚鐣檃uthor鍙垹闄わ級
+ * @description .eslintrc.js
+ */
+
+module.exports = {
+  root: true,
+  env: {
+    node: true,
+  },
+  extends: ['plugin:vue/recommended', '@vue/prettier'],
+  rules: {
+    // 'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
+    // 'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
+    // 'vue/no-v-html': 'off',
+  },
+  parserOptions: {
+    parser: 'babel-eslint',
+  },
+  overrides: [
+    {
+      files: [
+        '**/__tests__/*.{j,t}s?(x)',
+        '**/tests/unit/**/*.spec.{j,t}s?(x)',
+      ],
+      env: {
+        jest: true,
+      },
+    },
+  ],
+}
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..1fa0664
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,12 @@
+# .gitattributes
+*.html text eol=lf
+*.css text eol=lf
+*.js text eol=lf
+*.scss text eol=lf
+*.vue text eol=lf
+*.hbs text eol=lf
+*.sh text eol=lf
+*.md text eol=lf
+*.json text eol=lf
+*.yml text eol=lf
+*.js linguist-language=vue
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 087029a..6b55d83 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,44 +1,26 @@
-# Byte-compiled / optimized / DLL files
-__pycache__/
-*.py[cod]
+# .gitignore
+.DS_Store
+node_modules
+dist
+.env.local
+.env.*.local
+npm-debug.log*
+yarn.lock
+yarn-debug.log*
+yarn-error.log*
+.idea
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
+public/video
+*.zip
+*.7z
+/src/layouts/components/zx-layouts
+/zx-templates
+/package-lock.json
+/src/styles/themes/green.scss
+/src/styles/themes/dark.scss
+/src/styles/themes/glory.scss
 
-# C extensions
-*.so
-
-# Distribution / packaging
-.Python
-env/
-build/
-develop-eggs/
-dist/
-eggs/
-lib/
-lib64/
-parts/
-sdist/
-var/
-*.egg-info/
-.installed.cfg
-*.egg
-
-# Installer logs
-pip-log.txt
-pip-delete-this-directory.txt
-
-# Unit test / coverage reports
-htmlcov/
-.tox/
-.coverage
-.cache
-nosetests.xml
-coverage.xml
-
-# Translations
-*.mo
-*.pot
-
-# Django stuff:
-*.log
-
-# Sphinx documentation
-docs/_build/
diff --git a/.stylelintrc.js b/.stylelintrc.js
new file mode 100644
index 0000000..591e391
--- /dev/null
+++ b/.stylelintrc.js
@@ -0,0 +1,3 @@
+module.exports = {
+  extends: ['stylelint-config-recess-order', 'stylelint-config-prettier'],
+}
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..0099bf4
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2020 good luck
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/babel.config.js b/babel.config.js
new file mode 100644
index 0000000..34ff3d0
--- /dev/null
+++ b/babel.config.js
@@ -0,0 +1,7 @@
+/**
+ * @author chuzhixin 1204505056@qq.com 锛堜笉鎯充繚鐣檃uthor鍙垹闄わ級
+ * @description babel.config
+ */
+module.exports = {
+  presets: ['@vue/cli-plugin-babel/preset'],
+}
diff --git a/mock/controller/ad.js b/mock/controller/ad.js
new file mode 100644
index 0000000..b45a0dd
--- /dev/null
+++ b/mock/controller/ad.js
@@ -0,0 +1,40 @@
+const data = [
+  {
+    title: '寤哄厷鐧惧勾鎯婂枩绂忓埄锛屼粯璐圭増鏈拱涓�寰椾簩鍏ㄥ勾鏈�浣庝环锛岀偣鎴戣喘涔�',
+    url: 'http://vue-admin-beautiful.com/authorization',
+  },
+  /*   {
+    title: 'Admin Pro 3.0浠樿垂鐗堟湰宸插彂甯冿紝澧炲姞澶氶」璐村績鍔熻兘锛岀偣鎴戞彁鍓嶄綋楠�',
+    url: 'https://chu1204505056.gitee.io/admin-pro?hmsr=homeAd&hmpl=&hmcu=&hmkw=&hmci=',
+  },
+  {
+    title: 'Admin Plus 3.0鍐呮祴鐗堟湰宸插彂甯冿紝澧炲姞澶氶」璐村績鍔熻兘锛岀偣鎴戞彁鍓嶄綋楠�',
+    url: 'https://chu1204505056.gitee.io/admin-plus?hmsr=homeAd&hmpl=&hmcu=&hmkw=&hmci=',
+  },
+  {
+    title: 'vue-admin-beautiful锛坅ntdv锛� vue3.0鐗堟湰宸插彂甯冿紝鐐规垜鎻愬墠浣撻獙',
+    url: 'http://vue-admin-beautiful.com/vue-admin-beautiful-antdv?hmsr=homeAd&hmpl=&hmcu=&hmkw=&hmci=',
+  },
+  {
+    title: 'vue-admin-beautiful锛坋lement-plus锛� vue3.0鐗堟湰宸插彂甯冿紝鐐规垜鎻愬墠浣撻獙',
+    url: 'https://chu1204505056.gitee.io/admin-plus?hmsr=homeAd&hmpl=&hmcu=&hmkw=&hmci=',
+  },
+  {
+    title:
+      '绋嬪簭鏃犲浗鐣岋紝浣嗙▼搴忓憳鏈夊浗鐣岋紝涓浗鍥藉灏婁弗涓嶅鎸戣锛屽鏋滄偍鍦ㄧ壒娈婃椂鏈熺户缁喘涔癏M銆佽�愬厠銆侀樋杩揪鏂瓑鍝佺墝閭d箞鎮ㄥ皢鏃犳潈缁х画浣跨敤Vab',
+    url: 'https://chu1204505056.gitee.io/admin-pro?hmsr=homeAd&hmpl=&hmcu=&hmkw=&hmci=',
+  }, */
+]
+module.exports = [
+  {
+    url: '/ad/getList',
+    type: 'get',
+    response() {
+      return {
+        code: 200,
+        msg: 'success',
+        data,
+      }
+    },
+  },
+]
diff --git a/mock/controller/changeLog.js b/mock/controller/changeLog.js
new file mode 100644
index 0000000..6fbb88d
--- /dev/null
+++ b/mock/controller/changeLog.js
@@ -0,0 +1,250 @@
+const data = [
+  {
+    content: '鍦╣ithub涓婅幏寰椾簡绗竴涓猻tar锛屾劅鎭╀竴浣嶅悕鍙獴equiet2014鐨刧ithub鐢ㄦ埛',
+    timestamp: '2020-03-23',
+  },
+  {
+    content: '澧炲姞鏇存崲涓婚鍔熻兘',
+    timestamp: '2020-04-10',
+  },
+  {
+    content: '澶у箙绮剧畝浠g爜',
+    timestamp: '2020-04-14',
+  },
+  {
+    content: '淇缇ゅ弸鍙嶉鐨刡ug',
+    timestamp: '2020-04-16',
+  },
+  {
+    content: '鍓旈櫎maptalks',
+    timestamp: '2020-04-17',
+  },
+  {
+    content:
+      '鎹㈣绗︾粺涓�淇敼涓簂f 鏀寔鑻规灉 linux windows鍗忓悓寮�鍙� 寮哄埗寮�鍚渶涓ユ牸eslint瑙勫垯 涓嶈鍝� 涓ユ牸鏄湁濂藉鐨�',
+    timestamp: '2020-04-17',
+  },
+  {
+    content: '褰诲簳瀹屾垚鎵嬫満绔�傞厤,璁板綍杩欎竴澶╃啲澶滃埌浜嗘櫄涓婁笁鐐�',
+    timestamp: '2020-04-18',
+  },
+  {
+    content:
+      '鍒犻櫎babel-polyfill 鎻愰珮鎵撳寘閫熷害 鍑忓皯鍘嬬缉浣撶Н锛堟斁寮僫e鏄繖涓」鐩仛鍑虹殑鏈�浼熷ぇ鐨勫喅瀹氾級',
+    timestamp: '2020-04-18',
+  },
+  {
+    content: '婧愮爜绮剧畝鑷�800k',
+    timestamp: '2020-04-19',
+  },
+  {
+    content: '娣诲姞瑙嗛鎾斁鍣ㄧ粍浠�',
+    timestamp: '2020-04-20',
+  },
+  {
+    content: '淇璺敱鎳掑姞杞� 瀹屽杽涓婚閰嶈壊',
+    timestamp: '2020-04-22',
+  },
+  {
+    content: '淇鍏ㄥ眬axios鎷︽埅 鍔犲揩鍔ㄧ敾灞曠ず鏁堟灉 淇敼鐧诲綍椤垫牱寮�',
+    timestamp: '2020-04-24',
+  },
+  {
+    content: '绠�鍖栨潈闄愪笌鐧诲綍閫昏緫 鏇存柊mockServer',
+    timestamp: '2020-04-25',
+  },
+  {
+    content: '浼樺寲鐧诲綍閫�鍑洪�昏緫 浠g爜鏇存竻鏅� 閫�鍑轰笉鍐嶉噸杞界綉椤� 鏀逛负閲嶈浇璺敱褰㈠紡',
+    timestamp: '2020-04-26',
+  },
+  {
+    content: '鏃犵鐨勬寚璐e彧浼氳鎴戞洿鍔犲姫鍔� 淇sidebar 绠�鍖杙ermission',
+    timestamp: '2020-04-28',
+  },
+  {
+    content: '鍙堟槸涓�涓繁澶� 瀹炵幇浜嗚〃鏍煎鍒犳敼鏌ョ殑涓�閿敓鎴�',
+    timestamp: '2020-04-30',
+  },
+  {
+    content: '澶у箙浼樺寲tagsview鏍囩鍔ㄧ敾',
+    timestamp: '2020-05-02',
+  },
+  {
+    content: '涓夌鍥炬爣缁勪欢瀹炵幇mock妯℃嫙鍒嗛〉',
+    timestamp: '2020-05-03',
+  },
+  {
+    content: '娣诲姞浜唌arkdown缂栬緫鍣ㄧ粍浠�',
+    timestamp: '2020-05-04',
+  },
+  {
+    content: '娣诲姞stylelint-plus鑷姩瑙勬暣鎺掑簭鏍峰紡',
+    timestamp: '2020-05-06',
+  },
+  {
+    content: '娣诲姞鍟嗗煄妯℃澘',
+    timestamp: '2020-05-12',
+  },
+  {
+    content: 'github鏍囨槦瓒呰繃1000 鎰熸仼',
+    timestamp: '2020-05-13',
+  },
+  {
+    content: '娣诲姞楠岃瘉鐮佺粍浠�',
+    timestamp: '2020-05-14',
+  },
+  {
+    content: '淇妯悜鑿滃崟bug',
+    timestamp: '2020-05-16',
+  },
+  {
+    content: '鍙堣浜洪獋浜� 鎸哄ソ鐨� 璁╂垜涓嬪畾鍐冲績閲嶅啓浜唗absBar',
+    timestamp: '2020-05-20',
+  },
+  {
+    content: '浠縜nt-design 娣诲姞闆姳灞�',
+    timestamp: '2020-05-26',
+  },
+  {
+    content: '娣诲姞浜哄憳绠$悊妯″潡',
+    timestamp: '2020-06-02',
+  },
+  {
+    content: 'github鏍囨槦瓒呰繃2000 鎰熸仼',
+    timestamp: '2020-06-03',
+  },
+  {
+    content: '娣诲姞鐐叿鍦板浘缁勪欢',
+    timestamp: '2020-06-11',
+  },
+  {
+    content: '鎶界鏇村鍏叡閰嶇疆锛屾鏋朵娇鐢ㄦ洿椤烘墜',
+    timestamp: '2020-06-19',
+  },
+  {
+    content: '褰诲簳瀹屾垚浜唗absBar澶氭爣绛鹃〉鐨勯噸鏋�',
+    timestamp: '2020-06-22',
+  },
+  {
+    content: '鎰熸仼github鏍囨槦杩�3.0K 绁濆ぇ瀹剁鍗堣妭蹇箰',
+    timestamp: '2020-06-25',
+  },
+  {
+    content: '褰诲簳閲嶆瀯浜哠ideBar涓嶵opBar 澶у箙绮剧畝dom娓叉煋閫昏緫 鍏ㄧ悆棣栧彂',
+    timestamp: '2020-06-25',
+  },
+  {
+    content: '娣诲姞鑿滃崟绠$悊',
+    timestamp: '2020-07-7',
+  },
+  {
+    content: '棣栨閲囩敤sass-loader 9.0鍐欐硶锛屾劅璋ithub鐢ㄦ埛 shaonialife',
+    timestamp: '2020-07-7',
+  },
+  {
+    content: '娣诲姞vue-amap缁勪欢',
+    timestamp: '2020-07-11',
+  },
+  {
+    content: '淇敼zx-layouts寮曞叆鏂瑰紡',
+    timestamp: '2020-07-15',
+  },
+  {
+    content:
+      '璁板綍杩欎竴澶﹙ue-admin-beautiful鍦ㄦ彃浠跺競鍦恒�佺櫨搴﹀凡鑺辫垂瓒呰繃1涓囧厓骞垮憡璐圭敤锛屽笇鏈涗竴鍒囬兘鍊煎緱',
+    timestamp: '2020-07-18',
+  },
+  {
+    content: '涓婚閰嶇疆娣诲姞缁胯崼鑽夊満銆佽崳鑰�鍏歌棌銆佹殫榛戜箣瀛愭ā寮�',
+    timestamp: '2020-07-18',
+  },
+  {
+    content: '鍏ㄥ眬axios璇锋眰鍏ㄩ潰鏀寔Status Code鎷︽埅澶勭悊',
+    timestamp: '2020-07-29',
+  },
+  {
+    content: '閲嶆瀯鍏ㄥ眬loadding鍔犺浇浠g爜',
+    timestamp: '2020-07-31',
+  },
+  {
+    content: '鍗囩骇stylelint鑷姩鎺掑簭鍔熻兘',
+    timestamp: '2020-08-25',
+  },
+  {
+    content: '淇瑙嗛鎾斁鍣ㄧ粍浠堕噸杞借矾鐢卞け鏁堢殑bug',
+    timestamp: '2020-09-03',
+  },
+  {
+    content: '淇鏋佷釜鍒儏鍐礽mage-loader鎵撳寘鎶ラ敊',
+    timestamp: '2020-09-18',
+  },
+  {
+    content: '鍏ㄧ綉棣栦釜鍩轰簬vue3.0寮�鍙戠殑admin妗嗘灦宸插彂甯冿紝鍏蜂綋璇疯闂甮ithub',
+    timestamp: '2020-09-22',
+  },
+  {
+    content: '瀹屽杽璺敱鍚庣娓叉煋鏂规锛屽純鐢ㄤ箣鍓嶅啓娉�',
+    timestamp: '2020-09-30',
+  },
+  {
+    content: '澶х増鏈凯浠o紝璇峰叧娉╣ithub tag',
+    timestamp: '2020-09-30',
+  },
+  /* {
+    content:
+      "闈㈠鑷О鍔犳嬁澶у崕浜虹殑鎹曡泧鑰呰浣滆�卨aike9m鐨勫彂甯栬瘚姣侊紝鍜屼汉韬敾鍑伙紝鏈兂鍘荤煡涔庢壘浠栫悊璁猴紝鍙浣曚粬鐨勭煡涔庡凡缁忕敱浜庣粡甯稿彂甯冮敊璇█璁鸿瀹樻柟灏佸彿浜唄ttps://www.zhihu.com/people/laike9m锛屾棦鐒惰嚜宸遍兘涓嶆槸涓浗浜轰簡锛屽氨涓嶈濡勫姞鎻f祴鍥戒骇鐨勫紑婧愰」鐩簡锛屼竴鍒囬兘浜ょ粰鏃堕棿鍚э紝鍚庢潵鎴戦噴鎬�浜嗭紝laike9m銆丆ruii杩欑兢浜轰笉姝㈡槸鎶归粦鎴戠殑妗嗘灦锛岀敋鑷宠繕鍘绘姽榛戜腑鍥芥皯钀ヤ紒涓氱殑浣间郊鑰呭崕涓猴紝鍏呮弧浜嗘棤濂堝晩锛屼篃璁稿彧鏈夊彂甯冭繖鏍风殑瑷�璁猴紝浠栦滑鐨勭洰鐨勬墠鑳借揪鍒版妸 https://github.com/evil-huawei/evil-huawei",
+    timestamp: "2020-10-01",
+  }, */
+  {
+    content: '鍑屾櫒涓ょ偣锛屾垜绱簡锛岀Щ闄ゆ棤鐢ㄧ粍浠讹紝绮剧畝package',
+    timestamp: '2020-10-02',
+  },
+  {
+    content: '瀵屾枃鏈紪杈戝櫒鏀寔绮剧粏鍖栭厤缃�',
+    timestamp: '2020-10-20',
+  },
+  {
+    content: '鍏叡甯冨眬鏀寔鑷姩瀵煎嚭',
+    timestamp: '2020-10-23',
+  },
+  {
+    content: '閬僵娣诲姞楂樻柉妯$硦',
+    timestamp: '2020-10-25',
+  },
+  {
+    content: 'vue3.0 + element-plus鐗堟湰姝e紡涓婄嚎',
+    timestamp: '2020-12-5',
+  },
+  {
+    content: '瀹屾垚棣栭〉閲嶆瀯锛岃窡杩沞charts 5.0',
+    timestamp: '2020-12-7',
+  },
+  {
+    content: '鏇存柊sass鐗堟湰鑷虫渶鏂�',
+    timestamp: '2021-1-7',
+  },
+  {
+    content: '鏇存柊vue-echarts鐗堟湰鑷虫渶鏂�,options鍙樻洿涓簅ption',
+    timestamp: '2021-2-23',
+  },
+  {
+    content:
+      '寮�婧愮増鍝佺墝鍚嶅崌绾т负vue-admin-beautiful-pro锛屼粯璐圭増vue2.x鍝佺墝鍗囩骇涓篈dmin Pro锛屼粯璐圭増vue3.x鍝佺墝鍚嶅崌绾т负Admin Plus',
+    timestamp: '鏈�杩戞洿鏂�',
+  },
+]
+
+module.exports = [
+  {
+    url: '/changeLog/getList',
+    type: 'post',
+    response() {
+      return {
+        code: 200,
+        msg: 'success',
+        totalCount: 999,
+        data: data,
+      }
+    },
+  },
+]
diff --git a/mock/controller/colorfulIcon.js b/mock/controller/colorfulIcon.js
new file mode 100644
index 0000000..8d7625f
--- /dev/null
+++ b/mock/controller/colorfulIcon.js
@@ -0,0 +1,324 @@
+const data = [
+  'alphabetical_sorting',
+  'alphabetical_sorting',
+  'alarm_clock',
+  'area_chart',
+  'approval',
+  'answers',
+  'approve',
+  'assistant',
+  'automotive',
+  'automatic',
+  'bad_decision',
+  'bar_chart',
+  'bearish',
+  'biomass',
+  'biohazard',
+  'binoculars',
+  'bookmark',
+  'briefcase',
+  'biotech',
+  'broken_link',
+  'business',
+  'bullish',
+  'business_contact',
+  'businesswoman',
+  'cable_release',
+  'calculator',
+  'businessman',
+  'calendar',
+  'butting_in',
+  'call_transfer',
+  'callback',
+  'camcorder',
+  'camera',
+  'camcorder_pro',
+  'cancel',
+  'camera_addon',
+  'camera_identificatio',
+  'capacitor',
+  'candle_sticks',
+  'checkmark',
+  'circuit',
+  'charge_battery',
+  'clear_filters',
+  'clapperboard',
+  'clock',
+  'close_up_mode',
+  'collaboration',
+  'cell_phone',
+  'collapse',
+  'collect',
+  'cloth',
+  'combo_chart',
+  'comments',
+  'conference_call',
+  'compact_camera',
+  'contacts',
+  'copyleft',
+  'copyright',
+  'crystal_oscillator',
+  'cursor',
+  'currency_exchange',
+  'customer_support',
+  'dam',
+  'data_backup',
+  'data_configuration',
+  'data_encryption',
+  'data_protection',
+  'data_recovery',
+  'database',
+  'data_sheet',
+  'debt',
+  'decision',
+  'delete_column',
+  'delete_database',
+  'department',
+  'delete_row',
+  'deployment',
+  'dislike',
+  'disapprove',
+  'disclaimer',
+  'display',
+  'document',
+  'do_not_insert',
+  'do_not_mix',
+  'do_not_inhale',
+  'donate',
+  'down',
+  'doughnut_chart',
+  'down_left',
+  'down_right',
+  'download',
+  'edit_image',
+  'electrical_sensor',
+  'electrical_threshold',
+  'electricity',
+  'electro_devices',
+  'electronics',
+  'empty_battery',
+  'empty_filter',
+  'empty_trash',
+  'end_call',
+  'engineering',
+  'entering_heaven_aliv',
+  'expand',
+  'export',
+  'expired',
+  'factory',
+  'factory_breakdown',
+  'external',
+  'faq',
+  'feed_in',
+  'file',
+  'feedback',
+  'film',
+  'filled_filter',
+  'filing_cabinet',
+  'film_reel',
+  'flash_auto',
+  'fine_print',
+  'flash_off',
+  'flash_on',
+  'flow_chart',
+  'folder',
+  'frame',
+  'full_battery',
+  'full_trash',
+  'gallery',
+  'generic_sorting_asc',
+  'generic_sorting_desc',
+  'genealogy',
+  'globe',
+  'good_decision',
+  'headset',
+  'grid',
+  'graduation_cap',
+  'heat_map',
+  'high_priority',
+  'high_battery',
+  'image_file',
+  'home',
+  'idea',
+  'import',
+  'in_transit',
+  'integrated_webcam',
+  'inspection',
+  'invite',
+  'internal',
+  'ipad',
+  'info',
+  'iphone',
+  'kindle',
+  'key',
+  'landscape',
+  'left',
+  'left_down',
+  'left_up',
+  'leave',
+  'like_placeholder',
+  'light_at_the_end_of_',
+  'library',
+  'line_chart',
+  'link',
+  'like',
+  'lock',
+  'list',
+  'lock_landscape',
+  'low_battery',
+  'lock_portrait',
+  'low_priority',
+  'make_decision',
+  'medium_priority',
+  'manager',
+  'menu',
+  'middle_battery',
+  'minus',
+  'missed_call',
+  'mind_map',
+  'mms',
+  'multiple_cameras',
+  'money_transfer',
+  'music',
+  'multiple_devices',
+  'multiple_smartphones',
+  'multiple_inputs',
+  'negative_dynamic',
+  'neutral_decision',
+  'night_landscape',
+  'news',
+  'neutral_trading',
+  'night_portrait',
+  'no_idea',
+  'next',
+  'no_video',
+  'nook',
+  'ok',
+  'org_unit',
+  'opened_folder',
+  'old_time_camera',
+  'online_support',
+  'organization',
+  'package',
+  'paid',
+  'parallel_tasks',
+  'overtime',
+  'panorama',
+  'phone',
+  'phone_android',
+  'photo_reel',
+  'pie_chart',
+  'picture',
+  'planner',
+  'plus',
+  'podium_with_audience',
+  'podium_without_speak',
+  'podium_with_speaker',
+  'previous',
+  'portrait_mode',
+  'positive_dynamic',
+  'privacy',
+  'process',
+  'puzzle',
+  'questions',
+  'print',
+  'radar_plot',
+  'rating',
+  'ratings',
+  'reading',
+  'redo',
+  'reading_ebook',
+  'refresh',
+  'registered_trademark',
+  'right',
+  'reuse',
+  'remove_image',
+  'right_down',
+  'right_up',
+  'rotate_to_portrait',
+  'rules',
+  'rotate_camera',
+  'rotate_to_landscape',
+  'ruler',
+  'scatter_plot',
+  'search',
+  'safe',
+  'self_service_kiosk',
+  'selfie',
+  'serial_tasks',
+  'sales_performance',
+  'settings',
+  'services',
+  'share',
+  'shipped',
+  'sim_card',
+  'shop',
+  'service_mark',
+  'sim_card_chip',
+  'signature',
+  'smartphone_tablet',
+  'sound_recording_copy',
+  'sms',
+  'speaker',
+  'slr_back_side',
+  'start',
+  'stack_of_photos',
+  'statistics',
+  'sports_mode',
+  'support',
+  'synchronize',
+  'switch_camera',
+  'survey',
+  'tablet_android',
+  'template',
+  'trademark',
+  'todo_list',
+  'touchscreen_smartpho',
+  'timeline',
+  'tree_structure',
+  'undo',
+  'up_left',
+  'two_smartphones',
+  'unlock',
+  'up',
+  'up_right',
+  'upload',
+  'video_call',
+  'video_file',
+  'view_details',
+  'video_projector',
+  'vip',
+  'voice_presentation',
+  'webcam',
+  'voicemail',
+  'workflow',
+  'about',
+  'accept_database',
+  'add_image',
+  'add_column',
+  'add_database',
+  'add_row',
+]
+module.exports = [
+  {
+    url: '/colorfulIcon/getList',
+    type: 'post',
+    response(config) {
+      const { title, pageNo = 1, pageSize = 72 } = config.body
+      let mockList = data.filter((item) => {
+        if (title && item.indexOf(title) < 0) return false
+        return true
+      })
+      const pageList = mockList.filter(
+        (item, index) =>
+          index < pageSize * pageNo && index >= pageSize * (pageNo - 1)
+      )
+      return {
+        code: 200,
+        msg: 'success',
+        totalCount: mockList.length,
+        data: pageList,
+      }
+    },
+  },
+]
diff --git a/mock/controller/goodsList.js b/mock/controller/goodsList.js
new file mode 100644
index 0000000..82176a3
--- /dev/null
+++ b/mock/controller/goodsList.js
@@ -0,0 +1,43 @@
+const { mock } = require('mockjs')
+
+const List = []
+const count = 999
+let num = 0
+for (let i = 0; i < count; i++) {
+  List.push(
+    mock({
+      uuid: '@uuid',
+      image: `https://picsum.photos/300/600?random=${num++}`,
+      title: '@ctitle',
+      description: '@csentence',
+      link: 'https://www.baidu.com',
+      price: '@integer(100, 500)',
+      'status|1': [1, 0],
+      'isRecommend|1': [1, 0],
+    })
+  )
+}
+
+module.exports = [
+  {
+    url: '/goodsList/getList',
+    type: 'post',
+    response(config) {
+      const { title = '', pageNo = 1, pageSize = 20 } = config.body
+      let mockList = List.filter((item) => {
+        if (title && item.title.indexOf(title) < 0) return false
+        return true
+      })
+      const pageList = mockList.filter(
+        (item, index) =>
+          index < pageSize * pageNo && index >= pageSize * (pageNo - 1)
+      )
+      return {
+        code: 200,
+        msg: 'success',
+        totalCount: count,
+        data: pageList,
+      }
+    },
+  },
+]
diff --git a/mock/controller/icon.js b/mock/controller/icon.js
new file mode 100644
index 0000000..365400a
--- /dev/null
+++ b/mock/controller/icon.js
@@ -0,0 +1,985 @@
+const data = [
+  'air-freshener',
+  'align-center',
+  'align-justify',
+  'align-left',
+  'align-right',
+  'allergies',
+  'ambulance',
+  'american-sign-language-interpreting',
+  'anchor',
+  'angle-double-down',
+  'angle-double-left',
+  'angle-double-right',
+  'angle-double-up',
+  'angle-down',
+  'angle-left',
+  'angle-right',
+  'angle-up',
+  'angry',
+  'ankh',
+  'apple-alt',
+  'archive',
+  'archway',
+  'arrow-alt-circle-down',
+  'arrow-alt-circle-left',
+  'arrow-alt-circle-right',
+  'arrow-alt-circle-up',
+  'arrow-circle-down',
+  'arrow-circle-left',
+  'arrow-circle-right',
+  'arrow-circle-up',
+  'arrow-down',
+  'arrow-left',
+  'arrow-right',
+  'arrow-up',
+  'arrows-alt',
+  'arrows-alt-h',
+  'arrows-alt-v',
+  'assistive-listening-systems',
+  'asterisk',
+  'at',
+  'atlas',
+  'atom',
+  'audio-description',
+  'award',
+  'baby',
+  'baby-carriage',
+  'backspace',
+  'backward',
+  'bacon',
+  'bahai',
+  'balance-scale',
+  'balance-scale-left',
+  'balance-scale-right',
+  'ban',
+  'band-aid',
+  'barcode',
+  'bars',
+  'baseball-ball',
+  'basketball-ball',
+  'bath',
+  'battery-empty',
+  'battery-full',
+  'battery-half',
+  'battery-quarter',
+  'battery-three-quarters',
+  'bed',
+  'beer',
+  'bell',
+  'bell-slash',
+  'bezier-curve',
+  'bible',
+  'bicycle',
+  'biking',
+  'binoculars',
+  'biohazard',
+  'birthday-cake',
+  'blender',
+  'blender-phone',
+  'blind',
+  'blog',
+  'bold',
+  'bolt',
+  'bomb',
+  'bone',
+  'bong',
+  'book',
+  'book-dead',
+  'book-medical',
+  'book-open',
+  'book-reader',
+  'bookmark',
+  'border-all',
+  'border-none',
+  'border-style',
+  'bowling-ball',
+  'box',
+  'box-open',
+  'boxes',
+  'braille',
+  'brain',
+  'bread-slice',
+  'briefcase',
+  'briefcase-medical',
+  'broadcast-tower',
+  'broom',
+  'brush',
+  'bug',
+  'building',
+  'bullhorn',
+  'bullseye',
+  'burn',
+  'bus',
+  'bus-alt',
+  'business-time',
+  'calculator',
+  'calendar',
+  'calendar-alt',
+  'calendar-check',
+  'calendar-day',
+  'calendar-minus',
+  'calendar-plus',
+  'calendar-times',
+  'calendar-week',
+  'camera',
+  'camera-retro',
+  'campground',
+  'candy-cane',
+  'cannabis',
+  'capsules',
+  'car',
+  'car-alt',
+  'car-battery',
+  'car-crash',
+  'car-side',
+  'caravan',
+  'caret-down',
+  'caret-left',
+  'caret-right',
+  'caret-square-down',
+  'caret-square-left',
+  'caret-square-right',
+  'caret-square-up',
+  'caret-up',
+  'carrot',
+  'cart-arrow-down',
+  'cart-plus',
+  'cash-register',
+  'cat',
+  'certificate',
+  'chair',
+  'chalkboard',
+  'chalkboard-teacher',
+  'charging-station',
+  'chart-area',
+  'chart-bar',
+  'chart-line',
+  'chart-pie',
+  'check',
+  'check-circle',
+  'check-double',
+  'check-square',
+  'cheese',
+  'chess',
+  'chess-bishop',
+  'chess-board',
+  'chess-king',
+  'chess-knight',
+  'chess-pawn',
+  'chess-queen',
+  'chess-rook',
+  'chevron-circle-down',
+  'chevron-circle-left',
+  'chevron-circle-right',
+  'chevron-circle-up',
+  'chevron-down',
+  'chevron-left',
+  'chevron-right',
+  'chevron-up',
+  'child',
+  'church',
+  'circle',
+  'circle-notch',
+  'city',
+  'clinic-medical',
+  'clipboard',
+  'clipboard-check',
+  'clipboard-list',
+  'clock',
+  'clone',
+  'closed-captioning',
+  'cloud',
+  'cloud-download-alt',
+  'cloud-meatball',
+  'cloud-moon',
+  'cloud-moon-rain',
+  'cloud-rain',
+  'cloud-showers-heavy',
+  'cloud-sun',
+  'cloud-sun-rain',
+  'cloud-upload-alt',
+  'cocktail',
+  'code',
+  'code-branch',
+  'coffee',
+  'cog',
+  'cogs',
+  'coins',
+  'columns',
+  'comment',
+  'comment-alt',
+  'comment-dollar',
+  'comment-dots',
+  'comment-medical',
+  'comment-slash',
+  'comments',
+  'comments-dollar',
+  'compact-disc',
+  'compass',
+  'compress',
+  'compress-alt',
+  'compress-arrows-alt',
+  'concierge-bell',
+  'cookie',
+  'cookie-bite',
+  'copy',
+  'copyright',
+  'couch',
+  'credit-card',
+  'crop',
+  'crop-alt',
+  'cross',
+  'crosshairs',
+  'crow',
+  'crown',
+  'crutch',
+  'cube',
+  'cubes',
+  'cut',
+  'database',
+  'deaf',
+  'democrat',
+  'desktop',
+  'dharmachakra',
+  'diagnoses',
+  'dice',
+  'dice-d20',
+  'dice-d6',
+  'dice-five',
+  'dice-four',
+  'dice-one',
+  'dice-six',
+  'dice-three',
+  'dice-two',
+  'digital-tachograph',
+  'directions',
+  'divide',
+  'dizzy',
+  'dna',
+  'dog',
+  'dollar-sign',
+  'dolly',
+  'dolly-flatbed',
+  'donate',
+  'door-closed',
+  'door-open',
+  'dot-circle',
+  'dove',
+  'download',
+  'drafting-compass',
+  'dragon',
+  'draw-polygon',
+  'drum',
+  'drum-steelpan',
+  'drumstick-bite',
+  'dumbbell',
+  'dumpster',
+  'dumpster-fire',
+  'dungeon',
+  'edit',
+  'egg',
+  'eject',
+  'ellipsis-h',
+  'ellipsis-v',
+  'envelope',
+  'envelope-open',
+  'envelope-open-text',
+  'envelope-square',
+  'equals',
+  'eraser',
+  'ethernet',
+  'euro-sign',
+  'exchange-alt',
+  'exclamation',
+  'exclamation-circle',
+  'exclamation-triangle',
+  'expand',
+  'expand-alt',
+  'expand-arrows-alt',
+  'external-link-alt',
+  'external-link-square-alt',
+  'eye',
+  'eye-dropper',
+  'eye-slash',
+  'fan',
+  'fast-backward',
+  'fast-forward',
+  'fax',
+  'feather',
+  'feather-alt',
+  'female',
+  'fighter-jet',
+  'file',
+  'file-alt',
+  'file-archive',
+  'file-audio',
+  'file-code',
+  'file-contract',
+  'file-csv',
+  'file-download',
+  'file-excel',
+  'file-export',
+  'file-image',
+  'file-import',
+  'file-invoice',
+  'file-invoice-dollar',
+  'file-medical',
+  'file-medical-alt',
+  'file-pdf',
+  'file-powerpoint',
+  'file-prescription',
+  'file-signature',
+  'file-upload',
+  'file-video',
+  'file-word',
+  'fill',
+  'fill-drip',
+  'film',
+  'filter',
+  'fingerprint',
+  'fire',
+  'fire-alt',
+  'fire-extinguisher',
+  'first-aid',
+  'fish',
+  'fist-raised',
+  'flag',
+  'flag-checkered',
+  'flag-usa',
+  'flask',
+  'flushed',
+  'folder',
+  'folder-minus',
+  'folder-open',
+  'folder-plus',
+  'font',
+  'football-ball',
+  'forward',
+  'frog',
+  'frown',
+  'frown-open',
+  'funnel-dollar',
+  'futbol',
+  'gamepad',
+  'gas-pump',
+  'gavel',
+  'gem',
+  'genderless',
+  'ghost',
+  'gift',
+  'gifts',
+  'glass-cheers',
+  'glass-martini',
+  'glass-martini-alt',
+  'glass-whiskey',
+  'glasses',
+  'globe',
+  'globe-africa',
+  'globe-americas',
+  'globe-asia',
+  'globe-europe',
+  'golf-ball',
+  'gopuram',
+  'graduation-cap',
+  'greater-than',
+  'greater-than-equal',
+  'grimace',
+  'grin',
+  'grin-alt',
+  'grin-beam',
+  'grin-beam-sweat',
+  'grin-hearts',
+  'grin-squint',
+  'grin-squint-tears',
+  'grin-stars',
+  'grin-tears',
+  'grin-tongue',
+  'grin-tongue-squint',
+  'grin-tongue-wink',
+  'grin-wink',
+  'grip-horizontal',
+  'grip-lines',
+  'grip-lines-vertical',
+  'grip-vertical',
+  'guitar',
+  'h-square',
+  'hamburger',
+  'hammer',
+  'hamsa',
+  'hand-holding',
+  'hand-holding-heart',
+  'hand-holding-usd',
+  'hand-lizard',
+  'hand-middle-finger',
+  'hand-paper',
+  'hand-peace',
+  'hand-point-down',
+  'hand-point-left',
+  'hand-point-right',
+  'hand-point-up',
+  'hand-pointer',
+  'hand-rock',
+  'hand-scissors',
+  'hand-spock',
+  'hands',
+  'hands-helping',
+  'handshake',
+  'hanukiah',
+  'hard-hat',
+  'hashtag',
+  'hat-cowboy',
+  'hat-cowboy-side',
+  'hat-wizard',
+  'hdd',
+  'heading',
+  'headphones',
+  'headphones-alt',
+  'headset',
+  'heart',
+  'heart-broken',
+  'heartbeat',
+  'helicopter',
+  'highlighter',
+  'hiking',
+  'hippo',
+  'history',
+  'hockey-puck',
+  'holly-berry',
+  'home',
+  'horse',
+  'horse-head',
+  'hospital',
+  'hospital-alt',
+  'hospital-symbol',
+  'hot-tub',
+  'hotdog',
+  'hotel',
+  'hourglass',
+  'hourglass-end',
+  'hourglass-half',
+  'hourglass-start',
+  'house-damage',
+  'hryvnia',
+  'i-cursor',
+  'ice-cream',
+  'icicles',
+  'icons',
+  'id-badge',
+  'id-card',
+  'id-card-alt',
+  'igloo',
+  'image',
+  'images',
+  'inbox',
+  'indent',
+  'industry',
+  'infinity',
+  'info',
+  'info-circle',
+  'italic',
+  'jedi',
+  'joint',
+  'journal-whills',
+  'kaaba',
+  'key',
+  'keyboard',
+  'khanda',
+  'kiss',
+  'kiss-beam',
+  'kiss-wink-heart',
+  'kiwi-bird',
+  'landmark',
+  'language',
+  'laptop',
+  'laptop-code',
+  'laptop-medical',
+  'laugh',
+  'laugh-beam',
+  'laugh-squint',
+  'laugh-wink',
+  'layer-group',
+  'leaf',
+  'lemon',
+  'less-than',
+  'less-than-equal',
+  'level-down-alt',
+  'level-up-alt',
+  'life-ring',
+  'lightbulb',
+  'link',
+  'lira-sign',
+  'list',
+  'list-alt',
+  'list-ol',
+  'list-ul',
+  'location-arrow',
+  'lock',
+  'lock-open',
+  'long-arrow-alt-down',
+  'long-arrow-alt-left',
+  'long-arrow-alt-right',
+  'long-arrow-alt-up',
+  'low-vision',
+  'luggage-cart',
+  'magic',
+  'magnet',
+  'mail-bulk',
+  'male',
+  'map',
+  'map-marked',
+  'map-marked-alt',
+  'map-marker',
+  'map-marker-alt',
+  'map-pin',
+  'map-signs',
+  'marker',
+  'mars',
+  'mars-double',
+  'mars-stroke',
+  'mars-stroke-h',
+  'mars-stroke-v',
+  'mask',
+  'medal',
+  'medkit',
+  'meh',
+  'meh-blank',
+  'meh-rolling-eyes',
+  'memory',
+  'menorah',
+  'mercury',
+  'meteor',
+  'microchip',
+  'microphone',
+  'microphone-alt',
+  'microphone-alt-slash',
+  'microphone-slash',
+  'microscope',
+  'minus',
+  'minus-circle',
+  'minus-square',
+  'mitten',
+  'mobile',
+  'mobile-alt',
+  'money-bill',
+  'money-bill-alt',
+  'money-bill-wave',
+  'money-bill-wave-alt',
+  'money-check',
+  'money-check-alt',
+  'monument',
+  'moon',
+  'mortar-pestle',
+  'mosque',
+  'motorcycle',
+  'mountain',
+  'mouse',
+  'mouse-pointer',
+  'mug-hot',
+  'music',
+  'network-wired',
+  'neuter',
+  'newspaper',
+  'not-equal',
+  'notes-medical',
+  'object-group',
+  'object-ungroup',
+  'oil-can',
+  'om',
+  'otter',
+  'outdent',
+  'pager',
+  'paint-brush',
+  'paint-roller',
+  'palette',
+  'pallet',
+  'paper-plane',
+  'paperclip',
+  'parachute-box',
+  'paragraph',
+  'parking',
+  'passport',
+  'pastafarianism',
+  'paste',
+  'pause',
+  'pause-circle',
+  'paw',
+  'peace',
+  'pen',
+  'pen-alt',
+  'pen-fancy',
+  'pen-nib',
+  'pen-square',
+  'pencil-alt',
+  'pencil-ruler',
+  'people-carry',
+  'pepper-hot',
+  'percent',
+  'percentage',
+  'person-booth',
+  'phone',
+  'phone-alt',
+  'phone-slash',
+  'phone-square',
+  'phone-square-alt',
+  'phone-volume',
+  'photo-video',
+  'piggy-bank',
+  'pills',
+  'pizza-slice',
+  'place-of-worship',
+  'plane',
+  'plane-arrival',
+  'plane-departure',
+  'play',
+  'play-circle',
+  'plug',
+  'plus',
+  'plus-circle',
+  'plus-square',
+  'podcast',
+  'poll',
+  'poll-h',
+  'poo',
+  'poo-storm',
+  'poop',
+  'portrait',
+  'pound-sign',
+  'power-off',
+  'pray',
+  'praying-hands',
+  'prescription',
+  'prescription-bottle',
+  'prescription-bottle-alt',
+  'print',
+  'procedures',
+  'project-diagram',
+  'puzzle-piece',
+  'qrcode',
+  'question',
+  'question-circle',
+  'quidditch',
+  'quote-left',
+  'quote-right',
+  'quran',
+  'radiation',
+  'radiation-alt',
+  'rainbow',
+  'random',
+  'receipt',
+  'record-vinyl',
+  'recycle',
+  'redo',
+  'redo-alt',
+  'registered',
+  'remove-format',
+  'reply',
+  'reply-all',
+  'republican',
+  'restroom',
+  'retweet',
+  'ribbon',
+  'ring',
+  'road',
+  'robot',
+  'rocket',
+  'route',
+  'rss',
+  'rss-square',
+  'ruble-sign',
+  'ruler',
+  'ruler-combined',
+  'ruler-horizontal',
+  'ruler-vertical',
+  'running',
+  'rupee-sign',
+  'sad-cry',
+  'sad-tear',
+  'satellite',
+  'satellite-dish',
+  'save',
+  'school',
+  'screwdriver',
+  'scroll',
+  'sd-card',
+  'search',
+  'search-dollar',
+  'search-location',
+  'search-minus',
+  'search-plus',
+  'seedling',
+  'server',
+  'shapes',
+  'share',
+  'share-alt',
+  'share-alt-square',
+  'share-square',
+  'shekel-sign',
+  'shield-alt',
+  'ship',
+  'shipping-fast',
+  'shoe-prints',
+  'shopping-bag',
+  'shopping-basket',
+  'shopping-cart',
+  'shower',
+  'shuttle-van',
+  'sign',
+  'sign-in-alt',
+  'sign-language',
+  'sign-out-alt',
+  'signal',
+  'signature',
+  'sim-card',
+  'sitemap',
+  'skating',
+  'skiing',
+  'skiing-nordic',
+  'skull',
+  'skull-crossbones',
+  'slash',
+  'sleigh',
+  'sliders-h',
+  'smile',
+  'smile-beam',
+  'smile-wink',
+  'smog',
+  'smoking',
+  'smoking-ban',
+  'sms',
+  'snowboarding',
+  'snowflake',
+  'snowman',
+  'snowplow',
+  'socks',
+  'solar-panel',
+  'sort',
+  'sort-alpha-down',
+  'sort-alpha-down-alt',
+  'sort-alpha-up',
+  'sort-alpha-up-alt',
+  'sort-amount-down',
+  'sort-amount-down-alt',
+  'sort-amount-up',
+  'sort-amount-up-alt',
+  'sort-down',
+  'sort-numeric-down',
+  'sort-numeric-down-alt',
+  'sort-numeric-up',
+  'sort-numeric-up-alt',
+  'sort-up',
+  'spa',
+  'space-shuttle',
+  'spell-check',
+  'spider',
+  'spinner',
+  'splotch',
+  'spray-can',
+  'square',
+  'square-full',
+  'square-root-alt',
+  'stamp',
+  'star',
+  'star-and-crescent',
+  'star-half',
+  'star-half-alt',
+  'star-of-david',
+  'star-of-life',
+  'step-backward',
+  'step-forward',
+  'stethoscope',
+  'sticky-note',
+  'stop',
+  'stop-circle',
+  'stopwatch',
+  'store',
+  'store-alt',
+  'stream',
+  'street-view',
+  'strikethrough',
+  'stroopwafel',
+  'subscript',
+  'subway',
+  'suitcase',
+  'suitcase-rolling',
+  'sun',
+  'superscript',
+  'surprise',
+  'swatchbook',
+  'swimmer',
+  'swimming-pool',
+  'synagogue',
+  'sync',
+  'sync-alt',
+  'syringe',
+  'table',
+  'table-tennis',
+  'tablet',
+  'tablet-alt',
+  'tablets',
+  'tachometer-alt',
+  'tag',
+  'tags',
+  'tape',
+  'tasks',
+  'taxi',
+  'teeth',
+  'teeth-open',
+  'temperature-high',
+  'temperature-low',
+  'tenge',
+  'terminal',
+  'text-height',
+  'text-width',
+  'th',
+  'th-large',
+  'th-list',
+  'theater-masks',
+  'thermometer',
+  'thermometer-empty',
+  'thermometer-full',
+  'thermometer-half',
+  'thermometer-quarter',
+  'thermometer-three-quarters',
+  'thumbs-down',
+  'thumbs-up',
+  'thumbtack',
+  'ticket-alt',
+  'times',
+  'times-circle',
+  'tint',
+  'tint-slash',
+  'tired',
+  'toggle-off',
+  'toggle-on',
+  'toilet',
+  'toilet-paper',
+  'toolbox',
+  'tools',
+  'tooth',
+  'torah',
+  'torii-gate',
+  'tractor',
+  'trademark',
+  'traffic-light',
+  'trailer',
+  'train',
+  'tram',
+  'transgender',
+  'transgender-alt',
+  'trash',
+  'trash-alt',
+  'trash-restore',
+  'trash-restore-alt',
+  'tree',
+  'trophy',
+  'truck',
+  'truck-loading',
+  'truck-monster',
+  'truck-moving',
+  'truck-pickup',
+  'tshirt',
+  'tty',
+  'tv',
+  'umbrella',
+  'umbrella-beach',
+  'underline',
+  'undo',
+  'undo-alt',
+  'universal-access',
+  'university',
+  'unlink',
+  'unlock',
+  'unlock-alt',
+  'upload',
+  'user',
+  'user-alt',
+  'user-alt-slash',
+  'user-astronaut',
+  'user-check',
+  'user-circle',
+  'user-clock',
+  'user-cog',
+  'user-edit',
+  'user-friends',
+  'user-graduate',
+  'user-injured',
+  'user-lock',
+  'user-md',
+  'user-minus',
+  'user-ninja',
+  'user-nurse',
+  'user-plus',
+  'user-secret',
+  'user-shield',
+  'user-slash',
+  'user-tag',
+  'user-tie',
+  'user-times',
+  'users',
+  'users-cog',
+  'utensil-spoon',
+  'utensils',
+  'vector-square',
+  'venus',
+  'venus-double',
+  'venus-mars',
+  'vial',
+  'vials',
+  'video',
+  'video-slash',
+  'vihara',
+  'voicemail',
+  'volleyball-ball',
+  'volume-down',
+  'volume-mute',
+  'volume-off',
+  'volume-up',
+  'vote-yea',
+  'vr-cardboard',
+  'walking',
+  'wallet',
+  'warehouse',
+  'water',
+  'wave-square',
+  'weight',
+  'weight-hanging',
+  'wheelchair',
+  'wifi',
+  'wind',
+  'window-close',
+  'window-maximize',
+  'window-minimize',
+  'window-restore',
+  'wine-bottle',
+  'wine-glass',
+  'wine-glass-alt',
+  'won-sign',
+  'wrench',
+  'x-ray',
+  'yen-sign',
+  'yin-yang',
+]
+module.exports = [
+  {
+    url: '/icon/getList',
+    type: 'post',
+    response(config) {
+      const { title, pageNo = 1, pageSize = 72 } = config.body
+      let mockList = data.filter((item) => {
+        if (title && item.indexOf(title) < 0) return false
+        return true
+      })
+      const pageList = mockList.filter(
+        (item, index) =>
+          index < pageSize * pageNo && index >= pageSize * (pageNo - 1)
+      )
+      return {
+        code: 200,
+        msg: 'success',
+        totalCount: mockList.length,
+        data: pageList,
+      }
+    },
+  },
+]
diff --git a/mock/controller/menuManagement.js b/mock/controller/menuManagement.js
new file mode 100644
index 0000000..b86a178
--- /dev/null
+++ b/mock/controller/menuManagement.js
@@ -0,0 +1,51 @@
+module.exports = [
+  {
+    url: '/menuManagement/getTree',
+    type: 'post',
+    response() {
+      return {
+        code: 200,
+        msg: 'success',
+        totalCount: 999,
+        data: [
+          {
+            id: 'root',
+            label: '鍏ㄩ儴瑙掕壊',
+            children: [
+              {
+                id: '@id',
+                permission: 'admin',
+                label: 'admin瑙掕壊',
+              },
+              {
+                id: '@id',
+                permission: 'editor',
+                label: 'editor瑙掕壊',
+              },
+            ],
+          },
+        ],
+      }
+    },
+  },
+  {
+    url: '/menuManagement/doEdit',
+    type: 'post',
+    response() {
+      return {
+        code: 200,
+        msg: '妯℃嫙淇濆瓨鎴愬姛',
+      }
+    },
+  },
+  {
+    url: '/menuManagement/doDelete',
+    type: 'post',
+    response() {
+      return {
+        code: 200,
+        msg: '妯℃嫙鍒犻櫎鎴愬姛',
+      }
+    },
+  },
+]
diff --git a/mock/controller/notice.js b/mock/controller/notice.js
new file mode 100644
index 0000000..1ec6104
--- /dev/null
+++ b/mock/controller/notice.js
@@ -0,0 +1,33 @@
+const data = [
+  {
+    title:
+      '娓╅Θ鎻愮ず锛氶泦鎴愮増铏藉姛鑳戒赴瀵岋紝浣嗗啑浣欎緷璧栬繃澶氾紝寤鸿寮�鍙戞椂浣跨敤鍩虹鐗堣繘琛屽紑鍙戯紝 寤哄厷鐧惧勾鎯婂枩绂忓埄锛屼拱涓�寰椾簩锛屽叏骞存渶浣庝环锛屼竴骞翠粎姝や竴娆★細<a target="_blank" href="http://vue-admin-beautiful.com/authorization">鐐规垜鑾峰彇浼樻儬绂忓埄</a>',
+    closable: false,
+    type: 'success',
+  },
+  {
+    title:
+      '浣滆�呭瘎璇細鎰熻阿Star锛屾劅鎭╃浉閬囷紝鎰夸笘闂寸編濂戒笌鎴戜滑鐜幆鐩告墸锛屽姞娌癸紒灞忓箷鍓嶇殑鎴戜滑锛屾墦鐮存姊忥紝鍧氬畧鍒濆績銆傚叾瀹炰汉鐢熸敼鍙樺懡杩愮殑鏈轰細骞舵病鏈夊お澶氾紝鎴戜滑骞朵笉鏄笉浼樼锛屾垜浠篃骞朵笉鏄竴鏃犳槸澶勶紝鎴戜滑涔熷笇鏈涢┗瓒冲北宸呰浼椾汉浠版湜锛屼篃璁告垜浠己灏戠殑鍙槸涓�涓満浼氾紝缂哄皯鐨勫彧鏄敓鍛戒腑鐨勫甯堬紝鎴戝笇鏈涜繖涓鏋跺府鍔╁埌鏇村鐨勪汉锛屽笇鏈涙湁涓�澶╋紝鎴戜滑闈㈣瘯鐨勬椂鍊欎笉鍐嶈儐鎬紝甯屾湜鏈変竴澶╁埆浜虹湅鍒扮殑涓嶄粎浠呮槸浣犵殑鍔姏锛岃繕鏈変綘鐨勫姛鎴愬悕灏憋紝鍑轰汉澶村湴銆�',
+    closable: false,
+    type: 'warning',
+  },
+  {
+    title:
+      '闅忕瑪锛氭垜涓�鐩村湪瀵绘壘寮�婧愮殑鐪熻皼锛屾垜涓�鐩村啀鎯充粈涔堟槸寮�婧愶紝鎴戜竴寮�濮嬭寰楀厤璐瑰氨鏄紑婧愶紝濂藉儚鍙堜笉鏄�傛垜鐞嗚В鐨勫紑婧愭槸锛氫綘涔熷紑婧愶紝鎴戜篃寮�婧愶紝澶у涓�璧疯础鐚紝鐩镐簰甯姪銆傛垜鏈�鎷呭績鐨勪簨鎯呮槸锛氭垜涓�涓皬浜虹墿锛屽幓浼哄�欎竴浼楃殑浼告墜鍏氾紝鎴戞兂锛岃繖涓嶆槸寮�婧愯鏈夌殑姘涘洿銆傛垜杩樺お骞磋交锛屼笉鎳備粈涔堟槸鏍煎眬锛屾垜鍙煡閬擄紝鏃犵鐨勫府鍔╀粬浜猴紝鑳界粰鎴戝甫鏉ュ揩涔愶紝鍗翠笉鑳界粰鎴戝甫鏉ユ敹鍏ワ紝褰撶劧锛屾湁鏃跺�欙紝蹇箰瀵规垜鏉ヨ灏卞凡缁忚冻澶熶簡銆傚彲鎯滄垜鏄竴涓汉锛屾病鏈夌簿鍔涘府鍔╁埌姣忎竴涓汉锛屽彲鎯滆繖涓笘鐣岄渶瑕佽禋閽憋紝鎵嶈兘杩囦笂骞冲嚒鐨勭敓娲伙紝鍙儨浜嗘垜鐨勬ⅵ鎯筹紝杩欎釜鐗╂妯祦鐨勬椂浠o紝鐞嗘兂涓讳箟鐨勬垜浠紝鍗充娇鍐呭績鍧氬喅濡傞搧锛屼篃浼间箮瀵告闅捐銆�',
+    closable: false,
+    type: 'success',
+  },
+]
+module.exports = [
+  {
+    url: '/notice/getList',
+    type: 'post',
+    response() {
+      return {
+        code: 200,
+        msg: 'success',
+        data,
+      }
+    },
+  },
+]
diff --git a/mock/controller/personalCenter.js b/mock/controller/personalCenter.js
new file mode 100644
index 0000000..0db25e7
--- /dev/null
+++ b/mock/controller/personalCenter.js
@@ -0,0 +1,42 @@
+const { mock } = require('mockjs')
+
+module.exports = [
+  {
+    url: '/personalCenter/getList',
+    type: 'post',
+    response(config) {
+      return {
+        code: 200,
+        msg: 'success',
+        totalCount: 999,
+        data: mock({
+          'data|10': [
+            {
+              id: '@id',
+            },
+          ],
+        }).data,
+      }
+    },
+  },
+  {
+    url: '/personalCenter/doEdit',
+    type: 'post',
+    response() {
+      return {
+        code: 200,
+        msg: '妯℃嫙淇濆瓨鎴愬姛',
+      }
+    },
+  },
+  {
+    url: '/personalCenter/doDelete',
+    type: 'post',
+    response() {
+      return {
+        code: 200,
+        msg: '妯℃嫙鍒犻櫎鎴愬姛',
+      }
+    },
+  },
+]
diff --git a/mock/controller/remixIcon.js b/mock/controller/remixIcon.js
new file mode 100644
index 0000000..db299ce
--- /dev/null
+++ b/mock/controller/remixIcon.js
@@ -0,0 +1,2294 @@
+const data = [
+  '24-hours-fill',
+  '24-hours-line',
+  '4k-fill',
+  '4k-line',
+  'a-b',
+  'account-box-fill',
+  'account-box-line',
+  'account-circle-fill',
+  'account-circle-line',
+  'account-pin-box-fill',
+  'account-pin-box-line',
+  'account-pin-circle-fill',
+  'account-pin-circle-line',
+  'add-box-fill',
+  'add-box-line',
+  'add-circle-fill',
+  'add-circle-line',
+  'add-fill',
+  'add-line',
+  'admin-fill',
+  'admin-line',
+  'airplay-fill',
+  'airplay-line',
+  'alarm-fill',
+  'alarm-line',
+  'alarm-warning-fill',
+  'alarm-warning-line',
+  'album-fill',
+  'album-line',
+  'alert-fill',
+  'alert-line',
+  'aliens-fill',
+  'aliens-line',
+  'align-bottom',
+  'align-center',
+  'align-justify',
+  'align-left',
+  'align-right',
+  'align-top',
+  'align-vertically',
+  'alipay-fill',
+  'alipay-line',
+  'amazon-fill',
+  'amazon-line',
+  'anchor-fill',
+  'anchor-line',
+  'ancient-gate-fill',
+  'ancient-gate-line',
+  'ancient-pavilion-fill',
+  'ancient-pavilion-line',
+  'android-fill',
+  'android-line',
+  'angularjs-fill',
+  'angularjs-line',
+  'anticlockwise-2-fill',
+  'anticlockwise-2-line',
+  'anticlockwise-fill',
+  'anticlockwise-line',
+  'app-store-fill',
+  'app-store-line',
+  'apple-fill',
+  'apple-line',
+  'apps-2-fill',
+  'apps-2-line',
+  'apps-fill',
+  'apps-line',
+  'archive-drawer-fill',
+  'archive-drawer-line',
+  'archive-fill',
+  'archive-line',
+  'arrow-down-circle-fill',
+  'arrow-down-circle-line',
+  'arrow-down-fill',
+  'arrow-down-line',
+  'arrow-down-s-fill',
+  'arrow-down-s-line',
+  'arrow-drop-down-fill',
+  'arrow-drop-down-line',
+  'arrow-drop-left-fill',
+  'arrow-drop-left-line',
+  'arrow-drop-right-fill',
+  'arrow-drop-right-line',
+  'arrow-drop-up-fill',
+  'arrow-drop-up-line',
+  'arrow-go-back-fill',
+  'arrow-go-back-line',
+  'arrow-go-forward-fill',
+  'arrow-go-forward-line',
+  'arrow-left-circle-fill',
+  'arrow-left-circle-line',
+  'arrow-left-down-fill',
+  'arrow-left-down-line',
+  'arrow-left-fill',
+  'arrow-left-line',
+  'arrow-left-right-fill',
+  'arrow-left-right-line',
+  'arrow-left-s-fill',
+  'arrow-left-s-line',
+  'arrow-left-up-fill',
+  'arrow-left-up-line',
+  'arrow-right-circle-fill',
+  'arrow-right-circle-line',
+  'arrow-right-down-fill',
+  'arrow-right-down-line',
+  'arrow-right-fill',
+  'arrow-right-line',
+  'arrow-right-s-fill',
+  'arrow-right-s-line',
+  'arrow-right-up-fill',
+  'arrow-right-up-line',
+  'arrow-up-circle-fill',
+  'arrow-up-circle-line',
+  'arrow-up-down-fill',
+  'arrow-up-down-line',
+  'arrow-up-fill',
+  'arrow-up-line',
+  'arrow-up-s-fill',
+  'arrow-up-s-line',
+  'artboard-2-fill',
+  'artboard-2-line',
+  'artboard-fill',
+  'artboard-line',
+  'article-fill',
+  'article-line',
+  'aspect-ratio-fill',
+  'aspect-ratio-line',
+  'asterisk',
+  'at-fill',
+  'at-line',
+  'attachment-2',
+  'attachment-fill',
+  'attachment-line',
+  'auction-fill',
+  'auction-line',
+  'award-fill',
+  'award-line',
+  'baidu-fill',
+  'baidu-line',
+  'ball-pen-fill',
+  'ball-pen-line',
+  'bank-card-2-fill',
+  'bank-card-2-line',
+  'bank-card-fill',
+  'bank-card-line',
+  'bank-fill',
+  'bank-line',
+  'bar-chart-2-fill',
+  'bar-chart-2-line',
+  'bar-chart-box-fill',
+  'bar-chart-box-line',
+  'bar-chart-fill',
+  'bar-chart-grouped-fill',
+  'bar-chart-grouped-line',
+  'bar-chart-horizontal-fill',
+  'bar-chart-horizontal-line',
+  'bar-chart-line',
+  'barcode-box-fill',
+  'barcode-box-line',
+  'barcode-fill',
+  'barcode-line',
+  'barricade-fill',
+  'barricade-line',
+  'base-station-fill',
+  'base-station-line',
+  'basketball-fill',
+  'basketball-line',
+  'battery-2-charge-fill',
+  'battery-2-charge-line',
+  'battery-2-fill',
+  'battery-2-line',
+  'battery-charge-fill',
+  'battery-charge-line',
+  'battery-fill',
+  'battery-line',
+  'battery-low-fill',
+  'battery-low-line',
+  'battery-saver-fill',
+  'battery-saver-line',
+  'battery-share-fill',
+  'battery-share-line',
+  'bear-smile-fill',
+  'bear-smile-line',
+  'behance-fill',
+  'behance-line',
+  'bell-fill',
+  'bell-line',
+  'bike-fill',
+  'bike-line',
+  'bilibili-fill',
+  'bilibili-line',
+  'bill-fill',
+  'bill-line',
+  'billiards-fill',
+  'billiards-line',
+  'bit-coin-fill',
+  'bit-coin-line',
+  'blaze-fill',
+  'blaze-line',
+  'bluetooth-connect-fill',
+  'bluetooth-connect-line',
+  'bluetooth-fill',
+  'bluetooth-line',
+  'blur-off-fill',
+  'blur-off-line',
+  'body-scan-fill',
+  'body-scan-line',
+  'bold',
+  'book-2-fill',
+  'book-2-line',
+  'book-3-fill',
+  'book-3-line',
+  'book-fill',
+  'book-line',
+  'book-mark-fill',
+  'book-mark-line',
+  'book-open-fill',
+  'book-open-line',
+  'book-read-fill',
+  'book-read-line',
+  'booklet-fill',
+  'booklet-line',
+  'bookmark-2-fill',
+  'bookmark-2-line',
+  'bookmark-3-fill',
+  'bookmark-3-line',
+  'bookmark-fill',
+  'bookmark-line',
+  'boxing-fill',
+  'boxing-line',
+  'braces-fill',
+  'braces-line',
+  'brackets-fill',
+  'brackets-line',
+  'briefcase-2-fill',
+  'briefcase-2-line',
+  'briefcase-3-fill',
+  'briefcase-3-line',
+  'briefcase-4-fill',
+  'briefcase-4-line',
+  'briefcase-5-fill',
+  'briefcase-5-line',
+  'briefcase-fill',
+  'briefcase-line',
+  'bring-forward',
+  'bring-to-front',
+  'broadcast-fill',
+  'broadcast-line',
+  'brush-2-fill',
+  'brush-2-line',
+  'brush-3-fill',
+  'brush-3-line',
+  'brush-4-fill',
+  'brush-4-line',
+  'brush-fill',
+  'brush-line',
+  'bubble-chart-fill',
+  'bubble-chart-line',
+  'bug-2-fill',
+  'bug-2-line',
+  'bug-fill',
+  'bug-line',
+  'building-2-fill',
+  'building-2-line',
+  'building-3-fill',
+  'building-3-line',
+  'building-4-fill',
+  'building-4-line',
+  'building-fill',
+  'building-line',
+  'bus-2-fill',
+  'bus-2-line',
+  'bus-fill',
+  'bus-line',
+  'bus-wifi-fill',
+  'bus-wifi-line',
+  'cactus-fill',
+  'cactus-line',
+  'cake-2-fill',
+  'cake-2-line',
+  'cake-3-fill',
+  'cake-3-line',
+  'cake-fill',
+  'cake-line',
+  'calculator-fill',
+  'calculator-line',
+  'calendar-2-fill',
+  'calendar-2-line',
+  'calendar-check-fill',
+  'calendar-check-line',
+  'calendar-event-fill',
+  'calendar-event-line',
+  'calendar-fill',
+  'calendar-line',
+  'calendar-todo-fill',
+  'calendar-todo-line',
+  'camera-2-fill',
+  'camera-2-line',
+  'camera-3-fill',
+  'camera-3-line',
+  'camera-fill',
+  'camera-lens-fill',
+  'camera-lens-line',
+  'camera-line',
+  'camera-off-fill',
+  'camera-off-line',
+  'camera-switch-fill',
+  'camera-switch-line',
+  'capsule-fill',
+  'capsule-line',
+  'car-fill',
+  'car-line',
+  'car-washing-fill',
+  'car-washing-line',
+  'caravan-fill',
+  'caravan-line',
+  'cast-fill',
+  'cast-line',
+  'cellphone-fill',
+  'cellphone-line',
+  'celsius-fill',
+  'celsius-line',
+  'centos-fill',
+  'centos-line',
+  'character-recognition-fill',
+  'character-recognition-line',
+  'charging-pile-2-fill',
+  'charging-pile-2-line',
+  'charging-pile-fill',
+  'charging-pile-line',
+  'chat-1-fill',
+  'chat-1-line',
+  'chat-2-fill',
+  'chat-2-line',
+  'chat-3-fill',
+  'chat-3-line',
+  'chat-4-fill',
+  'chat-4-line',
+  'chat-check-fill',
+  'chat-check-line',
+  'chat-delete-fill',
+  'chat-delete-line',
+  'chat-download-fill',
+  'chat-download-line',
+  'chat-follow-up-fill',
+  'chat-follow-up-line',
+  'chat-forward-fill',
+  'chat-forward-line',
+  'chat-heart-fill',
+  'chat-heart-line',
+  'chat-history-fill',
+  'chat-history-line',
+  'chat-new-fill',
+  'chat-new-line',
+  'chat-off-fill',
+  'chat-off-line',
+  'chat-poll-fill',
+  'chat-poll-line',
+  'chat-private-fill',
+  'chat-private-line',
+  'chat-quote-fill',
+  'chat-quote-line',
+  'chat-settings-fill',
+  'chat-settings-line',
+  'chat-smile-2-fill',
+  'chat-smile-2-line',
+  'chat-smile-3-fill',
+  'chat-smile-3-line',
+  'chat-smile-fill',
+  'chat-smile-line',
+  'chat-upload-fill',
+  'chat-upload-line',
+  'chat-voice-fill',
+  'chat-voice-line',
+  'check-double-fill',
+  'check-double-line',
+  'check-fill',
+  'check-line',
+  'checkbox-blank-circle-fill',
+  'checkbox-blank-circle-line',
+  'checkbox-blank-fill',
+  'checkbox-blank-line',
+  'checkbox-circle-fill',
+  'checkbox-circle-line',
+  'checkbox-fill',
+  'checkbox-indeterminate-fill',
+  'checkbox-indeterminate-line',
+  'checkbox-line',
+  'checkbox-multiple-blank-fill',
+  'checkbox-multiple-blank-line',
+  'checkbox-multiple-fill',
+  'checkbox-multiple-line',
+  'china-railway-fill',
+  'china-railway-line',
+  'chrome-fill',
+  'chrome-line',
+  'clapperboard-fill',
+  'clapperboard-line',
+  'clipboard-fill',
+  'clipboard-line',
+  'clockwise-2-fill',
+  'clockwise-2-line',
+  'clockwise-fill',
+  'clockwise-line',
+  'close-circle-fill',
+  'close-circle-line',
+  'close-fill',
+  'close-line',
+  'closed-captioning-fill',
+  'closed-captioning-line',
+  'cloud-fill',
+  'cloud-line',
+  'cloud-off-fill',
+  'cloud-off-line',
+  'cloud-windy-fill',
+  'cloud-windy-line',
+  'cloudy-2-fill',
+  'cloudy-2-line',
+  'cloudy-fill',
+  'cloudy-line',
+  'code-box-fill',
+  'code-box-line',
+  'code-fill',
+  'code-line',
+  'code-s-fill',
+  'code-s-line',
+  'code-s-slash-fill',
+  'code-s-slash-line',
+  'code-view',
+  'codepen-fill',
+  'codepen-line',
+  'coin-fill',
+  'coin-line',
+  'coins-fill',
+  'coins-line',
+  'collage-fill',
+  'collage-line',
+  'command-fill',
+  'command-line',
+  'community-fill',
+  'community-line',
+  'compass-2-fill',
+  'compass-2-line',
+  'compass-3-fill',
+  'compass-3-line',
+  'compass-4-fill',
+  'compass-4-line',
+  'compass-discover-fill',
+  'compass-discover-line',
+  'compass-fill',
+  'compass-line',
+  'compasses-2-fill',
+  'compasses-2-line',
+  'compasses-fill',
+  'compasses-line',
+  'computer-fill',
+  'computer-line',
+  'contacts-book-2-fill',
+  'contacts-book-2-line',
+  'contacts-book-fill',
+  'contacts-book-line',
+  'contacts-book-upload-fill',
+  'contacts-book-upload-line',
+  'contacts-fill',
+  'contacts-line',
+  'contrast-2-fill',
+  'contrast-2-line',
+  'contrast-drop-2-fill',
+  'contrast-drop-2-line',
+  'contrast-drop-fill',
+  'contrast-drop-line',
+  'contrast-fill',
+  'contrast-line',
+  'copper-coin-fill',
+  'copper-coin-line',
+  'copper-diamond-fill',
+  'copper-diamond-line',
+  'copyleft-fill',
+  'copyleft-line',
+  'copyright-fill',
+  'copyright-line',
+  'coreos-fill',
+  'coreos-line',
+  'coupon-2-fill',
+  'coupon-2-line',
+  'coupon-3-fill',
+  'coupon-3-line',
+  'coupon-4-fill',
+  'coupon-4-line',
+  'coupon-5-fill',
+  'coupon-5-line',
+  'coupon-fill',
+  'coupon-line',
+  'cpu-fill',
+  'cpu-line',
+  'creative-commons-by-fill',
+  'creative-commons-by-line',
+  'creative-commons-fill',
+  'creative-commons-line',
+  'creative-commons-nc-fill',
+  'creative-commons-nc-line',
+  'creative-commons-nd-fill',
+  'creative-commons-nd-line',
+  'creative-commons-sa-fill',
+  'creative-commons-sa-line',
+  'creative-commons-zero-fill',
+  'creative-commons-zero-line',
+  'criminal-fill',
+  'criminal-line',
+  'crop-2-fill',
+  'crop-2-line',
+  'crop-fill',
+  'crop-line',
+  'css3-fill',
+  'css3-line',
+  'cup-fill',
+  'cup-line',
+  'currency-fill',
+  'currency-line',
+  'cursor-fill',
+  'cursor-line',
+  'customer-service-2-fill',
+  'customer-service-2-line',
+  'customer-service-fill',
+  'customer-service-line',
+  'dashboard-2-fill',
+  'dashboard-2-line',
+  'dashboard-3-fill',
+  'dashboard-3-line',
+  'dashboard-fill',
+  'dashboard-line',
+  'database-2-fill',
+  'database-2-line',
+  'database-fill',
+  'database-line',
+  'delete-back-2-fill',
+  'delete-back-2-line',
+  'delete-back-fill',
+  'delete-back-line',
+  'delete-bin-2-fill',
+  'delete-bin-2-line',
+  'delete-bin-3-fill',
+  'delete-bin-3-line',
+  'delete-bin-4-fill',
+  'delete-bin-4-line',
+  'delete-bin-5-fill',
+  'delete-bin-5-line',
+  'delete-bin-6-fill',
+  'delete-bin-6-line',
+  'delete-bin-7-fill',
+  'delete-bin-7-line',
+  'delete-bin-fill',
+  'delete-bin-line',
+  'delete-column',
+  'delete-row',
+  'device-fill',
+  'device-line',
+  'device-recover-fill',
+  'device-recover-line',
+  'dingding-fill',
+  'dingding-line',
+  'direction-fill',
+  'direction-line',
+  'disc-fill',
+  'disc-line',
+  'discord-fill',
+  'discord-line',
+  'discuss-fill',
+  'discuss-line',
+  'dislike-fill',
+  'dislike-line',
+  'disqus-fill',
+  'disqus-line',
+  'divide-fill',
+  'divide-line',
+  'donut-chart-fill',
+  'donut-chart-line',
+  'door-closed-fill',
+  'door-closed-line',
+  'door-fill',
+  'door-line',
+  'door-lock-box-fill',
+  'door-lock-box-line',
+  'door-lock-fill',
+  'door-lock-line',
+  'door-open-fill',
+  'door-open-line',
+  'dossier-fill',
+  'dossier-line',
+  'douban-fill',
+  'douban-line',
+  'double-quotes-l',
+  'double-quotes-r',
+  'download-2-fill',
+  'download-2-line',
+  'download-cloud-2-fill',
+  'download-cloud-2-line',
+  'download-cloud-fill',
+  'download-cloud-line',
+  'download-fill',
+  'download-line',
+  'draft-fill',
+  'draft-line',
+  'drag-drop-fill',
+  'drag-drop-line',
+  'drag-move-2-fill',
+  'drag-move-2-line',
+  'drag-move-fill',
+  'drag-move-line',
+  'dribbble-fill',
+  'dribbble-line',
+  'drive-fill',
+  'drive-line',
+  'drizzle-fill',
+  'drizzle-line',
+  'drop-fill',
+  'drop-line',
+  'dropbox-fill',
+  'dropbox-line',
+  'dual-sim-1-fill',
+  'dual-sim-1-line',
+  'dual-sim-2-fill',
+  'dual-sim-2-line',
+  'dv-fill',
+  'dv-line',
+  'dvd-fill',
+  'dvd-line',
+  'e-bike-2-fill',
+  'e-bike-2-line',
+  'e-bike-fill',
+  'e-bike-line',
+  'earth-fill',
+  'earth-line',
+  'earthquake-fill',
+  'earthquake-line',
+  'edge-fill',
+  'edge-line',
+  'edit-2-fill',
+  'edit-2-line',
+  'edit-box-fill',
+  'edit-box-line',
+  'edit-circle-fill',
+  'edit-circle-line',
+  'edit-fill',
+  'edit-line',
+  'eject-fill',
+  'eject-line',
+  'emotion-2-fill',
+  'emotion-2-line',
+  'emotion-fill',
+  'emotion-happy-fill',
+  'emotion-happy-line',
+  'emotion-laugh-fill',
+  'emotion-laugh-line',
+  'emotion-line',
+  'emotion-normal-fill',
+  'emotion-normal-line',
+  'emotion-sad-fill',
+  'emotion-sad-line',
+  'emotion-unhappy-fill',
+  'emotion-unhappy-line',
+  'empathize-fill',
+  'empathize-line',
+  'emphasis-cn',
+  'emphasis',
+  'english-input',
+  'equalizer-fill',
+  'equalizer-line',
+  'eraser-fill',
+  'eraser-line',
+  'error-warning-fill',
+  'error-warning-line',
+  'evernote-fill',
+  'evernote-line',
+  'exchange-box-fill',
+  'exchange-box-line',
+  'exchange-cny-fill',
+  'exchange-cny-line',
+  'exchange-dollar-fill',
+  'exchange-dollar-line',
+  'exchange-fill',
+  'exchange-funds-fill',
+  'exchange-funds-line',
+  'exchange-line',
+  'external-link-fill',
+  'external-link-line',
+  'eye-2-fill',
+  'eye-2-line',
+  'eye-close-fill',
+  'eye-close-line',
+  'eye-fill',
+  'eye-line',
+  'eye-off-fill',
+  'eye-off-line',
+  'facebook-box-fill',
+  'facebook-box-line',
+  'facebook-circle-fill',
+  'facebook-circle-line',
+  'facebook-fill',
+  'facebook-line',
+  'fahrenheit-fill',
+  'fahrenheit-line',
+  'feedback-fill',
+  'feedback-line',
+  'file-2-fill',
+  'file-2-line',
+  'file-3-fill',
+  'file-3-line',
+  'file-4-fill',
+  'file-4-line',
+  'file-add-fill',
+  'file-add-line',
+  'file-chart-2-fill',
+  'file-chart-2-line',
+  'file-chart-fill',
+  'file-chart-line',
+  'file-cloud-fill',
+  'file-cloud-line',
+  'file-code-fill',
+  'file-code-line',
+  'file-copy-2-fill',
+  'file-copy-2-line',
+  'file-copy-fill',
+  'file-copy-line',
+  'file-damage-fill',
+  'file-damage-line',
+  'file-download-fill',
+  'file-download-line',
+  'file-edit-fill',
+  'file-edit-line',
+  'file-excel-2-fill',
+  'file-excel-2-line',
+  'file-excel-fill',
+  'file-excel-line',
+  'file-fill',
+  'file-forbid-fill',
+  'file-forbid-line',
+  'file-gif-fill',
+  'file-gif-line',
+  'file-history-fill',
+  'file-history-line',
+  'file-hwp-fill',
+  'file-hwp-line',
+  'file-info-fill',
+  'file-info-line',
+  'file-line',
+  'file-list-2-fill',
+  'file-list-2-line',
+  'file-list-3-fill',
+  'file-list-3-line',
+  'file-list-fill',
+  'file-list-line',
+  'file-lock-fill',
+  'file-lock-line',
+  'file-mark-fill',
+  'file-mark-line',
+  'file-music-fill',
+  'file-music-line',
+  'file-paper-2-fill',
+  'file-paper-2-line',
+  'file-paper-fill',
+  'file-paper-line',
+  'file-pdf-fill',
+  'file-pdf-line',
+  'file-ppt-2-fill',
+  'file-ppt-2-line',
+  'file-ppt-fill',
+  'file-ppt-line',
+  'file-reduce-fill',
+  'file-reduce-line',
+  'file-search-fill',
+  'file-search-line',
+  'file-settings-fill',
+  'file-settings-line',
+  'file-shield-2-fill',
+  'file-shield-2-line',
+  'file-shield-fill',
+  'file-shield-line',
+  'file-shred-fill',
+  'file-shred-line',
+  'file-text-fill',
+  'file-text-line',
+  'file-transfer-fill',
+  'file-transfer-line',
+  'file-unknow-fill',
+  'file-unknow-line',
+  'file-upload-fill',
+  'file-upload-line',
+  'file-user-fill',
+  'file-user-line',
+  'file-warning-fill',
+  'file-warning-line',
+  'file-word-2-fill',
+  'file-word-2-line',
+  'file-word-fill',
+  'file-word-line',
+  'file-zip-fill',
+  'file-zip-line',
+  'film-fill',
+  'film-line',
+  'filter-2-fill',
+  'filter-2-line',
+  'filter-3-fill',
+  'filter-3-line',
+  'filter-fill',
+  'filter-line',
+  'filter-off-fill',
+  'filter-off-line',
+  'find-replace-fill',
+  'find-replace-line',
+  'finder-fill',
+  'finder-line',
+  'fingerprint-2-fill',
+  'fingerprint-2-line',
+  'fingerprint-fill',
+  'fingerprint-line',
+  'fire-fill',
+  'fire-line',
+  'firefox-fill',
+  'firefox-line',
+  'first-aid-kit-fill',
+  'first-aid-kit-line',
+  'flag-2-fill',
+  'flag-2-line',
+  'flag-fill',
+  'flag-line',
+  'flashlight-fill',
+  'flashlight-line',
+  'flask-fill',
+  'flask-line',
+  'flight-land-fill',
+  'flight-land-line',
+  'flight-takeoff-fill',
+  'flight-takeoff-line',
+  'flood-fill',
+  'flood-line',
+  'flow-chart',
+  'flutter-fill',
+  'flutter-line',
+  'focus-2-fill',
+  'focus-2-line',
+  'focus-3-fill',
+  'focus-3-line',
+  'focus-fill',
+  'focus-line',
+  'foggy-fill',
+  'foggy-line',
+  'folder-2-fill',
+  'folder-2-line',
+  'folder-3-fill',
+  'folder-3-line',
+  'folder-4-fill',
+  'folder-4-line',
+  'folder-5-fill',
+  'folder-5-line',
+  'folder-add-fill',
+  'folder-add-line',
+  'folder-chart-2-fill',
+  'folder-chart-2-line',
+  'folder-chart-fill',
+  'folder-chart-line',
+  'folder-download-fill',
+  'folder-download-line',
+  'folder-fill',
+  'folder-forbid-fill',
+  'folder-forbid-line',
+  'folder-history-fill',
+  'folder-history-line',
+  'folder-info-fill',
+  'folder-info-line',
+  'folder-keyhole-fill',
+  'folder-keyhole-line',
+  'folder-line',
+  'folder-lock-fill',
+  'folder-lock-line',
+  'folder-music-fill',
+  'folder-music-line',
+  'folder-open-fill',
+  'folder-open-line',
+  'folder-received-fill',
+  'folder-received-line',
+  'folder-reduce-fill',
+  'folder-reduce-line',
+  'folder-settings-fill',
+  'folder-settings-line',
+  'folder-shared-fill',
+  'folder-shared-line',
+  'folder-shield-2-fill',
+  'folder-shield-2-line',
+  'folder-shield-fill',
+  'folder-shield-line',
+  'folder-transfer-fill',
+  'folder-transfer-line',
+  'folder-unknow-fill',
+  'folder-unknow-line',
+  'folder-upload-fill',
+  'folder-upload-line',
+  'folder-user-fill',
+  'folder-user-line',
+  'folder-warning-fill',
+  'folder-warning-line',
+  'folder-zip-fill',
+  'folder-zip-line',
+  'folders-fill',
+  'folders-line',
+  'font-color',
+  'font-size-2',
+  'font-size',
+  'football-fill',
+  'football-line',
+  'footprint-fill',
+  'footprint-line',
+  'forbid-2-fill',
+  'forbid-2-line',
+  'forbid-fill',
+  'forbid-line',
+  'format-clear',
+  'fridge-fill',
+  'fridge-line',
+  'fullscreen-exit-fill',
+  'fullscreen-exit-line',
+  'fullscreen-fill',
+  'fullscreen-line',
+  'function-fill',
+  'function-line',
+  'functions',
+  'funds-box-fill',
+  'funds-box-line',
+  'funds-fill',
+  'funds-line',
+  'gallery-fill',
+  'gallery-line',
+  'gallery-upload-fill',
+  'gallery-upload-line',
+  'game-fill',
+  'game-line',
+  'gamepad-fill',
+  'gamepad-line',
+  'gas-station-fill',
+  'gas-station-line',
+  'gatsby-fill',
+  'gatsby-line',
+  'genderless-fill',
+  'genderless-line',
+  'ghost-2-fill',
+  'ghost-2-line',
+  'ghost-fill',
+  'ghost-line',
+  'ghost-smile-fill',
+  'ghost-smile-line',
+  'gift-2-fill',
+  'gift-2-line',
+  'gift-fill',
+  'gift-line',
+  'git-branch-fill',
+  'git-branch-line',
+  'git-commit-fill',
+  'git-commit-line',
+  'git-merge-fill',
+  'git-merge-line',
+  'git-pull-request-fill',
+  'git-pull-request-line',
+  'git-repository-commits-fill',
+  'git-repository-commits-line',
+  'git-repository-fill',
+  'git-repository-line',
+  'git-repository-private-fill',
+  'git-repository-private-line',
+  'github-fill',
+  'github-line',
+  'gitlab-fill',
+  'gitlab-line',
+  'global-fill',
+  'global-line',
+  'globe-fill',
+  'globe-line',
+  'goblet-fill',
+  'goblet-line',
+  'google-fill',
+  'google-line',
+  'google-play-fill',
+  'google-play-line',
+  'government-fill',
+  'government-line',
+  'gps-fill',
+  'gps-line',
+  'gradienter-fill',
+  'gradienter-line',
+  'grid-fill',
+  'grid-line',
+  'group-2-fill',
+  'group-2-line',
+  'group-fill',
+  'group-line',
+  'guide-fill',
+  'guide-line',
+  'h-1',
+  'h-2',
+  'h-3',
+  'h-4',
+  'h-5',
+  'h-6',
+  'hail-fill',
+  'hail-line',
+  'hammer-fill',
+  'hammer-line',
+  'hand-coin-fill',
+  'hand-coin-line',
+  'hand-heart-fill',
+  'hand-heart-line',
+  'hand-sanitizer-fill',
+  'hand-sanitizer-line',
+  'handbag-fill',
+  'handbag-line',
+  'hard-drive-2-fill',
+  'hard-drive-2-line',
+  'hard-drive-fill',
+  'hard-drive-line',
+  'hashtag',
+  'haze-2-fill',
+  'haze-2-line',
+  'haze-fill',
+  'haze-line',
+  'hd-fill',
+  'hd-line',
+  'heading',
+  'headphone-fill',
+  'headphone-line',
+  'health-book-fill',
+  'health-book-line',
+  'heart-2-fill',
+  'heart-2-line',
+  'heart-3-fill',
+  'heart-3-line',
+  'heart-add-fill',
+  'heart-add-line',
+  'heart-fill',
+  'heart-line',
+  'heart-pulse-fill',
+  'heart-pulse-line',
+  'hearts-fill',
+  'hearts-line',
+  'heavy-showers-fill',
+  'heavy-showers-line',
+  'history-fill',
+  'history-line',
+  'home-2-fill',
+  'home-2-line',
+  'home-3-fill',
+  'home-3-line',
+  'home-4-fill',
+  'home-4-line',
+  'home-5-fill',
+  'home-5-line',
+  'home-6-fill',
+  'home-6-line',
+  'home-7-fill',
+  'home-7-line',
+  'home-8-fill',
+  'home-8-line',
+  'home-fill',
+  'home-gear-fill',
+  'home-gear-line',
+  'home-heart-fill',
+  'home-heart-line',
+  'home-line',
+  'home-smile-2-fill',
+  'home-smile-2-line',
+  'home-smile-fill',
+  'home-smile-line',
+  'home-wifi-fill',
+  'home-wifi-line',
+  'honor-of-kings-fill',
+  'honor-of-kings-line',
+  'honour-fill',
+  'honour-line',
+  'hospital-fill',
+  'hospital-line',
+  'hotel-bed-fill',
+  'hotel-bed-line',
+  'hotel-fill',
+  'hotel-line',
+  'hotspot-fill',
+  'hotspot-line',
+  'hq-fill',
+  'hq-line',
+  'html5-fill',
+  'html5-line',
+  'ie-fill',
+  'ie-line',
+  'image-2-fill',
+  'image-2-line',
+  'image-add-fill',
+  'image-add-line',
+  'image-edit-fill',
+  'image-edit-line',
+  'image-fill',
+  'image-line',
+  'inbox-archive-fill',
+  'inbox-archive-line',
+  'inbox-fill',
+  'inbox-line',
+  'inbox-unarchive-fill',
+  'inbox-unarchive-line',
+  'increase-decrease-fill',
+  'increase-decrease-line',
+  'indent-decrease',
+  'indent-increase',
+  'indeterminate-circle-fill',
+  'indeterminate-circle-line',
+  'information-fill',
+  'information-line',
+  'infrared-thermometer-fill',
+  'infrared-thermometer-line',
+  'ink-bottle-fill',
+  'ink-bottle-line',
+  'input-cursor-move',
+  'input-method-fill',
+  'input-method-line',
+  'insert-column-left',
+  'insert-column-right',
+  'insert-row-bottom',
+  'insert-row-top',
+  'instagram-fill',
+  'instagram-line',
+  'install-fill',
+  'install-line',
+  'invision-fill',
+  'invision-line',
+  'italic',
+  'kakao-talk-fill',
+  'kakao-talk-line',
+  'key-2-fill',
+  'key-2-line',
+  'key-fill',
+  'key-line',
+  'keyboard-box-fill',
+  'keyboard-box-line',
+  'keyboard-fill',
+  'keyboard-line',
+  'keynote-fill',
+  'keynote-line',
+  'knife-blood-fill',
+  'knife-blood-line',
+  'knife-fill',
+  'knife-line',
+  'landscape-fill',
+  'landscape-line',
+  'layout-2-fill',
+  'layout-2-line',
+  'layout-3-fill',
+  'layout-3-line',
+  'layout-4-fill',
+  'layout-4-line',
+  'layout-5-fill',
+  'layout-5-line',
+  'layout-6-fill',
+  'layout-6-line',
+  'layout-bottom-2-fill',
+  'layout-bottom-2-line',
+  'layout-bottom-fill',
+  'layout-bottom-line',
+  'layout-column-fill',
+  'layout-column-line',
+  'layout-fill',
+  'layout-grid-fill',
+  'layout-grid-line',
+  'layout-left-2-fill',
+  'layout-left-2-line',
+  'layout-left-fill',
+  'layout-left-line',
+  'layout-line',
+  'layout-masonry-fill',
+  'layout-masonry-line',
+  'layout-right-2-fill',
+  'layout-right-2-line',
+  'layout-right-fill',
+  'layout-right-line',
+  'layout-row-fill',
+  'layout-row-line',
+  'layout-top-2-fill',
+  'layout-top-2-line',
+  'layout-top-fill',
+  'layout-top-line',
+  'leaf-fill',
+  'leaf-line',
+  'lifebuoy-fill',
+  'lifebuoy-line',
+  'lightbulb-fill',
+  'lightbulb-flash-fill',
+  'lightbulb-flash-line',
+  'lightbulb-line',
+  'line-chart-fill',
+  'line-chart-line',
+  'line-fill',
+  'line-height',
+  'line-line',
+  'link-m',
+  'link-unlink-m',
+  'link-unlink',
+  'link',
+  'linkedin-box-fill',
+  'linkedin-box-line',
+  'linkedin-fill',
+  'linkedin-line',
+  'links-fill',
+  'links-line',
+  'list-check-2',
+  'list-check',
+  'list-ordered',
+  'list-settings-fill',
+  'list-settings-line',
+  'list-unordered',
+  'live-fill',
+  'live-line',
+  'loader-2-fill',
+  'loader-2-line',
+  'loader-3-fill',
+  'loader-3-line',
+  'loader-4-fill',
+  'loader-4-line',
+  'loader-5-fill',
+  'loader-5-line',
+  'loader-fill',
+  'loader-line',
+  'lock-2-fill',
+  'lock-2-line',
+  'lock-fill',
+  'lock-line',
+  'lock-password-fill',
+  'lock-password-line',
+  'lock-unlock-fill',
+  'lock-unlock-line',
+  'login-box-fill',
+  'login-box-line',
+  'login-circle-fill',
+  'login-circle-line',
+  'logout-box-fill',
+  'logout-box-line',
+  'logout-box-r-fill',
+  'logout-box-r-line',
+  'logout-circle-fill',
+  'logout-circle-line',
+  'logout-circle-r-fill',
+  'logout-circle-r-line',
+  'luggage-cart-fill',
+  'luggage-cart-line',
+  'luggage-deposit-fill',
+  'luggage-deposit-line',
+  'lungs-fill',
+  'lungs-line',
+  'mac-fill',
+  'mac-line',
+  'macbook-fill',
+  'macbook-line',
+  'magic-fill',
+  'magic-line',
+  'mail-add-fill',
+  'mail-add-line',
+  'mail-check-fill',
+  'mail-check-line',
+  'mail-close-fill',
+  'mail-close-line',
+  'mail-download-fill',
+  'mail-download-line',
+  'mail-fill',
+  'mail-forbid-fill',
+  'mail-forbid-line',
+  'mail-line',
+  'mail-lock-fill',
+  'mail-lock-line',
+  'mail-open-fill',
+  'mail-open-line',
+  'mail-send-fill',
+  'mail-send-line',
+  'mail-settings-fill',
+  'mail-settings-line',
+  'mail-star-fill',
+  'mail-star-line',
+  'mail-unread-fill',
+  'mail-unread-line',
+  'mail-volume-fill',
+  'mail-volume-line',
+  'map-2-fill',
+  'map-2-line',
+  'map-fill',
+  'map-line',
+  'map-pin-2-fill',
+  'map-pin-2-line',
+  'map-pin-3-fill',
+  'map-pin-3-line',
+  'map-pin-4-fill',
+  'map-pin-4-line',
+  'map-pin-5-fill',
+  'map-pin-5-line',
+  'map-pin-add-fill',
+  'map-pin-add-line',
+  'map-pin-fill',
+  'map-pin-line',
+  'map-pin-range-fill',
+  'map-pin-range-line',
+  'map-pin-time-fill',
+  'map-pin-time-line',
+  'map-pin-user-fill',
+  'map-pin-user-line',
+  'mark-pen-fill',
+  'mark-pen-line',
+  'markdown-fill',
+  'markdown-line',
+  'markup-fill',
+  'markup-line',
+  'mastercard-fill',
+  'mastercard-line',
+  'mastodon-fill',
+  'mastodon-line',
+  'medal-2-fill',
+  'medal-2-line',
+  'medal-fill',
+  'medal-line',
+  'medicine-bottle-fill',
+  'medicine-bottle-line',
+  'medium-fill',
+  'medium-line',
+  'men-fill',
+  'men-line',
+  'mental-health-fill',
+  'mental-health-line',
+  'menu-2-fill',
+  'menu-2-line',
+  'menu-3-fill',
+  'menu-3-line',
+  'menu-4-fill',
+  'menu-4-line',
+  'menu-5-fill',
+  'menu-5-line',
+  'menu-add-fill',
+  'menu-add-line',
+  'menu-fill',
+  'menu-fold-fill',
+  'menu-fold-line',
+  'menu-line',
+  'menu-unfold-fill',
+  'menu-unfold-line',
+  'merge-cells-horizontal',
+  'merge-cells-vertical',
+  'message-2-fill',
+  'message-2-line',
+  'message-3-fill',
+  'message-3-line',
+  'message-fill',
+  'message-line',
+  'messenger-fill',
+  'messenger-line',
+  'meteor-fill',
+  'meteor-line',
+  'mic-2-fill',
+  'mic-2-line',
+  'mic-fill',
+  'mic-line',
+  'mic-off-fill',
+  'mic-off-line',
+  'mickey-fill',
+  'mickey-line',
+  'microscope-fill',
+  'microscope-line',
+  'microsoft-fill',
+  'microsoft-line',
+  'mind-map',
+  'mini-program-fill',
+  'mini-program-line',
+  'mist-fill',
+  'mist-line',
+  'money-cny-box-fill',
+  'money-cny-box-line',
+  'money-cny-circle-fill',
+  'money-cny-circle-line',
+  'money-dollar-box-fill',
+  'money-dollar-box-line',
+  'money-dollar-circle-fill',
+  'money-dollar-circle-line',
+  'money-euro-box-fill',
+  'money-euro-box-line',
+  'money-euro-circle-fill',
+  'money-euro-circle-line',
+  'money-pound-box-fill',
+  'money-pound-box-line',
+  'money-pound-circle-fill',
+  'money-pound-circle-line',
+  'moon-clear-fill',
+  'moon-clear-line',
+  'moon-cloudy-fill',
+  'moon-cloudy-line',
+  'moon-fill',
+  'moon-foggy-fill',
+  'moon-foggy-line',
+  'moon-line',
+  'more-2-fill',
+  'more-2-line',
+  'more-fill',
+  'more-line',
+  'motorbike-fill',
+  'motorbike-line',
+  'mouse-fill',
+  'mouse-line',
+  'movie-2-fill',
+  'movie-2-line',
+  'movie-fill',
+  'movie-line',
+  'music-2-fill',
+  'music-2-line',
+  'music-fill',
+  'music-line',
+  'mv-fill',
+  'mv-line',
+  'navigation-fill',
+  'navigation-line',
+  'netease-cloud-music-fill',
+  'netease-cloud-music-line',
+  'netflix-fill',
+  'netflix-line',
+  'newspaper-fill',
+  'newspaper-line',
+  'node-tree',
+  'notification-2-fill',
+  'notification-2-line',
+  'notification-3-fill',
+  'notification-3-line',
+  'notification-4-fill',
+  'notification-4-line',
+  'notification-badge-fill',
+  'notification-badge-line',
+  'notification-fill',
+  'notification-line',
+  'notification-off-fill',
+  'notification-off-line',
+  'npmjs-fill',
+  'npmjs-line',
+  'number-0',
+  'number-1',
+  'number-2',
+  'number-3',
+  'number-4',
+  'number-5',
+  'number-6',
+  'number-7',
+  'number-8',
+  'number-9',
+  'numbers-fill',
+  'numbers-line',
+  'nurse-fill',
+  'nurse-line',
+  'oil-fill',
+  'oil-line',
+  'omega',
+  'open-arm-fill',
+  'open-arm-line',
+  'open-source-fill',
+  'open-source-line',
+  'opera-fill',
+  'opera-line',
+  'order-play-fill',
+  'order-play-line',
+  'organization-chart',
+  'outlet-2-fill',
+  'outlet-2-line',
+  'outlet-fill',
+  'outlet-line',
+  'page-separator',
+  'pages-fill',
+  'pages-line',
+  'paint-brush-fill',
+  'paint-brush-line',
+  'paint-fill',
+  'paint-line',
+  'palette-fill',
+  'palette-line',
+  'pantone-fill',
+  'pantone-line',
+  'paragraph',
+  'parent-fill',
+  'parent-line',
+  'parentheses-fill',
+  'parentheses-line',
+  'parking-box-fill',
+  'parking-box-line',
+  'parking-fill',
+  'parking-line',
+  'passport-fill',
+  'passport-line',
+  'patreon-fill',
+  'patreon-line',
+  'pause-circle-fill',
+  'pause-circle-line',
+  'pause-fill',
+  'pause-line',
+  'pause-mini-fill',
+  'pause-mini-line',
+  'paypal-fill',
+  'paypal-line',
+  'pen-nib-fill',
+  'pen-nib-line',
+  'pencil-fill',
+  'pencil-line',
+  'pencil-ruler-2-fill',
+  'pencil-ruler-2-line',
+  'pencil-ruler-fill',
+  'pencil-ruler-line',
+  'percent-fill',
+  'percent-line',
+  'phone-camera-fill',
+  'phone-camera-line',
+  'phone-fill',
+  'phone-find-fill',
+  'phone-find-line',
+  'phone-line',
+  'phone-lock-fill',
+  'phone-lock-line',
+  'picture-in-picture-2-fill',
+  'picture-in-picture-2-line',
+  'picture-in-picture-exit-fill',
+  'picture-in-picture-exit-line',
+  'picture-in-picture-fill',
+  'picture-in-picture-line',
+  'pie-chart-2-fill',
+  'pie-chart-2-line',
+  'pie-chart-box-fill',
+  'pie-chart-box-line',
+  'pie-chart-fill',
+  'pie-chart-line',
+  'pin-distance-fill',
+  'pin-distance-line',
+  'ping-pong-fill',
+  'ping-pong-line',
+  'pinterest-fill',
+  'pinterest-line',
+  'pinyin-input',
+  'pixelfed-fill',
+  'pixelfed-line',
+  'plane-fill',
+  'plane-line',
+  'plant-fill',
+  'plant-line',
+  'play-circle-fill',
+  'play-circle-line',
+  'play-fill',
+  'play-line',
+  'play-list-2-fill',
+  'play-list-2-line',
+  'play-list-add-fill',
+  'play-list-add-line',
+  'play-list-fill',
+  'play-list-line',
+  'play-mini-fill',
+  'play-mini-line',
+  'playstation-fill',
+  'playstation-line',
+  'plug-2-fill',
+  'plug-2-line',
+  'plug-fill',
+  'plug-line',
+  'polaroid-2-fill',
+  'polaroid-2-line',
+  'polaroid-fill',
+  'polaroid-line',
+  'police-car-fill',
+  'police-car-line',
+  'price-tag-2-fill',
+  'price-tag-2-line',
+  'price-tag-3-fill',
+  'price-tag-3-line',
+  'price-tag-fill',
+  'price-tag-line',
+  'printer-cloud-fill',
+  'printer-cloud-line',
+  'printer-fill',
+  'printer-line',
+  'product-hunt-fill',
+  'product-hunt-line',
+  'profile-fill',
+  'profile-line',
+  'projector-2-fill',
+  'projector-2-line',
+  'projector-fill',
+  'projector-line',
+  'psychotherapy-fill',
+  'psychotherapy-line',
+  'pulse-fill',
+  'pulse-line',
+  'pushpin-2-fill',
+  'pushpin-2-line',
+  'pushpin-fill',
+  'pushpin-line',
+  'qq-fill',
+  'qq-line',
+  'qr-code-fill',
+  'qr-code-line',
+  'qr-scan-2-fill',
+  'qr-scan-2-line',
+  'qr-scan-fill',
+  'qr-scan-line',
+  'question-answer-fill',
+  'question-answer-line',
+  'question-fill',
+  'question-line',
+  'question-mark',
+  'questionnaire-fill',
+  'questionnaire-line',
+  'quill-pen-fill',
+  'quill-pen-line',
+  'radar-fill',
+  'radar-line',
+  'radio-2-fill',
+  'radio-2-line',
+  'radio-button-fill',
+  'radio-button-line',
+  'radio-fill',
+  'radio-line',
+  'rainbow-fill',
+  'rainbow-line',
+  'rainy-fill',
+  'rainy-line',
+  'reactjs-fill',
+  'reactjs-line',
+  'record-circle-fill',
+  'record-circle-line',
+  'record-mail-fill',
+  'record-mail-line',
+  'recycle-fill',
+  'recycle-line',
+  'red-packet-fill',
+  'red-packet-line',
+  'reddit-fill',
+  'reddit-line',
+  'refresh-fill',
+  'refresh-line',
+  'refund-2-fill',
+  'refund-2-line',
+  'refund-fill',
+  'refund-line',
+  'registered-fill',
+  'registered-line',
+  'remixicon-fill',
+  'remixicon-line',
+  'remote-control-2-fill',
+  'remote-control-2-line',
+  'remote-control-fill',
+  'remote-control-line',
+  'repeat-2-fill',
+  'repeat-2-line',
+  'repeat-fill',
+  'repeat-line',
+  'repeat-one-fill',
+  'repeat-one-line',
+  'reply-all-fill',
+  'reply-all-line',
+  'reply-fill',
+  'reply-line',
+  'reserved-fill',
+  'reserved-line',
+  'rest-time-fill',
+  'rest-time-line',
+  'restart-fill',
+  'restart-line',
+  'restaurant-2-fill',
+  'restaurant-2-line',
+  'restaurant-fill',
+  'restaurant-line',
+  'rewind-fill',
+  'rewind-line',
+  'rewind-mini-fill',
+  'rewind-mini-line',
+  'rhythm-fill',
+  'rhythm-line',
+  'riding-fill',
+  'riding-line',
+  'road-map-fill',
+  'road-map-line',
+  'roadster-fill',
+  'roadster-line',
+  'robot-fill',
+  'robot-line',
+  'rocket-2-fill',
+  'rocket-2-line',
+  'rocket-fill',
+  'rocket-line',
+  'rotate-lock-fill',
+  'rotate-lock-line',
+  'rounded-corner',
+  'route-fill',
+  'route-line',
+  'router-fill',
+  'router-line',
+  'rss-fill',
+  'rss-line',
+  'ruler-2-fill',
+  'ruler-2-line',
+  'ruler-fill',
+  'ruler-line',
+  'run-fill',
+  'run-line',
+  'safari-fill',
+  'safari-line',
+  'safe-2-fill',
+  'safe-2-line',
+  'safe-fill',
+  'safe-line',
+  'sailboat-fill',
+  'sailboat-line',
+  'save-2-fill',
+  'save-2-line',
+  'save-3-fill',
+  'save-3-line',
+  'save-fill',
+  'save-line',
+  'scales-2-fill',
+  'scales-2-line',
+  'scales-3-fill',
+  'scales-3-line',
+  'scales-fill',
+  'scales-line',
+  'scan-2-fill',
+  'scan-2-line',
+  'scan-fill',
+  'scan-line',
+  'scissors-2-fill',
+  'scissors-2-line',
+  'scissors-cut-fill',
+  'scissors-cut-line',
+  'scissors-fill',
+  'scissors-line',
+  'screenshot-2-fill',
+  'screenshot-2-line',
+  'screenshot-fill',
+  'screenshot-line',
+  'sd-card-fill',
+  'sd-card-line',
+  'sd-card-mini-fill',
+  'sd-card-mini-line',
+  'search-2-fill',
+  'search-2-line',
+  'search-eye-fill',
+  'search-eye-line',
+  'search-fill',
+  'search-line',
+  'secure-payment-fill',
+  'secure-payment-line',
+  'seedling-fill',
+  'seedling-line',
+  'send-backward',
+  'send-plane-2-fill',
+  'send-plane-2-line',
+  'send-plane-fill',
+  'send-plane-line',
+  'send-to-back',
+  'sensor-fill',
+  'sensor-line',
+  'separator',
+  'server-fill',
+  'server-line',
+  'service-fill',
+  'service-line',
+  'settings-2-fill',
+  'settings-2-line',
+  'settings-3-fill',
+  'settings-3-line',
+  'settings-4-fill',
+  'settings-4-line',
+  'settings-5-fill',
+  'settings-5-line',
+  'settings-6-fill',
+  'settings-6-line',
+  'settings-fill',
+  'settings-line',
+  'shape-2-fill',
+  'shape-2-line',
+  'shape-fill',
+  'shape-line',
+  'share-box-fill',
+  'share-box-line',
+  'share-circle-fill',
+  'share-circle-line',
+  'share-fill',
+  'share-forward-2-fill',
+  'share-forward-2-line',
+  'share-forward-box-fill',
+  'share-forward-box-line',
+  'share-forward-fill',
+  'share-forward-line',
+  'share-line',
+  'shield-check-fill',
+  'shield-check-line',
+  'shield-cross-fill',
+  'shield-cross-line',
+  'shield-fill',
+  'shield-flash-fill',
+  'shield-flash-line',
+  'shield-keyhole-fill',
+  'shield-keyhole-line',
+  'shield-line',
+  'shield-star-fill',
+  'shield-star-line',
+  'shield-user-fill',
+  'shield-user-line',
+  'ship-2-fill',
+  'ship-2-line',
+  'ship-fill',
+  'ship-line',
+  'shirt-fill',
+  'shirt-line',
+  'shopping-bag-2-fill',
+  'shopping-bag-2-line',
+  'shopping-bag-3-fill',
+  'shopping-bag-3-line',
+  'shopping-bag-fill',
+  'shopping-bag-line',
+  'shopping-basket-2-fill',
+  'shopping-basket-2-line',
+  'shopping-basket-fill',
+  'shopping-basket-line',
+  'shopping-cart-2-fill',
+  'shopping-cart-2-line',
+  'shopping-cart-fill',
+  'shopping-cart-line',
+  'showers-fill',
+  'showers-line',
+  'shuffle-fill',
+  'shuffle-line',
+  'shut-down-fill',
+  'shut-down-line',
+  'side-bar-fill',
+  'side-bar-line',
+  'signal-tower-fill',
+  'signal-tower-line',
+  'signal-wifi-1-fill',
+  'signal-wifi-1-line',
+  'signal-wifi-2-fill',
+  'signal-wifi-2-line',
+  'signal-wifi-3-fill',
+  'signal-wifi-3-line',
+  'signal-wifi-error-fill',
+  'signal-wifi-error-line',
+  'signal-wifi-fill',
+  'signal-wifi-line',
+  'signal-wifi-off-fill',
+  'signal-wifi-off-line',
+  'sim-card-2-fill',
+  'sim-card-2-line',
+  'sim-card-fill',
+  'sim-card-line',
+  'single-quotes-l',
+  'single-quotes-r',
+  'sip-fill',
+  'sip-line',
+  'skip-back-fill',
+  'skip-back-line',
+  'skip-back-mini-fill',
+  'skip-back-mini-line',
+  'skip-forward-fill',
+  'skip-forward-line',
+  'skip-forward-mini-fill',
+  'skip-forward-mini-line',
+  'skull-2-fill',
+  'skull-2-line',
+  'skull-fill',
+  'skull-line',
+  'skype-fill',
+  'skype-line',
+  'slack-fill',
+  'slack-line',
+  'slice-fill',
+  'slice-line',
+  'slideshow-2-fill',
+  'slideshow-2-line',
+  'slideshow-3-fill',
+  'slideshow-3-line',
+  'slideshow-4-fill',
+  'slideshow-4-line',
+  'slideshow-fill',
+  'slideshow-line',
+  'smartphone-fill',
+  'smartphone-line',
+  'snapchat-fill',
+  'snapchat-line',
+  'snowy-fill',
+  'snowy-line',
+  'sort-asc',
+  'sort-desc',
+  'sound-module-fill',
+  'sound-module-line',
+  'soundcloud-fill',
+  'soundcloud-line',
+  'space-ship-fill',
+  'space-ship-line',
+  'space',
+  'spam-2-fill',
+  'spam-2-line',
+  'spam-3-fill',
+  'spam-3-line',
+  'spam-fill',
+  'spam-line',
+  'speaker-2-fill',
+  'speaker-2-line',
+  'speaker-3-fill',
+  'speaker-3-line',
+  'speaker-fill',
+  'speaker-line',
+  'spectrum-fill',
+  'spectrum-line',
+  'speed-fill',
+  'speed-line',
+  'speed-mini-fill',
+  'speed-mini-line',
+  'split-cells-horizontal',
+  'split-cells-vertical',
+  'spotify-fill',
+  'spotify-line',
+  'spy-fill',
+  'spy-line',
+  'stack-fill',
+  'stack-line',
+  'stack-overflow-fill',
+  'stack-overflow-line',
+  'stackshare-fill',
+  'stackshare-line',
+  'star-fill',
+  'star-half-fill',
+  'star-half-line',
+  'star-half-s-fill',
+  'star-half-s-line',
+  'star-line',
+  'star-s-fill',
+  'star-s-line',
+  'star-smile-fill',
+  'star-smile-line',
+  'steam-fill',
+  'steam-line',
+  'steering-2-fill',
+  'steering-2-line',
+  'steering-fill',
+  'steering-line',
+  'stethoscope-fill',
+  'stethoscope-line',
+  'sticky-note-2-fill',
+  'sticky-note-2-line',
+  'sticky-note-fill',
+  'sticky-note-line',
+  'stock-fill',
+  'stock-line',
+  'stop-circle-fill',
+  'stop-circle-line',
+  'stop-fill',
+  'stop-line',
+  'stop-mini-fill',
+  'stop-mini-line',
+  'store-2-fill',
+  'store-2-line',
+  'store-3-fill',
+  'store-3-line',
+  'store-fill',
+  'store-line',
+  'strikethrough-2',
+  'strikethrough',
+  'subscript-2',
+  'subscript',
+  'subtract-fill',
+  'subtract-line',
+  'subway-fill',
+  'subway-line',
+  'subway-wifi-fill',
+  'subway-wifi-line',
+  'suitcase-2-fill',
+  'suitcase-2-line',
+  'suitcase-3-fill',
+  'suitcase-3-line',
+  'suitcase-fill',
+  'suitcase-line',
+  'sun-cloudy-fill',
+  'sun-cloudy-line',
+  'sun-fill',
+  'sun-foggy-fill',
+  'sun-foggy-line',
+  'sun-line',
+  'superscript-2',
+  'superscript',
+  'surgical-mask-fill',
+  'surgical-mask-line',
+  'surround-sound-fill',
+  'surround-sound-line',
+  'survey-fill',
+  'survey-line',
+  'swap-box-fill',
+  'swap-box-line',
+  'swap-fill',
+  'swap-line',
+  'switch-fill',
+  'switch-line',
+  'sword-fill',
+  'sword-line',
+  'syringe-fill',
+  'syringe-line',
+  't-box-fill',
+  't-box-line',
+  't-shirt-2-fill',
+  't-shirt-2-line',
+  't-shirt-air-fill',
+  't-shirt-air-line',
+  't-shirt-fill',
+  't-shirt-line',
+  'table-2',
+  'table-alt-fill',
+  'table-alt-line',
+  'table-fill',
+  'table-line',
+  'tablet-fill',
+  'tablet-line',
+  'takeaway-fill',
+  'takeaway-line',
+  'taobao-fill',
+  'taobao-line',
+  'tape-fill',
+  'tape-line',
+  'task-fill',
+  'task-line',
+  'taxi-fill',
+  'taxi-line',
+  'taxi-wifi-fill',
+  'taxi-wifi-line',
+  'team-fill',
+  'team-line',
+  'telegram-fill',
+  'telegram-line',
+  'temp-cold-fill',
+  'temp-cold-line',
+  'temp-hot-fill',
+  'temp-hot-line',
+  'terminal-box-fill',
+  'terminal-box-line',
+  'terminal-fill',
+  'terminal-line',
+  'terminal-window-fill',
+  'terminal-window-line',
+  'test-tube-fill',
+  'test-tube-line',
+  'text-direction-l',
+  'text-direction-r',
+  'text-spacing',
+  'text-wrap',
+  'text',
+  'thermometer-fill',
+  'thermometer-line',
+  'thumb-down-fill',
+  'thumb-down-line',
+  'thumb-up-fill',
+  'thumb-up-line',
+  'thunderstorms-fill',
+  'thunderstorms-line',
+  'ticket-2-fill',
+  'ticket-2-line',
+  'ticket-fill',
+  'ticket-line',
+  'time-fill',
+  'time-line',
+  'timer-2-fill',
+  'timer-2-line',
+  'timer-fill',
+  'timer-flash-fill',
+  'timer-flash-line',
+  'timer-line',
+  'todo-fill',
+  'todo-line',
+  'toggle-fill',
+  'toggle-line',
+  'tools-fill',
+  'tools-line',
+  'tornado-fill',
+  'tornado-line',
+  'trademark-fill',
+  'trademark-line',
+  'traffic-light-fill',
+  'traffic-light-line',
+  'train-fill',
+  'train-line',
+  'train-wifi-fill',
+  'train-wifi-line',
+  'translate-2',
+  'translate',
+  'travesti-fill',
+  'travesti-line',
+  'treasure-map-fill',
+  'treasure-map-line',
+  'trello-fill',
+  'trello-line',
+  'trophy-fill',
+  'trophy-line',
+  'truck-fill',
+  'truck-line',
+  'tumblr-fill',
+  'tumblr-line',
+  'tv-2-fill',
+  'tv-2-line',
+  'tv-fill',
+  'tv-line',
+  'twitch-fill',
+  'twitch-line',
+  'twitter-fill',
+  'twitter-line',
+  'typhoon-fill',
+  'typhoon-line',
+  'u-disk-fill',
+  'u-disk-line',
+  'ubuntu-fill',
+  'ubuntu-line',
+  'umbrella-fill',
+  'umbrella-line',
+  'underline',
+  'uninstall-fill',
+  'uninstall-line',
+  'unsplash-fill',
+  'unsplash-line',
+  'upload-2-fill',
+  'upload-2-line',
+  'upload-cloud-2-fill',
+  'upload-cloud-2-line',
+  'upload-cloud-fill',
+  'upload-cloud-line',
+  'upload-fill',
+  'upload-line',
+  'usb-fill',
+  'usb-line',
+  'user-2-fill',
+  'user-2-line',
+  'user-3-fill',
+  'user-3-line',
+  'user-4-fill',
+  'user-4-line',
+  'user-5-fill',
+  'user-5-line',
+  'user-6-fill',
+  'user-6-line',
+  'user-add-fill',
+  'user-add-line',
+  'user-fill',
+  'user-follow-fill',
+  'user-follow-line',
+  'user-heart-fill',
+  'user-heart-line',
+  'user-line',
+  'user-location-fill',
+  'user-location-line',
+  'user-received-2-fill',
+  'user-received-2-line',
+  'user-received-fill',
+  'user-received-line',
+  'user-search-fill',
+  'user-search-line',
+  'user-settings-fill',
+  'user-settings-line',
+  'user-shared-2-fill',
+  'user-shared-2-line',
+  'user-shared-fill',
+  'user-shared-line',
+  'user-smile-fill',
+  'user-smile-line',
+  'user-star-fill',
+  'user-star-line',
+  'user-unfollow-fill',
+  'user-unfollow-line',
+  'user-voice-fill',
+  'user-voice-line',
+  'video-add-fill',
+  'video-add-line',
+  'video-chat-fill',
+  'video-chat-line',
+  'video-download-fill',
+  'video-download-line',
+  'video-fill',
+  'video-line',
+  'video-upload-fill',
+  'video-upload-line',
+  'vidicon-2-fill',
+  'vidicon-2-line',
+  'vidicon-fill',
+  'vidicon-line',
+  'vimeo-fill',
+  'vimeo-line',
+  'vip-crown-2-fill',
+  'vip-crown-2-line',
+  'vip-crown-fill',
+  'vip-crown-line',
+  'vip-diamond-fill',
+  'vip-diamond-line',
+  'vip-fill',
+  'vip-line',
+  'virus-fill',
+  'virus-line',
+  'visa-fill',
+  'visa-line',
+  'voice-recognition-fill',
+  'voice-recognition-line',
+  'voiceprint-fill',
+  'voiceprint-line',
+  'volume-down-fill',
+  'volume-down-line',
+  'volume-mute-fill',
+  'volume-mute-line',
+  'volume-off-vibrate-fill',
+  'volume-off-vibrate-line',
+  'volume-up-fill',
+  'volume-up-line',
+  'volume-vibrate-fill',
+  'volume-vibrate-line',
+  'vuejs-fill',
+  'vuejs-line',
+  'walk-fill',
+  'walk-line',
+  'wallet-2-fill',
+  'wallet-2-line',
+  'wallet-3-fill',
+  'wallet-3-line',
+  'wallet-fill',
+  'wallet-line',
+  'water-flash-fill',
+  'water-flash-line',
+  'webcam-fill',
+  'webcam-line',
+  'wechat-2-fill',
+  'wechat-2-line',
+  'wechat-fill',
+  'wechat-line',
+  'wechat-pay-fill',
+  'wechat-pay-line',
+  'weibo-fill',
+  'weibo-line',
+  'whatsapp-fill',
+  'whatsapp-line',
+  'wheelchair-fill',
+  'wheelchair-line',
+  'wifi-fill',
+  'wifi-line',
+  'wifi-off-fill',
+  'wifi-off-line',
+  'window-2-fill',
+  'window-2-line',
+  'window-fill',
+  'window-line',
+  'windows-fill',
+  'windows-line',
+  'windy-fill',
+  'windy-line',
+  'wireless-charging-fill',
+  'wireless-charging-line',
+  'women-fill',
+  'women-line',
+  'wubi-input',
+  'xbox-fill',
+  'xbox-line',
+  'xing-fill',
+  'xing-line',
+  'youtube-fill',
+  'youtube-line',
+  'zcool-fill',
+  'zcool-line',
+  'zhihu-fill',
+  'zhihu-line',
+  'zoom-in-fill',
+  'zoom-in-line',
+  'zoom-out-fill',
+  'zoom-out-line',
+  'zzz-fill',
+  'zzz-line',
+]
+module.exports = [
+  {
+    url: '/remixIcon/getList',
+    type: 'post',
+    response(config) {
+      const { title, pageNo = 1, pageSize = 72 } = config.body
+      let mockList = data.filter((item) => {
+        if (title && item.indexOf(title) < 0) return false
+        return true
+      })
+      const pageList = mockList.filter(
+        (item, index) =>
+          index < pageSize * pageNo && index >= pageSize * (pageNo - 1)
+      )
+      return {
+        code: 200,
+        msg: 'success',
+        totalCount: mockList.length,
+        data: pageList,
+      }
+    },
+  },
+]
diff --git a/mock/controller/roleManagement.js b/mock/controller/roleManagement.js
new file mode 100644
index 0000000..3a2079e
--- /dev/null
+++ b/mock/controller/roleManagement.js
@@ -0,0 +1,53 @@
+const totalCount = 2
+const List = [
+  {
+    id: '@id',
+    permission: 'admin',
+  },
+  {
+    id: '@id',
+    permission: 'editor',
+  },
+]
+module.exports = [
+  {
+    url: '/roleManagement/getList',
+    type: 'post',
+    response(config) {
+      const { title = '', pageNo = 1, pageSize = 20 } = config.body
+      let mockList = List.filter((item) => {
+        return !(title && item.title.indexOf(title) < 0)
+      })
+      const pageList = mockList.filter(
+        (item, index) =>
+          index < pageSize * pageNo && index >= pageSize * (pageNo - 1)
+      )
+      return {
+        code: 200,
+        msg: 'success',
+        totalCount,
+        data: pageList,
+      }
+    },
+  },
+  {
+    url: '/roleManagement/doEdit',
+    type: 'post',
+    response() {
+      return {
+        code: 200,
+        msg: '妯℃嫙淇濆瓨鎴愬姛',
+      }
+    },
+  },
+  {
+    url: '/roleManagement/doDelete',
+    type: 'post',
+    response() {
+      return {
+        code: 200,
+        msg: '妯℃嫙鍒犻櫎鎴愬姛',
+      }
+    },
+  },
+]
diff --git a/mock/controller/router.js b/mock/controller/router.js
new file mode 100644
index 0000000..16f3b26
--- /dev/null
+++ b/mock/controller/router.js
@@ -0,0 +1,292 @@
+const data = [
+  {
+    path: '/',
+    component: 'Layout',
+    redirect: 'index',
+    children: [
+      {
+        path: 'index',
+        name: 'Index',
+        component: '@/views/index/index',
+        meta: {
+          title: '棣栭〉',
+          icon: 'home',
+          affix: true,
+        },
+      },
+    ],
+  },
+  {
+    path: '/personnelManagement',
+    component: 'Layout',
+    redirect: 'noRedirect',
+    name: 'PersonnelManagement',
+    meta: { title: '浜哄憳', icon: 'users-cog', permissions: ['admin'] },
+    children: [
+      {
+        path: 'userManagement',
+        name: 'UserManagement',
+        component: '@/views/personnelManagement/userManagement/index',
+        meta: { title: '鐢ㄦ埛绠$悊' },
+      },
+      {
+        path: 'roleManagement',
+        name: 'RoleManagement',
+        component: '@/views/personnelManagement/roleManagement/index',
+        meta: { title: '瑙掕壊绠$悊' },
+      },
+      {
+        path: 'menuManagement',
+        name: 'MenuManagement',
+        component: '@/views/personnelManagement/menuManagement/index',
+        meta: { title: '鑿滃崟绠$悊', badge: 'New' },
+      },
+    ],
+  },
+  {
+    path: '/vab',
+    component: 'Layout',
+    redirect: 'noRedirect',
+    name: 'Vab',
+    alwaysShow: true,
+    meta: { title: '缁勪欢', icon: 'cloud' },
+    children: [
+      {
+        path: 'permissions',
+        name: 'Permission',
+        component: '@/views/vab/permissions/index',
+        meta: {
+          title: '鏉冮檺鎺у埗',
+          permissions: ['admin', 'editor'],
+          badge: 'New',
+        },
+      },
+      {
+        path: 'icon',
+        component: 'EmptyLayout',
+        redirect: 'noRedirect',
+        name: 'Icon',
+        meta: {
+          title: '鍥炬爣',
+          permissions: ['admin'],
+        },
+        children: [
+          {
+            path: 'awesomeIcon',
+            name: 'AwesomeIcon',
+            component: '@/views/vab/icon/index',
+            meta: { title: '甯歌鍥炬爣' },
+          },
+          {
+            path: 'colorfulIcon',
+            name: 'ColorfulIcon',
+            component: '@/views/vab/icon/colorfulIcon',
+            meta: { title: '澶氬僵鍥炬爣' },
+          },
+        ],
+      },
+      {
+        path: 'table',
+        component: '@/views/vab/table/index',
+        name: 'Table',
+        meta: {
+          title: '琛ㄦ牸',
+          permissions: ['admin'],
+        },
+      },
+      {
+        path: 'map',
+        name: 'Map',
+        component: '@/views/vab/map/index',
+        meta: { title: '鍦板浘', permissions: ['admin'], badge: 'Pro' },
+      },
+      {
+        path: 'webSocket',
+        name: 'WebSocket',
+        component: '@/views/vab/webSocket/index',
+        meta: { title: 'webSocket', permissions: ['admin'] },
+      },
+      {
+        path: 'form',
+        name: 'Form',
+        component: '@/views/vab/form/index',
+        meta: { title: '琛ㄥ崟', permissions: ['admin'] },
+      },
+      {
+        path: 'element',
+        name: 'Element',
+        component: '@/views/vab/element/index',
+        meta: { title: '甯哥敤缁勪欢', permissions: ['admin'] },
+      },
+      {
+        path: 'tree',
+        name: 'Tree',
+        component: '@/views/vab/tree/index',
+        meta: { title: '鏍�', permissions: ['admin'] },
+      },
+      {
+        path: 'verify',
+        name: 'Verify',
+        component: '@/views/vab/verify/index',
+        meta: { title: '楠岃瘉鐮�', permissions: ['admin'] },
+      },
+      {
+        path: 'menu1',
+        component: '@/views/vab/nested/menu1/index',
+        name: 'Menu1',
+        alwaysShow: true,
+        meta: {
+          title: '宓屽璺敱 1',
+          permissions: ['admin'],
+        },
+        children: [
+          {
+            path: 'menu1-1',
+            name: 'Menu1-1',
+            alwaysShow: true,
+            meta: { title: '宓屽璺敱 1-1' },
+            component: '@/views/vab/nested/menu1/menu1-1/index',
+
+            children: [
+              {
+                path: 'menu1-1-1',
+                name: 'Menu1-1-1',
+                meta: { title: '宓屽璺敱 1-1-1' },
+                component: '@/views/vab/nested/menu1/menu1-1/menu1-1-1/index',
+              },
+            ],
+          },
+        ],
+      },
+      {
+        path: 'magnifier',
+        name: 'Magnifier',
+        component: '@/views/vab/magnifier/index',
+        meta: { title: '鏀惧ぇ闀�', permissions: ['admin'] },
+      },
+      {
+        path: 'loading',
+        name: 'Loading',
+        component: '@/views/vab/loading/index',
+        meta: { title: 'loading', permissions: ['admin'] },
+      },
+      {
+        path: 'player',
+        name: 'Player',
+        component: '@/views/vab/player/index',
+        meta: { title: '瑙嗛鎾斁鍣�', permissions: ['admin'] },
+      },
+      {
+        path: 'markdownEditor',
+        name: 'MarkdownEditor',
+        component: '@/views/vab/markdownEditor/index',
+        meta: { title: 'markdown缂栬緫鍣�', permissions: ['admin'] },
+      },
+      {
+        path: 'editor',
+        name: 'Editor',
+        component: '@/views/vab/editor/index',
+        meta: { title: '瀵屾枃鏈紪杈戝櫒', permissions: ['admin'], badge: 'New' },
+      },
+      {
+        path: 'backToTop',
+        name: 'BackToTop',
+        component: '@/views/vab/backToTop/index',
+        meta: { title: '杩斿洖椤堕儴', permissions: ['admin'] },
+      },
+      {
+        path: 'lodash',
+        name: 'Lodash',
+        component: '@/views/vab/lodash/index',
+        meta: { title: 'lodash', permissions: ['admin'] },
+      },
+      {
+        path: 'smallComponents',
+        name: 'SmallComponents',
+        component: '@/views/vab/smallComponents/index',
+        meta: { title: '灏忕粍浠�', permissions: ['admin'] },
+      },
+
+      {
+        path: 'upload',
+        name: 'Upload',
+        component: '@/views/vab/upload/index',
+        meta: { title: '涓婁紶', permissions: ['admin'] },
+      },
+      {
+        path: 'log',
+        name: 'Log',
+        component: '@/views/vab/errorLog/index',
+        meta: { title: '閿欒鏃ュ織妯℃嫙', permissions: ['admin'] },
+      },
+      {
+        path: 'more',
+        name: 'More',
+        component: '@/views/vab/more/index',
+        meta: { title: '鍏充簬', permissions: ['admin'] },
+      },
+    ],
+  },
+  {
+    path: '/mall',
+    component: 'Layout',
+    redirect: 'noRedirect',
+    name: 'Mall',
+    meta: {
+      title: '鍟嗗煄',
+      icon: 'shopping-cart',
+      permissions: ['admin'],
+    },
+
+    children: [
+      {
+        path: 'pay',
+        name: 'Pay',
+        component: '@/views/mall/pay/index',
+        meta: {
+          title: '鏀粯',
+          noKeepAlive: true,
+        },
+        children: null,
+      },
+      {
+        path: 'goodsList',
+        name: 'GoodsList',
+        component: '@/views/mall/goodsList/index',
+        meta: {
+          title: '鍟嗗搧鍒楄〃',
+        },
+      },
+    ],
+  },
+  {
+    path: '/error',
+    component: 'EmptyLayout',
+    redirect: 'noRedirect',
+    name: 'Error',
+    meta: { title: '閿欒椤�', icon: 'bug' },
+    children: [
+      {
+        path: '401',
+        name: 'Error401',
+        component: '@/views/401',
+        meta: { title: '401' },
+      },
+      {
+        path: '404',
+        name: 'Error404',
+        component: '@/views/404',
+        meta: { title: '404' },
+      },
+    ],
+  },
+]
+module.exports = [
+  {
+    url: '/menu/navigate',
+    type: 'post',
+    response() {
+      return { code: 200, msg: 'success', data: data }
+    },
+  },
+]
diff --git a/mock/controller/table.js b/mock/controller/table.js
new file mode 100644
index 0000000..264f493
--- /dev/null
+++ b/mock/controller/table.js
@@ -0,0 +1,88 @@
+const { mock } = require('mockjs')
+const { handleRandomImage } = require('../utils')
+
+const List = []
+const count = 999
+for (let i = 0; i < count; i++) {
+  List.push(
+    mock({
+      uuid: '@uuid',
+      id: '@id',
+      title: '@csentence(1, 2)',
+      'status|1': ['published', 'draft', 'deleted'],
+      author: '@cname',
+      datetime: '@datetime',
+      pageViews: '@integer(300, 5000)',
+      img: handleRandomImage(200, 200),
+      smallImg: handleRandomImage(40, 40),
+      switch: '@boolean',
+      percent: '@integer(80,99)',
+    })
+  )
+}
+
+module.exports = [
+  {
+    url: '/table/getList',
+    type: 'post',
+    response(config) {
+      if (!config.body) {
+        return {
+          code: 200,
+          msg: 'success',
+          totalCount: count,
+          data: mock({
+            'data|50': [
+              {
+                id: '@id',
+                title: '@csentence(1, 2)',
+                'status|1': ['published', 'draft', 'deleted'],
+                author: '@cname',
+                datetime: '@datetime',
+                pageViews: '@integer(300, 5000)',
+                img: handleRandomImage(200, 200),
+                smallImg: handleRandomImage(40, 40),
+                switch: '@boolean',
+                percent: '@integer(80,99)',
+              },
+            ],
+          }).data,
+        }
+      }
+      const { title = '', pageNo = 1, pageSize = 20 } = config.body
+      let mockList = List.filter((item) => {
+        return !(title && item.title.indexOf(title) < 0)
+      })
+      const pageList = mockList.filter(
+        (item, index) =>
+          index < pageSize * pageNo && index >= pageSize * (pageNo - 1)
+      )
+      return {
+        code: 200,
+        msg: 'success',
+        totalCount: count,
+        data: pageList,
+      }
+    },
+  },
+  {
+    url: '/table/doEdit',
+    type: 'post',
+    response() {
+      return {
+        code: 200,
+        msg: '妯℃嫙淇濆瓨鎴愬姛',
+      }
+    },
+  },
+  {
+    url: '/table/doDelete',
+    type: 'post',
+    response() {
+      return {
+        code: 200,
+        msg: '妯℃嫙鍒犻櫎鎴愬姛',
+      }
+    },
+  },
+]
diff --git a/mock/controller/tree.js b/mock/controller/tree.js
new file mode 100644
index 0000000..fc2133a
--- /dev/null
+++ b/mock/controller/tree.js
@@ -0,0 +1,54 @@
+const data = [
+  {
+    id: '1',
+    parentId: '0',
+    name: 'root',
+    title: 'root',
+    text: 'root',
+    value: '1',
+    rank: 1,
+    children: [
+      {
+        id: '32816b88ff72423f960e7d492a386131',
+        parentId: '1',
+        name: '涓�绾�',
+        title: '涓�绾�',
+        text: '涓�绾�',
+        value: '32816b88ff72423f960e7d492a386131',
+        rank: 2,
+        children: [
+          {
+            id: '9e11afc35d55475fb0bd3164b9684cbe',
+            parentId: '32816b88ff72423f960e7d492a386131',
+            name: '浜岀骇',
+            title: '浜岀骇',
+            text: '浜岀骇',
+            value: '9e11afc35d55475fb0bd3164b9684cbe',
+            rank: 3,
+            children: [
+              {
+                id: '4cc1b04635e4444292526c5391699077',
+                parentId: '9e11afc35d55475fb0bd3164b9684cbe',
+                name: '涓夌骇',
+                title: '涓夌骇',
+                text: '涓夌骇',
+                value: '4cc1b04635e4444292526c5391699077',
+                rank: 4,
+                children: [],
+              },
+            ],
+          },
+        ],
+      },
+    ],
+  },
+]
+module.exports = [
+  {
+    url: '/tree/list',
+    type: 'post',
+    response() {
+      return { code: 200, msg: 'success', data }
+    },
+  },
+]
diff --git a/mock/controller/user.js b/mock/controller/user.js
new file mode 100644
index 0000000..637c7c9
--- /dev/null
+++ b/mock/controller/user.js
@@ -0,0 +1,95 @@
+const accessTokens = {
+  admin: 'admin-accessToken',
+  editor: 'editor-accessToken',
+  test: 'test-accessToken',
+}
+
+module.exports = [
+  {
+    url: '/publicKey',
+    type: 'post',
+    response() {
+      return {
+        code: 200,
+        msg: 'success',
+        data: {
+          mockServer: true,
+          publicKey:
+            'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDBT2vr+dhZElF73FJ6xiP181txKWUSNLPQQlid6DUJhGAOZblluafIdLmnUyKE8mMHhT3R+Ib3ssZcJku6Hn72yHYj/qPkCGFv0eFo7G+GJfDIUeDyalBN0QsuiE/XzPHJBuJDfRArOiWvH0BXOv5kpeXSXM8yTt5Na1jAYSiQ/wIDAQAB',
+        },
+      }
+    },
+  },
+  {
+    url: '/login',
+    type: 'post',
+    response(config) {
+      const { username } = config.body
+      const accessToken = accessTokens[username]
+      if (!accessToken) {
+        return {
+          code: 500,
+          msg: '甯愭埛鎴栧瘑鐮佷笉姝g‘銆�',
+        }
+      }
+      return {
+        code: 200,
+        msg: 'success',
+        data: { accessToken },
+      }
+    },
+  },
+  {
+    url: '/register',
+    type: 'post',
+    response() {
+      return {
+        code: 200,
+        msg: '妯℃嫙娉ㄥ唽鎴愬姛',
+      }
+    },
+  },
+  {
+    url: '/userInfo',
+    type: 'post',
+    response(config) {
+      const { accessToken } = config.body
+      let permissions = ['admin']
+      let username = 'admin'
+      if ('admin-accessToken' === accessToken) {
+        permissions = ['admin']
+        username = 'admin'
+      }
+      if ('editor-accessToken' === accessToken) {
+        permissions = ['editor']
+        username = 'editor'
+      }
+      if ('test-accessToken' === accessToken) {
+        permissions = ['admin', 'editor']
+        username = 'test'
+      }
+      return {
+        code: 200,
+        msg: 'success',
+        data: {
+          permissions,
+          username,
+          'avatar|1': [
+            'https://i.gtimg.cn/club/item/face/img/2/15922_100.gif',
+            'https://i.gtimg.cn/club/item/face/img/8/15918_100.gif',
+          ],
+        },
+      }
+    },
+  },
+  {
+    url: '/logout',
+    type: 'post',
+    response() {
+      return {
+        code: 200,
+        msg: 'success',
+      }
+    },
+  },
+]
diff --git a/mock/controller/userManagement.js b/mock/controller/userManagement.js
new file mode 100644
index 0000000..f83e701
--- /dev/null
+++ b/mock/controller/userManagement.js
@@ -0,0 +1,70 @@
+const totalCount = 3
+const List = [
+  {
+    id: '@id',
+    username: 'admin',
+    password: 'admin',
+    email: '@email',
+    permissions: ['admin'],
+    datatime: '@datetime',
+  },
+  {
+    id: '@id',
+    username: 'editor',
+    password: 'editor',
+    email: '@email',
+    permissions: ['editor'],
+    datatime: '@datetime',
+  },
+  {
+    id: '@id',
+    username: 'test',
+    password: 'test',
+    email: '@email',
+    permissions: ['admin', 'editor'],
+    datatime: '@datetime',
+  },
+]
+module.exports = [
+  {
+    url: '/userManagement/getList',
+    type: 'post',
+    response(config) {
+      const { title = '', pageNo = 1, pageSize = 20 } = config.body
+      let mockList = List.filter((item) => {
+        if (title && item.title.indexOf(title) < 0) return false
+        return true
+      })
+      const pageList = mockList.filter(
+        (item, index) =>
+          index < pageSize * pageNo && index >= pageSize * (pageNo - 1)
+      )
+      return {
+        code: 200,
+        msg: 'success',
+        totalCount,
+        data: pageList,
+      }
+    },
+  },
+  {
+    url: '/userManagement/doEdit',
+    type: 'post',
+    response() {
+      return {
+        code: 200,
+        msg: '妯℃嫙淇濆瓨鎴愬姛',
+      }
+    },
+  },
+  {
+    url: '/userManagement/doDelete',
+    type: 'post',
+    response() {
+      return {
+        code: 200,
+        msg: '妯℃嫙鍒犻櫎鎴愬姛',
+      }
+    },
+  },
+]
diff --git a/mock/index.js b/mock/index.js
new file mode 100644
index 0000000..1910296
--- /dev/null
+++ b/mock/index.js
@@ -0,0 +1,98 @@
+const chokidar = require('chokidar')
+const bodyParser = require('body-parser')
+const chalk = require('chalk')
+const path = require('path')
+const { mock } = require('mockjs')
+const { baseURL } = require('../src/config')
+const mockDir = path.join(process.cwd(), 'mock')
+const { handleMockArray } = require('./utils')
+
+/**
+ *
+ * @param app
+ * @returns {{mockStartIndex: number, mockRoutesLength: number}}
+ */
+const registerRoutes = (app) => {
+  let mockLastIndex
+  const mocks = []
+  const mockArray = handleMockArray()
+  mockArray.forEach((item) => {
+    const obj = require(item)
+    mocks.push(...obj)
+  })
+  const mocksForServer = mocks.map((route) => {
+    return responseFake(route.url, route.type, route.response)
+  })
+  for (const mock of mocksForServer) {
+    app[mock.type](mock.url, mock.response)
+    mockLastIndex = app._router.stack.length
+  }
+  const mockRoutesLength = Object.keys(mocksForServer).length
+  return {
+    mockRoutesLength: mockRoutesLength,
+    mockStartIndex: mockLastIndex - mockRoutesLength,
+  }
+}
+
+/**
+ *
+ * @param url
+ * @param type
+ * @param respond
+ * @returns {{response(*=, *=): void, type: (*|string), url: RegExp}}
+ */
+const responseFake = (url, type, respond) => {
+  return {
+    url: new RegExp(`${baseURL}${url}`),
+    type: type || 'get',
+    response(req, res) {
+      res.status(200)
+      if (JSON.stringify(req.body) !== '{}') {
+        console.log(chalk.green(`> 璇锋眰鍦板潃锛�${req.path}`))
+        console.log(chalk.green(`> 璇锋眰鍙傛暟锛�${JSON.stringify(req.body)}\n`))
+      } else {
+        console.log(chalk.green(`> 璇锋眰鍦板潃锛�${req.path}\n`))
+      }
+      res.json(mock(respond instanceof Function ? respond(req, res) : respond))
+    },
+  }
+}
+/**
+ *
+ * @param app
+ */
+module.exports = (app) => {
+  app.use(bodyParser.json())
+  app.use(
+    bodyParser.urlencoded({
+      extended: true,
+    })
+  )
+
+  const mockRoutes = registerRoutes(app)
+  let mockRoutesLength = mockRoutes.mockRoutesLength
+  let mockStartIndex = mockRoutes.mockStartIndex
+  chokidar
+    .watch(mockDir, {
+      ignored: /mock-server/,
+      ignoreInitial: true,
+    })
+    .on('all', (event) => {
+      if (event === 'change' || event === 'add') {
+        try {
+          app._router.stack.splice(mockStartIndex, mockRoutesLength)
+
+          Object.keys(require.cache).forEach((item) => {
+            if (item.includes(mockDir)) {
+              delete require.cache[require.resolve(item)]
+            }
+          })
+          const mockRoutes = registerRoutes(app)
+          mockRoutesLength = mockRoutes.mockRoutesLength
+          mockStartIndex = mockRoutes.mockStartIndex
+        } catch (error) {
+          console.log(chalk.red(error))
+        }
+      }
+    })
+}
diff --git a/mock/utils/index.js b/mock/utils/index.js
new file mode 100644
index 0000000..840cdac
--- /dev/null
+++ b/mock/utils/index.js
@@ -0,0 +1,43 @@
+const { Random } = require('mockjs')
+const { join } = require('path')
+const fs = require('fs')
+
+/**
+ * @author chuzhixin 1204505056@qq.com 锛堜笉鎯充繚鐣檃uthor鍙垹闄わ級
+ * @description 闅忔満鐢熸垚鍥剧墖url銆�
+ * @param width
+ * @param height
+ * @returns {string}
+ */
+function handleRandomImage(width = 50, height = 50) {
+  return `https://picsum.photos/${width}/${height}?random=${Random.guid()}`
+}
+
+/**
+ * @author chuzhixin 1204505056@qq.com 锛堜笉鎯充繚鐣檃uthor鍙垹闄わ級
+ * @description 澶勭悊鎵�鏈� controller 妯″潡锛宯pm run serve鏃跺湪node鐜涓嚜鍔ㄨ緭鍑篶ontroller鏂囦欢澶逛笅Mock鎺ュ彛锛岃鍕夸慨鏀广��
+ * @returns {[]}
+ */
+function handleMockArray() {
+  const mockArray = []
+  const getFiles = (jsonPath) => {
+    const jsonFiles = []
+    const findJsonFile = (path) => {
+      const files = fs.readdirSync(path)
+      files.forEach((item) => {
+        const fPath = join(path, item)
+        const stat = fs.statSync(fPath)
+        if (stat.isDirectory() === true) findJsonFile(item)
+        if (stat.isFile() === true) jsonFiles.push(item)
+      })
+    }
+    findJsonFile(jsonPath)
+    jsonFiles.forEach((item) => mockArray.push(`./controller/${item}`))
+  }
+  getFiles('mock/controller')
+  return mockArray
+}
+module.exports = {
+  handleRandomImage,
+  handleMockArray,
+}
diff --git a/nginx.conf b/nginx.conf
new file mode 100644
index 0000000..e9b54e6
--- /dev/null
+++ b/nginx.conf
@@ -0,0 +1,45 @@
+user  nginx;
+worker_processes  auto;
+pid        /var/run/nginx.pid;
+
+events {
+    use epoll;
+    worker_connections 51200;
+    multi_accept on;
+}
+
+http {
+    include       /etc/nginx/mime.types;
+    default_type  application/octet-stream;
+
+    server_names_hash_bucket_size 512;
+    client_header_buffer_size 32k;
+    large_client_header_buffers 4 32k;
+    client_max_body_size 50m;
+
+    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
+                      '$status $body_bytes_sent "$http_referer" '
+                      '"$http_user_agent" "$http_x_forwarded_for"';
+    access_log  /var/log/nginx/access.log  main;
+    sendfile        on;
+    tcp_nopush     on;
+    tcp_nodelay on;
+
+    keepalive_timeout  65;
+
+    # gzip 鍘嬬缉
+    gzip on;
+    gzip_min_length  1k;
+    gzip_buffers     4 16k;
+    gzip_http_version 1.1;
+    gzip_comp_level 2;
+    gzip_types     text/plain application/javascript application/x-javascript text/javascript text/css application/xml;
+    gzip_vary on;
+    gzip_proxied   expired no-cache no-store private auth;
+    gzip_disable   "MSIE [1-6]\.";
+
+    limit_conn_zone $binary_remote_addr zone=perip:10m;
+    limit_conn_zone $server_name zone=perserver:10m;
+
+    include /etc/nginx/conf.d/*.conf;
+}
diff --git a/nginx.default.conf b/nginx.default.conf
new file mode 100644
index 0000000..e602ce3
--- /dev/null
+++ b/nginx.default.conf
@@ -0,0 +1,46 @@
+server {
+    listen       80;
+    server_name  localhost;
+
+    access_log  /data/log/nginx/access.log  main;
+    error_log   /data/log/nginx/error.log;
+
+    # 闈欐�佽祫婧�
+    location / {
+        root   /data/web;
+        index  index.html index.htm;
+        try_files $uri $uri/ /index.html;
+    }
+
+    # 鍓嶇浠g悊
+    location ^~ /鍚庣鏈嶅姟鍚� {
+        proxy_pass http://鍚庣鏈嶅姟IP鍦板潃:8080;
+        add_header Access-Control-Allow-Origin *;
+        add_header Access-Control-Allow-Credentials: true;
+        add_header Access-Control-Allow-Methods GET,POST,OPTIONS,PUT,DELETE;
+
+        proxy_http_version 1.1;
+        # 杩炴帴寤舵椂
+        proxy_connect_timeout 3600s;
+        proxy_read_timeout 3600s;
+        proxy_send_timeout 3600s;
+        # IP 绌块��
+        proxy_set_header        Host $proxy_host;
+        proxy_set_header        X-Real-IP $remote_addr;
+        proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;
+        # WebSocket 绌块��
+        proxy_set_header Origin "";
+        proxy_set_header Upgrade $http_upgrade;
+        proxy_set_header Connection "upgrade";
+    }
+
+    # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
+    #location ~ \.php$ {
+    #    root           html;
+    #    fastcgi_pass   127.0.0.1:9000;
+    #    fastcgi_index  index.php;
+    #    fastcgi_param  SCRIPT_FILENAME  /scripts$fastcgi_script_name;
+    #    include        fastcgi_params;
+    #}
+}
+
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..6da370f
--- /dev/null
+++ b/package.json
@@ -0,0 +1,105 @@
+{
+  "name": "vue-integrate",
+  "version": "1.0.0",
+  "participants": [],
+  "publishConfig": {
+    "registry": "https://npm.pkg.github.com/"
+  },
+  "scripts": {
+    "serve": "vue-cli-service serve",
+    "build": "vue-cli-service build",
+    "build:report": "vue-cli-service build --report",
+    "globle": "npm install -g cnpm --registry=https://registry.npm.taobao.org&&cnpm i rimraf npm-check-updates nrm -g&&rimraf node_modules&&cnpm i",
+    "lint": "vue-cli-service lint",
+    "lint:style": "stylelint-config-prettier-check",
+    "inspect": "vue-cli-service inspect",
+    "template": "plop",
+    "clear": "rimraf node_modules&&npm install --registry=https://registry.npm.taobao.org",
+    "image-webpack-loader": "cnpm i image-webpack-loader -D",
+    "use:npm": "nrm use npm",
+    "use:taobao": "nrm use taobao",
+    "update": "ncu -u --reject sass-loader,sass&&cnpm i",
+    "update:globle": "ncu -g --concurrency 10 --timeout 80000"
+  },
+  "gitHooks": {
+    "pre-commit": "lint-staged"
+  },
+  "lint-staged": {
+    "*.{js,jsx,vue}": [
+      "vue-cli-service lint",
+      "git add"
+    ]
+  },
+  "dependencies": {
+    "axios": "^0.21.1",
+    "clipboard": "^2.0.8",
+    "core-js": "^3.16.2",
+    "dayjs": "^1.10.6",
+    "echarts": "^5.1.2",
+    "element-ui": "^2.15.5",
+    "jsencrypt": "^3.2.1",
+    "lodash": "^4.17.21",
+    "maptalks": "^0.49.5",
+    "mapv": "^2.0.62",
+    "mockjs": "^1.1.0",
+    "nprogress": "^0.2.0",
+    "qs": "^6.10.1",
+    "screenfull": "^5.1.0",
+    "sortablejs": "^1.14.0",
+    "vab-icon": "^0.0.1",
+    "vue": "^2.6.14",
+    "vue-echarts": "6.0.0",
+    "vue-router": "^3.5.2",
+    "vuex": "^3.6.2",
+    "zx-count": "^0.3.7",
+    "zx-layouts": "^0.6.27",
+    "zx-magnifie": "^0.4.0",
+    "zx-markdown-editor": "^0.0.2",
+    "zx-player": "^1.0.2",
+    "zx-quill": "^0.0.3",
+    "zx-templates": "^0.0.26",
+    "zx-verify": "^0.0.2"
+  },
+  "devDependencies": {
+    "@vue/cli-plugin-babel": "^4.5.13",
+    "@vue/cli-plugin-eslint": "^4.5.13",
+    "@vue/cli-service": "^4.5.13",
+    "@vue/composition-api": "^1.1.3",
+    "@vue/eslint-config-prettier": "^6.0.0",
+    "babel-eslint": "^10.1.0",
+    "body-parser": "^1.19.0",
+    "chalk": "^4.1.2",
+    "chokidar": "^3.5.2",
+    "eslint": "^7.32.0",
+    "eslint-plugin-prettier": "^3.4.1",
+    "eslint-plugin-vue": "^7.16.0",
+    "filemanager-webpack-plugin": "^6.1.6",
+    "image-webpack-loader": "^7.0.1",
+    "lint-staged": "^11.1.2",
+    "plop": "^2.7.4",
+    "prettier": "^2.3.2",
+    "sass": "~1.32.13",
+    "sass-loader": "^10.1.1",
+    "stylelint": "^13.13.1",
+    "stylelint-config-prettier": "^8.0.2",
+    "stylelint-config-recess-order": "^2.5.0",
+    "svg-sprite-loader": "^6.0.9",
+    "vue-template-compiler": "^2.6.14",
+    "webpackbar": "^4.0.0"
+  },
+  "keywords": [
+    "vue",
+    "admin",
+    "dashboard",
+    "element-ui",
+    "vue-admin",
+    "element-admin",
+    "boilerplate",
+    "admin-template",
+    "management-system"
+  ],
+  "engines": {
+    "node": ">=8.9",
+    "npm": ">= 3.0.0"
+  }
+}
\ No newline at end of file
diff --git a/plopfile.js b/plopfile.js
new file mode 100644
index 0000000..8121409
--- /dev/null
+++ b/plopfile.js
@@ -0,0 +1,16 @@
+/**
+ * @author chuzhixin 1204505056@qq.com 锛堜笉鎯充繚鐣檃uthor鍙垹闄わ級
+ * @description 浠g爜鐢熸垚鏈�
+ */
+const viewGenerator = require('zx-templates/view/prompt')
+const curdGenerator = require('zx-templates/curd/prompt')
+const componentGenerator = require('zx-templates/component/prompt')
+const mockGenerator = require('zx-templates/mock/prompt')
+const vuexGenerator = require('zx-templates/vuex/prompt')
+module.exports = (plop) => {
+  plop.setGenerator('view', viewGenerator)
+  plop.setGenerator('curd', curdGenerator)
+  plop.setGenerator('component', componentGenerator)
+  plop.setGenerator('mock&api', mockGenerator)
+  plop.setGenerator('vuex', vuexGenerator)
+}
diff --git a/prettier.config.js b/prettier.config.js
new file mode 100644
index 0000000..51cd871
--- /dev/null
+++ b/prettier.config.js
@@ -0,0 +1,16 @@
+module.exports = {
+  // printWidth: 80,
+  // tabWidth: 4,
+  // useTabs: false,
+  // semi: false,
+  // singleQuote: true,
+  // quoteProps: 'as-needed',
+  // jsxSingleQuote: false,
+  // trailingComma: 'es5',
+  // bracketSpacing: true,
+  // jsxBracketSameLine: false,
+  // arrowParens: 'always',
+  // htmlWhitespaceSensitivity: 'ignore',
+  // vueIndentScriptAndStyle: true,
+  // endOfLine: 'lf',
+}
diff --git a/public/favicon.ico b/public/favicon.ico
new file mode 100644
index 0000000..7c31c49
--- /dev/null
+++ b/public/favicon.ico
Binary files differ
diff --git a/public/favicon_backup.ico b/public/favicon_backup.ico
new file mode 100644
index 0000000..df36fcf
--- /dev/null
+++ b/public/favicon_backup.ico
Binary files differ
diff --git a/public/index.html b/public/index.html
new file mode 100644
index 0000000..82c9527
--- /dev/null
+++ b/public/index.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html lang="zh-cmn-Hans">
+
+<head>
+  <meta charset="utf-8" />
+  <meta http-equiv="X-UA-Compatible" content="IE=edge" />
+  <meta name="viewport" content="width=device-width,initial-scale=1.0" />
+  <link rel="icon" href="<%= BASE_URL %>favicon.ico" />
+  <title>
+    <%= VUE_APP_TITLE %>
+  </title>
+
+  <meta name="author" content="<%= VUE_APP_AUTHOR %>" />
+  <link rel="stylesheet" href="<%= BASE_URL %>static/css/loading.css" />
+</head>
+
+<body>
+  <div id="vue-admin-beautiful">
+    <div class="first-loading-wrp">
+      <div class="loading-wrp">
+        <span class="dot dot-spin">
+          <i></i>
+          <i></i>
+          <i></i>
+          <i></i>
+        </span>
+      </div>
+    </div>
+  </div>
+</body>
+
+</html>
\ No newline at end of file
diff --git a/public/static/css/loading.css b/public/static/css/loading.css
new file mode 100644
index 0000000..b8624b0
--- /dev/null
+++ b/public/static/css/loading.css
@@ -0,0 +1,99 @@
+/**
+ * @description 闆姳灞忎唬鐮侊紝鍩轰簬ant-design淇敼
+ **/
+.first-loading-wrp {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  height: 90vh;
+  min-height: 90vh;
+}
+
+.first-loading-wrp > h1 {
+  font-size: 30px;
+  font-weight: bolder;
+}
+
+.first-loading-wrp .loading-wrp {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  padding: 98px;
+}
+
+.dot {
+  position: relative;
+  box-sizing: border-box;
+  display: inline-block;
+  width: 64px;
+  height: 64px;
+  font-size: 64px;
+  transform: rotate(45deg);
+  animation: antRotate 1.2s infinite linear;
+}
+
+.dot i {
+  position: absolute;
+  display: block;
+  width: 28px;
+  height: 28px;
+  background-color: #1890ff;
+  border-radius: 100%;
+  opacity: 0.3;
+  transform: scale(0.75);
+  transform-origin: 50% 50%;
+  animation: antSpinMove 1s infinite linear alternate;
+}
+
+.dot i:nth-child(1) {
+  top: 0;
+  left: 0;
+}
+
+.dot i:nth-child(2) {
+  top: 0;
+  right: 0;
+  -webkit-animation-delay: 0.4s;
+  animation-delay: 0.4s;
+}
+
+.dot i:nth-child(3) {
+  right: 0;
+  bottom: 0;
+  -webkit-animation-delay: 0.8s;
+  animation-delay: 0.8s;
+}
+
+.dot i:nth-child(4) {
+  bottom: 0;
+  left: 0;
+  -webkit-animation-delay: 1.2s;
+  animation-delay: 1.2s;
+}
+
+@keyframes antRotate {
+  to {
+    -webkit-transform: rotate(405deg);
+    transform: rotate(405deg);
+  }
+}
+
+@-webkit-keyframes antRotate {
+  to {
+    -webkit-transform: rotate(405deg);
+    transform: rotate(405deg);
+  }
+}
+
+@keyframes antSpinMove {
+  to {
+    opacity: 1;
+  }
+}
+
+@-webkit-keyframes antSpinMove {
+  to {
+    opacity: 1;
+  }
+}
diff --git a/src/App.vue b/src/App.vue
new file mode 100644
index 0000000..94f824e
--- /dev/null
+++ b/src/App.vue
@@ -0,0 +1,12 @@
+<template>
+  <div id="vue-admin-beautiful">
+    <router-view />
+  </div>
+</template>
+
+<script>
+  export default {
+    name: 'App',
+    mounted() {},
+  }
+</script>
diff --git a/src/api/package.js b/src/api/package.js
new file mode 100644
index 0000000..c8334f6
--- /dev/null
+++ b/src/api/package.js
@@ -0,0 +1,9 @@
+import request from '@/utils/request'
+
+export function publish(data) {
+  return request({
+    url: '/package/' + data.id + '/publish',
+    method: 'post',
+    data,
+  })
+}
diff --git a/src/api/project.js b/src/api/project.js
new file mode 100644
index 0000000..c892e47
--- /dev/null
+++ b/src/api/project.js
@@ -0,0 +1,51 @@
+import request from '@/utils/request'
+
+export function getList() {
+  return request({
+    url: '/projects',
+    method: 'get',
+  })
+}
+
+export function doEdit(data) {
+  if (!data.id) {
+    return request({
+      url: '/projects',
+      method: 'post',
+      data,
+    })
+  }
+
+  delete data.updatedAt
+  delete data.createdAt
+
+  return request({
+    url: '/projects',
+    method: 'put',
+    data,
+  })
+}
+
+export function deletePrj(id) {
+  return request({
+    url: '/projects/' + id,
+    method: 'delete',
+  })
+}
+
+export function getPkgList(id) {
+  return request({
+    url: '/projects/' + id + '/packages',
+    method: 'get',
+  })
+}
+
+export function buildPkg(data, version) {
+  delete data.updatedAt
+  delete data.createdAt
+  return request({
+    url: '/projects/build/' + version,
+    method: 'post',
+    data,
+  })
+}
diff --git a/src/api/user.js b/src/api/user.js
new file mode 100644
index 0000000..f661449
--- /dev/null
+++ b/src/api/user.js
@@ -0,0 +1,34 @@
+import request from '@/utils/request'
+import { loginRSA, tokenName } from '@/config'
+
+export async function login(data) {
+  return request({
+    url: '/user/login',
+    method: 'post',
+    data,
+  })
+}
+
+export function getUserInfo(accessToken) {
+  return request({
+    url: '/user/info',
+    method: 'post',
+    data: {
+      [tokenName]: accessToken,
+    },
+  })
+}
+
+export function logout() {
+  return request({
+    url: '/logout',
+    method: 'post',
+  })
+}
+
+export function register() {
+  return request({
+    url: '/register',
+    method: 'post',
+  })
+}
diff --git a/src/assets/comparison/left.jpg b/src/assets/comparison/left.jpg
new file mode 100644
index 0000000..f5133cf
--- /dev/null
+++ b/src/assets/comparison/left.jpg
Binary files differ
diff --git a/src/assets/comparison/right.jpg b/src/assets/comparison/right.jpg
new file mode 100644
index 0000000..625a53f
--- /dev/null
+++ b/src/assets/comparison/right.jpg
Binary files differ
diff --git a/src/assets/error_images/401.png b/src/assets/error_images/401.png
new file mode 100644
index 0000000..90bbf6e
--- /dev/null
+++ b/src/assets/error_images/401.png
Binary files differ
diff --git a/src/assets/error_images/404.png b/src/assets/error_images/404.png
new file mode 100644
index 0000000..14fa725
--- /dev/null
+++ b/src/assets/error_images/404.png
Binary files differ
diff --git a/src/assets/error_images/cloud.png b/src/assets/error_images/cloud.png
new file mode 100644
index 0000000..247c06b
--- /dev/null
+++ b/src/assets/error_images/cloud.png
Binary files differ
diff --git a/src/assets/ewm.png b/src/assets/ewm.png
new file mode 100644
index 0000000..a97a6ad
--- /dev/null
+++ b/src/assets/ewm.png
Binary files differ
diff --git a/src/assets/login_images/background.jpg b/src/assets/login_images/background.jpg
new file mode 100644
index 0000000..87ac496
--- /dev/null
+++ b/src/assets/login_images/background.jpg
Binary files differ
diff --git a/src/assets/pro.png b/src/assets/pro.png
new file mode 100644
index 0000000..09bcef5
--- /dev/null
+++ b/src/assets/pro.png
Binary files differ
diff --git a/src/assets/qr_logo/lqr_logo.png b/src/assets/qr_logo/lqr_logo.png
new file mode 100644
index 0000000..f81db46
--- /dev/null
+++ b/src/assets/qr_logo/lqr_logo.png
Binary files differ
diff --git a/src/assets/zfb_100.jpg b/src/assets/zfb_100.jpg
new file mode 100644
index 0000000..530857c
--- /dev/null
+++ b/src/assets/zfb_100.jpg
Binary files differ
diff --git a/src/assets/zfb_699.jpg b/src/assets/zfb_699.jpg
new file mode 100644
index 0000000..4760e7c
--- /dev/null
+++ b/src/assets/zfb_699.jpg
Binary files differ
diff --git a/src/assets/zfb_799.jpg b/src/assets/zfb_799.jpg
new file mode 100644
index 0000000..ff10604
--- /dev/null
+++ b/src/assets/zfb_799.jpg
Binary files differ
diff --git a/src/assets/zfb_kf.jpg b/src/assets/zfb_kf.jpg
new file mode 100644
index 0000000..a850a5e
--- /dev/null
+++ b/src/assets/zfb_kf.jpg
Binary files differ
diff --git a/src/colorfulIcon/index.js b/src/colorfulIcon/index.js
new file mode 100644
index 0000000..2dd876e
--- /dev/null
+++ b/src/colorfulIcon/index.js
@@ -0,0 +1,13 @@
+const req = require.context('./svg', false, /\.svg$/),
+  requireAll = (requireContext) => {
+    /*let a = requireContext.keys().map(requireContext);
+    let arr = [];
+    for (let i = 0; i < a.length; i++) {
+      console.log();
+      let icon = a[i].default.id;
+      arr.push(icon);
+    }
+    console.log(JSON.stringify(arr));*/
+    return requireContext.keys().map(requireContext)
+  }
+requireAll(req)
diff --git a/src/colorfulIcon/svg/alphabetical_sorting.svg b/src/colorfulIcon/svg/alphabetical_sorting.svg
new file mode 100644
index 0000000..3cfa035
--- /dev/null
+++ b/src/colorfulIcon/svg/alphabetical_sorting.svg
@@ -0,0 +1 @@
+<svg class="icon" width="128" height="128" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg"><path d="M358.4 853.333H245.333l-23.466 64H147.2l121.6-324.266h61.867l119.466 324.266h-68.266l-23.467-64zm-98.133-57.6h81.066l-40.533-121.6-40.533 121.6zm4.266-418.133h162.134v53.333H179.2V390.4L341.333 160H179.2v-53.333h243.2v36.266L264.533 377.6z" fill="#2196F3"/><path d="M810.667 704V106.667h-85.334V704h-128L768 917.333 938.667 704z" fill="#546E7A"/></svg>
\ No newline at end of file
diff --git a/src/colorfulIcon/svg/vab.svg b/src/colorfulIcon/svg/vab.svg
new file mode 100644
index 0000000..5c46271
--- /dev/null
+++ b/src/colorfulIcon/svg/vab.svg
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg version="1.1" id="Layer_1"
+     xmlns="http://www.w3.org/2000/svg"
+     width="550px" height="400px"
+     xml:space="preserve">
+<g id="PathID_1" transform="matrix(10.7099, 0, 0, 10.7099, 76.4, 396.15)" opacity="1">
+<path style="fill: #41b882; fill-opacity: 1;" d="M3.75 -36.65L18.4 -36.65Q22.75 -36.65 24.85 -36.25Q27 -35.9 28.7 -34.75Q30.4 -33.6 31.5 -31.7Q32.65 -29.8 32.65 -27.4Q32.65 -24.85 31.25 -22.7Q29.85 -20.55 27.5 -19.5Q30.85 -18.5 32.65 -16.15Q34.45 -13.8 34.45 -10.6Q34.45 -8.1 33.25 -5.75Q32.1 -3.4 30.1 -1.95Q28.1 -0.55 25.15 -0.25Q23.3 -0.05 16.2 0L3.75 0L3.75 -36.65M11.15 -30.55L11.15 -22.1L16 -22.1Q20.3 -22.1 21.35 -22.2Q23.25 -22.4 24.35 -23.5Q25.45 -24.6 25.45 -26.35Q25.45 -28.05 24.5 -29.1Q23.55 -30.2 21.7 -30.4Q20.6 -30.55 15.4 -30.55L11.15 -30.55M11.15 -16L11.15 -6.2L18 -6.2Q22 -6.2 23.05 -6.4Q24.7 -6.7 25.75 -7.85Q26.8 -9.05 26.8 -11Q26.8 -12.65 26 -13.8Q25.2 -14.95 23.65 -15.45Q22.15 -16 17.1 -16L11.15 -16" />
+</g>
+<g id="PathID_2" transform="matrix(10.7099, 0, 0, 10.7099, 76.4, 396.15)" opacity="1">
+</g>
+<g id="PathID_3" transform="matrix(5.31826, 0, 0, 2.59618, 172.9, 161.55)" opacity="1">
+<path style="fill: #35495e; fill-opacity: 1;" d="M3.75 -36.65L17.25 -36.65Q21.8 -36.65 24.2 -35.95Q27.45 -35 29.75 -32.55Q32.05 -30.15 33.25 -26.6Q34.45 -23.1 34.45 -17.95Q34.45 -13.45 33.3 -10.15Q31.95 -6.15 29.4 -3.7Q27.45 -1.8 24.2 -0.75Q21.75 0 17.65 0L3.75 0L3.75 -36.65M11.15 -30.45L11.15 -6.2L16.65 -6.2Q19.75 -6.2 21.1 -6.55Q22.9 -6.95 24.1 -8Q25.3 -9.1 26.05 -11.55Q26.8 -14.05 26.8 -18.3Q26.8 -22.55 26.05 -24.8Q25.3 -27.1 23.95 -28.35Q22.6 -29.65 20.5 -30.1Q18.95 -30.45 14.45 -30.45L11.15 -30.45" />
+</g>
+<g id="PathID_4" transform="matrix(5.31826, 0, 0, 2.59618, 172.9, 161.55)" opacity="1">
+</g>
+<g id="PathID_5" transform="matrix(5.78477, 0, 0, 3.1825, 171.7, 333.8)" opacity="1">
+<path style="fill: #35495e; fill-opacity: 1;" d="M3.75 -36.65L17.25 -36.65Q21.8 -36.65 24.2 -35.95Q27.45 -35 29.75 -32.55Q32.05 -30.15 33.25 -26.6Q34.45 -23.1 34.45 -17.95Q34.45 -13.45 33.3 -10.15Q31.95 -6.15 29.4 -3.7Q27.45 -1.8 24.2 -0.75Q21.75 0 17.65 0L3.75 0L3.75 -36.65M11.15 -30.45L11.15 -6.2L16.65 -6.2Q19.75 -6.2 21.1 -6.55Q22.9 -6.95 24.1 -8Q25.3 -9.1 26.05 -11.55Q26.8 -14.05 26.8 -18.3Q26.8 -22.55 26.05 -24.8Q25.3 -27.1 23.95 -28.35Q22.6 -29.65 20.5 -30.1Q18.95 -30.45 14.45 -30.45L11.15 -30.45" />
+</g>
+<g id="PathID_6" transform="matrix(5.78477, 0, 0, 3.1825, 171.7, 333.8)" opacity="1">
+</g>
+</svg>
diff --git a/src/components/SelectTree/index.vue b/src/components/SelectTree/index.vue
new file mode 100644
index 0000000..bacc7dd
--- /dev/null
+++ b/src/components/SelectTree/index.vue
@@ -0,0 +1,201 @@
+<template>
+  <div class="select-tree-template">
+    <el-select
+      v-model="selectValue"
+      :clearable="clearable"
+      :collapse-tags="selectType == 'multiple'"
+      :multiple="selectType == 'multiple'"
+      class="vab-tree-select"
+      value-key="id"
+      @clear="clearHandle"
+      @remove-tag="removeTag"
+    >
+      <el-option :value="selectKey">
+        <el-tree
+          id="treeOption"
+          ref="treeOption"
+          :current-node-key="currentNodeKey"
+          :data="treeOptions"
+          :default-checked-keys="defaultSelectedKeys"
+          :default-expanded-keys="defaultSelectedKeys"
+          :highlight-current="true"
+          :props="defaultProps"
+          :show-checkbox="selectType == 'multiple'"
+          node-key="id"
+          @check="checkNode"
+          @node-click="nodeClick"
+        ></el-tree>
+      </el-option>
+    </el-select>
+  </div>
+</template>
+
+<script>
+  export default {
+    name: 'SelectTreeTemplate',
+    props: {
+      /* 鏍戝舰缁撴瀯鏁版嵁 */
+      treeOptions: {
+        type: Array,
+        default: () => {
+          return []
+        },
+      },
+      /* 鍗曢��/澶氶�� */
+      selectType: {
+        type: String,
+        default: () => {
+          return 'single'
+        },
+      },
+      /* 鍒濆閫変腑鍊糼ey */
+      selectedKey: {
+        type: String,
+        default: () => {
+          return ''
+        },
+      },
+      /* 鍒濆閫変腑鍊糿ame */
+      selectedValue: {
+        type: String,
+        default: () => {
+          return ''
+        },
+      },
+      /* 鍙仛閫夋嫨鐨勫眰绾� */
+      selectLevel: {
+        type: [String, Number],
+        default: () => {
+          return ''
+        },
+      },
+      /* 鍙竻绌洪�夐」 */
+      clearable: {
+        type: Boolean,
+        default: () => {
+          return true
+        },
+      },
+    },
+    data() {
+      return {
+        defaultProps: {
+          children: 'children',
+          label: 'name',
+        },
+        defaultSelectedKeys: [], //鍒濆閫変腑鍊兼暟缁�
+        currentNodeKey: this.selectedKey,
+        selectValue:
+          this.selectType == 'multiple'
+            ? this.selectedValue.split(',')
+            : this.selectedValue, //涓嬫媺妗嗛�変腑鍊糽abel
+        selectKey:
+          this.selectType == 'multiple'
+            ? this.selectedKey.split(',')
+            : this.selectedKey, //涓嬫媺妗嗛�変腑鍊紇alue
+      }
+    },
+    mounted() {
+      const that = this
+      this.initTree()
+    },
+    methods: {
+      // 鍒濆鍖栨爲鐨勫��
+      initTree() {
+        const that = this
+        if (that.selectedKey) {
+          that.defaultSelectedKeys = that.selectedKey.split(',') // 璁剧疆榛樿灞曞紑
+          if (that.selectType == 'single') {
+            that.$refs.treeOption.setCurrentKey(that.selectedKey) // 璁剧疆榛樿閫変腑
+          } else {
+            that.$refs.treeOption.setCheckedKeys(that.defaultSelectedKeys)
+          }
+        }
+      },
+      // 娓呴櫎閫変腑
+      clearHandle() {
+        const that = this
+        this.selectValue = ''
+        this.selectKey = ''
+        this.defaultSelectedKeys = []
+        this.currentNodeKey = ''
+        this.clearSelected()
+        if (that.selectType == 'single') {
+          that.$refs.treeOption.setCurrentKey('') // 璁剧疆榛樿閫変腑
+        } else {
+          that.$refs.treeOption.setCheckedKeys([])
+        }
+      },
+      /* 娓呯┖閫変腑鏍峰紡 */
+      clearSelected() {
+        const allNode = document.querySelectorAll('#treeOption .el-tree-node')
+        allNode.forEach((element) => element.classList.remove('is-current'))
+      },
+      // select澶氶�夋椂绉婚櫎鏌愰」鎿嶄綔
+      removeTag(val) {
+        this.$refs.treeOption.setCheckedKeys([])
+      },
+      // 鐐瑰嚮鍙跺瓙鑺傜偣
+      nodeClick(data, node, el) {
+        if (data.rank >= this.selectLevel) {
+          this.selectValue = data.name
+          this.selectKey = data.id
+        }
+      },
+      // 鑺傜偣閫変腑鎿嶄綔
+      checkNode(data, node, el) {
+        const checkedNodes = this.$refs.treeOption.getCheckedNodes()
+        const keyArr = []
+        const valueArr = []
+        checkedNodes.forEach((item) => {
+          if (item.rank >= this.selectLevel) {
+            keyArr.push(item.id)
+            valueArr.push(item.name)
+          }
+        })
+        this.selectValue = valueArr
+        this.selectKey = keyArr
+      },
+    },
+  }
+</script>
+
+<style lang="scss" scoped>
+  .el-scrollbar .el-scrollbar__view .el-select-dropdown__item {
+    height: auto;
+    max-height: 274px;
+    padding: 0;
+    overflow-y: auto;
+  }
+
+  .el-select-dropdown__item.selected {
+    font-weight: normal;
+  }
+
+  ul li > .el-tree .el-tree-node__content {
+    height: auto;
+    padding: 0 20px;
+  }
+
+  .el-tree-node__label {
+    font-weight: normal;
+  }
+
+  .el-tree > .is-current .el-tree-node__label {
+    font-weight: 700;
+    color: #409eff;
+  }
+
+  .el-tree > .is-current .el-tree-node__children .el-tree-node__label {
+    font-weight: normal;
+    color: #606266;
+  }
+</style>
+<style lang="scss">
+  /* .vab-tree-select{
+      .el-tag__close.el-icon-close{
+        width:0;
+        overflow:hidden;
+      }
+    } */
+</style>
diff --git a/src/components/VabCharge/index.vue b/src/components/VabCharge/index.vue
new file mode 100644
index 0000000..609da97
--- /dev/null
+++ b/src/components/VabCharge/index.vue
@@ -0,0 +1,191 @@
+<template>
+  <div class="content">
+    <div class="g-container" :style="styleObj">
+      <div class="g-number">
+        <vab-count
+          :start-val="startVal"
+          :end-val="endVal"
+          :duration="duration"
+          :separator="separator"
+          :prefix="prefix"
+          :suffix="suffix"
+          :decimals="decimals"
+        />
+      </div>
+      <div class="g-contrast">
+        <div class="g-circle"></div>
+        <ul class="g-bubbles">
+          <li v-for="(item, index) in 15" :key="index"></li>
+        </ul>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+  export default {
+    name: 'VabCharge',
+    props: {
+      styleObj: {
+        type: Object,
+        default: () => {
+          return {}
+        },
+      },
+      startVal: {
+        type: Number,
+        default: 0,
+      },
+      endVal: {
+        type: Number,
+        default: 100,
+      },
+    },
+    data() {
+      return {
+        decimals: 2,
+        prefix: '',
+        suffix: '%',
+        separator: ',',
+        duration: 3000,
+      }
+    },
+    created() {},
+    mounted() {},
+    methods: {},
+  }
+</script>
+
+<style lang="scss" scoped>
+  .content {
+    position: relative;
+    display: flex;
+    align-items: center; /* 鍨傜洿灞呬腑 */
+    justify-content: center; /* 姘村钩灞呬腑 */
+    width: 100%;
+    background: #000;
+
+    .g-number {
+      position: absolute;
+      top: 27%;
+      z-index: 99;
+      width: 300px;
+      font-size: 32px;
+      color: #fff;
+      text-align: center;
+    }
+
+    .g-container {
+      position: relative;
+      width: 300px;
+      height: 400px;
+      margin: auto;
+    }
+
+    .g-contrast {
+      width: 300px;
+      height: 400px;
+      overflow: hidden;
+      background-color: #000;
+      filter: contrast(15) hue-rotate(0);
+      animation: hueRotate 10s infinite linear;
+    }
+
+    .g-circle {
+      position: relative;
+      box-sizing: border-box;
+      width: 300px;
+      height: 300px;
+      filter: blur(8px);
+
+      &::after {
+        position: absolute;
+        top: 40%;
+        left: 50%;
+        width: 200px;
+        height: 200px;
+        content: '';
+        background-color: #00ff6f;
+        border-radius: 42% 38% 62% 49% / 45%;
+        transform: translate(-50%, -50%) rotate(0);
+        animation: rotate 10s infinite linear;
+      }
+
+      &::before {
+        position: absolute;
+        top: 40%;
+        left: 50%;
+        z-index: 99;
+        width: 176px;
+        height: 176px;
+        content: '';
+        background-color: #000;
+        border-radius: 50%;
+        transform: translate(-50%, -50%);
+      }
+    }
+
+    .g-bubbles {
+      position: absolute;
+      bottom: 0;
+      left: 50%;
+      width: 100px;
+      height: 40px;
+      background-color: #00ff6f;
+      filter: blur(5px);
+      border-radius: 100px 100px 0 0;
+      transform: translate(-50%, 0);
+    }
+
+    li {
+      position: absolute;
+      background: #00ff6f;
+      border-radius: 50%;
+    }
+
+    @for $i from 0 through 15 {
+      li:nth-child(#{$i}) {
+        $width: 15 + random(15) + px;
+
+        top: 50%;
+        left: 15 + random(70) + px;
+        width: $width;
+        height: $width;
+        transform: translate(-50%, -50%);
+        animation: moveToTop
+          #{random(6) +
+          3}s
+          ease-in-out -#{random(5000) /
+          1000}s
+          infinite;
+      }
+    }
+
+    @keyframes rotate {
+      50% {
+        border-radius: 45% / 42% 38% 58% 49%;
+      }
+
+      100% {
+        transform: translate(-50%, -50%) rotate(720deg);
+      }
+    }
+
+    @keyframes moveToTop {
+      90% {
+        opacity: 1;
+      }
+
+      100% {
+        opacity: 0.1;
+        transform: translate(-50%, -180px);
+      }
+    }
+
+    @keyframes hueRotate {
+      100% {
+        filter: contrast(15) hue-rotate(360deg);
+      }
+    }
+  }
+</style>
diff --git a/src/components/VabProfile/index.vue b/src/components/VabProfile/index.vue
new file mode 100644
index 0000000..7f291dd
--- /dev/null
+++ b/src/components/VabProfile/index.vue
@@ -0,0 +1,313 @@
+<template>
+  <div class="card" :style="styleObj">
+    <div class="card-borders">
+      <div class="border-top"></div>
+      <div class="border-right"></div>
+      <div class="border-bottom"></div>
+      <div class="border-left"></div>
+    </div>
+    <div class="card-content">
+      <el-image :src="avatar" class="avatar"></el-image>
+      <div class="username">{{ username }}</div>
+      <div class="social-icons">
+        <a
+          v-for="(item, index) in iconArray"
+          :key="index"
+          class="social-icon"
+          :href="item.url"
+          target="_blank"
+        >
+          <vab-icon :icon="['fas', item.icon]" />
+        </a>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+  export default {
+    name: 'VabProfile',
+    props: {
+      styleObj: {
+        type: Object,
+        default: () => {
+          return {}
+        },
+      },
+      username: {
+        type: String,
+        default: '',
+      },
+      avatar: {
+        type: String,
+        default: '',
+      },
+      iconArray: {
+        type: Array,
+        default: () => {
+          return [
+            { icon: 'bell', url: '' },
+            { icon: 'bookmark', url: '' },
+            { icon: 'cloud-sun', url: '' },
+          ]
+        },
+      },
+    },
+    data() {
+      return {}
+    },
+    created() {},
+    mounted() {},
+    methods: {},
+  }
+</script>
+
+<style lang="scss" scoped>
+  .card {
+    --card-bg-color: hsl(240, 31%, 25%);
+    --card-bg-color-transparent: hsla(240, 31%, 25%, 0.7);
+
+    position: relative;
+    width: 100%;
+    height: 100%;
+
+    .card-borders {
+      position: absolute;
+      top: 0;
+      left: 0;
+      width: 100%;
+      height: 100%;
+      overflow: hidden;
+
+      .border-top {
+        position: absolute;
+        top: 0;
+        width: 100%;
+        height: 2px;
+        background: var(--card-bg-color);
+        transform: translateX(-100%);
+        animation: slide-in-horizontal 0.8s cubic-bezier(0.645, 0.045, 0.355, 1)
+          forwards;
+      }
+
+      .border-right {
+        position: absolute;
+        right: 0;
+        width: 2px;
+        height: 100%;
+        background: var(--card-bg-color);
+        transform: translateY(100%);
+        animation: slide-in-vertical 0.8s cubic-bezier(0.645, 0.045, 0.355, 1)
+          forwards;
+      }
+
+      .border-bottom {
+        position: absolute;
+        bottom: 0;
+        width: 100%;
+        height: 2px;
+        background: var(--card-bg-color);
+        transform: translateX(100%);
+        animation: slide-in-horizontal-reverse 0.8s
+          cubic-bezier(0.645, 0.045, 0.355, 1) forwards;
+      }
+
+      .border-left {
+        position: absolute;
+        top: 0;
+        width: 2px;
+        height: 100%;
+        background: var(--card-bg-color);
+        transform: translateY(-100%);
+        animation: slide-in-vertical-reverse 0.8s
+          cubic-bezier(0.645, 0.045, 0.355, 1) forwards;
+      }
+    }
+
+    .card-content {
+      display: flex;
+      flex-direction: column;
+      align-items: center;
+      height: 100%;
+      padding: 40px 0 40px 0;
+      background: var(--card-bg-color-transparent);
+      opacity: 0;
+      transform: scale(0.6);
+      animation: bump-in 0.5s 0.8s forwards;
+
+      .avatar {
+        width: 80px;
+        height: 80px;
+        border: 1px solid $base-color-white;
+        border-radius: 50%;
+        opacity: 0;
+        transform: scale(0.6);
+        animation: bump-in 0.5s 1s forwards;
+      }
+
+      .username {
+        position: relative;
+        margin-top: 20px;
+        margin-bottom: 20px;
+        font-size: 26px;
+        color: transparent;
+        letter-spacing: 2px;
+        animation: fill-text-white 1.2s 2s forwards;
+
+        &::before {
+          position: absolute;
+          top: 0;
+          left: 0;
+          width: 100%;
+          height: 100%;
+          color: black;
+          content: '';
+          background: #35b9f1;
+          transform: scaleX(0);
+          transform-origin: left;
+          animation: slide-in-out 1.2s 1.2s cubic-bezier(0.75, 0, 0, 1) forwards;
+        }
+      }
+
+      .social-icons {
+        display: flex;
+
+        .social-icon {
+          position: relative;
+          display: flex;
+          align-items: center;
+          justify-content: center;
+          width: 2.5em;
+          height: 2.5em;
+          margin: 0 15px;
+          color: white;
+          text-decoration: none;
+          border-radius: 50%;
+
+          @for $i from 1 through 3 {
+            &:nth-child(#{$i}) {
+              &::before {
+                animation-delay: 2s + 0.1s * $i;
+              }
+
+              &::after {
+                animation-delay: 2.1s + 0.1s * $i;
+              }
+
+              svg {
+                animation-delay: 2.2s + 0.1s * $i;
+              }
+            }
+          }
+
+          &::before,
+          &::after {
+            position: absolute;
+            top: 0;
+            left: 0;
+            width: 100%;
+            height: 100%;
+            content: '';
+            border-radius: inherit;
+            transform: scale(0);
+          }
+
+          &::before {
+            background: #f7f1e3;
+            animation: scale-in 0.5s cubic-bezier(0.75, 0, 0, 1) forwards;
+          }
+
+          &::after {
+            background: #2c3e50;
+            animation: scale-in 0.5s cubic-bezier(0.75, 0, 0, 1) forwards;
+          }
+
+          svg {
+            z-index: 99;
+            transform: scale(0);
+            animation: scale-in 0.5s cubic-bezier(0.75, 0, 0, 1) forwards;
+          }
+        }
+      }
+    }
+  }
+
+  @keyframes bump-in {
+    50% {
+      transform: scale(1.05);
+    }
+
+    to {
+      opacity: 1;
+      transform: scale(1);
+    }
+  }
+
+  @keyframes slide-in-horizontal {
+    50% {
+      transform: translateX(0);
+    }
+
+    to {
+      transform: translateX(100%);
+    }
+  }
+
+  @keyframes slide-in-horizontal-reverse {
+    50% {
+      transform: translateX(0);
+    }
+
+    to {
+      transform: translateX(-100%);
+    }
+  }
+
+  @keyframes slide-in-vertical {
+    50% {
+      transform: translateY(0);
+    }
+
+    to {
+      transform: translateY(-100%);
+    }
+  }
+
+  @keyframes slide-in-vertical-reverse {
+    50% {
+      transform: translateY(0);
+    }
+
+    to {
+      transform: translateY(100%);
+    }
+  }
+
+  @keyframes slide-in-out {
+    50% {
+      transform: scaleX(1);
+      transform-origin: left;
+    }
+
+    50.1% {
+      transform-origin: right;
+    }
+
+    100% {
+      transform: scaleX(0);
+      transform-origin: right;
+    }
+  }
+
+  @keyframes fill-text-white {
+    to {
+      color: white;
+    }
+  }
+
+  @keyframes scale-in {
+    to {
+      transform: scale(1);
+    }
+  }
+</style>
diff --git a/src/components/VabSnow/index.vue b/src/components/VabSnow/index.vue
new file mode 100644
index 0000000..5131d52
--- /dev/null
+++ b/src/components/VabSnow/index.vue
@@ -0,0 +1,82 @@
+<template>
+  <div class="content" :style="styleObj">
+    <div v-for="(item, index) in 200" :key="index" class="snow"></div>
+  </div>
+</template>
+
+<script>
+  export default {
+    name: 'VabSnow',
+    props: {
+      styleObj: {
+        type: Object,
+        default: () => {
+          return {}
+        },
+      },
+    },
+    data() {
+      return {}
+    },
+    created() {},
+    mounted() {},
+    methods: {},
+  }
+</script>
+
+<style lang="scss" scoped>
+  .content {
+    position: relative;
+    width: 100%;
+    height: 100%;
+    overflow: hidden;
+    background: radial-gradient(ellipse at bottom, #1b2735 0%, #090a0f 100%);
+    filter: drop-shadow(0 0 10px white);
+  }
+
+  @function random_range($min, $max) {
+    $rand: random();
+    $random_range: $min + floor($rand * (($max - $min) + 1));
+
+    @return $random_range;
+  }
+
+  .snow {
+    $total: 200;
+
+    position: absolute;
+    width: 10px;
+    height: 10px;
+    background: white;
+    border-radius: 50%;
+
+    @for $i from 1 through $total {
+      $random-x: random(1000000) * 0.0001vw;
+      $random-offset: random_range(-100000, 100000) * 0.0001vw;
+      $random-x-end: $random-x + $random-offset;
+      $random-x-end-yoyo: $random-x + ($random-offset / 2);
+      $random-yoyo-time: random_range(30000, 80000) / 100000;
+      $random-yoyo-y: $random-yoyo-time * 100vh;
+      $random-scale: random(10000) * 0.0001;
+      $fall-duration: random_range(10, 30) * 1s;
+      $fall-delay: random(30) * -1s;
+
+      &:nth-child(#{$i}) {
+        opacity: random(10000) * 0.0001;
+        transform: translate($random-x, -10px) scale($random-scale);
+        animation: fall-#{$i} $fall-duration $fall-delay linear infinite;
+      }
+
+      @keyframes fall-#{$i} {
+        #{percentage($random-yoyo-time)} {
+          transform: translate($random-x-end, $random-yoyo-y)
+            scale($random-scale);
+        }
+
+        to {
+          transform: translate($random-x-end-yoyo, 100vh) scale($random-scale);
+        }
+      }
+    }
+  }
+</style>
diff --git a/src/components/VabUpload/index.vue b/src/components/VabUpload/index.vue
new file mode 100644
index 0000000..31d83a5
--- /dev/null
+++ b/src/components/VabUpload/index.vue
@@ -0,0 +1,255 @@
+<template>
+  <el-dialog
+    :before-close="handleClose"
+    :close-on-click-modal="false"
+    :title="title"
+    :visible.sync="dialogFormVisible"
+    width="909px"
+  >
+    <div class="upload">
+      <el-alert
+        :closable="false"
+        :title="`鏀寔jpg銆乯peg銆乸ng鏍煎紡锛屽崟娆″彲鏈�澶氶�夋嫨${limit}寮犲浘鐗囷紝姣忓紶涓嶅彲澶т簬${size}M锛屽鏋滃ぇ浜�${size}M浼氳嚜鍔ㄤ负鎮ㄨ繃婊"
+        type="info"
+      ></el-alert>
+      <br />
+      <el-upload
+        ref="upload"
+        :action="action"
+        :auto-upload="false"
+        :close-on-click-modal="false"
+        :data="data"
+        :file-list="fileList"
+        :headers="headers"
+        :limit="limit"
+        :multiple="true"
+        :name="name"
+        :on-change="handleChange"
+        :on-error="handleError"
+        :on-exceed="handleExceed"
+        :on-preview="handlePreview"
+        :on-progress="handleProgress"
+        :on-remove="handleRemove"
+        :on-success="handleSuccess"
+        accept="image/png, image/jpeg"
+        class="upload-content"
+        list-type="picture-card"
+      >
+        <i slot="trigger" class="el-icon-plus"></i>
+        <el-dialog
+          :visible.sync="dialogVisible"
+          append-to-body
+          title="鏌ョ湅澶у浘"
+        >
+          <div>
+            <img :src="dialogImageUrl" alt="" width="100%" />
+          </div>
+        </el-dialog>
+      </el-upload>
+    </div>
+    <div
+      slot="footer"
+      class="dialog-footer"
+      style="position: relative; padding-right: 15px; text-align: right"
+    >
+      <div
+        v-if="show"
+        style="position: absolute; top: 10px; left: 15px; color: #999"
+      >
+        姝e湪涓婁紶涓�... 褰撳墠涓婁紶鎴愬姛鏁�:{{ imgSuccessNum }}寮� 褰撳墠涓婁紶澶辫触鏁�:{{
+          imgErrorNum
+        }}寮�
+      </div>
+      <el-button type="primary" @click="handleClose">鍏抽棴</el-button>
+      <el-button
+        :loading="loading"
+        size="small"
+        style="margin-left: 10px"
+        type="success"
+        @click="submitUpload"
+      >
+        寮�濮嬩笂浼�
+      </el-button>
+    </div>
+  </el-dialog>
+</template>
+
+<script>
+  import { baseURL, tokenName } from '@/config'
+
+  export default {
+    name: 'VabUpload',
+    props: {
+      url: {
+        type: String,
+        default: '/upload',
+        required: true,
+      },
+      name: {
+        type: String,
+        default: 'file',
+        required: true,
+      },
+      limit: {
+        type: Number,
+        default: 50,
+        required: true,
+      },
+      size: {
+        type: Number,
+        default: 1,
+        required: true,
+      },
+    },
+    data() {
+      return {
+        show: false,
+        loading: false,
+        dialogVisible: false,
+        dialogImageUrl: '',
+        action: 'https://vab-unicloud-3a9da9.service.tcloudbase.com/upload',
+        headers: {},
+        fileList: [],
+        picture: 'picture',
+        imgNum: 0,
+        imgSuccessNum: 0,
+        imgErrorNum: 0,
+        typeList: null,
+        title: '涓婁紶',
+        dialogFormVisible: false,
+        data: {},
+      }
+    },
+    computed: {
+      percentage() {
+        if (this.allImgNum == 0) return 0
+        return this.$baseLodash.round(this.imgNum / this.allImgNum, 2) * 100
+      },
+    },
+    methods: {
+      submitUpload() {
+        this.$refs.upload.submit()
+      },
+      handleProgress(event, file, fileList) {
+        this.loading = true
+        this.show = true
+      },
+      handleChange(file, fileList) {
+        if (file.size > 1048576 * this.size) {
+          fileList.map((item, index) => {
+            if (item === file) {
+              fileList.splice(index, 1)
+            }
+          })
+          this.fileList = fileList
+        } else {
+          this.allImgNum = fileList.length
+        }
+      },
+      handleSuccess(response, file, fileList) {
+        this.imgNum = this.imgNum + 1
+        this.imgSuccessNum = this.imgSuccessNum + 1
+        if (fileList.length === this.imgNum) {
+          setTimeout(() => {
+            this.$baseMessage(
+              `涓婁紶瀹屾垚! 鍏变笂浼�${fileList.length}寮犲浘鐗嘸,
+              'success'
+            )
+          }, 1000)
+        }
+
+        setTimeout(() => {
+          this.loading = false
+          this.show = false
+        }, 1000)
+      },
+      handleError(err, file, fileList) {
+        this.imgNum = this.imgNum + 1
+        this.imgErrorNum = this.imgErrorNum + 1
+        this.$baseMessage(
+          `鏂囦欢[${file.raw.name}]涓婁紶澶辫触,鏂囦欢澶у皬涓�${this.$baseLodash.round(
+            file.raw.size / 1024,
+            0
+          )}KB`,
+          'error'
+        )
+        setTimeout(() => {
+          this.loading = false
+          this.show = false
+        }, 1000)
+      },
+      handleRemove(file, fileList) {
+        this.imgNum = this.imgNum - 1
+        this.allNum = this.allNum - 1
+      },
+      handlePreview(file) {
+        this.dialogImageUrl = file.url
+        this.dialogVisible = true
+      },
+      handleExceed(files, fileList) {
+        this.$baseMessage(
+          `褰撳墠闄愬埗閫夋嫨 ${this.limit} 涓枃浠讹紝鏈閫夋嫨浜�
+             ${files.length}
+             涓枃浠禶,
+          'error'
+        )
+      },
+      handleShow(data) {
+        this.title = '涓婁紶'
+        this.data = data
+        this.dialogFormVisible = true
+      },
+      handleClose() {
+        this.fileList = []
+        this.picture = 'picture'
+        this.allImgNum = 0
+        this.imgNum = 0
+        this.imgSuccessNum = 0
+        this.imgErrorNum = 0
+        /* if ("development" === process.env.NODE_ENV) {
+          this.api = process.env.VUE_APP_BASE_API;
+        } else {
+          this.api = `${window.location.protocol}//${window.location.host}`;
+        }
+
+        this.action = this.api + this.url; */
+        this.dialogFormVisible = false
+      },
+    },
+  }
+</script>
+
+<style lang="scss" scoped>
+  .upload {
+    height: 500px;
+
+    .upload-content {
+      .el-upload__tip {
+        display: block;
+        height: 30px;
+        line-height: 30px;
+      }
+
+      ::v-deep {
+        .el-upload--picture-card {
+          width: 128px;
+          height: 128px;
+          margin: 3px 8px 8px 8px;
+          border: 2px dashed #c0ccda;
+        }
+
+        .el-upload-list--picture {
+          margin-bottom: 20px;
+        }
+
+        .el-upload-list--picture-card {
+          .el-upload-list__item {
+            width: 128px;
+            height: 128px;
+            margin: 3px 8px 8px 8px;
+          }
+        }
+      }
+    }
+  }
+</style>
diff --git a/src/config/index.js b/src/config/index.js
new file mode 100644
index 0000000..86e58d6
--- /dev/null
+++ b/src/config/index.js
@@ -0,0 +1,7 @@
+/**
+ * @description 3涓瓙閰嶇疆锛岄�氱敤閰嶇疆|涓婚閰嶇疆|缃戠粶閰嶇疆瀵煎嚭
+ */
+const setting = require('./setting.config')
+const theme = require('./theme.config')
+const network = require('./net.config')
+module.exports = Object.assign({}, setting, theme, network)
diff --git a/src/config/net.config.js b/src/config/net.config.js
new file mode 100644
index 0000000..6f2492d
--- /dev/null
+++ b/src/config/net.config.js
@@ -0,0 +1,23 @@
+/**
+ * @description 瀵煎嚭榛樿缃戣矾閰嶇疆
+ **/
+const network = {
+  // 榛樿鐨勬帴鍙e湴鍧� 濡傛灉鏄紑鍙戠幆澧冨拰鐢熶骇鐜璧皏ab-mock-server锛屽綋鐒朵綘涔熷彲浠ラ�夋嫨鑷繁閰嶇疆鎴愰渶瑕佺殑鎺ュ彛鍦板潃
+  baseURL:
+    process.env.NODE_ENV === 'development'
+      ? 'http://192.168.20.10:9696/api/v1/'
+      : 'http://192.168.20.10:9696/api/v1/',
+  //閰嶅悗绔暟鎹殑鎺ユ敹鏂瑰紡application/json;charset=UTF-8鎴栬�卆pplication/x-www-form-urlencoded;charset=UTF-8
+  contentType: 'application/json;charset=UTF-8',
+  //娑堟伅妗嗘秷澶辨椂闂�
+  messageDuration: 3000,
+  //鏈�闀胯姹傛椂闂�
+  requestTimeout: 5000,
+  //鎿嶄綔姝e父code锛屾敮鎸丼tring銆丄rray銆乮nt澶氱绫诲瀷
+  successCode: [200, 0],
+  //鐧诲綍澶辨晥code
+  invalidCode: 402,
+  //鏃犳潈闄恈ode
+  noPermissionCode: 401,
+}
+module.exports = network
diff --git a/src/config/permission.js b/src/config/permission.js
new file mode 100644
index 0000000..ce86eb5
--- /dev/null
+++ b/src/config/permission.js
@@ -0,0 +1,84 @@
+/**
+ * @author chuzhixin 1204505056@qq.com 锛堜笉鎯充繚鐣檃uthor鍙垹闄わ級
+ * @description 璺敱瀹堝崼锛岀洰鍓嶄袱绉嶆ā寮忥細all妯″紡涓巌ntelligence妯″紡
+ */
+import router from '@/router'
+import store from '@/store'
+import VabProgress from 'nprogress'
+import 'nprogress/nprogress.css'
+import getPageTitle from '@/utils/pageTitle'
+import {
+  authentication,
+  loginInterception,
+  progressBar,
+  recordRoute,
+  routesWhiteList,
+} from '@/config'
+
+VabProgress.configure({
+  easing: 'ease',
+  speed: 500,
+  trickleSpeed: 200,
+  showSpinner: false,
+})
+router.beforeResolve(async (to, from, next) => {
+  if (progressBar) VabProgress.start()
+  let hasToken = store.getters['user/accessToken']
+
+  if (!loginInterception) hasToken = true
+
+  if (hasToken) {
+    if (to.path === '/login') {
+      next({ path: '/' })
+      if (progressBar) VabProgress.done()
+    } else {
+      const hasPermissions =
+        store.getters['user/permissions'] &&
+        store.getters['user/permissions'].length > 0
+      if (hasPermissions) {
+        next()
+      } else {
+        try {
+          let permissions
+          if (!loginInterception) {
+            //settings.js loginInterception涓篺alse鏃讹紝鍒涘缓铏氭嫙鏉冮檺
+            await store.dispatch('user/setPermissions', ['admin'])
+            permissions = ['admin']
+          } else {
+            permissions = await store.dispatch('user/getUserInfo')
+          }
+
+          let accessRoutes = []
+          if (authentication === 'intelligence') {
+            accessRoutes = await store.dispatch('routes/setRoutes', permissions)
+          } else if (authentication === 'all') {
+            accessRoutes = await store.dispatch('routes/setAllRoutes')
+          }
+          accessRoutes.forEach((item) => {
+            router.addRoute(item)
+          })
+          next({ ...to, replace: true })
+        } catch {
+          await store.dispatch('user/resetAccessToken')
+          if (progressBar) VabProgress.done()
+        }
+      }
+    }
+  } else {
+    if (routesWhiteList.indexOf(to.path) !== -1) {
+      next()
+    } else {
+      if (recordRoute) {
+        next(`/login?redirect=${to.path}`)
+      } else {
+        next('/login')
+      }
+
+      if (progressBar) VabProgress.done()
+    }
+  }
+  document.title = getPageTitle(to.meta.title)
+})
+router.afterEach(() => {
+  if (progressBar) VabProgress.done()
+})
diff --git a/src/config/setting.config.js b/src/config/setting.config.js
new file mode 100644
index 0000000..c448c86
--- /dev/null
+++ b/src/config/setting.config.js
@@ -0,0 +1,70 @@
+/**
+ * @description 瀵煎嚭榛樿閫氱敤閰嶇疆
+ */
+const setting = {
+  // 寮�鍙戜互鍙婇儴缃叉椂鐨刄RL
+  publicPath: '',
+  // 鐢熶骇鐜鏋勫缓鏂囦欢鐨勭洰褰曞悕
+  outputDir: 'dist',
+  // 鏀剧疆鐢熸垚鐨勯潤鎬佽祫婧� (js銆乧ss銆乮mg銆乫onts) 鐨� (鐩稿浜� outputDir 鐨�) 鐩綍銆�
+  assetsDir: 'static',
+  // 寮�鍙戠幆澧冩瘡娆′繚瀛樻椂鏄惁杈撳嚭涓篹slint缂栬瘧璀﹀憡
+  lintOnSave: false,
+  // 杩涜缂栬瘧鐨勪緷璧�
+  transpileDependencies: [],
+  //鏍囬 锛堝寘鎷垵娆″姞杞介洩鑺卞睆鐨勬爣棰� 椤甸潰鐨勬爣棰� 娴忚鍣ㄧ殑鏍囬锛�
+  title: 'Basic鑷姩鏋勫缓绯荤粺',
+  //绠�鍐�
+  abbreviation: 'vab',
+  //寮�鍙戠幆澧冪鍙e彿
+  devPort: '81',
+  //鐗堟湰鍙�
+  version: process.env.VUE_APP_VERSION,
+  //杩欎竴椤归潪甯搁噸瑕侊紒璇峰姟蹇呬繚鐣橫IT鍗忚涓媝ackage.json鍙奵opyright浣滆�呬俊鎭� 鍗冲彲鍏嶈垂鍟嗙敤锛屼笉閬靛畧姝ら」绾﹀畾浣犲皢鏃犳硶浣跨敤璇ユ鏋讹紝濡傞渶鑷畾涔夌増鏉冧俊鎭鑱旂郴QQ1204505056
+  copyright: 'vab',
+  //鏄惁鏄剧ず椤甸潰搴曢儴鑷畾涔夌増鏉冧俊鎭�
+  footerCopyright: true,
+  //鏄惁鏄剧ず椤堕儴杩涘害鏉�
+  progressBar: true,
+  //缂撳瓨璺敱鐨勬渶澶ф暟閲�
+  keepAliveMaxNum: 99,
+  // 璺敱妯″紡锛屽彲閫夊�间负 history 鎴� hash
+  routerMode: 'hash',
+  //涓嶇粡杩噒oken鏍¢獙鐨勮矾鐢�
+  routesWhiteList: ['/login', '/register', '/404', '/401'],
+  //鍔犺浇鏃舵樉绀烘枃瀛�
+  loadingText: '姝e湪鍔犺浇涓�...',
+  //token鍚嶇О
+  tokenName: 'accessToken',
+  //token鍦╨ocalStorage銆乻essionStorage瀛樺偍鐨刱ey鐨勫悕绉�
+  tokenTableName: 'vue-admin-beautiful-2021',
+  //token瀛樺偍浣嶇疆localStorage sessionStorage
+  storage: 'localStorage',
+  //token澶辨晥鍥為��鍒扮櫥褰曢〉鏃舵槸鍚﹁褰曟湰娆$殑璺敱
+  recordRoute: true,
+  //鏄惁鏄剧ずlogo锛屼笉鏄剧ず鏃惰缃甪alse锛屾樉绀烘椂璇峰~鍐檙emixIcon鍥炬爣鍚嶇О锛屾殏鏃跺彧鏀寔璁剧疆remixIcon
+  logo: 'vuejs-fill',
+  //鏄惁鏄剧ず鍦ㄩ〉闈㈤珮浜敊璇�
+  errorLog: ['development', 'production'],
+  //鏄惁寮�鍚櫥褰曟嫤鎴�
+  loginInterception: true,
+  //鏄惁寮�鍚櫥褰昍SA鍔犲瘑
+  loginRSA: true,
+  //intelligence鍜宎ll涓ょ鏂瑰紡锛屽墠鑰呭悗绔潈闄愬彧鎺у埗permissions涓嶆帶鍒秜iew鏂囦欢鐨刬mport锛堝墠鍚庣閰嶅悎锛屽噺杞诲悗绔伐浣滈噺锛夛紝all鏂瑰紡瀹屽叏浜ょ粰鍚庣鍓嶇鍙礋璐e姞杞�
+  authentication: 'intelligence',
+  //vertical甯冨眬鏃舵槸鍚﹀彧淇濇寔涓�涓瓙鑿滃崟鐨勫睍寮�
+  uniqueOpened: true,
+  //vertical甯冨眬鏃堕粯璁ゅ睍寮�鐨勮彍鍗昿ath锛屼娇鐢ㄩ�楀彿闅斿紑寤鸿鍙睍寮�涓�涓�
+  defaultOopeneds: ['/vab'],
+  //闇�瑕佸姞loading灞傜殑璇锋眰锛岄槻姝㈤噸澶嶆彁浜�
+  debounce: ['doEdit'],
+  //闇�瑕佽嚜鍔ㄦ敞鍏ュ苟鍔犺浇鐨勬ā鍧�
+  providePlugin: { maptalks: 'maptalks', 'window.maptalks': 'maptalks' },
+  //npm run build鏃舵槸鍚﹁嚜鍔ㄧ敓鎴�7z鍘嬬缉鍖�
+  build7z: false,
+  //浠g爜鐢熸垚鏈虹敓鎴愬湪view涓嬬殑鏂囦欢澶瑰悕绉�
+  templateFolder: 'project',
+  //鏄惁鏄剧ず缁堢donation鎵撳嵃
+  donation: false,
+}
+module.exports = setting
diff --git a/src/config/settings.js b/src/config/settings.js
new file mode 100644
index 0000000..c070a66
--- /dev/null
+++ b/src/config/settings.js
@@ -0,0 +1,6 @@
+/**
+ * @description 3涓瓙閰嶇疆锛岄�氱敤閰嶇疆|涓婚閰嶇疆|缃戠粶閰嶇疆
+ */
+//榛樿閰嶇疆
+const { setting, theme, network } = require('./')
+module.exports = Object.assign({}, setting, theme, network)
diff --git a/src/config/theme.config.js b/src/config/theme.config.js
new file mode 100644
index 0000000..b6a69d2
--- /dev/null
+++ b/src/config/theme.config.js
@@ -0,0 +1,14 @@
+/**
+ * @description 瀵煎嚭榛樿涓婚閰嶇疆
+ */
+const theme = {
+  //鏄惁鍥藉畾澶撮儴 鍥哄畾fixed 涓嶅浐瀹歯oFixed
+  header: 'fixed',
+  //妯旱甯冨眬 horizontal vertical
+  layout: 'vertical',
+  //鏄惁寮�鍚富棰橀厤缃寜閽�
+  themeBar: true,
+  //鏄惁鏄剧ず澶氭爣绛鹃〉
+  tabsBar: true,
+}
+module.exports = theme
diff --git a/src/layouts/EmptyLayout.vue b/src/layouts/EmptyLayout.vue
new file mode 100644
index 0000000..98240ae
--- /dev/null
+++ b/src/layouts/EmptyLayout.vue
@@ -0,0 +1,3 @@
+<template>
+  <router-view />
+</template>
diff --git a/src/layouts/components/VabAppMain/index.vue b/src/layouts/components/VabAppMain/index.vue
new file mode 100644
index 0000000..cb5b6c7
--- /dev/null
+++ b/src/layouts/components/VabAppMain/index.vue
@@ -0,0 +1,92 @@
+<template>
+  <div v-if="routerView" class="app-main-container">
+    <transition mode="out-in" name="fade-transform">
+      <keep-alive :include="cachedRoutes" :max="keepAliveMaxNum">
+        <router-view :key="key" class="app-main-height" />
+      </keep-alive>
+    </transition>
+  </div>
+</template>
+
+<script>
+import { mapActions, mapGetters } from 'vuex'
+import { copyright, footerCopyright, keepAliveMaxNum, title } from '@/config'
+
+export default {
+  name: 'VabAppMain',
+  data() {
+    return {
+      show: false,
+      fullYear: new Date().getFullYear(),
+      copyright,
+      title,
+      keepAliveMaxNum,
+      routerView: true,
+      footerCopyright,
+    }
+  },
+  computed: {
+    ...mapGetters({
+      visitedRoutes: 'tabsBar/visitedRoutes',
+      device: 'settings/device',
+    }),
+    cachedRoutes() {
+      const cachedRoutesArr = []
+      this.visitedRoutes.forEach((item) => {
+        if (!item.meta.noKeepAlive) {
+          cachedRoutesArr.push(item.name)
+        }
+      })
+      return cachedRoutesArr
+    },
+    key() {
+      return this.$route.path
+    },
+  },
+  watch: {
+    $route: {
+      handler(route) {
+        if ('mobile' === this.device) this.foldSideBar()
+      },
+      immediate: true,
+    },
+  },
+  created() {
+    //閲嶈浇鎵�鏈夎矾鐢�
+    this.$baseEventBus.$on('reload-router-view', () => {
+      this.routerView = false
+      this.$nextTick(() => {
+        this.routerView = true
+      })
+    })
+  },
+  mounted() { },
+  methods: {
+    ...mapActions({
+      foldSideBar: 'settings/foldSideBar',
+    }),
+  },
+}
+</script>
+
+<style lang="scss" scoped>
+.app-main-container {
+  position: relative;
+  width: 100%;
+  overflow: hidden;
+  .vab-keel {
+    margin: $base-padding;
+  }
+  .app-main-height {
+    min-height: $base-app-main-height;
+  }
+
+  .footer-copyright {
+    min-height: 55px;
+    line-height: 55px;
+    color: rgba(0, 0, 0, 0.45);
+    text-align: center;
+    border-top: 1px dashed $base-border-color;
+  }
+}
+</style>
diff --git a/src/layouts/components/VabAvatar/index.vue b/src/layouts/components/VabAvatar/index.vue
new file mode 100644
index 0000000..6ee4913
--- /dev/null
+++ b/src/layouts/components/VabAvatar/index.vue
@@ -0,0 +1,83 @@
+<template>
+  <el-dropdown @command="handleCommand">
+    <span class="avatar-dropdown">
+      <!--<el-avatar class="user-avatar" :src="avatar"></el-avatar>-->
+      <img class="user-avatar" :src="avatar" alt />
+      <div class="user-name">
+        {{ username }}
+        <i class="el-icon-arrow-down el-icon--right"></i>
+      </div>
+    </span>
+
+    <el-dropdown-menu slot="dropdown">
+      <el-dropdown-item command="logout" divided>閫�鍑虹櫥褰�</el-dropdown-item>
+    </el-dropdown-menu>
+  </el-dropdown>
+</template>
+
+<script>
+import { mapGetters } from 'vuex'
+import { recordRoute } from '@/config'
+
+export default {
+  name: 'VabAvatar',
+  computed: {
+    ...mapGetters({
+      avatar: 'user/avatar',
+      username: 'user/username',
+    }),
+  },
+  methods: {
+    handleCommand(command) {
+      switch (command) {
+        case 'logout':
+          this.logout()
+          break
+      }
+    },
+    personalCenter() {
+      this.$router.push('/personalCenter/personalCenter')
+    },
+    logout() {
+      this.$baseConfirm(
+        '鎮ㄧ‘瀹氳閫�鍑�' + this.$baseTitle + '鍚�?',
+        null,
+        async () => {
+          await this.$store.dispatch('user/logout')
+          if (recordRoute) {
+            const fullPath = this.$route.fullPath
+            this.$router.push(`/login?redirect=${fullPath}`)
+          } else {
+            this.$router.push('/login')
+          }
+        }
+      )
+    },
+  },
+}
+</script>
+<style lang="scss" scoped>
+.avatar-dropdown {
+  display: flex;
+  align-content: center;
+  align-items: center;
+  justify-content: center;
+  justify-items: center;
+  height: 50px;
+  padding: 0;
+
+  .user-avatar {
+    width: 40px;
+    height: 40px;
+    cursor: pointer;
+    border-radius: 50%;
+  }
+
+  .user-name {
+    position: relative;
+    margin-left: 5px;
+    margin-left: 5px;
+    cursor: pointer;
+  }
+}
+</style>
diff --git a/src/layouts/components/VabBreadcrumb/index.vue b/src/layouts/components/VabBreadcrumb/index.vue
new file mode 100644
index 0000000..0259bcc
--- /dev/null
+++ b/src/layouts/components/VabBreadcrumb/index.vue
@@ -0,0 +1,63 @@
+<template>
+  <el-breadcrumb class="breadcrumb-container" separator=">">
+    <el-breadcrumb-item v-for="item in list" :key="item.path">
+      {{ item.meta.title }}
+    </el-breadcrumb-item>
+  </el-breadcrumb>
+</template>
+
+<script>
+  export default {
+    name: 'VabBreadcrumb',
+    data() {
+      return {
+        list: this.getBreadcrumb(),
+      }
+    },
+    watch: {
+      $route() {
+        this.list = this.getBreadcrumb()
+      },
+    },
+    methods: {
+      getBreadcrumb() {
+        return this.$route.matched.filter(
+          (item) => item.name && item.meta.title
+        )
+      },
+    },
+  }
+</script>
+
+<style lang="scss" scoped>
+  .breadcrumb-container {
+    height: $base-nav-bar-height;
+    font-size: $base-font-size-default;
+    line-height: $base-nav-bar-height;
+
+    ::v-deep {
+      .el-breadcrumb__item {
+        .el-breadcrumb__inner {
+          a {
+            display: flex;
+            float: left;
+            font-weight: normal;
+            color: #515a6e;
+
+            i {
+              margin-right: 3px;
+            }
+          }
+        }
+
+        &:last-child {
+          .el-breadcrumb__inner {
+            a {
+              color: #999;
+            }
+          }
+        }
+      }
+    }
+  }
+</style>
diff --git a/src/layouts/components/VabLogo/index.vue b/src/layouts/components/VabLogo/index.vue
new file mode 100644
index 0000000..c07c1b2
--- /dev/null
+++ b/src/layouts/components/VabLogo/index.vue
@@ -0,0 +1,92 @@
+<template>
+  <div :class="'logo-container-' + layout">
+    <router-link to="/">
+      <!-- 杩欓噷鏄痩ogo鍙樻洿鐨勪綅缃� -->
+      <vab-remix-icon v-if="logo" class="logo" :icon-class="logo" />
+      <span
+        class="title"
+        :class="{ 'hidden-xs-only': layout === 'horizontal' }"
+        :title="title"
+      >
+        {{ title }}
+      </span>
+    </router-link>
+  </div>
+</template>
+<script>
+  import { mapGetters } from 'vuex'
+
+  export default {
+    name: 'VabLogo',
+    data() {
+      return {
+        title: this.$baseTitle,
+      }
+    },
+    computed: {
+      ...mapGetters({
+        logo: 'settings/logo',
+        layout: 'settings/layout',
+      }),
+    },
+  }
+</script>
+<style lang="scss" scoped>
+  @mixin container {
+    position: relative;
+    height: $base-top-bar-height;
+    overflow: hidden;
+    line-height: $base-top-bar-height;
+    background: $base-menu-background;
+  }
+
+  @mixin logo {
+    display: inline-block;
+    width: 28px;
+    height: 28px;
+    margin-right: 3px;
+    color: $base-title-color;
+    vertical-align: middle;
+  }
+
+  @mixin title {
+    display: inline-block;
+    overflow: hidden;
+    font-size: 18px;
+    line-height: 55px;
+    color: $base-title-color;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+    vertical-align: middle;
+  }
+
+  .logo-container-horizontal {
+    @include container;
+
+    .logo {
+      @include logo;
+    }
+
+    .title {
+      @include title;
+    }
+  }
+
+  .logo-container-vertical {
+    @include container;
+
+    height: $base-logo-height;
+    line-height: $base-logo-height;
+    text-align: center;
+
+    .logo {
+      @include logo;
+    }
+
+    .title {
+      @include title;
+
+      max-width: calc(#{$base-left-menu-width} - 60px);
+    }
+  }
+</style>
diff --git a/src/layouts/components/VabNavBar/index.vue b/src/layouts/components/VabNavBar/index.vue
new file mode 100644
index 0000000..7b59b82
--- /dev/null
+++ b/src/layouts/components/VabNavBar/index.vue
@@ -0,0 +1,130 @@
+<template>
+  <div class="nav-bar-container">
+    <el-row :gutter="15">
+      <el-col :xs="4" :sm="12" :md="12" :lg="12" :xl="12">
+        <div class="left-panel">
+          <i
+            :class="collapse ? 'el-icon-s-unfold' : 'el-icon-s-fold'"
+            :title="collapse ? '灞曞紑' : '鏀惰捣'"
+            class="fold-unfold"
+            @click="handleCollapse"
+          ></i>
+          <vab-breadcrumb class="hidden-xs-only" />
+        </div>
+      </el-col>
+      <el-col :xs="20" :sm="12" :md="12" :lg="12" :xl="12">
+        <div class="right-panel">
+          <vab-error-log />
+          <vab-full-screen-bar @refresh="refreshRoute" />
+          <vab-icon title="閲嶈浇鎵�鏈夎矾鐢�" :pulse="pulse" :icon="['fas', 'redo']" @click="refreshRoute" />
+          <vab-avatar />
+          <!--  <vab-icon
+            title="閫�鍑虹郴缁�"
+            :icon="['fas', 'sign-out-alt']"
+            @click="logout"
+          />-->
+        </div>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script>
+import { mapActions, mapGetters } from 'vuex'
+
+export default {
+  name: 'VabNavBar',
+  data() {
+    return {
+      pulse: false,
+    }
+  },
+  computed: {
+    ...mapGetters({
+      collapse: 'settings/collapse',
+      visitedRoutes: 'tabsBar/visitedRoutes',
+      device: 'settings/device',
+      routes: 'routes/routes',
+    }),
+  },
+  methods: {
+    ...mapActions({
+      changeCollapse: 'settings/changeCollapse',
+    }),
+    handleCollapse() {
+      this.changeCollapse()
+    },
+    async refreshRoute() {
+      this.$baseEventBus.$emit('reload-router-view')
+      this.pulse = true
+      setTimeout(() => {
+        this.pulse = false
+      }, 1000)
+    },
+  },
+}
+</script>
+
+<style lang="scss" scoped>
+.nav-bar-container {
+  position: relative;
+  height: $base-nav-bar-height;
+  padding-right: $base-padding;
+  padding-left: $base-padding;
+  overflow: hidden;
+  user-select: none;
+  background: $base-color-white;
+  box-shadow: $base-box-shadow;
+
+  .left-panel {
+    display: flex;
+    align-items: center;
+    justify-items: center;
+    height: $base-nav-bar-height;
+
+    .fold-unfold {
+      color: $base-color-gray;
+      cursor: pointer;
+    }
+
+    ::v-deep {
+      .breadcrumb-container {
+        margin-left: 10px;
+      }
+    }
+  }
+
+  .right-panel {
+    display: flex;
+    align-content: center;
+    align-items: center;
+    justify-content: flex-end;
+    height: $base-nav-bar-height;
+
+    ::v-deep {
+      svg {
+        width: 1em;
+        height: 1em;
+        margin-right: 15px;
+        font-size: $base-font-size-small;
+        color: $base-color-gray;
+        cursor: pointer;
+        fill: $base-color-gray;
+      }
+
+      button {
+        svg {
+          margin-right: 0;
+          color: $base-color-white;
+          cursor: pointer;
+          fill: $base-color-white;
+        }
+      }
+
+      .el-badge {
+        margin-right: 15px;
+      }
+    }
+  }
+}
+</style>
diff --git a/src/layouts/components/VabThemeBar/index.vue b/src/layouts/components/VabThemeBar/index.vue
new file mode 100644
index 0000000..22c9df5
--- /dev/null
+++ b/src/layouts/components/VabThemeBar/index.vue
@@ -0,0 +1,255 @@
+<template>
+  <span v-if="themeBar">
+    <vab-icon title="涓婚閰嶇疆" :icon="['fas', 'palette']" @click="handleOpenThemeBar" />
+    <div class="theme-bar-setting">
+      <div @click="handleOpenThemeBar">
+        <vab-icon :icon="['fas', 'palette']" />
+        <p>涓婚閰嶇疆</p>
+      </div>
+      <div @click="handleGetCode">
+        <vab-icon :icon="['fas', 'laptop-code']"></vab-icon>
+        <p>鎷疯礉婧愮爜</p>
+      </div>
+    </div>
+
+    <el-drawer
+      title="涓婚閰嶇疆"
+      :visible.sync="drawerVisible"
+      direction="rtl"
+      append-to-body
+      size="470px"
+    >
+      <el-scrollbar style="height: 94vh; overflow: hidden">
+        <div class="el-drawer__body">
+          <el-form ref="form" :model="theme" label-position="top">
+            <el-form-item label="涓婚">
+              <el-radio-group v-model="theme.name">
+                <el-radio-button label="default">榛樿</el-radio-button>
+                <el-radio-button label="green">缁胯崼鑽夊満</el-radio-button>
+                <el-radio-button label="glory">鑽h��鍏歌棌</el-radio-button>
+                <!-- <el-radio-button label="orean">娴锋磱涔嬪績</el-radio-button>
+                <el-radio-button label="red">鏈堜笂閲嶇伀</el-radio-button>-->
+              </el-radio-group>
+            </el-form-item>
+            <el-form-item label="甯冨眬">
+              <el-radio-group v-model="theme.layout">
+                <el-radio-button label="vertical">绾靛悜甯冨眬</el-radio-button>
+                <el-radio-button label="horizontal">妯悜甯冨眬</el-radio-button>
+              </el-radio-group>
+            </el-form-item>
+            <el-form-item label="澶撮儴">
+              <el-radio-group v-model="theme.header">
+                <el-radio-button label="fixed">鍥哄畾澶撮儴</el-radio-button>
+                <el-radio-button label="noFixed">涓嶅浐瀹氬ご閮�</el-radio-button>
+              </el-radio-group>
+            </el-form-item>
+            <el-form-item label="澶氭爣绛�">
+              <el-radio-group v-model="theme.tabsBar">
+                <el-radio-button label="true">寮�鍚�</el-radio-button>
+                <el-radio-button label="false">涓嶅紑鍚�</el-radio-button>
+              </el-radio-group>
+            </el-form-item>
+            <el-form-item>
+              <el-button type="primary" @click="handleSaveTheme">淇濆瓨</el-button>
+            </el-form-item>
+          </el-form>
+        </div>
+      </el-scrollbar>
+    </el-drawer>
+  </span>
+</template>
+
+<script>
+import variables from '@/styles/variables.scss'
+import { mapActions, mapGetters } from 'vuex'
+import { layout as defaultLayout } from '@/config'
+export default {
+  name: 'VabThemeBar',
+  data() {
+    return {
+      drawerVisible: false,
+      theme: {
+        name: 'default',
+        layout: '',
+        header: 'fixed',
+        tabsBar: '',
+      },
+    }
+  },
+  computed: {
+    ...mapGetters({
+      layout: 'settings/layout',
+      header: 'settings/header',
+      tabsBar: 'settings/tabsBar',
+      themeBar: 'settings/themeBar',
+    }),
+  },
+  created() {
+    this.$baseEventBus.$on('theme', () => {
+      this.handleOpenThemeBar()
+    })
+    const theme = localStorage.getItem('vue-admin-beautiful-theme')
+    if (null !== theme) {
+      this.theme = JSON.parse(theme)
+      this.handleSetTheme()
+    } else {
+      this.theme.layout = this.layout
+      this.theme.header = this.header
+      this.theme.tabsBar = this.tabsBar
+    }
+  },
+  methods: {
+    ...mapActions({
+      changeLayout: 'settings/changeLayout',
+      changeHeader: 'settings/changeHeader',
+      changeTabsBar: 'settings/changeTabsBar',
+    }),
+    handleIsMobile() {
+      return document.body.getBoundingClientRect().width - 1 < 992
+    },
+    handleOpenThemeBar() {
+      this.drawerVisible = true
+    },
+    handleSetTheme() {
+      let { name, layout, header, tabsBar } = this.theme
+      localStorage.setItem(
+        'vue-admin-beautiful-theme',
+        `{
+            "name":"${name}",
+            "layout":"${layout}",
+            "header":"${header}",
+            "tabsBar":"${tabsBar}"
+          }`
+      )
+      if (!this.handleIsMobile()) this.changeLayout(layout)
+      this.changeHeader(header)
+      this.changeTabsBar(tabsBar)
+      document.getElementsByTagName(
+        'body'
+      )[0].className = `vue-admin-beautiful-theme-${name}`
+      this.drawerVisible = false
+    },
+    handleSaveTheme() {
+      this.handleSetTheme()
+    },
+    handleSetDfaultTheme() {
+      let { name } = this.theme
+      document
+        .getElementsByTagName('body')[0]
+        .classList.remove(`vue-admin-beautiful-theme-${name}`)
+      localStorage.removeItem('vue-admin-beautiful-theme')
+      this.$refs['form'].resetFields()
+      Object.assign(this.$data, this.$options.data())
+      this.changeHeader(defaultLayout)
+      this.theme.name = 'default'
+      this.theme.layout = this.layout
+      this.theme.header = this.header
+      this.theme.tabsBar = this.tabsBar
+      this.drawerVisible = false
+      location.reload()
+    },
+    handleGetCode() {
+      const url =
+        'https://github.com/chuzhixin/vue-admin-beautiful/tree/master/src/views'
+      let path = this.$route.path + '/index.vue'
+      if (path === '/vab/menu1/menu1-1/menu1-1-1/index.vue') {
+        path = '/vab/nested/menu1/menu1-1/menu1-1-1/index.vue'
+      }
+      if (path === '/vab/icon/awesomeIcon/index.vue') {
+        path = '/vab/icon/index.vue'
+      }
+      if (path === '/vab/icon/remixIcon/index.vue') {
+        path = '/vab/icon/remixIcon.vue'
+      }
+      if (path === '/vab/icon/colorfulIcon/index.vue') {
+        path = '/vab/icon/colorfulIcon.vue'
+      }
+      if (path === '/vab/table/comprehensiveTable/index.vue') {
+        path = '/vab/table/index.vue'
+      }
+      if (path === '/vab/table/inlineEditTable/index.vue') {
+        path = '/vab/table/inlineEditTable.vue'
+      }
+      window.open(url + path)
+    },
+  },
+}
+</script>
+
+<style lang="scss" scoped>
+@mixin right-bar {
+  position: fixed;
+  right: 0;
+  z-index: $base-z-index;
+  width: 60px;
+  min-height: 60px;
+  text-align: center;
+  cursor: pointer;
+  background: $base-color-blue;
+  border-radius: $base-border-radius;
+
+  > div {
+    padding-top: 10px;
+    border-bottom: 0 !important;
+
+    &:hover {
+      opacity: 0.9;
+    }
+
+    & + div {
+      border-top: 1px solid $base-color-white;
+    }
+
+    p {
+      padding: 0;
+      margin: 0;
+      font-size: $base-font-size-small;
+      line-height: 30px;
+      color: $base-color-white;
+    }
+  }
+}
+
+.theme-bar-setting {
+  @include right-bar;
+
+  top: calc((100vh - 110px) / 2);
+
+  ::v-deep {
+    svg:not(:root).svg-inline--fa {
+      display: block;
+      margin-right: auto;
+      margin-left: auto;
+      color: $base-color-white;
+    }
+
+    .svg-icon {
+      display: block;
+      margin-right: auto;
+      margin-left: auto;
+      font-size: 20px;
+      color: $base-color-white;
+      fill: $base-color-white;
+    }
+  }
+}
+
+.el-drawer__body {
+  padding: 20px;
+}
+</style>
+<style lang="scss">
+.el-drawer__wrapper {
+  outline: none !important;
+
+  * {
+    outline: none !important;
+  }
+}
+
+.vab-color-picker {
+  .el-color-dropdown__link-btn {
+    display: none;
+  }
+}
+</style>
diff --git a/src/layouts/export.js b/src/layouts/export.js
new file mode 100644
index 0000000..4d522ae
--- /dev/null
+++ b/src/layouts/export.js
@@ -0,0 +1,25 @@
+/**
+ * @author chuzhixin 1204505056@qq.com 锛堜笉鎯充繚鐣檃uthor鍙垹闄わ級
+ * @description 鍏叡甯冨眬鍙婃牱寮忚嚜鍔ㄥ紩鍏�
+ */
+
+import Vue from 'vue'
+
+const requireComponents = require.context('./components', true, /\.vue$/)
+requireComponents.keys().forEach((fileName) => {
+  const componentConfig = requireComponents(fileName)
+  const componentName = componentConfig.default.name
+  Vue.component(componentName, componentConfig.default || componentConfig)
+})
+
+const requireZxLayouts = require.context('zx-layouts', true, /\.vue$/)
+requireZxLayouts.keys().forEach((fileName) => {
+  const componentConfig = requireZxLayouts(fileName)
+  const componentName = componentConfig.default.name
+  Vue.component(componentName, componentConfig.default || componentConfig)
+})
+
+const requireThemes = require.context('@/styles/themes', true, /\.scss$/)
+requireThemes.keys().forEach((fileName) => {
+  require(`@/styles/themes/${fileName.slice(2)}`)
+})
diff --git a/src/layouts/index.vue b/src/layouts/index.vue
new file mode 100644
index 0000000..5d1cd55
--- /dev/null
+++ b/src/layouts/index.vue
@@ -0,0 +1,297 @@
+<template>
+  <div class="vue-admin-beautiful-wrapper" :class="classObj">
+    <div
+      v-if="'horizontal' === layout"
+      class="layout-container-horizontal"
+      :class="{
+        fixed: header === 'fixed',
+        'no-tabs-bar': tabsBar === 'false' || tabsBar === false,
+      }"
+    >
+      <div :class="header === 'fixed' ? 'fixed-header' : ''">
+        <vab-top-bar />
+        <div v-if="tabsBar === 'true' || tabsBar === true" :class="{ 'tag-view-show': tabsBar }">
+          <div class="vab-main">
+            <vab-tabs-bar />
+          </div>
+        </div>
+      </div>
+      <div class="vab-main main-padding"></div>
+    </div>
+    <div
+      v-else
+      class="layout-container-vertical"
+      :class="{
+        fixed: header === 'fixed',
+        'no-tabs-bar': tabsBar === 'false' || tabsBar === false,
+      }"
+    >
+      <div
+        v-if="device === 'mobile' && collapse === false"
+        class="mask"
+        @click="handleFoldSideBar"
+      />
+      <vab-side-bar />
+      <div class="vab-main" :class="collapse ? 'is-collapse-main' : ''">
+        <div :class="header === 'fixed' ? 'fixed-header' : ''">
+          <vab-nav-bar />
+          <vab-tabs-bar v-if="tabsBar === 'true' || tabsBar === true" />
+        </div>
+        <vab-app-main />
+      </div>
+    </div>
+    <el-backtop />
+  </div>
+</template>
+
+<script>
+import { mapActions, mapGetters } from 'vuex'
+import { tokenName } from '@/config'
+export default {
+  name: 'Layout',
+  data() {
+    return { oldLayout: '' }
+  },
+  computed: {
+    ...mapGetters({
+      layout: 'settings/layout',
+      tabsBar: 'settings/tabsBar',
+      collapse: 'settings/collapse',
+      header: 'settings/header',
+      device: 'settings/device',
+    }),
+    classObj() {
+      return {
+        mobile: this.device === 'mobile',
+      }
+    },
+  },
+  beforeMount() {
+    window.addEventListener('resize', this.handleResize)
+  },
+  beforeDestroy() {
+    window.removeEventListener('resize', this.handleResize)
+  },
+  mounted() {
+    this.oldLayout = this.layout
+    const userAgent = navigator.userAgent
+    if (userAgent.includes('Juejin')) {
+      this.$baseAlert(
+        'vue-admin-beautiful涓嶆敮鎸佸湪鎺橀噾鍐呯疆娴忚鍣ㄦ紨绀猴紝璇锋墜鍔ㄥ鍒朵互涓嬪湴鍧�鍒版祻瑙堝櫒涓煡鐪媓ttp://mpfhrd48.sanxing.uz7.cn/vue-admin-beautiful'
+      )
+    }
+    const isMobile = this.handleIsMobile()
+    if (isMobile) {
+      if (isMobile) {
+        //妯悜甯冨眬鏃跺鏋滄槸鎵嬫満绔闂偅涔堟敼鎴愮旱鍚戠増
+        this.$store.dispatch('settings/changeLayout', 'vertical')
+      } else {
+        this.$store.dispatch('settings/changeLayout', this.oldLayout)
+      }
+      this.$store.dispatch('settings/toggleDevice', 'mobile')
+      setTimeout(() => {
+        this.$store.dispatch('settings/foldSideBar')
+      }, 2000)
+    } else {
+      this.$store.dispatch('settings/openSideBar')
+    }
+    this.$nextTick(() => {
+      window.addEventListener(
+        'storage',
+        (e) => {
+          if (e.key === tokenName || e.key === null) window.location.reload()
+          if (e.key === tokenName && e.value === null)
+            window.location.reload()
+        },
+        false
+      )
+    })
+  },
+  methods: {
+    ...mapActions({
+      handleFoldSideBar: 'settings/foldSideBar',
+    }),
+    handleIsMobile() {
+      return document.body.getBoundingClientRect().width - 1 < 992
+    },
+    handleResize() {
+      if (!document.hidden) {
+        const isMobile = this.handleIsMobile()
+        if (isMobile) {
+          //妯悜甯冨眬鏃跺鏋滄槸鎵嬫満绔闂偅涔堟敼鎴愮旱鍚戠増
+          this.$store.dispatch('settings/changeLayout', 'vertical')
+        } else {
+          this.$store.dispatch('settings/changeLayout', this.oldLayout)
+        }
+
+        this.$store.dispatch(
+          'settings/toggleDevice',
+          isMobile ? 'mobile' : 'desktop'
+        )
+      }
+    },
+  },
+}
+</script>
+
+<style lang="scss" scoped>
+@mixin fix-header {
+  position: fixed;
+  top: 0;
+  right: 0;
+  left: 0;
+  z-index: $base-z-index - 2;
+  width: 100%;
+  overflow: hidden;
+}
+
+.vue-admin-beautiful-wrapper {
+  position: relative;
+  width: 100%;
+  height: 100%;
+
+  .layout-container-horizontal {
+    position: relative;
+
+    &.fixed {
+      padding-top: calc(#{$base-top-bar-height} + #{$base-tabs-bar-height});
+    }
+
+    &.fixed.no-tabs-bar {
+      padding-top: $base-top-bar-height;
+    }
+
+    ::v-deep {
+      .vab-main {
+        width: 88%;
+        margin: auto;
+      }
+
+      .fixed-header {
+        @include fix-header;
+      }
+
+      .tag-view-show {
+        background: $base-color-white;
+        box-shadow: $base-box-shadow;
+      }
+
+      .nav-bar-container {
+        .fold-unfold {
+          display: none;
+        }
+      }
+
+      .main-padding {
+        .app-main-container {
+          margin-top: $base-padding;
+          margin-bottom: $base-padding;
+          background: $base-color-white;
+        }
+      }
+    }
+  }
+
+  .layout-container-vertical {
+    position: relative;
+
+    .mask {
+      position: fixed;
+      top: 0;
+      right: 0;
+      bottom: 0;
+      left: 0;
+      z-index: $base-z-index - 1;
+      width: 100%;
+      height: 100vh;
+      overflow: hidden;
+      background: #000;
+      opacity: 0.5;
+    }
+
+    &.fixed {
+      padding-top: calc(#{$base-nav-bar-height} + #{$base-tabs-bar-height});
+    }
+
+    &.fixed.no-tabs-bar {
+      padding-top: $base-nav-bar-height;
+    }
+
+    .vab-main {
+      position: relative;
+      min-height: 100%;
+      margin-left: $base-left-menu-width;
+      background: #f6f8f9;
+      transition: $base-transition;
+
+      ::v-deep {
+        .fixed-header {
+          @include fix-header;
+
+          left: $base-left-menu-width;
+          width: $base-right-content-width;
+          box-shadow: $base-box-shadow;
+          transition: $base-transition;
+        }
+
+        .nav-bar-container {
+          position: relative;
+          box-sizing: border-box;
+        }
+
+        .tabs-bar-container {
+          box-sizing: border-box;
+        }
+
+        .app-main-container {
+          width: calc(100% - #{$base-padding} - #{$base-padding});
+          margin: $base-padding auto;
+          background: $base-color-white;
+          border-radius: $base-border-radius;
+        }
+      }
+
+      &.is-collapse-main {
+        margin-left: $base-left-menu-width-min;
+
+        ::v-deep {
+          .fixed-header {
+            left: $base-left-menu-width-min;
+            width: calc(100% - 65px);
+          }
+        }
+      }
+    }
+  }
+
+  /* 鎵嬫満绔紑濮� */
+  &.mobile {
+    ::v-deep {
+      .el-pager,
+      .el-pagination__jump {
+        display: none;
+      }
+
+      .layout-container-vertical {
+        .el-scrollbar.side-bar-container.is-collapse {
+          width: 0;
+        }
+
+        .vab-main {
+          width: 100%;
+          margin-left: 0;
+        }
+      }
+
+      .vab-main {
+        .fixed-header {
+          left: 0 !important;
+          width: 100% !important;
+        }
+      }
+    }
+  }
+
+  /* 鎵嬫満绔粨鏉� */
+}
+</style>
diff --git a/src/main.js b/src/main.js
new file mode 100644
index 0000000..6b496d7
--- /dev/null
+++ b/src/main.js
@@ -0,0 +1,24 @@
+import Vue from 'vue'
+import App from './App'
+import store from './store'
+import router from './router'
+import './plugins'
+import '@/layouts/export'
+/**
+ * @author chuzhixin 1204505056@qq.com 锛堜笉鎯充繚鐣檃uthor鍙垹闄わ級
+ * @description 鐢熶骇鐜榛樿閮戒娇鐢╩ock锛屽鏋滄寮忕敤浜庣敓浜х幆澧冩椂锛岃寰楀幓鎺�
+ */
+
+if (process.env.NODE_ENV === 'production') {
+  const { mockXHR } = require('@/utils/static')
+  mockXHR()
+}
+
+Vue.config.productionTip = false
+
+new Vue({
+  el: '#vue-admin-beautiful',
+  router,
+  store,
+  render: (h) => h(App),
+})
diff --git a/src/plugins/echarts.js b/src/plugins/echarts.js
new file mode 100644
index 0000000..f600f83
--- /dev/null
+++ b/src/plugins/echarts.js
@@ -0,0 +1,4 @@
+import 'echarts'
+import VabChart from 'vue-echarts'
+
+export default VabChart
diff --git a/src/plugins/element.js b/src/plugins/element.js
new file mode 100644
index 0000000..3b91ff0
--- /dev/null
+++ b/src/plugins/element.js
@@ -0,0 +1,9 @@
+import Vue from 'vue'
+import ElementUI from 'element-ui'
+import 'element-ui/lib/theme-chalk/display.css'
+
+import '@/styles/element-variables.scss'
+
+Vue.use(ElementUI, {
+  size: 'small',
+})
diff --git a/src/plugins/index.js b/src/plugins/index.js
new file mode 100644
index 0000000..2d4322a
--- /dev/null
+++ b/src/plugins/index.js
@@ -0,0 +1,17 @@
+/* 鍏叡寮曞叆,鍕块殢鎰忎慨鏀�,淇敼鏃堕渶缁忚繃纭 */
+import Vue from 'vue'
+import './element'
+import './support'
+import '@/styles/vab.scss'
+import '@/remixIcon'
+import '@/colorfulIcon'
+import '@/config/permission'
+import '@/utils/errorLog'
+import './vabIcon'
+import VabPermissions from 'zx-layouts/Permissions'
+import Vab from '@/utils/vab'
+import VabCount from 'zx-count'
+
+Vue.use(Vab)
+Vue.use(VabPermissions)
+Vue.use(VabCount)
diff --git a/src/plugins/support.js b/src/plugins/support.js
new file mode 100644
index 0000000..3910649
--- /dev/null
+++ b/src/plugins/support.js
@@ -0,0 +1,20 @@
+import { MessageBox } from 'element-ui'
+import { donation } from '@/config'
+import { dependencies, repository } from '../../package.json'
+
+if (!!window.ActiveXObject || 'ActiveXObject' in window) {
+  MessageBox({
+    title: '娓╅Θ鎻愮ず',
+    message:
+      '鑷�2015骞�3鏈堣捣锛屽井杞凡瀹e竷寮冪敤IE锛屼笖涓嶅啀瀵笽E鎻愪緵浠讳綍鏇存柊缁存姢锛岃<a target="_blank" style="color:blue" href="https://www.microsoft.com/zh-cn/edge/">鐐瑰嚮姝ゅ</a>璁块棶寰蒋瀹樼綉鏇存柊娴忚鍣紝濡傛灉鎮ㄤ娇鐢ㄧ殑鏄弻鏍告祻瑙堝櫒,璇锋偍鍒囨崲娴忚鍣ㄥ唴鏍镐负鏋侀�熸ā寮�',
+    type: 'warning',
+    showClose: false,
+    showConfirmButton: false,
+    closeOnClickModal: false,
+    closeOnPressEscape: false,
+    closeOnHashChange: false,
+    dangerouslyUseHTMLString: true,
+  })
+}
+if (!dependencies['vab-icon'] || !dependencies['zx-layouts'])
+  document.body.innerHTML = ''
diff --git a/src/plugins/vabIcon.js b/src/plugins/vabIcon.js
new file mode 100644
index 0000000..634bca7
--- /dev/null
+++ b/src/plugins/vabIcon.js
@@ -0,0 +1,4 @@
+import Vue from 'vue'
+import VabIcon from 'vab-icon'
+
+Vue.component('VabIcon', VabIcon)
diff --git a/src/plugins/vabMagnifier.js b/src/plugins/vabMagnifier.js
new file mode 100644
index 0000000..748fda2
--- /dev/null
+++ b/src/plugins/vabMagnifier.js
@@ -0,0 +1,3 @@
+import VabMagnifier from 'zx-magnifie'
+
+export default VabMagnifier
diff --git a/src/plugins/vabMarkdownEditor.js b/src/plugins/vabMarkdownEditor.js
new file mode 100644
index 0000000..03686b6
--- /dev/null
+++ b/src/plugins/vabMarkdownEditor.js
@@ -0,0 +1,5 @@
+import ZxMarkdownEditor from 'zx-markdown-editor'
+import 'zx-markdown-editor/dist/zx-markdown-editor.css'
+
+const VabMarkdownEditor = ZxMarkdownEditor
+export default VabMarkdownEditor
diff --git a/src/plugins/vabPlayer.js b/src/plugins/vabPlayer.js
new file mode 100644
index 0000000..69bf8a7
--- /dev/null
+++ b/src/plugins/vabPlayer.js
@@ -0,0 +1,3 @@
+import { VabPlayerMp4, VabPlayerHls, VabPlayerFlv } from 'zx-player'
+
+export { VabPlayerMp4, VabPlayerHls, VabPlayerFlv }
diff --git a/src/plugins/vabQuill.js b/src/plugins/vabQuill.js
new file mode 100644
index 0000000..eae4bbf
--- /dev/null
+++ b/src/plugins/vabQuill.js
@@ -0,0 +1,4 @@
+import 'zx-quill/dist/zx-quill.css'
+import VabQuill from 'zx-quill'
+
+export default VabQuill
diff --git a/src/plugins/vabVerify.js b/src/plugins/vabVerify.js
new file mode 100644
index 0000000..e98ae43
--- /dev/null
+++ b/src/plugins/vabVerify.js
@@ -0,0 +1,4 @@
+import VabVerify from 'zx-verify'
+import 'zx-verify/dist/zx-verify.css'
+
+export default VabVerify
diff --git a/src/remixIcon/index.js b/src/remixIcon/index.js
new file mode 100644
index 0000000..2dd876e
--- /dev/null
+++ b/src/remixIcon/index.js
@@ -0,0 +1,13 @@
+const req = require.context('./svg', false, /\.svg$/),
+  requireAll = (requireContext) => {
+    /*let a = requireContext.keys().map(requireContext);
+    let arr = [];
+    for (let i = 0; i < a.length; i++) {
+      console.log();
+      let icon = a[i].default.id;
+      arr.push(icon);
+    }
+    console.log(JSON.stringify(arr));*/
+    return requireContext.keys().map(requireContext)
+  }
+requireAll(req)
diff --git a/src/remixIcon/svg/qq-fill.svg b/src/remixIcon/svg/qq-fill.svg
new file mode 100644
index 0000000..1f446b1
--- /dev/null
+++ b/src/remixIcon/svg/qq-fill.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M19.913 14.529a31.977 31.977 0 00-.675-1.886l-.91-2.246c0-.026.012-.468.012-.696C18.34 5.86 16.507 2 12 2S5.66 5.86 5.66 9.7c0 .229.011.671.012.697l-.91 2.246a32.777 32.777 0 00-.675 1.886c-.86 2.737-.581 3.87-.369 3.895.455.054 1.771-2.06 1.771-2.06 0 1.224.637 2.822 2.016 3.976-.515.157-1.147.399-1.554.695-.365.267-.319.54-.253.65.289.481 4.955.307 6.303.157 1.347.15 6.014.324 6.302-.158.066-.11.112-.382-.253-.649-.407-.296-1.039-.538-1.555-.696 1.379-1.153 2.016-2.751 2.016-3.976 0 0 1.316 2.115 1.771 2.06.212-.025.49-1.157-.37-3.894"/></svg>
\ No newline at end of file
diff --git a/src/remixIcon/svg/vuejs-fill.svg b/src/remixIcon/svg/vuejs-fill.svg
new file mode 100644
index 0000000..1b1a137
--- /dev/null
+++ b/src/remixIcon/svg/vuejs-fill.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M1 3h4l7 12 7-12h4L12 22 1 3zm8.667 0L12 7l2.333-4h4.035L12 14 5.632 3h4.035z"/></svg>
diff --git a/src/router/index.js b/src/router/index.js
new file mode 100644
index 0000000..bac4ddc
--- /dev/null
+++ b/src/router/index.js
@@ -0,0 +1,86 @@
+import Vue from 'vue'
+import VueRouter from 'vue-router'
+import Layout from '@/layouts'
+import { publicPath, routerMode } from '@/config'
+
+Vue.use(VueRouter)
+export const constantRoutes = [
+  {
+    path: '/login',
+    component: () => import('@/views/login/index'),
+    hidden: true,
+  },
+  {
+    path: '/401',
+    name: '401',
+    component: () => import('@/views/401'),
+    hidden: true,
+  },
+  {
+    path: '/404',
+    name: '404',
+    component: () => import('@/views/404'),
+    hidden: true,
+  },
+]
+
+export const asyncRoutes = [
+  {
+    path: '/',
+    component: Layout,
+    redirect: '/index',
+    children: [
+      {
+        path: 'index',
+        name: 'Index',
+        component: () => import('@/views/index/index'),
+        meta: {
+          title: '棣栭〉',
+          icon: 'home',
+          affix: true,
+        },
+      },
+    ],
+  },
+
+  {
+    path: '/project',
+    component: Layout,
+    redirect: 'project',
+    // name: 'Vab',
+    // alwaysShow: true,
+    children: [
+      {
+        path: 'index',
+        component: () => import('@/views/project/index'),
+        name: 'Project',
+        meta: {
+          title: '椤圭洰',
+          icon: 'box-open',
+          permissions: ['admin'],
+        },
+      },
+    ],
+  },
+
+  {
+    path: '*',
+    redirect: '/404',
+    hidden: true,
+  },
+]
+
+const router = new VueRouter({
+  base: publicPath,
+  mode: routerMode,
+  scrollBehavior: () => ({
+    y: 0,
+  }),
+  routes: constantRoutes,
+})
+
+export function resetRouter() {
+  location.reload()
+}
+
+export default router
diff --git a/src/store/index.js b/src/store/index.js
new file mode 100644
index 0000000..0b20b4e
--- /dev/null
+++ b/src/store/index.js
@@ -0,0 +1,22 @@
+/**
+ * @author chuzhixin 1204505056@qq.com 锛堜笉鎯充繚鐣檃uthor鍙垹闄わ級
+ * @description 瀵煎叆鎵�鏈� vuex 妯″潡锛岃嚜鍔ㄥ姞鍏amespaced:true锛岀敤浜庤В鍐硋uex鍛藉悕鍐茬獊锛岃鍕夸慨鏀广��
+ */
+
+import Vue from 'vue'
+import Vuex from 'vuex'
+
+Vue.use(Vuex)
+const files = require.context('./modules', false, /\.js$/)
+const modules = {}
+
+files.keys().forEach((key) => {
+  modules[key.replace(/(\.\/|\.js)/g, '')] = files(key).default
+})
+Object.keys(modules).forEach((key) => {
+  modules[key]['namespaced'] = true
+})
+const store = new Vuex.Store({
+  modules,
+})
+export default store
diff --git a/src/store/modules/errorLog.js b/src/store/modules/errorLog.js
new file mode 100644
index 0000000..941c6e5
--- /dev/null
+++ b/src/store/modules/errorLog.js
@@ -0,0 +1,28 @@
+/**
+ * @author chuzhixin 1204505056@qq.com 锛堜笉鎯充繚鐣檃uthor鍙垹闄わ級
+ * @description 寮傚父鎹曡幏鐨勭姸鎬佹嫤鎴紝璇峰嬁淇敼
+ */
+
+const state = () => ({
+  errorLogs: [],
+})
+const getters = {
+  errorLogs: (state) => state.errorLogs,
+}
+const mutations = {
+  addErrorLog(state, errorLog) {
+    state.errorLogs.push(errorLog)
+  },
+  clearErrorLog: (state) => {
+    state.errorLogs.splice(0)
+  },
+}
+const actions = {
+  addErrorLog({ commit }, errorLog) {
+    commit('addErrorLog', errorLog)
+  },
+  clearErrorLog({ commit }) {
+    commit('clearErrorLog')
+  },
+}
+export default { state, getters, mutations, actions }
diff --git a/src/store/modules/routes.js b/src/store/modules/routes.js
new file mode 100644
index 0000000..916bcb1
--- /dev/null
+++ b/src/store/modules/routes.js
@@ -0,0 +1,42 @@
+/**
+ * @author chuzhixin 1204505056@qq.com 锛堜笉鎯充繚鐣檃uthor鍙垹闄わ級
+ * @description 璺敱鎷︽埅鐘舵�佺鐞嗭紝鐩墠涓ょ妯″紡锛歛ll妯″紡涓巌ntelligence妯″紡锛屽叾涓璸artialRoutes鏄彍鍗曟殏鏈娇鐢�
+ */
+import { asyncRoutes, constantRoutes } from '@/router'
+import { convertRouter, filterAsyncRoutes } from '@/utils/handleRoutes'
+
+const state = () => ({
+  routes: [],
+  partialRoutes: [],
+})
+const getters = {
+  routes: (state) => state.routes,
+  partialRoutes: (state) => state.partialRoutes,
+}
+const mutations = {
+  setRoutes(state, routes) {
+    state.routes = constantRoutes.concat(routes)
+  },
+  setAllRoutes(state, routes) {
+    // state.routes = constantRoutes.concat(routes)
+  },
+  setPartialRoutes(state, routes) {
+    state.partialRoutes = constantRoutes.concat(routes)
+  },
+}
+const actions = {
+  async setRoutes({ commit }, permissions) {
+    //寮�婧愮増鍙繃婊ゅ姩鎬佽矾鐢眕ermissions锛宎dmin涓嶅啀榛樿鎷ユ湁鍏ㄩ儴鏉冮檺
+    const finallyAsyncRoutes = await filterAsyncRoutes(
+      [...asyncRoutes],
+      permissions
+    )
+    commit('setRoutes', finallyAsyncRoutes)
+    return finallyAsyncRoutes
+  },
+  setPartialRoutes({ commit }, accessRoutes) {
+    commit('setPartialRoutes', accessRoutes)
+    return accessRoutes
+  },
+}
+export default { state, getters, mutations, actions }
diff --git a/src/store/modules/settings.js b/src/store/modules/settings.js
new file mode 100644
index 0000000..fc7cf9d
--- /dev/null
+++ b/src/store/modules/settings.js
@@ -0,0 +1,75 @@
+/**
+ * @author chuzhixin 1204505056@qq.com 锛堜笉鎯充繚鐣檃uthor鍙垹闄わ級
+ * @description 鎵�鏈夊叏灞�閰嶇疆鐨勭姸鎬佺鐞嗭紝濡傛棤蹇呰璇峰嬁淇敼
+ */
+
+import defaultSettings from '@/config'
+
+const { tabsBar, logo, layout, header, themeBar } = defaultSettings
+const theme =
+  JSON.parse(localStorage.getItem('vue-admin-beautiful-theme')) || ''
+const state = () => ({
+  tabsBar: theme.tabsBar || tabsBar,
+  logo,
+  collapse: false,
+  layout: theme.layout || layout,
+  header: theme.header || header,
+  device: 'desktop',
+  themeBar,
+})
+const getters = {
+  collapse: (state) => state.collapse,
+  device: (state) => state.device,
+  header: (state) => state.header,
+  layout: (state) => state.layout,
+  logo: (state) => state.logo,
+  tabsBar: (state) => state.tabsBar,
+  themeBar: (state) => state.themeBar,
+}
+const mutations = {
+  changeLayout: (state, layout) => {
+    if (layout) state.layout = layout
+  },
+  changeHeader: (state, header) => {
+    if (header) state.header = header
+  },
+  changeTabsBar: (state, tabsBar) => {
+    if (tabsBar) state.tabsBar = tabsBar
+  },
+  changeCollapse: (state) => {
+    state.collapse = !state.collapse
+  },
+  foldSideBar: (state) => {
+    state.collapse = true
+  },
+  openSideBar: (state) => {
+    state.collapse = false
+  },
+  toggleDevice: (state, device) => {
+    state.device = device
+  },
+}
+const actions = {
+  changeLayout({ commit }, layout) {
+    commit('changeLayout', layout)
+  },
+  changeHeader({ commit }, header) {
+    commit('changeHeader', header)
+  },
+  changeTabsBar({ commit }, tabsBar) {
+    commit('changeTabsBar', tabsBar)
+  },
+  changeCollapse({ commit }) {
+    commit('changeCollapse')
+  },
+  foldSideBar({ commit }) {
+    commit('foldSideBar')
+  },
+  openSideBar({ commit }) {
+    commit('openSideBar')
+  },
+  toggleDevice({ commit }, device) {
+    commit('toggleDevice', device)
+  },
+}
+export default { state, getters, mutations, actions }
diff --git a/src/store/modules/table.js b/src/store/modules/table.js
new file mode 100644
index 0000000..bf57c09
--- /dev/null
+++ b/src/store/modules/table.js
@@ -0,0 +1,23 @@
+/**
+ * @author chuzhixin 1204505056@qq.com 锛堜笉鎯充繚鐣檃uthor鍙垹闄わ級
+ * @description 浠g爜鐢熸垚鏈虹姸鎬佺鐞�
+ */
+
+const state = () => ({
+  srcCode: '',
+})
+const getters = {
+  srcTableCode: (state) => state.srcCode,
+}
+
+const mutations = {
+  setTableCode(state, srcCode) {
+    state.srcCode = srcCode
+  },
+}
+const actions = {
+  setTableCode({ commit }, srcCode) {
+    commit('setTableCode', srcCode)
+  },
+}
+export default { state, getters, mutations, actions }
diff --git a/src/store/modules/tabsBar.js b/src/store/modules/tabsBar.js
new file mode 100644
index 0000000..254d062
--- /dev/null
+++ b/src/store/modules/tabsBar.js
@@ -0,0 +1,112 @@
+/**
+ * @author chuzhixin 1204505056@qq.com 锛堜笉鎯充繚鐣檃uthor鍙垹闄わ級
+ * @description tabsBar澶氭爣绛鹃〉閫昏緫锛屽墠鏈熷�熼壌浜嗗緢澶氬紑婧愰」鐩彂鐜伴兘鏈変釜鍏卞悓鐨勭壒鐐瑰緢绻佺悙骞朵笉绗﹀悎妗嗘灦璁捐鐨勫垵琛凤紝鍚庢潵鍦╣ithub鐢ㄦ埛hipi鐨勫惎鍙戜笅瀹屾垚浜嗛噸鏋勶紝璇峰嬁淇敼
+ */
+
+const state = () => ({
+  visitedRoutes: [],
+})
+const getters = {
+  visitedRoutes: (state) => state.visitedRoutes,
+}
+const mutations = {
+  addVisitedRoute(state, route) {
+    let target = state.visitedRoutes.find((item) => item.path === route.path)
+    if (target) {
+      if (route.fullPath !== target.fullPath) Object.assign(target, route)
+      return
+    }
+    state.visitedRoutes.push(Object.assign({}, route))
+  },
+  delVisitedRoute(state, route) {
+    state.visitedRoutes.forEach((item, index) => {
+      if (item.path === route.path) state.visitedRoutes.splice(index, 1)
+    })
+  },
+  delOthersVisitedRoute(state, route) {
+    state.visitedRoutes = state.visitedRoutes.filter(
+      (item) => item.meta.affix || item.path === route.path
+    )
+  },
+  delLeftVisitedRoute(state, route) {
+    let index = state.visitedRoutes.length
+    state.visitedRoutes = state.visitedRoutes.filter((item) => {
+      if (item.name === route.name) index = state.visitedRoutes.indexOf(item)
+      return item.meta.affix || index <= state.visitedRoutes.indexOf(item)
+    })
+  },
+  delRightVisitedRoute(state, route) {
+    let index = state.visitedRoutes.length
+    state.visitedRoutes = state.visitedRoutes.filter((item) => {
+      if (item.name === route.name) index = state.visitedRoutes.indexOf(item)
+      return item.meta.affix || index >= state.visitedRoutes.indexOf(item)
+    })
+  },
+  delAllVisitedRoutes(state) {
+    state.visitedRoutes = state.visitedRoutes.filter((item) => item.meta.affix)
+  },
+  updateVisitedRoute(state, route) {
+    state.visitedRoutes.forEach((item) => {
+      if (item.path === route.path) item = Object.assign(item, route)
+    })
+  },
+}
+const actions = {
+  addVisitedRoute({ commit }, route) {
+    commit('addVisitedRoute', route)
+  },
+  async delRoute({ dispatch, state }, route) {
+    await dispatch('delVisitedRoute', route)
+    return {
+      visitedRoutes: [...state.visitedRoutes],
+    }
+  },
+  delVisitedRoute({ commit, state }, route) {
+    commit('delVisitedRoute', route)
+    return [...state.visitedRoutes]
+  },
+  async delOthersRoutes({ dispatch, state }, route) {
+    await dispatch('delOthersVisitedRoute', route)
+    return {
+      visitedRoutes: [...state.visitedRoutes],
+    }
+  },
+  async delLeftRoutes({ dispatch, state }, route) {
+    await dispatch('delLeftVisitedRoute', route)
+    return {
+      visitedRoutes: [...state.visitedRoutes],
+    }
+  },
+  async delRightRoutes({ dispatch, state }, route) {
+    await dispatch('delRightVisitedRoute', route)
+    return {
+      visitedRoutes: [...state.visitedRoutes],
+    }
+  },
+  delOthersVisitedRoute({ commit, state }, route) {
+    commit('delOthersVisitedRoute', route)
+    return [...state.visitedRoutes]
+  },
+  delLeftVisitedRoute({ commit, state }, route) {
+    commit('delLeftVisitedRoute', route)
+    return [...state.visitedRoutes]
+  },
+  delRightVisitedRoute({ commit, state }, route) {
+    commit('delRightVisitedRoute', route)
+    return [...state.visitedRoutes]
+  },
+  async delAllRoutes({ dispatch, state }, route) {
+    await dispatch('delAllVisitedRoutes', route)
+    return {
+      visitedRoutes: [...state.visitedRoutes],
+    }
+  },
+  delAllVisitedRoutes({ commit, state }) {
+    commit('delAllVisitedRoutes')
+    return [...state.visitedRoutes]
+  },
+  updateVisitedRoute({ commit }, route) {
+    commit('updateVisitedRoute', route)
+  },
+}
+export default { state, getters, mutations, actions }
diff --git a/src/store/modules/user.js b/src/store/modules/user.js
new file mode 100644
index 0000000..24d681e
--- /dev/null
+++ b/src/store/modules/user.js
@@ -0,0 +1,99 @@
+/**
+ * @author chuzhixin 1204505056@qq.com 锛堜笉鎯充繚鐣檃uthor鍙垹闄わ級
+ * @description 鐧诲綍銆佽幏鍙栫敤鎴蜂俊鎭�侀��鍑虹櫥褰曘�佹竻闄ccessToken閫昏緫锛屼笉寤鸿淇敼
+ */
+
+import Vue from 'vue'
+import { getUserInfo, login, logout } from '@/api/user'
+import {
+  getAccessToken,
+  removeAccessToken,
+  setAccessToken,
+} from '@/utils/accessToken'
+import { resetRouter } from '@/router'
+import { title, tokenName } from '@/config'
+
+const state = () => ({
+  accessToken: getAccessToken(),
+  username: '',
+  avatar: '',
+  permissions: [],
+})
+const getters = {
+  accessToken: (state) => state.accessToken,
+  username: (state) => state.username,
+  avatar: (state) => state.avatar,
+  permissions: (state) => state.permissions,
+}
+const mutations = {
+  setAccessToken(state, accessToken) {
+    state.accessToken = accessToken
+    setAccessToken(accessToken)
+  },
+  setUsername(state, username) {
+    state.username = username
+  },
+  setAvatar(state, avatar) {
+    state.avatar = avatar
+  },
+  setPermissions(state, permissions) {
+    state.permissions = permissions
+  },
+}
+const actions = {
+  setPermissions({ commit }, permissions) {
+    commit('setPermissions', permissions)
+  },
+  async login({ commit }, userInfo) {
+    const { data } = await login(userInfo)
+    const accessToken = data[tokenName]
+    if (accessToken) {
+      commit('setAccessToken', accessToken)
+      const hour = new Date().getHours()
+      const thisTime =
+        hour < 8
+          ? '鏃╀笂濂�'
+          : hour <= 11
+          ? '涓婂崍濂�'
+          : hour <= 13
+          ? '涓崍濂�'
+          : hour < 18
+          ? '涓嬪崍濂�'
+          : '鏅氫笂濂�'
+      Vue.prototype.$baseNotify(`娆㈣繋鐧诲綍${title}`, `${thisTime}锛乣)
+    } else {
+      Vue.prototype.$baseMessage(
+        `鐧诲綍鎺ュ彛寮傚父锛屾湭姝g‘杩斿洖${tokenName}...`,
+        'error'
+      )
+    }
+  },
+  async getUserInfo({ commit, state }) {
+    const { data } = await getUserInfo(state.accessToken)
+    if (!data) {
+      Vue.prototype.$baseMessage('楠岃瘉澶辫触锛岃閲嶆柊鐧诲綍...', 'error')
+      return false
+    }
+    let { permissions, username, avatar } = data
+    if (permissions && username && Array.isArray(permissions)) {
+      commit('setPermissions', permissions)
+      commit('setUsername', username)
+      commit('setAvatar', avatar)
+      return permissions
+    } else {
+      Vue.prototype.$baseMessage('鐢ㄦ埛淇℃伅鎺ュ彛寮傚父', 'error')
+      return false
+    }
+  },
+  async logout({ dispatch }) {
+    await logout(state.accessToken)
+    await dispatch('resetAccessToken')
+    await resetRouter()
+  },
+  resetAccessToken({ commit }) {
+    commit('setPermissions', [])
+    commit('setAccessToken', '')
+    removeAccessToken()
+  },
+}
+export default { state, getters, mutations, actions }
diff --git a/src/styles/element-variables.scss b/src/styles/element-variables.scss
new file mode 100644
index 0000000..8da98a7
--- /dev/null
+++ b/src/styles/element-variables.scss
@@ -0,0 +1,1037 @@
+@charset "utf-8";
+
+/* Transition
+-------------------------- */
+$--all-transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
+$--fade-transition: opacity 300ms cubic-bezier(0.23, 1, 0.32, 1);
+$--fade-linear-transition: opacity 200ms linear;
+$--md-fade-transition: transform 300ms cubic-bezier(0.23, 1, 0.32, 1),
+  opacity 300ms cubic-bezier(0.23, 1, 0.32, 1);
+$--border-transition-base: border-color 0.2s
+  cubic-bezier(0.645, 0.045, 0.355, 1);
+$--color-transition-base: color 0.2s cubic-bezier(0.645, 0.045, 0.355, 1);
+
+/* Color
+-------------------------- */
+/// color|1|Brand Color|0
+$--color-primary: $base-color-blue;
+/// color|1|Background Color|4
+$--color-white: #fff;
+/// color|1|Background Color|4
+$--color-black: #000;
+$--color-primary-light-1: mix($--color-white, $--color-primary, 10%);
+
+/* 53a8ff */
+$--color-primary-light-2: mix($--color-white, $--color-primary, 20%);
+
+/* 66b1ff */
+$--color-primary-light-3: mix($--color-white, $--color-primary, 30%);
+
+/* 79bbff */
+$--color-primary-light-4: mix($--color-white, $--color-primary, 40%);
+
+/* 8cc5ff */
+$--color-primary-light-5: mix($--color-white, $--color-primary, 50%);
+
+/* a0cfff */
+$--color-primary-light-6: mix($--color-white, $--color-primary, 60%);
+
+/* b3d8ff */
+$--color-primary-light-7: mix($--color-white, $--color-primary, 70%);
+
+/* c6e2ff */
+$--color-primary-light-8: mix($--color-white, $--color-primary, 80%);
+
+/* d9ecff */
+$--color-primary-light-9: mix($--color-white, $--color-primary, 90%);
+
+/* ecf5ff */
+/// color|1|Functional Color|1
+$--color-success: $base-color-green;
+/// color|1|Functional Color|1
+$--color-warning: $base-color-yellow;
+/// color|1|Functional Color|1
+$--color-danger: $base-color-red;
+/// color|1|Functional Color|1
+$--color-info: #909399;
+
+$--color-success-light: mix($--color-white, $--color-success, 80%);
+$--color-warning-light: mix($--color-white, $--color-warning, 80%);
+$--color-danger-light: mix($--color-white, $--color-danger, 80%);
+$--color-info-light: mix($--color-white, $--color-info, 80%);
+
+$--color-success-lighter: mix($--color-white, $--color-success, 90%);
+$--color-warning-lighter: mix($--color-white, $--color-warning, 90%);
+$--color-danger-lighter: mix($--color-white, $--color-danger, 90%);
+$--color-info-lighter: mix($--color-white, $--color-info, 90%);
+/// color|1|Font Color|2
+$--color-text-primary: #303133;
+/// color|1|Font Color|2
+$--color-text-regular: #606266;
+/// color|1|Font Color|2
+$--color-text-secondary: #909399;
+/// color|1|Font Color|2
+$--color-text-placeholder: #c0c4cc;
+/// color|1|Border Color|3
+$--border-color-base: #dcdfe6;
+/// color|1|Border Color|3
+$--border-color-light: #e4e7ed;
+/// color|1|Border Color|3
+$--border-color-lighter: #ebeef5;
+/// color|1|Border Color|3
+$--border-color-extra-light: #f2f6fc;
+
+// Background
+/// color|1|Background Color|4
+$--background-color-base: #f5f7fa;
+
+/* Link
+-------------------------- */
+$--link-color: $--color-primary-light-2;
+$--link-hover-color: $--color-primary;
+
+/* Border
+-------------------------- */
+$--border-width-base: 1px;
+$--border-style-base: solid;
+$--border-color-hover: $--color-text-placeholder;
+$--border-base: $--border-width-base $--border-style-base $--border-color-base;
+/// borderRadius|1|Radius|0
+$--border-radius-base: $base-border-radius;
+/// borderRadius|1|Radius|0
+$--border-radius-small: $base-border-radius;
+/// borderRadius|1|Radius|0
+$--border-radius-circle: 100%;
+/// borderRadius|1|Radius|0
+$--border-radius-zero: 0;
+
+// Box-shadow
+/// boxShadow|1|Shadow|1
+$--box-shadow-base: 0 2px 4px rgba(0, 0, 0, 0.12), 0 0 6px rgba(0, 0, 0, 0.04);
+// boxShadow|1|Shadow|1
+$--box-shadow-dark: 0 2px 4px rgba(0, 0, 0, 0.12), 0 0 6px rgba(0, 0, 0, 0.12);
+/// boxShadow|1|Shadow|1
+$--box-shadow-light: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+
+/* Fill
+-------------------------- */
+$--fill-base: $--color-white;
+
+/* Typography
+-------------------------- */
+$--font-path: "~element-ui/lib/theme-chalk/fonts";
+$--font-display: "auto";
+/// fontSize|1|Font Size|0
+$--font-size-extra-large: 20px;
+/// fontSize|1|Font Size|0
+$--font-size-large: 18px;
+/// fontSize|1|Font Size|0
+$--font-size-medium: 16px;
+/// fontSize|1|Font Size|0
+$--font-size-base: 14px;
+/// fontSize|1|Font Size|0
+$--font-size-small: 13px;
+/// fontSize|1|Font Size|0
+$--font-size-extra-small: 12px;
+/// fontWeight|1|Font Weight|1
+$--font-weight-primary: 500;
+/// fontWeight|1|Font Weight|1
+$--font-weight-secondary: 100;
+/// fontLineHeight|1|Line Height|2
+$--font-line-height-primary: 24px;
+/// fontLineHeight|1|Line Height|2
+$--font-line-height-secondary: 16px;
+$--font-color-disabled-base: #bbb;
+
+/* Size
+-------------------------- */
+$--size-base: 14px;
+
+/* z-index
+-------------------------- */
+$--index-normal: 1;
+$--index-top: 1000;
+$--index-popper: 2000;
+
+/* Disable base
+-------------------------- */
+$--disabled-fill-base: $--background-color-base;
+$--disabled-color-base: $--color-text-placeholder;
+$--disabled-border-base: $--border-color-light;
+
+/* Icon
+-------------------------- */
+$--icon-color: #666;
+$--icon-color-base: $--color-info;
+
+/* Checkbox
+-------------------------- */
+/// fontSize||Font|1
+$--checkbox-font-size: 14px;
+/// fontWeight||Font|1
+$--checkbox-font-weight: $--font-weight-primary;
+/// color||Color|0
+$--checkbox-font-color: $--color-text-regular;
+$--checkbox-input-height: 14px;
+$--checkbox-input-width: 14px;
+/// borderRadius||Border|2
+$--checkbox-border-radius: $--border-radius-small;
+/// color||Color|0
+$--checkbox-background-color: $--color-white;
+$--checkbox-input-border: $--border-base;
+
+/// color||Color|0
+$--checkbox-disabled-border-color: $--border-color-base;
+$--checkbox-disabled-input-fill: #edf2fc;
+$--checkbox-disabled-icon-color: $--color-text-placeholder;
+
+$--checkbox-disabled-checked-input-fill: $--border-color-extra-light;
+$--checkbox-disabled-checked-input-border-color: $--border-color-base;
+$--checkbox-disabled-checked-icon-color: $--color-text-placeholder;
+
+/// color||Color|0
+$--checkbox-checked-font-color: $--color-primary;
+$--checkbox-checked-input-border-color: $--color-primary;
+/// color||Color|0
+$--checkbox-checked-background-color: $--color-primary;
+$--checkbox-checked-icon-color: $--fill-base;
+
+$--checkbox-input-border-color-hover: $--color-primary;
+/// height||Other|4
+$--checkbox-bordered-height: 40px;
+/// padding||Spacing|3
+$--checkbox-bordered-padding: 9px 20px 9px 10px;
+/// padding||Spacing|3
+$--checkbox-bordered-medium-padding: 7px 20px 7px 10px;
+/// padding||Spacing|3
+$--checkbox-bordered-small-padding: 5px 15px 5px 10px;
+/// padding||Spacing|3
+$--checkbox-bordered-mini-padding: 3px 15px 3px 10px;
+$--checkbox-bordered-medium-input-height: 14px;
+$--checkbox-bordered-medium-input-width: 14px;
+/// height||Other|4
+$--checkbox-bordered-medium-height: 36px;
+$--checkbox-bordered-small-input-height: 12px;
+$--checkbox-bordered-small-input-width: 12px;
+/// height||Other|4
+$--checkbox-bordered-small-height: 32px;
+$--checkbox-bordered-mini-input-height: 12px;
+$--checkbox-bordered-mini-input-width: 12px;
+/// height||Other|4
+$--checkbox-bordered-mini-height: 28px;
+
+/// color||Color|0
+$--checkbox-button-checked-background-color: $--color-primary;
+/// color||Color|0
+$--checkbox-button-checked-font-color: $--color-white;
+/// color||Color|0
+$--checkbox-button-checked-border-color: $--color-primary;
+
+/* Radio
+-------------------------- */
+/// fontSize||Font|1
+$--radio-font-size: $--font-size-base;
+/// fontWeight||Font|1
+$--radio-font-weight: $--font-weight-primary;
+/// color||Color|0
+$--radio-font-color: $--color-text-regular;
+$--radio-input-height: 14px;
+$--radio-input-width: 14px;
+/// borderRadius||Border|2
+$--radio-input-border-radius: $--border-radius-circle;
+/// color||Color|0
+$--radio-input-background-color: $--color-white;
+$--radio-input-border: $--border-base;
+/// color||Color|0
+$--radio-input-border-color: $--border-color-base;
+/// color||Color|0
+$--radio-icon-color: $--color-white;
+
+$--radio-disabled-input-border-color: $--disabled-border-base;
+$--radio-disabled-input-fill: $--disabled-fill-base;
+$--radio-disabled-icon-color: $--disabled-fill-base;
+
+$--radio-disabled-checked-input-border-color: $--disabled-border-base;
+$--radio-disabled-checked-input-fill: $--disabled-fill-base;
+$--radio-disabled-checked-icon-color: $--color-text-placeholder;
+
+/// color||Color|0
+$--radio-checked-font-color: $--color-primary;
+/// color||Color|0
+$--radio-checked-input-border-color: $--color-primary;
+/// color||Color|0
+$--radio-checked-input-background-color: $--color-white;
+/// color||Color|0
+$--radio-checked-icon-color: $--color-primary;
+
+$--radio-input-border-color-hover: $--color-primary;
+
+$--radio-bordered-height: 40px;
+$--radio-bordered-padding: 12px 20px 0 10px;
+$--radio-bordered-medium-padding: 10px 20px 0 10px;
+$--radio-bordered-small-padding: 8px 15px 0 10px;
+$--radio-bordered-mini-padding: 6px 15px 0 10px;
+$--radio-bordered-medium-input-height: 14px;
+$--radio-bordered-medium-input-width: 14px;
+$--radio-bordered-medium-height: 36px;
+$--radio-bordered-small-input-height: 12px;
+$--radio-bordered-small-input-width: 12px;
+$--radio-bordered-small-height: 32px;
+$--radio-bordered-mini-input-height: 12px;
+$--radio-bordered-mini-input-width: 12px;
+$--radio-bordered-mini-height: 28px;
+
+/// fontSize||Font|1
+$--radio-button-font-size: $--font-size-base;
+/// color||Color|0
+$--radio-button-checked-background-color: $--color-primary;
+/// color||Color|0
+$--radio-button-checked-font-color: $--color-white;
+/// color||Color|0
+$--radio-button-checked-border-color: $--color-primary;
+$--radio-button-disabled-checked-fill: $--border-color-extra-light;
+
+/* Select
+-------------------------- */
+$--select-border-color-hover: $--border-color-hover;
+$--select-disabled-border: $--disabled-border-base;
+/// fontSize||Font|1
+$--select-font-size: $--font-size-base;
+$--select-close-hover-color: $--color-text-secondary;
+
+$--select-input-color: $--color-text-placeholder;
+$--select-multiple-input-color: #666;
+/// color||Color|0
+$--select-input-focus-border-color: $--color-primary;
+/// fontSize||Font|1
+$--select-input-font-size: 14px;
+
+$--select-option-color: $--color-text-regular;
+$--select-option-disabled-color: $--color-text-placeholder;
+$--select-option-disabled-background: $--color-white;
+/// height||Other|4
+$--select-option-height: 34px;
+$--select-option-hover-background: $--background-color-base;
+/// color||Color|0
+$--select-option-selected-font-color: $--color-primary;
+$--select-option-selected-hover: $--background-color-base;
+
+$--select-group-color: $--color-info;
+$--select-group-height: 30px;
+$--select-group-font-size: 12px;
+
+$--select-dropdown-background: $--color-white;
+$--select-dropdown-shadow: $--box-shadow-light;
+$--select-dropdown-empty-color: #999;
+/// height||Other|4
+$--select-dropdown-max-height: 274px;
+$--select-dropdown-padding: 6px 0;
+$--select-dropdown-empty-padding: 10px 0;
+$--select-dropdown-border: solid 1px $--border-color-light;
+
+/* Alert
+-------------------------- */
+$--alert-padding: 8px 16px;
+/// borderRadius||Border|2
+$--alert-border-radius: $--border-radius-base;
+/// fontSize||Font|1
+$--alert-title-font-size: 13px;
+/// fontSize||Font|1
+$--alert-description-font-size: 12px;
+/// fontSize||Font|1
+$--alert-close-font-size: 12px;
+/// fontSize||Font|1
+$--alert-close-customed-font-size: 13px;
+
+$--alert-success-color: $--color-success-lighter;
+$--alert-info-color: $--color-info-lighter;
+$--alert-warning-color: $--color-warning-lighter;
+$--alert-danger-color: $--color-danger-lighter;
+
+/// height||Other|4
+$--alert-icon-size: 16px;
+/// height||Other|4
+$--alert-icon-large-size: 28px;
+
+/* MessageBox
+-------------------------- */
+/// color||Color|0
+$--messagebox-title-color: $--color-text-primary;
+$--msgbox-width: 420px;
+$--msgbox-border-radius: $--border-radius-base;
+/// fontSize||Font|1
+$--messagebox-font-size: $--font-size-large;
+/// fontSize||Font|1
+$--messagebox-content-font-size: $--font-size-base;
+/// color||Color|0
+$--messagebox-content-color: $--color-text-regular;
+/// fontSize||Font|1
+$--messagebox-error-font-size: 12px;
+$--msgbox-padding-primary: 15px;
+/// color||Color|0
+$--messagebox-success-color: $--color-success;
+/// color||Color|0
+$--messagebox-info-color: $--color-info;
+/// color||Color|0
+$--messagebox-warning-color: $--color-warning;
+/// color||Color|0
+$--messagebox-danger-color: $--color-danger;
+
+/* Message
+-------------------------- */
+$--message-shadow: $--box-shadow-base;
+$--message-min-width: 380px;
+$--message-background-color: #edf2fc;
+$--message-padding: 15px 15px 15px 20px;
+/// color||Color|0
+$--message-close-icon-color: $--color-text-placeholder;
+/// height||Other|4
+$--message-close-size: 16px;
+/// color||Color|0
+$--message-close-hover-color: $--color-text-secondary;
+
+/// color||Color|0
+$--message-success-font-color: $--color-success;
+/// color||Color|0
+$--message-info-font-color: $--color-info;
+/// color||Color|0
+$--message-warning-font-color: $--color-warning;
+/// color||Color|0
+$--message-danger-font-color: $--color-danger;
+
+/* Notification
+-------------------------- */
+$--notification-width: 330px;
+/// padding||Spacing|3
+$--notification-padding: 14px 26px 14px 13px;
+$--notification-radius: 8px;
+$--notification-shadow: $--box-shadow-light;
+/// color||Color|0
+$--notification-border-color: $--border-color-lighter;
+$--notification-icon-size: 24px;
+$--notification-close-font-size: $--message-close-size;
+$--notification-group-margin-left: 13px;
+$--notification-group-margin-right: 8px;
+/// fontSize||Font|1
+$--notification-content-font-size: $--font-size-base;
+/// color||Color|0
+$--notification-content-color: $--color-text-regular;
+/// fontSize||Font|1
+$--notification-title-font-size: 16px;
+/// color||Color|0
+$--notification-title-color: $--color-text-primary;
+
+/// color||Color|0
+$--notification-close-color: $--color-text-secondary;
+/// color||Color|0
+$--notification-close-hover-color: $--color-text-regular;
+
+/// color||Color|0
+$--notification-success-icon-color: $--color-success;
+/// color||Color|0
+$--notification-info-icon-color: $--color-info;
+/// color||Color|0
+$--notification-warning-icon-color: $--color-warning;
+/// color||Color|0
+$--notification-danger-icon-color: $--color-danger;
+
+/* Input
+-------------------------- */
+$--input-font-size: $--font-size-base;
+/// color||Color|0
+$--input-font-color: $--color-text-regular;
+/// height||Other|4
+$--input-width: 140px;
+/// height||Other|4
+$--input-height: 40px;
+$--input-border: $--border-base;
+$--input-border-color: $--border-color-base;
+/// borderRadius||Border|2
+$--input-border-radius: $--border-radius-base;
+$--input-border-color-hover: $--border-color-hover;
+/// color||Color|0
+$--input-background-color: $--color-white;
+$--input-fill-disabled: $--disabled-fill-base;
+$--input-color-disabled: $--font-color-disabled-base;
+/// color||Color|0
+$--input-icon-color: $--color-text-placeholder;
+/// color||Color|0
+$--input-placeholder-color: $--color-text-placeholder;
+$--input-max-width: 314px;
+
+$--input-hover-border: $--border-color-hover;
+$--input-clear-hover-color: $--color-text-secondary;
+
+$--input-focus-border: $--color-primary;
+$--input-focus-fill: $--color-white;
+
+$--input-disabled-fill: $--disabled-fill-base;
+$--input-disabled-border: $--disabled-border-base;
+$--input-disabled-color: $--disabled-color-base;
+$--input-disabled-placeholder-color: $--color-text-placeholder;
+
+/// fontSize||Font|1
+$--input-medium-font-size: 14px;
+/// height||Other|4
+$--input-medium-height: 36px;
+/// fontSize||Font|1
+$--input-small-font-size: 13px;
+/// height||Other|4
+$--input-small-height: 32px;
+/// fontSize||Font|1
+$--input-mini-font-size: 12px;
+/// height||Other|4
+$--input-mini-height: 28px;
+
+/* Cascader
+-------------------------- */
+/// color||Color|0
+$--cascader-menu-font-color: $--color-text-regular;
+/// color||Color|0
+$--cascader-menu-selected-font-color: $--color-primary;
+$--cascader-menu-fill: $--fill-base;
+$--cascader-menu-font-size: $--font-size-base;
+$--cascader-menu-radius: $--border-radius-base;
+$--cascader-menu-border: solid 1px $--border-color-light;
+$--cascader-menu-shadow: $--box-shadow-light;
+$--cascader-node-background-hover: $--background-color-base;
+$--cascader-node-color-disabled: $--color-text-placeholder;
+$--cascader-color-empty: $--color-text-placeholder;
+$--cascader-tag-background: #f0f2f5;
+
+/* Group
+-------------------------- */
+$--group-option-flex: 0 0 (1/5) * 100%;
+$--group-option-offset-bottom: 12px;
+$--group-option-fill-hover: rgba($--color-black, 0.06);
+$--group-title-color: $--color-black;
+$--group-title-font-size: $--font-size-base;
+$--group-title-width: 66px;
+
+/* Tab
+-------------------------- */
+$--tab-font-size: $--font-size-base;
+$--tab-border-line: 1px solid #e4e4e4;
+$--tab-header-color-active: $--color-text-secondary;
+$--tab-header-color-hover: $--color-text-regular;
+$--tab-header-color: $--color-text-regular;
+$--tab-header-fill-active: rgba($--color-black, 0.06);
+$--tab-header-fill-hover: rgba($--color-black, 0.06);
+$--tab-vertical-header-width: 90px;
+$--tab-vertical-header-count-color: $--color-white;
+$--tab-vertical-header-count-fill: $--color-text-secondary;
+
+/* Button
+-------------------------- */
+/// fontSize||Font|1
+$--button-font-size: $--font-size-base;
+/// fontWeight||Font|1
+$--button-font-weight: $--font-weight-primary;
+/// borderRadius||Border|2
+$--button-border-radius: $--border-radius-base;
+/// padding||Spacing|3
+$--button-padding-vertical: 12px;
+/// padding||Spacing|3
+$--button-padding-horizontal: 20px;
+
+/// fontSize||Font|1
+$--button-medium-font-size: $--font-size-base;
+/// borderRadius||Border|2
+$--button-medium-border-radius: $--border-radius-base;
+/// padding||Spacing|3
+$--button-medium-padding-vertical: 10px;
+/// padding||Spacing|3
+$--button-medium-padding-horizontal: 20px;
+
+/// fontSize||Font|1
+$--button-small-font-size: 12px;
+$--button-small-border-radius: $--border-radius-base;
+/// padding||Spacing|3
+$--button-small-padding-vertical: 9px;
+/// padding||Spacing|3
+$--button-small-padding-horizontal: 15px;
+/// fontSize||Font|1
+$--button-mini-font-size: 12px;
+$--button-mini-border-radius: $--border-radius-base;
+/// padding||Spacing|3
+$--button-mini-padding-vertical: 7px;
+/// padding||Spacing|3
+$--button-mini-padding-horizontal: 15px;
+
+/// color||Color|0
+$--button-default-font-color: $--color-text-regular;
+/// color||Color|0
+$--button-default-background-color: $--color-white;
+/// color||Color|0
+$--button-default-border-color: $--border-color-base;
+
+/// color||Color|0
+$--button-disabled-font-color: $--color-text-placeholder;
+/// color||Color|0
+$--button-disabled-background-color: $--color-white;
+/// color||Color|0
+$--button-disabled-border-color: $--border-color-lighter;
+
+/// color||Color|0
+$--button-primary-border-color: $--color-primary;
+/// color||Color|0
+$--button-primary-font-color: $--color-white;
+/// color||Color|0
+$--button-primary-background-color: $--color-primary;
+/// color||Color|0
+$--button-success-border-color: $--color-success;
+/// color||Color|0
+$--button-success-font-color: $--color-white;
+/// color||Color|0
+$--button-success-background-color: $--color-success;
+/// color||Color|0
+$--button-warning-border-color: $--color-warning;
+/// color||Color|0
+$--button-warning-font-color: $--color-white;
+/// color||Color|0
+$--button-warning-background-color: $--color-warning;
+/// color||Color|0
+$--button-danger-border-color: $--color-danger;
+/// color||Color|0
+$--button-danger-font-color: $--color-white;
+/// color||Color|0
+$--button-danger-background-color: $--color-danger;
+/// color||Color|0
+$--button-info-border-color: $--color-info;
+/// color||Color|0
+$--button-info-font-color: $--color-white;
+/// color||Color|0
+$--button-info-background-color: $--color-info;
+
+$--button-hover-tint-percent: 20%;
+$--button-active-shade-percent: 10%;
+
+/* cascader
+-------------------------- */
+$--cascader-height: 200px;
+
+/* Switch
+-------------------------- */
+/// color||Color|0
+$--switch-on-color: $--color-primary;
+/// color||Color|0
+$--switch-off-color: $--border-color-base;
+/// fontSize||Font|1
+$--switch-font-size: $--font-size-base;
+$--switch-core-border-radius: 10px;
+// height||Other|4 TODO: width 浠g爜鍐欐鐨�40px 鎵�浠ヤ笅闈㈣繖涓変釜灞炴�ч兘娌℃剰涔�
+$--switch-width: 40px;
+// height||Other|4
+$--switch-height: 20px;
+// height||Other|4
+$--switch-button-size: 16px;
+
+/* Dialog
+-------------------------- */
+$--dialog-background-color: $--color-white;
+$--dialog-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
+/// fontSize||Font|1
+$--dialog-title-font-size: $--font-size-large;
+/// fontSize||Font|1
+$--dialog-content-font-size: 14px;
+/// fontLineHeight||LineHeight|2
+$--dialog-font-line-height: $--font-line-height-primary;
+/// padding||Spacing|3
+$--dialog-padding-primary: 20px;
+
+/* Table
+-------------------------- */
+/// color||Color|0
+$--table-border-color: $--border-color-lighter;
+$--table-border: 1px solid $--table-border-color;
+/// color||Color|0
+$--table-font-color: $--color-text-regular;
+/// color||Color|0
+$--table-header-font-color: $--color-text-secondary;
+/// color||Color|0
+$--table-row-hover-background-color: $--background-color-base;
+$--table-current-row-background-color: $--color-primary-light-9;
+/// color||Color|0
+$--table-header-background-color: $--color-white;
+$--table-fixed-box-shadow: 0 0 10px rgba(0, 0, 0, 0.12);
+
+/* Pagination
+-------------------------- */
+/// fontSize||Font|1
+$--pagination-font-size: 13px;
+/// color||Color|0
+$--pagination-background-color: $--color-white;
+/// color||Color|0
+$--pagination-font-color: $--color-text-primary;
+$--pagination-border-radius: $--border-radius-base;
+/// color||Color|0
+$--pagination-button-color: $--color-text-primary;
+/// height||Other|4
+$--pagination-button-width: 35.5px;
+/// height||Other|4
+$--pagination-button-height: 28px;
+/// color||Color|0
+$--pagination-button-disabled-color: $--color-text-placeholder;
+/// color||Color|0
+$--pagination-button-disabled-background-color: $--color-white;
+/// color||Color|0
+$--pagination-hover-color: $--color-primary;
+
+/* Popup
+-------------------------- */
+/// color||Color|0
+$--popup-modal-background-color: $--color-black;
+/// opacity||Other|1
+$--popup-modal-opacity: 0.5;
+
+/* Popover
+-------------------------- */
+/// color||Color|0
+$--popover-background-color: $--color-white;
+/// fontSize||Font|1
+$--popover-font-size: $--font-size-base;
+/// color||Color|0
+$--popover-border-color: $--border-color-lighter;
+$--popover-arrow-size: 6px;
+/// padding||Spacing|3
+$--popover-padding: 12px;
+$--popover-padding-large: 18px 20px;
+/// fontSize||Font|1
+$--popover-title-font-size: 16px;
+/// color||Color|0
+$--popover-title-font-color: $--color-text-primary;
+
+/* Tooltip
+-------------------------- */
+/// color|1|Color|0
+$--tooltip-fill: $--color-text-primary;
+/// color|1|Color|0
+$--tooltip-color: $--color-white;
+/// fontSize||Font|1
+$--tooltip-font-size: 12px;
+/// color||Color|0
+$--tooltip-border-color: $--color-text-primary;
+$--tooltip-arrow-size: 6px;
+/// padding||Spacing|3
+$--tooltip-padding: 10px;
+
+/* Tag
+-------------------------- */
+/// color||Color|0
+$--tag-info-color: $--color-info;
+/// color||Color|0
+$--tag-primary-color: $--color-primary;
+/// color||Color|0
+$--tag-success-color: $--color-success;
+/// color||Color|0
+$--tag-warning-color: $--color-warning;
+/// color||Color|0
+$--tag-danger-color: $--color-danger;
+/// fontSize||Font|1
+$--tag-font-size: 12px;
+$--tag-border-radius: $--border-radius-base;
+$--tag-padding: 0 10px;
+
+/* Tree
+-------------------------- */
+/// color||Color|0
+$--tree-node-hover-background-color: $--background-color-base;
+/// color||Color|0
+$--tree-font-color: $--color-text-regular;
+/// color||Color|0
+$--tree-expand-icon-color: $--color-text-placeholder;
+
+/* Dropdown
+-------------------------- */
+$--dropdown-menu-box-shadow: $--box-shadow-light;
+$--dropdown-menuItem-hover-fill: $--color-primary-light-9;
+$--dropdown-menuItem-hover-color: $--link-color;
+
+/* Badge
+-------------------------- */
+/// color||Color|0
+$--badge-background-color: $--color-danger;
+$--badge-radius: 10px;
+/// fontSize||Font|1
+$--badge-font-size: 12px;
+/// padding||Spacing|3
+$--badge-padding: 6px;
+/// height||Other|4
+$--badge-size: 18px;
+
+/* Card
+-------------------------- */
+/// color||Color|0
+$--card-border-color: $--border-color-lighter;
+$--card-border-radius: $--border-radius-base;
+/// padding||Spacing|3
+$--card-padding: 20px;
+
+/* Slider
+-------------------------- */
+/// color||Color|0
+$--slider-main-background-color: $--color-primary;
+/// color||Color|0
+$--slider-runway-background-color: $--border-color-light;
+$--slider-button-hover-color: mix($--color-primary, black, 97%);
+$--slider-stop-background-color: $--color-white;
+$--slider-disable-color: $--color-text-placeholder;
+$--slider-margin: 16px 0;
+$--slider-border-radius: $--border-radius-base;
+/// height|1|Other|4
+$--slider-height: 6px;
+/// height||Other|4
+$--slider-button-size: 16px;
+$--slider-button-wrapper-size: 36px;
+$--slider-button-wrapper-offset: -15px;
+
+/* Steps
+-------------------------- */
+$--steps-border-color: $--disabled-border-base;
+$--steps-border-radius: $--border-radius-base;
+$--steps-padding: 20px;
+
+/* Menu
+-------------------------- */
+/// fontSize||Font|1
+$--menu-item-font-size: $--font-size-base;
+/// color||Color|0
+$--menu-item-font-color: $--color-text-primary;
+/// color||Color|0
+$--menu-background-color: $--color-white;
+$--menu-item-hover-fill: $--color-primary-light-9;
+
+/* Rate
+-------------------------- */
+$--rate-height: 20px;
+/// fontSize||Font|1
+$--rate-font-size: $--font-size-base;
+/// height||Other|3
+$--rate-icon-size: 18px;
+/// margin||Spacing|2
+$--rate-icon-margin: 6px;
+$--rate-icon-color: $--color-text-placeholder;
+
+/* DatePicker
+-------------------------- */
+$--datepicker-font-color: $--color-text-regular;
+/// color|1|Color|0
+$--datepicker-off-font-color: $--color-text-placeholder;
+/// color||Color|0
+$--datepicker-header-font-color: $--color-text-regular;
+$--datepicker-icon-color: $--color-text-primary;
+$--datepicker-border-color: $--disabled-border-base;
+$--datepicker-inner-border-color: #e4e4e4;
+/// color||Color|0
+$--datepicker-inrange-background-color: $--border-color-extra-light;
+/// color||Color|0
+$--datepicker-inrange-hover-background-color: $--border-color-extra-light;
+/// color||Color|0
+$--datepicker-active-color: $--color-primary;
+/// color||Color|0
+$--datepicker-hover-font-color: $--color-primary;
+$--datepicker-cell-hover-color: #fff;
+
+/* Loading
+-------------------------- */
+/// height||Other|4
+$--loading-spinner-size: 42px;
+/// height||Other|4
+$--loading-fullscreen-spinner-size: 50px;
+
+/* Scrollbar
+-------------------------- */
+$--scrollbar-background-color: rgba($--color-text-secondary, 0.3);
+$--scrollbar-hover-background-color: rgba($--color-text-secondary, 0.5);
+
+/* Carousel
+-------------------------- */
+/// fontSize||Font|1
+$--carousel-arrow-font-size: 12px;
+$--carousel-arrow-size: 36px;
+$--carousel-arrow-background: rgba(31, 45, 61, 0.11);
+$--carousel-arrow-hover-background: rgba(31, 45, 61, 0.23);
+/// width||Other|4
+$--carousel-indicator-width: 30px;
+/// height||Other|4
+$--carousel-indicator-height: 2px;
+$--carousel-indicator-padding-horizontal: 4px;
+$--carousel-indicator-padding-vertical: 12px;
+$--carousel-indicator-out-color: $--border-color-hover;
+
+/* Collapse
+-------------------------- */
+/// color||Color|0
+$--collapse-border-color: $--border-color-lighter;
+/// height||Other|4
+$--collapse-header-height: 48px;
+/// color||Color|0
+$--collapse-header-background-color: $--color-white;
+/// color||Color|0
+$--collapse-header-font-color: $--color-text-primary;
+/// fontSize||Font|1
+$--collapse-header-font-size: 13px;
+/// color||Color|0
+$--collapse-content-background-color: $--color-white;
+/// fontSize||Font|1
+$--collapse-content-font-size: 13px;
+/// color||Color|0
+$--collapse-content-font-color: $--color-text-primary;
+
+/* Transfer
+-------------------------- */
+$--transfer-border-color: $--border-color-lighter;
+$--transfer-border-radius: $--border-radius-base;
+/// height||Other|4
+$--transfer-panel-width: 200px;
+/// height||Other|4
+$--transfer-panel-header-height: 40px;
+/// color||Color|0
+$--transfer-panel-header-background-color: $--background-color-base;
+/// height||Other|4
+$--transfer-panel-footer-height: 40px;
+/// height||Other|4
+$--transfer-panel-body-height: 246px;
+/// height||Other|4
+$--transfer-item-height: 30px;
+/// height||Other|4
+$--transfer-filter-height: 32px;
+
+/* Header
+  -------------------------- */
+$--header-padding: 0 20px;
+
+/* Footer
+-------------------------- */
+$--footer-padding: 0 20px;
+
+/* Main
+-------------------------- */
+$--main-padding: 20px;
+
+/* Timeline
+-------------------------- */
+$--timeline-node-size-normal: 12px;
+$--timeline-node-size-large: 14px;
+$--timeline-node-color: $--border-color-light;
+
+/* Backtop
+-------------------------- */
+/// color||Color|0
+$--backtop-background-color: $--color-white;
+/// color||Color|0
+$--backtop-font-color: $--color-primary;
+/// color||Color|0
+$--backtop-hover-background-color: $--border-color-extra-light;
+
+/* Link
+-------------------------- */
+/// fontSize||Font|1
+$--link-font-size: $--font-size-base;
+/// fontWeight||Font|1
+$--link-font-weight: $--font-weight-primary;
+/// color||Color|0
+$--link-default-font-color: $--color-text-regular;
+/// color||Color|0
+$--link-default-active-color: $--color-primary;
+/// color||Color|0
+$--link-disabled-font-color: $--color-text-placeholder;
+/// color||Color|0
+$--link-primary-font-color: $--color-primary;
+/// color||Color|0
+$--link-success-font-color: $--color-success;
+/// color||Color|0
+$--link-warning-font-color: $--color-warning;
+/// color||Color|0
+$--link-danger-font-color: $--color-danger;
+/// color||Color|0
+$--link-info-font-color: $--color-info;
+
+/* Calendar
+-------------------------- */
+/// border||Other|4
+$--calendar-border: $--table-border;
+/// color||Other|4
+$--calendar-selected-background-color: #f2f8fe;
+$--calendar-cell-width: 85px;
+
+/* Form
+-------------------------- */
+/// fontSize||Font|1
+$--form-label-font-size: $--font-size-base;
+
+/* Avatar
+-------------------------- */
+/// color||Color|0
+$--avatar-font-color: #fff;
+/// color||Color|0
+$--avatar-background-color: #c0c4cc;
+/// fontSize||Font Size|1
+$--avatar-text-font-size: 14px;
+/// fontSize||Font Size|1
+$--avatar-icon-font-size: 18px;
+/// borderRadius||Border|2
+$--avatar-border-radius: $--border-radius-base;
+/// size|1|Avatar Size|3
+$--avatar-large-size: 40px;
+/// size|1|Avatar Size|3
+$--avatar-medium-size: 36px;
+/// size|1|Avatar Size|3
+$--avatar-small-size: 28px;
+
+/* Break-point
+-------------------------- */
+$--sm: 768px;
+$--md: 992px;
+$--lg: 1200px;
+$--xl: 1920px;
+
+$--breakpoints: (
+  "xs": (
+    max-width: $--sm - 1,
+  ),
+  "sm": (
+    min-width: $--sm,
+  ),
+  "md": (
+    min-width: $--md,
+  ),
+  "lg": (
+    min-width: $--lg,
+  ),
+  "xl": (
+    min-width: $--xl,
+  ),
+);
+
+$--breakpoints-spec: (
+  "xs-only": (
+    max-width: $--sm - 1,
+  ),
+  "sm-and-up": (
+    min-width: $--sm,
+  ),
+  "sm-only": "(min-width: #{$--sm}) and (max-width: #{$--md - 1})",
+  "sm-and-down": (
+    max-width: $--md - 1,
+  ),
+  "md-and-up": (
+    min-width: $--md,
+  ),
+  "md-only": "(min-width: #{$--md}) and (max-width: #{$--lg - 1})",
+  "md-and-down": (
+    max-width: $--lg - 1,
+  ),
+  "lg-and-up": (
+    min-width: $--lg,
+  ),
+  "lg-only": "(min-width: #{$--lg}) and (max-width: #{$--xl - 1})",
+  "lg-and-down": (
+    max-width: $--xl - 1,
+  ),
+  "xl-only": (
+    min-width: $--xl,
+  ),
+);
+
+@import "~element-ui/packages/theme-chalk/src/index";
diff --git a/src/styles/loading.scss b/src/styles/loading.scss
new file mode 100644
index 0000000..f4b398a
--- /dev/null
+++ b/src/styles/loading.scss
@@ -0,0 +1,346 @@
+/**
+ * @author chuzhixin 1204505056@qq.com 锛堜笉鎯充繚鐣檃uthor鍙垹闄わ級
+ * @description 鍏ㄥ眬鍔犺浇鍔ㄧ敾
+ */
+
+@charset "utf-8";
+
+@import "./spinner/dots.css";
+@import "./spinner/gauge.css";
+@import "./spinner/inner-circles.css";
+@import "./spinner/plus.css";
+
+$base-loading: ".vab-loading-type";
+
+/* 鑷畾涔塴oading寮�濮� */
+#{$base-loading}1 {
+  display: flex;
+  width: 36px;
+  height: 36px;
+  margin: 0 auto 15px;
+  border: 3px solid transparent;
+  border-top-color: $base-color-blue;
+  border-bottom-color: $base-color-blue;
+  border-radius: 50%;
+  animation: vabLoading1-0 0.8s linear infinite;
+}
+
+#{$base-loading}1::before {
+  display: block;
+  width: 8px;
+  height: 8px;
+  margin: auto;
+  content: "";
+  border: 3px solid $base-color-blue;
+  border-radius: 50%;
+  animation: vabLoading1 0.5s alternate ease-in infinite;
+}
+
+@keyframes vabLoading1-0 {
+  to {
+    transform: rotate(360deg);
+  }
+}
+
+@keyframes vabLoading1 {
+  from {
+    transform: scale(0.5);
+  }
+
+  to {
+    transform: scale(1.2);
+  }
+}
+
+#{$base-loading}2 {
+  width: 20px;
+  height: 20px;
+  margin-top: -40px;
+  margin-left: -10px;
+  animation: vabLoading2 1s linear reverse infinite;
+}
+
+#{$base-loading}2::before {
+  display: block;
+  width: 36px;
+  height: 36px;
+  margin-top: -17px;
+  margin-left: -18px;
+  content: "";
+  animation: vabLoading2 0.4s linear infinite;
+}
+
+#{$base-loading}2::after {
+  display: block;
+  width: 8px;
+  height: 8px;
+  margin-top: -3px;
+  margin-left: -4px;
+  content: "";
+  animation: vabLoading2 0.4s linear infinite;
+}
+
+#{$base-loading}2::before,
+#{$base-loading}2,
+#{$base-loading}2::after {
+  position: absolute;
+  top: 40%;
+  left: 50%;
+  border: 3px solid transparent;
+  border-top-color: $base-color-blue;
+  border-right-color: $base-color-blue;
+  border-radius: 50%;
+}
+
+@keyframes vabLoading2 {
+  to {
+    transform: rotate(360deg);
+  }
+}
+
+#{$base-loading}3 {
+  display: inline-block;
+  width: 2.5em;
+  height: 3em;
+  margin-bottom: 15px;
+  border: 3px solid transparent;
+  border-top-color: $base-color-blue;
+  border-bottom-color: $base-color-blue;
+  border-radius: 50%;
+  animation: vabLoading3 2s ease infinite;
+}
+
+@keyframes vabLoading3 {
+  50% {
+    border-width: 8px;
+    transform: rotate(360deg) scale(0.4, 0.33);
+  }
+
+  100% {
+    border-width: 3px;
+    transform: rotate(720deg) scale(1, 1);
+  }
+}
+
+#{$base-loading}4 {
+  display: inline-block;
+  width: 30px;
+  height: 30px;
+  margin: 0 auto 10px;
+  border: 8px solid transparent;
+  border-bottom-color: $base-color-blue;
+  border-left-color: $base-color-blue;
+  border-radius: 50%;
+  animation: vabLoading4 1s linear infinite normal;
+}
+
+#{$base-loading}4::after {
+  display: block;
+  width: 15px;
+  height: 15px;
+  margin: 0;
+  content: " ";
+  border: 6px solid $base-color-blue;
+  border-bottom-color: transparent;
+  border-left-color: transparent;
+  border-radius: 50%;
+}
+
+@keyframes vabLoading4 {
+  0% {
+    opacity: 0.2;
+    transform: rotate(0deg);
+  }
+
+  50% {
+    opacity: 1;
+    transform: rotate(180deg);
+  }
+
+  100% {
+    opacity: 0.2;
+    transform: rotate(360deg);
+  }
+}
+
+#{$base-loading}5 {
+  display: block;
+  width: 0;
+  height: 0;
+  margin: 0 auto 15px;
+  border: solid 1.5em $base-color-blue;
+  border-right: solid 1.5em transparent;
+  border-left: solid 1.5em transparent;
+  border-radius: 100%;
+  animation: vabLoading5 1s linear infinite;
+}
+
+@keyframes vabLoading5 {
+  0% {
+    transform: rotate(0deg);
+  }
+
+  50% {
+    transform: rotate(60deg);
+  }
+
+  100% {
+    transform: rotate(360deg);
+  }
+}
+
+#{$base-loading}6 {
+  display: block;
+  width: 0;
+  height: 0;
+  margin: 0 auto 25px auto;
+  perspective: 200px;
+}
+
+#{$base-loading}6::before,
+#{$base-loading}6::after {
+  position: absolute;
+  width: 20px;
+  height: 20px;
+  content: "";
+  background: rgba(0, 0, 0, 0);
+  animation: vabLoading6 0.5s infinite alternate;
+}
+
+#{$base-loading}6::before {
+  left: 0;
+}
+
+#{$base-loading}6::after {
+  right: 0;
+  animation-delay: 0.15s;
+}
+
+@keyframes vabLoading6 {
+  0% {
+    box-shadow: 0 0 0 rgba(0, 0, 0, 0);
+    transform: scale(1) translateY(0) rotateX(0deg);
+  }
+
+  100% {
+    background: $base-color-blue;
+    box-shadow: 0 25px 40px rgba($base-color-blue, 0.5);
+    transform: scale(1.2) translateY(-25px) rotateX(45deg);
+  }
+}
+
+#{$base-loading}7 {
+  display: block;
+  width: 25px;
+  height: 25px;
+  margin: 0 auto 15px auto;
+  border: 2px solid $base-color-blue;
+  border-top-color: rgba($base-color-blue, 0.2);
+  border-right-color: rgba($base-color-blue, 0.2);
+  border-bottom-color: rgba($base-color-blue, 0.2);
+  border-radius: 100%;
+  animation: vabLoading7 infinite 0.75s linear;
+}
+
+@keyframes vabLoading7 {
+  0% {
+    transform: rotate(0);
+  }
+
+  100% {
+    transform: rotate(360deg);
+  }
+}
+
+#{$base-loading}8 {
+  position: relative;
+  box-sizing: border-box;
+  display: block;
+  width: 20px;
+  height: 20px;
+  margin: 0 auto 15px auto;
+  background-color: $base-color-blue;
+  border-radius: 50%;
+  box-shadow: 30px 0 0 0 $base-color-blue;
+  transform: translateX(-15px);
+}
+
+#{$base-loading}8::after {
+  position: absolute;
+  top: 8px;
+  left: 9px;
+  width: 10px;
+  height: 10px;
+  content: "";
+  background-color: $base-color-white;
+  border-radius: 50%;
+  box-shadow: 30px 0 0 0 $base-color-white;
+  animation: vabLoading8 2s ease-in-out infinite alternate;
+}
+
+@keyframes vabLoading8 {
+  0% {
+    left: 9px;
+  }
+
+  100% {
+    left: 1px;
+  }
+}
+
+#{$base-loading}9 {
+  position: relative;
+  box-sizing: border-box;
+  display: block;
+  width: 20px;
+  height: 20px;
+  margin: 0 auto 15px auto;
+  border: 1px $base-color-blue solid;
+  animation: vabLoading9 5s linear infinite;
+}
+
+#{$base-loading}9::after {
+  position: absolute;
+  top: -8px;
+  left: 0;
+  width: 4px;
+  height: 4px;
+  content: "";
+  background-color: $base-color-blue;
+  animation: vabLoading9_check 1s ease-in-out infinite;
+}
+
+@keyframes vabLoading9_check {
+  25% {
+    top: -8px;
+    left: 22px;
+  }
+
+  50% {
+    top: 22px;
+    left: 22px;
+  }
+
+  75% {
+    top: 22px;
+    left: -9px;
+  }
+
+  100% {
+    top: -7px;
+    left: -9px;
+  }
+}
+
+@keyframes vabLoading9 {
+  0% {
+    box-shadow: inset 0 0 0 0 rgba($base-color-blue, 0.5);
+    opacity: 0.5;
+  }
+
+  100% {
+    box-shadow: inset 0 -20px 0 0 $base-color-blue;
+  }
+}
+
+/* 鑷畾涔塴oading缁撴潫 */
diff --git a/src/styles/normalize.scss b/src/styles/normalize.scss
new file mode 100644
index 0000000..fe27fe8
--- /dev/null
+++ b/src/styles/normalize.scss
@@ -0,0 +1,353 @@
+@charset "utf-8";
+
+/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */
+
+/* Document
+   ========================================================================== */
+
+/**
+ * 1. Correct the line height in all browsers.
+ * 2. Prevent adjustments of font size after orientation changes in iOS.
+ */
+
+html {
+  line-height: 1.15; /* 1 */
+  -webkit-text-size-adjust: 100%; /* 2 */
+}
+
+/* Sections
+   ========================================================================== */
+
+/**
+ * Remove the margin in all browsers.
+ */
+
+body {
+  margin: 0;
+}
+
+/**
+ * Render the `main` element consistently in IE.
+ */
+
+main {
+  display: block;
+}
+
+/**
+ * Correct the font size and margin on `h1` elements within `section` and
+ * `article` contexts in Chrome, Firefox, and Safari.
+ */
+
+h1 {
+  margin: 0.67em 0;
+  font-size: 2em;
+}
+
+/* Grouping content
+   ========================================================================== */
+
+/**
+ * 1. Add the correct box sizing in Firefox.
+ * 2. Show the overflow in Edge and IE.
+ */
+
+hr {
+  box-sizing: content-box; /* 1 */
+  height: 0; /* 1 */
+  overflow: visible; /* 2 */
+}
+
+/**
+ * 1. Correct the inheritance and scaling of font size in all browsers.
+ * 2. Correct the odd `em` font sizing in all browsers.
+ */
+
+pre {
+  font-family: monospace; /* 1 */
+  font-size: 1em; /* 2 */
+}
+
+/* Text-level semantics
+   ========================================================================== */
+
+/**
+ * Remove the gray background on active links in IE 10.
+ */
+
+a {
+  background-color: transparent;
+}
+
+/**
+ * 1. Remove the bottom border in Chrome 57-
+ * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.
+ */
+
+abbr[title] {
+  text-decoration: underline; /* 2 */
+  text-decoration: underline dotted; /* 2 */
+  border-bottom: none; /* 1 */
+}
+
+/**
+ * Add the correct font weight in Chrome, Edge, and Safari.
+ */
+
+b,
+strong {
+  font-weight: bolder;
+}
+
+/**
+ * 1. Correct the inheritance and scaling of font size in all browsers.
+ * 2. Correct the odd `em` font sizing in all browsers.
+ */
+
+code,
+kbd,
+samp {
+  font-family: monospace; /* 1 */
+  font-size: 1em; /* 2 */
+}
+
+/**
+ * Add the correct font size in all browsers.
+ */
+
+small {
+  font-size: 80%;
+}
+
+/**
+ * Prevent `sub` and `sup` elements from affecting the line height in
+ * all browsers.
+ */
+
+sub,
+sup {
+  position: relative;
+  font-size: 75%;
+  line-height: 0;
+  vertical-align: baseline;
+}
+
+sub {
+  bottom: -0.25em;
+}
+
+sup {
+  top: -0.5em;
+}
+
+/* Embedded content
+   ========================================================================== */
+
+/**
+ * Remove the border on images inside links in IE 10.
+ */
+
+img {
+  border-style: none;
+}
+
+/* Forms
+   ========================================================================== */
+
+/**
+ * 1. Change the font styles in all browsers.
+ * 2. Remove the margin in Firefox and Safari.
+ */
+
+button,
+input,
+optgroup,
+select,
+textarea {
+  margin: 0; /* 2 */
+  font-family: inherit; /* 1 */
+  font-size: 100%; /* 1 */
+  line-height: 1.15; /* 1 */
+}
+
+/**
+ * Show the overflow in IE.
+ * 1. Show the overflow in Edge.
+ */
+
+button,
+input {
+  /* 1 */
+  overflow: visible;
+}
+
+/**
+ * Remove the inheritance of text transform in Edge, Firefox, and IE.
+ * 1. Remove the inheritance of text transform in Firefox.
+ */
+
+button,
+select {
+  /* 1 */
+  text-transform: none;
+}
+
+/**
+ * Correct the inability to style clickable types in iOS and Safari.
+ */
+
+button,
+[type="button"],
+[type="reset"],
+[type="submit"] {
+  -webkit-appearance: button;
+}
+
+/**
+ * Remove the inner border and padding in Firefox.
+ */
+
+button::-moz-focus-inner,
+[type="button"]::-moz-focus-inner,
+[type="reset"]::-moz-focus-inner,
+[type="submit"]::-moz-focus-inner {
+  padding: 0;
+  border-style: none;
+}
+
+/**
+ * Restore the focus styles unset by the previous rule.
+ */
+
+button:-moz-focusring,
+[type="button"]:-moz-focusring,
+[type="reset"]:-moz-focusring,
+[type="submit"]:-moz-focusring {
+  outline: 1px dotted ButtonText;
+}
+
+/**
+ * Correct the padding in Firefox.
+ */
+
+fieldset {
+  padding: 0.35em 0.75em 0.625em;
+}
+
+/**
+ * 1. Correct the text wrapping in Edge and IE.
+ * 2. Correct the color inheritance from `fieldset` elements in IE.
+ * 3. Remove the padding so developers are not caught out when they zero out
+ *    `fieldset` elements in all browsers.
+ */
+
+legend {
+  box-sizing: border-box; /* 1 */
+  display: table; /* 1 */
+  max-width: 100%; /* 1 */
+  padding: 0; /* 3 */
+  color: inherit; /* 2 */
+  white-space: normal; /* 1 */
+}
+
+/**
+ * Add the correct vertical alignment in Chrome, Firefox, and Opera.
+ */
+
+progress {
+  vertical-align: baseline;
+}
+
+/**
+ * Remove the default vertical scrollbar in IE 10+.
+ */
+
+textarea {
+  overflow: auto;
+}
+
+/**
+ * 1. Add the correct box sizing in IE 10.
+ * 2. Remove the padding in IE 10.
+ */
+
+[type="checkbox"],
+[type="radio"] {
+  box-sizing: border-box; /* 1 */
+  padding: 0; /* 2 */
+}
+
+/**
+ * Correct the cursor style of increment and decrement buttons in Chrome.
+ */
+
+[type="number"]::-webkit-inner-spin-button,
+[type="number"]::-webkit-outer-spin-button {
+  height: auto;
+}
+
+/**
+ * 1. Correct the odd appearance in Chrome and Safari.
+ * 2. Correct the outline style in Safari.
+ */
+
+[type="search"] {
+  -webkit-appearance: textfield; /* 1 */
+  outline-offset: -2px; /* 2 */
+}
+
+/**
+ * Remove the inner padding in Chrome and Safari on macOS.
+ */
+
+[type="search"]::-webkit-search-decoration {
+  -webkit-appearance: none;
+}
+
+/**
+ * 1. Correct the inability to style clickable types in iOS and Safari.
+ * 2. Change font properties to `inherit` in Safari.
+ */
+
+::-webkit-file-upload-button {
+  -webkit-appearance: button; /* 1 */
+  font: inherit; /* 2 */
+}
+
+/* Interactive
+   ========================================================================== */
+
+/*
+ * Add the correct display in Edge, IE 10+, and Firefox.
+ */
+
+details {
+  display: block;
+}
+
+/*
+ * Add the correct display in all browsers.
+ */
+
+summary {
+  display: list-item;
+}
+
+/* Misc
+   ========================================================================== */
+
+/**
+ * Add the correct display in IE 10+.
+ */
+
+template {
+  display: none;
+}
+
+/**
+ * Add the correct display in IE 10.
+ */
+
+[hidden] {
+  display: none;
+}
diff --git a/src/styles/spinner/dots.css b/src/styles/spinner/dots.css
new file mode 100644
index 0000000..33bef95
--- /dev/null
+++ b/src/styles/spinner/dots.css
@@ -0,0 +1,110 @@
+.dots-loader:not(:required) {
+  position: relative;
+  display: inline-block;
+  width: 7px;
+  height: 7px;
+  margin-bottom: 30px;
+  overflow: hidden;
+  text-indent: -9999px;
+  background: transparent;
+  border-radius: 100%;
+  box-shadow: #f86 -14px -14px 0 7px,
+    #fc6 14px -14px 0 7px,
+    #6d7 14px 14px 0 7px,
+    #4ae -14px 14px 0 7px;
+  transform-origin: 50% 50%;
+  animation: dots-loader 5s infinite ease-in-out;
+}
+
+@keyframes dots-loader {
+  0% {
+    box-shadow: #f86 -14px -14px 0 7px,
+      #fc6 14px -14px 0 7px,
+      #6d7 14px 14px 0 7px,
+      #4ae -14px 14px 0 7px;
+  }
+
+  8.33% {
+    box-shadow: #f86 14px -14px 0 7px,
+      #fc6 14px -14px 0 7px,
+      #6d7 14px 14px 0 7px,
+      #4ae -14px 14px 0 7px;
+  }
+
+  16.67% {
+    box-shadow: #f86 14px 14px 0 7px,
+      #fc6 14px 14px 0 7px,
+      #6d7 14px 14px 0 7px,
+      #4ae -14px 14px 0 7px;
+  }
+
+  25% {
+    box-shadow: #f86 -14px 14px 0 7px,
+      #fc6 -14px 14px 0 7px,
+      #6d7 -14px 14px 0 7px,
+      #4ae -14px 14px 0 7px;
+  }
+
+  33.33% {
+    box-shadow: #f86 -14px -14px 0 7px,
+      #fc6 -14px 14px 0 7px,
+      #6d7 -14px -14px 0 7px,
+      #4ae -14px -14px 0 7px;
+  }
+
+  41.67% {
+    box-shadow: #f86 14px -14px 0 7px,
+      #fc6 -14px 14px 0 7px,
+      #6d7 -14px -14px 0 7px,
+      #4ae 14px -14px 0 7px;
+  }
+
+  50% {
+    box-shadow: #f86 14px 14px 0 7px,
+      #fc6 -14px 14px 0 7px,
+      #6d7 -14px -14px 0 7px,
+      #4ae 14px -14px 0 7px;
+  }
+
+  58.33% {
+    box-shadow: #f86 -14px 14px 0 7px,
+      #fc6 -14px 14px 0 7px,
+      #6d7 -14px -14px 0 7px,
+      #4ae 14px -14px 0 7px;
+  }
+
+  66.67% {
+    box-shadow: #f86 -14px -14px 0 7px,
+      #fc6 -14px -14px 0 7px,
+      #6d7 -14px -14px 0 7px,
+      #4ae 14px -14px 0 7px;
+  }
+
+  75% {
+    box-shadow: #f86 14px -14px 0 7px,
+      #fc6 14px -14px 0 7px,
+      #6d7 14px -14px 0 7px,
+      #4ae 14px -14px 0 7px;
+  }
+
+  83.33% {
+    box-shadow: #f86 14px 14px 0 7px,
+      #fc6 14px -14px 0 7px,
+      #6d7 14px 14px 0 7px,
+      #4ae 14px 14px 0 7px;
+  }
+
+  91.67% {
+    box-shadow: #f86 -14px 14px 0 7px,
+      #fc6 14px -14px 0 7px,
+      #6d7 14px 14px 0 7px,
+      #4ae -14px 14px 0 7px;
+  }
+
+  100% {
+    box-shadow: #f86 -14px -14px 0 7px,
+      #fc6 14px -14px 0 7px,
+      #6d7 14px 14px 0 7px,
+      #4ae -14px 14px 0 7px;
+  }
+}
diff --git a/src/styles/spinner/gauge.css b/src/styles/spinner/gauge.css
new file mode 100644
index 0000000..1b82a28
--- /dev/null
+++ b/src/styles/spinner/gauge.css
@@ -0,0 +1,104 @@
+.gauge-loader:not(:required) {
+  position: relative;
+  display: inline-block;
+  width: 64px;
+  height: 32px;
+  margin-bottom: 10px;
+  overflow: hidden;
+  text-indent: -9999px;
+  background: #6ca;
+  border-top-left-radius: 32px;
+  border-top-right-radius: 32px;
+}
+
+.gauge-loader:not(:required)::before {
+  position: absolute;
+  top: 5px;
+  left: 30px;
+  width: 4px;
+  height: 27px;
+  content: "";
+  background: white;
+  border-radius: 2px;
+  transform-origin: 50% 100%;
+  animation: gauge-loader 4000ms infinite ease;
+}
+
+.gauge-loader:not(:required)::after {
+  position: absolute;
+  top: 26px;
+  left: 26px;
+  width: 13px;
+  height: 13px;
+  content: "";
+  background: white;
+  -moz-border-radius: 8px;
+  -webkit-border-radius: 8px;
+  border-radius: 8px;
+}
+
+@keyframes gauge-loader {
+  0% {
+    transform: rotate(-50deg);
+  }
+
+  10% {
+    transform: rotate(20deg);
+  }
+
+  20% {
+    transform: rotate(60deg);
+  }
+
+  24% {
+    transform: rotate(60deg);
+  }
+
+  40% {
+    transform: rotate(-20deg);
+  }
+
+  54% {
+    transform: rotate(70deg);
+  }
+
+  56% {
+    transform: rotate(78deg);
+  }
+
+  58% {
+    transform: rotate(73deg);
+  }
+
+  60% {
+    transform: rotate(75deg);
+  }
+
+  62% {
+    transform: rotate(70deg);
+  }
+
+  70% {
+    transform: rotate(-20deg);
+  }
+
+  80% {
+    transform: rotate(20deg);
+  }
+
+  83% {
+    transform: rotate(25deg);
+  }
+
+  86% {
+    transform: rotate(20deg);
+  }
+
+  89% {
+    transform: rotate(25deg);
+  }
+
+  100% {
+    transform: rotate(-50deg);
+  }
+}
diff --git a/src/styles/spinner/inner-circles.css b/src/styles/spinner/inner-circles.css
new file mode 100644
index 0000000..0144da2
--- /dev/null
+++ b/src/styles/spinner/inner-circles.css
@@ -0,0 +1,51 @@
+.inner-circles-loader:not(:required) {
+  position: relative;
+  display: inline-block;
+  width: 50px;
+  height: 50px;
+  margin-bottom: 10px;
+  overflow: hidden;
+  text-indent: -9999px;
+  background: rgba(25, 165, 152, 0.5);
+  border-radius: 50%;
+  transform: translate3d(0, 0, 0);
+}
+
+.inner-circles-loader:not(:required)::before,
+.inner-circles-loader:not(:required)::after {
+  position: absolute;
+  top: 0;
+  display: inline-block;
+  width: 50px;
+  height: 50px;
+  content: "";
+  border-radius: 50%;
+}
+
+.inner-circles-loader:not(:required)::before {
+  left: 0;
+  background: #c7efcf;
+  transform-origin: 0 50%;
+  animation: inner-circles-loader 3s infinite;
+}
+
+.inner-circles-loader:not(:required)::after {
+  right: 0;
+  background: #eef5db;
+  transform-origin: 100% 50%;
+  animation: inner-circles-loader 3s 0.2s reverse infinite;
+}
+
+@keyframes inner-circles-loader {
+  0% {
+    transform: rotate(0deg);
+  }
+
+  50% {
+    transform: rotate(360deg);
+  }
+
+  100% {
+    transform: rotate(0deg);
+  }
+}
diff --git a/src/styles/spinner/plus.css b/src/styles/spinner/plus.css
new file mode 100644
index 0000000..af4516f
--- /dev/null
+++ b/src/styles/spinner/plus.css
@@ -0,0 +1,341 @@
+.plus-loader:not(:required) {
+  position: relative;
+  display: inline-block;
+  width: 48px;
+  height: 48px;
+  margin-bottom: 10px;
+  overflow: hidden;
+  text-indent: -9999px;
+  background: #f86;
+  -moz-border-radius: 24px;
+  -webkit-border-radius: 24px;
+  border-radius: 24px;
+  -moz-transform: rotateZ(90deg);
+  -ms-transform: rotateZ(90deg);
+  -webkit-transform: rotateZ(90deg);
+  transform: rotateZ(90deg);
+  -moz-transform-origin: 50% 50%;
+  -ms-transform-origin: 50% 50%;
+  -webkit-transform-origin: 50% 50%;
+  transform-origin: 50% 50%;
+  -moz-animation: plus-loader-background 3s infinite ease-in-out;
+  -webkit-animation: plus-loader-background 3s infinite ease-in-out;
+  animation: plus-loader-background 3s infinite ease-in-out;
+}
+
+.plus-loader:not(:required)::after {
+  position: absolute;
+  top: 0;
+  right: 50%;
+  width: 50%;
+  height: 100%;
+  content: "";
+  background: #f86;
+  -moz-border-radius: 24px 0 0 24px;
+  -webkit-border-radius: 24px;
+  border-radius: 24px 0 0 24px;
+  -moz-transform-origin: 100% 50%;
+  -ms-transform-origin: 100% 50%;
+  -webkit-transform-origin: 100% 50%;
+  transform-origin: 100% 50%;
+  -moz-animation: plus-loader-top 3s infinite linear;
+  -webkit-animation: plus-loader-top 3s infinite linear;
+  animation: plus-loader-top 3s infinite linear;
+}
+
+.plus-loader:not(:required)::before {
+  position: absolute;
+  top: 0;
+  right: 50%;
+  width: 50%;
+  height: 100%;
+  content: "";
+  background: #fc6;
+  -moz-border-radius: 24px 0 0 24px;
+  -webkit-border-radius: 24px;
+  border-radius: 24px 0 0 24px;
+  -moz-transform-origin: 100% 50%;
+  -ms-transform-origin: 100% 50%;
+  -webkit-transform-origin: 100% 50%;
+  transform-origin: 100% 50%;
+  -moz-animation: plus-loader-bottom 3s infinite linear;
+  -webkit-animation: plus-loader-bottom 3s infinite linear;
+  animation: plus-loader-bottom 3s infinite linear;
+}
+
+@keyframes plus-loader-top {
+  2.5% {
+    background: #f86;
+    -moz-transform: rotateY(0deg);
+    -ms-transform: rotateY(0deg);
+    -webkit-transform: rotateY(0deg);
+    transform: rotateY(0deg);
+    -moz-animation-timing-function: ease-in;
+    -webkit-animation-timing-function: ease-in;
+    animation-timing-function: ease-in;
+  }
+
+  13.75% {
+    background: #ff430d;
+    -moz-transform: rotateY(90deg);
+    -ms-transform: rotateY(90deg);
+    -webkit-transform: rotateY(90deg);
+    transform: rotateY(90deg);
+    -moz-animation-timing-function: step-start;
+    -webkit-animation-timing-function: step-start;
+    animation-timing-function: step-start;
+  }
+
+  13.76% {
+    background: #ffae0d;
+    -moz-transform: rotateY(90deg);
+    -ms-transform: rotateY(90deg);
+    -webkit-transform: rotateY(90deg);
+    transform: rotateY(90deg);
+    -moz-animation-timing-function: ease-out;
+    -webkit-animation-timing-function: ease-out;
+    animation-timing-function: ease-out;
+  }
+
+  25% {
+    background: #fc6;
+    -moz-transform: rotateY(180deg);
+    -ms-transform: rotateY(180deg);
+    -webkit-transform: rotateY(180deg);
+    transform: rotateY(180deg);
+  }
+
+  27.5% {
+    background: #fc6;
+    -moz-transform: rotateY(180deg);
+    -ms-transform: rotateY(180deg);
+    -webkit-transform: rotateY(180deg);
+    transform: rotateY(180deg);
+    -moz-animation-timing-function: ease-in;
+    -webkit-animation-timing-function: ease-in;
+    animation-timing-function: ease-in;
+  }
+
+  41.25% {
+    background: #ffae0d;
+    -moz-transform: rotateY(90deg);
+    -ms-transform: rotateY(90deg);
+    -webkit-transform: rotateY(90deg);
+    transform: rotateY(90deg);
+    -moz-animation-timing-function: step-start;
+    -webkit-animation-timing-function: step-start;
+    animation-timing-function: step-start;
+  }
+
+  41.26% {
+    background: #2cc642;
+    -moz-transform: rotateY(90deg);
+    -ms-transform: rotateY(90deg);
+    -webkit-transform: rotateY(90deg);
+    transform: rotateY(90deg);
+    -moz-animation-timing-function: ease-out;
+    -webkit-animation-timing-function: ease-out;
+    animation-timing-function: ease-out;
+  }
+
+  50% {
+    background: #6d7;
+    -moz-transform: rotateY(0deg);
+    -ms-transform: rotateY(0deg);
+    -webkit-transform: rotateY(0deg);
+    transform: rotateY(0deg);
+  }
+
+  52.5% {
+    background: #6d7;
+    -moz-transform: rotateY(0deg);
+    -ms-transform: rotateY(0deg);
+    -webkit-transform: rotateY(0deg);
+    transform: rotateY(0deg);
+    -moz-animation-timing-function: ease-in;
+    -webkit-animation-timing-function: ease-in;
+    animation-timing-function: ease-in;
+  }
+
+  63.75% {
+    background: #2cc642;
+    -moz-transform: rotateY(90deg);
+    -ms-transform: rotateY(90deg);
+    -webkit-transform: rotateY(90deg);
+    transform: rotateY(90deg);
+    -moz-animation-timing-function: step-start;
+    -webkit-animation-timing-function: step-start;
+    animation-timing-function: step-start;
+  }
+
+  63.76% {
+    background: #1386d2;
+    -moz-transform: rotateY(90deg);
+    -ms-transform: rotateY(90deg);
+    -webkit-transform: rotateY(90deg);
+    transform: rotateY(90deg);
+    -moz-animation-timing-function: ease-out;
+    -webkit-animation-timing-function: ease-out;
+    animation-timing-function: ease-out;
+  }
+
+  75% {
+    background: #4ae;
+    -moz-transform: rotateY(180deg);
+    -ms-transform: rotateY(180deg);
+    -webkit-transform: rotateY(180deg);
+    transform: rotateY(180deg);
+  }
+
+  77.5% {
+    background: #4ae;
+    -moz-transform: rotateY(180deg);
+    -ms-transform: rotateY(180deg);
+    -webkit-transform: rotateY(180deg);
+    transform: rotateY(180deg);
+    -moz-animation-timing-function: ease-in;
+    -webkit-animation-timing-function: ease-in;
+    animation-timing-function: ease-in;
+  }
+
+  91.25% {
+    background: #1386d2;
+    -moz-transform: rotateY(90deg);
+    -ms-transform: rotateY(90deg);
+    -webkit-transform: rotateY(90deg);
+    transform: rotateY(90deg);
+    -moz-animation-timing-function: step-start;
+    -webkit-animation-timing-function: step-start;
+    animation-timing-function: step-start;
+  }
+
+  91.26% {
+    background: #ff430d;
+    -moz-transform: rotateY(90deg);
+    -ms-transform: rotateY(90deg);
+    -webkit-transform: rotateY(90deg);
+    transform: rotateY(90deg);
+    -moz-animation-timing-function: ease-in;
+    -webkit-animation-timing-function: ease-in;
+    animation-timing-function: ease-in;
+  }
+
+  100% {
+    background: #f86;
+    -moz-transform: rotateY(0deg);
+    -ms-transform: rotateY(0deg);
+    -webkit-transform: rotateY(0deg);
+    transform: rotateY(0deg);
+    -moz-animation-timing-function: step-start;
+    -webkit-animation-timing-function: step-start;
+    animation-timing-function: step-start;
+  }
+}
+
+@keyframes plus-loader-bottom {
+  0% {
+    background: #fc6;
+    -moz-animation-timing-function: step-start;
+    -webkit-animation-timing-function: step-start;
+    animation-timing-function: step-start;
+  }
+
+  50% {
+    background: #fc6;
+    -moz-animation-timing-function: step-start;
+    -webkit-animation-timing-function: step-start;
+    animation-timing-function: step-start;
+  }
+
+  75% {
+    background: #4ae;
+    -moz-animation-timing-function: step-start;
+    -webkit-animation-timing-function: step-start;
+    animation-timing-function: step-start;
+  }
+
+  100% {
+    background: #4ae;
+    -moz-animation-timing-function: step-start;
+    -webkit-animation-timing-function: step-start;
+    animation-timing-function: step-start;
+  }
+}
+
+@keyframes plus-loader-background {
+  0% {
+    background: #f86;
+    -moz-transform: rotateZ(180deg);
+    -ms-transform: rotateZ(180deg);
+    -webkit-transform: rotateZ(180deg);
+    transform: rotateZ(180deg);
+  }
+
+  25% {
+    background: #f86;
+    -moz-transform: rotateZ(180deg);
+    -ms-transform: rotateZ(180deg);
+    -webkit-transform: rotateZ(180deg);
+    transform: rotateZ(180deg);
+    -moz-animation-timing-function: step-start;
+    -webkit-animation-timing-function: step-start;
+    animation-timing-function: step-start;
+  }
+
+  27.5% {
+    background: #6d7;
+    -moz-transform: rotateZ(90deg);
+    -ms-transform: rotateZ(90deg);
+    -webkit-transform: rotateZ(90deg);
+    transform: rotateZ(90deg);
+  }
+
+  50% {
+    background: #6d7;
+    -moz-transform: rotateZ(90deg);
+    -ms-transform: rotateZ(90deg);
+    -webkit-transform: rotateZ(90deg);
+    transform: rotateZ(90deg);
+    -moz-animation-timing-function: step-start;
+    -webkit-animation-timing-function: step-start;
+    animation-timing-function: step-start;
+  }
+
+  52.5% {
+    background: #6d7;
+    -moz-transform: rotateZ(0deg);
+    -ms-transform: rotateZ(0deg);
+    -webkit-transform: rotateZ(0deg);
+    transform: rotateZ(0deg);
+  }
+
+  75% {
+    background: #6d7;
+    -moz-transform: rotateZ(0deg);
+    -ms-transform: rotateZ(0deg);
+    -webkit-transform: rotateZ(0deg);
+    transform: rotateZ(0deg);
+    -moz-animation-timing-function: step-start;
+    -webkit-animation-timing-function: step-start;
+    animation-timing-function: step-start;
+  }
+
+  77.5% {
+    background: #f86;
+    -moz-transform: rotateZ(270deg);
+    -ms-transform: rotateZ(270deg);
+    -webkit-transform: rotateZ(270deg);
+    transform: rotateZ(270deg);
+  }
+
+  100% {
+    background: #f86;
+    -moz-transform: rotateZ(270deg);
+    -ms-transform: rotateZ(270deg);
+    -webkit-transform: rotateZ(270deg);
+    transform: rotateZ(270deg);
+    -moz-animation-timing-function: step-start;
+    -webkit-animation-timing-function: step-start;
+    animation-timing-function: step-start;
+  }
+}
diff --git a/src/styles/themes/default.scss b/src/styles/themes/default.scss
new file mode 100644
index 0000000..7061af3
--- /dev/null
+++ b/src/styles/themes/default.scss
@@ -0,0 +1 @@
+/* 缁胯崼鑽夊満涓婚銆佽崳鑰�鍏歌棌涓婚銆佹殫榛戜箣瀛愪富棰樺姞QQ璁ㄨ缇�972435319銆�1139183756鍚庣鑱婄兢涓昏幏鍙栵紝鑾峰彇鍚庡皢涓婚鏀惧埌themes鏂囦欢澶规牴鐩綍鍗冲彲 */
diff --git a/src/styles/transition.scss b/src/styles/transition.scss
new file mode 100644
index 0000000..91409e5
--- /dev/null
+++ b/src/styles/transition.scss
@@ -0,0 +1,19 @@
+/**
+ * @author chuzhixin 1204505056@qq.com 锛堜笉鎯充繚鐣檃uthor鍙垹闄わ級
+ * @description vue杩囨浮鍔ㄧ敾
+ */
+
+@charset "utf-8";
+
+.fade-transform-leave-active,
+.fade-transform-enter-active {
+  transition: $base-transition;
+}
+
+.fade-transform-enter {
+  opacity: 0;
+}
+
+.fade-transform-leave-to {
+  opacity: 0;
+}
diff --git a/src/styles/vab.scss b/src/styles/vab.scss
new file mode 100644
index 0000000..c261841
--- /dev/null
+++ b/src/styles/vab.scss
@@ -0,0 +1,295 @@
+/**
+ * @author chuzhixin 1204505056@qq.com 锛堜笉鎯充繚鐣檃uthor鍙垹闄わ級
+ * @description 鍏ㄥ眬鏍峰紡
+ */
+
+@charset "utf-8";
+
+@import './normalize.scss';
+@import './transition.scss';
+@import './loading.scss';
+$base: '.vab';
+
+@mixin scrollbar {
+  max-height: 88vh;
+  margin-bottom: 0.5vh;
+  overflow-y: auto;
+
+  &::-webkit-scrollbar {
+    width: 0;
+    height: 0;
+    background: transparent;
+  }
+
+  &::-webkit-scrollbar-thumb {
+    background-color: rgba(144, 147, 153, 0.3);
+    border-radius: 10px;
+  }
+
+  &::-webkit-scrollbar-thumb:hover {
+    background-color: rgba(144, 147, 153, 0.3);
+  }
+}
+
+@mixin base-scrollbar {
+  &::-webkit-scrollbar {
+    width: 13px;
+    height: 13px;
+  }
+
+  &::-webkit-scrollbar-thumb {
+    background-color: rgba(0, 0, 0, 0.4);
+    background-clip: padding-box;
+    border: 3px solid transparent;
+    border-radius: 7px;
+  }
+
+  &::-webkit-scrollbar-thumb:hover {
+    background-color: rgba(0, 0, 0, 0.5);
+  }
+
+  &::-webkit-scrollbar-track {
+    background-color: transparent;
+  }
+
+  &::-webkit-scrollbar-track:hover {
+    background-color: #f8fafc;
+  }
+}
+
+img {
+  object-fit: cover;
+}
+
+a {
+  color: $base-color-blue;
+  text-decoration: none;
+  cursor: pointer;
+}
+
+* {
+  transition: $base-transition;
+}
+svg {
+  transition: none;
+  * {
+    transition: none;
+  }
+}
+
+html {
+  body {
+    position: relative;
+    height: 100vh;
+    padding: 0;
+    margin: 0;
+    font-family: Avenir, Helvetica, Arial, sans-serif;
+    font-size: $base-font-size-default;
+    color: #2c3e50;
+    background: #f6f8f9;
+    -webkit-font-smoothing: antialiased;
+    -moz-osx-font-smoothing: grayscale;
+
+    @include base-scrollbar;
+
+    div {
+      @include base-scrollbar;
+    }
+
+    svg,
+    i {
+      &:hover {
+        opacity: 0.8;
+      }
+    }
+
+    .v-modal {
+      backdrop-filter: blur(10px);
+    }
+
+    /* el-tag寮�濮� */
+    .el-tag + .el-tag {
+      margin-left: 10px;
+    }
+
+    /* el-tag缁撴潫 */
+
+    /* markdown缂栬緫鍣ㄥ紑濮� */
+    .editor-toolbar {
+      .no-mobile,
+      .fa-question-circle {
+        display: none;
+      }
+    }
+
+    /* markdown缂栬緫鍣ㄧ粨鏉� */
+
+    /* 闂撮殧绾垮紑濮� */
+    .el-divider--horizontal {
+      margin: 10px 0 25px 0;
+
+      .el-divider__text {
+        display: -webkit-box;
+        overflow: hidden;
+        text-overflow: ellipsis;
+        -webkit-line-clamp: 1;
+        -webkit-box-orient: vertical;
+      }
+    }
+
+    /* 闂撮殧绾跨粨鏉� */
+
+    /* 澶у浘灞曠ず寮�濮� */
+    .el-image-viewer {
+      &__close {
+        .el-icon-circle-close {
+          color: $base-color-white;
+        }
+      }
+    }
+
+    /* 澶у浘灞曠ず缁撴潫 */
+
+    .vue-admin-beautiful-wrapper {
+      .app-main-container {
+        @include base-scrollbar;
+
+        > [class*='-container'] {
+          * {
+            transition: none;
+          }
+          padding: $base-padding;
+          background: $base-color-white;
+        }
+      }
+    }
+
+    /* 杩涘害鏉″紑濮� */
+    #nprogress {
+      position: fixed;
+      z-index: $base-z-index;
+
+      .bar {
+        background: $base-color-blue !important;
+      }
+
+      .peg {
+        box-shadow: 0 0 10px $base-color-blue, 0 0 5px $base-color-blue !important;
+      }
+    }
+
+    /* 杩涘害鏉$粨鏉� */
+
+    /* 琛ㄦ牸寮�濮� */
+
+    .el-table {
+      .el-table__body-wrapper {
+        @include base-scrollbar;
+      }
+
+      th {
+        background: #f5f7fa;
+      }
+
+      td,
+      th {
+        position: relative;
+        box-sizing: border-box;
+        padding: 7.5px 0;
+
+        .cell {
+          font-size: $base-font-size-default;
+          font-weight: normal;
+          color: #606266;
+
+          .el-image {
+            width: 50px;
+            height: 50px;
+            border-radius: $base-border-radius;
+          }
+        }
+      }
+    }
+
+    /* 琛ㄦ牸缁撴潫 */
+
+    /* 鍒嗛〉寮�濮� */
+    .el-pagination {
+      padding: 2px 5px;
+      margin: 15px 0 0 0;
+      font-weight: normal;
+      color: $base-color-black;
+      text-align: center;
+    }
+
+    /* 鍒嗛〉缁撴潫 */
+
+    /* 鑿滃崟寮�濮� */
+    .el-menu.el-menu--popup.el-menu--popup-right-start {
+      @include scrollbar;
+    }
+
+    .el-menu.el-menu--popup.el-menu--popup-bottom-start {
+      @include scrollbar;
+    }
+
+    .el-submenu__title i {
+      color: $base-color-white;
+    }
+
+    /* 鑿滃崟缁撴潫 */
+
+    /* 寮圭獥寮�濮� */
+
+    .el-dialog,
+    .el-message-box {
+      &__body {
+        border-top: 1px solid $base-border-color;
+
+        .el-form {
+          padding-right: 30px;
+        }
+      }
+
+      &__footer {
+        padding: $base-padding;
+        text-align: right;
+        border-top: 1px solid $base-border-color;
+      }
+
+      &__content {
+        padding: 20px 20px 20px 20px;
+      }
+    }
+
+    /* 寮圭獥缁撴潫 */
+
+    /* 鍗$墖寮�濮� */
+    .el-card {
+      margin-bottom: 15px;
+
+      &__body {
+        padding: $base-padding;
+      }
+    }
+
+    /* 鍗$墖缁撴潫 */
+
+    /* 涓嬫媺鏍戞牱寮�-----------寮�濮� */
+    .select-tree-popper {
+      .el-scrollbar {
+        .el-scrollbar__view {
+          .el-select-dropdown__item {
+            height: auto;
+            max-height: 274px;
+            padding: 0;
+            overflow-y: auto;
+            line-height: 26px;
+          }
+        }
+      }
+    }
+
+    /* 涓嬫媺鏍戞牱寮�-----------缁撴潫 */
+  }
+}
diff --git a/src/styles/variables.scss b/src/styles/variables.scss
new file mode 100644
index 0000000..f51cf75
--- /dev/null
+++ b/src/styles/variables.scss
@@ -0,0 +1,83 @@
+/**
+ * @author chuzhixin 1204505056@qq.com 锛堜笉鎯充繚鐣檃uthor鍙垹闄わ級
+ * @description 鍏ㄥ眬涓婚鍙橀噺閰嶇疆
+ */
+/* stylelint-disable */
+@charset "utf-8";
+//妗嗘灦榛樿涓婚鑹�
+$base-color-default: #41b584;
+//榛樿灞傜骇
+$base-z-index: 999;
+//妯悜甯冨眬绾靛悜甯冨眬鏃惰彍鍗曡儗鏅壊
+$base-menu-background: #282c34;
+//鑿滃崟鏂囧瓧棰滆壊
+$base-menu-color: hsla(0, 0%, 100%, 0.95);
+//鑿滃崟閫変腑鏂囧瓧棰滆壊
+$base-menu-color-active: hsla(0, 0%, 100%, 0.95);
+//鑿滃崟閫変腑鑳屾櫙鑹�
+$base-menu-background-active: $base-color-default;
+//鏍囬棰滆壊
+$base-title-color: #fff;
+//瀛椾綋澶у皬閰嶇疆
+$base-font-size-small: 12px;
+$base-font-size-default: 14px;
+$base-font-size-big: 16px;
+$base-font-size-bigger: 18px;
+$base-font-size-max: 22px;
+$base-font-color: #606266;
+$base-color-blue: $base-color-default;
+$base-color-green: #47ba80;
+$base-color-white: #fff;
+$base-color-black: #000;
+$base-color-yellow: #fac858;
+$base-color-orange: #ff6700;
+$base-color-red: #f34d37;
+$base-color-gray: rgba(0, 0, 0, 0.65);
+$base-main-width: 1279px;
+$base-border-radius: 4px;
+$base-border-color: #dcdfe6;
+//杈撳叆妗嗛珮搴�
+$base-input-height: 32px;
+//榛樿paddiing
+$base-padding: 20px;
+//榛樿闃村奖
+$base-box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
+//妯悜甯冨眬鏃秚op-bar銆乴ogo銆佷竴绾ц彍鍗曠殑楂樺害
+$base-top-bar-height: 65px;
+//绾靛悜甯冨眬鏃秎ogo鐨勯珮搴�
+$base-logo-height: 75px;
+//椤堕儴nav-bar鐨勯珮搴�
+$base-nav-bar-height: 60px;
+//椤堕儴澶氭爣绛鹃〉tabs-bar鐨勯珮搴�
+$base-tabs-bar-height: 55px;
+//椤堕儴澶氭爣绛鹃〉tabs-bar涓瘡涓�涓猧tem鐨勯珮搴�
+$base-tag-item-height: 34px;
+//鑿滃崟li鏍囩鐨勯珮搴�
+$base-menu-item-height: 50px;
+//app-main鐨勯珮搴�
+$base-app-main-height: calc(
+  100vh - #{$base-nav-bar-height} - #{$base-tabs-bar-height} - #{$base-padding} -
+    #{$base-padding} - 55px - 55px
+);
+//绾靛悜甯冨眬鏃跺乏渚у鑸湭鎶樺彔鏃剁殑瀹藉害
+$base-left-menu-width: 256px;
+//绾靛悜甯冨眬鏃跺乏渚у鑸湭鎶樺彔鏃跺彸渚у唴瀹圭殑瀹藉害
+$base-right-content-width: calc(100% - #{$base-left-menu-width});
+//绾靛悜甯冨眬鏃跺乏渚у鑸凡鎶樺彔鏃剁殑瀹藉害
+$base-left-menu-width-min: 65px;
+//绾靛悜甯冨眬鏃跺乏渚у鑸凡鎶樺彔鏃跺彸渚у唴瀹圭殑瀹藉害
+$base-right-content-width-min: calc(100% - #{$base-left-menu-width-min});
+//榛樿鍔ㄧ敾
+$base-transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1), border 0s,
+  background 0s, color 0s, font-size 0s;
+//榛樿鍔ㄧ敾闀�
+$base-transition-time: 0.3s;
+
+:export {
+  //鑿滃崟鏂囧瓧棰滆壊鍙橀噺瀵煎嚭
+  menu-color: $base-menu-color;
+  //鑿滃崟閫変腑鏂囧瓧棰滆壊鍙橀噺瀵煎嚭
+  menu-color-active: $base-menu-color-active;
+  //鑿滃崟鑳屾櫙鑹插彉閲忓鍑�
+  menu-background: $base-menu-background;
+}
diff --git a/src/utils/accessToken.js b/src/utils/accessToken.js
new file mode 100644
index 0000000..1c79c78
--- /dev/null
+++ b/src/utils/accessToken.js
@@ -0,0 +1,59 @@
+import { storage, tokenTableName } from '@/config'
+
+/**
+ * @author chuzhixin 1204505056@qq.com 锛堜笉鎯充繚鐣檃uthor鍙垹闄わ級
+ * @description 鑾峰彇accessToken
+ * @returns {string|ActiveX.IXMLDOMNode|Promise<any>|any|IDBRequest<any>|MediaKeyStatus|FormDataEntryValue|Function|Promise<Credential | null>}
+ */
+export function getAccessToken() {
+  if (storage) {
+    if ('localStorage' === storage) {
+      return localStorage.getItem(tokenTableName)
+    } else if ('sessionStorage' === storage) {
+      return sessionStorage.getItem(tokenTableName)
+    } else {
+      return localStorage.getItem(tokenTableName)
+    }
+  } else {
+    return localStorage.getItem(tokenTableName)
+  }
+}
+
+/**
+ * @author chuzhixin 1204505056@qq.com 锛堜笉鎯充繚鐣檃uthor鍙垹闄わ級
+ * @description 瀛樺偍accessToken
+ * @param accessToken
+ * @returns {void|*}
+ */
+export function setAccessToken(accessToken) {
+  if (storage) {
+    if ('localStorage' === storage) {
+      return localStorage.setItem(tokenTableName, accessToken)
+    } else if ('sessionStorage' === storage) {
+      return sessionStorage.setItem(tokenTableName, accessToken)
+    } else {
+      return localStorage.setItem(tokenTableName, accessToken)
+    }
+  } else {
+    return localStorage.setItem(tokenTableName, accessToken)
+  }
+}
+
+/**
+ * @author chuzhixin 1204505056@qq.com 锛堜笉鎯充繚鐣檃uthor鍙垹闄わ級
+ * @description 绉婚櫎accessToken
+ * @returns {void|Promise<void>}
+ */
+export function removeAccessToken() {
+  if (storage) {
+    if ('localStorage' === storage) {
+      return localStorage.removeItem(tokenTableName)
+    } else if ('sessionStorage' === storage) {
+      return sessionStorage.clear()
+    } else {
+      return localStorage.removeItem(tokenTableName)
+    }
+  } else {
+    return localStorage.removeItem(tokenTableName)
+  }
+}
diff --git a/src/utils/clipboard.js b/src/utils/clipboard.js
new file mode 100644
index 0000000..20a19b7
--- /dev/null
+++ b/src/utils/clipboard.js
@@ -0,0 +1,31 @@
+import Vue from 'vue'
+import Clipboard from 'clipboard'
+
+function clipboardSuccess() {
+  Vue.prototype.$baseMessage('澶嶅埗鎴愬姛', 'success')
+}
+
+function clipboardError() {
+  Vue.prototype.$baseMessage('澶嶅埗澶辫触', 'error')
+}
+
+/**
+ * @author chuzhixin 1204505056@qq.com 锛堜笉鎯充繚鐣檃uthor鍙垹闄わ級
+ * @description 澶嶅埗鏁版嵁
+ * @param text
+ * @param event
+ */
+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/src/utils/errorLog.js b/src/utils/errorLog.js
new file mode 100644
index 0000000..07a836c
--- /dev/null
+++ b/src/utils/errorLog.js
@@ -0,0 +1,25 @@
+import Vue from 'vue'
+import store from '@/store'
+import { isArray, isString } from '@/utils/validate'
+import { errorLog } from '@/config'
+
+const needErrorLog = errorLog
+const 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 = (err, vm, info) => {
+    console.error('vue-admin-beautiful閿欒鎷︽埅:', err, vm, info)
+    const url = window.location.href
+    Vue.nextTick(() => {
+      store.dispatch('errorLog/addErrorLog', { err, vm, info, url })
+    })
+  }
+}
diff --git a/src/utils/handleRoutes.js b/src/utils/handleRoutes.js
new file mode 100644
index 0000000..c95ae92
--- /dev/null
+++ b/src/utils/handleRoutes.js
@@ -0,0 +1,63 @@
+/**
+ * @author chuzhixin 1204505056@qq.com 锛堜笉鎯充繚鐣檃uthor鍙垹闄わ級
+ * @description all妯″紡娓叉煋鍚庣杩斿洖璺敱
+ * @param constantRoutes
+ * @returns {*}
+ */
+export function convertRouter(asyncRoutes) {
+  return asyncRoutes.map((route) => {
+    if (route.component) {
+      if (route.component === 'Layout') {
+        route.component = (resolve) => require(['@/layouts'], resolve)
+      } else if (route.component === 'EmptyLayout') {
+        route.component = (resolve) =>
+          require(['@/layouts/EmptyLayout'], resolve)
+      } else {
+        const index = route.component.indexOf('views')
+        const path =
+          index > 0 ? route.component.slice(index) : `views/${route.component}`
+        route.component = (resolve) => require([`@/${path}`], resolve)
+      }
+    }
+    if (route.children && route.children.length)
+      route.children = convertRouter(route.children)
+    if (route.children && route.children.length === 0) delete route.children
+    return route
+  })
+}
+
+/**
+ * @author chuzhixin 1204505056@qq.com 锛堜笉鎯充繚鐣檃uthor鍙垹闄わ級
+ * @description 鍒ゆ柇褰撳墠璺敱鏄惁鍖呭惈鏉冮檺
+ * @param permissions
+ * @param route
+ * @returns {boolean|*}
+ */
+function hasPermission(permissions, route) {
+  if (route.meta && route.meta.permissions) {
+    return permissions.some((role) => route.meta.permissions.includes(role))
+  } else {
+    return true
+  }
+}
+
+/**
+ * @author chuzhixin 1204505056@qq.com 锛堜笉鎯充繚鐣檃uthor鍙垹闄わ級
+ * @description intelligence妯″紡鏍规嵁permissions鏁扮粍鎷︽埅璺敱
+ * @param routes
+ * @param permissions
+ * @returns {[]}
+ */
+export function filterAsyncRoutes(routes, permissions) {
+  const finallyRoutes = []
+  routes.forEach((route) => {
+    const item = { ...route }
+    if (hasPermission(permissions, item)) {
+      if (item.children) {
+        item.children = filterAsyncRoutes(item.children, permissions)
+      }
+      finallyRoutes.push(item)
+    }
+  })
+  return finallyRoutes
+}
diff --git a/src/utils/index.js b/src/utils/index.js
new file mode 100644
index 0000000..80e89c1
--- /dev/null
+++ b/src/utils/index.js
@@ -0,0 +1,266 @@
+/**
+ * @author chuzhixin 1204505056@qq.com 锛堜笉鎯充繚鐣檃uthor鍙垹闄わ級
+ * @description 鏍煎紡鍖栨椂闂�
+ * @param time
+ * @param cFormat
+ * @returns {string|null}
+ */
+export function parseTime(time, cFormat) {
+  if (arguments.length === 0) {
+    return null
+  }
+  const format = cFormat || '{y}-{m}-{d} {h}:{i}:{s}'
+  let date
+  if (typeof time === 'object') {
+    date = time
+  } else {
+    if (typeof time === 'string' && /^[0-9]+$/.test(time)) {
+      time = parseInt(time)
+    }
+    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(/{(y|m|d|h|i|s|a)+}/g, (result, key) => {
+    let value = formatObj[key]
+    if (key === 'a') {
+      return ['鏃�', '涓�', '浜�', '涓�', '鍥�', '浜�', '鍏�'][value]
+    }
+    if (result.length > 0 && value < 10) {
+      value = '0' + value
+    }
+    return value || 0
+  })
+  return time_str
+}
+
+/**
+ * @author chuzhixin 1204505056@qq.com 锛堜笉鎯充繚鐣檃uthor鍙垹闄わ級
+ * @description 鏍煎紡鍖栨椂闂�
+ * @param time
+ * @param 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() +
+      '鍒�'
+    )
+  }
+}
+
+/**
+ * @author chuzhixin 1204505056@qq.com 锛堜笉鎯充繚鐣檃uthor鍙垹闄わ級
+ * @description 灏唘rl璇锋眰鍙傛暟杞负json鏍煎紡
+ * @param url
+ * @returns {{}|any}
+ */
+export function paramObj(url) {
+  const search = url.split('?')[1]
+  if (!search) {
+    return {}
+  }
+  return JSON.parse(
+    '{"' +
+      decodeURIComponent(search)
+        .replace(/"/g, '\\"')
+        .replace(/&/g, '","')
+        .replace(/=/g, '":"')
+        .replace(/\+/g, ' ') +
+      '"}'
+  )
+}
+
+/**
+ * @author chuzhixin 1204505056@qq.com 锛堜笉鎯充繚鐣檃uthor鍙垹闄わ級
+ * @description 鐖跺瓙鍏崇郴鐨勬暟缁勮浆鎹㈡垚鏍戝舰缁撴瀯鏁版嵁
+ * @param data
+ * @returns {*}
+ */
+export function translateDataToTree(data) {
+  const parent = data.filter(
+    (value) => value.parentId === 'undefined' || value.parentId == null
+  )
+  const children = data.filter(
+    (value) => value.parentId !== 'undefined' && value.parentId != null
+  )
+  const translator = (parent, children) => {
+    parent.forEach((parent) => {
+      children.forEach((current, index) => {
+        if (current.parentId === parent.id) {
+          const temp = JSON.parse(JSON.stringify(children))
+          temp.splice(index, 1)
+          translator([current], temp)
+          typeof parent.children !== 'undefined'
+            ? parent.children.push(current)
+            : (parent.children = [current])
+        }
+      })
+    })
+  }
+  translator(parent, children)
+  return parent
+}
+
+/**
+ * @author chuzhixin 1204505056@qq.com 锛堜笉鎯充繚鐣檃uthor鍙垹闄わ級
+ * @description 鏍戝舰缁撴瀯鏁版嵁杞崲鎴愮埗瀛愬叧绯荤殑鏁扮粍
+ * @param data
+ * @returns {[]}
+ */
+export function translateTreeToData(data) {
+  const result = []
+  data.forEach((item) => {
+    const loop = (data) => {
+      result.push({
+        id: data.id,
+        name: data.name,
+        parentId: data.parentId,
+      })
+      const child = data.children
+      if (child) {
+        for (let i = 0; i < child.length; i++) {
+          loop(child[i])
+        }
+      }
+    }
+    loop(item)
+  })
+  return result
+}
+
+/**
+ * @author chuzhixin 1204505056@qq.com 锛堜笉鎯充繚鐣檃uthor鍙垹闄わ級
+ * @description 10浣嶆椂闂存埑杞崲
+ * @param time
+ * @returns {string}
+ */
+export function tenBitTimestamp(time) {
+  const date = new Date(time * 1000)
+  const y = date.getFullYear()
+  let m = date.getMonth() + 1
+  m = m < 10 ? '' + m : m
+  let d = date.getDate()
+  d = d < 10 ? '' + d : d
+  let h = date.getHours()
+  h = h < 10 ? '0' + h : h
+  let minute = date.getMinutes()
+  let second = date.getSeconds()
+  minute = minute < 10 ? '0' + minute : minute
+  second = second < 10 ? '0' + second : second
+  return y + '骞�' + m + '鏈�' + d + '鏃� ' + h + ':' + minute + ':' + second //缁勫悎
+}
+
+/**
+ * @author chuzhixin 1204505056@qq.com 锛堜笉鎯充繚鐣檃uthor鍙垹闄わ級
+ * @description 13浣嶆椂闂存埑杞崲
+ * @param time
+ * @returns {string}
+ */
+export function thirteenBitTimestamp(time) {
+  const date = new Date(time / 1)
+  const y = date.getFullYear()
+  let m = date.getMonth() + 1
+  m = m < 10 ? '' + m : m
+  let d = date.getDate()
+  d = d < 10 ? '' + d : d
+  let h = date.getHours()
+  h = h < 10 ? '0' + h : h
+  let minute = date.getMinutes()
+  let second = date.getSeconds()
+  minute = minute < 10 ? '0' + minute : minute
+  second = second < 10 ? '0' + second : second
+  return y + '骞�' + m + '鏈�' + d + '鏃� ' + h + ':' + minute + ':' + second //缁勫悎
+}
+
+/**
+ * @author chuzhixin 1204505056@qq.com 锛堜笉鎯充繚鐣檃uthor鍙垹闄わ級
+ * @description 鑾峰彇闅忔満id
+ * @param length
+ * @returns {string}
+ */
+export function uuid(length = 32) {
+  const num = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890'
+  let str = ''
+  for (let i = 0; i < length; i++) {
+    str += num.charAt(Math.floor(Math.random() * num.length))
+  }
+  return str
+}
+
+/**
+ * @author chuzhixin 1204505056@qq.com 锛堜笉鎯充繚鐣檃uthor鍙垹闄わ級
+ * @description m鍒皀鐨勯殢鏈烘暟
+ * @param m
+ * @param n
+ * @returns {number}
+ */
+export function random(m, n) {
+  return Math.floor(Math.random() * (m - n) + n)
+}
+
+/**
+ * @author chuzhixin 1204505056@qq.com 锛堜笉鎯充繚鐣檃uthor鍙垹闄わ級
+ * @description addEventListener
+ * @type {function(...[*]=)}
+ */
+export const on = (function () {
+  return function (element, event, handler, useCapture = false) {
+    if (element && event && handler) {
+      element.addEventListener(event, handler, useCapture)
+    }
+  }
+})()
+
+/**
+ * @author chuzhixin 1204505056@qq.com 锛堜笉鎯充繚鐣檃uthor鍙垹闄わ級
+ * @description removeEventListener
+ * @type {function(...[*]=)}
+ */
+export const off = (function () {
+  return function (element, event, handler, useCapture = false) {
+    if (element && event) {
+      element.removeEventListener(event, handler, useCapture)
+    }
+  }
+})()
diff --git a/src/utils/pageTitle.js b/src/utils/pageTitle.js
new file mode 100644
index 0000000..38334c3
--- /dev/null
+++ b/src/utils/pageTitle.js
@@ -0,0 +1,14 @@
+import { title } from '@/config'
+
+/**
+ * @author chuzhixin 1204505056@qq.com 锛堜笉鎯充繚鐣檃uthor鍙垹闄わ級
+ * @description 璁剧疆鏍囬
+ * @param pageTitle
+ * @returns {string}
+ */
+export default function getPageTitle(pageTitle) {
+  if (pageTitle) {
+    return `${pageTitle}-${title}`
+  }
+  return `${title}`
+}
diff --git a/src/utils/permission.js b/src/utils/permission.js
new file mode 100644
index 0000000..88b3506
--- /dev/null
+++ b/src/utils/permission.js
@@ -0,0 +1,20 @@
+import store from '@/store'
+
+/**
+ * @author chuzhixin 1204505056@qq.com 锛堜笉鎯充繚鐣檃uthor鍙垹闄わ級
+ * @description 妫�鏌ユ潈闄�
+ * @param value
+ * @returns {boolean}
+ */
+export default function checkPermission(value) {
+  if (value && value instanceof Array && value.length > 0) {
+    const permissions = store.getters['user/permissions']
+    const permissionPermissions = value
+
+    return permissions.some((role) => {
+      return permissionPermissions.includes(role)
+    })
+  } else {
+    return false
+  }
+}
diff --git a/src/utils/request.js b/src/utils/request.js
new file mode 100644
index 0000000..272a731
--- /dev/null
+++ b/src/utils/request.js
@@ -0,0 +1,125 @@
+import Vue from 'vue'
+import axios from 'axios'
+import {
+  baseURL,
+  contentType,
+  debounce,
+  invalidCode,
+  noPermissionCode,
+  requestTimeout,
+  successCode,
+  tokenName,
+  loginInterception,
+} from '@/config'
+import store from '@/store'
+import qs from 'qs'
+import router from '@/router'
+import { isArray } from '@/utils/validate'
+
+let loadingInstance
+
+/**
+ * @author chuzhixin 1204505056@qq.com 锛堜笉鎯充繚鐣檃uthor鍙垹闄わ級
+ * @description 澶勭悊code寮傚父
+ * @param {*} code
+ * @param {*} msg
+ */
+const handleCode = (code, msg) => {
+  switch (code) {
+    case invalidCode:
+      Vue.prototype.$baseMessage(msg || `鍚庣鎺ュ彛${code}寮傚父`, 'error')
+      store.dispatch('user/resetAccessToken').catch(() => {})
+      if (loginInterception) {
+        location.reload()
+      }
+      break
+    case noPermissionCode:
+      router.push({ path: '/401' }).catch(() => {})
+      break
+    default:
+      Vue.prototype.$baseMessage(msg || `鍚庣鎺ュ彛${code}寮傚父`, 'error')
+      break
+  }
+}
+
+const instance = axios.create({
+  baseURL,
+  timeout: requestTimeout,
+  headers: {
+    'Content-Type': contentType,
+  },
+})
+
+instance.interceptors.request.use(
+  (config) => {
+    if (store.getters['user/accessToken']) {
+      config.headers[tokenName] = store.getters['user/accessToken']
+    }
+    //杩欓噷浼氳繃婊ゆ墍鏈変负绌恒��0銆乫alse鐨刱ey锛屽鏋滀笉闇�瑕佽鑷娉ㄩ噴
+    if (config.data)
+      config.data = Vue.prototype.$baseLodash.pickBy(
+        config.data,
+        Vue.prototype.$baseLodash.identity
+      )
+    if (
+      config.data &&
+      config.headers['Content-Type'] ===
+        'application/x-www-form-urlencoded;charset=UTF-8'
+    )
+      config.data = qs.stringify(config.data)
+    if (debounce.some((item) => config.url.includes(item)))
+      loadingInstance = Vue.prototype.$baseLoading()
+    return config
+  },
+  (error) => {
+    return Promise.reject(error)
+  }
+)
+
+instance.interceptors.response.use(
+  (response) => {
+    if (loadingInstance) loadingInstance.close()
+
+    const { data, config } = response
+    const { code, msg } = data
+    // 鎿嶄綔姝e父Code鏁扮粍
+    const codeVerificationArray = isArray(successCode)
+      ? [...successCode]
+      : [...[successCode]]
+    // 鏄惁鎿嶄綔姝e父
+    if (codeVerificationArray.includes(code)) {
+      return data
+    } else {
+      handleCode(code, msg)
+      return Promise.reject(
+        'vue-admin-beautiful璇锋眰寮傚父鎷︽埅:' +
+          JSON.stringify({ url: config.url, code, msg }) || 'Error'
+      )
+    }
+  },
+  (error) => {
+    if (loadingInstance) loadingInstance.close()
+    const { response, message } = error
+    if (error.response && error.response.data) {
+      const { status, data } = response
+      handleCode(status, data.msg || message)
+      return Promise.reject(error)
+    } else {
+      let { message } = error
+      if (message === 'Network Error') {
+        message = '鍚庣鎺ュ彛杩炴帴寮傚父'
+      }
+      if (message.includes('timeout')) {
+        message = '鍚庣鎺ュ彛璇锋眰瓒呮椂'
+      }
+      if (message.includes('Request failed with status code')) {
+        const code = message.substr(message.length - 3)
+        message = '鍚庣鎺ュ彛' + code + '寮傚父'
+      }
+      Vue.prototype.$baseMessage(message || `鍚庣鎺ュ彛鏈煡寮傚父`, 'error')
+      return Promise.reject(error)
+    }
+  }
+)
+
+export default instance
diff --git a/src/utils/static.js b/src/utils/static.js
new file mode 100644
index 0000000..bd0abd9
--- /dev/null
+++ b/src/utils/static.js
@@ -0,0 +1,52 @@
+/**
+ * @author chuzhixin 1204505056@qq.com
+ * @description 瀵煎叆鎵�鏈� controller 妯″潡锛屾祻瑙堝櫒鐜涓嚜鍔ㄨ緭鍑篶ontroller鏂囦欢澶逛笅Mock鎺ュ彛锛岃鍕夸慨鏀广��
+ */
+import Mock from 'mockjs'
+import { paramObj } from '@/utils'
+
+const mocks = []
+const files = require.context('../../mock/controller', false, /\.js$/)
+
+files.keys().forEach((key) => {
+  mocks.push(...files(key))
+})
+
+export function mockXHR() {
+  Mock.XHR.prototype.proxy_send = Mock.XHR.prototype.send
+  Mock.XHR.prototype.send = function () {
+    if (this.custom.xhr) {
+      this.custom.xhr.withCredentials = this.withCredentials || false
+
+      if (this.responseType) {
+        this.custom.xhr.responseType = this.responseType
+      }
+    }
+    this.proxy_send(...arguments)
+  }
+
+  function XHRHttpRequst(respond) {
+    return function (options) {
+      let result
+      if (respond instanceof Function) {
+        const { body, type, url } = options
+        result = respond({
+          method: type,
+          body: JSON.parse(body),
+          query: paramObj(url),
+        })
+      } else {
+        result = respond
+      }
+      return Mock.mock(result)
+    }
+  }
+
+  mocks.forEach((item) => {
+    Mock.mock(
+      new RegExp(item.url),
+      item.type || 'get',
+      XHRHttpRequst(item.response)
+    )
+  })
+}
diff --git a/src/utils/vab.js b/src/utils/vab.js
new file mode 100644
index 0000000..e594e9a
--- /dev/null
+++ b/src/utils/vab.js
@@ -0,0 +1,180 @@
+import { loadingText, messageDuration, title } from '@/config'
+import * as lodash from 'lodash'
+import { Loading, Message, MessageBox, Notification } from 'element-ui'
+import store from '@/store'
+import { getAccessToken } from '@/utils/accessToken'
+
+const accessToken = store.getters['user/accessToken']
+const layout = store.getters['settings/layout']
+
+const install = (Vue, opts = {}) => {
+  /* 鍏ㄥ眬accessToken */
+  Vue.prototype.$baseAccessToken = () => {
+    return accessToken || getAccessToken()
+  }
+  /* 鍏ㄥ眬鏍囬 */
+  Vue.prototype.$baseTitle = (() => {
+    return title
+  })()
+  /* 鍏ㄥ眬鍔犺浇灞� */
+  Vue.prototype.$baseLoading = (index, text) => {
+    let loading
+    if (!index) {
+      loading = Loading.service({
+        lock: true,
+        text: text || loadingText,
+        background: 'hsla(0,0%,100%,.8)',
+      })
+    } else {
+      loading = Loading.service({
+        lock: true,
+        text: text || loadingText,
+        spinner: 'vab-loading-type' + index,
+        background: 'hsla(0,0%,100%,.8)',
+      })
+    }
+    return loading
+  }
+  /* 鍏ㄥ眬澶氬僵鍔犺浇灞� */
+  Vue.prototype.$baseColorfullLoading = (index, text) => {
+    let loading
+    if (!index) {
+      loading = Loading.service({
+        lock: true,
+        text: text || loadingText,
+        spinner: 'dots-loader',
+        background: 'hsla(0,0%,100%,.8)',
+      })
+    } else {
+      switch (index) {
+        case 1:
+          index = 'dots'
+          break
+        case 2:
+          index = 'gauge'
+          break
+        case 3:
+          index = 'inner-circles'
+          break
+        case 4:
+          index = 'plus'
+          break
+      }
+      loading = Loading.service({
+        lock: true,
+        text: text || loadingText,
+        spinner: index + '-loader',
+        background: 'hsla(0,0%,100%,.8)',
+      })
+    }
+    return loading
+  }
+  /* 鍏ㄥ眬Message */
+  Vue.prototype.$baseMessage = (message, type) => {
+    Message({
+      offset: 60,
+      showClose: true,
+      message: message,
+      type: type,
+      dangerouslyUseHTMLString: true,
+      duration: messageDuration,
+    })
+  }
+
+  /* 鍏ㄥ眬Alert */
+  Vue.prototype.$baseAlert = (content, title, callback) => {
+    MessageBox.alert(content, title || '娓╅Θ鎻愮ず', {
+      confirmButtonText: '纭畾',
+      dangerouslyUseHTMLString: true,
+      callback: (action) => {
+        if (callback) {
+          callback()
+        }
+      },
+    })
+  }
+
+  /* 鍏ㄥ眬Confirm */
+  Vue.prototype.$baseConfirm = (content, title, callback1, callback2) => {
+    MessageBox.confirm(content, title || '娓╅Θ鎻愮ず', {
+      confirmButtonText: '纭畾',
+      cancelButtonText: '鍙栨秷',
+      closeOnClickModal: false,
+      type: 'warning',
+    })
+      .then(() => {
+        if (callback1) {
+          callback1()
+        }
+      })
+      .catch(() => {
+        if (callback2) {
+          callback2()
+        }
+      })
+  }
+
+  /* 鍏ㄥ眬Notification */
+  Vue.prototype.$baseNotify = (message, title, type, position) => {
+    Notification({
+      title: title,
+      message: message,
+      position: position || 'top-right',
+      type: type || 'success',
+      duration: messageDuration,
+    })
+  }
+
+  /* 鍏ㄥ眬TableHeight */
+  Vue.prototype.$baseTableHeight = (formType) => {
+    let height = window.innerHeight
+    let paddingHeight = 400
+    const formHeight = 50
+
+    if (layout === 'vertical') {
+      paddingHeight = 365
+    }
+
+    if ('number' == typeof formType) {
+      height = height - paddingHeight - formHeight * formType
+    } else {
+      height = height - paddingHeight
+    }
+    return height
+  }
+
+  /* 鍏ㄥ眬map鍥惧眰 */
+  Vue.prototype.$baseMap = () => {
+    return new maptalks.Map('map', {
+      center: [116.41348403785, 39.910843952376],
+      zoom: 12,
+      minZoom: 1,
+      maxZoom: 19,
+      spatialReference: {
+        projection: 'baidu',
+      },
+      attribution: {
+        content: '&copy; vue-admin-beautiful',
+      },
+      baseLayer: new maptalks.TileLayer('base', {
+        cssFilter: 'sepia(100%) invert(90%)',
+        urlTemplate:
+          'http://online{s}.map.bdimg.com/onlinelabel/?qt=tile&x={x}&y={y}&z={z}&styles=pl&scaler=1&p=1',
+        subdomains: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
+        attribution:
+          '&copy; <a target="_blank" href="http://map.baidu.com">Baidu</a>',
+      }),
+    })
+  }
+
+  /* 鍏ㄥ眬lodash */
+  Vue.prototype.$baseLodash = lodash
+  /* 鍏ㄥ眬浜嬩欢鎬荤嚎 */
+  Vue.prototype.$baseEventBus = new Vue()
+}
+
+if (typeof window !== 'undefined' && window.Vue) {
+  install(window.Vue)
+}
+
+export default install
diff --git a/src/utils/validate.js b/src/utils/validate.js
new file mode 100644
index 0000000..01100ed
--- /dev/null
+++ b/src/utils/validate.js
@@ -0,0 +1,217 @@
+/**
+ * @author chuzhixin 1204505056@qq.com 锛堜笉鎯充繚鐣檃uthor鍙垹闄わ級
+ * @description 鍒よ鏄惁涓哄閾�
+ * @param path
+ * @returns {boolean}
+ */
+export function isExternal(path) {
+  return /^(https?:|mailto:|tel:)/.test(path)
+}
+
+/**
+ * @author chuzhixin 1204505056@qq.com 锛堜笉鎯充繚鐣檃uthor鍙垹闄わ級
+ * @description 鏍¢獙瀵嗙爜鏄惁灏忎簬6浣�
+ * @param str
+ * @returns {boolean}
+ */
+export function isPassword(str) {
+  return str.length >= 6
+}
+
+/**
+ * @author chuzhixin 1204505056@qq.com 锛堜笉鎯充繚鐣檃uthor鍙垹闄わ級
+ * @description 鍒ゆ柇鏄惁涓烘暟瀛�
+ * @param value
+ * @returns {boolean}
+ */
+export function isNumber(value) {
+  const reg = /^[0-9]*$/
+  return reg.test(value)
+}
+
+/**
+ * @author chuzhixin 1204505056@qq.com 锛堜笉鎯充繚鐣檃uthor鍙垹闄わ級
+ * @description 鍒ゆ柇鏄惁鏄悕绉�
+ * @param value
+ * @returns {boolean}
+ */
+export function isName(value) {
+  const reg = /^[\u4e00-\u9fa5a-zA-Z0-9]+$/
+  return reg.test(value)
+}
+
+/**
+ * @author chuzhixin 1204505056@qq.com 锛堜笉鎯充繚鐣檃uthor鍙垹闄わ級
+ * @description 鍒ゆ柇鏄惁涓篒P
+ * @param ip
+ * @returns {boolean}
+ */
+export function isIP(ip) {
+  const reg =
+    /^(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])$/
+  return reg.test(ip)
+}
+
+/**
+ * @author chuzhixin 1204505056@qq.com 锛堜笉鎯充繚鐣檃uthor鍙垹闄わ級
+ * @description 鍒ゆ柇鏄惁鏄紶缁熺綉绔�
+ * @param url
+ * @returns {boolean}
+ */
+export function isUrl(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)
+}
+
+/**
+ * @author chuzhixin 1204505056@qq.com 锛堜笉鎯充繚鐣檃uthor鍙垹闄わ級
+ * @description 鍒ゆ柇鏄惁鏄皬鍐欏瓧姣�
+ * @param str
+ * @returns {boolean}
+ */
+export function isLowerCase(str) {
+  const reg = /^[a-z]+$/
+  return reg.test(str)
+}
+
+/**
+ * @author chuzhixin 1204505056@qq.com 锛堜笉鎯充繚鐣檃uthor鍙垹闄わ級
+ * @description 鍒ゆ柇鏄惁鏄ぇ鍐欏瓧姣�
+ * @param str
+ * @returns {boolean}
+ */
+export function isUpperCase(str) {
+  const reg = /^[A-Z]+$/
+  return reg.test(str)
+}
+
+/**
+ * @author chuzhixin 1204505056@qq.com 锛堜笉鎯充繚鐣檃uthor鍙垹闄わ級
+ * @description 鍒ゆ柇鏄惁鏄ぇ鍐欏瓧姣嶅紑澶�
+ * @param str
+ * @returns {boolean}
+ */
+export function isAlphabets(str) {
+  const reg = /^[A-Za-z]+$/
+  return reg.test(str)
+}
+
+/**
+ * @author chuzhixin 1204505056@qq.com 锛堜笉鎯充繚鐣檃uthor鍙垹闄わ級
+ * @description 鍒ゆ柇鏄惁鏄瓧绗︿覆
+ * @param str
+ * @returns {boolean}
+ */
+export function isString(str) {
+  return typeof str === 'string' || str instanceof String
+}
+
+/**
+ * @author chuzhixin 1204505056@qq.com 锛堜笉鎯充繚鐣檃uthor鍙垹闄わ級
+ * @description 鍒ゆ柇鏄惁鏄暟缁�
+ * @param arg
+ * @returns {arg is any[]|boolean}
+ */
+export function isArray(arg) {
+  if (typeof Array.isArray === 'undefined') {
+    return Object.prototype.toString.call(arg) === '[object Array]'
+  }
+  return Array.isArray(arg)
+}
+
+/**
+ * @author chuzhixin 1204505056@qq.com 锛堜笉鎯充繚鐣檃uthor鍙垹闄わ級
+ * @description 鍒ゆ柇鏄惁鏄鍙e彿
+ * @param str
+ * @returns {boolean}
+ */
+export function isPort(str) {
+  const reg =
+    /^([0-9]|[1-9]\d|[1-9]\d{2}|[1-9]\d{3}|[1-5]\d{4}|6[0-4]\d{3}|65[0-4]\d{2}|655[0-2]\d|6553[0-5])$/
+  return reg.test(str)
+}
+
+/**
+ * @author chuzhixin 1204505056@qq.com 锛堜笉鎯充繚鐣檃uthor鍙垹闄わ級
+ * @description 鍒ゆ柇鏄惁鏄墜鏈哄彿
+ * @param str
+ * @returns {boolean}
+ */
+export function isPhone(str) {
+  const reg = /^1\d{10}$/
+  return reg.test(str)
+}
+
+/**
+ * @author chuzhixin 1204505056@qq.com 锛堜笉鎯充繚鐣檃uthor鍙垹闄わ級
+ * @description 鍒ゆ柇鏄惁鏄韩浠借瘉鍙�(绗簩浠�)
+ * @param str
+ * @returns {boolean}
+ */
+export function isIdCard(str) {
+  const reg =
+    /^[1-9]\d{5}(18|19|([23]\d))\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/
+  return reg.test(str)
+}
+
+/**
+ * @author chuzhixin 1204505056@qq.com 锛堜笉鎯充繚鐣檃uthor鍙垹闄わ級
+ * @description 鍒ゆ柇鏄惁鏄偖绠�
+ * @param str
+ * @returns {boolean}
+ */
+export function isEmail(str) {
+  const reg = /^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/
+  return reg.test(str)
+}
+
+/**
+ * @author chuzhixin 1204505056@qq.com 锛堜笉鎯充繚鐣檃uthor鍙垹闄わ級
+ * @description 鍒ゆ柇鏄惁涓枃
+ * @param str
+ * @returns {boolean}
+ */
+export function isChina(str) {
+  const reg = /^[\u4E00-\u9FA5]{2,4}$/
+  return reg.test(str)
+}
+
+/**
+ * @author chuzhixin 1204505056@qq.com 锛堜笉鎯充繚鐣檃uthor鍙垹闄わ級
+ * @description 鍒ゆ柇鏄惁涓虹┖
+ * @param str
+ * @returns {boolean}
+ */
+export function isBlank(str) {
+  return (
+    str == null ||
+    false ||
+    str === '' ||
+    str.trim() === '' ||
+    str.toLocaleLowerCase().trim() === 'null'
+  )
+}
+
+/**
+ * @author chuzhixin 1204505056@qq.com 锛堜笉鎯充繚鐣檃uthor鍙垹闄わ級
+ * @description 鍒ゆ柇鏄惁涓哄浐璇�
+ * @param str
+ * @returns {boolean}
+ */
+export function isTel(str) {
+  const reg =
+    /^(400|800)([0-9\\-]{7,10})|(([0-9]{4}|[0-9]{3})(-| )?)?([0-9]{7,8})((-| |杞�)*([0-9]{1,4}))?$/
+  return reg.test(str)
+}
+
+/**
+ * @author chuzhixin 1204505056@qq.com 锛堜笉鎯充繚鐣檃uthor鍙垹闄わ級
+ * @description 鍒ゆ柇鏄惁涓烘暟瀛椾笖鏈�澶氫袱浣嶅皬鏁�
+ * @param str
+ * @returns {boolean}
+ */
+export function isNum(str) {
+  const reg = /^\d+(\.\d{1,2})?$/
+  return reg.test(str)
+}
diff --git a/src/views/401.vue b/src/views/401.vue
new file mode 100644
index 0000000..a47ecac
--- /dev/null
+++ b/src/views/401.vue
@@ -0,0 +1,296 @@
+<template>
+  <div class="error-container">
+    <div class="error-content">
+      <el-row :gutter="20">
+        <el-col :xs="24" :sm="24" :md="12" :lg="12" :xl="12">
+          <div class="pic-error">
+            <img
+              alt="401"
+              class="pic-error-parent"
+              src="@/assets/error_images/401.png"
+            />
+            <img
+              alt="401"
+              class="pic-error-child left"
+              src="@/assets/error_images/cloud.png"
+            />
+            <img
+              alt="401"
+              class="pic-error-child"
+              src="@/assets/error_images/cloud.png"
+            />
+            <img
+              alt="401"
+              class="pic-error-child"
+              src="@/assets/error_images/cloud.png"
+            />
+          </div>
+        </el-col>
+
+        <el-col :xs="24" :sm="24" :md="12" :lg="12" :xl="12">
+          <div class="bullshit">            
+            <div class="bullshit-oops">{{ oops }}</div>
+            <div class="bullshit-headline">{{ headline }}</div>
+            <div class="bullshit-info">{{ info }}</div>
+            <a class="bullshit-return-home" href="#/index">
+              {{ jumpTime }}s&nbsp;{{ btn }}
+            </a>
+          </div>
+        </el-col>
+      </el-row>
+    </div>
+  </div>
+</template>
+
+<script>
+  export default {
+    name: 'Page401',
+    data() {
+      return {
+        jumpTime: 5,
+        oops: '鎶辨瓑!',
+        headline: '鎮ㄦ病鏈夋搷浣滄潈闄�...',
+        info: '褰撳墠甯愬彿娌℃湁鎿嶄綔鏉冮檺,璇疯仈绯荤鐞嗗憳銆�',
+        btn: '杩斿洖',
+        timer: 0,
+      }
+    },
+    mounted() {
+      this.timeChange()
+    },
+    beforeDestroy() {
+      clearInterval(this.timer)
+    },
+    methods: {
+      timeChange() {
+        this.timer = setInterval(() => {
+          if (this.jumpTime) {
+            this.jumpTime--
+          } else {
+            this.$router.push({ path: '/' })
+            this.$store.dispatch('tabsBar/delOthersRoutes', {
+              path: '/',
+            })
+            clearInterval(this.timer)
+          }
+        }, 1000)
+      },
+    },
+  }
+</script>
+
+<style lang="scss" scoped>
+  .error-container {
+    position: absolute;
+    top: 40%;
+    left: 50%;
+    transform: translate(-50%, -50%);
+
+    .error-content {
+      .pic-error {
+        position: relative;
+        float: left;
+        width: 120%;
+        overflow: hidden;
+
+        &-parent {
+          width: 100%;
+        }
+
+        &-child {
+          position: absolute;
+
+          &.left {
+            top: 17px;
+            left: 220px;
+            width: 80px;
+            opacity: 0;
+            animation-name: cloudLeft;
+            animation-duration: 2s;
+            animation-timing-function: linear;
+            animation-delay: 1s;
+            animation-fill-mode: forwards;
+          }
+
+          &.mid {
+            top: 10px;
+            left: 420px;
+            width: 46px;
+            opacity: 0;
+            animation-name: cloudMid;
+            animation-duration: 2s;
+            animation-timing-function: linear;
+            animation-delay: 1.2s;
+            animation-fill-mode: forwards;
+          }
+
+          &.right {
+            top: 100px;
+            left: 500px;
+            width: 62px;
+            opacity: 0;
+            animation-name: cloudRight;
+            animation-duration: 2s;
+            animation-timing-function: linear;
+            animation-delay: 1s;
+            animation-fill-mode: forwards;
+          }
+
+          @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 {
+          margin-bottom: 20px;
+          font-size: 32px;
+          font-weight: bold;
+          line-height: 40px;
+          color: $base-color-blue;
+          opacity: 0;
+          animation-name: slideUp;
+          animation-duration: 0.5s;
+          animation-fill-mode: forwards;
+        }
+
+        &-headline {
+          margin-bottom: 10px;
+          font-size: 20px;
+          font-weight: bold;
+          line-height: 24px;
+          color: #222;
+          opacity: 0;
+          animation-name: slideUp;
+          animation-duration: 0.5s;
+          animation-delay: 0.1s;
+          animation-fill-mode: forwards;
+        }
+
+        &-info {
+          margin-bottom: 30px;
+          font-size: 13px;
+          line-height: 21px;
+          color: $base-color-gray;
+          opacity: 0;
+          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;
+          font-size: 14px;
+          line-height: 36px;
+          color: #fff;
+          text-align: center;
+          cursor: pointer;
+          background: $base-color-blue;
+          border-radius: 100px;
+          opacity: 0;
+          animation-name: slideUp;
+          animation-duration: 0.5s;
+          animation-delay: 0.3s;
+          animation-fill-mode: forwards;
+        }
+
+        @keyframes slideUp {
+          0% {
+            opacity: 0;
+            transform: translateY(60px);
+          }
+
+          100% {
+            opacity: 1;
+            transform: translateY(0);
+          }
+        }
+      }
+    }
+  }
+</style>
diff --git a/src/views/404.vue b/src/views/404.vue
new file mode 100644
index 0000000..9c261b6
--- /dev/null
+++ b/src/views/404.vue
@@ -0,0 +1,296 @@
+<template>
+  <div class="error-container">
+    <div class="error-content">
+      <el-row :gutter="20">
+        <el-col :xs="24" :sm="24" :md="12" :lg="12" :xl="12">
+          <div class="pic-error">
+            <img
+              alt="401"
+              class="pic-error-parent"
+              src="@/assets/error_images/404.png"
+            />
+            <img
+              alt="401"
+              class="pic-error-child left"
+              src="@/assets/error_images/cloud.png"
+            />
+            <img
+              alt="401"
+              class="pic-error-child"
+              src="@/assets/error_images/cloud.png"
+            />
+            <img
+              alt="401"
+              class="pic-error-child"
+              src="@/assets/error_images/cloud.png"
+            />
+          </div>
+        </el-col>
+
+        <el-col :xs="24" :sm="24" :md="12" :lg="12" :xl="12">
+          <div class="bullshit">
+            <div class="bullshit-oops">{{ oops }}</div>
+            <div class="bullshit-headline">{{ headline }}</div>
+            <div class="bullshit-info">{{ info }}</div>
+            <a class="bullshit-return-home" href="#/index">
+              {{ jumpTime }}s&nbsp;{{ btn }}
+            </a>
+          </div>
+        </el-col>
+      </el-row>
+    </div>
+  </div>
+</template>
+
+<script>
+  export default {
+    name: 'Page404',
+    data() {
+      return {
+        jumpTime: 5,
+        oops: '鎶辨瓑!',
+        headline: '褰撳墠椤甸潰涓嶅瓨鍦�...',
+        info: '璇锋鏌ユ偍杈撳叆鐨勭綉鍧�鏄惁姝g‘锛屾垨鐐瑰嚮涓嬮潰鐨勬寜閽繑鍥為椤点��',
+        btn: '杩斿洖棣栭〉',
+        timer: 0,
+      }
+    },
+    mounted() {
+      this.timeChange()
+    },
+    beforeDestroy() {
+      clearInterval(this.timer)
+    },
+    methods: {
+      timeChange() {
+        this.timer = setInterval(() => {
+          if (this.jumpTime) {
+            this.jumpTime--
+          } else {
+            this.$router.push({ path: '/' })
+            this.$store.dispatch('tabsBar/delOthersRoutes', {
+              path: '/',
+            })
+            clearInterval(this.timer)
+          }
+        }, 1000)
+      },
+    },
+  }
+</script>
+
+<style lang="scss" scoped>
+  .error-container {
+    position: absolute;
+    top: 40%;
+    left: 50%;
+    transform: translate(-50%, -50%);
+
+    .error-content {
+      .pic-error {
+        position: relative;
+        float: left;
+        width: 120%;
+        overflow: hidden;
+
+        &-parent {
+          width: 100%;
+        }
+
+        &-child {
+          position: absolute;
+
+          &.left {
+            top: 17px;
+            left: 220px;
+            width: 80px;
+            opacity: 0;
+            animation-name: cloudLeft;
+            animation-duration: 2s;
+            animation-timing-function: linear;
+            animation-delay: 1s;
+            animation-fill-mode: forwards;
+          }
+
+          &.mid {
+            top: 10px;
+            left: 420px;
+            width: 46px;
+            opacity: 0;
+            animation-name: cloudMid;
+            animation-duration: 2s;
+            animation-timing-function: linear;
+            animation-delay: 1.2s;
+            animation-fill-mode: forwards;
+          }
+
+          &.right {
+            top: 100px;
+            left: 500px;
+            width: 62px;
+            opacity: 0;
+            animation-name: cloudRight;
+            animation-duration: 2s;
+            animation-timing-function: linear;
+            animation-delay: 1s;
+            animation-fill-mode: forwards;
+          }
+
+          @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 {
+          margin-bottom: 20px;
+          font-size: 32px;
+          font-weight: bold;
+          line-height: 40px;
+          color: $base-color-blue;
+          opacity: 0;
+          animation-name: slideUp;
+          animation-duration: 0.5s;
+          animation-fill-mode: forwards;
+        }
+
+        &-headline {
+          margin-bottom: 10px;
+          font-size: 20px;
+          font-weight: bold;
+          line-height: 24px;
+          color: #222;
+          opacity: 0;
+          animation-name: slideUp;
+          animation-duration: 0.5s;
+          animation-delay: 0.1s;
+          animation-fill-mode: forwards;
+        }
+
+        &-info {
+          margin-bottom: 30px;
+          font-size: 13px;
+          line-height: 21px;
+          color: $base-color-gray;
+          opacity: 0;
+          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;
+          font-size: 14px;
+          line-height: 36px;
+          color: #fff;
+          text-align: center;
+          cursor: pointer;
+          background: $base-color-blue;
+          border-radius: 100px;
+          opacity: 0;
+          animation-name: slideUp;
+          animation-duration: 0.5s;
+          animation-delay: 0.3s;
+          animation-fill-mode: forwards;
+        }
+
+        @keyframes slideUp {
+          0% {
+            opacity: 0;
+            transform: translateY(60px);
+          }
+
+          100% {
+            opacity: 1;
+            transform: translateY(0);
+          }
+        }
+      }
+    }
+  }
+</style>
diff --git a/src/views/index/index.vue b/src/views/index/index.vue
new file mode 100644
index 0000000..755824f
--- /dev/null
+++ b/src/views/index/index.vue
@@ -0,0 +1,11 @@
+<template>
+  <div>棣栭〉</div>
+</template>
+
+<script>
+export default {
+  name: '',
+}
+</script>
+
+<style></style>
diff --git a/src/views/login/index.vue b/src/views/login/index.vue
new file mode 100644
index 0000000..dd99764
--- /dev/null
+++ b/src/views/login/index.vue
@@ -0,0 +1,327 @@
+<template>
+  <div class="login-container">
+    <el-alert
+      title="beautiful boys and girls娆㈣繋鍔犲叆vue-admin-beautifulQQ缇わ細972435319"
+      type="success"
+      :closable="false"
+      style="position: fixed"
+    ></el-alert>
+    <el-row>
+      <el-col :xs="24" :sm="24" :md="12" :lg="16" :xl="16">
+        <div style="color: transparent">鍗犱綅绗�</div>
+      </el-col>
+      <el-col :xs="24" :sm="24" :md="12" :lg="8" :xl="8">
+        <el-form
+          ref="form"
+          :model="form"
+          :rules="rules"
+          class="login-form"
+          label-position="left"
+        >
+          <div class="title">hello !</div>
+          <div class="title-tips">娆㈣繋鏉ュ埌{{ title }}锛�</div>
+          <el-form-item style="margin-top: 40px" prop="username">
+            <span class="svg-container svg-container-admin">
+              <vab-icon :icon="['fas', 'user']" />
+            </span>
+            <el-input
+              v-model.trim="form.username"
+              v-focus
+              placeholder="璇疯緭鍏ョ敤鎴峰悕"
+              tabindex="1"
+              type="text"
+            />
+          </el-form-item>
+          <el-form-item prop="password">
+            <span class="svg-container">
+              <vab-icon :icon="['fas', 'lock']" />
+            </span>
+            <el-input
+              :key="passwordType"
+              ref="password"
+              v-model.trim="form.password"
+              :type="passwordType"
+              tabindex="2"
+              placeholder="璇疯緭鍏ュ瘑鐮�"
+              @keyup.enter.native="handleLogin"
+            />
+            <span
+              v-if="passwordType === 'password'"
+              class="show-password"
+              @click="handlePassword"
+            >
+              <vab-icon :icon="['fas', 'eye-slash']"></vab-icon>
+            </span>
+            <span v-else class="show-password" @click="handlePassword">
+              <vab-icon :icon="['fas', 'eye']"></vab-icon>
+            </span>
+          </el-form-item>
+          <el-button
+            :loading="loading"
+            class="login-btn"
+            type="primary"
+            @click="handleLogin"
+          >
+            鐧诲綍
+          </el-button>
+          <router-link to="/register">
+            <div style="margin-top: 20px">娉ㄥ唽</div>
+          </router-link>
+        </el-form>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script>
+  import { isPassword } from '@/utils/validate'
+
+  export default {
+    name: 'Login',
+    directives: {
+      focus: {
+        inserted(el) {
+          el.querySelector('input').focus()
+        },
+      },
+    },
+    data() {
+      const validateusername = (rule, value, callback) => {
+        if ('' == value) {
+          callback(new Error('鐢ㄦ埛鍚嶄笉鑳戒负绌�'))
+        } else {
+          callback()
+        }
+      }
+      const validatePassword = (rule, value, callback) => {
+        if (!isPassword(value)) {
+          callback(new Error('瀵嗙爜涓嶈兘灏戜簬6浣�'))
+        } else {
+          callback()
+        }
+      }
+      return {
+        nodeEnv: process.env.NODE_ENV,
+        title: this.$baseTitle,
+        form: {
+          username: '',
+          password: '',
+        },
+        rules: {
+          username: [
+            {
+              required: true,
+              trigger: 'blur',
+              validator: validateusername,
+            },
+          ],
+          password: [
+            {
+              required: true,
+              trigger: 'blur',
+              validator: validatePassword,
+            },
+          ],
+        },
+        loading: false,
+        passwordType: 'password',
+        redirect: undefined,
+      }
+    },
+    watch: {
+      $route: {
+        handler(route) {
+          this.redirect = (route.query && route.query.redirect) || '/'
+        },
+        immediate: true,
+      },
+    },
+    created() {
+      document.body.style.overflow = 'hidden'
+    },
+    beforeDestroy() {
+      document.body.style.overflow = 'auto'
+    },
+    mounted() {
+      this.form.username = 'admin'
+      this.form.password = '123456'
+      setTimeout(() => {
+        this.handleLogin()
+      }, 3000)
+    },
+    methods: {
+      handlePassword() {
+        this.passwordType === 'password'
+          ? (this.passwordType = '')
+          : (this.passwordType = 'password')
+        this.$nextTick(() => {
+          this.$refs.password.focus()
+        })
+      },
+      handleLogin() {
+        this.$refs.form.validate((valid) => {
+          if (valid) {
+            this.loading = true
+            this.$store
+              .dispatch('user/login', this.form)
+              .then(() => {
+                const routerPath =
+                  this.redirect === '/404' || this.redirect === '/401'
+                    ? '/'
+                    : this.redirect
+                this.$router.push(routerPath).catch(() => {})
+                this.loading = false
+              })
+              .catch(() => {
+                this.loading = false
+              })
+          } else {
+            return false
+          }
+        })
+      },
+    },
+  }
+</script>
+
+<style lang="scss" scoped>
+  .login-container {
+    height: 100vh;
+    background: url('~@/assets/login_images/background.jpg') center center fixed
+      no-repeat;
+    background-size: cover;
+
+    .title {
+      font-size: 54px;
+      font-weight: 500;
+      color: rgba(14, 18, 26, 1);
+    }
+
+    .title-tips {
+      margin-top: 29px;
+      font-size: 26px;
+      font-weight: 400;
+      color: rgba(14, 18, 26, 1);
+      text-overflow: ellipsis;
+      white-space: nowrap;
+    }
+
+    .login-btn {
+      display: inherit;
+      width: 220px;
+      height: 60px;
+      margin-top: 5px;
+      border: 0;
+
+      &:hover {
+        opacity: 0.9;
+      }
+    }
+
+    .login-form {
+      position: relative;
+      max-width: 100%;
+      margin: calc((100vh - 425px) / 2) 10% 10%;
+      overflow: hidden;
+
+      .forget-password {
+        width: 100%;
+        margin-top: 40px;
+        text-align: left;
+
+        .forget-pass {
+          width: 129px;
+          height: 19px;
+          font-size: 20px;
+          font-weight: 400;
+          color: rgba(92, 102, 240, 1);
+        }
+      }
+    }
+
+    .tips {
+      margin-bottom: 10px;
+      font-size: $base-font-size-default;
+      color: $base-color-white;
+
+      span {
+        &:first-of-type {
+          margin-right: 16px;
+        }
+      }
+    }
+
+    .title-container {
+      position: relative;
+
+      .title {
+        margin: 0 auto 40px auto;
+        font-size: 34px;
+        font-weight: bold;
+        color: $base-color-blue;
+        text-align: center;
+      }
+    }
+
+    .svg-container {
+      position: absolute;
+      top: 14px;
+      left: 15px;
+      z-index: $base-z-index;
+      font-size: 16px;
+      color: #d7dee3;
+      cursor: pointer;
+      user-select: none;
+    }
+
+    .show-password {
+      position: absolute;
+      top: 14px;
+      right: 25px;
+      font-size: 16px;
+      color: #d7dee3;
+      cursor: pointer;
+      user-select: none;
+    }
+
+    ::v-deep {
+      .el-form-item {
+        padding-right: 0;
+        margin: 20px 0;
+        color: #454545;
+        background: transparent;
+        border: 1px solid transparent;
+        border-radius: 2px;
+
+        &__content {
+          min-height: $base-input-height;
+          line-height: $base-input-height;
+        }
+
+        &__error {
+          position: absolute;
+          top: 100%;
+          left: 18px;
+          font-size: $base-font-size-small;
+          line-height: 18px;
+          color: $base-color-red;
+        }
+      }
+
+      .el-input {
+        box-sizing: border-box;
+
+        input {
+          height: 58px;
+          padding-left: 45px;
+          font-size: $base-font-size-default;
+          line-height: 58px;
+          color: $base-font-color;
+          background: #f6f4fc;
+          border: 0;
+          caret-color: $base-font-color;
+        }
+      }
+    }
+  }
+</style>
diff --git a/src/views/project/components/ProjectEdit.vue b/src/views/project/components/ProjectEdit.vue
new file mode 100644
index 0000000..d2740b4
--- /dev/null
+++ b/src/views/project/components/ProjectEdit.vue
@@ -0,0 +1,119 @@
+<template>
+  <el-dialog
+    :title="title"
+    :visible.sync="dialogFormVisible"
+    width="500px"
+    :close-on-click-modal="false"
+    @close="close"
+  >
+    <el-tabs v-model="activeName" @tab-click="handleClick">
+      <el-tab-pane label="鍩虹淇℃伅" name="base">
+        <el-form ref="form" :model="form" :rules="rules" label-width="80px">
+          <el-form-item label="椤圭洰鍚嶇О" prop="name">
+            <el-input v-model.trim="form.name" autocomplete="off"></el-input>
+          </el-form-item>
+          <el-form-item label="椤圭洰鍦板潃" prop="srcUrl">
+            <el-input v-model.trim="form.srcUrl" autocomplete="off"></el-input>
+          </el-form-item>
+          <el-form-item label="椤圭洰鐢ㄦ埛" prop="srcUser">
+            <el-input v-model.trim="form.srcUser" autocomplete="off"></el-input>
+          </el-form-item>
+          <el-form-item label="椤圭洰瀵嗙爜" prop="srcPassword">
+            <el-input v-model.trim="form.srcPassword" autocomplete="off"></el-input>
+          </el-form-item>
+          <el-form-item label="椤圭洰鎻忚堪" prop="desc">
+            <el-input v-model.trim="form.desc" autocomplete="off"></el-input>
+          </el-form-item>
+          <el-form-item label="缂栬瘧鍒嗘敮" prop="branch">
+            <el-input v-model.trim="form.branch" autocomplete="off"></el-input>
+          </el-form-item>
+          <el-form-item label="缂栬瘧鏋舵瀯" prop="arch">
+            <el-radio-group v-model="form.arch">
+              <el-radio label="x86">X86</el-radio>
+              <el-radio label="arm">ARM</el-radio>
+            </el-radio-group>
+          </el-form-item>
+          <el-form-item label="椤圭洰绫诲瀷" prop="type">
+            <el-radio-group v-model="form.type">
+              <el-radio label="sys">绯荤粺鍖�</el-radio>
+              <el-radio label="app">搴旂敤鍖�</el-radio>
+              <el-radio label="algo">绠楁硶鍖�</el-radio>
+            </el-radio-group>
+          </el-form-item>
+          <el-form-item label="鑷姩鎵撳寘" prop="autoBuild">
+            <el-switch v-model="form.autoBuild"></el-switch>
+          </el-form-item>
+        </el-form>
+      </el-tab-pane>
+      <el-tab-pane label="缂栬瘧鑴氭湰" name="build">
+        <textarea v-model="form.buildScript" cols="60" rows="10"></textarea>
+      </el-tab-pane>
+      <el-tab-pane label="鎵撳寘鑴氭湰" name="pack">
+        <textarea v-model="form.packScript" cols="60" rows="10"></textarea>
+      </el-tab-pane>
+    </el-tabs>
+
+    <div slot="footer" class="dialog-footer">
+      <el-button @click="close">鍙� 娑�</el-button>
+      <el-button type="primary" @click="save">纭� 瀹�</el-button>
+    </div>
+  </el-dialog>
+</template>
+
+<script>
+import { doEdit } from '@/api/project'
+
+export default {
+  name: 'ProjectEdit',
+  data() {
+    return {
+      activeName: 'base',
+      form: {
+        title: '',
+        author: '',
+      },
+      rules: {
+        title: [{ required: true, trigger: 'blur', message: '璇疯緭鍏ユ爣棰�' }],
+        author: [{ required: true, trigger: 'blur', message: '璇疯緭鍏ヤ綔鑰�' }],
+      },
+      title: '',
+      dialogFormVisible: false,
+    }
+  },
+  created() { },
+  methods: {
+    showEdit(row) {
+      if (!row) {
+        this.title = '娣诲姞'
+      } else {
+        this.title = '缂栬緫'
+        this.form = Object.assign({}, row)
+      }
+      this.dialogFormVisible = true
+    },
+    close() {
+      this.$refs['form'].resetFields()
+      this.form = this.$options.data().form
+      this.dialogFormVisible = false
+      this.$emit('fetch-data')
+    },
+    save() {
+      this.$refs['form'].validate(async (valid) => {
+        if (valid) {
+          const { msg } = await doEdit(this.form)
+          this.$baseMessage(msg, 'success')
+          this.$refs['form'].resetFields()
+          this.dialogFormVisible = false
+          this.$emit('fetch-data')
+          this.form = this.$options.data().form
+        } else {
+          return false
+        }
+      })
+    },
+    handleClick(tab, event) {
+      // console.log(tab, event)
+    },
+  },
+}
+</script>
diff --git a/src/views/project/index.vue b/src/views/project/index.vue
new file mode 100644
index 0000000..8df14be
--- /dev/null
+++ b/src/views/project/index.vue
@@ -0,0 +1,271 @@
+<template>
+  <div class="table-container">
+    <vab-query-form>
+      <vab-query-form-left-panel>
+        <el-form ref="form" :model="queryForm" :inline="true" @submit.native.prevent>
+          <el-form-item>
+            <el-input v-model="queryForm.title" placeholder="鍚嶇О" />
+          </el-form-item>
+          <el-form-item>
+            <el-select
+              v-model="queryForm.address"
+              placeholder="绫诲瀷"
+              class="handle-select mr10"
+              size="mini"
+            >
+              <el-option key="1" label="绯荤粺" value="绯荤粺"></el-option>
+              <el-option key="2" label="搴旂敤" value="搴旂敤"></el-option>
+              <el-option key="3" label="绠楁硶" value="绠楁硶"></el-option>
+            </el-select>
+          </el-form-item>
+          <el-form-item>
+            <el-button
+              icon="el-icon-search"
+              type="primary"
+              native-type="submit"
+              @click="handleQuery"
+            >鏌ヨ</el-button>
+          </el-form-item>
+        </el-form>
+      </vab-query-form-left-panel>
+      <vab-query-form-right-panel>
+        <el-button icon="el-icon-plus" type="primary" @click="handleAdd">
+          娣诲姞</el-button>
+        <el-button icon="el-icon-delete" type="danger" @click="handleDelete">鍒犻櫎</el-button>
+      </vab-query-form-right-panel>
+    </vab-query-form>
+
+    <el-table
+      ref="tableSort"
+      v-loading="listLoading"
+      stripe
+      :data="list"
+      :element-loading-text="elementLoadingText"
+      :height="height"
+      @selection-change="setSelectRows"
+      @expand-change="expandChange"
+    >
+      <el-table-column type="expand">
+        <template slot-scope="props">
+          <el-table :data="props.row.pkgList" :show-header="false" style="width: 100%">
+            <el-table-column prop="fileName"></el-table-column>
+            <el-table-column prop="version" width="150"></el-table-column>
+            <el-table-column prop="commit" width="150"></el-table-column>
+            <el-table-column prop="createdAt"></el-table-column>
+
+            <el-table-column prop="state" width="150">
+              <template #default="{ row }">
+                <el-tag>{{ packageState[row.state] }}</el-tag>
+              </template>
+            </el-table-column>
+            <el-table-column width="120">
+              <template #default="scope">
+                <el-button size="small" @click="handlePublish(scope.row)">鍙戝竷</el-button>
+              </template>
+            </el-table-column>
+          </el-table>
+        </template>
+      </el-table-column>
+      <!-- <el-table-column show-overflow-tooltip type="selection" width="55"></el-table-column> -->
+      <el-table-column show-overflow-tooltip label="搴忓彿" width="95">
+        <template #default="scope">{{ scope.$index + 1 }}</template>
+      </el-table-column>
+      <el-table-column show-overflow-tooltip prop="name" label="椤圭洰鍚嶇О"></el-table-column>
+      <el-table-column show-overflow-tooltip prop="srcUrl" label="椤圭洰鍦板潃"></el-table-column>
+      <el-table-column show-overflow-tooltip prop="type" label="绫诲瀷">
+        <template #default="{ row }">
+          <el-tag>{{ row.type | typeFilter }}</el-tag>
+        </template>
+      </el-table-column>
+      <!-- <el-table-column show-overflow-tooltip label="褰撳墠鐗堟湰" prop="latestVersion"></el-table-column> -->
+      <el-table-column label="鐘舵��">
+        <template #default="{ row }">
+          <el-tag>{{ row.state | stateFilter }}</el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column label="鍒涘缓鏃堕棿" prop="createdAt" width="200"></el-table-column>
+      <el-table-column label="鎿嶄綔" width="180px">
+        <template #default="{ row }">
+          <el-button type="text" @click="handleEdit(row)">缂栬緫</el-button>
+          <el-button type="text" style="color: red" @click="handleDeleteRow(row)">鍒犻櫎</el-button>
+          <el-button type="text" style="color: deepskyblue" @click="handlePack(row)">鎵撳寘</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+    <el-pagination
+      :background="background"
+      :current-page="queryForm.pageNo"
+      :layout="layout"
+      :page-size="queryForm.pageSize"
+      :total="total"
+      @current-change="handleCurrentChange"
+      @size-change="handleSizeChange"
+    ></el-pagination>
+    <table-edit ref="edit" @fetch-data="fetchData"></table-edit>
+  </div>
+</template>
+
+<script>
+import { getList, deletePrj, getPkgList, buildPkg } from '@/api/project'
+import { publish } from '@/api/package'
+
+import TableEdit from './components/ProjectEdit.vue'
+export default {
+  name: 'ComprehensiveTable',
+  components: {
+    TableEdit,
+  },
+  filters: {
+    stateFilter(state) {
+      const stateMap = ['涓嬭浇涓�', '鍚屾瀹屾垚', '鍚屾澶辫触']
+
+      return stateMap[state]
+    },
+    typeFilter(type) {
+      const typeMap = {
+        sys: '绯荤粺鍖�',
+        app: '搴旂敤鍖�',
+        algo: '绠楁硶鍖�',
+      }
+      return typeMap[type]
+    },
+  },
+  data() {
+    return {
+      imgShow: true,
+      list: [],
+      listLoading: true,
+      layout: 'total, sizes, prev, pager, next, jumper',
+      packageState: [
+        '瀹屾垚',
+        '宸叉彁浜�',
+        '鎺掗槦涓�',
+        '鎵撳寘涓�',
+        '缂栬瘧澶辫触',
+        '鎵撳寘澶辫触',
+      ],
+      total: 0,
+      background: true,
+      selectRows: '',
+      elementLoadingText: '姝e湪鍔犺浇...',
+      queryForm: {
+        pageNo: 1,
+        pageSize: 20,
+        title: '',
+      },
+    }
+  },
+  computed: {
+    height() {
+      return this.$baseTableHeight()
+    },
+  },
+  created() {
+    this.fetchData()
+  },
+  beforeDestroy() { },
+  mounted() { },
+  methods: {
+    setSelectRows(val) {
+      this.selectRows = val
+    },
+    handleAdd() {
+      this.$refs['edit'].showEdit()
+    },
+    handleEdit(row) {
+      this.$refs['edit'].showEdit(row)
+    },
+    handleDelete(row) {
+      if (this.selectRows.length > 0) {
+        const ids = this.selectRows.map((item) => item.id).join()
+        this.$baseConfirm('浣犵‘瀹氳鍒犻櫎閫変腑椤瑰悧', null, async () => {
+          const { msg } = await doDelete({ ids: ids })
+          this.$baseMessage(msg, 'success')
+          this.fetchData()
+        })
+      } else {
+        this.$baseMessage('鏈�変腑浠讳綍琛�', 'error')
+        return false
+      }
+    },
+    handleDeleteRow(row) {
+      if (row.id) {
+        this.$baseConfirm('浣犵‘瀹氳鍒犻櫎褰撳墠椤瑰悧', null, async () => {
+          const { msg } = await deletePrj(row.id)
+          this.$baseMessage(msg, 'success')
+          this.fetchData()
+        })
+      }
+    },
+    handleSizeChange(val) {
+      this.queryForm.pageSize = val
+      this.fetchData()
+    },
+    handleCurrentChange(val) {
+      this.queryForm.pageNo = val
+      this.fetchData()
+    },
+    handleQuery() {
+      this.queryForm.pageNo = 1
+      this.fetchData()
+    },
+    async fetchData() {
+      this.listLoading = true
+      const { data, total } = await getList(this.queryForm)
+
+      if (data) {
+        data.forEach((item) => {
+          item.pkgList = []
+        })
+
+        this.list = data
+      }
+
+      this.total = total
+      setTimeout(() => {
+        this.listLoading = false
+      }, 500)
+    },
+
+    async expandChange(row, expandRows) {
+      console.log(row)
+      if (expandRows.length == 0) {
+        console.log('fold')
+        return
+      }
+
+      const { data, total } = await getPkgList(row.id)
+      this.list.forEach((item, idx) => {
+        if (item.id === row.id) {
+          this.list[idx].pkgList = data
+        }
+      })
+    },
+    handlePack(row) {
+      this.$prompt('璇疯緭鍏ョ増鏈彿', '鎻愮ず', {
+        confirmButtonText: '纭畾',
+        cancelButtonText: '鍙栨秷',
+        inputPattern: /[\d+][\.\d+]*/,
+        inputErrorMessage: '鐗堟湰鍙锋牸寮忎笉姝g‘,渚� 1.0.0, 10.1.100',
+      })
+        .then(({ value }) => {
+          buildPkg(row, value).then((rsp) => {
+            console.log(rsp)
+            this.expandChange(row, 1)
+          })
+        })
+        .catch(() => {
+          this.$message({
+            type: 'info',
+            message: '鍙栨秷杈撳叆',
+          })
+        })
+    },
+    handlePublish(row) {
+      publish(row).then((rsp) => {
+        console.log(rsp)
+      })
+    },
+  },
+}
+</script>
diff --git a/vue.config.js b/vue.config.js
new file mode 100644
index 0000000..2973a1c
--- /dev/null
+++ b/vue.config.js
@@ -0,0 +1,194 @@
+const path = require('path')
+const {
+  publicPath,
+  assetsDir,
+  outputDir,
+  lintOnSave,
+  transpileDependencies,
+  title,
+  abbreviation,
+  devPort,
+  providePlugin,
+  build7z,
+  donation,
+} = require('./src/config')
+const { webpackBarName, webpackBanner, donationConsole } = require('zx-layouts')
+
+const { version, author } = require('./package.json')
+
+const Webpack = require('webpack')
+const WebpackBar = require('webpackbar')
+const FileManagerPlugin = require('filemanager-webpack-plugin')
+const dayjs = require('dayjs')
+const date = dayjs().format('YYYY_M_D')
+const time = dayjs().format('YYYY-M-D HH:mm:ss')
+process.env.VUE_APP_TITLE = title || 'vue-admin-beautiful'
+process.env.VUE_APP_AUTHOR = author || 'chuzhixin 1204505056@qq.com'
+process.env.VUE_APP_UPDATE_TIME = time
+process.env.VUE_APP_VERSION = version
+
+const resolve = (dir) => path.join(__dirname, dir)
+const mockServer = () => {
+  if (process.env.NODE_ENV === 'development') return require('./mock')
+  else return ''
+}
+
+module.exports = {
+  publicPath,
+  assetsDir,
+  outputDir,
+  lintOnSave,
+
+  transpileDependencies,
+  devServer: {
+    hot: true,
+    port: devPort,
+    open: true,
+    noInfo: false,
+    overlay: {
+      warnings: true,
+      errors: true,
+    },
+    after: mockServer(),
+  },
+  configureWebpack() {
+    return {
+      resolve: {
+        alias: {
+          '@': resolve('src'),
+        },
+      },
+      plugins: [
+        new Webpack.ProvidePlugin(providePlugin),
+        new WebpackBar({
+          name: webpackBarName,
+        }),
+      ],
+    }
+  },
+  chainWebpack(config) {
+    config.plugins.delete('preload')
+    config.plugins.delete('prefetch')
+    config.module
+      .rule('svg')
+      .exclude.add(resolve('src/remixIcon'))
+      .add(resolve('src/colorfulIcon'))
+      .end()
+
+    config.module
+      .rule('remixIcon')
+      .test(/\.svg$/)
+      .include.add(resolve('src/remixIcon'))
+      .end()
+      .use('svg-sprite-loader')
+      .loader('svg-sprite-loader')
+      .options({ symbolId: 'remix-icon-[name]' })
+      .end()
+
+    config.module
+      .rule('colorfulIcon')
+      .test(/\.svg$/)
+      .include.add(resolve('src/colorfulIcon'))
+      .end()
+      .use('svg-sprite-loader')
+      .loader('svg-sprite-loader')
+      .options({ symbolId: 'colorful-icon-[name]' })
+      .end()
+
+    /*  config.when(process.env.NODE_ENV === "development", (config) => {
+      config.devtool("source-map");
+    }); */
+    config.when(process.env.NODE_ENV !== 'development', (config) => {
+      config.performance.set('hints', false)
+      config.devtool('none')
+      config.optimization.splitChunks({
+        automaticNameDelimiter: '-',
+        chunks: 'all',
+        cacheGroups: {
+          chunk: {
+            name: 'vab-chunk',
+            test: /[\\/]node_modules[\\/]/,
+            minSize: 131072,
+            maxSize: 524288,
+            chunks: 'async',
+            minChunks: 2,
+            priority: 10,
+          },
+          vue: {
+            name: 'vue',
+            test: /[\\/]node_modules[\\/](vue(.*)|core-js)[\\/]/,
+            chunks: 'initial',
+            priority: 20,
+          },
+          elementUI: {
+            name: 'element-ui',
+            test: /[\\/]node_modules[\\/]element-ui(.*)[\\/]/,
+            priority: 30,
+          },
+          extra: {
+            name: 'vab-layouts',
+            test: resolve('src/layouts'),
+            priority: 40,
+          },
+        },
+      })
+      config
+        .plugin('banner')
+        .use(Webpack.BannerPlugin, [`${webpackBanner}${time}`])
+        .end()
+      config.module
+        .rule('images')
+        .use('image-webpack-loader')
+        .loader('image-webpack-loader')
+        .options({
+          bypassOnDebug: true,
+        })
+        .end()
+    })
+
+    if (build7z) {
+      config.when(process.env.NODE_ENV === 'production', (config) => {
+        config
+          .plugin('fileManager')
+          .use(FileManagerPlugin, [
+            {
+              onEnd: {
+                delete: [`./${outputDir}/video`, `./${outputDir}/data`],
+                archive: [
+                  {
+                    source: `./${outputDir}`,
+                    destination: `./${outputDir}/${abbreviation}_${outputDir}_${date}.7z`,
+                  },
+                ],
+              },
+            },
+          ])
+          .end()
+      })
+    }
+  },
+  runtimeCompiler: true,
+  productionSourceMap: false,
+  css: {
+    requireModuleExtension: true,
+    sourceMap: true,
+    loaderOptions: {
+      scss: {
+        /*sass-loader 8.0璇硶 */
+        //prependData: '@import "~@/styles/variables.scss";',
+
+        /*sass-loader 9.0鍐欐硶锛屾劅璋ithub鐢ㄦ埛 shaonialife*/
+        additionalData(content, loaderContext) {
+          const { resourcePath, rootContext } = loaderContext
+          const relativePath = path.relative(rootContext, resourcePath)
+          if (
+            relativePath.replace(/\\/g, '/') !== 'src/styles/variables.scss'
+          ) {
+            return '@import "~@/styles/variables.scss";' + content
+          }
+          return content
+        },
+      },
+    },
+  },
+}
diff --git a/webstorm.config.js b/webstorm.config.js
new file mode 100644
index 0000000..499195b
--- /dev/null
+++ b/webstorm.config.js
@@ -0,0 +1,6 @@
+/**
+ * @author chuzhixin 1204505056@qq.com 锛堜笉鎯充繚鐣檃uthor鍙垹闄わ級
+ * @description webstorm.config
+ */
+const webpackConfig = require('@vue/cli-service/webpack.config.js')
+module.exports = webpackConfig

--
Gitblit v1.8.0