前端
效果图片
1.webSocket.js
import store from "@/store"
var url="http://localhost:3000".replace("https://","wss://").replace("http://","ws://")+"/websocket/"
import axios from "axios"
const webSocket={
//链接
getUser(userId){
var ws= new WebSocket(url+userId)
return new Promise((resolve,reject)=>{
//链接成功
ws.onopen=function () {
console.log("链接成功!");
resolve(200)
}
//链接失败
ws.error=function () {
console.log("连接错误")
reject(404)
}
})
},
// //发送消息
// onSend(username,SendText){
// var ws= new WebSocket(url+username)
// //链接成功
// ws.onopen=function () {
// console.log("链接成功!");
// }
// //链接失败
// ws.error=function () {
// console.log("连接错误")
// }
// setTimeout(()=>{
// ws.send(JSON.stringify(SendText))
// },200)
// },
//收到消息
onMessage(username){
var ws= new WebSocket(url+username)
//链接成功
// console.log(store);
ws.onopen=function () {
console.log("链接成功!");
//离线消息
axios.get("http://localhost:3000/Chat/sendOfflineMessage/"+localStorage.getItem("username"))
}
//链接失败
ws.error=function () {
console.log("连接错误")
}
ws.onmessage=function(msg){
store.commit("OnMsg",JSON.parse(msg.data))
console.log(JSON.parse(msg.data));
}
},
//关闭
onClose(username){
var ws= new WebSocket(url+username)
//链接成功
ws.onopen=function () {
console.log("链接成功!");
}
//链接失败
ws.error=function () {
console.log("连接错误")
}
ws.onclose=function () {
console.log("关闭链接...");
return 403
}
}
}
export default webSocket
2.websocket.vue主件
<template>
<div style="width: 1200px">
<div v-if="true">
<div>
<input type="text" v-model="userId" placeholder="请输入发信人">
<button @click="login">发信人</button>
<input type="text" v-model="sendId" placeholder="请输入收信人">
<button @click="sender">收信人</button>
</div>
<Send v-if="isFlag"></Send>
</div>
</div>
</template>
<script setup>
import Send from "./Sender/index.vue"
import Table from "@/Table/index.vue"
const userId=ref("")
const sendId=ref("")
const isFlag=ref(false)
//收信人
const login=()=>{
// webSocket.getUser(user.value)
localStorage.setItem('userId',userId.value)
isFlag.value=true
// ElMessage.error(`${userId.value}登录成功`);
// msg()
}
//发信人
const sender=()=>{
localStorage.setItem("sendId",sendId.value)
}
// webSocket.onClose(localStorage.getItem("userId"))
//表格
//样式
// const headerStyle=ref({
// 'background-color': '#F9FAFB',
// 'font-weight':'bold',
// 'color':'#333',
// "padding-left":"50px",
// "padding-right":"50px"
// })
// const CellStyle=ref({
// 'padding-left':'50px',
// 'padding-right':'50px'
// })
// const tableData=istableData(1)
</script>
<style scoped>
.bg{
width: 786px;
height: 720px;
background:#fff;
}
</style>
2.1index.vue
<template>
<div>
<div class="bg">
<div class="pt20">
<Content class="mr50 ml50"></Content>
<Send></Send>
</div>
</div>
<!-- <Emoji></Emoji> -->
</div>
</template>
<script setup>
import Content from "./Content.vue"
import Send from "./Send.vue"
import {ref} from "vue"
import emoji from "@/assets/emoji.json"
console.log(emoji);
let emojis= "😃".codePointAt(0).toString(10)
console.log(emojis);
const m=ref("😃")
</script>
<style scoped>
.bg{
width: 786px;
height: 720px;
background:#fff;
}
</style>
3.Send,vue发送文字&发送信息
<template>
<div class="">
<hr>
<div>
<div class="justify-space-between emojibag">
<div class="emoji" @click="BtnEmoji">😃</div>
<div class="emoji" @click="BtnCommonly">🐷</div>
<div class="emoji">
<UploadImg></UploadImg>
</div>|
<div class="tag">发简历</div>
<div class="tag">换微信</div>
<div class="tag">换电话</div>
<!-- 表情 -->
<div class="isemoji" v-if="isEmoji" @mouseleave="BtnIsemoji" @mouseenter="isEmoji=true">
<Emoji></Emoji>
</div>
<!-- 常用语 -->
<div class="isCommonly" v-if="isCommonly" @mouseenter="isCommonly=true" @mouseleave="BtnIsCommonly">
<Commonly></Commonly>
</div>
</div>
</div>
<div class="ml50 mr50" style="position: relative" @keyup.enter.native="BtnSend()">
<div
ref="textarea"
class="SendText"
v-html="Text"
contenteditable="true"
@click="changeData($event)"
@keyup="changeData($event)">
</div>
</div>
<div class="justify-end mr50 pointer" @click="BtnSend()">
<el-tag>发送</el-tag>
</div>
</div>
</template>
<script setup>
import Emoji from "./emoji.vue"
import Busevent from "@/assets/Busevent.js";
import emoji from "@/assets/emoji.json"
import Commonly from "./Commonly.vue";
import {beforeupload} from "./Upfile.js"
import UploadImg from "./UploadImg.vue"
import webSocket from "@/webSocket.js"
import {useStore} from "vuex"
const store=new useStore()
let message={
"createTime":new Date().toLocaleString('zh', { hour12: false }).replaceAll('/', '-'),
"headImg": "https://file-1313175594.cos.ap-guangzhou.myqcloud.com/images/d1f9c7e77ec641bbb92ac41dd22b1c1e.jpg",
"msg": "",
"sendId":localStorage.getItem("userId"), //发送者
"type": "",
"userId":localStorage.getItem("sendId"), //发给
"userIds": []
}
// beforeupload()
//textarea显示消息
const SendText=ref("")
// 移入显示表情
const isEmoji=ref(false)
const BtnEmoji=()=>{
isEmoji.value=true
}
//移出表情隐藏
const BtnIsemoji=()=>{
setTimeout(()=>{
isEmoji.value=false
},1000)
}
//选择表情
const Text=ref("") //v-html
const startOffset=ref() //光标位置
Busevent.on("emoji",e=>{
let start=SendText.value.slice(0,startOffset.value)
console.log(start);
let end=SendText.value.slice(startOffset.value)
console.log(end);
SendText.value=start+e.emoji+end
// SendText.value=e.emoji
Text.value=SendText.value
isEmoji.value=false
})
//移入显示常用语
const isCommonly=ref(false)
const BtnCommonly=()=>{
isCommonly.value=true
}
//移出常用语隐藏
const BtnIsCommonly=()=>{
setTimeout(()=>{
isCommonly.value=false
},1000)
}
//选择常用语
Busevent.on("Commonly",e=>{
// console.log(e.msg);
SendText.value=""
Text.value=""
//清空内容
let hArr = document.querySelector('.SendText');
while (hArr.hasChildNodes()) {
hArr.removeChild(hArr.firstChild);
}
SendText.value=e.msg
Text.value=SendText.value
isCommonly.value=false
BtnSend()
})
//选择图片
Busevent.on("uploadImg",e=>{
// let img=e
message.msg=e
//判断是否服务器上传图片 img图片 msg消息
message.type="img"
store.dispatch("SendMsg",message).then(res=>{
if (res.data.code==200) {
store.commit("SendMsg",message)
}
})
})
//发送消息
const msgSend=ref("")
const BtnSend=()=>{
// textarea.value.blur()
//处理表情包
SendText.value=SendText.value.replace("<div><br></div>","")
let a=[...SendText.value]
// console.log(a);
const emojiRegex = /[\u{1F600}-\u{1F64F}]/gu;
// const emojiRegex = /[\uD83C|\uD83D|\uD83E][\uDC00-\uDFFF][\u200D|\uFE0F]|[\uD83C|\uD83D|\uD83E][\uDC00-\uDFFF]|[0-9|*|#]\uFE0F\u20E3|[0-9|#]\u20E3|[\u203C-\u3299]\uFE0F\u200D|[\u203C-\u3299]\uFE0F|[\u2122-\u2B55]|\u303D|[\A9|\AE]\u3030|\uA9|\uAE|\u3030/gi;
let s=""
let m
for (const i in a) {
// console.log(a[i]);
if(emojiRegex.test(a[i])) {
m="&#"+ a[i].codePointAt(0).toString(10)+";"
s+=m
}else{
s+=a[i]
}
}
console.log(s);
msgSend.value=s
// console.log(msgSend.value);
message.type="msg"
message.msg=msgSend.value
store.dispatch("SendMsg",message).then(res=>{
if (res.data.code==200) {
store.commit("SendMsg",message)
}
})
console.log("wwwwwwww:",message);
//清空内容
let hArr = document.querySelector('.SendText');
while (hArr.hasChildNodes()) {
hArr.removeChild(hArr.firstChild);
}
SendText.value=""
Text.value=""
// console.log(Text.value);
textarea.value.focus()
}
//获取焦点
const textarea=ref(null)
nextTick(()=>{
textarea.value.focus()
})
//获取v-html内容
const changeData= (event) =>{
SendText.value=event.srcElement.innerHTML
// 获取选定对象
var selection = getSelection();
// 设置最后光标对象
var lastEditRange = selection.getRangeAt(0)
// console.log(lastEditRange);
//获取光标index
startOffset.value=lastEditRange.startOffset
}
</script>
<style scoped>
.emojibag{
width: 300px;
margin-top: 5px;
margin-left: 50px;
position: relative;
}
.tag{
font-size: 12px;
border: 1px solid rgb(208, 212, 204);
border-radius: 5px;
width: 50px;
height: 25px;
text-align: center;
line-height: 25px;
cursor: pointer;
}
.emoji{
font-size: 18px;
cursor: pointer;
}
/* 表情框 */
.isemoji{
position:absolute;
top:-230px;
left: 0px;
}
/* 常用语框 */
.isCommonly{
position: absolute;;
top: -158px;
left: 40px;
}
.BtnSend{
position: absolute;
top:10;
font-weight: 400;
font-family:'Microsoft YaHei'
}
.SendText{
outline: none;
overflow-y: scroll;
height: 60px;
width: 300px;
}
/*滚动条整体样式*/
.SendText::-webkit-scrollbar {
width: 4px; /*高宽分别对应横竖滚动条的尺寸*/
height: 4px;
}
/*滚动条里面小方块*/
.SendText::-webkit-scrollbar-thumb {
border-radius: 5px;
-webkit-box-shadow: inset 0 0 5px rgba(0,0,0,0.2);
background: rgba(0,0,0,0.2);
display: none;
}
</style>
4.发送表情
<template>
<br>
<div class="bg">
<div class="emoji">
<div v-for="(item,index) in emoji">
<!-- <el-tooltip
class="box-item"
effect="light"
placement="top"
>
<template #content>
{{item.cn}}
<br />
<div style="font-size:20px">{{item.emoji}}</div>
</template>
<div class="boxemoji" @click="BtnEmoji">{{item.emoji}}</div>
</el-tooltip> -->
<div class="boxemoji" @click="BtnEmoji(item)">{{item.emoji}}</div>
</div>
</div>
</div>
</template>
<script setup>
import emoji from "@/assets/emoji.json"
import {ref,onMounted } from "vue"
import Busevent from "@/assets/Busevent.js";
//点击按钮
const BtnEmoji=(e)=>{
// console.log(e);
Busevent.emit("emoji",e)
}
</script>
<style scoped>
.bg{
background-color:#E0EAFD;
height: 200px;
width: 300px;
overflow: hidden;
overflow-y: scroll;
border-radius: 5px;
box-shadow: inset 0 0 5px rgba(0,0,0,0.2);
}
.bg::before{
content: '';
position: absolute;
width: 0;
height: 0;
bottom: -20px;
left: 1px;
border: 10px solid;
border-color: transparent #E0EAFD transparent transparent ;
box-shadow: inset 0 0 5px rgba(0,0,0,0.2);
transform: rotate(-90deg);
}
/*滚动条整体样式*/
.bg::-webkit-scrollbar {
width: 4px; /*高宽分别对应横竖滚动条的尺寸*/
height: 4px;
}
/*滚动条里面小方块*/
.bg::-webkit-scrollbar-thumb {
border-radius: 5px;
-webkit-box-shadow: inset 0 0 5px rgba(0,0,0,0.2);
background: rgba(0,0,0,0.2);
display: none;
}
/*滚动条里面轨道*/
.bg::-webkit-scrollbar-track {
-webkit-box-shadow: inset 0 0 5px rgba(0,0,0,0.2);
border-radius: 0;
background: rgba(0,0,0,0.1);
display: none;
}
.emoji{
display: flex;
flex-wrap: wrap;
}
.boxemoji{
margin-left: 2px;
padding: 2px;
cursor: pointer;
font-size: 24px;
width: 35px;
/* border: 1px solid red; */
}
</style>
4.1emoji.json文件
{
"微笑": {
"cn": "微笑",
"zb": "Smile",
"emoji": "🙂"
},
"撇嘴": {
"cn": "撇嘴",
"zb": "Grimace",
"emoji": "😦"
},
"色": {
"cn": "色",
"zb": "Drool",
"emoji": "😍"
},
"发呆": {
"cn": "发呆",
"zb": "Scowl",
"emoji": "😍"
},
"得意": {
"cn": "得意",
"zb": "CoolGuy",
"emoji": "😎"
},
"流泪": {
"cn": "流泪",
"zb": "Sob",
"emoji": "😭"
},
"害羞": {
"cn": "害羞",
"zb": "Shy",
"emoji": "😊"
},
"闭嘴": {
"cn": "闭嘴",
"zb": "Silent",
"emoji": "😷"
},
"睡": {
"cn": "睡",
"zb": "Sleep",
"emoji": "😴"
},
"大哭] ": {
"cn": "大哭] ",
"zb": "Cry] ",
"emoji": "😡"
},
"尴尬": {
"cn": "尴尬",
"zb": "Awkward",
"emoji": "😡"
},
"发怒": {
"cn": "发怒",
"zb": "Angry",
"emoji": "😛"
},
"调皮": {
"cn": "调皮",
"zb": "Tongue",
"emoji": "😀"
},
"呲牙": {
"cn": "呲牙",
"zb": "Grin",
"emoji": "😯"
},
"惊讶": {
"cn": "惊讶",
"zb": "Surprise",
"emoji": "🙁"
},
"难过": {
"cn": "难过",
"zb": "Frown",
"emoji": "😎"
},
"酷": {
"cn": "酷",
"zb": "Ruthless",
"emoji": "😨"
},
"冷汗": {
"cn": "冷汗",
"zb": "Blush",
"emoji": "😱"
},
"抓狂": {
"cn": "抓狂",
"zb": "Scream",
"emoji": "😵"
},
"吐] ": {
"cn": "吐",
"zb": "Puke",
"emoji": "😋"
},
"愉快": {
"cn": "愉快",
"zb": "Joyful",
"emoji": "🙄"
},
"白眼": {
"cn": "白眼",
"zb": "Slight",
"emoji": "🙄"
},
"傲慢": {
"cn": "傲慢",
"zb": "Smug",
"emoji": "😋"
},
"饥饿": {
"cn": "饥饿",
"zb": "Hungry",
"emoji": "😪"
},
"困": {
"cn": "困",
"zb": "Drowsy",
"emoji": "😫"
},
"惊恐": {
"cn": "惊恐",
"zb": "Panic",
"emoji": "😓"
},
"流汗": {
"cn": "流汗",
"zb": "Sweat",
"emoji": "😃"
},
"憨笑": {
"cn": "憨笑",
"zb": "Laugh",
"emoji": "😃"
},
"悠闲] ": {
"cn": "悠闲",
"zb": "Commando",
"emoji": "😆"
},
"奋斗": {
"cn": "奋斗",
"zb": "Determined",
"emoji": "😆"
},
"咒骂": {
"cn": "咒骂",
"zb": "Scold",
"emoji": "😆"
},
"疑问": {
"cn": "疑问",
"zb": "Shocked",
"emoji": "😆"
},
"嘘": {
"cn": "嘘",
"zb": "Shhh",
"emoji": "😵"
},
"晕": {
"cn": "晕",
"zb": "Dizzy",
"emoji": "😆"
},
"疯了": {
"cn": "疯了",
"zb": "Tormented",
"emoji": "😆"
},
"衰": {
"cn": "衰",
"zb": "Toasted",
"emoji": "😆"
},
"骷髅": {
"cn": "骷髅",
"zb": "Skull",
"emoji": "💀"
},
"敲打": {
"cn": "敲打",
"zb": "Hammer",
"emoji": "😬"
},
"再见] ": {
"cn": "再见] ",
"zb": "Wave] ",
"emoji": "😘"
},
"擦汗": {
"cn": "擦汗",
"zb": "Speechless",
"emoji": "😆"
},
"抠鼻": {
"cn": "抠鼻",
"zb": "NosePick",
"emoji": "😆"
},
"鼓掌": {
"cn": "鼓掌",
"zb": "Clap",
"emoji": "👏"
},
"糗大了": {
"cn": "糗大了",
"zb": "Shame",
"emoji": "😆"
},
"坏笑": {
"cn": "坏笑",
"zb": "Trick",
"emoji": "😆"
},
"左哼哼": {
"cn": "左哼哼",
"zb": "Bah!L",
"emoji": "😆"
},
"右哼哼": {
"cn": "右哼哼",
"zb": "Bah!R",
"emoji": "😆"
},
"哈欠": {
"cn": "哈欠",
"zb": "Yawn",
"emoji": "😆"
},
"鄙视": {
"cn": "鄙视",
"zb": "Pooh-pooh",
"emoji": "😆"
},
"委屈] ": {
"cn": "委屈",
"zb": "Shrunken",
"emoji": "😆"
},
"快哭了": {
"cn": "快哭了",
"zb": "TearingUp",
"emoji": "😆"
},
"阴险": {
"cn": "阴险",
"zb": "Sly",
"emoji": "😆"
},
"亲亲": {
"cn": "亲亲",
"zb": "Kiss",
"emoji": "😘"
},
"吓": {
"cn": "吓",
"zb": "Wrath",
"emoji": "😓"
},
"可怜": {
"cn": "可怜",
"zb": "Whimper",
"emoji": "😆"
},
"菜刀": {
"cn": "菜刀",
"zb": "Cleaver",
"emoji": "🔪"
},
"西瓜": {
"cn": "西瓜",
"zb": "Watermelon",
"emoji": "🍉"
},
"啤酒": {
"cn": "啤酒",
"zb": "Beer",
"emoji": "🍺"
},
"篮球": {
"cn": "篮球",
"zb": "Basketball",
"emoji": "🏀"
},
"乒乓] ": {
"cn": "乒乓",
"zb": "PingPong",
"emoji": "⚪"
},
"咖啡": {
"cn": "咖啡",
"zb": "Coffee",
"emoji": "☕"
},
"饭": {
"cn": "饭",
"zb": "Rice",
"emoji": "🍚"
},
"猪头": {
"cn": "猪头",
"zb": "Pig",
"emoji": "🐷"
},
"玫瑰": {
"cn": "玫瑰",
"zb": "Rose",
"emoji": "🌹"
},
"凋谢": {
"cn": "凋谢",
"zb": "Wilt",
"emoji": "🌹"
},
"嘴唇": {
"cn": "嘴唇",
"zb": "Lips",
"emoji": "👄"
},
"爱心": {
"cn": "爱心",
"zb": "Heart",
"emoji": "💗"
},
"心碎": {
"cn": "心碎",
"zb": "BrokenHeart",
"emoji": "💔"
},
"蛋糕": {
"cn": "蛋糕",
"zb": "Cake",
"emoji": "🎂"
},
"闪电] ": {
"cn": "闪电] ",
"zb": "Lightning",
"emoji": "⚡"
},
"炸弹": {
"cn": "炸弹",
"zb": "Bomb",
"emoji": "💣"
},
"刀": {
"cn": "刀",
"zb": "Dagger",
"emoji": "🗡"
},
"足球": {
"cn": "足球",
"zb": "Soccer",
"emoji": "⚽"
},
"瓢虫": {
"cn": "瓢虫",
"zb": "Ladybug",
"emoji": "🐞"
},
"便便": {
"cn": "便便",
"zb": "Poop",
"emoji": "💩"
},
"月亮": {
"cn": "月亮",
"zb": "Moon",
"emoji": "🌙"
},
"太阳": {
"cn": "太阳",
"zb": "Sun",
"emoji": "☀"
},
"礼物": {
"cn": "礼物",
"zb": "Gift",
"emoji": "🎁"
},
"拥抱": {
"cn": "拥抱",
"zb": "Hug",
"emoji": "🤗"
},
"强] ": {
"cn": "强",
"zb": "ThumbsUp",
"emoji": "👍"
},
"弱": {
"cn": "弱",
"zb": "ThumbsDown",
"emoji": "👎"
},
"握手": {
"cn": "握手",
"zb": "Shake",
"emoji": "👍"
},
"胜利": {
"cn": "胜利",
"zb": "Peace",
"emoji": "✌"
},
"抱拳": {
"cn": "抱拳",
"zb": "Fight",
"emoji": "✊"
},
"勾引": {
"cn": "勾引",
"zb": "Beckon",
"emoji": "✌"
},
"拳头": {
"cn": "拳头",
"zb": "Fist",
"emoji": "✊"
},
"差劲": {
"cn": "差劲",
"zb": "Pinky",
"emoji": "✌"
},
"爱你": {
"cn": "爱你",
"zb": "RockOn",
"emoji": "✌"
},
"NO": {
"cn": "NO",
"zb": "Nuh-uh",
"emoji": "✌"
},
"OK": {
"cn": "OK",
"zb": "OK",
"emoji": "🙂"
},
"嘿哈": {
"cn": "嘿哈",
"zb": "Hey",
"emoji": "🙂"
},
"捂脸": {
"cn": "捂脸",
"zb": "Facepalm",
"emoji": "🙂"
},
"奸笑": {
"cn": "奸笑",
"zb": "Smirk",
"emoji": "🙂"
},
"机智": {
"cn": "机智",
"zb": "Smart",
"emoji": "🙂"
},
"皱眉": {
"cn": "皱眉",
"zb": "Concerned",
"emoji": "🙂"
},
"耶": {
"cn": "耶",
"zb": "Yeah!",
"emoji": "🙂"
},
"吃瓜": {
"cn": "吃瓜",
"zb": "Onlooker",
"emoji": "🙂"
},
"加油": {
"cn": "加油",
"zb": "GoForIt",
"emoji": "🙂"
},
"汗": {
"cn": "汗",
"zb": "Sweats",
"emoji": "🙂"
},
"天啊": {
"cn": "天啊",
"zb": "OMG",
"emoji": "👌"
},
"社会社会": {
"cn": "社会社会",
"zb": "Respect",
"emoji": "🙂"
},
"旺柴": {
"cn": "旺柴",
"zb": "Doge",
"emoji": "🙂"
},
"好的": {
"cn": "好的",
"zb": "NoProb",
"emoji": "🙂"
},
"哇": {
"cn": "哇",
"zb": "Wow",
"emoji": "🙂"
}
}
5.发送快捷信息Commonly.vue
<template>
<div class="bg" @mouseleave="isCommonly=0">
<div v-for="item in CommonlyArr">
<div :class="{Commonly:true,isCommonly:item.index==isCommonly}" @click="BtnSubmit(item)" @mouseenter="BtnCurrr(item)">
{{item.index}}.{{item.msg}}
</div>
</div>
</div>
</template>
<script setup>
import Busevent from "@/assets/Busevent.js";
const CommonlyArr=ref([
{index:1,msg:"嗡嗡嗡嗡嗡嗡嗡嗡嗡嗡嗡嗡嗡嗡嗡嗡嗡嗡嗡嗡嗡嗡嗡嗡嗡嗡嗡"},
{index:2,msg:"qqqqqqqqqqq"},
{index:3,msg:"rrrrrrrrrrrrrr"},
{index:4,msg:"eeeeeeeeeeeeeeee"},
{index:5,msg:"eeeeeeeeeeeeeeee"},
{index:6,msg:"eeeeeeeeeeeeeeee"},
{index:7,msg:"eeeeeeeeeeeeeeee"}
])
// 选中样式
const isCommonly=ref(null)
const BtnCurrr=(e)=>{
isCommonly.value=e.index
}
//确认
const BtnSubmit=(e)=>{
Busevent.emit("Commonly",e)
}
</script>
<style scoped>
.bg{
background-color:#E0EAFD;
height: 150px;
width: 240px;
overflow: hidden;
overflow-y: scroll;
border-radius: 5px;
box-shadow: inset 0 0 5px rgba(0,0,0,0.2);
}
.bg::before{
content: '';
position: absolute;
width: 0;
height: 0;
bottom: -20px;
left: 1px;
border: 10px solid;
border-color: transparent #E0EAFD transparent transparent ;
box-shadow: inset 0 0 5px rgba(0,0,0,0.2);
transform: rotate(-90deg);
}
/*滚动条整体样式*/
.bg::-webkit-scrollbar {
/*高宽分别对应横竖滚动条的尺寸*/
width: 0px;
height: 0px;
}
/*滚动条里面小方块*/
.bg::-webkit-scrollbar-thumb {
border-radius: 5px;
-webkit-box-shadow: inset 0 0 5px rgba(0,0,0,0.2);
background: rgba(0,0,0,0.2);
display: none;
}
/*滚动条里面轨道*/
.bg::-webkit-scrollbar-track {
-webkit-box-shadow: inset 0 0 5px rgba(0,0,0,0.2);
border-radius: 0;
background: rgba(0,0,0,0.1);
display: none;
}
.Commonly{
padding:5px 20px;
cursor: pointer;
}
.isCommonly{
background: gainsboro;
color: #fff;
}
</style>
6.发送图片
<template>
<el-upload
v-model:file-list="fileList"
class="upload-demo"
action=""
:http-request="uploadImg"
accept=".jpg,.jpeg,.png,.gif"
:show-file-list="false"
:before-upload="onChange"
>
<img src="./logo.png" alt="" srcset="" class="w30">
</el-upload>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import {uploadFile} from "./Upfile.js"
import Busevent from "@/assets/Busevent.js";
const uploadImg=(val)=>{
uploadFile(val).then(res=>{
console.log(res);
Busevent.emit("uploadImg",res)
}).catch(err=>{
Busevent.emit("uploadImg",err)
})
}
</script>
6.1上传图片
import { ElMessage } from 'element-plus'
import axios from "axios"
/**
* before-upload上传文件之前的钩子
* @param {file} file
* @param {类型} type
* @param {大小} size
*/
export const beforeupload=(file,type,size)=>{
const isType=file.type===type
const issize=file.size/1024/1024<size
if (!isType) {
ElMessage.error("上传头像图片只能是"+type+"格式!")
return false
}
if (!issize) {
ElMessage.error("上传头像图片大小不能超过"+type+"MB!")
return false
}
return true
}
/**
* 上传成功
* @param {file} file
*/
export const handleAvatarSuccess=(file)=>{
console.log(file);
// return file.data
}
/**
*
* @param {file} files
* @param {路径} url
* @param {token} token
* @returns
*/
export const uploadFile=(files,url,token)=>{
// let imgurl=url+''
//base64
let reader = new FileReader();
reader.readAsDataURL(files.file)
let base64
reader.onload=e=>{
base64=e.target.result
// console.log(base64);
}
//正常上传
let imgurl="http://xxxx"
//请求头
http.defaults.headers["Authorization"] ='Bearer'+ token;
const formData=new FormData()
formData.append("file",files.file)
return new Promise((resolve, reject) => {
http({
url:imgurl,
data:formData,
method:'post',
}).then(res=>{
resolve(res.data.data)
}).catch(err=>{
reject(base64)
})
})
}
7.Content.vue内容展示
<template>
<div class="scrollBox" >
<div style="text-align:center" class="mt10 mb10">
<!-- {{store.state.OnMsg}} -->
{{DateTime}}
</div>
<div v-for="item in Message" >
<div v-if="item.userId!=''">
<div class="row mb20" v-if="item.sendId!=userId">
<div class="headimg">
<img :src="item.headImg" alt="" class="headimg">
</div>
<div class="boxMsg">
<div v-if="item.type=='img'" class="p">
<el-image
class='el-image'
style='width: 100px'
:src='item.msg'
lazy :zoom-rate='1.2'
:preview-src-list='srcList'
@click="BtnImg(item.msg)"
fit='contain'/>
</div>
<p v-html="item.msg" v-else></p>
</div>
</div>
<!-- 我send -->
<div class="myMessage mb20" v-else>
<div class="myheadimg">
<img :src="item.headImg" alt="" class="myheadimg">
</div>
<div class="boxMsg">
<div v-if="item.type=='img'" class="p">
<el-image
class='el-image'
style='width: 100px; '
:src='item.msg'
lazy :zoom-rate='1.2'
:preview-src-list='srcList'
@click="BtnImg(item.msg)"
fit='contain'/>
</div>
<p v-html="item.msg" v-else></p>
</div>
</div>
</div>
<!-- {{msg}} -->
</div>
<!-- 默认置底 -->
</div>
</template>
<script setup>
import {ref,onMounted,watch} from "vue"
import {useStore} from "vuex"
const store=new useStore()
// const msg=ref(webSocket.onMessage(localStorage.getItem('userId')))
//预览图片
const srcList = ref([])
const BtnImg=(e)=>{
srcList.value=[]
srcList.value.push(e)
}
//type:img:图片,msg:消息,pdf:简历,phone:手机号码,wx:微信
const Message=ref(store.state.OnMsg)
//时间
const DateTime=ref(new Date().toLocaleString('zh', { hour12: false }).replaceAll('/', '-'))
</script>
<style scoped>
.scrollBox{
max-height: 480px;
min-height: 480px;
overflow: hidden;
overflow-y: scroll;
/* -webkit-box-shadow: inset 0 0 5px rgba(0,0,0,0.2); */
}
/*滚动条整体样式*/
.scrollBox::-webkit-scrollbar {
width: 4px; /*高宽分别对应横竖滚动条的尺寸*/
height: 4px;
}
/*滚动条里面小方块*/
.scrollBox::-webkit-scrollbar-thumb {
border-radius: 5px;
-webkit-box-shadow: inset 0 0 5px rgba(0,0,0,0.2);
background: rgba(0,0,0,0.2);
display: none;
}
/*滚动条里面轨道*/
.innerbox::-webkit-scrollbar-track {
-webkit-box-shadow: inset 0 0 5px rgba(0,0,0,0.2);
border-radius: 0;
background: rgba(0,0,0,0.1);
}
.boxMsg{
max-width: 350px;
background-color:#E0EAFD;
border-radius: 2px;
padding: 6px;
margin: 0px;
box-shadow: inset 0 0 5px rgba(0,0,0,0.2);
}
.myMessage{
display: flex;
flex-direction:row-reverse;
}
.headimg{
width: 35px;
height: 35px;
border-radius: 50%;
margin-right: 10px;
}
.myheadimg{
width: 35px;
height: 35px;
border-radius: 50%;
margin-left: 5px;
margin-right: 10px;
}
p,.p{
padding-top:6px;
padding-left: 3px;
padding-right: 3px;
}
</style>
后端
<!--WebSocket-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.6</version>
</dependency>
<!--热部署-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
<scope>true</scope>
</dependency>
<!--Hutool工具-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.7.9</version>
</dependency>
1.WebSocketServer
package com.appapi.websocket;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.appapi.comm.R;
import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
import io.netty.util.internal.ObjectUtil;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.support.ManagedMap;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.socket.WebSocketSession;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;
import javax.annotation.Resource;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.util.concurrent.CopyOnWriteArraySet;
/**
* @Author: 文君
* @Date: 2023-08-30-8:14
* @Description:
*/
@ServerEndpoint(value = "/websocket/{userId}")
@Component
@Slf4j
public class WebSocketServer {
//redis
@Autowired
private RedisTemplate redisTemplate;
private static Logger L= LoggerFactory.getLogger(WebSocketServer.class);
//与某个客户端的连接会话,需要通过它来给客户端发送数据
private Session session;
//用户ID
private String userId;
//concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。
//虽然@Component默认是单例模式的,但springboot还是会为每个websocket连接初始化一个bean,所以可以用一个静态set保存起来。
// 注:底下WebSocket是当前类名
private static CopyOnWriteArraySet<WebSocketServer> webSockets =new CopyOnWriteArraySet<>();
// 用来存在线连接用户信息
private static ConcurrentHashMap<String,Session> sessionPool = new ConcurrentHashMap<String,Session>();
/**
* @Description 链接建立
* @Param session
* @Param userId
* @Return
* @Author wenjun
* @Date 2023/8/30 8:20
*/
@OnOpen
public void onOpen(Session session, @PathParam("userId") String userId){
try {
this.session = session;
this.userId = userId;
webSockets.add(this);
sessionPool.put(userId, session);
log.info("【websocket消息】有新的连接,总数为:"+webSockets.size());
} catch (Exception e) {
log.info(userId+"上线的时候通知所有人发生了错误");
}
}
/**
* @Description 链接关闭
* @Param
* @Return
* @Author wenjun
* @Date 2023/8/30 8:29
*/
@OnClose
public void onClose(){
try {
webSockets.remove(this);
sessionPool.remove(this.userId);
log.info("【websocket消息】连接断开,总数为:"+webSockets.size());
System.out.println("链接关闭");
} catch (Exception e) {
}
}
/**
* @Description 接收信息
* @Param message
* @Param session
* @Return
* @Author wenjun
* @Date 2023/8/30 8:32
*/
@OnMessage
public void onMessage(String message,Session session){
log.info("【websocket消息】收到客户端消息:"+message);
// try{
// }catch (Exception e){
// e.printStackTrace();
// }
}
/**
* @Description 离线消息
* @Param userId
* @Param message
* @Return
* @Author wenjun
* @Date 2023/8/31 11:29
*/
public void sendOfflineMessage(String userId){
try {
//发送离线消息
Object[] range = redisTemplate.opsForList().range(userId, 0, -1).toArray();
Session session = sessionPool.get(userId);
int RangIndex=range.length-1;
if (session != null&&session.isOpen()) {
for (int i = 0; i <range.length; i++) {
session.getAsyncRemote().sendText(range[i].toString());
System.out.println(range[i]);
System.out.println(i<range.length);
System.out.println(RangIndex+"RangIndex");
System.out.println(i+"i");
if (i==RangIndex){
//删除离线记录
redisTemplate.delete(userId);
return;
}
}
}else {
log.info("用户:{}不在线",userId);
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* @Description 此为广播消息(全体发送)
* @Param message
* @Return
* @Author wenjun
* @Date 2023/8/30 9:17
*/
public void sendAllMessage(String message) {
log.info("【websocket消息】广播消息:"+message);
for(WebSocketServer webSocket : webSockets) {
try {
if(webSocket.session.isOpen()) {
webSocket.session.getAsyncRemote().sendText(message);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* @Description 此为单点消息
* @Param userId
* @Param message
* @Return
* @Author wenjun
* @Date 2023/8/30 9:17
*/
public void sendOneMessage(String userId, String message) {
try {
Session session = sessionPool.get(userId);
if (session != null&&session.isOpen()) {
try {
log.info("【websocket消息】 单点消息:"+message);
session.getAsyncRemote().sendText(message);
} catch (Exception e) {
e.printStackTrace();
}
}else {
log.info("用户:{}不在线",userId);
JSONObject obj = JSONObject.parseObject(message);
redisTemplate.opsForList().leftPush(userId,obj);
}
}catch (Exception e) {
e.printStackTrace();
}
}
/**
* @Description 此为单点消息(多人)
* @Param userIds
* @Param message
* @Return
* @Author wenjun
* @Date 2023/8/30 9:17
*/
public void sendMoreMessage(String[] userIds, String message) {
for(String userId:userIds) {
Session session = sessionPool.get(userId);
if (session != null&&session.isOpen()) {
try {
log.info("【websocket消息】 单点消息:"+message);
session.getAsyncRemote().sendText(message);
} catch (Exception e) {
e.printStackTrace();
}
}else {
log.info("用户:{}不在线",userId);
}
}
}
/**
* @Description 发送错误时的处理
* @Param session
* @Param error
* @Return
* @Author wenjun
* @Date 2023/8/30 8:58
*/
@OnError
public void onError(Session session, Throwable error) {
log.error("用户错误,原因:"+error.getMessage());
error.printStackTrace();
}
}
2.ChatMessage
package com.appapi.websocket;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import io.swagger.annotations.ApiOperation;
import lombok.Data;
import java.time.LocalDateTime;
import java.util.List;
/**
* @Author: 文君
* @Date: 2023-08-30-11:47
* @Description:
*/
@Data
@ApiModel(value = "ChatMessage对象", description = "消息")
public class ChatMessage {
@ApiModelProperty("收信人")
private String userId;
@ApiModelProperty("多个收信人")
private String[] userIds;
@ApiModelProperty("消息")
private String msg;
@ApiModelProperty("消息类型")
private String type;
@ApiModelProperty("用户头像")
private String headImg;
@ApiModelProperty("发信人")
private String sendId;
@ApiModelProperty("发送时间")
@JsonDeserialize(using = LocalDateTimeDeserializer.class)
@JsonSerialize(using = LocalDateTimeSerializer.class)
@TableField(value = "create_time",fill = FieldFill.INSERT)
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createTime;
}
3.ChatController
package com.appapi.websocket;
import com.alibaba.fastjson2.JSONObject;
import com.appapi.comm.R;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.websocket.server.PathParam;
import java.time.LocalDateTime;
/**
* @Author: 文君
* @Date: 2023-08-30-11:37
* @Description:
*/
@Slf4j
@RestController
@RequestMapping("/Chat")
@Api(tags = "WebSocket")
public class ChatController {
@Resource
private WebSocketServer webSocketServer;
/**
* @Description 单发信息通过id
* @Param chatMessage
* @Return {@link R< ChatMessage>}
* @Author wenjun
* @Date 2023/8/30 14:19
*/
@PostMapping("/sendOneMessage")
@ApiOperation("单发信息通过id")
public R<ChatMessage> OneMessage(@RequestBody ChatMessage chatMessage){
chatMessage.setCreateTime(LocalDateTime.now());
JSONObject obj=JSONObject.from(chatMessage);
webSocketServer.sendOneMessage(chatMessage.getUserId(),obj.toJSONString());
log.info(chatMessage.toString());
return R.success();
}
/**
* @Description 多发信息通过id
* @Param chatMessage
* @Return {@link R< ChatMessage>}
* @Author wenjun
* @Date 2023/8/30 14:21
*/
@PostMapping("/sendMoreMessage")
@ApiOperation("多发信息通过id")
public R<ChatMessage> MoreMessage(@RequestBody ChatMessage chatMessage){
chatMessage.setCreateTime(LocalDateTime.now());
JSONObject obj=JSONObject.from(chatMessage);
webSocketServer.sendMoreMessage(chatMessage.getUserIds(),obj.toJSONString());
log.info(chatMessage.toString());
return R.success();
}
/**
* @Description 全部发送
* @Param chatMessage
* @Return {@link R< ChatMessage>}
* @Author wenjun
* @Date 2023/8/31 11:35
*/
@PostMapping("/sendAllMessage")
@ApiOperation("全部发送")
public R<ChatMessage> AllMessage(@RequestBody ChatMessage chatMessage){
chatMessage.setCreateTime(LocalDateTime.now());
//实体转json
JSONObject obj=JSONObject.from(chatMessage);
webSocketServer.sendAllMessage(obj.toJSONString());
log.info(chatMessage.toString());
return R.success();
}
/**
* @Description 离线消息
* @Param userId
* @Return {@link R< ChatMessage>}
* @Author wenjun
* @Date 2023/8/31 11:35
*/
@GetMapping("/sendOfflineMessage/{userId}")
@ApiOperation("离线消息")
public R<ChatMessage> OfflineMessage(@PathVariable String userId){
webSocketServer.sendOfflineMessage(userId);
return R.success();
}
}