文章目录
⭐前言
大家好,我是yma16,本文分享 性能监控系统搭建------vue3实现性能监控数据展示(第二章)。
背景:
为了实现前端性能耗时的数据监控,前端对外发布js的sdk,sdk的功能主要是性能耗时计算和数据上报。同时使用vue3和node开发一个数据监控的后台管理系统,主要功能是展示数据,提供一个api_key和token对外暴露的api接口去添加数据监控数据。
功能:vue3前端实现性能监控数据展示功能
由于在往期文章已经介绍如何从0到1搭建vue框架,所以这里主要分享数据展示逻辑。
node系列往期文章
node_windows环境变量配置
node_npm发布包
linux_配置node
node_nvm安装配置
node笔记_http服务搭建(渲染html、json)
node笔记_读文件
node笔记_写文件
node笔记_连接mysql实现crud
node笔记_formidable实现前后端联调的文件上传
node笔记_koa框架介绍
node_koa路由
node_生成目录
node_读写excel
node笔记_读取目录的文件
node笔记------调用免费qq的smtp发送html格式邮箱
node实战------搭建带swagger接口文档的后端koa项目(node后端就业储备知识)
node实战------后端koa结合jwt连接mysql实现权限登录(node后端就业储备知识)
node实战------koa给邮件发送验证码并缓存到redis服务(node后端储备知识)
koa系列项目文章
前端vite+vue3结合后端node+koa------实现代码模板展示平台(支持模糊搜索+分页查询)
node+vue3+mysql前后分离开发范式------实现对数据库表的增删改查
node+vue3+mysql前后分离开发范式------实现视频文件上传并渲染
koa-vue性能监控到封装sdk系列文章
性能监控系统搭建------node_koa实现性能监控数据上报(第一章)
⭐前端vue3项目展示数据
分享前端页面展示的代码逻辑实现
前端构建组成:vite+vue3+antd+axios
axios 库的使用
js
// @ts-ignore
import axios from "axios";
// 实例
const createInstance = (baseURL:string)=>{
return axios.create({
baseURL:baseURL,
timeout: 10000,
headers: {'X-Custom-Header': 'yma16'}
})
};
// @ts-ignore
const http:any=createInstance('');
// 添加请求拦截器
http.interceptors.request.use(function (config:any) {
// // 获取token 配置 jwt
const token = localStorage.getItem("userInfoPermissionToken")
config.headers.Authorization = `bearer ${token}`
// 在发送请求之前做些什么
return config;
}, function (error:any) {
// 对请求错误做些什么
return Promise.reject(error);
});
// 添加响应拦截器
http.interceptors.response.use(function (response:any) {
// 2xx 范围内的状态码都会触发该函数。
// 对响应数据做点什么
return response;
}, function (error:any) {
// 超出 2xx 范围的状态码都会触发该函数。
// 对响应错误做点什么
return Promise.reject(error);
});
// aixos 上传实例
const createUploadInstance = (baseURL:string)=>{
return axios.create({
baseURL:baseURL,
timeout: 1000000,
headers: {"Content-Type": "multipart/form-data"}
})
};
// @ts-ignore
const uploadHttp:any=createUploadInstance('');
// 添加请求拦截器
uploadHttp.interceptors.request.use(function (config:any) {
// // 获取token 配置 jwt
const token = localStorage.getItem("userInfoPermissionToken")
config.headers.Authorization = `bearer ${token}`
// 在发送请求之前做些什么
return config;
}, function (error:any) {
// 对请求错误做些什么
return Promise.reject(error);
});
// 添加响应拦截器
uploadHttp.interceptors.response.use(function (response:any) {
// 2xx 范围内的状态码都会触发该函数。
// 对响应数据做点什么
return response;
}, function (error:any) {
// 超出 2xx 范围的状态码都会触发该函数。
// 对响应错误做点什么
return Promise.reject(error);
});
export {http,uploadHttp};
http请求 koa的接口
javascript
import {http} from "@/http/index";
/**
* perf 列表
* @param params
*/
export const getPerfList: any = (params: any) => {
return http.post("/cloudApi/api/perf/list", params);
};
/**
* perf 删除
* @param params
*/
export const deletePerf: any = (params: any) => {
return http.post("/cloudApi/api/product/delete", params);
};
✏️ 分页列表查询展示数据
vue3 setup页面,布局分为上下两个部分,上方为模糊查询,下方为分页表格
html
<template>
<div>
<a-row>
<a-col :span="12">
<a-input v-model:value="state.name" placeholder="输入名称搜索" allowClear />
</a-col>
<a-col :span="12" style="padding-left: 20px">
<a-button type="primary" @click="searchBtn">
<search-outlined />查询
</a-button>
</a-col>
</a-row>
</div>
<div style="margin: 10px 0;">
<a-table :dataSource="state.perfList"
:row-key="record => record.id"
:pagination="state.pagination"
:loading="state.loading"
@change="handleTableChange"
:columns="state.columns"
:scroll="{ y: 800 }"
bordered
>
</a-table>
</div>
</template>
<script lang="ts" setup>
import {reactive,onMounted} from 'vue';
import {getPerfList} from '@/service/perf/index'
const state:any=reactive({
name:'',
loading:false,
perfList:[],
columns:[{
title: 'name',
dataIndex: 'name',
key: 'name',
},
{
title: 'type',
dataIndex: 'type',
key: 'type',
},
{
title: 'path',
dataIndex: 'path',
key: 'path',
},
{
title: 'fp_count',
dataIndex: 'fp_count',
key: 'fp_count',
},
{
title: 'tcp_count',
dataIndex: 'tcp_count',
key: 'tcp_count',
},
{
title: 'dns_count',
dataIndex: 'dns_count',
key: 'dns_count',
},
{
title: 'dcl_count',
dataIndex: 'dcl_count',
key: 'dcl_count',
},
{
title: '创建人',
dataIndex: 'create_user_id',
key: 'create_user_id',
},
],
pagination: {
showSizeChanger:true,
pageSizeOptions:['3', '6', '9'],
total: 3,
current: 1,
pageSize: 3,
},
});
const searchProductAction=async ()=>{
state.loading=true
try{
const res=await getPerfList({
name:state.name,
pageSize:state.pagination.pageSize,
page:state.pagination.current
})
console.log('res',res)
state.perfList=res?.data?.data
}
catch (e) {
console.log(e)
state.perfList=[]
}
state.loading=false
};
const searchBtn=()=>{
searchProductAction()
}
const handleTableChange=({ pageSize, current })=>{
state.pagination.current=current
state.pagination.pageSize=pageSize
searchBtn()
}
onMounted(()=>{
searchBtn()
})
</script>
💖分页展示效果
常规的表格分页展示
✏️ ehcarts图形化展现数据
网络频率使用饼图分布,使用用户数据使用地图分布,fmp耗时使用柱状图表示
html
<template>
<div class="base-container">
<div style="width: 100%; text-align: center; font-size: 24px; font-weight: bold">
性能数据展示
</div>
<div style="display: flex">
<div style="min-width: 400px; margin-right: 10px">
<div>渲染时间: {{ state.echarPietDuration }} ms</div>
<!-- 网络分布 饼图-->
<div style="width: 300px; height: 600px" :id="state.pieId"></div>
</div>
<div style="min-width: 550px; margin-right: 10px">
<div>渲染时间: {{ state.echarPietDuration }} ms</div>
<!-- 网络分布地图 -->
<div style="width: 400px; height: 600px" :id="state.mapId"></div>
</div>
<div style="min-width: 600px; margin-right: 10px">
<div>渲染时间: {{ state.echarLineDuration }} ms</div>
<!-- fmp 耗时 折现图 -->
<div style="width: 600px; height: 600px" :id="state.lineId"></div>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import * as echarts from "echarts";
import { reactive, onMounted, onBeforeMount } from "vue";
import chinaJson from "@/views/perf/view/data/map.js";
const state: any = reactive({
pieId: "pie-id",
mapId: "map-id",
lineId: "line-id",
locationGis: [],
echartPieInstance: null,
echarPietDuration: 0,
echartMapInstance: null,
echarMaptDuration: 0,
echartLineInstance: null,
echarLineDuration: 0,
});
function renderPieChart() {
// 基于准备好的dom,初始化echarts实例
const domInstance = document.getElementById(state.pieId);
if (domInstance) {
domInstance.removeAttribute("_echarts_instance_");
} else {
return;
}
console.log("domInstance", domInstance);
const myChart = echarts.init(domInstance);
const option = {
title: {
text: "网络频段占比",
left: "center",
},
tooltip: {
trigger: "item",
},
legend: {
orient: "vertical",
left: "left",
},
series: [
{
name: "网络频段",
type: "pie",
radius: "50%",
data: [
{ value: 25, name: "2g" },
{ value: 735, name: "3g" },
{ value: 580, name: "4g" },
{ value: 900, name: "5g" },
{ value: 300, name: "未知状态" },
],
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: "rgba(0, 0, 0, 0.5)",
},
},
},
],
};
console.log("option", option);
// 使用刚指定的配置项和数据显示图表。
myChart.setOption(option, true);
// 监听
state.echartPieInstance = myChart;
myChart.on("click", function (params: any) {
console.log("params", params);
});
window.onresize = myChart.resize;
performance.mark("render-pie-echart-end");
const measure = performance.measure(
"render-echart-duration",
"render-echart-start",
"render-pie-echart-end"
);
console.log("measure", measure);
console.log("measure type", typeof measure);
const duration = measure.duration;
console.log("duration", duration);
state.echarPietDuration = duration;
}
function renderLineChart() {
// 基于准备好的dom,初始化echarts实例
const domInstance = document.getElementById(state.lineId);
if (domInstance) {
domInstance.removeAttribute("_echarts_instance_");
} else {
return;
}
console.log("domInstance", domInstance);
const myChart = echarts.init(domInstance);
const option = {
title: {
text: "fmp消耗走势",
left: "left",
},
legend: {
data: ["fmpCount-line", "fmpCount-bar"],
},
xAxis: [
{
type: "category",
data: [
"2024-05-15",
"2024-05-16",
"2024-05-17",
"2024-05-18",
"2024-05-19",
"2024-05-20",
],
axisPointer: {
type: "shadow",
},
},
],
yAxis: [
{
type: "value",
name: "fmpCount-bar",
min: 0,
max: 250,
interval: 50,
axisLabel: {
formatter: "{value} ms",
},
},
{
type: "value",
name: "fmpCount-line",
min: 0,
max: 250,
interval: 50,
axisLabel: {
formatter: "{value} ms",
},
},
],
series: [
{
name: "fmpCount-bar",
type: "bar",
tooltip: {
valueFormatter: function (value: any) {
return value + " ms";
},
},
data: [2.0, 4.9, 7.0, 23.2, 25.6, 76.7, 135.6, 162.2, 32.6, 20.0, 6.4, 3.3],
},
{
name: "fmpCount-line",
type: "line",
yAxisIndex: 1,
tooltip: {
valueFormatter: function (value: any) {
return value + " ms";
},
},
data: [2.0, 4.9, 7.0, 23.2, 25.6, 76.7, 135.6, 162.2, 32.6, 20.0, 6.4, 3.3],
},
],
};
console.log("option", option);
// 使用刚指定的配置项和数据显示图表。
myChart.setOption(option, true);
// 监听
state.echartLineInstance = myChart;
myChart.on("click", function (params: any) {
console.log("params", params);
});
window.onresize = myChart.resize;
performance.mark("render-line-echart-end");
const measure = performance.measure(
"render-echart-duration",
"render-echart-start",
"render-line-echart-end"
);
console.log("measure", measure);
console.log("measure type", typeof measure);
const duration = measure.duration;
console.log("duration", duration);
state.echarLineDuration = duration;
}
function renderMapChart() {
// 基于准备好的dom,初始化echarts实例
const domInstance = document.getElementById(state.mapId);
if (domInstance) {
domInstance.removeAttribute("_echarts_instance_");
} else {
return;
}
console.log("domInstance", domInstance);
const myChart = echarts.init(domInstance);
const option = {
title: {
text: "网络地图分布",
color: "#fff",
},
visualMap: {
min: 0,
max: 50000,
text: ["High", "Low"],
realtime: false,
calculable: true,
inRange: {
color: ["lightskyblue", "yellow", "orangered"],
},
},
geo: {
// 经纬度中心
// center: state.centerLoction,
type: "map",
map: "chinaJson", // 这里的值要和上面registerMap的第一个参数一致
roam: false, // 拖拽
nameProperty: "name",
geoIndex: 1,
aspectScale: 0.75, // 长宽比, 默认值 0.75
// 悬浮标签
label: {
type: "map",
map: "chinaJson", // 这里的值要和上面registerMap的第一个参数一致
// roam: false, // 拖拽
// nameProperty: 'name',
show: true,
color: "#333",
formatter: function (params: any) {
return params.name;
},
// backgroundColor: '#546de5',
align: "center",
fontSize: 10,
width: (function () {
// let n = parseInt(Math.random() * 10)
return 110;
})(),
height: 50,
shadowColor: "rgba(255,255,255,.7)",
borderRadius: 10,
},
zoom: 1.2,
},
tooltip: {
show: true,
position: ["10%", "10%"],
},
series: [
{
// 放大波纹渲染显示
type: "effectScatter",
zlevel: 3,
showEffectOn: "render",
data: state.locationGis, // 配置散点的坐标数据
coordinateSystem: "geo", // 指明散点使用的坐标系统
rippleEffect: {
// 缩放
scale: 4,
// 涟漪的颜色
color: "rgba(41, 140, 253, 0.3)",
// 波纹数量
number: 2,
// 扩散方式 stroke(线条) fill(区域覆盖)
brushType: "fill",
},
// 形状
symbol: "circle",
},
],
};
console.log("option", option);
// 使用刚指定的配置项和数据显示图表。
myChart.setOption(option, true);
// 监听
state.echartMapInstance = myChart;
myChart.on("click", function (params: any) {
console.log("params", params);
});
window.onresize = myChart.resize;
performance.mark("render-map-echart-end");
const measure = performance.measure(
"render-echart-duration",
"render-echart-start",
"render-map-echart-end"
);
console.log("measure", measure);
console.log("measure type", typeof measure);
const duration = measure.duration;
console.log("duration", duration);
state.echarMaptDuration = duration;
}
const initMapLoc = () => {
let itemData = chinaJson.features;
let length = itemData.length;
for (let loc = 0; loc < length; ++loc) {
let name = itemData[loc].properties.name;
console.log("name", name);
let center = itemData[loc].properties.center;
state.locationGis.push({
value: center,
});
}
console.log(" state.locationGis", state.locationGis);
};
onBeforeMount(() => {
// 标记开始时刻
performance.mark("render-echart-start");
// 注册地图
echarts.registerMap("chinaJson", chinaJson);
// loc
});
onMounted(() => {
console.log("init mounted");
initMapLoc();
renderMapChart();
renderPieChart();
renderLineChart();
});
</script>
<style>
.base-container {
top: 0;
left: 0;
width: 100%;
height: 100%;
background-image: linear-gradient(
0deg,
rgba(41, 140, 253, 0.3) 0px,
rgba(41, 140, 253, 0.3) 1px,
transparent 1px,
transparent 100px
),
linear-gradient(
90deg,
rgba(41, 140, 253, 0.3) 0px,
rgba(41, 140, 253, 0.3) 1px,
transparent 1px,
transparent 100px
);
background-size: 25px 25px;
z-index: -1;
}
</style>
💖数据分布展示效果
页面展示如下
⭐结束
该系列也会同步到个人的掘金博客,本文分享到这结束,如有错误或者不足之处欢迎指出!
👍 点赞,是我创作的动力!
⭐️ 收藏,是我努力的方向!
✏️ 评论,是我进步的财富!
💖 最后,感谢你的阅读!