zs 5 months ago
parent
commit
d61f816304

+ 1 - 1
.env.development

@@ -5,6 +5,6 @@ NODE_ENV='development'
 VITE_APP_PORT = 3000
 
 # 代理前缀
-VITE_APP_BASE_API = '/ts/frame/api'
+VITE_APP_BASE_API = '/service/api'
 
 VITE_APP_HOST = "http://192.168.1.197"

+ 254 - 0
package-lock.json

@@ -9,12 +9,14 @@
       "version": "0.0.0",
       "dependencies": {
         "@element-plus/icons-vue": "^2.3.1",
+        "@kjgl77/datav-vue3": "^1.7.4",
         "@vueuse/core": "^10.7.2",
         "@vueuse/integrations": "^10.9.0",
         "@wangeditor/editor": "^5.1.23",
         "@wangeditor/editor-for-vue": "5.1.10",
         "axios": "^1.6.7",
         "dayjs": "^1.11.13",
+        "echarts": "^5.5.1",
         "element-plus": "^2.5.6",
         "lodash-es": "^4.17.21",
         "nprogress": "^0.2.0",
@@ -734,11 +736,121 @@
         "node": ">= 16"
       }
     },
+    "node_modules/@jiaminghi/bezier-curve": {
+      "version": "0.0.9",
+      "resolved": "https://registry.npmmirror.com/@jiaminghi/bezier-curve/-/bezier-curve-0.0.9.tgz",
+      "integrity": "sha512-u9xJPOEl6Dri2E9FfmJoGxYQY7vYJkURNX04Vj64tdi535tPrpkuf9Sm0lNr3QTKdHQh0DdNRsaa62FLQNQEEw==",
+      "dependencies": {
+        "@babel/runtime": "^7.5.5"
+      }
+    },
+    "node_modules/@jiaminghi/c-render": {
+      "version": "0.4.3",
+      "resolved": "https://registry.npmmirror.com/@jiaminghi/c-render/-/c-render-0.4.3.tgz",
+      "integrity": "sha512-FJfzj5hGj7MLqqqI2D7vEzHKbQ1Ynnn7PJKgzsjXaZpJzTqs2Yw5OSeZnm6l7Qj7jyPAP53lFvEQNH4o4j6s+Q==",
+      "dependencies": {
+        "@babel/runtime": "^7.5.5",
+        "@jiaminghi/bezier-curve": "*",
+        "@jiaminghi/color": "*",
+        "@jiaminghi/transition": "*"
+      }
+    },
+    "node_modules/@jiaminghi/charts": {
+      "version": "0.2.18",
+      "resolved": "https://registry.npmmirror.com/@jiaminghi/charts/-/charts-0.2.18.tgz",
+      "integrity": "sha512-K+HXaOOeWG9OOY1VG6M4mBreeeIAPhb9X+khG651AbnwEwL6G2UtcAQ8GWCq6GzhczcLwwhIhuaHqRygwHC0sA==",
+      "dependencies": {
+        "@babel/runtime": "^7.5.5",
+        "@jiaminghi/c-render": "^0.4.3"
+      }
+    },
+    "node_modules/@jiaminghi/color": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmmirror.com/@jiaminghi/color/-/color-1.1.3.tgz",
+      "integrity": "sha512-ZY3hdorgODk4OSTbxyXBPxAxHPIVf9rPlKJyK1C1db46a50J0reFKpAvfZG8zMG3lvM60IR7Qawgcu4ZDO3+Hg=="
+    },
+    "node_modules/@jiaminghi/transition": {
+      "version": "1.1.11",
+      "resolved": "https://registry.npmmirror.com/@jiaminghi/transition/-/transition-1.1.11.tgz",
+      "integrity": "sha512-owBggipoHMikDHHDW5Gc7RZYlVuvxHADiU4bxfjBVkHDAmmck+fCkm46n2JzC3j33hWvP9nSCAeh37t6stgWeg==",
+      "dependencies": {
+        "@babel/runtime": "^7.5.5"
+      }
+    },
     "node_modules/@jridgewell/sourcemap-codec": {
       "version": "1.4.15",
       "resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
       "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg=="
     },
+    "node_modules/@kjgl77/datav-vue3": {
+      "version": "1.7.4",
+      "resolved": "https://registry.npmmirror.com/@kjgl77/datav-vue3/-/datav-vue3-1.7.4.tgz",
+      "integrity": "sha512-zYVTVKkklUxwtiNKS1qPBilm4rTW+WItfp0zVpaRAI8wgXkLSPbDR9xPq2+UcU/Jft7/DVdMfBp709E2ResuPQ==",
+      "dependencies": {
+        "@jiaminghi/c-render": "^0.4.3",
+        "@jiaminghi/charts": "^0.2.18",
+        "@jiaminghi/color": "^1.1.3",
+        "@vueuse/core": "^10.11.1"
+      }
+    },
+    "node_modules/@kjgl77/datav-vue3/node_modules/@vueuse/core": {
+      "version": "10.11.1",
+      "resolved": "https://registry.npmmirror.com/@vueuse/core/-/core-10.11.1.tgz",
+      "integrity": "sha512-guoy26JQktXPcz+0n3GukWIy/JDNKti9v6VEMu6kV2sYBsWuGiTU8OWdg+ADfUbHg3/3DlqySDe7JmdHrktiww==",
+      "dependencies": {
+        "@types/web-bluetooth": "^0.0.20",
+        "@vueuse/metadata": "10.11.1",
+        "@vueuse/shared": "10.11.1",
+        "vue-demi": ">=0.14.8"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/antfu"
+      }
+    },
+    "node_modules/@kjgl77/datav-vue3/node_modules/@vueuse/metadata": {
+      "version": "10.11.1",
+      "resolved": "https://registry.npmmirror.com/@vueuse/metadata/-/metadata-10.11.1.tgz",
+      "integrity": "sha512-IGa5FXd003Ug1qAZmyE8wF3sJ81xGLSqTqtQ6jaVfkeZ4i5kS2mwQF61yhVqojRnenVew5PldLyRgvdl4YYuSw==",
+      "funding": {
+        "url": "https://github.com/sponsors/antfu"
+      }
+    },
+    "node_modules/@kjgl77/datav-vue3/node_modules/@vueuse/shared": {
+      "version": "10.11.1",
+      "resolved": "https://registry.npmmirror.com/@vueuse/shared/-/shared-10.11.1.tgz",
+      "integrity": "sha512-LHpC8711VFZlDaYUXEBbFBCQ7GS3dVU9mjOhhMhXP6txTV4EhYQg/KGnQuvt/sPAtoUKq7VVUnL6mVtFoL42sA==",
+      "dependencies": {
+        "vue-demi": ">=0.14.8"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/antfu"
+      }
+    },
+    "node_modules/@kjgl77/datav-vue3/node_modules/vue-demi": {
+      "version": "0.14.10",
+      "resolved": "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.14.10.tgz",
+      "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==",
+      "hasInstallScript": true,
+      "bin": {
+        "vue-demi-fix": "bin/vue-demi-fix.js",
+        "vue-demi-switch": "bin/vue-demi-switch.js"
+      },
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/antfu"
+      },
+      "peerDependencies": {
+        "@vue/composition-api": "^1.0.0-rc.1",
+        "vue": "^3.0.0-0 || ^2.6.0"
+      },
+      "peerDependenciesMeta": {
+        "@vue/composition-api": {
+          "optional": true
+        }
+      }
+    },
     "node_modules/@nodelib/fs.scandir": {
       "version": "2.1.5",
       "resolved": "https://registry.npmmirror.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@@ -2316,6 +2428,20 @@
         "domelementtype": "1"
       }
     },
+    "node_modules/echarts": {
+      "version": "5.5.1",
+      "resolved": "https://registry.npmmirror.com/echarts/-/echarts-5.5.1.tgz",
+      "integrity": "sha512-Fce8upazaAXUVUVsjgV6mBnGuqgO+JNDlcgF79Dksy4+wgGpQB2lmYoO4TSweFg/mZITdpGHomw/cNBJZj1icA==",
+      "dependencies": {
+        "tslib": "2.3.0",
+        "zrender": "5.6.0"
+      }
+    },
+    "node_modules/echarts/node_modules/tslib": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.3.0.tgz",
+      "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg=="
+    },
     "node_modules/element-plus": {
       "version": "2.5.6",
       "resolved": "https://registry.npmmirror.com/element-plus/-/element-plus-2.5.6.tgz",
@@ -6390,6 +6516,19 @@
       "engines": {
         "node": ">=10"
       }
+    },
+    "node_modules/zrender": {
+      "version": "5.6.0",
+      "resolved": "https://registry.npmmirror.com/zrender/-/zrender-5.6.0.tgz",
+      "integrity": "sha512-uzgraf4njmmHAbEUxMJ8Oxg+P3fT04O+9p7gY+wJRVxo8Ge+KmYv0WJev945EH4wFuc4OY2NLXz46FZrWS9xJg==",
+      "dependencies": {
+        "tslib": "2.3.0"
+      }
+    },
+    "node_modules/zrender/node_modules/tslib": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.3.0.tgz",
+      "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg=="
     }
   },
   "dependencies": {
@@ -6809,11 +6948,95 @@
       "resolved": "https://registry.npmmirror.com/@intlify/shared/-/shared-9.9.1.tgz",
       "integrity": "sha512-b3Pta1nwkz5rGq434v0psHwEwHGy1pYCttfcM22IE//K9owbpkEvFptx9VcuRAxjQdrO2If249cmDDjBu5wMDA=="
     },
