SocketIO介绍+SpringBoot整合SocketIO完成实时通信

news2024/11/18 20:02:11

Socket.IO笔记

即时通信是基于TCP长连接,建立连接之后,客户段/服务器可以无限次随时向对端发送数据,实现服务器数据发送的即时性

HTTP

HTTP是短链接,设计的目的是减少服务器的压力

HTTP伪即时通讯

  • 轮询 emmet
  • 长轮询 long pulling
    请添加图片描述

应用场景

  • 聊天功能
  • 在线即时推送 如下单后立即推送给商户

实现即时通讯

  • 自己搭建服务器
    • 选择支持的协议 websocket xmpp
    • 使用一些比较成熟的框架 socketio xmppframework
    • 自己封装socket
  • 使用成熟的第三方方案
    • 融云 环信

Websocket

websocket和http都是基于Tcp,不同在于HTTP建立的是短链接,而websocket建立的是长连接

缺点是websocket仅能支持部分平台

请添加图片描述

Socket.IO

Socket.io是基于WebSocket协议的一套成熟的解决方案,可以在浏览器和服务器之间实现实时,双向和基于事件的通信,SocketIO将WebSocket、AJAX和其它的通信方式全部封装成了统一的通信接口,也就是说,我们在使用SocketIO时,不用担心兼容问题,底层会自动选用最佳的通信方式。

优点

  • 易用性
  • 跨平台
  • 自适应

缺点

  • 传输的数据并不完全遵循webSocket协议,这就要求客户端和服务器端都必须使用socket.io解决方案

SpringBoot整合SocketIO实现实时推送

目录结构如下

请添加图片描述

1. pom.xml

        <dependency>
            <groupId>com.corundumstudio.socketio</groupId>
            <artifactId>netty-socketio</artifactId>
            <version>1.7.11</version>
        </dependency>

2.application.properties

server.port=8080
server.servlet.context-path=/
spring.mvc.view.prefix=/templates
spring.mvc.view.suffix=.html

#============================================================================
# netty socket io setting
#============================================================================
# host在本地测试可以设置为localhost或者本机IP,在Linux服务器跑可换成服务器IP
socketio.host=localhost
socketio.port=9092
# 设置最大每帧处理数据的长度,防止他人利用大数据来攻击服务器
socketio.maxFramePayloadLength=1048576
# 设置http交互最大内容长度
socketio.maxHttpContentLength=1048576
# socket连接数大小(如只监听一个端口boss线程组为1即可)
socketio.bossCount=1
socketio.workCount=100
socketio.allowCustomRequests=true
# 协议升级超时时间(毫秒),默认10秒。HTTP握手升级为ws协议超时时间
socketio.upgradeTimeout=1000000
# Ping消息超时时间(毫秒),默认60秒,这个时间间隔内没有接收到心跳消息就会发送超时事件
socketio.pingTimeout=6000000
# Ping消息间隔(毫秒),默认25秒。客户端向服务器发送一条心跳消息间隔
socketio.pingInterval=25000

3.创建辅助类

3.1创建通道连接缓存类

package com.erha.socketio.cache;

import com.corundumstudio.socketio.SocketIOClient;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @ClassNameClientCache
 * @Description TODO 缓存用户 - 页面sessionId - 通道连接
 * @Author DELL
 * @Date 2022/1/2113:55
 * @Version 1.0
 **/
@Component
public class ClientCache {
    /**
     * @Author 二哈老头子
     * @Description //TODO 用户信息缓存
     * @Date 14:00 2022/1/21
     * @Param
     * @return
     **/
    private static Map<String, HashMap<UUID, SocketIOClient>> concurrentHashMap = new ConcurrentHashMap<>();

    /**
     * @Author 二哈老头子
     * @Description //TODO userId-用户ID | sessionId-页面sessionId | socketIOClient-页面对应的通道连接
     * @Date 14:03 2022/1/21
     * @Param [userId, sessionId, socketIOClient]
     * @return void
     **/
    public void saveClient(String userId,UUID sessionId,SocketIOClient socketIOClient){
        HashMap<UUID, SocketIOClient> sessionIdClientCache = concurrentHashMap.get(userId);
        if(sessionIdClientCache == null){
            sessionIdClientCache = new HashMap<>();
        }
        sessionIdClientCache.put(sessionId,socketIOClient);
        concurrentHashMap.put(userId,sessionIdClientCache);
    }

