lrf 2 年之前
父节点
当前提交
1f0fdb4a3f
共有 75 个文件被更改,包括 3985 次插入242 次删除
  1. 2 0
      .env
  2. 33 0
      .eslintrc.js
  3. 454 38
      package-lock.json
  4. 7 0
      package.json
  5. 1 26
      src/App.vue
  6. 44 0
      src/assets/css/color-dark.css
  7. 4 0
      src/assets/css/icon.css
  8. 177 0
      src/assets/css/main.css
  9. 54 0
      src/assets/css/theme-ele/color-ele.css
  10. 二进制
      src/assets/css/theme-ele/fonts/element-icons.ttf
  11. 二进制
      src/assets/css/theme-ele/fonts/element-icons.woff
  12. 1 0
      src/assets/css/theme-ele/index.css
  13. 54 0
      src/assets/css/theme-green/color-green.css
  14. 二进制
      src/assets/css/theme-green/fonts/element-icons.ttf
  15. 二进制
      src/assets/css/theme-green/fonts/element-icons.woff
  16. 1 0
      src/assets/css/theme-green/index.css
  17. 51 0
      src/assets/css/theme-pink/color-pink.css
  18. 二进制
      src/assets/css/theme-pink/fonts/element-icons.ttf
  19. 二进制
      src/assets/css/theme-pink/fonts/element-icons.woff
  20. 1 0
      src/assets/css/theme-pink/index.css
  21. 51 0
      src/assets/css/theme-red/color-red.css
  22. 二进制
      src/assets/css/theme-red/fonts/element-icons.ttf
  23. 二进制
      src/assets/css/theme-red/fonts/element-icons.woff
  24. 1 0
      src/assets/css/theme-red/index.css
  25. 255 0
      src/assets/icon/iconfont.css
  26. 1 0
      src/assets/icon/iconfont.js
  27. 429 0
      src/assets/icon/iconfont.json
  28. 二进制
      src/assets/icon/iconfont.ttf
  29. 二进制
      src/assets/icon/iconfont.woff
  30. 二进制
      src/assets/icon/iconfont.woff2
  31. 二进制
      src/assets/img/file.png
  32. 二进制
      src/assets/img/img.jpg
  33. 二进制
      src/assets/login-background.jpg
  34. 二进制
      src/assets/logo.png
  35. 二进制
      src/assets/yzm.gif
  36. 0 130
      src/components/HelloWorld.vue
  37. 150 0
      src/components/usual/c-form.vue
  38. 19 0
      src/components/usual/c-search.vue
  39. 288 0
      src/components/usual/c-table.vue
  40. 116 0
      src/layout/Header.vue
  41. 81 0
      src/layout/Home.vue
  42. 136 0
      src/layout/Sidebar.vue
  43. 201 0
      src/layout/Tags.vue
  44. 33 0
      src/layout/breadcrumb.vue
  45. 6 0
      src/layout/bus.js
  46. 162 0
      src/layout/data/menu.js
  47. 85 0
      src/layout/data/site.js
  48. 79 0
      src/layout/directives.js
  49. 116 0
      src/layout/header/activitys.vue
  50. 73 0
      src/layout/header/notice.vue
  51. 30 0
      src/layout/i18n.js
  52. 15 5
      src/main.js
  53. 19 0
      src/plugins/axios.js
  54. 39 0
      src/plugins/check-res.js
  55. 22 0
      src/plugins/components.js
  56. 70 0
      src/plugins/directive.js
  57. 5 0
      src/plugins/element.js
  58. 4 0
      src/plugins/meta.js
  59. 21 0
      src/plugins/setting.js
  60. 65 0
      src/plugins/stomp.js
  61. 22 0
      src/router/guard.js
  62. 21 14
      src/router/index.js
  63. 8 5
      src/store/index.js
  64. 29 0
      src/store/module/user/action.js
  65. 40 0
      src/store/module/user/mutations.js
  66. 3 0
      src/store/module/user/state.js
  67. 49 0
      src/template/list.vue
  68. 131 0
      src/util/axios-wrapper.js
  69. 0 5
      src/views/About.vue
  70. 0 18
      src/views/Home.vue
  71. 27 0
      src/views/dev/dict/index.vue
  72. 28 0
      src/views/index.vue
  73. 143 0
      src/views/login.vue
  74. 0 0
      src/views/selfShop/coupon/index.vue
  75. 28 1
      vue.config.js

+ 2 - 0
.env

@@ -0,0 +1,2 @@
+VUE_APP_AXIOS_BASE_URL = ''
+VUE_APP_ROUTER="pointAdmin"

+ 33 - 0
.eslintrc.js

@@ -0,0 +1,33 @@
+// https://eslint.org/docs/user-guide/configuring
+
+module.exports = {
+  root: true,
+  env: {
+    node: true,
+  },
+  extends: ["plugin:vue/essential", "@vue/prettier"],
+  plugins: ["vue"],
+  rules: {
+    "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,
+      },
+    ],
+  },
+  parserOptions: {
+    parser: "babel-eslint",
+  },
+};

+ 454 - 38
package-lock.json

@@ -8,8 +8,15 @@
       "name": "web-admin",
       "version": "0.1.0",
       "dependencies": {
+        "axios": "^0.27.2",
         "core-js": "^3.6.5",
+        "element-ui": "^2.15.10",
+        "jsonwebtoken": "^8.5.1",
+        "lodash": "^4.17.21",
+        "moment": "^2.29.1",
+        "naf-core": "^0.1.2",
         "vue": "^2.6.11",
+        "vue-meta": "^2.4.0",
         "vue-router": "^3.2.0",
         "vuex": "^3.4.0"
       },
@@ -3233,11 +3240,18 @@
       "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==",
       "dev": true
     },
+    "node_modules/async-validator": {
+      "version": "1.8.5",
+      "resolved": "https://registry.npmjs.org/async-validator/-/async-validator-1.8.5.tgz",
+      "integrity": "sha512-tXBM+1m056MAX0E8TL2iCjg8WvSyXu0Zc8LNtYqrVeyoL3+esHRZ4SieE9fKQyyU09uONjnMEjrNBMqT0mbvmA==",
+      "dependencies": {
+        "babel-runtime": "6.x"
+      }
+    },
     "node_modules/asynckit": {
       "version": "0.4.0",
       "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
-      "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
-      "dev": true
+      "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
     },
     "node_modules/atob": {
       "version": "2.1.2",
@@ -3288,6 +3302,28 @@
       "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==",
       "dev": true
     },
+    "node_modules/axios": {
+      "version": "0.27.2",
+      "resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz",
+      "integrity": "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==",
+      "dependencies": {
+        "follow-redirects": "^1.14.9",
+        "form-data": "^4.0.0"
+      }
+    },
+    "node_modules/axios/node_modules/form-data": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/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/babel-eslint": {
       "version": "10.1.0",
       "resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-10.1.0.tgz",
@@ -3309,6 +3345,11 @@
         "eslint": ">= 4.12.1"
       }
     },
+    "node_modules/babel-helper-vue-jsx-merge-props": {
+      "version": "2.0.3",
+      "resolved": "https://registry.npmjs.org/babel-helper-vue-jsx-merge-props/-/babel-helper-vue-jsx-merge-props-2.0.3.tgz",
+      "integrity": "sha512-gsLiKK7Qrb7zYJNgiXKpXblxbV5ffSwR0f5whkPAaBAR4fhi6bwRZxX9wBlIc5M/v8CCkXUbXZL4N/nSE97cqg=="
+    },
     "node_modules/babel-loader": {
       "version": "8.2.5",
       "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.2.5.tgz",
@@ -3376,6 +3417,27 @@
         "@babel/core": "^7.0.0-0"
       }
     },
+    "node_modules/babel-runtime": {
+      "version": "6.26.0",
+      "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz",
+      "integrity": "sha512-ITKNuq2wKlW1fJg9sSW52eepoYgZBggvOAHC0u/CYu/qxQ9EVzThCgR69BnSXLHjy2f7SY5zaQ4yt7H9ZVxY2g==",
+      "dependencies": {
+        "core-js": "^2.4.0",
+        "regenerator-runtime": "^0.11.0"
+      }
+    },
+    "node_modules/babel-runtime/node_modules/core-js": {
+      "version": "2.6.12",
+      "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz",
+      "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==",
+      "deprecated": "core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js.",
+      "hasInstallScript": true
+    },
+    "node_modules/babel-runtime/node_modules/regenerator-runtime": {
+      "version": "0.11.1",
+      "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz",
+      "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg=="
+    },
     "node_modules/balanced-match": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
@@ -3787,6 +3849,11 @@
         "isarray": "^1.0.0"
       }
     },
+    "node_modules/buffer-equal-constant-time": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
+      "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="
+    },
     "node_modules/buffer-from": {
       "version": "1.1.2",
       "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
@@ -4522,7 +4589,6 @@
       "version": "1.0.8",
       "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
       "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
-      "dev": true,
       "dependencies": {
         "delayed-stream": "~1.0.0"
       },
@@ -5459,7 +5525,6 @@
       "version": "1.5.2",
       "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-1.5.2.tgz",
       "integrity": "sha512-95k0GDqvBjZavkuvzx/YqVLv/6YYa17fz6ILMSf7neqQITCPbnfEnQvEgMPNjH4kgobe7+WIL0yJEHku+H3qtQ==",
-      "dev": true,
       "engines": {
         "node": ">=0.10.0"
       }
@@ -5689,7 +5754,6 @@
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
       "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
-      "dev": true,
       "engines": {
         "node": ">=0.4.0"
       }
@@ -5933,6 +5997,14 @@
         "safer-buffer": "^2.1.0"
       }
     },
+    "node_modules/ecdsa-sig-formatter": {
+      "version": "1.0.11",
+      "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
+      "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
+      "dependencies": {
+        "safe-buffer": "^5.0.1"
+      }
+    },
     "node_modules/ee-first": {
       "version": "1.1.1",
       "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
@@ -5955,6 +6027,22 @@
       "integrity": "sha512-38KaYBNs0oCzWCpr6j7fY/W9vF0vSp4tKFIshQTgdZMhUpkxgotkQgjJP6iGMdmlsgMs3i0/Hkko4UXLTrkYVQ==",
       "dev": true
     },
+    "node_modules/element-ui": {
+      "version": "2.15.10",
+      "resolved": "https://registry.npmjs.org/element-ui/-/element-ui-2.15.10.tgz",
+      "integrity": "sha512-jmD++mU2wKXbisvx4fxOl2mHaU+HWHTAq/3Wf8x9Bwyu4GdDZPLABb+CGi3DWN6fPqdgRcd74aX39DO+YHObLw==",
+      "dependencies": {
+        "async-validator": "~1.8.1",
+        "babel-helper-vue-jsx-merge-props": "^2.0.0",
+        "deepmerge": "^1.2.0",
+        "normalize-wheel": "^1.0.1",
+        "resize-observer-polyfill": "^1.5.0",
+        "throttle-debounce": "^1.0.1"
+      },
+      "peerDependencies": {
+        "vue": "^2.5.17"
+      }
+    },
     "node_modules/elliptic": {
       "version": "6.5.4",
       "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz",
@@ -7218,7 +7306,6 @@
       "version": "1.15.2",
       "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz",
       "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==",
-      "dev": true,
       "funding": [
         {
           "type": "individual",
@@ -9109,6 +9196,35 @@
         "graceful-fs": "^4.1.6"
       }
     },
+    "node_modules/jsonwebtoken": {
+      "version": "8.5.1",
+      "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz",
+      "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==",
+      "dependencies": {
+        "jws": "^3.2.2",
+        "lodash.includes": "^4.3.0",
+        "lodash.isboolean": "^3.0.3",
+        "lodash.isinteger": "^4.0.4",
+        "lodash.isnumber": "^3.0.3",
+        "lodash.isplainobject": "^4.0.6",
+        "lodash.isstring": "^4.0.1",
+        "lodash.once": "^4.0.0",
+        "ms": "^2.1.1",
+        "semver": "^5.6.0"
+      },
+      "engines": {
+        "node": ">=4",
+        "npm": ">=1.4.28"
+      }
+    },
+    "node_modules/jsonwebtoken/node_modules/semver": {
+      "version": "5.7.1",
+      "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
+      "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
+      "bin": {
+        "semver": "bin/semver"
+      }
+    },
     "node_modules/jsprim": {
       "version": "1.4.2",
       "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz",
@@ -9124,6 +9240,25 @@
         "node": ">=0.6.0"
       }
     },
+    "node_modules/jwa": {
+      "version": "1.4.1",
+      "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz",
+      "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==",
+      "dependencies": {
+        "buffer-equal-constant-time": "1.0.1",
+        "ecdsa-sig-formatter": "1.0.11",
+        "safe-buffer": "^5.0.1"
+      }
+    },
+    "node_modules/jws": {
+      "version": "3.2.2",
+      "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz",
+      "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==",
+      "dependencies": {
+        "jwa": "^1.4.1",
+        "safe-buffer": "^5.0.1"
+      }
+    },
     "node_modules/killable": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/killable/-/killable-1.0.1.tgz",
@@ -9397,8 +9532,7 @@
     "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.debounce": {
       "version": "4.0.8",
@@ -9412,6 +9546,36 @@
       "integrity": "sha512-3j8wdDzYuWO3lM3Reg03MuQR957t287Rpcxp1njpEa8oDrikb+FwGdW3n+FELh/A6qib6yPit0j/pv9G/yeAqA==",
       "dev": true
     },
+    "node_modules/lodash.includes": {
+      "version": "4.3.0",
+      "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
+      "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w=="
+    },
+    "node_modules/lodash.isboolean": {
+      "version": "3.0.3",
+      "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
+      "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg=="
+    },
+    "node_modules/lodash.isinteger": {
+      "version": "4.0.4",
+      "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
+      "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA=="
+    },
+    "node_modules/lodash.isnumber": {
+      "version": "3.0.3",
+      "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz",
+      "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw=="
+    },
+    "node_modules/lodash.isplainobject": {
+      "version": "4.0.6",
+      "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
+      "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA=="
+    },
+    "node_modules/lodash.isstring": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
+      "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw=="
+    },
     "node_modules/lodash.kebabcase": {
       "version": "4.1.1",
       "resolved": "https://registry.npmjs.org/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz",
@@ -9430,6 +9594,11 @@
       "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==",
       "dev": true
     },
+    "node_modules/lodash.once": {
+      "version": "4.1.1",
+      "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
+      "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg=="
+    },
     "node_modules/lodash.transform": {
       "version": "4.6.0",
       "resolved": "https://registry.npmjs.org/lodash.transform/-/lodash.transform-4.6.0.tgz",
@@ -9652,7 +9821,6 @@
       "version": "1.52.0",
       "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
       "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
-      "dev": true,
       "engines": {
         "node": ">= 0.6"
       }
@@ -9661,7 +9829,6 @@
       "version": "2.1.35",
       "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
       "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
-      "dev": true,
       "dependencies": {
         "mime-db": "1.52.0"
       },
@@ -9830,6 +9997,14 @@
         "mkdirp": "bin/cmd.js"
       }
     },
+    "node_modules/moment": {
+      "version": "2.29.4",
+      "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz",
+      "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==",
+      "engines": {
+        "node": "*"
+      }
+    },
     "node_modules/move-concurrently": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz",
@@ -9847,8 +10022,7 @@
     "node_modules/ms": {
       "version": "2.1.2",
       "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
-      "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
-      "dev": true
+      "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
     },
     "node_modules/multicast-dns": {
       "version": "6.2.3",
@@ -9886,6 +10060,17 @@
         "thenify-all": "^1.0.0"
       }
     },
+    "node_modules/naf-core": {
+      "version": "0.1.2",
+      "resolved": "https://registry.npmjs.org/naf-core/-/naf-core-0.1.2.tgz",
+      "integrity": "sha512-qWVewfX6pp6gFxFYcO0dsPAJVTuxipERcqPBNEZdRypKuxRjAxlmKru8Ik9wptHFiIn9K4o6HRWEOXAURWfRqg==",
+      "dependencies": {
+        "lodash": "^4.17.11"
+      },
+      "engines": {
+        "node": ">=8.9.0"
+      }
+    },
     "node_modules/nan": {
       "version": "2.16.0",
       "resolved": "https://registry.npmjs.org/nan/-/nan-2.16.0.tgz",
@@ -10075,6 +10260,11 @@
         "node": ">=4"
       }
     },
