node+vue3的websocket前后端消息推送

node+vue3的websocket前后端消息推送

前期写web项目时,前端获取数据的方式一般是向后端发起数据请求,然后后端向前端发送数据,然后对数据进行渲染,这是最常规的一种数据通讯方式,适用于绝大部分前后端分离的项目

实际上还有一种场景,需要后端向前端主动发送数据,然后前端更新视图,这种一般出现在数据大屏显示项目中。后端每间隔一段时间向前端发送数据,然后驱动前端更新页面,使用的技术是websocket,研究了一段时间,算搞明白是怎么回事了

先看看最终前端的展示效果

动画

一、后端使用websocket

这里我用的后端是node,服务框架用的是express

首先安装express和ws,我的安装包信息如下:

{
  "name": "api",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "express": "^4.19.2",
    "ws": "^8.16.0"
  }
}

1、websocket服务

新建service/websocket_service.js文件,代码如下

const WebSocket = require("ws");
const { getProvinceData, getBarData } = require("../utils/getData");
const wss = new WebSocket.Server({ port: 9998 });

module.exports.listen = () => {
  wss.on("connection", (ws, req) => {
    console.log("WebSocket client connected");
    ws.on("close", () => {
      console.log("WebSocket client disconnected");
    });

    ws.on("message", (data) => {
      console.log(`Received message from client: ${data}`);
      if (data == "map") {
        ws.send(
          JSON.stringify({
            data: getProvinceData(),
          })
        );
      }
      if (data == "bar") {
        ws.send(
          JSON.stringify({
            data: getBarData(),
          })
        );
      }
    });
  });
};

这个文件是websocket的关键,后面会在后端入口文件app.js中引用。websocket连接成功后,会监听连接事件、关闭事件、接收消息事件,接收消息事件中,根据前端发送过来的字段,向前端推送不同的数据。正常情况,这里应该是从数据库取数据,而非随机数据,数据库取数据的逻辑写过无数遍,换一下代码就行了。

此外,还引用了一个随机获取数据的文件getData.js,

代码如下:

const province = [
  { name: "山东", value: 2706 },
  { name: "江苏", value: 2321 },
  { name: "广东", value: 2108 },
  { name: "河南", value: 2081 },
  { name: "四川", value: 1878 },
  { name: "湖南", value: 1760 },
  { name: "河北", value: 1659 },
  { name: "湖北", value: 1641 },
  { name: "安徽", value: 1411 },
  { name: "江西", value: 1340 },
  { name: "云南", value: 1195 },
  { name: "广西", value: 1189 },
  { name: "贵州", value: 1135 },
  { name: "辽宁", value: 1068 },
  { name: "浙江", value: 1030 },
  {
    name: "陕西",
    value: 727,
  },
  {
    name: "山西",
    value: 720,
  },
  {
    name: "福建",
    value: 714,
  },
  {
    name: "重庆",
    value: 589,
  },
  {
    name: "甘肃",
    value: 522,
  },
  {
    name: "内蒙古",
    value: 485,
  },
  {
    name: "吉林",
    value: 458,
  },
  {
    name: "黑龙江",
    value: 452,
  },
  {
    name: "新疆",
    value: 445,
  },
  {
    name: "上海",
    value: 267,
  },
  {
    name: "海南",
    value: 216,
  },
  {
    name: "北京",
    value: 213,
  },
  {
    name: "天津",
    value: 204,
  },
  {
    name: "宁夏",
    value: 171,
  },
  {
    name: "青海",
    value: 121,
  },
  { name: "西藏", value: 85 },
];

exports.getProvinceData = () => {
  const data = [];
  province.forEach((item) => {
    data.push({
      name: item.name,
      value: Math.floor(Math.random() * (1000 - 100 + 1)) + 100,
    });
  });
  return data
};

exports.getBarData = () => {
  const data = []
  for (let i = 0; i < 7; i++){
    data.push(Math.floor(Math.random() * (1000 - 100 + 1)) + 100)
  }
  return data
}

2、项目入口

也就是app.js,代码如下

const express = require("express");
const WebSocketService = require("./service/websocket_service");

const app = express();

app.listen(3019, () => {
  console.log("Server is running on port 3087");
});

// 开启服务端监听
// 当客户端连接后,就会监听
WebSocketService.listen()

除了常规启动express服务外,还要引入上面写好的websocket文件,并调用方法监听

