lrf 3 年之前
父節點
當前提交
8d70c99dbf

+ 22 - 9
.eslintrc.js

@@ -3,18 +3,31 @@ module.exports = {
   env: {
     node: true,
   },
-  extends: [
-    "plugin:vue/vue3-essential",
-    "eslint:recommended",
-    "@vue/typescript/recommended",
-    "@vue/prettier",
-    "@vue/prettier/@typescript-eslint",
-  ],
+  extends: ['plugin:vue/vue3-essential', 'eslint:recommended', '@vue/typescript/recommended', '@vue/prettier', '@vue/prettier/@typescript-eslint'],
   parserOptions: {
     ecmaVersion: 2020,
   },
   rules: {
-    "no-console": process.env.NODE_ENV === "production" ? "warn" : "off",
-    "no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off",
+    'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
+    'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
+    '@typescript-eslint/no-explicit-any': ['off'],
+    'max-len': [
+      'warn',
+      {
+        code: 10000,
+      },
+    ],
+    'no-unused-vars': 'off',
+    'no-console': 'off',
+    'prettier/prettier': [
+      'warn',
+      {
+        singleQuote: true,
+        trailingComma: 'es5',
+        bracketSpacing: true,
+        jsxBracketSameLine: true,
+        printWidth: 160,
+      },
+    ],
   },
 };

+ 357 - 22
package-lock.json

@@ -8,9 +8,12 @@
       "name": "vue-learn-ts",
       "version": "0.1.0",
       "dependencies": {
+        "@element-plus/icons-vue": "^2.0.6",
         "core-js": "^3.6.5",
+        "element-plus": "^2.2.9",
+        "lodash": "^4.17.21",
         "vue": "^3.0.0",
-        "vue-router": "^4.0.0-0"
+        "vue-router": "^4.0.13"
       },
       "devDependencies": {
         "@typescript-eslint/eslint-plugin": "^4.18.0",
@@ -1704,6 +1707,35 @@
         "node": ">=6.9.0"
       }
     },
+    "node_modules/@ctrl/tinycolor": {
+      "version": "3.4.1",
+      "resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-3.4.1.tgz",
+      "integrity": "sha512-ej5oVy6lykXsvieQtqZxCOaLT+xD4+QNarq78cIYISHmZXshCvROLudpQN3lfL8G0NL7plMSSK+zlyvCaIJ4Iw==",
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/@element-plus/icons-vue": {
+      "version": "2.0.6",
+      "resolved": "https://registry.npmjs.org/@element-plus/icons-vue/-/icons-vue-2.0.6.tgz",
+      "integrity": "sha512-lPpG8hYkjL/Z97DH5Ei6w6o22Z4YdNglWCNYOPcB33JCF2A4wye6HFgSI7hEt9zdLyxlSpiqtgf9XcYU+m5mew==",
+      "peerDependencies": {
+        "vue": "^3.2.0"
+      }
+    },
+    "node_modules/@floating-ui/core": {
+      "version": "0.7.3",
+      "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-0.7.3.tgz",
+      "integrity": "sha512-buc8BXHmG9l82+OQXOFU3Kr2XQx9ys01U/Q9HMIrZ300iLc8HLMgh7dcCqgYzAzf4BkoQvDcXf5Y+CuEZ5JBYg=="
+    },
+    "node_modules/@floating-ui/dom": {
+      "version": "0.5.4",
+      "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-0.5.4.tgz",
+      "integrity": "sha512-419BMceRLq0RrmTSDxn8hf9R3VCJv2K9PUfugh5JyEFmdjzDo+e8U5EdR8nzKq8Yj1htzLm3b6eQEEam3/rrtg==",
+      "dependencies": {
+        "@floating-ui/core": "^0.7.3"
+      }
+    },
     "node_modules/@hapi/address": {
       "version": "2.1.4",
       "resolved": "https://registry.npmjs.org/@hapi/address/-/address-2.1.4.tgz",
@@ -1878,6 +1910,16 @@
         "node": ">= 8"
       }
     },
+    "node_modules/@popperjs/core": {
+      "name": "@sxzz/popperjs-es",
+      "version": "2.11.7",
+      "resolved": "https://registry.npmjs.org/@sxzz/popperjs-es/-/popperjs-es-2.11.7.tgz",
+      "integrity": "sha512-Ccy0NlLkzr0Ex2FKvh2X+OyERHXJ88XJ1MXtsI9y9fGexlaXaVTPzBCRBwIxFkORuOb+uBqeu+RqnpgYTEZRUQ==",
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/popperjs"
+      }
+    },
     "node_modules/@soda/friendly-errors-webpack-plugin": {
       "version": "1.8.1",
       "resolved": "https://registry.npmjs.org/@soda/friendly-errors-webpack-plugin/-/friendly-errors-webpack-plugin-1.8.1.tgz",
@@ -2046,6 +2088,19 @@
       "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==",
       "dev": true
     },
+    "node_modules/@types/lodash": {
+      "version": "4.14.182",
+      "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.182.tgz",
+      "integrity": "sha512-/THyiqyQAP9AfARo4pF+aCGcyiQ94tX/Is2I7HofNRqoYLgN1PBoOWu2/zTA5zMxzP5EFutMtWtGAFRKUe961Q=="
+    },
+    "node_modules/@types/lodash-es": {
+      "version": "4.17.6",
+      "resolved": "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.6.tgz",
+      "integrity": "sha512-R+zTeVUKDdfoRxpAryaQNRKk3105Rrgx2CFRClIgRGaqDTdjsm8h6IYA8ir584W3ePzkZfst5xIgDwYrlh9HLg==",
+      "dependencies": {
+        "@types/lodash": "*"
+      }
+    },
     "node_modules/@types/mime": {
       "version": "1.3.2",
       "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz",
@@ -2132,6 +2187,11 @@
         "source-map": "^0.6.1"
       }
     },