+    "node_modules/normalize-wheel": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/normalize-wheel/-/normalize-wheel-1.0.1.tgz",
+      "integrity": "sha512-1OnlAPZ3zgrk8B91HyRj+eVv+kS5u+Z0SCsak6Xil/kmgEia50ga7zfkumayonZrImffAxPU/5WcyGhzetHNPA=="
+    },
     "node_modules/npm-run-path": {
       "version": "2.0.2",
       "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz",
@@ -12185,6 +12375,11 @@
       "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==",
       "dev": true
     },
+    "node_modules/resize-observer-polyfill": {
+      "version": "1.5.1",
+      "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz",
+      "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg=="
+    },
     "node_modules/resolve": {
       "version": "1.22.1",
       "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz",
@@ -12328,8 +12523,7 @@
     "node_modules/safe-buffer": {
       "version": "5.1.2",
       "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
-      "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
-      "dev": true
+      "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
     },
     "node_modules/safe-regex": {
       "version": "1.1.0",
@@ -13898,6 +14092,14 @@
         "node": ">=4.0.0"
       }
     },
+    "node_modules/throttle-debounce": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/throttle-debounce/-/throttle-debounce-1.1.0.tgz",
+      "integrity": "sha512-XH8UiPCQcWNuk2LYePibW/4qL97+ZQ1AN3FNXwZRBNPPowo/NRU5fAlDCSNBJIYCKbioZfuYtMhG4quqoJhVzg==",
+      "engines": {
+        "node": ">=4"
+      }
+    },
     "node_modules/through": {
       "version": "2.3.8",
       "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
@@ -14807,6 +15009,22 @@
         "node": ">=4.0.0"
       }
     },
+    "node_modules/vue-meta": {
+      "version": "2.4.0",
+      "resolved": "https://registry.npmjs.org/vue-meta/-/vue-meta-2.4.0.tgz",
+      "integrity": "sha512-XEeZUmlVeODclAjCNpWDnjgw+t3WA6gdzs6ENoIAgwO1J1d5p1tezDhtteLUFwcaQaTtayRrsx7GL6oXp/m2Jw==",
+      "dependencies": {
+        "deepmerge": "^4.2.2"
+      }
+    },
+    "node_modules/vue-meta/node_modules/deepmerge": {
+      "version": "4.2.2",
+      "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz",
+      "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
     "node_modules/vue-router": {
       "version": "3.6.5",
       "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-3.6.5.tgz",
@@ -17768,7 +17986,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",
@@ -17935,7 +18154,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/web-component-wrapper": {
       "version": "1.3.0",
@@ -18150,7 +18370,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",
@@ -18180,13 +18401,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",
@@ -18420,11 +18643,18 @@
       "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==",
       "dev": true
     },
+    "async-validator": {
+      "version": "1.8.5",
+      "resolved": "https://registry.npmjs.org/async-validator/-/async-validator-1.8.5.tgz",
+      "integrity": "sha512-tXBM+1m056MAX0E8TL2iCjg8WvSyXu0Zc8LNtYqrVeyoL3+esHRZ4SieE9fKQyyU09uONjnMEjrNBMqT0mbvmA==",
+      "requires": {
+        "babel-runtime": "6.x"
+      }
+    },
     "asynckit": {
       "version": "0.4.0",
       "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
-      "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
-      "dev": true
+      "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
     },
     "atob": {
       "version": "2.1.2",
@@ -18459,6 +18689,27 @@
       "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==",
       "dev": true
     },
+    "axios": {
+      "version": "0.27.2",
+      "resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz",
+      "integrity": "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==",
+      "requires": {
+        "follow-redirects": "^1.14.9",
+        "form-data": "^4.0.0"
+      },
+      "dependencies": {
+        "form-data": {
+          "version": "4.0.0",
+          "resolved": "https://registry.npmjs.org/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"
+          }
+        }
+      }
+    },
     "babel-eslint": {
       "version": "10.1.0",
       "resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-10.1.0.tgz",
@@ -18473,6 +18724,11 @@
         "resolve": "^1.12.0"
       }
     },
+    "babel-helper-vue-jsx-merge-props": {
+      "version": "2.0.3",
+      "resolved": "https://registry.npmjs.org/babel-helper-vue-jsx-merge-props/-/babel-helper-vue-jsx-merge-props-2.0.3.tgz",
+      "integrity": "sha512-gsLiKK7Qrb7zYJNgiXKpXblxbV5ffSwR0f5whkPAaBAR4fhi6bwRZxX9wBlIc5M/v8CCkXUbXZL4N/nSE97cqg=="
+    },
     "babel-loader": {
       "version": "8.2.5",
       "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.2.5.tgz",
@@ -18524,6 +18780,27 @@
         "@babel/helper-define-polyfill-provider": "^0.3.3"
       }
     },
+    "babel-runtime": {
+      "version": "6.26.0",
+      "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz",
+      "integrity": "sha512-ITKNuq2wKlW1fJg9sSW52eepoYgZBggvOAHC0u/CYu/qxQ9EVzThCgR69BnSXLHjy2f7SY5zaQ4yt7H9ZVxY2g==",
+      "requires": {
+        "core-js": "^2.4.0",
+        "regenerator-runtime": "^0.11.0"
+      },
+      "dependencies": {
+        "core-js": {
+          "version": "2.6.12",
+          "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz",
+          "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ=="
+        },
+        "regenerator-runtime": {
+          "version": "0.11.1",
+          "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz",
+          "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg=="
+        }
+      }
+    },
     "balanced-match": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
@@ -18864,6 +19141,11 @@
         "isarray": "^1.0.0"
       }
     },
+    "buffer-equal-constant-time": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
+      "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="
+    },
     "buffer-from": {
       "version": "1.1.2",
       "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
@@ -19453,7 +19735,6 @@
       "version": "1.0.8",
       "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
       "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
-      "dev": true,
       "requires": {
         "delayed-stream": "~1.0.0"
       }
@@ -20195,8 +20476,7 @@
     "deepmerge": {
       "version": "1.5.2",
       "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-1.5.2.tgz",
-      "integrity": "sha512-95k0GDqvBjZavkuvzx/YqVLv/6YYa17fz6ILMSf7neqQITCPbnfEnQvEgMPNjH4kgobe7+WIL0yJEHku+H3qtQ==",
-      "dev": true
+      "integrity": "sha512-95k0GDqvBjZavkuvzx/YqVLv/6YYa17fz6ILMSf7neqQITCPbnfEnQvEgMPNjH4kgobe7+WIL0yJEHku+H3qtQ=="
     },
     "default-gateway": {
       "version": "5.0.5",
@@ -20368,8 +20648,7 @@
     "delayed-stream": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
-      "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
-      "dev": true
+      "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="
     },
     "depd": {
       "version": "2.0.0",
@@ -20568,6 +20847,14 @@
         "safer-buffer": "^2.1.0"
       }
     },
+    "ecdsa-sig-formatter": {
+      "version": "1.0.11",
+      "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
+      "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
+      "requires": {
+        "safe-buffer": "^5.0.1"
+      }
+    },
     "ee-first": {
       "version": "1.1.1",
       "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
@@ -20586,6 +20873,19 @@
       "integrity": "sha512-38KaYBNs0oCzWCpr6j7fY/W9vF0vSp4tKFIshQTgdZMhUpkxgotkQgjJP6iGMdmlsgMs3i0/Hkko4UXLTrkYVQ==",
       "dev": true
     },
+    "element-ui": {
+      "version": "2.15.10",
+      "resolved": "https://registry.npmjs.org/element-ui/-/element-ui-2.15.10.tgz",
+      "integrity": "sha512-jmD++mU2wKXbisvx4fxOl2mHaU+HWHTAq/3Wf8x9Bwyu4GdDZPLABb+CGi3DWN6fPqdgRcd74aX39DO+YHObLw==",
+      "requires": {
+        "async-validator": "~1.8.1",
+        "babel-helper-vue-jsx-merge-props": "^2.0.0",
+        "deepmerge": "^1.2.0",
+        "normalize-wheel": "^1.0.1",
+        "resize-observer-polyfill": "^1.5.0",
+        "throttle-debounce": "^1.0.1"
+      }
+    },
     "elliptic": {
       "version": "6.5.4",
       "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz",
@@ -21584,8 +21884,7 @@
     "follow-redirects": {
       "version": "1.15.2",
       "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz",
-      "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==",
-      "dev": true
+      "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA=="
     },
     "for-in": {
       "version": "1.0.2",
@@ -23000,6 +23299,30 @@
         "graceful-fs": "^4.1.6"
       }
     },
+    "jsonwebtoken": {
+      "version": "8.5.1",
+      "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz",
+      "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==",
+      "requires": {
+        "jws": "^3.2.2",
+        "lodash.includes": "^4.3.0",
+        "lodash.isboolean": "^3.0.3",
+        "lodash.isinteger": "^4.0.4",
+        "lodash.isnumber": "^3.0.3",
+        "lodash.isplainobject": "^4.0.6",
+        "lodash.isstring": "^4.0.1",
+        "lodash.once": "^4.0.0",
+        "ms": "^2.1.1",
+        "semver": "^5.6.0"
+      },
+      "dependencies": {
+        "semver": {
+          "version": "5.7.1",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
+          "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ=="
+        }
+      }
+    },
     "jsprim": {
       "version": "1.4.2",
       "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz",
@@ -23012,6 +23335,25 @@
         "verror": "1.10.0"
       }
     },
+    "jwa": {
+      "version": "1.4.1",
+      "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz",
+      "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==",
+      "requires": {
+        "buffer-equal-constant-time": "1.0.1",
+        "ecdsa-sig-formatter": "1.0.11",
+        "safe-buffer": "^5.0.1"
+      }
+    },
+    "jws": {
+      "version": "3.2.2",
+      "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz",
+      "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==",
+      "requires": {
+        "jwa": "^1.4.1",
+        "safe-buffer": "^5.0.1"
+      }
+    },
     "killable": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/killable/-/killable-1.0.1.tgz",
@@ -23230,8 +23572,7 @@
     "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.debounce": {
       "version": "4.0.8",
@@ -23245,6 +23586,36 @@
       "integrity": "sha512-3j8wdDzYuWO3lM3Reg03MuQR957t287Rpcxp1njpEa8oDrikb+FwGdW3n+FELh/A6qib6yPit0j/pv9G/yeAqA==",
       "dev": true
     },
+    "lodash.includes": {
+      "version": "4.3.0",
+      "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
+      "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w=="
+    },
+    "lodash.isboolean": {
+      "version": "3.0.3",
+      "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
+      "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg=="
+    },
+    "lodash.isinteger": {
+      "version": "4.0.4",
+      "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
+      "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA=="
+    },
+    "lodash.isnumber": {
+      "version": "3.0.3",
+      "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz",
+      "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw=="
+    },
+    "lodash.isplainobject": {
+      "version": "4.0.6",
+      "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
+      "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA=="
+    },
+    "lodash.isstring": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
+      "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw=="
+    },
     "lodash.kebabcase": {
       "version": "4.1.1",
       "resolved": "https://registry.npmjs.org/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz",
@@ -23263,6 +23634,11 @@
       "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==",
       "dev": true
     },
+    "lodash.once": {
+      "version": "4.1.1",
+      "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
+      "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg=="
+    },
     "lodash.transform": {
       "version": "4.6.0",
       "resolved": "https://registry.npmjs.org/lodash.transform/-/lodash.transform-4.6.0.tgz",
@@ -23443,14 +23819,12 @@
     "mime-db": {
       "version": "1.52.0",
       "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
-      "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
-      "dev": true
+      "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="
     },
     "mime-types": {
       "version": "2.1.35",
       "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
       "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
-      "dev": true,
       "requires": {
         "mime-db": "1.52.0"
       }
@@ -23587,6 +23961,11 @@
         "minimist": "^1.2.6"
       }
     },
+    "moment": {
+      "version": "2.29.4",
+      "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz",
+      "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w=="
+    },
     "move-concurrently": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz",
@@ -23604,8 +23983,7 @@
     "ms": {
       "version": "2.1.2",
       "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
-      "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
-      "dev": true
+      "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
     },
     "multicast-dns": {
       "version": "6.2.3",
@@ -23640,6 +24018,14 @@
         "thenify-all": "^1.0.0"
       }
     },
+    "naf-core": {
+      "version": "0.1.2",
+      "resolved": "https://registry.npmjs.org/naf-core/-/naf-core-0.1.2.tgz",
+      "integrity": "sha512-qWVewfX6pp6gFxFYcO0dsPAJVTuxipERcqPBNEZdRypKuxRjAxlmKru8Ik9wptHFiIn9K4o6HRWEOXAURWfRqg==",
+      "requires": {
+        "lodash": "^4.17.11"
+      }
+    },
     "nan": {
       "version": "2.16.0",
       "resolved": "https://registry.npmjs.org/nan/-/nan-2.16.0.tgz",
@@ -23806,6 +24192,11 @@
         "sort-keys": "^1.0.0"
       }
     },
+    "normalize-wheel": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/normalize-wheel/-/normalize-wheel-1.0.1.tgz",
+      "integrity": "sha512-1OnlAPZ3zgrk8B91HyRj+eVv+kS5u+Z0SCsak6Xil/kmgEia50ga7zfkumayonZrImffAxPU/5WcyGhzetHNPA=="
+    },
     "npm-run-path": {
       "version": "2.0.2",
       "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz",
@@ -25538,6 +25929,11 @@
       "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==",
       "dev": true
     },
+    "resize-observer-polyfill": {
+      "version": "1.5.1",
+      "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz",
+      "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg=="
+    },
     "resolve": {
       "version": "1.22.1",
       "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz",
@@ -25650,8 +26046,7 @@
     "safe-buffer": {
       "version": "5.1.2",
       "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
-      "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
-      "dev": true
+      "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
     },
     "safe-regex": {
       "version": "1.1.0",
@@ -26954,6 +27349,11 @@
         }
       }
     },
+    "throttle-debounce": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/throttle-debounce/-/throttle-debounce-1.1.0.tgz",
+      "integrity": "sha512-XH8UiPCQcWNuk2LYePibW/4qL97+ZQ1AN3FNXwZRBNPPowo/NRU5fAlDCSNBJIYCKbioZfuYtMhG4quqoJhVzg=="
+    },
     "through": {
       "version": "2.3.8",
       "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
@@ -27680,6 +28080,21 @@
         }
       }
     },
+    "vue-meta": {
+      "version": "2.4.0",
+      "resolved": "https://registry.npmjs.org/vue-meta/-/vue-meta-2.4.0.tgz",
+      "integrity": "sha512-XEeZUmlVeODclAjCNpWDnjgw+t3WA6gdzs6ENoIAgwO1J1d5p1tezDhtteLUFwcaQaTtayRrsx7GL6oXp/m2Jw==",
+      "requires": {
+        "deepmerge": "^4.2.2"
+      },
+      "dependencies": {
+        "deepmerge": {
+          "version": "4.2.2",
+          "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz",
+          "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg=="
+        }
+      }
+    },
     "vue-router": {
       "version": "3.6.5",
       "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-3.6.5.tgz",
@@ -27742,7 +28157,8 @@
     "vuex": {
       "version": "3.6.2",
       "resolved": "https://registry.npmjs.org/vuex/-/vuex-3.6.2.tgz",
-      "integrity": "sha512-ETW44IqCgBpVomy520DT5jf8n0zoCac+sxWnn+hMe/CzaSejb/eVw2YToiXYX+Ex/AuHHia28vWTq4goAexFbw=="
+      "integrity": "sha512-ETW44IqCgBpVomy520DT5jf8n0zoCac+sxWnn+hMe/CzaSejb/eVw2YToiXYX+Ex/AuHHia28vWTq4goAexFbw==",
+      "requires": {}
     },
     "watchpack": {
       "version": "1.7.5",

+ 7 - 0
package.json

@@ -8,8 +8,15 @@
     "lint": "vue-cli-service lint"
   },
   "dependencies": {
+    "axios": "^0.27.2",
     "core-js": "^3.6.5",
+    "element-ui": "^2.15.10",
+    "jsonwebtoken": "^8.5.1",
+    "lodash": "^4.17.21",
+    "moment": "^2.29.1",
+    "naf-core": "^0.1.2",
     "vue": "^2.6.11",
+    "vue-meta": "^2.4.0",
     "vue-router": "^3.2.0",
     "vuex": "^3.4.0"
   },

+ 1 - 26
src/App.vue

@@ -1,32 +1,7 @@
 <template>
   <div id="app">
-    <div id="nav">
-      <router-link to="/">Home</router-link> |
-      <router-link to="/about">About</router-link>
-    </div>
     <router-view />
   </div>
 </template>
 
-<style lang="less">
-#app {
-  font-family: Avenir, Helvetica, Arial, sans-serif;
-  -webkit-font-smoothing: antialiased;
-  -moz-osx-font-smoothing: grayscale;
-  text-align: center;
-  color: #2c3e50;
-}
-
-#nav {
-  padding: 30px;
-
-  a {
-    font-weight: bold;
-    color: #2c3e50;
-
-    &.router-link-exact-active {
-      color: #42b983;
-    }
-  }
-}
-</style>
+<style lang="less"></style>

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

