Browse Source

封装axios请求,封装菜单栏、内容主体,封装pinia状态存储

@sun-chaoqun 2 years ago
parent
commit
ff8aa86e0d

+ 41 - 34
.eslintrc.cjs

@@ -1,36 +1,43 @@
 module.exports = {
-    "env": {
-        "browser": true,
-        "es2021": true,
-        "node": true
-    },
-    "extends": [
-        "eslint:recommended",
-        "plugin:vue/vue3-essential",
-        "plugin:@typescript-eslint/recommended",
-        'plugin:prettier/recommended', 
-        'eslint-config-prettier'
-    ],
-    "overrides": [
-    ],
-    "parser": "vue-eslint-parser",
-    "parserOptions": {
-        "ecmaVersion": "latest",
-        "sourceType": "module",
-        "parser": "@typescript-eslint/parser",
-        // "parser": 'vue-eslint-parser'
-    },
-    "plugins": [
-        "vue",
-        'prettier',
-        "@typescript-eslint"
-    ],
-    'globals': {
-        defineProps: 'readonly',
-        defineEmits: 'readonly',
-        defineExpose: 'readonly',
-        withDefaults: 'readonly'
-    },
-    "rules": {
-    }
+  env: {
+    browser: true,
+    es2021: true,
+    node: true
+  },
+  extends: [
+    // 'eslint:recommended',
+    'plugin:vue/vue3-essential',
+    'plugin:@typescript-eslint/recommended',
+    'plugin:prettier/recommended',
+    'prettier',
+    'eslint-config-prettier'
+  ],
+  overrides: [],
+  parser: 'vue-eslint-parser',
+  parserOptions: {
+    ecmaVersion: 'latest',
+    sourceType: 'module',
+    parser: '@typescript-eslint/parser'
+    // "parser": 'vue-eslint-parser'
+  },
+  plugins: ['vue', 'prettier', '@typescript-eslint'],
+  globals: {
+    defineProps: 'readonly',
+    defineEmits: 'readonly',
+    defineExpose: 'readonly',
+    withDefaults: 'readonly'
+  },
+  rules: {
+    'vue/multi-word-component-names': 'off',
+    'no-var': 'error',
+    'no-multiple-empty-lines': ['error', { max: 1 }],
+    'no-use-before-define': 'off',
+    'prefer-const': 'off',
+    'no-irregular-whitespace': 'off',
+
+    '@typescript-eslint/no-unused-vars': 'error',
+    '@typescript-eslint/prefer-ts-expect-error': 'error',
+    '@typescript-eslint/no-inferrable-types': 'off',
+    '@typescript-eslint/no-explicit-any': 'off' // 禁止使用 any 类型
+  }
 }

+ 0 - 0
.prettierrc.js → .prettierrc.cjs


+ 8 - 0
components.d.ts

@@ -9,9 +9,12 @@ export {}
 
 declare module '@vue/runtime-core' {
   export interface GlobalComponents {
+    404: typeof import('./src/components/ErrorMessage/404.vue')['default']
     ElAside: typeof import('element-plus/es')['ElAside']
     ElButton: typeof import('element-plus/es')['ElButton']
+    ElCard: typeof import('element-plus/es')['ElCard']
     ElContainer: typeof import('element-plus/es')['ElContainer']
+    ElEmpty: typeof import('element-plus/es')['ElEmpty']
     ElForm: typeof import('element-plus/es')['ElForm']
     ElFormItem: typeof import('element-plus/es')['ElFormItem']
     ElHeader: typeof import('element-plus/es')['ElHeader']
@@ -21,10 +24,15 @@ declare module '@vue/runtime-core' {
     ElMenu: typeof import('element-plus/es')['ElMenu']
     ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
     ElMenuItemGroup: typeof import('element-plus/es')['ElMenuItemGroup']
+    ElPagination: typeof import('element-plus/es')['ElPagination']
     ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
     ElSubMenu: typeof import('element-plus/es')['ElSubMenu']
+    ElTable: typeof import('element-plus/es')['ElTable']
+    ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
     HelloWorld: typeof import('./src/components/HelloWorld.vue')['default']
+    Pagination: typeof import('./src/components/TableBase/components/Pagination.vue')['default']
     RouterLink: typeof import('vue-router')['RouterLink']
     RouterView: typeof import('vue-router')['RouterView']
+    TableBase: typeof import('./src/components/TableBase/index.vue')['default']
   }
 }

+ 213 - 7
package-lock.json

@@ -9,7 +9,10 @@
       "version": "0.0.0",
       "dependencies": {
         "@element-plus/icons-vue": "^2.1.0",
+        "axios": "^1.3.4",
         "element-plus": "^2.3.0",
+        "nprogress": "^0.2.0",
+        "pinia": "^2.0.33",
         "vue": "^3.2.45",
         "vue-router": "^4.1.6"
       },
@@ -24,7 +27,6 @@
         "eslint-config-prettier": "^8.7.0",
         "eslint-plugin-prettier": "^4.2.1",
         "eslint-plugin-vue": "^9.9.0",
-        "nprogress": "^0.2.0",
         "prettier": "^2.8.4",
         "sass": "^1.59.3",
         "typescript": "^4.9.3",
@@ -1718,6 +1720,21 @@
       "resolved": "https://registry.npmmirror.com/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.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz",
+      "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
+    },
+    "node_modules/axios": {
+      "version": "1.3.4",
+      "resolved": "https://registry.npmmirror.com/axios/-/axios-1.3.4.tgz",
+      "integrity": "sha512-toYm+Bsyl6VC5wSkfkbbNB6ROv7KY93PEBBL6xyDczaIHasAiv4wPqQ/c4RjoQzipxRD2W5g21cOqQulZ7rHwQ==",
+      "dependencies": {
+        "follow-redirects": "^1.15.0",
+        "form-data": "^4.0.0",
+        "proxy-from-env": "^1.1.0"
+      }
+    },
     "node_modules/balanced-match": {
       "version": "1.0.2",
       "resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz",
@@ -1879,6 +1896,17 @@
       "integrity": "sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==",
       "dev": true
     },
+    "node_modules/combined-stream": {
+      "version": "1.0.8",
+      "resolved": "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz",
+      "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+      "dependencies": {
+        "delayed-stream": "~1.0.0"
+      },
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
     "node_modules/commander": {
       "version": "8.3.0",
       "resolved": "https://registry.npmmirror.com/commander/-/commander-8.3.0.tgz",
@@ -2002,6 +2030,14 @@
       "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
       "dev": true
     },