+    "node_modules/@types/web-bluetooth": {
+      "version": "0.0.14",
+      "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.14.tgz",
+      "integrity": "sha512-5d2RhCard1nQUC3aHcq/gHzWYO6K0WJmAbjO7mQJgCQKtZpgXxv1rOM6O/dBDhDYYVutk1sciOgNSe+5YyfM8A=="
+    },
     "node_modules/@types/webpack": {
       "version": "4.41.32",
       "resolved": "https://registry.npmjs.org/@types/webpack/-/webpack-4.41.32.tgz",
@@ -3246,6 +3306,88 @@
       "integrity": "sha512-Iu8Tbg3f+emIIMmI2ycSI8QcEuAUgPTgHwesDU1eKMLE4YC/c/sFbGc70QgMq31ijRftV0R7vCm9co6rldCeOA==",
       "dev": true
     },
+    "node_modules/@vueuse/core": {
+      "version": "8.9.2",
+      "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-8.9.2.tgz",
+      "integrity": "sha512-dE3/JgwqIHmmtmRBdZAnq87rZCSFbYVps2t3gWy9Jv/+Qp6sHSSKuPFtwguJVZ2OnaGnB/AMRmx4CuFRxFin3A==",
+      "dependencies": {
+        "@types/web-bluetooth": "^0.0.14",
+        "@vueuse/metadata": "8.9.2",
+        "@vueuse/shared": "8.9.2",
+        "vue-demi": "*"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/antfu"
+      },
+      "peerDependencies": {
+        "@vue/composition-api": "^1.1.0",
+        "vue": "^2.6.0 || ^3.2.0"
+      },
+      "peerDependenciesMeta": {
+        "@vue/composition-api": {
+          "optional": true
+        },
+        "vue": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@vueuse/core/node_modules/@vueuse/shared": {
+      "version": "8.9.2",
+      "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-8.9.2.tgz",
+      "integrity": "sha512-s4Nk82oheL5z1GywyGnqjob0MzbAt88olMZa0vgt/p3gcMsT8Ff7+SqmNgEFC6AAs6xiuhOAZpnew9Zs3d90yQ==",
+      "dependencies": {
+        "vue-demi": "*"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/antfu"
+      },
+      "peerDependencies": {
+        "@vue/composition-api": "^1.1.0",
+        "vue": "^2.6.0 || ^3.2.0"
+      },
+      "peerDependenciesMeta": {
+        "@vue/composition-api": {
+          "optional": true
+        },
+        "vue": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@vueuse/core/node_modules/vue-demi": {
+      "version": "0.13.4",
+      "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.13.4.tgz",
+      "integrity": "sha512-KP4lq9uSz0KZbaqCllRhnxMV3mYRsRWJfdsAhZyt5bV5O1RTpoeDptBRV9NOa/JgOpfaA9ane88VF7OjWNK/DA==",
+      "hasInstallScript": true,
+      "bin": {
+        "vue-demi-fix": "bin/vue-demi-fix.js",
+        "vue-demi-switch": "bin/vue-demi-switch.js"
+      },
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/antfu"
+      },
+      "peerDependencies": {
+        "@vue/composition-api": "^1.0.0-rc.1",
+        "vue": "^3.0.0-0 || ^2.6.0"
+      },
+      "peerDependenciesMeta": {
+        "@vue/composition-api": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@vueuse/metadata": {
+      "version": "8.9.2",
+      "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-8.9.2.tgz",
+      "integrity": "sha512-g2s2BeyeEtJElmMFfFPnM+BTvnt0omniyvz8U18/zXDpQIMGozlNQgHoFeratyMfgVBhH/u2VKzmchChtDsgPQ==",
+      "funding": {
+        "url": "https://github.com/sponsors/antfu"
+      }
+    },
     "node_modules/@webassemblyjs/ast": {
       "version": "1.9.0",
       "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.9.0.tgz",
@@ -3818,6 +3960,11 @@
       "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==",
       "dev": true
     },
+    "node_modules/async-validator": {
+      "version": "4.2.5",
+      "resolved": "https://registry.npmjs.org/async-validator/-/async-validator-4.2.5.tgz",
+      "integrity": "sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg=="
+    },
     "node_modules/asynckit": {
       "version": "0.4.0",
       "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
@@ -5976,6 +6123,11 @@
         "node": ">=0.10"
       }
     },
+    "node_modules/dayjs": {
+      "version": "1.11.3",
+      "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.3.tgz",
+      "integrity": "sha512-xxwlswWOlGhzgQ4TKzASQkUhqERI3egRNqgV4ScR8wlANA/A9tZ7miXa44vTTKEq5l7vWoL5G57bG3zA+Kow0A=="
+    },
     "node_modules/debug": {
       "version": "4.3.4",
       "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
@@ -6544,6 +6696,31 @@
       "integrity": "sha512-t3iFLHVIMhB8jGZ+8ui951nz6Bna5qKfhxezG3wzXdBJ79qFKPsE2chjjVFNqC1ewhfrPQrw9pmVeo4FFpZeQA==",
       "dev": true
     },
+    "node_modules/element-plus": {
+      "version": "2.2.9",
+      "resolved": "https://registry.npmjs.org/element-plus/-/element-plus-2.2.9.tgz",
+      "integrity": "sha512-jYbL0JkCdv95rkT6trZJjCAizLPySa0qcd2cgq+57SKQnCZAcNDDq4GbTuFRnNavdoeCJnuM3HIficTIUpsMOQ==",
+      "dependencies": {
+        "@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": "^8.7.5",
+        "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.1.2"
+      },
+      "peerDependencies": {
+        "vue": "^3.2.0"
+      }
+    },
     "node_modules/elliptic": {
       "version": "6.5.4",
       "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz",
@@ -6723,8 +6900,7 @@
     "node_modules/escape-html": {
       "version": "1.0.3",
       "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
-      "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
-      "dev": true
+      "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="
     },
     "node_modules/escape-string-regexp": {
       "version": "1.0.5",
@@ -10174,8 +10350,22 @@
     "node_modules/lodash": {
       "version": "4.17.21",
       "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
-      "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
-      "dev": true
+      "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
+    },
+    "node_modules/lodash-es": {
+      "version": "4.17.21",
+      "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
+      "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="
+    },
+    "node_modules/lodash-unified": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/lodash-unified/-/lodash-unified-1.0.2.tgz",
+      "integrity": "sha512-OGbEy+1P+UT26CYi4opY4gebD8cWRDxAT6MAObIVQMiqYdxZr1g3QHWCToVsm31x2NkLS4K3+MC2qInaRMa39g==",
+      "peerDependencies": {
+        "@types/lodash-es": "*",
+        "lodash": "*",
+        "lodash-es": "*"
+      }
     },
     "node_modules/lodash.debounce": {
       "version": "4.0.8",
@@ -10342,6 +10532,11 @@
         "node": ">= 4.0.0"
       }
     },
+    "node_modules/memoize-one": {
+      "version": "6.0.0",
+      "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz",
+      "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw=="
+    },
     "node_modules/memory-fs": {
       "version": "0.5.0",
       "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.5.0.tgz",
@@ -10957,6 +11152,11 @@
         "node": ">=4"
       }
     },