@@ -0,0 +1,44 @@
+.header {
+  background-color: #242f42 !important;
+}
+.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);
+}
+
+.sidebar {
+  background-color: #242f42 !important;
+}
+.sidebar-el-menu:not(.el-menu--collapse) {
+  background-color: #242f42 !important;
+}
+.el-menu-item:hover {
+  background-color: #142832 !important;
+}
+.el-menu-item {
+  color: #ffffff;
+}
+.el-menu-item.is-active {
+  background-color: #142832 !important;
+}

+ 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: 100%;
+    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;
+}

+ 54 - 0
src/assets/css/theme-ele/color-ele.css

@@ -0,0 +1,54 @@
+.header {
+  background-color: #409eff !important;
+}
+.login-wrap {
+  background: rgba(56, 157, 170, 0.82);
+}
+.plugins-tips {
+  background: #f2f2f2;
+}
+.plugins-tips a {
+  color: #409eff;
+}
+.el-upload--text em {
+  color: #409eff;
+}
+.pure-button {
+  background: #409eff;
+}
+.pagination > .active > a,
+.pagination > .active > a:hover,
+.pagination > .active > a:focus,
+.pagination > .active > span,
+.pagination > .active > span:hover,
+.pagination > .active > span:focus {
+  background-color: #409eff !important;
+  border-color: #409eff !important;
+}
+.tags-li.active {
+  border: 1px solid #409eff !important;
+  background-color: #409eff !important;
+}
+.collapse-btn:hover {
+  background: #409eff;
+}
+
+.sidebar {
+  background-color: #409eff !important;
+}
+.el-menu {
+  background-color: #409eff !important;
+}
+.el-menu-item.is-active {
+  color: #ffffff;
+  background-color: #403AFF !important;
+}
+.el-menu-item:hover{
+   background-color: #403AFF !important;
+}
+.sidebar-el-menu:not(.el-menu--collapse) {
+  background-color: #409eff !important;
+}
+.el-submenu__title:hover {
+  background-color: #403AFF !important;
+}

二进制
src/assets/css/theme-ele/fonts/element-icons.ttf


二进制
src/assets/css/theme-ele/fonts/element-icons.woff


文件差异内容过多而无法显示
+ 1 - 0
src/assets/css/theme-ele/index.css


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

@@ -0,0 +1,54 @@
+.header {
+  background-color: #07c4a8 !important;
+}
+.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 !important;
+  background-color: #00d1b2 !important;
+}
+.collapse-btn:hover {
+  background: #00d1b2;
+}
+
+.sidebar {
+  background-color: #00d1b2 !important;
+}
+.el-menu {
+  background-color: #00d1b2 !important;
+}
+.el-menu-item.is-active {
+  color: #ffffff;
+  background-color: #00a78e !important;
+}
+.el-menu-item:hover {
+  background-color: #00a78e !important;
+}
+.sidebar-el-menu:not(.el-menu--collapse) {
+  background-color: #00d1b2 !important;
+}
+.el-submenu__title:hover {
+  background-color: #00a78e !important;
+}

二进制
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


+ 51 - 0
src/assets/css/theme-pink/color-pink.css

@@ -0,0 +1,51 @@
+.header {
+  background-color: #D23CE6 !important;
+}
+.login-wrap {
+  background: rgba(56, 157, 170, 0.82);
+}
+.plugins-tips {
+  background: #f2f2f2;
+}
+.plugins-tips a {
+  color: #D23CE6;
+}
+.el-upload--text em {
+  color: #D23CE6;
+}
+.pure-button {
+  background: #D23CE6;
+}
+.pagination > .active > a,
+.pagination > .active > a:hover,
+.pagination > .active > a:focus,
+.pagination > .active > span,
+.pagination > .active > span:hover,
+.pagination > .active > span:focus {
+  background-color: #D23CE6 !important;
+  border-color: #D23CE6 !important;
+}
+.tags-li.active {
+  border: 1px solid #D23CE6 !important;
+  background-color: #D23CE6 !important;
+}
+.collapse-btn:hover {
+  background: #D23CE6;
+}
+
+.sidebar {
+  background-color: #D23CE6 !important;
+}
+.el-menu {
+  background-color: #D23CE6 !important;
+}
+.el-menu-item.is-active {
+  color: #ffffff;
+  background-color: #963CE6 !important;
+}
+.el-menu-item:hover{
+   background-color: #963CE6 !important;
+}
+.sidebar-el-menu:not(.el-menu--collapse) {
+  background-color: #D23CE6 !important;
+}

二进制
src/assets/css/theme-pink/fonts/element-icons.ttf


二进制
src/assets/css/theme-pink/fonts/element-icons.woff


文件差异内容过多而无法显示
+ 1 - 0
src/assets/css/theme-pink/index.css


+ 51 - 0
src/assets/css/theme-red/color-red.css

@@ -0,0 +1,51 @@
+.header {
+  background-color: #e31d33 !important;
+}
+.login-wrap {
+  background: rgba(56, 157, 170, 0.82);
+}
+.plugins-tips {
+  background: #f2f2f2;
+}
+.plugins-tips a {
+  color: #e31d33;
+}
+.el-upload--text em {
+  color: #e31d33;
+}
+.pure-button {
+  background: #e31d33;
+}
+.pagination > .active > a,
+.pagination > .active > a:hover,
+.pagination > .active > a:focus,
+.pagination > .active > span,
+.pagination > .active > span:hover,
+.pagination > .active > span:focus {
+  background-color: #e31d33 !important;
+  border-color: #e31d33 !important;
+}
+.tags-li.active {
+  border: 1px solid #e31d33 !important;
+  background-color: #e31d33 !important;
+}
+.collapse-btn:hover {
+  background: #e31d33;
+}
+
+.sidebar {
+  background-color: #e31d33 !important;
+}
+.el-menu {
+  background-color: #e31d33 !important;
+}
+.el-menu-item.is-active {
+  color: #ffffff;
+  background-color: #640F33 !important;
+}
+.el-menu-item:hover{
+   background-color: #640F33 !important;
+}
+.sidebar-el-menu:not(.el-menu--collapse) {
+  background-color: #e31d33 !important;
+}

二进制
src/assets/css/theme-red/fonts/element-icons.ttf


二进制
src/assets/css/theme-red/fonts/element-icons.woff


文件差异内容过多而无法显示
+ 1 - 0
src/assets/css/theme-red/index.css


+ 255 - 0
src/assets/icon/iconfont.css

@@ -0,0 +1,255 @@
+@font-face {
+  font-family: "iconfont"; /* Project id 2726812 */
+  src: url('iconfont.woff2?t=1644564414295') format('woff2'),
+       url('iconfont.woff?t=1644564414295') format('woff'),
+       url('iconfont.ttf?t=1644564414295') format('truetype');
+}
+
+.iconfont {
+  font-family: "iconfont" !important;
+  font-size: 16px;
+  font-style: normal;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+}
+
+.icon-rencai:before {
+  content: "\e60d";
+}
+
+.icon-peiyang:before {
+  content: "\e618";
+}
+
+.icon-zhicheng:before {
+  content: "\e68f";
+}
+
+.icon-jishufuwu:before {
+  content: "\eb64";
+}
+
+.icon-fabiao:before {
+  content: "\e654";
+}
+
+.icon-huodong:before {
+  content: "\e6f0";
+}
+
+.icon-kepu:before {
+  content: "\e6a9";
+}
+
+.icon-qita:before {
+  content: "\e6fe";
+}
+
+.icon-huojiangqingkuang:before {
+  content: "\e61a";
+}
+
+.icon-lunwen:before {
+  content: "\e62a";
+}
+
+.icon-zhishichanquanziliao_zhishichanquan-shenfenzheng:before {
+  content: "\e603";
+}
+
+.icon-xiangmuxinxi:before {
+  content: "\e661";
+}
+
+.icon-icon:before {
+  content: "\e69a";
+}
+
+.icon-jixiaoguanli:before {
+  content: "\e60f";
+}
+
+.icon-liebiao:before {
+  content: "\e601";
+}
+
+.icon-dabianyanjiuneirong:before {
+  content: "\e602";
+}
+
+.icon-guding:before {
+  content: "\e66e";
+}
+
+.icon-banzhuren:before {
+  content: "\e61b";
+}
+
+.icon-liudong:before {
+  content: "\e610";
+}
+
+.icon-xinxiliebiao:before {
+  content: "\e63d";
+}
+
+.icon-jibenxinxi:before {
+  content: "\e619";
+}
+
+.icon-jiekou:before {
+  content: "\e638";
+}
+
+.icon-xitongcaidan:before {
+  content: "\e696";
+}
+
+.icon-goujian:before {
+  content: "\e651";
+}
+
+.icon-daimashengcheng:before {
+  content: "\e636";
+}
+
+.icon-shenhe:before {
+  content: "\e65b";
+}
+
+.icon-moban:before {
+  content: "\e635";
+}
+
+.icon-zhanghao:before {
+  content: "\e65f";
+}
+
+.icon-xiugaishenqing:before {
+  content: "\e611";
+}
+
+.icon-ceshishenqing:before {
+  content: "\eb61";
+}
+
+.icon-shangchuan-copy:before {
+  content: "\e7a4";
+}
+
+.icon-shujujiankong:before {
+  content: "\e655";
+}
+
+.icon-dingshirenwu:before {
+  content: "\e621";
+}
+
+.icon-fuwujiankong:before {
+  content: "\e6a3";
+}
+
+.icon-zaixianyonghu:before {
+  content: "\e6ad";
+}
+
+.icon-huancunjiankong:before {
+  content: "\e637";
+}
+
+.icon-rizhiguanli:before {
+  content: "\e60e";
+}
+
+.icon-tongzhigonggao:before {
+  content: "\e65e";
+}
+
+.icon-denglurizhi:before {
+  content: "\e694";
+}
+
+.icon-zidianguanli:before {
+  content: "\e668";
+}
+
+.icon-bumenguanli:before {
+  content: "\e608";
+}
+
+.icon-caozuorizhi:before {
+  content: "\e60a";
+}
+
+.icon-xitongguanli-caidanguanli:before {
+  content: "\e628";
+}
+
+.icon-xitongguanli-jiaoseguanli:before {
+  content: "\e627";
+}
+
+.icon-canshushezhi:before {
+  content: "\e6ab";
+}
+
+.icon-yonghuguanli:before {
+  content: "\e645";
+}
+
+.icon-gangweiguanli:before {
+  content: "\e6a4";
+}
+
+.icon-dangjian_duiwujianshe:before {
+  content: "\e67d";
+}
+
+.icon-xitonggongju:before {
+  content: "\e674";
+}
+
+.icon-structuremanage:before {
+  content: "\e629";
+}
+
+.icon-keyan:before {
+  content: "\e600";
+}
+
+.icon-gudingzichanguanli:before {
+  content: "\e61d";
+}
+
+.icon-jixiao:before {
+  content: "\e605";
+}
+
+.icon-huodongshijian:before {
+  content: "\e606";
+}
+
+.icon-chengguozhanshi:before {
+  content: "\e659";
+}
+
+.icon-shouye:before {
+  content: "\e8c6";
+}
+
+.icon-xitongguanli:before {
+  content: "\e614";
+}
+
+.icon-essential-information:before {
+  content: "\e612";
+}
+
+.icon-xueshuguanli:before {
+  content: "\e77f";
+}
+
+.icon-xitongjiankong:before {
+  content: "\e63c";
+}
+

文件差异内容过多而无法显示
+ 1 - 0
src/assets/icon/iconfont.js


+ 429 - 0
src/assets/icon/iconfont.json

