設定 : 新增棟別、時間電價、MQTT解析 | 能源管理: 即時需量、碳排當量新增

This commit is contained in:
koko 2025-02-21 15:57:56 +08:00
parent 80fcfda16c
commit c8d8fbf254
25 changed files with 2281 additions and 129 deletions

453
package-lock.json generated
View File

@ -22,6 +22,7 @@
"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 +1153,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 +1297,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 +1383,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 +1461,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 +1519,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 +1668,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 +1899,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 +2090,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 +2239,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 +2491,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 +2528,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 +2573,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",
@ -2570,6 +2655,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 +2754,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 +2808,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 +2966,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 +3157,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 +3190,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 +3254,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 +3267,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 +3333,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 +3604,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 +3855,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 +3984,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 +4030,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 +4337,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 +4371,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 +4565,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 +4744,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 +5119,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 +5744,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 +5780,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 +6008,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 +6033,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 +6052,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 +6305,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",

View File

@ -23,6 +23,7 @@
"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",

View File

@ -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`;

View File

@ -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,

View File

@ -8,3 +8,15 @@ 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_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`;

View File

@ -6,6 +6,12 @@ import {
GET_SEARCH_API,
GET_REPORT_API,
GET_Excel_API,
GET_DEMAND_API,
POST_EDIT_DEMAND_API,
GET_CARBON_API,
POST_EDIT_CARBON_API,
GET_TIME_ELEC_API,
POST_TIME_ELEC_API
} from "./api";
import instance, { fileInstance } from "@/util/request";
import apihandler from "@/util/apihandler";
@ -110,3 +116,71 @@ export const getExcel = async ({
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 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,
});
};

View File

@ -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>

View File

