feat(mes): 更新配方追溯页面功能和界面优化

- 将查询字段从机台ID改为机台名称,并调整相关数据绑定
- 优化表单布局和输入组件的样式,提升用户体验
- 添加字典标签显示配方状态、称量类型和使用批次选项
- 增加SPC分析中的运行图功能并完善图表展示
- 重构日期范围处理逻辑,统一应用日期筛选条件
- 调整表格列宽配置,优化数据显示效果
- 完善混炼曲线图渲染逻辑,修复潜在的图表初始化问题
- 优化导出功能实现,使用下载工具替代原有方法
- 整理代码结构,移除注释掉的无用分隔线标记
- 添加SPC参数选项的动态计算和标签解析功能
master
zangch@mesnac.com 2 months ago
parent 7ac5b80ea2
commit 9243aeb087

@ -154,6 +154,7 @@ export interface MixTraceSpcResultVO {
export interface MixTraceQuery { export interface MixTraceQuery {
recipeCode?: string; recipeCode?: string;
machineId?: string | number; machineId?: string | number;
machineName?: string;
materialId?: string | number; materialId?: string | number;
materialName?: string; materialName?: string;
recipeState?: string; recipeState?: string;
@ -173,9 +174,11 @@ export interface MixTraceQuery {
*/ */
export interface SpcQuery { export interface SpcQuery {
machineId?: string | number; machineId?: string | number;
machineName?: string;
materialId?: string | number; materialId?: string | number;
recipeCode?: string; recipeCode?: string;
rubTypecode?: string; rubTypecode?: string;
rubType?: string;
mixId?: string | number; mixId?: string | number;
termCode?: string; termCode?: string;
paramName?: string; paramName?: string;

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

Loading…
Cancel
Save