@@ -0,0 +1,429 @@
+{
+  "id": "2726812",
+  "name": "基础动态研究管理平台",
+  "font_family": "iconfont",
+  "css_prefix_text": "icon-",
+  "description": "",
+  "glyphs": [
+    {
+      "icon_id": "3640193",
+      "name": "人才",
+      "font_class": "rencai",
+      "unicode": "e60d",
+      "unicode_decimal": 58893
+    },
+    {
+      "icon_id": "14690257",
+      "name": "培养",
+      "font_class": "peiyang",
+      "unicode": "e618",
+      "unicode_decimal": 58904
+    },
+    {
+      "icon_id": "14996003",
+      "name": "职称",
+      "font_class": "zhicheng",
+      "unicode": "e68f",
+      "unicode_decimal": 59023
+    },
+    {
+      "icon_id": "3868269",
+      "name": "技术服务",
+      "font_class": "jishufuwu",
+      "unicode": "eb64",
+      "unicode_decimal": 60260
+    },
+    {
+      "icon_id": "6548533",
+      "name": "发表",
+      "font_class": "fabiao",
+      "unicode": "e654",
+      "unicode_decimal": 58964
+    },
+    {
+      "icon_id": "6926770",
+      "name": "活动",
+      "font_class": "huodong",
+      "unicode": "e6f0",
+      "unicode_decimal": 59120
+    },
+    {
+      "icon_id": "11819079",
+      "name": "科普",
+      "font_class": "kepu",
+      "unicode": "e6a9",
+      "unicode_decimal": 59049
+    },
+    {
+      "icon_id": "687391",
+      "name": "其他",
+      "font_class": "qita",
+      "unicode": "e6fe",
+      "unicode_decimal": 59134
+    },
+    {
+      "icon_id": "3893266",
+      "name": "获奖情况",
+      "font_class": "huojiangqingkuang",
+      "unicode": "e61a",
+      "unicode_decimal": 58906
+    },
+    {
+      "icon_id": "6508142",
+      "name": "论文",
+      "font_class": "lunwen",
+      "unicode": "e62a",
+      "unicode_decimal": 58922
+    },
+    {
+      "icon_id": "11358804",
+      "name": "知识产权资料_知识产权-身份证",
+      "font_class": "zhishichanquanziliao_zhishichanquan-shenfenzheng",
+      "unicode": "e603",
+      "unicode_decimal": 58883
+    },
+    {
+      "icon_id": "8039248",
+      "name": "项目信息",
+      "font_class": "xiangmuxinxi",
+      "unicode": "e661",
+      "unicode_decimal": 58977
+    },
+    {
+      "icon_id": "1090745",
+      "name": "设备",
+      "font_class": "icon",
+      "unicode": "e69a",
+      "unicode_decimal": 59034
+    },
+    {
+      "icon_id": "7318940",
+      "name": "投入产出",
+      "font_class": "jixiaoguanli",
+      "unicode": "e60f",
+      "unicode_decimal": 58895
+    },
+    {
+      "icon_id": "5029",
+      "name": "列表",
+      "font_class": "liebiao",
+      "unicode": "e601",
+      "unicode_decimal": 58881
+    },
+    {
+      "icon_id": "3859727",
+      "name": "研究内容",
+      "font_class": "dabianyanjiuneirong",
+      "unicode": "e602",
+      "unicode_decimal": 58882
+    },
+    {
+      "icon_id": "6537190",
+      "name": "固定",
+      "font_class": "guding",
+      "unicode": "e66e",
+      "unicode_decimal": 58990
+    },
+    {
+      "icon_id": "11776602",
+      "name": "班主任",
+      "font_class": "banzhuren",
+      "unicode": "e61b",
+      "unicode_decimal": 58907
+    },
+    {
+      "icon_id": "22782353",
+      "name": "流动",
+      "font_class": "liudong",
+      "unicode": "e610",
+      "unicode_decimal": 58896
+    },
+    {
+      "icon_id": "978352",
+      "name": "信息列表",
+      "font_class": "xinxiliebiao",
+      "unicode": "e63d",
+      "unicode_decimal": 58941
+    },
+    {
+      "icon_id": "1376047",
+      "name": "基本信息",
+      "font_class": "jibenxinxi",
+      "unicode": "e619",
+      "unicode_decimal": 58905
+    },
+    {
+      "icon_id": "11520228",
+      "name": "接口",
+      "font_class": "jiekou",
+      "unicode": "e638",
+      "unicode_decimal": 58936
+    },
+    {
+      "icon_id": "666892",
+      "name": "系统菜单",
+      "font_class": "xitongcaidan",
+      "unicode": "e696",
+      "unicode_decimal": 59030
+    },
+    {
+      "icon_id": "12975227",
+      "name": "构建",
+      "font_class": "goujian",
+      "unicode": "e651",
+      "unicode_decimal": 58961
+    },
+    {
+      "icon_id": "24848379",
+      "name": "代码生成",
+      "font_class": "daimashengcheng",
+      "unicode": "e636",
+      "unicode_decimal": 58934
+    },
+    {
+      "icon_id": "623940",
+      "name": "审核",
+      "font_class": "shenhe",
+      "unicode": "e65b",
+      "unicode_decimal": 58971
+    },
+    {
+      "icon_id": "731583",
+      "name": "模板",
+      "font_class": "moban",
+      "unicode": "e635",
+      "unicode_decimal": 58933
+    },
+    {
+      "icon_id": "900157",
+      "name": "账号",
+      "font_class": "zhanghao",
+      "unicode": "e65f",
+      "unicode_decimal": 58975
+    },
+    {
+      "icon_id": "2129863",
+      "name": "修改申请",
+      "font_class": "xiugaishenqing",
+      "unicode": "e611",
+      "unicode_decimal": 58897
+    },
+    {
+      "icon_id": "3868271",
+      "name": "测试申请",
+      "font_class": "ceshishenqing",
+      "unicode": "eb61",
+      "unicode_decimal": 60257
+    },
+    {
+      "icon_id": "24847395",
+      "name": "上报",
+      "font_class": "shangchuan-copy",
+      "unicode": "e7a4",
+      "unicode_decimal": 59300
+    },
+    {
+      "icon_id": "11878039",
+      "name": "数据监控",
+      "font_class": "shujujiankong",
+      "unicode": "e655",
+      "unicode_decimal": 58965
+    },
+    {
+      "icon_id": "12896474",
+      "name": "定时任务",
+      "font_class": "dingshirenwu",
+      "unicode": "e621",
+      "unicode_decimal": 58913
+    },
+    {
+      "icon_id": "20853330",
+      "name": "服务监控",
+      "font_class": "fuwujiankong",
+      "unicode": "e6a3",
+      "unicode_decimal": 59043
+    },
+    {
+      "icon_id": "20853374",
+      "name": "在线用户",
+      "font_class": "zaixianyonghu",
+      "unicode": "e6ad",
+      "unicode_decimal": 59053
+    },
+    {
+      "icon_id": "24848420",
+      "name": "缓存监控",
+      "font_class": "huancunjiankong",
+      "unicode": "e637",
+      "unicode_decimal": 58935
+    },
+    {
+      "icon_id": "12694033",
+      "name": "日志管理",
+      "font_class": "rizhiguanli",
+      "unicode": "e60e",
+      "unicode_decimal": 58894
+    },
+    {
+      "icon_id": "570353",
+      "name": "通知公告",
+      "font_class": "tongzhigonggao",
+      "unicode": "e65e",
+      "unicode_decimal": 58974
+    },
+    {
+      "icon_id": "3978460",
+      "name": "登录日志",
+      "font_class": "denglurizhi",
+      "unicode": "e694",
+      "unicode_decimal": 59028
+    },
+    {
+      "icon_id": "8605754",
+      "name": "字典管理",
+      "font_class": "zidianguanli",
+      "unicode": "e668",
+      "unicode_decimal": 58984
+    },
+    {
+      "icon_id": "11409124",
+      "name": "部门管理",
+      "font_class": "bumenguanli",
+      "unicode": "e608",
+      "unicode_decimal": 58888
+    },
+    {
+      "icon_id": "12694009",
+      "name": "操作日志",
+      "font_class": "caozuorizhi",
+      "unicode": "e60a",
+      "unicode_decimal": 58890
+    },
+    {
+      "icon_id": "12889884",
+      "name": "系统管理-菜单管理",
+      "font_class": "xitongguanli-caidanguanli",
+      "unicode": "e628",
+      "unicode_decimal": 58920
+    },
+    {
+      "icon_id": "12889885",
+      "name": "系统管理-角色管理",
+      "font_class": "xitongguanli-jiaoseguanli",
+      "unicode": "e627",
+      "unicode_decimal": 58919
+    },
+    {
+      "icon_id": "15643731",
+      "name": "参数设置",
+      "font_class": "canshushezhi",
+      "unicode": "e6ab",
+      "unicode_decimal": 59051
+    },
+    {
+      "icon_id": "18321645",
+      "name": "用户管理",
+      "font_class": "yonghuguanli",
+      "unicode": "e645",
+      "unicode_decimal": 58949
+    },
+    {
+      "icon_id": "20853332",
+      "name": "岗位管理",
+      "font_class": "gangweiguanli",
+      "unicode": "e6a4",
+      "unicode_decimal": 59044
+    },
+    {
+      "icon_id": "23245152",
+      "name": "党建_队伍建设",
+      "font_class": "dangjian_duiwujianshe",
+      "unicode": "e67d",
+      "unicode_decimal": 59005
+    },
+    {
+      "icon_id": "576281",
+      "name": "系统工具",
+      "font_class": "xitonggongju",
+      "unicode": "e674",
+      "unicode_decimal": 58996
+    },
+    {
+      "icon_id": "2204157",
+      "name": "员工,人员,结构、管理",
+      "font_class": "structuremanage",
+      "unicode": "e629",
+      "unicode_decimal": 58921
+    },
+    {
+      "icon_id": "2584374",
+      "name": "科研",
+      "font_class": "keyan",
+      "unicode": "e600",
+      "unicode_decimal": 58880
+    },
+    {
+      "icon_id": "2959021",
+      "name": "固定资产管理",
+      "font_class": "gudingzichanguanli",
+      "unicode": "e61d",
+      "unicode_decimal": 58909
+    },
+    {
+      "icon_id": "4161081",
+      "name": "绩效",
+      "font_class": "jixiao",
+      "unicode": "e605",
+      "unicode_decimal": 58885
+    },
+    {
+      "icon_id": "5056403",
+      "name": "活动时间",
+      "font_class": "huodongshijian",
+      "unicode": "e606",
+      "unicode_decimal": 58886
+    },
+    {
+      "icon_id": "5106733",
+      "name": "成果展示",
+      "font_class": "chengguozhanshi",
+      "unicode": "e659",
+      "unicode_decimal": 58969
+    },
+    {
+      "icon_id": "11372774",
+      "name": "首页",
+      "font_class": "shouye",
+      "unicode": "e8c6",
+      "unicode_decimal": 59590
+    },
+    {
+      "icon_id": "12694055",
+      "name": "系统管理",
+      "font_class": "xitongguanli",
+      "unicode": "e614",
+      "unicode_decimal": 58900
+    },
+    {
+      "icon_id": "14440235",
+      "name": "基本信息",
+      "font_class": "essential-information",
+      "unicode": "e612",
+      "unicode_decimal": 58898
+    },
+    {
+      "icon_id": "15562232",
+      "name": "学术管理",
+      "font_class": "xueshuguanli",
+      "unicode": "e77f",
+      "unicode_decimal": 59263
+    },
+    {
+      "icon_id": "24848551",
+      "name": "系统监控",
+      "font_class": "xitongjiankong",
+      "unicode": "e63c",
+      "unicode_decimal": 58940
+    }
+  ]
+}

二进制
src/assets/icon/iconfont.ttf


二进制
src/assets/icon/iconfont.woff


二进制
src/assets/icon/iconfont.woff2


二进制
src/assets/img/file.png


二进制
src/assets/img/img.jpg


二进制
src/assets/login-background.jpg


二进制
src/assets/logo.png


二进制
src/assets/yzm.gif


+ 0 - 130
src/components/HelloWorld.vue

@@ -1,130 +0,0 @@
-<template>
-  <div class="hello">
-    <h1>{{ msg }}</h1>
-    <p>
-      For a guide and recipes on how to configure / customize this project,<br />
-      check out the
-      <a href="https://cli.vuejs.org" target="_blank" rel="noopener"
-        >vue-cli documentation</a
-      >.
-    </p>
-    <h3>Installed CLI Plugins</h3>
-    <ul>
-      <li>
-        <a
-          href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-babel"
-          target="_blank"
-          rel="noopener"
-          >babel</a
-        >
-      </li>
-      <li>
-        <a
-          href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-router"
-          target="_blank"
-          rel="noopener"
-          >router</a
-        >
-      </li>
-      <li>
-        <a
-          href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-vuex"
-          target="_blank"
-          rel="noopener"
-          >vuex</a
-        >
-      </li>
-      <li>
-        <a
-          href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-eslint"
-          target="_blank"
-          rel="noopener"
-          >eslint</a
-        >
-      </li>
-    </ul>
-    <h3>Essential Links</h3>
-    <ul>
-      <li>
-        <a href="https://vuejs.org" target="_blank" rel="noopener">Core Docs</a>
-      </li>
-      <li>
-        <a href="https://forum.vuejs.org" target="_blank" rel="noopener"
-          >Forum</a
-        >
-      </li>
-      <li>
-        <a href="https://chat.vuejs.org" target="_blank" rel="noopener"
-          >Community Chat</a
-        >
-      </li>
-      <li>
-        <a href="https://twitter.com/vuejs" target="_blank" rel="noopener"
-          >Twitter</a
-        >
-      </li>
-      <li>
-        <a href="https://news.vuejs.org" target="_blank" rel="noopener">News</a>
-      </li>
-    </ul>
-    <h3>Ecosystem</h3>
-    <ul>
-      <li>
-        <a href="https://router.vuejs.org" target="_blank" rel="noopener"
-          >vue-router</a
-        >
-      </li>
-      <li>
-        <a href="https://vuex.vuejs.org" target="_blank" rel="noopener">vuex</a>
-      </li>
-      <li>
-        <a
-          href="https://github.com/vuejs/vue-devtools#vue-devtools"
-          target="_blank"
-          rel="noopener"
-          >vue-devtools</a
-        >
-      </li>
-      <li>
-        <a href="https://vue-loader.vuejs.org" target="_blank" rel="noopener"
-          >vue-loader</a
-        >
-      </li>
-      <li>
-        <a
-          href="https://github.com/vuejs/awesome-vue"
-          target="_blank"
-          rel="noopener"
-          >awesome-vue</a
-        >
-      </li>
-    </ul>
-  </div>
-</template>
-
-<script>
-export default {
-  name: "HelloWorld",
-  props: {
-    msg: String,
-  },
-};
-</script>
-
-<!-- Add "scoped" attribute to limit CSS to this component only -->
-<style scoped lang="less">
-h3 {
-  margin: 40px 0 0;
-}
-ul {
-  list-style-type: none;
-  padding: 0;
-}
-li {
-  display: inline-block;
-  margin: 0 10px;
-}
-a {
-  color: #42b983;
-}
-</style>

+ 150 - 0
src/components/usual/c-form.vue

