MQTT位置路徑修改 | CviBuilding存進localStorage | 樓層與部門存到狀態裡 | 首頁、電價表語言包 |能源管理新增棟別、樓層、部門參數 | 歷史資料新增日期區間 | 樓層頁面deBug | 圖資deBug | MQTT頁面 | 系統監控: 新增棟別樓層部門 以及 spriteDbId的deBug | 電表設定

This commit is contained in:
koko 2025-03-13 16:17:09 +08:00
parent f956402648
commit 8b85e2d67c
47 changed files with 1605 additions and 714 deletions

View File

@ -1,4 +1,4 @@
VITE_API_BASEURL = "https://ibms-cvilux-api.production.mjmtech.com.tw" VITE_API_BASEURL = "https://ibms-cvilux-api.production.mjmtech.com.tw"
VITE_FILE_API_BASEURL = "https://cgems.cvilux-group.com:8088" VITE_FILE_API_BASEURL = "https://cgems.cvilux-group.com:8088"
VITE_MQTT_BASEURL = "ws://192.168.0.217:8083/mqtt" VITE_MQTT_BASEURL = "wss://mqttwss.mjm-staging.developers-homelab.net"
VITE_FORGE_BASEURL = "https://cgems.cvilux-group.com:8088/dist" VITE_FORGE_BASEURL = "https://cgems.cvilux-group.com:8088/dist"

View File

@ -1,4 +1,4 @@
VITE_API_BASEURL = "https://ibms-cvilux-api.production.mjmtech.com.tw" VITE_API_BASEURL = "https://ibms-cvilux-api.production.mjmtech.com.tw"
VITE_FILE_API_BASEURL = "https://cgems.cvilux-group.com:8088" VITE_FILE_API_BASEURL = "https://cgems.cvilux-group.com:8088"
VITE_MQTT_BASEURL = "wss://192.168.0.217:8084/mqtt" VITE_MQTT_BASEURL = "wss://mqttwss.mjm-staging.developers-homelab.net"
VITE_FORGE_BASEURL = "https://cgems.cvilux-group.com:8088/dist" VITE_FORGE_BASEURL = "https://cgems.cvilux-group.com:8088/dist"

View File