+    "node_modules/normalize-wheel-es": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/normalize-wheel-es/-/normalize-wheel-es-1.1.2.tgz",
+      "integrity": "sha512-scX83plWJXYH1J4+BhAuIHadROzxX0UBF3+HuZNY2Ks8BciE7tSTQ+5JhTsvzjaO0/EJdm4JBGrfObKxFf3Png=="
+    },
     "node_modules/npm-run-path": {
       "version": "2.0.2",
       "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz",
@@ -15970,11 +16170,11 @@
       }
     },
     "node_modules/vue-router": {
-      "version": "4.1.2",
-      "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.1.2.tgz",
-      "integrity": "sha512-5BP1qXFncVRwgV/XnqzsKApdMjQPqWIpoUBdL1ynz8HyLxIX/UDAx7Ql2BjmA5CXT/p61JfZvkpiFWFpaqcfag==",
+      "version": "4.0.13",
+      "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.0.13.tgz",
+      "integrity": "sha512-LmXrC+BkDRLak+d5xTMgUYraT3Nj0H/vCbP+7usGvIl9Viqd1UP6AsP0i69pSbn9O0dXK/xCdp4yPw21HqV9Jw==",
       "dependencies": {
-        "@vue/devtools-api": "^6.1.4"
+        "@vue/devtools-api": "^6.0.0"
       },
       "funding": {
         "url": "https://github.com/sponsors/posva"
@@ -18502,6 +18702,30 @@
         "to-fast-properties": "^2.0.0"
       }
     },
+    "@ctrl/tinycolor": {
+      "version": "3.4.1",
+      "resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-3.4.1.tgz",
+      "integrity": "sha512-ej5oVy6lykXsvieQtqZxCOaLT+xD4+QNarq78cIYISHmZXshCvROLudpQN3lfL8G0NL7plMSSK+zlyvCaIJ4Iw=="
+    },
+    "@element-plus/icons-vue": {
+      "version": "2.0.6",
+      "resolved": "https://registry.npmjs.org/@element-plus/icons-vue/-/icons-vue-2.0.6.tgz",
+      "integrity": "sha512-lPpG8hYkjL/Z97DH5Ei6w6o22Z4YdNglWCNYOPcB33JCF2A4wye6HFgSI7hEt9zdLyxlSpiqtgf9XcYU+m5mew==",
+      "requires": {}
+    },
+    "@floating-ui/core": {
+      "version": "0.7.3",
+      "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-0.7.3.tgz",
+      "integrity": "sha512-buc8BXHmG9l82+OQXOFU3Kr2XQx9ys01U/Q9HMIrZ300iLc8HLMgh7dcCqgYzAzf4BkoQvDcXf5Y+CuEZ5JBYg=="
+    },
+    "@floating-ui/dom": {
+      "version": "0.5.4",
+      "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-0.5.4.tgz",
+      "integrity": "sha512-419BMceRLq0RrmTSDxn8hf9R3VCJv2K9PUfugh5JyEFmdjzDo+e8U5EdR8nzKq8Yj1htzLm3b6eQEEam3/rrtg==",
+      "requires": {
+        "@floating-ui/core": "^0.7.3"
+      }
+    },
     "@hapi/address": {
       "version": "2.1.4",
       "resolved": "https://registry.npmjs.org/@hapi/address/-/address-2.1.4.tgz",
@@ -18643,6 +18867,11 @@
         "fastq": "^1.6.0"
       }
     },
+    "@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=="
+    },
     "@soda/friendly-errors-webpack-plugin": {
       "version": "1.8.1",
       "resolved": "https://registry.npmjs.org/@soda/friendly-errors-webpack-plugin/-/friendly-errors-webpack-plugin-1.8.1.tgz",
@@ -18789,6 +19018,19 @@
       "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==",
       "dev": true
     },
+    "@types/lodash": {
+      "version": "4.14.182",
+      "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.182.tgz",
+      "integrity": "sha512-/THyiqyQAP9AfARo4pF+aCGcyiQ94tX/Is2I7HofNRqoYLgN1PBoOWu2/zTA5zMxzP5EFutMtWtGAFRKUe961Q=="
+    },
+    "@types/lodash-es": {
+      "version": "4.17.6",
+      "resolved": "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.6.tgz",
+      "integrity": "sha512-R+zTeVUKDdfoRxpAryaQNRKk3105Rrgx2CFRClIgRGaqDTdjsm8h6IYA8ir584W3ePzkZfst5xIgDwYrlh9HLg==",
+      "requires": {
+        "@types/lodash": "*"
+      }
+    },
     "@types/mime": {
       "version": "1.3.2",
       "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz",
@@ -18875,6 +19117,11 @@
         "source-map": "^0.6.1"
       }
     },
+    "@types/web-bluetooth": {
+      "version": "0.0.14",
+      "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.14.tgz",
+      "integrity": "sha512-5d2RhCard1nQUC3aHcq/gHzWYO6K0WJmAbjO7mQJgCQKtZpgXxv1rOM6O/dBDhDYYVutk1sciOgNSe+5YyfM8A=="
+    },
     "@types/webpack": {
       "version": "4.41.32",
       "resolved": "https://registry.npmjs.org/@types/webpack/-/webpack-4.41.32.tgz",
@@ -19424,7 +19671,8 @@
       "version": "4.5.19",
       "resolved": "https://registry.npmjs.org/@vue/cli-plugin-vuex/-/cli-plugin-vuex-4.5.19.tgz",
       "integrity": "sha512-DUmfdkG3pCdkP7Iznd87RfE9Qm42mgp2hcrNcYQYSru1W1gX2dG/JcW8bxmeGSa06lsxi9LEIc/QD1yPajSCZw==",
-      "dev": true
+      "dev": true,
+      "requires": {}
     },
     "@vue/cli-service": {
       "version": "4.5.19",
@@ -19641,7 +19889,8 @@
       "version": "1.1.2",
       "resolved": "https://registry.npmjs.org/@vue/preload-webpack-plugin/-/preload-webpack-plugin-1.1.2.tgz",
       "integrity": "sha512-LIZMuJk38pk9U9Ur4YzHjlIyMuxPlACdBIHH9/nGYVTsaGKOSnSuELiE8vS9wa+dJpIYspYUOqk+L1Q4pgHQHQ==",
-      "dev": true
+      "dev": true,
+      "requires": {}
     },
     "@vue/reactivity": {
       "version": "3.2.37",
@@ -19702,6 +19951,38 @@
       "integrity": "sha512-Iu8Tbg3f+emIIMmI2ycSI8QcEuAUgPTgHwesDU1eKMLE4YC/c/sFbGc70QgMq31ijRftV0R7vCm9co6rldCeOA==",
       "dev": true
     },
