cola-web/src/views/Dashboard/index.vue

1263 lines
50 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="dashboard-container">
<div class="upper-section">
<div class="upper-left">
<div class="left-1">
<div class="block">
<div class="flex-row">
<h2 class="juice-title">果汁调配</h2>
<span class="flow-label">累计流量{{ formatNumberWithCommas(totalTrafficJuice) }}</span>
</div>
<div class="spacing"></div>
<div v-for="item in progressList" :key="item.name" class="juice-item"
:data-device-id="item?.deviceId"
@click="handleBlockClick(item?.deviceId, item.aliasName, false)">
<span class="juice-name">{{ item.name }}</span>
<div class="progress-bar" style="cursor: pointer;">
<div class="progress"
:style="{ width: item.rate + '%', background: filterStatusColor(item.deviceStatus) }">
<span class="progress-text">{{ formatNumberWithCommas(item.value) }}</span>
</div>
</div>
<img style="padding-left: 1rem;" v-if="item.rate < 10" src="@/assets/alarm.svg" />
</div>
</div>
<div class="block">
<div class="flex-row">
<h2 class="juice-title">果肉调配</h2>
<span class="flow-label">累计流量:{{ formatNumberWithCommas(totalTrafficPulp) }}</span>
</div>
<div class="spacing"></div>
<div v-for="item in innerProgressList" :key="item.name" class="juice-item"
:data-device-id="item?.deviceId"
@click="handleBlockClick(item?.deviceId, item.aliasName, false)">
<span class="juice-name">{{ item.name }}</span>
<div class="progress-bar" style="cursor: pointer;">
<div class="progress"
:style="{ width: item.rate + '%', background: filterStatusColor(item.deviceStatus) }">
<span class="progress-text">{{ formatNumberWithCommas(item.value) }}</span>
</div>
</div>
<img style="padding-left: 1rem;" v-if="item.rate > 90" src="@/assets/alarm.svg" />
</div>
</div>
</div>
<div class="left-2">
<div class="block">
<div class="flex-row" :data-device-id="uhtData?.deviceId"
@click="handleBlockClick(uhtData?.deviceId, uhtData.name, true)" style="cursor: pointer;">
<h2 class="juice-title">果汁 UHT</h2>
</div>
<div class="spacing"></div>
<div class="UHT-item">
<div class="info-panel" style="display: flex; justify-content: space-between;width: 44%;">
<div class="info-row">
<span class="info-label">当前配方:</span>
<span class="info-value">{{ processForm_uht.formula }}</span>
</div>
<div class="info-row">
<span class="info-label">当前状态:</span>
<span class="info-value">{{ processForm_uht.deviceStatus }}</span>
</div>
<div class="info-row">
<span class="info-label">当前步骤:</span>
<span class="info-value">{{ processForm_uht.mixerStep }}</span>
</div>
</div>
<div class="info-panel" style="display: flex; justify-content: space-between;width: 25%">
<div class="info-row">
<span class="info-label">累计流量:</span>
<span class="info-value">{{ formatNumberWithCommas(processForm_uht.totalTraffic) }}</span>
</div>
<div class="info-row">
<span class="info-label">产品流量:</span>
<span class="info-value">{{ formatNumberWithCommas(processForm_uht.productFlowRate) }}</span>
</div>
<div class="info-row">
<span class="info-label">平衡温度:</span>
<span class="info-value">{{ processForm_uht.temperature }}</span>
</div>
</div>
<div class="tag-panel">
<el-tag class="left-chip" effect="dark"
:style="{ 'background-color': processForm_uht.cleanStatus === '已碱洗' ? '#00BEDC' : '#6B6B7E' }"
round>
已碱洗
</el-tag>
<el-tag class="left-chip" effect="dark"
:style="{ 'background-color': processForm_uht.cleanStatus === '已酸洗' ? '#00BEDC' : '#6B6B7E' }"
round>
已酸洗
</el-tag>
<el-tag class="left-chip" effect="dark"
:style="{ 'background-color': processForm_uht.cleanStatus === '清洁状态' ? '#00BEDC' : '#6B6B7E' }"
round>
清洁状态
</el-tag>
<el-tag class="left-chip" effect="dark"
:style="{ 'background-color': processForm_uht.cleanStatus === '无菌状态' ? '#00BEDC' : '#6B6B7E' }"
round>
无菌状态
</el-tag>
<!-- <IxChip class="left-chip"
:background="processForm_uht.cleanStatus === '已碱洗' ? '#00BEDC' : '#6B6B7E'"
chip-color="#000028" variant="custom">
&emsp;&nbsp;已碱洗
</IxChip>
<IxChip class="left-chip"
:background="processForm_uht.cleanStatus === '已酸洗' ? '#00BEDC' : '#6B6B7E'"
chip-color="#000028" variant="custom">
&emsp;&nbsp;已酸洗
</IxChip>
<IxChip class="left-chip"
:background="processForm_uht.cleanStatus === '清洁状态' ? '#00BEDC' : '#6B6B7E'"
chip-color="#000028" variant="custom">
&emsp;清洁状态
</IxChip>
<IxChip class="left-chip"
:background="processForm_uht.cleanStatus === '无菌状态' ? '#00BEDC' : '#6B6B7E'"
chip-color="#000028" variant="custom">
&emsp;无菌状态
</IxChip> -->
</div>
</div>
</div>
<div class="block">
<div class="flex-row" :data-device-id="pulpUHTData?.deviceId"
@click="handleBlockClick(pulpUHTData?.deviceId, pulpUHTData.name, true)"
style="cursor: pointer;">
<h2 class="juice-title">果肉 UHT</h2>
</div>
<div class="spacing"></div>
<div class="UHT-item">
<div class="info-panel" style="display: flex; justify-content: space-between;width: 44%;">
<div class="info-row">
<span class="info-label">当前配方:</span>
<span class="info-value">{{ processForm_pulp.formula }}</span>
</div>
<div class="info-row">
<span class="info-label">当前状态:</span>
<span class="info-value">{{ processForm_pulp.deviceStatus }}</span>
</div>
<div class="info-row">
<span class="info-label">当前步骤:</span>
<span class="info-value">{{ processForm_pulp.mixerStep }}</span>
</div>
</div>
<div class="info-panel" style="display: flex; justify-content: space-between;width: 25%;">
<div class="info-row">
<span class="info-label">累计流量:</span>
<span class="info-value">{{ formatNumberWithCommas(processForm_pulp.totalTraffic) }}</span>
</div>
<div class="info-row">
<span class="info-label">产品流量:</span>
<span class="info-value">{{ formatNumberWithCommas(processForm_pulp.productFlowRate) }}</span>
</div>
<div class="info-row">
<span class="info-label">平衡温度:</span>
<span class="info-value">{{ processForm_pulp.temperature }}</span>
</div>
</div>
<div class="tag-panel">
<el-tag class="left-chip" effect="dark"
:style="{ 'background-color': processForm_pulp.cleanStatus === '已碱洗' ? '#00FFB9' : '#6B6B7E' }"
round>
已碱洗
</el-tag>
<el-tag class="left-chip" effect="dark"
:style="{ 'background-color': processForm_pulp.cleanStatus === '已酸洗' ? '#00FFB9' : '#6B6B7E' }"
round>
已酸洗
</el-tag>
<el-tag class="left-chip" effect="dark"
:style="{ 'background-color': processForm_pulp.cleanStatus === '清洁状态' ? '#00FFB9' : '#6B6B7E' }"
round>
清洁状态
</el-tag>
<el-tag class="left-chip" effect="dark"
:style="{ 'background-color': processForm_pulp.cleanStatus === '无菌状态' ? '#00FFB9' : '#6B6B7E' }"
round>
无菌状态
</el-tag>
<!-- <IxChip class="left-chip" :background="processForm_pulp.cleanStatus === '已碱洗' ? '#00FFB9' : '#6B6B7E'" chip-color="#000028" variant="custom">
&emsp;&nbsp;已碱洗
</IxChip>
<IxChip class="left-chip" :background="processForm_pulp.cleanStatus === '已酸洗' ? '#00FFB9' : '#6B6B7E'" chip-color="#000028" variant="custom">
&emsp;&nbsp;已酸洗
</IxChip>
<IxChip class="left-chip" :background="processForm_pulp.cleanStatus === '清洁状态' ? '#00FFB9' : '#6B6B7E'" chip-color="#000028" variant="custom">
&emsp;清洁状态
</IxChip>
<IxChip class="left-chip" :background="processForm_pulp.cleanStatus === '无菌状态' ? '#00FFB9' : '#6B6B7E'" chip-color="#000028" variant="custom">
&emsp;无菌状态
</IxChip> -->
</div>
</div>
</div>
</div>
</div>
<div class="upper-right">
<div class="block">
<div class="flex-row">
<h2 class="juice-title">果汁无菌罐</h2>
<div class="tag-panel-2">
<el-tag class="right-chip" effect="dark"
:style="{ 'background-color': juiceTank.cleanStatus === '清洁状态' ? '#00FFB9' : '#6B6B7E'}"
round>
清洁状态
</el-tag>
<el-tag class="right-chip" effect="dark"
:style="{ 'background-color': juiceTank.cleanStatus === '无菌状态' ? '#00FFB9' : '#6B6B7E'}"
round>
无菌状态
</el-tag>
<!-- <IxChip class="right-chip"
:background="juiceTank.cleanStatus === '清洁状态' ? '#00FFB9' : '#6B6B7E'"
chip-color="#000028" variant="custom" :style="{ cursor: 'default' }">
&emsp;&nbsp;清洁状态
</IxChip>
<IxChip class="right-chip"
:background="juiceTank.cleanStatus === '无菌状态' ? '#00FFB9' : '#6B6B7E'"
chip-color="#000028" variant="custom">
&emsp;&nbsp;无菌状态
</IxChip> -->
</div>
</div>
<div class="spacing"></div>
<div class="right-info-panel" style="padding-top: 1rem;">
<div class="info-item">
<span class="info-label">当前步骤:</span>
<span class="info-label">{{ juiceTank.mixerStep }}</span>
</div>
<div class="juice-item" style="padding: 0 0 0 1rem !important;">
<span class="info-label">当前液位:</span>
<div class="progress-bar">
<div class="progress"
:style="{ width: juiceTank.rate + '%', background: juiceTank.rate > 70 ? '#00D2A0' : juiceTank.rate > 50 ? '#FF9000' : juiceTank.rate > 30 ? '#FFD732' : '#FF0000' }">
<span class="progress-text">{{ formatNumberWithCommas(juiceTank.liquidLevel) }}</span>
</div>
</div>
</div>
<div class="info-item" style="padding: 1rem 0 0 0;">
<span class="info-label" :style="{ color: '#00FFB9' }">▼果汁流量:{{ formatNumberWithCommas(juiceTank.productFlowRate)
}}</span>
</div>
</div>
</div>
<div class="block">
<div class="flex-row">
<h2 class="juice-title">动态混合器</h2>
<div class="tag-panel-2">
<el-tag class="right-chip" effect="dark"
:style="{ 'background-color': dynamicMixer.cleanStatus === '清洁状态' ? '#00FFB9' : '#6B6B7E'}"
round>
清洁状态
</el-tag>
<el-tag class="right-chip" effect="dark"
:style="{ 'background-color': dynamicMixer.cleanStatus === '无菌状态' ? '#00FFB9' : '#6B6B7E'}"
round>
无菌状态
</el-tag>
<!-- <IxChip class="right-chip"
:background="dynamicMixer.cleanStatus === '清洁状态' ? '#00FFB9' : '#6B6B7E'"
chip-color="#000028" variant="custom">
&emsp;&nbsp;清洁状态
</IxChip>
<IxChip class="right-chip"
:background="dynamicMixer.cleanStatus === '无菌状态' ? '#00FFB9' : '#6B6B7E'"
chip-color="#000028" variant="custom">
&emsp;&nbsp;无菌状态
</IxChip> -->
</div>
</div>
<div class="spacing"></div>
<div class="UHT-item">
<div class="info-panel">
<div class="info-row">
<span class="info-label">当前配方:</span>
<span class="info-value">{{ dynamicMixer.formula }}</span>
</div>
<div class="info-row">
<span class="info-label">当前步骤:</span>
<span class="info-value">{{ dynamicMixer.mixerStep }}</span>
</div>
</div>
<div class="info-panel">
<div class="info-row">
<span class="info-label">当前液位:</span>
<span class="info-value">{{ formatNumberWithCommas(dynamicMixer.liquidLevel) }}</span>
</div>
<div class="info-row">
<span class="info-label">当前流量:</span>
<span class="info-value">{{ formatNumberWithCommas(dynamicMixer.productFlowRate) }}</span>
</div>
</div>
</div>
</div>
<div class="block">
<div class="info-item" style="padding: 0.5rem 0 0 0;">
<span class="info-label" :style="{ color: '#00FFB9' }">▲果肉流量:{{ formatNumberWithCommas(pulpTank.productFlowRate)
}}</span>
</div>
<div class="flex-row">
<h2 class="juice-title">果肉无菌罐</h2>
<div class="tag-panel-2">
<el-tag class="right-chip" effect="dark"
:style="{ 'background-color': pulpTank.cleanStatus === '清洁状态' ? '#00FFB9' : '#6B6B7E'}"
round>
清洁状态
</el-tag>
<el-tag class="right-chip" effect="dark"
:style="{ 'background-color': pulpTank.cleanStatus === '无菌状态' ? '#00FFB9' : '#6B6B7E'}"
round>
无菌状态
</el-tag>
<!-- <IxChip class="right-chip"
:background="pulpTank.cleanStatus === '清洁状态' ? '#00FFB9' : '#6B6B7E'"
chip-color="#000028" variant="custom">
&emsp;&nbsp;清洁状态
</IxChip>
<IxChip class="right-chip"
:background="pulpTank.cleanStatus === '无菌状态' ? '#00FFB9' : '#6B6B7E'"
chip-color="#000028" variant="custom">
&emsp;&nbsp;无菌状态
</IxChip> -->
</div>
</div>
<div class="spacing"></div>
<div class="info-item">
<span class="info-label">当前步骤:</span>
<span class="info-label">{{ pulpTank.mixerStep }}</span>
</div>
<div class="juice-item">
<span>当前液位:</span>
<div class="progress-bar">
<div class="progress"
:style="{ width: pulpTank.rate + '%', background: pulpTank.rate > 70 ? '#00D2A0' : pulpTank.rate > 50 ? '#FF9000' : pulpTank.rate > 30 ? '#FFD732' : '#FF0000' }">
<span class="progress-text">{{ formatNumberWithCommas(pulpTank.liquidLevel) }}</span>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="lower-section">
<div class="block">
<div class="flex-row-footer">
<h2 class="juice-title">{{ currentTitle }}</h2>
<div style="padding: 0.5rem 2rem 0 2rem;">
<!-- <IxButton Outline @click="changeTimeRange('all')">全部</IxButton>
<IxButton Outline @click="changeTimeRange('1h')">一小时</IxButton>
<IxButton Outline @click="changeTimeRange('2h')">二小时</IxButton>
<IxButton Outline @click="changeTimeRange('shift')">当班</IxButton> -->
<IxTabs :selected="selectedTab">
<IxTabItem @click="changeTimeRange('all')">全部</IxTabItem>
<IxTabItem @click="changeTimeRange('1h')">一小时</IxTabItem>
<IxTabItem @click="changeTimeRange('2h')">二小时</IxTabItem>
<IxTabItem @click="changeTimeRange('shift')">当班</IxTabItem>
</IxTabs>
</div>
</div>
<div class="spacing"></div>
<ProcessGanttChart ref="ganttChart" :process-data="formattedProcessData" :current-range="currentRange"
:end-time="globalTime" @segment-click="handleSegmentClick" />
</div>
</div>
<div class="lower-footer" v-if="deviceType">
<IxButton Outline class="btnStyle"> 状态{{ currentStatus }} </IxButton>
<IxButton Outline class="btnStyle"> 生产步骤{{ mixerStep }} </IxButton> <!-- 增加 step 字段显示 -->
<IxButton Outline class="btnStyle"> 开始时间{{ startTimeFormatted }} </IxButton>
<IxButton Outline class="btnStyle"> 结束时间{{ endTimeFormatted }} </IxButton>
<IxButton Outline class="btnStyle"> 流量{{ formatNumberWithCommas(productFlowRate) }} </IxButton>
<IxButton Outline class="btnStyle"> 配方{{ formula }} </IxButton>
<IxButton Outline class="btnStyle"> 持续时长{{ duration }} </IxButton>
<IxButton Outline id="triggerId" :disabled="currentStatus !== '停机'"> {{ selectedReason ? '停机原因:' +
selectedReason : '请选择停机原因 ▲' }} </IxButton>
<IxDropdown trigger="triggerId" class="drop-down">
<IxDropdownHeader label="停机原因"></IxDropdownHeader>
<IxDropdownItem v-for="reason in stopReasons" :key="reason" :label="reason"
@click="handleReasonChange(id, reason)"></IxDropdownItem>
<IxDivider></IxDivider>
<IxDropdownItem label="其他" @click="handleOtherReasonClick"></IxDropdownItem>
<div v-if="showOtherReasonInput" class="other-reason-input" @click.stop>
<input type="text" v-model="otherReason" placeholder="请输入其他原因" />
<IxButton @click="handleReasonConfirm(id)">确认</IxButton>
</div>
</IxDropdown>
</div>
<div class="lower-footer alternate" v-else>
<IxButton Outline class="btnStyle"> 状态:{{ currentStatus }} </IxButton>
<IxButton Outline class="btnStyle"> 开始时间:{{ startTimeFormatted }} </IxButton>
<IxButton Outline class="btnStyle"> 结束时间:{{ endTimeFormatted }} </IxButton>
<IxButton Outline class="btnStyle"> 持续时长:{{ duration }} </IxButton>
<IxButton Outline class="btnStyle"> 调配状态:{{ blendStatus }} </IxButton>
<IxButton Outline class="btnStyle"> 罐重{{ formatNumberWithCommas(capacity) }} </IxButton>
</div>
</div>
</template>
<script setup>
import moment from 'moment';
import momentTimezone from 'moment-timezone';
import { ref, onMounted, getCurrentInstance, onUnmounted, defineEmits } from 'vue'
import { IxChip, IxButton, IxDropdown, IxDropdownItem, IxDropdownHeader, IxDivider, IxTabItem, IxTabs } from '@siemens/ix-vue'; // 引入 Chip 组件
import { getCurrentReport, getHistoryReport, getGanttData, getStopReason, setStopReason, getStatusColor } from '@/api/dashboard.js';
import ProcessGanttChart from '@/components/ProcessGanttChart.vue'
import { useAlarmStore } from '@/stores/alarmStore'; // 引入 alarmStore
import { color } from 'echarts';
const emit = defineEmits(['send-data']);
const sendDataToParent = () => {
emit('send-data', 'SIEMENS MIS 1.0 前处理界面', 'mis-app');
};
// 设置为您期望的时区,比如 "Asia/Shanghai"
const timezone = 'Asia/Shanghai';
// 响应式数据
// 原始数据
const juiceData = ref({}) // 果汁调配数据
const pulpData = ref({}) // 果肉调配数据
const uhtData = ref({}) // 果汁杀菌数据
const pulpUHTData = ref({}) // 果肉杀菌数据
const juiceTankData = ref({}) // 果汁无菌罐数据
const pulpTankData = ref({}) // 果肉无菌罐数据
const dynamicMixerData = ref({}) // 动态混合器数据
// const currentTime = ref('')
const totalTrafficJuice = ref(0); // 果汁调配 累计流量
const totalTrafficPulp = ref(0); // 果汁调配 累计流量
const progressList = ref([])// 果汁调配 进度条
const innerProgressList = ref([])// 果肉调配 进度条
const processForm_uht = ref({})// 果汁UHT
const processForm_pulp = ref({})// 果肉UHT
const juiceTank = ref({})// 果汁无菌罐
const pulpTank = ref({})// 果肉无菌罐
const dynamicMixer = ref({})// 动态混合器
const statuses = ref([])
const processData = ref([])
// 新增函数,转换时间格式
const formatTime = (timeString) => {
const momentDate = moment(timeString);
return momentDate.tz(timezone).format('YYYY-MM-DD HH:mm');
}
// 新增函数,统一日期格式
const formatDates = (data) => {
return data.map(item => {
const newItem = { ...item };
newItem.beginTime = formatTime(item.beginTime);
newItem.endTime = formatTime(item.endTime);
return newItem;
});
}
// 处理后的新数据
const formattedProcessData = ref(formatDates(processData.value));
// 甘特图包含的信息
const id = ref('');
const currentStatus = ref('');
const startTimeFormatted = ref('');
const endTimeFormatted = ref('');
const duration = ref('');
const productFlowRate = ref('');
const formula = ref('');
const mixerStep = ref(''); // 增加 step 字段
const blendStatus = ref('');
const capacity = ref('');
const currentRange = ref('all'); // 增加 currentRange 响应式变量
const ganttChart = ref(null); // 增加 ganttChart 引用
const currentTitle = ref('');
// 全局响应式变量
const globalDeviceId = ref(null);
const globalTime = ref(moment().tz(timezone).format('YYYY-MM-DD HH:mm'));
const isAutoUpdate = ref(true); // 是否自动更新
const deviceType = ref(false);
// 新增响应式变量
const selectedReason = ref(''); // 新增响应式变量
const showOtherReasonInput = ref(false);
const otherReason = ref('');
const stopReasons = ref([]); // 新增响应式变量
const alarmStore = useAlarmStore(); // 使用 alarmStore
const alarmCount = ref(0); // 新增报警计数
// 更新报警计数的函数
const updateAlarmCount = () => {
alarmCount.value = progressList.value.filter(item => item.rate < 10).length +
innerProgressList.value.filter(item => item.rate > 90).length;
alarmStore.setAlarmCount(alarmCount.value); // 更新 alarmStore 中的报警计数
};
const formatNumberWithCommas = (number) => {
if (number === null || number === undefined) return '--';
return number.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
};
const updateCurrentInfo = (segment) => {
id.value = segment.id;
currentStatus.value = segment.deviceStatus;
startTimeFormatted.value = formatTime(segment.beginTime);
endTimeFormatted.value = formatTime(segment.endTime);
duration.value = segment.duration;
productFlowRate.value = segment.productFlowRate;
formula.value = segment.formula;
mixerStep.value = segment.mixerStep; // 更新 step 字段
selectedReason.value = segment.stopReason || '';
blendStatus.value = segment.blendStatus;
capacity.value = segment.capacity;
};
const handleSegmentClick = (segment) => {
updateCurrentInfo(segment);
};
const changeTimeRange = (range) => {
currentRange.value = range;
if (ganttChart.value && ganttChart.value.updateChart) {
ganttChart.value.updateChart();
}
};
//#TODO:主数据
// 处理接口返回的数据
const processDataFromAPI = (apiData) => {
const processedData = apiData.data.map(item => {
return {
deviceId: item.deviceId,
lineId: item.lineId,
name: item.name,
recordTime: item.recordTime,
data: {
id: item.data.id || 0,
weight: item.data.weight || 0,
deviceStatus: item.data.deviceStatus || '',
cleanStatus: item.data.cleanStatus || '',
productFlowRate: item.data.productFlowRate || 0,
name: item.data.name || '',
formula: item.data.formula || '',
mixerStep: item.data.mixerStep || '',
temperature: item.data.temperature || 0,
liquidLevel: item.data.liquidLevel || 0,
capacity: item.data.capacity,
aliasName: item.data.aliasName || '',
},
statusList: item.statusList || []
};
});
return processedData;
};
const processHistoryDataFromAPI = processDataFromAPI; // 复用 processDataFromAPI 函数
const getDataByName = (data, name) => {
const filteredData = data.filter(item => item.name === name);
if (filteredData.length === 0) {
return {
name,
deviceId: null,
totalTraffic: 0,
data: []
};
}
const mergedData = filteredData.map(item => ({
...item.data,
deviceId: item.deviceId // 增加 deviceId 字段
}));
return {
name,
deviceId: filteredData[0].deviceId,
totalTraffic: mergedData.reduce((acc, item) => acc + (item.weight || 0), 0),
data: mergedData
};
};
const getUHTDataByName = (data, name) => {
const filteredData = data.filter(item => item.name === name);
if (filteredData.length === 0) {
return {
name,
deviceId: null,
totalTraffic: 0,
data: []
};
}
const mergedData = filteredData.map(item => ({
...item.data,
deviceId: item.deviceId // 增加 deviceId 字段
}));
return {
name,
deviceId: filteredData[0].deviceId,
totalTraffic: mergedData.reduce((acc, item) => acc + (item.productFlowRate || 0), 0),
data: mergedData
};
};
const updateData = (processedData) => {
juiceData.value = getDataByName(processedData, "果汁调配");
pulpData.value = getDataByName(processedData, "果肉调配");
uhtData.value = getDataByName(processedData, "果汁杀菌");
pulpUHTData.value = getUHTDataByName(processedData, "果肉杀菌");
juiceTankData.value = getUHTDataByName(processedData, "果汁无菌罐");
pulpTankData.value = getUHTDataByName(processedData, "果肉无菌罐");
dynamicMixerData.value = getUHTDataByName(processedData, "动态混合器");
// 设置默认值
if (juiceData.value.deviceId) {
globalDeviceId.value = juiceData.value.deviceId;
currentTitle.value = juiceData.value.data[0].aliasName;
}
// 累计流量
totalTrafficJuice.value = juiceData.value.totalTraffic.toFixed(2);
totalTrafficPulp.value = pulpData.value.totalTraffic.toFixed(2);
if (juiceData.value.data.length > 0) {
progressList.value = juiceData.value.data.map(item => ({
name: item.name,
aliasName: item.aliasName,
deviceId: item.deviceId,
deviceStatus: item.deviceStatus,
value: item.weight,
rate: item.weight === 0 ? 0 : (item.weight / item.capacity) * 100
}));
}
if (pulpData.value.data.length > 0) {
innerProgressList.value = pulpData.value.data.map(item => ({
name: item.name,
aliasName: item.aliasName,
deviceId: item.deviceId,
deviceStatus: item.deviceStatus,
value: item.weight,
rate: item.weight === 0 ? 0 : (item.weight / item.capacity) * 100
}));
}
// 更新 UHT 数据
if (uhtData.value.data.length > 0) {
processForm_uht.value = {
name: uhtData.value.name,
formula: uhtData.value.data[0].formula,
totalTraffic: uhtData.value.totalTraffic,
deviceStatus: uhtData.value.data[0].deviceStatus,
productFlowRate: uhtData.value.data[0].productFlowRate,
mixerStep: uhtData.value.data[0].mixerStep,
temperature: uhtData.value.data[0].temperature,
cleanStatus: uhtData.value.data[0].cleanStatus // 增加 cleanStatus 字段
};
}
if (pulpUHTData.value.data.length > 0) {
processForm_pulp.value = {
name: pulpUHTData.value.name,
formula: pulpUHTData.value.data[0].formula,
totalTraffic: pulpUHTData.value.totalTraffic,
deviceStatus: pulpUHTData.value.data[0].deviceStatus,
productFlowRate: pulpUHTData.value.data[0].productFlowRate,
mixerStep: pulpUHTData.value.data[0].mixerStep,
temperature: pulpUHTData.value.data[0].temperature,
cleanStatus: pulpUHTData.value.data[0].cleanStatus
};
}
// 更新果汁无菌罐数据
if (juiceTankData.value.data.length > 0) {
juiceTank.value = {
mixerStep: juiceTankData.value.data[0].mixerStep,
rate: juiceTankData.value.data[0].liquidLevel === 0 ? 0 : (juiceTankData.value.data[0].liquidLevel / juiceTankData.value.data[0].capacity) * 100,
liquidLevel: juiceTankData.value.data[0].liquidLevel,
productFlowRate: juiceTankData.value.data[0].productFlowRate,
cleanStatus: juiceTankData.value.data[0].cleanStatus
};
}
if (pulpTankData.value.data.length > 0) {
pulpTank.value = {
mixerStep: pulpTankData.value.data[0].mixerStep,
rate: pulpTankData.value.data[0].liquidLevel === 0 ? 0 : (pulpTankData.value.data[0].liquidLevel / pulpTankData.value.data[0].capacity) * 100,
liquidLevel: pulpTankData.value.data[0].liquidLevel,
productFlowRate: pulpTankData.value.data[0].productFlowRate,
cleanStatus: pulpTankData.value.data[0].cleanStatus
};
}
if (dynamicMixerData.value.data.length > 0) {
dynamicMixer.value = {
formula: dynamicMixerData.value.data[0].formula,
status: dynamicMixerData.value.data[0].status,
liquidLevel: dynamicMixerData.value.data[0].liquidLevel,
mixerStep: dynamicMixerData.value.data[0].mixerStep,
temperature: dynamicMixerData.value.data[0].temperature,
productFlowRate: dynamicMixerData.value.data[0].productFlowRate,
cleanStatus: dynamicMixerData.value.data[0].cleanStatus
};
}
if (processedData === undefined) showWarningMessage("未查询到主数据!");
updateAlarmCount(); // 更新报警计数
};
// 读取接口数据
const fetchData = async () => {
try {
const response = await getCurrentReport();
if (response.code === 200) {
const processedData = processDataFromAPI(response);
updateData(processedData);
} else {
console.error('Error fetching data:', response.message);
}
} catch (error) {
console.error('Error fetching data:', error);
}
};
const fetchHistoryData = async (datetime) => {
try {
const response = await getHistoryReport(datetime);
if (response.code === 200) {
const processedData = processHistoryDataFromAPI(response);
updateData(processedData);
} else {
console.error('Error fetching history data:', response.message);
}
} catch (error) {
console.error('Error fetching history data:', error);
}
};
const processGanttDataResponse = (response) => {
if (response.data) {
const ganttData = response.data.map(item => ({
id: item.id,
deviceId: item.deviceId,
duration: item.duration,
beginTime: formatTime(item.beginTime),
endTime: formatTime(item.endTime),
deviceStatus: item.deviceStatus,
mixerStep: item.mixerStep,
productFlowRate: item.productFlowRate,
formula: item.formula,
lineId: item.lineId,
stopReason: item.stopReason,
blendStatus: item.blendStatus,
capacity: item.capacity,
color: filterStatusColor(item.deviceStatus)
}));
processData.value = ganttData;
formattedProcessData.value = formatDates(ganttData);
if (ganttChart.value && ganttChart.value.updateChart) {
ganttChart.value.updateChart();
}
// showInfoMessage('' + ganttData.length + ' 条数据已刷新!');
} else {
showWarningMessage('未查询到甘特图数据!');
processData.value = [];
formattedProcessData.value = [];
if (ganttChart.value && ganttChart.value.updateChart) {
ganttChart.value.updateChart();
}
}
};
const fetchGanttData = async () => {
try {
if (!globalDeviceId.value) {
console.error('Device ID is null');
return;
}
const response = await getGanttData(globalDeviceId.value, globalTime.value);
processGanttDataResponse(response);
} catch (error) {
console.error('Error fetching Gantt data:', error);
}
};
const handleBlockClick = async (deviceId, name, type) => {
try {
if (!deviceId) {
showWarningMessage('Device ID is null');
return;
}
// 增加一个处理当点击切换block时将信息框信息都清空
id.value = '';
currentStatus.value = '';
startTimeFormatted.value = '';
endTimeFormatted.value = '';
duration.value = '';
productFlowRate.value = '';
formula.value = '';
mixerStep.value = ''; // 清空 step 字段
selectedReason.value = '';
blendStatus.value = '';
capacity.value = '';
currentTitle.value = name;
globalDeviceId.value = deviceId;
deviceType.value = type;
const response = await getGanttData(deviceId, globalTime.value);
processGanttDataResponse(response);
} catch (error) {
console.error('Error fetching Gantt data:', error);
}
};
const { proxy } = getCurrentInstance();
const showInfoMessage = (message) => {
proxy.$message.info(message);
};
const showInfoMessageWithAction = (message, action) => {
proxy.$message.$confirm(message, action);
};
const showDangerMessage = (message) => {
proxy.$message.danger(message);
};
const showWarningMessage = (message) => {
proxy.$message.warning(message);
};
const showConfirmMessage = (message, onConfirm, onCancel) => {
proxy.$message.confirm(message, onConfirm, onCancel);
};
const handleReasonChange = (id, reason) => {
if (!startTimeFormatted.value) {
showWarningMessage('请先点击甘特图中的数据块,再设置停机原因!');
return;
}
showConfirmMessage(
'确定要设置停机原因为 "' + reason + '" 吗?',
() => {
selectedReason.value = reason;
setStopReason({
id,
stopDef: reason
}).then(() => {
// Update the segment in processData
const segment = processData.value.find(item => item.id === id);
if (segment) {
segment.stopReason = reason;
}
formattedProcessData.value = formatDates(processData.value);
if (ganttChart.value && ganttChart.value.updateChart) {
ganttChart.value.updateChart();
}
// showInfoMessage('停机原因已设置为:' + reason);
});
},
() => {
showWarningMessage('操作已取消')
}
);
};
const handleOtherReasonClick = (event) => {
event.stopPropagation();
showOtherReasonInput.value = true;
};
const handleReasonConfirm = (id) => {
if (!otherReason.value) {
showWarningMessage('请输入其他原因');
return;
}
handleReasonChange(id, otherReason.value);
showOtherReasonInput.value = false;
};
const fetchStopReason = async () => {
try {
const response = await getStopReason();
if (response.code === 200) {
stopReasons.value = response.data.map(item => item.name);
} else {
console.error('Error fetching stop reason:', response.message);
}
} catch (error) {
console.error('Error fetching stop reason:', error);
}
};
const filterStatusColor = (status) => {
const color = statuses.value.find(item => item.label === status);
return color ? color.color : '#6B6B7E';
};
const fetchStatusColors = async () => {
try {
const response = await getStatusColor();
if (response.code === 200) {
statuses.value = response.data.map(item => ({
label: item.name,
color: item.color
}));
} else {
console.error('Error fetching status colors:', response.message);
}
} catch (error) {
console.error('Error fetching status colors:', error);
}
};
const selectedTab = ref('');
const changeTab = (tabId) => (selectedTab.value = tabId);
let autoUpdateInterval = null;
// 生命周期钩子
onMounted(async () => {
await fetchStatusColors(); // 调用获取颜色状态数据的函数
await fetchData();
fetchGanttData();
fetchStopReason();
sendDataToParent();
const setupAutoUpdate = () => {
if (isAutoUpdate.value) {
autoUpdateInterval = setInterval(() => {
fetchData();
fetchGanttData();
}, 60000); // 每分钟刷新一次接口
} else if (autoUpdateInterval) {
clearInterval(autoUpdateInterval);
autoUpdateInterval = null;
}
};
setupAutoUpdate();
// 监听 update-history 事件
window.addEventListener('update-history', (event) => {
isAutoUpdate.value = false;
globalTime.value = event.detail;
fetchHistoryData(event.detail);
fetchGanttData();
setupAutoUpdate();
});
// 监听 reset 事件
window.addEventListener('reset', (event) => {
isAutoUpdate.value = true;
globalTime.value = moment().tz(timezone).format('YYYY-MM-DD HH:mm');
fetchData();
fetchGanttData();
setupAutoUpdate();
});
});
onUnmounted(() => {
if (autoUpdateInterval) {
clearInterval(autoUpdateInterval);
}
});
</script>
<style scoped>
/* 全局样式 */
.dashboard-container {
padding: 0 !important;
margin: 0 -1.5rem 0 0 !important;
display: flex;
flex-direction: column;
align-items: center;
background: #000028;
height: 100vh;
width: calc(100% + 1.5rem);
overflow: hidden;
color: white;
font-family: 'Microsoft YaHei', sans-serif;
font-size: 1rem;
/* 统一字体大小 */
}
/* 上部区域样式 */
.upper-section {
display: flex;
width: 100%;
height: 60%;
/* 调整高度 */
}
/* 上左区域样式 */
.upper-left {
display: flex;
width: 70%;
height: 100%;
}
/* 左一区域样式 */
.left-1 {
width: 40%;
}
/* 左二区域样式 */
.left-2 {
width: 60%;
}
.left-1,
.left-2 {
display: flex;
flex-direction: column;
height: 100%;
}
/* 间隔样式 */
.spacing {
height: 0.125rem;
background-color: #000028;
margin: 0.5rem 0;
/* 调整间隔 */
}
/* 进度条样式 */
.juice-item {
display: flex;
align-items: center;
padding: 1rem 1rem 1rem 1rem;
height: 22%;
}
.flex-row {
display: flex;
justify-content: space-between;
align-items: center;
height: 2.5rem;
}
.flex-row-footer {
display: flex;
align-items: center;
height: 2.5rem;
}
/* 标题和流量标签样式 */
.juice-title {
font-size: 1rem;
/* 统一字体大小 */
margin: 0.5rem 0 0 1rem;
/* 调整间距 */
}
.flow-label {
font-size: 1rem;
/* 统一字体大小 */
margin: 0.5rem 1rem 0 0;
/* 调整间距 */
}
.juice-name {
font-size: 1rem;
/* 统一字体大小 */
}
.juice-value {
font-size: 1rem;
/* 统一字体大小 */
}
/* 进度条样式 */
.progress-bar {
height: 1.2rem;
/* 调整高度 */
background: #000028;
border-radius: 0.5rem;
/* 调整圆角 */
width: 70%;
position: relative;
overflow: hidden;
}
/* 进度样式 */
.progress {
height: 100%;
border-radius: 0.5rem;
/* 调整圆角 */
transition: width 0.3s ease;
position: relative;
}
.progress-text {
position: absolute;
color: #000028;
font-size: 0.875rem;
font-weight: 500;
white-space: nowrap;
pointer-events: none;
}
.progress .progress-text {
left: 50%;
transform: translateX(-50%);
}
/* 区块样式 */
.block {
background: #23233C;
border: 0.0625rem solid rgba(255, 255, 255, 0.1);
border-radius: 0.5rem;
min-height: 5rem;
/* 调整最小高度 */
box-shadow: 0 0.25rem 0.375rem rgba(0, 0, 0, 0.2);
text-align: center;
margin: 0.5rem;
flex: 1;
}
/* UHT 项目样式 */
.UHT-item {
display: flex;
}
/* 恢复 info-panel 样式 */
.info-panel {
display: flex;
flex-direction: column;
justify-content: space-between;
padding: 1rem 1rem 0 0;
}
/* 信息行样式 */
.info-row {
display: flex;
margin: 0 0 2rem 1rem;
/* 调整间距 */
}
/* 信息项样式 */
.info-item {
display: flex;
align-items: center;
}
/* 信息标签样式 */
.info-label {
font-size: 1rem;
/* 统一字体大小 */
color: white;
}
/* 标签面板样式 */
.tag-panel {
display: flex;
flex-direction: column;
padding-left: 5rem;
width: 28%;
}
:deep(.ix-chip) {
cursor: default !important;
}
:deep(ix-chip) .container.custom {
color: rgb(0, 0, 40);
background-color: rgb(0, 190, 220);
}
/* 右侧区域样式 */
.upper-right {
display: flex;
flex-direction: column;
width: 30%;
/* 调整宽度 */
height: 100%;
}
/* 右侧信息行样式 */
.info-item {
margin: 0 0 0.5rem 1rem;
/* 调整间距 */
}
/* 左侧 IxChip 样式 */
.left-chip {
margin: 0.5rem;
width: 100%;
pointer-events: none;
border: none;
color: #000028;
}
/* 右侧 IxChip 样式 */
.right-chip {
margin: 0.5rem 0.5rem 0 0;
width: 90%;
pointer-events: none;
color: #000028;
border: none;
}
.tag-panel-2 {
width: 50%;
display: flex;
}
.right-info-panel {
display: flex;
flex-direction: column;
}
/* 下部区域样式 */
.lower-section {
width: 100%;
height: 20%;
/* 调整高度 */
display: flex;
justify-content: center;
}
/* 甘特图块样式 */
.gantt-block {
height: 10rem;
/* 调整高度 */
padding: 0;
}
.lower-footer {
display: flex;
flex-wrap: wrap;
align-items: center;
width: 100%;
gap: 0.625rem;
justify-content: space-between;
height: 10%;
margin: 1rem 0 0 0;
}
.lower-footer :deep(ix-button) {
flex: 1 1 calc(25% - 1.25rem);
max-width: calc(25% - 1.25rem);
margin: 0.3125rem;
text-align: center;
}
.lower-footer.alternate {
justify-content: center;
}
.lower-footer.alternate :deep(ix-button) {
flex: 1 1 calc(33.33% - 1.25rem);
max-width: calc(33.33% - 1.25rem);
margin: 0.3125rem;
text-align: center;
}
.drop-down {
width: calc(25% - 1.25rem);
background-color: #23233C;
}
.btnStyle {
pointer-events: none;
}
</style>