+    "node_modules/delayed-stream": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz",
+      "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
+      "engines": {
+        "node": ">=0.4.0"
+      }
+    },
     "node_modules/dir-glob": {
       "version": "3.0.1",
       "resolved": "https://registry.npmmirror.com/dir-glob/-/dir-glob-3.0.1.tgz",
@@ -2581,6 +2617,32 @@
       "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==",
       "dev": true
     },
+    "node_modules/follow-redirects": {
+      "version": "1.15.2",
+      "resolved": "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.15.2.tgz",
+      "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==",
+      "engines": {
+        "node": ">=4.0"
+      },
+      "peerDependenciesMeta": {
+        "debug": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/form-data": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmmirror.com/form-data/-/form-data-4.0.0.tgz",
+      "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
+      "dependencies": {
+        "asynckit": "^0.4.0",
+        "combined-stream": "^1.0.8",
+        "mime-types": "^2.1.12"
+      },
+      "engines": {
+        "node": ">= 6"
+      }
+    },
     "node_modules/fs-extra": {
       "version": "10.1.0",
       "resolved": "https://registry.npmmirror.com/fs-extra/-/fs-extra-10.1.0.tgz",
@@ -3126,6 +3188,25 @@
         "node": ">=8.6"
       }
     },
+    "node_modules/mime-db": {
+      "version": "1.52.0",
+      "resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz",
+      "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/mime-types": {
+      "version": "2.1.35",
+      "resolved": "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz",
+      "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+      "dependencies": {
+        "mime-db": "1.52.0"
+      },
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
     "node_modules/minimatch": {
       "version": "6.2.0",
       "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-6.2.0.tgz",
@@ -3234,8 +3315,7 @@
     "node_modules/nprogress": {
       "version": "0.2.0",
       "resolved": "https://registry.npmmirror.com/nprogress/-/nprogress-0.2.0.tgz",
-      "integrity": "sha512-I19aIingLgR1fmhftnbWWO3dXc0hSxqHQHQb3H8m+K3TnEn/iSeTZZOyvKXWqQESMwuUVnatlCnZdLBZZt2VSA==",
-      "dev": true
+      "integrity": "sha512-I19aIingLgR1fmhftnbWWO3dXc0hSxqHQHQb3H8m+K3TnEn/iSeTZZOyvKXWqQESMwuUVnatlCnZdLBZZt2VSA=="
     },
     "node_modules/nth-check": {
       "version": "2.1.1",
@@ -3390,6 +3470,50 @@
         "node": ">=8.6"
       }
     },
+    "node_modules/pinia": {
+      "version": "2.0.33",
+      "resolved": "https://registry.npmmirror.com/pinia/-/pinia-2.0.33.tgz",
+      "integrity": "sha512-HOj1yVV2itw6rNIrR2f7+MirGNxhORjrULL8GWgRwXsGSvEqIQ+SE0MYt6cwtpegzCda3i+rVTZM+AM7CG+kRg==",
+      "dependencies": {
+        "@vue/devtools-api": "^6.5.0",
+        "vue-demi": "*"
+      },
+      "peerDependencies": {
+        "@vue/composition-api": "^1.4.0",
+        "typescript": ">=4.4.4",
+        "vue": "^2.6.14 || ^3.2.0"
+      },
+      "peerDependenciesMeta": {
+        "@vue/composition-api": {
+          "optional": true
+        },
+        "typescript": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/pinia/node_modules/vue-demi": {
+      "version": "0.13.11",
+      "resolved": "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.13.11.tgz",
+      "integrity": "sha512-IR8HoEEGM65YY3ZJYAjMlKygDQn25D5ajNFNoKh9RSDMQtlzCxtfQjdQgv9jjK+m3377SsJXY8ysq8kLCZL25A==",
+      "hasInstallScript": true,
+      "bin": {
+        "vue-demi-fix": "bin/vue-demi-fix.js",
+        "vue-demi-switch": "bin/vue-demi-switch.js"
+      },
+      "engines": {
+        "node": ">=12"
+      },
+      "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/pkg-types": {
       "version": "1.0.2",
       "resolved": "https://registry.npmmirror.com/pkg-types/-/pkg-types-1.0.2.tgz",
@@ -3466,6 +3590,11 @@
         "node": ">=6.0.0"
       }
     },
+    "node_modules/proxy-from-env": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmmirror.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
+      "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
+    },
     "node_modules/punycode": {
       "version": "2.3.0",
       "resolved": "https://registry.npmmirror.com/punycode/-/punycode-2.3.0.tgz",
@@ -3826,7 +3955,7 @@
       "version": "4.9.5",
       "resolved": "https://registry.npmmirror.com/typescript/-/typescript-4.9.5.tgz",
       "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==",
-      "dev": true,
+      "devOptional": true,
       "bin": {
         "tsc": "bin/tsc",
         "tsserver": "bin/tsserver"
@@ -5571,6 +5700,21 @@
       "resolved": "https://registry.npmmirror.com/async-validator/-/async-validator-4.2.5.tgz",
       "integrity": "sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg=="
     },
+    "asynckit": {
+      "version": "0.4.0",
+      "resolved": "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz",
+      "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
+    },
+    "axios": {
+      "version": "1.3.4",
+      "resolved": "https://registry.npmmirror.com/axios/-/axios-1.3.4.tgz",
+      "integrity": "sha512-toYm+Bsyl6VC5wSkfkbbNB6ROv7KY93PEBBL6xyDczaIHasAiv4wPqQ/c4RjoQzipxRD2W5g21cOqQulZ7rHwQ==",
+      "requires": {
+        "follow-redirects": "^1.15.0",
+        "form-data": "^4.0.0",
+        "proxy-from-env": "^1.1.0"
+      }
+    },
     "balanced-match": {
       "version": "1.0.2",
       "resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz",
@@ -5703,6 +5847,14 @@
       "integrity": "sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==",
       "dev": true
     },
+    "combined-stream": {
+      "version": "1.0.8",
+      "resolved": "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz",
+      "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+      "requires": {
+        "delayed-stream": "~1.0.0"
+      }
+    },
     "commander": {
       "version": "8.3.0",
       "resolved": "https://registry.npmmirror.com/commander/-/commander-8.3.0.tgz",
@@ -5800,6 +5952,11 @@
       "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
       "dev": true
     },
+    "delayed-stream": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz",
+      "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="
+    },
     "dir-glob": {
       "version": "3.0.1",
       "resolved": "https://registry.npmmirror.com/dir-glob/-/dir-glob-3.0.1.tgz",
@@ -6265,6 +6422,21 @@
       "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==",
       "dev": true
     },
+    "follow-redirects": {
+      "version": "1.15.2",
+      "resolved": "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.15.2.tgz",
+      "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA=="
+    },
+    "form-data": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmmirror.com/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"
+      }
+    },
     "fs-extra": {
       "version": "10.1.0",
       "resolved": "https://registry.npmmirror.com/fs-extra/-/fs-extra-10.1.0.tgz",
@@ -6699,6 +6871,19 @@
         "picomatch": "^2.3.1"
       }
     },
