資產管理: 部門、IOT、MQTT欄位 | 系統監控: 系統小卡詳細內容 TABLE不分頁、顯示即時資料、NO Data改成0 | 運維管理: 維運、保養修正搜尋功能、廠商資料隱藏搜尋

This commit is contained in:
koko 2025-01-03 18:06:46 +08:00
parent d19c7fd240
commit 9e5ff1544c
28 changed files with 1516 additions and 146 deletions

View File

@ -1,3 +1,4 @@
VITE_API_BASEURL = "https://ibms-cvilux-api.production.mjmtech.com.tw"
VITE_FILE_API_BASEURL = "https://cgems.cvilux-group.com:8088"
VITE_MQTT_BASEURL = "ws://192.168.0.217:8083/mqtt"
VITE_FORGE_BASEURL = "https://cgems.cvilux-group.com:8088/dist"

View File

@ -1,3 +1,4 @@
VITE_API_BASEURL = "https://ibms-cvilux-api.production.mjmtech.com.tw"
VITE_FILE_API_BASEURL = "https://cgems.cvilux-group.com:8088"
VITE_MQTT_BASEURL = "wss://192.168.0.217:8084/mqtt"
VITE_FORGE_BASEURL = "https://cgems.cvilux-group.com:8088/dist"

435
package-lock.json generated
View File