@ -6,7 +6,6 @@ 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];
@ -33,10 +32,10 @@ onMounted(() => {
</div>
<ul
tabindex="0"
class="dropdown-content left-8 translate-y-2 z-[1] menu py-3 shadow rounded bg-[#4c625e] border text-center"
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"
class="text-white my-1 text-base cursor-pointer"
v-for="bui in store.buildings"
:key="bui.building_tag"
@click="selectBuilding(bui)"

View File

@ -98,7 +98,10 @@
"ranking": "排名",
"subtotal": "小计",
"unit_price": "单价",
"total_amount": "金额总计"
"total_amount": "金额总计",
"elec_price_list": "电价表",
"residential": "住宅型",
"standard": "标准型"
},
"alarm": {
"title": "显示警告",
@ -274,7 +277,8 @@
"index": "编号",
"floor_plan": "平面图",
"department": "部门",
"department_name": "部门名称"
"department_name": "部门名称",
"building": "栋别"
},
"accountManagement": {
"account_title": "帐号管理",

View File

@ -98,7 +98,10 @@
"ranking": "排名",
"subtotal": "小計",
"unit_price": "單價",
"total_amount": "金額總計"
"total_amount": "金額總計",
"elec_price_list": "電價表",
"residential": "住宅型",
"standard": "標準型"
},
"alarm": {
"title": "顯示警告",
@ -274,7 +277,8 @@
"index": "編號",
"floor_plan": "平面圖",
"department": "部門",
"department_name": "部門名稱"
"department_name": "部門名稱",
"building": "棟別"
},
"accountManagement": {
"account_title": "帳號管理",

View File

@ -98,7 +98,10 @@
"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"
},
"alarm": {
"title": "Warning",
@ -274,7 +277,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",

View File

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

View File

@ -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 carbonData = ref(null);
const defaultChartOption = ref({
tooltip: {
trigger: "axis",
@ -79,23 +82,22 @@ 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];
}
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 +111,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">

View File

@ -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,21 +40,29 @@ 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>
@ -67,17 +73,16 @@ const closeModal = () => {
<Modal
id="carbon_emission_item"
:title="t('energy.edit_carbon_emission')"
:open="open"
:onCancel="closeModal"
width="300"
>
<template #modalContent>
<form ref="form" class="mt-5 flex flex-col items-center">
<Input :value="formState" class="w-full" name="factor">
<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>

View File

@ -1,11 +1,14 @@
<script setup>
import LineChart from "@/components/chart/LineChart.vue";
import { ref, onMounted } from "vue";
import { ref, onMounted,watch } from "vue";
import ImmediateDemandModal from "./ImmediateDemandModal.vue";
import { getDemand } from "@/apis/energy";
import { useI18n } from "vue-i18n";
import useBuildingStore from "@/stores/useBuildingStore";
const store = useBuildingStore();
const { t } = useI18n();
const editRecord = ref(null);
const demandData = ref(null);
//
const data = {
categories: [
@ -111,27 +114,22 @@ const defaultChartOption = ref({
series: data.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];
}
immediate_demand_add_item.showModal();
};
const onCancel = () => {
editRecord.value = null;
immediate_demand_add_item.close();
};
const fetchData = async () => {
console.log("ok");
};
onMounted(() => {
//
});
watch(
() => store.selectedBuilding,
(newBuilding) => {
if (newBuilding) {
getData();
}
},
{ immediate: true }
);
</script>
<template>
@ -145,12 +143,7 @@ onMounted(() => {
{{ $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"

View File

@ -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"
>
<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 }}

View File

@ -6,6 +6,8 @@ import Dept from "./components/Dept.vue";
import ElecType from "./components/ElecType.vue";
import Vendor from "./components/Vendor.vue";
import Floors from "./components/Floors.vue";
import Building from "./components/Building.vue";
import ElecPriceManagement from "./components/ElecPriceManagement.vue";
import MITTList from "./components/MITTList.vue";
const route = useRoute();
@ -14,22 +16,20 @@ const currentComponent = ref(null);
const updateComponent = () => {
const { main_system_id, sub_system_id } = route.params;
if (main_system_id === "Setting") {
if (sub_system_id === "Department") {
currentComponent.value = Dept;
} else if(sub_system_id === "ElecType") {
currentComponent.value = ElecType;
} else if(sub_system_id === "Vendor") {
currentComponent.value = Vendor;
} else if(sub_system_id === "Floor") {
currentComponent.value = Floors;
}else{
currentComponent.value = MITTList;
}
} else if (main_system_id === "MQTT") {
if (sub_system_id === "Department") {
currentComponent.value = Dept;
} else if (sub_system_id === "ElecType") {
currentComponent.value = ElecType;
} else if (sub_system_id === "Vendor") {
currentComponent.value = Vendor;
} else if (sub_system_id === "Floor") {
currentComponent.value = Floors;
} else if (sub_system_id === "Building") {
currentComponent.value = Building;
} else if (sub_system_id === "ElecPricing") {
currentComponent.value = ElecPriceManagement;
} else if (sub_system_id === "MQTT_Result") {
currentComponent.value = MITTList;
} else {
currentComponent.value = null;
}
};

View File

@ -0,0 +1,107 @@
<script setup>
import Table from "@/components/customUI/Table.vue";
import BuildingModal from "./BuildingModal.vue";
import { getBuildings, deleteBuildings } from "@/apis/building";
import { onMounted, ref, inject, computed } from "vue";
import { useI18n } from "vue-i18n";
import useBuildingStore from "@/stores/useBuildingStore";
const store = useBuildingStore();
const { t } = useI18n();
const { openToast, cancelToastOpen } = inject("app_toast");
const columns = computed(() => [
{
title: t("accountManagement.index"),
key: "index",
},
{
title: t("assetManagement.building"),
key: "full_name",
filter: true,
},
{
title: t("accountManagement.operation"),
key: "operation",
},
]);
const dataSource = ref([]);
const loading = ref(false);
const getDataSource = async () => {
loading.value = true;
const res = await getBuildings();
store.buildings = res.data;
dataSource.value = res.data.map((d) => ({ ...d, key: d.building_guid }));
loading.value = false;
};
onMounted(() => {
getDataSource();
});
const formState = ref({
full_name: "",
});
const openModal = (record) => {
if (record.building_guid) {
formState.value = { ...record };
} else {
formState.value = {
full_name: "",
};
}
build_modal.showModal();
};
const remove = async (building_guid) => {
openToast("warning", t("msg.sure_to_delete"), "body", async () => {
await cancelToastOpen();
const res = await deleteBuildings(building_guid);
if (res.isSuccess) {
getDataSource();
openToast("success", t("msg.delete_success"));
} else {
openToast("error", res.msg);
}
});
};
</script>
<template>
<div class="flex justify-start items-center mt-10 mb-5">
<h3 class="text-xl mr-5">{{ $t("assetManagement.building") }}</h3>
<BuildingModal
:formState="formState"
:getData="getDataSource"
:openModal="openModal"
/>
</div>
<Table :columns="columns" :dataSource="dataSource" :loading="loading">
<template #bodyCell="{ record, column, index }">
<template v-if="column.key === 'index'">{{ index + 1 }}</template>
<template v-else-if="column.key === 'operation'">
<button
class="btn btn-sm btn-success text-white mr-2"
@click.stop.prevent="() => openModal(record)"
>
{{ $t("button.edit") }}
</button>
<button
class="btn btn-sm btn-error text-white"
@click.stop.prevent="() => remove(record.building_guid)"
>
{{ $t("button.delete") }}
</button>
</template>
<template v-else>
{{ record[column.key] }}
</template>
</template>
</Table>
</template>
<style lang="css" scoped></style>

View File

@ -0,0 +1,84 @@
<script setup>
import { ref, onMounted, defineProps, inject, watch } from "vue";
import * as yup from "yup";
import "yup-phone-lite";
import useFormErrorMessage from "@/hooks/useFormErrorMessage";
import { postBuildings } from "@/apis/building";
import { useI18n } from "vue-i18n";
const { t } = useI18n();
const { openToast } = inject("app_toast");
const props = defineProps({
formState: Object,
getData: Function,
openModal: Function
});
const buildScheme = yup.object({
full_name: yup.string().required(t("button.required")),
});
const { formErrorMsg, handleSubmit, handleErrorReset, updateScheme } =
useFormErrorMessage(buildScheme);
const onCancel = () => {
handleErrorReset();
build_modal.close();
};
const onOk = async () => {
const value = await handleSubmit(buildScheme, props.formState);
const res = await postBuildings(value);
if (res.isSuccess) {
props.getData();
onCancel();
} else {
openToast("error", res.msg, "#build_modal");
}
};
</script>
<template>
<button class="btn btn-sm btn-add " @click.stop.prevent="props.openModal">
<font-awesome-icon :icon="['fas', 'plus']" />{{ $t("button.add") }}
</button>
<Modal
id="build_modal"
:title="props.formState?.building_guid ? t('button.edit') : t('button.add')"
:onCancel="onCancel"
width="400"
>
<template #modalContent>
<form ref="form" class="mt-5 w-full flex flex-wrap justify-between">
<Input :value="formState" class="my-2" name="full_name">
<template #topLeft>{{
$t("assetManagement.building")
}}</template>
<template #bottomLeft
><span class="text-error text-base">
{{ formErrorMsg.full_name }}
</span></template
></Input
>
</form>
</template>
<template #modalAction>
<button
type="reset"
class="btn btn-outline-success mr-2"
@click.prevent="onCancel"
>
{{ $t("button.cancel") }}
</button>
<button
type="submit"
class="btn btn-outline-success"
@click.stop.prevent="onOk"
>
{{ $t("button.submit") }}
</button>
</template>
</Modal>
</template>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,105 @@
<script setup>
import ButtonGroup from "@/components/customUI/ButtonGroup.vue";
import ElecPriceRes from "./ElecPriceRes.vue";
import ElecPriceStd from "./ElecPriceStd.vue";
import { computed, watch, onBeforeMount, ref, provide } from "vue";
import useActiveBtn from "@/hooks/useActiveBtn";
import { useI18n } from "vue-i18n";
import { getTimeElec } from "@/apis/energy";
import useBuildingStore from "@/stores/useBuildingStore";
const store = useBuildingStore();
const { t, locale } = useI18n();
const sim2 = ref([]);
const sim3 = ref([]);
const stand2 = ref([]);
const stand3 = ref([]);
const changeComponent = (e, item) => {
changeActiveBtn(item);
};
const { items, changeActiveBtn, setItems } = useActiveBtn();
const initializeItems = () => {
setItems([
{
title: t("energy.residential"),
key: "Residential",
active: true,
component: ElecPriceRes,
},
{
title: t("energy.standard"),
key: "Standard",
active: false,
component: ElecPriceStd,
},
]);
};
onBeforeMount(() => {
initializeItems();
});
const activeTab = computed(() => {
return items.value.find(({ active }) => active);
});
const getData = async () => {
if (store.selectedBuilding.building_guid) {
const res = await getTimeElec(store.selectedBuilding.building_guid);
// ref
sim2.value = [];
sim3.value = [];
stand2.value = [];
stand3.value = [];
res.data.forEach((item) => {
switch (item.sheet) {
case "sim2":
sim2.value = item.data;
break;
case "sim3":
sim3.value = item.data;
break;
case "stand2":
stand2.value = item.data;
break;
case "stand3":
stand3.value = item.data;
break;
default:
console.warn(`Unknown sheet: ${item.sheet}`);
}
});
}
};
watch(
() => store.selectedBuilding,
(newBuilding) => {
if (newBuilding) {
getData();
}
},
{ immediate: true }
);
provide("time_elec", { sim2, sim3, stand2, stand3, getData });
</script>
<template>
<h1 class="text-2xl font-extrabold mb-2">
{{ $t("energy.elec_price_list") }}
</h1>
<ButtonGroup
:items="items"
:withLine="true"
class="my-6"
:onclick="changeComponent"
/>
<component :is="activeTab.component"></component>
</template>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,366 @@
<script setup>
import { computed, inject, watch, ref } from "vue";
import { useI18n } from "vue-i18n";
import { postTimeElec } from "@/apis/energy";
import useBuildingStore from "@/stores/useBuildingStore";
const store = useBuildingStore();
const { t } = useI18n();
const { sim2, sim3, getData } = inject("time_elec");
const sim2isEditing = ref(false);
const sim3isEditing = ref(false);
const sim2NewValue = ref([]);
const sim3NewValue = ref([]);
const onOk = async (sheet, data) => {
const res = await postTimeElec({
sheet: sheet,
cost: data,
building_guid: store.selectedBuilding.building_guid,
});
if (res.isSuccess) {
getData();
onCancel(sheet);
} else {
openToast("error", res.msg, "#immediate_demand_add_item");
}
};
const onCancel = (sheet) => {
if (sheet === "sim2") {
sim2isEditing.value = false;
sim2NewValue.value = [...sim2.value];
} else if (sheet === "sim3") {
sim3isEditing.value = false;
sim3NewValue.value = [...sim3.value];
}
};
watch(
sim2,
(newValue, oldValue) => {
sim2NewValue.value = [...newValue];
},
{ immediate: true }
);
watch(
sim3,
(newValue, oldValue) => {
sim3NewValue.value = [...newValue];
},
{ immediate: true }
);
</script>
<template>
<div class="flex justify-start items-center my-5">
<h3 class="text-xl mr-5">簡易型時間電價二段式</h3>
<button
v-if="!sim2isEditing"
class="btn btn-sm btn-add mr-3"
@click.stop.prevent="sim2isEditing = true"
>
<font-awesome-icon :icon="['fas', 'pencil-alt']" />{{
$t("button.start_edit")
}}
</button>
<template v-else>
<button
class="btn btn-sm btn-add mr-3"
@click.prevent="onOk('sim2', sim2NewValue)"
>
<font-awesome-icon :icon="['fas', 'save']" />{{ $t("button.confirm") }}
</button>
<button
class="btn btn-sm btn-outline-info mr-3"
@click.stop.prevent="onCancel('sim2')"
>
<font-awesome-icon :icon="['fas', 'times']" />{{ $t("button.cancel") }}
</button>
</template>
</div>
<table class="">
<thead>
<tr>
<th colspan="6" class="bg-teal-800 bg-opacity-20">分類</th>
<th class="bg-teal-800 bg-opacity-20">夏月<br />(6/1~9/30)</th>
<th class="bg-teal-800 bg-opacity-20">非夏月<br />(夏月以外的時間)</th>
</tr>
</thead>
<tbody>
<tr>
<td class="bg-teal-800 bg-opacity-40">基本電費</td>
<td colspan="4" class="bg-teal-800 bg-opacity-40">按戶計收</td>
<td class="bg-teal-800 bg-opacity-40">每戶每月</td>
<td colspan="2" class="bg-teal-800 bg-opacity-40">
<input
type="number"
v-model.number="sim2NewValue[0]"
:readonly="!sim2isEditing"
/>
</td>
</tr>
<tr>
<td rowspan="6">流動電費</td>
<td rowspan="4">週一~週五</td>
<td rowspan="2" class="bg-rose-600 bg-opacity-70">尖峰時間</td>
<td class="bg-rose-600 bg-opacity-20">夏月</td>
<td class="bg-rose-600 bg-opacity-20">09:00 ~ 24:00</td>
<td rowspan="5">每度</td>
<td class="bg-rose-600 bg-opacity-20">
<input
type="number"
v-model.number="sim2NewValue[1]"
:readonly="!sim2isEditing"
/>
</td>
<td class="bg-rose-600 bg-opacity-20">-</td>
</tr>
<tr>
<td class="bg-rose-600 bg-opacity-20">非夏月</td>
<td class="bg-rose-600 bg-opacity-20">
06:00 ~ 11:00<br />14:00 ~ 24:00
</td>
<td class="bg-rose-600 bg-opacity-20">-</td>
<td class="bg-rose-600 bg-opacity-20">
<input
type="number"
v-model.number="sim2NewValue[2]"
:readonly="!sim2isEditing"
/>
</td>
</tr>
<tr>
<td rowspan="2" class="bg-green-600 bg-opacity-60">離峰時間</td>
<td class="bg-green-600 bg-opacity-20">夏月</td>
<td class="bg-green-600 bg-opacity-20">00:00 ~ 09:00</td>
<td class="bg-green-600 bg-opacity-20">
<input
type="number"
v-model.number="sim2NewValue[3]"
:readonly="!sim2isEditing"
/>
</td>
<td class="bg-green-600 bg-opacity-20">-</td>
</tr>
<tr>
<td class="bg-green-600 bg-opacity-20">非夏月</td>
<td class="bg-green-600 bg-opacity-20">
00:00 ~ 06:00<br />11:00 ~ 14:00
</td>
<td class="bg-green-600 bg-opacity-20">-</td>
<td class="bg-green-600 bg-opacity-20">
<input
type="number"
v-model.number="sim2NewValue[4]"
:readonly="!sim2isEditing"
/>
</td>
</tr>
<tr>
<td>週六週日<br />及離峰日</td>
<td class="bg-green-600 bg-opacity-60">離峰時間</td>
<td colspan="2" class="bg-green-600 bg-opacity-20">全日</td>
<td class="bg-green-600 bg-opacity-20">
<input
type="number"
v-model.number="sim2NewValue[5]"
:readonly="!sim2isEditing"
/>
</td>
<td class="bg-green-600 bg-opacity-20">
<input
type="number"
v-model.number="sim2NewValue[6]"
:readonly="!sim2isEditing"
/>
</td>
</tr>
<tr>
<td colspan="4">每月總度數超過2000度之部分</td>
<td>每度</td>
<td colspan="2" class="!text-start">
<div class="flex items-center">
<input
type="number"
v-model.number="sim2NewValue[7]"
:readonly="!sim2isEditing"
/>
</div>
</td>
</tr>
</tbody>
</table>
<div class="flex justify-start items-center mt-16">
<h3 class="text-xl mr-5">簡易型時間電價三段式</h3>
<button
v-if="!sim3isEditing"
class="btn btn-sm btn-add mr-3"
@click.stop.prevent="sim3isEditing = true"
>
<font-awesome-icon :icon="['fas', 'pencil-alt']" />{{
$t("button.start_edit")
}}
</button>
<template v-else>
<button
class="btn btn-sm btn-add mr-3"
@click.prevent="onOk('sim3', sim3NewValue)"
>
<font-awesome-icon :icon="['fas', 'save']" />{{ $t("button.confirm") }}
</button>
<button
class="btn btn-sm btn-outline-info mr-3"
@click.stop.prevent="onCancel('sim3')"
>
<font-awesome-icon :icon="['fas', 'times']" />{{ $t("button.cancel") }}
</button>
</template>
</div>
<table class="my-5">
<thead>
<tr>
<th colspan="6" class="bg-teal-800 bg-opacity-20">分類</th>
<th class="bg-teal-800 bg-opacity-20">夏月<br />(6/1~9/30)</th>
<th class="bg-teal-800 bg-opacity-20">非夏月<br />(夏月以外的時間)</th>
</tr>
</thead>
<tbody>
<tr>
<td class="bg-teal-800 bg-opacity-40">基本電費</td>
<td colspan="4" class="bg-teal-800 bg-opacity-40">按戶計收</td>
<td class="bg-teal-800 bg-opacity-40">每戶每月</td>
<td colspan="2" class="bg-teal-800 bg-opacity-40">
<input
type="number"
v-model.number="sim3NewValue[0]"
:readonly="!sim3isEditing"
/>
</td>
</tr>
<tr>
<td rowspan="7">流動電費</td>
<td rowspan="5">週一~週五</td>
<td class="bg-rose-600 bg-opacity-70">尖峰時間</td>
<td class="bg-rose-600 bg-opacity-20">夏月</td>
<td class="bg-rose-600 bg-opacity-20">16:00 ~ 22:00</td>
<td rowspan="6">每度</td>
<td class="bg-rose-600 bg-opacity-20">
<input
type="number"
v-model.number="sim3NewValue[1]"
:readonly="!sim3isEditing"
/>
</td>
<td class="bg-rose-600 bg-opacity-20">-</td>
</tr>
<tr>
<td rowspan="2" class="bg-yellow-400 bg-opacity-80">半尖峰時間</td>
<td class="bg-yellow-500 bg-opacity-20">夏月</td>
<td class="bg-yellow-500 bg-opacity-20">
09:00 ~ 16:00<br />22:00~24:00
</td>
<td class="bg-yellow-500 bg-opacity-20">
<input
type="number"
v-model.number="sim3NewValue[2]"
:readonly="!sim3isEditing"
/>
</td>
<td class="bg-yellow-500 bg-opacity-20">-</td>
</tr>
<tr>
<td class="bg-yellow-500 bg-opacity-20">非夏月</td>
<td class="bg-yellow-500 bg-opacity-20">
06:00 ~ 11:00<br />14:00 ~ 24:00
</td>
<td class="bg-yellow-500 bg-opacity-20">-</td>
<td class="bg-yellow-500 bg-opacity-20">
<input
type="number"
v-model.number="sim3NewValue[3]"
:readonly="!sim3isEditing"
/>
</td>
</tr>
<tr>
<td rowspan="2" class="bg-green-600 bg-opacity-60">離峰時間</td>
<td class="bg-green-600 bg-opacity-20">夏月</td>
<td class="bg-green-600 bg-opacity-20">00:00 ~ 09:00</td>
<td class="bg-green-600 bg-opacity-20">
<input
type="number"
v-model.number="sim3NewValue[4]"
:readonly="!sim3isEditing"
/>
</td>
<td class="bg-green-600 bg-opacity-20">-</td>
</tr>
<tr>
<td class="bg-green-600 bg-opacity-20">非夏月</td>
<td class="bg-green-600 bg-opacity-20">
00:00 ~ 06:00<br />11:00 ~ 14:00
</td>
<td class="bg-green-600 bg-opacity-20">-</td>
<td class="bg-green-600 bg-opacity-20">
<input
type="number"
v-model.number="sim3NewValue[5]"
:readonly="!sim3isEditing"
/>
</td>
</tr>
<tr>
<td>週六週日<br />及離峰日</td>
<td colspan="2" class="bg-green-600 bg-opacity-60">離峰時間</td>
<td class="bg-green-600 bg-opacity-20">全日</td>
<td class="bg-green-600 bg-opacity-20">
<input
type="number"
v-model.number="sim3NewValue[6]"
:readonly="!sim3isEditing"
/>
</td>
<td class="bg-green-600 bg-opacity-20">
<input
type="number"
v-model.number="sim3NewValue[7]"
:readonly="!sim3isEditing"
/>
</td>
</tr>
<tr>
<td colspan="4">每月總度數超過2000度之部分</td>
<td>每度</td>
<td colspan="2" class="!text-start">
<div class="flex items-center">
<input
type="number"
v-model.number="sim3NewValue[8]"
:readonly="!sim3isEditing"
/>
</div>
</td>
</tr>
</tbody>
</table>
</template>
<style lang="scss" scoped>
th,
td {
text-align: center;
border: 1px solid #ddd;
padding: 0.75rem;
text-align: center;
font-size: 1.125rem;
line-height: 1.75rem;
font-weight: 600;
}
input {
@apply text-lg text-white w-full bg-transparent input input-bordered rounded-md px-3 border-info focus-within:border-info read-only:text-white read-only:border-0 read-only:focus-within:outline-0 read-only:focus:outline-0;
}
</style>

View File

@ -0,0 +1,597 @@
<script setup>
import { computed, inject, watch, ref } from "vue";
import { useI18n } from "vue-i18n";
import { postTimeElec } from "@/apis/energy";
import useBuildingStore from "@/stores/useBuildingStore";
const store = useBuildingStore();
const { t } = useI18n();
const { stand2, stand3, getData } = inject("time_elec");
const stand2isEditing = ref(false);
const stand3isEditing = ref(false);
const stand2NewValue = ref([]);
const stand3NewValue = ref([]);
const onOk = async (sheet, data) => {
const res = await postTimeElec({
sheet: sheet,
cost: data,
building_guid: store.selectedBuilding.building_guid,
});
if (res.isSuccess) {
getData();
onCancel(sheet);
} else {
openToast("error", res.msg, "#immediate_demand_add_item");
}
};
const onCancel = (sheet) => {
if (sheet === "stand2") {
stand2isEditing.value = false;
stand2NewValue.value = [...stand2.value];
} else if (sheet === "stand3") {
stand3isEditing.value = false;
stand3NewValue.value = [...stand3.value];
}
};
watch(
stand2,
(newValue, oldValue) => {
stand2NewValue.value = [...newValue];
},
{ immediate: true }
);
watch(
stand3,
(newValue, oldValue) => {
stand3NewValue.value = [...newValue];
},
{ immediate: true }
);
</script>
<template>
<div class="flex justify-start items-center my-5">
<h3 class="text-xl mr-5">標準型時間電價二段式</h3>
<button
v-if="!stand2isEditing"
class="btn btn-sm btn-add mr-3"
@click.stop.prevent="stand2isEditing = true"
>
<font-awesome-icon :icon="['fas', 'pencil-alt']" />{{
$t("button.start_edit")
}}
</button>
<template v-else>
<button
class="btn btn-sm btn-add mr-3"
@click.prevent="onOk('stand2', stand2NewValue)"
>
<font-awesome-icon :icon="['fas', 'save']" />{{ $t("button.confirm") }}
</button>
<button
class="btn btn-sm btn-outline-info mr-3"
@click.stop.prevent="onCancel('stand2')"
>
<font-awesome-icon :icon="['fas', 'times']" />{{ $t("button.cancel") }}
</button>
</template>
</div>
<table class="">
<thead>
<tr>
<th colspan="6" class="bg-teal-800 bg-opacity-20">分類</th>
<th class="bg-teal-800 bg-opacity-20">夏月<br />(6/1~9/30)</th>
<th class="bg-teal-800 bg-opacity-20">非夏月<br />(夏月以外的時間)</th>
</tr>
</thead>
<tbody>
<tr>
<td rowspan="6" class="bg-teal-800 bg-opacity-40">基本電費</td>
<td colspan="2" rowspan="2" class="bg-teal-800 bg-opacity-40">
按戶計收
</td>
<td colspan="2" class="bg-teal-800 bg-opacity-40">單相</td>
<td rowspan="2" class="bg-teal-800 bg-opacity-40">每戶每月</td>
<td colspan="2" class="bg-teal-800 bg-opacity-40">
<input
type="number"
v-model.number="stand2NewValue[0]"
:readonly="!stand2isEditing"
/>
</td>
</tr>
<tr>
<td colspan="2" class="bg-teal-800 bg-opacity-40">三相</td>
<td colspan="2" class="bg-teal-800 bg-opacity-40">
<input
type="number"
v-model.number="stand2NewValue[1]"
:readonly="!stand2isEditing"
/>
</td>
</tr>
<tr>
<td colspan="4" class="bg-teal-800 bg-opacity-40">經常契約</td>
<td rowspan="4" class="bg-teal-800 bg-opacity-40">每瓩每月</td>
<td class="bg-teal-800 bg-opacity-40">
<input
type="number"
v-model.number="stand2NewValue[2]"
:readonly="!stand2isEditing"
/>
</td>
<td class="bg-teal-800 bg-opacity-40">
<input
type="number"
v-model.number="stand2NewValue[3]"
:readonly="!stand2isEditing"
/>
</td>
</tr>
<tr>
<td colspan="4" class="bg-teal-800 bg-opacity-40">非夏日契約</td>
<td class="bg-teal-800 bg-opacity-40">-</td>
<td class="bg-teal-800 bg-opacity-40">
<input
type="number"
v-model.number="stand2NewValue[4]"
:readonly="!stand2isEditing"
/>
</td>
</tr>
<tr>
<td colspan="4" class="bg-teal-800 bg-opacity-40">週六半尖峰契約</td>
<td class="bg-teal-800 bg-opacity-40">
<input
type="number"
v-model.number="stand2NewValue[5]"
:readonly="!stand2isEditing"
/>
</td>
<td class="bg-teal-800 bg-opacity-40">
<input
type="number"
v-model.number="stand2NewValue[6]"
:readonly="!stand2isEditing"
/>
</td>
</tr>
<tr>
<td colspan="4" class="bg-teal-800 bg-opacity-40">離峰契約</td>
<td class="bg-teal-800 bg-opacity-40">
<input
type="number"
v-model.number="stand2NewValue[7]"
:readonly="!stand2isEditing"
/>
</td>
<td class="bg-teal-800 bg-opacity-40">
<input
type="number"
v-model.number="stand2NewValue[8]"
:readonly="!stand2isEditing"
/>
</td>
</tr>
<tr>
<td rowspan="9">流動電費</td>
<td rowspan="4">週一~週五</td>
<td rowspan="2" class="bg-rose-600 bg-opacity-70">尖峰時間</td>
<td class="bg-rose-600 bg-opacity-20">夏月</td>
<td class="bg-rose-600 bg-opacity-20">09:00 ~ 24:00</td>
<td rowspan="9">每度</td>
<td class="bg-rose-600 bg-opacity-20">
<input
type="number"
v-model.number="stand2NewValue[9]"
:readonly="!stand2isEditing"
/>
</td>
<td class="bg-rose-600 bg-opacity-20">-</td>
</tr>
<tr>
<td class="bg-rose-600 bg-opacity-20">非夏月</td>
<td class="bg-rose-600 bg-opacity-20">06:00 ~ 11:00<br />14:00~24:00</td>
<td class="bg-rose-600 bg-opacity-20">-</td>
<td class="bg-rose-600 bg-opacity-20">
<input
type="number"
v-model.number="stand2NewValue[10]"
:readonly="!stand2isEditing"
/>
</td>
</tr>
<tr>
<td rowspan="2" class="bg-green-600 bg-opacity-60">離峰時間</td>
<td class="bg-green-600 bg-opacity-20">夏月</td>
<td class="bg-green-600 bg-opacity-20">00:00 ~ 09:00</td>
<td class="bg-green-600 bg-opacity-20">
<input
type="number"
v-model.number="stand2NewValue[11]"
:readonly="!stand2isEditing"
/>
</td>
<td class="bg-green-600 bg-opacity-20">-</td>
</tr>
<tr>
<td class="bg-green-600 bg-opacity-20">非夏月</td>
<td class="bg-green-600 bg-opacity-20">
00:00 ~ 06:00<br />
11:00 ~ 14:00
</td>
<td class="bg-green-600 bg-opacity-20">-</td>
<td class="bg-green-600 bg-opacity-20">
<input
type="number"
v-model.number="stand2NewValue[12]"
:readonly="!stand2isEditing"
/>
</td>
</tr>
<tr>
<td rowspan="4">週六</td>
<td rowspan="2" class="bg-yellow-400 bg-opacity-80">半尖峰時間</td>
<td class="bg-yellow-400 bg-opacity-20">夏日</td>
<td class="bg-yellow-400 bg-opacity-20">09:00 ~ 24:00</td>
<td class="bg-yellow-400 bg-opacity-20">
<input
type="number"
v-model.number="stand2NewValue[13]"
:readonly="!stand2isEditing"
/>
</td>
<td class="bg-yellow-400 bg-opacity-20">-</td>
</tr>
<tr>
<td class="bg-yellow-400 bg-opacity-20">非夏日</td>
<td class="bg-yellow-400 bg-opacity-20">
06:00 ~ 11:00<br />
14:00 ~ 24:00
</td>
<td class="bg-yellow-400 bg-opacity-20">-</td>
<td class="bg-yellow-400 bg-opacity-20">
<input
type="number"
v-model.number="stand2NewValue[14]"
:readonly="!stand2isEditing"
/>
</td>
</tr>
<tr>
<td rowspan="2" class="bg-green-600 bg-opacity-60">離峰時間</td>
<td class="bg-green-600 bg-opacity-20">夏月</td>
<td class="bg-green-600 bg-opacity-20">00:00 ~ 09:00</td>
<td class="bg-green-600 bg-opacity-20">
<input
type="number"
v-model.number="stand2NewValue[15]"
:readonly="!stand2isEditing"
/>
</td>
<td class="bg-green-600 bg-opacity-20">-</td>
</tr>
<tr>
<td class="bg-green-600 bg-opacity-20">非夏月</td>
<td class="bg-green-600 bg-opacity-20">00:00 ~ 06:00<br />11:00 ~ 14:00</td>
<td class="bg-green-600 bg-opacity-20">-</td>
<td class="bg-green-600 bg-opacity-20">
<input
type="number"
v-model.number="stand2NewValue[16]"
:readonly="!stand2isEditing"
/>
</td>
</tr>
<tr>
<td>週六週日<br />及離峰日</td>
<td class="bg-green-600 bg-opacity-60">離峰時間</td>
<td colspan="2" class="bg-green-600 bg-opacity-20">全日</td>
<td class="bg-green-600 bg-opacity-20">
<input
type="number"
v-model.number="stand2NewValue[17]"
:readonly="!stand2isEditing"
/>
</td>
<td class="bg-green-600 bg-opacity-20">
<input
type="number"
v-model.number="stand2NewValue[18]"
:readonly="!stand2isEditing"
/>
</td>
</tr>
</tbody>
</table>
<div class="flex justify-start items-center mt-14">
<h3 class="text-xl mr-5">標準型時間電價三段式</h3>
<button
v-if="!stand3isEditing"
class="btn btn-sm btn-add mr-3"
@click.stop.prevent="stand3isEditing = true"
>
<font-awesome-icon :icon="['fas', 'pencil-alt']" />{{
$t("button.start_edit")
}}
</button>
<template v-else>
<button
class="btn btn-sm btn-add mr-3"
@click.prevent="onOk('stand3', stand3NewValue)"
>
<font-awesome-icon :icon="['fas', 'save']" />{{ $t("button.confirm") }}
</button>
<button
class="btn btn-sm btn-outline-info mr-3"
@click.stop.prevent="onCancel('stand3')"
>
<font-awesome-icon :icon="['fas', 'times']" />{{ $t("button.cancel") }}
</button>
</template>
</div>
<table class="my-5">
<thead>
<tr>
<th colspan="6" class="bg-teal-800 bg-opacity-20">分類</th>
<th class="bg-teal-800 bg-opacity-20">夏月<br />(6/1~9/30)</th>
<th class="bg-teal-800 bg-opacity-20">非夏月<br />(夏月以外的時間)</th>
</tr>
</thead>
<tbody>
<tr>
<td rowspan="6" class="bg-teal-800 bg-opacity-40">基本電費</td>
<td colspan="2" rowspan="2" class="bg-teal-800 bg-opacity-40">按戶計收</td>
<td colspan="2" class="bg-teal-800 bg-opacity-40">單相</td>
<td rowspan="2" class="bg-teal-800 bg-opacity-40">每戶每月</td>
<td colspan="2" class="bg-teal-800 bg-opacity-40">
<input
type="number"
v-model.number="stand3NewValue[0]"
:readonly="!stand3isEditing"
/>
</td>
</tr>
<tr>
<td colspan="2" class="bg-teal-800 bg-opacity-40">三相</td>
<td colspan="2" class="bg-teal-800 bg-opacity-40">
<input
type="number"
v-model.number="stand3NewValue[1]"
:readonly="!stand3isEditing"
/>
</td>
</tr>
<tr>
<td colspan="4" class="bg-teal-800 bg-opacity-40">經常契約</td>
<td rowspan="4" class="bg-teal-800 bg-opacity-40">每瓩每月</td>
<td class="bg-teal-800 bg-opacity-40">
<input
type="number"
v-model.number="stand3NewValue[2]"
:readonly="!stand3isEditing"
/>
</td>
<td class="bg-teal-800 bg-opacity-40">
<input
type="number"
v-model.number="stand3NewValue[3]"
:readonly="!stand3isEditing"
/>
</td>
</tr>
<tr>
<td colspan="4" class="bg-teal-800 bg-opacity-40">非夏日契約</td>
<td class="bg-teal-800 bg-opacity-40">
<input
type="number"
v-model.number="stand3NewValue[4]"
:readonly="!stand3isEditing"
/>
</td>
<td class="bg-teal-800 bg-opacity-40">
<input
type="number"
v-model.number="stand3NewValue[5]"
:readonly="!stand3isEditing"
/>
</td>
</tr>
<tr>
<td colspan="4" class="bg-teal-800 bg-opacity-40">週六半尖峰契約</td>
<td class="bg-teal-800 bg-opacity-40">
<input
type="number"
v-model.number="stand3NewValue[6]"
:readonly="!stand3isEditing"
/>
</td>
<td class="bg-teal-800 bg-opacity-40">
<input
type="number"
v-model.number="stand3NewValue[7]"
:readonly="!stand3isEditing"
/>
</td>
</tr>
<tr>
<td colspan="4" class="bg-teal-800 bg-opacity-40">離峰契約</td>
<td class="bg-teal-800 bg-opacity-40">
<input
type="number"
v-model.number="stand3NewValue[8]"
:readonly="!stand3isEditing"
/>
</td>
<td class="bg-teal-800 bg-opacity-40">
<input
type="number"
v-model.number="stand3NewValue[9]"
:readonly="!stand3isEditing"
/>
</td>
</tr>
<tr>
<td rowspan="10">流動電費</td>
<td rowspan="5">週一~週五</td>
<td class="bg-rose-600 bg-opacity-70">尖峰時間</td>
<td class="bg-rose-600 bg-opacity-20">夏月</td>
<td class="bg-rose-600 bg-opacity-20">16:00 ~ 22:00</td>
<td rowspan="10">每度</td>
<td class="bg-rose-600 bg-opacity-20">
<input
type="number"
v-model.number="stand3NewValue[10]"
:readonly="!stand3isEditing"
/>
</td>
<td class="bg-rose-600 bg-opacity-20">-</td>
</tr>
<tr>
<td rowspan="2" class="bg-yellow-400 bg-opacity-80">半尖峰時間</td>
<td class="bg-yellow-500 bg-opacity-20">夏月</td>
<td class="bg-yellow-500 bg-opacity-20">09:00 ~ 16:00<br />22:00 ~ 24:00</td>
<td class="bg-yellow-500 bg-opacity-20">
<input
type="number"
v-model.number="stand3NewValue[11]"
:readonly="!stand3isEditing"
/>
</td>
<td class="bg-yellow-500 bg-opacity-20">-</td>
</tr>
<tr>
<td class="bg-yellow-500 bg-opacity-20">非夏月</td>
<td class="bg-yellow-500 bg-opacity-20">06:00 ~ 11:00<br />14:00 ~ 24:00</td>
<td class="bg-yellow-500 bg-opacity-20">-</td>
<td class="bg-yellow-500 bg-opacity-20">
<input
type="number"
v-model.number="stand3NewValue[12]"
:readonly="!stand3isEditing"
/>
</td>
</tr>
<tr>
<td rowspan="2" class="bg-green-600 bg-opacity-60">離峰時間</td>
<td class="bg-green-600 bg-opacity-20">夏月</td>
<td class="bg-green-600 bg-opacity-20">00:00 ~ 09:00</td>
<td class="bg-green-600 bg-opacity-20">
<input
type="number"
v-model.number="stand3NewValue[13]"
:readonly="!stand3isEditing"
/>
</td>
<td class="bg-green-600 bg-opacity-20">-</td>
</tr>
<tr>
<td class="bg-green-600 bg-opacity-20">非夏月</td>
<td class="bg-green-600 bg-opacity-20">
00:00 ~ 06:00<br />
11:00 ~ 14:00
</td>
<td class="bg-green-600 bg-opacity-20">-</td>
<td class="bg-green-600 bg-opacity-20">
<input
type="number"
v-model.number="stand3NewValue[14]"
:readonly="!stand3isEditing"
/>
</td>
</tr>
<tr>
<td rowspan="4">週六</td>
<td rowspan="2" class="bg-yellow-400 bg-opacity-80">半尖峰時間</td>
<td class="bg-yellow-500 bg-opacity-20">夏日</td>
<td class="bg-yellow-500 bg-opacity-20">09:00 ~ 24:00</td>
<td class="bg-yellow-500 bg-opacity-20">
<input
type="number"
v-model.number="stand3NewValue[15]"
:readonly="!stand3isEditing"
/>
</td>
<td class="bg-yellow-500 bg-opacity-20">-</td>
</tr>
<tr>
<td class="bg-yellow-500 bg-opacity-20">非夏日</td>
<td class="bg-yellow-500 bg-opacity-20">
06:00 ~ 11:00<br />
14:00 ~ 24:00
</td>
<td class="bg-yellow-500 bg-opacity-20">-</td>
<td class="bg-yellow-500 bg-opacity-20">
<input
type="number"
v-model.number="stand3NewValue[16]"
:readonly="!stand3isEditing"
/>
</td>
</tr>
<tr>
<td rowspan="2" class="bg-green-600 bg-opacity-60">離峰時間</td>
<td class="bg-green-600 bg-opacity-20">夏月</td>
<td class="bg-green-600 bg-opacity-20">00:00 ~ 09:00</td>
<td class="bg-green-600 bg-opacity-20">
<input
type="number"
v-model.number="stand3NewValue[17]"
:readonly="!stand3isEditing"
/>
</td>
<td class="bg-green-600 bg-opacity-20">-</td>
</tr>
<tr>
<td class="bg-green-600 bg-opacity-20">非夏月</td>
<td class="bg-green-600 bg-opacity-20">00:00 ~ 06:00<br />11:00 ~ 14:00</td>
<td class="bg-green-600 bg-opacity-20">-</td>
<td class="bg-green-600 bg-opacity-20">
<input
type="number"
v-model.number="stand3NewValue[18]"
:readonly="!stand3isEditing"
/>
</td>
</tr>
<tr>
<td>週日及離峰日</td>
<td class="bg-green-600 bg-opacity-60">離峰時間</td>
<td colspan="2" class="bg-green-600 bg-opacity-20">全日</td>
<td class="bg-green-600 bg-opacity-20">
<input
type="number"
v-model.number="stand3NewValue[19]"
:readonly="!stand3isEditing"
/>
</td>
<td class="bg-green-600 bg-opacity-20">
<input
type="number"
v-model.number="stand3NewValue[20]"
:readonly="!stand3isEditing"
/>
</td>
</tr>
</tbody>
</table>
</template>
<style lang="scss" scoped>
th,
td {
text-align: center;
border: 1px solid #ddd;
padding: 0.5rem 0.75rem;
text-align: center;
font-size: 1.125rem;
line-height: 1.75rem;
font-weight: 600;
}
input {
@apply text-lg text-white w-full bg-transparent input input-bordered rounded-md px-3 border-info focus-within:border-info read-only:text-white read-only:border-0 read-only:focus-within:outline-0 read-only:focus:outline-0;
}
</style>

View File

@ -0,0 +1,91 @@
<script setup>
import { defineProps, defineEmits } from "vue";
const props = defineProps({
data: {
type: Array,
required: true
},
checkedNodes: {
type: Array,
default: () => []
}
});
const emit = defineEmits(["update:checkedNodes"]);
const toggleExpand = (node) => {
node.expanded = !node.expanded;
};
const isChecked = (node) => {
return props.checkedNodes.includes(node.value);
};
const isIndeterminate = (node) => {
if (!node.children?.length) return false;
const hasCheckedChild = node.children.some(child =>
isChecked(child) || isIndeterminate(child)
);
const allChildrenChecked = node.children.every(child => isChecked(child));
return hasCheckedChild && !allChildrenChecked;
};
const toggleNode = (node, checked) => {
const newCheckedNodes = new Set(props.checkedNodes);
const processNode = (currentNode) => {
if (checked) {
newCheckedNodes.add(currentNode.value);
} else {
newCheckedNodes.delete(currentNode.value);
}
currentNode.children?.forEach(child => {
processNode(child);
});
};
processNode(node);
emit("update:checkedNodes", Array.from(newCheckedNodes));
};
</script>
<template>
<ul class="pl-4 text-white ">
<li v-for="node in data" :key="node.value" class="mb-2">
<div class="flex items-center">
<span
v-if="node.children?.length"
@click="toggleExpand(node)"
class="cursor-pointer mr-2 w-4 inline-block"
>
{{ node.expanded ? '▼' : '▶' }}
</span>
<span v-else class="w-4 mr-2"></span>
<input
type="checkbox"
:checked="isChecked(node)"
:indeterminate="isIndeterminate(node)"
@change="toggleNode(node, $event.target.checked)"
class="mr-2"
/>
<span class="cursor-pointer" @click="toggleNode(node, !isChecked(node))">
{{ node.label }}
</span>
</div>
<MITTCheckboxTree
v-if="node.children?.length && node.expanded"
:data="node.children"
:checked-nodes="checkedNodes"
@update:checked-nodes="$emit('update:checkedNodes', $event)"
class="mt-2"
/>
</li>
</ul>
</template>

View File

@ -2,6 +2,7 @@
import { ref, onMounted, watch, computed } from "vue";
import { getAssetMainList, getAssetSubList, getIOTSchema } from "@/apis/asset";
import useActiveBtn from "@/hooks/useActiveBtn";
import MITTlISTAddModal from "./MITTlISTAddModal.vue";
import { useI18n } from "vue-i18n";
const { t } = useI18n();
const formState = ref({});
@ -74,6 +75,15 @@ const getDataSource = async (id) => {
loading.value = false;
};
const openModal = () => {
MQTT_Parse_item.showModal();
};
const onCancel = () => {
MQTT_Parse_item.close();
getDataSource(parseInt(selectedSubSysItems.key))
};
watch(
selectedMainSysItems,
(newValue) => {
@ -133,6 +143,13 @@ onMounted(() => {
"
/>
</div>
<div class="flex justify-start items-center mt-5 mb-2">
<h3 class="text-xl mr-5">MQTT_Parse : </h3>
<MITTlISTAddModal
:openModal="openModal"
:onCancel="onCancel"
/>
</div>
<Table
:columns="columns"
:dataSource="dataSource"

View File

@ -0,0 +1,235 @@
<script setup>
import { ref, onMounted, watch, defineProps } from "vue";
import { getAssetMainList, getAssetSubList, getIOTSchema } from "@/apis/asset";
import useActiveBtn from "@/hooks/useActiveBtn";
import MITTCheckboxTree from "./MITTCheckboxTree.vue";
import generateSchema from "json-schema-generator";
import { useI18n } from "vue-i18n";
const { t } = useI18n();
const props = defineProps({
openModal: Function,
onCancel: Function,
});
// jsonInput
const jsonInput = ref("");
const treeData = ref(null);
const checkedNodes = ref([]);
const form = ref(null);
const schemaName = ref("");
const formStates = ref([]);
const optionData = ref([
{ key: 0, full_name: "否" },
{ key: 1, full_name: "是" },
]);
// JSON Schema
const customGenerateSchema = (json) => {
if (typeof json !== "object" || json === null) return {};
const schema = { type: "object", properties: {} };
Object.entries(json).forEach(([key, value]) => {
if (typeof value === "object" && value !== null) {
schema.properties[key] = customGenerateSchema(value);
} else {
schema.properties[key] = { type: typeof value };
}
});
return schema;
};
// Tree
const transformToTree = (schema, parentPath = "") => {
if (typeof schema !== "object" || schema === null) return [];
return Object.entries(schema)
.map(([key, value]) => {
const currentPath = parentPath ? `${parentPath}_${key}` : key;
const isObject = typeof value === "object" && value !== null;
if (isObject && value.properties) {
return {
label: key,
value: currentPath,
expanded: true,
children: transformToTree(value.properties, currentPath),
};
}
return {
label: key,
value: currentPath,
expanded: true,
children: [],
};
})
.filter((item) => item !== null); // null
};
// JSON
const parseJson = () => {
try {
const json = JSON.parse(jsonInput.value);
// JSON
const schema = customGenerateSchema(json);
console.log("JSON Schema:", schema);
//
treeData.value = transformToTree(schema.properties);
console.log("轉換後的樹狀結構:", treeData.value);
} catch (error) {
console.error("JSON 格式不正確:", error);
alert("請檢查 JSON 格式,確保它是有效的!");
}
};
const clearAll = () => {
jsonInput.value = ""; // JSON
treeData.value = null; //
checkedNodes.value = []; //
};
watch(
() => checkedNodes.value,
(newCheckedNodes) => {
// node formState
formStates.value = newCheckedNodes.map((node) => ({
IoT_point_schema: node,
sys_point_name: null,
is_bool: 1,
decimal: 0,
is_open: 1,
}));
},
{ immediate: true }
);
</script>
<template>
<button class="btn btn-sm btn-add mr-3" @click.stop.prevent="openModal">
<font-awesome-icon :icon="['fas', 'plus']" />{{ $t("button.add") }}
</button>
<Modal
id="MQTT_Parse_item"
title="MQTT_Parse"
:open="open"
:onCancel="onCancel"
width="1600"
>
<template #modalContent>
<div class="flex w-full gap-4 mt-5">
<div class="w-1/2 relative">
<textarea
v-model="jsonInput"
placeholder="請貼上 JSON 格式數據"
class="w-full h-[500px] p-4 border rounded-lg font-mono text-white"
></textarea>
<button @click="clearAll" class="bg-error btn absolute top-5 right-5">
<font-awesome-icon :icon="['fas', 'trash-alt']" />
</button>
</div>
<button @click="parseJson" class="btn-success btn my-auto">
轉換
<font-awesome-icon
:icon="['fas', 'chevron-right']"
size="lg"
class="block"
/>
</button>
<div class="w-1/2 p-4 rounded-lg border overflow-y-scroll h-[500px]">
<MITTCheckboxTree
v-if="treeData"
:data="treeData"
v-model:checked-nodes="checkedNodes"
/>
<div v-else class="text-white">請在左側輸入 JSON 並點擊轉換按鈕</div>
</div>
</div>
<form ref="form" class="">
<div class="flex items-center mt-5">
<h2 class="text-lg font-bold whitespace-nowrap me-2">
結構名稱 :
</h2>
<Input v-model="schemaName" />
</div>
<table class="table" v-if="checkedNodes.length">
<thead>
<tr>
<th>IoT 點位名稱</th>
<th>IoT 點位結構</th>
<th>系統點位名稱</th>
<th>bool </th>
<th>小數點個數</th>
<th>點位開關</th>
</tr>
</thead>
<tbody>
<tr v-for="(node, index) in checkedNodes" :key="node">
<td>
<Input :value="formStates[index]" name="IoT_point_name" />
</td>
<td>
<Input
:value="formStates[index]"
name="IoT_point_schema"
:readonly="true"
/>
</td>
<td>
<select>
<option value="option1">選項 1</option>
<option value="option2">選項 2</option>
<option value="option3">選項 3</option>
</select>
</td>
<td>
<div class="flex w-36">
<Select
:value="formStates[index]"
selectClass="w-20 mx-auto border-info focus-within:border-info"
name="is_bool"
Attribute="full_name"
:options="optionData"
>
</Select>
</div>
</td>
<td>
<InputNumber
:value="formStates[index]"
class="mx-auto"
name="decimal"
>
</InputNumber>
</td>
<td>
<div class="flex w-36">
<Select
:value="formStates[index]"
selectClass="w-20 mx-auto border-info focus-within:border-info"
name="is_open"
Attribute="full_name"
:options="optionData"
>
</Select>
</div>
</td>
</tr>
</tbody>
</table>
</form>
</template>
</Modal>
</template>
<style lang="scss" scoped>
.table th {
@apply bg-cyan-600 bg-opacity-30 border border-white text-lg font-semibold text-white text-center px-2 py-3;
}
.table td {
@apply border border-white text-lg font-semibold text-white text-center px-2 py-3;
}
</style>