+    "mime-db": {
+      "version": "1.52.0",
+      "resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz",
+      "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="
+    },
+    "mime-types": {
+      "version": "2.1.35",
+      "resolved": "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz",
+      "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+      "requires": {
+        "mime-db": "1.52.0"
+      }
+    },
     "minimatch": {
       "version": "6.2.0",
       "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-6.2.0.tgz",
@@ -6797,8 +6982,7 @@
     "nprogress": {
       "version": "0.2.0",
       "resolved": "https://registry.npmmirror.com/nprogress/-/nprogress-0.2.0.tgz",
-      "integrity": "sha512-I19aIingLgR1fmhftnbWWO3dXc0hSxqHQHQb3H8m+K3TnEn/iSeTZZOyvKXWqQESMwuUVnatlCnZdLBZZt2VSA==",
-      "dev": true
+      "integrity": "sha512-I19aIingLgR1fmhftnbWWO3dXc0hSxqHQHQb3H8m+K3TnEn/iSeTZZOyvKXWqQESMwuUVnatlCnZdLBZZt2VSA=="
     },
     "nth-check": {
       "version": "2.1.1",
@@ -6926,6 +7110,23 @@
       "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
       "dev": true
     },
+    "pinia": {
+      "version": "2.0.33",
+      "resolved": "https://registry.npmmirror.com/pinia/-/pinia-2.0.33.tgz",
+      "integrity": "sha512-HOj1yVV2itw6rNIrR2f7+MirGNxhORjrULL8GWgRwXsGSvEqIQ+SE0MYt6cwtpegzCda3i+rVTZM+AM7CG+kRg==",
+      "requires": {
+        "@vue/devtools-api": "^6.5.0",
+        "vue-demi": "*"
+      },
+      "dependencies": {
+        "vue-demi": {
+          "version": "0.13.11",
+          "resolved": "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.13.11.tgz",
+          "integrity": "sha512-IR8HoEEGM65YY3ZJYAjMlKygDQn25D5ajNFNoKh9RSDMQtlzCxtfQjdQgv9jjK+m3377SsJXY8ysq8kLCZL25A==",
+          "requires": {}
+        }
+      }
+    },
     "pkg-types": {
       "version": "1.0.2",
       "resolved": "https://registry.npmmirror.com/pkg-types/-/pkg-types-1.0.2.tgz",
@@ -6986,6 +7187,11 @@
         "fast-diff": "^1.1.2"
       }
     },
