<template>
|
<div class="app-container">
|
<!-- 分析配置表单 -->
|
<el-card shadow="hover" style="margin-bottom: 20px;">
|
<div slot="header">正态分布分析配置</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>
|
<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>
|
|
<el-row :gutter="20" v-if="analysisResult">
|
<!-- 正态分布图 -->
|
<el-col :span="16">
|
<el-card shadow="hover">
|
<div slot="header">正态分布图</div>
|
<Echart
|
:xAxis="distributionXAxis"
|
:yAxis="distributionYAxis"
|
:series="distributionSeries"
|
:tooltip="{ trigger: 'axis' }"
|
:legend="{ data: ['频数', '正态曲线'] }"
|
:grid="{ left: '3%', right: '4%', bottom: '3%', containLabel: true }"
|
:chartStyle="{ height: '400px' }"
|
/>
|
</el-card>
|
</el-col>
|
<!-- 统计信息 -->
|
<el-col :span="8">
|
<el-card shadow="hover">
|
<div slot="header">统计信息</div>
|
<el-descriptions :column="1" border>
|
<el-descriptions-item label="样本数量">{{ analysisResult.statistics.sampleCount || 0 }}</el-descriptions-item>
|
<el-descriptions-item label="均值(μ)">{{ (analysisResult.statistics.mean || 0).toFixed(4) }}</el-descriptions-item>
|
<el-descriptions-item label="标准差(σ)">{{ (analysisResult.statistics.stdDev || 0).toFixed(4) }}</el-descriptions-item>
|
<el-descriptions-item label="最小值">{{ (analysisResult.statistics.min || 0).toFixed(4) }}</el-descriptions-item>
|
<el-descriptions-item label="最大值">{{ (analysisResult.statistics.max || 0).toFixed(4) }}</el-descriptions-item>
|
<el-descriptions-item label="极差">{{ (analysisResult.statistics.range || 0).toFixed(4) }}</el-descriptions-item>
|
<el-descriptions-item label="中位数">{{ (analysisResult.statistics.median || 0).toFixed(4) }}</el-descriptions-item>
|
<el-descriptions-item label="偏度">{{ (analysisResult.statistics.skewness || 0).toFixed(4) }}</el-descriptions-item>
|
<el-descriptions-item label="峰度">{{ (analysisResult.statistics.kurtosis || 0).toFixed(4) }}</el-descriptions-item>
|
</el-descriptions>
|
</el-card>
|
</el-col>
|
</el-row>
|
|
<!-- 直方图数据表格 -->
|
<el-row :gutter="20" style="margin-top: 20px;" v-if="analysisResult">
|
<el-col :span="12">
|
<el-card shadow="hover">
|
<div slot="header">频数分布表</div>
|
<el-table :data="analysisResult.histogramData" border style="width: 100%">
|
<el-table-column prop="interval" label="区间" />
|
<el-table-column prop="frequency" label="频数" />
|
<el-table-column prop="relativeFreq" label="相对频率">
|
<template slot-scope="scope">
|
{{ (scope.row.relativeFreq * 100).toFixed(2) }}%
|
</template>
|
</el-table-column>
|
<el-table-column prop="cumulativeFreq" label="累计频率">
|
<template slot-scope="scope">
|
{{ (scope.row.cumulativeFreq * 100).toFixed(2) }}%
|
</template>
|
</el-table-column>
|
</el-table>
|
</el-card>
|
</el-col>
|
<el-col :span="12">
|
<el-card shadow="hover">
|
<div slot="header">过程能力</div>
|
<el-row :gutter="20">
|
<el-col :span="12">
|
<div class="capability-item">
|
<div class="capability-label">规格上限(USL)</div>
|
<el-input-number v-model="analysisParams.usl" :precision="4" style="width: 100%;" @change="calculateCpk" />
|
</div>
|
</el-col>
|
<el-col :span="12">
|
<div class="capability-item">
|
<div class="capability-label">规格下限(LSL)</div>
|
<el-input-number v-model="analysisParams.lsl" :precision="4" style="width: 100%;" @change="calculateCpk" />
|
</div>
|
</el-col>
|
</el-row>
|
<el-divider />
|
<el-row :gutter="20">
|
<el-col :span="12">
|
<div class="capability-result">
|
<div class="capability-label">Cp</div>
|
<div class="capability-value" :style="{ color: getCapabilityColor(calculatedCpk.cp) }">
|
{{ calculatedCpk.cp ? calculatedCpk.cp.toFixed(4) : '--' }}
|
</div>
|
</div>
|
</el-col>
|
<el-col :span="12">
|
<div class="capability-result">
|
<div class="capability-label">Cpk</div>
|
<div class="capability-value" :style="{ color: getCapabilityColor(calculatedCpk.cpk) }">
|
{{ calculatedCpk.cpk ? calculatedCpk.cpk.toFixed(4) : '--' }}
|
</div>
|
</div>
|
</el-col>
|
</el-row>
|
</el-card>
|
</el-col>
|
</el-row>
|
|
<!-- 原始数据 -->
|
<el-card shadow="hover" style="margin-top: 20px;" v-if="analysisResult">
|
<div slot="header">原始数据</div>
|
<el-table :data="rawDataTable" border style="width: 100%" max-height="300">
|
<el-table-column type="index" label="序号" width="60" />
|
<el-table-column prop="value" label="检测值">
|
<template slot-scope="scope">
|
{{ scope.row.value ? scope.row.value.toFixed(4) : '' }}
|
</template>
|
</el-table-column>
|
<el-table-column prop="sampleCode" label="样品编号" />
|
<el-table-column prop="testTime" label="检测时间" />
|
<el-table-column prop="tester" label="检测人" />
|
</el-table>
|
</el-card>
|
</div>
|
</template>
|
|
<script>
|
import Echart from '@/components/echarts/echarts.vue'
|
import { normalDistributionAnalyze, getProjectList, getParamList, exportNormalDistribution } from '@/api/report/normalDistribution'
|
|
export default {
|
name: 'NormalDistribution',
|
components: { Echart },
|
data() {
|
return {
|
projectList: [],
|
paramList: [],
|
analysisParams: {
|
projectId: null,
|
paramName: null,
|
timeRange: [],
|
usl: null,
|
lsl: null
|
},
|
analysisResult: null,
|
rawDataTable: [],
|
calculatedCpk: {
|
cp: null,
|
cpk: null
|
},
|
// 正态分布图
|
distributionXAxis: [{ type: 'category', data: [] }],
|
distributionYAxis: [{ type: 'value' }],
|
distributionSeries: [
|
{ name: '频数', type: 'bar', data: [], barWidth: '60%' },
|
{ name: '正态曲线', type: 'line', data: [], smooth: true }
|
]
|
}
|
},
|
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
|
}
|
if (this.analysisParams.timeRange && this.analysisParams.timeRange.length === 2) {
|
params.startDate = this.analysisParams.timeRange[0]
|
params.endDate = this.analysisParams.timeRange[1]
|
}
|
normalDistributionAnalyze(params).then(res => {
|
this.analysisResult = res.data
|
this.renderCharts(res.data)
|
this.rawDataTable = (res.data.rawData || []).map(item => ({
|
value: item.value,
|
sampleCode: item.sampleCode,
|
testTime: item.testTime,
|
tester: item.tester
|
}))
|
this.$message.success('分析完成')
|
})
|
},
|
renderCharts(data) {
|
const histogramData = data.histogramData || []
|
const normalCurve = data.normalCurve || []
|
// 直方图数据
|
this.distributionXAxis[0].data = histogramData.map(item => item.interval)
|
this.distributionSeries[0].data = histogramData.map(item => item.frequency)
|
// 正态曲线数据
|
this.distributionSeries[1].data = normalCurve
|
},
|
calculateCpk() {
|
if (!this.analysisResult || !this.analysisParams.usl || !this.analysisParams.lsl) {
|
return
|
}
|
const stats = this.analysisResult.statistics
|
const mean = stats.mean
|
const stdDev = stats.stdDev
|
const usl = this.analysisParams.usl
|
const lsl = this.analysisParams.lsl
|
// Cp = (USL - LSL) / (6σ)
|
this.calculatedCpk.cp = (usl - lsl) / (6 * stdDev)
|
// Cpk = min[(USL - μ) / (3σ), (μ - LSL) / (3σ)]
|
const cpu = (usl - mean) / (3 * stdDev)
|
const cpl = (mean - lsl) / (3 * stdDev)
|
this.calculatedCpk.cpk = Math.min(cpu, cpl)
|
},
|
handleExport() {
|
if (!this.analysisResult) {
|
this.$message.warning('请先进行分析')
|
return
|
}
|
const params = {
|
projectId: this.analysisParams.projectId,
|
paramName: this.analysisParams.paramName
|
}
|
exportNormalDistribution(params).then(res => {
|
this.downloadFile(res, '正态分布分析数据.xlsx')
|
})
|
},
|
getCapabilityColor(val) {
|
if (!val) return '#909399'
|
if (val >= 1.33) return '#67C23A'
|
if (val >= 1) return '#E6A23C'
|
return '#F56C6C'
|
},
|
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-item {
|
margin-bottom: 15px;
|
}
|
.capability-label {
|
font-size: 14px;
|
color: #909399;
|
margin-bottom: 8px;
|
}
|
.capability-result {
|
text-align: center;
|
padding: 15px;
|
background: #f5f7fa;
|
border-radius: 8px;
|
}
|
.capability-value {
|
font-size: 24px;
|
font-weight: bold;
|
margin-top: 10px;
|
}
|
</style>
|