Vue接口平台十三——测试记录

效果图及说明

上面用散点图显示,x轴可以筛选时间,环境,任务名称。选中后,聚合方式会发生变化。y轴显示通过率。相同任务点的颜色会相同。

下方显示执行的所有任务简介。点击查看报告,跳转报告详情页面。

内容实现

前置准备

  1. 新建文件RecordView.vue
  2. 路由配置
  3. 左侧菜单栏配置

页面代码

页面展示

html 复制代码
<template>
  <div class="main">
    <!-- 渲染图标的元素 -->
    <div>
      <div style="margin-bottom: 10px;">
        <label>X轴选择: </label>
        <select @change="changeXAxis" v-model="selectedXAxis">
          <option value="create_time">时间</option>
          <option value="task">任务名称</option>
          <option value="env">测试环境</option>
        </select>
      </div>
      <div id="chat_pro_record" class="box1"></div>
    </div>

    <!-- 渲染表格的组件 -->
    <el-table :data="recordList" border style="width: 100%" size="small">
	  <el-table-column label="执行时间" prop="create_time" min-width="180"/>
      <el-table-column prop="env" label="执行环境"/>
      <el-table-column prop="task" label="测试计划"/>
      <el-table-column prop="all" label="总用例"/>
      <el-table-column prop="success" label="通过数"/>
      <el-table-column prop="pass_rate" label="通过率"/>
      <el-table-column label="测试报告" width="180">
        <template #default="scope">
          <el-button @click="showReport(scope.row)" icon="View" type="primary" plain
                     size="small">查看报告
          </el-button>
        </template>
      </el-table-column>
    </el-table>
  </div>
</template>

<style scoped lang="scss">
.box1 {
  height: 260px;
  background: #282828;
  margin-bottom: 10px;
}

.main {
  padding: 10px;
}
</style>

js

js 复制代码
<script setup>
import { ProjectStore } from '@/stores/module/ProStore'
import mychat from '@/utils/chart.js'
import http from '@/api/index'
import { ref, onMounted,onUnmounted } from 'vue'
import { useRouter } from 'vue-router'

const router = useRouter()
// 存储项目的执行记录
let recordList = ref([])
let chartInstance = null;

const selectedXAxis = ref('create_time');

const proStore = ProjectStore()

// 获取项目的所有测试执行记录
const getRecords = async function () {
  const response = await http.task.getRecordsApi({
    project: proStore.pro.id
  })
  recordList.value = response.data
  showChat()
}
// 组件加载完获取所有数据
onMounted(() => {
  getRecords()
})


const showChat = function () {
  console.log("recordList.value", recordList.value)
  const dom = document.getElementById('chat_pro_record')

  // 销毁之前的图表实例
  if (chartInstance) {
    chartInstance.dispose();
  }

  // 创建新的图表实例
  chartInstance = mychat.chart4(dom, recordList.value)
}

// X轴切换处理
function changeXAxis() {
  if (chartInstance && chartInstance.changeXAxis) {
    chartInstance.changeXAxis(selectedXAxis.value);
  }
}

function showReport(record) {
  router.push({
    name: "report",
    params: {
      id: record.id
    }
  })
}


// 组件卸载时清理资源
onUnmounted(() => {
  if (chartInstance) {
    chartInstance.dispose();
  }
})
</script>

这块内容比较简单,简单梳理一下逻辑

组件加载完获取所有records数据,获取完数据后,保存到变量recordList中,并且展示图表数据。

然后x轴选择可以触发函数,切换聚合目标。销毁原先的图数据,再展示新的图数据。

表格中的操作-查看报告绑定了函数,点击跳转报告详情。

图的绘制

