SerialStats.vue 16.3 KB
<script setup lang="ts">
import { ref, reactive, onMounted, computed, nextTick, watch } from 'vue'
import { Search, Download } from '@element-plus/icons-vue'
import * as echarts from 'echarts'
import dayjs from 'dayjs'

const queryForm = reactive({
    dateRange: [] as string[],
    page: 1,
    pageSize: 10
})

// Detailed Serial Device List
const deviceList = [
    '普通光猫串号', '机顶盒串号', '路由器串号', '从光猫串号', 
    '主光猫串号', '室内安防串号', '软终端串号', '云电脑终端串号', 'POE交换机串号'
]

const summary = ref({
    total: 5000,
    autoRate: 85.5,
    pass1: 70.2,
    pass2: 15.3,
    pass3: 10.1,
    avgTime: 12.5,
    avgAttempts: 1.4
})

const tableData = ref(deviceList.map(name => ({
    name,
    total: Math.floor(Math.random() * 1000 + 500),
    pass1: (Math.random() * 20 + 60).toFixed(1) + '%',
    pass2: (Math.random() * 10 + 10).toFixed(1) + '%',
    pass3: (Math.random() * 5 + 5).toFixed(1) + '%',
    pass3in: '95%',
    autoRate: (Math.random() * 15 + 75).toFixed(1) + '%',
    avgCount: (Math.random() * 0.5 + 1).toFixed(1),
    avgTime: (Math.random() * 5 + 8).toFixed(1) + 's'
})))

// Computed property to check if date range > 1 day
const isMultiDay = computed(() => {
    if (!queryForm.dateRange || queryForm.dateRange.length !== 2) return false
    const start = dayjs(queryForm.dateRange[0])
    const end = dayjs(queryForm.dateRange[1])
    return end.diff(start, 'day') >= 1
})

// Tab State (Only for Multi-Day)
const trendTab = ref('overall') // 'overall', 'details'

const passRateChartRef = ref<HTMLElement>()
const autoRateChartRef = ref<HTMLElement>()
const trendChartRef = ref<HTMLElement>()
const deviceTrendChartRef = ref<HTMLElement>()

// New Trend Charts Refs
const overallPassRateTrendRef = ref<HTMLElement>()
const overallAutoRateTrendRef = ref<HTMLElement>()
const devicePassRateChartRef = ref<HTMLElement>() // For Device Detail Tab - Pass Rate
const deviceAutoRateChartRef = ref<HTMLElement>() // For Device Detail Tab - Auto Rate

// Chart instances
let passRateChart: echarts.ECharts | null = null
let trendChart: echarts.ECharts | null = null
let overallPassRateChart: echarts.ECharts | null = null
let overallAutoRateChart: echarts.ECharts | null = null
let devicePassRateChart: echarts.ECharts | null = null
let deviceAutoRateChart: echarts.ECharts | null = null


/* -------------------------------------------------------------------------- */
/*                           SINGLE DAY CHARTS                                */
/* -------------------------------------------------------------------------- */

const initSingleDayCharts = () => {
    // 1. Device Pass Rate Bar Chart
    if (passRateChartRef.value) {
        passRateChart = echarts.getInstanceByDom(passRateChartRef.value) || echarts.init(passRateChartRef.value)
        passRateChart.setOption({
            title: { text: '设备识别通过率' },
            tooltip: { trigger: 'axis' },
            legend: { data: ['1次通过', '2次通过', '3次通过'], bottom: 0 },
            grid: { left: '3%', right: '4%', bottom: '15%', containLabel: true },
            xAxis: { type: 'category', data: deviceList, axisLabel: { interval: 0, rotate: 30 } },
            yAxis: { type: 'value', name: '%' },
            series: [
                { name: '1次通过', type: 'bar', data: deviceList.map(() => Math.random() * 20 + 60) },
                { name: '2次通过', type: 'bar', data: deviceList.map(() => Math.random() * 10 + 10) },
                { name: '3次通过', type: 'bar', data: deviceList.map(() => Math.random() * 5 + 5) }
            ]
        })
        passRateChart.resize()
    }

    // 2. Device Auto Identify Rate Chart (Single Day)
    if (trendChartRef.value) {
        trendChart = echarts.getInstanceByDom(trendChartRef.value) || echarts.init(trendChartRef.value)
        trendChart.setOption({
            title: { text: '设备自动识别通过率' },
            tooltip: { trigger: 'axis' },
            legend: { data: ['自动识别率'], bottom: 0 },
            xAxis: { type: 'category', data: deviceList, axisLabel: { interval: 0, rotate: 30 } },
            yAxis: { type: 'value', name: '%' },
            grid: { left: '3%', right: '4%', bottom: '15%', containLabel: true },
            series: [
                { name: '自动识别率', type: 'bar', data: deviceList.map(() => Math.random() * 10 + 80), itemStyle: { color: '#67C23A' } }
            ]
        })
        trendChart.resize()
    }
}


