检测计划统计功能
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -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>) => {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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)"
|
||||
>
|
||||
统计
|
||||
|
||||
Reference in New Issue
Block a user