效果图及说明

上面用散点图显示,x轴可以筛选时间,环境,任务名称。选中后,聚合方式会发生变化。y轴显示通过率。相同任务点的颜色会相同。
下方显示执行的所有任务简介。点击查看报告,跳转报告详情页面。
内容实现
前置准备
- 新建文件
RecordView.vue
- 路由配置
- 左侧菜单栏配置
页面代码
页面展示
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,再给出自己数据格式以及想要的效果生成,然后慢慢调试出来的。我也不会写。能用就行。
实现效果
这里通过率相同的点会重合,后续也可以再优化下这个问题。也可以不用管,,,看自己需要吧。