Pārlūkot izejas kodu

feat:升级vite2和组合式api

lin-xin 4 gadi atpakaļ
vecāks
revīzija
9bc43e2efc

+ 1 - 1
LICENSE

@@ -1,6 +1,6 @@
 MIT License
 
-Copyright (c) 2016-2019 vue-manage-system
+Copyright (c) 2016-2021 vue-manage-system
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal

+ 2 - 8
README.md

@@ -20,8 +20,6 @@
 
 > Vue2 版本请看 [tag-V4.2.0](https://github.com/lin-xin/vue-manage-system/tree/V4.2.0)
 
-> React + Ant Design 的版本正在开发中,仓库地址:[react-manage-system](https://github.com/lin-xin/react-manage-system)
-
 [English document](https://github.com/lin-xin/manage-system/blob/master/README_EN.md)
 
 ## 项目截图
@@ -53,16 +51,12 @@
 -   [x] Tab 选项卡
 -   [x] 表单
 -   [x] 图表 :bar_chart:
--   [ ] 富文本编辑器
--   [ ] markdown 编辑器
+-   [x] 富文本编辑器
 -   [x] 图片拖拽/裁剪上传
--   [ ] 支持切换主题色 :sparkles:
--   [ ] 列表拖拽排序
 -   [x] 权限测试
 -   [x] 404 / 403
 -   [x] 三级菜单
 -   [x] 自定义图标
--   [ ] 可拖拽弹窗
 -   [x] 国际化
 
 ## 安装步骤
@@ -73,7 +67,7 @@ cd vue-manage-system    // 进入模板目录
 npm install         // 安装项目依赖,等待安装完成之后,安装失败可用 cnpm 或 yarn
 
 // 开启服务器,浏览器访问 http://localhost:8080
-npm run serve
+npm run dev
 
 // 执行构建命令,生成的dist文件夹放在服务器下即可访问
 npm run build

+ 0 - 5
babel.config.js

@@ -1,5 +0,0 @@
-module.exports = {
-  presets: [
-    '@vue/cli-plugin-babel/preset'
-  ]
-}

+ 1 - 0
public/index.html → index.html

@@ -15,6 +15,7 @@
       Please enable it to continue.</strong>
   </noscript>
   <div id="app"></div>
+  <script type="module" src="/src/main.js"></script>
   <!-- built files will be auto injected -->
 </body>
 

+ 700 - 0
package-lock.json

@@ -0,0 +1,700 @@
+{
+  "name": "vue-manage-system",
+  "version": "5.1.0",
+  "lockfileVersion": 1,
+  "requires": true,
+  "dependencies": {
+    "@babel/helper-validator-identifier": {
+      "version": "7.14.5",
+      "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.5.tgz",
+      "integrity": "sha512-5lsetuxCLilmVGyiLEfoHBRX8UCFD+1m2x3Rj97WrW3V7H3u4RWRXA4evMjImCsin2J2YT0QaVDGf+z8ondbAg=="
+    },
+    "@babel/parser": {
+      "version": "7.14.7",
+      "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.7.tgz",
+      "integrity": "sha512-X67Z5y+VBJuHB/RjwECp8kSl5uYi0BvRbNeWqkaJCVh+LiTPl19WBUfG627psSgp9rSf6ojuXghQM3ha6qHHdA=="
+    },
+    "@babel/runtime": {
+      "version": "7.14.6",
+      "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.14.6.tgz",
+      "integrity": "sha512-/PCB2uJ7oM44tz8YhC4Z/6PeOKXp4K588f+5M3clr1M4zbqztlo0XEfJ2LEzj/FgwfgGcIdl8n7YYjTCI0BYwg==",
+      "requires": {
+        "regenerator-runtime": "^0.13.4"
+      }
+    },
+    "@babel/runtime-corejs3": {
+      "version": "7.14.7",
+      "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.14.7.tgz",
+      "integrity": "sha512-Wvzcw4mBYbTagyBVZpAJWI06auSIj033T/yNE0Zn1xcup83MieCddZA7ls3kme17L4NOGBrQ09Q+nKB41RLWBA==",
+      "requires": {
+        "core-js-pure": "^3.15.0",
+        "regenerator-runtime": "^0.13.4"
+      }
+    },
+    "@babel/types": {
+      "version": "7.14.5",
+      "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.5.tgz",
+      "integrity": "sha512-M/NzBpEL95I5Hh4dwhin5JlE7EzO5PHMAuzjxss3tiOBD46KfQvVedN/3jEPZvdRvtsK2222XfdHogNIttFgcg==",
+      "requires": {
+        "@babel/helper-validator-identifier": "^7.14.5",
+        "to-fast-properties": "^2.0.0"
+      }
+    },
+    "@intlify/core-base": {
+      "version": "9.1.6",
+      "resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-9.1.6.tgz",
+      "integrity": "sha512-d5GDPpsQbqPkisSJA5b6nJFEkalY/IHAd7vOLNd/Sj4YaNRzXtInu2FoqKiOv8e/lQnXGTpurdCZg5Jxq1Gsxw==",
+      "requires": {
+        "@intlify/devtools-if": "9.1.6",
+        "@intlify/message-compiler": "9.1.6",
+        "@intlify/message-resolver": "9.1.6",
+        "@intlify/runtime": "9.1.6",
+        "@intlify/shared": "9.1.6",
+        "@intlify/vue-devtools": "9.1.6"
+      }
+    },
+    "@intlify/devtools-if": {
+      "version": "9.1.6",
+      "resolved": "https://registry.npmjs.org/@intlify/devtools-if/-/devtools-if-9.1.6.tgz",
+      "integrity": "sha512-m8Api+kh+BtFa2FZ/JjIdr1ibsGGqBjdKCzWo5BZecEUxBquIeOQZwpokPh/0K5j+/PZleFXkVAMC5mNt+9WdA==",
+      "requires": {
+        "@intlify/shared": "9.1.6"
+      }
+    },
+    "@intlify/message-compiler": {
+      "version": "9.1.6",
+      "resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-9.1.6.tgz",
+      "integrity": "sha512-DR8645VOrVK6x/8tkaCpHnckMAIcoOgeNS5j0wB12RfZoXYQp7vAXMaOP511KMll2mXCREgIB0ojpajiof7yzQ==",
+      "requires": {
+        "@intlify/message-resolver": "9.1.6",
+        "@intlify/shared": "9.1.6",
+        "source-map": "0.6.1"
+      }
+    },
+    "@intlify/message-resolver": {
+      "version": "9.1.6",
+      "resolved": "https://registry.npmjs.org/@intlify/message-resolver/-/message-resolver-9.1.6.tgz",
+      "integrity": "sha512-UUnbawQa5U9sffd5wRIscqtyY1xWlwJbyfwCLPEWLvBhyAnCwPYlvaHGnnO0CSi0fzJTVwlV9DYzobh3agDeMA=="
+    },
+    "@intlify/runtime": {
+      "version": "9.1.6",
+      "resolved": "https://registry.npmjs.org/@intlify/runtime/-/runtime-9.1.6.tgz",
+      "integrity": "sha512-U1QZ+TPf3kQQvWo4BA2mj3cHAxMRHXNTBhu2u+deh6ubTqXdZ19XGBTMSasrXG6RE+zSio9oM+ndoLja7JGtPg==",
+      "requires": {
+        "@intlify/message-compiler": "9.1.6",
+        "@intlify/message-resolver": "9.1.6",
+        "@intlify/shared": "9.1.6"
+      }
+    },
+    "@intlify/shared": {
+      "version": "9.1.6",
+      "resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-9.1.6.tgz",
+      "integrity": "sha512-6MtsKulyfZxdD7OuxjaODjj8QWoHCnLFAk4wkWiHqBCa6UCTC0qXjtEeZ1MxpQihvFmmJZauBUu25EvtngW5qQ=="
+    },
+    "@intlify/vue-devtools": {
+      "version": "9.1.6",
+      "resolved": "https://registry.npmjs.org/@intlify/vue-devtools/-/vue-devtools-9.1.6.tgz",
+      "integrity": "sha512-UdNovg4OML9rIr1sOGZzTfNr1nUy4UQpDf5ni4dNC93T6FIkVJz0n1Np7Vp7e6gDjcmufRYcV99tEwjQSN9+5A==",
+      "requires": {
+        "@intlify/message-resolver": "9.1.6",
+        "@intlify/runtime": "9.1.6",
+        "@intlify/shared": "9.1.6"
+      }
+    },
+    "@popperjs/core": {
+      "version": "2.9.2",
+      "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.9.2.tgz",
+      "integrity": "sha512-VZMYa7+fXHdwIq1TDhSXoVmSPEGM/aa+6Aiq3nVVJ9bXr24zScr+NlKFKC3iPljA7ho/GAZr+d2jOf5GIRC30Q=="
+    },
+    "@types/estree": {
+      "version": "0.0.48",
+      "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.48.tgz",
+      "integrity": "sha512-LfZwXoGUDo0C3me81HXgkBg5CTQYb6xzEl+fNmbO4JdRiSKQ8A0GD1OBBvKAIsbCUgoyAty7m99GqqMQe784ew==",
+      "dev": true
+    },
+    "@types/lodash": {
+      "version": "4.14.170",
+      "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.170.tgz",
+      "integrity": "sha512-bpcvu/MKHHeYX+qeEN8GE7DIravODWdACVA1ctevD8CN24RhPZIKMn9ntfAsrvLfSX3cR5RrBKAbYm9bGs0A+Q=="
+    },
+    "@vitejs/plugin-vue": {
+      "version": "1.2.3",
+      "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-1.2.3.tgz",
+      "integrity": "sha512-LlnLpObkGKZ+b7dcpL4T24l13nPSHLjo+6Oc7MbZiKz5PMAUzADfNJ3EKfYIQ0l0969nxf2jp/9vsfnuJ7h6fw==",
+      "dev": true
+    },
+    "@vue/compiler-core": {
+      "version": "3.1.2",
+      "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.1.2.tgz",
+      "integrity": "sha512-nHmq7vLjq/XM2IMbZUcKWoH5sPXa2uR/nIKZtjbK5F3TcbnYE/zKsrSUR9WZJ03unlwotNBX1OyxVt9HbWD7/Q==",
+      "requires": {
+        "@babel/parser": "^7.12.0",
+        "@babel/types": "^7.12.0",
+        "@vue/shared": "3.1.2",
+        "estree-walker": "^2.0.1",
+        "source-map": "^0.6.1"
+      }
+    },
+    "@vue/compiler-dom": {
+      "version": "3.1.2",
+      "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.1.2.tgz",
+      "integrity": "sha512-k2+SWcWH0jL6WQAX7Or2ONqu5MbtTgTO0dJrvebQYzgqaKMXNI90RNeWeCxS4BnNFMDONpHBeFgbwbnDWIkmRg==",
+      "requires": {
+        "@vue/compiler-core": "3.1.2",
+        "@vue/shared": "3.1.2"
+      }
+    },
+    "@vue/compiler-sfc": {
+      "version": "3.1.2",
+      "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.1.2.tgz",
+      "integrity": "sha512-SeG/2+DvwejQ7oAiSx8BrDh5qOdqCYHGClPiTvVIHTfSIHiS2JjMbCANdDCjHkTOh/O7WZzo2JhdKm98bRBxTw==",
+      "dev": true,
+      "requires": {
+        "@babel/parser": "^7.13.9",
+        "@babel/types": "^7.13.0",
+        "@types/estree": "^0.0.48",
+        "@vue/compiler-core": "3.1.2",
+        "@vue/compiler-dom": "3.1.2",
+        "@vue/compiler-ssr": "3.1.2",
+        "@vue/shared": "3.1.2",
+        "consolidate": "^0.16.0",
+        "estree-walker": "^2.0.1",
+        "hash-sum": "^2.0.0",
+        "lru-cache": "^5.1.1",
+        "magic-string": "^0.25.7",
+        "merge-source-map": "^1.1.0",
+        "postcss": "^8.1.10",
+        "postcss-modules": "^4.0.0",
+        "postcss-selector-parser": "^6.0.4",
+        "source-map": "^0.6.1"
+      }
+    },
+    "@vue/compiler-ssr": {
+      "version": "3.1.2",
+      "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.1.2.tgz",
+      "integrity": "sha512-BwXo9LFk5OSWdMyZQ4bX1ELHX0Z/9F+ld/OaVnpUPzAZCHslBYLvyKUVDwv2C/lpLjRffpC2DOUEdl1+RP1aGg==",
+      "dev": true,
+      "requires": {
+        "@vue/compiler-dom": "3.1.2",
+        "@vue/shared": "3.1.2"
+      }
+    },
+    "@vue/devtools-api": {
+      "version": "6.0.0-beta.14",
+      "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.0.0-beta.14.tgz",
+      "integrity": "sha512-44fPrrN1cqcs6bFkT0C+yxTM6PZXLbR+ESh1U1j8UD22yO04gXvxH62HApMjLbS3WqJO/iCNC+CYT+evPQh2EQ=="
+    },
+    "@vue/reactivity": {
+      "version": "3.1.2",
+      "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.1.2.tgz",
+      "integrity": "sha512-glJzJoN2xE7I2lRvwKM5u1BHRPTd1yc8iaf//Lai/78/uYAvE5DXp5HzWRFOwMlbRvMGJHIQjOqoxj87cDAaag==",
+      "requires": {
+        "@vue/shared": "3.1.2"
+      }
+    },
+    "@vue/runtime-core": {
+      "version": "3.1.2",
+      "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.1.2.tgz",
+      "integrity": "sha512-gsPZG4dRIkixuuKmoj4P9IHgfT0yaFLcqWOM5F/bCk0nxQn1XtxH8oUehWuET726KhbukvDoJfe9G2CKviy80w==",
+      "requires": {
+        "@vue/reactivity": "3.1.2",
+        "@vue/shared": "3.1.2"
+      }
+    },
+    "@vue/runtime-dom": {
+      "version": "3.1.2",
+      "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.1.2.tgz",
+      "integrity": "sha512-QvINxjLucEZFzp5f0NVu7JqWYCv5TKQfkH2FDs/N6QNE4iKcYtKrWdT0HKfABnVXG28Znqv6rIH0dH4ZAOwxpA==",
+      "requires": {
+        "@vue/runtime-core": "3.1.2",
+        "@vue/shared": "3.1.2",
+        "csstype": "^2.6.8"
+      }
+    },
+    "@vue/shared": {
+      "version": "3.1.2",
+      "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.1.2.tgz",
+      "integrity": "sha512-EmH/poaDWBPJaPILXNI/1fvUbArJQmmTyVCwvvyDYDFnkPoTclAbHRAtyIvqfez7jybTDn077HTNILpxlsoWhg=="
+    },
+    "async-validator": {
+      "version": "3.5.2",
+      "resolved": "https://registry.npmjs.org/async-validator/-/async-validator-3.5.2.tgz",
+      "integrity": "sha512-8eLCg00W9pIRZSB781UUX/H6Oskmm8xloZfr09lz5bikRpBVDlJ3hRVuxxP1SxcwsEYfJ4IU8Q19Y8/893r3rQ=="
+    },
+    "axios": {
+      "version": "0.21.1",
+      "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz",
+      "integrity": "sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==",
+      "requires": {
+        "follow-redirects": "^1.10.0"
+      }
+    },
+    "big.js": {
+      "version": "5.2.2",
+      "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz",
+      "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==",
+      "dev": true
+    },
+    "bluebird": {
+      "version": "3.7.2",
+      "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz",
+      "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==",
+      "dev": true
+    },
+    "colorette": {
+      "version": "1.2.2",
+      "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.2.tgz",
+      "integrity": "sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w==",
+      "dev": true
+    },
+    "consolidate": {
+      "version": "0.16.0",
+      "resolved": "https://registry.npmjs.org/consolidate/-/consolidate-0.16.0.tgz",
+      "integrity": "sha512-Nhl1wzCslqXYTJVDyJCu3ODohy9OfBMB5uD2BiBTzd7w+QY0lBzafkR8y8755yMYHAaMD4NuzbAw03/xzfw+eQ==",
+      "dev": true,
+      "requires": {
+        "bluebird": "^3.7.2"
+      }
+    },
+    "core-js-pure": {
+      "version": "3.15.1",
+      "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.15.1.tgz",
+      "integrity": "sha512-OZuWHDlYcIda8sJLY4Ec6nWq2hRjlyCqCZ+jCflyleMkVt3tPedDVErvHslyS2nbO+SlBFMSBJYvtLMwxnrzjA=="
+    },
+    "cropperjs": {
+      "version": "1.5.12",
+      "resolved": "https://registry.npmjs.org/cropperjs/-/cropperjs-1.5.12.tgz",
+      "integrity": "sha512-re7UdjE5UnwdrovyhNzZ6gathI4Rs3KGCBSc8HCIjUo5hO42CtzyblmWLj6QWVw7huHyDMfpKxhiO2II77nhDw=="
+    },
+    "cssesc": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
+      "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
+      "dev": true
+    },
+    "csstype": {
+      "version": "2.6.17",
+      "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.17.tgz",
+      "integrity": "sha512-u1wmTI1jJGzCJzWndZo8mk4wnPTZd1eOIYTYvuEyOQGfmDl3TrabCCfKnOC86FZwW/9djqTl933UF/cS425i9A=="
+    },
+    "dayjs": {
+      "version": "1.10.5",
+      "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.10.5.tgz",
+      "integrity": "sha512-BUFis41ikLz+65iH6LHQCDm4YPMj5r1YFLdupPIyM4SGcXMmtiLQ7U37i+hGS8urIuqe7I/ou3IS1jVc4nbN4g=="
+    },
+    "element-plus": {
+      "version": "1.0.2-beta.52",
+      "resolved": "https://registry.npmjs.org/element-plus/-/element-plus-1.0.2-beta.52.tgz",
+      "integrity": "sha512-oAuJHwXyvM4dsuOz7HSDPIBVPqRJ1KEzFzGqYdqbBjQ/aw79uCJxvS9Q4q9/XrPMfPire09+bPTypiIaHkNBhA==",
+      "requires": {
+        "@popperjs/core": "^2.4.4",
+        "@types/lodash": "^4.14.161",
+        "async-validator": "^3.4.0",
+        "dayjs": "1.x",
+        "lodash": "^4.17.20",
+        "mitt": "^2.1.0",
+        "normalize-wheel": "^1.0.1",
+        "resize-observer-polyfill": "^1.5.1"
+      }
+    },
+    "emojis-list": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz",
+      "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==",
+      "dev": true
+    },
+    "esbuild": {
+      "version": "0.12.9",
+      "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.12.9.tgz",
+      "integrity": "sha512-MWRhAbMOJ9RJygCrt778rz/qNYgA4ZVj6aXnNPxFjs7PmIpb0fuB9Gmg5uWrr6n++XKwwm/RmSz6RR5JL2Ocsw==",
+      "dev": true
+    },
+    "estree-walker": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
+      "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="
+    },
+    "follow-redirects": {
+      "version": "1.14.1",
+      "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.1.tgz",
+      "integrity": "sha512-HWqDgT7ZEkqRzBvc2s64vSZ/hfOceEol3ac/7tKwzuvEyWx3/4UegXh5oBOIotkGsObyk3xznnSRVADBgWSQVg=="
+    },
+    "fsevents": {
+      "version": "2.3.2",
+      "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
+      "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
+      "dev": true,
+      "optional": true
+    },
+    "function-bind": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
+      "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
+      "dev": true
+    },
+    "generic-names": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/generic-names/-/generic-names-2.0.1.tgz",
+      "integrity": "sha512-kPCHWa1m9wGG/OwQpeweTwM/PYiQLrUIxXbt/P4Nic3LbGjCP0YwrALHW1uNLKZ0LIMg+RF+XRlj2ekT9ZlZAQ==",
+      "dev": true,
+      "requires": {
+        "loader-utils": "^1.1.0"
+      }
+    },
+    "has": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
+      "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
+      "dev": true,
+      "requires": {
+        "function-bind": "^1.1.1"
+      }
+    },
+    "hash-sum": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/hash-sum/-/hash-sum-2.0.0.tgz",
+      "integrity": "sha512-WdZTbAByD+pHfl/g9QSsBIIwy8IT+EsPiKDs0KNX+zSHhdDLFKdZu0BQHljvO+0QI/BasbMSUa8wYNCZTvhslg==",
+      "dev": true
+    },
+    "icss-replace-symbols": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/icss-replace-symbols/-/icss-replace-symbols-1.1.0.tgz",
+      "integrity": "sha1-Bupvg2ead0njhs/h/oEq5dsiPe0=",
+      "dev": true
+    },
+    "icss-utils": {
+      "version": "5.1.0",
+      "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz",
+      "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==",
+      "dev": true
+    },
+    "is-core-module": {
+      "version": "2.4.0",
+      "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.4.0.tgz",
+      "integrity": "sha512-6A2fkfq1rfeQZjxrZJGerpLCTHRNEBiSgnu0+obeJpEPZRUooHgsizvzv0ZjJwOz3iWIHdJtVWJ/tmPr3D21/A==",
+      "dev": true,
+      "requires": {
+        "has": "^1.0.3"
+      }
+    },
+    "json5": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
+      "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
+      "dev": true,
+      "requires": {
+        "minimist": "^1.2.0"
+      }
+    },
+    "loader-utils": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz",
+      "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==",
+      "dev": true,
+      "requires": {
+        "big.js": "^5.2.2",
+        "emojis-list": "^3.0.0",
+        "json5": "^1.0.1"
+      }
+    },
+    "lodash": {
+      "version": "4.17.21",
+      "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
+      "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
+    },
+    "lodash.camelcase": {
+      "version": "4.3.0",
+      "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz",
+      "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=",
+      "dev": true
+    },
+    "lru-cache": {
+      "version": "5.1.1",
+      "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
+      "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
+      "dev": true,
+      "requires": {
+        "yallist": "^3.0.2"
+      }
+    },
+    "magic-string": {
+      "version": "0.25.7",
+      "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz",
+      "integrity": "sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA==",
+      "dev": true,
+      "requires": {
+        "sourcemap-codec": "^1.4.4"
+      }
+    },
+    "merge-source-map": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/merge-source-map/-/merge-source-map-1.1.0.tgz",
+      "integrity": "sha512-Qkcp7P2ygktpMPh2mCQZaf3jhN6D3Z/qVZHSdWvQ+2Ef5HgRAPBO57A77+ENm0CPx2+1Ce/MYKi3ymqdfuqibw==",
+      "dev": true,
+      "requires": {
+        "source-map": "^0.6.1"
+      }
+    },
+    "minimist": {
+      "version": "1.2.5",
+      "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
+      "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
+      "dev": true
+    },
+    "mitt": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/mitt/-/mitt-2.1.0.tgz",
+      "integrity": "sha512-ILj2TpLiysu2wkBbWjAmww7TkZb65aiQO+DkVdUTBpBXq+MHYiETENkKFMtsJZX1Lf4pe4QOrTSjIfUwN5lRdg=="
+    },
+    "nanoid": {
+      "version": "3.1.23",
+      "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.23.tgz",
+      "integrity": "sha512-FiB0kzdP0FFVGDKlRLEQ1BgDzU87dy5NnzjeW9YZNt+/c3+q82EQDUwniSAUxp/F0gFNI1ZhKU1FqYsMuqZVnw==",
+      "dev": true
+    },
+    "normalize-wheel": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/normalize-wheel/-/normalize-wheel-1.0.1.tgz",
+      "integrity": "sha1-rsiGr/2wRQcNhWRH32Ls+GFG7EU="
+    },
+    "path-parse": {
+      "version": "1.0.7",
+      "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
+      "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
+      "dev": true
+    },
+    "postcss": {
+      "version": "8.3.5",
+      "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.3.5.tgz",
+      "integrity": "sha512-NxTuJocUhYGsMiMFHDUkmjSKT3EdH4/WbGF6GCi1NDGk+vbcUTun4fpbOqaPtD8IIsztA2ilZm2DhYCuyN58gA==",
+      "dev": true,
+      "requires": {
+        "colorette": "^1.2.2",
+        "nanoid": "^3.1.23",
+        "source-map-js": "^0.6.2"
+      }
+    },
+    "postcss-modules": {
+      "version": "4.1.3",
+      "resolved": "https://registry.npmjs.org/postcss-modules/-/postcss-modules-4.1.3.tgz",
+      "integrity": "sha512-dBT39hrXe4OAVYJe/2ZuIZ9BzYhOe7t+IhedYeQ2OxKwDpAGlkEN/fR0fGnrbx4BvgbMReRX4hCubYK9cE/pJQ==",
+      "dev": true,
+      "requires": {
+        "generic-names": "^2.0.1",
+        "icss-replace-symbols": "^1.1.0",
+        "lodash.camelcase": "^4.3.0",
+        "postcss-modules-extract-imports": "^3.0.0",
+        "postcss-modules-local-by-default": "^4.0.0",
+        "postcss-modules-scope": "^3.0.0",
+        "postcss-modules-values": "^4.0.0",
+        "string-hash": "^1.1.1"
+      }
+    },
+    "postcss-modules-extract-imports": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz",
+      "integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==",
+      "dev": true
+    },
+    "postcss-modules-local-by-default": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.0.tgz",
+      "integrity": "sha512-sT7ihtmGSF9yhm6ggikHdV0hlziDTX7oFoXtuVWeDd3hHObNkcHRo9V3yg7vCAY7cONyxJC/XXCmmiHHcvX7bQ==",
+      "dev": true,
+      "requires": {
+        "icss-utils": "^5.0.0",
+        "postcss-selector-parser": "^6.0.2",
+        "postcss-value-parser": "^4.1.0"
+      }
+    },
+    "postcss-modules-scope": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz",
+      "integrity": "sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg==",
+      "dev": true,
+      "requires": {
+        "postcss-selector-parser": "^6.0.4"
+      }
+    },
+    "postcss-modules-values": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz",
+      "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==",
+      "dev": true,
+      "requires": {
+        "icss-utils": "^5.0.0"
+      }
+    },
+    "postcss-selector-parser": {
+      "version": "6.0.6",
+      "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.6.tgz",
+      "integrity": "sha512-9LXrvaaX3+mcv5xkg5kFwqSzSH1JIObIx51PrndZwlmznwXRfxMddDvo9gve3gVR8ZTKgoFDdWkbRFmEhT4PMg==",
+      "dev": true,
+      "requires": {
+        "cssesc": "^3.0.0",
+        "util-deprecate": "^1.0.2"
+      }
+    },
+    "postcss-value-parser": {
+      "version": "4.1.0",
+      "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz",
+      "integrity": "sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==",
+      "dev": true
+    },
+    "regenerator-runtime": {
+      "version": "0.13.7",
+      "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz",
+      "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew=="
+    },
+    "resize-observer-polyfill": {
+      "version": "1.5.1",
+      "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz",
+      "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg=="
+    },
+    "resolve": {
+      "version": "1.20.0",
+      "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz",
+      "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==",
+      "dev": true,
+      "requires": {
+        "is-core-module": "^2.2.0",
+        "path-parse": "^1.0.6"
+      }
+    },
+    "rollup": {
+      "version": "2.52.3",
+      "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.52.3.tgz",
+      "integrity": "sha512-QF3Sju8Kl2z0osI4unyOLyUudyhOMK6G0AeqJWgfiyigqLAlnNrfBcDWDx+f1cqn+JU2iIYVkDrgQ6/KtwEfrg==",
+      "dev": true,
+      "requires": {
+        "fsevents": "~2.3.2"
+      }
+    },
+    "schart.js": {
+      "version": "3.0.4",
+      "resolved": "https://registry.npmjs.org/schart.js/-/schart.js-3.0.4.tgz",
+      "integrity": "sha512-uylb2u9rrHX1jyAuSAJUQON8XTfyDKI9kWj1J3fUlCQCkLVZ4HG4+IiV8qm//Z71dqvLI78QZ/fCBw0reB22Zw=="
+    },
+    "source-map": {
+      "version": "0.6.1",
+      "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+      "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
+    },
+    "source-map-js": {
+      "version": "0.6.2",
+      "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-0.6.2.tgz",
+      "integrity": "sha512-/3GptzWzu0+0MBQFrDKzw/DvvMTUORvgY6k6jd/VS6iCR4RDTKWH6v6WPwQoUO8667uQEf9Oe38DxAYWY5F/Ug==",
+      "dev": true
+    },
+    "sourcemap-codec": {
+      "version": "1.4.8",
+      "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz",
+      "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==",
+      "dev": true
+    },
+    "string-hash": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/string-hash/-/string-hash-1.1.3.tgz",
+      "integrity": "sha1-6Kr8CsGFW0Zmkp7X3RJ1311sgRs=",
+      "dev": true
+    },
+    "to-fast-properties": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
+      "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4="
+    },
+    "tslib": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz",
+      "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg=="
+    },
+    "util-deprecate": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+      "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=",
+      "dev": true
+    },
+    "vite": {
+      "version": "2.3.7",
+      "resolved": "https://registry.npmjs.org/vite/-/vite-2.3.7.tgz",
+      "integrity": "sha512-Y0xRz11MPYu/EAvzN94+FsOZHbSvO6FUvHv127CyG7mV6oDoay2bw+g5y9wW3Blf8OY3chaz3nc/DcRe1IQ3Nw==",
+      "dev": true,
+      "requires": {
+        "esbuild": "^0.12.5",
+        "fsevents": "~2.3.1",
+        "postcss": "^8.3.0",
+        "resolve": "^1.19.0",
+        "rollup": "^2.38.5"
+      }
+    },
+    "vue": {
+      "version": "3.1.2",
+      "resolved": "https://registry.npmjs.org/vue/-/vue-3.1.2.tgz",
+      "integrity": "sha512-q/rbKpb7aofax4ugqu2k/uj7BYuNPcd6Z5/qJtfkJQsE0NkwVoCyeSh7IZGH61hChwYn3CEkh4bHolvUPxlQ+w==",
+      "requires": {
+        "@vue/compiler-dom": "3.1.2",
+        "@vue/runtime-dom": "3.1.2",
+        "@vue/shared": "3.1.2"
+      }
+    },
+    "vue-cropperjs": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/vue-cropperjs/-/vue-cropperjs-5.0.0.tgz",
+      "integrity": "sha512-RhnC8O33uRZNkn74aiHZwNHnBJOXWlS4P6gsRI0lw4cZlWjKSCywZI9oSI9POlIPI6OYv30jvnHMXGch85tw7w==",
+      "requires": {
+        "cropperjs": "^1.5.6"
+      }
+    },
+    "vue-i18n": {
+      "version": "9.1.6",
+      "resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-9.1.6.tgz",
+      "integrity": "sha512-FEC4HZkTH6QRIu/A0wlo0VS/GH3w/fuCC6xfvoC8IyhhtbG9A+go9NfW+HZ1ZXdAcO4EWcVQi04M+iSwuxgixw==",
+      "requires": {
+        "@intlify/core-base": "9.1.6",
+        "@intlify/shared": "9.1.6",
+        "@intlify/vue-devtools": "9.1.6",
+        "@vue/devtools-api": "^6.0.0-beta.7"
+      }
+    },
+    "vue-router": {
+      "version": "4.0.10",
+      "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.0.10.tgz",
+      "integrity": "sha512-YbPf6QnZpyyWfnk7CUt2Bme+vo7TLfg1nGZNkvYqKYh4vLaFw6Gn8bPGdmt5m4qrGnKoXLqc4htAsd3dIukICA==",
+      "requires": {
+        "@vue/devtools-api": "^6.0.0-beta.14"
+      }
+    },
+    "vue-schart": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/vue-schart/-/vue-schart-2.0.0.tgz",
+      "integrity": "sha512-qAu3e5wfMcq26wK1xeHExEWfGpnjfoN1R/9QXblNi+AsU/p52X7tTwhi+Fw7H/otfEufhEY2X7z7emaoF4QO+g==",
+      "requires": {
+        "schart.js": "^3.0.0"
+      }
+    },
+    "vuex": {
+      "version": "4.0.2",
+      "resolved": "https://registry.npmjs.org/vuex/-/vuex-4.0.2.tgz",
+      "integrity": "sha512-M6r8uxELjZIK8kTKDGgZTYX/ahzblnzC4isU1tpmEuOIIKmV+TRdc+H4s8ds2NuZ7wpUTdGRzJRtoj+lI+pc0Q==",
+      "requires": {
+        "@vue/devtools-api": "^6.0.0-beta.11"
+      }
+    },
+    "wangeditor": {
+      "version": "4.7.4",
+      "resolved": "https://registry.npmjs.org/wangeditor/-/wangeditor-4.7.4.tgz",
+      "integrity": "sha512-MVWJyFZm3SOhIGFsxDwD6Q0ahIWQTYU0/otHxq85EnfdxtJBJ8c55iKDp3zIvBZaiBawBiYCpRmbn8HOedNj5w==",
+      "requires": {
+        "@babel/runtime": "^7.11.2",
+        "@babel/runtime-corejs3": "^7.11.2",
+        "tslib": "^2.1.0"
+      }
+    },
+    "yallist": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
+      "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
+      "dev": true
+    }
+  }
+}