后端就这些了

二、前端接收数据并展示

我用vite创建了一个基础的vue3项目,在App.vue组件上写大屏的代码

App.vue

<script setup>
import ChinaMap from '@/components/ChinaMap.vue'
import BarChart from '@/components/BarChart.vue'
</script>

<template>
  <div class="charts">
    <div class="chart first">
      <BarChart />
    </div>
    <div class="chart second">
      <ChinaMap />
    </div>
    <div class="chart third">
      <BarChart />

    </div>
    <div class="chart fourth">
      <BarChart />

    </div>
    <div class="chart fifth">
      <BarChart />

    </div>
    <div class="chart sixth">
      <BarChart />
    </div>
    <div class="chart seventh">
      <BarChart />

    </div>
    <div class="chart eighth">
      <BarChart />

    </div>
    <div class="chart ninth">
      <BarChart />

    </div>
  </div>
</template>

<style lang="scss" scoped>
.charts {
  height: 100vh;
  width: 100%;
  display: grid;
  gap: 10px;
  padding: 10px;
  grid-template-columns: repeat(4, 1fr);
  grid-template-rows: repeat(3, 1fr);

  .chart {
    box-shadow: rgba(6, 24, 44, 0.4) 0px 0px 0px 2px, rgba(6, 24, 44, 0.65) 0px 4px 6px -1px, rgba(255, 255, 255, 0.08) 0px 1px 0px inset;
  }

  .first {
    grid-row: 1/2;
    grid-column: 1/2;
  }

  .second {
    grid-row: 1/3;
    grid-column: 2/4;
  }

  .third {
    grid-row: 1/2;
    grid-column: 4/5;
  }

  .fourth {
    grid-row: 2/3;
    grid-column: 1/2;
  }

  .fifth {
    grid-row: 2/3;
    grid-column: 4/5;
  }

  .sixth {
    grid-row: 3/4;
    grid-column: 1/2;
  }

  .seventh {
    grid-row: 3/4;
    grid-column: 2/3;
  }

  .eighth {
    grid-row: 3/4;
    grid-column: 3/4;
  }

  .ninth {
    grid-row: 3/4;
    grid-column: 4/5;
  }
}
</style>

使用了grid布局,有个点需要注意一下

写外面包裹的容器时,一定要算好横向和竖向的子容器个数,也就是要写下面两行代码

  grid-template-columns: repeat(4, 1fr);
  grid-template-rows: repeat(3, 1fr);

表明行3行4列,不写的话,容器的宽高一直会随意变化,grid布局参考我的另一篇博客

根组件中还引入了俩画图的组件,这里我直接上代码了

/components/ChinaMap.vue

<template>
    <div class="chart" ref="chartRef">

    </div>
</template>

<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
import * as echarts from "echarts";
import "@/assets/json/china";
let ws = new WebSocket('ws://localhost:9998');
const chartData = ref([
    { name: "山东", value: 2706 },
    ])

let interval = null
const handleOpen = () => {
    console.log('WebSocket connection established');
    interval = setInterval(sendMessage, 5000)
}

const handleClose = () => {
    console.log('WebSocket connection closed');
    reLink()
}

const handleMessage = (event) => {
    const { data } = JSON.parse(event.data);
    chartData.value = data
    drawChart()
}

const handleError = () => {
    console.log('WebSocket connection error');
}

ws.addEventListener('open', handleOpen)
ws.addEventListener('close', handleClose)
ws.addEventListener('message', handleMessage)
ws.addEventListener('error', handleError)
// 重连
let timer = null
const reLink = () => {
    console.log('connecting failed, preparing relink...')
    const relinkTime = 1000
    timer = setInterval(() => {
        console.log('relinking....')
        ws = new WebSocket('ws://localhost:9998')
        if (ws.readyState === 0) {
            clearInterval(timer)
            timer = null
            ws.addEventListener('open', handleOpen)
            ws.addEventListener('close', handleClose)
            ws.addEventListener('message', handleMessage)
            ws.addEventListener('error', handleError)
        } else {
            relinkTime = relinkTime + 5000
        }
    }, relinkTime)
}

const sendMessage = () => {
    ws.send('map')
}