@@ -0,0 +1,150 @@
+<template>
+  <div id="c-form">
+    <el-form ref="form" :model="form" :rules="rules" :label-width="labelWidth" class="form" size="small" @submit.native.prevent>
+      <template v-for="(item, index) in fields">
+        <el-form-item v-if="display(item)" :key="'form-field-' + index" :label="getField('label', item)" :prop="item.model" :required="item.required">
+          <template v-if="!item.custom">
+            <template v-if="item.type === `date` || item.type === `datetime`">
+              <el-date-picker
+                v-model="form[item.model]"
+                :type="item.type"
+                placeholder="请选择"
+                :format="item.type === 'date' ? 'yyyy-MM-dd' : 'yyyy-MM-dd HH:mm:ss'"
+                :value-format="item.type === 'date' ? 'yyyy-MM-dd' : 'yyyy-MM-dd HH:mm:ss'"
+                v-bind="item.options"
+                style="width: 100%"
+                @change="dataChange(item.model)"
+              >
+              </el-date-picker>
+            </template>
+            <template v-else-if="item.type === `year` || item.type === `week` || item.type === `day`">
+              <el-date-picker
+                v-model="form[item.model]"
+                :type="item.type"
+                placeholder="请选择"
+                :format="`${item.type === 'year' ? 'yyyy' : item.type === 'week' ? 'MM' : 'dd'}`"
+                :value-format="`${item.type === 'year' ? 'yyyy' : item.type === 'week' ? 'MM' : 'dd'}`"
+                v-bind="item.options"
+                style="width: 100%"
+                @change="dataChange(item.model)"
+              >
+              </el-date-picker>
+            </template>
+            <template v-else-if="item.type === 'time'">
+              <el-time-picker
+                v-model="form[item.model]"
+                placeholder="请选择时间"
+                format="HH:mm"
+                value-format="HH:mm"
+                @change="dataChange(item.model)"
+              ></el-time-picker>
+            </template>
+            <template v-else-if="item.type === 'radio'">
+              <el-radio-group v-model="form[item.model]" size="mini" @change="dataChange(item.model)" v-bind="item.options">
+                <slot :name="item.model" v-bind="{ item }"></slot>
+              </el-radio-group>
+            </template>
+            <template v-else-if="item.type === 'checkbox'">
+              <el-checkbox-group v-model="form[item.model]" @change="dataChange(item.model)" v-bind="item.options">
+                <slot :name="item.model" v-bind="{ item }"></slot>
+              </el-checkbox-group>
+            </template>
+            <template v-else-if="item.type === 'select'">
+              <el-select
+                v-model="form[item.model]"
+                v-bind="item.options"
+                filterable
+                clearable
+                @change="dataChange(item.model)"
+                style="width: 100%"
+                :disabled="item.readonly"
+              >
+                <slot :name="item.model" v-bind="{ item }"></slot>
+              </el-select>
+            </template>
+            <template v-else-if="item.type === 'textarea'">
+              <el-input clearable v-model="form[item.model]" type="textarea" :autosize="{ minRows: 3, maxRows: 5 }" @change="dataChange(item.model)"></el-input>
+            </template>
+            <template v-else>
+              <el-input
+                clearable
+                v-model="form[item.model]"
+                :type="getField('type', item)"
+                :placeholder="getField('placeholder', item)"
+                :show-password="getField('type', item) === 'password'"
+                v-bind="item.options"
+                :readonly="item.readonly"
+                @change="dataChange(item.model)"
+              ></el-input>
+            </template>
+          </template>
+          <template v-else>
+            <slot :name="item.model" v-bind="{ item }"></slot>
+          </template>
+        </el-form-item>
+      </template>
+      <el-form-item label="" class="btn">
+        <slot name="submit">
+          <el-row type="flex" align="middle" justify="start">
+            <el-col :span="6">
+              <el-button type="primary" @click="save" v-use="'save'">{{ submitText }}</el-button>
+            </el-col>
+          </el-row>
+        </slot>
+      </el-form-item>
+    </el-form>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'c-form',
+  props: {
+    fields: { type: Array, default: () => [{ readonly: false }] },
+    rules: { type: Object, default: () => {} },
+    labelWidth: { type: String, default: '120px' },
+    submitText: { type: String, default: '保存' },
+    form: null,
+    reset: { type: Boolean, default: false },
+  },
+  model: {
+    prop: 'form',
+    event: 'change',
+  },
+  components: {},
+  data: function () {
+    return {};
+  },
+  methods: {
+    getField(item, data) {
+      let res = _.get(data, item, null);
+      if (item === 'type') res = res === null ? `text` : res;
+      if (item === 'placeholder') res = res === null ? `请输入${data.label}` : res;
+      if (item === 'required') res = res === null ? false : res;
+      if (item === `error`) res = res === null ? `${data.label}错误` : res;
+      return res;
+    },
+    save() {
+      this.$refs['form'].validate((valid) => {
+        if (valid) {
+          this.$emit(`save`, { data: JSON.parse(JSON.stringify(this.form)) });
+          if (this.reset) this.$refs.form.resetFields();
+        } else {
+          console.warn('form validate error!!!');
+        }
+      });
+    },
+    display(field) {
+      let dis = _.get(field, `display`);
+      if (!_.isFunction(dis)) return true;
+      else return dis(field, this.form);
+    },
+    dataChange(model) {
+      const value = JSON.parse(JSON.stringify(this.form[model]));
+      this.$emit('dataChange', { model, value });
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped></style>

+ 19 - 0
src/components/usual/c-search.vue

@@ -0,0 +1,19 @@
+<template>
+  <div id="c-search">
+    <p>c-search</p>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'c-search',
+  props: {},
+  components: {},
+  data: function () {
+    return {};
+  },
+  methods: {},
+};
+</script>
+
+<style lang="less" scoped></style>

+ 288 - 0
src/components/usual/c-table.vue

@@ -0,0 +1,288 @@
+<template>
+  <div id="c-table">
+    <el-table
+      ref="table"
+      :row-key="rowKey"
+      :data="data"
+      border
+      stripe
+      size="mini"
+      :max-height="height !== null ? height : ''"
+      @select="handleSelectionChange"
+      @select-all="handleSelectAll"
+      :show-summary="useSum"
+      @row-click="rowClick"
+      :summary-method="computedSum"
+    >
+      <el-table-column type="selection" width="55" v-if="select" :prop="rowKey" :reserve-selection="true"> </el-table-column>
+      <template v-for="(item, index) in fields">
+        <template v-if="item.custom">
+          <el-table-column :key="index" align="center" :label="item.label" v-bind="item.options" :show-overflow-tooltip="item.showTip || true">
+            <template v-slot="{ row }">
+              <slot :name="item.model" v-bind="{ item, row }"></slot>
+            </template>
+          </el-table-column>
+        </template>
+        <template v-else>
+          <el-table-column
+            :key="index"
+            align="center"
+            :label="item.label"
+            :prop="item.model"
+            :formatter="toFormatter"
+            :sortable="true"
+            v-bind="item.options"
+            :show-overflow-tooltip="item.showTip || true"
+          >
+          </el-table-column>
+        </template>
+      </template>
+      <template v-if="opera.length > 0">
+        <el-table-column label="操作" align="center" :width="operaWidth">
+          <template v-slot="{ row, $index }">
+            <template v-for="(item, index) in opera">
+              <template v-if="display(item, row)">
+                <template v-if="vOpera">
+                  <el-link
+                    v-opera="item.method"
+                    :key="`${item.model}-column-${index}`"
+                    :type="item.type || 'primary'"
+                    :icon="item.icon || ''"
+                    size="mini"
+                    style="padding-right: 10px"
+                    :underline="false"
+                    @click="handleOpera(row, item.method, item.confirm, item.methodZh, item.label, $index, item.confirmWord)"
+                  >
+                    {{ item.label }}
+                  </el-link>
+                </template>
+                <template v-else>
+                  <el-link
+                    :key="`${item.model}-column-${index}`"
+                    :type="item.type || 'primary'"
+                    :icon="item.icon || ''"
+                    size="mini"
+                    style="padding-right: 10px"
+                    :underline="false"
+                    @click="handleOpera(row, item.method, item.confirm, item.methodZh, item.label, $index, item.confirmWord)"
+                  >
+                    {{ item.label }}
+                  </el-link>
+                </template>
+              </template>
+            </template>
+          </template>
+        </el-table-column>
+      </template>
+    </el-table>
+    <el-row type="flex" align="middle" justify="end" style="padding-top: 1rem" v-if="usePage">
+      <el-col :span="24" style="text-align: right">
+        <el-pagination
+          background
+          layout="total, prev, pager, next"
+          :page-sizes="[10, 15, 20, 50, 100]"
+          :total="total"
+          :page-size="limit"
+          :current-page.sync="currentPage"
+          @current-change="changePage"
+          @size-change="sizeChange"
+        >
+        </el-pagination>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'c-table',
+  props: {
+    fields: { type: Array, required: true },
+    data: { type: Array, required: true },
+    opera: { type: Array, default: () => [] },
+    rowKey: { type: String, default: '_id' },
+    select: { type: Boolean, default: false },
+    selected: { type: Array, default: () => [] },
+    usePage: { type: Boolean, default: true },
+    total: { type: Number, default: 0 },
+    useSum: { type: Boolean, default: false },
+    sumcol: { type: Array, default: () => [] },
+    sumres: { type: String, default: 'total' },
+    limit: { type: Number, default: 10 },
+    height: null,
+    operaWidth: { type: Number, default: 200 },
+    vOpera: { type: Boolean, default: true },
+  },
+  components: {},
+  data: function () {
+    return {
+      pageSelected: [],
+      currentPage: 1,
+    };
+  },
+  created() {},
+  methods: {
+    toFormatter(row, column, cellValue, index) {
+      let this_fields = this.fields.filter((fil) => fil.model === column.property);
+      if (this_fields.length > 0) {
+        let format = _.get(this_fields[0], `format`, false);
+        if (format) {
+          let res;
+          if (_.isFunction(format)) {
+            res = format(cellValue);
+          } else {
+            res = this.toFormat({
+              model: this_fields[0].model,
+              value: cellValue,
+            });
+          }
+          return res;
+        } else return cellValue;
+      }
+    },
+    handleOpera(data, method, confirm = false, methodZh, label, index, confirmWord) {
+      let self = true;
+      if (_.isFunction(methodZh)) {
+        methodZh = methodZh(data);
+      } else if (!_.isString(methodZh)) {
+        methodZh = label;
+        self = false;
+      }
+      if (confirm) {
+        let word = self ? methodZh : `您确认${methodZh}该数据?`;
+        if (confirmWord) word = confirmWord;
+        this.$confirm(word, '提示', {
+          confirmButtonText: '确定',
+          cancelButtonText: '取消',
+          type: 'warning',
+        })
+          .then(() => {
+            this.$emit('opera', { data, index, method });
+          })
+          .catch(() => {});
+      } else {
+        this.$emit('opera', { data, index, method });
+      }
+    },
+    handleSelectionChange(selection, row) {
+      //根据row是否再pageSelected中,判断是添加还是删除
+      let isSelecteds = _.cloneDeep(this.pageSelected);
+      const is_has = isSelecteds.findIndex((f) => f[this.rowKey] === row[this.rowKey]);
+      if (is_has <= -1) {
+        // 没有找到,属于添加
+        isSelecteds.push(row);
+      } else {
+        // 找到了,删除
+        isSelecteds.splice(is_has, 1);
+      }
+      this.$set(this, 'pageSelected', isSelecteds);
+      this.$emit(`handleSelect`, isSelecteds);
+    },
+    handleSelectAll(selection) {
+      //处于没全选状态,选择之后一定是全选,只有处于全选状态时,才会反选(全取消)
+      // console.log(selection);
+      let res = [];
+      if (selection.length > 0) {
+        //全选
+        res = _.uniqBy(this.pageSelected.concat(selection), this.rowKey);
+      } else {
+        //全取消
+        res = _.differenceBy(this.pageSelected, this.data, this.rowKey);
+      }
+      this.$set(this, `pageSelected`, res);
+      this.$emit(`handleSelect`, res);
+    },
+    initSelection() {
+      this.$nextTick(() => {
+        this.$refs.table.clearSelection();
+        this.selected.forEach((info) => {
+          let d = this.data.filter((p) => p._id === info._id);
+          if (d.length > 0) this.$refs.table.toggleRowSelection(d[0]);
+        });
+      });
+    },
+    selectReset() {
+      this.$refs.table.clearSelection();
+    },
+    changePage(page = this.currentPage) {
+      this.$emit('query', { skip: (page - 1) * this.limit, limit: this.limit, ...this.searchInfo });
+    },
+    sizeChange(limit) {
+      this.limit = limit;
+      this.currentPage = 1;
+      this.$emit('query', { skip: 0, limit: this.limit, ...this.searchInfo });
+    },
+    rowClick(row, column, event) {
+      this.$emit(`rowClick`, row);
+    },
+    display(item, row) {
+      let display = _.get(item, `display`, true);
+      if (display === true) return true;
+      else {
+        let res = display(row);
+        return res;
+      }
+    },
+    // 计算合计
+    computedSum({ columns, data }) {
+      if (columns.length <= 0 || data.length <= 0) return '';
+      const result = [];
+      const reg = new RegExp(/^\d+$/);
+      for (const column of columns) {
+        // 判断有没有prop属性
+        const prop = _.get(column, 'property');
+        if (!prop) {
+          result.push('');
+          continue;
+        }
+        // 判断是否需要计算
+        const inlist = this.sumcol.find((f) => f == prop);
+        if (!inlist) {
+          result.push('');
+          continue;
+        }
+        let res = 0;
+        // 整理出要计算的属性(只取出数字或者可以为数字的值)
+        const resetList = data.map((i) => {
+          const d = _.get(i, prop);
+          const res = reg.test(d);
+          if (res) return d * 1;
+          else return 0;
+        });
+        if (this.sumres === 'total') {
+          res = this.totalComputed(resetList);
+        } else if (this.sumres === 'avg') {
+          res = this.avgComputed(resetList);
+        } else if (this.sumres === 'max') {
+          res = this.maxComputed(resetList);
+        } else if (this.sumres === 'min') {
+          res = this.minComputed(resetList);
+        }
+        result.push(res);
+      }
+      result[0] = '合计';
+      return result;
+    },
+    // 合计计算
+    totalComputed(data) {
+      const total = data.reduce((p, n) => p + n, 0);
+      return total;
+    },
+    // 平均值计算
+    avgComputed(data) {
+      const total = this.totalComputed(data);
+      return _.round(_.divide(total, data.length), 2);
+    },
+    // 最大值计算
+    maxComputed(data) {
+      return _.max(data);
+    },
+    // 最小值计算
+    minComputed(data) {
+      return _.min(data);
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped></style>

+ 116 - 0
src/layout/Header.vue

@@ -0,0 +1,116 @@
+<template>
+  <div id="Header">
+    <el-row>
+      <el-col :span="24" class="main header">
+        <el-col :span="24" class="one">
+          <el-col :span="8" class="left">
+            <!-- <span @click="collapseChage">
+              <i v-if="!collapse" class="el-icon-s-fold"></i>
+              <i v-else class="el-icon-s-unfold"></i>
+            </span> -->
+            <span>{{ siteInfo.zhTitle }}-管理中心</span>
+          </el-col>
+          <el-col :span="16" class="right">
+            <!-- <notice v-if="user.lab_id"></notice> -->
+            <i class="el-icon-user-solid"></i>
+            <span>{{ user.name || '游客' }}</span>
+            <!-- <span>游客</span> -->
+            <el-button type="danger" size="mini" @click="logout">退出登录</el-button>
+          </el-col>
+        </el-col>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script>
+const _ = require('lodash');
+const { siteInfo } = require('./data/site');
+import { mapState, mapMutations, createNamespacedHelpers } from 'vuex';
+import bus from './bus';
+export default {
+  name: 'Header',
+  props: {},
+  components: {},
+  data: function () {
+    return { collapse: false, siteInfo: siteInfo, options: {} };
+  },
+  created() {
+    const { options } = this.$router;
+    this.$set(this, `options`, options);
+  },
+  methods: {
+    ...mapMutations(['deleteUser']),
+    // 侧边栏折叠
+    collapseChage() {
+      this.collapse = !this.collapse;
+      bus.$emit('collapse', this.collapse);
+    },
+    // 退出登录
+    logout() {
+      this.deleteUser();
+      // 路由也得删除,但是没有删除路由的API,所以用replace来替换下
+      const base = _.get(this.$router, 'options.base', '');
+      window.location.replace(`/${base}/login`);
+      // this.$router.push('/login');
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+  mounted() {
+    if (document.body.clientWidth < 1500) {
+      this.collapseChage();
+    }
+  },
+  watch: {
+    test: {
+      deep: true,
+      immediate: true,
+      handler(val) {},
+    },
+  },
+};
+</script>
+
+<style lang="less" 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;
+      word-break: keep-all;
+      white-space: nowrap;
+      i {
+        position: relative;
+        top: 5px;
+        margin: 0px 15px;
+        font-size: 30px;
+        color: #fff;
+      }
+      span {
+        color: #fff;
+        font-size: 16px;
+        padding: 0 15px 0 0px;
+      }
+    }
+  }
+}
+</style>

+ 81 - 0
src/layout/Home.vue

@@ -0,0 +1,81 @@
+<template>
+  <div id="Home">
+    <el-container>
+      <el-header style="padding: 0"><v-head></v-head></el-header>
+      <el-container>
+        <el-aside width="200px"><v-sidebar></v-sidebar></el-aside>
+        <el-main>
+          <div class="content-box" :class="{ 'content-collapse': collapse }">
+            <v-tags></v-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">
+                    <el-scrollbar style="height: 81vh">
+                      <router-view style="height: 80vh"></router-view>
+                    </el-scrollbar>
+                  </el-col>
+                </el-row>
+              </transition>
+              <el-backtop target=".content"></el-backtop>
+            </el-col>
+          </div>
+        </el-main>
+      </el-container>
+    </el-container>
+  </div>
+</template>
+
+<script>
+import vHead from './Header.vue';
+import vSidebar from './Sidebar.vue';
+import vTags from './Tags.vue';
+import breadcrumb from './breadcrumb.vue';
+import bus from './bus';
+import { mapState, createNamespacedHelpers } from 'vuex';
+export default {
+  name: 'Home',
+  props: {},
+  components: { vHead, vSidebar, vTags, breadcrumb },
+  data: function () {
+    return {
+      collapse: false,
+      tagsList: [],
+    };
+  },
+  created() {
+    bus.$on('collapse-content', (msg) => {
+      this.collapse = msg;
+    });
+    // 只有在标签页列表里的页面才使用keep-alive,即关闭标签之后就不保存到内存中了。
+    bus.$on('tags', (msg) => {
+      let arr = [];
+      for (let i = 0, len = msg.length; i < len; i++) {
+        msg[i].name && arr.push(msg[i].name);
+      }
+      this.tagsList = arr;
+    });
+  },
+  methods: {},
+  computed: {
+    ...mapState(['user']),
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+  watch: {
+    test: {
+      deep: true,
+      immediate: true,
+      handler(val) {},
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped>
+/deep/ .el-scrollbar__wrap {
+  overflow-x: hidden !important;
+}
+</style>

+ 136 - 0
src/layout/Sidebar.vue

@@ -0,0 +1,136 @@
+<template>
+  <div id="Sidebar">
+    <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" unique-opened router>
+            <template v-for="item in items">
+              <!-- 二级菜单 -->
+              <template v-if="item.type === '0'">
+                <el-submenu :index="item._id || item.index" :key="item._id">
+                  <template slot="title">
+                    <i :class="['iconfont', item.icon]"></i>
+                    <span slot="title">{{ item.name }}</span>
+                  </template>
+                  <template v-for="subItem in item.children">
+                    <!-- 三级菜单 -->
+                    <el-submenu
+                      v-if="subItem.children && subItem.children.length > 0 && subItem.children.every((f) => f.type === '0' || f.type === '1')"
+                      :index="subItem._id"
+                      :key="subItem._id"
+                    >
+                      <template slot="title">
+                        <i :class="['iconfont', subItem.icon]"></i>
+                        <span slot="title">{{ subItem.name }}</span>
+                      </template>
+                      <el-menu-item v-for="(threeItem, i) in subItem.children" :key="i" :index="threeItem.path">
+                        <template slot="title">
+                          <i :class="['iconfont', threeItem.icon]"></i>
+                          <span slot="title">{{ threeItem.name }}</span>
+                        </template>
+                      </el-menu-item>
+                    </el-submenu>
+                    <el-menu-item v-else :index="subItem.path" :key="subItem.path">
+                      <template slot="title">
+                        <i :class="['iconfont', subItem.icon]"></i>
+                        <span slot="title">{{ subItem.name }}</span>
+                      </template>
+                    </el-menu-item>
+                  </template>
+                </el-submenu>
+              </template>
+              <!-- 一级菜单 -->
+              <template v-else>
+                <el-menu-item class="first" :index="item.path" :key="item.path">
+                  <i :class="['iconfont', item.icon]"></i>
+                  <span slot="title">{{ item.name }}</span>
+                </el-menu-item>
+              </template>
+            </template>
+          </el-menu>
+        </el-col>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script>
+const { system, adminMenu } = require('./data/menu');
+const { menuInfo } = require('./data/site');
+import { mapState, createNamespacedHelpers } from 'vuex';
+import _ from 'lodash';
+import bus from './bus';
+export default {
+  name: 'Sidebar',
+  props: {},
+  components: {},
+  data: function () {
+    return {
+      menuInfo: menuInfo,
+      collapse: false,
+      items: [...system, ...adminMenu],
+    };
+  },
+  created() {
+    bus.$on('collapse', (msg) => {
+      this.collapse = msg;
+      bus.$emit('collapse-content', msg);
+    });
+  },
+  methods: {
+    async getMenu() {
+      const { options } = this.$router;
+      if (!options) {
+        console.warn('获取菜单:解析base错误');
+        return;
+      }
+    },
+  },
+  computed: {
+    ...mapState(['user', 'menuList']),
+    onRoutes() {
+      return this.$route.path;
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+  watch: {
+    user: {
+      deep: true,
+      immediate: true,
+      handler(val) {
+        this.getMenu();
+      },
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped>
+.sidebar {
+  display: block;
+  position: absolute;
+  left: 0;
+  top: 60px;
+  bottom: 0;
+  overflow-y: scroll;
+}
+.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>

+ 201 - 0
src/layout/Tags.vue

@@ -0,0 +1,201 @@
+<template>
+  <div id="Tags">
+    <el-row v-if="showTags">
+      <el-col :span="24" class="main">
+        <el-col :span="24" class="one">
+          <el-col :span="23" class="left">
+            <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)"><i class="el-icon-close"></i></span> -->
+              </li>
+            </ul>
+          </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>
+import bus from './bus';
+import { mapState, createNamespacedHelpers } from 'vuex';
+export default {
+  name: 'Tags',
+  props: {},
+  components: {},
+  data: function () {
+    return {
+      tagsList: [],
+    };
+  },
+  created() {
+    this.setTags(this.$route);
+    // 监听关闭当前页面的标签页
+    bus.$on('close_current_tags', () => {
+      for (let i = 0, len = this.tagsList.length; i < len; i++) {
+        const item = this.tagsList[i];
+        if (item.path === this.$route.fullPath) {
+          if (i < len - 1) {
+            this.$router.push(this.tagsList[i + 1].path);
+          } else if (i > 0) {
+            this.$router.push(this.tagsList[i - 1].path);
+          } else {
+            this.$router.push('/');
+          }
+          this.tagsList.splice(i, 1);
+          break;
+        }
+      }
+    });
+  },
+  methods: {
+    isActive(path) {
+      return path === this.$route.fullPath;
+    },
+    // 打开标签
+    handleTags(command) {
+      command === 'other' ? this.closeOther() : this.closeAll();
+    },
+    // 关闭单个标签
+    closeTags(index) {
+      const delItem = this.tagsList.splice(index, 1)[0];
+      const item = this.tagsList[index] ? this.tagsList[index] : this.tagsList[index - 1];
+      if (item) {
+        delItem.path === this.$route.fullPath && this.$router.push(item.path);
+      } else {
+        this.$router.push('/adminCenter/homeIndex');
+      }
+    },
+    // 关闭全部标签
+    closeAll() {
+      this.tagsList = [];
+      this.$router.push('/adminCenter/homeIndex');
+    },
+    // 关闭其他标签
+    closeOther() {
+      const curItem = this.tagsList.filter((item) => {
+        return item.path === this.$route.fullPath;
+      });
+      this.tagsList = curItem;
+    },
+    // 设置标签
+    setTags(route) {
+      const isExist = this.tagsList.some((item) => {
+        return item.path === route.fullPath;
+      });
+      if (!isExist) {
+        if (this.tagsList.length >= 8) {
+          this.tagsList.shift();
+        }
+        this.tagsList.push({
+          title: route.meta.title,
+          path: route.fullPath,
+          name: route.matched[1].components.default.name,
+        });
+      }
+      bus.$emit('tags', this.tagsList);
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+    showTags() {
+      return this.tagsList.length > 0;
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+  watch: {
+    $route(newValue, oldValue) {
+      let index = this.tagsList.findIndex((i) => i.path == oldValue.path);
+      this.tagsList.splice(index, 1);
+      this.setTags(newValue);
+    },
+  },
+};
+</script>
+
+<style lang="less" 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>

+ 33 - 0
src/layout/breadcrumb.vue

@@ -0,0 +1,33 @@
+<template>
+  <div id="breadcrumb">
+    <el-row>
+      <el-col :span="24" class="crumbs">
+        <el-breadcrumb separator="/">
+          <el-breadcrumb-item> <i class="el-icon-s-grid"></i> {{ breadcrumbTitle }} </el-breadcrumb-item>
+        </el-breadcrumb>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script>
+import { mapState, createNamespacedHelpers } from 'vuex';
+export default {
+  metaInfo: {},
+  name: 'breadcrumb',
+  props: {
+    breadcrumbTitle: { type: String },
+  },
+  components: {},
+  data: function() {
+    return {};
+  },
+  created() {},
+  methods: {},
+  computed: {
+    ...mapState(['user']),
+  },
+};
+</script>
+
+<style lang="less" scoped></style>

+ 6 - 0
src/layout/bus.js

@@ -0,0 +1,6 @@
+import Vue from 'vue';
+
+// 使用 Event Bus
+const bus = new Vue();
+
+export default bus;

+ 162 - 0
src/layout/data/menu.js

@@ -0,0 +1,162 @@
+export const system = [
+  {
+    icon: 'icon-shouye',
+    path: '/',
+    name: '系统首页',
+    index: '1',
+  },
+];
+export const adminMenu = [
+  {
+    icon: 'icon-shouye',
+    // path: '/',
+    name: '系统管理',
+    index: '2',
+    type: '0',
+    children: [
+      {
+        icon: 'icon-rencai',
+        path: '/system/dict',
+        name: '字典设置',
+        index: '2-1',
+      },
+    ],
+  },
+];
+// 科研项目
+export const projectMenu = [
+  {
+    icon: 'icon-dangjian_duiwujianshe',
+    _id: 'projectmenu1',
+    name: '系统设置',
+    order_num: 1,
+    status: '0',
+    type: '0',
+    children: [
+      {
+        icon: 'icon-rencai',
+        path: '/system/dict',
+        name: '字典设置',
+        index: '1-1',
+      },
+      {
+        icon: 'icon-rencai',
+        path: '/system/menus',
+        name: '菜单设置',
+        index: '1-2',
+      },
+
+      {
+        icon: 'icon-rencai',
+        path: '/system/role',
+        name: '角色设置',
+        index: '1-3',
+      },
+    ],
+  },
+  {
+    icon: 'icon-keyan',
+    _id: 'projectmenu2',
+    name: '项目管理',
+    order_num: 2,
+    status: '0',
+    type: '0',
+    children: [
+      {
+        icon: 'icon-xiangmuxinxi',
+        path: '/platform/project',
+        name: '科研项目',
+        index: '2-1',
+      },
+      {
+        icon: 'icon-lunwen',
+        path: '/platform/paper',
+        name: '发表学术论文',
+        index: '2-2',
+      },
+      {
+        icon: 'icon-qita',
+        path: '/platform/other',
+        name: '其他成果',
+        index: '2-3',
+      },
+      {
+        icon: 'icon-huojiangqingkuang',
+        path: '/platform/award',
+        name: '获奖情况',
+        index: '2-4',
+      },
+      {
+        icon: 'icon-xiangmuxinxi',
+        path: '/platform/achieve',
+        name: '成果转化',
+        index: '2-5',
+      },
+    ],
+  },
+  {
+    icon: 'icon-xueshuguanli',
+    _id: 'projectmenu3',
+    name: '学术交流',
+    order_num: 3,
+    status: '0',
+    type: '0',
+    children: [
+      {
+        icon: 'icon-huodong',
+        path: '/xueshu/socialservices',
+        name: '省部级及以上活动',
+        index: '3-1',
+      },
+      {
+        icon: 'icon-kepu',
+        path: '/xueshu/scienceactivities',
+        name: '科普活动',
+        index: '3-2',
+      },
+      {
+        icon: 'icon-fabiao',
+        path: '/xueshu/report',
+        name: '发表/提交报告',
+        index: '3-3',
+      },
+    ],
+  },
+  {
+    icon: 'icon-dangjian_duiwujianshe',
+    _id: 'projectmenu4',
+    name: '队伍建设与人才培养',
+    order_num: 4,
+    status: '0',
+    type: '0',
+    children: [
+      {
+        icon: 'icon-rencai',
+        path: '/jianshe/personnelname',
+        name: '省部级以上人才称号',
+        index: '4-1',
+      },
+      {
+        icon: 'icon-zhicheng',
+        path: '/jianshe/title',
+        name: '职称晋升',
+        index: '4-2',
+      },
+      {
+        icon: 'icon-peiyang',
+        path: '/jianshe/doctor',
+        name: '博硕培养',
+        index: '4-3',
+      },
+    ],
+  },
+  {
+    icon: 'icon-chengguozhanshi',
+    path: '/outcome',
+    _id: 'projectmenu5',
+    name: '重要进展及重大报告',
+    order_num: 5,
+    status: '0',
+    type: '1',
+  },
+];

+ 85 - 0
src/layout/data/site.js

@@ -0,0 +1,85 @@
+// 网站基本设置
+export const siteInfo = {
+  display: true,
+  zhTitle: '商城管理',
+  enTitle: 'Changchun Furui Technology Co., Ltd',
+  logo_url: require('../../assets/logo.png'),
+};
+// 菜单设置
+export const menuInfo = {
+  info: {
+    display: true,
+    mode: 'horizontal',
+    // backColor: '#00d1b2',
+    textColor: '#ffffff',
+    // actColor: '#fe950e',
+    // actColor: '#ffffff',
+  },
+  menuList: [
+    { icon: '', index: '/test1/index', title: '测试菜单一' },
+    { icon: '', index: '/test2/index', title: '测试菜单二' },
+    { icon: '', index: '/test3/index', title: '测试菜单三' },
+    // {
+    //   icon: '',
+    //   index: '2',
+    //   title: '二级菜单',
+    //   subs: [
+    //     {
+    //       icon: '',
+    //       index: '/test3/index',
+    //       title: '二级菜单一',
+    //     },
+    //   ],
+    // },
+    // {
+    //   icon: '',
+    //   index: '3',
+    //   title: '三级菜单',
+    //   subs: [
+    //     {
+    //       icon: '',
+    //       index: '3-1',
+    //       title: '三级菜单-一级',
+    //       subs: [
+    //         {
+    //           icon: '',
+    //           index: '/test3/index',
+    //           title: '三级菜单-二级',
+    //         },
+    //       ],
+    //     },
+    //   ],
+    // },
+  ],
+};
+// 轮播图设置
+export const bannerInfo = {
+  info: {
+    display: true,
+    // 轮播高度
+    height: '400px',
+    // 指示器触发方式-默认值:hover,click:点击
+    trigger: '',
+    // 是否自动切换-默认值:true
+    autoplay: true,
+    // 自动切换秒数
+    interval: 3000,
+    // 指示器位置显示-默认值:显示,outside:外部,none:不显示,
+    indicatorpos: '',
+    // 切换箭头-默认值:鼠标滑过时显示,always:一直显示,never:一直隐藏
+    arrow: '',
+    // 轮播类型-card:卡片化
+    type: '',
+    // 是否循环显示:默认值:true
+    loop: true,
+    // 轮播垂直方向显示-默认值:横向,vertical:垂直
+    direction: 'horizontal',
+  },
+  list: [],
+};
+// 网站底部信息
+export const footInfo = {
+  display: true,
+  content:
+    '<p>技术运营:长春市福瑞科技有限公司</p><p>技术支持:长春市福瑞科技有限公司</p><p>地址:吉林省长春市朝阳区前进大街1244号电话:12345678901微信:123456邮箱:123456@163.com</p><p>吉ICP备2020007658号-1 Copyright 2019 版权所有 长春市福瑞科技有限公司 All Rights Reserved</p>',
+};

+ 79 - 0
src/layout/directives.js

@@ -0,0 +1,79 @@
+import Vue from 'vue';
+
+// v-dialogDrag: 弹窗拖拽属性
+Vue.directive('dialogDrag', {
+  bind(el, binding, vnode, oldVnode) {
+    const dialogHeaderEl = el.querySelector('.el-dialog__header');
+    const dragDom = el.querySelector('.el-dialog');
+
+    dialogHeaderEl.style.cssText += ';cursor:move;';
+    dragDom.style.cssText += ';top:0px;';
+
+    // 获取原有属性 ie dom元素.currentStyle 火狐谷歌 window.getComputedStyle(dom元素, null);
+    const sty = (() => {
+      if (window.document.currentStyle) {
+        return (dom, attr) => dom.currentStyle[attr];
+      } else {
+        return (dom, attr) => getComputedStyle(dom, false)[attr];
+      }
+    })();
+
+    dialogHeaderEl.onmousedown = e => {
+      // 鼠标按下,计算当前元素距离可视区的距离
+      const disX = e.clientX - dialogHeaderEl.offsetLeft;
+      const disY = e.clientY - dialogHeaderEl.offsetTop;
+
+      const screenWidth = document.body.clientWidth; // body当前宽度
+      const screenHeight = document.documentElement.clientHeight; // 可见区域高度(应为body高度,可某些环境下无法获取)
+
+      const dragDomWidth = dragDom.offsetWidth; // 对话框宽度
+      const dragDomheight = dragDom.offsetHeight; // 对话框高度
+
+      const minDragDomLeft = dragDom.offsetLeft;
+      const maxDragDomLeft = screenWidth - dragDom.offsetLeft - dragDomWidth;
+
+      const minDragDomTop = dragDom.offsetTop;
+      const maxDragDomTop = screenHeight - dragDom.offsetTop - dragDomheight;
+
+      // 获取到的值带px 正则匹配替换
+      let styL = sty(dragDom, 'left');
+      let styT = sty(dragDom, 'top');
+
+      // 注意在ie中 第一次获取到的值为组件自带50% 移动之后赋值为px
+      if (styL.includes('%')) {
+        styL = +document.body.clientWidth * (+styL.replace(/\%/g, '') / 100);
+        styT = +document.body.clientHeight * (+styT.replace(/\%/g, '') / 100);
+      } else {
+        styL = +styL.replace(/\px/g, '');
+        styT = +styT.replace(/\px/g, '');
+      }
+
+      document.onmousemove = function(e) {
+        // 通过事件委托,计算移动的距离
+        let left = e.clientX - disX;
+        let top = e.clientY - disY;
+
+        // 边界处理
+        if (-left > minDragDomLeft) {
+          left = -minDragDomLeft;
+        } else if (left > maxDragDomLeft) {
+          left = maxDragDomLeft;
+        }
+
+        if (-top > minDragDomTop) {
+          top = -minDragDomTop;
+        } else if (top > maxDragDomTop) {
+          top = maxDragDomTop;
+        }
+
+        // 移动当前元素
+        dragDom.style.cssText += `;left:${left + styL}px;top:${top + styT}px;`;
+      };
+
+      document.onmouseup = function(e) {
+        document.onmousemove = null;
+        document.onmouseup = null;
+      };
+    };
+  },
+});

+ 116 - 0
src/layout/header/activitys.vue

@@ -0,0 +1,116 @@
+<template>
+  <div id="activitys">
+    <template v-if="view === 'lab'">
+      <div class="view">
+        当前处于:
+        <el-tooltip class="item" effect="dark" placement="bottom">
+          <template #content> {{ getProp('start_time') }} 至 {{ getProp('end_time') }} </template>
+          <el-link>{{ getProp('year') }}年度-{{ getProp('name') }}</el-link>
+        </el-tooltip>
+        时间内
+      </div>
+    </template>
+    <template v-else-if="view === 'admin'">
+      <el-row class="rows">
+        <el-col :span="24">当前处于:</el-col>
+        <el-col :span="24" v-if="preparing._id">
+          筹建期:
+          <el-tooltip class="item" effect="dark" placement="bottom">
+            <template #content> {{ getProp('start_time', preparing) }} 至 {{ getProp('end_time', preparing) }} </template>
+            <el-link>{{ getProp('year', preparing) }}年度-{{ getProp('name', preparing) }}</el-link>
+          </el-tooltip>
+          时间内
+          <el-divider direction="vertical"></el-divider>
+        </el-col>
+        <el-col :span="24" v-if="working._id">
+          正常运营期:
+          <el-tooltip class="item" effect="dark" placement="bottom">
+            <template #content> {{ getProp('start_time', working) }} 至 {{ getProp('end_time', working) }} </template>
+            <el-link>{{ getProp('year', working) }}年度-{{ getProp('name', working) }}</el-link>
+          </el-tooltip>
+          时间内
+        </el-col>
+      </el-row>
+    </template>
+  </div>
+</template>
+
+<script>
+const _ = require('lodash');
+const moment = require('moment');
+import { mapState, createNamespacedHelpers } from 'vuex';
+const { mapActions } = createNamespacedHelpers('activitys');
+export default {
+  name: 'activitys',
+  props: {},
+  components: {},
+  data: function() {
+    return {
+      view: '',
+      preparing: {},
+      working: {},
+    };
+  },
+  computed: {
+    ...mapState(['user', 'isInAct']),
+  },
+  created() {
+    if (this.user.role_id === 'admin' || this.user.role_type === '1') {
+      // 管理员/超级管理员: 显示当前正处于哪个活动时间段,显示标题;
+      this.view = 'admin';
+      this.getActingTime();
+    } else {
+      // 显示自己处于哪个时间段即可
+      this.view = 'lab';
+    }
+  },
+  methods: {
+    ...mapActions(['query']),
+    getProp(prop, data = {}) {
+      if (this.view === 'lab') data = this.isInAct;
+      return _.get(data, prop);
+    },
+    async getActingTime() {
+      const res = await this.query({ is_use: '0' });
+      if (this.$checkRes(res)) {
+        const data = res.data;
+        const working = data.find(f => f.type === '1');
+        if (working) {
+          const { start_time, end_time } = working;
+          const is_in = moment().isBetween(start_time, end_time, null, '[]');
+          console.log(is_in);
+          if (is_in) this.$set(this, `working`, working);
+        }
+        const preparing = data.find(f => f.type === '-1');
+        if (preparing) {
+          const { start_time, end_time } = preparing;
+          const is_in = moment().isBetween(start_time, end_time, null, '[]');
+          console.log(is_in);
+          if (is_in) this.$set(this, `preparing`, preparing);
+        }
+      }
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped>
+.view {
+  color: white;
+  font-weight: 700;
+  padding: 15px 0;
+  font-size: 14px;
+  .el-link {
+    font-size: 16px;
+    color: red;
+  }
+}
+.rows {
+  color: white;
+  font-size: 14px;
+  .el-link {
+    font-size: 16px;
+    color: red;
+  }
+}
+</style>

+ 73 - 0
src/layout/header/notice.vue

@@ -0,0 +1,73 @@
+<template>
+  <span>
+    <el-badge :value="total" class="notice__badge">
+      <i class="el-icon-message-solid notice__icon" @click="toNotice()"></i>
+    </el-badge>
+  </span>
+</template>
+
+<script>
+import { mapState, createNamespacedHelpers } from 'vuex';
+const { mapActions } = createNamespacedHelpers('notice');
+export default {
+  name: 'notice',
+  props: {},
+  components: {},
+  data: function() {
+    return {
+      list: [],
+      total: 0,
+    };
+  },
+  computed: {
+    ...mapState(['user']),
+  },
+  created() {
+    this.getNotRead();
+  },
+  mounted() {
+    this.channel();
+  },
+  methods: {
+    ...mapActions(['notRead']),
+    toNotice() {
+      this.$router.push('/system/notice/view');
+    },
+    async getNotRead() {
+      const res = await this.notRead({ lab_id: this.user.lab_id });
+      if (this.$checkRes(res)) {
+        this.$set(this, `list`, res.data);
+        this.$set(this, `total`, res.data.length);
+      }
+    },
+    channel() {
+      if (!this.user.lab_id) return;
+      this.$stomp({
+        [`/exchange/lab-notice/${this.user.lab_id}`]: this.onMessage,
+      });
+      this.$stomp({
+        [`/exchange/lab-notice-fresh/${this.user.lab_id}`]: this.getNotRead,
+      });
+    },
+    onMessage(message) {
+      this.$notify.info({
+        title: '新消息',
+        message: '您有新的消息,请注意查收!',
+        position: 'bottom-right',
+      });
+      this.getNotRead();
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped>
+.notice__icon {
+  font-size: 26px;
+  cursor: pointer;
+}
+/deep/.el-badge__content.is-fixed {
+  top: 10px;
+  cursor: pointer;
+}
+</style>

+ 30 - 0
src/layout/i18n.js

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

+ 15 - 5
src/main.js

@@ -1,7 +1,17 @@
-import Vue from "vue";
-import App from "./App.vue";
-import router from "./router";
-import store from "./store";
+import Vue from 'vue';
+import App from './App.vue';
+import router from './router';
+import store from './store';
+import '@/plugins/element.js';
+import '@/plugins/axios';
+import '@/plugins/check-res';
+import '@/plugins/meta';
+
+import '@/assets/icon/iconfont.css';
+import '@/assets/css/main.css';
+// 默认样式
+import '@/assets/css/theme-ele/index.css';
+import '@/assets/css/theme-ele/color-ele.css';
 
 Vue.config.productionTip = false;
 
@@ -9,4 +19,4 @@ new Vue({
   router,
   store,
   render: (h) => h(App),
-}).$mount("#app");
+}).$mount('#app');

+ 19 - 0
src/plugins/axios.js

@@ -0,0 +1,19 @@
+import Vue from 'vue';
+import AxiosWrapper from '@/util/axios-wrapper';
+
+const Plugin = {
+  install(vue, options) {
+    // 3. 注入组件
+    vue.mixin({
+      created() {
+        if (this.$store && !this.$store.$axios) {
+          this.$store.$axios = this.$axios;
+        }
+      },
+    });
+    // 4. 添加实例方法
+    vue.prototype.$axios = new AxiosWrapper(options);
+  },
+};
+
+Vue.use(Plugin, { baseUrl: process.env.VUE_APP_AXIOS_BASE_URL });

+ 39 - 0
src/plugins/check-res.js

@@ -0,0 +1,39 @@
+/* eslint-disable no-underscore-dangle */
+/* eslint-disable no-param-reassign */
+/* eslint-disable no-unused-vars */
+/* eslint-disable no-shadow */
+import Vue from 'vue';
+import _ from 'lodash';
+import { Message } from 'element-ui';
+
+const vm = new Vue({});
+const Plugin = {
+  install(Vue, options) {
+    // 4. 添加实例方法
+    Vue.prototype.$checkRes = async (res, okText, errText) => {
+      let _okText = okText;
+      let _errText = errText;
+      if (!_.isFunction(okText) && _.isObject(okText) && okText != null) {
+        ({ okText: _okText, errText: _errText } = okText);
+      }
+      const { errcode = 0, errmsg } = res || {};
+      if (errcode === 0) {
+        if (_.isFunction(_okText)) {
+          return _okText();
+        }
+        if (_okText) {
+          Message.success(_okText);
+        }
+        return true;
+      }
+      if (_.isFunction(_errText)) {
+        return _errText();
+      }
+      Message.error(_errText || errmsg);
+      // Message({ message: _errText || errmsg, duration: 60000 });
+      return false;
+    };
+  },
+};
+
+Vue.use(Plugin);

+ 22 - 0
src/plugins/components.js

@@ -0,0 +1,22 @@
+import Vue from 'vue';
+import dataTable from '@common/src/components/frame/filter-page-table.vue';
+import dataForm from '@common/src/components/frame/form.vue';
+import eUpload from '@common/src/components/frame/e-upload.vue';
+import sUpload from '@common/src/components/frame/s-upload.vue';
+import dUpload from '@common/src/components/frame/d-upload.vue';
+import eDialog from '@common/src/components/frame/e-dialog.vue';
+import wangEditor from '@common/src/components/frame/wang-editor.vue';
+import infoForm from '@/components/infoForm.vue';
+const Plugin = (vue) => {
+  vue.prototype.$dev_mode = process.env.NODE_ENV === 'development';
+  vue.component('data-table', dataTable);
+  vue.component('data-form', dataForm);
+  vue.component('sUpload', sUpload);
+  vue.component('dUpload', dUpload);
+  vue.component('eUpload', eUpload);
+  vue.component('wangEditor', wangEditor);
+  vue.component('eDialog', eDialog);
+  vue.component('infoForm', infoForm);
+};
+
+Vue.use(Plugin);

+ 70 - 0
src/plugins/directive.js

@@ -0,0 +1,70 @@
+import Vue from 'vue';
+import router from '../router';
+import store from '../store';
+import operaList from '@/util/opera';
+const _ = require('lodash');
+// 实验室用
+Vue.directive('opera', {
+  inserted: (el, binding, vnode, oldVnode) => {
+    const opera = _.get(router, 'currentRoute.meta.opera');
+    // 没找到opera,就不管了
+    if (!opera) return;
+    // 取出指令绑定的值
+    let value = _.get(binding, 'value', {});
+    let method = _.get(value, 'method', 'others');
+    const isInAct = store.state.isInAct;
+    if (isInAct) {
+      // 如果指令绑定的值不在operaList中,默认为others
+      const r = operaList.find((f) => f._id === method);
+      if (!r) method = 'others';
+      if (!opera.includes(method)) el.style.display = 'none';
+      // else {
+      //   // 如果可以进行该操作,则检查数据部分是否有act_time.且和isInAct中的id是否一致,不一致不能进行改(其他时间段填写的内容不允许别的时间段改)
+      //   let actTime = _.get(value, 'data.act_time');
+      //   if (actTime) {
+      //     let { _id: nowActId } = isInAct;
+      //     if (actTime && nowActId && actTime !== nowActId) {
+      //       // 数据中有actTime,当前也有处于时间段中,且这两个id并不同,则隐藏
+      //       el.style.display = 'none';
+      //     }
+      //   }
+      // }
+    } else {
+      // 不在正常修改时间内
+      // 首先,将正常操作中,不受时间限制的项过滤出来
+      const outOfActOpera = operaList.filter((f) => f.outOfAct === true);
+      // 其次,将本页面可使用的操作 与 不受时间限制的操作 取交集: 即 获取本页面 不受时间限制的操作 列表
+      const intersection = _.intersection(
+        opera,
+        outOfActOpera.map((i) => i._id)
+      );
+      // 最后,查看交集中是否有当前操作
+      if (!intersection.includes(method)) el.style.display = 'none';
+    }
+  },
+});
+
+// 项目管理用
+Vue.directive('use', {
+  inserted: (el, binding, vnode, oldVnode) => {
+    const route = _.get(router, 'currentRoute.path');
+    const menu = _.get(store, 'state.user.role.menu');
+    if (!route || !menu) return false;
+    if (_.get(menu, 'mode') === 'all') return true;
+    const rules = menu[route];
+    // 不能没有这个路由的规则,即使规则是空的.也是存在的
+    if (!rules) el.style.display = 'none';
+    const { mode, opera } = rules;
+    // 全可以
+    if (mode === 'allow') return true;
+    // 全禁止
+    else if (mode === 'refuse') el.style.display = 'none';
+    // 选择性
+    else {
+      const code = _.get(binding, 'value');
+      if (!code) el.style.display = 'none';
+      const r = opera.find((f) => f === code);
+      if (!r) el.style.display = 'none';
+    }
+  },
+});

+ 5 - 0
src/plugins/element.js

@@ -0,0 +1,5 @@
+import Vue from 'vue';
+import Element from 'element-ui';
+import 'element-ui/lib/theme-chalk/index.css';
+
+Vue.use(Element, { size: 'small' });

+ 4 - 0
src/plugins/meta.js

@@ -0,0 +1,4 @@
+import Vue from 'vue';
+import Meta from 'vue-meta';
+
+Vue.use(Meta);

+ 21 - 0
src/plugins/setting.js

@@ -0,0 +1,21 @@
+import Vue from 'vue';
+
+Vue.config.weixin = {
+  // baseUrl: process.env.BASE_URL + 'weixin',
+  baseUrl: `http://${location.host}`,
+};
+
+Vue.config.stomp = {
+  // brokerURL: 'ws://192.168.1.118/ws',
+  brokerURL: '/ws', // ws://${location.host}/ws
+  // brokerURL: 'ws://127.0.0.1:8000/ws',
+  connectHeaders: {
+    host: 'platform',
+    login: 'visit', //visit
+    passcode: 'visit', //visit123
+  },
+  // debug: true,
+  reconnectDelay: 5000,
+  heartbeatIncoming: 4000,
+  heartbeatOutgoing: 4000,
+};

+ 65 - 0
src/plugins/stomp.js

@@ -0,0 +1,65 @@
+/**
+ * 基于WebStomp的消息处理插件
+ */
+
+import Vue from 'vue';
+import _ from 'lodash';
+import assert from 'assert';
+import { Client } from '@stomp/stompjs/esm5/client';
+
+const Plugin = {
+  install(Vue, options) {
+    assert(_.isObject(options));
+    if (options.debug && !_.isFunction(options.debug)) {
+      options.debug = (str) => {
+        console.log(str);
+      };
+    }
+    assert(_.isString(options.brokerURL));
+    if (!options.brokerURL.startsWith('ws://')) {
+      options.brokerURL = `ws://${location.host}${options.brokerURL}`;
+    }
+
+    // 3. 注入组件
+    Vue.mixin({
+      beforeDestroy: function () {
+        if (this.$stompClient) {
+          this.$stompClient.deactivate();
+          delete this.$stompClient;
+        }
+      },
+    });
+
+    // 4. 添加实例方法
+    Vue.prototype.$stomp = function (subscribes = {}) {
+      // connect to mq
+      const client = new Client(options);
+      client.onConnect = (frame) => {
+        // Do something, all subscribes must be done is this callback
+        // This is needed because this will be executed after a (re)connect
+        console.log('[stomp] connected');
+        Object.keys(subscribes)
+          .filter((p) => _.isFunction(subscribes[p]))
+          .forEach((key) => {
+            client.subscribe(key, subscribes[key]);
+          });
+      };
+
+      client.onStompError = (frame) => {
+        // Will be invoked in case of error encountered at Broker
+        // Bad login/passcode typically will cause an error
+        // Complaint brokers will set `message` header with a brief message. Body may contain details.
+        // Compliant brokers will terminate the connection after any error
+        console.log('Broker reported error: ' + frame.headers['message']);
+        console.log('Additional details: ' + frame.body);
+      };
+
+      client.activate();
+
+      this.$stompClient = client;
+    };
+  },
+};
+export default () => {
+  Vue.use(Plugin, Vue.config.stomp);
+};

+ 22 - 0
src/router/guard.js

@@ -0,0 +1,22 @@
+import store from '@/store/index';
+const _ = require('lodash');
+const jwt = require('jsonwebtoken');
+export default (router) => {
+  router.beforeEach((to, from, next) => {
+    console.log('line 4 in function:');
+    // 检查是不是登陆,登陆页面放过
+    if (to.path === '/login') {
+      next();
+      return;
+    }
+    const user = store.state.user;
+    if (!_.get(user, '_id')) {
+      const str = sessionStorage.getItem('user');
+      console.log(str);
+      if (!str) next('/login');
+      const obj = jwt.decode(str);
+      store.commit('setUser', obj, { root: true });
+    }
+    next();
+  });
+};

+ 21 - 14
src/router/index.js

@@ -1,30 +1,37 @@
-import Vue from "vue";
-import VueRouter from "vue-router";
-import Home from "../views/Home.vue";
-
+import Vue from 'vue';
+import VueRouter from 'vue-router';
+import gurad from './guard';
 Vue.use(VueRouter);
 
 const routes = [
   {
-    path: "/",
-    name: "Home",
-    component: Home,
+    path: '/login',
+    name: 'login',
+    meta: { title: '管理登录' },
+    component: () => import('../views/login.vue'),
   },
   {
-    path: "/about",
-    name: "About",
+    path: '/',
+    name: 'frame',
     // 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"),
+    component: () => import(/* webpackChunkName: "frame" */ '@/layout/Home.vue'),
+    children: [
+      {
+        path: '/',
+        name: 'index',
+        meta: { title: '首页' },
+        component: () => import(/* webpackChunkName: "frame" */ '@/views/index.vue'),
+      },
+    ],
   },
 ];
 
 const router = new VueRouter({
-  mode: "history",
-  base: process.env.BASE_URL,
+  mode: 'history',
+  base: process.env.VUE_APP_ROUTER,
   routes,
 });
-
+gurad(router);
 export default router;

+ 8 - 5
src/store/index.js

@@ -1,11 +1,14 @@
-import Vue from "vue";
-import Vuex from "vuex";
+import Vue from 'vue';
+import Vuex from 'vuex';
+import * as ustate from './module/user/state';
+import * as umutations from './module/user/mutations';
+import admin from './module/user/action';
 
 Vue.use(Vuex);
 
 export default new Vuex.Store({
-  state: {},
-  mutations: {},
+  state: { ...ustate },
+  mutations: { ...umutations },
   actions: {},
-  modules: {},
+  modules: { admin },
 });

+ 29 - 0
src/store/module/user/action.js

@@ -0,0 +1,29 @@
+import Vue from 'vue';
+import Vuex from 'vuex';
+Vue.use(Vuex);
+const api = {
+  url: '/point/v1/api/admin',
+};
+
+const state = () => ({});
+const mutations = {};
+
+const actions = {
+  async login({ commit }, payload) {
+    try {
+      const res = await this.$axios.$post(`${api.url}/login`, payload);
+      if (res.errcode === 0) {
+        sessionStorage.setItem('user', res.data);
+      }
+      return res;
+    } catch (error) {
+      return false;
+    }
+  },
+};
+export default {
+  namespaced: true,
+  state,
+  mutations,
+  actions,
+};

+ 40 - 0
src/store/module/user/mutations.js

@@ -0,0 +1,40 @@
+export const setUser = (state, payload) => {
+  state.user = payload;
+  // let res = true;
+  // //登陆时
+  // if (payload) {
+  //   state.token = payload;
+  // } else {
+  //   //已经登陆,切换路由时取出客户信息放在总store中
+  //   let token = localStorage.getItem('token');
+  //   if (token && token !== 'guest') {
+  //     state.user = jwt.decode(token);
+  //   } else if (token && token == 'guest') {
+  //     let user = localStorage.getItem('user');
+  //     state.user = JSON.parse(user);
+  //   } else {
+  //     let timestamp = new Date().getTime();
+  //     let user = {
+  //       // id: `guest${timestamp}`,
+  //       name: `游客${timestamp}`,
+  //     };
+  //     state.user = user;
+  //     localStorage.setItem('token', 'guest');
+  //     localStorage.setItem('user', JSON.stringify(user));
+  //     console.warn('游客身份');
+  //   }
+  // }
+  // return res;
+};
+
+export const deleteUser = (state, payload) => {
+  state.user = {};
+  state.menuList = [];
+  state.isLoadMenu = false;
+  sessionStorage.removeItem('token');
+};
+
+export const addMenu = (state, payload) => {
+  state.menuList = payload;
+  state.isLoadMenu = true;
+};

+ 3 - 0
src/store/module/user/state.js

@@ -0,0 +1,3 @@
+export const user = {};
+export const menuList = [];
+export const isLoadMenu = false;

+ 49 - 0
src/template/list.vue

@@ -0,0 +1,49 @@
+<template>
+  <div id="list">
+    <ctable
+      :fields="fields"
+      :opera="opera"
+      :data="list"
+      :total="total"
+      :limit="limit"
+      :usePage="usePage"
+      :vOpera="vOpera"
+      @query="search"
+      @rowClick="rowClick"
+      @opera="toOpera"
+    ></ctable>
+  </div>
+</template>
+
+<script>
+import ctable from '@/components/usual/c-table.vue';
+import { mapState, createNamespacedHelpers } from 'vuex';
+export default {
+  name: 'list',
+  props: {},
+  components: { ctable },
+  data: function () {
+    return {
+      fields: [],
+      opera: [],
+      list: [],
+      total: 0,
+      limit: 10,
+      usePage: true,
+      vOpera: false,
+    };
+  },
+  computed: {
+    ...mapState(['user']),
+  },
+  created() {},
+  methods: {
+    async search() {},
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+};
+</script>
+
+<style lang="less" scoped></style>

+ 131 - 0
src/util/axios-wrapper.js

@@ -0,0 +1,131 @@
+/* eslint-disable no-console */
+/* eslint-disable no-param-reassign */
+
+import _ from 'lodash';
+import Axios from 'axios';
+import { Util, Error } from 'naf-core';
+// import { Indicator } from 'mint-ui';
+
+const { trimData, isNullOrUndefined } = Util;
+const { ErrorCode } = Error;
+
+let currentRequests = 0;
+
+export default class AxiosWrapper {
+  constructor({ baseUrl = '', unwrap = true } = {}) {
+    this.baseUrl = baseUrl;
+    this.unwrap = unwrap;
+  }
+
+  // 替换uri中的参数变量
+  static merge(uri, query = {}) {
+    if (!uri.includes(':')) {
+      return uri;
+    }
+    const keys = [];
+    const regexp = /\/:([a-z0-9_]+)/gi;
+    let res;
+    // eslint-disable-next-line no-cond-assign
+    while ((res = regexp.exec(uri)) != null) {
+      keys.push(res[1]);
+    }
+    keys.forEach((key) => {
+      if (!isNullOrUndefined(query[key])) {
+        uri = uri.replace(`:${key}`, query[key]);
+      }
+    });
+    return uri;
+  }
+
+  $get(uri, query, options) {
+    return this.$request(uri, null, query, options);
+  }
+
+  $post(uri, data = {}, query, options) {
+    return this.$request(uri, data, query, options);
+  }
+  $delete(uri, data = {}, router, query, options = {}) {
+    options = { ...options, method: 'delete' };
+    return this.$request(uri, data, query, options, router);
+  }
+  async $request(uri, data, query, options) {
+    if (query && _.isObject(query)) {
+      const keys = Object.keys(query);
+      for (const key of keys) {
+        if ((query[key] && query[key] === '') || !query[key]) {
+          delete query[key];
+        }
+      }
+    }
+    // TODO: 合并query和options
+    if (_.isObject(query) && _.isObject(options)) {
+      options = { ...options, params: query, method: 'get' };
+    } else if (_.isObject(query) && !query.params) {
+      options = { params: query };
+    } else if (_.isObject(query) && query.params) {
+      options = query;
+    }
+    if (!options) options = {};
+    if (options.params) options.params = trimData(options.params);
+    const url = AxiosWrapper.merge(uri, options.params);
+    currentRequests += 1;
+    // Indicator.open({
+    //   spinnerType: 'fading-circle',
+    // });
+
+    try {
+      const axios = Axios.create({
+        baseURL: this.baseUrl,
+      });
+      // if (util.token && util.token !== null) axios.defaults.headers.common.Authorization = util.token;
+      const token = sessionStorage.getItem('token');
+      const apiToken = sessionStorage.getItem('apiToken');
+      if (token) axios.defaults.headers.common['user-token'] = token;
+      if (apiToken) axios.defaults.headers.common['api-token'] = apiToken;
+      let res = await axios.request({
+        method: isNullOrUndefined(data) ? 'get' : 'post',
+        url,
+        data,
+        responseType: 'json',
+        ...options,
+      });
+      res = res.data;
+      const { errcode, errmsg, details } = res;
+      if (errcode) {
+        console.warn(`[${uri}] fail: ${errcode}-${errmsg} ${details}`);
+        return res;
+      }
+      // unwrap data
+      if (this.unwrap) {
+        res = _.omit(res, ['errmsg', 'details']);
+        const keys = Object.keys(res);
+        if (keys.length === 1 && keys.includes('data')) {
+          res = res.data;
+        }
+      }
+      // 处理apiToken
+      const { apiToken: at, ...others } = res;
+      if (at) sessionStorage.setItem('apiToken', at);
+      return others;
+    } catch (err) {
+      let errmsg = '接口请求失败,请稍后重试';
+      if (err.response) {
+        const { status } = err.response;
+        if (status === 401) errmsg = '用户认证失败,请重新登录';
+        if (status === 403) errmsg = '当前用户不允许执行该操作';
+      }
+      console.error(
+        `[AxiosWrapper] 接口请求失败: ${err.config && err.config.url} - 
+        ${err.message}`
+      );
+      return { errcode: ErrorCode.SERVICE_FAULT, errmsg, details: err.message };
+    } finally {
+      /* eslint-disable */
+      currentRequests -= 1;
+      if (currentRequests <= 0) {
+        currentRequests = 0;
+        // Indicator.close();
+      }
+    }
+  }
+}

+ 0 - 5
src/views/About.vue

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

+ 0 - 18
src/views/Home.vue

@@ -1,18 +0,0 @@
-<template>
-  <div class="home">
-    <img alt="Vue logo" src="../assets/logo.png" />
-    <HelloWorld msg="Welcome to Your Vue.js App" />
-  </div>
-</template>
-
-<script>
-// @ is an alias to /src
-import HelloWorld from "@/components/HelloWorld.vue";
-
-export default {
-  name: "Home",
-  components: {
-    HelloWorld,
-  },
-};
-</script>

+ 27 - 0
src/views/dev/dict/index.vue

@@ -0,0 +1,27 @@
+<template>
+  <div id="index">
+    <p>index</p>
+  </div>
+</template>
+
+<script>
+import { mapState, createNamespacedHelpers } from 'vuex';
+export default {
+  name: 'index',
+  props: {},
+  components: {},
+  data: function () {
+    return {};
+  },
+  computed: {
+    ...mapState(['user']),
+  },
+  created() {},
+  methods: {},
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+};
+</script>
+
+<style lang="less" scoped></style>

+ 28 - 0
src/views/index.vue

@@ -0,0 +1,28 @@
+<template>
+  <div id="index">
+    <tlist></tlist>
+  </div>
+</template>
+
+<script>
+import tlist from '@/template/list.vue';
+import { mapState, createNamespacedHelpers } from 'vuex';
+export default {
+  name: 'index',
+  props: {},
+  components: { tlist },
+  data: function () {
+    return {};
+  },
+  computed: {
+    ...mapState(['user']),
+  },
+  created() {},
+  methods: {},
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+};
+</script>
+
+<style lang="less" scoped></style>

+ 143 - 0
src/views/login.vue

@@ -0,0 +1,143 @@
+<template>
+  <div id="login">
+    <el-row>
+      <el-col :span="24" class="main">
+        <div class="w_1200">
+          <el-col :span="24" class="one">
+            <el-col :span="24" class="one_1">
+              <el-col :span="24" class="txt"><h3>商城管理登陆</h3></el-col>
+              <el-col :span="24" class="form">
+                <el-form :model="form" :rules="rules" ref="form">
+                  <el-form-item label="" prop="account">
+                    <el-input placeholder="账号" v-model="form.account" @keyup.enter.native="onSubmit('form')">
+                      <i slot="prefix" class="el-input__icon el-icon-user-solid"></i>
+                    </el-input>
+                  </el-form-item>
+                  <el-form-item label="" prop="password">
+                    <el-input placeholder="密码" v-model="form.password" type="password" auto-complete="off" @keyup.enter.native="onSubmit('form')">
+                      <i slot="prefix" class="el-input__icon el-icon-lock"></i>
+                    </el-input>
+                  </el-form-item>
+                  <!-- <el-form-item label="" prop="code">
+                    <el-input placeholder="验证码" v-model="form.code" style="width: 75%" @keyup.enter.native="onSubmit('form')" disabled>
+                      <i slot="prefix" class="el-input__icon el-icon-folder-checked"></i>
+                    </el-input>
+                    <el-image :src="img" class="yzm"></el-image>
+                  </el-form-item> -->
+                  <el-form-item class="btn">
+                    <el-button type="primary" @click="onSubmit('form')">登录</el-button>
+                  </el-form-item>
+                </el-form>
+              </el-col>
+            </el-col>
+          </el-col>
+          <el-col :span="24" class="two"> </el-col>
+        </div>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script>
+const _ = require('lodash');
+import { mapState, createNamespacedHelpers } from 'vuex';
+const { mapActions: maU } = createNamespacedHelpers('admin');
+export default {
+  name: 'login',
+  props: {},
+  components: {},
+  data: function () {
+    return {
+      form: {},
+      rules: {
+        account: [{ required: true, message: '用户名不能为空', trigger: 'blur' }],
+        password: [{ required: true, message: '密码不能为空', trigger: 'blur' }],
+        code: [{ required: false, message: '验证码不能为空', trigger: 'blur' }],
+      },
+      // 验证码
+      img: require('@a/yzm.gif'),
+    };
+  },
+  created() {},
+  methods: {
+    ...maU(['login']),
+    onSubmit(formName) {
+      this.$refs[formName].validate(async (valid) => {
+        if (valid) {
+          const res = await this.login(this.form);
+          console.log(res);
+          if (!res) {
+            this.$message({ type: `error`, message: `登陆失败` });
+          } else {
+            if (res.errcode !== 0) {
+              this.$message({ type: 'error', message: `${res.errmsg}` });
+            } else {
+              this.$router.push({ path: '/' });
+            }
+          }
+        } else {
+          console.log('error submit!!');
+          return false;
+        }
+      });
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+  watch: {
+    test: {
+      deep: true,
+      immediate: true,
+      handler(val) {},
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped>
+.main {
+  background: url('~@/assets/login-background.jpg');
+  height: 100vh;
+  overflow: hidden;
+  .one {
+    .one_1 {
+      width: 400px;
+      padding: 25px 25px 5px 25px;
+      border-radius: 6px;
+      background: #fff;
+      margin: 35vh 75vh;
+      .txt {
+        text-align: center;
+        color: #707070;
+        margin: 0 0 15px 0;
+      }
+      .yzm {
+        float: right;
+        width: 80px;
+        height: 37px;
+      }
+      .btn {
+        .el-button {
+          width: 100%;
+        }
+      }
+    }
+  }
+  .two {
+    text-align: center;
+    p {
+      position: absolute;
+      bottom: 15px;
+      width: 1200px;
+      color: #fff;
+      font-family: Arial;
+      font-size: 12px;
+      letter-spacing: 1px;
+    }
+  }
+}
+</style>

+ 0 - 0
src/views/selfShop/coupon/index.vue


+ 28 - 1
vue.config.js

@@ -1,3 +1,30 @@
+const path = require('path');
 module.exports = {
-  lintOnSave: false,
+  publicPath: `/${process.env.VUE_APP_ROUTER}`,
+  outputDir: process.env.VUE_APP_ROUTER,
+  productionSourceMap: false,
+  configureWebpack: (config) => {
+    Object.assign(config, {
+      resolve: {
+        alias: {
+          '@': path.resolve(__dirname, './src'),
+          '@c': path.resolve(__dirname, './src/components'),
+          '@a': path.resolve(__dirname, './src/assets'),
+        },
+      },
+    });
+  },
+  devServer: {
+    port: '9001',
+    proxy: {
+      '/files': {
+        target: 'http://broadcast.waityou24.cn',
+      },
+      '/point/v1': {
+        target: 'http://127.0.0.1:14001', // 127.0.0.1:13003
+        changeOrigin: true,
+        ws: false,
+      },
+    },
+  },
 };