fix: 首頁與歷史資料頁圖表顯示優化

首頁:

1. 室內/冷藏與溫度/濕度位置互換

2. 碳排圖表改為顯示「每日」資料

3. 平面圖點擊 icons 顯示彈跳視窗

歷史資料頁:

1. 調整折線圖 X 軸過密的顯示問題
This commit is contained in:
MJM_2025_05\polly 2025-07-31 11:01:07 +08:00
parent 995cd77bef
commit 2e15353384
19 changed files with 5488 additions and 214 deletions

2
.gitattributes vendored Normal file
View File

@ -0,0 +1,2 @@
* text=auto
*.html text eol=lf

341
package-lock.json generated
View File

@ -40,6 +40,7 @@
"autoprefixer": "^10.4.16",
"daisyui": "^4.4.17",
"postcss": "^8.4.31",
"rollup-plugin-visualizer": "^6.0.3",
"sass": "^1.69.5",
"sass-loader": "^13.3.2",
"tailwindcss": "^3.3.5",
@ -1815,6 +1816,44 @@
"node": ">=0.10.0"
}
},
"node_modules/cliui": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
"integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
"dev": true,
"license": "ISC",
"dependencies": {
"string-width": "^4.2.0",
"strip-ansi": "^6.0.1",
"wrap-ansi": "^7.0.0"
},
"engines": {
"node": ">=12"
}
},
"node_modules/cliui/node_modules/ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/cliui/node_modules/strip-ansi": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"dev": true,
"license": "MIT",
"dependencies": {
"ansi-regex": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/clone": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz",
@ -1837,6 +1876,26 @@
"node": ">=0.10.0"
}
},
"node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"color-name": "~1.1.4"
},
"engines": {
"node": ">=7.0.0"
}
},
"node_modules/color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"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",
@ -2159,6 +2218,16 @@
"node": ">=0.10"
}
},
"node_modules/define-lazy-prop": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz",
"integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/define-property": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz",
@ -2282,6 +2351,13 @@
"integrity": "sha512-KD6CWjf1BnQG+NsXuyiTDDT1eV13sKuYsOUioXkQweYTQIbgHkXPry9K7M+7cKtYHnSUPitVaLrXYB1jTkkYrw==",
"dev": true
},
"node_modules/emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
"dev": true,
"license": "MIT"
},
"node_modules/emojis-list": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz",
@ -2763,6 +2839,16 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/get-caller-file": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
"dev": true,
"license": "ISC",
"engines": {
"node": "6.* || 8.* || >= 10.*"
}
},
"node_modules/get-value": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz",
@ -3119,6 +3205,22 @@
"node": ">= 0.4"
}
},
"node_modules/is-docker": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz",
"integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==",
"dev": true,
"license": "MIT",
"bin": {
"is-docker": "cli.js"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/is-extendable": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz",
@ -3137,6 +3239,16 @@
"node": ">=0.10.0"
}
},
"node_modules/is-fullwidth-code-point": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/is-glob": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
@ -3190,6 +3302,19 @@
"node": ">=0.10.0"
}
},
"node_modules/is-wsl": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz",
"integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==",
"dev": true,
"license": "MIT",
"dependencies": {
"is-docker": "^2.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/isarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
@ -4002,6 +4127,24 @@
"wrappy": "1"
}
},
"node_modules/open": {
"version": "8.4.2",
"resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz",
"integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"define-lazy-prop": "^2.0.0",
"is-docker": "^2.1.1",
"is-wsl": "^2.2.0"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/optimist": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz",
@ -4629,6 +4772,16 @@
"node": ">= 0.12"
}
},
"node_modules/require-directory": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
"integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/requirejs": {
"version": "2.3.6",
"resolved": "https://registry.npmjs.org/requirejs/-/requirejs-2.3.6.tgz",
@ -4711,6 +4864,60 @@
"fsevents": "~2.3.2"
}
},
"node_modules/rollup-plugin-visualizer": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/rollup-plugin-visualizer/-/rollup-plugin-visualizer-6.0.3.tgz",
"integrity": "sha512-ZU41GwrkDcCpVoffviuM9Clwjy5fcUxlz0oMoTXTYsK+tcIFzbdacnrr2n8TXcHxbGKKXtOdjxM2HUS4HjkwIw==",
"dev": true,
"license": "MIT",
"dependencies": {
"open": "^8.0.0",
"picomatch": "^4.0.2",
"source-map": "^0.7.4",
"yargs": "^17.5.1"
},
"bin": {
"rollup-plugin-visualizer": "dist/bin/cli.js"
},
"engines": {
"node": ">=18"
},
"peerDependencies": {
"rolldown": "1.x || ^1.0.0-beta",
"rollup": "2.x || 3.x || 4.x"
},
"peerDependenciesMeta": {
"rolldown": {
"optional": true
},
"rollup": {
"optional": true
}
}
},
"node_modules/rollup-plugin-visualizer/node_modules/picomatch": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/rollup-plugin-visualizer/node_modules/source-map": {
"version": "0.7.6",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz",
"integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==",
"dev": true,
"license": "BSD-3-Clause",
"engines": {
"node": ">= 12"
}
},
"node_modules/run-parallel": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
@ -5224,6 +5431,44 @@
"safe-buffer": "~5.2.0"
}
},
"node_modules/string-width": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"dev": true,
"license": "MIT",
"dependencies": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^6.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/string-width/node_modules/ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/string-width/node_modules/strip-ansi": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"dev": true,
"license": "MIT",
"dependencies": {
"ansi-regex": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/strip-ansi": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
@ -6384,6 +6629,63 @@
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
"license": "0BSD"
},
"node_modules/wrap-ansi": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"ansi-styles": "^4.0.0",
"string-width": "^4.1.0",
"strip-ansi": "^6.0.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
"node_modules/wrap-ansi/node_modules/ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/wrap-ansi/node_modules/ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dev": true,
"license": "MIT",
"dependencies": {
"color-convert": "^2.0.1"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/wrap-ansi/node_modules/strip-ansi": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"dev": true,
"license": "MIT",
"dependencies": {
"ansi-regex": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
@ -6411,6 +6713,16 @@
}
}
},
"node_modules/y18n": {
"version": "5.0.8",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
"integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
"dev": true,
"license": "ISC",
"engines": {
"node": ">=10"
}
},
"node_modules/yaml": {
"version": "2.3.4",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.4.tgz",
@ -6420,6 +6732,35 @@
"node": ">= 14"
}
},
"node_modules/yargs": {
"version": "17.7.2",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
"integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
"dev": true,
"license": "MIT",
"dependencies": {
"cliui": "^8.0.1",
"escalade": "^3.1.1",
"get-caller-file": "^2.0.5",
"require-directory": "^2.1.1",
"string-width": "^4.2.3",
"y18n": "^5.0.5",
"yargs-parser": "^21.1.1"
},
"engines": {
"node": ">=12"
}
},
"node_modules/yargs-parser": {
"version": "21.1.1",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
"integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
"dev": true,
"license": "ISC",
"engines": {
"node": ">=12"
}
},
"node_modules/yup": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/yup/-/yup-1.4.0.tgz",

