首頁與報表管理組件
This commit is contained in:
parent
cc25dce91c
commit
0e767795be
860
package-lock.json
generated
860
package-lock.json
generated
@ -9,6 +9,7 @@
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"axios": "^1.12.2",
|
||||
"dayjs": "^1.11.18",
|
||||
"echarts": "^6.0.0",
|
||||
"element-plus": "^2.11.4",
|
||||
"leaflet": "^1.9.4",
|
||||
@ -19,6 +20,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^6.0.1",
|
||||
"sass-embedded": "^1.93.2",
|
||||
"unplugin-auto-import": "^20.2.0",
|
||||
"unplugin-vue-components": "^29.1.0",
|
||||
"vite": "^7.1.7"
|
||||
@ -70,6 +72,13 @@
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@bufbuild/protobuf": {
|
||||
"version": "2.9.0",
|
||||
"resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.9.0.tgz",
|
||||
"integrity": "sha512-rnJenoStJ8nvmt9Gzye8nkYd6V22xUAnu4086ER7h1zJ508vStko4pMvDeQ446ilDTFpV5wnoc5YS7XvMwwMqA==",
|
||||
"dev": true,
|
||||
"license": "(Apache-2.0 AND BSD-3-Clause)"
|
||||
},
|
||||
"node_modules/@ctrl/tinycolor": {
|
||||
"version": "3.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-3.6.1.tgz",
|
||||
@ -604,6 +613,316 @@
|
||||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz",
|
||||
"integrity": "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"detect-libc": "^1.0.3",
|
||||
"is-glob": "^4.0.3",
|
||||
"micromatch": "^4.0.5",
|
||||
"node-addon-api": "^7.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@parcel/watcher-android-arm64": "2.5.1",
|
||||
"@parcel/watcher-darwin-arm64": "2.5.1",
|
||||
"@parcel/watcher-darwin-x64": "2.5.1",
|
||||
"@parcel/watcher-freebsd-x64": "2.5.1",
|
||||
"@parcel/watcher-linux-arm-glibc": "2.5.1",
|
||||
"@parcel/watcher-linux-arm-musl": "2.5.1",
|
||||
"@parcel/watcher-linux-arm64-glibc": "2.5.1",
|
||||
"@parcel/watcher-linux-arm64-musl": "2.5.1",
|
||||
"@parcel/watcher-linux-x64-glibc": "2.5.1",
|
||||
"@parcel/watcher-linux-x64-musl": "2.5.1",
|
||||
"@parcel/watcher-win32-arm64": "2.5.1",
|
||||
"@parcel/watcher-win32-ia32": "2.5.1",
|
||||
"@parcel/watcher-win32-x64": "2.5.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-android-arm64": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.1.tgz",
|
||||
"integrity": "sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-darwin-arm64": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.1.tgz",
|
||||
"integrity": "sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-darwin-x64": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.1.tgz",
|
||||
"integrity": "sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-freebsd-x64": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.1.tgz",
|
||||
"integrity": "sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-linux-arm-glibc": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.1.tgz",
|
||||
"integrity": "sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-linux-arm-musl": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.1.tgz",
|
||||
"integrity": "sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-linux-arm64-glibc": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.1.tgz",
|
||||
"integrity": "sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-linux-arm64-musl": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.1.tgz",
|
||||
"integrity": "sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-linux-x64-glibc": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.1.tgz",
|
||||
"integrity": "sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-linux-x64-musl": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.1.tgz",
|
||||
"integrity": "sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-win32-arm64": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.1.tgz",
|
||||
"integrity": "sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-win32-ia32": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.1.tgz",
|
||||
"integrity": "sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-win32-x64": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.1.tgz",
|
||||
"integrity": "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@popperjs/core": {
|
||||
"name": "@sxzz/popperjs-es",
|
||||
"version": "2.11.7",
|
||||
@ -1324,6 +1643,13 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/buffer-builder": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/buffer-builder/-/buffer-builder-0.2.0.tgz",
|
||||
"integrity": "sha512-7VPMEPuYznPSoR21NE1zvd2Xna6c/CloiZCfcMXR1Jny6PjX0N4Nsa38zcBFo/FMK+BlA+FLKbJCQ0i2yxp+Xg==",
|
||||
"dev": true,
|
||||
"license": "MIT/X11"
|
||||
},
|
||||
"node_modules/call-bind-apply-helpers": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
|
||||
@ -1362,6 +1688,13 @@
|
||||
"fsevents": "~2.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/colorjs.io": {
|
||||
"version": "0.5.2",
|
||||
"resolved": "https://registry.npmjs.org/colorjs.io/-/colorjs.io-0.5.2.tgz",
|
||||
"integrity": "sha512-twmVoizEW7ylZSN32OgKdXRmo1qg+wT5/6C3xu5b9QsWzSFAhHLn2xd8ro0diCsKfCj1RdaTP/nrcW+vAoQPIw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/combined-stream": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||
@ -1435,6 +1768,20 @@
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/detect-libc": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
|
||||
"integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"bin": {
|
||||
"detect-libc": "bin/detect-libc.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/dunder-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||
@ -1769,6 +2116,16 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/has-flag": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
||||
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/has-symbols": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
|
||||
@ -1814,6 +2171,13 @@
|
||||
"integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/immutable": {
|
||||
"version": "5.1.4",
|
||||
"resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.4.tgz",
|
||||
"integrity": "sha512-p6u1bG3YSnINT5RQmx/yRZBpenIl30kVxkTLDyHLIMk0gict704Q9n+thfDI7lTRm9vXdDYutVzXhzcThxTnXA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/is-binary-path": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
|
||||
@ -1961,6 +2325,35 @@
|
||||
"integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/micromatch": {
|
||||
"version": "4.0.8",
|
||||
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
|
||||
"integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"braces": "^3.0.3",
|
||||
"picomatch": "^2.3.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.6"
|
||||
}
|
||||
},
|
||||
"node_modules/micromatch/node_modules/picomatch": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
|
||||
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">=8.6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-db": {
|
||||
"version": "1.52.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||
@ -2045,6 +2438,14 @@
|
||||
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/node-addon-api": {
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz",
|
||||
"integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/normalize-path": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
|
||||
@ -2260,6 +2661,419 @@
|
||||
"fsevents": "~2.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/rxjs": {
|
||||
"version": "7.8.2",
|
||||
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz",
|
||||
"integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"tslib": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/sass": {
|
||||
"version": "1.93.2",
|
||||
"resolved": "https://registry.npmjs.org/sass/-/sass-1.93.2.tgz",
|
||||
"integrity": "sha512-t+YPtOQHpGW1QWsh1CHQ5cPIr9lbbGZLZnbihP/D/qZj/yuV68m8qarcV17nvkOX81BCrvzAlq2klCQFZghyTg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"chokidar": "^4.0.0",
|
||||
"immutable": "^5.0.2",
|
||||
"source-map-js": ">=0.6.2 <2.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"sass": "sass.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@parcel/watcher": "^2.4.1"
|
||||
}
|
||||
},
|
||||
"node_modules/sass-embedded": {
|
||||
"version": "1.93.2",
|
||||
"resolved": "https://registry.npmjs.org/sass-embedded/-/sass-embedded-1.93.2.tgz",
|
||||
"integrity": "sha512-FvQdkn2dZ8DGiLgi0Uf4zsj7r/BsiLImNa5QJ10eZalY6NfZyjrmWGFcuCN5jNwlDlXFJnftauv+UtvBKLvepQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@bufbuild/protobuf": "^2.5.0",
|
||||
"buffer-builder": "^0.2.0",
|
||||
"colorjs.io": "^0.5.0",
|
||||
"immutable": "^5.0.2",
|
||||
"rxjs": "^7.4.0",
|
||||
"supports-color": "^8.1.1",
|
||||
"sync-child-process": "^1.0.2",
|
||||
"varint": "^6.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"sass": "dist/bin/sass.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"sass-embedded-all-unknown": "1.93.2",
|
||||
"sass-embedded-android-arm": "1.93.2",
|
||||
"sass-embedded-android-arm64": "1.93.2",
|
||||
"sass-embedded-android-riscv64": "1.93.2",
|
||||
"sass-embedded-android-x64": "1.93.2",
|
||||
"sass-embedded-darwin-arm64": "1.93.2",
|
||||
"sass-embedded-darwin-x64": "1.93.2",
|
||||
"sass-embedded-linux-arm": "1.93.2",
|
||||
"sass-embedded-linux-arm64": "1.93.2",
|
||||
"sass-embedded-linux-musl-arm": "1.93.2",
|
||||
"sass-embedded-linux-musl-arm64": "1.93.2",
|
||||
"sass-embedded-linux-musl-riscv64": "1.93.2",
|
||||
"sass-embedded-linux-musl-x64": "1.93.2",
|
||||
"sass-embedded-linux-riscv64": "1.93.2",
|
||||
"sass-embedded-linux-x64": "1.93.2",
|
||||
"sass-embedded-unknown-all": "1.93.2",
|
||||
"sass-embedded-win32-arm64": "1.93.2",
|
||||
"sass-embedded-win32-x64": "1.93.2"
|
||||
}
|
||||
},
|
||||
"node_modules/sass-embedded-all-unknown": {
|
||||
"version": "1.93.2",
|
||||
"resolved": "https://registry.npmjs.org/sass-embedded-all-unknown/-/sass-embedded-all-unknown-1.93.2.tgz",
|
||||
"integrity": "sha512-GdEuPXIzmhRS5J7UKAwEvtk8YyHQuFZRcpnEnkA3rwRUI27kwjyXkNeIj38XjUQ3DzrfMe8HcKFaqWGHvblS7Q==",
|
||||
"cpu": [
|
||||
"!arm",
|
||||
"!arm64",
|
||||
"!riscv64",
|
||||
"!x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"sass": "1.93.2"
|
||||
}
|
||||
},
|
||||
"node_modules/sass-embedded-android-arm": {
|
||||
"version": "1.93.2",
|
||||
"resolved": "https://registry.npmjs.org/sass-embedded-android-arm/-/sass-embedded-android-arm-1.93.2.tgz",
|
||||
"integrity": "sha512-I8bpO8meZNo5FvFx5FIiE7DGPVOYft0WjuwcCCdeJ6duwfkl6tZdatex1GrSigvTsuz9L0m4ngDcX/Tj/8yMow==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/sass-embedded-android-arm64": {
|
||||
"version": "1.93.2",
|
||||
"resolved": "https://registry.npmjs.org/sass-embedded-android-arm64/-/sass-embedded-android-arm64-1.93.2.tgz",
|
||||
"integrity": "sha512-346f4iVGAPGcNP6V6IOOFkN5qnArAoXNTPr5eA/rmNpeGwomdb7kJyQ717r9rbJXxOG8OAAUado6J0qLsjnjXQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/sass-embedded-android-riscv64": {
|
||||
"version": "1.93.2",
|
||||
"resolved": "https://registry.npmjs.org/sass-embedded-android-riscv64/-/sass-embedded-android-riscv64-1.93.2.tgz",
|
||||
"integrity": "sha512-hSMW1s4yJf5guT9mrdkumluqrwh7BjbZ4MbBW9tmi1DRDdlw1Wh9Oy1HnnmOG8x9XcI1qkojtPL6LUuEJmsiDg==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/sass-embedded-android-x64": {
|
||||
"version": "1.93.2",
|
||||
"resolved": "https://registry.npmjs.org/sass-embedded-android-x64/-/sass-embedded-android-x64-1.93.2.tgz",
|
||||
"integrity": "sha512-JqktiHZduvn+ldGBosE40ALgQ//tGCVNAObgcQ6UIZznEJbsHegqStqhRo8UW3x2cgOO2XYJcrInH6cc7wdKbw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/sass-embedded-darwin-arm64": {
|
||||
"version": "1.93.2",
|
||||
"resolved": "https://registry.npmjs.org/sass-embedded-darwin-arm64/-/sass-embedded-darwin-arm64-1.93.2.tgz",
|
||||
"integrity": "sha512-qI1X16qKNeBJp+M/5BNW7v/JHCDYWr1/mdoJ7+UMHmP0b5AVudIZtimtK0hnjrLnBECURifd6IkulybR+h+4UA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/sass-embedded-darwin-x64": {
|
||||
"version": "1.93.2",
|
||||
"resolved": "https://registry.npmjs.org/sass-embedded-darwin-x64/-/sass-embedded-darwin-x64-1.93.2.tgz",
|
||||
"integrity": "sha512-4KeAvlkQ0m0enKUnDGQJZwpovYw99iiMb8CTZRSsQm8Eh7halbJZVmx67f4heFY/zISgVOCcxNg19GrM5NTwtA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/sass-embedded-linux-arm": {
|
||||
"version": "1.93.2",
|
||||
"resolved": "https://registry.npmjs.org/sass-embedded-linux-arm/-/sass-embedded-linux-arm-1.93.2.tgz",
|
||||
"integrity": "sha512-N3+D/ToHtzwLDO+lSH05Wo6/KRxFBPnbjVHASOlHzqJnK+g5cqex7IFAp6ozzlRStySk61Rp6d+YGrqZ6/P0PA==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/sass-embedded-linux-arm64": {
|
||||
"version": "1.93.2",
|
||||
"resolved": "https://registry.npmjs.org/sass-embedded-linux-arm64/-/sass-embedded-linux-arm64-1.93.2.tgz",
|
||||
"integrity": "sha512-9ftX6nd5CsShJqJ2WRg+ptaYvUW+spqZfJ88FbcKQBNFQm6L87luj3UI1rB6cP5EWrLwHA754OKxRJyzWiaN6g==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/sass-embedded-linux-musl-arm": {
|
||||
"version": "1.93.2",
|
||||
"resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-arm/-/sass-embedded-linux-musl-arm-1.93.2.tgz",
|
||||
"integrity": "sha512-XBTvx66yRenvEsp3VaJCb3HQSyqCsUh7R+pbxcN5TuzueybZi0LXvn9zneksdXcmjACMlMpIVXi6LyHPQkYc8A==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/sass-embedded-linux-musl-arm64": {
|
||||
"version": "1.93.2",
|
||||
"resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-arm64/-/sass-embedded-linux-musl-arm64-1.93.2.tgz",
|
||||
"integrity": "sha512-+3EHuDPkMiAX5kytsjEC1bKZCawB9J6pm2eBIzzLMPWbf5xdx++vO1DpT7hD4bm4ZGn0eVHgSOKIfP6CVz6tVg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/sass-embedded-linux-musl-riscv64": {
|
||||
"version": "1.93.2",
|
||||
"resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-riscv64/-/sass-embedded-linux-musl-riscv64-1.93.2.tgz",
|
||||
"integrity": "sha512-0sB5kmVZDKTYzmCSlTUnjh6mzOhzmQiW/NNI5g8JS4JiHw2sDNTvt1dsFTuqFkUHyEOY3ESTsfHHBQV8Ip4bEA==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/sass-embedded-linux-musl-x64": {
|
||||
"version": "1.93.2",
|
||||
"resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-x64/-/sass-embedded-linux-musl-x64-1.93.2.tgz",
|
||||
"integrity": "sha512-t3ejQ+1LEVuHy7JHBI2tWHhoMfhedUNDjGJR2FKaLgrtJntGnyD1RyX0xb3nuqL/UXiEAtmTmZY+Uh3SLUe1Hg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/sass-embedded-linux-riscv64": {
|
||||
"version": "1.93.2",
|
||||
"resolved": "https://registry.npmjs.org/sass-embedded-linux-riscv64/-/sass-embedded-linux-riscv64-1.93.2.tgz",
|
||||
"integrity": "sha512-e7AndEwAbFtXaLy6on4BfNGTr3wtGZQmypUgYpSNVcYDO+CWxatKVY4cxbehMPhxG9g5ru+eaMfynvhZt7fLaA==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/sass-embedded-linux-x64": {
|
||||
"version": "1.93.2",
|
||||
"resolved": "https://registry.npmjs.org/sass-embedded-linux-x64/-/sass-embedded-linux-x64-1.93.2.tgz",
|
||||
"integrity": "sha512-U3EIUZQL11DU0xDDHXexd4PYPHQaSQa2hzc4EzmhHqrAj+TyfYO94htjWOd+DdTPtSwmLp+9cTWwPZBODzC96w==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/sass-embedded-unknown-all": {
|
||||
"version": "1.93.2",
|
||||
"resolved": "https://registry.npmjs.org/sass-embedded-unknown-all/-/sass-embedded-unknown-all-1.93.2.tgz",
|
||||
"integrity": "sha512-7VnaOmyewcXohiuoFagJ3SK5ddP9yXpU0rzz+pZQmS1/+5O6vzyFCUoEt3HDRaLctH4GT3nUGoK1jg0ae62IfQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"!android",
|
||||
"!darwin",
|
||||
"!linux",
|
||||
"!win32"
|
||||
],
|
||||
"dependencies": {
|
||||
"sass": "1.93.2"
|
||||
}
|
||||
},
|
||||
"node_modules/sass-embedded-win32-arm64": {
|
||||
"version": "1.93.2",
|
||||
"resolved": "https://registry.npmjs.org/sass-embedded-win32-arm64/-/sass-embedded-win32-arm64-1.93.2.tgz",
|
||||
"integrity": "sha512-Y90DZDbQvtv4Bt0GTXKlcT9pn4pz8AObEjFF8eyul+/boXwyptPZ/A1EyziAeNaIEIfxyy87z78PUgCeGHsx3Q==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/sass-embedded-win32-x64": {
|
||||
"version": "1.93.2",
|
||||
"resolved": "https://registry.npmjs.org/sass-embedded-win32-x64/-/sass-embedded-win32-x64-1.93.2.tgz",
|
||||
"integrity": "sha512-BbSucRP6PVRZGIwlEBkp+6VQl2GWdkWFMN+9EuOTPrLxCJZoq+yhzmbjspd3PeM8+7WJ7AdFu/uRYdO8tor1iQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/sass/node_modules/chokidar": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
|
||||
"integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"readdirp": "^4.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 14.16.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/sass/node_modules/readdirp": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
|
||||
"integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">= 14.18.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "individual",
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/scule": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/scule/-/scule-1.3.0.tgz",
|
||||
@ -2316,6 +3130,45 @@
|
||||
"node": ">=16"
|
||||
}
|
||||
},
|
||||
"node_modules/supports-color": {
|
||||
"version": "8.1.1",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
|
||||
"integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"has-flag": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/supports-color?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/sync-child-process": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/sync-child-process/-/sync-child-process-1.0.2.tgz",
|
||||
"integrity": "sha512-8lD+t2KrrScJ/7KXCSyfhT3/hRq78rC0wBFqNJXv3mZyn6hW2ypM05JmlSvtqRbeq6jqA94oHbxAr2vYsJ8vDA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"sync-message-port": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/sync-message-port": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/sync-message-port/-/sync-message-port-1.1.3.tgz",
|
||||
"integrity": "sha512-GTt8rSKje5FilG+wEdfCkOcLL7LWqpMlr2c3LRuKt/YXxcJ52aGSbGBAdI4L3aaqfrBt6y711El53ItyH1NWzg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=16.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/tinyglobby": {
|
||||
"version": "0.2.15",
|
||||
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
|
||||
@ -2503,6 +3356,13 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/varint": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/varint/-/varint-6.0.0.tgz",
|
||||
"integrity": "sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "7.1.9",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-7.1.9.tgz",
|
||||
|
||||
@ -10,6 +10,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^1.12.2",
|
||||
"dayjs": "^1.11.18",
|
||||
"echarts": "^6.0.0",
|
||||
"element-plus": "^2.11.4",
|
||||
"leaflet": "^1.9.4",
|
||||
@ -20,6 +21,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^6.0.1",
|
||||
"sass-embedded": "^1.93.2",
|
||||
"unplugin-auto-import": "^20.2.0",
|
||||
"unplugin-vue-components": "^29.1.0",
|
||||
"vite": "^7.1.7"
|
||||
|
||||
@ -4,7 +4,6 @@ import { useRouter, useRoute } from "vue-router";
|
||||
import { ElConfigProvider } from "element-plus";
|
||||
import Navbar from "./components/Navbar.vue";
|
||||
import Sidebar from "./components/Sidebar.vue";
|
||||
import Breadcrumb from "./components/Breadcrumb.vue";
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
|
||||
@ -56,7 +55,6 @@ onUnmounted(() => {
|
||||
<Navbar v-model:isCollapse="isCollapse" />
|
||||
</el-header>
|
||||
<el-main>
|
||||
<!-- <Breadcrumb /> -->
|
||||
<router-view />
|
||||
</el-main>
|
||||
</el-container>
|
||||
|
||||
20
src/components.d.ts
vendored
20
src/components.d.ts
vendored
@ -8,42 +8,36 @@ export {}
|
||||
/* prettier-ignore */
|
||||
declare module 'vue' {
|
||||
export interface GlobalComponents {
|
||||
Breadcrumb: typeof import('./components/Breadcrumb.vue')['default']
|
||||
ComboBarLineChart: typeof import('./components/Chart/ComboBarLineChart.vue')['default']
|
||||
ElAside: typeof import('element-plus/es')['ElAside']
|
||||
ElAvatar: typeof import('element-plus/es')['ElAvatar']
|
||||
ElBreadcrumb: typeof import('element-plus/es')['ElBreadcrumb']
|
||||
ElBreadcrumbItem: typeof import('element-plus/es')['ElBreadcrumbItem']
|
||||
ElButton: typeof import('element-plus/es')['ElButton']
|
||||
ElCard: typeof import('element-plus/es')['ElCard']
|
||||
ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
|
||||
ElCol: typeof import('element-plus/es')['ElCol']
|
||||
ElContainer: typeof import('element-plus/es')['ElContainer']
|
||||
ElDescriptions: typeof import('element-plus/es')['ElDescriptions']
|
||||
ElDescriptionsItem: typeof import('element-plus/es')['ElDescriptionsItem']
|
||||
ElDatePicker: typeof import('element-plus/es')['ElDatePicker']
|
||||
ElDrawer: typeof import('element-plus/es')['ElDrawer']
|
||||
ElDropdown: typeof import('element-plus/es')['ElDropdown']
|
||||
ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
|
||||
ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu']
|
||||
ElHeader: typeof import('element-plus/es')['ElHeader']
|
||||
ElIcon: typeof import('element-plus/es')['ElIcon']
|
||||
ElList: typeof import('element-plus/es')['ElList']
|
||||
ElListItem: typeof import('element-plus/es')['ElListItem']
|
||||
ElListItemContent: typeof import('element-plus/es')['ElListItemContent']
|
||||
ElInput: typeof import('element-plus/es')['ElInput']
|
||||
ElMain: typeof import('element-plus/es')['ElMain']
|
||||
ElMenu: typeof import('element-plus/es')['ElMenu']
|
||||
ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
|
||||
ElRadioButton: typeof import('element-plus/es')['ElRadioButton']
|
||||
ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
|
||||
ElRow: typeof import('element-plus/es')['ElRow']
|
||||
ElStatistic: typeof import('element-plus/es')['ElStatistic']
|
||||
ElSubMenu: typeof import('element-plus/es')['ElSubMenu']
|
||||
ElTable: typeof import('element-plus/es')['ElTable']
|
||||
ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
|
||||
ElTooltip: typeof import('element-plus/es')['ElTooltip']
|
||||
InfoList: typeof import('./components/InfoList.vue')['default']
|
||||
LineChart: typeof import('./components/Chart/LineChart.vue')['default']
|
||||
Navbar: typeof import('./components/Navbar.vue')['default']
|
||||
PieChart: typeof import('./components/Chart/PieChart.vue')['default']
|
||||
ElTabPane: typeof import('element-plus/es')['ElTabPane']
|
||||
ElTabs: typeof import('element-plus/es')['ElTabs']
|
||||
RouterLink: typeof import('vue-router')['RouterLink']
|
||||
RouterView: typeof import('vue-router')['RouterView']
|
||||
Sidebar: typeof import('./components/Sidebar.vue')['default']
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,19 +0,0 @@
|
||||
<template>
|
||||
<el-breadcrumb separator="/">
|
||||
<el-breadcrumb-item v-for="(item, idx) in breadcrumbs" :key="item.path || idx" :to="item.path && idx !== breadcrumbs.length - 1 ? { path: item.path } : undefined">
|
||||
<span v-if="!item.path || idx === breadcrumbs.length - 1">{{ item.meta && item.meta.title ? item.meta.title : item.name }}</span>
|
||||
<span v-else>{{ item.meta && item.meta.title ? item.meta.title : item.name }}</span>
|
||||
</el-breadcrumb-item>
|
||||
</el-breadcrumb>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
|
||||
const route = useRoute();
|
||||
const breadcrumbs = computed(() => {
|
||||
// route.matched 會依序包含所有父路由
|
||||
return route.matched.filter(r => r.meta && r.meta.title);
|
||||
});
|
||||
</script>
|
||||
@ -1,103 +1,177 @@
|
||||
<template>
|
||||
<div v-if="showToolbox" style="text-align: right; margin: 3px 8px">
|
||||
<el-button size="small" :icon="Download" @click="exportChart()">
|
||||
下載圖表
|
||||
</el-button>
|
||||
<el-button size="small" :icon="FullScreen" @click="openChartInNewTab">
|
||||
全螢幕瀏覽
|
||||
</el-button>
|
||||
</div>
|
||||
<div :id="chartId" style="width: 100%; min-height: 250px"></div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onMounted, onUnmounted, nextTick, computed } from 'vue'
|
||||
import * as echarts from 'echarts'
|
||||
import { onMounted, onUnmounted, nextTick, computed } from "vue";
|
||||
import { Download, FullScreen } from "@element-plus/icons-vue";
|
||||
import * as echarts from "echarts";
|
||||
|
||||
const props = defineProps({
|
||||
data: {
|
||||
type: Array,
|
||||
required: true
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
series: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
xField: {
|
||||
type: String,
|
||||
default: "date",
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
})
|
||||
|
||||
const chartId = `comboChart-${Math.random().toString(36).slice(2)}`
|
||||
let chartInstance = null
|
||||
|
||||
const years = computed(() => props.data.map(item => item.year))
|
||||
|
||||
// 取得所有電廠名稱
|
||||
const plantNames = computed(() => {
|
||||
const set = new Set();
|
||||
props.data.forEach(item => {
|
||||
item.data.forEach(d => set.add(d.name));
|
||||
});
|
||||
return Array.from(set);
|
||||
default: "",
|
||||
},
|
||||
yLeftUnit: {
|
||||
type: String,
|
||||
default: "MW",
|
||||
},
|
||||
yRightUnit: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
showToolbox: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
|
||||
// 產生 series
|
||||
const chartId = `comboChart-${Math.random().toString(36).slice(2)}`;
|
||||
let chartInstance = null;
|
||||
|
||||
const xAxisData = computed(() => props.data.map((item) => item[props.xField]));
|
||||
|
||||
function getSeries() {
|
||||
const barSeries = plantNames.value.map(name => ({
|
||||
name,
|
||||
type: 'bar',
|
||||
data: props.data.map(item => {
|
||||
const found = item.data.find(d => d.name === name);
|
||||
return found ? found.value : 0;
|
||||
}),
|
||||
barMaxWidth: 32
|
||||
}));
|
||||
// 總發電量
|
||||
const totalSeries = {
|
||||
name: '總發電量',
|
||||
type: 'line',
|
||||
data: props.data.map(item => item.data.reduce((sum, d) => sum + d.value, 0)),
|
||||
symbol: 'circle',
|
||||
symbolSize: 10,
|
||||
lineStyle: { width: 3, color: '#409EFF' },
|
||||
itemStyle: { color: '#409EFF', borderColor: '#fff', borderWidth: 2 }
|
||||
};
|
||||
return [...barSeries, totalSeries];
|
||||
return props.series.map((s) => {
|
||||
const base = {
|
||||
name: s.name,
|
||||
type: s.type,
|
||||
data: props.data.map((item) => item[s.field]),
|
||||
barMaxWidth: 32,
|
||||
yAxisIndex: s.yAxisIndex || 0,
|
||||
};
|
||||
if (s.type === "bar") {
|
||||
base.itemStyle = { color: s.color || "#91cc75" };
|
||||
} else if (s.type === "line") {
|
||||
base.symbol = "circle";
|
||||
base.symbolSize = 10;
|
||||
base.itemStyle = {
|
||||
color: s.color || "#409EFF",
|
||||
borderColor: "#fff",
|
||||
borderWidth: 2,
|
||||
};
|
||||
}
|
||||
return base;
|
||||
});
|
||||
}
|
||||
|
||||
// 下載圖表圖片
|
||||
function exportChart(options = {}) {
|
||||
if (!chartInstance) return;
|
||||
const url = chartInstance.getDataURL({
|
||||
type: options.type || "png",
|
||||
pixelRatio: options.pixelRatio || 2,
|
||||
backgroundColor: options.backgroundColor || "#fff",
|
||||
name: options.name || props.title || "chart",
|
||||
...options,
|
||||
});
|
||||
// 觸發下載
|
||||
const a = document.createElement("a");
|
||||
a.href = url;
|
||||
a.download =
|
||||
(options.name || props.title || "chart") + "." + (options.type || "png");
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
}
|
||||
|
||||
// 另開新分頁顯示圖表圖片
|
||||
function openChartInNewTab() {
|
||||
if (!chartInstance) return;
|
||||
const url = chartInstance.getDataURL({
|
||||
type: "png",
|
||||
pixelRatio: 2,
|
||||
backgroundColor: "#fff",
|
||||
name: props.title || "chart",
|
||||
});
|
||||
const win = window.open();
|
||||
if (win) {
|
||||
win.document.write(
|
||||
`<title>${
|
||||
props.title || "圖表"
|
||||
}</title><img src='${url}' style='display:block;margin:auto;max-width:100vw;max-height:100vh;'/>`
|
||||
);
|
||||
win.document.close();
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
nextTick(() => {
|
||||
const chartDom = document.getElementById(chartId)
|
||||
const chartDom = document.getElementById(chartId);
|
||||
if (chartDom) {
|
||||
chartInstance = echarts.init(chartDom)
|
||||
chartInstance = echarts.init(chartDom);
|
||||
chartInstance.setOption({
|
||||
title: props.title ? {
|
||||
text: props.title,
|
||||
left: 'center',
|
||||
textStyle: { fontSize: 16, color: '#333' }
|
||||
} : undefined,
|
||||
title: props.title
|
||||
? {
|
||||
text: props.title,
|
||||
left: "center",
|
||||
textStyle: { fontSize: 16, color: "#333" },
|
||||
}
|
||||
: undefined,
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: { type: 'shadow' }
|
||||
trigger: "axis",
|
||||
axisPointer: { type: "shadow" },
|
||||
},
|
||||
legend: {
|
||||
bottom: 0
|
||||
bottom: 0,
|
||||
},
|
||||
grid: {
|
||||
left: 10,
|
||||
right: 10,
|
||||
top: 30,
|
||||
bottom: 30,
|
||||
containLabel: true
|
||||
containLabel: true,
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: years.value
|
||||
type: "category",
|
||||
data: xAxisData.value,
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
minInterval: 1,
|
||||
name: 'MW',
|
||||
},
|
||||
series: getSeries()
|
||||
})
|
||||
yAxis: [
|
||||
{
|
||||
type: "value",
|
||||
minInterval: 1,
|
||||
name: props.yLeftUnit,
|
||||
},
|
||||
props.yRightUnit
|
||||
? {
|
||||
type: "value",
|
||||
minInterval: 1,
|
||||
name: props.yRightUnit,
|
||||
position: "right",
|
||||
axisLine: { show: true },
|
||||
axisLabel: { show: true },
|
||||
}
|
||||
: undefined,
|
||||
].filter(Boolean),
|
||||
series: getSeries(),
|
||||
});
|
||||
}
|
||||
})
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
if (chartInstance) {
|
||||
chartInstance.dispose()
|
||||
chartInstance.dispose();
|
||||
}
|
||||
})
|
||||
});
|
||||
</script>
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div :id="chartId" style="width: 100%; min-height: 250px"></div>
|
||||
<div :id="chartId" :style="`width: 100%; min-height: ${minHeight}px`"></div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
@ -15,10 +15,23 @@ const props = defineProps({
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
yAxisName: {
|
||||
type: String,
|
||||
default: "MW",
|
||||
},
|
||||
minHeight: {
|
||||
type: Number,
|
||||
default: 250,
|
||||
},
|
||||
smooth: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
|
||||
const chartId = `lineChart-${Math.random().toString(36).slice(2)}`;
|
||||
let chartInstance = null;
|
||||
const minHeight = props.minHeight;
|
||||
|
||||
const xData = computed(() => props.data.map((item) => item.time));
|
||||
const yData = computed(() => props.data.map((item) => item.value));
|
||||
@ -45,8 +58,8 @@ onMounted(() => {
|
||||
bottom: 0,
|
||||
},
|
||||
grid: {
|
||||
left: 10,
|
||||
right: 10,
|
||||
left: 15,
|
||||
right: 15,
|
||||
top: 30,
|
||||
bottom: 30,
|
||||
containLabel: true,
|
||||
@ -59,7 +72,7 @@ onMounted(() => {
|
||||
yAxis: {
|
||||
type: "value",
|
||||
minInterval: 1,
|
||||
name: "MW",
|
||||
name: props.yAxisName,
|
||||
},
|
||||
series: [
|
||||
{
|
||||
@ -68,6 +81,7 @@ onMounted(() => {
|
||||
data: yData.value,
|
||||
symbol: "circle",
|
||||
symbolSize: 8,
|
||||
smooth: props.smooth,
|
||||
lineStyle: {
|
||||
width: 3,
|
||||
color: "#409EFF",
|
||||
|
||||
108
src/components/Common/Breadcrumb.vue
Normal file
108
src/components/Common/Breadcrumb.vue
Normal file
@ -0,0 +1,108 @@
|
||||
<template>
|
||||
<el-breadcrumb
|
||||
separator="/"
|
||||
style="margin: 15px 5px; font-size: 0.9rem; opacity: 0.7"
|
||||
>
|
||||
<el-breadcrumb-item
|
||||
v-for="(item, idx) in breadcrumbs"
|
||||
:key="item.path || idx"
|
||||
:to="
|
||||
item.path && idx !== breadcrumbs.length - 1
|
||||
? { path: item.path }
|
||||
: undefined
|
||||
"
|
||||
>
|
||||
<span v-if="!item.path || idx === breadcrumbs.length - 1">{{
|
||||
item.title || item.name
|
||||
}}</span>
|
||||
<span v-else>{{ item.title || item.name }}</span>
|
||||
</el-breadcrumb-item>
|
||||
</el-breadcrumb>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from "vue";
|
||||
import { useRoute } from "vue-router";
|
||||
import { menuConfig } from "../../constants/menuConfig.js";
|
||||
|
||||
// 更簡潔的選單查找與麵包屑組裝
|
||||
function findMenuItem(route) {
|
||||
for (const menu of menuConfig) {
|
||||
for (const child of menu.children) {
|
||||
// 靜態路由、名稱
|
||||
if (child.routeName === route.name || child.path === route.path) {
|
||||
return { parent: menu, item: child };
|
||||
}
|
||||
// 動態路由(如 /plant/:id)
|
||||
if (
|
||||
child.routeName === "PlantDetail" &&
|
||||
route.path &&
|
||||
route.path.startsWith("/plant/")
|
||||
) {
|
||||
return { parent: menu, item: child };
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function buildBreadcrumb(route) {
|
||||
const match = findMenuItem(route);
|
||||
if (!match) return [];
|
||||
const crumbs = [
|
||||
{
|
||||
name: match.parent.title,
|
||||
title: match.parent.title,
|
||||
path: null,
|
||||
meta: { title: match.parent.title },
|
||||
},
|
||||
];
|
||||
let itemTitle = match.item.title;
|
||||
if (route.name === "PlantDetail" && route.params.id) {
|
||||
const plantNames = {
|
||||
"1": "四磺子坪",
|
||||
"2": "宜蘭大清水",
|
||||
"3": "宜蘭小清水",
|
||||
};
|
||||
itemTitle = plantNames[route.params.id] || match.item.title;
|
||||
}
|
||||
crumbs.push({
|
||||
name: itemTitle,
|
||||
title: itemTitle,
|
||||
path: match.item.path,
|
||||
meta: { title: itemTitle },
|
||||
});
|
||||
return crumbs;
|
||||
}
|
||||
|
||||
const route = useRoute();
|
||||
|
||||
const breadcrumbs = computed(() => {
|
||||
const menuBreadcrumbs = buildBreadcrumb(route);
|
||||
if (menuBreadcrumbs.length > 0) {
|
||||
return menuBreadcrumbs;
|
||||
}
|
||||
const matchedRoutes = route.matched.filter((r) => r.meta && r.meta.title);
|
||||
if (matchedRoutes.length === 0) {
|
||||
return [];
|
||||
}
|
||||
const currentRoute = matchedRoutes[matchedRoutes.length - 1];
|
||||
if (currentRoute.meta.parentTitle) {
|
||||
return [
|
||||
{
|
||||
title: currentRoute.meta.parentTitle,
|
||||
path: null,
|
||||
},
|
||||
{
|
||||
title: currentRoute.meta.title,
|
||||
path: currentRoute.path,
|
||||
},
|
||||
];
|
||||
}
|
||||
return matchedRoutes.map((r) => ({
|
||||
title: r.meta.title,
|
||||
name: r.name,
|
||||
path: r.path,
|
||||
}));
|
||||
});
|
||||
</script>
|
||||
25
src/components/Common/CustomIframe.vue
Normal file
25
src/components/Common/CustomIframe.vue
Normal file
@ -0,0 +1,25 @@
|
||||
<template>
|
||||
<iframe
|
||||
:src="src"
|
||||
width="100%"
|
||||
frameborder="0"
|
||||
allowfullscreen
|
||||
:style="{ minHeight: minHeight }"
|
||||
></iframe>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
src: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
minHeight: {
|
||||
type: String,
|
||||
default: 'calc(100vh - 156px)',
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
||||
@ -22,7 +22,7 @@ const props = defineProps({
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
height: 175px;
|
||||
height: 180px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.info-list li {
|
||||
93
src/components/Home/PlantMapCard.vue
Normal file
93
src/components/Home/PlantMapCard.vue
Normal file
@ -0,0 +1,93 @@
|
||||
<template>
|
||||
<el-card shadow="always" class="custom-card">
|
||||
<template #header>
|
||||
<span>今日發電量</span>
|
||||
</template>
|
||||
<div id="leaflet-map" style="height: 485px; width: 100%"></div>
|
||||
</el-card>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onMounted, onUnmounted } from "vue";
|
||||
import L from "leaflet";
|
||||
import "leaflet/dist/leaflet.css";
|
||||
import markerIconBlue from "../../assets/marker-icon-blue.png";
|
||||
import markerShadow from "../../assets/marker-shadow.png";
|
||||
const props = defineProps({
|
||||
geothermalMarkers: Array,
|
||||
});
|
||||
let map = null;
|
||||
onMounted(() => {
|
||||
// 若先前已建立 Leaflet map(container 有 _leaflet_id),先移除以避免錯誤
|
||||
const container = document.getElementById("leaflet-map");
|
||||
if (container && container._leaflet_id) {
|
||||
try {
|
||||
// 移除舊的地圖實例
|
||||
const oldMap = container._leaflet_id && L.map(container);
|
||||
if (oldMap && oldMap.remove) oldMap.remove();
|
||||
} catch (e) {
|
||||
// noop
|
||||
}
|
||||
// 清除 id,讓下面正常建立
|
||||
delete container._leaflet_id;
|
||||
}
|
||||
|
||||
// 建立 icon
|
||||
const geothermalIcon = L.icon({
|
||||
iconUrl: markerIconBlue,
|
||||
iconSize: [25, 41],
|
||||
iconAnchor: [12, 41],
|
||||
popupAnchor: [1, -34],
|
||||
shadowUrl: markerShadow,
|
||||
shadowSize: [41, 41],
|
||||
});
|
||||
|
||||
map = L.map("leaflet-map").setView([25.05, 121.6], 9);
|
||||
L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
|
||||
maxZoom: 18,
|
||||
attribution: "© OpenStreetMap contributors",
|
||||
}).addTo(map);
|
||||
|
||||
(props.geothermalMarkers || []).forEach(({ position, popup }) => {
|
||||
L.marker(position, { icon: geothermalIcon })
|
||||
.addTo(map)
|
||||
.bindTooltip(
|
||||
`<h4>${popup.title}</h4>
|
||||
<div class='info'>
|
||||
<div>發電量 : ${popup.desc}</div>
|
||||
</div>
|
||||
`,
|
||||
{
|
||||
direction: "top",
|
||||
permanent: true,
|
||||
className: "custom-tooltip",
|
||||
offset: [0, -50],
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
onUnmounted(() => {
|
||||
if (map) map.remove();
|
||||
});
|
||||
</script>
|
||||
<style scoped>
|
||||
:deep(.custom-tooltip) {
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
border-radius: 5px;
|
||||
box-shadow: 0px 4px 8px rgba(44, 62, 80, 0.24);
|
||||
padding: 10px;
|
||||
}
|
||||
:deep(.custom-tooltip) h4 {
|
||||
margin-bottom: 5px;
|
||||
font-size: 1rem;
|
||||
color: #343a40;
|
||||
margin-top: 0;
|
||||
}
|
||||
:deep(.custom-tooltip) img {
|
||||
width: 100%;
|
||||
border-radius: 10px;
|
||||
}
|
||||
:deep(.custom-tooltip) .info {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
</style>
|
||||
@ -3,7 +3,7 @@
|
||||
<div class="title-area">
|
||||
<el-button class="collapse-btn" @click="toggleCollapse">
|
||||
<img
|
||||
v-if="collapseState"
|
||||
v-if="props.isCollapse"
|
||||
src="../assets/bars-3.svg"
|
||||
alt="展開"
|
||||
style="width: 24px; height: 24px"
|
||||
@ -34,19 +34,18 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, defineProps, defineEmits } from "vue";
|
||||
import { UserFilled } from "@element-plus/icons-vue";
|
||||
|
||||
const props = defineProps({
|
||||
isCollapse: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(["update:isCollapse"]);
|
||||
const collapseState = computed({
|
||||
get: () => props.isCollapse,
|
||||
set: (val: boolean) => emit("update:isCollapse", val),
|
||||
});
|
||||
|
||||
const toggleCollapse = () => {
|
||||
collapseState.value = !collapseState.value;
|
||||
emit("update:isCollapse", !props.isCollapse);
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
21
src/components/Report/ReportDetailTable.vue
Normal file
21
src/components/Report/ReportDetailTable.vue
Normal file
@ -0,0 +1,21 @@
|
||||
<template>
|
||||
<el-card shadow="always" class="custom-card">
|
||||
<template #header>
|
||||
<span>詳細資料</span>
|
||||
</template>
|
||||
<el-table :data="detailData" style="width: 100%">
|
||||
<el-table-column prop="date" label="時間" />
|
||||
<el-table-column prop="kwh" label="發電量(kWh)" />
|
||||
<el-table-column prop="hour" label="發電小時" />
|
||||
<el-table-column prop="pr" label="PR(%)" />
|
||||
<el-table-column prop="temppr" label="溫度修正PR(%)" />
|
||||
<el-table-column prop="datapr" label="PR資料完整度(%)" />
|
||||
</el-table>
|
||||
</el-card>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
detailData: Array
|
||||
});
|
||||
</script>
|
||||
97
src/components/Report/ReportFilter.vue
Normal file
97
src/components/Report/ReportFilter.vue
Normal file
@ -0,0 +1,97 @@
|
||||
<template>
|
||||
<el-card shadow="always" class="custom-card">
|
||||
<div class="header-row">
|
||||
<el-radio-group
|
||||
:model-value="radio"
|
||||
@update:model-value="(val) => $emit('update:radio', val)"
|
||||
size="large"
|
||||
fill="#409eff"
|
||||
>
|
||||
<el-radio-button label="日區間" value="day" />
|
||||
<el-radio-button label="月" value="month" />
|
||||
<el-radio-button label="年" value="year" />
|
||||
<el-radio-button label="歷年" value="all" />
|
||||
</el-radio-group>
|
||||
<template v-if="radio !== 'all'">
|
||||
<el-radio-group
|
||||
:model-value="radio2"
|
||||
@update:model-value="
|
||||
(val) => {
|
||||
$emit('update:radio2', val);
|
||||
handleRadio2Change(val);
|
||||
}
|
||||
"
|
||||
size="large"
|
||||
fill="#409eff"
|
||||
>
|
||||
<el-radio-button
|
||||
v-if="radio === 'day'"
|
||||
label="昨日"
|
||||
value="yesterday"
|
||||
/>
|
||||
<el-radio-button v-if="radio === 'day'" label="今日" value="today" />
|
||||
<el-radio-button
|
||||
v-if="radio === 'month'"
|
||||
label="上個月"
|
||||
value="lastmonth"
|
||||
/>
|
||||
<el-radio-button
|
||||
v-if="radio === 'month'"
|
||||
label="這個月"
|
||||
value="thismonth"
|
||||
/>
|
||||
<el-radio-button
|
||||
v-if="radio === 'year'"
|
||||
label="去年"
|
||||
value="lastyear"
|
||||
/>
|
||||
<el-radio-button
|
||||
v-if="radio === 'year'"
|
||||
label="今年"
|
||||
value="thisyear"
|
||||
/>
|
||||
</el-radio-group>
|
||||
<el-date-picker
|
||||
:model-value="dateValue"
|
||||
@update:model-value="$emit('update:dateValue', $event)"
|
||||
:type="datePickerType"
|
||||
:placeholder="datePickerPlaceholder"
|
||||
size="large"
|
||||
/>
|
||||
</template>
|
||||
<div class="button-row">
|
||||
<el-button type="primary" size="large" :icon="Search">查詢</el-button>
|
||||
<el-button type="success" size="large" :icon="Printer">查詢</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { Search, Printer } from "@element-plus/icons-vue";
|
||||
const props = defineProps({
|
||||
radio: String,
|
||||
radio2: String,
|
||||
dateValue: [String, Object],
|
||||
datePickerType: String,
|
||||
datePickerPlaceholder: String,
|
||||
});
|
||||
const emit = defineEmits([
|
||||
"update:radio",
|
||||
"update:radio2",
|
||||
"update:dateValue",
|
||||
"radio2Change",
|
||||
]);
|
||||
function handleRadio2Change(val) {
|
||||
emit("radio2Change", val);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.header-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
</style>
|
||||
21
src/components/Report/ReportSummaryTable.vue
Normal file
21
src/components/Report/ReportSummaryTable.vue
Normal file
@ -0,0 +1,21 @@
|
||||
<template>
|
||||
<el-card shadow="always" class="custom-card">
|
||||
<template #header>
|
||||
<span>總結</span>
|
||||
</template>
|
||||
<el-table :data="totalData" style="width: 100%">
|
||||
<el-table-column prop="date" label="時間" />
|
||||
<el-table-column prop="kwh" label="發電量(kWh)" />
|
||||
<el-table-column prop="hour" label="發電小時" />
|
||||
<el-table-column prop="pr" label="PR(%)" />
|
||||
<el-table-column prop="temppr" label="溫度修正PR(%)" />
|
||||
<el-table-column prop="datapr" label="PR資料完整度(%)" />
|
||||
</el-table>
|
||||
</el-card>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
totalData: Array
|
||||
});
|
||||
</script>
|
||||
85
src/components/Report/SiteMenu.vue
Normal file
85
src/components/Report/SiteMenu.vue
Normal file
@ -0,0 +1,85 @@
|
||||
<template>
|
||||
<el-card shadow="always" class="custom-card" body-style="padding: 0">
|
||||
<template #header>
|
||||
<el-input
|
||||
:model-value="input"
|
||||
@update:model-value="$emit('update:input', $event)"
|
||||
placeholder="搜尋關鍵字"
|
||||
:suffix-icon="Search"
|
||||
/>
|
||||
</template>
|
||||
<el-menu :default-openeds="openMenus" class="el-menu-demo" mode="vertical">
|
||||
<el-sub-menu index="1">
|
||||
<template #title>
|
||||
<el-icon><Help /></el-icon>
|
||||
<span>新北市</span>
|
||||
</template>
|
||||
<el-menu-item index="1-1">
|
||||
<el-checkbox
|
||||
:model-value="checkedArr[0]"
|
||||
@update:model-value="(val) => updateCheckedArr(0, val)"
|
||||
label="四磺子坪"
|
||||
size="large"
|
||||
/>
|
||||
</el-menu-item>
|
||||
</el-sub-menu>
|
||||
<el-sub-menu index="2">
|
||||
<template #title>
|
||||
<el-icon><Help /></el-icon>
|
||||
<span> 宜蘭縣</span>
|
||||
</template>
|
||||
<el-menu-item index="2-1">
|
||||
<el-checkbox
|
||||
:model-value="checkedArr[1]"
|
||||
@update:model-value="(val) => updateCheckedArr(1, val)"
|
||||
label="宜蘭小清水站"
|
||||
size="large"
|
||||
/>
|
||||
</el-menu-item>
|
||||
<el-menu-item index="2-2">
|
||||
<el-checkbox
|
||||
:model-value="checkedArr[2]"
|
||||
@update:model-value="(val) => updateCheckedArr(2, val)"
|
||||
label="宜蘭結元清水站"
|
||||
size="large"
|
||||
/>
|
||||
</el-menu-item>
|
||||
</el-sub-menu>
|
||||
</el-menu>
|
||||
</el-card>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from "vue";
|
||||
import { Help, Search } from "@element-plus/icons-vue";
|
||||
|
||||
const props = defineProps({
|
||||
input: String,
|
||||
checkedArr: Array,
|
||||
openMenus: Array,
|
||||
});
|
||||
const emit = defineEmits(["update:input", "update:checkedArr"]);
|
||||
|
||||
function updateCheckedArr(idx, val) {
|
||||
const arr = [...props.checkedArr];
|
||||
arr[idx] = val;
|
||||
emit("update:checkedArr", arr);
|
||||
}
|
||||
</script>
|
||||
<style scoped>
|
||||
:deep(.el-sub-menu__title) {
|
||||
font-size: 1rem;
|
||||
box-shadow: 0px 0px 13px 0px rgba(74, 53, 107, 0.08);
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
:deep(.el-sub-menu.is-opened > .el-sub-menu__title) {
|
||||
background-color: #627ca0;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* 子選單內容 */
|
||||
:deep(.el-menu-item) {
|
||||
border-bottom: 1px solid #e8ebf1;
|
||||
}
|
||||
</style>
|
||||
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<el-menu
|
||||
:default-active="$route.name"
|
||||
:default-active="activeMenuIndex"
|
||||
class="el-menu-vertical-demo"
|
||||
background-color="#00152a"
|
||||
active-text-color="#fff"
|
||||
@ -15,76 +15,32 @@
|
||||
/>
|
||||
<span>結元能源</span>
|
||||
</div>
|
||||
<el-sub-menu index="overview">
|
||||
|
||||
<el-sub-menu v-for="menu in menuConfig" :key="menu.id" :index="menu.id">
|
||||
<template #title>
|
||||
<el-icon><Location /></el-icon>
|
||||
<span>總覽</span>
|
||||
<el-icon>
|
||||
<component :is="getIconComponent(menu.icon)" />
|
||||
</el-icon>
|
||||
<span>{{ menu.title }}</span>
|
||||
</template>
|
||||
<el-menu-item index="PlantsMap" @click="$router.push('/plantsMap')">地圖總覽</el-menu-item>
|
||||
<el-menu-item index="PlantsOverview" @click="$router.push('/plants')">電廠總覽</el-menu-item>
|
||||
</el-sub-menu>
|
||||
<el-sub-menu index="factory-info">
|
||||
<template #title>
|
||||
<el-icon><Postcard /></el-icon>
|
||||
<span>電廠資訊</span>
|
||||
</template>
|
||||
<el-menu-item index="factory-1">四磺子坪</el-menu-item>
|
||||
<el-menu-item index="factory-2">宜蘭大清水</el-menu-item>
|
||||
<el-menu-item index="factory-3">宜蘭小清水</el-menu-item>
|
||||
</el-sub-menu>
|
||||
<el-sub-menu index="inspection">
|
||||
<template #title>
|
||||
<el-icon><DocumentChecked /></el-icon>
|
||||
<span>巡檢系統</span>
|
||||
</template>
|
||||
<el-menu-item index="inspection-task">巡檢任務</el-menu-item>
|
||||
<el-menu-item index="inspection-setting">巡檢設定</el-menu-item>
|
||||
</el-sub-menu>
|
||||
<el-sub-menu index="report">
|
||||
<template #title>
|
||||
<el-icon><DataLine /></el-icon>
|
||||
<span>報表查詢</span>
|
||||
</template>
|
||||
<el-menu-item index="report-factory">電廠報表</el-menu-item>
|
||||
</el-sub-menu>
|
||||
<el-sub-menu index="alert">
|
||||
<template #title>
|
||||
<el-icon><Bell /></el-icon>
|
||||
<span>即時告警</span>
|
||||
</template>
|
||||
<el-menu-item index="alert-event">異常事件查詢</el-menu-item>
|
||||
</el-sub-menu>
|
||||
<el-sub-menu index="material">
|
||||
<template #title>
|
||||
<el-icon><MessageBox /></el-icon>
|
||||
<span>備料管理</span>
|
||||
</template>
|
||||
<el-menu-item index="material-item">備品料件管理</el-menu-item>
|
||||
<el-menu-item index="material-location">倉庫櫃位管理</el-menu-item>
|
||||
</el-sub-menu>
|
||||
<el-sub-menu index="security">
|
||||
<template #title>
|
||||
<el-icon><VideoCamera /></el-icon>
|
||||
<span>智慧安防</span>
|
||||
</template>
|
||||
<el-menu-item index="security-system">安防系統</el-menu-item>
|
||||
</el-sub-menu>
|
||||
<el-sub-menu index="system">
|
||||
<template #title>
|
||||
<el-icon><Setting /></el-icon>
|
||||
<span>系統設定</span>
|
||||
</template>
|
||||
<el-menu-item index="system-factory">電廠設定</el-menu-item>
|
||||
<el-menu-item index="system-account">帳號設定</el-menu-item>
|
||||
|
||||
<el-menu-item
|
||||
v-for="item in menu.children"
|
||||
:key="item.id"
|
||||
:index="item.path || item.id"
|
||||
@click="handleMenuClick(item)"
|
||||
>
|
||||
{{ item.title }}
|
||||
</el-menu-item>
|
||||
</el-sub-menu>
|
||||
</el-menu>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, toRefs, defineProps } from "vue";
|
||||
import { computed } from "vue";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import { menuConfig } from "../constants/menuConfig.js";
|
||||
import {
|
||||
Document,
|
||||
Menu as IconMenu,
|
||||
Location,
|
||||
Setting,
|
||||
Postcard,
|
||||
@ -92,8 +48,52 @@ import {
|
||||
DataLine,
|
||||
Bell,
|
||||
MessageBox,
|
||||
VideoCamera
|
||||
VideoCamera,
|
||||
} from "@element-plus/icons-vue";
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
|
||||
// 圖標組件映射
|
||||
const iconComponents = {
|
||||
Location,
|
||||
Setting,
|
||||
Postcard,
|
||||
DocumentChecked,
|
||||
DataLine,
|
||||
Bell,
|
||||
MessageBox,
|
||||
VideoCamera,
|
||||
};
|
||||
|
||||
// 獲取圖標組件
|
||||
const getIconComponent = (iconName) => {
|
||||
return iconComponents[iconName] || Location;
|
||||
};
|
||||
|
||||
// 處理選單點擊
|
||||
const handleMenuClick = (item) => {
|
||||
if (item.path) {
|
||||
if (item.routeName && item.params) {
|
||||
router.push({ name: item.routeName, params: item.params });
|
||||
} else {
|
||||
router.push(item.path);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 計算當前激活的選單項
|
||||
const activeMenuIndex = computed(() => {
|
||||
// 嘗試根據當前路由匹配選單項
|
||||
for (const menu of menuConfig) {
|
||||
for (const item of menu.children) {
|
||||
if (item.routeName === route.name || item.path === route.path) {
|
||||
return item.path || item.id;
|
||||
}
|
||||
}
|
||||
}
|
||||
return route.name || route.path;
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@ -141,9 +141,9 @@ import {
|
||||
|
||||
/* 為 menu-item 添加圓點 */
|
||||
:deep(.el-menu-item::before) {
|
||||
content: '';
|
||||
content: "";
|
||||
position: absolute;
|
||||
left:30px;
|
||||
left: 30px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 6px;
|
||||
@ -154,9 +154,9 @@ import {
|
||||
|
||||
/* 選中狀態的圓點為藍色 */
|
||||
:deep(.el-menu-item.is-active::after) {
|
||||
content: '';
|
||||
content: "";
|
||||
position: absolute;
|
||||
right:25px;
|
||||
right: 25px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 6px;
|
||||
@ -179,12 +179,12 @@ import {
|
||||
}
|
||||
|
||||
:deep(.el-menu.el-menu--inline::before) {
|
||||
content: '';
|
||||
content: "";
|
||||
position: absolute;
|
||||
left:32.5px;
|
||||
left: 32.5px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: .8px;
|
||||
width: 0.8px;
|
||||
height: 110%;
|
||||
background-color: #95a2b5;
|
||||
z-index: 80;
|
||||
|
||||
129
src/constants/menuConfig.js
Normal file
129
src/constants/menuConfig.js
Normal file
@ -0,0 +1,129 @@
|
||||
export const menuConfig = [
|
||||
{
|
||||
id: 'overview',
|
||||
title: '總覽',
|
||||
icon: 'Location',
|
||||
children: [
|
||||
{
|
||||
id: 'PlantsMap',
|
||||
title: '地圖總覽',
|
||||
path: '/plantsMap',
|
||||
routeName: 'PlantsMap'
|
||||
},
|
||||
{
|
||||
id: 'PlantsOverview',
|
||||
title: '電廠總覽',
|
||||
path: '/plants',
|
||||
routeName: 'PlantsOverview'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'factory-info',
|
||||
title: '電廠資訊',
|
||||
icon: 'Postcard',
|
||||
children: [
|
||||
{
|
||||
id: 'plant-1',
|
||||
title: '四磺子坪',
|
||||
path: '/plant/1',
|
||||
routeName: 'PlantDetail',
|
||||
params: { id: 1 }
|
||||
},
|
||||
{
|
||||
id: 'plant-2',
|
||||
title: '宜蘭大清水',
|
||||
path: '/plant/2',
|
||||
routeName: 'PlantDetail',
|
||||
params: { id: 2 }
|
||||
},
|
||||
{
|
||||
id: 'plant-3',
|
||||
title: '宜蘭小清水',
|
||||
path: '/plant/3',
|
||||
routeName: 'PlantDetail',
|
||||
params: { id: 3 }
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'inspection',
|
||||
title: '巡檢系統',
|
||||
icon: 'DocumentChecked',
|
||||
children: [
|
||||
{
|
||||
id: 'inspection-task',
|
||||
title: '巡檢任務'
|
||||
},
|
||||
{
|
||||
id: 'inspection-setting',
|
||||
title: '巡檢設定'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'report',
|
||||
title: '報表查詢',
|
||||
icon: 'DataLine',
|
||||
children: [
|
||||
{
|
||||
id: 'PlantsReport',
|
||||
title: '電廠報表',
|
||||
path: '/plantsReport',
|
||||
routeName: 'PlantsReport'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'alert',
|
||||
title: '即時告警',
|
||||
icon: 'Bell',
|
||||
children: [
|
||||
{
|
||||
id: 'alert-event',
|
||||
title: '異常事件查詢'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'material',
|
||||
title: '備料管理',
|
||||
icon: 'MessageBox',
|
||||
children: [
|
||||
{
|
||||
id: 'material-item',
|
||||
title: '備品料件管理'
|
||||
},
|
||||
{
|
||||
id: 'material-location',
|
||||
title: '倉庫櫃位管理'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'security',
|
||||
title: '智慧安防',
|
||||
icon: 'VideoCamera',
|
||||
children: [
|
||||
{
|
||||
id: 'security-system',
|
||||
title: '安防系統'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'system',
|
||||
title: '系統設定',
|
||||
icon: 'Setting',
|
||||
children: [
|
||||
{
|
||||
id: 'system-factory',
|
||||
title: '電廠設定'
|
||||
},
|
||||
{
|
||||
id: 'system-account',
|
||||
title: '帳號設定'
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
@ -1,5 +1,5 @@
|
||||
import { createApp } from "vue";
|
||||
import "./style.css";
|
||||
import "./styles/style.css";
|
||||
import App from "./App.vue";
|
||||
import router from "./router";
|
||||
import { createPinia } from "pinia";
|
||||
|
||||
@ -1,21 +1,52 @@
|
||||
import { createRouter, createWebHashHistory } from "vue-router";
|
||||
import Home from "../views/Home.vue";
|
||||
import PlantsOverview from "../views/PlantsOverview.vue";
|
||||
import PlantReport from "../views/PlantReport.vue";
|
||||
import PlantDetail from "../views/PlantDetail.vue";
|
||||
|
||||
const routes = [
|
||||
{
|
||||
path: "/plantsMap",
|
||||
name: "PlantsMap",
|
||||
component: Home,
|
||||
meta: { title: "地圖總覽", icon: "Location", menuGroup: "overview" },
|
||||
meta: {
|
||||
title: "地圖總覽",
|
||||
icon: "Location",
|
||||
menuGroup: "overview",
|
||||
parentTitle: "總覽",
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/plants",
|
||||
name: "PlantsOverview",
|
||||
component: PlantsOverview,
|
||||
meta: { title: "電廠總覽", icon: "Postcard", menuGroup: "overview" },
|
||||
meta: {
|
||||
title: "電廠總覽",
|
||||
icon: "Postcard",
|
||||
menuGroup: "overview",
|
||||
parentTitle: "總覽"
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/plant/:id",
|
||||
name: "PlantDetail",
|
||||
component: PlantDetail,
|
||||
meta: {
|
||||
title: "電廠詳細",
|
||||
menuGroup: "factory-info",
|
||||
parentTitle: "電廠資訊",
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/plantsReport",
|
||||
name: "PlantsReport",
|
||||
component: PlantReport,
|
||||
meta: {
|
||||
title: "電廠報表",
|
||||
menuGroup: "report",
|
||||
parentTitle: "報表查詢",
|
||||
},
|
||||
},
|
||||
// 你可以依需求繼續擴充其他路由
|
||||
];
|
||||
|
||||
const router = createRouter({
|
||||
|
||||
@ -11,6 +11,7 @@
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
min-height: 100vh;
|
||||
@ -33,7 +34,8 @@ body {
|
||||
|
||||
.el-card.custom-card .el-card__header{
|
||||
font-weight: 600;
|
||||
font-size: 1rem;
|
||||
font-size: 1.2rem;
|
||||
letter-spacing: 0.8px;
|
||||
color: #213547;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,15 +1,11 @@
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted, onUnmounted } from "vue";
|
||||
import PieChart from "../components/Chart/PieChart.vue";
|
||||
import PlantMapCard from "../components/Home/PlantMapCard.vue";
|
||||
import InfoList from "../components/Home/InfoList.vue";
|
||||
import LineChart from "../components/Chart/LineChart.vue";
|
||||
import ComboBarLineChart from "../components/Chart/ComboBarLineChart.vue";
|
||||
import InfoList from "../components/InfoList.vue";
|
||||
import markerIconBlue from "../assets/marker-icon-blue.png";
|
||||
import markerShadow from "../assets/marker-shadow.png";
|
||||
import L from "leaflet";
|
||||
import "leaflet/dist/leaflet.css";
|
||||
|
||||
const avgData = [
|
||||
import ComboBarLineChart from '../components/Chart/ComboBarLineChart.vue';
|
||||
const rawAvgData = [
|
||||
{
|
||||
year: 2021,
|
||||
data: [
|
||||
@ -52,6 +48,12 @@ const avgData = [
|
||||
},
|
||||
];
|
||||
|
||||
const avgData = rawAvgData.map((item) => ({
|
||||
year: item.year,
|
||||
...Object.fromEntries(item.data.map((d) => [d.name, d.value])),
|
||||
total: item.data.reduce((sum, d) => sum + d.value, 0),
|
||||
}));
|
||||
|
||||
const weekTrend = [
|
||||
{ time: "周一", value: 120 },
|
||||
{ time: "周二", value: 132 },
|
||||
@ -63,6 +65,18 @@ const weekTrend = [
|
||||
];
|
||||
|
||||
const unconfirmed = ref(2014);
|
||||
// 異常狀態項目
|
||||
const abnormalItems = [
|
||||
{ label: "已復歸", value: 1234, style: "color: #67C23A; font-size: 2rem;" },
|
||||
{ label: "未復歸", value: 1345, style: "color: #F56C6C; font-size: 2rem;" },
|
||||
{ label: "已確認", value: 1120, style: "color: #409EFF; font-size: 2rem;" },
|
||||
{
|
||||
label: "未確認",
|
||||
value: unconfirmed,
|
||||
style: "color: #E6A23C; font-size: 2rem;",
|
||||
},
|
||||
];
|
||||
|
||||
const elecData = ref([
|
||||
{ value: 42, name: "大清水" },
|
||||
{ value: 26, name: "小清水" },
|
||||
@ -82,16 +96,6 @@ const plantAccumulate = reactive([
|
||||
{ label: "四磺子坪", value: 100, unit: "MW" },
|
||||
]);
|
||||
|
||||
// 自訂地熱 icon
|
||||
const geothermalIcon = L.icon({
|
||||
iconUrl: markerIconBlue,
|
||||
iconSize: [25, 41],
|
||||
iconAnchor: [12, 41],
|
||||
popupAnchor: [1, -34],
|
||||
shadowUrl: markerShadow,
|
||||
shadowSize: [41, 41],
|
||||
});
|
||||
|
||||
// 地熱廠 marker 資料
|
||||
const geothermalMarkers = [
|
||||
{
|
||||
@ -118,32 +122,15 @@ const geothermalMarkers = [
|
||||
|
||||
let timer = null;
|
||||
|
||||
const yearlySeries = [
|
||||
{ type: "bar", field: "大清水", name: "大清水", color: "#5070dd" },
|
||||
{ type: "bar", field: "小清水", name: "小清水", color: "#b6d634" },
|
||||
{ type: "bar", field: "四磺子坪", name: "四磺子坪", color: "#505372" },
|
||||
{ type: "line", field: "total", name: "總發電量", color: "#409EFF" },
|
||||
];
|
||||
|
||||
onMounted(() => {
|
||||
// 初始化 OpenStreetMap 地圖
|
||||
const map = L.map("leaflet-map").setView([25.05, 121.6], 9);
|
||||
L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
|
||||
maxZoom: 18,
|
||||
attribution: "© OpenStreetMap contributors",
|
||||
}).addTo(map);
|
||||
|
||||
geothermalMarkers.forEach(({ position, popup }) => {
|
||||
L.marker(position, { icon: geothermalIcon })
|
||||
.addTo(map)
|
||||
.bindTooltip(
|
||||
`<h4>${popup.title}</h4>
|
||||
<div class="info">
|
||||
<div>發電量 : ${popup.desc}</div>
|
||||
</div>
|
||||
`,
|
||||
{
|
||||
direction: "top",
|
||||
permanent: true,
|
||||
className: "custom-tooltip",
|
||||
offset: [0, -50], // Y 軸向上偏移 30px
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
// timer 與其他非地圖相關初始化保留在父層
|
||||
timer = setInterval(() => {
|
||||
unconfirmed.value++;
|
||||
|
||||
@ -162,42 +149,26 @@ onUnmounted(() => {
|
||||
<template>
|
||||
<el-row :gutter="20" style="margin-top: 20px">
|
||||
<el-col :xs="24" :lg="7">
|
||||
<el-card shadow class="custom-card">
|
||||
<el-card shadow="always" class="custom-card">
|
||||
<template #header>
|
||||
<span>異常狀態</span>
|
||||
</template>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12" style="margin-bottom: 10px">
|
||||
<el-col
|
||||
v-for="(item, idx) in abnormalItems"
|
||||
:key="idx"
|
||||
:span="12"
|
||||
:style="idx % 2 === 0 ? 'margin-bottom: 10px' : ''"
|
||||
>
|
||||
<el-statistic
|
||||
title="已復歸"
|
||||
:value="1234"
|
||||
value-style="color: #67C23A; font-size: 2rem;"
|
||||
/>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-statistic
|
||||
title="未復歸"
|
||||
:value="1345"
|
||||
value-style="color: #F56C6C; font-size: 2rem;"
|
||||
/>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-statistic
|
||||
title="已確認"
|
||||
:value="1120"
|
||||
value-style="color: #409EFF; font-size: 2rem;"
|
||||
/>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-statistic
|
||||
title="未確認"
|
||||
:value="unconfirmed"
|
||||
value-style="color: #E6A23C; font-size: 2rem;"
|
||||
:title="item.label"
|
||||
:value="item.value"
|
||||
:value-style="item.style || 'font-size: 2rem;'"
|
||||
/>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-card>
|
||||
<el-card shadow class="custom-card">
|
||||
<el-card shadow="always" class="custom-card">
|
||||
<template #header>
|
||||
<span>發電分布</span>
|
||||
</template>
|
||||
@ -205,21 +176,16 @@ onUnmounted(() => {
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :xs="24" :lg="10">
|
||||
<el-card shadow class="custom-card">
|
||||
<template #header>
|
||||
<span>今日發電量</span>
|
||||
</template>
|
||||
<div id="leaflet-map" style="height: 470px; width: 100%"></div>
|
||||
</el-card>
|
||||
<PlantMapCard :geothermalMarkers="geothermalMarkers" />
|
||||
</el-col>
|
||||
<el-col :xs="24" :lg="7">
|
||||
<el-card shadow class="custom-card">
|
||||
<el-card shadow="always" class="custom-card">
|
||||
<template #header>
|
||||
<span>電廠概覽</span>
|
||||
</template>
|
||||
<InfoList :data="plantOverview" />
|
||||
</el-card>
|
||||
<el-card shadow class="custom-card">
|
||||
<el-card shadow="always" class="custom-card">
|
||||
<template #header>
|
||||
<span>累積發電</span>
|
||||
</template>
|
||||
@ -227,7 +193,7 @@ onUnmounted(() => {
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :xs="24" :lg="12">
|
||||
<el-card shadow class="custom-card">
|
||||
<el-card shadow="always" class="custom-card">
|
||||
<template #header>
|
||||
<span>發電趨勢</span>
|
||||
</template>
|
||||
@ -235,39 +201,23 @@ onUnmounted(() => {
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :xs="24" :lg="12">
|
||||
<el-card shadow class="custom-card">
|
||||
<el-card shadow="always" class="custom-card">
|
||||
<template #header>
|
||||
<span>年平均發電比較</span>
|
||||
</template>
|
||||
<ComboBarLineChart :data="avgData" />
|
||||
<ComboBarLineChart
|
||||
:data="avgData"
|
||||
xField="year"
|
||||
:series="yearlySeries"
|
||||
yLeftUnit="MW"
|
||||
/>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
<script setup></script>
|
||||
|
||||
<style scoped>
|
||||
:deep(.el-statistic__head) {
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
:deep(.custom-tooltip) {
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
border-radius: 5px;
|
||||
box-shadow: 0px 4px 8px rgba(44, 62, 80, 0.24);
|
||||
padding: 10px;
|
||||
}
|
||||
:deep(.custom-tooltip) h4 {
|
||||
margin-bottom: 5px;
|
||||
font-size: 1rem;
|
||||
color: #343a40;
|
||||
margin-top: 0;
|
||||
}
|
||||
:deep(.custom-tooltip) img {
|
||||
width: 100%;
|
||||
border-radius: 10px;
|
||||
}
|
||||
:deep(.custom-tooltip) .info {
|
||||
font-size: .9rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
139
src/views/PlantDetail.vue
Normal file
139
src/views/PlantDetail.vue
Normal file
@ -0,0 +1,139 @@
|
||||
<template>
|
||||
<Breadcrumb />
|
||||
<el-row :gutter="20" style="margin-top: 20px">
|
||||
<el-col :xs="24">
|
||||
<el-card
|
||||
shadow="always"
|
||||
class="custom-card"
|
||||
style="margin-bottom: 0"
|
||||
:body-style="{ paddingTop: '16px', paddingBottom: '0' }"
|
||||
>
|
||||
<template #header>
|
||||
<span style="">{{ title }}</span>
|
||||
</template>
|
||||
<el-tabs
|
||||
v-model="activeName"
|
||||
type="border-card"
|
||||
class="demo-tabs"
|
||||
@tab-click="handleClick"
|
||||
>
|
||||
<el-tab-pane
|
||||
v-for="tab in tabList"
|
||||
:key="tab.name"
|
||||
:name="tab.name"
|
||||
>
|
||||
<template #label>
|
||||
<el-icon><component :is="tab.icon" /></el-icon>
|
||||
<span style="margin-left: 4px">{{ tab.label }}</span>
|
||||
</template>
|
||||
<template v-if="tab.src && activeName === tab.name">
|
||||
<CustomIframe :src="tab.src" minHeight="calc(100vh - 300px)" />
|
||||
</template>
|
||||
<template v-else-if="tab.name === 'abnormal-record'">
|
||||
異常紀錄
|
||||
</template>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from "vue";
|
||||
import { useRouter, useRoute } from "vue-router";
|
||||
import Breadcrumb from "../components/Common/Breadcrumb.vue";
|
||||
import CustomIframe from "../components/Common/CustomIframe.vue";
|
||||
import {
|
||||
Odometer,
|
||||
Postcard,
|
||||
ScaleToOriginal,
|
||||
Document,
|
||||
Coin,
|
||||
Warning,
|
||||
Operation,
|
||||
} from "@element-plus/icons-vue";
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
const title = computed(() => {
|
||||
if (route.params.id === "1") return "四磺子坪";
|
||||
if (route.params.id === "2") return "宜蘭大清水";
|
||||
if (route.params.id === "3") return "宜蘭小清水";
|
||||
return "";
|
||||
});
|
||||
|
||||
const siteId = computed(() => {
|
||||
if (route.params.id === "1") return "J022270001";
|
||||
if (route.params.id === "2") return "J033110001";
|
||||
if (route.params.id === "3") return "J033110002";
|
||||
return "";
|
||||
});
|
||||
|
||||
const activeName = ref("system-status");
|
||||
const handleClick = (tab, event) => {
|
||||
console.log("tab", tab, event);
|
||||
};
|
||||
|
||||
const tabList = computed(() => [
|
||||
{
|
||||
name: "system-status",
|
||||
label: "系統狀態",
|
||||
icon: Odometer,
|
||||
src: `/ord/station:|slot:/${siteId.value}/PID|view:?fullScreen=true`,
|
||||
},
|
||||
{
|
||||
name: "realtime-info",
|
||||
label: "即時資訊",
|
||||
icon: Postcard,
|
||||
src: `/ord/station:|slot:/${siteId.value}/RealTimeInformation|view:?fullScreen=true`,
|
||||
},
|
||||
{
|
||||
name: "basic-info",
|
||||
label: "基本資料",
|
||||
icon: ScaleToOriginal,
|
||||
src: `/ord/station:|slot:/${siteId.value}/Information|view:?fullScreen=true`,
|
||||
},
|
||||
{
|
||||
name: "device-info",
|
||||
label: "設備資訊",
|
||||
icon: Document,
|
||||
src: `/ord/station:|slot:/${siteId.value}/DeviceInformation|view:?fullScreen=true`,
|
||||
},
|
||||
{
|
||||
name: "history-data",
|
||||
label: "歷史資料",
|
||||
icon: Coin,
|
||||
src: `/ord/station:|slot:/${siteId.value}/History|view:?fullScreen=true`,
|
||||
},
|
||||
{
|
||||
name: "abnormal-record",
|
||||
label: "異常紀錄",
|
||||
icon: Warning,
|
||||
src: null,
|
||||
},
|
||||
]);
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
:deep(.el-tabs__header) {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.el-tabs--border-card {
|
||||
border: none;
|
||||
}
|
||||
|
||||
:deep(.el-tabs__item) {
|
||||
margin-top: 0px !important;
|
||||
margin-left: 0px !important;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
:deep(.el-tabs__item.is-active) {
|
||||
border-top-color: var(--el-border-color);
|
||||
}
|
||||
|
||||
:deep(.el-tabs--border-card) > .el-tabs__content {
|
||||
padding: 10px 0px;
|
||||
}
|
||||
</style>
|
||||
142
src/views/PlantReport.vue
Normal file
142
src/views/PlantReport.vue
Normal file
@ -0,0 +1,142 @@
|
||||
<template>
|
||||
<Breadcrumb />
|
||||
<el-row :gutter="20" style="margin-top: 20px">
|
||||
<el-col :xs="24" :md="5">
|
||||
<SiteMenu
|
||||
:input="input"
|
||||
:checkedArr="checkedArr"
|
||||
:openMenus="openMenus"
|
||||
@update:input="(val) => (input = val)"
|
||||
@update:checkedArr="(val) => (checkedArr = val)"
|
||||
/>
|
||||
</el-col>
|
||||
<el-col :xs="24" :md="19">
|
||||
<ReportFilter
|
||||
:radio="radio"
|
||||
:radio2="radio2"
|
||||
:dateValue="dateValue"
|
||||
:datePickerType="datePickerType"
|
||||
:datePickerPlaceholder="datePickerPlaceholder"
|
||||
@update:radio="(val) => (radio = val)"
|
||||
@update:radio2="(val) => (radio2 = val)"
|
||||
@update:dateValue="(val) => (dateValue = val)"
|
||||
@radio2Change="handleRadio2Change"
|
||||
/>
|
||||
<ReportSummaryTable :totalData="totalData" />
|
||||
<el-card shadow="always" class="custom-card">
|
||||
<ComboBarLineChart
|
||||
:data="chartData"
|
||||
:xField="'date'"
|
||||
:series="plantSeries"
|
||||
yLeftUnit="kWh"
|
||||
yRightUnit="%"
|
||||
:showToolbox="true"
|
||||
/>
|
||||
</el-card>
|
||||
<ReportDetailTable :detailData="detailData" />
|
||||
</el-col>
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from "vue";
|
||||
import dayjs from "dayjs";
|
||||
import Breadcrumb from "../components/Common/Breadcrumb.vue";
|
||||
import SiteMenu from "../components/Report/SiteMenu.vue";
|
||||
import ReportFilter from "../components/Report/ReportFilter.vue";
|
||||
import ReportSummaryTable from "../components/Report/ReportSummaryTable.vue";
|
||||
import ComboBarLineChart from "../components/Chart/ComboBarLineChart.vue";
|
||||
import ReportDetailTable from "../components/Report/ReportDetailTable.vue";
|
||||
|
||||
const radio = ref("month");
|
||||
const radio2 = ref("thismonth");
|
||||
const dateValue = ref(dayjs().format("YYYY-MM-DD"));
|
||||
const openMenus = ref(["1", "2"]);
|
||||
const input = ref("");
|
||||
const datePickerType = computed(() => {
|
||||
if (radio.value === "day") return "date";
|
||||
if (radio.value === "month") return "month";
|
||||
if (radio.value === "year") return "year";
|
||||
if (radio.value === "all") return "daterange";
|
||||
return "date";
|
||||
});
|
||||
const datePickerPlaceholder = computed(() => {
|
||||
if (radio.value === "day") return "選擇日期";
|
||||
if (radio.value === "month") return "選擇月份";
|
||||
if (radio.value === "year") return "選擇年份";
|
||||
if (radio.value === "all") return "";
|
||||
return "選擇日期";
|
||||
});
|
||||
function handleRadio2Change(val) {
|
||||
if (radio.value === "month") {
|
||||
if (val === "lastmonth") {
|
||||
dateValue.value = dayjs().subtract(1, "month").format("YYYY-MM");
|
||||
} else if (val === "thismonth") {
|
||||
dateValue.value = dayjs().format("YYYY-MM");
|
||||
}
|
||||
} else if (radio.value === "year") {
|
||||
if (val === "lastyear") {
|
||||
dateValue.value = dayjs().subtract(1, "year").format("YYYY");
|
||||
} else if (val === "thisyear") {
|
||||
dateValue.value = dayjs().format("YYYY");
|
||||
}
|
||||
} else if (radio.value === "day") {
|
||||
if (val === "yesterday") {
|
||||
dateValue.value = dayjs().subtract(1, "day").format("YYYY-MM-DD");
|
||||
} else if (val === "today") {
|
||||
dateValue.value = dayjs().format("YYYY-MM-DD");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 電廠勾選
|
||||
const checkedArr = ref([false, false, false]);
|
||||
|
||||
// 詳細資料 (20筆範例)
|
||||
const detailData = Array.from({ length: 20 }, (_, i) => {
|
||||
const day = (i + 1).toString().padStart(2, "0");
|
||||
return {
|
||||
date: `2025-10-${day}`,
|
||||
kwh: (Math.random() * 100 + 100).toFixed(2),
|
||||
hour: (Math.random() * 10 + 10).toFixed(2),
|
||||
pr: (Math.random() * 10 + 90).toFixed(2),
|
||||
temppr: (Math.random() * 10 + 90).toFixed(2),
|
||||
datapr: (Math.random() * 10 + 90).toFixed(2),
|
||||
};
|
||||
});
|
||||
|
||||
// 總結資料 (1筆範例)
|
||||
const totalData = [
|
||||
{
|
||||
date: "2025-10",
|
||||
kwh: detailData.reduce((sum, d) => sum + parseFloat(d.kwh), 0).toFixed(2),
|
||||
hour: detailData.reduce((sum, d) => sum + parseFloat(d.hour), 0).toFixed(2),
|
||||
pr: (
|
||||
detailData.reduce((sum, d) => sum + parseFloat(d.pr), 0) /
|
||||
detailData.length
|
||||
).toFixed(2),
|
||||
temppr: (
|
||||
detailData.reduce((sum, d) => sum + parseFloat(d.temppr), 0) /
|
||||
detailData.length
|
||||
).toFixed(2),
|
||||
datapr: (
|
||||
detailData.reduce((sum, d) => sum + parseFloat(d.datapr), 0) /
|
||||
detailData.length
|
||||
).toFixed(2),
|
||||
},
|
||||
];
|
||||
|
||||
// 圖表資料:x軸為日期,發電量為 bar,溫度為 line
|
||||
const chartData = detailData.map((d) => ({
|
||||
date: dayjs(d.date).format("MM-DD"),
|
||||
kwh: Number(d.kwh),
|
||||
temp: Number(d.temppr),
|
||||
}));
|
||||
// 圖表系列設定
|
||||
const plantSeries = [
|
||||
{ type: "bar", field: "kwh", name: "發電量", color: "#91cc75" },
|
||||
{ type: "line", field: "temp", name: "溫度修正PR(%)", color: "#409EFF" },
|
||||
];
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
333
src/views/PlantsOverview copy.vue
Normal file
333
src/views/PlantsOverview copy.vue
Normal file
@ -0,0 +1,333 @@
|
||||
<template>
|
||||
<Breadcrumb />
|
||||
<el-row :gutter="20" style="margin-top: 20px">
|
||||
<el-col :xs="24">
|
||||
<el-card shadow="always" class="custom-card">
|
||||
<template #header>
|
||||
<span style="">電廠總覽</span>
|
||||
</template>
|
||||
<el-row :gutter="20">
|
||||
<el-col :xs="24" style="margin-bottom: 15px">
|
||||
<div class="button-row">
|
||||
<el-button type="info" size="large">全選</el-button>
|
||||
<el-button type="primary" size="large"
|
||||
>新北市
|
||||
<span class="badge">1</span>
|
||||
</el-button>
|
||||
<el-button type="primary" size="large"
|
||||
>宜蘭縣
|
||||
<span class="badge">2</span>
|
||||
</el-button>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :xs="24" style="margin-bottom: 15px">
|
||||
<div class="button-row" style="display: flex; gap: 12px">
|
||||
<el-button type="info" size="large">全選</el-button>
|
||||
<el-checkbox
|
||||
v-model="checked1"
|
||||
size="large"
|
||||
style="margin: 0"
|
||||
border
|
||||
>
|
||||
<span style="vertical-align: middle; margin-right: 4px"
|
||||
>設備正常</span
|
||||
>
|
||||
<el-icon
|
||||
style="vertical-align: middle"
|
||||
color="#67C23A"
|
||||
size="large"
|
||||
><CircleCheckFilled
|
||||
/></el-icon>
|
||||
</el-checkbox>
|
||||
<el-checkbox
|
||||
v-model="checked2"
|
||||
size="large"
|
||||
style="margin: 0"
|
||||
border
|
||||
>
|
||||
<span style="vertical-align: middle; margin-right: 4px"
|
||||
>設備斷線</span
|
||||
>
|
||||
<el-icon
|
||||
style="vertical-align: middle"
|
||||
color="#E6A23C"
|
||||
size="large"
|
||||
><WarningFilled
|
||||
/></el-icon>
|
||||
</el-checkbox>
|
||||
<el-checkbox v-model="checked3" size="large" border>
|
||||
<span style="vertical-align: middle; margin-right: 4px"
|
||||
>設備斷線</span
|
||||
>
|
||||
<el-icon
|
||||
style="vertical-align: middle"
|
||||
color="#F56C6C"
|
||||
size="large"
|
||||
><RemoveFilled
|
||||
/></el-icon>
|
||||
</el-checkbox>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :xs="24" style="margin-bottom: 15px">
|
||||
<div
|
||||
class="button-row"
|
||||
style="display: flex; gap: 10px; align-items: center"
|
||||
>
|
||||
<el-button size="large" text>排序條件</el-button>
|
||||
<el-select
|
||||
v-model="value1"
|
||||
placeholder="Select"
|
||||
style="width: 150px"
|
||||
size="large"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in options1"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
<el-select
|
||||
v-model="value2"
|
||||
placeholder="Select"
|
||||
style="width: 150px"
|
||||
size="large"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in options2"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :xs="24">
|
||||
<el-row :gutter="40" style="margin-top: 10px">
|
||||
<el-col
|
||||
v-for="plant in plants"
|
||||
:key="plant.name"
|
||||
:xs="24"
|
||||
:sm="12"
|
||||
:md="8"
|
||||
>
|
||||
<el-card class="plants-card">
|
||||
<div class="plant-img-wrap">
|
||||
<img :src="plant.img" class="plant-img" />
|
||||
</div>
|
||||
<div class="plant-info-tilte">
|
||||
<el-icon :color="plant.statusColor" size="25">
|
||||
<component :is="plant.statusIcon" />
|
||||
</el-icon>
|
||||
<h3>{{ plant.name }}</h3>
|
||||
<p>
|
||||
<span>{{ plant.temp }}</span>
|
||||
<el-icon size="25">
|
||||
<Sunny />
|
||||
</el-icon>
|
||||
</p>
|
||||
</div>
|
||||
<ul class="plant-info-list">
|
||||
<li>
|
||||
<span>發電量</span>
|
||||
<div>
|
||||
<span class="value">{{ plant.power }}</span>
|
||||
<span>kWh</span>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<span>PR值</span>
|
||||
<div>
|
||||
<span class="value">{{ plant.pr }}</span>
|
||||
<span>%</span>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
<LineChart
|
||||
:data="plant.weekTrend"
|
||||
:minHeight="180"
|
||||
yAxisName="kWh"
|
||||
:smooth="true"
|
||||
/>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from "vue";
|
||||
import { ElIcon } from "element-plus";
|
||||
import {
|
||||
CircleCheckFilled,
|
||||
WarningFilled,
|
||||
RemoveFilled,
|
||||
Sunny,
|
||||
} from "@element-plus/icons-vue";
|
||||
|
||||
const checked1 = ref(false);
|
||||
const checked2 = ref(false);
|
||||
const checked3 = ref(false);
|
||||
|
||||
const value1 = ref(1);
|
||||
const value2 = ref(1);
|
||||
const options1 = [
|
||||
{ value: 1, label: "發電量 - 正序" },
|
||||
{ value: 2, label: "發電量 - 倒序" },
|
||||
];
|
||||
const options2 = [
|
||||
{ value: 1, label: "PR值 - 正序" },
|
||||
{ value: 2, label: "PR值 - 倒序" },
|
||||
];
|
||||
|
||||
const plants = [
|
||||
{
|
||||
name: "四磺子坪",
|
||||
power: 185,
|
||||
pr: 90,
|
||||
temp: "25°C",
|
||||
status: "正常",
|
||||
statusColor: "#67C23A",
|
||||
statusIcon: CircleCheckFilled,
|
||||
img: "https://192.168.0.206:8820/file/images/plants/plant01.png",
|
||||
weekTrend: [
|
||||
{ time: "周一", value: 120 },
|
||||
{ time: "周二", value: 132 },
|
||||
{ time: "周三", value: 121 },
|
||||
{ time: "周四", value: 134 },
|
||||
{ time: "周五", value: 180 },
|
||||
{ time: "周六", value: 230 },
|
||||
{ time: "周日", value: 210 },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "大清水",
|
||||
power: 170,
|
||||
pr: 91,
|
||||
temp: "27°C",
|
||||
status: "正常",
|
||||
statusColor: "#67C23A",
|
||||
statusIcon: CircleCheckFilled,
|
||||
img: "https://192.168.0.206:8820/file/images/plants/plant02.png",
|
||||
weekTrend: [
|
||||
{ time: "周一", value: 110 },
|
||||
{ time: "周二", value: 120 },
|
||||
{ time: "周三", value: 95 },
|
||||
{ time: "周四", value: 230 },
|
||||
{ time: "周五", value: 150 },
|
||||
{ time: "周六", value: 200 },
|
||||
{ time: "周日", value: 110 },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "大清水",
|
||||
power: 117,
|
||||
pr: 93,
|
||||
temp: "27°C",
|
||||
status: "正常",
|
||||
statusColor: "#67C23A",
|
||||
statusIcon: CircleCheckFilled,
|
||||
img: "https://192.168.0.206:8820/file/images/plants/plant03.png",
|
||||
weekTrend: [
|
||||
{ time: "周一", value: 100 },
|
||||
{ time: "周二", value: 140 },
|
||||
{ time: "周三", value: 120 },
|
||||
{ time: "周四", value: 180 },
|
||||
{ time: "周五", value: 140 },
|
||||
{ time: "周六", value: 200 },
|
||||
{ time: "周日", value: 120 },
|
||||
],
|
||||
},
|
||||
];
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.badge {
|
||||
padding: 0.25em 0.6em;
|
||||
font-size: 75%;
|
||||
line-height: 1.3;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
vertical-align: baseline;
|
||||
border-radius: 4px;
|
||||
background: var(--el-color-primary-dark-2);
|
||||
margin-left: 0.6em;
|
||||
}
|
||||
|
||||
:deep(.el-card.plants-card) {
|
||||
border-radius: 6px;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.08);
|
||||
background: #fff;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
:deep(.el-card.plants-card) .el-card__body {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.plant-img-wrap {
|
||||
width: 100%;
|
||||
height: 250px;
|
||||
background: #eee;
|
||||
overflow: hidden;
|
||||
}
|
||||
.plant-img {
|
||||
width: 100%;
|
||||
object-fit: cover;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.plant-info-tilte {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 12px 15px;
|
||||
background: #fff;
|
||||
gap: 10px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
font-size: 1rem;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.plant-info-tilte h3 {
|
||||
font-size: 1.2rem;
|
||||
font-weight: 600;
|
||||
margin: 0;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.plant-info-tilte p {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.plant-info-list {
|
||||
list-style: none;
|
||||
padding: 10px 20px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.plant-info-list li {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
justify-content: space-between;
|
||||
padding: 8px 0;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.plant-info-list .value {
|
||||
color: #409eff;
|
||||
font-size: 1.2rem;
|
||||
font-weight: bold;
|
||||
margin: 0 8px;
|
||||
width: 60%;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
@ -1,14 +1,17 @@
|
||||
<template>
|
||||
<h1>電廠總覽</h1>
|
||||
<p>這裡是電廠總覽頁面,請依需求補充內容。</p>
|
||||
<Breadcrumb />
|
||||
<el-row :gutter="20" style="margin-top: 20px">
|
||||
<el-col :xs="24">
|
||||
<CustomIframe
|
||||
src="/ord/file:^px/All.px|view:hx:HxPxView|view:?fullScreen=true"
|
||||
minHeight="calc(100vh - 156px)"
|
||||
/>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
// 可在此引入資料與元件
|
||||
import Breadcrumb from "../components/Common/Breadcrumb.vue";
|
||||
import CustomIframe from "../components/Common/CustomIframe.vue";
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.plants-overview {
|
||||
padding: 2rem;
|
||||
}
|
||||
</style>
|
||||
<style scoped></style>
|
||||
|
||||
@ -6,7 +6,7 @@ import { ElementPlusResolver } from "unplugin-vue-components/resolvers";
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig({
|
||||
base: './',
|
||||
base: "./",
|
||||
plugins: [
|
||||
vue(),
|
||||
AutoImport({
|
||||
@ -14,6 +14,7 @@ export default defineConfig({
|
||||
dts: "src/auto-imports.d.ts",
|
||||
}),
|
||||
Components({
|
||||
dirs: [], // 不自動掃描本地元件
|
||||
resolvers: [ElementPlusResolver()],
|
||||
dts: "src/components.d.ts",
|
||||
}),
|
||||
|
||||
Loading…
Reference in New Issue
Block a user