|
|
|
|
@ -0,0 +1,618 @@
|
|
|
|
|
<template>
|
|
|
|
|
<div class="p-2">
|
|
|
|
|
<el-tabs v-model="activeTab" type="border-card">
|
|
|
|
|
<!-- ==================== Tab1: 追溯列表(图5) ==================== -->
|
|
|
|
|
<el-tab-pane label="配方追溯" name="trace">
|
|
|
|
|
<transition :enter-active-class="proxy?.animate.searchAnimate.enter"
|
|
|
|
|
:leave-active-class="proxy?.animate.searchAnimate.leave">
|
|
|
|
|
<div v-show="showSearch" class="mb-[10px]">
|
|
|
|
|
<el-card shadow="hover">
|
|
|
|
|
<el-form ref="traceQueryFormRef" :model="traceQuery" :inline="true">
|
|
|
|
|
<el-form-item label="配方编码" prop="recipeCode">
|
|
|
|
|
<el-input v-model="traceQuery.recipeCode" placeholder="请输入配方编码" clearable
|
|
|
|
|
@keyup.enter="handleTraceQuery" />
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item label="机台" prop="machineId">
|
|
|
|
|
<el-input v-model="traceQuery.machineId" placeholder="请输入机台ID" clearable />
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item label="物料名称" prop="materialName">
|
|
|
|
|
<el-input v-model="traceQuery.materialName" placeholder="请输入物料名称" clearable />
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item label="胶种类型" prop="rubType">
|
|
|
|
|
<el-input v-model="traceQuery.rubType" placeholder="请输入胶种类型" clearable />
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item label="操作者" prop="operCode">
|
|
|
|
|
<el-input v-model="traceQuery.operCode" placeholder="请输入操作者" clearable />
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item label="创建日期" style="width: 380px;">
|
|
|
|
|
<el-date-picker v-model="traceDateRange" type="daterange" range-separator="-"
|
|
|
|
|
start-placeholder="开始日期" end-placeholder="结束日期"
|
|
|
|
|
value-format="YYYY-MM-DD" />
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item>
|
|
|
|
|
<el-button type="primary" icon="Search" @click="handleTraceQuery">搜索</el-button>
|
|
|
|
|
<el-button icon="Refresh" @click="resetTraceQuery">重置</el-button>
|
|
|
|
|
</el-form-item>
|
|
|
|
|
</el-form>
|
|
|
|
|
</el-card>
|
|
|
|
|
</div>
|
|
|
|
|
</transition>
|
|
|
|
|
|
|
|
|
|
<el-card shadow="hover">
|
|
|
|
|
<template #header>
|
|
|
|
|
<el-row :gutter="10" class="mb8">
|
|
|
|
|
<el-col :span="1.5">
|
|
|
|
|
<el-button type="warning" plain icon="Download" @click="handleExport"
|
|
|
|
|
v-hasPermi="['mes:mixTrace:export']">导出</el-button>
|
|
|
|
|
</el-col>
|
|
|
|
|
<right-toolbar v-model:showSearch="showSearch" @queryTable="getTraceList" />
|
|
|
|
|
</el-row>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<el-table v-loading="traceLoading" :data="traceList" @row-click="handleTraceRowClick"
|
|
|
|
|
highlight-current-row border stripe>
|
|
|
|
|
<el-table-column label="配方编码" prop="recipeCode" min-width="120" />
|
|
|
|
|
<el-table-column label="机台名称" prop="machineName" min-width="100" />
|
|
|
|
|
<el-table-column label="物料名称" prop="materialName" min-width="120" />
|
|
|
|
|
<el-table-column label="胶种类型" prop="rubType" min-width="100" />
|
|
|
|
|
<el-table-column label="胶种编码" prop="rubTypecode" min-width="100" />
|
|
|
|
|
<el-table-column label="配方重量" prop="totalWeight" min-width="90" align="right" />
|
|
|
|
|
<el-table-column label="版本号" prop="edtCode" min-width="70" align="center" />
|
|
|
|
|
<el-table-column label="操作者" prop="operCode" min-width="80" />
|
|
|
|
|
<el-table-column label="称量步数" prop="weightCount" min-width="80" align="center" />
|
|
|
|
|
<el-table-column label="混炼步数" prop="mixingCount" min-width="80" align="center" />
|
|
|
|
|
<el-table-column label="配方状态" prop="recipeState" min-width="80" align="center">
|
|
|
|
|
<template #default="{ row }">
|
|
|
|
|
<el-tag :type="row.recipeState === '1' ? 'success' : 'info'" size="small">
|
|
|
|
|
{{ row.recipeState === '1' ? '正用' : '停用' }}
|
|
|
|
|
</el-tag>
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
<el-table-column label="创建时间" prop="createTime" min-width="160" />
|
|
|
|
|
<el-table-column label="操作" width="80" align="center" fixed="right">
|
|
|
|
|
<template #default="{ row }">
|
|
|
|
|
<el-button link type="primary" @click.stop="openDetail(row)">详情</el-button>
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
</el-table>
|
|
|
|
|
|
|
|
|
|
<pagination v-show="traceTotal > 0" :total="traceTotal" v-model:page="traceQuery.pageNum"
|
|
|
|
|
v-model:limit="traceQuery.pageSize" @pagination="getTraceList" />
|
|
|
|
|
</el-card>
|
|
|
|
|
</el-tab-pane>
|
|
|
|
|
|
|
|
|
|
<!-- ==================== Tab2: SPC分析(图6/7/8/10) ==================== -->
|
|
|
|
|
<el-tab-pane label="SPC分析" name="spc">
|
|
|
|
|
<el-card shadow="hover" class="mb-[10px]">
|
|
|
|
|
<el-form ref="spcQueryFormRef" :model="spcQuery" :inline="true">
|
|
|
|
|
<el-form-item label="机台ID" prop="machineId">
|
|
|
|
|
<el-input v-model="spcQuery.machineId" placeholder="请输入机台ID" clearable />
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item label="配方编码" prop="recipeCode">
|
|
|
|
|
<el-input v-model="spcQuery.recipeCode" placeholder="请输入配方编码" clearable />
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item label="胶种编码" prop="rubTypecode">
|
|
|
|
|
<el-input v-model="spcQuery.rubTypecode" placeholder="请输入胶种编码" clearable />
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item label="工步号" prop="mixId">
|
|
|
|
|
<el-input v-model="spcQuery.mixId" placeholder="工步号" clearable style="width: 100px;" />
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item label="分析参数" prop="paramName">
|
|
|
|
|
<el-select v-model="spcQuery.paramName" placeholder="选择分析参数" style="width: 140px;">
|
|
|
|
|
<el-option label="混炼温度" value="mixingTemp" />
|
|
|
|
|
<el-option label="混炼时间" value="mixingTime" />
|
|
|
|
|
<el-option label="混炼能量" value="mixingEnergy" />
|
|
|
|
|
<el-option label="混炼功率" value="mixingPower" />
|
|
|
|
|
<el-option label="混炼压力" value="mixingPress" />
|
|
|
|
|
<el-option label="混炼转速" value="mixingSpeed" />
|
|
|
|
|
</el-select>
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item label="子组大小" prop="subgroupSize">
|
|
|
|
|
<el-input-number v-model="spcQuery.subgroupSize" :min="2" :max="10" style="width: 120px;" />
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item label="日期范围" style="width: 380px;">
|
|
|
|
|
<el-date-picker v-model="spcDateRange" type="daterange" range-separator="-"
|
|
|
|
|
start-placeholder="开始" end-placeholder="结束"
|
|
|
|
|
value-format="YYYY-MM-DD" />
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item>
|
|
|
|
|
<el-button type="primary" icon="Search" @click="handleSpcQuery">分析</el-button>
|
|
|
|
|
<el-button icon="Refresh" @click="resetSpcQuery">重置</el-button>
|
|
|
|
|
</el-form-item>
|
|
|
|
|
</el-form>
|
|
|
|
|
</el-card>
|
|
|
|
|
|
|
|
|
|
<!-- SPC 统计指标卡片 -->
|
|
|
|
|
<el-row :gutter="10" class="mb-[10px]" v-if="spcResult.sampleCount > 0">
|
|
|
|
|
<el-col :span="3" v-for="item in spcIndicators" :key="item.label">
|
|
|
|
|
<el-card shadow="hover" class="text-center">
|
|
|
|
|
<div class="text-xs text-gray-500">{{ item.label }}</div>
|
|
|
|
|
<div class="text-lg font-bold mt-1" :style="{ color: item.color || '#333' }">
|
|
|
|
|
{{ item.value }}
|
|
|
|
|
</div>
|
|
|
|
|
</el-card>
|
|
|
|
|
</el-col>
|
|
|
|
|
</el-row>
|
|
|
|
|
|
|
|
|
|
<!-- SPC 图表区域 -->
|
|
|
|
|
<el-row :gutter="10" class="mb-[10px]" v-if="spcResult.sampleCount > 0">
|
|
|
|
|
<el-col :span="12">
|
|
|
|
|
<el-card shadow="hover">
|
|
|
|
|
<template #header><span>能力分析图(直方图)</span></template>
|
|
|
|
|
<div ref="histogramChartRef" style="height: 350px;" />
|
|
|
|
|
</el-card>
|
|
|
|
|
</el-col>
|
|
|
|
|
<el-col :span="12">
|
|
|
|
|
<el-card shadow="hover">
|
|
|
|
|
<template #header><span>运行图</span></template>
|
|
|
|
|
<div ref="runChartRef" style="height: 350px;" />
|
|
|
|
|
</el-card>
|
|
|
|
|
</el-col>
|
|
|
|
|
</el-row>
|
|
|
|
|
<el-row :gutter="10" class="mb-[10px]" v-if="xbarRResult.sampleCount > 0">
|
|
|
|
|
<el-col :span="12">
|
|
|
|
|
<el-card shadow="hover">
|
|
|
|
|
<template #header><span>Xbar 均值图</span></template>
|
|
|
|
|
<div ref="xbarChartRef" style="height: 350px;" />
|
|
|
|
|
</el-card>
|
|
|
|
|
</el-col>
|
|
|
|
|
<el-col :span="12">
|
|
|
|
|
<el-card shadow="hover">
|
|
|
|
|
<template #header><span>R 极差图</span></template>
|
|
|
|
|
<div ref="rChartRef" style="height: 350px;" />
|
|
|
|
|
</el-card>
|
|
|
|
|
</el-col>
|
|
|
|
|
</el-row>
|
|
|
|
|
|
|
|
|
|
<!-- SPC 样本列表 -->
|
|
|
|
|
<el-card shadow="hover" v-if="spcSampleList.length > 0">
|
|
|
|
|
<template #header><span>SPC样本明细</span></template>
|
|
|
|
|
<el-table :data="spcSampleList" border stripe max-height="400">
|
|
|
|
|
<el-table-column label="配方编码" prop="recipeCode" min-width="120" />
|
|
|
|
|
<el-table-column label="机台" prop="machineName" min-width="100" />
|
|
|
|
|
<el-table-column label="工步号" prop="mixId" width="80" align="center" />
|
|
|
|
|
<el-table-column label="工步编码" prop="termCode" min-width="90" />
|
|
|
|
|
<el-table-column label="实测温度" prop="mixingTemp" width="90" align="right" />
|
|
|
|
|
<el-table-column label="设定温度" prop="setTemp" width="90" align="right" />
|
|
|
|
|
<el-table-column label="实测时间" prop="mixingTime" width="90" align="right" />
|
|
|
|
|
<el-table-column label="设定时间" prop="setTime" width="90" align="right" />
|
|
|
|
|
<el-table-column label="实测能量" prop="mixingEnergy" width="90" align="right" />
|
|
|
|
|
<el-table-column label="实测功率" prop="mixingPower" width="90" align="right" />
|
|
|
|
|
<el-table-column label="实测压力" prop="mixingPress" width="90" align="right" />
|
|
|
|
|
<el-table-column label="实测转速" prop="mixingSpeed" width="90" align="right" />
|
|
|
|
|
<el-table-column label="生产时间" prop="createTime" min-width="160" />
|
|
|
|
|
</el-table>
|
|
|
|
|
<pagination v-show="spcSampleTotal > 0" :total="spcSampleTotal"
|
|
|
|
|
v-model:page="spcQuery.pageNum" v-model:limit="spcQuery.pageSize"
|
|
|
|
|
@pagination="getSpcSamples" />
|
|
|
|
|
</el-card>
|
|
|
|
|
</el-tab-pane>
|
|
|
|
|
</el-tabs>
|
|
|
|
|
|
|
|
|
|
<!-- ==================== 追溯详情抽屉(图9) ==================== -->
|
|
|
|
|
<el-drawer v-model="detailVisible" title="配方追溯详情" size="70%" direction="rtl" destroy-on-close>
|
|
|
|
|
<template v-if="detailData">
|
|
|
|
|
<!-- 基础信息 -->
|
|
|
|
|
<el-descriptions title="基础信息" :column="3" border class="mb-4">
|
|
|
|
|
<el-descriptions-item label="配方编码">{{ detailData.recipeInfo?.recipeCode }}</el-descriptions-item>
|
|
|
|
|
<el-descriptions-item label="机台">{{ detailData.recipeInfo?.machineName }}</el-descriptions-item>
|
|
|
|
|
<el-descriptions-item label="物料">{{ detailData.recipeInfo?.materialName }}</el-descriptions-item>
|
|
|
|
|
<el-descriptions-item label="胶种类型">{{ detailData.recipeInfo?.rubType }}</el-descriptions-item>
|
|
|
|
|
<el-descriptions-item label="胶种编码">{{ detailData.recipeInfo?.rubTypecode }}</el-descriptions-item>
|
|
|
|
|
<el-descriptions-item label="版本号">{{ detailData.recipeInfo?.edtCode }}</el-descriptions-item>
|
|
|
|
|
<el-descriptions-item label="配方重量">{{ detailData.recipeInfo?.totalWeight }}</el-descriptions-item>
|
|
|
|
|
<el-descriptions-item label="填充系数">{{ detailData.recipeInfo?.fillCoefficient }}</el-descriptions-item>
|
|
|
|
|
<el-descriptions-item label="操作者">{{ detailData.recipeInfo?.operCode }}</el-descriptions-item>
|
|
|
|
|
<el-descriptions-item label="完成时间">{{ detailData.recipeInfo?.doneTime }}</el-descriptions-item>
|
|
|
|
|
<el-descriptions-item label="配方状态">
|
|
|
|
|
<el-tag :type="detailData.recipeInfo?.recipeState === '1' ? 'success' : 'info'" size="small">
|
|
|
|
|
{{ detailData.recipeInfo?.recipeState === '1' ? '正用' : '停用' }}
|
|
|
|
|
</el-tag>
|
|
|
|
|
</el-descriptions-item>
|
|
|
|
|
<el-descriptions-item label="创建时间">{{ detailData.recipeInfo?.createTime }}</el-descriptions-item>
|
|
|
|
|
</el-descriptions>
|
|
|
|
|
|
|
|
|
|
<!-- 称量明细 -->
|
|
|
|
|
<div class="mb-4">
|
|
|
|
|
<h4 class="mb-2 font-bold">称量明细({{ detailData.weightList?.length || 0 }}项)</h4>
|
|
|
|
|
<el-table :data="detailData.weightList" border stripe max-height="300" size="small">
|
|
|
|
|
<el-table-column label="序号" prop="weightSeq" width="60" align="center" />
|
|
|
|
|
<el-table-column label="称量类型" prop="weightType" width="90" />
|
|
|
|
|
<el-table-column label="动作编码" prop="actCode" min-width="90" />
|
|
|
|
|
<el-table-column label="设定重量" prop="setWeight" width="90" align="right" />
|
|
|
|
|
<el-table-column label="误差允许" prop="errorAllow" width="90" align="right" />
|
|
|
|
|
<el-table-column label="秤编码" prop="scaleCode" width="80" />
|
|
|
|
|
<el-table-column label="父级编码" prop="fatherCode" width="90" />
|
|
|
|
|
<el-table-column label="子级编码" prop="childCode" width="90" />
|
|
|
|
|
<el-table-column label="使用批次" prop="ifUseBat" width="80" align="center" />
|
|
|
|
|
<el-table-column label="最大比例" prop="maxRate" width="80" />
|
|
|
|
|
</el-table>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 混炼明细 -->
|
|
|
|
|
<div class="mb-4">
|
|
|
|
|
<h4 class="mb-2 font-bold">混炼明细({{ detailData.mixingList?.length || 0 }}项)</h4>
|
|
|
|
|
<el-table :data="detailData.mixingList" border stripe max-height="400" size="small">
|
|
|
|
|
<el-table-column label="步号" prop="mixId" width="60" align="center" />
|
|
|
|
|
<el-table-column label="工步编码" prop="termCode" min-width="80" />
|
|
|
|
|
<el-table-column label="条件编码" prop="condCode" width="80" />
|
|
|
|
|
<el-table-column label="动作编码" prop="actCode" width="80" />
|
|
|
|
|
<el-table-column label="设定时间" prop="setTime" width="80" align="right" />
|
|
|
|
|
<el-table-column label="实测时间" prop="mixingTime" width="80" align="right" />
|
|
|
|
|
<el-table-column label="设定温度" prop="setTemp" width="80" align="right" />
|
|
|
|
|
<el-table-column label="实测温度" prop="mixingTemp" width="80" align="right" />
|
|
|
|
|
<el-table-column label="设定能量" prop="setEnergy" width="80" align="right" />
|
|
|
|
|
<el-table-column label="实测能量" prop="mixingEnergy" width="80" align="right" />
|
|
|
|
|
<el-table-column label="设定功率" prop="setPower" width="80" align="right" />
|
|
|
|
|
<el-table-column label="实测功率" prop="mixingPower" width="80" align="right" />
|
|
|
|
|
<el-table-column label="设定压力" prop="setPres" width="80" align="right" />
|
|
|
|
|
<el-table-column label="实测压力" prop="mixingPress" width="80" align="right" />
|
|
|
|
|
<el-table-column label="设定转速" prop="setRota" width="80" align="right" />
|
|
|
|
|
<el-table-column label="实测转速" prop="mixingSpeed" width="80" align="right" />
|
|
|
|
|
</el-table>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 混炼曲线图(图4) -->
|
|
|
|
|
<div class="mb-4" v-if="detailData.mixingList && detailData.mixingList.length > 0">
|
|
|
|
|
<h4 class="mb-2 font-bold">密炼工作曲线</h4>
|
|
|
|
|
<el-card shadow="hover">
|
|
|
|
|
<div ref="detailCurveChartRef" style="height: 350px;" />
|
|
|
|
|
</el-card>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
</el-drawer>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<script setup lang="ts">
|
|
|
|
|
import { ref, reactive, computed, nextTick, onMounted, watch } from 'vue';
|
|
|
|
|
import { getCurrentInstance } from 'vue';
|
|
|
|
|
import type { ComponentInternalInstance } from 'vue';
|
|
|
|
|
import * as echarts from 'echarts';
|
|
|
|
|
import {
|
|
|
|
|
listMixTrace, exportMixTrace, getMixTraceDetail,
|
|
|
|
|
listSpcSamples, getSpcCapability, getSpcXbarR
|
|
|
|
|
} from '@/api/mes/mixTrace';
|
|
|
|
|
import type {
|
|
|
|
|
MixTraceListVO, MixTraceDetailVO, MixTraceSpcSampleVO, MixTraceSpcResultVO
|
|
|
|
|
} from '@/api/mes/mixTrace/types';
|
|
|
|
|
import { download } from '@/utils/request';
|
|
|
|
|
|
|
|
|
|
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
|
|
|
|
|
|
|
|
|
// ==================== 通用状态 ====================
|
|
|
|
|
const activeTab = ref('trace');
|
|
|
|
|
const showSearch = ref(true);
|
|
|
|
|
|
|
|
|
|
// ==================== 追溯列表(图5) ====================
|
|
|
|
|
const traceLoading = ref(false);
|
|
|
|
|
const traceList = ref<MixTraceListVO[]>([]);
|
|
|
|
|
const traceTotal = ref(0);
|
|
|
|
|
const traceDateRange = ref<string[]>([]);
|
|
|
|
|
const traceQueryFormRef = ref();
|
|
|
|
|
const traceQuery = reactive<any>({
|
|
|
|
|
recipeCode: undefined,
|
|
|
|
|
machineId: undefined,
|
|
|
|
|
materialName: undefined,
|
|
|
|
|
rubType: undefined,
|
|
|
|
|
operCode: undefined,
|
|
|
|
|
beginDate: undefined,
|
|
|
|
|
endDate: undefined,
|
|
|
|
|
pageNum: 1,
|
|
|
|
|
pageSize: 20
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const getTraceList = async () => {
|
|
|
|
|
traceLoading.value = true;
|
|
|
|
|
if (traceDateRange.value && traceDateRange.value.length === 2) {
|
|
|
|
|
traceQuery.beginDate = traceDateRange.value[0];
|
|
|
|
|
traceQuery.endDate = traceDateRange.value[1];
|
|
|
|
|
} else {
|
|
|
|
|
traceQuery.beginDate = undefined;
|
|
|
|
|
traceQuery.endDate = undefined;
|
|
|
|
|
}
|
|
|
|
|
try {
|
|
|
|
|
const res = await listMixTrace(traceQuery);
|
|
|
|
|
traceList.value = res.rows || [];
|
|
|
|
|
traceTotal.value = res.total || 0;
|
|
|
|
|
} finally {
|
|
|
|
|
traceLoading.value = false;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleTraceQuery = () => {
|
|
|
|
|
traceQuery.pageNum = 1;
|
|
|
|
|
getTraceList();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const resetTraceQuery = () => {
|
|
|
|
|
traceQueryFormRef.value?.resetFields();
|
|
|
|
|
traceDateRange.value = [];
|
|
|
|
|
traceQuery.beginDate = undefined;
|
|
|
|
|
traceQuery.endDate = undefined;
|
|
|
|
|
handleTraceQuery();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleExport = () => {
|
|
|
|
|
if (traceDateRange.value && traceDateRange.value.length === 2) {
|
|
|
|
|
traceQuery.beginDate = traceDateRange.value[0];
|
|
|
|
|
traceQuery.endDate = traceDateRange.value[1];
|
|
|
|
|
}
|
|
|
|
|
exportMixTrace(traceQuery);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleTraceRowClick = (row: MixTraceListVO) => {
|
|
|
|
|
openDetail(row);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// ==================== 追溯详情(图9) ====================
|
|
|
|
|
const detailVisible = ref(false);
|
|
|
|
|
const detailData = ref<MixTraceDetailVO | null>(null);
|
|
|
|
|
const detailCurveChartRef = ref<HTMLElement>();
|
|
|
|
|
let detailCurveChart: echarts.ECharts | null = null;
|
|
|
|
|
|
|
|
|
|
const openDetail = async (row: MixTraceListVO) => {
|
|
|
|
|
detailVisible.value = true;
|
|
|
|
|
const res = await getMixTraceDetail(row.recipeId);
|
|
|
|
|
detailData.value = res.data;
|
|
|
|
|
await nextTick();
|
|
|
|
|
renderDetailCurveChart();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/** 渲染密炼工作曲线(图4) */
|
|
|
|
|
const renderDetailCurveChart = () => {
|
|
|
|
|
if (!detailCurveChartRef.value || !detailData.value?.mixingList?.length) return;
|
|
|
|
|
if (detailCurveChart) detailCurveChart.dispose();
|
|
|
|
|
detailCurveChart = echarts.init(detailCurveChartRef.value);
|
|
|
|
|
const mixList = detailData.value.mixingList;
|
|
|
|
|
const xData = mixList.map(m => '步' + m.mixId);
|
|
|
|
|
|
|
|
|
|
detailCurveChart.setOption({
|
|
|
|
|
tooltip: { trigger: 'axis' },
|
|
|
|
|
legend: { top: 0 },
|
|
|
|
|
grid: { left: 60, right: 20, bottom: 30, top: 40 },
|
|
|
|
|
xAxis: { type: 'category', data: xData },
|
|
|
|
|
yAxis: [
|
|
|
|
|
{ type: 'value', name: '温度/能量/功率' },
|
|
|
|
|
{ type: 'value', name: '时间/转速', position: 'right' }
|
|
|
|
|
],
|
|
|
|
|
series: [
|
|
|
|
|
{ name: '实测温度', type: 'line', data: mixList.map(m => m.mixingTemp), smooth: true },
|
|
|
|
|
{ name: '设定温度', type: 'line', data: mixList.map(m => m.setTemp), lineStyle: { type: 'dashed' } },
|
|
|
|
|
{ name: '实测能量', type: 'line', data: mixList.map(m => m.mixingEnergy), smooth: true },
|
|
|
|
|
{ name: '实测功率', type: 'line', data: mixList.map(m => m.mixingPower), smooth: true },
|
|
|
|
|
{ name: '实测转速', type: 'line', yAxisIndex: 1, data: mixList.map(m => m.mixingSpeed), smooth: true },
|
|
|
|
|
{ name: '实测时间', type: 'bar', yAxisIndex: 1, data: mixList.map(m => m.mixingTime), barWidth: 15, opacity: 0.4 }
|
|
|
|
|
]
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// ==================== SPC分析(图6/7/8/10) ====================
|
|
|
|
|
const spcSampleList = ref<MixTraceSpcSampleVO[]>([]);
|
|
|
|
|
const spcSampleTotal = ref(0);
|
|
|
|
|
const spcResult = ref<MixTraceSpcResultVO>({ sampleCount: 0 } as MixTraceSpcResultVO);
|
|
|
|
|
const xbarRResult = ref<MixTraceSpcResultVO>({ sampleCount: 0 } as MixTraceSpcResultVO);
|
|
|
|
|
const spcDateRange = ref<string[]>([]);
|
|
|
|
|
const spcQueryFormRef = ref();
|
|
|
|
|
const spcQuery = reactive<any>({
|
|
|
|
|
machineId: undefined,
|
|
|
|
|
recipeCode: undefined,
|
|
|
|
|
rubTypecode: undefined,
|
|
|
|
|
mixId: undefined,
|
|
|
|
|
paramName: 'mixingTemp',
|
|
|
|
|
subgroupSize: 5,
|
|
|
|
|
beginDate: undefined,
|
|
|
|
|
endDate: undefined,
|
|
|
|
|
pageNum: 1,
|
|
|
|
|
pageSize: 50
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const histogramChartRef = ref<HTMLElement>();
|
|
|
|
|
const runChartRef = ref<HTMLElement>();
|
|
|
|
|
const xbarChartRef = ref<HTMLElement>();
|
|
|
|
|
const rChartRef = ref<HTMLElement>();
|
|
|
|
|
let histogramChart: echarts.ECharts | null = null;
|
|
|
|
|
let runChart: echarts.ECharts | null = null;
|
|
|
|
|
let xbarChart: echarts.ECharts | null = null;
|
|
|
|
|
let rChart: echarts.ECharts | null = null;
|
|
|
|
|
|
|
|
|
|
/** SPC 统计指标卡片 */
|
|
|
|
|
const spcIndicators = computed(() => {
|
|
|
|
|
const r = spcResult.value;
|
|
|
|
|
if (!r || !r.sampleCount) return [];
|
|
|
|
|
return [
|
|
|
|
|
{ label: '样本数', value: r.sampleCount, color: '#409eff' },
|
|
|
|
|
{ label: '均值', value: r.mean?.toFixed(2) ?? '-' },
|
|
|
|
|
{ label: '标准差', value: r.sigma?.toFixed(4) ?? '-' },
|
|
|
|
|
{ label: 'Cp', value: r.cp?.toFixed(3) ?? '-', color: cpkColor(r.cp) },
|
|
|
|
|
{ label: 'Cpk', value: r.cpk?.toFixed(3) ?? '-', color: cpkColor(r.cpk) },
|
|
|
|
|
{ label: 'Ppk', value: r.ppk?.toFixed(3) ?? '-', color: cpkColor(r.ppk) },
|
|
|
|
|
{ label: 'USL', value: r.usl?.toFixed(2) ?? '-' },
|
|
|
|
|
{ label: 'LSL', value: r.lsl?.toFixed(2) ?? '-' }
|
|
|
|
|
];
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const cpkColor = (v: number | null | undefined) => {
|
|
|
|
|
if (v == null) return '#999';
|
|
|
|
|
if (v >= 1.33) return '#67c23a';
|
|
|
|
|
if (v >= 1.0) return '#e6a23c';
|
|
|
|
|
return '#f56c6c';
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const buildSpcParams = () => {
|
|
|
|
|
if (spcDateRange.value && spcDateRange.value.length === 2) {
|
|
|
|
|
spcQuery.beginDate = spcDateRange.value[0];
|
|
|
|
|
spcQuery.endDate = spcDateRange.value[1];
|
|
|
|
|
} else {
|
|
|
|
|
spcQuery.beginDate = undefined;
|
|
|
|
|
spcQuery.endDate = undefined;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleSpcQuery = async () => {
|
|
|
|
|
buildSpcParams();
|
|
|
|
|
await Promise.all([getSpcSamples(), fetchSpcCapability(), fetchSpcXbarR()]);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const resetSpcQuery = () => {
|
|
|
|
|
spcQueryFormRef.value?.resetFields();
|
|
|
|
|
spcDateRange.value = [];
|
|
|
|
|
spcQuery.paramName = 'mixingTemp';
|
|
|
|
|
spcQuery.subgroupSize = 5;
|
|
|
|
|
spcQuery.pageNum = 1;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const getSpcSamples = async () => {
|
|
|
|
|
buildSpcParams();
|
|
|
|
|
const res = await listSpcSamples(spcQuery);
|
|
|
|
|
spcSampleList.value = res.rows || [];
|
|
|
|
|
spcSampleTotal.value = res.total || 0;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const fetchSpcCapability = async () => {
|
|
|
|
|
buildSpcParams();
|
|
|
|
|
const res = await getSpcCapability(spcQuery);
|
|
|
|
|
spcResult.value = res.data || { sampleCount: 0 } as MixTraceSpcResultVO;
|
|
|
|
|
await nextTick();
|
|
|
|
|
renderHistogramChart();
|
|
|
|
|
renderRunChart();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const fetchSpcXbarR = async () => {
|
|
|
|
|
buildSpcParams();
|
|
|
|
|
const res = await getSpcXbarR(spcQuery);
|
|
|
|
|
xbarRResult.value = res.data || { sampleCount: 0 } as MixTraceSpcResultVO;
|
|
|
|
|
await nextTick();
|
|
|
|
|
renderXbarChart();
|
|
|
|
|
renderRChart();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/** 直方图(图7) */
|
|
|
|
|
const renderHistogramChart = () => {
|
|
|
|
|
if (!histogramChartRef.value) return;
|
|
|
|
|
const r = spcResult.value;
|
|
|
|
|
if (!r.histogramBins?.length) return;
|
|
|
|
|
if (histogramChart) histogramChart.dispose();
|
|
|
|
|
histogramChart = echarts.init(histogramChartRef.value);
|
|
|
|
|
histogramChart.setOption({
|
|
|
|
|
tooltip: { trigger: 'axis' },
|
|
|
|
|
grid: { left: 50, right: 20, bottom: 60, top: 30 },
|
|
|
|
|
xAxis: { type: 'category', data: r.histogramBins, axisLabel: { rotate: 30, fontSize: 10 } },
|
|
|
|
|
yAxis: { type: 'value', name: '频次' },
|
|
|
|
|
series: [{
|
|
|
|
|
type: 'bar', data: r.histogramCounts,
|
|
|
|
|
itemStyle: { color: '#409eff' },
|
|
|
|
|
barWidth: '70%'
|
|
|
|
|
}],
|
|
|
|
|
graphic: [{
|
|
|
|
|
type: 'text',
|
|
|
|
|
left: 'center', bottom: 5,
|
|
|
|
|
style: {
|
|
|
|
|
text: `Cp=${r.cp?.toFixed(3) ?? '-'} Cpk=${r.cpk?.toFixed(3) ?? '-'} Ppk=${r.ppk?.toFixed(3) ?? '-'} σ=${r.sigma?.toFixed(4) ?? '-'}`,
|
|
|
|
|
fontSize: 12, fill: '#666'
|
|
|
|
|
}
|
|
|
|
|
}]
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/** 运行图(图8) */
|
|
|
|
|
const renderRunChart = () => {
|
|
|
|
|
if (!runChartRef.value) return;
|
|
|
|
|
const r = spcResult.value;
|
|
|
|
|
if (!r.sampleValues?.length) return;
|
|
|
|
|
if (runChart) runChart.dispose();
|
|
|
|
|
runChart = echarts.init(runChartRef.value);
|
|
|
|
|
|
|
|
|
|
const markLines: any[] = [];
|
|
|
|
|
if (r.usl != null) markLines.push({ yAxis: r.usl, name: 'USL', lineStyle: { color: '#f56c6c', type: 'dashed' } });
|
|
|
|
|
if (r.lsl != null) markLines.push({ yAxis: r.lsl, name: 'LSL', lineStyle: { color: '#f56c6c', type: 'dashed' } });
|
|
|
|
|
if (r.target != null) markLines.push({ yAxis: r.target, name: 'Target', lineStyle: { color: '#67c23a', type: 'solid' } });
|
|
|
|
|
if (r.mean != null) markLines.push({ yAxis: r.mean, name: 'Mean', lineStyle: { color: '#409eff', type: 'dotted' } });
|
|
|
|
|
|
|
|
|
|
runChart.setOption({
|
|
|
|
|
tooltip: { trigger: 'axis' },
|
|
|
|
|
grid: { left: 60, right: 20, bottom: 30, top: 20 },
|
|
|
|
|
xAxis: { type: 'category', data: r.sampleLabels || r.sampleValues.map((_, i) => i + 1) },
|
|
|
|
|
yAxis: { type: 'value', name: r.paramLabel || '' },
|
|
|
|
|
series: [{
|
|
|
|
|
type: 'line', data: r.sampleValues, smooth: false,
|
|
|
|
|
symbol: 'circle', symbolSize: 4,
|
|
|
|
|
markLine: { silent: true, data: markLines, label: { position: 'end', fontSize: 10 } }
|
|
|
|
|
}]
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/** Xbar 均值图 */
|
|
|
|
|
const renderXbarChart = () => {
|
|
|
|
|
if (!xbarChartRef.value) return;
|
|
|
|
|
const r = xbarRResult.value;
|
|
|
|
|
if (!r.xbarValues?.length) return;
|
|
|
|
|
if (xbarChart) xbarChart.dispose();
|
|
|
|
|
xbarChart = echarts.init(xbarChartRef.value);
|
|
|
|
|
xbarChart.setOption({
|
|
|
|
|
tooltip: { trigger: 'axis' },
|
|
|
|
|
grid: { left: 60, right: 20, bottom: 30, top: 20 },
|
|
|
|
|
xAxis: { type: 'category', data: r.subgroupLabels },
|
|
|
|
|
yAxis: { type: 'value', name: 'Xbar' },
|
|
|
|
|
series: [{
|
|
|
|
|
type: 'line', data: r.xbarValues, symbol: 'circle', symbolSize: 5,
|
|
|
|
|
markLine: {
|
|
|
|
|
silent: true,
|
|
|
|
|
label: { position: 'end', fontSize: 10 },
|
|
|
|
|
data: [
|
|
|
|
|
{ yAxis: r.uclX, name: 'UCL', lineStyle: { color: '#f56c6c', type: 'dashed' } },
|
|
|
|
|
{ yAxis: r.clX, name: 'CL', lineStyle: { color: '#67c23a' } },
|
|
|
|
|
{ yAxis: r.lclX, name: 'LCL', lineStyle: { color: '#f56c6c', type: 'dashed' } }
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
}]
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/** R 极差图 */
|
|
|
|
|
const renderRChart = () => {
|
|
|
|
|
if (!rChartRef.value) return;
|
|
|
|
|
const r = xbarRResult.value;
|
|
|
|
|
if (!r.rValues?.length) return;
|
|
|
|
|
if (rChart) rChart.dispose();
|
|
|
|
|
rChart = echarts.init(rChartRef.value);
|
|
|
|
|
rChart.setOption({
|
|
|
|
|
tooltip: { trigger: 'axis' },
|
|
|
|
|
grid: { left: 60, right: 20, bottom: 30, top: 20 },
|
|
|
|
|
xAxis: { type: 'category', data: r.subgroupLabels },
|
|
|
|
|
yAxis: { type: 'value', name: 'R' },
|
|
|
|
|
series: [{
|
|
|
|
|
type: 'line', data: r.rValues, symbol: 'circle', symbolSize: 5,
|
|
|
|
|
markLine: {
|
|
|
|
|
silent: true,
|
|
|
|
|
label: { position: 'end', fontSize: 10 },
|
|
|
|
|
data: [
|
|
|
|
|
{ yAxis: r.uclR, name: 'UCL', lineStyle: { color: '#f56c6c', type: 'dashed' } },
|
|
|
|
|
{ yAxis: r.clR, name: 'CL', lineStyle: { color: '#67c23a' } },
|
|
|
|
|
{ yAxis: r.lclR, name: 'LCL', lineStyle: { color: '#f56c6c', type: 'dashed' } }
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
}]
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// ==================== 初始化 ====================
|
|
|
|
|
onMounted(() => {
|
|
|
|
|
getTraceList();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
/** 监听窗口变化自适应图表 */
|
|
|
|
|
window.addEventListener('resize', () => {
|
|
|
|
|
histogramChart?.resize();
|
|
|
|
|
runChart?.resize();
|
|
|
|
|
xbarChart?.resize();
|
|
|
|
|
rChart?.resize();
|
|
|
|
|
detailCurveChart?.resize();
|
|
|
|
|
});
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
|
|
|
|
.text-center {
|
|
|
|
|
text-align: center;
|
|
|
|
|
}
|
|
|
|
|
</style>
|