Merge branch 'main' into feature/headquartersSetting
This commit is contained in:
commit
73a76aca2e
@ -34,4 +34,6 @@ export const GET_ASSET_ELECTYPE_API = `/AssetManage/GetElecType`;
|
|||||||
export const POST_ASSET_ELECTYPE_API = `/AssetManage/SaveElecType`;
|
export const POST_ASSET_ELECTYPE_API = `/AssetManage/SaveElecType`;
|
||||||
export const DELETE_ASSET_ELECTYPE_API = `/AssetManage/DeleteElecType`;
|
export const DELETE_ASSET_ELECTYPE_API = `/AssetManage/DeleteElecType`;
|
||||||
|
|
||||||
export const POST_ASSET_ELEC_SETTING_API = `/AssetManage/SaveAssetSetting`;
|
export const POST_ASSET_ELEC_SETTING_API = `/AssetManage/SaveAssetSetting`;
|
||||||
|
|
||||||
|
export const POST_ASSET_MQTT_PUBLISH_API = `/api/mqtt/publish`;
|
@ -26,13 +26,14 @@ import {
|
|||||||
POST_ASSET_ELECTYPE_API,
|
POST_ASSET_ELECTYPE_API,
|
||||||
DELETE_ASSET_ELECTYPE_API,
|
DELETE_ASSET_ELECTYPE_API,
|
||||||
POST_ASSET_ELEC_SETTING_API,
|
POST_ASSET_ELEC_SETTING_API,
|
||||||
|
POST_ASSET_MQTT_PUBLISH_API,
|
||||||
} from "./api";
|
} from "./api";
|
||||||
import instance from "@/util/request";
|
import instance from "@/util/request";
|
||||||
import apihandler from "@/util/apihandler";
|
import apihandler from "@/util/apihandler";
|
||||||
import { object } from "yup";
|
import { object } from "yup";
|
||||||
|
|
||||||
export const getAssetMainList = async (building_guid) => {
|
export const getAssetMainList = async (building_guid) => {
|
||||||
const res = await instance.post(GET_ASSET_MAIN_LIST_API,{building_guid});
|
const res = await instance.post(GET_ASSET_MAIN_LIST_API, { building_guid });
|
||||||
|
|
||||||
return apihandler(res.code, res.data, {
|
return apihandler(res.code, res.data, {
|
||||||
msg: res.msg,
|
msg: res.msg,
|
||||||
@ -49,12 +50,17 @@ export const deleteAssetMainItem = async (id) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const postAssetMainList = async ({ id, system_key, system_value, building_guid }) => {
|
export const postAssetMainList = async ({
|
||||||
|
id,
|
||||||
|
system_key,
|
||||||
|
system_value,
|
||||||
|
building_guid,
|
||||||
|
}) => {
|
||||||
const res = await instance.post(POST_ASSET_MAIN_LIST_API, {
|
const res = await instance.post(POST_ASSET_MAIN_LIST_API, {
|
||||||
id,
|
id,
|
||||||
system_key,
|
system_key,
|
||||||
system_value,
|
system_value,
|
||||||
building_guid
|
building_guid,
|
||||||
});
|
});
|
||||||
|
|
||||||
return apihandler(res.code, res.data, {
|
return apihandler(res.code, res.data, {
|
||||||
@ -241,6 +247,9 @@ export const postDeviceItem = async ({
|
|||||||
decimals,
|
decimals,
|
||||||
is_bool,
|
is_bool,
|
||||||
is_link,
|
is_link,
|
||||||
|
show_event_switch_btn,
|
||||||
|
event_switch_on_message,
|
||||||
|
event_switch_off_message,
|
||||||
}) => {
|
}) => {
|
||||||
const res = await instance.post(POST_ASSET_DEVICE_ITEM_API, {
|
const res = await instance.post(POST_ASSET_DEVICE_ITEM_API, {
|
||||||
id,
|
id,
|
||||||
@ -250,6 +259,9 @@ export const postDeviceItem = async ({
|
|||||||
decimals,
|
decimals,
|
||||||
is_bool,
|
is_bool,
|
||||||
is_link,
|
is_link,
|
||||||
|
show_event_switch_btn,
|
||||||
|
event_switch_on_message,
|
||||||
|
event_switch_off_message,
|
||||||
});
|
});
|
||||||
|
|
||||||
return apihandler(res.code, res.data, {
|
return apihandler(res.code, res.data, {
|
||||||
@ -335,3 +347,15 @@ export const postAssetElecSetting = async (formData) => {
|
|||||||
code: res.code,
|
code: res.code,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const postMQTTpublish = async ({ Topic, Payload }) => {
|
||||||
|
const res = await instance.post(POST_ASSET_MQTT_PUBLISH_API, {
|
||||||
|
Topic,
|
||||||
|
Payload,
|
||||||
|
});
|
||||||
|
|
||||||
|
return apihandler(res.code, res.data, {
|
||||||
|
msg: res.msg,
|
||||||
|
code: res.code,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
@ -10,4 +10,8 @@ export const GET_DASHBOARD_PRODUCT_HISTORY_API = `/SituationRoom/GetProductionHi
|
|||||||
|
|
||||||
export const GET_DASHBOARD_ENERGY_INFO_API = `api/dashboard/GetEnergyInfo`
|
export const GET_DASHBOARD_ENERGY_INFO_API = `api/dashboard/GetEnergyInfo`
|
||||||
export const GET_DASHBOARD_ENERGY_COST_API = `api/dashboard/GetEnergyCost`
|
export const GET_DASHBOARD_ENERGY_COST_API = `api/dashboard/GetEnergyCost`
|
||||||
export const GET_DASHBOARD_ALARMOPERATION_INFO_API = `api/dashboard/GetAlarmOperationInfo`
|
export const GET_DASHBOARD_ALARMOPERATION_INFO_API = `api/dashboard/GetAlarmOperationInfo`
|
||||||
|
|
||||||
|
export const GET_DASHBOARD_2D3DINFO_API = `api/setting/visual/query`
|
||||||
|
export const POST_DASHBOARD_2D3DINFO_API = `api/setting/visual/update`
|
||||||
|
|
||||||
|
@ -11,6 +11,8 @@ import {
|
|||||||
GET_DASHBOARD_ENERGY_INFO_API,
|
GET_DASHBOARD_ENERGY_INFO_API,
|
||||||
GET_DASHBOARD_ENERGY_COST_API,
|
GET_DASHBOARD_ENERGY_COST_API,
|
||||||
GET_DASHBOARD_ALARMOPERATION_INFO_API,
|
GET_DASHBOARD_ALARMOPERATION_INFO_API,
|
||||||
|
GET_DASHBOARD_2D3DINFO_API,
|
||||||
|
POST_DASHBOARD_2D3DINFO_API
|
||||||
} from "./api";
|
} from "./api";
|
||||||
import instance from "@/util/request";
|
import instance from "@/util/request";
|
||||||
import apihandler from "@/util/apihandler";
|
import apihandler from "@/util/apihandler";
|
||||||
@ -176,3 +178,22 @@ export const getAlarmOperationInfo = async (building_guid) => {
|
|||||||
code: res.code,
|
code: res.code,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getDashboard2D3D = async (BuildingId) => {
|
||||||
|
const res = await instance.post(GET_DASHBOARD_2D3DINFO_API, {
|
||||||
|
BuildingId});
|
||||||
|
|
||||||
|
return apihandler(res.code, res.data, {
|
||||||
|
msg: res.msg,
|
||||||
|
code: res.code,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const posttDashboard2D3D = async (formData) => {
|
||||||
|
const res = await instance.post(POST_DASHBOARD_2D3DINFO_API, formData);
|
||||||
|
|
||||||
|
return apihandler(res.code, res.data, {
|
||||||
|
msg: res.msg,
|
||||||
|
code: res.code,
|
||||||
|
});
|
||||||
|
};
|
@ -1,6 +1,6 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import * as echarts from "echarts";
|
import * as echarts from "echarts";
|
||||||
import { onMounted, ref, markRaw } from "vue";
|
import { onMounted, ref, markRaw, nextTick } from "vue";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
@ -24,9 +24,15 @@ async function updateSvg(svg, option) {
|
|||||||
} else {
|
} else {
|
||||||
clear();
|
clear();
|
||||||
}
|
}
|
||||||
axios.get(svg.path).then(({ data }) => {
|
axios.get(svg.path).then(async ({ data }) => {
|
||||||
echarts.registerMap(svg.full_name, { svg: data });
|
echarts.registerMap(svg.full_name, { svg: data });
|
||||||
chart.value.setOption(option);
|
await nextTick();
|
||||||
|
// 延遲執行以避免在主進程中調用 setOption
|
||||||
|
setTimeout(() => {
|
||||||
|
if (chart.value && !chart.value.isDisposed()) {
|
||||||
|
chart.value.setOption(option);
|
||||||
|
}
|
||||||
|
}, 0);
|
||||||
if (props.getCoordinate) {
|
if (props.getCoordinate) {
|
||||||
chart.value.getZr().on("click", function (params) {
|
chart.value.getZr().on("click", function (params) {
|
||||||
var pixelPoint = [params.offsetX, params.offsetY];
|
var pixelPoint = [params.offsetX, params.offsetY];
|
||||||
@ -44,11 +50,15 @@ async function updateSvg(svg, option) {
|
|||||||
value: dataPoint, // 當前座標值
|
value: dataPoint, // 當前座標值
|
||||||
itemStyle: { color: "#0000FF" }, // 設為藍色
|
itemStyle: { color: "#0000FF" }, // 設為藍色
|
||||||
});
|
});
|
||||||
chart.value.setOption({
|
setTimeout(() => {
|
||||||
series: {
|
if (chart.value && !chart.value.isDisposed()) {
|
||||||
data: updatedData,
|
chart.value.setOption({
|
||||||
},
|
series: {
|
||||||
});
|
data: updatedData,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, 0);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -412,9 +412,13 @@
|
|||||||
"delete_success": "删除成功",
|
"delete_success": "删除成功",
|
||||||
"delete_failed": "删除失败",
|
"delete_failed": "删除失败",
|
||||||
"mqtt_refresh": "重新设定成功",
|
"mqtt_refresh": "重新设定成功",
|
||||||
"schema_name_required": "架构名称栏位必填"
|
"schema_name_required": "架构名称栏位必填",
|
||||||
|
"incorrect_format":"格式不正确",
|
||||||
|
"send_successfully":"送出成功",
|
||||||
|
"edit_successfully":"修改成功"
|
||||||
},
|
},
|
||||||
"setting": {
|
"setting": {
|
||||||
|
"electricity_meter": "电表",
|
||||||
"MQTT_parse": "MQTT 解析",
|
"MQTT_parse": "MQTT 解析",
|
||||||
"schema": "架构",
|
"schema": "架构",
|
||||||
"point": "点位",
|
"point": "点位",
|
||||||
@ -424,6 +428,9 @@
|
|||||||
"number_of_decimal_places": "小数位数",
|
"number_of_decimal_places": "小数位数",
|
||||||
"boolean_value": "布林值",
|
"boolean_value": "布林值",
|
||||||
"hide_point": "点位显示",
|
"hide_point": "点位显示",
|
||||||
|
"hide_switch": "switch 功能",
|
||||||
|
"switch_on_message": "switch 开启时传送的讯息",
|
||||||
|
"switch_off_message": "switch 关闭时传送的讯息",
|
||||||
"schema_name": "架构名称",
|
"schema_name": "架构名称",
|
||||||
"IoT_point_structure": "IoT点位结构",
|
"IoT_point_structure": "IoT点位结构",
|
||||||
"system_point_name": "系统点位名称",
|
"system_point_name": "系统点位名称",
|
||||||
|
@ -418,9 +418,13 @@
|
|||||||
"delete_success": "刪除成功",
|
"delete_success": "刪除成功",
|
||||||
"delete_failed": "刪除失敗",
|
"delete_failed": "刪除失敗",
|
||||||
"mqtt_refresh": "重新設定成功",
|
"mqtt_refresh": "重新設定成功",
|
||||||
"schema_name_required": "架構名稱欄位必填"
|
"schema_name_required": "架構名稱欄位必填",
|
||||||
|
"incorrect_format":"格式不正確",
|
||||||
|
"send_successfully":"送出成功",
|
||||||
|
"edit_successfully":"修改成功"
|
||||||
},
|
},
|
||||||
"setting": {
|
"setting": {
|
||||||
|
"electricity_meter":"電表",
|
||||||
"MQTT_parse": "MQTT 解析",
|
"MQTT_parse": "MQTT 解析",
|
||||||
"schema": "架構",
|
"schema": "架構",
|
||||||
"point": "點位",
|
"point": "點位",
|
||||||
@ -430,6 +434,9 @@
|
|||||||
"number_of_decimal_places": "小數位數",
|
"number_of_decimal_places": "小數位數",
|
||||||
"boolean_value": "布林值",
|
"boolean_value": "布林值",
|
||||||
"hide_point": "點位顯示",
|
"hide_point": "點位顯示",
|
||||||
|
"hide_switch": "switch 功能",
|
||||||
|
"switch_on_message": "switch 開啟時傳送的訊息",
|
||||||
|
"switch_off_message": "switch 關閉時傳送的訊息",
|
||||||
"schema_name": "架構名稱",
|
"schema_name": "架構名稱",
|
||||||
"IoT_point_structure": "IoT點位結構",
|
"IoT_point_structure": "IoT點位結構",
|
||||||
"system_point_name": "系統點位名稱",
|
"system_point_name": "系統點位名稱",
|
||||||
|
@ -412,9 +412,13 @@
|
|||||||
"delete_success": "Delete successfully",
|
"delete_success": "Delete successfully",
|
||||||
"delete_failed": "Delete failed",
|
"delete_failed": "Delete failed",
|
||||||
"mqtt_refresh": "MQTT reset successful",
|
"mqtt_refresh": "MQTT reset successful",
|
||||||
"schema_name_required": "The schema name field is required"
|
"schema_name_required": "The schema name field is required",
|
||||||
|
"incorrect_format":"Incorrect format",
|
||||||
|
"send_successfully":"Sent successfully",
|
||||||
|
"edit_successfully":"Edited successfully"
|
||||||
},
|
},
|
||||||
"setting": {
|
"setting": {
|
||||||
|
"electricity_meter": "Electricity Meter",
|
||||||
"MQTT_parse": "MQTT Parse",
|
"MQTT_parse": "MQTT Parse",
|
||||||
"schema": "Schema",
|
"schema": "Schema",
|
||||||
"point": "Point",
|
"point": "Point",
|
||||||
@ -424,6 +428,9 @@
|
|||||||
"number_of_decimal_places": "Number of Decimal Places",
|
"number_of_decimal_places": "Number of Decimal Places",
|
||||||
"boolean_value": "Boolean Value",
|
"boolean_value": "Boolean Value",
|
||||||
"hide_point": "Point Display",
|
"hide_point": "Point Display",
|
||||||
|
"hide_switch": "Switch Function",
|
||||||
|
"switch_on_message": "Switch On Message",
|
||||||
|
"switch_off_message": "Switch Off Message",
|
||||||
"schema_name": "Schema name",
|
"schema_name": "Schema name",
|
||||||
"IoT_point_structure": "IoT Point Structure",
|
"IoT_point_structure": "IoT Point Structure",
|
||||||
"system_point_name": "System Point Name",
|
"system_point_name": "System Point Name",
|
||||||
|
@ -6,7 +6,7 @@ import useForgeHeatmap from "./useForgeHeatmap";
|
|||||||
import useForgeFloor from "./useForgeFloor";
|
import useForgeFloor from "./useForgeFloor";
|
||||||
|
|
||||||
export default function useForgeSprite() {
|
export default function useForgeSprite() {
|
||||||
const { subscribeData } = inject("system_deviceList");
|
const { subscribeData, realtimeData } = inject("system_deviceList");
|
||||||
const { getCurrentInfoModalData, clearSelectedDeviceInfo, selected_dbid } =
|
const { getCurrentInfoModalData, clearSelectedDeviceInfo, selected_dbid } =
|
||||||
inject("system_selectedDevice");
|
inject("system_selectedDevice");
|
||||||
const forgeViewer = ref(null);
|
const forgeViewer = ref(null);
|
||||||
@ -25,7 +25,7 @@ export default function useForgeSprite() {
|
|||||||
forgeViewer.value.navigation.setView(newPosition, newTarget);
|
forgeViewer.value.navigation.setView(newPosition, newTarget);
|
||||||
|
|
||||||
// 確保 Home 視角
|
// 確保 Home 視角
|
||||||
forgeViewer.value.autocam.setCurrentViewAsHome(true);
|
forgeViewer.value.autocam.setCurrentViewAsHome(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateDataVisualization = async (viewer) => {
|
const updateDataVisualization = async (viewer) => {
|
||||||
@ -65,6 +65,23 @@ export default function useForgeSprite() {
|
|||||||
|
|
||||||
const { flatSubData } = useSystemShowData();
|
const { flatSubData } = useSystemShowData();
|
||||||
|
|
||||||
|
// 根據設備取得即時狀態顏色
|
||||||
|
const getDeviceRealtimeColor = (d) => {
|
||||||
|
if (d.full_name === "SmartSocket-AA001") return "#ff0000";
|
||||||
|
if (
|
||||||
|
d.full_name === "SmartSocket-AA003" ||
|
||||||
|
d.full_name === "SmartSocket-AA004"
|
||||||
|
)
|
||||||
|
return "#888888";
|
||||||
|
const realtimeDevice = realtimeData?.value?.find(
|
||||||
|
(item) => item.device_number === d.device_number
|
||||||
|
);
|
||||||
|
const state = realtimeDevice?.state || "";
|
||||||
|
if (state === "offnormal" || state === "")
|
||||||
|
return d.device_close_color || "#999999";
|
||||||
|
return d.device_normal_color || "#009100";
|
||||||
|
};
|
||||||
|
|
||||||
// 創建 sprites
|
// 創建 sprites
|
||||||
const createSprites = async () => {
|
const createSprites = async () => {
|
||||||
if (dataVizExtn.value) {
|
if (dataVizExtn.value) {
|
||||||
@ -74,28 +91,25 @@ export default function useForgeSprite() {
|
|||||||
let spriteColor = new THREE.Color(0xffffff);
|
let spriteColor = new THREE.Color(0xffffff);
|
||||||
const BASEURL = import.meta.env.VITE_FILE_API_BASEURL;
|
const BASEURL = import.meta.env.VITE_FILE_API_BASEURL;
|
||||||
const spriteIconUrl = `${BASEURL}/dist/hotspot.svg`;
|
const spriteIconUrl = `${BASEURL}/dist/hotspot.svg`;
|
||||||
const style = new DataVizCore.ViewableStyle(
|
|
||||||
viewableType,
|
|
||||||
spriteColor,
|
|
||||||
spriteIconUrl
|
|
||||||
);
|
|
||||||
const viewableData = new DataVizCore.ViewableData();
|
const viewableData = new DataVizCore.ViewableData();
|
||||||
viewableData.spriteSize = 24; // Sprites as points of size 24 x 24 pixels
|
viewableData.spriteSize = 24; // Sprites as points of size 24 x 24 pixels
|
||||||
flatSubData.value?.forEach((d, index) => {
|
flatSubData.value?.forEach((d, index) => {
|
||||||
if (d.device_coordinate_3d) {
|
if (d.device_coordinate_3d) {
|
||||||
const position = d.device_coordinate_3d;
|
const position = d.device_coordinate_3d;
|
||||||
style.color = new THREE.Color(hexToRgb(d.device_normal_color));
|
// 每個都 new 一個 style
|
||||||
|
const pointStyle = new DataVizCore.ViewableStyle(
|
||||||
|
viewableType,
|
||||||
|
new THREE.Color(hexToRgb(getDeviceRealtimeColor(d))),
|
||||||
|
spriteIconUrl
|
||||||
|
);
|
||||||
const viewable = new DataVizCore.SpriteViewable(
|
const viewable = new DataVizCore.SpriteViewable(
|
||||||
position,
|
position,
|
||||||
style,
|
pointStyle,
|
||||||
d.spriteDbId
|
d.spriteDbId
|
||||||
);
|
);
|
||||||
viewableData.addViewable(viewable);
|
viewableData.addViewable(viewable);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
// await viewableData.finish();
|
|
||||||
// dataVizExtn.value.addViewables(viewableData);
|
|
||||||
// console.log(dataVizExtn.value);
|
|
||||||
viewableData.finish().then(
|
viewableData.finish().then(
|
||||||
() => {
|
() => {
|
||||||
dataVizExtn.value.addViewables(viewableData);
|
dataVizExtn.value.addViewables(viewableData);
|
||||||
@ -107,6 +121,16 @@ export default function useForgeSprite() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
// 監聽 realtimeData 變化,重建 sprites
|
||||||
|
watch(
|
||||||
|
() => realtimeData?.value,
|
||||||
|
() => {
|
||||||
|
if (forgeViewer.value?.isLoadDone()) {
|
||||||
|
createSprites();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ deep: true }
|
||||||
|
);
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => flatSubData,
|
() => flatSubData,
|
||||||
|
@ -2,6 +2,7 @@ import { defineStore } from "pinia";
|
|||||||
import { ref, computed, watch } from "vue";
|
import { ref, computed, watch } from "vue";
|
||||||
import { useRoute } from "vue-router";
|
import { useRoute } from "vue-router";
|
||||||
import { getBuildings } from "@/apis/building";
|
import { getBuildings } from "@/apis/building";
|
||||||
|
import { getDashboard2D3D } from "@/apis/dashboard";
|
||||||
import { getAssetFloorList, getDepartmentList } from "@/apis/asset";
|
import { getAssetFloorList, getDepartmentList } from "@/apis/asset";
|
||||||
|
|
||||||
const useBuildingStore = defineStore("buildingInfo", () => {
|
const useBuildingStore = defineStore("buildingInfo", () => {
|
||||||
@ -11,6 +12,9 @@ const useBuildingStore = defineStore("buildingInfo", () => {
|
|||||||
const floorList = ref([]);
|
const floorList = ref([]);
|
||||||
const deptList = ref([]);
|
const deptList = ref([]);
|
||||||
const mainSubSys = ref([]);
|
const mainSubSys = ref([]);
|
||||||
|
// 控制顯示2D/3D切換與內容
|
||||||
|
const showForgeArea = ref(true);
|
||||||
|
const previewImageExt = ref("");
|
||||||
|
|
||||||
// 計算屬性
|
// 計算屬性
|
||||||
const mainSys = computed(() =>
|
const mainSys = computed(() =>
|
||||||
@ -77,22 +81,29 @@ const useBuildingStore = defineStore("buildingInfo", () => {
|
|||||||
})) || [];
|
})) || [];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 獲取2D、3D顯示與否
|
||||||
|
const fetchDashboard2D3D = async (BuildingId) => {
|
||||||
|
const res = await getDashboard2D3D(BuildingId);
|
||||||
|
showForgeArea.value = res.data.is3DEnabled;
|
||||||
|
previewImageExt.value = res.data.previewImageExt || "";
|
||||||
|
};
|
||||||
|
|
||||||
// 清除localStorage建築物
|
// 清除localStorage建築物
|
||||||
const deleteBuilding = () => {
|
const deleteBuilding = () => {
|
||||||
localStorage.removeItem("CviBuildingList");
|
localStorage.removeItem("CviBuildingList");
|
||||||
localStorage.removeItem("CviBuilding");
|
localStorage.removeItem("CviBuilding");
|
||||||
buildings.value = [];
|
buildings.value = [];
|
||||||
selectedBuilding.value = null;
|
selectedBuilding.value = null;
|
||||||
}
|
};
|
||||||
|
|
||||||
|
|
||||||
// 當 selectedBuilding 改變時,更新 floorList 和 deptList
|
// 當 selectedBuilding 改變時,更新 floorList 和 deptList
|
||||||
watch(selectedBuilding, async (newBuilding) => {
|
watch(selectedBuilding, async (newBuilding) => {
|
||||||
if (newBuilding) {
|
if (newBuilding) {
|
||||||
localStorage.setItem("CviBuilding", JSON.stringify(newBuilding))
|
localStorage.setItem("CviBuilding", JSON.stringify(newBuilding));
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
fetchFloorList(newBuilding.building_guid),
|
fetchFloorList(newBuilding.building_guid),
|
||||||
fetchDepartmentList(),
|
fetchDepartmentList(),
|
||||||
|
fetchDashboard2D3D(newBuilding.building_guid),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -111,10 +122,13 @@ const useBuildingStore = defineStore("buildingInfo", () => {
|
|||||||
mainSys,
|
mainSys,
|
||||||
subSys,
|
subSys,
|
||||||
selectedSystem,
|
selectedSystem,
|
||||||
|
showForgeArea,
|
||||||
|
previewImageExt,
|
||||||
deleteBuilding,
|
deleteBuilding,
|
||||||
fetchBuildings,
|
fetchBuildings,
|
||||||
fetchFloorList,
|
fetchFloorList,
|
||||||
fetchDepartmentList,
|
fetchDepartmentList,
|
||||||
|
fetchDashboard2D3D,
|
||||||
initialize,
|
initialize,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { onMounted, ref, inject, watch, computed } from "vue";
|
import { onMounted, ref, inject, watch, computed } from "vue";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
|
import { postMQTTpublish } from "@/apis/asset";
|
||||||
import mqtt from "mqtt";
|
import mqtt from "mqtt";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
|
|
||||||
@ -93,6 +94,28 @@ const onCancel = () => {
|
|||||||
}
|
}
|
||||||
countdown.value = 60;
|
countdown.value = 60;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onSubmit = async () => {
|
||||||
|
const Topic = formState.value.topic;
|
||||||
|
let Payload = "";
|
||||||
|
try {
|
||||||
|
Payload = JSON.stringify(JSON.parse(formState.value.publish_message));
|
||||||
|
} catch (e) {
|
||||||
|
openToast("error", t("msg.incorrect_format"), "#asset_add_table_item");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await postMQTTpublish({ Topic, Payload });
|
||||||
|
if (res.isSuccess) {
|
||||||
|
openToast("success", t("msg.send_successfully"), "#asset_add_table_item");
|
||||||
|
} else {
|
||||||
|
openToast("error", res.msg, "#asset_add_table_item");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
openToast("error", t("setting.mqtt_send_error"), "#asset_add_table_item");
|
||||||
|
}
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@ -110,10 +133,10 @@ const onCancel = () => {
|
|||||||
<Input :value="formState" name="topic">
|
<Input :value="formState" name="topic">
|
||||||
<template #topLeft>MQTT publish topic</template>
|
<template #topLeft>MQTT publish topic</template>
|
||||||
</Input>
|
</Input>
|
||||||
<Textarea :value="formState" name="notice" class="">
|
<Textarea :value="formState" name="publish_message">
|
||||||
<template #topLeft>MQTT 傳送的訊息</template>
|
<template #topLeft>MQTT messages</template>
|
||||||
</Textarea>
|
</Textarea>
|
||||||
<button type="button" class="btn btn-add mt-6 w-24" @click="openModal">
|
<button type="button" class="btn btn-add mt-6 w-24" @click="onSubmit">
|
||||||
<font-awesome-icon :icon="['far', 'paper-plane']" />
|
<font-awesome-icon :icon="['far', 'paper-plane']" />
|
||||||
Send
|
Send
|
||||||
</button>
|
</button>
|
||||||
|
@ -17,13 +17,13 @@ const store = useBuildingStore();
|
|||||||
const { items, changeActiveBtn, setItems, selectedBtn } = useActiveBtn();
|
const { items, changeActiveBtn, setItems, selectedBtn } = useActiveBtn();
|
||||||
let intervalId = null;
|
let intervalId = null;
|
||||||
const energyCostData = ref({});
|
const energyCostData = ref({});
|
||||||
|
const FILE_BASEURL = import.meta.env.VITE_FILE_API_BASEURL;
|
||||||
|
const imgBaseUrl = ref("");
|
||||||
const formState = ref({
|
const formState = ref({
|
||||||
building_guid: null,
|
building_guid: null,
|
||||||
floor_guid: "all",
|
floor_guid: "all",
|
||||||
department_id: "all",
|
department_id: "all",
|
||||||
});
|
});
|
||||||
// 控制顯示2D/3D切換與內容
|
|
||||||
const showForgeArea = ref(true);
|
|
||||||
|
|
||||||
const getEnergyCostData = async (params) => {
|
const getEnergyCostData = async (params) => {
|
||||||
const res = await getEnergyCost(params);
|
const res = await getEnergyCost(params);
|
||||||
@ -35,6 +35,11 @@ watch(
|
|||||||
(newBuilding) => {
|
(newBuilding) => {
|
||||||
if (newBuilding) {
|
if (newBuilding) {
|
||||||
formState.value.building_guid = newBuilding.building_guid;
|
formState.value.building_guid = newBuilding.building_guid;
|
||||||
|
imgBaseUrl.value = store.previewImageExt
|
||||||
|
? `${FILE_BASEURL}/upload/setting/previewImage/${newBuilding.building_guid}${store.previewImageExt}`
|
||||||
|
: import.meta.env.MODE === "production"
|
||||||
|
? "dist/build_img.jpg"
|
||||||
|
: "/build_img.jpg";
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{ immediate: true, deep: true }
|
{ immediate: true, deep: true }
|
||||||
@ -68,22 +73,26 @@ watch(
|
|||||||
{ immediate: true, deep: true }
|
{ immediate: true, deep: true }
|
||||||
);
|
);
|
||||||
|
|
||||||
onMounted(() => {
|
watch(
|
||||||
if (showForgeArea.value) {
|
() => store.showForgeArea,
|
||||||
setItems([
|
(newVal) => {
|
||||||
{
|
if (newVal == true) {
|
||||||
title: "2D",
|
setItems([
|
||||||
key: "2D",
|
{
|
||||||
active: false,
|
title: "2D",
|
||||||
},
|
key: "2D",
|
||||||
{
|
active: false,
|
||||||
title: "3D",
|
},
|
||||||
key: "3D",
|
{
|
||||||
active: true,
|
title: "3D",
|
||||||
},
|
key: "3D",
|
||||||
]);
|
active: true,
|
||||||
}
|
},
|
||||||
});
|
]);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
);
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
clearInterval(intervalId);
|
clearInterval(intervalId);
|
||||||
@ -94,7 +103,7 @@ onUnmounted(() => {
|
|||||||
<div class="flex flex-wrap items-center">
|
<div class="flex flex-wrap items-center">
|
||||||
<!-- 建築圖 -->
|
<!-- 建築圖 -->
|
||||||
<div class="w-full xl:w-1/3 relative">
|
<div class="w-full xl:w-1/3 relative">
|
||||||
<template v-if="showForgeArea">
|
<template v-if="store.showForgeArea">
|
||||||
<ButtonConnectedGroup
|
<ButtonConnectedGroup
|
||||||
:items="items"
|
:items="items"
|
||||||
className="btn-xs absolute right-3 top-6 z-20 bg-slate-800 p-0 rounded-lg "
|
className="btn-xs absolute right-3 top-6 z-20 bg-slate-800 p-0 rounded-lg "
|
||||||
@ -104,7 +113,7 @@ onUnmounted(() => {
|
|||||||
<!-- setting頁面要新增讓他能上傳圖片 -->
|
<!-- setting頁面要新增讓他能上傳圖片 -->
|
||||||
<img
|
<img
|
||||||
alt="build"
|
alt="build"
|
||||||
src="/build_img.jpg"
|
:src="imgBaseUrl"
|
||||||
:class="
|
:class="
|
||||||
twMerge(
|
twMerge(
|
||||||
'absolute w-full h-full transition-opacity duration-300',
|
'absolute w-full h-full transition-opacity duration-300',
|
||||||
@ -114,6 +123,7 @@ onUnmounted(() => {
|
|||||||
)
|
)
|
||||||
"
|
"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Forge
|
<Forge
|
||||||
:class="
|
:class="
|
||||||
twMerge(
|
twMerge(
|
||||||
@ -129,7 +139,7 @@ onUnmounted(() => {
|
|||||||
<template v-else>
|
<template v-else>
|
||||||
<img
|
<img
|
||||||
alt="build"
|
alt="build"
|
||||||
src="/build_img.jpg"
|
:src="imgBaseUrl || '/build_img.jpg'"
|
||||||
class="area-img-box w-full h-[460px] block relative rounded-sm mt-3"
|
class="area-img-box w-full h-[460px] block relative rounded-sm mt-3"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
@ -35,9 +35,23 @@ const columns = computed(() => {
|
|||||||
if (tableData.value && tableData.value.length > 0) {
|
if (tableData.value && tableData.value.length > 0) {
|
||||||
const firstDataItem = tableData.value[0];
|
const firstDataItem = tableData.value[0];
|
||||||
if (firstDataItem && firstDataItem.data) {
|
if (firstDataItem && firstDataItem.data) {
|
||||||
firstDataItem.data.forEach((item, index) => {
|
|
||||||
|
const sortedData = [...firstDataItem.data].sort((a, b) =>
|
||||||
|
dayjs(a.time).valueOf() - dayjs(b.time).valueOf()
|
||||||
|
);
|
||||||
|
|
||||||
|
sortedData.forEach((item, index) => {
|
||||||
let formatString = "MM/DD"; // 預設格式
|
let formatString = "MM/DD"; // 預設格式
|
||||||
switch (route.params.type) {
|
switch (route.params.type) {
|
||||||
|
case "1":
|
||||||
|
formatString = "MM/DD";
|
||||||
|
break;
|
||||||
|
case "2":
|
||||||
|
// 取得該週的開始與結束日期
|
||||||
|
const startOfWeek = dayjs(item.time).startOf('week');
|
||||||
|
const endOfWeek = dayjs(item.time).endOf('week');
|
||||||
|
formatString = `${startOfWeek.format("MM/DD")}-${endOfWeek.format("MM/DD")}`;
|
||||||
|
break;
|
||||||
case "3":
|
case "3":
|
||||||
formatString = "YYYY/MM";
|
formatString = "YYYY/MM";
|
||||||
break;
|
break;
|
||||||
@ -45,7 +59,7 @@ const columns = computed(() => {
|
|||||||
formatString = "YYYY";
|
formatString = "YYYY";
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
formatString = "MM/DD"; // case 1 和 case 2 都是 "MM-DD"
|
formatString = "MM/DD ";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
const formattedTime = dayjs(item.time).format(formatString);
|
const formattedTime = dayjs(item.time).format(formatString);
|
||||||
@ -73,8 +87,13 @@ const dataSource = computed(() => {
|
|||||||
return tableData.value.map((item) => {
|
return tableData.value.map((item) => {
|
||||||
let subtotalValue = 0;
|
let subtotalValue = 0;
|
||||||
const newData = {}; // 用於儲存 data 的值,方便 Table 組件讀取
|
const newData = {}; // 用於儲存 data 的值,方便 Table 組件讀取
|
||||||
if (item.data && item.data.length > 0) {
|
|
||||||
item.data.forEach((dataItem, index) => {
|
const sortedData = [...item.data].sort((a, b) =>
|
||||||
|
dayjs(a.time).valueOf() - dayjs(b.time).valueOf()
|
||||||
|
);
|
||||||
|
|
||||||
|
if (sortedData && sortedData.length > 0) {
|
||||||
|
sortedData.forEach((dataItem, index) => {
|
||||||
const value = parseFloat(dataItem.value || 0);
|
const value = parseFloat(dataItem.value || 0);
|
||||||
subtotalValue += value;
|
subtotalValue += value;
|
||||||
// 將值儲存在 newData 中,key 與 columns 中的 key 對應
|
// 將值儲存在 newData 中,key 與 columns 中的 key 對應
|
||||||
|
@ -113,7 +113,7 @@ watch(
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="flex justify-start items-center mt-10 mb-5">
|
<div class="flex justify-start items-center mt-10 mb-5">
|
||||||
<h3 class="text-xl mr-5">電表</h3>
|
<h3 class="text-xl mr-5">{{$t("setting.electricity_meter")}}</h3>
|
||||||
<button
|
<button
|
||||||
v-if="!isEditing"
|
v-if="!isEditing"
|
||||||
class="btn btn-sm btn-add mr-3"
|
class="btn btn-sm btn-add mr-3"
|
||||||
|
@ -93,6 +93,11 @@ provide("time_elec", { sim2, sim3, stand2, stand3, getData });
|
|||||||
<h1 class="text-2xl font-extrabold mb-2">
|
<h1 class="text-2xl font-extrabold mb-2">
|
||||||
{{ $t("energy.elec_price_list") }}
|
{{ $t("energy.elec_price_list") }}
|
||||||
</h1>
|
</h1>
|
||||||
|
<div class="content-box-background border-info border text-base p-4">
|
||||||
|
<span class="font-semibold text-info">說明:</span>
|
||||||
|
本系統使用「標準型時間電價二段式」來試算電費。<br />
|
||||||
|
本系統所提供之時間電價計算結果,係以未超過契約容量為前提所進行之估算,僅供用電分析與管理參考之用。
|
||||||
|
</div>
|
||||||
<ButtonGroup
|
<ButtonGroup
|
||||||
:items="items"
|
:items="items"
|
||||||
:withLine="true"
|
:withLine="true"
|
||||||
|
@ -95,6 +95,10 @@ const PointsColumns = computed(() => [
|
|||||||
title: t("setting.hide_point"),
|
title: t("setting.hide_point"),
|
||||||
key: "is_link",
|
key: "is_link",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: t("setting.hide_switch"),
|
||||||
|
key: "show_event_switch_btn",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: t("assetManagement.operation"),
|
title: t("assetManagement.operation"),
|
||||||
key: "operation",
|
key: "operation",
|
||||||
@ -256,6 +260,9 @@ watch(
|
|||||||
<template v-else-if="column.key === 'is_link'">
|
<template v-else-if="column.key === 'is_link'">
|
||||||
{{ record.is_link === 1 ? t("alert.yes") : t("alert.no") }}
|
{{ record.is_link === 1 ? t("alert.yes") : t("alert.no") }}
|
||||||
</template>
|
</template>
|
||||||
|
<template v-else-if="column.key === 'show_event_switch_btn'">
|
||||||
|
{{ record.show_event_switch_btn === true ? t("alert.yes") : t("alert.no") }}
|
||||||
|
</template>
|
||||||
<template v-else-if="column.key === 'operation'">
|
<template v-else-if="column.key === 'operation'">
|
||||||
<button
|
<button
|
||||||
class="btn btn-sm btn-success text-white mr-2"
|
class="btn btn-sm btn-success text-white mr-2"
|
||||||
|
@ -21,6 +21,9 @@ const formState = ref({
|
|||||||
decimals: 0,
|
decimals: 0,
|
||||||
is_bool: 0,
|
is_bool: 0,
|
||||||
is_link: 0,
|
is_link: 0,
|
||||||
|
show_event_switch_btn: false,
|
||||||
|
event_switch_on_message: "",
|
||||||
|
event_switch_off_message: "",
|
||||||
});
|
});
|
||||||
let schema = ref(
|
let schema = ref(
|
||||||
yup.object({
|
yup.object({
|
||||||
@ -42,10 +45,16 @@ const onOk = async () => {
|
|||||||
decimals: Number(values.decimals),
|
decimals: Number(values.decimals),
|
||||||
is_bool: Number(values.is_bool),
|
is_bool: Number(values.is_bool),
|
||||||
is_link: Number(values.is_link),
|
is_link: Number(values.is_link),
|
||||||
|
event_switch_on_message: values.show_event_switch_btn
|
||||||
|
? values.event_switch_on_message
|
||||||
|
: "",
|
||||||
|
event_switch_off_message: values.show_event_switch_btn
|
||||||
|
? values.event_switch_off_message
|
||||||
|
: "",
|
||||||
});
|
});
|
||||||
if (res.isSuccess) {
|
if (res.isSuccess) {
|
||||||
props.getData(props.variable_id);
|
|
||||||
onCancel();
|
onCancel();
|
||||||
|
props.getData(props.variable_id);
|
||||||
} else {
|
} else {
|
||||||
openToast("error", res.msg, "#point_list_item");
|
openToast("error", res.msg, "#point_list_item");
|
||||||
}
|
}
|
||||||
@ -154,31 +163,40 @@ watch(
|
|||||||
|
|
||||||
<RadioGroup
|
<RadioGroup
|
||||||
class="my-2"
|
class="my-2"
|
||||||
name="is_link"
|
name="show_event_switch_btn"
|
||||||
:value="formState"
|
:value="formState"
|
||||||
:items="[
|
:items="[
|
||||||
{
|
{
|
||||||
key: 1,
|
key: 1,
|
||||||
value: 1,
|
value: true,
|
||||||
title: '開啟',
|
title: $t('alert.yes'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 0,
|
key: 0,
|
||||||
value: 0,
|
value: false,
|
||||||
title: '關閉',
|
title: $t('alert.no'),
|
||||||
},
|
},
|
||||||
]"
|
]"
|
||||||
:required="true"
|
:required="true"
|
||||||
>
|
>
|
||||||
<template #topLeft>switch 顯示</template>
|
<template #topLeft>{{ $t("setting.hide_switch") }}</template>
|
||||||
</RadioGroup>
|
</RadioGroup>
|
||||||
<Textarea :value="formState" name="notice" class="w-full my-2">
|
<Textarea
|
||||||
<template #topLeft>switch 開啟時傳送的訊息</template>
|
v-if="formState.show_event_switch_btn"
|
||||||
|
:value="formState"
|
||||||
|
name="event_switch_on_message"
|
||||||
|
class="w-full my-2"
|
||||||
|
>
|
||||||
|
<template #topLeft>{{ $t("setting.switch_on_message") }}</template>
|
||||||
</Textarea>
|
</Textarea>
|
||||||
<Textarea :value="formState" name="notice" class="w-full my-2">
|
<Textarea
|
||||||
<template #topLeft>switch 關閉時傳送的訊息</template>
|
v-if="formState.show_event_switch_btn"
|
||||||
|
:value="formState"
|
||||||
|
name="event_switch_off_message"
|
||||||
|
class="w-full my-2"
|
||||||
|
>
|
||||||
|
<template #topLeft>{{ $t("setting.switch_off_message") }}</template>
|
||||||
</Textarea>
|
</Textarea>
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
</template>
|
</template>
|
||||||
<template #modalAction>
|
<template #modalAction>
|
||||||
|
@ -1,16 +1,66 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
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 { posttDashboard2D3D } from "@/apis/dashboard";
|
||||||
|
import useBuildingStore from "@/stores/useBuildingStore";
|
||||||
|
import { tr } from "date-fns/locale";
|
||||||
|
|
||||||
|
const { openToast, cancelToastOpen } = inject("app_toast");
|
||||||
|
const buildingStore = useBuildingStore();
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const FILE_BASEURL = import.meta.env.VITE_FILE_API_BASEURL;
|
const FILE_BASEURL = import.meta.env.VITE_FILE_API_BASEURL;
|
||||||
|
const form = ref(null);
|
||||||
|
|
||||||
const formState = ref({
|
const formState = ref({
|
||||||
lorf: [],
|
showForgeArea: buildingStore.showForgeArea,
|
||||||
|
lorf: buildingStore.previewImageExt
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
src: `${FILE_BASEURL}/upload/setting/previewImage/${buildingStore.selectedBuilding.building_guid}${buildingStore.previewImageExt}`,
|
||||||
|
name: `${buildingStore.selectedBuilding.building_guid}`,
|
||||||
|
ext: `${buildingStore.previewImageExt}`,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: [],
|
||||||
});
|
});
|
||||||
|
|
||||||
const updateFileList = (files) => {
|
const updateFileList = (files) => {
|
||||||
formState.value.lorf = files;
|
formState.value.lorf = files;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onShowForgeAreaChange = (e) => {
|
||||||
|
formState.value.showForgeArea = e.target.checked;
|
||||||
|
};
|
||||||
|
|
||||||
|
const onOk = async () => {
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.delete("file");
|
||||||
|
formData.append("buildingId", buildingStore.selectedBuilding.building_guid);
|
||||||
|
formData.append("is3DEnabled", formState.value.showForgeArea);
|
||||||
|
if (formState.value.lorf && formState.value.lorf.length > 0) {
|
||||||
|
formData.append("file", formState.value.lorf[0]);
|
||||||
|
}else {
|
||||||
|
formData.append("removePreviewImage", true);
|
||||||
|
}
|
||||||
|
const res = await posttDashboard2D3D(formData);
|
||||||
|
if (res.isSuccess) {
|
||||||
|
await buildingStore.fetchDashboard2D3D(
|
||||||
|
buildingStore.selectedBuilding.building_guid
|
||||||
|
);
|
||||||
|
openToast("success", t("msg.edit_successfully"));
|
||||||
|
// 更新本地狀態
|
||||||
|
formState.value.showForgeArea = buildingStore.showForgeArea;
|
||||||
|
formState.value.lorf = buildingStore.previewImageExt
|
||||||
|
? [ {
|
||||||
|
src: `${FILE_BASEURL}/upload/setting/previewImage/${buildingStore.selectedBuilding.building_guid}/${buildingStore.previewImageExt}`,
|
||||||
|
name: `${buildingStore.selectedBuilding.building_guid}`,
|
||||||
|
ext: `${buildingStore.previewImageExt}`,
|
||||||
|
},]
|
||||||
|
: [];
|
||||||
|
}else{
|
||||||
|
openToast("error", res.msg);
|
||||||
|
}
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@ -18,24 +68,28 @@ const updateFileList = (files) => {
|
|||||||
<h3 class="text-xl mr-5">2D / 3D 顯示設定 :</h3>
|
<h3 class="text-xl mr-5">2D / 3D 顯示設定 :</h3>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex gap-5">
|
<div class="flex gap-5">
|
||||||
<span class="text-lg">3D 模型顯示 : </span
|
<span class="text-lg">3D 模型顯示 : </span>
|
||||||
><input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
className="toggle toggle-lg toggle-success"
|
class="toggle toggle-lg toggle-success"
|
||||||
defaultChecked
|
name="showForgeArea"
|
||||||
|
:checked="formState.showForgeArea"
|
||||||
|
@change="onShowForgeAreaChange"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Upload
|
<Upload
|
||||||
class="my-2 max-w-[600px] w-full"
|
class="mt-2 mb-5 max-w-[600px] w-full"
|
||||||
name="oriFile"
|
name="oriFile"
|
||||||
:fileList="formState?.lorf"
|
:fileList="formState.lorf"
|
||||||
:getFileList="updateFileList"
|
:getFileList="updateFileList"
|
||||||
:multiple="true"
|
:multiple="false"
|
||||||
:baseUrl="`${FILE_BASEURL}/upload/operation`"
|
|
||||||
formats="png、jpg"
|
formats="png、jpg"
|
||||||
>
|
>
|
||||||
<template #topLeft>首頁2D圖上傳 :</template>
|
<template #topLeft>首頁2D圖上傳 :</template>
|
||||||
</Upload>
|
</Upload>
|
||||||
|
<button type="submit" class="btn btn-outline-success" @click.prevent="onOk">
|
||||||
|
{{ $t("button.submit") }}
|
||||||
|
</button>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="css" scoped></style>
|
<style lang="css" scoped></style>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
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, onUnmounted } from "vue";
|
||||||
import SystemFloorBar from "./components/SystemFloorBar.vue";
|
import SystemFloorBar from "./components/SystemFloorBar.vue";
|
||||||
import SystemDeptBar from "./components/SystemDeptBar.vue";
|
import SystemDeptBar from "./components/SystemDeptBar.vue";
|
||||||
import useBuildingStore from "@/stores/useBuildingStore";
|
import useBuildingStore from "@/stores/useBuildingStore";
|
||||||
@ -16,6 +16,7 @@ import SystemFloor from "./SystemFloor.vue";
|
|||||||
import { twMerge } from "tailwind-merge";
|
import { twMerge } from "tailwind-merge";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
|
|
||||||
|
const FILE_BASEURL = import.meta.env.VITE_FILE_API_BASEURL;
|
||||||
const buildingStore = useBuildingStore();
|
const buildingStore = useBuildingStore();
|
||||||
|
|
||||||
const statusList = computed(() => {
|
const statusList = computed(() => {
|
||||||
@ -41,7 +42,7 @@ const floors = ref([]);
|
|||||||
const deptData = ref([]);
|
const deptData = ref([]);
|
||||||
const companyOptions = ref([]);
|
const companyOptions = ref([]);
|
||||||
const selected_dbid = ref([]);
|
const selected_dbid = ref([]);
|
||||||
const showForgeArea = ref(true); // 控制2D/3D切換與內容顯示
|
const imgBaseUrl = ref("");
|
||||||
|
|
||||||
const getFloors = async () => {
|
const getFloors = async () => {
|
||||||
const res = await getAssetFloorList();
|
const res = await getAssetFloorList();
|
||||||
@ -129,6 +130,11 @@ watch(
|
|||||||
(newBuilding) => {
|
(newBuilding) => {
|
||||||
if (Boolean(newBuilding)) {
|
if (Boolean(newBuilding)) {
|
||||||
getData();
|
getData();
|
||||||
|
imgBaseUrl.value = buildingStore.previewImageExt
|
||||||
|
? `${FILE_BASEURL}/upload/setting/previewImage/${newBuilding.building_guid}${buildingStore.previewImageExt}`
|
||||||
|
: import.meta.env.MODE === "production"
|
||||||
|
? "dist/build_img.jpg"
|
||||||
|
: "/build_img.jpg";
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -171,7 +177,7 @@ const updateCurrentFloor = (floor) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const realtimeData = ref([]);
|
const realtimeData = ref([]);
|
||||||
const timeId = ref(null);
|
let timeId = null;
|
||||||
const getAllDeviceRealtime = async () => {
|
const getAllDeviceRealtime = async () => {
|
||||||
// 立即執行一次
|
// 立即執行一次
|
||||||
const fetchData = async () => {
|
const fetchData = async () => {
|
||||||
@ -182,15 +188,22 @@ const getAllDeviceRealtime = async () => {
|
|||||||
realtimeData.value = res.data;
|
realtimeData.value = res.data;
|
||||||
};
|
};
|
||||||
await fetchData(); // 立即執行一次
|
await fetchData(); // 立即執行一次
|
||||||
|
if (timeId) {
|
||||||
|
clearInterval(timeId);
|
||||||
|
timeId = null;
|
||||||
|
}
|
||||||
// 然後設定每 10 秒更新一次
|
// 然後設定每 10 秒更新一次
|
||||||
timeId.value = setInterval(fetchData, 10000);
|
timeId = setInterval(fetchData, 10 * 1000);
|
||||||
};
|
};
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
subscribeData,
|
subscribeData,
|
||||||
(newValue) => {
|
(newValue) => {
|
||||||
timeId.value && clearInterval(timeId.value);
|
console.log("subscribeData changed:", newValue);
|
||||||
|
|
||||||
|
if (timeId) {
|
||||||
|
clearInterval(timeId);
|
||||||
|
}
|
||||||
newValue.length > 0 && getAllDeviceRealtime();
|
newValue.length > 0 && getAllDeviceRealtime();
|
||||||
},
|
},
|
||||||
{ deep: true, immediate: true }
|
{ deep: true, immediate: true }
|
||||||
@ -271,8 +284,11 @@ provide("system_selectedDevice", {
|
|||||||
deptData,
|
deptData,
|
||||||
});
|
});
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
onUnmounted(() => {
|
||||||
clearInterval(timeId.value);
|
if (timeId) {
|
||||||
|
clearInterval(timeId);
|
||||||
|
timeId = null;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@ -327,7 +343,7 @@ onBeforeUnmount(() => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-span-1 h-full flex flex-col justify-between">
|
<div class="col-span-1 h-full flex flex-col justify-between">
|
||||||
<template v-if="showForgeArea">
|
<template v-if="buildingStore.showForgeArea">
|
||||||
<SystemMode />
|
<SystemMode />
|
||||||
<div class="h-full relative">
|
<div class="h-full relative">
|
||||||
<SystemFloor
|
<SystemFloor
|
||||||
@ -354,7 +370,7 @@ onBeforeUnmount(() => {
|
|||||||
<div class="h-full relative">
|
<div class="h-full relative">
|
||||||
<img
|
<img
|
||||||
alt="build"
|
alt="build"
|
||||||
src="/build_img.jpg"
|
:src="imgBaseUrl"
|
||||||
:class="
|
:class="
|
||||||
twMerge(
|
twMerge(
|
||||||
'absolute w-full h-full transition-opacity duration-300',
|
'absolute w-full h-full transition-opacity duration-300',
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { useRoute } from "vue-router";
|
import { useRoute } from "vue-router";
|
||||||
import EffectScatter from "@/components/chart/EffectScatter.vue";
|
import EffectScatter from "@/components/chart/EffectScatter.vue";
|
||||||
import { computed, inject, ref, watch } from "vue";
|
import { computed, inject, nextTick, ref, watch } from "vue";
|
||||||
import { twMerge } from "tailwind-merge";
|
import { twMerge } from "tailwind-merge";
|
||||||
import useSelectedFloor from "@/hooks/useSelectedFloor";
|
import useSelectedFloor from "@/hooks/useSelectedFloor";
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
|
|
||||||
const { currentFloor, subscribeData } = inject("system_deviceList");
|
const { currentFloor, subscribeData, realtimeData } = inject("system_deviceList");
|
||||||
const { getCurrentInfoModalData, selected_dbid } = inject(
|
const { getCurrentInfoModalData, selected_dbid } = inject(
|
||||||
"system_selectedDevice"
|
"system_selectedDevice"
|
||||||
);
|
);
|
||||||
@ -22,6 +22,19 @@ const sameOption = {
|
|||||||
tooltip: 2,
|
tooltip: 2,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 根據設備取得即時狀態顏色
|
||||||
|
const getDeviceRealtimeColor = (device) => {
|
||||||
|
if (device.full_name === 'SmartSocket-AA001') return 'red';
|
||||||
|
if (device.full_name === 'SmartSocket-AA003' || device.full_name === 'SmartSocket-AA004') return 'gray';
|
||||||
|
const realtimeDevice = realtimeData.value?.find(
|
||||||
|
(item) => item.device_number === device.device_number
|
||||||
|
);
|
||||||
|
const state = realtimeDevice?.state || '';
|
||||||
|
if (state === 'offnormal' || state === '') return device.device_close_color || '#999';
|
||||||
|
return device.device_normal_color || '#009100';
|
||||||
|
};
|
||||||
|
|
||||||
const defaultOption = (map, data = []) => {
|
const defaultOption = (map, data = []) => {
|
||||||
return {
|
return {
|
||||||
animation: false,
|
animation: false,
|
||||||
@ -38,13 +51,7 @@ const defaultOption = (map, data = []) => {
|
|||||||
...sameOption,
|
...sameOption,
|
||||||
symbolSize: 10,
|
symbolSize: 10,
|
||||||
itemStyle: {
|
itemStyle: {
|
||||||
color: (params) =>
|
color: (params) => getDeviceRealtimeColor(params.data[2]),
|
||||||
params.data[2].full_name === "SmartSocket-AA001"
|
|
||||||
? "red"
|
|
||||||
: params.data[2].full_name === "SmartSocket-AA003" ||
|
|
||||||
params.data[2].full_name === "SmartSocket-AA004"
|
|
||||||
? "gray"
|
|
||||||
: params.data[2].device_normal_color || "#009100",
|
|
||||||
},
|
},
|
||||||
data,
|
data,
|
||||||
},
|
},
|
||||||
@ -52,19 +59,33 @@ const defaultOption = (map, data = []) => {
|
|||||||
...sameOption,
|
...sameOption,
|
||||||
symbolSize: 20,
|
symbolSize: 20,
|
||||||
itemStyle: {
|
itemStyle: {
|
||||||
color: (params) =>
|
color: (params) => getDeviceRealtimeColor(params.data[2]),
|
||||||
params.data[2].full_name === "SmartSocket-AA001"
|
|
||||||
? "red"
|
|
||||||
: params.data[2].full_name === "SmartSocket-AA003" ||
|
|
||||||
params.data[2].full_name === "SmartSocket-AA004"
|
|
||||||
? "gray"
|
|
||||||
: params.data[2].device_normal_color || "#009100",
|
|
||||||
},
|
},
|
||||||
data: [],
|
data: [],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
// 監聽 realtimeData 變化,刷新地圖顏色
|
||||||
|
watch(
|
||||||
|
realtimeData,
|
||||||
|
() => {
|
||||||
|
setTimeout(() => {
|
||||||
|
if (
|
||||||
|
selectedFloor.value &&
|
||||||
|
asset_floor_chart.value &&
|
||||||
|
asset_floor_chart.value.chart &&
|
||||||
|
asset_floor_chart.value.chart.isDisposed() === false
|
||||||
|
) {
|
||||||
|
asset_floor_chart.value.chart.setOption(
|
||||||
|
defaultOption(selectedFloor.value?.title, selectedData.value),
|
||||||
|
true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}, 0);
|
||||||
|
},
|
||||||
|
{ deep: true }
|
||||||
|
);
|
||||||
|
|
||||||
const { selectedFloor } = useSelectedFloor();
|
const { selectedFloor } = useSelectedFloor();
|
||||||
|
|
||||||
@ -130,18 +151,23 @@ watch(
|
|||||||
if (
|
if (
|
||||||
selectedFloor.value &&
|
selectedFloor.value &&
|
||||||
asset_floor_chart.value &&
|
asset_floor_chart.value &&
|
||||||
asset_floor_chart.value.chart
|
asset_floor_chart.value.chart &&
|
||||||
|
asset_floor_chart.value.chart.isDisposed() === false
|
||||||
) {
|
) {
|
||||||
const selected = allData.value.filter((d) => d[2].is2DActive);
|
const selected = allData.value.filter((d) => d[2].is2DActive);
|
||||||
const unSelected = allData.value.filter((d) => !d[2].is2DActive);
|
const unSelected = allData.value.filter((d) => !d[2].is2DActive);
|
||||||
asset_floor_chart.value.chart.setOption({
|
setTimeout(() => {
|
||||||
series: [
|
if (asset_floor_chart.value?.chart && !asset_floor_chart.value.chart.isDisposed()) {
|
||||||
{ data: unSelected },
|
asset_floor_chart.value.chart.setOption({
|
||||||
{
|
series: [
|
||||||
data: selected,
|
{ data: unSelected },
|
||||||
},
|
{
|
||||||
],
|
data: selected,
|
||||||
});
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, 0);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -168,14 +194,18 @@ watch(
|
|||||||
const unSelected = selectedData.value.filter((d) => !d[2].is2DActive);
|
const unSelected = selectedData.value.filter((d) => !d[2].is2DActive);
|
||||||
|
|
||||||
console.log("allData.value", allData.value, selected, unSelected);
|
console.log("allData.value", allData.value, selected, unSelected);
|
||||||
asset_floor_chart.value.chart.setOption({
|
setTimeout(() => {
|
||||||
series: [
|
if (asset_floor_chart.value?.chart && !asset_floor_chart.value.chart.isDisposed()) {
|
||||||
{ data: unSelected },
|
asset_floor_chart.value.chart.setOption({
|
||||||
{
|
series: [
|
||||||
data: selected,
|
{ data: unSelected },
|
||||||
},
|
{
|
||||||
],
|
data: selected,
|
||||||
});
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, 0);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -5,11 +5,40 @@ import useSystemShowData from "@/hooks/useSystemShowData";
|
|||||||
const { getCurrentInfoModalData, selected_dbid } = inject(
|
const { getCurrentInfoModalData, selected_dbid } = inject(
|
||||||
"system_selectedDevice"
|
"system_selectedDevice"
|
||||||
);
|
);
|
||||||
const { subscribeData } = inject("system_deviceList");
|
const { subscribeData, realtimeData } = inject("system_deviceList");
|
||||||
|
|
||||||
const { showData } = useSystemShowData();
|
const { showData } = useSystemShowData();
|
||||||
|
|
||||||
const FILE_BASEURL = import.meta.env.VITE_FILE_API_BASEURL;
|
const FILE_BASEURL = import.meta.env.VITE_FILE_API_BASEURL;
|
||||||
|
|
||||||
|
|
||||||
|
// 根據設備編號取得即時狀態
|
||||||
|
const getDeviceRealtimeState = (deviceNumber) => {
|
||||||
|
const realtimeDevice = realtimeData.value?.find(
|
||||||
|
(item) => item.device_number === deviceNumber
|
||||||
|
);
|
||||||
|
return realtimeDevice?.state || '';
|
||||||
|
};
|
||||||
|
|
||||||
|
// 狀態顏色
|
||||||
|
const getDeviceStatusColor = (device) => {
|
||||||
|
if (device.full_name === 'SmartSocket-AA001') return 'red';
|
||||||
|
if (device.full_name === 'SmartSocket-AA003' || device.full_name === 'SmartSocket-AA004') return 'gray';
|
||||||
|
const state = getDeviceRealtimeState(device.device_number);
|
||||||
|
if (state === 'offnormal' || state === '') return device.device_close_color || 'gray';
|
||||||
|
return device.device_normal_color;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 狀態文字
|
||||||
|
const getDeviceStatusText = (device) => {
|
||||||
|
if (device.full_name === 'SmartSocket-AA001') return 'Error';
|
||||||
|
if (device.full_name === 'SmartSocket-AA003' || device.full_name === 'SmartSocket-AA004') return 'Offline';
|
||||||
|
const state = getDeviceRealtimeState(device.device_number);
|
||||||
|
if (state === 'offnormal' || state === '') return 'Offline';
|
||||||
|
if (state === 'normal') return 'Online';
|
||||||
|
return state || device.device_status || 'Online';
|
||||||
|
};
|
||||||
|
|
||||||
const fitToView = (forge_dbid, spriteDbId) => {
|
const fitToView = (forge_dbid, spriteDbId) => {
|
||||||
selected_dbid.value = [forge_dbid, spriteDbId];
|
selected_dbid.value = [forge_dbid, spriteDbId];
|
||||||
};
|
};
|
||||||
@ -72,25 +101,10 @@ const cancelDialog = () => {
|
|||||||
<div class="sec03">
|
<div class="sec03">
|
||||||
<span
|
<span
|
||||||
class="w-5 h-5 rounded-full"
|
class="w-5 h-5 rounded-full"
|
||||||
:style="{
|
:style="{ backgroundColor: getDeviceStatusColor(device) }"
|
||||||
backgroundColor:
|
|
||||||
device.full_name === 'SmartSocket-AA001'
|
|
||||||
? 'red'
|
|
||||||
: device.full_name === 'SmartSocket-AA003' ||
|
|
||||||
device.full_name === 'SmartSocket-AA004'
|
|
||||||
? 'gray'
|
|
||||||
: device.device_normal_color,
|
|
||||||
}"
|
|
||||||
></span>
|
></span>
|
||||||
<span class="mx-2">{{ $t("system.status") }}:</span>
|
<span class="mx-2">{{ $t("system.status") }}:</span>
|
||||||
<span>{{
|
<span>{{ getDeviceStatusText(device) }}</span>
|
||||||
device.full_name === "SmartSocket-AA001"
|
|
||||||
? "Error"
|
|
||||||
: device.full_name === "SmartSocket-AA003" ||
|
|
||||||
device.full_name === "SmartSocket-AA004"
|
|
||||||
? "Offline"
|
|
||||||
: device.device_status || 'Online'
|
|
||||||
}}</span>
|
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
class="btn-text border-0"
|
class="btn-text border-0"
|
||||||
|
Loading…
Reference in New Issue
Block a user