浏览代码

改为typescript;升级elementplus

lin-xin 3 年之前
父节点
当前提交
995ab69b1e

+ 1 - 1
LICENSE

@@ -1,6 +1,6 @@
 MIT License
 
-Copyright (c) 2016-2021 vue-manage-system
+Copyright (c) 2016-2023 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

+ 40 - 49
README.md

@@ -22,16 +22,6 @@
 
 [English document](https://github.com/lin-xin/manage-system/blob/master/README_EN.md)
 
-## 项目截图
-
-### 登录
-
-![Image text](https://github.com/lin-xin/manage-system/raw/master/screenshots/wms3.png)
-
-### 首页
-
-![Image text](https://github.com/lin-xin/manage-system/raw/master/screenshots/wms1.png)
-
 ## 赞助商
 
 ### 好问
@@ -40,7 +30,7 @@
 
 专业问卷服务,一对一客服,按需定制 
 
-## 赞赏
+## 支持作者
 
 请作者喝杯咖啡吧!(微信号:linxin_20)
 
@@ -48,26 +38,25 @@
 
 ## 前言
 
-该方案作为一套多功能的后台框架模板,适用于绝大部分的后台管理系统(Web Management System)开发。基于 Vue3 + pinia,引用 Element Plus 组件库,方便开发快速简洁好看的组件。分离颜色样式,支持手动切换主题色,而且很方便使用自定义主题色
+该方案作为一套多功能的后台框架模板,适用于绝大部分的后台管理系统开发。基于 Vue3 + pinia,引用 Element Plus 组件库,方便开发。
 
 ## 功能
 
 -   [x] Element Plus
+-   [x] vite
+-   [x] pinia
 -   [x] 登录/注销
 -   [x] Dashboard
 -   [x] 表格
 -   [x] Tab 选项卡
 -   [x] 表单
 -   [x] 图表 :bar_chart:
--   [x] 富文本编辑器
+-   [x] 富文本/markdown编辑器
 -   [x] 图片拖拽/裁剪上传
--   [x] 权限测试
--   [x] 404 / 403
+-   [x] 权限管理
 -   [x] 三级菜单
 -   [x] 自定义图标
--   [x] 国际化
--   [x] vite
--   [x] pinia
+
 
 ## 安装步骤
 
@@ -76,7 +65,7 @@ git clone https://github.com/lin-xin/vue-manage-system.git      // 把模板下
 cd vue-manage-system    // 进入模板目录
 npm install         // 安装项目依赖,等待安装完成之后,安装失败可用 cnpm 或 yarn
 
-// 开启服务器,浏览器访问 http://localhost:8080
+// 运行
 npm run dev
 
 // 执行构建命令,生成的dist文件夹放在服务器下即可访问
@@ -98,38 +87,30 @@ vue.js 封装 sChart.js 的图表组件。访问地址:[vue-schart](https://gi
     </div>
 </template>
 
-<script>
-    import Schart from "vue-schart"; // 导入Schart组件
-    export default {
-        data() {
-            return {
-                options: {
-                    type: "bar",
-                    title: {
-                        text: "最近一周各品类销售图",
-                    },
-                    labels: ["周一", "周二", "周三", "周四", "周五"],
-                    datasets: [
-                        {
-                            label: "家电",
-                            data: [234, 278, 270, 190, 230],
-                        },
-                        {
-                            label: "百货",
-                            data: [164, 178, 190, 135, 160],
-                        },
-                        {
-                            label: "食品",
-                            data: [144, 198, 150, 235, 120],
-                        },
-                    ],
-                },
-            };
+<script setup>
+import { ref } from 'vue';
+import Schart from "vue-schart"; // 导入Schart组件
+const options = ref({
+    type: "bar",
+    title: {
+        text: "最近一周各品类销售图",
+    },
+    labels: ["周一", "周二", "周三", "周四", "周五"],
+    datasets: [
+        {
+            label: "家电",
+            data: [234, 278, 270, 190, 230],
         },
-        components: {
-            Schart,
+        {
+            label: "百货",
+            data: [164, 178, 190, 135, 160],
         },
-    };
+        {
+            label: "食品",
+            data: [144, 198, 150, 235, 120],
+        },
+    ],
+})
 </script>
 <style>
     .wrapper {
@@ -139,6 +120,16 @@ vue.js 封装 sChart.js 的图表组件。访问地址:[vue-schart](https://gi
 </style>
 ```
 
+## 项目截图
+
+### 登录
+
+![Image text](https://github.com/lin-xin/manage-system/raw/master/screenshots/wms3.png)
+
+### 首页
+
+![Image text](https://github.com/lin-xin/manage-system/raw/master/screenshots/wms1.png)
+
 ## License
 
 [MIT](https://github.com/lin-xin/vue-manage-system/blob/master/LICENSE)

+ 25 - 42
README_EN.md

@@ -37,13 +37,10 @@ The scheme as a set of multi-function background frame templates, suitable for m
 -   [x] Tabs
 -   [x] From
 -   [x] Chart :bar_chart:
--   [ ] Editor
--   [ ] Markdown
+-   [x] Editor
+-   [x] Markdown
 -   [x] Upload pictures by clipping or dragging
--   [ ] Support manual switch themes :sparkles:
--   [ ] List drag sort
 -   [x] Permission
--   [x] 404 / 403
 -   [x] Three level menu
 -   [x] Custom icon
 
@@ -55,8 +52,7 @@ The scheme as a set of multi-function background frame templates, suitable for m
 
 ## Local development
 
-    // Open server and access http://localhost:8080 in browser
-    npm run serve
+    npm run dev
 
 ## Constructing production
 
@@ -75,39 +71,30 @@ Vue.js wrapper for sChart.js. Github : [vue-schart](https://github.com/lin-xin/v
         <schart class="wrapper" canvasId="myCanvas" :options="options"></schart>
     </div>
 </template>
-
-<script>
-    import Schart from "vue-schart"; // 导入Schart组件
-    export default {
-        data() {
-            return {
-                options: {
-                    type: "bar",
-                    title: {
-                        text: "最近一周各品类销售图",
-                    },
-                    labels: ["周一", "周二", "周三", "周四", "周五"],
-                    datasets: [
-                        {
-                            label: "家电",
-                            data: [234, 278, 270, 190, 230],
-                        },
-                        {
-                            label: "百货",
-                            data: [164, 178, 190, 135, 160],
-                        },
-                        {
-                            label: "食品",
-                            data: [144, 198, 150, 235, 120],
-                        },
-                    ],
-                },
-            };
+<script setup>
+import { ref } from 'vue';
+import Schart from "vue-schart"; // 导入Schart组件
+const options = ref({
+    type: "bar",
+    title: {
+        text: "最近一周各品类销售图",
+    },
+    labels: ["周一", "周二", "周三", "周四", "周五"],
+    datasets: [
+        {
+            label: "家电",
+            data: [234, 278, 270, 190, 230],
+        },
+        {
+            label: "百货",
+            data: [164, 178, 190, 135, 160],
         },
-        components: {
-            Schart,
+        {
+            label: "食品",
+            data: [144, 198, 150, 235, 120],
         },
-    };
+    ],
+})
 </script>
 <style>
     .wrapper {
@@ -117,10 +104,6 @@ Vue.js wrapper for sChart.js. Github : [vue-schart](https://github.com/lin-xin/v
 </style>
 ```
 
-### element-ui
-
-A desktop component library based on vue.js2.0 . Github : [element](http://element.eleme.io/#/zh-CN/component/layout)
-
 ## Screenshot
 
 ### Default theme

+ 5 - 0
auto-imports.d.ts

@@ -0,0 +1,5 @@
+// Generated by 'unplugin-auto-import'
+export {}
+declare global {
+
+}

+ 52 - 0
components.d.ts

@@ -0,0 +1,52 @@
+// generated by unplugin-vue-components
+// We suggest you to commit this file into source control
+// Read more: https://github.com/vuejs/core/pull/3399
+import '@vue/runtime-core'
+
+export {}
+
+declare module '@vue/runtime-core' {
+  export interface GlobalComponents {
+    ElAvatar: typeof import('element-plus/es')['ElAvatar']
+    ElButton: typeof import('element-plus/es')['ElButton']
+    ElCard: typeof import('element-plus/es')['ElCard']
+    ElCascader: typeof import('element-plus/es')['ElCascader']
+    ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
+    ElCheckboxGroup: typeof import('element-plus/es')['ElCheckboxGroup']
+    ElCol: typeof import('element-plus/es')['ElCol']
+    ElDatePicker: typeof import('element-plus/es')['ElDatePicker']
+    ElDialog: typeof import('element-plus/es')['ElDialog']
+    ElDropdown: typeof import('element-plus/es')['ElDropdown']
+    ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
+    ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu']
+    ElForm: typeof import('element-plus/es')['ElForm']
+    ElFormItem: typeof import('element-plus/es')['ElFormItem']
+    ElIcon: typeof import('element-plus/es')['ElIcon']
+    ElImage: typeof import('element-plus/es')['ElImage']
+    ElInput: typeof import('element-plus/es')['ElInput']
+    ElMenu: typeof import('element-plus/es')['ElMenu']
+    ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
+    ElOption: typeof import('element-plus/es')['ElOption']
+    ElPagination: typeof import('element-plus/es')['ElPagination']
+    ElProgress: typeof import('element-plus/es')['ElProgress']
+    ElRadio: typeof import('element-plus/es')['ElRadio']
+    ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
+    ElRow: typeof import('element-plus/es')['ElRow']
+    ElSelect: typeof import('element-plus/es')['ElSelect']
+    ElSubMenu: typeof import('element-plus/es')['ElSubMenu']
+    ElSwitch: typeof import('element-plus/es')['ElSwitch']
+    ElTable: typeof import('element-plus/es')['ElTable']
+    ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
+    ElTabPane: typeof import('element-plus/es')['ElTabPane']
+    ElTabs: typeof import('element-plus/es')['ElTabs']
+    ElTag: typeof import('element-plus/es')['ElTag']
+    ElTimePicker: typeof import('element-plus/es')['ElTimePicker']
+    ElTooltip: typeof import('element-plus/es')['ElTooltip']
+    ElUpload: typeof import('element-plus/es')['ElUpload']
+    Header: typeof import('./src/components/header.vue')['default']
+    RouterLink: typeof import('vue-router')['RouterLink']
+    RouterView: typeof import('vue-router')['RouterView']
+    Sidebar: typeof import('./src/components/sidebar.vue')['default']
+    Tags: typeof import('./src/components/tags.vue')['default']
+  }
+}

+ 1 - 1
index.html

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

+ 1237 - 0
package-lock.json

@@ -0,0 +1,1237 @@
+{
+	"name": "vue-manage-system",
+	"version": "5.3.0",
+	"lockfileVersion": 1,
+	"requires": true,
+	"dependencies": {
+		"@antfu/utils": {
+			"version": "0.5.2",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/@antfu/utils/-/utils-0.5.2.tgz",
+			"integrity": "sha512-CQkeV+oJxUazwjlHD0/3ZD08QWKuGQkhnrKo3e6ly5pd48VUpXbb77q0xMU4+vc2CkJnDS02Eq/M9ugyX20XZA==",
+			"dev": true
+		},
+		"@babel/parser": {
+			"version": "7.18.11",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/@babel/parser/-/parser-7.18.11.tgz",
+			"integrity": "sha512-9JKn5vN+hDt0Hdqn1PiJ2guflwP+B6Ga8qbDuoF0PzzVhrzsKIJo8yGqVk6CmMHiMei9w1C1Bp9IMJSIK+HPIQ=="
+		},
+		"@babel/runtime": {
+			"version": "7.18.9",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/@babel/runtime/-/runtime-7.18.9.tgz",
+			"integrity": "sha512-lkqXDcvlFT5rvEjiu6+QYO+1GXrEHRo2LOtS7E4GtX5ESIZOgepqsZBVIj6Pv+a6zqsya9VCgiK1KAK4BvJDAw==",
+			"requires": {
+				"regenerator-runtime": "^0.13.4"
+			}
+		},
+		"@babel/runtime-corejs3": {
+			"version": "7.18.9",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/@babel/runtime-corejs3/-/runtime-corejs3-7.18.9.tgz",
+			"integrity": "sha512-qZEWeccZCrHA2Au4/X05QW5CMdm4VjUDCrGq5gf1ZDcM4hRqreKrtwAn7yci9zfgAS9apvnsFXiGBHBAxZdK9A==",
+			"requires": {
+				"core-js-pure": "^3.20.2",
+				"regenerator-runtime": "^0.13.4"
+			}
+		},
+		"@ctrl/tinycolor": {
+			"version": "3.4.1",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/@ctrl/tinycolor/-/tinycolor-3.4.1.tgz",
+			"integrity": "sha512-ej5oVy6lykXsvieQtqZxCOaLT+xD4+QNarq78cIYISHmZXshCvROLudpQN3lfL8G0NL7plMSSK+zlyvCaIJ4Iw=="
+		},
+		"@element-plus/icons-vue": {
+			"version": "2.0.9",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/@element-plus/icons-vue/-/icons-vue-2.0.9.tgz",
+			"integrity": "sha512-okdrwiVeKBmW41Hkl0eMrXDjzJwhQMuKiBOu17rOszqM+LS/yBYpNQNV5Jvoh06Wc+89fMmb/uhzf8NZuDuUaQ=="
+		},
+		"@esbuild/linux-loong64": {
+			"version": "0.14.54",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/@esbuild/linux-loong64/-/linux-loong64-0.14.54.tgz",
+			"integrity": "sha512-bZBrLAIX1kpWelV0XemxBZllyRmM6vgFQQG2GdNb+r3Fkp0FOh1NJSvekXDs7jq70k4euu1cryLMfU+mTXlEpw==",
+			"dev": true,
+			"optional": true
+		},
+		"@floating-ui/core": {
+			"version": "0.7.3",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/@floating-ui/core/-/core-0.7.3.tgz",
+			"integrity": "sha512-buc8BXHmG9l82+OQXOFU3Kr2XQx9ys01U/Q9HMIrZ300iLc8HLMgh7dcCqgYzAzf4BkoQvDcXf5Y+CuEZ5JBYg=="
+		},
+		"@floating-ui/dom": {
+			"version": "0.5.4",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/@floating-ui/dom/-/dom-0.5.4.tgz",
+			"integrity": "sha512-419BMceRLq0RrmTSDxn8hf9R3VCJv2K9PUfugh5JyEFmdjzDo+e8U5EdR8nzKq8Yj1htzLm3b6eQEEam3/rrtg==",
+			"requires": {
+				"@floating-ui/core": "^0.7.3"
+			}
+		},
+		"@nodelib/fs.scandir": {
+			"version": "2.1.5",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
+			"integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
+			"dev": true,
+			"requires": {
+				"@nodelib/fs.stat": "2.0.5",
+				"run-parallel": "^1.1.9"
+			}
+		},
+		"@nodelib/fs.stat": {
+			"version": "2.0.5",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
+			"integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
+			"dev": true
+		},
+		"@nodelib/fs.walk": {
+			"version": "1.2.8",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
+			"integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
+			"dev": true,
+			"requires": {
+				"@nodelib/fs.scandir": "2.1.5",
+				"fastq": "^1.6.0"
+			}
+		},
+		"@rollup/pluginutils": {
+			"version": "4.2.1",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/@rollup/pluginutils/-/pluginutils-4.2.1.tgz",
+			"integrity": "sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==",
+			"dev": true,
+			"requires": {
+				"estree-walker": "^2.0.1",
+				"picomatch": "^2.2.2"
+			}
+		},
+		"@types/lodash": {
+			"version": "4.14.184",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/@types/lodash/-/lodash-4.14.184.tgz",
+			"integrity": "sha512-RoZphVtHbxPZizt4IcILciSWiC6dcn+eZ8oX9IWEYfDMcocdd42f7NPI6fQj+6zI8y4E0L7gu2pcZKLGTRaV9Q=="
+		},
+		"@types/lodash-es": {
+			"version": "4.17.6",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/@types/lodash-es/-/lodash-es-4.17.6.tgz",
+			"integrity": "sha512-R+zTeVUKDdfoRxpAryaQNRKk3105Rrgx2CFRClIgRGaqDTdjsm8h6IYA8ir584W3ePzkZfst5xIgDwYrlh9HLg==",
+			"requires": {
+				"@types/lodash": "*"
+			}
+		},
+		"@types/marked": {
+			"version": "4.0.5",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/@types/marked/-/marked-4.0.5.tgz",
+			"integrity": "sha512-jMN2moJ+lSf1VZXQo3VXeMCjoXuciVONig8+U0YNBop5aBvQw4qkolx1Nzn1i0T8L2l9IZ3jju6bS1pPwlaY1w=="
+		},
+		"@types/web-bluetooth": {
+			"version": "0.0.15",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/@types/web-bluetooth/-/web-bluetooth-0.0.15.tgz",
+			"integrity": "sha512-w7hEHXnPMEZ+4nGKl/KDRVpxkwYxYExuHOYXyzIzCDzEZ9ZCGMAewulr9IqJu2LR4N37fcnb1XVeuZ09qgOxhA=="
+		},
+		"@vitejs/plugin-vue": {
+			"version": "3.0.3",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/@vitejs/plugin-vue/-/plugin-vue-3.0.3.tgz",
+			"integrity": "sha512-U4zNBlz9mg+TA+i+5QPc3N5lQvdUXENZLO2h0Wdzp56gI1MWhqJOv+6R+d4kOzoaSSq6TnGPBdZAXKOe4lXy6g==",
+			"dev": true
+		},
+		"@volar/code-gen": {
+			"version": "0.38.9",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/@volar/code-gen/-/code-gen-0.38.9.tgz",
+			"integrity": "sha512-n6LClucfA+37rQeskvh9vDoZV1VvCVNy++MAPKj2dT4FT+Fbmty/SDQqnsEBtdEe6E3OQctFvA/IcKsx3Mns0A==",
+			"dev": true,
+			"requires": {
+				"@volar/source-map": "0.38.9"
+			}
+		},
+		"@volar/source-map": {
+			"version": "0.38.9",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/@volar/source-map/-/source-map-0.38.9.tgz",
+			"integrity": "sha512-ba0UFoHDYry+vwKdgkWJ6xlQT+8TFtZg1zj9tSjj4PykW1JZDuM0xplMotLun4h3YOoYfY9K1huY5gvxmrNLIw==",
+			"dev": true
+		},
+		"@volar/vue-code-gen": {
+			"version": "0.38.9",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/@volar/vue-code-gen/-/vue-code-gen-0.38.9.tgz",
+			"integrity": "sha512-tzj7AoarFBKl7e41MR006ncrEmNPHALuk8aG4WdDIaG387X5//5KhWC5Ff3ZfB2InGSeNT+CVUd74M0gS20rjA==",
+			"dev": true,
+			"requires": {
+				"@volar/code-gen": "0.38.9",
+				"@volar/source-map": "0.38.9",
+				"@vue/compiler-core": "^3.2.37",
+				"@vue/compiler-dom": "^3.2.37",
+				"@vue/shared": "^3.2.37"
+			}
+		},
+		"@volar/vue-typescript": {
+			"version": "0.38.9",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/@volar/vue-typescript/-/vue-typescript-0.38.9.tgz",
+			"integrity": "sha512-iJMQGU91ADi98u8V1vXd2UBmELDAaeSP0ZJaFjwosClQdKlJQYc6MlxxKfXBZisHqfbhdtrGRyaryulnYtliZw==",
+			"dev": true,
+			"requires": {
+				"@volar/code-gen": "0.38.9",
+				"@volar/source-map": "0.38.9",
+				"@volar/vue-code-gen": "0.38.9",
+				"@vue/compiler-sfc": "^3.2.37",
+				"@vue/reactivity": "^3.2.37"
+			}
+		},
+		"@vue/compiler-core": {
+			"version": "3.2.37",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/@vue/compiler-core/-/compiler-core-3.2.37.tgz",
+			"integrity": "sha512-81KhEjo7YAOh0vQJoSmAD68wLfYqJvoiD4ulyedzF+OEk/bk6/hx3fTNVfuzugIIaTrOx4PGx6pAiBRe5e9Zmg==",
+			"requires": {
+				"@babel/parser": "^7.16.4",
+				"@vue/shared": "3.2.37",
+				"estree-walker": "^2.0.2",
+				"source-map": "^0.6.1"
+			}
+		},
+		"@vue/compiler-dom": {
+			"version": "3.2.37",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/@vue/compiler-dom/-/compiler-dom-3.2.37.tgz",
+			"integrity": "sha512-yxJLH167fucHKxaqXpYk7x8z7mMEnXOw3G2q62FTkmsvNxu4FQSu5+3UMb+L7fjKa26DEzhrmCxAgFLLIzVfqQ==",
+			"requires": {
+				"@vue/compiler-core": "3.2.37",
+				"@vue/shared": "3.2.37"
+			}
+		},
+		"@vue/compiler-sfc": {
+			"version": "3.2.37",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/@vue/compiler-sfc/-/compiler-sfc-3.2.37.tgz",
+			"integrity": "sha512-+7i/2+9LYlpqDv+KTtWhOZH+pa8/HnX/905MdVmAcI/mPQOBwkHHIzrsEsucyOIZQYMkXUiTkmZq5am/NyXKkg==",
+			"requires": {
+				"@babel/parser": "^7.16.4",
+				"@vue/compiler-core": "3.2.37",
+				"@vue/compiler-dom": "3.2.37",
+				"@vue/compiler-ssr": "3.2.37",
+				"@vue/reactivity-transform": "3.2.37",
+				"@vue/shared": "3.2.37",
+				"estree-walker": "^2.0.2",
+				"magic-string": "^0.25.7",
+				"postcss": "^8.1.10",
+				"source-map": "^0.6.1"
+			},
+			"dependencies": {
+				"magic-string": {
+					"version": "0.25.9",
+					"resolved": "https://repo.huaweicloud.com/repository/npm/magic-string/-/magic-string-0.25.9.tgz",
+					"integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==",
+					"requires": {
+						"sourcemap-codec": "^1.4.8"
+					}
+				}
+			}
+		},
+		"@vue/compiler-ssr": {
+			"version": "3.2.37",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/@vue/compiler-ssr/-/compiler-ssr-3.2.37.tgz",
+			"integrity": "sha512-7mQJD7HdXxQjktmsWp/J67lThEIcxLemz1Vb5I6rYJHR5vI+lON3nPGOH3ubmbvYGt8xEUaAr1j7/tIFWiEOqw==",
+			"requires": {
+				"@vue/compiler-dom": "3.2.37",
+				"@vue/shared": "3.2.37"
+			}
+		},
+		"@vue/devtools-api": {
+			"version": "6.2.1",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/@vue/devtools-api/-/devtools-api-6.2.1.tgz",
+			"integrity": "sha512-OEgAMeQXvCoJ+1x8WyQuVZzFo0wcyCmUR3baRVLmKBo1LmYZWMlRiXlux5jd0fqVJu6PfDbOrZItVqUEzLobeQ=="
+		},
+		"@vue/reactivity": {
+			"version": "3.2.37",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/@vue/reactivity/-/reactivity-3.2.37.tgz",
+			"integrity": "sha512-/7WRafBOshOc6m3F7plwzPeCu/RCVv9uMpOwa/5PiY1Zz+WLVRWiy0MYKwmg19KBdGtFWsmZ4cD+LOdVPcs52A==",
+			"requires": {
+				"@vue/shared": "3.2.37"
+			}
+		},
+		"@vue/reactivity-transform": {
+			"version": "3.2.37",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/@vue/reactivity-transform/-/reactivity-transform-3.2.37.tgz",
+			"integrity": "sha512-IWopkKEb+8qpu/1eMKVeXrK0NLw9HicGviJzhJDEyfxTR9e1WtpnnbYkJWurX6WwoFP0sz10xQg8yL8lgskAZg==",
+			"requires": {
+				"@babel/parser": "^7.16.4",
+				"@vue/compiler-core": "3.2.37",
+				"@vue/shared": "3.2.37",
+				"estree-walker": "^2.0.2",
+				"magic-string": "^0.25.7"
+			},
+			"dependencies": {
+				"magic-string": {
+					"version": "0.25.9",
+					"resolved": "https://repo.huaweicloud.com/repository/npm/magic-string/-/magic-string-0.25.9.tgz",
+					"integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==",
+					"requires": {
+						"sourcemap-codec": "^1.4.8"
+					}
+				}
+			}
+		},
+		"@vue/runtime-core": {
+			"version": "3.2.37",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/@vue/runtime-core/-/runtime-core-3.2.37.tgz",
+			"integrity": "sha512-JPcd9kFyEdXLl/i0ClS7lwgcs0QpUAWj+SKX2ZC3ANKi1U4DOtiEr6cRqFXsPwY5u1L9fAjkinIdB8Rz3FoYNQ==",
+			"requires": {
+				"@vue/reactivity": "3.2.37",
+				"@vue/shared": "3.2.37"
+			}
+		},
+		"@vue/runtime-dom": {
+			"version": "3.2.37",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/@vue/runtime-dom/-/runtime-dom-3.2.37.tgz",
+			"integrity": "sha512-HimKdh9BepShW6YozwRKAYjYQWg9mQn63RGEiSswMbW+ssIht1MILYlVGkAGGQbkhSh31PCdoUcfiu4apXJoPw==",
+			"requires": {
+				"@vue/runtime-core": "3.2.37",
+				"@vue/shared": "3.2.37",
+				"csstype": "^2.6.8"
+			}
+		},
+		"@vue/server-renderer": {
+			"version": "3.2.37",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/@vue/server-renderer/-/server-renderer-3.2.37.tgz",
+			"integrity": "sha512-kLITEJvaYgZQ2h47hIzPh2K3jG8c1zCVbp/o/bzQOyvzaKiCquKS7AaioPI28GNxIsE/zSx+EwWYsNxDCX95MA==",
+			"requires": {
+				"@vue/compiler-ssr": "3.2.37",
+				"@vue/shared": "3.2.37"
+			}
+		},
+		"@vue/shared": {
+			"version": "3.2.37",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/@vue/shared/-/shared-3.2.37.tgz",
+			"integrity": "sha512-4rSJemR2NQIo9Klm1vabqWjD8rs/ZaJSzMxkMNeJS6lHiUjjUeYFbooN19NgFjztubEKh3WlZUeOLVdbbUWHsw=="
+		},
+		"@vueuse/core": {
+			"version": "9.1.0",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/@vueuse/core/-/core-9.1.0.tgz",
+			"integrity": "sha512-BIroqvXEqt826aE9r3K5cox1zobuPuAzdYJ36kouC2TVhlXvFKIILgFVWrpp9HZPwB3aLzasmG3K87q7TSyXZg==",
+			"requires": {
+				"@types/web-bluetooth": "^0.0.15",
+				"@vueuse/metadata": "9.1.0",
+				"@vueuse/shared": "9.1.0",
+				"vue-demi": "*"
+			}
+		},
+		"@vueuse/metadata": {
+			"version": "9.1.0",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/@vueuse/metadata/-/metadata-9.1.0.tgz",
+			"integrity": "sha512-8OEhlog1iaAGTD3LICZ8oBGQdYeMwByvXetOtAOZCJOzyCRSwqwdggTsmVZZ1rkgYIEqgUBk942AsAPwM21s6A=="
+		},
+		"@vueuse/shared": {
+			"version": "9.1.0",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/@vueuse/shared/-/shared-9.1.0.tgz",
+			"integrity": "sha512-pB/3njQu4tfJJ78ajELNda0yMG6lKfpToQW7Soe09CprF1k3QuyoNi1tBNvo75wBDJWD+LOnr+c4B5HZ39jY/Q==",
+			"requires": {
+				"vue-demi": "*"
+			}
+		},
+		"acorn": {
+			"version": "8.8.0",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/acorn/-/acorn-8.8.0.tgz",
+			"integrity": "sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==",
+			"dev": true
+		},
+		"anymatch": {
+			"version": "3.1.2",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/anymatch/-/anymatch-3.1.2.tgz",
+			"integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==",
+			"dev": true,
+			"requires": {
+				"normalize-path": "^3.0.0",
+				"picomatch": "^2.0.4"
+			}
+		},
+		"async-validator": {
+			"version": "4.2.5",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/async-validator/-/async-validator-4.2.5.tgz",
+			"integrity": "sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg=="
+		},
+		"asynckit": {
+			"version": "0.4.0",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/asynckit/-/asynckit-0.4.0.tgz",
+			"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
+		},
+		"axios": {
+			"version": "0.27.2",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/axios/-/axios-0.27.2.tgz",
+			"integrity": "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==",
+			"requires": {
+				"follow-redirects": "^1.14.9",
+				"form-data": "^4.0.0"
+			}
+		},
+		"balanced-match": {
+			"version": "1.0.2",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/balanced-match/-/balanced-match-1.0.2.tgz",
+			"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+			"dev": true
+		},
+		"binary-extensions": {
+			"version": "2.2.0",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/binary-extensions/-/binary-extensions-2.2.0.tgz",
+			"integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
+			"dev": true
+		},
+		"brace-expansion": {
+			"version": "2.0.1",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/brace-expansion/-/brace-expansion-2.0.1.tgz",
+			"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
+			"dev": true,
+			"requires": {
+				"balanced-match": "^1.0.0"
+			}
+		},
+		"braces": {
+			"version": "3.0.2",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/braces/-/braces-3.0.2.tgz",
+			"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
+			"dev": true,
+			"requires": {
+				"fill-range": "^7.0.1"
+			}
+		},
+		"chokidar": {
+			"version": "3.5.3",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/chokidar/-/chokidar-3.5.3.tgz",
+			"integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==",
+			"dev": true,
+			"requires": {
+				"anymatch": "~3.1.2",
+				"braces": "~3.0.2",
+				"fsevents": "~2.3.2",
+				"glob-parent": "~5.1.2",
+				"is-binary-path": "~2.1.0",
+				"is-glob": "~4.0.1",
+				"normalize-path": "~3.0.0",
+				"readdirp": "~3.6.0"
+			}
+		},
+		"combined-stream": {
+			"version": "1.0.8",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/combined-stream/-/combined-stream-1.0.8.tgz",
+			"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+			"requires": {
+				"delayed-stream": "~1.0.0"
+			}
+		},
+		"core-js-pure": {
+			"version": "3.24.1",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/core-js-pure/-/core-js-pure-3.24.1.tgz",
+			"integrity": "sha512-r1nJk41QLLPyozHUUPmILCEMtMw24NG4oWK6RbsDdjzQgg9ZvrUsPBj1MnG0wXXp1DCDU6j+wUvEmBSrtRbLXg=="
+		},
+		"cropperjs": {
+			"version": "1.5.12",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/cropperjs/-/cropperjs-1.5.12.tgz",
+			"integrity": "sha512-re7UdjE5UnwdrovyhNzZ6gathI4Rs3KGCBSc8HCIjUo5hO42CtzyblmWLj6QWVw7huHyDMfpKxhiO2II77nhDw=="
+		},
+		"csstype": {
+			"version": "2.6.20",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/csstype/-/csstype-2.6.20.tgz",
+			"integrity": "sha512-/WwNkdXfckNgw6S5R125rrW8ez139lBHWouiBvX8dfMFtcn6V81REDqnH7+CRpRipfYlyU1CmOnOxrmGcFOjeA=="
+		},
+		"dayjs": {
+			"version": "1.11.5",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/dayjs/-/dayjs-1.11.5.tgz",
+			"integrity": "sha512-CAdX5Q3YW3Gclyo5Vpqkgpj8fSdLQcRuzfX6mC6Phy0nfJ0eGYOeS7m4mt2plDWLAtA4TqTakvbboHvUxfe4iA=="
+		},
+		"debug": {
+			"version": "4.3.4",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/debug/-/debug-4.3.4.tgz",
+			"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
+			"dev": true,
+			"requires": {
+				"ms": "2.1.2"
+			}
+		},
+		"delayed-stream": {
+			"version": "1.0.0",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/delayed-stream/-/delayed-stream-1.0.0.tgz",
+			"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="
+		},
+		"element-plus": {
+			"version": "2.2.14",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/element-plus/-/element-plus-2.2.14.tgz",
+			"integrity": "sha512-V5Pis0OHhePg1RgVogZrcefaVl8vjVn4Pn9Qsh/t2CbFgjg9kKOYFqf/tuP3ObSXGm3X89hpe0W+nLVAsaFnpw==",
+			"requires": {
+				"@ctrl/tinycolor": "^3.4.1",
+				"@element-plus/icons-vue": "^2.0.6",
+				"@floating-ui/dom": "^0.5.4",
+				"@popperjs/core": "npm:@sxzz/popperjs-es@^2.11.7",
+				"@types/lodash": "^4.14.182",
+				"@types/lodash-es": "^4.17.6",
+				"@vueuse/core": "^9.1.0",
+				"async-validator": "^4.2.5",
+				"dayjs": "^1.11.3",
+				"escape-html": "^1.0.3",
+				"lodash": "^4.17.21",
+				"lodash-es": "^4.17.21",
+				"lodash-unified": "^1.0.2",
+				"memoize-one": "^6.0.0",
+				"normalize-wheel-es": "^1.2.0"
+			},
+			"dependencies": {
+				"@popperjs/core": {
+					"version": "npm:@sxzz/popperjs-es@2.11.7",
+					"resolved": "https://registry.npmjs.org/@sxzz/popperjs-es/-/popperjs-es-2.11.7.tgz",
+					"integrity": "sha512-Ccy0NlLkzr0Ex2FKvh2X+OyERHXJ88XJ1MXtsI9y9fGexlaXaVTPzBCRBwIxFkORuOb+uBqeu+RqnpgYTEZRUQ=="
+				}
+			}
+		},
+		"esbuild": {
+			"version": "0.14.54",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/esbuild/-/esbuild-0.14.54.tgz",
+			"integrity": "sha512-Cy9llcy8DvET5uznocPyqL3BFRrFXSVqbgpMJ9Wz8oVjZlh/zUSNbPRbov0VX7VxN2JH1Oa0uNxZ7eLRb62pJA==",
+			"dev": true,
+			"requires": {
+				"@esbuild/linux-loong64": "0.14.54",
+				"esbuild-android-64": "0.14.54",
+				"esbuild-android-arm64": "0.14.54",
+				"esbuild-darwin-64": "0.14.54",
+				"esbuild-darwin-arm64": "0.14.54",
+				"esbuild-freebsd-64": "0.14.54",
+				"esbuild-freebsd-arm64": "0.14.54",
+				"esbuild-linux-32": "0.14.54",
+				"esbuild-linux-64": "0.14.54",
+				"esbuild-linux-arm": "0.14.54",
+				"esbuild-linux-arm64": "0.14.54",
+				"esbuild-linux-mips64le": "0.14.54",
+				"esbuild-linux-ppc64le": "0.14.54",
+				"esbuild-linux-riscv64": "0.14.54",
+				"esbuild-linux-s390x": "0.14.54",
+				"esbuild-netbsd-64": "0.14.54",
+				"esbuild-openbsd-64": "0.14.54",
+				"esbuild-sunos-64": "0.14.54",
+				"esbuild-windows-32": "0.14.54",
+				"esbuild-windows-64": "0.14.54",
+				"esbuild-windows-arm64": "0.14.54"
+			}
+		},
+		"esbuild-android-64": {
+			"version": "0.14.54",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/esbuild-android-64/-/esbuild-android-64-0.14.54.tgz",
+			"integrity": "sha512-Tz2++Aqqz0rJ7kYBfz+iqyE3QMycD4vk7LBRyWaAVFgFtQ/O8EJOnVmTOiDWYZ/uYzB4kvP+bqejYdVKzE5lAQ==",
+			"dev": true,
+			"optional": true
+		},
+		"esbuild-android-arm64": {
+			"version": "0.14.54",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/esbuild-android-arm64/-/esbuild-android-arm64-0.14.54.tgz",
+			"integrity": "sha512-F9E+/QDi9sSkLaClO8SOV6etqPd+5DgJje1F9lOWoNncDdOBL2YF59IhsWATSt0TLZbYCf3pNlTHvVV5VfHdvg==",
+			"dev": true,
+			"optional": true
+		},
+		"esbuild-darwin-64": {
+			"version": "0.14.54",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/esbuild-darwin-64/-/esbuild-darwin-64-0.14.54.tgz",
+			"integrity": "sha512-jtdKWV3nBviOd5v4hOpkVmpxsBy90CGzebpbO9beiqUYVMBtSc0AL9zGftFuBon7PNDcdvNCEuQqw2x0wP9yug==",
+			"dev": true,
+			"optional": true
+		},
+		"esbuild-darwin-arm64": {
+			"version": "0.14.54",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.54.tgz",
+			"integrity": "sha512-OPafJHD2oUPyvJMrsCvDGkRrVCar5aVyHfWGQzY1dWnzErjrDuSETxwA2HSsyg2jORLY8yBfzc1MIpUkXlctmw==",
+			"dev": true,
+			"optional": true
+		},
+		"esbuild-freebsd-64": {
+			"version": "0.14.54",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.54.tgz",
+			"integrity": "sha512-OKwd4gmwHqOTp4mOGZKe/XUlbDJ4Q9TjX0hMPIDBUWWu/kwhBAudJdBoxnjNf9ocIB6GN6CPowYpR/hRCbSYAg==",
+			"dev": true,
+			"optional": true
+		},
+		"esbuild-freebsd-arm64": {
+			"version": "0.14.54",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.54.tgz",
+			"integrity": "sha512-sFwueGr7OvIFiQT6WeG0jRLjkjdqWWSrfbVwZp8iMP+8UHEHRBvlaxL6IuKNDwAozNUmbb8nIMXa7oAOARGs1Q==",
+			"dev": true,
+			"optional": true
+		},
+		"esbuild-linux-32": {
+			"version": "0.14.54",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/esbuild-linux-32/-/esbuild-linux-32-0.14.54.tgz",
+			"integrity": "sha512-1ZuY+JDI//WmklKlBgJnglpUL1owm2OX+8E1syCD6UAxcMM/XoWd76OHSjl/0MR0LisSAXDqgjT3uJqT67O3qw==",
+			"dev": true,
+			"optional": true
+		},
+		"esbuild-linux-64": {
+			"version": "0.14.54",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/esbuild-linux-64/-/esbuild-linux-64-0.14.54.tgz",
+			"integrity": "sha512-EgjAgH5HwTbtNsTqQOXWApBaPVdDn7XcK+/PtJwZLT1UmpLoznPd8c5CxqsH2dQK3j05YsB3L17T8vE7cp4cCg==",
+			"dev": true,
+			"optional": true
+		},
+		"esbuild-linux-arm": {
+			"version": "0.14.54",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/esbuild-linux-arm/-/esbuild-linux-arm-0.14.54.tgz",
+			"integrity": "sha512-qqz/SjemQhVMTnvcLGoLOdFpCYbz4v4fUo+TfsWG+1aOu70/80RV6bgNpR2JCrppV2moUQkww+6bWxXRL9YMGw==",
+			"dev": true,
+			"optional": true
+		},
+		"esbuild-linux-arm64": {
+			"version": "0.14.54",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.54.tgz",
+			"integrity": "sha512-WL71L+0Rwv+Gv/HTmxTEmpv0UgmxYa5ftZILVi2QmZBgX3q7+tDeOQNqGtdXSdsL8TQi1vIaVFHUPDe0O0kdig==",
+			"dev": true,
+			"optional": true
+		},
+		"esbuild-linux-mips64le": {
+			"version": "0.14.54",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.54.tgz",
+			"integrity": "sha512-qTHGQB8D1etd0u1+sB6p0ikLKRVuCWhYQhAHRPkO+OF3I/iSlTKNNS0Lh2Oc0g0UFGguaFZZiPJdJey3AGpAlw==",
+			"dev": true,
+			"optional": true
+		},
+		"esbuild-linux-ppc64le": {
+			"version": "0.14.54",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.54.tgz",
+			"integrity": "sha512-j3OMlzHiqwZBDPRCDFKcx595XVfOfOnv68Ax3U4UKZ3MTYQB5Yz3X1mn5GnodEVYzhtZgxEBidLWeIs8FDSfrQ==",
+			"dev": true,
+			"optional": true
+		},
+		"esbuild-linux-riscv64": {
+			"version": "0.14.54",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.54.tgz",
+			"integrity": "sha512-y7Vt7Wl9dkOGZjxQZnDAqqn+XOqFD7IMWiewY5SPlNlzMX39ocPQlOaoxvT4FllA5viyV26/QzHtvTjVNOxHZg==",
+			"dev": true,
+			"optional": true
+		},
+		"esbuild-linux-s390x": {
+			"version": "0.14.54",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.54.tgz",
+			"integrity": "sha512-zaHpW9dziAsi7lRcyV4r8dhfG1qBidQWUXweUjnw+lliChJqQr+6XD71K41oEIC3Mx1KStovEmlzm+MkGZHnHA==",
+			"dev": true,
+			"optional": true
+		},
+		"esbuild-netbsd-64": {
+			"version": "0.14.54",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.54.tgz",
+			"integrity": "sha512-PR01lmIMnfJTgeU9VJTDY9ZerDWVFIUzAtJuDHwwceppW7cQWjBBqP48NdeRtoP04/AtO9a7w3viI+PIDr6d+w==",
+			"dev": true,
+			"optional": true
+		},
+		"esbuild-openbsd-64": {
+			"version": "0.14.54",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.54.tgz",
+			"integrity": "sha512-Qyk7ikT2o7Wu76UsvvDS5q0amJvmRzDyVlL0qf5VLsLchjCa1+IAvd8kTBgUxD7VBUUVgItLkk609ZHUc1oCaw==",
+			"dev": true,
+			"optional": true
+		},
+		"esbuild-sunos-64": {
+			"version": "0.14.54",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/esbuild-sunos-64/-/esbuild-sunos-64-0.14.54.tgz",
+			"integrity": "sha512-28GZ24KmMSeKi5ueWzMcco6EBHStL3B6ubM7M51RmPwXQGLe0teBGJocmWhgwccA1GeFXqxzILIxXpHbl9Q/Kw==",
+			"dev": true,
+			"optional": true
+		},
+		"esbuild-windows-32": {
+			"version": "0.14.54",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/esbuild-windows-32/-/esbuild-windows-32-0.14.54.tgz",
+			"integrity": "sha512-T+rdZW19ql9MjS7pixmZYVObd9G7kcaZo+sETqNH4RCkuuYSuv9AGHUVnPoP9hhuE1WM1ZimHz1CIBHBboLU7w==",
+			"dev": true,
+			"optional": true
+		},
+		"esbuild-windows-64": {
+			"version": "0.14.54",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/esbuild-windows-64/-/esbuild-windows-64-0.14.54.tgz",
+			"integrity": "sha512-AoHTRBUuYwXtZhjXZbA1pGfTo8cJo3vZIcWGLiUcTNgHpJJMC1rVA44ZereBHMJtotyN71S8Qw0npiCIkW96cQ==",
+			"dev": true,
+			"optional": true
+		},
+		"esbuild-windows-arm64": {
+			"version": "0.14.54",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.54.tgz",
+			"integrity": "sha512-M0kuUvXhot1zOISQGXwWn6YtS+Y/1RT9WrVIOywZnJHo3jCDyewAc79aKNQWFCQm+xNHVTq9h8dZKvygoXQQRg==",
+			"dev": true,
+			"optional": true
+		},
+		"escape-html": {
+			"version": "1.0.3",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/escape-html/-/escape-html-1.0.3.tgz",
+			"integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="
+		},
+		"escape-string-regexp": {
+			"version": "5.0.0",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz",
+			"integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==",
+			"dev": true
+		},
+		"estree-walker": {
+			"version": "2.0.2",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/estree-walker/-/estree-walker-2.0.2.tgz",
+			"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="
+		},
+		"fast-glob": {
+			"version": "3.2.11",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/fast-glob/-/fast-glob-3.2.11.tgz",
+			"integrity": "sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==",
+			"dev": true,
+			"requires": {
+				"@nodelib/fs.stat": "^2.0.2",
+				"@nodelib/fs.walk": "^1.2.3",
+				"glob-parent": "^5.1.2",
+				"merge2": "^1.3.0",
+				"micromatch": "^4.0.4"
+			}
+		},
+		"fastq": {
+			"version": "1.13.0",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/fastq/-/fastq-1.13.0.tgz",
+			"integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==",
+			"dev": true,
+			"requires": {
+				"reusify": "^1.0.4"
+			}
+		},
+		"fill-range": {
+			"version": "7.0.1",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/fill-range/-/fill-range-7.0.1.tgz",
+			"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
+			"dev": true,
+			"requires": {
+				"to-regex-range": "^5.0.1"
+			}
+		},
+		"follow-redirects": {
+			"version": "1.15.1",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/follow-redirects/-/follow-redirects-1.15.1.tgz",
+			"integrity": "sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA=="
+		},
+		"form-data": {
+			"version": "4.0.0",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/form-data/-/form-data-4.0.0.tgz",
+			"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
+			"requires": {
+				"asynckit": "^0.4.0",
+				"combined-stream": "^1.0.8",
+				"mime-types": "^2.1.12"
+			}
+		},
+		"fsevents": {
+			"version": "2.3.2",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/fsevents/-/fsevents-2.3.2.tgz",
+			"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
+			"dev": true,
+			"optional": true
+		},
+		"function-bind": {
+			"version": "1.1.1",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/function-bind/-/function-bind-1.1.1.tgz",
+			"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
+			"dev": true
+		},
+		"glob-parent": {
+			"version": "5.1.2",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/glob-parent/-/glob-parent-5.1.2.tgz",
+			"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+			"dev": true,
+			"requires": {
+				"is-glob": "^4.0.1"
+			}
+		},
+		"has": {
+			"version": "1.0.3",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/has/-/has-1.0.3.tgz",
+			"integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
+			"dev": true,
+			"requires": {
+				"function-bind": "^1.1.1"
+			}
+		},
+		"is-binary-path": {
+			"version": "2.1.0",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/is-binary-path/-/is-binary-path-2.1.0.tgz",
+			"integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
+			"dev": true,
+			"requires": {
+				"binary-extensions": "^2.0.0"
+			}
+		},
+		"is-core-module": {
+			"version": "2.10.0",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/is-core-module/-/is-core-module-2.10.0.tgz",
+			"integrity": "sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg==",
+			"dev": true,
+			"requires": {
+				"has": "^1.0.3"
+			}
+		},
+		"is-extglob": {
+			"version": "2.1.1",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/is-extglob/-/is-extglob-2.1.1.tgz",
+			"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+			"dev": true
+		},
+		"is-glob": {
+			"version": "4.0.3",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/is-glob/-/is-glob-4.0.3.tgz",
+			"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+			"dev": true,
+			"requires": {
+				"is-extglob": "^2.1.1"
+			}
+		},
+		"is-number": {
+			"version": "7.0.0",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/is-number/-/is-number-7.0.0.tgz",
+			"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+			"dev": true
+		},
+		"jsonc-parser": {
+			"version": "3.1.0",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/jsonc-parser/-/jsonc-parser-3.1.0.tgz",
+			"integrity": "sha512-DRf0QjnNeCUds3xTjKlQQ3DpJD51GvDjJfnxUVWg6PZTo2otSm+slzNAxU/35hF8/oJIKoG9slq30JYOsF2azg==",
+			"dev": true
+		},
+		"local-pkg": {
+			"version": "0.4.2",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/local-pkg/-/local-pkg-0.4.2.tgz",
+			"integrity": "sha512-mlERgSPrbxU3BP4qBqAvvwlgW4MTg78iwJdGGnv7kibKjWcJksrG3t6LB5lXI93wXRDvG4NpUgJFmTG4T6rdrg==",
+			"dev": true
+		},
+		"lodash": {
+			"version": "4.17.21",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/lodash/-/lodash-4.17.21.tgz",
+			"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
+		},
+		"lodash-es": {
+			"version": "4.17.21",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/lodash-es/-/lodash-es-4.17.21.tgz",
+			"integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="
+		},
+		"lodash-unified": {
+			"version": "1.0.2",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/lodash-unified/-/lodash-unified-1.0.2.tgz",
+			"integrity": "sha512-OGbEy+1P+UT26CYi4opY4gebD8cWRDxAT6MAObIVQMiqYdxZr1g3QHWCToVsm31x2NkLS4K3+MC2qInaRMa39g=="
+		},
+		"magic-string": {
+			"version": "0.26.2",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/magic-string/-/magic-string-0.26.2.tgz",
+			"integrity": "sha512-NzzlXpclt5zAbmo6h6jNc8zl2gNRGHvmsZW4IvZhTC4W7k4OlLP+S5YLussa/r3ixNT66KOQfNORlXHSOy/X4A==",
+			"dev": true,
+			"requires": {
+				"sourcemap-codec": "^1.4.8"
+			}
+		},
+		"md-editor-v3": {
+			"version": "2.2.1",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/md-editor-v3/-/md-editor-v3-2.2.1.tgz",
+			"integrity": "sha512-nOd8mlEhvC99l9Y8pDMwEi6EdCAeBp88Ffl24We+2uz3/Iympctm92L1qyNicONJRhtZJacQ4oWTQGMYKGAxVg==",
+			"requires": {
+				"@types/marked": "^4.0.3"
+			}
+		},
+		"memoize-one": {
+			"version": "6.0.0",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/memoize-one/-/memoize-one-6.0.0.tgz",
+			"integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw=="
+		},
+		"merge2": {
+			"version": "1.4.1",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/merge2/-/merge2-1.4.1.tgz",
+			"integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
+			"dev": true
+		},
+		"micromatch": {
+			"version": "4.0.5",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/micromatch/-/micromatch-4.0.5.tgz",
+			"integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==",
+			"dev": true,
+			"requires": {
+				"braces": "^3.0.2",
+				"picomatch": "^2.3.1"
+			}
+		},
+		"mime-db": {
+			"version": "1.52.0",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/mime-db/-/mime-db-1.52.0.tgz",
+			"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="
+		},
+		"mime-types": {
+			"version": "2.1.35",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/mime-types/-/mime-types-2.1.35.tgz",
+			"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+			"requires": {
+				"mime-db": "1.52.0"
+			}
+		},
+		"minimatch": {
+			"version": "5.1.0",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/minimatch/-/minimatch-5.1.0.tgz",
+			"integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==",
+			"dev": true,
+			"requires": {
+				"brace-expansion": "^2.0.1"
+			}
+		},
+		"mlly": {
+			"version": "0.5.13",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/mlly/-/mlly-0.5.13.tgz",
+			"integrity": "sha512-0SK2fqoan+PMjADs4I2egAtrtNtpjqRez6PDTCeAdGjUQNJCvO5o9v2NEq52WA1jFmMU97qBr/JgdvCquehDbA==",
+			"dev": true,
+			"requires": {
+				"acorn": "^8.8.0",
+				"pathe": "^0.3.4",
+				"pkg-types": "^0.3.3",
+				"ufo": "^0.8.5"
+			}
+		},
+		"ms": {
+			"version": "2.1.2",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/ms/-/ms-2.1.2.tgz",
+			"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+			"dev": true
+		},
+		"nanoid": {
+			"version": "3.3.4",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/nanoid/-/nanoid-3.3.4.tgz",
+			"integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw=="
+		},
+		"normalize-path": {
+			"version": "3.0.0",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/normalize-path/-/normalize-path-3.0.0.tgz",
+			"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+			"dev": true
+		},
+		"normalize-wheel-es": {
+			"version": "1.2.0",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/normalize-wheel-es/-/normalize-wheel-es-1.2.0.tgz",
+			"integrity": "sha512-Wj7+EJQ8mSuXr2iWfnujrimU35R2W4FAErEyTmJoJ7ucwTn2hOUSsRehMb5RSYkxXGTM7Y9QpvPmp++w5ftoJw=="
+		},
+		"path-parse": {
+			"version": "1.0.7",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/path-parse/-/path-parse-1.0.7.tgz",
+			"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
+			"dev": true
+		},
+		"pathe": {
+			"version": "0.3.5",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/pathe/-/pathe-0.3.5.tgz",
+			"integrity": "sha512-grU/QeYP0ChuE5kjU2/k8VtAeODzbernHlue0gTa27+ayGIu3wqYBIPGfP9r5xSqgCgDd4nWrjKXEfxMillByg==",
+			"dev": true
+		},
+		"picocolors": {
+			"version": "1.0.0",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/picocolors/-/picocolors-1.0.0.tgz",
+			"integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ=="
+		},
+		"picomatch": {
+			"version": "2.3.1",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/picomatch/-/picomatch-2.3.1.tgz",
+			"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+			"dev": true
+		},
+		"pinia": {
+			"version": "2.0.20",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/pinia/-/pinia-2.0.20.tgz",
+			"integrity": "sha512-fdHHumXW/0U5HhxmY1emo3I4z85p8NJPdbtFQSlmJXFe3ktuF0pYNVgVtk2q+j2zCtTufY763xzaEMx0t6T59g==",
+			"requires": {
+				"@vue/devtools-api": "^6.2.1",
+				"vue-demi": "*"
+			}
+		},
+		"pkg-types": {
+			"version": "0.3.4",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/pkg-types/-/pkg-types-0.3.4.tgz",
+			"integrity": "sha512-s214f/xkRpwlwVBToWq9Mu0XlU3HhZMYCnr2var8+jjbavBHh/VCh4pBLsJW29rJ//B1jb4HlpMIaNIMH+W2/w==",
+			"dev": true,
+			"requires": {
+				"jsonc-parser": "^3.1.0",
+				"mlly": "^0.5.13",
+				"pathe": "^0.3.5"
+			}
+		},
+		"postcss": {
+			"version": "8.4.16",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/postcss/-/postcss-8.4.16.tgz",
+			"integrity": "sha512-ipHE1XBvKzm5xI7hiHCZJCSugxvsdq2mPnsq5+UF+VHCjiBvtDrlxJfMBToWaP9D5XlgNmcFGqoHmUn0EYEaRQ==",
+			"requires": {
+				"nanoid": "^3.3.4",
+				"picocolors": "^1.0.0",
+				"source-map-js": "^1.0.2"
+			}
+		},
+		"queue-microtask": {
+			"version": "1.2.3",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/queue-microtask/-/queue-microtask-1.2.3.tgz",
+			"integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
+			"dev": true
+		},
+		"readdirp": {
+			"version": "3.6.0",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/readdirp/-/readdirp-3.6.0.tgz",
+			"integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
+			"dev": true,
+			"requires": {
+				"picomatch": "^2.2.1"
+			}
+		},
+		"regenerator-runtime": {
+			"version": "0.13.9",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz",
+			"integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA=="
+		},
+		"resolve": {
+			"version": "1.22.1",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/resolve/-/resolve-1.22.1.tgz",
+			"integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==",
+			"dev": true,
+			"requires": {
+				"is-core-module": "^2.9.0",
+				"path-parse": "^1.0.7",
+				"supports-preserve-symlinks-flag": "^1.0.0"
+			}
+		},
+		"reusify": {
+			"version": "1.0.4",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/reusify/-/reusify-1.0.4.tgz",
+			"integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==",
+			"dev": true
+		},
+		"rollup": {
+			"version": "2.77.3",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/rollup/-/rollup-2.77.3.tgz",
+			"integrity": "sha512-/qxNTG7FbmefJWoeeYJFbHehJ2HNWnjkAFRKzWN/45eNBBF/r8lo992CwcJXEzyVxs5FmfId+vTSTQDb+bxA+g==",
+			"dev": true,
+			"requires": {
+				"fsevents": "~2.3.2"
+			}
+		},
+		"run-parallel": {
+			"version": "1.2.0",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/run-parallel/-/run-parallel-1.2.0.tgz",
+			"integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
+			"dev": true,
+			"requires": {
+				"queue-microtask": "^1.2.2"
+			}
+		},
+		"schart.js": {
+			"version": "3.0.4",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/schart.js/-/schart.js-3.0.4.tgz",
+			"integrity": "sha512-uylb2u9rrHX1jyAuSAJUQON8XTfyDKI9kWj1J3fUlCQCkLVZ4HG4+IiV8qm//Z71dqvLI78QZ/fCBw0reB22Zw=="
+		},
+		"scule": {
+			"version": "0.3.2",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/scule/-/scule-0.3.2.tgz",
+			"integrity": "sha512-zIvPdjOH8fv8CgrPT5eqtxHQXmPNnV/vHJYffZhE43KZkvULvpCTvOt1HPlFaCZx287INL9qaqrZg34e8NgI4g==",
+			"dev": true
+		},
+		"source-map": {
+			"version": "0.6.1",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/source-map/-/source-map-0.6.1.tgz",
+			"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
+		},
+		"source-map-js": {
+			"version": "1.0.2",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/source-map-js/-/source-map-js-1.0.2.tgz",
+			"integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw=="
+		},
+		"sourcemap-codec": {
+			"version": "1.4.8",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz",
+			"integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA=="
+		},
+		"strip-literal": {
+			"version": "0.4.0",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/strip-literal/-/strip-literal-0.4.0.tgz",
+			"integrity": "sha512-ql/sBDoJOybTKSIOWrrh8kgUEMjXMwRAkZTD0EwiwxQH/6tTPkZvMIEjp0CRlpi6V5FMiJyvxeRkEi1KrGISoA==",
+			"dev": true,
+			"requires": {
+				"acorn": "^8.7.1"
+			}
+		},
+		"supports-preserve-symlinks-flag": {
+			"version": "1.0.0",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
+			"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
+			"dev": true
+		},
+		"to-regex-range": {
+			"version": "5.0.1",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/to-regex-range/-/to-regex-range-5.0.1.tgz",
+			"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+			"dev": true,
+			"requires": {
+				"is-number": "^7.0.0"
+			}
+		},
+		"tslib": {
+			"version": "2.4.0",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/tslib/-/tslib-2.4.0.tgz",
+			"integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ=="
+		},
+		"typescript": {
+			"version": "4.7.4",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/typescript/-/typescript-4.7.4.tgz",
+			"integrity": "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==",
+			"dev": true
+		},
+		"ufo": {
+			"version": "0.8.5",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/ufo/-/ufo-0.8.5.tgz",
+			"integrity": "sha512-e4+UtA5IRO+ha6hYklwj6r7BjiGMxS0O+UaSg9HbaTefg4kMkzj4tXzEBajRR+wkxf+golgAWKzLbytCUDMJAA==",
+			"dev": true
+		},
+		"unimport": {
+			"version": "0.6.7",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/unimport/-/unimport-0.6.7.tgz",
+			"integrity": "sha512-EMoVqDjswHkU+nD098QYHXH7Mkw7KwGDQAyeRF2lgairJnuO+wpkhIcmCqrD1OPJmsjkTbJ2tW6Ap8St0PuWZA==",
+			"dev": true,
+			"requires": {
+				"@rollup/pluginutils": "^4.2.1",
+				"escape-string-regexp": "^5.0.0",
+				"fast-glob": "^3.2.11",
+				"local-pkg": "^0.4.2",
+				"magic-string": "^0.26.2",
+				"mlly": "^0.5.7",
+				"pathe": "^0.3.3",
+				"scule": "^0.3.2",
+				"strip-literal": "^0.4.0",
+				"unplugin": "^0.9.0"
+			}
+		},
+		"unplugin": {
+			"version": "0.9.3",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/unplugin/-/unplugin-0.9.3.tgz",
+			"integrity": "sha512-GWXxizZG+tobNs8fuGTCeilerkkfZTZax2iivuE4pxLaF9wTnPJHOq8tbLKDb5ohVb+2BXNjrU9xx59yWTUnuw==",
+			"dev": true,
+			"requires": {
+				"acorn": "^8.8.0",
+				"chokidar": "^3.5.3",
+				"webpack-sources": "^3.2.3",
+				"webpack-virtual-modules": "^0.4.4"
+			}
+		},
+		"unplugin-auto-import": {
+			"version": "0.11.2",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/unplugin-auto-import/-/unplugin-auto-import-0.11.2.tgz",
+			"integrity": "sha512-1+VwBfn9dtiYv9SQLKP1AvZolUbK9xTVeAT+iOcEk4EHSFUlmIqBVLEKI76cifSQTLOJ3rZyPrEgptf3SZNLlQ==",
+			"dev": true,
+			"requires": {
+				"@antfu/utils": "^0.5.2",
+				"@rollup/pluginutils": "^4.2.1",
+				"local-pkg": "^0.4.2",
+				"magic-string": "^0.26.2",
+				"unimport": "^0.6.7",
+				"unplugin": "^0.9.3"
+			}
+		},
+		"unplugin-vue-components": {
+			"version": "0.22.4",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/unplugin-vue-components/-/unplugin-vue-components-0.22.4.tgz",
+			"integrity": "sha512-2rRZcM9OnJGXnYxQNfaceEYuPeVACcWySIjy8WBwIiN3onr980TmA3XE5pRJFt8zoQrUA+c46oyIq96noLqrEQ==",
+			"dev": true,
+			"requires": {
+				"@antfu/utils": "^0.5.2",
+				"@rollup/pluginutils": "^4.2.1",
+				"chokidar": "^3.5.3",
+				"debug": "^4.3.4",
+				"fast-glob": "^3.2.11",
+				"local-pkg": "^0.4.2",
+				"magic-string": "^0.26.2",
+				"minimatch": "^5.1.0",
+				"resolve": "^1.22.1",
+				"unplugin": "^0.9.0"
+			}
+		},
+		"vite": {
+			"version": "3.0.9",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/vite/-/vite-3.0.9.tgz",
+			"integrity": "sha512-waYABTM+G6DBTCpYAxvevpG50UOlZuynR0ckTK5PawNVt7ebX6X7wNXHaGIO6wYYFXSM7/WcuFuO2QzhBB6aMw==",
+			"dev": true,
+			"requires": {
+				"esbuild": "^0.14.47",
+				"fsevents": "~2.3.2",
+				"postcss": "^8.4.16",
+				"resolve": "^1.22.1",
+				"rollup": ">=2.75.6 <2.77.0 || ~2.77.0"
+			}
+		},
+		"vite-plugin-vue-setup-extend": {
+			"version": "0.4.0",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/vite-plugin-vue-setup-extend/-/vite-plugin-vue-setup-extend-0.4.0.tgz",
+			"integrity": "sha512-WMbjPCui75fboFoUTHhdbXzu4Y/bJMv5N9QT9a7do3wNMNHHqrk+Tn2jrSJU0LS5fGl/EG+FEDBYVUeWIkDqXQ==",
+			"dev": true,
+			"requires": {
+				"@vue/compiler-sfc": "^3.2.29",
+				"magic-string": "^0.25.7"
+			},
+			"dependencies": {
+				"magic-string": {
+					"version": "0.25.9",
+					"resolved": "https://repo.huaweicloud.com/repository/npm/magic-string/-/magic-string-0.25.9.tgz",
+					"integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==",
+					"dev": true,
+					"requires": {
+						"sourcemap-codec": "^1.4.8"
+					}
+				}
+			}
+		},
+		"vue": {
+			"version": "3.2.37",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/vue/-/vue-3.2.37.tgz",
+			"integrity": "sha512-bOKEZxrm8Eh+fveCqS1/NkG/n6aMidsI6hahas7pa0w/l7jkbssJVsRhVDs07IdDq7h9KHswZOgItnwJAgtVtQ==",
+			"requires": {
+				"@vue/compiler-dom": "3.2.37",
+				"@vue/compiler-sfc": "3.2.37",
+				"@vue/runtime-dom": "3.2.37",
+				"@vue/server-renderer": "3.2.37",
+				"@vue/shared": "3.2.37"
+			}
+		},
+		"vue-cropperjs": {
+			"version": "5.0.0",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/vue-cropperjs/-/vue-cropperjs-5.0.0.tgz",
+			"integrity": "sha512-RhnC8O33uRZNkn74aiHZwNHnBJOXWlS4P6gsRI0lw4cZlWjKSCywZI9oSI9POlIPI6OYv30jvnHMXGch85tw7w==",
+			"requires": {
+				"cropperjs": "^1.5.6"
+			}
+		},
+		"vue-demi": {
+			"version": "0.13.8",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/vue-demi/-/vue-demi-0.13.8.tgz",
+			"integrity": "sha512-Vy1zbZhCOdsmvGR6tJhAvO5vhP7eiS8xkbYQSoVa7o6KlIy3W8Rc53ED4qI4qpeRDjv3mLfXSEpYU6Yq4pgXRg=="
+		},
+		"vue-router": {
+			"version": "4.1.3",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/vue-router/-/vue-router-4.1.3.tgz",
+			"integrity": "sha512-XvK81bcYglKiayT7/vYAg/f36ExPC4t90R/HIpzrZ5x+17BOWptXLCrEPufGgZeuq68ww4ekSIMBZY1qdUdfjA==",
+			"requires": {
+				"@vue/devtools-api": "^6.1.4"
+			}
+		},
+		"vue-schart": {
+			"version": "2.0.0",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/vue-schart/-/vue-schart-2.0.0.tgz",
+			"integrity": "sha512-qAu3e5wfMcq26wK1xeHExEWfGpnjfoN1R/9QXblNi+AsU/p52X7tTwhi+Fw7H/otfEufhEY2X7z7emaoF4QO+g==",
+			"requires": {
+				"schart.js": "^3.0.0"
+			}
+		},
+		"vue-tsc": {
+			"version": "0.38.9",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/vue-tsc/-/vue-tsc-0.38.9.tgz",
+			"integrity": "sha512-Yoy5phgvGqyF98Fb4mYqboR4Q149jrdcGv5kSmufXJUq++RZJ2iMVG0g6zl+v3t4ORVWkQmRpsV4x2szufZ0LQ==",
+			"dev": true,
+			"requires": {
+				"@volar/vue-typescript": "0.38.9"
+			}
+		},
+		"wangeditor": {
+			"version": "4.7.15",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/wangeditor/-/wangeditor-4.7.15.tgz",
+			"integrity": "sha512-aPTdREd8BxXVyJ5MI+LU83FQ7u1EPd341iXIorRNYSOvoimNoZ4nPg+yn3FGbB93/owEa6buLw8wdhYnMCJQLg==",
+			"requires": {
+				"@babel/runtime": "^7.11.2",
+				"@babel/runtime-corejs3": "^7.11.2",
+				"tslib": "^2.1.0"
+			}
+		},
+		"webpack-sources": {
+			"version": "3.2.3",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/webpack-sources/-/webpack-sources-3.2.3.tgz",
+			"integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==",
+			"dev": true
+		},
+		"webpack-virtual-modules": {
+			"version": "0.4.4",
+			"resolved": "https://repo.huaweicloud.com/repository/npm/webpack-virtual-modules/-/webpack-virtual-modules-0.4.4.tgz",
+			"integrity": "sha512-h9atBP/bsZohWpHnr+2sic8Iecb60GxftXsWNLLLSqewgIsGzByd2gcIID4nXcG+3tNe4GQG3dLcff3kXupdRA==",
+			"dev": true
+		}
+	}
+}

+ 35 - 30
package.json

@@ -1,32 +1,37 @@
 {
-  "name": "vue-manage-system",
-  "version": "5.2.0",
-  "private": true,
-  "scripts": {
-    "dev": "vite",
-    "build": "vite build",
-    "serve": "vite preview"
-  },
-  "dependencies": {
-    "axios": "^0.21.1",
-    "element-plus": "^1.0.2-beta.52",
-    "md-editor-v3": "^2.2.0",
-    "pinia": "^2.0.14",
-    "vue": "^3.1.2",
-    "vue-cropperjs": "^5.0.0",
-    "vue-i18n": "^9.0.0",
-    "vue-router": "^4.0.10",
-    "vue-schart": "^2.0.0",
-    "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"
-  ]
+	"name": "vue-manage-system",
+	"version": "5.3.0",
+	"private": true,
+	"scripts": {
+		"dev": "vite",
+		"build": "vue-tsc --noEmit && vite build",
+		"serve": "vite preview"
+	},
+	"dependencies": {
+		"@element-plus/icons-vue": "^2.0.9",
+		"axios": "^0.27.2",
+		"element-plus": "^2.2.14",
+		"md-editor-v3": "^2.2.1",
+		"pinia": "^2.0.20",
+		"vue": "^3.2.37",
+		"vue-cropperjs": "^5.0.0",
+		"vue-router": "^4.1.3",
+		"vue-schart": "^2.0.0",
+		"wangeditor": "^4.7.15"
+	},
+	"devDependencies": {
+		"@vitejs/plugin-vue": "^3.0.0",
+		"@vue/compiler-sfc": "^3.1.2",
+		"typescript": "^4.6.4",
+		"unplugin-auto-import": "^0.11.2",
+		"unplugin-vue-components": "^0.22.4",
+		"vite": "^3.0.0",
+		"vite-plugin-vue-setup-extend": "^0.4.0",
+		"vue-tsc": "^0.38.4"
+	},
+	"browserslist": [
+		"> 1%",
+		"last 2 versions",
+		"not dead"
+	]
 }

+ 3 - 7
src/App.vue

@@ -1,12 +1,8 @@
 <template>
-    <router-view />
+	<router-view />
 </template>
 
-<script>
-export default {};
-</script>
-
 <style>
-@import "./assets/css/main.css";
-@import "./assets/css/color-dark.css";
+@import './assets/css/main.css';
+@import './assets/css/color-dark.css';
 </style>

+ 2 - 3
src/api/index.js → src/api/index.ts

@@ -1,9 +1,8 @@
 import request from '../utils/request';
 
-export const fetchData = query => {
+export const fetchData = () => {
     return request({
         url: './table.json',
-        method: 'get',
-        params: query
+        method: 'get'
     });
 };

+ 24 - 0
src/assets/css/main.css

@@ -174,4 +174,28 @@ a {
 
 .v-note-wrapper .v-note-panel {
     min-height: 500px;
+}
+
+[class*=" el-icon-"], [class^=el-icon-] {
+    speak: none;
+    font-style: normal;
+    font-weight: 400;
+    font-variant: normal;
+    text-transform: none;
+    line-height: 1;
+    vertical-align: baseline;
+    display: inline-block;
+    -webkit-font-smoothing: antialiased;
+    -moz-osx-font-smoothing: grayscale;
+}
+.el-sub-menu [class^=el-icon-] {
+    vertical-align: middle;
+    margin-right: 5px;
+    width: 24px;
+    text-align: center;
+    font-size: 18px;
+}
+
+[hidden]{
+    display: none !important;
 }

+ 118 - 125
src/components/Header.vue

@@ -1,161 +1,154 @@
 <template>
-    <div class="header">
-        <!-- 折叠按钮 -->
-        <div class="collapse-btn" @click="collapseChage">
-            <i v-if="!sidebar.collapse" class="el-icon-s-fold"></i>
-            <i v-else class="el-icon-s-unfold"></i>
-        </div>
-        <div class="logo">后台管理系统</div>
-        <div class="header-right">
-            <div class="header-user-con">
-                <!-- 消息中心 -->
-                <div class="btn-bell">
-                    <el-tooltip effect="dark" :content="message?`有${message}条未读消息`:`消息中心`" placement="bottom">
-                        <router-link to="/tabs">
-                            <i class="el-icon-bell"></i>
-                        </router-link>
-                    </el-tooltip>
-                    <span class="btn-bell-badge" v-if="message"></span>
-                </div>
-                <!-- 用户头像 -->
-                <div class="user-avator">
-                    <img src="../assets/img/img.jpg" />
-                </div>
-                <!-- 用户名下拉菜单 -->
-                <el-dropdown class="user-name" trigger="click" @command="handleCommand">
-                    <span class="el-dropdown-link">
-                        {{username}}
-                        <i class="el-icon-caret-bottom"></i>
-                    </span>
-                    <template #dropdown>
-                        <el-dropdown-menu>
-                            <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>
-                </el-dropdown>
-            </div>
-        </div>
-    </div>
+	<div class="header">
+		<!-- 折叠按钮 -->
+		<div class="collapse-btn" @click="collapseChage">
+			<el-icon v-if="sidebar.collapse"><Expand /></el-icon>
+			<el-icon v-else><Fold /></el-icon>
+		</div>
+		<div class="logo">后台管理系统</div>
+		<div class="header-right">
+			<div class="header-user-con">
+				<!-- 消息中心 -->
+				<div class="btn-bell" @click="router.push('/tabs')">
+					<el-tooltip
+						effect="dark"
+						:content="message ? `有${message}条未读消息` : `消息中心`"
+						placement="bottom"
+					>
+						<i class="el-icon-lx-notice"></i>
+					</el-tooltip>
+					<span class="btn-bell-badge" v-if="message"></span>
+				</div>
+				<!-- 用户头像 -->
+				<el-avatar class="user-avator" :size="30" :src="imgurl" />
+				<!-- 用户名下拉菜单 -->
+				<el-dropdown class="user-name" trigger="click" @command="handleCommand">
+					<span class="el-dropdown-link">
+						{{ username }}
+						<el-icon class="el-icon--right">
+							<arrow-down />
+						</el-icon>
+					</span>
+					<template #dropdown>
+						<el-dropdown-menu>
+							<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>
+				</el-dropdown>
+			</div>
+		</div>
+	</div>
 </template>
-<script>
-import { computed, onMounted } from "vue";
-import { useSidebarStore } from '../store/sidebar'
-import { useRouter } from "vue-router";
-export default {
-    setup() {
-        const username = localStorage.getItem("ms_username");
-        const message = 2;
+<script setup lang="ts">
+import { onMounted } from 'vue';
+import { useSidebarStore } from '../store/sidebar';
+import { useRouter } from 'vue-router';
+import imgurl from '../assets/img/img.jpg';
 
-        const sidebar = useSidebarStore();
-        // 侧边栏折叠
-        const collapseChage = () => {
-            sidebar.handleCollapse();
-        };
+const username: string | null = localStorage.getItem('ms_username');
+const message: number = 2;
 
-        onMounted(() => {
-            if (document.body.clientWidth < 1500) {
-                collapseChage();
-            }
-        });
+const sidebar = useSidebarStore();
+// 侧边栏折叠
+const collapseChage = () => {
+	sidebar.handleCollapse();
+};
 
-        // 用户名下拉菜单选择事件
-        const router = useRouter();
-        const handleCommand = (command) => {
-            if (command == "loginout") {
-                localStorage.removeItem("ms_username");
-                router.push("/login");
-            } else if (command == "user") {
-                router.push("/user");
-            }
-        };
+onMounted(() => {
+	if (document.body.clientWidth < 1500) {
+		collapseChage();
+	}
+});
 
-        return {
-            sidebar,
-            username,
-            message,
-            collapseChage,
-            handleCommand,
-        };
-    },
+// 用户名下拉菜单选择事件
+const router = useRouter();
+const handleCommand = (command: string) => {
+	if (command == 'loginout') {
+		localStorage.removeItem('ms_username');
+		router.push('/login');
+	} else if (command == 'user') {
+		router.push('/user');
+	}
 };
 </script>
 <style scoped>
 .header {
-    position: relative;
-    box-sizing: border-box;
-    width: 100%;
-    height: 70px;
-    font-size: 22px;
-    color: #fff;
+	position: relative;
+	box-sizing: border-box;
+	width: 100%;
+	height: 70px;
+	font-size: 22px;
+	color: #fff;
 }
 .collapse-btn {
-    float: left;
-    padding: 0 21px;
-    cursor: pointer;
-    line-height: 70px;
+	display: flex;
+	justify-content: center;
+	align-items: center;
+	height: 100%;
+	float: left;
+	padding: 0 21px;
+	cursor: pointer;
 }
 .header .logo {
-    float: left;
-    width: 250px;
-    line-height: 70px;
+	float: left;
+	width: 250px;
+	line-height: 70px;
 }
 .header-right {
-    float: right;
-    padding-right: 50px;
+	float: right;
+	padding-right: 50px;
 }
 .header-user-con {
-    display: flex;
-    height: 70px;
-    align-items: center;
+	display: flex;
+	height: 70px;
+	align-items: center;
 }
 .btn-fullscreen {
-    transform: rotate(45deg);
-    margin-right: 5px;
-    font-size: 24px;
+	transform: rotate(45deg);
+	margin-right: 5px;
+	font-size: 24px;
 }
 .btn-bell,
 .btn-fullscreen {
-    position: relative;
-    width: 30px;
-    height: 30px;
-    text-align: center;
-    border-radius: 15px;
-    cursor: pointer;
+	position: relative;
+	width: 30px;
+	height: 30px;
+	text-align: center;
+	border-radius: 15px;
+	cursor: pointer;
+	display: flex;
+	align-items: center;
 }
 .btn-bell-badge {
-    position: absolute;
-    right: 0;
-    top: -2px;
-    width: 8px;
-    height: 8px;
-    border-radius: 4px;
-    background: #f56c6c;
-    color: #fff;
+	position: absolute;
+	right: 4px;
+	top: 0px;
+	width: 8px;
+	height: 8px;
+	border-radius: 4px;
+	background: #f56c6c;
+	color: #fff;
 }
-.btn-bell .el-icon-bell {
-    color: #fff;
+.btn-bell .el-icon-lx-notice {
+	color: #fff;
 }
 .user-name {
-    margin-left: 10px;
+	margin-left: 10px;
 }
 .user-avator {
-    margin-left: 20px;
-}
-.user-avator img {
-    display: block;
-    width: 40px;
-    height: 40px;
-    border-radius: 50%;
+	margin-left: 20px;
 }
 .el-dropdown-link {
-    color: #fff;
-    cursor: pointer;
+	color: #fff;
+	cursor: pointer;
+	display: flex;
+	align-items: center;
 }
 .el-dropdown-menu__item {
-    text-align: center;
+	text-align: center;
 }
 </style>

+ 146 - 138
src/components/Sidebar.vue

@@ -1,156 +1,164 @@
 <template>
-    <div class="sidebar">
-        <el-menu class="sidebar-el-menu" :default-active="onRoutes" :collapse="sidebar.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">
-                        <template #title>
-                            <i :class="item.icon"></i>
-                            <span>{{ item.title }}</span>
-                        </template>
-                        <template v-for="subItem in item.subs">
-                            <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-submenu>
-                            <el-menu-item v-else :index="subItem.index" :key="subItem.index">{{ subItem.title }}
-                            </el-menu-item>
-                        </template>
-                    </el-submenu>
-                </template>
-                <template v-else>
-                    <el-menu-item :index="item.index" :key="item.index">
-                        <i :class="item.icon"></i>
-                        <template #title>{{ item.title }}</template>
-                    </el-menu-item>
-                </template>
-            </template>
-        </el-menu>
-    </div>
+	<div class="sidebar">
+		<el-menu
+			class="sidebar-el-menu"
+			:default-active="onRoutes"
+			:collapse="sidebar.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-sub-menu :index="item.index" :key="item.index" v-permiss="item.permiss">
+						<template #title>
+							<el-icon>
+								<component :is="item.icon"></component>
+							</el-icon>
+							<span>{{ item.title }}</span>
+						</template>
+						<template v-for="subItem in item.subs">
+							<el-sub-menu
+								v-if="subItem.subs"
+								:index="subItem.index"
+								:key="subItem.index"
+								v-permiss="item.permiss"
+							>
+								<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-sub-menu>
+							<el-menu-item v-else :index="subItem.index" v-permiss="item.permiss">
+								{{ subItem.title }}
+							</el-menu-item>
+						</template>
+					</el-sub-menu>
+				</template>
+				<template v-else>
+					<el-menu-item :index="item.index" :key="item.index" v-permiss="item.permiss">
+						<el-icon>
+							<component :is="item.icon"></component>
+						</el-icon>
+						<template #title>{{ item.title }}</template>
+					</el-menu-item>
+				</template>
+			</template>
+		</el-menu>
+	</div>
 </template>
 
-<script>
-import { computed } from "vue";
-import { useSidebarStore } from '../store/sidebar'
-import { useRoute } from "vue-router";
-export default {
-    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: "富文本编辑器",
-                            },
-                            {
-                                index: "/markdown",
-                                title: "markdown编辑器",
-                            },
-                        ],
-                    },
-                ],
-            },
-            {
-                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: "支持作者",
-            },
-        ];
+<script setup lang="ts">
+import { computed } from 'vue';
+import { useSidebarStore } from '../store/sidebar';
+import { useRoute } from 'vue-router';
 
-        const route = useRoute();
-        const onRoutes = computed(() => {
-            return route.path;
-        });
+const items = [
+	{
+		icon: 'Odometer',
+		index: '/dashboard',
+		title: '系统首页',
+		permiss: '1'
+	},
+	{
+		icon: 'Calendar',
+		index: '/table',
+		title: '基础表格',
+		permiss: '2'
+	},
+	{
+		icon: 'DocumentCopy',
+		index: '/tabs',
+		title: 'tab选项卡',
+		permiss: '3'
+	},
+	{
+		icon: 'Edit',
+		index: '3',
+		title: '表单相关',
+		permiss: '4',
+		subs: [
+			{
+				index: '/form',
+				title: '基本表单',
+				permiss: '5'
+			},
+			{
+				index: '/upload',
+				title: '文件上传',
+				permiss: '6'
+			},
+			{
+				index: '4',
+				title: '三级菜单',
+				permiss: '7',
+				subs: [
+					{
+						index: '/editor',
+						title: '富文本编辑器',
+						permiss: '8'
+					},
+					{
+						index: '/markdown',
+						title: 'markdown编辑器',
+						permiss: '9'
+					}
+				]
+			}
+		]
+	},
+	{
+		icon: 'Setting',
+		index: '/icon',
+		title: '自定义图标',
+		permiss: '10'
+	},
+	{
+		icon: 'PieChart',
+		index: '/charts',
+		title: 'schart图表',
+		permiss: '11'
+	},
+	{
+		icon: 'Warning',
+		index: '/permission',
+		title: '权限管理',
+		permiss: '13'
+	},
+	{
+		icon: 'CoffeeCup',
+		index: '/donate',
+		title: '支持作者',
+		permiss: '14'
+	}
+];
 
-        const sidebar = useSidebarStore();
+const route = useRoute();
+const onRoutes = computed(() => {
+	return route.path;
+});
 
-        return {
-            items,
-            onRoutes,
-            sidebar,
-        };
-    },
-};
+const sidebar = useSidebarStore();
 </script>
 
 <style scoped>
 .sidebar {
-    display: block;
-    position: absolute;
-    left: 0;
-    top: 70px;
-    bottom: 0;
-    overflow-y: scroll;
+	display: block;
+	position: absolute;
+	left: 0;
+	top: 70px;
+	bottom: 0;
+	overflow-y: scroll;
 }
 .sidebar::-webkit-scrollbar {
-    width: 0;
+	width: 0;
 }
 .sidebar-el-menu:not(.el-menu--collapse) {
-    width: 250px;
+	width: 250px;
 }
 .sidebar > ul {
-    height: 100%;
+	height: 100%;
 }
 </style>

+ 133 - 139
src/components/Tags.vue

@@ -1,174 +1,168 @@
 <template>
-    <div class="tags" v-if="tags.show">
-        <ul>
-            <li class="tags-li" v-for="(item,index) in tags.list" :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>
-                </span>
-            </li>
-        </ul>
-        <div class="tags-close-box">
-            <el-dropdown @command="handleTags">
-                <el-button size="mini" type="primary">
-                    标签选项
-                    <i class="el-icon-arrow-down el-icon--right"></i>
-                </el-button>
-                <template #dropdown>
-                    <el-dropdown-menu size="small">
-                        <el-dropdown-item command="other">关闭其他</el-dropdown-item>
-                        <el-dropdown-item command="all">关闭所有</el-dropdown-item>
-                    </el-dropdown-menu>
-                </template>
-            </el-dropdown>
-        </div>
-    </div>
+	<div class="tags" v-if="tags.show">
+		<ul>
+			<li
+				class="tags-li"
+				v-for="(item, index) in tags.list"
+				:class="{ active: isActive(item.path) }"
+				:key="index"
+			>
+				<router-link :to="item.path" class="tags-li-title">{{ item.title }}</router-link>
+				<el-icon @click="closeTags(index)"><Close /></el-icon>
+			</li>
+		</ul>
+		<div class="tags-close-box">
+			<el-dropdown @command="handleTags">
+				<el-button size="small" type="primary">
+					标签选项
+					<el-icon class="el-icon--right">
+						<arrow-down />
+					</el-icon>
+				</el-button>
+				<template #dropdown>
+					<el-dropdown-menu size="small">
+						<el-dropdown-item command="other">关闭其他</el-dropdown-item>
+						<el-dropdown-item command="all">关闭所有</el-dropdown-item>
+					</el-dropdown-menu>
+				</template>
+			</el-dropdown>
+		</div>
+	</div>
 </template>
 
-<script>
-import { useTagsStore } from '../store/tags'
-import { onBeforeRouteUpdate, useRoute, useRouter } from "vue-router";
-export default {
-    setup() {
-        const route = useRoute();
-        const router = useRouter();
-        const isActive = (path) => {
-            return path === route.fullPath;
-        };
+<script setup lang="ts">
+import { useTagsStore } from '../store/tags';
+import { onBeforeRouteUpdate, useRoute, useRouter } from 'vue-router';
 
-        const tags = useTagsStore();
-        // 关闭单个标签
-        const closeTags = (index) => {
-            const delItem = tags.list[index];
-            tags.delTagsItem(index);
-            const item = tags.list[index] ? tags.list[index] : tags.list[index - 1];
-            if (item) {
-                delItem.path === route.fullPath && router.push(item.path);
-            } else {
-                router.push("/");
-            }
-        };
-
-        // 设置标签
-        const setTags = (route) => {
-            const isExist = tags.list.some((item) => {
-                return item.path === route.fullPath;
-            });
-            if (!isExist) {
-                if (tags.list.length >= 8) tags.delTagsItem(0);
-                tags.setTagsItem({
-                    name: route.name,
-                    title: route.meta.title,
-                    path: route.fullPath,
-                });
-            }
-        };
-        setTags(route);
-        onBeforeRouteUpdate((to) => {
-            setTags(to);
-        });
+const route = useRoute();
+const router = useRouter();
+const isActive = (path: string) => {
+	return path === route.fullPath;
+};
 
-        // 关闭全部标签
-        const closeAll = () => {
-            tags.clearTags();
-            router.push("/");
-        };
-        // 关闭其他标签
-        const closeOther = () => {
-            const curItem = tags.list.filter((item) => {
-                return item.path === route.fullPath;
-            });
-            tags.closeTagsOther(curItem);
-        };
-        const handleTags = (command) => {
-            command === "other" ? closeOther() : closeAll();
-        };
+const tags = useTagsStore();
+// 关闭单个标签
+const closeTags = (index: number) => {
+	const delItem = tags.list[index];
+	tags.delTagsItem(index);
+	const item = tags.list[index] ? tags.list[index] : tags.list[index - 1];
+	if (item) {
+		delItem.path === route.fullPath && router.push(item.path);
+	} else {
+		router.push('/');
+	}
+};
 
-        // 关闭当前页面的标签页
-        // tags.closeCurrentTag({
-        //     $router: router,
-        //     $route: route
-        // });
+// 设置标签
+const setTags = (route: any) => {
+	const isExist = tags.list.some(item => {
+		return item.path === route.fullPath;
+	});
+	if (!isExist) {
+		if (tags.list.length >= 8) tags.delTagsItem(0);
+		tags.setTagsItem({
+			name: route.name,
+			title: route.meta.title,
+			path: route.fullPath
+		});
+	}
+};
+setTags(route);
+onBeforeRouteUpdate(to => {
+	setTags(to);
+});
 
-        return {
-            isActive,
-            tags,
-            closeTags,
-            handleTags,
-        };
-    },
+// 关闭全部标签
+const closeAll = () => {
+	tags.clearTags();
+	router.push('/');
+};
+// 关闭其他标签
+const closeOther = () => {
+	const curItem = tags.list.filter(item => {
+		return item.path === route.fullPath;
+	});
+	tags.closeTagsOther(curItem);
+};
+const handleTags = (command: string) => {
+	command === 'other' ? closeOther() : closeAll();
 };
-</script>
 
+// 关闭当前页面的标签页
+// tags.closeCurrentTag({
+//     $router: router,
+//     $route: route
+// });
+</script>
 
 <style>
 .tags {
-    position: relative;
-    height: 30px;
-    overflow: hidden;
-    background: #fff;
-    padding-right: 120px;
-    box-shadow: 0 5px 10px #ddd;
+	position: relative;
+	height: 30px;
+	overflow: hidden;
+	background: #fff;
+	padding-right: 120px;
+	box-shadow: 0 5px 10px #ddd;
 }
 
 .tags ul {
-    box-sizing: border-box;
-    width: 100%;
-    height: 100%;
+	box-sizing: border-box;
+	width: 100%;
+	height: 100%;
 }
 
 .tags-li {
-    float: left;
-    margin: 3px 5px 2px 3px;
-    border-radius: 3px;
-    font-size: 12px;
-    overflow: hidden;
-    cursor: pointer;
-    height: 23px;
-    line-height: 23px;
-    border: 1px solid #e9eaec;
-    background: #fff;
-    padding: 0 5px 0 12px;
-    vertical-align: middle;
-    color: #666;
-    -webkit-transition: all 0.3s ease-in;
-    -moz-transition: all 0.3s ease-in;
-    transition: all 0.3s ease-in;
+	display: flex;
+	align-items: center;
+	float: left;
+	margin: 3px 5px 2px 3px;
+	border-radius: 3px;
+	font-size: 12px;
+	overflow: hidden;
+	cursor: pointer;
+	height: 23px;
+	border: 1px solid #e9eaec;
+	background: #fff;
+	padding: 0 5px 0 12px;
+	color: #666;
+	-webkit-transition: all 0.3s ease-in;
+	-moz-transition: all 0.3s ease-in;
+	transition: all 0.3s ease-in;
 }
 
 .tags-li:not(.active):hover {
-    background: #f8f8f8;
+	background: #f8f8f8;
 }
 
 .tags-li.active {
-    color: #fff;
+	color: #fff;
 }
 
 .tags-li-title {
-    float: left;
-    max-width: 80px;
-    overflow: hidden;
-    white-space: nowrap;
-    text-overflow: ellipsis;
-    margin-right: 5px;
-    color: #666;
+	float: left;
+	max-width: 80px;
+	overflow: hidden;
+	white-space: nowrap;
+	text-overflow: ellipsis;
+	margin-right: 5px;
+	color: #666;
 }
 
 .tags-li.active .tags-li-title {
-    color: #fff;
+	color: #fff;
 }
 
 .tags-close-box {
-    position: absolute;
-    right: 0;
-    top: 0;
-    box-sizing: border-box;
-    padding-top: 1px;
-    text-align: center;
-    width: 110px;
-    height: 30px;
-    background: #fff;
-    box-shadow: -3px 0 15px 3px rgba(0, 0, 0, 0.1);
-    z-index: 10;
+	position: absolute;
+	right: 0;
+	top: 0;
+	box-sizing: border-box;
+	padding-top: 1px;
+	text-align: center;
+	width: 110px;
+	height: 30px;
+	background: #fff;
+	box-shadow: -3px 0 15px 3px rgba(0, 0, 0, 0.1);
+	z-index: 10;
 }
 </style>

+ 0 - 11
src/main.js

@@ -1,11 +0,0 @@
-import {createApp} from 'vue'
-import { createPinia } from 'pinia'
-import App from './App.vue'
-import router from './router'
-import installElementPlus from './plugins/element'
-import './assets/css/icon.css'
-const app = createApp(App)
-installElementPlus(app)
-app.use(createPinia())
-    .use(router)
-    .mount('#app')

+ 31 - 0
src/main.ts

@@ -0,0 +1,31 @@
+import {createApp} from 'vue'
+import { createPinia } from 'pinia'
+import ElementPlus from 'element-plus'
+import * as ElementPlusIconsVue from '@element-plus/icons-vue'
+import App from './App.vue'
+import router from './router'
+import { usePermissStore } from './store/permiss'
+import 'element-plus/dist/index.css'
+import './assets/css/icon.css'
+
+
+const app = createApp(App)
+
+app.use(createPinia())
+app.use(router)
+app.use(ElementPlus)
+// 注册elementplus图标
+for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
+    app.component(key, component)
+}
+// 自定义权限指令
+const permiss = usePermissStore()
+app.directive('permiss', {
+    mounted(el, binding) {
+        if(!permiss.key.includes(String(binding.value))){
+            el['hidden'] = true;
+        }
+    }
+})
+
+app.mount('#app')

+ 0 - 17
src/plugins/element.js

@@ -1,17 +0,0 @@
-import ElementPlus from 'element-plus'
-import { createI18n } from 'vue-i18n'
-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'
-
-const i18n = createI18n({
-  locale: localeZH.name,
-  fallbackLocale: localeEN.name,
-  messages,
-})
-
-export default (app) => {
-  app.use(ElementPlus, { locale:localeZH })
-  app.use(i18n)
-}

+ 146 - 150
src/router/index.js → src/router/index.ts

@@ -1,151 +1,147 @@
-import {createRouter, createWebHashHistory} from "vue-router";
-import Home from "../views/Home.vue";
-
-const routes = [
-    {
-        path: '/',
-        redirect: '/dashboard'
-    }, {
-        path: "/",
-        name: "Home",
-        component: Home,
-        children: [
-            {
-                path: "/dashboard",
-                name: "dashboard",
-                meta: {
-                    title: '系统首页'
-                },
-                component: () => import ( /* webpackChunkName: "dashboard" */ "../views/Dashboard.vue")
-            }, {
-                path: "/table",
-                name: "basetable",
-                meta: {
-                    title: '表格'
-                },
-                component: () => import ( /* webpackChunkName: "table" */ "../views/BaseTable.vue")
-            }, {
-                path: "/charts",
-                name: "basecharts",
-                meta: {
-                    title: '图表'
-                },
-                component: () => import ( /* webpackChunkName: "charts" */ "../views/BaseCharts.vue")
-            }, {
-                path: "/form",
-                name: "baseform",
-                meta: {
-                    title: '表单'
-                },
-                component: () => import ( /* webpackChunkName: "form" */ "../views/BaseForm.vue")
-            }, {
-                path: "/tabs",
-                name: "tabs",
-                meta: {
-                    title: 'tab标签'
-                },
-                component: () => import ( /* webpackChunkName: "tabs" */ "../views/Tabs.vue")
-            }, {
-                path: "/donate",
-                name: "donate",
-                meta: {
-                    title: '鼓励作者'
-                },
-                component: () => import ( /* webpackChunkName: "donate" */ "../views/Donate.vue")
-            }, {
-                path: "/permission",
-                name: "permission",
-                meta: {
-                    title: '权限管理',
-                    permission: true
-                },
-                component: () => import ( /* webpackChunkName: "permission" */ "../views/Permission.vue")
-            }, {
-                path: "/i18n",
-                name: "i18n",
-                meta: {
-                    title: '国际化语言'
-                },
-                component: () => import ( /* webpackChunkName: "i18n" */ "../views/I18n.vue")
-            }, {
-                path: "/upload",
-                name: "upload",
-                meta: {
-                    title: '上传插件'
-                },
-                component: () => import ( /* webpackChunkName: "upload" */ "../views/Upload.vue")
-            }, {
-                path: "/icon",
-                name: "icon",
-                meta: {
-                    title: '自定义图标'
-                },
-                component: () => import ( /* webpackChunkName: "icon" */ "../views/Icon.vue")
-            }, {
-                path: '/404',
-                name: '404',
-                meta: {
-                    title: '找不到页面'
-                },
-                component: () => import (/* webpackChunkName: "404" */ '../views/404.vue')
-            }, {
-                path: '/403',
-                name: '403',
-                meta: {
-                    title: '没有权限'
-                },
-                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')
-            }, {
-                path: '/markdown',
-                name: 'markdown',
-                meta: {
-                    title: 'markdown编辑器'
-                },
-                component: () => import (/* webpackChunkName: "markdown" */ '../views/Markdown.vue')
-            }
-        ]
-    }, {
-        path: "/login",
-        name: "Login",
-        meta: {
-            title: '登录'
-        },
-        component: () => import ( /* webpackChunkName: "login" */ "../views/Login.vue")
-    }
-];
-
-const router = createRouter({
-    history: createWebHashHistory(),
-    routes
-});
-
-router.beforeEach((to, from, next) => {
-    document.title = `${to.meta.title} | vue-manage-system`;
-    const role = localStorage.getItem('ms_username');
-    if (!role && to.path !== '/login') {
-        next('/login');
-    } else if (to.meta.permission) {
-        // 如果是管理员权限则可进入,这里只是简单的模拟管理员权限而已
-        role === 'admin'
-            ? next()
-            : next('/403');
-    } else {
-        next();
-    }
-});
-
+import {createRouter, createWebHashHistory, RouteRecordRaw} from "vue-router";
+import { usePermissStore } from '../store/permiss'
+import Home from "../views/Home.vue";
+
+const routes:RouteRecordRaw[] = [
+    {
+        path: '/',
+        redirect: '/dashboard'
+    }, {
+        path: "/",
+        name: "Home",
+        component: Home,
+        children: [
+            {
+                path: "/dashboard",
+                name: "dashboard",
+                meta: {
+                    title: '系统首页',
+                    permiss: '1'
+                },
+                component: () => import ( /* webpackChunkName: "dashboard" */ "../views/dashboard.vue")
+            }, {
+                path: "/table",
+                name: "basetable",
+                meta: {
+                    title: '表格',
+                    permiss: '2'
+                },
+                component: () => import ( /* webpackChunkName: "table" */ "../views/table.vue")
+            }, {
+                path: "/charts",
+                name: "basecharts",
+                meta: {
+                    title: '图表',
+                    permiss: '11'
+                },
+                component: () => import ( /* webpackChunkName: "charts" */ "../views/charts.vue")
+            }, {
+                path: "/form",
+                name: "baseform",
+                meta: {
+                    title: '表单',
+                    permiss: '5'
+                },
+                component: () => import ( /* webpackChunkName: "form" */ "../views/form.vue")
+            }, {
+                path: "/tabs",
+                name: "tabs",
+                meta: {
+                    title: 'tab标签',
+                    permiss: '3'
+                },
+                component: () => import ( /* webpackChunkName: "tabs" */ "../views/tabs.vue")
+            }, {
+                path: "/donate",
+                name: "donate",
+                meta: {
+                    title: '鼓励作者',
+                    permiss: '14'
+                },
+                component: () => import ( /* webpackChunkName: "donate" */ "../views/donate.vue")
+            }, {
+                path: "/permission",
+                name: "permission",
+                meta: {
+                    title: '权限管理',
+                    permiss: '13'
+                },
+                component: () => import ( /* webpackChunkName: "permission" */ "../views/permission.vue")
+            }, {
+                path: "/upload",
+                name: "upload",
+                meta: {
+                    title: '上传插件',
+                    permiss: '6'
+                },
+                component: () => import ( /* webpackChunkName: "upload" */ "../views/upload.vue")
+            }, {
+                path: "/icon",
+                name: "icon",
+                meta: {
+                    title: '自定义图标',
+                    permiss: '10'
+                },
+                component: () => import ( /* webpackChunkName: "icon" */ "../views/icon.vue")
+            },  {
+                path: '/user',
+                name: 'user',
+                meta: {
+                    title: '个人中心'
+                },
+                component: () => import (/* webpackChunkName: "user" */ '../views/user.vue')
+            }, {
+                path: '/editor',
+                name: 'editor',
+                meta: {
+                    title: '富文本编辑器',
+                    permiss: '8'
+                },
+                component: () => import (/* webpackChunkName: "editor" */ '../views/editor.vue')
+            }, {
+                path: '/markdown',
+                name: 'markdown',
+                meta: {
+                    title: 'markdown编辑器',
+                    permiss: '9'
+                },
+                component: () => import (/* webpackChunkName: "markdown" */ '../views/markdown.vue')
+            }
+        ]
+    }, {
+        path: "/login",
+        name: "Login",
+        meta: {
+            title: '登录'
+        },
+        component: () => import ( /* webpackChunkName: "login" */ "../views/login.vue")
+    }, {
+        path: '/403',
+        name: '403',
+        meta: {
+            title: '没有权限'
+        },
+        component: () => import (/* webpackChunkName: "403" */ '../views/403.vue')
+    },
+];
+
+const router = createRouter({
+    history: createWebHashHistory(),
+    routes
+});
+
+router.beforeEach((to, from, next) => {
+    document.title = `${to.meta.title} | vue-manage-system`;
+    const role = localStorage.getItem('ms_username');
+    const permiss = usePermissStore();
+    if (!role && to.path !== '/login') {
+        next('/login');
+    } else if (to.meta.permiss && !permiss.key.includes(to.meta.permiss)) {
+        // 如果没有权限,则进入403
+        next('/403');
+    } else {
+        next();
+    }
+});
+
 export default router;

+ 23 - 0
src/store/permiss.ts

@@ -0,0 +1,23 @@
+import { defineStore } from 'pinia';
+
+interface ObjectList {
+	[key: string]: string[];
+}
+
+export const usePermissStore = defineStore('permiss', {
+	state: () => {
+		const keys = localStorage.getItem('ms_keys');
+		return {
+			key: keys ? JSON.parse(keys) : <string[]>[],
+			defaultList: <ObjectList>{
+				admin: ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15', '16'],
+				user: ['1', '2', '3', '11', '13', '14', '15']
+			}
+		};
+	},
+	actions: {
+		handleSet(val: string[]) {
+			this.key = val;
+		}
+	}
+});

+ 0 - 17
src/store/sidebar.js

@@ -1,17 +0,0 @@
-import { defineStore } from 'pinia'
-
-export const useSidebarStore = defineStore('sidebar', {
-    state: () => {
-        return {
-            collapse: false
-        }
-    },
-    getters: {
-        
-    },
-    actions: {
-        handleCollapse() {
-            this.collapse = !this.collapse;
-        }
-    }
-})

+ 15 - 0
src/store/sidebar.ts

@@ -0,0 +1,15 @@
+import { defineStore } from 'pinia';
+
+export const useSidebarStore = defineStore('sidebar', {
+	state: () => {
+		return {
+			collapse: false
+		};
+	},
+	getters: {},
+	actions: {
+		handleCollapse() {
+			this.collapse = !this.collapse;
+		}
+	}
+});

+ 0 - 48
src/store/tags.js

@@ -1,48 +0,0 @@
-import { defineStore } from 'pinia'
-
-export const useTagsStore = defineStore('tags', {
-    state: () => {
-        return {
-            list: []
-        }
-    },
-    getters: {
-        show: (state) => {
-            return state.list.length > 0;
-        },
-        nameList: (state) => {
-            return state.list.map(item => item.name);
-        }
-    },
-    actions: {
-        delTagsItem(index) {
-            this.list.splice(index, 1);
-        },
-        setTagsItem(data) {
-            this.list.push(data)
-        },
-        clearTags() {
-            this.list = []
-        },
-        closeTagsOther(data) {
-            this.list = data;
-        },
-        closeCurrentTag(data) {
-            console.log(data)
-            for (let i = 0, len = this.list.length; i < len; i++) {
-                const item = this.list[i];
-                if (item.path === data.$route.fullPath) {
-                    if (i < len - 1) {
-                        data.$router.push(this.list[i + 1].path);
-                    } else if (i > 0) {
-                        data.$router.push(this.list[i - 1].path);
-                    } else {
-                        data.$router.push("/");
-                    }
-                    this.list.splice(i, 1);
-                    break;
-                }
-            }
-        },
-    }
-})

+ 53 - 0
src/store/tags.ts

@@ -0,0 +1,53 @@
+import { defineStore } from 'pinia';
+
+interface ListItem {
+	name: string;
+	path: string;
+	title: string;
+}
+
+export const useTagsStore = defineStore('tags', {
+	state: () => {
+		return {
+			list: <ListItem[]>[]
+		};
+	},
+	getters: {
+		show: state => {
+			return state.list.length > 0;
+		},
+		nameList: state => {
+			return state.list.map(item => item.name);
+		}
+	},
+	actions: {
+		delTagsItem(index: number) {
+			this.list.splice(index, 1);
+		},
+		setTagsItem(data: ListItem) {
+			this.list.push(data);
+		},
+		clearTags() {
+			this.list = [];
+		},
+		closeTagsOther(data: ListItem[]) {
+			this.list = data;
+		},
+		closeCurrentTag(data: any) {
+			for (let i = 0, len = this.list.length; i < len; i++) {
+				const item = this.list[i];
+				if (item.path === data.$route.fullPath) {
+					if (i < len - 1) {
+						data.$router.push(this.list[i + 1].path);
+					} else if (i > 0) {
+						data.$router.push(this.list[i - 1].path);
+					} else {
+						data.$router.push('/');
+					}
+					this.list.splice(i, 1);
+					break;
+				}
+			}
+		}
+	}
+});

+ 0 - 24
src/utils/i18n.js

@@ -1,24 +0,0 @@
-export default {
-    'zh-cn': {
-        i18n: {
-            breadcrumb: '国际化产品',
-            tips: '通过切换语言按钮,来改变当前内容的语言。',
-            btn: '切换英文',
-            title1: '常用用法',
-            p1: '要是你把你的秘密告诉了风,那就别怪风把它带给树。',
-            p2: '没有什么比信念更能支撑我们度过艰难的时光了。',
-            p3: '只要能把自己的事做好,并让自己快乐,你就领先于大多数人了。'
-        }
-    },
-    'en': {
-        i18n: {
-            breadcrumb: 'International Products',
-            tips: 'Click on the button to change the current language. ',
-            btn: 'Switch Chinese',
-            title1: 'Common usage',
-            p1: "If you reveal your secrets to the wind you should not blame the wind for  revealing them to the trees.",
-            p2: "Nothing can help us endure dark times better than our faith. ",
-            p3: "If you can do what you do best and be happy, you're further along in life  than most people."
-        }
-    }
-}

+ 7 - 10
src/utils/request.js → src/utils/request.ts

@@ -1,31 +1,28 @@
-import axios from 'axios';
+import axios, {AxiosInstance, AxiosError, AxiosResponse, AxiosRequestConfig} from 'axios';
 
-const service = axios.create({
-    // process.env.NODE_ENV === 'development' 来判断是否开发环境
-    // easy-mock服务挂了,暂时不使用了
-    // baseURL: 'https://www.easy-mock.com/mock/592501a391470c0ac1fab128',
+const service:AxiosInstance = axios.create({
     timeout: 5000
 });
 
 service.interceptors.request.use(
-    config => {
+    (config: AxiosRequestConfig) => {
         return config;
     },
-    error => {
+    (error: AxiosError) => {
         console.log(error);
         return Promise.reject();
     }
 );
 
 service.interceptors.response.use(
-    response => {
+    (response: AxiosResponse) => {
         if (response.status === 200) {
-            return response.data;
+            return response;
         } else {
             Promise.reject();
         }
     },
-    error => {
+    (error: AxiosError) => {
         console.log(error);
         return Promise.reject();
     }

+ 34 - 42
src/views/403.vue

@@ -1,62 +1,54 @@
 <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 {
-    name: "404",
-    setup() {
-        const router = useRouter();
-        const goBack = () => {
-            router.go(-1);
-        };
-        return {
-            goBack,
-        };
-    },
+<script setup lang="ts" name="403">
+import { useRouter } from 'vue-router';
+
+const router = useRouter();
+const goBack = () => {
+	router.go(-2);
 };
 </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;
+	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;
+	line-height: 1;
+	font-size: 250px;
+	font-weight: bolder;
+	color: #f02d2d;
 }
 .error-code span {
-    color: #00a854;
+	color: #00a854;
 }
 .error-desc {
-    font-size: 30px;
-    color: #777;
+	font-size: 30px;
+	color: #777;
 }
 .error-handle {
-    margin-top: 30px;
-    padding-bottom: 200px;
+	margin-top: 30px;
+	padding-bottom: 200px;
 }
 .error-btn {
-    margin-left: 100px;
+	margin-left: 100px;
 }
 </style>

+ 34 - 42
src/views/404.vue

@@ -1,62 +1,54 @@
 <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 {
-    name: "404",
-    setup() {
-        const router = useRouter();
-        const goBack = () => {
-            router.go(-1);
-        };
-        return {
-            goBack,
-        };
-    },
+<script setup lang="ts" name="404">
+import { useRouter } from 'vue-router';
+
+const router = useRouter();
+const goBack = () => {
+	router.go(-1);
 };
 </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;
+	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;
+	line-height: 1;
+	font-size: 250px;
+	font-weight: bolder;
+	color: #2d8cf0;
 }
 .error-code span {
-    color: #00a854;
+	color: #00a854;
 }
 .error-desc {
-    font-size: 30px;
-    color: #777;
+	font-size: 30px;
+	color: #777;
 }
 .error-handle {
-    margin-top: 30px;
-    padding-bottom: 200px;
+	margin-top: 30px;
+	padding-bottom: 200px;
 }
 .error-btn {
-    margin-left: 100px;
+	margin-left: 100px;
 }
 </style>

+ 0 - 158
src/views/BaseCharts.vue

@@ -1,158 +0,0 @@
-<template>
-    <div>
-        <div class="crumbs">
-            <el-breadcrumb separator="/">
-                <el-breadcrumb-item>
-                    <i class="el-icon-pie-chart"></i> schart图表
-                </el-breadcrumb-item>
-            </el-breadcrumb>
-        </div>
-        <div class="container">
-            <div class="plugins-tips">
-                vue-schart:vue.js封装sChart.js的图表组件。
-                访问地址:
-                <a href="https://github.com/lin-xin/vue-schart" target="_blank">vue-schart</a>
-            </div>
-            <div class="schart-box">
-                <div class="content-title">柱状图</div>
-                <schart class="schart" canvasId="bar" :options="options1"></schart>
-            </div>
-            <div class="schart-box">
-                <div class="content-title">折线图</div>
-                <schart class="schart" canvasId="line" :options="options2"></schart>
-            </div>
-            <div class="schart-box">
-                <div class="content-title">饼状图</div>
-                <schart class="schart" canvasId="pie" :options="options3"></schart>
-            </div>
-            <div class="schart-box">
-                <div class="content-title">环形图</div>
-                <schart class="schart" canvasId="ring" :options="options4"></schart>
-            </div>
-        </div>
-    </div>
-</template>
-
-<script>
-import Schart from "vue-schart";
-export default {
-    name: "basecharts",
-    components: {
-        Schart,
-    },
-    setup() {
-        const 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],
-                },
-            ],
-        };
-        const options2 = {
-            type: "line",
-            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],
-                },
-                {
-                    label: "食品",
-                    data: [114, 138, 200, 235, 190],
-                },
-            ],
-        };
-        const options3 = {
-            type: "pie",
-            title: {
-                text: "服装品类销售饼状图",
-            },
-            legend: {
-                position: "left",
-            },
-            bgColor: "#fbfbfb",
-            labels: [
-                "T恤",
-                "牛仔裤",
-                "连衣裙",
-                "毛衣",
-                "七分裤",
-                "短裙",
-                "羽绒服",
-            ],
-            datasets: [
-                {
-                    data: [334, 278, 190, 235, 260, 200, 141],
-                },
-            ],
-        };
-        const options4 = {
-            type: "ring",
-            title: {
-                text: "环形三等分",
-            },
-            showValue: false,
-            legend: {
-                position: "bottom",
-                bottom: 40,
-            },
-            bgColor: "#fbfbfb",
-            labels: ["vue", "react", "angular"],
-            datasets: [
-                {
-                    data: [500, 500, 500],
-                },
-            ],
-        };
-        return {
-            options1,
-            options2,
-            options3,
-            options4,
-        };
-    },
-};
-</script>
-
-<style scoped>
-.schart-box {
-    display: inline-block;
-    margin: 20px;
-}
-.schart {
-    width: 600px;
-    height: 400px;
-}
-.content-title {
-    clear: both;
-    font-weight: 400;
-    line-height: 50px;
-    margin: 10px 0;
-    font-size: 22px;
-    color: #1f2f3d;
-}
-</style>

+ 0 - 174
src/views/BaseForm.vue

@@ -1,174 +0,0 @@
-<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="form-box">
-                <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="选择器" 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>
-                            <el-option key="imoo" label="imoo" value="imoo"></el-option>
-                        </el-select>
-                    </el-form-item>
-                    <el-form-item label="日期时间">
-                        <el-col :span="11">
-                            <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-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="城市级联" prop="options">
-                        <el-cascader :options="options" v-model="form.options"></el-cascader>
-                    </el-form-item>
-                    <el-form-item label="选择开关" prop="delivery">
-                        <el-switch v-model="form.delivery"></el-switch>
-                    </el-form-item>
-                    <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="单选框" 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="文本框" 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 @click="onReset">重置表单</el-button>
-                    </el-form-item>
-                </el-form>
-            </div>
-        </div>
-    </div>
-</template>
-
-<script>
-import { reactive, ref } from "vue";
-import { ElMessage } from "element-plus";
-export default {
-    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" },
-            ],
-        };
-        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,
-        };
-    },
-};
-</script>

