Kaynağa Gözat

嗯 增加了前整车间工艺查看功能

wukai 2 ay önce
ebeveyn
işleme
ded278045f
1 değiştirilmiş dosya ile 725 ekleme ve 0 silme
  1. 725 0
      src/views/dye/hour/all.vue

+ 725 - 0
src/views/dye/hour/all.vue

@@ -0,0 +1,725 @@
+<template>
+  <div class="app-container">
+    <!-- 头部信息区域 -->
+    <el-card style="margin-bottom: 15px;">
+      <el-row :gutter="15" align="middle">
+        <!-- 时间选择 -->
+        <el-col :span="6">
+          <div style="display: flex; align-items: center; justify-content: center;">
+            <el-button icon="ArrowLeft" @click="navigateDay(-1)" style="margin-right: 10px;"></el-button>
+            <el-date-picker clearable
+                            v-model="queryParams.workDay"
+                            type="date"
+                            value-format="YYYY-MM-DD"
+                            placeholder="请选择日期"
+                            style="width: 130px"
+                            @change="handleQuery">
+            </el-date-picker>
+            <el-button icon="ArrowRight" @click="navigateDay(1)" style="margin-left: 10px;"></el-button>
+          </div>
+        </el-col>
+
+        <!-- 设备类型选择 -->
+        <el-col :span="6">
+          <div style="display: flex; align-items: center; flex-wrap: nowrap; gap: 10px;">
+            <span style="white-space: nowrap;">类型:</span>
+            <el-select v-model="selectedEquipmentType" placeholder="设备类型"
+                       @change="handleEquipmentTypeChange" clearable style="flex: 1; min-width: 100px;">
+              <el-option
+                  v-for="item in equipmentTypeOptions"
+                  :key="item.typeId"
+                  :label="item.typeName"
+                  :value="item.typeId">
+              </el-option>
+            </el-select>
+          </div>
+        </el-col>
+
+        <!-- 设备选择 -->
+        <el-col :span="6">
+          <div style="display: flex; align-items: center; flex-wrap: nowrap; gap: 10px;">
+            <span style="white-space: nowrap;">设备:</span>
+            <el-select v-model="selectedDevice" placeholder="请选择设备"
+                       @change="handleDeviceChange" clearable style="flex: 1; min-width: 100px;">
+              <el-option
+                  v-for="item in deviceOptions"
+                  :key="item.deviceId"
+                  :label="item.deviceName"
+                  :value="item.deviceId">
+              </el-option>
+            </el-select>
+          </div>
+        </el-col>
+
+        <!-- 参数选择 -->
+        <el-col :span="6">
+          <div style="display: flex; align-items: center; flex-wrap: nowrap; gap: 10px;">
+            <span style="white-space: nowrap;">参数:</span>
+            <el-select
+              v-model="selectedParams"
+              multiple
+              filterable
+              clearable
+              collapse-tags
+              collapse-tags-tooltip
+              placeholder="请选择参数"
+              style="flex: 1; min-width: 100px;"
+              :max-collapse-tags="3"
+            >
+              <el-option
+                v-for="item in paramOptions"
+                :key="item.paraCode"
+                :label="item.paraName"
+                :value="item.paraCode">
+              </el-option>
+            </el-select>
+          </div>
+        </el-col>
+      </el-row>
+    </el-card>
+
+    <!-- 图表展示 -->
+    <el-card>
+      <div v-if="paramOptions.length > 0 && selectedDevice">
+        <div v-if="selectedParams.length > 0">
+          <el-row :gutter="15">
+            <el-col
+              v-for="param in getSelectedParamDetails()"
+              :key="param.paraCode"
+              :span="8"
+              style="margin-bottom: 20px;">
+              <el-card style="height: 220px;">
+                <div :ref="el => { if (el) chartRefs[param.paraCode] = el }" style="width: 100%; height: 200px;"></div>
+              </el-card>
+            </el-col>
+          </el-row>
+        </div>
+
+        <div v-else style="text-align: center; padding: 40px;">
+          <el-empty description="请选择至少一个参数显示图表" />
+        </div>
+      </div>
+
+      <div v-else-if="queryExecuted" style="text-align: center; padding: 40px;">
+        <el-empty description="暂无数据" />
+      </div>
+
+      <div v-else style="text-align: center; padding: 40px;">
+        <el-empty description="请选择设备类型和设备" />
+      </div>
+    </el-card>
+
+    <!-- 分钟级数据弹窗 -->
+    <el-dialog
+        :title="minuteDialogTitle"
+        v-model="minuteDialogVisible"
+        width="1500px"
+        :before-close="handleMinuteDialogClose"
+        append-to-body
+    >
+      <div ref="minuteChartRef" style="width: 100%; height: 400px;"></div>
+      <el-table :data="minuteTableData" style="width: 100%; margin-top: 20px; height: 300px;" border>
+        <el-table-column prop="time" label="分钟" width="100" align="center"></el-table-column>
+        <el-table-column prop="value" :label="'数值' + (minuteDataUnit ? ' (' + minuteDataUnit + ')' : '')" align="center">
+          <template #default="scope">
+            <span v-if="scope.row.value !== null">{{ scope.row.value }}</span>
+            <span v-else>无数据</span>
+          </template>
+        </el-table-column>
+      </el-table>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup name="dyeHourAll">
+import * as echarts from 'echarts';
+import { listDeviceTypes } from "@/api/dyeing/gyfx";
+import { listDevice } from "@/api/dye/device";
+import { listHour } from "@/api/dye/hour.js";
+import { nextTick, ref } from 'vue';
+
+const { proxy } = getCurrentInstance();
+
+const deviceParams = ref([]); // 设备参数数据
+const queryExecuted = ref(false);
+const selectedParams = ref([]); // 选中的参数
+const selectedDeviceName = ref('');
+
+// 参数选项相关
+const paramOptions = ref([]); // 参数选项列表
+
+// 图表相关
+const chartRefs = ref({}); // 存储图表引用
+const chartInstances = ref({}); // 存储图表实例
+
+// 分钟级数据相关
+const minuteDialogVisible = ref(false);
+const minuteDialogTitle = ref('');
+const minuteChartRef = ref(null);
+let minuteChartInstance = null;
+const minuteTableData = ref([]);
+const minuteDataUnit = ref('');
+
+const data = reactive({
+  form: {},
+  queryParams: {
+    pageNum: 1,
+    pageSize: 10000,
+    workDay: new Date().toISOString().split('T')[0],
+  },
+  rules: {}
+});
+
+const selectedEquipmentType = ref('');
+const selectedDevice = ref('');
+const deviceOptions = ref([]);
+const equipmentTypeList = ref([]);
+const equipmentTypeOptions = ref([]);
+
+const { queryParams, form, rules } = toRefs(data);
+
+/** 查询设备类型列表 */
+function getList() {
+  listDeviceTypes({ pageSize: 10000 }).then(response => {
+    equipmentTypeList.value = response.rows;
+    // 提取设备类型选项
+    equipmentTypeOptions.value = response.rows.map(item => {
+      return {
+        typeId: item.typeId,
+        typeName: item.typeName
+      };
+    });
+
+    // 默认选择第一条设备类型
+    if (response.rows.length > 0 && !selectedEquipmentType.value) {
+      selectedEquipmentType.value = response.rows[0].typeId;
+      // 触发设备类型变化事件,加载对应的参数
+      handleEquipmentTypeChange(selectedEquipmentType.value);
+    }
+  });
+}
+
+function navigateDay(offset) {
+  const currentDate = new Date(queryParams.value.workDay);
+  currentDate.setDate(currentDate.getDate() + offset);
+  queryParams.value.workDay = currentDate.toISOString().split('T')[0];
+  // 日期变更时自动查询
+  handleQuery();
+}
+
+/** 处理设备类型变化 */
+function handleEquipmentTypeChange(val) {
+  // 清空设备选择
+  selectedDevice.value = '';
+  deviceOptions.value = [];
+  paramOptions.value = [];
+  deviceParams.value = [];
+  queryExecuted.value = false;
+
+  if (val) {
+    // 在设备类型列表中找到当前选中的设备类型
+    const selectedType = equipmentTypeList.value.find(item => item.typeId === val);
+    // 查找选中的参数信息
+    if (selectedType && selectedType.dyeTypeParaList) {
+      // 提取设备参数选项
+      paramOptions.value = selectedType.dyeTypeParaList.map(item => {
+        return {
+          paraCode: item.paraCode,
+          paraName: item.paraName,
+          updatedBy: item.updatedBy // 保存单位信息
+        };
+      });
+
+      // 获取该类型下的设备列表
+      listDevice({
+        pageSize: 10000,
+        pageNum: 1,
+        typeId: val
+      }).then(response => {
+        deviceOptions.value = response.rows;
+
+        // 默认选择第一台设备
+        if (response.rows.length > 0 && !selectedDevice.value) {
+          selectedDevice.value = response.rows[0].deviceId;
+          selectedDeviceName.value = response.rows[0].deviceName;
+          // 设备变更时自动查询
+          handleQuery();
+        }
+      });
+    } else {
+      paramOptions.value = [];
+    }
+  } else {
+    // 如果没有选择设备类型,清空设备参数选项
+    paramOptions.value = [];
+  }
+}
+
+/** 处理设备变化 */
+function handleDeviceChange(val) {
+  // 查找选中的设备名称
+  const device = deviceOptions.value.find(item => item.deviceId === val);
+  if (device) {
+    selectedDeviceName.value = device.deviceName;
+    // 设备变更时自动查询
+    handleQuery();
+  } else {
+    selectedDeviceName.value = '';
+  }
+  // 清空参数数据
+  deviceParams.value = [];
+  queryExecuted.value = false;
+}
+
+/** 查询按钮操作 */
+function handleQuery() {
+  if (!selectedEquipmentType.value) {
+    proxy.$modal.msgWarning("请先选择设备类型");
+    return;
+  }
+
+  if (!selectedDevice.value) {
+    proxy.$modal.msgWarning("请选择设备");
+    return;
+  }
+
+  // 查询该设备在指定日期的所有参数数据
+  listHour({
+    pageNum: 1,
+    pageSize: 10000,
+    deviceId: selectedDevice.value,
+    dataDate: queryParams.value.workDay
+  }).then(response => {
+    deviceParams.value = response.rows;
+
+    // 默认选中所有参数
+    selectedParams.value = paramOptions.value.map(param => param.paraCode);
+
+    queryExecuted.value = true;
+
+    // 等待DOM更新后初始化图表
+    nextTick(() => {
+      initCharts();
+    });
+  });
+}
+
+/** 重置按钮操作 */
+function resetQuery() {
+  proxy.resetForm("queryRef");
+  // 设置默认日期为当前日期
+  queryParams.value.workDay = new Date().toISOString().split('T')[0];
+
+  // 重置选择
+  selectedEquipmentType.value = '';
+  selectedDevice.value = '';
+  deviceOptions.value = [];
+  paramOptions.value = [];
+  deviceParams.value = [];
+  queryExecuted.value = false;
+  selectedDeviceName.value = '';
+  selectedParams.value = [];
+
+  // 清理图表
+  Object.values(chartInstances.value).forEach(instance => {
+    if (instance) instance.dispose();
+  });
+  chartInstances.value = {};
+  chartRefs.value = {};
+}
+
+/** 获取选中参数的详细信息 */
+function getSelectedParamDetails() {
+  return paramOptions.value.filter(param => selectedParams.value.includes(param.paraCode));
+}
+
+/** 初始化图表 */
+function initCharts() {
+  // 先清理已有的图表实例
+  Object.values(chartInstances.value).forEach(instance => {
+    if (instance) instance.dispose();
+  });
+  chartInstances.value = {};
+  
+  // 定义一组协调的颜色
+  const chartColors = ['#5470c6', '#91cc75', '#fac858', '#ee6666', '#73c0de', '#3ba272', '#fc8452', '#9a60b4', '#ea7ccc'];
+  
+  // 为每个选中的参数创建图表
+  selectedParams.value.forEach((paraCode, index) => {
+    const paramInfo = paramOptions.value.find(p => p.paraCode === paraCode);
+    if (paramInfo && chartRefs.value[paraCode]) {
+      // 获取该参数的所有小时数据(7点到次日7点)
+      const hoursData = getParamHoursData(paraCode);
+      
+      // 初始化图表
+      const chartInstance = echarts.init(chartRefs.value[paraCode]);
+      chartInstances.value[paraCode] = chartInstance;
+      
+      // 图表配置
+      const option = {
+        color: [chartColors[index % chartColors.length]], // 为每个图表使用不同的颜色
+        title: {
+          text: `${paramInfo.paraName}${paramInfo.updatedBy ? ' (' + paramInfo.updatedBy + ')' : ''}`,
+          left: 'center',
+          textStyle: {
+            fontSize: 14
+          }
+        },
+        tooltip: {
+          trigger: 'axis',
+          formatter: function (params) {
+            const dataIndex = params[0].dataIndex;
+            const dataItem = hoursData[dataIndex];
+            const fullTime = `${dataItem.dataDate} ${dataItem.hour.toString().padStart(2, '0')}:00`;
+            let result = fullTime + '<br/>';
+            params.forEach(param => {
+              let valueText = '';
+              if (param.value !== null) {
+                valueText = param.value + (paramInfo.updatedBy ? ' (' + paramInfo.updatedBy + ')' : '');
+              } else {
+                valueText = '无数据';
+              }
+              result += `<div style="display:flex;align-items:center;">
+                <span style="display:inline-block;margin-right:5px;border-radius:10px;width:10px;height:10px;background-color:${param.color};"></span>
+                ${param.seriesName}: ${valueText}
+              </div>`;
+            });
+            return result;
+          }
+        },
+        grid: {
+          left: '3%',
+          right: '4%',
+          top: '20%',
+          bottom: 30,
+          containLabel: true
+        },
+        xAxis: {
+          type: 'category',
+          boundaryGap: false,
+          data: hoursData.map(item => item.hour.toString().padStart(2, '0') + ':00'),
+        },
+        yAxis: {
+          type: 'value',
+        },
+        series: [{
+          name: paramInfo.paraName,
+          type: 'line',
+          data: hoursData.map(item => item.paraValue),
+          smooth: true,
+          showSymbol: false,
+          lineStyle: {
+            width: 2
+          },
+          markPoint: {
+            data: [
+              { 
+                type: 'max', 
+                name: '最大值',
+                itemStyle: {
+                  color: chartColors[index % chartColors.length]
+                }
+              },
+              { 
+                type: 'min', 
+                name: '最小值',
+                itemStyle: {
+                  color: chartColors[index % chartColors.length]
+                }
+              }
+            ]
+          },
+          markLine: {
+            data: [
+              { 
+                type: 'average', 
+                name: '平均值',
+                label: {
+                  position: 'middle',
+                  formatter: '{b}: {c}'
+                },
+                symbol: ['none', 'none'],
+                lineStyle: {
+                  color: chartColors[index % chartColors.length],
+                  type: 'dashed'
+                }
+              }
+            ]
+          }
+        }]
+      };
+      
+      chartInstance.setOption(option);
+      
+      // 添加点击事件监听
+      chartInstance.on('click', (params) => {
+        handleChartClick(params, paramInfo, hoursData);
+      });
+    }
+  });
+  
+  // 建立图表联动
+  const chartInstanceArray = Object.values(chartInstances.value);
+  if (chartInstanceArray.length > 1) {
+    echarts.connect(chartInstanceArray);
+  }
+}
+
+/** 获取参数的小时数据(7点到次日7点) */
+function getParamHoursData(paraCode) {
+  // 获取该参数的所有数据
+  const paramData = deviceParams.value.filter(p => p.paraCode === paraCode);
+
+  // 创建7点到次日7点的完整数组(24小时)
+  const hours = [];
+  for (let i = 7; i < 31; i++) {
+    const hour = i % 24;
+    hours.push(hour);
+  }
+
+  const fullHours = hours.map((hour, index) => {
+    const hourData = paramData.find(d => d.hour === hour);
+    // 计算日期,如果是次日(小时小于7点),则日期为查询日期的次日
+    let dataDate = queryParams.value.workDay;
+    if (hour < 7 && index >= 17) { // 17是24小时数组中第18个元素(从0开始),对应次日0点
+      const nextDate = new Date(queryParams.value.workDay);
+      nextDate.setDate(nextDate.getDate() + 1);
+      dataDate = nextDate.toISOString().split('T')[0];
+    }
+
+    return hourData || {
+      hour: hour,
+      paraValue: null,
+      dataDate: dataDate
+    };
+  });
+
+  return fullHours;
+}
+
+/** 图表点击事件处理 */
+function handleChartClick(params, paramInfo, hoursData) {
+  // 获取点击的小时数据
+  const dataIndex = params.dataIndex;
+  const dataItem = hoursData[dataIndex];
+
+  // 检查是否有分钟级数据
+  if (dataItem && dataItem.paraMValue) {
+    try {
+      // 解析分钟级数据
+      let minuteData;
+      try {
+        minuteData = typeof dataItem.paraMValue === 'string'
+          ? JSON.parse(dataItem.paraMValue)
+          : dataItem.paraMValue;
+      } catch (e) {
+        if (Array.isArray(dataItem.paraMValue)) {
+          minuteData = dataItem.paraMValue;
+        } else {
+          minuteData = [];
+        }
+      }
+
+      // 设置对话框标题
+      minuteDialogTitle.value = `${selectedDeviceName.value} - ${paramInfo.paraName} (${dataItem.dataDate} ${dataItem.hour.toString().padStart(2, '0')}:00)`;
+
+      // 设置表格数据
+      minuteTableData.value = [];
+      for (let i = 0; i < 60; i++) {
+        const dataItem = minuteData.find(item => parseInt(item.time) === i);
+        minuteTableData.value.push({
+          time: i.toString(),
+          value: dataItem ? dataItem.value : null
+        });
+      }
+
+      // 设置单位
+      minuteDataUnit.value = paramInfo.updatedBy || '';
+
+      // 显示对话框
+      minuteDialogVisible.value = true;
+
+      // 初始化分钟级图表
+      nextTick(() => {
+        initMinuteChart(minuteData, paramInfo);
+      });
+    } catch (error) {
+      proxy.$modal.msgError("数据解析失败");
+    }
+  } else {
+    proxy.$modal.msgWarning("该小时没有分钟级数据");
+  }
+}
+
+/** 初始化分钟级数据图表 */
+function initMinuteChart(minuteData, paramInfo) {
+  if (minuteChartInstance) {
+    minuteChartInstance.dispose();
+  }
+
+  if (minuteChartRef.value) {
+    minuteChartInstance = echarts.init(minuteChartRef.value);
+
+    // 准备分钟级数据
+    // 创建包含0-59分钟的完整时间数组
+    const timePoints = Array.from({length: 60}, (_, i) => i.toString());
+
+    // 为数据创建包含空值的完整数组
+    const completeValues = Array(60).fill(null);
+    minuteData.forEach(item => {
+      const index = parseInt(item.time);
+      if (index >= 0 && index < 60) {
+        completeValues[index] = item.value !== null ? parseFloat(item.value) : null;
+      }
+    });
+
+    const unit = paramInfo.updatedBy || '';
+    
+    // 为弹窗图表使用固定颜色
+    const chartColor = '#5470c6';
+
+    const option = {
+      color: [chartColor],
+      title: {
+        text: paramInfo.paraName + (unit ? ' (' + unit + ')' : ''),
+        left: 'center'
+      },
+      tooltip: {
+        trigger: 'axis',
+        formatter: function (params) {
+          let result = params[0].axisValue + '分<br/>';
+          params.forEach(param => {
+            let valueText = '';
+            if (param.value !== null) {
+              valueText = unit ? param.value + ' (' + unit + ')' : param.value;
+            } else {
+              valueText = '无数据';
+            }
+            result += `<div style="display:flex;align-items:center;">
+              <span style="display:inline-block;margin-right:5px;border-radius:10px;width:10px;height:10px;background-color:${param.color};"></span>
+              ${param.seriesName}: ${valueText}
+            </div>`;
+          });
+          return result;
+        }
+      },
+      grid: {
+        left: '3%',
+        right: '4%',
+        top: '20%',
+        bottom: 20,
+        containLabel: true
+      },
+      xAxis: {
+        type: 'category',
+        boundaryGap: false,
+        data: timePoints,
+        name: '分钟'
+      },
+      yAxis: {
+        type: 'value',
+      },
+      series: [{
+        name: paramInfo.paraName,
+        type: 'line',
+        data: completeValues,
+        smooth: true,
+        showSymbol: false,
+        lineStyle: {
+          width: 2
+        },
+        markPoint: {
+          data: [
+            { 
+              type: 'max', 
+              name: '最大值',
+              itemStyle: {
+                color: chartColor
+              }
+            },
+            { 
+              type: 'min', 
+              name: '最小值',
+              itemStyle: {
+                color: chartColor
+              }
+            }
+          ]
+        },
+        markLine: {
+          data: [
+            {
+              type: 'average',
+              name: '平均值',
+              label: {
+                position: 'middle',
+                formatter: '{b}: {c}'
+              },
+              symbol: ['none', 'none'],
+              lineStyle: {
+                color: chartColor,
+                type: 'dashed'
+                }
+            }
+          ]
+        }
+      }]
+    };
+
+    minuteChartInstance.setOption(option);
+  }
+}
+
+/** 关闭分钟级数据弹窗 */
+function handleMinuteDialogClose() {
+  minuteDialogVisible.value = false;
+  minuteTableData.value = [];
+  minuteDataUnit.value = '';
+
+  if (minuteChartInstance) {
+    minuteChartInstance.dispose();
+    minuteChartInstance = null;
+  }
+}
+
+/** 窗口大小改变时重置图表大小 */
+function resizeChart() {
+  Object.values(chartInstances.value).forEach(instance => {
+    if (instance) instance.resize();
+  });
+
+  if (minuteChartInstance) {
+    minuteChartInstance.resize();
+  }
+}
+
+onMounted(() => {
+  window.addEventListener('resize', resizeChart);
+  // 初始加载数据
+  getList();
+});
+
+onUnmounted(() => {
+  window.removeEventListener('resize', resizeChart);
+  // 清理图表实例
+  Object.values(chartInstances.value).forEach(instance => {
+    if (instance) instance.dispose();
+  });
+
+  // 断开图表联动
+  const chartInstanceArray = Object.values(chartInstances.value);
+  if (chartInstanceArray.length > 0) {
+    echarts.disconnect(chartInstanceArray);
+  }
+
+  if (minuteChartInstance) {
+    minuteChartInstance.dispose();
+  }
+});
+
+onActivated(() => {
+  resizeChart();
+});
+
+getList();
+</script>