性能监控系统搭建——vue3实现性能监控数据展示(第二章)

文章目录

⭐前言

大家好,我是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>

💖数据分布展示效果

页面展示如下

⭐结束

该系列也会同步到个人的掘金博客,本文分享到这结束,如有错误或者不足之处欢迎指出!

👍 点赞,是我创作的动力!

⭐️ 收藏,是我努力的方向!

✏️ 评论,是我进步的财富!

💖 最后,感谢你的阅读!

相关推荐
逆旅行天涯26 分钟前
【Threejs】从零开始(六)--GUI调试开发3D效果
前端·javascript·3d
长风清留扬1 小时前
小程序毕业设计-音乐播放器+源码(可播放)下载即用
javascript·小程序·毕业设计·课程设计·毕设·音乐播放器
m0_748247801 小时前
Flutter Intl包使用指南:实现国际化和本地化
前端·javascript·flutter
漫天转悠1 小时前
VScode中配置ESlint+Prettier详细步骤(图文详情)
vscode·vue
ZJ_.2 小时前
WPSJS:让 WPS 办公与 JavaScript 完美联动
开发语言·前端·javascript·vscode·ecmascript·wps
joan_852 小时前
layui表格templet图片渲染--模板字符串和字符串拼接
前端·javascript·layui
还是大剑师兰特3 小时前
什么是尾调用,使用尾调用有什么好处?
javascript·大剑师·尾调用
Watermelo6173 小时前
详解js柯里化原理及用法,探究柯里化在Redux Selector 的场景模拟、构建复杂的数据流管道、优化深度嵌套函数中的精妙应用
开发语言·前端·javascript·算法·数据挖掘·数据分析·ecmascript
一个处女座的程序猿O(∩_∩)O5 小时前
小型 Vue 项目,该不该用 Pinia 、Vuex呢?
前端·javascript·vue.js
燃先生._.11 小时前
Day-03 Vue(生命周期、生命周期钩子八个函数、工程化开发和脚手架、组件化开发、根组件、局部注册和全局注册的步骤)
前端·javascript·vue.js