feat(FQC): 新增合格率报表页面及组件
新增FQC模块下的合格率报表功能,包含概览和表格两个视图: 1. 概览视图展示总体合格率、一次合格率等关键指标和趋势图表 2. 表格视图展示详细数据并支持分页和导出 3. 添加时间范围选择、物料号查询等筛选功能
This commit is contained in:
@@ -0,0 +1,471 @@
|
||||
<template>
|
||||
<div class="qualification-rate-overview">
|
||||
<!-- 合格率概览区域 -->
|
||||
<el-row :gutter="20">
|
||||
<!-- 总体合格率卡片 -->
|
||||
<el-col :span="6">
|
||||
<el-card class="stat-card">
|
||||
<div class="stat-content">
|
||||
<div class="stat-title">总体合格率</div>
|
||||
<div class="stat-value">{{ overallRate }}%</div>
|
||||
<div class="stat-desc">
|
||||
<span class="stat-text">投入数: {{ totalInput }}</span>
|
||||
<span class="stat-text">合格数: {{ totalQualified }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
|
||||
<!-- 一次合格率卡片 -->
|
||||
<el-col :span="6">
|
||||
<el-card class="stat-card">
|
||||
<div class="stat-content">
|
||||
<div class="stat-title">一次合格率</div>
|
||||
<div class="stat-value">{{ firstPassRate }}%</div>
|
||||
<div class="stat-desc">
|
||||
<span class="stat-text">投入数: {{ firstPassInput }}</span>
|
||||
<span class="stat-text">合格数: {{ firstPassQualified }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
|
||||
<!-- 返工率卡片 -->
|
||||
<el-col :span="6">
|
||||
<el-card class="stat-card">
|
||||
<div class="stat-content">
|
||||
<div class="stat-title">返工率</div>
|
||||
<div class="stat-value">{{ reworkRate }}%</div>
|
||||
<div class="stat-desc">
|
||||
<span class="stat-text">返工数: {{ reworkCount }}</span>
|
||||
<span class="stat-text">占比: {{ reworkRatio }}%</span>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
|
||||
<!-- 报废率卡片 -->
|
||||
<el-col :span="6">
|
||||
<el-card class="stat-card">
|
||||
<div class="stat-content">
|
||||
<div class="stat-title">报废率</div>
|
||||
<div class="stat-value">{{ scrapRate }}%</div>
|
||||
<div class="stat-desc">
|
||||
<span class="stat-text">报废数: {{ scrapCount }}</span>
|
||||
<span class="stat-text">占比: {{ scrapRatio }}%</span>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 图表区域 -->
|
||||
<el-row :gutter="20" style="margin-top: 20px;">
|
||||
<!-- 合格率趋势图 -->
|
||||
<el-col :span="12">
|
||||
<el-card>
|
||||
<div slot="header" class="card-header">
|
||||
<span>合格率趋势图</span>
|
||||
</div>
|
||||
<div ref="trendChart" class="chart"></div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
|
||||
<!-- 不合格项分布图 -->
|
||||
<el-col :span="12">
|
||||
<el-card>
|
||||
<div slot="header" class="card-header">
|
||||
<span>不合格项分布图</span>
|
||||
</div>
|
||||
<div ref="defectChart" class="chart"></div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import * as echarts from 'echarts'
|
||||
import { GetProductAndPolishAndOneTimeFqcBoardData } from '@/api/qualityManagement/commonFQC.js'
|
||||
import { debounce } from '@/utils'
|
||||
|
||||
export default {
|
||||
name: 'QualificationRateOverview',
|
||||
props: {
|
||||
queryParams: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
timeRange: {
|
||||
type: Object,
|
||||
default: () => ({
|
||||
startTime: '',
|
||||
endTime: ''
|
||||
})
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 统计数据
|
||||
overallRate: '0.00',
|
||||
totalInput: 0,
|
||||
totalQualified: 0,
|
||||
firstPassRate: '0.00',
|
||||
firstPassInput: 0,
|
||||
firstPassQualified: 0,
|
||||
reworkRate: '0.00',
|
||||
reworkCount: 0,
|
||||
reworkRatio: '0.00',
|
||||
scrapRate: '0.00',
|
||||
scrapCount: 0,
|
||||
scrapRatio: '0.00',
|
||||
|
||||
// 图表实例
|
||||
trendChart: null,
|
||||
defectChart: null,
|
||||
|
||||
// 加载状态
|
||||
loading: false
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.initCharts()
|
||||
this.getOverviewData()
|
||||
|
||||
// 监听窗口大小变化
|
||||
this.__resizeHandler = debounce(() => {
|
||||
if (this.trendChart) {
|
||||
this.trendChart.resize()
|
||||
}
|
||||
if (this.defectChart) {
|
||||
this.defectChart.resize()
|
||||
}
|
||||
}, 100)
|
||||
window.addEventListener('resize', this.__resizeHandler)
|
||||
},
|
||||
beforeDestroy() {
|
||||
// 销毁图表实例
|
||||
if (this.trendChart) {
|
||||
this.trendChart.dispose()
|
||||
this.trendChart = null
|
||||
}
|
||||
if (this.defectChart) {
|
||||
this.defectChart.dispose()
|
||||
this.defectChart = null
|
||||
}
|
||||
window.removeEventListener('resize', this.__resizeHandler)
|
||||
},
|
||||
methods: {
|
||||
// 初始化图表
|
||||
initCharts() {
|
||||
// 初始化合格率趋势图
|
||||
this.trendChart = echarts.init(this.$refs.trendChart)
|
||||
// 初始化不合格项分布图
|
||||
this.defectChart = echarts.init(this.$refs.defectChart)
|
||||
},
|
||||
|
||||
// 获取概览数据
|
||||
async getOverviewData() {
|
||||
this.loading = true
|
||||
try {
|
||||
// 注释接口调用,直接使用测试数据
|
||||
// const params = {
|
||||
// startTime: this.timeRange.startTime,
|
||||
// endTime: this.timeRange.endTime,
|
||||
// ...this.queryParams
|
||||
// }
|
||||
|
||||
// // 调用API获取数据
|
||||
// const res = await GetProductAndPolishAndOneTimeFqcBoardData(params)
|
||||
|
||||
// if (res.code === 200 && res.data) {
|
||||
// this.updateStatData(res.data)
|
||||
// this.updateTrendChart(res.data.trendData)
|
||||
// this.updateDefectChart(res.data.defectData)
|
||||
// } else {
|
||||
// this.$message.error('获取数据失败')
|
||||
// this.useMockData()
|
||||
// }
|
||||
|
||||
// 直接使用模拟数据
|
||||
this.useMockData()
|
||||
} catch (error) {
|
||||
this.$message.error('获取数据异常')
|
||||
console.error('获取合格率概览数据异常:', error)
|
||||
// 使用模拟数据
|
||||
this.useMockData()
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
|
||||
// 使用模拟数据
|
||||
useMockData() {
|
||||
const mockData = {
|
||||
overallRate: 96.8,
|
||||
totalInput: 12500,
|
||||
totalQualified: 12100,
|
||||
firstPassRate: 92.5,
|
||||
firstPassInput: 10800,
|
||||
firstPassQualified: 9990,
|
||||
reworkRate: 3.2,
|
||||
reworkCount: 400,
|
||||
reworkRatio: 3.2,
|
||||
scrapRate: 0.5,
|
||||
scrapCount: 65,
|
||||
scrapRatio: 0.52
|
||||
}
|
||||
|
||||
// 生成趋势图模拟数据
|
||||
const trendData = {
|
||||
categories: [],
|
||||
series: []
|
||||
}
|
||||
|
||||
// 生成过去7天的数据
|
||||
const categories = []
|
||||
const overallRates = []
|
||||
const firstPassRates = []
|
||||
|
||||
for (let i = 6; i >= 0; i--) {
|
||||
const date = new Date()
|
||||
date.setDate(date.getDate() - i)
|
||||
categories.push(`${date.getMonth() + 1}/${date.getDate()}`)
|
||||
// 生成随机数据,围绕模拟数据上下波动
|
||||
overallRates.push((96.8 + (Math.random() * 2 - 1)).toFixed(2))
|
||||
firstPassRates.push((92.5 + (Math.random() * 2 - 1)).toFixed(2))
|
||||
}
|
||||
|
||||
trendData.categories = categories
|
||||
trendData.series = [
|
||||
{
|
||||
name: '总体合格率',
|
||||
data: overallRates
|
||||
},
|
||||
{
|
||||
name: '一次合格率',
|
||||
data: firstPassRates
|
||||
}
|
||||
]
|
||||
|
||||
// 生成不合格项分布模拟数据
|
||||
const defectData = [
|
||||
{ name: '外观缺陷', value: 150 },
|
||||
{ name: '尺寸不符', value: 85 },
|
||||
{ name: '装配问题', value: 65 },
|
||||
{ name: '性能测试失败', value: 40 },
|
||||
{ name: '其他原因', value: 30 }
|
||||
]
|
||||
|
||||
this.updateStatData(mockData)
|
||||
this.updateTrendChart(trendData)
|
||||
this.updateDefectChart(defectData)
|
||||
},
|
||||
|
||||
// 更新统计数据
|
||||
updateStatData(data) {
|
||||
this.overallRate = data.overallRate ? data.overallRate.toFixed(2) : '0.00'
|
||||
this.totalInput = data.totalInput || 0
|
||||
this.totalQualified = data.totalQualified || 0
|
||||
this.firstPassRate = data.firstPassRate ? data.firstPassRate.toFixed(2) : '0.00'
|
||||
this.firstPassInput = data.firstPassInput || 0
|
||||
this.firstPassQualified = data.firstPassQualified || 0
|
||||
this.reworkRate = data.reworkRate ? data.reworkRate.toFixed(2) : '0.00'
|
||||
this.reworkCount = data.reworkCount || 0
|
||||
this.reworkRatio = data.reworkRatio ? data.reworkRatio.toFixed(2) : '0.00'
|
||||
this.scrapRate = data.scrapRate ? data.scrapRate.toFixed(2) : '0.00'
|
||||
this.scrapCount = data.scrapCount || 0
|
||||
this.scrapRatio = data.scrapRatio ? data.scrapRatio.toFixed(2) : '0.00'
|
||||
},
|
||||
|
||||
// 更新趋势图
|
||||
updateTrendChart(data) {
|
||||
if (!this.trendChart) return
|
||||
|
||||
const option = {
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
formatter: function(params) {
|
||||
let result = params[0].name + '<br/>'
|
||||
params.forEach(param => {
|
||||
result += `${param.marker}${param.seriesName}: ${param.value}%<br/>`
|
||||
})
|
||||
return result
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
data: data.series ? data.series.map(item => item.name) : ['总体合格率', '一次合格率'],
|
||||
top: 0
|
||||
},
|
||||
grid: {
|
||||
left: '3%',
|
||||
right: '4%',
|
||||
bottom: '3%',
|
||||
top: '15%',
|
||||
containLabel: true
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
boundaryGap: false,
|
||||
data: data.categories || []
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
min: 80,
|
||||
max: 100,
|
||||
axisLabel: {
|
||||
formatter: '{value}%'
|
||||
}
|
||||
},
|
||||
series: data.series ? data.series.map(item => ({
|
||||
name: item.name,
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
data: item.data,
|
||||
lineStyle: {
|
||||
width: 2
|
||||
},
|
||||
itemStyle: {
|
||||
borderRadius: 4
|
||||
}
|
||||
})) : [
|
||||
{
|
||||
name: '总体合格率',
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
data: [],
|
||||
lineStyle: {
|
||||
width: 2
|
||||
},
|
||||
itemStyle: {
|
||||
borderRadius: 4
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
this.trendChart.setOption(option)
|
||||
},
|
||||
|
||||
// 更新不合格项分布图
|
||||
updateDefectChart(data) {
|
||||
if (!this.defectChart) return
|
||||
|
||||
const option = {
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
formatter: '{a} <br/>{b}: {c} ({d}%)'
|
||||
},
|
||||
legend: {
|
||||
orient: 'vertical',
|
||||
left: 10,
|
||||
data: data.map(item => item.name)
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '不合格项',
|
||||
type: 'pie',
|
||||
radius: ['40%', '70%'],
|
||||
avoidLabelOverlap: false,
|
||||
itemStyle: {
|
||||
borderRadius: 10,
|
||||
borderColor: '#fff',
|
||||
borderWidth: 2
|
||||
},
|
||||
label: {
|
||||
show: false,
|
||||
position: 'center'
|
||||
},
|
||||
emphasis: {
|
||||
label: {
|
||||
show: true,
|
||||
fontSize: '18',
|
||||
fontWeight: 'bold'
|
||||
}
|
||||
},
|
||||
labelLine: {
|
||||
show: false
|
||||
},
|
||||
data: data
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
this.defectChart.setOption(option)
|
||||
},
|
||||
|
||||
// 刷新数据
|
||||
refresh() {
|
||||
this.getOverviewData()
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
// 监听查询参数变化
|
||||
queryParams: {
|
||||
handler() {
|
||||
this.getOverviewData()
|
||||
},
|
||||
deep: true
|
||||
},
|
||||
|
||||
// 监听时间范围变化
|
||||
timeRange: {
|
||||
handler() {
|
||||
this.getOverviewData()
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.qualification-rate-overview {
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
height: 150px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.stat-content {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.stat-title {
|
||||
font-size: 14px;
|
||||
color: #606266;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
color: #409eff;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.stat-desc {
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.stat-text {
|
||||
display: block;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
font-weight: bold;
|
||||
font-size: 14px;
|
||||
padding: 10px 15px;
|
||||
}
|
||||
|
||||
.chart {
|
||||
width: 100%;
|
||||
height: 300px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,290 @@
|
||||
<template>
|
||||
<div class="qualification-rate-table">
|
||||
<!-- 工具栏 -->
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5">
|
||||
<el-button type="primary" icon="el-icon-download" @click="handleExport">
|
||||
导出
|
||||
</el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 表格 -->
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="dataList"
|
||||
border
|
||||
fit
|
||||
highlight-current-row
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-table-column type="index" width="50" label="序号" />
|
||||
<el-table-column prop="partNumber" label="物料号" width="120" />
|
||||
<el-table-column prop="description" label="产品描述" min-width="150" />
|
||||
<el-table-column prop="specification" label="规格" width="100" />
|
||||
<el-table-column prop="color" label="颜色" width="80" />
|
||||
<el-table-column prop="siteNo" label="站点" width="80" />
|
||||
<el-table-column prop="team" label="班组" width="80" />
|
||||
<el-table-column prop="requireNumber" label="投入数" width="80" align="right" />
|
||||
<el-table-column prop="qualifiedNumber" label="合格数" width="80" align="right" />
|
||||
<el-table-column prop="qualifiedRate" label="合格率" width="90" align="right">
|
||||
<template slot-scope="scope">
|
||||
<span :class="getRateClass(scope.row.qualifiedRate)">{{ scope.row.qualifiedRate }}%</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="polishNumber" label="抛光数" width="80" align="right" />
|
||||
<el-table-column prop="damoNumber" label="打磨数" width="80" align="right" />
|
||||
<el-table-column prop="baofeiNumber" label="报废数" width="80" align="right" />
|
||||
<el-table-column prop="isOnetime" label="一次合格" width="90" align="center">
|
||||
<template slot-scope="scope">
|
||||
<el-tag :type="scope.row.isOnetime === 1 ? 'success' : 'info'">
|
||||
{{ scope.row.isOnetime === 1 ? '是' : '否' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="isBack" label="返工件" width="90" align="center">
|
||||
<template slot-scope="scope">
|
||||
<el-tag :type="scope.row.isBack === 1 ? 'warning' : 'info'">
|
||||
{{ scope.row.isBack === 1 ? '是' : '否' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="startTime" label="开始时间" width="150" />
|
||||
<el-table-column prop="endTime" label="结束时间" width="150" />
|
||||
<el-table-column prop="workOrder" label="工单号" width="120" />
|
||||
</el-table>
|
||||
|
||||
<!-- 分页 -->
|
||||
<div class="pagination-container">
|
||||
<el-pagination
|
||||
background
|
||||
:current-page="queryParams.pageNum"
|
||||
:page-size="queryParams.pageSize"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
:total="total"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { GetWorkOrderFqcTableData } from '@/api/qualityManagement/commonFQC.js'
|
||||
|
||||
export default {
|
||||
name: 'QualificationRateTable',
|
||||
props: {
|
||||
queryParams: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
timeRange: {
|
||||
type: Object,
|
||||
default: () => ({
|
||||
startTime: '',
|
||||
endTime: ''
|
||||
})
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 数据列表
|
||||
dataList: [],
|
||||
// 总记录数
|
||||
total: 0,
|
||||
// 加载状态
|
||||
loading: false,
|
||||
// 查询参数
|
||||
tableQueryParams: {
|
||||
pageNum: 1,
|
||||
pageSize: 10
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.getTableData()
|
||||
},
|
||||
methods: {
|
||||
// 获取表格数据
|
||||
async getTableData() {
|
||||
this.loading = true
|
||||
try {
|
||||
// 注释接口调用,直接使用测试数据
|
||||
// const params = {
|
||||
// startTime: this.timeRange.startTime,
|
||||
// endTime: this.timeRange.endTime,
|
||||
// ...this.queryParams,
|
||||
// ...this.tableQueryParams
|
||||
// }
|
||||
|
||||
// // 调用API获取数据
|
||||
// const res = await GetWorkOrderFqcTableData(params)
|
||||
|
||||
// if (res.code === 200 && res.data) {
|
||||
// this.dataList = res.data.result || []
|
||||
// this.total = res.data.totalNum || 0
|
||||
// } else {
|
||||
// this.$message.error('获取数据失败')
|
||||
// // 使用模拟数据
|
||||
// this.useMockData()
|
||||
// }
|
||||
|
||||
// 直接使用模拟数据
|
||||
this.useMockData()
|
||||
} catch (error) {
|
||||
this.$message.error('获取数据异常')
|
||||
console.error('获取合格率报表数据异常:', error)
|
||||
// 使用模拟数据
|
||||
this.useMockData()
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
|
||||
// 使用模拟数据
|
||||
useMockData() {
|
||||
const mockData = []
|
||||
const partNumbers = ['PRT-001', 'PRT-002', 'PRT-003', 'PRT-004', 'PRT-005']
|
||||
const descriptions = ['左前车门后视镜', '右前车门后视镜', '左后车门后视镜', '右后车门后视镜', '前挡风玻璃']
|
||||
const specifications = ['A款', 'B款', 'C款', 'D款', 'E款']
|
||||
const colors = ['黑色', '白色', '银色', '蓝色', '红色']
|
||||
const sites = ['FQC-1', 'FQC-2', 'FQC-3', 'FQC-4', 'FQC-5']
|
||||
const teams = ['一班', '二班', '三班']
|
||||
|
||||
// 生成20条模拟数据
|
||||
for (let i = 1; i <= 20; i++) {
|
||||
const index = i % 5
|
||||
const requireNumber = Math.floor(Math.random() * 500) + 100
|
||||
const qualifiedNumber = Math.floor(requireNumber * (0.8 + Math.random() * 0.2))
|
||||
const qualifiedRate = ((qualifiedNumber / requireNumber) * 100).toFixed(2)
|
||||
const polishNumber = Math.floor((requireNumber - qualifiedNumber) * 0.7)
|
||||
const damoNumber = Math.floor((requireNumber - qualifiedNumber - polishNumber) * 0.8)
|
||||
const baofeiNumber = requireNumber - qualifiedNumber - polishNumber - damoNumber
|
||||
|
||||
// 生成随机日期
|
||||
const date = new Date()
|
||||
date.setDate(date.getDate() - Math.floor(Math.random() * 7))
|
||||
const startTime = new Date(date)
|
||||
startTime.setHours(Math.floor(Math.random() * 8) + 8, Math.floor(Math.random() * 60))
|
||||
const endTime = new Date(startTime)
|
||||
endTime.setHours(startTime.getHours() + 4 + Math.floor(Math.random() * 4))
|
||||
|
||||
mockData.push({
|
||||
id: i,
|
||||
partNumber: partNumbers[index],
|
||||
description: descriptions[index],
|
||||
specification: specifications[index],
|
||||
color: colors[index],
|
||||
siteNo: sites[index],
|
||||
team: teams[i % 3],
|
||||
requireNumber,
|
||||
qualifiedNumber,
|
||||
qualifiedRate,
|
||||
polishNumber,
|
||||
damoNumber,
|
||||
baofeiNumber,
|
||||
isOnetime: Math.random() > 0.5 ? 1 : 0,
|
||||
isBack: Math.random() > 0.3 ? 1 : 0,
|
||||
isPolish: Math.random() > 0.5 ? 1 : 0,
|
||||
isOut: Math.random() > 0.5 ? 1 : 0,
|
||||
startTime: startTime.toISOString(),
|
||||
endTime: endTime.toISOString(),
|
||||
workOrder: `WO-${date.getFullYear()}${String(date.getMonth() + 1).padStart(2, '0')}${String(date.getDate()).padStart(2, '0')}-${String(i).padStart(3, '0')}`
|
||||
})
|
||||
}
|
||||
|
||||
this.dataList = mockData
|
||||
this.total = 100 // 模拟总记录数
|
||||
},
|
||||
|
||||
// 获取合格率样式类
|
||||
getRateClass(rate) {
|
||||
const rateValue = parseFloat(rate)
|
||||
if (rateValue >= 95) {
|
||||
return 'rate-high'
|
||||
} else if (rateValue >= 90) {
|
||||
return 'rate-medium'
|
||||
} else {
|
||||
return 'rate-low'
|
||||
}
|
||||
},
|
||||
|
||||
// 处理导出
|
||||
handleExport() {
|
||||
this.$message.success('导出功能待实现')
|
||||
// 实际项目中应调用导出API
|
||||
},
|
||||
|
||||
// 处理页码变化
|
||||
handleSizeChange(pageSize) {
|
||||
this.tableQueryParams.pageSize = pageSize
|
||||
this.getTableData()
|
||||
},
|
||||
|
||||
// 处理页数变化
|
||||
handleCurrentChange(pageNum) {
|
||||
this.tableQueryParams.pageNum = pageNum
|
||||
this.getTableData()
|
||||
},
|
||||
|
||||
// 刷新数据
|
||||
refresh() {
|
||||
this.tableQueryParams.pageNum = 1
|
||||
this.getTableData()
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
// 监听查询参数变化
|
||||
queryParams: {
|
||||
handler() {
|
||||
this.tableQueryParams.pageNum = 1
|
||||
this.getTableData()
|
||||
},
|
||||
deep: true
|
||||
},
|
||||
|
||||
// 监听时间范围变化
|
||||
timeRange: {
|
||||
handler() {
|
||||
this.tableQueryParams.pageNum = 1
|
||||
this.getTableData()
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.qualification-rate-table {
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
.mb8 {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.pagination-container {
|
||||
margin-top: 16px;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
/* 合格率样式 */
|
||||
.rate-high {
|
||||
color: #67c23a;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.rate-medium {
|
||||
color: #e6a23c;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.rate-low {
|
||||
color: #f56c6c;
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,191 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<!-- 查询条件区域 -->
|
||||
<el-form :model="queryParams" size="small" label-position="right" inline ref="queryForm" :label-width="'100px'" @submit.native.prevent>
|
||||
<el-form-item label="时间区间">
|
||||
<el-date-picker
|
||||
v-model="timeRange"
|
||||
type="daterange"
|
||||
:picker-options="pickerOptions"
|
||||
range-separator="至"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
format="yyyy-MM-dd HH:mm:ss"
|
||||
value-format="yyyy-MM-dd'T'HH:mm:ss.000Z"
|
||||
style="width: 350px;"
|
||||
></el-date-picker>
|
||||
</el-form-item>
|
||||
<el-form-item label="物料号">
|
||||
<el-input v-model="queryParams.partNumber" placeholder="请输入物料号" style="width: 150px;"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="产品描述">
|
||||
<el-input v-model="queryParams.description" placeholder="请输入产品描述" style="width: 200px;"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="el-icon-search" @click="handleQuery">搜索</el-button>
|
||||
<el-button icon="el-icon-refresh" @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<!-- 标签页切换 -->
|
||||
<el-tabs v-model="activeTab" type="card" @tab-click="handleTabClick">
|
||||
<el-tab-pane label="合格率概览" name="overview">
|
||||
<qualification-rate-overview
|
||||
:query-params="queryParams"
|
||||
:time-range="{
|
||||
startTime: this.timeRange[0] ? this.timeRange[0].toISOString() : '',
|
||||
endTime: this.timeRange[1] ? this.timeRange[1].toISOString() : ''
|
||||
}"
|
||||
@refresh="handleQuery"
|
||||
></qualification-rate-overview>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="合格率报表" name="table">
|
||||
<qualification-rate-table
|
||||
:query-params="queryParams"
|
||||
:time-range="{
|
||||
startTime: this.timeRange[0] ? this.timeRange[0].toISOString() : '',
|
||||
endTime: this.timeRange[1] ? this.timeRange[1].toISOString() : ''
|
||||
}"
|
||||
@refresh="handleQuery"
|
||||
></qualification-rate-table>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import QualificationRateOverview from './components/qualificationRateOverview.vue'
|
||||
import QualificationRateTable from './components/qualificationRateTable.vue'
|
||||
|
||||
export default {
|
||||
name: 'QualificationRateReport',
|
||||
components: {
|
||||
QualificationRateOverview,
|
||||
QualificationRateTable
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 当前激活的标签页
|
||||
activeTab: 'overview',
|
||||
// 时间范围
|
||||
timeRange: [],
|
||||
// 查询参数
|
||||
queryParams: {
|
||||
partNumber: '',
|
||||
description: ''
|
||||
},
|
||||
// 日期选择器配置
|
||||
pickerOptions: {
|
||||
shortcuts: [
|
||||
{
|
||||
text: '本日',
|
||||
onClick: (picker) => {
|
||||
const now = new Date();
|
||||
const start = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
||||
picker.$emit('pick', [start, now]);
|
||||
}
|
||||
},
|
||||
{
|
||||
text: '本周',
|
||||
onClick: (picker) => {
|
||||
const now = new Date();
|
||||
const day = now.getDay() || 7; // 转换周日为7
|
||||
const start = new Date(now.getFullYear(), now.getMonth(), now.getDate() - day + 1);
|
||||
picker.$emit('pick', [start, now]);
|
||||
}
|
||||
},
|
||||
{
|
||||
text: '本月(26日-25日)',
|
||||
onClick: (picker) => {
|
||||
const now = new Date();
|
||||
const currentMonth = now.getMonth();
|
||||
const currentYear = now.getFullYear();
|
||||
let start, end;
|
||||
|
||||
// 计算前26日
|
||||
if (now.getDate() >= 26) {
|
||||
start = new Date(currentYear, currentMonth, 26);
|
||||
} else {
|
||||
const lastMonth = currentMonth === 0 ? 11 : currentMonth - 1;
|
||||
const lastMonthYear = currentMonth === 0 ? currentYear - 1 : currentYear;
|
||||
start = new Date(lastMonthYear, lastMonth, 26);
|
||||
}
|
||||
|
||||
// 计算本月25日
|
||||
end = new Date(currentYear, currentMonth, 25);
|
||||
|
||||
picker.$emit('pick', [start, end]);
|
||||
}
|
||||
},
|
||||
{
|
||||
text: '上月(26日-25日)',
|
||||
onClick: (picker) => {
|
||||
const now = new Date();
|
||||
const currentMonth = now.getMonth();
|
||||
const currentYear = now.getFullYear();
|
||||
const lastMonth = currentMonth === 0 ? 11 : currentMonth - 1;
|
||||
const lastMonthYear = currentMonth === 0 ? currentYear - 1 : currentYear;
|
||||
const prevLastMonth = lastMonth === 0 ? 11 : lastMonth - 1;
|
||||
const prevLastMonthYear = lastMonth === 0 ? lastMonthYear - 1 : lastMonthYear;
|
||||
|
||||
const start = new Date(prevLastMonthYear, prevLastMonth, 26);
|
||||
const end = new Date(lastMonthYear, lastMonth, 25);
|
||||
|
||||
picker.$emit('pick', [start, end]);
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
// 初始化时间范围为今日
|
||||
const now = new Date();
|
||||
const start = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
||||
this.timeRange = [start, now];
|
||||
},
|
||||
methods: {
|
||||
// 处理查询
|
||||
handleQuery() {
|
||||
// 触发子组件的刷新
|
||||
this.$emit('refresh')
|
||||
},
|
||||
|
||||
// 重置查询
|
||||
resetQuery() {
|
||||
this.$refs.queryForm.resetFields()
|
||||
// 重置时间范围为今日
|
||||
const now = new Date();
|
||||
const start = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
||||
this.timeRange = [start, now];
|
||||
// 触发子组件的刷新
|
||||
this.$emit('refresh')
|
||||
},
|
||||
|
||||
// 处理标签页切换
|
||||
handleTabClick() {
|
||||
this.handleQuery()
|
||||
}
|
||||
|
||||
},
|
||||
watch: {
|
||||
// 监听时间范围变化
|
||||
timeRange: {
|
||||
handler(newVal) {
|
||||
// 确保时间范围有效
|
||||
if (newVal && newVal.length === 2) {
|
||||
// 格式化时间范围以便子组件使用
|
||||
this.$emit('refresh')
|
||||
}
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.app-container {
|
||||
padding: 20px;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user