View File

@ -41,6 +41,7 @@
"autoprefixer": "^10.4.16",
"daisyui": "^4.4.17",
"postcss": "^8.4.31",
"rollup-plugin-visualizer": "^6.0.3",
"sass": "^1.69.5",
"sass-loader": "^13.3.2",
"tailwindcss": "^3.3.5",

View File

@ -16,6 +16,8 @@
"formats": "档案格式"
},
"dashboard": {
"production_quantity": "生产量",
"today_production_rate": "今日生产完成率",
"yesterday_today": "昨天/今天",
"elec_consumption_comparison": "用电量比较",
"elec_consumption_comparison_trend": "用电量比较趋势",
@ -45,8 +47,10 @@
"last_month": "上月",
"this_year": "今年",
"last_year": "去年",
"refrig_temp": "冷藏温度",
"indoor_temp": "室内温度",
"refrig_chart": "冷藏趨勢",
"indoor_chart": "室內趨勢",
"temperature": "温度",
"humidity": "湿度",
"alerts_data": "异常资料"
},
"history": {
@ -91,7 +95,7 @@
"year_elec_consumption": "今年用电度数(kWh)",
"interval_elec_consumption": "区间用电度数(kWh)",
"monthly_elec_consumption": "每月用电分析",
"monthly_carbon_emission_and_reduction": "每月碳排当量 (kgCO2e)",
"daily_carbon_emission_and_reduction": "每日碳排当量 (kgCO2e)",
"monthly_bill_power": "每月计费度数 (kWh)",
"interval_bill_degree": "区间计费度数",
"peak": "尖峰",

View File

@ -16,6 +16,8 @@
"formats": "檔案格式"
},
"dashboard": {
"production_quantity": "生產量",
"today_production_rate": "今日生產完成率",
"yesterday_today": "昨天/今天",
"elec_consumption_comparison": "用電量比較",
"elec_consumption_comparison_trend": "用電量比較趨勢",
@ -45,9 +47,11 @@
"last_month": "上月",
"this_year": "今年",
"last_year": "去年",
"refrig_temp":"冷藏溫度",
"indoor_temp":"室內溫度",
"alerts_data":"異常資料"
"refrig_chart": "冷藏趨勢",
"indoor_chart": "室內趨勢",
"temperature": "溫度",
"humidity": "濕度",
"alerts_data": "異常資料"
},
"history": {
"title": "歷史資料",
@ -91,7 +95,7 @@
"year_elec_consumption": "今年用電度數(kWh)",
"interval_elec_consumption": "區間用電度數(kWh)",
"monthly_elec_consumption": "每月用電分析",
"monthly_carbon_emission_and_reduction": "每月碳排當量 (kgCO2e)",
"daily_carbon_emission_and_reduction": "每日碳排當量 (kgCO2e)",
"monthly_bill_power": "每月計費度數 (kWh)",
"interval_bill_degree": "區間計費度數",
"peak": "尖峰",
@ -155,8 +159,8 @@
"latest_elec_consumption": "最新用電度數",
"daily_elec": "每日用電度數",
"line_voltage": "線電壓",
"electric_current" : "電流",
"monthly_elec_bill":"每月電費分析"
"electric_current": "電流",
"monthly_elec_bill": "每月電費分析"
},
"alarm": {
"title": "顯示警告",
@ -182,7 +186,7 @@
"alarmClass": "告警條件",
"device_name": "設備名稱",
"device_number": "設備編號",
"device_point_name":"點位名稱",
"device_point_name": "點位名稱",
"date": "發生日期",
"time": "發生時間",
"error_msg": "異常原因",
@ -389,28 +393,28 @@
"restore": "復原",
"stop_edit": "停止修改",
"start_edit": "開始修改",
"convert":"轉換"
"convert": "轉換"
},
"msg": {
"sure_to_delete": "是否確認刪除該項目?",
"sure_to_delete_permanent": "是否確認永久刪除該項目?",
"delete_success": "刪除成功",
"delete_failed": "刪除失敗",
"mqtt_refresh":"重新設定成功"
"mqtt_refresh": "重新設定成功"
},
"setting": {
"MQTT_parse": "MQTT 解析",
"schema":"架構",
"point":"點位",
"description":"描述",
"IoT_point_name":"IoT 點位名稱",
"IoT_point_code":"IoT 點位代號",
"number_of_decimal_places":"小數位數",
"boolean_value":"布林值",
"hide_point":"點位顯示",
"schema_name":"架構名稱",
"IoT_point_structure" :"IoT點位結構",
"system_point_name":"系統點位名稱",
"schema": "架構",
"point": "點位",
"description": "描述",
"IoT_point_name": "IoT 點位名稱",
"IoT_point_code": "IoT 點位代號",
"number_of_decimal_places": "小數位數",
"boolean_value": "布林值",
"hide_point": "點位顯示",
"schema_name": "架構名稱",
"IoT_point_structure": "IoT點位結構",
"system_point_name": "系統點位名稱",
"json_format_text": "請貼上 JSON 格式數據",
"json_click_text": "請在左側輸入JSON並點選轉換按鈕"
}