+ 29 - 54
package.json

@@ -1,56 +1,31 @@
 {
-    "name": "vue-manage-system",
-    "version": "5.0.0",
-    "private": true,
-    "scripts": {
-        "serve": "vue-cli-service serve",
-        "build": "vue-cli-service build",
-        "lint": "vue-cli-service lint"
-    },
-    "dependencies": {
-        "axios": "^0.21.1",
-        "core-js": "~3.9.1",
-        "element-plus": "1.0.2-beta.33",
-        "vue": "~3.0.7",
-        "vue-cropperjs": "^5.0.0",
-        "vue-i18n": "^9.0.0",
-        "vue-loader-v16": "^16.0.0-beta.5.4",
-        "vue-router": "~4.0.4",
-        "vue-schart": "^2.0.0",
-        "vuex": "~4.0.0"
-    },
-    "devDependencies": {
-        "@vue/cli-plugin-babel": "~4.5.11",
-        "@vue/cli-plugin-eslint": "~4.5.11",
-        "@vue/cli-plugin-router": "~4.5.11",
-        "@vue/cli-plugin-vuex": "~4.5.11",
-        "@vue/cli-service": "~4.5.11",
-        "@vue/compiler-sfc": "~3.0.7",
-        "babel-eslint": "~10.1.0",
-        "eslint": "~6.8.0",
-        "eslint-plugin-vue": "~7.7.0",
-        "sass": "~1.32.8",
-        "sass-loader": "~8.0.2",
-        "vue-cli-plugin-element-plus": "0.0.13",
-        "webpack": "4.44.2"
-    },
-    "eslintConfig": {
-        "root": true,
-        "env": {
-            "node": true
-        },
-        "extends": [
-            "plugin:vue/vue3-essential",
-            "eslint:recommended"
-        ],
-        "parserOptions": {
-            "parser": "babel-eslint"
-        },
-        "rules": {}
-    },
-    "browserslist": [
-        "> 1%",
-        "last 2 versions",
-        "not dead"
-    ]
+  "name": "vue-manage-system",
+  "version": "5.1.0",
+  "private": true,
+  "scripts": {
+    "dev": "vite",
+    "build": "vite build",
+    "serve": "vite preview"
+  },
+  "dependencies": {
+    "axios": "^0.21.1",
+    "element-plus": "1.0.2-beta.52",
+    "vue": "^3.1.2",
+    "vue-cropperjs": "^5.0.0",
+    "vue-i18n": "^9.0.0",
+    "vue-router": "^4.0.10",
+    "vue-schart": "^2.0.0",
+    "vuex": "^4.0.2",
+    "wangeditor": "^4.7.4"
+  },
+  "devDependencies": {
+    "vite": "2.3.7",
+    "@vitejs/plugin-vue": "^1.2.3",
+    "@vue/compiler-sfc": "^3.1.2"
+  },
+  "browserslist": [
+    "> 1%",
+    "last 2 versions",
+    "not dead"
+  ]
 }

