<template>
|
<div class="app-container">
|
<!-- 分析配置表单 -->
|
<el-card shadow="hover" style="margin-bottom: 20px;">
|
<div slot="header">SPC分析配置</div>
|
<el-form ref="analysisForm" :model="analysisParams" :inline="true" size="small" label-width="100px">
|
<el-form-item label="检测项目" prop="projectId">
|
<el-select v-model="analysisParams.projectId" placeholder="请选择检测项目" style="width: 200px" @change="handleProjectChange">
|
<el-option v-for="item in projectList" :key="item.id" :label="item.projectName" :value="item.id" />
|
</el-select>
|
</el-form-item>
|
<el-form-item label="检测参数" prop="paramName">
|
<el-select v-model="analysisParams.paramName" placeholder="请选择检测参数" style="width: 200px">
|
<el-option v-for="item in paramList" :key="item.paramName" :label="item.paramName" :value="item.paramName" />
|
</el-select>
|
</el-form-item>
|
<el-form-item label="时间范围" prop="timeRange">
|
<el-date-picker
|
v-model="analysisParams.timeRange"
|
type="daterange"
|
range-separator="-"
|
start-placeholder="开始时间"
|
end-placeholder="结束时间"
|
value-format="yyyy-MM-dd"
|
style="width: 240px"
|
/>
|
</el-form-item>
|
<el-form-item label="子组大小" prop="subgroupSize">
|
<el-input-number v-model="analysisParams.subgroupSize" :min="2" :max="25" style="width: 150px" />
|
</el-form-item>
|
<el-form-item label="控制上限UCL" prop="ucl">
|
<el-input-number v-model="analysisParams.ucl" :precision="4" style="width: 150px" />
|
</el-form-item>
|
<el-form-item label="控制下限LCL" prop="lcl">
|
<el-input-number v-model="analysisParams.lcl" :precision="4" style="width: 150px" />
|
</el-form-item>
|
<el-form-item label="目标值CL" prop="targetValue">
|
<el-input-number v-model="analysisParams.targetValue" :precision="4" style="width: 150px" />
|
</el-form-item>
|
<el-form-item>
|
<el-button type="primary" icon="el-icon-data-analysis" @click="handleAnalysis">开始分析</el-button>
|
<el-button type="success" icon="el-icon-download" @click="handleExport">导出数据</el-button>
|
</el-form-item>
|
</el-form>
|
</el-card>
|
|
<!-- SPC图表区域 -->
|
<el-row :gutter="20" v-if="analysisResult">
|
<el-col :span="12">
|
<el-card shadow="hover">
|
<div slot="header">X-Bar控制图</div>
|
<Echart
|
:xAxis="xBarXAxis"
|
:yAxis="xBarYAxis"
|
:series="xBarSeries"
|
:tooltip="{ trigger: 'axis' }"
|
:grid="{ left: '3%', right: '4%', bottom: '3%', containLabel: true }"
|
:chartStyle="{ height: '350px' }"
|
/>
|
</el-card>
|
</el-col>
|
<el-col :span="12">
|
<el-card shadow="hover">
|
<div slot="header">R控制图</div>
|
<Echart
|
:xAxis="rChartXAxis"
|
:yAxis="rChartYAxis"
|
:series="rChartSeries"
|
:tooltip="{ trigger: 'axis' }"
|
:grid="{ left: '3%', right: '4%', bottom: '3%', containLabel: true }"
|
:chartStyle="{ height: '350px' }"
|
/>
|
</el-card>
|
</el-col>
|
</el-row>
|
|
<!-- 制程能力分析 -->
|
<el-card shadow="hover" style="margin-top: 20px;" v-if="analysisResult">
|
<div slot="header">制程能力分析</div>
|
<el-row :gutter="20">
|
<el-col :span="6">
|
<div class="capability-card">
|
<div class="capability-label">Cp (制程精密度)</div>
|
<div class="capability-value" :style="{ color: getCapabilityColor(analysisResult.capability.cp) }">
|
{{ analysisResult.capability.cp || '--' }}
|
</div>
|
<div class="capability-status">{{ getCapabilityStatus(analysisResult.capability.cp) }}</div>
|
</div>
|
</el-col>
|
<el-col :span="6">
|
<div class="capability-card">
|
<div class="capability-label">Cpk (制程精确度)</div>
|
<div class="capability-value" :style="{ color: getCapabilityColor(analysisResult.capability.cpk) }">
|
{{ analysisResult.capability.cpk || '--' }}
|
</div>
|
<div class="capability-status">{{ getCapabilityStatus(analysisResult.capability.cpk) }}</div>
|
</div>
|
</el-col>
|
<el-col :span="6">
|
<div class="capability-card">
|
<div class="capability-label">Pp (过程精密度)</div>
|
<div class="capability-value" :style="{ color: getCapabilityColor(analysisResult.capability.pp) }">
|
{{ analysisResult.capability.pp || '--' }}
|
</div>
|
<div class="capability-status">{{ getCapabilityStatus(analysisResult.capability.pp) }}</div>
|
</div>
|
</el-col>
|
<el-col :span="6">
|
<div class="capability-card">
|
<div class="capability-label">Ppk (过程精确度)</div>
|
<div class="capability-value" :style="{ color: getCapabilityColor(analysisResult.capability.ppk) }">
|
{{ analysisResult.capability.ppk || '--' }}
|
</div>
|
<div class="capability-status">{{ getCapabilityStatus(analysisResult.capability.ppk) }}</div>
|
</div>
|
</el-col>
|
</el-row>
|
</el-card>
|
|
<!-- 分析数据表格 -->
|
<el-card shadow="hover" style="margin-top: 20px;" v-if="analysisResult">
|
<div slot="header">分析数据明细</div>
|
<el-table :data="analysisDataTable" border style="width: 100%" max-height="400">
|
<el-table-column prop="subgroupNo" label="子组号" width="80" />
|
<el-table-column prop="xBar" label="X均值" width="100" />
|
<el-table-column prop="range" label="极差R" width="100" />
|
<el-table-column v-for="(item, index) in subgroupColumns" :key="index" :prop="`value${index + 1}`" :label="`数据${index + 1}`" width="100" />
|
<el-table-column prop="status" label="状态" width="100">
|
<template slot-scope="scope">
|
<el-tag :type="scope.row.status === '正常' ? 'success' : 'danger'">{{ scope.row.status }}</el-tag>
|
</template>
|
</el-table-column>
|
</el-table>
|
</el-card>
|
</div>
|
</template>
|
|
<script>
|
import Echart from '@/components/echarts/echarts.vue'
|
import { spcAnalyze, getProjectList, getParamList, exportSpcData } from '@/api/report/spcChart'
|
|
export default {
|
name: 'SpcChart',
|
components: { Echart },
|
data() {
|
return {
|
projectList: [],
|
paramList: [],
|
analysisParams: {
|
projectId: null,
|
paramName: null,
|
timeRange: [],
|
subgroupSize: 5,
|
ucl: null,
|
lcl: null,
|
targetValue: null
|
},
|
analysisResult: null,
|
analysisDataTable: [],
|
subgroupColumns: [],
|
// X-Bar图表
|
xBarXAxis: [{ type: 'category', data: [] }],
|
xBarYAxis: [{ type: 'value' }],
|
xBarSeries: [
|
{ name: 'X均值', type: 'line', data: [], markLine: { data: [] } }
|
],
|
// R图表
|
rChartXAxis: [{ type: 'category', data: [] }],
|
rChartYAxis: [{ type: 'value' }],
|
rChartSeries: [
|
{ name: '极差R', type: 'line', data: [], markLine: { data: [] } }
|
]
|
}
|
},
|
mounted() {
|
this.getProjectList()
|
},
|
methods: {
|
getProjectList() {
|
getProjectList().then(res => {
|
this.projectList = res.data || []
|
})
|
},
|
handleProjectChange(projectId) {
|
this.analysisParams.paramName = null
|
getParamList(projectId).then(res => {
|
this.paramList = res.data || []
|
})
|
},
|
handleAnalysis() {
|
if (!this.analysisParams.projectId) {
|
this.$message.warning('请选择检测项目')
|
return
|
}
|
if (!this.analysisParams.paramName) {
|
this.$message.warning('请选择检测参数')
|
return
|
}
|
const params = {
|
projectId: this.analysisParams.projectId,
|
paramName: this.analysisParams.paramName,
|
subgroupSize: this.analysisParams.subgroupSize,
|
ucl: this.analysisParams.ucl,
|
lcl: this.analysisParams.lcl,
|
targetValue: this.analysisParams.targetValue
|
}
|
if (this.analysisParams.timeRange && this.analysisParams.timeRange.length === 2) {
|
params.startDate = this.analysisParams.timeRange[0]
|
params.endDate = this.analysisParams.timeRange[1]
|
}
|
spcAnalyze(params).then(res => {
|
this.analysisResult = res.data
|
this.renderCharts(res.data)
|
this.renderDataTable(res.data)
|
this.$message.success('分析完成')
|
})
|
},
|
renderCharts(data) {
|
const xBarData = data.xBar || {}
|
const rChartData = data.rChart || {}
|
const subgroupLabels = (xBarData.data || []).map((_, i) => `组${i + 1}`)
|
// X-Bar图
|
this.xBarXAxis[0].data = subgroupLabels
|
this.xBarSeries[0].data = xBarData.data || []
|
this.xBarSeries[0].markLine = {
|
data: [
|
{ yAxis: xBarData.ucl, name: 'UCL', lineStyle: { color: '#F56C6C' } },
|
{ yAxis: xBarData.lcl, name: 'LCL', lineStyle: { color: '#F56C6C' } },
|
{ yAxis: xBarData.cl, name: 'CL', lineStyle: { color: '#409EFF' } }
|
]
|
}
|
// R图
|
this.rChartXAxis[0].data = subgroupLabels
|
this.rChartSeries[0].data = rChartData.data || []
|
this.rChartSeries[0].markLine = {
|
data: [
|
{ yAxis: rChartData.ucl, name: 'UCL', lineStyle: { color: '#F56C6C' } },
|
{ yAxis: rChartData.lcl, name: 'LCL', lineStyle: { color: '#F56C6C' } },
|
{ yAxis: rChartData.cl, name: 'CL', lineStyle: { color: '#409EFF' } }
|
]
|
}
|
},
|
renderDataTable(data) {
|
const subgroupSize = this.analysisParams.subgroupSize
|
this.subgroupColumns = []
|
for (let i = 1; i <= subgroupSize; i++) {
|
this.subgroupColumns.push({ prop: `value${i}` })
|
}
|
const rawData = data.rawData || []
|
const xBarData = data.xBar?.data || []
|
const rChartData = data.rChart?.data || []
|
const ucl = data.xBar?.ucl
|
const lcl = data.xBar?.lcl
|
this.analysisDataTable = rawData.map((group, index) => {
|
const xBar = xBarData[index]
|
const status = (ucl && lcl) ? (xBar >= lcl && xBar <= ucl ? '正常' : '异常') : '正常'
|
const row = {
|
subgroupNo: index + 1,
|
xBar: xBar?.toFixed(4),
|
range: rChartData[index]?.toFixed(4),
|
status
|
}
|
group.forEach((val, i) => {
|
row[`value${i + 1}`] = val?.toFixed(4)
|
})
|
return row
|
})
|
},
|
handleExport() {
|
if (!this.analysisResult) {
|
this.$message.warning('请先进行SPC分析')
|
return
|
}
|
const params = {
|
projectId: this.analysisParams.projectId,
|
paramName: this.analysisParams.paramName
|
}
|
exportSpcData(params).then(res => {
|
this.downloadFile(res, 'SPC分析数据.xlsx')
|
})
|
},
|
getCapabilityColor(val) {
|
if (val >= 1.33) return '#67C23A'
|
if (val >= 1) return '#E6A23C'
|
return '#F56C6C'
|
},
|
getCapabilityStatus(val) {
|
if (val >= 1.33) return '制程能力优秀'
|
if (val >= 1) return '制程能力合格'
|
return '制程能力不足'
|
},
|
downloadFile(data, fileName) {
|
const blob = new Blob([data])
|
const url = window.URL.createObjectURL(blob)
|
const link = document.createElement('a')
|
link.href = url
|
link.download = fileName
|
link.click()
|
window.URL.revokeObjectURL(url)
|
}
|
}
|
}
|
</script>
|
|
<style scoped>
|
.capability-card {
|
text-align: center;
|
padding: 20px;
|
background: #f5f7fa;
|
border-radius: 8px;
|
}
|
.capability-label {
|
font-size: 14px;
|
color: #909399;
|
}
|
.capability-value {
|
font-size: 32px;
|
font-weight: bold;
|
margin-top: 10px;
|
}
|
.capability-status {
|
font-size: 12px;
|
color: #909399;
|
margin-top: 5px;
|
}
|
</style>
|