|
|
@@ -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>
|