+ 0 - 196
src/views/BaseTable.vue

@@ -1,196 +0,0 @@
-<template>
-    <div>
-        <div class="crumbs">
-            <el-breadcrumb separator="/">
-                <el-breadcrumb-item>
-                    <i class="el-icon-lx-cascades"></i> 基础表格
-                </el-breadcrumb-item>
-            </el-breadcrumb>
-        </div>
-        <div class="container">
-            <div class="handle-box">
-                <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>
-                </el-select>
-                <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">
-                <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="账户余额">
-                    <template #default="scope">¥{{ scope.row.money }}</template>
-                </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>
-                    </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="
-                                scope.row.state === '成功'
-                                    ? 'success'
-                                    : scope.row.state === '失败'
-                                    ? 'danger'
-                                    : ''
-                            ">{{ 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>
-                    </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>
-            </div>
-        </div>
-
-        <!-- 编辑弹出框 -->
-        <el-dialog title="编辑" v-model="editVisible" width="30%">
-            <el-form label-width="70px">
-                <el-form-item label="用户名">
-                    <el-input v-model="form.name"></el-input>
-                </el-form-item>
-                <el-form-item label="地址">
-                    <el-input v-model="form.address"></el-input>
-                </el-form-item>
-            </el-form>
-            <template #footer>
-                <span class="dialog-footer">
-                    <el-button @click="editVisible = false">取 消</el-button>
-                    <el-button type="primary" @click="saveEdit">确 定</el-button>
-                </span>
-            </template>
-        </el-dialog>
-    </div>
-</template>
-
-<script>
-import { ref, reactive } from "vue";
-import { ElMessage, ElMessageBox } from "element-plus";
-import { fetchData } from "../api/index";
-
-export default {
-    name: "basetable",
-    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;
-            });
-        };
-        getData();
-
-        // 查询操作
-        const handleSearch = () => {
-            query.pageIndex = 1;
-            getData();
-        };
-        // 分页导航
-        const handlePageChange = (val) => {
-            query.pageIndex = val;
-            getData();
-        };
-
-        // 删除操作
-        const handleDelete = (index) => {
-            // 二次确认删除
-            ElMessageBox.confirm("确定要删除吗?", "提示", {
-                type: "warning",
-            })
-                .then(() => {
-                    ElMessage.success("删除成功");
-                    tableData.value.splice(index, 1);
-                })
-                .catch(() => {});
-        };
-
-        // 表格编辑时弹窗和保存
-        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>
-
-<style scoped>
-.handle-box {
-    margin-bottom: 20px;
-}
-
-.handle-select {
-    width: 120px;
-}
-
-.handle-input {
-    width: 300px;
-    display: inline-block;
-}
-.table {
-    width: 100%;
-    font-size: 14px;
-}
-.red {
-    color: #ff0000;
-}
-.mr10 {
-    margin-right: 10px;
-}
-.table-td-thumb {
-    display: block;
-    margin: auto;
-    width: 40px;
-    height: 40px;
-}
-</style>

