361 lines
8.6 KiB
Vue
361 lines
8.6 KiB
Vue
<script setup>
|
|
import { ref, onMounted, watch, computed } from "vue";
|
|
import * as echarts from "echarts";
|
|
import BarChart from "@/components/chart/BarChart.vue";
|
|
import { useI18n } from "vue-i18n";
|
|
|
|
const { locale, t } = useI18n();
|
|
|
|
// 假資料
|
|
const fakeEnergyData = ref({
|
|
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 currentType = ref({
|
|
name: "today",
|
|
});
|
|
const energyTypeList = ref([
|
|
{
|
|
title: t("dashboard.daily_relative_change"),
|
|
key: "today",
|
|
},
|
|
{
|
|
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 barChartOptions = computed(() => ({
|
|
xAxis: {
|
|
type: "category",
|
|
data: chartData.value.map((item) => item.name),
|
|
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.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: {
|
|
trigger: "axis",
|
|
axisPointer: { type: "shadow" },
|
|
formatter: function (params) {
|
|
let tooltipText = `<div>${params[0].axisValueLabel}</div>`;
|
|
const filteredParams = params.filter((item) => item.seriesType === "bar");
|
|
filteredParams.forEach((item) => {
|
|
tooltipText += `<div>${item.marker} ${
|
|
item.value ? item.value : "-"
|
|
}</div>`;
|
|
});
|
|
|
|
return tooltipText;
|
|
},
|
|
},
|
|
}));
|
|
|
|
function updateChartData() {
|
|
// 從 fakeEnergyData 提取資料
|
|
chartData.value = fakeEnergyData.value.buildings.map((building) => {
|
|
let currentKey = currentType.value.name;
|
|
let lastKey;
|
|
|
|
switch (currentType.value.name) {
|
|
case "today":
|
|
lastKey = "yesterday";
|
|
break;
|
|
case "week":
|
|
lastKey = "lastWeek";
|
|
break;
|
|
case "month":
|
|
lastKey = "lastMonth";
|
|
break;
|
|
case "year":
|
|
lastKey = "lastYear";
|
|
break;
|
|
default:
|
|
lastKey = "yesterday";
|
|
}
|
|
|
|
return {
|
|
name: building.name,
|
|
current: building[currentKey],
|
|
last: building[lastKey],
|
|
difference: building[currentKey] - building[lastKey],
|
|
};
|
|
});
|
|
}
|
|
|
|
// 使用 watch 監聽 fakeEnergyData 的變化
|
|
watch(
|
|
() => [fakeEnergyData.value, currentType.value],
|
|
() => {
|
|
updateChartData();
|
|
},
|
|
{ deep: true, immediate: true } // 立即執行一次,確保初始資料載入
|
|
);
|
|
|
|
// 监听 currentType 的变化
|
|
watch(currentType, () => {
|
|
updateChartData();
|
|
});
|
|
|
|
watch(locale, () => {
|
|
updateChartData();
|
|
});
|
|
</script>
|
|
|
|
<template>
|
|
<div class="flex flex-wrap">
|
|
<div class="w-full chart-data relative px-3 mb-3">
|
|
<div class="flex flex-wrap items-center justify-between">
|
|
<h2 class="font-light pt-1 px-1">
|
|
{{ $t("dashboard.relative_energy_consumption") }}
|
|
</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 class="h-[100px]">
|
|
<BarChart
|
|
id="dashboard_chart_compare"
|
|
class="h-full"
|
|
:option="barChartOptions"
|
|
/>
|
|
</div>
|
|
|
|
<!-- 表格數據展示 -->
|
|
<div class="flex justify-between">
|
|
<div
|
|
v-for="(data, index) in chartData"
|
|
:key="index"
|
|
class="w-1/4 text-center mx-1"
|
|
>
|
|
<div
|
|
class="text-xs bg-cyan-900 p-1 border border-cyan-100 border-opacity-20"
|
|
>
|
|
{{ labels[0] }}
|
|
</div>
|
|
<div
|
|
class="text-sm bg-cyan-900 p-1 border border-cyan-100 border-opacity-20"
|
|
>
|
|
{{ data.current ?? "-" }}
|
|
</div>
|
|
<div
|
|
class="text-xs bg-cyan-900 p-1 border border-cyan-100 border-opacity-20"
|
|
>
|
|
{{ labels[1] }}
|
|
</div>
|
|
<div
|
|
class="text-sm bg-cyan-900 p-1 border border-cyan-100 border-opacity-20"
|
|
>
|
|
{{ data.last ?? "-" }}
|
|
</div>
|
|
<div
|
|
class="text-sm bg-cyan-900 p-1 border border-cyan-100 border-opacity-20"
|
|
>
|
|
<span
|
|
:class="{
|
|
'text-red-500': data.difference > 0,
|
|
'text-green-500': data.difference < 0,
|
|
}"
|
|
>
|
|
{{
|
|
data.difference
|
|
? (data.difference > 0 ? "+" : "") + data.difference
|
|
: "-"
|
|
}}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<style lang="scss" scoped>
|
|
.chart-data:before {
|
|
@apply absolute -left-0 -top-2 h-10 w-10 bg-no-repeat z-10;
|
|
content: "";
|
|
background: url(@ASSET/img/chart-data-background01.svg) center center;
|
|
}
|
|
|
|
.chart-data::after {
|
|
@apply absolute -right-1 -bottom-3 h-10 w-10 bg-no-repeat z-10;
|
|
content: "";
|
|
background: url(@ASSET/img/chart-data-background02.svg) center center;
|
|
}
|
|
</style>
|