+    "proxy-from-env": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmmirror.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
+      "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
+    },
     "punycode": {
       "version": "2.3.0",
       "resolved": "https://registry.npmmirror.com/punycode/-/punycode-2.3.0.tgz",
@@ -7257,7 +7463,7 @@
       "version": "4.9.5",
       "resolved": "https://registry.npmmirror.com/typescript/-/typescript-4.9.5.tgz",
       "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==",
-      "dev": true
+      "devOptional": true
     },
     "ufo": {
       "version": "1.1.1",

+ 4 - 2
package.json

@@ -10,10 +10,12 @@
   },
   "dependencies": {
     "@element-plus/icons-vue": "^2.1.0",
+    "axios": "^1.3.4",
     "element-plus": "^2.3.0",
+    "nprogress": "^0.2.0",
+    "pinia": "^2.0.33",
     "vue": "^3.2.45",
-    "vue-router": "^4.1.6",
-    "nprogress": "^0.2.0"
+    "vue-router": "^4.1.6"
   },
   "devDependencies": {
     "@babel/core": "^7.21.3",

+ 1 - 3
src/App.vue

@@ -1,6 +1,4 @@
-<script setup lang="ts">
-
-</script>
+<script setup lang="ts"></script>
 
 <template>
   <router-view></router-view>

+ 76 - 0
src/api/http.ts

@@ -0,0 +1,76 @@
+import axios from 'axios'
+import type { AxiosInstance, AxiosRequestConfig, InternalAxiosRequestConfig, AxiosResponse } from 'axios'
+
+interface RequestInterceptors {
+  // 请求拦截
+  requestInterceptors?: (config: InternalAxiosRequestConfig) => InternalAxiosRequestConfig
+  requestInterceptorsCatch?: (err: any) => any
+  // 响应拦截
+  responseInterceptors?: (config: AxiosResponse) => AxiosResponse
+  responseInterceptorsCatch?: (err: any) => any
+}
+// 自定义传入的参数
+interface RequestConfig extends AxiosRequestConfig {
+  interceptors?: RequestInterceptors
+}
+
+class Request {
+  // axios 实例
+  instance: AxiosInstance
+  // 拦截器对象
+  interceptorsObj?: RequestInterceptors
+  constructor(config: RequestConfig) {
+    this.instance = axios.create(config)
+
+    this.instance.interceptors.request.use(
+      (res: InternalAxiosRequestConfig) => {
+        console.log('全局请求拦截器')
+        return res
+      },
+      (err: any) => err
+    )
+
+    // 使用实例拦截器
+    this.instance.interceptors.request.use(
+      this.interceptorsObj?.requestInterceptors,
+      this.interceptorsObj?.requestInterceptorsCatch
+    )
+    this.instance.interceptors.response.use(
+      this.interceptorsObj?.responseInterceptors,
+      this.interceptorsObj?.responseInterceptorsCatch
+    )
+
+    this.instance.interceptors.response.use(
+      // 因为我们接口的数据都在res.data下,所以我们直接返回res.data
+      (res: AxiosResponse) => {
+        console.log('全局响应拦截器')
+        return res.data
+      },
+      (err: any) => err
+    )
+  }
+
+  request<T>(config: RequestConfig): Promise<T> {
+    return new Promise((resolve, reject) => {
+      // 如果我们为单个请求设置拦截器,这里使用单个请求的拦截器
+      if (config.interceptors?.requestInterceptors) {
+        config = config.interceptors.requestInterceptors(config)
+      }
+      this.instance
+        .request<any, T>(config)
+        .then(res => {
+          // 如果我们为单个响应设置拦截器,这里使用单个响应的拦截器
+          if (config.interceptors?.responseInterceptors) {
+            res = config.interceptors.responseInterceptors<T>(res)
+          }
+
+          resolve(res)
+        })
+        .catch((err: any) => {
+          reject(err)
+        })
+    })
+  }
+}
+
+export default Request

+ 87 - 0
src/api/index.ts

@@ -0,0 +1,87 @@
+import axios from 'axios'
+import type { AxiosInstance, AxiosError, AxiosRequestConfig, InternalAxiosRequestConfig, AxiosResponse } from 'axios'
+import { ElMessage } from 'element-plus'
+import { ResultEnum, ResultData } from './interface/index'
+
+const config = {
+  // 默认地址请求地址,可在 .env.*** 文件中修改
+  baseURL: import.meta.env.VITE_API_URL as string,
+  // 设置超时时间(10s)
+  timeout: ResultEnum.TIMEOUT as number,
+  // 跨域时候允许携带凭证
+  withCredentials: true
+}
+
+class RequestHttp {
+  service: AxiosInstance
+  public constructor(config: AxiosRequestConfig) {
+    // 实例化axios
+    this.service = axios.create(config)
+
+    /**
+     * @description 请求拦截器
+     * 客户端发送请求 -> [请求拦截器] -> 服务器
+     * token校验(JWT) : 接受服务器返回的token,存储到vuex/pinia/本地储存当中
+     */
+    this.service.interceptors.request.use(
+      (config: InternalAxiosRequestConfig) => {
+        return config
+      },
+      (err: any) => err
+    )
+
+    /**
+     * @description 响应拦截器
+     *  服务器换返回信息 -> [拦截统一处理] -> 客户端JS获取到信息
+     */
+    this.service.interceptors.response.use(
+      (response: AxiosResponse) => {
+        const { data } = response
+        // const globalStore = GlobalStore()
+        // * 在请求结束后,并关闭请求 loading
+        // tryHideFullScreenLoading()
+        // * 登陆失效(code == 401)
+        // if (data.code == ResultEnum.OVERDUE) {
+        //   ElMessage.error(data.msg)
+        //   globalStore.setToken('')
+        //   router.replace(LOGIN_URL)
+        //   return Promise.reject(data)
+        // }
+        // * 全局错误信息拦截(防止下载文件得时候返回数据流,没有code,直接报错)
+        if (data.code && data.code !== ResultEnum.SUCCESS) {
+          ElMessage.error(data.msg)
+          return Promise.reject(data)
+        }
+        // * 成功请求(在页面上除非特殊情况,否则不用在页面处理失败逻辑)
+        return data
+      },
+      async (error: AxiosError) => {
+        // const { response } = error
+        // tryHideFullScreenLoading()
+        // 请求超时 && 网络错误单独判断,没有 response
+        if (error.message.indexOf('timeout') !== -1) ElMessage.error('请求超时!请您稍后重试')
+        if (error.message.indexOf('Network Error') !== -1) ElMessage.error('网络错误!请您稍后重试')
+        // 根据响应的错误状态码,做不同的处理
+        // if (response) checkStatus(response.status)
+        // 服务器结果都没有返回(可能服务器错误可能客户端断网),断网处理:可以跳转到断网页面
+        // if (!window.navigator.onLine) router.replace('/500')
+        return Promise.reject(error)
+      }
+    )
+  }
+  // * 常用请求方法封装
+  get<T>(url: string, params?: object, _object = {}): Promise<ResultData<T>> {
+    return this.service.get(url, { params, ..._object })
+  }
+  post<T>(url: string, params?: object, _object = {}): Promise<ResultData<T>> {
+    return this.service.post(url, params, _object)
+  }
+  put<T>(url: string, params?: object, _object = {}): Promise<ResultData<T>> {
+    return this.service.put(url, params, _object)
+  }
+  delete<T>(url: string, params?: any, _object = {}): Promise<ResultData<T>> {
+    return this.service.delete(url, { params, ..._object })
+  }
+}
+
+export default new RequestHttp(config)

+ 33 - 0
src/api/interface/index.ts

@@ -0,0 +1,33 @@
+// * 请求枚举配置
+/**
+ * @description:请求配置
+ */
+export enum ResultEnum {
+  SUCCESS = 200,
+  ERROR = 500,
+  OVERDUE = 401,
+  TIMEOUT = 10000,
+  TYPE = 'success'
+}
+
+/**
+ * @description:请求方法
+ */
+export enum RequestEnum {
+  GET = 'GET',
+  POST = 'POST',
+  PATCH = 'PATCH',
+  PUT = 'PUT',
+  DELETE = 'DELETE'
+}
+
+// * 请求响应参数(不包含data)
+export interface Result {
+  code: string
+  msg: string
+}
+
+// * 请求响应参数(包含data)
+export interface ResultData<T = any> extends Result {
+  data: T
+}

BIN
src/assets/images/404.png


+ 19 - 0
src/components/ErrorMessage/404.vue

@@ -0,0 +1,19 @@
+<template>
+  <div class="not-container">
+    <img src="@/assets/images/404.png" class="not-img" alt="404" />
+    <div class="not-detail">
+      <h2>404</h2>
+      <h4>抱歉,您访问的页面不存在~🤷‍♂️🤷‍♀️</h4>
+      <el-button type="primary" @click="router.push('/')">返回首页</el-button>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts" name="404">
+import { useRouter } from 'vue-router'
+const router = useRouter()
+</script>
+
+<style scoped lang="scss">
+@import './index.scss';
+</style>

+ 15 - 0
src/components/ErrorMessage/index.scss

@@ -0,0 +1,15 @@
+.not-container {
+  height: 100%;
+  width: 100%;
+  position: relative;
+  img {
+    height: 100%;
+    width: 100%;
+  }
+  .not-detail {
+    position: absolute;
+    bottom: 20%;
+    right: 15%;
+    line-height: 40px;
+  }
+}

+ 0 - 38
src/components/HelloWorld.vue

@@ -1,38 +0,0 @@
-<script setup lang="ts">
-import { ref } from 'vue'
-
-defineProps<{ msg: string }>()
-
-const count = ref(0)
-</script>
-
-<template>
-  <h1>{{ msg }}</h1>
-
-  <div class="card">
-    <button type="button" @click="count++">count is {{ count }}</button>
-    <p>
-      Edit
-      <code>components/HelloWorld.vue</code> to test HMR
-    </p>
-  </div>
-
-  <p>
-    Check out
-    <a href="https://vuejs.org/guide/quick-start.html#local" target="_blank"
-      >create-vue</a
-    >, the official Vue + Vite starter
-  </p>
-  <p>
-    Install
-    <a href="https://github.com/johnsoncodehk/volar" target="_blank">Volar</a>
-    in your IDE for a better DX
-  </p>
-  <p class="read-the-docs">Click on the Vite and Vue logos to learn more</p>
-</template>
-
-<style scoped>
-.read-the-docs {
-  color: #888;
-}
-</style>

+ 41 - 0
src/components/TableBase/components/Pagination.vue

@@ -0,0 +1,41 @@
+<script setup lang="ts">
+interface Pageable {
+  pageNum: number
+  pageSize: number
+  total: number
+  small: boolean
+  disabled: boolean
+}
+
+interface PaginationProps {
+  pageable: Pageable
+  handleSizeChange: (size: number) => void
+  handleCurrentChange: (currentPage: number) => void
+}
+
+defineProps<PaginationProps>()
+</script>
+<template>
+  <div class="pagination">
+    <el-pagination
+      :current-page="pageable.pageNum"
+      :page-size="pageable.pageSize"
+      :page-sizes="[100, 200, 300, 400]"
+      :small="pageable.small"
+      :disabled="pageable.disabled"
+      :background="true"
+      layout="total, sizes, prev, pager, next, jumper"
+      :total="pageable.total"
+      @size-change="handleSizeChange"
+      @current-change="handleCurrentChange"
+    />
+  </div>
+</template>
+
+<style scoped lang="scss">
+.pagination {
+  display: flex;
+  justify-content: end;
+  margin-top: 1rem;
+}
+</style>

+ 27 - 0
src/components/TableBase/index.scss

@@ -0,0 +1,27 @@
+.table-header {
+  background-color: #fff;
+  height: 5rem;
+  margin-bottom: 1rem;
+  border: 1px solid var(--el-border-color-light);
+  border-radius: 4px;
+}
+
+.table-header,
+.card {
+  box-shadow: var(--el-box-shadow-light);
+}
+
+.card {
+  height: calc(100% - 5rem - 1rem);
+  box-sizing: border-box;
+  padding: 20px;
+  overflow-x: hidden;
+  background-color: var(--el-fill-color-blank);
+  border: 1px solid var(--el-border-color-light);
+  border-radius: 4px;
+  box-shadow: 0 0 12px rgb(0 0 0 / 5%);
+
+  display: flex;
+  flex-direction: column;
+  justify-content: space-between;
+}

+ 99 - 0
src/components/TableBase/index.vue

@@ -0,0 +1,99 @@
+<script setup lang="ts">
+import { ref } from 'vue'
+import Pagination from './components/Pagination.vue'
+import { useTable } from '@/hooks/useTable'
+import { ElTable, TableProps } from 'element-plus'
+import { ColumnProps } from './interface/index'
+
+interface ProTableProps extends Partial<Omit<TableProps<any>, 'data'>> {
+  columns: ColumnProps[] // 列配置项
+  requestApi: (params: any) => Promise<any> // 请求表格数据的api ==> 必传
+  dataCallback?: (data: any) => any // 返回数据的回调函数,可以对数据进行处理 ==> 非必传
+  title?: string // 表格标题,目前只在打印的时候用到 ==> 非必传
+  pagination?: boolean // 是否需要分页组件 ==> 非必传(默认为true)
+  initParam?: any // 初始化请求参数 ==> 非必传(默认为{})
+  border?: boolean // 是否带有纵向边框 ==> 非必传(默认为true)
+  toolButton?: boolean // 是否显示表格功能按钮 ==> 非必传(默认为true)
+  selectId?: string // 当表格数据多选时,所指定的 id ==> 非必传(默认为 id)
+  // searchCol?: number | Record<BreakPoint, number> // 表格搜索项 每列占比配置 ==> 非必传 { xs: 1, sm: 2, md: 2, lg: 3, xl: 4 }
+}
+
+// 接受父组件参数,配置默认值
+const props = withDefaults(defineProps<ProTableProps>(), {
+  columns: () => [],
+  pagination: true,
+  initParam: {},
+  border: true,
+  toolButton: true,
+  selectId: 'id',
+  searchCol: () => ({ xs: 1, sm: 2, md: 2, lg: 3, xl: 4 })
+})
+console.log(props)
+
+// 表格操作 Hooks
+const {
+  tableData,
+  pageable,
+  // searchParam,
+  // searchInitParam,
+  // getTableList,
+  // search,
+  // reset,
+  handleSizeChange,
+  handleCurrentChange
+} = useTable(props.requestApi, props.initParam, props.pagination, props.dataCallback)
+// 接收 columns 并设置为响应式
+const tableColumns = ref<ColumnProps[]>(props.columns)
+
+console.log(tableData, tableColumns)
+</script>
+
+<template>
+  <div class="table-header"></div>
+
+  <div class="card table">
+    <el-table :data="tableData">
+      <!-- 默认插槽 -->
+      <slot></slot>
+      <template v-for="item in tableColumns" :key="item">
+        <!-- selection || index -->
+        <el-table-column
+          v-bind="item"
+          :align="item.align ?? 'center'"
+          :reserve-selection="item.type == 'selection'"
+          v-if="item.type == 'selection' || item.type == 'index'"
+        >
+        </el-table-column>
+        <!-- expand 支持 tsx 语法 && 作用域插槽 (tsx > slot) -->
+        <el-table-column v-bind="item" :align="item.align ?? 'center'" v-if="item.type == 'expand'" v-slot="scope">
+          <component :is="item.render" :row="scope.row" v-if="item.render"> </component>
+          <slot :name="item.type" :row="scope.row" v-else></slot>
+        </el-table-column>
+        <el-table-column v-bind="item" :align="item.align ?? 'center'" v-if="!item.type && item.prop" v-slot="scope">
+          <slot v-if="item.fixed" :name="item.fixed" :row="scope.row"></slot>
+        </el-table-column>
+      </template>
+      <!-- 插入表格最后一行之后的插槽 -->
+      <template #append>
+        <slot name="append"> </slot>
+      </template>
+      <!-- 表格无数据情况 -->
+      <template #empty>
+        <el-empty></el-empty>
+      </template>
+    </el-table>
+    <!-- 分页组件 -->
+    <slot name="pagination">
+      <Pagination
+        v-if="pagination"
+        :pageable="pageable"
+        :handleSizeChange="handleSizeChange"
+        :handleCurrentChange="handleCurrentChange"
+      />
+    </slot>
+  </div>
+</template>
+
+<style scoped lang="scss">
+@import './index.scss';
+</style>

+ 46 - 0
src/components/TableBase/interface/index.ts

@@ -0,0 +1,46 @@
+import { TableColumnCtx } from 'element-plus/es/components/table/src/table-column/defaults'
+
+export interface EnumProps {
+  label: string // 选项框显示的文字
+  value: any // 选项框值
+  disabled?: boolean // 是否禁用此选项
+  tagType?: string // 当 tag 为 true 时,此选择会指定 tag 显示类型
+  children?: EnumProps[] // 为树形选择时,可以通过 children 属性指定子选项
+  [key: string]: any
+}
+
+export type SearchType =
+  | 'input'
+  | 'input-number'
+  | 'select'
+  | 'select-v2'
+  | 'tree-select'
+  | 'cascader'
+  | 'date-picker'
+  | 'time-picker'
+  | 'time-select'
+  | 'switch'
+  | 'slider'
+
+export type SearchProps = {
+  el: SearchType // 当前项搜索框的类型
+  props?: any // 搜索项参数,根据 element plus 官方文档来传递,该属性所有值会透传到组件
+  key?: string // 当搜索项 key 不为 prop 属性时,可通过 key 指定
+  order?: number // 搜索项排序(从大到小)
+  span?: number // 搜索项所占用的列数,默认为1列
+  offset?: number // 搜索字段左侧偏移列数
+  defaultValue?: string | number | boolean | any[] // 搜索项默认值
+}
+
+export interface ColumnProps<T = any>
+  extends Partial<Omit<TableColumnCtx<T>, 'children' | 'renderHeader' | 'renderCell'>> {
+  tag?: boolean // 是否是标签展示
+  isShow?: boolean // 是否显示在表格当中
+  search?: SearchProps | undefined // 搜索项配置
+  // enum?: EnumProps[] | ((params?: any) => Promise<any>) // 枚举类型(渲染值的字典)
+  isFilterEnum?: boolean // 当前单元格值是否根据 enum 格式化(示例:enum 只作为搜索项数据)
+  fieldNames?: { label: string; value: string } // 指定 label && value 的 key 值
+  headerRender?: (row: ColumnProps) => any // 自定义表头内容渲染(tsx语法)
+  render?: (scope: { row: T }) => any // 自定义单元格内容渲染(tsx语法)
+  _children?: ColumnProps<T>[] // 多级表头
+}

+ 6 - 0
src/env.d.ts

@@ -0,0 +1,6 @@
+declare module '*.vue' {
+  import type { DefineComponent } from 'vue'
+  // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types
+  const component: DefineComponent<{}, {}, any>
+  export default component
+}

+ 101 - 0
src/hooks/useTable.ts

@@ -0,0 +1,101 @@
+import { reactive, toRefs } from 'vue'
+interface Pageable {
+  pageNum: number
+  pageSize: number
+  total: number
+  small: boolean
+  disabled: boolean
+}
+interface Table {
+  tableData: any[]
+  pageable: Pageable
+  searchParam: any
+  searchInitParam: any
+  totalParam: any
+}
+
+/**
+ *
+ * @param requestApi 请求接口
+ * @param initParam 请求参数
+ * @param isPagination 是否需要分页
+ * @param dataCallback 对后台返回参数进行处理
+ * @returns
+ */
+export const useTable = (
+  requestApi: (params: any) => Promise<any>,
+  initParam: object = {},
+  isPagination: boolean = true,
+  dataCallback?: (data: any) => any
+) => {
+  const state = reactive<Table>({
+    // 表格数据
+    tableData: [
+      {
+        date: '2016-05-03',
+        name: 'Tom',
+        address: 'No. 189, Grove St, Los Angeles'
+      },
+      {
+        date: '2016-05-02',
+        name: 'Tom',
+        address: 'No. 189, Grove St, Los Angeles'
+      },
+      {
+        date: '2016-05-04',
+        name: 'Tom',
+        address: 'No. 189, Grove St, Los Angeles'
+      },
+      {
+        date: '2016-05-01',
+        name: 'Tom',
+        address: 'No. 189, Grove St, Los Angeles'
+      }
+    ],
+    // 分页数据
+    pageable: {
+      // 当前页数
+      pageNum: 1,
+      // 每页显示条数
+      pageSize: 10,
+      // 总条数
+      total: 0,
+      small: false,
+      disabled: false
+    },
+    // 查询参数(只包括查询)
+    searchParam: {},
+    // 初始化默认的查询参数
+    searchInitParam: {},
+    // 总参数(包含分页和查询参数)
+    totalParam: {}
+  })
+
+  const getTableList = async () => {
+    const res = await requestApi(initParam)
+    dataCallback && dataCallback(res)
+    console.log(res)
+  }
+
+  if (isPagination) {
+  }
+
+  const handleSizeChange = () => {
+    return false
+  }
+
+  const handleCurrentChange = () => {
+    return false
+  }
+
+  return {
+    ...toRefs(state),
+    // searchParam,
+    // searchInitParam,
+    getTableList,
+    // search,
+    // reset,
+    handleSizeChange,
+    handleCurrentChange
+  }
+}

+ 9 - 0
src/layouts/Main/index.scss

@@ -0,0 +1,9 @@
+.el-main {
+  box-sizing: border-box;
+  padding: 10px 12px;
+  overflow-x: hidden;
+  background-color: #f2f2f2;
+  &::-webkit-scrollbar {
+    background-color: #f0f2f5;
+  }
+}

+ 17 - 0
src/layouts/Main/index.vue

@@ -0,0 +1,17 @@
+<script setup lang="ts"></script>
+
+<template>
+  <el-main>
+    <router-view v-slot="{ Component, route }">
+      <transition appear name="fade-transform" mode="out-in">
+        <keep-alive>
+          <component :is="Component" :key="route.path" />
+        </keep-alive>
+      </transition>
+    </router-view>
+  </el-main>
+</template>
+
+<style scoped lang="scss">
+@import './index.scss';
+</style>

+ 32 - 0
src/layouts/Menu/SubMenu.vue

@@ -0,0 +1,32 @@
+<script setup lang="ts">
+import { useRouter } from 'vue-router'
+const router = useRouter()
+defineProps<{ menuList: Menu.MenuOptions[] }>()
+const handleClickMenu = (route: Menu.MenuOptions) => {
+  router.push(route.path)
+}
+</script>
+
+<template>
+  <div v-for="subItem in menuList" :key="subItem.path">
+    <el-sub-menu v-if="subItem.children && subItem.children.length > 0" :index="subItem.path">
+      <template #title>
+        <el-icon>
+          <component :is="subItem.meta.icon"></component>
+        </el-icon>
+        <span>{{ subItem.meta.title }}</span>
+      </template>
+      <SubMenu :menuList="subItem.children" />
+    </el-sub-menu>
+    <el-menu-item v-else :index="subItem.path" @click="handleClickMenu(subItem)">
+      <el-icon>
+        <component :is="subItem.meta.icon"></component>
+      </el-icon>
+      <template #title>
+        <span>{{ subItem.meta.title }}</span>
+      </template>
+    </el-menu-item>
+  </div>
+</template>
+
+<style></style>

+ 8 - 6
src/main.ts

@@ -1,13 +1,15 @@
-import { createApp } from "vue";
+import { createApp } from 'vue'
 // 全局默认样式
-import "./style.scss";
+import './style.scss'
 // 进度条样式
-import "nprogress/nprogress.css"; 
+import 'nprogress/nprogress.css'
 // element-ui样式
 import 'element-plus/dist/index.css'
-import App from "./App.vue";
+import App from './App.vue'
 // vue Router
-import router from "@/router/index";
+import router from '@/router/index'
+
+import store from '@/stores/index'
 
 import * as ElementPlusIconsVue from '@element-plus/icons-vue'
 
@@ -17,4 +19,4 @@ for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
   app.component(key, component)
 }
 