+    "@vueuse/core": {
+      "version": "8.9.2",
+      "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-8.9.2.tgz",
+      "integrity": "sha512-dE3/JgwqIHmmtmRBdZAnq87rZCSFbYVps2t3gWy9Jv/+Qp6sHSSKuPFtwguJVZ2OnaGnB/AMRmx4CuFRxFin3A==",
+      "requires": {
+        "@types/web-bluetooth": "^0.0.14",
+        "@vueuse/metadata": "8.9.2",
+        "@vueuse/shared": "8.9.2",
+        "vue-demi": "*"
+      },
+      "dependencies": {
+        "@vueuse/shared": {
+          "version": "8.9.2",
+          "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-8.9.2.tgz",
+          "integrity": "sha512-s4Nk82oheL5z1GywyGnqjob0MzbAt88olMZa0vgt/p3gcMsT8Ff7+SqmNgEFC6AAs6xiuhOAZpnew9Zs3d90yQ==",
+          "requires": {
+            "vue-demi": "*"
+          }
+        },
+        "vue-demi": {
+          "version": "0.13.4",
+          "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.13.4.tgz",
+          "integrity": "sha512-KP4lq9uSz0KZbaqCllRhnxMV3mYRsRWJfdsAhZyt5bV5O1RTpoeDptBRV9NOa/JgOpfaA9ane88VF7OjWNK/DA==",
+          "requires": {}
+        }
+      }
+    },
+    "@vueuse/metadata": {
+      "version": "8.9.2",
+      "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-8.9.2.tgz",
+      "integrity": "sha512-g2s2BeyeEtJElmMFfFPnM+BTvnt0omniyvz8U18/zXDpQIMGozlNQgHoFeratyMfgVBhH/u2VKzmchChtDsgPQ=="
+    },
     "@webassemblyjs/ast": {
       "version": "1.9.0",
       "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.9.0.tgz",
@@ -19909,7 +20190,8 @@
       "version": "5.3.2",
       "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
       "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
-      "dev": true
+      "dev": true,
+      "requires": {}
     },
     "acorn-walk": {
       "version": "7.2.0",
@@ -19939,13 +20221,15 @@
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-1.0.1.tgz",
       "integrity": "sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ==",
-      "dev": true
+      "dev": true,
+      "requires": {}
     },
     "ajv-keywords": {
       "version": "3.5.2",
       "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
       "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
-      "dev": true
+      "dev": true,
+      "requires": {}
     },
     "alphanum-sort": {
       "version": "1.0.2",
@@ -20179,6 +20463,11 @@
       "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==",
       "dev": true
     },
+    "async-validator": {
+      "version": "4.2.5",
+      "resolved": "https://registry.npmjs.org/async-validator/-/async-validator-4.2.5.tgz",
+      "integrity": "sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg=="
+    },
     "asynckit": {
       "version": "0.4.0",
       "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
@@ -21907,6 +22196,11 @@
         "assert-plus": "^1.0.0"
       }
     },
+    "dayjs": {
+      "version": "1.11.3",
+      "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.3.tgz",
+      "integrity": "sha512-xxwlswWOlGhzgQ4TKzASQkUhqERI3egRNqgV4ScR8wlANA/A9tZ7miXa44vTTKEq5l7vWoL5G57bG3zA+Kow0A=="
+    },
     "debug": {
       "version": "4.3.4",
       "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
@@ -22349,6 +22643,28 @@
       "integrity": "sha512-t3iFLHVIMhB8jGZ+8ui951nz6Bna5qKfhxezG3wzXdBJ79qFKPsE2chjjVFNqC1ewhfrPQrw9pmVeo4FFpZeQA==",
       "dev": true
     },
+    "element-plus": {
+      "version": "2.2.9",
+      "resolved": "https://registry.npmjs.org/element-plus/-/element-plus-2.2.9.tgz",
+      "integrity": "sha512-jYbL0JkCdv95rkT6trZJjCAizLPySa0qcd2cgq+57SKQnCZAcNDDq4GbTuFRnNavdoeCJnuM3HIficTIUpsMOQ==",
+      "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": "^8.7.5",
+        "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.1.2"
+      }
+    },
     "elliptic": {
       "version": "6.5.4",
       "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz",
@@ -22500,8 +22816,7 @@
     "escape-html": {
       "version": "1.0.3",
       "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
-      "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
-      "dev": true
+      "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="
     },
     "escape-string-regexp": {
       "version": "1.0.5",
@@ -25136,8 +25451,18 @@
     "lodash": {
       "version": "4.17.21",
       "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
-      "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
-      "dev": true
+      "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
+    },
+    "lodash-es": {
+      "version": "4.17.21",
+      "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
+      "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="
+    },
+    "lodash-unified": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/lodash-unified/-/lodash-unified-1.0.2.tgz",
+      "integrity": "sha512-OGbEy+1P+UT26CYi4opY4gebD8cWRDxAT6MAObIVQMiqYdxZr1g3QHWCToVsm31x2NkLS4K3+MC2qInaRMa39g==",
+      "requires": {}
     },
     "lodash.debounce": {
       "version": "4.0.8",
@@ -25276,6 +25601,11 @@
         "fs-monkey": "^1.0.3"
       }
     },