const chartRef = ref(null)
let initChart
const drawChart = () => {
    if (initChart != null && initChart != "" && initChart != undefined) {
        initChart.dispose(); //销毁
    }
    initChart = echarts.init(chartRef.value)
    initChart.setOption({
        backgroundColor: "transparent", // 设置背景色透明
        // 必须设置
        tooltip: {
            show: false,
        },

        visualMap: {
            text: ["", ""],
            showLabel: true,
            left: "100",
            bottom: "100",
            min: 0,
            max: 1000,
            inRange: {
                color: ["#edfbfb", "#b7d6f3", "#40a9ed", "#3598c1", "#215096"],
            },
            // splitNumber: 5,
            seriesIndex: "0",
        },
        series: [
            // 地图配置
            {
                name: "数值",
                type: "map",
                map: "china",
                zoom: 1,
                tooltip: {
                    show: true,
                    formatter: function (param) {
                        return param.data.name + ": " + param.data.value
                    },
                },
                label: {
                    show: true, // 显示省份名称
                    color: "#000",
                    align: "center",
                },
                top: "10%",
                left: "center",
                aspectScale: 0.75,
                roam: true, // 地图缩放和平移
                itemStyle: {
                    borderColor: "#3ad6ff", // 省分界线颜色  阴影效果的
                    borderWidth: 1,
                    areaColor: "#F5F5F5",
                    opacity: 1,
                },
                // 去除选中状态
                select: {
                    disabled: true,
                },
                // 控制鼠标悬浮上去的效果
                emphasis: {
                    // 聚焦后颜色
                    disabled: false, // 开启高亮
                    label: {
                        align: "center",
                        color: "#ffffff",
                    },
                    itemStyle: {
                        color: "#ffffff",
                        areaColor: "#0075f4", // 阴影效果 鼠标移动上去的颜色
                    },
                },
                z: 2,
                data: chartData.value,
            },
        ],
    })
}

onMounted(() => {
    setTimeout(() => {
        drawChart()
    }, 200)
});

onUnmounted(() => {
    ws.close();
    clearInterval(interval)
});
</script>

<style lang="scss" scoped>

</style>

画图的代码不解释了,主要是websocket的使用

现在WebSocket已经是window的内置类了,所以不需要安装啥,直接上来生成示例就行了

websocket需要监听open/close/message/error事件,核心的就是message事件了,此外,还做了断开后重连的操作,逻辑是设置定时器重新连接服务端

注意这里的message事件是指接收服务端传递过来的message事件

**有一点需要注意,websocket只能传递字符串类型数据,不能传递对象,**所以后端在使用ws.send时,需要使用JSON.stringify转换格式,前端在接收时也需要使用JSON.parse解码

BarChart.vue中的代码

<template>
    <div class="chart" ref="chartRef">

    </div>
</template>

<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
import * as echarts from "echarts";

const chartData = ref([120, 200, 150, 80, 70, 110, 130])

let ws = new WebSocket('ws://localhost:9998');
let interval = null
const handleOpen = () => {
    console.log('WebSocket connection established');
    interval = setInterval(sendMessage, 5000)
}

const handleClose = () => {
    console.log('WebSocket connection closed');
    reLink()
}

const handleMessage = (event) => {
    const { data } = JSON.parse(event.data);
    // console.log(data)
    chartData.value = data
    drawChart()
}

const handleError = () => {
    console.log('WebSocket connection error');
}

ws.addEventListener('open', handleOpen)
ws.addEventListener('close', handleClose)
ws.addEventListener('message', handleMessage)
ws.addEventListener('error', handleError)
// 重连
let timer = null
const reLink = () => {
    console.log('connecting failed, preparing relink...')
    const relinkTime = 1000
    timer = setInterval(() => {
        console.log('relinking....')
        ws = new WebSocket('ws://localhost:9998')
        if (ws.readyState === 0) {
            clearInterval(timer)
            timer = null
            ws.addEventListener('open', handleOpen)
            ws.addEventListener('close', handleClose)
            ws.addEventListener('message', handleMessage)
            ws.addEventListener('error', handleError)
        } else {
            relinkTime = relinkTime + 5000
        }
    }, relinkTime)
}

const sendMessage = () => {
    ws.send('bar')
}

const chartRef = ref(null)
let initChart
const drawChart = () => {
    if (initChart != null && initChart != "" && initChart != undefined) {
        initChart.dispose(); //销毁
    }
    initChart = echarts.init(chartRef.value)
    initChart.setOption({
        xAxis: {
            type: 'category',
            data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
        },
        yAxis: {
            type: 'value'
        },
        series: [
            {
                data: chartData.value,
                type: 'bar'
            }
        ]
    })
}
onMounted(() => {
    setTimeout(() => {
        drawChart()
    }, 200)
});

