Compare commits
22 Commits
12d171dc51
...
main
Author | SHA1 | Date | |
---|---|---|---|
0ef591ca1b | |||
edd7469ef2 | |||
acb1f89b0a | |||
a7ed0340b7 | |||
e6939183fe | |||
ac5c88a047 | |||
f0e9d7fda6 | |||
14e3f9ea4a | |||
68d834aec9 | |||
3e96223bd9 | |||
5d02cffcf4 | |||
14187ad9bc | |||
0772269c33 | |||
b55ba4003d | |||
502b4f3778 | |||
c8e00421b2 | |||
8b85e2d67c | |||
f956402648 | |||
c8d8fbf254 | |||
80fcfda16c | |||
a91c4397a4 | |||
5b1ff9749d |
@ -1,4 +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_MQTT_BASEURL = "wss://mqttwss.mjm-staging.developers-homelab.net"
|
||||
VITE_FORGE_BASEURL = "https://cgems.cvilux-group.com:8088/dist"
|
@ -1,4 +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"
|
||||
VITE_MQTT_BASEURL = "wss://mqttwss.mjm-staging.developers-homelab.net"
|
||||
# VITE_FORGE_BASEURL = "https://cgems.cvilux-group.com:8088/dist"
|
@ -1,3 +1,3 @@
|
||||
VITE_API_BASEURL = "http://220.132.206.5:8008"
|
||||
VITE_FILE_API_BASEURL = "http://220.132.206.5:8085/file"
|
||||
VITE_FORGE_BASEURL = "http://localhost:5173"
|
||||
VITE_API_BASEURL = "https://ibms-cvilux-demo-api.production.mjmtech.com.tw"
|
||||
VITE_FILE_API_BASEURL = "https://cgems.cvilux-group.com:8088"
|
||||
VITE_MQTT_BASEURL = "wss://mqttwss.mjm-staging.developers-homelab.net"
|
29
.github/prompts/exportCSV.prompt.md
vendored
Normal file
29
.github/prompts/exportCSV.prompt.md
vendored
Normal file
@ -0,0 +1,29 @@
|
||||
---
|
||||
mode: agent
|
||||
---
|
||||
# API 路徑整理與引用檢查規則
|
||||
|
||||
## 目標
|
||||
- 針對 apis 目錄下所有子資料夾(如 account、alert、asset、building、dashboard、energy、forge、graph、history、login、operation、productSetting、system)的 api.js 與 index.js 檔案,完整追蹤 API 路徑的實際引用情形。
|
||||
- 追蹤流程:
|
||||
1. 先在 api.js 找出所有 API 路徑常數(如 `export const GET_XXX_API = '/path'`)。
|
||||
2. 在 index.js 檔案確認這些常數有被 import 並包裝成 API function(如 `getXXX`)。
|
||||
3. 再全專案搜尋這些 API function 是否有被其他檔案 import 並呼叫。
|
||||
- 產生一份 csv 報表,格式如下:
|
||||
|
||||
| API 路徑 | 定義常數 | API function | 是否有被引用 |
|
||||
|----------|----------|--------------|-------------|
|
||||
| /user | GET_USER_API | getUser | Y |
|
||||
| /admin | GET_ADMIN_API | getAdmin | N |
|
||||
|
||||
## 詳細規則
|
||||
- 處理 apis 目錄下所有子資料夾的 api.js 與 index.js 檔案。
|
||||
- API 路徑的定義需涵蓋 get/post/put/delete 等(如 `export const API = '/path'`)。
|
||||
- 只統計有被 index.js import 並包裝成 function 的 API 路徑。
|
||||
- 檢查 function 是否有被其他檔案 import 並呼叫(排除 apis 目錄本身)。
|
||||
- 匹配到的檔案需記錄完整路徑,可多個檔案以分號分隔。
|
||||
- 統計結果輸出為 csv 檔案。
|
||||
|
||||
## 輸出
|
||||
- 檔名:api_usage_report.csv
|
||||
- 欄位:API 路徑, 定義常數, API function, 是否有被引用,
|
460
package-lock.json
generated
460
package-lock.json
generated
@ -20,8 +20,8 @@
|
||||
"date-fns": "^3.3.1",
|
||||
"dayjs": "^1.11.10",
|
||||
"echarts": "^5.4.3",
|
||||
"flag-icons": "^7.2.3",
|
||||
"jquery-ui": "^1.14.1",
|
||||
"json-schema-generator": "^2.0.6",
|
||||
"mqtt": "^5.10.3",
|
||||
"pinia": "^2.1.7",
|
||||
"requirejs": "^2.3.6",
|
||||
@ -1152,8 +1152,6 @@
|
||||
"version": "6.12.6",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
|
||||
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "^3.1.1",
|
||||
"fast-json-stable-stringify": "^2.0.0",
|
||||
@ -1298,6 +1296,24 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/asn1": {
|
||||
"version": "0.2.6",
|
||||
"resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz",
|
||||
"integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"safer-buffer": "~2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/assert-plus": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
|
||||
"integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/assign-symbols": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz",
|
||||
@ -1366,6 +1382,21 @@
|
||||
"postcss": "^8.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/aws-sign2": {
|
||||
"version": "0.7.0",
|
||||
"resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz",
|
||||
"integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/aws4": {
|
||||
"version": "1.13.2",
|
||||
"resolved": "https://registry.npmjs.org/aws4/-/aws4-1.13.2.tgz",
|
||||
"integrity": "sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "1.6.2",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.6.2.tgz",
|
||||
@ -1429,6 +1460,15 @@
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/bcrypt-pbkdf": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
|
||||
"integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==",
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"tweetnacl": "^0.14.3"
|
||||
}
|
||||
},
|
||||
"node_modules/big.js": {
|
||||
"version": "5.2.2",
|
||||
"resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz",
|
||||
@ -1478,8 +1518,7 @@
|
||||
"node_modules/bluebird": {
|
||||
"version": "3.7.2",
|
||||
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz",
|
||||
"integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==",
|
||||
"dev": true
|
||||
"integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg=="
|
||||
},
|
||||
"node_modules/boolbase": {
|
||||
"version": "1.0.0",
|
||||
@ -1628,6 +1667,12 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"node_modules/caseless": {
|
||||
"version": "0.12.0",
|
||||
"resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
|
||||
"integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==",
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/chalk": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
|
||||
@ -1853,6 +1898,12 @@
|
||||
"url": "https://opencollective.com/core-js"
|
||||
}
|
||||
},
|
||||
"node_modules/core-util-is": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
|
||||
"integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/cors": {
|
||||
"version": "2.8.5",
|
||||
"resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
|
||||
@ -2038,6 +2089,18 @@
|
||||
"url": "https://opencollective.com/daisyui"
|
||||
}
|
||||
},
|
||||
"node_modules/dashdash": {
|
||||
"version": "1.14.1",
|
||||
"resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
|
||||
"integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"assert-plus": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/date-fns": {
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.3.1.tgz",
|
||||
@ -2175,6 +2238,16 @@
|
||||
"domelementtype": "1"
|
||||
}
|
||||
},
|
||||
"node_modules/ecc-jsbn": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
|
||||
"integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"jsbn": "~0.1.0",
|
||||
"safer-buffer": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/echarts": {
|
||||
"version": "5.4.3",
|
||||
"resolved": "https://registry.npmjs.org/echarts/-/echarts-5.4.3.tgz",
|
||||
@ -2417,6 +2490,12 @@
|
||||
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/extend": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
|
||||
"integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/extend-shallow": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
|
||||
@ -2448,12 +2527,19 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/extsprintf": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz",
|
||||
"integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==",
|
||||
"engines": [
|
||||
"node >=0.6.0"
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/fast-deep-equal": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
||||
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
|
||||
"dev": true,
|
||||
"peer": true
|
||||
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
|
||||
},
|
||||
"node_modules/fast-glob": {
|
||||
"version": "3.3.2",
|
||||
@ -2486,9 +2572,7 @@
|
||||
"node_modules/fast-json-stable-stringify": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
|
||||
"integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
|
||||
"dev": true,
|
||||
"peer": true
|
||||
"integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="
|
||||
},
|
||||
"node_modules/fast-unique-numbers": {
|
||||
"version": "8.0.13",
|
||||
@ -2536,12 +2620,6 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/flag-icons": {
|
||||
"version": "7.2.3",
|
||||
"resolved": "https://registry.npmjs.org/flag-icons/-/flag-icons-7.2.3.tgz",
|
||||
"integrity": "sha512-X2gUdteNuqdNqob2KKTJTS+ZCvyWeLCtDz9Ty8uJP17Y4o82Y+U/Vd4JNrdwTAjagYsRznOn9DZ+E/Q52qbmqg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"version": "1.15.3",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz",
|
||||
@ -2570,6 +2648,15 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/forever-agent": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
|
||||
"integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/form-data": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
|
||||
@ -2660,6 +2747,15 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/getpass": {
|
||||
"version": "0.1.7",
|
||||
"resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
|
||||
"integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"assert-plus": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/glob": {
|
||||
"version": "7.1.6",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
|
||||
@ -2705,6 +2801,29 @@
|
||||
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/har-schema": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz",
|
||||
"integrity": "sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==",
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/har-validator": {
|
||||
"version": "5.1.5",
|
||||
"resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz",
|
||||
"integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==",
|
||||
"deprecated": "this library is no longer supported",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ajv": "^6.12.3",
|
||||
"har-schema": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/has-ansi": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz",
|
||||
@ -2840,6 +2959,21 @@
|
||||
"readable-stream": "^3.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/http-signature": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz",
|
||||
"integrity": "sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"assert-plus": "^1.0.0",
|
||||
"jsprim": "^1.2.2",
|
||||
"sshpk": "^1.7.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.8",
|
||||
"npm": ">=1.3.7"
|
||||
}
|
||||
},
|
||||
"node_modules/ieee754": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
|
||||
@ -3016,6 +3150,12 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/is-typedarray": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
|
||||
"integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/is-windows": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz",
|
||||
@ -3043,6 +3183,12 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/isstream": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
|
||||
"integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/jest-worker": {
|
||||
"version": "27.5.1",
|
||||
"resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz",
|
||||
@ -3101,6 +3247,12 @@
|
||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
|
||||
},
|
||||
"node_modules/jsbn": {
|
||||
"version": "0.1.1",
|
||||
"resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
|
||||
"integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/json-parse-even-better-errors": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
|
||||
@ -3108,12 +3260,47 @@
|
||||
"dev": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/json-promise": {
|
||||
"version": "1.1.8",
|
||||
"resolved": "https://registry.npmjs.org/json-promise/-/json-promise-1.1.8.tgz",
|
||||
"integrity": "sha512-rz31P/7VfYnjQFrF60zpPTT0egMPlc8ZvIQHWs4ZtNZNnAXRmXo6oS+6eyWr5sEMG03OVhklNrTXxiIRYzoUgQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"bluebird": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/json-schema": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz",
|
||||
"integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==",
|
||||
"license": "(AFL-2.1 OR BSD-3-Clause)"
|
||||
},
|
||||
"node_modules/json-schema-generator": {
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/json-schema-generator/-/json-schema-generator-2.0.6.tgz",
|
||||
"integrity": "sha512-WyWDTK3jnv/OBI43uWw7pTGoDQ62PfccySZCHTBsOfS6D9QhsQr+95Wcwq5lqjzkiDQkTNkWzXEqHOhswfufmw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"json-promise": "^1.1.8",
|
||||
"mkdirp": "^0.5.0",
|
||||
"optimist": "^0.6.1",
|
||||
"pretty-data": "^0.40.0",
|
||||
"request": "^2.81.0"
|
||||
},
|
||||
"bin": {
|
||||
"json-schema-generator": "bin/cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/json-schema-traverse": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
|
||||
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
|
||||
"dev": true,
|
||||
"peer": true
|
||||
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="
|
||||
},
|
||||
"node_modules/json-stringify-safe": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
|
||||
"integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/json5": {
|
||||
"version": "1.0.2",
|
||||
@ -3139,6 +3326,21 @@
|
||||
"graceful-fs": "^4.1.6"
|
||||
}
|
||||
},
|
||||
"node_modules/jsprim": {
|
||||
"version": "1.4.2",
|
||||
"resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz",
|
||||
"integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"assert-plus": "1.0.0",
|
||||
"extsprintf": "1.3.0",
|
||||
"json-schema": "0.4.0",
|
||||
"verror": "1.10.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/kind-of": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz",
|
||||
@ -3395,6 +3597,18 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/mkdirp": {
|
||||
"version": "0.5.6",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz",
|
||||
"integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"minimist": "^1.2.6"
|
||||
},
|
||||
"bin": {
|
||||
"mkdirp": "bin/cmd.js"
|
||||
}
|
||||
},
|
||||
"node_modules/mqtt": {
|
||||
"version": "5.10.3",
|
||||
"resolved": "https://registry.npmjs.org/mqtt/-/mqtt-5.10.3.tgz",
|
||||
@ -3634,6 +3848,15 @@
|
||||
"js-sdsl": "4.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/oauth-sign": {
|
||||
"version": "0.9.0",
|
||||
"resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz",
|
||||
"integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/object-assign": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||
@ -3754,6 +3977,22 @@
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"node_modules/optimist": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz",
|
||||
"integrity": "sha512-snN4O4TkigujZphWLN0E//nQmm7790RYaE53DdL7ZYwee2D8DDo9/EyYiKUfN3rneWUjhJnueija3G9I2i0h3g==",
|
||||
"license": "MIT/X11",
|
||||
"dependencies": {
|
||||
"minimist": "~0.0.1",
|
||||
"wordwrap": "~0.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/optimist/node_modules/minimist": {
|
||||
"version": "0.0.10",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz",
|
||||
"integrity": "sha512-iotkTvxc+TwOm5Ieim8VnSNvCDjCK9S8G3scJ50ZthspSxa7jx50jkhYduuAtAjvfDUwSgOwf8+If99AlOEhyw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/pascalcase": {
|
||||
"version": "0.1.1",
|
||||
"resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz",
|
||||
@ -3784,6 +4023,12 @@
|
||||
"integrity": "sha512-sTitTPYnn23esFR3RlqYBWn4c45WGeLcsKzQiUpXJAyfcWkolvlYpV8FLo7JishK946oQwMFUCHXQ9AjGPKExw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/performance-now": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
|
||||
"integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/picocolors": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
|
||||
@ -4085,6 +4330,15 @@
|
||||
"posthtml-render": "^1.0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/pretty-data": {
|
||||
"version": "0.40.0",
|
||||
"resolved": "https://registry.npmjs.org/pretty-data/-/pretty-data-0.40.0.tgz",
|
||||
"integrity": "sha512-YFLnEdDEDnkt/GEhet5CYZHCvALw6+Elyb/tp8kQG03ZSIuzeaDWpZYndCXwgqu4NAjh1PI534dhDS1mHarRnQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/process": {
|
||||
"version": "0.11.10",
|
||||
"resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
|
||||
@ -4110,16 +4364,35 @@
|
||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
|
||||
},
|
||||
"node_modules/psl": {
|
||||
"version": "1.15.0",
|
||||
"resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz",
|
||||
"integrity": "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"punycode": "^2.3.1"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/lupomontero"
|
||||
}
|
||||
},
|
||||
"node_modules/punycode": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
|
||||
"integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/qs": {
|
||||
"version": "6.5.3",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz",
|
||||
"integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==",
|
||||
"license": "BSD-3-Clause",
|
||||
"engines": {
|
||||
"node": ">=0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/query-string": {
|
||||
"version": "4.3.4",
|
||||
"resolved": "https://registry.npmjs.org/query-string/-/query-string-4.3.4.tgz",
|
||||
@ -4285,6 +4558,52 @@
|
||||
"node": ">=0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/request": {
|
||||
"version": "2.88.2",
|
||||
"resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz",
|
||||
"integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==",
|
||||
"deprecated": "request has been deprecated, see https://github.com/request/request/issues/3142",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"aws-sign2": "~0.7.0",
|
||||
"aws4": "^1.8.0",
|
||||
"caseless": "~0.12.0",
|
||||
"combined-stream": "~1.0.6",
|
||||
"extend": "~3.0.2",
|
||||
"forever-agent": "~0.6.1",
|
||||
"form-data": "~2.3.2",
|
||||
"har-validator": "~5.1.3",
|
||||
"http-signature": "~1.2.0",
|
||||
"is-typedarray": "~1.0.0",
|
||||
"isstream": "~0.1.2",
|
||||
"json-stringify-safe": "~5.0.1",
|
||||
"mime-types": "~2.1.19",
|
||||
"oauth-sign": "~0.9.0",
|
||||
"performance-now": "^2.1.0",
|
||||
"qs": "~6.5.2",
|
||||
"safe-buffer": "^5.1.2",
|
||||
"tough-cookie": "~2.5.0",
|
||||
"tunnel-agent": "^0.6.0",
|
||||
"uuid": "^3.3.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/request/node_modules/form-data": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz",
|
||||
"integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.6",
|
||||
"mime-types": "^2.1.12"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.12"
|
||||
}
|
||||
},
|
||||
"node_modules/requirejs": {
|
||||
"version": "2.3.6",
|
||||
"resolved": "https://registry.npmjs.org/requirejs/-/requirejs-2.3.6.tgz",
|
||||
@ -4418,6 +4737,12 @@
|
||||
"ret": "~0.1.10"
|
||||
}
|
||||
},
|
||||
"node_modules/safer-buffer": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/sass": {
|
||||
"version": "1.69.5",
|
||||
"resolved": "https://registry.npmjs.org/sass/-/sass-1.69.5.tgz",
|
||||
@ -4787,6 +5112,31 @@
|
||||
"node": ">= 10.x"
|
||||
}
|
||||
},
|
||||
"node_modules/sshpk": {
|
||||
"version": "1.18.0",
|
||||
"resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.18.0.tgz",
|
||||
"integrity": "sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"asn1": "~0.2.3",
|
||||
"assert-plus": "^1.0.0",
|
||||
"bcrypt-pbkdf": "^1.0.0",
|
||||
"dashdash": "^1.12.0",
|
||||
"ecc-jsbn": "~0.1.1",
|
||||
"getpass": "^0.1.1",
|
||||
"jsbn": "~0.1.0",
|
||||
"safer-buffer": "^2.0.2",
|
||||
"tweetnacl": "~0.14.0"
|
||||
},
|
||||
"bin": {
|
||||
"sshpk-conv": "bin/sshpk-conv",
|
||||
"sshpk-sign": "bin/sshpk-sign",
|
||||
"sshpk-verify": "bin/sshpk-verify"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/stable": {
|
||||
"version": "0.1.8",
|
||||
"resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz",
|
||||
@ -5387,6 +5737,19 @@
|
||||
"resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz",
|
||||
"integrity": "sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg=="
|
||||
},
|
||||
"node_modules/tough-cookie": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz",
|
||||
"integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==",
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"psl": "^1.1.28",
|
||||
"punycode": "^2.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/traverse": {
|
||||
"version": "0.6.8",
|
||||
"resolved": "https://registry.npmjs.org/traverse/-/traverse-0.6.8.tgz",
|
||||
@ -5410,6 +5773,24 @@
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz",
|
||||
"integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg=="
|
||||
},
|
||||
"node_modules/tunnel-agent": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
|
||||
"integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"safe-buffer": "^5.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/tweetnacl": {
|
||||
"version": "0.14.5",
|
||||
"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
|
||||
"integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==",
|
||||
"license": "Unlicense"
|
||||
},
|
||||
"node_modules/type-fest": {
|
||||
"version": "2.19.0",
|
||||
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz",
|
||||
@ -5620,8 +6001,6 @@
|
||||
"version": "4.4.1",
|
||||
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
|
||||
"integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"punycode": "^2.1.0"
|
||||
}
|
||||
@ -5647,6 +6026,16 @@
|
||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
|
||||
},
|
||||
"node_modules/uuid": {
|
||||
"version": "3.4.0",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
|
||||
"integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==",
|
||||
"deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"uuid": "bin/uuid"
|
||||
}
|
||||
},
|
||||
"node_modules/vary": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
||||
@ -5656,6 +6045,20 @@
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/verror": {
|
||||
"version": "1.10.0",
|
||||
"resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz",
|
||||
"integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==",
|
||||
"engines": [
|
||||
"node >=0.6.0"
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"assert-plus": "^1.0.0",
|
||||
"core-util-is": "1.0.2",
|
||||
"extsprintf": "^1.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-4.5.0.tgz",
|
||||
@ -5895,6 +6298,15 @@
|
||||
"integrity": "sha512-poXpCylU7ExuvZK8z+On3kX+S8o/2dQ/SVYueKA0D4WEMXROXgY8Ez50/bQEUmvoSMMrWcrJqCHuhAbsiwg7Dg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/wordwrap": {
|
||||
"version": "0.0.3",
|
||||
"resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz",
|
||||
"integrity": "sha512-1tMA907+V4QmxV7dbRvb4/8MaRALK6q9Abid3ndMYnbyo8piisCmeONVqVSXqQA3KaP4SLt5b7ud6E2sqP8TFw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/worker-timers": {
|
||||
"version": "7.1.8",
|
||||
"resolved": "https://registry.npmjs.org/worker-timers/-/worker-timers-7.1.8.tgz",
|
||||
|
@ -6,7 +6,8 @@
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview"
|
||||
"preview": "vite preview",
|
||||
"build:staging": "vite build --mode staging"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ant-design/icons-vue": "^7.0.1",
|
||||
@ -21,8 +22,8 @@
|
||||
"date-fns": "^3.3.1",
|
||||
"dayjs": "^1.11.10",
|
||||
"echarts": "^5.4.3",
|
||||
"flag-icons": "^7.2.3",
|
||||
"jquery-ui": "^1.14.1",
|
||||
"json-schema-generator": "^2.0.6",
|
||||
"mqtt": "^5.10.3",
|
||||
"pinia": "^2.1.7",
|
||||
"requirejs": "^2.3.6",
|
||||
|
BIN
public/build_img.jpg
Normal file
BIN
public/build_img.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 137 KiB |
@ -1,5 +1,6 @@
|
||||
export const POST_ACK_API = `/obix/alarm`;
|
||||
export const GET_ALERT_FORMID_API = `/Alert/AlertList`;
|
||||
export const GET_ALERT_LOG_API = `api/Alarm/GetAlarmLog`;
|
||||
export const POST_OPERATION_RECORD_API = `/operation/SavOpeRecord`;
|
||||
|
||||
export const GET_ALERT_SUB_LIST_API = `api/Device/GetMainSub`;
|
||||
@ -9,12 +10,17 @@ export const GET_ALERT_MEMBER = `api/Alarm/GetAlarmMember`;
|
||||
export const POST_ALERT_MEMBER = `api/Alarm/SaveAlarmMember`;
|
||||
export const DELETE_ALERT_MEMBER = `api/Alarm/DeleteAlarmMember`;
|
||||
export const GET_NOTICE_LIST_API = `api/Alarm/GetNotice`;
|
||||
export const GET_SHOW_ALERT_API = `api/Alarm/GetShowAlarm`; // 取得告警顯示清單
|
||||
|
||||
export const GET_OUTLIERS_LIST_API = `api/Alarm/GetAlarmSetting`;
|
||||
export const GET_OUTLIERS_DEVLIST_API = `api/Alarm/GetDevList`; // 取得設備
|
||||
export const GET_OUTLIERS_POINTS_API = `api/Alarm/GetAlarmPoints`; // 取得點位
|
||||
export const POST_OUTLIERS_SETTING_API = `api/Alarm/SaveAlarmSetting`; // 新增與修改
|
||||
export const DELETE_OUTLIERS_SETTING_API = `api/Alarm/DeleteAlarmSetting`; // 刪除
|
||||
export const GET_FACTOR_API = `api/Alarm/GetFactor`; // 刪除
|
||||
|
||||
export const GET_ALERT_SCHEDULE_LIST_API = `api/Alarm/GetAlarmSchedule`;
|
||||
export const POST_ALERT_SCHEDULE = `api/Alarm/SaveAlarmSchedule`;
|
||||
export const DELETE_ALERT_SCHEDULE = `api/Alarm/DeleteAlarmSchedule`;
|
||||
export const DELETE_ALERT_SCHEDULE = `api/Alarm/DeleteAlarmSchedule`;
|
||||
|
||||
export const POST_ALERT_MQTT_REFRESH = `api/Alarm/MQTTRefresh`;
|
@ -1,51 +1,28 @@
|
||||
import {
|
||||
POST_ACK_API,
|
||||
GET_ALERT_FORMID_API,
|
||||
GET_ALERT_LOG_API,
|
||||
POST_OPERATION_RECORD_API,
|
||||
GET_ALERT_SUB_LIST_API,
|
||||
GET_OUTLIERS_LIST_API,
|
||||
GET_OUTLIERS_DEVLIST_API,
|
||||
GET_OUTLIERS_POINTS_API,
|
||||
POST_OUTLIERS_SETTING_API,
|
||||
DELETE_OUTLIERS_SETTING_API,
|
||||
GET_FACTOR_API,
|
||||
GET_ALERT_MEMBER_LIST_API,
|
||||
GET_ALERT_MEMBER,
|
||||
POST_ALERT_MEMBER,
|
||||
DELETE_ALERT_MEMBER,
|
||||
GET_NOTICE_LIST_API,
|
||||
GET_SHOW_ALERT_API,
|
||||
GET_ALERT_SCHEDULE_LIST_API,
|
||||
POST_ALERT_SCHEDULE,
|
||||
DELETE_ALERT_SCHEDULE,
|
||||
POST_ALERT_MQTT_REFRESH
|
||||
} from "./api";
|
||||
import instance from "@/util/request";
|
||||
import apihandler from "@/util/apihandler";
|
||||
import axios from "axios";
|
||||
|
||||
export const postChgAck = async (uuid) => {
|
||||
try {
|
||||
const data =
|
||||
'<obj is="obix:AckAlarmIn"><str name="ackUser" val="obix" /></obj>';
|
||||
const res = await axios.post(`${POST_ACK_API}/${uuid}/ack`, data, {
|
||||
headers: {
|
||||
"Content-Type": "text/plain",
|
||||
},
|
||||
});
|
||||
|
||||
// 解析XML錯誤信息
|
||||
const parser = new DOMParser();
|
||||
const xmlDoc = parser.parseFromString(res.data, "text/xml");
|
||||
const errElement = xmlDoc.querySelector("err");
|
||||
|
||||
if (errElement) {
|
||||
console.error("Error in acknowledging alarm");
|
||||
return { isSuccess: false, msg: `Error in acknowledging alarm` };
|
||||
}
|
||||
|
||||
return { isSuccess: true };
|
||||
} catch (error) {
|
||||
console.error("Error in acknowledging alarm:", error);
|
||||
return { isSuccess: false, msg: "Error in acknowledging alarm" };
|
||||
}
|
||||
};
|
||||
|
||||
export const getAlertFormId = async (uuid) => {
|
||||
const res = await instance.post(GET_ALERT_FORMID_API, uuid);
|
||||
@ -55,6 +32,24 @@ export const getAlertFormId = async (uuid) => {
|
||||
});
|
||||
};
|
||||
|
||||
export const getAlertLog = async ({
|
||||
Start_date,
|
||||
End_date,
|
||||
isRecovery,
|
||||
device_name_tag,
|
||||
}) => {
|
||||
const res = await instance.post(GET_ALERT_LOG_API, {
|
||||
Start_date,
|
||||
End_date,
|
||||
isRecovery,
|
||||
device_name_tag,
|
||||
});
|
||||
return apihandler(res.code, res.data, {
|
||||
msg: res.msg,
|
||||
code: res.code,
|
||||
});
|
||||
};
|
||||
|
||||
export const postOperationRecord = async (formData) => {
|
||||
const res = await instance.post(POST_OPERATION_RECORD_API, formData);
|
||||
|
||||
@ -64,8 +59,10 @@ export const postOperationRecord = async (formData) => {
|
||||
});
|
||||
};
|
||||
|
||||
export const getAlertSubList = async () => {
|
||||
const res = await instance.post(GET_ALERT_SUB_LIST_API, {});
|
||||
export const getAlertSubList = async (building_guid) => {
|
||||
const res = await instance.post(GET_ALERT_SUB_LIST_API, {
|
||||
building_guid,
|
||||
});
|
||||
|
||||
return apihandler(res.code, res.data, {
|
||||
msg: res.msg,
|
||||
@ -151,6 +148,15 @@ export const getOutliersPoints = async (id) => {
|
||||
});
|
||||
};
|
||||
|
||||
export const getFactors = async () => {
|
||||
const res = await instance.post(GET_FACTOR_API);
|
||||
|
||||
return apihandler(res.code, res.data, {
|
||||
msg: res.msg,
|
||||
code: res.code,
|
||||
});
|
||||
};
|
||||
|
||||
export const postOutliersSetting = async (data) => {
|
||||
const res = await instance.post(POST_OUTLIERS_SETTING_API, data);
|
||||
|
||||
@ -160,6 +166,26 @@ export const postOutliersSetting = async (data) => {
|
||||
});
|
||||
};
|
||||
|
||||
export const delOutliersSetting = async (Id) => {
|
||||
const res = await instance.post(DELETE_OUTLIERS_SETTING_API, {
|
||||
Id,
|
||||
});
|
||||
|
||||
return apihandler(res.code, res.data, {
|
||||
msg: res.msg,
|
||||
code: res.code,
|
||||
});
|
||||
};
|
||||
|
||||
export const getShowAlarm = async () => {
|
||||
const res = await instance.post(GET_SHOW_ALERT_API);
|
||||
|
||||
return apihandler(res.code, res.data, {
|
||||
msg: res.msg,
|
||||
code: res.code,
|
||||
});
|
||||
};
|
||||
|
||||
export const getAlarmScheduleList = async () => {
|
||||
const res = await instance.post(GET_ALERT_SCHEDULE_LIST_API, {});
|
||||
|
||||
@ -189,4 +215,13 @@ export const deleteAlarmSchedule = async (id) => {
|
||||
console.error("API request failed", error);
|
||||
return { isSuccess: false, msg: "API request failed" };
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export const postMQTTRefresh = async () => {
|
||||
const res = await instance.post(POST_ALERT_MQTT_REFRESH);
|
||||
|
||||
return apihandler(res.code, res.data, {
|
||||
msg: res.msg,
|
||||
code: res.code,
|
||||
});
|
||||
};
|
||||
|
@ -20,9 +20,20 @@ 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 POST_ASSET_IOT_SCHEMA_API = `/AssetManage/SaveResponseSchema`;
|
||||
|
||||
export const GET_ASSET_DEVICE_ITEM_API = `/AssetManage/GetDeviceItem`;
|
||||
export const POST_ASSET_DEVICE_ITEM_API = `/AssetManage/SaveDeviceItem`;
|
||||
export const DELETE_ASSET_DEVICE_ITEM_API = `/AssetManage/DeleteDeviceItem`;
|
||||
|
||||
export const GET_ASSET_DEPARTMENT_API = `/AssetManage/GetDepartment`;
|
||||
export const POST_ASSET_DEPARTMENT_API = `/AssetManage/SaveDepartment`;
|
||||
export const DELETE_ASSET_DEPARTMENT_API = `/AssetManage/DeleteDepartment`;
|
||||
|
||||
export const GET_ASSET_ELECTYPE_API = `/AssetManage/GetElecType`;
|
||||
export const POST_ASSET_ELECTYPE_API = `/AssetManage/SaveElecType`;
|
||||
export const DELETE_ASSET_ELECTYPE_API = `/AssetManage/DeleteElecType`;
|
||||
|
||||
export const POST_ASSET_ELEC_SETTING_API = `/AssetManage/SaveAssetSetting`;
|
||||
|
||||
export const POST_ASSET_MQTT_PUBLISH_API = `/api/mqtt/publish`;
|
@ -15,17 +15,25 @@ import {
|
||||
POST_ASSET_SINGLE_API,
|
||||
GET_ASSET_SUB_POINT_API,
|
||||
GET_ASSET_IOT_SCHEMA_API,
|
||||
POST_ASSET_IOT_SCHEMA_API,
|
||||
GET_ASSET_DEVICE_ITEM_API,
|
||||
POST_ASSET_DEVICE_ITEM_API,
|
||||
DELETE_ASSET_DEVICE_ITEM_API,
|
||||
GET_ASSET_DEPARTMENT_API,
|
||||
POST_ASSET_DEPARTMENT_API,
|
||||
DELETE_ASSET_DEPARTMENT_API,
|
||||
GET_ASSET_ELECTYPE_API,
|
||||
POST_ASSET_ELECTYPE_API,
|
||||
DELETE_ASSET_ELECTYPE_API,
|
||||
POST_ASSET_ELEC_SETTING_API,
|
||||
POST_ASSET_MQTT_PUBLISH_API,
|
||||
} from "./api";
|
||||
import instance from "@/util/request";
|
||||
import apihandler from "@/util/apihandler";
|
||||
import { object } from "yup";
|
||||
|
||||
export const getAssetMainList = async () => {
|
||||
const res = await instance.post(GET_ASSET_MAIN_LIST_API);
|
||||
export const getAssetMainList = async (building_guid) => {
|
||||
const res = await instance.post(GET_ASSET_MAIN_LIST_API, { building_guid });
|
||||
|
||||
return apihandler(res.code, res.data, {
|
||||
msg: res.msg,
|
||||
@ -42,11 +50,17 @@ export const deleteAssetMainItem = async (id) => {
|
||||
});
|
||||
};
|
||||
|
||||
export const postAssetMainList = async ({ id, system_key, system_value }) => {
|
||||
export const postAssetMainList = async ({
|
||||
id,
|
||||
system_key,
|
||||
system_value,
|
||||
building_guid,
|
||||
}) => {
|
||||
const res = await instance.post(POST_ASSET_MAIN_LIST_API, {
|
||||
id,
|
||||
system_key,
|
||||
system_value,
|
||||
building_guid,
|
||||
});
|
||||
|
||||
return apihandler(res.code, res.data, {
|
||||
@ -64,18 +78,8 @@ export const getAssetSubList = async (id) => {
|
||||
});
|
||||
};
|
||||
|
||||
export const postAssetSubList = async ({
|
||||
system_key,
|
||||
system_value,
|
||||
system_parent_id,
|
||||
id,
|
||||
}) => {
|
||||
const res = await instance.post(POST_ASSET_SUB_LIST_API, {
|
||||
system_key,
|
||||
system_value,
|
||||
system_parent_id,
|
||||
id,
|
||||
});
|
||||
export const postAssetSubList = async (formData) => {
|
||||
const res = await instance.post(POST_ASSET_SUB_LIST_API, formData);
|
||||
|
||||
return apihandler(res.code, res.data, {
|
||||
msg: res.msg,
|
||||
@ -154,8 +158,8 @@ export const deleteAssetItem = async (main_id) => {
|
||||
});
|
||||
};
|
||||
|
||||
export const getAssetFloorList = async () => {
|
||||
const res = await instance.post(GET_ASSET_FLOOR_LIST_API);
|
||||
export const getAssetFloorList = async (building_guid) => {
|
||||
const res = await instance.post(GET_ASSET_FLOOR_LIST_API, { building_guid });
|
||||
|
||||
return apihandler(res.code, res.data, {
|
||||
msg: res.msg,
|
||||
@ -205,7 +209,69 @@ export const getAssetSubPoint = async (sub_system_tag) => {
|
||||
};
|
||||
|
||||
export const getIOTSchema = async (variable_id) => {
|
||||
const res = await instance.post(GET_ASSET_IOT_SCHEMA_API, {variable_id});
|
||||
const res = await instance.post(GET_ASSET_IOT_SCHEMA_API, { variable_id });
|
||||
|
||||
return apihandler(res.code, res.data, {
|
||||
msg: res.msg,
|
||||
code: res.code,
|
||||
});
|
||||
};
|
||||
|
||||
export const postIOTSchema = async ({ name, variable_id, points }) => {
|
||||
const res = await instance.post(POST_ASSET_IOT_SCHEMA_API, {
|
||||
name,
|
||||
variable_id,
|
||||
points,
|
||||
});
|
||||
|
||||
return apihandler(res.code, res.data, {
|
||||
msg: res.msg,
|
||||
code: res.code,
|
||||
});
|
||||
};
|
||||
|
||||
export const getDeviceItem = async (variable_id) => {
|
||||
const res = await instance.post(GET_ASSET_DEVICE_ITEM_API, { variable_id });
|
||||
|
||||
return apihandler(res.code, res.data, {
|
||||
msg: res.msg,
|
||||
code: res.code,
|
||||
});
|
||||
};
|
||||
|
||||
export const postDeviceItem = async ({
|
||||
id,
|
||||
variable_id,
|
||||
full_name,
|
||||
points,
|
||||
decimals,
|
||||
is_bool,
|
||||
is_link,
|
||||
show_event_switch_btn,
|
||||
event_switch_on_message,
|
||||
event_switch_off_message,
|
||||
}) => {
|
||||
const res = await instance.post(POST_ASSET_DEVICE_ITEM_API, {
|
||||
id,
|
||||
variable_id,
|
||||
full_name,
|
||||
points,
|
||||
decimals,
|
||||
is_bool,
|
||||
is_link,
|
||||
show_event_switch_btn,
|
||||
event_switch_on_message,
|
||||
event_switch_off_message,
|
||||
});
|
||||
|
||||
return apihandler(res.code, res.data, {
|
||||
msg: res.msg,
|
||||
code: res.code,
|
||||
});
|
||||
};
|
||||
|
||||
export const deleteDeviceItem = async (id) => {
|
||||
const res = await instance.post(DELETE_ASSET_DEVICE_ITEM_API, { id });
|
||||
|
||||
return apihandler(res.code, res.data, {
|
||||
msg: res.msg,
|
||||
@ -250,4 +316,46 @@ export const getElecTypeList = async () => {
|
||||
msg: res.msg,
|
||||
code: res.code,
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
export const postElecTypeList = async ({ name, id }) => {
|
||||
const res = await instance.post(POST_ASSET_ELECTYPE_API, {
|
||||
name,
|
||||
id,
|
||||
});
|
||||
|
||||
return apihandler(res.code, res.data, {
|
||||
msg: res.msg,
|
||||
code: res.code,
|
||||
});
|
||||
};
|
||||
|
||||
export const deleteElecTypeItem = async (id) => {
|
||||
const res = await instance.post(DELETE_ASSET_ELECTYPE_API, { id });
|
||||
|
||||
return apihandler(res.code, res.data, {
|
||||
msg: res.msg,
|
||||
code: res.code,
|
||||
});
|
||||
};
|
||||
|
||||
export const postAssetElecSetting = async (formData) => {
|
||||
const res = await instance.post(POST_ASSET_ELEC_SETTING_API, formData);
|
||||
|
||||
return apihandler(res.code, res.data, {
|
||||
msg: res.msg,
|
||||
code: res.code,
|
||||
});
|
||||
};
|
||||
|
||||
export const postMQTTpublish = async ({ Topic, Payload }) => {
|
||||
const res = await instance.post(POST_ASSET_MQTT_PUBLISH_API, {
|
||||
Topic,
|
||||
Payload,
|
||||
});
|
||||
|
||||
return apihandler(res.code, res.data, {
|
||||
msg: res.msg,
|
||||
code: res.code,
|
||||
});
|
||||
};
|
||||
|
@ -1,4 +1,6 @@
|
||||
export const GET_BUILDING_API = `/api/Device/GetBuild`;
|
||||
export const GET_BUILDING_API = `/AssetManage/GetBuildingList`;
|
||||
export const POST_BUILDING_API = `/AssetManage/SaveBuilding`;
|
||||
export const DELETE_BUILDING_API = `/AssetManage/DeleteBuilding`;
|
||||
export const GET_AUTHPAGE_API = `/api/GetUsrFroList`;
|
||||
export const GET_SUBAUTHPAGE_API = `/api/Device/GetMainSub`;
|
||||
export const GET_ALL_DEVICE_API = `/api/Device/GetAllDevice`;
|
||||
|
@ -1,5 +1,7 @@
|
||||
import {
|
||||
GET_BUILDING_API,
|
||||
POST_BUILDING_API,
|
||||
DELETE_BUILDING_API,
|
||||
GET_AUTHPAGE_API,
|
||||
GET_SUBAUTHPAGE_API,
|
||||
GET_ALL_DEVICE_API,
|
||||
@ -16,6 +18,27 @@ export const getBuildings = async () => {
|
||||
});
|
||||
};
|
||||
|
||||
export const postBuildings = async ({ full_name, building_guid }) => {
|
||||
const res = await instance.post(POST_BUILDING_API, {
|
||||
full_name,
|
||||
building_guid,
|
||||
});
|
||||
|
||||
return apihandler(res.code, res.data, {
|
||||
msg: res.msg,
|
||||
code: res.code,
|
||||
});
|
||||
};
|
||||
|
||||
export const deleteBuildings = async (building_guid) => {
|
||||
const res = await instance.post(DELETE_BUILDING_API, { building_guid });
|
||||
|
||||
return apihandler(res.code, res.data, {
|
||||
msg: res.msg,
|
||||
code: res.code,
|
||||
});
|
||||
};
|
||||
|
||||
export const getAuth = async (lang) => {
|
||||
const res = await instance.post(GET_AUTHPAGE_API, {
|
||||
lang,
|
||||
@ -26,10 +49,8 @@ export const getAuth = async (lang) => {
|
||||
});
|
||||
};
|
||||
|
||||
export const getAllSysSidebar = async () => {
|
||||
const res = await instance.post(GET_SUBAUTHPAGE_API, {
|
||||
building_tag: "",
|
||||
});
|
||||
export const getAllSysSidebar = async (building_guid) => {
|
||||
const res = await instance.post(GET_SUBAUTHPAGE_API, {building_guid});
|
||||
return apihandler(res.code, res.data, {
|
||||
msg: res.msg,
|
||||
code: res.code,
|
||||
|
@ -6,4 +6,12 @@ export const GET_DASHBOARD_ROOM_TEMP_API = `/SituationRoom/GetFormulaRoomStatusD
|
||||
export const GET_DASHBOARD_ENERGY_API = `/SituationRoom/GetEnergeData`;
|
||||
export const POST_DASHBOARD_PRODUCT_TARGET_SETTING_API = `/SituationRoom/SetTargetSetting`;
|
||||
export const GET_DASHBOARD_PRODUCT_TARGET_SETTING_API = `/SituationRoom/GetTargetSetting`
|
||||
export const GET_DASHBOARD_PRODUCT_HISTORY_API = `/SituationRoom/GetProductionHistory`
|
||||
export const GET_DASHBOARD_PRODUCT_HISTORY_API = `/SituationRoom/GetProductionHistory`
|
||||
|
||||
export const GET_DASHBOARD_ENERGY_INFO_API = `api/dashboard/GetEnergyInfo`
|
||||
export const GET_DASHBOARD_ENERGY_COST_API = `api/dashboard/GetEnergyCost`
|
||||
export const GET_DASHBOARD_ALARMOPERATION_INFO_API = `api/dashboard/GetAlarmOperationInfo`
|
||||
|
||||
export const GET_DASHBOARD_2D3DINFO_API = `api/setting/visual/query`
|
||||
export const POST_DASHBOARD_2D3DINFO_API = `api/setting/visual/update`
|
||||
|
||||
|
@ -8,6 +8,11 @@ import {
|
||||
POST_DASHBOARD_PRODUCT_TARGET_SETTING_API,
|
||||
GET_DASHBOARD_PRODUCT_TARGET_SETTING_API,
|
||||
GET_DASHBOARD_PRODUCT_HISTORY_API,
|
||||
GET_DASHBOARD_ENERGY_INFO_API,
|
||||
GET_DASHBOARD_ENERGY_COST_API,
|
||||
GET_DASHBOARD_ALARMOPERATION_INFO_API,
|
||||
GET_DASHBOARD_2D3DINFO_API,
|
||||
POST_DASHBOARD_2D3DINFO_API
|
||||
} from "./api";
|
||||
import instance from "@/util/request";
|
||||
import apihandler from "@/util/apihandler";
|
||||
@ -135,3 +140,60 @@ export const getDashboardProductRecord = async ({ start_time, end_time }) => {
|
||||
});
|
||||
};
|
||||
|
||||
export const getEnergyInfo = async (building_guid) => {
|
||||
const res = await instance.post(GET_DASHBOARD_ENERGY_INFO_API, {
|
||||
building_guid,
|
||||
});
|
||||
|
||||
return apihandler(res.code, res.data, {
|
||||
msg: res.msg,
|
||||
code: res.code,
|
||||
});
|
||||
};
|
||||
|
||||
export const getEnergyCost = async ({
|
||||
department_id,
|
||||
floor_guid,
|
||||
building_guid,
|
||||
}) => {
|
||||
const res = await instance.post(GET_DASHBOARD_ENERGY_COST_API, {
|
||||
department_id,
|
||||
floor_guid,
|
||||
building_guid,
|
||||
});
|
||||
|
||||
return apihandler(res.code, res.data, {
|
||||
msg: res.msg,
|
||||
code: res.code,
|
||||
});
|
||||
};
|
||||
|
||||
export const getAlarmOperationInfo = async (building_guid) => {
|
||||
const res = await instance.post(GET_DASHBOARD_ALARMOPERATION_INFO_API, {
|
||||
building_guid,
|
||||
});
|
||||
|
||||
return apihandler(res.code, res.data, {
|
||||
msg: res.msg,
|
||||
code: res.code,
|
||||
});
|
||||
};
|
||||
|
||||
export const getDashboard2D3D = async (BuildingId) => {
|
||||
const res = await instance.post(GET_DASHBOARD_2D3DINFO_API, {
|
||||
BuildingId});
|
||||
|
||||
return apihandler(res.code, res.data, {
|
||||
msg: res.msg,
|
||||
code: res.code,
|
||||
});
|
||||
};
|
||||
|
||||
export const posttDashboard2D3D = async (formData) => {
|
||||
const res = await instance.post(POST_DASHBOARD_2D3DINFO_API, formData);
|
||||
|
||||
return apihandler(res.code, res.data, {
|
||||
msg: res.msg,
|
||||
code: res.code,
|
||||
});
|
||||
};
|
@ -2,6 +2,22 @@ export const GET_REALTIME_DIST_API = `/api/Energe/GetRealTimeDistribution`;
|
||||
export const GET_ELECUSE_DAY_API = `/api/Energe/GetElecUseDay`;
|
||||
export const GET_TAI_POWER_API = `/api/Energe/GetTaipower`;
|
||||
|
||||
export const GET_SIDEBAR_API = `/api/Energe/GetEnergySideBar`;
|
||||
export const GET_SIDEBAR_API = `/api/GetSideBar`;
|
||||
export const GET_SEARCH_API = `/api/Energe/GetFilter`;
|
||||
|
||||
export const GET_REPORT_API = `/api/Energe/GetReport`;
|
||||
export const GET_Excel_API = `/api/Energe/GetReportExcel`;
|
||||
|
||||
// 即時需量
|
||||
export const GET_DEMAND_API = `/api/Energe/SearchDemandValue`;
|
||||
export const POST_ADD_DEMAND_API = `/api/Energe/AddDemandValue`;
|
||||
export const POST_EDIT_DEMAND_API = `/api/Energe/UpdateDemandValue`;
|
||||
export const GET_REALTIME_DEMAND_API = `/api/Energe/GetRealTimeDemand`;
|
||||
|
||||
// 碳排係數
|
||||
export const GET_CARBON_API = `/api/Energe/SearchCarbonValue`;
|
||||
export const POST_EDIT_CARBON_API = `/api/Energe/UpdateCarbonValue`;
|
||||
|
||||
// 時間電價
|
||||
export const GET_TIME_ELEC_API = `/api/Energe/SearchTimeElec`;
|
||||
export const POST_TIME_ELEC_API = `/api/Energe/UpdateTimeElecValue`;
|
@ -4,12 +4,30 @@ import {
|
||||
GET_TAI_POWER_API,
|
||||
GET_SIDEBAR_API,
|
||||
GET_SEARCH_API,
|
||||
GET_REPORT_API,
|
||||
GET_Excel_API,
|
||||
GET_DEMAND_API,
|
||||
POST_EDIT_DEMAND_API,
|
||||
GET_REALTIME_DEMAND_API,
|
||||
GET_CARBON_API,
|
||||
POST_EDIT_CARBON_API,
|
||||
GET_TIME_ELEC_API,
|
||||
POST_TIME_ELEC_API,
|
||||
} from "./api";
|
||||
import instance from "@/util/request";
|
||||
import instance, { fileInstance } from "@/util/request";
|
||||
import apihandler from "@/util/apihandler";
|
||||
import downloadExcel from "@/util/downloadExcel";
|
||||
|
||||
export const getRealTimeDist = async () => {
|
||||
const res = await instance.post(GET_REALTIME_DIST_API);
|
||||
export const getRealTimeDist = async ({
|
||||
building_guid,
|
||||
department_id_list,
|
||||
floor_guid_list,
|
||||
}) => {
|
||||
const res = await instance.post(GET_REALTIME_DIST_API, {
|
||||
building_guid,
|
||||
department_id_list,
|
||||
floor_guid_list,
|
||||
});
|
||||
|
||||
return apihandler(res.code, res.data, {
|
||||
msg: res.msg,
|
||||
@ -17,8 +35,16 @@ export const getRealTimeDist = async () => {
|
||||
});
|
||||
};
|
||||
|
||||
export const getElecUseDay = async () => {
|
||||
const res = await instance.post(GET_ELECUSE_DAY_API);
|
||||
export const getElecUseDay = async ({
|
||||
building_guid,
|
||||
department_id_list,
|
||||
floor_guid_list,
|
||||
}) => {
|
||||
const res = await instance.post(GET_ELECUSE_DAY_API,{
|
||||
building_guid,
|
||||
department_id_list,
|
||||
floor_guid_list,
|
||||
});
|
||||
|
||||
return apihandler(res.code, res.data, {
|
||||
msg: res.msg,
|
||||
@ -26,8 +52,18 @@ export const getElecUseDay = async () => {
|
||||
});
|
||||
};
|
||||
|
||||
export const getTaipower = async () => {
|
||||
const res = await instance.post(GET_TAI_POWER_API);
|
||||
export const getTaipower = async ({
|
||||
coefficient,
|
||||
building_guid,
|
||||
department_id_list,
|
||||
floor_guid_list,
|
||||
}) => {
|
||||
const res = await instance.post(GET_TAI_POWER_API, {
|
||||
coefficient,
|
||||
building_guid,
|
||||
department_id_list,
|
||||
floor_guid_list,
|
||||
});
|
||||
|
||||
return apihandler(res.code, res.data, {
|
||||
msg: res.msg,
|
||||
@ -35,8 +71,8 @@ export const getTaipower = async () => {
|
||||
});
|
||||
};
|
||||
|
||||
export const getEnergySideBar = async () => {
|
||||
const res = await instance.post(GET_SIDEBAR_API);
|
||||
export const getSideBar = async (system_type) => {
|
||||
const res = await instance.post(GET_SIDEBAR_API, { system_type });
|
||||
|
||||
return apihandler(res.code, res.data, {
|
||||
msg: res.msg,
|
||||
@ -52,3 +88,145 @@ export const getEnergySearch = async (type) => {
|
||||
code: res.code,
|
||||
});
|
||||
};
|
||||
|
||||
export const getReport = async ({
|
||||
department,
|
||||
elecType,
|
||||
floor,
|
||||
start_time,
|
||||
end_time,
|
||||
type,
|
||||
}) => {
|
||||
const res = await instance.post(GET_REPORT_API, {
|
||||
department,
|
||||
elecType,
|
||||
floor,
|
||||
start_time,
|
||||
end_time,
|
||||
type,
|
||||
});
|
||||
|
||||
return apihandler(res.code, res.data, {
|
||||
msg: res.msg,
|
||||
code: res.code,
|
||||
});
|
||||
};
|
||||
|
||||
export const getExcel = async ({
|
||||
department,
|
||||
elecType,
|
||||
floor,
|
||||
start_time,
|
||||
end_time,
|
||||
type,
|
||||
}) => {
|
||||
const res = await fileInstance.post(
|
||||
GET_Excel_API,
|
||||
{
|
||||
department,
|
||||
elecType,
|
||||
floor,
|
||||
start_time,
|
||||
end_time,
|
||||
type,
|
||||
},
|
||||
{ responseType: "blob" }
|
||||
);
|
||||
|
||||
return apihandler(
|
||||
res.code,
|
||||
res,
|
||||
{
|
||||
msg: res.msg,
|
||||
code: res.code,
|
||||
},
|
||||
downloadExcel
|
||||
);
|
||||
};
|
||||
|
||||
export const getDemand = async (building_guid) => {
|
||||
const res = await instance.post(GET_DEMAND_API, { building_guid });
|
||||
|
||||
return apihandler(res.code, res.data, {
|
||||
msg: res.msg,
|
||||
code: res.code,
|
||||
});
|
||||
};
|
||||
|
||||
export const postEditDemand = async ({
|
||||
id,
|
||||
contract,
|
||||
alert,
|
||||
reset,
|
||||
building_guid,
|
||||
}) => {
|
||||
const res = await instance.put(POST_EDIT_DEMAND_API, {
|
||||
id,
|
||||
contract,
|
||||
alert,
|
||||
reset,
|
||||
building_guid,
|
||||
});
|
||||
|
||||
return apihandler(res.code, res.data, {
|
||||
msg: res.msg,
|
||||
code: res.code,
|
||||
});
|
||||
};
|
||||
|
||||
export const getCarbonValue = async (building_guid) => {
|
||||
const res = await instance.post(GET_CARBON_API, { building_guid });
|
||||
|
||||
return apihandler(res.code, res.data, {
|
||||
msg: res.msg,
|
||||
code: res.code,
|
||||
});
|
||||
};
|
||||
|
||||
export const postEditCarbonValue = async ({
|
||||
id,
|
||||
coefficient,
|
||||
building_guid,
|
||||
}) => {
|
||||
const res = await instance.put(POST_EDIT_CARBON_API, {
|
||||
id,
|
||||
coefficient,
|
||||
building_guid,
|
||||
});
|
||||
|
||||
return apihandler(res.code, res.data, {
|
||||
msg: res.msg,
|
||||
code: res.code,
|
||||
});
|
||||
};
|
||||
|
||||
export const getRealTimeDemand = async (building_guid) => {
|
||||
const res = await instance.post(GET_REALTIME_DEMAND_API, { building_guid });
|
||||
|
||||
return apihandler(res.code, res.data, {
|
||||
msg: res.msg,
|
||||
code: res.code,
|
||||
});
|
||||
};
|
||||
|
||||
export const getTimeElec = async (building_guid) => {
|
||||
const res = await instance.post(GET_TIME_ELEC_API, { building_guid });
|
||||
|
||||
return apihandler(res.code, res.data, {
|
||||
msg: res.msg,
|
||||
code: res.code,
|
||||
});
|
||||
};
|
||||
|
||||
export const postTimeElec = async ({ sheet, cost, building_guid }) => {
|
||||
const res = await instance.put(POST_TIME_ELEC_API, {
|
||||
sheet,
|
||||
cost,
|
||||
building_guid,
|
||||
});
|
||||
|
||||
return apihandler(res.code, res.data, {
|
||||
msg: res.msg,
|
||||
code: res.code,
|
||||
});
|
||||
};
|
||||
|
@ -4,6 +4,10 @@ export const GET_HISTORY_SIDEBAR_API = `/api/History/GetDeviceInfo`;
|
||||
export const GET_HISTORY_POINT_API = `/api/History/GetAllDevPoi`;
|
||||
export const GET_HISTORY_DATA_API = `/api/History/GetHistoryData`;
|
||||
export const GET_HISTORY_EXPORT_API = `/api/ExportHistoryExcel`;
|
||||
export const GET_HISTORY_EXPORT_REPORT_API = `/api/History/GetHistoryExcelReport`;
|
||||
export const GET_HISTORY_EXPORT_CURVE_API = `/api/History/GetHistoricalCurveExcelReport`;
|
||||
export const GET_HISTORY_EXPORT_QUICK_API = `/api/History/GetQuickMeteringExcelReport`;
|
||||
export const GET_HISTORY_EXPORT_CLASS_API = `/api/History/GetElectricityClassificationExcelReport`;
|
||||
|
||||
export const GET_HISTORY_FAVORITE_API = `/api/History/GetHistoryFavorite`;
|
||||
export const POST_HISTORY_FAVORITE_API = `/api/History/SaveHistoryFavorite`;
|
||||
|
@ -7,6 +7,10 @@ import {
|
||||
DELETE_HISTORY_FAVORITE_API,
|
||||
UPDATE_HISTORY_FAVORITE_API,
|
||||
GET_HISTORY_EXPORT_API,
|
||||
GET_HISTORY_EXPORT_REPORT_API,
|
||||
GET_HISTORY_EXPORT_CURVE_API,
|
||||
GET_HISTORY_EXPORT_QUICK_API,
|
||||
GET_HISTORY_EXPORT_CLASS_API,
|
||||
} from "./api";
|
||||
import instance, { fileInstance } from "@/util/request";
|
||||
import apihandler from "@/util/apiHandler";
|
||||
@ -16,11 +20,13 @@ export const getHistorySideBar = async ({
|
||||
sub_system_tag,
|
||||
department_id,
|
||||
elec_type_id,
|
||||
building_guid,
|
||||
}) => {
|
||||
const res = await instance.post(GET_HISTORY_SIDEBAR_API, {
|
||||
sub_system_tag,
|
||||
department_id,
|
||||
elec_type_id,
|
||||
building_guid,
|
||||
});
|
||||
|
||||
return apihandler(res.code, res.data, {
|
||||
@ -79,7 +85,52 @@ export const getHistoryData = async ({
|
||||
};
|
||||
|
||||
export const getHistoryExportData = async ({
|
||||
Start_date,
|
||||
End_date,
|
||||
Start_time,
|
||||
End_time,
|
||||
Device_list,
|
||||
Points,
|
||||
Type,
|
||||
table_type,
|
||||
}) => {
|
||||
const api =
|
||||
parseInt(table_type) === 1
|
||||
? GET_HISTORY_EXPORT_CURVE_API
|
||||
: parseInt(table_type) === 2
|
||||
? GET_HISTORY_EXPORT_QUICK_API
|
||||
: parseInt(table_type) === 3
|
||||
? GET_HISTORY_EXPORT_CLASS_API
|
||||
: GET_HISTORY_EXPORT_API;
|
||||
|
||||
const res = await fileInstance.post(
|
||||
api,
|
||||
{
|
||||
Start_date: Start_date,
|
||||
End_date: End_date,
|
||||
Start_time: Start_time,
|
||||
End_time: End_time,
|
||||
Points: Array.isArray(Points) ? Points : [Points],
|
||||
Device_list: Array.isArray(Device_list) ? Device_list : [Device_list],
|
||||
Type: parseInt(Type),
|
||||
Building_tag_list: [...new Set(Device_list.map((d) => d.split("_")[1]))],
|
||||
table_type: parseInt(table_type),
|
||||
},
|
||||
{ responseType: "blob" }
|
||||
);
|
||||
|
||||
return apihandler(
|
||||
res.code,
|
||||
res,
|
||||
{
|
||||
msg: res.msg,
|
||||
code: res.code,
|
||||
},
|
||||
downloadExcel
|
||||
);
|
||||
};
|
||||
|
||||
export const getHistoryExportReport = async ({
|
||||
Start_date,
|
||||
End_date,
|
||||
Start_time,
|
||||
@ -87,19 +138,8 @@ export const getHistoryExportData = async ({
|
||||
Device_list,
|
||||
Points,
|
||||
}) => {
|
||||
/*
|
||||
{
|
||||
Type,
|
||||
Start_date,
|
||||
End_date,
|
||||
Start_time,
|
||||
End_time,
|
||||
Device_list,
|
||||
Points,
|
||||
}
|
||||
*/
|
||||
const res = await fileInstance.post(
|
||||
GET_HISTORY_EXPORT_API,
|
||||
GET_HISTORY_EXPORT_REPORT_API,
|
||||
{
|
||||
// ...exportContent,
|
||||
Start_date: Start_date,
|
||||
@ -108,7 +148,6 @@ export const getHistoryExportData = async ({
|
||||
End_time: End_time,
|
||||
Points: Array.isArray(Points) ? Points : [Points],
|
||||
Device_list: Array.isArray(Device_list) ? Device_list : [Device_list],
|
||||
Type: parseInt(Type),
|
||||
Building_tag_list: [...new Set(Device_list.map((d) => d.split("_")[1]))],
|
||||
},
|
||||
{ responseType: "blob" }
|
||||
|
@ -20,13 +20,13 @@ export const getSystemFloors = async (building_tag, sub_system_tag) => {
|
||||
|
||||
export const getSystemDevices = async ({
|
||||
sub_system_tag,
|
||||
building_tag,
|
||||
floor_tag,
|
||||
building_guid,
|
||||
department_id_list,
|
||||
}) => {
|
||||
const res = await instance.post(GET_SYSTEM_DEVICE_LIST_API, {
|
||||
sub_system_tag,
|
||||
building_tag,
|
||||
floor_tag,
|
||||
building_guid,
|
||||
department_id_list,
|
||||
});
|
||||
|
||||
return apihandler(res.code, res.data, {
|
||||
|
@ -58,6 +58,7 @@
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
font-weight: normal;
|
||||
scrollbar-color: auto !important;
|
||||
}
|
||||
|
||||
body {
|
||||
|
@ -1,25 +1,41 @@
|
||||
<script setup>
|
||||
import { onMounted } from "vue";
|
||||
import useAlarmStore from "@/stores/useAlarmStore";
|
||||
import { ackSingleAlarm } from "@/apis/building";
|
||||
import { ref, onMounted, onUnmounted } from "vue";
|
||||
import { getAlertLog } from "@/apis/alert";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
const store = useAlarmStore();
|
||||
const dataSource = ref([]);
|
||||
let intervalId = null; // 用來儲存 setInterval 的 ID
|
||||
|
||||
const getAlarmData = async () => {
|
||||
const res = await getAlertLog({
|
||||
isRecovery: 1,
|
||||
Start_date: dayjs().format("YYYY-MM-DD"),
|
||||
End_date: dayjs().format("YYYY-MM-DD"),
|
||||
});
|
||||
dataSource.value = (res.data || []).map((d) => ({ ...d, key: d.id }));
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
store.getAlarmDataFromBaja();
|
||||
getAlarmData();
|
||||
|
||||
intervalId = setInterval(() => {
|
||||
getAlarmData();
|
||||
}, 30 * 1000);
|
||||
});
|
||||
|
||||
const ackedAlarm = async (uuid) => {
|
||||
const res = await ackSingleAlarm(uuid);
|
||||
console.log("ackedAlarm", res);
|
||||
};
|
||||
onUnmounted(() => {
|
||||
if (intervalId) {
|
||||
clearInterval(intervalId);
|
||||
intervalId = null;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<ul class="pr-4 min-h-full text-base-content">
|
||||
<!-- Sidebar content here -->
|
||||
<li class="my-3" v-for="alarm in store.alarmData" :key="alarm.uuid">
|
||||
<li class="my-3" v-for="alarm in dataSource" :key="alarm.id">
|
||||
<div
|
||||
class="w-full shadow-xl border border-success bg-body bg-opacity-80"
|
||||
>
|
||||
@ -34,30 +50,22 @@ const ackedAlarm = async (uuid) => {
|
||||
>
|
||||
<small>
|
||||
<span class="mr-4"
|
||||
>{{ alarm.timestamp_date }} {{ alarm.timestamp_time }}</span
|
||||
>{{ alarm.created_at }}</span
|
||||
>
|
||||
<font-awesome-icon
|
||||
<!-- <font-awesome-icon
|
||||
:icon="['fas', 'times']"
|
||||
size="lg"
|
||||
class="text-white"
|
||||
/>
|
||||
/> -->
|
||||
</small>
|
||||
</p>
|
||||
<div class="divider my-2"></div>
|
||||
<div>
|
||||
<p>{{ $t("alarm.number") }}:{{ alarm.uuid }}</p>
|
||||
<!-- <p>異常等級:255</p> -->
|
||||
<p>{{ $t("alarm.category") }}:{{ alarm.alarmClass }}</p>
|
||||
<p>{{ $t("alarm.device_name") }}:{{ alarm.full_name }}</p>
|
||||
<p>{{ $t("alarm.message") }}:{{ alarm.msg }}</p>
|
||||
</div>
|
||||
<div class="card-actions mt-1 justify-end">
|
||||
<button
|
||||
class="btn btn-sm btn-success"
|
||||
@click="() => ackedAlarm(alarm.uuid)"
|
||||
>
|
||||
{{ $t("alarm.confirm") }}
|
||||
</button>
|
||||
<p>{{ $t("alarm.number") }}:{{ alarm.id }}</p>
|
||||
<p>{{ $t("alert.alarmClass") }}:{{ alarm.factor }}</p>
|
||||
<p>{{ $t("alarm.device_name") }}:{{ alarm.device_number }}</p>
|
||||
<p>{{ $t("alert.device_point_name") }}:{{ alarm.points }}</p>
|
||||
<p>{{ $t("alert.error_msg") }}:{{ alarm.reason }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -68,12 +76,12 @@ const ackedAlarm = async (uuid) => {
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.card::before {
|
||||
@apply absolute h-5 w-5 top-1 left-1 bg-no-repeat z-10 bg-[url('../../assets/img/table/content-box-background01.svg')] bg-center;
|
||||
@apply absolute h-5 w-5 top-1 left-1 bg-no-repeat z-10 bg-[url('../../assets/img/table/content-box-background01.svg')] bg-center;
|
||||
content: "";
|
||||
}
|
||||
|
||||
.card::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;
|
||||
@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: "";
|
||||
}
|
||||
</style>
|
||||
</style>
|
@ -36,7 +36,7 @@ const toggleErrIcon = () => {
|
||||
</div>
|
||||
<div
|
||||
v-if="showErr"
|
||||
class="drawer-side translate-y-20 max-h-[90vh] overflow-x-hidden overflow-y-scroll"
|
||||
class="drawer-side translate-y-20 max-h-[90vh] overflow-x-hidden overflow-y-scroll w-[300px] left-auto right-0"
|
||||
>
|
||||
<AlarmCards />
|
||||
</div>
|
||||
|
@ -1,6 +1,6 @@
|
||||
<script setup>
|
||||
import * as echarts from "echarts";
|
||||
import { onMounted, ref, markRaw } from "vue";
|
||||
import { onMounted, ref, markRaw, nextTick } from "vue";
|
||||
import axios from "axios";
|
||||
|
||||
const props = defineProps({
|
||||
@ -24,9 +24,15 @@ async function updateSvg(svg, option) {
|
||||
} else {
|
||||
clear();
|
||||
}
|
||||
axios.get(svg.path).then(({ data }) => {
|
||||
axios.get(svg.path).then(async ({ data }) => {
|
||||
echarts.registerMap(svg.full_name, { svg: data });
|
||||
chart.value.setOption(option);
|
||||
await nextTick();
|
||||
// 延遲執行以避免在主進程中調用 setOption
|
||||
setTimeout(() => {
|
||||
if (chart.value && !chart.value.isDisposed()) {
|
||||
chart.value.setOption(option);
|
||||
}
|
||||
}, 0);
|
||||
if (props.getCoordinate) {
|
||||
chart.value.getZr().on("click", function (params) {
|
||||
var pixelPoint = [params.offsetX, params.offsetY];
|
||||
@ -44,11 +50,15 @@ async function updateSvg(svg, option) {
|
||||
value: dataPoint, // 當前座標值
|
||||
itemStyle: { color: "#0000FF" }, // 設為藍色
|
||||
});
|
||||
chart.value.setOption({
|
||||
series: {
|
||||
data: updatedData,
|
||||
},
|
||||
});
|
||||
setTimeout(() => {
|
||||
if (chart.value && !chart.value.isDisposed()) {
|
||||
chart.value.setOption({
|
||||
series: {
|
||||
data: updatedData,
|
||||
},
|
||||
});
|
||||
}
|
||||
}, 0);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
@ -64,10 +64,11 @@ watch(
|
||||
twMerge(
|
||||
'flex-col text-xl',
|
||||
cls,
|
||||
openChildren.includes(dataParentKey) || open ? 'flex' : 'hidden'
|
||||
openChildren.includes(d.key) || open ? 'flex' : 'hidden'
|
||||
)
|
||||
"
|
||||
v-for="d in data"
|
||||
:key="d.key"
|
||||
:data-parent="d.key"
|
||||
:open="open"
|
||||
>
|
||||
|
@ -9,7 +9,7 @@ const props = defineProps({
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
value: String,
|
||||
value: Object,
|
||||
isTopLabelExist: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
@ -73,7 +73,7 @@ const curWidth = computed(() => {
|
||||
:disabled="disabled"
|
||||
:readonly="readonly"
|
||||
:required="required"
|
||||
class="text-lg text-white bg-transparent w-full input input-bordered rounded-md px-3 border-info focus-within:border-info read-only:bg-base-300 read-only:text-zinc-500 read-only:border-0 read-only:focus-within:outline-0 read-only:focus:outline-0"
|
||||
class="text-lg text-white bg-transparent w-full input input-bordered rounded-md px-3 border-info focus-within:border-info read-only:bg-base-300 read-only:text-zinc-300 read-only:border-0 read-only:focus-within:outline-0 read-only:focus:outline-0"
|
||||
/>
|
||||
<input
|
||||
v-else
|
||||
@ -84,7 +84,7 @@ const curWidth = computed(() => {
|
||||
:disabled="disabled"
|
||||
:readonly="readonly"
|
||||
:required="required"
|
||||
class="text-lg text-white bg-transparent w-full input input-bordered rounded-md px-3 border-info focus-within:border-info read-only:bg-base-300 read-only:text-zinc-500 read-only:border-0 read-only:focus-within:outline-0 read-only:focus:outline-0"
|
||||
class="text-lg text-white bg-transparent w-full input input-bordered rounded-md px-3 border-info focus-within:border-info read-only:bg-base-300 read-only:text-zinc-300 read-only:border-0 read-only:focus-within:outline-0 read-only:focus:outline-0"
|
||||
/>
|
||||
<div :class="twMerge(isBottomLabelExist ? 'label' : '')">
|
||||
<span class="label-text-alt"><slot name="bottomLeft"></slot></span>
|
||||
|
@ -9,7 +9,7 @@ const props = defineProps({
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
value: String,
|
||||
value: Object,
|
||||
isTopLabelExist: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
|
@ -51,7 +51,7 @@ onMounted(() => {
|
||||
: 'focus-visible:outline-none backdrop:bg-transparent',
|
||||
)" :style="modalStyle" v-draggable="draggable">
|
||||
<div :class="twMerge(
|
||||
'modal-box static rounded-md border border-info py-5 px-6 overflow-y-scroll bg-normal',
|
||||
'modal-box static rounded-md border border-info py-5 px-6 overflow-y-auto bg-normal',
|
||||
modalClass
|
||||
)
|
||||
" :style="{ minWidth: isNaN(width) ? width : `${width}px` }">
|
||||
|
@ -4,7 +4,7 @@ import { twMerge } from "tailwind-merge";
|
||||
|
||||
const props = defineProps({
|
||||
name: String,
|
||||
value: String,
|
||||
value: Object,
|
||||
items: Array,
|
||||
isLabelExist: {
|
||||
type: Boolean,
|
||||
|
@ -1,5 +1,5 @@
|
||||
<script setup>
|
||||
import { defineProps, ref, computed, watch } from "vue";
|
||||
import { defineProps, ref, computed, watch, nextTick, watchEffect } from "vue";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
/* --------------------------------------------------------------
|
||||
options: [
|
||||
@ -18,7 +18,7 @@ const props = defineProps({
|
||||
Attribute: String,
|
||||
onChange: Function,
|
||||
selectClass: String,
|
||||
value: String || Number,
|
||||
value: Object,
|
||||
isTopLabelExist: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
@ -68,14 +68,12 @@ const selectOption = (value) => {
|
||||
isOpen.value = false;
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.value[props.name],
|
||||
(newKey) => {
|
||||
isOpen.value = false;
|
||||
selectedOption.value = props.options.find((option) => option.key == newKey);
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
watchEffect(() => {
|
||||
isOpen.value = false;
|
||||
selectedOption.value = props.options.find(
|
||||
(option) => option.key == props.value[props.name]
|
||||
);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@ -88,7 +86,7 @@ watch(
|
||||
<div
|
||||
:class="
|
||||
twMerge(
|
||||
'select rounded-md text-lg cursor-pointer h-fit',
|
||||
'select rounded-md text-lg cursor-pointer pt-2',
|
||||
disabled ? 'text-gray-300' : 'bg-transparent border-info'
|
||||
)
|
||||
"
|
||||
|
@ -18,7 +18,7 @@ const props = defineProps({
|
||||
Attribute: String,
|
||||
onChange: Function,
|
||||
selectClass: String,
|
||||
value: String || Number,
|
||||
value: Object,
|
||||
isTopLabelExist: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
@ -70,9 +70,7 @@ const props = defineProps({
|
||||
:class="twMerge(disabled ? `text-white` : 'text-dark')"
|
||||
:value="option.value || option.key || option"
|
||||
>
|
||||
<span>
|
||||
{{ option[Attribute] || option }}
|
||||
</span>
|
||||
</option>
|
||||
</select>
|
||||
<div :class="twMerge(isBottomLabelExist ? 'label' : '')">
|
||||
|
@ -3,7 +3,7 @@ import { defineProps } from "vue";
|
||||
|
||||
const props = defineProps({
|
||||
name: String,
|
||||
value: String,
|
||||
value: Object,
|
||||
placeholder: String,
|
||||
});
|
||||
</script>
|
||||
@ -15,7 +15,7 @@ const props = defineProps({
|
||||
<span class="label-text-alt"> <slot name="topRight"></slot></span>
|
||||
</div>
|
||||
<textarea
|
||||
class="textarea text-lg rounded-md border-info focus-within:border-info h-24"
|
||||
class="textarea text-lg rounded-md border-info focus-within:border-info h-40"
|
||||
:placeholder="placeholder"
|
||||
:name="name"
|
||||
v-model="value[name]"
|
||||
|
@ -93,8 +93,8 @@ const createSprites = async (dataVizExtn) => {
|
||||
const DataVizCore = Autodesk.DataVisualization.Core;
|
||||
const viewableType = DataVizCore.ViewableType.SPRITE;
|
||||
let spriteColor = new THREE.Color(0xffffff);
|
||||
const BASEURL = import.meta.env.VITE_FORGE_BASEURL;
|
||||
const spriteIconUrl = `${BASEURL}/hotspot.svg`;
|
||||
const BASEURL = import.meta.env.VITE_FILE_API_BASEURL;
|
||||
const spriteIconUrl = `${BASEURL}/dist/hotspot.svg`;
|
||||
const style = new DataVizCore.ViewableStyle(
|
||||
viewableType,
|
||||
spriteColor,
|
||||
|
@ -1,17 +1,22 @@
|
||||
<script setup>
|
||||
import { onMounted, ref } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import NavbarItem from "./NavbarItem.vue";
|
||||
import NavbarBuilding from "./NavbarBuilding.vue";
|
||||
import Logo from "@/assets/img/logo.svg";
|
||||
import useUserInfoStore from "@/stores/useUserInfoStore";
|
||||
import useBuildingStore from "@/stores/useBuildingStore";
|
||||
|
||||
import AlarmDrawer from "@/components/alarm/AlarmDrawer.vue";
|
||||
import NavbarLang from "./NavbarLang.vue";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
const user = ref("");
|
||||
const menuShow = ref(true);
|
||||
const router = useRouter();
|
||||
|
||||
const store = useUserInfoStore();
|
||||
const storeBuilding = useBuildingStore();
|
||||
onMounted(() => {
|
||||
const name = store.user.user_name;
|
||||
if (name) {
|
||||
@ -24,12 +29,26 @@ const toggleMenu = () => {
|
||||
};
|
||||
|
||||
const src = import.meta.env.MODE === "production" ? "./logo.svg" : Logo;
|
||||
|
||||
const logout = () => {
|
||||
document.cookie = "JWT-Authorization=; Max-Age=0";
|
||||
document.cookie = "user_name=; Max-Age=0";
|
||||
store.user.token = "";
|
||||
store.user.user_name = "";
|
||||
storeBuilding.deleteBuilding();
|
||||
router.push({ path: "/login" });
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<header class="navbar bg-dark text-light-info w-full relative z-50">
|
||||
<div class="navbar-start min-w-[480px] lg:min-w-[440px]">
|
||||
<div tabindex="0" role="button" class="btn btn-ghost lg:hidden" @click="toggleMenu">
|
||||
<div
|
||||
tabindex="0"
|
||||
role="button"
|
||||
class="btn btn-ghost lg:hidden"
|
||||
@click="toggleMenu"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-5 w-5"
|
||||
@ -93,12 +112,12 @@ const src = import.meta.env.MODE === "production" ? "./logo.svg" : Logo;
|
||||
class="dropdown-content translate-y-2 z-[100] menu py-3 shadow rounded w-32 bg-[#4c625e] border text-center"
|
||||
>
|
||||
<li class="text-white">
|
||||
<router-link
|
||||
to="logout"
|
||||
type="link"
|
||||
<a
|
||||
href="#"
|
||||
@click.prevent="logout"
|
||||
class="flex flex-col justify-center items-center"
|
||||
>{{ $t("sign_out") }}
|
||||
</router-link>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
@ -1,50 +1,48 @@
|
||||
<script setup>
|
||||
import { getBuildings } from "@/apis/building";
|
||||
import { onMounted, ref } from "vue";
|
||||
import { onMounted } from "vue";
|
||||
import useBuildingStore from "@/stores/useBuildingStore";
|
||||
|
||||
const store = useBuildingStore();
|
||||
|
||||
const getBui = async () => {
|
||||
console.log(store.buildings);
|
||||
const res = await getBuildings();
|
||||
store.buildings = res.data;
|
||||
store.selectedBuilding = res?.data[0];
|
||||
};
|
||||
|
||||
const selectBuilding = (bui) => {
|
||||
store.selectedBuilding = bui;
|
||||
store.selectedBuilding = bui; // 改變 selectedBuilding,watch 會自動更新資料
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
getBui();
|
||||
store.initialize(); // 初始化資料
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="dropdown dropdown-bottom ">
|
||||
<div
|
||||
tabindex="0"
|
||||
role="button"
|
||||
class="text-white ml-8 text-lg font-semiLight"
|
||||
>
|
||||
{{ store.selectedBuilding?.full_name }}
|
||||
<font-awesome-icon :icon="['fas', 'angle-down']" class="ml-1" />
|
||||
</div>
|
||||
<ul
|
||||
tabindex="0"
|
||||
class="dropdown-content left-8 translate-y-2 z-[1] menu py-3 shadow rounded bg-[#4c625e] border text-center"
|
||||
>
|
||||
<li
|
||||
class="text-white my-1 text-base"
|
||||
v-for="bui in store.buildings"
|
||||
:key="bui.building_tag"
|
||||
@click="selectBuilding(bui)"
|
||||
<template v-if="store.buildings.length > 1">
|
||||
<div class="dropdown dropdown-bottom">
|
||||
<div
|
||||
tabindex="0"
|
||||
role="button"
|
||||
class="text-white ml-8 text-lg font-semiLight"
|
||||
>
|
||||
{{ bui.full_name }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
{{ store.selectedBuilding?.full_name }}
|
||||
<font-awesome-icon :icon="['fas', 'angle-down']" class="ml-1" />
|
||||
</div>
|
||||
<ul
|
||||
tabindex="0"
|
||||
class="dropdown-content w-48 left-8 translate-y-2 z-[1] menu py-3 shadow rounded bg-[#4c625e] border text-center"
|
||||
>
|
||||
<li
|
||||
class="text-white my-1 text-base cursor-pointer"
|
||||
v-for="bui in store.buildings"
|
||||
:key="bui.building_tag"
|
||||
@click="selectBuilding(bui)"
|
||||
>
|
||||
{{ bui.full_name }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="text-white ml-8 text-lg font-semiLight">
|
||||
{{ store.selectedBuilding?.full_name }}
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
|
@ -2,7 +2,7 @@
|
||||
import { onMounted, ref, watch, computed } from "vue";
|
||||
import { AUTHPAGES } from "@/constant";
|
||||
import { getAuth, getAllSysSidebar } from "@/apis/building";
|
||||
import { getEnergySideBar } from "@/apis/energy";
|
||||
import { getSideBar } from "@/apis/energy";
|
||||
import useBuildingStore from "@/stores/useBuildingStore";
|
||||
import useUserInfoStore from "@/stores/useUserInfoStore";
|
||||
import { useI18n } from "vue-i18n";
|
||||
@ -37,20 +37,20 @@ const authPages = computed(() =>
|
||||
);
|
||||
|
||||
const open = ref(false);
|
||||
const getSubMonitorPage = async (building) => {
|
||||
const res = await getAllSysSidebar();
|
||||
const getSubMonitorPage = async (building_guid) => {
|
||||
const res = await getAllSysSidebar(building_guid);
|
||||
buildingStore.mainSubSys = res.data.history_Main_Systems;
|
||||
menu_array.value = res.data.history_Main_Systems;
|
||||
};
|
||||
const getSubEnergyPage = async () => {
|
||||
const res = await getEnergySideBar();
|
||||
const getSubPage = async (system_type) => {
|
||||
const res = await getSideBar(system_type);
|
||||
menu_array.value = res.data;
|
||||
};
|
||||
const showDrawer = (authCode) => {
|
||||
const showDrawer = async (authCode) => {
|
||||
if (authCode === "PF1") {
|
||||
getSubMonitorPage();
|
||||
} else if (authCode === "PF2") {
|
||||
getSubEnergyPage();
|
||||
await getSubMonitorPage(buildingStore.selectedBuilding.building_guid);
|
||||
} else if (authCode === "PF2" || authCode === "PF11") {
|
||||
await getSubPage(authCode === "PF2" ? "Energy" : "Setting");
|
||||
}
|
||||
currentAuthCode.value = authCode;
|
||||
open.value = true;
|
||||
@ -71,7 +71,7 @@ watch(
|
||||
() => buildingStore.selectedBuilding,
|
||||
(newVal) => {
|
||||
if (newVal !== null) {
|
||||
getSubMonitorPage(newVal.building_tag);
|
||||
getSubMonitorPage(newVal.building_guid);
|
||||
}
|
||||
}
|
||||
);
|
||||
@ -105,7 +105,11 @@ onMounted(() => {
|
||||
:key="page.authCode"
|
||||
>
|
||||
<a
|
||||
v-if="page.authCode === 'PF1' || page.authCode === 'PF2'"
|
||||
v-if="
|
||||
page.authCode === 'PF1' ||
|
||||
page.authCode === 'PF2' ||
|
||||
page.authCode === 'PF11'
|
||||
"
|
||||
@click="showDrawer(page.authCode)"
|
||||
:class="
|
||||
twMerge(
|
||||
@ -115,6 +119,9 @@ onMounted(() => {
|
||||
: '',
|
||||
page.authCode === 'PF2' &&
|
||||
route.fullPath.includes('/energyManagement')
|
||||
? 'router-link-active router-link-exact-active'
|
||||
: '',
|
||||
page.authCode === 'PF11' && route.fullPath.includes('/setting')
|
||||
? 'router-link-active router-link-exact-active'
|
||||
: ''
|
||||
)
|
||||
@ -168,18 +175,26 @@ onMounted(() => {
|
||||
<a-menu-item
|
||||
v-for="sub in currentAuthCode === 'PF1'
|
||||
? main.history_Sub_systems
|
||||
: currentAuthCode === 'PF2'
|
||||
? main.sub
|
||||
: main.sub"
|
||||
:key="sub.sub_system_tag+`_`+sub.type"
|
||||
:key="sub.sub_system_tag + `_` + sub.type"
|
||||
@click="() => onClose()"
|
||||
>
|
||||
<router-link
|
||||
:to="{
|
||||
name:
|
||||
currentAuthCode === 'PF2' ? 'energyManagement' : 'sub_system',
|
||||
currentAuthCode === 'PF2'
|
||||
? 'energyManagement'
|
||||
: currentAuthCode === 'PF11'
|
||||
? 'setting'
|
||||
: 'sub_system',
|
||||
params: {
|
||||
main_system_id: main.main_system_tag,
|
||||
sub_system_id: sub.sub_system_tag,
|
||||
...(currentAuthCode === 'PF2' ? { type: sub.type } : { floor_id: 'main' }),
|
||||
...(currentAuthCode === 'PF2' || currentAuthCode === 'PF11'
|
||||
? { type: sub.type }
|
||||
: { floor_id: 'main' }),
|
||||
},
|
||||
}"
|
||||
>
|
||||
|
@ -8,7 +8,10 @@
|
||||
"table": {
|
||||
"no_data": "表中数据为空",
|
||||
"in_otal": "笔资料",
|
||||
"skip_to": "跳至"
|
||||
"skip_to": "跳至",
|
||||
"serial_number": "序列号",
|
||||
"name": "名称",
|
||||
"time": "时间"
|
||||
},
|
||||
"upload": {
|
||||
"title": "选择一个文件或拖放到这里",
|
||||
@ -20,14 +23,35 @@
|
||||
"elec_consumption_comparison": "用电量比较",
|
||||
"elec_consumption_comparison_trend": "用电量比较趋势",
|
||||
"electricity_consumption": "用电量",
|
||||
"today_electricity_consumption": "今日用电量",
|
||||
"yesterday_electricity_consumption": "昨天用电量",
|
||||
"today_electricity_consumption": "今日用电量 ( kWH )",
|
||||
"yesterday_electricity_consumption": "昨天用电量 ( kWH )",
|
||||
"instant_power": "即时功率 ( kW )",
|
||||
"instant_contract_capacity_ratio": "契約容量佔比 ( % )",
|
||||
"this_last_week": "本周/上周",
|
||||
"thisweek_electricity_consumption": "本周用电量",
|
||||
"lastweek_electricity_consumption": "上周用电量",
|
||||
"one_hour": "1小时",
|
||||
"four_hour": "4小时",
|
||||
"eight_hour": "8小时"
|
||||
"eight_hour": "8小时",
|
||||
"energy_ranking": "能耗排行",
|
||||
"last_30_days_energy_trend": "近30天能耗趋势",
|
||||
"today_energy_consumption": "本日能耗",
|
||||
"this_month_energy_consumption": "本月能耗",
|
||||
"relative_energy_consumption": "环比能耗",
|
||||
"daily_relative_change": "日环比",
|
||||
"weekly_relative_change": "周环比",
|
||||
"monthly_relative_change": "月环比",
|
||||
"yearly_relative_change": "年环比",
|
||||
"today": "今日",
|
||||
"yesterday": "昨日",
|
||||
"this_week": "本周",
|
||||
"last_week": "上周",
|
||||
"this_month": "本月",
|
||||
"last_month": "上月",
|
||||
"this_year": "今年",
|
||||
"last_year": "去年",
|
||||
"work_order": "工单",
|
||||
"system_status": "系统状态"
|
||||
},
|
||||
"history": {
|
||||
"title": "历史资料",
|
||||
@ -98,12 +122,45 @@
|
||||
"ranking": "排名",
|
||||
"subtotal": "小计",
|
||||
"unit_price": "单价",
|
||||
"total_amount": "金额总计"
|
||||
"total_amount": "金额总计",
|
||||
"elec_price_list": "电价表",
|
||||
"residential": "住宅型",
|
||||
"standard": "标准型",
|
||||
"simple_elec_price_two_stage": "简易型时间电价二段式",
|
||||
"simple_elec_price_three_stage": "简易型时间电价三段式",
|
||||
"classification": "分类",
|
||||
"summer_months": "夏月",
|
||||
"non_summer_months": "非夏月",
|
||||
"time_outside_summer_months": "夏月以外的时间",
|
||||
"basic_elec_charge": "基本电费",
|
||||
"charged_per_household": "按户计收",
|
||||
"per_household_month": "每户每月",
|
||||
"mon_to_friday": "周一~周五",
|
||||
"peak_hours": "尖峰时间",
|
||||
"semi_peak_hours": "半尖峰时间",
|
||||
"off_peak_hours": "离峰时间",
|
||||
"price_per_kwh": "每度",
|
||||
"all_day": "全日",
|
||||
"sat_sun_off_peak_days": "周六、周日及离峰日",
|
||||
"usage_over_2000kwh": "每月总度数超过2000度之部分",
|
||||
"add": "加",
|
||||
"standard_time_of_use_tariff_2_stage": "标准型时间电价二段式",
|
||||
"standard_time_of_use_tariff_3_stage": "标准型时间电价三段式",
|
||||
"single_phase": "单相",
|
||||
"three_phase": "三相",
|
||||
"frequent_contract": "经常契约",
|
||||
"per_kw_per_month": "每瓩每月",
|
||||
"non_summer_contract": "非夏日契约",
|
||||
"saturday_semi_peak_contract": "周六半尖峰契约",
|
||||
"off_peak_contract": "离峰契约",
|
||||
"variable_electricity_charge": "流动电费",
|
||||
"saturday": "周六",
|
||||
"sunday_and_off_peak_days": "周日及离峰日"
|
||||
},
|
||||
"alarm": {
|
||||
"title": "显示警告",
|
||||
"notify": "异常通知",
|
||||
"number": "异常编号",
|
||||
"number": "异常ID",
|
||||
"category": "异常类别",
|
||||
"device_name": "设备名称",
|
||||
"message": "异常讯息",
|
||||
@ -121,9 +178,10 @@
|
||||
"end_date": "结束日期",
|
||||
"building_and_floor": "栋别-楼层",
|
||||
"uuid": "异常ID",
|
||||
"alarmClass": "异常类别",
|
||||
"alarmClass": "告警条件",
|
||||
"device_name": "设备名称",
|
||||
"device_number": "设备编号",
|
||||
"device_point_name": "点位名称",
|
||||
"date": "发生日期",
|
||||
"time": "发生时间",
|
||||
"error_msg": "异常原因",
|
||||
@ -150,10 +208,12 @@
|
||||
"qualifications": "限定条件",
|
||||
"upper_limit": "上限",
|
||||
"lower_limit": "下限",
|
||||
"delay": "持续秒数",
|
||||
"highDelay": "上限持续秒数",
|
||||
"lowDelay": "下限持续秒数",
|
||||
"warning_method": "警示方式",
|
||||
"warning_time": "警示时间",
|
||||
"warning_value": "警示值",
|
||||
"operation": "功能",
|
||||
"alarm_settings": "异常设定",
|
||||
"time_setting": "时间设定",
|
||||
@ -177,7 +237,11 @@
|
||||
"friday": "星期五",
|
||||
"saturday": "星期六",
|
||||
"schedule_name": "时段名称",
|
||||
"schedule_content": "时段内容"
|
||||
"schedule_content": "时段内容",
|
||||
"reorganization": "MQTT 告警重整",
|
||||
"online": "在线",
|
||||
"offline": "离线",
|
||||
"alarm": "告警"
|
||||
},
|
||||
"operation": {
|
||||
"title": "运维管理",
|
||||
@ -206,6 +270,8 @@
|
||||
"responsible_vendor": "负责厂商",
|
||||
"not_completed": "未完成",
|
||||
"completed": "已完成",
|
||||
"complete": "已完成",
|
||||
"incomplete": "未完成",
|
||||
"worker_id": "工作人员编号",
|
||||
"notice": "注意事项",
|
||||
"result_description": "结果描述",
|
||||
@ -274,7 +340,8 @@
|
||||
"index": "编号",
|
||||
"floor_plan": "平面图",
|
||||
"department": "部门",
|
||||
"department_name": "部门名称"
|
||||
"department_name": "部门名称",
|
||||
"building": "栋别"
|
||||
},
|
||||
"accountManagement": {
|
||||
"account_title": "帐号管理",
|
||||
@ -324,12 +391,38 @@
|
||||
"confirm": "确认",
|
||||
"restore": "复原",
|
||||
"stop_edit": "停止修改",
|
||||
"start_edit": "开始修改"
|
||||
"start_edit": "开始修改",
|
||||
"convert": "轉換"
|
||||
},
|
||||
"msg": {
|
||||
"sure_to_delete": "是否确认删除该项目?",
|
||||
"sure_to_delete_permanent": "是否确认永久删除该项目?",
|
||||
"delete_success": "删除成功",
|
||||
"delete_failed": "删除失败"
|
||||
"delete_failed": "删除失败",
|
||||
"mqtt_refresh": "重新设定成功",
|
||||
"schema_name_required": "架构名称栏位必填",
|
||||
"incorrect_format":"格式不正确",
|
||||
"send_successfully":"送出成功",
|
||||
"edit_successfully":"修改成功"
|
||||
},
|
||||
"setting": {
|
||||
"electricity_meter": "电表",
|
||||
"MQTT_parse": "MQTT 解析",
|
||||
"schema": "架构",
|
||||
"point": "点位",
|
||||
"description": "描述",
|
||||
"IoT_point_name": "IoT 点位名称",
|
||||
"IoT_point_code": "IoT 点位代号",
|
||||
"number_of_decimal_places": "小数位数",
|
||||
"boolean_value": "布林值",
|
||||
"hide_point": "点位显示",
|
||||
"hide_switch": "switch 功能",
|
||||
"switch_on_message": "switch 开启时传送的讯息",
|
||||
"switch_off_message": "switch 关闭时传送的讯息",
|
||||
"schema_name": "架构名称",
|
||||
"IoT_point_structure": "IoT点位结构",
|
||||
"system_point_name": "系统点位名称",
|
||||
"json_format_text": "请贴上 JSON 格式数据",
|
||||
"json_click_text": "请在左侧输入JSON并点选转换按钮"
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,10 @@
|
||||
"table": {
|
||||
"no_data": "表中數據為空",
|
||||
"in_otal": "筆資料",
|
||||
"skip_to": "跳至"
|
||||
"skip_to": "跳至",
|
||||
"serial_number": "序號",
|
||||
"name": "姓名",
|
||||
"time": "時間"
|
||||
},
|
||||
"upload": {
|
||||
"title": "選擇一個文件或拖放到這裡",
|
||||
@ -20,14 +23,35 @@
|
||||
"elec_consumption_comparison": "用電量比較",
|
||||
"elec_consumption_comparison_trend": "用電量比較趨勢",
|
||||
"electricity_consumption": "用電量",
|
||||
"today_electricity_consumption": "今日用電量",
|
||||
"yesterday_electricity_consumption": "昨天用電量",
|
||||
"today_electricity_consumption": "今日用電量 ( kWH )",
|
||||
"yesterday_electricity_consumption": "昨天用電量 ( kWH )",
|
||||
"instant_power": "即時功率 ( kW )",
|
||||
"instant_contract_capacity_ratio": "契約容量佔比 ( % )",
|
||||
"this_last_week": "本週/上週",
|
||||
"thisweek_electricity_consumption": "本周用電量",
|
||||
"lastweek_electricity_consumption": "上週用電量",
|
||||
"one_hour": "1小時",
|
||||
"four_hour": "4小時",
|
||||
"eight_hour": "8小時"
|
||||
"eight_hour": "8小時",
|
||||
"energy_ranking": "能耗排行",
|
||||
"last_30_days_energy_trend": "近30天能耗趨勢",
|
||||
"today_energy_consumption": "本日能耗",
|
||||
"this_month_energy_consumption": "本月能耗",
|
||||
"relative_energy_consumption": "環比能耗",
|
||||
"daily_relative_change": "日環比",
|
||||
"weekly_relative_change": "周環比",
|
||||
"monthly_relative_change": "月環比",
|
||||
"yearly_relative_change": "年環比",
|
||||
"today": "今日",
|
||||
"yesterday": "昨日",
|
||||
"this_week": "本周",
|
||||
"last_week": "上周",
|
||||
"this_month": "本月",
|
||||
"last_month": "上月",
|
||||
"this_year": "今年",
|
||||
"last_year": "去年",
|
||||
"work_order": "工單",
|
||||
"system_status": "系統狀態"
|
||||
},
|
||||
"history": {
|
||||
"title": "歷史資料",
|
||||
@ -98,12 +122,45 @@
|
||||
"ranking": "排名",
|
||||
"subtotal": "小計",
|
||||
"unit_price": "單價",
|
||||
"total_amount": "金額總計"
|
||||
"total_amount": "金額總計",
|
||||
"elec_price_list": "電價表",
|
||||
"residential": "住宅型",
|
||||
"standard": "標準型",
|
||||
"simple_elec_price_two_stage": "簡易型時間電價二段式",
|
||||
"simple_elec_price_three_stage": "簡易型時間電價三段式",
|
||||
"classification": "分類",
|
||||
"summer_months": "夏月",
|
||||
"non_summer_months": "非夏月",
|
||||
"time_outside_summer_months": "夏月以外的時間",
|
||||
"basic_elec_charge": "基本電費",
|
||||
"charged_per_household": "按戶計收",
|
||||
"per_household_month": "每戶每月",
|
||||
"mon_to_friday": "週一~週五",
|
||||
"peak_hours": "尖峰時間",
|
||||
"semi_peak_hours": "半尖峰時間",
|
||||
"off_peak_hours": "離峰時間",
|
||||
"price_per_kwh": "每度",
|
||||
"all_day": "全日",
|
||||
"sat_sun_off_peak_days": "週六、週日及離峰日",
|
||||
"usage_over_2000kwh": "每月總度數超過2000度之部分",
|
||||
"add": "加",
|
||||
"standard_time_of_use_tariff_2_stage": "標準型時間電價二段式",
|
||||
"standard_time_of_use_tariff_3_stage": "標準型時間電價三段式",
|
||||
"single_phase": "單相",
|
||||
"three_phase": "三相",
|
||||
"frequent_contract": "經常契約",
|
||||
"per_kw_per_month": "每瓩每月",
|
||||
"non_summer_contract": "非夏日契約",
|
||||
"saturday_semi_peak_contract": "週六半尖峰契約",
|
||||
"off_peak_contract": "離峰契約",
|
||||
"variable_electricity_charge": "流動電費",
|
||||
"saturday": "週六",
|
||||
"sunday_and_off_peak_days": "週日及離峰日"
|
||||
},
|
||||
"alarm": {
|
||||
"title": "顯示警告",
|
||||
"notify": "異常通知",
|
||||
"number": "異常編號",
|
||||
"number": "異常ID",
|
||||
"category": "異常類別",
|
||||
"device_name": "設備名稱",
|
||||
"message": "異常訊息",
|
||||
@ -121,9 +178,10 @@
|
||||
"end_date": "結束日期",
|
||||
"building_and_floor": "棟別-樓層",
|
||||
"uuid": "異常ID",
|
||||
"alarmClass": "異常類別",
|
||||
"alarmClass": "告警條件",
|
||||
"device_name": "設備名稱",
|
||||
"device_number": "設備編號",
|
||||
"device_point_name": "點位名稱",
|
||||
"date": "發生日期",
|
||||
"time": "發生時間",
|
||||
"error_msg": "異常原因",
|
||||
@ -150,10 +208,12 @@
|
||||
"qualifications": "限定條件",
|
||||
"upper_limit": "上限",
|
||||
"lower_limit": "下限",
|
||||
"delay": "持續秒數",
|
||||
"highDelay": "上限持續秒數",
|
||||
"lowDelay": "下限持續秒數",
|
||||
"warning_method": "警示方式",
|
||||
"warning_time": "警示時間",
|
||||
"warning_value": "警示值",
|
||||
"operation": "功能",
|
||||
"alarm_settings": "異常設定",
|
||||
"time_setting": "時間設定",
|
||||
@ -177,7 +237,11 @@
|
||||
"friday": "星期五",
|
||||
"saturday": "星期六",
|
||||
"schedule_name": "時段名稱",
|
||||
"schedule_content": "時段內容"
|
||||
"schedule_content": "時段內容",
|
||||
"reorganization": "MQTT 告警重整",
|
||||
"online": "在線",
|
||||
"offline": "離線",
|
||||
"alarm": "告警"
|
||||
},
|
||||
"operation": {
|
||||
"title": "運維管理",
|
||||
@ -206,6 +270,8 @@
|
||||
"responsible_vendor": "負責廠商",
|
||||
"not_completed": "未完成",
|
||||
"completed": "已完成",
|
||||
"complete": "已完成",
|
||||
"incomplete": "未完成",
|
||||
"worker_id": "工作人員編號",
|
||||
"notice": "注意事項",
|
||||
"result_description": "結果描述",
|
||||
@ -274,7 +340,8 @@
|
||||
"index": "編號",
|
||||
"floor_plan": "平面圖",
|
||||
"department": "部門",
|
||||
"department_name": "部門名稱"
|
||||
"department_name": "部門名稱",
|
||||
"building": "棟別"
|
||||
},
|
||||
"accountManagement": {
|
||||
"account_title": "帳號管理",
|
||||
@ -324,12 +391,38 @@
|
||||
"confirm": "確認",
|
||||
"restore": "復原",
|
||||
"stop_edit": "停止修改",
|
||||
"start_edit": "開始修改"
|
||||
"start_edit": "開始修改",
|
||||
"convert": "轉換"
|
||||
},
|
||||
"msg": {
|
||||
"sure_to_delete": "是否確認刪除該項目?",
|
||||
"sure_to_delete_permanent": "是否確認永久刪除該項目?",
|
||||
"delete_success": "刪除成功",
|
||||
"delete_failed": "刪除失敗"
|
||||
"delete_failed": "刪除失敗",
|
||||
"mqtt_refresh": "重新設定成功",
|
||||
"schema_name_required": "架構名稱欄位必填",
|
||||
"incorrect_format":"格式不正確",
|
||||
"send_successfully":"送出成功",
|
||||
"edit_successfully":"修改成功"
|
||||
},
|
||||
"setting": {
|
||||
"electricity_meter":"電表",
|
||||
"MQTT_parse": "MQTT 解析",
|
||||
"schema": "架構",
|
||||
"point": "點位",
|
||||
"description": "描述",
|
||||
"IoT_point_name": "IoT 點位名稱",
|
||||
"IoT_point_code": "IoT 點位代號",
|
||||
"number_of_decimal_places": "小數位數",
|
||||
"boolean_value": "布林值",
|
||||
"hide_point": "點位顯示",
|
||||
"hide_switch": "switch 功能",
|
||||
"switch_on_message": "switch 開啟時傳送的訊息",
|
||||
"switch_off_message": "switch 關閉時傳送的訊息",
|
||||
"schema_name": "架構名稱",
|
||||
"IoT_point_structure": "IoT點位結構",
|
||||
"system_point_name": "系統點位名稱",
|
||||
"json_format_text": "請貼上 JSON 格式數據",
|
||||
"json_click_text": "請在左側輸入JSON並點選轉換按鈕"
|
||||
}
|
||||
}
|
||||
|
@ -8,13 +8,51 @@
|
||||
"table": {
|
||||
"no_data": "No data",
|
||||
"in_otal": "items in total",
|
||||
"skip_to": "Skip to"
|
||||
"skip_to": "Skip to",
|
||||
"serial_number": "Serial Number",
|
||||
"name": "Name",
|
||||
"time": "Time"
|
||||
},
|
||||
"upload": {
|
||||
"title": "Select a file or drag and drop here",
|
||||
"description": "File size cannot exceed 10MB",
|
||||
"formats": "File formats"
|
||||
},
|
||||
"dashboard": {
|
||||
"yesterday_today": "Yesterday / Today's",
|
||||
"elec_consumption_comparison": "Electricity Consumption Comparison",
|
||||
"elec_consumption_comparison_trend": "Electricity Consumption Comparison Trend",
|
||||
"electricity_consumption": "electricity consumption",
|
||||
"today_electricity_consumption": "Today's electricity consumption in kWH",
|
||||
"yesterday_electricity_consumption": "Yesterday's electricity consumption in kWH",
|
||||
"instant_power": "Instant power kW",
|
||||
"instant_contract_capacity_ratio": "Instant contract capacity ratio %",
|
||||
"this_last_week": "This Week's / Last Week's",
|
||||
"thisweek_electricity_consumption": "This week’s electricity consumption",
|
||||
"lastweek_electricity_consumption": "Last week’s electricity consumption",
|
||||
"one_hour": "1 hour",
|
||||
"four_hour": "4 hour",
|
||||
"eight_hour": "8 hour",
|
||||
"energy_ranking": "Energy consumption ranking",
|
||||
"last_30_days_energy_trend": "Energy consumption trend for the past 30 days",
|
||||
"today_energy_consumption": "Today",
|
||||
"this_month_energy_consumption": "This month",
|
||||
"relative_energy_consumption": "Energy consumption trend",
|
||||
"daily_relative_change": "Daily",
|
||||
"weekly_relative_change": "Weekly",
|
||||
"monthly_relative_change": "Monthly",
|
||||
"yearly_relative_change": "Yearly",
|
||||
"today": "Today",
|
||||
"yesterday": "Yesterday",
|
||||
"this_week": "This week",
|
||||
"last_week": "Last week",
|
||||
"this_month": "This month",
|
||||
"last_month": "Last month",
|
||||
"this_year": "This year",
|
||||
"last_year": "Last year",
|
||||
"work_order": "Work Order",
|
||||
"system_status": "System Status"
|
||||
},
|
||||
"history": {
|
||||
"title": "Historical Data",
|
||||
"building_name": "Building",
|
||||
@ -33,20 +71,6 @@
|
||||
"end_date": "End date",
|
||||
"end_time": "End time"
|
||||
},
|
||||
"dashboard": {
|
||||
"yesterday_today": "Yesterday / Today's",
|
||||
"elec_consumption_comparison": "Electricity Consumption Comparison",
|
||||
"elec_consumption_comparison_trend": "Electricity Consumption Comparison Trend",
|
||||
"electricity_consumption": "electricity consumption",
|
||||
"today_electricity_consumption": "Today’s electricity consumption",
|
||||
"yesterday_electricity_consumption": "Yesterday’s electricity consumption",
|
||||
"this_last_week": "This Week's / Last Week's",
|
||||
"thisweek_electricity_consumption": "This week’s electricity consumption",
|
||||
"lastweek_electricity_consumption": "Last week’s electricity consumption",
|
||||
"one_hour": "1 hour",
|
||||
"four_hour": "4 hour",
|
||||
"eight_hour": "8 hour"
|
||||
},
|
||||
"system": {
|
||||
"status": "Status",
|
||||
"details": "Details",
|
||||
@ -98,7 +122,40 @@
|
||||
"ranking": "Ranking",
|
||||
"subtotal": "Subtotal",
|
||||
"unit_price": "Unit price",
|
||||
"total_amount": "Total amount"
|
||||
"total_amount": "Total amount",
|
||||
"elec_price_list": "Electricity Price List",
|
||||
"residential": "Residential",
|
||||
"standard": "Standard",
|
||||
"simple_elec_price_two_stage": "Simple Time-of-Use Electricity Price (Two-Tier)",
|
||||
"simple_elec_price_three_stage": "Simple Time-of-Use Electricity Price (Three-Tier)",
|
||||
"classification": "Classification",
|
||||
"summer_months": "Summer Months",
|
||||
"non_summer_months": "Non-Summer Months",
|
||||
"time_outside_summer_months": "Time Outside Summer Months",
|
||||
"basic_elec_charge": "Basic Electricity Charge",
|
||||
"charged_per_household": "Charged Per Household",
|
||||
"per_household_month": "Per Household Per Month",
|
||||
"mon_to_friday": "Monday to Friday",
|
||||
"peak_hours": "Peak Hours",
|
||||
"semi_peak_hours": "Semi-Peak Hours",
|
||||
"off_peak_hours": "Off-Peak Hours",
|
||||
"price_per_kwh": "Price Per kWh",
|
||||
"all_day": "All Day",
|
||||
"sat_sun_off_peak_days": "Saturday, Sunday, and Off-Peak Days",
|
||||
"usage_over_2000kwh": "Usage Over 2000 kWh Per Month",
|
||||
"add": "Add",
|
||||
"standard_time_of_use_tariff_2_stage": "Standard Time-of-Use Tariff (Two-Tier)",
|
||||
"standard_time_of_use_tariff_3_stage": "Standard Time-of-Use Tariff (Three-Tier)",
|
||||
"single_phase": "Single Phase",
|
||||
"three_phase": "Three Phase",
|
||||
"frequent_contract": "Demand Charge",
|
||||
"per_kw_per_month": "Per kW Per Month",
|
||||
"non_summer_contract": "Non-Summer Demand Charge",
|
||||
"saturday_semi_peak_contract": "Saturday Semi-Peak Demand Charge",
|
||||
"off_peak_contract": "Off-Peak Demand Charge",
|
||||
"variable_electricity_charge": "Variable Electricity Charge",
|
||||
"saturday": "Saturday",
|
||||
"sunday_and_off_peak_days": "Sunday and Off-Peak Days"
|
||||
},
|
||||
"alarm": {
|
||||
"title": "Warning",
|
||||
@ -121,9 +178,10 @@
|
||||
"end_date": "End Date",
|
||||
"building_and_floor": "Building - Floor",
|
||||
"uuid": "Exception ID",
|
||||
"alarmClass": "Exception Category",
|
||||
"alarmClass": "Alarm Conditions",
|
||||
"device_name": "Device Name",
|
||||
"device_number": "Device Number",
|
||||
"device_point_name": "Point Name",
|
||||
"date": "Occurrence Date",
|
||||
"time": "Occurrence Time",
|
||||
"error_msg": "Abnormal Cause",
|
||||
@ -150,10 +208,12 @@
|
||||
"qualifications": "Qualifications",
|
||||
"upper_limit": "Upper Limit",
|
||||
"lower_limit": "Lower Limit",
|
||||
"delay": "Duration (s)",
|
||||
"highDelay": "Max Duration (s)",
|
||||
"lowDelay": "Min Duration (s)",
|
||||
"warning_method": "Warning Method",
|
||||
"warning_time": "Warning Time",
|
||||
"warning_value": "Warning Value",
|
||||
"operation": "Function",
|
||||
"alarm_settings": "Abnormal Alarm Settings",
|
||||
"time_setting": "Time Setting",
|
||||
@ -177,7 +237,11 @@
|
||||
"friday": "Friday",
|
||||
"saturday": "Saturday",
|
||||
"schedule_name": "Time period name",
|
||||
"schedule_content": "Time period content"
|
||||
"schedule_content": "Time period content",
|
||||
"reorganization": "MQTT Alarm Reorganization",
|
||||
"online": "Online",
|
||||
"offline": "Offline",
|
||||
"alarm": "Alarm"
|
||||
},
|
||||
"operation": {
|
||||
"title": "Operation And Maintenance Management",
|
||||
@ -206,6 +270,8 @@
|
||||
"responsible_vendor": "Responsible Vendor",
|
||||
"not_completed": "Not Completed",
|
||||
"completed": "Completed",
|
||||
"complete": "Comp",
|
||||
"incomplete": "Inc",
|
||||
"worker_id": "Worker ID",
|
||||
"notice": "Notice",
|
||||
"result_description": "Result Description",
|
||||
@ -274,7 +340,8 @@
|
||||
"index": "Serial Number",
|
||||
"floor_plan": "Floor Plan",
|
||||
"department": "Department",
|
||||
"department_name": "Department Name"
|
||||
"department_name": "Department Name",
|
||||
"building": "Building"
|
||||
},
|
||||
"accountManagement": {
|
||||
"account_title": "Account Management",
|
||||
@ -324,12 +391,38 @@
|
||||
"confirm": "Confirm",
|
||||
"restore": "Restore",
|
||||
"stop_edit": "Stop editing",
|
||||
"start_edit": "Start editing"
|
||||
"start_edit": "Start editing",
|
||||
"convert": "Convert"
|
||||
},
|
||||
"msg": {
|
||||
"sure_to_delete": "Are you sure to delete this item?",
|
||||
"sure_to_delete_permanent": "Are you sure you want to permanently delete this item?",
|
||||
"delete_success": "Delete successfully",
|
||||
"delete_failed": "Delete failed"
|
||||
"delete_failed": "Delete failed",
|
||||
"mqtt_refresh": "MQTT reset successful",
|
||||
"schema_name_required": "The schema name field is required",
|
||||
"incorrect_format":"Incorrect format",
|
||||
"send_successfully":"Sent successfully",
|
||||
"edit_successfully":"Edited successfully"
|
||||
},
|
||||
"setting": {
|
||||
"electricity_meter": "Electricity Meter",
|
||||
"MQTT_parse": "MQTT Parse",
|
||||
"schema": "Schema",
|
||||
"point": "Point",
|
||||
"description": "Description",
|
||||
"IoT_point_name": "IoT Point Name",
|
||||
"IoT_point_code": "IoT Point Code",
|
||||
"number_of_decimal_places": "Number of Decimal Places",
|
||||
"boolean_value": "Boolean Value",
|
||||
"hide_point": "Point Display",
|
||||
"hide_switch": "Switch Function",
|
||||
"switch_on_message": "Switch On Message",
|
||||
"switch_off_message": "Switch Off Message",
|
||||
"schema_name": "Schema name",
|
||||
"IoT_point_structure": "IoT Point Structure",
|
||||
"system_point_name": "System Point Name",
|
||||
"json_format_text": "Please paste JSON format data",
|
||||
"json_click_text": "Please enter JSON on the left and click the conversion button"
|
||||
}
|
||||
}
|
||||
|
@ -61,4 +61,10 @@ export const AUTHPAGES = [
|
||||
pageName: "ProductSetting",
|
||||
navigate: "/productSetting",
|
||||
},
|
||||
{
|
||||
authCode: "PF11",
|
||||
icon: "cog",
|
||||
pageName: "Setting",
|
||||
navigate: "/Setting",
|
||||
},
|
||||
];
|
||||
|
@ -59,8 +59,13 @@ import {
|
||||
faEyeSlash,
|
||||
faGlobe,
|
||||
faDownload,
|
||||
faStream
|
||||
faStream,
|
||||
faSave,
|
||||
faCrown,
|
||||
faClock,
|
||||
faCheckCircle
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import { faCircle,faPaperPlane } from "@fortawesome/free-regular-svg-icons";
|
||||
|
||||
/* add icons to the library */
|
||||
library.add(
|
||||
@ -120,7 +125,13 @@ library.add(
|
||||
faEyeSlash,
|
||||
faGlobe,
|
||||
faDownload,
|
||||
faStream
|
||||
faStream,
|
||||
faSave,
|
||||
faCrown,
|
||||
faClock,
|
||||
faCheckCircle,
|
||||
faCircle,
|
||||
faPaperPlane
|
||||
);
|
||||
|
||||
export default library;
|
||||
|
@ -6,7 +6,7 @@ import useForgeHeatmap from "./useForgeHeatmap";
|
||||
import useForgeFloor from "./useForgeFloor";
|
||||
|
||||
export default function useForgeSprite() {
|
||||
const { subscribeData } = inject("system_deviceList");
|
||||
const { subscribeData, realtimeData } = inject("system_deviceList");
|
||||
const { getCurrentInfoModalData, clearSelectedDeviceInfo, selected_dbid } =
|
||||
inject("system_selectedDevice");
|
||||
const forgeViewer = ref(null);
|
||||
@ -25,7 +25,7 @@ export default function useForgeSprite() {
|
||||
forgeViewer.value.navigation.setView(newPosition, newTarget);
|
||||
|
||||
// 確保 Home 視角
|
||||
forgeViewer.value.autocam.setCurrentViewAsHome(true);
|
||||
forgeViewer.value.autocam.setCurrentViewAsHome(true);
|
||||
};
|
||||
|
||||
const updateDataVisualization = async (viewer) => {
|
||||
@ -65,6 +65,23 @@ export default function useForgeSprite() {
|
||||
|
||||
const { flatSubData } = useSystemShowData();
|
||||
|
||||
// 根據設備取得即時狀態顏色
|
||||
const getDeviceRealtimeColor = (d) => {
|
||||
if (d.full_name === "SmartSocket-AA001") return "#ff0000";
|
||||
if (
|
||||
d.full_name === "SmartSocket-AA003" ||
|
||||
d.full_name === "SmartSocket-AA004"
|
||||
)
|
||||
return "#888888";
|
||||
const realtimeDevice = realtimeData?.value?.find(
|
||||
(item) => item.device_number === d.device_number
|
||||
);
|
||||
const state = realtimeDevice?.state || "";
|
||||
if (state === "offnormal" || state === "")
|
||||
return d.device_close_color || "#999999";
|
||||
return d.device_normal_color || "#009100";
|
||||
};
|
||||
|
||||
// 創建 sprites
|
||||
const createSprites = async () => {
|
||||
if (dataVizExtn.value) {
|
||||
@ -72,30 +89,27 @@ export default function useForgeSprite() {
|
||||
const DataVizCore = Autodesk.DataVisualization.Core;
|
||||
const viewableType = DataVizCore.ViewableType.SPRITE;
|
||||
let spriteColor = new THREE.Color(0xffffff);
|
||||
const BASEURL = import.meta.env.VITE_FORGE_BASEURL;
|
||||
const spriteIconUrl = `${BASEURL}/hotspot.svg`;
|
||||
const style = new DataVizCore.ViewableStyle(
|
||||
viewableType,
|
||||
spriteColor,
|
||||
spriteIconUrl
|
||||
);
|
||||
const BASEURL = import.meta.env.VITE_FILE_API_BASEURL;
|
||||
const spriteIconUrl = `${BASEURL}/dist/hotspot.svg`;
|
||||
const viewableData = new DataVizCore.ViewableData();
|
||||
viewableData.spriteSize = 24; // Sprites as points of size 24 x 24 pixels
|
||||
flatSubData.value?.forEach((d, index) => {
|
||||
if (d.device_coordinate_3d) {
|
||||
const position = d.device_coordinate_3d;
|
||||
style.color = new THREE.Color(hexToRgb(d.device_normal_color));
|
||||
// 每個都 new 一個 style
|
||||
const pointStyle = new DataVizCore.ViewableStyle(
|
||||
viewableType,
|
||||
new THREE.Color(hexToRgb(getDeviceRealtimeColor(d))),
|
||||
spriteIconUrl
|
||||
);
|
||||
const viewable = new DataVizCore.SpriteViewable(
|
||||
position,
|
||||
style,
|
||||
pointStyle,
|
||||
d.spriteDbId
|
||||
);
|
||||
viewableData.addViewable(viewable);
|
||||
}
|
||||
});
|
||||
// await viewableData.finish();
|
||||
// dataVizExtn.value.addViewables(viewableData);
|
||||
// console.log(dataVizExtn.value);
|
||||
viewableData.finish().then(
|
||||
() => {
|
||||
dataVizExtn.value.addViewables(viewableData);
|
||||
@ -107,6 +121,16 @@ export default function useForgeSprite() {
|
||||
);
|
||||
}
|
||||
};
|
||||
// 監聽 realtimeData 變化,重建 sprites
|
||||
watch(
|
||||
() => realtimeData?.value,
|
||||
() => {
|
||||
if (forgeViewer.value?.isLoadDone()) {
|
||||
createSprites();
|
||||
}
|
||||
},
|
||||
{ deep: true }
|
||||
);
|
||||
|
||||
watch(
|
||||
() => flatSubData,
|
||||
|
@ -17,7 +17,6 @@ import "virtual:svg-icons-register";
|
||||
// 引入项目中的全部全局组件
|
||||
import SvgIcon from "@/components/svgIcon.vue";
|
||||
import library from "./fontawsomeIconRegister";
|
||||
import "flag-icons/css/flag-icons.min.css";
|
||||
|
||||
/* import font awesome icon component */
|
||||
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
|
||||
|
@ -8,6 +8,7 @@ import AssetManagement from "@/views/AssetManagement/AssetManagement.vue";
|
||||
import AlertManagement from "@/views/alert/AlertManagement.vue";
|
||||
import ProductSetting from "@/views/productSetting/ProductSetting.vue";
|
||||
import EnergyManagement from "@/views/energyManagement/EnergyManagement.vue";
|
||||
import SettingManagement from "@/views/setting/SettingManagement.vue";
|
||||
import Login from "@/views/login/Login.vue";
|
||||
import useUserInfoStore from "@/stores/useUserInfoStore";
|
||||
import useGetCookie from "@/hooks/useGetCookie";
|
||||
@ -84,6 +85,11 @@ const router = createRouter({
|
||||
name: "energyManagement",
|
||||
component: EnergyManagement,
|
||||
},
|
||||
{
|
||||
path: "/setting/:main_system_id/:sub_system_id/:type",
|
||||
name: "setting",
|
||||
component: SettingManagement,
|
||||
},
|
||||
{
|
||||
path: "/mytestfile/mjm",
|
||||
name: "mytestfile",
|
||||
@ -101,15 +107,6 @@ router.beforeEach(async (to, from, next) => {
|
||||
const token = useGetCookie("JWT-Authorization");
|
||||
const user_name = useGetCookie("user_name");
|
||||
|
||||
if (to.path === "/logout") {
|
||||
document.cookie = "JWT-Authorization=; Max-Age=0";
|
||||
document.cookie = "user_name=; Max-Age=0";
|
||||
auth.user.token = "";
|
||||
auth.user.user_name = "";
|
||||
window.location.reload();
|
||||
next({ path: "/login" });
|
||||
}
|
||||
|
||||
if ((authRequired && !token) || to.path === "/") {
|
||||
auth.user.token = "";
|
||||
next({ path: "/login" });
|
||||
|
@ -1,17 +1,22 @@
|
||||
import { defineStore } from "pinia";
|
||||
import { ref, computed } from "vue";
|
||||
import { ref, computed, watch } from "vue";
|
||||
import { useRoute } from "vue-router";
|
||||
import { getBuildings } from "@/apis/building";
|
||||
import { getDashboard2D3D } from "@/apis/dashboard";
|
||||
import { getAssetFloorList, getDepartmentList } from "@/apis/asset";
|
||||
|
||||
const useBuildingStore = defineStore("buildingInfo", () => {
|
||||
// 所有棟別
|
||||
// 狀態定義
|
||||
const buildings = ref([]);
|
||||
|
||||
const selectedBuilding = ref(null);
|
||||
|
||||
// 所有大小類系統
|
||||
const floorList = ref([]);
|
||||
const deptList = ref([]);
|
||||
const mainSubSys = ref([]);
|
||||
// 控制顯示2D/3D切換與內容
|
||||
const showForgeArea = ref(true);
|
||||
const previewImageExt = ref("");
|
||||
|
||||
// 所有大類
|
||||
// 計算屬性
|
||||
const mainSys = computed(() =>
|
||||
mainSubSys.value.map(({ main_system_tag, full_name }) => ({
|
||||
main_system_tag,
|
||||
@ -21,7 +26,6 @@ const useBuildingStore = defineStore("buildingInfo", () => {
|
||||
|
||||
const subSys = computed(() => {
|
||||
let subPages = [];
|
||||
|
||||
mainSubSys.value.forEach(({ main_system_tag, history_Sub_systems }) => {
|
||||
subPages = [
|
||||
...subPages,
|
||||
@ -32,7 +36,6 @@ const useBuildingStore = defineStore("buildingInfo", () => {
|
||||
})),
|
||||
];
|
||||
});
|
||||
|
||||
return subPages;
|
||||
});
|
||||
|
||||
@ -44,14 +47,89 @@ const useBuildingStore = defineStore("buildingInfo", () => {
|
||||
return null;
|
||||
});
|
||||
|
||||
// 獲取所有建築物
|
||||
const fetchBuildings = async () => {
|
||||
// const res = await getBuildings();
|
||||
buildings.value = JSON.parse(localStorage.getItem("CviBuildingList")) || [];
|
||||
const storedBuilding = JSON.parse(localStorage.getItem("CviBuilding"));
|
||||
if (buildings.value.length > 0) {
|
||||
selectedBuilding.value = storedBuilding || buildings.value[0]; // 預設選第一個建築
|
||||
} else {
|
||||
selectedBuilding.value = null; // 如果沒有建築物,清空選擇
|
||||
}
|
||||
};
|
||||
|
||||
// 獲取樓層資料
|
||||
const fetchFloorList = async (building_guid) => {
|
||||
const res = await getAssetFloorList(building_guid);
|
||||
floorList.value =
|
||||
res.data[0]?.floors.map((d) => ({
|
||||
...d,
|
||||
title: d.full_name,
|
||||
key: d.floor_guid,
|
||||
})) || [];
|
||||
};
|
||||
|
||||
// 獲取部門資料
|
||||
const fetchDepartmentList = async () => {
|
||||
const res = await getDepartmentList();
|
||||
deptList.value =
|
||||
res.data.map((d) => ({
|
||||
...d,
|
||||
title: d.name,
|
||||
key: d.id,
|
||||
})) || [];
|
||||
};
|
||||
|
||||
// 獲取2D、3D顯示與否
|
||||
const fetchDashboard2D3D = async (BuildingId) => {
|
||||
const res = await getDashboard2D3D(BuildingId);
|
||||
showForgeArea.value = res.data.is3DEnabled;
|
||||
previewImageExt.value = res.data.previewImageExt || "";
|
||||
};
|
||||
|
||||
// 清除localStorage建築物
|
||||
const deleteBuilding = () => {
|
||||
localStorage.removeItem("CviBuildingList");
|
||||
localStorage.removeItem("CviBuilding");
|
||||
buildings.value = [];
|
||||
selectedBuilding.value = null;
|
||||
};
|
||||
|
||||
// 當 selectedBuilding 改變時,更新 floorList 和 deptList
|
||||
watch(selectedBuilding, async (newBuilding) => {
|
||||
if (newBuilding) {
|
||||
localStorage.setItem("CviBuilding", JSON.stringify(newBuilding));
|
||||
await Promise.all([
|
||||
fetchFloorList(newBuilding.building_guid),
|
||||
fetchDepartmentList(),
|
||||
fetchDashboard2D3D(newBuilding.building_guid),
|
||||
]);
|
||||
}
|
||||
});
|
||||
|
||||
// 初始化資料
|
||||
const initialize = async () => {
|
||||
await fetchBuildings();
|
||||
};
|
||||
|
||||
return {
|
||||
buildings,
|
||||
selectedBuilding,
|
||||
floorList,
|
||||
deptList,
|
||||
mainSubSys,
|
||||
mainSys,
|
||||
subSys,
|
||||
selectedSystem,
|
||||
showForgeArea,
|
||||
previewImageExt,
|
||||
deleteBuilding,
|
||||
fetchBuildings,
|
||||
fetchFloorList,
|
||||
fetchDepartmentList,
|
||||
fetchDashboard2D3D,
|
||||
initialize,
|
||||
};
|
||||
});
|
||||
|
||||
export default useBuildingStore;
|
||||
|
@ -2,38 +2,59 @@ import useGetCookie from "@/hooks/useGetCookie";
|
||||
import axios from "axios";
|
||||
const BASEURL = import.meta.env.VITE_API_BASEURL;
|
||||
|
||||
// --- 請求攔截器的共用邏輯 ---
|
||||
const requestInterceptor = (config) => {
|
||||
// 確保 headers 物件存在
|
||||
if (!config.headers) {
|
||||
config.headers = {};
|
||||
}
|
||||
|
||||
// 1. 取得並附加最新的 Token
|
||||
const token = useGetCookie("JWT-Authorization");
|
||||
if (token) {
|
||||
// 正確做法:修改屬性,而不是覆蓋整個物件
|
||||
config.headers.Authorization = `Bearer ${token}`;
|
||||
}
|
||||
|
||||
// 2. 取得並附加選定的建築物 GUID
|
||||
const storedBuilding = localStorage.getItem("CviBuilding");
|
||||
if (storedBuilding) {
|
||||
try {
|
||||
const buildingObject = JSON.parse(storedBuilding);
|
||||
if (buildingObject && buildingObject.building_guid) {
|
||||
// 與後端約定好要用哪個標頭,這裡使用 'X-Building-GUID' 作為範例
|
||||
config.headers["X-Building-GUID"] = buildingObject.building_guid;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("解析 localStorage 中的 CviBuilding 失敗:", e);
|
||||
}
|
||||
}
|
||||
return config;
|
||||
};
|
||||
|
||||
const requestErrorInterceptor = (error) => {
|
||||
return Promise.reject(error);
|
||||
};
|
||||
|
||||
|
||||
// --- 一般 API 實例 ---
|
||||
const instance = axios.create({
|
||||
baseURL: BASEURL,
|
||||
timeout: -1,
|
||||
headers: { Authorization: `Bearer ${useGetCookie("JWT-Authorization")}` },
|
||||
timeout: 10000, // 建議設定超時
|
||||
// 移除靜態 headers
|
||||
});
|
||||
|
||||
// Add a request interceptor
|
||||
instance.interceptors.request.use(
|
||||
function (config) {
|
||||
// Do something before request is sent
|
||||
const token = useGetCookie("JWT-Authorization");
|
||||
config.headers = {
|
||||
Authorization: `Bearer ${token}`,
|
||||
};
|
||||
return config;
|
||||
},
|
||||
function (error) {
|
||||
// Do something with request error
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
// 使用共用的攔截器
|
||||
instance.interceptors.request.use(requestInterceptor, requestErrorInterceptor);
|
||||
|
||||
// Add a response interceptor
|
||||
instance.interceptors.response.use(
|
||||
function (response) {
|
||||
// Any status code that lie within the range of 2xx cause this function to trigger
|
||||
// Do something with response data
|
||||
const { status, data, headers } = response;
|
||||
const { data } = response;
|
||||
|
||||
return {
|
||||
...data,
|
||||
};
|
||||
return { ...data };
|
||||
},
|
||||
function (error) {
|
||||
// Any status codes that falls outside the range of 2xx cause this function to trigger
|
||||
@ -45,27 +66,16 @@ instance.interceptors.response.use(
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
// --- 檔案處理 API 實例 ---
|
||||
export const fileInstance = axios.create({
|
||||
baseURL: BASEURL,
|
||||
timeout: -1,
|
||||
headers: { Authorization: `Bearer ${useGetCookie("JWT-Authorization")}` },
|
||||
// 移除靜態 headers
|
||||
});
|
||||
|
||||
// Add a request interceptor
|
||||
fileInstance.interceptors.request.use(
|
||||
function (config) {
|
||||
// Do something before request is sent
|
||||
const token = useGetCookie("JWT-Authorization");
|
||||
config.headers = {
|
||||
Authorization: `Bearer ${token}`,
|
||||
};
|
||||
return config;
|
||||
},
|
||||
function (error) {
|
||||
// Do something with request error
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
// 使用共用的攔截器
|
||||
fileInstance.interceptors.request.use(requestInterceptor, requestErrorInterceptor);
|
||||
|
||||
// Add a response interceptor
|
||||
fileInstance.interceptors.response.use(
|
||||
|
@ -1,14 +1,17 @@
|
||||
<script setup>
|
||||
import { ref, provide, onMounted, watch } from "vue";
|
||||
import { ref, provide, onMounted, watch, computed } from "vue";
|
||||
import AssetMainList from "./components/AssetMainList.vue";
|
||||
import AssetSubList from "./components/AssetSubList.vue";
|
||||
import AssetTable from "./components/AssetTable.vue";
|
||||
import { getOperationCompanyList } from "@/apis/operation";
|
||||
import { getIOTSchema } from "@/apis/asset";
|
||||
import { getIOTSchema, getElecTypeList } from "@/apis/asset";
|
||||
import useSearchParam from "@/hooks/useSearchParam";
|
||||
import useBuildingStore from "@/stores/useBuildingStore";
|
||||
const storeBuild = useBuildingStore();
|
||||
const { searchParams, changeParams } = useSearchParam();
|
||||
const companyOptions = ref([]);
|
||||
const iotSchemaOptions = ref([]);
|
||||
const elecTypeOptions = ref([]);
|
||||
const getCompany = async () => {
|
||||
const res = await getOperationCompanyList();
|
||||
companyOptions.value = res.data.map((d) => ({ ...d, key: d.id }));
|
||||
@ -17,9 +20,17 @@ const getIOTSchemaOptions = async (id) => {
|
||||
const res = await getIOTSchema(Number(id));
|
||||
iotSchemaOptions.value = res.data.map((d) => ({ ...d, key: d.id }));
|
||||
};
|
||||
const getElecType = async () => {
|
||||
const res = await getElecTypeList();
|
||||
elecTypeOptions.value = res.data.map((d) => ({ ...d, key: d.id }));
|
||||
};
|
||||
|
||||
const departmentList = computed(() => storeBuild.deptList);
|
||||
const floors = computed(() => storeBuild.floorList);
|
||||
|
||||
onMounted(() => {
|
||||
getCompany();
|
||||
getElecType();
|
||||
});
|
||||
|
||||
watch(
|
||||
@ -37,6 +48,9 @@ watch(
|
||||
provide("asset_modal_options", {
|
||||
companyOptions,
|
||||
iotSchemaOptions,
|
||||
elecTypeOptions,
|
||||
departmentList,
|
||||
floors,
|
||||
});
|
||||
</script>
|
||||
|
||||
|
@ -5,6 +5,8 @@ import { ref, onMounted, watch, inject } from "vue";
|
||||
import useSearchParam from "@/hooks/useSearchParam";
|
||||
import useActiveBtn from "@/hooks/useActiveBtn";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import useBuildingStore from "@/stores/useBuildingStore";
|
||||
const store = useBuildingStore();
|
||||
const { t } = useI18n();
|
||||
const { searchParams, changeParams } = useSearchParam();
|
||||
const { items, changeActiveBtn, setItems, selectedBtn } = useActiveBtn();
|
||||
@ -17,7 +19,7 @@ const formState = ref({
|
||||
});
|
||||
|
||||
const getMainSystems = async () => {
|
||||
const res = await getAssetMainList();
|
||||
const res = await getAssetMainList(store.selectedBuilding.building_guid);
|
||||
const cate = res.data.map((d, index) => ({
|
||||
...d,
|
||||
title: d.system_key,
|
||||
@ -60,9 +62,15 @@ const deleteItem = async (id) => {
|
||||
});
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
getMainSystems();
|
||||
});
|
||||
watch(
|
||||
() => store.selectedBuilding,
|
||||
(newBuilding) => {
|
||||
if (newBuilding) {
|
||||
getMainSystems();
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
watch(selectedBtn, (newValue) => {
|
||||
changeParams({
|
||||
@ -83,12 +91,12 @@ watch(selectedBtn, (newValue) => {
|
||||
:getData="getMainSystems"
|
||||
:formState="formState"
|
||||
/>
|
||||
<button
|
||||
<!-- <button
|
||||
@click.stop.prevent="isEditMode = !isEditMode"
|
||||
class="btn btn-sm btn-outline-success"
|
||||
>
|
||||
{{ isEditMode ? t("button.stop_edit") : t("button.start_edit") }}
|
||||
</button>
|
||||
</button> -->
|
||||
</div>
|
||||
<ButtonConnectedGroup
|
||||
:items="items"
|
||||
|
@ -3,8 +3,11 @@ import { ref, defineProps, inject } from "vue";
|
||||
import * as yup from "yup";
|
||||
import useFormErrorMessage from "@/hooks/useFormErrorMessage";
|
||||
import { postAssetMainList } from "@/apis/asset";
|
||||
import useBuildingStore from "@/stores/useBuildingStore";
|
||||
import useSearchParam from "@/hooks/useSearchParam";
|
||||
import { useI18n } from "vue-i18n";
|
||||
|
||||
const storeBuild = useBuildingStore();
|
||||
const { t } = useI18n();
|
||||
const { openToast } = inject("app_toast");
|
||||
const { searchParams, changeParams } = useSearchParam();
|
||||
@ -30,6 +33,7 @@ const onOk = async () => {
|
||||
const res = await postAssetMainList({
|
||||
...props.formState,
|
||||
id: props.formState ? props.formState.id : 0,
|
||||
building_guid:storeBuild.selectedBuilding?.building_guid || null,
|
||||
});
|
||||
|
||||
if (res.isSuccess) {
|
||||
@ -47,9 +51,9 @@ const onReset = () => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<button class="btn btn-sm btn-success" @click.stop.prevent="openModal">
|
||||
<!-- <button class="btn btn-sm btn-success" @click.stop.prevent="openModal">
|
||||
<font-awesome-icon :icon="['fas', 'plus']" />{{ $t("button.add") }}
|
||||
</button>
|
||||
</button> -->
|
||||
<Modal
|
||||
id="asset_add_main_item"
|
||||
:title="
|
||||
@ -57,9 +61,8 @@ const onReset = () => {
|
||||
? t('assetManagement.edit_system_category')
|
||||
: t('assetManagement.add_system_category')
|
||||
"
|
||||
:open="open"
|
||||
:onCancel="onReset"
|
||||
width="300"
|
||||
:width="300"
|
||||
>
|
||||
<template #modalContent>
|
||||
<form ref="form" class="mt-5 flex flex-col items-center">
|
||||
@ -71,7 +74,7 @@ const onReset = () => {
|
||||
</span></template
|
||||
>
|
||||
</Input>
|
||||
<Input name="system_value" :value="formState" :readonly="props.formState?.id">
|
||||
<Input name="system_value" :value="formState" :readonly="Boolean(props.formState?.id)">
|
||||
<template #topLeft>{{ $t("assetManagement.system_value") }}</template>
|
||||
<template #bottomLeft
|
||||
><span class="text-error text-base">
|
||||
|
@ -45,21 +45,41 @@ watch(
|
||||
deep: true,
|
||||
}
|
||||
);
|
||||
const formState = ref({
|
||||
id: 0,
|
||||
system_key: "",
|
||||
system_value: "",
|
||||
system_parent_id: 0,
|
||||
file: [],
|
||||
});
|
||||
|
||||
const editRecord = ref(null);
|
||||
// 編輯 modal
|
||||
const openModal = () => {
|
||||
|
||||
|
||||
const openModal = (item) => {
|
||||
if (item.id) {
|
||||
formState.value = { ...item };
|
||||
|
||||
if (item.device_image) {
|
||||
const subFile = item
|
||||
? {
|
||||
name: item.device_image,
|
||||
src: item.device_image,
|
||||
ext: item.device_image?.split(".")[1],
|
||||
}
|
||||
: {};
|
||||
formState.value.file = [subFile];
|
||||
}
|
||||
} else {
|
||||
formState.value = {
|
||||
id: 0,
|
||||
system_key: "",
|
||||
system_value: "",
|
||||
system_parent_id: 0,
|
||||
file: [],
|
||||
};
|
||||
}
|
||||
asset_add_sub_item.showModal();
|
||||
};
|
||||
const onCancel = () => {
|
||||
editRecord.value = null;
|
||||
asset_add_sub_item.close();
|
||||
};
|
||||
|
||||
const edit = (item) => {
|
||||
editRecord.value = item;
|
||||
openModal();
|
||||
};
|
||||
|
||||
const deleteItem = async (id) => {
|
||||
openToast("warning", t("msg.sure_to_delete"), "body", async () => {
|
||||
@ -83,9 +103,8 @@ const deleteItem = async (id) => {
|
||||
</h2>
|
||||
<AssetSubListAddModal
|
||||
:openModal="openModal"
|
||||
:onCancel="onCancel"
|
||||
:getData="getSubSystems"
|
||||
:editRecord="editRecord"
|
||||
:formState="formState"
|
||||
/>
|
||||
<button
|
||||
@click.stop.prevent="isEditMode = !isEditMode"
|
||||
@ -112,7 +131,7 @@ const deleteItem = async (id) => {
|
||||
<template v-if="isEditMode">
|
||||
<span
|
||||
class="ml-2 text-base text-warning"
|
||||
@click.stop.prevent="() => edit(item)"
|
||||
@click.stop.prevent="() => openModal(item)"
|
||||
>
|
||||
<FontAwesomeIcon :icon="['fas', 'pencil-alt']"></FontAwesomeIcon>
|
||||
</span>
|
||||
|
@ -5,18 +5,25 @@ import useFormErrorMessage from "@/hooks/useFormErrorMessage";
|
||||
import useSearchParam from "@/hooks/useSearchParam";
|
||||
import { getAssetMainList, postAssetSubList } from "@/apis/asset";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import useBuildingStore from "@/stores/useBuildingStore";
|
||||
const store = useBuildingStore();
|
||||
const { t } = useI18n();
|
||||
const { searchParams, changeParams } = useSearchParam();
|
||||
const props = defineProps({
|
||||
openModal: Function,
|
||||
onCancel: Function,
|
||||
getData: Function,
|
||||
editRecord: Object,
|
||||
formState: Object,
|
||||
});
|
||||
|
||||
const form = ref(null);
|
||||
const mainSystem = ref([]);
|
||||
|
||||
const updateFileList = (files) => {
|
||||
console.log("file", files);
|
||||
props.formState.file = files;
|
||||
};
|
||||
|
||||
const getMainSystems = async () => {
|
||||
const res = await getAssetMainList();
|
||||
const res = await getAssetMainList(store.selectedBuilding.building_guid);
|
||||
mainSystem.value = res.data.map((d) => ({ ...d, key: d.id }));
|
||||
};
|
||||
|
||||
@ -24,51 +31,55 @@ let subSysSchema = yup.object({
|
||||
system_key: yup.string().required(t("button.required")), // 名稱
|
||||
system_value: yup.string().required(t("button.required")), // 代稱
|
||||
system_parent_id: yup.number().required(t("button.required")), // 大類id
|
||||
file: yup.array(),
|
||||
});
|
||||
|
||||
const { formErrorMsg, handleSubmit, handleErrorReset } =
|
||||
useFormErrorMessage(subSysSchema);
|
||||
|
||||
const formState = ref({
|
||||
system_key: "",
|
||||
system_value: "",
|
||||
system_parent_id: 0,
|
||||
});
|
||||
|
||||
const resetForm = () => {
|
||||
formState.value = {
|
||||
const onCancel = () => {
|
||||
props.formState = {
|
||||
id: 0,
|
||||
system_key: "",
|
||||
system_value: "",
|
||||
system_parent_id: 0,
|
||||
file: [],
|
||||
};
|
||||
asset_add_sub_item.close();
|
||||
updateFileList([]);
|
||||
handleErrorReset();
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
getMainSystems();
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.editRecord,
|
||||
(newValue) => {
|
||||
if (newValue) {
|
||||
formState.value = newValue;
|
||||
} else {
|
||||
resetForm();
|
||||
() => store.selectedBuilding,
|
||||
(newBuilding) => {
|
||||
if (newBuilding) {
|
||||
getMainSystems();
|
||||
}
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
const onOk = async () => {
|
||||
// 編輯
|
||||
const value = await handleSubmit(subSysSchema, formState.value);
|
||||
const res = await postAssetSubList({
|
||||
...formState.value,
|
||||
id: props.editRecord ? props.editRecord.id : 0,
|
||||
});
|
||||
const value = await handleSubmit(subSysSchema, props.formState);
|
||||
console.log("props.formState", props.formState);
|
||||
|
||||
const formData = new FormData(form.value);
|
||||
formData.delete("file");
|
||||
formData.append("id", props.formState.id);
|
||||
|
||||
if (props.formState.file[0]) {
|
||||
formData.append("file", props.formState.file[0]);
|
||||
}
|
||||
if (props.formState.Device_image) {
|
||||
formData.append("Device_image", props.formState.Device_image);
|
||||
}
|
||||
|
||||
const res = await postAssetSubList(formData);
|
||||
if (res.isSuccess) {
|
||||
props.getData(parseInt(searchParams.value.mainSys_id));
|
||||
props.onCancel();
|
||||
onCancel();
|
||||
}
|
||||
};
|
||||
</script>
|
||||
@ -79,10 +90,13 @@ const onOk = async () => {
|
||||
</button>
|
||||
<Modal
|
||||
id="asset_add_sub_item"
|
||||
:title="props.editRecord?.id ? t('assetManagement.edit_device_category') : t('assetManagement.add_device_category')"
|
||||
:open="open"
|
||||
:title="
|
||||
props.formState?.id
|
||||
? t('assetManagement.edit_device_category')
|
||||
: t('assetManagement.add_device_category')
|
||||
"
|
||||
:onCancel="onCancel"
|
||||
width="300"
|
||||
:width="300"
|
||||
>
|
||||
<template #modalContent>
|
||||
<form ref="form" class="mt-5 flex flex-col items-center">
|
||||
@ -94,7 +108,11 @@ const onOk = async () => {
|
||||
</span></template
|
||||
>
|
||||
</Input>
|
||||
<Input name="system_value" :value="formState" :readonly="props.editRecord?.id">
|
||||
<Input
|
||||
name="system_value"
|
||||
:value="formState"
|
||||
:readonly="Boolean(props.formState?.id)"
|
||||
>
|
||||
<template #topLeft>{{ $t("assetManagement.system_value") }}</template>
|
||||
<template #bottomLeft
|
||||
><span class="text-error text-base">
|
||||
@ -109,7 +127,7 @@ const onOk = async () => {
|
||||
name="system_parent_id"
|
||||
:value="formState"
|
||||
selectClass="border-info focus-within:border-info"
|
||||
:disabled="props.editRecord?.id"
|
||||
:disabled="Boolean(props.formState?.id)"
|
||||
>
|
||||
<template #topLeft>{{
|
||||
$t("assetManagement.system_parent")
|
||||
@ -120,6 +138,14 @@ const onOk = async () => {
|
||||
</span></template
|
||||
>
|
||||
</Select>
|
||||
<Upload
|
||||
name="file"
|
||||
:fileList="formState.file || []"
|
||||
:getFileList="updateFileList"
|
||||
:multiple="false"
|
||||
>
|
||||
<template #topLeft>{{ $t("operation.upload_file") }}</template>
|
||||
</Upload>
|
||||
</form>
|
||||
</template>
|
||||
<template #modalAction>
|
||||
|
@ -4,28 +4,18 @@ import { onMounted, ref, watch, inject, provide, computed } from "vue";
|
||||
import useSearchParam from "@/hooks/useSearchParam";
|
||||
import AssetTableAddModal from "./AssetTableAddModal.vue";
|
||||
import { getOperationCompanyList } from "@/apis/operation";
|
||||
import { getAssetFloorList } from "@/apis/asset";
|
||||
import { postMQTTRefresh } from "@/apis/alert";
|
||||
import dayjs from "dayjs";
|
||||
import { useI18n } from "vue-i18n";
|
||||
const { t } = useI18n();
|
||||
const { openToast, cancelToastOpen } = inject("app_toast");
|
||||
|
||||
const { companyOptions, departmentList, floors } = inject(
|
||||
"asset_modal_options"
|
||||
);
|
||||
const FILE_BASEURL = import.meta.env.VITE_FILE_API_BASEURL;
|
||||
const { searchParams, changeParams } = useSearchParam();
|
||||
|
||||
const companyOptions = ref([]);
|
||||
const getCompany = async () => {
|
||||
const res = await getOperationCompanyList();
|
||||
companyOptions.value = res.data.map((d) => ({ ...d, key: d.id }));
|
||||
};
|
||||
|
||||
const floors = ref([]);
|
||||
const totalCoordinates = ref({});
|
||||
const getFloors = async () => {
|
||||
const res = await getAssetFloorList();
|
||||
floors.value = res.data[0]?.floors.map((d) => ({ ...d, key: d.floor_guid }));
|
||||
};
|
||||
|
||||
const tableData = ref([]);
|
||||
const getAssetData = async () => {
|
||||
totalCoordinates.value = {}; // 在獲取新數據之前清空 totalCoordinates
|
||||
@ -45,6 +35,8 @@ const getAssetData = async () => {
|
||||
floor: floors.value.find(({ floor_guid }) => d.floor_guid === floor_guid)
|
||||
?.full_name,
|
||||
company: companyOptions.value.find(({ id }) => d.operation_id === id),
|
||||
department: departmentList.value.find(({ id }) => d.department_id === id)
|
||||
?.name,
|
||||
buying_date: d?.buying_date
|
||||
? dayjs(d?.buying_date).format("YYYY-MM-DD")
|
||||
: "",
|
||||
@ -55,18 +47,25 @@ const getAssetData = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
const refreshMQTT = async () => {
|
||||
const res = await postMQTTRefresh();
|
||||
if (res.isSuccess) {
|
||||
openToast("success", t("msg.mqtt_refresh"));
|
||||
} else {
|
||||
openToast("error", res.msg, "#outliers_add_table_item");
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(async () => {
|
||||
await getCompany();
|
||||
await getFloors();
|
||||
getAssetData();
|
||||
});
|
||||
|
||||
const columns = computed(() => [
|
||||
{
|
||||
title: t("assetManagement.device_number"),
|
||||
key: "device_number",
|
||||
class: "break-all",
|
||||
},
|
||||
// {
|
||||
// title: t("assetManagement.device_number"),
|
||||
// key: "device_number",
|
||||
// class: "break-all",
|
||||
// },
|
||||
{
|
||||
title: t("assetManagement.device_name"),
|
||||
key: "full_name",
|
||||
@ -83,6 +82,11 @@ const columns = computed(() => [
|
||||
filter: true,
|
||||
sort: true,
|
||||
},
|
||||
{
|
||||
title: t("assetManagement.department"),
|
||||
key: "department",
|
||||
filter: true,
|
||||
},
|
||||
{
|
||||
title: t("assetManagement.device_coordinate"),
|
||||
key: "device_coordinate",
|
||||
@ -121,8 +125,8 @@ watch(
|
||||
(newValue) => {
|
||||
if (newValue.value?.subSys_id) {
|
||||
getAssetData();
|
||||
}else{
|
||||
tableData.value=[];
|
||||
} else {
|
||||
tableData.value = [];
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -170,7 +174,7 @@ const remove = async (id) => {
|
||||
const res = await deleteAssetItem(id);
|
||||
if (res.isSuccess) {
|
||||
getAssetData();
|
||||
openToast("success", t("msg.delete_success"));
|
||||
openToast("success", t("msg.delete_success"));
|
||||
} else {
|
||||
openToast("error", res.msg);
|
||||
}
|
||||
@ -183,14 +187,21 @@ provide("asset_table_data", {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex justify-start items-center mt-10">
|
||||
<h3 class="text-xl mr-5">{{ $t("assetManagement.device_list") }}</h3>
|
||||
<AssetTableAddModal
|
||||
:openModal="openModal"
|
||||
:onCancel="onCancel"
|
||||
:editRecord="editRecord"
|
||||
:getData="getAssetData"
|
||||
/>
|
||||
<div class="flex justify-between items-center mt-10">
|
||||
<div class="flex">
|
||||
<h3 class="text-xl mr-5">{{ $t("assetManagement.device_list") }}</h3>
|
||||
<AssetTableAddModal
|
||||
:openModal="openModal"
|
||||
:onCancel="onCancel"
|
||||
:editRecord="editRecord"
|
||||
:getData="getAssetData"
|
||||
/>
|
||||
</div>
|
||||
<button class="btn btn-sm btn-add" @click.prevent="refreshMQTT">
|
||||
<font-awesome-icon :icon="['fas', 'cog']" />{{
|
||||
$t("alert.reorganization")
|
||||
}}
|
||||
</button>
|
||||
</div>
|
||||
<Table :columns="columns" :dataSource="tableData" class="mt-3">
|
||||
<template #bodyCell="{ record, column, index }">
|
||||
|
@ -108,9 +108,8 @@ const closeModal = () => {
|
||||
<Modal
|
||||
id="asset_add_table_item"
|
||||
:title="editRecord?.main_id ? $t('assetManagement.edit_device') : $t('assetManagement.add_device')"
|
||||
:open="open"
|
||||
:onCancel="closeModal"
|
||||
width="1600"
|
||||
:width="1600"
|
||||
>
|
||||
<template #modalContent>
|
||||
<form ref="form" class="grid grid-cols-5 gap-5">
|
||||
|
@ -2,20 +2,18 @@
|
||||
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 useSearchParam from "@/hooks/useSearchParam";
|
||||
import AssetTableModalLeftInfoMQTT from "./AssetTableModalLeftInfoMQTT.vue";
|
||||
import useUserInfoStore from "@/stores/useUserInfoStore";
|
||||
import dayjs from "dayjs";
|
||||
import { useI18n } from "vue-i18n";
|
||||
const { t } = useI18n();
|
||||
const FILE_BASEURL = import.meta.env.VITE_FILE_API_BASEURL;
|
||||
|
||||
const { searchParams, changeParams } = useSearchParam();
|
||||
const { updateLeftFields, formErrorMsg, formState } = inject(
|
||||
"asset_table_modal_form"
|
||||
);
|
||||
const { companyOptions, iotSchemaOptions } = inject("asset_modal_options");
|
||||
const { companyOptions, iotSchemaOptions, elecTypeOptions, departmentList } = inject("asset_modal_options");
|
||||
const store = useUserInfoStore();
|
||||
let schema = {
|
||||
full_name: yup.string().nullable(true),
|
||||
@ -52,6 +50,7 @@ onBeforeMount(() => {
|
||||
operation_id: 0,
|
||||
response_schema_id: 0,
|
||||
department_id: 0,
|
||||
elec_type_id: null,
|
||||
asset_number: "",
|
||||
topic: "",
|
||||
sub_device: [],
|
||||
@ -113,7 +112,17 @@ watch(
|
||||
</span></template
|
||||
></Input
|
||||
>
|
||||
<AssetTableModalLeftInfoDept />
|
||||
<div class="flex items-center w-72">
|
||||
<Select
|
||||
:value="formState"
|
||||
selectClass="border-info focus-within:border-info"
|
||||
name="department_id"
|
||||
Attribute="name"
|
||||
:options="departmentList"
|
||||
>
|
||||
<template #topLeft>{{ $t("assetManagement.department") }}</template>
|
||||
</Select>
|
||||
</div>
|
||||
<Input
|
||||
:value="formState"
|
||||
width="290"
|
||||
@ -141,6 +150,18 @@ watch(
|
||||
<template #topLeft>IoT</template>
|
||||
</Select>
|
||||
</div>
|
||||
<div class="flex items-center w-72" v-if="searchParams.mainSys_id==26">
|
||||
<Select
|
||||
:value="formState"
|
||||
selectClass="border-info focus-within:border-info"
|
||||
name="elec_type_id"
|
||||
Attribute="name"
|
||||
:options="elecTypeOptions"
|
||||
:required="true"
|
||||
>
|
||||
<template #topLeft>{{$t("energy.electricity_classification")}}</template>
|
||||
</Select>
|
||||
</div>
|
||||
<Input :value="formState" width="290" name="asset_number">
|
||||
<template #topLeft>{{ $t("assetManagement.asset_number") }}</template>
|
||||
<template #bottomLeft
|
||||
@ -186,8 +207,6 @@ watch(
|
||||
</Select>
|
||||
</div>
|
||||
<AssetTableModalLeftInfoMQTT />
|
||||
<AssetTableModalLeftInfoGraph />
|
||||
<AssetTableModalLeftInfoIoT />
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
|
@ -127,7 +127,7 @@ const onCancel = () => {
|
||||
id="asset_add_dept"
|
||||
:title="t('assetManagement.department')"
|
||||
:onCancel="onCancel"
|
||||
width="400"
|
||||
:width="400"
|
||||
>
|
||||
<template #modalContent>
|
||||
<form ref="form">
|
||||
|
@ -138,7 +138,7 @@ onMounted(async () => {
|
||||
id="asset_add_graph_item"
|
||||
:title="t('graphManagement.title')"
|
||||
:onCancel="onCancel"
|
||||
width="500"
|
||||
:width="500"
|
||||
>
|
||||
<template #modalContent>
|
||||
<ul class="menu bg-base-200 rounded-box text-lg w-full mt-3">
|
||||
|
@ -65,7 +65,7 @@ const getPoint = async (sub_system_tag) => {
|
||||
setPoints(
|
||||
res.data.map((d, index) => ({
|
||||
...d,
|
||||
title: d.points,
|
||||
title: d.full_name,
|
||||
key: d.points,
|
||||
active: false,
|
||||
}))
|
||||
@ -225,7 +225,7 @@ const deleteItem = (value) => {
|
||||
id="asset_add_IoT_item"
|
||||
:title="t('assetManagement.associated_device')"
|
||||
:onCancel="onCancel"
|
||||
width="900"
|
||||
:width="900"
|
||||
>
|
||||
<template #modalContent>
|
||||
<ButtonGroup
|
||||
|
@ -1,13 +1,13 @@
|
||||
<script setup>
|
||||
import { onMounted, ref, inject, watch, computed } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { postMQTTpublish } from "@/apis/asset";
|
||||
import mqtt from "mqtt";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
const { t } = useI18n();
|
||||
const { openToast, cancelToastOpen } = inject("app_toast");
|
||||
const { formState } = inject(
|
||||
"asset_table_modal_form"
|
||||
);
|
||||
const { formState } = inject("asset_table_modal_form");
|
||||
const BASEURL = import.meta.env.VITE_MQTT_BASEURL;
|
||||
// MQTT相關
|
||||
const mqttClient = ref(null); // MQTT客戶端
|
||||
@ -26,10 +26,13 @@ const openModal = () => {
|
||||
const connectMqtt = () => {
|
||||
const topic = formState.value.topic || ""; // 取得主題
|
||||
const mqttHost = `${BASEURL}`;
|
||||
const protocol = import.meta.env.MODE === "production" ? "wss" : "ws"; // 根據伺服器配置,需要設置為 "ws" 或 "wss"
|
||||
const protocol = "wss"; // 根據伺服器配置,需要設置為 "ws" 或 "wss"
|
||||
mqttClient.value = mqtt.connect(mqttHost, {
|
||||
protocol,
|
||||
protocol,
|
||||
reconnectPeriod: 1000, // 每秒嘗試重新連線
|
||||
username: "admin", // MQTT 帳號
|
||||
password: "mjmadmin@99", // MQTT 密碼
|
||||
port: 443,
|
||||
});
|
||||
|
||||
mqttClient.value.on("connect", () => {
|
||||
@ -47,7 +50,14 @@ const connectMqtt = () => {
|
||||
|
||||
mqttClient.value.on("message", (topic, message) => {
|
||||
// 儲存接收到的訊息
|
||||
receivedMessages.value.push({ topic, message: message.toString() });
|
||||
const now = dayjs(); // 使用 dayjs() 取得當前時間
|
||||
const timestamp = now.format("YYYY-MM-DD HH:mm:ss");
|
||||
|
||||
receivedMessages.value.push({
|
||||
topic,
|
||||
message: message.toString(),
|
||||
timestamp: timestamp,
|
||||
});
|
||||
clearInterval(timer); // 收到訊息後清除倒計時
|
||||
});
|
||||
|
||||
@ -84,12 +94,34 @@ const onCancel = () => {
|
||||
}
|
||||
countdown.value = 60;
|
||||
};
|
||||
|
||||
const onSubmit = async () => {
|
||||
const Topic = formState.value.topic;
|
||||
let Payload = "";
|
||||
try {
|
||||
Payload = JSON.stringify(JSON.parse(formState.value.publish_message));
|
||||
} catch (e) {
|
||||
openToast("error", t("msg.incorrect_format"), "#asset_add_table_item");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await postMQTTpublish({ Topic, Payload });
|
||||
if (res.isSuccess) {
|
||||
openToast("success", t("msg.send_successfully"), "#asset_add_table_item");
|
||||
} else {
|
||||
openToast("error", res.msg, "#asset_add_table_item");
|
||||
}
|
||||
} catch (error) {
|
||||
openToast("error", t("setting.mqtt_send_error"), "#asset_add_table_item");
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex w-72">
|
||||
<Input :value="formState" name="topic" >
|
||||
<template #topLeft>MQTT Topic</template>
|
||||
<div class="flex col-span-2 pb-5">
|
||||
<Input :value="formState" name="topic">
|
||||
<template #topLeft>MQTT subscribe topic</template>
|
||||
</Input>
|
||||
<button type="button" class="btn btn-add mt-11 ms-1" @click="openModal">
|
||||
<font-awesome-icon :icon="['fas', 'cog']" />
|
||||
@ -97,7 +129,20 @@ const onCancel = () => {
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<Modal id="mqtt_test" title="MQTT Topic" :onCancel="onCancel" width="400">
|
||||
<div class="flex flex-col col-span-2 border-t-gray-400 border-t py-5">
|
||||
<Input :value="formState" name="topic">
|
||||
<template #topLeft>MQTT publish topic</template>
|
||||
</Input>
|
||||
<Textarea :value="formState" name="publish_message">
|
||||
<template #topLeft>MQTT messages</template>
|
||||
</Textarea>
|
||||
<button type="button" class="btn btn-add mt-6 w-24" @click="onSubmit">
|
||||
<font-awesome-icon :icon="['far', 'paper-plane']" />
|
||||
Send
|
||||
</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">
|
||||
@ -107,8 +152,14 @@ const onCancel = () => {
|
||||
: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>
|
||||
<strong class="text-base block text-info mb-2"
|
||||
>{{ message.topic }} :</strong
|
||||
>
|
||||
<p class="text-sm break-words">{{ message.message }}</p>
|
||||
<p class="text-xs text-slate-200 pt-2">
|
||||
<FontAwesomeIcon :icon="['fas', 'clock']" class="me-1" />
|
||||
{{ message.timestamp }}
|
||||
</p>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
@ -1,21 +1,17 @@
|
||||
<script setup>
|
||||
import { onMounted, ref, inject, onBeforeMount, watch, computed } from "vue";
|
||||
import EffectScatter from "@/components/chart/EffectScatter.vue";
|
||||
import {
|
||||
getAssetFloorList,
|
||||
postAssetFloor,
|
||||
deleteAssetFloor,
|
||||
} from "@/apis/asset";
|
||||
import useFormErrorMessage from "@/hooks/useFormErrorMessage";
|
||||
import useBuildingStore from "@/stores/useBuildingStore";
|
||||
import AssetTableModalLeftInfoIoT from "./AssetTableModalLeftInfoIoT.vue";
|
||||
import AssetTableModalLeftInfoGraph from "./AssetTableModalLeftInfoGraph.vue";
|
||||
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 FILE_BASEURL = import.meta.env.VITE_FILE_API_BASEURL;
|
||||
const { totalCoordinates } = inject("asset_table_data");
|
||||
const { floors } = inject("asset_modal_options");
|
||||
const { updateRightFields, formErrorMsg, formState } = inject(
|
||||
"asset_table_modal_form"
|
||||
);
|
||||
@ -34,7 +30,6 @@ onBeforeMount(() => {
|
||||
|
||||
const asset_floor_chart = ref(null);
|
||||
const currentFloor = ref(null);
|
||||
const selectedOption = ref("add");
|
||||
const parsedCoordinates = ref(null);
|
||||
|
||||
const defaultOption = (map, data = []) => {
|
||||
@ -45,7 +40,10 @@ const defaultOption = (map, data = []) => {
|
||||
name: coordString,
|
||||
value: coordinate,
|
||||
itemStyle: {
|
||||
color: coordString === formState.value.device_coordinate ? "#0000FF" : "#b02a02",
|
||||
color:
|
||||
coordString === formState.value.device_coordinate
|
||||
? "#0000FF"
|
||||
: "#b02a02",
|
||||
},
|
||||
};
|
||||
});
|
||||
@ -75,13 +73,14 @@ const defaultOption = (map, data = []) => {
|
||||
watch(currentFloor, (newValue) => {
|
||||
if (newValue?.floor_map_name) {
|
||||
const coordinates =
|
||||
(totalCoordinates.value?.[newValue.floor_guid]?.filter(
|
||||
totalCoordinates.value?.[newValue.floor_guid]?.filter(
|
||||
(coord) => coord !== ""
|
||||
) || []);
|
||||
) || [];
|
||||
|
||||
parsedCoordinates.value = coordinates.length > 0
|
||||
? coordinates.map((coord) => JSON.parse(coord))
|
||||
: [];
|
||||
parsedCoordinates.value =
|
||||
coordinates.length > 0
|
||||
? coordinates.map((coord) => JSON.parse(coord))
|
||||
: [];
|
||||
|
||||
asset_floor_chart.value.updateSvg(
|
||||
{
|
||||
@ -92,12 +91,6 @@ watch(currentFloor, (newValue) => {
|
||||
);
|
||||
}
|
||||
});
|
||||
const floors = ref([]);
|
||||
|
||||
const getFloors = async () => {
|
||||
const res = await getAssetFloorList();
|
||||
floors.value = res.data[0]?.floors.map((d) => ({ ...d, key: d.floor_guid }));
|
||||
};
|
||||
|
||||
watch(
|
||||
formState,
|
||||
@ -120,136 +113,31 @@ const getCoordinate = (position) => {
|
||||
formState.value.device_coordinate = JSON.stringify(position);
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
getFloors();
|
||||
});
|
||||
|
||||
// modal
|
||||
const openModal = () => {
|
||||
if (selectedOption.value === "add") {
|
||||
FloorFormState.value = {
|
||||
full_name: "",
|
||||
floorFile: [],
|
||||
};
|
||||
} else if (selectedOption.value === "edit") {
|
||||
const floor = floors.value.find(
|
||||
(f) => f.floor_guid === formState.value.floor_guid
|
||||
);
|
||||
if (floor) {
|
||||
console.log("floor", floor);
|
||||
FloorFormState.value = {
|
||||
full_name: floor.full_name,
|
||||
floorFile: [],
|
||||
};
|
||||
}
|
||||
}
|
||||
asset_add_floor.showModal();
|
||||
};
|
||||
|
||||
const form = ref(null);
|
||||
const FloorFormState = ref({
|
||||
full_name: "",
|
||||
floorFile: [],
|
||||
});
|
||||
|
||||
const floorScheme = yup.object({
|
||||
full_name: yup.string().required(t("button.required")),
|
||||
floorFile: yup.array(),
|
||||
});
|
||||
|
||||
const updateFileList = (files) => {
|
||||
console.log("file", files);
|
||||
FloorFormState.value.floorFile = files;
|
||||
};
|
||||
|
||||
const {
|
||||
formErrorMsg: floorFormErrorMsg,
|
||||
handleSubmit,
|
||||
handleErrorReset,
|
||||
updateScheme,
|
||||
} = useFormErrorMessage(floorScheme);
|
||||
const onOk = async () => {
|
||||
const value = handleSubmit(floorScheme, FloorFormState.value);
|
||||
const formData = new FormData(form.value);
|
||||
formData.append("floor_guid", selectedOption.value === "add" ? null :currentFloor.value.floor_guid);
|
||||
formData.append("building_tag", store.selectedBuilding.building_tag);
|
||||
formData.append("initMapName", FloorFormState.value.floorFile[0]?.name);
|
||||
formData.append("mapFile", FloorFormState.value.floorFile[0]);
|
||||
formData.delete("floorFile");
|
||||
for (let [key, value] of formData) {
|
||||
console.log(key, value);
|
||||
}
|
||||
|
||||
const res = await postAssetFloor(formData);
|
||||
if (res.isSuccess) {
|
||||
getFloors();
|
||||
onCancel();
|
||||
}
|
||||
};
|
||||
const onDelete = async () => {
|
||||
openToast("warning", t("msg.sure_to_delete"), "#asset_add_table_item", async () => {
|
||||
await cancelToastOpen();
|
||||
const res = await deleteAssetFloor({
|
||||
floor_guid: formState.value.floor_guid,
|
||||
});
|
||||
if (res.isSuccess) {
|
||||
getFloors();
|
||||
openToast("success", t("msg.delete_success"), "#asset_add_table_item");
|
||||
} else {
|
||||
openToast("error", res.msg, "#asset_add_table_item");
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const onCancel = () => {
|
||||
FloorFormState.value = {
|
||||
full_name: "",
|
||||
floorFile: [],
|
||||
};
|
||||
asset_add_floor.close();
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- 平面圖 -->
|
||||
|
||||
|
||||
<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"
|
||||
name="floor_guid"
|
||||
Attribute="full_name"
|
||||
:options="floors"
|
||||
:isBottomLabelExist="false"
|
||||
>
|
||||
<template #topLeft>{{ $t("assetManagement.floor") }}</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>
|
||||
<Select
|
||||
:value="formState"
|
||||
selectClass="border-info focus-within:border-info"
|
||||
name="floor_guid"
|
||||
Attribute="full_name"
|
||||
:options="floors"
|
||||
:isBottomLabelExist="false"
|
||||
>
|
||||
<template #topLeft>{{ $t("assetManagement.floor") }}</template>
|
||||
</Select>
|
||||
<Input
|
||||
:value="formState"
|
||||
width="270"
|
||||
name="device_coordinate"
|
||||
:disabled="true"
|
||||
>
|
||||
<template #topLeft>{{ $t("assetManagement.device_coordinate") }}</template>
|
||||
<template #topLeft>{{
|
||||
$t("assetManagement.device_coordinate")
|
||||
}}</template>
|
||||
<template #bottomLeft
|
||||
><span class="text-error text-base">
|
||||
{{ formErrorMsg.device_coordinate }}
|
||||
@ -257,7 +145,7 @@ const onCancel = () => {
|
||||
></Input
|
||||
>
|
||||
</div>
|
||||
<div class="relative">
|
||||
<div class="relative min-h-[70vh]">
|
||||
<EffectScatter
|
||||
id="asset_floor_chart"
|
||||
ref="asset_floor_chart"
|
||||
@ -271,52 +159,13 @@ const onCancel = () => {
|
||||
/>
|
||||
<div
|
||||
v-if="!currentFloor?.floor_map_url"
|
||||
class="absolute top-0 left-0 flex justify-center items-center min-h-[500px] w-full border border-stone-900 shadow-lg bg-sub-success bg-opacity-25 rounded-md"
|
||||
class="absolute top-0 left-0 flex justify-center items-center min-h-[70vh] w-full border border-stone-900 shadow-lg bg-sub-success bg-opacity-25 rounded-md"
|
||||
>
|
||||
<p class="text-2xl">{{ $t("assetManagement.add_floor_text") }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<Modal
|
||||
id="asset_add_floor"
|
||||
:title="t('assetManagement.floor_plan')"
|
||||
:onCancel="onCancel"
|
||||
width="400"
|
||||
>
|
||||
<template #modalContent>
|
||||
<form ref="form">
|
||||
<Input :value="FloorFormState" width="270" name="full_name">
|
||||
<template #topLeft>{{ $t("assetManagement.system_name") }}</template>
|
||||
<template #bottomLeft
|
||||
><span class="text-error text-base">
|
||||
{{ floorFormErrorMsg.full_name }}
|
||||
</span></template
|
||||
></Input
|
||||
>
|
||||
<Upload
|
||||
name="floorFile"
|
||||
:fileList="FloorFormState.floorFile"
|
||||
:getFileList="updateFileList"
|
||||
:multiple="false"
|
||||
class="col-span-2"
|
||||
formats="svg"
|
||||
>
|
||||
<template #topLeft>{{ $t("assetManagement.oriFile") }}</template>
|
||||
</Upload>
|
||||
</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
|
||||
>
|
||||
<AssetTableModalLeftInfoGraph />
|
||||
<AssetTableModalLeftInfoIoT />
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
|
@ -2,7 +2,7 @@
|
||||
import ButtonGroup from "@/components/customUI/ButtonGroup.vue";
|
||||
import Account from "./components/Account.vue";
|
||||
import Role from "./components/Role.vue";
|
||||
import { computed, watch, onBeforeMount } from "vue";
|
||||
import { computed, watch, onBeforeMount, markRaw } from "vue";
|
||||
import useActiveBtn from "@/hooks/useActiveBtn";
|
||||
import { useI18n } from "vue-i18n";
|
||||
const { t, locale } = useI18n();
|
||||
@ -18,13 +18,13 @@ const initializeItems = () => {
|
||||
title: t("accountManagement.account_title"),
|
||||
key: "account",
|
||||
active: true,
|
||||
component: Account,
|
||||
component: markRaw(Account),
|
||||
},
|
||||
{
|
||||
title: t("accountManagement.role_title"),
|
||||
key: "role",
|
||||
active: false,
|
||||
component: Role,
|
||||
component: markRaw(Role),
|
||||
},
|
||||
]);
|
||||
};
|
||||
|
@ -127,7 +127,7 @@ const onOk = async () => {
|
||||
id="account_user_modal"
|
||||
:title="formState?.Id ? t('button.edit') : t('button.add')"
|
||||
:onCancel="onCancel"
|
||||
width="710"
|
||||
:width="710"
|
||||
>
|
||||
<template #modalContent>
|
||||
<form ref="form" class="mt-5 w-full flex flex-wrap justify-between">
|
||||
|
@ -9,7 +9,7 @@ const { t } = useI18n();
|
||||
const { openToast } = inject("app_toast");
|
||||
|
||||
const props = defineProps({
|
||||
account: String,
|
||||
account: Object,
|
||||
});
|
||||
|
||||
const formState = ref({
|
||||
@ -48,7 +48,7 @@ const onOk = async () => {
|
||||
id="account_user_password_modal"
|
||||
:title="t('accountManagement.change_password')"
|
||||
:onCancel="onCancel"
|
||||
width="710"
|
||||
:width="710"
|
||||
>
|
||||
<template #modalContent>
|
||||
<p class="mt-10 text-3xl">{{ account.Name }}</p>
|
||||
|
@ -13,7 +13,7 @@ import useActiveBtn from "@/hooks/useActiveBtn";
|
||||
import { useI18n } from "vue-i18n";
|
||||
const { t } = useI18n();
|
||||
const props = defineProps({
|
||||
selectedRole: String,
|
||||
selectedRole: Object,
|
||||
cancelModal: Function,
|
||||
disabled: Boolean,
|
||||
update: Function,
|
||||
|
@ -2,7 +2,7 @@
|
||||
import ButtonGroup from "@/components/customUI/ButtonGroup.vue";
|
||||
import AlertQuery from "./components/AlertQuery/AlertQuery.vue";
|
||||
import AlertSetting from "./components/AlertSetting/AlertSetting.vue";
|
||||
import { computed, watch, onBeforeMount } from "vue";
|
||||
import { computed, watch, onBeforeMount, markRaw } from "vue";
|
||||
import useActiveBtn from "@/hooks/useActiveBtn";
|
||||
import { useI18n } from "vue-i18n";
|
||||
const { t, locale } = useI18n();
|
||||
@ -18,13 +18,13 @@ const initializeItems = () => {
|
||||
title: t("alert.query_title"),
|
||||
key: "Query",
|
||||
active: true,
|
||||
component: AlertQuery,
|
||||
component: markRaw(AlertQuery),
|
||||
},
|
||||
{
|
||||
title: t("alert.setting_title"),
|
||||
key: "Setting",
|
||||
active: false,
|
||||
component: AlertSetting,
|
||||
component: markRaw(AlertSetting),
|
||||
},
|
||||
]);
|
||||
};
|
||||
|
@ -4,8 +4,7 @@ import AlertTable from "./AlertTable.vue";
|
||||
import AlertTableModal from "./AlertTableModal.vue";
|
||||
import { ref, provide, onMounted } from "vue";
|
||||
import useSearchParam from "@/hooks/useSearchParam";
|
||||
import useAlarmData from "@/hooks/baja/useAlarmData";
|
||||
import { getAlertFormId } from "@/apis/alert";
|
||||
import { getAlertLog } from "@/apis/alert";
|
||||
import {
|
||||
getOperationDeviceList,
|
||||
getOperationCompanyList,
|
||||
@ -13,18 +12,14 @@ import {
|
||||
} from "@/apis/operation";
|
||||
import { getAccountUserList } from "@/apis/account";
|
||||
import useBuildingStore from "@/stores/useBuildingStore";
|
||||
import useAlarmStore from "@/stores/useAlarmStore";
|
||||
const storeAlarm = useAlarmStore();
|
||||
|
||||
const { searchParams } = useSearchParam();
|
||||
const { getAlarmByBaja, alarmData } = useAlarmData();
|
||||
const store = useBuildingStore();
|
||||
|
||||
const tableLoading = ref(false);
|
||||
const dataSource = ref([]);
|
||||
const model_data = ref({
|
||||
model_userList: [],
|
||||
model_devList: [],
|
||||
model_companyList: [],
|
||||
});
|
||||
|
||||
@ -49,23 +44,6 @@ const updateEditRecord = (data) => {
|
||||
}
|
||||
};
|
||||
|
||||
const getFormId = async (uuid) => {
|
||||
const res = await getAlertFormId(uuid);
|
||||
return res.data;
|
||||
};
|
||||
|
||||
const getModalDevList = async () => {
|
||||
const sub_system_tags = searchParams.value.system_tag.map(
|
||||
(tag) => tag.split("_")[1]
|
||||
);
|
||||
const res = await getOperationDeviceList({
|
||||
list_sub_system_tag: sub_system_tags,
|
||||
device_building_tag: store.buildings[0].building_tag,
|
||||
device_area_tag: "NTPC",
|
||||
});
|
||||
return res.data.map((d) => ({ ...d, key: d.device_number }));
|
||||
};
|
||||
|
||||
const getModalUserList = async () => {
|
||||
const res = await getAccountUserList({});
|
||||
return res.data.map((d) => ({ ...d, key: d.userinfo_guid }));
|
||||
@ -77,63 +55,23 @@ const getModalCompanyList = async () => {
|
||||
};
|
||||
|
||||
const getAllOptions = async () => {
|
||||
Promise.all([
|
||||
getModalDevList(),
|
||||
getModalUserList(),
|
||||
getModalCompanyList(),
|
||||
]).then(([devices, users, companies]) => {
|
||||
model_data.value.model_userList = users;
|
||||
model_data.value.model_devList = devices;
|
||||
model_data.value.model_companyList = companies;
|
||||
});
|
||||
};
|
||||
|
||||
const updateDataSource = (data) => {
|
||||
dataSource.value = (data || []).map((d) => ({ ...d, key: d.uuid }));
|
||||
Promise.all([getModalUserList(), getModalCompanyList()]).then(
|
||||
([users, companies]) => {
|
||||
model_data.value.model_userList = users;
|
||||
model_data.value.model_companyList = companies;
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const search = async () => {
|
||||
tableLoading.value = true;
|
||||
if (Object.keys(searchParams.value).length !== 0) {
|
||||
storeAlarm.getAlarmDataFromBaja();
|
||||
updateDataSource(storeAlarm.alarmData);
|
||||
const res = await getAlertLog({
|
||||
...searchParams.value,
|
||||
isRecovery: Number(searchParams.value.isRecovery),
|
||||
});
|
||||
dataSource.value = (res.data || []).map((d) => ({ ...d, key: d.id }));
|
||||
tableLoading.value = false;
|
||||
/*
|
||||
await getAlarmByBaja(
|
||||
searchParams.value.start_created_at,
|
||||
searchParams.value.end_created_at,
|
||||
searchParams.value.isRecover,
|
||||
searchParams.value.isAck,
|
||||
searchParams.value.system_tag,
|
||||
async (result) => {
|
||||
alarmData.value = result.data;
|
||||
// 確保所有 alarm 都包含 formId
|
||||
alarmData.value = alarmData.value.map((alarm) => ({
|
||||
...alarm,
|
||||
formId: null, // 初始設置 formId 為 null
|
||||
}));
|
||||
|
||||
const uuids = alarmData.value.map((alarm) => ({ uuid: alarm.uuid }));
|
||||
const formIds = await getFormId(uuids);
|
||||
|
||||
if (Array.isArray(formIds)) {
|
||||
formIds.forEach((form) => {
|
||||
if (form && form.uuid) {
|
||||
const index = alarmData.value.findIndex(
|
||||
(alarm) => alarm.uuid === form.uuid
|
||||
);
|
||||
if (index !== -1) {
|
||||
alarmData.value[index].formId = form.formId || null;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
updateDataSource(alarmData.value);
|
||||
tableLoading.value = false;
|
||||
}
|
||||
);
|
||||
*/
|
||||
}
|
||||
};
|
||||
|
||||
@ -141,11 +79,17 @@ const openModal = async (record) => {
|
||||
try {
|
||||
if (record.formId) {
|
||||
const res = await getOperationEditRecord(record.formId);
|
||||
updateEditRecord({ ...res.data, uuid: record.uuid });
|
||||
updateEditRecord({
|
||||
...res.data,
|
||||
uuid: res.data.error_code,
|
||||
device_number: record.device_number
|
||||
});
|
||||
} else {
|
||||
updateEditRecord(record);
|
||||
updateEditRecord({
|
||||
...record,
|
||||
uuid: record.id,
|
||||
});
|
||||
}
|
||||
await getAllOptions();
|
||||
alert_action_item.showModal();
|
||||
} catch (error) {
|
||||
console.error("Error opening modal:", error);
|
||||
@ -153,6 +97,7 @@ const openModal = async (record) => {
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
getAllOptions();
|
||||
if (Object.keys(searchParams.value).length !== 0) {
|
||||
search();
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
<script setup>
|
||||
import { inject } from "vue";
|
||||
import AlertSearchNormalBtns from "./AlertSearchNormalBtns.vue";
|
||||
import AlertSearchAckBtns from "./AlertSearchAckBtns.vue";
|
||||
import AlertSearchTimeRange from "./AlertSearchTimeRange.vue";
|
||||
import AlertSearchTypesButton from "./AlertSearchTypesButton.vue";
|
||||
|
||||
@ -13,7 +12,7 @@ const { search } = inject("alert_table");
|
||||
<div class="w-full flex flex-wrap flex-col custom-border px-4 pt-0 pb-4 mb-4">
|
||||
<div class="w-full flex flex-wrap items-center justify-start">
|
||||
<AlertSearchNormalBtns />
|
||||
<AlertSearchAckBtns />
|
||||
<!-- <AlertSearchAckBtns /> -->
|
||||
<AlertSearchTimeRange />
|
||||
<button class="btn btn-search ml-8" @click.stop.prevent="search">
|
||||
<font-awesome-icon :icon="['fas', 'search']" class=" text-lg" />
|
||||
|
@ -11,12 +11,12 @@ const initializeItems = () => {
|
||||
setItems([
|
||||
{
|
||||
title: t("alert.offnormal"),
|
||||
key: "offnormal",
|
||||
key: 1,
|
||||
active: true,
|
||||
},
|
||||
{
|
||||
title: t("alert.normal"),
|
||||
key: "normal",
|
||||
key: 2,
|
||||
active: false,
|
||||
},
|
||||
]);
|
||||
@ -34,7 +34,7 @@ watch(locale, () => {
|
||||
watch(
|
||||
selectedBtn,
|
||||
(newValue) => {
|
||||
changeParams({ ...searchParams.value, isRecover: newValue.key });
|
||||
changeParams({ ...searchParams.value, isRecovery: newValue.key });
|
||||
}
|
||||
);
|
||||
|
||||
@ -42,8 +42,8 @@ watch(
|
||||
watch(
|
||||
searchParams,
|
||||
(newSearchParams) => {
|
||||
if (!newSearchParams.isRecover) {
|
||||
changeParams({ ...newSearchParams, isRecover: "offnormal" });
|
||||
if (!newSearchParams.isRecovery) {
|
||||
changeParams({ ...newSearchParams, isRecovery: 1 });
|
||||
}
|
||||
},
|
||||
{ immediate: true } // 確保在初始化立即觸發
|
||||
|
@ -9,17 +9,17 @@ const { searchParams, changeParams } = useSearchParam();
|
||||
const dateRange = ref([
|
||||
{
|
||||
key: "start_at",
|
||||
value: searchParams.value.start_created_at
|
||||
? dayjs(searchParams.value.start_created_at).valueOf()
|
||||
: dayjs().subtract(30, "day").valueOf(),
|
||||
value: searchParams.value.Start_date
|
||||
? dayjs(searchParams.value.Start_date)
|
||||
: dayjs().subtract(30, "day"),
|
||||
dateFormat: "yyyy-MM-dd",
|
||||
placeholder: t("alert.start_date"),
|
||||
},
|
||||
{
|
||||
key: "end_at",
|
||||
value: searchParams.value.end_created_at
|
||||
? dayjs(searchParams.value.end_created_at).valueOf()
|
||||
: dayjs().valueOf(),
|
||||
value: searchParams.value.End_date
|
||||
? dayjs(searchParams.value.End_date)
|
||||
: dayjs(),
|
||||
dateFormat: "yyyy-MM-dd",
|
||||
placeholder: t("alert.end_date"),
|
||||
},
|
||||
@ -29,13 +29,13 @@ const changeTimeRange = () => {
|
||||
const newRange = [
|
||||
{
|
||||
key: "start_at",
|
||||
value: dayjs().subtract(30, "day").startOf("day").valueOf(), // 向前推30天並設置為當天的00:00:00.000
|
||||
value: dayjs().subtract(30, "day"),
|
||||
dateFormat: "yyyy-MM-dd",
|
||||
placeholder: t("alert.start_date"),
|
||||
},
|
||||
{
|
||||
key: "end_at",
|
||||
value: dayjs().endOf("day").valueOf(), // 設置為今日的23:59:59.999
|
||||
value: dayjs().endOf("day"),
|
||||
dateFormat: "yyyy-MM-dd",
|
||||
placeholder: t("alert.end_date"),
|
||||
},
|
||||
@ -45,16 +45,16 @@ const changeTimeRange = () => {
|
||||
|
||||
changeParams({
|
||||
...searchParams.value,
|
||||
start_created_at: newRange[0].value,
|
||||
end_created_at: newRange[1].value,
|
||||
Start_date: newRange[0].value,
|
||||
End_date: newRange[1].value,
|
||||
});
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
// 初始化日期範圍
|
||||
if (
|
||||
!searchParams.value.start_created_at ||
|
||||
!searchParams.value.end_created_at
|
||||
!searchParams.value.Start_date ||
|
||||
!searchParams.value.End_date
|
||||
) {
|
||||
changeTimeRange();
|
||||
}
|
||||
@ -66,10 +66,8 @@ watch(
|
||||
() => {
|
||||
changeParams({
|
||||
...searchParams.value,
|
||||
start_created_at: dayjs(dateRange.value[0].value)
|
||||
.startOf("day")
|
||||
.valueOf(),
|
||||
end_created_at: dayjs(dateRange.value[1].value).endOf("day").valueOf(),
|
||||
Start_date: dayjs(dateRange.value[0].value).format("YYYY-MM-DD"),
|
||||
End_date: dayjs(dateRange.value[1].value).format("YYYY-MM-DD"),
|
||||
});
|
||||
},
|
||||
{ deep: true } // 確保在初始化立即觸發
|
||||
|
@ -12,12 +12,12 @@ const changeCheckedItem = () => {
|
||||
if (checkedItem.value.length === store.subSys.length) {
|
||||
changeParams({
|
||||
...searchParams.value,
|
||||
system_tag: [],
|
||||
device_name_tag: [],
|
||||
});
|
||||
} else {
|
||||
changeParams({
|
||||
...searchParams.value,
|
||||
system_tag: store.subSys.map(({ main_system_tag, sub_system_tag }) => main_system_tag+`_`+sub_system_tag),
|
||||
device_name_tag: store.subSys.map(({ sub_system_tag }) => sub_system_tag),
|
||||
});
|
||||
}
|
||||
};
|
||||
@ -26,16 +26,16 @@ const onChange = (value, checked) => {
|
||||
if (checked) {
|
||||
changeParams({
|
||||
...searchParams.value,
|
||||
system_tag: searchParams.value.system_tag
|
||||
? typeof searchParams.value.system_tag === "string"
|
||||
? [searchParams.value.system_tag, value]
|
||||
: [...searchParams.value.system_tag, value]
|
||||
device_name_tag: searchParams.value.device_name_tag
|
||||
? typeof searchParams.value.device_name_tag === "string"
|
||||
? [searchParams.value.device_name_tag, value]
|
||||
: [...searchParams.value.device_name_tag, value]
|
||||
: [value],
|
||||
});
|
||||
} else {
|
||||
changeParams({
|
||||
...searchParams.value,
|
||||
sub_system_tag: searchParams.value.system_tag.filter(
|
||||
device_name_tag: searchParams.value.device_name_tag.filter(
|
||||
(sub) => sub !== value
|
||||
),
|
||||
});
|
||||
@ -43,18 +43,18 @@ const onChange = (value, checked) => {
|
||||
};
|
||||
|
||||
const checkedItem = computed(() =>
|
||||
searchParams.value.system_tag
|
||||
? typeof searchParams.value.system_tag === "string"
|
||||
? [searchParams.value.system_tag]
|
||||
: searchParams.value.system_tag
|
||||
searchParams.value.device_name_tag
|
||||
? typeof searchParams.value.device_name_tag === "string"
|
||||
? [searchParams.value.device_name_tag]
|
||||
: searchParams.value.device_name_tag
|
||||
: []
|
||||
);
|
||||
|
||||
watch(searchParams, (newValue) => {
|
||||
if (!newValue.system_tag) {
|
||||
if (!newValue.device_name_tag) {
|
||||
changeParams({
|
||||
...newValue,
|
||||
system_tag: [store.subSys[0]?.main_system_tag+`_`+store.subSys[0]?.sub_system_tag],
|
||||
device_name_tag: [store.subSys[0]?.sub_system_tag],
|
||||
});
|
||||
}
|
||||
});
|
||||
@ -75,8 +75,8 @@ watch(searchParams, (newValue) => {
|
||||
v-for="sub in store.subSys"
|
||||
:key="sub.key"
|
||||
:title="sub.full_name"
|
||||
:value="sub.main_system_tag+`_`+sub.sub_system_tag"
|
||||
:checked="checkedItem.includes(sub.main_system_tag+`_`+sub.sub_system_tag)"
|
||||
:value="sub.sub_system_tag"
|
||||
:checked="checkedItem.includes(sub.sub_system_tag)"
|
||||
class="mx-3 my-3 xl:my-0 text-lg"
|
||||
:onChange="onChange"
|
||||
/>
|
||||
|
@ -1,6 +1,5 @@
|
||||
<script setup>
|
||||
import { inject, computed } from "vue";
|
||||
import { postChgAck } from "@/apis/alert";
|
||||
import { Button } from "ant-design-vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
const { t } = useI18n();
|
||||
@ -8,71 +7,39 @@ const { dataSource, openModal, search, tableLoading } = inject("alert_table");
|
||||
|
||||
const columns = computed(() => [
|
||||
{
|
||||
key: "building_tag",
|
||||
title: t("alert.building_and_floor"),
|
||||
},
|
||||
{
|
||||
key: "uuid",
|
||||
key: "id",
|
||||
title: t("alert.uuid"),
|
||||
},
|
||||
{
|
||||
key: "alarmClass",
|
||||
key: "factor",
|
||||
title: t("alert.alarmClass"),
|
||||
},
|
||||
{
|
||||
key: "full_name",
|
||||
key: "device_number",
|
||||
title: t("alert.device_name"),
|
||||
},
|
||||
{
|
||||
key: "device_number",
|
||||
title: t("alert.device_number"),
|
||||
key: "points",
|
||||
title: t("alert.device_point_name"),
|
||||
},
|
||||
{
|
||||
key: "timestamp_date",
|
||||
title: t("alert.date"),
|
||||
},
|
||||
{
|
||||
key: "timestamp_time",
|
||||
key: "created_at",
|
||||
title: t("alert.time"),
|
||||
},
|
||||
{
|
||||
key: "msg",
|
||||
key: "reason",
|
||||
title: t("alert.error_msg"),
|
||||
},
|
||||
{
|
||||
key: "ackState",
|
||||
title: t("alert.ack_state"),
|
||||
},
|
||||
{
|
||||
key: "repairOrder",
|
||||
title: t("alert.repair_order_number"),
|
||||
},
|
||||
]);
|
||||
|
||||
const chgAck = async (devUuid) => {
|
||||
const res = await postChgAck(devUuid);
|
||||
if (res.isSuccess) {
|
||||
search?.();
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Table :loading="tableLoading" :columns="columns" :dataSource="dataSource">
|
||||
<template #bodyCell="{ record, column, index }">
|
||||
<template v-if="column.key === 'ackState'">
|
||||
<template v-if="record.ackState === 'Unacked'">
|
||||
<button
|
||||
class="btn btn-sm btn-success text-white whitespace-nowrap mr-2"
|
||||
@click.stop.prevent="() => chgAck(record.uuid)"
|
||||
>
|
||||
{{ $t("alert.unacked") }}
|
||||
</button>
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ record.ackedTime }}
|
||||
</template>
|
||||
</template>
|
||||
<template v-if="column.key === 'repairOrder'">
|
||||
<button
|
||||
class="btn btn-sm btn-success text-white whitespace-nowrap mr-2"
|
||||
|
@ -27,12 +27,13 @@ const dateItem = ref([
|
||||
|
||||
const formState = ref({
|
||||
formId: null,
|
||||
uuid: "",
|
||||
uuid: null,
|
||||
work_type: 2,
|
||||
fix_do: "",
|
||||
fix_do_code: "",
|
||||
fix_firm: "",
|
||||
status: 0,
|
||||
device_number: "",
|
||||
work_person_id: "",
|
||||
start_time: dayjs().format("YYYY-MM-DD"),
|
||||
notice: "",
|
||||
@ -49,7 +50,6 @@ const { model_data, updateEditRecord, search } = inject("alert_modal") || {
|
||||
let alertSchema = yup.object({
|
||||
start_time: yup.date().required(t("button.required")),
|
||||
fix_do: yup.string().required(t("button.required")),
|
||||
fix_do_code: yup.string().required(t("button.required")),
|
||||
fix_firm: yup.string().required(t("button.required")),
|
||||
status: yup.number().required(t("button.required")),
|
||||
work_person_id: yup.string().required(t("button.required")),
|
||||
@ -88,12 +88,12 @@ const onOk = async () => {
|
||||
props.editRecord.uuid && formData.append("error_code", props.editRecord.uuid);
|
||||
|
||||
formData.append("work_type", 2);
|
||||
|
||||
if (props.editRecord.device_number) {
|
||||
formData.append("fix_do_code", props.editRecord.device_number);
|
||||
} else if (props.editRecord.fix_do_code) {
|
||||
formData.append("fix_do_code", props.editRecord.fix_do_code);
|
||||
}
|
||||
formData.append(
|
||||
"fix_do_code",
|
||||
props.editRecord.main_id
|
||||
? props.editRecord.main_id
|
||||
: props.editRecord.fix_do_code
|
||||
);
|
||||
|
||||
try {
|
||||
const value = await handleSubmit(alertSchema, formState.value);
|
||||
@ -110,12 +110,13 @@ const onOk = async () => {
|
||||
const onCancel = () => {
|
||||
formState.value = {
|
||||
formId: null,
|
||||
uuid: "",
|
||||
uuid: null,
|
||||
work_type: 2,
|
||||
fix_do: "",
|
||||
fix_do_code: "",
|
||||
fix_firm: "",
|
||||
status: 0,
|
||||
device_number: "",
|
||||
work_person_id: "",
|
||||
start_time: dayjs().format("YYYY-MM-DD"),
|
||||
notice: "",
|
||||
@ -147,10 +148,6 @@ watch(
|
||||
if (key === "full_name") {
|
||||
formState.value.fix_do = value;
|
||||
}
|
||||
// 維修項目代碼
|
||||
if (key === "device_number" || key === "fix_do_code") {
|
||||
formState.value.fix_do_code = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -163,12 +160,12 @@ watch(
|
||||
id="alert_action_item"
|
||||
:title="t('alert.repair_order')"
|
||||
:onCancel="onCancel"
|
||||
width="710"
|
||||
:width="710"
|
||||
>
|
||||
<template #modalContent>
|
||||
<form ref="form" class="mt-5 w-full flex flex-wrap justify-between">
|
||||
<Input
|
||||
v-if="formState.value && formState.value.formId"
|
||||
v-if="formState && formState.formId"
|
||||
class="my-2"
|
||||
:value="formState"
|
||||
name="formId"
|
||||
@ -202,7 +199,7 @@ watch(
|
||||
{
|
||||
key: 1,
|
||||
value: 1,
|
||||
title: $t('alert.maintenance'),
|
||||
title: $t('operation.maintenance'),
|
||||
},
|
||||
{
|
||||
key: 2,
|
||||
@ -223,13 +220,10 @@ watch(
|
||||
</span>
|
||||
</template>
|
||||
</Input>
|
||||
<Select
|
||||
:value="formState"
|
||||
<Input
|
||||
class="my-2"
|
||||
selectClass="border-info focus-within:border-info"
|
||||
name="fix_do_code"
|
||||
Attribute="full_name"
|
||||
:options="model_data.model_devList"
|
||||
:value="formState"
|
||||
name="device_number"
|
||||
:required="true"
|
||||
:disabled="true"
|
||||
>
|
||||
@ -239,7 +233,7 @@ watch(
|
||||
{{ formErrorMsg.fix_do_code }}
|
||||
</span></template
|
||||
>
|
||||
</Select>
|
||||
</Input>
|
||||
<Select
|
||||
:value="formState"
|
||||
class="my-2"
|
||||
@ -323,7 +317,7 @@ watch(
|
||||
class="btn btn-outline-success"
|
||||
@click.stop.prevent="onOk"
|
||||
>
|
||||
{{ $t("button.submit")}}
|
||||
{{ $t("button.submit") }}
|
||||
</button>
|
||||
</template>
|
||||
</Modal>
|
||||
|
@ -94,9 +94,8 @@ const closeModal = () => {
|
||||
<Modal
|
||||
id="notify_add_table_item"
|
||||
:title="t('alert.notify_list')"
|
||||
:open="open"
|
||||
:onCancel="closeModal"
|
||||
width="300"
|
||||
:width="300"
|
||||
>
|
||||
<template #modalContent>
|
||||
<form ref="form" class="mt-5 flex flex-col items-center">
|
||||
@ -128,7 +127,7 @@ const closeModal = () => {
|
||||
<p class="text-light text-base">{{ $t("alert.notify_items") }}</p>
|
||||
<AlertNoticesTable
|
||||
:SaveCheckAuth="SaveCheckAuth"
|
||||
:NoticeData="[noticeList[2]]"
|
||||
:NoticeData="[noticeList[1]]"
|
||||
:onChange="onChange"
|
||||
/>
|
||||
<span class="text-error text-base">
|
||||
|
@ -4,6 +4,9 @@ import {
|
||||
getOutliersList,
|
||||
getOutliersDevList,
|
||||
getOutliersPoints,
|
||||
getFactors,
|
||||
delOutliersSetting,
|
||||
postMQTTRefresh,
|
||||
} from "@/apis/alert";
|
||||
import useSearchParam from "@/hooks/useSearchParam";
|
||||
import AlertOutliersTableAddModal from "./AlertOutliersTableAddModal.vue";
|
||||
@ -11,12 +14,14 @@ import AlertOutliersTableAddModal from "./AlertOutliersTableAddModal.vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
const { t } = useI18n();
|
||||
const { noticeList, timesList } = inject("notify_table");
|
||||
const { openToast, cancelToastOpen } = inject("app_toast");
|
||||
const { searchParams, changeParams } = useSearchParam();
|
||||
|
||||
const tableData = ref([]);
|
||||
const dev_data = ref({
|
||||
devList: [],
|
||||
alarmPoints: [],
|
||||
alarmFactors: [],
|
||||
});
|
||||
const editRecord = ref(null);
|
||||
|
||||
@ -32,7 +37,7 @@ const columns = computed(() => [
|
||||
},
|
||||
{
|
||||
title: t("alert.item"),
|
||||
key: "points",
|
||||
key: "points_name",
|
||||
},
|
||||
{
|
||||
title: t("alert.enable"),
|
||||
@ -42,35 +47,35 @@ const columns = computed(() => [
|
||||
title: t("alert.qualifications"),
|
||||
key: "factor_name",
|
||||
},
|
||||
{
|
||||
title: `${t("alert.upper_limit")} (>=)`,
|
||||
key: "highLimit",
|
||||
},
|
||||
{
|
||||
title: `${t("alert.lower_limit")} (<=)`,
|
||||
key: "lowLimit",
|
||||
},
|
||||
{
|
||||
title: t("alert.highDelay"),
|
||||
key: "highDelay",
|
||||
},
|
||||
{
|
||||
title: t("alert.lowDelay"),
|
||||
key: "lowDelay",
|
||||
},
|
||||
// {
|
||||
// title: `${t("alert.upper_limit")} (>=)`,
|
||||
// key: "highLimit",
|
||||
// },
|
||||
// {
|
||||
// title: `${t("alert.lower_limit")} (<=)`,
|
||||
// key: "lowLimit",
|
||||
// },
|
||||
// {
|
||||
// title: t("alert.highDelay"),
|
||||
// key: "highDelay",
|
||||
// },
|
||||
// {
|
||||
// title: t("alert.lowDelay"),
|
||||
// key: "lowDelay",
|
||||
// },
|
||||
{
|
||||
title: t("alert.warning_method"),
|
||||
key: "warning_method",
|
||||
},
|
||||
{
|
||||
title: t("alert.warning_time"),
|
||||
key: "warning_time",
|
||||
width: 150,
|
||||
},
|
||||
// {
|
||||
// title: t("alert.warning_time"),
|
||||
// key: "warning_time",
|
||||
// width: 150,
|
||||
// },
|
||||
{
|
||||
title: t("alert.operation"),
|
||||
key: "operation",
|
||||
width: 150,
|
||||
width: 300,
|
||||
},
|
||||
]);
|
||||
|
||||
@ -94,6 +99,14 @@ const getAlarmPoints = async () => {
|
||||
}));
|
||||
};
|
||||
|
||||
const getFactor = async () => {
|
||||
const res = await getFactors();
|
||||
return res.data.map((d) => ({
|
||||
...d,
|
||||
key: d.id,
|
||||
}));
|
||||
};
|
||||
|
||||
const getOutliersData = async () => {
|
||||
const res = await getOutliersList({
|
||||
device_name_tag: searchParams.value?.subSys_id,
|
||||
@ -106,10 +119,9 @@ const getOutliersData = async () => {
|
||||
const matchedPoints = dev_data.value.alarmPoints.find(
|
||||
(p) => p.points === item.points
|
||||
);
|
||||
const matchedFactor =
|
||||
matchedPoints?.factor && item.factor
|
||||
? matchedPoints?.factor?.find((f) => f.id === item.factor)
|
||||
: null;
|
||||
const matchedFactor = dev_data.value.alarmFactors.find(
|
||||
(p) => p.id === item.factor
|
||||
);
|
||||
const matchedTime = timesList.value.find(
|
||||
(t) => t.id === item.schedule_id
|
||||
);
|
||||
@ -126,20 +138,24 @@ const getOutliersData = async () => {
|
||||
return {
|
||||
...item,
|
||||
device_name: matchedDevice ? matchedDevice.device_name : "",
|
||||
points: matchedPoints ? matchedPoints.full_name : "",
|
||||
is_bool: matchedPoints ? matchedPoints.is_bool : 1,
|
||||
points_name: matchedPoints ? matchedPoints.full_name : "",
|
||||
factor_name: matchedFactor ? matchedFactor.full_name : "",
|
||||
warning_method: warningMethodKeys,
|
||||
warning_time: matchedTime ? matchedTime.schedule_name : "",
|
||||
// warning_time: matchedTime ? matchedTime.schedule_name : "",
|
||||
};
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const getAllOptions = async () => {
|
||||
const [devices, points] = await Promise.all([getDevList(), getAlarmPoints()]);
|
||||
const [devices, points, factors] = await Promise.all([
|
||||
getDevList(),
|
||||
getAlarmPoints(),
|
||||
getFactor(),
|
||||
]);
|
||||
dev_data.value.devList = devices;
|
||||
dev_data.value.alarmPoints = points;
|
||||
dev_data.value.alarmFactors = factors;
|
||||
};
|
||||
|
||||
watch(
|
||||
@ -162,6 +178,28 @@ const openModal = (record) => {
|
||||
outliers_add_table_item.showModal();
|
||||
};
|
||||
|
||||
const refreshMQTT = async () => {
|
||||
const res = await postMQTTRefresh();
|
||||
if (res.isSuccess) {
|
||||
openToast("success", t("msg.mqtt_refresh"));
|
||||
} else {
|
||||
openToast("error", res.msg, "#outliers_add_table_item");
|
||||
}
|
||||
};
|
||||
|
||||
const remove = async (Id) => {
|
||||
openToast("warning", t("msg.sure_to_delete"), "body", async () => {
|
||||
await cancelToastOpen();
|
||||
const res = await delOutliersSetting(Id);
|
||||
if (res.isSuccess) {
|
||||
getOutliersData();
|
||||
openToast("success", t("msg.delete_success"));
|
||||
} else {
|
||||
openToast("error", res.msg);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const onCancel = () => {
|
||||
editRecord.value = null;
|
||||
outliers_add_table_item.close();
|
||||
@ -169,15 +207,20 @@ const onCancel = () => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex justify-start items-center mt-10">
|
||||
<h3 class="text-xl mr-5">{{ $t("alert.alarm_settings") }}</h3>
|
||||
<AlertOutliersTableAddModal
|
||||
:openModal="openModal"
|
||||
:onCancel="onCancel"
|
||||
:editRecord="editRecord"
|
||||
:getData="getOutliersData"
|
||||
:OptionsData="dev_data"
|
||||
/>
|
||||
<div class="flex items-center justify-between mt-10">
|
||||
<div class="flex">
|
||||
<h3 class="text-xl mr-5">{{ $t("alert.alarm_settings") }}</h3>
|
||||
<AlertOutliersTableAddModal
|
||||
:openModal="openModal"
|
||||
:onCancel="onCancel"
|
||||
:editRecord="editRecord"
|
||||
:getData="getOutliersData"
|
||||
:OptionsData="dev_data"
|
||||
/>
|
||||
</div>
|
||||
<button class="btn btn-sm btn-add" @click.prevent="refreshMQTT">
|
||||
<font-awesome-icon :icon="['fas', 'cog']" />{{ $t("alert.reorganization") }}
|
||||
</button>
|
||||
</div>
|
||||
<Table :columns="columns" :dataSource="tableData" class="mt-3">
|
||||
<template #bodyCell="{ record, column, index }">
|
||||
@ -188,6 +231,12 @@ const onCancel = () => {
|
||||
>
|
||||
{{ $t("button.edit") }}
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-sm btn-error text-white"
|
||||
@click.stop.prevent="() => remove(record.id)"
|
||||
>
|
||||
{{ $t("button.delete") }}
|
||||
</button>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'enable'">
|
||||
{{ record.enable === 1 ? t("alert.yes") : t("alert.no") }}
|
||||
@ -195,9 +244,9 @@ const onCancel = () => {
|
||||
<template v-else-if="column.key === 'warning_method'">
|
||||
<span class="whitespace-pre">{{ record.warning_method }}</span>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'warning_time'">
|
||||
<!-- <template v-else-if="column.key === 'warning_time'">
|
||||
<span class="whitespace-pre">{{ record.warning_time }}</span>
|
||||
</template>
|
||||
</template> -->
|
||||
<template v-else>
|
||||
{{ record[column.key] }}
|
||||
</template>
|
||||
|
@ -26,8 +26,9 @@ const formState = ref({
|
||||
device_name_tag: searchParams.value?.subSys_id,
|
||||
points: "",
|
||||
enable: 0,
|
||||
is_bool: 1,
|
||||
factor: null,
|
||||
factor: 1,
|
||||
alarm_value: "",
|
||||
delay: 0,
|
||||
highLimit: null,
|
||||
lowLimit: null,
|
||||
highDelay: null,
|
||||
@ -51,8 +52,7 @@ const { formErrorMsg, handleSubmit, handleErrorReset } = useFormErrorMessage(
|
||||
);
|
||||
|
||||
const SaveCheckAuth = ref([]);
|
||||
const isBool = ref(1);
|
||||
const factorData = ref([]);
|
||||
const factorNum = ref(1);
|
||||
|
||||
watch(
|
||||
() => props.editRecord,
|
||||
@ -61,32 +61,19 @@ watch(
|
||||
formState.value = {
|
||||
...newValue,
|
||||
};
|
||||
console.log('formState.value',formState.value);
|
||||
|
||||
SaveCheckAuth.value = newValue.notices ? [...newValue.notices] : [];
|
||||
|
||||
if (newValue.points) {
|
||||
onPointsChange(newValue.points);
|
||||
if (newValue.factor) {
|
||||
onFactorsChange(newValue.factor);
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const onPointsChange = (selectedPoint) => {
|
||||
const pointData = props.OptionsData.alarmPoints.find(
|
||||
(p) => p.points === selectedPoint
|
||||
);
|
||||
if (pointData) {
|
||||
isBool.value = pointData.is_bool;
|
||||
formState.value.is_bool = pointData.is_bool;
|
||||
if (pointData.factor && Array.isArray(pointData.factor)) {
|
||||
factorData.value = pointData.factor.map((d) => ({
|
||||
...d,
|
||||
key: d.id,
|
||||
}));
|
||||
} else {
|
||||
factorData.value = [];
|
||||
formState.value.factor = 0;
|
||||
}
|
||||
}
|
||||
const onFactorsChange = (selectedFactor) => {
|
||||
factorNum.value = selectedFactor;
|
||||
};
|
||||
|
||||
const onNoticesChange = (value, checked) => {
|
||||
@ -121,8 +108,7 @@ const closeModal = () => {
|
||||
formState.value = {};
|
||||
handleErrorReset();
|
||||
props.onCancel();
|
||||
factorData.value = [];
|
||||
isBool.value = 1;
|
||||
factorNum.value = 1;
|
||||
};
|
||||
</script>
|
||||
|
||||
@ -133,9 +119,8 @@ const closeModal = () => {
|
||||
<Modal
|
||||
id="outliers_add_table_item"
|
||||
:title="t('alert.alarm_settings')"
|
||||
:open="open"
|
||||
:onCancel="closeModal"
|
||||
width="710"
|
||||
:width="710"
|
||||
>
|
||||
<template #modalContent>
|
||||
<form ref="form" class="mt-5 w-full flex flex-wrap justify-between">
|
||||
@ -161,7 +146,6 @@ const closeModal = () => {
|
||||
name="points"
|
||||
Attribute="full_name"
|
||||
:options="OptionsData.alarmPoints"
|
||||
:onChange="onPointsChange"
|
||||
>
|
||||
<template #topLeft>{{ $t("alert.item") }}</template>
|
||||
<template #bottomLeft
|
||||
@ -170,6 +154,17 @@ const closeModal = () => {
|
||||
</span></template
|
||||
>
|
||||
</Select>
|
||||
<Select
|
||||
:value="formState"
|
||||
class="my-2"
|
||||
selectClass="border-info focus-within:border-info"
|
||||
name="factor"
|
||||
Attribute="full_name"
|
||||
:options="OptionsData.alarmFactors"
|
||||
:onChange="onFactorsChange"
|
||||
>
|
||||
<template #topLeft>{{ $t("alert.qualifications") }}</template>
|
||||
</Select>
|
||||
<RadioGroup
|
||||
class="my-2"
|
||||
name="enable"
|
||||
@ -190,62 +185,60 @@ const closeModal = () => {
|
||||
>
|
||||
<template #topLeft>{{ $t("alert.status") }}</template>
|
||||
</RadioGroup>
|
||||
<Select :value="formState" class="my-2" selectClass="border-info focus-within:border-info" name="schedule_id"
|
||||
Attribute="schedule_name" :options="timesList">
|
||||
<template #topLeft>{{$t("alert.warning_time")}}</template>
|
||||
<template #topRight><button v-if="formState.schedule_id" class="text-base btn-text-without-border"
|
||||
@click="() => {formState.schedule_id = null}"><font-awesome-icon
|
||||
:icon="['fas', 'times']"
|
||||
class="text-[#a5abb1] me-1"
|
||||
/>{{$t("alert.clear")}}</button></template>
|
||||
</Select>
|
||||
<Select
|
||||
<template v-if="factorNum == 1">
|
||||
<InputNumber :value="formState" class="my-2" name="delay">
|
||||
<template #topLeft>{{ $t("alert.delay") }}</template>
|
||||
</InputNumber>
|
||||
</template>
|
||||
<template v-if="factorNum == 2">
|
||||
<div class="flex gap-4 w-full">
|
||||
<InputNumber :value="formState" class="my-2" name="highLimit">
|
||||
<template #topLeft>{{ $t("alert.upper_limit") }}(>=)</template>
|
||||
</InputNumber>
|
||||
<InputNumber :value="formState" class="my-2" name="lowLimit">
|
||||
<template #topLeft>{{ $t("alert.lower_limit") }}(<=)</template>
|
||||
</InputNumber>
|
||||
</div>
|
||||
<div class="flex gap-4 w-full">
|
||||
<InputNumber :value="formState" class="my-2" name="highDelay">
|
||||
<template #topLeft>{{ $t("alert.highDelay") }}</template>
|
||||
</InputNumber>
|
||||
<InputNumber :value="formState" class="my-2" name="lowDelay">
|
||||
<template #topLeft>{{ $t("alert.lowDelay") }}</template>
|
||||
</InputNumber>
|
||||
</div>
|
||||
</template>
|
||||
<template v-if="factorNum == 3">
|
||||
<Input :value="formState" class="my-2" name="alarm_value">
|
||||
<template #topLeft>{{ $t("alert.warning_value") }}</template>
|
||||
</Input>
|
||||
</template>
|
||||
<!-- <Select
|
||||
:value="formState"
|
||||
class="my-2"
|
||||
selectClass="border-info focus-within:border-info"
|
||||
name="factor"
|
||||
Attribute="full_name"
|
||||
:options="factorData"
|
||||
v-if="factorData.length !== 0"
|
||||
name="schedule_id"
|
||||
Attribute="schedule_name"
|
||||
:options="timesList"
|
||||
>
|
||||
<template #topLeft>{{ $t("alert.qualifications") }}</template>
|
||||
</Select>
|
||||
<div class="flex gap-4 w-full">
|
||||
<InputNumber
|
||||
:value="formState"
|
||||
class="my-2"
|
||||
name="highLimit"
|
||||
v-if="!isBool"
|
||||
>
|
||||
<template #topLeft>{{ $t("alert.upper_limit") }}(>=)</template>
|
||||
</InputNumber>
|
||||
<InputNumber
|
||||
:value="formState"
|
||||
class="my-2"
|
||||
name="lowLimit"
|
||||
v-if="!isBool"
|
||||
>
|
||||
<template #topLeft>{{ $t("alert.lower_limit") }}(<=)</template>
|
||||
</InputNumber>
|
||||
</div>
|
||||
<div class="flex gap-4 w-full">
|
||||
<InputNumber
|
||||
:value="formState"
|
||||
class="my-2"
|
||||
name="highDelay"
|
||||
v-if="!isBool"
|
||||
>
|
||||
<template #topLeft>{{ $t("alert.highDelay") }}</template>
|
||||
</InputNumber>
|
||||
<InputNumber
|
||||
:value="formState"
|
||||
class="my-2"
|
||||
name="lowDelay"
|
||||
v-if="!isBool"
|
||||
>
|
||||
<template #topLeft>{{ $t("alert.lowDelay") }}</template>
|
||||
</InputNumber>
|
||||
</div>
|
||||
<template #topLeft>{{ $t("alert.warning_time") }}</template>
|
||||
<template #topRight
|
||||
><button
|
||||
v-if="formState.schedule_id"
|
||||
class="text-base btn-text-without-border"
|
||||
@click="
|
||||
() => {
|
||||
formState.schedule_id = null;
|
||||
}
|
||||
"
|
||||
>
|
||||
<font-awesome-icon
|
||||
:icon="['fas', 'times']"
|
||||
class="text-[#a5abb1] me-1"
|
||||
/>{{ $t("alert.clear") }}
|
||||
</button>
|
||||
</template>
|
||||
</Select> -->
|
||||
<div class="w-full mt-5">
|
||||
<p class="text-light text-lg ml-1">
|
||||
{{ $t("alert.warning_method") }}
|
||||
|
@ -10,17 +10,17 @@ const { locale } = useI18n();
|
||||
const timesList = ref([]);
|
||||
const noticeList = ref([]);
|
||||
|
||||
const timesListData = async () => {
|
||||
const res = await getAlarmScheduleList();
|
||||
timesList.value = res.data.map((items) => ({
|
||||
...items,
|
||||
key:items.id,
|
||||
schedule_array: JSON.parse(items.schedule_json).map((time, index) => ({
|
||||
day: index + 1,
|
||||
time,
|
||||
})),
|
||||
}));
|
||||
};
|
||||
// const timesListData = async () => {
|
||||
// const res = await getAlarmScheduleList();
|
||||
// timesList.value = res.data.map((items) => ({
|
||||
// ...items,
|
||||
// key:items.id,
|
||||
// schedule_array: JSON.parse(items.schedule_json).map((time, index) => ({
|
||||
// day: index + 1,
|
||||
// time,
|
||||
// })),
|
||||
// }));
|
||||
// };
|
||||
|
||||
const NoticeListData = async () => {
|
||||
const res = await getNoticeList(locale.value);
|
||||
@ -32,18 +32,22 @@ watch(locale, () => {
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
timesListData();
|
||||
// timesListData();
|
||||
NoticeListData();
|
||||
});
|
||||
|
||||
provide("notify_table", { timesList, noticeList, timesListData });
|
||||
provide("notify_table", {
|
||||
timesList,
|
||||
noticeList,
|
||||
// timesListData
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<AlertSubList />
|
||||
<AlertOutliersTable />
|
||||
<AlertTimeTable />
|
||||
<!-- <AlertTimeTable /> -->
|
||||
<AlertNotifyTable />
|
||||
</div>
|
||||
</template>
|
||||
|
@ -3,12 +3,13 @@ import { ref, onMounted, watch } from "vue";
|
||||
import useSearchParam from "@/hooks/useSearchParam";
|
||||
import useActiveBtn from "@/hooks/useActiveBtn";
|
||||
import { getAlertSubList } from "@/apis/alert";
|
||||
|
||||
import useBuildingStore from "@/stores/useBuildingStore";
|
||||
const store = useBuildingStore();
|
||||
const { searchParams, changeParams } = useSearchParam();
|
||||
const { items, changeActiveBtn, setItems, selectedBtn } = useActiveBtn();
|
||||
|
||||
const getSubSystems = async () => {
|
||||
const res = await getAlertSubList();
|
||||
const res = await getAlertSubList(store.selectedBuilding.building_guid);
|
||||
const history_Sub_systems = res.data.history_Main_Systems.flatMap(
|
||||
(mainSystem) => {
|
||||
return mainSystem.history_Sub_systems;
|
||||
@ -24,9 +25,15 @@ const getSubSystems = async () => {
|
||||
setItems(subSystems);
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
getSubSystems();
|
||||
});
|
||||
watch(
|
||||
() => store.selectedBuilding,
|
||||
(newBuilding) => {
|
||||
if (newBuilding) {
|
||||
getSubSystems();
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
watch(selectedBtn,
|
||||
(newValue) => {
|
||||
|
@ -1,75 +1,188 @@
|
||||
<script setup>
|
||||
import { onMounted, onUnmounted, ref, provide, watch } from "vue";
|
||||
import { getEnergyCost } from "@/apis/dashboard";
|
||||
import ButtonConnectedGroup from "@/components/customUI/ButtonConnectedGroup.vue";
|
||||
import Forge from "@/components/forge/Forge.vue";
|
||||
import DashboardStat from "./components/DashboardStat.vue";
|
||||
import DashboardElecChart from "./components/DashboardElecChart.vue";
|
||||
import DashboardSysCard from "./components/DashboardSysCard.vue";
|
||||
import DashboardSysProgress from "./components/DashboardSysProgress.vue";
|
||||
import { getDashboardInit } from "@/apis/dashboard";
|
||||
import { onMounted, ref, provide, watch } from "vue";
|
||||
import DashboardElecRank from "./components/DashboardElecRank.vue";
|
||||
import DashboardElecTrends from "./components/DashboardElecTrends.vue";
|
||||
import DashboardElecCompare from "./components/DashboardElecCompare.vue";
|
||||
import useBuildingStore from "@/stores/useBuildingStore";
|
||||
import useActiveBtn from "@/hooks/useActiveBtn";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
const initialData = ref(null);
|
||||
// const forgeData = ref([]);
|
||||
const init = async () => {
|
||||
const res = await getDashboardInit();
|
||||
initialData.value = res.data;
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
init();
|
||||
const store = useBuildingStore();
|
||||
const { items, changeActiveBtn, setItems, selectedBtn } = useActiveBtn();
|
||||
let intervalId = null;
|
||||
const energyCostData = ref({});
|
||||
const FILE_BASEURL = import.meta.env.VITE_FILE_API_BASEURL;
|
||||
const imgBaseUrl = ref("");
|
||||
const formState = ref({
|
||||
building_guid: null,
|
||||
floor_guid: "all",
|
||||
department_id: "all",
|
||||
});
|
||||
|
||||
const intervalOption = ref({});
|
||||
const currentIntervalType = ref("");
|
||||
|
||||
const openModal = (type) => {
|
||||
currentIntervalType.value = type;
|
||||
dashboard_more.showModal();
|
||||
const getEnergyCostData = async (params) => {
|
||||
const res = await getEnergyCost(params);
|
||||
energyCostData.value = res.data;
|
||||
};
|
||||
|
||||
const decideIntervalOption = (option) => {
|
||||
intervalOption.value[currentIntervalType.value] = option;
|
||||
};
|
||||
watch(
|
||||
() => store.selectedBuilding,
|
||||
(newBuilding) => {
|
||||
if (newBuilding) {
|
||||
formState.value.building_guid = newBuilding.building_guid;
|
||||
imgBaseUrl.value = store.previewImageExt
|
||||
? `${FILE_BASEURL}/upload/setting/previewImage/${newBuilding.building_guid}${store.previewImageExt}`
|
||||
: import.meta.env.MODE === "production"
|
||||
? "dist/build_img.jpg"
|
||||
: "/build_img.jpg";
|
||||
}
|
||||
},
|
||||
{ immediate: true, deep: true }
|
||||
);
|
||||
|
||||
provide("dashboard_items", {
|
||||
initialData,
|
||||
// forgeData,
|
||||
openModal,
|
||||
decideIntervalOption,
|
||||
intervalOption,
|
||||
currentIntervalType,
|
||||
watch(
|
||||
() => formState.value,
|
||||
(newVal) => {
|
||||
if (newVal) {
|
||||
const params = { ...newVal };
|
||||
|
||||
if (params.floor_guid === "all") {
|
||||
delete params.floor_guid;
|
||||
}
|
||||
if (params.department_id === "all") {
|
||||
delete params.department_id;
|
||||
}
|
||||
|
||||
if (params.building_guid) {
|
||||
getEnergyCostData(params);
|
||||
}
|
||||
|
||||
if (intervalId) {
|
||||
clearInterval(intervalId);
|
||||
}
|
||||
intervalId = setInterval(() => {
|
||||
getEnergyCostData(params);
|
||||
}, 3600000);
|
||||
}
|
||||
},
|
||||
{ immediate: true, deep: true }
|
||||
);
|
||||
|
||||
watch(
|
||||
() => store.showForgeArea,
|
||||
(newVal) => {
|
||||
if (newVal == true) {
|
||||
setItems([
|
||||
{
|
||||
title: "2D",
|
||||
key: "2D",
|
||||
active: false,
|
||||
},
|
||||
{
|
||||
title: "3D",
|
||||
key: "3D",
|
||||
active: true,
|
||||
},
|
||||
]);
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
onUnmounted(() => {
|
||||
clearInterval(intervalId);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-wrap items-center">
|
||||
<!-- 建築圖 -->
|
||||
<div class="w-full xl:w-1/3">
|
||||
<div class="area-img-box">
|
||||
<Forge />
|
||||
</div>
|
||||
<div class="w-full xl:w-1/3 relative">
|
||||
<template v-if="store.showForgeArea">
|
||||
<ButtonConnectedGroup
|
||||
:items="items"
|
||||
className="btn-xs absolute right-3 top-6 z-20 bg-slate-800 p-0 rounded-lg "
|
||||
:onclick="(e, item) => changeActiveBtn(item)"
|
||||
/>
|
||||
<div class="area-img-box relative">
|
||||
<!-- setting頁面要新增讓他能上傳圖片 -->
|
||||
<img
|
||||
alt="build"
|
||||
:src="imgBaseUrl"
|
||||
:class="
|
||||
twMerge(
|
||||
'absolute w-full h-full transition-opacity duration-300',
|
||||
selectedBtn?.key == '2D'
|
||||
? 'opacity-100 z-10'
|
||||
: 'opacity-0 z-0'
|
||||
)
|
||||
"
|
||||
/>
|
||||
|
||||
<Forge
|
||||
:class="
|
||||
twMerge(
|
||||
'absolute transition-opacity duration-300',
|
||||
selectedBtn?.key == '3D'
|
||||
? 'opacity-100 z-10'
|
||||
: 'opacity-0 z-0'
|
||||
)
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<img
|
||||
alt="build"
|
||||
:src="imgBaseUrl || '/build_img.jpg'"
|
||||
class="area-img-box w-full h-[460px] block relative rounded-sm mt-3"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<div class="w-full xl:w-2/3">
|
||||
<!-- 用電數據 -->
|
||||
<DashboardStat />
|
||||
<!-- 用電圖表 -->
|
||||
<DashboardElecChart />
|
||||
|
||||
<div class="flex flex-wrap pt-4">
|
||||
<!-- 當月能耗排行 -->
|
||||
<div class="lg:w-1/3 w-full">
|
||||
<DashboardElecRank :energyCostData="energyCostData" />
|
||||
</div>
|
||||
<!-- 近30天能耗趨勢 -->
|
||||
<div class="lg:w-2/3 w-full">
|
||||
<DashboardElecTrends
|
||||
:formState="formState"
|
||||
:energyCostData="energyCostData"
|
||||
:getEnergyCostData="getEnergyCostData"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 設備小卡 -->
|
||||
<div class="w-full lg:w-2/3">
|
||||
<div class="w-full lg:w-1/3">
|
||||
<DashboardSysCard />
|
||||
</div>
|
||||
<!--狀態、進度-->
|
||||
<div class="w-full lg:w-1/3">
|
||||
<DashboardSysProgress />
|
||||
</div>
|
||||
<!-- 環比能耗 -->
|
||||
<div class="w-full lg:w-1/3">
|
||||
<DashboardElecCompare :energyCostData="energyCostData" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="css" scoped>
|
||||
.area-img-box {
|
||||
@apply border border-light-info bg-dark-info w-full h-[400px] block relative rounded-sm mb-4;
|
||||
@apply border border-light-info bg-dark-info w-full h-[460px] block relative rounded-sm mt-3;
|
||||
}
|
||||
|
||||
.area-img-box::before {
|
||||
|
305
src/views/dashboard/components/DashboardElecCompare.vue
Normal file
305
src/views/dashboard/components/DashboardElecCompare.vue
Normal file
@ -0,0 +1,305 @@
|
||||
<script setup>
|
||||
import { ref, onMounted, watch, computed } from "vue";
|
||||
import * as echarts from "echarts";
|
||||
import BarChart from "@/components/chart/BarChart.vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
|
||||
const { locale, t } = useI18n();
|
||||
const props = defineProps({
|
||||
energyCostData: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
const chartData = ref([]); // 初始化為空陣列
|
||||
|
||||
const labels = computed(() => [
|
||||
t("dashboard.today"),
|
||||
t("dashboard.yesterday"),
|
||||
t("dashboard.this_week"),
|
||||
t("dashboard.last_week"),
|
||||
t("dashboard.this_month"),
|
||||
t("dashboard.last_month"),
|
||||
t("dashboard.this_year"),
|
||||
t("dashboard.last_year"),
|
||||
]);
|
||||
const barWidth = 30; // Set barWidth
|
||||
|
||||
const barChartOptions = ref({
|
||||
xAxis: {
|
||||
type: "category",
|
||||
data: chartData.value.map((item) => item.category),
|
||||
axisLine: { lineStyle: { color: "#fff" } },
|
||||
},
|
||||
yAxis: { type: "value", show: false },
|
||||
series: [], // 初始化為空陣列
|
||||
tooltip: {
|
||||
trigger: "axis",
|
||||
axisPointer: { type: "shadow" },
|
||||
formatter: function (params) {
|
||||
let tooltipText = `<div>${params[0].axisValueLabel}</div>`;
|
||||
const filteredParams = params.filter((item) => item.seriesType === "bar");
|
||||
filteredParams.forEach((item) => {
|
||||
tooltipText += `<div>${item.marker} ${
|
||||
item.value ? item.value : "-"
|
||||
}</div>`;
|
||||
});
|
||||
|
||||
return tooltipText;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
function updateChartData(newEnergyCostData) {
|
||||
if (newEnergyCostData && newEnergyCostData.compare) {
|
||||
// 從 props.energyCostData.compare 中提取資料
|
||||
const compareData = newEnergyCostData.compare;
|
||||
|
||||
// 轉換資料格式
|
||||
chartData.value = [
|
||||
{
|
||||
category: t("dashboard.daily_relative_change"),
|
||||
this: compareData.day.current,
|
||||
last: compareData.day.last,
|
||||
change: compareData.day.percentage,
|
||||
},
|
||||
{
|
||||
category: t("dashboard.weekly_relative_change"),
|
||||
this: compareData.week.current,
|
||||
last: compareData.week.last,
|
||||
change: compareData.week.percentage,
|
||||
},
|
||||
{
|
||||
category: t("dashboard.monthly_relative_change"),
|
||||
this: compareData.month.current,
|
||||
last: compareData.month.last,
|
||||
change: compareData.month.percentage,
|
||||
},
|
||||
{
|
||||
category: t("dashboard.yearly_relative_change"),
|
||||
this: compareData.year.current,
|
||||
last: compareData.year.last,
|
||||
change: compareData.year.percentage,
|
||||
},
|
||||
];
|
||||
|
||||
// 更新 barChartOptions
|
||||
barChartOptions.value = {
|
||||
xAxis: {
|
||||
type: "category",
|
||||
data: chartData.value.map((item) => item.category),
|
||||
axisLine: { lineStyle: { color: "#fff" } },
|
||||
},
|
||||
yAxis: { type: "value", show: false },
|
||||
grid: {
|
||||
left: "-10%",
|
||||
right: "1%",
|
||||
bottom: "3%",
|
||||
top: "4%",
|
||||
containLabel: true,
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: "當前週期",
|
||||
data: chartData.value.map((item) => item.this),
|
||||
type: "bar",
|
||||
barWidth: barWidth,
|
||||
barGap: "-10%",
|
||||
itemStyle: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{ offset: 0, color: "#186B80" },
|
||||
{ offset: 1, color: "#50C3E3" },
|
||||
]),
|
||||
shadowBlur: 5,
|
||||
shadowColor: "rgba(0, 0, 0, 0.3)",
|
||||
shadowOffsetY: 2,
|
||||
shadowOffsetX: 5,
|
||||
},
|
||||
z: 3,
|
||||
},
|
||||
{
|
||||
name: "對比週期",
|
||||
data: chartData.value.map((item) => item.last),
|
||||
type: "bar",
|
||||
barWidth: barWidth,
|
||||
itemStyle: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{ offset: 0, color: "#988F2C" },
|
||||
{ offset: 1, color: "#FFF26D" },
|
||||
]),
|
||||
shadowBlur: 5,
|
||||
shadowColor: "rgba(0, 0, 0, 0.3)",
|
||||
shadowOffsetY: 2,
|
||||
shadowOffsetX: 5,
|
||||
},
|
||||
},
|
||||
{
|
||||
// this top
|
||||
z: 6,
|
||||
type: "pictorialBar",
|
||||
symbolPosition: "end",
|
||||
data: chartData.value.map((item) => item.this),
|
||||
symbol: "diamond",
|
||||
symbolOffset: ["-45%", "-50%"],
|
||||
symbolSize: [barWidth, barWidth * 0.5],
|
||||
itemStyle: {
|
||||
borderWidth: 0,
|
||||
color: "#50C3E3",
|
||||
},
|
||||
},
|
||||
{
|
||||
// this bot
|
||||
z: 6,
|
||||
type: "pictorialBar",
|
||||
symbolPosition: "start",
|
||||
data: chartData.value.map((item) => item.this),
|
||||
symbol: "diamond",
|
||||
symbolOffset: ["-45%", "50%"],
|
||||
symbolSize: [barWidth, barWidth * 0.5],
|
||||
itemStyle: {
|
||||
borderWidth: 0,
|
||||
color: "#50C3E3",
|
||||
},
|
||||
},
|
||||
{
|
||||
// last top
|
||||
z: 3,
|
||||
type: "pictorialBar",
|
||||
symbolPosition: "end",
|
||||
data: chartData.value.map((item) => item.last),
|
||||
symbol: "diamond",
|
||||
symbolOffset: ["45%", "-50%"],
|
||||
symbolSize: [barWidth, barWidth * 0.5],
|
||||
itemStyle: {
|
||||
borderWidth: 0,
|
||||
color: "#FFF26D",
|
||||
},
|
||||
},
|
||||
{
|
||||
// last bot
|
||||
z: 3,
|
||||
type: "pictorialBar",
|
||||
symbolPosition: "start",
|
||||
data: chartData.value.map((item) => item.last),
|
||||
symbol: "diamond",
|
||||
symbolOffset: ["45%", "50%"],
|
||||
symbolSize: [barWidth, barWidth * 0.5],
|
||||
itemStyle: {
|
||||
borderWidth: 0,
|
||||
color: "#FFF26D",
|
||||
},
|
||||
},
|
||||
],
|
||||
tooltip: {
|
||||
trigger: "axis",
|
||||
axisPointer: { type: "shadow" },
|
||||
formatter: function (params) {
|
||||
let tooltipText = `<div>${params[0].axisValueLabel}</div>`;
|
||||
const filteredParams = params.filter(
|
||||
(item) => item.seriesType === "bar"
|
||||
);
|
||||
filteredParams.forEach((item) => {
|
||||
tooltipText += `<div>${item.marker} ${
|
||||
item.value ? item.value : "-"
|
||||
}</div>`;
|
||||
});
|
||||
|
||||
return tooltipText;
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
// 使用 watch 監聽 energyCostData 的變化
|
||||
watch(
|
||||
() => props.energyCostData,
|
||||
(newEnergyCostData) => {
|
||||
updateChartData(newEnergyCostData);
|
||||
},
|
||||
{ immediate: true } // 立即執行一次,確保初始資料載入
|
||||
);
|
||||
|
||||
watch(locale, () => {
|
||||
updateChartData(props.energyCostData);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-wrap">
|
||||
<div class="w-full chart-data relative px-8 py-3">
|
||||
<div class="flex flex-wrap items-center justify-between">
|
||||
<h2 class="font-light">
|
||||
{{ $t("dashboard.relative_energy_consumption") }}
|
||||
</h2>
|
||||
</div>
|
||||
<div class="h-[180px]">
|
||||
<BarChart
|
||||
id="dashboard_chart_compare"
|
||||
class="h-full"
|
||||
:option="barChartOptions"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 表格數據展示 -->
|
||||
<div class="flex justify-between">
|
||||
<div
|
||||
v-for="(data, index) in chartData"
|
||||
:key="index"
|
||||
class="w-1/4 text-center mx-1"
|
||||
>
|
||||
<div
|
||||
class="text-sm bg-cyan-900 p-1 border border-cyan-100 border-opacity-20"
|
||||
>
|
||||
{{ labels[index * 2] }}
|
||||
</div>
|
||||
<div
|
||||
class="text-sm bg-cyan-900 p-1 border border-cyan-100 border-opacity-20"
|
||||
>
|
||||
{{ data.this ?? "-" }}
|
||||
</div>
|
||||
<div
|
||||
class="text-sm bg-cyan-900 p-1 border border-cyan-100 border-opacity-20"
|
||||
>
|
||||
{{ labels[index * 2 + 1] }}
|
||||
</div>
|
||||
<div
|
||||
class="text-sm bg-cyan-900 p-1 border border-cyan-100 border-opacity-20"
|
||||
>
|
||||
{{ data.last ?? "-" }}
|
||||
</div>
|
||||
<div
|
||||
class="text-sm bg-cyan-900 p-1 border border-cyan-100 border-opacity-20"
|
||||
>
|
||||
<span
|
||||
:class="{
|
||||
'text-red-500': data.change > 0,
|
||||
'text-green-500': data.change < 0,
|
||||
}"
|
||||
>
|
||||
{{
|
||||
data.change
|
||||
? (data.change > 0 ? "+" : "") + data.change + "%"
|
||||
: "-"
|
||||
}}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.chart-data:before {
|
||||
@apply absolute right-0 left-2 top-0 h-10 w-10 bg-no-repeat z-10;
|
||||
content: "";
|
||||
background: url(@ASSET/img/chart-data-background01.svg) center center;
|
||||
}
|
||||
|
||||
.chart-data::after {
|
||||
@apply absolute right-0 bottom-0 h-10 w-10 bg-no-repeat z-10;
|
||||
content: "";
|
||||
background: url(@ASSET/img/chart-data-background02.svg) center center;
|
||||
}
|
||||
</style>
|
109
src/views/dashboard/components/DashboardElecRank.vue
Normal file
109
src/views/dashboard/components/DashboardElecRank.vue
Normal file
@ -0,0 +1,109 @@
|
||||
<script setup>
|
||||
import { ref, watch } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
const { t } = useI18n();
|
||||
|
||||
const props = defineProps({
|
||||
energyCostData: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
const energyTypeList = ref([
|
||||
{
|
||||
title: t("dashboard.today_energy_consumption"),
|
||||
key: "today",
|
||||
},
|
||||
{
|
||||
title: t("dashboard.this_month_energy_consumption"),
|
||||
key: "month",
|
||||
},
|
||||
]);
|
||||
const currentEnergyType = ref({
|
||||
name: "month",
|
||||
});
|
||||
|
||||
// 取得當前能耗資料
|
||||
const getCurrentEnergyData = () => {
|
||||
if (!props.energyCostData || !props.energyCostData.rank) {
|
||||
return []; // 或者返回一些默认值
|
||||
}
|
||||
|
||||
return currentEnergyType.value.name === "month"
|
||||
? props.energyCostData?.rank.month || []
|
||||
: props.energyCostData?.rank.day || [];
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="state-box-col relative ps-2 h-full max-h-[350px]">
|
||||
<div class="state-box h-full">
|
||||
<!-- 標題和切換按鈕 -->
|
||||
<div class="flex justify-between items-center mb-2">
|
||||
<h2 class="font-light relative">
|
||||
{{ $t("dashboard.energy_ranking") }}
|
||||
</h2>
|
||||
<Select
|
||||
:value="currentEnergyType"
|
||||
class="!w-24"
|
||||
selectClass="border-info focus-within:border-info btn-xs text-xs"
|
||||
name="name"
|
||||
Attribute="title"
|
||||
:options="energyTypeList"
|
||||
:isTopLabelExist="false"
|
||||
:isBottomLabelExist="false"
|
||||
>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<!-- 能耗排名列表 -->
|
||||
<div class="max-h-[300px] overflow-y-auto">
|
||||
<table class="table table-sm text-center">
|
||||
<tbody>
|
||||
<tr
|
||||
v-for="(item, index) in getCurrentEnergyData()"
|
||||
:key="index"
|
||||
:class="[
|
||||
{ 'text-red-300': index + 1 === 1 },
|
||||
{ 'text-orange-300': index + 1 === 2 },
|
||||
{ 'text-yellow-300': index + 1 === 3 },
|
||||
{ 'text-teal-300': index + 1 > 3 },
|
||||
]"
|
||||
>
|
||||
<td class="flex items-center">
|
||||
<font-awesome-icon :icon="['fas', 'crown']" class="me-1" />{{
|
||||
index + 1
|
||||
}}
|
||||
</td>
|
||||
<td>{{ item.name }}</td>
|
||||
<td>{{ item.value }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.state-box {
|
||||
@apply border-2 border-light-info rounded-sm p-2 text-white relative;
|
||||
}
|
||||
|
||||
.state-box-col:before {
|
||||
@apply absolute left-0 right-0 -top-0.5 m-auto h-2 w-36 bg-no-repeat bg-center z-10;
|
||||
content: "";
|
||||
background-image: url(@ASSET/img/state-box-top.png);
|
||||
}
|
||||
|
||||
.state-box-col:after {
|
||||
@apply absolute left-0 right-0 -bottom-0.5 m-auto h-2 w-36 bg-no-repeat bg-center z-10;
|
||||
content: "";
|
||||
background-image: url(@ASSET/img/state-box-bottom.png);
|
||||
}
|
||||
|
||||
tr td {
|
||||
@apply text-[13px] text-start;
|
||||
}
|
||||
</style>
|
250
src/views/dashboard/components/DashboardElecTrends.vue
Normal file
250
src/views/dashboard/components/DashboardElecTrends.vue
Normal file
@ -0,0 +1,250 @@
|
||||
<script setup>
|
||||
import { ref, onMounted, watch } from "vue";
|
||||
import * as echarts from "echarts";
|
||||
import BarChart from "@/components/chart/BarChart.vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import dayjs from "dayjs";
|
||||
import useBuildingStore from "@/stores/useBuildingStore";
|
||||
|
||||
const storeBuild = useBuildingStore();
|
||||
const { t } = useI18n();
|
||||
|
||||
const props = defineProps({
|
||||
formState: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
energyCostData: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
getEnergyCostData: {
|
||||
type: Function,
|
||||
required: true,
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
const chartData = ref([]);
|
||||
const floorList = ref([]);
|
||||
const deptList = ref([]);
|
||||
const weekComparisonOption = ref({});
|
||||
|
||||
// 生成柱狀圖的 option
|
||||
const generateCylinderChartOption = (data) => {
|
||||
const barWidth = 15;
|
||||
return {
|
||||
xAxis: {
|
||||
type: "category",
|
||||
data: data.map((item) => item.date),
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: "#fff",
|
||||
},
|
||||
},
|
||||
},
|
||||
yAxis: {
|
||||
type: "value",
|
||||
name: "kWh",
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: "#fff",
|
||||
},
|
||||
},
|
||||
splitLine: {
|
||||
show: false,
|
||||
},
|
||||
},
|
||||
series: [
|
||||
{
|
||||
data: data.map((item) => item.energy),
|
||||
type: "bar",
|
||||
barWidth: barWidth,
|
||||
itemStyle: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 1, 1, [
|
||||
{ offset: 0, color: "#1F7B47" },
|
||||
{ offset: 1, color: "#247E95" },
|
||||
]),
|
||||
shadowBlur: 5,
|
||||
shadowColor: "rgba(0, 0, 0, 0.5)",
|
||||
shadowOffsetY: 5,
|
||||
},
|
||||
},
|
||||
{
|
||||
z: 15,
|
||||
type: "pictorialBar",
|
||||
symbolPosition: "end",
|
||||
data: data.map((item) => item.energy),
|
||||
symbol: "diamond",
|
||||
symbolOffset: [0, -5],
|
||||
symbolSize: [barWidth, barWidth * 0.5],
|
||||
itemStyle: {
|
||||
color: "#62E39A",
|
||||
},
|
||||
},
|
||||
{
|
||||
z: 10,
|
||||
type: "pictorialBar",
|
||||
data: data.map((item) => item.energy),
|
||||
symbol: "diamond",
|
||||
symbolSize: [barWidth, barWidth * 0.5],
|
||||
symbolOffset: [0, 6],
|
||||
itemStyle: {
|
||||
color: "#247E95",
|
||||
},
|
||||
},
|
||||
],
|
||||
grid: {
|
||||
left: "0%",
|
||||
right: "1%",
|
||||
bottom: "3%",
|
||||
top: "10%",
|
||||
containLabel: true,
|
||||
},
|
||||
tooltip: {
|
||||
trigger: "axis",
|
||||
formatter: function (params) {
|
||||
const item = params[0];
|
||||
return `<p>${item.name}</p> <p>${item.marker}Energy consumption : ${item.value}</p>`;
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const processEnergyData = () => {
|
||||
if (!props.energyCostData || !props.energyCostData.trend) {
|
||||
chartData.value = [];
|
||||
weekComparisonOption.value = generateCylinderChartOption(chartData.value);
|
||||
return;
|
||||
}
|
||||
|
||||
const dailyData = [...props.energyCostData.trend].sort(
|
||||
(a, b) => new Date(a.time) - new Date(b.time)
|
||||
);
|
||||
|
||||
chartData.value = dailyData.map((item) => ({
|
||||
date: dayjs(item.time).format("MM/DD"),
|
||||
energy: item.value,
|
||||
}));
|
||||
|
||||
weekComparisonOption.value = generateCylinderChartOption(chartData.value);
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.energyCostData,
|
||||
(newEnergyCostData) => {
|
||||
processEnergyData();
|
||||
},
|
||||
{ deep: true, immediate: true }
|
||||
);
|
||||
|
||||
watch(
|
||||
() => storeBuild.floorList,
|
||||
(newValue) => {
|
||||
if (newValue) {
|
||||
console.log('newValue',newValue);
|
||||
|
||||
floorList.value = [
|
||||
{
|
||||
title: "All",
|
||||
key: "all",
|
||||
},
|
||||
...storeBuild.floorList,
|
||||
];
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
() => storeBuild.floorList,
|
||||
(newValue) => {
|
||||
if (newValue) {
|
||||
floorList.value = [
|
||||
{
|
||||
title: "All",
|
||||
key: "all",
|
||||
},
|
||||
...storeBuild.floorList,
|
||||
];
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
() => storeBuild.deptList,
|
||||
(newValue) => {
|
||||
if (newValue) {
|
||||
deptList.value = [
|
||||
{
|
||||
title: "All",
|
||||
key: "all",
|
||||
},
|
||||
...storeBuild.deptList,
|
||||
];
|
||||
}
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
immediate: true,
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="w-full chart-data relative px-8 py-1">
|
||||
<div class="flex flex-wrap items-center justify-between">
|
||||
<h2 class="font-light">{{ $t("dashboard.last_30_days_energy_trend") }}</h2>
|
||||
<div class="flex items-center w-52 gap-4">
|
||||
<Select
|
||||
:value="props.formState"
|
||||
class="my-2"
|
||||
selectClass="border-info focus-within:border-info btn-xs text-xs"
|
||||
name="floor_guid"
|
||||
Attribute="title"
|
||||
:options="floorList"
|
||||
:isTopLabelExist="false"
|
||||
:isBottomLabelExist="false"
|
||||
>
|
||||
</Select>
|
||||
<Select
|
||||
:value="props.formState"
|
||||
class="my-2"
|
||||
selectClass="border-info focus-within:border-info btn-xs text-xs"
|
||||
name="department_id"
|
||||
Attribute="title"
|
||||
:options="deptList"
|
||||
:isTopLabelExist="false"
|
||||
:isBottomLabelExist="false"
|
||||
>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="h-[300px]">
|
||||
<BarChart
|
||||
id="dashboard_chart_week_comparison"
|
||||
class="h-full"
|
||||
:option="weekComparisonOption"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.chart-data:before {
|
||||
@apply absolute right-0 left-2 top-0 h-10 w-10 bg-no-repeat z-10;
|
||||
content: "";
|
||||
background: url(@ASSET/img/chart-data-background01.svg) center center;
|
||||
}
|
||||
|
||||
.chart-data::after {
|
||||
@apply absolute right-0 bottom-0 h-10 w-10 bg-no-repeat z-10;
|
||||
content: "";
|
||||
background: url(@ASSET/img/chart-data-background02.svg) center center;
|
||||
}
|
||||
</style>
|
@ -1,31 +1,100 @@
|
||||
<script setup>
|
||||
import dayjs from "dayjs";
|
||||
import { computed } from "vue";
|
||||
import { ref, watch, onMounted, onUnmounted } from "vue";
|
||||
import { getEnergyInfo } from "@/apis/dashboard";
|
||||
import useBuildingStore from "@/stores/useBuildingStore";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
const mockData = [
|
||||
{
|
||||
value: "305.50",
|
||||
label: "Today's electricity consumption in kWH",
|
||||
import { useI18n } from "vue-i18n";
|
||||
|
||||
const { locale, t } = useI18n();
|
||||
const store = useBuildingStore();
|
||||
const energyData = ref([]);
|
||||
let intervalId = null;
|
||||
|
||||
const getEnergyInfos = async () => {
|
||||
try {
|
||||
const res = await getEnergyInfo(store.selectedBuilding.building_guid);
|
||||
const apiData = res.data;
|
||||
|
||||
energyData.value = [
|
||||
{
|
||||
value: apiData.todayKWH ? apiData.todayKWH : "N/A",
|
||||
label: t("dashboard.today_electricity_consumption"),
|
||||
},
|
||||
{
|
||||
value: apiData.yesterdayKWH ? apiData.yesterdayKWH : "N/A",
|
||||
label: t("dashboard.yesterday_electricity_consumption"),
|
||||
},
|
||||
{
|
||||
value: apiData.instantKW ? apiData.instantKW : "N/A",
|
||||
label: t("dashboard.instant_power"),
|
||||
},
|
||||
{
|
||||
value: apiData.instantContractRatio
|
||||
? apiData.instantContractRatio
|
||||
: "N/A",
|
||||
label: t("dashboard.instant_contract_capacity_ratio"),
|
||||
},
|
||||
];
|
||||
} catch (error) {
|
||||
console.error("Error fetching energy info:", error);
|
||||
energyData.value = [
|
||||
{ value: "N/A", label: "Today's electricity consumption in kWH" },
|
||||
{ value: "N/A", label: "Yesterday's electricity consumption in kWH" },
|
||||
{ value: "N/A", label: "Instant power kW" },
|
||||
{ value: "N/A", label: "Instant contract capacity ratio %" },
|
||||
];
|
||||
}
|
||||
};
|
||||
|
||||
watch(
|
||||
() => store.selectedBuilding,
|
||||
(newBuilding) => {
|
||||
if (newBuilding) {
|
||||
getEnergyInfos();
|
||||
|
||||
if (intervalId) {
|
||||
clearInterval(intervalId);
|
||||
}
|
||||
intervalId = setInterval(() => {
|
||||
getEnergyInfos();
|
||||
}, 30000);
|
||||
}
|
||||
},
|
||||
{
|
||||
value: "886.75",
|
||||
label: "Yesterday's electricity consumption in kWH",
|
||||
},
|
||||
{
|
||||
value: "7.84",
|
||||
label: "Instant power kW",
|
||||
},
|
||||
{
|
||||
value: "1.96",
|
||||
label: "Instant contract capacity ratio %",
|
||||
},
|
||||
];
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
watch(locale, () => {
|
||||
if (energyData.value.length) {
|
||||
energyData.value = [
|
||||
{
|
||||
...energyData.value[0],
|
||||
label: t("dashboard.today_electricity_consumption"),
|
||||
},
|
||||
{
|
||||
...energyData.value[1],
|
||||
label: t("dashboard.yesterday_electricity_consumption"),
|
||||
},
|
||||
{
|
||||
...energyData.value[2],
|
||||
label: t("dashboard.instant_power"),
|
||||
},
|
||||
{
|
||||
...energyData.value[3],
|
||||
label: t("dashboard.instant_contract_capacity_ratio"),
|
||||
},
|
||||
];
|
||||
}
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
clearInterval(intervalId);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-wrap">
|
||||
<div
|
||||
v-for="(item, index) in mockData"
|
||||
v-for="(item, index) in energyData"
|
||||
:key="index"
|
||||
class="xl:w-1/4 md:w-1/2 w-full item-data-box relative px-3"
|
||||
>
|
||||
@ -41,7 +110,7 @@ const mockData = [
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.item-data {
|
||||
@apply relative mb-4 min-h-[135px] h-full flex flex-col justify-center;
|
||||
@apply relative mb-4 min-h-[100px] h-full flex flex-col justify-center;
|
||||
}
|
||||
|
||||
.item-data-box:after {
|
||||
@ -92,7 +161,7 @@ const mockData = [
|
||||
}
|
||||
|
||||
.item-data h2:after {
|
||||
@apply absolute left-[5%] right-[5%] bottom-0 w-[90%] h-[10px] box-border border-b-2 border-[#58bfe8] ;
|
||||
@apply absolute left-[5%] right-[5%] bottom-0 w-[90%] h-[10px] box-border border-b-2 border-[#58bfe8];
|
||||
content: "";
|
||||
}
|
||||
|
||||
|
@ -2,123 +2,10 @@
|
||||
import { ref } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import useBuildingStore from "@/stores/useBuildingStore";
|
||||
const store = useBuildingStore();
|
||||
const router = useRouter();
|
||||
// 假資料
|
||||
const mockData = ref([
|
||||
{
|
||||
title: "Air Detection System",
|
||||
icon: "temperature-high",
|
||||
isError: false,
|
||||
main_system_tag: "Dust",
|
||||
sub_system_tag: "EM",
|
||||
},
|
||||
{
|
||||
title: "Lighting System",
|
||||
icon: "lightbulb",
|
||||
isError: false,
|
||||
main_system_tag: "LS",
|
||||
sub_system_tag: "ECLS",
|
||||
},
|
||||
{
|
||||
title: "Air Condition System",
|
||||
icon: "fan",
|
||||
isError: false,
|
||||
main_system_tag: "ME",
|
||||
sub_system_tag: "TH",
|
||||
},
|
||||
{
|
||||
title: "Electricity System",
|
||||
icon: "bolt",
|
||||
isError: false,
|
||||
main_system_tag: "EE",
|
||||
sub_system_tag: "ECP3",
|
||||
},
|
||||
{
|
||||
title: "Elevator System",
|
||||
icon: "building",
|
||||
isError: false,
|
||||
main_system_tag: null,
|
||||
sub_system_tag: null,
|
||||
},
|
||||
{
|
||||
title: "High Voltage Switchboard",
|
||||
icon: "charging-station",
|
||||
isError: false,
|
||||
main_system_tag: null,
|
||||
sub_system_tag: null,
|
||||
},
|
||||
{
|
||||
title: "Low Voltage Switchboard",
|
||||
icon: "charging-station",
|
||||
isError: false,
|
||||
main_system_tag: null,
|
||||
sub_system_tag: null,
|
||||
},
|
||||
{
|
||||
title: "Water Supply System",
|
||||
icon: "tint",
|
||||
isError: false,
|
||||
main_system_tag: null,
|
||||
sub_system_tag: null,
|
||||
},
|
||||
{
|
||||
title: "Sewage And Wastewater Equipment",
|
||||
icon: "water",
|
||||
isError: false,
|
||||
main_system_tag: null,
|
||||
sub_system_tag: null,
|
||||
},
|
||||
{
|
||||
title: "Emergency Generator",
|
||||
icon: "car-battery",
|
||||
isError: false,
|
||||
main_system_tag: null,
|
||||
sub_system_tag: null,
|
||||
},
|
||||
{
|
||||
title: "Fire Equipment",
|
||||
icon: "fire-extinguisher",
|
||||
isError: false,
|
||||
main_system_tag: null,
|
||||
sub_system_tag: null,
|
||||
},
|
||||
{
|
||||
title: "CCTV System",
|
||||
icon: "video",
|
||||
isError: false,
|
||||
main_system_tag: null,
|
||||
sub_system_tag: null,
|
||||
},
|
||||
{
|
||||
title: "Access Control System",
|
||||
icon: "door-open",
|
||||
isError: false,
|
||||
main_system_tag: null,
|
||||
sub_system_tag: null,
|
||||
},
|
||||
{
|
||||
title: "Shutdown System",
|
||||
icon: "car",
|
||||
isError: false,
|
||||
main_system_tag: null,
|
||||
sub_system_tag: null,
|
||||
},
|
||||
{
|
||||
title: "Emergency Rescue System",
|
||||
icon: "exclamation-triangle",
|
||||
isError: false,
|
||||
main_system_tag: null,
|
||||
sub_system_tag: null,
|
||||
},
|
||||
{
|
||||
title: "Air Supply Aand Exhaust System",
|
||||
icon: "wind",
|
||||
isError: false,
|
||||
main_system_tag: null,
|
||||
sub_system_tag: null,
|
||||
},
|
||||
]);
|
||||
|
||||
const FILE_BASEURL = import.meta.env.VITE_FILE_API_BASEURL;
|
||||
const navigateToSubSystem = (mainSystemId, subSystemId) => {
|
||||
router.push({
|
||||
name: "sub_system",
|
||||
@ -132,13 +19,13 @@ const navigateToSubSystem = (mainSystemId, subSystemId) => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-wrap -mx-1">
|
||||
<div class="flex flex-wrap -mx-1 h-[21rem] overflow-y-auto items-start content-start">
|
||||
<div
|
||||
v-for="(item, index) in mockData"
|
||||
v-for="(item, index) in store.subSys"
|
||||
:key="index"
|
||||
:class="
|
||||
twMerge(
|
||||
'w-full sm:w-1/2 lg:w-1/4 relative my-2 ',
|
||||
'w-full sm:w-1/2 relative my-2',
|
||||
item.sub_system_tag
|
||||
? 'saturate-200 cursor-pointer text-base text-info'
|
||||
: 'grayscale opacity-70 cursor-not-allowed text-sm'
|
||||
@ -154,14 +41,20 @@ const navigateToSubSystem = (mainSystemId, subSystemId) => {
|
||||
|
||||
<div class="equipment-item">
|
||||
<div class="w-16">
|
||||
<img
|
||||
v-if="item.device_image_url"
|
||||
class="w-7 m-auto"
|
||||
:src="`${FILE_BASEURL}/${item.device_image_url}`"
|
||||
/>
|
||||
<FontAwesomeIcon
|
||||
v-else
|
||||
class="text-2xl mt-1 m-auto"
|
||||
:icon="['fas', item.icon]"
|
||||
:icon="['fas', 'tv']"
|
||||
></FontAwesomeIcon>
|
||||
</div>
|
||||
<div class="icon-text">
|
||||
<div class="">
|
||||
{{ item.title }}
|
||||
{{ item.full_name }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,60 +1,106 @@
|
||||
<script setup>
|
||||
import { ref, computed } from "vue";
|
||||
import { ref, computed, watch, onUnmounted } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import { getAlarmOperationInfo } from "@/apis/dashboard";
|
||||
import useBuildingStore from "@/stores/useBuildingStore";
|
||||
import DashboardSysProgressModal from "./DashboardSysProgressModal.vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
|
||||
const { t } = useI18n();
|
||||
const router = useRouter();
|
||||
const store = useBuildingStore();
|
||||
const equipmentData = ref({
|
||||
title: "System Status",
|
||||
items: [
|
||||
{ label: "Auxiliary", online: 6, offline: 0, alarm: 0 },
|
||||
{ label: "Air Detection", online: 31, offline: 0, alarm: 2 },
|
||||
{ label: "Electricity", online: 12, offline: 0, alarm: 1 },
|
||||
{ label: "Lighting", online: 20, offline: 3, alarm: 0 },
|
||||
{ label: "Air Condition", online: 23, offline: 0, alarm: 0 },
|
||||
],
|
||||
title: t("dashboard.system_status"),
|
||||
items: [],
|
||||
});
|
||||
|
||||
const orderData = ref({
|
||||
title: "Work Order",
|
||||
items: [
|
||||
{ label: "Unassigned", value: 2 },
|
||||
{ label: "Assigned", value: 4 },
|
||||
{ label: "Completed", value: 1 },
|
||||
],
|
||||
title: t("dashboard.work_order"),
|
||||
items: [],
|
||||
});
|
||||
const modalData = ref({});
|
||||
let intervalId = null;
|
||||
|
||||
const getAlarmsInfos = async () => {
|
||||
try {
|
||||
const res = await getAlarmOperationInfo(
|
||||
store.selectedBuilding.building_guid
|
||||
);
|
||||
const apiData = res.data;
|
||||
|
||||
// 轉換 equipmentData 的資料格式
|
||||
if (apiData && apiData.alarm) {
|
||||
equipmentData.value.items = apiData.alarm.map((item) => ({
|
||||
label: item.name,
|
||||
online: item.online || 0,
|
||||
offline: item.offline || 0,
|
||||
alarm: item.alarm || 0,
|
||||
}));
|
||||
}
|
||||
|
||||
// 轉換 orderData 的資料格式
|
||||
if (apiData && apiData.operation) {
|
||||
orderData.value.items = [
|
||||
{
|
||||
label: t("operation.repair"),
|
||||
complete: apiData.operation.repair.complete || 0,
|
||||
incomplete: apiData.operation.repair.incomplete || 0,
|
||||
},
|
||||
{
|
||||
label: t("operation.maintenance"),
|
||||
complete: apiData.operation.upkeep.complete || 0,
|
||||
incomplete: apiData.operation.upkeep.incomplete || 0,
|
||||
},
|
||||
];
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error fetching alarm info:", error);
|
||||
}
|
||||
};
|
||||
|
||||
const navigateToMaintenance = (item) => {
|
||||
router.push({
|
||||
name: "operation",
|
||||
query: {
|
||||
work_type: item == "Repair" ? "2" : "1",
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const openModal = (item) => {
|
||||
modalData.value = item;
|
||||
system_status_modal.showModal();
|
||||
};
|
||||
|
||||
const onCancel = () => {
|
||||
modalData.value = {};
|
||||
system_status_modal.close();
|
||||
};
|
||||
|
||||
watch(
|
||||
() => store.selectedBuilding,
|
||||
(newBuilding) => {
|
||||
if (newBuilding) {
|
||||
getAlarmsInfos();
|
||||
|
||||
if (intervalId) {
|
||||
clearInterval(intervalId);
|
||||
}
|
||||
intervalId = setInterval(() => {
|
||||
getAlarmsInfos();
|
||||
}, 30000);
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
onUnmounted(() => {
|
||||
clearInterval(intervalId);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DashboardSysProgressModal :onCancel="onCancel" :modalData="modalData" />
|
||||
<div class="flex flex-wrap">
|
||||
<div class="w-full sm:w-3/5 state-box-col relative ps-2">
|
||||
<div class="state-box">
|
||||
<div class="title">
|
||||
<img class="state-title01" src="@ASSET/img/state-title01.svg" />
|
||||
<span class="">{{ equipmentData.title }}</span>
|
||||
<img class="state-title02" src="@ASSET/img/state-title02.svg" />
|
||||
</div>
|
||||
<table class="table table-sm text-center">
|
||||
<thead>
|
||||
<tr class="border-cyan-400 text-cyan-100">
|
||||
<th></th>
|
||||
<th>Online</th>
|
||||
<th>Offline</th>
|
||||
<th>Alarm</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr
|
||||
v-for="(item, index) in equipmentData.items"
|
||||
:key="index"
|
||||
class="border-cyan-400"
|
||||
>
|
||||
<th class="px-0 text-start">{{ item.label }}</th>
|
||||
<td>{{ item.online }}</td>
|
||||
<td>{{ item.offline }}</td>
|
||||
<td>{{ item.alarm }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-full sm:w-2/5 state-box-col relative ps-2">
|
||||
<div class="state-box">
|
||||
<div class="title">
|
||||
@ -66,19 +112,60 @@ const orderData = ref({
|
||||
<thead>
|
||||
<tr class="border-cyan-400 text-cyan-100">
|
||||
<th></th>
|
||||
<th>value</th>
|
||||
<th>{{ $t("operation.complete") }}</th>
|
||||
<th>{{ $t("operation.incomplete") }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr
|
||||
v-for="(item, index) in orderData.items"
|
||||
:key="index"
|
||||
class="border-cyan-400"
|
||||
class="border-cyan-400 cursor-pointer hover:text-info"
|
||||
@click.stop.prevent="navigateToMaintenance(item.label)"
|
||||
>
|
||||
<th class="px-0 text-start">
|
||||
<span>{{ item.label }}</span>
|
||||
</th>
|
||||
<td>{{ item.value }}</td>
|
||||
<td>{{ item.complete }}</td>
|
||||
<td>{{ item.incomplete }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-full sm:w-3/5 state-box-col relative ps-2">
|
||||
<div class="state-box">
|
||||
<div class="title">
|
||||
<img class="state-title01" src="@ASSET/img/state-title01.svg" />
|
||||
<span class="">{{ equipmentData.title }}</span>
|
||||
<img class="state-title02" src="@ASSET/img/state-title02.svg" />
|
||||
</div>
|
||||
<table class="table table-sm text-center">
|
||||
<thead>
|
||||
<tr class="border-cyan-400 text-cyan-100">
|
||||
<th></th>
|
||||
<th>{{ $t("alert.online") }}</th>
|
||||
<th>{{ $t("alert.offline") }}</th>
|
||||
<th>{{ $t("alert.alarm") }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr
|
||||
v-for="(item, index) in equipmentData.items"
|
||||
:key="index"
|
||||
class="border-cyan-400 cursor-pointer hover:text-info"
|
||||
@click.stop.prevent="openModal(item)"
|
||||
>
|
||||
<th class="px-0 text-start">{{ item.label }}</th>
|
||||
<td>
|
||||
{{ item.online.length }}
|
||||
</td>
|
||||
<td>
|
||||
{{ item.offline.length }}
|
||||
</td>
|
||||
<td>
|
||||
{{ item.alarm.length }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
@ -101,7 +188,7 @@ const orderData = ref({
|
||||
}
|
||||
|
||||
.state-box {
|
||||
@apply h-80 border-2 border-light-info rounded-sm py-2 px-6 text-white relative;
|
||||
@apply h-[22rem] border-2 border-light-info rounded-sm py-2 px-6 text-white relative;
|
||||
}
|
||||
|
||||
.state-box:after {
|
||||
@ -117,7 +204,7 @@ const orderData = ref({
|
||||
}
|
||||
|
||||
.state-box .title {
|
||||
@apply relative flex items-center mb-4;
|
||||
@apply relative flex items-center mb-1;
|
||||
}
|
||||
|
||||
.state-box .title .state-title01 {
|
||||
|
124
src/views/dashboard/components/DashboardSysProgressModal.vue
Normal file
124
src/views/dashboard/components/DashboardSysProgressModal.vue
Normal file
@ -0,0 +1,124 @@
|
||||
<script setup>
|
||||
import { ref, onMounted, defineProps, inject, watch } from "vue";
|
||||
import useActiveBtn from "@/hooks/useActiveBtn";
|
||||
import { useI18n } from "vue-i18n";
|
||||
|
||||
const { t } = useI18n();
|
||||
const props = defineProps({
|
||||
onCancel: Function,
|
||||
modalData: Object,
|
||||
});
|
||||
const { items, changeActiveBtn, setItems, selectedBtn } = useActiveBtn();
|
||||
const detailData = ref([]);
|
||||
onMounted(() => {
|
||||
setItems([
|
||||
{
|
||||
title: t("alert.online"),
|
||||
key: "online",
|
||||
active: true,
|
||||
},
|
||||
{
|
||||
title: t("alert.offline"),
|
||||
key: "offline",
|
||||
active: false,
|
||||
},
|
||||
{
|
||||
title: t("alert.alarm"),
|
||||
key: "alarm",
|
||||
active: false,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
watch(selectedBtn, (newVal, oldVal) => {
|
||||
if (newVal) {
|
||||
detailData.value = props.modalData[newVal.key];
|
||||
}
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.modalData,
|
||||
(newVal, oldVal) => {
|
||||
if (newVal) {
|
||||
changeActiveBtn(items.value[0]);
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal id="system_status_modal" :onCancel="onCancel" :width="600">
|
||||
<template #modalTitle>
|
||||
<div class="flex items-center justify-between">
|
||||
<ButtonGroup
|
||||
:items="items"
|
||||
:withLine="true"
|
||||
className="btn-sm"
|
||||
:onclick="
|
||||
(e, item) => {
|
||||
changeActiveBtn(item);
|
||||
}
|
||||
"
|
||||
/>
|
||||
<button
|
||||
type="link"
|
||||
class="btn-link btn-text-without-border px-2"
|
||||
@click="onCancel"
|
||||
>
|
||||
<font-awesome-icon :icon="['fas', 'times']" class="text-[#a5abb1]" />
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
<template #modalContent>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="table text-base mt-5">
|
||||
<thead>
|
||||
<tr>
|
||||
<th
|
||||
class="text-base border text-white text-center bg-cyan-600 bg-opacity-30"
|
||||
>
|
||||
{{ $t("table.serial_number") }}
|
||||
</th>
|
||||
<th
|
||||
class="text-base border text-white text-center bg-cyan-600 bg-opacity-30"
|
||||
>
|
||||
{{ $t("table.name") }}
|
||||
</th>
|
||||
<th
|
||||
class="text-base border text-white text-center bg-cyan-600 bg-opacity-30"
|
||||
>
|
||||
{{ $t("table.time") }}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr
|
||||
v-if="detailData?.length > 0"
|
||||
v-for="(equipment, index) in detailData"
|
||||
:key="index"
|
||||
class="hover:bg-gray-700"
|
||||
>
|
||||
<td class="border text-white text-center">
|
||||
{{ index + 1 }}
|
||||
</td>
|
||||
<td class="border text-white text-center">
|
||||
{{ equipment.name }}
|
||||
</td>
|
||||
<td class="border text-white text-center">
|
||||
{{ equipment.time || "-" }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-else>
|
||||
<td colspan="3" class="border text-white text-center">
|
||||
{{ $t("table.no_data") }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</template>
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped></style>
|
@ -1,5 +1,5 @@
|
||||
<script setup>
|
||||
import { ref, onMounted, watch } from "vue";
|
||||
import { ref, onMounted, watch, markRaw } from "vue";
|
||||
import { useRoute } from "vue-router";
|
||||
|
||||
import EnergyChart from "./components/EnergyChart/EnergyChart.vue";
|
||||
@ -14,12 +14,12 @@ const updateComponent = () => {
|
||||
|
||||
if (main_system_id === "energy_chart") {
|
||||
if (sub_system_id === "chart") {
|
||||
currentComponent.value = EnergyChart;
|
||||
currentComponent.value = markRaw(EnergyChart);
|
||||
} else {
|
||||
currentComponent.value = EnergyHistoryTable;
|
||||
currentComponent.value = markRaw(EnergyHistoryTable);
|
||||
}
|
||||
} else if (main_system_id === "energy_report") {
|
||||
currentComponent.value = EnergyReport;
|
||||
currentComponent.value = markRaw(EnergyReport);
|
||||
} else {
|
||||
currentComponent.value = null;
|
||||
}
|
||||
|
@ -3,9 +3,12 @@ import BarChart from "@/components/chart/BarChart.vue";
|
||||
import { ref, onMounted, inject, watch } from "vue";
|
||||
import CarbonEmissionModal from "./CarbonEmissionModal.vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { getCarbonValue } from "@/apis/energy";
|
||||
import useBuildingStore from "@/stores/useBuildingStore";
|
||||
const store = useBuildingStore();
|
||||
const { t, locale } = useI18n();
|
||||
const { taipower_data } = inject("energy_data");
|
||||
const editRecord = ref(null);
|
||||
const { taipower_data, carbonValue } = inject("energy_data");
|
||||
const carbonData = ref(null);
|
||||
const defaultChartOption = ref({
|
||||
tooltip: {
|
||||
trigger: "axis",
|
||||
@ -79,23 +82,23 @@ watch(locale, () => {
|
||||
updateChartNames();
|
||||
});
|
||||
|
||||
const openModal = (record) => {
|
||||
if (record) {
|
||||
editRecord.value = record;
|
||||
} else {
|
||||
editRecord.value = null;
|
||||
const getData = async () => {
|
||||
if (store.selectedBuilding.building_guid) {
|
||||
const res = await getCarbonValue(store.selectedBuilding.building_guid);
|
||||
carbonData.value = res.data[0];
|
||||
carbonValue.value = res.data[0].coefficient;
|
||||
}
|
||||
carbon_emission_item.showModal();
|
||||
};
|
||||
|
||||
const onCancel = () => {
|
||||
editRecord.value = null;
|
||||
carbon_emission_item.close();
|
||||
};
|
||||
|
||||
const fetchData = async () => {
|
||||
console.log("ok");
|
||||
};
|
||||
watch(
|
||||
() => store.selectedBuilding,
|
||||
(newBuilding) => {
|
||||
if (newBuilding) {
|
||||
getData();
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
updateChartNames();
|
||||
@ -109,10 +112,8 @@ onMounted(() => {
|
||||
{{ $t("energy.monthly_carbon_emission_and_reduction") }}
|
||||
</div>
|
||||
<CarbonEmissionModal
|
||||
:openModal="openModal"
|
||||
:onCancel="onCancel"
|
||||
:editRecord="editRecord"
|
||||
:fetchData="fetchData"
|
||||
:carbonData="carbonData"
|
||||
:getData="getData"
|
||||
/>
|
||||
</div>
|
||||
<div class="bar-box">
|
||||
|
@ -1,35 +1,33 @@
|
||||
<script setup>
|
||||
import { inject, defineProps, watch, ref } from "vue";
|
||||
import useFormErrorMessage from "@/hooks/useFormErrorMessage";
|
||||
import { postAlertMember } from "@/apis/alert";
|
||||
import { postEditCarbonValue } from "@/apis/energy";
|
||||
import * as yup from "yup";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import useBuildingStore from "@/stores/useBuildingStore";
|
||||
const store = useBuildingStore();
|
||||
const { t } = useI18n();
|
||||
const { openToast } = inject("app_toast");
|
||||
const props = defineProps({
|
||||
openModal: Function,
|
||||
onCancel: Function,
|
||||
editRecord: Object,
|
||||
fetchData: Function,
|
||||
carbonData: Object,
|
||||
getData: Function,
|
||||
});
|
||||
|
||||
let scheme = yup.object({
|
||||
factor: yup.number().required(t("button.required")),
|
||||
coefficient: yup.number().required(t("button.required")),
|
||||
});
|
||||
|
||||
const form = ref(null);
|
||||
const formState = ref({
|
||||
factor: null,
|
||||
coefficient: null,
|
||||
});
|
||||
|
||||
const SaveCheckAuth = ref([]);
|
||||
|
||||
const { formErrorMsg, handleSubmit, handleErrorReset } = useFormErrorMessage(
|
||||
scheme.value
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.editRecord,
|
||||
() => props.carbonData,
|
||||
(newValue) => {
|
||||
if (newValue) {
|
||||
formState.value = {
|
||||
@ -42,42 +40,54 @@ watch(
|
||||
const onOk = async () => {
|
||||
const values = await handleSubmit(scheme, formState.value);
|
||||
|
||||
const res = await postAlertMember({
|
||||
const res = await postEditCarbonValue({
|
||||
...values,
|
||||
building_guid: store.selectedBuilding.building_guid,
|
||||
});
|
||||
if (res.isSuccess) {
|
||||
props.fetchData();
|
||||
props.getData();
|
||||
closeModal();
|
||||
} else {
|
||||
openToast("error", res.msg, "#carbon_emission_item");
|
||||
}
|
||||
};
|
||||
|
||||
const openModal = () => {
|
||||
carbon_emission_item.showModal();
|
||||
};
|
||||
|
||||
const onCancel = () => {
|
||||
carbon_emission_item.close();
|
||||
};
|
||||
|
||||
const closeModal = () => {
|
||||
SaveCheckAuth.value = [];
|
||||
handleErrorReset();
|
||||
props.onCancel();
|
||||
onCancel();
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<button class="btn btn-sm btn-success ms-auto me-3" @click.stop.prevent="openModal">
|
||||
<button
|
||||
class="btn btn-sm btn-success ms-auto me-3"
|
||||
@click.stop.prevent="openModal"
|
||||
>
|
||||
{{ $t("button.edit") }}
|
||||
</button>
|
||||
<Modal
|
||||
id="carbon_emission_item"
|
||||
:title="t('energy.edit_carbon_emission')"
|
||||
:open="open"
|
||||
:onCancel="closeModal"
|
||||
width="300"
|
||||
:width="300"
|
||||
>
|
||||
<template #modalContent>
|
||||
<form ref="form" class="mt-5 flex flex-col items-center">
|
||||
<Input :value="formState" class="w-full" name="factor">
|
||||
<template #topLeft>{{$t('energy.carbon_emission_coefficient')}}</template>
|
||||
<Input :value="formState" class="w-full" name="coefficient">
|
||||
<template #topLeft>{{
|
||||
$t("energy.carbon_emission_coefficient")
|
||||
}}</template>
|
||||
<template #bottomLeft>
|
||||
<span class="text-error text-base">
|
||||
{{ formErrorMsg.factor }}
|
||||
{{ formErrorMsg.coefficient }}
|
||||
</span>
|
||||
</template>
|
||||
</Input>
|
||||
|
@ -1,12 +1,13 @@
|
||||
<script setup>
|
||||
import { ref, onMounted, nextTick, computed } from "vue";
|
||||
import { ref, onMounted, watch, inject } from "vue";
|
||||
import * as echarts from "echarts";
|
||||
import { getRealTimeDist } from "@/apis/energy";
|
||||
import { useI18n } from "vue-i18n";
|
||||
|
||||
const { search_data } = inject("energy_data");
|
||||
const { t } = useI18n();
|
||||
|
||||
const chartDiv = ref(null);
|
||||
let myChart = null; // 添加 myChart 变量
|
||||
|
||||
const chartOption = {
|
||||
tooltip: {
|
||||
@ -39,7 +40,7 @@ const chartOption = {
|
||||
borderWidth: 0,
|
||||
},
|
||||
lineStyle: {
|
||||
color: 'gradient',
|
||||
color: "gradient",
|
||||
opacity: 0.7,
|
||||
curveness: 0.5,
|
||||
},
|
||||
@ -47,49 +48,85 @@ const chartOption = {
|
||||
],
|
||||
};
|
||||
|
||||
const loadData = async () => {
|
||||
const res = await getRealTimeDist();
|
||||
if (res.isSuccess) {
|
||||
const loadData = async (value) => {
|
||||
const res = await getRealTimeDist(value);
|
||||
if (res.isSuccess && res.data && res.data.length !== 0) {
|
||||
const rawData = res.data;
|
||||
const totalValue = rawData.reduce((acc, item) => acc + item.value, 0);
|
||||
if (rawData) {
|
||||
const totalValue = rawData.reduce((acc, item) => acc + item.value, 0);
|
||||
|
||||
const sortedData = [...rawData].sort((a, b) => b.value - a.value);
|
||||
// 構造 data 節點
|
||||
const data = [
|
||||
{ name: "Total", value: totalValue, percentage: 100 },
|
||||
...sortedData.map((item) => ({
|
||||
name: item.key,
|
||||
const sortedData = [...rawData].sort((a, b) => b.value - a.value);
|
||||
// 構造 data 節點
|
||||
const data = [
|
||||
{ name: "Total", value: totalValue, percentage: 100 },
|
||||
...sortedData.map((item) => ({
|
||||
name: item.key,
|
||||
value: item.value,
|
||||
percentage: item.percentage,
|
||||
})),
|
||||
];
|
||||
|
||||
// 構造 links 連結
|
||||
const links = sortedData.map((item, index) => ({
|
||||
source: "Total",
|
||||
target: item.key,
|
||||
value: item.value,
|
||||
percentage: item.percentage,
|
||||
})),
|
||||
];
|
||||
}));
|
||||
|
||||
// 構造 links 連結
|
||||
const links = sortedData.map((item, index) => ({
|
||||
source: "Total",
|
||||
target: item.key,
|
||||
value: item.value,
|
||||
percentage: item.percentage,
|
||||
}));
|
||||
const colors = [
|
||||
"#45f4ef",
|
||||
"#17CEE3",
|
||||
"#E4EA00",
|
||||
"#62E39A",
|
||||
"#E9971F",
|
||||
"#E52EFF",
|
||||
];
|
||||
// 更新 chartOption
|
||||
chartOption.series[0].data = data.map((item, index) => ({
|
||||
...item,
|
||||
itemStyle: {
|
||||
color: colors[index % colors.length], // 節點顏色
|
||||
},
|
||||
}));
|
||||
chartOption.series[0].links = links;
|
||||
|
||||
const colors = ["#45f4ef", "#17CEE3", "#E4EA00", "#62E39A", "#E9971F", "#E52EFF"];
|
||||
// 更新 chartOption
|
||||
chartOption.series[0].data = data.map((item, index) => ({
|
||||
...item,
|
||||
itemStyle: {
|
||||
color: colors[index % colors.length], // 節點顏色
|
||||
},
|
||||
}));
|
||||
chartOption.series[0].links = links;
|
||||
|
||||
// 初始化圖表
|
||||
const myChart = echarts.init(chartDiv.value);
|
||||
myChart.setOption(chartOption);
|
||||
// 初始化圖表
|
||||
if (myChart) {
|
||||
myChart.dispose(); // 銷毀之前的實例
|
||||
}
|
||||
myChart = echarts.init(chartDiv.value);
|
||||
myChart.setOption(chartOption);
|
||||
}
|
||||
} else {
|
||||
// 清空圖表
|
||||
if (myChart) {
|
||||
myChart.dispose(); // 銷毀之前的實例
|
||||
myChart = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
watch(
|
||||
search_data,
|
||||
(newValue, oldValue) => {
|
||||
if (
|
||||
newValue.building_guid &&
|
||||
JSON.stringify(newValue) !== JSON.stringify(oldValue)
|
||||
) {
|
||||
loadData(newValue);
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
deep: true,
|
||||
}
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
loadData();
|
||||
// 初始化圖表
|
||||
myChart = echarts.init(chartDiv.value);
|
||||
myChart.setOption(chartOption);
|
||||
});
|
||||
</script>
|
||||
|
||||
@ -114,4 +151,4 @@ ul li:last-child:after {
|
||||
@apply absolute top-0 bottom-0 left-full block w-full h-[1px] bg-slate-600 m-auto z-10;
|
||||
content: "";
|
||||
}
|
||||
</style>
|
||||
</style>
|
@ -1,4 +1,5 @@
|
||||
<script setup>
|
||||
import { ref, computed, provide, watch } from "vue";
|
||||
import ImmediateDemandChart from "./ImmediateDemandChart.vue";
|
||||
import ElecConsumption from "./ElecConsumption.vue";
|
||||
import UsageInformation from "./UsageInformation.vue";
|
||||
@ -6,26 +7,130 @@ import MonthlyElecBillChart from "./MonthlyElecBillChart.vue";
|
||||
import CarbonEmissionChart from "./CarbonEmissionChart.vue";
|
||||
import BillingDegreeChart from "./BillingDegreeChart.vue";
|
||||
import IntervalBillChart from "./IntervalBillChart.vue";
|
||||
import useActiveBtn from "@/hooks/useActiveBtn";
|
||||
import { getTaipower } from "@/apis/energy";
|
||||
import { ref, onMounted, provide } from "vue";
|
||||
import useBuildingStore from "@/stores/useBuildingStore";
|
||||
|
||||
const storeBuild = useBuildingStore();
|
||||
// 選樓層
|
||||
const {
|
||||
items: sysFloorItems,
|
||||
changeActiveBtn: changeFloorActiveBtn,
|
||||
setItems: setFloorItems,
|
||||
selectedBtn: selectedFloorItems,
|
||||
} = useActiveBtn("multiple");
|
||||
// 選部門
|
||||
const {
|
||||
items: sysDeptItems,
|
||||
changeActiveBtn: changeDeptActiveBtn,
|
||||
setItems: setDeptItems,
|
||||
selectedBtn: selectedDeptItems,
|
||||
} = useActiveBtn("multiple");
|
||||
|
||||
const taipower_data = ref([]);
|
||||
const getData = async () => {
|
||||
const res = await getTaipower();
|
||||
const carbonValue = ref(null);
|
||||
const search_data = computed(() => {
|
||||
return {
|
||||
coefficient: carbonValue.value,
|
||||
building_guid: storeBuild.selectedBuilding?.building_guid || null,
|
||||
department_id_list: selectedDeptItems.value.map((item) => item.key),
|
||||
floor_guid_list: selectedFloorItems.value.map((item) => item.key),
|
||||
};
|
||||
});
|
||||
|
||||
const getData = async (value) => {
|
||||
const res = await getTaipower(value);
|
||||
if (res.isSuccess) {
|
||||
taipower_data.value = res.data;
|
||||
taipower_data.value = res.data
|
||||
? res.data.sort((a, b) => a.month.localeCompare(b.month))
|
||||
: [];
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
getData();
|
||||
});
|
||||
watch(
|
||||
search_data,
|
||||
(newValue, oldValue) => {
|
||||
if (
|
||||
newValue.building_guid &&
|
||||
newValue.coefficient &&
|
||||
JSON.stringify(newValue) !== JSON.stringify(oldValue)
|
||||
) {
|
||||
getData(newValue);
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
deep: true,
|
||||
}
|
||||
);
|
||||
|
||||
provide("energy_data", { taipower_data });
|
||||
watch(
|
||||
() => storeBuild.floorList,
|
||||
(newValue) => {
|
||||
if (newValue) {
|
||||
const floorList = newValue.map((d) => ({
|
||||
...d,
|
||||
active: true,
|
||||
}));
|
||||
setFloorItems(floorList);
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
() => storeBuild.deptList,
|
||||
(newValue) => {
|
||||
if (newValue) {
|
||||
const deptList = newValue.map((d) => ({
|
||||
...d,
|
||||
active: true,
|
||||
}));
|
||||
setDeptItems(deptList);
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
}
|
||||
);
|
||||
|
||||
provide("energy_data", { taipower_data, search_data, carbonValue });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-wrap items-center mb-4">
|
||||
<div class="w-full border border-info px-4 py-2 rounded mt-3">
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="text-md font-extrabold">{{ $t("energy.floor") }} :</span>
|
||||
<ButtonGroup
|
||||
:items="sysFloorItems"
|
||||
:withLine="true"
|
||||
className="btn-xs rounded-md"
|
||||
:onclick="
|
||||
(e, item) => {
|
||||
changeFloorActiveBtn(item);
|
||||
}
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="text-md font-extrabold"
|
||||
>{{ $t("assetManagement.department") }} :</span
|
||||
>
|
||||
<ButtonGroup
|
||||
:items="sysDeptItems"
|
||||
:withLine="true"
|
||||
className="btn-xs rounded-md"
|
||||
:onclick="
|
||||
(e, item) => {
|
||||
changeDeptActiveBtn(item);
|
||||
}
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-full xl:w-5/12 lg:w-1/2">
|
||||
<ElecConsumption />
|
||||
</div>
|
||||
|
@ -1,89 +1,29 @@
|
||||
<script setup>
|
||||
import LineChart from "@/components/chart/LineChart.vue";
|
||||
import { ref, onMounted } from "vue";
|
||||
import { ref, onMounted, watch, onUnmounted } from "vue";
|
||||
import ImmediateDemandModal from "./ImmediateDemandModal.vue";
|
||||
import { getDemand, getRealTimeDemand } from "@/apis/energy";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import useBuildingStore from "@/stores/useBuildingStore";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
const store = useBuildingStore();
|
||||
const { t } = useI18n();
|
||||
|
||||
const editRecord = ref(null);
|
||||
// 假資料
|
||||
const data = {
|
||||
categories: [
|
||||
"16:22:29",
|
||||
"16:22:37",
|
||||
"16:22:47",
|
||||
"16:23:00",
|
||||
"16:23:08",
|
||||
"16:23:18",
|
||||
],
|
||||
series: [
|
||||
{
|
||||
name: t("energy.real_time_Trend"), // 即時趨勢
|
||||
type: "line",
|
||||
data: [320, 310, 300, 305, 310, 300],
|
||||
smooth: true,
|
||||
lineStyle: {
|
||||
width: 3,
|
||||
},
|
||||
itemStyle: {
|
||||
color: "#17CEE3",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: t("energy.contract_capacity"), // 契約容量
|
||||
type: "line",
|
||||
data: [400, 400, 400, 400, 400, 400],
|
||||
smooth: true,
|
||||
lineStyle: {
|
||||
width: 3,
|
||||
},
|
||||
itemStyle: {
|
||||
color: "#E4EA00",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: t("energy.alert_capacity"), // 警戒容量
|
||||
type: "line",
|
||||
data: [350, 350, 350, 350, 350, 350],
|
||||
smooth: true,
|
||||
lineStyle: {
|
||||
width: 3,
|
||||
},
|
||||
itemStyle: {
|
||||
color: "#62E39A",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: t("energy.reset_value"), // 偵測值
|
||||
type: "line",
|
||||
data: [280, 300, 290, 295, 300, 290],
|
||||
smooth: true,
|
||||
lineStyle: {
|
||||
width: 3,
|
||||
},
|
||||
itemStyle: {
|
||||
color: "#E9971F",
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const demandData = ref(null);
|
||||
const realTimeDemand = ref([]);
|
||||
const demand_chart = ref(null);
|
||||
const intervalId = ref(null);
|
||||
// 預設為空,避免 `null` 造成 `ECharts` 失敗
|
||||
const defaultChartOption = ref({
|
||||
tooltip: {
|
||||
trigger: "axis",
|
||||
},
|
||||
tooltip: { trigger: "axis" },
|
||||
legend: {
|
||||
data: data.series.map((s) => s.name),
|
||||
textStyle: {
|
||||
color: "#ffffff",
|
||||
fontSize: 16,
|
||||
},
|
||||
orient: "horizontal",
|
||||
data: [],
|
||||
textStyle: { color: "#ffffff", fontSize: 16 },
|
||||
bottom: "0%",
|
||||
},
|
||||
grid: {
|
||||
top: "10%",
|
||||
top: "3%",
|
||||
left: "0%",
|
||||
right: "0%",
|
||||
bottom: "15%",
|
||||
@ -91,46 +31,125 @@ const defaultChartOption = ref({
|
||||
},
|
||||
xAxis: {
|
||||
type: "category",
|
||||
splitLine: {
|
||||
show: false,
|
||||
},
|
||||
axisLabel: {
|
||||
color: "#ffffff",
|
||||
},
|
||||
data: data.categories,
|
||||
splitLine: { show: false },
|
||||
axisLabel: { color: "#ffffff" },
|
||||
data: [], // 預設空值,避免 undefined
|
||||
},
|
||||
yAxis: {
|
||||
type: "value",
|
||||
splitLine: {
|
||||
show: false,
|
||||
},
|
||||
axisLabel: {
|
||||
color: "#ffffff",
|
||||
},
|
||||
splitLine: { show: false },
|
||||
axisLabel: { color: "#ffffff" },
|
||||
},
|
||||
series: data.series,
|
||||
series: [],
|
||||
});
|
||||
|
||||
const openModal = (record) => {
|
||||
if (record) {
|
||||
editRecord.value = record;
|
||||
} else {
|
||||
editRecord.value = null;
|
||||
// 取得契約數據
|
||||
const getData = async () => {
|
||||
if (store.selectedBuilding.building_guid) {
|
||||
const res = await getDemand(store.selectedBuilding.building_guid);
|
||||
demandData.value = res.data[0];
|
||||
updateChart();
|
||||
}
|
||||
immediate_demand_add_item.showModal();
|
||||
};
|
||||
|
||||
const onCancel = () => {
|
||||
editRecord.value = null;
|
||||
immediate_demand_add_item.close();
|
||||
// 取得即時數據
|
||||
const getRealTime = async () => {
|
||||
if (store.selectedBuilding.building_guid) {
|
||||
const res = await getRealTimeDemand(store.selectedBuilding.building_guid);
|
||||
realTimeDemand.value = res.data.reverse();
|
||||
updateChart();
|
||||
}
|
||||
};
|
||||
|
||||
const fetchData = async () => {
|
||||
console.log("ok");
|
||||
// 更新圖表資料
|
||||
const updateChart = () => {
|
||||
if (!demandData.value || !realTimeDemand.value.length) return;
|
||||
|
||||
defaultChartOption.value = {
|
||||
...defaultChartOption.value,
|
||||
legend: {
|
||||
...defaultChartOption.value.legend,
|
||||
data: [
|
||||
t("energy.real_time_Trend"),
|
||||
t("energy.contract_capacity"),
|
||||
t("energy.alert_capacity"),
|
||||
t("energy.reset_value"),
|
||||
],
|
||||
},
|
||||
xAxis: {
|
||||
...defaultChartOption.value.xAxis,
|
||||
data: realTimeDemand.value.map(({ time }) =>
|
||||
dayjs(time).format("HH:mm:ss")
|
||||
),
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: t("energy.real_time_Trend"),
|
||||
type: "line",
|
||||
data: realTimeDemand.value.map((d) => d.value),
|
||||
smooth: true,
|
||||
lineStyle: { width: 3 },
|
||||
itemStyle: { color: "#17CEE3" },
|
||||
},
|
||||
{
|
||||
name: t("energy.contract_capacity"),
|
||||
type: "line",
|
||||
data: Array(realTimeDemand.value.length).fill(
|
||||
demandData.value.contract
|
||||
),
|
||||
smooth: true,
|
||||
lineStyle: { width: 3 },
|
||||
itemStyle: { color: "#E4EA00" },
|
||||
},
|
||||
{
|
||||
name: t("energy.alert_capacity"),
|
||||
type: "line",
|
||||
data: Array(realTimeDemand.value.length).fill(demandData.value.alert),
|
||||
smooth: true,
|
||||
lineStyle: { width: 3 },
|
||||
itemStyle: { color: "#62E39A" },
|
||||
},
|
||||
{
|
||||
name: t("energy.reset_value"),
|
||||
type: "line",
|
||||
data: Array(realTimeDemand.value.length).fill(demandData.value.reset),
|
||||
smooth: true,
|
||||
lineStyle: { width: 3 },
|
||||
itemStyle: { color: "#E9971F" },
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
// 確保 `chart` 有更新
|
||||
if (demand_chart.value?.chart) {
|
||||
// demand_chart.value.chart.clear();
|
||||
demand_chart.value.chart.setOption(defaultChartOption.value);
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
//
|
||||
// 監聽建築變更時重新抓取數據
|
||||
watch(
|
||||
() => store.selectedBuilding,
|
||||
(newBuilding) => {
|
||||
if (newBuilding) {
|
||||
getData();
|
||||
getRealTime();
|
||||
// 每 30 秒重新取得即時數據
|
||||
if (intervalId.value) {
|
||||
clearInterval(intervalId.value);
|
||||
}
|
||||
// 設置新的定時器
|
||||
intervalId.value = setInterval(getRealTime, 30000);
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
onUnmounted(() => {
|
||||
// 清除定時器
|
||||
clearInterval(intervalId.value); // 使用 intervalId.value
|
||||
intervalId.value = null; // 清空 intervalId
|
||||
console.log("Interval cleared!");
|
||||
});
|
||||
</script>
|
||||
|
||||
@ -139,18 +158,16 @@ onMounted(() => {
|
||||
<div class="flex items-center text-white mb-5">
|
||||
<div class="flex items-end text-base relative text mr-32">
|
||||
{{ $t("energy.immediate_demand") }}
|
||||
<span class="text-2xl px-2.5">245.48 kw</span>
|
||||
<span class="text-2xl px-2.5"
|
||||
>{{
|
||||
realTimeDemand.length > 0
|
||||
? realTimeDemand[realTimeDemand.length - 1].value
|
||||
: "---"
|
||||
}}
|
||||
kw</span
|
||||
>
|
||||
</div>
|
||||
<div class="flex items-end text-base">
|
||||
{{ $t("energy.average_demand") }}
|
||||
<span class="text-2xl px-2.5">230.8 kw</span>
|
||||
</div>
|
||||
<ImmediateDemandModal
|
||||
:openModal="openModal"
|
||||
:onCancel="onCancel"
|
||||
:editRecord="editRecord"
|
||||
:fetchData="fetchData"
|
||||
/>
|
||||
<ImmediateDemandModal :demandData="demandData" :getData="getData" />
|
||||
</div>
|
||||
<LineChart
|
||||
id="immediate_demand_chart"
|
||||
|
@ -1,22 +1,22 @@
|
||||
<script setup>
|
||||
import { inject, defineProps, watch, ref } from "vue";
|
||||
import useFormErrorMessage from "@/hooks/useFormErrorMessage";
|
||||
import { postAlertMember } from "@/apis/alert";
|
||||
import { postEditDemand } from "@/apis/energy";
|
||||
import * as yup from "yup";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import useBuildingStore from "@/stores/useBuildingStore";
|
||||
const store = useBuildingStore();
|
||||
const { t } = useI18n();
|
||||
const { openToast } = inject("app_toast");
|
||||
const props = defineProps({
|
||||
openModal: Function,
|
||||
onCancel: Function,
|
||||
editRecord: Object,
|
||||
fetchData: Function,
|
||||
demandData: Object,
|
||||
getData: Function,
|
||||
});
|
||||
|
||||
let scheme = yup.object({
|
||||
contract: yup.number().required(t("button.required")),
|
||||
alert:yup.number().required(t("button.required")),
|
||||
reset:yup.number().required(t("button.required")),
|
||||
alert: yup.number().required(t("button.required")),
|
||||
reset: yup.number().required(t("button.required")),
|
||||
});
|
||||
|
||||
const form = ref(null);
|
||||
@ -26,14 +26,12 @@ const formState = ref({
|
||||
reset: null,
|
||||
});
|
||||
|
||||
const SaveCheckAuth = ref([]);
|
||||
|
||||
const { formErrorMsg, handleSubmit, handleErrorReset } = useFormErrorMessage(
|
||||
scheme.value
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.editRecord,
|
||||
() => props.demandData,
|
||||
(newValue) => {
|
||||
if (newValue) {
|
||||
formState.value = {
|
||||
@ -46,11 +44,12 @@ watch(
|
||||
const onOk = async () => {
|
||||
const values = await handleSubmit(scheme, formState.value);
|
||||
|
||||
const res = await postAlertMember({
|
||||
const res = await postEditDemand({
|
||||
...values,
|
||||
"building_guid":store.selectedBuilding.building_guid,
|
||||
});
|
||||
if (res.isSuccess) {
|
||||
props.fetchData();
|
||||
props.getData();
|
||||
closeModal();
|
||||
} else {
|
||||
openToast("error", res.msg, "#immediate_demand_add_item");
|
||||
@ -58,27 +57,36 @@ const onOk = async () => {
|
||||
};
|
||||
|
||||
const closeModal = () => {
|
||||
SaveCheckAuth.value = [];
|
||||
handleErrorReset();
|
||||
props.onCancel();
|
||||
onCancel();
|
||||
};
|
||||
|
||||
const openModal = () => {
|
||||
immediate_demand_add_item.showModal();
|
||||
};
|
||||
|
||||
const onCancel = () => {
|
||||
immediate_demand_add_item.close();
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<button class="btn btn-sm btn-success ms-auto me-6 my-3" @click.stop.prevent="openModal">
|
||||
<button
|
||||
class="btn btn-sm btn-success ms-auto me-6 my-3"
|
||||
@click.stop.prevent="openModal"
|
||||
>
|
||||
{{ $t("button.edit") }}
|
||||
</button>
|
||||
<Modal
|
||||
id="immediate_demand_add_item"
|
||||
:title="t('energy.edit_automatic_demand')"
|
||||
:open="open"
|
||||
:onCancel="closeModal"
|
||||
width="300"
|
||||
:width="300"
|
||||
>
|
||||
<template #modalContent>
|
||||
<form ref="form" class="mt-5 flex flex-col items-center">
|
||||
<Input :value="formState" class="w-full" name="contract">
|
||||
<template #topLeft>{{$t("energy.contract_capacity")}}</template>
|
||||
<template #topLeft>{{ $t("energy.contract_capacity") }}</template>
|
||||
<template #bottomLeft>
|
||||
<span class="text-error text-base">
|
||||
{{ formErrorMsg.contract }}
|
||||
@ -86,7 +94,7 @@ const closeModal = () => {
|
||||
</template>
|
||||
</Input>
|
||||
<Input class="w-full" :value="formState" name="alert">
|
||||
<template #topLeft>{{$t("energy.alert_capacity")}}</template>
|
||||
<template #topLeft>{{ $t("energy.alert_capacity") }}</template>
|
||||
<template #bottomLeft>
|
||||
<span class="text-error text-base">
|
||||
{{ formErrorMsg.alert }}
|
||||
@ -94,7 +102,7 @@ const closeModal = () => {
|
||||
</template>
|
||||
</Input>
|
||||
<Input class="w-full" :value="formState" name="reset">
|
||||
<template #topLeft>{{$t("energy.reset_value")}}</template>
|
||||
<template #topLeft>{{ $t("energy.reset_value") }}</template>
|
||||
<template #bottomLeft>
|
||||
<span class="text-error text-base">
|
||||
{{ formErrorMsg.reset }}
|
||||
|
@ -1,9 +1,9 @@
|
||||
<script setup>
|
||||
import BarChart from "@/components/chart/BarChart.vue";
|
||||
import { ref, onMounted, computed } from "vue";
|
||||
import { ref, watch, computed, inject } from "vue";
|
||||
import { getElecUseDay } from "@/apis/energy";
|
||||
import { useI18n } from "vue-i18n";
|
||||
|
||||
const { search_data } = inject("energy_data");
|
||||
const { t } = useI18n();
|
||||
const dataSource = ref([]);
|
||||
const dateRange = ref({
|
||||
@ -107,22 +107,40 @@ const chartOption = computed(() => {
|
||||
};
|
||||
});
|
||||
|
||||
const loadData = async () => {
|
||||
const res = await getElecUseDay();
|
||||
if (res.isSuccess) {
|
||||
dataSource.value = res.data.map((d) => ({ ...d, key: d.id }));
|
||||
const loadData = async (value) => {
|
||||
const res = await getElecUseDay(value);
|
||||
if (res.isSuccess && res.data) {
|
||||
dataSource.value = res.data
|
||||
.sort((a, b) => a.time.localeCompare(b.time))
|
||||
.map((d) => ({ ...d, key: d.id }));
|
||||
|
||||
const dates = res.data.map((d) => d.time.split(" ")[0]);
|
||||
dateRange.value = {
|
||||
min: dates[0],
|
||||
max: dates[dates.length - 1],
|
||||
};
|
||||
} else {
|
||||
// 初始化圖表
|
||||
dataSource.value = [];
|
||||
dateRange.value = { min: null, max: null };
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
loadData();
|
||||
});
|
||||
watch(
|
||||
search_data,
|
||||
(newValue, oldValue) => {
|
||||
if (
|
||||
newValue.building_guid &&
|
||||
JSON.stringify(newValue) !== JSON.stringify(oldValue)
|
||||
) {
|
||||
loadData(newValue);
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
deep: true,
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -64,12 +64,12 @@ const defaultChartOption = ref({
|
||||
},
|
||||
{
|
||||
name: "",
|
||||
type: "bar",
|
||||
stack: "total",
|
||||
type: "line",
|
||||
data: [],
|
||||
itemStyle: {
|
||||
color: "#62E39A",
|
||||
},
|
||||
lineStyle: { width: 3 },
|
||||
},
|
||||
],
|
||||
});
|
||||
|
@ -19,7 +19,7 @@ const calculateData = () => {
|
||||
item.month.startsWith(currentYear)
|
||||
);
|
||||
|
||||
const totalElecBills = filteredData.reduce((sum, item) => sum + item.kWh, 0);
|
||||
const totalElecBills = filteredData.reduce((sum, item) => sum + item.costTotal, 0);
|
||||
const latestMonthData = filteredData[filteredData.length - 1];
|
||||
const latestMonth = latestMonthData ? latestMonthData.month : "";
|
||||
const monthDays = latestMonth ? daysInMonth(latestMonth) : 0;
|
||||
|
@ -3,7 +3,7 @@ import { computed, defineProps, inject, ref, watch } from "vue";
|
||||
import { useRoute } from "vue-router";
|
||||
import { getHistoryData, getHistoryExportData } from "@/apis/history";
|
||||
import useSearchParam from "@/hooks/useSearchParam";
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useI18n } from "vue-i18n";
|
||||
const { t } = useI18n();
|
||||
const { searchParams } = useSearchParam();
|
||||
const route = useRoute();
|
||||
@ -26,7 +26,6 @@ const cancelToastOpen = () => {
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
const submit = async (e, type = "") => {
|
||||
e?.preventDefault();
|
||||
e?.stopPropagation();
|
||||
@ -41,9 +40,15 @@ const submit = async (e, type = "") => {
|
||||
|
||||
if (type === "export") {
|
||||
const res = await getHistoryExportData({
|
||||
type: searchParams.value.selectedType,
|
||||
...params,
|
||||
...searchParams.value,
|
||||
Type:
|
||||
route.params.type != 1
|
||||
? 2
|
||||
: searchParams.value.Type
|
||||
? searchParams.value.Type
|
||||
: 1,
|
||||
table_type: route.params.type,
|
||||
}).catch((err) => {
|
||||
isToastOpen.value = {
|
||||
open: true,
|
||||
@ -53,8 +58,13 @@ const submit = async (e, type = "") => {
|
||||
} else {
|
||||
const res = await getHistoryData({
|
||||
...searchParams.value,
|
||||
Type: 1,
|
||||
table_type:route.params.type
|
||||
Type:
|
||||
route.params.type != 1
|
||||
? 2
|
||||
: searchParams.value.Type
|
||||
? searchParams.value.Type
|
||||
: 1,
|
||||
table_type: route.params.type,
|
||||
});
|
||||
updateTableData(res.data);
|
||||
}
|
||||
@ -82,7 +92,7 @@ const submitBtns = computed(() => [
|
||||
disabled: isSearchButtonDisabled.value,
|
||||
},
|
||||
{
|
||||
title: t("button.export"),
|
||||
title: t("button.export"),
|
||||
key: "export",
|
||||
icon: "download",
|
||||
btn: "btn-export",
|
||||
@ -115,13 +125,18 @@ watch(
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Toast
|
||||
<Toast
|
||||
:content="isToastOpen.content"
|
||||
:open="isToastOpen.open"
|
||||
status="info"
|
||||
:cancel="cancelToastOpen"
|
||||
/>
|
||||
<ButtonGroup class="ml-5" :items="submitBtns" :withLine="false" :withBtnClass="true"/>
|
||||
<ButtonGroup
|
||||
class="ml-5"
|
||||
:items="submitBtns"
|
||||
:withLine="false"
|
||||
:withBtnClass="true"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
|
@ -3,8 +3,10 @@ import { inject, computed, ref, watch } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import LineChart from "@/components/chart/LineChart.vue";
|
||||
import { SECOND_CHART_COLOR } from "@/constant";
|
||||
import useSearchParam from "@/hooks/useSearchParam";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
const { searchParams } = useSearchParam();
|
||||
const { t } = useI18n();
|
||||
const { tableData } = inject("energy_table_data");
|
||||
const history_chart = ref(null);
|
||||
@ -46,7 +48,10 @@ const defaultChartOption = {
|
||||
splitLine: { show: false },
|
||||
axisLabel: {
|
||||
color: "#ffffff",
|
||||
formatter: (value) => dayjs(value).format("HH:mm"), // 格式化為時間
|
||||
formatter: (value) =>
|
||||
searchParams.value.Type == 2
|
||||
? dayjs(value).format("HH:mm")
|
||||
: dayjs(value).format("MM-DD"), // 格式化為時間
|
||||
},
|
||||
data: [],
|
||||
},
|
||||
@ -54,6 +59,10 @@ const defaultChartOption = {
|
||||
type: "value",
|
||||
splitLine: { show: false },
|
||||
axisLabel: { color: "#ffffff" },
|
||||
// interval: 100, //Y 軸的刻度間隔
|
||||
min: "dataMin",
|
||||
max: "dataMax",
|
||||
// splitArea: { show: false },
|
||||
},
|
||||
series: [],
|
||||
};
|
||||
@ -64,7 +73,7 @@ const formatChartData = (data) => {
|
||||
const seriesKey = `${item.device_name || ""}_${item.item_name || ""}`;
|
||||
acc[seriesKey] = {
|
||||
timestamps: item.data.map((d) =>
|
||||
dayjs(d.timestamp).format("YYYY-MM-DD HH:mm")
|
||||
dayjs(d.time).format("YYYY-MM-DD HH:mm")
|
||||
),
|
||||
values: item.data.map((d) =>
|
||||
d.value == "無資料" ? null : parseFloat(d.value)
|
||||
@ -73,7 +82,6 @@ const formatChartData = (data) => {
|
||||
minValue: parseFloat(item.minValue),
|
||||
averageValue: parseFloat(item.averageValue),
|
||||
};
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
};
|
||||
@ -86,8 +94,7 @@ watch(
|
||||
const formattedData = formatChartData(newData);
|
||||
|
||||
const series = Object.keys(formattedData).map((seriesKey, index) => {
|
||||
const { maxValue, minValue, averageValue } =
|
||||
formattedData[seriesKey];
|
||||
const { maxValue, minValue, averageValue } = formattedData[seriesKey];
|
||||
return {
|
||||
name: seriesKey,
|
||||
type: "line",
|
||||
|
@ -132,7 +132,7 @@ const columns = computed(() => {
|
||||
key: "endTime",
|
||||
},
|
||||
{
|
||||
title: t("energy.ectricity_classification"),
|
||||
title: t("energy.electricity_classification"),
|
||||
key: "elecType",
|
||||
},
|
||||
{
|
||||
|
@ -14,9 +14,11 @@ import EnergyActionButton from "./EnergyActionButton.vue";
|
||||
import EnergySearchTime from "./EnergySearchTime.vue";
|
||||
import useActiveBtn from "@/hooks/useActiveBtn";
|
||||
import { getEnergySearch } from "@/apis/energy";
|
||||
import { getDepartmentList, getElecTypeList } from "@/apis/asset";
|
||||
import { getElecTypeList } from "@/apis/asset";
|
||||
import useSearchParam from "@/hooks/useSearchParam";
|
||||
import dayjs from "dayjs";
|
||||
import useBuildingStore from "@/stores/useBuildingStore";
|
||||
|
||||
const storeBuild = useBuildingStore();
|
||||
const { deptData, elecType, subSystem } = inject("energy_table_data");
|
||||
const { searchParams, changeParams } = useSearchParam();
|
||||
const route = useRoute();
|
||||
@ -57,24 +59,13 @@ const {
|
||||
selectedBtn: selectedPoints,
|
||||
} = useActiveBtn("multiple");
|
||||
|
||||
const getDepartment = async () => {
|
||||
const res = await getDepartmentList();
|
||||
const dept = res.data.map((d, index) => ({
|
||||
...d,
|
||||
title: d.name,
|
||||
key: d.id,
|
||||
active: false,
|
||||
}));
|
||||
setDeptItems(dept);
|
||||
};
|
||||
|
||||
const getElecType = async () => {
|
||||
const res = await getElecTypeList();
|
||||
const elecType = res.data.map((d, index) => ({
|
||||
...d,
|
||||
title: d.name,
|
||||
key: d.id,
|
||||
active: false,
|
||||
active: true,
|
||||
}));
|
||||
setElecTypeItems(elecType);
|
||||
};
|
||||
@ -161,8 +152,23 @@ watch(
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
() => storeBuild.deptList,
|
||||
(newValue) => {
|
||||
if (newValue) {
|
||||
const deptList = newValue.map((d) => ({
|
||||
...d,
|
||||
active: true,
|
||||
}));
|
||||
setDeptItems(deptList);
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
}
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
getDepartment();
|
||||
getElecType();
|
||||
});
|
||||
</script>
|
||||
|
@ -4,14 +4,40 @@ import useSearchParam from "@/hooks/useSearchParam";
|
||||
import dayjs from "dayjs";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useRoute } from "vue-router";
|
||||
import useActiveBtn from "@/hooks/useActiveBtn";
|
||||
|
||||
const { t, locale } = useI18n();
|
||||
const { searchParams, changeParams } = useSearchParam();
|
||||
const route = useRoute();
|
||||
const {
|
||||
items: searchTypeItems,
|
||||
changeActiveBtn: changeTypeActiveBtn,
|
||||
setItems: setTypeItems,
|
||||
selectedBtn: selectedTypeItems,
|
||||
} = useActiveBtn();
|
||||
|
||||
const itemsForStartTime = ref([]);
|
||||
|
||||
const itemsForEndTime = ref();
|
||||
|
||||
const initializeItems = () => {
|
||||
setTypeItems([
|
||||
{
|
||||
title: t("history.date_range"),
|
||||
key: 1,
|
||||
active: searchParams.value.Type
|
||||
? parseInt(searchParams.value.Type) === 1
|
||||
: true,
|
||||
},
|
||||
{
|
||||
title: t("history.time_range"),
|
||||
key: 2,
|
||||
active: searchParams.value.Type
|
||||
? parseInt(searchParams.value.Type) === 2
|
||||
: false,
|
||||
},
|
||||
]);
|
||||
|
||||
itemsForStartTime.value = [
|
||||
{
|
||||
key: "Start_date",
|
||||
@ -56,10 +82,10 @@ const initializeItems = () => {
|
||||
watch(
|
||||
() => route.params.type,
|
||||
() => {
|
||||
initializeItems();
|
||||
initializeItems();
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
immediate: true,
|
||||
}
|
||||
);
|
||||
|
||||
@ -101,6 +127,13 @@ watch(
|
||||
deep: true,
|
||||
}
|
||||
);
|
||||
|
||||
watch(selectedTypeItems, (newValue) => {
|
||||
changeParams({
|
||||
...searchParams.value,
|
||||
Type: newValue.key,
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@ -108,6 +141,16 @@ watch(
|
||||
<h2 class="text-lg font-bold ps-2 whitespace-nowrap">
|
||||
{{ $t("history.date_range") }} :
|
||||
</h2>
|
||||
<ButtonGroup
|
||||
v-if="route.params.type == 1"
|
||||
:items="searchTypeItems"
|
||||
:withLine="true"
|
||||
:onclick="
|
||||
(e, item) => {
|
||||
changeTypeActiveBtn(item);
|
||||
}
|
||||
"
|
||||
/>
|
||||
<DateGroup class="mr-3" :items="itemsForStartTime" :withLine="true" />
|
||||
<DateGroup :items="itemsForEndTime" :withLine="true" />
|
||||
</div>
|
||||
|
@ -1,18 +1,21 @@
|
||||
<script setup>
|
||||
import Checkbox from "@/components/customUI/Checkbox.vue";
|
||||
import { computed, ref, watch, inject } from "vue";
|
||||
import { useRoute } from "vue-router";
|
||||
import useBuildingStore from "@/stores/useBuildingStore";
|
||||
import useSearchParam from "@/hooks/useSearchParam";
|
||||
import { getHistorySideBar } from "@/apis/history";
|
||||
import { useI18n } from "vue-i18n";
|
||||
const { t } = useI18n();
|
||||
const route = useRoute();
|
||||
const storeBuild = useBuildingStore();
|
||||
const buildingGuid = computed(() => storeBuild.selectedBuilding?.building_guid);
|
||||
const { searchParams, changeParams } = useSearchParam();
|
||||
const { deptData, elecType, subSystem } = inject("energy_table_data");
|
||||
const selectedBuilding = ref([]);
|
||||
const deviceData = ref([]);
|
||||
const searchTerm = ref(""); //搜尋文字
|
||||
const activeSearchTerm = ref("");
|
||||
|
||||
const getDeviceData = async ({
|
||||
sub_system_tag,
|
||||
department_id,
|
||||
@ -21,7 +24,8 @@ const getDeviceData = async ({
|
||||
const res = await getHistorySideBar({
|
||||
sub_system_tag,
|
||||
department_id,
|
||||
elec_type_id,
|
||||
elec_type_id: route.params.type == 3 ? elec_type_id : [],
|
||||
building_guid: buildingGuid.value,
|
||||
});
|
||||
deviceData.value = (res.data || []).map((building) => ({
|
||||
building_tag: building.building_tag,
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user