+ 228 - 278
src/views/Dashboard.vue

@@ -1,348 +1,298 @@
 <template>
-    <div>
-        <el-row :gutter="20">
-            <el-col :span="8">
-                <el-card shadow="hover" class="mgb20" style="height:252px;">
-                    <div class="user-info">
-                        <img src="../assets/img/img.jpg" class="user-avator" alt />
-                        <div class="user-info-cont">
-                            <div class="user-info-name">{{ name }}</div>
-                            <div>{{ role }}</div>
-                        </div>
-                    </div>
-                    <div class="user-info-list">
-                        上次登录时间:
-                        <span>2019-11-01</span>
-                    </div>
-                    <div class="user-info-list">
-                        上次登录地点:
-                        <span>东莞</span>
-                    </div>
-                </el-card>
-                <el-card shadow="hover" style="height:252px;">
-                    <template #header>
-                        <div class="clearfix">
-                            <span>语言详情</span>
-                        </div>
-                    </template>
-                    Vue
-                    <el-progress :percentage="71.3" color="#42b983"></el-progress>JavaScript
-                    <el-progress :percentage="24.1" color="#f1e05a"></el-progress>CSS
-                    <el-progress :percentage="13.7"></el-progress>HTML
-                    <el-progress :percentage="5.9" color="#f56c6c"></el-progress>
-                </el-card>
-            </el-col>
-            <el-col :span="16">
-                <el-row :gutter="20" class="mgb20">
-                    <el-col :span="8">
-                        <el-card shadow="hover" :body-style="{ padding: '0px' }">
-                            <div class="grid-content grid-con-1">
-                                <i class="el-icon-user-solid grid-con-icon"></i>
-                                <div class="grid-cont-right">
-                                    <div class="grid-num">1234</div>
-                                    <div>用户访问量</div>
-                                </div>
-                            </div>
-                        </el-card>
-                    </el-col>
-                    <el-col :span="8">
-                        <el-card shadow="hover" :body-style="{ padding: '0px' }">
-                            <div class="grid-content grid-con-2">
-                                <i class="el-icon-message-solid grid-con-icon"></i>
-                                <div class="grid-cont-right">
-                                    <div class="grid-num">321</div>
-                                    <div>系统消息</div>
-                                </div>
-                            </div>
-                        </el-card>
-                    </el-col>
-                    <el-col :span="8">
-                        <el-card shadow="hover" :body-style="{ padding: '0px' }">
-                            <div class="grid-content grid-con-3">
-                                <i class="el-icon-s-goods grid-con-icon"></i>
-                                <div class="grid-cont-right">
-                                    <div class="grid-num">5000</div>
-                                    <div>数量</div>
-                                </div>
-                            </div>
-                        </el-card>
-                    </el-col>
-                </el-row>
-                <el-card shadow="hover" style="height:403px;">
-                    <template #header>
-                        <div class="clearfix">
-                            <span>待办事项</span>
-                            <el-button style="float: right; padding: 3px 0" type="text">添加</el-button>
-                        </div>
-                    </template>
+	<div>
+		<el-row :gutter="20">
+			<el-col :span="8">
+				<el-card shadow="hover" class="mgb20" style="height: 252px">
+					<div class="user-info">
+						<el-avatar :size="120" :src="imgurl" />
+						<div class="user-info-cont">
+							<div class="user-info-name">{{ name }}</div>
+							<div>{{ role }}</div>
+						</div>
+					</div>
+					<div class="user-info-list">
+						上次登录时间:
+						<span>2022-10-01</span>
+					</div>
+					<div class="user-info-list">
+						上次登录地点:
+						<span>东莞</span>
+					</div>
+				</el-card>
+				<el-card shadow="hover" style="height: 252px">
+					<template #header>
+						<div class="clearfix">
+							<span>语言详情</span>
+						</div>
+					</template>
+					Vue
+					<el-progress :percentage="71.3" color="#42b983"></el-progress>JavaScript
+					<el-progress :percentage="24.1" color="#f1e05a"></el-progress>CSS
+					<el-progress :percentage="13.7"></el-progress>HTML
+					<el-progress :percentage="5.9" color="#f56c6c"></el-progress>
+				</el-card>
+			</el-col>
+			<el-col :span="16">
+				<el-row :gutter="20" class="mgb20">
+					<el-col :span="8">
+						<el-card shadow="hover" :body-style="{ padding: '0px' }">
+							<div class="grid-content grid-con-1">
+								<el-icon class="grid-con-icon"><User /></el-icon>
+								<div class="grid-cont-right">
+									<div class="grid-num">1234</div>
+									<div>用户访问量</div>
+								</div>
+							</div>
+						</el-card>
+					</el-col>
+					<el-col :span="8">
+						<el-card shadow="hover" :body-style="{ padding: '0px' }">
+							<div class="grid-content grid-con-2">
+								<el-icon class="grid-con-icon"><ChatDotRound /></el-icon>
+								<div class="grid-cont-right">
+									<div class="grid-num">321</div>
+									<div>系统消息</div>
+								</div>
+							</div>
+						</el-card>
+					</el-col>
+					<el-col :span="8">
+						<el-card shadow="hover" :body-style="{ padding: '0px' }">
+							<div class="grid-content grid-con-3">
+								<el-icon class="grid-con-icon"><Goods /></el-icon>
+								<div class="grid-cont-right">
+									<div class="grid-num">5000</div>
+									<div>商品数量</div>
+								</div>
+							</div>
+						</el-card>
+					</el-col>
+				</el-row>
+				<el-card shadow="hover" style="height: 403px">
+					<template #header>
+						<div class="clearfix">
+							<span>待办事项</span>
+							<el-button style="float: right; padding: 3px 0" text>添加</el-button>
+						</div>
+					</template>
 