@ -22,12 +22,14 @@
"echarts": "^5.4.3",
"flag-icons": "^7.2.3",
"jquery-ui": "^1.14.1",
"mqtt": "^5.10.3",
"pinia": "^2.1.7",
"requirejs": "^2.3.6",
"tailwind-merge": "^2.2.1",
"vue": "^3.3.4",
"vue-i18n": "^10.0.4",
"vue-router": "^4.2.5",
"vuedraggable": "^4.1.0",
"yup": "^1.4.0",
"yup-phone-lite": "^2.0.1"
},
@ -102,9 +104,10 @@
}
},
"node_modules/@babel/runtime": {
"version": "7.23.9",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.9.tgz",
"integrity": "sha512-0CX6F+BI2s9dkUqr08KFrAIZgNFj75rdBU/DjCyYLIaV/quFjkk6T+EJ2LkZHyZTbEV4L5p97mNkUsHl2wLFAw==",
"version": "7.26.0",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.0.tgz",
"integrity": "sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==",
"license": "MIT",
"dependencies": {
"regenerator-runtime": "^0.14.0"
},
@ -764,11 +767,26 @@
"version": "20.10.4",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.4.tgz",
"integrity": "sha512-D08YG6rr8X90YB56tSIuBaddy/UXAA9RKJoFvrsnogAum/0pmjkgi4+2nx96A330FmioegBWmEYQ+syqCFaveg==",
"dev": true,
"dependencies": {
"undici-types": "~5.26.4"
}
},
"node_modules/@types/readable-stream": {
"version": "4.0.18",
"resolved": "https://registry.npmjs.org/@types/readable-stream/-/readable-stream-4.0.18.tgz",
"integrity": "sha512-21jK/1j+Wg+7jVw1xnSwy/2Q1VgVjWuFssbYGTREPUBeZ+rqVFl2udq0IkxzPC0ZhOzVceUbyIACFZKLqKEBlA==",
"license": "MIT",
"dependencies": {
"@types/node": "*",
"safe-buffer": "~5.1.1"
}
},
"node_modules/@types/readable-stream/node_modules/safe-buffer": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
"license": "MIT"
},
"node_modules/@types/svgo": {
"version": "2.6.4",
"resolved": "https://registry.npmjs.org/@types/svgo/-/svgo-2.6.4.tgz",
@ -778,6 +796,15 @@
"@types/node": "*"
}
},
"node_modules/@types/ws": {
"version": "8.5.13",
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.13.tgz",
"integrity": "sha512-osM/gWBTPKgHV8XkTunnegTRIsvF6owmf5w+JtAfOw472dptdm0dlGv4xCt6GwQRcC2XVOvvRE/0bAoQcL2QkA==",
"license": "MIT",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@vitejs/plugin-vue": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-4.5.0.tgz",
@ -1087,6 +1114,18 @@
"dev": true,
"peer": true
},
"node_modules/abort-controller": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
"integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==",
"license": "MIT",
"dependencies": {
"event-target-shim": "^5.0.0"
},
"engines": {
"node": ">=6.5"
}
},
"node_modules/acorn": {
"version": "8.11.2",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz",
@ -1370,6 +1409,26 @@
"node": ">=0.10.0"
}
},
"node_modules/base64-js": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT"
},
"node_modules/big.js": {
"version": "5.2.2",
"resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz",
@ -1388,6 +1447,34 @@
"node": ">=8"
}
},
"node_modules/bl": {
"version": "6.0.16",
"resolved": "https://registry.npmjs.org/bl/-/bl-6.0.16.tgz",
"integrity": "sha512-V/kz+z2Mx5/6qDfRCilmrukUXcXuCoXKg3/3hDvzKKoSUx8CJKudfIoT29XZc3UE9xBvxs5qictiHdprwtteEg==",
"license": "MIT",
"dependencies": {
"@types/readable-stream": "^4.0.0",
"buffer": "^6.0.3",
"inherits": "^2.0.4",
"readable-stream": "^4.2.0"
}
},
"node_modules/bl/node_modules/readable-stream": {
"version": "4.5.2",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz",
"integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==",
"license": "MIT",
"dependencies": {
"abort-controller": "^3.0.0",
"buffer": "^6.0.3",
"events": "^3.3.0",
"process": "^0.11.10",
"string_decoder": "^1.3.0"
},
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
}
},
"node_modules/bluebird": {
"version": "3.7.2",
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz",
@ -1454,12 +1541,34 @@
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
}
},
"node_modules/buffer": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
"integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT",
"dependencies": {
"base64-js": "^1.3.1",
"ieee754": "^1.2.1"
}
},
"node_modules/buffer-from": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
"dev": true,
"peer": true
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="
},
"node_modules/cache-base": {
"version": "1.0.1",
@ -1684,6 +1793,12 @@
"node": ">= 6"
}
},
"node_modules/commist": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/commist/-/commist-3.2.0.tgz",
"integrity": "sha512-4PIMoPniho+LqXmpS5d3NuGYncG6XWlkBSVGiWycL22dd42OYdUGil2CWuzklaJoNxyxUSpO4MKIBU94viWNAw==",
"license": "MIT"
},
"node_modules/component-emitter": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz",
@ -1704,6 +1819,21 @@
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
"dev": true
},
"node_modules/concat-stream": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz",
"integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==",
"engines": [
"node >= 6.0"
],
"license": "MIT",
"dependencies": {
"buffer-from": "^1.0.0",
"inherits": "^2.0.3",
"readable-stream": "^3.0.2",
"typedarray": "^0.0.6"
}
},
"node_modules/copy-descriptor": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz",
@ -1926,7 +2056,6 @@
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
"dev": true,
"dependencies": {
"ms": "2.1.2"
},
@ -2213,12 +2342,19 @@
"node": ">= 0.6"
}
},
"node_modules/event-target-shim": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz",
"integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/events": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
"integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==",
"dev": true,
"peer": true,
"engines": {
"node": ">=0.8.x"
}
@ -2354,6 +2490,25 @@
"dev": true,
"peer": true
},
"node_modules/fast-unique-numbers": {
"version": "8.0.13",
"resolved": "https://registry.npmjs.org/fast-unique-numbers/-/fast-unique-numbers-8.0.13.tgz",
"integrity": "sha512-7OnTFAVPefgw2eBJ1xj2PGGR9FwYzSUso9decayHgCDX4sJkHLdcsYTytTg+tYv+wKF3U8gJuSBz2jJpQV4u/g==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.23.8",
"tslib": "^2.6.2"
},
"engines": {
"node": ">=16.1.0"
}
},
"node_modules/fast-unique-numbers/node_modules/tslib": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
"license": "0BSD"
},
"node_modules/fastparse": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.2.tgz",
@ -2665,6 +2820,12 @@
"he": "bin/he"
}
},
"node_modules/help-me": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/help-me/-/help-me-5.0.0.tgz",
"integrity": "sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==",
"license": "MIT"
},
"node_modules/htmlparser2": {
"version": "3.10.1",
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz",
@ -2679,6 +2840,26 @@
"readable-stream": "^3.1.1"
}
},
"node_modules/ieee754": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "BSD-3-Clause"
},
"node_modules/image-size": {
"version": "0.5.5",
"resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz",
@ -2710,8 +2891,7 @@
"node_modules/inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"dev": true
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
},
"node_modules/is-accessor-descriptor": {
"version": "1.0.1",
@ -2906,6 +3086,16 @@
"integrity": "sha512-pZe//GGmwJndub7ZghVHz7vjb2LgC1m8B07Au3eYqeqv9emhESByMXxaEgkUkEqJe87oBbSniGYoQNIBklc7IQ==",
"dev": true
},
"node_modules/js-sdsl": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.3.0.tgz",
"integrity": "sha512-mifzlm2+5nZ+lEcLJMoBK0/IH/bDg8XnJfd/Wq6IP+xoCjLZsTOnV2QpxlVbX9bMnkl5PdEjNtBJ9Cj1NjifhQ==",
"license": "MIT",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/js-sdsl"
}
},
"node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@ -3035,6 +3225,12 @@
"loose-envify": "cli.js"
}
},
"node_modules/lru-cache": {
"version": "10.4.3",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
"integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
"license": "ISC"
},
"node_modules/magic-string": {
"version": "0.30.5",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.5.tgz",
@ -3149,7 +3345,6 @@
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
"dev": true,
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
@ -3200,11 +3395,69 @@
"node": ">=0.10.0"
}
},
"node_modules/mqtt": {
"version": "5.10.3",
"resolved": "https://registry.npmjs.org/mqtt/-/mqtt-5.10.3.tgz",
"integrity": "sha512-hA/6YrUS4fywhBGCjH/XXUuLeueJiPqruVVWjK2A24Ma4KcWfZ/x8x07aoesBV+HXDWBC08tbT4IWfSXNW0Jtw==",
"license": "MIT",
"dependencies": {
"@types/readable-stream": "^4.0.5",
"@types/ws": "^8.5.9",
"commist": "^3.2.0",
"concat-stream": "^2.0.0",
"debug": "^4.3.4",
"help-me": "^5.0.0",
"lru-cache": "^10.0.1",
"minimist": "^1.2.8",
"mqtt-packet": "^9.0.1",
"number-allocator": "^1.0.14",
"readable-stream": "^4.4.2",
"reinterval": "^1.1.0",
"rfdc": "^1.3.0",
"split2": "^4.2.0",
"worker-timers": "^7.1.4",
"ws": "^8.17.1"
},
"bin": {
"mqtt": "build/bin/mqtt.js",
"mqtt_pub": "build/bin/pub.js",
"mqtt_sub": "build/bin/sub.js"
},
"engines": {
"node": ">=16.0.0"
}
},
"node_modules/mqtt-packet": {
"version": "9.0.1",
"resolved": "https://registry.npmjs.org/mqtt-packet/-/mqtt-packet-9.0.1.tgz",
"integrity": "sha512-koZF1V/X2RZUI6uD9wN5OK1JxxcG1ofAR4H3LjCw1FkeKzruZQ26aAA6v2m1lZyWONZIR5wMMJFrZJDRNzbiQw==",
"license": "MIT",
"dependencies": {
"bl": "^6.0.8",
"debug": "^4.3.4",
"process-nextick-args": "^2.0.1"
}
},
"node_modules/mqtt/node_modules/readable-stream": {
"version": "4.5.2",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz",
"integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==",
"license": "MIT",
"dependencies": {
"abort-controller": "^3.0.0",
"buffer": "^6.0.3",
"events": "^3.3.0",
"process": "^0.11.10",
"string_decoder": "^1.3.0"
},
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
}
},
"node_modules/ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
"dev": true
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
"node_modules/mz": {
"version": "2.7.0",
@ -3371,6 +3624,16 @@
"url": "https://github.com/fb55/nth-check?sponsor=1"
}
},
"node_modules/number-allocator": {
"version": "1.0.14",
"resolved": "https://registry.npmjs.org/number-allocator/-/number-allocator-1.0.14.tgz",
"integrity": "sha512-OrL44UTVAvkKdOdRQZIJpLkAdjXGTRda052sN4sO77bKEzYYqWKMBjQvrJFzqygI99gL6Z4u2xctPW1tB8ErvA==",
"license": "MIT",
"dependencies": {
"debug": "^4.3.1",
"js-sdsl": "4.3.0"
}
},
"node_modules/object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
@ -3822,6 +4085,21 @@
"posthtml-render": "^1.0.6"
}
},
"node_modules/process": {
"version": "0.11.10",
"resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
"integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==",
"license": "MIT",
"engines": {
"node": ">= 0.6.0"
}
},
"node_modules/process-nextick-args": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
"license": "MIT"
},
"node_modules/property-expr": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/property-expr/-/property-expr-2.0.6.tgz",
@ -3898,7 +4176,6 @@
"version": "3.6.2",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
"dev": true,
"dependencies": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
@ -3984,6 +4261,12 @@
"node": ">=0.10.0"
}
},
"node_modules/reinterval": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/reinterval/-/reinterval-1.1.0.tgz",
"integrity": "sha512-QIRet3SYrGp0HUHO88jVskiG6seqUGC5iAG7AwI/BV4ypGcuqk9Du6YQBUOUqm9c8pw1eyLoIaONifRua1lsEQ==",
"license": "MIT"
},
"node_modules/repeat-element": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.4.tgz",
@ -4062,6 +4345,12 @@
"node": ">=0.10.0"
}
},
"node_modules/rfdc": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz",
"integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==",
"license": "MIT"
},
"node_modules/rollup": {
"version": "3.29.4",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.4.tgz",
@ -4105,7 +4394,6 @@
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
"dev": true,
"funding": [
{
"type": "github",
@ -4377,6 +4665,12 @@
"node": ">=0.10.0"
}
},
"node_modules/sortablejs": {
"version": "1.14.0",
"resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.14.0.tgz",
"integrity": "sha512-pBXvQCs5/33fdN1/39pPL0NZF20LeRbLQ5jtnheIPN9JQAaufGjKdWduZn4U7wCtVuzKhmRkI0DFYHYRbB2H1w==",
"license": "MIT"
},
"node_modules/source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
@ -4484,6 +4778,15 @@
"node": ">=0.10.0"
}
},
"node_modules/split2": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz",
"integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==",
"license": "ISC",
"engines": {
"node": ">= 10.x"
}
},
"node_modules/stable": {
"version": "0.1.8",
"resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz",
@ -4542,7 +4845,6 @@
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
"dev": true,
"dependencies": {
"safe-buffer": "~5.2.0"
}
@ -5119,11 +5421,16 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/typedarray": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
"integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==",
"license": "MIT"
},
"node_modules/undici-types": {
"version": "5.26.5",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
"dev": true
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="
},
"node_modules/union-value": {
"version": "1.0.1",
@ -5338,8 +5645,7 @@
"node_modules/util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
"dev": true
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
},
"node_modules/vary": {
"version": "1.1.2",
@ -5492,6 +5798,18 @@
"vue": "^3.0.0"
}
},
"node_modules/vuedraggable": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/vuedraggable/-/vuedraggable-4.1.0.tgz",
"integrity": "sha512-FU5HCWBmsf20GpP3eudURW3WdWTKIbEIQxh9/8GE806hydR9qZqRRxRE3RjqX7PkuLuMQG/A7n3cfj9rCEchww==",
"license": "MIT",
"dependencies": {
"sortablejs": "1.14.0"
},
"peerDependencies": {
"vue": "^3.0.1"
}
},
"node_modules/warning": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz",
@ -5577,12 +5895,85 @@
"integrity": "sha512-poXpCylU7ExuvZK8z+On3kX+S8o/2dQ/SVYueKA0D4WEMXROXgY8Ez50/bQEUmvoSMMrWcrJqCHuhAbsiwg7Dg==",
"dev": true
},
"node_modules/worker-timers": {
"version": "7.1.8",
"resolved": "https://registry.npmjs.org/worker-timers/-/worker-timers-7.1.8.tgz",
"integrity": "sha512-R54psRKYVLuzff7c1OTFcq/4Hue5Vlz4bFtNEIarpSiCYhpifHU3aIQI29S84o1j87ePCYqbmEJPqwBTf+3sfw==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.24.5",
"tslib": "^2.6.2",
"worker-timers-broker": "^6.1.8",
"worker-timers-worker": "^7.0.71"
}
},
"node_modules/worker-timers-broker": {
"version": "6.1.8",
"resolved": "https://registry.npmjs.org/worker-timers-broker/-/worker-timers-broker-6.1.8.tgz",
"integrity": "sha512-FUCJu9jlK3A8WqLTKXM9E6kAmI/dR1vAJ8dHYLMisLNB/n3GuaFIjJ7pn16ZcD1zCOf7P6H62lWIEBi+yz/zQQ==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.24.5",
"fast-unique-numbers": "^8.0.13",
"tslib": "^2.6.2",
"worker-timers-worker": "^7.0.71"
}
},
"node_modules/worker-timers-broker/node_modules/tslib": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
"license": "0BSD"
},
"node_modules/worker-timers-worker": {
"version": "7.0.71",
"resolved": "https://registry.npmjs.org/worker-timers-worker/-/worker-timers-worker-7.0.71.tgz",
"integrity": "sha512-ks/5YKwZsto1c2vmljroppOKCivB/ma97g9y77MAAz2TBBjPPgpoOiS1qYQKIgvGTr2QYPT3XhJWIB6Rj2MVPQ==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.24.5",
"tslib": "^2.6.2"
}
},
"node_modules/worker-timers-worker/node_modules/tslib": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
"license": "0BSD"
},
"node_modules/worker-timers/node_modules/tslib": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
"license": "0BSD"
},
"node_modules/wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
"dev": true
},
"node_modules/ws": {
"version": "8.18.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz",
"integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==",
"license": "MIT",
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": ">=5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
},
"node_modules/yaml": {
"version": "2.3.4",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.4.tgz",

View File

@ -23,12 +23,14 @@
"echarts": "^5.4.3",
"flag-icons": "^7.2.3",
"jquery-ui": "^1.14.1",
"mqtt": "^5.10.3",
"pinia": "^2.1.7",
"requirejs": "^2.3.6",
"tailwind-merge": "^2.2.1",
"vue": "^3.3.4",
"vue-i18n": "^10.0.4",
"vue-router": "^4.2.5",
"vuedraggable": "^4.1.0",
"yup": "^1.4.0",
"yup-phone-lite": "^2.0.1"
},

