Browse Source

Merge branch 'develop' of gitadmin/tuoheng_pilot_web into release

tags/v1.0.0
zhangtao 2 years ago
parent
commit
faca443200
77 changed files with 4892 additions and 66 deletions
  1. +2
    -2
      .env
  2. +1
    -1
      .env.development
  3. +2
    -2
      .env.localhost
  4. +1
    -1
      .env.test
  5. +2
    -1
      .gitignore
  6. +40
    -15
      index.html
  7. +178
    -0
      package-lock.json
  8. +2
    -0
      package.json
  9. +7
    -0
      public/aliyun-upload-sdk/aliyun-upload-sdk-1.5.2.min.js
  10. +20
    -0
      public/aliyun-upload-sdk/lib/aliyun-oss-sdk-6.13.0.min.js
  11. +1
    -0
      public/aliyun-upload-sdk/lib/es6-promise.min.js
  12. +64
    -0
      src/api/dashboard/index.js
  13. +59
    -0
      src/api/equipment/box.js
  14. +28
    -0
      src/api/equipment/index.js
  15. +59
    -0
      src/api/equipment/mount.js
  16. +59
    -0
      src/api/equipment/uva.js
  17. +47
    -0
      src/api/system/banner.js
  18. +47
    -0
      src/api/system/user.js
  19. +117
    -0
      src/api/task/index.js
  20. +249
    -0
      src/components/UploadVod/index.vue
  21. +3
    -3
      src/router/routes/modules/index.js
  22. +68
    -0
      src/utils/dictionary.js
  23. +24
    -0
      src/utils/echarts/README.md
  24. +216
    -0
      src/utils/echarts/index.js
  25. +3
    -2
      src/utils/http/interceptors.js
  26. +5
    -0
      src/utils/is.js
  27. +11
    -0
      src/utils/map/GMap.js
  28. +49
    -0
      src/utils/map/README.md
  29. +5
    -2
      src/utils/ui/theme.js
  30. BIN
      src/views/dashboard/assets/clients.png
  31. BIN
      src/views/dashboard/assets/fly.png
  32. BIN
      src/views/dashboard/assets/platform.png
  33. BIN
      src/views/dashboard/assets/task.png
  34. BIN
      src/views/dashboard/assets/total.png
  35. +37
    -0
      src/views/dashboard/components/ContComp.vue
  36. +97
    -0
      src/views/dashboard/components/FootComp.vue
  37. +111
    -0
      src/views/dashboard/components/HeadComp.vue
  38. +74
    -0
      src/views/dashboard/components/TabComp.vue
  39. +16
    -10
      src/views/dashboard/index.vue
  40. +134
    -0
      src/views/equipment-library/box-manage/components/BoxModal.vue
  41. +82
    -2
      src/views/equipment-library/box-manage/index.vue
  42. +19
    -0
      src/views/equipment-library/box-manage/tools/form.js
  43. +14
    -0
      src/views/equipment-library/box-manage/tools/search.js
  44. +117
    -0
      src/views/equipment-library/box-manage/tools/table.js
  45. +0
    -17
      src/views/equipment-library/equipment-manage/index.vue
  46. +135
    -0
      src/views/equipment-library/mount-manage/components/MountModal.vue
  47. +97
    -0
      src/views/equipment-library/mount-manage/index.vue
  48. +21
    -0
      src/views/equipment-library/mount-manage/tools/form.js
  49. +14
    -0
      src/views/equipment-library/mount-manage/tools/search.js
  50. +124
    -0
      src/views/equipment-library/mount-manage/tools/table.js
  51. +144
    -0
      src/views/equipment-library/uva-manage/components/UVAModal.vue
  52. +83
    -3
      src/views/equipment-library/uva-manage/index.vue
  53. +26
    -0
      src/views/equipment-library/uva-manage/tools/form.js
  54. +14
    -0
      src/views/equipment-library/uva-manage/tools/search.js
  55. +122
    -0
      src/views/equipment-library/uva-manage/tools/table.js
  56. +158
    -0
      src/views/system-manage/banner-manage/BannerModal.vue
  57. +47
    -1
      src/views/system-manage/banner-manage/index.vue
  58. +19
    -0
      src/views/system-manage/banner-manage/tool/search.js
  59. +133
    -0
      src/views/system-manage/banner-manage/tool/table.js
  60. +283
    -0
      src/views/task-manage/components/DrawComp.vue
  61. +73
    -0
      src/views/task-manage/components/MapComp.vue
  62. +325
    -0
      src/views/task-manage/components/UserModal.vue
  63. +50
    -0
      src/views/task-manage/hook/basic.js
  64. +33
    -0
      src/views/task-manage/hook/equipment.js
  65. +28
    -0
      src/views/task-manage/hook/execution.js
  66. +41
    -0
      src/views/task-manage/hook/fly.js
  67. +11
    -0
      src/views/task-manage/hook/index.js
  68. +95
    -2
      src/views/task-manage/index.vue
  69. +89
    -0
      src/views/task-manage/tools/drawForm.js
  70. +85
    -0
      src/views/task-manage/tools/form.js
  71. +46
    -0
      src/views/task-manage/tools/search.js
  72. +154
    -0
      src/views/task-manage/tools/table.js
  73. +140
    -0
      src/views/user-manage/components/UserModal.vue
  74. +56
    -2
      src/views/user-manage/index.vue
  75. +30
    -0
      src/views/user-manage/tools/form.js
  76. +14
    -0
      src/views/user-manage/tools/search.js
  77. +132
    -0
      src/views/user-manage/tools/table.js

+ 2
- 2
.env View File

@@ -1,8 +1,8 @@
# title
VITE_APP_TITLE = '河湖长眼'
VITE_APP_TITLE = '智飞'

# 端口号
VITE_PORT = 3000
VITE_PORT = 3050


VITE_SERVER = "/pilot/admin"

+ 1
- 1
.env.development View File

@@ -5,7 +5,7 @@ VITE_PUBLIC_PATH = '/'
VITE_APP_USE_MOCK = false

# proxy
VITE_PROXY = [["/api","http://192.168.11.11:7011/api"]]
VITE_PROXY = [["/api","http://192.168.11.11:9055"]]

# base api
VITE_APP_GLOB_BASE_API = '/api'

+ 2
- 2
.env.localhost View File

@@ -2,10 +2,10 @@
VITE_PUBLIC_PATH = '/'

# 是否启用MOCK
VITE_APP_USE_MOCK = true
VITE_APP_USE_MOCK = false

# proxy
VITE_PROXY = [["/api-local","http://127.0.0.1:8002/api"],["/api-mock","http://127.0.0.1:8003"]]
VITE_PROXY = [["/api-local","http://127.0.0.1:8002"],["/api-mock","http://127.0.0.1:8003"]]

# base api
VITE_APP_GLOB_BASE_API = '/api-local'

+ 1
- 1
.env.test View File

@@ -5,7 +5,7 @@ VITE_PUBLIC_PATH = '/'
VITE_APP_USE_MOCK = false

# proxy
VITE_PROXY = [["/api","http://192.168.11.241:7011/api"]]
VITE_PROXY = [["/api","http://192.168.11.11:9055"]]

# base api
VITE_APP_GLOB_BASE_API = '/api'

+ 2
- 1
.gitignore View File

@@ -2,4 +2,5 @@ node_modules
.DS_Store
dist
dist-ssr
*.local
*.local
.idea

+ 40
- 15
index.html View File

@@ -1,17 +1,42 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="Expires" content="0" />
<meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="Cache-control" content="no-cache" />
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="icon" href="/favicon.ico" />
<title><%= title %></title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>

<head>
<meta charset="UTF-8" />
<meta http-equiv="Expires" content="0" />
<meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="Cache-control" content="no-cache" />
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="icon" href="/favicon.ico" />
<!-- IE需要es6-promise,目前支持到IE10 -->
<script src="./public/aliyun-upload-sdk/lib/es6-promise.min.js"></script>
<script src="./public/aliyun-upload-sdk/lib/aliyun-oss-sdk-6.13.0.min.js"></script>
<script src="./public/aliyun-upload-sdk/aliyun-upload-sdk-1.5.2.min.js"></script>
<title>
<%= title %>
</title>
<script type="text/javascript">
window._AMapSecurityConfig = {
// serviceHost:'http://127.0.0.1:8002/_AMapService',
securityJsCode: 'eb839debb422cd65cc598664067ee7d8'
}
</script>
<style type="text/css">
.amap-logo {
display: none;
opacity: 0 !important;
}

.amap-copyright {
opacity: 0;
}
</style>
</head>

<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>

</html>

+ 178
- 0
package-lock.json View File

@@ -8,14 +8,17 @@
"name": "vite_vue3",
"version": "0.0.0",
"dependencies": {
"@amap/amap-jsapi-loader": "^1.0.1",
"@tinymce/tinymce-vue": "^4.0.5",
"@vicons/antd": "^0.10.0",
"@vicons/ionicons5": "^0.10.0",
"ali-oss": "^6.17.1",
"axios": "^0.26.1",
"chart-all": "^1.0.2",
"dayjs": "^1.11.2",
"mockjs": "^1.1.0",
"pinia": "^2.0.13",
"pinia-plugin-persist": "^1.0.0",
"tinymce": "^5.10.2",
"vue": "^3.2.16",
"vue-router": "^4.0.14",
@@ -59,6 +62,11 @@
"node": "8 || 10 || 12 || 14 || 16 || 17"
}
},
"node_modules/@amap/amap-jsapi-loader": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@amap/amap-jsapi-loader/-/amap-jsapi-loader-1.0.1.tgz",
"integrity": "sha512-nPyLKt7Ow/ThHLkSvn2etQlUzqxmTVgK7bIgwdBRTg2HK5668oN7xVxkaiRe3YZEzGzfV2XgH5Jmu2T73ljejw=="
},
"node_modules/@ampproject/remapping": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.1.2.tgz",
@@ -2842,6 +2850,17 @@
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
"node_modules/chart-all": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/chart-all/-/chart-all-1.0.2.tgz",
"integrity": "sha512-gxkvz4rsnsr+nM5pnQt+4m6lKkie8JQYRhsyae+VlWMn3iR2iqhQo98AsYFUgKZAdm0Cvsh6ZvDlZnLx2VwLZg==",
"dependencies": {
"echarts": "^4.9.0",
"echarts-gl": "^1.1.2",
"echarts-liquidfill": "^2.0.6",
"zrender": "^4.3.1"
}
},
"node_modules/chokidar": {
"version": "3.5.3",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
@@ -2896,6 +2915,11 @@
"integrity": "sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A==",
"dev": true
},
"node_modules/claygl": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/claygl/-/claygl-1.3.0.tgz",
"integrity": "sha512-+gGtJjT6SSHD2l2yC3MCubW/sCV40tZuSs5opdtn79vFSGUgp/lH139RNEQ6Jy078/L0aV8odCw8RSrUcMfLaQ=="
},
"node_modules/clean-css": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.0.tgz",
@@ -4096,6 +4120,40 @@
"node": ">=6.0.0"
}
},
"node_modules/echarts": {
"version": "4.9.0",
"resolved": "https://registry.npmjs.org/echarts/-/echarts-4.9.0.tgz",
"integrity": "sha512-+ugizgtJ+KmsJyyDPxaw2Br5FqzuBnyOWwcxPKO6y0gc5caYcfnEUIlNStx02necw8jmKmTafmpHhGo4XDtEIA==",
"dependencies": {
"zrender": "4.3.2"
}
},
"node_modules/echarts-gl": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/echarts-gl/-/echarts-gl-1.1.2.tgz",
"integrity": "sha512-EVGx9RS2eMzaCgAMJSDCeLId4g8oFCFn78Fdh+0xIXASiZw/gPnJqr1vQgnQhmXhiUKixkIhIzfdc//qrct/Hg==",
"dependencies": {
"claygl": "^1.2.1",
"zrender": "^4.0.4"
},
"peerDependencies": {
"echarts": "^4.1.0"
}
},
"node_modules/echarts-liquidfill": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/echarts-liquidfill/-/echarts-liquidfill-2.0.6.tgz",
"integrity": "sha512-p+AH0O9/BtwXMQQyhjJbMZo+GwRAgWG/DCyK5r27PQzpS0UWrgXu57MyEFc0A8Ub3sRuqEu08BuxwHICBkSWSQ==",
"peerDependencies": {
"echarts": "^4.8.0",
"zrender": "^4.3.1"
}
},
"node_modules/echarts/node_modules/zrender": {
"version": "4.3.2",
"resolved": "https://registry.npmjs.org/zrender/-/zrender-4.3.2.tgz",
"integrity": "sha512-bIusJLS8c4DkIcdiK+s13HiQ/zjQQVgpNohtd8d94Y2DnJqgM1yjh/jpDb8DoL6hd7r8Awagw8e3qK/oLaWr3g=="
},
"node_modules/ee-first": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
@@ -8380,6 +8438,49 @@
}
}
},
"node_modules/pinia-plugin-persist": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/pinia-plugin-persist/-/pinia-plugin-persist-1.0.0.tgz",
"integrity": "sha512-M4hBBd8fz/GgNmUPaaUsC29y1M09lqbXrMAHcusVoU8xlQi1TqgkWnnhvMikZwr7Le/hVyMx8KUcumGGrR6GVw==",
"dependencies": {
"vue-demi": "^0.12.1"
},
"peerDependencies": {
"@vue/composition-api": "^1.0.0",
"pinia": "^2.0.0",
"vue": "^2.0.0 || >=3.0.0"
},
"peerDependenciesMeta": {
"@vue/composition-api": {
"optional": true
}
}
},
"node_modules/pinia-plugin-persist/node_modules/vue-demi": {
"version": "0.12.5",
"resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.12.5.tgz",
"integrity": "sha512-BREuTgTYlUr0zw0EZn3hnhC3I6gPWv+Kwh4MCih6QcAeaTlaIX0DwOVN0wHej7hSvDPecz4jygy/idsgKfW58Q==",
"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/pinia/node_modules/vue-demi": {
"version": "0.13.1",
"resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.13.1.tgz",
@@ -12076,6 +12177,11 @@
"resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz",
"integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=",
"dev": true
},
"node_modules/zrender": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/zrender/-/zrender-4.3.1.tgz",
"integrity": "sha512-CeH2TpJeCdG0TAGYoPSAcFX2ogdug1K7LIn9UO/q9HWqQ54gWhrMAlDP9AwWYMUDhrPe4VeazQ4DW3msD96nUQ=="
}
},
"dependencies": {
@@ -12090,6 +12196,11 @@
"js-message": "1.0.7"
}
},
"@amap/amap-jsapi-loader": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@amap/amap-jsapi-loader/-/amap-jsapi-loader-1.0.1.tgz",
"integrity": "sha512-nPyLKt7Ow/ThHLkSvn2etQlUzqxmTVgK7bIgwdBRTg2HK5668oN7xVxkaiRe3YZEzGzfV2XgH5Jmu2T73ljejw=="
},
"@ampproject/remapping": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.1.2.tgz",
@@ -14321,6 +14432,17 @@
"integrity": "sha512-Fo07WOYGqMfCWHOzSXOt2CxDbC6skS/jO9ynEcmpANMoPrD+W1r1K6Vx7iNm+AQmETU1Xr2t+n8nzkV9t6xh3w==",
"dev": true
},
"chart-all": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/chart-all/-/chart-all-1.0.2.tgz",
"integrity": "sha512-gxkvz4rsnsr+nM5pnQt+4m6lKkie8JQYRhsyae+VlWMn3iR2iqhQo98AsYFUgKZAdm0Cvsh6ZvDlZnLx2VwLZg==",
"requires": {
"echarts": "^4.9.0",
"echarts-gl": "^1.1.2",
"echarts-liquidfill": "^2.0.6",
"zrender": "^4.3.1"
}
},
"chokidar": {
"version": "3.5.3",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
@@ -14360,6 +14482,11 @@
"integrity": "sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A==",
"dev": true
},
"claygl": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/claygl/-/claygl-1.3.0.tgz",
"integrity": "sha512-+gGtJjT6SSHD2l2yC3MCubW/sCV40tZuSs5opdtn79vFSGUgp/lH139RNEQ6Jy078/L0aV8odCw8RSrUcMfLaQ=="
},
"clean-css": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.0.tgz",
@@ -15270,6 +15397,36 @@
"integrity": "sha512-wK2sCs4feiiJeFXn3zvY0p41mdU5VUgbgs1rNsc/y5ngFUijdWd+iIN8eoyuZHKB8xN6BL4PdWmzqFmxNg6V2w==",
"dev": true
},
"echarts": {
"version": "4.9.0",
"resolved": "https://registry.npmjs.org/echarts/-/echarts-4.9.0.tgz",
"integrity": "sha512-+ugizgtJ+KmsJyyDPxaw2Br5FqzuBnyOWwcxPKO6y0gc5caYcfnEUIlNStx02necw8jmKmTafmpHhGo4XDtEIA==",
"requires": {
"zrender": "4.3.2"
},
"dependencies": {
"zrender": {
"version": "4.3.2",
"resolved": "https://registry.npmjs.org/zrender/-/zrender-4.3.2.tgz",
"integrity": "sha512-bIusJLS8c4DkIcdiK+s13HiQ/zjQQVgpNohtd8d94Y2DnJqgM1yjh/jpDb8DoL6hd7r8Awagw8e3qK/oLaWr3g=="
}
}
},
"echarts-gl": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/echarts-gl/-/echarts-gl-1.1.2.tgz",
"integrity": "sha512-EVGx9RS2eMzaCgAMJSDCeLId4g8oFCFn78Fdh+0xIXASiZw/gPnJqr1vQgnQhmXhiUKixkIhIzfdc//qrct/Hg==",
"requires": {
"claygl": "^1.2.1",
"zrender": "^4.0.4"
}
},
"echarts-liquidfill": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/echarts-liquidfill/-/echarts-liquidfill-2.0.6.tgz",
"integrity": "sha512-p+AH0O9/BtwXMQQyhjJbMZo+GwRAgWG/DCyK5r27PQzpS0UWrgXu57MyEFc0A8Ub3sRuqEu08BuxwHICBkSWSQ==",
"requires": {}
},
"ee-first": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
@@ -18456,6 +18613,22 @@
}
}
},
"pinia-plugin-persist": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/pinia-plugin-persist/-/pinia-plugin-persist-1.0.0.tgz",
"integrity": "sha512-M4hBBd8fz/GgNmUPaaUsC29y1M09lqbXrMAHcusVoU8xlQi1TqgkWnnhvMikZwr7Le/hVyMx8KUcumGGrR6GVw==",
"requires": {
"vue-demi": "^0.12.1"
},
"dependencies": {
"vue-demi": {
"version": "0.12.5",
"resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.12.5.tgz",
"integrity": "sha512-BREuTgTYlUr0zw0EZn3hnhC3I6gPWv+Kwh4MCih6QcAeaTlaIX0DwOVN0wHej7hSvDPecz4jygy/idsgKfW58Q==",
"requires": {}
}
}
},
"platform": {
"version": "1.3.6",
"resolved": "https://registry.npmjs.org/platform/-/platform-1.3.6.tgz",
@@ -21184,6 +21357,11 @@
"dev": true
}
}
},
"zrender": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/zrender/-/zrender-4.3.1.tgz",
"integrity": "sha512-CeH2TpJeCdG0TAGYoPSAcFX2ogdug1K7LIn9UO/q9HWqQ54gWhrMAlDP9AwWYMUDhrPe4VeazQ4DW3msD96nUQ=="
}
}
}