View File

@ -16,6 +16,8 @@
"formats": "File formats"
},
"dashboard": {
"production_quantity": "Production Quantity",
"today_production_rate": "Today's Production Completion Rate",
"yesterday_today": "Yesterday / Today's",
"elec_consumption_comparison": "Electricity Consumption Comparison",
"elec_consumption_comparison_trend": "Electricity Consumption Comparison Trend",
@ -45,8 +47,10 @@
"last_month": "Last month",
"this_year": "This year",
"last_year": "Last year",
"refrig_temp": "Refrigeration temperature",
"indoor_temp": "Indoor temperature",
"refrig_chart": "Refrigeration chart",
"indoor_chart": "Indoor chart",
"temperature": "Temp.",
"humidity": "Hum.",
"alerts_data": "Abnormal data"
},
"history": {
@ -91,7 +95,7 @@
"year_elec_consumption": "This year's electricity consumption (kWh)",
"interval_elec_consumption": "Interval electricity consumption (kWh)",
"monthly_elec_consumption": "Monthly electricity consumption analysis",
"monthly_carbon_emission_and_reduction": "Monthly carbon emission equivalent (kgCO2e)",
"daily_carbon_emission_and_reduction": "Daily carbon emission equivalent (kgCO2e)",
"monthly_bill_power": "Monthly billing power (kWh)",
"interval_bill_degree": "Interval billing degree",
"peak": "Peak",

View File

@ -1,93 +1,80 @@
import { createRouter, createWebHashHistory } from "vue-router";
import Dashboard from "@/views/dashboard/Dashboard.vue";
import History from "@/views/history/History.vue";
import Operation from "@/views/operation/Operation.vue";
import GraphManagement from "@/views/graphManagement/GraphManagement.vue";
import AccountManagement from "@/views/accountManagement/AccountManagement.vue";
import AssetManagement from "@/views/AssetManagement/AssetManagement.vue";
import AlertManagement from "@/views/alert/AlertManagement.vue";
import EnergyManagement from "@/views/energyManagement/EnergyManagement.vue";
import SettingManagement from "@/views/setting/SettingManagement.vue";
import Login from "@/views/login/Login.vue";
import useUserInfoStore from "@/stores/useUserInfoStore";
import useGetCookie from "@/hooks/useGetCookie";
import System from "@/views/system/System.vue";
import SystemFloor from "@/views/system/SystemFloor.vue";
import Test from "@/views/Test.vue";
import SystemMain from "@/views/system/SystemMain.vue";
const router = createRouter({
history: createWebHashHistory(import.meta.env.BASE_URL),
// linkActiveClass: "is-active",
routes: [
{
path: "/login",
name: "login",
component: Login,
component: () => import("@/views/login/Login.vue"),
},
{
path: "/dashboard",
index: true,
name: "dashboard",
component: Dashboard,
component: () => import("@/views/dashboard/Dashboard.vue"),
},
{
path: "/system/:main_system_id/:sub_system_id",
name: "system",
component: System,
component: () => import("@/views/system/System.vue"),
children: [
{
path: ":floor_id",
name: "sub_system",
component: SystemMain,
component: () => import("@/views/system/SystemMain.vue"),
},
],
},
{
path: "/historyData",
name: "history",
component: History,
component: () => import("@/views/history/History.vue"),
},
{
path: "/operation",
name: "operation",
component: Operation,
component: () => import("@/views/operation/Operation.vue"),
},
{
path: "/graphManagement",
name: "graphManagement",
component: GraphManagement,
component: () => import("@/views/graphManagement/GraphManagement.vue"),
},
{
path: "/accountManagement",
name: "accountManagement",
component: AccountManagement,
component: () =>
import("@/views/accountManagement/AccountManagement.vue"),
},
{
path: "/assetManagement",
name: "assetManagement",
component: AssetManagement,
component: () => import("@/views/AssetManagement/AssetManagement.vue"),
},
{
path: "/alert",
name: "alert",
component: AlertManagement,
component: () => import("@/views/alert/AlertManagement.vue"),
},
{
path: "/energyManagement",
name: "energyManagement",
component: EnergyManagement,
component: () => import("@/views/energyManagement/EnergyManagement.vue"),
},
{
path: "/setting/:main_system_id/:sub_system_id/:type",
name: "setting",
component: SettingManagement,
component: () => import("@/views/setting/SettingManagement.vue"),
},
{
path: "/mytestfile/mjm",
name: "mytestfile",
component: Test,
component: () => import("@/views/Test.vue"),
},
],
});

View File

@ -8,6 +8,7 @@ import useBuildingStore from "@/stores/useBuildingStore";
const store = useBuildingStore();
const { t, locale } = useI18n();
const taipower_data = ref([]);
const elecUseDayData = ref([]);
const carbonValue = ref(null);
const carbonData = ref(null);
const search_data = computed(() => {
@ -75,7 +76,7 @@ const getData = async (value) => {
const res = await getTaipower(value);
if (res.isSuccess) {
taipower_data.value = res.data
? res.data.sort((a, b) => a.month.localeCompare(b.month))
? res.data.sort((a, b) => a.day.localeCompare(b.month))
: [];
}
};
@ -107,6 +108,7 @@ watch(
JSON.stringify(newValue) !== JSON.stringify(oldValue)
) {
getData(newValue);
getElecUseDayData(newValue);
}
},
{
@ -119,12 +121,12 @@ watch(
watch(
taipower_data,
() => {
//
const months = taipower_data.value.map((item) => item.month);
//
const days = taipower_data.value.map((item) => item.day);
const carbonTotal = taipower_data.value.map((item) => item.carbon);
// xAxis series
defaultChartOption.value.xAxis.data = months;
//
defaultChartOption.value.xAxis.data = days;
defaultChartOption.value.series[0].data = carbonTotal;
},
{ deep: true }
@ -144,7 +146,7 @@ onMounted(() => {
<div class="mb-3 relative">
<h3 class="font-bold text-xl text-center">
<span class="text-info">
{{ $t("energy.monthly_carbon_emission_and_reduction") }}
{{ $t("energy.daily_carbon_emission_and_reduction") }}
</span>
</h3>
<DashboardEmissionModal :carbonData="carbonData" :getData="getCarbonData" />

View File

@ -2,12 +2,14 @@
import LineChart from "@/components/chart/LineChart.vue";
import { SECOND_CHART_COLOR } from "@/constant";
import dayjs from "dayjs";
import { ref, watch, onUnmounted } from "vue";
import { ref, watch, onMounted, onUnmounted, computed } from "vue";
import useActiveBtn from "@/hooks/useActiveBtn";
import { getDashboardTemp } from "@/apis/dashboard";
import useSearchParams from "@/hooks/useSearchParam";
import useBuildingStore from "@/stores/useBuildingStore";
import { useI18n } from "vue-i18n";
const { t, locale } = useI18n();
const { searchParams } = useSearchParams();
const buildingStore = useBuildingStore();
const intervalType = "immediateTemp";
@ -17,6 +19,9 @@ const { items, changeActiveBtn, setItems, selectedBtn } = useActiveBtn();
const data = ref([]);
const other_real_temp_chart = ref(null);
const currentOptionType = ref(1); // 1: , 2:
const noData = ref(false);
const defaultChartOption = ref({
tooltip: {
trigger: "axis",
@ -36,72 +41,50 @@ const defaultChartOption = ref({
containLabel: true,
},
xAxis: {
// type: 'time',
type: "category",
splitLine: {
show: false,
},
axisLabel: {
color: "#ffffff",
},
splitLine: { show: false },
axisLabel: { color: "#ffffff" },
data: [],
},
yAxis: {
type: "value",
splitLine: {
show: false,
},
axisLabel: {
color: "#ffffff",
},
splitLine: { show: false },
axisLabel: { color: "#ffffff" },
},
series: [],
});
const getData = async (tempOption) => {
const getData = async () => {
const res = await getDashboardTemp({
building_guid: buildingStore.selectedBuilding.building_guid,
tempOption, // 1 2:
timeInterval: 1, // =>1.4.8
option: 2, // 2:
tempOption: 2, // tempOption 1: 2:
timeInterval: 1,
option: currentOptionType.value, // option1: 2:
});
if (res.isSuccess) {
if (tempOption === 1) {
console.log("室內溼度資料:", res.data["室溫"]);
data.value = res.data["室溫"] || [];
} else {
console.log("冷藏溼度資料:", res.data["冷藏溫度"]);
data.value = res.data["冷藏溫度"] || [];
}
const key = "冷藏溫度";
const label = currentOptionType.value === 1 ? "溫度" : "濕度";
console.log(`冷藏${label}資料:`, res.data[key]);
data.value = res.data[key] || [];
noData.value = !data.value || data.value.length === 0;
} else {
noData.value = true;
}
};
//
const buttonItems = computed(() => [
{ key: 1, title: t("dashboard.temperature"), active: true },
{ key: 2, title: t("dashboard.humidity"), active: false },
]);
//
watch(
() => buildingStore.sysConfig,
(newValue) => {
if (newValue) {
// sysConfig
const itemsArr = buildingStore.sysConfig?.humiture_options
? Object.entries(buildingStore.sysConfig.humiture_options).map(
([key, title], index) => ({
key: Number(key),
title,
active: index === 0,
})
)
: [];
setItems(itemsArr);
} else {
//
if (timeoutTimer.value) {
clearInterval(timeoutTimer.value);
}
}
() => locale.value,
() => {
setItems(buttonItems.value);
},
{
immediate: true,
}
{ immediate: true }
);
watch(
@ -111,18 +94,15 @@ watch(
clearInterval(timeoutTimer.value);
}
if (newValue?.key) {
getData(newValue.key);
//
if (newValue?.key === 1 || newValue?.key === 2) {
currentOptionType.value = newValue.key;
getData();
timeoutTimer.value = setInterval(() => {
getData(newValue.key);
getData();
}, 60 * 1000);
}
},
{
immediate: true,
deep: true,
}
{ immediate: true, deep: true }
);
watch(
@ -170,7 +150,6 @@ watch(
);
onUnmounted(() => {
//
if (timeoutTimer.value) {
clearInterval(timeoutTimer.value);
}
@ -178,18 +157,23 @@ onUnmounted(() => {
</script>
<template>
<h3 class="text-info text-xl text-center">濕度趨勢</h3>
<h3 class="text-info text-xl text-center">
{{ $t("dashboard.refrig_chart") }}
</h3>
<div className="my-3 w-full flex justify-center relative">
<ButtonConnectedGroup
:items="items"
:onclick="
(e, item) => {
changeActiveBtn(item);
}
"
:onclick="(e, item) => changeActiveBtn(item)"
/>
</div>
<div
v-if="noData"
class="text-center text-white text-lg min-h-[260px] flex items-center justify-center"
>
無資料
</div>
<LineChart
v-else
id="dashboard_other_real_temp"
class="min-h-[260px] max-h-fit"
:option="defaultChartOption"

View File

@ -55,7 +55,7 @@ const defaultChartOption = ref({
});
const timeoutTimer = ref("");
const indoor_temp_chart = ref(null);
const indoor_chart = ref(null);
const data = ref([]);
const getData = async (timeInterval) => {
@ -75,7 +75,7 @@ watch(
data,
(newValue) => {
newValue.length > 0 &&
indoor_temp_chart.value.chart.setOption({
indoor_chart.value.chart.setOption({
legend: {
data: newValue.map(({ full_name }) => full_name),
},
@ -143,14 +143,14 @@ onUnmounted(() => {
<template>
<h3 class="text-info font-bold text-xl text-center mb-3 relative">
<span>{{ $t("dashboard.indoor_temp") }}</span>
<span>{{ $t("dashboard.indoor_chart") }}</span>
</h3>
<LineChart
id="dashboard_indoor_temp"
id="dashboard_indoor_chart"
class="min-h-[300px] max-h-fit"
:option="defaultChartOption"
ref="indoor_temp_chart"
ref="indoor_chart"
/>
</template>

View File

@ -120,7 +120,9 @@ onMounted(() => {
</script>
<template>
<div>
<h3 class="text-info text-xl text-center mb-3">生產量</h3>
<h3 class="text-info text-xl text-center mb-3">
{{ $t("dashboard.production_quantity") }}
</h3>
<div class="w-full grid grid-cols-3">
<div>
<GaugeChart

View File

@ -44,15 +44,17 @@ onMounted(() => {
</script>
<template>
<DashboardProductCompleteModal/>
<DashboardProductCompleteModal />
<div class="mb-3 relative">
<h3 class="text-info text-xl text-center">今日生產完成率 </h3>
<h3 class="text-info text-xl text-center">
{{ $t("dashboard.today_production_rate") }} ()
</h3>
<button
type="button"
class="btn btn-xs btn-success absolute top-0 right-0"
@click.stop="openModal"
>
設定
{{ $t("button.edit") }}
</button>
</div>
<div className="my-3 w-full flex justify-center relative">

View File

@ -146,7 +146,7 @@ onUnmounted(() => {
<template>
<h3 class="text-info font-bold text-xl text-center mb-3 relative">
<span>{{ $t("dashboard.refrig_temp") }}</span>
<span>{{ $t("dashboard.refrig_chart") }}</span>
</h3>
<LineChart

View File

@ -2,12 +2,14 @@
import LineChart from "@/components/chart/LineChart.vue";
import { SECOND_CHART_COLOR } from "@/constant";
import dayjs from "dayjs";
import { ref, watch, onUnmounted } from "vue";
import { ref, watch, onMounted, onUnmounted, computed } from "vue";
import useActiveBtn from "@/hooks/useActiveBtn";
import { getDashboardTemp } from "@/apis/dashboard";
import useSearchParams from "@/hooks/useSearchParam";
import useBuildingStore from "@/stores/useBuildingStore";
import { useI18n } from "vue-i18n";
const { t, locale } = useI18n();
const { searchParams } = useSearchParams();
const buildingStore = useBuildingStore();
const intervalType = "immediateTemp";
@ -17,6 +19,9 @@ const { items, changeActiveBtn, setItems, selectedBtn } = useActiveBtn();
const data = ref([]);
const other_real_temp_chart = ref(null);
const currentOptionType = ref(1); // 1: , 2:
const noData = ref(false);
const defaultChartOption = ref({
tooltip: {
trigger: "axis",
@ -36,24 +41,15 @@ const defaultChartOption = ref({
containLabel: true,
},
xAxis: {
// type: 'time',
type: "category",
splitLine: {
show: false,
},
axisLabel: {
color: "#ffffff",
},
splitLine: { show: false },
axisLabel: { color: "#ffffff" },
data: [],
},
yAxis: {
type: "value",
splitLine: {
show: false,
},
axisLabel: {
color: "#ffffff",
},
splitLine: { show: false },
axisLabel: { color: "#ffffff" },
},
series: [],
});
@ -61,47 +57,33 @@ const defaultChartOption = ref({
const getData = async (tempOption) => {
const res = await getDashboardTemp({
building_guid: buildingStore.selectedBuilding.building_guid,
tempOption, // 1 2:
timeInterval: 1, // =>1.4.8
option: 1, // 1:
tempOption, // tempOption 1: 2:
timeInterval: 1,
option: currentOptionType.value, // option1: 2:
});
if (res.isSuccess) {
if (tempOption === 1) {
console.log("室內溫度資料:", res.data["室溫"]);
data.value = res.data["室溫"] || [];
} else {
console.log("冷藏溫度資料:", res.data["冷藏溫度"]);
data.value = res.data["冷藏溫度"] || [];
}
const key = "室溫";
const label = currentOptionType.value === 1 ? "溫度" : "濕度";
// console.log(`${label}:`, res.data[key]);
data.value = res.data[key] || [];
noData.value = !data.value || data.value.length === 0;
} else {
noData.value = true;
}
};
//
const buttonItems = computed(() => [
{ key: 1, title: t("dashboard.temperature"), active: true },
{ key: 2, title: t("dashboard.humidity"), active: false },
]);
watch(
() => buildingStore.sysConfig,
(newValue) => {
if (newValue) {
// sysConfig
const itemsArr = buildingStore.sysConfig?.humiture_options
? Object.entries(buildingStore.sysConfig.humiture_options).map(
([key, title], index) => ({
key: Number(key),
title,
active: index === 0,
})
)
: [];
setItems(itemsArr);
} else {
//
if (timeoutTimer.value) {
clearInterval(timeoutTimer.value);
}
}
() => locale.value,
() => {
setItems(buttonItems.value);
},
{
immediate: true,
}
{ immediate: true }
);
watch(
@ -111,18 +93,15 @@ watch(
clearInterval(timeoutTimer.value);
}
if (newValue?.key) {
getData(newValue.key);
//
if (newValue?.key === 1 || newValue?.key === 2) {
currentOptionType.value = newValue.key; // 1:, 2:
getData(1); // tempOption 1
timeoutTimer.value = setInterval(() => {
getData(newValue.key);
getData(1);
}, 60 * 1000);
}
},
{
immediate: true,
deep: true,
}
{ immediate: true, deep: true }
);
watch(
@ -170,7 +149,6 @@ watch(
);
onUnmounted(() => {
//
if (timeoutTimer.value) {
clearInterval(timeoutTimer.value);
}
@ -178,17 +156,21 @@ onUnmounted(() => {
</script>
<template>
<h3 class="text-info text-xl text-center">溫度趨勢</h3>
<h3 class="text-info text-xl text-center">
{{ $t("dashboard.indoor_chart") }}
</h3>
<div className="my-3 w-full flex justify-center relative">
<ButtonConnectedGroup
:items="items"
:onclick="
(e, item) => {
changeActiveBtn(item);
}
"
:onclick="(e, item) => changeActiveBtn(item)"
/>
</div>
<div
v-if="noData"
class="text-center text-white text-lg min-h-[260px] flex items-center justify-center"
>
無資料
</div>
<LineChart
id="dashboard_other_real_temp"
class="min-h-[260px] max-h-fit"

View File

@ -109,7 +109,7 @@ onMounted(() => {
<div class="bg-slate-800 p-3">
<div class="flex items-center">
<div class="text-white mb-3 text-base">
{{ $t("energy.monthly_carbon_emission_and_reduction") }}
{{ $t("energy.daily_carbon_emission_and_reduction") }}
</div>
<CarbonEmissionModal
:carbonData="carbonData"

View File

@ -3,7 +3,7 @@ import HistorySidebar from "./components/HistorySidebar.vue";
import HistorySearch from "./components/HistorySearch.vue";
import HistoryTable from "./components/HistoryTable.vue";
import dayjs from "dayjs";
import { ref, provide } from "vue";
import { ref, provide, watch } from "vue";
const tableData = ref([]);
const loading = ref(false);
@ -21,6 +21,10 @@ provide("history_table_data", {
loading,
updateLoading,
});
watch(tableData, (newVal) => {
console.log("🔍 提供端 tableData 改變:", newVal);
});
</script>
<template>

View File

@ -11,7 +11,6 @@ const { t } = useI18n();
const { tableData } = inject("history_table_data");
const history_chart = ref(null);
//
const defaultChartOption = {
tooltip: {
trigger: "axis",
@ -51,7 +50,7 @@ const defaultChartOption = {
formatter: (value) =>
searchParams.value.Type == 2
? dayjs(value).format("HH:mm")
: dayjs(value).format("MM-DD"), //
: dayjs(value).format("MM-DD"),
},
data: [],
},
@ -59,15 +58,12 @@ const defaultChartOption = {
type: "value",
splitLine: { show: false },
axisLabel: { color: "#ffffff" },
// interval: 100, //Y
min: "dataMin",
max: "dataMax",
// splitArea: { show: false },
max: "dataMax",
},
series: [],
};
//
const formatChartData = (data) => {
return data.reduce((acc, item) => {
const seriesKey = `${item.device_name || ""}_${item.item_name || ""}`;
@ -86,19 +82,33 @@ const formatChartData = (data) => {
}, {});
};
// tableData
watch(
tableData,
(newData) => {
if (newData?.length > 0) {
const formattedData = formatChartData(newData);
const xDataRaw = formattedData[Object.keys(formattedData)[0]].timestamps;
const totalPoints = xDataRaw.length;
const tickCount = 30;
const interval = Math.max(1, Math.floor(totalPoints / (tickCount - 1)));
const sampledTime = xDataRaw.filter((_, idx) => idx % interval === 0);
if (sampledTime[sampledTime.length - 1] !== xDataRaw[totalPoints - 1]) {
sampledTime.push(xDataRaw[totalPoints - 1]);
}
const series = Object.keys(formattedData).map((seriesKey, index) => {
const { maxValue, minValue, averageValue } = formattedData[seriesKey];
const { timestamps, values, maxValue, minValue, averageValue } =
formattedData[seriesKey];
const timeToValue = Object.fromEntries(
timestamps.map((t, i) => [t, values[i]])
);
return {
name: seriesKey,
type: "line",
data: formattedData[seriesKey].values,
data: sampledTime.map((t) => timeToValue[t] ?? null),
showSymbol: false,
itemStyle: {
color: SECOND_CHART_COLOR[index % SECOND_CHART_COLOR.length],
@ -127,7 +137,6 @@ watch(
};
});
//
history_chart.value?.chart.setOption(
{
...defaultChartOption,
@ -137,10 +146,7 @@ watch(
},
xAxis: {
...defaultChartOption.xAxis,
data:
Object.keys(formattedData).length > 0
? formattedData[Object.keys(formattedData)[0]].timestamps
: [],
data: sampledTime,
},
series,
},
@ -161,4 +167,4 @@ watch(
/>
</template>
<style lang="scss" scoped></style>
<style lang="scss" scoped></style>

4949
stats.html Normal file

File diff suppressed because one or more lines are too long

View File

@ -4,7 +4,7 @@ module.exports = {
"./node_modules/vue-tailwind-datepicker/**/*.js",
],
purge: ["./index.html", "./src/**/*.{vue,js,ts,jsx,tsx}"],
darkMode: false, // or 'media' or 'class'
darkMode: false,
theme: {
extend: {
backgroundImage: {