View File

@ -18,3 +18,9 @@ export const DELETE_ASSET_FLOOR_API = `/AssetManage/DeleteFloor`;
export const GET_ASSET_IOT_LIST_API = `/AssetManage/GetIOTList`;
export const GET_ASSET_SUB_POINT_API = `/AssetManage/GetSubPoint`;
export const GET_ASSET_IOT_SCHEMA_API = `/AssetManage/GetResponseSchema`;
export const GET_ASSET_DEPARTMENT_API = `/AssetManage/GetDepartment`;
export const POST_ASSET_DEPARTMENT_API = `/AssetManage/SaveDepartment`;
export const DELETE_ASSET_DEPARTMENT_API = `/AssetManage/DeleteDepartment`;

View File

@ -14,6 +14,10 @@ import {
DELETE_ASSET_ITEM_API,
POST_ASSET_SINGLE_API,
GET_ASSET_SUB_POINT_API,
GET_ASSET_IOT_SCHEMA_API,
GET_ASSET_DEPARTMENT_API,
POST_ASSET_DEPARTMENT_API,
DELETE_ASSET_DEPARTMENT_API,
} from "./api";
import instance from "@/util/request";
import apihandler from "@/util/apihandler";
@ -124,16 +128,15 @@ export const postAssetSingle = async (data) => {
});
} else {
value.forEach((element, index) => {
formData.append(`sub_device[${index}].device_number`, element.device_number);
formData.append(
`sub_device[${index}].device_number`,
element.device_number
);
formData.append(`sub_device[${index}].points`, element.points);
});
}
} else {
if (key === "device_number" && value === "") {
formData.append(key, "0");
} else {
formData.append(key, value);
}
formData.append(key, value);
}
}
@ -199,3 +202,42 @@ export const getAssetSubPoint = async (sub_system_tag) => {
code: res.code,
});
};
export const getIOTSchema = async (sub_system_tag, points) => {
const res = await instance.post(GET_ASSET_IOT_SCHEMA_API, {});
return apihandler(res.code, res.data, {
msg: res.msg,
code: res.code,
});
};
export const getDepartmentList = async () => {
const res = await instance.post(GET_ASSET_DEPARTMENT_API, {});
return apihandler(res.code, res.data, {
msg: res.msg,
code: res.code,
});
};
export const postDepartmentList = async ({ name, id }) => {
const res = await instance.post(POST_ASSET_DEPARTMENT_API, {
name,
id,
});
return apihandler(res.code, res.data, {
msg: res.msg,
code: res.code,
});
};
export const deleteDepartmentItem = async (id) => {
const res = await instance.post(DELETE_ASSET_DEPARTMENT_API, { id });
return apihandler(res.code, res.data, {
msg: res.msg,
code: res.code,
});
};

View File

