检测计划统计功能

This commit is contained in:
caozehui
2026-05-28 08:44:15 +08:00
parent ce1738daf0
commit 1202f64bfc
6 changed files with 147 additions and 30 deletions

View File

@@ -72,10 +72,7 @@ export namespace Plan {
export interface PlanStatisticsItem {
itemId: string;
itemName: string;
totalCount: number;
qualifiedCount: number;
unqualifiedCount: number;
passRate: number;
}
export interface PlanStatistics {
@@ -83,9 +80,11 @@ export namespace Plan {
planName: string;
totalCheckCount: number;
checkedDeviceCount: number;
uncheckedDeviceCount: number;
firstQualifiedDeviceCount: number;
secondQualifiedDeviceCount: number;
thirdOrMoreQualifiedDeviceCount: number;
qualifiedDeviceCount: number;
unqualifiedDeviceCount: number;
unqualifiedItemCount: number;
firstPassRate: number;

View File

@@ -94,7 +94,7 @@ export const staticsAnalyse = (params: { id: string[] }) => {
return http.download('/adPlan/analyse', params)
}
export const getPlanStatistics = (params: { planId: string }) => {
export const getPlanStatistics = (params: { planId: string; manufacturer?: string; devType?: string }) => {
return http.post<Plan.PlanStatistics>(`/adPlan/statistics`, params)
}

View File

@@ -541,7 +541,6 @@ const columns = reactive<ColumnProps<Device.ResPqDev>[]>([
{ prop: 'operation', label: '操作', fixed: 'right', minWidth :200,isShow: operationShow }
])
let testType = 'test' // 检测类型:'test'-检测 'reTest'-复检
let qualifiedCount = 0 //合格数量
//比对单个报告生成
@@ -577,8 +576,6 @@ const handleSelectionChange = (selection: any[]) => {
} else {
testType = 'reTest'
}
qualifiedCount=selection.filter(item => item.checkResult == 1).length
let devices: CheckData.Device[] = selection.map((item: any) => {
return {
deviceId: item.id,
@@ -601,6 +598,19 @@ const handleSelectionChange = (selection: any[]) => {
}
}
const isUncheckedDevice = (device: Device.ResPqDev) => Number(device.checkState) === 0 || Number(device.checkResult) === 2
const hasCheckedSelectedDevice = () => channelsSelection.value.some(device => !isUncheckedDevice(device))
const hasUncheckedSelectedDevice = () => channelsSelection.value.some(device => isUncheckedDevice(device))
const hasCheckedUnqualifiedSelectedDevice = () =>
channelsSelection.value.some(device => !isUncheckedDevice(device) && Number(device.checkResult) === 0)
const shouldShowRecheckModeDialog = () => hasCheckedSelectedDevice()
const canUseUnqualifiedItemRecheck = () => hasCheckedUnqualifiedSelectedDevice() && !hasUncheckedSelectedDevice()
//查询
const handleSearch = () => {
proTable.value?.getTableList()
@@ -925,12 +935,12 @@ const handleTest = async (val: string) => {
dialogTitle.value = val
if (val === '手动检测') {
checkStore.setShowDetailType(2)
if (testType === 'reTest') {
if (shouldShowRecheckModeDialog()) {
ElMessageBox.confirm('请选择复检检测方式', '设备复检', {
distinguishCancelAndClose: true,
confirmButtonText: '不合格项复检',
cancelButtonText: '全部复检',
showConfirmButton:qualifiedCount<=0,
showConfirmButton: canUseUnqualifiedItemRecheck(),
type: 'warning'
})
.then(() => {
@@ -965,11 +975,12 @@ const handleTest = async (val: string) => {
checkStore.setCheckType(1)
checkStore.initSelectTestItems()
// 一键检测
if (testType === 'reTest' && modeStore.currentMode != '比对式') {
if (shouldShowRecheckModeDialog() && modeStore.currentMode != '比对式') {
ElMessageBox.confirm('请选择复检检测方式', '设备复检', {
distinguishCancelAndClose: true,
confirmButtonText: '不合格项复检',
cancelButtonText: '全部复检',
showConfirmButton: canUseUnqualifiedItemRecheck(),
type: 'warning'
})
.then(() => {

View File

@@ -214,7 +214,7 @@ const childDetail = (data: Plan.ResPlan) => {
}
const isCompletedPlanNode = (data: Partial<Plan.ResPlan>) => {
return Number(data.testState) === 2
return [1, 2].includes(Number(data.testState))
}
const openStatistics = (data: Partial<Plan.ResPlan>) => {

View File

@@ -11,8 +11,45 @@
<div v-loading="loading" class="plan-statistics">
<el-empty v-if="loadFailed" description="统计数据加载失败" />
<template v-else>
<el-form class="filter-bar" :model="filters" inline label-width="72px">
<el-form-item label="设备厂家">
<el-select
v-model="filters.manufacturer"
filterable
placeholder="全部"
class="filter-select"
@change="reloadStatistics"
>
<el-option label="全部" value="" />
<el-option
v-for="item in manufacturerOptions"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item label="设备类型">
<el-select
v-model="filters.devType"
filterable
placeholder="全部"
class="filter-select"
@change="reloadStatistics"
>
<el-option label="全部" value="" />
<el-option
v-for="item in devTypeOptions"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</el-form-item>
</el-form>
<div class="summary-grid">
<div v-for="item in summaryItems" :key="item.label" class="summary-item">
<div v-for="item in summaryItems" :key="item.label" class="summary-item" :class="item.type">
<span class="summary-label">{{ item.label }}</span>
<strong class="summary-value">{{ item.value }}</strong>
</div>
@@ -33,15 +70,6 @@
</div>
</div>
<el-table :data="statisticsData.itemDistributions" height="260" border>
<el-table-column prop="itemName" label="检测大项" min-width="220" show-overflow-tooltip />
<el-table-column prop="totalCount" label="执行次数" width="110" align="center" />
<el-table-column prop="qualifiedCount" label="合格次数" width="110" align="center" />
<el-table-column prop="unqualifiedCount" label="不合格次数" width="120" align="center" />
<el-table-column label="合格率" width="110" align="center">
<template #default="scope">{{ formatRate(scope.row.passRate) }}</template>
</el-table-column>
</el-table>
</template>
</template>
</div>
@@ -53,16 +81,26 @@ import { computed, nextTick, onMounted, onUnmounted, reactive, ref } from 'vue'
import * as echarts from 'echarts'
import { ElMessage } from 'element-plus'
import { getPlanStatistics } from '@/api/plan/plan'
import { getPqDev } from '@/api/device/device'
import type { Plan } from '@/api/plan/interface'
import type { Device } from '@/api/device/interface/device'
import { useDictStore } from '@/stores/modules/dict'
interface SelectOption {
id: string
name: string
}
const emptyStatistics = (): Plan.PlanStatistics => ({
planId: '',
planName: '',
totalCheckCount: 0,
checkedDeviceCount: 0,
uncheckedDeviceCount: 0,
firstQualifiedDeviceCount: 0,
secondQualifiedDeviceCount: 0,
thirdOrMoreQualifiedDeviceCount: 0,
qualifiedDeviceCount: 0,
unqualifiedDeviceCount: 0,
unqualifiedItemCount: 0,
firstPassRate: 0,
@@ -76,9 +114,17 @@ const dialogVisible = ref(false)
const loading = ref(false)
const loadFailed = ref(false)
const planName = ref('')
const currentPlanId = ref('')
const rateChartRef = ref<HTMLDivElement>()
const itemChartRef = ref<HTMLDivElement>()
const statisticsData = reactive<Plan.PlanStatistics>(emptyStatistics())
const filters = reactive({
manufacturer: '',
devType: ''
})
const dictStore = useDictStore()
const manufacturerOptions = computed<SelectOption[]>(() => dictStore.getDictData('Dev_Manufacturers') as SelectOption[])
const devTypeOptions = ref<SelectOption[]>([])
let rateChart: echarts.ECharts | null = null
let itemChart: echarts.ECharts | null = null
@@ -92,13 +138,10 @@ const isEmpty = computed(() => {
})
const summaryItems = computed(() => [
{ label: '总次数', value: statisticsData.totalCheckCount },
{ label: '未检设备', value: statisticsData.uncheckedDeviceCount },
{ label: '已检设备', value: statisticsData.checkedDeviceCount },
{ label: '一次合格率', value: formatRate(statisticsData.firstPassRate) },
{ label: '二次合格率', value: formatRate(statisticsData.secondPassRate) },
{ label: '3次+合格率', value: formatRate(statisticsData.thirdOrMorePassRate) },
{ label: '不合格率', value: formatRate(statisticsData.unqualifiedRate) },
{ label: '不合格次数', value: statisticsData.unqualifiedItemCount }
{ label: '合格设备', value: statisticsData.qualifiedDeviceCount, type: 'is-qualified' },
{ label: '不合格设备', value: statisticsData.unqualifiedDeviceCount, type: 'is-unqualified' }
])
const resetData = () => {
@@ -124,12 +167,29 @@ const open = async (row: Partial<Plan.ReqPlan>) => {
resetData()
disposeCharts()
loadFailed.value = false
currentPlanId.value = row.id
filters.manufacturer = ''
filters.devType = ''
planName.value = row.name || ''
dialogVisible.value = true
await loadFilterOptions()
await loadStatistics()
}
const reloadStatistics = async () => {
if (!dialogVisible.value || !currentPlanId.value) return
await loadStatistics()
}
const loadStatistics = async () => {
loading.value = true
try {
const { data } = await getPlanStatistics({ planId: row.id })
const { data } = await getPlanStatistics({
planId: currentPlanId.value,
manufacturer: filters.manufacturer || undefined,
devType: filters.devType || undefined
})
Object.assign(statisticsData, {
...emptyStatistics(),
...data,
@@ -145,6 +205,19 @@ const open = async (row: Partial<Plan.ReqPlan>) => {
}
}
const loadFilterOptions = async () => {
if (devTypeOptions.value.length) return
try {
const { data } = await getPqDev()
devTypeOptions.value = ((data || []) as Device.ResDev[]).map(item => ({
id: item.id,
name: item.name
}))
} catch (error) {
devTypeOptions.value = []
}
}
const renderCharts = () => {
if (!dialogVisible.value || loadFailed.value || isEmpty.value) return
renderRateChart()
@@ -247,6 +320,7 @@ const handleClosed = () => {
disposeCharts()
resetData()
loadFailed.value = false
currentPlanId.value = ''
}
onMounted(() => {
@@ -274,9 +348,20 @@ defineExpose({ open })
min-height: 0;
}
.filter-bar {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-bottom: 12px;
}
.filter-select {
width: 220px;
}
.summary-grid {
display: grid;
grid-template-columns: repeat(7, minmax(0, 1fr));
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 10px;
margin-bottom: 12px;
}
@@ -305,6 +390,24 @@ defineExpose({ open })
line-height: 28px;
}
.summary-item.is-qualified {
border-color: var(--el-color-success-light-5);
background: var(--el-color-success-light-9);
}
.summary-item.is-qualified .summary-value {
color: var(--el-color-success);
}
.summary-item.is-unqualified {
border-color: var(--el-color-danger-light-5);
background: var(--el-color-danger-light-9);
}
.summary-item.is-unqualified .summary-value {
color: var(--el-color-danger);
}
.chart-grid {
display: grid;
grid-template-columns: 1fr 1fr;
@@ -373,6 +476,10 @@ defineExpose({ open })
gap: 8px;
}
.filter-select {
width: 100%;
}
.summary-item {
padding: 10px;
}

View File

@@ -104,7 +104,7 @@
v-auth.plan="'analysis'"
link
icon="PieChart"
v-if="scope.row.testState == '2' && modeStore.currentMode != '比对式'"
v-if="(scope.row.testState == '1' || scope.row.testState == '2') && modeStore.currentMode != '比对式'"
@click="openStatistics(scope.row)"
>
统计