-                    <el-table :show-header="false" :data="todoList" style="width:100%;">
-                        <el-table-column width="40">
-                            <template #default="scope">
-                                <el-checkbox v-model="scope.row.status"></el-checkbox>
-                            </template>
-                        </el-table-column>
-                        <el-table-column>
-                            <template #default="scope">
-                                <div class="todo-item" :class="{
-                                        'todo-item-del': scope.row.status,
-                                    }">{{ scope.row.title }}</div>
-                            </template>
-                        </el-table-column>
-                        <el-table-column width="60">
-                            <template>
-                                <i class="el-icon-edit"></i>
-                                <i class="el-icon-delete"></i>
-                            </template>
-                        </el-table-column>
-                    </el-table>
-                </el-card>
-            </el-col>
-        </el-row>
-        <el-row :gutter="20">
-            <el-col :span="12">
-                <el-card shadow="hover">
-                    <schart ref="bar" class="schart" canvasId="bar" :options="options"></schart>
-                </el-card>
-            </el-col>
-            <el-col :span="12">
-                <el-card shadow="hover">
-                    <schart ref="line" class="schart" canvasId="line" :options="options2"></schart>
-                </el-card>
-            </el-col>
-        </el-row>
-    </div>
+					<el-table :show-header="false" :data="todoList" style="width: 100%">
+						<el-table-column width="40">
+							<template #default="scope">
+								<el-checkbox v-model="scope.row.status"></el-checkbox>
+							</template>
+						</el-table-column>
+						<el-table-column>
+							<template #default="scope">
+								<div
+									class="todo-item"
+									:class="{
+										'todo-item-del': scope.row.status
+									}"
+								>
+									{{ scope.row.title }}
+								</div>
+							</template>
+						</el-table-column>
+					</el-table>
+				</el-card>
+			</el-col>
+		</el-row>
+		<el-row :gutter="20">
+			<el-col :span="12">
+				<el-card shadow="hover">
+					<schart ref="bar" class="schart" canvasId="bar" :options="options"></schart>
+				</el-card>
+			</el-col>
+			<el-col :span="12">
+				<el-card shadow="hover">
+					<schart ref="line" class="schart" canvasId="line" :options="options2"></schart>
+				</el-card>
+			</el-col>
+		</el-row>
+	</div>
 </template>
 