    /**
     * @Author 二哈老头子
     * @Description //TODO 获取用户的页面通道信息
     * @Date 14:12 2022/1/21
     * @Param [userId]
     * @return java.util.HashMap<java.util.UUID,com.corundumstudio.socketio.SocketIOClient>
     **/
    public HashMap<UUID,SocketIOClient> getUserClient(String userId){
        return concurrentHashMap.get(userId);
    }

    /**
     * @Author 二哈老头子
     * @Description //TODO 根据用户Id及页面sessionID删除页面通道连接
     * @Date 14:14 2022/1/21
     * @Param [userId, sessionId]
     * @return void
     **/
    public void deleteSessionClientByUserId(String userId,UUID sessionId){
        concurrentHashMap.get(userId).remove(sessionId);
    }

    /**
     * @Author 二哈老头子
     * @Description //TODO 根据用户Id删除用户通道连接缓存 暂无使用
     * @Date 14:19 2022/1/21
     * @Param [userId]
     * @return void
     **/
    public void deleteUserCacheByUserId(String userId){
        concurrentHashMap.remove(userId);
    }
}

3.2创建消息类

package com.erha.socketio.pojo;

import org.springframework.stereotype.Component;

/**
 * @ClassNameMessageInfo
 * @Description TODO
 * @Author DELL
 * @Date 2022/1/2114:36
 * @Version 1.0
 **/
@Component
public class MessageInfo {
    private String userID;
    private String userName;
    private String message;

    public MessageInfo() {
    }

    public MessageInfo(String userID, String userName, String message) {
        this.userID = userID;
        this.userName = userName;
        this.message = message;
    }

    public MessageInfo(String userName, String message) {
        this.userName = userName;
        this.message = message;
    }