+ 2
- 0
package.json View File

@@ -9,11 +9,13 @@
"serve": "vite preview"
},
"dependencies": {
"@amap/amap-jsapi-loader": "^1.0.1",
"@tinymce/tinymce-vue": "^4.0.5",
"@vicons/antd": "^0.10.0",
"@vicons/ionicons5": "^0.10.0",
"ali-oss": "^6.17.1",
"axios": "^0.26.1",
"chart-all": "^1.0.2",
"dayjs": "^1.11.2",
"mockjs": "^1.1.0",
"pinia": "^2.0.13",

+ 7
- 0
public/aliyun-upload-sdk/aliyun-upload-sdk-1.5.2.min.js
File diff suppressed because it is too large
View File


+ 20
- 0
public/aliyun-upload-sdk/lib/aliyun-oss-sdk-6.13.0.min.js
File diff suppressed because it is too large
View File


+ 1
- 0
public/aliyun-upload-sdk/lib/es6-promise.min.js
File diff suppressed because it is too large
View File


+ 64
- 0
src/api/dashboard/index.js View File

@@ -0,0 +1,64 @@
import { defAxios as request } from '@/utils/http'

/**
* 任务总数数量统计
* @returns
*/
export const statisticsTask = () => request({
url: '/index/statisticsTask',
method: 'GET'
})

/**
* 任务总数 趋势
* @returns
*/
export const pilotTaskDate = () => request({
url: '/index/pilotTaskDate',
method: 'GET'
})

/**
* 飞手飞行时长
* @returns
*/
export const pilotTaskTimeCount = () => request({
url: '/index/pilotTaskTimeCount',
method: 'GET'
})

/**
* 任务状态
* @returns
*/
export const taskStatusStatistics = () => request({
url: '/index/taskStatusStatistics',
method: 'GET'
})

/**
* 飞手飞行任务数量
* @returns
*/
export const pilotTaskStatistics = () => request({
url: '/index/pilotTaskStatistics',
method: 'GET'
})

/**
* 平台任务数量
* @returns
*/
export const platformTasks = () => request({
url: '/index/platformTasks',
method: 'GET'
})

/**
* 最新任务列表
* @returns
*/
export const newestTask = () => request({
url: '/index/newestTask',
method: 'GET'
})

+ 59
- 0
src/api/equipment/box.js View File

@@ -0,0 +1,59 @@
import { defAxios as request } from '@/utils/http'

/**
* 获取云盒列表(分页)
* @param {Object}
* @returns
*/
export function boxPage(params) {
return request({
url: '/cloudBox/index',
method: 'get',
params
})
}

/**
* 获取云盒列表
* params
* @returns
*/
export function boxAll() {
return request({
url: '/cloudBox/getList',
method: 'get'
})
}

/**
* 创建盒子
* @returns
*/
export function boxCreate(data) {
return request({
url: '/cloudBox/add',
method: 'post',
data
})
}
/**
* 编辑盒子
* @returns
*/
export function boxUpdate(data) {
return request({
url: '/cloudBox/edit',
method: 'put',
data
})
}
/**
* 删除盒子
* @returns
*/
export function boxDelete(ids) {
return request({
url: `/cloudBox/delete/${ids}`,
method: 'delete'
})
}

+ 28
- 0
src/api/equipment/index.js View File

@@ -0,0 +1,28 @@
import { defAxios as request } from '@/utils/http'

/**
* 无人机列表
* @returns
*/
export const getEquipment = () => request({
url: '/equipment/getList',
method: 'GET'
})

/**
* 挂载设备列表
* @returns
*/
export const getEquipmentMount = () => request({
url: '/equipmentMount/getList',
method: 'GET'
})

/**
* 挂载盒子
* @returns
*/
export const getCloud = () => request({
url: '/cloudBox/getList',
method: 'GET'
})

+ 59
- 0
src/api/equipment/mount.js View File

@@ -0,0 +1,59 @@
import { defAxios as request } from '@/utils/http'

/**
* 获取挂载设备列表(分页)
* @param {Object}
* @returns
*/
export function mountPage(params) {
return request({
url: '/equipmentMount/index',
method: 'get',
params
})
}

/**
* 获取挂载设备
* params
* @returns
*/
export function mountAll() {
return request({
url: '/equipmentMount/getList',
method: 'get'
})
}

/**
* 创建挂载设备
* @returns
*/
export function mountCreate(data) {
return request({
url: '/equipmentMount/add',
method: 'post',
data
})
}
/**
* 编辑挂载设备
* @returns
*/
export function mountUpdate(data) {
return request({
url: '/equipmentMount/edit',
method: 'put',
data
})
}
/**
* 删除挂载设备
* @returns
*/
export function mountDelete(ids) {
return request({
url: `/equipmentMount/delete/${ids}`,
method: 'delete'
})
}

+ 59
- 0
src/api/equipment/uva.js View File

@@ -0,0 +1,59 @@
import { defAxios as request } from '@/utils/http'

/**
* 获取挂载设备列表(分页)
* @param {Object}
* @returns
*/
export function uvaPage(params) {
return request({
url: '/equipment/index',
method: 'get',
params
})
}

/**
* 获取挂载设备
* params
* @returns
*/
export function uvaAll() {
return request({
url: '/equipment/getList',
method: 'get'
})
}

/**
* 创建挂载设备
* @returns
*/
export function uvaCreate(data) {
return request({
url: '/equipment/add',
method: 'post',
data
})
}
/**
* 编辑挂载设备
* @returns
*/
export function uvaUpdate(data) {
return request({
url: '/equipment/edit',
method: 'put',
data
})
}
/**
* 删除挂载设备
* @returns
*/
export function uvaDelete(ids) {
return request({
url: `/equipment/delete/${ids}`,
method: 'delete'
})
}

+ 47
- 0
src/api/system/banner.js View File

@@ -0,0 +1,47 @@
import { defAxios as request } from '@/utils/http'

/**
* 获取云盒列表(分页)
* @param {Object}
* @returns
*/
export function bannerPage(params) {
return request({
url: '/pilotAd/index',
method: 'get',
params
})
}

/**
* 创建盒子
* @returns
*/
export function bannerCreate(data) {
return request({
url: '/pilotAd/add',
method: 'post',
data
})
}
/**
* 编辑盒子
* @returns
*/
export function bannerUpdate(data) {
return request({
url: '/pilotAd/edit',
method: 'put',
data
})
}
/**
* 删除盒子
* @returns
*/
export function bannerDelete(ids) {
return request({
url: `/pilotAd/delete/${ids}`,
method: 'delete'
})
}

+ 47
- 0
src/api/system/user.js View File

@@ -0,0 +1,47 @@
import { defAxios as request } from '@/utils/http'

/**
* 获取用户列表(分页)
* @param {Object}
* @returns
*/
export function userPage(params) {
return request({
url: '/user/index',
method: 'get',
params
})
}

/**
* 创建用户
* @returns
*/
export function userCreate(data) {
return request({
url: '/user/add',
method: 'post',
data
})
}
/**
* 编辑用户
* @returns
*/
export function userUpdate(data) {
return request({
url: '/user/edit',
method: 'put',
data
})
}
/**
* 删除用户
* @returns
*/
export function userDelete(ids) {
return request({
url: `/delete/${ids}`,
method: 'delete'
})
}

+ 117
- 0
src/api/task/index.js View File

@@ -0,0 +1,117 @@
import { defAxios as request } from '@/utils/http'

/**
* 任务查询
* @returns
*/
export const getTaskList = params => request({
url: '/task/getTaskList',
method: 'GET',
params
})

/**
* 任务详情
* @param {*} params id
* @returns
*/
export const getTaskInfo = params => request({
url: `/task/getInfo/${params}`,
method: 'GET'
})

/**
* 获取飞手列表
* @returns
*/
export const getTaskPilot = () => request({
url: '/task/getPilot',
method: 'GET'
})

/**
* 添加任务
* @param {*} params
* @returns
*/
export const taskAdd = data => request({
url: '/task/add',
method: 'POST',
data
})

/**
* 编辑任务
* @param {*} params
* @returns
*/
export const taskEdit = data => request({
url: '/task/edit',
method: 'PUT',
data
})

/**
* 删除任务
* @param {*} params
* @returns
*/
export const taskDel = params => request({
url: `/task/delete/${params}`,
method: 'DELETE'
})

/**
* 分配飞手
* @param {*} params
* @returns
*/
export const distributionPilot = data => request({
url: '/task/distributionPilot',
method: 'PUT',
data
})

/**
* 飞手接单
* @param {*} params
* @returns
*/
export const pilotOrder = data => request({
url: '/task/pilotOrder',
method: 'PUT',
data
})

/**
* 开始飞行
* @param {*} params
* @returns
*/
export const pilotStart = data => request({
url: '/task/pilotStart',
method: 'PUT',
data
})

/**
* 结束飞行
* @param {*} params
* @returns
*/
export const pilotEnd = data => request({
url: '/task/pilotEnd',
method: 'PUT',
data
})

/**
* 上传飞行文件
* @param {*} params
* @returns
*/
export const uploadFlightUrl = data => request({
url: '/task/uploadFlightUrl',
method: 'PUT',
data
})

+ 249
- 0
src/components/UploadVod/index.vue View File

@@ -0,0 +1,249 @@
<template>
<n-upload
action="#"
:max="data.limit"
:file-list="data.fileList"
:default-upload="false"
class="upload"
@before-upload="beforeUpload"
@change="handleChange"
>
<n-button>
<n-icon>
<FileOutlined />
</n-icon>
选择文件
</n-button>
</n-upload>

<n-button
v-if="!autoUpload"
type="primary"
:disabled="data.fileList.length === 0"
style="margin-top: 16px"
class="btn"

@click="handleUploadStart"
>
<n-icon>
<UploadOutlined />
</n-icon>
{{ text }}
</n-button>

</template>

<script setup name="UploadVod">
import { FileOutlined, UploadOutlined } from '@vicons/antd'
import { getAuth, refreshAuth } from '@/api/common/upload.js'
import { reactive } from 'vue'

const props = defineProps({
limit: {
type: Number,
default: 10
},
autoUpload: {
type: Boolean,
default: false
},
text: {
type: String,
default: '开始上传'
}
})
const emit = defineEmits(['uploadStatus'])

const data = reactive({
limit: props.limit,
timeout: '', // 请求超时时间
partSize: '', // 分片大小
parallel: '', // 分片数
retryCount: '', // 失败重试次数
retryDuration: '', // 失败重试间隔
region: 'cn-shanghai', // 配置项
userId: '224202346013657350', // 阿里云帐号ID

uploader: null, // 上传实例
fileList: [], // 上传的文件列表
uploadFile: [], // 选中的文件列表
readyFile: {}, // 准备上传的文件列表
uploaderObject: {} // 上传的实例化对象列表
})

/**
* @description: 校验文件是否合规
* @param {*} file
* @return {*}
*/
const beforeUpload = ({ file }) => {
// if (!file.type.match('video.*')) {
// $message.error('请选择正确的视频')
// return false
// } else {
const hasSelect = data.fileList.some((item) => item.name === file.name)
if (!hasSelect) {
data.uploadFile.push(file.file)
} else {
$message.error('您已选择过该文件')
return false
}
// }
}

/**
* @description: 文件发生变化时,过滤不符合条件与重复的文件
* @param {Array} fileList
* @return {*}
*/
const handleChange = ({ fileList }) => {
data.fileList = fileList
/* 若未实例化过,则实例化上传对象 */
if (!data.uploader) {
data.uploader = createUploader()
}
const status = fileList.length ? 'ready' : 'no-file'
emit('uploadStatus', { status, list: data.readyFile })
handleUploadReady() // 准备上传文件
}

/**
* @description: 创建上传的实例化对象
* @param {*}
* @return {*}
*/
const createUploader = () => {
const uploader = new AliyunUpload.Vod({
timeout: data.timeout || 60000,
partSize: data.partSize || 1048576,
parallel: data.parallel || 5,
retryCount: data.retryCount || 3,
retryDuration: data.retryDuration || 2,
region: data.region,
userId: data.userId,
/* 添加文件成功 */
addFileSuccess: function(uploadInfo) {
// console.log('addFileSuccess: ' + uploadInfo.file.name)
},
// 开始上传
onUploadstarted: function(uploadInfo) {
console.log('uploader', uploader)
if (uploadInfo.videoId) {
// 如果uploadInfo.videoId存在,调用刷新视频上传凭证接口
refreshAuth(uploadInfo.videoId).then(res => {
uploader.setUploadAuthAndAddress(uploadInfo, res.data.uploadAuth, res.data.uploadAddress, res.data.videoId)
})
} else {
const params = {
title: uploadInfo.file.name,
fileName: uploadInfo.file.name
}
getAuth(params).then(res => {
uploader.setUploadAuthAndAddress(uploadInfo, res.data.uploadAuth, res.data.uploadAddress, res.data.videoId)
})
}
},

// 文件上传成功
onUploadSucceed: function(uploadInfo) {
const fileName = uploadInfo.file.name
data.fileList.forEach((item) => {
console.log(item)
if (item.name === fileName) {
data.readyFile[item.uid].url = uploadInfo.object
}
})
},
// 文件上传失败
onUploadFailed: function(uploadInfo, code, message) {
console.log('onUploadFailed: file:' + uploadInfo.file.name + ',code:' + code + ', message:' + message)
},

// 取消文件上传
onUploadCanceled: function(uploadInfo, code, message) {
// console.log('Canceled file: ' + uploadInfo.file.name + ', code: ' + code + ', message:' + message)
},

// 文件上传进度,单位:字节, 可以在这个函数中拿到上传进度并显示在页面上
onUploadProgress: function(uploadInfo, totalSize, progress) {
// console.log("onUploadProgress:file:" + uploadInfo.file.name + ", fileSize:" + totalSize + ", percent:" + Math.ceil(progress * 100) + "%")
const progressPercent = Math.ceil(progress * 100)
const fileName = uploadInfo.file.name
data.fileList.forEach((item) => {
if (item.name === fileName) {
item.status = progressPercent === 100 ? 'success' : 'uploading'
item.percent = progressPercent
}
})
},

// 上传凭证超时
onUploadTokenExpired: function(uploadInfo) {
console.log('onUploadTokenExpired', uploadInfo)
refreshAuth(uploadInfo.videoId).then(res => {
uploader.resumeUploadWithAuth(uploadInfo, res.data.uploadAuth, res.data.uploadAddress, res.data.videoId)
})
},

// 全部文件上传结束
onUploadEnd: function(uploadInfo) {
// console.log('onUploadEnd: uploaded all the files', uploadInfo)
emit('uploadStatus', { status: 'success', list: data.readyFile })
}
})
return uploader
}

/**
* @description: 新建文件到实例对象中
* @param {*}
* @return {*}
*/
const handleUploadReady = () => {
data.uploadFile.forEach((item, index) => {
/* 是否已经新建过该文件 */
if (!Object.keys(data.readyFile).includes(item.uid)) {
const obj = {
name: item.name,
url: ''
}
data.readyFile[item.uid] = obj
data.uploader.addFile(item, null, null, null, '{"Vod":{}}')
}
})
if (props.autoUpload) { // 判断是否自动上传
handleUploadStart()
}
// handleUploadStart()
}

/**
* @description: 开始上传文件
* @param {*}
* @return {*}
*/
const handleUploadStart = () => {
if (data.uploader !== null) {
data.uploader.startUpload()
emit('uploadStatus', { status: 'uploading', list: data.readyFile })
}
}
</script>

<style lang="scss" scoped>
.upload {
list-style: none;
padding: 10px 20px;
}

.upload .upload-item__name {
margin-bottom: 5px;
}

.btn {
width: 120px;
margin-left: calc(50% - 60px);
margin-top: 10px;
margin-bottom: 30px;
}
</style>

+ 3
- 3
src/router/routes/modules/index.js View File

@@ -70,12 +70,12 @@ export default [
}
},
{
path: 'equipment-manage',
component: () => import('@/views/equipment-library/equipment-manage/index.vue'),
path: 'mount-manage',
component: () => import('@/views/equipment-library/mount-manage/index.vue'),
name: 'EquipmentMount',
title: '挂载设备',
meta: {
title: '盒子管理',
title: '挂载设备',
role: ['admin']
}
},