-<script>
-import Schart from "vue-schart";
-import { reactive } from "vue";
-export default {
-    name: "dashboard",
-    components: { Schart },
-    setup() {
-        const name = localStorage.getItem("ms_username");
-        const role = name === "admin" ? "超级管理员" : "普通用户";
+<script setup lang="ts" name="dashboard">
+import Schart from 'vue-schart';
+import { reactive } from 'vue';
+import imgurl from '../assets/img/img.jpg';
 
-        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: [
-                {
-                    label: "家电",
-                    data: [234, 278, 270, 190, 230],
-                },
-                {
-                    label: "百货",
-                    data: [164, 178, 190, 135, 160],
-                },
-                {
-                    label: "食品",
-                    data: [144, 198, 150, 235, 120],
-                },
-            ],
-        };
-        const 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],
-                },
-            ],
-        };
-        const todoList = reactive([
-            {
-                title: "今天要修复100个bug",
-                status: false,
-            },
-            {
-                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,
-            },
-        ]);
+const name = localStorage.getItem('ms_username');
+const role: string = name === 'admin' ? '超级管理员' : '普通用户';
 
-        return {
-            name,
-            data,
-            options,
-            options2,
-            todoList,
-            role,
-        };
-    },
+const 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 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]
+		}
+	]
+};
+const todoList = reactive([
+	{
+		title: '今天要修复100个bug',
+		status: false
+	},
+	{
+		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
+	}
+]);
 </script>
 
 <style scoped>
 .el-row {
-    margin-bottom: 20px;
+	margin-bottom: 20px;
 }
 
 .grid-content {
-    display: flex;
-    align-items: center;
-    height: 100px;
+	display: flex;
+	align-items: center;
+	height: 100px;
 }
 
 .grid-cont-right {
-    flex: 1;
-    text-align: center;
-    font-size: 14px;
-    color: #999;
+	flex: 1;
+	text-align: center;
+	font-size: 14px;
+	color: #999;
 }
 
 .grid-num {
-    font-size: 30px;
-    font-weight: bold;
+	font-size: 30px;
+	font-weight: bold;
 }
 
 .grid-con-icon {
-    font-size: 50px;
-    width: 100px;
-    height: 100px;
-    text-align: center;
-    line-height: 100px;
-    color: #fff;
+	font-size: 50px;
+	width: 100px;
+	height: 100px;
+	text-align: center;
+	line-height: 100px;
+	color: #fff;
 }
 
 .grid-con-1 .grid-con-icon {
-    background: rgb(45, 140, 240);
+	background: rgb(45, 140, 240);
 }
 
 .grid-con-1 .grid-num {
-    color: rgb(45, 140, 240);
+	color: rgb(45, 140, 240);
 }
 
 .grid-con-2 .grid-con-icon {
-    background: rgb(100, 213, 114);
+	background: rgb(100, 213, 114);
 }
 
 .grid-con-2 .grid-num {
-    color: rgb(45, 140, 240);
+	color: rgb(100, 213, 114);
 }
 
 .grid-con-3 .grid-con-icon {
-    background: rgb(242, 94, 67);
+	background: rgb(242, 94, 67);
 }
 
 .grid-con-3 .grid-num {
-    color: rgb(242, 94, 67);
+	color: rgb(242, 94, 67);
 }
 
 .user-info {
-    display: flex;
-    align-items: center;
-    padding-bottom: 20px;
-    border-bottom: 2px solid #ccc;
-    margin-bottom: 20px;
-}
-
-.user-avator {
-    width: 120px;
-    height: 120px;
-    border-radius: 50%;
+	display: flex;
+	align-items: center;
+	padding-bottom: 20px;
+	border-bottom: 2px solid #ccc;
+	margin-bottom: 20px;
 }
 
 .user-info-cont {
-    padding-left: 50px;
-    flex: 1;
-    font-size: 14px;
-    color: #999;
+	padding-left: 50px;
+	flex: 1;
+	font-size: 14px;
+	color: #999;
 }
 
 .user-info-cont div:first-child {
-    font-size: 30px;
-    color: #222;
+	font-size: 30px;
+	color: #222;
 }
 
 .user-info-list {
-    font-size: 14px;
-    color: #999;
-    line-height: 25px;
+	font-size: 14px;
+	color: #999;
+	line-height: 25px;
 }
 
 .user-info-list span {
-    margin-left: 70px;
+	margin-left: 70px;
 }
 
 .mgb20 {
-    margin-bottom: 20px;
+	margin-bottom: 20px;
 }
 
 .todo-item {
-    font-size: 14px;
+	font-size: 14px;
 }
 
 .todo-item-del {
-    text-decoration: line-through;
-    color: #999;
+	text-decoration: line-through;
+	color: #999;
 }
 
 .schart {
-    width: 100%;
-    height: 300px;
+	width: 100%;
+	height: 300px;
 }
 </style>