+    "@jiaminghi/bezier-curve": {
+      "version": "0.0.9",
+      "resolved": "https://registry.npmmirror.com/@jiaminghi/bezier-curve/-/bezier-curve-0.0.9.tgz",
+      "integrity": "sha512-u9xJPOEl6Dri2E9FfmJoGxYQY7vYJkURNX04Vj64tdi535tPrpkuf9Sm0lNr3QTKdHQh0DdNRsaa62FLQNQEEw==",
+      "requires": {
+        "@babel/runtime": "^7.5.5"
+      }
+    },
+    "@jiaminghi/c-render": {
+      "version": "0.4.3",
+      "resolved": "https://registry.npmmirror.com/@jiaminghi/c-render/-/c-render-0.4.3.tgz",
+      "integrity": "sha512-FJfzj5hGj7MLqqqI2D7vEzHKbQ1Ynnn7PJKgzsjXaZpJzTqs2Yw5OSeZnm6l7Qj7jyPAP53lFvEQNH4o4j6s+Q==",
+      "requires": {
+        "@babel/runtime": "^7.5.5",
+        "@jiaminghi/bezier-curve": "*",
+        "@jiaminghi/color": "*",
+        "@jiaminghi/transition": "*"
+      }
+    },
+    "@jiaminghi/charts": {
+      "version": "0.2.18",
+      "resolved": "https://registry.npmmirror.com/@jiaminghi/charts/-/charts-0.2.18.tgz",
+      "integrity": "sha512-K+HXaOOeWG9OOY1VG6M4mBreeeIAPhb9X+khG651AbnwEwL6G2UtcAQ8GWCq6GzhczcLwwhIhuaHqRygwHC0sA==",
+      "requires": {
+        "@babel/runtime": "^7.5.5",
+        "@jiaminghi/c-render": "^0.4.3"
+      }
+    },
+    "@jiaminghi/color": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmmirror.com/@jiaminghi/color/-/color-1.1.3.tgz",
+      "integrity": "sha512-ZY3hdorgODk4OSTbxyXBPxAxHPIVf9rPlKJyK1C1db46a50J0reFKpAvfZG8zMG3lvM60IR7Qawgcu4ZDO3+Hg=="
+    },
+    "@jiaminghi/transition": {
+      "version": "1.1.11",
+      "resolved": "https://registry.npmmirror.com/@jiaminghi/transition/-/transition-1.1.11.tgz",
+      "integrity": "sha512-owBggipoHMikDHHDW5Gc7RZYlVuvxHADiU4bxfjBVkHDAmmck+fCkm46n2JzC3j33hWvP9nSCAeh37t6stgWeg==",
+      "requires": {
+        "@babel/runtime": "^7.5.5"
+      }
+    },
     "@jridgewell/sourcemap-codec": {
       "version": "1.4.15",
       "resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
       "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg=="
     },
+    "@kjgl77/datav-vue3": {
+      "version": "1.7.4",
+      "resolved": "https://registry.npmmirror.com/@kjgl77/datav-vue3/-/datav-vue3-1.7.4.tgz",
+      "integrity": "sha512-zYVTVKkklUxwtiNKS1qPBilm4rTW+WItfp0zVpaRAI8wgXkLSPbDR9xPq2+UcU/Jft7/DVdMfBp709E2ResuPQ==",
+      "requires": {
+        "@jiaminghi/c-render": "^0.4.3",
+        "@jiaminghi/charts": "^0.2.18",
+        "@jiaminghi/color": "^1.1.3",
+        "@vueuse/core": "^10.11.1"
+      },
+      "dependencies": {
+        "@vueuse/core": {
+          "version": "10.11.1",
+          "resolved": "https://registry.npmmirror.com/@vueuse/core/-/core-10.11.1.tgz",
+          "integrity": "sha512-guoy26JQktXPcz+0n3GukWIy/JDNKti9v6VEMu6kV2sYBsWuGiTU8OWdg+ADfUbHg3/3DlqySDe7JmdHrktiww==",
+          "requires": {
+            "@types/web-bluetooth": "^0.0.20",
+            "@vueuse/metadata": "10.11.1",
+            "@vueuse/shared": "10.11.1",
+            "vue-demi": ">=0.14.8"
+          }
+        },
+        "@vueuse/metadata": {
+          "version": "10.11.1",
+          "resolved": "https://registry.npmmirror.com/@vueuse/metadata/-/metadata-10.11.1.tgz",
+          "integrity": "sha512-IGa5FXd003Ug1qAZmyE8wF3sJ81xGLSqTqtQ6jaVfkeZ4i5kS2mwQF61yhVqojRnenVew5PldLyRgvdl4YYuSw=="
+        },
+        "@vueuse/shared": {
+          "version": "10.11.1",
+          "resolved": "https://registry.npmmirror.com/@vueuse/shared/-/shared-10.11.1.tgz",
+          "integrity": "sha512-LHpC8711VFZlDaYUXEBbFBCQ7GS3dVU9mjOhhMhXP6txTV4EhYQg/KGnQuvt/sPAtoUKq7VVUnL6mVtFoL42sA==",
+          "requires": {
+            "vue-demi": ">=0.14.8"
+          }
+        },
+        "vue-demi": {
+          "version": "0.14.10",
+          "resolved": "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.14.10.tgz",
+          "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==",
+          "requires": {}
+        }
+      }
+    },
     "@nodelib/fs.scandir": {
       "version": "2.1.5",
       "resolved": "https://registry.npmmirror.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@@ -7960,6 +8183,22 @@
         "domelementtype": "1"
       }
     },
+    "echarts": {
+      "version": "5.5.1",
+      "resolved": "https://registry.npmmirror.com/echarts/-/echarts-5.5.1.tgz",
+      "integrity": "sha512-Fce8upazaAXUVUVsjgV6mBnGuqgO+JNDlcgF79Dksy4+wgGpQB2lmYoO4TSweFg/mZITdpGHomw/cNBJZj1icA==",
+      "requires": {
+        "tslib": "2.3.0",
+        "zrender": "5.6.0"
+      },
+      "dependencies": {
+        "tslib": {
+          "version": "2.3.0",
+          "resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.3.0.tgz",
+          "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg=="
+        }
+      }
+    },
     "element-plus": {
       "version": "2.5.6",
       "resolved": "https://registry.npmmirror.com/element-plus/-/element-plus-2.5.6.tgz",
@@ -11126,6 +11365,21 @@
       "resolved": "https://registry.npmmirror.com/yocto-queue/-/yocto-queue-0.1.0.tgz",
       "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
       "dev": true
+    },
+    "zrender": {
+      "version": "5.6.0",
+      "resolved": "https://registry.npmmirror.com/zrender/-/zrender-5.6.0.tgz",
+      "integrity": "sha512-uzgraf4njmmHAbEUxMJ8Oxg+P3fT04O+9p7gY+wJRVxo8Ge+KmYv0WJev945EH4wFuc4OY2NLXz46FZrWS9xJg==",
+      "requires": {
+        "tslib": "2.3.0"
+      },
+      "dependencies": {
+        "tslib": {
+          "version": "2.3.0",
+          "resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.3.0.tgz",
+          "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg=="
+        }
+      }
     }
   }
 }

+ 2 - 0
package.json