onUnmounted(() => {
    ws.close();
    clearInterval(interval)
});
</script>

<style lang="scss" scoped></style>

逻辑差不多,不再解释了,最好的做法是把重复的代码放到hooks中去

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/548772.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

java的ConcurrentHashMap深入理解

概要 怎么保证线程安全&#xff1a; 在初始化数组时用了cas操作&#xff0c;sizectl作为占位标志(U.compareAndSwapInt(this, SIZECTL, sc, -1&#xff09;&#xff1b;获取数组中的元素是否已经有了&#xff0c;用Volatile修饰数组&#xff08;保证可见性&#xff09;&#…

边缘计算网关有哪些优势?-天拓四方

随着信息化、智能化浪潮的持续推进&#xff0c;计算技术正以前所未有的速度发展&#xff0c;而边缘计算网关作为其中的重要一环&#xff0c;以其独特的优势正在逐步改变我们的生活方式和工作模式。本文将详细解析边缘计算网关的优势。 首先&#xff0c;边缘计算网关具有显著的…

【好书推荐6】《Excel函数与公式应用大全for Excel 365 Excel 2021》

【好书推荐6】《Excel函数与公式应用大全for Excel 365 & Excel 2021》 写在最前面《Excel函数与公式应用大全for Excel 365 & Excel 2021》关键点内容简介作者简介前言/序言目录 &#x1f308;你好呀&#xff01;我是 是Yu欸 &#x1f30c; 2024每日百字篆刻时光&…

Linux之命令行参数的原理以及实现,环境变量限时增加删除和永久增加删除以及代码获取环境变量

个人主页&#xff1a;点我进入主页 专栏分类&#xff1a;C语言初阶 C语言进阶 数据结构初阶 Linux C初阶 算法 欢迎大家点赞&#xff0c;评论&#xff0c;收藏。 一起努力&#xff0c;一起奔赴大厂 一.命令行参数 1.1main函数参数 在我们学习c语言时我们的main函数…

Vue - 5( 16000 字 Vue2 入门级教程)

一&#xff1a;Vue 初阶 1.1 组件自定义事件 在 Vue 中&#xff0c;组件间通过自定义事件进行通信是一种常见的模式。自定义事件允许子组件向父组件发送消息&#xff0c;也可以在组件内部进行事件的绑定、触发和解绑。让我们详细讲解这些知识点。 1.1.1 组件自定义事件 在 …

最前沿・量子退火建模方法(2) : Domain wall encoding讲解和python实现

前言 上篇讲的subQUBO属于方法论&#xff0c;这次讲个通过编码量子比特的方式&#xff0c;同样的约束条件&#xff0c;不同的编码&#xff0c;所需的量子比特数是不同的。有的编码方式&#xff0c;很节省量子比特。比如&#xff0c;这次要讲的Domain wall encoding。 一、Doma…

一文总结:Python的封装、继承和多态

整个程序员的生涯&#xff0c;最重要的一个知识根基就是面向对象的理解和掌握深度。如果你意识到了面向对象开发思想的重要性&#xff0c;请仔细学习这篇文章。 希望对你有帮助&#xff01; 这篇详细地解释封装、继承和多态&#xff0c;并在最后提供一个综合示例来总结这三个…

PyQt介绍——弹框介绍和使用

PyQt介绍——弹框介绍和使用 一、QMessageBox QMessageBox是一种通用的弹出式对话框&#xff0c;用于显示消息&#xff0c;允许用户通过单击不同的标准按钮对消息进行反馈 QMessageBox类提供了许多常用的弹出式对话框&#xff0c;如提示、警告、错误、询问、关于等对话框。这…

Hyper-v 新建 Windows 虚拟机卡在“Press any key to boot from CD or DVD...,无法按下任何按键

Hyper-v 新建 Windows 虚拟机卡在“Press any key to boot from CD or DVD…&#xff0c;无法按下任何按键 在显示这个界面之后点击启动&#xff0c;之后立刻狂按F2&#xff0c; 然后就能进去了

​代码混淆的原理是什么?常见代码混淆方法介绍