+ 10 - 22
src/views/Donate.vue

@@ -1,26 +1,14 @@
 <template>
-    <div>
-        <div class="crumbs">
-            <el-breadcrumb separator="/">
-                <el-breadcrumb-item>
-                    <i class="el-icon-lx-redpacket_fill"></i> 支持作者
-                </el-breadcrumb-item>
-            </el-breadcrumb>
-        </div>
-        <div class="container">
-            <div class="plugins-tips">如果该框架对你有帮助,那就请作者喝杯饮料吧!加微信号linxin_20探讨问题。</div>
-            <div>
-                <img src="https://lin-xin.gitee.io/images/weixin.jpg" />
-            </div>
-        </div>
-    </div>
+	<div class="container">
+		<div class="plugins-tips">
+			如果该框架对你有帮助,那就请作者喝杯饮料吧!<el-icon><ColdDrink /></el-icon> 加微信号linxin_20探讨问题。
+		</div>
+		<div>
+			<img src="https://lin-xin.gitee.io/images/weixin.jpg" />
+		</div>
+	</div>
 </template>
 
-<script>
-export default {
-    name: "donate"
-};
-</script>
+<script setup lang="ts" name="donate"></script>
 
-<style>
-</style>
+<style></style>

+ 31 - 52
src/views/Editor.vue