@ -20,6 +20,11 @@ export const GET_ASSET_IOT_LIST_API = `/AssetManage/GetIOTList`;
export const GET_ASSET_SUB_POINT_API = `/AssetManage/GetSubPoint`; export const GET_ASSET_SUB_POINT_API = `/AssetManage/GetSubPoint`;
export const GET_ASSET_IOT_SCHEMA_API = `/AssetManage/GetResponseSchema`; export const GET_ASSET_IOT_SCHEMA_API = `/AssetManage/GetResponseSchema`;
export const POST_ASSET_IOT_SCHEMA_API = `/AssetManage/SaveResponseSchema`;
export const GET_ASSET_DEVICE_ITEM_API = `/AssetManage/GetDeviceItem`;
export const POST_ASSET_DEVICE_ITEM_API = `/AssetManage/SaveDeviceItem`;
export const DELETE_ASSET_DEVICE_ITEM_API = `/AssetManage/DeleteDeviceItem`;
export const GET_ASSET_DEPARTMENT_API = `/AssetManage/GetDepartment`; export const GET_ASSET_DEPARTMENT_API = `/AssetManage/GetDepartment`;
export const POST_ASSET_DEPARTMENT_API = `/AssetManage/SaveDepartment`; export const POST_ASSET_DEPARTMENT_API = `/AssetManage/SaveDepartment`;
@ -28,3 +33,5 @@ export const DELETE_ASSET_DEPARTMENT_API = `/AssetManage/DeleteDepartment`;
export const GET_ASSET_ELECTYPE_API = `/AssetManage/GetElecType`; 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`;

View File

@ -15,12 +15,17 @@ import {
POST_ASSET_SINGLE_API, POST_ASSET_SINGLE_API,
GET_ASSET_SUB_POINT_API, GET_ASSET_SUB_POINT_API,
GET_ASSET_IOT_SCHEMA_API, GET_ASSET_IOT_SCHEMA_API,
POST_ASSET_IOT_SCHEMA_API,
GET_ASSET_DEVICE_ITEM_API,
POST_ASSET_DEVICE_ITEM_API,
DELETE_ASSET_DEVICE_ITEM_API,
GET_ASSET_DEPARTMENT_API, GET_ASSET_DEPARTMENT_API,
POST_ASSET_DEPARTMENT_API, POST_ASSET_DEPARTMENT_API,
DELETE_ASSET_DEPARTMENT_API, DELETE_ASSET_DEPARTMENT_API,
GET_ASSET_ELECTYPE_API, GET_ASSET_ELECTYPE_API,
POST_ASSET_ELECTYPE_API, POST_ASSET_ELECTYPE_API,
DELETE_ASSET_ELECTYPE_API DELETE_ASSET_ELECTYPE_API,
POST_ASSET_ELEC_SETTING_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";
@ -156,8 +161,8 @@ export const deleteAssetItem = async (main_id) => {
}); });
}; };
export const getAssetFloorList = async () => { export const getAssetFloorList = async (building_guid) => {
const res = await instance.post(GET_ASSET_FLOOR_LIST_API); const res = await instance.post(GET_ASSET_FLOOR_LIST_API, { building_guid });
return apihandler(res.code, res.data, { return apihandler(res.code, res.data, {
msg: res.msg, msg: res.msg,
@ -207,7 +212,63 @@ export const getAssetSubPoint = async (sub_system_tag) => {
}; };
export const getIOTSchema = async (variable_id) => { export const getIOTSchema = async (variable_id) => {
const res = await instance.post(GET_ASSET_IOT_SCHEMA_API, {variable_id}); const res = await instance.post(GET_ASSET_IOT_SCHEMA_API, { variable_id });
return apihandler(res.code, res.data, {
msg: res.msg,
code: res.code,
});
};
export const postIOTSchema = async ({ name, variable_id, points }) => {
const res = await instance.post(POST_ASSET_IOT_SCHEMA_API, {
name,
variable_id,
points,
});
return apihandler(res.code, res.data, {
msg: res.msg,
code: res.code,
});
};
export const getDeviceItem = async (variable_id) => {
const res = await instance.post(GET_ASSET_DEVICE_ITEM_API, { variable_id });
return apihandler(res.code, res.data, {
msg: res.msg,
code: res.code,
});
};
export const postDeviceItem = async ({
id,
variable_id,
full_name,
points,
decimals,
is_bool,
is_link,
}) => {
const res = await instance.post(POST_ASSET_DEVICE_ITEM_API, {
id,
variable_id,
full_name,
points,
decimals,
is_bool,
is_link,
});
return apihandler(res.code, res.data, {
msg: res.msg,
code: res.code,
});
};
export const deleteDeviceItem = async (id) => {
const res = await instance.post(DELETE_ASSET_DEVICE_ITEM_API, { id });
return apihandler(res.code, res.data, { return apihandler(res.code, res.data, {
msg: res.msg, msg: res.msg,
@ -273,4 +334,13 @@ export const deleteElecTypeItem = async (id) => {
msg: res.msg, msg: res.msg,
code: res.code, code: res.code,
}); });
}; };
export const postAssetElecSetting = async (formData) => {
const res = await instance.post(POST_ASSET_ELEC_SETTING_API, formData);
return apihandler(res.code, res.data, {
msg: res.msg,
code: res.code,
});
};

View File

@ -11,14 +11,22 @@ import {
GET_CARBON_API, GET_CARBON_API,
POST_EDIT_CARBON_API, POST_EDIT_CARBON_API,
GET_TIME_ELEC_API, GET_TIME_ELEC_API,
POST_TIME_ELEC_API POST_TIME_ELEC_API,
} from "./api"; } from "./api";
import instance, { fileInstance } from "@/util/request"; import instance, { fileInstance } from "@/util/request";
import apihandler from "@/util/apihandler"; import apihandler from "@/util/apihandler";
import downloadExcel from "@/util/downloadExcel"; import downloadExcel from "@/util/downloadExcel";
export const getRealTimeDist = async () => { export const getRealTimeDist = async ({
const res = await instance.post(GET_REALTIME_DIST_API); building_guid,
department_id_list,
floor_guid_list,
}) => {
const res = await instance.post(GET_REALTIME_DIST_API, {
building_guid,
department_id_list,
floor_guid_list,
});
return apihandler(res.code, res.data, { return apihandler(res.code, res.data, {
msg: res.msg, msg: res.msg,
@ -26,8 +34,16 @@ export const getRealTimeDist = async () => {
}); });
}; };
export const getElecUseDay = async () => { export const getElecUseDay = async ({
const res = await instance.post(GET_ELECUSE_DAY_API); building_guid,
department_id_list,
floor_guid_list,
}) => {
const res = await instance.post(GET_ELECUSE_DAY_API,{
building_guid,
department_id_list,
floor_guid_list,
});
return apihandler(res.code, res.data, { return apihandler(res.code, res.data, {
msg: res.msg, msg: res.msg,
@ -35,8 +51,18 @@ export const getElecUseDay = async () => {
}); });
}; };
export const getTaipower = async () => { export const getTaipower = async ({
const res = await instance.post(GET_TAI_POWER_API); coefficient,
building_guid,
department_id_list,
floor_guid_list,
}) => {
const res = await instance.post(GET_TAI_POWER_API, {
coefficient,
building_guid,
department_id_list,
floor_guid_list,
});
return apihandler(res.code, res.data, { return apihandler(res.code, res.data, {
msg: res.msg, msg: res.msg,
@ -118,7 +144,7 @@ export const getExcel = async ({
}; };
export const getDemand = async (building_guid) => { export const getDemand = async (building_guid) => {
const res = await instance.post(GET_DEMAND_API, {building_guid}); const res = await instance.post(GET_DEMAND_API, { building_guid });
return apihandler(res.code, res.data, { return apihandler(res.code, res.data, {
msg: res.msg, msg: res.msg,
@ -126,13 +152,19 @@ export const getDemand = async (building_guid) => {
}); });
}; };
export const postEditDemand = async ({ id, contract, alert, reset, building_guid }) => { export const postEditDemand = async ({
id,
contract,
alert,
reset,
building_guid,
}) => {
const res = await instance.put(POST_EDIT_DEMAND_API, { const res = await instance.put(POST_EDIT_DEMAND_API, {
id, id,
contract, contract,
alert, alert,
reset, reset,
building_guid building_guid,
}); });
return apihandler(res.code, res.data, { return apihandler(res.code, res.data, {
@ -142,7 +174,7 @@ export const postEditDemand = async ({ id, contract, alert, reset, building_guid
}; };
export const getCarbonValue = async (building_guid) => { export const getCarbonValue = async (building_guid) => {
const res = await instance.post(GET_CARBON_API, {building_guid}); const res = await instance.post(GET_CARBON_API, { building_guid });
return apihandler(res.code, res.data, { return apihandler(res.code, res.data, {
msg: res.msg, msg: res.msg,
@ -150,11 +182,15 @@ export const getCarbonValue = async (building_guid) => {
}); });
}; };
export const postEditCarbonValue = async ({ id, coefficient, building_guid }) => { export const postEditCarbonValue = async ({
id,
coefficient,
building_guid,
}) => {
const res = await instance.put(POST_EDIT_CARBON_API, { const res = await instance.put(POST_EDIT_CARBON_API, {
id, id,
coefficient, coefficient,
building_guid building_guid,
}); });
return apihandler(res.code, res.data, { return apihandler(res.code, res.data, {
@ -164,7 +200,7 @@ export const postEditCarbonValue = async ({ id, coefficient, building_guid }) =>
}; };
export const getTimeElec = async (building_guid) => { export const getTimeElec = async (building_guid) => {
const res = await instance.post(GET_TIME_ELEC_API, {building_guid}); const res = await instance.post(GET_TIME_ELEC_API, { building_guid });
return apihandler(res.code, res.data, { return apihandler(res.code, res.data, {
msg: res.msg, msg: res.msg,
@ -174,9 +210,9 @@ export const getTimeElec = async (building_guid) => {
export const postTimeElec = async ({ sheet, cost, building_guid }) => { export const postTimeElec = async ({ sheet, cost, building_guid }) => {
const res = await instance.put(POST_TIME_ELEC_API, { const res = await instance.put(POST_TIME_ELEC_API, {
sheet, sheet,
cost, cost,
building_guid building_guid,
}); });
return apihandler(res.code, res.data, { return apihandler(res.code, res.data, {

View File

@ -16,11 +16,13 @@ export const getHistorySideBar = async ({
sub_system_tag, sub_system_tag,
department_id, department_id,
elec_type_id, elec_type_id,
building_guid,
}) => { }) => {
const res = await instance.post(GET_HISTORY_SIDEBAR_API, { const res = await instance.post(GET_HISTORY_SIDEBAR_API, {
sub_system_tag, sub_system_tag,
department_id, department_id,
elec_type_id, elec_type_id,
building_guid,
}); });
return apihandler(res.code, res.data, { return apihandler(res.code, res.data, {

View File

@ -6,6 +6,7 @@ const store = useBuildingStore();
const selectBuilding = (bui) => { const selectBuilding = (bui) => {
store.selectedBuilding = bui; // selectedBuildingwatch store.selectedBuilding = bui; // selectedBuildingwatch
localStorage.setItem("CviBuilding", JSON.stringify(bui));
}; };
onMounted(() => { onMounted(() => {

View File

@ -27,7 +27,24 @@
"lastweek_electricity_consumption": "上周用电量", "lastweek_electricity_consumption": "上周用电量",
"one_hour": "1小时", "one_hour": "1小时",
"four_hour": "4小时", "four_hour": "4小时",
"eight_hour": "8小时" "eight_hour": "8小时",
"energy_ranking": "能耗排行",
"last_30_days_energy_trend": "近30天能耗趋势",
"today_energy_consumption": "本日能耗",
"this_month_energy_consumption": "本月能耗",
"relative_energy_consumption": "环比能耗",
"daily_relative_change": "日环比",
"weekly_relative_change": "周环比",
"monthly_relative_change": "月环比",
"yearly_relative_change": "年环比",
"today": "今日",
"yesterday": "昨日",
"this_week": "本周",
"last_week": "上周",
"this_month": "本月",
"last_month": "上月",
"this_year": "今年",
"last_year": "去年"
}, },
"history": { "history": {
"title": "历史资料", "title": "历史资料",
@ -358,12 +375,29 @@
"confirm": "确认", "confirm": "确认",
"restore": "复原", "restore": "复原",
"stop_edit": "停止修改", "stop_edit": "停止修改",
"start_edit": "开始修改" "start_edit": "开始修改",
"convert": "轉換"
}, },
"msg": { "msg": {
"sure_to_delete": "是否确认删除该项目?", "sure_to_delete": "是否确认删除该项目?",
"sure_to_delete_permanent": "是否确认永久删除该项目?", "sure_to_delete_permanent": "是否确认永久删除该项目?",
"delete_success": "删除成功", "delete_success": "删除成功",
"delete_failed": "删除失败" "delete_failed": "删除失败"
},
"setting": {
"MQTT_parse": "MQTT 解析",
"schema": "架构",
"point": "点位",
"description": "描述",
"IoT_point_name": "IoT 点位名称",
"IoT_point_code": "IoT 点位代号",
"number_of_decimal_places": "小数位数",
"boolean_value": "布林值",
"hide_point": "点位显示",
"schema_name": "架构名称",
"IoT_point_structure": "IoT点位结构",
"system_point_name": "系统点位名称",
"json_format_text": "请贴上 JSON 格式数据",
"json_click_text": "请在左侧输入JSON并点选转换按钮"
} }
} }

View File

@ -27,7 +27,24 @@
"lastweek_electricity_consumption": "上週用電量", "lastweek_electricity_consumption": "上週用電量",
"one_hour": "1小時", "one_hour": "1小時",
"four_hour": "4小時", "four_hour": "4小時",
"eight_hour": "8小時" "eight_hour": "8小時",
"energy_ranking": "能耗排行",
"last_30_days_energy_trend": "近30天能耗趨勢",
"today_energy_consumption": "本日能耗",
"this_month_energy_consumption": "本月能耗",
"relative_energy_consumption": "環比能耗",
"daily_relative_change": "日環比",
"weekly_relative_change": "周環比",
"monthly_relative_change": "月環比",
"yearly_relative_change": "年環比",
"today": "今日",
"yesterday": "昨日",
"this_week": "本周",
"last_week": "上周",
"this_month": "本月",
"last_month": "上月",
"this_year": "今年",
"last_year": "去年"
}, },
"history": { "history": {
"title": "歷史資料", "title": "歷史資料",
@ -102,12 +119,12 @@
"elec_price_list": "電價表", "elec_price_list": "電價表",
"residential": "住宅型", "residential": "住宅型",
"standard": "標準型", "standard": "標準型",
"simple_elec_price_two_stage":"簡易型時間電價二段式", "simple_elec_price_two_stage": "簡易型時間電價二段式",
"simple_elec_price_three_stage":"簡易型時間電價三段式", "simple_elec_price_three_stage": "簡易型時間電價三段式",
"classification":"分類", "classification": "分類",
"summer_months":"夏月", "summer_months": "夏月",
"non_summer_months":"非夏月", "non_summer_months": "非夏月",
"time_outside_summer_months":"夏月以外的時間", "time_outside_summer_months": "夏月以外的時間",
"basic_elec_charge": "基本電費", "basic_elec_charge": "基本電費",
"charged_per_household": "按戶計收", "charged_per_household": "按戶計收",
"per_household_month": "每戶每月", "per_household_month": "每戶每月",
@ -358,12 +375,29 @@
"confirm": "確認", "confirm": "確認",
"restore": "復原", "restore": "復原",
"stop_edit": "停止修改", "stop_edit": "停止修改",
"start_edit": "開始修改" "start_edit": "開始修改",
"convert":"轉換"
}, },
"msg": { "msg": {
"sure_to_delete": "是否確認刪除該項目?", "sure_to_delete": "是否確認刪除該項目?",
"sure_to_delete_permanent": "是否確認永久刪除該項目?", "sure_to_delete_permanent": "是否確認永久刪除該項目?",
"delete_success": "刪除成功", "delete_success": "刪除成功",
"delete_failed": "刪除失敗" "delete_failed": "刪除失敗"
},
"setting": {
"MQTT_parse": "MQTT 解析",
"schema":"架構",
"point":"點位",
"description":"描述",
"IoT_point_name":"IoT 點位名稱",
"IoT_point_code":"IoT 點位代號",
"number_of_decimal_places":"小數位數",
"boolean_value":"布林值",
"hide_point":"點位顯示",
"schema_name":"架構名稱",
"IoT_point_structure" :"IoT點位結構",
"system_point_name":"系統點位名稱",
"json_format_text": "請貼上 JSON 格式數據",
"json_click_text": "請在左側輸入JSON並點選轉換按鈕"
} }
} }

View File

@ -15,6 +15,37 @@
"description": "File size cannot exceed 10MB", "description": "File size cannot exceed 10MB",
"formats": "File formats" "formats": "File formats"
}, },
"dashboard": {
"yesterday_today": "Yesterday / Today's",
"elec_consumption_comparison": "Electricity Consumption Comparison",
"elec_consumption_comparison_trend": "Electricity Consumption Comparison Trend",
"electricity_consumption": "electricity consumption",
"today_electricity_consumption": "Todays electricity consumption",
"yesterday_electricity_consumption": "Yesterdays electricity consumption",
"this_last_week": "This Week's / Last Week's",
"thisweek_electricity_consumption": "This weeks electricity consumption",
"lastweek_electricity_consumption": "Last weeks electricity consumption",
"one_hour": "1 hour",
"four_hour": "4 hour",
"eight_hour": "8 hour",
"energy_ranking": "Energy consumption ranking",
"last_30_days_energy_trend": "Energy consumption trend for the past 30 days",
"today_energy_consumption": "Today",
"this_month_energy_consumption": "This month",
"relative_energy_consumption": "Energy consumption trend",
"daily_relative_change": "Daily",
"weekly_relative_change": "Weekly",
"monthly_relative_change": "Monthly",
"yearly_relative_change": "Yearly",
"today": "Today",
"yesterday": "Yesterday",
"this_week": "This week",
"last_week": "Last week",
"this_month": "This month",
"last_month": "Last month",
"this_year": "This year",
"last_year": "Last year"
},
"history": { "history": {
"title": "Historical Data", "title": "Historical Data",
"building_name": "Building", "building_name": "Building",
@ -33,20 +64,6 @@
"end_date": "End date", "end_date": "End date",
"end_time": "End time" "end_time": "End time"
}, },
"dashboard": {
"yesterday_today": "Yesterday / Today's",
"elec_consumption_comparison": "Electricity Consumption Comparison",
"elec_consumption_comparison_trend": "Electricity Consumption Comparison Trend",
"electricity_consumption": "electricity consumption",
"today_electricity_consumption": "Todays electricity consumption",
"yesterday_electricity_consumption": "Yesterdays electricity consumption",
"this_last_week": "This Week's / Last Week's",
"thisweek_electricity_consumption": "This weeks electricity consumption",
"lastweek_electricity_consumption": "Last weeks electricity consumption",
"one_hour": "1 hour",
"four_hour": "4 hour",
"eight_hour": "8 hour"
},
"system": { "system": {
"status": "Status", "status": "Status",
"details": "Details", "details": "Details",
@ -358,12 +375,29 @@
"confirm": "Confirm", "confirm": "Confirm",
"restore": "Restore", "restore": "Restore",
"stop_edit": "Stop editing", "stop_edit": "Stop editing",
"start_edit": "Start editing" "start_edit": "Start editing",
"convert": "Convert"
}, },
"msg": { "msg": {
"sure_to_delete": "Are you sure to delete this item?", "sure_to_delete": "Are you sure to delete this item?",
"sure_to_delete_permanent": "Are you sure you want to permanently delete this item?", "sure_to_delete_permanent": "Are you sure you want to permanently delete this item?",
"delete_success": "Delete successfully", "delete_success": "Delete successfully",
"delete_failed": "Delete failed" "delete_failed": "Delete failed"
},
"setting": {
"MQTT_parse": "MQTT Parse",
"schema": "Schema",
"point": "Point",
"description": "Description",
"IoT_point_name": "IoT Point Name",
"IoT_point_code": "IoT Point Code",
"number_of_decimal_places": "Number of Decimal Places",
"boolean_value": "Boolean Value",
"hide_point": "Point Display",
"schema_name": "Schema name",
"IoT_point_structure": "IoT Point Structure",
"system_point_name": "System Point Name",
"json_format_text": "Please paste JSON format data",
"json_click_text": "Please enter JSON on the left and click the conversion button"
} }
} }

View File

@ -61,8 +61,11 @@ import {
faDownload, faDownload,
faStream, faStream,
faSave, faSave,
faCrown faCrown,
faClock,
faCheckCircle
} from "@fortawesome/free-solid-svg-icons"; } from "@fortawesome/free-solid-svg-icons";
import { faCircle } from "@fortawesome/free-regular-svg-icons";
/* add icons to the library */ /* add icons to the library */
library.add( library.add(
@ -124,7 +127,10 @@ library.add(
faDownload, faDownload,
faStream, faStream,
faSave, faSave,
faCrown faCrown,
faClock,
faCheckCircle,
faCircle
); );
export default library; export default library;

View File

@ -48,13 +48,14 @@ const useBuildingStore = defineStore("buildingInfo", () => {
const res = await getBuildings(); const res = await getBuildings();
buildings.value = res.data; buildings.value = res.data;
if (res.data.length > 0 && !selectedBuilding.value) { if (res.data.length > 0 && !selectedBuilding.value) {
selectedBuilding.value = res.data[0]; // 預設選第一個建築 const storedBuilding = JSON.parse(localStorage.getItem("CviBuilding"));
selectedBuilding.value = storedBuilding || res.data[0]; // 預設選第一個建築
} }
}; };
// 獲取樓層資料 // 獲取樓層資料
const fetchFloorList = async () => { const fetchFloorList = async (building_guid) => {
const res = await getAssetFloorList(); const res = await getAssetFloorList(building_guid);
floorList.value = res.data[0]?.floors.map((d) => ({ floorList.value = res.data[0]?.floors.map((d) => ({
...d, ...d,
title: d.full_name, title: d.full_name,
@ -75,7 +76,7 @@ const useBuildingStore = defineStore("buildingInfo", () => {
// 當 selectedBuilding 改變時,更新 floorList 和 deptList // 當 selectedBuilding 改變時,更新 floorList 和 deptList
watch(selectedBuilding, async (newBuilding) => { watch(selectedBuilding, async (newBuilding) => {
if (newBuilding) { if (newBuilding) {
await Promise.all([fetchFloorList(), fetchDepartmentList()]); await Promise.all([fetchFloorList(newBuilding.building_guid), fetchDepartmentList()]);
} }
}); });

View File

@ -1,5 +1,5 @@
<script setup> <script setup>
import { ref, provide, onMounted, watch } from "vue"; import { ref, provide, onMounted, watch, computed } from "vue";
import AssetMainList from "./components/AssetMainList.vue"; import AssetMainList from "./components/AssetMainList.vue";
import AssetSubList from "./components/AssetSubList.vue"; import AssetSubList from "./components/AssetSubList.vue";
import AssetTable from "./components/AssetTable.vue"; import AssetTable from "./components/AssetTable.vue";
@ -12,8 +12,6 @@ const { searchParams, changeParams } = useSearchParam();
const companyOptions = ref([]); const companyOptions = ref([]);
const iotSchemaOptions = ref([]); const iotSchemaOptions = ref([]);
const elecTypeOptions = ref([]); const elecTypeOptions = ref([]);
const departmentList = ref([]);
const floors = ref([]);
const getCompany = async () => { const getCompany = async () => {
const res = await getOperationCompanyList(); const res = await getOperationCompanyList();
companyOptions.value = res.data.map((d) => ({ ...d, key: d.id })); companyOptions.value = res.data.map((d) => ({ ...d, key: d.id }));
@ -27,11 +25,12 @@ const getElecType = async () => {
elecTypeOptions.value = res.data.map((d) => ({ ...d, key: d.id })); elecTypeOptions.value = res.data.map((d) => ({ ...d, key: d.id }));
}; };
const departmentList = computed(() => storeBuild.deptList);
const floors = computed(() => storeBuild.floorList);
onMounted(() => { onMounted(() => {
getCompany(); getCompany();
getElecType(); getElecType();
floors.value = storeBuild.floorList;
departmentList.value = storeBuild.deptList;
}); });
watch( watch(

View File

@ -9,23 +9,11 @@ import dayjs from "dayjs";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
const { t } = useI18n(); const { t } = useI18n();
const { openToast, cancelToastOpen } = inject("app_toast"); const { openToast, cancelToastOpen } = inject("app_toast");
const { companyOptions, departmentList, floors } = inject("asset_modal_options");
const FILE_BASEURL = import.meta.env.VITE_FILE_API_BASEURL; const FILE_BASEURL = import.meta.env.VITE_FILE_API_BASEURL;
const { searchParams, changeParams } = useSearchParam(); const { searchParams, changeParams } = useSearchParam();
const companyOptions = ref([]);
const getCompany = async () => {
const res = await getOperationCompanyList();
companyOptions.value = res.data.map((d) => ({ ...d, key: d.id }));
};
const floors = ref([]);
const totalCoordinates = ref({}); const totalCoordinates = ref({});
const getFloors = async () => {
const res = await getAssetFloorList();
floors.value = res.data[0]?.floors.map((d) => ({ ...d, key: d.floor_guid }));
};
const tableData = ref([]); const tableData = ref([]);
const getAssetData = async () => { const getAssetData = async () => {
totalCoordinates.value = {}; // totalCoordinates totalCoordinates.value = {}; // totalCoordinates
@ -45,6 +33,7 @@ const getAssetData = async () => {
floor: floors.value.find(({ floor_guid }) => d.floor_guid === floor_guid) floor: floors.value.find(({ floor_guid }) => d.floor_guid === floor_guid)
?.full_name, ?.full_name,
company: companyOptions.value.find(({ id }) => d.operation_id === id), company: companyOptions.value.find(({ id }) => d.operation_id === id),
department: departmentList.value.find(({ id }) => d.department_id === id)?.name,
buying_date: d?.buying_date buying_date: d?.buying_date
? dayjs(d?.buying_date).format("YYYY-MM-DD") ? dayjs(d?.buying_date).format("YYYY-MM-DD")
: "", : "",
@ -56,17 +45,15 @@ const getAssetData = async () => {
}; };
onMounted(async () => { onMounted(async () => {
await getCompany();
await getFloors();
getAssetData(); getAssetData();
}); });
const columns = computed(() => [ const columns = computed(() => [
{ // {
title: t("assetManagement.device_number"), // title: t("assetManagement.device_number"),
key: "device_number", // key: "device_number",
class: "break-all", // class: "break-all",
}, // },
{ {
title: t("assetManagement.device_name"), title: t("assetManagement.device_name"),
key: "full_name", key: "full_name",
@ -83,6 +70,11 @@ const columns = computed(() => [
filter: true, filter: true,
sort: true, sort: true,
}, },
{
title: t("assetManagement.department"),
key: "department",
filter: true,
},
{ {
title: t("assetManagement.device_coordinate"), title: t("assetManagement.device_coordinate"),
key: "device_coordinate", key: "device_coordinate",

View File

@ -2,12 +2,11 @@
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 mqtt from "mqtt"; import mqtt from "mqtt";
import dayjs from "dayjs";
const { t } = useI18n(); const { t } = useI18n();
const { openToast, cancelToastOpen } = inject("app_toast"); const { openToast, cancelToastOpen } = inject("app_toast");
const { formState } = inject( const { formState } = inject("asset_table_modal_form");
"asset_table_modal_form"
);
const BASEURL = import.meta.env.VITE_MQTT_BASEURL; const BASEURL = import.meta.env.VITE_MQTT_BASEURL;
// MQTT // MQTT
const mqttClient = ref(null); // MQTT const mqttClient = ref(null); // MQTT
@ -26,10 +25,13 @@ const openModal = () => {
const connectMqtt = () => { const connectMqtt = () => {
const topic = formState.value.topic || ""; // const topic = formState.value.topic || ""; //
const mqttHost = `${BASEURL}`; const mqttHost = `${BASEURL}`;
const protocol = import.meta.env.MODE === "production" ? "wss" : "ws"; // "ws" "wss" const protocol = "wss"; // "ws" "wss"
mqttClient.value = mqtt.connect(mqttHost, { mqttClient.value = mqtt.connect(mqttHost, {
protocol, protocol,
reconnectPeriod: 1000, // reconnectPeriod: 1000, //
username: "admin", // MQTT
password: "mjmadmin@99", // MQTT
port: 443,
}); });
mqttClient.value.on("connect", () => { mqttClient.value.on("connect", () => {
@ -47,7 +49,14 @@ const connectMqtt = () => {
mqttClient.value.on("message", (topic, message) => { mqttClient.value.on("message", (topic, message) => {
// //
receivedMessages.value.push({ topic, message: message.toString() }); const now = dayjs(); // 使 dayjs()
const timestamp = now.format("YYYY-MM-DD HH:mm:ss");
receivedMessages.value.push({
topic,
message: message.toString(),
timestamp: timestamp,
});
clearInterval(timer); // clearInterval(timer); //
}); });
@ -88,7 +97,7 @@ const onCancel = () => {
<template> <template>
<div class="flex w-72"> <div class="flex w-72">
<Input :value="formState" name="topic" > <Input :value="formState" name="topic">
<template #topLeft>MQTT Topic</template> <template #topLeft>MQTT Topic</template>
</Input> </Input>
<button type="button" class="btn btn-add mt-11 ms-1" @click="openModal"> <button type="button" class="btn btn-add mt-11 ms-1" @click="openModal">
@ -107,8 +116,14 @@ const onCancel = () => {
:key="index" :key="index"
class="bg-base-200 rounded-md text-wrap shadow shadow-slate-400 p-4 my-2 me-2" class="bg-base-200 rounded-md text-wrap shadow shadow-slate-400 p-4 my-2 me-2"
> >
<strong class=" text-base block text-info mb-2">{{ message.topic }} :</strong> <strong class="text-base block text-info mb-2"
<p class=" text-sm break-words">{{ message.message }}</p> >{{ message.topic }} :</strong
>
<p class="text-sm break-words">{{ message.message }}</p>
<p class="text-xs text-slate-200 pt-2">
<FontAwesomeIcon :icon="['fas', 'clock']" class="me-1" />
{{ message.timestamp }}
</p>
</li> </li>
</ul> </ul>
</div> </div>

View File

@ -2,35 +2,37 @@
import { ref, onMounted } from "vue"; import { ref, onMounted } from "vue";
import * as echarts from "echarts"; import * as echarts from "echarts";
import BarChart from "@/components/chart/BarChart.vue"; import BarChart from "@/components/chart/BarChart.vue";
import { useI18n } from "vue-i18n";
const { t } = useI18n();
const chartData = ref([ const chartData = ref([
{ {
category: "日環比", category: t("dashboard.daily_relative_change"),
this: 230.68, this: 230.68,
last: 377.33, last: 377.33,
change: -39, change: -39,
}, },
{ {
category: "周環比", category: t("dashboard.weekly_relative_change"),
this: 608.01, this: 608.01,
last: 2711.09, last: 2711.09,
change: -78, change: -78,
}, },
{ {
category: "月環比", category: t("dashboard.monthly_relative_change"),
this: 6473.8, this: 6473.8,
last: 12701.69, last: 12701.69,
change: -49, change: -49,
}, },
{ {
category: "年環比", category: t("dashboard.yearly_relative_change"),
this: 46687.17, this: 46687.17,
last: null, last: null,
change: null, change: null,
}, },
]); ]);
const labels = ["今日", "昨日", "本周", "上周", "本月", "上月", "今年", "去年"]; const labels = [t("dashboard.today"), t("dashboard.yesterday"), t("dashboard.this_week"), t("dashboard.last_week"), t("dashboard.this_month"), t("dashboard.last_month"), t("dashboard.this_year"), t("dashboard.last_year")];
const barWidth = 30; // Set barWidth const barWidth = 30; // Set barWidth
const barChartOptions = ref({ const barChartOptions = ref({
@ -161,7 +163,7 @@ const barChartOptions = ref({
<div class="flex flex-wrap"> <div class="flex flex-wrap">
<div class="w-full chart-data relative px-8 py-3"> <div class="w-full chart-data relative px-8 py-3">
<div class="flex flex-wrap items-center justify-between"> <div class="flex flex-wrap items-center justify-between">
<h2 class="font-light">環比能耗</h2> <h2 class="font-light">{{ $t("dashboard.relative_energy_consumption") }}</h2>
</div> </div>
<div class="h-[180px]"> <div class="h-[180px]">
<BarChart <BarChart

View File

@ -1,6 +1,7 @@
<script setup> <script setup>
import { ref } from "vue"; import { ref } from "vue";
import { useI18n } from "vue-i18n";
const { t } = useI18n();
// - // -
const mockEnergyData = { const mockEnergyData = {
monthly: [ monthly: [
@ -47,9 +48,9 @@ const getCurrentEnergyData = () => {
<div class="state-box"> <div class="state-box">
<!-- 標題和切換按鈕 --> <!-- 標題和切換按鈕 -->
<div class="flex justify-between items-center mb-4"> <div class="flex justify-between items-center mb-4">
<h2 class="font-light w-1/2 relative">當月能耗排行</h2> <h2 class="font-light relative">{{$t("dashboard.energy_ranking")}}</h2>
<button @click="toggleEnergyType" class="btn btn-info btn-xs"> <button @click="toggleEnergyType" class="btn btn-info btn-xs">
{{ currentEnergyType === "monthly" ? "本日能耗" : "當月能耗" }} {{ currentEnergyType === "monthly" ? t("dashboard.today_energy_consumption") : t("dashboard.this_month_energy_consumption") }}
</button> </button>
</div> </div>

View File

@ -1,5 +1,5 @@
<script setup> <script setup>
import { ref, onMounted } from "vue"; import { ref, onMounted, watch } from "vue";
import * as echarts from "echarts"; import * as echarts from "echarts";
import BarChart from "@/components/chart/BarChart.vue"; import BarChart from "@/components/chart/BarChart.vue";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
@ -113,24 +113,25 @@ const generateCylinderChartOption = (data) => {
const weekComparisonOption = generateCylinderChartOption(energyData.value); const weekComparisonOption = generateCylinderChartOption(energyData.value);
onMounted(() => { watch(
if ( () => [storeBuild.deptList.length, storeBuild.floorList.length],
storeBuild.deptList.length > 0 && ([deptListLength, floorListLength]) => {
storeBuild.floorList.length > 0 if (deptListLength > 0 && floorListLength > 0) {
) { formState.value = {
formState.value = { ...formState.value,
...formState.value, floorId: storeBuild.floorList[0].key,
floorId: storeBuild.floorList[0].key, deptId: storeBuild.deptList[0].key,
deptId: storeBuild.deptList[0].key, };
}; }
} },
}); { immediate: true }
);
</script> </script>
<template> <template>
<div class="w-full chart-data relative px-8 py-1"> <div class="w-full chart-data relative px-8 py-1">
<div class="flex flex-wrap items-center justify-between"> <div class="flex flex-wrap items-center justify-between">
<h2 class="font-light">近30天能耗趨勢</h2> <h2 class="font-light">{{$t("dashboard.last_30_days_energy_trend")}}</h2>
<div class="flex items-center w-52 gap-4"> <div class="flex items-center w-52 gap-4">
<Select <Select
:value="formState" :value="formState"

View File

@ -5,121 +5,6 @@ import { twMerge } from "tailwind-merge";
import useBuildingStore from "@/stores/useBuildingStore"; import useBuildingStore from "@/stores/useBuildingStore";
const store = useBuildingStore(); const store = useBuildingStore();
const router = useRouter(); const router = useRouter();
//
const mockData = ref([
{
title: "Air Detection System",
icon: "temperature-high",
isError: false,
main_system_tag: "Dust",
sub_system_tag: "EM",
},
{
title: "Lighting System",
icon: "lightbulb",
isError: false,
main_system_tag: "LS",
sub_system_tag: "ECLS",
},
{
title: "Air Condition System",
icon: "fan",
isError: false,
main_system_tag: "ME",
sub_system_tag: "TH",
},
{
title: "Electricity System",
icon: "bolt",
isError: false,
main_system_tag: "EE",
sub_system_tag: "ECP3",
},
{
title: "Elevator System",
icon: "building",
isError: false,
main_system_tag: null,
sub_system_tag: null,
},
{
title: "High Voltage Switchboard",
icon: "charging-station",
isError: false,
main_system_tag: null,
sub_system_tag: null,
},
{
title: "Low Voltage Switchboard",
icon: "charging-station",
isError: false,
main_system_tag: null,
sub_system_tag: null,
},
{
title: "Water Supply System",
icon: "tint",
isError: false,
main_system_tag: null,
sub_system_tag: null,
},
{
title: "Sewage And Wastewater Equipment",
icon: "water",
isError: false,
main_system_tag: null,
sub_system_tag: null,
},
{
title: "Emergency Generator",
icon: "car-battery",
isError: false,
main_system_tag: null,
sub_system_tag: null,
},
{
title: "Fire Equipment",
icon: "fire-extinguisher",
isError: false,
main_system_tag: null,
sub_system_tag: null,
},
{
title: "CCTV System",
icon: "video",
isError: false,
main_system_tag: null,
sub_system_tag: null,
},
{
title: "Access Control System",
icon: "door-open",
isError: false,
main_system_tag: null,
sub_system_tag: null,
},
{
title: "Shutdown System",
icon: "car",
isError: false,
main_system_tag: null,
sub_system_tag: null,
},
{
title: "Emergency Rescue System",
icon: "exclamation-triangle",
isError: false,
main_system_tag: null,
sub_system_tag: null,
},
{
title: "Air Supply Aand Exhaust System",
icon: "wind",
isError: false,
main_system_tag: null,
sub_system_tag: null,
},
]);
const navigateToSubSystem = (mainSystemId, subSystemId) => { const navigateToSubSystem = (mainSystemId, subSystemId) => {
router.push({ router.push({

View File

@ -7,7 +7,7 @@ import { getCarbonValue } from "@/apis/energy";
import useBuildingStore from "@/stores/useBuildingStore"; import useBuildingStore from "@/stores/useBuildingStore";
const store = useBuildingStore(); const store = useBuildingStore();
const { t, locale } = useI18n(); const { t, locale } = useI18n();
const { taipower_data } = inject("energy_data"); const { taipower_data, carbonValue } = inject("energy_data");
const carbonData = ref(null); const carbonData = ref(null);
const defaultChartOption = ref({ const defaultChartOption = ref({
tooltip: { tooltip: {
@ -86,6 +86,7 @@ const getData = async () => {
if (store.selectedBuilding.building_guid) { if (store.selectedBuilding.building_guid) {
const res = await getCarbonValue(store.selectedBuilding.building_guid); const res = await getCarbonValue(store.selectedBuilding.building_guid);
carbonData.value = res.data[0]; carbonData.value = res.data[0];
carbonValue.value = res.data[0].coefficient;
} }
}; };

View File

@ -1,9 +1,9 @@
<script setup> <script setup>
import { ref, onMounted, nextTick, computed } from "vue"; import { ref, onMounted, nextTick, watch, inject } from "vue";
import * as echarts from "echarts"; import * as echarts from "echarts";
import { getRealTimeDist } from "@/apis/energy"; import { getRealTimeDist } from "@/apis/energy";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
const { search_data } = inject("energy_data");
const { t } = useI18n(); const { t } = useI18n();
const chartDiv = ref(null); const chartDiv = ref(null);
@ -39,7 +39,7 @@ const chartOption = {
borderWidth: 0, borderWidth: 0,
}, },
lineStyle: { lineStyle: {
color: 'gradient', color: "gradient",
opacity: 0.7, opacity: 0.7,
curveness: 0.5, curveness: 0.5,
}, },
@ -47,50 +47,74 @@ const chartOption = {
], ],
}; };
const loadData = async () => { const loadData = async (value) => {
const res = await getRealTimeDist(); const res = await getRealTimeDist(value);
if (res.isSuccess) { if (res.isSuccess && res.data && res.data.length !== 0) {
const rawData = res.data; const rawData = res.data;
const totalValue = rawData.reduce((acc, item) => acc + item.value, 0); if (rawData) {
const totalValue = rawData.reduce((acc, item) => acc + item.value, 0);
const sortedData = [...rawData].sort((a, b) => b.value - a.value); const sortedData = [...rawData].sort((a, b) => b.value - a.value);
// data // data
const data = [ const data = [
{ name: "Total", value: totalValue, percentage: 100 }, { name: "Total", value: totalValue, percentage: 100 },
...sortedData.map((item) => ({ ...sortedData.map((item) => ({
name: item.key, name: item.key,
value: item.value,
percentage: item.percentage,
})),
];
// links
const links = sortedData.map((item, index) => ({
source: "Total",
target: item.key,
value: item.value, value: item.value,
percentage: item.percentage, percentage: item.percentage,
})), }));
];
// links const colors = [
const links = sortedData.map((item, index) => ({ "#45f4ef",
source: "Total", "#17CEE3",
target: item.key, "#E4EA00",
value: item.value, "#62E39A",
percentage: item.percentage, "#E9971F",
})); "#E52EFF",
];
const colors = ["#45f4ef", "#17CEE3", "#E4EA00", "#62E39A", "#E9971F", "#E52EFF"]; // chartOption
// chartOption chartOption.series[0].data = data.map((item, index) => ({
chartOption.series[0].data = data.map((item, index) => ({ ...item,
...item, itemStyle: {
itemStyle: { color: colors[index % colors.length], //
color: colors[index % colors.length], // },
}, }));
})); chartOption.series[0].links = links;
chartOption.series[0].links = links;
//
const myChart = echarts.init(chartDiv.value);
myChart.setOption(chartOption);
}
} else {
// //
const myChart = echarts.init(chartDiv.value); echarts.init(chartDiv.value).clear();
myChart.setOption(chartOption);
} }
}; };
onMounted(() => { watch(
loadData(); search_data,
}); (newValue, oldValue) => {
if (
newValue.building_guid &&
JSON.stringify(newValue) !== JSON.stringify(oldValue)
) {
loadData(newValue);
}
},
{
immediate: true,
deep: true,
}
);
</script> </script>
<template> <template>

View File

@ -1,5 +1,5 @@
<script setup> <script setup>
import { ref, onMounted, provide } from "vue"; import { ref, computed, provide, watch } from "vue";
import ImmediateDemandChart from "./ImmediateDemandChart.vue"; import ImmediateDemandChart from "./ImmediateDemandChart.vue";
import ElecConsumption from "./ElecConsumption.vue"; import ElecConsumption from "./ElecConsumption.vue";
import UsageInformation from "./UsageInformation.vue"; import UsageInformation from "./UsageInformation.vue";
@ -28,25 +28,80 @@ const {
} = useActiveBtn("multiple"); } = useActiveBtn("multiple");
const taipower_data = ref([]); const taipower_data = ref([]);
const getData = async () => { const carbonValue = ref(null);
const res = await getTaipower(); const search_data = computed(() => {
return {
coefficient: carbonValue.value,
building_guid: storeBuild.selectedBuilding?.building_guid || null,
department_id_list: selectedDeptItems.value.map((item) => item.key),
floor_guid_list: selectedFloorItems.value.map((item) => item.key),
};
});
const getData = async (value) => {
const res = await getTaipower(value);
if (res.isSuccess) { if (res.isSuccess) {
taipower_data.value = res.data; taipower_data.value = res.data
? res.data.sort((a, b) => a.month.localeCompare(b.month))
: [];
} }
}; };
onMounted(() => { watch(
getData(); search_data,
setDeptItems(storeBuild.deptList); (newValue, oldValue) => {
setFloorItems(storeBuild.floorList); if (
}); newValue.building_guid &&
newValue.coefficient &&
JSON.stringify(newValue) !== JSON.stringify(oldValue)
) {
getData(newValue);
}
},
{
immediate: true,
deep: true,
}
);
provide("energy_data", { taipower_data }); watch(
() => storeBuild.floorList,
(newValue) => {
if (newValue) {
const floorList = newValue.map((d) => ({
...d,
active: true,
}));
setFloorItems(floorList);
}
},
{
immediate: true,
}
);
watch(
() => storeBuild.deptList,
(newValue) => {
if (newValue) {
const deptList = newValue.map((d) => ({
...d,
active: true,
}));
setDeptItems(deptList);
}
},
{
immediate: true,
}
);
provide("energy_data", { taipower_data, search_data, carbonValue });
</script> </script>
<template> <template>
<div class="flex flex-wrap items-center mb-4"> <div class="flex flex-wrap items-center mb-4">
<div class="w-full border border-info px-4 py-2 rounded my-3"> <div class="w-full border border-info px-4 py-2 rounded mt-3">
<div class="flex items-center gap-3"> <div class="flex items-center gap-3">
<span class="text-md font-extrabold">{{ $t("energy.floor") }} :</span> <span class="text-md font-extrabold">{{ $t("energy.floor") }} :</span>
<ButtonGroup <ButtonGroup

View File

@ -1,9 +1,9 @@
<script setup> <script setup>
import BarChart from "@/components/chart/BarChart.vue"; import BarChart from "@/components/chart/BarChart.vue";
import { ref, onMounted, computed } from "vue"; import { ref, watch, computed, inject } from "vue";
import { getElecUseDay } from "@/apis/energy"; import { getElecUseDay } from "@/apis/energy";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
const { search_data } = inject("energy_data");
const { t } = useI18n(); const { t } = useI18n();
const dataSource = ref([]); const dataSource = ref([]);
const dateRange = ref({ const dateRange = ref({
@ -107,10 +107,12 @@ const chartOption = computed(() => {
}; };
}); });
const loadData = async () => { const loadData = async (value) => {
const res = await getElecUseDay(); const res = await getElecUseDay(value);
if (res.isSuccess) { if (res.isSuccess) {
dataSource.value = res.data.map((d) => ({ ...d, key: d.id })); dataSource.value = res.data
.sort((a, b) => a.time.localeCompare(b.time))
.map((d) => ({ ...d, key: d.id }));
const dates = res.data.map((d) => d.time.split(" ")[0]); const dates = res.data.map((d) => d.time.split(" ")[0]);
dateRange.value = { dateRange.value = {
@ -120,9 +122,21 @@ const loadData = async () => {
} }
}; };
onMounted(() => { watch(
loadData(); search_data,
}); (newValue, oldValue) => {
if (
newValue.building_guid &&
JSON.stringify(newValue) !== JSON.stringify(oldValue)
) {
loadData(newValue);
}
},
{
immediate: true,
deep: true,
}
);
</script> </script>
<template> <template>

View File

@ -19,7 +19,7 @@ const calculateData = () => {
item.month.startsWith(currentYear) item.month.startsWith(currentYear)
); );
const totalElecBills = filteredData.reduce((sum, item) => sum + item.kWh, 0); const totalElecBills = filteredData.reduce((sum, item) => sum + item.costTotal, 0);
const latestMonthData = filteredData[filteredData.length - 1]; const latestMonthData = filteredData[filteredData.length - 1];
const latestMonth = latestMonthData ? latestMonthData.month : ""; const latestMonth = latestMonthData ? latestMonthData.month : "";
const monthDays = latestMonth ? daysInMonth(latestMonth) : 0; const monthDays = latestMonth ? daysInMonth(latestMonth) : 0;

View File

@ -3,7 +3,7 @@ import { computed, defineProps, inject, ref, watch } from "vue";
import { useRoute } from "vue-router"; import { useRoute } from "vue-router";
import { getHistoryData, getHistoryExportData } from "@/apis/history"; import { getHistoryData, getHistoryExportData } from "@/apis/history";
import useSearchParam from "@/hooks/useSearchParam"; import useSearchParam from "@/hooks/useSearchParam";
import { useI18n } from 'vue-i18n'; import { useI18n } from "vue-i18n";
const { t } = useI18n(); const { t } = useI18n();
const { searchParams } = useSearchParam(); const { searchParams } = useSearchParam();
const route = useRoute(); const route = useRoute();
@ -26,7 +26,6 @@ const cancelToastOpen = () => {
}; };
}; };
const submit = async (e, type = "") => { const submit = async (e, type = "") => {
e?.preventDefault(); e?.preventDefault();
e?.stopPropagation(); e?.stopPropagation();
@ -53,8 +52,13 @@ const submit = async (e, type = "") => {
} else { } else {
const res = await getHistoryData({ const res = await getHistoryData({
...searchParams.value, ...searchParams.value,
Type: 1, Type:
table_type:route.params.type route.params.type != 1
? 2
: searchParams.value.Type
? searchParams.value.Type
: 1,
table_type: route.params.type,
}); });
updateTableData(res.data); updateTableData(res.data);
} }
@ -82,7 +86,7 @@ const submitBtns = computed(() => [
disabled: isSearchButtonDisabled.value, disabled: isSearchButtonDisabled.value,
}, },
{ {
title: t("button.export"), title: t("button.export"),
key: "export", key: "export",
icon: "download", icon: "download",
btn: "btn-export", btn: "btn-export",
@ -115,13 +119,18 @@ watch(
</script> </script>
<template> <template>
<Toast <Toast
:content="isToastOpen.content" :content="isToastOpen.content"
:open="isToastOpen.open" :open="isToastOpen.open"
status="info" status="info"
:cancel="cancelToastOpen" :cancel="cancelToastOpen"
/> />
<ButtonGroup class="ml-5" :items="submitBtns" :withLine="false" :withBtnClass="true"/> <ButtonGroup
class="ml-5"
:items="submitBtns"
:withLine="false"
:withBtnClass="true"
/>
</template> </template>
<style lang="scss" scoped></style> <style lang="scss" scoped></style>

View File

@ -64,7 +64,7 @@ const formatChartData = (data) => {
const seriesKey = `${item.device_name || ""}_${item.item_name || ""}`; const seriesKey = `${item.device_name || ""}_${item.item_name || ""}`;
acc[seriesKey] = { acc[seriesKey] = {
timestamps: item.data.map((d) => timestamps: item.data.map((d) =>
dayjs(d.timestamp).format("YYYY-MM-DD HH:mm") dayjs(d.time).format("YYYY-MM-DD HH:mm")
), ),
values: item.data.map((d) => values: item.data.map((d) =>
d.value == "無資料" ? null : parseFloat(d.value) d.value == "無資料" ? null : parseFloat(d.value)
@ -73,7 +73,6 @@ const formatChartData = (data) => {
minValue: parseFloat(item.minValue), minValue: parseFloat(item.minValue),
averageValue: parseFloat(item.averageValue), averageValue: parseFloat(item.averageValue),
}; };
return acc; return acc;
}, {}); }, {});
}; };
@ -86,8 +85,7 @@ watch(
const formattedData = formatChartData(newData); const formattedData = formatChartData(newData);
const series = Object.keys(formattedData).map((seriesKey, index) => { const series = Object.keys(formattedData).map((seriesKey, index) => {
const { maxValue, minValue, averageValue } = const { maxValue, minValue, averageValue } = formattedData[seriesKey];
formattedData[seriesKey];
return { return {
name: seriesKey, name: seriesKey,
type: "line", type: "line",

View File

@ -65,7 +65,7 @@ const getElecType = async () => {
...d, ...d,
title: d.name, title: d.name,
key: d.id, key: d.id,
active: false, active: true,
})); }));
setElecTypeItems(elecType); setElecTypeItems(elecType);
}; };
@ -152,8 +152,23 @@ watch(
} }
); );
watch(
() => storeBuild.deptList,
(newValue) => {
if (newValue) {
const deptList = newValue.map((d) => ({
...d,
active: true,
}));
setDeptItems(deptList);
}
},
{
immediate: true,
}
);
onMounted(() => { onMounted(() => {
setDeptItems(storeBuild.deptList);
getElecType(); getElecType();
}); });
</script> </script>

View File

@ -4,14 +4,40 @@ import useSearchParam from "@/hooks/useSearchParam";
import dayjs from "dayjs"; import dayjs from "dayjs";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
import { useRoute } from "vue-router"; import { useRoute } from "vue-router";
import useActiveBtn from "@/hooks/useActiveBtn";
const { t, locale } = useI18n(); const { t, locale } = useI18n();
const { searchParams, changeParams } = useSearchParam(); const { searchParams, changeParams } = useSearchParam();
const route = useRoute(); const route = useRoute();
const {
items: searchTypeItems,
changeActiveBtn: changeTypeActiveBtn,
setItems: setTypeItems,
selectedBtn: selectedTypeItems,
} = useActiveBtn();
const itemsForStartTime = ref([]); const itemsForStartTime = ref([]);
const itemsForEndTime = ref(); const itemsForEndTime = ref();
const initializeItems = () => { const initializeItems = () => {
setTypeItems([
{
title: t("history.date_range"),
key: 1,
active: searchParams.value.Type
? parseInt(searchParams.value.Type) === 1
: true,
},
{
title: t("history.time_range"),
key: 2,
active: searchParams.value.Type
? parseInt(searchParams.value.Type) === 2
: false,
},
]);
itemsForStartTime.value = [ itemsForStartTime.value = [
{ {
key: "Start_date", key: "Start_date",
@ -56,10 +82,10 @@ const initializeItems = () => {
watch( watch(
() => route.params.type, () => route.params.type,
() => { () => {
initializeItems(); initializeItems();
}, },
{ {
immediate: true, immediate: true,
} }
); );
@ -101,6 +127,13 @@ watch(
deep: true, deep: true,
} }
); );
watch(selectedTypeItems, (newValue) => {
changeParams({
...searchParams.value,
Type: newValue.key,
});
});
</script> </script>
<template> <template>
@ -108,6 +141,16 @@ watch(
<h2 class="text-lg font-bold ps-2 whitespace-nowrap"> <h2 class="text-lg font-bold ps-2 whitespace-nowrap">
{{ $t("history.date_range") }} : {{ $t("history.date_range") }} :
</h2> </h2>
<ButtonGroup
v-if="route.params.type == 1"
:items="searchTypeItems"
:withLine="true"
:onclick="
(e, item) => {
changeTypeActiveBtn(item);
}
"
/>
<DateGroup class="mr-3" :items="itemsForStartTime" :withLine="true" /> <DateGroup class="mr-3" :items="itemsForStartTime" :withLine="true" />
<DateGroup :items="itemsForEndTime" :withLine="true" /> <DateGroup :items="itemsForEndTime" :withLine="true" />
</div> </div>

View File

@ -6,13 +6,14 @@ import useSearchParam from "@/hooks/useSearchParam";
import { getHistorySideBar } from "@/apis/history"; import { getHistorySideBar } from "@/apis/history";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
const { t } = useI18n(); const { t } = useI18n();
const storeBuild = useBuildingStore();
const buildingGuid = computed(() => storeBuild.selectedBuilding?.building_guid);
const { searchParams, changeParams } = useSearchParam(); const { searchParams, changeParams } = useSearchParam();
const { deptData, elecType, subSystem } = inject("energy_table_data"); const { deptData, elecType, subSystem } = inject("energy_table_data");
const selectedBuilding = ref([]); const selectedBuilding = ref([]);
const deviceData = ref([]); const deviceData = ref([]);
const searchTerm = ref(""); // const searchTerm = ref(""); //
const activeSearchTerm = ref(""); const activeSearchTerm = ref("");
const getDeviceData = async ({ const getDeviceData = async ({
sub_system_tag, sub_system_tag,
department_id, department_id,
@ -22,6 +23,7 @@ const getDeviceData = async ({
sub_system_tag, sub_system_tag,
department_id, department_id,
elec_type_id, elec_type_id,
building_guid: buildingGuid.value,
}); });
deviceData.value = (res.data || []).map((building) => ({ deviceData.value = (res.data || []).map((building) => ({
building_tag: building.building_tag, building_tag: building.building_tag,

View File

@ -47,7 +47,8 @@ const getElecType = async () => {
const elecType = res.data.map((d, index) => ({ const elecType = res.data.map((d, index) => ({
...d, ...d,
title: d.name, title: d.name,
key: d.id key: d.id,
active: true,
})); }));
setElecItems(elecType); setElecItems(elecType);
}; };
@ -99,9 +100,39 @@ watch(
{ immediate: true } { immediate: true }
); );
watch(
() => storeBuild.deptList,
(newValue) => {
if (newValue) {
const deptList = newValue.map((d) => ({
...d,
active: true,
}));
setDeptItems(deptList);
}
},
{
immediate: true,
}
);
watch(
() => storeBuild.floorList,
(newValue) => {
if (newValue) {
const floorList = newValue.map((d) => ({
...d,
active: true,
}));
setFloorItems(floorList);
}
},
{
immediate: true,
}
);
onMounted(() => { onMounted(() => {
setDeptItems(storeBuild.deptList);
setFloorItems(storeBuild.floorList);
getElecType(); getElecType();
}); });
</script> </script>

View File

@ -65,7 +65,7 @@ const updateFileList = (files) => {
}; };
const resetForm = () => { const resetForm = () => {
onSubSysClick(options.value[0].key); // onSubSysClick(options.value[0].key);
updateFileList([]); updateFileList([]);
}; };

View File

@ -122,29 +122,15 @@ const getPoint = async (deviceList) => {
}; };
watch( watch(
selectedPoints, [selectedPoints, selectedDeptItems],
(newVal, oldVal) => { ([newPoints, newDeptItems], [oldPoints, oldDeptItems]) => {
changeParams({ changeParams({
...searchParams.value, ...searchParams.value,
Points: newVal.map((d) => d.points), Points: newPoints.map((d) => d.points),
Dept: newDeptItems.map((d) => d.id),
}); });
}, },
{ { immediate: true }
immediate: true,
}
);
watch(
selectedDeptItems,
(newVal, oldVal) => {
changeParams({
...searchParams.value,
Dept: newVal.map((d) => d.id),
});
},
{
immediate: true,
}
); );
const form = ref(null); const form = ref(null);
@ -154,14 +140,32 @@ watch(searchParams, (newVal, oldValue) => {
(newVal?.Device_list?.length && typeof oldValue.Device_list === "string") || (newVal?.Device_list?.length && typeof oldValue.Device_list === "string") ||
(newVal?.Device_list?.length && !oldValue?.Device_list?.length) (newVal?.Device_list?.length && !oldValue?.Device_list?.length)
) { ) {
getPoint( if (newVal?.Device_list[0]!==null){
typeof newVal.Device_list == "string" getPoint(
? [newVal.Device_list] typeof newVal.Device_list == "string"
: newVal.Device_list ? [newVal.Device_list]
); : newVal.Device_list
);
}
} }
}); });
watch(
() => store.deptList,
(newValue) => {
if (newValue) {
const deptList = newValue.map((d) => ({
...d,
active: true,
}));
setDeptItems(deptList);
}
},
{
immediate: true,
}
);
onMounted(() => { onMounted(() => {
setMainSysItems( setMainSysItems(
store.mainSubSys.map(({ full_name, main_system_tag }, index) => ({ store.mainSubSys.map(({ full_name, main_system_tag }, index) => ({
@ -170,13 +174,6 @@ onMounted(() => {
active: index === 0, active: index === 0,
})) }))
); );
setDeptItems(
store.deptList.map((d, index) => ({
...d,
title: d.name,
key: d.id,
}))
);
}); });
onBeforeMount(() => { onBeforeMount(() => {

View File

@ -5,17 +5,23 @@ import useBuildingStore from "@/stores/useBuildingStore";
import useSearchParam from "@/hooks/useSearchParam"; import useSearchParam from "@/hooks/useSearchParam";
import { getHistorySideBar } from "@/apis/history"; import { getHistorySideBar } from "@/apis/history";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
const { t } = useI18n(); const { t } = useI18n();
const { searchParams, changeParams } = useSearchParam(); const { searchParams, changeParams } = useSearchParam();
const storeBuild = useBuildingStore();
const selectedBuilding = ref([]); const selectedBuilding = ref([]);
const deviceData = ref([]); const deviceData = ref([]);
const searchTerm = ref(""); // const searchTerm = ref(""); //
const activeSearchTerm = ref(""); const activeSearchTerm = ref("");
const getDeviceData = async (sub_system_tag,department_id) => { const getDeviceData = async (sub_system_tag, department_id) => {
const deptArray = department_id? department_id.map(Number):null; const deptArray = department_id ? department_id.map(Number) : [];
const res = await getHistorySideBar({sub_system_tag: sub_system_tag,department_id:deptArray}); const res = await getHistorySideBar({
sub_system_tag: sub_system_tag,
department_id: deptArray,
elec_type_id: [],
building_guid: storeBuild.selectedBuilding?.building_guid || null,
});
deviceData.value = res.data.map((building) => ({ deviceData.value = res.data.map((building) => ({
building_tag: building.building_tag, building_tag: building.building_tag,
building_name: building.building_name, building_name: building.building_name,
@ -30,9 +36,7 @@ const getDeviceData = async (sub_system_tag,department_id) => {
})); }));
selectedBuilding.value = res.data.map((d) => d.building_tag); selectedBuilding.value = res.data.map((d) => d.building_tag);
changeSelected( changeSelected([res.data[0]?.floor_list[0]?.device_list[0]?.device_number]);
[res.data[0]?.floor_list[0]?.device_list[0]?.device_number]
);
}; };
const sysIsExisted = (building_tag) => { const sysIsExisted = (building_tag) => {
@ -57,7 +61,7 @@ watch(
newVal.sub_system_tag && newVal.sub_system_tag &&
newVal.sub_system_tag != oldVal?.sub_system_tag newVal.sub_system_tag != oldVal?.sub_system_tag
) { ) {
getDeviceData(newVal.sub_system_tag,searchParams.value.Dept); getDeviceData(newVal.sub_system_tag, searchParams.value.Dept);
} }
}, },
{ {
@ -69,11 +73,8 @@ watch(
watch( watch(
searchParams, searchParams,
(newVal, oldVal) => { (newVal, oldVal) => {
if ( if (newVal.Dept && newVal.Dept?.length != oldVal?.Dept?.length) {
newVal.Dept && getDeviceData(searchParams.value.sub_system_tag, newVal.Dept);
newVal.Dept?.length != oldVal?.Dept?.length
) {
getDeviceData(searchParams.value.sub_system_tag,newVal.Dept);
} }
}, },
{ {
@ -182,15 +183,19 @@ const changeSelected = (Device_list, renew = false) => {
const filteredDeviceData = computed(() => { const filteredDeviceData = computed(() => {
if (!activeSearchTerm.value) return deviceData.value; if (!activeSearchTerm.value) return deviceData.value;
return deviceData.value.map((building) => ({ return deviceData.value
...building, .map((building) => ({
floors: building.floors.map((floor) => ({ ...building,
...floor, floors: building.floors
devices: floor.devices.filter((device) => .map((floor) => ({
device.device_name.includes(activeSearchTerm.value) ...floor,
), devices: floor.devices.filter((device) =>
})).filter(floor => floor.devices.length > 0) device.device_name.includes(activeSearchTerm.value)
})).filter(building => building.floors.length > 0); ),
}))
.filter((floor) => floor.devices.length > 0),
}))
.filter((building) => building.floors.length > 0);
}); });
const handleSearch = (e) => { const handleSearch = (e) => {
@ -222,7 +227,10 @@ const handleInput = (e) => {
/> />
</label> </label>
<ul class="menu text-lg"> <ul class="menu text-lg">
<template v-for="building in filteredDeviceData" :key="building.building_tag"> <template
v-for="building in filteredDeviceData"
:key="building.building_tag"
>
<li> <li>
<details :open="selectedBuilding.includes(building.building_tag)"> <details :open="selectedBuilding.includes(building.building_tag)">
<summary> <summary>

View File

@ -9,6 +9,7 @@ import Floors from "./components/Floors.vue";
import Building from "./components/Building.vue"; import Building from "./components/Building.vue";
import ElecPriceManagement from "./components/ElecPriceManagement.vue"; import ElecPriceManagement from "./components/ElecPriceManagement.vue";
import MQTTList from "./components/MQTTList.vue"; import MQTTList from "./components/MQTTList.vue";
import Demand from "./components/Demand.vue";
// import PointList from "./components/PointList.vue"; // import PointList from "./components/PointList.vue";
const route = useRoute(); const route = useRoute();
@ -31,6 +32,8 @@ const updateComponent = () => {
currentComponent.value = ElecPriceManagement; currentComponent.value = ElecPriceManagement;
} else if (sub_system_id === "MQTT_Result") { } else if (sub_system_id === "MQTT_Result") {
currentComponent.value = MQTTList; currentComponent.value = MQTTList;
} else if (sub_system_id === "Demand") {
currentComponent.value = Demand;
} }
}; };

View File

@ -0,0 +1,169 @@
<script setup>
import { onMounted, ref, inject, computed, watch } from "vue";
import { getAssetList, postAssetElecSetting } from "@/apis/asset";
import { useI18n } from "vue-i18n";
import { twMerge } from "tailwind-merge";
import useBuildingStore from "@/stores/useBuildingStore";
import { getElecTypeList } from "@/apis/asset";
const { t } = useI18n();
const { openToast, cancelToastOpen } = inject("app_toast");
const storeBuild = useBuildingStore();
const departmentList = computed(() => storeBuild.deptList);
const floors = computed(() => storeBuild.floorList);
const elecType = ref([]);
const isEditing = ref(false);
const tableData = ref([]);
const columns = computed(() => [
{
title: t("assetManagement.operation"),
key: "operation",
width: 200,
},
{
title: t("assetManagement.device_number"),
key: "device_number",
class: "break-all",
},
{
title: t("assetManagement.device_name"),
key: "full_name",
filter: true,
class: "break-all",
},
{
title: t("assetManagement.floor"),
key: "floor",
filter: true,
sort: true,
},
{
title: t("assetManagement.department"),
key: "department",
filter: true,
},
{
title: t("energy.electricity_classification"),
key: "elecType",
filter: true,
},
]);
const getElecType = async () => {
const res = await getElecTypeList();
elecType.value = res.data.map((d, index) => ({
...d,
key: d.id,
}));
};
const getAssetData = async () => {
// Electricity Meter P3
const res = await getAssetList(465);
if (res.isSuccess) {
tableData.value = res.data.map((d) => ({
...d,
key: d.id,
floor: floors.value.find(({ floor_guid }) => d.floor_guid === floor_guid)
?.full_name,
department: departmentList.value.find(({ id }) => d.department_id === id)
?.name,
elecType: elecType.value.find(({ id }) => d.elec_type_id === id)?.name,
}));
}
};
const onOk = async () => {
const formData = tableData.value.map((item) => ({
main_id: item.main_id,
is_demand: item.is_demand,
}));
const res = await postAssetElecSetting(formData);
if (res.isSuccess) {
onCancel();
} else {
openToast("error", res.msg);
}
};
const onCancel = async () => {
isEditing.value = false;
await getAssetData();
};
const Check = (id) => {
const items = tableData.value.find((d) => d.main_id === id);
items.is_demand = items.is_demand ? 0 : 1;
};
onMounted(() => {
getElecType();
});
watch(
[floors, departmentList],
() => {
if (floors.value.length > 0 && departmentList.value.length > 0) {
getAssetData();
}
},
{ immediate: true }
);
</script>
<template>
<div class="flex justify-start items-center mt-10 mb-5">
<h3 class="text-xl mr-5">電表</h3>
<button
v-if="!isEditing"
class="btn btn-sm btn-add mr-3"
@click.stop.prevent="isEditing = true"
>
<font-awesome-icon :icon="['fas', 'pencil-alt']" />{{
$t("button.start_edit")
}}
</button>
<template v-else>
<button class="btn btn-sm btn-add mr-3" @click.prevent="onOk()">
<font-awesome-icon :icon="['fas', 'save']" />{{ $t("button.confirm") }}
</button>
<button
class="btn btn-sm btn-outline-info mr-3"
@click.stop.prevent="onCancel()"
>
<font-awesome-icon :icon="['fas', 'times']" />{{ $t("button.cancel") }}
</button>
</template>
</div>
<Table :columns="columns" :dataSource="tableData" class="mt-3">
<template #bodyCell="{ record, column, index }">
<template v-if="column.key === 'operation'">
<template v-if="!isEditing">
<FontAwesomeIcon
v-if="record.is_demand"
:icon="['fas', 'check-circle']"
size="lg"
class="text-gray-300"
/>
<FontAwesomeIcon
v-else
:icon="['far', 'circle']"
size="lg"
class="text-gray-300"
/>
</template>
<template v-else>
<Checkbox
:checked="record.is_demand"
@click="() => Check(record.main_id)"
></Checkbox>
</template>
</template>
<template v-else>
{{ record[column.key] }}
</template>
</template>
</Table>
</template>
<style lang="css" scoped></style>

View File

@ -20,7 +20,7 @@ const onOk = async (sheet, data) => {
getData(); getData();
onCancel(sheet); onCancel(sheet);
} else { } else {
openToast("error", res.msg, "#immediate_demand_add_item"); openToast("error", res.msg);
} }
}; };
@ -53,7 +53,7 @@ watch(
<template> <template>
<div class="flex justify-start items-center my-5"> <div class="flex justify-start items-center my-5">
<h3 class="text-xl mr-5">簡易型時間電價二段式</h3> <h3 class="text-xl mr-5">{{ $t("energy.simple_elec_price_two_stage") }}</h3>
<button <button
v-if="!sim2isEditing" v-if="!sim2isEditing"
class="btn btn-sm btn-add mr-3" class="btn btn-sm btn-add mr-3"
@ -81,16 +81,16 @@ watch(
<table class=""> <table class="">
<thead> <thead>
<tr> <tr>
<th colspan="6" class="bg-teal-800 bg-opacity-20">分類</th> <th colspan="6" class="bg-teal-800 bg-opacity-20">{{ $t("energy.classification") }}</th>
<th class="bg-teal-800 bg-opacity-20">夏月<br />(6/1~9/30)</th> <th class="bg-teal-800 bg-opacity-20">{{ $t("energy.summer_months") }}<br />(6/1~9/30)</th>
<th class="bg-teal-800 bg-opacity-20">非夏月<br />(夏月以外的時間)</th> <th class="bg-teal-800 bg-opacity-20">{{ $t("energy.non_summer_months") }}<br />({{ $t("energy.time_outside_summer_months") }})</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr> <tr>
<td class="bg-teal-800 bg-opacity-40">基本電費</td> <td class="bg-teal-800 bg-opacity-40">{{$t("energy.fixed_elec_cost")}}</td>
<td colspan="4" class="bg-teal-800 bg-opacity-40">按戶計收</td> <td colspan="4" class="bg-teal-800 bg-opacity-40">{{$t("energy.charged_per_household")}}</td>
<td class="bg-teal-800 bg-opacity-40">每戶每月</td> <td class="bg-teal-800 bg-opacity-40">{{$t("energy.per_household_month")}}</td>
<td colspan="2" class="bg-teal-800 bg-opacity-40"> <td colspan="2" class="bg-teal-800 bg-opacity-40">
<input <input
type="number" type="number"
@ -100,12 +100,12 @@ watch(
</td> </td>
</tr> </tr>
<tr> <tr>
<td rowspan="6">流動電費</td> <td rowspan="6">{{$t("energy.var_elec_cost")}}</td>
<td rowspan="4">週一~週五</td> <td rowspan="4">{{$t("energy.mon_to_friday")}}</td>
<td rowspan="2" class="bg-rose-600 bg-opacity-70">尖峰時間</td> <td rowspan="2" class="bg-rose-600 bg-opacity-70">{{$t("energy.peak_hours")}}</td>
<td class="bg-rose-600 bg-opacity-20">夏月</td> <td class="bg-rose-600 bg-opacity-20">{{$t("energy.summer_months")}}</td>
<td class="bg-rose-600 bg-opacity-20">09:00 ~ 24:00</td> <td class="bg-rose-600 bg-opacity-20">09:00 ~ 24:00</td>
<td rowspan="5">每度</td> <td rowspan="5">{{$t("energy.price_per_kwh")}}</td>
<td class="bg-rose-600 bg-opacity-20"> <td class="bg-rose-600 bg-opacity-20">
<input <input
type="number" type="number"
@ -116,7 +116,7 @@ watch(
<td class="bg-rose-600 bg-opacity-20">-</td> <td class="bg-rose-600 bg-opacity-20">-</td>
</tr> </tr>
<tr> <tr>
<td class="bg-rose-600 bg-opacity-20">非夏月</td> <td class="bg-rose-600 bg-opacity-20">{{$t("energy.non_summer_months")}}</td>
<td class="bg-rose-600 bg-opacity-20"> <td class="bg-rose-600 bg-opacity-20">
06:00 ~ 11:00<br />14:00 ~ 24:00 06:00 ~ 11:00<br />14:00 ~ 24:00
</td> </td>
@ -130,8 +130,8 @@ watch(
</td> </td>
</tr> </tr>
<tr> <tr>
<td rowspan="2" class="bg-green-600 bg-opacity-60">離峰時間</td> <td rowspan="2" class="bg-green-600 bg-opacity-60">{{$t("energy.off_peak_hours")}}</td>
<td class="bg-green-600 bg-opacity-20">夏月</td> <td class="bg-green-600 bg-opacity-20">{{$t("energy.summer_months")}}</td>
<td class="bg-green-600 bg-opacity-20">00:00 ~ 09:00</td> <td class="bg-green-600 bg-opacity-20">00:00 ~ 09:00</td>
<td class="bg-green-600 bg-opacity-20"> <td class="bg-green-600 bg-opacity-20">
<input <input
@ -143,7 +143,7 @@ watch(
<td class="bg-green-600 bg-opacity-20">-</td> <td class="bg-green-600 bg-opacity-20">-</td>
</tr> </tr>
<tr> <tr>
<td class="bg-green-600 bg-opacity-20">非夏月</td> <td class="bg-green-600 bg-opacity-20">{{$t("energy.non_summer_months")}}</td>
<td class="bg-green-600 bg-opacity-20"> <td class="bg-green-600 bg-opacity-20">
00:00 ~ 06:00<br />11:00 ~ 14:00 00:00 ~ 06:00<br />11:00 ~ 14:00
</td> </td>
@ -157,9 +157,9 @@ watch(
</td> </td>
</tr> </tr>
<tr> <tr>
<td>週六週日<br />及離峰日</td> <td>{{$t("energy.sat_sun_off_peak_days")}}</td>
<td class="bg-green-600 bg-opacity-60">離峰時間</td> <td class="bg-green-600 bg-opacity-60">{{$t("energy.off_peak_hours")}}</td>
<td colspan="2" class="bg-green-600 bg-opacity-20">全日</td> <td colspan="2" class="bg-green-600 bg-opacity-20">{{$t("energy.all_day")}}</td>
<td class="bg-green-600 bg-opacity-20"> <td class="bg-green-600 bg-opacity-20">
<input <input
type="number" type="number"
@ -176,11 +176,11 @@ watch(
</td> </td>
</tr> </tr>
<tr> <tr>
<td colspan="4">每月總度數超過2000度之部分</td> <td colspan="4">{{$t("energy.usage_over_2000kwh")}}</td>
<td>每度</td> <td>{{$t("energy.price_per_kwh")}}</td>
<td colspan="2" class="!text-start"> <td colspan="2" class="!text-start">
<div class="flex items-center"> <div class="flex items-center">
{{$t("energy.add")}}
<input <input
type="number" type="number"
v-model.number="sim2NewValue[7]" v-model.number="sim2NewValue[7]"
@ -193,7 +193,7 @@ watch(
</table> </table>
<div class="flex justify-start items-center mt-16"> <div class="flex justify-start items-center mt-16">
<h3 class="text-xl mr-5">簡易型時間電價三段式</h3> <h3 class="text-xl mr-5"> {{$t("energy.simple_elec_price_three_stage")}}</h3>
<button <button
v-if="!sim3isEditing" v-if="!sim3isEditing"
class="btn btn-sm btn-add mr-3" class="btn btn-sm btn-add mr-3"
@ -221,16 +221,16 @@ watch(
<table class="my-5"> <table class="my-5">
<thead> <thead>
<tr> <tr>
<th colspan="6" class="bg-teal-800 bg-opacity-20">分類</th> <th colspan="6" class="bg-teal-800 bg-opacity-20">{{$t("energy.classification")}}</th>
<th class="bg-teal-800 bg-opacity-20">夏月<br />(6/1~9/30)</th> <th class="bg-teal-800 bg-opacity-20">{{$t("energy.summer_months")}}<br />(6/1~9/30)</th>
<th class="bg-teal-800 bg-opacity-20">非夏月<br />(夏月以外的時間)</th> <th class="bg-teal-800 bg-opacity-20">{{$t("energy.non_summer_months")}}<br />({{$t("energy.time_outside_summer_months")}})</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr> <tr>
<td class="bg-teal-800 bg-opacity-40">基本電費</td> <td class="bg-teal-800 bg-opacity-40">{{$t("energy.fixed_elec_cost")}}</td>
<td colspan="4" class="bg-teal-800 bg-opacity-40">按戶計收</td> <td colspan="4" class="bg-teal-800 bg-opacity-40">{{$t("energy.charged_per_household")}}</td>
<td class="bg-teal-800 bg-opacity-40">每戶每月</td> <td class="bg-teal-800 bg-opacity-40">{{$t("energy.per_household_month")}}</td>
<td colspan="2" class="bg-teal-800 bg-opacity-40"> <td colspan="2" class="bg-teal-800 bg-opacity-40">
<input <input
type="number" type="number"
@ -240,12 +240,12 @@ watch(
</td> </td>
</tr> </tr>
<tr> <tr>
<td rowspan="7">流動電費</td> <td rowspan="7">{{$t("energy.var_elec_cost")}}</td>
<td rowspan="5">週一~週五</td> <td rowspan="5">{{$t("energy.mon_to_friday")}}</td>
<td class="bg-rose-600 bg-opacity-70">尖峰時間</td> <td class="bg-rose-600 bg-opacity-70">{{$t("energy.peak_hours")}}</td>
<td class="bg-rose-600 bg-opacity-20">夏月</td> <td class="bg-rose-600 bg-opacity-20">{{$t("energy.summer_months")}}</td>
<td class="bg-rose-600 bg-opacity-20">16:00 ~ 22:00</td> <td class="bg-rose-600 bg-opacity-20">16:00 ~ 22:00</td>
<td rowspan="6">每度</td> <td rowspan="6">{{$t("energy.price_per_kwh")}}</td>
<td class="bg-rose-600 bg-opacity-20"> <td class="bg-rose-600 bg-opacity-20">
<input <input
type="number" type="number"
@ -256,8 +256,8 @@ watch(
<td class="bg-rose-600 bg-opacity-20">-</td> <td class="bg-rose-600 bg-opacity-20">-</td>
</tr> </tr>
<tr> <tr>
<td rowspan="2" class="bg-yellow-400 bg-opacity-80">半尖峰時間</td> <td rowspan="2" class="bg-yellow-400 bg-opacity-80">{{$t("energy.semi_peak_hours")}}</td>
<td class="bg-yellow-500 bg-opacity-20">夏月</td> <td class="bg-yellow-500 bg-opacity-20">{{$t("energy.summer_months")}}</td>
<td class="bg-yellow-500 bg-opacity-20"> <td class="bg-yellow-500 bg-opacity-20">
09:00 ~ 16:00<br />22:00~24:00 09:00 ~ 16:00<br />22:00~24:00
</td> </td>
@ -271,7 +271,7 @@ watch(
<td class="bg-yellow-500 bg-opacity-20">-</td> <td class="bg-yellow-500 bg-opacity-20">-</td>
</tr> </tr>
<tr> <tr>
<td class="bg-yellow-500 bg-opacity-20">非夏月</td> <td class="bg-yellow-500 bg-opacity-20">{{$t("energy.non_summer_months")}}</td>
<td class="bg-yellow-500 bg-opacity-20"> <td class="bg-yellow-500 bg-opacity-20">
06:00 ~ 11:00<br />14:00 ~ 24:00 06:00 ~ 11:00<br />14:00 ~ 24:00
</td> </td>
@ -285,8 +285,8 @@ watch(
</td> </td>
</tr> </tr>
<tr> <tr>
<td rowspan="2" class="bg-green-600 bg-opacity-60">離峰時間</td> <td rowspan="2" class="bg-green-600 bg-opacity-60">{{$t("energy.off_peak_hours")}}</td>
<td class="bg-green-600 bg-opacity-20">夏月</td> <td class="bg-green-600 bg-opacity-20">{{$t("energy.summer_months")}}</td>
<td class="bg-green-600 bg-opacity-20">00:00 ~ 09:00</td> <td class="bg-green-600 bg-opacity-20">00:00 ~ 09:00</td>
<td class="bg-green-600 bg-opacity-20"> <td class="bg-green-600 bg-opacity-20">
<input <input
@ -298,7 +298,7 @@ watch(
<td class="bg-green-600 bg-opacity-20">-</td> <td class="bg-green-600 bg-opacity-20">-</td>
</tr> </tr>
<tr> <tr>
<td class="bg-green-600 bg-opacity-20">非夏月</td> <td class="bg-green-600 bg-opacity-20">{{$t("energy.non_summer_months")}}</td>
<td class="bg-green-600 bg-opacity-20"> <td class="bg-green-600 bg-opacity-20">
00:00 ~ 06:00<br />11:00 ~ 14:00 00:00 ~ 06:00<br />11:00 ~ 14:00
</td> </td>
@ -312,9 +312,9 @@ watch(
</td> </td>
</tr> </tr>
<tr> <tr>
<td>週六週日<br />及離峰日</td> <td>{{$t("energy.sat_sun_off_peak_days")}}</td>
<td colspan="2" class="bg-green-600 bg-opacity-60">離峰時間</td> <td colspan="2" class="bg-green-600 bg-opacity-60">{{$t("energy.off_peak_hours")}}</td>
<td class="bg-green-600 bg-opacity-20">全日</td> <td class="bg-green-600 bg-opacity-20">{{$t("energy.all_day")}}</td>
<td class="bg-green-600 bg-opacity-20"> <td class="bg-green-600 bg-opacity-20">
<input <input
type="number" type="number"
@ -331,11 +331,11 @@ watch(
</td> </td>
</tr> </tr>
<tr> <tr>
<td colspan="4">每月總度數超過2000度之部分</td> <td colspan="4">{{$t("energy.usage_over_2000kwh")}}</td>
<td>每度</td> <td>{{$t("energy.price_per_kwh")}}</td>
<td colspan="2" class="!text-start"> <td colspan="2" class="!text-start">
<div class="flex items-center"> <div class="flex items-center">
{{$t("energy.add")}}
<input <input
type="number" type="number"
v-model.number="sim3NewValue[8]" v-model.number="sim3NewValue[8]"

View File

@ -53,7 +53,9 @@ watch(
<template> <template>
<div class="flex justify-start items-center my-5"> <div class="flex justify-start items-center my-5">
<h3 class="text-xl mr-5">標準型時間電價二段式</h3> <h3 class="text-xl mr-5">
{{ $t("energy.standard_time_of_use_tariff_2_stage") }}
</h3>
<button <button
v-if="!stand2isEditing" v-if="!stand2isEditing"
class="btn btn-sm btn-add mr-3" class="btn btn-sm btn-add mr-3"
@ -81,19 +83,33 @@ watch(
<table class=""> <table class="">
<thead> <thead>
<tr> <tr>
<th colspan="6" class="bg-teal-800 bg-opacity-20">分類</th> <th colspan="6" class="bg-teal-800 bg-opacity-20">
<th class="bg-teal-800 bg-opacity-20">夏月<br />(6/1~9/30)</th> {{ $t("energy.classification") }}
<th class="bg-teal-800 bg-opacity-20">非夏月<br />(夏月以外的時間)</th> </th>
<th class="bg-teal-800 bg-opacity-20">
{{ $t("energy.summer_months") }}<br />(6/1~9/30)
</th>
<th class="bg-teal-800 bg-opacity-20">
{{ $t("energy.non_summer_months") }}<br />({{
$t("energy.time_outside_summer_months")
}})
</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr> <tr>
<td rowspan="6" class="bg-teal-800 bg-opacity-40">基本電費</td> <td rowspan="6" class="bg-teal-800 bg-opacity-40">
<td colspan="2" rowspan="2" class="bg-teal-800 bg-opacity-40"> {{ $t("energy.fixed_elec_cost") }}
按戶計收 </td>
<td colspan="2" rowspan="2" class="bg-teal-800 bg-opacity-40">
{{ $t("energy.charged_per_household") }}
</td>
<td colspan="2" class="bg-teal-800 bg-opacity-40">
{{ $t("energy.single_phase") }}
</td>
<td rowspan="2" class="bg-teal-800 bg-opacity-40">
{{ $t("energy.per_household_month") }}
</td> </td>
<td colspan="2" class="bg-teal-800 bg-opacity-40">單相</td>
<td rowspan="2" class="bg-teal-800 bg-opacity-40">每戶每月</td>
<td colspan="2" class="bg-teal-800 bg-opacity-40"> <td colspan="2" class="bg-teal-800 bg-opacity-40">
<input <input
type="number" type="number"
@ -103,7 +119,9 @@ watch(
</td> </td>
</tr> </tr>
<tr> <tr>
<td colspan="2" class="bg-teal-800 bg-opacity-40">三相</td> <td colspan="2" class="bg-teal-800 bg-opacity-40">
{{ $t("energy.three_phase") }}
</td>
<td colspan="2" class="bg-teal-800 bg-opacity-40"> <td colspan="2" class="bg-teal-800 bg-opacity-40">
<input <input
type="number" type="number"
@ -113,8 +131,12 @@ watch(
</td> </td>
</tr> </tr>
<tr> <tr>
<td colspan="4" class="bg-teal-800 bg-opacity-40">經常契約</td> <td colspan="4" class="bg-teal-800 bg-opacity-40">
<td rowspan="4" class="bg-teal-800 bg-opacity-40">每瓩每月</td> {{ $t("energy.frequent_contract") }}
</td>
<td rowspan="4" class="bg-teal-800 bg-opacity-40">
{{ $t("energy.per_kw_per_month") }}
</td>
<td class="bg-teal-800 bg-opacity-40"> <td class="bg-teal-800 bg-opacity-40">
<input <input
type="number" type="number"
@ -131,7 +153,9 @@ watch(
</td> </td>
</tr> </tr>
<tr> <tr>
<td colspan="4" class="bg-teal-800 bg-opacity-40">非夏日契約</td> <td colspan="4" class="bg-teal-800 bg-opacity-40">
{{ $t("energy.non_summer_contract") }}
</td>
<td class="bg-teal-800 bg-opacity-40">-</td> <td class="bg-teal-800 bg-opacity-40">-</td>
<td class="bg-teal-800 bg-opacity-40"> <td class="bg-teal-800 bg-opacity-40">
<input <input
@ -142,7 +166,9 @@ watch(
</td> </td>
</tr> </tr>
<tr> <tr>
<td colspan="4" class="bg-teal-800 bg-opacity-40">週六半尖峰契約</td> <td colspan="4" class="bg-teal-800 bg-opacity-40">
{{ $t("energy.saturday_semi_peak_contract") }}
</td>
<td class="bg-teal-800 bg-opacity-40"> <td class="bg-teal-800 bg-opacity-40">
<input <input
type="number" type="number"
@ -159,7 +185,9 @@ watch(
</td> </td>
</tr> </tr>
<tr> <tr>
<td colspan="4" class="bg-teal-800 bg-opacity-40">離峰契約</td> <td colspan="4" class="bg-teal-800 bg-opacity-40">
{{ $t("energy.off_peak_contract") }}
</td>
<td class="bg-teal-800 bg-opacity-40"> <td class="bg-teal-800 bg-opacity-40">
<input <input
type="number" type="number"
@ -176,12 +204,16 @@ watch(
</td> </td>
</tr> </tr>
<tr> <tr>
<td rowspan="9">流動電費</td> <td rowspan="9">{{ $t("energy.var_elec_cost") }}</td>
<td rowspan="4">週一~週五</td> <td rowspan="4">{{ $t("energy.mon_to_friday") }}</td>
<td rowspan="2" class="bg-rose-600 bg-opacity-70">尖峰時間</td> <td rowspan="2" class="bg-rose-600 bg-opacity-70">
<td class="bg-rose-600 bg-opacity-20">夏月</td> {{ $t("energy.peak_hours") }}
</td>
<td class="bg-rose-600 bg-opacity-20">
{{ $t("energy.summer_months") }}
</td>
<td class="bg-rose-600 bg-opacity-20">09:00 ~ 24:00</td> <td class="bg-rose-600 bg-opacity-20">09:00 ~ 24:00</td>
<td rowspan="9">每度</td> <td rowspan="9">{{ $t("energy.price_per_kwh") }}</td>
<td class="bg-rose-600 bg-opacity-20"> <td class="bg-rose-600 bg-opacity-20">
<input <input
type="number" type="number"
@ -192,8 +224,12 @@ watch(
<td class="bg-rose-600 bg-opacity-20">-</td> <td class="bg-rose-600 bg-opacity-20">-</td>
</tr> </tr>
<tr> <tr>
<td class="bg-rose-600 bg-opacity-20">非夏月</td> <td class="bg-rose-600 bg-opacity-20">
<td class="bg-rose-600 bg-opacity-20">06:00 ~ 11:00<br />14:00~24:00</td> {{ $t("energy.non_summer_months") }}
</td>
<td class="bg-rose-600 bg-opacity-20">
06:00 ~ 11:00<br />14:00~24:00
</td>
<td class="bg-rose-600 bg-opacity-20">-</td> <td class="bg-rose-600 bg-opacity-20">-</td>
<td class="bg-rose-600 bg-opacity-20"> <td class="bg-rose-600 bg-opacity-20">
<input <input
@ -204,8 +240,12 @@ watch(
</td> </td>
</tr> </tr>
<tr> <tr>
<td rowspan="2" class="bg-green-600 bg-opacity-60">離峰時間</td> <td rowspan="2" class="bg-green-600 bg-opacity-60">
<td class="bg-green-600 bg-opacity-20">夏月</td> {{ $t("energy.off_peak_hours") }}
</td>
<td class="bg-green-600 bg-opacity-20">
{{ $t("energy.summer_months") }}
</td>
<td class="bg-green-600 bg-opacity-20">00:00 ~ 09:00</td> <td class="bg-green-600 bg-opacity-20">00:00 ~ 09:00</td>
<td class="bg-green-600 bg-opacity-20"> <td class="bg-green-600 bg-opacity-20">
<input <input
@ -217,7 +257,9 @@ watch(
<td class="bg-green-600 bg-opacity-20">-</td> <td class="bg-green-600 bg-opacity-20">-</td>
</tr> </tr>
<tr> <tr>
<td class="bg-green-600 bg-opacity-20">非夏月</td> <td class="bg-green-600 bg-opacity-20">
{{ $t("energy.non_summer_months") }}
</td>
<td class="bg-green-600 bg-opacity-20"> <td class="bg-green-600 bg-opacity-20">
00:00 ~ 06:00<br /> 00:00 ~ 06:00<br />
11:00 ~ 14:00 11:00 ~ 14:00
@ -232,9 +274,13 @@ watch(
</td> </td>
</tr> </tr>
<tr> <tr>
<td rowspan="4">週六</td> <td rowspan="4">{{ $t("energy.saturday") }}</td>
<td rowspan="2" class="bg-yellow-400 bg-opacity-80">半尖峰時間</td> <td rowspan="2" class="bg-yellow-400 bg-opacity-80">
<td class="bg-yellow-400 bg-opacity-20">夏日</td> {{ $t("energy.semi_peak_hours") }}
</td>
<td class="bg-yellow-400 bg-opacity-20">
{{ $t("energy.summer_months") }}
</td>
<td class="bg-yellow-400 bg-opacity-20">09:00 ~ 24:00</td> <td class="bg-yellow-400 bg-opacity-20">09:00 ~ 24:00</td>
<td class="bg-yellow-400 bg-opacity-20"> <td class="bg-yellow-400 bg-opacity-20">
<input <input
@ -246,7 +292,9 @@ watch(
<td class="bg-yellow-400 bg-opacity-20">-</td> <td class="bg-yellow-400 bg-opacity-20">-</td>
</tr> </tr>
<tr> <tr>
<td class="bg-yellow-400 bg-opacity-20">非夏日</td> <td class="bg-yellow-400 bg-opacity-20">
{{ $t("energy.non_summer_months") }}
</td>
<td class="bg-yellow-400 bg-opacity-20"> <td class="bg-yellow-400 bg-opacity-20">
06:00 ~ 11:00<br /> 06:00 ~ 11:00<br />
14:00 ~ 24:00 14:00 ~ 24:00
@ -261,8 +309,12 @@ watch(
</td> </td>
</tr> </tr>
<tr> <tr>
<td rowspan="2" class="bg-green-600 bg-opacity-60">離峰時間</td> <td rowspan="2" class="bg-green-600 bg-opacity-60">
<td class="bg-green-600 bg-opacity-20">夏月</td> {{ $t("energy.off_peak_hours") }}
</td>
<td class="bg-green-600 bg-opacity-20">
{{ $t("energy.summer_months") }}
</td>
<td class="bg-green-600 bg-opacity-20">00:00 ~ 09:00</td> <td class="bg-green-600 bg-opacity-20">00:00 ~ 09:00</td>
<td class="bg-green-600 bg-opacity-20"> <td class="bg-green-600 bg-opacity-20">
<input <input
@ -274,8 +326,12 @@ watch(
<td class="bg-green-600 bg-opacity-20">-</td> <td class="bg-green-600 bg-opacity-20">-</td>
</tr> </tr>
<tr> <tr>
<td class="bg-green-600 bg-opacity-20">非夏月</td> <td class="bg-green-600 bg-opacity-20">
<td class="bg-green-600 bg-opacity-20">00:00 ~ 06:00<br />11:00 ~ 14:00</td> {{ $t("energy.non_summer_months") }}
</td>
<td class="bg-green-600 bg-opacity-20">
00:00 ~ 06:00<br />11:00 ~ 14:00
</td>
<td class="bg-green-600 bg-opacity-20">-</td> <td class="bg-green-600 bg-opacity-20">-</td>
<td class="bg-green-600 bg-opacity-20"> <td class="bg-green-600 bg-opacity-20">
<input <input
@ -286,9 +342,13 @@ watch(
</td> </td>
</tr> </tr>
<tr> <tr>
<td>週六週日<br />及離峰日</td> <td>{{ $t("energy.sat_sun_off_peak_days") }}</td>
<td class="bg-green-600 bg-opacity-60">離峰時間</td> <td class="bg-green-600 bg-opacity-60">
<td colspan="2" class="bg-green-600 bg-opacity-20">全日</td> {{ $t("energy.off_peak_hours") }}
</td>
<td colspan="2" class="bg-green-600 bg-opacity-20">
{{ $t("energy.all_day") }}
</td>
<td class="bg-green-600 bg-opacity-20"> <td class="bg-green-600 bg-opacity-20">
<input <input
type="number" type="number"
@ -308,7 +368,9 @@ watch(
</table> </table>
<div class="flex justify-start items-center mt-14"> <div class="flex justify-start items-center mt-14">
<h3 class="text-xl mr-5">標準型時間電價三段式</h3> <h3 class="text-xl mr-5">
{{ $t("energy.standard_time_of_use_tariff_3_stage") }}
</h3>
<button <button
v-if="!stand3isEditing" v-if="!stand3isEditing"
class="btn btn-sm btn-add mr-3" class="btn btn-sm btn-add mr-3"
@ -336,17 +398,33 @@ watch(
<table class="my-5"> <table class="my-5">
<thead> <thead>
<tr> <tr>
<th colspan="6" class="bg-teal-800 bg-opacity-20">分類</th> <th colspan="6" class="bg-teal-800 bg-opacity-20">
<th class="bg-teal-800 bg-opacity-20">夏月<br />(6/1~9/30)</th> {{ $t("energy.classification") }}
<th class="bg-teal-800 bg-opacity-20">非夏月<br />(夏月以外的時間)</th> </th>
<th class="bg-teal-800 bg-opacity-20">
{{ $t("energy.summer_months") }}<br />(6/1~9/30)
</th>
<th class="bg-teal-800 bg-opacity-20">
{{ $t("energy.non_summer_months") }}<br />({{
$t("energy.time_outside_summer_months")
}})
</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr> <tr>
<td rowspan="6" class="bg-teal-800 bg-opacity-40">基本電費</td> <td rowspan="6" class="bg-teal-800 bg-opacity-40">
<td colspan="2" rowspan="2" class="bg-teal-800 bg-opacity-40">按戶計收</td> {{ $t("energy.fixed_elec_cost") }}
<td colspan="2" class="bg-teal-800 bg-opacity-40">單相</td> </td>
<td rowspan="2" class="bg-teal-800 bg-opacity-40">每戶每月</td> <td colspan="2" rowspan="2" class="bg-teal-800 bg-opacity-40">
{{ $t("energy.charged_per_household") }}
</td>
<td colspan="2" class="bg-teal-800 bg-opacity-40">
{{ $t("energy.single_phase") }}
</td>
<td rowspan="2" class="bg-teal-800 bg-opacity-40">
{{ $t("energy.per_household_month") }}
</td>
<td colspan="2" class="bg-teal-800 bg-opacity-40"> <td colspan="2" class="bg-teal-800 bg-opacity-40">
<input <input
type="number" type="number"
@ -356,7 +434,9 @@ watch(
</td> </td>
</tr> </tr>
<tr> <tr>
<td colspan="2" class="bg-teal-800 bg-opacity-40">三相</td> <td colspan="2" class="bg-teal-800 bg-opacity-40">
{{ $t("energy.three_phase") }}
</td>
<td colspan="2" class="bg-teal-800 bg-opacity-40"> <td colspan="2" class="bg-teal-800 bg-opacity-40">
<input <input
type="number" type="number"
@ -366,8 +446,12 @@ watch(
</td> </td>
</tr> </tr>
<tr> <tr>
<td colspan="4" class="bg-teal-800 bg-opacity-40">經常契約</td> <td colspan="4" class="bg-teal-800 bg-opacity-40">
<td rowspan="4" class="bg-teal-800 bg-opacity-40">每瓩每月</td> {{ $t("energy.frequent_contract") }}
</td>
<td rowspan="4" class="bg-teal-800 bg-opacity-40">
{{ $t("energy.per_kw_per_month") }}
</td>
<td class="bg-teal-800 bg-opacity-40"> <td class="bg-teal-800 bg-opacity-40">
<input <input
type="number" type="number"
@ -384,7 +468,9 @@ watch(
</td> </td>
</tr> </tr>
<tr> <tr>
<td colspan="4" class="bg-teal-800 bg-opacity-40">非夏日契約</td> <td colspan="4" class="bg-teal-800 bg-opacity-40">
{{ $t("energy.non_summer_contract") }}
</td>
<td class="bg-teal-800 bg-opacity-40"> <td class="bg-teal-800 bg-opacity-40">
<input <input
type="number" type="number"
@ -401,7 +487,9 @@ watch(
</td> </td>
</tr> </tr>
<tr> <tr>
<td colspan="4" class="bg-teal-800 bg-opacity-40">週六半尖峰契約</td> <td colspan="4" class="bg-teal-800 bg-opacity-40">
{{ $t("energy.saturday_semi_peak_contract") }}
</td>
<td class="bg-teal-800 bg-opacity-40"> <td class="bg-teal-800 bg-opacity-40">
<input <input
type="number" type="number"
@ -418,7 +506,9 @@ watch(
</td> </td>
</tr> </tr>
<tr> <tr>
<td colspan="4" class="bg-teal-800 bg-opacity-40">離峰契約</td> <td colspan="4" class="bg-teal-800 bg-opacity-40">
{{ $t("energy.off_peak_contract") }}
</td>
<td class="bg-teal-800 bg-opacity-40"> <td class="bg-teal-800 bg-opacity-40">
<input <input
type="number" type="number"
@ -435,12 +525,14 @@ watch(
</td> </td>
</tr> </tr>
<tr> <tr>
<td rowspan="10">流動電費</td> <td rowspan="10">{{ $t("energy.var_elec_cost") }}</td>
<td rowspan="5">週一~週五</td> <td rowspan="5">{{ $t("energy.mon_to_friday") }}</td>
<td class="bg-rose-600 bg-opacity-70">尖峰時間</td> <td class="bg-rose-600 bg-opacity-70">{{ $t("energy.peak_hours") }}</td>
<td class="bg-rose-600 bg-opacity-20">夏月</td> <td class="bg-rose-600 bg-opacity-20">
{{ $t("energy.summer_months") }}
</td>
<td class="bg-rose-600 bg-opacity-20">16:00 ~ 22:00</td> <td class="bg-rose-600 bg-opacity-20">16:00 ~ 22:00</td>
<td rowspan="10">每度</td> <td rowspan="10">{{ $t("energy.price_per_kwh") }}</td>
<td class="bg-rose-600 bg-opacity-20"> <td class="bg-rose-600 bg-opacity-20">
<input <input
type="number" type="number"
@ -451,9 +543,15 @@ watch(
<td class="bg-rose-600 bg-opacity-20">-</td> <td class="bg-rose-600 bg-opacity-20">-</td>
</tr> </tr>
<tr> <tr>
<td rowspan="2" class="bg-yellow-400 bg-opacity-80">半尖峰時間</td> <td rowspan="2" class="bg-yellow-400 bg-opacity-80">
<td class="bg-yellow-500 bg-opacity-20">夏月</td> {{ $t("energy.semi_peak_hours") }}
<td class="bg-yellow-500 bg-opacity-20">09:00 ~ 16:00<br />22:00 ~ 24:00</td> </td>
<td class="bg-yellow-500 bg-opacity-20">
{{ $t("energy.summer_months") }}
</td>
<td class="bg-yellow-500 bg-opacity-20">
09:00 ~ 16:00<br />22:00 ~ 24:00
</td>
<td class="bg-yellow-500 bg-opacity-20"> <td class="bg-yellow-500 bg-opacity-20">
<input <input
type="number" type="number"
@ -464,8 +562,12 @@ watch(
<td class="bg-yellow-500 bg-opacity-20">-</td> <td class="bg-yellow-500 bg-opacity-20">-</td>
</tr> </tr>
<tr> <tr>
<td class="bg-yellow-500 bg-opacity-20">非夏月</td> <td class="bg-yellow-500 bg-opacity-20">
<td class="bg-yellow-500 bg-opacity-20">06:00 ~ 11:00<br />14:00 ~ 24:00</td> {{ $t("energy.non_summer_months") }}
</td>
<td class="bg-yellow-500 bg-opacity-20">
06:00 ~ 11:00<br />14:00 ~ 24:00
</td>
<td class="bg-yellow-500 bg-opacity-20">-</td> <td class="bg-yellow-500 bg-opacity-20">-</td>
<td class="bg-yellow-500 bg-opacity-20"> <td class="bg-yellow-500 bg-opacity-20">
<input <input
@ -476,8 +578,12 @@ watch(
</td> </td>
</tr> </tr>
<tr> <tr>
<td rowspan="2" class="bg-green-600 bg-opacity-60">離峰時間</td> <td rowspan="2" class="bg-green-600 bg-opacity-60">
<td class="bg-green-600 bg-opacity-20">夏月</td> {{ $t("energy.off_peak_hours") }}
</td>
<td class="bg-green-600 bg-opacity-20">
{{ $t("energy.summer_months") }}
</td>
<td class="bg-green-600 bg-opacity-20">00:00 ~ 09:00</td> <td class="bg-green-600 bg-opacity-20">00:00 ~ 09:00</td>
<td class="bg-green-600 bg-opacity-20"> <td class="bg-green-600 bg-opacity-20">
<input <input
@ -489,7 +595,9 @@ watch(
<td class="bg-green-600 bg-opacity-20">-</td> <td class="bg-green-600 bg-opacity-20">-</td>
</tr> </tr>
<tr> <tr>
<td class="bg-green-600 bg-opacity-20">非夏月</td> <td class="bg-green-600 bg-opacity-20">
{{ $t("energy.non_summer_months") }}
</td>
<td class="bg-green-600 bg-opacity-20"> <td class="bg-green-600 bg-opacity-20">
00:00 ~ 06:00<br /> 00:00 ~ 06:00<br />
11:00 ~ 14:00 11:00 ~ 14:00
@ -504,9 +612,13 @@ watch(
</td> </td>
</tr> </tr>
<tr> <tr>
<td rowspan="4">週六</td> <td rowspan="4">{{ $t("energy.saturday") }}</td>
<td rowspan="2" class="bg-yellow-400 bg-opacity-80">半尖峰時間</td> <td rowspan="2" class="bg-yellow-400 bg-opacity-80">
<td class="bg-yellow-500 bg-opacity-20">夏日</td> {{ $t("energy.semi_peak_hours") }}
</td>
<td class="bg-yellow-500 bg-opacity-20">
{{ $t("energy.summer_months") }}
</td>
<td class="bg-yellow-500 bg-opacity-20">09:00 ~ 24:00</td> <td class="bg-yellow-500 bg-opacity-20">09:00 ~ 24:00</td>
<td class="bg-yellow-500 bg-opacity-20"> <td class="bg-yellow-500 bg-opacity-20">
<input <input
@ -518,7 +630,9 @@ watch(
<td class="bg-yellow-500 bg-opacity-20">-</td> <td class="bg-yellow-500 bg-opacity-20">-</td>
</tr> </tr>
<tr> <tr>
<td class="bg-yellow-500 bg-opacity-20">非夏日</td> <td class="bg-yellow-500 bg-opacity-20">
{{ $t("energy.non_summer_months") }}
</td>
<td class="bg-yellow-500 bg-opacity-20"> <td class="bg-yellow-500 bg-opacity-20">
06:00 ~ 11:00<br /> 06:00 ~ 11:00<br />
14:00 ~ 24:00 14:00 ~ 24:00
@ -533,8 +647,12 @@ watch(
</td> </td>
</tr> </tr>
<tr> <tr>
<td rowspan="2" class="bg-green-600 bg-opacity-60">離峰時間</td> <td rowspan="2" class="bg-green-600 bg-opacity-60">
<td class="bg-green-600 bg-opacity-20">夏月</td> {{ $t("energy.off_peak_hours") }}
</td>
<td class="bg-green-600 bg-opacity-20">
{{ $t("energy.summer_months") }}
</td>
<td class="bg-green-600 bg-opacity-20">00:00 ~ 09:00</td> <td class="bg-green-600 bg-opacity-20">00:00 ~ 09:00</td>
<td class="bg-green-600 bg-opacity-20"> <td class="bg-green-600 bg-opacity-20">
<input <input
@ -546,8 +664,12 @@ watch(
<td class="bg-green-600 bg-opacity-20">-</td> <td class="bg-green-600 bg-opacity-20">-</td>
</tr> </tr>
<tr> <tr>
<td class="bg-green-600 bg-opacity-20">非夏月</td> <td class="bg-green-600 bg-opacity-20">
<td class="bg-green-600 bg-opacity-20">00:00 ~ 06:00<br />11:00 ~ 14:00</td> {{ $t("energy.non_summer_months") }}
</td>
<td class="bg-green-600 bg-opacity-20">
00:00 ~ 06:00<br />11:00 ~ 14:00
</td>
<td class="bg-green-600 bg-opacity-20">-</td> <td class="bg-green-600 bg-opacity-20">-</td>
<td class="bg-green-600 bg-opacity-20"> <td class="bg-green-600 bg-opacity-20">
<input <input
@ -558,9 +680,13 @@ watch(
</td> </td>
</tr> </tr>
<tr> <tr>
<td>週日及離峰日</td> <td>{{ $t("energy.sunday_and_off_peak_days") }}</td>
<td class="bg-green-600 bg-opacity-60">離峰時間</td> <td class="bg-green-600 bg-opacity-60">
<td colspan="2" class="bg-green-600 bg-opacity-20">全日</td> {{ $t("energy.off_peak_hours") }}
</td>
<td colspan="2" class="bg-green-600 bg-opacity-20">
{{ $t("energy.all_day") }}
</td>
<td class="bg-green-600 bg-opacity-20"> <td class="bg-green-600 bg-opacity-20">
<input <input
type="number" type="number"

View File

@ -1,9 +1,12 @@
<script setup> <script setup>
import Table from "@/components/customUI/Table.vue"; import Table from "@/components/customUI/Table.vue";
import FloorsModal from "./FloorsModal.vue"; import FloorsModal from "./FloorsModal.vue";
import { getAssetFloorList, deleteAssetFloor } from "@/apis/asset"; import { deleteAssetFloor } from "@/apis/asset";
import { onMounted, ref, inject, computed } from "vue"; import { onMounted, ref, inject, computed } from "vue";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
import useBuildingStore from "@/stores/useBuildingStore";
const storeBuild = useBuildingStore();
const FILE_BASEURL = import.meta.env.VITE_FILE_API_BASEURL; const FILE_BASEURL = import.meta.env.VITE_FILE_API_BASEURL;
const { t } = useI18n(); const { t } = useI18n();
const { openToast, cancelToastOpen } = inject("app_toast"); const { openToast, cancelToastOpen } = inject("app_toast");
@ -28,18 +31,10 @@ const columns = computed(() => [
}, },
]); ]);
const dataSource = ref([]);
const loading = ref(false); const loading = ref(false);
const getDataSource = async () => {
loading.value = true;
const res = await getAssetFloorList();
dataSource.value = res.data[0]?.floors.map((d) => ({ ...d, key: d.floor_guid }));
loading.value = false;
};
onMounted(() => { onMounted(() => {
getDataSource(); storeBuild.fetchFloorList(storeBuild.selectedBuilding?.building_guid);
}); });
const formState = ref({ const formState = ref({
@ -47,9 +42,22 @@ const formState = ref({
floorFile: [], floorFile: [],
}); });
const openModal = (record) => { const openModal = (record) => {
if (record.floor_guid) { if (record.floor_guid) {
formState.value = { ...record }; console.log('record',record);
formState.value.floor_guid = record.floor_guid;
formState.value.full_name = record.full_name;
if(record.floor_map_url){
const floorFile = record
? {
// size: record.oriSize,
name: record.floor_map_name,
src: `${record.floor_map_url}.svg`,
ext: 'svg',
}
: {};
formState.value.floorFile = [floorFile];
}
} else { } else {
formState.value = { formState.value = {
full_name: "", full_name: "",
@ -59,14 +67,14 @@ const openModal = (record) => {
floor_modal.showModal(); floor_modal.showModal();
}; };
const remove = async () => { const remove = async (id) => {
openToast("warning", t("msg.sure_to_delete"), "body", async () => { openToast("warning", t("msg.sure_to_delete"), "body", async () => {
await cancelToastOpen(); await cancelToastOpen();
const res = await deleteAssetFloor({ const res = await deleteAssetFloor({
floor_guid: formState.value.floor_guid, floor_guid: id,
}); });
if (res.isSuccess) { if (res.isSuccess) {
getDataSource(); storeBuild.fetchFloorList(storeBuild.selectedBuilding?.building_guid);
openToast("success", t("msg.delete_success")); openToast("success", t("msg.delete_success"));
} else { } else {
openToast("error", res.msg); openToast("error", res.msg);
@ -78,13 +86,13 @@ const remove = async () => {
<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">{{ $t("energy.floor") }}</h3> <h3 class="text-xl mr-5">{{ $t("energy.floor") }}</h3>
<FloorsModal <FloorsModal :formState="formState" :openModal="openModal" />
:formState="formState"
:getData="getDataSource"
:openModal="openModal"
/>
</div> </div>
<Table :columns="columns" :dataSource="dataSource" :loading="loading"> <Table
:columns="columns"
:dataSource="storeBuild.floorList"
:loading="loading"
>
<template #bodyCell="{ record, column, index }"> <template #bodyCell="{ record, column, index }">
<template v-if="column.key === 'index'">{{ index + 1 }}</template> <template v-if="column.key === 'index'">{{ index + 1 }}</template>
<template v-else-if="column.key === 'operation'"> <template v-else-if="column.key === 'operation'">
@ -96,13 +104,17 @@ const remove = async () => {
</button> </button>
<button <button
class="btn btn-sm btn-error text-white" class="btn btn-sm btn-error text-white"
@click.stop.prevent="() => remove()" @click.stop.prevent="() => remove(record.floor_guid)"
> >
{{ $t("button.delete") }} {{ $t("button.delete") }}
</button> </button>
</template> </template>
<template v-else-if="column.key === 'floor_plan'"> <template v-else-if="column.key === 'floor_plan'">
<img :src="`${FILE_BASEURL}/${record.floor_map_url}.svg`" class="w-[200px] bg-white mx-auto" /> <img
v-if="record.floor_map_url"
:src="`${FILE_BASEURL}/${record.floor_map_url}.svg`"
class="w-[200px] bg-white mx-auto"
/>
</template> </template>
<template v-else> <template v-else>
{{ record[column.key] }} {{ record[column.key] }}

View File

@ -5,16 +5,17 @@ import "yup-phone-lite";
import useFormErrorMessage from "@/hooks/useFormErrorMessage"; import useFormErrorMessage from "@/hooks/useFormErrorMessage";
import { postAssetFloor } from "@/apis/asset"; import { postAssetFloor } from "@/apis/asset";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
import useBuildingStore from "@/stores/useBuildingStore";
const storeBuild = useBuildingStore();
const { t } = useI18n(); const { t } = useI18n();
const { openToast } = inject("app_toast"); const { openToast } = inject("app_toast");
const props = defineProps({ const props = defineProps({
formState: Object, formState: Object,
getData: Function,
openModal: Function, openModal: Function,
}); });
const form = ref(null); const form = ref(null);
const floorScheme = yup.object({ const floorScheme = yup.object({
full_name: yup.string().required(t("button.required")), full_name: yup.string().required(t("button.required")),
floorFile: yup.array(), floorFile: yup.array(),
@ -22,7 +23,7 @@ const floorScheme = yup.object({
const updateFileList = (files) => { const updateFileList = (files) => {
console.log("file", files); console.log("file", files);
FloorFormState.value.floorFile = files; props.formState.floorFile = files;
}; };
const { formErrorMsg, handleSubmit, handleErrorReset, updateScheme } = const { formErrorMsg, handleSubmit, handleErrorReset, updateScheme } =
@ -30,23 +31,26 @@ const { formErrorMsg, handleSubmit, handleErrorReset, updateScheme } =
const onCancel = () => { const onCancel = () => {
handleErrorReset(); handleErrorReset();
updateFileList([]);
floor_modal.close(); floor_modal.close();
}; };
const onOk = async () => { const onOk = async () => {
const value = await handleSubmit(floorScheme, props.formState); const value = await handleSubmit(floorScheme, props.formState);
const formData = new FormData(form.value); const formData = new FormData(form.value);
formData.append( if (props.formState.floor_guid) {
"floor_guid", formData.append("floor_guid", props.formState.floor_guid);
selectedOption.value === "add" ? null : currentFloor.value.floor_guid }
); if (props.formState.floorFile[0]) {
formData.append("building_tag", store.selectedBuilding.building_tag); formData.append("initMapName", props.formState.floorFile[0]?.name);
formData.append("initMapName", FloorFormState.value.floorFile[0]?.name); formData.append("mapFile", props.formState.floorFile[0]);
formData.append("mapFile", FloorFormState.value.floorFile[0]); }
formData.append("building_guid", storeBuild.selectedBuilding.building_guid);
formData.delete("floorFile"); formData.delete("floorFile");
const res = await postAssetFloor(formData); const res = await postAssetFloor(formData);
if (res.isSuccess) { if (res.isSuccess) {
props.getData(); storeBuild.fetchFloorList(storeBuild.selectedBuilding?.building_guid);
onCancel(); onCancel();
} else { } else {
openToast("error", res.msg, "#floor_modal"); openToast("error", res.msg, "#floor_modal");

View File

@ -79,7 +79,7 @@ const toggleNode = (node, checked) => {
</span> </span>
</div> </div>
<MITTCheckboxTree <MQTTCheckboxTree
v-if="node.children?.length && node.expanded" v-if="node.children?.length && node.expanded"
:data="node.children" :data="node.children"
:checked-nodes="checkedNodes" :checked-nodes="checkedNodes"

View File

@ -1,16 +1,24 @@
<script setup> <script setup>
import { ref, onMounted, watch, computed } from "vue"; import { ref, onMounted, watch, computed, inject } from "vue";
import { getAssetMainList, getAssetSubList, getIOTSchema } from "@/apis/asset"; import {
getAssetMainList,
getAssetSubList,
getIOTSchema,
getDeviceItem,
deleteDeviceItem,
} from "@/apis/asset";
import useActiveBtn from "@/hooks/useActiveBtn"; import useActiveBtn from "@/hooks/useActiveBtn";
import MQTTListAddModal from "./MQTTListAddModal.vue"; import MQTTListAddModal from "./MQTTListAddModal.vue";
import PointListAddModal from "./PointListAddModal.vue"; import PointListAddModal from "./PointListAddModal.vue";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
const { t } = useI18n(); const { t } = useI18n();
const formState = ref({}); const { openToast, cancelToastOpen } = inject("app_toast");
const MQTTDataSource = ref([]); const MQTTDataSource = ref([]);
const MQTTloading = ref(false); const MQTTloading = ref(false);
const PointsDataSource = ref([]); const PointsDataSource = ref([]);
const Pointsloading = ref(false); const Pointsloading = ref(false);
const variable_id = ref(null);
const editRecord = ref(null);
// //
const { const {
items: mainSysItems, items: mainSysItems,
@ -50,36 +58,44 @@ const getSubSystems = async (id) => {
const MQTTColumns = computed(() => [ const MQTTColumns = computed(() => [
{ {
title: "schema", title: t("setting.schema"),
key: "schema_name", key: "schema_name",
}, },
{ {
title: "point", title: t("setting.point"),
key: "pointOrg", key: "pointOrg",
filter: true, filter: true,
}, },
{ {
title: "Description", title: t("setting.description"),
key: "description", key: "description",
}, },
]); ]);
const PointsColumns = computed(() => [ const PointsColumns = computed(() => [
{ {
title: "IoT 點位名稱", title: t("setting.IoT_point_name"),
key: "IoT_point_name", key: "full_name",
}, },
{ {
title: "小數點個數", title: t("setting.IoT_point_code"),
key: "decimal", key: "points",
}, },
{ {
title: "bool 值", title: t("setting.number_of_decimal_places"),
key: "decimals",
},
{
title: t("setting.boolean_value"),
key: "is_bool", key: "is_bool",
}, },
{ {
title: "點位隱藏", title: t("setting.hide_point"),
key: "is_open", key: "is_link",
},
{
title: t("assetManagement.operation"),
key: "operation",
}, },
]); ]);
@ -97,6 +113,46 @@ const getDataSource = async (id) => {
MQTTloading.value = false; MQTTloading.value = false;
}; };
const getPointsData = async (id) => {
Pointsloading.value = true;
const res = await getDeviceItem(id);
PointsDataSource.value = res.data.map((d) => ({
...d,
key: d.id,
}));
Pointsloading.value = false;
};
const remove = async (id) => {
openToast("warning", t("msg.sure_to_delete"), "body", async () => {
await cancelToastOpen();
const res = await deleteDeviceItem(id);
if (res.isSuccess) {
getPointsData(variable_id.value);
openToast("success", t("msg.delete_success"));
} else {
openToast("error", res.msg);
}
});
};
const openModal = (record) => {
if (record?.id) {
editRecord.value = record;
} else {
editRecord.value = {
id: 0,
variable_id: variable_id.value,
full_name: "",
points: "",
decimals: 0,
is_bool: 0,
is_link: 0,
};
}
point_list_item.showModal();
};
watch( watch(
selectedMainSysItems, selectedMainSysItems,
(newValue) => { (newValue) => {
@ -114,6 +170,8 @@ watch(
(newValue) => { (newValue) => {
if (newValue && newValue.key) { if (newValue && newValue.key) {
getDataSource(parseInt(newValue.key)); getDataSource(parseInt(newValue.key));
getPointsData(parseInt(newValue.key));
variable_id.value = parseInt(newValue.key);
} }
}, },
{ {
@ -157,23 +215,55 @@ onMounted(() => {
/> />
</div> </div>
<div class="flex justify-start items-center mt-5 mb-2"> <div class="flex justify-start items-center mt-5 mb-2">
<h3 class="text-xl mr-5">MQTT_Parse : </h3> <h3 class="text-xl mr-5">{{ $t("setting.MQTT_parse") }} :</h3>
<MQTTListAddModal /> <MQTTListAddModal
:getData="getDataSource"
:pointsData="PointsDataSource"
:variable_id="variable_id"
/>
</div> </div>
<Table <Table
:columns="MQTTColumns" :columns="MQTTColumns"
:dataSource="MQTTDataSource" :dataSource="MQTTDataSource"
:loading="MQTTloading" :loading="MQTTloading"
></Table> >
</Table>
<div class="flex justify-start items-center mt-5 mb-2"> <div class="flex justify-start items-center mt-5 mb-2">
<h3 class="text-xl mr-5">Points : </h3> <h3 class="text-xl mr-5">{{ $t("setting.point") }} :</h3>
<PointListAddModal /> <PointListAddModal
:getData="getPointsData"
:variable_id="variable_id"
:editRecord="editRecord"
:openModal="openModal"
/>
</div> </div>
<Table <Table
:columns="PointsColumns" :columns="PointsColumns"
:dataSource="PointsDataSource" :dataSource="PointsDataSource"
:loading="Pointsloading" :loading="Pointsloading"
></Table> ><template #bodyCell="{ record, column, index }">
<template v-if="column.key === 'is_bool'">
{{ record.is_bool === 1 ? t("alert.yes") : t("alert.no") }}
</template>
<template v-else-if="column.key === 'is_link'">
{{ record.is_link === 1 ? t("alert.yes") : t("alert.no") }}
</template>
<template v-else-if="column.key === 'operation'">
<button
class="btn btn-sm btn-success text-white mr-2"
@click.stop.prevent="() => openModal(record)"
>
{{ $t("button.edit") }}
</button>
<button
class="btn btn-sm btn-error text-white"
@click.stop.prevent="() => remove(record.id)"
>
{{ $t("button.delete") }}
</button>
</template>
</template>
</Table>
</div> </div>
</template> </template>

View File

@ -1,10 +1,15 @@
<script setup> <script setup>
import { ref, onMounted, watch, defineProps } from "vue"; import { ref, onMounted, watch, defineProps, inject } from "vue";
import { getAssetMainList, getAssetSubList, getIOTSchema } from "@/apis/asset"; import { postIOTSchema } from "@/apis/asset";
import useActiveBtn from "@/hooks/useActiveBtn";
import MQTTCheckboxTree from "./MQTTCheckboxTree.vue"; import MQTTCheckboxTree from "./MQTTCheckboxTree.vue";
import generateSchema from "json-schema-generator"; import generateSchema from "json-schema-generator";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
const { openToast } = inject("app_toast");
const props = defineProps({
getData: Function,
pointsData: Object,
variable_id: Number,
});
const { t } = useI18n(); const { t } = useI18n();
// jsonInput // jsonInput
const jsonInput = ref(""); const jsonInput = ref("");
@ -14,10 +19,6 @@ const checkedNodes = ref([]);
const form = ref(null); const form = ref(null);
const schemaName = ref(""); const schemaName = ref("");
const formStates = ref([]); const formStates = ref([]);
const optionData = ref([
{ key: 0, full_name: "否" },
{ key: 1, full_name: "是" },
]);
// JSON Schema // JSON Schema
const customGenerateSchema = (json) => { const customGenerateSchema = (json) => {
@ -92,22 +93,42 @@ watch(
(newCheckedNodes) => { (newCheckedNodes) => {
// node formState // node formState
formStates.value = newCheckedNodes.map((node) => ({ formStates.value = newCheckedNodes.map((node) => ({
IoT_point_schema: node, PointOrg: node,
sys_point_name: null, PointSys: "",
is_bool: 1, item_id: null,
decimal: 0,
is_open: 1,
})); }));
}, },
{ immediate: true } { immediate: true }
); );
const onOk = async () => {
const points = formStates.value.map((state) => ({
PointOrg: state.PointOrg,
PointSys: props.pointsData.find((point) => point.id === state.item_id)
.points,
item_id: state.item_id,
}));
const apiData = {
name: schemaName.value,
variable_id: props.variable_id,
points: points,
};
const res = await postIOTSchema(apiData);
if (res.isSuccess) {
props.getData(props.variable_id);
onCancel();
} else {
openToast("error", res.msg, "#MQTT_Parse_item");
}
};
const openModal = () => { const openModal = () => {
MQTT_Parse_item.showModal(); MQTT_Parse_item.showModal();
}; };
const onCancel = () => { const onCancel = () => {
MQTT_Parse_item.close(); MQTT_Parse_item.close();
clearAll();
}; };
</script> </script>
@ -117,7 +138,7 @@ const onCancel = () => {
</button> </button>
<Modal <Modal
id="MQTT_Parse_item" id="MQTT_Parse_item"
title="MQTT_Parse" :title="t('setting.MQTT_parse')"
:onCancel="onCancel" :onCancel="onCancel"
:width="1600" :width="1600"
> >
@ -126,21 +147,18 @@ const onCancel = () => {
<div class="w-full"> <div class="w-full">
<textarea <textarea
v-model="jsonInput" v-model="jsonInput"
placeholder="請貼上 JSON 格式數據" :placeholder="t('setting.json_format_text')"
class="w-full h-[500px] p-4 border rounded-lg font-mono text-white" class="w-full h-[500px] p-4 border rounded-lg font-mono text-white"
></textarea> ></textarea>
</div> </div>
<div class="my-auto"> <div class="my-auto">
<button @click="parseJson" class="btn-success btn w-24 mb-4"> <button @click="parseJson" class="btn-success btn w-28 mb-4">
轉換 {{ $t("button.convert")
<font-awesome-icon }}<font-awesome-icon :icon="['fas', 'chevron-right']" />
:icon="['fas', 'chevron-right']"
size="lg"
class="block"
/>
</button> </button>
<button @click="clearAll" class="bg-error btn w-24"> <button @click="clearAll" class="bg-error btn w-28">
刪除<font-awesome-icon :icon="['fas', 'trash-alt']" /> {{ $t("button.delete")
}}<font-awesome-icon :icon="['fas', 'trash-alt']" />
</button> </button>
</div> </div>
<div class="w-full p-4 rounded-lg border overflow-y-scroll h-[500px]"> <div class="w-full p-4 rounded-lg border overflow-y-scroll h-[500px]">
@ -149,81 +167,71 @@ const onCancel = () => {
:data="treeData" :data="treeData"
v-model:checked-nodes="checkedNodes" v-model:checked-nodes="checkedNodes"
/> />
<div v-else class="text-white">請在左側輸入 JSON 並點擊轉換按鈕</div> <div v-else class="text-white">
{{ $t("setting.json_click_text") }}
</div>
</div> </div>
</div> </div>
<form ref="form" class="" v-if="checkedNodes.length"> <form ref="form" class="" v-if="checkedNodes.length">
<div class="flex items-center mt-5"> <div class="flex items-center mt-5">
<h2 class="text-lg font-bold whitespace-nowrap me-2">結構名稱 :</h2> <h2 class="text-lg font-bold whitespace-nowrap me-2">
<Input v-model="schemaName" /> {{ $t("setting.schema_name") }}:
</h2>
<input
type="text"
v-model.text="schemaName"
class="input border-info focus-within:border-info"
/>
</div> </div>
<table class="table"> <table class="table w-1/2 mt-2">
<thead> <thead>
<tr> <tr>
<!-- <th>IoT 點位名稱</th> --> <th>{{$t("setting.IoT_point_structure")}}</th>
<th>IoT 點位結構</th> <th>{{$t("setting.system_point_name")}}</th>
<th>系統點位名稱</th>
<!-- <th>bool </th>
<th>小數點個數</th>
<th>點位隱藏</th> -->
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr v-for="(node, index) in checkedNodes" :key="node"> <tr v-for="(node, index) in checkedNodes" :key="node">
<!-- <td> <td>
<Input :value="formStates[index]" name="IoT_point_name" />
</td> -->
<td class="w-1/2">
<Input <Input
:value="formStates[index]" :value="formStates[index]"
name="IoT_point_schema" name="PointOrg"
:readonly="true" :readonly="true"
class="mx-auto"
/> />
</td> </td>
<td> <td>
<select> <Select
<option value="option1">選項 1</option>
<option value="option2">選項 2</option>
<option value="option3">選項 3</option>
</select>
</td>
<!-- <td>
<div class="flex w-36">
<Select
:value="formStates[index]"
selectClass="w-20 mx-auto border-info focus-within:border-info"
name="is_bool"
Attribute="full_name"
:options="optionData"
>
</Select>
</div>
</td>
<td>
<InputNumber
:value="formStates[index]" :value="formStates[index]"
class="mx-auto" class="my-2 mx-auto"
name="decimal" selectClass="border-info focus-within:border-info "
name="item_id"
Attribute="full_name"
:options="props.pointsData"
> >
</InputNumber> </Select>
</td> </td>
<td>
<div class="flex w-36">
<Select
:value="formStates[index]"
selectClass="w-20 mx-auto border-info focus-within:border-info"
name="is_open"
Attribute="full_name"
:options="optionData"
>
</Select>
</div>
</td> -->
</tr> </tr>
</tbody> </tbody>
</table> </table>
</form> </form>
</template> </template>
<template #modalAction>
<button
type="reset"
class="btn btn-outline-success mr-2"
@click.prevent="onCancel"
>
{{ $t("button.cancel") }}
</button>
<button
type="submit"
class="btn btn-outline-success"
@click.stop.prevent="onOk"
>
{{ $t("button.submit") }}
</button>
</template>
</Modal> </Modal>
</template> </template>

View File

@ -1,56 +1,114 @@
<script setup> <script setup>
import { ref, onMounted, watch, defineProps } from "vue"; import { ref, onMounted, watch, defineProps, inject, readonly } from "vue";
import useFormErrorMessage from "@/hooks/useFormErrorMessage"; import useFormErrorMessage from "@/hooks/useFormErrorMessage";
import { postDeviceItem } from "@/apis/asset";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
import * as yup from "yup"; import * as yup from "yup";
const { openToast } = inject("app_toast");
const { t } = useI18n(); const { t } = useI18n();
const props = defineProps({
getData: Function,
openModal: Function,
editRecord: Object,
variable_id: Number,
});
const form = ref(null); const form = ref(null);
const formState = ref({ const formState = ref({
id: 0, id: 0,
IoT_point_name: "", variable_id: props.variable_id,
full_name: "",
points: "",
decimals: 0,
is_bool: 0, is_bool: 0,
decimal: 2, is_link: 0,
is_open: 0,
}); });
let schema = ref( let schema = ref(
yup.object({ yup.object({
IoT_point_name: yup.string().required(t("button.required")), full_name: yup.string().required(t("button.required")),
is_bool: yup.string().required(t("button.required")), decimals: yup.number().required(t("button.required")),
decimal: yup.string().required(t("button.required")), is_bool: yup.number().required(t("button.required")),
is_open: yup.string().required(t("button.required")), is_link: yup.number().required(t("button.required")),
}) })
); );
const { formErrorMsg, handleSubmit, handleErrorReset, updateScheme } = const { formErrorMsg, handleSubmit, handleErrorReset, updateScheme } =
useFormErrorMessage(schema.value); useFormErrorMessage(schema.value);
const openModal = () => { const onOk = async () => {
point_list_item.showModal(); const values = await handleSubmit(schema.value, formState.value);
console.log("value", values);
const res = await postDeviceItem({
...values,
decimals: Number(values.decimals),
is_bool: Number(values.is_bool),
is_link: Number(values.is_link),
});
if (res.isSuccess) {
props.getData(props.variable_id);
onCancel();
} else {
openToast("error", res.msg, "#point_list_item");
}
}; };
const onCancel = () => { const onCancel = () => {
point_list_item.close(); point_list_item.close();
}; };
watch(
() => props.editRecord,
(newValue) => {
if (newValue) {
formState.value = {
...newValue,
};
} else {
formState.value = {
id: 0,
variable_id: props.variable_id,
full_name: "",
points: "",
decimals: 0,
is_bool: 0,
is_link: 0,
};
}
}
);
</script> </script>
<template> <template>
<button class="btn btn-sm btn-add mr-3" @click.stop.prevent="openModal"> <button class="btn btn-sm btn-add mr-3" @click.stop.prevent="props.openModal">
<font-awesome-icon :icon="['fas', 'plus']" />{{ $t("button.add") }} <font-awesome-icon :icon="['fas', 'plus']" />{{ $t("button.add") }}
</button> </button>
<Modal id="point_list_item" title="Points" :onCancel="onCancel" :width="710"> <Modal id="point_list_item" title="Points" :onCancel="onCancel" :width="710">
<template #modalContent> <template #modalContent>
<form ref="form" class="mt-5 w-full flex flex-wrap justify-between"> <form ref="form" class="mt-5 w-full flex flex-wrap justify-between">
<Input :value="formState" class="my-2" name="IoT_point_name"> <Input :value="formState" class="my-2" name="full_name">
<template #topLeft>IoT 點位名稱</template> <template #topLeft>{{ $t("setting.IoT_point_name") }}</template>
<template #bottomLeft <template #bottomLeft
><span class="text-error text-base"> ><span class="text-error text-base">
{{ formErrorMsg.IoT_point_name }} {{ formErrorMsg.full_name }}
</span></template </span></template
></Input ></Input
> >
<InputNumber :value="formState" class="my-2" name="decimal"> <Input
<template #topLeft>小數點個數</template> :value="formState"
class="my-2"
name="points"
:disabled="props.editRecord?.id"
>
<template #topLeft>{{ $t("setting.IoT_point_code") }}</template>
<template #bottomLeft
><span class="text-error text-base">
{{ formErrorMsg.points }}
</span></template
></Input
>
<InputNumber :value="formState" class="my-2" name="decimals">
<template #topLeft>{{
$t("setting.number_of_decimal_places")
}}</template>
</InputNumber> </InputNumber>
<RadioGroup <RadioGroup
class="my-2" class="my-2"
@ -70,12 +128,12 @@ const onCancel = () => {
]" ]"
:required="true" :required="true"
> >
<template #topLeft>bool </template> <template #topLeft>{{ $t("setting.boolean_value") }}</template>
</RadioGroup> </RadioGroup>
<RadioGroup <RadioGroup
class="my-2" class="my-2"
name="is_open" name="is_link"
:value="formState" :value="formState"
:items="[ :items="[
{ {
@ -91,10 +149,26 @@ const onCancel = () => {
]" ]"
:required="true" :required="true"
> >
<template #topLeft>點位隱藏</template> <template #topLeft>{{ $t("setting.hide_point") }}</template>
</RadioGroup> </RadioGroup>
</form> </form>
</template> </template>
<template #modalAction>
<button
type="reset"
class="btn btn-outline-success mr-2"
@click.prevent="onCancel"
>
{{ $t("button.cancel") }}
</button>
<button
type="submit"
class="btn btn-outline-success"
@click.stop.prevent="onOk"
>
{{ $t("button.submit") }}
</button>
</template>
</Modal> </Modal>
</template> </template>

View File

@ -93,7 +93,7 @@ watch(
}, },
params.data[2] params.data[2]
); );
selected_dbid.value[0] = params.data[2].forge_dbid; selected_dbid.value[1] = params.data[2].spriteDbId;
}); });
} }
}, },
@ -146,7 +146,7 @@ watch(
allData.value allData.value
) { ) {
selectedData.value = selectedData.value.map((item) => { selectedData.value = selectedData.value.map((item) => {
if (item[2].forge_dbid === newSelectedDbid[0]) { if (item[2].spriteDbId === newSelectedDbid[1]) {
return [...item.slice(0, 2), { ...item[2], is2DActive: true }]; return [...item.slice(0, 2), { ...item[2], is2DActive: true }];
} }
return [...item.slice(0, 2), { ...item[2], is2DActive: false }]; return [...item.slice(0, 2), { ...item[2], is2DActive: false }];

View File

@ -1,51 +1,118 @@
<script setup> <script setup>
import { computed, inject, watch } from "vue" import { computed, inject, watch } from "vue";
import useSystemShowData from "@/hooks/useSystemShowData" import useSystemShowData from "@/hooks/useSystemShowData";
const { getCurrentInfoModalData, selected_dbid } = inject("system_selectedDevice") const { getCurrentInfoModalData, selected_dbid } = inject(
"system_selectedDevice"
);
const { subscribeData } = inject("system_deviceList"); const { subscribeData } = 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 fitToView = (forge_dbid, spriteDbId) => { const fitToView = (forge_dbid, spriteDbId) => {
selected_dbid.value = [forge_dbid, spriteDbId]; selected_dbid.value = [forge_dbid, spriteDbId];
}; };
const openDialog = () => {
document.getElementById("iframe_modal").showModal();
};
const cancelDialog = () => {
document.getElementById("iframe_modal").close();
};
</script> </script>
<template> <template>
<!-- <InfoModal :data="currentInfoModalData" /> --> <dialog
id="iframe_modal"
className="modal w-[590px] h-[490px] my-auto rounded-lg mx-auto"
>
<iframe
src="https://app.mockplus.cn/p/zNB4y8k5L"
frameborder="0"
class="w-full h-full"
></iframe>
<Button
type="link"
class="btn-link btn-text-without-border z-20 absolute right-[2.3rem] top-[2.2rem]"
@click="cancelDialog"
>
<font-awesome-icon
:icon="['fas', 'times']"
class="text-[#a5abb1] text-2xl"
/>
</Button>
</dialog>
<template v-if="showData.length > 0"> <template v-if="showData.length > 0">
<div class="equipment-show" v-for="d in showData" :key="d.full_name"> <div class="equipment-show" v-for="d in showData" :key="d.full_name">
<template v-if="d.device_list.length > 0"> <template v-if="d.device_list.length > 0">
<p class="title">{{ d.full_name }}</p> <p class="title">{{ d.full_name }}</p>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-5"> <div class="grid grid-cols-1 lg:grid-cols-2 gap-5">
<div class="col-auto relative" v-for="device in d.device_list" :key="device.device_guid"> <div
<div class="item h-full cursor-pointer" @click="() => fitToView(device.forge_dbid, device.spriteDbId)"> class="col-auto relative"
v-for="device in d.device_list"
:key="device.device_guid"
>
<div
class="item h-full cursor-pointer"
@click="() => fitToView(device.forge_dbid, device.spriteDbId)"
>
<div class="left h-full flex flex-wrap justify-center"> <div class="left h-full flex flex-wrap justify-center">
<div class="sec02 w-full"> <div class="sec02 w-full">
<img v-if="device.device_image_url" :src="`${FILE_BASEURL}/upload/device_icon/${device.device_image}`" <img
:alt="device.device_image"> v-if="device.device_image_url"
:src="`${FILE_BASEURL}/upload/device_icon/${device.device_image}`"
:alt="device.device_image"
/>
<span v-else></span> <span v-else></span>
<span>{{ device.full_name }}</span> <span>{{ device.full_name }}</span>
</div> </div>
<div class="flex justify-between w-full self-end"> <div class="flex justify-between w-full self-end">
<div class="sec03"> <div class="sec03">
<span class="w-5 h-5 rounded-full" :style="{ backgroundColor: device.device_normal_color }"></span> <span
class="w-5 h-5 rounded-full"
:style="{
backgroundColor:
device.full_name === 'SmartSocket-AA001'
? 'red'
: device.full_name === 'SmartSocket-AA003' ||
device.full_name === 'SmartSocket-AA004'
? 'gray'
: device.device_normal_color,
}"
></span>
<span class="mx-2">{{ $t("system.status") }}:</span> <span class="mx-2">{{ $t("system.status") }}:</span>
<span>{{ device.device_status }}</span> <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 class="btn-text border-0 " <button
@click.prevent="(e) => getCurrentInfoModalData(e, { left: e.clientX, top: e.clientY }, device)">{{ class="btn-text border-0"
$t("system.details") }}</button> @click.prevent="
(e) => {
if (device.full_name === 'SmartSocket-AA001') {
openDialog();
} else {
getCurrentInfoModalData(
e,
{ left: e.clientX, top: e.clientY },
device
);
}
}
"
>
{{ $t("system.details") }}
</button>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</template> </template>
@ -53,7 +120,7 @@ const fitToView = (forge_dbid, spriteDbId) => {
</template> </template>
</template> </template>
<style lang='css' scoped> <style lang="css" scoped>
/*設備顯示*/ /*設備顯示*/
.title { .title {
@apply text-lg text-white relative inline-block mb-5 after:absolute after:-bottom-2 after:left-1/4 after:w-4/5 after:h-[1px] after:bg-info; @apply text-lg text-white relative inline-block mb-5 after:absolute after:-bottom-2 after:left-1/4 after:w-4/5 after:h-[1px] after:bg-info;
@ -88,7 +155,7 @@ const fitToView = (forge_dbid, spriteDbId) => {
} }
.equipment-show .item .sec01 span:nth-child(2) { .equipment-show .item .sec01 span:nth-child(2) {
font-size: .6rem; font-size: 0.6rem;
} }
.equipment-show .item .sec02 { .equipment-show .item .sec02 {
@ -144,4 +211,4 @@ const fitToView = (forge_dbid, spriteDbId) => {
display: block; display: block;
margin-right: 10px; margin-right: 10px;
} */ } */
</style> </style>

View File

@ -12,18 +12,24 @@ const {
changeActiveBtn: changeDeptActiveBtn, changeActiveBtn: changeDeptActiveBtn,
setItems: setDeptItems, setItems: setDeptItems,
selectedBtn: selectedDeptItems, selectedBtn: selectedDeptItems,
} = useActiveBtn(); } = useActiveBtn("multiple");
onMounted(() => { watch(
const deptList = [ () => storeBuild.deptList,
{ (newValue) => {
title: "All", if (newValue) {
key: "main", const deptList = newValue.map((d) => ({
}, ...d,
...storeBuild.deptList, active: true,
]; }));
setDeptItems(deptList); setDeptItems(deptList);
}); }
},
{
deep: true,
immediate: true,
}
);
</script> </script>
<template> <template>
@ -34,6 +40,7 @@ onMounted(() => {
<ButtonGroup <ButtonGroup
:items="sysDeptItems" :items="sysDeptItems"
className="btn-xs rounded-md" className="btn-xs rounded-md"
:withLine="true"
:onclick=" :onclick="
(e, item) => { (e, item) => {
changeDeptActiveBtn(item); changeDeptActiveBtn(item);

View File

@ -1,74 +1,88 @@
<script setup> <script setup>
import { useRoute, useRouter } from 'vue-router'; import { useRoute, useRouter } from "vue-router";
import { getAssetFloorList } from "@/apis/asset"; import { onMounted, ref, watch, inject } from "vue";
import { onMounted, ref, watch, inject } from 'vue';
import useBuildingStore from "@/stores/useBuildingStore"; import useBuildingStore from "@/stores/useBuildingStore";
import useActiveBtn from "@/hooks/useActiveBtn" import useActiveBtn from "@/hooks/useActiveBtn";
const route = useRoute() const route = useRoute();
const router = useRouter() const router = useRouter();
const store = useBuildingStore(); const store = useBuildingStore();
const { updateCurrentFloor } = inject("system_deviceList") const { updateCurrentFloor } = inject("system_deviceList");
const { items, changeActiveBtn, setItems, selectedBtn } = useActiveBtn(); const { items, changeActiveBtn, setItems, selectedBtn } = useActiveBtn();
const getFloors = async () => {
const res = await getAssetFloorList()
let data = res.data.find(d => d.building_tag === store.selectedBuilding?.building_tag)
data = [
{
title: "All",
key: "main",
active: route.params.floor_id === "main",
},
...data.floors.map((d, idx) => ({
title: d.full_name,
key: d.floor_guid,
active: route.params.floor_id === d.floor_guid,
map_url: d.floor_map_url ? d.floor_map_url + ".svg" : null
}))
]
setItems(data);
updateCurrentFloor(data)
}
const onClick = (item) => { const onClick = (item) => {
changeActiveBtn(item) changeActiveBtn(item);
router.push({ router.push({
name: 'sub_system', params: { name: "sub_system",
...route.params, floor_id: item.key params: {
}, query: { ...route.query, gas: route.query.gas, mode: item.key === "main" ? "3D" : route.query.mode } ...route.params,
}) floor_id: item.key,
},
query: {
...route.query,
gas: route.query.gas,
mode: item.key === "main" ? "3D" : route.query.mode,
},
});
};
} watch(
() => route.params.sub_system_id,
watch(() => route.params.sub_system_id, (newValue, oldValue) => { (newValue, oldValue) => {
if (newValue !== oldValue) { if (newValue !== oldValue) {
setItems(items.value.map((item, index) => ({ ...item, active: index === 0 }))); setItems(
items.value.map((item, index) => ({ ...item, active: index === 0 }))
);
}
},
{
deep: true,
} }
);
}, { watch(
deep: true () => store.floorList,
}) (newValue) => {
if (newValue) {
watch(() => store.selectedBuilding, (newValue) => { console.log('newValue',newValue);
newValue && getFloors()
}, { const floorList = [
deep: true, {
immediate: true title: "All",
}) key: "main",
active: route.params.floor_id === "main",
},
...store.floorList.map((d, idx) => ({
title: d.full_name,
key: d.floor_guid,
active: route.params.floor_id === d.floor_guid,
map_url: d.floor_map_url ? d.floor_map_url + ".svg" : null,
})),
];
setItems(floorList);
updateCurrentFloor(floorList);
}
},
{
deep: true,
immediate: true,
}
);
</script> </script>
<template> <template>
<div class="flex items-center gap-3"> <div class="flex items-center gap-3">
<span class="text-md font-extrabold">{{ $t("energy.floor") }} :</span> <span class="text-md font-extrabold">{{ $t("energy.floor") }} :</span>
<ButtonGroup :items="items" :withLine="false" className="btn-xs rounded-md" :onclick="(e, item) => onClick(item)" /> <ButtonGroup
:items="items"
:withLine="true"
className="btn-xs rounded-md"
:onclick="(e, item) => onClick(item)"
/>
</div> </div>
</template> </template>
<style lang='scss' scoped></style> <style lang="scss" scoped></style>