調整儀表板與電表卡片的顯示邏輯 | 新增電表卡片即時數據獲取與圖表顯示功能

This commit is contained in:
koko 2025-09-04 14:04:17 +08:00
parent cea34c2e9d
commit babef0e2ae
8 changed files with 490 additions and 128 deletions

View File

@ -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(

View File

@ -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 {

View File

@ -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">

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>