@ -0,0 +1,436 @@
<script setup>
import { twMerge } from "tailwind-merge";
import { computed, defineProps, provide, ref, watch } from "vue";
import Pagination from "@/components/customUI/Pagination.vue";
import Checkbox from "@/components/customUI/Checkbox.vue";
import draggable from "vuedraggable";
import dayjs from "dayjs";
import { useI18n } from "vue-i18n";
const { t } = useI18n();
/*
column={
title,key,class, width, filter:Boolean, sort:Boolean
}
*/
const props = defineProps({
columns: Array,
dataSource: Array,
rowKey: String,
withStyle: {
type: Boolean,
default: true,
},
withDraggable: {
type: Boolean,
default: false,
},
pagination: { type: Boolean, default: true } || {
pageSize: Number,
totalPages: Number,
totalItems: Number,
},
loading: Boolean,
});
const currentDataSource = ref([]);
const dataSourceStorage = ref([]);
const isDraggable = ref(props.withDraggable);
watch(
() => props.dataSource,
(newValue) => {
dataSourceStorage.value = newValue;
filterItems.value = Object.fromEntries(
props.columns.map((c, i) => [
c.key,
[...new Set(newValue.map((d) => d[c.key]))].map((name) => ({
name,
selected: false,
})),
])
);
}
);
const updateDataSource = (data) => {
console.log("update", data);
currentDataSource.value = data;
};
provide("current_table_data", {
updateDataSource,
});
const sortRule = ref({});
const filterColumn = ref({});
const filterItems = ref({});
const selectedFilterItem = ref({});
const toggleFilterModal = (key) => {
let newFilter = Object.assign(filterColumn.value);
for (let oKey in newFilter) {
newFilter[oKey] = key === oKey && !newFilter[key];
}
filterColumn.value = newFilter;
};
watch(
() => props.columns,
(newValue) => {
sortRule.value = Object.fromEntries(newValue.map((c) => [c.key, 0]));
filterColumn.value = Object.fromEntries(
newValue.map((c, i) => [c.key, false])
);
selectedFilterItem.value = Object.fromEntries(
newValue.map((c, i) => [c.key, []])
);
},
{
immediate: true,
}
);
/*
0:取消
1:ascending
2:descending
*/
const toggleSortRule = (key) => {
let newSort = Object.assign(sortRule.value);
for (let oKey in newSort) {
newSort[oKey] = key === oKey ? newSort[key] : 0;
}
sortRule.value = newSort;
};
const sort = (column) => {
toggleSortRule(column);
const cantSort = ["object", "boolean"];
console.log(props.dataSource?.[0][column]);
if (cantSort.includes(typeof props.dataSource?.[0][column])) return;
// ->
const newArray = Object.assign(props.dataSource, []).sort((a, b) => {
// if (column === "timestamp") {
// return dayjs(a[column]).valueOf() - dayjs(b[column]).valueOf();
// }
if (typeof a[column] === "number") return a[column] - b[column];
else if (typeof a[column] === "string") {
console.log(a[column], b[column], a[column].localeCompare(b[column]));
return a[column].localeCompare(b[column]);
}
// return parseInt(a[column]) - parseInt(b[column]);
});
if (sortRule.value[column] === 0) {
sortRule.value[column] = 1;
dataSourceStorage.value = newArray;
} else if (sortRule.value[column] === 1) {
sortRule.value[column] = 2;
dataSourceStorage.value = newArray.reverse();
} else if (sortRule.value[column] === 2) {
sortRule.value[column] = 0;
dataSourceStorage.value = props.dataSource;
}
};
const form = ref(null);
const onFilter = (key, reset = false) => {
const formData = new FormData(form.value);
reset && formData.delete(key);
for (let [name, value] of formData) {
console.log(name, value);
}
selectedFilterItem.value[key] = formData.getAll(key);
toggleFilterModal(key);
};
watch(
selectedFilterItem,
(newVal) => {
let newData = Object.assign(props.dataSource);
for (let key in newVal) {
if (newVal[key].length > 0) {
newData = newData.filter((d) => newVal[key].includes(d[key]));
}
}
dataSourceStorage.value = newData;
},
{
deep: true,
}
);
</script>
<template>
<div :class="withStyle ? 'content-box' : 'py-5'">
<div class="content-decoration">
<button
v-if="withDraggable"
class="btn btn-sm mb-2"
@click.stop.prevent="isDraggable = !isDraggable"
>
<font-awesome-icon :icon="['fas', 'stream']" />
{{ isDraggable ? "開始排序" : "完成排序" }}
</button>
<slot name="beforeTable"></slot>
<form ref="form" class="overflow-x-auto">
<table
:class="
twMerge(
withStyle ? 'table' : 'table border',
currentDataSource.length === 0 ? 'h-28' : ''
)
"
>
<!-- head -->
<thead>
<tr>
<th
v-for="column in columns"
:key="column.key"
:class="`${column.class ? column.class : ''}`"
:style="{
width: `${
column.width
? typeof column.width === 'string'
? column.width
: column.width + 'px'
: 'auto'
}`,
}"
>
<span class="flex justify-center">
{{ column.title }}
<div
v-if="column.sort"
class="flex flex-col justify-center w-3 mx-2 relative"
@click="() => sort(column.key)"
>
<font-awesome-icon
:icon="['fas', 'sort-up']"
:class="
twMerge(
'absolute top-0',
sortRule[column.key] === 1 ? 'text-success' : ''
)
"
size="lg"
/>
<font-awesome-icon
:icon="['fas', 'sort-down']"
:class="
twMerge(
'absolute bottom-1',
sortRule[column.key] === 2 ? 'text-success' : ''
)
"
size="lg"
/>
</div>
<div class="ml-2 relative" v-if="column.filter">
<font-awesome-icon
:icon="['fas', 'filter']"
:class="
twMerge(
filterColumn[column.key] ||
selectedFilterItem[column.key].length > 0
? 'text-success'
: ''
)
"
@click="() => toggleFilterModal(column.key)"
/>
<div
class="absolute top-full -left-1/2 z-50"
v-if="filterColumn[column.key]"
>
<div class="card min-w-max bg-body shadow-xl px-10 py-5">
<Checkbox
v-for="item in filterItems[column.key]"
:title="item.name"
:value="item.name"
:key="item.name"
:name="column.key"
:checked="
selectedFilterItem[column.key].includes(item.name)
"
className="justify-start"
/>
<div class="card-actions mt-4 justify-end">
<input
type="reset"
class="btn btn-sm text-white btn-error"
:value="t('button.reset')"
@click="() => onFilter(column.key, true)"
/>
<button
class="btn btn-sm btn-success"
@click.stop.prevent="() => onFilter(column.key)"
>
{{ $t("button.submit") }}
</button>
</div>
</div>
</div>
</div>
</span>
</th>
</tr>
</thead>
<tbody v-if="isDraggable">
<tr v-if="loading">
<td :colspan="columns.length">
<Loading />
</td>
</tr>
<tr v-else-if="currentDataSource.length == 0">
<td :colspan="columns.length">{{ $t("table.no_data") }}</td>
</tr>
<template v-else :sort="sortRule">
<tr
v-for="(data, index) in currentDataSource"
:key="data.key || data[rowKey]"
>
<template
v-for="column in columns"
:key="`${data.key || data[rowKey]}_${column.key}`"
>
<td
:class="column.class"
:style="{
width: `${
column.width
? typeof column.width === 'string'
? column.width
: column.width + 'px'
: 'auto'
}`,
}"
>
<slot
name="bodyCell"
v-bind="{ record: data, column, index }"
>
{{ data[column.key] }}</slot
>
</td>
</template>
</tr>
</template>
</tbody>
<draggable
tag="tbody"
:list="dataSourceStorage"
item-key="rowKey"
v-else
>
<template #item="{ element, index }">
<tr :key="element[rowKey] || element.key" >
<template
v-for="column in columns"
:key="`${element[rowKey] || element.key}_${column.key}`"
>
<td
:class="column.class"
:style="{
width: `
${
column.width
? typeof column.width === 'string'
? column.width
: column.width + 'px'
: 'auto'
}`,
}"
>
<slot
name="bodyCell"
v-bind="{ record: element, column, index }"
>
{{ element[column.key] }}</slot
>
</td>
</template>
</tr>
</template>
</draggable>
</table>
</form>
<slot name="afterTable"></slot>
<Pagination
:class="twMerge(!isDraggable ? 'hidden' : 'flex')"
:pagination="pagination"
:dataSource="dataSourceStorage"
:sort="sortRule"
/>
</div>
<div class="content-decoration2"></div>
</div>
</template>
<style lang="css" scoped>
/**資料框**/
.content-box {
@apply border border-info p-1 relative mb-4 bg-transparent;
}
.content-box .table {
@apply rounded-none;
}
.table th {
@apply bg-cyan-600 bg-opacity-30 border-r border-b border-white text-lg font-semibold text-white text-center px-2 py-3;
}
.table td {
@apply border-r border-b border-white text-lg font-semibold text-white text-center px-2 py-3;
}
.table tr td:last-child,
.table tr:first-child th:last-child {
border-right: 0;
}
/* .table tr:last-child td {
border-bottom: v-bind("withStyle ? '0px': '1px'");
} */
/**資料框裝飾**/
.content-box::before {
@apply absolute top-1 left-1 h-5 w-5 bg-no-repeat z-10 bg-[url('../../assets/img/table/content-box-background01.svg')] bg-center;
content: "";
}
.content-box::after {
@apply absolute bottom-1 right-1 h-5 w-5 bg-no-repeat z-10 bg-[url('../../assets/img/table/content-box-background05.svg')] bg-center;
content: "";
}
.content-box .content-decoration {
@apply bg-normal px-8 py-4;
}
/* .content-box .content-decoration::before {
@apply absolute -top-3 -right-[10px] h-8 w-8 bg-no-repeat z-10 bg-[url('../../assets/img/table/content-box-background02.svg')] bg-center;
content: "";
} */
.content-box .content-decoration2::before {
@apply absolute -bottom-1 -left-8 h-14 w-14 bg-no-repeat z-10 bg-[url('../../assets/img/table/content-box-background03.svg')] bg-center;
content: "";
}
/* .content-box .content-decoration2::after {
content: "";
background: url(../../assets/img/table/content-box-background04.svg) center
center;
position: absolute;
right: -27px;
bottom: -7px;
height: 65px;
width: 50px;
background-repeat: no-repeat;
z-index: 2;
} */
</style>