@@ -1,58 +1,37 @@
 <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>
+	<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>
 </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 setup lang="ts" name="editor">
+import WangEditor from 'wangeditor';
+import { ref, reactive, onMounted, onBeforeUnmount } from 'vue';
+
+const editor = ref(null);
+const content = reactive({
+	html: '',
+	text: ''
+});
+let instance: any;
+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);
 };
 </script>
 
-<style>
-</style>
+<style></style>

+ 23 - 38
src/views/Home.vue

@@ -1,41 +1,26 @@
 <template>
-    <div class="about">
-        <v-header />
-        <v-sidebar />
-        <div class="content-box" :class="{ 'content-collapse': sidebar.collapse }">
-            <v-tags></v-tags>
-            <div class="content">
-                <router-view v-slot="{ Component }">
-                    <transition name="move" mode="out-in">
-                        <keep-alive :include="tags.nameList">
-                            <component :is="Component" />
-                        </keep-alive>
-                    </transition>
-                </router-view>
-                <!-- <el-backtop target=".content"></el-backtop> -->
-            </div>
-        </div>
-    </div>
+	<v-header />
+	<v-sidebar />
+	<div class="content-box" :class="{ 'content-collapse': sidebar.collapse }">
+		<v-tags></v-tags>
+		<div class="content">
+			<router-view v-slot="{ Component }">
+				<transition name="move" mode="out-in">
+					<keep-alive :include="tags.nameList">
+						<component :is="Component"></component>
+					</keep-alive>
+				</transition>
+			</router-view>
+		</div>
+	</div>
 </template>
-<script>
-import { useSidebarStore } from '../store/sidebar'
-import { useTagsStore } from '../store/tags'
-import vHeader from "../components/Header.vue";
-import vSidebar from "../components/Sidebar.vue";
-import vTags from "../components/Tags.vue";
-export default {
-    components: {
-        vHeader,
-        vSidebar,
-        vTags,
-    },
-    setup() {
-        const sidebar = useSidebarStore();
-        const tags = useTagsStore();
-        return {
-            tags,
-            sidebar,
-        };
-    },
-};
+<script setup lang="ts">
+import { useSidebarStore } from '../store/sidebar';
+import { useTagsStore } from '../store/tags';
+import vHeader from '../components/header.vue';
+import vSidebar from '../components/sidebar.vue';
+import vTags from '../components/tags.vue';
+
+const sidebar = useSidebarStore();
+const tags = useTagsStore();
 </script>

+ 0 - 40
src/views/I18n.vue

@@ -1,40 +0,0 @@
-<template>
-    <section class="main">
-        <div class="crumbs">
-            <el-breadcrumb separator="/">
-                <el-breadcrumb-item>
-                    <i class="el-icon-lx-global"></i>
-                    {{$t('i18n.breadcrumb')}}
-                </el-breadcrumb-item>
-            </el-breadcrumb>
-        </div>
-        <div class="container">
-            <span>{{$t('i18n.tips')}}</span>
-            <el-button
-                type="primary"
-                @click="$i18n.locale = $i18n.locale === 'zh-cn'?'en':'zh-cn';"
-            >{{$t('i18n.btn')}}</el-button>
-            <div class="list">
-                <h2>{{$t('i18n.title1')}}</h2>
-                <p>{{$t('i18n.p1')}}</p>
-                <p>{{$t('i18n.p2')}}</p>
-                <p>{{$t('i18n.p3')}}</p>
-            </div>
-        </div>
-    </section>
-</template>
-
-<script>
-export default {
-    name: "i18n"
-};
-</script>
-
-<style scoped>
-.list {
-    padding: 30px 0;
-}
-.list p {
-    margin-bottom: 20px;
-}
-</style>

+ 188 - 205
src/views/Icon.vue

@@ -1,229 +1,212 @@
 <template>
-    <div>
-        <div class="crumbs">
-            <el-breadcrumb separator="/">
-                <el-breadcrumb-item>
-                    <i class="el-icon-lx-emoji"></i> 自定义图标
-                </el-breadcrumb-item>
-            </el-breadcrumb>
-        </div>
-        <div class="container">
-            <h2>使用方法</h2>
-            <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>
-            </p>
-            <p class="example-p">
-                <i class="el-icon-lx-weibo" style="font-size: 30px;color:#fd5656"></i>
-                <span>&lt;i class=&quot;el-icon-lx-weibo&quot;&gt;&lt;/i&gt;</span>
-            </p>
-            <p class="example-p">
-                <i class="el-icon-lx-emojifill" style="font-size: 30px;color: #ffc300"></i>
-                <span>&lt;i class=&quot;el-icon-lx-emojifill&quot;&gt;&lt;/i&gt;</span>
-            </p>
-            <br />
-            <h2>图标</h2>
-            <div class="search-box">
-                <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">
-                    <div class="icon-li-content">
-                        <i :class="`el-icon-lx-${item}`"></i>
-                        <span>{{item}}</span>
-                    </div>
-                </li>
-            </ul>
-        </div>
-    </div>
+	<div class="container">
+		<h2>使用方法</h2>
+		<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>
+		</p>
+		<p class="example-p">
+			<i class="el-icon-lx-weibo" style="font-size: 30px; color: #fd5656"></i>
+			<span>&lt;i class=&quot;el-icon-lx-weibo&quot;&gt;&lt;/i&gt;</span>
+		</p>
+		<p class="example-p">
+			<i class="el-icon-lx-emojifill" style="font-size: 30px; color: #ffc300"></i>
+			<span>&lt;i class=&quot;el-icon-lx-emojifill&quot;&gt;&lt;/i&gt;</span>
+		</p>
+		<br />
+		<h2>图标</h2>
+		<div class="search-box">
+			<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">
+				<div class="icon-li-content">
+					<i :class="`el-icon-lx-${item}`"></i>
+					<span>{{ item }}</span>
+				</div>
+			</li>
+		</ul>
+	</div>
 </template>
 
-<script>
-import { computed, ref } from "vue";
-export default {
-    name: "icon",
-    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;
-            });
-        });
+<script setup lang="ts" name="icon">
+import { computed, ref } from 'vue';
 
-        return {
-            iconList,
-            keyword,
-            list,
-        };
-    },
-};
+const iconList: Array<string> = [
+	'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;
+	});
+});
 </script>
 
 <style scoped>
 .example-p {
-    height: 45px;
-    display: flex;
-    align-items: center;
+	height: 45px;
+	display: flex;
+	align-items: center;
 }
 .search-box {
-    text-align: center;
-    margin-top: 10px;
+	text-align: center;
+	margin-top: 10px;
 }
 .search {
-    width: 300px;
+	width: 300px;
 }
 ul,
 li {
-    list-style: none;
+	list-style: none;
 }
 .icon-li {
-    display: inline-block;
-    padding: 10px;
-    width: 120px;
-    height: 120px;
+	display: inline-block;
+	padding: 10px;
+	width: 120px;
+	height: 120px;
 }
 .icon-li-content {
-    display: flex;
-    height: 100%;
-    flex-direction: column;
-    align-items: center;
-    justify-content: center;
-    cursor: pointer;
+	display: flex;
+	height: 100%;
+	flex-direction: column;
+	align-items: center;
+	justify-content: center;
+	cursor: pointer;
 }
 .icon-li-content i {
-    font-size: 36px;
-    color: #606266;
+	font-size: 36px;
+	color: #606266;
 }
 .icon-li-content span {
-    margin-top: 10px;
-    color: #787878;
+	margin-top: 10px;
+	color: #787878;
 }
-</style>
+</style>

+ 105 - 101
src/views/Login.vue

@@ -1,125 +1,129 @@
 <template>
-    <div class="login-wrap">
-        <div class="ms-login">
-            <div class="ms-title">后台管理系统</div>
-            <el-form :model="param" :rules="rules" ref="login" label-width="0px" class="ms-content">
-                <el-form-item prop="username">
-                    <el-input v-model="param.username" placeholder="username">
-                        <template #prepend>
-                            <el-button icon="el-icon-user"></el-button>
-                        </template>
-                    </el-input>
-                </el-form-item>
-                <el-form-item prop="password">
-                    <el-input type="password" placeholder="password" v-model="param.password"
-                        @keyup.enter="submitForm()">
-                        <template #prepend>
-                            <el-button icon="el-icon-lock"></el-button>
-                        </template>
-                    </el-input>
-                </el-form-item>
-                <div class="login-btn">
-                    <el-button type="primary" @click="submitForm()">登录</el-button>
-                </div>
-                <p class="login-tips">Tips : 用户名和密码随便填。</p>
-            </el-form>
-        </div>
-    </div>
+	<div class="login-wrap">
+		<div class="ms-login">
+			<div class="ms-title">后台管理系统</div>
+			<el-form :model="param" :rules="rules" ref="login" label-width="0px" class="ms-content">
+				<el-form-item prop="username">
+					<el-input v-model="param.username" placeholder="username">
+						<template #prepend>
+							<el-button :icon="User"></el-button>
+						</template>
+					</el-input>
+				</el-form-item>
+				<el-form-item prop="password">
+					<el-input
+						type="password"
+						placeholder="password"
+						v-model="param.password"
+						@keyup.enter="submitForm(login)"
+					>
+						<template #prepend>
+							<el-button :icon="Lock"></el-button>
+						</template>
+					</el-input>
+				</el-form-item>
+				<div class="login-btn">
+					<el-button type="primary" @click="submitForm(login)">登录</el-button>
+				</div>
+				<p class="login-tips">Tips : 用户名和密码随便填。</p>
+			</el-form>
+		</div>
+	</div>
 </template>
 
-<script>
-import { ref, reactive } from "vue";
-import { useTagsStore } from '../store/tags'
-import { useRouter } from "vue-router";
-import { ElMessage } from "element-plus";
+<script setup lang="ts">
+import { ref, reactive } from 'vue';
+import { useTagsStore } from '../store/tags';
+import { usePermissStore } from '../store/permiss';
+import { useRouter } from 'vue-router';
+import { ElMessage } from 'element-plus';
+import type { FormInstance, FormRules } from 'element-plus';
+import { Lock, User } from '@element-plus/icons-vue';
 
-export default {
-    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" },
-            ],
-        };
-        const login = ref(null);
-        const submitForm = () => {
-            login.value.validate((valid) => {
-                if (valid) {
-                    ElMessage.success("登录成功");
-                    localStorage.setItem("ms_username", param.username);
-                    router.push("/");
-                } else {
-                    ElMessage.error("登录成功");
-                    return false;
-                }
-            });
-        };
+interface LoginInfo {
+	username: string;
+	password: string;
+}
 
-        const tags = useTagsStore();
-        tags.clearTags();
+const router = useRouter();
+const param = reactive<LoginInfo>({
+	username: 'admin',
+	password: '123123'
+});
 
-        return {
-            param,
-            rules,
-            login,
-            submitForm,
-        };
-    },
+const rules: FormRules = {
+	username: [
+		{
+			required: true,
+			message: '请输入用户名',
+			trigger: 'blur'
+		}
+	],
+	password: [{ required: true, message: '请输入密码', trigger: 'blur' }]
 };
+const permiss = usePermissStore();
+const login = ref<FormInstance>();
+const submitForm = (formEl: FormInstance | undefined) => {
+	if (!formEl) return;
+	formEl.validate((valid: boolean) => {
+		if (valid) {
+			ElMessage.success('登录成功');
+			localStorage.setItem('ms_username', param.username);
+			const keys = permiss.defaultList[param.username == 'admin' ? 'admin' : 'user'];
+			permiss.handleSet(keys);
+			localStorage.setItem('ms_keys', JSON.stringify(keys));
+			router.push('/');
+		} else {
+			ElMessage.error('登录成功');
+			return false;
+		}
+	});
+};
+
+const tags = useTagsStore();
+tags.clearTags();
 </script>
 
 <style scoped>
 .login-wrap {
-    position: relative;
-    width: 100%;
-    height: 100%;
-    background-image: url(../assets/img/login-bg.jpg);
-    background-size: 100%;
+	position: relative;
+	width: 100%;
+	height: 100%;
+	background-image: url(../assets/img/login-bg.jpg);
+	background-size: 100%;
 }
 .ms-title {
-    width: 100%;
-    line-height: 50px;
-    text-align: center;
-    font-size: 20px;
-    color: #fff;
-    border-bottom: 1px solid #ddd;
+	width: 100%;
+	line-height: 50px;
+	text-align: center;
+	font-size: 20px;
+	color: #fff;
+	border-bottom: 1px solid #ddd;
 }
 .ms-login {
-    position: absolute;
-    left: 50%;
-    top: 50%;
-    width: 350px;
-    margin: -190px 0 0 -175px;
-    border-radius: 5px;
-    background: rgba(255, 255, 255, 0.3);
-    overflow: hidden;
+	position: absolute;
+	left: 50%;
+	top: 50%;
+	width: 350px;
+	margin: -190px 0 0 -175px;
+	border-radius: 5px;
+	background: rgba(255, 255, 255, 0.3);
+	overflow: hidden;
 }
 .ms-content {
-    padding: 30px 30px;
+	padding: 30px 30px;
 }
 .login-btn {
-    text-align: center;
+	text-align: center;
 }
 .login-btn button {
-    width: 100%;
-    height: 36px;
-    margin-bottom: 10px;
+	width: 100%;
+	height: 36px;
+	margin-bottom: 10px;
 }
 .login-tips {
-    font-size: 12px;
-    line-height: 30px;
-    color: #fff;
+	font-size: 12px;
+	line-height: 30px;
+	color: #fff;
 }
-</style>
+</style>

+ 14 - 25
src/views/Markdown.vue

@@ -1,32 +1,21 @@
 <template>
-    <div>
-        <div class="crumbs">
-            <el-breadcrumb separator="/">
-                <el-breadcrumb-item>
-                    <i class="el-icon-lx-calendar"></i> 表单
-                </el-breadcrumb-item>
-                <el-breadcrumb-item>markdown编辑器</el-breadcrumb-item>
-            </el-breadcrumb>
-        </div>
-        <div class="container">
-            <div class="plugins-tips">
-                md-editor-v3:vue3版本的 markdown 编辑器,配置丰富,请详看文档。
-                访问地址:
-                <a href="https://imzbf.github.io/md-editor-v3/index" target="_blank">md-editor-v3</a>
-            </div>
-            <md-editor class="mgb20" v-model="text" @on-upload-img="onUploadImg"/>
-            <el-button type="primary">提交</el-button>
-        </div>
-    </div>
+	<div class="container">
+		<div class="plugins-tips">
+			md-editor-v3:vue3版本的 markdown 编辑器,配置丰富,请详看文档。 访问地址:
+			<a href="https://imzbf.github.io/md-editor-v3/index" target="_blank">md-editor-v3</a>
+		</div>
+		<md-editor class="mgb20" v-model="text" @on-upload-img="onUploadImg" />
+		<el-button type="primary">提交</el-button>
+	</div>
 </template>
 
-<script setup>
-import { ref} from "vue";
+<script setup lang="ts" name="md">
+import { ref } from 'vue';
 import MdEditor from 'md-editor-v3';
 import 'md-editor-v3/lib/style.css';
 
 const text = ref('Hello Editor!');
-const onUploadImg = (files)=>{
-    console.log(files)
-}
-</script>
+const onUploadImg = (files: any) => {
+	console.log(files);
+};
+</script>

+ 128 - 31
src/views/Permission.vue

@@ -1,40 +1,137 @@
 <template>
-    <div>
-        <div class="crumbs">
-            <el-breadcrumb separator="/">
-                <el-breadcrumb-item>
-                    <i class="el-icon-lx-warn"></i> 权限测试
-                </el-breadcrumb-item>
-            </el-breadcrumb>
-        </div>
-        <div class="container">
-            <h1>管理员权限页面</h1>
-            <p>只有用 admin 账号登录的才拥有管理员权限,才能进到这个页面,其他账号想进来都会跳到403页面,重新用管理员账号登录才有权限。</p>
-            <p>
-                想尝试一下,请
-                <router-link to="/login" class="logout">退出登录</router-link>,随便输入个账号名,再进来试试看。
-            </p>
-        </div>
-    </div>
+	<div class="container">
+		<div class="plugins-tips">通过 v-permiss 自定义指令实现权限管理,使用非 admin 账号登录,可查看效果。</div>
+		<div class="mgb20">
+			<span class="label">角色:</span>
+			<el-select v-model="role" @change="handleChange">
+				<el-option label="超级管理员" value="admin"></el-option>
+				<el-option label="普通用户" value="user"></el-option>
+			</el-select>
+		</div>
+		<div class="mgb20 tree-wrapper">
+			<el-tree
+				ref="tree"
+				:data="data"
+				node-key="id"
+				default-expand-all
+				show-checkbox
+				:default-checked-keys="checkedKeys"
+			/>
+		</div>
+		<el-button type="primary" @click="onSubmit">保存权限</el-button>
+	</div>
 </template>
 
-<script>
-export default {
-    name: "permission"
+<script setup lang="ts" name="permission">
+import { ref } from 'vue';
+import { ElTree } from 'element-plus';
+import { usePermissStore } from '../store/permiss';
+
+const role = ref<string>('admin');
+
+interface Tree {
+	id: string;
+	label: string;
+	children?: Tree[];
+}
+
+const data: Tree[] = [
+	{
+		id: '1',
+		label: '系统首页'
+	},
+	{
+		id: '2',
+		label: '基础表格',
+		children: [
+			{
+				id: '15',
+				label: '编辑'
+			},
+			{
+				id: '16',
+				label: '删除'
+			}
+		]
+	},
+	{
+		id: '3',
+		label: 'tab选项卡'
+	},
+	{
+		id: '4',
+		label: '表单相关',
+		children: [
+			{
+				id: '5',
+				label: '基本表单'
+			},
+			{
+				id: '6',
+				label: '文件上传'
+			},
+			{
+				id: '7',
+				label: '三级菜单',
+				children: [
+					{
+						id: '8',
+						label: '富文本编辑器'
+					},
+					{
+						id: '9',
+						label: 'markdown编辑器'
+					}
+				]
+			}
+		]
+	},
+	{
+		id: '10',
+		label: '自定义图标'
+	},
+	{
+		id: '11',
+		label: 'schart图表'
+	},
+
+	{
+		id: '13',
+		label: '权限管理'
+	},
+	{
+		id: '14',
+		label: '支持作者'
+	}
+];
+
+const permiss = usePermissStore();
+
+// 获取当前权限
+const checkedKeys = ref<string[]>([]);
+const getPremission = () => {
+	// 请求接口返回权限
+	checkedKeys.value = permiss.defaultList[role.value];
+};
+getPremission();
+
+// 保存权限
+const tree = ref<InstanceType<typeof ElTree>>();
+const onSubmit = () => {
+	// 获取选中的权限
+	console.log(tree.value!.getCheckedKeys(false));
+};
+
+const handleChange = (val: string[]) => {
+	tree.value!.setCheckedKeys(permiss.defaultList[role.value]);
 };
 </script>
 
 <style scoped>
-h1 {
-    text-align: center;
-    margin: 30px 0;
-}
-p {
-    line-height: 30px;
-    margin-bottom: 10px;
-    text-indent: 2em;
+.tree-wrapper {
+	max-width: 500px;
 }
-.logout {
-    color: #409eff;
+.label {
+	font-size: 14px;
 }
-</style>
+</style>

+ 102 - 123
src/views/Tabs.vue

@@ -1,137 +1,116 @@
 <template>
-    <div class="">
-        <div class="crumbs">
-            <el-breadcrumb separator="/">
-                <el-breadcrumb-item><i class="el-icon-lx-copy"></i> tab选项卡</el-breadcrumb-item>
-            </el-breadcrumb>
-        </div>
-        <div class="container">
-            <el-tabs v-model="message">
-                <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>
-                            </template>
-                        </el-table-column>
-                        <el-table-column prop="date" width="180"></el-table-column>
-                        <el-table-column width="120">
-                            <template #default="scope">
-                                <el-button size="small" @click="handleRead(scope.$index)">标为已读</el-button>
-                            </template>
-                        </el-table-column>
-                    </el-table>
-                    <div class="handle-row">
-                        <el-button type="primary">全部标为已读</el-button>
-                    </div>
-                </el-tab-pane>
-                <el-tab-pane :label="`已读消息(${state.read.length})`" name="second">
-                    <template v-if="message === 'second'">
-                        <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>
-                                </template>
-                            </el-table-column>
-                            <el-table-column prop="date" width="150"></el-table-column>
-                            <el-table-column width="120">
-                                <template #default="scope">
-                                    <el-button type="danger" @click="handleDel(scope.$index)">删除</el-button>
-                                </template>
-                            </el-table-column>
-                        </el-table>
-                        <div class="handle-row">
-                            <el-button type="danger">删除全部</el-button>
-                        </div>
-                    </template>
-                </el-tab-pane>
-                <el-tab-pane :label="`回收站(${state.recycle.length})`" name="third">
-                    <template v-if="message === 'third'">
-                        <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>
-                                </template>
-                            </el-table-column>
-                            <el-table-column prop="date" width="150"></el-table-column>
-                            <el-table-column width="120">
-                                <template #default="scope">
-                                    <el-button @click="handleRestore(scope.$index)">还原</el-button>
-                                </template>
-                            </el-table-column>
-                        </el-table>
-                        <div class="handle-row">
-                            <el-button type="danger">清空回收站</el-button>
-                        </div>
-                    </template>
-                </el-tab-pane>
-            </el-tabs>
-        </div>
-    </div>
+	<div class="container">
+		<el-tabs v-model="message">
+			<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>
+						</template>
+					</el-table-column>
+					<el-table-column prop="date" width="180"></el-table-column>
+					<el-table-column width="120">
+						<template #default="scope">
+							<el-button size="small" @click="handleRead(scope.$index)">标为已读</el-button>
+						</template>
+					</el-table-column>
+				</el-table>
+				<div class="handle-row">
+					<el-button type="primary">全部标为已读</el-button>
+				</div>
+			</el-tab-pane>
+			<el-tab-pane :label="`已读消息(${state.read.length})`" name="second">
+				<template v-if="message === 'second'">
+					<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>
+							</template>
+						</el-table-column>
+						<el-table-column prop="date" width="150"></el-table-column>
+						<el-table-column width="120">
+							<template #default="scope">
+								<el-button type="danger" @click="handleDel(scope.$index)">删除</el-button>
+							</template>
+						</el-table-column>
+					</el-table>
+					<div class="handle-row">
+						<el-button type="danger">删除全部</el-button>
+					</div>
+				</template>
+			</el-tab-pane>
+			<el-tab-pane :label="`回收站(${state.recycle.length})`" name="third">
+				<template v-if="message === 'third'">
+					<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>
+							</template>
+						</el-table-column>
+						<el-table-column prop="date" width="150"></el-table-column>
+						<el-table-column width="120">
+							<template #default="scope">
+								<el-button @click="handleRestore(scope.$index)">还原</el-button>
+							</template>
+						</el-table-column>
+					</el-table>
+					<div class="handle-row">
+						<el-button type="danger">清空回收站</el-button>
+					</div>
+				</template>
+			</el-tab-pane>
+		</el-tabs>
+	</div>
 </template>
 
-<script>
-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点进行升级维护",
-                },
-            ],
-        });
+<script setup lang="ts" name="tabs">
+import { ref, reactive } from 'vue';
 