+ 68
- 0
src/utils/dictionary.js View File

@@ -28,3 +28,71 @@ export const MENU_STATUS = [
{ label: '在用', value: 1 },
{ label: '停用', value: 2 }
]

export const ROLE_TYPE = [
{ label: '管理员', value: 1 },
{ label: '飞手', value: 2 }
]

export const EQUIPMENT_TYPE = [
{ label: '高清相机', value: 1 },
{ label: '多光谱相机', value: 2 },
{ label: '空中喊话', value: 3 },
{ label: '大功率照明', value: 4 },
{ label: '双光热成像相机', value: 5 },
{ label: '抛投钩', value: 6 },
{ label: '抛投绳', value: 7 },
{ label: '无人机喷火枪', value: 8 },
{ label: '灭火弹抛投器', value: 9 },
{ label: '气体采集盒', value: 10 },
{ label: '无人机取水器', value: 11 },
{ label: '其他', value: 12 }
]

export const POWER_TYPE = [
{ label: '电动', value: 1 },
{ label: '油动', value: 2 },
{ label: '混合', value: 3 }
]

/**
* 状态
*/
export const TASK_STATUS = [
{
label: '任务待分配',
value: 5
},
{
label: '任务已分配',
value: 10
},
{
label: '飞手已接单',
value: 15
},
{
label: '任务飞行中',
value: 20
},
{
label: '任务已完成',
value: 25
}
]

// 拍摄方式
export const PHOTOGRAPHY_WAY = [
{
label: '普通巡检',
value: '1'
},
{
label: '正射影像',
value: '2'
},
{
label: '倾斜摄影',
value: '3'
}
]

+ 24
- 0
src/utils/echarts/README.md View File

@@ -0,0 +1,24 @@

## use

```js
<template>
<div id="testLine" style="width: 50%; height: 300px;"></div>
</template>

<script>
import { onMounted } from "vue";
import myChart from "@/utils/echarts";
export default {
name: "about",
setup() {
onMounted(() => {
myChart.line1('testLine', (e) => {
console.log('点我了', e)
});
})
}
}
</script>

```

+ 216
- 0
src/utils/echarts/index.js View File

@@ -0,0 +1,216 @@
import chartClass from 'chart-all'
import * as echarts from 'echarts'

export default {
// 首页 - 任务数据
line1: (id, xAxis, YAxis) => {
const { chart } = new chartClass(id)
chart.setOption({
grid: {
top: '15%',
left: '5%',
right: '7%',
bottom: '5%',
containLabel: true
},
tooltip: {
trigger: 'axis',
axisPointer: {
// 坐标轴指示器,坐标轴触发有效
type: 'shadow' // 默认为直线,可选为:'line' | 'shadow'
}
},
xAxis: [{
type: 'category',
// boundaryGap: true, //坐标轴两边留白
data: xAxis,
animation: true,
boundaryGap: false, // 贴边
// offset: 5,
axisLine: {
// x轴
show: true,
lineStyle: {
color: 'rgba(137, 137, 137, 0.6)'
}
},
axisTick: {
// x轴刻度线
show: false
},
axisLabel: {
// x轴刻度文案
show: true
},
splitLine: {
// 网格
show: false
}
}],
yAxis: [{
type: 'value',
animation: true,
min: 0,
minInterval: 1,
axisLine: {
// y轴
show: false
},
axisTick: {
// y轴刻度线
show: false
},
axisLabel: {
// y轴刻度文案
show: true
},
splitLine: {
// 网格
show: true,
lineStyle: {
type: 'dashed',
color: 'rgba(137, 137, 137, 0.15)'
}
}
}],
series: [{
type: 'line',
emphasis: {
focus: 'series'
},
name: '',
smooth: true, // 平滑曲线
showSymbol: true, // 节点
symbolSize: 6,
animationDuration: 2500,
lineStyle: {
width: 3
},
itemStyle: {
color: 'rgba(32,204,255, 0.8)'
},
areaStyle: {
normal: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{
offset: 0,
color: 'rgba(32,204,255, 0.8)'
},
{
offset: 1,
color: 'rgba(60,212,191, 0.1)'
}
])
}
},
data: YAxis
}]
})
},

// 首页 - 统计
line2: (id, xAxis, yAxis) => {
const { chart } = new chartClass(id)
chart.setOption({
grid: {
top: '4%',
left: '5%',
right: '8%',
bottom: '4%',
containLabel: true
},
legend: {
show: false
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow' // 默认为直线,可选为:'line' | 'shadow'
}
},
yAxis: [{
type: 'category',
data: xAxis,
animation: true,
offset: 5,
axisLine: {
show: false
},
axisTick: {
// x轴刻度线
show: false
},
axisLabel: {
// x轴刻度文案
show: true,
showMaxLabel: true,
rotate: 0, // 倾斜
interval: 0,
formatter: function(params) {
var newParamsName = '' // 最终拼接成的字符串
var paramsNameNumber = params.length // 实际标签的个数
var provideNumber = 7 // 每行能显示的字的个数
var rowNumber = Math.ceil(paramsNameNumber / provideNumber) // 换行的话,需要显示几行,向上取整
/**
* 判断标签的个数是否大于规定的个数, 如果大于,则进行换行处理 如果不大于,即等于或小于,就返回原标签
*/
// 条件等同于rowNumber>1
if (paramsNameNumber > provideNumber) {
/** 循环每一行,p表示行 */
for (var p = 0; p < rowNumber; p++) {
var tempStr = '' // 表示每一次截取的字符串
var start = p * provideNumber // 开始截取的位置
var end = start + provideNumber // 结束截取的位置
// 此处特殊处理最后一行的索引值
if (p == rowNumber - 1) {
// 最后一次不换行
tempStr = params.substring(start, paramsNameNumber)
} else {
// 每一次拼接字符串并换行
tempStr = params.substring(start, end) + '\n'
}
newParamsName += tempStr // 最终拼成的字符串
}
} else {
// 将旧标签的值赋给新标签
newParamsName = params
}
// 将最终的字符串返回
return newParamsName
}
}
}],
xAxis: [{
type: 'value',
name: '',
animation: true,
axisLine: {
// y轴
show: false
},
axisTick: {
// y轴刻度线
show: false
},
splitLine: {
lineStyle: {
color: 'rgba(102, 102, 102, 0.1)'
}
}
}],
series: [{
barWidth: 22,
type: 'bar',
emphasis: {
focus: 'series'
},
itemStyle: {
color: 'rgba(64, 134, 255, 1)',
barBorderRadius: [10]
},
data: yAxis
}]
})
}

}

+ 3
- 2
src/utils/http/interceptors.js View File

@@ -13,7 +13,8 @@ export function setupInterceptor(service) {
if (isWithoutToken(config)) {
return config
}
const token = getToken()
// const token = getToken()
const token = 'token'
if (token) {
config.headers.Authorization = token
return config
@@ -63,4 +64,4 @@ export function setupInterceptor(service) {
return Promise.reject(error)
}
)
}
}

+ 5
- 0
src/utils/is.js View File

@@ -105,6 +105,11 @@ export function isExternal(path) {
return /^(https?:|mailto:|tel:)/.test(path)
}

export function isPhone(phone) {
const reg = /^(0|86|17951)?(13[0-9]|15[012356789]|166|17[3678]|18[0-9]|14[57])[0-9]{8}$/
return reg.test(phone)
}

export const isServer = typeof window === 'undefined'

export const isClient = !isServer

+ 11
- 0
src/utils/map/GMap.js View File

@@ -0,0 +1,11 @@
import AMapLoader from '@amap/amap-jsapi-loader'

export default AMapLoader.load({
'key': '709c024e3faa4a92473b9d847ca87702',
'version': '2.0',
'plugins': ['AMap.ToolBar', 'AMap.AutoComplete', 'AMap.PlaceSearch', 'AMap.Geocoder'],
AMapUI: {
version: '1.1',
plugins: ['overlay/SimpleMarker']
}
})

+ 49
- 0
src/utils/map/README.md View File

@@ -0,0 +1,49 @@

```js
import GMap from '@/utils/map/GMap'

GMap.then((AMap) => {
let map = new AMap.Map('mapComp', {
zoom: 13,
center: [118.773319, 31.828123],
});

// 根据经纬度 反查详细地址
const geocoder = new AMap.Geocoder()
geocoder.getAddress([116.396574, 39.992706], function(status, result) {
if (status === 'complete' && result.info === 'OK') {
console.log('result', result);
}
})

// 添加缩放工具
const toolbar = new AMap.ToolBar();
map.addControl(toolbar);

// 搜索
AMapUI.loadUI(['misc/PoiPicker'], function (PoiPicker) {

const poiPicker = new PoiPicker({
input: 'pickerInput'
});

poiPicker.on('poiPicked', ({ item }) => {

let inp = document.getElementById('pickerInput');
inp.value = item.name;

const marker = new AMap.Marker({
title: item.address
});

marker.setMap(map);
marker.setPosition(item.location);
map.setCenter(marker.getPosition());

})
});

}).catch(e => console.log(e))


```

+ 5
- 2
src/utils/ui/theme.js View File

@@ -1,7 +1,10 @@
const common = {
primaryColor: '#36ad6a',
primaryColor: 'rgba(24, 144, 255, 1)',
textColor: 'rgba(51, 51, 51, 1)',
whiteColor: 'rgba(255, 255, 255, 1)'
whiteColor: 'rgba(255, 255, 255, 1)',
errorColor: '#ff3333',
primaryColorHover: 'rgba(24, 144, 255, 0.8)',
primaryColorPressed: 'rgba(24, 144, 255, 1)'
}

const themeOverrides = {

BIN
src/views/dashboard/assets/clients.png View File

Before After
Width: 40  |  Height: 40  |  Size: 1.1KB

BIN
src/views/dashboard/assets/fly.png View File

Before After
Width: 40  |  Height: 40  |  Size: 1.3KB

BIN
src/views/dashboard/assets/platform.png View File

Before After
Width: 40  |  Height: 40  |  Size: 1018B

BIN
src/views/dashboard/assets/task.png View File

Before After
Width: 40  |  Height: 40  |  Size: 1.2KB

BIN
src/views/dashboard/assets/total.png View File

Before After
Width: 40  |  Height: 40  |  Size: 1.0KB

+ 37
- 0
src/views/dashboard/components/ContComp.vue View File

@@ -0,0 +1,37 @@
<template>
<div class="ContComp">
<div class="title">任务数据</div>
<div id="chart01" />
</div>
</template>

<script setup name="ContComp">
import { onMounted } from 'vue'
import myChart from '@/utils/echarts'
import { pilotTaskDate } from '@/api/dashboard/index'

onMounted(() => {
pilotTaskDate().then(({ data }) => {
const xAxis = data.map(it => it.checkTime)
const YAxis = data.map(it => it.pilotTaskSum)
myChart.line1('chart01', xAxis, YAxis)
})
})

</script>
<style scoped lang='scss'>
.ContComp {
margin-top: 20px;

.title {
font-size: 16px;
font-weight: 600;
color: rgba(0, 0, 0, 1);
}

#chart01 {
width: 100%;
height: 300px;
}
}
</style>

+ 97
- 0
src/views/dashboard/components/FootComp.vue View File

@@ -0,0 +1,97 @@
<template>
<div class="FootComp">
<n-grid x-gap="12" :cols="3">
<n-gi>
<div class="test">
<div class="comp">
<div class="title">飞手飞行时长统计</div>
<div id="chart02" />
</div>
<div class="comp" style="margin-top: 20px;">
<div class="title">任务状态统计</div>
<div id="chart03" />
</div>
</div>
</n-gi>
<n-gi>
<div class="test">
<div class="comp">
<div class="title">飞手飞行任务数量统计</div>
<div id="chart04" />
</div>
<div class="comp" style="margin-top: 20px;">
<div class="title">平台任务数量统计</div>
<div id="chart05" />
</div>
</div>
</n-gi>
<n-gi>
<tab-comp class="comp" />
</n-gi>
</n-grid>
</div>
</template>

<script setup name="FootComp">
import TabComp from './TabComp.vue'
import { onMounted } from 'vue'
import myChart from '@/utils/echarts'
import { pilotTaskTimeCount, taskStatusStatistics, pilotTaskStatistics, platformTasks } from '@/api/dashboard/index'

onMounted(() => {
// 飞手飞行时长
pilotTaskTimeCount().then(({ data }) => {
const xAxis = data.map(it => it.flightHandName)
const YAxis = data.map(it => it.flightTime)
myChart.line2('chart02', xAxis, YAxis)
})
// 任务状态统计
taskStatusStatistics().then(({ data }) => {
const xAxis = data.map(it => it.statusType)
const YAxis = data.map(it => it.pilotTaskSum)
myChart.line2('chart03', xAxis, YAxis)
})
// 飞手飞行任务数量
pilotTaskStatistics().then(({ data }) => {
const xAxis = data.map(it => it.flightHandName)
const YAxis = data.map(it => it.pilotTaskSum)
myChart.line2('chart04', xAxis, YAxis)
})
// 平台任务数量
platformTasks().then(({ data }) => {
const xAxis = data.map(it => it.platformName)
const YAxis = data.map(it => it.pilotTaskSum)
myChart.line2('chart05', xAxis, YAxis)
})
})

