检测计划统计功能
This commit is contained in:
@@ -69,5 +69,31 @@ export namespace Plan {
|
|||||||
maxTime: number;
|
maxTime: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface PlanStatisticsItem {
|
||||||
|
itemId: string;
|
||||||
|
itemName: string;
|
||||||
|
totalCount: number;
|
||||||
|
qualifiedCount: number;
|
||||||
|
unqualifiedCount: number;
|
||||||
|
passRate: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PlanStatistics {
|
||||||
|
planId: string;
|
||||||
|
planName: string;
|
||||||
|
totalCheckCount: number;
|
||||||
|
checkedDeviceCount: number;
|
||||||
|
firstQualifiedDeviceCount: number;
|
||||||
|
secondQualifiedDeviceCount: number;
|
||||||
|
thirdOrMoreQualifiedDeviceCount: number;
|
||||||
|
unqualifiedDeviceCount: number;
|
||||||
|
unqualifiedItemCount: number;
|
||||||
|
firstPassRate: number;
|
||||||
|
secondPassRate: number;
|
||||||
|
thirdOrMorePassRate: number;
|
||||||
|
unqualifiedRate: number;
|
||||||
|
itemDistributions: PlanStatisticsItem[];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -94,6 +94,10 @@ export const staticsAnalyse = (params: { id: string[] }) => {
|
|||||||
return http.download('/adPlan/analyse', params)
|
return http.download('/adPlan/analyse', params)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const getPlanStatistics = (params: { planId: string }) => {
|
||||||
|
return http.post<Plan.PlanStatistics>(`/adPlan/statistics`, params)
|
||||||
|
}
|
||||||
|
|
||||||
//根据计划id分页查询被检设
|
//根据计划id分页查询被检设
|
||||||
export const getDevListByPlanId = (params: any) => {
|
export const getDevListByPlanId = (params: any) => {
|
||||||
return http.post(`/adPlan/listDevByPlanId`, params)
|
return http.post(`/adPlan/listDevByPlanId`, params)
|
||||||
@@ -159,4 +163,4 @@ export const importAndMergePlanCheckData = (params: Plan.ResPlan) => {
|
|||||||
return http.upload(`/adPlan/importAndMergePlanCheckData`, params, {
|
return http.upload(`/adPlan/importAndMergePlanCheckData`, params, {
|
||||||
timeout: 60000 * 20
|
timeout: 60000 * 20
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,7 @@
|
|||||||
@node-click="handleNodeClick"
|
@node-click="handleNodeClick"
|
||||||
>
|
>
|
||||||
<template #default="{ node, data }">
|
<template #default="{ node, data }">
|
||||||
<span class="custom-tree-node" style="display: flex; align-items: center;">
|
<span class="custom-tree-node">
|
||||||
<!-- 父节点图标 -->
|
<!-- 父节点图标 -->
|
||||||
<Platform
|
<Platform
|
||||||
v-if="!data.pid"
|
v-if="!data.pid"
|
||||||
@@ -39,50 +39,52 @@
|
|||||||
}"
|
}"
|
||||||
/>
|
/>
|
||||||
<!-- 节点名称 -->
|
<!-- 节点名称 -->
|
||||||
<span>{{ node.label }}</span>
|
<span class="node-label">{{ node.label }}</span>
|
||||||
<!-- 子节点右侧图标 + tooltip -->
|
<span class="node-actions">
|
||||||
<el-tooltip
|
<PieChart
|
||||||
v-if="
|
v-if="isCompletedPlanNode(node.data)"
|
||||||
node.label != '未检' &&
|
class="node-action-icon"
|
||||||
node.label != '检测中' &&
|
@click.stop="openStatistics(node.data)"
|
||||||
node.label != '检测完成' &&
|
style="margin-right: 8px"
|
||||||
hasChildrenInPlanTable(node.data)
|
|
||||||
"
|
|
||||||
placement="top"
|
|
||||||
:manual="true"
|
|
||||||
content="子计划信息"
|
|
||||||
>
|
|
||||||
<List
|
|
||||||
@click.stop="childDetail(node.data)"
|
|
||||||
style="
|
|
||||||
width: 16px;
|
|
||||||
height: 16px;
|
|
||||||
margin-left: 8px;
|
|
||||||
cursor: pointer;
|
|
||||||
color: var(--el-color-primary);
|
|
||||||
"
|
|
||||||
/>
|
/>
|
||||||
</el-tooltip>
|
<!-- 子节点右侧图标 + tooltip -->
|
||||||
|
<el-tooltip
|
||||||
|
v-if="
|
||||||
|
node.label != '未检' &&
|
||||||
|
node.label != '检测中' &&
|
||||||
|
node.label != '检测完成' &&
|
||||||
|
hasChildrenInPlanTable(node.data)
|
||||||
|
"
|
||||||
|
placement="top"
|
||||||
|
:manual="true"
|
||||||
|
content="子计划信息"
|
||||||
|
>
|
||||||
|
<List class="node-action-icon" @click.stop="childDetail(node.data)" />
|
||||||
|
</el-tooltip>
|
||||||
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
</el-tree>
|
</el-tree>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<SourceOpen ref="openSourceView" :width="width" :height="height + 175"></SourceOpen>
|
<SourceOpen ref="openSourceView" :width="width" :height="height + 175"></SourceOpen>
|
||||||
|
<PlanStatisticsPopup ref="planStatisticsPopupRef" />
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { type Plan } from '@/api/plan/interface'
|
import { type Plan } from '@/api/plan/interface'
|
||||||
import { List, Menu, Platform } from '@element-plus/icons-vue'
|
import { List, Menu, PieChart, Platform } from '@element-plus/icons-vue'
|
||||||
import { nextTick, onMounted, ref, watch } from 'vue'
|
import { nextTick, onMounted, ref, watch } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { useCheckStore } from '@/stores/modules/check'
|
import { useCheckStore } from '@/stores/modules/check'
|
||||||
import { ElTooltip } from 'element-plus'
|
import { ElTooltip } from 'element-plus'
|
||||||
import SourceOpen from '@/views/plan/planList/components/childrenPlan.vue'
|
import SourceOpen from '@/views/plan/planList/components/childrenPlan.vue'
|
||||||
|
import PlanStatisticsPopup from '@/views/plan/planList/components/planStatisticsPopup.vue'
|
||||||
import { getPlanList } from '@/api/plan/plan.ts'
|
import { getPlanList } from '@/api/plan/plan.ts'
|
||||||
import { useModeStore } from '@/stores/modules/mode' // 引入模式 store
|
import { useModeStore } from '@/stores/modules/mode' // 引入模式 store
|
||||||
import { useDictStore } from '@/stores/modules/dict'
|
import { useDictStore } from '@/stores/modules/dict'
|
||||||
|
|
||||||
const openSourceView = ref()
|
const openSourceView = ref()
|
||||||
|
const planStatisticsPopupRef = ref<InstanceType<typeof PlanStatisticsPopup> | null>(null)
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const checkStore = useCheckStore()
|
const checkStore = useCheckStore()
|
||||||
const filterText = ref('')
|
const filterText = ref('')
|
||||||
@@ -211,6 +213,14 @@ const childDetail = (data: Plan.ResPlan) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isCompletedPlanNode = (data: Partial<Plan.ResPlan>) => {
|
||||||
|
return Number(data.testState) === 2
|
||||||
|
}
|
||||||
|
|
||||||
|
const openStatistics = (data: Partial<Plan.ResPlan>) => {
|
||||||
|
planStatisticsPopupRef.value?.open(data)
|
||||||
|
}
|
||||||
|
|
||||||
function buildTree(flatList: any[]): any[] {
|
function buildTree(flatList: any[]): any[] {
|
||||||
const map = new Map()
|
const map = new Map()
|
||||||
const tree: any[] = []
|
const tree: any[] = []
|
||||||
@@ -293,6 +303,40 @@ defineExpose({ getTreeData, clickTableToTree })
|
|||||||
margin-top: 12px;
|
margin-top: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:deep(.el-tree-node__content) {
|
||||||
|
padding-right: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-tree-node {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.node-label {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.node-actions {
|
||||||
|
flex: none;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
margin-left: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.node-action-icon {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
color: var(--el-color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
//.filter-tree span {
|
//.filter-tree span {
|
||||||
// font-size: 16px;
|
// font-size: 16px;
|
||||||
// display:block;
|
// display:block;
|
||||||
|
|||||||
@@ -0,0 +1,385 @@
|
|||||||
|
<template>
|
||||||
|
<el-dialog
|
||||||
|
v-model="dialogVisible"
|
||||||
|
:title="`检测计划统计 - ${planName || '/'}`"
|
||||||
|
width="min(1280px, 92vw)"
|
||||||
|
class="plan-statistics-dialog"
|
||||||
|
destroy-on-close
|
||||||
|
draggable
|
||||||
|
@closed="handleClosed"
|
||||||
|
>
|
||||||
|
<div v-loading="loading" class="plan-statistics">
|
||||||
|
<el-empty v-if="loadFailed" description="统计数据加载失败" />
|
||||||
|
<template v-else>
|
||||||
|
<div class="summary-grid">
|
||||||
|
<div v-for="item in summaryItems" :key="item.label" class="summary-item">
|
||||||
|
<span class="summary-label">{{ item.label }}</span>
|
||||||
|
<strong class="summary-value">{{ item.value }}</strong>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="isEmpty" class="empty-area">
|
||||||
|
<el-empty description="暂无统计数据" />
|
||||||
|
</div>
|
||||||
|
<template v-else>
|
||||||
|
<div class="chart-grid">
|
||||||
|
<div class="chart-panel">
|
||||||
|
<div class="panel-title">合格率</div>
|
||||||
|
<div ref="rateChartRef" class="chart"></div>
|
||||||
|
</div>
|
||||||
|
<div class="chart-panel">
|
||||||
|
<div class="panel-title">检测大项不合格分布</div>
|
||||||
|
<div ref="itemChartRef" class="chart"></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>
|
||||||
|
</div>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
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 type { Plan } from '@/api/plan/interface'
|
||||||
|
|
||||||
|
const emptyStatistics = (): Plan.PlanStatistics => ({
|
||||||
|
planId: '',
|
||||||
|
planName: '',
|
||||||
|
totalCheckCount: 0,
|
||||||
|
checkedDeviceCount: 0,
|
||||||
|
firstQualifiedDeviceCount: 0,
|
||||||
|
secondQualifiedDeviceCount: 0,
|
||||||
|
thirdOrMoreQualifiedDeviceCount: 0,
|
||||||
|
unqualifiedDeviceCount: 0,
|
||||||
|
unqualifiedItemCount: 0,
|
||||||
|
firstPassRate: 0,
|
||||||
|
secondPassRate: 0,
|
||||||
|
thirdOrMorePassRate: 0,
|
||||||
|
unqualifiedRate: 0,
|
||||||
|
itemDistributions: []
|
||||||
|
})
|
||||||
|
|
||||||
|
const dialogVisible = ref(false)
|
||||||
|
const loading = ref(false)
|
||||||
|
const loadFailed = ref(false)
|
||||||
|
const planName = ref('')
|
||||||
|
const rateChartRef = ref<HTMLDivElement>()
|
||||||
|
const itemChartRef = ref<HTMLDivElement>()
|
||||||
|
const statisticsData = reactive<Plan.PlanStatistics>(emptyStatistics())
|
||||||
|
let rateChart: echarts.ECharts | null = null
|
||||||
|
let itemChart: echarts.ECharts | null = null
|
||||||
|
|
||||||
|
const isEmpty = computed(() => {
|
||||||
|
return (
|
||||||
|
!loading.value &&
|
||||||
|
statisticsData.totalCheckCount === 0 &&
|
||||||
|
statisticsData.checkedDeviceCount === 0 &&
|
||||||
|
statisticsData.itemDistributions.length === 0
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
const summaryItems = computed(() => [
|
||||||
|
{ label: '总次数', value: statisticsData.totalCheckCount },
|
||||||
|
{ 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 }
|
||||||
|
])
|
||||||
|
|
||||||
|
const resetData = () => {
|
||||||
|
Object.assign(statisticsData, emptyStatistics())
|
||||||
|
}
|
||||||
|
|
||||||
|
const formatRate = (value: number | string | null | undefined) => {
|
||||||
|
const numberValue = Number(value)
|
||||||
|
if (!Number.isFinite(numberValue)) return '0%'
|
||||||
|
return `${numberValue.toFixed(2)}%`
|
||||||
|
}
|
||||||
|
|
||||||
|
const normalizeRate = (value: number | string | null | undefined) => {
|
||||||
|
const numberValue = Number(value)
|
||||||
|
return Number.isFinite(numberValue) ? numberValue : 0
|
||||||
|
}
|
||||||
|
|
||||||
|
const open = async (row: Partial<Plan.ReqPlan>) => {
|
||||||
|
if (!row.id) {
|
||||||
|
ElMessage.error('计划信息缺失,无法统计')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resetData()
|
||||||
|
disposeCharts()
|
||||||
|
loadFailed.value = false
|
||||||
|
planName.value = row.name || ''
|
||||||
|
dialogVisible.value = true
|
||||||
|
loading.value = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { data } = await getPlanStatistics({ planId: row.id })
|
||||||
|
Object.assign(statisticsData, {
|
||||||
|
...emptyStatistics(),
|
||||||
|
...data,
|
||||||
|
itemDistributions: data?.itemDistributions || []
|
||||||
|
})
|
||||||
|
await nextTick()
|
||||||
|
renderCharts()
|
||||||
|
} catch (error) {
|
||||||
|
loadFailed.value = true
|
||||||
|
ElMessage.error('统计数据加载失败')
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const renderCharts = () => {
|
||||||
|
if (!dialogVisible.value || loadFailed.value || isEmpty.value) return
|
||||||
|
renderRateChart()
|
||||||
|
renderItemChart()
|
||||||
|
resizeCharts()
|
||||||
|
}
|
||||||
|
|
||||||
|
const renderRateChart = () => {
|
||||||
|
if (!rateChartRef.value) return
|
||||||
|
rateChart?.dispose()
|
||||||
|
rateChart = echarts.init(rateChartRef.value)
|
||||||
|
const rateData = [
|
||||||
|
{
|
||||||
|
name: '一次合格率',
|
||||||
|
value: normalizeRate(statisticsData.firstPassRate),
|
||||||
|
count: statisticsData.firstQualifiedDeviceCount
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '二次合格率',
|
||||||
|
value: normalizeRate(statisticsData.secondPassRate),
|
||||||
|
count: statisticsData.secondQualifiedDeviceCount
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '三次及以上合格率',
|
||||||
|
value: normalizeRate(statisticsData.thirdOrMorePassRate),
|
||||||
|
count: statisticsData.thirdOrMoreQualifiedDeviceCount
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '不合格率',
|
||||||
|
value: normalizeRate(statisticsData.unqualifiedRate),
|
||||||
|
count: statisticsData.unqualifiedDeviceCount
|
||||||
|
}
|
||||||
|
]
|
||||||
|
rateChart.setOption({
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'item',
|
||||||
|
formatter: (params: any) => {
|
||||||
|
return `${params.name}<br/>${formatRate(params.value)}<br/>设备数:${params.data?.count || 0}`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
legend: { bottom: 0, left: 'center' },
|
||||||
|
color: ['#67c23a', '#409eff', '#e6a23c', '#f56c6c'],
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: '合格率',
|
||||||
|
type: 'pie',
|
||||||
|
radius: ['42%', '68%'],
|
||||||
|
center: ['50%', '43%'],
|
||||||
|
avoidLabelOverlap: true,
|
||||||
|
data: rateData,
|
||||||
|
label: {
|
||||||
|
formatter: ({ name, value }: any) => `${name}\n${formatRate(value)}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const renderItemChart = () => {
|
||||||
|
if (!itemChartRef.value) return
|
||||||
|
itemChart?.dispose()
|
||||||
|
itemChart = echarts.init(itemChartRef.value)
|
||||||
|
const topItems = [...statisticsData.itemDistributions]
|
||||||
|
.sort((a, b) => (b.unqualifiedCount || 0) - (a.unqualifiedCount || 0))
|
||||||
|
.slice(0, 8)
|
||||||
|
itemChart.setOption({
|
||||||
|
tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } },
|
||||||
|
grid: { left: 48, right: 20, top: 36, bottom: 48 },
|
||||||
|
xAxis: {
|
||||||
|
type: 'category',
|
||||||
|
data: topItems.map(item => item.itemName || '/'),
|
||||||
|
axisLabel: { interval: 0, rotate: 28 }
|
||||||
|
},
|
||||||
|
yAxis: { type: 'value', minInterval: 1 },
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: '不合格次数',
|
||||||
|
type: 'bar',
|
||||||
|
barWidth: 30,
|
||||||
|
data: topItems.map(item => item.unqualifiedCount || 0),
|
||||||
|
itemStyle: { color: '#f56c6c' }
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const disposeCharts = () => {
|
||||||
|
rateChart?.dispose()
|
||||||
|
itemChart?.dispose()
|
||||||
|
rateChart = null
|
||||||
|
itemChart = null
|
||||||
|
}
|
||||||
|
|
||||||
|
const resizeCharts = () => {
|
||||||
|
rateChart?.resize()
|
||||||
|
itemChart?.resize()
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleClosed = () => {
|
||||||
|
disposeCharts()
|
||||||
|
resetData()
|
||||||
|
loadFailed.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
window.addEventListener('resize', resizeCharts)
|
||||||
|
})
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
window.removeEventListener('resize', resizeCharts)
|
||||||
|
disposeCharts()
|
||||||
|
})
|
||||||
|
|
||||||
|
defineExpose({ open })
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
:deep(.plan-statistics-dialog) {
|
||||||
|
max-width: 92vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.plan-statistics-dialog .el-dialog__body) {
|
||||||
|
padding: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plan-statistics {
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(7, minmax(0, 1fr));
|
||||||
|
gap: 10px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary-item {
|
||||||
|
min-height: 64px;
|
||||||
|
padding: 10px 12px;
|
||||||
|
border: 1px solid var(--el-border-color-light);
|
||||||
|
border-radius: 6px;
|
||||||
|
background: var(--el-fill-color-lighter);
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary-label {
|
||||||
|
display: block;
|
||||||
|
color: var(--el-text-color-secondary);
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary-value {
|
||||||
|
display: block;
|
||||||
|
margin-top: 6px;
|
||||||
|
color: var(--el-text-color-primary);
|
||||||
|
font-size: 20px;
|
||||||
|
line-height: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 12px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-panel {
|
||||||
|
border: 1px solid var(--el-border-color-light);
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-title {
|
||||||
|
color: var(--el-text-color-primary);
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart {
|
||||||
|
width: 100%;
|
||||||
|
height: 250px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-area {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-height: 320px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 1200px) {
|
||||||
|
.summary-grid {
|
||||||
|
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 900px) {
|
||||||
|
.summary-grid {
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart {
|
||||||
|
height: 260px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 640px) {
|
||||||
|
:deep(.plan-statistics-dialog) {
|
||||||
|
width: 96vw;
|
||||||
|
max-width: 96vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.plan-statistics-dialog .el-dialog__body) {
|
||||||
|
max-height: calc(92vh - 110px);
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary-grid {
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary-item {
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary-value {
|
||||||
|
font-size: 18px;
|
||||||
|
line-height: 24px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -99,6 +99,16 @@
|
|||||||
被检设备
|
被检设备
|
||||||
</el-button>
|
</el-button>
|
||||||
<!-- <el-button type='primary' link :icon='List' @click='showDeviceOpen(scope.row)'>设备绑定</el-button> -->
|
<!-- <el-button type='primary' link :icon='List' @click='showDeviceOpen(scope.row)'>设备绑定</el-button> -->
|
||||||
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
v-auth.plan="'analysis'"
|
||||||
|
link
|
||||||
|
icon="PieChart"
|
||||||
|
v-if="scope.row.testState == '2' && modeStore.currentMode != '比对式'"
|
||||||
|
@click="openStatistics(scope.row)"
|
||||||
|
>
|
||||||
|
统计
|
||||||
|
</el-button>
|
||||||
<el-button
|
<el-button
|
||||||
type="primary"
|
type="primary"
|
||||||
v-auth.plan="'analysis'"
|
v-auth.plan="'analysis'"
|
||||||
@@ -136,6 +146,7 @@
|
|||||||
|
|
||||||
<ImportExcel ref="planImportExcel" />
|
<ImportExcel ref="planImportExcel" />
|
||||||
<ImportZip ref="planImportZip" @result="importResult" />
|
<ImportZip ref="planImportZip" @result="importResult" />
|
||||||
|
<PlanStatisticsPopup ref="planStatisticsPopupRef" />
|
||||||
|
|
||||||
<ChildrenPlan
|
<ChildrenPlan
|
||||||
:refresh-table="refreshTable"
|
:refresh-table="refreshTable"
|
||||||
@@ -163,6 +174,7 @@ import { computed, onMounted, reactive, ref, watch } from 'vue'
|
|||||||
import type { Plan } from '@/api/plan/interface'
|
import type { Plan } from '@/api/plan/interface'
|
||||||
import PlanPopup from '@/views/plan/planList/components/planPopup.vue' // 导入子组件
|
import PlanPopup from '@/views/plan/planList/components/planPopup.vue' // 导入子组件
|
||||||
import ChildrenPlan from '@/views/plan/planList/components/childrenPlan.vue'
|
import ChildrenPlan from '@/views/plan/planList/components/childrenPlan.vue'
|
||||||
|
import PlanStatisticsPopup from '@/views/plan/planList/components/planStatisticsPopup.vue'
|
||||||
import { useViewSize } from '@/hooks/useViewSize'
|
import { useViewSize } from '@/hooks/useViewSize'
|
||||||
import { useDictStore } from '@/stores/modules/dict'
|
import { useDictStore } from '@/stores/modules/dict'
|
||||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
@@ -187,6 +199,7 @@ const proTable = ref<ProTableInstance>()
|
|||||||
const errorStandardPopup = ref()
|
const errorStandardPopup = ref()
|
||||||
const testSourcePopup = ref()
|
const testSourcePopup = ref()
|
||||||
const planPopup = ref()
|
const planPopup = ref()
|
||||||
|
const planStatisticsPopupRef = ref<InstanceType<typeof PlanStatisticsPopup> | null>(null)
|
||||||
|
|
||||||
const modeStore = useModeStore()
|
const modeStore = useModeStore()
|
||||||
const tableData = ref<any[]>([])
|
const tableData = ref<any[]>([])
|
||||||
@@ -530,7 +543,7 @@ const columns = reactive<ColumnProps<Plan.ReqPlan>[]>([
|
|||||||
isShow: modeStore.currentMode == '比对式'
|
isShow: modeStore.currentMode == '比对式'
|
||||||
},
|
},
|
||||||
|
|
||||||
{ prop: 'operation', label: '操作', fixed: 'right', minWidth: 250 }
|
{ prop: 'operation', label: '操作', fixed: 'right', minWidth: 320 }
|
||||||
])
|
])
|
||||||
|
|
||||||
function isVisible(row: Plan.ReqPlan) {
|
function isVisible(row: Plan.ReqPlan) {
|
||||||
@@ -654,6 +667,10 @@ const statisticalAnalysis = async (row: Partial<Plan.ReqPlan> = {}) => {
|
|||||||
useDownload(staticsAnalyse, '分析结果', [row.id], false, '.xlsx')
|
useDownload(staticsAnalyse, '分析结果', [row.id], false, '.xlsx')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const openStatistics = (row: Partial<Plan.ReqPlan> = {}) => {
|
||||||
|
planStatisticsPopupRef.value?.open(row)
|
||||||
|
}
|
||||||
|
|
||||||
const importSubClick = () => {
|
const importSubClick = () => {
|
||||||
const params = {
|
const params = {
|
||||||
title: '导入检测计划',
|
title: '导入检测计划',
|
||||||
@@ -671,4 +688,4 @@ const importResult = async (success: boolean | undefined) => {
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped></style>
|
<style scoped></style>
|
||||||
|
|||||||
Reference in New Issue
Block a user