+ 35 - 33
src/components/Header.vue

@@ -10,11 +10,7 @@
             <div class="header-user-con">
                 <!-- 消息中心 -->
                 <div class="btn-bell">
-                    <el-tooltip
-                        effect="dark"
-                        :content="message?`有${message}条未读消息`:`消息中心`"
-                        placement="bottom"
-                    >
+                    <el-tooltip effect="dark" :content="message?`有${message}条未读消息`:`消息中心`" placement="bottom">
                         <router-link to="/tabs">
                             <i class="el-icon-bell"></i>
                         </router-link>
@@ -36,6 +32,7 @@
                             <a href="https://github.com/lin-xin/vue-manage-system" target="_blank">
                                 <el-dropdown-item>项目仓库</el-dropdown-item>
                             </a>
+                            <el-dropdown-item command="user">个人中心</el-dropdown-item>
                             <el-dropdown-item divided command="loginout">退出登录</el-dropdown-item>
                         </el-dropdown-menu>
                     </template>
@@ -45,41 +42,46 @@
     </div>
 </template>
 <script>
+import { computed, onMounted } from "vue";
+import { useStore } from "vuex";
+import { useRouter } from "vue-router";
 export default {
-    data() {
-        return {
-            fullscreen: false,
-            name: "linxin",
-            message: 2
+    setup() {
+        const username = localStorage.getItem("ms_username");
+        const message = 2;
+
+        const store = useStore();
+        const collapse = computed(() => store.state.collapse);
+        // 侧边栏折叠
+        const collapseChage = () => {
+            store.commit("handleCollapse", !collapse.value);
         };
-    },
-    computed: {
-        username() {
-            let username = localStorage.getItem("ms_username");
-            return username ? username : this.name;
-        },
-        collapse() {
-            return this.$store.state.collapse;
-        }
-    },
-    methods: {
+
+        onMounted(() => {
+            if (document.body.clientWidth < 1500) {
+                collapseChage();
+            }
+        });
+
         // 用户名下拉菜单选择事件
-        handleCommand(command) {
+        const router = useRouter();
+        const handleCommand = (command) => {
             if (command == "loginout") {
                 localStorage.removeItem("ms_username");
-                this.$router.push("/login");
+                router.push("/login");
+            } else if (command == "user") {
+                router.push("/user");
             }
-        },
-        // 侧边栏折叠
-        collapseChage() {
-            this.$store.commit("hadndleCollapse", !this.collapse);
-        }
+        };
+
+        return {
+            username,
+            message,
+            collapse,
+            collapseChage,
+            handleCommand,
+        };
     },
-    mounted() {
-        if (document.body.clientWidth < 1500) {
-            this.collapseChage();
-        }
-    }
 };
 </script>
 <style scoped>

+ 101 - 131
src/components/Sidebar.vue

@@ -1,15 +1,7 @@
 <template>
     <div class="sidebar">
-        <el-menu
-            class="sidebar-el-menu"
-            :default-active="onRoutes"
-            :collapse="collapse"
-            background-color="#324157"
-            text-color="#bfcbd9"
-            active-text-color="#20a0ff"
-            unique-opened
-            router
-        >
+        <el-menu class="sidebar-el-menu" :default-active="onRoutes" :collapse="collapse" background-color="#324157"
+            text-color="#bfcbd9" active-text-color="#20a0ff" unique-opened router>
             <template v-for="item in items">
                 <template v-if="item.subs">
                     <el-submenu :index="item.index" :key="item.index">
@@ -18,23 +10,13 @@
                             <span>{{ item.title }}</span>
                         </template>
                         <template v-for="subItem in item.subs">
-                            <el-submenu
-                                v-if="subItem.subs"
-                                :index="subItem.index"
-                                :key="subItem.index"
-                            >
+                            <el-submenu v-if="subItem.subs" :index="subItem.index" :key="subItem.index">
                                 <template #title>{{ subItem.title }}</template>
-                                <el-menu-item
-                                    v-for="(threeItem, i) in subItem.subs"
-                                    :key="i"
-                                    :index="threeItem.index"
-                                >{{ threeItem.title }}</el-menu-item>
+                                <el-menu-item v-for="(threeItem, i) in subItem.subs" :key="i" :index="threeItem.index">
+                                    {{ threeItem.title }}</el-menu-item>
                             </el-submenu>
-                            <el-menu-item
-                                v-else
-                                :index="subItem.index"
-                                :key="subItem.index"
-                            >{{ subItem.title }}</el-menu-item>
+                            <el-menu-item v-else :index="subItem.index" :key="subItem.index">{{ subItem.title }}
+                            </el-menu-item>
                         </template>
                     </el-submenu>
                 </template>
@@ -50,116 +32,104 @@
 </template>
 
 <script>
-// import bus from "../common/bus";
+import { computed, watch } from "vue";
+import { useStore } from "vuex";
+import { useRoute } from "vue-router";
 export default {
-    data() {
+    setup() {
+        const items = [
+            {
+                icon: "el-icon-lx-home",
+                index: "/dashboard",
+                title: "系统首页",
+            },
+            {
+                icon: "el-icon-lx-cascades",
+                index: "/table",
+                title: "基础表格",
+            },
+            {
+                icon: "el-icon-lx-copy",
+                index: "/tabs",
+                title: "tab选项卡",
+            },
+            {
+                icon: "el-icon-lx-calendar",
+                index: "3",
+                title: "表单相关",
+                subs: [
+                    {
+                        index: "/form",
+                        title: "基本表单",
+                    },
+                    {
+                        index: "/upload",
+                        title: "文件上传",
+                    },
+                    {
+                        index: "4",
+                        title: "三级菜单",
+                        subs: [
+                            {
+                                index: "/editor",
+                                title: "富文本编辑器",
+                            },
+                        ],
+                    },
+                ],
+            },
+            {
+                icon: "el-icon-lx-emoji",
+                index: "/icon",
+                title: "自定义图标",
+            },
+            {
+                icon: "el-icon-pie-chart",
+                index: "/charts",
+                title: "schart图表",
+            },
+            {
+                icon: "el-icon-lx-global",
+                index: "/i18n",
+                title: "国际化功能",
+            },
+            {
+                icon: "el-icon-lx-warn",
+                index: "7",
+                title: "错误处理",
+                subs: [
+                    {
+                        index: "/permission",
+                        title: "权限测试",
+                    },
+                    {
+                        index: "/404",
+                        title: "404页面",
+                    },
+                ],
+            },
+            {
+                icon: "el-icon-lx-redpacket_fill",
+                index: "/donate",
+                title: "支持作者",
+            },
+        ];
+
+        const route = useRoute();
+
+        const onRoutes = computed(() => {
+            return route.path;
+        });
+
+        const store = useStore();
+        const collapse = computed(() => store.state.collapse);
+
         return {
-            items: [
-                {
-                    icon: "el-icon-lx-home",
-                    index: "dashboard",
-                    title: "系统首页"
-                },
-                {
-                    icon: "el-icon-lx-cascades",
-                    index: "table",
-                    title: "基础表格"
-                },
-                {
-                    icon: "el-icon-lx-copy",
-                    index: "tabs",
-                    title: "tab选项卡"
-                },
-                {
-                    icon: "el-icon-lx-calendar",
-                    index: "3",
-                    title: "表单相关",
-                    subs: [
-                        {
-                            index: "form",
-                            title: "基本表单"
-                        },
-                        //         {
-                        //             index: "3-2",
-                        //             title: "三级菜单",
-                        //             subs: [
-                        //                 {
-                        //                     index: "editor",
-                        //                     title: "富文本编辑器"
-                        //                 },
-                        //                 {
-                        //                     index: "markdown",
-                        //                     title: "markdown编辑器"
-                        //                 }
-                        //             ]
-                        //         },
-                        {
-                            index: "upload",
-                            title: "文件上传"
-                        }
-                    ]
-                },
-                {
-                    icon: "el-icon-lx-emoji",
-                    index: "icon",
-                    title: "自定义图标"
-                },
-                {
-                    icon: "el-icon-pie-chart",
-                    index: "charts",
-                    title: "schart图表"
-                },
-                // {
-                //     icon: "el-icon-rank",
-                //     index: "6",
-                //     title: "拖拽组件",
-                //     subs: [
-                //         {
-                //             index: "drag",
-                //             title: "拖拽列表"
-                //         },
-                //         {
-                //             index: "dialog",
-                //             title: "拖拽弹框"
-                //         }
-                //     ]
-                // },
-                {
-                    icon: "el-icon-lx-global",
-                    index: "i18n",
-                    title: "国际化功能"
-                },
-                {
-                    icon: "el-icon-lx-warn",
-                    index: "7",
-                    title: "错误处理",
-                    subs: [
-                        {
-                            index: "permission",
-                            title: "权限测试"
-                        },
-                        {
-                            index: "404",
-                            title: "404页面"
-                        }
-                    ]
-                },
-                {
-                    icon: "el-icon-lx-redpacket_fill",
-                    index: "/donate",
-                    title: "支持作者"
-                }
-            ]
+            items,
+            onRoutes,
+            collapse,
         };
     },
-    computed: {
-        onRoutes() {
-            return this.$route.path.replace("/", "");
-        },
-        collapse(){
-            return this.$store.state.collapse
-        }
-    }
 };
 </script>
 

+ 65 - 62
src/components/Tags.vue

@@ -1,12 +1,7 @@
 <template>
     <div class="tags" v-if="showTags">
         <ul>
-            <li
-                class="tags-li"
-                v-for="(item,index) in tagsList"
-                :class="{'active': isActive(item.path)}"
-                :key="index"
-            >
+            <li class="tags-li" v-for="(item,index) in tagsList" :class="{'active': isActive(item.path)}" :key="index">
                 <router-link :to="item.path" class="tags-li-title">{{item.title}}</router-link>
                 <span class="tags-li-icon" @click="closeTags(index)">
                     <i class="el-icon-close"></i>
@@ -31,78 +26,86 @@
 </template>
 
 <script>
+import { computed } from "vue";
+import { useStore } from "vuex";
+import { onBeforeRouteUpdate, useRoute, useRouter } from "vue-router";
 export default {
-    computed: {
-        tagsList() {
-            return this.$store.state.tagsList;
-        },
-        showTags() {
-            return this.tagsList.length > 0;
-        }
-    },
-    methods: {
-        isActive(path) {
-            return path === this.$route.fullPath;
-        },
+    setup() {
+        const route = useRoute();
+        const router = useRouter();
+        const isActive = (path) => {
+            return path === route.fullPath;
+        };
+
+        const store = useStore();
+        const tagsList = computed(() => store.state.tagsList);
+        const showTags = computed(() => tagsList.value.length > 0);
+
         // 关闭单个标签
-        closeTags(index) {
-            const delItem = this.tagsList[index];
-            this.$store.commit("delTagsItem", { index });
-            const item = this.tagsList[index]
-                ? this.tagsList[index]
-                : this.tagsList[index - 1];
+        const closeTags = (index) => {
+            const delItem = tagsList.value[index];
+            store.commit("delTagsItem", { index });
+            const item = tagsList.value[index]
+                ? tagsList.value[index]
+                : tagsList.value[index - 1];
             if (item) {
-                delItem.path === this.$route.fullPath &&
-                    this.$router.push(item.path);
+                delItem.path === route.fullPath && router.push(item.path);
             } else {
-                this.$router.push("/");
+                router.push("/");
             }
-        },
-        // 关闭全部标签
-        closeAll() {
-            this.$store.commit("clearTags");
-            this.$router.push("/");
-        },
-        // 关闭其他标签
-        closeOther() {
-            const curItem = this.tagsList.filter(item => {
-                return item.path === this.$route.fullPath;
-            });
-            this.$store.commit("closeTagsOther", curItem);
-        },
+        };
+
         // 设置标签
-        setTags(route) {
-            const isExist = this.tagsList.some(item => {
+        const setTags = (route) => {
+            const isExist = tagsList.value.some((item) => {
                 return item.path === route.fullPath;
             });
             if (!isExist) {
-                if (this.tagsList.length >= 8) {
-                    this.$store.commit("delTagsItem", { index: 0 });
+                if (tagsList.value.length >= 8) {
+                    store.commit("delTagsItem", { index: 0 });
                 }
-                this.$store.commit("setTagsItem", {
+                store.commit("setTagsItem", {
                     name: route.name,
                     title: route.meta.title,
-                    path: route.fullPath
+                    path: route.fullPath,
                 });
             }
-        },
-        handleTags(command) {
-            command === "other" ? this.closeOther() : this.closeAll();
-        }
-    },
-    watch: {
-        $route(newValue) {
-            this.setTags(newValue);
-        }
-    },
-    created() {
-        this.setTags(this.$route);
+        };
+        setTags(route);
+        onBeforeRouteUpdate((to) => {
+            setTags(to);
+        });
+
+        // 关闭全部标签
+        const closeAll = () => {
+            store.commit("clearTags");
+            router.push("/");
+        };
+        // 关闭其他标签
+        const closeOther = () => {
+            const curItem = tagsList.value.filter((item) => {
+                return item.path === route.fullPath;
+            });
+            store.commit("closeTagsOther", curItem);
+        };
+        const handleTags = (command) => {
+            command === "other" ? closeOther() : closeAll();
+        };
+
         // 关闭当前页面的标签页
-        // this.$store.commit("closeCurrentTag", {
-        //     $router: this.$router,
-        //     $route: this.$route
+        // store.commit("closeCurrentTag", {
+        //     $router: router,
+        //     $route: route
         // });
-    }
+
+        return {
+            isActive,
+            tagsList,
+            showTags,
+            closeTags,
+            handleTags,
+        };
+    },
 };
 </script>
 

+ 0 - 12
src/plugins/element.js

@@ -4,18 +4,6 @@ import 'element-plus/lib/theme-chalk/index.css'
 import localeZH from 'element-plus/lib/locale/lang/zh-cn'
 import localeEN from 'element-plus/lib/locale/lang/en'
 import messages from '../utils/i18n'
-// console.log(msg)
-
-// const messages = {
-//   [localeEN.name]: {
-//     el: localeEN.el,
-//     i18n: msg.en.i18n,
-//   },
-//   [localeZH.name]: {
-//     el: localeZH.el,
-//     i18n: msg.zh.i18n,
-//   },
-// }
 
 const i18n = createI18n({
   locale: localeZH.name,

+ 29 - 39
src/router/index.js

@@ -1,4 +1,4 @@
-import {createRouter, createWebHistory} from "vue-router";
+import {createRouter, createWebHashHistory} from "vue-router";
 import Home from "../views/Home.vue";
 
 const routes = [
@@ -16,54 +16,42 @@ const routes = [
                 meta: {
                     title: '系统首页'
                 },
-                component: () => import (
-                /* webpackChunkName: "dashboard" */
-                "../views/Dashboard.vue")
+                component: () => import ( /* webpackChunkName: "dashboard" */ "../views/Dashboard.vue")
             }, {
                 path: "/table",
                 name: "basetable",
                 meta: {
                     title: '表格'
                 },
-                component: () => import (
-                /* webpackChunkName: "table" */
-                "../views/BaseTable.vue")
+                component: () => import ( /* webpackChunkName: "table" */ "../views/BaseTable.vue")
             }, {
                 path: "/charts",
                 name: "basecharts",
                 meta: {
                     title: '图表'
                 },
-                component: () => import (
-                /* webpackChunkName: "charts" */
-                "../views/BaseCharts.vue")
+                component: () => import ( /* webpackChunkName: "charts" */ "../views/BaseCharts.vue")
             }, {
                 path: "/form",
                 name: "baseform",
                 meta: {
                     title: '表单'
                 },
-                component: () => import (
-                /* webpackChunkName: "form" */
-                "../views/BaseForm.vue")
+                component: () => import ( /* webpackChunkName: "form" */ "../views/BaseForm.vue")
             }, {
                 path: "/tabs",
                 name: "tabs",
                 meta: {
                     title: 'tab标签'
                 },
-                component: () => import (
-                /* webpackChunkName: "tabs" */
-                "../views/Tabs.vue")
+                component: () => import ( /* webpackChunkName: "tabs" */ "../views/Tabs.vue")
             }, {
                 path: "/donate",
                 name: "donate",
                 meta: {
                     title: '鼓励作者'
                 },
-                component: () => import (
-                /* webpackChunkName: "donate" */
-                "../views/Donate.vue")
+                component: () => import ( /* webpackChunkName: "donate" */ "../views/Donate.vue")
             }, {
                 path: "/permission",
                 name: "permission",
@@ -71,52 +59,56 @@ const routes = [
                     title: '权限管理',
                     permission: true
                 },
-                component: () => import (
-                /* webpackChunkName: "permission" */
-                "../views/Permission.vue")
+                component: () => import ( /* webpackChunkName: "permission" */ "../views/Permission.vue")
             }, {
                 path: "/i18n",
                 name: "i18n",
                 meta: {
                     title: '国际化语言'
                 },
-                component: () => import (
-                /* webpackChunkName: "i18n" */
-                "../views/I18n.vue")
+                component: () => import ( /* webpackChunkName: "i18n" */ "../views/I18n.vue")
             }, {
                 path: "/upload",
                 name: "upload",
                 meta: {
                     title: '上传插件'
                 },
-                component: () => import (
-                /* webpackChunkName: "upload" */
-                "../views/Upload.vue")
+                component: () => import ( /* webpackChunkName: "upload" */ "../views/Upload.vue")
             }, {
                 path: "/icon",
                 name: "icon",
                 meta: {
                     title: '自定义图标'
                 },
-                component: () => import (
-                /* webpackChunkName: "icon" */
-                "../views/Icon.vue")
+                component: () => import ( /* webpackChunkName: "icon" */ "../views/Icon.vue")
             }, {
                 path: '/404',
                 name: '404',
                 meta: {
                     title: '找不到页面'
                 },
-                component: () => import (/* webpackChunkName: "404" */
-                '../views/404.vue')
+                component: () => import (/* webpackChunkName: "404" */ '../views/404.vue')
             }, {
                 path: '/403',
                 name: '403',
                 meta: {
                     title: '没有权限'
                 },
-                component: () => import (/* webpackChunkName: "403" */
-                '../views/403.vue')
+                component: () => import (/* webpackChunkName: "403" */ '../views/403.vue')
+            }, {
+                path: '/user',
+                name: 'user',
+                meta: {
+                    title: '个人中心'
+                },
+                component: () => import (/* webpackChunkName: "user" */ '../views/User.vue')
+            }, {
+                path: '/editor',
+                name: 'editor',
+                meta: {
+                    title: '富文本编辑器'
+                },
+                component: () => import (/* webpackChunkName: "editor" */ '../views/Editor.vue')
             }
         ]
     }, {
@@ -125,14 +117,12 @@ const routes = [
         meta: {
             title: '登录'
         },
-        component: () => import (
-        /* webpackChunkName: "login" */
-        "../views/Login.vue")
+        component: () => import ( /* webpackChunkName: "login" */ "../views/Login.vue")
     }
 ];
 
 const router = createRouter({
-    history: createWebHistory(process.env.BASE_URL),
+    history: createWebHashHistory(),
     routes
 });
 

+ 1 - 1
src/store/index.js

@@ -47,7 +47,7 @@ export default createStore({
             }
         },
         // 侧边栏折叠
-        hadndleCollapse(state, data) {
+        handleCollapse(state, data) {
             state.collapse = data;
         }
     },

+ 52 - 46
src/views/403.vue

@@ -1,56 +1,62 @@
 <template>
-  <div class="error-page">
-      <div class="error-code">4<span>0</span>3</div>
-      <div class="error-desc">啊哦~ 你没有权限访问该页面哦</div>
-      <div class="error-handle">
-          <router-link to="/">
-            <el-button type="primary" size="large">返回首页</el-button>
-          </router-link>
-          <el-button class="error-btn" type="primary" size="large" @click="goBack">返回上一页</el-button>
-      </div>
-  </div>
+    <div class="error-page">
+        <div class="error-code">4<span>0</span>3</div>
+        <div class="error-desc">啊哦~ 你没有权限访问该页面哦</div>
+        <div class="error-handle">
+            <router-link to="/">
+                <el-button type="primary" size="large">返回首页</el-button>
+            </router-link>
+            <el-button class="error-btn" type="primary" size="large" @click="goBack">返回上一页</el-button>
+        </div>
+    </div>
 </template>
 
 <script>
+import { useRouter } from "vue-router";
 export default {
-  methods: {
-      goBack(){
-          this.$router.go(-1);
-      }
-  }
-}
+    name: "404",
+    setup() {
+        const router = useRouter();
+        const goBack = () => {
+            router.go(-1);
+        };
+        return {
+            goBack,
+        };
+    },
+};
 </script>
 
 
 <style scoped>
-    .error-page{
-        display: flex;
-        justify-content: center;
-        align-items: center;
-        flex-direction: column;
-        width: 100%;
-        height: 100%;
-        background: #f3f3f3;
-        box-sizing: border-box;
-    }
-    .error-code{
-        line-height: 1;
-        font-size: 250px;
-        font-weight: bolder;
-        color: #f02d2d;
-    }
-    .error-code span{
-        color: #00a854;
-    }
-    .error-desc{
-        font-size: 30px;
-        color: #777;
-    }
-    .error-handle{
-        margin-top: 30px;
-        padding-bottom: 200px;
-    }
-    .error-btn{
-        margin-left: 100px;
-    }
+.error-page {
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    flex-direction: column;
+    width: 100%;
+    height: 100%;
+    background: #f3f3f3;
+    box-sizing: border-box;
+}
+.error-code {
+    line-height: 1;
+    font-size: 250px;
+    font-weight: bolder;
+    color: #f02d2d;
+}
+.error-code span {
+    color: #00a854;
+}
+.error-desc {
+    font-size: 30px;
+    color: #777;
+}
+.error-handle {
+    margin-top: 30px;
+    padding-bottom: 200px;
+}
+.error-btn {
+    margin-left: 100px;
+}
 </style>

+ 52 - 46
src/views/404.vue

@@ -1,56 +1,62 @@
 <template>
-  <div class="error-page">
-      <div class="error-code">4<span>0</span>4</div>
-      <div class="error-desc">啊哦~ 你所访问的页面不存在</div>
-      <div class="error-handle">
-          <router-link to="/">
-            <el-button type="primary" size="large">返回首页</el-button>
-          </router-link>
-          <el-button class="error-btn" type="primary" size="large" @click="goBack">返回上一页</el-button>
-      </div>
-  </div>
+    <div class="error-page">
+        <div class="error-code">4<span>0</span>4</div>
+        <div class="error-desc">啊哦~ 你所访问的页面不存在</div>
+        <div class="error-handle">
+            <router-link to="/">
+                <el-button type="primary" size="large">返回首页</el-button>
+            </router-link>
+            <el-button class="error-btn" type="primary" size="large" @click="goBack">返回上一页</el-button>
+        </div>
+    </div>
 </template>
 
 <script>
+import { useRouter } from "vue-router";
 export default {
-  methods: {
-      goBack(){
-          this.$router.go(-1);
-      }
-  }
-}
+    name: "404",
+    setup() {
+        const router = useRouter();
+        const goBack = () => {
+            router.go(-1);
+        };
+        return {
+            goBack,
+        };
+    },
+};
 </script>
 
 
 <style scoped>
-    .error-page{
-        display: flex;
-        justify-content: center;
-        align-items: center;
-        flex-direction: column;
-        width: 100%;
-        height: 100%;
-        background: #f3f3f3;
-        box-sizing: border-box;
-    }
-    .error-code{
-        line-height: 1;
-        font-size: 250px;
-        font-weight: bolder;
-        color: #2d8cf0;
-    }
-    .error-code span{
-        color: #00a854;
-    }
-    .error-desc{
-        font-size: 30px;
-        color: #777;
-    }
-    .error-handle{
-        margin-top: 30px;
-        padding-bottom: 200px;
-    }
-    .error-btn{
-        margin-left: 100px;
-    }
+.error-page {
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    flex-direction: column;
+    width: 100%;
+    height: 100%;
+    background: #f3f3f3;
+    box-sizing: border-box;
+}
+.error-code {
+    line-height: 1;
+    font-size: 250px;
+    font-weight: bolder;
+    color: #2d8cf0;
+}
+.error-code span {
+    color: #00a854;
+}
+.error-desc {
+    font-size: 30px;
+    color: #777;
+}
+.error-handle {
+    margin-top: 30px;
+    padding-bottom: 200px;
+}
+.error-btn {
+    margin-left: 100px;
+}
 </style>

+ 0 - 5
src/views/About.vue

@@ -1,5 +0,0 @@
-<template>
-  <div class="about">
-    <h1>This is an about page</h1>
-  </div>
-</template>

+ 90 - 81
src/views/BaseCharts.vue

@@ -11,10 +11,7 @@
             <div class="plugins-tips">
                 vue-schart:vue.js封装sChart.js的图表组件。
                 访问地址:
-                <a
-                    href="https://github.com/lin-xin/vue-schart"
-                    target="_blank"
-                >vue-schart</a>
+                <a href="https://github.com/lin-xin/vue-schart" target="_blank">vue-schart</a>
             </div>
             <div class="schart-box">
                 <div class="content-title">柱状图</div>
@@ -37,95 +34,107 @@
 </template>
 
 <script>
-import Schart from 'vue-schart';
+import Schart from "vue-schart";
 export default {
-    name: 'basecharts',
+    name: "basecharts",
     components: {
-        Schart
+        Schart,
     },
-    data() {
-        return {
-            options1: {
-                type: 'bar',
-                title: {
-                    text: '最近一周各品类销售图'
-                },
-                bgColor: '#fbfbfb',
-                labels: ['周一', '周二', '周三', '周四', '周五'],
-                datasets: [
-                    {
-                        label: '家电',
-                        fillColor: 'rgba(241, 49, 74, 0.5)',
-                        data: [234, 278, 270, 190, 230]
-                    },
-                    {
-                        label: '百货',
-                        data: [164, 178, 190, 135, 160]
-                    },
-                    {
-                        label: '食品',
-                        data: [144, 198, 150, 235, 120]
-                    }
-                ]
+    setup() {
+        const options1 = {
+            type: "bar",
+            title: {
+                text: "最近一周各品类销售图",
             },
-            options2: {
-                type: 'line',
-                title: {
-                    text: '最近几个月各品类销售趋势图'
+            bgColor: "#fbfbfb",
+            labels: ["周一", "周二", "周三", "周四", "周五"],
+            datasets: [
+                {
+                    label: "家电",
+                    fillColor: "rgba(241, 49, 74, 0.5)",
+                    data: [234, 278, 270, 190, 230],
+                },
+                {
+                    label: "百货",
+                    data: [164, 178, 190, 135, 160],
+                },
+                {
+                    label: "食品",
+                    data: [144, 198, 150, 235, 120],
                 },
-                bgColor: '#fbfbfb',
-                labels: ['6月', '7月', '8月', '9月', '10月'],
-                datasets: [
-                    {
-                        label: '家电',
-                        data: [234, 278, 270, 190, 230]
-                    },
-                    {
-                        label: '百货',
-                        data: [164, 178, 150, 135, 160]
-                    },
-                    {
-                        label: '食品',
-                        data: [114, 138, 200, 235, 190]
-                    }
-                ]
+            ],
+        };
+        const options2 = {
+            type: "line",
+            title: {
+                text: "最近几个月各品类销售趋势图",
             },
-            options3: {
-                type: 'pie',
-                title: {
-                    text: '服装品类销售饼状图'
+            bgColor: "#fbfbfb",
+            labels: ["6月", "7月", "8月", "9月", "10月"],
+            datasets: [
+                {
+                    label: "家电",
+                    data: [234, 278, 270, 190, 230],
+                },
+                {
+                    label: "百货",
+                    data: [164, 178, 150, 135, 160],
                 },
-                legend: {
-                    position: 'left'
+                {
+                    label: "食品",
+                    data: [114, 138, 200, 235, 190],
                 },
-                bgColor: '#fbfbfb',
-                labels: ['T恤', '牛仔裤', '连衣裙', '毛衣', '七分裤', '短裙', '羽绒服'],
-                datasets: [
-                    {
-                        data: [334, 278, 190, 235, 260, 200, 141]
-                    }
-                ]
+            ],
+        };
+        const options3 = {
+            type: "pie",
+            title: {
+                text: "服装品类销售饼状图",
             },
-            options4: {
-                type: 'ring',
-                title: {
-                    text: '环形三等分'
+            legend: {
+                position: "left",
+            },
+            bgColor: "#fbfbfb",
+            labels: [
+                "T恤",
+                "牛仔裤",
+                "连衣裙",
+                "毛衣",
+                "七分裤",
+                "短裙",
+                "羽绒服",
+            ],
+            datasets: [
+                {
+                    data: [334, 278, 190, 235, 260, 200, 141],
                 },
-                showValue: false,
-                legend: {
-                    position: 'bottom',
-                    bottom: 40
+            ],
+        };
+        const options4 = {
+            type: "ring",
+            title: {
+                text: "环形三等分",
+            },
+            showValue: false,
+            legend: {
+                position: "bottom",
+                bottom: 40,
+            },
+            bgColor: "#fbfbfb",
+            labels: ["vue", "react", "angular"],
+            datasets: [
+                {
+                    data: [500, 500, 500],
                 },
-                bgColor: '#fbfbfb',
-                labels: ['vue', 'react', 'angular'],
-                datasets: [
-                    {
-                        data: [500, 500, 500]
-                    }
-                ]
-            }
+            ],
         };
-    }
+        return {
+            options1,
+            options2,
+            options3,
+            options4,
+        };
+    },
 };
 </script>
 

+ 113 - 90
src/views/BaseForm.vue

@@ -10,11 +10,11 @@
         </div>
         <div class="container">
             <div class="form-box">
-                <el-form ref="form" :model="form" label-width="80px">
-                    <el-form-item label="表单名称">
+                <el-form ref="formRef" :rules="rules" :model="form" label-width="80px">
+                    <el-form-item label="表单名称" prop="name">
                         <el-input v-model="form.name"></el-input>
                     </el-form-item>
-                    <el-form-item label="选择器">
+                    <el-form-item label="选择器" prop="region">
                         <el-select v-model="form.region" placeholder="请选择">
                             <el-option key="bbk" label="步步高" value="bbk"></el-option>
                             <el-option key="xtc" label="小天才" value="xtc"></el-option>
@@ -23,48 +23,45 @@
                     </el-form-item>
                     <el-form-item label="日期时间">
                         <el-col :span="11">
-                            <el-date-picker
-                                type="date"
-                                placeholder="选择日期"
-                                v-model="form.date1"
-                                style="width: 100%;"
-                            ></el-date-picker>
+                            <el-form-item prop="date1">
+                                <el-date-picker type="date" placeholder="选择日期" v-model="form.date1"
+                                    style="width: 100%;"></el-date-picker>
+                            </el-form-item>
                         </el-col>
                         <el-col class="line" :span="2">-</el-col>
                         <el-col :span="11">
-                            <el-time-picker
-                                placeholder="选择时间"
-                                v-model="form.date2"
-                                style="width: 100%;"
-                            ></el-time-picker>
+                            <el-form-item prop="date2">
+                                <el-time-picker placeholder="选择时间" v-model="form.date2" style="width: 100%;">
+                                </el-time-picker>
+                            </el-form-item>
                         </el-col>
                     </el-form-item>
-                    <el-form-item label="城市级联">
+                    <el-form-item label="城市级联" prop="options">
                         <el-cascader :options="options" v-model="form.options"></el-cascader>
                     </el-form-item>
-                    <el-form-item label="选择开关">
+                    <el-form-item label="选择开关" prop="delivery">
                         <el-switch v-model="form.delivery"></el-switch>
                     </el-form-item>
-                    <el-form-item label="多选框">
+                    <el-form-item label="多选框" prop="type">
                         <el-checkbox-group v-model="form.type">
                             <el-checkbox label="步步高" name="type"></el-checkbox>
                             <el-checkbox label="小天才" name="type"></el-checkbox>
                             <el-checkbox label="imoo" name="type"></el-checkbox>
                         </el-checkbox-group>
                     </el-form-item>
-                    <el-form-item label="单选框">
+                    <el-form-item label="单选框" prop="resource">
                         <el-radio-group v-model="form.resource">
                             <el-radio label="步步高"></el-radio>
                             <el-radio label="小天才"></el-radio>
                             <el-radio label="imoo"></el-radio>
                         </el-radio-group>
                     </el-form-item>
-                    <el-form-item label="文本框">
+                    <el-form-item label="文本框" prop="desc">
                         <el-input type="textarea" rows="5" v-model="form.desc"></el-input>
                     </el-form-item>
                     <el-form-item>
                         <el-button type="primary" @click="onSubmit">表单提交</el-button>
-                        <el-button>取消</el-button>
+                        <el-button @click="onReset">重置表单</el-button>
                     </el-form-item>
                 </el-form>
             </div>
@@ -73,79 +70,105 @@
 </template>
 
 <script>
+import { reactive, ref } from "vue";
+import { ElMessage } from "element-plus";
 export default {
-    name: 'baseform',
-    data() {
-        return {
-            options: [
-                {
-                    value: 'guangdong',
-                    label: '广东省',
-                    children: [
-                        {
-                            value: 'guangzhou',
-                            label: '广州市',
-                            children: [
-                                {
-                                    value: 'tianhe',
-                                    label: '天河区'
-                                },
-                                {
-                                    value: 'haizhu',
-                                    label: '海珠区'
-                                }
-                            ]
-                        },
-                        {
-                            value: 'dongguan',
-                            label: '东莞市',
-                            children: [
-                                {
-                                    value: 'changan',
-                                    label: '长安镇'
-                                },
-                                {
-                                    value: 'humen',
-                                    label: '虎门镇'
-                                }
-                            ]
-                        }
-                    ]
-                },
-                {
-                    value: 'hunan',
-                    label: '湖南省',
-                    children: [
-                        {
-                            value: 'changsha',
-                            label: '长沙市',
-                            children: [
-                                {
-                                    value: 'yuelu',
-                                    label: '岳麓区'
-                                }
-                            ]
-                        }
-                    ]
-                }
+    name: "baseform",
+    setup() {
+        const options = [
+            {
+                value: "guangdong",
+                label: "广东省",
+                children: [
+                    {
+                        value: "guangzhou",
+                        label: "广州市",
+                        children: [
+                            {
+                                value: "tianhe",
+                                label: "天河区",
+                            },
+                            {
+                                value: "haizhu",
+                                label: "海珠区",
+                            },
+                        ],
+                    },
+                    {
+                        value: "dongguan",
+                        label: "东莞市",
+                        children: [
+                            {
+                                value: "changan",
+                                label: "长安镇",
+                            },
+                            {
+                                value: "humen",
+                                label: "虎门镇",
+                            },
+                        ],
+                    },
+                ],
+            },
+            {
+                value: "hunan",
+                label: "湖南省",
+                children: [
+                    {
+                        value: "changsha",
+                        label: "长沙市",
+                        children: [
+                            {
+                                value: "yuelu",
+                                label: "岳麓区",
+                            },
+                        ],
+                    },
+                ],
+            },
+        ];
+        const rules = {
+            name: [
+                { required: true, message: "请输入表单名称", trigger: "blur" },
             ],
-            form: {
-                name: '',
-                region: '',
-                date1: '',
-                date2: '',
-                delivery: true,
-                type: ['步步高'],
-                resource: '小天才',
-                desc: '',
-                options: []
-            }
+        };
+        const formRef = ref(null);
+        const form = reactive({
+            name: "",
+            region: "",
+            date1: "",
+            date2: "",
+            delivery: true,
+            type: ["步步高"],
+            resource: "小天才",
+            desc: "",
+            options: [],
+        });
+        // 提交
+        const onSubmit = () => {
+            // 表单校验
+            formRef.value.validate((valid) => {
+                if (valid) {
+                    console.log(form);
+                    ElMessage.success("提交成功!");
+                } else {
+                    return false;
+                }
+            });
+        };
+        // 重置
+        const onReset = () => {
+            formRef.value.resetFields();
+        };
+
+        return {
+            options,
+            rules,
+            formRef,
+            form,
+            onSubmit,
+            onReset,
         };
     },
-    methods: {
-        onSubmit() {
-            this.$message.success('提交成功!');
-        }
-    }
 };
 </script>

+ 85 - 116
src/views/BaseTable.vue

@@ -9,12 +9,6 @@
         </div>
         <div class="container">
             <div class="handle-box">
-                <el-button
-                    type="primary"
-                    icon="el-icon-delete"
-                    class="handle-del mr10"
-                    @click="delAllSelection"
-                >批量删除</el-button>
                 <el-select v-model="query.address" placeholder="地址" class="handle-select mr10">
                     <el-option key="1" label="广东省" value="广东省"></el-option>
                     <el-option key="2" label="湖南省" value="湖南省"></el-option>
@@ -22,15 +16,7 @@
                 <el-input v-model="query.name" placeholder="用户名" class="handle-input mr10"></el-input>
                 <el-button type="primary" icon="el-icon-search" @click="handleSearch">搜索</el-button>
             </div>
-            <el-table
-                :data="tableData"
-                border
-                class="table"
-                ref="multipleTable"
-                header-cell-class-name="table-header"
-                @selection-change="handleSelectionChange"
-            >
-                <el-table-column type="selection" width="55" align="center"></el-table-column>
+            <el-table :data="tableData" border class="table" ref="multipleTable" header-cell-class-name="table-header">
                 <el-table-column prop="id" label="ID" width="55" align="center"></el-table-column>
                 <el-table-column prop="name" label="用户名"></el-table-column>
                 <el-table-column label="账户余额">
@@ -38,60 +24,42 @@
                 </el-table-column>
                 <el-table-column label="头像(查看大图)" align="center">
                     <template #default="scope">
-                        <el-image
-                            class="table-td-thumb"
-                            :src="scope.row.thumb"
-                            :preview-src-list="[scope.row.thumb]"
-                        ></el-image>
+                        <el-image class="table-td-thumb" :src="scope.row.thumb" :preview-src-list="[scope.row.thumb]">
+                        </el-image>
                     </template>
                 </el-table-column>
                 <el-table-column prop="address" label="地址"></el-table-column>
                 <el-table-column label="状态" align="center">
                     <template #default="scope">
-                        <el-tag
-                            :type="
+                        <el-tag :type="
                                 scope.row.state === '成功'
                                     ? 'success'
                                     : scope.row.state === '失败'
                                     ? 'danger'
                                     : ''
-                            "
-                        >{{ scope.row.state }}</el-tag>
+                            ">{{ scope.row.state }}</el-tag>
                     </template>
                 </el-table-column>
 
                 <el-table-column prop="date" label="注册时间"></el-table-column>
                 <el-table-column label="操作" width="180" align="center">
                     <template #default="scope">
-                        <el-button
-                            type="text"
-                            icon="el-icon-edit"
-                            @click="handleEdit(scope.$index, scope.row)"
-                        >编辑</el-button>
-                        <el-button
-                            type="text"
-                            icon="el-icon-delete"
-                            class="red"
-                            @click="handleDelete(scope.$index, scope.row)"
-                        >删除</el-button>
+                        <el-button type="text" icon="el-icon-edit" @click="handleEdit(scope.$index, scope.row)">编辑
+                        </el-button>
+                        <el-button type="text" icon="el-icon-delete" class="red"
+                            @click="handleDelete(scope.$index, scope.row)">删除</el-button>
                     </template>
                 </el-table-column>
             </el-table>
             <div class="pagination">
-                <el-pagination
-                    background
-                    layout="total, prev, pager, next"
-                    :current-page="query.pageIndex"
-                    :page-size="query.pageSize"
-                    :total="pageTotal"
-                    @current-change="handlePageChange"
-                ></el-pagination>
+                <el-pagination background layout="total, prev, pager, next" :current-page="query.pageIndex"
+                    :page-size="query.pageSize" :total="pageTotal" @current-change="handlePageChange"></el-pagination>
             </div>
         </div>
 
         <!-- 编辑弹出框 -->
         <el-dialog title="编辑" v-model="editVisible" width="30%">
-            <el-form ref="form" :model="form" label-width="70px">
+            <el-form label-width="70px">
                 <el-form-item label="用户名">
                     <el-input v-model="form.name"></el-input>
                 </el-form-item>
@@ -110,88 +78,89 @@
 </template>
 
 <script>
+import { ref, reactive } from "vue";
+import { ElMessage, ElMessageBox } from "element-plus";
 import { fetchData } from "../api/index";
+
 export default {
     name: "basetable",
-    data() {
-        return {
-            query: {
-                address: "",
-                name: "",
-                pageIndex: 1,
-                pageSize: 10
-            },
-            tableData: [],
-            multipleSelection: [],
-            delList: [],
-            editVisible: false,
-            pageTotal: 0,
-            form: {},
-            idx: -1,
-            id: -1
-        };
-    },
-    created() {
-        this.getData();
-    },
-    methods: {
-        // 获取 easy-mock 的模拟数据
-        getData() {
-            fetchData(this.query).then(res => {
-                console.log(res);
-                this.tableData = res.list;
-                this.pageTotal = res.pageTotal || 50;
+    setup() {
+        const query = reactive({
+            address: "",
+            name: "",
+            pageIndex: 1,
+            pageSize: 10,
+        });
+        const tableData = ref([]);
+        const pageTotal = ref(0);
+        // 获取表格数据
+        const getData = () => {
+            fetchData(query).then((res) => {
+                tableData.value = res.list;
+                pageTotal.value = res.pageTotal || 50;
             });
-        },
-        // 触发搜索按钮
-        handleSearch() {
-            this.$set(this.query, "pageIndex", 1);
-            this.getData();
-        },
+        };
+        getData();
+
+        // 查询操作
+        const handleSearch = () => {
+            query.pageIndex = 1;
+            getData();
+        };
+        // 分页导航
+        const handlePageChange = (val) => {
+            query.pageIndex = val;
+            getData();
+        };
+
         // 删除操作
-        handleDelete(index) {
+        const handleDelete = (index) => {
             // 二次确认删除
-            this.$confirm("确定要删除吗?", "提示", {
-                type: "warning"
+            ElMessageBox.confirm("确定要删除吗?", "提示", {
+                type: "warning",
             })
                 .then(() => {
-                    this.$message.success("删除成功");
-                    this.tableData.splice(index, 1);
+                    ElMessage.success("删除成功");
+                    tableData.value.splice(index, 1);
                 })
                 .catch(() => {});
-        },
-        // 多选操作
-        handleSelectionChange(val) {
-            this.multipleSelection = val;
-        },
-        delAllSelection() {
-            const length = this.multipleSelection.length;
-            let str = "";
-            this.delList = this.delList.concat(this.multipleSelection);
-            for (let i = 0; i < length; i++) {
-                str += this.multipleSelection[i].name + " ";
-            }
-            this.$message.error(`删除了${str}`);
-            this.multipleSelection = [];
-        },
-        // 编辑操作
-        handleEdit(index, row) {
-            this.idx = index;
-            this.form = row;
-            this.editVisible = true;
-        },
-        // 保存编辑
-        saveEdit() {
-            this.editVisible = false;
-            this.$message.success(`修改第 ${this.idx + 1} 行成功`);
-            this.$set(this.tableData, this.idx, this.form);
-        },
-        // 分页导航
-        handlePageChange(val) {
-            this.$set(this.query, "pageIndex", val);
-            this.getData();
-        }
-    }
+        };
+
+        // 表格编辑时弹窗和保存
+        const editVisible = ref(false);
+        let form = reactive({
+            name: "",
+            address: "",
+        });
+        let idx = -1;
+        const handleEdit = (index, row) => {
+            idx = index;
+            Object.keys(form).forEach((item) => {
+                form[item] = row[item];
+            });
+            editVisible.value = true;
+        };
+        const saveEdit = () => {
+            editVisible.value = false;
+            ElMessage.success(`修改第 ${idx + 1} 行成功`);
+            Object.keys(form).forEach((item) => {
+                tableData.value[idx][item] = form[item];
+            });
+        };
+
+        return {
+            query,
+            tableData,
+            pageTotal,
+            editVisible,
+            form,
+            handleSearch,
+            handlePageChange,
+            handleDelete,
+            handleEdit,
+            saveEdit,
+        };
+    },
 };
 </script>
 

+ 100 - 111
src/views/Dashboard.vue

@@ -84,12 +84,9 @@
                         </el-table-column>
                         <el-table-column>
                             <template #default="scope">
-                                <div
-                                    class="todo-item"
-                                    :class="{
+                                <div class="todo-item" :class="{
                                         'todo-item-del': scope.row.status,
-                                    }"
-                                >{{ scope.row.title }}</div>
+                                    }">{{ scope.row.title }}</div>
                             </template>
                         </el-table-column>
                         <el-table-column width="60">
@@ -119,131 +116,123 @@
 
 <script>
 import Schart from "vue-schart";
+import { reactive } from "vue";
 export default {
     name: "dashboard",
-    data() {
-        return {
-            name: localStorage.getItem("ms_username"),
-            todoList: [
-                {
-                    title: "今天要修复100个bug",
-                    status: false
-                },
-                {
-                    title: "今天要修复100个bug",
-                    status: false
-                },
+    components: { Schart },
+    setup() {
+        const name = localStorage.getItem("ms_username");
+        const role = name === "admin" ? "超级管理员" : "普通用户";
+
+        const data = reactive([
+            {
+                name: "2018/09/04",
+                value: 1083,
+            },
+            {
+                name: "2018/09/05",
+                value: 941,
+            },
+            {
+                name: "2018/09/06",
+                value: 1139,
+            },
+            {
+                name: "2018/09/07",
+                value: 816,
+            },
+            {
+                name: "2018/09/08",
+                value: 327,
+            },
+            {
+                name: "2018/09/09",
+                value: 228,
+            },
+            {
+                name: "2018/09/10",
+                value: 1065,
+            },
+        ]);
+        const options = {
+            type: "bar",
+            title: {
+                text: "最近一周各品类销售图",
+            },
+            xRorate: 25,
+            labels: ["周一", "周二", "周三", "周四", "周五"],
+            datasets: [
                 {
-                    title: "今天要写100行代码加几个bug吧",
-                    status: false
+                    label: "家电",
+                    data: [234, 278, 270, 190, 230],
                 },
                 {
-                    title: "今天要修复100个bug",
-                    status: false
+                    label: "百货",
+                    data: [164, 178, 190, 135, 160],
                 },
                 {
-                    title: "今天要修复100个bug",
-                    status: true
+                    label: "食品",
+                    data: [144, 198, 150, 235, 120],
                 },
-                {
-                    title: "今天要写100行代码加几个bug吧",
-                    status: true
-                }
             ],
-            data: [
-                {
-                    name: "2018/09/04",
-                    value: 1083
-                },
-                {
-                    name: "2018/09/05",
-                    value: 941
-                },
-                {
-                    name: "2018/09/06",
-                    value: 1139
-                },
+        };
+        const options2 = {
+            type: "line",
+            title: {
+                text: "最近几个月各品类销售趋势图",
+            },
+            labels: ["6月", "7月", "8月", "9月", "10月"],
+            datasets: [
                 {
-                    name: "2018/09/07",
-                    value: 816
+                    label: "家电",
+                    data: [234, 278, 270, 190, 230],
                 },
                 {
-                    name: "2018/09/08",
-                    value: 327
+                    label: "百货",
+                    data: [164, 178, 150, 135, 160],
                 },
                 {
-                    name: "2018/09/09",
-                    value: 228
+                    label: "食品",
+                    data: [74, 118, 200, 235, 90],
                 },
-                {
-                    name: "2018/09/10",
-                    value: 1065
-                }
             ],
-            options: {
-                type: "bar",
-                title: {
-                    text: "最近一周各品类销售图"
-                },
-                xRorate: 25,
-                labels: ["周一", "周二", "周三", "周四", "周五"],
-                datasets: [
-                    {
-                        label: "家电",
-                        data: [234, 278, 270, 190, 230]
-                    },
-                    {
-                        label: "百货",
-                        data: [164, 178, 190, 135, 160]
-                    },
-                    {
-                        label: "食品",
-                        data: [144, 198, 150, 235, 120]
-                    }
-                ]
+        };
+        const todoList = reactive([
+            {
+                title: "今天要修复100个bug",
+                status: false,
             },
-            options2: {
-                type: "line",
-                title: {
-                    text: "最近几个月各品类销售趋势图"
-                },
-                labels: ["6月", "7月", "8月", "9月", "10月"],
-                datasets: [
-                    {
-                        label: "家电",
-                        data: [234, 278, 270, 190, 230]
-                    },
-                    {
-                        label: "百货",
-                        data: [164, 178, 150, 135, 160]
-                    },
-                    {
-                        label: "食品",
-                        data: [74, 118, 200, 235, 90]
-                    }
-                ]
-            }
+            {
+                title: "今天要修复100个bug",
+                status: false,
+            },
+            {
+                title: "今天要写100行代码加几个bug吧",
+                status: false,
+            },
+            {
+                title: "今天要修复100个bug",
+                status: false,
+            },
+            {
+                title: "今天要修复100个bug",
+                status: true,
+            },
+            {
+                title: "今天要写100行代码加几个bug吧",
+                status: true,
+            },
+        ]);
+
+        return {
+            name,
+            data,
+            options,
+            options2,
+            todoList,
+            role,
         };
     },
-    components: {
-        Schart
-    },
-    computed: {
-        role() {
-            return this.name === "admin" ? "超级管理员" : "普通用户";
-        }
-    },
-
-    methods: {
-        changeDate() {
-            const now = new Date().getTime();
-            this.data.forEach((item, index) => {
-                const date = new Date(now - (6 - index) * 86400000);
-                item.name = `${date.getFullYear()}/${date.getMonth() +
-                    1}/${date.getDate()}`;
-            });
-        }
-    }
 };
 </script>
 

+ 58 - 0
src/views/Editor.vue

@@ -0,0 +1,58 @@
+<template>
+    <div>
+        <div class="crumbs">
+            <el-breadcrumb separator="/">
+                <el-breadcrumb-item>
+                    <i class="el-icon-lx-calendar"></i> 表单
+                </el-breadcrumb-item>
+                <el-breadcrumb-item>富文本编辑器</el-breadcrumb-item>
+            </el-breadcrumb>
+        </div>
+        <div class="container">
+            <div class="plugins-tips">
+                wangEditor:轻量级 web 富文本编辑器,配置方便,使用简单。
+                访问地址:
+                <a href="https://www.wangeditor.com/doc/" target="_blank">wangEditor</a>
+            </div>
+            <div class="mgb20" ref='editor'></div>
+            <el-button type="primary" @click="syncHTML">提交</el-button>
+        </div>
+    </div>
+</template>
+
+<script>
+import WangEditor from "wangEditor";
+import { ref, reactive, onMounted, onBeforeUnmount } from "vue";
+export default {
+    name: "editor",
+    setup() {
+        const editor = ref(null);
+        const content = reactive({
+            html: "",
+            text: "",
+        });
+        let instance;
+        onMounted(() => {
+            instance = new WangEditor(editor.value);
+            instance.config.zIndex = 1;
+            instance.create();
+        });
+        onBeforeUnmount(() => {
+            instance.destroy();
+            instance = null;
+        });
+        const syncHTML = () => {
+            content.html = instance.txt.html();
+            console.log(content.html);
+        };
+        return {
+            syncHTML,
+            editor,
+            content,
+        };
+    },
+};
+</script>
+
+<style>
+</style>

+ 16 - 11
src/views/Home.vue

@@ -18,22 +18,27 @@
     </div>
 </template>
 <script>
-import vHeader from "../components/Header";
-import vSidebar from "../components/Sidebar";
+import { computed } from "vue";
+import { useStore } from "vuex";
+import vHeader from "../components/Header.vue";
+import vSidebar from "../components/Sidebar.vue";
 import vTags from "../components/Tags.vue";
 export default {
     components: {
         vHeader,
         vSidebar,
-        vTags
+        vTags,
+    },
+    setup() {
+        const store = useStore();
+        const tagsList = computed(() =>
+            store.state.tagsList.map((item) => item.name)
+        );
+        const collapse = computed(() => store.state.collapse);
+        return {
+            tagsList,
+            collapse,
+        };
     },
-    computed: {
-        tagsList() {
-            return this.$store.state.tagsList.map(item => item.name);
-        },
-        collapse() {
-            return this.$store.state.collapse;
-        }
-    }
 };
 </script>

+ 141 - 146
src/views/Icon.vue

@@ -9,9 +9,7 @@
         </div>
         <div class="container">
             <h2>使用方法</h2>
-            <p
-                style="line-height: 50px;"
-            >直接通过设置类名为 el-icon-lx-iconName 来使用即可。例如:(共{{iconList.length}}个图标)</p>
+            <p style="line-height: 50px;">直接通过设置类名为 el-icon-lx-iconName 来使用即可。例如:(共{{iconList.length}}个图标)</p>
             <p class="example-p">
                 <i class="el-icon-lx-redpacket_fill" style="font-size: 30px;color: #ff5900"></i>
                 <span>&lt;i class=&quot;el-icon-lx-redpacket_fill&quot;&gt;&lt;/i&gt;</span>
@@ -27,13 +25,7 @@
             <br />
             <h2>图标</h2>
             <div class="search-box">
-                <el-input
-                    class="search"
-                    size="large"
-                    v-model="keyword"
-                    clearable
-                    placeholder="请输入图标名称"
-                ></el-input>
+                <el-input class="search" size="large" v-model="keyword" clearable placeholder="请输入图标名称"></el-input>
             </div>
             <ul>
                 <li class="icon-li" v-for="(item,index) in list" :key="index">
@@ -48,147 +40,150 @@
 </template>
 
 <script>
+import { computed, ref } from "vue";
 export default {
     name: "icon",
-    data() {
+    setup() {
+        const iconList = [
+            "attentionforbid",
+            "attentionforbidfill",
+            "attention",
+            "attentionfill",
+            "tag",
+            "tagfill",
+            "people",
+            "peoplefill",
+            "notice",
+            "noticefill",
+            "mobile",
+            "mobilefill",
+            "voice",
+            "voicefill",
+            "unlock",
+            "lock",
+            "home",
+            "homefill",
+            "delete",
+            "deletefill",
+            "notification",
+            "notificationfill",
+            "notificationforbidfill",
+            "like",
+            "likefill",
+            "comment",
+            "commentfill",
+            "camera",
+            "camerafill",
+            "warn",
+            "warnfill",
+            "time",
+            "timefill",
+            "location",
+            "locationfill",
+            "favor",
+            "favorfill",
+            "skin",
+            "skinfill",
+            "news",
+            "newsfill",
+            "record",
+            "recordfill",
+            "emoji",
+            "emojifill",
+            "message",
+            "messagefill",
+            "goods",
+            "goodsfill",
+            "crown",
+            "crownfill",
+            "move",
+            "add",
+            "hot",
+            "hotfill",
+            "service",
+            "servicefill",
+            "present",
+            "presentfill",
+            "pic",
+            "picfill",
+            "rank",
+            "rankfill",
+            "male",
+            "female",
+            "down",
+            "top",
+            "recharge",
+            "rechargefill",
+            "forward",
+            "forwardfill",
+            "info",
+            "infofill",
+            "redpacket",
+            "redpacket_fill",
+            "roundadd",
+            "roundaddfill",
+            "friendadd",
+            "friendaddfill",
+            "cart",
+            "cartfill",
+            "more",
+            "moreandroid",
+            "back",
+            "right",
+            "shop",
+            "shopfill",
+            "question",
+            "questionfill",
+            "roundclose",
+            "roundclosefill",
+            "roundcheck",
+            "roundcheckfill",
+            "global",
+            "mail",
+            "punch",
+            "exit",
+            "upload",
+            "read",
+            "file",
+            "link",
+            "full",
+            "group",
+            "friend",
+            "profile",
+            "addressbook",
+            "calendar",
+            "text",
+            "copy",
+            "share",
+            "wifi",
+            "vipcard",
+            "weibo",
+            "remind",
+            "refresh",
+            "filter",
+            "settings",
+            "scan",
+            "qrcode",
+            "cascades",
+            "apps",
+            "sort",
+            "searchlist",
+            "search",
+            "edit",
+        ];
+        const keyword = ref("");
+        const list = computed(() => {
+            return iconList.filter((item) => {
+                return item.indexOf(keyword.value) !== -1;
+            });
+        });
+
         return {
-            keyword: "",
-            iconList: [
-                "attentionforbid",
-                "attentionforbidfill",
-                "attention",
-                "attentionfill",
-                "tag",
-                "tagfill",
-                "people",
-                "peoplefill",
-                "notice",
-                "noticefill",
-                "mobile",
-                "mobilefill",
-                "voice",
-                "voicefill",
-                "unlock",
-                "lock",
-                "home",
-                "homefill",
-                "delete",
-                "deletefill",
-                "notification",
-                "notificationfill",
-                "notificationforbidfill",
-                "like",
-                "likefill",
-                "comment",
-                "commentfill",
-                "camera",
-                "camerafill",
-                "warn",
-                "warnfill",
-                "time",
-                "timefill",
-                "location",
-                "locationfill",
-                "favor",
-                "favorfill",
-                "skin",
-                "skinfill",
-                "news",
-                "newsfill",
-                "record",
-                "recordfill",
-                "emoji",
-                "emojifill",
-                "message",
-                "messagefill",
-                "goods",
-                "goodsfill",
-                "crown",
-                "crownfill",
-                "move",
-                "add",
-                "hot",
-                "hotfill",
-                "service",
-                "servicefill",
-                "present",
-                "presentfill",
-                "pic",
-                "picfill",
-                "rank",
-                "rankfill",
-                "male",
-                "female",
-                "down",
-                "top",
-                "recharge",
-                "rechargefill",
-                "forward",
-                "forwardfill",
-                "info",
-                "infofill",
-                "redpacket",
-                "redpacket_fill",
-                "roundadd",
-                "roundaddfill",
-                "friendadd",
-                "friendaddfill",
-                "cart",
-                "cartfill",
-                "more",
-                "moreandroid",
-                "back",
-                "right",
-                "shop",
-                "shopfill",
-                "question",
-                "questionfill",
-                "roundclose",
-                "roundclosefill",
-                "roundcheck",
-                "roundcheckfill",
-                "global",
-                "mail",
-                "punch",
-                "exit",
-                "upload",
-                "read",
-                "file",
-                "link",
-                "full",
-                "group",
-                "friend",
-                "profile",
-                "addressbook",
-                "calendar",
-                "text",
-                "copy",
-                "share",
-                "wifi",
-                "vipcard",
-                "weibo",
-                "remind",
-                "refresh",
-                "filter",
-                "settings",
-                "scan",
-                "qrcode",
-                "cascades",
-                "apps",
-                "sort",
-                "searchlist",
-                "search",
-                "edit"
-            ]
+            iconList,
+            keyword,
+            list,
         };
     },
-    computed: {
-        list() {
-            return this.iconList.filter(item => {
-                return item.indexOf(this.keyword) !== -1;
-            });
-        }
-    }
 };
 </script>
 

+ 44 - 33
src/views/Login.vue

@@ -11,12 +11,8 @@
                     </el-input>
                 </el-form-item>
                 <el-form-item prop="password">
-                    <el-input
-                        type="password"
-                        placeholder="password"
-                        v-model="param.password"
-                        @keyup.enter="submitForm()"
-                    >
+                    <el-input type="password" placeholder="password" v-model="param.password"
+                        @keyup.enter="submitForm()">
                         <template #prepend>
                             <el-button icon="el-icon-lock"></el-button>
                         </template>
@@ -32,40 +28,55 @@
 </template>
 
 <script>
+import { ref, reactive } from "vue";
+import { useStore } from "vuex";
+import { useRouter } from "vue-router";
+import { ElMessage } from "element-plus";
+
 export default {
-    data() {
-        return {
-            param: {
-                username: "admin",
-                password: "123123"
-            },
-            rules: {
-                username: [
-                    { required: true, message: "请输入用户名", trigger: "blur" }
-                ],
-                password: [
-                    { required: true, message: "请输入密码", trigger: "blur" }
-                ]
-            }
+    setup() {
+        const router = useRouter();
+        const param = reactive({
+            username: "admin",
+            password: "123123",
+        });
+
+        const rules = {
+            username: [
+                {
+                    required: true,
+                    message: "请输入用户名",
+                    trigger: "blur",
+                },
+            ],
+            password: [
+                { required: true, message: "请输入密码", trigger: "blur" },
+            ],
         };
-    },
-    created() {
-        this.$store.commit("clearTags");
-    },
-    methods: {
-        submitForm() {
-            this.$refs.login.validate(valid => {
+        const login = ref(null);
+        const submitForm = () => {
+            login.value.validate((valid) => {
                 if (valid) {
-                    this.$message.success("登录成功");
-                    localStorage.setItem("ms_username", this.param.username);
-                    this.$router.push("/");
+                    ElMessage.success("登录成功");
+                    localStorage.setItem("ms_username", param.username);
+                    router.push("/");
                 } else {
-                    this.$message.error("请输入账号和密码");
+                    ElMessage.error("登录成功");
                     return false;
                 }
             });
-        }
-    }
+        };
+
+        const store = useStore();
+        store.commit("clearTags");
+
+        return {
+            param,
+            rules,
+            login,
+            submitForm,
+        };
+    },
 };
 </script>
 

+ 60 - 52
src/views/Tabs.vue

@@ -7,8 +7,8 @@
         </div>
         <div class="container">
             <el-tabs v-model="message">
-                <el-tab-pane :label="`未读消息(${unread.length})`" name="first">
-                    <el-table :data="unread" :show-header="false" style="width: 100%">
+                <el-tab-pane :label="`未读消息(${state.unread.length})`" name="first">
+                    <el-table :data="state.unread" :show-header="false" style="width: 100%">
                         <el-table-column>
                             <template #default="scope">
                                 <span class="message-title">{{scope.row.title}}</span>
@@ -25,9 +25,9 @@
                         <el-button type="primary">全部标为已读</el-button>
                     </div>
                 </el-tab-pane>
-                <el-tab-pane :label="`已读消息(${read.length})`" name="second">
+                <el-tab-pane :label="`已读消息(${state.read.length})`" name="second">
                     <template v-if="message === 'second'">
-                        <el-table :data="read" :show-header="false" style="width: 100%">
+                        <el-table :data="state.read" :show-header="false" style="width: 100%">
                             <el-table-column>
                                 <template #default="scope">
                                     <span class="message-title">{{scope.row.title}}</span>
@@ -45,9 +45,9 @@
                         </div>
                     </template>
                 </el-tab-pane>
-                <el-tab-pane :label="`回收站(${recycle.length})`" name="third">
+                <el-tab-pane :label="`回收站(${state.recycle.length})`" name="third">
                     <template v-if="message === 'third'">
-                        <el-table :data="recycle" :show-header="false" style="width: 100%">
+                        <el-table :data="state.recycle" :show-header="false" style="width: 100%">
                             <el-table-column>
                                 <template #default="scope">
                                     <span class="message-title">{{scope.row.title}}</span>
@@ -71,58 +71,66 @@
 </template>
 
 <script>
-    export default {
-        name: 'tabs',
-        data() {
-            return {
-                message: 'first',
-                showHeader: false,
-                unread: [{
-                    date: '2018-04-19 20:00:00',
-                    title: '【系统通知】该系统将于今晚凌晨2点到5点进行升级维护',
-                },{
-                    date: '2018-04-19 21:00:00',
-                    title: '今晚12点整发大红包,先到先得',
-                }],
-                read: [{
-                    date: '2018-04-19 20:00:00',
-                    title: '【系统通知】该系统将于今晚凌晨2点到5点进行升级维护'
-                }],
-                recycle: [{
-                    date: '2018-04-19 20:00:00',
-                    title: '【系统通知】该系统将于今晚凌晨2点到5点进行升级维护'
-                }]
-            }
-        },
-        methods: {
-            handleRead(index) {
-                const item = this.unread.splice(index, 1);
-                console.log(item);
-                this.read = item.concat(this.read);
-            },
-            handleDel(index) {
-                const item = this.read.splice(index, 1);
-                this.recycle = item.concat(this.recycle);
-            },
-            handleRestore(index) {
-                const item = this.recycle.splice(index, 1);
-                this.read = item.concat(this.read);
-            }
-        },
-        computed: {
-            unreadNum(){
-                return this.unread.length;
-            }
-        }
-    }
+import { ref, reactive } from "vue";
+export default {
+    name: "tabs",
+    setup() {
+        const message = ref("first");
+        const state = reactive({
+            unread: [
+                {
+                    date: "2018-04-19 20:00:00",
+                    title: "【系统通知】该系统将于今晚凌晨2点到5点进行升级维护",
+                },
+                {
+                    date: "2018-04-19 21:00:00",
+                    title: "今晚12点整发大红包,先到先得",
+                },
+            ],
+            read: [
+                {
+                    date: "2018-04-19 20:00:00",
+                    title: "【系统通知】该系统将于今晚凌晨2点到5点进行升级维护",
+                },
+            ],
+            recycle: [
+                {
+                    date: "2018-04-19 20:00:00",
+                    title: "【系统通知】该系统将于今晚凌晨2点到5点进行升级维护",
+                },
+            ],
+        });
 
+        const handleRead = (index) => {
+            const item = state.unread.splice(index, 1);
+            console.log(item);
+            state.read = item.concat(state.read);
+        };
+        const handleDel = (index) => {
+            const item = state.read.splice(index, 1);
+            state.recycle = item.concat(state.recycle);
+        };
+        const handleRestore = (index) => {
+            const item = state.recycle.splice(index, 1);
+            state.read = item.concat(state.read);
+        };
+
+        return {
+            message,
+            state,
+            handleRead,
+            handleDel,
+            handleRestore,
+        };
+    },
+};
 </script>
 
 <style>
-.message-title{
+.message-title {
     cursor: pointer;
 }
-.handle-row{
+.handle-row {
     margin-top: 30px;
 }
 </style>

+ 36 - 83
src/views/Upload.vue

@@ -13,17 +13,9 @@
             <div class="plugins-tips">
                 Element UI自带上传组件。
                 访问地址:
-                <a
-                    href="http://element.eleme.io/#/zh-CN/component/upload"
-                    target="_blank"
-                >Element UI Upload</a>
+                <a href="http://element.eleme.io/#/zh-CN/component/upload" target="_blank">Element UI Upload</a>
             </div>
-            <el-upload
-                class="upload-demo"
-                drag
-                action="http://jsonplaceholder.typicode.com/api/posts/"
-                multiple
-            >
+            <el-upload class="upload-demo" drag action="http://jsonplaceholder.typicode.com/api/posts/" multiple>
                 <i class="el-icon-upload"></i>
                 <div class="el-upload__text">
                     将文件拖到此处,或
@@ -38,97 +30,58 @@
             <div class="plugins-tips">
                 vue-cropperjs:一个封装了 cropperjs 的 Vue 组件。
                 访问地址:
-                <a
-                    href="https://github.com/Agontuk/vue-cropperjs"
-                    target="_blank"
-                >vue-cropperjs</a>
+                <a href="https://github.com/Agontuk/vue-cropperjs" target="_blank">vue-cropperjs</a>
             </div>
-            <div class="crop-demo">
-                <img :src="cropImg" class="pre-img" />
-                <div class="crop-demo-btn">
-                    选择图片
-                    <input
-                        class="crop-input"
-                        type="file"
-                        name="image"
-                        accept="image/*"
-                        @change="setImage"
-                    />
-                </div>
-            </div>
-
-            <el-dialog title="裁剪图片" v-model="dialogVisible" width="600px">
-                <vue-cropper
-                    ref="cropper"
-                    :src="imgSrc"
-                    :ready="cropImage"
-                    :zoom="cropImage"
-                    :cropmove="cropImage"
-                    style="width:100%;height:300px;"
-                ></vue-cropper>
-                <template #footer>
-                    <span class="dialog-footer">
-                        <el-button @click="cancelCrop">取 消</el-button>
-                        <el-button type="primary" @click="dialogVisible = false">确 定</el-button>
-                    </span>
-                </template>
-            </el-dialog>
         </div>
     </div>
 </template>
 
 <script>
-import VueCropper from "vue-cropperjs";
-import "cropperjs/dist/cropper.css";
+
 export default {
     name: "upload",
-    data() {
-        return {
-            defaultSrc: require("../assets/img/img.jpg"),
-            fileList: [],
-            imgSrc: "",
-            cropImg: "",
-            dialogVisible: false
-        };
-    },
     components: {
-        VueCropper
+        VueCropper,
     },
-    methods: {
-        setImage(e) {
+    setup() {
+        const imgSrc = ref("");
+        const cropImg = ref(defaultSrc);
+        const dialogVisible = ref(false);
+        const cropper = ref(null);
+
+        const setImage = (e) => {
             const file = e.target.files[0];
             if (!file.type.includes("image/")) {
                 return;
             }
             const reader = new FileReader();
-            reader.onload = event => {
-                this.dialogVisible = true;
-                this.imgSrc = event.target.result;
-                this.$refs.cropper &&
-                    this.$refs.cropper.replace(event.target.result);
+            reader.onload = (event) => {
+                dialogVisible.value = true;
+                imgSrc.value = event.target.result;
+                cropper.value && cropper.value.replace(event.target.result);
             };
             reader.readAsDataURL(file);
-        },
-        cropImage() {
-            this.cropImg = this.$refs.cropper.getCroppedCanvas().toDataURL();
-        },
-        cancelCrop() {
-            this.dialogVisible = false;
-            this.cropImg = this.defaultSrc;
-        },
-        imageuploaded(res) {
-            console.log(res);
-        },
-        handleError() {
-            this.$notify.error({
-                title: "上传失败",
-                message: "图片上传接口上传失败,可更改为自己的服务器接口"
-            });
-        }
+        };
+
+        const cropImage = () => {
+            cropImg.value = cropper.value.getCroppedCanvas().toDataURL();
+        };
+
+        const cancelCrop = () => {
+            dialogVisible.value = false;
+            cropImg.value = defaultSrc;
+        };
+
+        return {
+            cropper,
+            imgSrc,
+            cropImg,
+            dialogVisible,
+            setImage,
+            cropImage,
+            cancelCrop,
+        };
     },
-    created() {
-        this.cropImg = this.defaultSrc;
-    }
 };
 </script>
 

+ 192 - 0
src/views/User.vue

@@ -0,0 +1,192 @@
+<template>
+    <div>
+        <el-row :gutter="20">
+            <el-col :span="12">
+                <el-card shadow="hover">
+                    <template #header>
+                        <div class="clearfix">
+                            <span>基础信息</span>
+                        </div>
+                    </template>
+                    <div class="info">
+                        <div class="info-image" @click="showDialog">
+                            <img :src="avatarImg" />
+                            <span class="info-edit">
+                                <i class="el-icon-lx-camerafill"></i>
+                            </span>
+                        </div>
+                        <div class="info-name">{{ name }}</div>
+                        <div class="info-desc">不可能!我的代码怎么可能会有bug!</div>
+                    </div>
+                </el-card>
+            </el-col>
+            <el-col :span="12">
+                <el-card shadow="hover">
+                    <template #header>
+                        <div class="clearfix">
+                            <span>账户编辑</span>
+                        </div>
+                    </template>
+                    <el-form label-width="90px">
+                        <el-form-item label="用户名:"> {{ name }} </el-form-item>
+                        <el-form-item label="旧密码:">
+                            <el-input type="password" v-model="form.old"></el-input>
+                        </el-form-item>
+                        <el-form-item label="新密码:">
+                            <el-input type="password" v-model="form.new"></el-input>
+                        </el-form-item>
+                        <el-form-item label="个人简介:">
+                            <el-input v-model="form.desc"></el-input>
+                        </el-form-item>
+                        <el-form-item>
+                            <el-button type="primary" @click="onSubmit">保存</el-button>
+                        </el-form-item>
+                    </el-form>
+                </el-card>
+            </el-col>
+        </el-row>
+        <el-dialog title="裁剪图片" v-model="dialogVisible" width="600px">
+            <vue-cropper ref="cropper" :src="imgSrc" :ready="cropImage" :zoom="cropImage" :cropmove="cropImage"
+                style="width: 100%; height: 400px"></vue-cropper>
+
+            <template #footer>
+                <span class="dialog-footer">
+                    <el-button class="crop-demo-btn" type="primary">选择图片
+                        <input class="crop-input" type="file" name="image" accept="image/*" @change="setImage" />
+                    </el-button>
+                    <el-button type="primary" @click="saveAvatar">上传并保存</el-button>
+                </span>
+            </template>
+        </el-dialog>
+    </div>
+</template>
+
+<script>
+import { reactive, ref } from "vue";
+import VueCropper from "vue-cropperjs";
+import "cropperjs/dist/cropper.css";
+import avatar from "../assets/img/img.jpg";
+export default {
+    name: "user",
+    components: {
+        VueCropper,
+    },
+    setup() {
+        const name = localStorage.getItem("ms_username");
+        const form = reactive({
+            old: "",
+            new: "",
+            desc: "不可能!我的代码怎么可能会有bug!",
+        });
+        const onSubmit = () => {};
+
+        const avatarImg = ref(avatar);
+        const imgSrc = ref("");
+        const cropImg = ref("");
+        const dialogVisible = ref(false);
+        const cropper = ref(null);
+
+        const showDialog = () => {
+            dialogVisible.value = true;
+            imgSrc.value = avatarImg.value;
+        };
+
+        const setImage = (e) => {
+            const file = e.target.files[0];
+            if (!file.type.includes("image/")) {
+                return;
+            }
+            const reader = new FileReader();
+            reader.onload = (event) => {
+                dialogVisible.value = true;
+                imgSrc.value = event.target.result;
+                cropper.value && cropper.value.replace(event.target.result);
+            };
+            reader.readAsDataURL(file);
+        };
+
+        const cropImage = () => {
+            cropImg.value = cropper.value.getCroppedCanvas().toDataURL();
+        };
+
+        const saveAvatar = () => {
+            avatarImg.value = cropImg.value;
+            dialogVisible.value = false;
+        };
+
+        return {
+            name,
+            form,
+            onSubmit,
+            cropper,
+            avatarImg,
+            imgSrc,
+            cropImg,
+            showDialog,
+            dialogVisible,
+            setImage,
+            cropImage,
+            saveAvatar,
+        };
+    },
+};
+</script>
+
+<style scoped>
+.info {
+    text-align: center;
+    padding: 35px 0;
+}
+.info-image {
+    position: relative;
+    margin: auto;
+    width: 100px;
+    height: 100px;
+    background: #f8f8f8;
+    border: 1px solid #eee;
+    border-radius: 50px;
+    overflow: hidden;
+}
+.info-image img {
+    width: 100%;
+    height: 100%;
+}
+.info-edit {
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    position: absolute;
+    left: 0;
+    top: 0;
+    width: 100%;
+    height: 100%;
+    background: rgba(0, 0, 0, 0.5);
+    opacity: 0;
+    transition: opacity 0.3s ease;
+}
+.info-edit i {
+    color: #eee;
+    font-size: 25px;
+}
+.info-image:hover .info-edit {
+    opacity: 1;
+}
+.info-name {
+    margin: 15px 0 10px;
+    font-size: 24px;
+    font-weight: 500;
+    color: #262626;
+}
+.crop-demo-btn {
+    position: relative;
+}
+.crop-input {
+    position: absolute;
+    width: 100px;
+    height: 40px;
+    left: 0;
+    top: 0;
+    opacity: 0;
+    cursor: pointer;
+}
+</style>

+ 9 - 0
vite.config.js

@@ -0,0 +1,9 @@
+import vue from '@vitejs/plugin-vue'
+
+export default {
+    base: './',
+    plugins: [vue()],
+    optimizeDeps: {
+        include: ['schart.js']
+    }
+}