</script>
<style scoped lang='scss'>
.FootComp {
margin-top: 20px;

.comp {
padding: 10px;
background-color: #fff;
border-radius: 4px;
box-shadow: 0px 2px 6px 0px rgba(0, 0, 0, 0.25);
}

.title {
font-size: 16px;
font-weight: bold;

overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}

#chart02,
#chart03,
#chart04,
#chart05 {
width: 100%;
height: 195px;
}
}
</style>

+ 111
- 0
src/views/dashboard/components/HeadComp.vue View File

@@ -0,0 +1,111 @@
<template>
<div class="HeadComp">

<div v-for="(it, i) in headList" :key="'head' + it.name" class="box" :class="{border: i % 2 != 0}">
<template v-if="i % 2 == 0">
<img :src="it.img" class="img">
<div class="item">
<div class="name">{{ it.name }}</div>
<div class="num">{{ it.value }} <span v-if="i !== headList.length -1" class="span">个</span></div>
</div>
</template>
</div>

</div>
</template>

<script setup name="HeadComp">
import { reactive, ref } from 'vue'
import total from '../assets/total.png'
import task from '../assets/task.png'
import platform from '../assets/platform.png'
import clients from '../assets/clients.png'
import fly from '../assets/fly.png'
import { statisticsTask } from '@/api/dashboard/index'

const headList = reactive([
{
name: '总条数',
value: null,
img: total
},
{},
{
name: '待执行任务',
value: null,
img: task
},
{},
{
name: '接入平台数量',
value: null,
img: platform
},
{},
{
name: '服务客户数量',
value: null,
img: clients
},
{},
{
name: '累计飞行时长',
value: null,
img: fly
}
])

statisticsTask().then(({ data }) => {
headList[0].value = data.allTaskNumber
headList[2].value = data.performTask
headList[4].value = data.platformNumber
headList[6].value = data.customerNumber
headList[8].value = data.flightTimeCount
})

</script>
<style scoped lang='scss'>
.HeadComp {
display: flex;
justify-content: space-around;
flex-wrap: wrap;
font-size: 14px;

height: 60px;

.box {
display: flex;
align-items: center;

.img {
width: 24px;
height: 24px;
}

.item {
margin-left: 20px;

.name {
margin-bottom: 2px;
color: rgba(0, 0, 0, 0.75);
}

.num {
text-align: end;
margin-top: 2px;
font-weight: bold;
margin-right: 10px;
color: rgba(0, 0, 0, 1);

.span {
font-weight: 400;
}
}
}
}

.border {
border: 1px solid rgba(207, 207, 207, 0.65);
}
}
</style>

+ 74
- 0
src/views/dashboard/components/TabComp.vue View File

@@ -0,0 +1,74 @@
<template>
<div class="TabComp">
<div class="title">
<div class="title-left">最新任务</div>
<div class="title-right" @click="$router.push('/task/task-manage')">查看更多</div>
</div>
<n-space vertical>
<n-table striped style="table-layout:fixed;">
<thead>
<tr>
<th class="one">序号</th>
<th style="text-align: center;">任务名称</th>
<th style="text-align: center;">执行时间</th>
</tr>
</thead>
<tbody>
<tr v-for="(it, i) in tabList" :key="'list' + it.name">
<td class="one">{{ i + 1 }}</td>
<td class="hid">{{ it.taskName }}</td>
<td class="hid">{{ it.taskStartTime }}</td>
</tr>
</tbody>
</n-table>
</n-space>
</div>
</template>

<script setup name="tabComp">
import { ref } from 'vue'
import { newestTask } from '@/api/dashboard/index'

const tabList = ref([])

newestTask().then(({ data }) => {
tabList.value = data
})

</script>

