新增告警日誌API整合,調整告警查詢邏輯與顯示,優化儀表板告警顯示

This commit is contained in:
koko 2025-09-11 10:52:52 +08:00
parent 30b3cb586b
commit a4e56a7e2d
16 changed files with 211 additions and 236 deletions

View File

@ -3,9 +3,12 @@ import { RouterView } from "vue-router";
import Navbar from "./components/navbar/Navbar.vue"; import Navbar from "./components/navbar/Navbar.vue";
import useUserInfoStore from "@/stores/useUserInfoStore"; import useUserInfoStore from "@/stores/useUserInfoStore";
import { ref, provide, onUnmounted, onMounted } from "vue"; import { ref, provide, onUnmounted, onMounted } from "vue";
import { getAlertLog } from "@/apis/alert";
import dayjs from "dayjs";
const store = useUserInfoStore(); const store = useUserInfoStore();
const alerts = ref([]);
let isToastOpen = ref({ let isToastOpen = ref({
open: false, open: false,
content: "", content: "",
@ -24,6 +27,16 @@ const cancelToastOpen = () => {
}; };
}; };
let alertInterval = null;
const getAlert = async () => {
const res = await getAlertLog({
isRecovery: 1,
Start_date: dayjs().subtract(30, "day").format("YYYY-MM-DD"),
End_date: dayjs().format("YYYY-MM-DD"),
});
alerts.value = (res.data || []).map((d) => ({ ...d, key: d.id }));
};
const openToast = (status, content, to = "body", confirm = null) => { const openToast = (status, content, to = "body", confirm = null) => {
isToastOpen.value = { isToastOpen.value = {
open: true, open: true,
@ -33,8 +46,19 @@ const openToast = (status, content, to = "body", confirm = null) => {
confirm, confirm,
}; };
}; };
onMounted(() => {
getAlert();
alertInterval = setInterval(getAlert, 30 * 1000);
});
onUnmounted(() => {
if (alertInterval) clearInterval(alertInterval);
});
provide("app_toast", { openToast, cancelToastOpen }); provide("app_toast", { openToast, cancelToastOpen });
provide("app_toggle", { forgeLock, toggleForgeLock }); provide("app_toggle", { forgeLock, toggleForgeLock });
provide("app_alerts", alerts);
</script> </script>
<template> <template>

View File

@ -1,5 +1,6 @@
export const POST_ACK_API = `/obix/alarm`; export const POST_ACK_API = `/obix/alarm`;
export const GET_ALERT_FORMID_API = `/Alert/AlertList`; export const GET_ALERT_FORMID_API = `/Alert/AlertList`;
export const GET_ALERT_LOG_API = `api/Alarm/GetAlarmLog`;
export const POST_OPERATION_RECORD_API = `/operation/SavOpeRecord`; export const POST_OPERATION_RECORD_API = `/operation/SavOpeRecord`;
export const GET_ALERT_SUB_LIST_API = `api/Device/GetMainSub`; export const GET_ALERT_SUB_LIST_API = `api/Device/GetMainSub`;

View File

@ -1,6 +1,7 @@
import { import {
POST_ACK_API, POST_ACK_API,
GET_ALERT_FORMID_API, GET_ALERT_FORMID_API,
GET_ALERT_LOG_API,
POST_OPERATION_RECORD_API, POST_OPERATION_RECORD_API,
GET_ALERT_SUB_LIST_API, GET_ALERT_SUB_LIST_API,
GET_OUTLIERS_LIST_API, GET_OUTLIERS_LIST_API,
@ -58,6 +59,24 @@ export const getAlertFormId = async (uuid) => {
}); });
}; };
export const getAlertLog = async ({
Start_date,
End_date,
isRecovery,
device_name_tag,
}) => {
const res = await instance.post(GET_ALERT_LOG_API, {
Start_date,
End_date,
isRecovery,
device_name_tag,
});
return apihandler(res.code, res.data, {
msg: res.msg,
code: res.code,
});
};
export const postOperationRecord = async (formData) => { export const postOperationRecord = async (formData) => {
const res = await instance.post(POST_OPERATION_RECORD_API, formData); const res = await instance.post(POST_OPERATION_RECORD_API, formData);

View File

@ -37,7 +37,6 @@ onMounted(() => {
<div class="drawer drawer-end"> <div class="drawer drawer-end">
<input id="alarm" type="checkbox" class="drawer-toggle" /> <input id="alarm" type="checkbox" class="drawer-toggle" />
<div class="drawer-content"> <div class="drawer-content">
<!-- Page content here -->
<label <label
for="alarm" for="alarm"
class="drawer-button flex flex-col justify-center items-center btn-group" class="drawer-button flex flex-col justify-center items-center btn-group"

View File

@ -85,15 +85,6 @@ const updateMeterPositionsLocal = () => {
meterPositions.value = newPositions; meterPositions.value = newPositions;
}; };
// meterList
watch(
() => props.meterList,
() => {
updateMeterPositionsLocal();
},
{ deep: true }
);
const forgeDom = ref(null); const forgeDom = ref(null);
const initViewer = (container) => { const initViewer = (container) => {
@ -135,8 +126,6 @@ const initForge = () => {
viewer.isLoadDone() viewer.isLoadDone()
); );
updateForgeViewer(viewer); updateForgeViewer(viewer);
// meter
updateMeterPositionsLocal();
} }
); );
@ -199,7 +188,6 @@ onUnmounted(() => {
> >
<p class="absolute z-10 top-14 left-[27%]"> <p class="absolute z-10 top-14 left-[27%]">
更新時間 : {{ props.realTime }} 更新時間 : {{ props.realTime }}
{{ searchParams?.option == 4 ? " (溫度)" : "" }}
</p> </p>
<div v-show="searchParams?.option == 4" class="absolute z-10 heatbar"> <div v-show="searchParams?.option == 4" class="absolute z-10 heatbar">
<div class="w-40 flex justify-between text-[10px] mb-1"> <div class="w-40 flex justify-between text-[10px] mb-1">

View File

@ -76,9 +76,9 @@ onMounted(() => {
> >
</button> </button>
</li> </li>
<li> <!-- <li>
<AlarmDrawer /> <AlarmDrawer />
</li> </li> -->
<li> <li>
<div class="dropdown dropdown-bottom dropdown-end"> <div class="dropdown dropdown-bottom dropdown-end">
<button <button
@ -149,7 +149,6 @@ onMounted(() => {
} }
.menu-box .btn-group span { .menu-box .btn-group span {
color: #fff;
display: block; display: block;
margin-top: 0px; margin-top: 0px;
} }

View File

@ -1,14 +1,17 @@
<script setup> <script setup>
import { onMounted, ref, watch, computed } from "vue"; import { onMounted, ref, watch, computed, inject } from "vue";
import { AUTHPAGES } from "@/constant"; import { AUTHPAGES } from "@/constant";
import { getAuth, getAllSysSidebar } from "@/apis/building"; import { getAuth, getAllSysSidebar } from "@/apis/building";
import useBuildingStore from "@/stores/useBuildingStore"; import useBuildingStore from "@/stores/useBuildingStore";
import useUserInfoStore from "@/stores/useUserInfoStore"; import useUserInfoStore from "@/stores/useUserInfoStore";
import { twMerge } from "tailwind-merge";
const store = useUserInfoStore(); const store = useUserInfoStore();
const buildingStore = useBuildingStore(); const buildingStore = useBuildingStore();
const alerts = inject("app_alerts");
const alarmslength = computed(() => (alerts?.value || []).length);
const iniFroList = async () => { const iniFroList = async () => {
const res = await getAuth(); const res = await getAuth();
@ -57,25 +60,58 @@ onMounted(() => {
v-if="$route.path !== page.navigate" v-if="$route.path !== page.navigate"
:to="page.navigate" :to="page.navigate"
type="link" type="link"
class="flex flex-col justify-center items-center btn-group text-white" class="flex flex-col justify-center items-center btn-group"
> >
<font-awesome-icon <font-awesome-icon
:icon="['fas', page.icon]" :icon="['fas', page.icon]"
size="2x" size="2x"
class="menu-icon" :class="
twMerge(
'menu-icon',
page.showView === 'alert' && alarmslength > 0
? 'text-red-500 animate-pulse'
: 'text-white'
)
"
/> />
<span
:class="
twMerge(
page.showView === 'alert' && alarmslength > 0
? 'text-red-200'
: 'text-white'
)
"
>
{{ page.subName }} {{ page.subName }}
</span>
</router-link> </router-link>
<div <div
v-else v-else
class="flex flex-col justify-center items-center btn-group text-white router-link-active cursor-pointer" class="flex flex-col justify-center items-center btn-group router-link-active cursor-pointer"
> >
<font-awesome-icon <font-awesome-icon
:icon="['fas', page.icon]" :icon="['fas', page.icon]"
size="2x" size="2x"
class="menu-icon" :class="
/> twMerge(
'menu-icon',
page.showView === 'alert' && alarmslength > 0
? 'text-red-500 animate-pulse'
: ''
)
"
/><span
:class="
twMerge(
page.showView === 'alert' && alarmslength > 0
? 'text-red-200'
: ''
)
"
>
{{ page.subName }} {{ page.subName }}
</span>
</div> </div>
</li> </li>
</ul> </ul>

View File

@ -4,12 +4,8 @@ import AlertTable from "./AlertTable.vue";
import AlertTableModal from "./AlertTableModal.vue"; import AlertTableModal from "./AlertTableModal.vue";
import { ref, provide, onMounted, watch } from "vue"; import { ref, provide, onMounted, watch } from "vue";
import useSearchParam from "@/hooks/useSearchParam"; import useSearchParam from "@/hooks/useSearchParam";
import useAlarmData from "@/hooks/baja/useAlarmData"; import { getAlertLog } from "@/apis/alert";
import { import {
getAlertFormId,
} from "@/apis/alert";
import {
getOperationDeviceList,
getOperationCompanyList, getOperationCompanyList,
getOperationEditRecord, getOperationEditRecord,
} from "@/apis/operation"; } from "@/apis/operation";
@ -17,14 +13,12 @@ import { getAccountUserList } from "@/apis/account";
import useBuildingStore from "@/stores/useBuildingStore"; import useBuildingStore from "@/stores/useBuildingStore";
const { searchParams } = useSearchParam(); const { searchParams } = useSearchParam();
const { getAlarmByBaja, alarmData } = useAlarmData();
const store = useBuildingStore(); const store = useBuildingStore();
const tableLoading = ref(false); const tableLoading = ref(false);
const dataSource = ref([]); const dataSource = ref([]);
const model_data = ref({ const model_data = ref({
model_userList: [], model_userList: [],
model_devList: [],
model_companyList: [], model_companyList: [],
}); });
@ -49,30 +43,6 @@ const updateEditRecord = (data) => {
} }
}; };
const getFormId = async (uuid) => {
const res = await getAlertFormId(uuid);
return res.data;
};
const getModalDevList = async () => {
const sub_system_tags = searchParams.value.system_tag.map(tag => tag.split('_')[1]);
const res = await getOperationDeviceList({
list_sub_system_tag: sub_system_tags,
device_building_tag: store.buildings[0].building_tag,
device_area_tag: "NTPC",
});
return res.data.map((d) => {
const formattedKey = d.device_number
.replace(/-/g, '_')
.split('_')
.slice(0, 8)
.join('_');
return { ...d, key: formattedKey };
});
};
const getModalUserList = async () => { const getModalUserList = async () => {
const res = await getAccountUserList({}); const res = await getAccountUserList({});
return res.data.map((d) => ({ ...d, key: d.userinfo_guid })); return res.data.map((d) => ({ ...d, key: d.userinfo_guid }));
@ -84,15 +54,12 @@ const getModalCompanyList = async () => {
}; };
const getAllOptions = async () => { const getAllOptions = async () => {
Promise.all([ Promise.all([getModalUserList(), getModalCompanyList()]).then(
getModalDevList(), ([devices, users, companies]) => {
getModalUserList(),
getModalCompanyList(),
]).then(([devices, users, companies]) => {
model_data.value.model_userList = users; model_data.value.model_userList = users;
model_data.value.model_devList = devices;
model_data.value.model_companyList = companies; model_data.value.model_companyList = companies;
}); }
);
}; };
const updateDataSource = (data) => { const updateDataSource = (data) => {
@ -100,38 +67,13 @@ const updateDataSource = (data) => {
}; };
const search = async () => { const search = async () => {
if (Object.keys(searchParams.value).length !== 0) {
tableLoading.value = true; tableLoading.value = true;
await getAlarmByBaja( if (Object.keys(searchParams.value).length !== 0) {
searchParams.value.start_created_at, const res = await getAlertLog({
searchParams.value.end_created_at, ...searchParams.value,
searchParams.value.isRecover, isRecovery: Number(searchParams.value.isRecovery),
searchParams.value.isAck,
searchParams.value.system_tag,
async (result) => {
alarmData.value = result.data;
// alarm formId
alarmData.value = alarmData.value.map(alarm => ({
...alarm,
formId: null // formId null
}));
const uuids = alarmData.value.map(alarm => ({ uuid: alarm.uuid }));
const formIds = await getFormId(uuids);
if (Array.isArray(formIds)) {
formIds.forEach((form) => {
if (form && form.uuid) {
const index = alarmData.value.findIndex(alarm => alarm.uuid === form.uuid);
if (index !== -1) {
alarmData.value[index].formId = form.formId || null;
}
}
}); });
}; dataSource.value = (res.data || []).map((d) => ({ ...d, key: d.id }));
updateDataSource(alarmData.value);
}
);
tableLoading.value = false; tableLoading.value = false;
} }
}; };
@ -140,11 +82,17 @@ const openModal = async (record) => {
try { try {
if (record.formId) { if (record.formId) {
const res = await getOperationEditRecord(record.formId); const res = await getOperationEditRecord(record.formId);
updateEditRecord({ ...res.data, uuid: record.uuid }); updateEditRecord({
...res.data,
uuid: res.data.error_code,
device_number: record.device_number,
});
} else { } else {
updateEditRecord(record); updateEditRecord({
...record,
uuid: record.id,
});
} }
await getAllOptions();
alert_action_item.showModal(); alert_action_item.showModal();
} catch (error) { } catch (error) {
console.error("Error opening modal:", error); console.error("Error opening modal:", error);
@ -153,20 +101,18 @@ const openModal = async (record) => {
watch( watch(
() => ({ () => ({
isAck: searchParams.value.isAck, isRecovery: searchParams.value.isRecovery,
isRecover: searchParams.value.isRecover, Start_date: searchParams.value.Start_date,
Start_date: searchParams.value.start_created_at, End_date: searchParams.value.End_date,
End_date: searchParams.value.end_created_at, device_name_tag: searchParams.value.device_name_tag,
system_tag: searchParams.value.system_tag,
}), }),
(val) => { (val) => {
// //
if ( if (
val.isAck !== undefined && val.isRecovery !== undefined &&
val.isRecover !== undefined &&
val.Start_date && val.Start_date &&
val.End_date && val.End_date &&
val.system_tag val.device_name_tag
) { ) {
search(); search();
} }
@ -175,7 +121,13 @@ watch(
); );
provide("alert_modal", { model_data, search, updateEditRecord }); provide("alert_modal", { model_data, search, updateEditRecord });
provide("alert_table", { openModal, updateEditRecord, dataSource, search, tableLoading }); provide("alert_table", {
openModal,
updateEditRecord,
dataSource,
search,
tableLoading,
});
</script> </script>
<template> <template>

View File

@ -1,7 +1,6 @@
<script setup> <script setup>
import { inject } from "vue"; import { inject } from "vue";
import AlertSearchNormalBtns from "./AlertSearchNormalBtns.vue"; import AlertSearchNormalBtns from "./AlertSearchNormalBtns.vue";
import AlertSearchAckBtns from "./AlertSearchAckBtns.vue";
import AlertSearchTimeRange from "./AlertSearchTimeRange.vue"; import AlertSearchTimeRange from "./AlertSearchTimeRange.vue";
import AlertSearchTypesButton from "./AlertSearchTypesButton.vue"; import AlertSearchTypesButton from "./AlertSearchTypesButton.vue";
@ -13,7 +12,6 @@ const { search } = inject("alert_table");
<div class="w-full flex flex-wrap flex-col custom-border px-4 pt-0 pb-4 mb-4"> <div class="w-full flex flex-wrap flex-col custom-border px-4 pt-0 pb-4 mb-4">
<div class="w-full flex flex-wrap items-center justify-start"> <div class="w-full flex flex-wrap items-center justify-start">
<AlertSearchNormalBtns /> <AlertSearchNormalBtns />
<AlertSearchAckBtns />
<AlertSearchTimeRange /> <AlertSearchTimeRange />
<button class="btn btn-success ml-8" @click.stop.prevent="search">查詢</button> <button class="btn btn-success ml-8" @click.stop.prevent="search">查詢</button>
</div> </div>

View File

@ -29,7 +29,7 @@ onMounted(() => {
watch( watch(
selectedBtn, selectedBtn,
(newValue) => { (newValue) => {
changeParams({ ...searchParams.value, isAck: newValue.key }); changeParams({ ...searchParams.value });
}, },
); );

View File

@ -11,12 +11,12 @@ onMounted(() => {
setItems([ setItems([
{ {
title: "未復歸", title: "未復歸",
key: "offnormal", key: 1,
active: true, active: true,
}, },
{ {
title: "已賦歸", title: "已賦歸",
key: "normal", key: 2,
active: false, active: false,
}, },
]); ]);
@ -27,7 +27,7 @@ onMounted(() => {
watch( watch(
selectedBtn, selectedBtn,
(newValue) => { (newValue) => {
changeParams({ ...searchParams.value, isRecover: newValue.key }); changeParams({ ...searchParams.value, isRecovery: newValue.key });
} }
); );
@ -35,8 +35,8 @@ watch(
watch( watch(
searchParams, searchParams,
(newSearchParams) => { (newSearchParams) => {
if (!newSearchParams.isRecover) { if (!newSearchParams.isRecovery) {
changeParams({ ...newSearchParams, isRecover: "offnormal" }); changeParams({ ...newSearchParams, isRecovery: 1 });
} }
}, },
{ immediate: true } // { immediate: true } //

View File

@ -8,13 +8,17 @@ const { searchParams, changeParams } = useSearchParam();
const dateRange = ref([ const dateRange = ref([
{ {
key: "start_at", key: "start_at",
value: searchParams.value.start_created_at ? dayjs(searchParams.value.start_created_at).valueOf() : dayjs().subtract(30, 'day').valueOf(), value: searchParams.value.Start_date
? dayjs(searchParams.value.Start_date)
: dayjs().subtract(30, "day"),
dateFormat: "yyyy-MM-dd", dateFormat: "yyyy-MM-dd",
placeholder: "起始日期", placeholder: "起始日期",
}, },
{ {
key: "end_at", key: "end_at",
value: searchParams.value.end_created_at ? dayjs(searchParams.value.end_created_at).valueOf() : dayjs().valueOf(), value: searchParams.value.End_date
? dayjs(searchParams.value.End_date)
: dayjs(),
dateFormat: "yyyy-MM-dd", dateFormat: "yyyy-MM-dd",
placeholder: "結束日期", placeholder: "結束日期",
}, },
@ -24,13 +28,13 @@ const changeTimeRange = () => {
const newRange = [ const newRange = [
{ {
key: "start_at", key: "start_at",
value: dayjs().subtract(30, 'day').startOf('day').valueOf(), // 3000:00:00.000 value: dayjs().subtract(30, "day"),
dateFormat: "yyyy-MM-dd", dateFormat: "yyyy-MM-dd",
placeholder: "起始日期", placeholder: "起始日期",
}, },
{ {
key: "end_at", key: "end_at",
value: dayjs().endOf('day').valueOf(), // 23:59:59.999 value: dayjs().endOf("day"),
dateFormat: "yyyy-MM-dd", dateFormat: "yyyy-MM-dd",
placeholder: "結束日期", placeholder: "結束日期",
}, },
@ -40,14 +44,17 @@ const changeTimeRange = () => {
changeParams({ changeParams({
...searchParams.value, ...searchParams.value,
start_created_at: newRange[0].value, Start_date: newRange[0].value,
end_created_at: newRange[1].value, End_date: newRange[1].value,
}); });
}; };
onMounted(() => { onMounted(() => {
// //
if (!searchParams.value.start_created_at || !searchParams.value.end_created_at) { if (
!searchParams.value.Start_date ||
!searchParams.value.End_date
) {
changeTimeRange(); changeTimeRange();
} }
}); });
@ -58,8 +65,8 @@ watch(
() => { () => {
changeParams({ changeParams({
...searchParams.value, ...searchParams.value,
start_created_at: dayjs(dateRange.value[0].value).startOf('day').valueOf(), Start_date: dayjs(dateRange.value[0].value).format("YYYY-MM-DD"),
end_created_at: dayjs(dateRange.value[1].value).endOf('day').valueOf(), End_date: dayjs(dateRange.value[1].value).format("YYYY-MM-DD"),
}); });
}, },
{ deep: true } // { deep: true } //

View File

@ -11,12 +11,12 @@ const changeCheckedItem = () => {
if (checkedItem.value.length === store.subSys.length) { if (checkedItem.value.length === store.subSys.length) {
changeParams({ changeParams({
...searchParams.value, ...searchParams.value,
system_tag: [store.subSys[0]?.main_system_tag+`_`+store.subSys[0]?.sub_system_tag], device_name_tag: [store.subSys[0]?.sub_system_tag],
}); });
} else { } else {
changeParams({ changeParams({
...searchParams.value, ...searchParams.value,
system_tag: store.subSys.map(({ main_system_tag, sub_system_tag }) => main_system_tag+`_`+sub_system_tag), device_name_tag: store.subSys.map(({ sub_system_tag }) => sub_system_tag),
}); });
} }
}; };
@ -25,16 +25,16 @@ const onChange = (value, checked) => {
if (checked) { if (checked) {
changeParams({ changeParams({
...searchParams.value, ...searchParams.value,
system_tag: searchParams.value.system_tag device_name_tag: searchParams.value.device_name_tag
? typeof searchParams.value.system_tag === "string" ? typeof searchParams.value.device_name_tag === "string"
? [searchParams.value.system_tag, value] ? [searchParams.value.device_name_tag, value]
: [...searchParams.value.system_tag, value] : [...searchParams.value.device_name_tag, value]
: [value], : [value],
}); });
} else { } else {
changeParams({ changeParams({
...searchParams.value, ...searchParams.value,
sub_system_tag: searchParams.value.system_tag.filter( device_name_tag: searchParams.value.device_name_tag.filter(
(sub) => sub !== value (sub) => sub !== value
), ),
}); });
@ -42,18 +42,18 @@ const onChange = (value, checked) => {
}; };
const checkedItem = computed(() => const checkedItem = computed(() =>
searchParams.value.system_tag searchParams.value.device_name_tag
? typeof searchParams.value.system_tag === "string" ? typeof searchParams.value.device_name_tag === "string"
? [searchParams.value.system_tag] ? [searchParams.value.device_name_tag]
: searchParams.value.system_tag : searchParams.value.device_name_tag
: [] : []
); );
watch(searchParams, (newValue) => { watch(searchParams, (newValue) => {
if (!newValue.system_tag) { if (!newValue.device_name_tag) {
changeParams({ changeParams({
...newValue, ...newValue,
system_tag: store.subSys.map(({ main_system_tag, sub_system_tag }) => main_system_tag+`_`+sub_system_tag), device_name_tag: store.subSys.map(({ sub_system_tag }) => sub_system_tag),
}); });
} }
}); });
@ -71,8 +71,8 @@ watch(searchParams, (newValue) => {
v-for="sub in store.subSys" v-for="sub in store.subSys"
:key="sub.key" :key="sub.key"
:title="sub.full_name" :title="sub.full_name"
:value="sub.main_system_tag+`_`+sub.sub_system_tag" :value="sub.sub_system_tag"
:checked="checkedItem.includes(sub.main_system_tag+`_`+sub.sub_system_tag)" :checked="checkedItem.includes(sub.sub_system_tag)"
class="mx-3 my-3 xl:my-0 text-lg" class="mx-3 my-3 xl:my-0 text-lg"
:onChange="onChange" :onChange="onChange"
/> />

View File

@ -1,48 +1,34 @@
<script setup> <script setup>
import { inject } from "vue"; import { inject } from "vue";
import { import { postChgAck } from "@/apis/alert";
postChgAck,
} from "@/apis/alert";
import { Button } from "ant-design-vue"; import { Button } from "ant-design-vue";
const { dataSource, openModal, search, tableLoading } = inject("alert_table"); const { dataSource, openModal, search, tableLoading } = inject("alert_table");
const columns = [ const columns = [
{ {
key: "building_tag", key: "alarm_guid",
title: "棟別-樓層",
},
{
key: "uuid",
title: "異常ID", title: "異常ID",
}, },
{ {
key: "alarmClass", key: "factor",
title: "異常類別", title: "告警條件",
},
{
key: "full_name",
title: "維修項目",
}, },
{ {
key: "device_number", key: "device_number",
title: "設備編號", title: "設備名稱",
}, },
{ {
key: "timestamp_date", key: "points",
title: "發生日期", title: "點位名稱",
}, },
{ {
key: "timestamp_time", key: "created_at",
title: "發生時間", title: "發生時間",
}, },
{ {
key: "msg", key: "reason",
title: "異常原因", title: "異常原因",
}, },
{
key: "ackState",
title: "Ack 確認",
},
{ {
key: "repairOrder", key: "repairOrder",
title: "派工 / 維運單號", title: "派工 / 維運單號",
@ -55,25 +41,16 @@ const chgAck = async (devUuid) => {
search?.(); search?.();
} }
}; };
</script> </script>
<template> <template>
<Table :loading="tableLoading" :columns="columns" :dataSource="dataSource"> <Table :loading="tableLoading" :columns="columns" :dataSource="dataSource">
<template #bodyCell="{ record, column, index }"> <template #bodyCell="{ record, column, index }">
<template v-if="column.key === 'ackState'">
<template v-if="record.ackState === 'Unacked'">
<button class="btn btn-sm btn-success text-white whitespace-nowrap mr-2" @click.stop.prevent="() => chgAck(record.uuid)">
未確認
</button>
</template>
<template v-else>
{{ record.ackedTime }}
</template>
</template>
<template v-if="column.key === 'repairOrder'"> <template v-if="column.key === 'repairOrder'">
<button class="btn btn-sm btn-success text-white whitespace-nowrap mr-2" <button
@click.stop.prevent="() => openModal(record)"> class="btn btn-sm btn-success text-white whitespace-nowrap mr-2"
@click.stop.prevent="() => openModal(record)"
>
<span v-if="record.formId">{{ record.formId }}</span> <span v-if="record.formId">{{ record.formId }}</span>
<span v-else> <span v-else>
<font-awesome-icon :icon="['fas', 'plus']" />維修單 <font-awesome-icon :icon="['fas', 'plus']" />維修單

View File

@ -29,12 +29,13 @@ const dateItem = ref([
const formState = ref( const formState = ref(
{ {
formId: null, formId: null,
uuid: "", alarm_guid: "",
work_type: 2, work_type: 2,
fix_do: "", fix_do: "",
fix_do_code: "", fix_do_code: "",
fix_firm: "", fix_firm: "",
status: 0, status: 0,
device_number: "",
work_person_id: "", work_person_id: "",
start_time: dayjs().format("YYYY-MM-DD"), start_time: dayjs().format("YYYY-MM-DD"),
notice: "", notice: "",
@ -87,7 +88,7 @@ const onOk = async () => {
props.editRecord.id && formData.append("id", props.editRecord.id); props.editRecord.id && formData.append("id", props.editRecord.id);
props.editRecord.uuid && formData.append("error_code", props.editRecord.uuid); props.editRecord.alarm_guid && formData.append("error_code", props.editRecord.alarm_guid);
formData.append("work_type", 2); formData.append("work_type", 2);
@ -112,12 +113,13 @@ const onOk = async () => {
const onCancel = () => { const onCancel = () => {
formState.value = { formState.value = {
formId: null, formId: null,
uuid: "", alarm_guid: "",
work_type: 2, work_type: 2,
fix_do: "", fix_do: "",
fix_do_code: "", fix_do_code: "",
fix_firm: "", fix_firm: "",
status: 0, status: 0,
device_number: "",
work_person_id: "", work_person_id: "",
start_time: dayjs().format("YYYY-MM-DD"), start_time: dayjs().format("YYYY-MM-DD"),
notice: "", notice: "",
@ -143,10 +145,6 @@ watch(
formState.value.start_time = value ? dayjs(value).format("YYYY-MM-DD") : dayjs().format("YYYY-MM-DD"); formState.value.start_time = value ? dayjs(value).format("YYYY-MM-DD") : dayjs().format("YYYY-MM-DD");
dateItem.value[0].value = value; dateItem.value[0].value = value;
} }
//
if (key === "full_name") {
formState.value.fix_do = value;
}
// //
if (key === "device_number" || key === "fix_do_code") { if (key === "device_number" || key === "fix_do_code") {
formState.value.fix_do_code = value; formState.value.fix_do_code = value;
@ -165,8 +163,8 @@ watch(
<Input v-if="formState.value && formState.value.formId" class="my-2" :value="formState" name="formId" readonly> <Input v-if="formState.value && formState.value.formId" class="my-2" :value="formState" name="formId" readonly>
<template #topLeft>表單編號</template> <template #topLeft>表單編號</template>
</Input> </Input>
<Input :value="formState" class="my-2" name="uuid" readonly> <Input :value="formState" class="my-2" name="alarm_guid" readonly>
<template #topLeft>異常編號</template> <template #topLeft>異常ID</template>
</Input> </Input>
<DateGroup class="my-2" :items="dateItem" inputClass="w-full shadow-none" :required="true"> <DateGroup class="my-2" :items="dateItem" inputClass="w-full shadow-none" :required="true">
<template #topLeft>預計開始時間</template> <template #topLeft>預計開始時間</template>
@ -191,20 +189,25 @@ watch(
]" :required="true" :disabled="true"> ]" :required="true" :disabled="true">
<template #topLeft>項目</template> <template #topLeft>項目</template>
</Select> </Select>
<Input class="my-2" :value="formState" name="fix_do" :required="true" readonly> <Input class="my-2" :value="formState" name="fix_do" :required="true">
<template #topLeft>維修項目</template> <template #topLeft>維修項目</template>
<template #bottomLeft><span class="text-error text-base"> <template #bottomLeft><span class="text-error text-base">
{{ formErrorMsg.fix_do }} {{ formErrorMsg.fix_do }}
</span> </span>
</template> </template>
</Input> </Input>
<Select :value="formState" class="my-2" selectClass="border-info focus-within:border-info" name="fix_do_code" <Input
Attribute="full_name" :options="model_data.model_devList" :required="true" :disabled="true"> class="my-2"
<template #topLeft>維修項目代碼(設備編號)</template> :value="formState"
name="device_number"
:required="true"
:disabled="true"
>
<template #topLeft>設備名稱</template>
<template #bottomLeft><span class="text-error text-base"> <template #bottomLeft><span class="text-error text-base">
{{ formErrorMsg.fix_do_code }} {{ formErrorMsg.fix_do_code }}
</span></template> </span></template>
</Select> </Input>
<Select :value="formState" class="my-2" selectClass="border-info focus-within:border-info" name="fix_firm" <Select :value="formState" class="my-2" selectClass="border-info focus-within:border-info" name="fix_firm"
Attribute="name" :options="model_data.model_companyList" :required="true"> Attribute="name" :options="model_data.model_companyList" :required="true">
<template #topLeft>負責廠商</template> <template #topLeft>負責廠商</template>

View File

@ -1,47 +1,14 @@
<script setup> <script setup>
import useAlarmStore from "@/stores/useAlarmStore";
import dayjs from "dayjs"; import dayjs from "dayjs";
import { ref,computed } from "vue"; import { ref, computed, inject } from "vue";
const alerts = inject("app_alerts");
const store = useAlarmStore(); const alarms = computed(() =>
// const alarms = computed(() => (alerts.value || []).slice(0, 5).map((d) => ({
// store.alarmData.slice(0, 5).map((d) => ({ ...d,
// ...d, timestamp_date: dayjs(d.created_at).format("YYYY.MM.DD"),
// timestamp_date: dayjs(d.timestamp_date).format("YYYY.MM.DD"), timestamp_time: dayjs(d.created_at).format("HH:mm:ss"),
// timestamp_time: d.timestamp_time.split(":").slice(0, 2).join(":"), })).sort((a, b) => (dayjs(a.created_at).isBefore(dayjs(b.created_at)) ? 1 : -1))
// })) );
// );
const alarms = ref([
{
uuid: 1,
timestamp_date: "2020-01-02",
timestamp_time: "11:02",
msg: "xx異常",
status: "處理中",
},
{
uuid: 2,
timestamp_date: "2020-01-03",
timestamp_time: "12:02",
msg: "xx異常",
status: "已處理",
},
{
uuid: 3,
timestamp_date: "2020-01-04",
timestamp_time: "13:02",
msg: "xx異常",
status: "已處理",
},
{
uuid: 4,
timestamp_date: "2020-01-05",
timestamp_time: "14:02",
msg: "xx異常",
status: "處理中",
},
]);
</script> </script>
<template> <template>
@ -54,17 +21,22 @@ const alarms = ref([
<tr class=""> <tr class="">
<th>日期</th> <th>日期</th>
<th>時間</th> <th>時間</th>
<th>備註</th> <th>設備名稱</th>
<th>狀態</th> <th>異常原因</th>
</tr> </tr>
</thead> </thead>
<tbody class="text-base"> <tbody class="text-base">
<tr> <tr v-if="alarms.length === 0">
<td colspan="4" class="text-center h-40"> <td colspan="4" class="text-center h-40">
<!-- <span>無資料</span> --> <div class="text-gray-400">目前無告警訊息</div>
</td> </td>
</tr> </tr>
<tr v-else v-for="alarm in alarms" :key="alarm.alarm_guid">
<td>{{ alarm.timestamp_date }}</td>
<td>{{ alarm.timestamp_time }}</td>
<td>{{ alarm.device_number }}</td>
<td>{{ alarm.reason }}</td>
</tr>
</tbody> </tbody>
</table> </table>
</div> </div>