@@ -12,12 +12,14 @@
   },
   "dependencies": {
     "@element-plus/icons-vue": "^2.3.1",
+    "@kjgl77/datav-vue3": "^1.7.4",
     "@vueuse/core": "^10.7.2",
     "@vueuse/integrations": "^10.9.0",
     "@wangeditor/editor": "^5.1.23",
     "@wangeditor/editor-for-vue": "5.1.10",
     "axios": "^1.6.7",
     "dayjs": "^1.11.13",
+    "echarts": "^5.5.1",
     "element-plus": "^2.5.6",
     "lodash-es": "^4.17.21",
     "nprogress": "^0.2.0",

src/assets/images/401.gif → public/images/401.gif


src/assets/images/404.png → public/images/404.png


src/assets/images/404_cloud.png → public/images/404_cloud.png


BIN
public/images/home1.png


BIN
public/images/home2.png


BIN
public/images/home3.png


BIN
public/images/home4.png


src/assets/images/login-bg.jpg → public/images/login-bg.jpg


src/assets/logo.png → public/images/logo.png


+ 1 - 0
src/lang/package/zh-cn/common.js

@@ -7,6 +7,7 @@ export default {
   exam: '审核',
   resetPwd: '重置密码',
   dict: '字典数据',
+  export: '导出',
   delete_confirm: '您确定删除该数据?',
   search: '查询',
   reset: '重置',

+ 1 - 1
src/layout/site.js

@@ -2,7 +2,7 @@
 export const siteInfo = {
   display: false,
   zhTitle: '新一代信息技术孵化平台',
-  logo_url: '/src/assets/logo.png'
+  logo_url: '/images/logo.png'
 }
 // 菜单设置
 export const menuInfo = {

+ 40 - 0
src/store/api/core/node.js

@@ -0,0 +1,40 @@
+import { defineStore } from 'pinia'
+import { AxiosWrapper } from '@/utils/axios-wrapper'
+import { get } from 'lodash-es'
+const url = '/node'
+const axios = new AxiosWrapper()
+
+export const NodeStore = defineStore('node', () => {
+  const query = async ({ skip = 0, limit = undefined, ...info } = {}) => {
+    let cond = {}
+    if (skip) cond.skip = skip
+    if (limit) cond.limit = limit
+    cond = { ...cond, ...info }
+    const res = await axios.$get(`${url}`, cond)
+    return res
+  }
+  const fetch = async (payload) => {
+    const res = await axios.$get(`${url}/${payload}`)
+    return res
+  }
+  const create = async (payload) => {
+    const res = await axios.$post(`${url}`, payload)
+    return res
+  }
+  const update = async (payload) => {
+    const id = get(payload, 'id', get(payload, '_id'))
+    const res = await axios.$post(`${url}/${id}`, payload)
+    return res
+  }
+  const del = async (payload) => {
+    const res = await axios.$delete(`${url}/${payload}`)
+    return res
+  }
+  return {
+    query,
+    fetch,
+    create,
+    update,
+    del
+  }
+})

+ 40 - 0
src/store/api/core/operate.js

@@ -0,0 +1,40 @@
+import { defineStore } from 'pinia'
+import { AxiosWrapper } from '@/utils/axios-wrapper'
+import { get } from 'lodash-es'
+const url = '/operate'
+const axios = new AxiosWrapper()
+
+export const OperateStore = defineStore('operate', () => {
+  const query = async ({ skip = 0, limit = undefined, ...info } = {}) => {
+    let cond = {}
+    if (skip) cond.skip = skip
+    if (limit) cond.limit = limit
+    cond = { ...cond, ...info }
+    const res = await axios.$get(`${url}`, cond)
+    return res
+  }
+  const fetch = async (payload) => {
+    const res = await axios.$get(`${url}/${payload}`)
+    return res
+  }
+  const create = async (payload) => {
+    const res = await axios.$post(`${url}`, payload)
+    return res
+  }
+  const update = async (payload) => {
+    const id = get(payload, 'id', get(payload, '_id'))
+    const res = await axios.$post(`${url}/${id}`, payload)
+    return res
+  }
+  const del = async (payload) => {
+    const res = await axios.$delete(`${url}/${payload}`)
+    return res
+  }
+  return {
+    query,
+    fetch,
+    create,
+    update,
+    del
+  }
+})

+ 40 - 0
src/store/api/core/partition.js

@@ -0,0 +1,40 @@
+import { defineStore } from 'pinia'
+import { AxiosWrapper } from '@/utils/axios-wrapper'
+import { get } from 'lodash-es'
+const url = '/partition'
+const axios = new AxiosWrapper()
+
+export const PartitionStore = defineStore('partition', () => {
+  const query = async ({ skip = 0, limit = undefined, ...info } = {}) => {
+    let cond = {}
+    if (skip) cond.skip = skip
+    if (limit) cond.limit = limit
+    cond = { ...cond, ...info }
+    const res = await axios.$get(`${url}`, cond)
+    return res
+  }
+  const fetch = async (payload) => {
+    const res = await axios.$get(`${url}/${payload}`)
+    return res
+  }
+  const create = async (payload) => {
+    const res = await axios.$post(`${url}`, payload)
+    return res
+  }
+  const update = async (payload) => {
+    const id = get(payload, 'id', get(payload, '_id'))
+    const res = await axios.$post(`${url}/${id}`, payload)
+    return res
+  }
+  const del = async (payload) => {
+    const res = await axios.$delete(`${url}/${payload}`)
+    return res
+  }
+  return {
+    query,
+    fetch,
+    create,
+    update,
+    del
+  }
+})

+ 40 - 0
src/store/api/system/organization.js

@@ -0,0 +1,40 @@
+import { defineStore } from 'pinia'
+import { AxiosWrapper } from '@/utils/axios-wrapper'
+import { get } from 'lodash-es'
+const url = '/organization'
+const axios = new AxiosWrapper()
+
+export const OrganizationStore = defineStore('organization', () => {
+  const query = async ({ skip = 0, limit = undefined, ...info } = {}) => {
+    let cond = {}
+    if (skip) cond.skip = skip
+    if (limit) cond.limit = limit
+    cond = { ...cond, ...info }
+    const res = await axios.$get(`${url}`, cond)
+    return res
+  }
+  const fetch = async (payload) => {
+    const res = await axios.$get(`${url}/${payload}`)
+    return res
+  }
+  const create = async (payload) => {
+    const res = await axios.$post(`${url}`, payload)
+    return res
+  }
+  const update = async (payload) => {
+    const id = get(payload, 'id', get(payload, '_id'))
+    const res = await axios.$post(`${url}/${id}`, payload)
+    return res
+  }
+  const del = async (payload) => {
+    const res = await axios.$delete(`${url}/${payload}`)
+    return res
+  }
+  return {
+    query,
+    fetch,
+    create,
+    update,
+    del
+  }
+})

+ 78 - 6
src/views/colony/node/index.vue

@@ -1,20 +1,92 @@
 <template>
-  <div id="index">
-    <el-row>
-      <el-col :span="24" class="main animate__animated animate__backInRight" v-loading="loading">
-        <el-col :span="24" class="one"> 节点管理 </el-col>
-      </el-col>
-    </el-row>
+  <div class="main animate__animated animate__backInRight" v-loading="loading">
+    <custom-search-bar :fields="fields.filter((f) => f.isSearch)" v-model="searchForm" @search="search" @reset="toReset"></custom-search-bar>
+    <custom-button-bar :fields="buttonFields" @add="toAdd"></custom-button-bar>
+    <custom-table :data="data" :fields="fields" @query="search" :total="total" :opera="opera" @edit="toEdit" @delete="toDelete" :select="true"> </custom-table>
+    <el-dialog v-model="dialog" title="数据维护信息" :destroy-on-close="false" @close="toClose" width="50%">
+      <custom-form v-model="form" :fields="fields" :rules="{}" @save="toSave"> </custom-form>
+    </el-dialog>
   </div>
 </template>
 
 <script setup>
+const $checkRes = inject('$checkRes')
+import { cloneDeep, get } from 'lodash-es'
+const { t } = useI18n()
+// 接口
+import { NodeStore } from '@/store/api/core/node'
+const store = NodeStore()
+const data = ref([])
+const searchForm = ref({})
+const fields = [
+  { label: '节点名称', model: 'name', isSearch: true },
+  { label: '节点类型', model: 'type' },
+  { label: 'CPU已用/总数', model: 'cpu_num' },
+  { label: 'GPU已用/总数', model: 'gpu_num' },
+  { label: '分区', model: 'partition' },
+  { label: '运行状态', model: 'status' },
+  { label: '当前作业数', model: 'work_num' }
+]
+const opera = [
+  { label: t('common.update'), method: 'edit' },
+  { label: t('common.delete'), method: 'delete', confirm: true, type: 'danger' }
+]
+const buttonFields = [{ label: t('common.add'), method: 'add' }]
+let skip = 0
+let limit = inject('limit')
+const total = ref(20)
 // 加载中
 const loading = ref(false)
+const dialog = ref(false)
+const form = ref({})
 // 请求
 onMounted(async () => {
   loading.value = true
+  await search({ skip, limit })
   loading.value = false
 })
+const search = async (query = { skip: 0, limit }) => {
+  const info = { skip: query.skip, limit: query.limit, ...searchForm.value }
+  const res = await store.query(info)
+  if (res.errcode == '0') {
+    data.value = res.data.data
+    total.value = res.data.total
+  }
+}
+// 添加
+const toAdd = () => {
+  dialog.value = true
+}
+// 修改
+const toEdit = (data) => {
+  form.value = data
+  dialog.value = true
+}
+// 删除
+const toDelete = async (data) => {
+  const res = await store.del(data._id)
+  if ($checkRes(res, true)) {
+    search({ skip: 0, limit })
+  }
+}
+const toSave = async () => {
+  const data = cloneDeep(form.value)
+  let res
+  if (get(data, '_id')) res = await store.update(data)
+  else res = await store.create({ ...data })
+  if ($checkRes(res, true)) {
+    search({ skip: 0, limit })
+    toClose()
+  }
+}
+// 重置
+const toReset = async () => {
+  searchForm.value = {}
+  await search({ skip, limit })
+}
+const toClose = () => {
+  form.value = {}
+  dialog.value = false
+}
 </script>
 <style scoped lang="scss"></style>

+ 77 - 6
src/views/colony/partition/index.vue

@@ -1,20 +1,91 @@
 <template>
-  <div id="index">
-    <el-row>
-      <el-col :span="24" class="main animate__animated animate__backInRight" v-loading="loading">
-        <el-col :span="24" class="one"> 分区管理 </el-col>
-      </el-col>
-    </el-row>
+  <div class="main animate__animated animate__backInRight" v-loading="loading">
+    <custom-search-bar :fields="fields.filter((f) => f.isSearch)" v-model="searchForm" @search="search" @reset="toReset"></custom-search-bar>
+    <custom-button-bar :fields="buttonFields" @add="toAdd"></custom-button-bar>
+    <custom-table :data="data" :fields="fields" @query="search" :total="total" :opera="opera" @edit="toEdit" @delete="toDelete" :select="true"> </custom-table>
+    <el-dialog v-model="dialog" title="数据维护信息" :destroy-on-close="false" @close="toClose" width="50%">
+      <custom-form v-model="form" :fields="fields" :rules="{}" @save="toSave"> </custom-form>
+    </el-dialog>
   </div>
 </template>
 
 <script setup>
+const $checkRes = inject('$checkRes')
+import { cloneDeep, get } from 'lodash-es'
+const { t } = useI18n()
+// 接口
+import { PartitionStore } from '@/store/api/core/partition'
+const store = PartitionStore()
+const data = ref([])
+const searchForm = ref({})
+const fields = [
+  { label: '分区名称', model: 'name', isSearch: true },
+  { label: '当前作业数', model: 'work_num' },
+  { label: '节点数', model: 'num' },
+  { label: 'CPU核数', model: 'cpu_num' },
+  { label: 'GPU核数', model: 'gpu_num' },
+  { label: '描述', model: 'remark', type: 'textarea' }
+]
+const opera = [
+  { label: t('common.update'), method: 'edit' },
+  { label: t('common.delete'), method: 'delete', confirm: true, type: 'danger' }
+]
+const buttonFields = [{ label: t('common.add'), method: 'add' }]
+let skip = 0
+let limit = inject('limit')
+const total = ref(20)
 // 加载中
 const loading = ref(false)
+const dialog = ref(false)
+const form = ref({})
 // 请求
 onMounted(async () => {
   loading.value = true
+  await search({ skip, limit })
   loading.value = false
 })
+const search = async (query = { skip: 0, limit }) => {
+  const info = { skip: query.skip, limit: query.limit, ...searchForm.value }
+  const res = await store.query(info)
+  if (res.errcode == '0') {
+    data.value = res.data.data
+    total.value = res.data.total
+  }
+}
+// 添加
+const toAdd = () => {
+  dialog.value = true
+}
+// 修改
+const toEdit = (data) => {
+  form.value = data
+  dialog.value = true
+}
+// 删除
+const toDelete = async (data) => {
+  const res = await store.del(data._id)
+  if ($checkRes(res, true)) {
+    search({ skip: 0, limit })
+  }
+}
+const toSave = async () => {
+  const data = cloneDeep(form.value)
+  let res
+  if (get(data, '_id')) res = await store.update(data)
+  else res = await store.create({ ...data })
+  if ($checkRes(res, true)) {
+    search({ skip: 0, limit })
+    toClose()
+  }
+}
+// 重置
+const toReset = async () => {
+  searchForm.value = {}
+  await search({ skip, limit })
+}
+const toClose = () => {
+  form.value = {}
+  dialog.value = false
+}
 </script>
 <style scoped lang="scss"></style>

+ 1 - 1
src/views/error-page/401.vue

@@ -9,7 +9,7 @@ defineComponent({
 })
 
 const state = reactive({
-  errGif: new URL(`../../assets/images/401.gif`, import.meta.url).href,
+  errGif: new URL(`/images/401.gif`, import.meta.url).href,
 
   ewizardClap: 'https://wpimg.wallstcn.com/007ef517-bafd-4066-aae4-6883632d9646',
   dialogVisible: false

+ 4 - 4
src/views/error-page/404.vue

@@ -2,10 +2,10 @@
   <div class="wscn-http404-container">
     <div class="wscn-http404">
       <div class="pic-404">
-        <img class="pic-404__parent" src="@/assets/images/404.png" alt="404" />
-        <img class="pic-404__child left" src="@/assets/images/404_cloud.png" alt="404" />
-        <img class="pic-404__child mid" src="@/assets/images/404_cloud.png" alt="404" />
-        <img class="pic-404__child right" src="@/assets/images/404_cloud.png" alt="404" />
+        <img class="pic-404__parent" src="/images/404.png" alt="404" />
+        <img class="pic-404__child left" src="/images/404_cloud.png" alt="404" />
+        <img class="pic-404__child mid" src="/images/404_cloud.png" alt="404" />
+        <img class="pic-404__child right" src="/images/404_cloud.png" alt="404" />
       </div>
       <div class="bullshit">
         <div class="bullshit__oops">404错误!</div>

+ 73 - 0
src/views/home/echarts/echarts1.vue

@@ -0,0 +1,73 @@
+<template>
+  <div ref="echarts1" class="echarts1"></div>
+</template>
+<style scoped lang="scss">
+.echarts1 {
+  height: 100%;
+  width: 100%;
+}
+</style>
+<script setup>
+import * as echarts from 'echarts'
+const echarts1 = ref()
+onMounted(() => {
+  echarts1View()
+})
+function echarts1View() {
+  const myChart1 = echarts.init(echarts1.value)
+  const option1 = {
+    tooltip: {
+      // 鼠标悬浮提示数据
+      trigger: 'axis',
+      borderWidth: 15,
+      textStyle: {
+        // 文字提示样式
+        fontSize: '16'
+      },
+      axisPointer: {
+        // 坐标轴虚线
+        type: 'cross',
+        label: {
+          backgroundColor: '#6a7985'
+        }
+      }
+    },
+
+    // },
+    grid: {
+      // 控制图表的位置
+      left: '5%',
+      right: '5%',
+      top: '5%',
+      bottom: '5%',
+      containLabel: true
+    },
+    xAxis: {
+      axisLabel: {
+        // X轴线 标签修改
+        textStyle: {
+          color: '#333', //更改坐标轴文字颜色
+          fontSize: 14 //更改坐标轴文字大小
+        }
+      },
+      data: ['computerPartiton', 'cu-amd']
+    },
+    yAxis: {},
+    series: [
+      {
+        data: [28, 20],
+        type: 'bar',
+        label: {
+          show: true, // 显示标签
+          position: 'top' // 标签位置顶部
+        },
+        barWidth: '20%' //调整柱状图宽度
+      }
+    ]
+  }
+  myChart1.setOption(option1)
+  window.addEventListener('resize', function () {
+    myChart1.resize()
+  })
+}
+</script>

+ 59 - 0
src/views/home/echarts/echarts2.vue

@@ -0,0 +1,59 @@
+<template>
+  <div ref="echarts2" class="echarts2"></div>
+</template>
+<style scoped lang="scss">
+.echarts2 {
+  height: 100%;
+  width: 100%;
+}
+</style>
+<script setup>
+import * as echarts from 'echarts'
+const echarts2 = ref()
+onMounted(() => {
+  echarts2View()
+})
+function echarts2View() {
+  const myChart2 = echarts.init(echarts2.value)
+  const option2 = {
+    tooltip: {
+      trigger: 'item'
+    },
+    legend: {
+      orient: 'vertical',
+      left: 'left'
+    },
+    series: [
+      {
+        name: '实时状态作业数',
+        type: 'pie',
+        radius: '80%',
+        itemStyle: {
+          normal: {
+            label: {
+              show: true,
+              formatter: '{b} : ({c})'
+            }
+          },
+          labelLine: { show: true }
+        },
+        data: [
+          { value: 5, name: 'PENDING' },
+          { value: 24, name: 'RUNNING' }
+        ],
+        emphasis: {
+          itemStyle: {
+            shadowBlur: 10,
+            shadowOffsetX: 0,
+            shadowColor: 'rgba(0, 0, 0, 0.5)'
+          }
+        }
+      }
+    ]
+  }
+  myChart2.setOption(option2)
+  window.addEventListener('resize', function () {
+    myChart2.resize()
+  })
+}
+</script>

+ 127 - 0
src/views/home/echarts/echarts3.vue

@@ -0,0 +1,127 @@
+<template>
+  <div ref="echarts3" class="echarts3"></div>
+</template>
+<style scoped lang="scss">
+.echarts3 {
+  height: 100%;
+  width: 100%;
+}
+</style>
+<script setup>
+import * as echarts from 'echarts'
+const echarts3 = ref()
+onMounted(() => {
+  echarts3View()
+})
+function echarts3View() {
+  const myChart3 = echarts.init(echarts3.value)
+  const option3 = {
+    tooltip: {
+      trigger: 'axis'
+    },
+    grid: {
+      left: '10%',
+      right: '0%',
+      bottom: '10%',
+      top: '5%'
+    },
+    legend: {
+      orient: 'horizontal',
+      x: '20px',
+      y: '10px',
+      itemGap: 50, //图例之间间距
+      itemWidth: 12, //图例宽
+      itemHeight: 12, //图例高
+      icon: 'circle',
+      textStyle: {
+        fontSize: 14 //更改坐标轴文字大小
+      }
+    },
+    xAxis: {
+      type: 'category',
+      axisTick: {
+        //x轴刻度尺
+        show: false,
+        alignWithLabel: true
+      },
+      axisLine: {
+        //x轴线条颜色
+        show: true,
+        lineStyle: {
+          color: '#dadada',
+          width: 0.5
+        }
+      },
+      axisLabel: {
+        //x轴文字倾斜
+        show: true,
+        textStyle: {
+          color: '#333', //更改坐标轴文字颜色
+          fontSize: 14 //更改坐标轴文字大小
+        }
+      },
+      data: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月']
+    },
+    yAxis: {
+      type: 'value',
+      name: '',
+      nameTextStyle: {
+        padding: [0, 30, 5, 0], // y轴name位置
+        color: '#333', //更改坐标轴文字颜色
+        fontSize: 14 //更改坐标轴文字大小
+      },
+      splitLine: {
+        show: true, //关闭网格线
+        lineStyle: {
+          color: '#dadada',
+          width: 0.5
+        }
+      },
+      axisLine: {
+        //y轴线条颜色
+        show: false
+      },
+      axisTick: {
+        //x轴刻度
+        show: false
+      },
+      axisLabel: {
+        //y轴文字倾斜
+        textStyle: {
+          color: '#333', //更改坐标轴文字颜色
+          fontSize: 14 //更改坐标轴文字大小
+        }
+      }
+    },
+    series: [
+      {
+        type: 'line',
+        stack: 'Total',
+        symbolSize: 8, //设定实心点的大小
+        smooth: true, //面积图改成弧形状
+        lineStyle: {
+          normal: {
+            color: '#168eff', //外边线颜色
+            width: 3, //外边线宽度
+            shadowColor: '#168eff', //线阴影颜色
+            shadowOffsetY: 10, //阴影大小
+            shadowBlur: 15
+          }
+        },
+        itemStyle: {
+          normal: {
+            //节点颜色
+            color: '#168eff'
+          }
+        },
+        showSymbol: true, //去除面积图节点圆
+        data: [148, 108, 96, 95, 84, 69, 123, 160, 190, 123, 251, 220]
+      }
+    ]
+  }
+  myChart3.setOption(option3)
+  window.addEventListener('resize', function () {
+    myChart3.resize()
+  })
+}
+</script>

+ 127 - 0
src/views/home/echarts/echarts4.vue

@@ -0,0 +1,127 @@
+<template>
+  <div ref="echarts4" class="echarts4"></div>
+</template>
+<style scoped lang="scss">
+.echarts4 {
+  height: 100%;
+  width: 100%;
+}
+</style>
+<script setup>
+import * as echarts from 'echarts'
+const echarts4 = ref()
+onMounted(() => {
+  echarts4View()
+})
+function echarts4View() {
+  const myChart4 = echarts.init(echarts4.value)
+  const option4 = {
+    tooltip: {
+      trigger: 'axis'
+    },
+    grid: {
+      left: '5%',
+      right: '0%',
+      bottom: '10%',
+      top: '5%'
+    },
+    legend: {
+      orient: 'horizontal',
+      x: '20px',
+      y: '10px',
+      itemGap: 50, //图例之间间距
+      itemWidth: 12, //图例宽
+      itemHeight: 12, //图例高
+      icon: 'circle',
+      textStyle: {
+        fontSize: 14 //更改坐标轴文字大小
+      }
+    },
+    xAxis: {
+      type: 'category',
+      axisTick: {
+        //x轴刻度尺
+        show: false,
+        alignWithLabel: true
+      },
+      axisLine: {
+        //x轴线条颜色
+        show: true,
+        lineStyle: {
+          color: '#dadada',
+          width: 0.5
+        }
+      },
+      axisLabel: {
+        //x轴文字倾斜
+        show: true,
+        textStyle: {
+          color: '#333', //更改坐标轴文字颜色
+          fontSize: 14 //更改坐标轴文字大小
+        }
+      },
+      data: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月']
+    },
+    yAxis: {
+      type: 'value',
+      name: '',
+      nameTextStyle: {
+        padding: [0, 30, 5, 0], // y轴name位置
+        color: '#333', //更改坐标轴文字颜色
+        fontSize: 14 //更改坐标轴文字大小
+      },
+      splitLine: {
+        show: true, //关闭网格线
+        lineStyle: {
+          color: '#dadada',
+          width: 0.5
+        }
+      },
+      axisLine: {
+        //y轴线条颜色
+        show: false
+      },
+      axisTick: {
+        //x轴刻度
+        show: false
+      },
+      axisLabel: {
+        //y轴文字倾斜
+        textStyle: {
+          color: '#333', //更改坐标轴文字颜色
+          fontSize: 14 //更改坐标轴文字大小
+        }
+      }
+    },
+    series: [
+      {
+        type: 'line',
+        stack: 'Total',
+        symbolSize: 8, //设定实心点的大小
+        smooth: true, //面积图改成弧形状
+        lineStyle: {
+          normal: {
+            color: '#168eff', //外边线颜色
+            width: 3, //外边线宽度
+            shadowColor: '#168eff', //线阴影颜色
+            shadowOffsetY: 10, //阴影大小
+            shadowBlur: 15
+          }
+        },
+        itemStyle: {
+          normal: {
+            //节点颜色
+            color: '#168eff'
+          }
+        },
+        showSymbol: true, //去除面积图节点圆
+        data: [148, 108, 96, 95, 84, 69, 123, 160, 190, 123, 251, 220]
+      }
+    ]
+  }
+  myChart4.setOption(option4)
+  window.addEventListener('resize', function () {
+    myChart4.resize()
+  })
+}
+</script>

+ 127 - 0
src/views/home/echarts/echarts5.vue

@@ -0,0 +1,127 @@
+<template>
+  <div ref="echarts5" class="echarts5"></div>
+</template>
+<style scoped lang="scss">
+.echarts5 {
+  height: 100%;
+  width: 100%;
+}
+</style>
+<script setup>
+import * as echarts from 'echarts'
+const echarts5 = ref()
+onMounted(() => {
+  echarts5View()
+})
+function echarts5View() {
+  const myChart5 = echarts.init(echarts5.value)
+  const option5 = {
+    tooltip: {
+      trigger: 'axis'
+    },
+    grid: {
+      left: '10%',
+      right: '0%',
+      bottom: '10%',
+      top: '5%'
+    },
+    legend: {
+      orient: 'horizontal',
+      x: '20px',
+      y: '10px',
+      itemGap: 50, //图例之间间距
+      itemWidth: 12, //图例宽
+      itemHeight: 12, //图例高
+      icon: 'circle',
+      textStyle: {
+        fontSize: 14 //更改坐标轴文字大小
+      }
+    },
+    xAxis: {
+      type: 'category',
+      axisTick: {
+        //x轴刻度尺
+        show: false,
+        alignWithLabel: true
+      },
+      axisLine: {
+        //x轴线条颜色
+        show: true,
+        lineStyle: {
+          color: '#dadada',
+          width: 0.5
+        }
+      },
+      axisLabel: {
+        //x轴文字倾斜
+        show: true,
+        textStyle: {
+          color: '#333', //更改坐标轴文字颜色
+          fontSize: 14 //更改坐标轴文字大小
+        }
+      },
+      data: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月']
+    },
+    yAxis: {
+      type: 'value',
+      name: '',
+      nameTextStyle: {
+        padding: [0, 30, 5, 0], // y轴name位置
+        color: '#333', //更改坐标轴文字颜色
+        fontSize: 14 //更改坐标轴文字大小
+      },
+      splitLine: {
+        show: true, //关闭网格线
+        lineStyle: {
+          color: '#dadada',
+          width: 0.5
+        }
+      },
+      axisLine: {
+        //y轴线条颜色
+        show: false
+      },
+      axisTick: {
+        //x轴刻度
+        show: false
+      },
+      axisLabel: {
+        //y轴文字倾斜
+        textStyle: {
+          color: '#333', //更改坐标轴文字颜色
+          fontSize: 14 //更改坐标轴文字大小
+        }
+      }
+    },
+    series: [
+      {
+        type: 'line',
+        stack: 'Total',
+        symbolSize: 8, //设定实心点的大小
+        smooth: true, //面积图改成弧形状
+        lineStyle: {
+          normal: {
+            color: '#168eff', //外边线颜色
+            width: 3, //外边线宽度
+            shadowColor: '#168eff', //线阴影颜色
+            shadowOffsetY: 10, //阴影大小
+            shadowBlur: 15
+          }
+        },
+        itemStyle: {
+          normal: {
+            //节点颜色
+            color: '#168eff'
+          }
+        },
+        showSymbol: true, //去除面积图节点圆
+        data: [148, 108, 96, 95, 84, 69, 123, 160, 190, 123, 251, 220]
+      }
+    ]
+  }
+  myChart5.setOption(option5)
+  window.addEventListener('resize', function () {
+    myChart5.resize()
+  })
+}
+</script>

+ 151 - 0
src/views/home/echarts/echarts6.vue

@@ -0,0 +1,151 @@
+<template>
+  <div ref="echarts6" class="echarts6"></div>
+</template>
+<style scoped lang="scss">
+.echarts6 {
+  height: 100%;
+  width: 100%;
+}
+</style>
+<script setup>
+import * as echarts from 'echarts'
+const echarts6 = ref()
+onMounted(() => {
+  echarts6View()
+})
+function echarts6View() {
+  let ydata = ['上海', '北京', '深圳', '天津', '河南']
+  let xdata = [12, 13, 14, 15, 16]
+  const myChart6 = echarts.init(echarts6.value)
+  const option6 = {
+    tooltip: {
+      trigger: 'axis'
+    },
+    grid: {
+      left: '15%',
+      right: '5%',
+      bottom: '0%',
+      top: '0%',
+      containLabel: false
+    },
+    xAxis: {
+      type: 'value',
+      show: false
+    },
+    yAxis: {
+      type: 'category',
+      data: ydata,
+      axisLine: {
+        show: false
+      },
+      axisTick: {
+        show: false
+      },
+      axisLabel: {
+        margin: 70,
+        width: 60,
+        align: 'left',
+        overflow: 'truncate',
+        formatter: function (value, index) {
+          let ind = index + 1
+          if (ind == ydata.length) {
+            return '{one|' + (ydata.length - index) + '} {a|' + value + '}'
+          } else if (ind + 1 == ydata.length) {
+            return '{two|' + (ydata.length - index) + '} {b|' + value + '}'
+          } else if (ind + 2 == ydata.length) {
+            return '{three|' + (ydata.length - index) + '} {c|' + value + '}'
+          }
+          if (ydata.length - index > 9) {
+            return '{five|' + (ydata.length - index) + '} {d|' + value + '}'
+          }
+          return '{four|' + (ydata.length - index) + '} {d|' + value + '}'
+        },
+        rich: {
+          a: {
+            color: '#59c9f9'
+          },
+          b: {
+            color: '#59c9f9'
+          },
+          c: {
+            color: '#59c9f9'
+          },
+          d: {
+            color: '#59c9f9'
+          },
+          // 第一名
+          one: {
+            backgroundColor: '#E86452',
+            color: 'white',
+            width: 12,
+            height: 16,
+            padding: [1, 0, 0, 5],
+            borderRadius: 10,
+            fontSize: 11
+          },
+          // 第二名
+          two: {
+            backgroundColor: '#FF9845',
+            color: 'white',
+            width: 12,
+            height: 16,
+            padding: [1, 0, 0, 5],
+            borderRadius: 10,
+            fontSize: 11
+          },
+          // 第三名
+          three: {
+            backgroundColor: '#F6BD16',
+            color: 'white',
+            width: 12,
+            height: 16,
+            padding: [1, 0, 0, 5],
+            borderRadius: 10,
+            fontSize: 11
+          },
+          // 一位数
+          four: {
+            backgroundColor: 'rgba(0,0,0,0.15)',
+            color: 'white',
+            width: 12,
+            height: 16,
+            padding: [1, 0, 0, 5],
+            borderRadius: 10,
+            fontSize: 11
+          },
+          // 两位数
+          five: {
+            backgroundColor: 'rgba(0,0,0,0.15)',
+            color: 'white',
+            width: 16,
+            height: 16,
+            padding: [1, 0, 0, 1],
+            borderRadius: 10,
+            fontSize: 11
+          }
+        }
+      }
+    },
+    series: [
+      {
+        type: 'bar',
+        showBackground: true,
+        label: {
+          show: true,
+          position: 'right',
+          color: 'rgba(0,0,0,0.45)'
+        },
+        barWidth: 20,
+        itemStyle: {
+          color: '#5B8FF9'
+        },
+        data: xdata
+      }
+    ]
+  }
+  myChart6.setOption(option6)
+  window.addEventListener('resize', function () {
+    myChart6.resize()
+  })
+}
+</script>

+ 151 - 0
src/views/home/echarts/echarts7.vue

@@ -0,0 +1,151 @@
+<template>
+  <div ref="echarts7" class="echarts7"></div>
+</template>
+<style scoped lang="scss">
+.echarts7 {
+  height: 100%;
+  width: 100%;
+}
+</style>
+<script setup>
+import * as echarts from 'echarts'
+const echarts7 = ref()
+onMounted(() => {
+  echarts7View()
+})
+function echarts7View() {
+  let ydata = ['上海', '北京', '深圳', '天津', '河南']
+  let xdata = [12, 13, 14, 15, 16]
+  const myChart7 = echarts.init(echarts7.value)
+  const option7 = {
+    tooltip: {
+      trigger: 'axis'
+    },
+    grid: {
+      left: '15%',
+      right: '5%',
+      bottom: '0%',
+      top: '0%',
+      containLabel: false
+    },
+    xAxis: {
+      type: 'value',
+      show: false
+    },
+    yAxis: {
+      type: 'category',
+      data: ydata,
+      axisLine: {
+        show: false
+      },
+      axisTick: {
+        show: false
+      },
+      axisLabel: {
+        margin: 70,
+        width: 60,
+        align: 'left',
+        overflow: 'truncate',
+        formatter: function (value, index) {
+          let ind = index + 1
+          if (ind == ydata.length) {
+            return '{one|' + (ydata.length - index) + '} {a|' + value + '}'
+          } else if (ind + 1 == ydata.length) {
+            return '{two|' + (ydata.length - index) + '} {b|' + value + '}'
+          } else if (ind + 2 == ydata.length) {
+            return '{three|' + (ydata.length - index) + '} {c|' + value + '}'
+          }
+          if (ydata.length - index > 9) {
+            return '{five|' + (ydata.length - index) + '} {d|' + value + '}'
+          }
+          return '{four|' + (ydata.length - index) + '} {d|' + value + '}'
+        },
+        rich: {
+          a: {
+            color: '#59c9f9'
+          },
+          b: {
+            color: '#59c9f9'
+          },
+          c: {
+            color: '#59c9f9'
+          },
+          d: {
+            color: '#59c9f9'
+          },
+          // 第一名
+          one: {
+            backgroundColor: '#E86452',
+            color: 'white',
+            width: 12,
+            height: 16,
+            padding: [1, 0, 0, 5],
+            borderRadius: 10,
+            fontSize: 11
+          },
+          // 第二名
+          two: {
+            backgroundColor: '#FF9845',
+            color: 'white',
+            width: 12,
+            height: 16,
+            padding: [1, 0, 0, 5],
+            borderRadius: 10,
+            fontSize: 11
+          },
+          // 第三名
+          three: {
+            backgroundColor: '#F6BD16',
+            color: 'white',
+            width: 12,
+            height: 16,
+            padding: [1, 0, 0, 5],
+            borderRadius: 10,
+            fontSize: 11
+          },
+          // 一位数
+          four: {
+            backgroundColor: 'rgba(0,0,0,0.15)',
+            color: 'white',
+            width: 12,
+            height: 16,
+            padding: [1, 0, 0, 5],
+            borderRadius: 10,
+            fontSize: 11
+          },
+          // 两位数
+          five: {
+            backgroundColor: 'rgba(0,0,0,0.15)',
+            color: 'white',
+            width: 16,
+            height: 16,
+            padding: [1, 0, 0, 1],
+            borderRadius: 10,
+            fontSize: 11
+          }
+        }
+      }
+    },
+    series: [
+      {
+        type: 'bar',
+        showBackground: true,
+        label: {
+          show: true,
+          position: 'right',
+          color: 'rgba(0,0,0,0.45)'
+        },
+        barWidth: 20,
+        itemStyle: {
+          color: '#5B8FF9'
+        },
+        data: xdata
+      }
+    ]
+  }
+  myChart7.setOption(option7)
+  window.addEventListener('resize', function () {
+    myChart7.resize()
+  })
+}
+</script>

+ 236 - 2
src/views/home/index.vue

@@ -2,13 +2,205 @@
   <div id="index">
     <el-row>
       <el-col :span="24" class="main animate__animated animate__backInRight" v-loading="loading">
-        <el-col :span="24" class="one"> 首页 </el-col>
+        <el-row :gutter="20" class="one">
+          <el-col :span="6">
+            <el-card>
+              <div class="one_1">
+                <div class="title">
+                  <span>节点</span>
+                  <span>{{ nodeList[0].num || 0 }}</span>
+                </div>
+                <el-image class="image" :src="home1" fit="fill" />
+              </div>
+              <div class="one_2">
+                <el-divider style="margin: 10px 0"></el-divider>
+                <el-switch v-model="value1" disabled />
+                <span>开机({{ nodeList[1].num || 0 }})</span>
+                <el-divider direction="vertical"></el-divider>
+                <el-switch v-model="value2" disabled />
+                <span>关机({{ nodeList[2].num || 0 }})</span>
+              </div>
+            </el-card>
+          </el-col>
+          <el-col :span="6">
+            <el-card>
+              <div class="one_1">
+                <div class="title">
+                  <span>CPU(核)</span>
+                  <span>{{ cpuList[0].num || 0 }}</span>
+                </div>
+                <el-image class="image" :src="home2" fit="fill" />
+              </div>
+              <div class="one_2">
+                <el-divider style="margin: 10px 0"></el-divider>
+                <el-switch v-model="value1" disabled />
+                <span>已用({{ cpuList[1].num || 0 }})</span>
+                <el-divider direction="vertical"></el-divider>
+                <el-switch v-model="value2" disabled />
+                <span>未用({{ cpuList[2].num || 0 }})</span>
+              </div>
+            </el-card>
+          </el-col>
+          <el-col :span="6">
+            <el-card>
+              <div class="one_1">
+                <div class="title">
+                  <span>用户总数</span>
+                  <span>{{ userList[0].num || 0 }}</span>
+                </div>
+                <el-image class="image" :src="home3" fit="fill" />
+              </div>
+              <div class="one_3">
+                <el-progress :percentage="100" :stroke-width="15" :show-text="false" striped striped-flow :duration="600" />
+              </div>
+            </el-card>
+          </el-col>
+          <el-col :span="6">
+            <el-card>
+              <div class="one_1">
+                <div class="title">
+                  <span>在线人数</span>
+                  <span>{{ userList[1].num || 0 }}</span>
+                </div>
+                <el-image class="image" :src="home4" fit="fill" />
+              </div>
+              <div class="one_3">
+                <el-progress :percentage="2" :stroke-width="15" :show-text="false" striped striped-flow :duration="600" />
+              </div>
+            </el-card>
+          </el-col>
+        </el-row>
+        <el-row :gutter="20" class="two">
+          <el-col :span="8">
+            <el-card>
+              <template #header>
+                <div class="card-header">
+                  <span>分区节点数</span>
+                </div>
+              </template>
+              <div class="echarts">
+                <echarts1></echarts1>
+              </div>
+            </el-card>
+          </el-col>
+          <el-col :span="16">
+            <el-card>
+              <template #header>
+                <div class="card-header">
+                  <span>实时状态作业数(总数:{{ work_total || 0 }})</span>
+                </div>
+              </template>
+              <div class="echarts">
+                <echarts2></echarts2>
+              </div>
+            </el-card>
+          </el-col>
+        </el-row>
+        <el-row :gutter="20" class="two">
+          <el-col :span="8">
+            <el-card>
+              <template #header>
+                <div class="card-header">
+                  <span>存储利用率(%)</span>
+                </div>
+              </template>
+              <div class="echarts">
+                <echarts3></echarts3>
+              </div>
+            </el-card>
+          </el-col>
+          <el-col :span="16">
+            <el-card>
+              <template #header>
+                <div class="card-header">
+                  <span>CPU资源利用率(%)</span>
+                </div>
+              </template>
+              <div class="echarts">
+                <echarts4></echarts4>
+              </div>
+            </el-card>
+          </el-col>
+        </el-row>
+        <el-row :gutter="20" class="two">
+          <el-col :span="8">
+            <el-card>
+              <template #header>
+                <div class="card-header">
+                  <span>作业提交数</span>
+                </div>
+              </template>
+              <div class="echarts">
+                <echarts5></echarts5>
+              </div>
+            </el-card>
+          </el-col>
+          <el-col :span="8">
+            <el-card>
+              <template #header>
+                <div class="card-header">
+                  <span>组织提交作业TOP5(30天)</span>
+                </div>
+              </template>
+              <div class="echarts">
+                <echarts6></echarts6>
+              </div>
+            </el-card>
+          </el-col>
+          <el-col :span="8">
+            <el-card>
+              <template #header>
+                <div class="card-header">
+                  <span>用户提交作业TOP5(30天)</span>
+                </div>
+              </template>
+              <div class="echarts">
+                <echarts7></echarts7>
+              </div>
+            </el-card>
+          </el-col>
+        </el-row>
       </el-col>
     </el-row>
   </div>
 </template>
 
 <script setup>
+// 图片引入
+import home1 from '/images/home1.png'
+import home2 from '/images/home2.png'
+import home3 from '/images/home3.png'
+import home4 from '/images/home4.png'
+// 组件
+import echarts1 from './echarts/echarts1.vue'
+import echarts2 from './echarts/echarts2.vue'
+import echarts3 from './echarts/echarts3.vue'
+import echarts4 from './echarts/echarts4.vue'
+import echarts5 from './echarts/echarts5.vue'
+import echarts6 from './echarts/echarts6.vue'
+import echarts7 from './echarts/echarts7.vue'
+// 开关状态
+const value1 = ref(true)
+const value2 = ref(false)
+// 节点情况
+const nodeList = ref([
+  { name: '节点总数', num: 49 },
+  { name: '开机总数', num: 0 },
+  { name: '关机总数', num: 49 }
+])
+// CPU情况
+const cpuList = ref([
+  { name: 'CPU总数', num: 5464 },
+  { name: '已用总数', num: 1280 },
+  { name: '未用总数', num: 4184 }
+])
+// 用户数量
+const userList = ref([
+  { name: '用户总数', num: 22 },
+  { name: '在线人数', num: 1 }
+])
+// 作业总数
+const work_total = ref(29)
 // 加载中
 const loading = ref(false)
 // 请求
@@ -17,4 +209,46 @@ onMounted(async () => {
   loading.value = false
 })
 </script>
-<style scoped lang="scss"></style>
+<style scoped lang="scss">
+.main {
+  .one {
+    margin: 20px 0 0 0;
+    .one_1 {
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      .title {
+        display: flex;
+        flex-direction: column;
+        span:first-child {
+          color: #909399;
+        }
+        span:last-child {
+          font-size: 20px;
+          font-weight: bold;
+          margin: 10px 0 0 0;
+        }
+      }
+      .image {
+        width: 50px;
+        height: 50px;
+      }
+    }
+    .one_2 {
+      text-align: center;
+      span {
+        padding: 0 10px;
+      }
+    }
+    .one_3 {
+      margin: 39px 0 0 0;
+    }
+  }
+  .two {
+    margin: 20px 0;
+    .echarts {
+      height: 200px;
+    }
+  }
+}
+</style>

+ 1 - 1
src/views/login/index.vue

@@ -80,7 +80,7 @@ function handleLogin() {
   display: flex;
   align-items: center;
   justify-content: center;
-  background: url('@/assets/images/login-bg.jpg') no-repeat center right;
+  background: url('/images/login-bg.jpg') no-repeat center right;
 
   .lang {
     display: flex;

+ 106 - 6
src/views/operate/index.vue

@@ -1,20 +1,120 @@
 <template>
-  <div id="index">
-    <el-row>
-      <el-col :span="24" class="main animate__animated animate__backInRight" v-loading="loading">
-        <el-col :span="24" class="one"> 操作日志 </el-col>
-      </el-col>
-    </el-row>
+  <div class="main animate__animated animate__backInRight" v-loading="loading">
+    <custom-search-bar :fields="fields.filter((f) => f.isSearch)" v-model="searchForm" @search="search" @reset="toReset"></custom-search-bar>
+    <custom-button-bar :fields="buttonFields" @add="toAdd"></custom-button-bar>
+    <custom-table :data="data" :fields="fields" @query="search" :total="total" :opera="opera" @edit="toEdit" @delete="toDelete" @export="toExport" :select="true">
+      <template #result="{ row }">
+        <el-icon v-if="row.result == '0'" :size="20" color="#67C23A"><SuccessFilled /></el-icon>
+        <el-icon v-else color="#F56C6C" :size="20"><CircleCloseFilled /></el-icon>
+      </template>
+    </custom-table>
+    <el-dialog v-model="dialog" title="数据维护信息" :destroy-on-close="false" @close="toClose" width="50%">
+      <custom-form v-model="form" :fields="fields" :rules="{}" @save="toSave">
+        <template #result>
+          <el-radio-group v-model="form.result">
+            <el-radio v-for="i in isUseList" :key="i._id" :label="i.value">{{ i.label }}</el-radio>
+          </el-radio-group>
+        </template>
+      </custom-form>
+    </el-dialog>
   </div>
 </template>
 
 <script setup>
+const $checkRes = inject('$checkRes')
+import { cloneDeep, get } from 'lodash-es'
+const { t } = useI18n()
+// 接口
+import { OperateStore } from '@/store/api/core/operate'
+import { DictDataStore } from '@/store/api/system/dictData'
+const store = OperateStore()
+const dictDataStore = DictDataStore()
+const data = ref([])
+const searchForm = ref({})
+const fields = [
+  { label: '操作IP', model: 'operator_ip' },
+  { label: '操作者', model: 'operator_name', isSearch: true },
+  { label: '操作时间', model: 'time', isSearch: true, type: 'datetime' },
+  { label: '模块名称', model: 'module_name', isSearch: true },
+  { label: '操作', model: 'operation' },
+  { label: '操作结果', model: 'result', custom: true },
+  { label: '操作详情', model: 'detail', type: 'textarea' }
+]
+const opera = [
+  { label: t('common.update'), method: 'edit' },
+  { label: t('common.delete'), method: 'delete', confirm: true, type: 'danger' }
+]
+const buttonFields = [
+  { label: t('common.add'), method: 'add' },
+  { label: t('common.export'), method: 'export' }
+]
+let skip = 0
+let limit = inject('limit')
+const total = ref(20)
 // 加载中
 const loading = ref(false)
+const dialog = ref(false)
+const form = ref({})
 // 请求
 onMounted(async () => {
   loading.value = true
+  await searchOther()
+  await search({ skip, limit })
   loading.value = false
 })
+const isUseList = ref([])
+const searchOther = async () => {
+  const result = await dictDataStore.query({ code: 'isUse', is_use: '0' })
+  if ($checkRes(result)) {
+    isUseList.value = result.data.data
+  }
+}
+const search = async (query = { skip: 0, limit }) => {
+  const info = { skip: query.skip, limit: query.limit, ...searchForm.value }
+  const res = await store.query(info)
+  if (res.errcode == '0') {
+    data.value = res.data.data
+    total.value = res.data.total
+  }
+}
+// 添加
+const toAdd = () => {
+  dialog.value = true
+}
+// 修改
+const toEdit = (data) => {
+  form.value = data
+  dialog.value = true
+}
+// 删除
+const toDelete = async (data) => {
+  const res = await store.del(data._id)
+  if ($checkRes(res, true)) {
+    search({ skip: 0, limit })
+  }
+}
+// 导出
+const toExport = () => {
+  console.log('导出')
+}
+const toSave = async () => {
+  const data = cloneDeep(form.value)
+  let res
+  if (get(data, '_id')) res = await store.update(data)
+  else res = await store.create({ ...data })
+  if ($checkRes(res, true)) {
+    search({ skip: 0, limit })
+    toClose()
+  }
+}
+// 重置
+const toReset = async () => {
+  searchForm.value = {}
+  await search({ skip, limit })
+}
+const toClose = () => {
+  form.value = {}
+  dialog.value = false
+}
 </script>
 <style scoped lang="scss"></style>

+ 82 - 6
src/views/system/organization/index.vue

@@ -1,20 +1,96 @@
 <template>
-  <div id="index">
-    <el-row>
-      <el-col :span="24" class="main animate__animated animate__backInRight" v-loading="loading">
-        <el-col :span="24" class="one"> 组织管理 </el-col>
-      </el-col>
-    </el-row>
+  <div class="main animate__animated animate__backInRight" v-loading="loading">
+    <custom-search-bar :fields="fields.filter((f) => f.isSearch)" v-model="searchForm" @search="search" @reset="toReset"></custom-search-bar>
+    <custom-button-bar :fields="buttonFields" @add="toAdd"></custom-button-bar>
+    <custom-table :data="data" :fields="fields" @query="search" :total="total" :opera="opera" @edit="toEdit" @delete="toDelete" :select="true"> </custom-table>
+    <el-dialog v-model="dialog" title="数据维护信息" :destroy-on-close="false" @close="toClose" width="50%">
+      <custom-form v-model="form" :fields="fields" :rules="{}" @save="toSave"> </custom-form>
+    </el-dialog>
   </div>
 </template>
 
 <script setup>
+const $checkRes = inject('$checkRes')
+import { cloneDeep, get } from 'lodash-es'
+const { t } = useI18n()
+// 接口
+import { OrganizationStore } from '@/store/api/system/organization'
+const store = OrganizationStore()
+const data = ref([])
+const searchForm = ref({})
+const fields = [
+  { label: '组织名称', model: 'name', isSearch: true },
+  { label: '上级组织', model: 'manager' },
+  { label: '组成员数', model: 'num' },
+  { label: '最大运行作业数', model: 'max_work' },
+  { label: '最大使用核数', model: 'max_pit' },
+  { label: '最大使用GPU数', model: 'max_cpu' },
+  { label: '计费折扣', model: 'money' },
+  { label: '存储配额(G)', model: 'quota' },
+  { label: '已使用存储(G)', model: 'storage' },
+  { label: '共享文件夹', model: 'folder' },
+  { label: '备注', model: 'remark' }
+]
+const opera = [
+  { label: t('common.update'), method: 'edit' },
+  { label: t('common.delete'), method: 'delete', confirm: true, type: 'danger' }
+]
+const buttonFields = [{ label: t('common.add'), method: 'add' }]
+let skip = 0
+let limit = inject('limit')
+const total = ref(20)
 // 加载中
 const loading = ref(false)
+const dialog = ref(false)
+const form = ref({})
 // 请求
 onMounted(async () => {
   loading.value = true
+  await search({ skip, limit })
   loading.value = false
 })
+const search = async (query = { skip: 0, limit }) => {
+  const info = { skip: query.skip, limit: query.limit, ...searchForm.value }
+  const res = await store.query(info)
+  if (res.errcode == '0') {
+    data.value = res.data.data
+    total.value = res.data.total
+  }
+}
+// 添加
+const toAdd = () => {
+  dialog.value = true
+}
+// 修改
+const toEdit = (data) => {
+  form.value = data
+  dialog.value = true
+}
+// 删除
+const toDelete = async (data) => {
+  const res = await store.del(data._id)
+  if ($checkRes(res, true)) {
+    search({ skip: 0, limit })
+  }
+}
+const toSave = async () => {
+  const data = cloneDeep(form.value)
+  let res
+  if (get(data, '_id')) res = await store.update(data)
+  else res = await store.create({ ...data })
+  if ($checkRes(res, true)) {
+    search({ skip: 0, limit })
+    toClose()
+  }
+}
+// 重置
+const toReset = async () => {
+  searchForm.value = {}
+  await search({ skip, limit })
+}
+const toClose = () => {
+  form.value = {}
+  dialog.value = false
+}
 </script>
 <style scoped lang="scss"></style>