    @Override
    public String toString() {
        return "MessageInfo{" +
                "userID='" + userID + ''' +
                ", userName='" + userName + ''' +
                ", message='" + message + ''' +
                '}';
    }

    public String getUserID() {
        return userID;
    }

    public void setUserID(String userID) {
        this.userID = userID;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }
}

4.创建SocketIO配置类

package com.erha.socketio.config;

import com.corundumstudio.socketio.SocketConfig;
import com.corundumstudio.socketio.SocketIOServer;
import com.erha.socketio.handler.SocketIOHandler;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;

import javax.annotation.Resource;

/**
 * @ClassNameSocketIOConfig
 * @Description TODO
 * @Author DELL
 * @Date 2022/1/2114:19
 * @Version 1.0
 **/
@Configuration
public class SocketIOConfig implements InitializingBean {

    @Resource
    private SocketIOHandler socketIOHandler;

    @Value("${socketio.host}")
    private String host;

    @Value("${socketio.port}")
    private Integer port;

    @Value("${socketio.bossCount}")
    private int bossCount;

    @Value("${socketio.workCount}")
    private int workCount;

    @Value("${socketio.allowCustomRequests}")
    private boolean allowCustomRequests;

    @Value("${socketio.upgradeTimeout}")
    private int upgradeTimeout;

    @Value("${socketio.pingTimeout}")
    private int pingTimeout;

    @Value("${socketio.pingInterval}")
    private int pingInterval;


    @Override
    public void afterPropertiesSet() throws Exception {
        SocketConfig socketConfig = new SocketConfig();
        socketConfig.setReuseAddress(true);
        socketConfig.setTcpNoDelay(true);
        socketConfig.setSoLinger(0);

        com.corundumstudio.socketio.Configuration configuration = new com.corundumstudio.socketio.Configuration();
        configuration.setSocketConfig(socketConfig);
        // host在本地测试可以设置为localhost或者本机IP,在Linux服务器跑可换成服务器IP
        configuration.setHostname(host);
        configuration.setPort(port);
        // socket连接数大小(如只监听一个端口boss线程组为1即可)
        configuration.setBossThreads(bossCount);
        configuration.setWorkerThreads(workCount);
        configuration.setAllowCustomRequests(allowCustomRequests);
        // 协议升级超时时间(毫秒),默认10秒。HTTP握手升级为ws协议超时时间
        configuration.setUpgradeTimeout(upgradeTimeout);
        // Ping消息超时时间(毫秒),默认60秒,这个时间间隔内没有接收到心跳消息就会发送超时事件
        configuration.setPingTimeout(pingTimeout);
        // Ping消息间隔(毫秒),默认25秒。客户端向服务器发送一条心跳消息间隔
        configuration.setPingInterval(pingInterval);

        SocketIOServer socketIOServer = new SocketIOServer(configuration);
        //添加事件监听器
        socketIOServer.addListeners(socketIOHandler);
        //启动SocketIOServer
        socketIOServer.start();
        System.out.println("SocketIO启动完毕");
    }
}

5.SocketIO事件响应类

package com.erha.socketio.handler;

import com.corundumstudio.socketio.AckRequest;
import com.corundumstudio.socketio.SocketIOClient;
import com.corundumstudio.socketio.annotation.OnConnect;
import com.corundumstudio.socketio.annotation.OnDisconnect;
import com.corundumstudio.socketio.annotation.OnEvent;
import com.erha.socketio.cache.ClientCache;
import com.erha.socketio.pojo.MessageInfo;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.UUID;

/**
 * @ClassNameSocketIOHandler
 * @Description TODO
 * @Author DELL
 * @Date 2022/1/2114:29
 * @Version 1.0
 **/
@Component
public class SocketIOHandler {
    @Resource
    private ClientCache clientCache;

    /**
     * @Author 二哈老头子
     * @Description //TODO 客户端连接的时候触发,前端js触发:socket = io.connect("http://localhost:9092");
     * @Date 12:12 2022/1/20
     * @Param [client]
     * @return void
     **/
    @OnConnect
    public void onConnect(SocketIOClient client){
        String userId = client.getHandshakeData().getSingleUrlParam("userId");
        UUID sessionId = client.getSessionId();
        clientCache.saveClient(userId,sessionId, client);
        System.out.println("userId: "+userId+"连接建立成功 - "+sessionId);
    }

    /**
     * @Author 二哈老头子
     * @Description //TODO 客户端关闭连接时触发:前端js触发:socket.disconnect();
     * @Date 12:14 2022/1/20
     * @Param [client]
     * @return void
     **/
    @OnDisconnect
    public void onDisconnect(SocketIOClient client){
        String userId = client.getHandshakeData().getSingleUrlParam("userId");
        UUID sessionId = client.getSessionId();
        clientCache.deleteSessionClientByUserId(userId,sessionId);
        System.out.println("userId: "+userId+"连接关闭成功 - "+sessionId);
    }

    /**
     * @Author 二哈老头子
     * @Description
     *  //TODO 自定义消息事件,客户端js触发:socket.emit('messageevent', {msgContent: msg});时触发该方法
     *  //TODO 前端js的 socket.emit("事件名","参数数据")方法,是触发后端自定义消息事件的时候使用的
     *  //TODO 前端js的 socket.on("事件名",匿名函数(服务器向客户端发送的数据))为监听服务器端的事件
     * @Date 13:51 2022/1/20
     * @Param [client, request, data]
     * @return void
     **/
    @OnEvent("chatevent")
    public void chatEvent(SocketIOClient client, AckRequest ackRequest, MessageInfo message){
        HashMap<UUID, SocketIOClient> userClient = clientCache.getUserClient("79");
        Iterator<Map.Entry<UUID, SocketIOClient>> iterator = userClient.entrySet().iterator();
        while(iterator.hasNext()){
            Map.Entry<UUID, SocketIOClient> next = iterator.next();
            next.getValue().sendEvent("chatevent", message);
        }
        System.out.println(message);
    }
}

6.控制层类Controller

6.1页面跳转基础类

@Controller
public class baseController {
    @RequestMapping(value = "/")
    public String index(){
        return "index";
    }
}

6.2页面根据通道连接发送信息测试类

@RestController
@RequestMapping("/push")
public class PushController {
    @Resource
    private ClientCache clientCache;

    @GetMapping("/user/{userId}")
    public String pushTuUser(@PathVariable("userId") String userId){
        HashMap<UUID, SocketIOClient> userClient = clientCache.getUserClient(userId);
        userClient.forEach((uuid, socketIOClient) -> {
            //向客户端推送消息
            socketIOClient.sendEvent("chatevent",new MessageInfo("管理员","向客户段发送的消息"));
        });
        return "success";
    }
}

7.前端页面

前端页面需要引入socket.io.js 的js包

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>Demo Chat</title>
    <link href="css/bootstrap.css" rel="stylesheet">
    <style>
        body {
            padding:20px;
        }
        #console {
            height: 400px;
            overflow: auto;
        }
        .username-msg {color:orange;}
        .connect-msg {color:green;}
        .disconnect-msg {color:red;}
        .send-msg {color:#888}
    </style>
    <script src="js/socket.io/socket.io.js"></script>
    <script src="js/moment.min.js"></script>
    <script src="http://code.jquery.com/jquery-1.10.1.min.js"></script>
    <script>
        var userName = 'user' + Math.floor((Math.random()*1000)+1);
        
        //创建通道连接
        var socket =  io.connect('http://localhost:9092?userId=79');

        socket.on('connect', function() {
            output('<span class="connect-msg">Client has connected to the server!</span>');
        });
        
        socket.on('chatevent', function(message) {
            output('<span class="username-msg">' + message.userName + ':</span> ' + message.message);
        });

        socket.on('disconnect', function() {
            output('<span class="disconnect-msg">The client has disconnected!</span>');
        });

        //关闭通道连接 可被@OnDisconnect注解的方法监听
        function sendDisconnect() {
            socket.disconnect();
        }

        function sendMessage() {
            var message = $('#msg').val();
            $('#msg').val('');
            var jsonObject = {
                userName: userName,
                message: message
            };
            //发往后端@OnEvent("chatevent")注解的方法
            socket.emit('chatevent', jsonObject);
        }

        function output(message) {
            var currentTime = "<span class='time'>" +  moment().format('HH:mm:ss.SSS') + "</span>";
            var element = $("<div>" + currentTime + " " + message + "</div>");
            $('#console').prepend(element);
        }

        $(document).keydown(function(e){
            if(e.keyCode == 13) {
                $('#send').click();
            }
        });
    </script>
</head>

<body>

<h1>Netty-socketio Demo Chat</h1>
<br/>
<div id="console" class="well">
</div>
<form class="well form-inline" onsubmit="return false;">
    <input id="msg" class="input-xlarge" type="text" placeholder="Type something..."/>
    <button type="button" onClick="sendMessage()" class="btn" id="send">Send</button>
    <button type="button" onClick="sendDisconnect()" class="btn">Disconnect</button>
</form>

</body>
</html>

核心代码

创建连接,该语句的执行对应会调用服务端的SocketIO事件相应类中@OnConnect注解的方法

//创建通道连接 
var socket =  io.connect('http://localhost:9092?userId=79');  // --@OnConnect注解的方法

监听代码

//事件监听服务器端监听器的@OnConnect注解方法
socket.on('connect', function() {
    output('<span class="connect-msg">Client has connected to the server!</span>');
});
//事件监听服务器端监听器的@OnDisconnect注解的方法
socket.on('disconnect', function() {
    output('<span class="disconnect-msg">The client has disconnected!</span>');
});

//自定义页面监听事件监听后端发送来的信息 后端页面对应代码 socketIOClient.sendEvent("chatevent",message);
socket.on('chatevent', function(message) {
    output('<span class="username-msg">' + message.userName + ':</span> ' + message.message);
});

//自定义事件格式
socket.on("自定义事件名称",function(message){.....})

功能代码

//关闭通道连接 可被@OnDisconnect注解的方法监听
socket.disconnect();

//发往服务器端 @OnEvent("chatevent")注解的方法
socket.emit('chatevent', message);

效果展示

输入集成链接 http://localhost:8080/ 进入聊天页面

[外链图片转存中...(img-6QZ8Q8Hr-1642756854466)]

后台提示:

[外链图片转存中...(img-zLUD8WwJ-1642756854467)]

输入 http://localhost:8080/push/user/79 先从服务端发送消息

[外链图片转存中...(img-dMxKdyMj-1642756854468)]

页面信息发送

[外链图片转存中...(img-67DkYwU1-1642756854468)]

关闭链接

[外链图片转存中...(img-upT1ohTY-1642756854469)]
[外链图片转存中...(img-2Hia5s0V-1642756854469)]

先自我介绍一下,小编13年上师交大毕业,曾经在小公司待过,去过华为OPPO等大厂,18年进入阿里,直到现在。深知大多数初中级java工程师,想要升技能,往往是需要自己摸索成长或是报班学习,但对于培训机构动则近万元的学费,着实压力不小。自己不成体系的自学效率很低又漫长,而且容易碰到天花板技术停止不前。因此我收集了一份《java开发全套学习资料》送给大家,初衷也很简单,就是希望帮助到想自学又不知道该从何学起的朋友,同时减轻大家的负担。添加下方名片,即可获取全套学习资料哦

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

vue打包之后出现空白页的原因以及解决方式

路由模式 history 新建项目什么都不动&#xff0c;路由模式&#xff1a;history, 直接npm run build打包 打包之后&#xff0c;直接打开dist文件里面的ndex.html可以看到页面是空白的&#xff0c;控制台是这样的。 再看看网页源码, 对比dist文件夹结构可以看到资源路径的引入…

【前端】React使用react-markdown+antd实现引入渲染markdown文件

项目中遇见一个需求&#xff0c;要求直接在浏览器打开markdown文件进行预览&#xff0c;初次使用遇见一些坎坷&#xff0c;以下记录实现过程&#xff0c;将其封装成了一个组件。 1.下载依赖 yarn add react-markdown//其余样式插件&#xff1a; yarn add remark-gfm yarn …

vue+springboot使用文件流实现文件下载

前言 本次将使用vue和springboot通过文件流的方式教大家怎么去实现文件的下载&#xff0c;那么话不多说&#xff0c;我们直接开始 相关vue导出文件 以下链接为我其他vue进行下载导出文件文章 vue实现把字符串导出为txt 步骤 文件路径 要进行下载的话&#xff0c;我们肯定…

WEB核心【案例:文件下载,案例:点击切换验证码,几种获取properties资源方式】第十二章

目录 1.文件下载 1.1超链接下载&#xff1a; 1.2自定义servlet下载 1.3小结 2.点击切换验证码 2.1前置只是-验证码生成 2.2分析及代码实现 2.3需求2&#xff1a;点击切换验证码-绕过缓存 3.几种获取preperties资源方式 &#x1f49f;作者简介&#xff1a;大家好呀&…

CTF中Web题目的各种基础的思路-----入门篇十分的详细

我期间也考察很多人的&#xff0c;但搞这个的确实有点少&#xff0c;希望这篇可以大家一点帮助&#xff0c;这篇文章也借鉴一些人的文章&#xff0c;还有很多东西&#xff0c;我没搞&#xff0c;确实有点麻烦&#xff0c;但以后还会不断更新的&#xff0c;希望大家在web这里少走…

Web系统常见安全漏洞介绍及解决方案-CSRF攻击

&#x1f433;博客主页&#xff1a;拒绝冗余 – 生命不息&#xff0c;折腾不止 &#x1f310;订阅专栏&#xff1a;『Web安全』 &#x1f4f0;如觉得博主文章写的不错或对你有所帮助的话&#xff0c;还望大家多多支持呀&#xff01; &#x1f449;关注✨、点赞&#x1f44d;、收…

CSS基本布局——grid布局

grid布局简介&#xff1a; Grid布局是将容器划分成“行”和“列”&#xff0c;产生单元格&#xff0c;然后指定“项目所在”的单元格&#xff0c;可以看作是二维布局。 基本概念&#xff1a; 容器&#xff08;container&#xff09;——有容器属性项目&#xff08;items&…

vite项目优化

首先在讲述vite优化之前&#xff0c;我们先来分析一下和传统的项目管理构建工具的区别&#xff0c;以webpack为例&#xff0c;它是利用plugin插件和loader加载器对项目的所有模块和依赖统一通过入口文件进行编译&#xff0c;从而变成我们html所需要的js格式渲染我们的页面。 随…

Vue项目目录结构介绍(三)

前言 本章我们会对一个 Vue 项目的目录结构进行讲解&#xff0c;解释各子目录以及文件的作用&#xff0c;前端的模块化&#xff0c;Vue 单文件组件规范等。 1、基础目录和文件介绍 在上一章&#xff0c;我们通过 vue-cli 创建了一个新的项目&#xff0c;生成的项目目录里已经包…

tauri+vite+vue3开发环境下创建、启动运行和打包发布

目录 1.创建项目 2.安装依赖 3.启动项目 4.打包生成windows安装包 5.安装打包生成的安装包 1.创建项目 运行下面命令创建一个tauri项目 pnpm create tauri-app 我创建该项目时的node版本为16.15.0 兼容性注意 Vite 需要 Node.js 版本 14.18&#xff0c;16。然而&#x…

【玩转CSS】这些高级技巧,你都会吗

&#x1f525;一个人走得远了&#xff0c;就会忘记自己为了什么而出发&#xff0c;希望你可以不忘初心&#xff0c;不要随波逐流&#xff0c;一直走下去&#x1f3b6; &#x1f98b; 欢迎关注&#x1f5b1;点赞&#x1f44d;收藏&#x1f31f;留言&#x1f43e; &#x1f984; …

js去掉两个数组相同的元素、js删除数组中某一个对象、js快速查找数组中重复项下标

一、js去掉两个数组相同的元素 注意&#xff1a;这里并非是数组去重&#xff0c;数组去重是去掉一个数组中相同的元素&#xff0c;这里是比较两个数组&#xff0c;过滤掉二者相同的&#xff0c;留下不同的。 通过 some() 在对方数组里面查找相同元素&#xff0c;再利用filter…

老老实实的程序员该如何描述自己的缺点

答辩的时候&#xff0c;晋升的时候&#xff0c;面试的时候&#xff0c;你有没有经常遇到一个问题&#xff0c;那就是你觉得自己有什么缺点吗&#xff1f; 目录 1. 每个人都有缺点 2. 这道题在考什么&#xff1f; 3. 我之前是怎么回答的 4. 你可以这样回答试一试 5. 总结 …

浅析<router-view> v-slot事例

官方关于<router-link> 的 v-slot的相关介绍: https://router.vuejs.org/zh/api/#router-view-%E7%9A%84-v-slot 并给出了一个例子&#xff1a; <router-view v-slot"{ Component, route }"><transition :name"route.meta.transition || fade&q…

selenium驱动Firefox安装和环境配置

目录 一、前言 二、版本 三、配置环境 四、在pycharm中添加selenium 五、测试代码&#xff0c;成功打开百度&#xff0c;则配置成功 一、前言 根据多篇文章总结了一下自己操作过程&#xff0c;主要是想记录一下。 二、版本 1.查看自己的Firefox的版本&#xff0c;在浏览器…

Web视频video自动播放(移动端及PC端)

做了个关于视频播放的活动&#xff0c;被各种问题折腾得精疲力竭。为了日后能够轻松点&#xff0c;特记录下出现的各种问题及解决方法。 活动要适配移动端&#xff08;IPhone、Android&#xff09;和PC端&#xff08;Chrome&#xff09; 需要解决的问题&#xff1a;移动端禁止全…

vue echarts饼图环形 (随着legend动态显示数据总数)

目录 1.安装echarts 2.引入echarts 3.创建要放入echarts实例的一个盒子 4.创建echarts实例 5.随着legend动态显示数据总数 效果视频 1.安装echarts npm install echarts --save 2.引入echarts 在 当前vue文件中引入 echarts 如下图所示&#xff1a; 3.创建要放入echa…

Vue项目实战——【基于 Vue3.x + Vant UI】实现一个多功能记账本(搭建开发环境)

基于 Vue3.x Vant UI 的多功能记账本&#xff08;二&#xff09; 文章目录基于 Vue3.x Vant UI 的多功能记账本&#xff08;二&#xff09;搭建开发环境项目演示1、创建项目2、配置路由3、添加 Vant UI 组件库4、移动端 rem 配置5、添加 iconfont 文字图标库6、二次封装 Axio…

Vue实现鼠标悬浮隐藏与显示图片效果 @mouseenter 和 @mouseleave事件

前言 前端vue 有个功能是鼠标移动到指定item上显示出来一个编辑和删除的图标 鼠标悬停在列表那么需要有悬浮显示的列表编辑和删除icon 文字不好描述&#xff0c;因为是web端录屏也比较麻烦 这里用截图说明 图片说明 功能实现 之前没做过这种效果&#xff0c;问了一下我的组长…

echarts 地图和柱状图结合(在地图上显示柱状图)

如图&#xff0c;需求要做一个在地图上显示柱状图的echarts图&#xff0c;但是百度了半天&#xff0c;发现很少有人发这种例子。这个代码也是借鉴的别人的文章&#xff0c;但需求肯定不完全一致&#xff0c;那我会根据我的需求把代码和注意事项发出来并解释。&#xff08;如果有…