View File

@ -30,6 +30,10 @@ const props = defineProps({
const currentDataSource = ref([]);
const dataSourceStorage = ref([]);
const tableDataSource = computed(() =>
props.pagination ? currentDataSource.value : dataSourceStorage.value
);
watch(
() => props.dataSource,
(newValue) => {
@ -271,12 +275,12 @@ watch(
<Loading />
</td>
</tr>
<tr v-else-if="currentDataSource.length == 0">
<tr v-else-if="tableDataSource.length == 0">
<td :colspan="columns.length">{{ $t("table.no_data") }}</td>
</tr>
<template v-else :sort="sortRule">
<tr
v-for="(data, index) in currentDataSource"
v-for="(data, index) in tableDataSource"
:key="data.key || data[rowKey]"
>
<template
@ -310,6 +314,7 @@ watch(
</form>
<slot name="afterTable"></slot>
<Pagination
v-if="pagination"
:pagination="pagination"
:dataSource="dataSourceStorage"
:sort="sortRule"

View File

@ -24,7 +24,10 @@
"yesterday_electricity_consumption": "昨天用电量",
"this_last_week": "本周/上周",
"thisweek_electricity_consumption": "本周用电量",
"lastweek_electricity_consumption": "上周用电量"
"lastweek_electricity_consumption": "上周用电量",
"one_hour": "1小时",
"four_hour": "4小时",
"eight_hour": "8小时"
},
"history": {
"title": "历史资料",
@ -170,7 +173,7 @@
"phone": "电话",
"email": "email",
"created_at": "建立日期",
"maintainance": "保养",
"maintenance": "保养",
"repair": "维修",
"company_info": "厂商资料",
"repair_item": "维修项目",
@ -244,7 +247,9 @@
"associated_device": "关联设备",
"choose": "选择",
"index": "编号",
"floor_plan": "平面图"
"floor_plan": "平面图",
"department": "部门",
"department_name": "部门名称"
},
"accountManagement": {
"account_title": "帐号管理",

View File

@ -24,7 +24,10 @@
"yesterday_electricity_consumption": "昨天用電量",
"this_last_week": "本週/上週",
"thisweek_electricity_consumption": "本周用電量",
"lastweek_electricity_consumption": "上週用電量"
"lastweek_electricity_consumption": "上週用電量",
"one_hour": "1小時",
"four_hour": "4小時",
"eight_hour": "8小時"
},
"history": {
"title": "歷史資料",
@ -170,7 +173,7 @@
"phone": "電話",
"email": "email",
"created_at": "建立日期",
"maintainance": "保養",
"maintenance": "保養",
"repair": "維修",
"company_info": "廠商資料",
"repair_item": "維修項目",
@ -244,7 +247,9 @@
"associated_device": "關聯設備",
"choose": "選擇",
"index": "編號",
"floor_plan": "平面圖"
"floor_plan": "平面圖",
"department": "部門",
"department_name": "部門名稱"
},
"accountManagement": {
"account_title": "帳號管理",

View File

@ -42,7 +42,10 @@
"yesterday_electricity_consumption": "Yesterdays electricity consumption",
"this_last_week": "This Week's / Last Week's",
"thisweek_electricity_consumption": "This weeks electricity consumption",
"lastweek_electricity_consumption": "Last weeks electricity consumption"
"lastweek_electricity_consumption": "Last weeks electricity consumption",
"one_hour": "1 hour",
"four_hour": "4 hour",
"eight_hour": "8 hour"
},
"system": {
"status": "Status",
@ -147,9 +150,9 @@
"notify_items": "Notification Items",
"notify_list": "Notification List",
"choose": "Choose",
"day_time":"Week/Time",
"click_time_period":"Please click the time period with your mouse",
"clear":"Clear"
"day_time": "Week/Time",
"click_time_period": "Please click the time period with your mouse",
"clear": "Clear"
},
"operation": {
"title": "Operation And Maintenance Management",
@ -244,7 +247,9 @@
"associated_device": "Associated Devices",
"choose": "Choose",
"index": "Serial Number",
"floor_plan": "Floor Plan"
"floor_plan": "Floor Plan",
"department": "Department",
"department_name": "Department Name"
},
"accountManagement": {
"account_title": "Account Management",

View File

@ -58,7 +58,8 @@ import {
faEye,
faEyeSlash,
faGlobe,
faDownload
faDownload,
faStream
} from "@fortawesome/free-solid-svg-icons";
/* add icons to the library */
@ -118,7 +119,8 @@ library.add(
faEye,
faEyeSlash,
faGlobe,
faDownload
faDownload,
faStream
);
export default library;

View File

@ -39,7 +39,7 @@ instance.interceptors.response.use(
// Any status codes that falls outside the range of 2xx cause this function to trigger
// Do something with response error
if (error.response && error.response.status === 401) {
window.location.href = "/logout";
window.location.href = "/";
}
return Promise.reject(error);
}

View File

@ -114,7 +114,7 @@ const closeModal = () => {
>
<template #modalContent>
<form ref="form" class="grid grid-cols-5 gap-5">
<div class="grid grid-cols-2 col-span-2">
<div class="grid grid-cols-2 col-span-2 items-end">
<AssetTableModalLeft :current_component_key="current_component_key" />
</div>
<div class="col-span-3">

View File

@ -3,10 +3,12 @@ import { ref, inject, onBeforeMount, onMounted, watch } from "vue";
import * as yup from "yup";
import "yup-phone-lite";
import AssetTableModalLeftInfoIoT from "./AssetTableModalLeftInfoIoT.vue";
import AssetTableModalLeftInfoDept from "./AssetTableModalLeftInfoDept.vue";
import AssetTableModalLeftInfoGraph from "./AssetTableModalLeftInfoGraph.vue";
import AssetTableModalLeftInfoMQTT from "./AssetTableModalLeftInfoMQTT.vue";
import { getOperationCompanyList } from "@/apis/operation";
import { getIOTSchema } from "@/apis/asset";
import useSearchParam from "@/hooks/useSearchParam";
import OperationTableModal from "@/views/operation/components/OperationTableModal.vue";
import dayjs from "dayjs";
import { useI18n } from "vue-i18n";
const { t } = useI18n();
@ -28,6 +30,15 @@ let schema = {
.number()
.transform((value) => (Number.isNaN(value) ? null : value))
.nullable(true),
response_schema_id: yup
.number()
.transform((value) => (Number.isNaN(value) ? null : value))
.nullable(true),
department_id: yup
.number()
.transform((value) => (Number.isNaN(value) ? null : value))
.nullable(true),
topic: yup.string().nullable(true),
asset_number: yup.string().nullable(true),
sub_device: yup.array().nullable(true),
oriFile: yup.array().nullable(true),
@ -41,7 +52,10 @@ onBeforeMount(() => {
brand: "",
device_model: "",
operation_id: 0,
response_schema_id: 0,
department_id: 0,
asset_number: "",
topic: "",
sub_device: [],
oriFile: [],
buying_date: "",
@ -80,28 +94,25 @@ watch(formState, (newValue) => {
}
});
const updateFileList = (files) => {
formState.value = { ...formState.value, oriFile: files };
};
const companyOptions = ref([]);
const iotSchemaOptions = ref([]);
const getCompany = async () => {
const res = await getOperationCompanyList();
companyOptions.value = res.data.map((d) => ({ ...d, key: d.id }));
};
const getIOTSchemaOptions = async () => {
const res = await getIOTSchema();
iotSchemaOptions.value = res.data.map((d) => ({ ...d, key: d.id }));
};
onMounted(() => {
getCompany();
getIOTSchemaOptions();
});
const openCompanyAddModal = () => {
changeParams({ ...searchParams.value, work_type: 3 }); // company Add modal
operation_action_item.showModal();
};
</script>
<template>
<!-- information -->
<Input :value="formState" width="270" name="full_name">
<Input :value="formState" width="290" name="full_name">
<template #topLeft>{{ $t("assetManagement.device_name") }}</template>
<template #bottomLeft
><span class="text-error text-base">
@ -109,15 +120,8 @@ const openCompanyAddModal = () => {
</span></template
></Input
>
<Input :value="formState" width="270" name="operate_text">
<template #topLeft>Mac</template>
<template #bottomLeft
><span class="text-error text-base">
{{ formErrorMsg.operate_text }}
</span></template
></Input
>
<Input :value="formState" width="270" name="device_number">
<AssetTableModalLeftInfoDept />
<Input :value="formState" width="290" name="device_number">
<template #topLeft
>Tag_Name ({{ $t("assetManagement.fill_text") }})</template
>
@ -127,28 +131,19 @@ const openCompanyAddModal = () => {
</span></template
></Input
>
<!-- <Input :value="formState" width="270" name="floor_guid">
<template #topLeft>設備位置樓層 / 區域</template>
<template #bottomLeft
><span class="text-error text-base">
{{ formErrorMsg.floor_guid }}
</span></template
></Input
> -->
<Input
:value="formState"
width="270"
name="device_coordinate"
:disabled="true"
>
<template #topLeft>{{ $t("assetManagement.device_coordinate") }}</template>
<template #bottomLeft
><span class="text-error text-base">
{{ formErrorMsg.device_coordinate }}
</span></template
></Input
>
<Input :value="formState" width="270" name="asset_number">
<div class="flex items-center w-72">
<Select
:value="formState"
selectClass="border-info focus-within:border-info"
name="response_schema_id"
Attribute="name"
:options="iotSchemaOptions"
:required="true"
>
<template #topLeft>IoT</template>
</Select>
</div>
<Input :value="formState" width="290" name="asset_number">
<template #topLeft>{{ $t("assetManagement.asset_number") }}</template>
<template #bottomLeft
><span class="text-error text-base">
@ -156,7 +151,7 @@ const openCompanyAddModal = () => {
</span></template
></Input
>
<DateGroup :items="buying_date" width="270" :withLine="false">
<DateGroup :items="buying_date" width="290" :withLine="false">
<template #topLeft>{{ $t("assetManagement.buying_date") }}</template>
<template #bottomLeft
><span class="text-error text-base">
@ -164,7 +159,7 @@ const openCompanyAddModal = () => {
</span></template
>
</DateGroup>
<Input :value="formState" width="270" name="brand">
<Input :value="formState" width="290" name="brand">
<template #topLeft>{{ $t("assetManagement.brand") }}</template>
<template #bottomLeft
><span class="text-error text-base">
@ -172,7 +167,7 @@ const openCompanyAddModal = () => {
</span></template
></Input
>
<Input :value="formState" width="270" name="device_model">
<Input :value="formState" width="290" name="device_model">
<template #topLeft>{{ $t("assetManagement.modal") }}</template>
<template #bottomLeft
><span class="text-error text-base">
@ -180,7 +175,7 @@ const openCompanyAddModal = () => {
</span></template
></Input
>
<div class="flex items-center col-span-2">
<div class="flex items-center w-72">
<Select
:value="formState"
selectClass="border-info focus-within:border-info"
@ -191,27 +186,9 @@ const openCompanyAddModal = () => {
>
<template #topLeft>{{ $t("assetManagement.company") }}</template>
</Select>
<OperationTableModal type="asset" />
<button
type="button"
class="btn btn-add ml-2 mt-7"
@click="openCompanyAddModal"
>
<font-awesome-icon :icon="['fas', 'plus']" />
{{ $t("button.add") }}
</button>
</div>
<AssetTableModalLeftInfoMQTT />
<AssetTableModalLeftInfoGraph />
<!-- <Upload
name="oriFile"
:fileList="formState.oriFile"
:getFileList="updateFileList"
:multiple="true"
class="col-span-2"
:baseUrl="FILE_BASEURL"
>
<template #topLeft>{{ $t("assetManagement.oriFile") }}</template>
</Upload> -->
<AssetTableModalLeftInfoIoT />
</template>

View File

@ -0,0 +1,161 @@
<script setup>
import { onMounted, ref, inject, onBeforeMount, watch, computed } from "vue";
import {
getDepartmentList,
postDepartmentList,
deleteDepartmentItem,
} from "@/apis/asset";
import useFormErrorMessage from "@/hooks/useFormErrorMessage";
import * as yup from "yup";
import { twMerge } from "tailwind-merge";
import { useI18n } from "vue-i18n";
const { t } = useI18n();
const { openToast, cancelToastOpen } = inject("app_toast");
const { formState } = inject("asset_table_modal_form");
const selectedOption = ref("add");
const DeptFormState = ref({ id: 0, name: "" });
const departmentList = ref([]);
const getDepartment = async () => {
const res = await getDepartmentList();
departmentList.value = res.data.map((d) => ({ ...d, key: d.id }));
};
onMounted(() => {
getDepartment();
});
// modal
const openModal = () => {
if (selectedOption.value === "add") {
DeptFormState.value = {
id: 0,
name: "",
};
} else if (selectedOption.value === "edit") {
const dept = departmentList.value.find(
(d) => d.id === formState.value.department_id
);
if (dept) {
DeptFormState.value = {
id: dept.id,
name: dept.name,
};
}
}
asset_add_dept.showModal();
};
const form = ref(null);
const deptScheme = yup.object({
name: yup.string().required(t("button.required")),
});
const { formErrorMsg, handleSubmit, handleErrorReset, updateScheme } =
useFormErrorMessage(deptScheme);
const onOk = async () => {
const value = await handleSubmit(deptScheme, DeptFormState.value);
const res = await postDepartmentList(value);
if (res.isSuccess) {
getDepartment();
onCancel();
}
};
const onDelete = async () => {
openToast(
"warning",
t("msg.sure_to_delete"),
"#asset_add_table_item",
async () => {
await cancelToastOpen();
const res = await deleteDepartmentItem(formState.value.department_id);
if (res.isSuccess) {
getDepartment();
openToast("success", t("msg.delete_success"), "#asset_add_table_item");
} else {
openToast("error", res.msg, "#asset_add_table_item");
}
}
);
};
const onCancel = () => {
DeptFormState.value = {
id: 0,
name: "",
};
asset_add_dept.close();
};
</script>
<template>
<div className="join w-72 mb-4">
<Select
:value="formState"
selectClass="border-info focus-within:border-info rounded-r-none"
name="department_id"
Attribute="name"
:options="departmentList"
:isBottomLabelExist="false"
>
<template #topLeft>{{ $t("assetManagement.department") }}</template>
</Select>
<select
v-model="selectedOption"
className="select border-info focus-within:border-info join-item mt-11"
>
<option value="add" selected>{{ $t("button.add") }}</option>
<option value="edit">{{ $t("button.edit") }}</option>
<option value="delete">{{ $t("button.delete") }}</option>
</select>
<button
type="button"
class="btn btn-success join-item mt-11"
@click="selectedOption === 'delete' ? onDelete() : openModal()"
:aria-label="$t('button.submit')"
>
{{ $t("button.submit") }}
</button>
</div>
<Modal
id="asset_add_dept"
:title="t('assetManagement.department')"
:onCancel="onCancel"
width="400"
>
<template #modalContent>
<form ref="form">
<Input :value="DeptFormState" width="270" name="name">
<template #topLeft>{{
$t("assetManagement.department_name")
}}</template>
<template #bottomLeft
><span class="text-error text-base">
{{ formErrorMsg.name }}
</span></template
></Input
>
</form>
</template>
<template #modalAction>
<button
type="reset"
class="btn btn-outline-success mr-2"
@click.prevent="onCancel"
>
{{ $t("button.cancel") }}
</button>
<button type="submit" class="btn btn-outline-success" @click="onOk">
{{ $t("button.submit") }}
</button>
</template></Modal
>
</template>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,134 @@
<script setup>
import { onMounted, ref, inject, watch, computed } from "vue";
import { useI18n } from "vue-i18n";
import mqtt from "mqtt";
const { t } = useI18n();
const { openToast, cancelToastOpen } = inject("app_toast");
const { formState } = inject(
"asset_table_modal_form"
);
const BASEURL = import.meta.env.VITE_MQTT_BASEURL;
// MQTT
const mqttClient = ref(null); // MQTT
const receivedMessages = ref([]); //
const countdown = ref(60); // 60
let timer = null; //
const openModal = () => {
if (!mqttClient.value) {
connectMqtt();
}
mqtt_test.showModal();
startCountdown(); //
};
const connectMqtt = () => {
const topic = formState.value.topic || ""; //
const mqttHost = `${BASEURL}`;
const protocol = import.meta.env.MODE === "production" ? "wss" : "ws"; // "ws" "wss"
mqttClient.value = mqtt.connect(mqttHost, {
protocol,
reconnectPeriod: 1000, //
});
mqttClient.value.on("connect", () => {
console.log("MQTT 已連接");
if (topic) {
mqttClient.value.subscribe(topic, (err) => {
if (!err) {
console.log(`已訂閱主題: ${topic}`);
} else {
console.error("訂閱失敗: ", err);
}
});
}
});
mqttClient.value.on("message", (topic, message) => {
//
receivedMessages.value.push({ topic, message: message.toString() });
clearInterval(timer); //
});
mqttClient.value.on("error", (err) => {
console.error("MQTT 連線錯誤: ", err);
});
};
const startCountdown = () => {
if (timer) return; //
timer = setInterval(() => {
if (countdown.value > 0) {
countdown.value--;
} else {
onCancel(); // 1 onCancel
}
}, 1000);
};
const onCancel = () => {
receivedMessages.value = [];
mqtt_test.close();
// MQTT
if (mqttClient.value) {
mqttClient.value.end();
mqttClient.value = null;
}
//
if (timer) {
clearInterval(timer);
timer = null; //
}
countdown.value = 60;
};
</script>
<template>
<div class="flex w-72">
<Input :value="formState" name="topic" >
<template #topLeft>MQTT Topic</template>
</Input>
<button type="button" class="btn btn-add mt-11 ms-1" @click="openModal">
<font-awesome-icon :icon="['fas', 'cog']" />
Test
</button>
</div>
<Modal id="mqtt_test" title="MQTT Topic" :onCancel="onCancel" width="400">
<template #modalContent>
<!-- 顯示接收到的訊息 -->
<div v-if="receivedMessages.length > 0" class="overflow-y-auto h-96">
<ul>
<li
v-for="(message, index) in receivedMessages"
:key="index"
class="bg-base-200 rounded-md text-wrap shadow shadow-slate-400 p-4 my-2 me-2"
>
<strong class=" text-base block text-info mb-2">{{ message.topic }} :</strong>
<p class=" text-sm break-words">{{ message.message }}</p>
</li>
</ul>
</div>
<!-- 顯示 loading 和倒計時 -->
<p v-else class="text-center mt-20">
<Loading />
<br />
<span class="text-base">{{ countdown }} seconds</span>
</p>
</template>
<template #modalAction>
<button
type="reset"
class="btn btn-outline-success mr-2"
@click.prevent="onCancel"
>
{{ t("button.cancel") }}
</button>
</template>
</Modal>
</template>
<style lang="scss" scoped></style>

View File

@ -74,10 +74,10 @@ const defaultOption = (map, data = []) => {
watch(currentFloor, (newValue) => {
if (newValue?.floor_map_name) {
const coordinates = totalCoordinates.value[newValue.floor_guid] || [];
if (coordinates.length === 0 || coordinates.every(coord => coord === "")) return;
const parsedCoordinates = coordinates.map((coord) => {
return JSON.parse(coord);
});
asset_floor_chart.value.updateSvg(
{
full_name: newValue?.floor_map_name,
@ -209,8 +209,8 @@ const onCancel = () => {
<template>
<!-- 平面圖 -->
<div class="flex items-center justify-between mb-5">
<div className="join">
<div class="flex gap-4 mb-5">
<div className="join w-80 mb-4">
<Select
:value="formState"
selectClass="border-info focus-within:border-info rounded-r-none"
@ -238,6 +238,19 @@ const onCancel = () => {
{{ $t("button.submit") }}
</button>
</div>
<Input
:value="formState"
width="270"
name="device_coordinate"
:disabled="true"
>
<template #topLeft>{{ $t("assetManagement.device_coordinate") }}</template>
<template #bottomLeft
><span class="text-error text-base">
{{ formErrorMsg.device_coordinate }}
</span></template
></Input
>
</div>
<div class="relative">
<EffectScatter

View File

@ -89,7 +89,7 @@ const getAllOptions = async () => {
};
const updateDataSource = (data) => {
dataSource.value = data.map((d) => ({ ...d, key: d.uuid }));
dataSource.value = (data || []).map((d) => ({ ...d, key: d.uuid }));
};
const search = async () => {

View File

@ -19,7 +19,7 @@ const store = useBuildingStore();
const dataSource = ref([]);
const updateDataSource = (data) => {
dataSource.value = data.map((d) => ({
dataSource.value = (data || []).map((d) => ({
...d,
key: d.id,
start_time: d?.start_time ? dayjs(d?.start_time).format("YYYY-MM-DD") : "",

View File

@ -81,7 +81,7 @@ watch(
:items="submitBtns"
:withLine="false"
:withBtnClass="true"
class="ml-5 mr-8 xl:mr-10"
v-if="selectedWorkType?.work_type !== 3"
/>
<button class="btn btn-add" @click.stop.prevent="() => openModal()">

View File

@ -135,7 +135,8 @@ watch(
"
/>
<OperationSearchType :selected="selectedSearchType" />
<OperationSearchType :selected="selectedSearchType"
v-if="selectedWorkType?.work_type !== 3" />
<OperationActionButton :selectedWorkType="selectedWorkType" />
</div>
</form>

View File

@ -116,7 +116,7 @@ watch(
} else if (newSearchParams.search_type === "serial") {
changeParams({
...searchParams.value,
serial_number: newSerialNumber.value,
serial_number: newSerialNumber,
});
}
},
@ -151,7 +151,7 @@ watch(
name="serial_number"
:class="twMerge(searchParams?.work_type === '3' ? '' : 'mr-3')"
:placeholder="searchParams?.work_type === '3' ? $t('operation.enter_text') : $t('operation.enter_serial')"
:value="serial_number"
:value="searchParams"
/>
</template>

View File

@ -530,7 +530,7 @@ watch(
class="my-2"
name="email"
>
<template #topLeft>>{{ $t("operation.email") }}</template>
<template #topLeft>{{ $t("operation.email") }}</template>
<template #bottomLeft
><span class="text-error text-base">
{{ companyFormErrorMsg.email }}

View File

@ -206,6 +206,9 @@ const getCurrentInfoModalData = async (e, position, value) => {
: "",
};
}
const immediateRes = await getSystemRealTime([value.device_number]);
realtimeData.value = immediateRes.data;
}
const mobile = isMobile(e);
selectedDevice.value = {

View File

@ -0,0 +1,194 @@
<script setup>
import { inject, onMounted, onUnmounted, ref, nextTick, watch } from "vue";
import { getHistoryData } from "@/apis/history";
import LineChart from "@/components/chart/LineChart.vue";
import { SECOND_CHART_COLOR } from "@/constant";
import dayjs from "dayjs";
import { useI18n } from "vue-i18n";
const { t } = useI18n();
const { selectedDevice: data } = inject("system_selectedDevice");
const pointsList = ref([]);
const timeList = ref([
{ value: 1, name: t("dashboard.one_hour") },
{ value: 4, name: t("dashboard.four_hour") },
{ value: 8, name: t("dashboard.eight_hour") },
]);
const chartData = ref([]);
const forge_chart = ref(null);
const loading = ref(false);
//
const defaultChartOption = {
tooltip: {
trigger: "axis",
},
legend: {
data: [],
textStyle: {
color: "#ffffff",
fontSize: 16,
},
},
grid: {
top: "25%",
left: "0%",
right: "0%",
bottom: "0%",
containLabel: true,
},
xAxis: {
type: "category",
splitLine: { show: false },
axisLabel: {
color: "#ffffff",
formatter: (value) => dayjs(value).format("HH:mm"), //
},
data: [],
},
yAxis: {
type: "value",
splitLine: { show: false },
axisLabel: { color: "#ffffff" },
},
series: [],
};
const formState = ref({
Cumulant: 1,
Type: 2,
Points: [],
Start_date: dayjs().format("YYYY-MM-DD"),
Start_time: dayjs().format("HH:00"),
End_date: dayjs().format("YYYY-MM-DD"),
End_time: dayjs().format("HH:00"),
Device_list: [],
});
const updateTimeRange = (hours) => {
const now = dayjs();
const startTime = now.subtract(hours, "hour");
formState.value.Start_date = startTime.format("YYYY-MM-DD");
formState.value.Start_time = startTime.format("HH:00");
formState.value.End_date = now.format("YYYY-MM-DD");
formState.value.End_time = now.format("HH:00");
};
const onSearch = async () => {
loading.value = true;
const res = await getHistoryData(formState.value);
if (res.isSuccess) {
if (res.data.items.length > 0) {
chartData.value = res.data.items
.map((d) => ({
timestamp: d.timestamp,
value: parseFloat(d.value),
}))
.sort((a, b) => new Date(a.timestamp) - new Date(b.timestamp));
//
await nextTick();
if (forge_chart.value?.chart) {
forge_chart.value.chart.setOption({
xAxis: {
data: chartData.value.map((d) => d.timestamp),
},
series: [
{
name: data?.value?.value.full_name,
type: "line",
data: chartData.value.map((d) => d.value),
showSymbol: false,
itemStyle: {
color: SECOND_CHART_COLOR[0], // 使
},
},
],
});
}
} else {
chartData.value = [];
if (forge_chart.value?.chart) {
forge_chart.value.chart.clear(); //
}
}
} else {
console.error("API Error:", res.msg);
}
loading.value = false;
};
onMounted(() => {
console.log("Initial data:", data?.value?.value);
if (data?.value?.value.device_number) {
formState.value.Device_list = [data?.value?.value.device_number];
}
if (data?.value?.value.points) {
pointsList.value = data?.value?.value?.points.map((item) => ({
name: item.full_name,
value: item.points,
}));
}
});
onUnmounted(() => {
formState.value = {};
chartData.value = [];
});
watch(
() => formState.value.Points,
(newPoints) => {
if (newPoints.includes("Total")) {
formState.value.Cumulant = 2;
} else {
formState.value.Cumulant = 1;
}
},
{ immediate: true }
);
</script>
<template>
<div class="flex items-center gap-4">
<Select
:value="formState"
class=""
selectClass="border-info focus-within:border-info"
name="Points"
Attribute="name"
:options="pointsList"
></Select>
<Select
:value="formState"
class=""
selectClass="border-info focus-within:border-info"
name="time"
Attribute="name"
:options="timeList"
:onChange="updateTimeRange"
></Select>
<button class="btn btn-search" @click.stop.prevent="onSearch">
<font-awesome-icon :icon="['fas', 'search']" />
{{ $t("button.search") }}
</button>
</div>
<div class="min-h-[300px] relative">
<span
v-if="loading"
className="loading loading-spinner loading-lg text-info absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 z-20"
></span>
<LineChart
v-if="chartData.length > 0"
id="forge_chart"
class="min-h-[300px] max-h-fit"
:option="defaultChartOption"
ref="forge_chart"
/>
<p class="text-center text-xl" v-if="!loading && chartData.length === 0">
{{ $t("table.no_data") }}
</p>
</div>
</template>
<style lang="scss" scoped></style>

View File

@ -3,9 +3,9 @@ import { defineProps, inject, ref, watch } from "vue";
import SystemInfoModalDesktop from "./SystemInfoModalDesktop.vue";
import SystemInfoModalCog from "./SystemInfoModalCog.vue";
import SystemInfoModalImage from "./SystemInfoModalImage.vue";
import SystemInfoModalChart from "./SystemInfoModalChart.vue";
import { twMerge } from "tailwind-merge";
const { selectedDevice: data, clearSelectedDeviceInfo } = inject("system_selectedDevice")
@ -13,7 +13,8 @@ const currentTab = ref("desktop");
const tabs = {
desktop: SystemInfoModalDesktop,
cog: SystemInfoModalCog,
image: SystemInfoModalImage
image: SystemInfoModalImage,
// chart: SystemInfoModalChart,
};
const changeOpenKey = (key) => {
@ -44,28 +45,10 @@ const onCancel = () => {
<font-awesome-icon :icon="['fas', 'cog']" size="lg"
:class="twMerge(currentTab === 'cog' ? 'text-success' : 'text-[#a5abb1]')" />
</Button>
<!-- <Button
type="link"
class="btn-link btn-text-without-border px-2"
@click="() => changeOpenKey('triangle')"
>
<font-awesome-icon
:icon="['fas', 'exclamation-triangle']"
size="lg"
class="text-[#a5abb1]"
/>
</Button>
<Button
type="link"
class="btn-link btn-text-without-border px-2"
@click="() => changeOpenKey('bars')"
>
<font-awesome-icon
:icon="['fas', 'bars']"
size="lg"
class="text-[#a5abb1]"
/>
</Button>-->
<!-- <Button type="link" class="btn-link btn-text-without-border px-2" @click="() => changeOpenKey('chart')">
<font-awesome-icon :icon="['fas', 'chart-line']" size="lg"
:class="twMerge(currentTab === 'chart' ? 'text-success' : 'text-[#a5abb1]')" />
</Button> -->
<Button type="link" class="btn-link btn-text-without-border px-2" @click="onCancel">
<font-awesome-icon :icon="['fas', 'times']" size="lg" class="text-[#a5abb1]" />
</Button>

View File

@ -11,14 +11,16 @@ const data = computed(() => {
return selectedDevice.value?.value?.points?.map((d) => ({
...d,
value: selectedDeviceRealtime?.value?.find(({ point }) => point === d.points)?.value || "No Data"
value: selectedDeviceRealtime?.value?.find(({ point }) => point === d.points)?.value || 0
})) || []
});
watch(selectedDeviceRealtime, (newValue) => {
watch(selectedDeviceRealtime, (newValue,oldValue) => {
if (newValue) {
loading.value = false;
updatedTime.value = dayjs(new Date()).format("YYYY-MM-DD HH:mm:ss");
}else{
loading.value = true;
}
});
@ -40,6 +42,7 @@ const columns = [{
:columns="columns"
:dataSource="data || []"
:withStyle="false"
:pagination="false"
>
</Table>
</template>