登入帳號紀錄cookie | table的過濾篩選可以輸入關鍵字 | 能源管理: 新增能源分析、能號報表 | 帳號管理:過濾webUser、admin | 告警設定: 新增警示時間 | 設備管理: iot欄位預設、tag_name只有在webUser時顯示、系統類別和設備類別的更新時如果沒有subSys_id時表格清空新增設備按鈕也不能按、樓層2d圖更新bug修正 | 系統監控: 系統小卡的desktop要根據ori_device_name分類
This commit is contained in:
parent
9e5ff1544c
commit
8e2b5e1e2c
@ -14,3 +14,7 @@ export const GET_OUTLIERS_LIST_API = `api/Alarm/GetAlarmSetting`;
|
||||
export const GET_OUTLIERS_DEVLIST_API = `api/Alarm/GetDevList`; // 取得設備
|
||||
export const GET_OUTLIERS_POINTS_API = `api/Alarm/GetAlarmPoints`; // 取得點位
|
||||
export const POST_OUTLIERS_SETTING_API = `api/Alarm/SaveAlarmSetting`; // 新增與修改
|
||||
|
||||
export const GET_ALERT_SCHEDULE_LIST_API = `api/Alarm/GetAlarmSchedule`;
|
||||
export const POST_ALERT_SCHEDULE = `api/Alarm/SaveAlarmSchedule`;
|
||||
export const DELETE_ALERT_SCHEDULE = `api/Alarm/DeleteAlarmSchedule`;
|
@ -12,6 +12,9 @@ import {
|
||||
POST_ALERT_MEMBER,
|
||||
DELETE_ALERT_MEMBER,
|
||||
GET_NOTICE_LIST_API,
|
||||
GET_ALERT_SCHEDULE_LIST_API,
|
||||
POST_ALERT_SCHEDULE,
|
||||
DELETE_ALERT_SCHEDULE,
|
||||
} from "./api";
|
||||
import instance from "@/util/request";
|
||||
import apihandler from "@/util/apihandler";
|
||||
@ -156,3 +159,34 @@ export const postOutliersSetting = async (data) => {
|
||||
code: res.code,
|
||||
});
|
||||
};
|
||||
|
||||
export const getAlarmScheduleList = async () => {
|
||||
const res = await instance.post(GET_ALERT_SCHEDULE_LIST_API, {});
|
||||
|
||||
return apihandler(res.code, res.data, {
|
||||
msg: res.msg,
|
||||
code: res.code,
|
||||
});
|
||||
};
|
||||
|
||||
export const postAlertSchedule = async (data) => {
|
||||
const res = await instance.post(POST_ALERT_SCHEDULE, data);
|
||||
|
||||
return apihandler(res.code, res.data, {
|
||||
msg: res.msg,
|
||||
code: res.code,
|
||||
});
|
||||
};
|
||||
|
||||
export const deleteAlarmSchedule = async (id) => {
|
||||
try {
|
||||
const res = await instance.post(DELETE_ALERT_SCHEDULE, { id });
|
||||
return {
|
||||
isSuccess: res.code === "0000",
|
||||
msg: res.msg || "刪除成功",
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("API request failed", error);
|
||||
return { isSuccess: false, msg: "API request failed" };
|
||||
}
|
||||
};
|
@ -24,3 +24,5 @@ export const GET_ASSET_IOT_SCHEMA_API = `/AssetManage/GetResponseSchema`;
|
||||
export const GET_ASSET_DEPARTMENT_API = `/AssetManage/GetDepartment`;
|
||||
export const POST_ASSET_DEPARTMENT_API = `/AssetManage/SaveDepartment`;
|
||||
export const DELETE_ASSET_DEPARTMENT_API = `/AssetManage/DeleteDepartment`;
|
||||
|
||||
export const GET_ASSET_ELECTYPE_API = `/AssetManage/GetElecType`;
|
||||
|
@ -18,6 +18,7 @@ import {
|
||||
GET_ASSET_DEPARTMENT_API,
|
||||
POST_ASSET_DEPARTMENT_API,
|
||||
DELETE_ASSET_DEPARTMENT_API,
|
||||
GET_ASSET_ELECTYPE_API,
|
||||
} from "./api";
|
||||
import instance from "@/util/request";
|
||||
import apihandler from "@/util/apihandler";
|
||||
@ -203,8 +204,8 @@ export const getAssetSubPoint = async (sub_system_tag) => {
|
||||
});
|
||||
};
|
||||
|
||||
export const getIOTSchema = async (sub_system_tag, points) => {
|
||||
const res = await instance.post(GET_ASSET_IOT_SCHEMA_API, {});
|
||||
export const getIOTSchema = async (variable_id) => {
|
||||
const res = await instance.post(GET_ASSET_IOT_SCHEMA_API, {variable_id});
|
||||
|
||||
return apihandler(res.code, res.data, {
|
||||
msg: res.msg,
|
||||
@ -241,3 +242,12 @@ export const deleteDepartmentItem = async (id) => {
|
||||
code: res.code,
|
||||
});
|
||||
};
|
||||
|
||||
export const getElecTypeList = async () => {
|
||||
const res = await instance.post(GET_ASSET_ELECTYPE_API, {});
|
||||
|
||||
return apihandler(res.code, res.data, {
|
||||
msg: res.msg,
|
||||
code: res.code,
|
||||
});
|
||||
};
|
@ -1,3 +1,7 @@
|
||||
export const GET_REALTIME_DIST_API = `/api/Energe/GetRealTimeDistribution`;
|
||||
export const GET_ELECUSE_DAY_API = `/api/Energe/GetElecUseDay`;
|
||||
export const GET_TAI_POWER_API = `/api/Energe/GetTaipower`;
|
||||
|
||||
export const GET_SIDEBAR_API = `/api/Energe/GetEnergySideBar`;
|
||||
export const GET_SEARCH_API = `/api/Energe/GetFilter`;
|
||||
|
||||
|
@ -2,6 +2,8 @@ import {
|
||||
GET_REALTIME_DIST_API,
|
||||
GET_ELECUSE_DAY_API,
|
||||
GET_TAI_POWER_API,
|
||||
GET_SIDEBAR_API,
|
||||
GET_SEARCH_API,
|
||||
} from "./api";
|
||||
import instance from "@/util/request";
|
||||
import apihandler from "@/util/apihandler";
|
||||
@ -32,3 +34,21 @@ export const getTaipower = async () => {
|
||||
code: res.code,
|
||||
});
|
||||
};
|
||||
|
||||
export const getEnergySideBar = async () => {
|
||||
const res = await instance.post(GET_SIDEBAR_API);
|
||||
|
||||
return apihandler(res.code, res.data, {
|
||||
msg: res.msg,
|
||||
code: res.code,
|
||||
});
|
||||
};
|
||||
|
||||
export const getEnergySearch = async (type) => {
|
||||
const res = await instance.post(GET_SEARCH_API, { type });
|
||||
|
||||
return apihandler(res.code, res.data, {
|
||||
msg: res.msg,
|
||||
code: res.code,
|
||||
});
|
||||
};
|
||||
|
@ -12,9 +12,15 @@ import instance, { fileInstance } from "@/util/request";
|
||||
import apihandler from "@/util/apiHandler";
|
||||
import downloadExcel from "@/util/downloadExcel";
|
||||
|
||||
export const getHistorySideBar = async (sub_system_tag) => {
|
||||
export const getHistorySideBar = async ({
|
||||
sub_system_tag,
|
||||
department_id,
|
||||
elec_type_id,
|
||||
}) => {
|
||||
const res = await instance.post(GET_HISTORY_SIDEBAR_API, {
|
||||
sub_system_tag,
|
||||
department_id,
|
||||
elec_type_id,
|
||||
});
|
||||
|
||||
return apihandler(res.code, res.data, {
|
||||
@ -42,6 +48,7 @@ export const getHistoryData = async ({
|
||||
End_time,
|
||||
Device_list,
|
||||
Points,
|
||||
table_type,
|
||||
}) => {
|
||||
/*
|
||||
{
|
||||
@ -62,6 +69,7 @@ export const getHistoryData = async ({
|
||||
Device_list: Array.isArray(Device_list) ? Device_list : [Device_list],
|
||||
Points: Array.isArray(Points) ? Points : [Points],
|
||||
Type: parseInt(Type),
|
||||
table_type: parseInt(table_type),
|
||||
});
|
||||
|
||||
return apihandler(res.code, res.data, {
|
||||
|
@ -13,6 +13,10 @@ export async function Login({ account, password }) {
|
||||
document.cookie = `JWT-Authorization=${res.data.token}; Max-Age=${
|
||||
24 * 60 * 60 * 1000
|
||||
}`;
|
||||
// 設定 user_name Cookie
|
||||
document.cookie = `user_name=${res.data.user_name}; Max-Age=${
|
||||
24 * 60 * 60 * 1000
|
||||
}`;
|
||||
}
|
||||
|
||||
return apihandler(res.code, res.data, {
|
||||
|
@ -118,6 +118,6 @@ const curWidth = computed(() => {
|
||||
</style>
|
||||
<style scoped>
|
||||
.line {
|
||||
@apply mr-3 relative z-20 after:absolute after:top-1/2 after:-right-3 after:w-3 after:h-[1px] after:bg-info after:z-10 last:after:h-0;
|
||||
@apply mr-3 relative z-30 after:absolute after:top-1/2 after:-right-3 after:w-3 after:h-[1px] after:bg-info after:z-10 last:after:h-0;
|
||||
}
|
||||
</style>
|
||||
|
@ -51,7 +51,7 @@ watch(
|
||||
);
|
||||
|
||||
const updateDataSource = (data) => {
|
||||
console.log("update", data);
|
||||
// console.log("update", data);
|
||||
currentDataSource.value = data;
|
||||
};
|
||||
provide("current_table_data", {
|
||||
@ -62,6 +62,7 @@ const sortRule = ref({});
|
||||
const filterColumn = ref({});
|
||||
const filterItems = ref({});
|
||||
const selectedFilterItem = ref({});
|
||||
const searchQuery = ref("");
|
||||
|
||||
const toggleFilterModal = (key) => {
|
||||
let newFilter = Object.assign(filterColumn.value);
|
||||
@ -136,7 +137,10 @@ const form = ref(null);
|
||||
|
||||
const onFilter = (key, reset = false) => {
|
||||
const formData = new FormData(form.value);
|
||||
reset && formData.delete(key);
|
||||
if (reset) {
|
||||
formData.delete(key);
|
||||
searchQuery.value = "";
|
||||
}
|
||||
for (let [name, value] of formData) {
|
||||
console.log(name, value);
|
||||
}
|
||||
@ -232,13 +236,28 @@ watch(
|
||||
"
|
||||
@click="() => toggleFilterModal(column.key)"
|
||||
/>
|
||||
<div
|
||||
class="absolute top-full -left-1/2 z-50"
|
||||
v-if="filterColumn[column.key]"
|
||||
>
|
||||
<div class="fixed z-50" v-if="filterColumn[column.key]">
|
||||
<div class="card min-w-max bg-body shadow-xl px-10 py-5">
|
||||
<label
|
||||
class="input input-bordered bg-transparent rounded-lg flex items-center px-2 mb-4 border-success focus-within:border-success"
|
||||
>
|
||||
<font-awesome-icon
|
||||
:icon="['fas', 'search']"
|
||||
class="w-6 h-6 mr-2 text-success"
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
:placeholder="t('operation.enter_text')"
|
||||
class="text-white bg-transparent w-full"
|
||||
v-model="searchQuery"
|
||||
/>
|
||||
</label>
|
||||
<div class="max-h-72 overflow-x-auto px-2">
|
||||
<Checkbox
|
||||
v-for="item in filterItems[column.key]"
|
||||
v-for="item in filterItems[column.key].filter(
|
||||
(item) =>
|
||||
item.name && item.name.includes(searchQuery)
|
||||
)"
|
||||
:title="item.name"
|
||||
:value="item.name"
|
||||
:key="item.name"
|
||||
@ -248,6 +267,7 @@ watch(
|
||||
"
|
||||
className="justify-start"
|
||||
/>
|
||||
</div>
|
||||
<div class="card-actions mt-4 justify-end">
|
||||
<input
|
||||
type="reset"
|
||||
|
@ -2,6 +2,7 @@
|
||||
import { onMounted, ref, watch, computed } from "vue";
|
||||
import { AUTHPAGES } from "@/constant";
|
||||
import { getAuth, getAllSysSidebar } from "@/apis/building";
|
||||
import { getEnergySideBar } from "@/apis/energy";
|
||||
import useBuildingStore from "@/stores/useBuildingStore";
|
||||
import useUserInfoStore from "@/stores/useUserInfoStore";
|
||||
import { useI18n } from "vue-i18n";
|
||||
@ -13,6 +14,8 @@ const store = useUserInfoStore();
|
||||
const buildingStore = useBuildingStore();
|
||||
const route = useRoute();
|
||||
const openKeys = ref([]); // 追蹤當前打開的子菜單
|
||||
const menu_array = ref([]);
|
||||
const currentAuthCode = ref("");
|
||||
|
||||
const iniFroList = async () => {
|
||||
const res = await getAuth(locale.value);
|
||||
@ -37,9 +40,19 @@ const open = ref(false);
|
||||
const getSubMonitorPage = async (building) => {
|
||||
const res = await getAllSysSidebar();
|
||||
buildingStore.mainSubSys = res.data.history_Main_Systems;
|
||||
menu_array.value = res.data.history_Main_Systems;
|
||||
};
|
||||
const showDrawer = () => {
|
||||
const getSubEnergyPage = async () => {
|
||||
const res = await getEnergySideBar();
|
||||
menu_array.value = res.data;
|
||||
};
|
||||
const showDrawer = (authCode) => {
|
||||
if (authCode === "PF1") {
|
||||
getSubMonitorPage();
|
||||
} else if (authCode === "PF2") {
|
||||
getSubEnergyPage();
|
||||
}
|
||||
currentAuthCode.value = authCode;
|
||||
open.value = true;
|
||||
};
|
||||
const onClose = () => {
|
||||
@ -89,14 +102,19 @@ onMounted(() => {
|
||||
<li
|
||||
v-for="page in authPages"
|
||||
class="flex flex-col items-center justify-center"
|
||||
:key="page.authCode"
|
||||
>
|
||||
<a
|
||||
v-if="page.authCode === 'PF1'"
|
||||
@click="showDrawer"
|
||||
v-if="page.authCode === 'PF1' || page.authCode === 'PF2'"
|
||||
@click="showDrawer(page.authCode)"
|
||||
:class="
|
||||
twMerge(
|
||||
'flex lg:flex-col justify-center items-center btn-group text-white cursor-pointer',
|
||||
route.fullPath.includes('/system')
|
||||
page.authCode === 'PF1' && route.fullPath.includes('/system')
|
||||
? 'router-link-active router-link-exact-active'
|
||||
: '',
|
||||
page.authCode === 'PF2' &&
|
||||
route.fullPath.includes('/energyManagement')
|
||||
? 'router-link-active router-link-exact-active'
|
||||
: ''
|
||||
)
|
||||
@ -142,22 +160,26 @@ onMounted(() => {
|
||||
@openChange="handleOpenChange"
|
||||
>
|
||||
<a-sub-menu
|
||||
v-for="main in buildingStore.mainSubSys"
|
||||
v-for="main in menu_array"
|
||||
:key="main.main_system_tag"
|
||||
:title="main.full_name"
|
||||
v-if="menu_array.length > 0 && open"
|
||||
>
|
||||
<a-menu-item
|
||||
v-for="sub in main.history_Sub_systems"
|
||||
:key="sub.sub_system_tag"
|
||||
v-for="sub in currentAuthCode === 'PF1'
|
||||
? main.history_Sub_systems
|
||||
: main.sub"
|
||||
:key="sub.sub_system_tag+`_`+sub.type"
|
||||
@click="() => onClose()"
|
||||
>
|
||||
<router-link
|
||||
:to="{
|
||||
name: 'sub_system',
|
||||
name:
|
||||
currentAuthCode === 'PF2' ? 'energyManagement' : 'sub_system',
|
||||
params: {
|
||||
main_system_id: main.main_system_tag,
|
||||
sub_system_id: sub.sub_system_tag,
|
||||
floor_id: 'main',
|
||||
...(currentAuthCode === 'PF2' ? { type: sub.type } : { floor_id: 'main' }),
|
||||
},
|
||||
}"
|
||||
>
|
||||
|
@ -82,7 +82,23 @@
|
||||
"total_elec_cost": "总电费",
|
||||
"carbon_equivalent": "碳排当量",
|
||||
"edit_carbon_emission": "编辑碳排放系数",
|
||||
"carbon_emission_coefficient": "碳排放系数"
|
||||
"carbon_emission_coefficient": "碳排放系数",
|
||||
"electricity_classification": "用电分类",
|
||||
"electricity_price": "电费每度单价",
|
||||
"floor": "楼层",
|
||||
"maximum": "最大值",
|
||||
"maximum_time": "最大值时间",
|
||||
"minimum": "最小值",
|
||||
"minimum_time": "最小值时间",
|
||||
"average_value": "平均值",
|
||||
"start_value": "起始值(kWh)",
|
||||
"end_value": "截止值(kWh)",
|
||||
"difference": "差值(kWh)",
|
||||
"power_consumption": "用电量(kWh)",
|
||||
"ranking": "排名",
|
||||
"subtotal": "小计",
|
||||
"unit_price": "单价",
|
||||
"total_amount": "金额总计"
|
||||
},
|
||||
"alarm": {
|
||||
"title": "显示警告",
|
||||
@ -152,7 +168,16 @@
|
||||
"choose": "选择",
|
||||
"day_time": "星期/时间",
|
||||
"click_time_period": "请用滑鼠点击时间段",
|
||||
"clear": "清空"
|
||||
"clear": "清空",
|
||||
"sunday": "星期日",
|
||||
"monday": "星期一",
|
||||
"tuesday": "星期二",
|
||||
"wednesday": "星期三",
|
||||
"thursday": "星期四",
|
||||
"friday": "星期五",
|
||||
"saturday": "星期六",
|
||||
"schedule_name": "时段名称",
|
||||
"schedule_content": "时段内容"
|
||||
},
|
||||
"operation": {
|
||||
"title": "运维管理",
|
||||
|
@ -82,7 +82,23 @@
|
||||
"total_elec_cost": "總電費",
|
||||
"carbon_equivalent": "碳排當量",
|
||||
"edit_carbon_emission": "編輯碳排放係數",
|
||||
"carbon_emission_coefficient":"碳排放係數"
|
||||
"carbon_emission_coefficient": "碳排放係數",
|
||||
"electricity_classification": "用電分類",
|
||||
"electricity_price": "電費每度單價",
|
||||
"floor": "樓層",
|
||||
"maximum": "最大值",
|
||||
"maximum_time": "最大值時間",
|
||||
"minimum": "最小值",
|
||||
"minimum_time": "最小值時間",
|
||||
"average_value": "平均值",
|
||||
"start_value": "起始值(kWh)",
|
||||
"end_value": "截止值(kWh)",
|
||||
"difference": "差值(kWh)",
|
||||
"power_consumption": "用電量(kWh)",
|
||||
"ranking": "排名",
|
||||
"subtotal": "小計",
|
||||
"unit_price": "單價",
|
||||
"total_amount": "金額總計"
|
||||
},
|
||||
"alarm": {
|
||||
"title": "顯示警告",
|
||||
@ -152,7 +168,16 @@
|
||||
"choose": "選擇",
|
||||
"day_time": "星期/時間",
|
||||
"click_time_period": "請用滑鼠點擊時間段",
|
||||
"clear":"清空"
|
||||
"clear": "清空",
|
||||
"sunday": "星期日",
|
||||
"monday": "星期一",
|
||||
"tuesday": "星期二",
|
||||
"wednesday": "星期三",
|
||||
"thursday": "星期四",
|
||||
"friday": "星期五",
|
||||
"saturday": "星期六",
|
||||
"schedule_name": "時段名稱",
|
||||
"schedule_content": "時段內容"
|
||||
},
|
||||
"operation": {
|
||||
"title": "運維管理",
|
||||
|
@ -82,7 +82,23 @@
|
||||
"total_elec_cost": "Total Elec. Cost",
|
||||
"carbon_equivalent": "Carbon Equivalent",
|
||||
"edit_carbon_emission": "Edit carbon emission coefficient",
|
||||
"carbon_emission_coefficient": "Carbon emission coefficient"
|
||||
"carbon_emission_coefficient": "Carbon emission coefficient",
|
||||
"electricity_classification": "Electricity Classification",
|
||||
"electricity_price": "Electricity charge per unit price",
|
||||
"floor": "Floor",
|
||||
"maximum": "Maximum",
|
||||
"maximum_time": "Maximum time",
|
||||
"minimum": "Minimum value",
|
||||
"minimum_time": "Minimum time",
|
||||
"average_value": "Average value",
|
||||
"start_value": "Start value (kWh)",
|
||||
"end_value": "End value (kWh)",
|
||||
"difference": "Difference (kWh)",
|
||||
"power_consumption": "Power consumption (kWh)",
|
||||
"ranking": "Ranking",
|
||||
"subtotal": "Subtotal",
|
||||
"unit_price": "Unit price",
|
||||
"total_amount": "Total amount"
|
||||
},
|
||||
"alarm": {
|
||||
"title": "Warning",
|
||||
@ -152,7 +168,16 @@
|
||||
"choose": "Choose",
|
||||
"day_time": "Week/Time",
|
||||
"click_time_period": "Please click the time period with your mouse",
|
||||
"clear": "Clear"
|
||||
"clear": "Clear",
|
||||
"sunday": "Sunday",
|
||||
"monday": "Monday",
|
||||
"tuesday": "Tuesday",
|
||||
"wednesday": "Wednesday",
|
||||
"thursday": "Thursday",
|
||||
"friday": "Friday",
|
||||
"saturday": "Saturday",
|
||||
"schedule_name": "Time period name",
|
||||
"schedule_content": "Time period content"
|
||||
},
|
||||
"operation": {
|
||||
"title": "Operation And Maintenance Management",
|
||||
|
@ -80,7 +80,7 @@ const router = createRouter({
|
||||
component: ProductSetting,
|
||||
},
|
||||
{
|
||||
path: "/energyManagement",
|
||||
path: "/energyManagement/:main_system_id/:sub_system_id/:type",
|
||||
name: "energyManagement",
|
||||
component: EnergyManagement,
|
||||
},
|
||||
@ -99,10 +99,13 @@ router.beforeEach(async (to, from, next) => {
|
||||
const authRequired = !publicPages.includes(to.path);
|
||||
const auth = useUserInfoStore();
|
||||
const token = useGetCookie("JWT-Authorization");
|
||||
const user_name = useGetCookie("user_name");
|
||||
|
||||
if (to.path === "/logout") {
|
||||
document.cookie = "JWT-Authorization=";
|
||||
document.cookie = "JWT-Authorization=; Max-Age=0";
|
||||
document.cookie = "user_name=; Max-Age=0";
|
||||
auth.user.token = "";
|
||||
auth.user.user_name = "";
|
||||
window.location.reload();
|
||||
next({ path: "/login" });
|
||||
}
|
||||
@ -111,10 +114,13 @@ router.beforeEach(async (to, from, next) => {
|
||||
auth.user.token = "";
|
||||
next({ path: "/login" });
|
||||
} else if (!authRequired) {
|
||||
document.cookie = "JWT-Authorization=";
|
||||
document.cookie = "JWT-Authorization=; Max-Age=0";
|
||||
document.cookie = "user_name=; Max-Age=0";
|
||||
auth.user.token = "";
|
||||
auth.user.user_name = "";
|
||||
} else {
|
||||
auth.user.token = token;
|
||||
auth.user.user_name = user_name;
|
||||
}
|
||||
next();
|
||||
});
|
||||
|
@ -5,6 +5,7 @@ const useUserInfoStore = defineStore("userInfo", () => {
|
||||
const user = ref({
|
||||
token: "",
|
||||
expires: 0,
|
||||
user_name:"",
|
||||
});
|
||||
|
||||
const auth_page = ref([]);
|
||||
|
@ -1,7 +1,43 @@
|
||||
<script setup>
|
||||
import { ref, provide, onMounted, watch } from "vue";
|
||||
import AssetMainList from "./components/AssetMainList.vue";
|
||||
import AssetSubList from "./components/AssetSubList.vue";
|
||||
import AssetTable from "./components/AssetTable.vue";
|
||||
import { getOperationCompanyList } from "@/apis/operation";
|
||||
import { getIOTSchema } from "@/apis/asset";
|
||||
import useSearchParam from "@/hooks/useSearchParam";
|
||||
const { searchParams, changeParams } = useSearchParam();
|
||||
const companyOptions = ref([]);
|
||||
const iotSchemaOptions = ref([]);
|
||||
const getCompany = async () => {
|
||||
const res = await getOperationCompanyList();
|
||||
companyOptions.value = res.data.map((d) => ({ ...d, key: d.id }));
|
||||
};
|
||||
const getIOTSchemaOptions = async (id) => {
|
||||
const res = await getIOTSchema(Number(id));
|
||||
iotSchemaOptions.value = res.data.map((d) => ({ ...d, key: d.id }));
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
getCompany();
|
||||
});
|
||||
|
||||
watch(
|
||||
() => searchParams.value.subSys_id,
|
||||
(newValue) => {
|
||||
if (newValue) {
|
||||
getIOTSchemaOptions(newValue);
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
}
|
||||
);
|
||||
|
||||
provide("asset_modal_options", {
|
||||
companyOptions,
|
||||
iotSchemaOptions,
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -66,7 +66,6 @@ onMounted(() => {
|
||||
|
||||
watch(selectedBtn, (newValue) => {
|
||||
changeParams({
|
||||
...searchParams.value,
|
||||
mainSys_id: newValue.key,
|
||||
subSys_id: null,
|
||||
});
|
||||
|
@ -39,7 +39,6 @@ const getAssetData = async () => {
|
||||
}
|
||||
totalCoordinates.value[floorGuid].push(d.device_coordinate);
|
||||
});
|
||||
|
||||
tableData.value = res.data.map((d) => ({
|
||||
...d,
|
||||
key: d.id,
|
||||
@ -122,6 +121,8 @@ watch(
|
||||
(newValue) => {
|
||||
if (newValue.value?.subSys_id) {
|
||||
getAssetData();
|
||||
}else{
|
||||
tableData.value=[];
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -102,7 +102,7 @@ const closeModal = () => {
|
||||
</script>
|
||||
|
||||
<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="openModal" :disabled="!searchParams.subSys_id">
|
||||
<font-awesome-icon :icon="['fas', 'plus']" />{{ $t("button.add") }}
|
||||
</button>
|
||||
<Modal
|
||||
|
@ -6,19 +6,17 @@ import AssetTableModalLeftInfoIoT from "./AssetTableModalLeftInfoIoT.vue";
|
||||
import AssetTableModalLeftInfoDept from "./AssetTableModalLeftInfoDept.vue";
|
||||
import AssetTableModalLeftInfoGraph from "./AssetTableModalLeftInfoGraph.vue";
|
||||
import AssetTableModalLeftInfoMQTT from "./AssetTableModalLeftInfoMQTT.vue";
|
||||
import { getOperationCompanyList } from "@/apis/operation";
|
||||
import { getIOTSchema } from "@/apis/asset";
|
||||
import useSearchParam from "@/hooks/useSearchParam";
|
||||
import useUserInfoStore from "@/stores/useUserInfoStore";
|
||||
import dayjs from "dayjs";
|
||||
import { useI18n } from "vue-i18n";
|
||||
const { t } = useI18n();
|
||||
const FILE_BASEURL = import.meta.env.VITE_FILE_API_BASEURL;
|
||||
|
||||
const { searchParams, changeParams } = useSearchParam();
|
||||
const { updateLeftFields, formErrorMsg, formState } = inject(
|
||||
"asset_table_modal_form"
|
||||
);
|
||||
|
||||
const { companyOptions, iotSchemaOptions } = inject("asset_modal_options");
|
||||
const store = useUserInfoStore();
|
||||
let schema = {
|
||||
full_name: yup.string().nullable(true),
|
||||
operate_text: yup.string().nullable(true),
|
||||
@ -94,20 +92,15 @@ watch(formState, (newValue) => {
|
||||
}
|
||||
});
|
||||
|
||||
const companyOptions = ref([]);
|
||||
const iotSchemaOptions = ref([]);
|
||||
const getCompany = async () => {
|
||||
const res = await getOperationCompanyList();
|
||||
companyOptions.value = res.data.map((d) => ({ ...d, key: d.id }));
|
||||
};
|
||||
const getIOTSchemaOptions = async () => {
|
||||
const res = await getIOTSchema();
|
||||
iotSchemaOptions.value = res.data.map((d) => ({ ...d, key: d.id }));
|
||||
};
|
||||
onMounted(() => {
|
||||
getCompany();
|
||||
getIOTSchemaOptions();
|
||||
});
|
||||
watch(
|
||||
() => iotSchemaOptions.value,
|
||||
(newVal) => {
|
||||
if (newVal && newVal.length > 0) {
|
||||
formState.value.response_schema_id = newVal[0].id;
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@ -121,7 +114,12 @@ onMounted(() => {
|
||||
></Input
|
||||
>
|
||||
<AssetTableModalLeftInfoDept />
|
||||
<Input :value="formState" width="290" name="device_number">
|
||||
<Input
|
||||
:value="formState"
|
||||
width="290"
|
||||
name="device_number"
|
||||
v-if="store.user.user_name == 'webUser'"
|
||||
>
|
||||
<template #topLeft
|
||||
>Tag_Name ({{ $t("assetManagement.fill_text") }})</template
|
||||
>
|
||||
|
@ -44,6 +44,7 @@ const modalColumns = computed(() => [
|
||||
{
|
||||
title: "tag",
|
||||
key: "device_name",
|
||||
filter: true
|
||||
},
|
||||
{
|
||||
title: t("assetManagement.point"),
|
||||
|
@ -35,6 +35,7 @@ onBeforeMount(() => {
|
||||
const asset_floor_chart = ref(null);
|
||||
const currentFloor = ref(null);
|
||||
const selectedOption = ref("add");
|
||||
const parsedCoordinates = ref(null);
|
||||
|
||||
const defaultOption = (map, data = []) => {
|
||||
// 生成坐標數據,根據坐標值的不同設置不同顏色
|
||||
@ -73,17 +74,21 @@ const defaultOption = (map, data = []) => {
|
||||
|
||||
watch(currentFloor, (newValue) => {
|
||||
if (newValue?.floor_map_name) {
|
||||
const coordinates = totalCoordinates.value[newValue.floor_guid] || [];
|
||||
if (coordinates.length === 0 || coordinates.every(coord => coord === "")) return;
|
||||
const parsedCoordinates = coordinates.map((coord) => {
|
||||
return JSON.parse(coord);
|
||||
});
|
||||
const coordinates =
|
||||
(totalCoordinates.value?.[newValue.floor_guid]?.filter(
|
||||
(coord) => coord !== ""
|
||||
) || []);
|
||||
|
||||
parsedCoordinates.value = coordinates.length > 0
|
||||
? coordinates.map((coord) => JSON.parse(coord))
|
||||
: [];
|
||||
|
||||
asset_floor_chart.value.updateSvg(
|
||||
{
|
||||
full_name: newValue?.floor_map_name,
|
||||
path: `${FILE_BASEURL}/${newValue?.floor_map_url}.svg`,
|
||||
full_name: newValue.floor_map_name,
|
||||
path: `${FILE_BASEURL}/${newValue.floor_map_url}.svg`,
|
||||
},
|
||||
defaultOption(newValue?.floor_map_name, parsedCoordinates)
|
||||
defaultOption(newValue.floor_map_name, parsedCoordinates.value)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
@ -59,7 +59,12 @@ const searchData = ref({
|
||||
const getDataSource = async () => {
|
||||
loading.value = true;
|
||||
const res = await getAccountUserList(searchData.value);
|
||||
dataSource.value = res.data;
|
||||
// 過濾掉 webUser
|
||||
dataSource.value = res.data.filter(
|
||||
(i) =>
|
||||
i.userinfo_guid !== "6ac24708-3a40-4199-88c5-22df310cd1a8" &&
|
||||
i.userinfo_guid !== "B43E3CA7-96DD-4FC7-B6E6-974ACC3B0878"
|
||||
);
|
||||
loading.value = false;
|
||||
};
|
||||
|
||||
|
@ -7,11 +7,10 @@ import {
|
||||
} from "@/apis/alert";
|
||||
import useSearchParam from "@/hooks/useSearchParam";
|
||||
import AlertOutliersTableAddModal from "./AlertOutliersTableAddModal.vue";
|
||||
import AlertOutliersTimeModal from "./AlertOutliersTimeModal.vue";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
import { useI18n } from "vue-i18n";
|
||||
const { t } = useI18n();
|
||||
const { noticeList } = inject("notify_table");
|
||||
const { noticeList, timesList } = inject("notify_table");
|
||||
const { searchParams, changeParams } = useSearchParam();
|
||||
|
||||
const tableData = ref([]);
|
||||
@ -111,6 +110,9 @@ const getOutliersData = async () => {
|
||||
matchedPoints?.factor && item.factor
|
||||
? matchedPoints?.factor?.find((f) => f.id === item.factor)
|
||||
: null;
|
||||
const matchedTime = timesList.value.find(
|
||||
(t) => t.id === item.schedule_id
|
||||
);
|
||||
const warningMethodKeys = item.notices
|
||||
?.map((noticeValue) => {
|
||||
const matchedNotice = noticeList.value.find(
|
||||
@ -128,6 +130,7 @@ const getOutliersData = async () => {
|
||||
is_bool: matchedPoints ? matchedPoints.is_bool : 1,
|
||||
factor_name: matchedFactor ? matchedFactor.full_name : "",
|
||||
warning_method: warningMethodKeys,
|
||||
warning_time: matchedTime ? matchedTime.schedule_name : "",
|
||||
};
|
||||
});
|
||||
}
|
||||
@ -163,14 +166,6 @@ const onCancel = () => {
|
||||
editRecord.value = null;
|
||||
outliers_add_table_item.close();
|
||||
};
|
||||
|
||||
const openTimeModal = () => {
|
||||
outliers_time_item.showModal();
|
||||
};
|
||||
|
||||
const onTimeCancel = () => {
|
||||
outliers_time_item.close();
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@ -183,10 +178,6 @@ const onTimeCancel = () => {
|
||||
:getData="getOutliersData"
|
||||
:OptionsData="dev_data"
|
||||
/>
|
||||
<AlertOutliersTimeModal
|
||||
:openModal="openTimeModal"
|
||||
:onCancel="onTimeCancel"
|
||||
/>
|
||||
</div>
|
||||
<Table :columns="columns" :dataSource="tableData" class="mt-3">
|
||||
<template #bodyCell="{ record, column, index }">
|
||||
@ -205,13 +196,7 @@ const onTimeCancel = () => {
|
||||
<span class="whitespace-pre">{{ record.warning_method }}</span>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'warning_time'">
|
||||
<button
|
||||
class="btn btn-sm btn-success text-white pb-3"
|
||||
:disabled="!record.enable"
|
||||
@click.stop.prevent="() => openTimeModal(record)"
|
||||
>
|
||||
{{ $t("alert.time_setting") }}
|
||||
</button>
|
||||
<span class="whitespace-pre">{{ record.warning_time }}</span>
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ record[column.key] }}
|
||||
|
@ -8,7 +8,7 @@ import * as yup from "yup";
|
||||
import { useI18n } from "vue-i18n";
|
||||
const { t } = useI18n();
|
||||
const { openToast } = inject("app_toast");
|
||||
const { noticeList } = inject("notify_table");
|
||||
const { timesList, noticeList } = inject("notify_table");
|
||||
const { searchParams, changeParams } = useSearchParam();
|
||||
|
||||
const props = defineProps({
|
||||
@ -190,6 +190,15 @@ const closeModal = () => {
|
||||
>
|
||||
<template #topLeft>{{ $t("alert.status") }}</template>
|
||||
</RadioGroup>
|
||||
<Select :value="formState" class="my-2" selectClass="border-info focus-within:border-info" name="schedule_id"
|
||||
Attribute="schedule_name" :options="timesList">
|
||||
<template #topLeft>{{$t("alert.warning_time")}}</template>
|
||||
<template #topRight><button v-if="formState.schedule_id" class="text-base btn-text-without-border"
|
||||
@click="() => {formState.schedule_id = null}"><font-awesome-icon
|
||||
:icon="['fas', 'times']"
|
||||
class="text-[#a5abb1] me-1"
|
||||
/>{{$t("alert.clear")}}</button></template>
|
||||
</Select>
|
||||
<Select
|
||||
:value="formState"
|
||||
class="my-2"
|
||||
|
@ -1,14 +1,27 @@
|
||||
<script setup>
|
||||
import AlertSubList from "./AlertSubList.vue";
|
||||
import AlertOutliersTable from "./AlertOutliersTable.vue";
|
||||
import AlertTimeTable from "./AlertTimeTable.vue";
|
||||
import AlertNotifyTable from "./AlertNotifyTable.vue";
|
||||
import { ref, provide, onMounted, watch } from "vue";
|
||||
import { getNoticeList } from "@/apis/alert";
|
||||
import { getAlarmScheduleList, getNoticeList } from "@/apis/alert";
|
||||
import { useI18n } from "vue-i18n";
|
||||
const { locale } = useI18n();
|
||||
|
||||
const timesList = ref([]);
|
||||
const noticeList = ref([]);
|
||||
|
||||
const timesListData = async () => {
|
||||
const res = await getAlarmScheduleList();
|
||||
timesList.value = res.data.map((items) => ({
|
||||
...items,
|
||||
key:items.id,
|
||||
schedule_array: JSON.parse(items.schedule_json).map((time, index) => ({
|
||||
day: index + 1,
|
||||
time,
|
||||
})),
|
||||
}));
|
||||
};
|
||||
|
||||
const NoticeListData = async () => {
|
||||
const res = await getNoticeList(locale.value);
|
||||
noticeList.value = res.data;
|
||||
@ -19,16 +32,18 @@ watch(locale, () => {
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
timesListData();
|
||||
NoticeListData();
|
||||
});
|
||||
|
||||
provide("notify_table", { noticeList });
|
||||
provide("notify_table", { timesList, noticeList, timesListData });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<AlertSubList />
|
||||
<AlertOutliersTable />
|
||||
<AlertTimeTable />
|
||||
<AlertNotifyTable />
|
||||
</div>
|
||||
</template>
|
||||
|
110
src/views/alert/components/AlertSetting/AlertTimeTable.vue
Normal file
110
src/views/alert/components/AlertSetting/AlertTimeTable.vue
Normal file
@ -0,0 +1,110 @@
|
||||
<script setup>
|
||||
import { ref, inject, onMounted } from "vue";
|
||||
import { deleteAlarmSchedule } from "@/apis/alert";
|
||||
import AlertTimeTableAddModal from "./AlertTimeTableAddModal.vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
const { t } = useI18n();
|
||||
const { openToast, cancelToastOpen } = inject("app_toast");
|
||||
const tableData = ref([]);
|
||||
const editRecord = ref(null);
|
||||
const { timesList,timesListData } = inject("notify_table");
|
||||
const weekDate = ref({
|
||||
1: t("alert.sunday"),
|
||||
2: t("alert.monday"),
|
||||
3: t("alert.tuesday"),
|
||||
4: t("alert.wednesday"),
|
||||
5: t("alert.thursday"),
|
||||
6: t("alert.friday"),
|
||||
7: t("alert.saturday"),
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
timesListData();
|
||||
});
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: t("alert.schedule_name"),
|
||||
key: "schedule_name",
|
||||
},
|
||||
{
|
||||
title: t("alert.schedule_content"),
|
||||
key: "schedule_content",
|
||||
},
|
||||
{
|
||||
title: t("alert.operation"),
|
||||
key: "operation",
|
||||
width: 200,
|
||||
},
|
||||
];
|
||||
|
||||
const openModal = (record) => {
|
||||
if (record) {
|
||||
editRecord.value = record;
|
||||
} else {
|
||||
editRecord.value = null;
|
||||
}
|
||||
outliers_time_item.showModal();
|
||||
};
|
||||
|
||||
const onCancel = () => {
|
||||
editRecord.value = null;
|
||||
outliers_time_item.close();
|
||||
};
|
||||
|
||||
const remove = async (id) => {
|
||||
openToast("warning", t("msg.sure_to_delete"), "body", async () => {
|
||||
await cancelToastOpen();
|
||||
const res = await deleteAlarmSchedule(id);
|
||||
if (res.isSuccess) {
|
||||
timesListData();
|
||||
openToast("success", t("msg.delete_success"));
|
||||
} else {
|
||||
openToast("error", res.msg);
|
||||
}
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex justify-start items-center mt-10">
|
||||
<h3 class="text-xl mr-5">{{$t("alert.warning_time")}}</h3>
|
||||
<AlertTimeTableAddModal :openModal="openModal" :onCancel="onCancel" :editRecord="editRecord" :weekDate="weekDate" />
|
||||
</div>
|
||||
<Table :columns="columns" :dataSource="timesList" class="w-3/4 mt-3">
|
||||
<template #bodyCell="{ record, column, index }">
|
||||
<template v-if="column.key === 'schedule_content'">
|
||||
<ul class="text-left">
|
||||
<li v-for="(item, index) in record.schedule_array" :key="index">
|
||||
<strong v-if="item.time.length">
|
||||
<span>{{ weekDate[item.day] }}: </span>
|
||||
<span v-for="(time, timeIndex) in item.time" :key="timeIndex">
|
||||
{{ time }}
|
||||
<span v-if="timeIndex < item.time.length - 1">, </span>
|
||||
</span>
|
||||
</strong>
|
||||
</li>
|
||||
</ul>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'operation'">
|
||||
<button
|
||||
class="btn btn-sm btn-success text-white mr-2"
|
||||
@click.stop.prevent="() => openModal(record)"
|
||||
>
|
||||
{{$("button.edit")}}
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-sm btn-error text-white"
|
||||
@click.stop.prevent="() => remove(record.id)"
|
||||
>
|
||||
{{$("button.delete")}}
|
||||
</button>
|
||||
</template>
|
||||
<template v-else>
|
||||
<pre>{{ record[column.key] }}</pre>
|
||||
</template>
|
||||
</template>
|
||||
</Table>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped></style>
|
@ -1,19 +1,41 @@
|
||||
<script setup>
|
||||
import { defineProps, onMounted, onUnmounted, ref, reactive } from "vue";
|
||||
import { defineProps, onMounted, watch, ref, reactive, inject } from "vue";
|
||||
import useFormErrorMessage from "@/hooks/useFormErrorMessage";
|
||||
import { postAlertSchedule } from "@/apis/alert";
|
||||
import * as yup from "yup";
|
||||
import { useI18n } from "vue-i18n";
|
||||
const { t } = useI18n();
|
||||
|
||||
const { openToast } = inject("app_toast");
|
||||
const { timesListData } = inject("notify_table");
|
||||
const props = defineProps({
|
||||
openModal: Function,
|
||||
onCancel: Function,
|
||||
defaultTimeSection: {
|
||||
type: Object,
|
||||
default: () => [],
|
||||
},
|
||||
editRecord: Object,
|
||||
weekDate: Object,
|
||||
});
|
||||
|
||||
let scheme = yup.object({
|
||||
schedule_name: yup.string().required(t("button.required")),
|
||||
});
|
||||
|
||||
const form = ref(null);
|
||||
const schedule_array = ref([]);
|
||||
const formState = ref({
|
||||
id: 0,
|
||||
schedule_name: "",
|
||||
});
|
||||
|
||||
const { formErrorMsg, handleSubmit, handleErrorReset } = useFormErrorMessage(
|
||||
scheme.value
|
||||
);
|
||||
|
||||
const closeModal = () => {
|
||||
props.onCancel();
|
||||
formState.value = {
|
||||
id: 0,
|
||||
schedule_name: "",
|
||||
};
|
||||
clear();
|
||||
};
|
||||
|
||||
@ -43,15 +65,6 @@ const tableHeader = ref([
|
||||
"22",
|
||||
"23",
|
||||
]);
|
||||
const weekDate = ref({
|
||||
1: "Mon",
|
||||
2: "Tue",
|
||||
3: "Wed",
|
||||
4: "Thu",
|
||||
5: "Fri",
|
||||
6: "Sat",
|
||||
7: "Sun",
|
||||
});
|
||||
|
||||
const rowUnit = ref([]); //每一單位格子
|
||||
const timeContent = ref([]); //選中的時間段原始數據
|
||||
@ -79,8 +92,8 @@ const handleMouseMove = (i, day) => {
|
||||
|
||||
const initSchedule = () => {
|
||||
for (let i = 0; i < 7; i++) {
|
||||
const dayIndex = i + 1;
|
||||
const defaultTimes = props.defaultTimeSection[dayIndex] || [];
|
||||
const dayIndex = i;
|
||||
const defaultTimes = schedule_array.value[dayIndex] || [];
|
||||
let arr = [];
|
||||
for (let j = 0; j < 96; j++) {
|
||||
const timeSlot = j / 4;
|
||||
@ -206,51 +219,67 @@ const filterTime = (start, end) => {
|
||||
};
|
||||
|
||||
const clear = () => {
|
||||
rowUnit.value.forEach((item) => {
|
||||
item.forEach((item1) => {
|
||||
item1.class = null;
|
||||
});
|
||||
schedule_array.value = [];
|
||||
rowUnit.value = [];
|
||||
timeContent.value = [];
|
||||
timeSection.value = [];
|
||||
timeStr.value = [];
|
||||
};
|
||||
|
||||
const onOk = async () => {
|
||||
const values = await handleSubmit(scheme, formState.value);
|
||||
|
||||
const res = await postAlertSchedule({
|
||||
...values,
|
||||
schedule_json: timeSection.value ? JSON.stringify(timeSection.value) : "",
|
||||
});
|
||||
|
||||
timeContent.value.forEach((item) => {
|
||||
item.arr = [];
|
||||
});
|
||||
|
||||
timeSection.value.forEach((item) => {
|
||||
item.length = 0;
|
||||
});
|
||||
|
||||
timeStr.value.length = 0;
|
||||
for (let i = 0; i < 7; i++) {
|
||||
timeStr.value.push("");
|
||||
if (res.isSuccess) {
|
||||
timesListData();
|
||||
closeModal();
|
||||
} else {
|
||||
openToast("error", res.msg, "#outliers_time_item");
|
||||
}
|
||||
};
|
||||
|
||||
const onOk = async () => {};
|
||||
|
||||
onMounted(() => {
|
||||
watch(
|
||||
() => props.editRecord,
|
||||
(newValue) => {
|
||||
if (newValue) {
|
||||
formState.value = {
|
||||
...newValue,
|
||||
};
|
||||
schedule_array.value = newValue?.schedule_json
|
||||
? JSON.parse(newValue?.schedule_json)
|
||||
: [];
|
||||
initSchedule();
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<button class="btn btn-sm btn-add mr-3" @click.stop.prevent="openModal">
|
||||
<font-awesome-icon :icon="['fas', 'plus']" />{{ $t("button.add") }}
|
||||
</button>
|
||||
<Modal
|
||||
id="outliers_time_item"
|
||||
:open="open"
|
||||
:onCancel="closeModal"
|
||||
width="1400"
|
||||
:title="t('alert.warning_time')"
|
||||
>
|
||||
<template #modalTitle>
|
||||
<p>{{ $t("alert.warning_time") }}</p>
|
||||
<button class="fixed right-10 top-5" @click.prevent="closeModal">
|
||||
<font-awesome-icon
|
||||
:icon="['fas', 'times']"
|
||||
size="1x"
|
||||
class="text-[#a5abb1]"
|
||||
/>
|
||||
</button>
|
||||
</template>
|
||||
<template #modalContent>
|
||||
<form ref="form">
|
||||
<Input :value="formState" class="mt-2" name="schedule_name">
|
||||
<template #topLeft>{{ $t("alert.schedule_name") }}</template>
|
||||
<template #bottomLeft>
|
||||
<span class="text-error text-base">
|
||||
{{ formErrorMsg.schedule_name }}
|
||||
</span>
|
||||
</template>
|
||||
</Input>
|
||||
<p class="text-light text-lg my-2">{{ $t("alert.schedule_content") }}</p>
|
||||
<div class="weektime mt-5">
|
||||
<div class="calendar">
|
||||
<table class="calendar-table w-full">
|
||||
@ -295,11 +324,16 @@ onMounted(() => {
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="97" class="py-2">
|
||||
<span class="text-base">{{
|
||||
<span class="text-base me-2">{{
|
||||
$t("alert.click_time_period")
|
||||
}}</span>
|
||||
<a
|
||||
@click="clear"
|
||||
@click="
|
||||
() => {
|
||||
clear();
|
||||
initSchedule();
|
||||
}
|
||||
"
|
||||
class="cursor-pointer text-active text-base"
|
||||
>
|
||||
{{ $t("alert.clear") }}
|
||||
@ -324,6 +358,7 @@ onMounted(() => {
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</template>
|
||||
<template #modalAction>
|
||||
<button
|
@ -1,54 +1,39 @@
|
||||
<script setup>
|
||||
import ImmediateDemandChart from "./components/ImmediateDemandChart.vue";
|
||||
import ElecConsumption from "./components/ElecConsumption.vue";
|
||||
import UsageInformation from "./components/UsageInformation.vue";
|
||||
import MonthlyElecBillChart from "./components/MonthlyElecBillChart.vue";
|
||||
import CarbonEmissionChart from "./components/CarbonEmissionChart.vue";
|
||||
import BillingDegreeChart from "./components/BillingDegreeChart.vue";
|
||||
import IntervalBillChart from "./components/IntervalBillChart.vue";
|
||||
import { getTaipower } from "@/apis/energy";
|
||||
import { ref, onMounted, provide } from "vue";
|
||||
import { ref, onMounted, watch } from "vue";
|
||||
import { useRoute } from "vue-router";
|
||||
|
||||
const taipower_data = ref([]);
|
||||
const getData = async () => {
|
||||
const res = await getTaipower();
|
||||
if (res.isSuccess) {
|
||||
taipower_data.value = res.data;
|
||||
import EnergyChart from "./components/EnergyChart/EnergyChart.vue";
|
||||
import EnergyHistoryTable from "./components/EnergyHistoryTable/EnergyHistoryTable.vue";
|
||||
import EnergyReport from "./components/EnergyReport/EnergyReport.vue";
|
||||
|
||||
const route = useRoute();
|
||||
const currentComponent = ref(null);
|
||||
|
||||
const updateComponent = () => {
|
||||
const { main_system_id, sub_system_id } = route.params;
|
||||
|
||||
if (main_system_id === "energy_chart") {
|
||||
if (sub_system_id === "chart") {
|
||||
currentComponent.value = EnergyChart;
|
||||
} else {
|
||||
currentComponent.value = EnergyHistoryTable;
|
||||
}
|
||||
} else if (main_system_id === "energy_report") {
|
||||
currentComponent.value = EnergyReport;
|
||||
} else {
|
||||
currentComponent.value = null;
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
getData();
|
||||
});
|
||||
onMounted(updateComponent);
|
||||
|
||||
provide("energy_data", { taipower_data });
|
||||
watch(
|
||||
() => route.params,
|
||||
() => {
|
||||
updateComponent();
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-wrap items-center mb-4">
|
||||
<div class="w-full xl:w-5/12 lg:w-1/2">
|
||||
<ElecConsumption />
|
||||
</div>
|
||||
<div class="w-full xl:w-7/12 lg:w-1/2">
|
||||
<ImmediateDemandChart />
|
||||
</div>
|
||||
<div class="w-full xl:w-1/3 px-3">
|
||||
<UsageInformation />
|
||||
</div>
|
||||
<div class="w-full xl:w-1/3 px-3">
|
||||
<MonthlyElecBillChart />
|
||||
</div>
|
||||
<div class="w-full xl:w-1/3 px-3">
|
||||
<CarbonEmissionChart />
|
||||
</div>
|
||||
<div class="w-full xl:w-1/3 px-3">
|
||||
<BillingDegreeChart />
|
||||
</div>
|
||||
<div class="w-full xl:w-2/3 px-3">
|
||||
<IntervalBillChart />
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid gap-4 grid-cols-3"></div>
|
||||
<component :is="currentComponent" />
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
|
@ -0,0 +1,54 @@
|
||||
<script setup>
|
||||
import ImmediateDemandChart from "./ImmediateDemandChart.vue";
|
||||
import ElecConsumption from "./ElecConsumption.vue";
|
||||
import UsageInformation from "./UsageInformation.vue";
|
||||
import MonthlyElecBillChart from "./MonthlyElecBillChart.vue";
|
||||
import CarbonEmissionChart from "./CarbonEmissionChart.vue";
|
||||
import BillingDegreeChart from "./BillingDegreeChart.vue";
|
||||
import IntervalBillChart from "./IntervalBillChart.vue";
|
||||
import { getTaipower } from "@/apis/energy";
|
||||
import { ref, onMounted, provide } from "vue";
|
||||
|
||||
const taipower_data = ref([]);
|
||||
const getData = async () => {
|
||||
const res = await getTaipower();
|
||||
if (res.isSuccess) {
|
||||
taipower_data.value = res.data;
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
getData();
|
||||
});
|
||||
|
||||
provide("energy_data", { taipower_data });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-wrap items-center mb-4">
|
||||
<div class="w-full xl:w-5/12 lg:w-1/2">
|
||||
<ElecConsumption />
|
||||
</div>
|
||||
<div class="w-full xl:w-7/12 lg:w-1/2">
|
||||
<ImmediateDemandChart />
|
||||
</div>
|
||||
<div class="w-full xl:w-1/3 px-3">
|
||||
<UsageInformation />
|
||||
</div>
|
||||
<div class="w-full xl:w-1/3 px-3">
|
||||
<MonthlyElecBillChart />
|
||||
</div>
|
||||
<div class="w-full xl:w-1/3 px-3">
|
||||
<CarbonEmissionChart />
|
||||
</div>
|
||||
<div class="w-full xl:w-1/3 px-3">
|
||||
<BillingDegreeChart />
|
||||
</div>
|
||||
<div class="w-full xl:w-2/3 px-3">
|
||||
<IntervalBillChart />
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid gap-4 grid-cols-3"></div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped></style>
|
@ -0,0 +1,127 @@
|
||||
<script setup>
|
||||
import { computed, defineProps, inject, ref, watch } from "vue";
|
||||
import { useRoute } from "vue-router";
|
||||
import { getHistoryData, getHistoryExportData } from "@/apis/history";
|
||||
import useSearchParam from "@/hooks/useSearchParam";
|
||||
import { useI18n } from 'vue-i18n';
|
||||
const { t } = useI18n();
|
||||
const { searchParams } = useSearchParam();
|
||||
const route = useRoute();
|
||||
|
||||
const props = defineProps({
|
||||
form: Object,
|
||||
});
|
||||
|
||||
const { updateTableData } = inject("energy_table_data");
|
||||
|
||||
let isToastOpen = ref({
|
||||
open: false,
|
||||
content: "",
|
||||
});
|
||||
|
||||
const cancelToastOpen = () => {
|
||||
isToastOpen.value = {
|
||||
open: false,
|
||||
content: "",
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
const submit = async (e, type = "") => {
|
||||
e?.preventDefault();
|
||||
e?.stopPropagation();
|
||||
|
||||
const formData = new FormData(props.form);
|
||||
|
||||
let params = {};
|
||||
for (const pair of formData.entries()) {
|
||||
console.log(pair[0], pair[1]);
|
||||
params = { ...params, [pair[0]]: pair[1] };
|
||||
}
|
||||
|
||||
if (type === "export") {
|
||||
const res = await getHistoryExportData({
|
||||
type: searchParams.value.selectedType,
|
||||
...params,
|
||||
...searchParams.value,
|
||||
}).catch((err) => {
|
||||
isToastOpen.value = {
|
||||
open: true,
|
||||
content: err.msg,
|
||||
};
|
||||
});
|
||||
} else {
|
||||
const res = await getHistoryData({
|
||||
...searchParams.value,
|
||||
Type: 1,
|
||||
table_type:route.params.type
|
||||
});
|
||||
updateTableData(res.data);
|
||||
}
|
||||
};
|
||||
|
||||
const isSearchButtonDisabled = computed(() => {
|
||||
if (
|
||||
!searchParams.value.Points?.length ||
|
||||
!searchParams.value.Device_list?.length
|
||||
) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
const submitBtns = computed(() => [
|
||||
{
|
||||
title: t("button.search"),
|
||||
key: "submit",
|
||||
active: false,
|
||||
onClick: submit,
|
||||
icon: "search",
|
||||
btn: "btn-search",
|
||||
disabled: isSearchButtonDisabled.value,
|
||||
},
|
||||
{
|
||||
title: t("button.export"),
|
||||
key: "export",
|
||||
icon: "download",
|
||||
btn: "btn-export",
|
||||
active: false,
|
||||
onClick: (e) => submit(e, "export"),
|
||||
disabled: isSearchButtonDisabled.value,
|
||||
},
|
||||
]);
|
||||
|
||||
const once = ref(false);
|
||||
watch(
|
||||
searchParams,
|
||||
(newVal) => {
|
||||
if (
|
||||
newVal.Type &&
|
||||
newVal.Points?.length &&
|
||||
newVal.sub_system_id &&
|
||||
newVal.Device_list?.length
|
||||
) {
|
||||
if (!once.value) {
|
||||
once.value = true;
|
||||
submit();
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Toast
|
||||
:content="isToastOpen.content"
|
||||
:open="isToastOpen.open"
|
||||
status="info"
|
||||
:cancel="cancelToastOpen"
|
||||
/>
|
||||
<ButtonGroup class="ml-5" :items="submitBtns" :withLine="false" :withBtnClass="true"/>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped></style>
|
@ -0,0 +1,157 @@
|
||||
<script setup>
|
||||
import { inject, computed, ref, watch } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import LineChart from "@/components/chart/LineChart.vue";
|
||||
import { SECOND_CHART_COLOR } from "@/constant";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
const { t } = useI18n();
|
||||
const { tableData } = inject("energy_table_data");
|
||||
const history_chart = ref(null);
|
||||
|
||||
// 預設圖表選項
|
||||
const defaultChartOption = {
|
||||
tooltip: {
|
||||
trigger: "axis",
|
||||
},
|
||||
legend: {
|
||||
data: [],
|
||||
textStyle: {
|
||||
color: "#ffffff",
|
||||
fontSize: 16,
|
||||
},
|
||||
},
|
||||
grid: {
|
||||
top: "25%",
|
||||
left: "0%",
|
||||
right: "0%",
|
||||
bottom: "0%",
|
||||
containLabel: true,
|
||||
},
|
||||
toolbox: {
|
||||
show: true,
|
||||
feature: {
|
||||
saveAsImage: {
|
||||
type: "png",
|
||||
name: "Energy_management_chart",
|
||||
backgroundColor: "rgba(29, 36, 44, 1)",
|
||||
iconStyle: {
|
||||
borderColor: "rgba(53,237,237, 1)",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
xAxis: {
|
||||
type: "category",
|
||||
splitLine: { show: false },
|
||||
axisLabel: {
|
||||
color: "#ffffff",
|
||||
formatter: (value) => dayjs(value).format("HH:mm"), // 格式化為時間
|
||||
},
|
||||
data: [],
|
||||
},
|
||||
yAxis: {
|
||||
type: "value",
|
||||
splitLine: { show: false },
|
||||
axisLabel: { color: "#ffffff" },
|
||||
},
|
||||
series: [],
|
||||
};
|
||||
|
||||
// 格式化數據
|
||||
const formatChartData = (data) => {
|
||||
return data.reduce((acc, item) => {
|
||||
const seriesKey = `${item.device_name || ""}_${item.item_name || ""}`;
|
||||
acc[seriesKey] = {
|
||||
timestamps: item.data.map((d) =>
|
||||
dayjs(d.timestamp).format("YYYY-MM-DD HH:mm")
|
||||
),
|
||||
values: item.data.map((d) =>
|
||||
d.value == "無資料" ? null : parseFloat(d.value)
|
||||
),
|
||||
maxValue: parseFloat(item.maxValue),
|
||||
minValue: parseFloat(item.minValue),
|
||||
averageValue: parseFloat(item.averageValue),
|
||||
};
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
};
|
||||
|
||||
// 監聽 tableData,更新圖表
|
||||
watch(
|
||||
tableData,
|
||||
(newData) => {
|
||||
if (newData?.length > 0) {
|
||||
const formattedData = formatChartData(newData);
|
||||
|
||||
const series = Object.keys(formattedData).map((seriesKey, index) => {
|
||||
const { maxValue, minValue, averageValue } =
|
||||
formattedData[seriesKey];
|
||||
return {
|
||||
name: seriesKey,
|
||||
type: "line",
|
||||
data: formattedData[seriesKey].values,
|
||||
showSymbol: false,
|
||||
itemStyle: {
|
||||
color: SECOND_CHART_COLOR[index % SECOND_CHART_COLOR.length],
|
||||
},
|
||||
markPoint: {
|
||||
data: [
|
||||
maxValue !== null
|
||||
? { type: "max", name: "Max", value: maxValue }
|
||||
: null,
|
||||
minValue !== null
|
||||
? { type: "min", name: "Min", value: minValue }
|
||||
: null,
|
||||
].filter(Boolean),
|
||||
},
|
||||
markLine: {
|
||||
data:
|
||||
averageValue !== null
|
||||
? [
|
||||
{
|
||||
yAxis: averageValue,
|
||||
name: "Avg",
|
||||
},
|
||||
]
|
||||
: [],
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
// 更新圖表
|
||||
history_chart.value?.chart.setOption(
|
||||
{
|
||||
...defaultChartOption,
|
||||
legend: {
|
||||
...defaultChartOption.legend,
|
||||
data: Object.keys(formattedData),
|
||||
},
|
||||
xAxis: {
|
||||
...defaultChartOption.xAxis,
|
||||
data:
|
||||
Object.keys(formattedData).length > 0
|
||||
? formattedData[Object.keys(formattedData)[0]].timestamps
|
||||
: [],
|
||||
},
|
||||
series,
|
||||
},
|
||||
true
|
||||
);
|
||||
}
|
||||
},
|
||||
{ deep: true }
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<LineChart
|
||||
id="history_chart"
|
||||
class="min-h-[350px] max-h-fit"
|
||||
:option="defaultChartOption"
|
||||
ref="history_chart"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped></style>
|
@ -0,0 +1,170 @@
|
||||
<script setup>
|
||||
import Table from "@/components/customUI/Table.vue";
|
||||
import EnergyDataCahrt from "./EnergyDataCahrt.vue";
|
||||
import { inject, computed, watch, ref, onMounted } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useRoute } from "vue-router";
|
||||
|
||||
const { t } = useI18n();
|
||||
const route = useRoute();
|
||||
const { tableData } = inject("energy_table_data");
|
||||
const routeType = ref(1);
|
||||
const processedTableData = ref([]); // 使用 ref 來儲存處理後的數據
|
||||
|
||||
// 處理計算邏輯的函數
|
||||
const processData = (data) => {
|
||||
if (!data || !Array.isArray(data) || data.length === 0) {
|
||||
return [];
|
||||
}
|
||||
return data ? [...data] : [];
|
||||
};
|
||||
|
||||
// 使用 watch 來監聽 tableData 和 routeType 的變化
|
||||
watch(
|
||||
tableData,
|
||||
(newTableData) => {
|
||||
processedTableData.value = processData(newTableData);
|
||||
},
|
||||
{ immediate: true } // 立即執行一次
|
||||
);
|
||||
|
||||
const columns = computed(() => {
|
||||
const columnMap = {
|
||||
1: [
|
||||
{
|
||||
title: t("assetManagement.department"),
|
||||
key: "department_name",
|
||||
},
|
||||
{
|
||||
title: t("energy.floor"),
|
||||
key: "floor_name",
|
||||
},
|
||||
{
|
||||
title: t("history.device_name"),
|
||||
key: "device_name",
|
||||
filter: true,
|
||||
},
|
||||
{
|
||||
title: t("assetManagement.point"),
|
||||
key: "item_name",
|
||||
},
|
||||
{
|
||||
title: t("energy.maximum"),
|
||||
key: "maxValue",
|
||||
},
|
||||
{
|
||||
title: t("energy.maximum_time"),
|
||||
key: "maxTime",
|
||||
},
|
||||
{
|
||||
title: t("energy.minimum"),
|
||||
key: "minValue",
|
||||
},
|
||||
{
|
||||
title: t("energy.minimum_time"),
|
||||
key: "minTime",
|
||||
},
|
||||
{
|
||||
title: t("energy.average_value"),
|
||||
key: "averageValue",
|
||||
},
|
||||
],
|
||||
2: [
|
||||
{
|
||||
title: t("assetManagement.department"),
|
||||
key: "department_name",
|
||||
},
|
||||
{
|
||||
title: t("energy.floor"),
|
||||
key: "floor_name",
|
||||
},
|
||||
{
|
||||
title: t("history.device_name"),
|
||||
key: "device_name",
|
||||
filter: true,
|
||||
},
|
||||
{
|
||||
title: t("history.start_time"),
|
||||
key: "startTime",
|
||||
},
|
||||
{
|
||||
title: t("energy.start_value"),
|
||||
key: "startValue",
|
||||
},
|
||||
{
|
||||
title: t("history.end_time"),
|
||||
key: "endTime",
|
||||
filter: true,
|
||||
},
|
||||
{
|
||||
title: t("energy.end_value"),
|
||||
key: "endValue",
|
||||
},
|
||||
{
|
||||
title: t("energy.difference"),
|
||||
key: "diffValue",
|
||||
},
|
||||
],
|
||||
3: [
|
||||
{
|
||||
title: t("assetManagement.department"),
|
||||
key: "department_name",
|
||||
},
|
||||
{
|
||||
title: t("energy.floor"),
|
||||
key: "floor_name",
|
||||
},
|
||||
{
|
||||
title: t("history.device_name"),
|
||||
key: "device_name",
|
||||
filter: true,
|
||||
},
|
||||
{
|
||||
title: t("energy.power_consumption"),
|
||||
key: "value",
|
||||
},
|
||||
{
|
||||
title: t("history.start_time"),
|
||||
key: "startTime",
|
||||
},
|
||||
{
|
||||
title: t("history.end_time"),
|
||||
key: "endTime",
|
||||
},
|
||||
{
|
||||
title: t("energy.ectricity_classification"),
|
||||
key: "elecType",
|
||||
},
|
||||
{
|
||||
title: t("energy.ranking"),
|
||||
key: "rank",
|
||||
},
|
||||
],
|
||||
};
|
||||
return columnMap[routeType.value] || columnMap[1];
|
||||
});
|
||||
|
||||
watch(
|
||||
() => route.params.type,
|
||||
(newType) => {
|
||||
const type = parseInt(newType);
|
||||
if (!isNaN(type) && type >= 1 && type <= 3) {
|
||||
routeType.value = type;
|
||||
} else {
|
||||
routeType.value = 1;
|
||||
}
|
||||
tableData.value = [];
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Table :columns="columns" :dataSource="processedTableData">
|
||||
<template #beforeTable>
|
||||
<EnergyDataCahrt v-if="route.params.type == 1" class="mb-10" />
|
||||
</template>
|
||||
</Table>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped></style>
|
@ -0,0 +1,37 @@
|
||||
<script setup>
|
||||
import EnergySidebar from "./EnergySidebar.vue";
|
||||
import EnergySearch from "./EnergySearch.vue";
|
||||
import EnergyDataTable from "./EnergyDataTable.vue";
|
||||
import dayjs from "dayjs";
|
||||
import { ref, provide } from "vue";
|
||||
|
||||
const tableData = ref([]);
|
||||
const deptData = ref([]);
|
||||
const elecType = ref([]);
|
||||
const subSystem = ref(null);
|
||||
const updateTableData = (data) => {
|
||||
tableData.value = data ? data : [];
|
||||
};
|
||||
|
||||
provide("energy_table_data", {
|
||||
tableData,
|
||||
updateTableData,
|
||||
deptData,
|
||||
elecType,
|
||||
subSystem,
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="grid grid-cols-10 gap-2">
|
||||
<div class="col-span-2">
|
||||
<EnergySidebar />
|
||||
</div>
|
||||
<div class="col-span-8">
|
||||
<EnergySearch />
|
||||
<EnergyDataTable />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped></style>
|
@ -0,0 +1,245 @@
|
||||
<script setup>
|
||||
import {
|
||||
computed,
|
||||
ref,
|
||||
watch,
|
||||
inject,
|
||||
onMounted,
|
||||
onBeforeUnmount,
|
||||
onBeforeMount,
|
||||
} from "vue";
|
||||
import { useRoute } from "vue-router";
|
||||
import ButtonGroup from "@/components/customUI/ButtonGroup.vue";
|
||||
import EnergyActionButton from "./EnergyActionButton.vue";
|
||||
import EnergySearchTime from "./EnergySearchTime.vue";
|
||||
import useActiveBtn from "@/hooks/useActiveBtn";
|
||||
import { getEnergySearch } from "@/apis/energy";
|
||||
import { getDepartmentList, getElecTypeList } from "@/apis/asset";
|
||||
import useSearchParam from "@/hooks/useSearchParam";
|
||||
import dayjs from "dayjs";
|
||||
const { deptData, elecType, subSystem } = inject("energy_table_data");
|
||||
const { searchParams, changeParams } = useSearchParam();
|
||||
const route = useRoute();
|
||||
// 選部門
|
||||
const {
|
||||
items: sysDeptItems,
|
||||
changeActiveBtn: changeDeptActiveBtn,
|
||||
setItems: setDeptItems,
|
||||
selectedBtn: selectedDeptItems,
|
||||
} = useActiveBtn("multiple");
|
||||
// 選用電分類
|
||||
const {
|
||||
items: sysElecTypeItems,
|
||||
changeActiveBtn: changeElecTypeActiveBtn,
|
||||
setItems: setElecTypeItems,
|
||||
selectedBtn: selectedElecTypeItems,
|
||||
} = useActiveBtn("multiple");
|
||||
// 選大類
|
||||
const {
|
||||
items: sysMainTagItems,
|
||||
changeActiveBtn: changeMainSysActiveBtn,
|
||||
setItems: setMainSysItems,
|
||||
selectedBtn: selectedMainSysItems,
|
||||
} = useActiveBtn();
|
||||
// 選小類
|
||||
const {
|
||||
items: sysTagItems,
|
||||
changeActiveBtn: changeSysActiveBtn,
|
||||
setItems: setSysItems,
|
||||
selectedBtn: selectedSysItems,
|
||||
} = useActiveBtn();
|
||||
|
||||
// 選點位
|
||||
const {
|
||||
items: points,
|
||||
changeActiveBtn: changeActivePoint,
|
||||
setItems: setPoints,
|
||||
selectedBtn: selectedPoints,
|
||||
} = useActiveBtn("multiple");
|
||||
|
||||
const getDepartment = async () => {
|
||||
const res = await getDepartmentList();
|
||||
const dept = res.data.map((d, index) => ({
|
||||
...d,
|
||||
title: d.name,
|
||||
key: d.id,
|
||||
active: false,
|
||||
}));
|
||||
setDeptItems(dept);
|
||||
};
|
||||
|
||||
const getElecType = async () => {
|
||||
const res = await getElecTypeList();
|
||||
const elecType = res.data.map((d, index) => ({
|
||||
...d,
|
||||
title: d.name,
|
||||
key: d.id,
|
||||
active: false,
|
||||
}));
|
||||
setElecTypeItems(elecType);
|
||||
};
|
||||
|
||||
const getSystems = async (type) => {
|
||||
const res = await getEnergySearch(Number(type));
|
||||
if (res?.data) {
|
||||
// 整理 main_system_tag
|
||||
const mainSystemMap = new Map();
|
||||
res.data.forEach((item) => {
|
||||
if (!mainSystemMap.has(item.main_system_tag)) {
|
||||
mainSystemMap.set(item.main_system_tag, {
|
||||
title: item.full_name,
|
||||
key: item.main_system_tag,
|
||||
active: false,
|
||||
subs: [],
|
||||
});
|
||||
}
|
||||
mainSystemMap.get(item.main_system_tag).subs.push({
|
||||
title: item.sub.full_name,
|
||||
key: item.sub.sub_system_tag,
|
||||
active: false,
|
||||
points: item.sub.point,
|
||||
});
|
||||
});
|
||||
|
||||
const mainSystems = Array.from(mainSystemMap.values());
|
||||
if (mainSystems.length > 0) {
|
||||
mainSystems[0].active = true;
|
||||
}
|
||||
setMainSysItems(mainSystems);
|
||||
}
|
||||
};
|
||||
|
||||
watch(selectedMainSysItems, (newVal) => {
|
||||
if (newVal) {
|
||||
let currentSub = newVal.subs;
|
||||
if (currentSub && currentSub.length > 0) {
|
||||
currentSub[0].active = true;
|
||||
}
|
||||
setSysItems(currentSub);
|
||||
setPoints([]);
|
||||
}
|
||||
});
|
||||
|
||||
watch(selectedSysItems, (newVal, oldVal) => {
|
||||
if (newVal?.key !== searchParams.value.sub_system_tag) {
|
||||
let currentPoints = newVal.points;
|
||||
setPoints(
|
||||
currentPoints.map((point, index) => ({
|
||||
title: point.full_name,
|
||||
key: point.point_system_tag,
|
||||
active: index == 0,
|
||||
}))
|
||||
);
|
||||
subSystem.value = newVal.key;
|
||||
}
|
||||
});
|
||||
|
||||
watch(selectedPoints, (newVal, oldVal) => {
|
||||
changeParams({
|
||||
...searchParams.value,
|
||||
Points: newVal.map((d) => d.key),
|
||||
});
|
||||
});
|
||||
|
||||
watch(selectedDeptItems, (newVal, oldVal) => {
|
||||
deptData.value = newVal.map((d) => d.key);
|
||||
});
|
||||
|
||||
watch(selectedElecTypeItems, (newVal, oldVal) => {
|
||||
elecType.value = newVal.map((d) => d.key);
|
||||
});
|
||||
|
||||
watch(
|
||||
() => route.params.type,
|
||||
(newVal) => {
|
||||
if (newVal) {
|
||||
getSystems(newVal);
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
}
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
getDepartment();
|
||||
getElecType();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col custom-border p-4 mb-4">
|
||||
<div class="flex items-center gap-4 mb-4">
|
||||
<h2 class="text-lg font-bold ps-2 whitespace-nowrap">
|
||||
{{ $t("assetManagement.department") }} :
|
||||
</h2>
|
||||
<ButtonGroup
|
||||
:items="sysDeptItems"
|
||||
:withLine="true"
|
||||
:onclick="
|
||||
(e, item) => {
|
||||
changeDeptActiveBtn(item);
|
||||
}
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex items-center gap-4 mb-4" v-if="route.params.type == 3">
|
||||
<h2 class="text-lg font-bold ps-2 whitespace-nowrap">
|
||||
{{ $t("energy.electricity_classification") }} :
|
||||
</h2>
|
||||
<ButtonGroup
|
||||
:items="sysElecTypeItems"
|
||||
:withLine="true"
|
||||
:onclick="
|
||||
(e, item) => {
|
||||
changeElecTypeActiveBtn(item);
|
||||
}
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex items-center gap-4 mb-4">
|
||||
<h2 class="text-lg font-bold ps-2 whitespace-nowrap">
|
||||
{{ $t("history.system_category") }} :
|
||||
</h2>
|
||||
<ButtonGroup
|
||||
:items="sysMainTagItems"
|
||||
:withLine="true"
|
||||
:onclick="
|
||||
(e, item) => {
|
||||
changeMainSysActiveBtn(item);
|
||||
}
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex items-center gap-4 mb-4">
|
||||
<h2 class="text-lg font-bold ps-2 whitespace-nowrap">
|
||||
{{ $t("history.device_category") }} :
|
||||
</h2>
|
||||
<ButtonGroup
|
||||
:items="sysTagItems"
|
||||
:withLine="true"
|
||||
:onclick="
|
||||
(e, item) => {
|
||||
changeSysActiveBtn(item);
|
||||
}
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex items-center gap-4">
|
||||
<h2 class="text-lg font-bold ps-2 whitespace-nowrap">
|
||||
{{ $t("history.point") }} :
|
||||
</h2>
|
||||
<ButtonGroup
|
||||
:items="points"
|
||||
:withLine="true"
|
||||
:onclick="
|
||||
(e, item) => {
|
||||
changeActivePoint(item);
|
||||
}
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
<EnergySearchTime />
|
||||
<EnergyActionButton />
|
||||
</div>
|
||||
</template>
|
@ -0,0 +1,116 @@
|
||||
<script setup>
|
||||
import { ref, watch, onMounted } from "vue";
|
||||
import useSearchParam from "@/hooks/useSearchParam";
|
||||
import dayjs from "dayjs";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useRoute } from "vue-router";
|
||||
const { t, locale } = useI18n();
|
||||
const { searchParams, changeParams } = useSearchParam();
|
||||
const route = useRoute();
|
||||
const itemsForStartTime = ref([]);
|
||||
|
||||
const itemsForEndTime = ref();
|
||||
|
||||
const initializeItems = () => {
|
||||
itemsForStartTime.value = [
|
||||
{
|
||||
key: "Start_date",
|
||||
value: searchParams.value?.Start_date
|
||||
? dayjs(searchParams.value?.Start_date)
|
||||
: dayjs(),
|
||||
dateFormat: "yyyy-MM-dd",
|
||||
placeholder: t("history.start_date"),
|
||||
},
|
||||
{
|
||||
key: "Start_time",
|
||||
value: { hours: 0, minutes: 0, seconds: 0 },
|
||||
dateFormat: "HH:mm",
|
||||
timePicker: true,
|
||||
placeholder: t("history.start_time"),
|
||||
},
|
||||
];
|
||||
|
||||
itemsForEndTime.value = [
|
||||
{
|
||||
key: "End_date",
|
||||
value: searchParams.value?.End_date
|
||||
? dayjs(searchParams.value?.End_date)
|
||||
: dayjs(),
|
||||
dateFormat: "yyyy-MM-dd",
|
||||
placeholder: t("history.end_date"),
|
||||
},
|
||||
{
|
||||
key: "End_time",
|
||||
value: {
|
||||
hours: dayjs().format("HH"),
|
||||
minutes: dayjs().format("mm"),
|
||||
seconds: 0,
|
||||
},
|
||||
dateFormat: "HH:mm",
|
||||
timePicker: true,
|
||||
placeholder: t("history.end_time"),
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
watch(
|
||||
() => route.params.type,
|
||||
() => {
|
||||
initializeItems();
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
}
|
||||
);
|
||||
|
||||
watch(locale, () => {
|
||||
initializeItems();
|
||||
});
|
||||
|
||||
watch(
|
||||
[itemsForStartTime, itemsForEndTime],
|
||||
([newStart, newEnd]) => {
|
||||
if (newStart && newEnd) {
|
||||
const dates = Object.fromEntries(
|
||||
[newStart[0], newEnd[0]].map(({ key, value, dateFormat }) => [
|
||||
key,
|
||||
dayjs(value).format(dateFormat.toLocaleUpperCase()),
|
||||
])
|
||||
);
|
||||
|
||||
const times = Object.fromEntries(
|
||||
[newStart[1], newEnd[1]].map(
|
||||
({ key, value, dateFormat }, index, arr) => [
|
||||
key,
|
||||
`${value.hours}:${value.minutes}`,
|
||||
]
|
||||
)
|
||||
);
|
||||
changeParams({
|
||||
...searchParams.value,
|
||||
...dates,
|
||||
Start_time: dayjs(`${dates.Start_date} ${times.Start_time}`).format(
|
||||
"HH:mm"
|
||||
),
|
||||
End_time: dayjs(`${dates.End_date} ${times.End_time}`).format("HH:mm"),
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
deep: true,
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex items-center gap-4 mb-4">
|
||||
<h2 class="text-lg font-bold ps-2 whitespace-nowrap">
|
||||
{{ $t("history.date_range") }} :
|
||||
</h2>
|
||||
<DateGroup class="mr-3" :items="itemsForStartTime" :withLine="true" />
|
||||
<DateGroup :items="itemsForEndTime" :withLine="true" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped></style>
|
@ -0,0 +1,261 @@
|
||||
<script setup>
|
||||
import Checkbox from "@/components/customUI/Checkbox.vue";
|
||||
import { computed, ref, watch, inject } from "vue";
|
||||
import useBuildingStore from "@/stores/useBuildingStore";
|
||||
import useSearchParam from "@/hooks/useSearchParam";
|
||||
import { getHistorySideBar } from "@/apis/history";
|
||||
import { useI18n } from "vue-i18n";
|
||||
const { t } = useI18n();
|
||||
const { searchParams, changeParams } = useSearchParam();
|
||||
const { deptData, elecType, subSystem } = inject("energy_table_data");
|
||||
const selectedBuilding = ref([]);
|
||||
const deviceData = ref([]);
|
||||
const searchTerm = ref(""); //搜尋文字
|
||||
const activeSearchTerm = ref("");
|
||||
|
||||
const getDeviceData = async ({
|
||||
sub_system_tag,
|
||||
department_id,
|
||||
elec_type_id,
|
||||
}) => {
|
||||
const res = await getHistorySideBar({
|
||||
sub_system_tag,
|
||||
department_id,
|
||||
elec_type_id,
|
||||
});
|
||||
deviceData.value = (res.data || []).map((building) => ({
|
||||
building_tag: building.building_tag,
|
||||
building_name: building.building_name,
|
||||
floors: building.floor_list.map((floor) => ({
|
||||
floor_tag: floor.device_floor_tag,
|
||||
floor_name: floor.floor_name,
|
||||
devices: floor.device_list.map((device) => ({
|
||||
...device,
|
||||
key: device.device_number,
|
||||
})),
|
||||
})),
|
||||
}));
|
||||
|
||||
selectedBuilding.value = (res.data || []).map((d) => d.building_tag);
|
||||
changeSelected([res.data[0]?.floor_list[0]?.device_list[0]?.device_number]);
|
||||
};
|
||||
|
||||
const sysIsExisted = (building_tag) => {
|
||||
return selectedBuilding.value.includes(building_tag);
|
||||
};
|
||||
|
||||
watch(
|
||||
[() => subSystem.value, () => deptData.value, () => elecType.value],
|
||||
(newVal) => {
|
||||
const [sub_system_tag, department_id, elec_type_id] = newVal;
|
||||
getDeviceData({
|
||||
sub_system_tag,
|
||||
department_id,
|
||||
elec_type_id,
|
||||
});
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
deep: true,
|
||||
}
|
||||
);
|
||||
|
||||
const selectedDeviceNumber = computed(() => {
|
||||
return typeof searchParams.value.Device_list === "string"
|
||||
? [searchParams.value.Device_list]
|
||||
: searchParams.value.Device_list || [];
|
||||
});
|
||||
|
||||
const isChecked = (device, checked) => {
|
||||
if (checked) {
|
||||
changeSelected([...selectedDeviceNumber.value, device]);
|
||||
} else {
|
||||
changeSelected(
|
||||
selectedDeviceNumber.value.filter(
|
||||
(device_number) => device_number !== device
|
||||
)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const checkedBuilding = computed(() => {
|
||||
let selected = [];
|
||||
deviceData.value.forEach(({ building_tag, floors }) => {
|
||||
let allDevices = [];
|
||||
floors.forEach((floor) => {
|
||||
allDevices = [...allDevices, ...floor.devices];
|
||||
});
|
||||
|
||||
if (
|
||||
selectedDeviceNumber.value?.filter(
|
||||
(d) => typeof d === "string" && d.split("_")[1] === building_tag
|
||||
).length === allDevices.length
|
||||
) {
|
||||
selected.push(building_tag);
|
||||
}
|
||||
});
|
||||
return selected;
|
||||
});
|
||||
|
||||
const buildingCheck = (building_tag) => {
|
||||
const building = deviceData.value.find(
|
||||
(d) => d.building_tag === building_tag
|
||||
);
|
||||
const allDevicesInBuilding = building.floors.flatMap((floor) =>
|
||||
floor.devices.map((device) => device.device_number)
|
||||
);
|
||||
|
||||
if (checkedBuilding.value?.includes(building_tag)) {
|
||||
// 取消勾選該建築下的所有設備
|
||||
changeSelected(
|
||||
selectedDeviceNumber.value.filter(
|
||||
(device_number) => !allDevicesInBuilding.includes(device_number)
|
||||
)
|
||||
);
|
||||
} else {
|
||||
// 勾選該建築下的所有設備
|
||||
changeSelected([
|
||||
...new Set([...selectedDeviceNumber.value, ...allDevicesInBuilding]),
|
||||
]);
|
||||
}
|
||||
};
|
||||
|
||||
const areAllDevicesCheckedInFloor = (devices) => {
|
||||
return devices.every((device) =>
|
||||
selectedDeviceNumber.value.includes(device.device_number)
|
||||
);
|
||||
};
|
||||
|
||||
const toggleFloorDevices = (devices) => {
|
||||
const allChecked = areAllDevicesCheckedInFloor(devices);
|
||||
|
||||
if (allChecked) {
|
||||
changeSelected(
|
||||
selectedDeviceNumber.value.filter(
|
||||
(device_number) =>
|
||||
!devices.some((device) => device.device_number === device_number)
|
||||
)
|
||||
);
|
||||
} else {
|
||||
const deviceNumbers = devices.map((device) => device.device_number);
|
||||
changeSelected([
|
||||
...new Set([...selectedDeviceNumber.value, ...deviceNumbers]),
|
||||
]);
|
||||
}
|
||||
};
|
||||
|
||||
const changeSelected = (Device_list) => {
|
||||
changeParams({
|
||||
...searchParams.value,
|
||||
Device_list,
|
||||
});
|
||||
};
|
||||
|
||||
const filteredDeviceData = computed(() => {
|
||||
if (!activeSearchTerm.value) return deviceData.value;
|
||||
return deviceData.value
|
||||
.map((building) => ({
|
||||
...building,
|
||||
floors: building.floors
|
||||
.map((floor) => ({
|
||||
...floor,
|
||||
devices: floor.devices.filter((device) =>
|
||||
device.device_name.includes(activeSearchTerm.value)
|
||||
),
|
||||
}))
|
||||
.filter((floor) => floor.devices.length > 0),
|
||||
}))
|
||||
.filter((building) => building.floors.length > 0);
|
||||
});
|
||||
|
||||
const handleSearch = (e) => {
|
||||
e.preventDefault();
|
||||
activeSearchTerm.value = searchTerm.value;
|
||||
};
|
||||
|
||||
const handleInput = (e) => {
|
||||
searchTerm.value = e.target.value;
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="custom-border p-4">
|
||||
<label
|
||||
class="input input-bordered bg-transparent rounded-sm flex items-center px-2 border-info focus-within:border-info"
|
||||
>
|
||||
<font-awesome-icon
|
||||
:icon="['fas', 'search']"
|
||||
class="w-6 h-6 mr-2 text-info"
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
:value="searchTerm"
|
||||
@input="handleInput"
|
||||
:placeholder="t('button.enter_text')"
|
||||
class="text-white bg-transparent w-full"
|
||||
@keyup.enter="handleSearch"
|
||||
/>
|
||||
</label>
|
||||
<ul class="menu text-lg">
|
||||
<template
|
||||
v-for="building in filteredDeviceData"
|
||||
:key="building.building_tag"
|
||||
>
|
||||
<li>
|
||||
<details :open="selectedBuilding.includes(building.building_tag)">
|
||||
<summary>
|
||||
<Checkbox
|
||||
:title="building.building_name"
|
||||
:checked="checkedBuilding.includes(building.building_tag)"
|
||||
@click="() => buildingCheck(building.building_tag)"
|
||||
/>
|
||||
</summary>
|
||||
<ul>
|
||||
<template v-for="floor in building.floors" :key="floor.floor_tag">
|
||||
<li>
|
||||
<details open>
|
||||
<summary>
|
||||
<Checkbox
|
||||
:title="floor.floor_name"
|
||||
:checked="areAllDevicesCheckedInFloor(floor.devices)"
|
||||
@click="toggleFloorDevices(floor.devices)"
|
||||
/>
|
||||
</summary>
|
||||
<ul>
|
||||
<template
|
||||
v-for="device in floor.devices"
|
||||
:key="device.device_number"
|
||||
>
|
||||
<li class="flex items-start">
|
||||
<Checkbox
|
||||
:title="device.device_name"
|
||||
:checked="
|
||||
selectedDeviceNumber.includes(
|
||||
device.device_number
|
||||
)
|
||||
"
|
||||
@click="
|
||||
() =>
|
||||
isChecked(
|
||||
device.device_number,
|
||||
!selectedDeviceNumber.includes(
|
||||
device.device_number
|
||||
)
|
||||
)
|
||||
"
|
||||
/>
|
||||
</li>
|
||||
</template>
|
||||
</ul>
|
||||
</details>
|
||||
</li>
|
||||
</template>
|
||||
</ul>
|
||||
</details>
|
||||
</li>
|
||||
</template>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped></style>
|
@ -0,0 +1,23 @@
|
||||
<script setup>
|
||||
import EnergyReportSearch from "./EnergyReportSearch.vue";
|
||||
import EnergyReportTable from "./EnergyReportTable.vue";
|
||||
import dayjs from "dayjs";
|
||||
import { ref, provide } from "vue";
|
||||
|
||||
const tableData = ref([]);
|
||||
const loading = ref(false);
|
||||
|
||||
provide("energy_table_data", {
|
||||
tableData,
|
||||
loading,
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="mt-2">
|
||||
<EnergyReportSearch />
|
||||
<EnergyReportTable />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped></style>
|
@ -0,0 +1,186 @@
|
||||
<script setup>
|
||||
import { ref, onMounted, watch, inject } from "vue";
|
||||
import { useRoute } from "vue-router";
|
||||
import useActiveBtn from "@/hooks/useActiveBtn";
|
||||
import EnergyReportTimeRange from "./EnergyReportTimeRange.vue";
|
||||
import {
|
||||
getDepartmentList,
|
||||
getAssetFloorList,
|
||||
getElecTypeList,
|
||||
} from "@/apis/asset";
|
||||
import { getAccountUserList } from "@/apis/account";
|
||||
import { useI18n } from "vue-i18n";
|
||||
const { t } = useI18n();
|
||||
const route = useRoute();
|
||||
const { tableData, loading } = inject("energy_table_data");
|
||||
const formState = ref({
|
||||
department: [],
|
||||
elecType: [],
|
||||
floor: [],
|
||||
start_time: null,
|
||||
end_time: null,
|
||||
type: 0,
|
||||
});
|
||||
// 選部門
|
||||
const {
|
||||
items: sysDeptItems,
|
||||
changeActiveBtn: changeDeptActiveBtn,
|
||||
setItems: setDeptItems,
|
||||
selectedBtn: selectedDeptItems,
|
||||
} = useActiveBtn("multiple");
|
||||
// 選用電分類
|
||||
const {
|
||||
items: sysElecItems,
|
||||
changeActiveBtn: changeElecActiveBtn,
|
||||
setItems: setElecItems,
|
||||
selectedBtn: selectedElecItems,
|
||||
} = useActiveBtn("multiple");
|
||||
// 選樓層
|
||||
const {
|
||||
items: sysFloorItems,
|
||||
changeActiveBtn: changeFloorActiveBtn,
|
||||
setItems: setFloorItems,
|
||||
selectedBtn: selectedFloorItems,
|
||||
} = useActiveBtn("multiple");
|
||||
|
||||
const getDepartment = async () => {
|
||||
const res = await getDepartmentList();
|
||||
const dept = res.data.map((d, index) => ({
|
||||
...d,
|
||||
title: d.name,
|
||||
key: d.id,
|
||||
active: index === 0,
|
||||
}));
|
||||
setDeptItems(dept);
|
||||
};
|
||||
|
||||
const getElecType = async () => {
|
||||
const res = await getElecTypeList();
|
||||
const elecType = res.data.map((d, index) => ({
|
||||
...d,
|
||||
title: d.name,
|
||||
key: d.id,
|
||||
active: index === 0,
|
||||
}));
|
||||
setElecItems(elecType);
|
||||
};
|
||||
|
||||
const getFloor = async () => {
|
||||
const res = await getAssetFloorList();
|
||||
const Floor = res.data[0]?.floors.map((d, index) => ({
|
||||
...d,
|
||||
title: d.full_name,
|
||||
key: d.floor_guid,
|
||||
active: index === 0,
|
||||
}));
|
||||
setFloorItems(Floor);
|
||||
};
|
||||
|
||||
const onSearch = async () => {
|
||||
loading.value = true;
|
||||
const res = await getAccountUserList(formState.value);
|
||||
tableData.value = res.data;
|
||||
loading.value = false;
|
||||
};
|
||||
|
||||
// 監聽部門選取
|
||||
watch(
|
||||
selectedDeptItems,
|
||||
(newVal) => {
|
||||
formState.value.department = newVal.map((item) => item.key);
|
||||
},
|
||||
{ deep: true }
|
||||
);
|
||||
|
||||
// 監聽用電分類選取
|
||||
watch(
|
||||
selectedElecItems,
|
||||
(newVal) => {
|
||||
formState.value.elecType = newVal.map((item) => item.key);
|
||||
},
|
||||
{ deep: true }
|
||||
);
|
||||
|
||||
// 監聽樓層選取
|
||||
watch(
|
||||
selectedFloorItems,
|
||||
(newVal) => {
|
||||
formState.value.floor = newVal.map((item) => item.key);
|
||||
},
|
||||
{ deep: true }
|
||||
);
|
||||
|
||||
// 監聽路由參數變化
|
||||
watch(
|
||||
() => route.params.type,
|
||||
(newVal) => {
|
||||
formState.value.type = newVal;
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
getDepartment();
|
||||
getElecType();
|
||||
getFloor();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="w-full custom-border px-4 pb-4 mb-4">
|
||||
<EnergyReportTimeRange :form="formState" />
|
||||
<div class="flex items-center gap-4 mb-4">
|
||||
<h2 class="text-lg font-bold whitespace-nowrap">
|
||||
{{ $t("assetManagement.department") }} :
|
||||
</h2>
|
||||
<ButtonGroup
|
||||
:items="sysDeptItems"
|
||||
:withLine="true"
|
||||
:onclick="
|
||||
(e, item) => {
|
||||
changeDeptActiveBtn(item);
|
||||
}
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex items-center gap-4 mb-4">
|
||||
<h2 class="text-lg font-bold whitespace-nowrap">
|
||||
{{ $t("energy.floor") }} :
|
||||
</h2>
|
||||
<ButtonGroup
|
||||
:items="sysFloorItems"
|
||||
:withLine="true"
|
||||
:onclick="
|
||||
(e, item) => {
|
||||
changeFloorActiveBtn(item);
|
||||
}
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex items-center gap-4 mb-4">
|
||||
<h2 class="text-lg font-bold whitespace-nowrap">
|
||||
{{ $t("energy.electricity_classification") }} :
|
||||
</h2>
|
||||
<ButtonGroup
|
||||
:items="sysElecItems"
|
||||
:withLine="true"
|
||||
:onclick="
|
||||
(e, item) => {
|
||||
changeElecActiveBtn(item);
|
||||
}
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<button class="btn btn-search me-4" @click.stop.prevent="onSearch">
|
||||
<font-awesome-icon :icon="['fas', 'search']" class="text-lg" />
|
||||
{{ $t("button.search") }}
|
||||
</button>
|
||||
<button class="btn btn-export" @click.stop.prevent="search">
|
||||
<font-awesome-icon :icon="['fas', 'download']" class="text-lg" />
|
||||
{{ $t("button.export") }}
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped></style>
|
@ -0,0 +1,33 @@
|
||||
<script setup>
|
||||
import Table from "@/components/customUI/Table.vue";
|
||||
import { inject, computed } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
const { t } = useI18n();
|
||||
const { tableData, loading } = inject("energy_table_data");
|
||||
const columns = computed(() => [
|
||||
{
|
||||
title: t("history.building_name"),
|
||||
key: "building_name",
|
||||
},
|
||||
{
|
||||
title: t("energy.floor"),
|
||||
key: "building_name",
|
||||
},
|
||||
{
|
||||
title: t("history.device_name"),
|
||||
key: "device_name",
|
||||
filter: true,
|
||||
},
|
||||
{
|
||||
title: t("energy.subtotal"),
|
||||
key: "subtotal",
|
||||
}
|
||||
]);
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Table :columns="columns" :dataSource="tableData" :loading="loading"></Table>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped></style>
|
@ -0,0 +1,129 @@
|
||||
<script setup>
|
||||
import { ref, watch, defineProps } from "vue";
|
||||
import { useRoute } from "vue-router";
|
||||
import dayjs from "dayjs";
|
||||
import { useI18n } from "vue-i18n";
|
||||
const { t, locale } = useI18n();
|
||||
const route = useRoute();
|
||||
const dateRange = ref([]);
|
||||
const props = defineProps({
|
||||
form:Object,
|
||||
});
|
||||
const setDateRange = (type) => {
|
||||
switch (Number(type)) {
|
||||
case 1:
|
||||
dateRange.value = [
|
||||
{
|
||||
key: "start_at",
|
||||
value: dayjs().startOf("month").valueOf(),
|
||||
dateFormat: "yyyy-MM-dd",
|
||||
placeholder: t("alert.start_date"),
|
||||
},
|
||||
{
|
||||
key: "end_at",
|
||||
value: dayjs().endOf("month").valueOf(),
|
||||
dateFormat: "yyyy-MM-dd",
|
||||
placeholder: t("alert.end_date"),
|
||||
},
|
||||
];
|
||||
break;
|
||||
case 2:
|
||||
dateRange.value = [
|
||||
{
|
||||
key: "start_at",
|
||||
value: dayjs().startOf("week").valueOf(),
|
||||
dateFormat: "yyyy-MM-dd",
|
||||
placeholder: t("alert.start_date"),
|
||||
},
|
||||
{
|
||||
key: "end_at",
|
||||
value: dayjs().endOf("week").valueOf(),
|
||||
dateFormat: "yyyy-MM-dd",
|
||||
placeholder: t("alert.end_date"),
|
||||
},
|
||||
];
|
||||
break;
|
||||
case 3:
|
||||
dateRange.value = [
|
||||
{
|
||||
key: "start_at",
|
||||
value: dayjs().startOf("year").valueOf(),
|
||||
dateFormat: "yyyy-MM-dd",
|
||||
placeholder: t("alert.start_date"),
|
||||
},
|
||||
{
|
||||
key: "end_at",
|
||||
value: dayjs().endOf("year").valueOf(),
|
||||
dateFormat: "yyyy-MM-dd",
|
||||
placeholder: t("alert.end_date"),
|
||||
},
|
||||
];
|
||||
break;
|
||||
case 4:
|
||||
dateRange.value = [
|
||||
{
|
||||
key: "start_at",
|
||||
value: dayjs("2024-01-01").valueOf(),
|
||||
dateFormat: "yyyy-MM-dd",
|
||||
placeholder: t("alert.start_date"),
|
||||
},
|
||||
{
|
||||
key: "end_at",
|
||||
value: dayjs().endOf("year").valueOf(),
|
||||
dateFormat: "yyyy-MM-dd",
|
||||
placeholder: t("alert.end_date"),
|
||||
},
|
||||
];
|
||||
break;
|
||||
default:
|
||||
dateRange.value = [
|
||||
{
|
||||
key: "start_at",
|
||||
value: dayjs("2024-01-01").valueOf(),
|
||||
dateFormat: "yyyy-MM-dd",
|
||||
placeholder: t("alert.start_date"),
|
||||
},
|
||||
{
|
||||
key: "end_at",
|
||||
value: dayjs().endOf("year").valueOf(),
|
||||
dateFormat: "yyyy-MM-dd",
|
||||
placeholder: t("alert.end_date"),
|
||||
},
|
||||
];
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
// 監聽路由參數變化
|
||||
watch(
|
||||
() => route.params.type,
|
||||
(newVal) => {
|
||||
setDateRange(newVal)
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
|
||||
// 監聽 dateRange 的變化
|
||||
watch(
|
||||
dateRange,
|
||||
() => {
|
||||
if(dateRange.value.length > 0){
|
||||
props.form.start_time= dayjs(dateRange.value[0].value).format("YYYY-MM-DD");
|
||||
props.form.end_time= dayjs(dateRange.value[1].value).format("YYYY-MM-DD");
|
||||
}
|
||||
},
|
||||
{ deep: true, immediate:true }
|
||||
);
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex items-center">
|
||||
<span class="text-lg font-bold me-4">{{ $t("operation.date") }} : </span>
|
||||
<DateGroup :items="dateRange" :withLine="true" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped></style>
|
@ -13,8 +13,8 @@ const deviceData = ref([]);
|
||||
const searchTerm = ref(""); //搜尋文字
|
||||
const activeSearchTerm = ref("");
|
||||
|
||||
const getDeviceData = async (sub_tag, renew) => {
|
||||
const res = await getHistorySideBar(sub_tag);
|
||||
const getDeviceData = async (sub_system_tag, renew) => {
|
||||
const res = await getHistorySideBar({sub_system_tag: sub_system_tag});
|
||||
deviceData.value = res.data.map((building) => ({
|
||||
building_tag: building.building_tag,
|
||||
building_name: building.building_name,
|
||||
|
@ -32,7 +32,7 @@ watch(
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal id="system_info_modal" :onCancel="onCancel" width="600" :draggable="!data?.isMobile" :backdrop="false">
|
||||
<Modal id="system_info_modal" :width="600" :draggable="!data?.isMobile" :backdrop="false">
|
||||
<template #modalContent>
|
||||
<SystemInfoModalContent />
|
||||
</template>
|
||||
|
@ -1,50 +1,77 @@
|
||||
<script setup>
|
||||
import { ref, computed, inject, watch } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import dayjs from 'dayjs';
|
||||
const { t, locale } = useI18n();
|
||||
import dayjs from "dayjs";
|
||||
|
||||
const { selectedDevice, selectedDeviceRealtime } = inject("system_selectedDevice");
|
||||
const loading = ref(true);
|
||||
const updatedTime = ref(null);
|
||||
const data = computed(() => {
|
||||
const { t } = useI18n();
|
||||
const { selectedDevice, selectedDeviceRealtime } = inject(
|
||||
"system_selectedDevice"
|
||||
);
|
||||
|
||||
return selectedDevice.value?.value?.points?.map((d) => ({
|
||||
const groupedData = computed(() => {
|
||||
const grouped = {};
|
||||
|
||||
if (selectedDeviceRealtime?.value) {
|
||||
selectedDeviceRealtime.value.forEach((record) => {
|
||||
const { ori_device_number, time } = record;
|
||||
|
||||
if (!grouped[ori_device_number]) {
|
||||
grouped[ori_device_number] = {
|
||||
time,
|
||||
data: [],
|
||||
};
|
||||
}
|
||||
|
||||
selectedDevice.value.value.points.forEach((d) => {
|
||||
if (record.point === d.points) {
|
||||
grouped[ori_device_number].data.push({
|
||||
...d,
|
||||
value: selectedDeviceRealtime?.value?.find(({ point }) => point === d.points)?.value || 0
|
||||
})) || []
|
||||
value: record.value,
|
||||
});
|
||||
|
||||
watch(selectedDeviceRealtime, (newValue,oldValue) => {
|
||||
if (newValue) {
|
||||
loading.value = false;
|
||||
updatedTime.value = dayjs(new Date()).format("YYYY-MM-DD HH:mm:ss");
|
||||
}else{
|
||||
loading.value = true;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const columns = [{
|
||||
title: t("system.attribute"),
|
||||
key: "full_name"
|
||||
},
|
||||
{
|
||||
title: t("system.value"),
|
||||
key: "value"
|
||||
}]
|
||||
|
||||
return grouped;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span v-if="updatedTime" class="mt-5 text-info">{{$t("operation.updated_time")}} : {{ updatedTime }}</span>
|
||||
<Table
|
||||
:loading="loading"
|
||||
:columns="columns"
|
||||
:dataSource="data || []"
|
||||
:withStyle="false"
|
||||
:pagination="false"
|
||||
<div v-if="selectedDeviceRealtime">
|
||||
<div v-for="(group, oriDeviceNumber) in groupedData" :key="oriDeviceNumber">
|
||||
<p class="mt-5 text-info">
|
||||
{{ $t("operation.updated_time") }} : {{ group.time }}
|
||||
</p>
|
||||
<table class="table mt-2">
|
||||
<thead>
|
||||
<tr>
|
||||
<th
|
||||
class="border text-white text-lg text-center bg-cyan-600 bg-opacity-30"
|
||||
>
|
||||
</Table>
|
||||
{{ $t("system.attribute") }}
|
||||
</th>
|
||||
<th
|
||||
class="border text-white text-lg text-center bg-cyan-600 bg-opacity-30"
|
||||
>
|
||||
{{ $t("system.value") }}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(group, index) in group.data" :key="index">
|
||||
<td class="border text-white text-lg text-center">
|
||||
{{ group.full_name }}
|
||||
</td>
|
||||
<td class="border text-white text-lg text-center">
|
||||
{{ group.value }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-2xl text-center my-6" v-else>{{$t("table.no_data")}}</p>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
|
Loading…
Reference in New Issue
Block a user