總部首頁 api 串接 | navbar語言包
This commit is contained in:
parent
1812ce2495
commit
db5f15dfde
@ -39,9 +39,9 @@ export const deleteBuildings = async (building_guid) => {
|
||||
});
|
||||
};
|
||||
|
||||
export const getAuth = async (lang) => {
|
||||
export const getAuth = async (build) => {
|
||||
const res = await instance.post(GET_AUTHPAGE_API, {
|
||||
lang,
|
||||
build,
|
||||
});
|
||||
return apihandler(res.code, res.data, {
|
||||
msg: res.msg,
|
||||
@ -50,7 +50,7 @@ export const getAuth = async (lang) => {
|
||||
};
|
||||
|
||||
export const getAllSysSidebar = async (building_guid) => {
|
||||
const res = await instance.post(GET_SUBAUTHPAGE_API, {building_guid});
|
||||
const res = await instance.post(GET_SUBAUTHPAGE_API, { building_guid });
|
||||
return apihandler(res.code, res.data, {
|
||||
msg: res.msg,
|
||||
code: res.code,
|
||||
|
6
src/apis/headquarters/api.js
Normal file
6
src/apis/headquarters/api.js
Normal file
@ -0,0 +1,6 @@
|
||||
export const GET_SITES_SYSTEM_STATUS_API = `/api/monitoring/sites-system-status`;
|
||||
export const GET_SITES_SYSTEM_ENERGY_COST_RANK_API = `/api/energy-manager/all-site/energy-cost-rank`;
|
||||
export const GET_SITES_SYSTEM_ENERGY_COST_TREND_API = `/api/energy-manager/all-site/energy-cost-trend`;
|
||||
export const GET_SITES_SYSTEM_ENERGY_COST_GROWTH_API = `/api/energy-manager/all-site/energy-cost-growth-rate`;
|
||||
|
||||
|
44
src/apis/headquarters/index.js
Normal file
44
src/apis/headquarters/index.js
Normal file
@ -0,0 +1,44 @@
|
||||
import {
|
||||
GET_SITES_SYSTEM_STATUS_API,
|
||||
GET_SITES_SYSTEM_ENERGY_COST_RANK_API,
|
||||
GET_SITES_SYSTEM_ENERGY_COST_TREND_API,
|
||||
GET_SITES_SYSTEM_ENERGY_COST_GROWTH_API,
|
||||
} from "./api";
|
||||
import instance from "@/util/request";
|
||||
import apihandler from "@/util/apihandler";
|
||||
|
||||
export const getSystemStatus = async (building_ids) => {
|
||||
const res = await instance.post(GET_SITES_SYSTEM_STATUS_API, building_ids);
|
||||
|
||||
return apihandler(res.code, res.data, {
|
||||
msg: res.msg,
|
||||
code: res.code,
|
||||
});
|
||||
};
|
||||
|
||||
export const getSystemEnergyCostRank = async (building_ids) => {
|
||||
const res = await instance.post(GET_SITES_SYSTEM_ENERGY_COST_RANK_API, building_ids);
|
||||
|
||||
return apihandler(res.code, res.data, {
|
||||
msg: res.msg,
|
||||
code: res.code,
|
||||
});
|
||||
};
|
||||
|
||||
export const getSystemEnergyCostTrend = async (building_ids) => {
|
||||
const res = await instance.post(GET_SITES_SYSTEM_ENERGY_COST_TREND_API, building_ids);
|
||||
|
||||
return apihandler(res.code, res.data, {
|
||||
msg: res.msg,
|
||||
code: res.code,
|
||||
});
|
||||
}
|
||||
|
||||
export const getSystemEnergyCostGrowth = async (building_ids) => {
|
||||
const res = await instance.get(GET_SITES_SYSTEM_ENERGY_COST_GROWTH_API, building_ids);
|
||||
|
||||
return apihandler(res.code, res.data, {
|
||||
msg: res.msg,
|
||||
code: res.code,
|
||||
});
|
||||
}
|
@ -1,9 +1,10 @@
|
||||
<script setup>
|
||||
import { onMounted } from "vue";
|
||||
import { onMounted,watch } from "vue";
|
||||
import useBuildingStore from "@/stores/useBuildingStore";
|
||||
import { useRouter } from "vue-router";
|
||||
|
||||
const store = useBuildingStore();
|
||||
|
||||
const router = useRouter();
|
||||
const selectBuilding = (bui) => {
|
||||
store.selectedBuilding = bui; // 改變 selectedBuilding,watch 會自動更新資料
|
||||
};
|
||||
@ -11,6 +12,19 @@ const selectBuilding = (bui) => {
|
||||
onMounted(() => {
|
||||
store.initialize(); // 初始化資料
|
||||
});
|
||||
|
||||
watch(
|
||||
() => store.selectedBuilding,
|
||||
(newValue) => {
|
||||
console.log('Selected building changed:', newValue);
|
||||
|
||||
if (newValue.is_headquarter == true) {
|
||||
router.replace({ path: "/headquarters" });
|
||||
} else {
|
||||
router.replace({ path: "/dashboard" });
|
||||
}
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -17,8 +17,8 @@ const openKeys = ref([]); // 追蹤當前打開的子菜單
|
||||
const menu_array = ref([]);
|
||||
const currentAuthCode = ref("");
|
||||
|
||||
const iniFroList = async () => {
|
||||
const res = await getAuth(locale.value);
|
||||
const iniFroList = async (build) => {
|
||||
const res = await getAuth(build);
|
||||
|
||||
store.updateAuthPage(
|
||||
res.data.map((d) =>
|
||||
@ -72,23 +72,21 @@ watch(
|
||||
(newVal) => {
|
||||
if (newVal !== null) {
|
||||
getSubMonitorPage(newVal.building_guid);
|
||||
iniFroList(newVal.building_guid);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
watch(locale, () => {
|
||||
iniFroList();
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
iniFroList();
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<ul class="px-1 menu-box my-2">
|
||||
<li class="flex flex-col items-center justify-center">
|
||||
<router-link
|
||||
:to="{ name: 'dashboard' }"
|
||||
:to="{
|
||||
name:
|
||||
buildingStore.selectedBuilding?.is_headquarter === true
|
||||
? 'headquarters'
|
||||
: 'dashboard',
|
||||
}"
|
||||
class="flex lg:flex-col justify-center items-center btn-group text-white"
|
||||
>
|
||||
<font-awesome-icon
|
||||
@ -96,7 +94,7 @@ onMounted(() => {
|
||||
size="2x"
|
||||
class="w-10 m-auto"
|
||||
/>
|
||||
<span>{{ $t("home") }}</span>
|
||||
<span>{{ $t("navbar.home") }}</span>
|
||||
</router-link>
|
||||
</li>
|
||||
<li
|
||||
@ -132,11 +130,11 @@ onMounted(() => {
|
||||
size="2x"
|
||||
class="w-10 m-auto"
|
||||
/>
|
||||
<span>{{ page.subName }}</span>
|
||||
<span>{{ $t(`navbar.${page.showView}`) }}</span>
|
||||
</a>
|
||||
<router-link
|
||||
v-else
|
||||
:to="page.navigate"
|
||||
:to="`/` + page.showView"
|
||||
type="link"
|
||||
class="flex lg:flex-col justify-center items-center btn-group text-white"
|
||||
>
|
||||
@ -145,7 +143,7 @@ onMounted(() => {
|
||||
size="2x"
|
||||
class="w-10 m-auto"
|
||||
/>
|
||||
<span>{{ page.subName }}</span>
|
||||
<span>{{ $t(`navbar.${page.showView}`) }}</span>
|
||||
</router-link>
|
||||
</li>
|
||||
</ul>
|
||||
@ -192,7 +190,7 @@ onMounted(() => {
|
||||
params: {
|
||||
main_system_id: main.main_system_tag,
|
||||
sub_system_id: sub.sub_system_tag,
|
||||
...(currentAuthCode === 'PF2' || currentAuthCode === 'PF11'
|
||||
...(currentAuthCode === 'PF2' || currentAuthCode === 'PF11'
|
||||
? { type: sub.type }
|
||||
: { floor_id: 'main' }),
|
||||
},
|
||||
|
@ -13,6 +13,18 @@
|
||||
"name": "名称",
|
||||
"time": "时间"
|
||||
},
|
||||
"navbar": {
|
||||
"home": "首页",
|
||||
"sysMonBtnList": "系统监控",
|
||||
"historyData": "历史资料",
|
||||
"energyManagement": "能源管理",
|
||||
"alert": "告警",
|
||||
"operation": "运维管理",
|
||||
"graphManagement": "图资管理",
|
||||
"AssetManagement": "资产管理",
|
||||
"accountManagement": "帐号管理",
|
||||
"Setting": "系统设定"
|
||||
},
|
||||
"upload": {
|
||||
"title": "选择一个文件或拖放到这里",
|
||||
"description": "档案不超过 10MB",
|
||||
|
@ -10,9 +10,27 @@
|
||||
"in_otal": "筆資料",
|
||||
"skip_to": "跳至",
|
||||
"serial_number": "序號",
|
||||
"name": "姓名",
|
||||
"name": "名稱",
|
||||
"time": "時間"
|
||||
},
|
||||
"navbar": {
|
||||
"home": "首頁",
|
||||
"sysMonBtnList": "系統監控",
|
||||
"historyData": "歷史資料",
|
||||
"energyManagement": "能源管理",
|
||||
"alert": "告警",
|
||||
"operation": "運維管理",
|
||||
"graphManagement": "圖資管理",
|
||||
"AssetManagement": "資產管理",
|
||||
"accountManagement": "帳號管理",
|
||||
"Setting": "系統設定",
|
||||
"energy":{
|
||||
"energy_chart": "能耗圖表",
|
||||
"energy_report": "能耗報表",
|
||||
"chart":"圖表分析",
|
||||
"history":"歷史資料"
|
||||
}
|
||||
},
|
||||
"upload": {
|
||||
"title": "選擇一個文件或拖放到這裡",
|
||||
"description": "檔案不超過 10MB",
|
||||
|
@ -13,6 +13,18 @@
|
||||
"name": "Name",
|
||||
"time": "Time"
|
||||
},
|
||||
"navbar": {
|
||||
"home": "Home",
|
||||
"sysMonBtnList": "Monitoring",
|
||||
"historyData": "History Data",
|
||||
"energyManagement": "Energy",
|
||||
"alert": "Alert",
|
||||
"operation": "Maintenance",
|
||||
"graphManagement": "Graph",
|
||||
"AssetManagement": "Devices",
|
||||
"accountManagement": "Account",
|
||||
"Setting": "Setting"
|
||||
},
|
||||
"upload": {
|
||||
"title": "Select a file or drag and drop here",
|
||||
"description": "File size cannot exceed 10MB",
|
||||
|
@ -2,69 +2,49 @@ export const AUTHPAGES = [
|
||||
{
|
||||
authCode: "PF0",
|
||||
icon: "home",
|
||||
navigate: "/dashboard",
|
||||
},
|
||||
{
|
||||
authCode: "PF1",
|
||||
icon: "tv",
|
||||
navigate: "/system",
|
||||
},
|
||||
{
|
||||
authCode: "PF2",
|
||||
icon: "chart-pie",
|
||||
pageName: "energyManagement",
|
||||
navigate: "/energyManagement",
|
||||
},
|
||||
{
|
||||
authCode: "PF3",
|
||||
icon: "chart-area",
|
||||
navigate: "/historyData",
|
||||
},
|
||||
{
|
||||
authCode: "PF4",
|
||||
icon: "chart-line",
|
||||
navigate: "/historyData",
|
||||
},
|
||||
{
|
||||
authCode: "PF5",
|
||||
icon: "bell",
|
||||
pageName: "alert",
|
||||
navigate: "/alert",
|
||||
},
|
||||
{
|
||||
authCode: "PF6",
|
||||
icon: "server",
|
||||
pageName: "operation",
|
||||
navigate: "/operation",
|
||||
},
|
||||
{
|
||||
authCode: "PF7",
|
||||
icon: "image",
|
||||
pageName: "graphManagement",
|
||||
navigate: "/graphManagement",
|
||||
},
|
||||
{
|
||||
authCode: "PF8",
|
||||
icon: "user",
|
||||
pageName: "accountManagement",
|
||||
navigate: "/accountManagement",
|
||||
},
|
||||
{
|
||||
authCode: "PF9",
|
||||
icon: "database",
|
||||
pageName: "AssetManagement",
|
||||
navigate: "/assetManagement",
|
||||
},
|
||||
{
|
||||
authCode: "PF10",
|
||||
icon: "leaf",
|
||||
pageName: "ProductSetting",
|
||||
navigate: "/productSetting",
|
||||
},
|
||||
{
|
||||
authCode: "PF11",
|
||||
icon: "cog",
|
||||
pageName: "Setting",
|
||||
navigate: "/Setting",
|
||||
},
|
||||
];
|
||||
|
@ -107,7 +107,7 @@ const router = createRouter({
|
||||
router.beforeEach(async (to, from, next) => {
|
||||
console.log("route", to, location, document.cookie);
|
||||
// redirect to login page if not logged in and trying to access a restricted page
|
||||
const publicPages = ["/login", "/", "/headquarters"];
|
||||
const publicPages = ["/login", "/"];
|
||||
const authRequired = !publicPages.includes(to.path);
|
||||
const auth = useUserInfoStore();
|
||||
const token = useGetCookie("JWT-Authorization");
|
||||
@ -116,7 +116,7 @@ router.beforeEach(async (to, from, next) => {
|
||||
if ((authRequired && !token) || to.path === "/") {
|
||||
auth.user.token = "";
|
||||
next({ path: "/login" });
|
||||
} else if (!authRequired && (to.path === "/login" || to.path === "/")) {
|
||||
} else if (!authRequired) {
|
||||
document.cookie = "JWT-Authorization=; Max-Age=0";
|
||||
document.cookie = "user_name=; Max-Age=0";
|
||||
auth.user.token = "";
|
||||
|
@ -4,7 +4,6 @@ import { ref } from "vue";
|
||||
const useUserInfoStore = defineStore("userInfo", () => {
|
||||
const user = ref({
|
||||
token: "",
|
||||
expires: 0,
|
||||
user_name:"",
|
||||
});
|
||||
|
||||
|
@ -51,9 +51,10 @@ watch(
|
||||
if (intervalId) {
|
||||
clearInterval(intervalId);
|
||||
}
|
||||
// 每小時呼叫一次
|
||||
intervalId = setInterval(() => {
|
||||
getEnergyCostData(params);
|
||||
}, 3600000);
|
||||
}, 60 * 60 * 1000);
|
||||
}
|
||||
},
|
||||
{ immediate: true, deep: true }
|
||||
@ -91,13 +92,10 @@ onUnmounted(() => {
|
||||
/>
|
||||
</div>
|
||||
<div class="w-full xl:w-1/4 mt-2">
|
||||
<ElecRank :energyCostData="energyCostData" />
|
||||
<ElecRank />
|
||||
<ElecTrends
|
||||
:formState="formState"
|
||||
:energyCostData="energyCostData"
|
||||
:getEnergyCostData="getEnergyCostData"
|
||||
/>
|
||||
<ElecCompare :energyCostData="energyCostData" />
|
||||
<ElecCompare />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -1,69 +1,22 @@
|
||||
<script setup>
|
||||
import { ref, onMounted, watch, computed } from "vue";
|
||||
import { ref, onMounted, watch, computed, onUnmounted } from "vue";
|
||||
import * as echarts from "echarts";
|
||||
import { getSystemEnergyCostGrowth } from "@/apis/headquarters";
|
||||
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 energyCostGrowthData = ref({ day: [], week: [], month: [], year: [] });
|
||||
|
||||
const chartData = ref([]);
|
||||
const currentType = ref({
|
||||
name: "today",
|
||||
name: "day",
|
||||
});
|
||||
const energyTypeList = ref([
|
||||
{
|
||||
title: t("dashboard.daily_relative_change"),
|
||||
key: "today",
|
||||
key: "day",
|
||||
},
|
||||
{
|
||||
title: t("dashboard.weekly_relative_change"),
|
||||
@ -78,10 +31,10 @@ const energyTypeList = ref([
|
||||
key: "year",
|
||||
},
|
||||
]);
|
||||
|
||||
let intervalId = null;
|
||||
const labels = computed(() => {
|
||||
switch (currentType.value.name) {
|
||||
case "today":
|
||||
case "day":
|
||||
return [t("dashboard.today"), t("dashboard.yesterday")];
|
||||
case "week":
|
||||
return [t("dashboard.this_week"), t("dashboard.last_week")];
|
||||
@ -106,7 +59,7 @@ const barChartOptions = computed(() => ({
|
||||
left: "-10%",
|
||||
right: "1%",
|
||||
bottom: "3%",
|
||||
top: "4%",
|
||||
top: "10%",
|
||||
containLabel: true,
|
||||
},
|
||||
series: [
|
||||
@ -208,9 +161,7 @@ const barChartOptions = computed(() => ({
|
||||
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>`;
|
||||
tooltipText += `<div>${item.marker} ${item.value}</div>`;
|
||||
});
|
||||
|
||||
return tooltipText;
|
||||
@ -218,54 +169,58 @@ const barChartOptions = computed(() => ({
|
||||
},
|
||||
}));
|
||||
|
||||
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],
|
||||
async function fetchEnergyCostGrowth() {
|
||||
try {
|
||||
const res = await getSystemEnergyCostGrowth();
|
||||
energyCostGrowthData.value = res.data || {
|
||||
day: [],
|
||||
week: [],
|
||||
month: [],
|
||||
year: [],
|
||||
};
|
||||
});
|
||||
updateChartData();
|
||||
} catch (error) {
|
||||
console.error("Error fetching energy cost growth:", error);
|
||||
energyCostGrowthData.value = { day: [], week: [], month: [], year: [] };
|
||||
chartData.value = [];
|
||||
}
|
||||
}
|
||||
|
||||
function updateChartData() {
|
||||
const list = energyCostGrowthData.value[currentType.value.name] || [];
|
||||
chartData.value = list.map((item) => ({
|
||||
name: item.name,
|
||||
current: item.current,
|
||||
last: item.last,
|
||||
difference: ((item.current ?? 0) - (item.last ?? 0)).toFixed(2),
|
||||
percentage: item.percentage,
|
||||
}));
|
||||
}
|
||||
|
||||
// 使用 watch 監聽 fakeEnergyData 的變化
|
||||
watch(
|
||||
() => [fakeEnergyData.value, currentType.value],
|
||||
() => {
|
||||
updateChartData();
|
||||
() => currentType.value.name,
|
||||
(newValue) => {
|
||||
if (newValue) {
|
||||
updateChartData();
|
||||
}
|
||||
},
|
||||
{ deep: true, immediate: true } // 立即執行一次,確保初始資料載入
|
||||
{
|
||||
immediate: true,
|
||||
}
|
||||
);
|
||||
|
||||
// 监听 currentType 的变化
|
||||
watch(currentType, () => {
|
||||
updateChartData();
|
||||
onMounted(() => {
|
||||
fetchEnergyCostGrowth();
|
||||
if (intervalId) {
|
||||
clearInterval(intervalId);
|
||||
}
|
||||
intervalId = setInterval(() => {
|
||||
fetchEnergyCostGrowth();
|
||||
}, 60 * 60 * 1000);
|
||||
});
|
||||
|
||||
watch(locale, () => {
|
||||
updateChartData();
|
||||
onUnmounted(() => {
|
||||
clearInterval(intervalId);
|
||||
});
|
||||
</script>
|
||||
|
||||
@ -301,7 +256,8 @@ watch(locale, () => {
|
||||
<div
|
||||
v-for="(data, index) in chartData"
|
||||
:key="index"
|
||||
class="w-1/4 text-center mx-1"
|
||||
class="text-center mx-1"
|
||||
:style="{ width: 100 / chartData.length + '%' }"
|
||||
>
|
||||
<div
|
||||
class="text-xs bg-cyan-900 p-1 border border-cyan-100 border-opacity-20"
|
||||
|
@ -1,19 +1,17 @@
|
||||
<script setup>
|
||||
import { ref, watch } from "vue";
|
||||
import { ref, watch, computed, onUnmounted } from "vue";
|
||||
import { getSystemEnergyCostRank } from "@/apis/headquarters";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import useBuildingStore from "@/stores/useBuildingStore";
|
||||
|
||||
const store = useBuildingStore();
|
||||
const { t } = useI18n();
|
||||
|
||||
const props = defineProps({
|
||||
energyCostData: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
const energyCostData = ref({});
|
||||
const energyTypeList = ref([
|
||||
{
|
||||
title: t("dashboard.today_energy_consumption"),
|
||||
key: "today",
|
||||
key: "day",
|
||||
},
|
||||
{
|
||||
title: t("dashboard.this_month_energy_consumption"),
|
||||
@ -23,17 +21,48 @@ const energyTypeList = ref([
|
||||
const currentEnergyType = ref({
|
||||
name: "month",
|
||||
});
|
||||
let intervalId = null;
|
||||
|
||||
// 取得當前能耗資料
|
||||
const getCurrentEnergyData = () => {
|
||||
if (!props.energyCostData || !props.energyCostData.rank) {
|
||||
return []; // 或者返回一些默认值
|
||||
const currentEnergyData = computed(() => {
|
||||
if (!energyCostData.value) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return currentEnergyType.value.name === "month"
|
||||
? props.energyCostData?.rank.month || []
|
||||
: props.energyCostData?.rank.day || [];
|
||||
? energyCostData.value?.month || []
|
||||
: energyCostData.value?.day || [];
|
||||
});
|
||||
|
||||
const getEnergyRank = async () => {
|
||||
try {
|
||||
const res = await getSystemEnergyCostRank({
|
||||
building_ids: store.buildings.map((building) => building.building_guid),
|
||||
});
|
||||
energyCostData.value = res.data;
|
||||
} catch (error) {
|
||||
console.error("Error fetching energy cost rank:", error);
|
||||
}
|
||||
};
|
||||
|
||||
watch(
|
||||
() => store.buildings,
|
||||
(newBuilding) => {
|
||||
if (newBuilding) {
|
||||
getEnergyRank();
|
||||
|
||||
if (intervalId) {
|
||||
clearInterval(intervalId);
|
||||
}
|
||||
intervalId = setInterval(() => {
|
||||
getEnergyRank();
|
||||
}, 60 * 60 * 1000);
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
onUnmounted(() => {
|
||||
clearInterval(intervalId);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@ -62,7 +91,7 @@ const getCurrentEnergyData = () => {
|
||||
<table class="table table-sm text-center">
|
||||
<tbody>
|
||||
<tr
|
||||
v-for="(item, index) in getCurrentEnergyData()"
|
||||
v-for="(item, index) in currentEnergyData"
|
||||
:key="index"
|
||||
:class="[
|
||||
{ 'text-red-300': index + 1 === 1 },
|
||||
@ -71,13 +100,15 @@ const getCurrentEnergyData = () => {
|
||||
{ 'text-teal-300': index + 1 > 3 },
|
||||
]"
|
||||
>
|
||||
<td class="flex items-center">
|
||||
<font-awesome-icon :icon="['fas', 'crown']" class="me-1" />{{
|
||||
index + 1
|
||||
}}
|
||||
</td>
|
||||
<td>{{ item.name }}</td>
|
||||
<td>{{ item.value }}</td>
|
||||
<td class="px-0 align-top">
|
||||
<p class="flex items-center">
|
||||
<font-awesome-icon :icon="['fas', 'crown']" class="me-1" />
|
||||
{{ index + 1 }}
|
||||
</p>
|
||||
</td>
|
||||
<td class="align-top whitespace-nowrap px-0">{{ item.site_name }}</td>
|
||||
<td class="align-top">{{ item.name }}</td>
|
||||
<td class="align-top ps-0">{{ item.value }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
@ -1,6 +1,7 @@
|
||||
<script setup>
|
||||
import { ref, onMounted, watch } from "vue";
|
||||
import { ref, onMounted, watch, onUnmounted } from "vue";
|
||||
import * as echarts from "echarts";
|
||||
import { getSystemEnergyCostTrend } from "@/apis/headquarters";
|
||||
import BarChart from "@/components/chart/BarChart.vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import dayjs from "dayjs";
|
||||
@ -9,29 +10,12 @@ import useBuildingStore from "@/stores/useBuildingStore";
|
||||
const storeBuild = useBuildingStore();
|
||||
const { t } = useI18n();
|
||||
|
||||
const props = defineProps({
|
||||
formState: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
energyCostData: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
getEnergyCostData: {
|
||||
type: Function,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
const chartData = ref([]);
|
||||
const buildingList = ref([]);
|
||||
const floorList = ref([]);
|
||||
const deptList = ref([]);
|
||||
const energyCostData = ref([]);
|
||||
const weekComparisonOption = ref({});
|
||||
const currentType = ref({
|
||||
name: "all",
|
||||
});
|
||||
const currentType = ref({});
|
||||
let intervalId = null;
|
||||
// 生成柱狀圖的 option
|
||||
const generateCylinderChartOption = (data) => {
|
||||
const barWidth = 15;
|
||||
@ -113,42 +97,38 @@ const generateCylinderChartOption = (data) => {
|
||||
};
|
||||
};
|
||||
|
||||
const processEnergyData = () => {
|
||||
if (!props.energyCostData || !props.energyCostData.trend) {
|
||||
chartData.value = [];
|
||||
const processEnergyData = async () => {
|
||||
try {
|
||||
const res = await getSystemEnergyCostTrend({
|
||||
building_ids: [currentType.value.name],
|
||||
});
|
||||
energyCostData.value = res.data.trend || [];
|
||||
if (!energyCostData.value || energyCostData.value.length === 0) {
|
||||
chartData.value = [];
|
||||
weekComparisonOption.value = generateCylinderChartOption(chartData.value);
|
||||
return;
|
||||
}
|
||||
const dailyData = [...energyCostData.value].sort(
|
||||
(a, b) => new Date(a.time) - new Date(b.time)
|
||||
);
|
||||
chartData.value = dailyData.map((item) => ({
|
||||
date: dayjs(item.time).format("MM/DD"),
|
||||
energy: item.value,
|
||||
}));
|
||||
weekComparisonOption.value = generateCylinderChartOption(chartData.value);
|
||||
return;
|
||||
} catch (error) {
|
||||
console.error("Error fetching energy cost trend:", error);
|
||||
}
|
||||
|
||||
const dailyData = [...props.energyCostData.trend].sort(
|
||||
(a, b) => new Date(a.time) - new Date(b.time)
|
||||
);
|
||||
|
||||
chartData.value = dailyData.map((item) => ({
|
||||
date: dayjs(item.time).format("MM/DD"),
|
||||
energy: item.value,
|
||||
}));
|
||||
|
||||
weekComparisonOption.value = generateCylinderChartOption(chartData.value);
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.energyCostData,
|
||||
(newEnergyCostData) => {
|
||||
processEnergyData();
|
||||
},
|
||||
{ deep: true, immediate: true }
|
||||
);
|
||||
|
||||
watch(
|
||||
() => storeBuild.buildings,
|
||||
(newValue) => {
|
||||
if (newValue) {
|
||||
currentType.value = {
|
||||
name: newValue[0]?.building_guid || "all",
|
||||
};
|
||||
buildingList.value = [
|
||||
{
|
||||
title: "All",
|
||||
key: "all",
|
||||
},
|
||||
...newValue.map((building) => ({
|
||||
title: building.full_name,
|
||||
key: building.building_guid,
|
||||
@ -161,42 +141,28 @@ watch(
|
||||
}
|
||||
);
|
||||
|
||||
// 監聽 currentType 變化時重新取得資料
|
||||
watch(
|
||||
() => storeBuild.floorList,
|
||||
() => currentType.value.name,
|
||||
(newValue) => {
|
||||
if (newValue) {
|
||||
processEnergyData();
|
||||
if (intervalId) {
|
||||
clearInterval(intervalId);
|
||||
}
|
||||
intervalId = setInterval(() => {
|
||||
processEnergyData();
|
||||
}, 60 * 60 * 1000);
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
}
|
||||
);
|
||||
|
||||
floorList.value = [
|
||||
{
|
||||
title: "All",
|
||||
key: "all",
|
||||
},
|
||||
...storeBuild.floorList,
|
||||
];
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
}
|
||||
);
|
||||
watch(
|
||||
() => storeBuild.deptList,
|
||||
(newValue) => {
|
||||
if (newValue) {
|
||||
deptList.value = [
|
||||
{
|
||||
title: "All",
|
||||
key: "all",
|
||||
},
|
||||
...storeBuild.deptList,
|
||||
];
|
||||
}
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
immediate: true,
|
||||
}
|
||||
);
|
||||
onUnmounted(() => {
|
||||
clearInterval(intervalId);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@ -207,7 +173,7 @@ watch(
|
||||
</h2>
|
||||
<Select
|
||||
:value="currentType"
|
||||
class="w-auto my-2"
|
||||
class="w-[8.5rem] my-2"
|
||||
selectClass="border-info focus-within:border-info btn-xs text-xs"
|
||||
name="name"
|
||||
Attribute="title"
|
||||
|
@ -1,9 +1,9 @@
|
||||
<script setup>
|
||||
import { ref, computed, watch, onUnmounted } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import { getAlarmOperationInfo } from "@/apis/dashboard";
|
||||
import { getSystemStatus } from "@/apis/headquarters";
|
||||
import useBuildingStore from "@/stores/useBuildingStore";
|
||||
// import DashboardSysProgressModal from "./DashboardSysProgressModal.vue";
|
||||
import SysProgressModal from "./SysProgressModal.vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
|
||||
const { t } = useI18n();
|
||||
@ -16,17 +16,27 @@ const equipmentData = ref({
|
||||
const modalData = ref({});
|
||||
let intervalId = null;
|
||||
|
||||
const openModal = (item) => {
|
||||
modalData.value = item;
|
||||
system_status_modal.showModal();
|
||||
};
|
||||
|
||||
const onCancel = () => {
|
||||
modalData.value = {};
|
||||
system_status_modal.close();
|
||||
};
|
||||
|
||||
const getAlarmsInfos = async () => {
|
||||
try {
|
||||
const res = await getAlarmOperationInfo(
|
||||
store.selectedBuilding.building_guid
|
||||
);
|
||||
const res = await getSystemStatus({
|
||||
building_ids: store.buildings.map((building) => building.building_guid),
|
||||
});
|
||||
const apiData = res.data;
|
||||
|
||||
// 轉換 equipmentData 的資料格式
|
||||
if (apiData && apiData.alarm) {
|
||||
equipmentData.value.items = apiData.alarm.map((item) => ({
|
||||
label: item.name,
|
||||
label: item.system_name,
|
||||
online: item.online || 0,
|
||||
offline: item.offline || 0,
|
||||
alarm: item.alarm || 0,
|
||||
@ -38,7 +48,7 @@ const getAlarmsInfos = async () => {
|
||||
};
|
||||
|
||||
watch(
|
||||
() => store.selectedBuilding,
|
||||
() => store.buildings,
|
||||
(newBuilding) => {
|
||||
if (newBuilding) {
|
||||
getAlarmsInfos();
|
||||
@ -60,44 +70,45 @@ onUnmounted(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- <DashboardSysProgressModal :onCancel="onCancel" :modalData="modalData" /> -->
|
||||
<div class="w-full state-box-col relative">
|
||||
<div class="state-box">
|
||||
<div class="title">
|
||||
<img class="state-title01" src="@ASSET/img/state-title01.svg" />
|
||||
<span class="">{{ equipmentData.title }}</span>
|
||||
<img class="state-title02" src="@ASSET/img/state-title02.svg" />
|
||||
</div>
|
||||
<table class="table table-sm text-center">
|
||||
<thead>
|
||||
<tr class="border-cyan-400 text-cyan-100">
|
||||
<th></th>
|
||||
<th>{{ $t("alert.online") }}</th>
|
||||
<th>{{ $t("alert.offline") }}</th>
|
||||
<th>{{ $t("alert.alarm") }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr
|
||||
v-for="(item, index) in equipmentData.items"
|
||||
:key="index"
|
||||
class="border-cyan-400 cursor-pointer hover:text-info"
|
||||
>
|
||||
<th class="px-0 text-start">{{ item.label }}</th>
|
||||
<td>
|
||||
{{ item.online.length }}
|
||||
</td>
|
||||
<td>
|
||||
{{ item.offline.length }}
|
||||
</td>
|
||||
<td>
|
||||
{{ item.alarm.length }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<SysProgressModal :onCancel="onCancel" :modalData="modalData" />
|
||||
<div class="w-full state-box-col relative">
|
||||
<div class="state-box">
|
||||
<div class="title">
|
||||
<img class="state-title01" src="@ASSET/img/state-title01.svg" />
|
||||
<span class="">{{ equipmentData.title }}</span>
|
||||
<img class="state-title02" src="@ASSET/img/state-title02.svg" />
|
||||
</div>
|
||||
<table class="table table-sm text-center">
|
||||
<thead>
|
||||
<tr class="border-cyan-400 text-cyan-100">
|
||||
<th></th>
|
||||
<th>{{ $t("alert.online") }}</th>
|
||||
<th>{{ $t("alert.offline") }}</th>
|
||||
<th>{{ $t("alert.alarm") }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr
|
||||
v-for="(item, index) in equipmentData.items"
|
||||
:key="index"
|
||||
class="border-cyan-400 cursor-pointer hover:text-info"
|
||||
@click.stop.prevent="openModal(item)"
|
||||
>
|
||||
<th class="px-0 text-start">{{ item.label }}</th>
|
||||
<td>
|
||||
{{ item.online.length }}
|
||||
</td>
|
||||
<td>
|
||||
{{ item.offline.length }}
|
||||
</td>
|
||||
<td>
|
||||
{{ item.alarm.length }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
132
src/views/headquarters/components/SysProgressModal.vue
Normal file
132
src/views/headquarters/components/SysProgressModal.vue
Normal file
@ -0,0 +1,132 @@
|
||||
<script setup>
|
||||
import { ref, onMounted, defineProps, inject, watch } from "vue";
|
||||
import useActiveBtn from "@/hooks/useActiveBtn";
|
||||
import { useI18n } from "vue-i18n";
|
||||
|
||||
const { t } = useI18n();
|
||||
const props = defineProps({
|
||||
onCancel: Function,
|
||||
modalData: Object,
|
||||
});
|
||||
const { items, changeActiveBtn, setItems, selectedBtn } = useActiveBtn();
|
||||
const detailData = ref([]);
|
||||
onMounted(() => {
|
||||
setItems([
|
||||
{
|
||||
title: t("alert.online"),
|
||||
key: "online",
|
||||
active: true,
|
||||
},
|
||||
{
|
||||
title: t("alert.offline"),
|
||||
key: "offline",
|
||||
active: false,
|
||||
},
|
||||
{
|
||||
title: t("alert.alarm"),
|
||||
key: "alarm",
|
||||
active: false,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
watch(selectedBtn, (newVal, oldVal) => {
|
||||
if (newVal) {
|
||||
detailData.value = props.modalData[newVal.key];
|
||||
}
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.modalData,
|
||||
(newVal, oldVal) => {
|
||||
if (newVal) {
|
||||
changeActiveBtn(items.value[0]);
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal id="system_status_modal" :onCancel="onCancel" :width="600">
|
||||
<template #modalTitle>
|
||||
<div class="flex items-center justify-between">
|
||||
<ButtonGroup
|
||||
:items="items"
|
||||
:withLine="true"
|
||||
className="btn-sm"
|
||||
:onclick="
|
||||
(e, item) => {
|
||||
changeActiveBtn(item);
|
||||
}
|
||||
"
|
||||
/>
|
||||
<button
|
||||
type="link"
|
||||
class="btn-link btn-text-without-border px-2"
|
||||
@click="onCancel"
|
||||
>
|
||||
<font-awesome-icon :icon="['fas', 'times']" class="text-[#a5abb1]" />
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
<template #modalContent>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="table text-base mt-5">
|
||||
<thead>
|
||||
<tr>
|
||||
<th
|
||||
class="text-base border text-white text-center bg-cyan-600 bg-opacity-30"
|
||||
>
|
||||
{{ $t("table.serial_number") }}
|
||||
</th>
|
||||
<th
|
||||
class="text-base border text-white text-center bg-cyan-600 bg-opacity-30"
|
||||
>
|
||||
{{ $t("history.building_name") }}
|
||||
</th>
|
||||
<th
|
||||
class="text-base border text-white text-center bg-cyan-600 bg-opacity-30"
|
||||
>
|
||||
{{ $t("table.name") }}
|
||||
</th>
|
||||
<th
|
||||
class="text-base border text-white text-center bg-cyan-600 bg-opacity-30"
|
||||
>
|
||||
{{ $t("table.time") }}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr
|
||||
v-if="detailData?.length > 0"
|
||||
v-for="(equipment, index) in detailData"
|
||||
:key="index"
|
||||
class="hover:bg-gray-700"
|
||||
>
|
||||
<td class="border text-white text-center">
|
||||
{{ index + 1 }}
|
||||
</td>
|
||||
<td class="border text-white text-center">
|
||||
{{ equipment.building_name }}
|
||||
</td>
|
||||
<td class="border text-white text-center">
|
||||
{{ equipment.name }}
|
||||
</td>
|
||||
<td class="border text-white text-center">
|
||||
{{ equipment.time || "-" }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-else>
|
||||
<td colspan="4" class="border text-white text-center">
|
||||
{{ $t("table.no_data") }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</template>
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped></style>
|
@ -37,7 +37,7 @@ const doLogin = async () => {
|
||||
const res = await Login(value);
|
||||
if (res.isSuccess) {
|
||||
store.user = res.data;
|
||||
localStorage.setItem("CviBuildingList", JSON.stringify(res.data.buildingIdList));
|
||||
localStorage.setItem("CviBuildingList", JSON.stringify(res.data.building_infos));
|
||||
router.replace({ path: "/dashboard" });
|
||||
} else {
|
||||
openToast("error", res.msg);
|
||||
|
Loading…
Reference in New Issue
Block a user