總部首頁 api 串接 | navbar語言包

This commit is contained in:
koko 2025-08-04 15:13:18 +08:00
parent 1812ce2495
commit db5f15dfde
18 changed files with 475 additions and 298 deletions

View File

@ -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,

View 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`;

View 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,
});
}

View File

@ -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; // selectedBuildingwatch
};
@ -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>

View File

@ -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' }),
},

View File

@ -13,6 +13,18 @@
"name": "名称",
"time": "时间"
},
"navbar": {
"home": "首页",
"sysMonBtnList": "系统监控",
"historyData": "历史资料",
"energyManagement": "能源管理",
"alert": "告警",
"operation": "运维管理",
"graphManagement": "图资管理",
"AssetManagement": "资产管理",
"accountManagement": "帐号管理",
"Setting": "系统设定"
},
"upload": {
"title": "选择一个文件或拖放到这里",
"description": "档案不超过 10MB",

View File

@ -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",

View File

@ -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",

View File

@ -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",
},
];

View File

@ -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 = "";

View File

@ -4,7 +4,6 @@ import { ref } from "vue";
const useUserInfoStore = defineStore("userInfo", () => {
const user = ref({
token: "",
expires: 0,
user_name:"",
});

View File

@ -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>

View File

@ -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"

View File

@ -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>

View File

@ -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"

View File

@ -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>

View 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>

View File

@ -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);