-        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);
-        };
+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点进行升级维护'
+		}
+	]
+});
 
-        return {
-            message,
-            state,
-            handleRead,
-            handleDel,
-            handleRestore,
-        };
-    },
+const handleRead = (index: number) => {
+	const item = state.unread.splice(index, 1);
+	state.read = item.concat(state.read);
+};
+const handleDel = (index: number) => {
+	const item = state.read.splice(index, 1);
+	state.recycle = item.concat(state.recycle);
+};
+const handleRestore = (index: number) => {
+	const item = state.recycle.splice(index, 1);
+	state.read = item.concat(state.read);
 };
 </script>
 
 <style>
 .message-title {
-    cursor: pointer;
+	cursor: pointer;
 }
 .handle-row {
-    margin-top: 30px;
+	margin-top: 30px;
 }
 </style>
-

+ 54 - 117
src/views/Upload.vue

@@ -1,133 +1,70 @@
 <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="content-title">支持拖拽</div>
-            <div class="plugins-tips">
-                Element UI自带上传组件。
-                访问地址:
-                <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>
-                <i class="el-icon-upload"></i>
-                <div class="el-upload__text">
-                    将文件拖到此处,或
-                    <em>点击上传</em>
-                </div>
-                <template #tip>
-                    <div class="el-upload__tip">只能上传 jpg/png 文件,且不超过 500kb</div>
-                </template>
-            </el-upload>
+	<div class="container">
+		<div class="content-title">支持拖拽</div>
+		<div class="plugins-tips">
+			Element Plus自带上传组件。 访问地址:
+			<a href="https://element-plus.org/zh-CN/component/upload.html" target="_blank">Element Plus Upload</a>
+		</div>
+		<el-upload class="upload-demo" drag action="http://jsonplaceholder.typicode.com/api/posts/" multiple>
+			<el-icon class="el-icon--upload"><upload-filled /></el-icon>
+			<div class="el-upload__text">
+				将文件拖到此处,或
+				<em>点击上传</em>
+			</div>
+			<template #tip>
+				<div class="el-upload__tip">只能上传 jpg/png 文件,且不超过 500kb</div>
+			</template>
+		</el-upload>
 
-            <div class="content-title">支持裁剪</div>
-            <div class="plugins-tips">
-                vue-cropperjs:一个封装了 cropperjs 的 Vue 组件。
-                访问地址:
-                <a href="https://github.com/Agontuk/vue-cropperjs" target="_blank">vue-cropperjs</a>
-            </div>
-        </div>
-    </div>
+		<div class="content-title">支持裁剪</div>
+		<div class="plugins-tips">
+			vue-cropperjs:一个封装了 cropperjs 的 Vue 组件。 访问地址:
+			<a href="https://github.com/Agontuk/vue-cropperjs" target="_blank">vue-cropperjs</a>。 示例请查看
+			<router-link to="/user">个人中心</router-link>
+		</div>
+	</div>
 </template>
 
-<script>
-import { ref } from "vue";
-import VueCropper from "vue-cropperjs";
-import "cropperjs/dist/cropper.css";
-import defaultSrc from "../assets/img/img.jpg";
-export default {
-    name: "upload",
-    components: {
-        VueCropper,
-    },
-    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) => {
-                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 cancelCrop = () => {
-            dialogVisible.value = false;
-            cropImg.value = defaultSrc;
-        };
-
-        return {
-            cropper,
-            imgSrc,
-            cropImg,
-            dialogVisible,
-            setImage,
-            cropImage,
-            cancelCrop,
-        };
-    },
-};
-</script>
-
 <style scoped>
 .content-title {
-    font-weight: 400;
-    line-height: 50px;
-    margin: 10px 0;
-    font-size: 22px;
-    color: #1f2f3d;
+	font-weight: 400;
+	line-height: 50px;
+	margin: 10px 0;
+	font-size: 22px;
+	color: #1f2f3d;
 }
 
 .pre-img {
-    width: 100px;
-    height: 100px;
-    background: #f8f8f8;
-    border: 1px solid #eee;
-    border-radius: 5px;
+	width: 100px;
+	height: 100px;
+	background: #f8f8f8;
+	border: 1px solid #eee;
+	border-radius: 5px;
 }
 .crop-demo {
-    display: flex;
-    align-items: flex-end;
+	display: flex;
+	align-items: flex-end;
 }
 .crop-demo-btn {
-    position: relative;
-    width: 100px;
-    height: 40px;
-    line-height: 40px;
-    padding: 0 20px;
-    margin-left: 30px;
-    background-color: #409eff;
-    color: #fff;
-    font-size: 14px;
-    border-radius: 4px;
-    box-sizing: border-box;
+	position: relative;
+	width: 100px;
+	height: 40px;
+	line-height: 40px;
+	padding: 0 20px;
+	margin-left: 30px;
+	background-color: #409eff;
+	color: #fff;
+	font-size: 14px;
+	border-radius: 4px;
+	box-sizing: border-box;
 }
 .crop-input {
-    position: absolute;
-    width: 100px;
-    height: 40px;
-    left: 0;
-    top: 0;
-    opacity: 0;
-    cursor: pointer;
+	position: absolute;
+	width: 100px;
+	height: 40px;
+	left: 0;
+	top: 0;
+	opacity: 0;
+	cursor: pointer;
 }
-</style>
+</style>

+ 144 - 162
src/views/User.vue

@@ -1,192 +1,174 @@
 <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>
+	<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">
+							<el-avatar :size="100" :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 #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 = () => {};
+<script setup lang="ts" name="user">
+import { reactive, ref } from 'vue';
+import VueCropper from 'vue-cropperjs';
+import 'cropperjs/dist/cropper.css';
+import avatar from '../assets/img/img.jpg';
 
-        const avatarImg = ref(avatar);
-        const imgSrc = ref("");
-        const cropImg = ref("");
-        const dialogVisible = ref(false);
-        const cropper = ref(null);
+const name = localStorage.getItem('ms_username');
+const form = reactive({
+	old: '',
+	new: '',
+	desc: '不可能!我的代码怎么可能会有bug!'
+});
+const onSubmit = () => {};
 
-        const showDialog = () => {
-            dialogVisible.value = true;
-            imgSrc.value = avatarImg.value;
-        };
+const avatarImg = ref(avatar);
+const imgSrc = ref('');
+const cropImg = ref('');
+const dialogVisible = ref(false);
+const cropper: any = ref();
 
-        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 showDialog = () => {
+	dialogVisible.value = true;
+	imgSrc.value = avatarImg.value;
+};
 
-        const cropImage = () => {
-            cropImg.value = cropper.value.getCroppedCanvas().toDataURL();
-        };
+const setImage = (e: any) => {
+	const file = e.target.files[0];
+	if (!file.type.includes('image/')) {
+		return;
+	}
+	const reader = new FileReader();
+	reader.onload = (event: any) => {
+		dialogVisible.value = true;
+		imgSrc.value = event.target.result;
+		cropper.value && cropper.value.replace(event.target.result);
+	};
+	reader.readAsDataURL(file);
+};
 
-        const saveAvatar = () => {
-            avatarImg.value = cropImg.value;
-            dialogVisible.value = false;
-        };
+const cropImage = () => {
+	cropImg.value = cropper.value.getCroppedCanvas().toDataURL();
+};
 
-        return {
-            name,
-            form,
-            onSubmit,
-            cropper,
-            avatarImg,
-            imgSrc,
-            cropImg,
-            showDialog,
-            dialogVisible,
-            setImage,
-            cropImage,
-            saveAvatar,
-        };
-    },
+const saveAvatar = () => {
+	avatarImg.value = cropImg.value;
+	dialogVisible.value = false;
 };
 </script>
 
 <style scoped>
 .info {
-    text-align: center;
-    padding: 35px 0;
+	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%;
+	position: relative;
+	margin: auto;
+	width: 100px;
+	height: 100px;
+	background: #f8f8f8;
+	border: 1px solid #eee;
+	border-radius: 50px;
+	overflow: hidden;
 }
+
 .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;
+	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;
+	color: #eee;
+	font-size: 25px;
 }
 .info-image:hover .info-edit {
-    opacity: 1;
+	opacity: 1;
 }
 .info-name {
-    margin: 15px 0 10px;
-    font-size: 24px;
-    font-weight: 500;
-    color: #262626;
+	margin: 15px 0 10px;
+	font-size: 24px;
+	font-weight: 500;
+	color: #262626;
 }
 .crop-demo-btn {
-    position: relative;
+	position: relative;
 }
 .crop-input {
-    position: absolute;
-    width: 100px;
-    height: 40px;
-    left: 0;
-    top: 0;
-    opacity: 0;
-    cursor: pointer;
+	position: absolute;
+	width: 100px;
+	height: 40px;
+	left: 0;
+	top: 0;
+	opacity: 0;
+	cursor: pointer;
 }
-</style>
+</style>

+ 127 - 0
src/views/charts.vue

@@ -0,0 +1,127 @@
+<template>
+	<div class="container">
+		<div class="plugins-tips">
+			vue-schart:vue.js封装sChart.js的图表组件。 访问地址:
+			<a href="https://github.com/lin-xin/vue-schart" target="_blank">vue-schart</a>
+		</div>
+		<div class="schart-box">
+			<div class="content-title">柱状图</div>
+			<schart class="schart" canvasId="bar" :options="options1"></schart>
+		</div>
+		<div class="schart-box">
+			<div class="content-title">折线图</div>
+			<schart class="schart" canvasId="line" :options="options2"></schart>
+		</div>
+		<div class="schart-box">
+			<div class="content-title">饼状图</div>
+			<schart class="schart" canvasId="pie" :options="options3"></schart>
+		</div>
+		<div class="schart-box">
+			<div class="content-title">环形图</div>
+			<schart class="schart" canvasId="ring" :options="options4"></schart>
+		</div>
+	</div>
+</template>
+
+<script setup lang="ts" name="basecharts">
+import Schart from 'vue-schart';
+
+const 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]
+		}
+	]
+};
+const options2 = {
+	type: 'line',
+	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]
+		},
+		{
+			label: '食品',
+			data: [114, 138, 200, 235, 190]
+		}
+	]
+};
+const options3 = {
+	type: 'pie',
+	title: {
+		text: '服装品类销售饼状图'
+	},
+	legend: {
+		position: 'left'
+	},
+	bgColor: '#fbfbfb',
+	labels: ['T恤', '牛仔裤', '连衣裙', '毛衣', '七分裤', '短裙', '羽绒服'],
+	datasets: [
+		{
+			data: [334, 278, 190, 235, 260, 200, 141]
+		}
+	]
+};
+const options4 = {
+	type: 'ring',
+	title: {
+		text: '环形三等分'
+	},
+	showValue: false,
+	legend: {
+		position: 'bottom',
+		bottom: 40
+	},
+	bgColor: '#fbfbfb',
+	labels: ['vue', 'react', 'angular'],
+	datasets: [
+		{
+			data: [500, 500, 500]
+		}
+	]
+};
+</script>
+
+<style scoped>
+.schart-box {
+	display: inline-block;
+	margin: 20px;
+}
+.schart {
+	width: 600px;
+	height: 400px;
+}
+.content-title {
+	clear: both;
+	font-weight: 400;
+	line-height: 50px;
+	margin: 10px 0;
+	font-size: 22px;
+	color: #1f2f3d;
+}
+</style>

+ 156 - 0
src/views/form.vue

@@ -0,0 +1,156 @@
+<template>
+	<div class="container">
+		<div class="form-box">
+			<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="选择器" 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>
+						<el-option key="imoo" label="imoo" value="imoo"></el-option>
+					</el-select>
+				</el-form-item>
+				<el-form-item label="日期时间">
+					<el-col :span="11">
+						<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-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="城市级联" prop="options">
+					<el-cascader :options="options" v-model="form.options"></el-cascader>
+				</el-form-item>
+				<el-form-item label="选择开关" prop="delivery">
+					<el-switch v-model="form.delivery"></el-switch>
+				</el-form-item>
+				<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="单选框" 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="文本框" 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(formRef)">表单提交</el-button>
+					<el-button @click="onReset(formRef)">重置表单</el-button>
+				</el-form-item>
+			</el-form>
+		</div>
+	</div>
+</template>
+
+<script setup lang="ts" name="baseform">
+import { reactive, ref } from 'vue';
+import { ElMessage } from 'element-plus';
+import type { FormInstance, FormRules } from 'element-plus';
+
+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: FormRules = {
+	name: [{ required: true, message: '请输入表单名称', trigger: 'blur' }]
+};
+const formRef = ref<FormInstance>();
+const form = reactive({
+	name: '',
+	region: '',
+	date1: '',
+	date2: '',
+	delivery: true,
+	type: ['步步高'],
+	resource: '小天才',
+	desc: '',
+	options: []
+});
+// 提交
+const onSubmit = (formEl: FormInstance | undefined) => {
+	// 表单校验
+	if (!formEl) return;
+	formEl.validate(valid => {
+		if (valid) {
+			console.log(form);
+			ElMessage.success('提交成功!');
+		} else {
+			return false;
+		}
+	});
+};
+// 重置
+const onReset = (formEl: FormInstance | undefined) => {
+	if (!formEl) return;
+	formEl.resetFields();
+};
+</script>

+ 191 - 0
src/views/table.vue

@@ -0,0 +1,191 @@
+<template>
+	<div>
+		<div class="container">
+			<div class="handle-box">
+				<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>
+				</el-select>
+				<el-input v-model="query.name" placeholder="用户名" class="handle-input mr10"></el-input>
+				<el-button type="primary" :icon="Search" @click="handleSearch">搜索</el-button>
+				<el-button type="primary" :icon="Plus">新增</el-button>
+			</div>
+			<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="账户余额">
+					<template #default="scope">¥{{ scope.row.money }}</template>
+				</el-table-column>
+				<el-table-column label="头像(查看大图)" align="center">
+					<template #default="scope">
+						<el-image
+							class="table-td-thumb"
+							:src="scope.row.thumb"
+							:z-index="10"
+							:preview-src-list="[scope.row.thumb]"
+							preview-teleported
+						>
+						</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="scope.row.state === '成功' ? 'success' : scope.row.state === '失败' ? 'danger' : ''"
+						>
+							{{ scope.row.state }}
+						</el-tag>
+					</template>
+				</el-table-column>
+
+				<el-table-column prop="date" label="注册时间"></el-table-column>
+				<el-table-column label="操作" width="220" align="center">
+					<template #default="scope">
+						<el-button text :icon="Edit" @click="handleEdit(scope.$index, scope.row)" v-permiss="15">
+							编辑
+						</el-button>
+						<el-button text :icon="Delete" class="red" @click="handleDelete(scope.$index)" v-permiss="16">
+							删除
+						</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>
+			</div>
+		</div>
+
+		<!-- 编辑弹出框 -->
+		<el-dialog title="编辑" v-model="editVisible" width="30%">
+			<el-form label-width="70px">
+				<el-form-item label="用户名">
+					<el-input v-model="form.name"></el-input>
+				</el-form-item>
+				<el-form-item label="地址">
+					<el-input v-model="form.address"></el-input>
+				</el-form-item>
+			</el-form>
+			<template #footer>
+				<span class="dialog-footer">
+					<el-button @click="editVisible = false">取 消</el-button>
+					<el-button type="primary" @click="saveEdit">确 定</el-button>
+				</span>
+			</template>
+		</el-dialog>
+	</div>
+</template>
+
+<script setup lang="ts" name="basetable">
+import { ref, reactive } from 'vue';
+import { ElMessage, ElMessageBox } from 'element-plus';
+import { Delete, Edit, Search, Plus } from '@element-plus/icons-vue';
+import { fetchData } from '../api/index';
+
+interface TableItem {
+	id: number;
+	name: string;
+	money: string;
+	state: string;
+	date: string;
+	address: string;
+}
+
+const query = reactive({
+	address: '',
+	name: '',
+	pageIndex: 1,
+	pageSize: 10
+});
+const tableData = ref<TableItem[]>([]);
+const pageTotal = ref(0);
+// 获取表格数据
+const getData = () => {
+	fetchData().then(res => {
+		tableData.value = res.data.list;
+		pageTotal.value = res.data.pageTotal || 50;
+	});
+};
+getData();
+
+// 查询操作
+const handleSearch = () => {
+	query.pageIndex = 1;
+	getData();
+};
+// 分页导航
+const handlePageChange = (val: number) => {
+	query.pageIndex = val;
+	getData();
+};
+
+// 删除操作
+const handleDelete = (index: number) => {
+	// 二次确认删除
+	ElMessageBox.confirm('确定要删除吗?', '提示', {
+		type: 'warning'
+	})
+		.then(() => {
+			ElMessage.success('删除成功');
+			tableData.value.splice(index, 1);
+		})
+		.catch(() => {});
+};
+
+// 表格编辑时弹窗和保存
+const editVisible = ref(false);
+let form = reactive({
+	name: '',
+	address: ''
+});
+let idx: number = -1;
+const handleEdit = (index: number, row: any) => {
+	idx = index;
+	form.name = row.name;
+	form.address = row.address;
+	editVisible.value = true;
+};
+const saveEdit = () => {
+	editVisible.value = false;
+	ElMessage.success(`修改第 ${idx + 1} 行成功`);
+	tableData.value[idx].name = form.name;
+	tableData.value[idx].address = form.address;
+};
+</script>
+
+<style scoped>
+.handle-box {
+	margin-bottom: 20px;
+}
+
+.handle-select {
+	width: 120px;
+}
+
+.handle-input {
+	width: 300px;
+}
+.table {
+	width: 100%;
+	font-size: 14px;
+}
+.red {
+	color: #ff0000;
+}
+.mr10 {
+	margin-right: 10px;
+}
+.table-td-thumb {
+	display: block;
+	margin: auto;
+	width: 40px;
+	height: 40px;
+}
+</style>

+ 10 - 0
src/vite-env.d.ts

@@ -0,0 +1,10 @@
+/// <reference types="vite/client" />
+
+declare module '*.vue' {
+  import type { DefineComponent } from 'vue'
+  const component: DefineComponent<{}, {}, any>
+  export default component
+}
+
+declare module 'vue-schart';
+declare module 'vue-cropperjs';

+ 18 - 0
tsconfig.json

@@ -0,0 +1,18 @@
+{
+  "compilerOptions": {
+    "target": "ESNext",
+    "useDefineForClassFields": true,
+    "module": "ESNext",
+    "moduleResolution": "Node",
+    "strict": true,
+    "jsx": "preserve",
+    "sourceMap": true,
+    "resolveJsonModule": true,
+    "isolatedModules": true,
+    "esModuleInterop": true,
+    "lib": ["ESNext", "DOM"],
+    "skipLibCheck": true
+  },
+  "include": ["src/**/*.ts", "src/**/*.d.ts","src/**/*.vue"],
+  "references": [{ "path": "./tsconfig.node.json" }]
+}

+ 9 - 0
tsconfig.node.json

@@ -0,0 +1,9 @@
+{
+  "compilerOptions": {
+    "composite": true,
+    "module": "ESNext",
+    "moduleResolution": "Node",
+    "allowSyntheticDefaultImports": true
+  },
+  "include": ["vite.config.ts"]
+}

+ 0 - 9
vite.config.js

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

+ 22 - 0
vite.config.ts

@@ -0,0 +1,22 @@
+import { defineConfig } from 'vite';
+import vue from '@vitejs/plugin-vue';
+import VueSetupExtend from 'vite-plugin-vue-setup-extend';
+import AutoImport from 'unplugin-auto-import/vite';
+import Components from 'unplugin-vue-components/vite';
+import { ElementPlusResolver } from 'unplugin-vue-components/resolvers';
+export default defineConfig({
+	base: './',
+	plugins: [
+		vue(),
+		VueSetupExtend(),
+		AutoImport({
+			resolvers: [ElementPlusResolver()]
+		}),
+		Components({
+			resolvers: [ElementPlusResolver()]
+		})
+	],
+	optimizeDeps: {
+		include: ['schart.js']
+	}
+});