update: 修改甘特图&优化
This commit is contained in:
parent
da17bba934
commit
fd2c2f303d
|
@ -16,6 +16,5 @@
|
||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
color: #2c3e50;
|
color: #2c3e50;
|
||||||
border: var(--theme-primary-bdr-1);
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
|
@ -19,7 +19,24 @@ export function getHistoryReport(inputTime) {
|
||||||
// 获取前处理页面甘特图数据
|
// 获取前处理页面甘特图数据
|
||||||
export function getGanttData(deviceId, inputTime) {
|
export function getGanttData(deviceId, inputTime) {
|
||||||
return request({
|
return request({
|
||||||
url: `/State?deviceId=${deviceId}&inputTime=${inputTime}`,
|
url: `/State?deviceId=${deviceId}&inputTime=${inputTime}`,
|
||||||
method: 'get'
|
method: 'get'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获取停机原因列表
|
||||||
|
export function getStopReason() {
|
||||||
|
return request({
|
||||||
|
url: '/State/stop-reason',
|
||||||
|
method: 'get'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 停机原因维护
|
||||||
|
export function setStopReason(data) {
|
||||||
|
return request({
|
||||||
|
url: '/State/stop-reason',
|
||||||
|
method: 'post',
|
||||||
|
data
|
||||||
|
});
|
||||||
|
}
|
|
@ -0,0 +1,66 @@
|
||||||
|
<template>
|
||||||
|
<div class="custom-datetime-picker">
|
||||||
|
<div class="picker-header">
|
||||||
|
<span>选择日期和时间</span>
|
||||||
|
<button @click="emitDone">确定</button>
|
||||||
|
</div>
|
||||||
|
<div class="picker-body">
|
||||||
|
<input type="datetime-local" v-model="datetime" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref } from 'vue';
|
||||||
|
|
||||||
|
const datetime = ref('');
|
||||||
|
|
||||||
|
const emitDone = () => {
|
||||||
|
const event = new CustomEvent('done', { detail: datetime.value });
|
||||||
|
window.dispatchEvent(event);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.custom-datetime-picker {
|
||||||
|
background-color: #23233C;
|
||||||
|
padding: 1rem;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.picker-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.picker-header span {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.picker-header button {
|
||||||
|
background-color: #1a1a40;
|
||||||
|
color: #fff;
|
||||||
|
border: none;
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.picker-body {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.picker-body input {
|
||||||
|
background-color: #1a1a40;
|
||||||
|
color: #fff;
|
||||||
|
border: none;
|
||||||
|
padding: 0.5rem;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,12 +1,37 @@
|
||||||
<template>
|
<template>
|
||||||
|
<!-- 甘特图容器 -->
|
||||||
<div class="gantt-chart-container">
|
<div class="gantt-chart-container">
|
||||||
<div ref="ganttChart" class="gantt-chart"></div>
|
<!-- 甘特图 -->
|
||||||
|
<div ref="ganttChart" class="gantt-chart">
|
||||||
|
<!-- 甘特图条目 -->
|
||||||
|
<div v-for="(item, index) in mappedData" :key="index" class="gantt-bar" :style="getBarStyle(item)" @click="handleChartClick(item)">
|
||||||
|
<!-- 甘特图条目提示 -->
|
||||||
|
<div class="gantt-bar-tooltip">
|
||||||
|
<div>开始时间:{{ item.beginTime }}</div>
|
||||||
|
<div>结束时间:{{ item.endTime }}</div>
|
||||||
|
<div>生产步骤:{{ item.mixerStep || '--' }}</div>
|
||||||
|
<div>流量:{{ item.productFlowRate || '--' }}</div>
|
||||||
|
<div>配方:{{ item.formula || '--' }}</div>
|
||||||
|
<div>持续时长:{{ item.duration || '--' }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- 甘特图间隙条目 -->
|
||||||
|
<div v-for="(gap, index) in gaps" :key="'gap-' + index" class="gantt-bar gap-bar" :style="getGapStyle(gap)">
|
||||||
|
</div>
|
||||||
|
<!-- 甘特图 X 轴 -->
|
||||||
|
<div class="gantt-x-axis">
|
||||||
|
<div v-for="(tick, index) in xTicks" :key="index" class="gantt-x-tick" :style="getTickStyle(tick)">
|
||||||
|
<div class="gantt-x-tick-line"></div>
|
||||||
|
<div class="gantt-x-tick-label">{{ tick.label }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- <div class="gantt-x-axis-line"></div> -->
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, onMounted, onBeforeUnmount, watch, nextTick, shallowRef } from 'vue'
|
import { ref, onMounted, onBeforeUnmount, watch, nextTick } from 'vue'
|
||||||
import * as echarts from 'echarts'
|
|
||||||
|
|
||||||
// Props 定义
|
// Props 定义
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
|
@ -26,7 +51,6 @@ const emit = defineEmits(['segment-click'])
|
||||||
|
|
||||||
// 响应式数据
|
// 响应式数据
|
||||||
const currentRange = ref('all')
|
const currentRange = ref('all')
|
||||||
const chart = shallowRef(null)
|
|
||||||
const ganttChart = ref(null)
|
const ganttChart = ref(null)
|
||||||
const currentStatus = ref('')
|
const currentStatus = ref('')
|
||||||
const startTimeFormatted = ref('')
|
const startTimeFormatted = ref('')
|
||||||
|
@ -34,159 +58,71 @@ const endTimeFormatted = ref('')
|
||||||
const duration = ref('')
|
const duration = ref('')
|
||||||
const productFlowRate = ref('')
|
const productFlowRate = ref('')
|
||||||
const formula = ref('')
|
const formula = ref('')
|
||||||
|
const mappedData = ref([]) // 修改为 ref
|
||||||
|
const xTicks = ref([])
|
||||||
|
const gaps = ref([])
|
||||||
|
|
||||||
// 修改 resize 处理函数
|
// 初始化甘特图
|
||||||
const handleResize = echarts.throttle(() => {
|
|
||||||
if (chart.value) {
|
|
||||||
chart.value.resize();
|
|
||||||
}
|
|
||||||
}, 100);
|
|
||||||
|
|
||||||
// 方法
|
|
||||||
const initChart = () => {
|
const initChart = () => {
|
||||||
try {
|
if (ganttChart.value) {
|
||||||
if (chart.value) {
|
|
||||||
chart.value.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!ganttChart.value) return;
|
|
||||||
|
|
||||||
// 创建新实例
|
|
||||||
chart.value = echarts.init(ganttChart.value);
|
|
||||||
|
|
||||||
// 设置基础配置
|
|
||||||
const baseOption = {
|
|
||||||
backgroundColor: 'transparent',
|
|
||||||
xAxis: {
|
|
||||||
type: 'category',
|
|
||||||
data: []
|
|
||||||
},
|
|
||||||
yAxis: {
|
|
||||||
type: 'value',
|
|
||||||
min: 0,
|
|
||||||
max: 1
|
|
||||||
},
|
|
||||||
series: [{
|
|
||||||
type: 'bar',
|
|
||||||
data: []
|
|
||||||
}]
|
|
||||||
};
|
|
||||||
|
|
||||||
chart.value.setOption(baseOption);
|
|
||||||
|
|
||||||
// 绑定resize事件
|
|
||||||
window.addEventListener('resize', handleResize);
|
|
||||||
|
|
||||||
// 更新数据
|
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
updateChart();
|
updateChart();
|
||||||
|
generateXTicks();
|
||||||
|
generateGaps();
|
||||||
});
|
});
|
||||||
} catch (error) {
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateChart = () => {
|
// 获取当班开始时间
|
||||||
if (!chart.value || !ganttChart.value) return;
|
const getShiftStartTime = (now) => {
|
||||||
|
const hour = now.getHours();
|
||||||
try {
|
if (hour >= 7 && hour < 19) {
|
||||||
const timeLabels = generateTimeLabels();
|
// 白班:8:00 - 20:00
|
||||||
const baseDate = new Date().toISOString().split('T')[0];
|
return new Date(now.getFullYear(), now.getMonth(), now.getDate(), 7, 0, 0).getTime();
|
||||||
|
} else {
|
||||||
const option = {
|
// 晚班:19:00 - 次日7:00
|
||||||
backgroundColor: 'transparent',
|
if (hour >= 20) {
|
||||||
tooltip: {
|
return new Date(now.getFullYear(), now.getMonth(), now.getDate(), 19, 0, 0).getTime();
|
||||||
trigger: 'item',
|
} else {
|
||||||
formatter: (params) => {
|
return new Date(now.getFullYear(), now.getMonth(), now.getDate() - 1, 19, 0, 0).getTime();
|
||||||
if (!params.data || typeof params.data !== 'object') return ''
|
|
||||||
return `
|
|
||||||
<div style="white-space: normal;">
|
|
||||||
<div>开始时间:${formatTime(params.data.beginTime)}</div>
|
|
||||||
<div>结束时间:${formatTime(params.data.endTime)}</div>
|
|
||||||
<div>生产步骤:${params.data.mixerStep || '--'}</div> <!-- 确保 step 字段正确显示 -->
|
|
||||||
<div>流量:${params.data.productFlowRate || '--'}</div>
|
|
||||||
<div>配方:${params.data.formula || '--'}</div>
|
|
||||||
<div>持续时长:${params.data.duration || '--'}</div>
|
|
||||||
</div>
|
|
||||||
`
|
|
||||||
},
|
|
||||||
position: function (point, params, dom, rect, size) {
|
|
||||||
// 这里自定义位置逻辑
|
|
||||||
// point 是鼠标位置,params 是数据项相关参数,dom 是提示框的 DOM 对象,rect 是图表区域,size 是提示框大小
|
|
||||||
return [point[0] + 10, point[1] - 100];
|
|
||||||
}
|
|
||||||
},
|
|
||||||
grid: {
|
|
||||||
top: 40,
|
|
||||||
bottom: 20,
|
|
||||||
left: 10,
|
|
||||||
right: 10,
|
|
||||||
containLabel: true
|
|
||||||
},
|
|
||||||
xAxis: {
|
|
||||||
type: 'category',
|
|
||||||
position: 'bottom', // 修改位置到底部
|
|
||||||
data: timeLabels,
|
|
||||||
axisLine: {
|
|
||||||
show: true,
|
|
||||||
lineStyle: {
|
|
||||||
color: 'rgba(255, 255, 255, 0.3)'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
axisLabel: {
|
|
||||||
color: 'rgba(255, 255, 255, 0.7)',
|
|
||||||
interval: 3, // 每4个标签显示一个(15分钟 * 4 = 1小时)
|
|
||||||
fontSize: 12,
|
|
||||||
margin: 15,
|
|
||||||
rotate: 0,
|
|
||||||
formatter: function (value) {
|
|
||||||
// 只显示整点时间
|
|
||||||
if (value.includes('/')) {
|
|
||||||
const [date, time] = value.split(' ')
|
|
||||||
const [year, month, day] = date.split('/')
|
|
||||||
|
|
||||||
if (time === '0') {
|
|
||||||
return `${year}/${month}/${day} ${time}:00`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const [hour, minute] = value.split(':')
|
|
||||||
if (minute === '00') {
|
|
||||||
return `${hour}:00`
|
|
||||||
}
|
|
||||||
return ''
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
yAxis: {
|
|
||||||
show: false,
|
|
||||||
type: 'value',
|
|
||||||
min: 0,
|
|
||||||
max: 1
|
|
||||||
},
|
|
||||||
series: [{
|
|
||||||
type: 'bar',
|
|
||||||
barWidth: '108%',
|
|
||||||
barGap: 0,
|
|
||||||
itemStyle: {
|
|
||||||
borderRadius: 0
|
|
||||||
},
|
|
||||||
data: mapDataToTimeline(timeLabels, baseDate) || [] // 确保有默认值
|
|
||||||
}]
|
|
||||||
}
|
}
|
||||||
chart.value.setOption(option, true)
|
|
||||||
// 重新绑定点击事件
|
|
||||||
chart.value.off('click')
|
|
||||||
chart.value.on('click', 'series', (params) => {
|
|
||||||
if (params.data && typeof params.data === 'object') {
|
|
||||||
handleChartClick(params.data)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Update chart error:', error);
|
|
||||||
// 如果更新失败,尝试重新初始化
|
|
||||||
initChart();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 更新甘特图数据
|
||||||
|
const updateChart = () => {
|
||||||
|
const now = new Date();
|
||||||
|
let startTime;
|
||||||
|
|
||||||
|
switch (currentRange.value) {
|
||||||
|
case '1h':
|
||||||
|
startTime = now.getTime() - 60 * 60 * 1000;
|
||||||
|
break;
|
||||||
|
case '2h':
|
||||||
|
startTime = now.getTime() - 2 * 60 * 60 * 1000;
|
||||||
|
break;
|
||||||
|
case 'shift':
|
||||||
|
// 当班逻辑修改,根据当前时间 判断属于白班或晚班
|
||||||
|
startTime = getShiftStartTime(now);//now.getTime() - 12 * 60 * 60 * 1000;
|
||||||
|
break;
|
||||||
|
case 'all':
|
||||||
|
default:
|
||||||
|
startTime = props.processData.length > 0 ? new Date(props.processData[0].beginTime).getTime() : now.getTime();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
const filteredData = props.processData.filter(item => {
|
||||||
|
const itemStartTime = new Date(item.beginTime).getTime();
|
||||||
|
const itemEndTime = new Date(item.endTime).getTime();
|
||||||
|
return itemStartTime >= startTime || itemEndTime >= startTime;
|
||||||
|
});
|
||||||
|
mappedData.value = mapDataToTimeline(filteredData, startTime);
|
||||||
|
nextTick(() => {
|
||||||
|
generateXTicks();
|
||||||
|
generateGaps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 格式化时间
|
||||||
const formatTime = (timeString) => {
|
const formatTime = (timeString) => {
|
||||||
if (!timeString) return '--';
|
if (!timeString) return '--';
|
||||||
const date = new Date(timeString);
|
const date = new Date(timeString);
|
||||||
|
@ -194,93 +130,33 @@ const formatTime = (timeString) => {
|
||||||
return `${date.getFullYear()}-${(date.getMonth() + 1).toString().padStart(2, '0')}-${date.getDate().toString().padStart(2, '0')} ${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`;
|
return `${date.getFullYear()}-${(date.getMonth() + 1).toString().padStart(2, '0')}-${date.getDate().toString().padStart(2, '0')} ${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapDataToTimeline = (timeLabels, baseDate) => {
|
// 将数据映射到时间轴
|
||||||
return timeLabels.map((label) => {
|
const mapDataToTimeline = (data = props.processData, startTime) => {
|
||||||
// 获取当前时间标签对应的小时和分钟
|
return data.map(item => {
|
||||||
let currentTime;
|
const beginTime = new Date(item.beginTime);
|
||||||
if (label.includes('/')) {
|
const endTime = new Date(item.endTime);
|
||||||
const [date, time] = label.split(' ');
|
const adjustedBeginTime = beginTime.getTime() < startTime ? new Date(startTime) : beginTime;
|
||||||
const [year, month, day] = date.split('/').map(Number);
|
const adjustedDuration = endTime - adjustedBeginTime;
|
||||||
const [hour, minute] = time.split(':').map(Number);
|
return {
|
||||||
currentTime = new Date(year, month - 1, day, hour, minute, 0, 0);
|
id: item.id,
|
||||||
} else {
|
stopReason: item.stopReason,
|
||||||
const [hour, minute] = label.split(':').map(Number);
|
name: 'Process',
|
||||||
currentTime = new Date(baseDate);
|
value: adjustedDuration,
|
||||||
currentTime.setHours(hour, minute, 0, 0);
|
deviceStatus: item.deviceStatus,
|
||||||
}
|
mixerStep: item.mixerStep,
|
||||||
|
beginTime: formatTime(adjustedBeginTime),
|
||||||
// 查找是否有数据在这个时间点
|
endTime: formatTime(endTime),
|
||||||
const matchingData = props.processData.find(item => {
|
productFlowRate: item.productFlowRate,
|
||||||
const beginTime = new Date(item.beginTime).getTime();
|
formula: item.formula,
|
||||||
const endTime = new Date(item.endTime).getTime();
|
duration: `${Math.floor(adjustedDuration / 1000)}秒`,
|
||||||
return currentTime.getTime() >= beginTime && currentTime.getTime() <= endTime; // 修改比较条件,确保数据正确对齐
|
itemStyle: {
|
||||||
});
|
color: getStatusColor(item.deviceStatus)
|
||||||
|
|
||||||
if (matchingData) {
|
|
||||||
return {
|
|
||||||
value: 1,
|
|
||||||
deviceStatus: matchingData.deviceStatus,
|
|
||||||
mixerStep: matchingData.mixerStep,
|
|
||||||
beginTime: formatTime(matchingData.beginTime),
|
|
||||||
endTime: formatTime(matchingData.endTime),
|
|
||||||
productFlowRate: matchingData.productFlowRate,
|
|
||||||
formula: matchingData.formula,
|
|
||||||
duration: matchingData.duration,
|
|
||||||
itemStyle: {
|
|
||||||
color: getStatusColor(matchingData.deviceStatus) // 确保颜色正确
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return { value: 0 }; // 确保返回对象格式一致
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const generateTimeLabels = () => {
|
|
||||||
const times = [];
|
|
||||||
const now = new Date();
|
|
||||||
let startHour, endHour;
|
|
||||||
|
|
||||||
switch (currentRange.value) {
|
|
||||||
case '1h':
|
|
||||||
startHour = Math.max(now.getHours() - 1, 0); // 确保 startHour 不为负值
|
|
||||||
endHour = now.getHours();
|
|
||||||
break;
|
|
||||||
case '2h':
|
|
||||||
startHour = Math.max(now.getHours() - 2, 0); // 确保 startHour 不为负值
|
|
||||||
endHour = now.getHours();
|
|
||||||
break;
|
|
||||||
case 'shift':
|
|
||||||
startHour = Math.max(now.getHours() - 12, 0); // 确保 startHour 不为负值
|
|
||||||
endHour = now.getHours();
|
|
||||||
break;
|
|
||||||
case 'all':
|
|
||||||
default:
|
|
||||||
startHour = Math.max(now.getHours() - 24, 0); // 确保 startHour 不为负值
|
|
||||||
endHour = now.getHours();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let hour = startHour; hour <= endHour; hour++) {
|
|
||||||
const currentHour = hour % 24;
|
|
||||||
const isNextDay = hour >= 24;
|
|
||||||
const labelDate = new Date(now);
|
|
||||||
|
|
||||||
if (isNextDay) {
|
|
||||||
labelDate.setDate(labelDate.getDate() + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let minute = 0; minute < 60; minute += 15) {
|
|
||||||
let timeLabel = `${currentHour}:${minute.toString().padStart(2, '0')}`;
|
|
||||||
if (currentHour === 0 && minute === 0) {
|
|
||||||
const dateStr = `${labelDate.getFullYear().toString().slice(-2)}/${(labelDate.getMonth() + 1).toString().padStart(2, '0')}/${(labelDate.getDate()).toString().padStart(2, '0')}`;
|
|
||||||
timeLabel = `${dateStr} ${currentHour}`;
|
|
||||||
}
|
}
|
||||||
times.push(timeLabel);
|
};
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
return times;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
// 更新当前信息
|
||||||
const updateCurrentInfo = () => {
|
const updateCurrentInfo = () => {
|
||||||
const currentData = props.processData[props.processData.length - 1] || {}
|
const currentData = props.processData[props.processData.length - 1] || {}
|
||||||
currentStatus.value = currentData.deviceStatus || '--'
|
currentStatus.value = currentData.deviceStatus || '--'
|
||||||
|
@ -291,7 +167,7 @@ const updateCurrentInfo = () => {
|
||||||
const start = new Date(currentData.beginTime)
|
const start = new Date(currentData.beginTime)
|
||||||
const end = new Date(currentData.endTime)
|
const end = new Date(currentData.endTime)
|
||||||
const diff = end - start
|
const diff = end - start
|
||||||
duration.value = Math.floor(diff / (1000 * 60)) + ' 分钟'
|
duration.value = Math.floor(diff / (1000 * 60)) + '分钟'
|
||||||
} else {
|
} else {
|
||||||
duration.value = '--'
|
duration.value = '--'
|
||||||
}
|
}
|
||||||
|
@ -300,32 +176,27 @@ const updateCurrentInfo = () => {
|
||||||
formula.value = currentData.formula || '--'
|
formula.value = currentData.formula || '--'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 改变时间范围
|
||||||
const changeTimeRange = (range) => {
|
const changeTimeRange = (range) => {
|
||||||
currentRange.value = range;
|
currentRange.value = range;
|
||||||
updateChart();
|
updateChart();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获取状态颜色
|
||||||
const getStatusColor = (status) => {
|
const getStatusColor = (status) => {
|
||||||
const colors = {
|
const colors = {
|
||||||
'running': '#00E4FF',
|
'空闲': '#00E4FF',
|
||||||
'cleaning': '#47D3BD', // 清洗状态
|
'生产': '#47D3BD',
|
||||||
'waiting': '#FFA849',
|
'调配': '#FFA849',
|
||||||
'completed': '#47D3BD',
|
'定容': '#f3feb0',
|
||||||
production: '#4CAF50',
|
'CIP': '#FFC107'
|
||||||
downtime: '#F44336',
|
|
||||||
standby: '#FFC107'
|
|
||||||
}
|
}
|
||||||
return colors[status] || '#00E4FF'
|
return colors[status] || '#00E4FF'
|
||||||
}
|
}
|
||||||
|
|
||||||
// const formatTime = (time) => {
|
// 处理甘特图点击事件
|
||||||
// if (!time) return ''
|
|
||||||
// const date = new Date(time)
|
|
||||||
// return `${date.getFullYear()}/${date.getMonth() + 1}/${date.getDate()} ${date.getHours()}:${String(date.getMinutes()).padStart(2, '0')}`
|
|
||||||
// }
|
|
||||||
|
|
||||||
const handleChartClick = (data) => {
|
const handleChartClick = (data) => {
|
||||||
currentStatus.value = data.status
|
currentStatus.value = data.deviceStatus
|
||||||
startTimeFormatted.value = formatTime(data.beginTime)
|
startTimeFormatted.value = formatTime(data.beginTime)
|
||||||
endTimeFormatted.value = formatTime(data.endTime)
|
endTimeFormatted.value = formatTime(data.endTime)
|
||||||
|
|
||||||
|
@ -333,7 +204,7 @@ const handleChartClick = (data) => {
|
||||||
const start = new Date(data.beginTime)
|
const start = new Date(data.beginTime)
|
||||||
const end = new Date(data.endTime)
|
const end = new Date(data.endTime)
|
||||||
const diff = end - start
|
const diff = end - start
|
||||||
duration.value = Math.floor(diff / (1000 * 60)) + ' 分钟'
|
duration.value = Math.floor(diff / (1000 * 60)) + '分钟'
|
||||||
} else {
|
} else {
|
||||||
duration.value = '--'
|
duration.value = '--'
|
||||||
}
|
}
|
||||||
|
@ -345,34 +216,205 @@ const handleChartClick = (data) => {
|
||||||
emit('segment-click', data)
|
emit('segment-click', data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获取甘特图条目样式
|
||||||
|
const getBarStyle = (item) => {
|
||||||
|
const startTime = new Date(item.beginTime).getTime()
|
||||||
|
const endTime = new Date(item.endTime).getTime()
|
||||||
|
const totalDuration = endTime - startTime
|
||||||
|
const containerWidth = ganttChart.value ? ganttChart.value.clientWidth : 0
|
||||||
|
|
||||||
|
let chartStartTime;
|
||||||
|
switch (currentRange.value) {
|
||||||
|
case '1h':
|
||||||
|
chartStartTime = new Date().getTime() - 60 * 60 * 1000;
|
||||||
|
break;
|
||||||
|
case '2h':
|
||||||
|
chartStartTime = new Date().getTime() - 2 * 60 * 60 * 1000;
|
||||||
|
break;
|
||||||
|
case 'shift':
|
||||||
|
chartStartTime = getShiftStartTime(new Date());//new Date().getTime() - 12 * 60 * 60 * 1000;
|
||||||
|
break;
|
||||||
|
case 'all':
|
||||||
|
default:
|
||||||
|
chartStartTime = props.processData.length > 0 ? new Date(props.processData[0].beginTime).getTime() : new Date().getTime();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const chartEndTime = new Date().getTime(); // 使用当前时间作为结束时间
|
||||||
|
const totalChartDuration = chartEndTime - chartStartTime
|
||||||
|
const adjustedStartTime = startTime < chartStartTime ? chartStartTime : startTime;
|
||||||
|
const adjustedDuration = endTime - adjustedStartTime;
|
||||||
|
const barWidth = (adjustedDuration / totalChartDuration) * containerWidth
|
||||||
|
const leftPosition = ((adjustedStartTime - chartStartTime) / totalChartDuration) * containerWidth
|
||||||
|
return {
|
||||||
|
width: `${barWidth / 16}rem`, // 将 px 转换为 rem
|
||||||
|
backgroundColor: item.itemStyle.color,
|
||||||
|
left: `calc(${leftPosition / 16}rem + 1rem)` // 将 px 转换为 rem,并增加 1rem 的 gap
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取甘特图间隙条目样式
|
||||||
|
const getGapStyle = (gap) => {
|
||||||
|
const startTime = new Date(gap.startTime).getTime()
|
||||||
|
const endTime = new Date(gap.endTime).getTime()
|
||||||
|
const totalDuration = endTime - startTime
|
||||||
|
const containerWidth = ganttChart.value ? ganttChart.value.clientWidth : 0
|
||||||
|
|
||||||
|
let chartStartTime;
|
||||||
|
switch (currentRange.value) {
|
||||||
|
case '1h':
|
||||||
|
chartStartTime = new Date().getTime() - 60 * 60 * 1000;
|
||||||
|
break;
|
||||||
|
case '2h':
|
||||||
|
chartStartTime = new Date().getTime() - 2 * 60 * 60 * 1000;
|
||||||
|
break;
|
||||||
|
case 'shift':
|
||||||
|
chartStartTime = getShiftStartTime(new Date());//new Date().getTime() - 12 * 60 * 60 * 1000;
|
||||||
|
break;
|
||||||
|
case 'all':
|
||||||
|
default:
|
||||||
|
chartStartTime = props.processData.length > 0 ? new Date(props.processData[0].beginTime).getTime() : new Date().getTime();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const chartEndTime = new Date().getTime(); // 使用当前时间作为结束时间
|
||||||
|
const totalChartDuration = chartEndTime - chartStartTime
|
||||||
|
const adjustedStartTime = startTime < chartStartTime ? chartStartTime : startTime;
|
||||||
|
const adjustedDuration = endTime - adjustedStartTime;
|
||||||
|
const barWidth = (adjustedDuration / totalChartDuration) * containerWidth
|
||||||
|
const leftPosition = ((adjustedStartTime - chartStartTime) / totalChartDuration) * containerWidth
|
||||||
|
|
||||||
|
return {
|
||||||
|
width: `${barWidth / 16}rem`, // 将 px 转换为 rem
|
||||||
|
backgroundColor: '#D3D3D3', // 灰色
|
||||||
|
left: `calc(${leftPosition / 16}rem + 1rem)` // 将 px 转换为 rem,并增加 1rem 的 gap
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成 X 轴刻度
|
||||||
|
const generateXTicks = () => {
|
||||||
|
const containerWidth = ganttChart.value ? ganttChart.value.clientWidth : 0
|
||||||
|
let startTime, endTime;
|
||||||
|
|
||||||
|
const now = new Date();
|
||||||
|
if (props.processData.length > 0) {
|
||||||
|
startTime = new Date(props.processData[0].beginTime).getTime();
|
||||||
|
} else {
|
||||||
|
startTime = now.getTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (currentRange.value) {
|
||||||
|
case '1h':
|
||||||
|
startTime = now.getTime() - 60 * 60 * 1000;
|
||||||
|
endTime = now.getTime();
|
||||||
|
break;
|
||||||
|
case '2h':
|
||||||
|
startTime = now.getTime() - 2 * 60 * 60 * 1000;
|
||||||
|
endTime = now.getTime();
|
||||||
|
break;
|
||||||
|
case 'shift':
|
||||||
|
startTime = getShiftStartTime(now);// now.getTime() - 12 * 60 * 60 * 1000;
|
||||||
|
endTime = now.getTime();
|
||||||
|
break;
|
||||||
|
case 'all':
|
||||||
|
default:
|
||||||
|
endTime = now.getTime();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const totalDuration = endTime - startTime;
|
||||||
|
const tickInterval = totalDuration / 10; // 将时间段分为10个刻度
|
||||||
|
const ticks = [];
|
||||||
|
|
||||||
|
for (let i = 0; i <= 10; i++) {
|
||||||
|
const time = startTime + i * tickInterval;
|
||||||
|
const tickTime = new Date(time);
|
||||||
|
const label = `${(tickTime.getMonth() + 1).toString().padStart(2, '0')}-${tickTime.getDate().toString().padStart(2, '0')} ${tickTime.getHours().toString().padStart(2, '0')}:${tickTime.getMinutes().toString().padStart(2, '0')}`;
|
||||||
|
const position = (i / 10) * containerWidth;
|
||||||
|
ticks.push({ label, position });
|
||||||
|
}
|
||||||
|
|
||||||
|
xTicks.value = ticks;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成甘特图间隙
|
||||||
|
const generateGaps = () => {
|
||||||
|
const gapsArray = [];
|
||||||
|
let startTime;
|
||||||
|
|
||||||
|
const now = new Date();
|
||||||
|
switch (currentRange.value) {
|
||||||
|
case '1h':
|
||||||
|
startTime = now.getTime() - 60 * 60 * 1000;
|
||||||
|
break;
|
||||||
|
case '2h':
|
||||||
|
startTime = now.getTime() - 2 * 60 * 60 * 1000;
|
||||||
|
break;
|
||||||
|
case 'shift':
|
||||||
|
startTime = getShiftStartTime(now);//now.getTime() - 12 * 60 * 60 * 1000;
|
||||||
|
break;
|
||||||
|
case 'all':
|
||||||
|
default:
|
||||||
|
startTime = props.processData.length > 0 ? new Date(props.processData[0].beginTime).getTime() : now.getTime();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (props.processData.length > 0) {
|
||||||
|
let previousEndTime = startTime;
|
||||||
|
for (let i = 0; i < props.processData.length; i++) {
|
||||||
|
const currentStartTime = new Date(props.processData[i].beginTime).getTime();
|
||||||
|
if (currentStartTime > previousEndTime) {
|
||||||
|
gapsArray.push({
|
||||||
|
startTime: previousEndTime,
|
||||||
|
endTime: currentStartTime
|
||||||
|
});
|
||||||
|
}
|
||||||
|
previousEndTime = new Date(props.processData[i].endTime).getTime();
|
||||||
|
}
|
||||||
|
const currentTime = new Date().getTime();
|
||||||
|
if (currentTime > previousEndTime) {
|
||||||
|
gapsArray.push({
|
||||||
|
startTime: previousEndTime,
|
||||||
|
endTime: currentTime
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
gaps.value = gapsArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取 X 轴刻度样式
|
||||||
|
const getTickStyle = (tick) => {
|
||||||
|
return {
|
||||||
|
left: `calc(${tick.position / 16}rem + 1rem)`, // 将 px 转换为 rem,并增加 1rem 的 gap
|
||||||
|
transform: 'translateX(-50%)',
|
||||||
|
whiteSpace: 'nowrap' // 确保标签不换行
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 修改生命周期钩子
|
// 修改生命周期钩子
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
// 确保 DOM 已经渲染完成
|
// 确保 DOM 已经渲染完成
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
initChart();
|
if (ganttChart.value) {
|
||||||
updateCurrentInfo();
|
initChart();
|
||||||
|
updateCurrentInfo();
|
||||||
|
}
|
||||||
}, 0);
|
}, 0);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
try {
|
// 清理工作
|
||||||
window.removeEventListener('resize', handleResize);
|
|
||||||
if (chart.value) {
|
|
||||||
chart.value.dispose();
|
|
||||||
chart.value = null;
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Cleanup error:', error);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// 修改监听器
|
// 修改监听器
|
||||||
watch(() => props.processData, () => {
|
watch(() => props.processData, () => {
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
if (chart.value) {
|
if (ganttChart.value) {
|
||||||
updateChart();
|
updateChart();
|
||||||
|
generateXTicks(); // 确保 x 轴刻度更新
|
||||||
|
generateGaps(); // 确保 gaps 更新
|
||||||
updateCurrentInfo();
|
updateCurrentInfo();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -385,22 +427,97 @@ watch(() => props.currentRange, (newRange) => {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
/* 甘特图容器样式 */
|
||||||
.gantt-chart-container {
|
.gantt-chart-container {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 75%;
|
height: 75%;
|
||||||
/* background: #23233C; */
|
border-radius: 0.5rem;
|
||||||
border-radius: 8px;
|
|
||||||
backdrop-filter: blur(10px);
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
padding: 0 1rem; /* 增加左右两边的 gap */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 甘特图样式 */
|
||||||
.gantt-chart {
|
.gantt-chart {
|
||||||
background: #23233C;
|
background: #23233C;
|
||||||
flex: 1;
|
width: calc(100% - 2rem); /* 减去左右两边的 gap */
|
||||||
|
height: 70%;
|
||||||
|
min-height: 5rem;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 甘特图 X 轴样式 */
|
||||||
|
.gantt-x-axis {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
height: 1.8rem;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 甘特图 X 轴刻度样式 */
|
||||||
|
.gantt-x-tick {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
min-height: 80px;
|
font-size: 0.75rem;
|
||||||
|
color: #fff;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
white-space: nowrap; /* 确保标签不换行 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 甘特图 X 轴刻度线样式 */
|
||||||
|
.gantt-x-tick-line {
|
||||||
|
width: 0.0625rem;
|
||||||
|
height: 100%;
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 甘特图 X 轴刻度标签样式 */
|
||||||
|
.gantt-x-tick-label {
|
||||||
|
margin-top: 0.125rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 甘特图条目样式 */
|
||||||
|
.gantt-bar {
|
||||||
|
position: absolute;
|
||||||
|
height: 1.875rem;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 甘特图条目提示样式 */
|
||||||
|
.gantt-bar-tooltip {
|
||||||
|
display: none;
|
||||||
|
position: absolute;
|
||||||
|
top: -3.75rem;
|
||||||
|
left: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.7);
|
||||||
|
color: #fff;
|
||||||
|
padding: 0.3125rem;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
white-space: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gantt-bar:hover .gantt-bar-tooltip {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gantt-x-axis-line {
|
||||||
|
width: 100%;
|
||||||
|
height: 0.0625rem;
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gap-bar {
|
||||||
|
background-color: #D3D3D3; /* 灰色 */
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
|
@ -1,7 +1,8 @@
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
|
||||||
const service = axios.create({
|
const service = axios.create({
|
||||||
baseURL: 'http://192.168.1.199:8080/api', // 接口的基础路径'http://cs-vsc.siemens.com.cn:8005',//
|
// baseURL: 'http://192.168.1.199:8080/api', // 办公室测试接口
|
||||||
|
baseURL: 'http://39.105.9.124:8090/api', // 家用测试接口
|
||||||
timeout: 5000, // 请求超时时间
|
timeout: 5000, // 请求超时时间
|
||||||
headers: {
|
headers: {
|
||||||
"Access-Control-Allow-Origin": "*",
|
"Access-Control-Allow-Origin": "*",
|
||||||
|
|
|
@ -10,12 +10,11 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="spacing"></div>
|
<div class="spacing"></div>
|
||||||
<div v-for="item in progressList" :key="item.name" class="juice-item"
|
<div v-for="item in progressList" :key="item.name" class="juice-item"
|
||||||
:data-device-id="item?.deviceId" @click="handleBlockClick(item?.deviceId)"
|
:data-device-id="item?.deviceId" @click="handleBlockClick(item?.deviceId, item.name, false)" >
|
||||||
style="cursor: pointer;">
|
|
||||||
<span class="juice-name">{{ item.name }}:</span>
|
<span class="juice-name">{{ item.name }}:</span>
|
||||||
<div class="progress-bar">
|
<div class="progress-bar" style="cursor: pointer;">
|
||||||
<div class="progress"
|
<div class="progress"
|
||||||
:style="{ width: item.rate + '%', background: item.rate > 70 ? '#00D2A0' : item.rate > 50 ? '#FF9000' : item.rate > 30 ? '#FFD732' : '#FF0000' }">
|
:style="{ width: item.rate + '%', background: getStatusColor(item.deviceStatus) }">
|
||||||
<span class="progress-text">{{ item.value }}</span>
|
<span class="progress-text">{{ item.value }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -29,12 +28,11 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="spacing"></div>
|
<div class="spacing"></div>
|
||||||
<div v-for="item in innerProgressList" :key="item.name" class="juice-item"
|
<div v-for="item in innerProgressList" :key="item.name" class="juice-item"
|
||||||
:data-device-id="item?.deviceId" @click="handleBlockClick(item?.deviceId)"
|
:data-device-id="item?.deviceId" @click="handleBlockClick(item?.deviceId, item.name, false)">
|
||||||
style="cursor: pointer;">
|
|
||||||
<span class="juice-name">{{ item.name }}:</span>
|
<span class="juice-name">{{ item.name }}:</span>
|
||||||
<div class="progress-bar">
|
<div class="progress-bar" style="cursor: pointer;">
|
||||||
<div class="progress"
|
<div class="progress"
|
||||||
:style="{ width: item.rate + '%', background: item.rate > 70 ? '#00D2A0' : item.rate > 50 ? '#FF9000' : item.rate > 30 ? '#FFD732' : '#FF0000' }">
|
:style="{ width: item.rate + '%', background: getStatusColor(item.deviceStatus) }">
|
||||||
<span class="progress-text">{{ item.value }}</span>
|
<span class="progress-text">{{ item.value }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -45,7 +43,7 @@
|
||||||
<div class="left-2">
|
<div class="left-2">
|
||||||
<div class="block">
|
<div class="block">
|
||||||
<div class="flex-row" :data-device-id="uhtData?.deviceId"
|
<div class="flex-row" :data-device-id="uhtData?.deviceId"
|
||||||
@click="handleBlockClick(uhtData?.deviceId)" style="cursor: pointer;">
|
@click="handleBlockClick(uhtData?.deviceId, uhtData.name, true)" style="cursor: pointer;">
|
||||||
<h2 class="juice-title">果汁 UHT</h2>
|
<h2 class="juice-title">果汁 UHT</h2>
|
||||||
</div>
|
</div>
|
||||||
<div class="spacing"></div>
|
<div class="spacing"></div>
|
||||||
|
@ -96,7 +94,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="block">
|
<div class="block">
|
||||||
<div class="flex-row" :data-device-id="pulpUHTData?.deviceId"
|
<div class="flex-row" :data-device-id="pulpUHTData?.deviceId"
|
||||||
@click="handleBlockClick(pulpUHTData?.deviceId)" style="cursor: pointer;">
|
@click="handleBlockClick(pulpUHTData?.deviceId, pulpUHTData.name, true)" style="cursor: pointer;">
|
||||||
<h2 class="juice-title">果肉 UHT</h2>
|
<h2 class="juice-title">果肉 UHT</h2>
|
||||||
</div>
|
</div>
|
||||||
<div class="spacing"></div>
|
<div class="spacing"></div>
|
||||||
|
@ -175,7 +173,6 @@
|
||||||
<span class="progress-text">{{ juiceTank.liquidLevel }}</span>
|
<span class="progress-text">{{ juiceTank.liquidLevel }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<img style="padding-left: 1rem;" v-if="juiceTank.rate < 10" src="@/assets/alarm.svg" />
|
|
||||||
</div>
|
</div>
|
||||||
<div class="info-item">
|
<div class="info-item">
|
||||||
<svg width="15" height="15" viewBox="0 0 15 15" class="arrow">
|
<svg width="15" height="15" viewBox="0 0 15 15" class="arrow">
|
||||||
|
@ -222,8 +219,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="block" :data-device-id="pulpTankData?.deviceId"
|
<div class="block">
|
||||||
@click="handleBlockClick(pulpTankData?.deviceId)">
|
|
||||||
<div class="info-item">
|
<div class="info-item">
|
||||||
<svg width="15" height="15" viewBox="0 0 15 15" class="arrow">
|
<svg width="15" height="15" viewBox="0 0 15 15" class="arrow">
|
||||||
<polygon points="0,0 15,7.5 0,15" fill="#00FFB9" />
|
<polygon points="0,0 15,7.5 0,15" fill="#00FFB9" />
|
||||||
|
@ -255,7 +251,6 @@
|
||||||
<span class="progress-text">{{ pulpTank.liquidLevel }}</span>
|
<span class="progress-text">{{ pulpTank.liquidLevel }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<img style="padding-left: 1rem;" v-if="pulpTank.rate < 10" src="@/assets/alarm.svg" />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -263,8 +258,8 @@
|
||||||
<div class="lower-section">
|
<div class="lower-section">
|
||||||
<div class="block">
|
<div class="block">
|
||||||
<div class="flex-row">
|
<div class="flex-row">
|
||||||
<h2 class="juice-title">生产进度</h2>
|
<h2 class="juice-title">{{ currentTitle }}</h2>
|
||||||
<div>
|
<div style="padding: 0.5rem 2rem 0 0;">
|
||||||
<IxButton Outline @click="changeTimeRange('all')">全部</IxButton>
|
<IxButton Outline @click="changeTimeRange('all')">全部</IxButton>
|
||||||
<IxButton Outline @click="changeTimeRange('1h')">一小时</IxButton>
|
<IxButton Outline @click="changeTimeRange('1h')">一小时</IxButton>
|
||||||
<IxButton Outline @click="changeTimeRange('2h')">二小时</IxButton>
|
<IxButton Outline @click="changeTimeRange('2h')">二小时</IxButton>
|
||||||
|
@ -273,43 +268,46 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="spacing"></div>
|
<div class="spacing"></div>
|
||||||
<ProcessGanttChart ref="ganttChart" :process-data="formattedProcessData" :current-range="currentRange"
|
<ProcessGanttChart ref="ganttChart" :process-data="formattedProcessData" :current-range="currentRange"
|
||||||
@update-info="updateCurrentInfo" @segment-click="handleSegmentClick" />
|
@segment-click="handleSegmentClick" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="lower-footer">
|
<div class="lower-footer" v-if="deviceType">
|
||||||
<IxButton Outline> 状态:{{ currentStatus }} </IxButton>
|
<IxButton Outline class="btnStyle"> 状态:{{ currentStatus }} </IxButton>
|
||||||
<IxButton Outline> 生产步骤:{{ mixerStep }} </IxButton> <!-- 增加 step 字段显示 -->
|
<IxButton Outline class="btnStyle"> 生产步骤:{{ mixerStep }} </IxButton> <!-- 增加 step 字段显示 -->
|
||||||
<IxButton Outline> 开始时间:{{ startTimeFormatted }} </IxButton>
|
<IxButton Outline class="btnStyle"> 开始时间:{{ startTimeFormatted }} </IxButton>
|
||||||
<IxButton Outline> 结束时间:{{ endTimeFormatted }} </IxButton>
|
<IxButton Outline class="btnStyle"> 结束时间:{{ endTimeFormatted }} </IxButton>
|
||||||
<IxButton Outline> 流量:{{ productFlowRate }} </IxButton>
|
<IxButton Outline class="btnStyle"> 流量:{{ productFlowRate }} </IxButton>
|
||||||
<IxButton Outline> 配方:{{ formula }} </IxButton>
|
<IxButton Outline class="btnStyle"> 配方:{{ formula }} </IxButton>
|
||||||
<IxButton Outline> 持续时长:{{ duration }} </IxButton>
|
<IxButton Outline class="btnStyle"> 持续时长:{{ duration }} </IxButton>
|
||||||
<IxButton Outline id="triggerId"> {{ selectedReason ? '停机原因:' + selectedReason : '请选择停机原因' }} </IxButton>
|
<IxButton Outline id="triggerId"> {{ selectedReason ? '停机原因:' + selectedReason : '请选择停机原因' }} </IxButton>
|
||||||
<IxDropdown trigger="triggerId" class="drop-down">
|
<IxDropdown trigger="triggerId" class="drop-down">
|
||||||
<IxDropdownHeader label="停机原因"></IxDropdownHeader>
|
<IxDropdownHeader label="停机原因"></IxDropdownHeader>
|
||||||
<IxDropdownItem label="灌注机温度过高" @click="handleReasonChange('灌注机温度过高')"></IxDropdownItem>
|
<IxDropdownItem v-for="reason in stopReasons" :key="reason" :label="reason" @click="handleReasonChange(id, reason)"></IxDropdownItem>
|
||||||
<IxDropdownItem label="设备故障" @click="handleReasonChange('设备故障')"></IxDropdownItem>
|
|
||||||
<IxDropdownItem label="原料不足" @click="handleReasonChange('原料不足')"></IxDropdownItem>
|
|
||||||
<IxDivider></IxDivider>
|
<IxDivider></IxDivider>
|
||||||
<IxDropdownItem label="其他" @click="handleOtherReasonClick"></IxDropdownItem>
|
<IxDropdownItem label="其他" @click="handleOtherReasonClick"></IxDropdownItem>
|
||||||
<div v-if="showOtherReasonInput" class="other-reason-input" @click.stop>
|
<div v-if="showOtherReasonInput" class="other-reason-input" @click.stop>
|
||||||
<input type="text" v-model="otherReason" placeholder="请输入其他原因" />
|
<input type="text" v-model="otherReason" placeholder="请输入其他原因" />
|
||||||
<IxButton @click="handleReasonConfirm">确认</IxButton>
|
<IxButton @click="handleReasonConfirm(id)">确认</IxButton>
|
||||||
</div>
|
</div>
|
||||||
</IxDropdown>
|
</IxDropdown>
|
||||||
<!-- <IxButton @click="showInfoMessage">Show Info Message</IxButton>
|
</div>
|
||||||
<IxButton @click="showDangerMessage">Show Danger Message</IxButton>
|
<div class="lower-footer alternate" v-else>
|
||||||
<IxButton @click="showWarningMessage">Show Warning Message</IxButton> -->
|
<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"> 调配状态:{{ currentStatus }} </IxButton>
|
||||||
|
<IxButton Outline class="btnStyle"> 罐重:{{ currentStatus }} </IxButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import moment from'moment';
|
import moment from 'moment';
|
||||||
import momentTimezone from'moment-timezone';
|
import momentTimezone from 'moment-timezone';
|
||||||
import { ref, onMounted, getCurrentInstance, computed } from 'vue'
|
import { ref, onMounted, getCurrentInstance, onUnmounted } from 'vue'
|
||||||
import { IxChip, IxButton, IxDropdown, IxDropdownItem, IxDropdownHeader, IxDivider } from '@siemens/ix-vue'; // 引入 Chip 组件
|
import { IxChip, IxButton, IxDropdown, IxDropdownItem, IxDropdownHeader, IxDivider } from '@siemens/ix-vue'; // 引入 Chip 组件
|
||||||
import { getCurrentReport, getHistoryReport, getGanttData } from '@/api/dashboard.js';
|
import { getCurrentReport, getHistoryReport, getGanttData, getStopReason, setStopReason } from '@/api/dashboard.js';
|
||||||
import GradientProgressBar from '@/components/GradientProgressBar.vue'
|
import GradientProgressBar from '@/components/GradientProgressBar.vue'
|
||||||
import ProcessStatusBar from '@/components/ProcessStatusBar.vue'
|
import ProcessStatusBar from '@/components/ProcessStatusBar.vue'
|
||||||
import ProcessGanttChart from '@/components/ProcessGanttChart.vue'
|
import ProcessGanttChart from '@/components/ProcessGanttChart.vue'
|
||||||
|
@ -349,8 +347,8 @@ const processData = ref([])
|
||||||
|
|
||||||
// 新增函数,转换时间格式
|
// 新增函数,转换时间格式
|
||||||
const formatTime = (timeString) => {
|
const formatTime = (timeString) => {
|
||||||
const momentDate = moment.utc(timeString);
|
const momentDate = moment(timeString);
|
||||||
return momentDate.format('YYYY-MM-DD HH:mm');
|
return momentDate.tz(timezone).format('YYYY-MM-DD HH:mm');
|
||||||
}
|
}
|
||||||
|
|
||||||
// 新增函数,统一日期格式
|
// 新增函数,统一日期格式
|
||||||
|
@ -363,10 +361,11 @@ const formatDates = (data) => {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// 处理后的新数据
|
// 处理后的新数据
|
||||||
const formattedProcessData = ref(formatDates(processData.value));
|
const formattedProcessData = ref(formatDates(processData.value));
|
||||||
|
|
||||||
|
// 甘特图包含的信息
|
||||||
|
const id = ref('');
|
||||||
const currentStatus = ref('');
|
const currentStatus = ref('');
|
||||||
const startTimeFormatted = ref('');
|
const startTimeFormatted = ref('');
|
||||||
const endTimeFormatted = ref('');
|
const endTimeFormatted = ref('');
|
||||||
|
@ -377,16 +376,19 @@ const mixerStep = ref(''); // 增加 step 字段
|
||||||
const currentRange = ref('all'); // 增加 currentRange 响应式变量
|
const currentRange = ref('all'); // 增加 currentRange 响应式变量
|
||||||
const ganttChart = ref(null); // 增加 ganttChart 引用
|
const ganttChart = ref(null); // 增加 ganttChart 引用
|
||||||
|
|
||||||
|
const currentTitle = ref('生产进度');
|
||||||
|
|
||||||
// 全局响应式变量
|
// 全局响应式变量
|
||||||
const globalDeviceId = ref(null);
|
const globalDeviceId = ref(null);
|
||||||
const globalTime = computed(() => {
|
const globalTime = ref(moment().tz(timezone).format('YYYY-MM-DD HH:mm'));
|
||||||
return moment().tz(timezone).format('YYYY-MM-DD HH:mm');
|
const isAutoUpdate = ref(true); // 是否自动更新
|
||||||
});
|
const deviceType = ref(true);
|
||||||
|
|
||||||
// 新增响应式变量
|
// 新增响应式变量
|
||||||
const selectedReason = ref(''); // 新增响应式变量
|
const selectedReason = ref(''); // 新增响应式变量
|
||||||
const showOtherReasonInput = ref(false);
|
const showOtherReasonInput = ref(false);
|
||||||
const otherReason = ref('');
|
const otherReason = ref('');
|
||||||
|
const stopReasons = ref([]); // 新增响应式变量
|
||||||
|
|
||||||
// 方法
|
// 方法
|
||||||
const removeStatus = (id) => {
|
const removeStatus = (id) => {
|
||||||
|
@ -394,6 +396,8 @@ const removeStatus = (id) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateCurrentInfo = (segment) => {
|
const updateCurrentInfo = (segment) => {
|
||||||
|
|
||||||
|
id.value = segment.id;
|
||||||
currentStatus.value = segment.deviceStatus;
|
currentStatus.value = segment.deviceStatus;
|
||||||
startTimeFormatted.value = formatTime(segment.beginTime);
|
startTimeFormatted.value = formatTime(segment.beginTime);
|
||||||
endTimeFormatted.value = formatTime(segment.endTime);
|
endTimeFormatted.value = formatTime(segment.endTime);
|
||||||
|
@ -401,6 +405,7 @@ const updateCurrentInfo = (segment) => {
|
||||||
productFlowRate.value = segment.productFlowRate;
|
productFlowRate.value = segment.productFlowRate;
|
||||||
formula.value = segment.formula;
|
formula.value = segment.formula;
|
||||||
mixerStep.value = segment.mixerStep; // 更新 step 字段
|
mixerStep.value = segment.mixerStep; // 更新 step 字段
|
||||||
|
selectedReason.value = segment.stopReason || '';
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSegmentClick = (segment) => {
|
const handleSegmentClick = (segment) => {
|
||||||
|
@ -431,7 +436,7 @@ const processDataFromAPI = (apiData) => {
|
||||||
id: item.data.id || 0,
|
id: item.data.id || 0,
|
||||||
weight: item.data.weight || 0,
|
weight: item.data.weight || 0,
|
||||||
capacity: item.data.capacity,
|
capacity: item.data.capacity,
|
||||||
status: item.data.status || '',
|
deviceStatus: item.data.deviceStatus || '',
|
||||||
productFlowRate: item.data.productFlowRate || 0,
|
productFlowRate: item.data.productFlowRate || 0,
|
||||||
name: item.data.name || '',
|
name: item.data.name || '',
|
||||||
formula: item.data.formula || '',
|
formula: item.data.formula || '',
|
||||||
|
@ -445,6 +450,17 @@ const processDataFromAPI = (apiData) => {
|
||||||
return processedData;
|
return processedData;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getStatusColor = (status) => {
|
||||||
|
const colors = {
|
||||||
|
'空闲': '#00E4FF',
|
||||||
|
'生产': '#47D3BD',
|
||||||
|
'调配': '#FFA849',
|
||||||
|
'定容': '#f3feb0',
|
||||||
|
'CIP': '#FFC107'
|
||||||
|
}
|
||||||
|
return colors[status] || '#00E4FF'
|
||||||
|
}
|
||||||
|
|
||||||
const processHistoryDataFromAPI = processDataFromAPI; // 复用 processDataFromAPI 函数
|
const processHistoryDataFromAPI = processDataFromAPI; // 复用 processDataFromAPI 函数
|
||||||
|
|
||||||
const getDataByName = (data, name) => {
|
const getDataByName = (data, name) => {
|
||||||
|
@ -490,16 +506,16 @@ const updateData = (processedData) => {
|
||||||
progressList.value = juiceData.value.data.map(item => ({
|
progressList.value = juiceData.value.data.map(item => ({
|
||||||
name: item.name,
|
name: item.name,
|
||||||
deviceId: item.deviceId,
|
deviceId: item.deviceId,
|
||||||
|
deviceStatus: item.deviceStatus,
|
||||||
value: item.weight,
|
value: item.weight,
|
||||||
rate: item.weight === 0 ? 0 : (item.weight / item.capacity) * 100
|
rate: item.weight === 0 ? 0 : (item.weight / item.capacity) * 100
|
||||||
}));
|
}));
|
||||||
console.log("🚀 ~ updateData ~ progressList.value:", progressList.value)
|
|
||||||
|
|
||||||
}
|
}
|
||||||
if (pulpData.value.data.length > 0) {
|
if (pulpData.value.data.length > 0) {
|
||||||
innerProgressList.value = pulpData.value.data.map(item => ({
|
innerProgressList.value = pulpData.value.data.map(item => ({
|
||||||
name: item.name,
|
name: item.name,
|
||||||
deviceId: item.deviceId,
|
deviceId: item.deviceId,
|
||||||
|
deviceStatus: item.deviceStatus,
|
||||||
value: item.weight,
|
value: item.weight,
|
||||||
rate: item.weight === 0 ? 0 : (item.weight / item.capacity) * 100
|
rate: item.weight === 0 ? 0 : (item.weight / item.capacity) * 100
|
||||||
}));
|
}));
|
||||||
|
@ -507,6 +523,7 @@ const updateData = (processedData) => {
|
||||||
// 更新 UHT 数据
|
// 更新 UHT 数据
|
||||||
if (uhtData.value.data.length > 0) {
|
if (uhtData.value.data.length > 0) {
|
||||||
processForm_uht.value = {
|
processForm_uht.value = {
|
||||||
|
name: uhtData.value.name,
|
||||||
formula: uhtData.value.data[0].formula,
|
formula: uhtData.value.data[0].formula,
|
||||||
totalTraffic: uhtData.value.totalTraffic,
|
totalTraffic: uhtData.value.totalTraffic,
|
||||||
status: uhtData.value.data[0].status,
|
status: uhtData.value.data[0].status,
|
||||||
|
@ -517,6 +534,7 @@ const updateData = (processedData) => {
|
||||||
}
|
}
|
||||||
if (pulpUHTData.value.data.length > 0) {
|
if (pulpUHTData.value.data.length > 0) {
|
||||||
processForm_pulp.value = {
|
processForm_pulp.value = {
|
||||||
|
name: pulpUHTData.value.name,
|
||||||
formula: pulpUHTData.value.data[0].formula,
|
formula: pulpUHTData.value.data[0].formula,
|
||||||
totalTraffic: pulpUHTData.value.totalTraffic,
|
totalTraffic: pulpUHTData.value.totalTraffic,
|
||||||
status: pulpUHTData.value.data[0].status,
|
status: pulpUHTData.value.data[0].status,
|
||||||
|
@ -552,7 +570,7 @@ const updateData = (processedData) => {
|
||||||
productFlowRate: dynamicMixerData.value.data[0].productFlowRate
|
productFlowRate: dynamicMixerData.value.data[0].productFlowRate
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (processedData.data !== undefined) showInfoMessage("数据刷新成功!");
|
if (processedData !== undefined) showInfoMessage("数据刷新成功!");
|
||||||
else showWarningMessage("未查询到历史数据!");
|
else showWarningMessage("未查询到历史数据!");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -588,6 +606,7 @@ const fetchHistoryData = async (datetime) => {
|
||||||
const processGanttDataResponse = (response) => {
|
const processGanttDataResponse = (response) => {
|
||||||
if (response.data) {
|
if (response.data) {
|
||||||
const ganttData = response.data.map(item => ({
|
const ganttData = response.data.map(item => ({
|
||||||
|
id: item.id,
|
||||||
deviceId: item.deviceId,
|
deviceId: item.deviceId,
|
||||||
duration: item.duration,
|
duration: item.duration,
|
||||||
beginTime: formatTime(item.beginTime),
|
beginTime: formatTime(item.beginTime),
|
||||||
|
@ -596,11 +615,11 @@ const processGanttDataResponse = (response) => {
|
||||||
mixerStep: item.mixerStep,
|
mixerStep: item.mixerStep,
|
||||||
productFlowRate: item.productFlowRate,
|
productFlowRate: item.productFlowRate,
|
||||||
formula: item.formula,
|
formula: item.formula,
|
||||||
lineId: item.lineId
|
lineId: item.lineId,
|
||||||
|
stopReason: item.stopReason
|
||||||
}));
|
}));
|
||||||
processData.value = ganttData;
|
|
||||||
console.log("🚀 ~ processGanttDataResponse ~ processData.value:", processData.value)
|
|
||||||
|
|
||||||
|
processData.value = ganttData;
|
||||||
formattedProcessData.value = formatDates(ganttData);
|
formattedProcessData.value = formatDates(ganttData);
|
||||||
if (ganttChart.value && ganttChart.value.updateChart) {
|
if (ganttChart.value && ganttChart.value.updateChart) {
|
||||||
ganttChart.value.updateChart();
|
ganttChart.value.updateChart();
|
||||||
|
@ -622,32 +641,6 @@ const fetchGanttData = async () => {
|
||||||
console.error('Device ID is null');
|
console.error('Device ID is null');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// 生成假数据
|
|
||||||
// const fakeData = [
|
|
||||||
// {
|
|
||||||
// deviceId: globalDeviceId.value,
|
|
||||||
// duration: '2h',
|
|
||||||
// beginTime: '2025-02-26 08:00',
|
|
||||||
// endTime: '2025-02-26 10:00',
|
|
||||||
// deviceStatus: 'running',
|
|
||||||
// mixerStep: '混合',
|
|
||||||
// productFlowRate: 500,
|
|
||||||
// formula: '配方A',
|
|
||||||
// lineId: 1
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// deviceId: globalDeviceId.value,
|
|
||||||
// duration: '1h',
|
|
||||||
// beginTime: '2025-02-26 10:00',
|
|
||||||
// endTime: '2025-02-26 11:00',
|
|
||||||
// deviceStatus: 'cleaning',
|
|
||||||
// mixerStep: '等待',
|
|
||||||
// productFlowRate: 0,
|
|
||||||
// formula: '配方B',
|
|
||||||
// lineId: 1
|
|
||||||
// }
|
|
||||||
// ];
|
|
||||||
// processGanttDataResponse({ data: fakeData });
|
|
||||||
const response = await getGanttData(globalDeviceId.value, globalTime.value);
|
const response = await getGanttData(globalDeviceId.value, globalTime.value);
|
||||||
processGanttDataResponse(response);
|
processGanttDataResponse(response);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -655,39 +648,22 @@ const fetchGanttData = async () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleBlockClick = async (deviceId) => {
|
const handleBlockClick = async (deviceId, name, type) => {
|
||||||
try {
|
try {
|
||||||
if (!deviceId) {
|
if (!deviceId) {
|
||||||
showWarningMessage('Device ID is null');
|
showWarningMessage('Device ID is null');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
currentTitle.value = name;
|
||||||
globalDeviceId.value = deviceId;
|
globalDeviceId.value = deviceId;
|
||||||
const currentTime = moment();
|
deviceType.value = type;
|
||||||
const formattedTime = currentTime.format('YYYY-MM-DD HH:mm');
|
const response = await getGanttData(deviceId, globalTime.value);
|
||||||
console.log("🚀 ~ handleBlockClick ~ formattedTime:", formattedTime)
|
|
||||||
|
|
||||||
globalTime.value = formattedTime;
|
|
||||||
const response = await getGanttData(deviceId, formattedTime);
|
|
||||||
processGanttDataResponse(response);
|
processGanttDataResponse(response);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching Gantt data:', error);
|
console.error('Error fetching Gantt data:', error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const getHistory = async (datetime) => {
|
|
||||||
try {
|
|
||||||
const response = await getHistoryReport(datetime);
|
|
||||||
if (response.code === 200) {
|
|
||||||
// 处理历史数据
|
|
||||||
console.log('历史数据:', response.data);
|
|
||||||
} else {
|
|
||||||
console.error('Error fetching history data:', response.message);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error fetching history data:', error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const { proxy } = getCurrentInstance();
|
const { proxy } = getCurrentInstance();
|
||||||
|
|
||||||
const showInfoMessage = (message) => {
|
const showInfoMessage = (message) => {
|
||||||
|
@ -710,7 +686,7 @@ const showConfirmMessage = (message, onConfirm, onCancel) => {
|
||||||
proxy.$message.confirm(message, onConfirm, onCancel);
|
proxy.$message.confirm(message, onConfirm, onCancel);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleReasonChange = (reason) => {
|
const handleReasonChange = (id, reason) => {
|
||||||
if (!startTimeFormatted.value) {
|
if (!startTimeFormatted.value) {
|
||||||
showWarningMessage('请先点击甘特图中的数据块,再设置停机原因!');
|
showWarningMessage('请先点击甘特图中的数据块,再设置停机原因!');
|
||||||
return;
|
return;
|
||||||
|
@ -719,7 +695,21 @@ const handleReasonChange = (reason) => {
|
||||||
'确定要设置停机原因为 "' + reason + '" 吗?',
|
'确定要设置停机原因为 "' + reason + '" 吗?',
|
||||||
() => {
|
() => {
|
||||||
selectedReason.value = reason;
|
selectedReason.value = reason;
|
||||||
showInfoMessage('停机原因已设置为:' + 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('操作已取消')
|
showWarningMessage('操作已取消')
|
||||||
|
@ -732,38 +722,76 @@ const handleOtherReasonClick = (event) => {
|
||||||
showOtherReasonInput.value = true;
|
showOtherReasonInput.value = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleReasonConfirm = () => {
|
const handleReasonConfirm = (id) => {
|
||||||
if (!otherReason.value) {
|
if (!otherReason.value) {
|
||||||
showWarningMessage('请输入其他原因');
|
showWarningMessage('请输入其他原因');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
handleReasonChange(otherReason.value);
|
handleReasonChange(id, otherReason.value);
|
||||||
showOtherReasonInput.value = false;
|
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);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let autoUpdateInterval = null;
|
||||||
|
|
||||||
// 生命周期钩子
|
// 生命周期钩子
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await fetchData();
|
await fetchData();
|
||||||
fetchGanttData();
|
fetchGanttData();
|
||||||
|
fetchStopReason();
|
||||||
updateTime();
|
updateTime();
|
||||||
setInterval(updateTime, 1000);
|
setInterval(updateTime, 1000);
|
||||||
setInterval(() => {
|
|
||||||
fetchData();
|
const setupAutoUpdate = () => {
|
||||||
fetchGanttData();
|
if (isAutoUpdate.value) {
|
||||||
}, 60000); // 每分钟刷新一次接口
|
autoUpdateInterval = setInterval(() => {
|
||||||
|
fetchData();
|
||||||
|
fetchGanttData();
|
||||||
|
}, 60000); // 每分钟刷新一次接口
|
||||||
|
} else if (autoUpdateInterval) {
|
||||||
|
clearInterval(autoUpdateInterval);
|
||||||
|
autoUpdateInterval = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
setupAutoUpdate();
|
||||||
|
|
||||||
// 监听 update-history 事件
|
// 监听 update-history 事件
|
||||||
window.addEventListener('update-history', (event) => {
|
window.addEventListener('update-history', (event) => {
|
||||||
getHistory(event.detail);
|
isAutoUpdate.value = false;
|
||||||
|
globalTime.value = event.detail;
|
||||||
fetchHistoryData(event.detail);
|
fetchHistoryData(event.detail);
|
||||||
|
fetchGanttData();
|
||||||
|
setupAutoUpdate();
|
||||||
});
|
});
|
||||||
|
|
||||||
// 监听 reset 事件
|
// 监听 reset 事件
|
||||||
window.addEventListener('reset', (event) => {
|
window.addEventListener('reset', (event) => {
|
||||||
|
isAutoUpdate.value = true;
|
||||||
|
globalTime.value = moment().tz(timezone).format('YYYY-MM-DD HH:mm');
|
||||||
fetchData();
|
fetchData();
|
||||||
fetchGanttData();
|
fetchGanttData();
|
||||||
|
setupAutoUpdate();
|
||||||
});
|
});
|
||||||
})
|
});
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
if (autoUpdateInterval) {
|
||||||
|
clearInterval(autoUpdateInterval);
|
||||||
|
}
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
@ -780,7 +808,8 @@ onMounted(async () => {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
color: white;
|
color: white;
|
||||||
font-family: 'Microsoft YaHei', sans-serif;
|
font-family: 'Microsoft YaHei', sans-serif;
|
||||||
font-size: 1rem; /* 统一字体大小 */
|
font-size: 1rem;
|
||||||
|
/* 统一字体大小 */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 上部区域样式 */
|
/* 上部区域样式 */
|
||||||
|
@ -841,23 +870,27 @@ onMounted(async () => {
|
||||||
|
|
||||||
/* 标题和流量标签样式 */
|
/* 标题和流量标签样式 */
|
||||||
.juice-title {
|
.juice-title {
|
||||||
font-size: 1rem; /* 统一字体大小 */
|
font-size: 1rem;
|
||||||
|
/* 统一字体大小 */
|
||||||
margin: 0.5rem 0 0 1rem;
|
margin: 0.5rem 0 0 1rem;
|
||||||
/* 调整间距 */
|
/* 调整间距 */
|
||||||
}
|
}
|
||||||
|
|
||||||
.flow-label {
|
.flow-label {
|
||||||
font-size: 1rem; /* 统一字体大小 */
|
font-size: 1rem;
|
||||||
|
/* 统一字体大小 */
|
||||||
margin: 0.5rem 1rem 0 0;
|
margin: 0.5rem 1rem 0 0;
|
||||||
/* 调整间距 */
|
/* 调整间距 */
|
||||||
}
|
}
|
||||||
|
|
||||||
.juice-name {
|
.juice-name {
|
||||||
font-size: 1rem; /* 统一字体大小 */
|
font-size: 1rem;
|
||||||
|
/* 统一字体大小 */
|
||||||
}
|
}
|
||||||
|
|
||||||
.juice-value {
|
.juice-value {
|
||||||
font-size: 1rem; /* 统一字体大小 */
|
font-size: 1rem;
|
||||||
|
/* 统一字体大小 */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 进度条样式 */
|
/* 进度条样式 */
|
||||||
|
@ -936,7 +969,8 @@ onMounted(async () => {
|
||||||
|
|
||||||
/* 信息标签样式 */
|
/* 信息标签样式 */
|
||||||
.info-label {
|
.info-label {
|
||||||
font-size: 1rem; /* 统一字体大小 */
|
font-size: 1rem;
|
||||||
|
/* 统一字体大小 */
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1045,8 +1079,23 @@ onMounted(async () => {
|
||||||
text-align: center;
|
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 {
|
.drop-down {
|
||||||
width: calc(25% - 1.25rem);
|
width: calc(25% - 1.25rem);
|
||||||
background-color: #23233C;
|
background-color: #23233C;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.btnStyle {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -79,6 +79,7 @@ const toggleDatetimePicker = () => {
|
||||||
const handleReset = () => {
|
const handleReset = () => {
|
||||||
// 触发主页中的 getHistory 方法
|
// 触发主页中的 getHistory 方法
|
||||||
const event = new CustomEvent('reset');
|
const event = new CustomEvent('reset');
|
||||||
|
updateTime();
|
||||||
window.dispatchEvent(event);
|
window.dispatchEvent(event);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -103,10 +104,13 @@ const updateHistory = (datetime) => {
|
||||||
const hours = String(parsedDate.getHours()).padStart(2, '0');
|
const hours = String(parsedDate.getHours()).padStart(2, '0');
|
||||||
const minutes = String(parsedDate.getMinutes()).padStart(2, '0');
|
const minutes = String(parsedDate.getMinutes()).padStart(2, '0');
|
||||||
const seconds = String(parsedDate.getSeconds()).padStart(2, '0');
|
const seconds = String(parsedDate.getSeconds()).padStart(2, '0');
|
||||||
const formattedDatetime = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
|
const formattedDatetime = `${year}-${month}-${day} ${hours}:${minutes}`;
|
||||||
// 触发主页中的 getHistory 方法
|
// 触发主页中的 getHistory 方法
|
||||||
console.log("🚀 ~ updateHistory ~ formattedDatetime:", formattedDatetime)
|
console.log("🚀 ~ updateHistory ~ formattedDatetime:", formattedDatetime)
|
||||||
const event = new CustomEvent('update-history', { detail: formattedDatetime });
|
const event = new CustomEvent('update-history', { detail: formattedDatetime });
|
||||||
|
|
||||||
|
currentTime.value = formattedDatetime;
|
||||||
|
|
||||||
window.dispatchEvent(event);
|
window.dispatchEvent(event);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -214,6 +218,10 @@ ix-application {
|
||||||
/* 确保在最上层 */
|
/* 确保在最上层 */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:deep(ix-datetime-picker){
|
||||||
|
--theme-menu--background: #23233C;
|
||||||
|
}
|
||||||
|
|
||||||
:deep(IxApplication) {
|
:deep(IxApplication) {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
/* 确保没有外边距 */
|
/* 确保没有外边距 */
|
||||||
|
|
Loading…
Reference in New Issue