检测计划统计功能
This commit is contained in:
@@ -72,10 +72,7 @@ export namespace Plan {
|
|||||||
export interface PlanStatisticsItem {
|
export interface PlanStatisticsItem {
|
||||||
itemId: string;
|
itemId: string;
|
||||||
itemName: string;
|
itemName: string;
|
||||||
totalCount: number;
|
|
||||||
qualifiedCount: number;
|
|
||||||
unqualifiedCount: number;
|
unqualifiedCount: number;
|
||||||
passRate: number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PlanStatistics {
|
export interface PlanStatistics {
|
||||||
@@ -83,9 +80,11 @@ export namespace Plan {
|
|||||||
planName: string;
|
planName: string;
|
||||||
totalCheckCount: number;
|
totalCheckCount: number;
|
||||||
checkedDeviceCount: number;
|
checkedDeviceCount: number;
|
||||||
|
uncheckedDeviceCount: number;
|
||||||
firstQualifiedDeviceCount: number;
|
firstQualifiedDeviceCount: number;
|
||||||
secondQualifiedDeviceCount: number;
|
secondQualifiedDeviceCount: number;
|
||||||
thirdOrMoreQualifiedDeviceCount: number;
|
thirdOrMoreQualifiedDeviceCount: number;
|
||||||
|
qualifiedDeviceCount: number;
|
||||||
unqualifiedDeviceCount: number;
|
unqualifiedDeviceCount: number;
|
||||||
unqualifiedItemCount: number;
|
unqualifiedItemCount: number;
|
||||||
firstPassRate: number;
|
firstPassRate: number;
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ export const staticsAnalyse = (params: { id: string[] }) => {
|
|||||||
return http.download('/adPlan/analyse', params)
|
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)
|
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 }
|
{ prop: 'operation', label: '操作', fixed: 'right', minWidth :200,isShow: operationShow }
|
||||||
])
|
])
|
||||||
let testType = 'test' // 检测类型:'test'-检测 'reTest'-复检
|
let testType = 'test' // 检测类型:'test'-检测 'reTest'-复检
|
||||||
let qualifiedCount = 0 //合格数量
|
|
||||||
|
|
||||||
|
|
||||||
//比对单个报告生成
|
//比对单个报告生成
|
||||||
@@ -577,8 +576,6 @@ const handleSelectionChange = (selection: any[]) => {
|
|||||||
} else {
|
} else {
|
||||||
testType = 'reTest'
|
testType = 'reTest'
|
||||||
}
|
}
|
||||||
qualifiedCount=selection.filter(item => item.checkResult == 1).length
|
|
||||||
|
|
||||||
let devices: CheckData.Device[] = selection.map((item: any) => {
|
let devices: CheckData.Device[] = selection.map((item: any) => {
|
||||||
return {
|
return {
|
||||||
deviceId: item.id,
|
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 = () => {
|
const handleSearch = () => {
|
||||||
proTable.value?.getTableList()
|
proTable.value?.getTableList()
|
||||||
@@ -925,12 +935,12 @@ const handleTest = async (val: string) => {
|
|||||||
dialogTitle.value = val
|
dialogTitle.value = val
|
||||||
if (val === '手动检测') {
|
if (val === '手动检测') {
|
||||||
checkStore.setShowDetailType(2)
|
checkStore.setShowDetailType(2)
|
||||||
if (testType === 'reTest') {
|
if (shouldShowRecheckModeDialog()) {
|
||||||
ElMessageBox.confirm('请选择复检检测方式', '设备复检', {
|
ElMessageBox.confirm('请选择复检检测方式', '设备复检', {
|
||||||
distinguishCancelAndClose: true,
|
distinguishCancelAndClose: true,
|
||||||
confirmButtonText: '不合格项复检',
|
confirmButtonText: '不合格项复检',
|
||||||
cancelButtonText: '全部复检',
|
cancelButtonText: '全部复检',
|
||||||
showConfirmButton:qualifiedCount<=0,
|
showConfirmButton: canUseUnqualifiedItemRecheck(),
|
||||||
type: 'warning'
|
type: 'warning'
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
@@ -965,11 +975,12 @@ const handleTest = async (val: string) => {
|
|||||||
checkStore.setCheckType(1)
|
checkStore.setCheckType(1)
|
||||||
checkStore.initSelectTestItems()
|
checkStore.initSelectTestItems()
|
||||||
// 一键检测
|
// 一键检测
|
||||||
if (testType === 'reTest' && modeStore.currentMode != '比对式') {
|
if (shouldShowRecheckModeDialog() && modeStore.currentMode != '比对式') {
|
||||||
ElMessageBox.confirm('请选择复检检测方式', '设备复检', {
|
ElMessageBox.confirm('请选择复检检测方式', '设备复检', {
|
||||||
distinguishCancelAndClose: true,
|
distinguishCancelAndClose: true,
|
||||||
confirmButtonText: '不合格项复检',
|
confirmButtonText: '不合格项复检',
|
||||||
cancelButtonText: '全部复检',
|
cancelButtonText: '全部复检',
|
||||||
|
showConfirmButton: canUseUnqualifiedItemRecheck(),
|
||||||
type: 'warning'
|
type: 'warning'
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
|
|||||||
@@ -214,7 +214,7 @@ const childDetail = (data: Plan.ResPlan) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const isCompletedPlanNode = (data: Partial<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>) => {
|
const openStatistics = (data: Partial<Plan.ResPlan>) => {
|
||||||
|
|||||||
@@ -11,8 +11,45 @@
|
|||||||
<div v-loading="loading" class="plan-statistics">
|
<div v-loading="loading" class="plan-statistics">
|
||||||
<el-empty v-if="loadFailed" description="统计数据加载失败" />
|
<el-empty v-if="loadFailed" description="统计数据加载失败" />
|
||||||
<template v-else>
|
<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 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>
|
<span class="summary-label">{{ item.label }}</span>
|
||||||
<strong class="summary-value">{{ item.value }}</strong>
|
<strong class="summary-value">{{ item.value }}</strong>
|
||||||
</div>
|
</div>
|
||||||
@@ -33,15 +70,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</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>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
@@ -53,16 +81,26 @@ import { computed, nextTick, onMounted, onUnmounted, reactive, ref } from 'vue'
|
|||||||
import * as echarts from 'echarts'
|
import * as echarts from 'echarts'
|
||||||
import { ElMessage } from 'element-plus'
|
import { ElMessage } from 'element-plus'
|
||||||
import { getPlanStatistics } from '@/api/plan/plan'
|
import { getPlanStatistics } from '@/api/plan/plan'
|
||||||
|
import { getPqDev } from '@/api/device/device'
|
||||||
import type { Plan } from '@/api/plan/interface'
|
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 => ({
|
const emptyStatistics = (): Plan.PlanStatistics => ({
|
||||||
planId: '',
|
planId: '',
|
||||||
planName: '',
|
planName: '',
|
||||||
totalCheckCount: 0,
|
totalCheckCount: 0,
|
||||||
checkedDeviceCount: 0,
|
checkedDeviceCount: 0,
|
||||||
|
uncheckedDeviceCount: 0,
|
||||||
firstQualifiedDeviceCount: 0,
|
firstQualifiedDeviceCount: 0,
|
||||||
secondQualifiedDeviceCount: 0,
|
secondQualifiedDeviceCount: 0,
|
||||||
thirdOrMoreQualifiedDeviceCount: 0,
|
thirdOrMoreQualifiedDeviceCount: 0,
|
||||||
|
qualifiedDeviceCount: 0,
|
||||||
unqualifiedDeviceCount: 0,
|
unqualifiedDeviceCount: 0,
|
||||||
unqualifiedItemCount: 0,
|
unqualifiedItemCount: 0,
|
||||||
firstPassRate: 0,
|
firstPassRate: 0,
|
||||||
@@ -76,9 +114,17 @@ const dialogVisible = ref(false)
|
|||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const loadFailed = ref(false)
|
const loadFailed = ref(false)
|
||||||
const planName = ref('')
|
const planName = ref('')
|
||||||
|
const currentPlanId = ref('')
|
||||||
const rateChartRef = ref<HTMLDivElement>()
|
const rateChartRef = ref<HTMLDivElement>()
|
||||||
const itemChartRef = ref<HTMLDivElement>()
|
const itemChartRef = ref<HTMLDivElement>()
|
||||||
const statisticsData = reactive<Plan.PlanStatistics>(emptyStatistics())
|
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 rateChart: echarts.ECharts | null = null
|
||||||
let itemChart: echarts.ECharts | null = null
|
let itemChart: echarts.ECharts | null = null
|
||||||
|
|
||||||
@@ -92,13 +138,10 @@ const isEmpty = computed(() => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const summaryItems = computed(() => [
|
const summaryItems = computed(() => [
|
||||||
{ label: '总次数', value: statisticsData.totalCheckCount },
|
{ label: '未检设备', value: statisticsData.uncheckedDeviceCount },
|
||||||
{ label: '已检设备', value: statisticsData.checkedDeviceCount },
|
{ label: '已检设备', value: statisticsData.checkedDeviceCount },
|
||||||
{ label: '一次合格率', value: formatRate(statisticsData.firstPassRate) },
|
{ label: '合格设备', value: statisticsData.qualifiedDeviceCount, type: 'is-qualified' },
|
||||||
{ label: '二次合格率', value: formatRate(statisticsData.secondPassRate) },
|
{ label: '不合格设备', value: statisticsData.unqualifiedDeviceCount, type: 'is-unqualified' }
|
||||||
{ label: '3次+合格率', value: formatRate(statisticsData.thirdOrMorePassRate) },
|
|
||||||
{ label: '不合格率', value: formatRate(statisticsData.unqualifiedRate) },
|
|
||||||
{ label: '不合格次数', value: statisticsData.unqualifiedItemCount }
|
|
||||||
])
|
])
|
||||||
|
|
||||||
const resetData = () => {
|
const resetData = () => {
|
||||||
@@ -124,12 +167,29 @@ const open = async (row: Partial<Plan.ReqPlan>) => {
|
|||||||
resetData()
|
resetData()
|
||||||
disposeCharts()
|
disposeCharts()
|
||||||
loadFailed.value = false
|
loadFailed.value = false
|
||||||
|
currentPlanId.value = row.id
|
||||||
|
filters.manufacturer = ''
|
||||||
|
filters.devType = ''
|
||||||
planName.value = row.name || ''
|
planName.value = row.name || ''
|
||||||
dialogVisible.value = true
|
dialogVisible.value = true
|
||||||
|
await loadFilterOptions()
|
||||||
|
await loadStatistics()
|
||||||
|
}
|
||||||
|
|
||||||
|
const reloadStatistics = async () => {
|
||||||
|
if (!dialogVisible.value || !currentPlanId.value) return
|
||||||
|
await loadStatistics()
|
||||||
|
}
|
||||||
|
|
||||||
|
const loadStatistics = async () => {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
|
|
||||||
try {
|
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, {
|
Object.assign(statisticsData, {
|
||||||
...emptyStatistics(),
|
...emptyStatistics(),
|
||||||
...data,
|
...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 = () => {
|
const renderCharts = () => {
|
||||||
if (!dialogVisible.value || loadFailed.value || isEmpty.value) return
|
if (!dialogVisible.value || loadFailed.value || isEmpty.value) return
|
||||||
renderRateChart()
|
renderRateChart()
|
||||||
@@ -247,6 +320,7 @@ const handleClosed = () => {
|
|||||||
disposeCharts()
|
disposeCharts()
|
||||||
resetData()
|
resetData()
|
||||||
loadFailed.value = false
|
loadFailed.value = false
|
||||||
|
currentPlanId.value = ''
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
@@ -274,9 +348,20 @@ defineExpose({ open })
|
|||||||
min-height: 0;
|
min-height: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.filter-bar {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 10px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-select {
|
||||||
|
width: 220px;
|
||||||
|
}
|
||||||
|
|
||||||
.summary-grid {
|
.summary-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(7, minmax(0, 1fr));
|
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
}
|
}
|
||||||
@@ -305,6 +390,24 @@ defineExpose({ open })
|
|||||||
line-height: 28px;
|
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 {
|
.chart-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr 1fr;
|
grid-template-columns: 1fr 1fr;
|
||||||
@@ -373,6 +476,10 @@ defineExpose({ open })
|
|||||||
gap: 8px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.filter-select {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.summary-item {
|
.summary-item {
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -104,7 +104,7 @@
|
|||||||
v-auth.plan="'analysis'"
|
v-auth.plan="'analysis'"
|
||||||
link
|
link
|
||||||
icon="PieChart"
|
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)"
|
@click="openStatistics(scope.row)"
|
||||||
>
|
>
|
||||||
统计
|
统计
|
||||||
|
|||||||
Reference in New Issue
Block a user