-app.use(router).mount("#app");
+app.use(router).use(store).mount('#app')

+ 12 - 13
src/router/index.ts

@@ -1,22 +1,21 @@
-import { createRouter, createWebHashHistory } from "vue-router";
-import NProgress from 'nprogress';
-import { staticRouter } from './modules/staticRouter'
+import { createRouter, createWebHashHistory } from 'vue-router'
+import NProgress from 'nprogress'
+import { staticRouter, errorRouter } from './modules/staticRouter'
 
 const router = createRouter({
-	history: createWebHashHistory(),
-  routes:[...staticRouter],
-	strict: false,
-});
+  history: createWebHashHistory(),
+  routes: [...staticRouter, ...errorRouter],
+  strict: false
+})
 
-router.beforeEach((to,from,next)=>{
-  NProgress.start();  // 开始加载
+router.beforeEach((to, from, next) => {
+  NProgress.start() // 开始加载
 
   next()
 })
 
-
-router.afterEach(()=>{
-  NProgress.done();  // 加载完成
+router.afterEach(() => {
+  NProgress.done() // 加载完成
 })
 
-export default router;
+export default router

+ 59 - 22
src/router/modules/staticRouter.ts

@@ -1,25 +1,62 @@
-import { RouteRecordRaw } from "vue-router";
-
-
+import { RouteRecordRaw } from 'vue-router'
 export const staticRouter: RouteRecordRaw[] = [
-	{
-		path: "/",
-		redirect: '/index'
-	},
   {
-		path: '/index',
-		name: "Home",
-		component: () => import("@/views/home/index.vue"),
-		meta: {
-			title: "首页"
-		}
-	},
+    path: '/',
+    redirect: '/index'
+  },
+  {
+    path: '/index',
+    name: 'Index',
+    component: () => import('@/views/Index.vue'),
+    children: [
+      {
+        path: '/home',
+        name: 'Home',
+        component: () => import('@/views/home/index.vue'),
+        meta: {
+          title: '首页'
+        }
+      },
+      {
+        path: '/roles',
+        name: 'Roles',
+        component: () => import('@/views/account/roles/index.vue'),
+        meta: {
+          title: '角色管理'
+        }
+      },
+      {
+        path: '/users',
+        name: 'Users',
+        component: () => import('@/views/account/users/index.vue'),
+        meta: {
+          title: '账户管理'
+        }
+      }
+    ]
+  },
+  {
+    path: '/login',
+    name: 'login',
+    component: () => import('@/views/Login.vue'),
+    meta: {
+      title: '登录'
+    }
+  }
+]
+
+export const errorRouter = [
+  {
+    path: '/404',
+    name: '404',
+    component: () => import('@/components/ErrorMessage/404.vue'),
+    meta: {
+      title: '404页面'
+    }
+  },
   {
-		path: '/login',
-		name: "login",
-		component: () => import("@/views/Login.vue"),
-		meta: {
-			title: "登录"
-		}
-	},
-];
+    path: '/:pathMatch(.*)*',
+    name: 'notFound',
+    redirect: { name: '404' }
+  }
+]

+ 13 - 0
src/stores/index.ts

@@ -0,0 +1,13 @@
+import { defineStore, createPinia } from 'pinia'
+
+export const GlobalStore = defineStore({
+  id: 'GlobalState',
+  state: () => ({
+    loading: false,
+    token: ''
+  })
+})
+
+const pinia = createPinia()
+
+export default pinia

+ 9 - 0
src/styles/element.scss

@@ -0,0 +1,9 @@
+// * table-box 表格页面样式
+.table,
+.table-box {
+  display: flex;
+  flex: 1;
+  flex-direction: column;
+  width: 100%;
+  height: 100%;
+}

+ 0 - 1
src/types/plugins.d.ts

@@ -1 +0,0 @@
-declare module "nprogress";

+ 17 - 0
src/typings/global.d.ts

@@ -0,0 +1,17 @@
+// * Menu
+declare namespace Menu {
+  interface MenuOptions {
+    path: string
+    name: string
+    component?: string | (() => Promise<any>)
+    redirect?: string
+    meta: MetaProps
+    children?: MenuOptions[]
+  }
+  interface MetaProps {
+    icon: string
+    title: string
+    activeMenu?: string
+    isKeepAlive: boolean
+  }
+}

+ 1 - 0
src/typings/plugins.d.ts

@@ -0,0 +1 @@
+declare module 'nprogress'

+ 169 - 0
src/views/Index.vue

@@ -0,0 +1,169 @@
+<script setup lang="ts">
+import SubMenu from '@/layouts/Menu/SubMenu.vue'
+import Main from '@/layouts/Main/index.vue'
+const routerList = [
+  {
+    path: '/index',
+    name: 'Home',
+    meta: {
+      icon: 'HomeFilled',
+      title: '首页',
+      isKeepAlive: true
+    }
+  },
+  {
+    path: '/account',
+    name: 'Account',
+    meta: {
+      icon: 'HomeFilled',
+      title: '账户管理',
+      isKeepAlive: true
+    },
+    children: [
+      {
+        path: '/roles',
+        name: 'Roles',
+        meta: {
+          icon: 'Memo',
+          title: '账户管理',
+          isKeepAlive: true
+        }
+      },
+      {
+        path: '/users',
+        name: 'Users',
+        meta: {
+          icon: 'UserFilled',
+          title: '角色管理',
+          isKeepAlive: true
+        }
+      }
+    ]
+  },
+  {
+    path: '',
+    name: '仓库管理',
+    iocn: '',
+    meta: {
+      icon: 'HomeFilled',
+      title: '首页',
+      isKeepAlive: true
+    }
+  },
+  {
+    path: '',
+    name: '薪资管理',
+    iocn: '',
+    meta: {
+      icon: 'HomeFilled',
+      title: '首页',
+      isKeepAlive: true
+    },
+    children: [
+      {
+        path: '',
+        name: '薪资管理',
+        meta: {
+          icon: 'HomeFilled',
+          title: '首页',
+          isKeepAlive: true
+        }
+      },
+      {
+        path: '',
+        name: '薪资统计',
+        meta: {
+          icon: 'HomeFilled',
+          title: '首页',
+          isKeepAlive: true
+        }
+      },
+      {
+        path: '',
+        name: '我的薪资',
+        meta: {
+          icon: 'HomeFilled',
+          title: '首页',
+          isKeepAlive: true
+        }
+      }
+    ]
+  },
+  {
+    path: '',
+    name: '薪资管理',
+    iocn: '',
+    meta: {
+      icon: 'HomeFilled',
+      title: '首页',
+      isKeepAlive: true
+    },
+    children: [
+      {
+        path: '',
+        name: '统筹管理',
+        meta: {
+          icon: 'HomeFilled',
+          title: '首页',
+          isKeepAlive: true
+        }
+      },
+      {
+        path: '',
+        name: '统筹管理(财务)',
+        meta: {
+          icon: 'HomeFilled',
+          title: '首页',
+          isKeepAlive: true
+        }
+      }
+      // { index: '5-3', path: '', name: '加班审批', iocn: '' },
+      // { index: '5-4', path: '', name: '请假审批', iocn: '' },
+      // { index: '5-5', path: '', name: '我的加班', iocn: '' },
+      // { index: '5-6', path: '', name: '我的请假', iocn: '' }
+    ]
+  }
+]
+</script>
+
+<template>
+  <el-container class="home-container">
+    <el-aside width="210px">
+      <el-scrollbar class="scrollbar">
+        <el-menu :unique-opened="true" :collapse-transition="false" text-color="#303133">
+          <SubMenu :menuList="routerList" />
+        </el-menu>
+      </el-scrollbar>
+    </el-aside>
+    <el-container>
+      <el-header>
+        <div class="header-lf">header</div>
+      </el-header>
+      <!-- <el-main>
+        <router-view></router-view>
+      </el-main> -->
+      <Main />
+    </el-container>
+  </el-container>
+</template>
+
+<style scoped lang="scss">
+.home-container {
+  height: 100%;
+  .scrollbar {
+    border-right: solid 1px var(--el-menu-border-color);
+    :deep(.el-menu) {
+      border-right: none;
+    }
+  }
+  // .el-aside {
+  //   background-color: #f2f2f2;
+  // }
+  .el-header {
+    display: flex;
+    // justify-content: center;
+    align-items: center;
+    border-bottom: solid 1px var(--el-menu-border-color);
+  }
+}
+</style>

+ 6 - 7
src/views/Login.vue

@@ -9,7 +9,6 @@ const ruleForm = reactive({
 })
 
 const validatePass = (rule: any, value: any, callback: any) => {
-
   if (value === '') {
     callback(new Error('请输入密码'))
   } else {
@@ -19,14 +18,14 @@ const validatePass = (rule: any, value: any, callback: any) => {
 
 const rules = reactive<FormRules>({
   username: [{ required: true, message: '请输入用户账号', trigger: 'blur' }],
-  password: [{ required: true, validator: validatePass, trigger: 'blur' }],
+  password: [{ required: true, validator: validatePass, trigger: 'blur' }]
 })
 
 const submitForm = (formEl: FormInstance | undefined) => {
   if (!formEl) return
-  formEl.validate((valid) => {
+  formEl.validate(valid => {
     if (valid) {
-      console.log(ruleForm);
+      console.log(ruleForm)
     } else {
       console.log('error submit!')
       return false
@@ -35,8 +34,8 @@ const submitForm = (formEl: FormInstance | undefined) => {
 }
 
 let passType = ref('password')
-const changeType = ()=>{
-  passType.value = passType.value === 'password'?'text':'password'
+const changeType = () => {
+  passType.value = passType.value === 'password' ? 'text' : 'password'
 }
 </script>
 
@@ -67,5 +66,5 @@ const changeType = ()=>{
 </template>
 
 <style scoped lang="scss">
-@import './login.scss'
+@import './login.scss';
 </style>

+ 41 - 7
src/views/account/roles/index.vue

@@ -1,13 +1,47 @@
 <script setup lang="ts">
-
+import TableBase from '@/components/TableBase/index.vue'
+import { ColumnProps } from '@/components/TableBase/interface/index'
+import { Edit, Delete } from '@element-plus/icons-vue'
+const columns: ColumnProps[] = [
+  { type: 'index', label: '#', width: 80 },
+  { prop: 'date', label: '时间', width: 120, search: { el: 'input' } },
+  {
+    prop: 'name',
+    label: '姓名',
+    width: 120,
+    sortable: true,
+    // enum: getUserGender,
+    search: { el: 'select' },
+    fieldNames: { label: 'genderLabel', value: 'genderValue' }
+  },
+  { prop: 'address', label: '身份证号' },
+  { prop: 'email', label: '邮箱' },
+  { prop: 'address', label: '居住地址' },
+  {
+    prop: 'status',
+    label: '用户状态',
+    width: 120,
+    sortable: true,
+    tag: true,
+    // enum: getUserStatus,
+    search: { el: 'select' },
+    fieldNames: { label: 'userLabel', value: 'userStatus' }
+  },
+  { prop: 'createTime', label: '创建时间', width: 180 },
+  { prop: 'operation', label: '操作', width: 430, fixed: 'right' }
+]
+const requestApi = async () => {
+  return false
+}
 </script>
 
 <template>
-  <div class="role">
-    角色
-  </div>
+  <TableBase :columns="columns" :requestApi="requestApi">
+    <template #right>
+      <el-button link type="primary" size="small" :icon="Edit">编辑</el-button>
+      <el-button link type="danger" size="small" :icon="Delete">删除</el-button>
+    </template>
+  </TableBase>
 </template>
 
-<style scoped>
-
-</style>
+<style scoped></style>

+ 3 - 34
src/views/home/index.vue

@@ -1,38 +1,7 @@
-<script setup lang="ts">
-
-</script>
+<script setup lang="ts"></script>
 
 <template>
-  <el-container class="home-container">
-    <el-aside width="200px">
-      <el-scrollbar>
-        <el-menu>
-          <el-sub-menu index="1">
-            <template #title>
-              <el-icon><message /></el-icon>Navigator One
-            </template>
-            <el-menu-item-group>
-              <el-menu-item index="1-1">Option 1</el-menu-item>
-              <el-menu-item index="1-2">Option 2</el-menu-item>
-            </el-menu-item-group>
-            <el-menu-item-group>
-              <el-menu-item index="1-3">Option 3</el-menu-item>
-            </el-menu-item-group>
-          </el-sub-menu>
-        </el-menu>
-      </el-scrollbar>
-    </el-aside>
-    <el-container>
-        <el-header>Header</el-header>
-        <el-main>
-          <router-view></router-view>
-        </el-main>
-    </el-container>
-  </el-container>
+  <div class="home">home</div>
 </template>
 
-<style scoped lang="scss">
-.home-container{
-  height:100%
-}
-</style>
+<style scoped lang="scss"></style>