+    "memoize-one": {
+      "version": "6.0.0",
+      "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz",
+      "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw=="
+    },
     "memory-fs": {
       "version": "0.5.0",
       "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.5.0.tgz",
@@ -25799,6 +26129,11 @@
         "sort-keys": "^1.0.0"
       }
     },
+    "normalize-wheel-es": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/normalize-wheel-es/-/normalize-wheel-es-1.1.2.tgz",
+      "integrity": "sha512-scX83plWJXYH1J4+BhAuIHadROzxX0UBF3+HuZNY2Ks8BciE7tSTQ+5JhTsvzjaO0/EJdm4JBGrfObKxFf3Png=="
+    },
     "npm-run-path": {
       "version": "2.0.2",
       "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz",
@@ -29848,11 +30183,11 @@
       }
     },
     "vue-router": {
-      "version": "4.1.2",
-      "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.1.2.tgz",
-      "integrity": "sha512-5BP1qXFncVRwgV/XnqzsKApdMjQPqWIpoUBdL1ynz8HyLxIX/UDAx7Ql2BjmA5CXT/p61JfZvkpiFWFpaqcfag==",
+      "version": "4.0.13",
+      "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.0.13.tgz",
+      "integrity": "sha512-LmXrC+BkDRLak+d5xTMgUYraT3Nj0H/vCbP+7usGvIl9Viqd1UP6AsP0i69pSbn9O0dXK/xCdp4yPw21HqV9Jw==",
       "requires": {
-        "@vue/devtools-api": "^6.1.4"
+        "@vue/devtools-api": "^6.0.0"
       }
     },
     "vue-style-loader": {

+ 4 - 1
package.json

@@ -8,9 +8,12 @@
     "lint": "vue-cli-service lint"
   },
   "dependencies": {
+    "@element-plus/icons-vue": "^2.0.6",
     "core-js": "^3.6.5",
+    "element-plus": "^2.2.9",
+    "lodash": "^4.17.21",
     "vue": "^3.0.0",
-    "vue-router": "^4.0.0-0"
+    "vue-router": "^4.0.13"
   },
   "devDependencies": {
     "@typescript-eslint/eslint-plugin": "^4.18.0",

+ 16 - 22
src/App.vue

@@ -1,30 +1,24 @@
 <template>
-  <div id="nav">
-    <router-link to="/">Home</router-link> |
-    <router-link to="/about">About</router-link>
-  </div>
   <router-view />
 </template>
 
 <style lang="scss">
-#app {
-  font-family: Avenir, Helvetica, Arial, sans-serif;
-  -webkit-font-smoothing: antialiased;
-  -moz-osx-font-smoothing: grayscale;
-  text-align: center;
-  color: #2c3e50;
+@import './assets/css/main.css';
+@import './assets/css/color-dark.css';
+body {
+  margin: 0;
 }
-
-#nav {
-  padding: 30px;
-
-  a {
-    font-weight: bold;
-    color: #2c3e50;
-
-    &.router-link-exact-active {
-      color: #42b983;
-    }
-  }
+.w_1200 {
+  width: 1200px;
+  margin: 0 auto;
+}
+p {
+  margin: 0;
+  padding: 0;
+}
+.textOver {
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
 }
 </style>

+ 28 - 0
src/assets/css/color-dark.css

@@ -0,0 +1,28 @@
+.header{
+    background-color: #242f42;
+}
+.login-wrap{
+    background: #324157;
+}
+.plugins-tips{
+    background: #eef1f6;
+}
+.plugins-tips a{
+    color: #20a0ff;
+}
+.el-upload--text em {
+    color: #20a0ff;
+}
+.pure-button{
+    background: #20a0ff;
+}
+.tags-li.active {
+    border: 1px solid #409EFF;
+    background-color: #409EFF;
+}
+.message-title{
+    color: #20a0ff;
+}
+.collapse-btn:hover{
+    background: rgb(40,52,70);
+}

+ 4 - 0
src/assets/css/icon.css

@@ -0,0 +1,4 @@
+
+    [class*=" el-icon-lx"], [class^=el-icon-lx] {
+        font-family: lx-iconfont!important;
+    }

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

@@ -0,0 +1,177 @@
+* {
+    margin: 0;
+    padding: 0;
+}
+
+html,
+body,
+#app,
+.wrapper {
+    width: 100%;
+    height: 100%;
+    overflow: hidden;
+}
+
+body {
+    font-family: 'PingFang SC', "Helvetica Neue", Helvetica, "microsoft yahei", arial, STHeiTi, sans-serif;
+}
+
+a {
+    text-decoration: none
+}
+
+
+.content-box {
+    position: absolute;
+    left: 200px;
+    right: 0;
+    top: 60px;
+    bottom: 0;
+    padding-bottom: 30px;
+    -webkit-transition: left .3s ease-in-out;
+    transition: left .3s ease-in-out;
+    background: #f0f0f0;
+}
+
+.content {
+    width: auto;
+    height: 100%;
+    padding: 10px;
+    overflow-y: scroll;
+    box-sizing: border-box;
+}
+
+.content-collapse {
+    left: 65px;
+}
+
+.container {
+    padding: 30px;
+    background: #fff;
+    border: 1px solid #ddd;
+    border-radius: 5px;
+}
+
+.crumbs {
+    margin: 10px 0;
+}
+
+.el-table th {
+    background-color: #f5f7fa !important;
+}
+
+.pagination {
+    margin: 20px 0;
+    text-align: right;
+}
+
+.plugins-tips {
+    padding: 20px 10px;
+    margin-bottom: 20px;
+}
+
+.el-button+.el-tooltip {
+    margin-left: 10px;
+}
+
+.el-table tr:hover {
+    background: #f6faff;
+}
+
+.mgb20 {
+    margin-bottom: 20px;
+}
+
+.move-enter-active,
+.move-leave-active {
+    transition: opacity .5s;
+}
+
+.move-enter,
+.move-leave {
+    opacity: 0;
+}
+
+/*BaseForm*/
+
+.form-box {
+    width: 600px;
+}
+
+.form-box .line {
+    text-align: center;
+}
+
+.el-time-panel__content::after,
+.el-time-panel__content::before {
+    margin-top: -7px;
+}
+
+.el-time-spinner__wrapper .el-scrollbar__wrap:not(.el-scrollbar__wrap--hidden-default) {
+    padding-bottom: 0;
+}
+
+/*Upload*/
+
+.pure-button {
+    width: 150px;
+    height: 40px;
+    line-height: 40px;
+    text-align: center;
+    color: #fff;
+    border-radius: 3px;
+}
+
+.g-core-image-corp-container .info-aside {
+    height: 45px;
+}
+
+.el-upload--text {
+    background-color: #fff;
+    border: 1px dashed #d9d9d9;
+    border-radius: 6px;
+    box-sizing: border-box;
+    width: 360px;
+    height: 180px;
+    text-align: center;
+    cursor: pointer;
+    position: relative;
+    overflow: hidden;
+}
+
+.el-upload--text .el-icon-upload {
+    font-size: 67px;
+    color: #97a8be;
+    margin: 40px 0 16px;
+    line-height: 50px;
+}
+
+.el-upload--text {
+    color: #97a8be;
+    font-size: 14px;
+    text-align: center;
+}
+
+.el-upload--text em {
+    font-style: normal;
+}
+
+/*VueEditor*/
+
+.ql-container {
+    min-height: 400px;
+}
+
+.ql-snow .ql-tooltip {
+    transform: translateX(117.5px) translateY(10px) !important;
+}
+
+.editor-btn {
+    margin-top: 20px;
+}
+
+/*markdown*/
+
+.v-note-wrapper .v-note-panel {
+    min-height: 500px;
+}

+ 29 - 0
src/assets/css/theme-green/color-green.css

@@ -0,0 +1,29 @@
+.header{
+    background-color: #07c4a8;
+}
+.login-wrap{
+    background: rgba(56, 157, 170, 0.82);;
+}
+.plugins-tips{
+    background: #f2f2f2;
+}
+.plugins-tips a{
+    color: #00d1b2;
+}
+.el-upload--text em {
+    color: #00d1b2;
+}
+.pure-button{
+    background: #00d1b2;
+}
+.pagination > .active > a, .pagination > .active > a:hover, .pagination > .active > a:focus, .pagination > .active > span, .pagination > .active > span:hover, .pagination > .active > span:focus {
+    background-color: #00d1b2 !important;
+    border-color: #00d1b2 !important;
+}
+.tags-li.active {
+    border: 1px solid #00d1b2;
+    background-color: #00d1b2;
+}
+.collapse-btn:hover{
+    background: #00d1b2;
+}

二進制
src/assets/css/theme-green/fonts/element-icons.ttf


二進制
src/assets/css/theme-green/fonts/element-icons.woff


文件差異過大導致無法顯示
+ 1 - 0
src/assets/css/theme-green/index.css


+ 94 - 0
src/layout/FHead.vue

@@ -0,0 +1,94 @@
+<template>
+  <el-row>
+    <el-col :span="24" class="main header">
+      <el-col :span="24" class="one">
+        <el-row>
+          <el-col :span="12" class="left">
+            <span @click="collapseChage">
+              <el-icon v-if="!collapse"><Fold /></el-icon>
+              <el-icon v-else><Expand /></el-icon>
+            </span>
+            <!-- {{ siteInfo.zhTitle }}- -->
+            <span>管理中心</span>
+          </el-col>
+          <el-col :span="12" class="right">
+            <el-icon color="#ffffff" :size="30" title="刷新当前页面" @click="refresh()" class="icon__header"><Refresh /></el-icon>
+            <el-icon color="#ffffff" :size="30"><UserFilled /></el-icon>
+            <span>{{ user.name || '游客' }}</span>
+            <el-button type="danger" size="small" @click="logout">退出登录</el-button>
+          </el-col>
+        </el-row>
+      </el-col>
+    </el-col>
+  </el-row>
+</template>
+
+<script lang="ts">
+import { defineComponent } from 'vue';
+export default defineComponent({
+  name: 'fHead',
+  components: {},
+});
+</script>
+
+<script setup lang="ts">
+import { User } from '../types/user';
+import { reactive, defineEmits, defineProps } from 'vue';
+interface IModels {
+  collapse: boolean;
+}
+const props = defineProps<IModels>();
+const emit = defineEmits(['refresh', 'update:collapse']);
+const refresh = () => {
+  emit('refresh');
+};
+// 侧边栏折叠
+const collapseChage = () => {
+  emit('update:collapse', !props.collapse);
+};
+const uData = { name: 'test', _id: '123', age: '13' };
+let user = reactive(new User(uData));
+const logout = () => {
+  console.log('line 48 in function:');
+};
+</script>
+
+<style lang="scss" scoped>
+.main {
+  .one {
+    height: 60px;
+    border-bottom: 1px solid #f1f1f1;
+    padding: 0 10px;
+    .left {
+      line-height: 60px;
+      span {
+        display: inline-block;
+        margin: 0 10px;
+        font-size: 22px;
+        color: #ffffff;
+        font-weight: bold;
+        font-family: cursive;
+      }
+    }
+    .right {
+      text-align: right;
+      line-height: 60px;
+      i {
+        position: relative;
+        top: 5px;
+        margin: 0px 15px;
+        font-size: 30px;
+        color: #fff;
+      }
+      span {
+        color: #fff;
+        font-size: 16px;
+        padding: 0 15px 0 0px;
+      }
+    }
+  }
+}
+.icon__header {
+  cursor: pointer;
+}
+</style>

+ 79 - 0
src/layout/FSidebar.vue

@@ -0,0 +1,79 @@
+<template>
+  <div id="FSidebar">
+    <el-row class="sidebar">
+      <el-col :span="24" class="main">
+        <el-col :span="24" class="one">
+          <el-menu
+            class="sidebar-el-menu"
+            :default-active="onRoutes"
+            :collapse="collapse"
+            background-color="#324157"
+            text-color="#bfcbd9"
+            active-text-color="#20a0ff"
+            unique-opened
+            @select="menuSelect"
+          >
+            <template v-for="item in list" :key="`menuItem${item.id}`">
+              <menu-item :item="item"></menu-item>
+            </template>
+          </el-menu>
+        </el-col>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script lang="ts">
+import { defineComponent } from 'vue';
+export default defineComponent({
+  name: 'FSidebar',
+  components: {},
+});
+</script>
+
+<script setup lang="ts">
+import MenuItem from './sidebar/MenuItem.vue';
+import _ from 'lodash';
+import { system } from './sidebar/menu';
+import { ref, reactive, defineProps, computed } from 'vue';
+import { useRouter, useRoute } from 'vue-router';
+let props = defineProps({
+  collapse: { type: Boolean },
+});
+let route = useRoute();
+let router = useRouter();
+let onRoutes = computed(() => route.path);
+let list = system;
+let menuSelect = (index: string) => {
+  router.push(index);
+};
+</script>
+
+<style lang="scss" scoped>
+.sidebar {
+  display: block;
+  position: absolute;
+  left: 0;
+  top: 60px;
+  bottom: 0;
+  overflow-y: scroll;
+  background-color: rgb(50, 65, 87);
+}
+.sidebar::-webkit-scrollbar {
+  width: 0;
+}
+.sidebar-el-menu:not(.el-menu--collapse) {
+  width: 200px;
+}
+.sidebar > ul {
+  height: 100%;
+}
+.main {
+  .one {
+    .iconfont {
+      font-size: 18px;
+      margin: 0 5px 0 0;
+    }
+  }
+}
+</style>

+ 152 - 0
src/layout/FTags.vue

@@ -0,0 +1,152 @@
+<template>
+  <div id="FTags">
+    <el-row>
+      <el-col :span="24" class="main">
+        <el-col :span="24" class="one">
+          <el-col :span="23" class="left">
+            <!-- #region 标签显示 -->
+            <ul>
+              <li class="tags-li" v-for="(item, index) in tagsList" :class="{ active: isActive(item.path) }" :key="index">
+                <router-link :to="item.path" class="tags-li-title">
+                  {{ item.title }}
+                </router-link>
+                <span class="tags-li-icon" @click="closeTags(index)">
+                  <el-icon><Close /></el-icon>
+                </span>
+              </li>
+            </ul>
+            <!-- #endregion -->
+          </el-col>
+          <el-col :span="1" class="right">
+            <!-- <el-dropdown @command="handleTags">
+              <el-button size="mini" type="primary"> 标签选项<i class="el-icon-arrow-down el-icon--right"></i> </el-button>
+              <el-dropdown-menu size="small" slot="dropdown">
+                <el-dropdown-item command="other">关闭其他</el-dropdown-item>
+                <el-dropdown-item command="all">关闭所有</el-dropdown-item>
+              </el-dropdown-menu>
+            </el-dropdown> -->
+          </el-col>
+        </el-col>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script lang="ts">
+import { defineComponent } from 'vue';
+export default defineComponent({
+  name: 'FTags',
+  components: {},
+});
+</script>
+
+<script setup lang="ts">
+import { ref, reactive, watch } from 'vue';
+import { useRoute, useRouter } from 'vue-router';
+import _ from 'lodash';
+
+let route = useRoute();
+let router = useRouter();
+// 判断是否是当前路由(包含参数)
+let isActive = (path: string) => path === route.fullPath;
+
+// #region 标签部分
+// 标签接口
+interface tag {
+  title: string;
+  path: string;
+}
+// 定义标签列表
+let tagsList = reactive([] as tag[]);
+// 设置标签
+let setTags = (route: Record<string, any>) => {
+  const { fullPath: path, meta } = route;
+  const { title } = meta;
+  const tag: tag = { title, path };
+  const r = tagsList.some((f) => _.isEqual(tag, f));
+  if (!r) tagsList.push(tag);
+};
+// 监听路由变化,设置标签
+watch(route, (val) => setTags(val), { deep: true, immediate: true });
+// 关闭标签
+let closeTags = (index: number) => {
+  tagsList.splice(index, 1);
+  if (tagsList.length === 0) router.push({ name: 'Index' });
+};
+// #endregion
+
+console.log(123);
+</script>
+
+<style lang="scss" scoped>
+.main {
+  .one {
+    position: relative;
+    height: 30px;
+    overflow: hidden;
+    background: #fff;
+    padding-right: 120px;
+    box-shadow: 0 5px 10px #ddd;
+    .left {
+      ul {
+        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;
+      }
+      .tags-li:not(.active):hover {
+        background: #f8f8f8;
+      }
+      .tags-li.active {
+        color: #fff;
+      }
+      .tags-li-title {
+        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;
+      }
+      .tags-li.active {
+        border: 1px solid #409eff;
+        background-color: #409eff;
+      }
+    }
+    .right {
+      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>

+ 58 - 0
src/layout/home.vue

@@ -0,0 +1,58 @@
+<template>
+  <div id="home">
+    <el-container>
+      <el-header style="padding: 0">
+        <f-header @refresh="refresh" v-model:collapse="collapse"></f-header>
+      </el-header>
+      <el-container>
+        <el-aside width="200px">
+          <f-sidebar :collapse="collapse" />
+        </el-aside>
+        <el-main>
+          <div class="content-box" :class="{ 'content-collapse': collapse }">
+            <f-tags />
+            <el-col :span="24" class="content">
+              <transition name="move" mode="out-in">
+                <el-row>
+                  <!-- <breadcrumb :breadcrumbTitle="this.$route.meta.title"></breadcrumb> -->
+                  <el-col :span="24" class="container" style="padding: 15px"><router-view :key="viewKey"></router-view></el-col>
+                </el-row>
+              </transition>
+              <el-backtop target=".content"></el-backtop>
+            </el-col>
+          </div>
+        </el-main>
+      </el-container>
+    </el-container>
+  </div>
+</template>
+
+<script lang="ts">
+import { defineComponent } from 'vue';
+export default defineComponent({
+  name: 'home',
+  components: {},
+  props: {},
+  data() {
+    return {
+      // collapse: false,
+      // viewKey: new Date().getTime(),
+    };
+  },
+  methods: {},
+});
+</script>
+
+<script setup lang="ts">
+import FHeader from './FHead.vue';
+import FSidebar from './FSidebar.vue';
+import FTags from './FTags.vue';
+import { ref } from 'vue';
+let collapse = ref(false);
+let viewKey = ref(new Date().getTime());
+const refresh = () => {
+  viewKey.value = new Date().getTime();
+};
+</script>
+
+<style lang="scss" scoped></style>

+ 32 - 0
src/layout/sidebar/MenuItem.vue

@@ -0,0 +1,32 @@
+<template>
+  <el-sub-menu :index="item.id" :key="item.id" v-if="item.children">
+    <template v-slot:title>
+      <i :class="['iconfont', item.icon]"></i>
+      <span>{{ item.name }}</span>
+    </template>
+    <template v-for="ci in item.children" :key="`menuItem${ci.id}`">
+      <menu-item :item="ci"></menu-item>
+    </template>
+  </el-sub-menu>
+  <el-menu-item class="first" :index="item.path" :key="item.path" v-else>
+    <i :class="['iconfont', item.icon]"></i>
+    <template #title>{{ item.name }}</template>
+  </el-menu-item>
+</template>
+
+<script lang="ts">
+import { defineComponent } from 'vue';
+export default defineComponent({
+  name: 'MenuItem',
+  components: {},
+});
+</script>
+
+<script setup lang="ts">
+import { ref, reactive, defineProps } from 'vue';
+let props = defineProps({
+  item: { type: Object, required: true },
+});
+</script>
+
+<style lang="scss" scoped></style>

+ 12 - 0
src/layout/sidebar/menu.ts

@@ -0,0 +1,12 @@
+export const system = [
+  { icon: 'icon-shouye', path: '/', name: '首页', id: '1' },
+  {
+    icon: 'icon-setting',
+    name: '系统设置',
+    id: '2',
+    children: [
+      { icon: 'el-icon-setting', name: '菜单管理', id: '3', path: '/admin/menu' },
+      { icon: 'el-icon-setting', name: '角色管理', id: '4', path: '/admin/role' },
+    ],
+  },
+];

+ 13 - 4
src/main.ts

@@ -1,5 +1,14 @@
-import { createApp } from "vue";
-import App from "./App.vue";
-import router from "./router";
+import { createApp } from 'vue';
+import App from './App.vue';
+import router from './router';
+import ElementPlus from 'element-plus';
+import 'element-plus/dist/index.css';
+import * as ElementPlusIconsVue from '@element-plus/icons-vue';
 
-createApp(App).use(router).mount("#app");
+const app = createApp(App);
+app.use(router);
+app.use(ElementPlus);
+for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
+  app.component(key, component);
+}
+app.mount('#app');

+ 25 - 14
src/router/index.ts

@@ -1,20 +1,31 @@
-import { createRouter, createWebHistory, RouteRecordRaw } from "vue-router";
-import Home from "../views/Home.vue";
+import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router';
+import Home from '../views/Home.vue';
 
 const routes: Array<RouteRecordRaw> = [
   {
-    path: "/",
-    name: "Home",
-    component: Home,
-  },
-  {
-    path: "/about",
-    name: "About",
-    // route level code-splitting
-    // this generates a separate chunk (about.[hash].js) for this route
-    // which is lazy-loaded when the route is visited.
-    component: () =>
-      import(/* webpackChunkName: "about" */ "../views/About.vue"),
+    path: '/',
+    name: 'Home',
+    component: () => import(/* webpackChunkName: "frame" */ '../layout/home.vue'),
+    children: [
+      {
+        path: '/',
+        name: 'Index',
+        meta: { title: '首页' },
+        component: () => import(/* webpackChunkName: "index" */ '../views/admin/index/index.vue'),
+      },
+      {
+        path: '/admin/menu',
+        name: 'Menu',
+        meta: { title: '菜单管理' },
+        component: () => import(/* webpackChunkName: "menu" */ '../views/admin/menu/index.vue'),
+      },
+      {
+        path: '/admin/role',
+        name: 'Role',
+        meta: { title: '角色管理' },
+        component: () => import(/* webpackChunkName: "role" */ '../views/admin/role/index.vue'),
+      },
+    ],
   },
 ];
 

+ 17 - 0
src/types/user.ts

@@ -0,0 +1,17 @@
+// 接口负责声明
+interface IUser {
+  name: string;
+  _id: string;
+}
+
+// 类负责实现
+class User implements IUser {
+  name: string;
+  _id: string;
+  constructor(data: Record<string, string>) {
+    this.name = data.name;
+    this._id = data._id;
+  }
+}
+
+export { IUser, User };

+ 4 - 4
src/views/Home.vue

@@ -6,13 +6,13 @@
 </template>
 
 <script lang="ts">
-import { defineComponent } from "vue";
-import HelloWorld from "@/components/HelloWorld.vue"; // @ is an alias to /src
-
+import { defineComponent } from 'vue';
+import HelloWorld from '@/components/HelloWorld.vue'; // @ is an alias to /src
 export default defineComponent({
-  name: "Home",
+  name: 'Home',
   components: {
     HelloWorld,
   },
+  methods: {},
 });
 </script>

+ 19 - 0
src/views/admin/index/index.vue

@@ -0,0 +1,19 @@
+<template>
+  <div id="index">
+    <p>index</p>
+  </div>
+</template>
+
+<script lang="ts">
+import { defineComponent } from 'vue';
+export default defineComponent({
+  name: 'index',
+  components: {},
+});
+</script>
+
+<script setup lang="ts">
+import { ref, reactive } from 'vue';
+</script>
+
+<style lang="scss" scoped></style>

+ 19 - 0
src/views/admin/menu/index.vue

@@ -0,0 +1,19 @@
+<template>
+  <div id="index">
+    <p>menu</p>
+  </div>
+</template>
+
+<script lang="ts">
+import { defineComponent } from 'vue';
+export default defineComponent({
+  name: 'index',
+  components: {},
+});
+</script>
+
+<script setup lang="ts">
+import { ref, reactive } from 'vue';
+</script>
+
+<style lang="scss" scoped></style>

+ 19 - 0
src/views/admin/role/index.vue

@@ -0,0 +1,19 @@
+<template>
+  <div id="index">
+    <p>role</p>
+  </div>
+</template>
+
+<script lang="ts">
+import { defineComponent } from 'vue';
+export default defineComponent({
+  name: 'index',
+  components: {},
+});
+</script>
+
+<script setup lang="ts">
+import { ref, reactive } from 'vue';
+</script>
+
+<style lang="scss" scoped></style>

+ 2 - 1
tsconfig.json

@@ -12,7 +12,8 @@
     "sourceMap": true,
     "baseUrl": ".",
     "types": [
-      "webpack-env"
+      "webpack-env",
+      "element-plus/global"
     ],
     "paths": {
       "@/*": [