/* -------------------------------------------------------------------------- */
/*                           MULTI DAY CHARTS                                 */
/* -------------------------------------------------------------------------- */

const initMultiDayCharts = () => {
    // Generate dates based on selection, or mock if not selected
    let dates = []
    if (queryForm.dateRange && queryForm.dateRange.length === 2) {
         let current = dayjs(queryForm.dateRange[0])
         const end = dayjs(queryForm.dateRange[1])
         while(current.isBefore(end) || current.isSame(end, 'day')) {
             dates.push(current.format('YYYY-MM-DD'))
             current = current.add(1, 'day')
         }
         // Limit to avoid too many points for mock
         if(dates.length > 20) dates = dates.slice(0, 20)
    } else {
        // Default 7 days
        for(let i=0; i<7; i++) dates.push(dayjs().subtract(6-i, 'day').format('YYYY-MM-DD'))
    }

    if (trendTab.value === 'overall') {
        // 1. Overall Pass Rate Trend: Stacked or Multi-Line for 1st, 2nd, 3rd pass rates
        if (overallPassRateTrendRef.value) {
            overallPassRateChart = echarts.getInstanceByDom(overallPassRateTrendRef.value) || echarts.init(overallPassRateTrendRef.value)
            overallPassRateChart.setOption({
                title: { text: '整体通过率时间趋势' },
                tooltip: { trigger: 'axis' },
                legend: { data: ['1次通过率', '2次通过率', '3次通过率'], bottom: 0 },
                xAxis: { type: 'category', data: dates },
                yAxis: { type: 'value', name: '%' },
                grid: { left: '3%', right: '4%', bottom: '15%', containLabel: true },
                series: [
                    { name: '1次通过率', type: 'line', data: dates.map(() => Math.random() * 10 + 70), smooth: true },
                    { name: '2次通过率', type: 'line', data: dates.map(() => Math.random() * 5 + 15), smooth: true },
                    { name: '3次通过率', type: 'line', data: dates.map(() => Math.random() * 5 + 5), smooth: true }
                ]
            }, true)
            overallPassRateChart.resize()
        }

        // 2. Overall Auto Rate Trend
        if (overallAutoRateTrendRef.value) {
            overallAutoRateChart = echarts.getInstanceByDom(overallAutoRateTrendRef.value) || echarts.init(overallAutoRateTrendRef.value)
            overallAutoRateChart.setOption({
                title: { text: '整体自动识别率时间趋势' },
                tooltip: { trigger: 'axis' },
                xAxis: { type: 'category', data: dates },
                yAxis: { type: 'value', name: '%' },
                grid: { left: '3%', right: '4%', bottom: '3%', containLabel: true },
                series: [{ name: '自动识别率', type: 'line', data: dates.map(() => Math.random() * 10 + 80), smooth: true, itemStyle: { color: '#67C23A' } }]
            }, true)
            overallAutoRateChart.resize()
        }
    } else {
       // 'details' Tab
       
       // 1. Device Pass Rate Stats (New Chart requested)
       nextTick(() => {
           if (devicePassRateChartRef.value) {
               devicePassRateChart = echarts.getInstanceByDom(devicePassRateChartRef.value) || echarts.init(devicePassRateChartRef.value)
               devicePassRateChart.setOption({
                    title: { text: '设备识别通过率' },
                    tooltip: { trigger: 'axis' },
                    legend: { data: ['1次通过', '2次通过', '3次通过'], bottom: 0 },
                    grid: { left: '3%', right: '4%', bottom: '15%', containLabel: true },
                    xAxis: { type: 'category', data: deviceList, axisLabel: { interval: 0, rotate: 30 } },
                    yAxis: { type: 'value', name: '%' },
                    series: [
                        { name: '1次通过', type: 'bar', data: deviceList.map(() => Math.random() * 20 + 60) },
                        { name: '2次通过', type: 'bar', data: deviceList.map(() => Math.random() * 10 + 10) },
                        { name: '3次通过', type: 'bar', data: deviceList.map(() => Math.random() * 5 + 5) }
                    ]
               })
               devicePassRateChart.resize()
           }
       })

       // 2. Device Auto Rate Stats
       // x: Device Name, y: Auto Rate
       nextTick(() => {
           if (deviceAutoRateChartRef.value) {
               deviceAutoRateChart = echarts.getInstanceByDom(deviceAutoRateChartRef.value) || echarts.init(deviceAutoRateChartRef.value)
               deviceAutoRateChart.setOption({
                   title: { text: '设备自动识别率' },
                   tooltip: { trigger: 'axis' },
                   legend: { data: ['自动识别率'], bottom: 0 },
                   xAxis: { type: 'category', data: deviceList, axisLabel: { interval: 0, rotate: 30 } },
                   yAxis: { type: 'value', name: '%' },
                   grid: { left: '3%', right: '4%', bottom: '15%', containLabel: true },
                   series: [
                       { name: '自动识别率', type: 'bar', data: deviceList.map(() => Math.random() * 15 + 75), itemStyle: { color: '#409EFF' } }
                   ]
               })
               deviceAutoRateChart.resize()
           }
       })
    }
}

