首頁重新切版 | 樓層與部門放置store | 設定MQTT重新切版
This commit is contained in:
parent
c8d8fbf254
commit
f956402648
@ -1,27 +1,20 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { getBuildings } from "@/apis/building";
|
import { onMounted } from "vue";
|
||||||
import { onMounted, ref } from "vue";
|
|
||||||
import useBuildingStore from "@/stores/useBuildingStore";
|
import useBuildingStore from "@/stores/useBuildingStore";
|
||||||
|
|
||||||
const store = useBuildingStore();
|
const store = useBuildingStore();
|
||||||
|
|
||||||
const getBui = async () => {
|
|
||||||
const res = await getBuildings();
|
|
||||||
store.buildings = res.data;
|
|
||||||
store.selectedBuilding = res?.data[0];
|
|
||||||
};
|
|
||||||
|
|
||||||
const selectBuilding = (bui) => {
|
const selectBuilding = (bui) => {
|
||||||
store.selectedBuilding = bui;
|
store.selectedBuilding = bui; // 改變 selectedBuilding,watch 會自動更新資料
|
||||||
};
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
getBui();
|
store.initialize(); // 初始化資料
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="dropdown dropdown-bottom ">
|
<div class="dropdown dropdown-bottom">
|
||||||
<div
|
<div
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
role="button"
|
role="button"
|
||||||
@ -46,4 +39,4 @@ onMounted(() => {
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped></style>
|
<style lang="scss" scoped></style>
|
@ -46,11 +46,11 @@ const getSubPage = async (system_type) => {
|
|||||||
const res = await getSideBar(system_type);
|
const res = await getSideBar(system_type);
|
||||||
menu_array.value = res.data;
|
menu_array.value = res.data;
|
||||||
};
|
};
|
||||||
const showDrawer = (authCode) => {
|
const showDrawer = async (authCode) => {
|
||||||
if (authCode === "PF1") {
|
if (authCode === "PF1") {
|
||||||
getSubMonitorPage();
|
await getSubMonitorPage();
|
||||||
} else if (authCode === "PF2" || authCode === "PF11") {
|
} else if (authCode === "PF2" || authCode === "PF11") {
|
||||||
getSubPage(authCode === "PF2" ? "Energy" : "Setting");
|
await getSubPage(authCode === "PF2" ? "Energy" : "Setting");
|
||||||
}
|
}
|
||||||
currentAuthCode.value = authCode;
|
currentAuthCode.value = authCode;
|
||||||
open.value = true;
|
open.value = true;
|
||||||
|
@ -101,7 +101,37 @@
|
|||||||
"total_amount": "金额总计",
|
"total_amount": "金额总计",
|
||||||
"elec_price_list": "电价表",
|
"elec_price_list": "电价表",
|
||||||
"residential": "住宅型",
|
"residential": "住宅型",
|
||||||
"standard": "标准型"
|
"standard": "标准型",
|
||||||
|
"simple_elec_price_two_stage": "简易型时间电价二段式",
|
||||||
|
"simple_elec_price_three_stage": "简易型时间电价三段式",
|
||||||
|
"classification": "分类",
|
||||||
|
"summer_months": "夏月",
|
||||||
|
"non_summer_months": "非夏月",
|
||||||
|
"time_outside_summer_months": "夏月以外的时间",
|
||||||
|
"basic_elec_charge": "基本电费",
|
||||||
|
"charged_per_household": "按户计收",
|
||||||
|
"per_household_month": "每户每月",
|
||||||
|
"mon_to_friday": "周一~周五",
|
||||||
|
"peak_hours": "尖峰时间",
|
||||||
|
"semi_peak_hours": "半尖峰时间",
|
||||||
|
"off_peak_hours": "离峰时间",
|
||||||
|
"price_per_kwh": "每度",
|
||||||
|
"all_day": "全日",
|
||||||
|
"sat_sun_off_peak_days": "周六、周日及离峰日",
|
||||||
|
"usage_over_2000kwh": "每月总度数超过2000度之部分",
|
||||||
|
"add": "加",
|
||||||
|
"standard_time_of_use_tariff_2_stage": "标准型时间电价二段式",
|
||||||
|
"standard_time_of_use_tariff_3_stage": "标准型时间电价三段式",
|
||||||
|
"single_phase": "单相",
|
||||||
|
"three_phase": "三相",
|
||||||
|
"frequent_contract": "经常契约",
|
||||||
|
"per_kw_per_month": "每瓩每月",
|
||||||
|
"non_summer_contract": "非夏日契约",
|
||||||
|
"saturday_semi_peak_contract": "周六半尖峰契约",
|
||||||
|
"off_peak_contract": "离峰契约",
|
||||||
|
"variable_electricity_charge": "流动电费",
|
||||||
|
"saturday": "周六",
|
||||||
|
"sunday_and_off_peak_days": "周日及离峰日"
|
||||||
},
|
},
|
||||||
"alarm": {
|
"alarm": {
|
||||||
"title": "显示警告",
|
"title": "显示警告",
|
||||||
|
@ -101,7 +101,37 @@
|
|||||||
"total_amount": "金額總計",
|
"total_amount": "金額總計",
|
||||||
"elec_price_list": "電價表",
|
"elec_price_list": "電價表",
|
||||||
"residential": "住宅型",
|
"residential": "住宅型",
|
||||||
"standard": "標準型"
|
"standard": "標準型",
|
||||||
|
"simple_elec_price_two_stage":"簡易型時間電價二段式",
|
||||||
|
"simple_elec_price_three_stage":"簡易型時間電價三段式",
|
||||||
|
"classification":"分類",
|
||||||
|
"summer_months":"夏月",
|
||||||
|
"non_summer_months":"非夏月",
|
||||||
|
"time_outside_summer_months":"夏月以外的時間",
|
||||||
|
"basic_elec_charge": "基本電費",
|
||||||
|
"charged_per_household": "按戶計收",
|
||||||
|
"per_household_month": "每戶每月",
|
||||||
|
"mon_to_friday": "週一~週五",
|
||||||
|
"peak_hours": "尖峰時間",
|
||||||
|
"semi_peak_hours": "半尖峰時間",
|
||||||
|
"off_peak_hours": "離峰時間",
|
||||||
|
"price_per_kwh": "每度",
|
||||||
|
"all_day": "全日",
|
||||||
|
"sat_sun_off_peak_days": "週六、週日及離峰日",
|
||||||
|
"usage_over_2000kwh": "每月總度數超過2000度之部分",
|
||||||
|
"add": "加",
|
||||||
|
"standard_time_of_use_tariff_2_stage": "標準型時間電價二段式",
|
||||||
|
"standard_time_of_use_tariff_3_stage": "標準型時間電價三段式",
|
||||||
|
"single_phase": "單相",
|
||||||
|
"three_phase": "三相",
|
||||||
|
"frequent_contract": "經常契約",
|
||||||
|
"per_kw_per_month": "每瓩每月",
|
||||||
|
"non_summer_contract": "非夏日契約",
|
||||||
|
"saturday_semi_peak_contract": "週六半尖峰契約",
|
||||||
|
"off_peak_contract": "離峰契約",
|
||||||
|
"variable_electricity_charge": "流動電費",
|
||||||
|
"saturday": "週六",
|
||||||
|
"sunday_and_off_peak_days": "週日及離峰日"
|
||||||
},
|
},
|
||||||
"alarm": {
|
"alarm": {
|
||||||
"title": "顯示警告",
|
"title": "顯示警告",
|
||||||
|
@ -101,7 +101,37 @@
|
|||||||
"total_amount": "Total amount",
|
"total_amount": "Total amount",
|
||||||
"elec_price_list": "Electricity Price List",
|
"elec_price_list": "Electricity Price List",
|
||||||
"residential": "Residential",
|
"residential": "Residential",
|
||||||
"standard": "Standard"
|
"standard": "Standard",
|
||||||
|
"simple_elec_price_two_stage": "Simple Time-of-Use Electricity Price (Two-Tier)",
|
||||||
|
"simple_elec_price_three_stage": "Simple Time-of-Use Electricity Price (Three-Tier)",
|
||||||
|
"classification": "Classification",
|
||||||
|
"summer_months": "Summer Months",
|
||||||
|
"non_summer_months": "Non-Summer Months",
|
||||||
|
"time_outside_summer_months": "Time Outside Summer Months",
|
||||||
|
"basic_elec_charge": "Basic Electricity Charge",
|
||||||
|
"charged_per_household": "Charged Per Household",
|
||||||
|
"per_household_month": "Per Household Per Month",
|
||||||
|
"mon_to_friday": "Monday to Friday",
|
||||||
|
"peak_hours": "Peak Hours",
|
||||||
|
"semi_peak_hours": "Semi-Peak Hours",
|
||||||
|
"off_peak_hours": "Off-Peak Hours",
|
||||||
|
"price_per_kwh": "Price Per kWh",
|
||||||
|
"all_day": "All Day",
|
||||||
|
"sat_sun_off_peak_days": "Saturday, Sunday, and Off-Peak Days",
|
||||||
|
"usage_over_2000kwh": "Usage Over 2000 kWh Per Month",
|
||||||
|
"add": "Add",
|
||||||
|
"standard_time_of_use_tariff_2_stage": "Standard Time-of-Use Tariff (Two-Tier)",
|
||||||
|
"standard_time_of_use_tariff_3_stage": "Standard Time-of-Use Tariff (Three-Tier)",
|
||||||
|
"single_phase": "Single Phase",
|
||||||
|
"three_phase": "Three Phase",
|
||||||
|
"frequent_contract": "Demand Charge",
|
||||||
|
"per_kw_per_month": "Per kW Per Month",
|
||||||
|
"non_summer_contract": "Non-Summer Demand Charge",
|
||||||
|
"saturday_semi_peak_contract": "Saturday Semi-Peak Demand Charge",
|
||||||
|
"off_peak_contract": "Off-Peak Demand Charge",
|
||||||
|
"variable_electricity_charge": "Variable Electricity Charge",
|
||||||
|
"saturday": "Saturday",
|
||||||
|
"sunday_and_off_peak_days": "Sunday and Off-Peak Days"
|
||||||
},
|
},
|
||||||
"alarm": {
|
"alarm": {
|
||||||
"title": "Warning",
|
"title": "Warning",
|
||||||
|
@ -61,6 +61,7 @@ import {
|
|||||||
faDownload,
|
faDownload,
|
||||||
faStream,
|
faStream,
|
||||||
faSave,
|
faSave,
|
||||||
|
faCrown
|
||||||
} from "@fortawesome/free-solid-svg-icons";
|
} from "@fortawesome/free-solid-svg-icons";
|
||||||
|
|
||||||
/* add icons to the library */
|
/* add icons to the library */
|
||||||
@ -122,7 +123,8 @@ library.add(
|
|||||||
faGlobe,
|
faGlobe,
|
||||||
faDownload,
|
faDownload,
|
||||||
faStream,
|
faStream,
|
||||||
faSave
|
faSave,
|
||||||
|
faCrown
|
||||||
);
|
);
|
||||||
|
|
||||||
export default library;
|
export default library;
|
||||||
|
@ -1,17 +1,18 @@
|
|||||||
import { defineStore } from "pinia";
|
import { defineStore } from "pinia";
|
||||||
import { ref, computed } from "vue";
|
import { ref, computed, watch } from "vue";
|
||||||
import { useRoute } from "vue-router";
|
import { useRoute } from "vue-router";
|
||||||
|
import { getBuildings } from "@/apis/building";
|
||||||
|
import { getAssetFloorList, getDepartmentList } from "@/apis/asset";
|
||||||
|
|
||||||
const useBuildingStore = defineStore("buildingInfo", () => {
|
const useBuildingStore = defineStore("buildingInfo", () => {
|
||||||
// 所有棟別
|
// 狀態定義
|
||||||
const buildings = ref([]);
|
const buildings = ref([]);
|
||||||
|
|
||||||
const selectedBuilding = ref(null);
|
const selectedBuilding = ref(null);
|
||||||
|
const floorList = ref([]);
|
||||||
// 所有大小類系統
|
const deptList = ref([]);
|
||||||
const mainSubSys = ref([]);
|
const mainSubSys = ref([]);
|
||||||
|
|
||||||
// 所有大類
|
// 計算屬性
|
||||||
const mainSys = computed(() =>
|
const mainSys = computed(() =>
|
||||||
mainSubSys.value.map(({ main_system_tag, full_name }) => ({
|
mainSubSys.value.map(({ main_system_tag, full_name }) => ({
|
||||||
main_system_tag,
|
main_system_tag,
|
||||||
@ -21,7 +22,6 @@ const useBuildingStore = defineStore("buildingInfo", () => {
|
|||||||
|
|
||||||
const subSys = computed(() => {
|
const subSys = computed(() => {
|
||||||
let subPages = [];
|
let subPages = [];
|
||||||
|
|
||||||
mainSubSys.value.forEach(({ main_system_tag, history_Sub_systems }) => {
|
mainSubSys.value.forEach(({ main_system_tag, history_Sub_systems }) => {
|
||||||
subPages = [
|
subPages = [
|
||||||
...subPages,
|
...subPages,
|
||||||
@ -32,7 +32,6 @@ const useBuildingStore = defineStore("buildingInfo", () => {
|
|||||||
})),
|
})),
|
||||||
];
|
];
|
||||||
});
|
});
|
||||||
|
|
||||||
return subPages;
|
return subPages;
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -44,14 +43,60 @@ const useBuildingStore = defineStore("buildingInfo", () => {
|
|||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 獲取所有建築物
|
||||||
|
const fetchBuildings = async () => {
|
||||||
|
const res = await getBuildings();
|
||||||
|
buildings.value = res.data;
|
||||||
|
if (res.data.length > 0 && !selectedBuilding.value) {
|
||||||
|
selectedBuilding.value = res.data[0]; // 預設選第一個建築
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 獲取樓層資料
|
||||||
|
const fetchFloorList = async () => {
|
||||||
|
const res = await getAssetFloorList();
|
||||||
|
floorList.value = res.data[0]?.floors.map((d) => ({
|
||||||
|
...d,
|
||||||
|
title: d.full_name,
|
||||||
|
key: d.floor_guid,
|
||||||
|
})) || [];
|
||||||
|
};
|
||||||
|
|
||||||
|
// 獲取部門資料
|
||||||
|
const fetchDepartmentList = async () => {
|
||||||
|
const res = await getDepartmentList();
|
||||||
|
deptList.value = res.data.map((d) => ({
|
||||||
|
...d,
|
||||||
|
title: d.name,
|
||||||
|
key: d.id,
|
||||||
|
})) || [];
|
||||||
|
};
|
||||||
|
|
||||||
|
// 當 selectedBuilding 改變時,更新 floorList 和 deptList
|
||||||
|
watch(selectedBuilding, async (newBuilding) => {
|
||||||
|
if (newBuilding) {
|
||||||
|
await Promise.all([fetchFloorList(), fetchDepartmentList()]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 初始化資料
|
||||||
|
const initialize = async () => {
|
||||||
|
await fetchBuildings();
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
buildings,
|
buildings,
|
||||||
selectedBuilding,
|
selectedBuilding,
|
||||||
|
floorList,
|
||||||
|
deptList,
|
||||||
mainSubSys,
|
mainSubSys,
|
||||||
mainSys,
|
mainSys,
|
||||||
subSys,
|
subSys,
|
||||||
selectedSystem,
|
selectedSystem,
|
||||||
|
fetchBuildings,
|
||||||
|
fetchFloorList,
|
||||||
|
fetchDepartmentList,
|
||||||
|
initialize,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
export default useBuildingStore;
|
export default useBuildingStore;
|
||||||
|
@ -4,8 +4,10 @@ import AssetMainList from "./components/AssetMainList.vue";
|
|||||||
import AssetSubList from "./components/AssetSubList.vue";
|
import AssetSubList from "./components/AssetSubList.vue";
|
||||||
import AssetTable from "./components/AssetTable.vue";
|
import AssetTable from "./components/AssetTable.vue";
|
||||||
import { getOperationCompanyList } from "@/apis/operation";
|
import { getOperationCompanyList } from "@/apis/operation";
|
||||||
import { getDepartmentList, getIOTSchema, getElecTypeList, getAssetFloorList } from "@/apis/asset";
|
import { getIOTSchema, getElecTypeList } from "@/apis/asset";
|
||||||
import useSearchParam from "@/hooks/useSearchParam";
|
import useSearchParam from "@/hooks/useSearchParam";
|
||||||
|
import useBuildingStore from "@/stores/useBuildingStore";
|
||||||
|
const storeBuild = useBuildingStore();
|
||||||
const { searchParams, changeParams } = useSearchParam();
|
const { searchParams, changeParams } = useSearchParam();
|
||||||
const companyOptions = ref([]);
|
const companyOptions = ref([]);
|
||||||
const iotSchemaOptions = ref([]);
|
const iotSchemaOptions = ref([]);
|
||||||
@ -24,20 +26,12 @@ const getElecType = async () => {
|
|||||||
const res = await getElecTypeList();
|
const res = await getElecTypeList();
|
||||||
elecTypeOptions.value = res.data.map((d) => ({ ...d, key: d.id }));
|
elecTypeOptions.value = res.data.map((d) => ({ ...d, key: d.id }));
|
||||||
};
|
};
|
||||||
const getDepartment = async () => {
|
|
||||||
const res = await getDepartmentList();
|
|
||||||
departmentList.value = res.data.map((d) => ({ ...d, key: d.id }));
|
|
||||||
};
|
|
||||||
const getFloors = async () => {
|
|
||||||
const res = await getAssetFloorList();
|
|
||||||
floors.value = res.data[0]?.floors.map((d) => ({ ...d, key: d.floor_guid }));
|
|
||||||
};
|
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
getCompany();
|
getCompany();
|
||||||
getElecType();
|
getElecType();
|
||||||
getDepartment();
|
floors.value = storeBuild.floorList;
|
||||||
getFloors();
|
departmentList.value = storeBuild.deptList;
|
||||||
});
|
});
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
|
import { onMounted, ref, provide, watch } from "vue";
|
||||||
|
import { getDashboardInit } from "@/apis/dashboard";
|
||||||
import Forge from "@/components/forge/Forge.vue";
|
import Forge from "@/components/forge/Forge.vue";
|
||||||
import DashboardStat from "./components/DashboardStat.vue";
|
import DashboardStat from "./components/DashboardStat.vue";
|
||||||
import DashboardElecChart from "./components/DashboardElecChart.vue";
|
|
||||||
import DashboardSysCard from "./components/DashboardSysCard.vue";
|
import DashboardSysCard from "./components/DashboardSysCard.vue";
|
||||||
import DashboardSysProgress from "./components/DashboardSysProgress.vue";
|
import DashboardSysProgress from "./components/DashboardSysProgress.vue";
|
||||||
import { getDashboardInit } from "@/apis/dashboard";
|
import DashboardElecRank from "./components/DashboardElecRank.vue";
|
||||||
import { onMounted, ref, provide, watch } from "vue";
|
import DashboardElecTrends from "./components/DashboardElecTrends.vue";
|
||||||
|
import DashboardElecCompare from "./components/DashboardElecCompare.vue";
|
||||||
|
|
||||||
const initialData = ref(null);
|
const initialData = ref(null);
|
||||||
// const forgeData = ref([]);
|
// const forgeData = ref([]);
|
||||||
@ -52,24 +54,37 @@ provide("dashboard_items", {
|
|||||||
<div class="w-full xl:w-2/3">
|
<div class="w-full xl:w-2/3">
|
||||||
<!-- 用電數據 -->
|
<!-- 用電數據 -->
|
||||||
<DashboardStat />
|
<DashboardStat />
|
||||||
<!-- 用電圖表 -->
|
|
||||||
<DashboardElecChart />
|
<div class="flex flex-wrap pt-4">
|
||||||
|
<!-- 當月能耗排行 -->
|
||||||
|
<div class="lg:w-1/3 w-full ">
|
||||||
|
<DashboardElecRank />
|
||||||
|
</div>
|
||||||
|
<!-- 近30天能耗趨勢 -->
|
||||||
|
<div class="lg:w-2/3 w-full ">
|
||||||
|
<DashboardElecTrends />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 設備小卡 -->
|
<!-- 設備小卡 -->
|
||||||
<div class="w-full lg:w-2/3">
|
<div class="w-full lg:w-1/3">
|
||||||
<DashboardSysCard />
|
<DashboardSysCard />
|
||||||
</div>
|
</div>
|
||||||
<!--狀態、進度-->
|
<!--狀態、進度-->
|
||||||
<div class="w-full lg:w-1/3">
|
<div class="w-full lg:w-1/3">
|
||||||
<DashboardSysProgress />
|
<DashboardSysProgress />
|
||||||
</div>
|
</div>
|
||||||
|
<!-- 環比能耗 -->
|
||||||
|
<div class="w-full lg:w-1/3">
|
||||||
|
<DashboardElecCompare />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="css" scoped>
|
<style lang="css" scoped>
|
||||||
.area-img-box {
|
.area-img-box {
|
||||||
@apply border border-light-info bg-dark-info w-full h-[400px] block relative rounded-sm mb-4;
|
@apply border border-light-info bg-dark-info w-full h-[460px] block relative rounded-sm mt-3;
|
||||||
}
|
}
|
||||||
|
|
||||||
.area-img-box::before {
|
.area-img-box::before {
|
||||||
|
235
src/views/dashboard/components/DashboardElecCompare.vue
Normal file
235
src/views/dashboard/components/DashboardElecCompare.vue
Normal file
@ -0,0 +1,235 @@
|
|||||||
|
<script setup>
|
||||||
|
import { ref, onMounted } from "vue";
|
||||||
|
import * as echarts from "echarts";
|
||||||
|
import BarChart from "@/components/chart/BarChart.vue";
|
||||||
|
|
||||||
|
const chartData = ref([
|
||||||
|
{
|
||||||
|
category: "日環比",
|
||||||
|
this: 230.68,
|
||||||
|
last: 377.33,
|
||||||
|
change: -39,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
category: "周環比",
|
||||||
|
this: 608.01,
|
||||||
|
last: 2711.09,
|
||||||
|
change: -78,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
category: "月環比",
|
||||||
|
this: 6473.8,
|
||||||
|
last: 12701.69,
|
||||||
|
change: -49,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
category: "年環比",
|
||||||
|
this: 46687.17,
|
||||||
|
last: null,
|
||||||
|
change: null,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
const labels = ["今日", "昨日", "本周", "上周", "本月", "上月", "今年", "去年"];
|
||||||
|
const barWidth = 30; // Set barWidth
|
||||||
|
|
||||||
|
const barChartOptions = ref({
|
||||||
|
xAxis: {
|
||||||
|
type: "category",
|
||||||
|
data: chartData.value.map((item) => item.category),
|
||||||
|
axisLine: { lineStyle: { color: "#fff" } },
|
||||||
|
},
|
||||||
|
yAxis: { type: "value", show: false },
|
||||||
|
grid: {
|
||||||
|
left: "-10%",
|
||||||
|
right: "0%",
|
||||||
|
bottom: "3%",
|
||||||
|
top: "10%",
|
||||||
|
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;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="flex flex-wrap">
|
||||||
|
<div class="w-full chart-data relative px-8 py-3">
|
||||||
|
<div class="flex flex-wrap items-center justify-between">
|
||||||
|
<h2 class="font-light">環比能耗</h2>
|
||||||
|
</div>
|
||||||
|
<div class="h-[180px]">
|
||||||
|
<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-sm bg-cyan-900 p-1 border border-cyan-100 border-opacity-20"
|
||||||
|
>
|
||||||
|
{{ labels[index * 2] }}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="text-sm bg-cyan-900 p-1 border border-cyan-100 border-opacity-20"
|
||||||
|
>
|
||||||
|
{{ data.this ?? "-" }}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="text-sm bg-cyan-900 p-1 border border-cyan-100 border-opacity-20"
|
||||||
|
>
|
||||||
|
{{ labels[index * 2 + 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.change > 0,
|
||||||
|
'text-green-500': data.change < 0,
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
{{
|
||||||
|
data.change
|
||||||
|
? (data.change > 0 ? "+" : "") + data.change + "%"
|
||||||
|
: "-"
|
||||||
|
}}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.chart-data:before {
|
||||||
|
@apply absolute right-0 left-2 top-0 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-0 bottom-0 h-10 w-10 bg-no-repeat z-10;
|
||||||
|
content: "";
|
||||||
|
background: url(@ASSET/img/chart-data-background02.svg) center center;
|
||||||
|
}
|
||||||
|
</style>
|
103
src/views/dashboard/components/DashboardElecRank.vue
Normal file
103
src/views/dashboard/components/DashboardElecRank.vue
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
<script setup>
|
||||||
|
import { ref } from "vue";
|
||||||
|
|
||||||
|
// 假資料 - 設備能耗列表
|
||||||
|
const mockEnergyData = {
|
||||||
|
monthly: [
|
||||||
|
{ id: "62090003", name: "電機-100HP 排氣扇", energy: 450, unit: "kWh" },
|
||||||
|
{ id: "62090113", name: "電機-75P 泵浦", energy: 80, unit: "kWh" },
|
||||||
|
{ id: "62090043", name: "電機-50P 電II", energy: 40, unit: "kWh" },
|
||||||
|
{ id: "62090038", name: "電機-50P 電II", energy: 35, unit: "kWh" },
|
||||||
|
{ id: "62090106", name: "電機-50P 泵浦", energy: 25, unit: "kWh" },
|
||||||
|
{ id: "62090086", name: "電機-30P 排氣", energy: 20, unit: "kWh" },
|
||||||
|
{ id: "19010225", name: "VOC 廢氣處理設備", energy: 15, unit: "kWh" },
|
||||||
|
{ id: "14010138", name: "固體燃料燒結機", energy: 10, unit: "kWh" },
|
||||||
|
],
|
||||||
|
daily: [
|
||||||
|
{ id: "62090003", name: "電機-100HP 排氣扇", energy: 132533, unit: "kWh" },
|
||||||
|
{ id: "62090113", name: "電機-75P 泵浦", energy: 24445, unit: "kWh" },
|
||||||
|
{ id: "62090043", name: "電機-50P 電II", energy: 11775, unit: "kWh" },
|
||||||
|
{ id: "62090038", name: "電機-50P 電II", energy: 10600, unit: "kWh" },
|
||||||
|
{ id: "62090106", name: "電機-50P 泵浦", energy: 6660, unit: "kWh" },
|
||||||
|
{ id: "62090086", name: "電機-30P 排氣", energy: 6579, unit: "kWh" },
|
||||||
|
{ id: "19010225", name: "VOC 廢氣處理設備", energy: 6367, unit: "kWh" },
|
||||||
|
{ id: "14010138", name: "固體燃料燒結機", energy: 2392, unit: "kWh" },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
// 當前顯示的能耗類型
|
||||||
|
const currentEnergyType = ref("monthly");
|
||||||
|
|
||||||
|
// 切換能耗類型
|
||||||
|
const toggleEnergyType = () => {
|
||||||
|
currentEnergyType.value =
|
||||||
|
currentEnergyType.value === "monthly" ? "daily" : "monthly";
|
||||||
|
};
|
||||||
|
|
||||||
|
// 取得當前能耗資料
|
||||||
|
const getCurrentEnergyData = () => {
|
||||||
|
return currentEnergyType.value === "monthly"
|
||||||
|
? mockEnergyData.monthly
|
||||||
|
: mockEnergyData.daily;
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="state-box-col relative ps-2">
|
||||||
|
<div class="state-box">
|
||||||
|
<!-- 標題和切換按鈕 -->
|
||||||
|
<div class="flex justify-between items-center mb-4">
|
||||||
|
<h2 class="font-light w-1/2 relative">當月能耗排行</h2>
|
||||||
|
<button @click="toggleEnergyType" class="btn btn-info btn-xs">
|
||||||
|
{{ currentEnergyType === "monthly" ? "本日能耗" : "當月能耗" }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 能耗排名列表 -->
|
||||||
|
<table class="table table-sm text-center">
|
||||||
|
<tbody>
|
||||||
|
<tr
|
||||||
|
v-for="(item, index) in getCurrentEnergyData()"
|
||||||
|
:key="item.id"
|
||||||
|
:class="[
|
||||||
|
{ 'text-red-300': index + 1 === 1 },
|
||||||
|
{ 'text-orange-300': index + 1 === 2 },
|
||||||
|
{ 'text-yellow-300': index + 1 === 3 },
|
||||||
|
{ '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.energy }} {{ item.unit }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.state-box {
|
||||||
|
@apply border-2 border-light-info rounded-sm p-2 text-white relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.state-box-col:before {
|
||||||
|
@apply absolute left-0 right-0 -top-0.5 m-auto h-2 w-36 bg-no-repeat bg-center z-10;
|
||||||
|
content: "";
|
||||||
|
background-image: url(@ASSET/img/state-box-top.png);
|
||||||
|
}
|
||||||
|
|
||||||
|
.state-box-col:after {
|
||||||
|
@apply absolute left-0 right-0 -bottom-0.5 m-auto h-2 w-36 bg-no-repeat bg-center z-10;
|
||||||
|
content: "";
|
||||||
|
background-image: url(@ASSET/img/state-box-bottom.png);
|
||||||
|
}
|
||||||
|
|
||||||
|
tr td {
|
||||||
|
@apply text-[13px] text-start;
|
||||||
|
}
|
||||||
|
</style>
|
181
src/views/dashboard/components/DashboardElecTrends.vue
Normal file
181
src/views/dashboard/components/DashboardElecTrends.vue
Normal file
@ -0,0 +1,181 @@
|
|||||||
|
<script setup>
|
||||||
|
import { ref, onMounted } from "vue";
|
||||||
|
import * as echarts from "echarts";
|
||||||
|
import BarChart from "@/components/chart/BarChart.vue";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
|
import useBuildingStore from "@/stores/useBuildingStore";
|
||||||
|
|
||||||
|
const storeBuild = useBuildingStore();
|
||||||
|
const { t } = useI18n();
|
||||||
|
const formState = ref({
|
||||||
|
floorId: 0,
|
||||||
|
deptId: 0,
|
||||||
|
});
|
||||||
|
// 模擬數據(你可以替換成從 API 或 store 獲取的真實數據)
|
||||||
|
const energyData = ref([
|
||||||
|
{ date: "11-18", energy: 500.1 },
|
||||||
|
{ date: "11-20", energy: 400 },
|
||||||
|
{ date: "11-22", energy: 450 },
|
||||||
|
{ date: "11-24", energy: 420 },
|
||||||
|
{ date: "11-26", energy: 430 },
|
||||||
|
{ date: "11-28", energy: 460 },
|
||||||
|
{ date: "11-30", energy: 480 },
|
||||||
|
{ date: "12-02", energy: 410 },
|
||||||
|
{ date: "12-04", energy: 440 },
|
||||||
|
{ date: "12-06", energy: 450 },
|
||||||
|
{ date: "12-08", energy: 470 },
|
||||||
|
{ date: "12-10", energy: 399.26 },
|
||||||
|
{ date: "12-12", energy: 380 },
|
||||||
|
{ date: "12-14", energy: 320 },
|
||||||
|
{ date: "12-16", energy: 230.68 },
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 生成柱狀圖的 option
|
||||||
|
const generateCylinderChartOption = (data) => {
|
||||||
|
const barWidth = 25;
|
||||||
|
return {
|
||||||
|
xAxis: {
|
||||||
|
type: "category",
|
||||||
|
data: data.map((item) => item.date),
|
||||||
|
axisLine: {
|
||||||
|
lineStyle: {
|
||||||
|
color: "#fff",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
type: "value",
|
||||||
|
name: "kWh",
|
||||||
|
axisLine: {
|
||||||
|
lineStyle: {
|
||||||
|
color: "#fff",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
splitLine: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
data: data.map((item) => item.energy),
|
||||||
|
type: "bar",
|
||||||
|
barWidth: barWidth,
|
||||||
|
itemStyle: {
|
||||||
|
color: new echarts.graphic.LinearGradient(0, 0, 1, 1, [
|
||||||
|
{ offset: 0, color: "#1F7B47" },
|
||||||
|
{ offset: 1, color: "#247E95" },
|
||||||
|
]),
|
||||||
|
shadowBlur: 5,
|
||||||
|
shadowColor: "rgba(0, 0, 0, 0.5)",
|
||||||
|
shadowOffsetY: 5,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
z: 12,
|
||||||
|
type: "pictorialBar",
|
||||||
|
symbolPosition: "end",
|
||||||
|
data: data.map((item) => item.energy),
|
||||||
|
symbol: "diamond",
|
||||||
|
symbolOffset: [0, -5],
|
||||||
|
symbolSize: [barWidth, barWidth * 0.5],
|
||||||
|
itemStyle: {
|
||||||
|
color: "#62E39A",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
z: 12,
|
||||||
|
type: "pictorialBar",
|
||||||
|
data: data.map((item) => item.energy),
|
||||||
|
symbol: "diamond",
|
||||||
|
symbolSize: [barWidth, barWidth * 0.5],
|
||||||
|
symbolOffset: [0, 6],
|
||||||
|
itemStyle: {
|
||||||
|
color: "#247E95",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
grid: {
|
||||||
|
left: "0%",
|
||||||
|
right: "0%",
|
||||||
|
bottom: "3%",
|
||||||
|
top: "15%",
|
||||||
|
containLabel: true,
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
trigger: "axis",
|
||||||
|
formatter: function (params) {
|
||||||
|
const item = params[0];
|
||||||
|
return `<p>${item.name}</p> <p>${item.marker}能耗 : ${item.value}</p>`;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const weekComparisonOption = generateCylinderChartOption(energyData.value);
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (
|
||||||
|
storeBuild.deptList.length > 0 &&
|
||||||
|
storeBuild.floorList.length > 0
|
||||||
|
) {
|
||||||
|
formState.value = {
|
||||||
|
...formState.value,
|
||||||
|
floorId: storeBuild.floorList[0].key,
|
||||||
|
deptId: storeBuild.deptList[0].key,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="w-full chart-data relative px-8 py-1">
|
||||||
|
<div class="flex flex-wrap items-center justify-between">
|
||||||
|
<h2 class="font-light">近30天能耗趨勢</h2>
|
||||||
|
<div class="flex items-center w-52 gap-4">
|
||||||
|
<Select
|
||||||
|
:value="formState"
|
||||||
|
class="my-2"
|
||||||
|
selectClass="border-info focus-within:border-info btn-xs text-xs"
|
||||||
|
name="floorId"
|
||||||
|
Attribute="title"
|
||||||
|
:options="storeBuild.floorList"
|
||||||
|
:isTopLabelExist="false"
|
||||||
|
:isBottomLabelExist="false"
|
||||||
|
>
|
||||||
|
</Select>
|
||||||
|
<Select
|
||||||
|
:value="formState"
|
||||||
|
class="my-2"
|
||||||
|
selectClass="border-info focus-within:border-info btn-xs text-xs"
|
||||||
|
name="deptId"
|
||||||
|
Attribute="title"
|
||||||
|
:options="storeBuild.deptList"
|
||||||
|
:isTopLabelExist="false"
|
||||||
|
:isBottomLabelExist="false"
|
||||||
|
>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="h-[300px]">
|
||||||
|
<BarChart
|
||||||
|
id="dashboard_chart_week_comparison"
|
||||||
|
class="h-full"
|
||||||
|
:option="weekComparisonOption"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.chart-data:before {
|
||||||
|
@apply absolute right-0 left-2 top-0 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-0 bottom-0 h-10 w-10 bg-no-repeat z-10;
|
||||||
|
content: "";
|
||||||
|
background: url(@ASSET/img/chart-data-background02.svg) center center;
|
||||||
|
}
|
||||||
|
</style>
|
@ -41,7 +41,7 @@ const mockData = [
|
|||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.item-data {
|
.item-data {
|
||||||
@apply relative mb-4 min-h-[135px] h-full flex flex-col justify-center;
|
@apply relative mb-4 min-h-[100px] h-full flex flex-col justify-center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.item-data-box:after {
|
.item-data-box:after {
|
||||||
|
@ -2,6 +2,8 @@
|
|||||||
import { ref } from "vue";
|
import { ref } from "vue";
|
||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
import { twMerge } from "tailwind-merge";
|
import { twMerge } from "tailwind-merge";
|
||||||
|
import useBuildingStore from "@/stores/useBuildingStore";
|
||||||
|
const store = useBuildingStore();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
// 假資料
|
// 假資料
|
||||||
const mockData = ref([
|
const mockData = ref([
|
||||||
@ -129,16 +131,18 @@ const navigateToSubSystem = (mainSystemId, subSystemId) => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
console.log("subSys",store.subSys)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="flex flex-wrap -mx-1">
|
<div class="flex flex-wrap -mx-1 h-[21rem] overflow-y-scroll">
|
||||||
<div
|
<div
|
||||||
v-for="(item, index) in mockData"
|
v-for="(item, index) in store.subSys"
|
||||||
:key="index"
|
:key="index"
|
||||||
:class="
|
:class="
|
||||||
twMerge(
|
twMerge(
|
||||||
'w-full sm:w-1/2 lg:w-1/4 relative my-2 ',
|
'w-full sm:w-1/2 relative my-2 ',
|
||||||
item.sub_system_tag
|
item.sub_system_tag
|
||||||
? 'saturate-200 cursor-pointer text-base text-info'
|
? 'saturate-200 cursor-pointer text-base text-info'
|
||||||
: 'grayscale opacity-70 cursor-not-allowed text-sm'
|
: 'grayscale opacity-70 cursor-not-allowed text-sm'
|
||||||
@ -154,14 +158,15 @@ const navigateToSubSystem = (mainSystemId, subSystemId) => {
|
|||||||
|
|
||||||
<div class="equipment-item">
|
<div class="equipment-item">
|
||||||
<div class="w-16">
|
<div class="w-16">
|
||||||
<FontAwesomeIcon
|
<!-- <FontAwesomeIcon
|
||||||
class="text-2xl mt-1 m-auto"
|
class="text-2xl mt-1 m-auto"
|
||||||
:icon="['fas', item.icon]"
|
:icon="['fas', 'tv']"
|
||||||
></FontAwesomeIcon>
|
></FontAwesomeIcon> -->
|
||||||
|
<img class="w-7 m-auto" src="https://cgems.cvilux-group.com:8088/upload/device_icon/3454f5e0-3afa-4ace-ae54-a68bf7183e7d.png">
|
||||||
</div>
|
</div>
|
||||||
<div class="icon-text">
|
<div class="icon-text">
|
||||||
<div class="">
|
<div class="">
|
||||||
{{ item.title }}
|
{{ item.full_name }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { ref, computed } from "vue";
|
import { ref, computed } from "vue";
|
||||||
|
import DashboardSysProgressModal from "./DashboardSysProgressModal.vue";
|
||||||
|
|
||||||
const equipmentData = ref({
|
const equipmentData = ref({
|
||||||
title: "System Status",
|
title: "System Status",
|
||||||
@ -20,10 +21,46 @@ const orderData = ref({
|
|||||||
{ label: "Completed", value: 1 },
|
{ label: "Completed", value: 1 },
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const openModal = () => {
|
||||||
|
system_status_modal.showModal();
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
<DashboardSysProgressModal
|
||||||
|
/>
|
||||||
<div class="flex flex-wrap">
|
<div class="flex flex-wrap">
|
||||||
|
<div class="w-full sm:w-2/5 state-box-col relative ps-2">
|
||||||
|
<div class="state-box">
|
||||||
|
<div class="title">
|
||||||
|
<img class="state-title01" src="@ASSET/img/state-title01.svg" />
|
||||||
|
<span>{{ orderData.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>value</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr
|
||||||
|
v-for="(item, index) in orderData.items"
|
||||||
|
:key="index"
|
||||||
|
class="border-cyan-400 cursor-pointer hover:text-info"
|
||||||
|
@click.stop.prevent="() => openModal()"
|
||||||
|
>
|
||||||
|
<th class="px-0 text-start">
|
||||||
|
<span>{{ item.label }}</span>
|
||||||
|
</th>
|
||||||
|
<td>{{ item.value }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="w-full sm:w-3/5 state-box-col relative ps-2">
|
<div class="w-full sm:w-3/5 state-box-col relative ps-2">
|
||||||
<div class="state-box">
|
<div class="state-box">
|
||||||
<div class="title">
|
<div class="title">
|
||||||
@ -44,7 +81,8 @@ const orderData = ref({
|
|||||||
<tr
|
<tr
|
||||||
v-for="(item, index) in equipmentData.items"
|
v-for="(item, index) in equipmentData.items"
|
||||||
:key="index"
|
:key="index"
|
||||||
class="border-cyan-400"
|
class="border-cyan-400 cursor-pointer hover:text-info"
|
||||||
|
@click.stop.prevent="() => openModal()"
|
||||||
>
|
>
|
||||||
<th class="px-0 text-start">{{ item.label }}</th>
|
<th class="px-0 text-start">{{ item.label }}</th>
|
||||||
<td>{{ item.online }}</td>
|
<td>{{ item.online }}</td>
|
||||||
@ -55,35 +93,6 @@ const orderData = ref({
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-full sm:w-2/5 state-box-col relative ps-2">
|
|
||||||
<div class="state-box">
|
|
||||||
<div class="title">
|
|
||||||
<img class="state-title01" src="@ASSET/img/state-title01.svg" />
|
|
||||||
<span>{{ orderData.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>value</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr
|
|
||||||
v-for="(item, index) in orderData.items"
|
|
||||||
:key="index"
|
|
||||||
class="border-cyan-400"
|
|
||||||
>
|
|
||||||
<th class="px-0 text-start">
|
|
||||||
<span>{{ item.label }}</span>
|
|
||||||
</th>
|
|
||||||
<td>{{ item.value }}</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -101,7 +110,7 @@ const orderData = ref({
|
|||||||
}
|
}
|
||||||
|
|
||||||
.state-box {
|
.state-box {
|
||||||
@apply h-80 border-2 border-light-info rounded-sm py-2 px-6 text-white relative;
|
@apply h-[22rem] border-2 border-light-info rounded-sm py-2 px-6 text-white relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.state-box:after {
|
.state-box:after {
|
||||||
|
83
src/views/dashboard/components/DashboardSysProgressModal.vue
Normal file
83
src/views/dashboard/components/DashboardSysProgressModal.vue
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
<script setup>
|
||||||
|
import { ref, onMounted, defineProps, inject, watch } from "vue";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
|
const { t } = useI18n();
|
||||||
|
const { openToast } = inject("app_toast");
|
||||||
|
|
||||||
|
const onCancel = () => {
|
||||||
|
system_status_modal.close();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 假資料 - 在線設備列表
|
||||||
|
const mockEquipmentData = ref([
|
||||||
|
{ name: "12030182 (放空機-30噸)", lastOnline: "2024-12-17 17:15:00" },
|
||||||
|
{ name: "12080043 (空壓機-50P 日立)", lastOnline: "2024-12-17 17:15:00" },
|
||||||
|
{ name: "12030204 (注塑機-40噸)", lastOnline: "2024-12-17 17:15:00" },
|
||||||
|
{ name: "12040084 (冷卻水)", lastOnline: "2024-12-17 17:15:00" },
|
||||||
|
{ name: "11060241 (蒸騰式冷氣機)", lastOnline: "2024-12-17 17:15:00" },
|
||||||
|
{ name: "12060010 (串壓機-啟業)", lastOnline: "2024-12-17 17:15:00" },
|
||||||
|
{ name: "12020005 (串壓機-工業30噸)", lastOnline: "2024-12-17 17:15:00" },
|
||||||
|
{ name: "12030179 (注塑機-30噸)", lastOnline: "2024-12-17 17:15:00" },
|
||||||
|
{ name: "12040145 (串壓機-三銘)", lastOnline: "2024-12-17 17:15:00" },
|
||||||
|
{
|
||||||
|
name: "12030163 (注氨全自動加氨型-00:00-01:00)",
|
||||||
|
lastOnline: "2024-12-17 17:15:00",
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Modal id="system_status_modal" :onCancel="onCancel" :width="600">
|
||||||
|
<template #modalTitle>
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<h2>在線設備</h2>
|
||||||
|
<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"
|
||||||
|
>
|
||||||
|
設備名稱
|
||||||
|
</th>
|
||||||
|
<th
|
||||||
|
class="text-base border text-white text-center bg-cyan-600 bg-opacity-30"
|
||||||
|
>
|
||||||
|
最近在線時間
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr
|
||||||
|
v-for="(equipment, index) in mockEquipmentData"
|
||||||
|
:key="index"
|
||||||
|
class="hover:bg-gray-700"
|
||||||
|
>
|
||||||
|
<td class="border text-white text-center">
|
||||||
|
{{ equipment.name }}
|
||||||
|
</td>
|
||||||
|
<td class="border text-white text-center">
|
||||||
|
{{ equipment.lastOnline }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</Modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped></style>
|
@ -1,4 +1,5 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
|
import { ref, onMounted, provide } from "vue";
|
||||||
import ImmediateDemandChart from "./ImmediateDemandChart.vue";
|
import ImmediateDemandChart from "./ImmediateDemandChart.vue";
|
||||||
import ElecConsumption from "./ElecConsumption.vue";
|
import ElecConsumption from "./ElecConsumption.vue";
|
||||||
import UsageInformation from "./UsageInformation.vue";
|
import UsageInformation from "./UsageInformation.vue";
|
||||||
@ -6,8 +7,25 @@ import MonthlyElecBillChart from "./MonthlyElecBillChart.vue";
|
|||||||
import CarbonEmissionChart from "./CarbonEmissionChart.vue";
|
import CarbonEmissionChart from "./CarbonEmissionChart.vue";
|
||||||
import BillingDegreeChart from "./BillingDegreeChart.vue";
|
import BillingDegreeChart from "./BillingDegreeChart.vue";
|
||||||
import IntervalBillChart from "./IntervalBillChart.vue";
|
import IntervalBillChart from "./IntervalBillChart.vue";
|
||||||
|
import useActiveBtn from "@/hooks/useActiveBtn";
|
||||||
import { getTaipower } from "@/apis/energy";
|
import { getTaipower } from "@/apis/energy";
|
||||||
import { ref, onMounted, provide } from "vue";
|
import useBuildingStore from "@/stores/useBuildingStore";
|
||||||
|
|
||||||
|
const storeBuild = useBuildingStore();
|
||||||
|
// 選樓層
|
||||||
|
const {
|
||||||
|
items: sysFloorItems,
|
||||||
|
changeActiveBtn: changeFloorActiveBtn,
|
||||||
|
setItems: setFloorItems,
|
||||||
|
selectedBtn: selectedFloorItems,
|
||||||
|
} = useActiveBtn("multiple");
|
||||||
|
// 選部門
|
||||||
|
const {
|
||||||
|
items: sysDeptItems,
|
||||||
|
changeActiveBtn: changeDeptActiveBtn,
|
||||||
|
setItems: setDeptItems,
|
||||||
|
selectedBtn: selectedDeptItems,
|
||||||
|
} = useActiveBtn("multiple");
|
||||||
|
|
||||||
const taipower_data = ref([]);
|
const taipower_data = ref([]);
|
||||||
const getData = async () => {
|
const getData = async () => {
|
||||||
@ -19,6 +37,8 @@ const getData = async () => {
|
|||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
getData();
|
getData();
|
||||||
|
setDeptItems(storeBuild.deptList);
|
||||||
|
setFloorItems(storeBuild.floorList);
|
||||||
});
|
});
|
||||||
|
|
||||||
provide("energy_data", { taipower_data });
|
provide("energy_data", { taipower_data });
|
||||||
@ -26,6 +46,36 @@ provide("energy_data", { taipower_data });
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="flex flex-wrap items-center mb-4">
|
<div class="flex flex-wrap items-center mb-4">
|
||||||
|
<div class="w-full border border-info px-4 py-2 rounded my-3">
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
<span class="text-md font-extrabold">{{ $t("energy.floor") }} :</span>
|
||||||
|
<ButtonGroup
|
||||||
|
:items="sysFloorItems"
|
||||||
|
:withLine="true"
|
||||||
|
className="btn-xs rounded-md"
|
||||||
|
:onclick="
|
||||||
|
(e, item) => {
|
||||||
|
changeFloorActiveBtn(item);
|
||||||
|
}
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
<span class="text-md font-extrabold"
|
||||||
|
>{{ $t("assetManagement.department") }} :</span
|
||||||
|
>
|
||||||
|
<ButtonGroup
|
||||||
|
:items="sysDeptItems"
|
||||||
|
:withLine="true"
|
||||||
|
className="btn-xs rounded-md"
|
||||||
|
:onclick="
|
||||||
|
(e, item) => {
|
||||||
|
changeDeptActiveBtn(item);
|
||||||
|
}
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="w-full xl:w-5/12 lg:w-1/2">
|
<div class="w-full xl:w-5/12 lg:w-1/2">
|
||||||
<ElecConsumption />
|
<ElecConsumption />
|
||||||
</div>
|
</div>
|
||||||
|
@ -14,9 +14,11 @@ import EnergyActionButton from "./EnergyActionButton.vue";
|
|||||||
import EnergySearchTime from "./EnergySearchTime.vue";
|
import EnergySearchTime from "./EnergySearchTime.vue";
|
||||||
import useActiveBtn from "@/hooks/useActiveBtn";
|
import useActiveBtn from "@/hooks/useActiveBtn";
|
||||||
import { getEnergySearch } from "@/apis/energy";
|
import { getEnergySearch } from "@/apis/energy";
|
||||||
import { getDepartmentList, getElecTypeList } from "@/apis/asset";
|
import { getElecTypeList } from "@/apis/asset";
|
||||||
import useSearchParam from "@/hooks/useSearchParam";
|
import useSearchParam from "@/hooks/useSearchParam";
|
||||||
import dayjs from "dayjs";
|
import useBuildingStore from "@/stores/useBuildingStore";
|
||||||
|
|
||||||
|
const storeBuild = useBuildingStore();
|
||||||
const { deptData, elecType, subSystem } = inject("energy_table_data");
|
const { deptData, elecType, subSystem } = inject("energy_table_data");
|
||||||
const { searchParams, changeParams } = useSearchParam();
|
const { searchParams, changeParams } = useSearchParam();
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
@ -57,17 +59,6 @@ const {
|
|||||||
selectedBtn: selectedPoints,
|
selectedBtn: selectedPoints,
|
||||||
} = useActiveBtn("multiple");
|
} = useActiveBtn("multiple");
|
||||||
|
|
||||||
const getDepartment = async () => {
|
|
||||||
const res = await getDepartmentList();
|
|
||||||
const dept = res.data.map((d, index) => ({
|
|
||||||
...d,
|
|
||||||
title: d.name,
|
|
||||||
key: d.id,
|
|
||||||
active: false,
|
|
||||||
}));
|
|
||||||
setDeptItems(dept);
|
|
||||||
};
|
|
||||||
|
|
||||||
const getElecType = async () => {
|
const getElecType = async () => {
|
||||||
const res = await getElecTypeList();
|
const res = await getElecTypeList();
|
||||||
const elecType = res.data.map((d, index) => ({
|
const elecType = res.data.map((d, index) => ({
|
||||||
@ -162,7 +153,7 @@ watch(
|
|||||||
);
|
);
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
getDepartment();
|
setDeptItems(storeBuild.deptList);
|
||||||
getElecType();
|
getElecType();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
@ -3,13 +3,12 @@ import { ref, onMounted, watch, inject } from "vue";
|
|||||||
import { useRoute } from "vue-router";
|
import { useRoute } from "vue-router";
|
||||||
import useActiveBtn from "@/hooks/useActiveBtn";
|
import useActiveBtn from "@/hooks/useActiveBtn";
|
||||||
import EnergyReportTimeRange from "./EnergyReportTimeRange.vue";
|
import EnergyReportTimeRange from "./EnergyReportTimeRange.vue";
|
||||||
import {
|
import { getElecTypeList } from "@/apis/asset";
|
||||||
getDepartmentList,
|
|
||||||
getAssetFloorList,
|
|
||||||
getElecTypeList,
|
|
||||||
} from "@/apis/asset";
|
|
||||||
import { getReport, getExcel } from "@/apis/energy";
|
import { getReport, getExcel } from "@/apis/energy";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
|
import useBuildingStore from "@/stores/useBuildingStore";
|
||||||
|
|
||||||
|
const storeBuild = useBuildingStore();
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const { tableData, loading } = inject("energy_table_data");
|
const { tableData, loading } = inject("energy_table_data");
|
||||||
@ -43,39 +42,16 @@ const {
|
|||||||
selectedBtn: selectedFloorItems,
|
selectedBtn: selectedFloorItems,
|
||||||
} = useActiveBtn("multiple");
|
} = useActiveBtn("multiple");
|
||||||
|
|
||||||
const getDepartment = async () => {
|
|
||||||
const res = await getDepartmentList();
|
|
||||||
const dept = res.data.map((d, index) => ({
|
|
||||||
...d,
|
|
||||||
title: d.name,
|
|
||||||
key: d.id,
|
|
||||||
active: index === 0,
|
|
||||||
}));
|
|
||||||
setDeptItems(dept);
|
|
||||||
};
|
|
||||||
|
|
||||||
const getElecType = async () => {
|
const getElecType = async () => {
|
||||||
const res = await getElecTypeList();
|
const res = await getElecTypeList();
|
||||||
const elecType = res.data.map((d, index) => ({
|
const elecType = res.data.map((d, index) => ({
|
||||||
...d,
|
...d,
|
||||||
title: d.name,
|
title: d.name,
|
||||||
key: d.id,
|
key: d.id
|
||||||
active: index === 0,
|
|
||||||
}));
|
}));
|
||||||
setElecItems(elecType);
|
setElecItems(elecType);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getFloor = async () => {
|
|
||||||
const res = await getAssetFloorList();
|
|
||||||
const Floor = res.data[0]?.floors.map((d, index) => ({
|
|
||||||
...d,
|
|
||||||
title: d.full_name,
|
|
||||||
key: d.floor_guid,
|
|
||||||
active: index === 0,
|
|
||||||
}));
|
|
||||||
setFloorItems(Floor);
|
|
||||||
};
|
|
||||||
|
|
||||||
const onSearch = async () => {
|
const onSearch = async () => {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
const res = await getReport(formState.value);
|
const res = await getReport(formState.value);
|
||||||
@ -124,9 +100,9 @@ watch(
|
|||||||
);
|
);
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
getDepartment();
|
setDeptItems(storeBuild.deptList);
|
||||||
|
setFloorItems(storeBuild.floorList);
|
||||||
getElecType();
|
getElecType();
|
||||||
getFloor();
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -26,6 +26,13 @@ import dayjs from "dayjs";
|
|||||||
const { searchParams, changeParams } = useSearchParam();
|
const { searchParams, changeParams } = useSearchParam();
|
||||||
|
|
||||||
const store = useBuildingStore();
|
const store = useBuildingStore();
|
||||||
|
// 選部門
|
||||||
|
const {
|
||||||
|
items: sysDeptItems,
|
||||||
|
changeActiveBtn: changeDeptActiveBtn,
|
||||||
|
setItems: setDeptItems,
|
||||||
|
selectedBtn: selectedDeptItems,
|
||||||
|
} = useActiveBtn("multiple");
|
||||||
// 選大類
|
// 選大類
|
||||||
const {
|
const {
|
||||||
items: sysMainTagItems,
|
items: sysMainTagItems,
|
||||||
@ -40,6 +47,13 @@ const {
|
|||||||
setItems: setSysItems,
|
setItems: setSysItems,
|
||||||
selectedBtn: selectedSysItems,
|
selectedBtn: selectedSysItems,
|
||||||
} = useActiveBtn();
|
} = useActiveBtn();
|
||||||
|
// 設定點位
|
||||||
|
const {
|
||||||
|
items: points,
|
||||||
|
changeActiveBtn: changeActivePoint,
|
||||||
|
setItems: setPoints,
|
||||||
|
selectedBtn: selectedPoints,
|
||||||
|
} = useActiveBtn("multiple");
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => store.mainSys,
|
() => store.mainSys,
|
||||||
@ -60,11 +74,13 @@ watch(
|
|||||||
() => selectedMainSysItems,
|
() => selectedMainSysItems,
|
||||||
(newVal, oldVal) => {
|
(newVal, oldVal) => {
|
||||||
setSysItems(
|
setSysItems(
|
||||||
store.subSys.filter((s) => s.main_system_tag === newVal.value?.key).map(({ full_name, sub_system_tag }, index) => ({
|
store.subSys
|
||||||
title: full_name,
|
.filter((s) => s.main_system_tag === newVal.value?.key)
|
||||||
key: sub_system_tag,
|
.map(({ full_name, sub_system_tag }, index) => ({
|
||||||
active: index === 0,
|
title: full_name,
|
||||||
}))
|
key: sub_system_tag,
|
||||||
|
active: index === 0,
|
||||||
|
}))
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -93,14 +109,6 @@ watch(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
// 設定點位
|
|
||||||
const {
|
|
||||||
items: points,
|
|
||||||
changeActiveBtn: changeActivePoint,
|
|
||||||
setItems: setPoints,
|
|
||||||
selectedBtn: selectedPoints,
|
|
||||||
} = useActiveBtn("multiple");
|
|
||||||
|
|
||||||
const getPoint = async (deviceList) => {
|
const getPoint = async (deviceList) => {
|
||||||
const res = await getHistoryPoints(deviceList);
|
const res = await getHistoryPoints(deviceList);
|
||||||
setPoints(
|
setPoints(
|
||||||
@ -126,6 +134,19 @@ watch(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
selectedDeptItems,
|
||||||
|
(newVal, oldVal) => {
|
||||||
|
changeParams({
|
||||||
|
...searchParams.value,
|
||||||
|
Dept: newVal.map((d) => d.id),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
{
|
||||||
|
immediate: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
const form = ref(null);
|
const form = ref(null);
|
||||||
|
|
||||||
watch(searchParams, (newVal, oldValue) => {
|
watch(searchParams, (newVal, oldValue) => {
|
||||||
@ -149,6 +170,13 @@ onMounted(() => {
|
|||||||
active: index === 0,
|
active: index === 0,
|
||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
|
setDeptItems(
|
||||||
|
store.deptList.map((d, index) => ({
|
||||||
|
...d,
|
||||||
|
title: d.name,
|
||||||
|
key: d.id,
|
||||||
|
}))
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
onBeforeMount(() => {
|
onBeforeMount(() => {
|
||||||
@ -159,6 +187,20 @@ onBeforeMount(() => {
|
|||||||
<template>
|
<template>
|
||||||
<div class="flex flex-col custom-border p-4 mb-4">
|
<div class="flex flex-col custom-border p-4 mb-4">
|
||||||
<!-- <HistoryFavoriteOption class="mb-4" />-->
|
<!-- <HistoryFavoriteOption class="mb-4" />-->
|
||||||
|
<div class="flex items-center gap-4 mb-4">
|
||||||
|
<h2 class="text-lg font-bold ps-2 whitespace-nowrap">
|
||||||
|
{{ $t("assetManagement.department") }} :
|
||||||
|
</h2>
|
||||||
|
<ButtonGroup
|
||||||
|
:items="sysDeptItems"
|
||||||
|
:withLine="true"
|
||||||
|
:onclick="
|
||||||
|
(e, item) => {
|
||||||
|
changeDeptActiveBtn(item);
|
||||||
|
}
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<div class="flex items-center gap-4 mb-4">
|
<div class="flex items-center gap-4 mb-4">
|
||||||
<h2 class="text-lg font-bold ps-2 whitespace-nowrap">
|
<h2 class="text-lg font-bold ps-2 whitespace-nowrap">
|
||||||
{{ $t("history.system_category") }} :
|
{{ $t("history.system_category") }} :
|
||||||
|
@ -13,8 +13,9 @@ const deviceData = ref([]);
|
|||||||
const searchTerm = ref(""); //搜尋文字
|
const searchTerm = ref(""); //搜尋文字
|
||||||
const activeSearchTerm = ref("");
|
const activeSearchTerm = ref("");
|
||||||
|
|
||||||
const getDeviceData = async (sub_system_tag, renew) => {
|
const getDeviceData = async (sub_system_tag,department_id) => {
|
||||||
const res = await getHistorySideBar({sub_system_tag: sub_system_tag});
|
const deptArray = department_id? department_id.map(Number):null;
|
||||||
|
const res = await getHistorySideBar({sub_system_tag: sub_system_tag,department_id:deptArray});
|
||||||
deviceData.value = res.data.map((building) => ({
|
deviceData.value = res.data.map((building) => ({
|
||||||
building_tag: building.building_tag,
|
building_tag: building.building_tag,
|
||||||
building_name: building.building_name,
|
building_name: building.building_name,
|
||||||
@ -30,8 +31,7 @@ const getDeviceData = async (sub_system_tag, renew) => {
|
|||||||
|
|
||||||
selectedBuilding.value = res.data.map((d) => d.building_tag);
|
selectedBuilding.value = res.data.map((d) => d.building_tag);
|
||||||
changeSelected(
|
changeSelected(
|
||||||
[res.data[0]?.floor_list[0]?.device_list[0]?.device_number],
|
[res.data[0]?.floor_list[0]?.device_list[0]?.device_number]
|
||||||
renew
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -57,7 +57,7 @@ watch(
|
|||||||
newVal.sub_system_tag &&
|
newVal.sub_system_tag &&
|
||||||
newVal.sub_system_tag != oldVal?.sub_system_tag
|
newVal.sub_system_tag != oldVal?.sub_system_tag
|
||||||
) {
|
) {
|
||||||
getDeviceData(newVal.sub_system_tag, true);
|
getDeviceData(newVal.sub_system_tag,searchParams.value.Dept);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -66,6 +66,21 @@ watch(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
searchParams,
|
||||||
|
(newVal, oldVal) => {
|
||||||
|
if (
|
||||||
|
newVal.Dept &&
|
||||||
|
newVal.Dept?.length != oldVal?.Dept?.length
|
||||||
|
) {
|
||||||
|
getDeviceData(searchParams.value.sub_system_tag,newVal.Dept);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
immediate: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
const selectedDeviceNumber = computed(() => {
|
const selectedDeviceNumber = computed(() => {
|
||||||
return typeof searchParams.value.Device_list === "string"
|
return typeof searchParams.value.Device_list === "string"
|
||||||
? [searchParams.value.Device_list]
|
? [searchParams.value.Device_list]
|
||||||
|
@ -8,7 +8,8 @@ import Vendor from "./components/Vendor.vue";
|
|||||||
import Floors from "./components/Floors.vue";
|
import Floors from "./components/Floors.vue";
|
||||||
import Building from "./components/Building.vue";
|
import Building from "./components/Building.vue";
|
||||||
import ElecPriceManagement from "./components/ElecPriceManagement.vue";
|
import ElecPriceManagement from "./components/ElecPriceManagement.vue";
|
||||||
import MITTList from "./components/MITTList.vue";
|
import MQTTList from "./components/MQTTList.vue";
|
||||||
|
// import PointList from "./components/PointList.vue";
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const currentComponent = ref(null);
|
const currentComponent = ref(null);
|
||||||
@ -29,7 +30,7 @@ const updateComponent = () => {
|
|||||||
} else if (sub_system_id === "ElecPricing") {
|
} else if (sub_system_id === "ElecPricing") {
|
||||||
currentComponent.value = ElecPriceManagement;
|
currentComponent.value = ElecPriceManagement;
|
||||||
} else if (sub_system_id === "MQTT_Result") {
|
} else if (sub_system_id === "MQTT_Result") {
|
||||||
currentComponent.value = MITTList;
|
currentComponent.value = MQTTList;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,9 +1,12 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import Table from "@/components/customUI/Table.vue";
|
import Table from "@/components/customUI/Table.vue";
|
||||||
import DeptModal from "./DeptModal.vue";
|
import DeptModal from "./DeptModal.vue";
|
||||||
import { getDepartmentList, deleteDepartmentItem } from "@/apis/asset";
|
import { deleteDepartmentItem } from "@/apis/asset";
|
||||||
import { onMounted, ref, inject, computed } from "vue";
|
import { onMounted, ref, inject, computed } from "vue";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
|
import useBuildingStore from "@/stores/useBuildingStore";
|
||||||
|
|
||||||
|
const storeBuild = useBuildingStore();
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const { openToast, cancelToastOpen } = inject("app_toast");
|
const { openToast, cancelToastOpen } = inject("app_toast");
|
||||||
|
|
||||||
@ -23,20 +26,8 @@ const columns = computed(() => [
|
|||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const dataSource = ref([]);
|
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
|
|
||||||
const getDataSource = async () => {
|
|
||||||
loading.value = true;
|
|
||||||
const res = await getDepartmentList();
|
|
||||||
dataSource.value = res.data.map((d) => ({ ...d, key: d.id }));
|
|
||||||
loading.value = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
getDataSource();
|
|
||||||
});
|
|
||||||
|
|
||||||
const formState = ref({
|
const formState = ref({
|
||||||
id: 0,
|
id: 0,
|
||||||
name: "",
|
name: "",
|
||||||
@ -59,13 +50,16 @@ const remove = async (id) => {
|
|||||||
await cancelToastOpen();
|
await cancelToastOpen();
|
||||||
const res = await deleteDepartmentItem(id);
|
const res = await deleteDepartmentItem(id);
|
||||||
if (res.isSuccess) {
|
if (res.isSuccess) {
|
||||||
getDataSource();
|
storeBuild.fetchDepartmentList();
|
||||||
openToast("success", t("msg.delete_success"));
|
openToast("success", t("msg.delete_success"));
|
||||||
} else {
|
} else {
|
||||||
openToast("error", res.msg);
|
openToast("error", res.msg);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
onMounted(() => {
|
||||||
|
storeBuild.fetchDepartmentList();
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@ -73,11 +67,10 @@ const remove = async (id) => {
|
|||||||
<h3 class="text-xl mr-5">{{ $t("assetManagement.department") }}</h3>
|
<h3 class="text-xl mr-5">{{ $t("assetManagement.department") }}</h3>
|
||||||
<DeptModal
|
<DeptModal
|
||||||
:formState="formState"
|
:formState="formState"
|
||||||
:getData="getDataSource"
|
|
||||||
:openModal="openModal"
|
:openModal="openModal"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Table :columns="columns" :dataSource="dataSource" :loading="loading">
|
<Table :columns="columns" :dataSource="storeBuild.deptList" :loading="loading">
|
||||||
<template #bodyCell="{ record, column, index }">
|
<template #bodyCell="{ record, column, index }">
|
||||||
<template v-if="column.key === 'index'">{{ index + 1 }}</template>
|
<template v-if="column.key === 'index'">{{ index + 1 }}</template>
|
||||||
<template v-else-if="column.key === 'operation'">
|
<template v-else-if="column.key === 'operation'">
|
||||||
|
@ -5,12 +5,14 @@ import "yup-phone-lite";
|
|||||||
import useFormErrorMessage from "@/hooks/useFormErrorMessage";
|
import useFormErrorMessage from "@/hooks/useFormErrorMessage";
|
||||||
import { postDepartmentList } from "@/apis/asset";
|
import { postDepartmentList } from "@/apis/asset";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
|
import useBuildingStore from "@/stores/useBuildingStore";
|
||||||
|
|
||||||
|
const storeBuild = useBuildingStore();
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const { openToast } = inject("app_toast");
|
const { openToast } = inject("app_toast");
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
formState: Object,
|
formState: Object,
|
||||||
getData: Function,
|
|
||||||
openModal: Function
|
openModal: Function
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -30,7 +32,7 @@ const onOk = async () => {
|
|||||||
const value = await handleSubmit(deptScheme, props.formState);
|
const value = await handleSubmit(deptScheme, props.formState);
|
||||||
const res = await postDepartmentList(value);
|
const res = await postDepartmentList(value);
|
||||||
if (res.isSuccess) {
|
if (res.isSuccess) {
|
||||||
props.getData();
|
storeBuild.fetchDepartmentList();
|
||||||
onCancel();
|
onCancel();
|
||||||
} else {
|
} else {
|
||||||
openToast("error", res.msg, "#dept_modal");
|
openToast("error", res.msg, "#dept_modal");
|
||||||
|
180
src/views/setting/components/MQTTList.vue
Normal file
180
src/views/setting/components/MQTTList.vue
Normal file
@ -0,0 +1,180 @@
|
|||||||
|
<script setup>
|
||||||
|
import { ref, onMounted, watch, computed } from "vue";
|
||||||
|
import { getAssetMainList, getAssetSubList, getIOTSchema } from "@/apis/asset";
|
||||||
|
import useActiveBtn from "@/hooks/useActiveBtn";
|
||||||
|
import MQTTListAddModal from "./MQTTListAddModal.vue";
|
||||||
|
import PointListAddModal from "./PointListAddModal.vue";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
|
const { t } = useI18n();
|
||||||
|
const formState = ref({});
|
||||||
|
const MQTTDataSource = ref([]);
|
||||||
|
const MQTTloading = ref(false);
|
||||||
|
const PointsDataSource = ref([]);
|
||||||
|
const Pointsloading = ref(false);
|
||||||
|
// 系統類別
|
||||||
|
const {
|
||||||
|
items: mainSysItems,
|
||||||
|
changeActiveBtn: changeMainSysActiveBtn,
|
||||||
|
setItems: setMainSysItems,
|
||||||
|
selectedBtn: selectedMainSysItems,
|
||||||
|
} = useActiveBtn();
|
||||||
|
// 設備類別
|
||||||
|
const {
|
||||||
|
items: subSysItems,
|
||||||
|
changeActiveBtn: changeSubSysActiveBtn,
|
||||||
|
setItems: setSubSysItems,
|
||||||
|
selectedBtn: selectedSubSysItems,
|
||||||
|
} = useActiveBtn();
|
||||||
|
|
||||||
|
const getMainSystems = async () => {
|
||||||
|
const res = await getAssetMainList();
|
||||||
|
const cate = res.data.map((d, index) => ({
|
||||||
|
...d,
|
||||||
|
title: d.system_key,
|
||||||
|
key: d.id,
|
||||||
|
active: index === 0,
|
||||||
|
}));
|
||||||
|
setMainSysItems(cate);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getSubSystems = async (id) => {
|
||||||
|
const res = await getAssetSubList(id);
|
||||||
|
const sub = res.data.map((d, index) => ({
|
||||||
|
...d,
|
||||||
|
title: d.system_key,
|
||||||
|
key: d.id,
|
||||||
|
active: index === 0,
|
||||||
|
}));
|
||||||
|
setSubSysItems(sub);
|
||||||
|
};
|
||||||
|
|
||||||
|
const MQTTColumns = computed(() => [
|
||||||
|
{
|
||||||
|
title: "schema",
|
||||||
|
key: "schema_name",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "point",
|
||||||
|
key: "pointOrg",
|
||||||
|
filter: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Description",
|
||||||
|
key: "description",
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
const PointsColumns = computed(() => [
|
||||||
|
{
|
||||||
|
title: "IoT 點位名稱",
|
||||||
|
key: "IoT_point_name",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "小數點個數",
|
||||||
|
key: "decimal",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "bool 值",
|
||||||
|
key: "is_bool",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "點位隱藏",
|
||||||
|
key: "is_open",
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
const getDataSource = async (id) => {
|
||||||
|
MQTTloading.value = true;
|
||||||
|
const res = await getIOTSchema(id);
|
||||||
|
MQTTDataSource.value = res.data.flatMap((d) =>
|
||||||
|
d.details.map((detail) => ({
|
||||||
|
key: `${d.id}-${detail.pointOrg}`,
|
||||||
|
schema_name: d.name,
|
||||||
|
pointOrg: detail.pointOrg,
|
||||||
|
description: detail.describe,
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
MQTTloading.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
watch(
|
||||||
|
selectedMainSysItems,
|
||||||
|
(newValue) => {
|
||||||
|
if (newValue && newValue.key) {
|
||||||
|
getSubSystems(parseInt(newValue.key));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
deep: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
selectedSubSysItems,
|
||||||
|
(newValue) => {
|
||||||
|
if (newValue && newValue.key) {
|
||||||
|
getDataSource(parseInt(newValue.key));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
deep: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
getMainSystems();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="">
|
||||||
|
<div class="flex items-center gap-4 mb-4">
|
||||||
|
<h2 class="text-lg font-bold whitespace-nowrap">
|
||||||
|
{{ $t("history.system_category") }} :
|
||||||
|
</h2>
|
||||||
|
<ButtonGroup
|
||||||
|
:items="mainSysItems"
|
||||||
|
:withLine="true"
|
||||||
|
:onclick="
|
||||||
|
(e, item) => {
|
||||||
|
changeMainSysActiveBtn(item);
|
||||||
|
}
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-4 mb-4">
|
||||||
|
<h2 class="text-lg font-bold whitespace-nowrap">
|
||||||
|
{{ $t("history.device_category") }} :
|
||||||
|
</h2>
|
||||||
|
<ButtonGroup
|
||||||
|
:items="subSysItems"
|
||||||
|
:withLine="true"
|
||||||
|
:onclick="
|
||||||
|
(e, item) => {
|
||||||
|
changeSubSysActiveBtn(item);
|
||||||
|
}
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-start items-center mt-5 mb-2">
|
||||||
|
<h3 class="text-xl mr-5">MQTT_Parse : </h3>
|
||||||
|
<MQTTListAddModal />
|
||||||
|
</div>
|
||||||
|
<Table
|
||||||
|
:columns="MQTTColumns"
|
||||||
|
:dataSource="MQTTDataSource"
|
||||||
|
:loading="MQTTloading"
|
||||||
|
></Table>
|
||||||
|
<div class="flex justify-start items-center mt-5 mb-2">
|
||||||
|
<h3 class="text-xl mr-5">Points : </h3>
|
||||||
|
<PointListAddModal />
|
||||||
|
</div>
|
||||||
|
<Table
|
||||||
|
:columns="PointsColumns"
|
||||||
|
:dataSource="PointsDataSource"
|
||||||
|
:loading="Pointsloading"
|
||||||
|
></Table>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped></style>
|
@ -2,14 +2,10 @@
|
|||||||
import { ref, onMounted, watch, defineProps } from "vue";
|
import { ref, onMounted, watch, defineProps } from "vue";
|
||||||
import { getAssetMainList, getAssetSubList, getIOTSchema } from "@/apis/asset";
|
import { getAssetMainList, getAssetSubList, getIOTSchema } from "@/apis/asset";
|
||||||
import useActiveBtn from "@/hooks/useActiveBtn";
|
import useActiveBtn from "@/hooks/useActiveBtn";
|
||||||
import MITTCheckboxTree from "./MITTCheckboxTree.vue";
|
import MQTTCheckboxTree from "./MQTTCheckboxTree.vue";
|
||||||
import generateSchema from "json-schema-generator";
|
import generateSchema from "json-schema-generator";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const props = defineProps({
|
|
||||||
openModal: Function,
|
|
||||||
onCancel: Function,
|
|
||||||
});
|
|
||||||
// 新增:將 jsonInput 與結構資料初始化
|
// 新增:將 jsonInput 與結構資料初始化
|
||||||
const jsonInput = ref("");
|
const jsonInput = ref("");
|
||||||
const treeData = ref(null);
|
const treeData = ref(null);
|
||||||
@ -26,7 +22,7 @@ const optionData = ref([
|
|||||||
// JSON Schema 轉換
|
// JSON Schema 轉換
|
||||||
const customGenerateSchema = (json) => {
|
const customGenerateSchema = (json) => {
|
||||||
if (typeof json !== "object" || json === null) return {};
|
if (typeof json !== "object" || json === null) return {};
|
||||||
|
|
||||||
const schema = { type: "object", properties: {} };
|
const schema = { type: "object", properties: {} };
|
||||||
Object.entries(json).forEach(([key, value]) => {
|
Object.entries(json).forEach(([key, value]) => {
|
||||||
if (typeof value === "object" && value !== null) {
|
if (typeof value === "object" && value !== null) {
|
||||||
@ -105,6 +101,14 @@ watch(
|
|||||||
},
|
},
|
||||||
{ immediate: true }
|
{ immediate: true }
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const openModal = () => {
|
||||||
|
MQTT_Parse_item.showModal();
|
||||||
|
};
|
||||||
|
|
||||||
|
const onCancel = () => {
|
||||||
|
MQTT_Parse_item.close();
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@ -114,32 +118,33 @@ watch(
|
|||||||
<Modal
|
<Modal
|
||||||
id="MQTT_Parse_item"
|
id="MQTT_Parse_item"
|
||||||
title="MQTT_Parse"
|
title="MQTT_Parse"
|
||||||
:open="open"
|
|
||||||
:onCancel="onCancel"
|
:onCancel="onCancel"
|
||||||
width="1600"
|
:width="1600"
|
||||||
>
|
>
|
||||||
<template #modalContent>
|
<template #modalContent>
|
||||||
<div class="flex w-full gap-4 mt-5">
|
<div class="flex w-full gap-4 mt-5">
|
||||||
<div class="w-1/2 relative">
|
<div class="w-full">
|
||||||
<textarea
|
<textarea
|
||||||
v-model="jsonInput"
|
v-model="jsonInput"
|
||||||
placeholder="請貼上 JSON 格式數據"
|
placeholder="請貼上 JSON 格式數據"
|
||||||
class="w-full h-[500px] p-4 border rounded-lg font-mono text-white"
|
class="w-full h-[500px] p-4 border rounded-lg font-mono text-white"
|
||||||
></textarea>
|
></textarea>
|
||||||
<button @click="clearAll" class="bg-error btn absolute top-5 right-5">
|
</div>
|
||||||
<font-awesome-icon :icon="['fas', 'trash-alt']" />
|
<div class="my-auto">
|
||||||
|
<button @click="parseJson" class="btn-success btn w-24 mb-4">
|
||||||
|
轉換
|
||||||
|
<font-awesome-icon
|
||||||
|
:icon="['fas', 'chevron-right']"
|
||||||
|
size="lg"
|
||||||
|
class="block"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
<button @click="clearAll" class="bg-error btn w-24">
|
||||||
|
刪除<font-awesome-icon :icon="['fas', 'trash-alt']" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<button @click="parseJson" class="btn-success btn my-auto">
|
<div class="w-full p-4 rounded-lg border overflow-y-scroll h-[500px]">
|
||||||
轉換
|
<MQTTCheckboxTree
|
||||||
<font-awesome-icon
|
|
||||||
:icon="['fas', 'chevron-right']"
|
|
||||||
size="lg"
|
|
||||||
class="block"
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
<div class="w-1/2 p-4 rounded-lg border overflow-y-scroll h-[500px]">
|
|
||||||
<MITTCheckboxTree
|
|
||||||
v-if="treeData"
|
v-if="treeData"
|
||||||
:data="treeData"
|
:data="treeData"
|
||||||
v-model:checked-nodes="checkedNodes"
|
v-model:checked-nodes="checkedNodes"
|
||||||
@ -147,30 +152,28 @@ watch(
|
|||||||
<div v-else class="text-white">請在左側輸入 JSON 並點擊轉換按鈕</div>
|
<div v-else class="text-white">請在左側輸入 JSON 並點擊轉換按鈕</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<form ref="form" class="">
|
<form ref="form" class="" v-if="checkedNodes.length">
|
||||||
<div class="flex items-center mt-5">
|
<div class="flex items-center mt-5">
|
||||||
<h2 class="text-lg font-bold whitespace-nowrap me-2">
|
<h2 class="text-lg font-bold whitespace-nowrap me-2">結構名稱 :</h2>
|
||||||
結構名稱 :
|
|
||||||
</h2>
|
|
||||||
<Input v-model="schemaName" />
|
<Input v-model="schemaName" />
|
||||||
</div>
|
</div>
|
||||||
<table class="table" v-if="checkedNodes.length">
|
<table class="table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>IoT 點位名稱</th>
|
<!-- <th>IoT 點位名稱</th> -->
|
||||||
<th>IoT 點位結構</th>
|
<th>IoT 點位結構</th>
|
||||||
<th>系統點位名稱</th>
|
<th>系統點位名稱</th>
|
||||||
<th>bool 值</th>
|
<!-- <th>bool 值</th>
|
||||||
<th>小數點個數</th>
|
<th>小數點個數</th>
|
||||||
<th>點位開關</th>
|
<th>點位隱藏</th> -->
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr v-for="(node, index) in checkedNodes" :key="node">
|
<tr v-for="(node, index) in checkedNodes" :key="node">
|
||||||
<td>
|
<!-- <td>
|
||||||
<Input :value="formStates[index]" name="IoT_point_name" />
|
<Input :value="formStates[index]" name="IoT_point_name" />
|
||||||
</td>
|
</td> -->
|
||||||
<td>
|
<td class="w-1/2">
|
||||||
<Input
|
<Input
|
||||||
:value="formStates[index]"
|
:value="formStates[index]"
|
||||||
name="IoT_point_schema"
|
name="IoT_point_schema"
|
||||||
@ -184,7 +187,7 @@ watch(
|
|||||||
<option value="option3">選項 3</option>
|
<option value="option3">選項 3</option>
|
||||||
</select>
|
</select>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<!-- <td>
|
||||||
<div class="flex w-36">
|
<div class="flex w-36">
|
||||||
<Select
|
<Select
|
||||||
:value="formStates[index]"
|
:value="formStates[index]"
|
||||||
@ -215,7 +218,7 @@ watch(
|
|||||||
>
|
>
|
||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td> -->
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
@ -2,7 +2,7 @@
|
|||||||
import { ref, onMounted, watch, computed } from "vue";
|
import { ref, onMounted, watch, computed } from "vue";
|
||||||
import { getAssetMainList, getAssetSubList, getIOTSchema } from "@/apis/asset";
|
import { getAssetMainList, getAssetSubList, getIOTSchema } from "@/apis/asset";
|
||||||
import useActiveBtn from "@/hooks/useActiveBtn";
|
import useActiveBtn from "@/hooks/useActiveBtn";
|
||||||
import MITTlISTAddModal from "./MITTlISTAddModal.vue";
|
import PointListAddModal from "./PointListAddModal.vue";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const formState = ref({});
|
const formState = ref({});
|
||||||
@ -46,14 +46,9 @@ const getSubSystems = async (id) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const columns = computed(() => [
|
const columns = computed(() => [
|
||||||
{
|
|
||||||
title: "schema",
|
|
||||||
key: "schema_name",
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
title: "point",
|
title: "point",
|
||||||
key: "pointOrg",
|
key: "pointOrg",
|
||||||
filter: true,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Description",
|
title: "Description",
|
||||||
@ -76,12 +71,11 @@ const getDataSource = async (id) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const openModal = () => {
|
const openModal = () => {
|
||||||
MQTT_Parse_item.showModal();
|
point_list_item.showModal();
|
||||||
};
|
};
|
||||||
|
|
||||||
const onCancel = () => {
|
const onCancel = () => {
|
||||||
MQTT_Parse_item.close();
|
point_list_item.close();
|
||||||
getDataSource(parseInt(selectedSubSysItems.key))
|
|
||||||
};
|
};
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
@ -144,8 +138,8 @@ onMounted(() => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-start items-center mt-5 mb-2">
|
<div class="flex justify-start items-center mt-5 mb-2">
|
||||||
<h3 class="text-xl mr-5">MQTT_Parse : </h3>
|
<h3 class="text-xl mr-5">Points : </h3>
|
||||||
<MITTlISTAddModal
|
<PointListAddModal
|
||||||
:openModal="openModal"
|
:openModal="openModal"
|
||||||
:onCancel="onCancel"
|
:onCancel="onCancel"
|
||||||
/>
|
/>
|
101
src/views/setting/components/PointListAddModal.vue
Normal file
101
src/views/setting/components/PointListAddModal.vue
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
<script setup>
|
||||||
|
import { ref, onMounted, watch, defineProps } from "vue";
|
||||||
|
import useFormErrorMessage from "@/hooks/useFormErrorMessage";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
|
import * as yup from "yup";
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const form = ref(null);
|
||||||
|
const formState = ref({
|
||||||
|
id: 0,
|
||||||
|
IoT_point_name: "",
|
||||||
|
is_bool: 0,
|
||||||
|
decimal: 2,
|
||||||
|
is_open: 0,
|
||||||
|
});
|
||||||
|
let schema = ref(
|
||||||
|
yup.object({
|
||||||
|
IoT_point_name: yup.string().required(t("button.required")),
|
||||||
|
is_bool: yup.string().required(t("button.required")),
|
||||||
|
decimal: yup.string().required(t("button.required")),
|
||||||
|
is_open: yup.string().required(t("button.required")),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const { formErrorMsg, handleSubmit, handleErrorReset, updateScheme } =
|
||||||
|
useFormErrorMessage(schema.value);
|
||||||
|
|
||||||
|
const openModal = () => {
|
||||||
|
point_list_item.showModal();
|
||||||
|
};
|
||||||
|
|
||||||
|
const onCancel = () => {
|
||||||
|
point_list_item.close();
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<button class="btn btn-sm btn-add mr-3" @click.stop.prevent="openModal">
|
||||||
|
<font-awesome-icon :icon="['fas', 'plus']" />{{ $t("button.add") }}
|
||||||
|
</button>
|
||||||
|
<Modal id="point_list_item" title="Points" :onCancel="onCancel" :width="710">
|
||||||
|
<template #modalContent>
|
||||||
|
<form ref="form" class="mt-5 w-full flex flex-wrap justify-between">
|
||||||
|
<Input :value="formState" class="my-2" name="IoT_point_name">
|
||||||
|
<template #topLeft>IoT 點位名稱</template>
|
||||||
|
<template #bottomLeft
|
||||||
|
><span class="text-error text-base">
|
||||||
|
{{ formErrorMsg.IoT_point_name }}
|
||||||
|
</span></template
|
||||||
|
></Input
|
||||||
|
>
|
||||||
|
<InputNumber :value="formState" class="my-2" name="decimal">
|
||||||
|
<template #topLeft>小數點個數</template>
|
||||||
|
</InputNumber>
|
||||||
|
<RadioGroup
|
||||||
|
class="my-2"
|
||||||
|
name="is_bool"
|
||||||
|
:value="formState"
|
||||||
|
:items="[
|
||||||
|
{
|
||||||
|
key: 1,
|
||||||
|
value: 1,
|
||||||
|
title: $t('alert.yes'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 0,
|
||||||
|
value: 0,
|
||||||
|
title: $t('alert.no'),
|
||||||
|
},
|
||||||
|
]"
|
||||||
|
:required="true"
|
||||||
|
>
|
||||||
|
<template #topLeft>bool 值</template>
|
||||||
|
</RadioGroup>
|
||||||
|
|
||||||
|
<RadioGroup
|
||||||
|
class="my-2"
|
||||||
|
name="is_open"
|
||||||
|
:value="formState"
|
||||||
|
:items="[
|
||||||
|
{
|
||||||
|
key: 1,
|
||||||
|
value: 1,
|
||||||
|
title: $t('alert.yes'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 0,
|
||||||
|
value: 0,
|
||||||
|
title: $t('alert.no'),
|
||||||
|
},
|
||||||
|
]"
|
||||||
|
:required="true"
|
||||||
|
>
|
||||||
|
<template #topLeft>點位隱藏</template>
|
||||||
|
</RadioGroup>
|
||||||
|
</form>
|
||||||
|
</template>
|
||||||
|
</Modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped></style>
|
@ -2,6 +2,7 @@
|
|||||||
import { RouterView, useRoute } from 'vue-router';
|
import { RouterView, useRoute } from 'vue-router';
|
||||||
import { computed, watch, provide, ref, onMounted, onBeforeUnmount } from "vue";
|
import { computed, watch, provide, ref, onMounted, onBeforeUnmount } from "vue";
|
||||||
import SystemFloorBar from './components/SystemFloorBar.vue';
|
import SystemFloorBar from './components/SystemFloorBar.vue';
|
||||||
|
import SystemDeptBar from './components/SystemDeptBar.vue';
|
||||||
import useBuildingStore from "@/stores/useBuildingStore";
|
import useBuildingStore from "@/stores/useBuildingStore";
|
||||||
import ForgeForSystem from "@/components/forge/ForgeForSystem.vue";
|
import ForgeForSystem from "@/components/forge/ForgeForSystem.vue";
|
||||||
import { getSystemDevices, getSystemRealTime } from "@/apis/system";
|
import { getSystemDevices, getSystemRealTime } from "@/apis/system";
|
||||||
@ -238,7 +239,11 @@ onBeforeUnmount(() => {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<SystemInfoModal :data="selectedDevice" />
|
<SystemInfoModal :data="selectedDevice" />
|
||||||
<SystemFloorBar />
|
<div class="border border-info px-4 py-2">
|
||||||
|
<h5 class="text-md font-extrabold me-4">{{ buildingStore.selectedSystem?.full_name }}</h5>
|
||||||
|
<SystemFloorBar />
|
||||||
|
<SystemDeptBar />
|
||||||
|
</div>
|
||||||
<div class="grid grid-cols-2 gap-5 mt-8 mb-4">
|
<div class="grid grid-cols-2 gap-5 mt-8 mb-4">
|
||||||
<div class="col-span-1 h-[calc(100vh-170px)] flex flex-col justify-start">
|
<div class="col-span-1 h-[calc(100vh-170px)] flex flex-col justify-start">
|
||||||
<div>
|
<div>
|
||||||
|
46
src/views/system/components/SystemDeptBar.vue
Normal file
46
src/views/system/components/SystemDeptBar.vue
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
<script setup>
|
||||||
|
import { useRoute, useRouter } from "vue-router";
|
||||||
|
import { getDepartmentList } from "@/apis/asset";
|
||||||
|
import { onMounted, ref, watch, inject } from "vue";
|
||||||
|
import useActiveBtn from "@/hooks/useActiveBtn";
|
||||||
|
import useBuildingStore from "@/stores/useBuildingStore";
|
||||||
|
|
||||||
|
const storeBuild = useBuildingStore();
|
||||||
|
// 選部門
|
||||||
|
const {
|
||||||
|
items: sysDeptItems,
|
||||||
|
changeActiveBtn: changeDeptActiveBtn,
|
||||||
|
setItems: setDeptItems,
|
||||||
|
selectedBtn: selectedDeptItems,
|
||||||
|
} = useActiveBtn();
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
const deptList = [
|
||||||
|
{
|
||||||
|
title: "All",
|
||||||
|
key: "main",
|
||||||
|
},
|
||||||
|
...storeBuild.deptList,
|
||||||
|
];
|
||||||
|
setDeptItems(deptList);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
<span class="text-md font-extrabold"
|
||||||
|
>{{ $t("assetManagement.department") }} :</span
|
||||||
|
>
|
||||||
|
<ButtonGroup
|
||||||
|
:items="sysDeptItems"
|
||||||
|
className="btn-xs rounded-md"
|
||||||
|
:onclick="
|
||||||
|
(e, item) => {
|
||||||
|
changeDeptActiveBtn(item);
|
||||||
|
}
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped></style>
|
@ -65,8 +65,8 @@ watch(() => store.selectedBuilding, (newValue) => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="flex items-center border border-info px-4 py-2">
|
<div class="flex items-center gap-3">
|
||||||
<h5 class="text-md font-extrabold me-4">{{ store.selectedSystem?.full_name }}</h5>
|
<span class="text-md font-extrabold">{{ $t("energy.floor") }} :</span>
|
||||||
<ButtonGroup :items="items" :withLine="false" className="btn-xs rounded-md" :onclick="(e, item) => onClick(item)" />
|
<ButtonGroup :items="items" :withLine="false" className="btn-xs rounded-md" :onclick="(e, item) => onClick(item)" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
Loading…
Reference in New Issue
Block a user