調整近30天能耗趨勢、環比能耗(假資料)

This commit is contained in:
koko 2025-07-28 14:53:40 +08:00
parent 8d23b695c6
commit 1812ce2495
6 changed files with 308 additions and 231 deletions

BIN
public/CviLux_globalmap.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

View File

@ -80,10 +80,16 @@ onUnmounted(() => {
未來瀚荃會持續精進提供更快更好以及高附加價值的產品與服務來滿足您的需求 未來瀚荃會持續精進提供更快更好以及高附加價值的產品與服務來滿足您的需求
</p> </p>
</div> </div>
<!--狀態進度--> <!--在線狀態-->
<SysProgress /> <SysProgress />
</div> </div>
<div class="w-full xl:w-2/4 mt-2"></div> <div class="w-full xl:w-2/4 max-h-[calc(100vh-90px)] mt-2 border border-cyan-400 shadow-md shadow-cyan-500/40">
<img
src="/CviLux_globalmap.jpg"
alt=""
class="w-full h-full invert object-cover "
/>
</div>
<div class="w-full xl:w-1/4 mt-2"> <div class="w-full xl:w-1/4 mt-2">
<ElecRank :energyCostData="energyCostData" /> <ElecRank :energyCostData="energyCostData" />
<ElecTrends <ElecTrends
@ -98,6 +104,6 @@ onUnmounted(() => {
<style lang="scss" scoped> <style lang="scss" scoped>
.area-img-box { .area-img-box {
@apply border border-light-info bg-gray-900/80 backdrop-blur-lg relative overflow-hidden shadow-md shadow-blue-300 mb-3; @apply border border-light-info bg-gray-900/80 backdrop-blur-lg relative overflow-hidden shadow-md shadow-blue-300 mb-4;
} }
</style> </style>

View File

@ -5,35 +5,202 @@ import BarChart from "@/components/chart/BarChart.vue";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
const { locale, t } = useI18n(); const { locale, t } = useI18n();
const props = defineProps({
energyCostData: { //
type: Object, const fakeEnergyData = ref({
required: true, buildings: [
}, {
name: "A棟",
today: 100,
yesterday: 90,
week: 500,
lastWeek: 450,
month: 2000,
lastMonth: 1800,
year: 24000,
lastYear: 22000,
},
{
name: "B棟",
today: 120,
yesterday: 110,
week: 600,
lastWeek: 550,
month: 2400,
lastMonth: 2200,
year: 28000,
lastYear: 26000,
},
{
name: "C棟",
today: 80,
yesterday: 70,
week: 400,
lastWeek: 350,
month: 1600,
lastMonth: 1400,
year: 19000,
lastYear: 17000,
},
{
name: "D棟",
today: 110,
yesterday: 100,
week: 550,
lastWeek: 500,
month: 2200,
lastMonth: 2000,
year: 26000,
lastYear: 24000,
},
],
}); });
const chartData = ref([]); // const chartData = ref([]);
const currentType = ref({
const labels = computed(() => [ name: "today",
t("dashboard.today"), });
t("dashboard.yesterday"), const energyTypeList = ref([
t("dashboard.this_week"), {
t("dashboard.last_week"), title: t("dashboard.daily_relative_change"),
t("dashboard.this_month"), key: "today",
t("dashboard.last_month"), },
t("dashboard.this_year"), {
t("dashboard.last_year"), title: t("dashboard.weekly_relative_change"),
key: "week",
},
{
title: t("dashboard.monthly_relative_change"),
key: "month",
},
{
title: t("dashboard.yearly_relative_change"),
key: "year",
},
]); ]);
const labels = computed(() => {
switch (currentType.value.name) {
case "today":
return [t("dashboard.today"), t("dashboard.yesterday")];
case "week":
return [t("dashboard.this_week"), t("dashboard.last_week")];
case "month":
return [t("dashboard.this_month"), t("dashboard.last_month")];
case "year":
return [t("dashboard.this_year"), t("dashboard.last_year")];
default:
return [t("dashboard.today"), t("dashboard.yesterday")];
}
});
const barWidth = 30; // Set barWidth const barWidth = 30; // Set barWidth
const barChartOptions = ref({ const barChartOptions = computed(() => ({
xAxis: { xAxis: {
type: "category", type: "category",
data: chartData.value.map((item) => item.category), data: chartData.value.map((item) => item.name),
axisLine: { lineStyle: { color: "#fff" } }, axisLine: { lineStyle: { color: "#fff" } },
}, },
yAxis: { type: "value", show: false }, yAxis: { type: "value", show: false },
series: [], // grid: {
left: "-10%",
right: "1%",
bottom: "3%",
top: "4%",
containLabel: true,
},
series: [
{
name: "當前",
data: chartData.value.map((item) => item.current),
type: "bar",
barWidth: barWidth,
barGap: "-10%",
itemStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: "#186B80" },
{ offset: 1, color: "#50C3E3" },
]),
shadowBlur: 5,
shadowColor: "rgba(0, 0, 0, 0.3)",
shadowOffsetY: 2,
shadowOffsetX: 5,
},
z: 3,
},
{
name: "對比",
data: chartData.value.map((item) => item.last),
type: "bar",
barWidth: barWidth,
itemStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: "#988F2C" },
{ offset: 1, color: "#FFF26D" },
]),
shadowBlur: 5,
shadowColor: "rgba(0, 0, 0, 0.3)",
shadowOffsetY: 2,
shadowOffsetX: 5,
},
},
{
// this top
z: 6,
type: "pictorialBar",
symbolPosition: "end",
data: chartData.value.map((item) => item.current),
symbol: "diamond",
symbolOffset: ["-45%", "-50%"],
symbolSize: [barWidth, barWidth * 0.5],
itemStyle: {
borderWidth: 0,
color: "#50C3E3",
},
},
{
// this bot
z: 6,
type: "pictorialBar",
symbolPosition: "start",
data: chartData.value.map((item) => item.current),
symbol: "diamond",
symbolOffset: ["-45%", "50%"],
symbolSize: [barWidth, barWidth * 0.5],
itemStyle: {
borderWidth: 0,
color: "#50C3E3",
},
},
{
// last top
z: 3,
type: "pictorialBar",
symbolPosition: "end",
data: chartData.value.map((item) => item.last),
symbol: "diamond",
symbolOffset: ["45%", "-50%"],
symbolSize: [barWidth, barWidth * 0.5],
itemStyle: {
borderWidth: 0,
color: "#FFF26D",
},
},
{
// last bot
z: 3,
type: "pictorialBar",
symbolPosition: "start",
data: chartData.value.map((item) => item.last),
symbol: "diamond",
symbolOffset: ["45%", "50%"],
symbolSize: [barWidth, barWidth * 0.5],
itemStyle: {
borderWidth: 0,
color: "#FFF26D",
},
},
],
tooltip: { tooltip: {
trigger: "axis", trigger: "axis",
axisPointer: { type: "shadow" }, axisPointer: { type: "shadow" },
@ -49,179 +216,56 @@ const barChartOptions = ref({
return tooltipText; return tooltipText;
}, },
}, },
}); }));
function updateChartData(newEnergyCostData) { function updateChartData() {
if (newEnergyCostData && newEnergyCostData.compare) { // fakeEnergyData
// props.energyCostData.compare chartData.value = fakeEnergyData.value.buildings.map((building) => {
const compareData = newEnergyCostData.compare; let currentKey = currentType.value.name;
let lastKey;
// switch (currentType.value.name) {
chartData.value = [ case "today":
{ lastKey = "yesterday";
category: t("dashboard.daily_relative_change"), break;
this: compareData.day.current, case "week":
last: compareData.day.last, lastKey = "lastWeek";
change: compareData.day.percentage, break;
}, case "month":
{ lastKey = "lastMonth";
category: t("dashboard.weekly_relative_change"), break;
this: compareData.week.current, case "year":
last: compareData.week.last, lastKey = "lastYear";
change: compareData.week.percentage, break;
}, default:
{ lastKey = "yesterday";
category: t("dashboard.monthly_relative_change"), }
this: compareData.month.current,
last: compareData.month.last,
change: compareData.month.percentage,
},
{
category: t("dashboard.yearly_relative_change"),
this: compareData.year.current,
last: compareData.year.last,
change: compareData.year.percentage,
},
];
// barChartOptions return {
barChartOptions.value = { name: building.name,
xAxis: { current: building[currentKey],
type: "category", last: building[lastKey],
data: chartData.value.map((item) => item.category), difference: building[currentKey] - building[lastKey],
axisLine: { lineStyle: { color: "#fff" } },
},
yAxis: { type: "value", show: false },
grid: {
left: "-10%",
right: "1%",
bottom: "3%",
top: "4%",
containLabel: true,
},
series: [
{
name: "當前週期",
data: chartData.value.map((item) => item.this),
type: "bar",
barWidth: barWidth,
barGap: "-10%",
itemStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: "#186B80" },
{ offset: 1, color: "#50C3E3" },
]),
shadowBlur: 5,
shadowColor: "rgba(0, 0, 0, 0.3)",
shadowOffsetY: 2,
shadowOffsetX: 5,
},
z: 3,
},
{
name: "對比週期",
data: chartData.value.map((item) => item.last),
type: "bar",
barWidth: barWidth,
itemStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: "#988F2C" },
{ offset: 1, color: "#FFF26D" },
]),
shadowBlur: 5,
shadowColor: "rgba(0, 0, 0, 0.3)",
shadowOffsetY: 2,
shadowOffsetX: 5,
},
},
{
// this top
z: 6,
type: "pictorialBar",
symbolPosition: "end",
data: chartData.value.map((item) => item.this),
symbol: "diamond",
symbolOffset: ["-45%", "-50%"],
symbolSize: [barWidth, barWidth * 0.5],
itemStyle: {
borderWidth: 0,
color: "#50C3E3",
},
},
{
// this bot
z: 6,
type: "pictorialBar",
symbolPosition: "start",
data: chartData.value.map((item) => item.this),
symbol: "diamond",
symbolOffset: ["-45%", "50%"],
symbolSize: [barWidth, barWidth * 0.5],
itemStyle: {
borderWidth: 0,
color: "#50C3E3",
},
},
{
// last top
z: 3,
type: "pictorialBar",
symbolPosition: "end",
data: chartData.value.map((item) => item.last),
symbol: "diamond",
symbolOffset: ["45%", "-50%"],
symbolSize: [barWidth, barWidth * 0.5],
itemStyle: {
borderWidth: 0,
color: "#FFF26D",
},
},
{
// last bot
z: 3,
type: "pictorialBar",
symbolPosition: "start",
data: chartData.value.map((item) => item.last),
symbol: "diamond",
symbolOffset: ["45%", "50%"],
symbolSize: [barWidth, barWidth * 0.5],
itemStyle: {
borderWidth: 0,
color: "#FFF26D",
},
},
],
tooltip: {
trigger: "axis",
axisPointer: { type: "shadow" },
formatter: function (params) {
let tooltipText = `<div>${params[0].axisValueLabel}</div>`;
const filteredParams = params.filter(
(item) => item.seriesType === "bar"
);
filteredParams.forEach((item) => {
tooltipText += `<div>${item.marker} ${
item.value ? item.value : "-"
}</div>`;
});
return tooltipText;
},
},
}; };
} });
} }
// 使 watch energyCostData
// 使 watch fakeEnergyData
watch( watch(
() => props.energyCostData, () => [fakeEnergyData.value, currentType.value],
(newEnergyCostData) => { () => {
updateChartData(newEnergyCostData); updateChartData();
}, },
{ immediate: true } // { deep: true, immediate: true } //
); );
// currentType
watch(currentType, () => {
updateChartData();
});
watch(locale, () => { watch(locale, () => {
updateChartData(props.energyCostData); updateChartData();
}); });
</script> </script>
@ -229,9 +273,20 @@ watch(locale, () => {
<div class="flex flex-wrap"> <div class="flex flex-wrap">
<div class="w-full chart-data relative px-3 mb-3"> <div class="w-full chart-data relative px-3 mb-3">
<div class="flex flex-wrap items-center justify-between"> <div class="flex flex-wrap items-center justify-between">
<h2 class="font-light"> <h2 class="font-light pt-1 px-1">
{{ $t("dashboard.relative_energy_consumption") }} {{ $t("dashboard.relative_energy_consumption") }}
</h2> </h2>
<Select
:value="currentType"
class="!w-24"
selectClass="border-info focus-within:border-info btn-xs text-xs"
name="name"
Attribute="title"
:options="energyTypeList"
:isTopLabelExist="false"
:isBottomLabelExist="false"
>
</Select>
</div> </div>
<div class="h-[100px]"> <div class="h-[100px]">
<BarChart <BarChart
@ -249,19 +304,19 @@ watch(locale, () => {
class="w-1/4 text-center mx-1" class="w-1/4 text-center mx-1"
> >
<div <div
class="text-sm bg-cyan-900 p-1 border border-cyan-100 border-opacity-20" class="text-xs bg-cyan-900 p-1 border border-cyan-100 border-opacity-20"
> >
{{ labels[index * 2] }} {{ labels[0] }}
</div> </div>
<div <div
class="text-sm bg-cyan-900 p-1 border border-cyan-100 border-opacity-20" class="text-sm bg-cyan-900 p-1 border border-cyan-100 border-opacity-20"
> >
{{ data.this ?? "-" }} {{ data.current ?? "-" }}
</div> </div>
<div <div
class="text-sm bg-cyan-900 p-1 border border-cyan-100 border-opacity-20" class="text-xs bg-cyan-900 p-1 border border-cyan-100 border-opacity-20"
> >
{{ labels[index * 2 + 1] }} {{ labels[1] }}
</div> </div>
<div <div
class="text-sm bg-cyan-900 p-1 border border-cyan-100 border-opacity-20" class="text-sm bg-cyan-900 p-1 border border-cyan-100 border-opacity-20"
@ -273,13 +328,13 @@ watch(locale, () => {
> >
<span <span
:class="{ :class="{
'text-red-500': data.change > 0, 'text-red-500': data.difference > 0,
'text-green-500': data.change < 0, 'text-green-500': data.difference < 0,
}" }"
> >
{{ {{
data.change data.difference
? (data.change > 0 ? "+" : "") + data.change + "%" ? (data.difference > 0 ? "+" : "") + data.difference
: "-" : "-"
}} }}
</span> </span>

View File

@ -37,7 +37,7 @@ const getCurrentEnergyData = () => {
</script> </script>
<template> <template>
<div class="state-box-col relative h-full max-h-[200px] mb-3"> <div class="state-box-col relative h-full max-h-[190px] mb-3">
<div class="state-box h-full"> <div class="state-box h-full">
<!-- 標題和切換按鈕 --> <!-- 標題和切換按鈕 -->
<div class="flex justify-between items-center mb-2"> <div class="flex justify-between items-center mb-2">
@ -58,7 +58,7 @@ const getCurrentEnergyData = () => {
</div> </div>
<!-- 能耗排名列表 --> <!-- 能耗排名列表 -->
<div class="max-h-[150px] overflow-y-auto"> <div class="max-h-[140px] overflow-y-auto">
<table class="table table-sm text-center"> <table class="table table-sm text-center">
<tbody> <tbody>
<tr <tr

View File

@ -11,25 +11,27 @@ const { t } = useI18n();
const props = defineProps({ const props = defineProps({
formState: { formState: {
type: Object, type: Object,
required: true, required: true,
}, },
energyCostData: { energyCostData: {
type: Object, type: Object,
required: true, required: true,
}, },
getEnergyCostData: { getEnergyCostData: {
type: Function, type: Function,
required: true, required: true,
} },
}); });
const chartData = ref([]); const chartData = ref([]);
const buildingList = ref([]);
const floorList = ref([]); const floorList = ref([]);
const deptList = ref([]); const deptList = ref([]);
const weekComparisonOption = ref({}); const weekComparisonOption = ref({});
const currentType = ref({
name: "all",
});
// option // option
const generateCylinderChartOption = (data) => { const generateCylinderChartOption = (data) => {
const barWidth = 15; const barWidth = 15;
@ -98,7 +100,7 @@ const generateCylinderChartOption = (data) => {
left: "0%", left: "0%",
right: "0%", right: "0%",
bottom: "3%", bottom: "3%",
top: "10%", top: "17%",
containLabel: true, containLabel: true,
}, },
tooltip: { tooltip: {
@ -113,7 +115,7 @@ const generateCylinderChartOption = (data) => {
const processEnergyData = () => { const processEnergyData = () => {
if (!props.energyCostData || !props.energyCostData.trend) { if (!props.energyCostData || !props.energyCostData.trend) {
chartData.value = []; chartData.value = [];
weekComparisonOption.value = generateCylinderChartOption(chartData.value); weekComparisonOption.value = generateCylinderChartOption(chartData.value);
return; return;
} }
@ -123,8 +125,8 @@ const processEnergyData = () => {
); );
chartData.value = dailyData.map((item) => ({ chartData.value = dailyData.map((item) => ({
date: dayjs(item.time).format("MM/DD"), date: dayjs(item.time).format("MM/DD"),
energy: item.value, energy: item.value,
})); }));
weekComparisonOption.value = generateCylinderChartOption(chartData.value); weekComparisonOption.value = generateCylinderChartOption(chartData.value);
@ -135,15 +137,35 @@ watch(
(newEnergyCostData) => { (newEnergyCostData) => {
processEnergyData(); processEnergyData();
}, },
{ deep: true, immediate: true } { deep: true, immediate: true }
);
watch(
() => storeBuild.buildings,
(newValue) => {
if (newValue) {
buildingList.value = [
{
title: "All",
key: "all",
},
...newValue.map((building) => ({
title: building.full_name,
key: building.building_guid,
})),
];
}
},
{
immediate: true,
}
); );
watch( watch(
() => storeBuild.floorList, () => storeBuild.floorList,
(newValue) => { (newValue) => {
if (newValue) { if (newValue) {
console.log('newValue',newValue);
floorList.value = [ floorList.value = [
{ {
title: "All", title: "All",
@ -157,25 +179,6 @@ watch(
immediate: true, immediate: true,
} }
); );
watch(
() => storeBuild.floorList,
(newValue) => {
if (newValue) {
floorList.value = [
{
title: "All",
key: "all",
},
...storeBuild.floorList,
];
}
},
{
immediate: true,
}
);
watch( watch(
() => storeBuild.deptList, () => storeBuild.deptList,
(newValue) => { (newValue) => {
@ -199,7 +202,20 @@ watch(
<template> <template>
<div class="w-full chart-data relative px-3 mb-3"> <div class="w-full chart-data relative px-3 mb-3">
<div class="flex flex-wrap items-center justify-between"> <div class="flex flex-wrap items-center justify-between">
<h2 class="font-light">{{ $t("dashboard.last_30_days_energy_trend") }}</h2> <h2 class="font-light pt-1 px-1">
{{ $t("dashboard.last_30_days_energy_trend") }}
</h2>
<Select
:value="currentType"
class="w-auto my-2"
selectClass="border-info focus-within:border-info btn-xs text-xs"
name="name"
Attribute="title"
:options="buildingList"
:isTopLabelExist="false"
:isBottomLabelExist="false"
>
</Select>
</div> </div>
<div class="h-[200px]"> <div class="h-[200px]">
<BarChart <BarChart
@ -223,4 +239,4 @@ watch(
content: ""; content: "";
background: url(@ASSET/img/chart-data-background02.svg) center center; background: url(@ASSET/img/chart-data-background02.svg) center center;
} }
</style> </style>

View File

@ -114,7 +114,7 @@ onUnmounted(() => {
} }
.state-box { .state-box {
@apply h-[21rem] border border-light-info shadow-md shadow-blue-300 rounded-sm py-2 px-6 mb-5 text-white relative; @apply h-[21rem] border border-light-info shadow-md shadow-blue-300 rounded-sm py-2 px-6 mb-2 text-white relative;
} }
.state-box:after { .state-box:after {