登入帳號紀錄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_DEVLIST_API = `api/Alarm/GetDevList`; // 取得設備
|
||||||
export const GET_OUTLIERS_POINTS_API = `api/Alarm/GetAlarmPoints`; // 取得點位
|
export const GET_OUTLIERS_POINTS_API = `api/Alarm/GetAlarmPoints`; // 取得點位
|
||||||
export const POST_OUTLIERS_SETTING_API = `api/Alarm/SaveAlarmSetting`; // 新增與修改
|
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,
|
POST_ALERT_MEMBER,
|
||||||
DELETE_ALERT_MEMBER,
|
DELETE_ALERT_MEMBER,
|
||||||
GET_NOTICE_LIST_API,
|
GET_NOTICE_LIST_API,
|
||||||
|
GET_ALERT_SCHEDULE_LIST_API,
|
||||||
|
POST_ALERT_SCHEDULE,
|
||||||
|
DELETE_ALERT_SCHEDULE,
|
||||||
} 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,3 +159,34 @@ export const postOutliersSetting = async (data) => {
|
|||||||
code: res.code,
|
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 GET_ASSET_DEPARTMENT_API = `/AssetManage/GetDepartment`;
|
||||||
export const POST_ASSET_DEPARTMENT_API = `/AssetManage/SaveDepartment`;
|
export const POST_ASSET_DEPARTMENT_API = `/AssetManage/SaveDepartment`;
|
||||||
export const DELETE_ASSET_DEPARTMENT_API = `/AssetManage/DeleteDepartment`;
|
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,
|
GET_ASSET_DEPARTMENT_API,
|
||||||
POST_ASSET_DEPARTMENT_API,
|
POST_ASSET_DEPARTMENT_API,
|
||||||
DELETE_ASSET_DEPARTMENT_API,
|
DELETE_ASSET_DEPARTMENT_API,
|
||||||
|
GET_ASSET_ELECTYPE_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";
|
||||||
@ -203,8 +204,8 @@ export const getAssetSubPoint = async (sub_system_tag) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getIOTSchema = async (sub_system_tag, points) => {
|
export const getIOTSchema = async (variable_id) => {
|
||||||
const res = await instance.post(GET_ASSET_IOT_SCHEMA_API, {});
|
const res = await instance.post(GET_ASSET_IOT_SCHEMA_API, {variable_id});
|
||||||
|
|
||||||
return apihandler(res.code, res.data, {
|
return apihandler(res.code, res.data, {
|
||||||
msg: res.msg,
|
msg: res.msg,
|
||||||
@ -241,3 +242,12 @@ export const deleteDepartmentItem = async (id) => {
|
|||||||
code: res.code,
|
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_REALTIME_DIST_API = `/api/Energe/GetRealTimeDistribution`;
|
||||||
export const GET_ELECUSE_DAY_API = `/api/Energe/GetElecUseDay`;
|
export const GET_ELECUSE_DAY_API = `/api/Energe/GetElecUseDay`;
|
||||||
export const GET_TAI_POWER_API = `/api/Energe/GetTaipower`;
|
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_REALTIME_DIST_API,
|
||||||
GET_ELECUSE_DAY_API,
|
GET_ELECUSE_DAY_API,
|
||||||
GET_TAI_POWER_API,
|
GET_TAI_POWER_API,
|
||||||
|
GET_SIDEBAR_API,
|
||||||
|
GET_SEARCH_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";
|
||||||
@ -31,4 +33,22 @@ export const getTaipower = async () => {
|
|||||||
msg: res.msg,
|
msg: res.msg,
|
||||||
code: res.code,
|
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 apihandler from "@/util/apiHandler";
|
||||||
import downloadExcel from "@/util/downloadExcel";
|
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, {
|
const res = await instance.post(GET_HISTORY_SIDEBAR_API, {
|
||||||
sub_system_tag,
|
sub_system_tag,
|
||||||
|
department_id,
|
||||||
|
elec_type_id,
|
||||||
});
|
});
|
||||||
|
|
||||||
return apihandler(res.code, res.data, {
|
return apihandler(res.code, res.data, {
|
||||||
@ -42,6 +48,7 @@ export const getHistoryData = async ({
|
|||||||
End_time,
|
End_time,
|
||||||
Device_list,
|
Device_list,
|
||||||
Points,
|
Points,
|
||||||
|
table_type,
|
||||||
}) => {
|
}) => {
|
||||||
/*
|
/*
|
||||||
{
|
{
|
||||||
@ -62,6 +69,7 @@ export const getHistoryData = async ({
|
|||||||
Device_list: Array.isArray(Device_list) ? Device_list : [Device_list],
|
Device_list: Array.isArray(Device_list) ? Device_list : [Device_list],
|
||||||
Points: Array.isArray(Points) ? Points : [Points],
|
Points: Array.isArray(Points) ? Points : [Points],
|
||||||
Type: parseInt(Type),
|
Type: parseInt(Type),
|
||||||
|
table_type: parseInt(table_type),
|
||||||
});
|
});
|
||||||
|
|
||||||
return apihandler(res.code, res.data, {
|
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=${
|
document.cookie = `JWT-Authorization=${res.data.token}; Max-Age=${
|
||||||
24 * 60 * 60 * 1000
|
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, {
|
return apihandler(res.code, res.data, {
|
||||||
|
@ -118,6 +118,6 @@ const curWidth = computed(() => {
|
|||||||
</style>
|
</style>
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.line {
|
.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>
|
</style>
|
||||||
|
@ -51,7 +51,7 @@ watch(
|
|||||||
);
|
);
|
||||||
|
|
||||||
const updateDataSource = (data) => {
|
const updateDataSource = (data) => {
|
||||||
console.log("update", data);
|
// console.log("update", data);
|
||||||
currentDataSource.value = data;
|
currentDataSource.value = data;
|
||||||
};
|
};
|
||||||
provide("current_table_data", {
|
provide("current_table_data", {
|
||||||
@ -62,6 +62,7 @@ const sortRule = ref({});
|
|||||||
const filterColumn = ref({});
|
const filterColumn = ref({});
|
||||||
const filterItems = ref({});
|
const filterItems = ref({});
|
||||||
const selectedFilterItem = ref({});
|
const selectedFilterItem = ref({});
|
||||||
|
const searchQuery = ref("");
|
||||||
|
|
||||||
const toggleFilterModal = (key) => {
|
const toggleFilterModal = (key) => {
|
||||||
let newFilter = Object.assign(filterColumn.value);
|
let newFilter = Object.assign(filterColumn.value);
|
||||||
@ -136,7 +137,10 @@ const form = ref(null);
|
|||||||
|
|
||||||
const onFilter = (key, reset = false) => {
|
const onFilter = (key, reset = false) => {
|
||||||
const formData = new FormData(form.value);
|
const formData = new FormData(form.value);
|
||||||
reset && formData.delete(key);
|
if (reset) {
|
||||||
|
formData.delete(key);
|
||||||
|
searchQuery.value = "";
|
||||||
|
}
|
||||||
for (let [name, value] of formData) {
|
for (let [name, value] of formData) {
|
||||||
console.log(name, value);
|
console.log(name, value);
|
||||||
}
|
}
|
||||||
@ -232,22 +236,38 @@ watch(
|
|||||||
"
|
"
|
||||||
@click="() => toggleFilterModal(column.key)"
|
@click="() => toggleFilterModal(column.key)"
|
||||||
/>
|
/>
|
||||||
<div
|
<div class="fixed z-50" v-if="filterColumn[column.key]">
|
||||||
class="absolute top-full -left-1/2 z-50"
|
|
||||||
v-if="filterColumn[column.key]"
|
|
||||||
>
|
|
||||||
<div class="card min-w-max bg-body shadow-xl px-10 py-5">
|
<div class="card min-w-max bg-body shadow-xl px-10 py-5">
|
||||||
<Checkbox
|
<label
|
||||||
v-for="item in filterItems[column.key]"
|
class="input input-bordered bg-transparent rounded-lg flex items-center px-2 mb-4 border-success focus-within:border-success"
|
||||||
:title="item.name"
|
>
|
||||||
:value="item.name"
|
<font-awesome-icon
|
||||||
:key="item.name"
|
:icon="['fas', 'search']"
|
||||||
:name="column.key"
|
class="w-6 h-6 mr-2 text-success"
|
||||||
:checked="
|
/>
|
||||||
selectedFilterItem[column.key].includes(item.name)
|
<input
|
||||||
"
|
type="text"
|
||||||
className="justify-start"
|
: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].filter(
|
||||||
|
(item) =>
|
||||||
|
item.name && item.name.includes(searchQuery)
|
||||||
|
)"
|
||||||
|
:title="item.name"
|
||||||
|
:value="item.name"
|
||||||
|
:key="item.name"
|
||||||
|
:name="column.key"
|
||||||
|
:checked="
|
||||||
|
selectedFilterItem[column.key].includes(item.name)
|
||||||
|
"
|
||||||
|
className="justify-start"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<div class="card-actions mt-4 justify-end">
|
<div class="card-actions mt-4 justify-end">
|
||||||
<input
|
<input
|
||||||
type="reset"
|
type="reset"
|
||||||
@ -314,7 +334,7 @@ watch(
|
|||||||
</form>
|
</form>
|
||||||
<slot name="afterTable"></slot>
|
<slot name="afterTable"></slot>
|
||||||
<Pagination
|
<Pagination
|
||||||
v-if="pagination"
|
v-if="pagination"
|
||||||
:pagination="pagination"
|
:pagination="pagination"
|
||||||
:dataSource="dataSourceStorage"
|
:dataSource="dataSourceStorage"
|
||||||
:sort="sortRule"
|
:sort="sortRule"
|
||||||
@ -327,7 +347,7 @@ watch(
|
|||||||
<style lang="css" scoped>
|
<style lang="css" scoped>
|
||||||
/**資料框**/
|
/**資料框**/
|
||||||
.content-box {
|
.content-box {
|
||||||
@apply border border-info p-1 relative mb-4 bg-transparent ;
|
@apply border border-info p-1 relative mb-4 bg-transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
.content-box .table {
|
.content-box .table {
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
import { onMounted, ref, watch, computed } from "vue";
|
import { onMounted, ref, watch, computed } from "vue";
|
||||||
import { AUTHPAGES } from "@/constant";
|
import { AUTHPAGES } from "@/constant";
|
||||||
import { getAuth, getAllSysSidebar } from "@/apis/building";
|
import { getAuth, getAllSysSidebar } from "@/apis/building";
|
||||||
|
import { getEnergySideBar } from "@/apis/energy";
|
||||||
import useBuildingStore from "@/stores/useBuildingStore";
|
import useBuildingStore from "@/stores/useBuildingStore";
|
||||||
import useUserInfoStore from "@/stores/useUserInfoStore";
|
import useUserInfoStore from "@/stores/useUserInfoStore";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
@ -13,6 +14,8 @@ const store = useUserInfoStore();
|
|||||||
const buildingStore = useBuildingStore();
|
const buildingStore = useBuildingStore();
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const openKeys = ref([]); // 追蹤當前打開的子菜單
|
const openKeys = ref([]); // 追蹤當前打開的子菜單
|
||||||
|
const menu_array = ref([]);
|
||||||
|
const currentAuthCode = ref("");
|
||||||
|
|
||||||
const iniFroList = async () => {
|
const iniFroList = async () => {
|
||||||
const res = await getAuth(locale.value);
|
const res = await getAuth(locale.value);
|
||||||
@ -37,9 +40,19 @@ const open = ref(false);
|
|||||||
const getSubMonitorPage = async (building) => {
|
const getSubMonitorPage = async (building) => {
|
||||||
const res = await getAllSysSidebar();
|
const res = await getAllSysSidebar();
|
||||||
buildingStore.mainSubSys = res.data.history_Main_Systems;
|
buildingStore.mainSubSys = res.data.history_Main_Systems;
|
||||||
|
menu_array.value = res.data.history_Main_Systems;
|
||||||
};
|
};
|
||||||
const showDrawer = () => {
|
const getSubEnergyPage = async () => {
|
||||||
getSubMonitorPage();
|
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;
|
open.value = true;
|
||||||
};
|
};
|
||||||
const onClose = () => {
|
const onClose = () => {
|
||||||
@ -89,14 +102,19 @@ onMounted(() => {
|
|||||||
<li
|
<li
|
||||||
v-for="page in authPages"
|
v-for="page in authPages"
|
||||||
class="flex flex-col items-center justify-center"
|
class="flex flex-col items-center justify-center"
|
||||||
|
:key="page.authCode"
|
||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
v-if="page.authCode === 'PF1'"
|
v-if="page.authCode === 'PF1' || page.authCode === 'PF2'"
|
||||||
@click="showDrawer"
|
@click="showDrawer(page.authCode)"
|
||||||
:class="
|
:class="
|
||||||
twMerge(
|
twMerge(
|
||||||
'flex lg:flex-col justify-center items-center btn-group text-white cursor-pointer',
|
'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'
|
? 'router-link-active router-link-exact-active'
|
||||||
: ''
|
: ''
|
||||||
)
|
)
|
||||||
@ -142,22 +160,26 @@ onMounted(() => {
|
|||||||
@openChange="handleOpenChange"
|
@openChange="handleOpenChange"
|
||||||
>
|
>
|
||||||
<a-sub-menu
|
<a-sub-menu
|
||||||
v-for="main in buildingStore.mainSubSys"
|
v-for="main in menu_array"
|
||||||
:key="main.main_system_tag"
|
:key="main.main_system_tag"
|
||||||
:title="main.full_name"
|
:title="main.full_name"
|
||||||
|
v-if="menu_array.length > 0 && open"
|
||||||
>
|
>
|
||||||
<a-menu-item
|
<a-menu-item
|
||||||
v-for="sub in main.history_Sub_systems"
|
v-for="sub in currentAuthCode === 'PF1'
|
||||||
:key="sub.sub_system_tag"
|
? main.history_Sub_systems
|
||||||
|
: main.sub"
|
||||||
|
:key="sub.sub_system_tag+`_`+sub.type"
|
||||||
@click="() => onClose()"
|
@click="() => onClose()"
|
||||||
>
|
>
|
||||||
<router-link
|
<router-link
|
||||||
:to="{
|
:to="{
|
||||||
name: 'sub_system',
|
name:
|
||||||
|
currentAuthCode === 'PF2' ? 'energyManagement' : 'sub_system',
|
||||||
params: {
|
params: {
|
||||||
main_system_id: main.main_system_tag,
|
main_system_id: main.main_system_tag,
|
||||||
sub_system_id: sub.sub_system_tag,
|
sub_system_id: sub.sub_system_tag,
|
||||||
floor_id: 'main',
|
...(currentAuthCode === 'PF2' ? { type: sub.type } : { floor_id: 'main' }),
|
||||||
},
|
},
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
@ -178,8 +200,8 @@ onMounted(() => {
|
|||||||
@apply bg-transparent lg:w-[95px] w-full mb-5 lg:mb-0;
|
@apply bg-transparent lg:w-[95px] w-full mb-5 lg:mb-0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.menu-box .btn-group span{
|
.menu-box .btn-group span {
|
||||||
@apply text-2xl ms-2 lg:ms-0 lg:text-sm;
|
@apply text-2xl ms-2 lg:ms-0 lg:text-sm;
|
||||||
}
|
}
|
||||||
|
|
||||||
.menu-box > li {
|
.menu-box > li {
|
||||||
|
@ -82,7 +82,23 @@
|
|||||||
"total_elec_cost": "总电费",
|
"total_elec_cost": "总电费",
|
||||||
"carbon_equivalent": "碳排当量",
|
"carbon_equivalent": "碳排当量",
|
||||||
"edit_carbon_emission": "编辑碳排放系数",
|
"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": {
|
"alarm": {
|
||||||
"title": "显示警告",
|
"title": "显示警告",
|
||||||
@ -152,7 +168,16 @@
|
|||||||
"choose": "选择",
|
"choose": "选择",
|
||||||
"day_time": "星期/时间",
|
"day_time": "星期/时间",
|
||||||
"click_time_period": "请用滑鼠点击时间段",
|
"click_time_period": "请用滑鼠点击时间段",
|
||||||
"clear": "清空"
|
"clear": "清空",
|
||||||
|
"sunday": "星期日",
|
||||||
|
"monday": "星期一",
|
||||||
|
"tuesday": "星期二",
|
||||||
|
"wednesday": "星期三",
|
||||||
|
"thursday": "星期四",
|
||||||
|
"friday": "星期五",
|
||||||
|
"saturday": "星期六",
|
||||||
|
"schedule_name": "时段名称",
|
||||||
|
"schedule_content": "时段内容"
|
||||||
},
|
},
|
||||||
"operation": {
|
"operation": {
|
||||||
"title": "运维管理",
|
"title": "运维管理",
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
"upload": {
|
"upload": {
|
||||||
"title": "選擇一個文件或拖放到這裡",
|
"title": "選擇一個文件或拖放到這裡",
|
||||||
"description": "檔案不超過 10MB",
|
"description": "檔案不超過 10MB",
|
||||||
"formats": "檔案格式"
|
"formats": "檔案格式"
|
||||||
},
|
},
|
||||||
"dashboard": {
|
"dashboard": {
|
||||||
"yesterday_today": "昨天/今天",
|
"yesterday_today": "昨天/今天",
|
||||||
@ -82,7 +82,23 @@
|
|||||||
"total_elec_cost": "總電費",
|
"total_elec_cost": "總電費",
|
||||||
"carbon_equivalent": "碳排當量",
|
"carbon_equivalent": "碳排當量",
|
||||||
"edit_carbon_emission": "編輯碳排放係數",
|
"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": {
|
"alarm": {
|
||||||
"title": "顯示警告",
|
"title": "顯示警告",
|
||||||
@ -150,9 +166,18 @@
|
|||||||
"notify_items": "通知項目",
|
"notify_items": "通知項目",
|
||||||
"notify_list": "通知名單",
|
"notify_list": "通知名單",
|
||||||
"choose": "選擇",
|
"choose": "選擇",
|
||||||
"day_time":"星期/時間",
|
"day_time": "星期/時間",
|
||||||
"click_time_period":"請用滑鼠點擊時間段",
|
"click_time_period": "請用滑鼠點擊時間段",
|
||||||
"clear":"清空"
|
"clear": "清空",
|
||||||
|
"sunday": "星期日",
|
||||||
|
"monday": "星期一",
|
||||||
|
"tuesday": "星期二",
|
||||||
|
"wednesday": "星期三",
|
||||||
|
"thursday": "星期四",
|
||||||
|
"friday": "星期五",
|
||||||
|
"saturday": "星期六",
|
||||||
|
"schedule_name": "時段名稱",
|
||||||
|
"schedule_content": "時段內容"
|
||||||
},
|
},
|
||||||
"operation": {
|
"operation": {
|
||||||
"title": "運維管理",
|
"title": "運維管理",
|
||||||
@ -298,10 +323,10 @@
|
|||||||
"download": "下載",
|
"download": "下載",
|
||||||
"confirm": "確認",
|
"confirm": "確認",
|
||||||
"restore": "復原",
|
"restore": "復原",
|
||||||
"stop_edit":"停止修改",
|
"stop_edit": "停止修改",
|
||||||
"start_edit":"開始修改"
|
"start_edit": "開始修改"
|
||||||
},
|
},
|
||||||
"msg":{
|
"msg": {
|
||||||
"sure_to_delete": "是否確認刪除該項目?",
|
"sure_to_delete": "是否確認刪除該項目?",
|
||||||
"sure_to_delete_permanent": "是否確認永久刪除該項目?",
|
"sure_to_delete_permanent": "是否確認永久刪除該項目?",
|
||||||
"delete_success": "刪除成功",
|
"delete_success": "刪除成功",
|
||||||
|
@ -82,7 +82,23 @@
|
|||||||
"total_elec_cost": "Total Elec. Cost",
|
"total_elec_cost": "Total Elec. Cost",
|
||||||
"carbon_equivalent": "Carbon Equivalent",
|
"carbon_equivalent": "Carbon Equivalent",
|
||||||
"edit_carbon_emission": "Edit carbon emission coefficient",
|
"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": {
|
"alarm": {
|
||||||
"title": "Warning",
|
"title": "Warning",
|
||||||
@ -152,7 +168,16 @@
|
|||||||
"choose": "Choose",
|
"choose": "Choose",
|
||||||
"day_time": "Week/Time",
|
"day_time": "Week/Time",
|
||||||
"click_time_period": "Please click the time period with your mouse",
|
"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": {
|
"operation": {
|
||||||
"title": "Operation And Maintenance Management",
|
"title": "Operation And Maintenance Management",
|
||||||
|
@ -80,7 +80,7 @@ const router = createRouter({
|
|||||||
component: ProductSetting,
|
component: ProductSetting,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/energyManagement",
|
path: "/energyManagement/:main_system_id/:sub_system_id/:type",
|
||||||
name: "energyManagement",
|
name: "energyManagement",
|
||||||
component: EnergyManagement,
|
component: EnergyManagement,
|
||||||
},
|
},
|
||||||
@ -99,10 +99,13 @@ router.beforeEach(async (to, from, next) => {
|
|||||||
const authRequired = !publicPages.includes(to.path);
|
const authRequired = !publicPages.includes(to.path);
|
||||||
const auth = useUserInfoStore();
|
const auth = useUserInfoStore();
|
||||||
const token = useGetCookie("JWT-Authorization");
|
const token = useGetCookie("JWT-Authorization");
|
||||||
|
const user_name = useGetCookie("user_name");
|
||||||
|
|
||||||
if (to.path === "/logout") {
|
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.token = "";
|
||||||
|
auth.user.user_name = "";
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
next({ path: "/login" });
|
next({ path: "/login" });
|
||||||
}
|
}
|
||||||
@ -111,10 +114,13 @@ router.beforeEach(async (to, from, next) => {
|
|||||||
auth.user.token = "";
|
auth.user.token = "";
|
||||||
next({ path: "/login" });
|
next({ path: "/login" });
|
||||||
} else if (!authRequired) {
|
} 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.token = "";
|
||||||
|
auth.user.user_name = "";
|
||||||
} else {
|
} else {
|
||||||
auth.user.token = token;
|
auth.user.token = token;
|
||||||
|
auth.user.user_name = user_name;
|
||||||
}
|
}
|
||||||
next();
|
next();
|
||||||
});
|
});
|
||||||
|
@ -5,6 +5,7 @@ const useUserInfoStore = defineStore("userInfo", () => {
|
|||||||
const user = ref({
|
const user = ref({
|
||||||
token: "",
|
token: "",
|
||||||
expires: 0,
|
expires: 0,
|
||||||
|
user_name:"",
|
||||||
});
|
});
|
||||||
|
|
||||||
const auth_page = ref([]);
|
const auth_page = ref([]);
|
||||||
|
@ -1,7 +1,43 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
|
import { ref, provide, onMounted, watch } 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";
|
||||||
|
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>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
@ -66,7 +66,6 @@ onMounted(() => {
|
|||||||
|
|
||||||
watch(selectedBtn, (newValue) => {
|
watch(selectedBtn, (newValue) => {
|
||||||
changeParams({
|
changeParams({
|
||||||
...searchParams.value,
|
|
||||||
mainSys_id: newValue.key,
|
mainSys_id: newValue.key,
|
||||||
subSys_id: null,
|
subSys_id: null,
|
||||||
});
|
});
|
||||||
|
@ -39,7 +39,6 @@ const getAssetData = async () => {
|
|||||||
}
|
}
|
||||||
totalCoordinates.value[floorGuid].push(d.device_coordinate);
|
totalCoordinates.value[floorGuid].push(d.device_coordinate);
|
||||||
});
|
});
|
||||||
|
|
||||||
tableData.value = res.data.map((d) => ({
|
tableData.value = res.data.map((d) => ({
|
||||||
...d,
|
...d,
|
||||||
key: d.id,
|
key: d.id,
|
||||||
@ -122,6 +121,8 @@ watch(
|
|||||||
(newValue) => {
|
(newValue) => {
|
||||||
if (newValue.value?.subSys_id) {
|
if (newValue.value?.subSys_id) {
|
||||||
getAssetData();
|
getAssetData();
|
||||||
|
}else{
|
||||||
|
tableData.value=[];
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -102,7 +102,7 @@ const closeModal = () => {
|
|||||||
</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="openModal" :disabled="!searchParams.subSys_id">
|
||||||
<font-awesome-icon :icon="['fas', 'plus']" />{{ $t("button.add") }}
|
<font-awesome-icon :icon="['fas', 'plus']" />{{ $t("button.add") }}
|
||||||
</button>
|
</button>
|
||||||
<Modal
|
<Modal
|
||||||
|
@ -6,19 +6,17 @@ import AssetTableModalLeftInfoIoT from "./AssetTableModalLeftInfoIoT.vue";
|
|||||||
import AssetTableModalLeftInfoDept from "./AssetTableModalLeftInfoDept.vue";
|
import AssetTableModalLeftInfoDept from "./AssetTableModalLeftInfoDept.vue";
|
||||||
import AssetTableModalLeftInfoGraph from "./AssetTableModalLeftInfoGraph.vue";
|
import AssetTableModalLeftInfoGraph from "./AssetTableModalLeftInfoGraph.vue";
|
||||||
import AssetTableModalLeftInfoMQTT from "./AssetTableModalLeftInfoMQTT.vue";
|
import AssetTableModalLeftInfoMQTT from "./AssetTableModalLeftInfoMQTT.vue";
|
||||||
import { getOperationCompanyList } from "@/apis/operation";
|
import useUserInfoStore from "@/stores/useUserInfoStore";
|
||||||
import { getIOTSchema } from "@/apis/asset";
|
|
||||||
import useSearchParam from "@/hooks/useSearchParam";
|
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const FILE_BASEURL = import.meta.env.VITE_FILE_API_BASEURL;
|
const FILE_BASEURL = import.meta.env.VITE_FILE_API_BASEURL;
|
||||||
|
|
||||||
const { searchParams, changeParams } = useSearchParam();
|
|
||||||
const { updateLeftFields, formErrorMsg, formState } = inject(
|
const { updateLeftFields, formErrorMsg, formState } = inject(
|
||||||
"asset_table_modal_form"
|
"asset_table_modal_form"
|
||||||
);
|
);
|
||||||
|
const { companyOptions, iotSchemaOptions } = inject("asset_modal_options");
|
||||||
|
const store = useUserInfoStore();
|
||||||
let schema = {
|
let schema = {
|
||||||
full_name: yup.string().nullable(true),
|
full_name: yup.string().nullable(true),
|
||||||
operate_text: yup.string().nullable(true),
|
operate_text: yup.string().nullable(true),
|
||||||
@ -94,20 +92,15 @@ watch(formState, (newValue) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const companyOptions = ref([]);
|
watch(
|
||||||
const iotSchemaOptions = ref([]);
|
() => iotSchemaOptions.value,
|
||||||
const getCompany = async () => {
|
(newVal) => {
|
||||||
const res = await getOperationCompanyList();
|
if (newVal && newVal.length > 0) {
|
||||||
companyOptions.value = res.data.map((d) => ({ ...d, key: d.id }));
|
formState.value.response_schema_id = newVal[0].id;
|
||||||
};
|
}
|
||||||
const getIOTSchemaOptions = async () => {
|
},
|
||||||
const res = await getIOTSchema();
|
{ immediate: true }
|
||||||
iotSchemaOptions.value = res.data.map((d) => ({ ...d, key: d.id }));
|
);
|
||||||
};
|
|
||||||
onMounted(() => {
|
|
||||||
getCompany();
|
|
||||||
getIOTSchemaOptions();
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@ -121,7 +114,12 @@ onMounted(() => {
|
|||||||
></Input
|
></Input
|
||||||
>
|
>
|
||||||
<AssetTableModalLeftInfoDept />
|
<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
|
<template #topLeft
|
||||||
>Tag_Name ({{ $t("assetManagement.fill_text") }})</template
|
>Tag_Name ({{ $t("assetManagement.fill_text") }})</template
|
||||||
>
|
>
|
||||||
|
@ -44,6 +44,7 @@ const modalColumns = computed(() => [
|
|||||||
{
|
{
|
||||||
title: "tag",
|
title: "tag",
|
||||||
key: "device_name",
|
key: "device_name",
|
||||||
|
filter: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t("assetManagement.point"),
|
title: t("assetManagement.point"),
|
||||||
|
@ -35,13 +35,14 @@ onBeforeMount(() => {
|
|||||||
const asset_floor_chart = ref(null);
|
const asset_floor_chart = ref(null);
|
||||||
const currentFloor = ref(null);
|
const currentFloor = ref(null);
|
||||||
const selectedOption = ref("add");
|
const selectedOption = ref("add");
|
||||||
|
const parsedCoordinates = ref(null);
|
||||||
|
|
||||||
const defaultOption = (map, data = []) => {
|
const defaultOption = (map, data = []) => {
|
||||||
// 生成坐標數據,根據坐標值的不同設置不同顏色
|
// 生成坐標數據,根據坐標值的不同設置不同顏色
|
||||||
const formattedData = data.map((coordinate) => {
|
const formattedData = data.map((coordinate) => {
|
||||||
const coordString = JSON.stringify(coordinate);
|
const coordString = JSON.stringify(coordinate);
|
||||||
return {
|
return {
|
||||||
name: coordString,
|
name: coordString,
|
||||||
value: coordinate,
|
value: coordinate,
|
||||||
itemStyle: {
|
itemStyle: {
|
||||||
color: coordString === formState.value.device_coordinate ? "#0000FF" : "#b02a02",
|
color: coordString === formState.value.device_coordinate ? "#0000FF" : "#b02a02",
|
||||||
@ -73,17 +74,21 @@ const defaultOption = (map, data = []) => {
|
|||||||
|
|
||||||
watch(currentFloor, (newValue) => {
|
watch(currentFloor, (newValue) => {
|
||||||
if (newValue?.floor_map_name) {
|
if (newValue?.floor_map_name) {
|
||||||
const coordinates = totalCoordinates.value[newValue.floor_guid] || [];
|
const coordinates =
|
||||||
if (coordinates.length === 0 || coordinates.every(coord => coord === "")) return;
|
(totalCoordinates.value?.[newValue.floor_guid]?.filter(
|
||||||
const parsedCoordinates = coordinates.map((coord) => {
|
(coord) => coord !== ""
|
||||||
return JSON.parse(coord);
|
) || []);
|
||||||
});
|
|
||||||
|
parsedCoordinates.value = coordinates.length > 0
|
||||||
|
? coordinates.map((coord) => JSON.parse(coord))
|
||||||
|
: [];
|
||||||
|
|
||||||
asset_floor_chart.value.updateSvg(
|
asset_floor_chart.value.updateSvg(
|
||||||
{
|
{
|
||||||
full_name: newValue?.floor_map_name,
|
full_name: newValue.floor_map_name,
|
||||||
path: `${FILE_BASEURL}/${newValue?.floor_map_url}.svg`,
|
path: `${FILE_BASEURL}/${newValue.floor_map_url}.svg`,
|
||||||
},
|
},
|
||||||
defaultOption(newValue?.floor_map_name, parsedCoordinates)
|
defaultOption(newValue.floor_map_name, parsedCoordinates.value)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -183,16 +188,16 @@ const onOk = async () => {
|
|||||||
};
|
};
|
||||||
const onDelete = async () => {
|
const onDelete = async () => {
|
||||||
openToast("warning", t("msg.sure_to_delete"), "#asset_add_table_item", async () => {
|
openToast("warning", t("msg.sure_to_delete"), "#asset_add_table_item", async () => {
|
||||||
await cancelToastOpen();
|
await cancelToastOpen();
|
||||||
const res = await deleteAssetFloor({
|
const res = await deleteAssetFloor({
|
||||||
floor_guid: formState.value.floor_guid,
|
floor_guid: formState.value.floor_guid,
|
||||||
});
|
});
|
||||||
if (res.isSuccess) {
|
if (res.isSuccess) {
|
||||||
getFloors();
|
getFloors();
|
||||||
openToast("success", t("msg.delete_success"), "#asset_add_table_item");
|
openToast("success", t("msg.delete_success"), "#asset_add_table_item");
|
||||||
} else {
|
} else {
|
||||||
openToast("error", res.msg, "#asset_add_table_item");
|
openToast("error", res.msg, "#asset_add_table_item");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -239,18 +244,18 @@ const onCancel = () => {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<Input
|
<Input
|
||||||
:value="formState"
|
:value="formState"
|
||||||
width="270"
|
width="270"
|
||||||
name="device_coordinate"
|
name="device_coordinate"
|
||||||
:disabled="true"
|
:disabled="true"
|
||||||
>
|
>
|
||||||
<template #topLeft>{{ $t("assetManagement.device_coordinate") }}</template>
|
<template #topLeft>{{ $t("assetManagement.device_coordinate") }}</template>
|
||||||
<template #bottomLeft
|
<template #bottomLeft
|
||||||
><span class="text-error text-base">
|
><span class="text-error text-base">
|
||||||
{{ formErrorMsg.device_coordinate }}
|
{{ formErrorMsg.device_coordinate }}
|
||||||
</span></template
|
</span></template
|
||||||
></Input
|
></Input
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
<EffectScatter
|
<EffectScatter
|
||||||
|
@ -59,7 +59,12 @@ const searchData = ref({
|
|||||||
const getDataSource = async () => {
|
const getDataSource = async () => {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
const res = await getAccountUserList(searchData.value);
|
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;
|
loading.value = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -127,8 +132,8 @@ const removeAccount = async (id) => {
|
|||||||
await cancelToastOpen();
|
await cancelToastOpen();
|
||||||
const res = await delAccount(id);
|
const res = await delAccount(id);
|
||||||
if (res.isSuccess) {
|
if (res.isSuccess) {
|
||||||
getDataSource();
|
getDataSource();
|
||||||
openToast("success", t("msg.delete_success"));
|
openToast("success", t("msg.delete_success"));
|
||||||
} else {
|
} else {
|
||||||
openToast("error", res.msg);
|
openToast("error", res.msg);
|
||||||
}
|
}
|
||||||
|
@ -7,11 +7,10 @@ import {
|
|||||||
} from "@/apis/alert";
|
} from "@/apis/alert";
|
||||||
import useSearchParam from "@/hooks/useSearchParam";
|
import useSearchParam from "@/hooks/useSearchParam";
|
||||||
import AlertOutliersTableAddModal from "./AlertOutliersTableAddModal.vue";
|
import AlertOutliersTableAddModal from "./AlertOutliersTableAddModal.vue";
|
||||||
import AlertOutliersTimeModal from "./AlertOutliersTimeModal.vue";
|
|
||||||
import dayjs from "dayjs";
|
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const { noticeList } = inject("notify_table");
|
const { noticeList, timesList } = inject("notify_table");
|
||||||
const { searchParams, changeParams } = useSearchParam();
|
const { searchParams, changeParams } = useSearchParam();
|
||||||
|
|
||||||
const tableData = ref([]);
|
const tableData = ref([]);
|
||||||
@ -111,6 +110,9 @@ const getOutliersData = async () => {
|
|||||||
matchedPoints?.factor && item.factor
|
matchedPoints?.factor && item.factor
|
||||||
? matchedPoints?.factor?.find((f) => f.id === item.factor)
|
? matchedPoints?.factor?.find((f) => f.id === item.factor)
|
||||||
: null;
|
: null;
|
||||||
|
const matchedTime = timesList.value.find(
|
||||||
|
(t) => t.id === item.schedule_id
|
||||||
|
);
|
||||||
const warningMethodKeys = item.notices
|
const warningMethodKeys = item.notices
|
||||||
?.map((noticeValue) => {
|
?.map((noticeValue) => {
|
||||||
const matchedNotice = noticeList.value.find(
|
const matchedNotice = noticeList.value.find(
|
||||||
@ -128,6 +130,7 @@ const getOutliersData = async () => {
|
|||||||
is_bool: matchedPoints ? matchedPoints.is_bool : 1,
|
is_bool: matchedPoints ? matchedPoints.is_bool : 1,
|
||||||
factor_name: matchedFactor ? matchedFactor.full_name : "",
|
factor_name: matchedFactor ? matchedFactor.full_name : "",
|
||||||
warning_method: warningMethodKeys,
|
warning_method: warningMethodKeys,
|
||||||
|
warning_time: matchedTime ? matchedTime.schedule_name : "",
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -163,14 +166,6 @@ const onCancel = () => {
|
|||||||
editRecord.value = null;
|
editRecord.value = null;
|
||||||
outliers_add_table_item.close();
|
outliers_add_table_item.close();
|
||||||
};
|
};
|
||||||
|
|
||||||
const openTimeModal = () => {
|
|
||||||
outliers_time_item.showModal();
|
|
||||||
};
|
|
||||||
|
|
||||||
const onTimeCancel = () => {
|
|
||||||
outliers_time_item.close();
|
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@ -183,10 +178,6 @@ const onTimeCancel = () => {
|
|||||||
:getData="getOutliersData"
|
:getData="getOutliersData"
|
||||||
:OptionsData="dev_data"
|
:OptionsData="dev_data"
|
||||||
/>
|
/>
|
||||||
<AlertOutliersTimeModal
|
|
||||||
:openModal="openTimeModal"
|
|
||||||
:onCancel="onTimeCancel"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<Table :columns="columns" :dataSource="tableData" class="mt-3">
|
<Table :columns="columns" :dataSource="tableData" class="mt-3">
|
||||||
<template #bodyCell="{ record, column, index }">
|
<template #bodyCell="{ record, column, index }">
|
||||||
@ -205,13 +196,7 @@ const onTimeCancel = () => {
|
|||||||
<span class="whitespace-pre">{{ record.warning_method }}</span>
|
<span class="whitespace-pre">{{ record.warning_method }}</span>
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="column.key === 'warning_time'">
|
<template v-else-if="column.key === 'warning_time'">
|
||||||
<button
|
<span class="whitespace-pre">{{ record.warning_time }}</span>
|
||||||
class="btn btn-sm btn-success text-white pb-3"
|
|
||||||
:disabled="!record.enable"
|
|
||||||
@click.stop.prevent="() => openTimeModal(record)"
|
|
||||||
>
|
|
||||||
{{ $t("alert.time_setting") }}
|
|
||||||
</button>
|
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
{{ record[column.key] }}
|
{{ record[column.key] }}
|
||||||
|
@ -8,7 +8,7 @@ import * as yup from "yup";
|
|||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const { openToast } = inject("app_toast");
|
const { openToast } = inject("app_toast");
|
||||||
const { noticeList } = inject("notify_table");
|
const { timesList, noticeList } = inject("notify_table");
|
||||||
const { searchParams, changeParams } = useSearchParam();
|
const { searchParams, changeParams } = useSearchParam();
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
@ -190,6 +190,15 @@ const closeModal = () => {
|
|||||||
>
|
>
|
||||||
<template #topLeft>{{ $t("alert.status") }}</template>
|
<template #topLeft>{{ $t("alert.status") }}</template>
|
||||||
</RadioGroup>
|
</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
|
<Select
|
||||||
:value="formState"
|
:value="formState"
|
||||||
class="my-2"
|
class="my-2"
|
||||||
|
@ -1,14 +1,27 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import AlertSubList from "./AlertSubList.vue";
|
import AlertSubList from "./AlertSubList.vue";
|
||||||
import AlertOutliersTable from "./AlertOutliersTable.vue";
|
import AlertOutliersTable from "./AlertOutliersTable.vue";
|
||||||
|
import AlertTimeTable from "./AlertTimeTable.vue";
|
||||||
import AlertNotifyTable from "./AlertNotifyTable.vue";
|
import AlertNotifyTable from "./AlertNotifyTable.vue";
|
||||||
import { ref, provide, onMounted, watch } from "vue";
|
import { ref, provide, onMounted, watch } from "vue";
|
||||||
import { getNoticeList } from "@/apis/alert";
|
import { getAlarmScheduleList, getNoticeList } from "@/apis/alert";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
const { locale } = useI18n();
|
const { locale } = useI18n();
|
||||||
|
const timesList = ref([]);
|
||||||
const noticeList = 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 NoticeListData = async () => {
|
||||||
const res = await getNoticeList(locale.value);
|
const res = await getNoticeList(locale.value);
|
||||||
noticeList.value = res.data;
|
noticeList.value = res.data;
|
||||||
@ -19,16 +32,18 @@ watch(locale, () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
timesListData();
|
||||||
NoticeListData();
|
NoticeListData();
|
||||||
});
|
});
|
||||||
|
|
||||||
provide("notify_table", { noticeList });
|
provide("notify_table", { timesList, noticeList, timesListData });
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<AlertSubList />
|
<AlertSubList />
|
||||||
<AlertOutliersTable />
|
<AlertOutliersTable />
|
||||||
|
<AlertTimeTable />
|
||||||
<AlertNotifyTable />
|
<AlertNotifyTable />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</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>
|
<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";
|
import { useI18n } from "vue-i18n";
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const { openToast } = inject("app_toast");
|
||||||
|
const { timesListData } = inject("notify_table");
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
openModal: Function,
|
openModal: Function,
|
||||||
onCancel: Function,
|
onCancel: Function,
|
||||||
defaultTimeSection: {
|
editRecord: Object,
|
||||||
type: Object,
|
weekDate: Object,
|
||||||
default: () => [],
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
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 = () => {
|
const closeModal = () => {
|
||||||
props.onCancel();
|
props.onCancel();
|
||||||
|
formState.value = {
|
||||||
|
id: 0,
|
||||||
|
schedule_name: "",
|
||||||
|
};
|
||||||
clear();
|
clear();
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -43,15 +65,6 @@ const tableHeader = ref([
|
|||||||
"22",
|
"22",
|
||||||
"23",
|
"23",
|
||||||
]);
|
]);
|
||||||
const weekDate = ref({
|
|
||||||
1: "Mon",
|
|
||||||
2: "Tue",
|
|
||||||
3: "Wed",
|
|
||||||
4: "Thu",
|
|
||||||
5: "Fri",
|
|
||||||
6: "Sat",
|
|
||||||
7: "Sun",
|
|
||||||
});
|
|
||||||
|
|
||||||
const rowUnit = ref([]); //每一單位格子
|
const rowUnit = ref([]); //每一單位格子
|
||||||
const timeContent = ref([]); //選中的時間段原始數據
|
const timeContent = ref([]); //選中的時間段原始數據
|
||||||
@ -79,8 +92,8 @@ const handleMouseMove = (i, day) => {
|
|||||||
|
|
||||||
const initSchedule = () => {
|
const initSchedule = () => {
|
||||||
for (let i = 0; i < 7; i++) {
|
for (let i = 0; i < 7; i++) {
|
||||||
const dayIndex = i + 1;
|
const dayIndex = i;
|
||||||
const defaultTimes = props.defaultTimeSection[dayIndex] || [];
|
const defaultTimes = schedule_array.value[dayIndex] || [];
|
||||||
let arr = [];
|
let arr = [];
|
||||||
for (let j = 0; j < 96; j++) {
|
for (let j = 0; j < 96; j++) {
|
||||||
const timeSlot = j / 4;
|
const timeSlot = j / 4;
|
||||||
@ -206,124 +219,146 @@ const filterTime = (start, end) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const clear = () => {
|
const clear = () => {
|
||||||
rowUnit.value.forEach((item) => {
|
schedule_array.value = [];
|
||||||
item.forEach((item1) => {
|
rowUnit.value = [];
|
||||||
item1.class = null;
|
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) => {
|
if (res.isSuccess) {
|
||||||
item.arr = [];
|
timesListData();
|
||||||
});
|
closeModal();
|
||||||
|
} else {
|
||||||
timeSection.value.forEach((item) => {
|
openToast("error", res.msg, "#outliers_time_item");
|
||||||
item.length = 0;
|
|
||||||
});
|
|
||||||
|
|
||||||
timeStr.value.length = 0;
|
|
||||||
for (let i = 0; i < 7; i++) {
|
|
||||||
timeStr.value.push("");
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const onOk = async () => {};
|
watch(
|
||||||
|
() => props.editRecord,
|
||||||
onMounted(() => {
|
(newValue) => {
|
||||||
initSchedule();
|
if (newValue) {
|
||||||
});
|
formState.value = {
|
||||||
|
...newValue,
|
||||||
|
};
|
||||||
|
schedule_array.value = newValue?.schedule_json
|
||||||
|
? JSON.parse(newValue?.schedule_json)
|
||||||
|
: [];
|
||||||
|
initSchedule();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<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
|
<Modal
|
||||||
id="outliers_time_item"
|
id="outliers_time_item"
|
||||||
:open="open"
|
:open="open"
|
||||||
:onCancel="closeModal"
|
:onCancel="closeModal"
|
||||||
width="1400"
|
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>
|
<template #modalContent>
|
||||||
<div class="weektime mt-5">
|
<form ref="form">
|
||||||
<div class="calendar">
|
<Input :value="formState" class="mt-2" name="schedule_name">
|
||||||
<table class="calendar-table w-full">
|
<template #topLeft>{{ $t("alert.schedule_name") }}</template>
|
||||||
<thead class="calendar-head">
|
<template #bottomLeft>
|
||||||
<tr>
|
<span class="text-error text-base">
|
||||||
<th rowspan="6" class="w-20 py-4">
|
{{ formErrorMsg.schedule_name }}
|
||||||
{{ $t("alert.day_time") }}
|
</span>
|
||||||
</th>
|
</template>
|
||||||
<th colspan="48">00:00 - 12:00</th>
|
</Input>
|
||||||
<th colspan="48">12:00 - 24:00</th>
|
<p class="text-light text-lg my-2">{{ $t("alert.schedule_content") }}</p>
|
||||||
</tr>
|
<div class="weektime mt-5">
|
||||||
<tr>
|
<div class="calendar">
|
||||||
<td
|
<table class="calendar-table w-full">
|
||||||
colspan="4"
|
<thead class="calendar-head">
|
||||||
v-for="(item, index) in tableHeader"
|
<tr>
|
||||||
:key="index"
|
<th rowspan="6" class="w-20 py-4">
|
||||||
>
|
{{ $t("alert.day_time") }}
|
||||||
{{ item }}
|
</th>
|
||||||
</td>
|
<th colspan="48">00:00 - 12:00</th>
|
||||||
</tr>
|
<th colspan="48">12:00 - 24:00</th>
|
||||||
</thead>
|
</tr>
|
||||||
<tbody id="tableBody">
|
<tr>
|
||||||
<tr v-for="(day, index) in weekDate" :key="index">
|
<td
|
||||||
<td>{{ day }}</td>
|
colspan="4"
|
||||||
<td
|
v-for="(item, index) in tableHeader"
|
||||||
v-for="(item, i) in rowUnit[index - 1]"
|
|
||||||
:key="i"
|
|
||||||
@mousedown.prevent="handleMouseDown(i, index - 1)"
|
|
||||||
@mouseup.prevent="handleMouseUp(i, index - 1)"
|
|
||||||
@mousemove.prevent="handleMouseMove(i, index - 1)"
|
|
||||||
class="calendar-atom-time"
|
|
||||||
:class="{
|
|
||||||
'ui-selected': item.class === 'ui-selected',
|
|
||||||
'hover-highlight':
|
|
||||||
downEvent &&
|
|
||||||
index - 1 >= hoverRange.startDay &&
|
|
||||||
index - 1 <= hoverRange.endDay &&
|
|
||||||
i >= hoverRange.startSlot &&
|
|
||||||
i <= hoverRange.endSlot,
|
|
||||||
}"
|
|
||||||
></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td colspan="97" class="py-2">
|
|
||||||
<span class="text-base">{{
|
|
||||||
$t("alert.click_time_period")
|
|
||||||
}}</span>
|
|
||||||
<a
|
|
||||||
@click="clear"
|
|
||||||
class="cursor-pointer text-active text-base"
|
|
||||||
>
|
|
||||||
{{ $t("alert.clear") }}
|
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td colspan="97" class="timeContent">
|
|
||||||
<div
|
|
||||||
v-for="(item, index) in timeStr"
|
|
||||||
:key="index"
|
:key="index"
|
||||||
v-show="item.length"
|
|
||||||
>
|
>
|
||||||
<span>{{ weekDate[index + 1] }}: </span>
|
{{ item }}
|
||||||
<strong>
|
</td>
|
||||||
<span>{{ item }}</span>
|
</tr>
|
||||||
</strong>
|
</thead>
|
||||||
</div>
|
<tbody id="tableBody">
|
||||||
</td>
|
<tr v-for="(day, index) in weekDate" :key="index">
|
||||||
</tr>
|
<td>{{ day }}</td>
|
||||||
</tbody>
|
<td
|
||||||
</table>
|
v-for="(item, i) in rowUnit[index - 1]"
|
||||||
|
:key="i"
|
||||||
|
@mousedown.prevent="handleMouseDown(i, index - 1)"
|
||||||
|
@mouseup.prevent="handleMouseUp(i, index - 1)"
|
||||||
|
@mousemove.prevent="handleMouseMove(i, index - 1)"
|
||||||
|
class="calendar-atom-time"
|
||||||
|
:class="{
|
||||||
|
'ui-selected': item.class === 'ui-selected',
|
||||||
|
'hover-highlight':
|
||||||
|
downEvent &&
|
||||||
|
index - 1 >= hoverRange.startDay &&
|
||||||
|
index - 1 <= hoverRange.endDay &&
|
||||||
|
i >= hoverRange.startSlot &&
|
||||||
|
i <= hoverRange.endSlot,
|
||||||
|
}"
|
||||||
|
></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td colspan="97" class="py-2">
|
||||||
|
<span class="text-base me-2">{{
|
||||||
|
$t("alert.click_time_period")
|
||||||
|
}}</span>
|
||||||
|
<a
|
||||||
|
@click="
|
||||||
|
() => {
|
||||||
|
clear();
|
||||||
|
initSchedule();
|
||||||
|
}
|
||||||
|
"
|
||||||
|
class="cursor-pointer text-active text-base"
|
||||||
|
>
|
||||||
|
{{ $t("alert.clear") }}
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td colspan="97" class="timeContent">
|
||||||
|
<div
|
||||||
|
v-for="(item, index) in timeStr"
|
||||||
|
:key="index"
|
||||||
|
v-show="item.length"
|
||||||
|
>
|
||||||
|
<span>{{ weekDate[index + 1] }}: </span>
|
||||||
|
<strong>
|
||||||
|
<span>{{ item }}</span>
|
||||||
|
</strong>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</form>
|
||||||
</template>
|
</template>
|
||||||
<template #modalAction>
|
<template #modalAction>
|
||||||
<button
|
<button
|
@ -1,54 +1,39 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import ImmediateDemandChart from "./components/ImmediateDemandChart.vue";
|
import { ref, onMounted, watch } from "vue";
|
||||||
import ElecConsumption from "./components/ElecConsumption.vue";
|
import { useRoute } from "vue-router";
|
||||||
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";
|
|
||||||
|
|
||||||
const taipower_data = ref([]);
|
import EnergyChart from "./components/EnergyChart/EnergyChart.vue";
|
||||||
const getData = async () => {
|
import EnergyHistoryTable from "./components/EnergyHistoryTable/EnergyHistoryTable.vue";
|
||||||
const res = await getTaipower();
|
import EnergyReport from "./components/EnergyReport/EnergyReport.vue";
|
||||||
if (res.isSuccess) {
|
|
||||||
taipower_data.value = res.data;
|
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(() => {
|
onMounted(updateComponent);
|
||||||
getData();
|
|
||||||
});
|
|
||||||
|
|
||||||
provide("energy_data", { taipower_data });
|
watch(
|
||||||
|
() => route.params,
|
||||||
|
() => {
|
||||||
|
updateComponent();
|
||||||
|
}
|
||||||
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="flex flex-wrap items-center mb-4">
|
<component :is="currentComponent" />
|
||||||
<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>
|
</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 searchTerm = ref(""); //搜尋文字
|
||||||
const activeSearchTerm = ref("");
|
const activeSearchTerm = ref("");
|
||||||
|
|
||||||
const getDeviceData = async (sub_tag, renew) => {
|
const getDeviceData = async (sub_system_tag, renew) => {
|
||||||
const res = await getHistorySideBar(sub_tag);
|
const res = await getHistorySideBar({sub_system_tag: sub_system_tag});
|
||||||
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,
|
||||||
|
@ -32,7 +32,7 @@ watch(
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<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>
|
<template #modalContent>
|
||||||
<SystemInfoModalContent />
|
<SystemInfoModalContent />
|
||||||
</template>
|
</template>
|
||||||
|
@ -1,50 +1,77 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { ref, computed, inject, watch } from "vue";
|
import { ref, computed, inject, watch } from "vue";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
import dayjs from 'dayjs';
|
import dayjs from "dayjs";
|
||||||
const { t, locale } = useI18n();
|
|
||||||
|
|
||||||
const { selectedDevice, selectedDeviceRealtime } = inject("system_selectedDevice");
|
const { t } = useI18n();
|
||||||
const loading = ref(true);
|
const { selectedDevice, selectedDeviceRealtime } = inject(
|
||||||
const updatedTime = ref(null);
|
"system_selectedDevice"
|
||||||
const data = computed(() => {
|
);
|
||||||
|
|
||||||
return selectedDevice.value?.value?.points?.map((d) => ({
|
const groupedData = computed(() => {
|
||||||
...d,
|
const grouped = {};
|
||||||
value: selectedDeviceRealtime?.value?.find(({ point }) => point === d.points)?.value || 0
|
|
||||||
})) || []
|
|
||||||
});
|
|
||||||
|
|
||||||
watch(selectedDeviceRealtime, (newValue,oldValue) => {
|
if (selectedDeviceRealtime?.value) {
|
||||||
if (newValue) {
|
selectedDeviceRealtime.value.forEach((record) => {
|
||||||
loading.value = false;
|
const { ori_device_number, time } = record;
|
||||||
updatedTime.value = dayjs(new Date()).format("YYYY-MM-DD HH:mm:ss");
|
|
||||||
}else{
|
if (!grouped[ori_device_number]) {
|
||||||
loading.value = true;
|
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: record.value,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return grouped;
|
||||||
});
|
});
|
||||||
|
|
||||||
const columns = [{
|
|
||||||
title: t("system.attribute"),
|
|
||||||
key: "full_name"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: t("system.value"),
|
|
||||||
key: "value"
|
|
||||||
}]
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<span v-if="updatedTime" class="mt-5 text-info">{{$t("operation.updated_time")}} : {{ updatedTime }}</span>
|
<div v-if="selectedDeviceRealtime">
|
||||||
<Table
|
<div v-for="(group, oriDeviceNumber) in groupedData" :key="oriDeviceNumber">
|
||||||
:loading="loading"
|
<p class="mt-5 text-info">
|
||||||
:columns="columns"
|
{{ $t("operation.updated_time") }} : {{ group.time }}
|
||||||
:dataSource="data || []"
|
</p>
|
||||||
:withStyle="false"
|
<table class="table mt-2">
|
||||||
:pagination="false"
|
<thead>
|
||||||
>
|
<tr>
|
||||||
</Table>
|
<th
|
||||||
|
class="border text-white text-lg text-center bg-cyan-600 bg-opacity-30"
|
||||||
|
>
|
||||||
|
{{ $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>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped></style>
|
<style lang="scss" scoped></style>
|
||||||
|
Loading…
Reference in New Issue
Block a user