本文主要想你介绍代码混淆的原理&#xff0c;常见代码混淆方法&#xff0c;欢迎查阅~ 移动应用代码安全非常重要&#xff0c;代码逆向会导致代码逻辑被获取&#xff0c;进一步导致控制流被hook&#xff0c;安全防线被破&#xff0c;给APP安全带来巨大风险&#xff0c;因此开发者…

淘宝扭蛋机小程序:扭转购物新风尚,开启惊喜连连之旅

随着移动互联网的飞速发展&#xff0c;淘宝作为国内领先的电商平台&#xff0c;始终致力于为用户带来更加新颖、有趣的购物体验。如今&#xff0c;我们隆重推出淘宝扭蛋机小程序&#xff0c;将传统扭蛋机的乐趣与电商购物的便捷完美结合&#xff0c;为用户带来前所未有的惊喜与…

PyTorch构建自然语言处理模型

一、整体流程 二、 详细步骤 1. 准备数据 在构建自然语言处理模型之前&#xff0c;首先需要准备数据。可以使用PyTorch提供的Dataset和DataLoader类来加载和处理数据。 # 导入必要的库 import torch from torch.utils.data import Dataset, DataLoader # 定义自定义Dataset类…

Linux进程控制篇

1. 进程创建 fork()函数创建一个进程&#xff1a; 父进程返回子进程的pid子进程返回0 创建进程后&#xff0c;我们希望子进程做的事有两种&#xff1a; 子进程帮父进程完成同样的工作子进程干其他任务 进程 内核的相关管理数据结构(tack_struct mm_struct 页表) 代码和数据…

python零基础入门笔记【源源老师】

1. print() 输出 &#xff08;1&#xff09;认识英文单词 print: 输出&#xff08;2&#xff09;print() 的作用 print()&#xff1a;用于在控制台上输出你想要的内容。 &#xff08;3&#xff09;代码演示 举例1&#xff1a; 【注意&#xff1a;】用双引号包裹的&#xff0…

OpenHarmony开发实例:【新闻客户端】

介绍 本篇Codelab我们将教会大家如何构建一个简易的OpenHarmony新闻客户端&#xff08;JS版本&#xff09;。应用包含两级页面&#xff0c;分别是主页面和详情页面&#xff0c;两个页面都展示了丰富的UI组件&#xff0c;其中详情页的实现逻辑中还展示了如何通过调用相应接口&a…

交友盲盒1.4.5

本文来自&#xff1a;微擎交友盲盒1.4.5 - 源码1688 应用介绍 品牌其他语言PHP数据库Mysql移动端Wap自适应公众服务号大小80 MB规格整站源码授权免授权源文件完全开源&#xff08;含全部源文件&#xff09;伪静态需要操作系统Windows&#xff0c;Linux安装方式QQ远程协助web服…

客户资料不翼而飞?企业数据保护攻略

在数字化经济时代&#xff0c;企业的客户资料等同于商业生命线&#xff0c;一旦泄露&#xff0c;后果不堪设想。例如&#xff0c;2017年Equifax的数据泄露事件&#xff0c;造成超过1.4亿用户的个人信息外泄&#xff0c;不仅给用户带来风险&#xff0c;也让公司名誉受损&#xf…

IDC发布2023年中国整体超融合市场报告,深信服第一

4月15日&#xff0c;IDC发布了《中国软件定义存储 (SDS)及超融合存储系统 (HCI)市场季度跟踪报告&#xff0c;2023年第四季度》。 报告显示&#xff0c;中国超融合市场在2023年较去年同期实现2.9%增长&#xff0c;其中HCI 验证系统市场占有率较去年同期上升近4%&#xff0c;接近…

Day01-环境准备与镜像案例

Day01-环境准备与镜像案例 1. 容器架构1.1 Iaas Paas Saas (了解)1.2 什么是容器1.3 容器vs虚拟机1.4 Docker极速上手指南1&#xff09;配置docker源(用于安装docker)2&#xff09;docker下载镜像加速的配置3&#xff09;自动补全 1.5 Docker C/S架构1.6 Docker的镜像管理1&…

MySQL基础-----约束详解

目录 一. 概述: 二.约束演示&#xff1a; 三.外键约束&#xff1a; 3.1介绍&#xff1a; 3.2外键约束语法&#xff1a; 3.3删除&#xff0c;更新行为&#xff1a; 一. 概述: &#x1f9d0;&#x1f9d0;概念&#xff1a;约束是作用于表中字段上的规则&#xff0c;用于限制…