const handleSearch = () => {
    nextTick(() => {
        updateView()
    })
}

const updateView = () => {
    if (isMultiDay.value) {
        initMultiDayCharts()
    } else {
        initSingleDayCharts()
    }
}

watch([isMultiDay, trendTab], () => {
    nextTick(() => {
        updateView()
    })
})

const handleResize = () => {
    passRateChart?.resize()
    trendChart?.resize()
    overallPassRateChart?.resize()
    overallPassRateChart?.resize()
    overallAutoRateChart?.resize()
    devicePassRateChart?.resize()
    deviceAutoRateChart?.resize()
}

onMounted(() => {
    updateView()
    window.addEventListener('resize', handleResize)
})
</script>

<template>
  <div class="serial-stats-page">
     <!-- Filter -->
    <el-card shadow="never" class="mb-4">
      <el-form :inline="true" :model="queryForm">
        <el-form-item label="日期">
           <el-date-picker
            v-model="queryForm.dateRange"
            type="datetimerange"
            range-separator="至"
            start-placeholder="开始时间"
            end-placeholder="结束时间"
            value-format="YYYY-MM-DD"
          />
        </el-form-item>
        <el-form-item>
          <el-button type="primary" :icon="Search" @click="handleSearch">查询</el-button>
        </el-form-item>
      </el-form>
    </el-card>

    <!-- Summary Cards -->
    <div class="grid grid-cols-7 gap-2 mb-4">
        <el-card shadow="never" body-style="padding: 10px"><div class="text-xs text-gray-500">提交总次数</div><div class="text-lg font-bold">{{ summary.total }}</div></el-card>
        <el-card shadow="never" body-style="padding: 10px"><div class="text-xs text-gray-500">自动识别占比</div><div class="text-lg font-bold text-blue-500">{{ summary.autoRate }}%</div></el-card>
        <el-card shadow="never" body-style="padding: 10px"><div class="text-xs text-gray-500">1次通过率</div><div class="text-lg font-bold text-green-500">{{ summary.pass1 }}%</div></el-card>
        <el-card shadow="never" body-style="padding: 10px"><div class="text-xs text-gray-500">2次通过率</div><div class="text-lg font-bold text-green-500">{{ summary.pass2 }}%</div></el-card>
        <el-card shadow="never" body-style="padding: 10px"><div class="text-xs text-gray-500">3次内通过率</div><div class="text-lg font-bold text-green-500">{{ (summary.pass1 + summary.pass2 + summary.pass3).toFixed(1) }}%</div></el-card>
        <el-card shadow="never" body-style="padding: 10px"><div class="text-xs text-gray-500">平均耗时</div><div class="text-lg font-bold">{{ summary.avgTime }}s</div></el-card>
        <el-card shadow="never" body-style="padding: 10px"><div class="text-xs text-gray-500">平均尝试</div><div class="text-lg font-bold">{{ summary.avgAttempts }}</div></el-card>
    </div>

    <!-- Charts Area -->
    <!-- Case 1: Single Day -->
    <div v-if="!isMultiDay" class="grid grid-cols-1 gap-4 mb-4">
        <el-card shadow="never">
            <div ref="passRateChartRef" style="height: 350px"></div>
        </el-card>
        <el-card shadow="never">
            <div ref="trendChartRef" style="height: 350px"></div>
        </el-card>
    </div>

    <!-- Case 2: Multi Day -->
    <el-card v-else shadow="never" class="mb-4">
        <template #header>
            <div class="flex items-center gap-4">
                <span class="font-bold">趋势分析</span>
                <el-radio-group v-model="trendTab" size="small">
                    <el-radio-button value="overall">整体趋势</el-radio-button>
                    <el-radio-button value="details">设备详情</el-radio-button>
                </el-radio-group>
            </div>
        </template>
        
        <!-- Overall Trend Tab Content -->
        <div v-if="trendTab === 'overall'" class="grid grid-cols-1 gap-4">
            <div ref="overallPassRateTrendRef" style="height: 350px; border: 1px solid #f0f0f0; border-radius: 4px; padding: 10px;"></div>
            <div ref="overallAutoRateTrendRef" style="height: 350px; border: 1px solid #f0f0f0; border-radius: 4px; padding: 10px;"></div>
        </div>
        
        <!-- Device Details Tab Content -->
        <div v-if="trendTab === 'details'">
             <div ref="devicePassRateChartRef" style="height: 400px; border: 1px solid #f0f0f0; border-radius: 4px; padding: 10px;" class="mb-4"></div>
             <div ref="deviceAutoRateChartRef" style="height: 400px; border: 1px solid #f0f0f0; border-radius: 4px; padding: 10px;" class="mb-4"></div>
             
             <!-- Table for Device Details -->
             <el-table :data="tableData" border stripe>
                <el-table-column prop="name" label="设备名称" />
                <el-table-column prop="total" label="提交总次数" sortable />
                <el-table-column prop="pass1" label="1次通过率" sortable />
                <el-table-column prop="pass2" label="2次通过率" sortable />
                <el-table-column prop="pass3" label="3次通过率" sortable />
                <el-table-column prop="pass3in" label="3次内通过率" sortable />
                <el-table-column prop="autoRate" label="自动识别占比" sortable />
                <el-table-column prop="avgCount" label="平均识别次数" sortable />
                <el-table-column prop="avgTime" label="平均耗时" sortable />
            </el-table>
        </div>
    </el-card>

     <!-- Single Day Table (Outside of multi-day card) -->
     <div v-if="!isMultiDay" class="mb-4">
         <el-card shadow="never">
            <template #header>
                <div class="flex justify-between items-center">
                    <span class="font-bold">设备串号明细</span>
                    <el-button type="success" link :icon="Download">导出表格</el-button>
                </div>
            </template>
            <el-table :data="tableData" border stripe>
                <el-table-column prop="name" label="设备名称" />
                <el-table-column prop="total" label="提交总次数" sortable />
                <el-table-column prop="pass1" label="1次通过率" sortable />
                <el-table-column prop="pass2" label="2次通过率" sortable />
                <el-table-column prop="pass3" label="3次通过率" sortable />
                <el-table-column prop="pass3in" label="3次内通过率" sortable />
                <el-table-column prop="autoRate" label="自动识别占比" sortable />
                <el-table-column prop="avgCount" label="平均识别次数" sortable />
                <el-table-column prop="avgTime" label="平均耗时" sortable />
            </el-table>
        </el-card>
    </div>

  </div>
</template>