1171 lines
43 KiB
Vue
1171 lines
43 KiB
Vue
<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">累计流量:{{ 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.name, false)" >
|
||
<span class="juice-name">{{ item.name }}:</span>
|
||
<div class="progress-bar" style="cursor: pointer;">
|
||
<div class="progress"
|
||
:style="{ width: item.rate + '%', background: getStatusColor(item.deviceStatus) }">
|
||
<span class="progress-text">{{ 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">累计流量:{{ 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.name, false)">
|
||
<span class="juice-name">{{ item.name }}:</span>
|
||
<div class="progress-bar" style="cursor: pointer;">
|
||
<div class="progress"
|
||
:style="{ width: item.rate + '%', background: getStatusColor(item.deviceStatus) }">
|
||
<span class="progress-text">{{ 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: 45%;">
|
||
<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;">
|
||
<div class="info-row">
|
||
<span class="info-label">累计流量:</span>
|
||
<span class="info-value">{{ processForm_uht.totalTraffic }}</span>
|
||
</div>
|
||
<div class="info-row">
|
||
<span class="info-label">产品流量:</span>
|
||
<span class="info-value">{{ 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">
|
||
<IxChip class="left-chip"
|
||
:background="processForm_uht.cleanStatus === '已碱洗' ? '#00BEDC' : '#6B6B7E'"
|
||
chip-color="#000028"
|
||
variant="custom">
|
||
  已碱洗
|
||
</IxChip>
|
||
<IxChip class="left-chip"
|
||
:background="processForm_uht.cleanStatus === '已酸洗' ? '#00FFB9' : '#6B6B7E'"
|
||
chip-color="#000028"
|
||
variant="custom">
|
||
  已酸洗
|
||
</IxChip>
|
||
<IxChip class="left-chip"
|
||
:background="processForm_uht.cleanStatus === '清洁状态' ? '#00BEDC' : '#6B6B7E'"
|
||
chip-color="#000028"
|
||
variant="custom">
|
||
 清洁状态
|
||
</IxChip>
|
||
<IxChip class="left-chip"
|
||
:background="processForm_uht.cleanStatus === '无菌状态' ? '#6B6B7E' : '#00BEDC'"
|
||
chip-color="#000028"
|
||
variant="custom">
|
||
 无菌状态
|
||
</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: 45%;">
|
||
<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;">
|
||
<div class="info-row">
|
||
<span class="info-label">累计流量:</span>
|
||
<span class="info-value">{{ processForm_pulp.totalTraffic }}</span>
|
||
</div>
|
||
<div class="info-row">
|
||
<span class="info-label">产品流量:</span>
|
||
<span class="info-value">{{ 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">
|
||
<IxChip class="left-chip" background="#00BEDC" chip-color="#000028" variant="custom">
|
||
  已碱洗
|
||
</IxChip>
|
||
<IxChip class="left-chip" background="#00FFB9" chip-color="#000028" variant="custom">
|
||
  已酸洗
|
||
</IxChip>
|
||
<IxChip class="left-chip" background="#00BEDC" chip-color="#000028" variant="custom">
|
||
 清洁状态
|
||
</IxChip>
|
||
<IxChip class="left-chip" background="#6B6B7E" chip-color="#000028" variant="custom">
|
||
 无菌状态
|
||
</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">
|
||
<IxChip class="right-chip" background="#00BEDC" chip-color="#000028" variant="custom"
|
||
:style="{ cursor: 'default' }">
|
||
  已碱洗
|
||
</IxChip>
|
||
<IxChip class="right-chip" background="#00FFB9" chip-color="#000028" variant="custom">
|
||
  已酸洗
|
||
</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">{{ juiceTank.liquidLevel }}</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="info-item" style="padding: 1rem 0 0 0;">
|
||
<svg width="15" height="15" viewBox="0 0 15 15" class="arrow">
|
||
<polygon points="0,0 15,7.5 0,15" fill="#00FFB9" />
|
||
</svg>
|
||
<span class="info-label" :style="{ color: '#00FFB9' }">果汁流量:{{ juiceTank.productFlowRate
|
||
}}</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="block">
|
||
<div class="flex-row">
|
||
<h2 class="juice-title">动态混合器</h2>
|
||
<div class="tag-panel-2">
|
||
<IxChip class="right-chip" background="#00BEDC" chip-color="#000028" variant="custom">
|
||
  已碱洗
|
||
</IxChip>
|
||
<IxChip class="right-chip" background="#00FFB9" chip-color="#000028" variant="custom">
|
||
  已酸洗
|
||
</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">{{ dynamicMixer.liquidLevel }}</span>
|
||
</div>
|
||
<div class="info-row">
|
||
<span class="info-label">当前流量:</span>
|
||
<span class="info-value">{{ dynamicMixer.productFlowRate }}</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="block">
|
||
<div class="info-item" style="padding: 0.5rem 0 0 0;">
|
||
<svg width="15" height="15" viewBox="0 0 15 15" class="arrow">
|
||
<polygon points="0,0 15,7.5 0,15" fill="#00FFB9" />
|
||
</svg>
|
||
<span class="info-label" :style="{ color: '#00FFB9' }">果汁流量:{{ pulpTank.productFlowRate
|
||
}}</span>
|
||
</div>
|
||
<div class="flex-row">
|
||
<h2 class="juice-title">果肉无菌罐</h2>
|
||
<div class="tag-panel-2">
|
||
<IxChip class="right-chip" background="#00BEDC" chip-color="#000028" variant="custom">
|
||
  已碱洗
|
||
</IxChip>
|
||
<IxChip class="right-chip" background="#00FFB9" chip-color="#000028" variant="custom">
|
||
  已酸洗
|
||
</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">{{ 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"> 流量:{{ productFlowRate }} </IxButton>
|
||
<IxButton Outline class="btnStyle"> 配方:{{ formula }} </IxButton>
|
||
<IxButton Outline class="btnStyle"> 持续时长:{{ duration }} </IxButton>
|
||
<IxButton Outline id="triggerId"> {{ 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"> 罐重:{{ 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 } from '@/api/dashboard.js';
|
||
import ProcessGanttChart from '@/components/ProcessGanttChart.vue'
|
||
|
||
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([
|
||
{ id: 1, label: '已碱洗', color: '#00BEDC' },
|
||
{ id: 2, label: '已酸洗', color: '#00FFB9' },
|
||
{ id: 3, label: '无菌状态', color: '#6B6B7E' },
|
||
{ id: 4, label: '清洁状态', color: '#00BEDC' }
|
||
]);
|
||
|
||
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 removeStatus = (id) => {
|
||
statuses.value = statuses.value.filter(status => status.id !== id);
|
||
};
|
||
|
||
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,
|
||
capacity: item.data.capacity,
|
||
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
|
||
},
|
||
statusList: item.statusList || []
|
||
};
|
||
});
|
||
return processedData;
|
||
};
|
||
|
||
const getStatusColor = (status) => {
|
||
const colors = {
|
||
'空闲': '#00E4FF',
|
||
'生产': '#47D3BD',
|
||
'调配': '#FFA849',
|
||
'定容': '#f3feb0',
|
||
'CIP': '#FFC107'
|
||
}
|
||
return colors[status] || '#00E4FF'
|
||
}
|
||
|
||
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].name;
|
||
}
|
||
// 累计流量
|
||
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,
|
||
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,
|
||
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
|
||
};
|
||
}
|
||
// 更新果汁无菌罐数据
|
||
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
|
||
};
|
||
}
|
||
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
|
||
};
|
||
}
|
||
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
|
||
};
|
||
}
|
||
if (processedData === undefined) showWarningMessage("未查询到主数据!");
|
||
};
|
||
|
||
// 读取接口数据
|
||
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
|
||
}));
|
||
|
||
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 selectedTab = ref('');
|
||
const changeTab = (tabId) => (selectedTab.value = tabId);
|
||
|
||
let autoUpdateInterval = null;
|
||
|
||
// 生命周期钩子
|
||
onMounted(async () => {
|
||
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;
|
||
}
|
||
|
||
: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;
|
||
}
|
||
|
||
/* 右侧 IxChip 样式 */
|
||
.right-chip {
|
||
margin: 0.5rem 0.5rem 0 0;
|
||
width: 95%;
|
||
pointer-events: 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;
|
||
}
|
||
|
||
/* 箭头样式 */
|
||
.arrow {
|
||
width: 0;
|
||
/* 调整高度 */
|
||
padding: 0;
|
||
}
|
||
|
||
/* 箭头样式 */
|
||
.arrow {
|
||
width: 0;
|
||
height: 0;
|
||
border-left: 0.3125rem solid transparent;
|
||
border-right: 0.3125rem solid transparent;
|
||
border-top: 0.3125rem solid #00FFB9;
|
||
}
|
||
|
||
.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>
|