首頁重新切版 | 樓層與部門放置store | 設定MQTT重新切版
This commit is contained in:
parent
c8d8fbf254
commit
f956402648
@ -1,27 +1,20 @@
|
||||
<script setup>
|
||||
import { getBuildings } from "@/apis/building";
|
||||
import { onMounted, ref } from "vue";
|
||||
import { onMounted } from "vue";
|
||||
import useBuildingStore from "@/stores/useBuildingStore";
|
||||
|
||||
const store = useBuildingStore();
|
||||
|
||||
const getBui = async () => {
|
||||
const res = await getBuildings();
|
||||
store.buildings = res.data;
|
||||
store.selectedBuilding = res?.data[0];
|
||||
};
|
||||
|
||||
const selectBuilding = (bui) => {
|
||||
store.selectedBuilding = bui;
|
||||
store.selectedBuilding = bui; // 改變 selectedBuilding,watch 會自動更新資料
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
getBui();
|
||||
store.initialize(); // 初始化資料
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="dropdown dropdown-bottom ">
|
||||
<div class="dropdown dropdown-bottom">
|
||||
<div
|
||||
tabindex="0"
|
||||
role="button"
|
||||
|
@ -46,11 +46,11 @@ const getSubPage = async (system_type) => {
|
||||
const res = await getSideBar(system_type);
|
||||
menu_array.value = res.data;
|
||||
};
|
||||
const showDrawer = (authCode) => {
|
||||
const showDrawer = async (authCode) => {
|
||||
if (authCode === "PF1") {
|
||||
getSubMonitorPage();
|
||||
await getSubMonitorPage();
|
||||
} else if (authCode === "PF2" || authCode === "PF11") {
|
||||
getSubPage(authCode === "PF2" ? "Energy" : "Setting");
|
||||
await getSubPage(authCode === "PF2" ? "Energy" : "Setting");
|
||||
}
|
||||
currentAuthCode.value = authCode;
|
||||
open.value = true;
|
||||
|
@ -101,7 +101,37 @@
|
||||
"total_amount": "金额总计",
|
||||
"elec_price_list": "电价表",
|
||||
"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": {
|
||||
"title": "显示警告",
|
||||
|
@ -101,7 +101,37 @@
|
||||
"total_amount": "金額總計",
|
||||
"elec_price_list": "電價表",
|
||||
"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": {
|
||||
"title": "顯示警告",
|
||||
|
@ -101,7 +101,37 @@
|
||||
"total_amount": "Total amount",
|
||||
"elec_price_list": "Electricity Price List",
|
||||
"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": {
|
||||
"title": "Warning",
|
||||
|
@ -61,6 +61,7 @@ import {
|
||||
faDownload,
|
||||
faStream,
|
||||
faSave,
|
||||
faCrown
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
/* add icons to the library */
|
||||
@ -122,7 +123,8 @@ library.add(
|
||||
faGlobe,
|
||||
faDownload,
|
||||
faStream,
|
||||
faSave
|
||||
faSave,
|
||||
faCrown
|
||||
);
|
||||
|
||||
export default library;
|
||||
|
@ -1,17 +1,18 @@
|
||||
import { defineStore } from "pinia";
|
||||
import { ref, computed } from "vue";
|
||||
import { ref, computed, watch } from "vue";
|
||||
import { useRoute } from "vue-router";
|
||||
import { getBuildings } from "@/apis/building";
|
||||
import { getAssetFloorList, getDepartmentList } from "@/apis/asset";
|
||||
|
||||
const useBuildingStore = defineStore("buildingInfo", () => {
|
||||
// 所有棟別
|
||||
// 狀態定義
|
||||
const buildings = ref([]);
|
||||
|
||||
const selectedBuilding = ref(null);
|
||||
|
||||
// 所有大小類系統
|
||||
const floorList = ref([]);
|
||||
const deptList = ref([]);
|
||||
const mainSubSys = ref([]);
|
||||
|
||||
// 所有大類
|
||||
// 計算屬性
|
||||
const mainSys = computed(() =>
|
||||
mainSubSys.value.map(({ main_system_tag, full_name }) => ({
|
||||
main_system_tag,
|
||||
@ -21,7 +22,6 @@ const useBuildingStore = defineStore("buildingInfo", () => {
|
||||
|
||||
const subSys = computed(() => {
|
||||
let subPages = [];
|
||||
|
||||
mainSubSys.value.forEach(({ main_system_tag, history_Sub_systems }) => {
|
||||
subPages = [
|
||||
...subPages,
|
||||
@ -32,7 +32,6 @@ const useBuildingStore = defineStore("buildingInfo", () => {
|
||||
})),
|
||||
];
|
||||
});
|
||||
|
||||
return subPages;
|
||||
});
|
||||
|
||||
@ -44,14 +43,60 @@ const useBuildingStore = defineStore("buildingInfo", () => {
|
||||
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 {
|
||||
buildings,
|
||||
selectedBuilding,
|
||||
floorList,
|
||||
deptList,
|
||||
mainSubSys,
|
||||
mainSys,
|
||||
subSys,
|
||||
selectedSystem,
|
||||
fetchBuildings,
|
||||
fetchFloorList,
|
||||
fetchDepartmentList,
|
||||
initialize,
|
||||
};
|
||||
});
|
||||
|
||||
export default useBuildingStore;
|
||||
|
@ -4,8 +4,10 @@ import AssetMainList from "./components/AssetMainList.vue";
|
||||
import AssetSubList from "./components/AssetSubList.vue";
|
||||
import AssetTable from "./components/AssetTable.vue";
|
||||
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 useBuildingStore from "@/stores/useBuildingStore";
|
||||
const storeBuild = useBuildingStore();
|
||||
const { searchParams, changeParams } = useSearchParam();
|
||||
const companyOptions = ref([]);
|
||||
const iotSchemaOptions = ref([]);
|
||||
@ -24,20 +26,12 @@ const getElecType = async () => {
|
||||
const res = await getElecTypeList();
|
||||
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(() => {
|
||||
getCompany();
|
||||
getElecType();
|
||||
getDepartment();
|
||||
getFloors();
|
||||
floors.value = storeBuild.floorList;
|
||||
departmentList.value = storeBuild.deptList;
|
||||
});
|
||||
|
||||
watch(
|
||||
|
@ -1,11 +1,13 @@
|
||||
<script setup>
|
||||
import { onMounted, ref, provide, watch } from "vue";
|
||||
import { getDashboardInit } from "@/apis/dashboard";
|
||||
import Forge from "@/components/forge/Forge.vue";
|
||||
import DashboardStat from "./components/DashboardStat.vue";
|
||||
import DashboardElecChart from "./components/DashboardElecChart.vue";
|
||||
import DashboardSysCard from "./components/DashboardSysCard.vue";
|
||||
import DashboardSysProgress from "./components/DashboardSysProgress.vue";
|
||||
import { getDashboardInit } from "@/apis/dashboard";
|
||||
import { onMounted, ref, provide, watch } from "vue";
|
||||
import DashboardElecRank from "./components/DashboardElecRank.vue";
|
||||
import DashboardElecTrends from "./components/DashboardElecTrends.vue";
|
||||
import DashboardElecCompare from "./components/DashboardElecCompare.vue";
|
||||
|
||||
const initialData = ref(null);
|
||||
// const forgeData = ref([]);
|
||||
@ -52,24 +54,37 @@ provide("dashboard_items", {
|
||||
<div class="w-full xl:w-2/3">
|
||||
<!-- 用電數據 -->
|
||||
<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 class="w-full lg:w-2/3">
|
||||
<div class="w-full lg:w-1/3">
|
||||
<DashboardSysCard />
|
||||
</div>
|
||||
<!--狀態、進度-->
|
||||
<div class="w-full lg:w-1/3">
|
||||
<DashboardSysProgress />
|
||||
</div>
|
||||
<!-- 環比能耗 -->
|
||||
<div class="w-full lg:w-1/3">
|
||||
<DashboardElecCompare />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="css" scoped>
|
||||
.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 {
|
||||
|
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>
|
||||
.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 {
|
||||
|
@ -2,6 +2,8 @@
|
||||
import { ref } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import useBuildingStore from "@/stores/useBuildingStore";
|
||||
const store = useBuildingStore();
|
||||
const router = useRouter();
|
||||
// 假資料
|
||||
const mockData = ref([
|
||||
@ -129,16 +131,18 @@ const navigateToSubSystem = (mainSystemId, subSystemId) => {
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
console.log("subSys",store.subSys)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-wrap -mx-1">
|
||||
<div class="flex flex-wrap -mx-1 h-[21rem] overflow-y-scroll">
|
||||
<div
|
||||
v-for="(item, index) in mockData"
|
||||
v-for="(item, index) in store.subSys"
|
||||
:key="index"
|
||||
:class="
|
||||
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
|
||||
? 'saturate-200 cursor-pointer text-base text-info'
|
||||
: 'grayscale opacity-70 cursor-not-allowed text-sm'
|
||||
@ -154,14 +158,15 @@ const navigateToSubSystem = (mainSystemId, subSystemId) => {
|
||||
|
||||
<div class="equipment-item">
|
||||
<div class="w-16">
|
||||
<FontAwesomeIcon
|
||||
<!-- <FontAwesomeIcon
|
||||
class="text-2xl mt-1 m-auto"
|
||||
:icon="['fas', item.icon]"
|
||||
></FontAwesomeIcon>
|
||||
:icon="['fas', 'tv']"
|
||||
></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 class="icon-text">
|
||||
<div class="">
|
||||
{{ item.title }}
|
||||
{{ item.full_name }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,5 +1,6 @@
|
||||
<script setup>
|
||||
import { ref, computed } from "vue";
|
||||
import DashboardSysProgressModal from "./DashboardSysProgressModal.vue";
|
||||
|
||||
const equipmentData = ref({
|
||||
title: "System Status",
|
||||
@ -20,10 +21,46 @@ const orderData = ref({
|
||||
{ label: "Completed", value: 1 },
|
||||
],
|
||||
});
|
||||
|
||||
const openModal = () => {
|
||||
system_status_modal.showModal();
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DashboardSysProgressModal
|
||||
/>
|
||||
<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="state-box">
|
||||
<div class="title">
|
||||
@ -44,7 +81,8 @@ const orderData = ref({
|
||||
<tr
|
||||
v-for="(item, index) in equipmentData.items"
|
||||
: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>
|
||||
<td>{{ item.online }}</td>
|
||||
@ -55,35 +93,6 @@ const orderData = ref({
|
||||
</table>
|
||||
</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>
|
||||
</template>
|
||||
|
||||
@ -101,7 +110,7 @@ const orderData = ref({
|
||||
}
|
||||
|
||||
.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 {
|
||||
|
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>
|
||||
import { ref, onMounted, provide } from "vue";
|
||||
import ImmediateDemandChart from "./ImmediateDemandChart.vue";
|
||||
import ElecConsumption from "./ElecConsumption.vue";
|
||||
import UsageInformation from "./UsageInformation.vue";
|
||||
@ -6,8 +7,25 @@ import MonthlyElecBillChart from "./MonthlyElecBillChart.vue";
|
||||
import CarbonEmissionChart from "./CarbonEmissionChart.vue";
|
||||
import BillingDegreeChart from "./BillingDegreeChart.vue";
|
||||
import IntervalBillChart from "./IntervalBillChart.vue";
|
||||
import useActiveBtn from "@/hooks/useActiveBtn";
|
||||
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 getData = async () => {
|
||||
@ -19,6 +37,8 @@ const getData = async () => {
|
||||
|
||||
onMounted(() => {
|
||||
getData();
|
||||
setDeptItems(storeBuild.deptList);
|
||||
setFloorItems(storeBuild.floorList);
|
||||
});
|
||||
|
||||
provide("energy_data", { taipower_data });
|
||||
@ -26,6 +46,36 @@ provide("energy_data", { taipower_data });
|
||||
|
||||
<template>
|
||||
<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">
|
||||
<ElecConsumption />
|
||||
</div>
|
||||
|
@ -14,9 +14,11 @@ import EnergyActionButton from "./EnergyActionButton.vue";
|
||||
import EnergySearchTime from "./EnergySearchTime.vue";
|
||||
import useActiveBtn from "@/hooks/useActiveBtn";
|
||||
import { getEnergySearch } from "@/apis/energy";
|
||||
import { getDepartmentList, getElecTypeList } from "@/apis/asset";
|
||||
import { getElecTypeList } from "@/apis/asset";
|
||||
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 { searchParams, changeParams } = useSearchParam();
|
||||
const route = useRoute();
|
||||
@ -57,17 +59,6 @@ const {
|
||||
selectedBtn: selectedPoints,
|
||||
} = 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 res = await getElecTypeList();
|
||||
const elecType = res.data.map((d, index) => ({
|
||||
@ -162,7 +153,7 @@ watch(
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
getDepartment();
|
||||
setDeptItems(storeBuild.deptList);
|
||||
getElecType();
|
||||
});
|
||||
</script>
|
||||
|
@ -3,13 +3,12 @@ import { ref, onMounted, watch, inject } from "vue";
|
||||
import { useRoute } from "vue-router";
|
||||
import useActiveBtn from "@/hooks/useActiveBtn";
|
||||
import EnergyReportTimeRange from "./EnergyReportTimeRange.vue";
|
||||
import {
|
||||
getDepartmentList,
|
||||
getAssetFloorList,
|
||||
getElecTypeList,
|
||||
} from "@/apis/asset";
|
||||
import { getElecTypeList } from "@/apis/asset";
|
||||
import { getReport, getExcel } from "@/apis/energy";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import useBuildingStore from "@/stores/useBuildingStore";
|
||||
|
||||
const storeBuild = useBuildingStore();
|
||||
const { t } = useI18n();
|
||||
const route = useRoute();
|
||||
const { tableData, loading } = inject("energy_table_data");
|
||||
@ -43,39 +42,16 @@ const {
|
||||
selectedBtn: selectedFloorItems,
|
||||
} = 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 res = await getElecTypeList();
|
||||
const elecType = res.data.map((d, index) => ({
|
||||
...d,
|
||||
title: d.name,
|
||||
key: d.id,
|
||||
active: index === 0,
|
||||
key: d.id
|
||||
}));
|
||||
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 () => {
|
||||
loading.value = true;
|
||||
const res = await getReport(formState.value);
|
||||
@ -124,9 +100,9 @@ watch(
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
getDepartment();
|
||||
setDeptItems(storeBuild.deptList);
|
||||
setFloorItems(storeBuild.floorList);
|
||||
getElecType();
|
||||
getFloor();
|
||||
});
|
||||
</script>
|
||||
|
||||
|
@ -26,6 +26,13 @@ import dayjs from "dayjs";
|
||||
const { searchParams, changeParams } = useSearchParam();
|
||||
|
||||
const store = useBuildingStore();
|
||||
// 選部門
|
||||
const {
|
||||
items: sysDeptItems,
|
||||
changeActiveBtn: changeDeptActiveBtn,
|
||||
setItems: setDeptItems,
|
||||
selectedBtn: selectedDeptItems,
|
||||
} = useActiveBtn("multiple");
|
||||
// 選大類
|
||||
const {
|
||||
items: sysMainTagItems,
|
||||
@ -40,6 +47,13 @@ const {
|
||||
setItems: setSysItems,
|
||||
selectedBtn: selectedSysItems,
|
||||
} = useActiveBtn();
|
||||
// 設定點位
|
||||
const {
|
||||
items: points,
|
||||
changeActiveBtn: changeActivePoint,
|
||||
setItems: setPoints,
|
||||
selectedBtn: selectedPoints,
|
||||
} = useActiveBtn("multiple");
|
||||
|
||||
watch(
|
||||
() => store.mainSys,
|
||||
@ -60,11 +74,13 @@ watch(
|
||||
() => selectedMainSysItems,
|
||||
(newVal, oldVal) => {
|
||||
setSysItems(
|
||||
store.subSys.filter((s) => s.main_system_tag === newVal.value?.key).map(({ full_name, sub_system_tag }, index) => ({
|
||||
title: full_name,
|
||||
key: sub_system_tag,
|
||||
active: index === 0,
|
||||
}))
|
||||
store.subSys
|
||||
.filter((s) => s.main_system_tag === newVal.value?.key)
|
||||
.map(({ full_name, sub_system_tag }, index) => ({
|
||||
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 res = await getHistoryPoints(deviceList);
|
||||
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);
|
||||
|
||||
watch(searchParams, (newVal, oldValue) => {
|
||||
@ -149,6 +170,13 @@ onMounted(() => {
|
||||
active: index === 0,
|
||||
}))
|
||||
);
|
||||
setDeptItems(
|
||||
store.deptList.map((d, index) => ({
|
||||
...d,
|
||||
title: d.name,
|
||||
key: d.id,
|
||||
}))
|
||||
);
|
||||
});
|
||||
|
||||
onBeforeMount(() => {
|
||||
@ -159,6 +187,20 @@ onBeforeMount(() => {
|
||||
<template>
|
||||
<div class="flex flex-col custom-border p-4 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">
|
||||
<h2 class="text-lg font-bold ps-2 whitespace-nowrap">
|
||||
{{ $t("history.system_category") }} :
|
||||
|
@ -13,8 +13,9 @@ const deviceData = ref([]);
|
||||
const searchTerm = ref(""); //搜尋文字
|
||||
const activeSearchTerm = ref("");
|
||||
|
||||
const getDeviceData = async (sub_system_tag, renew) => {
|
||||
const res = await getHistorySideBar({sub_system_tag: sub_system_tag});
|
||||
const getDeviceData = async (sub_system_tag,department_id) => {
|
||||
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) => ({
|
||||
building_tag: building.building_tag,
|
||||
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);
|
||||
changeSelected(
|
||||
[res.data[0]?.floor_list[0]?.device_list[0]?.device_number],
|
||||
renew
|
||||
[res.data[0]?.floor_list[0]?.device_list[0]?.device_number]
|
||||
);
|
||||
};
|
||||
|
||||
@ -57,7 +57,7 @@ watch(
|
||||
newVal.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(() => {
|
||||
return typeof searchParams.value.Device_list === "string"
|
||||
? [searchParams.value.Device_list]
|
||||
|
@ -8,7 +8,8 @@ import Vendor from "./components/Vendor.vue";
|
||||
import Floors from "./components/Floors.vue";
|
||||
import Building from "./components/Building.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 currentComponent = ref(null);
|
||||
@ -29,7 +30,7 @@ const updateComponent = () => {
|
||||
} else if (sub_system_id === "ElecPricing") {
|
||||
currentComponent.value = ElecPriceManagement;
|
||||
} else if (sub_system_id === "MQTT_Result") {
|
||||
currentComponent.value = MITTList;
|
||||
currentComponent.value = MQTTList;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -1,9 +1,12 @@
|
||||
<script setup>
|
||||
import Table from "@/components/customUI/Table.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 { useI18n } from "vue-i18n";
|
||||
import useBuildingStore from "@/stores/useBuildingStore";
|
||||
|
||||
const storeBuild = useBuildingStore();
|
||||
const { t } = useI18n();
|
||||
const { openToast, cancelToastOpen } = inject("app_toast");
|
||||
|
||||
@ -23,20 +26,8 @@ const columns = computed(() => [
|
||||
},
|
||||
]);
|
||||
|
||||
const dataSource = ref([]);
|
||||
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({
|
||||
id: 0,
|
||||
name: "",
|
||||
@ -59,13 +50,16 @@ const remove = async (id) => {
|
||||
await cancelToastOpen();
|
||||
const res = await deleteDepartmentItem(id);
|
||||
if (res.isSuccess) {
|
||||
getDataSource();
|
||||
storeBuild.fetchDepartmentList();
|
||||
openToast("success", t("msg.delete_success"));
|
||||
} else {
|
||||
openToast("error", res.msg);
|
||||
}
|
||||
});
|
||||
};
|
||||
onMounted(() => {
|
||||
storeBuild.fetchDepartmentList();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@ -73,11 +67,10 @@ const remove = async (id) => {
|
||||
<h3 class="text-xl mr-5">{{ $t("assetManagement.department") }}</h3>
|
||||
<DeptModal
|
||||
:formState="formState"
|
||||
:getData="getDataSource"
|
||||
:openModal="openModal"
|
||||
/>
|
||||
</div>
|
||||
<Table :columns="columns" :dataSource="dataSource" :loading="loading">
|
||||
<Table :columns="columns" :dataSource="storeBuild.deptList" :loading="loading">
|
||||
<template #bodyCell="{ record, column, index }">
|
||||
<template v-if="column.key === 'index'">{{ index + 1 }}</template>
|
||||
<template v-else-if="column.key === 'operation'">
|
||||
|
@ -5,12 +5,14 @@ import "yup-phone-lite";
|
||||
import useFormErrorMessage from "@/hooks/useFormErrorMessage";
|
||||
import { postDepartmentList } from "@/apis/asset";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import useBuildingStore from "@/stores/useBuildingStore";
|
||||
|
||||
const storeBuild = useBuildingStore();
|
||||
const { t } = useI18n();
|
||||
const { openToast } = inject("app_toast");
|
||||
|
||||
const props = defineProps({
|
||||
formState: Object,
|
||||
getData: Function,
|
||||
openModal: Function
|
||||
});
|
||||
|
||||
@ -30,7 +32,7 @@ const onOk = async () => {
|
||||
const value = await handleSubmit(deptScheme, props.formState);
|
||||
const res = await postDepartmentList(value);
|
||||
if (res.isSuccess) {
|
||||
props.getData();
|
||||
storeBuild.fetchDepartmentList();
|
||||
onCancel();
|
||||
} else {
|
||||
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 { getAssetMainList, getAssetSubList, getIOTSchema } from "@/apis/asset";
|
||||
import useActiveBtn from "@/hooks/useActiveBtn";
|
||||
import MITTCheckboxTree from "./MITTCheckboxTree.vue";
|
||||
import MQTTCheckboxTree from "./MQTTCheckboxTree.vue";
|
||||
import generateSchema from "json-schema-generator";
|
||||
import { useI18n } from "vue-i18n";
|
||||
const { t } = useI18n();
|
||||
const props = defineProps({
|
||||
openModal: Function,
|
||||
onCancel: Function,
|
||||
});
|
||||
// 新增:將 jsonInput 與結構資料初始化
|
||||
const jsonInput = ref("");
|
||||
const treeData = ref(null);
|
||||
@ -105,6 +101,14 @@ watch(
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
const openModal = () => {
|
||||
MQTT_Parse_item.showModal();
|
||||
};
|
||||
|
||||
const onCancel = () => {
|
||||
MQTT_Parse_item.close();
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@ -114,32 +118,33 @@ watch(
|
||||
<Modal
|
||||
id="MQTT_Parse_item"
|
||||
title="MQTT_Parse"
|
||||
:open="open"
|
||||
:onCancel="onCancel"
|
||||
width="1600"
|
||||
:width="1600"
|
||||
>
|
||||
<template #modalContent>
|
||||
<div class="flex w-full gap-4 mt-5">
|
||||
<div class="w-1/2 relative">
|
||||
<div class="w-full">
|
||||
<textarea
|
||||
v-model="jsonInput"
|
||||
placeholder="請貼上 JSON 格式數據"
|
||||
class="w-full h-[500px] p-4 border rounded-lg font-mono text-white"
|
||||
></textarea>
|
||||
<button @click="clearAll" class="bg-error btn absolute top-5 right-5">
|
||||
<font-awesome-icon :icon="['fas', 'trash-alt']" />
|
||||
</div>
|
||||
<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>
|
||||
</div>
|
||||
<button @click="parseJson" class="btn-success btn my-auto">
|
||||
轉換
|
||||
<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
|
||||
<div class="w-full p-4 rounded-lg border overflow-y-scroll h-[500px]">
|
||||
<MQTTCheckboxTree
|
||||
v-if="treeData"
|
||||
:data="treeData"
|
||||
v-model:checked-nodes="checkedNodes"
|
||||
@ -147,30 +152,28 @@ watch(
|
||||
<div v-else class="text-white">請在左側輸入 JSON 並點擊轉換按鈕</div>
|
||||
</div>
|
||||
</div>
|
||||
<form ref="form" class="">
|
||||
<form ref="form" class="" v-if="checkedNodes.length">
|
||||
<div class="flex items-center mt-5">
|
||||
<h2 class="text-lg font-bold whitespace-nowrap me-2">
|
||||
結構名稱 :
|
||||
</h2>
|
||||
<h2 class="text-lg font-bold whitespace-nowrap me-2">結構名稱 :</h2>
|
||||
<Input v-model="schemaName" />
|
||||
</div>
|
||||
<table class="table" v-if="checkedNodes.length">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>IoT 點位名稱</th>
|
||||
<!-- <th>IoT 點位名稱</th> -->
|
||||
<th>IoT 點位結構</th>
|
||||
<th>系統點位名稱</th>
|
||||
<th>bool 值</th>
|
||||
<!-- <th>bool 值</th>
|
||||
<th>小數點個數</th>
|
||||
<th>點位開關</th>
|
||||
<th>點位隱藏</th> -->
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(node, index) in checkedNodes" :key="node">
|
||||
<td>
|
||||
<!-- <td>
|
||||
<Input :value="formStates[index]" name="IoT_point_name" />
|
||||
</td>
|
||||
<td>
|
||||
</td> -->
|
||||
<td class="w-1/2">
|
||||
<Input
|
||||
:value="formStates[index]"
|
||||
name="IoT_point_schema"
|
||||
@ -184,7 +187,7 @@ watch(
|
||||
<option value="option3">選項 3</option>
|
||||
</select>
|
||||
</td>
|
||||
<td>
|
||||
<!-- <td>
|
||||
<div class="flex w-36">
|
||||
<Select
|
||||
:value="formStates[index]"
|
||||
@ -215,7 +218,7 @@ watch(
|
||||
>
|
||||
</Select>
|
||||
</div>
|
||||
</td>
|
||||
</td> -->
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
@ -2,7 +2,7 @@
|
||||
import { ref, onMounted, watch, computed } from "vue";
|
||||
import { getAssetMainList, getAssetSubList, getIOTSchema } from "@/apis/asset";
|
||||
import useActiveBtn from "@/hooks/useActiveBtn";
|
||||
import MITTlISTAddModal from "./MITTlISTAddModal.vue";
|
||||
import PointListAddModal from "./PointListAddModal.vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
const { t } = useI18n();
|
||||
const formState = ref({});
|
||||
@ -46,14 +46,9 @@ const getSubSystems = async (id) => {
|
||||
};
|
||||
|
||||
const columns = computed(() => [
|
||||
{
|
||||
title: "schema",
|
||||
key: "schema_name",
|
||||
},
|
||||
{
|
||||
title: "point",
|
||||
key: "pointOrg",
|
||||
filter: true,
|
||||
},
|
||||
{
|
||||
title: "Description",
|
||||
@ -76,12 +71,11 @@ const getDataSource = async (id) => {
|
||||
};
|
||||
|
||||
const openModal = () => {
|
||||
MQTT_Parse_item.showModal();
|
||||
point_list_item.showModal();
|
||||
};
|
||||
|
||||
const onCancel = () => {
|
||||
MQTT_Parse_item.close();
|
||||
getDataSource(parseInt(selectedSubSysItems.key))
|
||||
point_list_item.close();
|
||||
};
|
||||
|
||||
watch(
|
||||
@ -144,8 +138,8 @@ onMounted(() => {
|
||||
/>
|
||||
</div>
|
||||
<div class="flex justify-start items-center mt-5 mb-2">
|
||||
<h3 class="text-xl mr-5">MQTT_Parse : </h3>
|
||||
<MITTlISTAddModal
|
||||
<h3 class="text-xl mr-5">Points : </h3>
|
||||
<PointListAddModal
|
||||
:openModal="openModal"
|
||||
: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 { computed, watch, provide, ref, onMounted, onBeforeUnmount } from "vue";
|
||||
import SystemFloorBar from './components/SystemFloorBar.vue';
|
||||
import SystemDeptBar from './components/SystemDeptBar.vue';
|
||||
import useBuildingStore from "@/stores/useBuildingStore";
|
||||
import ForgeForSystem from "@/components/forge/ForgeForSystem.vue";
|
||||
import { getSystemDevices, getSystemRealTime } from "@/apis/system";
|
||||
@ -238,7 +239,11 @@ onBeforeUnmount(() => {
|
||||
|
||||
<template>
|
||||
<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="col-span-1 h-[calc(100vh-170px)] flex flex-col justify-start">
|
||||
<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>
|
||||
|
||||
<template>
|
||||
<div class="flex items-center border border-info px-4 py-2">
|
||||
<h5 class="text-md font-extrabold me-4">{{ store.selectedSystem?.full_name }}</h5>
|
||||
<div class="flex items-center gap-3">
|
||||
<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)" />
|
||||
</div>
|
||||
</template>
|
||||
|
Loading…
Reference in New Issue
Block a user