<style lang="scss" scoped>
.TabComp {
height: 500px;

.title {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;

&-left {
font-size: 16px;
font-weight: bold;
}

&-right {
font-size: 12px;
color: rgba(36, 158, 255, 1);
cursor: pointer;
}
}

.one {
width: 45px;
text-align: center;
}

.hid {
text-align: center;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
</style>

+ 16
- 10
src/views/dashboard/index.vue View File

@@ -1,17 +1,23 @@
<template>
<div>
主页
</div>
</template>
<n-card class="wrapper">

<script>
export default {
name: 'HomePage',
setup() {
<head-comp />

}
}
<cont-comp />

<foot-comp />

</n-card>
</template>

<script setup name="dashboard">
import HeadComp from './components/HeadComp.vue'
import ContComp from './components/ContComp.vue'
import FootComp from './components/FootComp.vue'

</script>
<style scoped lang='scss'>
.wrapper {
background: rgba(240, 242, 245, 1);
}
</style>

+ 134
- 0
src/views/equipment-library/box-manage/components/BoxModal.vue View File

@@ -0,0 +1,134 @@
<template>
<Modal
:options="getModalOptions"
:on-positive-click="handleConfirm"
:on-negative-click="handleClose"
:on-close="handleClose"
>
<template #Context>
<n-form
ref="formRef"
:model="BoxForm"
:rules="BoxRules"
:label-width="80"
label-placement="left"
require-mark-placement="left"
:disabled="disabled"
>
<template v-for="(item,index) in getFormOptions" :key="index">
<n-form-item :label="item.label" :path="item.key">
<n-input v-if="item.type === 'input'" v-model:value="BoxForm[item.key]" v-bind="item.props" />
</n-form-item>
</template>
</n-form>
</template>
</Modal>
</template>

<script>
import { form } from '../tools/form.js'
import { defineComponent, ref, reactive, computed, toRefs } from 'vue'
import { boxCreate, boxUpdate } from '@/api/equipment/box.js'
import Modal from '@/components/Modal/index.vue'
export default defineComponent({
name: 'BoxModal',
components: { Modal },
props: {
visible: {
type: Boolean,
default: false
},
type: {
type: String,
default: 'create'
},
data: {
type: Object,
default: () => {}
}
},
emits: {
'update:visible': null,
'reload': null
},
setup(props, { emit }) {
const MODAL_TYPE = {
'create': '新建盒子信息',
'preview': '盒子详情',
'update': '编辑盒子信息'
}
const { BoxForm, BoxRules } = form
const formRef = ref()
const data = reactive({
BoxForm: {
...BoxForm,
...props.data
},
BoxRules: {
...BoxRules
},
disabled: props.type === 'preview'
})

const getModalOptions = computed(() => {
return {
title: MODAL_TYPE[props.type],
show: props.visible,
negativeText: '取消',
positiveText: '确认'
}
})

const getFormOptions = computed(() => {
return {
...form.formItem
}
})

function handleConfirm() {
formRef.value?.validate((errors) => {
if (!errors) {
const params = { ...data.BoxForm }
if (params.id) {
/* 编辑 */
boxUpdate(params)
.then(res => {
if (res.code === 0) {
emit('reload')
handleClose()
}
})
} else {
/* 新增 */
boxCreate(params)
.then(res => {
if (res.code === 0) {
emit('reload')
handleClose()
}
})
}
} else {
$message.error('请完善必填信息')
}
})
}

/* 关闭弹窗 */
const handleClose = () => {
emit('update:visible', false)
}

return {
...toRefs(data),
formRef,
getModalOptions,
getFormOptions,
handleConfirm,
handleClose
}
}
})
</script>
<style scoped lang='scss'>
</style>

+ 82
- 2
src/views/equipment-library/box-manage/index.vue View File

@@ -1,17 +1,97 @@
<template>
<div>
盒子管理
<n-card>
<headSearch :info="search" @search="handleSearch" @reset="handleSearch" />
<data-table
ref="tableRef"
:columns="columns"
:request="loadDataTable"
:row-key="(row) => row.id"
size="large"
@update:checked-row-keys="handleCheck"
>
<template #tableTitle>
<n-button type="primary" @click="handleModal"> 新建 </n-button>
<n-popconfirm
negative-text="取消"
positive-text="确认"
@positive-click="handleDelete"
>
<template #trigger>
<n-button type="primary"> 删除 </n-button>
</template>
确认要删除选中数据吗?
</n-popconfirm>
</template>
</data-table>
</n-card>
</div>
<!-- 新增、编辑弹窗 -->
<BoxModal v-if="modalShow" v-model:visible="modalShow" :data="rowData" :type="modalType" @reload="handleSearch" />
</template>

<script>
import search from './tools/search.js'
import table from './tools/table.js'
import headSearch from '@/components/Search/index.vue'
import dataTable from '@/components/DataTable/index.vue'
import BoxModal from './components/BoxModal.vue'
import { ref, unref, toRefs, reactive, onUnmounted } from 'vue'
import { boxPage } from '@/api/equipment/box.js'

export default {
name: 'BoxManage',
components: { dataTable, BoxModal, headSearch },
setup() {
const data = reactive({
...toRefs(table),
search
})

const loadDataTable = async(res) => {
const _params = {
...unref(data.searchParams),
...res
}
return await boxPage(_params)
}

// 新增
function handleModal() {
data.rowData = null
data.modalType = 'create'
data.modalShow = true
}

const selectedIds = ref([])
function handleCheck(rowKeys) {
selectedIds.value = rowKeys
}

function handleDelete() {
if (selectedIds.value.length) {
data.deleteData(selectedIds.value)
} else {
$message.warning('请至少选中一条数据')
}
}

onUnmounted(() => {
data.searchParams = null
})

return {
...toRefs(data),
loadDataTable,
handleModal,
handleDelete,
handleCheck
}
}
}

</script>
<style scoped lang='scss'>
.n-button + .n-button {
margin-left: 10px;
}
</style>

+ 19
- 0
src/views/equipment-library/box-manage/tools/form.js View File

@@ -0,0 +1,19 @@
import { reactive } from 'vue'

export const form = reactive({
BoxForm: {
boxSn: null,
boxName: null,
streamCode: null
},
BoxRules: {
boxSn: [{ required: true, message: '请输入设备编号', trigger: 'blur' }],
boxName: [{ required: true, message: '请输入盒子名称', type: 'string', trigger: 'blur' }],
streamCode: [{ required: true, message: '请输入通道编码', trigger: 'blur' }]
},
formItem: [
{ type: 'input', key: 'boxSn', label: '设备编号', props: { maxlength: '20', placeholder: '请输入设备编号', clearable: true }},
{ type: 'input', key: 'boxName', label: '盒子名称', props: { maxlength: '20', placeholder: '请输入盒子名称', clearable: true }},
{ type: 'input', key: 'streamCode', label: '通道编码', props: { maxlength: '20', placeholder: '请输入通道编码', clearable: true }}
]
})

+ 14
- 0
src/views/equipment-library/box-manage/tools/search.js View File

@@ -0,0 +1,14 @@
import { reactive } from 'vue'

const data = reactive([
{
label: '盒子名称',
key: 'boxName',
props: {
placeholder: '请输入盒子名称'
}
}
])

export default data


+ 117
- 0
src/views/equipment-library/box-manage/tools/table.js View File

@@ -0,0 +1,117 @@
import { h, ref, reactive } from 'vue'
import TableAction from '@/components/DataTable/tools/Action.vue'
import { boxDelete } from '@/api/equipment/box.js'

/* 注册table */
const tableRef = ref()
const searchParams = ref()

function handleSearch(params) {
searchParams.value = { ...params }
tableRef.value.reFetch({ searchParams })
}

/**
* @description: 获取数据及操作
* @param {*} row 单行数据
* @param {*} type 操作类型 create:创建,preview:预览,edit:编辑
* @return {*}
*/
function getRowData(row, type) {
data.rowData = row
data.modalType = type
data.modalShow = true
}

// 删除接口
function deleteData(data) {
boxDelete(data)
.then((res) => {
if (res.code === 0) {
handleSearch()
}
})
.catch((e) => {
console.log(e)
})
}

const data = reactive({
tableRef,
searchParams,
rowData: {},
modalType: 'create',
modalShow: false,
configModalShow: false,
handleSearch,
deleteData,
columns: [
{ type: 'selection' },
{
title: '设备编号',
key: 'boxSn',
align: 'center'
},
{
title: '盒子名称',
key: 'boxName',
align: 'center'
},
{
title: '通道编码',
key: 'streamCode',
align: 'center',
Minwidth: 160
},
{
title: '创建时间',
key: 'createTime',
align: 'center',
Minwidth: 160
},
{
title: '创建人',
key: 'createUser',
align: 'center',
width: 100
},
{
title: '操作',
align: 'center',
width: 150,
fixed: 'right',
render(row) {
return h(TableAction, {
actions: [
{
label: '编辑',
type: 'button',
props: {
type: 'primary',
text: true,
onClick: getRowData.bind(null, row, 'update')
},
auth: 'basic_list'
},
{
label: '删除',
type: 'popconfirm',
auth: 'basic_list',
tip: '确定删除这条数据吗?',
props: {
onPositiveClick: deleteData.bind(null, [row.id])
},
ButtonProps: {
text: true,
type: 'primary'
}
}
],
align: 'center'
})
}
}
]
})

export default data

+ 0
- 17
src/views/equipment-library/equipment-manage/index.vue View File

@@ -1,17 +0,0 @@
<template>
<div>
挂载设备
</div>
</template>

<script>
export default {
name: 'EquipmentManage',
setup() {

}
}

</script>
<style scoped lang='scss'>
</style>

+ 135
- 0
src/views/equipment-library/mount-manage/components/MountModal.vue View File

@@ -0,0 +1,135 @@
<template>
<Modal
:options="getModalOptions"
:on-positive-click="handleConfirm"
:on-negative-click="handleClose"
:on-close="handleClose"
>
<template #Context>
<n-form
ref="formRef"
:model="mountForm"
:rules="mountRules"
:label-width="80"
label-placement="left"
require-mark-placement="left"
:disabled="disabled"
>
<template v-for="(item,index) in getFormOptions" :key="index">
<n-form-item :label="item.label" :path="item.key">
<n-input v-if="item.type === 'input'" v-model:value="mountForm[item.key]" v-bind="item.props" />
<n-select v-if="item.type === 'select'" v-model:value="mountForm[item.key]" v-bind="item.props" />
</n-form-item>
</template>
</n-form>
</template>
</Modal>
</template>

<script>
import { form } from '../tools/form.js'
import { defineComponent, ref, reactive, computed, toRefs } from 'vue'
import { mountCreate, mountUpdate } from '@/api/equipment/mount.js'
import Modal from '@/components/Modal/index.vue'
export default defineComponent({
name: 'MountModal',
components: { Modal },
props: {
visible: {
type: Boolean,
default: false
},
type: {
type: String,
default: 'create'
},
data: {
type: Object,
default: () => {}
}
},
emits: {
'update:visible': null,
'reload': null
},
setup(props, { emit }) {
const MODAL_TYPE = {
'create': '新建挂载',
'preview': '挂载详情',
'update': '编辑挂载信息'
}
const { mountForm, mountRules } = form
const formRef = ref()
const data = reactive({
mountForm: {
...mountForm,
...props.data
},
mountRules: {
...mountRules
},
disabled: props.type === 'preview'
})

const getModalOptions = computed(() => {
return {
title: MODAL_TYPE[props.type],
show: props.visible,
negativeText: '取消',
positiveText: '确认'
}
})

const getFormOptions = computed(() => {
return {
...form.formItem
}
})

function handleConfirm() {
formRef.value?.validate((errors) => {
if (!errors) {
const params = { ...data.mountForm }
if (params.id) {
/* 编辑 */
mountUpdate(params)
.then(res => {
if (res.code === 0) {
emit('reload')
handleClose()
}
})
} else {
/* 新增 */
mountCreate(params)
.then(res => {
if (res.code === 0) {
emit('reload')
handleClose()
}
})
}
} else {
$message.error('请完善必填信息')
}
})
}

/* 关闭弹窗 */
const handleClose = () => {
emit('update:visible', false)
}

return {
...toRefs(data),
formRef,
getModalOptions,
getFormOptions,
handleConfirm,
handleClose
}
}
})
</script>
<style scoped lang='scss'>
</style>

+ 97
- 0
src/views/equipment-library/mount-manage/index.vue View File

@@ -0,0 +1,97 @@
<template>
<div>
<n-card>
<headSearch :info="search" @search="handleSearch" @reset="handleSearch" />
<data-table
ref="tableRef"
:columns="columns"
:request="loadDataTable"
:row-key="(row) => row.id"
size="large"
@update:checked-row-keys="handleCheck"
>
<template #tableTitle>
<n-button type="primary" @click="handleModal"> 新建 </n-button>
<n-popconfirm
negative-text="取消"
positive-text="确认"
@positive-click="handleDelete"
>
<template #trigger>
<n-button type="primary"> 删除 </n-button>
</template>
确认要删除选中数据吗?
</n-popconfirm>
</template>
</data-table>
</n-card>
</div>
<!-- 新增、编辑弹窗 -->
<MountModal v-if="modalShow" v-model:visible="modalShow" :data="rowData" :type="modalType" @reload="handleSearch" />
</template>

<script>
import search from './tools/search.js'
import table from './tools/table.js'
import headSearch from '@/components/Search/index.vue'
import dataTable from '@/components/DataTable/index.vue'
import MountModal from './components/MountModal.vue'
import { ref, unref, toRefs, reactive, onUnmounted } from 'vue'
import { mountPage } from '@/api/equipment/mount.js'

export default {
name: 'BoxManage',
components: { dataTable, MountModal, headSearch },
setup() {
const data = reactive({
...toRefs(table),
search
})

const loadDataTable = async(res) => {
const _params = {
...unref(data.searchParams),
...res
}
return await mountPage(_params)
}

// 新增
function handleModal() {
data.rowData = null
data.modalType = 'create'
data.modalShow = true
}

const selectedIds = ref([])
function handleCheck(rowKeys) {
selectedIds.value = rowKeys
}

function handleDelete() {
if (selectedIds.value.length) {
data.deleteData(selectedIds.value)
} else {
$message.warning('请至少选中一条数据')
}
}

onUnmounted(() => {
data.searchParams = null
})

return {
...toRefs(data),
loadDataTable,
handleModal,
handleDelete,
handleCheck
}
}
}
</script>
<style scoped lang='scss'>
.n-button + .n-button {
margin-left: 10px;
}
</style>

+ 21
- 0
src/views/equipment-library/mount-manage/tools/form.js View File

@@ -0,0 +1,21 @@
import { reactive } from 'vue'
import { EQUIPMENT_TYPE } from '@/utils/dictionary.js'

export const form = reactive({
mountForm: {
code: null,
name: null,
type: null
},
mountRules: {
boxSn: [{ required: true, message: '请输入设备编号', trigger: 'blur' }],
name: [{ required: true, message: '请输入盒子名称', type: 'string', trigger: 'blur' }],
type: [{ required: true, type: 'number', message: '请输入通道编码', trigger: 'blur' }]
},
formItem: [
{ type: 'input', key: 'code', label: '设备编号', props: { maxlength: '20', placeholder: '请输入设备编号', clearable: true }},
{ type: 'input', key: 'name', label: '设备名称', props: { maxlength: '20', placeholder: '请输入盒子名称', clearable: true }},
{ type: 'select', key: 'type', label: '设备类型', props: { options: EQUIPMENT_TYPE, clearable: true }},
{ type: 'input', key: 'note', label: '备注', props: { type: 'textarea', autosize: { maxlength: '200', minRows: 3, maxRows: 3 }}}
]
})

+ 14
- 0
src/views/equipment-library/mount-manage/tools/search.js View File

@@ -0,0 +1,14 @@
import { reactive } from 'vue'

const data = reactive([
{
label: '设备名称',
key: 'name',
props: {
placeholder: '请输入设备名称'
}
}
])

export default data


+ 124
- 0
src/views/equipment-library/mount-manage/tools/table.js View File

@@ -0,0 +1,124 @@
import { h, ref, reactive } from 'vue'
import TableTags from '@/components/DataTable/tools/Tags.vue'
import TableAction from '@/components/DataTable/tools/Action.vue'
import { mountDelete } from '@/api/equipment/mount.js'
import { EQUIPMENT_TYPE } from '@/utils/dictionary.js'

/* 注册table */
const tableRef = ref()
const searchParams = ref()

function handleSearch(params) {
searchParams.value = { ...params }
tableRef.value.reFetch({ searchParams })
}

/**
* @description: 获取数据及操作
* @param {*} row 单行数据
* @param {*} type 操作类型 create:创建,preview:预览,edit:编辑
* @return {*}
*/
function getRowData(row, type) {
data.rowData = row
data.modalType = type
data.modalShow = true
}

// 删除接口
function deleteData(data) {
mountDelete(data)
.then((res) => {
if (res.code === 0) {
handleSearch()
}
})
.catch((e) => {
console.log(e)
})
}

const data = reactive({
tableRef,
searchParams,
rowData: {},
modalType: 'create',
modalShow: false,
configModalShow: false,
handleSearch,
deleteData,
columns: [
{ type: 'selection' },
{
title: '设备编号',
key: 'code',
align: 'center'
},
{
title: '设备名称',
key: 'name',
align: 'center'
},
{
title: '设备类型',
key: 'type',
align: 'center',
render(row) {
return h(TableTags, {
data: row.type,
filters: EQUIPMENT_TYPE
})
}
},
{
title: '创建时间',
key: 'createTime',
align: 'center',
Minwidth: 160
},
{
title: '创建人',
key: 'createUser',
align: 'center',
width: 100
},
{
title: '操作',
align: 'center',
width: 150,
fixed: 'right',
render(row) {
return h(TableAction, {
actions: [
{
label: '编辑',
type: 'button',
props: {
type: 'primary',
text: true,
onClick: getRowData.bind(null, row, 'update')
},
auth: 'basic_list'
},
{
label: '删除',
type: 'popconfirm',
auth: 'basic_list',
tip: '确定删除这条数据吗?',
props: {
onPositiveClick: deleteData.bind(null, [row.id])
},
ButtonProps: {
text: true,
type: 'primary'
}
}
],
align: 'center'
})
}
}
]
})

export default data

+ 144
- 0
src/views/equipment-library/uva-manage/components/UVAModal.vue View File

@@ -0,0 +1,144 @@
<template>
<Modal
:options="getModalOptions"
:on-positive-click="handleConfirm"
:on-negative-click="handleClose"
:on-close="handleClose"
>
<template #Context>
<n-form
ref="formRef"
:model="uvaForm"
:rules="uvaRules"
:label-width="80"
label-placement="left"
require-mark-placement="left"
:disabled="disabled"
>
<template v-for="(item,index) in getFormOptions" :key="index">
<n-form-item :label="item.label" :path="item.key">
<n-input v-if="item.type === 'input'" v-model:value="uvaForm[item.key]" v-bind="item.props" />
<n-select v-if="item.type === 'select'" v-model:value="uvaForm[item.key]" v-bind="item.props" />
<n-input-number v-if="item.type === 'number'" v-model:value="uvaForm[item.key]" v-bind="item.props">
<template v-if="item.suffix" #suffix>
{{ item.suffix }}
</template>
</n-input-number>
</n-form-item>
</template>
</n-form>
</template>
</Modal>
</template>

<script>
import { form } from '../tools/form.js'
import { defineComponent, ref, reactive, computed, toRefs } from 'vue'
import { uvaCreate, uvaUpdate } from '@/api/equipment/uva.js'
import Modal from '@/components/Modal/index.vue'
export default defineComponent({
name: 'UvaModal',
components: { Modal },
props: {
visible: {
type: Boolean,
default: false
},
type: {
type: String,
default: 'create'
},
data: {
type: Object,
default: () => {}
}
},
emits: {
'update:visible': null,
'reload': null
},
setup(props, { emit }) {
const MODAL_TYPE = {
'create': '新建盒子信息',
'preview': '盒子详情',
'update': '编辑盒子信息'
}
const { uvaForm, uvaRules } = form
const formRef = ref()
const data = reactive({
uvaForm: {
...uvaForm,
...props.data
},
uvaRules: {
...uvaRules
},
disabled: props.type === 'preview'
})

const getModalOptions = computed(() => {
return {
title: MODAL_TYPE[props.type],
show: props.visible,
negativeText: '取消',
positiveText: '确认'
}
})

const getFormOptions = computed(() => {
return {
...form.formItem
}
})

function handleConfirm() {
formRef.value?.validate((errors) => {
if (!errors) {
const params = { ...data.uvaForm }
if (params.id) {
/* 编辑 */
uvaUpdate(params)
.then(res => {
if (res.code === 0) {
emit('reload')
handleClose()
}
})
} else {
/* 新增 */
uvaCreate(params)
.then(res => {
if (res.code === 0) {
emit('reload')
handleClose()
}
})
}
} else {
$message.error('请完善必填信息')
}
})
}

/* 关闭弹窗 */
const handleClose = () => {
emit('update:visible', false)
}

return {
...toRefs(data),
formRef,
getModalOptions,
getFormOptions,
handleConfirm,
handleClose
}
}
})
</script>

<style scoped lang='scss'>
.n-input-number{
width: 100%;
}
</style>

+ 83
- 3
src/views/equipment-library/uva-manage/index.vue View File

@@ -1,17 +1,97 @@
<template>
<div>
无人机管理
<n-card>
<headSearch :info="search" @search="handleSearch" @reset="handleSearch" />
<data-table
ref="tableRef"
:columns="columns"
:request="loadDataTable"
:row-key="(row) => row.id"
size="large"
@update:checked-row-keys="handleCheck"
>
<template #tableTitle>
<n-button type="primary" @click="handleModal"> 新建 </n-button>
<n-popconfirm
negative-text="取消"
positive-text="确认"
@positive-click="handleDelete"
>
<template #trigger>
<n-button type="primary"> 删除 </n-button>
</template>
确认要删除选中数据吗?
</n-popconfirm>
</template>
</data-table>
</n-card>
</div>
<!-- 新增、编辑弹窗 -->
<UVAModal v-if="modalShow" v-model:visible="modalShow" :data="rowData" :type="modalType" @reload="handleSearch" />
</template>

<script>
import search from './tools/search.js'
import table from './tools/table.js'
import headSearch from '@/components/Search/index.vue'
import dataTable from '@/components/DataTable/index.vue'
import UVAModal from './components/UVAModal.vue'
import { ref, unref, toRefs, reactive, onUnmounted } from 'vue'
import { uvaPage } from '@/api/equipment/uva.js'

export default {
name: 'UVAManage',
name: 'BoxManage',
components: { dataTable, UVAModal, headSearch },
setup() {
const data = reactive({
...toRefs(table),
search
})

const loadDataTable = async(res) => {
const _params = {
...unref(data.searchParams),
...res
}
return await uvaPage(_params)
}

// 新增
function handleModal() {
data.rowData = null
data.modalType = 'create'
data.modalShow = true
}

const selectedIds = ref([])
function handleCheck(rowKeys) {
selectedIds.value = rowKeys
}

function handleDelete() {
if (selectedIds.value.length) {
data.deleteData(selectedIds.value)
} else {
$message.warning('请至少选中一条数据')
}
}

onUnmounted(() => {
data.searchParams = null
})

return {
...toRefs(data),
loadDataTable,
handleModal,
handleDelete,
handleCheck
}
}
}

</script>
<style scoped lang='scss'>
.n-button + .n-button {
margin-left: 10px;
}
</style>

+ 26
- 0
src/views/equipment-library/uva-manage/tools/form.js View File

@@ -0,0 +1,26 @@
import { reactive } from 'vue'
import { POWER_TYPE } from '@/utils/dictionary.js'

export const form = reactive({
uvaForm: {
code: null,
name: null,
powerType: null,
accuracy: null,
manufacturer: null
},
uvaRules: {
code: [{ required: true, message: '请输入设备编号', trigger: 'blur' }],
name: [{ required: true, message: '请输入盒子名称', type: 'string', trigger: 'blur' }],
powerType: [{ required: true, type: 'number', message: '请选择动力类型', trigger: 'blur' }],
accuracy: [{ required: true, type: 'number', message: '请输入定位精度', trigger: 'blur' }],
manufacturer: [{ required: true, message: '请输入生产厂家', trigger: 'blur' }]
},
formItem: [
{ type: 'input', key: 'code', label: '设备编号', props: { maxlength: '20', placeholder: '请输入设备编号', clearable: true }},
{ type: 'input', key: 'name', label: '设备名称', props: { maxlength: '20', placeholder: '请输入盒子名称', clearable: true }},
{ type: 'select', key: 'powerType', label: '动力类型', props: { options: POWER_TYPE, clearable: true }},
{ type: 'number', key: 'accuracy', label: '定位精度', props: { min: 0, placeholder: '请输入通道编码', showButton: false, clearable: true }, suffix: '米' },
{ type: 'input', key: 'manufacturer', label: '生产厂家', props: { maxlength: '20', placeholder: '请输入生产厂家', clearable: true }}
]
})

+ 14
- 0
src/views/equipment-library/uva-manage/tools/search.js View File

@@ -0,0 +1,14 @@
import { reactive } from 'vue'

const data = reactive([
{
label: '设备名称',
key: 'name',
props: {
placeholder: '请输入设备名称'
}
}
])

export default data


+ 122
- 0
src/views/equipment-library/uva-manage/tools/table.js View File

@@ -0,0 +1,122 @@
import { h, ref, reactive } from 'vue'
import TableTags from '@/components/DataTable/tools/Tags.vue'
import TableAction from '@/components/DataTable/tools/Action.vue'
import { uvaDelete } from '@/api/equipment/uva.js'
import { POWER_TYPE } from '@/utils/dictionary.js'

/* 注册table */
const tableRef = ref()
const searchParams = ref()

function handleSearch(params) {
searchParams.value = { ...params }
tableRef.value.reFetch({ searchParams })
}

/**
* @description: 获取数据及操作
* @param {*} row 单行数据
* @param {*} type 操作类型 create:创建,preview:预览,edit:编辑
* @return {*}
*/
function getRowData(row, type) {
data.rowData = row
data.modalType = type
data.modalShow = true
}

// 删除接口
function deleteData(data) {
uvaDelete(data)
.then((res) => {
if (res.code === 0) {
handleSearch()
}
})
.catch((e) => {
console.log(e)
})
}

const data = reactive({
tableRef,
searchParams,
rowData: {},
modalType: 'create',
modalShow: false,
configModalShow: false,
handleSearch,
deleteData,
columns: [
{ type: 'selection' },
{
title: '设备编号',
key: 'code',
align: 'center'
},
{
title: '设备名称',
key: 'name',
align: 'center'
},
{
title: '动力类型',
key: 'powerType',
align: 'center',
render(row) {
return h(TableTags, {
data: row.powerType,
filters: POWER_TYPE
})
}
},
{
title: '定位精度',
key: 'accuracy',
align: 'center'
},
{
title: '生产厂家',
key: 'manufacturer',
align: 'center'
},
{
title: '操作',
align: 'center',
width: 150,
fixed: 'right',
render(row) {
return h(TableAction, {
actions: [
{
label: '编辑',
type: 'button',
props: {
type: 'primary',
text: true,
onClick: getRowData.bind(null, row, 'update')
},
auth: 'basic_list'
},
{
label: '删除',
type: 'popconfirm',
auth: 'basic_list',
tip: '确定删除这条数据吗?',
props: {
onPositiveClick: deleteData.bind(null, [row.id])
},
ButtonProps: {
text: true,
type: 'primary'
}
}
],
align: 'center'
})
}
}
]
})

export default data

+ 158
- 0
src/views/system-manage/banner-manage/BannerModal.vue View File

@@ -0,0 +1,158 @@
<template>
<Modal
:options="getModalOptions"
:on-positive-click="handleConfirm"
:on-negative-click="handleClose"
:on-close="handleClose"
>
<template #Context>
<n-form
ref="formRef"
:model="form"
:rules="rules"
:label-width="80"
label-placement="left"
require-mark-placement="left"
>
<n-form-item label="横幅图片" path="cover">
<UploadOss ref="ossRefs" :default-list="initUpload" @upload-status="handleUploadStatus" />
</n-form-item>
<!-- <n-form-item label="排序" path="sort">
<n-input-number v-model:value="form.sort" clearable />
</n-form-item> -->
</n-form>
</template>
</Modal>
</template>

<script>
import { defineComponent, ref, reactive, computed, toRefs, watch } from 'vue'
import Modal from '@/components/Modal/index.vue'
import UploadOss from '@/components/UploadOss/index.vue'
import { bannerCreate, bannerUpdate } from '@/api/system/banner.js'
export default defineComponent({
name: 'BannerModal',
components: { Modal, UploadOss },
props: {
visible: {
type: Boolean,
default: false
},
type: {
type: String,
default: 'create'
},
data: {
type: Object,
default: () => null
}
},
emits: {
'update:visible': null,
'reload': null
},
setup(props, { emit }) {
const MODAL_TYPE = {
'create': '新建横幅',
'preview': '横幅详情',
'update': '编辑横幅'
}
const data = reactive({
form: { ...props.data },
initUpload: '',
rules: {
cover: [{ required: true, message: '请选择横幅图片', type: 'string', trigger: 'blur' }]
}
})

watch(() => props.visible, (value) => {
if (value) {
if (props.data) {
data.initUpload = props.data.cover
}
} else {
data.initUpload = ''
}
})

const getModalOptions = computed(() => {
return {
show: props.visible,
title: MODAL_TYPE[props.type],
negativeText: '取消',
positiveText: '确认'
}
})

function handleUploadStatus(status) {
data.form.cover = status
}

const formRef = ref()
const ossRefs = ref()
function handleConfirm() {
if (props.type !== 'preview') {
formRef.value?.validate((errors) => {
if (!errors) {
const uploads = ossRefs.value.map((item, index) => {
return item.startUpload()
})
Promise.all(uploads)
.then(response => {
const isError = response.map((item) => {
return item.includes('error')
})
if (!isError.includes(true)) {
const imageStr = response.join()
const params = {
...data.form,
cover: imageStr[0]
}
if (params.id) {
/* 编辑 */
bannerUpdate(params)
.then(res => {
if (res.code === 0) {
emit('reload')
handleClose()
}
})
} else {
/* 新增 */
bannerCreate(params)
.then(res => {
if (res.code === 0) {
emit('reload')
handleClose()
}
})
}
} else {
$message.error('图片上传失败,请稍后重试')
}
})
handleClose()
} else {
$message.error('请完善必填信息')
}
})
} else {
handleClose()
}
}

/* 关闭弹窗 */
const handleClose = () => {
emit('update:visible', false)
}

return {
...toRefs(data),
getModalOptions,
handleUploadStatus,
handleConfirm,
handleClose
}
}
})
</script>

+ 47
- 1
src/views/system-manage/banner-manage/index.vue View File

@@ -1,14 +1,60 @@
<template>
<div>
移动端横幅管理
<n-card>
<!-- <headSearch :info="search" @search="handleSearch" @reset="handleSearch" /> -->
<data-table
ref="tableRef"
:columns="columns"
:row-key="(row) => row.id"
:request="loadDataTable"
size="large"
@update:checked-row-keys="handleCheck"
>
<template #tableTitle>
<n-button type="primary" @click="handleModal"> 新建 </n-button>
</template>
</data-table>
</n-card>
</div>

<!-- 新增、编辑弹窗 -->
<BannerModal v-if="modalShow" v-model:visible="modalShow" :type="modalType" :data="rowData" @reload="handleSearch" />
</template>

<script>
import { unref, toRefs, reactive } from 'vue'
// import headSearch from '@/components/Search/index.vue'
import table from './tool/table.js'
import dataTable from '@/components/DataTable/index.vue'
import BannerModal from './BannerModal.vue'
import { bannerPage } from '@/api/system/banner.js'
export default {
name: 'BannerManage',
components: { dataTable, BannerModal },
setup() {
const data = reactive({
...toRefs(table)
})
const loadDataTable = async(res) => {
const _params = {
...unref(data.searchParams),
...res
}
return await bannerPage(_params)
}

// 新增
function handleModal() {
data.rowData = null
data.modalType = 'create'
data.modalShow = true
}

return {
...toRefs(data),
loadDataTable,
handleModal
}
}
}


+ 19
- 0
src/views/system-manage/banner-manage/tool/search.js View File

@@ -0,0 +1,19 @@
import { reactive } from 'vue'
export const search = reactive({
search: [
{
label: '用户账号',
key: 'username',
props: {
placeholder: '请输入用户账号'
}
},
{
label: '用户姓名',
key: 'realname',
props: {
placeholder: '请输入用户姓名'
}
}
]
})

+ 133
- 0
src/views/system-manage/banner-manage/tool/table.js View File

@@ -0,0 +1,133 @@
import { h, ref, reactive } from 'vue'
import TableImage from '@/components/DataTable/tools/Image.vue'
import TableAction from '@/components/DataTable/tools/Action.vue'
import { bannerDelete } from '@/api/system/banner.js'
/* 注册table */
const tableRef = ref()
const searchParams = ref()

function handleSearch(params) {
searchParams.value = { ...params }
tableRef.value.reFetch({ searchParams })
}

/**
* @description: 获取数据及操作
* @param {*} row 单行数据
* @param {*} type 操作类型 create:创建,preview:预览,edit:编辑
* @return {*}
*/
function getRowData(row, type) {
data.rowData = row
data.modalType = type
data.modalShow = true
}

/**
* @description: 删除用户接口
* @param {Array} ids 用户id集合
* @return {*}
*/
function deleteData(ids) {
bannerDelete(ids).then((res) => {
if (res.code === 0) {
handleSearch()
}
}).catch(e => {
console.log(e)
})
}

const data = reactive({
tableRef,
searchParams,
rowData: {},
modalType: 'create',
modalShow: false,
handleSearch,
deleteData,
columns: [
{
title: '序号',
key: 'key',
render: (_, index) => {
return `${index + 1}`
},
align: 'center'
},
{
title: '横幅图片',
key: 'cover',
align: 'center',
render(row) {
return h(TableImage, {
images: {
width: 36,
height: 36,
src: row.cover
}
})
}
},
{
title: '上传人',
key: 'updateUser',
align: 'center',
width: 160
},
{
title: '上传时间',
key: 'updateTime',
align: 'center',
width: 160
},
{
title: '操作',
align: 'center',
width: 150,
fixed: 'right',
render(row) {
return h(TableAction, {
actions: [
{
label: '详情',
type: 'button',
props: {
type: 'primary',
text: true,
onClick: getRowData.bind(null, row, 'preview')
},
auth: 'basic_list'
},
{
label: '修改',
type: 'button',
props: {
type: 'primary',
text: true,
onClick: getRowData.bind(null, row, 'update')
},
auth: 'basic_list'
},
{
label: '删除',
type: 'popconfirm',
auth: 'basic_list',
tip: '确定删除这条数据吗?',
props: {
onPositiveClick: deleteData.bind(null, [row.id])
},
ButtonProps: {
text: true,
type: 'error'
}
}
],
align: 'center'
})
}
}
]
})

export default data

+ 283
- 0
src/views/task-manage/components/DrawComp.vue View File

@@ -0,0 +1,283 @@
<template>

<!-- 状态 -->
<div class="head">
<n-steps size="small" :current="current" :status="'process'">
<n-step v-for="(it, i) in TASK_STATUS" :key="i + it.label" :title="it.label" />
</n-steps>
</div>

<!-- 基本信息 -->
<div class="cont">
<n-descriptions label-placement="left" label-align="right" :column="4" title="任务基本信息">
<n-descriptions-item v-for="(it, i) in basicInfo" :key="i + it.label" :label="it.label">
{{ it.value }}
</n-descriptions-item>
</n-descriptions>
</div>

<!-- 飞行信息 - 待分配飞手:状态为5 -->
<div v-if="current === 1 && isAdmin" class="cont">
<div class="title">飞行信息</div>
<n-form
ref="formRef"
:model="form.userForm"
:rules="form.userRules"
:label-width="85"
label-placement="left"
require-mark-placement="left"
>
<template v-for="it in form.formItem" :key="it.key">
<n-form-item v-if="it?.isLive !== data.isLive" :label="it.label" :path="it.key">
<n-select v-model:value="form.userForm[it.key]" v-bind="it.props" />
</n-form-item>
</template>
</n-form>
<n-button type="info" class="btn" @click="submitClick">提交</n-button>
</div>

<!-- 飞行信息 - 状态不为5 -->
<div v-if="current !== 1" class="cont">
<n-descriptions label-placement="left" label-align="right" :column="4" title="飞行信息">
<template v-for="(it, i) in flyInfo" :key="i + it.label">
<n-descriptions-item v-if="current !== it?.current" :label="it.label">
{{ it.value }}
</n-descriptions-item>
</template>
</n-descriptions>
</div>

<!-- 设备/影响基本信息 - 飞手接单: 管理员 -->
<div v-if="current == 2 && isAdmin" class="cont">
<n-descriptions label-placement="left" label-align="right" :column="4" title="设备/影响基本信息">
<template v-for="(it, i) in equipmentInfo" :key="i + it.label">
<n-descriptions-item v-if="it?.isLive !== data.isLive" :label="it.label">
{{ it.value }}
</n-descriptions-item>
</template>
</n-descriptions>
</div>

<!-- 飞手接单: 飞手 -->
<div v-if="current === 2 && !isAdmin" class="cont">
<div class="title">飞行信息{{ '-isAdmin:' + isAdmin + data.isLive }}</div>
<n-form
ref="formRefOrder"
:model="form.userForm"
:rules="form.userRulesOrder"
:label-width="85"
label-placement="left"
require-mark-placement="left"
>
<template v-for="it in form.formItemOrder" :key="it.key">
<n-form-item v-if="it?.isLive !== data.isLive" :label="it.label" :path="it.key">
<n-select v-model:value="form.userForm[it.key]" v-bind="it.props" />
</n-form-item>
</template>
</n-form>
<n-button type="info" class="btn" @click="flyClick">接单</n-button>
</div>

<!-- 设备/影响基本信息 - 已接单 -->
<div v-if="current === 3" class="cont">
<n-descriptions label-placement="left" label-align="right" :column="4" title="设备/影响基本信息">
<template v-for="(it, i) in executionInfo" :key="i + it.label">
<n-descriptions-item :label="it.label">
{{ it.value }}
</n-descriptions-item>
</template>
</n-descriptions>
<n-button v-if="isAdmin" type="info" class="btn" @click="startFly">开始飞行</n-button>
</div>

<!-- 设备/影响基本信息 - 执行中: 管理员 -->
<div v-if="current === 4" class="cont">
<n-descriptions label-placement="left" label-align="right" :column="4" title="设备/影响基本信息">
<template v-for="(it, i) in executionInfo" :key="i + it.label">
<n-descriptions-item :label="it.label">
{{ it.value }}
</n-descriptions-item>
</template>
</n-descriptions>
<n-button v-if="isAdmin" type="Error" class="btn" @click="endFly">结束飞行</n-button>
</div>

<div v-if="current === 5" class="cont">
<div class="title">飞行文件</div>

<UploadVod @upload-status="vodStatus" />

</div>

</template>

<script setup name="DrawComp">
import { ref, defineProps, reactive, defineEmits } from 'vue'
import { TASK_STATUS } from '@/utils/dictionary'
import { distributionPilot, pilotOrder, pilotStart, pilotEnd, uploadFlightUrl } from '@/api/task/index.js'
import { form, getPilotList, getEquipment, getEquipmentMount, getCloudMount } from '../tools/drawForm'
import { basic, fly, equipment, execution } from '../hook/index'
import UploadVod from '@/components/UploadVod/index.vue'

getPilotList() // 获取飞手列表
getEquipment() // 无人机列表
getEquipmentMount() // 挂载设备列表
getCloudMount() // 挂载盒子列表

const props = defineProps({
detail: {
type: Object,
required: true
}
})

const { detail: { data }} = props // 传来的详情数据

const current = data.pilotStatus / 5 // 当前状态

const isAdmin = Math.random() > 0.5 // 模拟是否管理员
console.log('随即模拟是否管理员:', isAdmin)

const formRef = ref() // 表格refs - 分配飞手
const formRefOrder = ref() // 表格refs - 飞手接单

const basicInfo = basic(data) // 基础信息
const flyInfo = fly(data) // 飞行信息
const equipmentInfo = equipment(data) // 设备/影响基本信息
const executionInfo = execution(data) // 执行中信息

const emit = defineEmits(['close'])

// 分配飞手
const submitClick = () => {
formRef.value?.validate().then(() => {
const { userForm } = form
distributionPilot({
...userForm,
id: data.id
}).then(({ code }) => {
if (code === 0) {
emit('close')
}
})
}).catch(e => {
$message.error('请完善必填信息')
})
}
// 飞手接单
const flyClick = () => {
formRefOrder.value?.validate().then(() => {
const { userForm } = form
pilotOrder({
...userForm,
flightHandId: data.flightHandId,
id: data.id
}).then(({ code }) => {
if (code === 0) {
emit('close')
}
})
}).catch(e => {
$message.error('请完善必填信息')
})
}

// 开始飞行
const startFly = () => {
pilotStart({ id: data.id }).then(({ code, msg }) => {
if (code === 0) {
emit('close')
} else {
$message.error(msg)
}
})
}

// 结束飞行
const endFly = () => {
pilotEnd({ id: data.id }).then(({ code, msg }) => {
if (code === 0) {
emit('close')
} else {
$message.error(msg)
}
})
}

// 视频上传
const vodStatus = ({ status, list }) => {
console.log(status, list)
const videoUrl = []
for (var key in list) {
videoUrl.push(list[key].url)
}
const videoList = videoUrl.join(',')
console.log('videoList', videoList)
return
if (status === 'success') {
// 视频上传
uploadFlightUrl({
id: data.id,
videoUrl: videoList
}).then(({ code }) => {
console.log(code)
})
}
}

</script>

<style lang="scss" scoped>
.head {
margin: 20px;
padding: 50px 10px 50px 30px;
background-color: #fff;
}

.cont {
margin: 20px;
background-color: #fff;

.title {
margin: 20px;
padding: 20px;
font-size: 18px;
font-weight: bold;
}

.btn {
width: 70px;
margin-left: calc(50% - 30px);
margin-top: 10px;
margin-bottom: 30px;
}
}

:deep(.n-descriptions-header) {
font-weight: bold;
margin: 10px;
padding: 20px 0;
border-bottom: 1px solid rgba(240, 242, 245, 0.8);
}

:deep(.n-descriptions-table) {
margin: 10px;
padding: 10px;
}

:deep(.n-drawer-body-content-wrapper) {
background-color: rgba(240, 242, 245, 1);
}

:deep(.n-form) {
display: flex;
flex-wrap: wrap;
justify-content: flex-start;
padding: 0 20px;
}

:deep(.n-form-item) {
width: 230px;
margin-right: 15px;
}
</style>

+ 73
- 0
src/views/task-manage/components/MapComp.vue View File

@@ -0,0 +1,73 @@
<template>
<div class="mapComp" id="mapComp"></div>
<input id="pickerInput" type="text" placeholder="输入关键字选取地点" />

</template>

<script setup name="mapComp">
import GMap from '@/utils/map/GMap'

const emit = defineEmits(['mapEmit'])

GMap.then((AMap) => {
let map = new AMap.Map('mapComp', {
zoom: 13,
center: [118.773319, 31.828123],
});

// 添加缩放工具
const toolbar = new AMap.ToolBar();
map.addControl(toolbar);

// 搜索
AMapUI.loadUI(['misc/PoiPicker'], function (PoiPicker) {

const poiPicker = new PoiPicker({
input: 'pickerInput'
});

poiPicker.on('poiPicked', ({ item }) => {

let inp = document.getElementById('pickerInput');
inp.value = item.name;

const marker = new AMap.Marker({
title: item.address
});

marker.setMap(map);
marker.setPosition(item.location);
map.setCenter(marker.getPosition());

emit('mapEmit', item);

})
});
}).catch(e => console.log(e))

</script>

<style lang="scss" scoped>
.mapComp {
width: 100%;
height: 400px;
}

#pickerInput {
position: absolute;
z-index: 9999;
top: 30px;
left: 30px;
width: 200px;
padding: 5px 5px;
}

.amap_lib_placeSearch .poibox.highlight {
background-color: #CAE1FF;
}

.amap_lib_placeSearch .poi-more {
display: none !important;
}
</style>

+ 325
- 0
src/views/task-manage/components/UserModal.vue View File

@@ -0,0 +1,325 @@
<template>
<Modal
:options="getModalOptions"
:on-positive-click="handleConfirm"
:on-negative-click="handleClose"
:on-close="handleClose"
>
<template #Context>
<n-form
ref="formRef"
:model="userForm"
:rules="userRules"
:label-width="80"
label-placement="left"
require-mark-placement="left"
:disabled="disabled"
>
<template v-for="(item,index) in getFormOptions" :key="index">
<n-form-item :label="item.label" :path="item.key">
<UploadOss
v-if="item.type === 'oss'"
:ref="el=>{ossRefs[item.refIndex] = el}"
:default-list="userForm[item.file]"
@upload-status="handleUploadStatus"
/>
<n-input v-if="item.type === 'input'" v-model:value="userForm[item.key]" v-bind="item.props" />
<n-select v-if="item.type === 'select'" v-model:value="userForm[item.key]" v-bind="item.props" />
<div v-if="item.type === 'map'" id="mapContainer" class="map" />

<n-input v-if="item.type === 'inputStart'" v-model:value="userForm[item.key]" disabled v-bind="item.props">
<template #suffix>
<n-button quaternary type="info" @click="showStart = true">去标记
<n-icon size="20" color="rgba(42, 130, 228, 1)">
<LocationSharp />
</n-icon>
</n-button>
</template>
</n-input>

<n-input v-if="item.type === 'inputEnd'" v-model:value="userForm[item.key]" disabled v-bind="item.props">
<template #suffix>
<n-button quaternary type="info" @click="showEnd = true">去标记
<n-icon size="20" color="rgba(42, 130, 228, 1)">
<LocationSharp />
</n-icon>
</n-button>
</template>
</n-input>

<n-date-picker
v-if="item.type === 'datetime'"
v-model:formatted-value="userForm[item.key]"
v-bind="item.props"
type="datetime"
value-format="yyyy-MM-dd HH:mm:ss"
:is-date-disabled="isDateDisabled"
/>
<n-radio-group v-if="item.type === 'radio'" v-model:value="userForm[item.key]" :name="item.key">
<n-space>
<n-radio v-for="(cItem,cIndex) in item.options" :key="`${item.key}_${cIndex}`" :value="cItem.value"> {{
cItem.label }}</n-radio>
</n-space>
</n-radio-group>
</n-form-item>
</template>
</n-form>
</template>
</Modal>

<n-modal
v-model:show="showStart"
:mask-closable="false"
preset="dialog"
title=""
positive-text="确定"
@positive-click="userForm.patrolLocation = startVal"
@negative-click="showStart = false"
>
<map-comp @mapEmit="startHandle" />
</n-modal>

<n-modal
v-model:show="showEnd"
:mask-closable="false"
preset="dialog"
title=""
positive-text="确定"
@positive-click="userForm.endValue = endVal"
@negative-click="showEnd = false"
>
<map-comp @mapEmit="endHandle" />
</n-modal>

</template>

<script>
import { form, getPilotList, getEquipment, getEquipmentMount } from '../tools/form.js'
import { defineComponent, ref, reactive, computed, toRefs } from 'vue'
import Modal from '@/components/Modal/index.vue'
import UploadOss from '@/components/UploadOss/index.vue'
import { taskAdd, taskEdit } from '@/api/task'
import { LocationSharp } from '@vicons/ionicons5'
// import AMapLoader from '@amap/amap-jsapi-loader'
import MapComp from './MapComp.vue'
import GMap from '@/utils/map/GMap'
import dayjs from 'dayjs'

export default defineComponent({
name: 'UserModal',
components: { Modal, UploadOss, MapComp, LocationSharp },
props: {
visible: {
type: Boolean,
default: false
},
type: {
type: String,
default: 'create'
},
data: {
type: Object,
default: () => null
}
},
emits: {
'update:visible': null,
'reload': null
},
setup(props, { emit }) {
const MODAL_TYPE = {
'create': '新建任务',
'preview': '任务详情',
'update': '编辑任务'
}
getPilotList()
getEquipment()
getEquipmentMount()
const { formItem, userForm, userRules } = form
const formRef = ref()
const ossRefs = ref([])
const data = reactive({
userForm: {
...userForm,
...props.data
},
userRules: {
...userRules
},
disabled: props.type === 'preview'
})
const getModalOptions = computed(() => {
return {
show: props.visible,
title: MODAL_TYPE[props.type],
negativeText: '取消',
positiveText: '确认'
}
})

const getFormOptions = computed(() => {
return {
...formItem
}
})

function handleUploadStatus(status) {
data.userForm.imageStatus = status
}

let geocoder = null
// 地图
GMap.then((AMap) => {
let Gmap = null

// 坐标求地址
geocoder = new AMap.Geocoder()

if (props.type === 'update') { // 打开编辑
Gmap = new AMap.Map('mapContainer', {
zoom: 13,
center: [data.userForm.startLongitude, data.userForm.startLatitude]
})

var marker = new AMap.Marker({
position: new AMap.LngLat(data.userForm.startLongitude, data.userForm.startLatitude),
icon: '//vdata.amap.com/icons/b18/1/2.png',
title: '起点'
})

// 将创建的点标记添加到已有的地图实例:
Gmap.add(marker)

// 起点赋值
geocoder.getAddress([data.userForm.startLongitude, data.userForm.startLatitude], (status, result) => {
if (status === 'complete' && result.info === 'OK') {
data.userForm.patrolLocation = result.regeocode.formattedAddress
}
})

// 终点赋值
geocoder.getAddress([data.userForm.endLongitude, data.userForm.endLatitude], (status, result) => {
if (status === 'complete' && result.info === 'OK') {
data.userForm.endValue = result.regeocode.formattedAddress
}
})
} else {
Gmap = new AMap.Map('mapContainer', {
zoom: 13,
center: [118.773319, 31.828123]
})
}

// 添加缩放工具
const toolbar = new AMap.ToolBar()
Gmap.addControl(toolbar)
}).catch(e => console.log(e))

// 地图辅助参数
const showStart = ref(false)
const showEnd = ref(false)

const startVal = ref(null)
const endVal = ref(null)

// 起点地图 确定事件
const startHandle = e => {
const { location: { lat }} = e
const { location: { lng }} = e
geocoder.getAddress([lng, lat], function(status, result) {
if (status === 'complete' && result.info === 'OK') {
startVal.value = result.regeocode.formattedAddress
data.userForm.startLongitude = String(lng)
data.userForm.startLatitude = String(lat)
}
})
}
// 终点地图 确定事件
const endHandle = e => {
const { location: { lat }} = e
const { location: { lng }} = e
geocoder.getAddress([lng, lat], function(status, result) {
if (status === 'complete' && result.info === 'OK') {
endVal.value = result.regeocode.formattedAddress
data.userForm.endLongitude = String(lng)
data.userForm.endLatitude = String(lat)
}
})
}

function handleConfirm() {
formRef.value?.validate(async(errors) => {
if (!errors) {
const params = {
...data.userForm,
taskStartTime: String(data.userForm.taskStartTime)
}

if (params.taskCode) {
/* 编辑 */
taskEdit(params)
.then(res => {
if (res.code === 0) {
emit('reload')
handleClose()
}
})
} else {
/* 新增 */
taskAdd(params)
.then(res => {
if (res.code === 0) {
emit('reload')
handleClose()
}
})
}
} else {
$message.error('请完善必填信息')
}
})
}

/* 关闭弹窗 */
const handleClose = () => {
emit('update:visible', false)
}

const isDateDisabled = number => {
return number < dayjs().startOf('day')
}
return {
...toRefs(data),
formRef,
ossRefs,
getModalOptions,
getFormOptions,
handleUploadStatus,
handleConfirm,
handleClose,
showStart,
showEnd,
startVal,
endVal,
startHandle,
endHandle,
isDateDisabled
}
}
})
</script>
<style scoped lang='scss'>
.map {
width: 100%;
height: 155px;
margin-bottom: 10px;
}

:deep(.n-form-item-blank) {
width: 100%;
}

:deep(.__button-1jezgko-hlmmi) {
padding-right: 0;
}
</style>

+ 50
- 0
src/views/task-manage/hook/basic.js View File

@@ -0,0 +1,50 @@
import { customRef } from 'vue'

export default function(data) {
const list = [
{
label: '平台',
value: data.platformName
},
{
label: '客户名称',
value: data.tenantName
},
{
label: '任务编号',
value: data.taskCode
},
{
label: '巡逻地点',
value: data.patrolLocation
},
{
label: '任务发起人',
value: data.sponsorName
},
{
label: '发起人联系方式',
value: data.sponsorPhone
},
{
label: '任务发起时间',
value: data.createTime
},
{
label: '期望执行时间',
value: data.taskStartTime
},
{
label: '备注',
value: data.remark
}
]
return customRef((track, trigger) => {
return {
get() {
track()
return list
}
}
})
}

+ 33
- 0
src/views/task-manage/hook/equipment.js View File

@@ -0,0 +1,33 @@
import { customRef } from 'vue'

const photographyWay = ['普通巡检', '正射影像', '倾斜摄影'] // 摄影方式

export default function(data) {
const list = [
{
label: '执飞无人机',
value: data.equipmentName
},
{
label: '摄影方式',
value: photographyWay[data.photographyWay - 1]
},
{
label: '挂载设备',
value: data.equipmentMountName
},
{
label: '盒子名称',
value: data.cloudBoxName,
isLive: 1
}
]
return customRef((track, trigger) => {
return {
get() {
track()
return list
}
}
})
}

+ 28
- 0
src/views/task-manage/hook/execution.js View File

@@ -0,0 +1,28 @@
import { customRef } from 'vue'

const photographyWay = ['普通巡检', '正射影像', '倾斜摄影'] // 摄影方式

export default function(data) {
const list = [
{
label: '执飞无人机',
value: data.equipmentName
},
{
label: '摄影方式',
value: photographyWay[data.photographyWay - 1]
},
{
label: '挂载设备',
value: data.equipmentMountName
}
]
return customRef((track, trigger) => {
return {
get() {
track()
return list
}
}
})
}

+ 41
- 0
src/views/task-manage/hook/fly.js View File

@@ -0,0 +1,41 @@
import { customRef } from 'vue'

const status = ['任务待分配', '任务已分配', '飞手已接单', '任务飞行中', '任务已完成'] // 摄影方式

export default function(data) {
const list = [
{
label: '分配人',
value: data.distributeUserName
},
{
label: '分配飞手时间',
value: data.distributeTime
},
{
label: '飞手',
value: data.flightHandName
},
{
label: '飞手接单时间',
value: data.ordersTime
},
{
label: '飞手接单状态',
value: status[(data.pilotStatus / 5) - 1]
},
{
label: '飞行开始时间',
value: data.flightStartTime,
current: 4
}
]
return customRef((track, trigger) => {
return {
get() {
track()
return list
}
}
})
}

+ 11
- 0
src/views/task-manage/hook/index.js View File

@@ -0,0 +1,11 @@
import basic from './basic.js'
import fly from './fly.js'
import equipment from './equipment.js'
import execution from './execution.js'

export {
basic,
fly,
equipment,
execution
}

+ 95
- 2
src/views/task-manage/index.vue View File

@@ -1,17 +1,110 @@
<template>
<div>
任务管理
<n-card>
<!-- 搜索 -->
<headSearch :info="search" @search="handleSearch" @reset="handleSearch" />

<!-- 表格 -->
<data-table
ref="tableRef"
:columns="columns"
:row-key="(row) => row.id"
:request="loadDataTable"
size="large"
@update:checked-row-keys="handleCheck"
>
<template #tableTitle>
<n-button type="primary" @click="handleModal"> 新建 </n-button>
</template>
</data-table>
</n-card>
</div>

<!-- 新增、编辑弹窗 -->
<UserModal v-if="modalShow" v-model:visible="modalShow" :type="modalType" :data="rowData" @reload="handleSearch" />

<!-- 详情 - 抽屉 -->
<n-drawer v-model:show="showDraw" :width="'calc(100vw - 210px)'" :placement="'right'" resizable>
<n-drawer-content closable>
<draw-comp :detail="detail" @close="showDraw = false" />
</n-drawer-content>
</n-drawer>

</template>

<script>
import { search } from './tools/search.js'
import table from './tools/table.js'
import headSearch from '@/components/Search/index.vue'
import dataTable from '@/components/DataTable/index.vue'
import DrawComp from './components/DrawComp.vue'
import UserModal from './components/UserModal.vue'
import { getTaskList } from '@/api/task/index'
import { unref, ref, toRefs, reactive, onUnmounted } from 'vue'

export default {
name: 'TaskManage',
components: { dataTable, UserModal, headSearch, DrawComp },
setup() {
const data = reactive({
...toRefs(table),
...toRefs(search)
})

const loadDataTable = async(res) => {
const _params = {
...unref(data.searchParams),
...res
}
return await getTaskList(_params)
}
// 新增
function handleModal() {
data.rowData = null
data.modalType = 'create'
data.modalShow = true
}

// 选择表格数据
const selectedIds = ref([])
function handleCheck(rowKeys) {
selectedIds.value = rowKeys
}

// 批量删除
// function deleteComplex() {
// if (selectedIds.value.length) {
// data.deleteData(selectedIds.value)
// } else {
// $message.warning('请至少选中一条数据')
// }
// }

onUnmounted(() => {
data.searchParams = null
})

return {
...toRefs(data),
loadDataTable,
handleModal,
selectedIds,
// deleteComplex,
handleCheck
}
},
methods: {

}
}

</script>

<style scoped lang='scss'>
.n-button+.n-button {
margin-left: 10px;
}

:deep(.n-drawer-body-content-wrapper) {
background-color: rgba(240, 242, 245, 0.8);
}
</style>

+ 89
- 0
src/views/task-manage/tools/drawForm.js View File

@@ -0,0 +1,89 @@
import { ref, reactive } from 'vue'
import { getTaskPilot } from '@/api/task/index.js'
import { getEquipment as equipment, getEquipmentMount as equipmentMount, getCloud } from '@/api/equipment'
import { PHOTOGRAPHY_WAY } from '@/utils/dictionary'

// 无人机列表
const equipmentList = ref([])

// 挂载设备列表
const equipmentMountList = ref([])

// 飞手列表
const pilotList = ref([])

// 挂载盒子
const cloudList = ref([])

export const form = reactive({
userForm: {
flightHandId: null,
equipmentId: null,
equipmentMountId: null,
cloudBoxId: null,
photographyWay: null
},
userRules: {
flightHandId: [{ required: true, message: '请选择飞手', trigger: 'blur' }]
},
formItem: [
{ type: 'select', key: 'flightHandId', label: '飞手', props: { options: pilotList, placeholder: '请选择飞手', clearable: true }},
{ type: 'select', key: 'equipmentId', label: '执飞无人机', props: { options: equipmentList, placeholder: '请选择执飞无人机', clearable: true }},
{ type: 'select', key: 'equipmentMountId', label: '挂载设备', props: { options: equipmentMountList, placeholder: '请选择挂载设备', clearable: true }},
{ type: 'select', key: 'cloudBoxId', label: '挂载盒子', isLive: 1, props: { options: cloudList, placeholder: '请选择盒子', clearable: true }},
{ type: 'select', key: 'photographyWay', label: '拍摄方式', props: { options: PHOTOGRAPHY_WAY, placeholder: '请选择拍摄方式', clearable: true }}
],
// 飞手接单 - 校验
userRulesOrder: {
equipmentId: [{ required: true, message: '请选择执飞无人机', trigger: 'blur' }],
photographyWay: [{ required: true, message: '请选择摄影方式', trigger: 'blur' }],
equipmentMountId: [{ required: true, message: '请选择挂载设备', trigger: 'blur' }]
},
// 飞手接单 - 表单
formItemOrder: [
{ type: 'select', key: 'equipmentId', label: '执飞无人机', props: { options: equipmentList, placeholder: '请选择执飞无人机', clearable: true }},
{ type: 'select', key: 'photographyWay', label: '摄影方式', props: { options: PHOTOGRAPHY_WAY, placeholder: '请选择拍摄方式', clearable: true }},
{ type: 'select', key: 'equipmentMountId', label: '挂载设备', props: { options: equipmentMountList, placeholder: '请选择挂载设备', clearable: true }},
{ type: 'select', key: 'cloudBoxId', label: '盒子名称', isLive: 0, props: { options: cloudList, placeholder: '请选择盒子', clearable: true }}
]
})

// 获取飞手列表
export const getPilotList = async function() {
const { data } = await getTaskPilot()
pilotList.value = data.map(it => ({
...it,
label: it.username,
value: it.id
}))
}

// 无人机列表
export const getEquipment = async function() {
const { data } = await equipment()
equipmentList.value = data.map(it => ({
...it,
label: it.manufacturer,
value: it.id
}))
}

// 挂载设备列表
export const getEquipmentMount = async function() {
const { data } = await equipmentMount()
equipmentMountList.value = data.map(it => ({
...it,
label: it.name,
value: it.id
}))
}

// 挂载盒子列表
export const getCloudMount = async function() {
const { data } = await getCloud()
cloudList.value = data.map(it => ({
...it,
label: it.boxName,
value: it.id
}))
}

+ 85
- 0
src/views/task-manage/tools/form.js View File

@@ -0,0 +1,85 @@
import { ref, reactive } from 'vue'
import { getTaskPilot } from '@/api/task/index.js'
import { getEquipment as equipment, getEquipmentMount as equipmentMount } from '@/api/equipment'
import { PHOTOGRAPHY_WAY } from '@/utils/dictionary'

// 无人机列表
const equipmentList = ref([])

// 挂载设备列表
const equipmentMountList = ref([])

// 飞手列表
const pilotList = ref()

export const form = reactive({
userForm: {
inspectionType: null,
taskName: '',
patrolLocation: '',
endValue: '',
taskStartTime: null,
flightHandId: '',
equipmentId: '',
equipmentMountId: '',
photographyWay: null,
remark: ''
},
userRules: {
inspectionType: [{ required: true, message: '请选择巡检类型', type: 'number', trigger: ['blur', 'change'] }],
taskName: [{ required: true, message: '请输入任务名称', trigger: 'blur' }],
patrolLocation: [{ required: true, message: '请输入任务起点', trigger: 'blur' }],
endValue: [{ required: true, message: '请输入任务终点', trigger: 'blur' }],
taskStartTime: [{ required: true, message: '请输入飞行时间', trigger: 'blur' }],
flightHandId: [{ required: true, message: '请选择飞手', trigger: 'blur' }]
},
formItem: [
{ type: 'select', key: 'inspectionType', label: '巡检类型', props: { options: [{ label: '自营计划', value: 1 }], maxlength: '20', placeholder: '请输入巡检类型', clearable: true }},
{ type: 'input', key: 'taskName', label: '任务名称', props: { maxlength: '20', placeholder: '请输入任务名称', clearable: true }},

{ type: 'map' },

{ type: 'inputStart', key: 'patrolLocation', label: '任务起点', props: { maxlength: '20', placeholder: '请选择任务起点', clearable: true }},
{ type: 'inputEnd', key: 'endValue', label: '任务终点', props: { maxlength: '20', placeholder: '请选择任务终点', clearable: true }},

{ type: 'datetime', key: 'taskStartTime', label: '飞行时间', props: { maxlength: '20', placeholder: '请选择飞行时间', clearable: true }},
{ type: 'select', key: 'flightHandId', label: '飞手', props: { options: pilotList, placeholder: '请选择飞手', clearable: true }},
{ type: 'select', key: 'equipmentId', label: '执飞无人机', props: { options: equipmentList, maxlength: '20', placeholder: '请选择执飞无人机', clearable: true }},
{ type: 'select', key: 'equipmentMountId', label: '挂载设备', props: { options: equipmentMountList, maxlength: '20', placeholder: '请选择挂载设备', clearable: true }},
{ type: 'select', key: 'photographyWay', label: '拍摄方式', props: { options: PHOTOGRAPHY_WAY, maxlength: '20', placeholder: '请选择拍摄方式', clearable: true }},

{ type: 'input', key: 'remark', label: '备注', props: { type: 'textarea', autosize: { maxlength: '200', minRows: 3, maxRows: 3 }}}

]
})

// 获取飞手列表
export const getPilotList = async function() {
const { data } = await getTaskPilot()
pilotList.value = data.map(it => ({
...it,
label: it.username,
value: it.id
}))
}

// 无人机列表
export const getEquipment = async function() {
const { data } = await equipment()
equipmentList.value = data.map(it => ({
...it,
label: it.manufacturer,
value: it.id
}))
}

// 挂载设备列表
export const getEquipmentMount = async function() {
const { data } = await equipmentMount()
equipmentMountList.value = data.map(it => ({
...it,
label: it.name,
value: it.id
}))
}


+ 46
- 0
src/views/task-manage/tools/search.js View File

@@ -0,0 +1,46 @@
import { ref, reactive, computed } from 'vue'
import { TASK_STATUS } from '@/utils/dictionary'

export const search = reactive({
search: [
{
label: '任务状态',
type: 'select',
key: 'status',
props: {
placeholder: '请输入任务状态',
options: TASK_STATUS
}
},
{
label: '平台名称',
key: 'platformName',
props: {
placeholder: '请输入平台名称'
}
},
{
label: '租户名称',
key: 'tenantName',
props: {
placeholder: '请输入租户名称'
}
},
{
label: '选择日期',
type: 'date',
key: 'taskStartTime',
props: {
placeholder: '请选择日期'
}
},
{
label: '任务名称',
key: 'taskName',
props: {
placeholder: '请选择任务名称'
}
}
]
})


+ 154
- 0
src/views/task-manage/tools/table.js View File

@@ -0,0 +1,154 @@
import { h, ref, reactive } from 'vue'
import TableImage from '@/components/DataTable/tools/Image.vue'
import TableTags from '@/components/DataTable/tools/Tags.vue'
import TableSwitch from '@/components/DataTable/tools/Switch.vue'
import TableAction from '@/components/DataTable/tools/Action.vue'
import { resetPassword, deleteUser, setUserStatus } from '@/api/system/user/index.js'
import { getTaskInfo, taskDel } from '@/api/task'

/* 注册table */
const tableRef = ref()
const searchParams = ref()

function handleSearch(params) {
searchParams.value = { ...params }
tableRef.value.reFetch({ searchParams })
}

/**
* @description: 获取数据及操作
* @param {*} row 单行数据
* @param {*} type 操作类型 create:创建,preview:预览,edit:编辑
* @return {*}
*/
async function getRowData(row, type) {
data.detail = await getTaskInfo(row.id) // 根据id获取详情
data.rowData = row
data.modalType = type
data.showDraw = true
}


/**
* @description: 编辑
* @return {*}
*/
function editHandle(row, type) {
getTaskInfo(row.id).then(res => {
data.rowData = res.data
data.modalType = type
data.modalShow = true
})
}

/**
* @description: 删除用户接口
* @param {Array} ids 用户id集合
* @return {*}
*/
function deleteData(ids) {
taskDel(ids).then((res) => {
if (res.code === 0) {
handleSearch()
}
}).catch(e => {
console.log(e)
})
}

const data = reactive({
tableRef,
searchParams,
rowData: {},
modalType: 'create',
modalShow: false,
showDraw: false,
detail: {},
handleSearch,
deleteData,
columns: [
{
title: '任务编码',
key: 'taskCode',
align: 'center'
},
{
title: '任务名称',
key: 'taskName',
align: 'center'
},
{
title: '租户名称',
key: 'tenantName',
align: 'center'
},
{
title: '期望执行时间',
key: 'taskStartTime',
align: 'center'
},
{
title: '巡逻地点',
key: 'patrolLocation',
align: 'center',
},
{
title: '任务状态',
key: 'status',
align: 'center',
},
{
title: '飞手姓名',
key: 'flightHandName',
align: 'center',
},
{
title: '操作',
align: 'center',
width: 150,
fixed: 'right',
render(row) {
return h(TableAction, {
actions: [
{
label: '详情',
type: 'button',
props: {
type: 'primary',
text: true,
onClick: getRowData.bind(null, row)
},
auth: 'basic_list'
},
{
label: '编辑',
type: 'button',
props: {
type: 'primary',
text: true,
onClick: editHandle.bind(null, row, 'update')
},
auth: 'basic_list'
},
{
label: '删除',
type: 'popconfirm',
auth: 'basic_list',
tip: '确定删除这条数据吗?',
props: {
onPositiveClick: deleteData.bind(null, row.id)
},
ButtonProps: {
text: true,
type: 'primary'
}
}
],
align: 'center'
})
}
}
]
})

export default data

+ 140
- 0
src/views/user-manage/components/UserModal.vue View File

@@ -0,0 +1,140 @@
<template>
<Modal
:options="getModalOptions"
:on-positive-click="handleConfirm"
:on-negative-click="handleClose"
:on-close="handleClose"
>
<template #Context>
<n-form
ref="formRef"
:model="userForm"
:rules="userRules"
:label-width="80"
label-placement="left"
require-mark-placement="left"
:disabled="disabled"
>
<template v-for="(item,index) in getFormOptions" :key="index">
<n-form-item :label="item.label" :path="item.key">
<n-input v-if="item.type === 'input'" v-model:value="userForm[item.key]" v-bind="item.props" />
<n-select v-if="item.type === 'select'" v-model:value="userForm[item.key]" v-bind="item.props" />
</n-form-item>
</template>
</n-form>
</template>
</Modal>
</template>

<script>
import { form } from '../tools/form.js'
import { defineComponent, ref, reactive, computed, toRefs } from 'vue'
import { userCreate, userUpdate } from '@/api/system/user.js'
import Modal from '@/components/Modal/index.vue'
export default defineComponent({
name: 'UserModal',
components: { Modal },
props: {
visible: {
type: Boolean,
default: false
},
type: {
type: String,
default: 'create'
},
data: {
type: Object,
default: () => {}
}
},
emits: {
'update:visible': null,
'reload': null
},
setup(props, { emit }) {
const MODAL_TYPE = {
'create': '新建盒子信息',
'preview': '盒子详情',
'update': '编辑盒子信息'
}
const { userForm, userRules } = form
const formRef = ref()
const data = reactive({
userForm: {
...userForm,
...props.data
},
userRules: {
...userRules
},
disabled: props.type === 'preview'
})

const getModalOptions = computed(() => {
return {
title: MODAL_TYPE[props.type],
show: props.visible,
negativeText: '取消',
positiveText: '确认'
}
})

const getFormOptions = computed(() => {
const formOptions = [
...form.formItem
]
if (props.type !== 'create') {
formOptions.splice(4, 1)
}
return formOptions
})

function handleConfirm() {
formRef.value?.validate((errors) => {
if (!errors) {
const params = { ...data.userForm }
if (params.id) {
/* 编辑 */
delete params['password']
userUpdate(params)
.then(res => {
if (res.code === 0) {
emit('reload')
handleClose()
}
})
} else {
/* 新增 */
userCreate(params)
.then(res => {
if (res.code === 0) {
emit('reload')
handleClose()
}
})
}
} else {
$message.error('请完善必填信息')
}
})
}

/* 关闭弹窗 */
const handleClose = () => {
emit('update:visible', false)
}

return {
...toRefs(data),
formRef,
getModalOptions,
getFormOptions,
handleConfirm,
handleClose
}
}
})
</script>
<style scoped lang='scss'>
</style>

+ 56
- 2
src/views/user-manage/index.vue View File

@@ -1,17 +1,71 @@
<template>
<div>
人员管理
<n-card>
<headSearch :info="search" @search="handleSearch" @reset="handleSearch" />
<data-table
ref="tableRef"
:columns="columns"
:request="loadDataTable"
:row-key="(row) => row.id"
size="large"
>
<template #tableTitle>
<n-button type="primary" @click="handleModal"> 新建 </n-button>
</template>
</data-table>
</n-card>
</div>
<!-- 新增、编辑弹窗 -->
<UserModal v-if="modalShow" v-model:visible="modalShow" :data="rowData" :type="modalType" @reload="handleSearch" />
</template>

<script>
import search from './tools/search.js'
import table from './tools/table.js'
import headSearch from '@/components/Search/index.vue'
import dataTable from '@/components/DataTable/index.vue'
import UserModal from './components/UserModal.vue'
import { unref, toRefs, reactive, onUnmounted } from 'vue'
import { userPage } from '@/api/system/user.js'

export default {
name: 'UserManage',
components: { dataTable, UserModal, headSearch },
setup() {
const data = reactive({
...toRefs(table),
search
})

const loadDataTable = async(res) => {
const _params = {
...unref(data.searchParams),
...res
}
return await userPage(_params)
}

// 新增
function handleModal() {
data.rowData = null
data.modalType = 'create'
data.modalShow = true
}

onUnmounted(() => {
data.searchParams = null
})

return {
...toRefs(data),
loadDataTable,
handleModal
}
}
}

</script>
<style scoped lang='scss'>
.n-button + .n-button {
margin-left: 10px;
}
</style>

+ 30
- 0
src/views/user-manage/tools/form.js View File

@@ -0,0 +1,30 @@
import { reactive } from 'vue'
import { ROLE_TYPE } from '@/utils/dictionary.js'
import { isPhone } from '@/utils/is.js'

export const form = reactive({
userForm: {
realname: null,
mobile: null,
type: null,
username: null,
password: null
},
userRules: {
realname: [{ required: true, message: '请输入姓名', trigger: 'blur' }],
mobile: [
// { required: true, message: '请输入联系电话', type: 'string', trigger: 'blur' },
{ required: true, message: '请输入正确的联系电话', pattern: /^(0|86|17951)?(13[0-9]|15[012356789]|166|17[3678]|18[0-9]|14[57])[0-9]{8}$/, trigger: 'blur' }
],
type: [{ required: true, type: 'number', message: '请选择身份', trigger: 'blur' }],
username: [{ required: true, message: '请输入帐号', trigger: 'blur' }],
password: [{ required: true, message: '请输入初始密码', trigger: 'blur' }]
},
formItem: [
{ type: 'input', key: 'realname', label: '姓名', props: { maxlength: '20', placeholder: '请输入姓名', clearable: true }},
{ type: 'input', key: 'mobile', label: '联系电话', props: { maxlength: '20', placeholder: '请输入联系电话', clearable: true }},
{ type: 'select', key: 'type', label: '身份选择', props: { options: ROLE_TYPE, clearable: true }},
{ type: 'input', key: 'username', label: '帐号', props: { maxlength: '20', placeholder: '请输入帐号', clearable: true }},
{ type: 'input', key: 'password', label: '初始密码', props: { maxlength: '20', placeholder: '请输入初始密码', clearable: true }}
]
})

+ 14
- 0
src/views/user-manage/tools/search.js View File

@@ -0,0 +1,14 @@
import { reactive } from 'vue'

const data = reactive([
{
label: '设备名称',
key: 'name',
props: {
placeholder: '请输入设备名称'
}
}
])

export default data


+ 132
- 0
src/views/user-manage/tools/table.js View File

@@ -0,0 +1,132 @@
import { h, ref, reactive } from 'vue'
import TableTags from '@/components/DataTable/tools/Tags.vue'
import TableSwitch from '@/components/DataTable/tools/Switch.vue'
import TableAction from '@/components/DataTable/tools/Action.vue'
import { ROLE_TYPE } from '@/utils/dictionary.js'
import { userUpdate } from '@/api/system/user.js'

/* 注册table */
const tableRef = ref()
const searchParams = ref()

function handleSearch(params) {
searchParams.value = { ...params }
tableRef.value.reFetch({ searchParams })
}

/**
* @description: 获取数据及操作
* @param {*} row 单行数据
* @param {*} type 操作类型 create:创建,preview:预览,edit:编辑
* @return {*}
*/
function getRowData(row, type) {
data.rowData = row
data.modalType = type
data.modalShow = true
}

// 设置状态
function changeStatus(row) {
userUpdate({ id: row.data.id, status: row.value }).then(res => {
if (res.code === 0) {
handleSearch()
}
}).catch(e => {
console.log(e)
})
}

const data = reactive({
tableRef,
searchParams,
rowData: {},
modalType: 'create',
modalShow: false,
configModalShow: false,
handleSearch,
columns: [
{
title: '序号',
key: 'key',
render: (_, index) => {
return `${index + 1}`
},
align: 'center',
width: 100
},
{
title: '角色',
key: 'type',
align: 'center',
render(row) {
return h(TableTags, {
data: row.type,
filters: ROLE_TYPE
})
}
},
{
title: '姓名',
key: 'realname',
align: 'center'
},
{
title: '手机号',
key: 'mobile',
align: 'center',
Minwidth: 160
},
{
title: '任务次数',
key: 'taskCount',
align: 'center',
Minwidth: 160
},
{
title: '上次登录时间',
key: 'loginTime',
align: 'center'
},
{
title: '状态',
key: 'status',
align: 'center',
width: 100,
render(row) {
return h(TableSwitch, {
data: { id: row.id, status: row.status },
rowKey: 'status',
checkedValue: 1,
uncheckedValue: 2,
onChange: changeStatus.bind(row)
})
}
},
{
title: '操作',
align: 'center',
width: 150,
fixed: 'right',
render(row) {
return h(TableAction, {
actions: [
{
label: '编辑',
type: 'button',
props: {
type: 'primary',
text: true,
onClick: getRowData.bind(null, row, 'update')
},
auth: 'basic_list'
}
],
align: 'center'
})
}
}
]
})

export default data

Loading…
Cancel
Save