調整儀表板與電表卡片的顯示邏輯 | 新增電表卡片即時數據獲取與圖表顯示功能
This commit is contained in:
parent
cea34c2e9d
commit
babef0e2ae
@ -107,21 +107,6 @@ const initForge = () => {
|
|||||||
viewer.isLoadDone()
|
viewer.isLoadDone()
|
||||||
);
|
);
|
||||||
updateForgeViewer(viewer);
|
updateForgeViewer(viewer);
|
||||||
|
|
||||||
// const tree = viewer.model.getData().instanceTree;
|
|
||||||
// hideAllObjects(tree, visibleDbid.value);
|
|
||||||
// visibleDbid.value.forEach((dbid) => {
|
|
||||||
// if (dbid === 58) {
|
|
||||||
// viewer.setThemingColor(dbid, new THREE.Vector4(1, 0, 0, 1));
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
// 印出被點選物件的 dbid
|
|
||||||
// viewer.addEventListener(
|
|
||||||
// Autodesk.Viewing.SELECTION_CHANGED_EVENT,
|
|
||||||
// function (event) {
|
|
||||||
// console.log("你選取的 forge_dbid:", event.dbIdArray);
|
|
||||||
// }
|
|
||||||
// );
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
viewer.addEventListener(
|
viewer.addEventListener(
|
||||||
|
@ -495,22 +495,6 @@ export default function useSystemStatusByBaja(updateHeatBarIsShow) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
watch(initialData, (newValue) => {
|
|
||||||
if (newValue) {
|
|
||||||
getDevice(searchParams.value.option);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
watch(
|
|
||||||
searchParams,
|
|
||||||
(newValue) => {
|
|
||||||
getDevice(newValue.option);
|
|
||||||
},
|
|
||||||
{
|
|
||||||
deep: true,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// 動態Sprites創建函數
|
// 動態Sprites創建函數
|
||||||
const createSprites = async (viewer, dbId, reverse = false) => {
|
const createSprites = async (viewer, dbId, reverse = false) => {
|
||||||
try {
|
try {
|
||||||
|
@ -9,13 +9,26 @@ import DashboardElectricity from "./components/DashboardElectricity.vue";
|
|||||||
import DashboardAlert from "./components/DashboardAlert.vue";
|
import DashboardAlert from "./components/DashboardAlert.vue";
|
||||||
import DashboardForgeOptionButton from "./components/DashboardForgeOptionButton.vue";
|
import DashboardForgeOptionButton from "./components/DashboardForgeOptionButton.vue";
|
||||||
import DashboardForgeOptionCard from "./components/DashboardForgeOptionCard.vue";
|
import DashboardForgeOptionCard from "./components/DashboardForgeOptionCard.vue";
|
||||||
import { getDashboardInit, getDashboardOptionRealTimeData } from "@/apis/dashboard";
|
import {
|
||||||
|
getDashboardInit,
|
||||||
|
getDashboardOptionRealTimeData,
|
||||||
|
} from "@/apis/dashboard";
|
||||||
|
import { getAssetFloorList } from "@/apis/asset";
|
||||||
|
import { getOperationCompanyList } from "@/apis/operation";
|
||||||
import useSearchParams from "@/hooks/useSearchParam";
|
import useSearchParams from "@/hooks/useSearchParam";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
|
|
||||||
const initialData = ref(null);
|
const initialData = ref(null);
|
||||||
const realTimeData = ref(null);
|
const realTimeData = ref(null);
|
||||||
const realTime = ref(null);
|
const realTime = ref(null);
|
||||||
|
// 電表列表
|
||||||
|
const meterList = ref([]);
|
||||||
|
const selectedMeter = ref(null);
|
||||||
|
// 樓層列表
|
||||||
|
const floors = ref([]);
|
||||||
|
// 廠商列表
|
||||||
|
const companyOptions = ref([]);
|
||||||
|
|
||||||
const { searchParams } = useSearchParams();
|
const { searchParams } = useSearchParams();
|
||||||
let intervalId = null;
|
let intervalId = null;
|
||||||
|
|
||||||
@ -34,6 +47,11 @@ const getDevice = async (option = 1) => {
|
|||||||
option: parseInt(option),
|
option: parseInt(option),
|
||||||
});
|
});
|
||||||
realTimeData.value = res.data;
|
realTimeData.value = res.data;
|
||||||
|
if (res.data?.meterData) {
|
||||||
|
meterList.value = (res.data?.meterData || []).sort();
|
||||||
|
} else {
|
||||||
|
meterList.value = [];
|
||||||
|
}
|
||||||
realTime.value = dayjs().format("YYYY-MM-DD HH:mm:ss");
|
realTime.value = dayjs().format("YYYY-MM-DD HH:mm:ss");
|
||||||
console.log("實時數據:", realTimeData.value);
|
console.log("實時數據:", realTimeData.value);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@ -41,6 +59,16 @@ const getDevice = async (option = 1) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getFloors = async () => {
|
||||||
|
const res = await getAssetFloorList();
|
||||||
|
floors.value = res.data[0]?.floors.map((d) => ({ ...d, key: d.floor_guid }));
|
||||||
|
};
|
||||||
|
|
||||||
|
const getCompany = async () => {
|
||||||
|
const res = await getOperationCompanyList();
|
||||||
|
companyOptions.value = res.data.map((d) => ({ ...d, key: d.id }));
|
||||||
|
};
|
||||||
|
|
||||||
// 開始定時器
|
// 開始定時器
|
||||||
const startInterval = (option) => {
|
const startInterval = (option) => {
|
||||||
// 清除之前的定時器
|
// 清除之前的定時器
|
||||||
@ -66,7 +94,7 @@ const stopInterval = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
()=>searchParams.value.option,
|
() => searchParams.value.option,
|
||||||
(newValue) => {
|
(newValue) => {
|
||||||
if (newValue) {
|
if (newValue) {
|
||||||
startInterval(newValue);
|
startInterval(newValue);
|
||||||
@ -80,6 +108,8 @@ watch(
|
|||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
init();
|
init();
|
||||||
|
getFloors();
|
||||||
|
getCompany();
|
||||||
});
|
});
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
@ -98,9 +128,19 @@ onUnmounted(() => {
|
|||||||
<DashboardAlert />
|
<DashboardAlert />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Forge :fullScreen="true" :initialData="initialData" :realTime="realTime"/>
|
<Forge :fullScreen="true" :initialData="initialData" :realTime="realTime" />
|
||||||
<DashboardForgeOptionButton :initialData="initialData" />
|
<DashboardForgeOptionButton
|
||||||
<DashboardForgeOptionCard :realTimeData="realTimeData"/>
|
:initialData="initialData"
|
||||||
|
:meterList="meterList"
|
||||||
|
:selectedMeter="selectedMeter"
|
||||||
|
@update:selectedMeter="selectedMeter = $event"
|
||||||
|
/>
|
||||||
|
<DashboardForgeOptionCard
|
||||||
|
:realTimeData="realTimeData"
|
||||||
|
:selectedMeter="selectedMeter"
|
||||||
|
:floors="floors"
|
||||||
|
:companyOptions="companyOptions"
|
||||||
|
/>
|
||||||
<div class="w-1/4 flex flex-col justify-start border-dashboard z-20">
|
<div class="w-1/4 flex flex-col justify-start border-dashboard z-20">
|
||||||
<div class=""><DashboardImmediateTemp /></div>
|
<div class=""><DashboardImmediateTemp /></div>
|
||||||
<div class="mt-5">
|
<div class="mt-5">
|
||||||
|
@ -5,15 +5,18 @@ import { twMerge } from "tailwind-merge";
|
|||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
initialData: Object,
|
initialData: Object,
|
||||||
|
meterList: Array,
|
||||||
|
selectedMeter: [String, Number, null],
|
||||||
});
|
});
|
||||||
|
|
||||||
const { changeParams, searchParams } = useSearchParams();
|
const { changeParams, searchParams } = useSearchParams();
|
||||||
|
const emit = defineEmits(["update:selectedMeter"]);
|
||||||
watch(
|
watch(
|
||||||
() => props.initialData,
|
() => props.initialData,
|
||||||
(newValue) => {
|
(newValue) => {
|
||||||
if (newValue?.options[0]) {
|
if (newValue?.options[0]) {
|
||||||
const { option, camera_position, target_position, top } = newValue.options[0];
|
const { option, camera_position, target_position, top } =
|
||||||
|
newValue.options[0];
|
||||||
changeParams({ option, camera_position, target_position, top });
|
changeParams({ option, camera_position, target_position, top });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -27,30 +30,80 @@ watch(
|
|||||||
v-for="option in initialData.options"
|
v-for="option in initialData.options"
|
||||||
:key="`option_${option.option}`"
|
:key="`option_${option.option}`"
|
||||||
>
|
>
|
||||||
<button
|
<template v-if="option.option === 5 && option.visible">
|
||||||
v-if="option.visible"
|
<div class="dropdown inline-block">
|
||||||
:key="`option_${option.option}`"
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
:class="
|
class="btn mx-1"
|
||||||
twMerge(
|
:class="
|
||||||
'btn mx-1',
|
parseInt(searchParams?.option) === 5
|
||||||
parseInt(searchParams?.option) === option.option
|
? 'btn-info'
|
||||||
? 'btn-info'
|
: 'btn-outline-info'
|
||||||
: 'btn-outline-info'
|
"
|
||||||
)
|
@click.prevent="
|
||||||
"
|
() => {
|
||||||
@click.prevent="
|
changeParams({
|
||||||
() =>
|
option: option.option,
|
||||||
changeParams({
|
});
|
||||||
option: option.option,
|
}
|
||||||
camera_position: option.camera_position,
|
"
|
||||||
target_position: option.target_position,
|
tabindex="0"
|
||||||
top: option.top
|
>
|
||||||
})
|
{{ option.text }}
|
||||||
"
|
</button>
|
||||||
>
|
<ul
|
||||||
{{ option.text }}
|
class="dropdown-content menu bg-base-100 rounded-box z-[1] w-32 p-2 shadow"
|
||||||
</button>
|
>
|
||||||
|
<li v-for="meter in props.meterList" :key="meter.main_id">
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
:style="{
|
||||||
|
background:
|
||||||
|
props.selectedMeter === meter.main_id ? '#2563eb' : 'transparent',
|
||||||
|
}"
|
||||||
|
@click.prevent="
|
||||||
|
() => {
|
||||||
|
changeParams({
|
||||||
|
option: 5,
|
||||||
|
camera_position: meter.camera_position,
|
||||||
|
target_position: meter.target_position,
|
||||||
|
});
|
||||||
|
emit('update:selectedMeter', meter.main_id);
|
||||||
|
}
|
||||||
|
"
|
||||||
|
>
|
||||||
|
{{ meter.name }}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<button
|
||||||
|
v-if="option.visible"
|
||||||
|
:key="`option_${option.option}`"
|
||||||
|
type="button"
|
||||||
|
:class="
|
||||||
|
twMerge(
|
||||||
|
'btn mx-1',
|
||||||
|
parseInt(searchParams?.option) === option.option
|
||||||
|
? 'btn-info'
|
||||||
|
: 'btn-outline-info'
|
||||||
|
)
|
||||||
|
"
|
||||||
|
@click.prevent="
|
||||||
|
() =>
|
||||||
|
changeParams({
|
||||||
|
option: option.option,
|
||||||
|
camera_position: option.camera_position,
|
||||||
|
target_position: option.target_position,
|
||||||
|
top: option.top,
|
||||||
|
})
|
||||||
|
"
|
||||||
|
>
|
||||||
|
{{ option.text }}
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -15,16 +15,11 @@ import ValveCard from "./dashboardForgeCards/ValveCard.vue";
|
|||||||
const { searchParams, changeParams } = useSearchParams();
|
const { searchParams, changeParams } = useSearchParams();
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
realTimeData: Object,
|
realTimeData: Object,
|
||||||
|
selectedMeter: String,
|
||||||
|
floors: Array,
|
||||||
|
companyOptions: Array,
|
||||||
});
|
});
|
||||||
|
|
||||||
const tabs = [
|
|
||||||
{ label: "生產資訊" },
|
|
||||||
{ label: "投料進度" },
|
|
||||||
{ label: "品檢" },
|
|
||||||
{ label: "流量計" },
|
|
||||||
{ label: "SIP" },
|
|
||||||
];
|
|
||||||
|
|
||||||
// 生產資訊卡片資料
|
// 生產資訊卡片資料
|
||||||
const productionData = computed(() => {
|
const productionData = computed(() => {
|
||||||
return props.realTimeData?.productionData || [];
|
return props.realTimeData?.productionData || [];
|
||||||
@ -36,24 +31,9 @@ const heaterData = computed(() => {
|
|||||||
return props.realTimeData?.heaterData || [];
|
return props.realTimeData?.heaterData || [];
|
||||||
});
|
});
|
||||||
|
|
||||||
// 管理所有設備的activeTab狀態
|
|
||||||
const deviceActiveTabs = ref(new Map());
|
|
||||||
|
|
||||||
// 獲取或創建設備的activeTab
|
|
||||||
const getDeviceActiveTab = (deviceName, defaultTab = "生產資訊") => {
|
|
||||||
if (!deviceActiveTabs.value.has(deviceName)) {
|
|
||||||
deviceActiveTabs.value.set(deviceName, ref(defaultTab));
|
|
||||||
}
|
|
||||||
return deviceActiveTabs.value.get(deviceName);
|
|
||||||
};
|
|
||||||
|
|
||||||
// 二重釜資料
|
// 二重釜資料
|
||||||
const vesselsData = computed(() => {
|
const vesselsData = computed(() => {
|
||||||
const data = props.realTimeData?.productionData || [];
|
return props.realTimeData?.productionData || [];
|
||||||
return data.map((vessel) => ({
|
|
||||||
...vessel,
|
|
||||||
activeTab: getDeviceActiveTab(vessel.name),
|
|
||||||
}));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// 加熱器資料
|
// 加熱器資料
|
||||||
@ -63,11 +43,7 @@ const heaterPotData = computed(() => {
|
|||||||
|
|
||||||
// 調理鍋資料
|
// 調理鍋資料
|
||||||
const cookingPotData = computed(() => {
|
const cookingPotData = computed(() => {
|
||||||
const data = props.realTimeData?.cookingData || [];
|
return props.realTimeData?.cookingData || [];
|
||||||
return data.map((pot) => ({
|
|
||||||
...pot,
|
|
||||||
activeTab2: getDeviceActiveTab(pot.name),
|
|
||||||
}));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// 冷藏室資料
|
// 冷藏室資料
|
||||||
@ -78,7 +54,11 @@ const refrigerationData = computed(() => {
|
|||||||
// 電錶資料
|
// 電錶資料
|
||||||
const meterData = computed(() => {
|
const meterData = computed(() => {
|
||||||
const data = props.realTimeData?.meterData || [];
|
const data = props.realTimeData?.meterData || [];
|
||||||
return data.sort((a, b) => a.name.localeCompare(b.name));
|
// 篩選出符合 selectedMeter 的資料
|
||||||
|
const filtered = props.selectedMeter
|
||||||
|
? data.filter((meter) => meter.main_id === props.selectedMeter)
|
||||||
|
: [];
|
||||||
|
return filtered;
|
||||||
});
|
});
|
||||||
|
|
||||||
// 威鈦閥異常訊號資料
|
// 威鈦閥異常訊號資料
|
||||||
@ -105,7 +85,6 @@ const valveData = computed(() => {
|
|||||||
v-for="(vessel, index) in vesselsData"
|
v-for="(vessel, index) in vesselsData"
|
||||||
:key="index"
|
:key="index"
|
||||||
:vessel="vessel"
|
:vessel="vessel"
|
||||||
:tabs="tabs.filter(tab => tab.label !== '品檢')"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -120,7 +99,6 @@ const valveData = computed(() => {
|
|||||||
v-for="(pot, index) in cookingPotData"
|
v-for="(pot, index) in cookingPotData"
|
||||||
:key="index"
|
:key="index"
|
||||||
:pot="pot"
|
:pot="pot"
|
||||||
:tabs="tabs"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -137,6 +115,8 @@ const valveData = computed(() => {
|
|||||||
v-for="(meter, index) in meterData"
|
v-for="(meter, index) in meterData"
|
||||||
:key="index"
|
:key="index"
|
||||||
:meter="meter"
|
:meter="meter"
|
||||||
|
:floors="props.floors"
|
||||||
|
:companyOptions="props.companyOptions"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -1,8 +1,16 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
|
import { ref } from "vue";
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
pot: Object,
|
pot: Object,
|
||||||
tabs: Array,
|
|
||||||
});
|
});
|
||||||
|
const tabs = [
|
||||||
|
{ label: "生產資訊" },
|
||||||
|
{ label: "投料進度" },
|
||||||
|
{ label: "品檢" },
|
||||||
|
{ label: "流量計" },
|
||||||
|
{ label: "SIP" },
|
||||||
|
];
|
||||||
|
const activeTab = ref(tabs[0].label);
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div class="card bg-slate-200 text-accent-content rounded-md w-[25rem]">
|
<div class="card bg-slate-200 text-accent-content rounded-md w-[25rem]">
|
||||||
@ -19,15 +27,15 @@ const props = defineProps({
|
|||||||
class="tab"
|
class="tab"
|
||||||
:class="{
|
:class="{
|
||||||
'tab-active !bg-green-500 !text-white shadow-sm shadow-slate-800':
|
'tab-active !bg-green-500 !text-white shadow-sm shadow-slate-800':
|
||||||
pot.activeTab2.value === tab.label,
|
activeTab === tab.label,
|
||||||
}"
|
}"
|
||||||
@click.prevent="pot.activeTab2.value = tab.label"
|
@click.prevent="activeTab = tab.label"
|
||||||
>
|
>
|
||||||
{{ tab.label }}
|
{{ tab.label }}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="p-0">
|
<div class="p-0">
|
||||||
<div v-if="pot.activeTab2.value === '生產資訊'">
|
<div v-if="activeTab === '生產資訊'">
|
||||||
<ul class="leading-7 tracking-wider text-slate-700 px-2">
|
<ul class="leading-7 tracking-wider text-slate-700 px-2">
|
||||||
<li><b>品名:</b> {{ pot.productInfo?.product }}</li>
|
<li><b>品名:</b> {{ pot.productInfo?.product }}</li>
|
||||||
<li><b>鍋次:</b> 第{{ pot.productInfo?.batch || "__" }}鍋</li>
|
<li><b>鍋次:</b> 第{{ pot.productInfo?.batch || "__" }}鍋</li>
|
||||||
@ -36,7 +44,7 @@ const props = defineProps({
|
|||||||
<li><b>狀態:</b> {{ pot.productInfo?.status }}</li>
|
<li><b>狀態:</b> {{ pot.productInfo?.status }}</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="pot.activeTab2.value === '投料進度'">
|
<div v-else-if="activeTab === '投料進度'">
|
||||||
<div class="h-40 overflow-x-auto">
|
<div class="h-40 overflow-x-auto">
|
||||||
<table class="table table-sm table-pin-rows">
|
<table class="table table-sm table-pin-rows">
|
||||||
<thead>
|
<thead>
|
||||||
@ -68,7 +76,7 @@ const props = defineProps({
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="pot.activeTab2.value === '品檢'">
|
<div v-else-if="activeTab === '品檢'">
|
||||||
<div class="h-40 overflow-x-auto">
|
<div class="h-40 overflow-x-auto">
|
||||||
<table class="table table-sm table-pin-rows whitespace-nowrap">
|
<table class="table table-sm table-pin-rows whitespace-nowrap">
|
||||||
<thead>
|
<thead>
|
||||||
@ -98,14 +106,17 @@ const props = defineProps({
|
|||||||
<td>{{ flow.actual_PH }}</td>
|
<td>{{ flow.actual_PH }}</td>
|
||||||
<td>{{ flow.actual_Brix }}</td>
|
<td>{{ flow.actual_Brix }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr v-if="!pot.qcResult || !pot.qcResult.length" class="h-28 border-0">
|
<tr
|
||||||
|
v-if="!pot.qcResult || !pot.qcResult.length"
|
||||||
|
class="h-28 border-0"
|
||||||
|
>
|
||||||
<td colspan="8" class="text-center">無品檢資料</td>
|
<td colspan="8" class="text-center">無品檢資料</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="pot.activeTab2.value === '流量計'">
|
<div v-else-if="activeTab === '流量計'">
|
||||||
<div class="h-40 overflow-x-auto">
|
<div class="h-40 overflow-x-auto">
|
||||||
<table class="table table-sm">
|
<table class="table table-sm">
|
||||||
<thead>
|
<thead>
|
||||||
@ -133,7 +144,7 @@ const props = defineProps({
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="pot.activeTab2.value === 'SIP'">
|
<div v-else-if="activeTab === 'SIP'">
|
||||||
<div class="">
|
<div class="">
|
||||||
<table class="table table-sm whitespace-nowrap">
|
<table class="table table-sm whitespace-nowrap">
|
||||||
<thead>
|
<thead>
|
||||||
@ -145,17 +156,15 @@ const props = defineProps({
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr
|
<tr
|
||||||
v-for="(value, index) in pot.sip"
|
v-for="(value, index) in pot.sip"
|
||||||
:key="index"
|
:key="index"
|
||||||
class="border-0">
|
class="border-0"
|
||||||
|
>
|
||||||
<th>{{ value.count }}</th>
|
<th>{{ value.count }}</th>
|
||||||
<td>{{ value.startTime }}</td>
|
<td>{{ value.startTime }}</td>
|
||||||
<td>{{ value.endTime }}</td>
|
<td>{{ value.endTime }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr
|
<tr v-if="!pot.sip || !pot.sip.length" class="h-28 border-0">
|
||||||
v-if="!pot.sip || !pot.sip.length"
|
|
||||||
class="h-28 border-0"
|
|
||||||
>
|
|
||||||
<td colspan="3" class="text-center">無SIP資料</td>
|
<td colspan="3" class="text-center">無SIP資料</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
|
@ -1,17 +1,321 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
defineProps({ meter: Object })
|
import { defineProps, onMounted, onUnmounted, ref, nextTick, watch } from "vue";
|
||||||
|
import { getHistoryPoints, getHistoryData } from "@/apis/history";
|
||||||
|
import { getAssetSingle } from "@/apis/asset";
|
||||||
|
import { getOperationCompanyList } from "@/apis/operation";
|
||||||
|
import LineChart from "@/components/chart/LineChart.vue";
|
||||||
|
import { SECOND_CHART_COLOR } from "@/constant";
|
||||||
|
import dayjs from "dayjs";
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
meter: Object,
|
||||||
|
floors: Array,
|
||||||
|
companyOptions: Array,
|
||||||
|
});
|
||||||
|
const tabs = ["即時資訊", "設備資訊", "趨勢查詢"];
|
||||||
|
const activeTab = ref(tabs[0]);
|
||||||
|
const meterDetails = ref({});
|
||||||
|
const pointsList = ref([]);
|
||||||
|
const timeList = ref([
|
||||||
|
{ value: 1, name: "1小時" },
|
||||||
|
{ value: 4, name: "4小時" },
|
||||||
|
{ value: 8, name: "8小時" },
|
||||||
|
]);
|
||||||
|
const chartData = ref([]);
|
||||||
|
const forge_chart = ref(null);
|
||||||
|
const loading = ref(false);
|
||||||
|
|
||||||
|
// 預設圖表選項
|
||||||
|
const defaultChartOption = {
|
||||||
|
tooltip: {
|
||||||
|
trigger: "axis",
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
data: [],
|
||||||
|
textStyle: {
|
||||||
|
color: "#ffffff",
|
||||||
|
fontSize: 16,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
top: "25%",
|
||||||
|
left: "0%",
|
||||||
|
right: "0%",
|
||||||
|
bottom: "0%",
|
||||||
|
containLabel: true,
|
||||||
|
},
|
||||||
|
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 formState = ref({
|
||||||
|
Cumulant: 1,
|
||||||
|
Type: 2,
|
||||||
|
Points: [],
|
||||||
|
Start_date: dayjs().format("YYYY-MM-DD"),
|
||||||
|
Start_time: dayjs().format("HH:00"),
|
||||||
|
End_date: dayjs().format("YYYY-MM-DD"),
|
||||||
|
End_time: dayjs().format("HH:00"),
|
||||||
|
Device_list: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
const updateTimeRange = (hours) => {
|
||||||
|
const now = dayjs();
|
||||||
|
const startTime = now.subtract(hours, "hour");
|
||||||
|
formState.value.Start_date = startTime.format("YYYY-MM-DD");
|
||||||
|
formState.value.Start_time = startTime.format("HH:00");
|
||||||
|
formState.value.End_date = now.format("YYYY-MM-DD");
|
||||||
|
formState.value.End_time = now.format("HH:00");
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSearch = async () => {
|
||||||
|
loading.value = true;
|
||||||
|
const res = await getHistoryData(formState.value);
|
||||||
|
if (res.isSuccess) {
|
||||||
|
if (res.data.items.length > 0) {
|
||||||
|
chartData.value = res.data.items
|
||||||
|
.map((d) => ({
|
||||||
|
timestamp: d.timestamp,
|
||||||
|
value: parseFloat(d.value),
|
||||||
|
}))
|
||||||
|
.sort((a, b) => new Date(a.timestamp) - new Date(b.timestamp));
|
||||||
|
|
||||||
|
// 更新圖表
|
||||||
|
await nextTick();
|
||||||
|
if (forge_chart.value?.chart) {
|
||||||
|
forge_chart.value.chart.setOption({
|
||||||
|
xAxis: {
|
||||||
|
data: chartData.value.map((d) => d.timestamp),
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: props.data?.value.full_name,
|
||||||
|
type: "line",
|
||||||
|
data: chartData.value.map((d) => d.value),
|
||||||
|
showSymbol: false,
|
||||||
|
itemStyle: {
|
||||||
|
color: SECOND_CHART_COLOR[0], // 使用預設顏色
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
chartData.value = [];
|
||||||
|
if (forge_chart.value?.chart) {
|
||||||
|
forge_chart.value.chart.clear(); // 清空圖表
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.error("API Error:", res.msg);
|
||||||
|
}
|
||||||
|
loading.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getPoint = async (Device_list, Cumulant) => {
|
||||||
|
const res = await getHistoryPoints({ Device_list, Cumulant });
|
||||||
|
pointsList.value = res.data.map((d, index) => ({
|
||||||
|
...d,
|
||||||
|
name: d.item_name,
|
||||||
|
key: d.points,
|
||||||
|
}));
|
||||||
|
if (pointsList.value.length > 0) {
|
||||||
|
formState.value.Points = pointsList.value[0].points;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
getPoint(["CHY_F1_EE_E4_U1F_NA_CPM12D_N1"], 1);
|
||||||
|
if (timeList.value.length > 0) {
|
||||||
|
formState.value.time = timeList.value[0].value;
|
||||||
|
updateTimeRange(timeList.value[0].value);
|
||||||
|
}
|
||||||
|
onSearch();
|
||||||
|
});
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
formState.value = {};
|
||||||
|
chartData.value = [];
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.meter?.main_id,
|
||||||
|
async (main_id, prev_main_id) => {
|
||||||
|
if (!main_id) {
|
||||||
|
meterDetails.value = {};
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 如果 main_id 沒有變化則不呼叫 API
|
||||||
|
if (main_id === prev_main_id) return;
|
||||||
|
const res = await getAssetSingle(main_id);
|
||||||
|
if (res.isSuccess) {
|
||||||
|
meterDetails.value = {
|
||||||
|
...res.data,
|
||||||
|
key: res.data.main_id,
|
||||||
|
floor:
|
||||||
|
props.floors.find(
|
||||||
|
({ floor_guid }) => res.data.floor_guid === floor_guid
|
||||||
|
)?.full_name || "",
|
||||||
|
company:
|
||||||
|
props.companyOptions.find(({ id }) => res.data.operation_id === id)
|
||||||
|
?.name || "",
|
||||||
|
contact_person:
|
||||||
|
props.companyOptions.find(({ id }) => res.data.operation_id === id)
|
||||||
|
?.contact_person || "",
|
||||||
|
buying_date: res.data.buying_date
|
||||||
|
? dayjs(res.data.buying_date).format("YYYY-MM-DD")
|
||||||
|
: "",
|
||||||
|
created_at: res.data.created_at
|
||||||
|
? dayjs(res.data.created_at).format("YYYY-MM-DD")
|
||||||
|
: "",
|
||||||
|
};
|
||||||
|
|
||||||
|
formState.value.Device_list = res.data.device_number || [];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
);
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div class="card bg-slate-200 text-accent-content rounded-md w-60">
|
<div class="card bg-slate-200 text-accent-content rounded-md w-96">
|
||||||
<div class="card-body p-3">
|
<div class="card-body p-3">
|
||||||
<h2 class="card-title">{{ meter.name }}</h2>
|
<h2 class="card-title">{{ meter.name }}</h2>
|
||||||
<div class="shadow-inner shadow-slate-400 rounded-md p-2">
|
<div
|
||||||
<ul class="leading-7 tracking-wider text-slate-700 px-2">
|
role="tablist"
|
||||||
<li><b>電流:</b> {{ meter.current }}</li>
|
class="tabs tabs-boxed tabs-sm bg-opacity-50 shadow-inner shadow-slate-600"
|
||||||
<li><b>電壓:</b> {{ meter.voltage }}</li>
|
>
|
||||||
<li><b>功率:</b> {{ meter.power }}</li>
|
<a
|
||||||
<li><b>用電量:</b> {{ meter.energyConsumption }}</li>
|
v-for="tab in tabs"
|
||||||
</ul>
|
:key="tab"
|
||||||
|
role="tab"
|
||||||
|
class="tab"
|
||||||
|
:class="{
|
||||||
|
'tab-active !bg-green-500 !text-white shadow-sm shadow-slate-800':
|
||||||
|
activeTab === tab,
|
||||||
|
}"
|
||||||
|
@click.prevent="activeTab = tab"
|
||||||
|
>
|
||||||
|
{{ tab }}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="p-0">
|
||||||
|
<div v-if="activeTab === '即時資訊'">
|
||||||
|
<ul class="leading-7 tracking-wider text-slate-700 px-2">
|
||||||
|
<li><b>電流:</b> {{ meter.current }}</li>
|
||||||
|
<li><b>電壓:</b> {{ meter.voltage }}</li>
|
||||||
|
<li><b>功率:</b> {{ meter.power }}</li>
|
||||||
|
<li><b>用電量:</b> {{ meter.energyConsumption }}</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="activeTab === '設備資訊'">
|
||||||
|
<table class="table table-sm w-full text-slate-600">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td class="font-bold">設備編號</td>
|
||||||
|
<td>{{ meterDetails?.device_number || "—" }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="font-bold">設備名稱</td>
|
||||||
|
<td>
|
||||||
|
{{ meterDetails?.full_name || meter.name || "—" }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="font-bold">資產編號</td>
|
||||||
|
<td>{{ meterDetails?.asset_number || "—" }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="font-bold">設備位置</td>
|
||||||
|
<td>{{ meterDetails?.floor || "—" }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="font-bold">圖面標識</td>
|
||||||
|
<td>{{ meterDetails?.device_coordinate || "—" }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="font-bold">品牌 / 型號</td>
|
||||||
|
<td>
|
||||||
|
{{ meterDetails?.brand || "—" }} /
|
||||||
|
{{ meterDetails?.device_model || "—" }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="font-bold">廠商 / 聯絡人</td>
|
||||||
|
<td>
|
||||||
|
{{ meterDetails?.company || "—" }} /
|
||||||
|
{{ meterDetails?.contact_person || "—" }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="font-bold">購買日期</td>
|
||||||
|
<td>{{ meterDetails?.buying_date || "—" }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="font-bold">建立時間</td>
|
||||||
|
<td>{{ meterDetails?.created_at || "—" }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="activeTab === '趨勢查詢'">
|
||||||
|
<div class="flex items-center gap-4">
|
||||||
|
<Select
|
||||||
|
:value="formState"
|
||||||
|
class=""
|
||||||
|
selectClass="select-sm text-sm text-white bg-gray-500/80 shadow-inner shadow-slate-500 border-info focus-within:border-info"
|
||||||
|
name="Points"
|
||||||
|
Attribute="name"
|
||||||
|
:options="pointsList"
|
||||||
|
></Select>
|
||||||
|
<Select
|
||||||
|
:value="formState"
|
||||||
|
class=""
|
||||||
|
selectClass="select-sm text-sm text-white bg-gray-500/80 shadow-inner shadow-slate-500 border-info focus-within:border-info"
|
||||||
|
name="time"
|
||||||
|
Attribute="name"
|
||||||
|
:options="timeList"
|
||||||
|
@change="(value) => updateTimeRange(value)"
|
||||||
|
></Select>
|
||||||
|
<button
|
||||||
|
class="btn btn-sm btn-success"
|
||||||
|
@click.stop.prevent="onSearch"
|
||||||
|
>
|
||||||
|
<font-awesome-icon :icon="['fas', 'search']" class="" />搜尋
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="min-h-[300px] relative">
|
||||||
|
<span
|
||||||
|
v-if="loading"
|
||||||
|
className="loading loading-spinner loading-lg text-info absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 z-20"
|
||||||
|
></span>
|
||||||
|
<LineChart
|
||||||
|
v-if="chartData.length > 0"
|
||||||
|
id="forge_chart"
|
||||||
|
class="min-h-[300px] max-h-fit"
|
||||||
|
:option="defaultChartOption"
|
||||||
|
ref="forge_chart"
|
||||||
|
/>
|
||||||
|
<p
|
||||||
|
class="text-center text-base text-gray-500 pt-16"
|
||||||
|
v-if="!loading && chartData.length === 0"
|
||||||
|
>
|
||||||
|
沒有資料
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,8 +1,15 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
|
import { ref } from "vue";
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
vessel: Object,
|
vessel: Object,
|
||||||
tabs: Array,
|
|
||||||
});
|
});
|
||||||
|
const tabs = [
|
||||||
|
{ label: "生產資訊" },
|
||||||
|
{ label: "投料進度" },
|
||||||
|
{ label: "流量計" },
|
||||||
|
{ label: "SIP" },
|
||||||
|
];
|
||||||
|
const activeTab = ref(tabs[0].label);
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div class="card bg-slate-200 text-accent-content rounded-md w-[25rem]">
|
<div class="card bg-slate-200 text-accent-content rounded-md w-[25rem]">
|
||||||
@ -19,15 +26,15 @@ const props = defineProps({
|
|||||||
class="tab"
|
class="tab"
|
||||||
:class="{
|
:class="{
|
||||||
'tab-active !bg-green-500 !text-white shadow-sm shadow-slate-800':
|
'tab-active !bg-green-500 !text-white shadow-sm shadow-slate-800':
|
||||||
vessel.activeTab.value === tab.label,
|
activeTab === tab.label,
|
||||||
}"
|
}"
|
||||||
@click.prevent="vessel.activeTab.value = tab.label"
|
@click.prevent="activeTab = tab.label"
|
||||||
>
|
>
|
||||||
{{ tab.label }}
|
{{ tab.label }}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="p-0">
|
<div class="p-0">
|
||||||
<div v-if="vessel.activeTab.value === '生產資訊'">
|
<div v-if="activeTab === '生產資訊'">
|
||||||
<ul class="leading-7 tracking-wider text-slate-700 px-2">
|
<ul class="leading-7 tracking-wider text-slate-700 px-2">
|
||||||
<li><b>品名:</b> {{ vessel.productInfo?.product }}</li>
|
<li><b>品名:</b> {{ vessel.productInfo?.product }}</li>
|
||||||
<li><b>鍋次:</b> 第{{ vessel.productInfo?.batch || "__" }}鍋</li>
|
<li><b>鍋次:</b> 第{{ vessel.productInfo?.batch || "__" }}鍋</li>
|
||||||
@ -35,7 +42,7 @@ const props = defineProps({
|
|||||||
<li><b>狀態:</b> {{ vessel.productInfo?.status }}</li>
|
<li><b>狀態:</b> {{ vessel.productInfo?.status }}</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="vessel.activeTab.value === '投料進度'">
|
<div v-else-if="activeTab === '投料進度'">
|
||||||
<div class="h-40 overflow-x-auto">
|
<div class="h-40 overflow-x-auto">
|
||||||
<table class="table table-sm table-pin-rows">
|
<table class="table table-sm table-pin-rows">
|
||||||
<thead>
|
<thead>
|
||||||
@ -67,7 +74,7 @@ const props = defineProps({
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="vessel.activeTab.value === '流量計'">
|
<div v-else-if="activeTab === '流量計'">
|
||||||
<div class="h-40 overflow-x-auto">
|
<div class="h-40 overflow-x-auto">
|
||||||
<table class="table table-sm table-pin-rows ">
|
<table class="table table-sm table-pin-rows ">
|
||||||
<thead>
|
<thead>
|
||||||
@ -95,7 +102,7 @@ const props = defineProps({
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="vessel.activeTab.value === 'SIP'">
|
<div v-else-if="activeTab === 'SIP'">
|
||||||
<div class="h-40 overflow-x-auto">
|
<div class="h-40 overflow-x-auto">
|
||||||
<table class="table table-sm table-pin-rows">
|
<table class="table table-sm table-pin-rows">
|
||||||
<thead>
|
<thead>
|
||||||
|
Loading…
Reference in New Issue
Block a user