js 复制代码
// utils/chart.js
chart4(ele, datas) {
        if (!ele || !datas || datas.length === 0) {
            console.warn('Invalid element or data for chart4');
            return;
        }

        const indices = {
            id: 0,
            env: 1,
            task: 2,
            create_time: 3,
            all: 4,
            success: 5,
            fail: 6,
            error: 7,
            pass_rate: 8,
            tester: 9,
            status: 10
        };

        const schema = [
            {name: 'id', index: 0},
            {name: 'env', index: 1},
            {name: 'task', index: 2},
            {name: 'create_time', index: 3},
            {name: 'all', index: 4},
            {name: 'success', index: 5},
            {name: 'fail', index: 6},
            {name: 'error', index: 7},
            {name: 'pass_rate', index: 8},
            {name: 'tester', index: 9},
            {name: 'status', index: 10}
        ];

        const fieldIndices = schema.reduce(function (obj, item) {
            obj[item.name] = item.index;
            return obj;
        }, {});

        // 用于存储不同类别的颜色映射
        const categoryColors = {};
        let myChart = null;
        let data;
        let currentXAxis = 'create_time';
        let currentSymbolSize = 10; // 默认点大小

        // 模拟数据
        const originData = datas;

        function normalizeData(originData) {
            // 收集所有唯一类别以分配颜色
            const categories = new Set();
            originData.forEach(function (row) {
                if (row.env) categories.add(row.env);
                if (row.task) categories.add(row.task);
            });

            // 为每个唯一类别分配颜色
            let categoryArray = Array.from(categories);
            let hStep = Math.round(300 / (categoryArray.length - 1 || 1));
            categoryArray.forEach((category, i) => {
                categoryColors[category] = echarts.color.modifyHSL('#5A94DF', hStep * i);
            });

            // 转换数据格式
            let processedData = originData.map(function (row) {
                let processedRow = new Array(schema.length);
                for (let i = 0; i < schema.length; i++) {
                    processedRow[i] = row[schema[i].name];
                }
                return processedRow;
            });

            processedData.forEach(function (row) {
                for (let index = 0; index < row.length; index++) {
                    if (
                        index !== indices.env &&
                        index !== indices.task &&
                        index !== indices.create_time
                    ) {
                        if (index === indices.pass_rate) {
                            row[index] = parseFloat(row[index]) || 0;
                        } else if (index !== indices.id && index !== indices.tester && index !== indices.status) {
                            row[index] = parseFloat(row[index]) || 0;
                        }
                    }
                }
            });

            return processedData;
        }

        function getOption(data, xAxisField = 'create_time') {
            // 为category类型x轴准备数据
            let uniqueXValues = [...new Set(data.map(item => item[fieldIndices[xAxisField]]))];

            // 如果是时间字段,按时间排序,确保最新的在右边
            if (xAxisField === 'create_time') {
                uniqueXValues.sort((a, b) => new Date(a) - new Date(b));
            }

            return {
                xAxis: {
                    type: 'category',
                    name: xAxisField,
                    data: uniqueXValues,
                    splitLine: {show: false}
                },
                yAxis: {
                    type: 'value',
                    name: '通过率(%)',
                    splitLine: {show: false},
                    axisLabel: {
                        formatter: '{value}%'
                    }
                },
                series: [
                    {
                        zlevel: 1,
                        name: '测试结果',
                        type: 'scatter',
                        data: data.map(function (item, idx) {
                            // 根据当前X轴字段选择颜色分类依据
                            let category;
                            if (xAxisField === 'env') {
                                category = item[indices.task]; // X轴是环境时,按任务分类颜色
                            } else {
                                category = item[indices.task]; // 其他情况按任务分类颜色
                            }

                            return {
                                value: [item[fieldIndices[xAxisField]], item[indices.pass_rate], category, idx],
                                itemStyle: {
                                    color: categoryColors[category] || '#5A94DF'
                                }
                            };
                        }),
                        animationThreshold: 5000,
                        progressiveThreshold: 5000,
                        symbolSize: currentSymbolSize // 使用当前设置的点大小
                    }
                ],
                animationEasingUpdate: 'cubicInOut',
                animationDurationUpdate: 2000,
                tooltip: {
                    trigger: 'item',
                    formatter: function (params) {
                        const dataIndex = params.data.value[3];
                        const rowData = data[dataIndex];
                        if (!rowData) return '';
                        return `${rowData[indices.task] || ''}<br/>
            ${xAxisField}: ${params.data.value[0]}<br/>
            环境: ${rowData[indices.env] || ''}<br/>
            通过率: ${rowData[indices.pass_rate] || 0}%`;
                    }
                },
                legend: {
                    show: true,
                    data: Object.keys(categoryColors),
                    textStyle: {
                        color: '#fff'
                    }
                }
            };
        }

        // X轴切换函数
        function changeXAxis(xAxisField) {
            if (data && myChart) {
                currentXAxis = xAxisField;
                const option = getOption(data, xAxisField);
                myChart.setOption(option, true); // true表示不合并选项,完全替换
            }
        }

        // 点大小调整函数
        function changeSymbolSize(size) {
            if (myChart) {
                currentSymbolSize = size;
                const option = getOption(data, currentXAxis);
                myChart.setOption(option, true);
            }
        }

        try {
            myChart = echarts.init(ele);

            // 初始化数据
            data = normalizeData(originData);
            const option = getOption(data, currentXAxis);

            myChart.setOption(option);

            window.addEventListener('resize', () => {
                if (myChart) {
                    myChart.resize();
                }
            });

            // 返回图表实例和控制函数,供外部调用
            return {
                chart: myChart,
                changeXAxis: changeXAxis,
                changeSymbolSize: changeSymbolSize, // 返回点大小调整函数
                dispose: () => {
                    if (myChart) {
                        myChart.dispose();
                    }
                }
            };
        } catch (error) {
            console.error('Error initializing chart:', error);
            return null;
        }
    }

这块代码,是直接把echarts官网的示例丢给AI,再给出自己数据格式以及想要的效果生成,然后慢慢调试出来的。我也不会写。能用就行。

实现效果



这里通过率相同的点会重合,后续也可以再优化下这个问题。也可以不用管,,,看自己需要吧。