|
|
<template>
|
|
|
<div class="equipment-status-container">
|
|
|
<!-- 完成度筛选器 -->
|
|
|
<div class="filter-container">
|
|
|
<span class="filter-label">完成度筛选:</span>
|
|
|
<el-select v-model="selectedCompletionLevel" @change="onCompletionLevelChange" placeholder="请选择完成度" style="width: 150px">
|
|
|
<el-option label="全部显示" value="all" />
|
|
|
<el-option label="0% - 25%" value="low" />
|
|
|
<el-option label="25% - 75%" value="medium" />
|
|
|
<el-option label="75% - 100%" value="high" />
|
|
|
<el-option label="100%" value="full" />
|
|
|
</el-select>
|
|
|
</div>
|
|
|
|
|
|
<div class="equipment-grid">
|
|
|
<div v-for="equipment in sortedEquipmentData" :key="equipment.equipment_code" class="equipment-item">
|
|
|
<div class="equipment-header">
|
|
|
<span class="equipment-name">{{ equipment.equipment_code }}</span>
|
|
|
<span class="equipment-health" v-if="equipment.status_records && equipment.status_records.length > 0">
|
|
|
健康度: {{ equipment.status_records[0].health_score }}%
|
|
|
</span>
|
|
|
</div>
|
|
|
<div class="equipment-body">
|
|
|
<div class="axle-container">
|
|
|
<div class="axle-side left">
|
|
|
<div v-for="record in getLeftAxles(equipment)" :key="`${record.axle_number}-${record.specification || 'no-spec'}-${record.model || 'no-model'}-${record.update_time || 'no-data'}`"
|
|
|
v-show="shouldShowAxle(record.degree_of_completion)"
|
|
|
class="axle-item"
|
|
|
:class="[getCompletionClass(record.degree_of_completion), { 'not-running': !record.update_time }]"
|
|
|
@dblclick="showAxleDetails(record, equipment.equipment_code)">
|
|
|
<div class="axle-number">{{ record.axle_number }} <span v-if="!record.update_time" class="not-running-label">(不在运行)</span></div>
|
|
|
<div class="axle-spec" v-if="record.update_time">规格: {{ record.specification }}</div>
|
|
|
<div class="axle-model" v-if="record.update_time">型号: {{ record.model }}</div>
|
|
|
<div class="axle-disc" v-if="record.update_time">线盘: {{ record.raw_wire_disc || '无' }}</div>
|
|
|
<div class="axle-progress" v-if="record.update_time">完成度: {{ (record.degree_of_completion * 100).toFixed(0) }}%</div>
|
|
|
<div class="axle-stock" v-if="record.update_time && (record.total_number || record.total_net_weight || record.total_gross_weight)">
|
|
|
<div class="stock-item" v-if="record.total_number">总箱数: {{ record.total_number }}</div>
|
|
|
<div class="stock-item" v-if="record.total_net_weight">总净重: {{ record.total_net_weight }}kg</div>
|
|
|
<div class="stock-item" v-if="record.total_gross_weight">总毛重: {{ record.total_gross_weight }}kg</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
<div class="axle-side right">
|
|
|
<div v-for="record in getRightAxles(equipment)" :key="`${record.axle_number}-${record.specification || 'no-spec'}-${record.model || 'no-model'}-${record.update_time || 'no-data'}`"
|
|
|
v-show="shouldShowAxle(record.degree_of_completion)"
|
|
|
class="axle-item"
|
|
|
:class="[getCompletionClass(record.degree_of_completion), { 'not-running': !record.update_time }]"
|
|
|
@dblclick="showAxleDetails(record, equipment.equipment_code)">
|
|
|
<div class="axle-number">{{ record.axle_number }} <span v-if="!record.update_time" class="not-running-label">(不在运行)</span></div>
|
|
|
<div class="axle-spec" v-if="record.update_time">规格: {{ record.specification }}</div>
|
|
|
<div class="axle-model" v-if="record.update_time">型号: {{ record.model }}</div>
|
|
|
<div class="axle-disc" v-if="record.update_time">线盘: {{ record.raw_wire_disc || '无' }}</div>
|
|
|
<div class="axle-progress" v-if="record.update_time">完成度: {{ (record.degree_of_completion * 100).toFixed(0) }}%</div>
|
|
|
<div class="axle-stock" v-if="record.update_time && (record.total_number || record.total_net_weight || record.total_gross_weight)">
|
|
|
<div class="stock-item" v-if="record.total_number">总箱数: {{ record.total_number }}</div>
|
|
|
<div class="stock-item" v-if="record.total_net_weight">总净重: {{ record.total_net_weight }}kg</div>
|
|
|
<div class="stock-item" v-if="record.total_gross_weight">总毛重: {{ record.total_gross_weight }}kg</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
<div v-if="!isEquipmentRunning(equipment)" class="no-records">
|
|
|
设备当前未运行
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- 轴详情对话框 -->
|
|
|
<el-dialog
|
|
|
v-model="dialogVisible"
|
|
|
title="轴详细信息"
|
|
|
width="80%"
|
|
|
top="5vh"
|
|
|
:before-close="handleClose">
|
|
|
<div v-if="selectedAxle" class="axle-details-container">
|
|
|
<!-- 左侧:轴基本信息 -->
|
|
|
<div class="axle-info-panel">
|
|
|
<div class="detail-title">基本信息</div>
|
|
|
<div class="detail-row">
|
|
|
<span class="detail-label">设备编号:</span>
|
|
|
<span class="detail-value">{{ selectedEquipmentCode }}</span>
|
|
|
</div>
|
|
|
<div class="detail-row">
|
|
|
<span class="detail-label">轴号:</span>
|
|
|
<span class="detail-value">{{ selectedAxle.axle_number }}</span>
|
|
|
</div>
|
|
|
<div class="detail-row">
|
|
|
<span class="detail-label">运行状态:</span>
|
|
|
<span class="detail-value" :class="{ 'status-running': selectedAxle.update_time, 'status-stopped': !selectedAxle.update_time }">
|
|
|
{{ selectedAxle.update_time ? '运行中' : '不在运行' }}
|
|
|
</span>
|
|
|
</div>
|
|
|
<div v-if="selectedAxle.update_time" class="detail-row specification-row">
|
|
|
<span class="detail-label">规格:</span>
|
|
|
<span class="detail-value spec-item">{{ selectedAxle.specification || '无' }}</span>
|
|
|
<span class="detail-label model-label">型号:</span>
|
|
|
<span class="detail-value model-item">{{ selectedAxle.model || '无' }}</span>
|
|
|
<span class="detail-label disc-label">线盘:</span>
|
|
|
<span class="detail-value disc-item">{{ selectedAxle.raw_wire_disc || '无' }}</span>
|
|
|
</div>
|
|
|
<div v-if="selectedAxle.update_time" class="detail-row">
|
|
|
<span class="detail-label">完成度:</span>
|
|
|
<span class="detail-value">{{ (selectedAxle.degree_of_completion * 100).toFixed(1) }}%</span>
|
|
|
</div>
|
|
|
<div v-if="selectedAxle.update_time && (selectedAxle.total_number || selectedAxle.total_net_weight || selectedAxle.total_gross_weight)" class="stock-details">
|
|
|
<div class="detail-title">库存信息</div>
|
|
|
<div v-if="selectedAxle.total_number || selectedAxle.total_net_weight || selectedAxle.total_gross_weight" class="detail-row stock-row">
|
|
|
<span v-if="selectedAxle.total_number" class="detail-label stock-label">总箱数:</span>
|
|
|
<span v-if="selectedAxle.total_number" class="detail-value stock-item-number">{{ selectedAxle.total_number }}</span>
|
|
|
<span v-if="selectedAxle.total_net_weight" class="detail-label stock-label">总净重:</span>
|
|
|
<span v-if="selectedAxle.total_net_weight" class="detail-value stock-item-weight">{{ selectedAxle.total_net_weight }}kg</span>
|
|
|
<span v-if="selectedAxle.total_gross_weight" class="detail-label stock-label">总毛重:</span>
|
|
|
<span v-if="selectedAxle.total_gross_weight" class="detail-value stock-item-weight">{{ selectedAxle.total_gross_weight }}kg</span>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<div v-if="productionScheduleLoading" v-loading="true" class="production-schedule-loading"></div>
|
|
|
<div v-else-if="productionScheduleData" class="production-schedule-details">
|
|
|
<div class="detail-title">排产信息</div>
|
|
|
<div v-if="productionScheduleData.axle_final_average_speed" class="detail-row">
|
|
|
<span class="detail-label">产速:</span>
|
|
|
<span class="detail-value">{{ productionScheduleData.axle_final_average_speed }} kg/小时</span>
|
|
|
</div>
|
|
|
<div v-if="productionScheduleData.axle_final_average_weight" class="detail-row">
|
|
|
<span class="detail-label">每轴均重:</span>
|
|
|
<span class="detail-value">{{ productionScheduleData.axle_final_average_weight }}kg</span>
|
|
|
</div>
|
|
|
</div>
|
|
|
<div v-if="selectedAxle.update_time" class="current-order-details">
|
|
|
<div class="detail-title">当前订单</div>
|
|
|
<div class="detail-row">
|
|
|
<span class="detail-label">设备轴号:</span>
|
|
|
<span class="detail-value">{{ selectedAxle.axle_number }}</span>
|
|
|
</div>
|
|
|
<div v-if="selectedAxle.total_quantity" class="detail-row">
|
|
|
<span class="detail-label">总轴数:</span>
|
|
|
<el-input-number
|
|
|
v-model="editableTotalQuantity"
|
|
|
:min="1"
|
|
|
:precision="0"
|
|
|
size="small"
|
|
|
@change="onTotalQuantityChange"
|
|
|
class="editable-input"
|
|
|
></el-input-number>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<div v-if="selectedAxle.update_time && selectedAxle.total_quantity && productionScheduleData && productionScheduleData.axle_final_average_speed && productionScheduleData.axle_final_average_weight" class="calculation-details">
|
|
|
<div class="detail-title">生产计算</div>
|
|
|
<div class="detail-row">
|
|
|
<span class="detail-label">小时轴数:</span>
|
|
|
<span class="detail-value">{{ calculateAxlesPerHour() }} 轴</span>
|
|
|
</div>
|
|
|
<div v-if="selectedAxle.update_time && selectedAxle.total_quantity && productionScheduleData && productionScheduleData.axle_final_average_speed && productionScheduleData.axle_final_average_weight" class="detail-row stock-row">
|
|
|
<span class="detail-label stock-label">每天重量:</span>
|
|
|
<span class="detail-value stock-item-weight">{{ calculateDailyWeight() }} kg</span>
|
|
|
<span class="detail-label stock-label">每天轴数:</span>
|
|
|
<span class="detail-value stock-item-number">{{ calculateAxlesPerDay() }} 轴</span>
|
|
|
<span class="detail-label stock-label">每天箱数:</span>
|
|
|
<span class="detail-value stock-item-number">{{ calculateBoxesPerDay() }} 箱</span>
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<!-- 转移概率信息 -->
|
|
|
<div v-if="transitionProbabilitiesLoading" v-loading="true" class="transition-probabilities-loading"></div>
|
|
|
<div v-else-if="transitionProbabilitiesData && transitionProbabilitiesData.current_specification_transitions" class="transition-probabilities-details">
|
|
|
<div class="detail-title">当前规格的转移概率</div>
|
|
|
<div class="transition-probabilities-content">
|
|
|
<div v-for="(info, spec) in transitionProbabilitiesData.current_specification_transitions" :key="spec" class="transition-probability-item">
|
|
|
<span class="detail-label">到 {{ spec }}:</span>
|
|
|
<span class="detail-value">概率 {{ (info.probability * 100).toFixed(2) }}%, 转移次数 {{ info.count }}</span>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- 右侧:历史数据图表 -->
|
|
|
<div class="history-chart-panel">
|
|
|
<div class="chart-controls">
|
|
|
|
|
|
<div class="detail-title">历史数据分析</div>
|
|
|
<el-select v-model="selectedTimeRange" @change="onTimeRangeChange" style="width: 120px">
|
|
|
<el-option label="近1天" value="近1天" />
|
|
|
<el-option label="近3天" value="近3天" />
|
|
|
<el-option label="近1周" value="近1周" />
|
|
|
<el-option label="近1月" value="近1月" />
|
|
|
</el-select>
|
|
|
</div>
|
|
|
|
|
|
<div v-if="historyDataLoading || salesDataLoading" v-loading="true" class="chart-loading"></div>
|
|
|
|
|
|
<div v-else-if="!hasHistoryData && !hasSalesData" class="no-history-data">
|
|
|
暂无数据
|
|
|
</div>
|
|
|
|
|
|
<div v-else class="charts-container">
|
|
|
<!-- 图表选项卡 -->
|
|
|
<el-tabs v-model="activeChartTab" type="border-card" @tab-click="handleTabClick">
|
|
|
<el-tab-pane label="总箱数变化" name="totalNumber">
|
|
|
<div ref="totalNumberChartRef" class="chart"></div>
|
|
|
</el-tab-pane>
|
|
|
<el-tab-pane label="总净重变化" name="totalNetWeight">
|
|
|
<div ref="totalNetWeightChartRef" class="chart"></div>
|
|
|
</el-tab-pane>
|
|
|
<el-tab-pane label="总毛重变化" name="totalGrossWeight">
|
|
|
<div ref="totalGrossWeightChartRef" class="chart"></div>
|
|
|
</el-tab-pane>
|
|
|
<el-tab-pane label="销量变化" name="salesData">
|
|
|
<div ref="salesChartRef" class="chart"></div>
|
|
|
</el-tab-pane>
|
|
|
</el-tabs>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
<template #footer>
|
|
|
<span class="dialog-footer">
|
|
|
<el-button @click="dialogVisible = false">关闭</el-button>
|
|
|
</span>
|
|
|
</template>
|
|
|
</el-dialog>
|
|
|
</div>
|
|
|
</template>
|
|
|
|
|
|
<script setup>
|
|
|
import { defineProps, computed, ref, onMounted, nextTick, watch } from 'vue';
|
|
|
import { ElDialog, ElButton, ElSelect, ElOption, ElTabs, ElTabPane, ElInputNumber } from 'element-plus';
|
|
|
import { getDateRangeByTimeRange } from '@/utils/dateFormat';
|
|
|
import * as echarts from 'echarts';
|
|
|
import { API_CONFIG } from "@/config/api";
|
|
|
|
|
|
const props = defineProps({
|
|
|
equipmentData: {
|
|
|
type: Array,
|
|
|
required: true
|
|
|
}
|
|
|
});
|
|
|
|
|
|
// 设备代码与轴号的映射表
|
|
|
const equipmentAxlesMap = {
|
|
|
'QB001': ['左边', '右边'],
|
|
|
'QB002': ['左边', '右边'],
|
|
|
'QB003': ['左边', '右边'],
|
|
|
'QB004': ['左1', '左2', '左3', '左4', '左5', '右1', '右2', '右3', '右4', '右5'],
|
|
|
'QB0042': ['左1', '左2', '左3', '左4', '左5', '右1', '右2', '右3', '右4', '右5'],
|
|
|
'QB005': ['左1', '左2', '左3', '左4', '左5', '左6', '右1', '右2', '右3', '右4', '右5', '右6'],
|
|
|
'QB007': ['左边', '右边'],
|
|
|
'QB008': ['左边', '右边'],
|
|
|
'QB009': ['左边', '右边'],
|
|
|
'QB010': ['左边', '右边'],
|
|
|
'QB011': ['左边', '右边'],
|
|
|
'QB012': ['左边', '右边'],
|
|
|
'QB013': ['左边', '右边'],
|
|
|
'QB014': ['左边', '右边'],
|
|
|
'QB015': ['左边', '右边'],
|
|
|
'QB016': ['左边', '右边'],
|
|
|
'QB017': ['左边', '右边'],
|
|
|
'QB018': ['左1', '左2', '右1', '右2'],
|
|
|
'QB019': ['左1', '左2', '右1', '右2'],
|
|
|
'QB020': ['左1', '左2', '右1', '右2'],
|
|
|
'QB024': ['左1', '左2', '左3', '左4', '右1', '右2', '右3', '右4'],
|
|
|
'QB025': ['左边', '右边'],
|
|
|
'QB026': ['左边', '右边'],
|
|
|
'QB027': ['左边', '右边'],
|
|
|
'QB028': ['左1', '左2', '右1', '右2'],
|
|
|
'QB029': ['左边', '右边'],
|
|
|
'QB030': ['左边', '右边'],
|
|
|
'QB031': ['左边', '右边'],
|
|
|
'QB032': ['左边', '右边'],
|
|
|
'QB033': ['左边', '右边'],
|
|
|
'QB034': ['左边', '右边']
|
|
|
};
|
|
|
|
|
|
// 对设备数据进行排序,将不在生产中的设备移到最后
|
|
|
const sortedEquipmentData = computed(() => {
|
|
|
// 创建一个副本以避免修改原始数组
|
|
|
const equipmentCopy = [...props.equipmentData];
|
|
|
|
|
|
return equipmentCopy.sort((a, b) => {
|
|
|
// 使用新的判断函数检查设备是否在运行
|
|
|
const aIsRunning = isEquipmentRunning(a);
|
|
|
const bIsRunning = isEquipmentRunning(b);
|
|
|
|
|
|
// 如果a在运行而b不在运行,a排在前面
|
|
|
if (aIsRunning && !bIsRunning) return -1;
|
|
|
// 如果a不在运行而b在运行,b排在前面
|
|
|
if (!aIsRunning && bIsRunning) return 1;
|
|
|
// 如果都在运行或都不在运行,按设备编号排序
|
|
|
return a.equipment_code.localeCompare(b.equipment_code);
|
|
|
});
|
|
|
});
|
|
|
|
|
|
// 获取左侧轴信息
|
|
|
const getLeftAxles = (equipment) => {
|
|
|
if (!equipment.status_records || equipment.status_records.length === 0) {
|
|
|
// 如果没有运行记录,返回所有左侧轴的未运行状态
|
|
|
const validAxles = equipmentAxlesMap[equipment.equipment_code] || [];
|
|
|
return validAxles.filter(axle => axle.includes('左') || axle.includes('left')).map(axleNumber => ({
|
|
|
axle_number: axleNumber,
|
|
|
specification: '',
|
|
|
model: '',
|
|
|
raw_wire_disc: '',
|
|
|
degree_of_completion: 0,
|
|
|
total_number: null,
|
|
|
total_net_weight: null,
|
|
|
total_gross_weight: null,
|
|
|
isRunning: false
|
|
|
}));
|
|
|
}
|
|
|
|
|
|
// 获取设备对应的预期轴号列表
|
|
|
const validAxles = equipmentAxlesMap[equipment.equipment_code] || [];
|
|
|
const validLeftAxles = validAxles.filter(axle =>
|
|
|
axle.includes('左') || axle.includes('left')
|
|
|
);
|
|
|
|
|
|
// 存储所有的轴记录,包括重复的轴号
|
|
|
const resultAxles = [];
|
|
|
const processedAxles = new Set(); // 用于跟踪已处理的轴号
|
|
|
|
|
|
// 处理运行中的记录
|
|
|
for (const record of equipment.status_records) {
|
|
|
if (!record.axle_number) continue;
|
|
|
|
|
|
// 检查记录中包含哪些左侧轴
|
|
|
for (const axleNumber of validLeftAxles) {
|
|
|
if (record.axle_number.includes(axleNumber)) {
|
|
|
// 对于复合轴号(如"左1,左2")需要特殊处理
|
|
|
if (record.axle_number.includes(',')) {
|
|
|
// 分割复合轴号,检查是否包含当前轴号
|
|
|
const axleNumbers = record.axle_number.split(',');
|
|
|
if (axleNumbers.includes(axleNumber)) {
|
|
|
resultAxles.push({
|
|
|
...record,
|
|
|
axle_number: axleNumber, // 使用单个轴号
|
|
|
axle_count: axleNumbers.length // 更新轴数为实际数量
|
|
|
});
|
|
|
processedAxles.add(axleNumber);
|
|
|
}
|
|
|
} else {
|
|
|
// 单个轴号
|
|
|
resultAxles.push({
|
|
|
...record,
|
|
|
axle_number: axleNumber // 使用实际的轴号
|
|
|
});
|
|
|
processedAxles.add(axleNumber);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 添加未运行的左侧轴
|
|
|
for (const axleNumber of validLeftAxles) {
|
|
|
if (!processedAxles.has(axleNumber)) {
|
|
|
resultAxles.push({
|
|
|
axle_number: axleNumber,
|
|
|
specification: '',
|
|
|
model: '',
|
|
|
raw_wire_disc: '',
|
|
|
degree_of_completion: 0,
|
|
|
total_number: null,
|
|
|
total_net_weight: null,
|
|
|
total_gross_weight: null,
|
|
|
isRunning: false
|
|
|
});
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 按照有效轴号列表的顺序排序,相同轴号的记录会排在一起
|
|
|
return resultAxles.sort((a, b) => {
|
|
|
const aIndex = validLeftAxles.indexOf(a.axle_number);
|
|
|
const bIndex = validLeftAxles.indexOf(b.axle_number);
|
|
|
if (aIndex !== bIndex) {
|
|
|
return aIndex - bIndex;
|
|
|
}
|
|
|
// 如果轴号相同,按更新时间排序(最新的在前)
|
|
|
if (a.update_time && b.update_time) {
|
|
|
return new Date(b.update_time) - new Date(a.update_time);
|
|
|
}
|
|
|
// 如果一个有更新时间一个没有,有更新时间的在前
|
|
|
return a.update_time ? -1 : 1;
|
|
|
});
|
|
|
};
|
|
|
|
|
|
// 获取右侧轴信息
|
|
|
const getRightAxles = (equipment) => {
|
|
|
if (!equipment.status_records || equipment.status_records.length === 0) {
|
|
|
// 如果没有运行记录,返回所有右侧轴的未运行状态
|
|
|
const validAxles = equipmentAxlesMap[equipment.equipment_code] || [];
|
|
|
return validAxles.filter(axle => axle.includes('右') || axle.includes('right')).map(axleNumber => ({
|
|
|
axle_number: axleNumber,
|
|
|
specification: '',
|
|
|
model: '',
|
|
|
raw_wire_disc: '',
|
|
|
degree_of_completion: 0,
|
|
|
total_number: null,
|
|
|
total_net_weight: null,
|
|
|
total_gross_weight: null,
|
|
|
isRunning: false
|
|
|
}));
|
|
|
}
|
|
|
|
|
|
// 获取设备对应的预期轴号列表
|
|
|
const validAxles = equipmentAxlesMap[equipment.equipment_code] || [];
|
|
|
const validRightAxles = validAxles.filter(axle =>
|
|
|
axle.includes('右') || axle.includes('right')
|
|
|
);
|
|
|
|
|
|
// 存储所有的轴记录,包括重复的轴号
|
|
|
const resultAxles = [];
|
|
|
const processedAxles = new Set(); // 用于跟踪已处理的轴号
|
|
|
|
|
|
// 处理运行中的记录
|
|
|
for (const record of equipment.status_records) {
|
|
|
if (!record.axle_number) continue;
|
|
|
|
|
|
// 检查记录中包含哪些右侧轴
|
|
|
for (const axleNumber of validRightAxles) {
|
|
|
if (record.axle_number.includes(axleNumber)) {
|
|
|
// 对于复合轴号(如"右1,右2")需要特殊处理
|
|
|
if (record.axle_number.includes(',')) {
|
|
|
// 分割复合轴号,检查是否包含当前轴号
|
|
|
const axleNumbers = record.axle_number.split(',');
|
|
|
if (axleNumbers.includes(axleNumber)) {
|
|
|
resultAxles.push({
|
|
|
...record,
|
|
|
axle_number: axleNumber, // 使用单个轴号
|
|
|
axle_count: axleNumbers.length // 更新轴数为实际数量
|
|
|
});
|
|
|
processedAxles.add(axleNumber);
|
|
|
}
|
|
|
} else {
|
|
|
// 单个轴号
|
|
|
resultAxles.push({
|
|
|
...record,
|
|
|
axle_number: axleNumber // 使用实际的轴号
|
|
|
});
|
|
|
processedAxles.add(axleNumber);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 添加未运行的右侧轴
|
|
|
for (const axleNumber of validRightAxles) {
|
|
|
if (!processedAxles.has(axleNumber)) {
|
|
|
resultAxles.push({
|
|
|
axle_number: axleNumber,
|
|
|
specification: '',
|
|
|
model: '',
|
|
|
raw_wire_disc: '',
|
|
|
degree_of_completion: 0,
|
|
|
total_number: null,
|
|
|
total_net_weight: null,
|
|
|
total_gross_weight: null,
|
|
|
isRunning: false
|
|
|
});
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 按照有效轴号列表的顺序排序,相同轴号的记录会排在一起
|
|
|
return resultAxles.sort((a, b) => {
|
|
|
const aIndex = validRightAxles.indexOf(a.axle_number);
|
|
|
const bIndex = validRightAxles.indexOf(b.axle_number);
|
|
|
if (aIndex !== bIndex) {
|
|
|
return aIndex - bIndex;
|
|
|
}
|
|
|
// 如果轴号相同,按更新时间排序(最新的在前)
|
|
|
if (a.update_time && b.update_time) {
|
|
|
return new Date(b.update_time) - new Date(a.update_time);
|
|
|
}
|
|
|
// 如果一个有更新时间一个没有,有更新时间的在前
|
|
|
return a.update_time ? -1 : 1;
|
|
|
});
|
|
|
};
|
|
|
|
|
|
// 检查设备是否在运行
|
|
|
const isEquipmentRunning = (equipment) => {
|
|
|
if (!equipment.status_records || equipment.status_records.length === 0) return false;
|
|
|
|
|
|
// 获取设备对应的预期轴号列表
|
|
|
const validAxles = equipmentAxlesMap[equipment.equipment_code] || [];
|
|
|
|
|
|
// 检查是否有至少一个有效轴号在status_records中
|
|
|
return equipment.status_records.some(record =>
|
|
|
record.axle_number && validAxles.some(axle => record.axle_number.includes(axle))
|
|
|
);
|
|
|
};
|
|
|
|
|
|
// 提取轴号中的数字
|
|
|
const extractAxleNumber = (axleStr) => {
|
|
|
if (!axleStr) return Infinity;
|
|
|
|
|
|
// 使用正则表达式提取数字,如从"左1"中提取出1
|
|
|
const match = axleStr.match(/(\d+)/);
|
|
|
if (match) {
|
|
|
return parseInt(match[0], 10);
|
|
|
}
|
|
|
|
|
|
// 如果没有数字,例如只有"左边"或"右边",则使用默认值
|
|
|
if (axleStr.includes('左') || axleStr.includes('left')) return 0;
|
|
|
if (axleStr.includes('右') || axleStr.includes('right')) return 0;
|
|
|
|
|
|
return Infinity;
|
|
|
};
|
|
|
|
|
|
// 根据完成度返回对应的CSS类名
|
|
|
const getCompletionClass = (degree) => {
|
|
|
const percentage = degree * 100;
|
|
|
if (percentage < 25) return 'completion-low';
|
|
|
if (percentage < 75) return 'completion-medium';
|
|
|
if (percentage < 100) return 'completion-high';
|
|
|
return 'completion-full';
|
|
|
};
|
|
|
|
|
|
// 对话框相关状态
|
|
|
const dialogVisible = ref(false);
|
|
|
const selectedAxle = ref(null);
|
|
|
const selectedEquipmentCode = ref('');
|
|
|
|
|
|
// 历史数据相关状态
|
|
|
const historyDataLoading = ref(false);
|
|
|
const historyData = ref([]);
|
|
|
const selectedTimeRange = ref('近1月'); // 默认时间范围
|
|
|
const activeChartTab = ref('totalNumber');
|
|
|
const hasHistoryData = ref(false);
|
|
|
|
|
|
// 销量数据相关状态
|
|
|
const salesDataLoading = ref(false);
|
|
|
const salesData = ref([]);
|
|
|
const hasSalesData = ref(false);
|
|
|
|
|
|
// 完成度筛选相关状态
|
|
|
const selectedCompletionLevel = ref('all'); // 默认显示全部
|
|
|
|
|
|
// 排产信息相关状态
|
|
|
const productionScheduleData = ref(null);
|
|
|
const productionScheduleLoading = ref(false);
|
|
|
|
|
|
// 转移概率相关状态
|
|
|
const transitionProbabilitiesData = ref(null);
|
|
|
const transitionProbabilitiesLoading = ref(false);
|
|
|
|
|
|
// 可编辑的总轴数
|
|
|
const editableTotalQuantity = ref(0);
|
|
|
|
|
|
// 图表引用
|
|
|
const totalNumberChartRef = ref(null);
|
|
|
const totalNetWeightChartRef = ref(null);
|
|
|
const totalGrossWeightChartRef = ref(null);
|
|
|
const salesChartRef = ref(null);
|
|
|
|
|
|
// 图表实例
|
|
|
let totalNumberChart = null;
|
|
|
let totalNetWeightChart = null;
|
|
|
let totalGrossWeightChart = null;
|
|
|
let salesChart = null;
|
|
|
|
|
|
// 显示轴详情对话框
|
|
|
const showAxleDetails = (record, equipmentCode) => {
|
|
|
selectedAxle.value = { ...record };
|
|
|
selectedEquipmentCode.value = equipmentCode;
|
|
|
dialogVisible.value = true;
|
|
|
|
|
|
// 重置排产信息
|
|
|
productionScheduleData.value = null;
|
|
|
|
|
|
// 如果轴在运行且有规格和线盘信息,则获取历史数据和排产信息
|
|
|
if (record.update_time && record.model && record.specification && record.wire_disc) {
|
|
|
fetchHistoryData(record.model, record.specification, record.wire_disc);
|
|
|
fetchProductionScheduleData(record.model, record.specification, record.wire_disc, equipmentCode, record.axle_number);
|
|
|
fetchSalesData(record.model, record.specification);
|
|
|
fetchTransitionProbabilitiesData(equipmentCode, record.axle_number, record.model, record.specification, record.wire_disc);
|
|
|
} else {
|
|
|
hasHistoryData.value = false;
|
|
|
}
|
|
|
|
|
|
// 在对话框显示后初始化图表
|
|
|
nextTick(() => {
|
|
|
initCharts();
|
|
|
});
|
|
|
};
|
|
|
|
|
|
// 关闭对话框
|
|
|
const handleClose = () => {
|
|
|
dialogVisible.value = false;
|
|
|
// 重置转移概率数据
|
|
|
transitionProbabilitiesData.value = null;
|
|
|
// 销毁图表实例
|
|
|
if (totalNumberChart) {
|
|
|
totalNumberChart.dispose();
|
|
|
totalNumberChart = null;
|
|
|
}
|
|
|
if (totalNetWeightChart) {
|
|
|
totalNetWeightChart.dispose();
|
|
|
totalNetWeightChart = null;
|
|
|
}
|
|
|
if (totalGrossWeightChart) {
|
|
|
totalGrossWeightChart.dispose();
|
|
|
totalGrossWeightChart = null;
|
|
|
}
|
|
|
if (salesChart) {
|
|
|
salesChart.dispose();
|
|
|
salesChart = null;
|
|
|
}
|
|
|
};
|
|
|
|
|
|
// 监听对话框打开状态
|
|
|
watch(dialogVisible, (newValue) => {
|
|
|
if (newValue) {
|
|
|
// 对话框打开后,确保图表容器已渲染
|
|
|
setTimeout(() => {
|
|
|
initCharts();
|
|
|
|
|
|
// 如果已有历史数据,则更新图表
|
|
|
if (hasHistoryData.value && historyData.value.length > 0) {
|
|
|
// 再次延迟以确保图表容器完全初始化
|
|
|
setTimeout(() => {
|
|
|
updateCharts();
|
|
|
}, 300);
|
|
|
}
|
|
|
|
|
|
// 如果已有销量数据,则更新图表
|
|
|
if (hasSalesData.value && salesData.value.length > 0) {
|
|
|
// 再次延迟以确保图表容器完全初始化
|
|
|
setTimeout(() => {
|
|
|
updateCharts();
|
|
|
}, 300);
|
|
|
}
|
|
|
}, 300);
|
|
|
}
|
|
|
});
|
|
|
|
|
|
// 时间范围改变时获取新的历史数据
|
|
|
const onTimeRangeChange = () => {
|
|
|
if (selectedAxle.value && selectedAxle.value.update_time &&
|
|
|
selectedAxle.value.specification && selectedAxle.value.wire_disc) {
|
|
|
fetchHistoryData(
|
|
|
selectedAxle.value.model,
|
|
|
selectedAxle.value.specification,
|
|
|
selectedAxle.value.wire_disc
|
|
|
);
|
|
|
fetchSalesData(
|
|
|
selectedAxle.value.model,
|
|
|
selectedAxle.value.specification
|
|
|
);
|
|
|
}
|
|
|
};
|
|
|
|
|
|
// 选项卡切换处理函数
|
|
|
const handleTabClick = (tab) => {
|
|
|
// 使用setTimeout确保DOM完全渲染后再操作图表
|
|
|
setTimeout(() => {
|
|
|
if (tab.props.name === 'totalNumber' && !totalNumberChart && totalNumberChartRef.value) {
|
|
|
totalNumberChart = echarts.init(totalNumberChartRef.value);
|
|
|
updateCharts();
|
|
|
} else if (tab.props.name === 'totalNetWeight' && !totalNetWeightChart && totalNetWeightChartRef.value) {
|
|
|
totalNetWeightChart = echarts.init(totalNetWeightChartRef.value);
|
|
|
updateCharts();
|
|
|
} else if (tab.props.name === 'totalGrossWeight' && !totalGrossWeightChart && totalGrossWeightChartRef.value) {
|
|
|
totalGrossWeightChart = echarts.init(totalGrossWeightChartRef.value);
|
|
|
updateCharts();
|
|
|
} else if (tab.props.name === 'salesData' && !salesChart && salesChartRef.value) {
|
|
|
salesChart = echarts.init(salesChartRef.value);
|
|
|
updateCharts();
|
|
|
}
|
|
|
|
|
|
// 强制图表重新渲染
|
|
|
if (tab.props.name === 'totalNumber' && totalNumberChart) {
|
|
|
totalNumberChart.resize();
|
|
|
} else if (tab.props.name === 'totalNetWeight' && totalNetWeightChart) {
|
|
|
totalNetWeightChart.resize();
|
|
|
} else if (tab.props.name === 'totalGrossWeight' && totalGrossWeightChart) {
|
|
|
totalGrossWeightChart.resize();
|
|
|
} else if (tab.props.name === 'salesData' && salesChart) {
|
|
|
salesChart.resize();
|
|
|
}
|
|
|
}, 100);
|
|
|
};
|
|
|
|
|
|
// 获取历史数据
|
|
|
const fetchHistoryData = (model, specification, wireDisc) => {
|
|
|
historyDataLoading.value = true;
|
|
|
|
|
|
// 获取日期范围
|
|
|
const dateRange = getDateRangeByTimeRange(selectedTimeRange.value);
|
|
|
|
|
|
// 构建API URL
|
|
|
let apiUrl = `${API_CONFIG.BASE_URL}/api/plan/wms/history?model=${model}&specification=${specification}&wire_disc=${wireDisc}&start_date=${dateRange.start_date}&end_date=${dateRange.end_date}`;
|
|
|
|
|
|
fetch(apiUrl)
|
|
|
.then(response => response.json())
|
|
|
.then(data => {
|
|
|
if (data.code === 200 && data.data && data.data.records) {
|
|
|
historyData.value = data.data.records;
|
|
|
hasHistoryData.value = historyData.value.length > 0;
|
|
|
|
|
|
if (hasHistoryData.value) {
|
|
|
// 使用setTimeout确保DOM完全渲染后再初始化图表
|
|
|
setTimeout(() => {
|
|
|
initCharts();
|
|
|
|
|
|
// 再次延迟以确保图表容器完全初始化
|
|
|
setTimeout(() => {
|
|
|
updateCharts();
|
|
|
}, 300);
|
|
|
}, 300);
|
|
|
}
|
|
|
} else {
|
|
|
historyData.value = [];
|
|
|
hasHistoryData.value = false;
|
|
|
}
|
|
|
})
|
|
|
.catch(error => {
|
|
|
console.error('历史数据API调用失败:', error);
|
|
|
historyData.value = [];
|
|
|
hasHistoryData.value = false;
|
|
|
})
|
|
|
.finally(() => {
|
|
|
historyDataLoading.value = false;
|
|
|
});
|
|
|
};
|
|
|
|
|
|
// 获取销量数据
|
|
|
const fetchSalesData = (model, specification) => {
|
|
|
salesDataLoading.value = true;
|
|
|
|
|
|
// 获取日期范围
|
|
|
const dateRange = getDateRangeByTimeRange(selectedTimeRange.value);
|
|
|
|
|
|
// 构建API URL
|
|
|
let apiUrl = `${API_CONFIG.BASE_URL}/api/sale/records?model=${model}&specification=${specification}&start_date=${dateRange.start_date}&end_date=${dateRange.end_date}`;
|
|
|
|
|
|
fetch(apiUrl)
|
|
|
.then(response => response.json())
|
|
|
.then(data => {
|
|
|
if (data.code === 200 && data.data) {
|
|
|
salesData.value = data.data;
|
|
|
hasSalesData.value = salesData.value.length > 0;
|
|
|
|
|
|
if (hasSalesData.value) {
|
|
|
// 使用setTimeout确保DOM完全渲染后再初始化图表
|
|
|
setTimeout(() => {
|
|
|
initCharts();
|
|
|
|
|
|
// 再次延迟以确保图表容器完全初始化
|
|
|
setTimeout(() => {
|
|
|
updateCharts();
|
|
|
}, 300);
|
|
|
}, 300);
|
|
|
}
|
|
|
} else {
|
|
|
salesData.value = [];
|
|
|
hasSalesData.value = false;
|
|
|
}
|
|
|
})
|
|
|
.catch(error => {
|
|
|
console.error('销量数据API调用失败:', error);
|
|
|
salesData.value = [];
|
|
|
hasSalesData.value = false;
|
|
|
})
|
|
|
.finally(() => {
|
|
|
salesDataLoading.value = false;
|
|
|
});
|
|
|
};
|
|
|
|
|
|
// 获取排产信息数据
|
|
|
const fetchProductionScheduleData = (model, specification, wireDisc, equipmentCode, axleNumber) => {
|
|
|
productionScheduleLoading.value = true;
|
|
|
|
|
|
// 构建API URL
|
|
|
let apiUrl = `${API_CONFIG.BASE_URL}/api/plan/qb_machine_unit_stats?model=${model}&specification=${specification}&wire_disc=${wireDisc}`;
|
|
|
|
|
|
fetch(apiUrl)
|
|
|
.then(response => response.json())
|
|
|
.then(data => {
|
|
|
if (data.code === 200 && data.data && data.data.qb_machine_unit_stats && data.data.qb_machine_unit_stats.data) {
|
|
|
// 构建匹配键 "equipment_code-axle_number"
|
|
|
const matchKey = `${equipmentCode}-${axleNumber}`;
|
|
|
|
|
|
// 查找匹配的数据
|
|
|
if (data.data.qb_machine_unit_stats.data[matchKey]) {
|
|
|
productionScheduleData.value = data.data.qb_machine_unit_stats.data[matchKey];
|
|
|
} else {
|
|
|
console.log(`未找到匹配的排产数据: ${matchKey}`);
|
|
|
productionScheduleData.value = null;
|
|
|
}
|
|
|
} else {
|
|
|
console.log('排产信息API返回格式不正确或无数据');
|
|
|
productionScheduleData.value = null;
|
|
|
}
|
|
|
})
|
|
|
.catch(error => {
|
|
|
console.error('排产信息API调用失败:', error);
|
|
|
productionScheduleData.value = null;
|
|
|
})
|
|
|
.finally(() => {
|
|
|
productionScheduleLoading.value = false;
|
|
|
});
|
|
|
};
|
|
|
|
|
|
// 获取转移概率数据
|
|
|
const fetchTransitionProbabilitiesData = (equipmentCode, axleNumber, model, specification, wireDisc) => {
|
|
|
transitionProbabilitiesLoading.value = true;
|
|
|
|
|
|
// 使用URL编码处理参数,特别是中文和特殊字符
|
|
|
const params = new URLSearchParams({
|
|
|
equipment_code: equipmentCode,
|
|
|
axle_number: axleNumber,
|
|
|
model: model,
|
|
|
specification: specification,
|
|
|
wire_disc: wireDisc
|
|
|
});
|
|
|
|
|
|
// 构建API URL
|
|
|
let apiUrl = `${API_CONFIG.BASE_URL}/api/plan/transition/probabilities?${params.toString()}`;
|
|
|
|
|
|
console.log('转移概率API URL:', apiUrl);
|
|
|
|
|
|
fetch(apiUrl)
|
|
|
.then(response => {
|
|
|
if (!response.ok) {
|
|
|
throw new Error(`HTTP error! Status: ${response.status}`);
|
|
|
}
|
|
|
return response.json();
|
|
|
})
|
|
|
.then(data => {
|
|
|
console.log('转移概率API响应:', data);
|
|
|
if (data.code === 200 && data.data) {
|
|
|
transitionProbabilitiesData.value = data.data;
|
|
|
} else {
|
|
|
transitionProbabilitiesData.value = null;
|
|
|
}
|
|
|
})
|
|
|
.catch(error => {
|
|
|
console.error('转移概率API调用失败:', error);
|
|
|
transitionProbabilitiesData.value = null;
|
|
|
})
|
|
|
.finally(() => {
|
|
|
transitionProbabilitiesLoading.value = false;
|
|
|
});
|
|
|
};
|
|
|
|
|
|
// 计算小时轴数 = 总轴数 * 产速 / 每轴均重
|
|
|
const calculateAxlesPerHour = () => {
|
|
|
if (!selectedAxle.value || !editableTotalQuantity.value ||
|
|
|
!productionScheduleData.value || !productionScheduleData.value.axle_final_average_speed ||
|
|
|
!productionScheduleData.value.axle_final_average_weight) {
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
const totalQuantity = editableTotalQuantity.value;
|
|
|
const speed = productionScheduleData.value.axle_final_average_speed;
|
|
|
const weightPerAxle = productionScheduleData.value.axle_final_average_weight;
|
|
|
|
|
|
// 公式:总轴数 * 产速 / 每轴均重
|
|
|
const result = (totalQuantity * speed / weightPerAxle);
|
|
|
return result.toFixed(2);
|
|
|
};
|
|
|
|
|
|
// 计算每天轴数 = 小时轴数 * 24
|
|
|
const calculateAxlesPerDay = () => {
|
|
|
const axlesPerHour = calculateAxlesPerHour();
|
|
|
return (axlesPerHour * 24).toFixed(2);
|
|
|
};
|
|
|
|
|
|
// 计算每天重量 = 产速 * 24小时 * 总轴数
|
|
|
const calculateDailyWeight = () => {
|
|
|
if (!productionScheduleData.value || !productionScheduleData.value.axle_final_average_speed || !editableTotalQuantity.value) {
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
const speed = productionScheduleData.value.axle_final_average_speed;
|
|
|
const totalQuantity = editableTotalQuantity.value;
|
|
|
// 公式:产速 * 24小时 * 总轴数
|
|
|
const result = speed * 24 * totalQuantity;
|
|
|
return result.toFixed(2);
|
|
|
};
|
|
|
|
|
|
// 计算每天箱数,根据wire_disc进行判断
|
|
|
const calculateBoxesPerDay = () => {
|
|
|
const axlesPerDay = calculateAxlesPerDay();
|
|
|
// 如果没有选中轴或者没有线盘信息,返回0
|
|
|
if (!selectedAxle.value || !selectedAxle.value.raw_wire_disc) {
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
// 确保axlesPerDay是数字类型
|
|
|
const axlesPerDayNum = parseFloat(axlesPerDay);
|
|
|
if (isNaN(axlesPerDayNum)) {
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
// 根据wire_disc判断计算方法
|
|
|
if (selectedAxle.value.wire_disc === 'PT-4') {
|
|
|
// 如果是'PT-4',则每天轴数/4
|
|
|
return (axlesPerDayNum / 4).toFixed(2);
|
|
|
} else {
|
|
|
// 其他情况,每天轴数/1
|
|
|
return axlesPerDayNum.toFixed(2);
|
|
|
}
|
|
|
};
|
|
|
|
|
|
// 总轴数变更处理函数
|
|
|
const onTotalQuantityChange = (newValue) => {
|
|
|
// 更新计算结果
|
|
|
// Vue的响应式系统会自动更新,无需手动触发
|
|
|
};
|
|
|
|
|
|
// 监听选中轴的变化,初始化可编辑总轴数
|
|
|
watch(selectedAxle, (newVal) => {
|
|
|
if (newVal && newVal.total_quantity) {
|
|
|
editableTotalQuantity.value = newVal.total_quantity;
|
|
|
}
|
|
|
}, { immediate: true });
|
|
|
|
|
|
// 完成度筛选变化处理函数
|
|
|
const onCompletionLevelChange = () => {
|
|
|
// 筛选逻辑通过shouldShowAxle函数处理,这里不需要额外操作
|
|
|
};
|
|
|
|
|
|
// 判断是否应该显示轴
|
|
|
const shouldShowAxle = (degree) => {
|
|
|
if (!degree) return false;
|
|
|
|
|
|
const percentage = degree * 100;
|
|
|
|
|
|
switch (selectedCompletionLevel.value) {
|
|
|
case 'all':
|
|
|
return true;
|
|
|
case 'low':
|
|
|
return percentage >= 0 && percentage < 25;
|
|
|
case 'medium':
|
|
|
return percentage >= 25 && percentage < 75;
|
|
|
case 'high':
|
|
|
return percentage >= 75 && percentage < 100;
|
|
|
case 'full':
|
|
|
return percentage >= 100;
|
|
|
default:
|
|
|
return true;
|
|
|
}
|
|
|
};
|
|
|
|
|
|
// 初始化图表
|
|
|
const initCharts = () => {
|
|
|
// 销毁已有图表实例
|
|
|
if (totalNumberChart) {
|
|
|
totalNumberChart.dispose();
|
|
|
totalNumberChart = null;
|
|
|
}
|
|
|
if (totalNetWeightChart) {
|
|
|
totalNetWeightChart.dispose();
|
|
|
totalNetWeightChart = null;
|
|
|
}
|
|
|
if (totalGrossWeightChart) {
|
|
|
totalGrossWeightChart.dispose();
|
|
|
totalGrossWeightChart = null;
|
|
|
}
|
|
|
if (salesChart) {
|
|
|
salesChart.dispose();
|
|
|
salesChart = null;
|
|
|
}
|
|
|
|
|
|
// 初始化新图表实例
|
|
|
if (totalNumberChartRef.value) {
|
|
|
totalNumberChart = echarts.init(totalNumberChartRef.value);
|
|
|
}
|
|
|
|
|
|
if (totalNetWeightChartRef.value) {
|
|
|
totalNetWeightChart = echarts.init(totalNetWeightChartRef.value);
|
|
|
}
|
|
|
|
|
|
if (totalGrossWeightChartRef.value) {
|
|
|
totalGrossWeightChart = echarts.init(totalGrossWeightChartRef.value);
|
|
|
}
|
|
|
|
|
|
if (salesChartRef.value) {
|
|
|
salesChart = echarts.init(salesChartRef.value);
|
|
|
}
|
|
|
};
|
|
|
|
|
|
// 更新图表数据
|
|
|
const updateCharts = () => {
|
|
|
// 确保图表容器已经渲染完成
|
|
|
nextTick(() => {
|
|
|
// 初始化或重新初始化图表实例
|
|
|
if (!totalNumberChart && totalNumberChartRef.value) {
|
|
|
totalNumberChart = echarts.init(totalNumberChartRef.value);
|
|
|
}
|
|
|
if (!totalNetWeightChart && totalNetWeightChartRef.value) {
|
|
|
totalNetWeightChart = echarts.init(totalNetWeightChartRef.value);
|
|
|
}
|
|
|
if (!totalGrossWeightChart && totalGrossWeightChartRef.value) {
|
|
|
totalGrossWeightChart = echarts.init(totalGrossWeightChartRef.value);
|
|
|
}
|
|
|
if (!salesChart && salesChartRef.value) {
|
|
|
salesChart = echarts.init(salesChartRef.value);
|
|
|
}
|
|
|
|
|
|
// 更新总箱数图表
|
|
|
if (totalNumberChart && historyData.value && historyData.value.length > 0) {
|
|
|
const timeData = historyData.value.map(item => {
|
|
|
const date = new Date(item.create_time);
|
|
|
return `${date.getMonth() + 1}/${date.getDate()} ${date.getHours()}:${date.getMinutes().toString().padStart(2, '0')}`;
|
|
|
});
|
|
|
|
|
|
const totalNumberData = historyData.value.map(item => item.total_number);
|
|
|
|
|
|
// 准备销量数据,按日期聚合
|
|
|
let salesBoxCountData = [];
|
|
|
if (salesData.value && salesData.value.length > 0) {
|
|
|
// 创建一个映射,键为日期,值为当天总箱数
|
|
|
const salesBoxCountMap = {};
|
|
|
salesData.value.forEach(item => {
|
|
|
const date = new Date(item.date);
|
|
|
const dateStr = `${date.getMonth() + 1}/${date.getDate()}`;
|
|
|
if (!salesBoxCountMap[dateStr]) {
|
|
|
salesBoxCountMap[dateStr] = 0;
|
|
|
}
|
|
|
salesBoxCountMap[dateStr] += item.box_count;
|
|
|
});
|
|
|
|
|
|
// 将销量数据映射到时间轴上
|
|
|
salesBoxCountData = timeData.map(timeStr => {
|
|
|
// 从时间字符串中提取日期部分(去掉时间部分)
|
|
|
const dateStr = timeStr.split(' ')[0];
|
|
|
return salesBoxCountMap[dateStr] || 0;
|
|
|
});
|
|
|
}
|
|
|
|
|
|
const totalNumberOption = {
|
|
|
title: {
|
|
|
text: '总箱数变化趋势',
|
|
|
left: 'center',
|
|
|
textStyle: {
|
|
|
fontSize: 16
|
|
|
}
|
|
|
},
|
|
|
tooltip: {
|
|
|
trigger: 'axis',
|
|
|
formatter: function(params) {
|
|
|
let result = params[0].name + '<br/>';
|
|
|
params.forEach(param => {
|
|
|
result += `${param.seriesName}: ${param.value} 箱<br/>`;
|
|
|
});
|
|
|
return result;
|
|
|
}
|
|
|
},
|
|
|
legend: {
|
|
|
data: ['生产箱数', '销量箱数'],
|
|
|
top: 30
|
|
|
},
|
|
|
grid: {
|
|
|
left: '3%',
|
|
|
right: '4%',
|
|
|
bottom: '3%',
|
|
|
top: 60,
|
|
|
containLabel: true
|
|
|
},
|
|
|
xAxis: {
|
|
|
type: 'category',
|
|
|
data: timeData,
|
|
|
axisLabel: {
|
|
|
rotate: 45
|
|
|
}
|
|
|
},
|
|
|
yAxis: {
|
|
|
type: 'value',
|
|
|
name: '箱数'
|
|
|
},
|
|
|
series: [
|
|
|
{
|
|
|
name: '生产箱数',
|
|
|
data: totalNumberData,
|
|
|
type: 'line',
|
|
|
smooth: true,
|
|
|
itemStyle: {
|
|
|
color: '#409EFF'
|
|
|
},
|
|
|
areaStyle: {
|
|
|
opacity: 0.3
|
|
|
}
|
|
|
},
|
|
|
{
|
|
|
name: '销量箱数',
|
|
|
data: salesBoxCountData,
|
|
|
type: 'line',
|
|
|
smooth: true,
|
|
|
itemStyle: {
|
|
|
color: '#E6A23C'
|
|
|
}
|
|
|
}
|
|
|
]
|
|
|
};
|
|
|
totalNumberChart.setOption(totalNumberOption, true); // true表示不合并选项
|
|
|
}
|
|
|
|
|
|
// 更新总净重图表
|
|
|
if (totalNetWeightChart && historyData.value && historyData.value.length > 0) {
|
|
|
const timeData = historyData.value.map(item => {
|
|
|
const date = new Date(item.create_time);
|
|
|
return `${date.getMonth() + 1}/${date.getDate()} ${date.getHours()}:${date.getMinutes().toString().padStart(2, '0')}`;
|
|
|
});
|
|
|
|
|
|
const totalNetWeightData = historyData.value.map(item => item.total_net_weight);
|
|
|
|
|
|
// 准备销量数据,按日期聚合
|
|
|
let salesWeightData = [];
|
|
|
if (salesData.value && salesData.value.length > 0) {
|
|
|
// 创建一个映射,键为日期,值为当天总重量
|
|
|
const salesWeightMap = {};
|
|
|
salesData.value.forEach(item => {
|
|
|
const date = new Date(item.date);
|
|
|
const dateStr = `${date.getMonth() + 1}/${date.getDate()}`;
|
|
|
if (!salesWeightMap[dateStr]) {
|
|
|
salesWeightMap[dateStr] = 0;
|
|
|
}
|
|
|
salesWeightMap[dateStr] += item.weight;
|
|
|
});
|
|
|
|
|
|
// 将销量数据映射到时间轴上
|
|
|
salesWeightData = timeData.map(timeStr => {
|
|
|
// 从时间字符串中提取日期部分(去掉时间部分)
|
|
|
const dateStr = timeStr.split(' ')[0];
|
|
|
return salesWeightMap[dateStr] || 0;
|
|
|
});
|
|
|
}
|
|
|
|
|
|
const totalNetWeightOption = {
|
|
|
title: {
|
|
|
text: '总净重变化趋势',
|
|
|
left: 'center',
|
|
|
textStyle: {
|
|
|
fontSize: 16
|
|
|
}
|
|
|
},
|
|
|
tooltip: {
|
|
|
trigger: 'axis',
|
|
|
formatter: function(params) {
|
|
|
let result = params[0].name + '<br/>';
|
|
|
params.forEach(param => {
|
|
|
result += `${param.seriesName}: ${param.value} kg<br/>`;
|
|
|
});
|
|
|
return result;
|
|
|
}
|
|
|
},
|
|
|
legend: {
|
|
|
data: ['生产净重', '销量净重'],
|
|
|
top: 30
|
|
|
},
|
|
|
grid: {
|
|
|
left: '3%',
|
|
|
right: '4%',
|
|
|
bottom: '3%',
|
|
|
top: 60,
|
|
|
containLabel: true
|
|
|
},
|
|
|
xAxis: {
|
|
|
type: 'category',
|
|
|
data: timeData,
|
|
|
axisLabel: {
|
|
|
rotate: 45
|
|
|
}
|
|
|
},
|
|
|
yAxis: {
|
|
|
type: 'value',
|
|
|
name: '重量(kg)'
|
|
|
},
|
|
|
series: [
|
|
|
{
|
|
|
name: '生产净重',
|
|
|
data: totalNetWeightData,
|
|
|
type: 'line',
|
|
|
smooth: true,
|
|
|
itemStyle: {
|
|
|
color: '#67C23A'
|
|
|
},
|
|
|
areaStyle: {
|
|
|
opacity: 0.3
|
|
|
}
|
|
|
},
|
|
|
{
|
|
|
name: '销量净重',
|
|
|
data: salesWeightData,
|
|
|
type: 'line',
|
|
|
smooth: true,
|
|
|
itemStyle: {
|
|
|
color: '#F56C6C'
|
|
|
}
|
|
|
}
|
|
|
]
|
|
|
};
|
|
|
totalNetWeightChart.setOption(totalNetWeightOption, true); // true表示不合并选项
|
|
|
}
|
|
|
|
|
|
// 更新总毛重图表
|
|
|
if (totalGrossWeightChart && historyData.value && historyData.value.length > 0) {
|
|
|
const timeData = historyData.value.map(item => {
|
|
|
const date = new Date(item.create_time);
|
|
|
return `${date.getMonth() + 1}/${date.getDate()} ${date.getHours()}:${date.getMinutes().toString().padStart(2, '0')}`;
|
|
|
});
|
|
|
|
|
|
const totalGrossWeightData = historyData.value.map(item => item.total_gross_weight);
|
|
|
|
|
|
// 准备销量数据,按日期聚合
|
|
|
let salesWeightData = [];
|
|
|
if (salesData.value && salesData.value.length > 0) {
|
|
|
// 创建一个映射,键为日期,值为当天总重量
|
|
|
const salesWeightMap = {};
|
|
|
salesData.value.forEach(item => {
|
|
|
const date = new Date(item.date);
|
|
|
const dateStr = `${date.getMonth() + 1}/${date.getDate()}`;
|
|
|
if (!salesWeightMap[dateStr]) {
|
|
|
salesWeightMap[dateStr] = 0;
|
|
|
}
|
|
|
salesWeightMap[dateStr] += item.weight;
|
|
|
});
|
|
|
|
|
|
// 将销量数据映射到时间轴上
|
|
|
salesWeightData = timeData.map(timeStr => {
|
|
|
// 从时间字符串中提取日期部分(去掉时间部分)
|
|
|
const dateStr = timeStr.split(' ')[0];
|
|
|
return salesWeightMap[dateStr] || 0;
|
|
|
});
|
|
|
}
|
|
|
|
|
|
const totalGrossWeightOption = {
|
|
|
title: {
|
|
|
text: '总毛重变化趋势',
|
|
|
left: 'center',
|
|
|
textStyle: {
|
|
|
fontSize: 16
|
|
|
}
|
|
|
},
|
|
|
tooltip: {
|
|
|
trigger: 'axis',
|
|
|
formatter: function(params) {
|
|
|
let result = params[0].name + '<br/>';
|
|
|
params.forEach(param => {
|
|
|
result += `${param.seriesName}: ${param.value} kg<br/>`;
|
|
|
});
|
|
|
return result;
|
|
|
}
|
|
|
},
|
|
|
legend: {
|
|
|
data: ['生产毛重', '销量重量'],
|
|
|
top: 30
|
|
|
},
|
|
|
grid: {
|
|
|
left: '3%',
|
|
|
right: '4%',
|
|
|
bottom: '3%',
|
|
|
top: 60,
|
|
|
containLabel: true
|
|
|
},
|
|
|
xAxis: {
|
|
|
type: 'category',
|
|
|
data: timeData,
|
|
|
axisLabel: {
|
|
|
rotate: 45
|
|
|
}
|
|
|
},
|
|
|
yAxis: {
|
|
|
type: 'value',
|
|
|
name: '重量(kg)'
|
|
|
},
|
|
|
series: [
|
|
|
{
|
|
|
name: '生产毛重',
|
|
|
data: totalGrossWeightData,
|
|
|
type: 'line',
|
|
|
smooth: true,
|
|
|
itemStyle: {
|
|
|
color: '#E6A23C'
|
|
|
},
|
|
|
areaStyle: {
|
|
|
opacity: 0.3
|
|
|
}
|
|
|
},
|
|
|
{
|
|
|
name: '销量重量',
|
|
|
data: salesWeightData,
|
|
|
type: 'line',
|
|
|
smooth: true,
|
|
|
itemStyle: {
|
|
|
color: '#F56C6C'
|
|
|
}
|
|
|
}
|
|
|
]
|
|
|
};
|
|
|
totalGrossWeightChart.setOption(totalGrossWeightOption, true); // true表示不合并选项
|
|
|
}
|
|
|
|
|
|
// 更新销量图表
|
|
|
if (salesChart && salesData.value && salesData.value.length > 0) {
|
|
|
// 准备销量数据
|
|
|
const salesTimeData = salesData.value.map(item => {
|
|
|
const date = new Date(item.date);
|
|
|
return `${date.getMonth() + 1}/${date.getDate()}`;
|
|
|
});
|
|
|
|
|
|
const boxCountData = salesData.value.map(item => item.box_count);
|
|
|
const weightData = salesData.value.map(item => item.weight);
|
|
|
|
|
|
const salesOption = {
|
|
|
title: {
|
|
|
text: '销量变化趋势',
|
|
|
left: 'center',
|
|
|
textStyle: {
|
|
|
fontSize: 16
|
|
|
}
|
|
|
},
|
|
|
tooltip: {
|
|
|
trigger: 'axis',
|
|
|
formatter: function(params) {
|
|
|
let result = params[0].name + '<br/>';
|
|
|
params.forEach(param => {
|
|
|
if (param.seriesName === '箱数') {
|
|
|
result += `${param.seriesName}: ${param.value} 箱<br/>`;
|
|
|
} else if (param.seriesName === '重量') {
|
|
|
result += `${param.seriesName}: ${param.value} kg<br/>`;
|
|
|
}
|
|
|
});
|
|
|
return result;
|
|
|
}
|
|
|
},
|
|
|
legend: {
|
|
|
data: ['箱数', '重量'],
|
|
|
top: 30
|
|
|
},
|
|
|
grid: {
|
|
|
left: '3%',
|
|
|
right: '4%',
|
|
|
bottom: '3%',
|
|
|
top: 60,
|
|
|
containLabel: true
|
|
|
},
|
|
|
xAxis: {
|
|
|
type: 'category',
|
|
|
data: salesTimeData,
|
|
|
axisLabel: {
|
|
|
rotate: 45
|
|
|
}
|
|
|
},
|
|
|
yAxis: [
|
|
|
{
|
|
|
type: 'value',
|
|
|
name: '箱数',
|
|
|
position: 'left'
|
|
|
},
|
|
|
{
|
|
|
type: 'value',
|
|
|
name: '重量(kg)',
|
|
|
position: 'right'
|
|
|
}
|
|
|
],
|
|
|
series: [
|
|
|
{
|
|
|
name: '箱数',
|
|
|
type: 'bar',
|
|
|
data: boxCountData,
|
|
|
itemStyle: {
|
|
|
color: '#409EFF'
|
|
|
}
|
|
|
},
|
|
|
{
|
|
|
name: '重量',
|
|
|
type: 'line',
|
|
|
yAxisIndex: 1,
|
|
|
data: weightData,
|
|
|
smooth: true,
|
|
|
itemStyle: {
|
|
|
color: '#67C23A'
|
|
|
},
|
|
|
areaStyle: {
|
|
|
opacity: 0.3
|
|
|
}
|
|
|
}
|
|
|
]
|
|
|
};
|
|
|
salesChart.setOption(salesOption, true); // true表示不合并选项
|
|
|
}
|
|
|
});
|
|
|
};
|
|
|
</script>
|
|
|
|
|
|
<style scoped>
|
|
|
.equipment-status-container {
|
|
|
padding: 20px;
|
|
|
}
|
|
|
|
|
|
.equipment-grid {
|
|
|
display: grid;
|
|
|
grid-template-columns: repeat(auto-fill, minmax(400px, 1fr));
|
|
|
gap: 20px;
|
|
|
}
|
|
|
|
|
|
.equipment-item {
|
|
|
border: 1px solid #dcdfe6;
|
|
|
border-radius: 4px;
|
|
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.12), 0 0 6px rgba(0, 0, 0, 0.04);
|
|
|
overflow: hidden;
|
|
|
}
|
|
|
|
|
|
.equipment-header {
|
|
|
background-color: #f5f7fa;
|
|
|
padding: 10px 15px;
|
|
|
font-size: 16px;
|
|
|
border-bottom: 1px solid #dcdfe6;
|
|
|
display: flex;
|
|
|
justify-content: space-between;
|
|
|
align-items: center;
|
|
|
}
|
|
|
|
|
|
.equipment-name {
|
|
|
font-weight: bold;
|
|
|
}
|
|
|
|
|
|
.equipment-health {
|
|
|
font-size: 14px;
|
|
|
color: #67c23a;
|
|
|
}
|
|
|
|
|
|
.equipment-body {
|
|
|
padding: 15px;
|
|
|
}
|
|
|
|
|
|
.axle-container {
|
|
|
display: flex;
|
|
|
justify-content: space-between;
|
|
|
min-height: 200px;
|
|
|
}
|
|
|
|
|
|
.axle-side {
|
|
|
width: 48%;
|
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
|
gap: 8px;
|
|
|
}
|
|
|
|
|
|
.axle-item {
|
|
|
padding: 8px;
|
|
|
border-radius: 4px;
|
|
|
background-color: #f5f7fa;
|
|
|
border: 2px solid #e4e7ed;
|
|
|
position: relative;
|
|
|
}
|
|
|
|
|
|
/* 0% - 25% 完成度 - 浅绿色边框 */
|
|
|
.axle-item.completion-low {
|
|
|
border-color: #b3e19d;
|
|
|
}
|
|
|
|
|
|
.axle-item.completion-low::before {
|
|
|
content: '';
|
|
|
position: absolute;
|
|
|
left: 0;
|
|
|
top: 0;
|
|
|
bottom: 0;
|
|
|
width: 4px;
|
|
|
background-color: #b3e19d;
|
|
|
border-radius: 4px 0 0 4px;
|
|
|
}
|
|
|
|
|
|
/* 25% - 75% 完成度 - 中等绿色边框 */
|
|
|
.axle-item.completion-medium {
|
|
|
border-color: #67c23a;
|
|
|
}
|
|
|
|
|
|
.axle-item.completion-medium::before {
|
|
|
content: '';
|
|
|
position: absolute;
|
|
|
left: 0;
|
|
|
top: 0;
|
|
|
bottom: 0;
|
|
|
width: 4px;
|
|
|
background-color: #67c23a;
|
|
|
border-radius: 4px 0 0 4px;
|
|
|
}
|
|
|
|
|
|
/* 75% - 100% 完成度 - 深绿色边框 */
|
|
|
.axle-item.completion-high {
|
|
|
border-color: #529b2e;
|
|
|
}
|
|
|
|
|
|
.axle-item.completion-high::before {
|
|
|
content: '';
|
|
|
position: absolute;
|
|
|
left: 0;
|
|
|
top: 0;
|
|
|
bottom: 0;
|
|
|
width: 4px;
|
|
|
background-color: #529b2e;
|
|
|
border-radius: 4px 0 0 4px;
|
|
|
}
|
|
|
|
|
|
/* 100%及以上完成度 - 最深绿色边框 */
|
|
|
.axle-item.completion-full {
|
|
|
border-color: #387c2a;
|
|
|
}
|
|
|
|
|
|
.axle-item.completion-full::before {
|
|
|
content: '';
|
|
|
position: absolute;
|
|
|
left: 0;
|
|
|
top: 0;
|
|
|
bottom: 0;
|
|
|
width: 4px;
|
|
|
background-color: #387c2a;
|
|
|
border-radius: 4px 0 0 4px;
|
|
|
}
|
|
|
|
|
|
.axle-number {
|
|
|
font-weight: bold;
|
|
|
margin-bottom: 4px;
|
|
|
}
|
|
|
|
|
|
.axle-spec {
|
|
|
font-size: 12px;
|
|
|
color: #606266;
|
|
|
margin-bottom: 2px;
|
|
|
background-color: rgba(64, 158, 255, 0.1);
|
|
|
padding: 2px 4px;
|
|
|
border-radius: 3px;
|
|
|
display: inline-block;
|
|
|
}
|
|
|
|
|
|
.axle-model {
|
|
|
font-size: 12px;
|
|
|
color: #909399;
|
|
|
margin-bottom: 2px;
|
|
|
background-color: rgba(103, 194, 58, 0.1);
|
|
|
padding: 2px 4px;
|
|
|
border-radius: 3px;
|
|
|
display: inline-block;
|
|
|
}
|
|
|
|
|
|
.axle-disc {
|
|
|
font-size: 12px;
|
|
|
color: #606266;
|
|
|
margin-bottom: 2px;
|
|
|
background-color: rgba(230, 162, 60, 0.1);
|
|
|
padding: 2px 4px;
|
|
|
border-radius: 3px;
|
|
|
display: inline-block;
|
|
|
}
|
|
|
|
|
|
.axle-progress {
|
|
|
font-size: 12px;
|
|
|
color: #409eff;
|
|
|
font-weight: bold;
|
|
|
}
|
|
|
|
|
|
.axle-stock {
|
|
|
margin-top: 6px;
|
|
|
padding-top: 6px;
|
|
|
border-top: 1px dashed #e4e7ed;
|
|
|
}
|
|
|
|
|
|
.stock-item {
|
|
|
font-size: 11px;
|
|
|
color: #606266;
|
|
|
margin-bottom: 2px;
|
|
|
}
|
|
|
|
|
|
.no-records {
|
|
|
text-align: center;
|
|
|
color: #909399;
|
|
|
padding: 20px 0;
|
|
|
}
|
|
|
|
|
|
/* 不在运行的轴样式 */
|
|
|
.axle-item.not-running {
|
|
|
background-color: #f5f7fa;
|
|
|
opacity: 0.7;
|
|
|
border-color: #c0c4cc;
|
|
|
}
|
|
|
|
|
|
.axle-item.not-running::before {
|
|
|
background-color: #c0c4cc;
|
|
|
}
|
|
|
|
|
|
.not-running-label {
|
|
|
font-size: 12px;
|
|
|
color: #f56c6c;
|
|
|
font-weight: normal;
|
|
|
}
|
|
|
|
|
|
/* 筛选器样式 */
|
|
|
.filter-container {
|
|
|
margin-bottom: 20px;
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
}
|
|
|
|
|
|
.filter-label {
|
|
|
margin-right: 10px;
|
|
|
font-weight: bold;
|
|
|
color: #606266;
|
|
|
}
|
|
|
|
|
|
/* 对话框样式 */
|
|
|
.axle-details-container {
|
|
|
display: flex;
|
|
|
min-height: 650px;
|
|
|
}
|
|
|
|
|
|
/* 左侧面板样式 */
|
|
|
.axle-info-panel {
|
|
|
flex: 1;
|
|
|
padding: 10px 20px 10px 10px;
|
|
|
border-right: 1px solid #EBEEF5;
|
|
|
max-height: 650px;
|
|
|
overflow-y: auto;
|
|
|
}
|
|
|
|
|
|
/* 右侧面板样式 */
|
|
|
.history-chart-panel {
|
|
|
flex: 1;
|
|
|
padding: 10px;
|
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
|
}
|
|
|
|
|
|
.detail-row {
|
|
|
display: flex;
|
|
|
margin-bottom: 12px;
|
|
|
line-height: 1.5;
|
|
|
}
|
|
|
|
|
|
/* 规格、型号、线盘在同一行显示的特殊样式 */
|
|
|
.specification-row {
|
|
|
flex-wrap: wrap;
|
|
|
}
|
|
|
|
|
|
.spec-item {
|
|
|
background-color: rgba(64, 158, 255, 0.1);
|
|
|
padding: 2px 6px;
|
|
|
border-radius: 3px;
|
|
|
margin-right: 10px;
|
|
|
}
|
|
|
|
|
|
.model-label {
|
|
|
min-width: auto;
|
|
|
margin-left: 5px;
|
|
|
}
|
|
|
|
|
|
.model-item {
|
|
|
background-color: rgba(103, 194, 58, 0.1);
|
|
|
padding: 2px 6px;
|
|
|
border-radius: 3px;
|
|
|
margin-right: 10px;
|
|
|
}
|
|
|
|
|
|
.disc-label {
|
|
|
min-width: auto;
|
|
|
margin-left: 5px;
|
|
|
}
|
|
|
|
|
|
.disc-item {
|
|
|
background-color: rgba(230, 162, 60, 0.1);
|
|
|
padding: 2px 6px;
|
|
|
border-radius: 3px;
|
|
|
}
|
|
|
|
|
|
/* 库存信息在同一行显示的特殊样式 */
|
|
|
.stock-row {
|
|
|
flex-wrap: wrap;
|
|
|
}
|
|
|
|
|
|
.stock-label {
|
|
|
min-width: auto;
|
|
|
margin-left: 5px;
|
|
|
}
|
|
|
|
|
|
.stock-item-number {
|
|
|
background-color: rgba(64, 158, 255, 0.1);
|
|
|
padding: 2px 6px;
|
|
|
border-radius: 3px;
|
|
|
margin-right: 10px;
|
|
|
}
|
|
|
|
|
|
.stock-item-weight {
|
|
|
background-color: rgba(144, 147, 153, 0.1);
|
|
|
padding: 2px 6px;
|
|
|
border-radius: 3px;
|
|
|
margin-right: 10px;
|
|
|
}
|
|
|
|
|
|
/* 转移概率样式 */
|
|
|
.transition-probabilities-loading {
|
|
|
height: 80px;
|
|
|
position: relative;
|
|
|
}
|
|
|
|
|
|
.transition-probabilities-details {
|
|
|
margin-top: 15px;
|
|
|
border-top: 1px dashed #e4e7ed;
|
|
|
padding-top: 10px;
|
|
|
}
|
|
|
|
|
|
.transition-probabilities-content {
|
|
|
background-color: #f8f9fa;
|
|
|
border-radius: 4px;
|
|
|
padding: 8px;
|
|
|
max-height: 150px;
|
|
|
overflow-y: auto;
|
|
|
}
|
|
|
|
|
|
.transition-probability-item {
|
|
|
display: flex;
|
|
|
justify-content: space-between;
|
|
|
margin-bottom: 4px;
|
|
|
font-size: 12px;
|
|
|
line-height: 1.4;
|
|
|
}
|
|
|
|
|
|
.detail-label {
|
|
|
min-width: 100px;
|
|
|
font-weight: bold;
|
|
|
color: #606266;
|
|
|
}
|
|
|
|
|
|
.detail-value {
|
|
|
flex: 1;
|
|
|
color: #303133;
|
|
|
}
|
|
|
|
|
|
.detail-title {
|
|
|
font-weight: bold;
|
|
|
color: #303133;
|
|
|
margin: 15px 0 10px;
|
|
|
padding-bottom: 5px;
|
|
|
border-bottom: 1px solid #e4e7ed;
|
|
|
}
|
|
|
|
|
|
.status-running {
|
|
|
color: #67c23a;
|
|
|
}
|
|
|
|
|
|
.status-stopped {
|
|
|
color: #f56c6c;
|
|
|
}
|
|
|
|
|
|
/* 图表控制区域 */
|
|
|
.chart-controls {
|
|
|
display: flex;
|
|
|
justify-content: space-between;
|
|
|
align-items: center;
|
|
|
margin-bottom: 15px;
|
|
|
}
|
|
|
|
|
|
/* 图表容器样式 */
|
|
|
.charts-container {
|
|
|
flex: 1;
|
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
|
}
|
|
|
|
|
|
.chart {
|
|
|
height: 500px;
|
|
|
width: 100%;
|
|
|
}
|
|
|
|
|
|
/* 加载状态 */
|
|
|
.chart-loading {
|
|
|
height: 500px;
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
justify-content: center;
|
|
|
}
|
|
|
|
|
|
/* 无数据状态 */
|
|
|
.no-history-data {
|
|
|
height: 500px;
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
justify-content: center;
|
|
|
color: #909399;
|
|
|
font-size: 16px;
|
|
|
}
|
|
|
|
|
|
/* 排产信息加载状态 */
|
|
|
.production-schedule-loading {
|
|
|
height: 60px;
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
justify-content: center;
|
|
|
}
|
|
|
|
|
|
/* 排产信息面板样式 */
|
|
|
.production-schedule-details {
|
|
|
margin-top: 10px;
|
|
|
}
|
|
|
|
|
|
/* 当前订单面板样式 */
|
|
|
.current-order-details {
|
|
|
margin-top: 10px;
|
|
|
}
|
|
|
|
|
|
/* 生产计算面板样式 */
|
|
|
.calculation-details {
|
|
|
margin-top: 10px;
|
|
|
}
|
|
|
|
|
|
/* 每天生产数据在同一行显示的特殊样式 */
|
|
|
.daily-production-row {
|
|
|
flex-wrap: wrap;
|
|
|
}
|
|
|
|
|
|
.daily-production-item {
|
|
|
min-width: 30%;
|
|
|
margin-right: 10px;
|
|
|
display: flex;
|
|
|
}
|
|
|
|
|
|
.daily-production-item .detail-label {
|
|
|
margin-right: 5px;
|
|
|
}
|
|
|
|
|
|
/* 可编辑输入框样式 */
|
|
|
.editable-input {
|
|
|
width: 100px;
|
|
|
}
|
|
|
|
|
|
/* 添加悬停效果,提示用户可以双击 */
|
|
|
.axle-item {
|
|
|
cursor: pointer;
|
|
|
}
|
|
|
|
|
|
.axle-item:hover {
|
|
|
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
|
|
|
}
|
|
|
</style> |